From c2ebd014fb0141707dc47fc76b6fce202bfa670d Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Mon, 23 Oct 2023 02:32:08 -0700 Subject: [PATCH] Uncomment some of the tests --- src/bun.js/bindings/BunString.cpp | 8 + src/bun.js/bindings/bindings.cpp | 6 +- src/bun.js/bindings/structure.cpp | 29 +- src/js/bun/sql.ts | 12 +- src/js/out/InternalModuleRegistryConstants.h | 6 +- src/sql/postgres.zig | 660 +++-- src/string.zig | 18 + test/js/sql/bootstrap.js | 34 + test/js/sql/sql.test.ts | 2525 ++++++++++++++++++ 9 files changed, 3046 insertions(+), 252 deletions(-) create mode 100644 test/js/sql/bootstrap.js create mode 100644 test/js/sql/sql.test.ts diff --git a/src/bun.js/bindings/BunString.cpp b/src/bun.js/bindings/BunString.cpp index 99fd021757..96824ee8a9 100644 --- a/src/bun.js/bindings/BunString.cpp +++ b/src/bun.js/bindings/BunString.cpp @@ -33,6 +33,14 @@ extern "C" bool BunString__fromJS(JSC::JSGlobalObject* globalObject, JSC::Encode return bunString->tag != BunStringTag::Dead; } +extern "C" bool BunString__fromJSRef(JSC::JSGlobalObject* globalObject, JSC::EncodedJSValue encodedValue, BunString* bunString) +{ + + JSC::JSValue value = JSC::JSValue::decode(encodedValue); + *bunString = Bun::toStringRef(globalObject, value); + return bunString->tag != BunStringTag::Dead; +} + extern "C" BunString BunString__createAtom(const char* bytes, size_t length) { if (simdutf::validate_ascii(bytes, length)) { diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp index bb25f67685..7901c2dcc5 100644 --- a/src/bun.js/bindings/bindings.cpp +++ b/src/bun.js/bindings/bindings.cpp @@ -97,6 +97,7 @@ #include "AsyncContextFrame.h" #include "JavaScriptCore/InternalFieldTuple.h" +#include "JavaScriptCore/JSDateMath.h" template static void copyToUWS(WebCore::FetchHeaders* headers, UWSResponse* res) @@ -4716,9 +4717,10 @@ CPP_DECL double JSC__JSValue__getUnixTimestamp(JSC__JSValue timeValue) return date->internalNumber(); } -extern "C" double WTF__parseDateFromNullTerminatedCharacters(const char* nullTerminatedChars) +extern "C" double Bun__parseDate(JSC::JSGlobalObject* globalObject, BunString* str) { - return WTF::parseDateFromNullTerminatedCharacters(nullTerminatedChars); + auto& vm = globalObject->vm(); + return vm.dateCache.parseDate(globalObject, vm, Bun::toWTFString(*str)); } extern "C" EncodedJSValue JSC__JSValue__dateInstanceFromNullTerminatedString(JSC::JSGlobalObject* globalObject, const char* nullTerminatedChars) diff --git a/src/bun.js/bindings/structure.cpp b/src/bun.js/bindings/structure.cpp index d4be66f133..dbe752d8f2 100644 --- a/src/bun.js/bindings/structure.cpp +++ b/src/bun.js/bindings/structure.cpp @@ -19,7 +19,7 @@ typedef union DataCellValue { uint8_t null_value; WTF::StringImpl* string; double number; - int32_t integer; + uint32_t integer; int64_t bigint; bool boolean; double date; @@ -30,17 +30,19 @@ typedef union DataCellValue { enum class DataCellTag : uint8_t { Null = 0, String = 1, - Integer = 2, - Bigint = 3, - Boolean = 4, - Date = 5, - Bytea = 6, - Json = 7, + Double = 2, + Integer = 3, + Bigint = 4, + Boolean = 5, + Date = 6, + Bytea = 7, + Json = 8, }; typedef struct DataCell { DataCellTag tag; DataCellValue value; + bool freeValue; } DataCell; static JSC::JSValue toJS(JSC::Structure* structure, DataCell* cells, unsigned count, JSC::JSGlobalObject* globalObject) @@ -56,9 +58,11 @@ static JSC::JSValue toJS(JSC::Structure* structure, DataCell* cells, unsigned co break; case DataCellTag::String: { object->putDirectOffset(vm, i, jsString(vm, WTF::String(cell.value.string))); - cell.value.string->deref(); break; } + case DataCellTag::Double: + object->putDirectOffset(vm, i, jsDoubleNumber(cell.value.number)); + break; case DataCellTag::Integer: object->putDirectOffset(vm, i, jsNumber(cell.value.integer)); break; @@ -82,8 +86,6 @@ static JSC::JSValue toJS(JSC::Structure* structure, DataCell* cells, unsigned co case DataCellTag::Json: { auto str = WTF::String(cell.value.string); JSC::JSValue json = JSC::JSONParse(globalObject, str); - cell.value.string->deref(); - object->putDirectOffset(vm, i, json); break; } @@ -157,13 +159,6 @@ extern "C" EncodedJSValue JSC__createEmptyObjectWithStructure(JSC::JSGlobalObjec return JSValue::encode(object); } -extern "C" void JSC__runInDeferralContext(JSC::VM* vm, void* ptr, void (*callback)(void*)) -{ - GCDeferralContext context(*vm); - JSC::DisallowGC disallowGC; - callback(ptr); -} - extern "C" void JSC__putDirectOffset(JSC::VM* vm, JSC::EncodedJSValue object, unsigned int offset, JSC::EncodedJSValue value) { JSValue::decode(object).getObject()->putDirectOffset(*vm, offset, JSValue::decode(value)); diff --git a/src/js/bun/sql.ts b/src/js/bun/sql.ts index 62e17c8627..d53c71baae 100644 --- a/src/js/bun/sql.ts +++ b/src/js/bun/sql.ts @@ -162,7 +162,17 @@ function createConnection({ hostname, port, username, password, tls, query, data function normalizeStrings(strings) { if ($isJSArray(strings)) { - return strings.join("?"); + const count = strings.length; + if (count === 0) { + return ""; + } + + var out = strings[0]; + for (var i = 1; i < count; i++) { + out += "$" + i; + out += strings[i]; + } + return out; } return strings + ""; diff --git a/src/js/out/InternalModuleRegistryConstants.h b/src/js/out/InternalModuleRegistryConstants.h index 39fda03d59..b448b48815 100644 --- a/src/js/out/InternalModuleRegistryConstants.h +++ b/src/js/out/InternalModuleRegistryConstants.h @@ -10,7 +10,7 @@ static constexpr ASCIILiteral BunFFICode = ASCIILiteral::fromLiteralUnsafe("(fun // // -static constexpr ASCIILiteral BunSqlCode = ASCIILiteral::fromLiteralUnsafe("(function (){\"use strict\";// src/js/out/tmp/bun/sql.ts\nvar createConnection = function({ hostname, port, username, password, tls, query, database }, onConnected, onClose) {\n return _createConnection(hostname, Number(port), username || \"\", password || \"\", database || \"\", tls || null, query || \"\", onConnected, onClose);\n}, normalizeStrings = function(strings) {\n if (@isJSArray(strings))\n return strings.join(\"\?\");\n return strings + \"\";\n}, loadOptions = function(o) {\n var hostname, port, username, password, database, tls, url, query, adapter;\n const env = Bun.env;\n if (typeof o === \"undefined\" || typeof o === \"string\" && o.length === 0) {\n const urlString = env.POSTGRES_URL || env.DATABASE_URL || env.PGURL || env.PG_URL;\n if (urlString)\n url = new URL(urlString), o = {};\n } else if (o && typeof o === \"object\") {\n if (o instanceof URL)\n url = o;\n else if (o\?.url) {\n const _url = o.url;\n if (typeof _url === \"string\")\n url = new URL(_url);\n else if (_url && typeof _url === \"object\" && _url instanceof URL)\n url = _url;\n }\n } else if (typeof o === \"string\")\n url = new URL(o);\n if (url) {\n if ({ hostname, port, username, password, protocol: adapter } = o = url, adapter[adapter.length - 1] === \":\")\n adapter = adapter.slice(0, -1);\n const queryObject = url.searchParams.toJSON();\n query = \"\";\n for (let key in queryObject)\n query += `${encodeURIComponent(key)}=${encodeURIComponent(queryObject[key])} `;\n query = query.trim();\n }\n if (!o)\n o = {};\n if (hostname ||= o.hostname || o.host || env.PGHOST || \"localhost\", port ||= Number(o.port || env.PGPORT || 5432), username ||= o.username || o.user || env.PGUSERNAME || env.PGUSER || env.USER || env.USERNAME || \"postgres\", database ||= o.database || o.db || (url\?.pathname \?\? \"\").slice(1) || env.PGDATABASE || username, password ||= o.password || o.pass || env.PGPASSWORD || \"\", tls ||= o.tls || o.ssl, adapter ||= o.adapter || \"postgres\", port = Number(port), !Number.isSafeInteger(port) || port < 1 || port > 65535)\n throw new Error(`Invalid port: ${port}`);\n if (adapter && !(adapter === \"postgres\" || adapter === \"postgresql\"))\n throw new Error(`Unsupported adapter: ${adapter}. Only \\\"postgres\\\" is supported for now`);\n return { hostname, port, username, password, database, tls, query };\n}, SQL = function(o) {\n var connection, connected = !1, connecting = !1, closed = !1, onConnect = [], connectionInfo = loadOptions(o);\n function connectedHandler(query, handle, err) {\n if (err)\n return query.reject(err);\n if (!connected)\n return query.reject(new Error(\"Not connected\"));\n if (query.cancelled)\n return query.reject(new Error(\"Query cancelled\"));\n handle.run(connection, query);\n }\n function pendingConnectionHandler(query, handle) {\n if (onConnect.push((err) => connectedHandler(query, handle, err)), !connecting)\n connecting = !0, connection = createConnection(connectionInfo, onConnected, onClose);\n }\n function closedConnectionHandler(query, handle) {\n query.reject(new Error(\"Connection closed\"));\n }\n function onConnected(err, result) {\n connected = !err;\n for (let handler of onConnect)\n handler(err);\n onConnect = [];\n }\n function onClose(err) {\n closed = !0, onConnected(err, @undefined);\n }\n function connectedSQL(strings, values) {\n return new Query(createQuery(normalizeStrings(strings), values), connectedHandler);\n }\n function closedSQL(strings, values) {\n return new Query(@undefined, closedConnectionHandler);\n }\n function pendingSQL(strings, values) {\n return new Query(createQuery(normalizeStrings(strings), values), pendingConnectionHandler);\n }\n function sql(strings, ...values) {\n if (closed)\n return closedSQL(strings, values);\n if (connected)\n return connectedSQL(strings, values);\n return pendingSQL(strings, values);\n }\n return sql.connect = () => {\n if (closed)\n return @Promise.reject(new Error(\"Connection closed\"));\n if (connected)\n return @Promise.resolve(sql);\n var { resolve, reject, promise } = @Promise.withResolvers();\n if (onConnect.push((err) => err \? reject(err) : resolve(sql)), !connecting)\n connecting = !0, connection = createConnection(connectionInfo, onConnected, onClose);\n return promise;\n }, sql.close = () => {\n if (closed)\n return @Promise.resolve();\n var { resolve, promise } = @Promise.withResolvers();\n return onConnect.push(resolve), connection.close(), promise;\n }, sql.flush = () => {\n if (closed || !connected)\n return;\n connection.flush();\n }, sql;\n}, queryStatus_active = 1 << 1, queryStatus_cancelled = 1 << 2, queryStatus_error = 1 << 3, queryStatus_executed = 1 << 4;\nvar _resolve = Symbol(\"resolve\"), _reject = Symbol(\"reject\"), _handle = Symbol(\"handle\"), _run = Symbol(\"run\"), _queryStatus = Symbol(\"status\"), _handler = Symbol(\"handler\"), PublicPromise = @Promise, { createConnection: _createConnection, createQuery, PostgresSQLConnection, init } = @lazy(\"bun:sql\");\n\nclass Query extends PublicPromise {\n [_resolve];\n [_reject];\n [_handle];\n [_handler];\n [_queryStatus] = 0;\n constructor(handle, handler) {\n var resolve_, reject_;\n super((resolve, reject) => {\n resolve_ = resolve, reject_ = reject;\n });\n this[_resolve] = resolve_, this[_reject] = reject_, this[_handle] = handle, this[_handler] = handler, this[_queryStatus] = handle \? 0 : queryStatus_cancelled;\n }\n async[_run]() {\n const { [_handle]: handle, [_handler]: handler, [_queryStatus]: status } = this;\n if (status & (queryStatus_executed | queryStatus_cancelled))\n return;\n return this[_queryStatus] |= queryStatus_executed, await 1, handler(this, handle);\n }\n get active() {\n return (this[_queryStatus] & queryStatus_active) !== 0;\n }\n set active(value) {\n if (this[_queryStatus] & (queryStatus_cancelled | queryStatus_error))\n return;\n if (value)\n this[_queryStatus] |= queryStatus_active;\n else\n this[_queryStatus] &= ~queryStatus_active;\n }\n get cancelled() {\n return (this[_queryStatus] & queryStatus_cancelled) !== 0;\n }\n resolve(x) {\n return this[_queryStatus] &= ~queryStatus_active, this[_handle].done(), this[_resolve](x);\n }\n reject(x) {\n return this[_queryStatus] &= ~queryStatus_active, this[_queryStatus] |= queryStatus_error, this[_handle].done(), this[_reject](x);\n }\n cancel() {\n var status = this[_queryStatus];\n if (status & queryStatus_cancelled)\n return this;\n if (this[_queryStatus] |= queryStatus_cancelled, status & queryStatus_executed)\n this[_handle].cancel();\n return this;\n }\n execute() {\n return this[_run](), this;\n }\n raw() {\n return this[_handle].raw = 2, this;\n }\n values() {\n return this[_handle].raw = 1, this;\n }\n then() {\n return this[_run](), super.@then.@apply(this, arguments);\n }\n catch() {\n return this[_run](), super.@catch.@apply(this, arguments);\n }\n finally() {\n return this[_run](), super.@finally.@apply(this, arguments);\n }\n}\nObject.defineProperty(Query, Symbol.species, {\n get() {\n return PublicPromise;\n }\n});\nObject.defineProperty(Query, Symbol.toStringTag, {\n get() {\n return \"Query\";\n }\n});\ninit(function(query, result) {\n try {\n query.resolve(result);\n } catch (e) {\n console.log(e);\n }\n}, function(query, reject) {\n try {\n query.reject(reject);\n } catch (e) {\n console.log(e);\n }\n});\nvar lazyDefaultSQL, defaultSQLObject = function sql(strings, ...values) {\n if (!lazyDefaultSQL)\n lazyDefaultSQL = SQL(@undefined), Object.assign(defaultSQLObject, lazyDefaultSQL), exportsObject.default = exportsObject.sql = lazyDefaultSQL;\n return lazyDefaultSQL(strings, ...values);\n}, exportsObject = {\n sql: defaultSQLObject,\n default: defaultSQLObject,\n SQL,\n Query\n};\nreturn exportsObject})\n"); +static constexpr ASCIILiteral BunSqlCode = ASCIILiteral::fromLiteralUnsafe("(function (){\"use strict\";// src/js/out/tmp/bun/sql.ts\nvar createConnection = function({ hostname, port, username, password, tls, query, database }, onConnected, onClose) {\n return _createConnection(hostname, Number(port), username || \"\", password || \"\", database || \"\", tls || null, query || \"\", onConnected, onClose);\n}, normalizeStrings = function(strings) {\n if (@isJSArray(strings)) {\n const count = strings.length;\n if (count === 0)\n return \"\";\n var out = strings[0];\n for (var i = 1;i < count; i++)\n out += \"$\" + i, out += strings[i];\n return out;\n }\n return strings + \"\";\n}, loadOptions = function(o) {\n var hostname, port, username, password, database, tls, url, query, adapter;\n const env = Bun.env;\n if (typeof o === \"undefined\" || typeof o === \"string\" && o.length === 0) {\n const urlString = env.POSTGRES_URL || env.DATABASE_URL || env.PGURL || env.PG_URL;\n if (urlString)\n url = new URL(urlString), o = {};\n } else if (o && typeof o === \"object\") {\n if (o instanceof URL)\n url = o;\n else if (o\?.url) {\n const _url = o.url;\n if (typeof _url === \"string\")\n url = new URL(_url);\n else if (_url && typeof _url === \"object\" && _url instanceof URL)\n url = _url;\n }\n } else if (typeof o === \"string\")\n url = new URL(o);\n if (url) {\n if ({ hostname, port, username, password, protocol: adapter } = o = url, adapter[adapter.length - 1] === \":\")\n adapter = adapter.slice(0, -1);\n const queryObject = url.searchParams.toJSON();\n query = \"\";\n for (let key in queryObject)\n query += `${encodeURIComponent(key)}=${encodeURIComponent(queryObject[key])} `;\n query = query.trim();\n }\n if (!o)\n o = {};\n if (hostname ||= o.hostname || o.host || env.PGHOST || \"localhost\", port ||= Number(o.port || env.PGPORT || 5432), username ||= o.username || o.user || env.PGUSERNAME || env.PGUSER || env.USER || env.USERNAME || \"postgres\", database ||= o.database || o.db || (url\?.pathname \?\? \"\").slice(1) || env.PGDATABASE || username, password ||= o.password || o.pass || env.PGPASSWORD || \"\", tls ||= o.tls || o.ssl, adapter ||= o.adapter || \"postgres\", port = Number(port), !Number.isSafeInteger(port) || port < 1 || port > 65535)\n throw new Error(`Invalid port: ${port}`);\n if (adapter && !(adapter === \"postgres\" || adapter === \"postgresql\"))\n throw new Error(`Unsupported adapter: ${adapter}. Only \\\"postgres\\\" is supported for now`);\n return { hostname, port, username, password, database, tls, query };\n}, SQL = function(o) {\n var connection, connected = !1, connecting = !1, closed = !1, onConnect = [], connectionInfo = loadOptions(o);\n function connectedHandler(query, handle, err) {\n if (err)\n return query.reject(err);\n if (!connected)\n return query.reject(new Error(\"Not connected\"));\n if (query.cancelled)\n return query.reject(new Error(\"Query cancelled\"));\n handle.run(connection, query);\n }\n function pendingConnectionHandler(query, handle) {\n if (onConnect.push((err) => connectedHandler(query, handle, err)), !connecting)\n connecting = !0, connection = createConnection(connectionInfo, onConnected, onClose);\n }\n function closedConnectionHandler(query, handle) {\n query.reject(new Error(\"Connection closed\"));\n }\n function onConnected(err, result) {\n connected = !err;\n for (let handler of onConnect)\n handler(err);\n onConnect = [];\n }\n function onClose(err) {\n closed = !0, onConnected(err, @undefined);\n }\n function connectedSQL(strings, values) {\n return new Query(createQuery(normalizeStrings(strings), values), connectedHandler);\n }\n function closedSQL(strings, values) {\n return new Query(@undefined, closedConnectionHandler);\n }\n function pendingSQL(strings, values) {\n return new Query(createQuery(normalizeStrings(strings), values), pendingConnectionHandler);\n }\n function sql(strings, ...values) {\n if (closed)\n return closedSQL(strings, values);\n if (connected)\n return connectedSQL(strings, values);\n return pendingSQL(strings, values);\n }\n return sql.connect = () => {\n if (closed)\n return @Promise.reject(new Error(\"Connection closed\"));\n if (connected)\n return @Promise.resolve(sql);\n var { resolve, reject, promise } = @Promise.withResolvers();\n if (onConnect.push((err) => err \? reject(err) : resolve(sql)), !connecting)\n connecting = !0, connection = createConnection(connectionInfo, onConnected, onClose);\n return promise;\n }, sql.close = () => {\n if (closed)\n return @Promise.resolve();\n var { resolve, promise } = @Promise.withResolvers();\n return onConnect.push(resolve), connection.close(), promise;\n }, sql.flush = () => {\n if (closed || !connected)\n return;\n connection.flush();\n }, sql;\n}, queryStatus_active = 1 << 1, queryStatus_cancelled = 1 << 2, queryStatus_error = 1 << 3, queryStatus_executed = 1 << 4;\nvar _resolve = Symbol(\"resolve\"), _reject = Symbol(\"reject\"), _handle = Symbol(\"handle\"), _run = Symbol(\"run\"), _queryStatus = Symbol(\"status\"), _handler = Symbol(\"handler\"), PublicPromise = @Promise, { createConnection: _createConnection, createQuery, PostgresSQLConnection, init } = @lazy(\"bun:sql\");\n\nclass Query extends PublicPromise {\n [_resolve];\n [_reject];\n [_handle];\n [_handler];\n [_queryStatus] = 0;\n constructor(handle, handler) {\n var resolve_, reject_;\n super((resolve, reject) => {\n resolve_ = resolve, reject_ = reject;\n });\n this[_resolve] = resolve_, this[_reject] = reject_, this[_handle] = handle, this[_handler] = handler, this[_queryStatus] = handle \? 0 : queryStatus_cancelled;\n }\n async[_run]() {\n const { [_handle]: handle, [_handler]: handler, [_queryStatus]: status } = this;\n if (status & (queryStatus_executed | queryStatus_cancelled))\n return;\n return this[_queryStatus] |= queryStatus_executed, await 1, handler(this, handle);\n }\n get active() {\n return (this[_queryStatus] & queryStatus_active) !== 0;\n }\n set active(value) {\n if (this[_queryStatus] & (queryStatus_cancelled | queryStatus_error))\n return;\n if (value)\n this[_queryStatus] |= queryStatus_active;\n else\n this[_queryStatus] &= ~queryStatus_active;\n }\n get cancelled() {\n return (this[_queryStatus] & queryStatus_cancelled) !== 0;\n }\n resolve(x) {\n return this[_queryStatus] &= ~queryStatus_active, this[_handle].done(), this[_resolve](x);\n }\n reject(x) {\n return this[_queryStatus] &= ~queryStatus_active, this[_queryStatus] |= queryStatus_error, this[_handle].done(), this[_reject](x);\n }\n cancel() {\n var status = this[_queryStatus];\n if (status & queryStatus_cancelled)\n return this;\n if (this[_queryStatus] |= queryStatus_cancelled, status & queryStatus_executed)\n this[_handle].cancel();\n return this;\n }\n execute() {\n return this[_run](), this;\n }\n raw() {\n return this[_handle].raw = 2, this;\n }\n values() {\n return this[_handle].raw = 1, this;\n }\n then() {\n return this[_run](), super.@then.@apply(this, arguments);\n }\n catch() {\n return this[_run](), super.@catch.@apply(this, arguments);\n }\n finally() {\n return this[_run](), super.@finally.@apply(this, arguments);\n }\n}\nObject.defineProperty(Query, Symbol.species, { value: PublicPromise });\nObject.defineProperty(Query, Symbol.toStringTag, { value: \"Query\" });\ninit(function(query, result) {\n try {\n query.resolve(result);\n } catch (e) {\n console.log(e);\n }\n}, function(query, reject) {\n try {\n query.reject(reject);\n } catch (e) {\n console.log(e);\n }\n});\nvar lazyDefaultSQL, defaultSQLObject = function sql(strings, ...values) {\n if (!lazyDefaultSQL)\n lazyDefaultSQL = SQL(@undefined), Object.assign(defaultSQLObject, lazyDefaultSQL), exportsObject.default = exportsObject.sql = lazyDefaultSQL;\n return lazyDefaultSQL(strings, ...values);\n}, exportsObject = {\n sql: defaultSQLObject,\n default: defaultSQLObject,\n SQL,\n Query\n};\nreturn exportsObject})\n"); // // @@ -263,7 +263,7 @@ static constexpr ASCIILiteral BunFFICode = ASCIILiteral::fromLiteralUnsafe("(fun // // -static constexpr ASCIILiteral BunSqlCode = ASCIILiteral::fromLiteralUnsafe("(function (){\"use strict\";// src/js/out/tmp/bun/sql.ts\nvar createConnection = function({ hostname, port, username, password, tls, query, database }, onConnected, onClose) {\n return _createConnection(hostname, Number(port), username || \"\", password || \"\", database || \"\", tls || null, query || \"\", onConnected, onClose);\n}, normalizeStrings = function(strings) {\n if (@isJSArray(strings))\n return strings.join(\"\?\");\n return strings + \"\";\n}, loadOptions = function(o) {\n var hostname, port, username, password, database, tls, url, query, adapter;\n const env = Bun.env;\n if (typeof o === \"undefined\" || typeof o === \"string\" && o.length === 0) {\n const urlString = env.POSTGRES_URL || env.DATABASE_URL || env.PGURL || env.PG_URL;\n if (urlString)\n url = new URL(urlString), o = {};\n } else if (o && typeof o === \"object\") {\n if (o instanceof URL)\n url = o;\n else if (o\?.url) {\n const _url = o.url;\n if (typeof _url === \"string\")\n url = new URL(_url);\n else if (_url && typeof _url === \"object\" && _url instanceof URL)\n url = _url;\n }\n } else if (typeof o === \"string\")\n url = new URL(o);\n if (url) {\n if ({ hostname, port, username, password, protocol: adapter } = o = url, adapter[adapter.length - 1] === \":\")\n adapter = adapter.slice(0, -1);\n const queryObject = url.searchParams.toJSON();\n query = \"\";\n for (let key in queryObject)\n query += `${encodeURIComponent(key)}=${encodeURIComponent(queryObject[key])} `;\n query = query.trim();\n }\n if (!o)\n o = {};\n if (hostname ||= o.hostname || o.host || env.PGHOST || \"localhost\", port ||= Number(o.port || env.PGPORT || 5432), username ||= o.username || o.user || env.PGUSERNAME || env.PGUSER || env.USER || env.USERNAME || \"postgres\", database ||= o.database || o.db || (url\?.pathname \?\? \"\").slice(1) || env.PGDATABASE || username, password ||= o.password || o.pass || env.PGPASSWORD || \"\", tls ||= o.tls || o.ssl, adapter ||= o.adapter || \"postgres\", port = Number(port), !Number.isSafeInteger(port) || port < 1 || port > 65535)\n throw new Error(`Invalid port: ${port}`);\n if (adapter && !(adapter === \"postgres\" || adapter === \"postgresql\"))\n throw new Error(`Unsupported adapter: ${adapter}. Only \\\"postgres\\\" is supported for now`);\n return { hostname, port, username, password, database, tls, query };\n}, SQL = function(o) {\n var connection, connected = !1, connecting = !1, closed = !1, onConnect = [], connectionInfo = loadOptions(o);\n function connectedHandler(query, handle, err) {\n if (err)\n return query.reject(err);\n if (!connected)\n return query.reject(new Error(\"Not connected\"));\n if (query.cancelled)\n return query.reject(new Error(\"Query cancelled\"));\n handle.run(connection, query);\n }\n function pendingConnectionHandler(query, handle) {\n if (onConnect.push((err) => connectedHandler(query, handle, err)), !connecting)\n connecting = !0, connection = createConnection(connectionInfo, onConnected, onClose);\n }\n function closedConnectionHandler(query, handle) {\n query.reject(new Error(\"Connection closed\"));\n }\n function onConnected(err, result) {\n connected = !err;\n for (let handler of onConnect)\n handler(err);\n onConnect = [];\n }\n function onClose(err) {\n closed = !0, onConnected(err, @undefined);\n }\n function connectedSQL(strings, values) {\n return new Query(createQuery(normalizeStrings(strings), values), connectedHandler);\n }\n function closedSQL(strings, values) {\n return new Query(@undefined, closedConnectionHandler);\n }\n function pendingSQL(strings, values) {\n return new Query(createQuery(normalizeStrings(strings), values), pendingConnectionHandler);\n }\n function sql(strings, ...values) {\n if (closed)\n return closedSQL(strings, values);\n if (connected)\n return connectedSQL(strings, values);\n return pendingSQL(strings, values);\n }\n return sql.connect = () => {\n if (closed)\n return @Promise.reject(new Error(\"Connection closed\"));\n if (connected)\n return @Promise.resolve(sql);\n var { resolve, reject, promise } = @Promise.withResolvers();\n if (onConnect.push((err) => err \? reject(err) : resolve(sql)), !connecting)\n connecting = !0, connection = createConnection(connectionInfo, onConnected, onClose);\n return promise;\n }, sql.close = () => {\n if (closed)\n return @Promise.resolve();\n var { resolve, promise } = @Promise.withResolvers();\n return onConnect.push(resolve), connection.close(), promise;\n }, sql.flush = () => {\n if (closed || !connected)\n return;\n connection.flush();\n }, sql;\n}, queryStatus_active = 1 << 1, queryStatus_cancelled = 1 << 2, queryStatus_error = 1 << 3, queryStatus_executed = 1 << 4;\nvar _resolve = Symbol(\"resolve\"), _reject = Symbol(\"reject\"), _handle = Symbol(\"handle\"), _run = Symbol(\"run\"), _queryStatus = Symbol(\"status\"), _handler = Symbol(\"handler\"), PublicPromise = @Promise, { createConnection: _createConnection, createQuery, PostgresSQLConnection, init } = @lazy(\"bun:sql\");\n\nclass Query extends PublicPromise {\n [_resolve];\n [_reject];\n [_handle];\n [_handler];\n [_queryStatus] = 0;\n constructor(handle, handler) {\n var resolve_, reject_;\n super((resolve, reject) => {\n resolve_ = resolve, reject_ = reject;\n });\n this[_resolve] = resolve_, this[_reject] = reject_, this[_handle] = handle, this[_handler] = handler, this[_queryStatus] = handle \? 0 : queryStatus_cancelled;\n }\n async[_run]() {\n const { [_handle]: handle, [_handler]: handler, [_queryStatus]: status } = this;\n if (status & (queryStatus_executed | queryStatus_cancelled))\n return;\n return this[_queryStatus] |= queryStatus_executed, await 1, handler(this, handle);\n }\n get active() {\n return (this[_queryStatus] & queryStatus_active) !== 0;\n }\n set active(value) {\n if (this[_queryStatus] & (queryStatus_cancelled | queryStatus_error))\n return;\n if (value)\n this[_queryStatus] |= queryStatus_active;\n else\n this[_queryStatus] &= ~queryStatus_active;\n }\n get cancelled() {\n return (this[_queryStatus] & queryStatus_cancelled) !== 0;\n }\n resolve(x) {\n return this[_queryStatus] &= ~queryStatus_active, this[_handle].done(), this[_resolve](x);\n }\n reject(x) {\n return this[_queryStatus] &= ~queryStatus_active, this[_queryStatus] |= queryStatus_error, this[_handle].done(), this[_reject](x);\n }\n cancel() {\n var status = this[_queryStatus];\n if (status & queryStatus_cancelled)\n return this;\n if (this[_queryStatus] |= queryStatus_cancelled, status & queryStatus_executed)\n this[_handle].cancel();\n return this;\n }\n execute() {\n return this[_run](), this;\n }\n raw() {\n return this[_handle].raw = 2, this;\n }\n values() {\n return this[_handle].raw = 1, this;\n }\n then() {\n return this[_run](), super.@then.@apply(this, arguments);\n }\n catch() {\n return this[_run](), super.@catch.@apply(this, arguments);\n }\n finally() {\n return this[_run](), super.@finally.@apply(this, arguments);\n }\n}\nObject.defineProperty(Query, Symbol.species, {\n get() {\n return PublicPromise;\n }\n});\nObject.defineProperty(Query, Symbol.toStringTag, {\n get() {\n return \"Query\";\n }\n});\ninit(function(query, result) {\n try {\n query.resolve(result);\n } catch (e) {\n console.log(e);\n }\n}, function(query, reject) {\n try {\n query.reject(reject);\n } catch (e) {\n console.log(e);\n }\n});\nvar lazyDefaultSQL, defaultSQLObject = function sql(strings, ...values) {\n if (!lazyDefaultSQL)\n lazyDefaultSQL = SQL(@undefined), Object.assign(defaultSQLObject, lazyDefaultSQL), exportsObject.default = exportsObject.sql = lazyDefaultSQL;\n return lazyDefaultSQL(strings, ...values);\n}, exportsObject = {\n sql: defaultSQLObject,\n default: defaultSQLObject,\n SQL,\n Query\n};\nreturn exportsObject})\n"); +static constexpr ASCIILiteral BunSqlCode = ASCIILiteral::fromLiteralUnsafe("(function (){\"use strict\";// src/js/out/tmp/bun/sql.ts\nvar createConnection = function({ hostname, port, username, password, tls, query, database }, onConnected, onClose) {\n return _createConnection(hostname, Number(port), username || \"\", password || \"\", database || \"\", tls || null, query || \"\", onConnected, onClose);\n}, normalizeStrings = function(strings) {\n if (@isJSArray(strings)) {\n const count = strings.length;\n if (count === 0)\n return \"\";\n var out = strings[0];\n for (var i = 1;i < count; i++)\n out += \"$\" + i, out += strings[i];\n return out;\n }\n return strings + \"\";\n}, loadOptions = function(o) {\n var hostname, port, username, password, database, tls, url, query, adapter;\n const env = Bun.env;\n if (typeof o === \"undefined\" || typeof o === \"string\" && o.length === 0) {\n const urlString = env.POSTGRES_URL || env.DATABASE_URL || env.PGURL || env.PG_URL;\n if (urlString)\n url = new URL(urlString), o = {};\n } else if (o && typeof o === \"object\") {\n if (o instanceof URL)\n url = o;\n else if (o\?.url) {\n const _url = o.url;\n if (typeof _url === \"string\")\n url = new URL(_url);\n else if (_url && typeof _url === \"object\" && _url instanceof URL)\n url = _url;\n }\n } else if (typeof o === \"string\")\n url = new URL(o);\n if (url) {\n if ({ hostname, port, username, password, protocol: adapter } = o = url, adapter[adapter.length - 1] === \":\")\n adapter = adapter.slice(0, -1);\n const queryObject = url.searchParams.toJSON();\n query = \"\";\n for (let key in queryObject)\n query += `${encodeURIComponent(key)}=${encodeURIComponent(queryObject[key])} `;\n query = query.trim();\n }\n if (!o)\n o = {};\n if (hostname ||= o.hostname || o.host || env.PGHOST || \"localhost\", port ||= Number(o.port || env.PGPORT || 5432), username ||= o.username || o.user || env.PGUSERNAME || env.PGUSER || env.USER || env.USERNAME || \"postgres\", database ||= o.database || o.db || (url\?.pathname \?\? \"\").slice(1) || env.PGDATABASE || username, password ||= o.password || o.pass || env.PGPASSWORD || \"\", tls ||= o.tls || o.ssl, adapter ||= o.adapter || \"postgres\", port = Number(port), !Number.isSafeInteger(port) || port < 1 || port > 65535)\n throw new Error(`Invalid port: ${port}`);\n if (adapter && !(adapter === \"postgres\" || adapter === \"postgresql\"))\n throw new Error(`Unsupported adapter: ${adapter}. Only \\\"postgres\\\" is supported for now`);\n return { hostname, port, username, password, database, tls, query };\n}, SQL = function(o) {\n var connection, connected = !1, connecting = !1, closed = !1, onConnect = [], connectionInfo = loadOptions(o);\n function connectedHandler(query, handle, err) {\n if (err)\n return query.reject(err);\n if (!connected)\n return query.reject(new Error(\"Not connected\"));\n if (query.cancelled)\n return query.reject(new Error(\"Query cancelled\"));\n handle.run(connection, query);\n }\n function pendingConnectionHandler(query, handle) {\n if (onConnect.push((err) => connectedHandler(query, handle, err)), !connecting)\n connecting = !0, connection = createConnection(connectionInfo, onConnected, onClose);\n }\n function closedConnectionHandler(query, handle) {\n query.reject(new Error(\"Connection closed\"));\n }\n function onConnected(err, result) {\n connected = !err;\n for (let handler of onConnect)\n handler(err);\n onConnect = [];\n }\n function onClose(err) {\n closed = !0, onConnected(err, @undefined);\n }\n function connectedSQL(strings, values) {\n return new Query(createQuery(normalizeStrings(strings), values), connectedHandler);\n }\n function closedSQL(strings, values) {\n return new Query(@undefined, closedConnectionHandler);\n }\n function pendingSQL(strings, values) {\n return new Query(createQuery(normalizeStrings(strings), values), pendingConnectionHandler);\n }\n function sql(strings, ...values) {\n if (closed)\n return closedSQL(strings, values);\n if (connected)\n return connectedSQL(strings, values);\n return pendingSQL(strings, values);\n }\n return sql.connect = () => {\n if (closed)\n return @Promise.reject(new Error(\"Connection closed\"));\n if (connected)\n return @Promise.resolve(sql);\n var { resolve, reject, promise } = @Promise.withResolvers();\n if (onConnect.push((err) => err \? reject(err) : resolve(sql)), !connecting)\n connecting = !0, connection = createConnection(connectionInfo, onConnected, onClose);\n return promise;\n }, sql.close = () => {\n if (closed)\n return @Promise.resolve();\n var { resolve, promise } = @Promise.withResolvers();\n return onConnect.push(resolve), connection.close(), promise;\n }, sql.flush = () => {\n if (closed || !connected)\n return;\n connection.flush();\n }, sql;\n}, queryStatus_active = 1 << 1, queryStatus_cancelled = 1 << 2, queryStatus_error = 1 << 3, queryStatus_executed = 1 << 4;\nvar _resolve = Symbol(\"resolve\"), _reject = Symbol(\"reject\"), _handle = Symbol(\"handle\"), _run = Symbol(\"run\"), _queryStatus = Symbol(\"status\"), _handler = Symbol(\"handler\"), PublicPromise = @Promise, { createConnection: _createConnection, createQuery, PostgresSQLConnection, init } = @lazy(\"bun:sql\");\n\nclass Query extends PublicPromise {\n [_resolve];\n [_reject];\n [_handle];\n [_handler];\n [_queryStatus] = 0;\n constructor(handle, handler) {\n var resolve_, reject_;\n super((resolve, reject) => {\n resolve_ = resolve, reject_ = reject;\n });\n this[_resolve] = resolve_, this[_reject] = reject_, this[_handle] = handle, this[_handler] = handler, this[_queryStatus] = handle \? 0 : queryStatus_cancelled;\n }\n async[_run]() {\n const { [_handle]: handle, [_handler]: handler, [_queryStatus]: status } = this;\n if (status & (queryStatus_executed | queryStatus_cancelled))\n return;\n return this[_queryStatus] |= queryStatus_executed, await 1, handler(this, handle);\n }\n get active() {\n return (this[_queryStatus] & queryStatus_active) !== 0;\n }\n set active(value) {\n if (this[_queryStatus] & (queryStatus_cancelled | queryStatus_error))\n return;\n if (value)\n this[_queryStatus] |= queryStatus_active;\n else\n this[_queryStatus] &= ~queryStatus_active;\n }\n get cancelled() {\n return (this[_queryStatus] & queryStatus_cancelled) !== 0;\n }\n resolve(x) {\n return this[_queryStatus] &= ~queryStatus_active, this[_handle].done(), this[_resolve](x);\n }\n reject(x) {\n return this[_queryStatus] &= ~queryStatus_active, this[_queryStatus] |= queryStatus_error, this[_handle].done(), this[_reject](x);\n }\n cancel() {\n var status = this[_queryStatus];\n if (status & queryStatus_cancelled)\n return this;\n if (this[_queryStatus] |= queryStatus_cancelled, status & queryStatus_executed)\n this[_handle].cancel();\n return this;\n }\n execute() {\n return this[_run](), this;\n }\n raw() {\n return this[_handle].raw = 2, this;\n }\n values() {\n return this[_handle].raw = 1, this;\n }\n then() {\n return this[_run](), super.@then.@apply(this, arguments);\n }\n catch() {\n return this[_run](), super.@catch.@apply(this, arguments);\n }\n finally() {\n return this[_run](), super.@finally.@apply(this, arguments);\n }\n}\nObject.defineProperty(Query, Symbol.species, { value: PublicPromise });\nObject.defineProperty(Query, Symbol.toStringTag, { value: \"Query\" });\ninit(function(query, result) {\n try {\n query.resolve(result);\n } catch (e) {\n console.log(e);\n }\n}, function(query, reject) {\n try {\n query.reject(reject);\n } catch (e) {\n console.log(e);\n }\n});\nvar lazyDefaultSQL, defaultSQLObject = function sql(strings, ...values) {\n if (!lazyDefaultSQL)\n lazyDefaultSQL = SQL(@undefined), Object.assign(defaultSQLObject, lazyDefaultSQL), exportsObject.default = exportsObject.sql = lazyDefaultSQL;\n return lazyDefaultSQL(strings, ...values);\n}, exportsObject = {\n sql: defaultSQLObject,\n default: defaultSQLObject,\n SQL,\n Query\n};\nreturn exportsObject})\n"); // // @@ -517,7 +517,7 @@ static constexpr ASCIILiteral BunFFICode = ASCIILiteral::fromLiteralUnsafe("(fun // // -static constexpr ASCIILiteral BunSqlCode = ASCIILiteral::fromLiteralUnsafe("(function (){\"use strict\";// src/js/out/tmp/bun/sql.ts\nvar createConnection = function({ hostname, port, username, password, tls, query, database }, onConnected, onClose) {\n return _createConnection(hostname, Number(port), username || \"\", password || \"\", database || \"\", tls || null, query || \"\", onConnected, onClose);\n}, normalizeStrings = function(strings) {\n if (@isJSArray(strings))\n return strings.join(\"\?\");\n return strings + \"\";\n}, loadOptions = function(o) {\n var hostname, port, username, password, database, tls, url, query, adapter;\n const env = Bun.env;\n if (typeof o === \"undefined\" || typeof o === \"string\" && o.length === 0) {\n const urlString = env.POSTGRES_URL || env.DATABASE_URL || env.PGURL || env.PG_URL;\n if (urlString)\n url = new URL(urlString), o = {};\n } else if (o && typeof o === \"object\") {\n if (o instanceof URL)\n url = o;\n else if (o\?.url) {\n const _url = o.url;\n if (typeof _url === \"string\")\n url = new URL(_url);\n else if (_url && typeof _url === \"object\" && _url instanceof URL)\n url = _url;\n }\n } else if (typeof o === \"string\")\n url = new URL(o);\n if (url) {\n if ({ hostname, port, username, password, protocol: adapter } = o = url, adapter[adapter.length - 1] === \":\")\n adapter = adapter.slice(0, -1);\n const queryObject = url.searchParams.toJSON();\n query = \"\";\n for (let key in queryObject)\n query += `${encodeURIComponent(key)}=${encodeURIComponent(queryObject[key])} `;\n query = query.trim();\n }\n if (!o)\n o = {};\n if (hostname ||= o.hostname || o.host || env.PGHOST || \"localhost\", port ||= Number(o.port || env.PGPORT || 5432), username ||= o.username || o.user || env.PGUSERNAME || env.PGUSER || env.USER || env.USERNAME || \"postgres\", database ||= o.database || o.db || (url\?.pathname \?\? \"\").slice(1) || env.PGDATABASE || username, password ||= o.password || o.pass || env.PGPASSWORD || \"\", tls ||= o.tls || o.ssl, adapter ||= o.adapter || \"postgres\", port = Number(port), !Number.isSafeInteger(port) || port < 1 || port > 65535)\n throw new Error(`Invalid port: ${port}`);\n if (adapter && !(adapter === \"postgres\" || adapter === \"postgresql\"))\n throw new Error(`Unsupported adapter: ${adapter}. Only \\\"postgres\\\" is supported for now`);\n return { hostname, port, username, password, database, tls, query };\n}, SQL = function(o) {\n var connection, connected = !1, connecting = !1, closed = !1, onConnect = [], connectionInfo = loadOptions(o);\n function connectedHandler(query, handle, err) {\n if (err)\n return query.reject(err);\n if (!connected)\n return query.reject(new Error(\"Not connected\"));\n if (query.cancelled)\n return query.reject(new Error(\"Query cancelled\"));\n handle.run(connection, query);\n }\n function pendingConnectionHandler(query, handle) {\n if (onConnect.push((err) => connectedHandler(query, handle, err)), !connecting)\n connecting = !0, connection = createConnection(connectionInfo, onConnected, onClose);\n }\n function closedConnectionHandler(query, handle) {\n query.reject(new Error(\"Connection closed\"));\n }\n function onConnected(err, result) {\n connected = !err;\n for (let handler of onConnect)\n handler(err);\n onConnect = [];\n }\n function onClose(err) {\n closed = !0, onConnected(err, @undefined);\n }\n function connectedSQL(strings, values) {\n return new Query(createQuery(normalizeStrings(strings), values), connectedHandler);\n }\n function closedSQL(strings, values) {\n return new Query(@undefined, closedConnectionHandler);\n }\n function pendingSQL(strings, values) {\n return new Query(createQuery(normalizeStrings(strings), values), pendingConnectionHandler);\n }\n function sql(strings, ...values) {\n if (closed)\n return closedSQL(strings, values);\n if (connected)\n return connectedSQL(strings, values);\n return pendingSQL(strings, values);\n }\n return sql.connect = () => {\n if (closed)\n return @Promise.reject(new Error(\"Connection closed\"));\n if (connected)\n return @Promise.resolve(sql);\n var { resolve, reject, promise } = @Promise.withResolvers();\n if (onConnect.push((err) => err \? reject(err) : resolve(sql)), !connecting)\n connecting = !0, connection = createConnection(connectionInfo, onConnected, onClose);\n return promise;\n }, sql.close = () => {\n if (closed)\n return @Promise.resolve();\n var { resolve, promise } = @Promise.withResolvers();\n return onConnect.push(resolve), connection.close(), promise;\n }, sql.flush = () => {\n if (closed || !connected)\n return;\n connection.flush();\n }, sql;\n}, queryStatus_active = 1 << 1, queryStatus_cancelled = 1 << 2, queryStatus_error = 1 << 3, queryStatus_executed = 1 << 4;\nvar _resolve = Symbol(\"resolve\"), _reject = Symbol(\"reject\"), _handle = Symbol(\"handle\"), _run = Symbol(\"run\"), _queryStatus = Symbol(\"status\"), _handler = Symbol(\"handler\"), PublicPromise = @Promise, { createConnection: _createConnection, createQuery, PostgresSQLConnection, init } = @lazy(\"bun:sql\");\n\nclass Query extends PublicPromise {\n [_resolve];\n [_reject];\n [_handle];\n [_handler];\n [_queryStatus] = 0;\n constructor(handle, handler) {\n var resolve_, reject_;\n super((resolve, reject) => {\n resolve_ = resolve, reject_ = reject;\n });\n this[_resolve] = resolve_, this[_reject] = reject_, this[_handle] = handle, this[_handler] = handler, this[_queryStatus] = handle \? 0 : queryStatus_cancelled;\n }\n async[_run]() {\n const { [_handle]: handle, [_handler]: handler, [_queryStatus]: status } = this;\n if (status & (queryStatus_executed | queryStatus_cancelled))\n return;\n return this[_queryStatus] |= queryStatus_executed, await 1, handler(this, handle);\n }\n get active() {\n return (this[_queryStatus] & queryStatus_active) !== 0;\n }\n set active(value) {\n if (this[_queryStatus] & (queryStatus_cancelled | queryStatus_error))\n return;\n if (value)\n this[_queryStatus] |= queryStatus_active;\n else\n this[_queryStatus] &= ~queryStatus_active;\n }\n get cancelled() {\n return (this[_queryStatus] & queryStatus_cancelled) !== 0;\n }\n resolve(x) {\n return this[_queryStatus] &= ~queryStatus_active, this[_handle].done(), this[_resolve](x);\n }\n reject(x) {\n return this[_queryStatus] &= ~queryStatus_active, this[_queryStatus] |= queryStatus_error, this[_handle].done(), this[_reject](x);\n }\n cancel() {\n var status = this[_queryStatus];\n if (status & queryStatus_cancelled)\n return this;\n if (this[_queryStatus] |= queryStatus_cancelled, status & queryStatus_executed)\n this[_handle].cancel();\n return this;\n }\n execute() {\n return this[_run](), this;\n }\n raw() {\n return this[_handle].raw = 2, this;\n }\n values() {\n return this[_handle].raw = 1, this;\n }\n then() {\n return this[_run](), super.@then.@apply(this, arguments);\n }\n catch() {\n return this[_run](), super.@catch.@apply(this, arguments);\n }\n finally() {\n return this[_run](), super.@finally.@apply(this, arguments);\n }\n}\nObject.defineProperty(Query, Symbol.species, {\n get() {\n return PublicPromise;\n }\n});\nObject.defineProperty(Query, Symbol.toStringTag, {\n get() {\n return \"Query\";\n }\n});\ninit(function(query, result) {\n try {\n query.resolve(result);\n } catch (e) {\n console.log(e);\n }\n}, function(query, reject) {\n try {\n query.reject(reject);\n } catch (e) {\n console.log(e);\n }\n});\nvar lazyDefaultSQL, defaultSQLObject = function sql(strings, ...values) {\n if (!lazyDefaultSQL)\n lazyDefaultSQL = SQL(@undefined), Object.assign(defaultSQLObject, lazyDefaultSQL), exportsObject.default = exportsObject.sql = lazyDefaultSQL;\n return lazyDefaultSQL(strings, ...values);\n}, exportsObject = {\n sql: defaultSQLObject,\n default: defaultSQLObject,\n SQL,\n Query\n};\nreturn exportsObject})\n"); +static constexpr ASCIILiteral BunSqlCode = ASCIILiteral::fromLiteralUnsafe("(function (){\"use strict\";// src/js/out/tmp/bun/sql.ts\nvar createConnection = function({ hostname, port, username, password, tls, query, database }, onConnected, onClose) {\n return _createConnection(hostname, Number(port), username || \"\", password || \"\", database || \"\", tls || null, query || \"\", onConnected, onClose);\n}, normalizeStrings = function(strings) {\n if (@isJSArray(strings)) {\n const count = strings.length;\n if (count === 0)\n return \"\";\n var out = strings[0];\n for (var i = 1;i < count; i++)\n out += \"$\" + i, out += strings[i];\n return out;\n }\n return strings + \"\";\n}, loadOptions = function(o) {\n var hostname, port, username, password, database, tls, url, query, adapter;\n const env = Bun.env;\n if (typeof o === \"undefined\" || typeof o === \"string\" && o.length === 0) {\n const urlString = env.POSTGRES_URL || env.DATABASE_URL || env.PGURL || env.PG_URL;\n if (urlString)\n url = new URL(urlString), o = {};\n } else if (o && typeof o === \"object\") {\n if (o instanceof URL)\n url = o;\n else if (o\?.url) {\n const _url = o.url;\n if (typeof _url === \"string\")\n url = new URL(_url);\n else if (_url && typeof _url === \"object\" && _url instanceof URL)\n url = _url;\n }\n } else if (typeof o === \"string\")\n url = new URL(o);\n if (url) {\n if ({ hostname, port, username, password, protocol: adapter } = o = url, adapter[adapter.length - 1] === \":\")\n adapter = adapter.slice(0, -1);\n const queryObject = url.searchParams.toJSON();\n query = \"\";\n for (let key in queryObject)\n query += `${encodeURIComponent(key)}=${encodeURIComponent(queryObject[key])} `;\n query = query.trim();\n }\n if (!o)\n o = {};\n if (hostname ||= o.hostname || o.host || env.PGHOST || \"localhost\", port ||= Number(o.port || env.PGPORT || 5432), username ||= o.username || o.user || env.PGUSERNAME || env.PGUSER || env.USER || env.USERNAME || \"postgres\", database ||= o.database || o.db || (url\?.pathname \?\? \"\").slice(1) || env.PGDATABASE || username, password ||= o.password || o.pass || env.PGPASSWORD || \"\", tls ||= o.tls || o.ssl, adapter ||= o.adapter || \"postgres\", port = Number(port), !Number.isSafeInteger(port) || port < 1 || port > 65535)\n throw new Error(`Invalid port: ${port}`);\n if (adapter && !(adapter === \"postgres\" || adapter === \"postgresql\"))\n throw new Error(`Unsupported adapter: ${adapter}. Only \\\"postgres\\\" is supported for now`);\n return { hostname, port, username, password, database, tls, query };\n}, SQL = function(o) {\n var connection, connected = !1, connecting = !1, closed = !1, onConnect = [], connectionInfo = loadOptions(o);\n function connectedHandler(query, handle, err) {\n if (err)\n return query.reject(err);\n if (!connected)\n return query.reject(new Error(\"Not connected\"));\n if (query.cancelled)\n return query.reject(new Error(\"Query cancelled\"));\n handle.run(connection, query);\n }\n function pendingConnectionHandler(query, handle) {\n if (onConnect.push((err) => connectedHandler(query, handle, err)), !connecting)\n connecting = !0, connection = createConnection(connectionInfo, onConnected, onClose);\n }\n function closedConnectionHandler(query, handle) {\n query.reject(new Error(\"Connection closed\"));\n }\n function onConnected(err, result) {\n connected = !err;\n for (let handler of onConnect)\n handler(err);\n onConnect = [];\n }\n function onClose(err) {\n closed = !0, onConnected(err, @undefined);\n }\n function connectedSQL(strings, values) {\n return new Query(createQuery(normalizeStrings(strings), values), connectedHandler);\n }\n function closedSQL(strings, values) {\n return new Query(@undefined, closedConnectionHandler);\n }\n function pendingSQL(strings, values) {\n return new Query(createQuery(normalizeStrings(strings), values), pendingConnectionHandler);\n }\n function sql(strings, ...values) {\n if (closed)\n return closedSQL(strings, values);\n if (connected)\n return connectedSQL(strings, values);\n return pendingSQL(strings, values);\n }\n return sql.connect = () => {\n if (closed)\n return @Promise.reject(new Error(\"Connection closed\"));\n if (connected)\n return @Promise.resolve(sql);\n var { resolve, reject, promise } = @Promise.withResolvers();\n if (onConnect.push((err) => err \? reject(err) : resolve(sql)), !connecting)\n connecting = !0, connection = createConnection(connectionInfo, onConnected, onClose);\n return promise;\n }, sql.close = () => {\n if (closed)\n return @Promise.resolve();\n var { resolve, promise } = @Promise.withResolvers();\n return onConnect.push(resolve), connection.close(), promise;\n }, sql.flush = () => {\n if (closed || !connected)\n return;\n connection.flush();\n }, sql;\n}, queryStatus_active = 1 << 1, queryStatus_cancelled = 1 << 2, queryStatus_error = 1 << 3, queryStatus_executed = 1 << 4;\nvar _resolve = Symbol(\"resolve\"), _reject = Symbol(\"reject\"), _handle = Symbol(\"handle\"), _run = Symbol(\"run\"), _queryStatus = Symbol(\"status\"), _handler = Symbol(\"handler\"), PublicPromise = @Promise, { createConnection: _createConnection, createQuery, PostgresSQLConnection, init } = @lazy(\"bun:sql\");\n\nclass Query extends PublicPromise {\n [_resolve];\n [_reject];\n [_handle];\n [_handler];\n [_queryStatus] = 0;\n constructor(handle, handler) {\n var resolve_, reject_;\n super((resolve, reject) => {\n resolve_ = resolve, reject_ = reject;\n });\n this[_resolve] = resolve_, this[_reject] = reject_, this[_handle] = handle, this[_handler] = handler, this[_queryStatus] = handle \? 0 : queryStatus_cancelled;\n }\n async[_run]() {\n const { [_handle]: handle, [_handler]: handler, [_queryStatus]: status } = this;\n if (status & (queryStatus_executed | queryStatus_cancelled))\n return;\n return this[_queryStatus] |= queryStatus_executed, await 1, handler(this, handle);\n }\n get active() {\n return (this[_queryStatus] & queryStatus_active) !== 0;\n }\n set active(value) {\n if (this[_queryStatus] & (queryStatus_cancelled | queryStatus_error))\n return;\n if (value)\n this[_queryStatus] |= queryStatus_active;\n else\n this[_queryStatus] &= ~queryStatus_active;\n }\n get cancelled() {\n return (this[_queryStatus] & queryStatus_cancelled) !== 0;\n }\n resolve(x) {\n return this[_queryStatus] &= ~queryStatus_active, this[_handle].done(), this[_resolve](x);\n }\n reject(x) {\n return this[_queryStatus] &= ~queryStatus_active, this[_queryStatus] |= queryStatus_error, this[_handle].done(), this[_reject](x);\n }\n cancel() {\n var status = this[_queryStatus];\n if (status & queryStatus_cancelled)\n return this;\n if (this[_queryStatus] |= queryStatus_cancelled, status & queryStatus_executed)\n this[_handle].cancel();\n return this;\n }\n execute() {\n return this[_run](), this;\n }\n raw() {\n return this[_handle].raw = 2, this;\n }\n values() {\n return this[_handle].raw = 1, this;\n }\n then() {\n return this[_run](), super.@then.@apply(this, arguments);\n }\n catch() {\n return this[_run](), super.@catch.@apply(this, arguments);\n }\n finally() {\n return this[_run](), super.@finally.@apply(this, arguments);\n }\n}\nObject.defineProperty(Query, Symbol.species, { value: PublicPromise });\nObject.defineProperty(Query, Symbol.toStringTag, { value: \"Query\" });\ninit(function(query, result) {\n try {\n query.resolve(result);\n } catch (e) {\n console.log(e);\n }\n}, function(query, reject) {\n try {\n query.reject(reject);\n } catch (e) {\n console.log(e);\n }\n});\nvar lazyDefaultSQL, defaultSQLObject = function sql(strings, ...values) {\n if (!lazyDefaultSQL)\n lazyDefaultSQL = SQL(@undefined), Object.assign(defaultSQLObject, lazyDefaultSQL), exportsObject.default = exportsObject.sql = lazyDefaultSQL;\n return lazyDefaultSQL(strings, ...values);\n}, exportsObject = {\n sql: defaultSQLObject,\n default: defaultSQLObject,\n SQL,\n Query\n};\nreturn exportsObject})\n"); // // diff --git a/src/sql/postgres.zig b/src/sql/postgres.zig index abb464aafe..d97043a53e 100644 --- a/src/sql/postgres.zig +++ b/src/sql/postgres.zig @@ -153,6 +153,10 @@ pub const protocol = struct { pub fn write(this: LengthWriter) anyerror!void { try this.context.pwrite(&Int32(this.context.offset() - this.index), this.index); } + + pub fn writeExcludingSelf(this: LengthWriter) anyerror!void { + try this.context.pwrite(&Int32(this.context.offset() -| (this.index + 4)), this.index); + } }; pub inline fn length(this: @This()) anyerror!LengthWriter { @@ -180,6 +184,10 @@ pub const protocol = struct { try this.write(std.mem.asBytes(&@byteSwap(@as(u64, @bitCast(value))))); } + pub fn @"f32"(this: @This(), value: f32) !void { + try this.write(std.mem.asBytes(&@byteSwap(@as(u32, @bitCast(value))))); + } + pub fn short(this: @This(), value: PostgresShort) !void { try this.write(std.mem.asBytes(&@byteSwap(value))); } @@ -674,6 +682,29 @@ pub const protocol = struct { } pub const decode = decoderWrap(ErrorResponse, decodeInternal).decode; + + pub fn toJS(this: ErrorResponse, globalObject: *JSC.JSGlobalObject) JSC.JSValue { + var b = bun.StringBuilder{}; + defer b.deinit(bun.default_allocator); + + for (this.messages.items) |msg| { + b.cap += switch (msg) { + inline else => |m| m.utf8ByteLength(), + } + 1; + } + b.allocate(bun.default_allocator) catch {}; + + for (this.messages.items) |msg| { + var str = switch (msg) { + inline else => |m| m.toUTF8(bun.default_allocator), + }; + defer str.deinit(); + _ = b.append(str.slice()); + _ = b.append("\n"); + } + + return globalObject.createSyntaxErrorInstance("Postgres error occurred\n{s}", .{b.allocatedSlice()[0..b.len]}); + } }; pub const PortalOrPreparedStatement = union(enum) { @@ -778,6 +809,8 @@ pub const protocol = struct { } }; + pub const null_int32 = 4294967295; + pub const DataRow = struct { pub fn decode(context: anytype, comptime ContextType: type, reader: NewReader(ContextType), comptime forEach: fn (@TypeOf(context), index: u32, bytes: ?*Data) anyerror!bool) anyerror!void { var remaining_bytes = try reader.length(); @@ -789,6 +822,9 @@ pub const protocol = struct { const byte_length = try reader.int32(); switch (byte_length) { 0 => break, + null_int32 => { + if (!try forEach(context, @intCast(index), null)) break; + }, else => { var bytes = try reader.bytes(@intCast(byte_length)); if (!try forEach(context, @intCast(index), &bytes)) break; @@ -1332,11 +1368,18 @@ pub const types = struct { number = 0, json = 114, boolean = 16, - date = 1184, - datetime = 1114, + timestamptz = 1184, + timestamp = 1114, time = 1082, bytea = 17, - bigint = 20, + int64 = 20, + timetz = 1266, + double = 701, + float = 700, + int32 = 23, + + /// numeric(precision, decimal), arbitrary precision number + numeric = 1700, _, fn toJSWithType( @@ -1346,7 +1389,7 @@ pub const types = struct { value: Type, ) anyerror!JSC.JSValue { switch (tag) { - .number => { + .numeric => { return number.toJS(globalObject, value); }, @@ -1358,7 +1401,7 @@ pub const types = struct { return boolean.toJS(globalObject, value); }, - .date => { + .timestamp, .timestamptz => { return date.toJS(globalObject, value); }, @@ -1366,10 +1409,14 @@ pub const types = struct { return bytea.toJS(globalObject, value); }, - .bigint => { + .int64 => { return JSC.JSValue.fromInt64NoTruncate(globalObject, value); }, + .int32 => { + return number.toJS(globalObject, value); + }, + else => { return string.toJS(globalObject, value); }, @@ -1396,7 +1443,7 @@ pub const types = struct { } if (tag == .JSDate) { - return .date; + return .timestamp; } if (tag.isTypedArray()) { @@ -1404,7 +1451,7 @@ pub const types = struct { } if (tag == .HeapBigInt) { - return .bigint; + return .int64; } if (tag.isArrayLike() and value.getLength(globalObject) > 0) { @@ -1416,8 +1463,12 @@ pub const types = struct { } } + if (value.isInt32()) { + return .int32; + } + if (value.isNumber()) { - return .number; + return .double; } if (value.isBoolean()) { @@ -1581,11 +1632,13 @@ pub const PostgresSQLQuery = struct { status: Status = Status.pending, is_done: bool = false, ref_count: u32 = 1, + binary: bool = false, pub usingnamespace JSC.Codegen.JSPostgresSQLQuery; pub const Status = enum(u8) { pending, + written, running, success, fail, @@ -1640,6 +1693,23 @@ pub const PostgresSQLQuery = struct { const function = vm.rareData().postgresql_context.onQueryResolveFn.get().?; globalObject.queueMicrotask(function, &[_]JSC.JSValue{targetValue}); } + pub fn onWriteFail(this: *@This(), err: anyerror, globalObject: *JSC.JSGlobalObject) void { + this.status = .fail; + + const thisValue = this.thisValue; + const targetValue = this.target.trySwap() orelse JSC.JSValue.zero; + if (thisValue == .zero or targetValue == .zero) { + return; + } + + const instance = globalObject.createTypeErrorInstance("Failed to bind query: {s}", .{@errorName(err)}); + + // TODO: error handling + var vm = JSC.VirtualMachine.get(); + const function = vm.rareData().postgresql_context.onQueryRejectFn.get().?; + globalObject.queueMicrotask(function, &[_]JSC.JSValue{ targetValue, instance }); + } + pub fn onError(this: *@This(), err: protocol.ErrorResponse, globalObject: *JSC.JSGlobalObject) void { this.status = .fail; defer this.deref(); @@ -1649,31 +1719,11 @@ pub const PostgresSQLQuery = struct { if (thisValue == .zero or targetValue == .zero) { return; } - var b = bun.StringBuilder{}; - for (err.messages.items) |msg| { - b.cap += switch (msg) { - inline else => |m| m.utf8ByteLength(), - } + 1; - } - b.allocate(bun.default_allocator) catch {}; - for (err.messages.items) |msg| { - var str = switch (msg) { - inline else => |m| m.toUTF8(bun.default_allocator), - }; - defer str.deinit(); - _ = b.append(str.slice()); - _ = b.append("\n"); - } - const instance = globalObject.createSyntaxErrorInstance("Postgres error occurred\n{s}", .{b.allocatedSlice()[0..b.len]}); - - b.deinit(bun.default_allocator); - - defer this.deref(); // TODO: error handling var vm = JSC.VirtualMachine.get(); const function = vm.rareData().postgresql_context.onQueryRejectFn.get().?; - globalObject.queueMicrotask(function, &[_]JSC.JSValue{ targetValue, instance }); + globalObject.queueMicrotask(function, &[_]JSC.JSValue{ targetValue, err.toJS(globalObject) }); } pub fn onSuccess(this: *@This(), _: []const u8, globalObject: *JSC.JSGlobalObject) void { @@ -1786,39 +1836,68 @@ pub const PostgresSQLQuery = struct { signature.deinit(); return .zero; }; - if (entry.found_existing) { - this.statement = entry.value_ptr.*; - this.statement.?.ref(); - signature.deinit(); - PostgresRequest.bindAndExecute(globalObject, this.statement.?, binding_value, PostgresSQLConnection.Writer, writer) catch |err| { - globalObject.throwError(err, "failed to bind and execute query"); + const has_params = signature.fields.len > 0; + var did_write = false; - return .zero; - }; - } else { - PostgresRequest.prepareAndQueryWithSignature(globalObject, query_str.slice(), binding_value, PostgresSQLConnection.Writer, writer, &signature) catch |err| { - globalObject.throwError(err, "failed to prepare and query"); + enqueue: { + if (entry.found_existing) { + this.statement = entry.value_ptr.*; + this.statement.?.ref(); signature.deinit(); - return .zero; - }; - var stmt = bun.default_allocator.create(PostgresSQLStatement) catch |err| { - globalObject.throwError(err, "failed to allocate statement"); - return .zero; - }; + if (has_params and this.statement.?.status == .parsing) { + // if it has params, we need to wait for ParamDescription to be received before we can write the data + } else { + this.binary = this.statement.?.fields.len > 0; - stmt.* = .{ - .signature = signature, - .ref_count = 2, - }; - this.statement = stmt; - entry.value_ptr.* = stmt; + PostgresRequest.bindAndExecute(globalObject, this.statement.?, binding_value, PostgresSQLConnection.Writer, writer) catch |err| { + globalObject.throwError(err, "failed to bind and execute query"); + + return .zero; + }; + did_write = true; + } + + break :enqueue; + } + + // If it does not have params, we can write and execute immediately in one go + if (!has_params) { + PostgresRequest.prepareAndQueryWithSignature(globalObject, query_str.slice(), binding_value, PostgresSQLConnection.Writer, writer, &signature) catch |err| { + globalObject.throwError(err, "failed to prepare and query"); + signature.deinit(); + return .zero; + }; + did_write = true; + } else { + PostgresRequest.writeQuery(query_str.slice(), signature.name, signature.fields, PostgresSQLConnection.Writer, writer) catch |err| { + globalObject.throwError(err, "failed to write query"); + signature.deinit(); + return .zero; + }; + writer.write(&protocol.Sync) catch |err| { + globalObject.throwError(err, "failed to flush"); + signature.deinit(); + return .zero; + }; + } + + { + var stmt = bun.default_allocator.create(PostgresSQLStatement) catch |err| { + globalObject.throwError(err, "failed to allocate statement"); + return .zero; + }; + + stmt.* = .{ .signature = signature, .ref_count = 2, .status = PostgresSQLStatement.Status.parsing }; + this.statement = stmt; + entry.value_ptr.* = stmt; + } } connection.requests.writeItem(this) catch {}; this.ref(); - this.status = .running; + this.status = if (did_write) .running else .pending; if (connection.is_ready_for_query) connection.flushData(); @@ -1860,27 +1939,43 @@ pub const PostgresRequest = struct { var iter = JSC.JSArrayIterator.init(values_array, globalObject); if (iter.len > 0) { - try writer.short(@intCast(iter.len)); + var needs_to_write_format_codes: bool = true; + var consecutive_zero_count: u32 = 0; while (iter.next()) |value| { - if (value.isUndefinedOrNull()) { - try writer.short(0); - continue; - } - const tag = try types.Tag.fromJS(globalObject, value); switch (tag) { - .bytea, .number => { - try writer.short(0); + // For now, we only support binary parameters when they're bytea + // This is because postgres needs the exact types in the database + // It's relatively safe to assume that Buffer/TypedArray input is bytea + .bytea => { + if (needs_to_write_format_codes) { + needs_to_write_format_codes = false; + try writer.short(@truncate(iter.len)); + } + + for (0..consecutive_zero_count) |_| { + try writer.short(0); + } + consecutive_zero_count = 0; + try writer.short(1); }, else => { - try writer.short(1); + consecutive_zero_count += 1; }, } } - try writer.short(@intCast(iter.len)); + if (needs_to_write_format_codes) { + try writer.short(0); + } else { + for (0..consecutive_zero_count) |_| { + try writer.short(0); + } + } + + try writer.short(@truncate(iter.len)); } else { try writer.short(0); try writer.short(0); @@ -1893,55 +1988,58 @@ pub const PostgresRequest = struct { while (iter.next()) |value| { if (value.isUndefinedOrNull()) { debug(" -> NULL", .{}); - try writer.int32(4); - try writer.null(); + try writer.int32(@bitCast(@as(i32, -1))); continue; } const tag = try types.Tag.fromJS(globalObject, value); switch (tag) { - .number => { - debug(" -> {s}", .{@tagName(tag)}); - if (value.isInt32()) { - try writer.int32(4); - try writer.int32(value.to(int32)); - } else { - try writer.int32(8); - try writer.f64(value.coerceToDouble(globalObject)); - } - }, .json => { debug(" -> {s}", .{@tagName(tag)}); var str = bun.String.empty; + defer str.deref(); value.jsonStringify(globalObject, 0, &str); - try writer.String(str); + const slice = str.toUTF8WithoutRef(bun.default_allocator); + defer slice.deinit(); + const l = try writer.length(); + try writer.write(slice.slice()); + try l.writeExcludingSelf(); }, .boolean => { debug(" -> {s}", .{@tagName(tag)}); + + const l = try writer.length(); try writer.boolean(value.toBoolean()); - try writer.write(&[_]u8{0}); + try l.writeExcludingSelf(); }, - .time, .datetime, .date => { + .time, .timestamp, .timestamptz => { debug(" -> {s}", .{@tagName(tag)}); var buf = std.mem.zeroes([28]u8); const str = value.toISOString(globalObject, &buf); - try writer.string(str); + const l = try writer.length(); + try writer.write(str); + try l.writeExcludingSelf(); }, .bytea => { var bytes: []const u8 = ""; if (value.asArrayBuffer(globalObject)) |buf| { bytes = buf.byteSlice(); } - try writer.int32(@intCast(bytes.len)); + const l = try writer.length(); debug(" -> {s}: {d}", .{ @tagName(tag), bytes.len }); - try writer.bytes(bytes); + try writer.write(bytes); + try l.writeExcludingSelf(); }, else => { - debug(" -> string", .{}); - // TODO: check if this leaks - var str = value.toBunString(globalObject); - try writer.String(str); + debug(" -> {s}", .{@tagName(tag)}); + const str = String.fromJSRef(value, globalObject); + defer str.deref(); + const slice = str.toUTF8WithoutRef(bun.default_allocator); + defer slice.deinit(); + const l = try writer.length(); + try writer.write(slice.slice()); + try l.writeExcludingSelf(); }, } } @@ -1949,7 +2047,7 @@ pub const PostgresRequest = struct { var any_non_text_fields: bool = false; for (result_fields) |field| { if (switch (@as(types.Tag, @enumFromInt(field.type_oid))) { - .bigint, .number => true, + .int32, .double, .float, .bytea, .number => true, else => false, }) { any_non_text_fields = true; @@ -1962,7 +2060,7 @@ pub const PostgresRequest = struct { for (result_fields) |field| { try writer.short( switch (@as(types.Tag, @enumFromInt(field.type_oid))) { - .bigint, .number => 1, + .int32, .double, .float, .bytea, .number => 1, else => 0, }, ); @@ -2049,7 +2147,7 @@ pub const PostgresRequest = struct { comptime Context: type, writer: protocol.NewWriter(Context), ) !void { - try writeBind(statement.signature.name, bun.String.empty, globalObject, array_value, &.{}, Context, writer); + try writeBind(statement.signature.name, bun.String.empty, globalObject, array_value, statement.fields, Context, writer); var exec = protocol.Execute{ .p = .{ .prepared_statement = statement.signature.name, @@ -2245,19 +2343,6 @@ pub const PostgresSQLConnection = struct { } pub fn onData(this: *PostgresSQLConnection, data: []const u8) void { - const Deferrer = struct { - data: []const u8, - this: *PostgresSQLConnection, - - pub fn run(d: *@This()) callconv(.C) void { - _onData(d.this, d.data); - } - }; - var deferrer = Deferrer{ .data = data, .this = this }; - this.globalObject.vm().runInDeferralContext(&deferrer, @ptrCast(&Deferrer.run)); - } - - fn _onData(this: *PostgresSQLConnection, data: []const u8) void { var vm = this.globalObject.bunVM(); defer vm.drainMicrotasks(); if (this.read_buffer.remaining().len == 0) { @@ -2267,20 +2352,20 @@ pub const PostgresSQLConnection = struct { PostgresRequest.onData(this, protocol.StackReader, reader) catch |err| { if (err == error.ShortRead) { if (comptime bun.Environment.allow_assert) { - if (@errorReturnTrace()) |trace| { - debug("Received short read: last_message_start: {d}, head: {d}, len: {d}\n{}", .{ - offset, - consumed, - data.len, - trace, - }); - } else { - debug("Received short read: last_message_start: {d}, head: {d}, len: {d}", .{ - offset, - consumed, - data.len, - }); - } + // if (@errorReturnTrace()) |trace| { + // debug("Received short read: last_message_start: {d}, head: {d}, len: {d}\n{}", .{ + // offset, + // consumed, + // data.len, + // trace, + // }); + // } else { + debug("Received short read: last_message_start: {d}, head: {d}, len: {d}", .{ + offset, + consumed, + data.len, + }); + // } } this.read_buffer.head = 0; @@ -2314,20 +2399,20 @@ pub const PostgresSQLConnection = struct { } if (comptime bun.Environment.allow_assert) { - if (@errorReturnTrace()) |trace| { - debug("Received short read: last_message_start: {d}, head: {d}, len: {d}\n{}", .{ - this.last_message_start, - this.read_buffer.head, - this.read_buffer.byte_list.len, - trace, - }); - } else { - debug("Received short read: last_message_start: {d}, head: {d}, len: {d}", .{ - this.last_message_start, - this.read_buffer.head, - this.read_buffer.byte_list.len, - }); - } + // if (@errorReturnTrace()) |trace| { + // debug("Received short read: last_message_start: {d}, head: {d}, len: {d}\n{}", .{ + // this.last_message_start, + // this.read_buffer.head, + // this.read_buffer.byte_list.len, + // trace, + // }); + // } else { + debug("Received short read: last_message_start: {d}, head: {d}, len: {d}", .{ + this.last_message_start, + this.read_buffer.head, + this.read_buffer.byte_list.len, + }); + // } } return; @@ -2633,71 +2718,11 @@ pub const PostgresSQLConnection = struct { }; } - const CellPutter = struct { - object: JSC.JSValue, - vm: *JSC.VM, - globalObject: *JSC.JSGlobalObject, - fields: []const protocol.FieldDescription, - - pub fn put(this: *const CellPutter, index: u32, optional_bytes: ?*Data) anyerror!bool { - const putDirectOffset = JSC.JSObject.putDirectOffset; - var bytes_ = optional_bytes orelse { - putDirectOffset(this.object, this.vm, index, JSC.JSValue.jsNull()); - return true; - }; - defer bytes_.deinit(); - const bytes = bytes_.slice(); - const object = this.object; - object.ensureStillAlive(); - - switch (@as(types.Tag, @enumFromInt(this.fields[index].type_oid))) { - .number => { - switch (bytes.len) { - 0 => { - putDirectOffset(object, this.vm, index, JSC.JSValue.jsNull()); - }, - 2 => { - putDirectOffset(object, this.vm, index, JSC.JSValue.jsNumber(@as(int32, @as(short, @bitCast(bytes[0..2].*))))); - }, - 4 => { - putDirectOffset(object, this.vm, index, JSC.JSValue.jsNumber(@as(int32, @bitCast(bytes[0..4].*)))); - }, - else => { - var eight: usize = 0; - @memcpy(@as(*[8]u8, @ptrCast(&eight))[0..bytes.len], bytes[0..@min(8, bytes.len)]); - eight = @byteSwap(eight); - putDirectOffset(object, this.vm, index, JSC.JSValue.jsNumber(@as(f64, @bitCast(eight)))); - }, - } - }, - .json => { - var str = bun.String.create(bytes); - defer str.deref(); - putDirectOffset(object, this.vm, index, str.toJSForParseJSON(this.globalObject)); - }, - .boolean => { - putDirectOffset(object, this.vm, index, JSC.JSValue.jsBoolean(bytes.len > 0 and bytes[0] == 't')); - }, - .time, .datetime, .date => { - putDirectOffset(object, this.vm, index, JSC.JSValue.fromDateString(this.globalObject, bytes_.sliceZ())); - }, - .bytea => { - putDirectOffset(object, this.vm, index, JSC.ArrayBuffer.createBuffer(this.globalObject, bytes)); - }, - else => { - var str = bun.String.create(bytes); - defer str.deref(); - putDirectOffset(object, this.vm, index, str.toJS(this.globalObject)); - }, - } - return true; - } - }; - pub const DataCell = extern struct { tag: Tag, value: Value, + free_value: bool = false, pub const Tag = enum(u8) { null = 0, @@ -2715,7 +2740,7 @@ pub const PostgresSQLConnection = struct { null: u8, string: bun.WTF.StringImpl, double: f64, - int32: i32, + int32: u32, int64: i64, boolean: bool, date: f64, @@ -2724,6 +2749,8 @@ pub const PostgresSQLConnection = struct { }; pub fn deinit(this: *DataCell) void { + if (!this.free_value) return; + switch (this.tag) { .string => { this.value.string.deref(); @@ -2731,69 +2758,191 @@ pub const PostgresSQLConnection = struct { .json => { this.value.json.deref(); }, + .bytea => { + if (this.value.bytea[1] == 0) return; + const slice = @as([*]u8, @ptrFromInt(this.value.bytea[0]))[0..this.value.bytea[1]]; + bun.default_allocator.free(slice); + }, + else => {}, } } - extern fn WTF__parseDateFromNullTerminatedCharacters([*:0]const u8) f64; - pub const Putter = struct { list: []DataCell, fields: []const protocol.FieldDescription, + binary: bool = false, + count: usize = 0, + globalObject: *JSC.JSGlobalObject, extern fn JSC__constructObjectFromDataCell(*JSC.JSGlobalObject, JSC.JSValue, JSC.JSValue, [*]DataCell, u32) JSC.JSValue; pub fn toJS(this: *Putter, globalObject: *JSC.JSGlobalObject, array: JSC.JSValue, structure: JSC.JSValue) JSC.JSValue { return JSC__constructObjectFromDataCell(globalObject, array, structure, this.list.ptr, @truncate(this.fields.len)); } + pub fn parseBinary(comptime tag: types.Tag, comptime ReturnType: type, bytes: []const u8) !ReturnType { + switch (comptime tag) { + .double => { + return @as(f64, @bitCast(try parseBinary(.int64, i64, bytes))); + }, + .int64 => { + // pq_getmsgfloat8 + if (bytes.len != 8) return error.InvalidBinaryData; + return @byteSwap(@as(i64, @bitCast(bytes[0..8].*))); + }, + .int32 => { + // pq_getmsgint + switch (bytes.len) { + 1 => { + return @as(u32, @as(u8, @bitCast(bytes[0..1].*))); + }, + 2 => { + return @as(u32, @byteSwap(@as(u16, @bitCast(bytes[0..2].*)))); + }, + 4 => { + return @byteSwap(@as(u32, @bitCast(bytes[0..4].*))); + }, + else => { + return error.UnsupportedIntegerSize; + }, + } + }, + .float => { + // pq_getmsgfloat4 + return @as(f32, @bitCast(try parseBinary(.int32, u32, bytes))); + }, + else => @compileError("TODO"), + } + } + pub fn put(this: *Putter, index: u32, optional_bytes: ?*Data) anyerror!bool { var bytes_ = optional_bytes orelse { this.list[index] = DataCell{ .tag = .null, .value = .{ .null = 0 } }; + this.count += 1; return true; }; const bytes = bytes_.slice(); - switch (@as(types.Tag, @enumFromInt(this.fields[index].type_oid))) { - .number => { - switch (bytes.len) { - 0 => { - this.list[index] = DataCell{ .tag = .null, .value = .{ .null = 0 } }; - }, - 2 => { - this.list[index] = DataCell{ .tag = .int32, .value = .{ .int32 = @as(i32, @as(short, @bitCast(bytes[0..2].*))) } }; - }, - 4 => { - this.list[index] = DataCell{ .tag = .int32, .value = .{ .int32 = @as(i32, @bitCast(bytes[0..4].*)) } }; - }, - else => { - var eight: i64 = 0; - @memcpy(@as(*[8]u8, @ptrCast(&eight))[0..bytes.len], bytes[0..@min(8, bytes.len)]); - eight = @byteSwap(eight); - this.list[index] = DataCell{ .tag = .int64, .value = .{ .int64 = eight } }; - }, + const oid = this.fields[index].type_oid; + + switch (@as(types.Tag, @enumFromInt(oid))) { + .int32 => { + if (this.binary) { + this.list[index] = DataCell{ .tag = .int32, .value = .{ .int32 = try parseBinary(.int32, u32, bytes) } }; + } else { + this.list[index] = DataCell{ .tag = .int32, .value = .{ .int32 = bun.fmt.parseInt(u32, bytes, 0) catch 0 } }; + } + }, + .double => { + if (this.binary and bytes.len == 8) { + this.list[index] = DataCell{ .tag = .double, .value = .{ .double = try parseBinary(.double, f64, bytes) } }; + } else { + const double: f64 = bun.parseDouble(bytes) catch std.math.nan(f64); + this.list[index] = DataCell{ .tag = .double, .value = .{ .double = double } }; + } + }, + .float => { + if (this.binary and bytes.len == 4) { + this.list[index] = DataCell{ .tag = .double, .value = .{ .double = try parseBinary(.float, f32, bytes) } }; + } else { + const float: f64 = bun.parseDouble(bytes) catch std.math.nan(f64); + this.list[index] = DataCell{ .tag = .double, .value = .{ .double = float } }; } }, .json => { - this.list[index] = DataCell{ .tag = .json, .value = .{ .json = bun.String.create(bytes).value.WTFStringImpl } }; + this.list[index] = DataCell{ .tag = .json, .value = .{ .json = bun.String.create(bytes).value.WTFStringImpl }, .free_value = true }; }, .boolean => { this.list[index] = DataCell{ .tag = .boolean, .value = .{ .boolean = bytes.len > 0 and bytes[0] == 't' } }; }, - .time, .datetime, .date => { - this.list[index] = DataCell{ .tag = .date, .value = .{ .date = WTF__parseDateFromNullTerminatedCharacters(bytes_.sliceZ().ptr) } }; + .time, .timestamp, .timestamptz => { + var str = bun.String.init(bytes); + defer str.deref(); + this.list[index] = DataCell{ .tag = .date, .value = .{ .date = str.parseDate(this.globalObject) } }; }, .bytea => { - this.list[index] = DataCell{ .tag = .bytea, .value = .{ .bytea = .{ @intFromPtr(bytes_.slice().ptr), bytes.len } } }; + if (this.binary) { + this.list[index] = DataCell{ .tag = .bytea, .value = .{ .bytea = .{ @intFromPtr(bytes_.slice().ptr), bytes.len } } }; + } else { + if (bun.strings.hasPrefixComptime(bytes, "\\x")) { + const hex = bytes[2..]; + const len = hex.len / 2; + var buf = try bun.default_allocator.alloc(u8, len); + errdefer bun.default_allocator.free(buf); + + this.list[index] = DataCell{ + .tag = .bytea, + .value = .{ + .bytea = .{ + @intFromPtr(buf.ptr), + try bun.strings.decodeHexToBytes(buf, u8, hex), + }, + }, + .free_value = true, + }; + } else { + return error.UnsupportedByteaFormat; + } + } }, else => { - this.list[index] = DataCell{ .tag = .string, .value = .{ .string = bun.String.create(bytes).value.WTFStringImpl } }; + this.list[index] = DataCell{ .tag = .string, .value = .{ .string = bun.String.create(bytes).value.WTFStringImpl }, .free_value = true }; }, } + this.count += 1; return true; } }; }; + fn advance(this: *PostgresSQLConnection) !void { + defer this.updateRef(); + + while (this.requests.readableLength() > 0) { + var req: *PostgresSQLQuery = this.requests.peekItem(0); + switch (req.status) { + .pending => { + var stmt = req.statement orelse return error.ExpectedStatement; + if (stmt.status == .failed) { + req.onError(stmt.error_response, this.globalObject); + this.requests.discard(1); + } else { + break; + } + }, + .success, .fail => { + this.requests.discard(1); + req.deref(); + }, + else => break, + } + } + + while (this.requests.readableLength() > 0) { + var req: *PostgresSQLQuery = this.requests.peekItem(0); + var stmt = req.statement orelse return error.ExpectedStatement; + + switch (stmt.status) { + .prepared => { + if (req.status == .pending and stmt.status == .prepared) { + const binding_value = PostgresSQLQuery.bindingGetCached(req.thisValue) orelse .zero; + PostgresRequest.bindAndExecute(this.globalObject, stmt, binding_value, PostgresSQLConnection.Writer, this.writer()) catch |err| { + req.onWriteFail(err, this.globalObject); + req.deref(); + this.requests.discard(1); + continue; + }; + req.status = .running; + req.binary = stmt.fields.len > 0; + } else { + break; + } + }, + else => break, + } + } + } + pub fn on(this: *PostgresSQLConnection, comptime MessageType: @Type(.EnumLiteral), comptime Context: type, reader: protocol.NewReader(Context)) !void { debug("on({s})", .{@tagName(MessageType)}); if (comptime MessageType != .ReadyForQuery) { @@ -2807,9 +2956,20 @@ pub const PostgresSQLConnection = struct { var structure = statement.structure(this.js_value, this.globalObject); std.debug.assert(!structure.isEmptyOrUndefinedOrNull()); + var putter = DataCell.Putter{ + .list = &.{}, + .fields = statement.fields, + .binary = request.binary, + .globalObject = this.globalObject, + }; var stack_buf: [64]DataCell = undefined; var cells: []DataCell = stack_buf[0..@min(statement.fields.len, stack_buf.len)]; + defer { + for (cells[0..putter.count]) |*cell| { + cell.deinit(); + } + } var free_cells = false; defer if (free_cells) bun.default_allocator.free(cells); @@ -2817,11 +2977,7 @@ pub const PostgresSQLConnection = struct { cells = try bun.default_allocator.alloc(DataCell, statement.fields.len); free_cells = true; } - - var putter = DataCell.Putter{ - .list = cells, - .fields = statement.fields, - }; + putter.list = cells; try protocol.DataRow.decode( &putter, @@ -2863,6 +3019,8 @@ pub const PostgresSQLConnection = struct { this.is_ready_for_query = true; this.socket.setTimeout(300); + try this.advance(); + this.flushData(); }, .CommandComplete => { @@ -2893,6 +3051,9 @@ pub const PostgresSQLConnection = struct { var request = this.current() orelse return error.ExpectedRequest; var statement = request.statement orelse return error.ExpectedStatement; statement.parameters = description.parameters; + if (statement.status == .parsing) { + statement.status = .prepared; + } }, .RowDescription => { var description: protocol.RowDescription = undefined; @@ -2922,10 +3083,43 @@ pub const PostgresSQLConnection = struct { .ErrorResponse => { var err: protocol.ErrorResponse = undefined; try err.decodeInternal(Context, reader); - defer { - err.deinit(); + + if (this.status == .connecting) { + this.status = .failed; + defer { + err.deinit(); + this.poll_ref.unref(this.globalObject.bunVM()); + this.updateHasPendingActivity(); + } + + const on_connect = this.on_connect.swap(); + if (on_connect == .zero) return; + const js_value = this.js_value; + js_value.ensureStillAlive(); + this.globalObject.queueMicrotask(on_connect, &[_]JSC.JSValue{ err.toJS(this.globalObject), js_value }); + + // it shouldn't enqueue any requests while connecting + std.debug.assert(this.requests.count == 0); + return; } + var request = this.current() orelse return error.ExpectedRequest; + var is_error_owned = true; + defer { + if (is_error_owned) { + err.deinit(); + } + } + if (request.statement) |stmt| { + if (stmt.status == PostgresSQLStatement.Status.parsing) { + stmt.status = PostgresSQLStatement.Status.failed; + stmt.error_response = err; + is_error_owned = false; + if (this.statements.remove(bun.hash(stmt.signature.name))) { + stmt.deref(); + } + } + } _ = this.requests.discard(1); this.updateRef(); @@ -3010,6 +3204,14 @@ pub const PostgresSQLStatement = struct { fields: []const protocol.FieldDescription = &[_]protocol.FieldDescription{}, parameters: []const int32 = &[_]int32{}, signature: Signature, + status: Status = Status.parsing, + error_response: protocol.ErrorResponse = .{}, + + pub const Status = enum { + parsing, + prepared, + failed, + }; pub fn ref(this: *@This()) void { std.debug.assert(this.ref_count > 0); this.ref_count += 1; @@ -3035,6 +3237,7 @@ pub const PostgresSQLStatement = struct { bun.default_allocator.free(this.fields); bun.default_allocator.free(this.parameters); this.cached_structure.deinit(); + this.error_response.deinit(); this.signature.deinit(); bun.default_allocator.destroy(this); } @@ -3076,7 +3279,6 @@ const Signature = struct { pub fn hash(this: *const Signature) u64 { var hasher = std.hash.Wyhash.init(0); - defer hasher.deinit(); hasher.update(this.name); hasher.update(std.mem.sliceAsBytes(this.fields)); return hasher.final(); @@ -3103,16 +3305,16 @@ const Signature = struct { } const tag = try types.Tag.fromJS(globalObject, value); - try fields.append(@byteSwap(@intFromEnum(tag))); + try fields.append(0); switch (tag) { .number => try name.appendSlice(".number"), .json => try name.appendSlice(".json"), .boolean => try name.appendSlice(".boolean"), - .date => try name.appendSlice(".date"), - .datetime => try name.appendSlice(".datetime"), + .timestamp => try name.appendSlice(".timestamp"), + .timestamptz => try name.appendSlice(".timestamptz"), .time => try name.appendSlice(".time"), .bytea => try name.appendSlice(".bytea"), - .bigint => try name.appendSlice(".bigint"), + .int64 => try name.appendSlice(".int64"), else => try name.appendSlice(".string"), } } diff --git a/src/string.zig b/src/string.zig index 6fe8190136..4eb0bdb042 100644 --- a/src/string.zig +++ b/src/string.zig @@ -487,6 +487,17 @@ pub const String = extern struct { } } + pub fn fromJSRef(value: bun.JSC.JSValue, globalObject: *JSC.JSGlobalObject) String { + JSC.markBinding(@src()); + + var out: String = String.dead; + if (BunString__fromJSRef(globalObject, value, &out)) { + return out; + } else { + return String.dead; + } + } + pub fn tryFromJS(value: bun.JSC.JSValue, globalObject: *JSC.JSGlobalObject) ?String { JSC.markBinding(@src()); @@ -690,11 +701,18 @@ pub const String = extern struct { }; } + extern fn Bun__parseDate(*JSC.JSGlobalObject, *String) f64; extern fn BunString__fromJS(globalObject: *JSC.JSGlobalObject, value: bun.JSC.JSValue, out: *String) bool; + extern fn BunString__fromJSRef(globalObject: *JSC.JSGlobalObject, value: bun.JSC.JSValue, out: *String) bool; extern fn BunString__toJS(globalObject: *JSC.JSGlobalObject, in: *String) JSC.JSValue; extern fn BunString__toJSWithLength(globalObject: *JSC.JSGlobalObject, in: *String, usize) JSC.JSValue; extern fn BunString__toWTFString(this: *String) void; + pub fn parseDate(this: *String, globalObject: *JSC.JSGlobalObject) f64 { + JSC.markBinding(@src()); + return Bun__parseDate(globalObject, this); + } + pub fn ref(this: String) void { switch (this.tag) { .WTFStringImpl => this.value.WTFStringImpl.ref(), diff --git a/test/js/sql/bootstrap.js b/test/js/sql/bootstrap.js new file mode 100644 index 0000000000..c25a8a5683 --- /dev/null +++ b/test/js/sql/bootstrap.js @@ -0,0 +1,34 @@ +import { spawnSync } from "child_process"; + +exec("dropdb", ["bun_sql_test"]); + +// Needs to have a server.crt file https://www.postgresql.org/docs/current/ssl-tcp.html#SSL-CERTIFICATE-CREATION +// exec('psql', ['-c', 'alter system set ssl=on']) +exec("psql", ["-c", "drop user bun_sql_test"]); +exec("psql", ["-c", "create user bun_sql_test"]); +exec("psql", ["-c", "alter system set password_encryption=md5"]); +exec("psql", ["-c", "select pg_reload_conf()"]); +exec("psql", ["-c", "drop user if exists bun_sql_test_md5"]); +exec("psql", ["-c", "create user bun_sql_test_md5 with password 'bun_sql_test_md5'"]); +exec("psql", ["-c", "alter system set password_encryption='scram-sha-256'"]); +exec("psql", ["-c", "select pg_reload_conf()"]); +exec("psql", ["-c", "drop user if exists bun_sql_test_scram"]); +exec("psql", ["-c", "create user bun_sql_test_scram with password 'bun_sql_test_scram'"]); + +exec("createdb", ["bun_sql_test"]); +exec("psql", ["-c", "grant all on database bun_sql_test to bun_sql_test"]); +exec("psql", ["-c", "alter database bun_sql_test owner to bun_sql_test"]); + +export function exec(cmd, args) { + const { stderr } = spawnSync(cmd, args, { stdio: "pipe", encoding: "utf8" }); + if (stderr && !stderr.includes("already exists") && !stderr.includes("does not exist")) throw stderr; +} + +async function execAsync(cmd, args) { + // eslint-disable-line + let stderr = ""; + const cp = await spawn(cmd, args, { stdio: "pipe", encoding: "utf8" }); // eslint-disable-line + cp.stderr.on("data", x => (stderr += x)); + await new Promise(x => cp.on("exit", x)); + if (stderr && !stderr.includes("already exists") && !stderr.includes("does not exist")) throw new Error(stderr); +} diff --git a/test/js/sql/sql.test.ts b/test/js/sql/sql.test.ts new file mode 100644 index 0000000000..6f954077bf --- /dev/null +++ b/test/js/sql/sql.test.ts @@ -0,0 +1,2525 @@ +import "./bootstrap.js"; +import net from "net"; +import fs from "fs"; +import crypto from "crypto"; + +import { sql } from "bun:sql"; +import { expect, test as t } from "bun:test"; + +process.env.DATABASE_URL = "postgres://bun_sql_test@localhost:5432/bun_sql_test"; +const delay = ms => Bun.sleep(ms); +// const rel = x => new URL(x, import.meta.url) + +// const login = { +// user: 'postgres_js_test' +// } + +// const login_md5 = { +// user: 'postgres_js_test_md5', +// pass: 'postgres_js_test_md5' +// } + +// const login_scram = { +// user: 'postgres_js_test_scram', +// pass: 'postgres_js_test_scram' +// } + +// const options = { +// db: 'postgres_js_test', +// user: login.user, +// pass: login.pass, +// idle_timeout, +// connect_timeout: 1, +// max: 1 +// } + +// t('Connects with no options', async() => { +// const sql = postgres({ max: 1 }) + +// const result = (await sql`select 1 as x`)[0].x +// await sql.end() + +// return [1, result] +// }) + +// t('Uses default database without slash', async() => { +// const sql = postgres('postgres://localhost') +// return [sql.options.user, sql.options.database] +// }) + +// t('Uses default database with slash', async() => { +// const sql = postgres('postgres://localhost/') +// return [sql.options.user, sql.options.database] +// }) + +// console.log(sql`select 1`) +t("Result is array", async () => { + expect(await sql`select 1`).toBeArray(); +}); + +// t("Result has command", async () => expect((await sql`select 1`).command).toBe("SELECT")); + +// t.only("Create table", async () => { +// await sql`create table test(int int)`; +// await sql`drop table test`; +// }); + +// t.only("Drop table", async () => { +// await sql`create table test(int int)`; +// await sql`drop table test`; +// // Verify that table is dropped +// const result = await sql`select * from pg_catalog.pg_tables where tablename = 'test'`; +// expect(result).toBeArrayOfSize(0); +// }); + +t("null", async () => { + expect((await sql`select ${null} as x`)[0].x).toBeNull(); +}); + +t("Integer", async () => { + expect((await sql`select ${1} as x`)[0].x).toBe(1); +}); + +t("String", async () => { + expect((await sql`select ${"hello"} as x`)[0].x).toBe("hello"); +}); + +t("Boolean false", async () => [false, (await sql`select ${false} as x`)[0].x]); + +t("Boolean true", async () => [true, (await sql`select ${true} as x`)[0].x]); + +t("Date", async () => { + const now = new Date(); + const then = (await sql`select ${now}::timestamp as x`)[0].x; + expect(then).toEqual(now); +}); + +// t("Json", async () => { +// const x = (await sql`select ${sql.json({ a: "hello", b: 42 })} as x`)[0].x; +// return ["hello,42", [x.a, x.b].join()]; +// }); + +// t('implicit json', async() => { +// const x = (await sql`select ${ { a: 'hello', b: 42 } }::json as x`)[0].x +// return ['hello,42', [x.a, x.b].join()] +// }) + +// t('implicit jsonb', async() => { +// const x = (await sql`select ${ { a: 'hello', b: 42 } }::jsonb as x`)[0].x +// return ['hello,42', [x.a, x.b].join()] +// }) + +// t('Empty array', async() => +// [true, Array.isArray((await sql`select ${ sql.array([], 1009) } as x`)[0].x)] +// ) + +// t('String array', async() => +// ['123', (await sql`select ${ '{1,2,3}' }::int[] as x`)[0].x.join('')] +// ) + +// t('Array of Integer', async() => +// ['3', (await sql`select ${ sql.array([1, 2, 3]) } as x`)[0].x[2]] +// ) + +// t('Array of String', async() => +// ['c', (await sql`select ${ sql.array(['a', 'b', 'c']) } as x`)[0].x[2]] +// ) + +// t('Array of Date', async() => { +// const now = new Date() +// return [now.getTime(), (await sql`select ${ sql.array([now, now, now]) } as x`)[0].x[2].getTime()] +// }) + +// t('Array of Box', async() => [ +// '(3,4),(1,2);(6,7),(4,5)', +// (await sql`select ${ '{(1,2),(3,4);(4,5),(6,7)}' }::box[] as x`)[0].x.join(';') +// ]) + +// t('Nested array n2', async() => +// ['4', (await sql`select ${ sql.array([[1, 2], [3, 4]]) } as x`)[0].x[1][1]] +// ) + +// t('Nested array n3', async() => +// ['6', (await sql`select ${ sql.array([[[1, 2]], [[3, 4]], [[5, 6]]]) } as x`)[0].x[2][0][1]] +// ) + +// t('Escape in arrays', async() => +// ['Hello "you",c:\\windows', (await sql`select ${ sql.array(['Hello "you"', 'c:\\windows']) } as x`)[0].x.join(',')] +// ) + +// t('Escapes', async() => { +// return ['hej"hej', Object.keys((await sql`select 1 as ${ sql('hej"hej') }`)[0])[0]] +// }) + +// t('null for int', async() => { +// await sql`create table test (x int)` +// return [1, (await sql`insert into test values(${ null })`).count, await sql`drop table test`] +// }) + +// t('Throws on illegal transactions', async() => { +// const sql = postgres({ ...options, max: 2, fetch_types: false }) +// const error = await sql`begin`.catch(e => e) +// return [ +// error.code, +// 'UNSAFE_TRANSACTION' +// ] +// }) + +// t('Transaction throws', async() => { +// await sql`create table test (a int)` +// return ['22P02', await sql.begin(async sql => { +// await sql`insert into test values(1)` +// await sql`insert into test values('hej')` +// }).catch(x => x.code), await sql`drop table test`] +// }) + +// t('Transaction rolls back', async() => { +// await sql`create table test (a int)` +// await sql.begin(async sql => { +// await sql`insert into test values(1)` +// await sql`insert into test values('hej')` +// }).catch(() => { /* ignore */ }) +// return [0, (await sql`select a from test`).count, await sql`drop table test`] +// }) + +// t('Transaction throws on uncaught savepoint', async() => { +// await sql`create table test (a int)` + +// return ['fail', (await sql.begin(async sql => { +// await sql`insert into test values(1)` +// await sql.savepoint(async sql => { +// await sql`insert into test values(2)` +// throw new Error('fail') +// }) +// }).catch((err) => err.message)), await sql`drop table test`] +// }) + +// t('Transaction throws on uncaught named savepoint', async() => { +// await sql`create table test (a int)` + +// return ['fail', (await sql.begin(async sql => { +// await sql`insert into test values(1)` +// await sql.savepoit('watpoint', async sql => { +// await sql`insert into test values(2)` +// throw new Error('fail') +// }) +// }).catch(() => 'fail')), await sql`drop table test`] +// }) + +// t('Transaction succeeds on caught savepoint', async() => { +// await sql`create table test (a int)` +// await sql.begin(async sql => { +// await sql`insert into test values(1)` +// await sql.savepoint(async sql => { +// await sql`insert into test values(2)` +// throw new Error('please rollback') +// }).catch(() => { /* ignore */ }) +// await sql`insert into test values(3)` +// }) + +// return ['2', (await sql`select count(1) from test`)[0].count, await sql`drop table test`] +// }) + +// t('Savepoint returns Result', async() => { +// let result +// await sql.begin(async sql => { +// result = await sql.savepoint(sql => +// sql`select 1 as x` +// ) +// }) + +// return [1, result[0].x] +// }) + +// t('Prepared transaction', async() => { +// await sql`create table test (a int)` + +// await sql.begin(async sql => { +// await sql`insert into test values(1)` +// await sql.prepare('tx1') +// }) + +// await sql`commit prepared 'tx1'` + +// return ['1', (await sql`select count(1) from test`)[0].count, await sql`drop table test`] +// }) + +// t('Transaction requests are executed implicitly', async() => { +// const sql = postgres({ debug: true, idle_timeout: 1, fetch_types: false }) +// return [ +// 'testing', +// (await sql.begin(sql => [ +// sql`select set_config('postgres_js.test', 'testing', true)`, +// sql`select current_setting('postgres_js.test') as x` +// ]))[1][0].x +// ] +// }) + +// t('Uncaught transaction request errors bubbles to transaction', async() => [ +// '42703', +// (await sql.begin(sql => [ +// sql`select wat`, +// sql`select current_setting('postgres_js.test') as x, ${ 1 } as a` +// ]).catch(e => e.code)) +// ]) + +// t('Fragments in transactions', async() => [ +// true, +// (await sql.begin(sql => sql`select true as x where ${ sql`1=1` }`))[0].x +// ]) + +// t('Transaction rejects with rethrown error', async() => [ +// 'WAT', +// await sql.begin(async sql => { +// try { +// await sql`select exception` +// } catch (ex) { +// throw new Error('WAT') +// } +// }).catch(e => e.message) +// ]) + +// t('Parallel transactions', async() => { +// await sql`create table test (a int)` +// return ['11', (await Promise.all([ +// sql.begin(sql => sql`select 1`), +// sql.begin(sql => sql`select 1`) +// ])).map(x => x.count).join(''), await sql`drop table test`] +// }) + +// t('Many transactions at beginning of connection', async() => { +// const sql = postgres(options) +// const xs = await Promise.all(Array.from({ length: 100 }, () => sql.begin(sql => sql`select 1`))) +// return [100, xs.length] +// }) + +// t('Transactions array', async() => { +// await sql`create table test (a int)` + +// return ['11', (await sql.begin(sql => [ +// sql`select 1`.then(x => x), +// sql`select 1` +// ])).map(x => x.count).join(''), await sql`drop table test`] +// }) + +// t('Transaction waits', async() => { +// await sql`create table test (a int)` +// await sql.begin(async sql => { +// await sql`insert into test values(1)` +// await sql.savepoint(async sql => { +// await sql`insert into test values(2)` +// throw new Error('please rollback') +// }).catch(() => { /* ignore */ }) +// await sql`insert into test values(3)` +// }) + +// return ['11', (await Promise.all([ +// sql.begin(sql => sql`select 1`), +// sql.begin(sql => sql`select 1`) +// ])).map(x => x.count).join(''), await sql`drop table test`] +// }) + +// t('Helpers in Transaction', async() => { +// return ['1', (await sql.begin(async sql => +// await sql`select ${ sql({ x: 1 }) }` +// ))[0].x] +// }) + +// t('Undefined values throws', async() => { +// let error + +// await sql` +// select ${ undefined } as x +// `.catch(x => error = x.code) + +// return ['UNDEFINED_VALUE', error] +// }) + +// t('Transform undefined', async() => { +// const sql = postgres({ ...options, transform: { undefined: null } }) +// return [null, (await sql`select ${ undefined } as x`)[0].x] +// }) + +// t('Transform undefined in array', async() => { +// const sql = postgres({ ...options, transform: { undefined: null } }) +// return [null, (await sql`select * from (values ${ sql([undefined, undefined]) }) as x(x, y)`)[0].y] +// }) + +// t('Null sets to null', async() => +// [null, (await sql`select ${ null } as x`)[0].x] +// ) + +// t('Throw syntax error', async() => +// ['42601', (await sql`wat 1`.catch(x => x)).code] +// ) + +// t('Connect using uri', async() => +// [true, await new Promise((resolve, reject) => { +// const sql = postgres('postgres://' + login.user + ':' + (login.pass || '') + '@localhost:5432/' + options.db, { +// idle_timeout +// }) +// sql`select 1`.then(() => resolve(true), reject) +// })] +// ) + +// t('Options from uri with special characters in user and pass', async() => { +// const opt = postgres({ user: 'öla', pass: 'pass^word' }).options +// return [[opt.user, opt.pass].toString(), 'öla,pass^word'] +// }) + +// t('Fail with proper error on no host', async() => +// ['ECONNREFUSED', (await new Promise((resolve, reject) => { +// const sql = postgres('postgres://localhost:33333/' + options.db, { +// idle_timeout +// }) +// sql`select 1`.then(reject, resolve) +// })).code] +// ) + +// t('Connect using SSL', async() => +// [true, (await new Promise((resolve, reject) => { +// postgres({ +// ssl: { rejectUnauthorized: false }, +// idle_timeout +// })`select 1`.then(() => resolve(true), reject) +// }))] +// ) + +// t('Connect using SSL require', async() => +// [true, (await new Promise((resolve, reject) => { +// postgres({ +// ssl: 'require', +// idle_timeout +// })`select 1`.then(() => resolve(true), reject) +// }))] +// ) + +// t('Connect using SSL prefer', async() => { +// await exec('psql', ['-c', 'alter system set ssl=off']) +// await exec('psql', ['-c', 'select pg_reload_conf()']) + +// const sql = postgres({ +// ssl: 'prefer', +// idle_timeout +// }) + +// return [ +// 1, (await sql`select 1 as x`)[0].x, +// await exec('psql', ['-c', 'alter system set ssl=on']), +// await exec('psql', ['-c', 'select pg_reload_conf()']) +// ] +// }) + +// t('Reconnect using SSL', { timeout: 2 }, async() => { +// const sql = postgres({ +// ssl: 'require', +// idle_timeout: 0.1 +// }) + +// await sql`select 1` +// await delay(200) + +// return [1, (await sql`select 1 as x`)[0].x] +// }) + +// t('Login without password', async() => { +// return [true, (await postgres({ ...options, ...login })`select true as x`)[0].x] +// }) + +// t('Login using MD5', async() => { +// return [true, (await postgres({ ...options, ...login_md5 })`select true as x`)[0].x] +// }) + +// t('Login using scram-sha-256', async() => { +// return [true, (await postgres({ ...options, ...login_scram })`select true as x`)[0].x] +// }) + +// t('Parallel connections using scram-sha-256', { +// timeout: 2 +// }, async() => { +// const sql = postgres({ ...options, ...login_scram }) +// return [true, (await Promise.all([ +// sql`select true as x, pg_sleep(0.01)`, +// sql`select true as x, pg_sleep(0.01)`, +// sql`select true as x, pg_sleep(0.01)` +// ]))[0][0].x] +// }) + +// t('Support dynamic password function', async() => { +// return [true, (await postgres({ +// ...options, +// ...login_scram, +// pass: () => 'postgres_js_test_scram' +// })`select true as x`)[0].x] +// }) + +// t('Support dynamic async password function', async() => { +// return [true, (await postgres({ +// ...options, +// ...login_scram, +// pass: () => Promise.resolve('postgres_js_test_scram') +// })`select true as x`)[0].x] +// }) + +// t('Point type', async() => { +// const sql = postgres({ +// ...options, +// types: { +// point: { +// to: 600, +// from: [600], +// serialize: ([x, y]) => '(' + x + ',' + y + ')', +// parse: (x) => x.slice(1, -1).split(',').map(x => +x) +// } +// } +// }) + +// await sql`create table test (x point)` +// await sql`insert into test (x) values (${ sql.types.point([10, 20]) })` +// return [20, (await sql`select x from test`)[0].x[1], await sql`drop table test`] +// }) + +// t('Point type array', async() => { +// const sql = postgres({ +// ...options, +// types: { +// point: { +// to: 600, +// from: [600], +// serialize: ([x, y]) => '(' + x + ',' + y + ')', +// parse: (x) => x.slice(1, -1).split(',').map(x => +x) +// } +// } +// }) + +// await sql`create table test (x point[])` +// await sql`insert into test (x) values (${ sql.array([sql.types.point([10, 20]), sql.types.point([20, 30])]) })` +// return [30, (await sql`select x from test`)[0].x[1][1], await sql`drop table test`] +// }) + +// t('sql file', async() => +// [1, (await sql.file(rel('select.sql')))[0].x] +// ) + +// t('sql file has forEach', async() => { +// let result +// await sql +// .file(rel('select.sql'), { cache: false }) +// .forEach(({ x }) => result = x) + +// return [1, result] +// }) + +// t('sql file throws', async() => +// ['ENOENT', (await sql.file(rel('selectomondo.sql')).catch(x => x.code))] +// ) + +// t('sql file cached', async() => { +// await sql.file(rel('select.sql')) +// await delay(20) + +// return [1, (await sql.file(rel('select.sql')))[0].x] +// }) + +// t('Parameters in file', async() => { +// const result = await sql.file( +// rel('select-param.sql'), +// ['hello'] +// ) +// return ['hello', result[0].x] +// }) + +// t('Connection ended promise', async() => { +// const sql = postgres(options) + +// await sql.end() + +// return [undefined, await sql.end()] +// }) + +// t('Connection ended timeout', async() => { +// const sql = postgres(options) + +// await sql.end({ timeout: 10 }) + +// return [undefined, await sql.end()] +// }) + +// t('Connection ended error', async() => { +// const sql = postgres(options) +// await sql.end() +// return ['CONNECTION_ENDED', (await sql``.catch(x => x.code))] +// }) + +// t('Connection end does not cancel query', async() => { +// const sql = postgres(options) + +// const promise = sql`select 1 as x`.execute() + +// await sql.end() + +// return [1, (await promise)[0].x] +// }) + +// t('Connection destroyed', async() => { +// const sql = postgres(options) +// process.nextTick(() => sql.end({ timeout: 0 })) +// return ['CONNECTION_DESTROYED', await sql``.catch(x => x.code)] +// }) + +// t('Connection destroyed with query before', async() => { +// const sql = postgres(options) +// , error = sql`select pg_sleep(0.2)`.catch(err => err.code) + +// sql.end({ timeout: 0 }) +// return ['CONNECTION_DESTROYED', await error] +// }) + +// t('transform column', async() => { +// const sql = postgres({ +// ...options, +// transform: { column: x => x.split('').reverse().join('') } +// }) + +// await sql`create table test (hello_world int)` +// await sql`insert into test values (1)` +// return ['dlrow_olleh', Object.keys((await sql`select * from test`)[0])[0], await sql`drop table test`] +// }) + +// t('column toPascal', async() => { +// const sql = postgres({ +// ...options, +// transform: { column: postgres.toPascal } +// }) + +// await sql`create table test (hello_world int)` +// await sql`insert into test values (1)` +// return ['HelloWorld', Object.keys((await sql`select * from test`)[0])[0], await sql`drop table test`] +// }) + +// t('column toCamel', async() => { +// const sql = postgres({ +// ...options, +// transform: { column: postgres.toCamel } +// }) + +// await sql`create table test (hello_world int)` +// await sql`insert into test values (1)` +// return ['helloWorld', Object.keys((await sql`select * from test`)[0])[0], await sql`drop table test`] +// }) + +// t('column toKebab', async() => { +// const sql = postgres({ +// ...options, +// transform: { column: postgres.toKebab } +// }) + +// await sql`create table test (hello_world int)` +// await sql`insert into test values (1)` +// return ['hello-world', Object.keys((await sql`select * from test`)[0])[0], await sql`drop table test`] +// }) + +// t('Transform nested json in arrays', async() => { +// const sql = postgres({ +// ...options, +// transform: postgres.camel +// }) +// return ['aBcD', (await sql`select '[{"a_b":1},{"c_d":2}]'::jsonb as x`)[0].x.map(Object.keys).join('')] +// }) + +// t('Transform deeply nested json object in arrays', async() => { +// const sql = postgres({ +// ...options, +// transform: postgres.camel +// }) +// return [ +// 'childObj_deeplyNestedObj_grandchildObj', +// (await sql` +// select '[{"nested_obj": {"child_obj": 2, "deeply_nested_obj": {"grandchild_obj": 3}}}]'::jsonb as x +// `)[0].x.map(x => { +// let result +// for (const key in x) +// result = [...Object.keys(x[key]), ...Object.keys(x[key].deeplyNestedObj)] +// return result +// })[0] +// .join('_') +// ] +// }) + +// t('Transform deeply nested json array in arrays', async() => { +// const sql = postgres({ +// ...options, +// transform: postgres.camel +// }) +// return [ +// 'childArray_deeplyNestedArray_grandchildArray', +// (await sql` +// select '[{"nested_array": [{"child_array": 2, "deeply_nested_array": [{"grandchild_array":3}]}]}]'::jsonb AS x +// `)[0].x.map((x) => { +// let result +// for (const key in x) +// result = [...Object.keys(x[key][0]), ...Object.keys(x[key][0].deeplyNestedArray[0])] +// return result +// })[0] +// .join('_') +// ] +// }) + +// t('Bypass transform for json primitive', async() => { +// const sql = postgres({ +// ...options, +// transform: postgres.camel +// }) + +// const x = ( +// await sql`select 'null'::json as a, 'false'::json as b, '"a"'::json as c, '1'::json as d` +// )[0] + +// return [ +// JSON.stringify({ a: null, b: false, c: 'a', d: 1 }), +// JSON.stringify(x) +// ] +// }) + +// t('Bypass transform for jsonb primitive', async() => { +// const sql = postgres({ +// ...options, +// transform: postgres.camel +// }) + +// const x = ( +// await sql`select 'null'::jsonb as a, 'false'::jsonb as b, '"a"'::jsonb as c, '1'::jsonb as d` +// )[0] + +// return [ +// JSON.stringify({ a: null, b: false, c: 'a', d: 1 }), +// JSON.stringify(x) +// ] +// }) + +// t('unsafe', async() => { +// await sql`create table test (x int)` +// return [1, (await sql.unsafe('insert into test values ($1) returning *', [1]))[0].x, await sql`drop table test`] +// }) + +// t('unsafe simple', async() => { +// return [1, (await sql.unsafe('select 1 as x'))[0].x] +// }) + +// t('unsafe simple includes columns', async() => { +// return ['x', (await sql.unsafe('select 1 as x').values()).columns[0].name] +// }) + +// t('unsafe describe', async() => { +// const q = 'insert into test values (1)' +// await sql`create table test(a int unique)` +// await sql.unsafe(q).describe() +// const x = await sql.unsafe(q).describe() +// return [ +// q, +// x.string, +// await sql`drop table test` +// ] +// }) + +// t('simple query using unsafe with multiple statements', async() => { +// return [ +// '1,2', +// (await sql.unsafe('select 1 as x;select 2 as x')).map(x => x[0].x).join() +// ] +// }) + +// t('simple query using simple() with multiple statements', async() => { +// return [ +// '1,2', +// (await sql`select 1 as x;select 2 as x`.simple()).map(x => x[0].x).join() +// ] +// }) + +// t('listen and notify', async() => { +// const sql = postgres(options) +// const channel = 'hello' +// const result = await new Promise(async r => { +// await sql.listen(channel, r) +// sql.notify(channel, 'works') +// }) + +// return [ +// 'works', +// result, +// sql.end() +// ] +// }) + +// t('double listen', async() => { +// const sql = postgres(options) +// , channel = 'hello' + +// let count = 0 + +// await new Promise((resolve, reject) => +// sql.listen(channel, resolve) +// .then(() => sql.notify(channel, 'world')) +// .catch(reject) +// ).then(() => count++) + +// await new Promise((resolve, reject) => +// sql.listen(channel, resolve) +// .then(() => sql.notify(channel, 'world')) +// .catch(reject) +// ).then(() => count++) + +// // for coverage +// sql.listen('weee', () => { /* noop */ }).then(sql.end) + +// return [2, count] +// }) + +// t('multiple listeners work after a reconnect', async() => { +// const sql = postgres(options) +// , xs = [] + +// const s1 = await sql.listen('test', x => xs.push('1', x)) +// await sql.listen('test', x => xs.push('2', x)) +// await sql.notify('test', 'a') +// await delay(50) +// await sql`select pg_terminate_backend(${ s1.state.pid })` +// await delay(200) +// await sql.notify('test', 'b') +// await delay(50) +// sql.end() + +// return ['1a2a1b2b', xs.join('')] +// }) + +// t('listen and notify with weird name', async() => { +// const sql = postgres(options) +// const channel = 'wat-;.ø.§' +// const result = await new Promise(async r => { +// const { unlisten } = await sql.listen(channel, r) +// sql.notify(channel, 'works') +// await delay(50) +// await unlisten() +// }) + +// return [ +// 'works', +// result, +// sql.end() +// ] +// }) + +// t('listen and notify with upper case', async() => { +// const sql = postgres(options) +// const channel = 'withUpperChar' +// const result = await new Promise(async r => { +// await sql.listen(channel, r) +// sql.notify(channel, 'works') +// }) + +// return [ +// 'works', +// result, +// sql.end() +// ] +// }) + +// t('listen reconnects', { timeout: 2 }, async() => { +// const sql = postgres(options) +// , resolvers = {} +// , a = new Promise(r => resolvers.a = r) +// , b = new Promise(r => resolvers.b = r) + +// let connects = 0 + +// const { state: { pid } } = await sql.listen( +// 'test', +// x => x in resolvers && resolvers[x](), +// () => connects++ +// ) +// await sql.notify('test', 'a') +// await a +// await sql`select pg_terminate_backend(${ pid })` +// await delay(100) +// await sql.notify('test', 'b') +// await b +// sql.end() +// return [connects, 2] +// }) + +// t('listen result reports correct connection state after reconnection', async() => { +// const sql = postgres(options) +// , xs = [] + +// const result = await sql.listen('test', x => xs.push(x)) +// const initialPid = result.state.pid +// await sql.notify('test', 'a') +// await sql`select pg_terminate_backend(${ initialPid })` +// await delay(50) +// sql.end() + +// return [result.state.pid !== initialPid, true] +// }) + +// t('unlisten removes subscription', async() => { +// const sql = postgres(options) +// , xs = [] + +// const { unlisten } = await sql.listen('test', x => xs.push(x)) +// await sql.notify('test', 'a') +// await delay(50) +// await unlisten() +// await sql.notify('test', 'b') +// await delay(50) +// sql.end() + +// return ['a', xs.join('')] +// }) + +// t('listen after unlisten', async() => { +// const sql = postgres(options) +// , xs = [] + +// const { unlisten } = await sql.listen('test', x => xs.push(x)) +// await sql.notify('test', 'a') +// await delay(50) +// await unlisten() +// await sql.notify('test', 'b') +// await delay(50) +// await sql.listen('test', x => xs.push(x)) +// await sql.notify('test', 'c') +// await delay(50) +// sql.end() + +// return ['ac', xs.join('')] +// }) + +// t('multiple listeners and unlisten one', async() => { +// const sql = postgres(options) +// , xs = [] + +// await sql.listen('test', x => xs.push('1', x)) +// const s2 = await sql.listen('test', x => xs.push('2', x)) +// await sql.notify('test', 'a') +// await delay(50) +// await s2.unlisten() +// await sql.notify('test', 'b') +// await delay(50) +// sql.end() + +// return ['1a2a1b', xs.join('')] +// }) + +// t('responds with server parameters (application_name)', async() => +// ['postgres.js', await new Promise((resolve, reject) => postgres({ +// ...options, +// onparameter: (k, v) => k === 'application_name' && resolve(v) +// })`select 1`.catch(reject))] +// ) + +// t('has server parameters', async() => { +// return ['postgres.js', (await sql`select 1`.then(() => sql.parameters.application_name))] +// }) + +// t('big query body', { timeout: 2 }, async() => { +// await sql`create table test (x int)` +// return [50000, (await sql`insert into test ${ +// sql([...Array(50000).keys()].map(x => ({ x }))) +// }`).count, await sql`drop table test`] +// }) + +// t('Throws if more than 65534 parameters', async() => { +// await sql`create table test (x int)` +// return ['MAX_PARAMETERS_EXCEEDED', (await sql`insert into test ${ +// sql([...Array(65535).keys()].map(x => ({ x }))) +// }`.catch(e => e.code)), await sql`drop table test`] +// }) + +// t('let postgres do implicit cast of unknown types', async() => { +// await sql`create table test (x timestamp with time zone)` +// const [{ x }] = await sql`insert into test values (${ new Date().toISOString() }) returning *` +// return [true, x instanceof Date, await sql`drop table test`] +// }) + +// t('only allows one statement', async() => +// ['42601', await sql`select 1; select 2`.catch(e => e.code)] +// ) + +// t('await sql() throws not tagged error', async() => { +// let error +// try { +// await sql('select 1') +// } catch (e) { +// error = e.code +// } +// return ['NOT_TAGGED_CALL', error] +// }) + +// t('sql().then throws not tagged error', async() => { +// let error +// try { +// sql('select 1').then(() => { /* noop */ }) +// } catch (e) { +// error = e.code +// } +// return ['NOT_TAGGED_CALL', error] +// }) + +// t('sql().catch throws not tagged error', async() => { +// let error +// try { +// await sql('select 1') +// } catch (e) { +// error = e.code +// } +// return ['NOT_TAGGED_CALL', error] +// }) + +// t('sql().finally throws not tagged error', async() => { +// let error +// try { +// sql('select 1').finally(() => { /* noop */ }) +// } catch (e) { +// error = e.code +// } +// return ['NOT_TAGGED_CALL', error] +// }) + +// t('little bobby tables', async() => { +// const name = 'Robert\'); DROP TABLE students;--' + +// await sql`create table students (name text, age int)` +// await sql`insert into students (name) values (${ name })` + +// return [ +// name, (await sql`select name from students`)[0].name, +// await sql`drop table students` +// ] +// }) + +// t('Connection errors are caught using begin()', { +// timeout: 2 +// }, async() => { +// let error +// try { +// const sql = postgres({ host: 'localhost', port: 1 }) + +// await sql.begin(async(sql) => { +// await sql`insert into test (label, value) values (${1}, ${2})` +// }) +// } catch (err) { +// error = err +// } + +// return [ +// true, +// error.code === 'ECONNREFUSED' || +// error.message === 'Connection refused (os error 61)' +// ] +// }) + +// t('dynamic table name', async() => { +// await sql`create table test(a int)` +// return [ +// 0, (await sql`select * from ${ sql('test') }`).count, +// await sql`drop table test` +// ] +// }) + +// t('dynamic schema name', async() => { +// await sql`create table test(a int)` +// return [ +// 0, (await sql`select * from ${ sql('public') }.test`).count, +// await sql`drop table test` +// ] +// }) + +// t('dynamic schema and table name', async() => { +// await sql`create table test(a int)` +// return [ +// 0, (await sql`select * from ${ sql('public.test') }`).count, +// await sql`drop table test` +// ] +// }) + +// t('dynamic column name', async() => { +// return ['!not_valid', Object.keys((await sql`select 1 as ${ sql('!not_valid') }`)[0])[0]] +// }) + +// t('dynamic select as', async() => { +// return ['2', (await sql`select ${ sql({ a: 1, b: 2 }) }`)[0].b] +// }) + +// t('dynamic select as pluck', async() => { +// return [undefined, (await sql`select ${ sql({ a: 1, b: 2 }, 'a') }`)[0].b] +// }) + +// t('dynamic insert', async() => { +// await sql`create table test (a int, b text)` +// const x = { a: 42, b: 'the answer' } + +// return ['the answer', (await sql`insert into test ${ sql(x) } returning *`)[0].b, await sql`drop table test`] +// }) + +// t('dynamic insert pluck', async() => { +// await sql`create table test (a int, b text)` +// const x = { a: 42, b: 'the answer' } + +// return [null, (await sql`insert into test ${ sql(x, 'a') } returning *`)[0].b, await sql`drop table test`] +// }) + +// t('dynamic in with empty array', async() => { +// await sql`create table test (a int)` +// await sql`insert into test values (1)` +// return [ +// (await sql`select * from test where null in ${ sql([]) }`).count, +// 0, +// await sql`drop table test` +// ] +// }) + +// t('dynamic in after insert', async() => { +// await sql`create table test (a int, b text)` +// const [{ x }] = await sql` +// with x as ( +// insert into test values (1, 'hej') +// returning * +// ) +// select 1 in ${ sql([1, 2, 3]) } as x from x +// ` +// return [ +// true, x, +// await sql`drop table test` +// ] +// }) + +// t('array insert', async() => { +// await sql`create table test (a int, b int)` +// return [2, (await sql`insert into test (a, b) values ${ sql([1, 2]) } returning *`)[0].b, await sql`drop table test`] +// }) + +// t('where parameters in()', async() => { +// await sql`create table test (x text)` +// await sql`insert into test values ('a')` +// return [ +// (await sql`select * from test where x in ${ sql(['a', 'b', 'c']) }`)[0].x, +// 'a', +// await sql`drop table test` +// ] +// }) + +// t('where parameters in() values before', async() => { +// return [2, (await sql` +// with rows as ( +// select * from (values (1), (2), (3), (4)) as x(a) +// ) +// select * from rows where a in ${ sql([3, 4]) } +// `).count] +// }) + +// t('dynamic multi row insert', async() => { +// await sql`create table test (a int, b text)` +// const x = { a: 42, b: 'the answer' } + +// return [ +// 'the answer', +// (await sql`insert into test ${ sql([x, x]) } returning *`)[1].b, await sql`drop table test` +// ] +// }) + +// t('dynamic update', async() => { +// await sql`create table test (a int, b text)` +// await sql`insert into test (a, b) values (17, 'wrong')` + +// return [ +// 'the answer', +// (await sql`update test set ${ sql({ a: 42, b: 'the answer' }) } returning *`)[0].b, await sql`drop table test` +// ] +// }) + +// t('dynamic update pluck', async() => { +// await sql`create table test (a int, b text)` +// await sql`insert into test (a, b) values (17, 'wrong')` + +// return [ +// 'wrong', +// (await sql`update test set ${ sql({ a: 42, b: 'the answer' }, 'a') } returning *`)[0].b, await sql`drop table test` +// ] +// }) + +// t('dynamic select array', async() => { +// await sql`create table test (a int, b text)` +// await sql`insert into test (a, b) values (42, 'yay')` +// return ['yay', (await sql`select ${ sql(['a', 'b']) } from test`)[0].b, await sql`drop table test`] +// }) + +// t('dynamic returning array', async() => { +// await sql`create table test (a int, b text)` +// return [ +// 'yay', +// (await sql`insert into test (a, b) values (42, 'yay') returning ${ sql(['a', 'b']) }`)[0].b, +// await sql`drop table test` +// ] +// }) + +// t('dynamic select args', async() => { +// await sql`create table test (a int, b text)` +// await sql`insert into test (a, b) values (42, 'yay')` +// return ['yay', (await sql`select ${ sql('a', 'b') } from test`)[0].b, await sql`drop table test`] +// }) + +// t('dynamic values single row', async() => { +// const [{ b }] = await sql` +// select * from (values ${ sql(['a', 'b', 'c']) }) as x(a, b, c) +// ` + +// return ['b', b] +// }) + +// t('dynamic values multi row', async() => { +// const [, { b }] = await sql` +// select * from (values ${ sql([['a', 'b', 'c'], ['a', 'b', 'c']]) }) as x(a, b, c) +// ` + +// return ['b', b] +// }) + +// t('connection parameters', async() => { +// const sql = postgres({ +// ...options, +// connection: { +// 'some.var': 'yay' +// } +// }) + +// return ['yay', (await sql`select current_setting('some.var') as x`)[0].x] +// }) + +// t('Multiple queries', async() => { +// const sql = postgres(options) + +// return [4, (await Promise.all([ +// sql`select 1`, +// sql`select 2`, +// sql`select 3`, +// sql`select 4` +// ])).length] +// }) + +// t('Multiple statements', async() => +// [2, await sql.unsafe(` +// select 1 as x; +// select 2 as a; +// `).then(([, [x]]) => x.a)] +// ) + +// t('throws correct error when authentication fails', async() => { +// const sql = postgres({ +// ...options, +// ...login_md5, +// pass: 'wrong' +// }) +// return ['28P01', await sql`select 1`.catch(e => e.code)] +// }) + +// t('notice', async() => { +// let notice +// const log = console.log // eslint-disable-line +// console.log = function(x) { // eslint-disable-line +// notice = x +// } + +// const sql = postgres(options) + +// await sql`create table if not exists users()` +// await sql`create table if not exists users()` + +// console.log = log // eslint-disable-line + +// return ['NOTICE', notice.severity] +// }) + +// t('notice hook', async() => { +// let notice +// const sql = postgres({ +// ...options, +// onnotice: x => notice = x +// }) + +// await sql`create table if not exists users()` +// await sql`create table if not exists users()` + +// return ['NOTICE', notice.severity] +// }) + +// t('bytea serializes and parses', async() => { +// const buf = Buffer.from('wat') + +// await sql`create table test (x bytea)` +// await sql`insert into test values (${ buf })` + +// return [ +// buf.toString(), +// (await sql`select x from test`)[0].x.toString(), +// await sql`drop table test` +// ] +// }) + +// t('forEach', async() => { +// let result +// await sql`select 1 as x`.forEach(({ x }) => result = x) +// return [1, result] +// }) + +// t('forEach returns empty array', async() => { +// return [0, (await sql`select 1 as x`.forEach(() => { /* noop */ })).length] +// }) + +// t('Cursor', async() => { +// const order = [] +// await sql`select 1 as x union select 2 as x`.cursor(async([x]) => { +// order.push(x.x + 'a') +// await delay(100) +// order.push(x.x + 'b') +// }) +// return ['1a1b2a2b', order.join('')] +// }) + +// t('Unsafe cursor', async() => { +// const order = [] +// await sql.unsafe('select 1 as x union select 2 as x').cursor(async([x]) => { +// order.push(x.x + 'a') +// await delay(100) +// order.push(x.x + 'b') +// }) +// return ['1a1b2a2b', order.join('')] +// }) + +// t('Cursor custom n', async() => { +// const order = [] +// await sql`select * from generate_series(1,20)`.cursor(10, async(x) => { +// order.push(x.length) +// }) +// return ['10,10', order.join(',')] +// }) + +// t('Cursor custom with rest n', async() => { +// const order = [] +// await sql`select * from generate_series(1,20)`.cursor(11, async(x) => { +// order.push(x.length) +// }) +// return ['11,9', order.join(',')] +// }) + +// t('Cursor custom with less results than batch size', async() => { +// const order = [] +// await sql`select * from generate_series(1,20)`.cursor(21, async(x) => { +// order.push(x.length) +// }) +// return ['20', order.join(',')] +// }) + +// t('Cursor cancel', async() => { +// let result +// await sql`select * from generate_series(1,10) as x`.cursor(async([{ x }]) => { +// result = x +// return sql.CLOSE +// }) +// return [1, result] +// }) + +// t('Cursor throw', async() => { +// const order = [] +// await sql`select 1 as x union select 2 as x`.cursor(async([x]) => { +// order.push(x.x + 'a') +// await delay(100) +// throw new Error('watty') +// }).catch(() => order.push('err')) +// return ['1aerr', order.join('')] +// }) + +// t('Cursor error', async() => [ +// '42601', +// await sql`wat`.cursor(() => { /* noop */ }).catch((err) => err.code) +// ]) + +// t('Multiple Cursors', { timeout: 2 }, async() => { +// const result = [] +// await sql.begin(async sql => [ +// await sql`select 1 as cursor, x from generate_series(1,4) as x`.cursor(async([row]) => { +// result.push(row.x) +// await new Promise(r => setTimeout(r, 20)) +// }), +// await sql`select 2 as cursor, x from generate_series(101,104) as x`.cursor(async([row]) => { +// result.push(row.x) +// await new Promise(r => setTimeout(r, 10)) +// }) +// ]) + +// return ['1,2,3,4,101,102,103,104', result.join(',')] +// }) + +// t('Cursor as async iterator', async() => { +// const order = [] +// for await (const [x] of sql`select generate_series(1,2) as x;`.cursor()) { +// order.push(x.x + 'a') +// await delay(10) +// order.push(x.x + 'b') +// } + +// return ['1a1b2a2b', order.join('')] +// }) + +// t('Cursor as async iterator with break', async() => { +// const order = [] +// for await (const xs of sql`select generate_series(1,2) as x;`.cursor()) { +// order.push(xs[0].x + 'a') +// await delay(10) +// order.push(xs[0].x + 'b') +// break +// } + +// return ['1a1b', order.join('')] +// }) + +// t('Async Iterator Unsafe cursor', async() => { +// const order = [] +// for await (const [x] of sql.unsafe('select 1 as x union select 2 as x').cursor()) { +// order.push(x.x + 'a') +// await delay(10) +// order.push(x.x + 'b') +// } +// return ['1a1b2a2b', order.join('')] +// }) + +// t('Async Iterator Cursor custom n', async() => { +// const order = [] +// for await (const x of sql`select * from generate_series(1,20)`.cursor(10)) +// order.push(x.length) + +// return ['10,10', order.join(',')] +// }) + +// t('Async Iterator Cursor custom with rest n', async() => { +// const order = [] +// for await (const x of sql`select * from generate_series(1,20)`.cursor(11)) +// order.push(x.length) + +// return ['11,9', order.join(',')] +// }) + +// t('Async Iterator Cursor custom with less results than batch size', async() => { +// const order = [] +// for await (const x of sql`select * from generate_series(1,20)`.cursor(21)) +// order.push(x.length) +// return ['20', order.join(',')] +// }) + +// t('Transform row', async() => { +// const sql = postgres({ +// ...options, +// transform: { row: () => 1 } +// }) + +// return [1, (await sql`select 'wat'`)[0]] +// }) + +// t('Transform row forEach', async() => { +// let result +// const sql = postgres({ +// ...options, +// transform: { row: () => 1 } +// }) + +// await sql`select 1`.forEach(x => result = x) + +// return [1, result] +// }) + +// t('Transform value', async() => { +// const sql = postgres({ +// ...options, +// transform: { value: () => 1 } +// }) + +// return [1, (await sql`select 'wat' as x`)[0].x] +// }) + +// t('Transform columns from', async() => { +// const sql = postgres({ +// ...options, +// transform: postgres.fromCamel +// }) +// await sql`create table test (a_test int, b_test text)` +// await sql`insert into test ${ sql([{ aTest: 1, bTest: 1 }]) }` +// await sql`update test set ${ sql({ aTest: 2, bTest: 2 }) }` +// return [ +// 2, +// (await sql`select ${ sql('aTest', 'bTest') } from test`)[0].a_test, +// await sql`drop table test` +// ] +// }) + +// t('Transform columns to', async() => { +// const sql = postgres({ +// ...options, +// transform: postgres.toCamel +// }) +// await sql`create table test (a_test int, b_test text)` +// await sql`insert into test ${ sql([{ a_test: 1, b_test: 1 }]) }` +// await sql`update test set ${ sql({ a_test: 2, b_test: 2 }) }` +// return [ +// 2, +// (await sql`select a_test, b_test from test`)[0].aTest, +// await sql`drop table test` +// ] +// }) + +// t('Transform columns from and to', async() => { +// const sql = postgres({ +// ...options, +// transform: postgres.camel +// }) +// await sql`create table test (a_test int, b_test text)` +// await sql`insert into test ${ sql([{ aTest: 1, bTest: 1 }]) }` +// await sql`update test set ${ sql({ aTest: 2, bTest: 2 }) }` +// return [ +// 2, +// (await sql`select ${ sql('aTest', 'bTest') } from test`)[0].aTest, +// await sql`drop table test` +// ] +// }) + +// t('Transform columns from and to (legacy)', async() => { +// const sql = postgres({ +// ...options, +// transform: { +// column: { +// to: postgres.fromCamel, +// from: postgres.toCamel +// } +// } +// }) +// await sql`create table test (a_test int, b_test text)` +// await sql`insert into test ${ sql([{ aTest: 1, bTest: 1 }]) }` +// await sql`update test set ${ sql({ aTest: 2, bTest: 2 }) }` +// return [ +// 2, +// (await sql`select ${ sql('aTest', 'bTest') } from test`)[0].aTest, +// await sql`drop table test` +// ] +// }) + +// t('Unix socket', async() => { +// const sql = postgres({ +// ...options, +// host: process.env.PGSOCKET || '/tmp' // eslint-disable-line +// }) + +// return [1, (await sql`select 1 as x`)[0].x] +// }) + +// t('Big result', async() => { +// return [100000, (await sql`select * from generate_series(1, 100000)`).count] +// }) + +// t('Debug', async() => { +// let result +// const sql = postgres({ +// ...options, +// debug: (connection_id, str) => result = str +// }) + +// await sql`select 1` + +// return ['select 1', result] +// }) + +// t('bigint is returned as String', async() => [ +// 'string', +// typeof (await sql`select 9223372036854777 as x`)[0].x +// ]) + +// t('int is returned as Number', async() => [ +// 'number', +// typeof (await sql`select 123 as x`)[0].x +// ]) + +// t('numeric is returned as string', async() => [ +// 'string', +// typeof (await sql`select 1.2 as x`)[0].x +// ]) + +// t('Async stack trace', async() => { +// const sql = postgres({ ...options, debug: false }) +// return [ +// parseInt(new Error().stack.split('\n')[1].match(':([0-9]+):')[1]) + 1, +// parseInt(await sql`error`.catch(x => x.stack.split('\n').pop().match(':([0-9]+):')[1])) +// ] +// }) + +// t('Debug has long async stack trace', async() => { +// const sql = postgres({ ...options, debug: true }) + +// return [ +// 'watyo', +// await yo().catch(x => x.stack.match(/wat|yo/g).join('')) +// ] + +// function yo() { +// return wat() +// } + +// function wat() { +// return sql`error` +// } +// }) + +// t('Error contains query string', async() => [ +// 'selec 1', +// (await sql`selec 1`.catch(err => err.query)) +// ]) + +// t('Error contains query serialized parameters', async() => [ +// 1, +// (await sql`selec ${ 1 }`.catch(err => err.parameters[0])) +// ]) + +// t('Error contains query raw parameters', async() => [ +// 1, +// (await sql`selec ${ 1 }`.catch(err => err.args[0])) +// ]) + +// t('Query and parameters on errorare not enumerable if debug is not set', async() => { +// const sql = postgres({ ...options, debug: false }) + +// return [ +// false, +// (await sql`selec ${ 1 }`.catch(err => err.propertyIsEnumerable('parameters') || err.propertyIsEnumerable('query'))) +// ] +// }) + +// t('Query and parameters are enumerable if debug is set', async() => { +// const sql = postgres({ ...options, debug: true }) + +// return [ +// true, +// (await sql`selec ${ 1 }`.catch(err => err.propertyIsEnumerable('parameters') && err.propertyIsEnumerable('query'))) +// ] +// }) + +// t('connect_timeout', { timeout: 20 }, async() => { +// const connect_timeout = 0.2 +// const server = net.createServer() +// server.listen() +// const sql = postgres({ port: server.address().port, host: '127.0.0.1', connect_timeout }) +// const start = Date.now() +// let end +// await sql`select 1`.catch((e) => { +// if (e.code !== 'CONNECT_TIMEOUT') +// throw e +// end = Date.now() +// }) +// server.close() +// return [connect_timeout, Math.floor((end - start) / 100) / 10] +// }) + +// t('connect_timeout throws proper error', async() => [ +// 'CONNECT_TIMEOUT', +// await postgres({ +// ...options, +// ...login_scram, +// connect_timeout: 0.001 +// })`select 1`.catch(e => e.code) +// ]) + +// t('connect_timeout error message includes host:port', { timeout: 20 }, async() => { +// const connect_timeout = 0.2 +// const server = net.createServer() +// server.listen() +// const sql = postgres({ port: server.address().port, host: '127.0.0.1', connect_timeout }) +// const port = server.address().port +// let err +// await sql`select 1`.catch((e) => { +// if (e.code !== 'CONNECT_TIMEOUT') +// throw e +// err = e.message +// }) +// server.close() +// return [['write CONNECT_TIMEOUT 127.0.0.1:', port].join(''), err] +// }) + +// t('requests works after single connect_timeout', async() => { +// let first = true + +// const sql = postgres({ +// ...options, +// ...login_scram, +// connect_timeout: { valueOf() { return first ? (first = false, 0.0001) : 1 } } +// }) + +// return [ +// 'CONNECT_TIMEOUT,,1', +// [ +// await sql`select 1 as x`.then(() => 'success', x => x.code), +// await delay(10), +// (await sql`select 1 as x`)[0].x +// ].join(',') +// ] +// }) + +// t('Postgres errors are of type PostgresError', async() => +// [true, (await sql`bad keyword`.catch(e => e)) instanceof sql.PostgresError] +// ) + +// t('Result has columns spec', async() => +// ['x', (await sql`select 1 as x`).columns[0].name] +// ) + +// t('forEach has result as second argument', async() => { +// let x +// await sql`select 1 as x`.forEach((_, result) => x = result) +// return ['x', x.columns[0].name] +// }) + +// t('Result as arrays', async() => { +// const sql = postgres({ +// ...options, +// transform: { +// row: x => Object.values(x) +// } +// }) + +// return ['1,2', (await sql`select 1 as a, 2 as b`)[0].join(',')] +// }) + +// t('Insert empty array', async() => { +// await sql`create table tester (ints int[])` +// return [ +// Array.isArray((await sql`insert into tester (ints) values (${ sql.array([]) }) returning *`)[0].ints), +// true, +// await sql`drop table tester` +// ] +// }) + +// t('Insert array in sql()', async() => { +// await sql`create table tester (ints int[])` +// return [ +// Array.isArray((await sql`insert into tester ${ sql({ ints: sql.array([]) }) } returning *`)[0].ints), +// true, +// await sql`drop table tester` +// ] +// }) + +// t('Automatically creates prepared statements', async() => { +// const sql = postgres(options) +// const result = await sql`select * from pg_prepared_statements` +// return [true, result.some(x => x.name = result.statement.name)] +// }) + +// t('no_prepare: true disables prepared statements (deprecated)', async() => { +// const sql = postgres({ ...options, no_prepare: true }) +// const result = await sql`select * from pg_prepared_statements` +// return [false, result.some(x => x.name = result.statement.name)] +// }) + +// t('prepare: false disables prepared statements', async() => { +// const sql = postgres({ ...options, prepare: false }) +// const result = await sql`select * from pg_prepared_statements` +// return [false, result.some(x => x.name = result.statement.name)] +// }) + +// t('prepare: true enables prepared statements', async() => { +// const sql = postgres({ ...options, prepare: true }) +// const result = await sql`select * from pg_prepared_statements` +// return [true, result.some(x => x.name = result.statement.name)] +// }) + +// t('prepares unsafe query when "prepare" option is true', async() => { +// const sql = postgres({ ...options, prepare: true }) +// const result = await sql.unsafe('select * from pg_prepared_statements where name <> $1', ['bla'], { prepare: true }) +// return [true, result.some(x => x.name = result.statement.name)] +// }) + +// t('does not prepare unsafe query by default', async() => { +// const sql = postgres({ ...options, prepare: true }) +// const result = await sql.unsafe('select * from pg_prepared_statements where name <> $1', ['bla']) +// return [false, result.some(x => x.name = result.statement.name)] +// }) + +// t('Recreate prepared statements on transformAssignedExpr error', { timeout: 1 }, async() => { +// const insert = () => sql`insert into test (name) values (${ '1' }) returning name` +// await sql`create table test (name text)` +// await insert() +// await sql`alter table test alter column name type int using name::integer` +// return [ +// 1, +// (await insert())[0].name, +// await sql`drop table test` +// ] +// }) + +// t('Throws correct error when retrying in transactions', async() => { +// await sql`create table test(x int)` +// const error = await sql.begin(sql => sql`insert into test (x) values (${ false })`).catch(e => e) +// return [ +// error.code, +// '42804', +// sql`drop table test` +// ] +// }) + +// t('Recreate prepared statements on RevalidateCachedQuery error', async() => { +// const select = () => sql`select name from test` +// await sql`create table test (name text)` +// await sql`insert into test values ('1')` +// await select() +// await sql`alter table test alter column name type int using name::integer` +// return [ +// 1, +// (await select())[0].name, +// await sql`drop table test` +// ] +// }) + +// t('Catches connection config errors', async() => { +// const sql = postgres({ ...options, user: { toString: () => { throw new Error('wat') } }, database: 'prut' }) + +// return [ +// 'wat', +// await sql`select 1`.catch((e) => e.message) +// ] +// }) + +// t('Catches connection config errors with end', async() => { +// const sql = postgres({ ...options, user: { toString: () => { throw new Error('wat') } }, database: 'prut' }) + +// return [ +// 'wat', +// await sql`select 1`.catch((e) => e.message), +// await sql.end() +// ] +// }) + +// t('Catches query format errors', async() => [ +// 'wat', +// await sql.unsafe({ toString: () => { throw new Error('wat') } }).catch((e) => e.message) +// ]) + +// t('Multiple hosts', { +// timeout: 1 +// }, async() => { +// const s1 = postgres({ idle_timeout }) +// , s2 = postgres({ idle_timeout, port: 5433 }) +// , sql = postgres('postgres://localhost:5432,localhost:5433', { idle_timeout, max: 1 }) +// , result = [] + +// const id1 = (await s1`select system_identifier as x from pg_control_system()`)[0].x +// const id2 = (await s2`select system_identifier as x from pg_control_system()`)[0].x + +// const x1 = await sql`select 1` +// result.push((await sql`select system_identifier as x from pg_control_system()`)[0].x) +// await s1`select pg_terminate_backend(${ x1.state.pid }::int)` +// await delay(50) + +// const x2 = await sql`select 1` +// result.push((await sql`select system_identifier as x from pg_control_system()`)[0].x) +// await s2`select pg_terminate_backend(${ x2.state.pid }::int)` +// await delay(50) + +// result.push((await sql`select system_identifier as x from pg_control_system()`)[0].x) + +// return [[id1, id2, id1].join(','), result.join(',')] +// }) + +// t('Escaping supports schemas and tables', async() => { +// await sql`create schema a` +// await sql`create table a.b (c int)` +// await sql`insert into a.b (c) values (1)` +// return [ +// 1, +// (await sql`select ${ sql('a.b.c') } from a.b`)[0].c, +// await sql`drop table a.b`, +// await sql`drop schema a` +// ] +// }) + +// t('Raw method returns rows as arrays', async() => { +// const [x] = await sql`select 1`.raw() +// return [ +// Array.isArray(x), +// true +// ] +// }) + +// t('Raw method returns values unparsed as Buffer', async() => { +// const [[x]] = await sql`select 1`.raw() +// return [ +// x instanceof Uint8Array, +// true +// ] +// }) + +// t('Array returns rows as arrays of columns', async() => { +// return [(await sql`select 1`.values())[0][0], 1] +// }) + +// t('Copy read', async() => { +// const result = [] + +// await sql`create table test (x int)` +// await sql`insert into test select * from generate_series(1,10)` +// const readable = await sql`copy test to stdout`.readable() +// readable.on('data', x => result.push(x)) +// await new Promise(r => readable.on('end', r)) + +// return [ +// result.length, +// 10, +// await sql`drop table test` +// ] +// }) + +// t('Copy write', { timeout: 2 }, async() => { +// await sql`create table test (x int)` +// const writable = await sql`copy test from stdin`.writable() + +// writable.write('1\n') +// writable.write('1\n') +// writable.end() + +// await new Promise(r => writable.on('finish', r)) + +// return [ +// (await sql`select 1 from test`).length, +// 2, +// await sql`drop table test` +// ] +// }) + +// t('Copy write as first', async() => { +// await sql`create table test (x int)` +// const first = postgres(options) +// const writable = await first`COPY test FROM STDIN WITH(FORMAT csv, HEADER false, DELIMITER ',')`.writable() +// writable.write('1\n') +// writable.write('1\n') +// writable.end() + +// await new Promise(r => writable.on('finish', r)) + +// return [ +// (await sql`select 1 from test`).length, +// 2, +// await sql`drop table test` +// ] +// }) + +// t('Copy from file', async() => { +// await sql`create table test (x int, y int, z int)` +// await new Promise(async r => fs +// .createReadStream(rel('copy.csv')) +// .pipe(await sql`copy test from stdin`.writable()) +// .on('finish', r) +// ) + +// return [ +// JSON.stringify(await sql`select * from test`), +// '[{"x":1,"y":2,"z":3},{"x":4,"y":5,"z":6}]', +// await sql`drop table test` +// ] +// }) + +// t('Copy from works in transaction', async() => { +// await sql`create table test(x int)` +// const xs = await sql.begin(async sql => { +// (await sql`copy test from stdin`.writable()).end('1\n2') +// await delay(20) +// return sql`select 1 from test` +// }) + +// return [ +// xs.length, +// 2, +// await sql`drop table test` +// ] +// }) + +// t('Copy from abort', async() => { +// const sql = postgres(options) +// const readable = fs.createReadStream(rel('copy.csv')) + +// await sql`create table test (x int, y int, z int)` +// await sql`TRUNCATE TABLE test` + +// const writable = await sql`COPY test FROM STDIN`.writable() + +// let aborted + +// readable +// .pipe(writable) +// .on('error', (err) => aborted = err) + +// writable.destroy(new Error('abort')) +// await sql.end() + +// return [ +// 'abort', +// aborted.message, +// await postgres(options)`drop table test` +// ] +// }) + +// t('multiple queries before connect', async() => { +// const sql = postgres({ ...options, max: 2 }) +// const xs = await Promise.all([ +// sql`select 1 as x`, +// sql`select 2 as x`, +// sql`select 3 as x`, +// sql`select 4 as x` +// ]) + +// return [ +// '1,2,3,4', +// xs.map(x => x[0].x).join() +// ] +// }) + +// t('subscribe', { timeout: 2 }, async() => { +// const sql = postgres({ +// database: 'postgres_js_test', +// publications: 'alltables' +// }) + +// await sql.unsafe('create publication alltables for all tables') + +// const result = [] + +// const { unsubscribe } = await sql.subscribe('*', (row, { command, old }) => { +// result.push(command, row.name, row.id, old && old.name, old && old.id) +// }) + +// await sql` +// create table test ( +// id serial primary key, +// name text +// ) +// ` + +// await sql`alter table test replica identity default` +// await sql`insert into test (name) values ('Murray')` +// await sql`update test set name = 'Rothbard'` +// await sql`update test set id = 2` +// await sql`delete from test` +// await sql`alter table test replica identity full` +// await sql`insert into test (name) values ('Murray')` +// await sql`update test set name = 'Rothbard'` +// await sql`delete from test` +// await delay(10) +// await unsubscribe() +// await sql`insert into test (name) values ('Oh noes')` +// await delay(10) +// return [ +// 'insert,Murray,1,,,update,Rothbard,1,,,update,Rothbard,2,,1,delete,,2,,,insert,Murray,2,,,update,Rothbard,2,Murray,2,delete,Rothbard,2,,', // eslint-disable-line +// result.join(','), +// await sql`drop table test`, +// await sql`drop publication alltables`, +// await sql.end() +// ] +// }) + +// t('subscribe with transform', { timeout: 2 }, async() => { +// const sql = postgres({ +// transform: { +// column: { +// from: postgres.toCamel, +// to: postgres.fromCamel +// } +// }, +// database: 'postgres_js_test', +// publications: 'alltables' +// }) + +// await sql.unsafe('create publication alltables for all tables') + +// const result = [] + +// const { unsubscribe } = await sql.subscribe('*', (row, { command, old }) => +// result.push(command, row.nameInCamel || row.id, old && old.nameInCamel) +// ) + +// await sql` +// create table test ( +// id serial primary key, +// name_in_camel text +// ) +// ` + +// await sql`insert into test (name_in_camel) values ('Murray')` +// await sql`update test set name_in_camel = 'Rothbard'` +// await sql`delete from test` +// await sql`alter table test replica identity full` +// await sql`insert into test (name_in_camel) values ('Murray')` +// await sql`update test set name_in_camel = 'Rothbard'` +// await sql`delete from test` +// await delay(10) +// await unsubscribe() +// await sql`insert into test (name_in_camel) values ('Oh noes')` +// await delay(10) +// return [ +// 'insert,Murray,,update,Rothbard,,delete,1,,insert,Murray,,update,Rothbard,Murray,delete,Rothbard,', +// result.join(','), +// await sql`drop table test`, +// await sql`drop publication alltables`, +// await sql.end() +// ] +// }) + +// t('subscribe reconnects and calls onsubscribe', { timeout: 4 }, async() => { +// const sql = postgres({ +// database: 'postgres_js_test', +// publications: 'alltables', +// fetch_types: false +// }) + +// await sql.unsafe('create publication alltables for all tables') + +// const result = [] +// let onsubscribes = 0 + +// const { unsubscribe, sql: subscribeSql } = await sql.subscribe( +// '*', +// (row, { command, old }) => result.push(command, row.name || row.id, old && old.name), +// () => onsubscribes++ +// ) + +// await sql` +// create table test ( +// id serial primary key, +// name text +// ) +// ` + +// await sql`insert into test (name) values ('Murray')` +// await delay(10) +// await subscribeSql.close() +// await delay(500) +// await sql`delete from test` +// await delay(100) +// await unsubscribe() +// return [ +// '2insert,Murray,,delete,1,', +// onsubscribes + result.join(','), +// await sql`drop table test`, +// await sql`drop publication alltables`, +// await sql.end() +// ] +// }) + +// t('Execute', async() => { +// const result = await new Promise((resolve) => { +// const sql = postgres({ ...options, fetch_types: false, debug:(id, query) => resolve(query) }) +// sql`select 1`.execute() +// }) + +// return [result, 'select 1'] +// }) + +// t('Cancel running query', async() => { +// const query = sql`select pg_sleep(2)` +// setTimeout(() => query.cancel(), 200) +// const error = await query.catch(x => x) +// return ['57014', error.code] +// }) + +// t('Cancel piped query', { timeout: 5 }, async() => { +// await sql`select 1` +// const last = sql`select pg_sleep(1)`.execute() +// const query = sql`select pg_sleep(2) as dig` +// setTimeout(() => query.cancel(), 500) +// const error = await query.catch(x => x) +// await last +// return ['57014', error.code] +// }) + +// t('Cancel queued query', async() => { +// const query = sql`select pg_sleep(2) as nej` +// const tx = sql.begin(sql => ( +// query.cancel(), +// sql`select pg_sleep(0.5) as hej, 'hejsa'` +// )) +// const error = await query.catch(x => x) +// await tx +// return ['57014', error.code] +// }) + +// t('Fragments', async() => [ +// 1, +// (await sql` +// ${ sql`select` } 1 as x +// `)[0].x +// ]) + +// t('Result becomes array', async() => [ +// true, +// (await sql`select 1`).slice() instanceof Array +// ]) + +// t('Describe', async() => { +// const type = (await sql`select ${ 1 }::int as x`.describe()).types[0] +// return [23, type] +// }) + +// t('Describe a statement', async() => { +// await sql`create table tester (name text, age int)` +// const r = await sql`select name, age from tester where name like $1 and age > $2`.describe() +// return [ +// '25,23/name:25,age:23', +// `${ r.types.join(',') }/${ r.columns.map(c => `${c.name}:${c.type}`).join(',') }`, +// await sql`drop table tester` +// ] +// }) + +// t('Include table oid and column number in column details', async() => { +// await sql`create table tester (name text, age int)` +// const r = await sql`select name, age from tester where name like $1 and age > $2`.describe() +// const [{ oid }] = await sql`select oid from pg_class where relname = 'tester'` + +// return [ +// `table:${oid},number:1|table:${oid},number:2`, +// `${ r.columns.map(c => `table:${c.table},number:${c.number}`).join('|') }`, +// await sql`drop table tester` +// ] +// }) + +// t('Describe a statement without parameters', async() => { +// await sql`create table tester (name text, age int)` +// const r = await sql`select name, age from tester`.describe() +// return [ +// '0,2', +// `${ r.types.length },${ r.columns.length }`, +// await sql`drop table tester` +// ] +// }) + +// t('Describe a statement without columns', async() => { +// await sql`create table tester (name text, age int)` +// const r = await sql`insert into tester (name, age) values ($1, $2)`.describe() +// return [ +// '2,0', +// `${ r.types.length },${ r.columns.length }`, +// await sql`drop table tester` +// ] +// }) + +// t('Large object', async() => { +// const file = rel('index.js') +// , md5 = crypto.createHash('md5').update(fs.readFileSync(file)).digest('hex') + +// const lo = await sql.largeObject() +// await new Promise(async r => fs.createReadStream(file).pipe(await lo.writable()).on('finish', r)) +// await lo.seek(0) + +// const out = crypto.createHash('md5') +// await new Promise(r => lo.readable().then(x => x.on('data', x => out.update(x)).on('end', r))) + +// return [ +// md5, +// out.digest('hex'), +// await lo.close() +// ] +// }) + +// t('Catches type serialize errors', async() => { +// const sql = postgres({ +// idle_timeout, +// types: { +// text: { +// from: 25, +// to: 25, +// parse: x => x, +// serialize: () => { throw new Error('watSerialize') } +// } +// } +// }) + +// return [ +// 'watSerialize', +// (await sql`select ${ 'wat' }`.catch(e => e.message)) +// ] +// }) + +// t('Catches type parse errors', async() => { +// const sql = postgres({ +// idle_timeout, +// types: { +// text: { +// from: 25, +// to: 25, +// parse: () => { throw new Error('watParse') }, +// serialize: x => x +// } +// } +// }) + +// return [ +// 'watParse', +// (await sql`select 'wat'`.catch(e => e.message)) +// ] +// }) + +// t('Catches type serialize errors in transactions', async() => { +// const sql = postgres({ +// idle_timeout, +// types: { +// text: { +// from: 25, +// to: 25, +// parse: x => x, +// serialize: () => { throw new Error('watSerialize') } +// } +// } +// }) + +// return [ +// 'watSerialize', +// (await sql.begin(sql => ( +// sql`select 1`, +// sql`select ${ 'wat' }` +// )).catch(e => e.message)) +// ] +// }) + +// t('Catches type parse errors in transactions', async() => { +// const sql = postgres({ +// idle_timeout, +// types: { +// text: { +// from: 25, +// to: 25, +// parse: () => { throw new Error('watParse') }, +// serialize: x => x +// } +// } +// }) + +// return [ +// 'watParse', +// (await sql.begin(sql => ( +// sql`select 1`, +// sql`select 'wat'` +// )).catch(e => e.message)) +// ] +// }) + +// t('Prevent premature end of connection in transaction', async() => { +// const sql = postgres({ max_lifetime: 0.01, idle_timeout }) +// const result = await sql.begin(async sql => { +// await sql`select 1` +// await delay(20) +// await sql`select 1` +// return 'yay' +// }) + +// return [ +// 'yay', +// result +// ] +// }) + +// t('Ensure reconnect after max_lifetime with transactions', { timeout: 5 }, async() => { +// const sql = postgres({ +// max_lifetime: 0.01, +// idle_timeout, +// max: 1 +// }) + +// let x = 0 +// while (x++ < 10) await sql.begin(sql => sql`select 1 as x`) + +// return [true, true] +// }) + +// t('Custom socket', {}, async() => { +// let result +// const sql = postgres({ +// socket: () => new Promise((resolve, reject) => { +// const socket = new net.Socket() +// socket.connect(5432) +// socket.once('data', x => result = x[0]) +// socket.on('error', reject) +// socket.on('connect', () => resolve(socket)) +// }), +// idle_timeout +// }) + +// await sql`select 1` + +// return [ +// result, +// 82 +// ] +// }) + +// t('Ensure drain only dequeues if ready', async() => { +// const sql = postgres(options) + +// const res = await Promise.all([ +// sql.unsafe('SELECT 0+$1 --' + '.'.repeat(100000), [1]), +// sql.unsafe('SELECT 0+$1+$2+$3', [1, 2, 3]) +// ]) + +// return [res.length, 2] +// }) + +// t('Supports fragments as dynamic parameters', async() => { +// await sql`create table test (a int, b bool)` +// await sql`insert into test values(1, true)` +// await sql`insert into test ${ +// sql({ +// a: 2, +// b: sql`exists(select 1 from test where b = ${ true })` +// }) +// }` + +// return [ +// '1,t2,t', +// (await sql`select * from test`.raw()).join(''), +// await sql`drop table test` +// ] +// }) + +// t('Supports nested fragments with parameters', async() => { +// await sql`create table test ${ +// sql`(${ sql('a') } ${ sql`int` })` +// }` +// await sql`insert into test values(1)` +// return [ +// 1, +// (await sql`select a from test`)[0].a, +// await sql`drop table test` +// ] +// }) + +// t('Supports multiple nested fragments with parameters', async() => { +// const [{ b }] = await sql`select * ${ +// sql`from ${ +// sql`(values (2, ${ 1 }::int)) as x(${ sql(['a', 'b']) })` +// }` +// }` +// return [ +// 1, +// b +// ] +// }) + +// t('Supports arrays of fragments', async() => { +// const [{ x }] = await sql` +// ${ [sql`select`, sql`1`, sql`as`, sql`x`] } +// ` + +// return [ +// 1, +// x +// ] +// }) + +// t('Does not try rollback when commit errors', async() => { +// let notice = null +// const sql = postgres({ ...options, onnotice: x => notice = x }) +// await sql`create table test(x int constraint test_constraint unique deferrable initially deferred)` + +// await sql.begin('isolation level serializable', async sql => { +// await sql`insert into test values(1)` +// await sql`insert into test values(1)` +// }).catch(e => e) + +// return [ +// notice, +// null, +// await sql`drop table test` +// ] +// }) + +// t('Last keyword used even with duplicate keywords', async() => { +// await sql`create table test (x int)` +// await sql`insert into test values(1)` +// const [{ x }] = await sql` +// select +// 1 in (1) as x +// from test +// where x in ${ sql([1, 2]) } +// ` + +// return [x, true, await sql`drop table test`] +// }) + +// t('Insert array with null', async() => { +// await sql`create table test (x int[])` +// await sql`insert into test ${ sql({ x: [1, null, 3] }) }` +// return [ +// 1, +// (await sql`select x from test`)[0].x[0], +// await sql`drop table test` +// ] +// }) + +// t('Insert array with undefined throws', async() => { +// await sql`create table test (x int[])` +// return [ +// 'UNDEFINED_VALUE', +// await sql`insert into test ${ sql({ x: [1, undefined, 3] }) }`.catch(e => e.code), +// await sql`drop table test` +// ] +// }) + +// t('Insert array with undefined transform', async() => { +// const sql = postgres({ ...options, transform: { undefined: null } }) +// await sql`create table test (x int[])` +// await sql`insert into test ${ sql({ x: [1, undefined, 3] }) }` +// return [ +// 1, +// (await sql`select x from test`)[0].x[0], +// await sql`drop table test` +// ] +// }) + +// t('concurrent cursors', async() => { +// const xs = [] + +// await Promise.all([...Array(7)].map((x, i) => [ +// sql`select ${ i }::int as a, generate_series(1, 2) as x`.cursor(([x]) => xs.push(x.a + x.x)) +// ]).flat()) + +// return ['12233445566778', xs.join('')] +// }) + +// t('concurrent cursors multiple connections', async() => { +// const sql = postgres({ ...options, max: 2 }) +// const xs = [] + +// await Promise.all([...Array(7)].map((x, i) => [ +// sql`select ${ i }::int as a, generate_series(1, 2) as x`.cursor(([x]) => xs.push(x.a + x.x)) +// ]).flat()) + +// return ['12233445566778', xs.sort().join('')] +// }) + +// t('reserve connection', async() => { +// const reserved = await sql.reserve() + +// setTimeout(() => reserved.release(), 510) + +// const xs = await Promise.all([ +// reserved`select 1 as x`.then(([{ x }]) => ({ time: Date.now(), x })), +// sql`select 2 as x`.then(([{ x }]) => ({ time: Date.now(), x })), +// reserved`select 3 as x`.then(([{ x }]) => ({ time: Date.now(), x })) +// ]) + +// if (xs[1].time - xs[2].time < 500) +// throw new Error('Wrong time') + +// return [ +// '123', +// xs.map(x => x.x).join('') +// ] +// })