@atscript/db-postgres 0.1.38 → 0.1.40
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/README.md +59 -0
- package/dist/index-HFavdjnf.d.mts +6 -0
- package/dist/index-Tl2u3rTo.d.cts +6 -0
- package/dist/index.cjs +308 -305
- package/dist/index.d.cts +279 -0
- package/dist/index.d.mts +279 -0
- package/dist/index.mjs +283 -257
- package/dist/plugin-C4tRUZGB.mjs +53 -0
- package/dist/plugin-Cx6tCzk4.cjs +58 -0
- package/dist/plugin.cjs +6 -72
- package/dist/plugin.d.cts +2 -0
- package/dist/plugin.d.mts +2 -0
- package/dist/plugin.mjs +2 -47
- package/package.json +26 -36
- package/LICENSE +0 -21
- package/dist/index.d.ts +0 -278
- package/dist/plugin.d.ts +0 -5
package/dist/index.cjs
CHANGED
|
@@ -1,55 +1,46 @@
|
|
|
1
|
-
"
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
11
|
-
key = keys[i];
|
|
12
|
-
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
13
|
-
get: ((k) => from[k]).bind(null, key),
|
|
14
|
-
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
15
|
-
});
|
|
16
|
-
}
|
|
17
|
-
return to;
|
|
18
|
-
};
|
|
19
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
20
|
-
value: mod,
|
|
21
|
-
enumerable: true
|
|
22
|
-
}) : target, mod));
|
|
23
|
-
|
|
24
|
-
//#endregion
|
|
25
|
-
const __atscript_db = __toESM(require("@atscript/db"));
|
|
26
|
-
const __atscript_db_sql_tools = __toESM(require("@atscript/db-sql-tools"));
|
|
27
|
-
const __atscript_core = __toESM(require("@atscript/core"));
|
|
28
|
-
|
|
29
|
-
//#region packages/db-postgres/src/sql-builder.ts
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_plugin = require("./plugin-Cx6tCzk4.cjs");
|
|
3
|
+
let _atscript_db = require("@atscript/db");
|
|
4
|
+
let _atscript_db_sql_tools = require("@atscript/db-sql-tools");
|
|
5
|
+
//#region src/sql-builder.ts
|
|
6
|
+
/**
|
|
7
|
+
* PostgreSQL-aware default value for a given design type.
|
|
8
|
+
* PG uses native BOOLEAN (not TINYINT), so defaults must be `false`/`true` not `0`/`1`.
|
|
9
|
+
*/
|
|
30
10
|
function defaultValueForType(designType) {
|
|
31
11
|
if (designType === "boolean") return "false";
|
|
32
12
|
if (designType === "json" || designType === "object") return "'{}'";
|
|
33
13
|
if (designType === "array") return "'[]'";
|
|
34
|
-
return (0,
|
|
14
|
+
return (0, _atscript_db_sql_tools.defaultValueForType)(designType);
|
|
35
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* PostgreSQL-aware default value literal.
|
|
18
|
+
* Converts boolean defaults to `true`/`false` instead of `1`/`0`.
|
|
19
|
+
*/
|
|
36
20
|
function defaultValueToSqlLiteral(designType, value) {
|
|
37
21
|
if (designType === "boolean") return value === "true" || value === "1" ? "true" : "false";
|
|
38
|
-
return (0,
|
|
22
|
+
return (0, _atscript_db_sql_tools.defaultValueToSqlLiteral)(designType, value);
|
|
39
23
|
}
|
|
24
|
+
/** Escapes a PostgreSQL identifier by doubling double-quotes. */
|
|
40
25
|
function esc(name) {
|
|
41
26
|
return name.replace(/"/g, "\"\"");
|
|
42
27
|
}
|
|
28
|
+
/** Double-quote-quotes a single identifier. */
|
|
43
29
|
function qi(name) {
|
|
44
30
|
return `"${esc(name)}"`;
|
|
45
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Double-quote-quotes a table name, handling `schema.table` format.
|
|
34
|
+
* Input is a raw name like `public.users` or just `users`.
|
|
35
|
+
*/
|
|
46
36
|
function quoteTableName(name) {
|
|
47
37
|
const dot = name.indexOf(".");
|
|
48
38
|
if (dot >= 0) return `${qi(name.slice(0, dot))}.${qi(name.slice(dot + 1))}`;
|
|
49
39
|
return qi(name);
|
|
50
40
|
}
|
|
51
|
-
/** Converts JS values to SQL-bindable params, keeping booleans native. */
|
|
52
|
-
|
|
41
|
+
/** Converts JS values to SQL-bindable params, keeping booleans native. */
|
|
42
|
+
function toPgValue(value) {
|
|
43
|
+
if (value === void 0) return null;
|
|
53
44
|
if (value === null) return null;
|
|
54
45
|
if (value instanceof Date) return value.toISOString();
|
|
55
46
|
if (typeof value === "object") return JSON.stringify(value);
|
|
@@ -65,7 +56,7 @@ const pgDialect = {
|
|
|
65
56
|
unlimitedLimit: "ALL",
|
|
66
57
|
toValue: toPgValue,
|
|
67
58
|
toParam(value) {
|
|
68
|
-
if (value ===
|
|
59
|
+
if (value === void 0) return null;
|
|
69
60
|
return value;
|
|
70
61
|
},
|
|
71
62
|
regex(quotedCol, value) {
|
|
@@ -81,26 +72,33 @@ const pgDialect = {
|
|
|
81
72
|
}
|
|
82
73
|
};
|
|
83
74
|
function buildInsert(table, data) {
|
|
84
|
-
return (0,
|
|
75
|
+
return (0, _atscript_db_sql_tools.buildInsert)(pgDialect, table, data);
|
|
85
76
|
}
|
|
86
77
|
function buildSelect(table, where, controls) {
|
|
87
|
-
return (0,
|
|
78
|
+
return (0, _atscript_db_sql_tools.buildSelect)(pgDialect, table, where, controls);
|
|
88
79
|
}
|
|
89
|
-
function buildUpdate(table, data, where, limit) {
|
|
90
|
-
return (0,
|
|
80
|
+
function buildUpdate(table, data, where, limit, ops) {
|
|
81
|
+
return (0, _atscript_db_sql_tools.buildUpdate)(pgDialect, table, data, where, limit, ops);
|
|
91
82
|
}
|
|
92
83
|
function buildDelete(table, where, limit) {
|
|
93
|
-
return (0,
|
|
84
|
+
return (0, _atscript_db_sql_tools.buildDelete)(pgDialect, table, where, limit);
|
|
94
85
|
}
|
|
95
86
|
function buildCreateView(viewName, plan, columns, resolveFieldRef) {
|
|
96
|
-
return (0,
|
|
87
|
+
return (0, _atscript_db_sql_tools.buildCreateView)(pgDialect, viewName, plan, columns, resolveFieldRef);
|
|
97
88
|
}
|
|
98
89
|
function buildAggregateSelect(table, where, controls) {
|
|
99
|
-
return (0,
|
|
90
|
+
return (0, _atscript_db_sql_tools.buildAggregateSelect)(pgDialect, table, where, controls);
|
|
100
91
|
}
|
|
101
92
|
function buildAggregateCount(table, where, controls) {
|
|
102
|
-
return (0,
|
|
93
|
+
return (0, _atscript_db_sql_tools.buildAggregateCount)(pgDialect, table, where, controls);
|
|
103
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* Maps portable collation values to PostgreSQL collation names.
|
|
97
|
+
*
|
|
98
|
+
* Returns `null` for `'nocase'` — case-insensitive columns use CITEXT type instead
|
|
99
|
+
* (provisioned via `CREATE EXTENSION IF NOT EXISTS citext` in `ensureTable`).
|
|
100
|
+
* Users can also opt into ICU collation explicitly via `@db.pg.collate "und-u-ks-level2"`.
|
|
101
|
+
*/
|
|
104
102
|
function collationToPg(collation) {
|
|
105
103
|
switch (collation) {
|
|
106
104
|
case "binary": return "\"C\"";
|
|
@@ -114,7 +112,8 @@ function collationToPg(collation) {
|
|
|
114
112
|
* PostgreSQL only has signed integer types:
|
|
115
113
|
* uint16 (0-65535) → INTEGER (not SMALLINT which caps at 32767)
|
|
116
114
|
* uint32 (0-4.3B) → BIGINT (not INTEGER which caps at ~2.1B)
|
|
117
|
-
*/
|
|
115
|
+
*/
|
|
116
|
+
function intTypeFromTags(tags) {
|
|
118
117
|
if (tags?.has("int8") || tags?.has("byte")) return "SMALLINT";
|
|
119
118
|
if (tags?.has("uint8")) return "SMALLINT";
|
|
120
119
|
if (tags?.has("int16") || tags?.has("port")) return "SMALLINT";
|
|
@@ -124,6 +123,12 @@ function collationToPg(collation) {
|
|
|
124
123
|
if (tags?.has("int64") || tags?.has("uint64")) return "BIGINT";
|
|
125
124
|
return "INTEGER";
|
|
126
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* Maps an Atscript field descriptor to a PostgreSQL column type.
|
|
128
|
+
*
|
|
129
|
+
* For FK fields, delegates to the target PK's type via `field.fkTargetField`
|
|
130
|
+
* so the FK column type always matches the referenced column.
|
|
131
|
+
*/
|
|
127
132
|
function pgTypeFromField(field) {
|
|
128
133
|
if (field.fkTargetField) return pgTypeFromField(field.fkTargetField);
|
|
129
134
|
const tags = field.type?.type?.tags;
|
|
@@ -132,36 +137,36 @@ function pgTypeFromField(field) {
|
|
|
132
137
|
if (pgTypeOverride) return pgTypeOverride;
|
|
133
138
|
const precision = metadata?.get("db.column.precision");
|
|
134
139
|
switch (field.designType) {
|
|
135
|
-
case "number":
|
|
140
|
+
case "number":
|
|
136
141
|
if (precision) return `NUMERIC(${precision.precision},${precision.scale})`;
|
|
137
142
|
if (field.defaultValue?.kind === "fn" && field.defaultValue.fn === "increment") return "BIGINT";
|
|
138
143
|
if (field.defaultValue?.kind === "fn" && field.defaultValue.fn === "now") return "BIGINT";
|
|
139
144
|
if (tags?.has("int")) return intTypeFromTags(tags);
|
|
140
145
|
return "DOUBLE PRECISION";
|
|
141
|
-
}
|
|
142
146
|
case "integer": return intTypeFromTags(tags);
|
|
143
|
-
case "decimal":
|
|
147
|
+
case "decimal":
|
|
144
148
|
if (precision) return `NUMERIC(${precision.precision},${precision.scale})`;
|
|
145
149
|
return "NUMERIC(10,2)";
|
|
146
|
-
}
|
|
147
150
|
case "boolean": return "BOOLEAN";
|
|
148
151
|
case "string": {
|
|
149
152
|
if (field.collate === "nocase" && !metadata?.get("db.pg.collate")) return "CITEXT";
|
|
150
153
|
if (tags?.has("char")) return "CHAR(1)";
|
|
151
|
-
const maxLen = metadata?.get("expect.maxLength")?.length;
|
|
152
|
-
if (maxLen !==
|
|
154
|
+
const maxLen = (metadata?.get("expect.maxLength"))?.length;
|
|
155
|
+
if (maxLen !== void 0) return `VARCHAR(${maxLen})`;
|
|
153
156
|
if (field.isPrimaryKey || field.defaultValue) return "VARCHAR(255)";
|
|
154
157
|
return "TEXT";
|
|
155
158
|
}
|
|
156
159
|
case "json":
|
|
157
160
|
case "object":
|
|
158
161
|
case "array": return "JSONB";
|
|
159
|
-
default:
|
|
162
|
+
default:
|
|
160
163
|
if (field.isPrimaryKey || field.defaultValue) return "VARCHAR(255)";
|
|
161
164
|
return "TEXT";
|
|
162
|
-
}
|
|
163
165
|
}
|
|
164
166
|
}
|
|
167
|
+
/**
|
|
168
|
+
* Builds a CREATE TABLE IF NOT EXISTS statement with PostgreSQL syntax.
|
|
169
|
+
*/
|
|
165
170
|
function buildCreateTable(table, fields, foreignKeys, options) {
|
|
166
171
|
const colDefs = [];
|
|
167
172
|
const primaryKeys = fields.filter((f) => f.isPrimaryKey);
|
|
@@ -171,17 +176,17 @@ function buildCreateTable(table, fields, foreignKeys, options) {
|
|
|
171
176
|
let def = `${qi(field.physicalName)} ${sqlType}`;
|
|
172
177
|
if (options?.incrementFields?.has(field.physicalName)) {
|
|
173
178
|
const start = options.autoIncrementStart;
|
|
174
|
-
def += start !==
|
|
179
|
+
def += start !== void 0 ? ` GENERATED BY DEFAULT AS IDENTITY (START WITH ${start})` : " GENERATED BY DEFAULT AS IDENTITY";
|
|
175
180
|
}
|
|
176
181
|
if (!field.optional && !field.isPrimaryKey && !options?.incrementFields?.has(field.physicalName)) def += " NOT NULL";
|
|
177
182
|
if (field.defaultValue?.kind === "value") def += ` DEFAULT ${defaultValueToSqlLiteral(field.designType, field.defaultValue.value)}`;
|
|
178
|
-
else if (field.defaultValue?.kind === "fn") {
|
|
183
|
+
else if (field.defaultValue?.kind === "fn") {
|
|
179
184
|
if (field.defaultValue.fn === "uuid") def += " DEFAULT gen_random_uuid()";
|
|
180
|
-
else if (field.defaultValue.fn === "now") def += " DEFAULT (extract(epoch from now()) * 1000)::bigint";
|
|
185
|
+
else if (field.defaultValue.fn === "now") def += " DEFAULT (extract(epoch from now()) * 1000)::bigint";
|
|
181
186
|
}
|
|
182
187
|
const nativeCollate = field.type?.metadata?.get("db.pg.collate");
|
|
183
188
|
if (nativeCollate) def += ` COLLATE "${nativeCollate}"`;
|
|
184
|
-
else if (field.collate) {
|
|
189
|
+
else if (field.collate) {
|
|
185
190
|
const pgCollate = collationToPg(field.collate);
|
|
186
191
|
if (pgCollate) def += ` COLLATE ${pgCollate}`;
|
|
187
192
|
}
|
|
@@ -201,47 +206,88 @@ else if (field.collate) {
|
|
|
201
206
|
const localCols = fk.fields.map((f) => qi(f)).join(", ");
|
|
202
207
|
const targetCols = fk.targetFields.map((f) => qi(f)).join(", ");
|
|
203
208
|
let constraint = `FOREIGN KEY (${localCols}) REFERENCES ${qi(fk.targetTable)} (${targetCols})`;
|
|
204
|
-
if (fk.onDelete) constraint += ` ON DELETE ${(0,
|
|
205
|
-
if (fk.onUpdate) constraint += ` ON UPDATE ${(0,
|
|
209
|
+
if (fk.onDelete) constraint += ` ON DELETE ${(0, _atscript_db_sql_tools.refActionToSql)(fk.onDelete)}`;
|
|
210
|
+
if (fk.onUpdate) constraint += ` ON UPDATE ${(0, _atscript_db_sql_tools.refActionToSql)(fk.onUpdate)}`;
|
|
206
211
|
colDefs.push(constraint);
|
|
207
212
|
}
|
|
208
213
|
return `CREATE TABLE IF NOT EXISTS ${quoteTableName(table)} (${colDefs.join(", ")})`;
|
|
209
214
|
}
|
|
215
|
+
/**
|
|
216
|
+
* Offsets numbered placeholders ($1, $2, ...) in a SQL fragment by a given amount.
|
|
217
|
+
* Used when WHERE params follow SET params in UPDATE statements.
|
|
218
|
+
*/
|
|
210
219
|
function offsetPlaceholders(fragment, offset) {
|
|
211
220
|
if (offset === 0) return fragment;
|
|
212
|
-
const sql = fragment.sql.replace(/\$(\d+)/g, (_, n) => `$${Number(n) + offset}`);
|
|
213
221
|
return {
|
|
214
|
-
sql,
|
|
222
|
+
sql: fragment.sql.replace(/\$(\d+)/g, (_, n) => `$${Number(n) + offset}`),
|
|
215
223
|
params: fragment.params
|
|
216
224
|
};
|
|
217
225
|
}
|
|
218
|
-
|
|
219
226
|
//#endregion
|
|
220
|
-
//#region
|
|
227
|
+
//#region src/filter-builder.ts
|
|
228
|
+
/**
|
|
229
|
+
* Translates a filter expression into a parameterized PostgreSQL WHERE clause.
|
|
230
|
+
*
|
|
231
|
+
* Note: The returned fragment uses `?` placeholders (not `$N`).
|
|
232
|
+
* Finalization to `$N` happens when the fragment is consumed by a DML builder
|
|
233
|
+
* (buildSelect, buildUpdate, etc.) via the dialect's `paramPlaceholder`.
|
|
234
|
+
*
|
|
235
|
+
* Case-insensitive columns (`@db.collate 'nocase'`) are handled by CITEXT
|
|
236
|
+
* column type at the storage level — no query-side wrapping needed.
|
|
237
|
+
*/
|
|
221
238
|
function buildWhere(filter) {
|
|
222
|
-
return (0,
|
|
239
|
+
return (0, _atscript_db_sql_tools.buildWhere)(pgDialect, filter);
|
|
223
240
|
}
|
|
224
|
-
|
|
225
241
|
//#endregion
|
|
226
|
-
//#region
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
value,
|
|
230
|
-
enumerable: true,
|
|
231
|
-
configurable: true,
|
|
232
|
-
writable: true
|
|
233
|
-
});
|
|
234
|
-
else obj[key] = value;
|
|
235
|
-
return obj;
|
|
236
|
-
}
|
|
237
|
-
/** PostgreSQL COUNT() may return string (bigint) — parse to number. */ function parseCount(value) {
|
|
242
|
+
//#region src/postgres-adapter.ts
|
|
243
|
+
/** PostgreSQL COUNT() may return string (bigint) — parse to number. */
|
|
244
|
+
function parseCount(value) {
|
|
238
245
|
if (typeof value === "string") return Number.parseInt(value, 10);
|
|
239
246
|
return value ?? 0;
|
|
240
247
|
}
|
|
241
|
-
|
|
242
|
-
|
|
248
|
+
/**
|
|
249
|
+
* PostgreSQL adapter for {@link AtscriptDbTable}.
|
|
250
|
+
*
|
|
251
|
+
* Accepts any {@link TPgDriver} implementation — the actual PostgreSQL driver
|
|
252
|
+
* is fully swappable (pg Pool, custom implementations, etc.).
|
|
253
|
+
*
|
|
254
|
+
* Usage:
|
|
255
|
+
* ```typescript
|
|
256
|
+
* import { PgDriver, PostgresAdapter } from '@atscript/db-postgres'
|
|
257
|
+
* import { DbSpace } from '@atscript/db'
|
|
258
|
+
*
|
|
259
|
+
* const driver = new PgDriver('postgresql://user@localhost:5432/mydb')
|
|
260
|
+
* const space = new DbSpace(() => new PostgresAdapter(driver))
|
|
261
|
+
* const users = space.getTable(UsersType)
|
|
262
|
+
* ```
|
|
263
|
+
*/
|
|
264
|
+
var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
|
|
265
|
+
supportsColumnModify = true;
|
|
266
|
+
static NATIVE_DEFAULT_FNS = new Set([
|
|
267
|
+
"now",
|
|
268
|
+
"uuid",
|
|
269
|
+
"increment"
|
|
270
|
+
]);
|
|
271
|
+
_incrementFields = /* @__PURE__ */ new Set();
|
|
272
|
+
_autoIncrementStart;
|
|
273
|
+
/** Physical column names with @db.collate 'nocase'. Used to trigger CITEXT extension. */
|
|
274
|
+
_nocaseColumns = /* @__PURE__ */ new Set();
|
|
275
|
+
/** Whether citext extension has been provisioned (avoids redundant round-trips). */
|
|
276
|
+
_citextProvisioned = false;
|
|
277
|
+
/** Whether the connected PostgreSQL instance has the pgvector extension. */
|
|
278
|
+
_supportsVector;
|
|
279
|
+
/** Vector fields: physical field name → { dimensions, similarity, indexName }. */
|
|
280
|
+
_vectorFields = /* @__PURE__ */ new Map();
|
|
281
|
+
/** Default similarity thresholds per vector field (from @db.search.vector.threshold). */
|
|
282
|
+
_vectorThresholds = /* @__PURE__ */ new Map();
|
|
283
|
+
/** Schema name for queries (null falls through to 'public'). */
|
|
284
|
+
get _schema() {
|
|
243
285
|
return this._table.schema ?? null;
|
|
244
286
|
}
|
|
287
|
+
constructor(driver) {
|
|
288
|
+
super();
|
|
289
|
+
this.driver = driver;
|
|
290
|
+
}
|
|
245
291
|
async _beginTransaction() {
|
|
246
292
|
const conn = await this.driver.getConnection();
|
|
247
293
|
try {
|
|
@@ -274,11 +320,12 @@ var PostgresAdapter = class PostgresAdapter extends __atscript_db.BaseDbAdapter
|
|
|
274
320
|
/**
|
|
275
321
|
* Returns the active executor: dedicated connection if inside a transaction,
|
|
276
322
|
* otherwise the pool-based driver.
|
|
277
|
-
*/
|
|
278
|
-
|
|
279
|
-
return
|
|
323
|
+
*/
|
|
324
|
+
_exec() {
|
|
325
|
+
return this._getTransactionState() ?? this.driver;
|
|
280
326
|
}
|
|
281
|
-
/** PostgreSQL enforces FK constraints natively. */
|
|
327
|
+
/** PostgreSQL enforces FK constraints natively. */
|
|
328
|
+
supportsNativeForeignKeys() {
|
|
282
329
|
return true;
|
|
283
330
|
}
|
|
284
331
|
prepareId(id, _fieldType) {
|
|
@@ -309,7 +356,7 @@ var PostgresAdapter = class PostgresAdapter extends __atscript_db.BaseDbAdapter
|
|
|
309
356
|
indexName
|
|
310
357
|
});
|
|
311
358
|
const threshold = metadata.get("db.search.vector.threshold");
|
|
312
|
-
if (threshold !==
|
|
359
|
+
if (threshold !== void 0) this._vectorThresholds.set(indexName, threshold);
|
|
313
360
|
}
|
|
314
361
|
}
|
|
315
362
|
getDesiredTableOptions() {
|
|
@@ -322,8 +369,9 @@ var PostgresAdapter = class PostgresAdapter extends __atscript_db.BaseDbAdapter
|
|
|
322
369
|
* Converts vector fields between JavaScript `number[]` and pgvector text format `[1,2,3]`.
|
|
323
370
|
* The pg driver serializes JS arrays as PostgreSQL array literals `{1,2,3}` which is
|
|
324
371
|
* invalid for the pgvector `vector` type — it expects bracket-delimited `[1,2,3]`.
|
|
325
|
-
*/
|
|
326
|
-
|
|
372
|
+
*/
|
|
373
|
+
formatValue(field) {
|
|
374
|
+
if (!this._vectorFields.has(field.path)) return;
|
|
327
375
|
return {
|
|
328
376
|
toStorage: (value) => Array.isArray(value) ? `[${value.join(",")}]` : value,
|
|
329
377
|
fromStorage: (value) => typeof value === "string" ? JSON.parse(value) : value
|
|
@@ -336,29 +384,24 @@ var PostgresAdapter = class PostgresAdapter extends __atscript_db.BaseDbAdapter
|
|
|
336
384
|
* PostgreSQL uses SQLSTATE codes:
|
|
337
385
|
* - 23505 = unique_violation
|
|
338
386
|
* - 23503 = foreign_key_violation
|
|
339
|
-
*/
|
|
387
|
+
*/
|
|
388
|
+
async _wrapConstraintError(fn) {
|
|
340
389
|
try {
|
|
341
390
|
return await fn();
|
|
342
391
|
} catch (error) {
|
|
343
392
|
if (error && typeof error === "object" && "code" in error) {
|
|
344
393
|
const err = error;
|
|
345
|
-
if (err.code === "23505") {
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
}]);
|
|
351
|
-
}
|
|
352
|
-
if (err.code === "23503") {
|
|
353
|
-
const errors = this._mapFkError(err.detail ?? err.message, err.constraint);
|
|
354
|
-
throw new __atscript_db.DbError("FK_VIOLATION", errors);
|
|
355
|
-
}
|
|
394
|
+
if (err.code === "23505") throw new _atscript_db.DbError("CONFLICT", [{
|
|
395
|
+
path: this._extractFieldFromConstraint(err.constraint) ?? "",
|
|
396
|
+
message: err.detail ?? err.message
|
|
397
|
+
}]);
|
|
398
|
+
if (err.code === "23503") throw new _atscript_db.DbError("FK_VIOLATION", this._mapFkError(err.detail ?? err.message, err.constraint));
|
|
356
399
|
}
|
|
357
400
|
throw error;
|
|
358
401
|
}
|
|
359
402
|
}
|
|
360
403
|
_extractFieldFromConstraint(constraint) {
|
|
361
|
-
if (!constraint) return
|
|
404
|
+
if (!constraint) return;
|
|
362
405
|
const tableName = this._table.tableName;
|
|
363
406
|
if (constraint.startsWith(`${tableName}_`) && constraint.endsWith("_key")) {
|
|
364
407
|
const fieldPart = constraint.slice(tableName.length + 1, -4);
|
|
@@ -371,9 +414,8 @@ var PostgresAdapter = class PostgresAdapter extends __atscript_db.BaseDbAdapter
|
|
|
371
414
|
const fkMatch = detail.match(/Key \(([^)]+)\)/);
|
|
372
415
|
if (fkMatch) {
|
|
373
416
|
const physicalCol = fkMatch[1].split(",")[0].trim();
|
|
374
|
-
const field = this._table.fieldDescriptors.find((f) => f.physicalName === physicalCol);
|
|
375
417
|
return [{
|
|
376
|
-
path:
|
|
418
|
+
path: this._table.fieldDescriptors.find((f) => f.physicalName === physicalCol)?.path ?? physicalCol,
|
|
377
419
|
message: detail
|
|
378
420
|
}];
|
|
379
421
|
}
|
|
@@ -387,9 +429,8 @@ var PostgresAdapter = class PostgresAdapter extends __atscript_db.BaseDbAdapter
|
|
|
387
429
|
const pkCols = this._table.primaryKeys.map((pk) => qi(pk));
|
|
388
430
|
if (pkCols.length > 0) sql += ` RETURNING ${pkCols.join(", ")}`;
|
|
389
431
|
this._log(sql, params);
|
|
390
|
-
const
|
|
391
|
-
|
|
392
|
-
return { insertedId: this._resolveInsertedId(data, returned ? Object.values(returned)[0] : undefined) };
|
|
432
|
+
const returned = (await this._wrapConstraintError(() => this._exec().run(sql, params))).rows?.[0];
|
|
433
|
+
return { insertedId: this._resolveInsertedId(data, returned ? Object.values(returned)[0] : void 0) };
|
|
393
434
|
}
|
|
394
435
|
async insertMany(data) {
|
|
395
436
|
if (data.length === 0) return {
|
|
@@ -424,7 +465,7 @@ var PostgresAdapter = class PostgresAdapter extends __atscript_db.BaseDbAdapter
|
|
|
424
465
|
const result = await this._wrapConstraintError(() => this._exec().run(sql, params));
|
|
425
466
|
for (let i = 0; i < batchSize; i++) {
|
|
426
467
|
const returned = result.rows?.[i];
|
|
427
|
-
allIds.push(this._resolveInsertedId(data[offset + i], returned ? Object.values(returned)[0] :
|
|
468
|
+
allIds.push(this._resolveInsertedId(data[offset + i], returned ? Object.values(returned)[0] : void 0));
|
|
428
469
|
}
|
|
429
470
|
}
|
|
430
471
|
return {
|
|
@@ -451,40 +492,32 @@ var PostgresAdapter = class PostgresAdapter extends __atscript_db.BaseDbAdapter
|
|
|
451
492
|
}
|
|
452
493
|
async count(query) {
|
|
453
494
|
const where = buildWhere(query.filter);
|
|
454
|
-
const
|
|
455
|
-
|
|
456
|
-
sql: `SELECT COUNT(*) as cnt FROM ${quoteTableName(tableName)} WHERE ${where.sql}`,
|
|
495
|
+
const { sql, params } = (0, _atscript_db_sql_tools.finalizeParams)(pgDialect, {
|
|
496
|
+
sql: `SELECT COUNT(*) as cnt FROM ${quoteTableName(this.resolveTableName())} WHERE ${where.sql}`,
|
|
457
497
|
params: where.params
|
|
458
|
-
};
|
|
459
|
-
const { sql, params } = (0, __atscript_db_sql_tools.finalizeParams)(pgDialect, raw);
|
|
498
|
+
});
|
|
460
499
|
this._log(sql, params);
|
|
461
|
-
|
|
462
|
-
return parseCount(row?.cnt);
|
|
500
|
+
return parseCount((await this._exec().get(sql, params))?.cnt);
|
|
463
501
|
}
|
|
464
502
|
async aggregate(query) {
|
|
465
503
|
const where = buildWhere(query.filter);
|
|
466
504
|
const tableName = this.resolveTableName();
|
|
467
505
|
if (query.controls.$count) {
|
|
468
|
-
const { sql
|
|
469
|
-
this._log(sql
|
|
470
|
-
|
|
471
|
-
const count = parseCount(row?.count);
|
|
472
|
-
return [{ count }];
|
|
506
|
+
const { sql, params } = buildAggregateCount(tableName, where, query.controls);
|
|
507
|
+
this._log(sql, params);
|
|
508
|
+
return [{ count: parseCount((await this._exec().get(sql, params))?.count) }];
|
|
473
509
|
}
|
|
474
510
|
const { sql, params } = buildAggregateSelect(tableName, where, query.controls);
|
|
475
511
|
this._log(sql, params);
|
|
476
512
|
return this._exec().all(sql, params);
|
|
477
513
|
}
|
|
478
|
-
async updateOne(filter, data) {
|
|
514
|
+
async updateOne(filter, data, ops) {
|
|
479
515
|
const where = buildWhere(filter);
|
|
480
516
|
const tableName = this.resolveTableName();
|
|
481
|
-
const
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
const offsetWhere = offsetPlaceholders(finalizedWhere, keys.length);
|
|
486
|
-
const sql = `UPDATE ${quoteTableName(tableName)} SET ${setClauses.join(", ")} WHERE ctid = (SELECT ctid FROM ${quoteTableName(tableName)} WHERE ${offsetWhere.sql} LIMIT 1)`;
|
|
487
|
-
const params = [...setParams, ...where.params];
|
|
517
|
+
const { sql, params } = buildUpdate(tableName, data, {
|
|
518
|
+
sql: `ctid = (SELECT ctid FROM ${quoteTableName(tableName)} WHERE ${where.sql} LIMIT 1)`,
|
|
519
|
+
params: where.params
|
|
520
|
+
}, void 0, ops);
|
|
488
521
|
this._log(sql, params);
|
|
489
522
|
const result = await this._wrapConstraintError(() => this._exec().run(sql, params));
|
|
490
523
|
return {
|
|
@@ -492,9 +525,9 @@ var PostgresAdapter = class PostgresAdapter extends __atscript_db.BaseDbAdapter
|
|
|
492
525
|
modifiedCount: result.affectedRows
|
|
493
526
|
};
|
|
494
527
|
}
|
|
495
|
-
async updateMany(filter, data) {
|
|
528
|
+
async updateMany(filter, data, ops) {
|
|
496
529
|
const where = buildWhere(filter);
|
|
497
|
-
const { sql, params } = buildUpdate(this.resolveTableName(), data, where);
|
|
530
|
+
const { sql, params } = buildUpdate(this.resolveTableName(), data, where, void 0, ops);
|
|
498
531
|
this._log(sql, params);
|
|
499
532
|
const result = await this._wrapConstraintError(() => this._exec().run(sql, params));
|
|
500
533
|
return {
|
|
@@ -511,21 +544,18 @@ var PostgresAdapter = class PostgresAdapter extends __atscript_db.BaseDbAdapter
|
|
|
511
544
|
async deleteOne(filter) {
|
|
512
545
|
const where = buildWhere(filter);
|
|
513
546
|
const tableName = this.resolveTableName();
|
|
514
|
-
const
|
|
547
|
+
const { sql, params } = (0, _atscript_db_sql_tools.finalizeParams)(pgDialect, {
|
|
515
548
|
sql: `DELETE FROM ${quoteTableName(tableName)} WHERE ctid = (SELECT ctid FROM ${quoteTableName(tableName)} WHERE ${where.sql} LIMIT 1)`,
|
|
516
549
|
params: where.params
|
|
517
|
-
};
|
|
518
|
-
const { sql, params } = (0, __atscript_db_sql_tools.finalizeParams)(pgDialect, raw);
|
|
550
|
+
});
|
|
519
551
|
this._log(sql, params);
|
|
520
|
-
|
|
521
|
-
return { deletedCount: result.affectedRows };
|
|
552
|
+
return { deletedCount: (await this._wrapConstraintError(() => this._exec().run(sql, params))).affectedRows };
|
|
522
553
|
}
|
|
523
554
|
async deleteMany(filter) {
|
|
524
555
|
const where = buildWhere(filter);
|
|
525
556
|
const { sql, params } = buildDelete(this.resolveTableName(), where);
|
|
526
557
|
this._log(sql, params);
|
|
527
|
-
|
|
528
|
-
return { deletedCount: result.affectedRows };
|
|
558
|
+
return { deletedCount: (await this._wrapConstraintError(() => this._exec().run(sql, params))).affectedRows };
|
|
529
559
|
}
|
|
530
560
|
async ensureTable() {
|
|
531
561
|
if (this._nocaseColumns.size > 0 && !this._citextProvisioned) try {
|
|
@@ -533,10 +563,10 @@ var PostgresAdapter = class PostgresAdapter extends __atscript_db.BaseDbAdapter
|
|
|
533
563
|
this._citextProvisioned = true;
|
|
534
564
|
} catch (err) {
|
|
535
565
|
const msg = err instanceof Error ? err.message : String(err);
|
|
536
|
-
throw new Error(`Failed to create citext extension for @db.collate 'nocase' columns: ${msg}.
|
|
566
|
+
throw new Error(`Failed to create citext extension for @db.collate 'nocase' columns: ${msg}. Either run 'CREATE EXTENSION citext' as a superuser, or use @db.pg.type "CITEXT" after provisioning the extension manually.`, { cause: err });
|
|
537
567
|
}
|
|
538
|
-
if (this._supportsVector ===
|
|
539
|
-
if (this._table instanceof
|
|
568
|
+
if (this._supportsVector === void 0 && this._vectorFields.size > 0) await this._detectVectorSupport();
|
|
569
|
+
if (this._table instanceof _atscript_db.AtscriptDbView) return this._ensureView();
|
|
540
570
|
const sql = buildCreateTable(this.resolveTableName(), this._table.fieldDescriptors, this._table.foreignKeys, {
|
|
541
571
|
incrementFields: this._incrementFields,
|
|
542
572
|
autoIncrementStart: this._autoIncrementStart,
|
|
@@ -593,18 +623,18 @@ var PostgresAdapter = class PostgresAdapter extends __atscript_db.BaseDbAdapter
|
|
|
593
623
|
const sqlType = this.typeMapper(field);
|
|
594
624
|
let ddl = `ALTER TABLE ${quoteTableName(tableName)} ADD COLUMN ${qi(field.physicalName)} ${sqlType}`;
|
|
595
625
|
if (field.defaultValue?.kind === "fn" && field.defaultValue.fn === "increment") ddl += " GENERATED BY DEFAULT AS IDENTITY";
|
|
596
|
-
else {
|
|
626
|
+
else {
|
|
597
627
|
if (!field.optional && !field.isPrimaryKey) ddl += " NOT NULL";
|
|
598
628
|
if (field.defaultValue?.kind === "value") ddl += ` DEFAULT ${defaultValueToSqlLiteral(field.designType, field.defaultValue.value)}`;
|
|
599
|
-
else if (field.defaultValue?.kind === "fn") {
|
|
629
|
+
else if (field.defaultValue?.kind === "fn") {
|
|
600
630
|
if (field.defaultValue.fn === "uuid") ddl += " DEFAULT gen_random_uuid()";
|
|
601
|
-
else if (field.defaultValue.fn === "now") ddl += " DEFAULT (extract(epoch from now()) * 1000)::bigint";
|
|
631
|
+
else if (field.defaultValue.fn === "now") ddl += " DEFAULT (extract(epoch from now()) * 1000)::bigint";
|
|
602
632
|
} else if (!field.optional && !field.isPrimaryKey) ddl += ` DEFAULT ${defaultValueForType(field.designType)}`;
|
|
603
633
|
}
|
|
604
634
|
if (field.collate) {
|
|
605
635
|
const nativeCollate = field.type?.metadata?.get("db.pg.collate");
|
|
606
636
|
if (nativeCollate) ddl += ` COLLATE "${nativeCollate}"`;
|
|
607
|
-
else {
|
|
637
|
+
else {
|
|
608
638
|
const pgCollate = collationToPg(field.collate);
|
|
609
639
|
if (pgCollate) ddl += ` COLLATE ${pgCollate}`;
|
|
610
640
|
}
|
|
@@ -635,7 +665,7 @@ else {
|
|
|
635
665
|
for (const { field } of diff.defaultChanged ?? []) {
|
|
636
666
|
let ddl;
|
|
637
667
|
if (field.defaultValue?.kind === "value") ddl = `ALTER TABLE ${quoteTableName(tableName)} ALTER COLUMN ${qi(field.physicalName)} SET DEFAULT ${defaultValueToSqlLiteral(field.designType, field.defaultValue.value)}`;
|
|
638
|
-
else if (field.defaultValue?.kind === "fn") {
|
|
668
|
+
else if (field.defaultValue?.kind === "fn") {
|
|
639
669
|
const fnExpr = field.defaultValue.fn === "now" ? "(extract(epoch from now()) * 1000)::bigint" : field.defaultValue.fn === "uuid" ? "gen_random_uuid()" : `${field.defaultValue.fn}()`;
|
|
640
670
|
ddl = `ALTER TABLE ${quoteTableName(tableName)} ALTER COLUMN ${qi(field.physicalName)} SET DEFAULT ${fnExpr}`;
|
|
641
671
|
} else ddl = `ALTER TABLE ${quoteTableName(tableName)} ALTER COLUMN ${qi(field.physicalName)} DROP DEFAULT`;
|
|
@@ -671,7 +701,7 @@ else if (field.defaultValue?.kind === "fn") {
|
|
|
671
701
|
SELECT constraint_name FROM information_schema.table_constraints
|
|
672
702
|
WHERE table_name = $2 AND table_schema = COALESCE($1, 'public') AND constraint_type = 'PRIMARY KEY'
|
|
673
703
|
)`, [schema, this._table.tableName]);
|
|
674
|
-
const fkByName = new Map();
|
|
704
|
+
const fkByName = /* @__PURE__ */ new Map();
|
|
675
705
|
for (const fk of fkRefs) {
|
|
676
706
|
let entry = fkByName.get(fk.constraint_name);
|
|
677
707
|
if (!entry) {
|
|
@@ -750,7 +780,8 @@ else if (field.defaultValue?.kind === "fn") {
|
|
|
750
780
|
* value doesn't conflict with existing data. PostgreSQL's GENERATED BY DEFAULT
|
|
751
781
|
* AS IDENTITY does not advance the sequence when rows are inserted with explicit
|
|
752
782
|
* values, so this is needed after data seeding, bulk imports, or recreateTable().
|
|
753
|
-
*/
|
|
783
|
+
*/
|
|
784
|
+
async _resetIdentitySequences() {
|
|
754
785
|
if (this._incrementFields.size === 0) return;
|
|
755
786
|
const tableName = this.resolveTableName();
|
|
756
787
|
const emptyFallback = this._autoIncrementStart ?? 1;
|
|
@@ -762,8 +793,7 @@ else if (field.defaultValue?.kind === "fn") {
|
|
|
762
793
|
}
|
|
763
794
|
}
|
|
764
795
|
async tableExists() {
|
|
765
|
-
|
|
766
|
-
return row?.exists ?? false;
|
|
796
|
+
return (await this._exec().get(`SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = $1 AND table_schema = COALESCE($2, 'public')) AS "exists"`, [this._table.tableName, this._schema]))?.exists ?? false;
|
|
767
797
|
}
|
|
768
798
|
async dropTable() {
|
|
769
799
|
const ddl = `DROP TABLE IF EXISTS ${quoteTableName(this.resolveTableName())} CASCADE`;
|
|
@@ -809,9 +839,9 @@ else if (field.defaultValue?.kind === "fn") {
|
|
|
809
839
|
createIndex: async (index) => {
|
|
810
840
|
if (index.type === "fulltext") {
|
|
811
841
|
const tsvectorExpr = this._buildTsvectorExpr(index.fields);
|
|
812
|
-
const sql
|
|
813
|
-
this._log(sql
|
|
814
|
-
await this._exec().exec(sql
|
|
842
|
+
const sql = `CREATE INDEX IF NOT EXISTS ${qi(index.key)} ON ${quoteTableName(this.resolveTableName())} USING gin(to_tsvector('english', ${tsvectorExpr}))`;
|
|
843
|
+
this._log(sql);
|
|
844
|
+
await this._exec().exec(sql);
|
|
815
845
|
return;
|
|
816
846
|
}
|
|
817
847
|
const unique = index.type === "unique" ? "UNIQUE " : "";
|
|
@@ -821,8 +851,7 @@ else if (field.defaultValue?.kind === "fn") {
|
|
|
821
851
|
await this._exec().exec(sql);
|
|
822
852
|
},
|
|
823
853
|
dropIndex: async (name) => {
|
|
824
|
-
const
|
|
825
|
-
const sql = `DROP INDEX IF EXISTS ${schemaPrefix}${qi(name)}`;
|
|
854
|
+
const sql = `DROP INDEX IF EXISTS ${schema ? `${qi(schema)}.` : ""}${qi(name)}`;
|
|
826
855
|
this._log(sql);
|
|
827
856
|
await this._exec().exec(sql);
|
|
828
857
|
}
|
|
@@ -837,25 +866,25 @@ else if (field.defaultValue?.kind === "fn") {
|
|
|
837
866
|
}
|
|
838
867
|
async syncForeignKeys() {
|
|
839
868
|
const existingByName = await this._getExistingFkConstraints();
|
|
840
|
-
const desiredFkKeys = new Set();
|
|
841
|
-
for (const fk of this._table.foreignKeys.values()) desiredFkKeys.add([...fk.fields].
|
|
869
|
+
const desiredFkKeys = /* @__PURE__ */ new Set();
|
|
870
|
+
for (const fk of this._table.foreignKeys.values()) desiredFkKeys.add([...fk.fields].toSorted().join(","));
|
|
842
871
|
for (const [constraintName, columns] of existingByName) {
|
|
843
|
-
const key = [...columns].
|
|
872
|
+
const key = [...columns].toSorted().join(",");
|
|
844
873
|
if (!desiredFkKeys.has(key)) {
|
|
845
874
|
const ddl = `ALTER TABLE ${quoteTableName(this.resolveTableName())} DROP CONSTRAINT ${qi(constraintName)}`;
|
|
846
875
|
this._log(ddl);
|
|
847
876
|
await this._exec().exec(ddl);
|
|
848
877
|
}
|
|
849
878
|
}
|
|
850
|
-
const existingKeys = new Set([...existingByName.values()].map((cols) => cols.
|
|
879
|
+
const existingKeys = new Set([...existingByName.values()].map((cols) => cols.toSorted().join(",")));
|
|
851
880
|
for (const fk of this._table.foreignKeys.values()) {
|
|
852
|
-
const key = [...fk.fields].
|
|
881
|
+
const key = [...fk.fields].toSorted().join(",");
|
|
853
882
|
if (!existingKeys.has(key)) {
|
|
854
883
|
const localCols = fk.fields.map((f) => qi(f)).join(", ");
|
|
855
884
|
const targetCols = fk.targetFields.map((f) => qi(f)).join(", ");
|
|
856
885
|
let ddl = `ALTER TABLE ${quoteTableName(this.resolveTableName())} ADD FOREIGN KEY (${localCols}) REFERENCES ${qi(fk.targetTable)} (${targetCols})`;
|
|
857
|
-
if (fk.onDelete) ddl += ` ON DELETE ${(0,
|
|
858
|
-
if (fk.onUpdate) ddl += ` ON UPDATE ${(0,
|
|
886
|
+
if (fk.onDelete) ddl += ` ON DELETE ${(0, _atscript_db_sql_tools.refActionToSql)(fk.onDelete)}`;
|
|
887
|
+
if (fk.onUpdate) ddl += ` ON UPDATE ${(0, _atscript_db_sql_tools.refActionToSql)(fk.onUpdate)}`;
|
|
859
888
|
this._log(ddl);
|
|
860
889
|
await this._exec().exec(ddl);
|
|
861
890
|
}
|
|
@@ -866,7 +895,7 @@ else if (field.defaultValue?.kind === "fn") {
|
|
|
866
895
|
const keySet = new Set(fkFieldKeys);
|
|
867
896
|
const existingByName = await this._getExistingFkConstraints();
|
|
868
897
|
for (const [constraintName, cols] of existingByName) {
|
|
869
|
-
const key = cols.
|
|
898
|
+
const key = cols.toSorted().join(",");
|
|
870
899
|
if (keySet.has(key)) {
|
|
871
900
|
const ddl = `ALTER TABLE ${quoteTableName(this.resolveTableName())} DROP CONSTRAINT ${qi(constraintName)}`;
|
|
872
901
|
this._log(ddl);
|
|
@@ -874,14 +903,15 @@ else if (field.defaultValue?.kind === "fn") {
|
|
|
874
903
|
}
|
|
875
904
|
}
|
|
876
905
|
}
|
|
877
|
-
/** Queries information_schema for existing FK constraints. */
|
|
906
|
+
/** Queries information_schema for existing FK constraints. */
|
|
907
|
+
async _getExistingFkConstraints() {
|
|
878
908
|
const rows = await this._exec().all(`SELECT kcu.constraint_name, kcu.column_name
|
|
879
909
|
FROM information_schema.table_constraints tc
|
|
880
910
|
JOIN information_schema.key_column_usage kcu
|
|
881
911
|
ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema
|
|
882
912
|
WHERE tc.table_name = $1 AND tc.table_schema = COALESCE($2, 'public')
|
|
883
913
|
AND tc.constraint_type = 'FOREIGN KEY'`, [this._table.tableName, this._schema]);
|
|
884
|
-
const byName = new Map();
|
|
914
|
+
const byName = /* @__PURE__ */ new Map();
|
|
885
915
|
for (const row of rows) {
|
|
886
916
|
let cols = byName.get(row.constraint_name);
|
|
887
917
|
if (!cols) {
|
|
@@ -926,14 +956,12 @@ else if (field.defaultValue?.kind === "fn") {
|
|
|
926
956
|
return this._exec().all(sql, params);
|
|
927
957
|
})();
|
|
928
958
|
const countPromise = (async () => {
|
|
929
|
-
const
|
|
959
|
+
const { sql, params } = (0, _atscript_db_sql_tools.finalizeParams)(pgDialect, {
|
|
930
960
|
sql: `SELECT COUNT(*) as cnt FROM ${quoteTableName(tableName)} WHERE ${combinedWhere.sql}`,
|
|
931
961
|
params: combinedWhere.params
|
|
932
|
-
};
|
|
933
|
-
const { sql, params } = (0, __atscript_db_sql_tools.finalizeParams)(pgDialect, raw);
|
|
962
|
+
});
|
|
934
963
|
this._log(sql, params);
|
|
935
|
-
|
|
936
|
-
return parseCount(row?.cnt);
|
|
964
|
+
return parseCount((await this._exec().get(sql, params))?.cnt);
|
|
937
965
|
})();
|
|
938
966
|
const [data, count] = await Promise.all([selectPromise, countPromise]);
|
|
939
967
|
return {
|
|
@@ -952,20 +980,21 @@ else if (field.defaultValue?.kind === "fn") {
|
|
|
952
980
|
params: [...where.params, text]
|
|
953
981
|
};
|
|
954
982
|
}
|
|
955
|
-
/** Builds the tsvector SQL expression for a fulltext index's fields. Must match between index DDL and queries. */
|
|
983
|
+
/** Builds the tsvector SQL expression for a fulltext index's fields. Must match between index DDL and queries. */
|
|
984
|
+
_buildTsvectorExpr(fields) {
|
|
956
985
|
return fields.map((f) => `coalesce(${qi(f.name)}, '')`).join(" || ' ' || ");
|
|
957
986
|
}
|
|
958
987
|
_getFulltextIndex(indexName) {
|
|
959
988
|
for (const index of this._table.indexes.values()) if (index.type === "fulltext") {
|
|
960
989
|
if (!indexName || index.key === indexName) return index;
|
|
961
990
|
}
|
|
962
|
-
return undefined;
|
|
963
991
|
}
|
|
964
992
|
/**
|
|
965
993
|
* Detects pgvector support by attempting to enable the extension.
|
|
966
994
|
* Idempotent — safe to call multiple times.
|
|
967
|
-
*/
|
|
968
|
-
|
|
995
|
+
*/
|
|
996
|
+
async _detectVectorSupport() {
|
|
997
|
+
if (this._supportsVector !== void 0) return this._supportsVector;
|
|
969
998
|
try {
|
|
970
999
|
await this._exec().exec("CREATE EXTENSION IF NOT EXISTS vector");
|
|
971
1000
|
this._supportsVector = true;
|
|
@@ -992,13 +1021,13 @@ else if (field.defaultValue?.kind === "fn") {
|
|
|
992
1021
|
this._log(sql, params);
|
|
993
1022
|
this._log(countSql, countParams);
|
|
994
1023
|
const [data, countRow] = await Promise.all([this._exec().all(sql, params), this._exec().get(countSql, countParams)]);
|
|
995
|
-
const count = parseCount(countRow?.cnt);
|
|
996
1024
|
return {
|
|
997
1025
|
data,
|
|
998
|
-
count
|
|
1026
|
+
count: parseCount(countRow?.cnt)
|
|
999
1027
|
};
|
|
1000
1028
|
}
|
|
1001
|
-
/** Resolves vector field and computes shared context for vector search SQL builders. */
|
|
1029
|
+
/** Resolves vector field and computes shared context for vector search SQL builders. */
|
|
1030
|
+
_prepareVectorSearch(vector, query, indexName) {
|
|
1002
1031
|
let field;
|
|
1003
1032
|
let vec;
|
|
1004
1033
|
if (indexName) {
|
|
@@ -1033,10 +1062,10 @@ else if (field.defaultValue?.kind === "fn") {
|
|
|
1033
1062
|
}
|
|
1034
1063
|
_buildVectorSearchQuery(vector, query, indexName) {
|
|
1035
1064
|
const ctx = this._prepareVectorSearch(vector, query, indexName);
|
|
1036
|
-
let inner = `SELECT *, (${qi(ctx.field)} ${ctx.distanceOp} $1::vector) AS _distance FROM ${quoteTableName(ctx.tableName)} WHERE ${offsetPlaceholders((0,
|
|
1065
|
+
let inner = `SELECT *, (${qi(ctx.field)} ${ctx.distanceOp} $1::vector) AS _distance FROM ${quoteTableName(ctx.tableName)} WHERE ${offsetPlaceholders((0, _atscript_db_sql_tools.finalizeParams)(pgDialect, ctx.where), 1).sql}`;
|
|
1037
1066
|
const params = [ctx.vectorStr, ...ctx.where.params];
|
|
1038
1067
|
let sql = `SELECT * FROM (${inner}) _v`;
|
|
1039
|
-
if (ctx.threshold !==
|
|
1068
|
+
if (ctx.threshold !== void 0) {
|
|
1040
1069
|
sql += ` WHERE _distance <= $${params.length + 1}`;
|
|
1041
1070
|
params.push(thresholdToDistance(ctx.threshold, ctx.vec.similarity));
|
|
1042
1071
|
}
|
|
@@ -1055,10 +1084,10 @@ else if (field.defaultValue?.kind === "fn") {
|
|
|
1055
1084
|
}
|
|
1056
1085
|
_buildVectorSearchCountQuery(vector, query, indexName) {
|
|
1057
1086
|
const ctx = this._prepareVectorSearch(vector, query, indexName);
|
|
1058
|
-
let inner = `SELECT (${qi(ctx.field)} ${ctx.distanceOp} $1::vector) AS _distance FROM ${quoteTableName(ctx.tableName)} WHERE ${offsetPlaceholders((0,
|
|
1087
|
+
let inner = `SELECT (${qi(ctx.field)} ${ctx.distanceOp} $1::vector) AS _distance FROM ${quoteTableName(ctx.tableName)} WHERE ${offsetPlaceholders((0, _atscript_db_sql_tools.finalizeParams)(pgDialect, ctx.where), 1).sql}`;
|
|
1059
1088
|
const params = [ctx.vectorStr, ...ctx.where.params];
|
|
1060
1089
|
let sql = `SELECT COUNT(*) AS cnt FROM (${inner}) _v`;
|
|
1061
|
-
if (ctx.threshold !==
|
|
1090
|
+
if (ctx.threshold !== void 0) {
|
|
1062
1091
|
sql += ` WHERE _distance <= $${params.length + 1}`;
|
|
1063
1092
|
params.push(thresholdToDistance(ctx.threshold, ctx.vec.similarity));
|
|
1064
1093
|
}
|
|
@@ -1067,26 +1096,19 @@ else if (field.defaultValue?.kind === "fn") {
|
|
|
1067
1096
|
params
|
|
1068
1097
|
};
|
|
1069
1098
|
}
|
|
1070
|
-
/** Resolves threshold: query-time $threshold > schema-level @db.search.vector.threshold. */
|
|
1099
|
+
/** Resolves threshold: query-time $threshold > schema-level @db.search.vector.threshold. */
|
|
1100
|
+
_resolveVectorThreshold(controls, indexName) {
|
|
1071
1101
|
const queryThreshold = controls.$threshold;
|
|
1072
|
-
if (queryThreshold !==
|
|
1102
|
+
if (queryThreshold !== void 0) return queryThreshold;
|
|
1073
1103
|
return this._vectorThresholds.get(indexName);
|
|
1074
1104
|
}
|
|
1075
|
-
constructor(driver) {
|
|
1076
|
-
super(), _define_property$1(this, "driver", void 0), _define_property$1(this, "supportsColumnModify", void 0), _define_property$1(this, "_incrementFields", void 0), _define_property$1(this, "_autoIncrementStart", void 0), _define_property$1(this, "_nocaseColumns", void 0), _define_property$1(this, "_citextProvisioned", void 0), _define_property$1(this, "_supportsVector", void 0), _define_property$1(this, "_vectorFields", void 0), _define_property$1(this, "_vectorThresholds", void 0), this.driver = driver, this.supportsColumnModify = true, this._incrementFields = new Set(), this._nocaseColumns = new Set(), this._citextProvisioned = false, this._vectorFields = new Map(), this._vectorThresholds = new Map();
|
|
1077
|
-
}
|
|
1078
1105
|
};
|
|
1079
|
-
_define_property$1(PostgresAdapter, "NATIVE_DEFAULT_FNS", new Set([
|
|
1080
|
-
"now",
|
|
1081
|
-
"uuid",
|
|
1082
|
-
"increment"
|
|
1083
|
-
]));
|
|
1084
1106
|
/**
|
|
1085
1107
|
* Normalizes PostgreSQL information_schema data_type values
|
|
1086
1108
|
* to match the format produced by `pgTypeFromField()`.
|
|
1087
|
-
*/
|
|
1088
|
-
|
|
1089
|
-
switch (
|
|
1109
|
+
*/
|
|
1110
|
+
function normalizePgType(dataType, maxLength, numericPrecision, numericScale, udtName, formattedType) {
|
|
1111
|
+
switch (dataType.toLowerCase()) {
|
|
1090
1112
|
case "character varying": return maxLength ? `VARCHAR(${maxLength})` : "VARCHAR(255)";
|
|
1091
1113
|
case "character": return maxLength ? `CHAR(${maxLength})` : "CHAR(1)";
|
|
1092
1114
|
case "integer": return "INTEGER";
|
|
@@ -1101,20 +1123,20 @@ _define_property$1(PostgresAdapter, "NATIVE_DEFAULT_FNS", new Set([
|
|
|
1101
1123
|
case "timestamp with time zone": return "TIMESTAMPTZ";
|
|
1102
1124
|
case "timestamp without time zone": return "TIMESTAMP";
|
|
1103
1125
|
case "uuid": return "UUID";
|
|
1104
|
-
case "user-defined":
|
|
1126
|
+
case "user-defined":
|
|
1105
1127
|
if (udtName === "vector") return formattedType;
|
|
1106
1128
|
if (udtName === "citext") return "CITEXT";
|
|
1107
1129
|
return udtName?.toUpperCase() ?? "USER-DEFINED";
|
|
1108
|
-
}
|
|
1109
1130
|
default: return dataType.toUpperCase();
|
|
1110
1131
|
}
|
|
1111
1132
|
}
|
|
1112
1133
|
/**
|
|
1113
1134
|
* Normalizes PostgreSQL column_default values to match the format
|
|
1114
1135
|
* produced by `serializeDefaultValue()`.
|
|
1115
|
-
*/
|
|
1136
|
+
*/
|
|
1137
|
+
function normalizePgDefault(value, isIdentity) {
|
|
1116
1138
|
if (isIdentity === "YES") return "fn:increment";
|
|
1117
|
-
if (value === null) return
|
|
1139
|
+
if (value === null) return;
|
|
1118
1140
|
let cleaned = value;
|
|
1119
1141
|
while (cleaned.startsWith("(") && cleaned.endsWith(")")) cleaned = cleaned.slice(1, -1);
|
|
1120
1142
|
const lower = cleaned.toLowerCase();
|
|
@@ -1137,60 +1159,58 @@ _define_property$1(PostgresAdapter, "NATIVE_DEFAULT_FNS", new Set([
|
|
|
1137
1159
|
* pgvector cosine distance = 1 - cosine_similarity, range [0, 2]
|
|
1138
1160
|
*
|
|
1139
1161
|
* Conversion: distance = 2 * (1 - score)
|
|
1140
|
-
*/
|
|
1162
|
+
*/
|
|
1163
|
+
function thresholdToDistance(threshold, similarity) {
|
|
1141
1164
|
switch (similarity) {
|
|
1142
1165
|
case "euclidean": return threshold;
|
|
1143
1166
|
case "dotProduct": return -threshold;
|
|
1144
1167
|
default: return 2 * (1 - threshold);
|
|
1145
1168
|
}
|
|
1146
1169
|
}
|
|
1147
|
-
/** Maps generic similarity metric to PostgreSQL distance operator. */
|
|
1170
|
+
/** Maps generic similarity metric to PostgreSQL distance operator. */
|
|
1171
|
+
function similarityToPgOp(similarity) {
|
|
1148
1172
|
switch (similarity) {
|
|
1149
1173
|
case "euclidean": return "<->";
|
|
1150
1174
|
case "dotProduct": return "<#>";
|
|
1151
1175
|
default: return "<=>";
|
|
1152
1176
|
}
|
|
1153
1177
|
}
|
|
1154
|
-
/** Maps generic similarity metric to pgvector index ops class. */
|
|
1178
|
+
/** Maps generic similarity metric to pgvector index ops class. */
|
|
1179
|
+
function similarityToPgOps(similarity) {
|
|
1155
1180
|
switch (similarity) {
|
|
1156
1181
|
case "euclidean": return "vector_l2_ops";
|
|
1157
1182
|
case "dotProduct": return "vector_ip_ops";
|
|
1158
1183
|
default: return "vector_cosine_ops";
|
|
1159
1184
|
}
|
|
1160
1185
|
}
|
|
1161
|
-
/** Formats a number[] vector as pgvector input: '[1.0, 2.0, ...]'. */
|
|
1186
|
+
/** Formats a number[] vector as pgvector input: '[1.0, 2.0, ...]'. */
|
|
1187
|
+
function vectorToString(vector) {
|
|
1162
1188
|
return `[${vector.join(",")}]`;
|
|
1163
1189
|
}
|
|
1164
|
-
|
|
1165
1190
|
//#endregion
|
|
1166
|
-
//#region
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
value,
|
|
1170
|
-
enumerable: true,
|
|
1171
|
-
configurable: true,
|
|
1172
|
-
writable: true
|
|
1173
|
-
});
|
|
1174
|
-
else obj[key] = value;
|
|
1175
|
-
return obj;
|
|
1176
|
-
}
|
|
1177
|
-
/** pg rejects `undefined` in bind arrays — coerce to `null`. */ function sanitizeParams(params) {
|
|
1191
|
+
//#region src/pg-driver.ts
|
|
1192
|
+
/** pg rejects `undefined` in bind arrays — coerce to `null`. */
|
|
1193
|
+
function sanitizeParams(params) {
|
|
1178
1194
|
if (!params) return [];
|
|
1179
|
-
return params.map((v) => v ===
|
|
1195
|
+
return params.map((v) => v === void 0 ? null : v);
|
|
1180
1196
|
}
|
|
1181
|
-
/** Parses TIMESTAMP/TIMESTAMPTZ to epoch milliseconds. */
|
|
1197
|
+
/** Parses TIMESTAMP/TIMESTAMPTZ to epoch milliseconds. */
|
|
1198
|
+
function parseTimestamp(val) {
|
|
1182
1199
|
const ms = new Date(val).getTime();
|
|
1183
1200
|
return Number.isNaN(ms) ? val : ms;
|
|
1184
1201
|
}
|
|
1185
|
-
/** Parses NUMERIC to number. */
|
|
1202
|
+
/** Parses NUMERIC to number. */
|
|
1203
|
+
function parseNumeric(val) {
|
|
1186
1204
|
const n = Number.parseFloat(val);
|
|
1187
1205
|
return Number.isNaN(n) ? val : n;
|
|
1188
1206
|
}
|
|
1189
|
-
/** Parses INT8/BIGINT to number. Returns string if value exceeds safe integer range. */
|
|
1207
|
+
/** Parses INT8/BIGINT to number. Returns string if value exceeds safe integer range. */
|
|
1208
|
+
function parseBigInt(val) {
|
|
1190
1209
|
const n = Number.parseInt(val, 10);
|
|
1191
1210
|
return Number.isNaN(n) || !Number.isSafeInteger(n) ? val : n;
|
|
1192
1211
|
}
|
|
1193
|
-
/** OIDs for types we override. */
|
|
1212
|
+
/** OIDs for types we override. */
|
|
1213
|
+
const TIMESTAMP_OID = 1114;
|
|
1194
1214
|
const TIMESTAMPTZ_OID = 1184;
|
|
1195
1215
|
const NUMERIC_OID = 1700;
|
|
1196
1216
|
const INT8_OID = 20;
|
|
@@ -1201,7 +1221,8 @@ const INT8_OID = 20;
|
|
|
1201
1221
|
* - TIMESTAMP/TIMESTAMPTZ → epoch milliseconds (number)
|
|
1202
1222
|
* - NUMERIC → number (not string)
|
|
1203
1223
|
* - INT8/BIGINT → number (for JS-safe range)
|
|
1204
|
-
*/
|
|
1224
|
+
*/
|
|
1225
|
+
function createCustomTypes(pgTypes) {
|
|
1205
1226
|
const overrides = new Map([
|
|
1206
1227
|
[TIMESTAMP_OID, parseTimestamp],
|
|
1207
1228
|
[TIMESTAMPTZ_OID, parseTimestamp],
|
|
@@ -1214,35 +1235,78 @@ const INT8_OID = 20;
|
|
|
1214
1235
|
return pgTypes.getTypeParser(oid, format);
|
|
1215
1236
|
} };
|
|
1216
1237
|
}
|
|
1238
|
+
/**
|
|
1239
|
+
* {@link TPgDriver} implementation backed by `pg` (node-postgres).
|
|
1240
|
+
*
|
|
1241
|
+
* Accepts a connection URI string, a `pg.PoolConfig` object, or a pre-created
|
|
1242
|
+
* `pg.Pool` instance.
|
|
1243
|
+
*
|
|
1244
|
+
* ```typescript
|
|
1245
|
+
* import { PgDriver } from '@atscript/db-postgres'
|
|
1246
|
+
*
|
|
1247
|
+
* // Connection URI
|
|
1248
|
+
* const driver = new PgDriver('postgresql://user:pass@localhost:5432/mydb')
|
|
1249
|
+
*
|
|
1250
|
+
* // Pool options
|
|
1251
|
+
* const driver = new PgDriver({
|
|
1252
|
+
* host: 'localhost',
|
|
1253
|
+
* user: 'postgres',
|
|
1254
|
+
* database: 'mydb',
|
|
1255
|
+
* max: 10,
|
|
1256
|
+
* })
|
|
1257
|
+
*
|
|
1258
|
+
* // Pre-created pool
|
|
1259
|
+
* import pg from 'pg'
|
|
1260
|
+
* const pool = new pg.Pool({ connectionString: '...' })
|
|
1261
|
+
* const driver = new PgDriver(pool)
|
|
1262
|
+
* ```
|
|
1263
|
+
*
|
|
1264
|
+
* Requires `pg` to be installed:
|
|
1265
|
+
* ```bash
|
|
1266
|
+
* pnpm add pg
|
|
1267
|
+
* ```
|
|
1268
|
+
*/
|
|
1217
1269
|
var PgDriver = class {
|
|
1270
|
+
pool;
|
|
1271
|
+
poolInit;
|
|
1272
|
+
constructor(poolOrConfig) {
|
|
1273
|
+
if (typeof poolOrConfig === "object" && typeof poolOrConfig.query === "function") this.pool = poolOrConfig;
|
|
1274
|
+
else this.poolInit = import("pg").then((pg) => {
|
|
1275
|
+
const Pool = pg.default?.Pool ?? pg.Pool;
|
|
1276
|
+
const types = pg.default?.types ?? pg.types;
|
|
1277
|
+
const customTypes = types ? createCustomTypes(types) : void 0;
|
|
1278
|
+
if (typeof poolOrConfig === "string") this.pool = new Pool({
|
|
1279
|
+
connectionString: poolOrConfig,
|
|
1280
|
+
types: customTypes
|
|
1281
|
+
});
|
|
1282
|
+
else this.pool = new Pool({
|
|
1283
|
+
...poolOrConfig,
|
|
1284
|
+
types: customTypes
|
|
1285
|
+
});
|
|
1286
|
+
return this.pool;
|
|
1287
|
+
});
|
|
1288
|
+
}
|
|
1218
1289
|
getPool() {
|
|
1219
1290
|
return this.pool || this.poolInit;
|
|
1220
1291
|
}
|
|
1221
1292
|
async run(sql, params) {
|
|
1222
|
-
const
|
|
1223
|
-
const result = await pool.query(sql, sanitizeParams(params));
|
|
1293
|
+
const result = await (await this.getPool()).query(sql, sanitizeParams(params));
|
|
1224
1294
|
return {
|
|
1225
1295
|
affectedRows: result.rowCount ?? 0,
|
|
1226
1296
|
rows: result.rows ?? []
|
|
1227
1297
|
};
|
|
1228
1298
|
}
|
|
1229
1299
|
async all(sql, params) {
|
|
1230
|
-
|
|
1231
|
-
const result = await pool.query(sql, sanitizeParams(params));
|
|
1232
|
-
return result.rows;
|
|
1300
|
+
return (await (await this.getPool()).query(sql, sanitizeParams(params))).rows;
|
|
1233
1301
|
}
|
|
1234
1302
|
async get(sql, params) {
|
|
1235
|
-
|
|
1236
|
-
const result = await pool.query(sql, sanitizeParams(params));
|
|
1237
|
-
return result.rows[0] ?? null;
|
|
1303
|
+
return (await (await this.getPool()).query(sql, sanitizeParams(params))).rows[0] ?? null;
|
|
1238
1304
|
}
|
|
1239
1305
|
async exec(sql) {
|
|
1240
|
-
|
|
1241
|
-
await pool.query(sql);
|
|
1306
|
+
await (await this.getPool()).query(sql);
|
|
1242
1307
|
}
|
|
1243
1308
|
async getConnection() {
|
|
1244
|
-
const
|
|
1245
|
-
const client = await pool.connect();
|
|
1309
|
+
const client = await (await this.getPool()).connect();
|
|
1246
1310
|
return {
|
|
1247
1311
|
async run(sql, params) {
|
|
1248
1312
|
const result = await client.query(sql, sanitizeParams(params));
|
|
@@ -1252,12 +1316,10 @@ var PgDriver = class {
|
|
|
1252
1316
|
};
|
|
1253
1317
|
},
|
|
1254
1318
|
async all(sql, params) {
|
|
1255
|
-
|
|
1256
|
-
return result.rows;
|
|
1319
|
+
return (await client.query(sql, sanitizeParams(params))).rows;
|
|
1257
1320
|
},
|
|
1258
1321
|
async get(sql, params) {
|
|
1259
|
-
|
|
1260
|
-
return result.rows[0] ?? null;
|
|
1322
|
+
return (await client.query(sql, sanitizeParams(params))).rows[0] ?? null;
|
|
1261
1323
|
},
|
|
1262
1324
|
async exec(sql) {
|
|
1263
1325
|
await client.query(sql);
|
|
@@ -1268,87 +1330,28 @@ var PgDriver = class {
|
|
|
1268
1330
|
};
|
|
1269
1331
|
}
|
|
1270
1332
|
async close() {
|
|
1271
|
-
|
|
1272
|
-
await pool.end();
|
|
1273
|
-
}
|
|
1274
|
-
constructor(poolOrConfig) {
|
|
1275
|
-
_define_property(this, "pool", void 0);
|
|
1276
|
-
_define_property(this, "poolInit", void 0);
|
|
1277
|
-
if (typeof poolOrConfig === "object" && typeof poolOrConfig.query === "function") this.pool = poolOrConfig;
|
|
1278
|
-
else this.poolInit = import("pg").then((pg) => {
|
|
1279
|
-
const Pool = pg.default?.Pool ?? pg.Pool;
|
|
1280
|
-
const types = pg.default?.types ?? pg.types;
|
|
1281
|
-
const customTypes = types ? createCustomTypes(types) : undefined;
|
|
1282
|
-
if (typeof poolOrConfig === "string") this.pool = new Pool({
|
|
1283
|
-
connectionString: poolOrConfig,
|
|
1284
|
-
types: customTypes
|
|
1285
|
-
});
|
|
1286
|
-
else this.pool = new Pool({
|
|
1287
|
-
...poolOrConfig,
|
|
1288
|
-
types: customTypes
|
|
1289
|
-
});
|
|
1290
|
-
return this.pool;
|
|
1291
|
-
});
|
|
1333
|
+
await (await this.getPool()).end();
|
|
1292
1334
|
}
|
|
1293
1335
|
};
|
|
1294
|
-
|
|
1295
1336
|
//#endregion
|
|
1296
|
-
//#region
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
type: "string",
|
|
1305
|
-
description: "Native PostgreSQL column type (e.g., \"CITEXT\", \"INET\", \"MACADDR\")."
|
|
1306
|
-
}
|
|
1307
|
-
}),
|
|
1308
|
-
schema: new __atscript_core.AnnotationSpec({
|
|
1309
|
-
description: "Specifies the PostgreSQL schema for the table.\n\n**Default:** `\"public\"`\n\n```atscript\n@db.pg.schema \"analytics\"\nexport interface Events { ... }\n```",
|
|
1310
|
-
nodeType: ["interface"],
|
|
1311
|
-
multiple: false,
|
|
1312
|
-
argument: {
|
|
1313
|
-
name: "schema",
|
|
1314
|
-
type: "string",
|
|
1315
|
-
description: "PostgreSQL schema name (e.g., \"public\", \"analytics\")."
|
|
1316
|
-
}
|
|
1317
|
-
}),
|
|
1318
|
-
collate: new __atscript_core.AnnotationSpec({
|
|
1319
|
-
description: "Specifies a native PostgreSQL collation (overrides portable `@db.column.collate`).\n\n```atscript\n@db.pg.collate \"tr-x-icu\"\nname: string\n```",
|
|
1320
|
-
nodeType: ["interface", "prop"],
|
|
1321
|
-
multiple: false,
|
|
1322
|
-
argument: {
|
|
1323
|
-
name: "collation",
|
|
1324
|
-
type: "string",
|
|
1325
|
-
description: "Native PostgreSQL collation name (e.g., \"tr-x-icu\", \"C\", \"und-x-icu\")."
|
|
1326
|
-
}
|
|
1327
|
-
})
|
|
1328
|
-
};
|
|
1329
|
-
|
|
1330
|
-
//#endregion
|
|
1331
|
-
//#region packages/db-postgres/src/plugin/index.ts
|
|
1332
|
-
const PostgresPlugin = () => ({
|
|
1333
|
-
name: "postgres",
|
|
1334
|
-
config() {
|
|
1335
|
-
return { annotations: { db: { pg: annotations } } };
|
|
1336
|
-
}
|
|
1337
|
-
});
|
|
1338
|
-
|
|
1339
|
-
//#endregion
|
|
1340
|
-
//#region packages/db-postgres/src/index.ts
|
|
1337
|
+
//#region src/index.ts
|
|
1338
|
+
/**
|
|
1339
|
+
* Creates a {@link DbSpace} backed by a PostgreSQL connection pool.
|
|
1340
|
+
*
|
|
1341
|
+
* @param uri - PostgreSQL connection URI (e.g., `postgresql://user@localhost:5432/mydb`)
|
|
1342
|
+
* @param options - Additional pool options passed to pg.
|
|
1343
|
+
* @returns A `DbSpace` that creates `PostgresAdapter` instances per table.
|
|
1344
|
+
*/
|
|
1341
1345
|
function createAdapter(uri, options) {
|
|
1342
1346
|
const driver = new PgDriver({
|
|
1343
1347
|
connectionString: uri,
|
|
1344
1348
|
...options
|
|
1345
1349
|
});
|
|
1346
|
-
return new
|
|
1350
|
+
return new _atscript_db.DbSpace(() => new PostgresAdapter(driver));
|
|
1347
1351
|
}
|
|
1348
|
-
|
|
1349
1352
|
//#endregion
|
|
1350
|
-
exports.PgDriver = PgDriver
|
|
1351
|
-
exports.PostgresAdapter = PostgresAdapter
|
|
1352
|
-
exports.PostgresPlugin = PostgresPlugin
|
|
1353
|
-
exports.buildWhere = buildWhere
|
|
1354
|
-
exports.createAdapter = createAdapter
|
|
1353
|
+
exports.PgDriver = PgDriver;
|
|
1354
|
+
exports.PostgresAdapter = PostgresAdapter;
|
|
1355
|
+
exports.PostgresPlugin = require_plugin.PostgresPlugin;
|
|
1356
|
+
exports.buildWhere = buildWhere;
|
|
1357
|
+
exports.createAdapter = createAdapter;
|