@warlock.js/cascade 4.5.0 → 4.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/cjs/index.cjs +1012 -104
- package/cjs/index.cjs.map +1 -1
- package/esm/contracts/database-driver.contract.d.mts +27 -4
- package/esm/contracts/database-driver.contract.d.mts.map +1 -1
- package/esm/contracts/database-id-generator.contract.d.mts +38 -0
- package/esm/contracts/database-id-generator.contract.d.mts.map +1 -1
- package/esm/contracts/index.d.mts +1 -1
- package/esm/contracts/query-builder.contract.d.mts +27 -0
- package/esm/contracts/query-builder.contract.d.mts.map +1 -1
- package/esm/data-source/data-source.d.mts +21 -1
- package/esm/data-source/data-source.d.mts.map +1 -1
- package/esm/data-source/data-source.mjs +22 -0
- package/esm/data-source/data-source.mjs.map +1 -1
- package/esm/drivers/mongodb/mongodb-driver.d.mts +2 -2
- package/esm/drivers/mongodb/mongodb-driver.d.mts.map +1 -1
- package/esm/drivers/mongodb/mongodb-driver.mjs +5 -4
- package/esm/drivers/mongodb/mongodb-driver.mjs.map +1 -1
- package/esm/drivers/mongodb/mongodb-id-generator.d.mts +98 -48
- package/esm/drivers/mongodb/mongodb-id-generator.d.mts.map +1 -1
- package/esm/drivers/mongodb/mongodb-id-generator.mjs +153 -59
- package/esm/drivers/mongodb/mongodb-id-generator.mjs.map +1 -1
- package/esm/drivers/mongodb/mongodb-query-builder.d.mts +25 -0
- package/esm/drivers/mongodb/mongodb-query-builder.d.mts.map +1 -1
- package/esm/drivers/mongodb/mongodb-query-builder.mjs +32 -0
- package/esm/drivers/mongodb/mongodb-query-builder.mjs.map +1 -1
- package/esm/drivers/mongodb/mongodb-query-parser.d.mts +36 -0
- package/esm/drivers/mongodb/mongodb-query-parser.d.mts.map +1 -1
- package/esm/drivers/mongodb/mongodb-query-parser.mjs +80 -1
- package/esm/drivers/mongodb/mongodb-query-parser.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-dialect.d.mts +32 -4
- package/esm/drivers/postgres/postgres-dialect.d.mts.map +1 -1
- package/esm/drivers/postgres/postgres-dialect.mjs +57 -4
- package/esm/drivers/postgres/postgres-dialect.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-driver.d.mts +83 -1
- package/esm/drivers/postgres/postgres-driver.d.mts.map +1 -1
- package/esm/drivers/postgres/postgres-driver.mjs +129 -16
- package/esm/drivers/postgres/postgres-driver.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-query-builder.d.mts +32 -0
- package/esm/drivers/postgres/postgres-query-builder.d.mts.map +1 -1
- package/esm/drivers/postgres/postgres-query-builder.mjs +47 -1
- package/esm/drivers/postgres/postgres-query-builder.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-query-parser.d.mts +13 -2
- package/esm/drivers/postgres/postgres-query-parser.d.mts.map +1 -1
- package/esm/drivers/postgres/postgres-query-parser.mjs +21 -4
- package/esm/drivers/postgres/postgres-query-parser.mjs.map +1 -1
- package/esm/drivers/postgres/types.d.mts +15 -0
- package/esm/drivers/postgres/types.d.mts.map +1 -1
- package/esm/drivers/sql/sql-dialect.contract.d.mts +20 -0
- package/esm/drivers/sql/sql-dialect.contract.d.mts.map +1 -1
- package/esm/expressions/aggregate-expressions.d.mts +71 -34
- package/esm/expressions/aggregate-expressions.d.mts.map +1 -1
- package/esm/expressions/aggregate-expressions.mjs +80 -7
- package/esm/expressions/aggregate-expressions.mjs.map +1 -1
- package/esm/expressions/column-expressions.d.mts +193 -0
- package/esm/expressions/column-expressions.d.mts.map +1 -0
- package/esm/expressions/column-expressions.mjs +152 -0
- package/esm/expressions/column-expressions.mjs.map +1 -0
- package/esm/index.d.mts +3 -2
- package/esm/index.mjs +2 -1
- package/esm/model/methods/write-methods.d.mts +29 -0
- package/esm/model/methods/write-methods.d.mts.map +1 -0
- package/esm/model/methods/write-methods.mjs +164 -2
- package/esm/model/methods/write-methods.mjs.map +1 -1
- package/esm/model/model.d.mts +64 -3
- package/esm/model/model.d.mts.map +1 -1
- package/esm/model/model.mjs +65 -3
- package/esm/model/model.mjs.map +1 -1
- package/esm/writer/database-writer.d.mts.map +1 -1
- package/esm/writer/database-writer.mjs +4 -3
- package/esm/writer/database-writer.mjs.map +1 -1
- package/llms-full.txt +111 -8
- package/llms.txt +3 -3
- package/package.json +4 -4
- package/skills/README.md +3 -3
- package/skills/aggregate-data/SKILL.md +43 -3
- package/skills/manage-transactions/SKILL.md +45 -2
- package/skills/perform-atomic-ops/SKILL.md +23 -3
|
@@ -201,18 +201,24 @@ var PostgresDialect = class {
|
|
|
201
201
|
/**
|
|
202
202
|
* Translate a database-agnostic aggregate expression to PostgreSQL SQL.
|
|
203
203
|
*
|
|
204
|
-
* The
|
|
205
|
-
* `floor`, `first` and `last` are
|
|
206
|
-
* single-scalar `GROUP BY` equivalent on
|
|
207
|
-
* of emitting a silently-different semantic
|
|
204
|
+
* The scalar aggregates map to their ANSI SQL function, and `countDistinct`
|
|
205
|
+
* maps to `COUNT(DISTINCT …)`. `distinct`, `floor`, `first` and `last` are
|
|
206
|
+
* MongoDB-only for v1 — none has a single-scalar `GROUP BY` equivalent on
|
|
207
|
+
* PostgreSQL, so they throw instead of emitting a silently-different semantic
|
|
208
|
+
* (the footgun this guards).
|
|
208
209
|
*
|
|
209
210
|
* @param expression - The abstract aggregate (`$agg.*`) to translate
|
|
210
211
|
* @returns The SQL fragment (e.g. `SUM("amount")`, `COUNT(*)`)
|
|
211
212
|
*/
|
|
212
213
|
aggregateToSql(expression) {
|
|
214
|
+
if (expression.__expr) {
|
|
215
|
+
if (expression.__agg !== "sum") throw new Error(`$agg.${expression.__agg} does not support a composed column expression yet — only $agg.sum(...) does. Use a bare column name here.`);
|
|
216
|
+
return `SUM(${this.columnExpressionToSql(expression.__expr)})`;
|
|
217
|
+
}
|
|
213
218
|
const column = expression.__field === null ? "*" : this.quoteIdentifier(expression.__field);
|
|
214
219
|
switch (expression.__agg) {
|
|
215
220
|
case "count": return "COUNT(*)";
|
|
221
|
+
case "countDistinct": return `COUNT(DISTINCT ${column})`;
|
|
216
222
|
case "sum": return `SUM(${column})`;
|
|
217
223
|
case "avg": return `AVG(${column})`;
|
|
218
224
|
case "min": return `MIN(${column})`;
|
|
@@ -220,6 +226,53 @@ var PostgresDialect = class {
|
|
|
220
226
|
default: throw new Error(`$agg.${expression.__agg} is MongoDB-only and not supported on a PostgreSQL groupBy. Use selectRaw / havingRaw with the equivalent SQL (window function / DISTINCT / FLOOR) if you need it here.`);
|
|
221
227
|
}
|
|
222
228
|
}
|
|
229
|
+
/**
|
|
230
|
+
* Compile a typed {@link ColumnExpression} tree into a SQL fragment.
|
|
231
|
+
*
|
|
232
|
+
* Column references flow through {@link quoteIdentifier} (so user-supplied
|
|
233
|
+
* names are quoted/escaped, never raw-interpolated); literals are emitted as
|
|
234
|
+
* numeric/boolean SQL constants; arithmetic ops are parenthesised. The `raw`
|
|
235
|
+
* node is the only path that emits a string verbatim — it is opt-in by name.
|
|
236
|
+
*
|
|
237
|
+
* @param expression - The expression tree to compile
|
|
238
|
+
* @returns A SQL fragment (e.g. `("price" * "quantity")`)
|
|
239
|
+
*/
|
|
240
|
+
columnExpressionToSql(expression) {
|
|
241
|
+
switch (expression.__expr) {
|
|
242
|
+
case "column": return this.quoteIdentifier(expression.column);
|
|
243
|
+
case "literal": return typeof expression.value === "boolean" ? this.booleanLiteral(expression.value) : String(expression.value);
|
|
244
|
+
case "raw": return expression.expression;
|
|
245
|
+
case "add":
|
|
246
|
+
case "subtract":
|
|
247
|
+
case "multiply":
|
|
248
|
+
case "divide": {
|
|
249
|
+
const operator = {
|
|
250
|
+
add: "+",
|
|
251
|
+
subtract: "-",
|
|
252
|
+
multiply: "*",
|
|
253
|
+
divide: "/"
|
|
254
|
+
}[expression.__expr];
|
|
255
|
+
return `(${expression.operands.map((operand) => this.columnExpressionToSql(operand)).join(` ${operator} `)})`;
|
|
256
|
+
}
|
|
257
|
+
default: throw new Error(`Unsupported column expression node: ${JSON.stringify(expression)}`);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Build a `date_trunc` bucket expression for a portable `groupByDate`.
|
|
262
|
+
*
|
|
263
|
+
* @param column - The date/timestamp column to truncate
|
|
264
|
+
* @param unit - The bucket granularity
|
|
265
|
+
* @returns The SQL fragment (e.g. `date_trunc('month', "created_at")`)
|
|
266
|
+
*
|
|
267
|
+
* @example
|
|
268
|
+
* ```typescript
|
|
269
|
+
* dialect.dateTruncSql("created_at", "month");
|
|
270
|
+
* // date_trunc('month', "created_at")
|
|
271
|
+
* ```
|
|
272
|
+
*/
|
|
273
|
+
dateTruncSql(column, unit) {
|
|
274
|
+
return `date_trunc('${unit}', ${this.quoteIdentifier(column)})`;
|
|
275
|
+
}
|
|
223
276
|
};
|
|
224
277
|
|
|
225
278
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"postgres-dialect.mjs","names":[],"sources":["../../../../../../../../@warlock.js/cascade/src/drivers/postgres/postgres-dialect.ts"],"sourcesContent":["/**\n * PostgreSQL Dialect Implementation\n *\n * Implements the SqlDialectContract for PostgreSQL-specific SQL syntax.\n * Handles parameter placeholders ($1, $2), identifier quoting, and\n * PostgreSQL-specific features like JSONB operators.\n *\n * @module cascade/drivers/postgres\n */\n\nimport type { AggregateExpression } from \"../../expressions\";\nimport type { SqlDialectContract } from \"../sql/sql-dialect.contract\";\n\n/**\n * PostgreSQL-specific SQL dialect implementation.\n *\n * Provides PostgreSQL syntax for:\n * - Parameter placeholders ($1, $2, $3...)\n * - Identifier quoting with double quotes\n * - JSONB operators (->, ->>, @>)\n * - ILIKE for case-insensitive matching\n * - RETURNING clause support\n *\n * @example\n * ```typescript\n * const dialect = new PostgresDialect();\n *\n * dialect.placeholder(1); // \"$1\"\n * dialect.quoteIdentifier('user'); // '\"user\"'\n * dialect.jsonExtract('data', 'name'); // \"data\"->>'name'\n * ```\n */\nexport class PostgresDialect implements SqlDialectContract {\n /**\n * Dialect name identifier.\n */\n public readonly name = \"postgres\" as const;\n\n /**\n * PostgreSQL supports the RETURNING clause for INSERT/UPDATE/DELETE.\n */\n public readonly supportsReturning = true;\n\n /**\n * PostgreSQL uses ON CONFLICT for upsert operations.\n */\n public readonly upsertKeyword = \"ON CONFLICT\" as const;\n\n /**\n * Generate a PostgreSQL parameter placeholder.\n *\n * PostgreSQL uses numbered placeholders: $1, $2, $3, etc.\n *\n * @param index - The 1-based parameter index\n * @returns The placeholder string (e.g., \"$1\")\n */\n public placeholder(index: number): string {\n return `$${index}`;\n }\n\n /**\n * Quote an identifier using PostgreSQL's double-quote syntax.\n *\n * Handles escaping of embedded double quotes by doubling them.\n * This is necessary for reserved words and special characters.\n *\n * @param identifier - The identifier (table/column name) to quote\n * @returns The quoted identifier (e.g., '\"user\"')\n */\n public quoteIdentifier(identifier: string): string {\n // Split on dots for qualified names (schema.table.column)\n const parts = identifier.split(\".\");\n return parts.map((part) => `\"${part.replace(/\"/g, '\"\"')}\"`).join(\".\");\n }\n\n /**\n * Convert a boolean to PostgreSQL literal.\n *\n * @param value - The boolean value\n * @returns \"TRUE\" or \"FALSE\"\n */\n public booleanLiteral(value: boolean): string {\n return value ? \"TRUE\" : \"FALSE\";\n }\n\n /**\n * Build LIMIT/OFFSET clause for PostgreSQL.\n *\n * @param limit - Maximum rows to return\n * @param offset - Rows to skip\n * @returns The SQL clause (e.g., \"LIMIT 10 OFFSET 20\")\n */\n public limitOffset(limit?: number, offset?: number): string {\n const parts: string[] = [];\n\n if (limit !== undefined) {\n parts.push(`LIMIT ${limit}`);\n }\n\n if (offset !== undefined) {\n parts.push(`OFFSET ${offset}`);\n }\n\n return parts.join(\" \");\n }\n\n /**\n * Build a JSON path extraction expression for PostgreSQL.\n *\n * Uses the ->> operator for text extraction from JSONB columns.\n * Supports nested paths using chained operators.\n *\n * @param column - The JSONB column name\n * @param path - The path to extract (dot notation: \"user.name\")\n * @returns The SQL expression (e.g., \"data\"->>'user'->>'name')\n */\n public jsonExtract(column: string, path: string): string {\n const quotedColumn = this.quoteIdentifier(column);\n const pathParts = path.split(\".\");\n\n if (pathParts.length === 1) {\n return `${quotedColumn}->>'${pathParts[0]}'`;\n }\n\n // For nested paths: data->'user'->>'name' (last one gets text extraction)\n const jsonPath = pathParts\n .slice(0, -1)\n .map((p) => `'${p}'`)\n .join(\"->\");\n const lastKey = pathParts[pathParts.length - 1];\n\n return `${quotedColumn}->${jsonPath}->>'${lastKey}'`;\n }\n\n /**\n * Build a JSON contains expression for PostgreSQL.\n *\n * Uses the @> containment operator for JSONB columns.\n *\n * @param column - The JSONB column name\n * @param value - The value to check for\n * @param path - Optional path within the JSON\n * @returns The SQL expression\n */\n public jsonContains(column: string, value: unknown, path?: string): string {\n const quotedColumn = this.quoteIdentifier(column);\n\n if (path) {\n // Check if a specific path contains the value\n const jsonValue = JSON.stringify({ [path]: value });\n return `${quotedColumn} @> '${jsonValue}'::jsonb`;\n }\n\n // Check if the column contains the value (for arrays or objects)\n const jsonValue = JSON.stringify(value);\n return `${quotedColumn} @> '${jsonValue}'::jsonb`;\n }\n\n /**\n * Build a LIKE pattern expression for PostgreSQL.\n *\n * Uses ILIKE for case-insensitive matching, LIKE for case-sensitive.\n *\n * @param pattern - The pattern to match\n * @param caseInsensitive - Whether to use case-insensitive matching\n * @returns Object with operator and pattern\n */\n public likePattern(\n pattern: string,\n caseInsensitive = true,\n ): { operator: string; pattern: string } {\n // Escape special characters in the pattern (%, _, \\)\n const escapedPattern = pattern.replace(/\\\\/g, \"\\\\\\\\\").replace(/%/g, \"\\\\%\").replace(/_/g, \"\\\\_\");\n\n return {\n operator: caseInsensitive ? \"ILIKE\" : \"LIKE\",\n pattern: escapedPattern,\n };\n }\n\n /**\n * Build an array contains expression for PostgreSQL.\n *\n * Uses ANY() for checking if a value is in an array column.\n *\n * @param column - The array column name\n * @param paramIndex - The parameter index\n * @returns The SQL expression\n */\n public arrayContains(column: string, paramIndex: number): string {\n return `${this.placeholder(paramIndex)} = ANY(${this.quoteIdentifier(column)})`;\n }\n\n /**\n * Get the PostgreSQL SQL type for an abstract type.\n *\n * @param type - The abstract type name\n * @param options - Type-specific options\n * @returns The PostgreSQL type string\n */\n public getSqlType(\n type: string,\n options?: { length?: number; precision?: number; scale?: number; dimensions?: number },\n ): string {\n switch (type) {\n case \"string\":\n return options?.length ? `VARCHAR(${options.length})` : \"TEXT\";\n case \"char\":\n return `CHAR(${options?.length ?? 1})`;\n case \"text\":\n return \"TEXT\";\n case \"mediumText\":\n case \"longText\":\n return \"TEXT\"; // PostgreSQL doesn't distinguish text sizes\n case \"integer\":\n return \"INTEGER\";\n case \"smallInteger\":\n return \"SMALLINT\";\n case \"tinyInteger\":\n return \"SMALLINT\"; // PostgreSQL doesn't have TINYINT\n case \"bigInteger\":\n return \"BIGINT\";\n case \"float\":\n return \"REAL\";\n case \"double\":\n return \"DOUBLE PRECISION\";\n case \"decimal\":\n if (options?.precision !== undefined) {\n const scale = options.scale ?? 0;\n return `DECIMAL(${options.precision}, ${scale})`;\n }\n return \"DECIMAL\";\n case \"boolean\":\n return \"BOOLEAN\";\n case \"date\":\n return \"DATE\";\n case \"dateTime\":\n return \"TIMESTAMP\";\n case \"timestamp\":\n return \"TIMESTAMPTZ\"; // With timezone\n case \"time\":\n return \"TIME\";\n case \"year\":\n return \"SMALLINT\"; // PostgreSQL doesn't have YEAR type\n case \"json\":\n return \"JSONB\"; // Prefer JSONB for indexing and operators\n case \"binary\":\n return \"BYTEA\";\n case \"uuid\":\n return \"UUID\";\n case \"ulid\":\n return \"CHAR(26)\"; // ULIDs are 26 characters\n case \"ipAddress\":\n return \"INET\";\n case \"macAddress\":\n return \"MACADDR\";\n case \"point\":\n return \"POINT\";\n case \"polygon\":\n return \"POLYGON\";\n case \"lineString\":\n return \"PATH\";\n case \"geometry\":\n return \"GEOMETRY\"; // Requires PostGIS\n case \"vector\":\n return options?.dimensions ? `VECTOR(${options.dimensions})` : \"VECTOR\"; // Requires pgvector\n case \"enum\":\n return \"TEXT\"; // PostgreSQL enums need CREATE TYPE first\n case \"set\":\n return \"TEXT[]\"; // Use array for set-like behavior\n // PostgreSQL native array types\n case \"arrayInt\":\n return \"INTEGER[]\";\n case \"arrayBigInt\":\n return \"BIGINT[]\";\n case \"arrayFloat\":\n return \"REAL[]\";\n case \"arrayDecimal\":\n if (options?.precision !== undefined) {\n const scale = options.scale ?? 0;\n return `DECIMAL(${options.precision}, ${scale})[]`;\n }\n return \"DECIMAL[]\";\n case \"arrayBoolean\":\n return \"BOOLEAN[]\";\n case \"arrayText\":\n return \"TEXT[]\";\n case \"arrayDate\":\n return \"DATE[]\";\n case \"arrayTimestamp\":\n return \"TIMESTAMPTZ[]\";\n case \"arrayUuid\":\n return \"UUID[]\";\n case \"arrayJson\":\n return \"JSONB[]\";\n default:\n return type.toUpperCase();\n }\n }\n\n /**\n * Translate a database-agnostic aggregate expression to PostgreSQL SQL.\n *\n * The five scalar aggregates map to their ANSI SQL function. `distinct`,\n * `floor`, `first` and `last` are MongoDB-only for v1 — none has a\n * single-scalar `GROUP BY` equivalent on PostgreSQL, so they throw instead\n * of emitting a silently-different semantic (the footgun this guards).\n *\n * @param expression - The abstract aggregate (`$agg.*`) to translate\n * @returns The SQL fragment (e.g. `SUM(\"amount\")`, `COUNT(*)`)\n */\n public aggregateToSql(expression: AggregateExpression): string {\n const column = expression.__field === null ? \"*\" : this.quoteIdentifier(expression.__field);\n\n switch (expression.__agg) {\n case \"count\":\n return \"COUNT(*)\";\n case \"sum\":\n return `SUM(${column})`;\n case \"avg\":\n return `AVG(${column})`;\n case \"min\":\n return `MIN(${column})`;\n case \"max\":\n return `MAX(${column})`;\n default:\n throw new Error(\n `$agg.${expression.__agg} is MongoDB-only and not supported on a ` +\n `PostgreSQL groupBy. Use selectRaw / havingRaw with the equivalent ` +\n `SQL (window function / DISTINCT / FLOOR) if you need it here.`,\n );\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"postgres-dialect.mjs","names":[],"sources":["../../../../../../../../@warlock.js/cascade/src/drivers/postgres/postgres-dialect.ts"],"sourcesContent":["/**\n * PostgreSQL Dialect Implementation\n *\n * Implements the SqlDialectContract for PostgreSQL-specific SQL syntax.\n * Handles parameter placeholders ($1, $2), identifier quoting, and\n * PostgreSQL-specific features like JSONB operators.\n *\n * @module cascade/drivers/postgres\n */\n\nimport type { AggregateExpression, ColumnExpression } from \"../../expressions\";\nimport type { SqlDialectContract } from \"../sql/sql-dialect.contract\";\n\n/**\n * PostgreSQL-specific SQL dialect implementation.\n *\n * Provides PostgreSQL syntax for:\n * - Parameter placeholders ($1, $2, $3...)\n * - Identifier quoting with double quotes\n * - JSONB operators (->, ->>, @>)\n * - ILIKE for case-insensitive matching\n * - RETURNING clause support\n *\n * @example\n * ```typescript\n * const dialect = new PostgresDialect();\n *\n * dialect.placeholder(1); // \"$1\"\n * dialect.quoteIdentifier('user'); // '\"user\"'\n * dialect.jsonExtract('data', 'name'); // \"data\"->>'name'\n * ```\n */\nexport class PostgresDialect implements SqlDialectContract {\n /**\n * Dialect name identifier.\n */\n public readonly name = \"postgres\" as const;\n\n /**\n * PostgreSQL supports the RETURNING clause for INSERT/UPDATE/DELETE.\n */\n public readonly supportsReturning = true;\n\n /**\n * PostgreSQL uses ON CONFLICT for upsert operations.\n */\n public readonly upsertKeyword = \"ON CONFLICT\" as const;\n\n /**\n * Generate a PostgreSQL parameter placeholder.\n *\n * PostgreSQL uses numbered placeholders: $1, $2, $3, etc.\n *\n * @param index - The 1-based parameter index\n * @returns The placeholder string (e.g., \"$1\")\n */\n public placeholder(index: number): string {\n return `$${index}`;\n }\n\n /**\n * Quote an identifier using PostgreSQL's double-quote syntax.\n *\n * Handles escaping of embedded double quotes by doubling them.\n * This is necessary for reserved words and special characters.\n *\n * @param identifier - The identifier (table/column name) to quote\n * @returns The quoted identifier (e.g., '\"user\"')\n */\n public quoteIdentifier(identifier: string): string {\n // Split on dots for qualified names (schema.table.column)\n const parts = identifier.split(\".\");\n return parts.map((part) => `\"${part.replace(/\"/g, '\"\"')}\"`).join(\".\");\n }\n\n /**\n * Convert a boolean to PostgreSQL literal.\n *\n * @param value - The boolean value\n * @returns \"TRUE\" or \"FALSE\"\n */\n public booleanLiteral(value: boolean): string {\n return value ? \"TRUE\" : \"FALSE\";\n }\n\n /**\n * Build LIMIT/OFFSET clause for PostgreSQL.\n *\n * @param limit - Maximum rows to return\n * @param offset - Rows to skip\n * @returns The SQL clause (e.g., \"LIMIT 10 OFFSET 20\")\n */\n public limitOffset(limit?: number, offset?: number): string {\n const parts: string[] = [];\n\n if (limit !== undefined) {\n parts.push(`LIMIT ${limit}`);\n }\n\n if (offset !== undefined) {\n parts.push(`OFFSET ${offset}`);\n }\n\n return parts.join(\" \");\n }\n\n /**\n * Build a JSON path extraction expression for PostgreSQL.\n *\n * Uses the ->> operator for text extraction from JSONB columns.\n * Supports nested paths using chained operators.\n *\n * @param column - The JSONB column name\n * @param path - The path to extract (dot notation: \"user.name\")\n * @returns The SQL expression (e.g., \"data\"->>'user'->>'name')\n */\n public jsonExtract(column: string, path: string): string {\n const quotedColumn = this.quoteIdentifier(column);\n const pathParts = path.split(\".\");\n\n if (pathParts.length === 1) {\n return `${quotedColumn}->>'${pathParts[0]}'`;\n }\n\n // For nested paths: data->'user'->>'name' (last one gets text extraction)\n const jsonPath = pathParts\n .slice(0, -1)\n .map((p) => `'${p}'`)\n .join(\"->\");\n const lastKey = pathParts[pathParts.length - 1];\n\n return `${quotedColumn}->${jsonPath}->>'${lastKey}'`;\n }\n\n /**\n * Build a JSON contains expression for PostgreSQL.\n *\n * Uses the @> containment operator for JSONB columns.\n *\n * @param column - The JSONB column name\n * @param value - The value to check for\n * @param path - Optional path within the JSON\n * @returns The SQL expression\n */\n public jsonContains(column: string, value: unknown, path?: string): string {\n const quotedColumn = this.quoteIdentifier(column);\n\n if (path) {\n // Check if a specific path contains the value\n const jsonValue = JSON.stringify({ [path]: value });\n return `${quotedColumn} @> '${jsonValue}'::jsonb`;\n }\n\n // Check if the column contains the value (for arrays or objects)\n const jsonValue = JSON.stringify(value);\n return `${quotedColumn} @> '${jsonValue}'::jsonb`;\n }\n\n /**\n * Build a LIKE pattern expression for PostgreSQL.\n *\n * Uses ILIKE for case-insensitive matching, LIKE for case-sensitive.\n *\n * @param pattern - The pattern to match\n * @param caseInsensitive - Whether to use case-insensitive matching\n * @returns Object with operator and pattern\n */\n public likePattern(\n pattern: string,\n caseInsensitive = true,\n ): { operator: string; pattern: string } {\n // Escape special characters in the pattern (%, _, \\)\n const escapedPattern = pattern.replace(/\\\\/g, \"\\\\\\\\\").replace(/%/g, \"\\\\%\").replace(/_/g, \"\\\\_\");\n\n return {\n operator: caseInsensitive ? \"ILIKE\" : \"LIKE\",\n pattern: escapedPattern,\n };\n }\n\n /**\n * Build an array contains expression for PostgreSQL.\n *\n * Uses ANY() for checking if a value is in an array column.\n *\n * @param column - The array column name\n * @param paramIndex - The parameter index\n * @returns The SQL expression\n */\n public arrayContains(column: string, paramIndex: number): string {\n return `${this.placeholder(paramIndex)} = ANY(${this.quoteIdentifier(column)})`;\n }\n\n /**\n * Get the PostgreSQL SQL type for an abstract type.\n *\n * @param type - The abstract type name\n * @param options - Type-specific options\n * @returns The PostgreSQL type string\n */\n public getSqlType(\n type: string,\n options?: { length?: number; precision?: number; scale?: number; dimensions?: number },\n ): string {\n switch (type) {\n case \"string\":\n return options?.length ? `VARCHAR(${options.length})` : \"TEXT\";\n case \"char\":\n return `CHAR(${options?.length ?? 1})`;\n case \"text\":\n return \"TEXT\";\n case \"mediumText\":\n case \"longText\":\n return \"TEXT\"; // PostgreSQL doesn't distinguish text sizes\n case \"integer\":\n return \"INTEGER\";\n case \"smallInteger\":\n return \"SMALLINT\";\n case \"tinyInteger\":\n return \"SMALLINT\"; // PostgreSQL doesn't have TINYINT\n case \"bigInteger\":\n return \"BIGINT\";\n case \"float\":\n return \"REAL\";\n case \"double\":\n return \"DOUBLE PRECISION\";\n case \"decimal\":\n if (options?.precision !== undefined) {\n const scale = options.scale ?? 0;\n return `DECIMAL(${options.precision}, ${scale})`;\n }\n return \"DECIMAL\";\n case \"boolean\":\n return \"BOOLEAN\";\n case \"date\":\n return \"DATE\";\n case \"dateTime\":\n return \"TIMESTAMP\";\n case \"timestamp\":\n return \"TIMESTAMPTZ\"; // With timezone\n case \"time\":\n return \"TIME\";\n case \"year\":\n return \"SMALLINT\"; // PostgreSQL doesn't have YEAR type\n case \"json\":\n return \"JSONB\"; // Prefer JSONB for indexing and operators\n case \"binary\":\n return \"BYTEA\";\n case \"uuid\":\n return \"UUID\";\n case \"ulid\":\n return \"CHAR(26)\"; // ULIDs are 26 characters\n case \"ipAddress\":\n return \"INET\";\n case \"macAddress\":\n return \"MACADDR\";\n case \"point\":\n return \"POINT\";\n case \"polygon\":\n return \"POLYGON\";\n case \"lineString\":\n return \"PATH\";\n case \"geometry\":\n return \"GEOMETRY\"; // Requires PostGIS\n case \"vector\":\n return options?.dimensions ? `VECTOR(${options.dimensions})` : \"VECTOR\"; // Requires pgvector\n case \"enum\":\n return \"TEXT\"; // PostgreSQL enums need CREATE TYPE first\n case \"set\":\n return \"TEXT[]\"; // Use array for set-like behavior\n // PostgreSQL native array types\n case \"arrayInt\":\n return \"INTEGER[]\";\n case \"arrayBigInt\":\n return \"BIGINT[]\";\n case \"arrayFloat\":\n return \"REAL[]\";\n case \"arrayDecimal\":\n if (options?.precision !== undefined) {\n const scale = options.scale ?? 0;\n return `DECIMAL(${options.precision}, ${scale})[]`;\n }\n return \"DECIMAL[]\";\n case \"arrayBoolean\":\n return \"BOOLEAN[]\";\n case \"arrayText\":\n return \"TEXT[]\";\n case \"arrayDate\":\n return \"DATE[]\";\n case \"arrayTimestamp\":\n return \"TIMESTAMPTZ[]\";\n case \"arrayUuid\":\n return \"UUID[]\";\n case \"arrayJson\":\n return \"JSONB[]\";\n default:\n return type.toUpperCase();\n }\n }\n\n /**\n * Translate a database-agnostic aggregate expression to PostgreSQL SQL.\n *\n * The scalar aggregates map to their ANSI SQL function, and `countDistinct`\n * maps to `COUNT(DISTINCT …)`. `distinct`, `floor`, `first` and `last` are\n * MongoDB-only for v1 — none has a single-scalar `GROUP BY` equivalent on\n * PostgreSQL, so they throw instead of emitting a silently-different semantic\n * (the footgun this guards).\n *\n * @param expression - The abstract aggregate (`$agg.*`) to translate\n * @returns The SQL fragment (e.g. `SUM(\"amount\")`, `COUNT(*)`)\n */\n public aggregateToSql(expression: AggregateExpression): string {\n // When a composed column expression is present, the aggregate operates on\n // it instead of a bare column (e.g. SUM(price * quantity)). Only `sum`\n // supports the expression form in v1.\n if (expression.__expr) {\n if (expression.__agg !== \"sum\") {\n throw new Error(\n `$agg.${expression.__agg} does not support a composed column ` +\n `expression yet — only $agg.sum(...) does. Use a bare column name here.`,\n );\n }\n\n return `SUM(${this.columnExpressionToSql(expression.__expr)})`;\n }\n\n const column = expression.__field === null ? \"*\" : this.quoteIdentifier(expression.__field);\n\n switch (expression.__agg) {\n case \"count\":\n return \"COUNT(*)\";\n case \"countDistinct\":\n return `COUNT(DISTINCT ${column})`;\n case \"sum\":\n return `SUM(${column})`;\n case \"avg\":\n return `AVG(${column})`;\n case \"min\":\n return `MIN(${column})`;\n case \"max\":\n return `MAX(${column})`;\n default:\n throw new Error(\n `$agg.${expression.__agg} is MongoDB-only and not supported on a ` +\n `PostgreSQL groupBy. Use selectRaw / havingRaw with the equivalent ` +\n `SQL (window function / DISTINCT / FLOOR) if you need it here.`,\n );\n }\n }\n\n /**\n * Compile a typed {@link ColumnExpression} tree into a SQL fragment.\n *\n * Column references flow through {@link quoteIdentifier} (so user-supplied\n * names are quoted/escaped, never raw-interpolated); literals are emitted as\n * numeric/boolean SQL constants; arithmetic ops are parenthesised. The `raw`\n * node is the only path that emits a string verbatim — it is opt-in by name.\n *\n * @param expression - The expression tree to compile\n * @returns A SQL fragment (e.g. `(\"price\" * \"quantity\")`)\n */\n public columnExpressionToSql(expression: ColumnExpression): string {\n switch (expression.__expr) {\n case \"column\":\n return this.quoteIdentifier(expression.column);\n case \"literal\":\n return typeof expression.value === \"boolean\"\n ? this.booleanLiteral(expression.value)\n : String(expression.value);\n case \"raw\":\n return expression.expression;\n case \"add\":\n case \"subtract\":\n case \"multiply\":\n case \"divide\": {\n const operator = { add: \"+\", subtract: \"-\", multiply: \"*\", divide: \"/\" }[expression.__expr];\n const parts = expression.operands.map((operand) => this.columnExpressionToSql(operand));\n return `(${parts.join(` ${operator} `)})`;\n }\n default:\n throw new Error(`Unsupported column expression node: ${JSON.stringify(expression)}`);\n }\n }\n\n /**\n * Build a `date_trunc` bucket expression for a portable `groupByDate`.\n *\n * @param column - The date/timestamp column to truncate\n * @param unit - The bucket granularity\n * @returns The SQL fragment (e.g. `date_trunc('month', \"created_at\")`)\n *\n * @example\n * ```typescript\n * dialect.dateTruncSql(\"created_at\", \"month\");\n * // date_trunc('month', \"created_at\")\n * ```\n */\n public dateTruncSql(column: string, unit: \"day\" | \"week\" | \"month\" | \"year\"): string {\n return `date_trunc('${unit}', ${this.quoteIdentifier(column)})`;\n }\n}\n"],"mappings":"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"}
|
|
@@ -89,6 +89,22 @@ declare class PostgresDriver implements DriverContract {
|
|
|
89
89
|
* Sync adapter instance (lazy-loaded).
|
|
90
90
|
*/
|
|
91
91
|
private _syncAdapter;
|
|
92
|
+
/**
|
|
93
|
+
* Explicit, table-agnostic override list of column names that hold native
|
|
94
|
+
* PostgreSQL arrays (`JSONB[]`, `TEXT[]`, …) and must NOT be JSON-text
|
|
95
|
+
* encoded. Merged with (and superseded per-table by) the schema
|
|
96
|
+
* introspection below; kept as a manual escape hatch.
|
|
97
|
+
*
|
|
98
|
+
* @see PostgresPoolConfig.nativeArrayColumns
|
|
99
|
+
*/
|
|
100
|
+
private readonly _nativeArrayColumns;
|
|
101
|
+
/**
|
|
102
|
+
* Native-array columns discovered by introspecting the live schema on
|
|
103
|
+
* connect, keyed `table → { column, … }`. Authoritative and table-scoped, so
|
|
104
|
+
* a column that is `TEXT[]` in one table and `jsonb` in another is encoded
|
|
105
|
+
* correctly for each — no app configuration required.
|
|
106
|
+
*/
|
|
107
|
+
private _introspectedArrayColumns;
|
|
92
108
|
/**
|
|
93
109
|
* Create a new PostgreSQL driver instance.
|
|
94
110
|
*
|
|
@@ -141,9 +157,75 @@ declare class PostgresDriver implements DriverContract {
|
|
|
141
157
|
* that need special handling for PostgreSQL storage.
|
|
142
158
|
*
|
|
143
159
|
* @param data - The data object to serialize
|
|
160
|
+
* @param table - Optional table name; when given, columns introspected as
|
|
161
|
+
* native arrays on that table are bound raw (see {@link serializeValue}).
|
|
144
162
|
* @returns Serialized data ready for PostgreSQL
|
|
145
163
|
*/
|
|
146
|
-
serialize(data: Record<string, unknown
|
|
164
|
+
serialize(data: Record<string, unknown>, table?: string): Record<string, unknown>;
|
|
165
|
+
/**
|
|
166
|
+
* Serialize a single column value into a node-pg bindable parameter.
|
|
167
|
+
*
|
|
168
|
+
* Shared by {@link serialize} (INSERT path) and {@link buildUpdateQuery}
|
|
169
|
+
* `$set` (UPDATE path) so both encode `json` / `jsonb` columns identically.
|
|
170
|
+
*
|
|
171
|
+
* Encoding rules (in order):
|
|
172
|
+
* - `Date` → ISO string.
|
|
173
|
+
* - `bigint` → decimal string (node-pg has no native bigint binding).
|
|
174
|
+
* - all-number array → pgvector literal `'[n1,n2,...]'`. node-pg would
|
|
175
|
+
* otherwise emit a `{n1,n2,...}` array literal, which the `vector` type
|
|
176
|
+
* rejects. This branch is preserved exactly.
|
|
177
|
+
* - any other array (object-array, string-array, mixed, empty `[]`) →
|
|
178
|
+
* `JSON.stringify`. node-pg renders a raw JS array as a PostgreSQL array
|
|
179
|
+
* literal `{...}` (and `[]` as `{}`), which a `json` / `jsonb` column
|
|
180
|
+
* rejects — so we bind the value as JSON text instead, the form those
|
|
181
|
+
* columns accept. Columns known to be native arrays — via schema
|
|
182
|
+
* introspection or the `nativeArrayColumns` config — are exempt: their raw
|
|
183
|
+
* array is passed through so node-pg emits the `{...}` literal a genuine
|
|
184
|
+
* `JSONB[]` / `TEXT[]` column needs.
|
|
185
|
+
* - plain object → `JSON.stringify`. Equivalent to node-pg's own object
|
|
186
|
+
* handling, made explicit so both write paths agree.
|
|
187
|
+
* - everything else (scalars: string, number, boolean, null) → untouched.
|
|
188
|
+
*
|
|
189
|
+
* Distinguishing native-array from `json` / `jsonb` columns: a value alone
|
|
190
|
+
* can't tell them apart, so the driver introspects the live schema on connect
|
|
191
|
+
* (see {@link loadNativeArrayColumns}) and consults that per-table map here
|
|
192
|
+
* via {@link isNativeArrayColumn}. The explicit `nativeArrayColumns` config
|
|
193
|
+
* still works as a table-agnostic override. No `::jsonb` placeholder cast is
|
|
194
|
+
* added: a JSON-text string binds correctly to `json` / `jsonb` without one,
|
|
195
|
+
* and a blind cast would misfire on columns we cannot positively identify as
|
|
196
|
+
* jsonb.
|
|
197
|
+
*
|
|
198
|
+
* @param key - Column name (used to resolve native-array columns)
|
|
199
|
+
* @param value - The raw value to serialize (never `undefined`)
|
|
200
|
+
* @param table - Optional table name; enables the per-table native-array lookup
|
|
201
|
+
* @returns The value ready to bind as a query parameter
|
|
202
|
+
*/
|
|
203
|
+
private serializeValue;
|
|
204
|
+
/**
|
|
205
|
+
* Whether `column` on `table` is a native PostgreSQL array. True when the
|
|
206
|
+
* connect-time schema introspection saw it as `data_type = 'ARRAY'` for that
|
|
207
|
+
* table (authoritative, per-table), or when it's listed in the table-agnostic
|
|
208
|
+
* `nativeArrayColumns` config override.
|
|
209
|
+
*/
|
|
210
|
+
private isNativeArrayColumn;
|
|
211
|
+
/**
|
|
212
|
+
* Introspect the live schema for native-array columns so array values bind
|
|
213
|
+
* correctly with zero app configuration.
|
|
214
|
+
*
|
|
215
|
+
* A JS array must be bound two opposite ways depending on the column: as JSON
|
|
216
|
+
* text for a `json` / `jsonb` column, but as a raw array (which node-pg
|
|
217
|
+
* renders `{...}`) for a native `TEXT[]` / `JSONB[]` / `INTEGER[]` column. The
|
|
218
|
+
* serializer sees values, not types, so without this it JSON-stringifies
|
|
219
|
+
* every array — which a native-array column rejects with "malformed array
|
|
220
|
+
* literal". One `information_schema` query at connect, cached for the
|
|
221
|
+
* connection lifetime, removes the need to hand-list `nativeArrayColumns`.
|
|
222
|
+
*
|
|
223
|
+
* Best-effort: any failure (e.g. restricted catalog access) is logged and
|
|
224
|
+
* leaves the map empty so the config override still applies — it never blocks
|
|
225
|
+
* connect. A schema change made within a live connection isn't reflected
|
|
226
|
+
* until the next connect.
|
|
227
|
+
*/
|
|
228
|
+
private loadNativeArrayColumns;
|
|
147
229
|
/**
|
|
148
230
|
* Get the dirty tracker for this driver.
|
|
149
231
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"postgres-driver.d.mts","names":[],"sources":["../../../../../../../../@warlock.js/cascade/src/drivers/postgres/postgres-driver.ts"],"mappings":";;;;;;;;;;;;;AAgD2C;AA0D3C;;AA1D2C,KADtC,MAAA,gBAAsB,IAAI;AAAA,KAC1B,YAAA,gBAA4B,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA0D9B,cAAA,YAA0B,cAAA;EAAA,
|
|
1
|
+
{"version":3,"file":"postgres-driver.d.mts","names":[],"sources":["../../../../../../../../@warlock.js/cascade/src/drivers/postgres/postgres-driver.ts"],"mappings":"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"}
|
|
@@ -125,12 +125,29 @@ var PostgresDriver = class {
|
|
|
125
125
|
*/
|
|
126
126
|
_syncAdapter;
|
|
127
127
|
/**
|
|
128
|
+
* Explicit, table-agnostic override list of column names that hold native
|
|
129
|
+
* PostgreSQL arrays (`JSONB[]`, `TEXT[]`, …) and must NOT be JSON-text
|
|
130
|
+
* encoded. Merged with (and superseded per-table by) the schema
|
|
131
|
+
* introspection below; kept as a manual escape hatch.
|
|
132
|
+
*
|
|
133
|
+
* @see PostgresPoolConfig.nativeArrayColumns
|
|
134
|
+
*/
|
|
135
|
+
_nativeArrayColumns;
|
|
136
|
+
/**
|
|
137
|
+
* Native-array columns discovered by introspecting the live schema on
|
|
138
|
+
* connect, keyed `table → { column, … }`. Authoritative and table-scoped, so
|
|
139
|
+
* a column that is `TEXT[]` in one table and `jsonb` in another is encoded
|
|
140
|
+
* correctly for each — no app configuration required.
|
|
141
|
+
*/
|
|
142
|
+
_introspectedArrayColumns = /* @__PURE__ */ new Map();
|
|
143
|
+
/**
|
|
128
144
|
* Create a new PostgreSQL driver instance.
|
|
129
145
|
*
|
|
130
146
|
* @param config - PostgreSQL connection configuration
|
|
131
147
|
*/
|
|
132
148
|
constructor(config) {
|
|
133
149
|
this.config = config;
|
|
150
|
+
this._nativeArrayColumns = new Set(config.nativeArrayColumns ?? []);
|
|
134
151
|
}
|
|
135
152
|
/**
|
|
136
153
|
* Get the connection pool instance.
|
|
@@ -189,6 +206,7 @@ var PostgresDriver = class {
|
|
|
189
206
|
(await this._pool.connect()).release();
|
|
190
207
|
log.success("database.postgres", "connection", `Connected to database ${colors.bold(colors.yellowBright(this.config.database))}`);
|
|
191
208
|
this._isConnected = true;
|
|
209
|
+
await this.loadNativeArrayColumns();
|
|
192
210
|
this.emit("connected");
|
|
193
211
|
} catch (error) {
|
|
194
212
|
log.fatal("database.postgres", "connection", "Failed to connect to database");
|
|
@@ -225,21 +243,115 @@ var PostgresDriver = class {
|
|
|
225
243
|
* that need special handling for PostgreSQL storage.
|
|
226
244
|
*
|
|
227
245
|
* @param data - The data object to serialize
|
|
246
|
+
* @param table - Optional table name; when given, columns introspected as
|
|
247
|
+
* native arrays on that table are bound raw (see {@link serializeValue}).
|
|
228
248
|
* @returns Serialized data ready for PostgreSQL
|
|
229
249
|
*/
|
|
230
|
-
serialize(data) {
|
|
250
|
+
serialize(data, table) {
|
|
231
251
|
const serialized = {};
|
|
232
252
|
for (const [key, value] of Object.entries(data)) {
|
|
233
253
|
if (value === void 0) continue;
|
|
234
|
-
|
|
235
|
-
else if (typeof value === "bigint") serialized[key] = value.toString();
|
|
236
|
-
else if (typeof value === "object" && value !== null && !Array.isArray(value)) serialized[key] = value;
|
|
237
|
-
else if (Array.isArray(value) && value.length > 0 && value.every((v) => typeof v === "number")) serialized[key] = `[${value.join(",")}]`;
|
|
238
|
-
else serialized[key] = value;
|
|
254
|
+
serialized[key] = this.serializeValue(key, value, table);
|
|
239
255
|
}
|
|
240
256
|
return serialized;
|
|
241
257
|
}
|
|
242
258
|
/**
|
|
259
|
+
* Serialize a single column value into a node-pg bindable parameter.
|
|
260
|
+
*
|
|
261
|
+
* Shared by {@link serialize} (INSERT path) and {@link buildUpdateQuery}
|
|
262
|
+
* `$set` (UPDATE path) so both encode `json` / `jsonb` columns identically.
|
|
263
|
+
*
|
|
264
|
+
* Encoding rules (in order):
|
|
265
|
+
* - `Date` → ISO string.
|
|
266
|
+
* - `bigint` → decimal string (node-pg has no native bigint binding).
|
|
267
|
+
* - all-number array → pgvector literal `'[n1,n2,...]'`. node-pg would
|
|
268
|
+
* otherwise emit a `{n1,n2,...}` array literal, which the `vector` type
|
|
269
|
+
* rejects. This branch is preserved exactly.
|
|
270
|
+
* - any other array (object-array, string-array, mixed, empty `[]`) →
|
|
271
|
+
* `JSON.stringify`. node-pg renders a raw JS array as a PostgreSQL array
|
|
272
|
+
* literal `{...}` (and `[]` as `{}`), which a `json` / `jsonb` column
|
|
273
|
+
* rejects — so we bind the value as JSON text instead, the form those
|
|
274
|
+
* columns accept. Columns known to be native arrays — via schema
|
|
275
|
+
* introspection or the `nativeArrayColumns` config — are exempt: their raw
|
|
276
|
+
* array is passed through so node-pg emits the `{...}` literal a genuine
|
|
277
|
+
* `JSONB[]` / `TEXT[]` column needs.
|
|
278
|
+
* - plain object → `JSON.stringify`. Equivalent to node-pg's own object
|
|
279
|
+
* handling, made explicit so both write paths agree.
|
|
280
|
+
* - everything else (scalars: string, number, boolean, null) → untouched.
|
|
281
|
+
*
|
|
282
|
+
* Distinguishing native-array from `json` / `jsonb` columns: a value alone
|
|
283
|
+
* can't tell them apart, so the driver introspects the live schema on connect
|
|
284
|
+
* (see {@link loadNativeArrayColumns}) and consults that per-table map here
|
|
285
|
+
* via {@link isNativeArrayColumn}. The explicit `nativeArrayColumns` config
|
|
286
|
+
* still works as a table-agnostic override. No `::jsonb` placeholder cast is
|
|
287
|
+
* added: a JSON-text string binds correctly to `json` / `jsonb` without one,
|
|
288
|
+
* and a blind cast would misfire on columns we cannot positively identify as
|
|
289
|
+
* jsonb.
|
|
290
|
+
*
|
|
291
|
+
* @param key - Column name (used to resolve native-array columns)
|
|
292
|
+
* @param value - The raw value to serialize (never `undefined`)
|
|
293
|
+
* @param table - Optional table name; enables the per-table native-array lookup
|
|
294
|
+
* @returns The value ready to bind as a query parameter
|
|
295
|
+
*/
|
|
296
|
+
serializeValue(key, value, table) {
|
|
297
|
+
if (value instanceof Date) return value.toISOString();
|
|
298
|
+
if (typeof value === "bigint") return value.toString();
|
|
299
|
+
if (Array.isArray(value)) {
|
|
300
|
+
if (value.length > 0 && value.every((v) => typeof v === "number")) return `[${value.join(",")}]`;
|
|
301
|
+
if (this.isNativeArrayColumn(table, key)) return value;
|
|
302
|
+
return JSON.stringify(value);
|
|
303
|
+
}
|
|
304
|
+
if (typeof value === "object" && value !== null) return JSON.stringify(value);
|
|
305
|
+
return value;
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Whether `column` on `table` is a native PostgreSQL array. True when the
|
|
309
|
+
* connect-time schema introspection saw it as `data_type = 'ARRAY'` for that
|
|
310
|
+
* table (authoritative, per-table), or when it's listed in the table-agnostic
|
|
311
|
+
* `nativeArrayColumns` config override.
|
|
312
|
+
*/
|
|
313
|
+
isNativeArrayColumn(table, column) {
|
|
314
|
+
if (table && this._introspectedArrayColumns.get(table)?.has(column)) return true;
|
|
315
|
+
return this._nativeArrayColumns.has(column);
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Introspect the live schema for native-array columns so array values bind
|
|
319
|
+
* correctly with zero app configuration.
|
|
320
|
+
*
|
|
321
|
+
* A JS array must be bound two opposite ways depending on the column: as JSON
|
|
322
|
+
* text for a `json` / `jsonb` column, but as a raw array (which node-pg
|
|
323
|
+
* renders `{...}`) for a native `TEXT[]` / `JSONB[]` / `INTEGER[]` column. The
|
|
324
|
+
* serializer sees values, not types, so without this it JSON-stringifies
|
|
325
|
+
* every array — which a native-array column rejects with "malformed array
|
|
326
|
+
* literal". One `information_schema` query at connect, cached for the
|
|
327
|
+
* connection lifetime, removes the need to hand-list `nativeArrayColumns`.
|
|
328
|
+
*
|
|
329
|
+
* Best-effort: any failure (e.g. restricted catalog access) is logged and
|
|
330
|
+
* leaves the map empty so the config override still applies — it never blocks
|
|
331
|
+
* connect. A schema change made within a live connection isn't reflected
|
|
332
|
+
* until the next connect.
|
|
333
|
+
*/
|
|
334
|
+
async loadNativeArrayColumns() {
|
|
335
|
+
try {
|
|
336
|
+
const result = await this.query(`SELECT table_name, column_name
|
|
337
|
+
FROM information_schema.columns
|
|
338
|
+
WHERE table_schema = ANY (current_schemas(false))
|
|
339
|
+
AND data_type = 'ARRAY'`);
|
|
340
|
+
const map = /* @__PURE__ */ new Map();
|
|
341
|
+
for (const { table_name, column_name } of result.rows) {
|
|
342
|
+
let columns = map.get(table_name);
|
|
343
|
+
if (!columns) {
|
|
344
|
+
columns = /* @__PURE__ */ new Set();
|
|
345
|
+
map.set(table_name, columns);
|
|
346
|
+
}
|
|
347
|
+
columns.add(column_name);
|
|
348
|
+
}
|
|
349
|
+
this._introspectedArrayColumns = map;
|
|
350
|
+
} catch {
|
|
351
|
+
log.warn("database.postgres", "introspection", "Could not introspect native-array columns; using the nativeArrayColumns config only");
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
243
355
|
* Get the dirty tracker for this driver.
|
|
244
356
|
*/
|
|
245
357
|
getDirtyTracker(data) {
|
|
@@ -288,7 +400,7 @@ var PostgresDriver = class {
|
|
|
288
400
|
* @returns The inserted document
|
|
289
401
|
*/
|
|
290
402
|
async insert(table, document, _options) {
|
|
291
|
-
const serialized = this.serialize(document);
|
|
403
|
+
const serialized = this.serialize(document, table);
|
|
292
404
|
const filteredData = Object.fromEntries(Object.entries(serialized).filter(([key, value]) => {
|
|
293
405
|
if (key === "id" && (value === null || value === void 0)) return false;
|
|
294
406
|
return true;
|
|
@@ -315,7 +427,7 @@ var PostgresDriver = class {
|
|
|
315
427
|
if (documents.length === 0) return [];
|
|
316
428
|
const allColumns = /* @__PURE__ */ new Set();
|
|
317
429
|
for (const doc of documents) {
|
|
318
|
-
const serialized = this.serialize(doc);
|
|
430
|
+
const serialized = this.serialize(doc, table);
|
|
319
431
|
Object.keys(serialized).forEach((key) => allColumns.add(key));
|
|
320
432
|
}
|
|
321
433
|
const columns = Array.from(allColumns);
|
|
@@ -325,7 +437,7 @@ var PostgresDriver = class {
|
|
|
325
437
|
const params = [];
|
|
326
438
|
let paramIndex = 1;
|
|
327
439
|
for (const doc of documents) {
|
|
328
|
-
const serialized = this.serialize(doc);
|
|
440
|
+
const serialized = this.serialize(doc, table);
|
|
329
441
|
const rowPlaceholders = [];
|
|
330
442
|
for (const col of columns) if (col in serialized) {
|
|
331
443
|
rowPlaceholders.push(this.dialect.placeholder(paramIndex++));
|
|
@@ -392,7 +504,7 @@ var PostgresDriver = class {
|
|
|
392
504
|
* @returns The replaced document or null
|
|
393
505
|
*/
|
|
394
506
|
async replace(table, filter, document, _options) {
|
|
395
|
-
const serialized = this.serialize(document);
|
|
507
|
+
const serialized = this.serialize(document, table);
|
|
396
508
|
const columns = Object.keys(serialized);
|
|
397
509
|
const values = Object.values(serialized);
|
|
398
510
|
const quotedTable = this.dialect.quoteIdentifier(table);
|
|
@@ -414,7 +526,7 @@ var PostgresDriver = class {
|
|
|
414
526
|
* @returns The upserted row
|
|
415
527
|
*/
|
|
416
528
|
async upsert(table, filter, document, options) {
|
|
417
|
-
const serialized = this.serialize(document);
|
|
529
|
+
const serialized = this.serialize(document, table);
|
|
418
530
|
const columns = Object.keys(serialized);
|
|
419
531
|
const values = Object.values(serialized);
|
|
420
532
|
if (columns.length === 0) throw new Error("Cannot upsert empty document");
|
|
@@ -540,13 +652,14 @@ var PostgresDriver = class {
|
|
|
540
652
|
* @throws {Error} If transaction fails or is explicitly rolled back
|
|
541
653
|
*/
|
|
542
654
|
async transaction(fn, options) {
|
|
543
|
-
|
|
655
|
+
const ctx = { rollback(reason) {
|
|
656
|
+
throw new TransactionRollbackError(reason);
|
|
657
|
+
} };
|
|
658
|
+
if (databaseTransactionContext.hasActiveTransaction()) return fn(ctx);
|
|
544
659
|
const tx = await this.beginTransaction(options);
|
|
545
660
|
databaseTransactionContext.enter({ session: tx.context });
|
|
546
661
|
try {
|
|
547
|
-
const result = await fn(
|
|
548
|
-
throw new TransactionRollbackError(reason);
|
|
549
|
-
} });
|
|
662
|
+
const result = await fn(ctx);
|
|
550
663
|
await tx.commit();
|
|
551
664
|
return result;
|
|
552
665
|
} catch (error) {
|
|
@@ -708,7 +821,7 @@ var PostgresDriver = class {
|
|
|
708
821
|
let paramIndex = 1;
|
|
709
822
|
if (update.$set) for (const [key, value] of Object.entries(update.$set)) {
|
|
710
823
|
setClauses.push(`${this.dialect.quoteIdentifier(key)} = ${this.dialect.placeholder(paramIndex++)}`);
|
|
711
|
-
params.push(value);
|
|
824
|
+
params.push(value === void 0 ? value : this.serializeValue(key, value, table));
|
|
712
825
|
}
|
|
713
826
|
if (update.$unset) for (const key of Object.keys(update.$unset)) setClauses.push(`${this.dialect.quoteIdentifier(key)} = NULL`);
|
|
714
827
|
if (update.$inc) for (const [key, amount] of Object.entries(update.$inc)) {
|