@getstrata/core 2.0.7 → 2.0.8
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 +4 -0
- package/dist/core/database/schema/blueprint.d.ts +19 -1
- package/dist/core/database/schema/grammars/compileStatements.d.ts +1 -1
- package/dist/core/database/schema/index.d.ts +1 -1
- package/dist/entries/database/schema.js +233 -518
- package/dist/index.js +59 -6
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,12 +7,25 @@ interface IndexDefinition {
|
|
|
7
7
|
where?: string;
|
|
8
8
|
order?: "asc" | "desc";
|
|
9
9
|
}
|
|
10
|
+
interface TableForeignKeyDefinition {
|
|
11
|
+
columns: string[];
|
|
12
|
+
referencesTable: string;
|
|
13
|
+
referencesColumns: string[];
|
|
14
|
+
name?: string;
|
|
15
|
+
onDelete?: "cascade" | "set null" | "restrict";
|
|
16
|
+
}
|
|
17
|
+
interface TableCheckDefinition {
|
|
18
|
+
expression: string;
|
|
19
|
+
name?: string;
|
|
20
|
+
}
|
|
10
21
|
type BlueprintAction = "create" | "alter" | "drop";
|
|
11
22
|
declare class Blueprint {
|
|
12
23
|
readonly table: string;
|
|
13
24
|
readonly action: BlueprintAction;
|
|
14
25
|
readonly columns: ColumnDefinition[];
|
|
15
26
|
readonly indexes: IndexDefinition[];
|
|
27
|
+
readonly foreignKeys: TableForeignKeyDefinition[];
|
|
28
|
+
readonly checks: TableCheckDefinition[];
|
|
16
29
|
readonly droppedColumns: string[];
|
|
17
30
|
readonly droppedIndexes: string[];
|
|
18
31
|
constructor(table: string, action: BlueprintAction);
|
|
@@ -32,6 +45,11 @@ declare class Blueprint {
|
|
|
32
45
|
dropSoftDeletes(): void;
|
|
33
46
|
dropIndex(name: string): void;
|
|
34
47
|
unique(columns: string | string[], name?: string): void;
|
|
48
|
+
foreignKey(columns: string[], referencesTable: string, referencesColumns: string[], options?: {
|
|
49
|
+
name?: string;
|
|
50
|
+
onDelete?: "cascade" | "set null" | "restrict";
|
|
51
|
+
}): void;
|
|
52
|
+
check(expression: string, name?: string): void;
|
|
35
53
|
index(columns: string | string[], options?: {
|
|
36
54
|
name?: string;
|
|
37
55
|
order?: "asc" | "desc";
|
|
@@ -43,5 +61,5 @@ declare class Blueprint {
|
|
|
43
61
|
fullText(columns: string | string[], name?: string): void;
|
|
44
62
|
ginIndex(column: string, name?: string): void;
|
|
45
63
|
}
|
|
46
|
-
export type { BlueprintAction, IndexDefinition, IndexKind };
|
|
64
|
+
export type { BlueprintAction, IndexDefinition, IndexKind, TableCheckDefinition, TableForeignKeyDefinition, };
|
|
47
65
|
export { Blueprint };
|
|
@@ -3,6 +3,6 @@ import type { ColumnDefinition } from "../columnDefinition.ts";
|
|
|
3
3
|
import type { DatabaseDriver } from "../driver.ts";
|
|
4
4
|
declare function compileDropTable(driver: DatabaseDriver, tableName: string): string[];
|
|
5
5
|
declare function compileColumn(driver: DatabaseDriver, column: ColumnDefinition, mode: "create" | "alter"): string;
|
|
6
|
-
declare function compileIndex(
|
|
6
|
+
declare function compileIndex(driver: DatabaseDriver, tableName: string, index: IndexDefinition): string;
|
|
7
7
|
declare function compileBlueprint(driver: DatabaseDriver, blueprint: Blueprint): string[];
|
|
8
8
|
export { compileBlueprint, compileColumn, compileDropTable, compileIndex };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type { BlueprintAction, IndexDefinition, IndexKind } from "./blueprint.ts";
|
|
1
|
+
export type { BlueprintAction, IndexDefinition, IndexKind, TableCheckDefinition, TableForeignKeyDefinition, } from "./blueprint.ts";
|
|
2
2
|
export { Blueprint } from "./blueprint.ts";
|
|
3
3
|
export type { ColumnKind, ForeignKeyOptions } from "./columnDefinition.ts";
|
|
4
4
|
export { ColumnDefinition, ForeignIdColumnDefinition, inferReferencedTable, } from "./columnDefinition.ts";
|
|
@@ -52,6 +52,181 @@ function resolveDatabaseDriver(options = {}) {
|
|
|
52
52
|
}
|
|
53
53
|
var init_driver = () => {};
|
|
54
54
|
|
|
55
|
+
// ../../src/core/runtime/asyncContextStore.ts
|
|
56
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
57
|
+
function createAsyncContextStore(key) {
|
|
58
|
+
const symbol = Symbol.for(key);
|
|
59
|
+
const globalRecord = globalThis;
|
|
60
|
+
const existing = globalRecord[symbol];
|
|
61
|
+
if (existing) {
|
|
62
|
+
return existing;
|
|
63
|
+
}
|
|
64
|
+
const store = new AsyncLocalStorage;
|
|
65
|
+
globalRecord[symbol] = store;
|
|
66
|
+
return store;
|
|
67
|
+
}
|
|
68
|
+
var init_asyncContextStore = () => {};
|
|
69
|
+
|
|
70
|
+
// ../../src/core/database/dialect.ts
|
|
71
|
+
function assertSafeIdentifier(identifier) {
|
|
72
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(identifier)) {
|
|
73
|
+
throw new Error(`Invalid SQL identifier: ${identifier}`);
|
|
74
|
+
}
|
|
75
|
+
return identifier;
|
|
76
|
+
}
|
|
77
|
+
function quoteIdentifierFor(dialect, column) {
|
|
78
|
+
return dialect.quoteIdentifier(column);
|
|
79
|
+
}
|
|
80
|
+
function dialectFor(driver) {
|
|
81
|
+
return dialects[driver];
|
|
82
|
+
}
|
|
83
|
+
function currentSqlDialect() {
|
|
84
|
+
return dialectContext.getStore() ?? dialectOverride ?? dialectFor(resolveDatabaseDriver());
|
|
85
|
+
}
|
|
86
|
+
var postgresDialect, mysqlDialect, sqliteDialect, dialects, dialectContext, dialectOverride = null;
|
|
87
|
+
var init_dialect = __esm(() => {
|
|
88
|
+
init_asyncContextStore();
|
|
89
|
+
init_driver();
|
|
90
|
+
postgresDialect = {
|
|
91
|
+
driver: "pgsql",
|
|
92
|
+
placeholder(index) {
|
|
93
|
+
return `$${index}`;
|
|
94
|
+
},
|
|
95
|
+
quoteIdentifier(identifier) {
|
|
96
|
+
return `"${assertSafeIdentifier(identifier)}"`;
|
|
97
|
+
},
|
|
98
|
+
nowExpression() {
|
|
99
|
+
return "NOW()";
|
|
100
|
+
},
|
|
101
|
+
epochMillisecondsExpression() {
|
|
102
|
+
return "CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT)";
|
|
103
|
+
},
|
|
104
|
+
rowLockClause(skipLocked = false) {
|
|
105
|
+
return ` FOR UPDATE${skipLocked ? " SKIP LOCKED" : ""}`;
|
|
106
|
+
},
|
|
107
|
+
timestampValue(value) {
|
|
108
|
+
return value.toISOString();
|
|
109
|
+
},
|
|
110
|
+
returningClause(columns) {
|
|
111
|
+
return ` RETURNING ${columns}`;
|
|
112
|
+
},
|
|
113
|
+
ilikeOperator() {
|
|
114
|
+
return "ILIKE";
|
|
115
|
+
},
|
|
116
|
+
nullsLastSuffix() {
|
|
117
|
+
return " NULLS LAST";
|
|
118
|
+
},
|
|
119
|
+
castToText(expression) {
|
|
120
|
+
return `${expression}::text`;
|
|
121
|
+
},
|
|
122
|
+
upsertSuffix(conflictColumns, updateColumns) {
|
|
123
|
+
const target = conflictColumns.map((column) => quoteIdentifierFor(this, column)).join(", ");
|
|
124
|
+
if (updateColumns.length === 0) {
|
|
125
|
+
return ` ON CONFLICT (${target}) DO NOTHING`;
|
|
126
|
+
}
|
|
127
|
+
const assignments = updateColumns.map((column) => {
|
|
128
|
+
const quoted = quoteIdentifierFor(this, column);
|
|
129
|
+
return `${quoted} = excluded.${quoted}`;
|
|
130
|
+
}).join(", ");
|
|
131
|
+
return ` ON CONFLICT (${target}) DO UPDATE SET ${assignments}`;
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
mysqlDialect = {
|
|
135
|
+
driver: "mysql",
|
|
136
|
+
placeholder() {
|
|
137
|
+
return "?";
|
|
138
|
+
},
|
|
139
|
+
quoteIdentifier(identifier) {
|
|
140
|
+
return `\`${assertSafeIdentifier(identifier)}\``;
|
|
141
|
+
},
|
|
142
|
+
nowExpression() {
|
|
143
|
+
return "CURRENT_TIMESTAMP";
|
|
144
|
+
},
|
|
145
|
+
epochMillisecondsExpression() {
|
|
146
|
+
return "CAST(UNIX_TIMESTAMP(CURRENT_TIMESTAMP(3)) * 1000 AS UNSIGNED)";
|
|
147
|
+
},
|
|
148
|
+
rowLockClause(skipLocked = false) {
|
|
149
|
+
return ` FOR UPDATE${skipLocked ? " SKIP LOCKED" : ""}`;
|
|
150
|
+
},
|
|
151
|
+
timestampValue(value) {
|
|
152
|
+
return value.toISOString().slice(0, 19).replace("T", " ");
|
|
153
|
+
},
|
|
154
|
+
returningClause() {
|
|
155
|
+
return "";
|
|
156
|
+
},
|
|
157
|
+
ilikeOperator() {
|
|
158
|
+
return "LIKE";
|
|
159
|
+
},
|
|
160
|
+
nullsLastSuffix() {
|
|
161
|
+
return "";
|
|
162
|
+
},
|
|
163
|
+
castToText(expression) {
|
|
164
|
+
return `CAST(${expression} AS CHAR)`;
|
|
165
|
+
},
|
|
166
|
+
upsertSuffix(conflictColumns, updateColumns) {
|
|
167
|
+
if (updateColumns.length === 0) {
|
|
168
|
+
const anchor = quoteIdentifierFor(this, conflictColumns[0] ?? "");
|
|
169
|
+
return ` ON DUPLICATE KEY UPDATE ${anchor} = ${anchor}`;
|
|
170
|
+
}
|
|
171
|
+
const assignments = updateColumns.map((column) => {
|
|
172
|
+
const quoted = quoteIdentifierFor(this, column);
|
|
173
|
+
return `${quoted} = VALUES(${quoted})`;
|
|
174
|
+
}).join(", ");
|
|
175
|
+
return ` ON DUPLICATE KEY UPDATE ${assignments}`;
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
sqliteDialect = {
|
|
179
|
+
driver: "sqlite",
|
|
180
|
+
placeholder() {
|
|
181
|
+
return "?";
|
|
182
|
+
},
|
|
183
|
+
quoteIdentifier(identifier) {
|
|
184
|
+
return `"${assertSafeIdentifier(identifier)}"`;
|
|
185
|
+
},
|
|
186
|
+
nowExpression() {
|
|
187
|
+
return "strftime('%Y-%m-%dT%H:%M:%fZ', 'now')";
|
|
188
|
+
},
|
|
189
|
+
epochMillisecondsExpression() {
|
|
190
|
+
return "CAST((julianday('now') - 2440587.5) * 86400000 AS INTEGER)";
|
|
191
|
+
},
|
|
192
|
+
rowLockClause() {
|
|
193
|
+
return "";
|
|
194
|
+
},
|
|
195
|
+
timestampValue(value) {
|
|
196
|
+
return value.toISOString();
|
|
197
|
+
},
|
|
198
|
+
returningClause(columns) {
|
|
199
|
+
return ` RETURNING ${columns}`;
|
|
200
|
+
},
|
|
201
|
+
ilikeOperator() {
|
|
202
|
+
return "LIKE";
|
|
203
|
+
},
|
|
204
|
+
nullsLastSuffix() {
|
|
205
|
+
return "";
|
|
206
|
+
},
|
|
207
|
+
castToText(expression) {
|
|
208
|
+
return `CAST(${expression} AS TEXT)`;
|
|
209
|
+
},
|
|
210
|
+
upsertSuffix(conflictColumns, updateColumns) {
|
|
211
|
+
const target = conflictColumns.map((column) => quoteIdentifierFor(this, column)).join(", ");
|
|
212
|
+
if (updateColumns.length === 0) {
|
|
213
|
+
return ` ON CONFLICT (${target}) DO NOTHING`;
|
|
214
|
+
}
|
|
215
|
+
const assignments = updateColumns.map((column) => {
|
|
216
|
+
const quoted = quoteIdentifierFor(this, column);
|
|
217
|
+
return `${quoted} = excluded.${quoted}`;
|
|
218
|
+
}).join(", ");
|
|
219
|
+
return ` ON CONFLICT (${target}) DO UPDATE SET ${assignments}`;
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
dialects = {
|
|
223
|
+
pgsql: postgresDialect,
|
|
224
|
+
mysql: mysqlDialect,
|
|
225
|
+
sqlite: sqliteDialect
|
|
226
|
+
};
|
|
227
|
+
dialectContext = createAsyncContextStore("@getstrata/sqlDialect");
|
|
228
|
+
});
|
|
229
|
+
|
|
55
230
|
// ../../src/core/database/schema/columnDefinition.ts
|
|
56
231
|
class ColumnDefinition {
|
|
57
232
|
name;
|
|
@@ -150,6 +325,8 @@ class Blueprint {
|
|
|
150
325
|
action;
|
|
151
326
|
columns = [];
|
|
152
327
|
indexes = [];
|
|
328
|
+
foreignKeys = [];
|
|
329
|
+
checks = [];
|
|
153
330
|
droppedColumns = [];
|
|
154
331
|
droppedIndexes = [];
|
|
155
332
|
constructor(table, action) {
|
|
@@ -164,6 +341,9 @@ class Blueprint {
|
|
|
164
341
|
return column;
|
|
165
342
|
}
|
|
166
343
|
string(name, length) {
|
|
344
|
+
if (length !== undefined && (!Number.isSafeInteger(length) || length <= 0)) {
|
|
345
|
+
throw new Error("String length must be a positive safe integer.");
|
|
346
|
+
}
|
|
167
347
|
const column = new ColumnDefinition(name, "string");
|
|
168
348
|
column.length = length;
|
|
169
349
|
column.notNullable();
|
|
@@ -241,6 +421,22 @@ class Blueprint {
|
|
|
241
421
|
kind: "unique"
|
|
242
422
|
});
|
|
243
423
|
}
|
|
424
|
+
foreignKey(columns, referencesTable, referencesColumns, options = {}) {
|
|
425
|
+
if (!columns.length || columns.length !== referencesColumns.length) {
|
|
426
|
+
throw new Error("Foreign key columns must be nonempty and match referenced columns.");
|
|
427
|
+
}
|
|
428
|
+
this.foreignKeys.push({
|
|
429
|
+
columns: [...columns],
|
|
430
|
+
referencesTable,
|
|
431
|
+
referencesColumns: [...referencesColumns],
|
|
432
|
+
...options
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
check(expression, name) {
|
|
436
|
+
if (!expression.trim())
|
|
437
|
+
throw new Error("Check expression must not be empty.");
|
|
438
|
+
this.checks.push({ expression, name });
|
|
439
|
+
}
|
|
244
440
|
index(columns, options = {}) {
|
|
245
441
|
this.indexes.push({
|
|
246
442
|
name: options.name,
|
|
@@ -284,518 +480,8 @@ class UnsupportedSchemaFeatureError extends Error {
|
|
|
284
480
|
this.name = "UnsupportedSchemaFeatureError";
|
|
285
481
|
}
|
|
286
482
|
}
|
|
287
|
-
// ../../src/core/database/
|
|
288
|
-
|
|
289
|
-
function quoteIdentifier(identifier) {
|
|
290
|
-
return currentSqlDialect().quoteIdentifier(identifier);
|
|
291
|
-
}
|
|
292
|
-
function returningSuffix(columns) {
|
|
293
|
-
return currentSqlDialect().returningClause(columns);
|
|
294
|
-
}
|
|
295
|
-
function qualifyColumn(tableName, column) {
|
|
296
|
-
return `${quoteIdentifier(tableName)}.${quoteIdentifier(column)}`;
|
|
297
|
-
}
|
|
298
|
-
function resolveQualifiedColumn(defaultTable, columnName) {
|
|
299
|
-
if (columnName.includes(".")) {
|
|
300
|
-
const [table, column] = columnName.split(".", 2);
|
|
301
|
-
if (!table || !column) {
|
|
302
|
-
throw new Error(`Invalid qualified column: ${columnName}`);
|
|
303
|
-
}
|
|
304
|
-
return qualifyColumn(table, column);
|
|
305
|
-
}
|
|
306
|
-
return qualifyColumn(defaultTable, columnName);
|
|
307
|
-
}
|
|
308
|
-
function parseQualifiedColumn(reference) {
|
|
309
|
-
const [table, column] = reference.split(".", 2);
|
|
310
|
-
if (!table || !column) {
|
|
311
|
-
throw new Error(`Join columns must be qualified as table.column: ${reference}`);
|
|
312
|
-
}
|
|
313
|
-
return { table, column };
|
|
314
|
-
}
|
|
315
|
-
function normalizeDirection(direction = "ASC") {
|
|
316
|
-
return direction.toUpperCase() === "DESC" ? "DESC" : "ASC";
|
|
317
|
-
}
|
|
318
|
-
function isQueryOperator(value) {
|
|
319
|
-
return value !== null && !Array.isArray(value) && !(value instanceof Date) && typeof value === "object";
|
|
320
|
-
}
|
|
321
|
-
function pushParam(values, value) {
|
|
322
|
-
values.push(value);
|
|
323
|
-
return currentSqlDialect().placeholder(values.length);
|
|
324
|
-
}
|
|
325
|
-
var SUPPORTED_OPERATORS = new Set([
|
|
326
|
-
"eq",
|
|
327
|
-
"ne",
|
|
328
|
-
"in",
|
|
329
|
-
"notIn",
|
|
330
|
-
"gt",
|
|
331
|
-
"gte",
|
|
332
|
-
"lt",
|
|
333
|
-
"lte",
|
|
334
|
-
"isNull",
|
|
335
|
-
"ilike",
|
|
336
|
-
"tsMatch"
|
|
337
|
-
]);
|
|
338
|
-
function buildInClause(column, values, params, negated = false) {
|
|
339
|
-
if (values.length === 0) {
|
|
340
|
-
return negated ? "1 = 1" : "1 = 0";
|
|
341
|
-
}
|
|
342
|
-
const placeholders = values.map((value) => pushParam(params, value)).join(", ");
|
|
343
|
-
return `${column}${negated ? " NOT" : ""} IN (${placeholders})`;
|
|
344
|
-
}
|
|
345
|
-
function buildOperatorClauses(column, operator, params) {
|
|
346
|
-
const clauses = [];
|
|
347
|
-
const unsupported = Object.keys(operator).filter((key) => !SUPPORTED_OPERATORS.has(key));
|
|
348
|
-
if (unsupported.length > 0) {
|
|
349
|
-
throw new Error(`Unsupported query operator(s) for ${column}: ${unsupported.join(", ")}. Supported: ${[...SUPPORTED_OPERATORS].join(", ")}.`);
|
|
350
|
-
}
|
|
351
|
-
if (operator.isNull === true) {
|
|
352
|
-
clauses.push(`${column} IS NULL`);
|
|
353
|
-
}
|
|
354
|
-
if (operator.isNull === false) {
|
|
355
|
-
clauses.push(`${column} IS NOT NULL`);
|
|
356
|
-
}
|
|
357
|
-
if (operator.eq !== undefined) {
|
|
358
|
-
if (operator.eq === null) {
|
|
359
|
-
clauses.push(`${column} IS NULL`);
|
|
360
|
-
} else {
|
|
361
|
-
clauses.push(`${column} = ${pushParam(params, operator.eq)}`);
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
if (operator.ne !== undefined) {
|
|
365
|
-
if (operator.ne === null) {
|
|
366
|
-
clauses.push(`${column} IS NOT NULL`);
|
|
367
|
-
} else {
|
|
368
|
-
clauses.push(`${column} <> ${pushParam(params, operator.ne)}`);
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
if (operator.in !== undefined) {
|
|
372
|
-
clauses.push(buildInClause(column, operator.in, params));
|
|
373
|
-
}
|
|
374
|
-
if (operator.notIn !== undefined) {
|
|
375
|
-
clauses.push(buildInClause(column, operator.notIn, params, true));
|
|
376
|
-
}
|
|
377
|
-
if (operator.gt !== undefined) {
|
|
378
|
-
clauses.push(`${column} > ${pushParam(params, operator.gt)}`);
|
|
379
|
-
}
|
|
380
|
-
if (operator.gte !== undefined) {
|
|
381
|
-
clauses.push(`${column} >= ${pushParam(params, operator.gte)}`);
|
|
382
|
-
}
|
|
383
|
-
if (operator.lt !== undefined) {
|
|
384
|
-
clauses.push(`${column} < ${pushParam(params, operator.lt)}`);
|
|
385
|
-
}
|
|
386
|
-
if (operator.lte !== undefined) {
|
|
387
|
-
clauses.push(`${column} <= ${pushParam(params, operator.lte)}`);
|
|
388
|
-
}
|
|
389
|
-
if (operator.ilike !== undefined) {
|
|
390
|
-
clauses.push(`${column} ${currentSqlDialect().ilikeOperator()} ${pushParam(params, operator.ilike)}`);
|
|
391
|
-
}
|
|
392
|
-
if (operator.tsMatch !== undefined) {
|
|
393
|
-
if (currentSqlDialect().driver !== "pgsql") {
|
|
394
|
-
throw new Error("Full-text search (tsMatch) is only available on PostgreSQL.");
|
|
395
|
-
}
|
|
396
|
-
clauses.push(`${column} @@ plainto_tsquery('english', ${pushParam(params, operator.tsMatch)})`);
|
|
397
|
-
}
|
|
398
|
-
return clauses;
|
|
399
|
-
}
|
|
400
|
-
function appendWhereParts(tableName, where, params) {
|
|
401
|
-
const clauses = [];
|
|
402
|
-
for (const [columnName, filterValue] of Object.entries(where)) {
|
|
403
|
-
if (filterValue === undefined) {
|
|
404
|
-
continue;
|
|
405
|
-
}
|
|
406
|
-
const column = resolveQualifiedColumn(tableName, columnName);
|
|
407
|
-
if (Array.isArray(filterValue)) {
|
|
408
|
-
clauses.push(buildInClause(column, filterValue, params));
|
|
409
|
-
continue;
|
|
410
|
-
}
|
|
411
|
-
if (isQueryOperator(filterValue)) {
|
|
412
|
-
clauses.push(...buildOperatorClauses(column, filterValue, params));
|
|
413
|
-
continue;
|
|
414
|
-
}
|
|
415
|
-
if (filterValue === null) {
|
|
416
|
-
clauses.push(`${column} IS NULL`);
|
|
417
|
-
continue;
|
|
418
|
-
}
|
|
419
|
-
clauses.push(`${column} = ${pushParam(params, filterValue)}`);
|
|
420
|
-
}
|
|
421
|
-
return clauses.join(" AND ");
|
|
422
|
-
}
|
|
423
|
-
function buildWhereClause(tableName, where = {}) {
|
|
424
|
-
const params = [];
|
|
425
|
-
const body = appendWhereParts(tableName, where, params);
|
|
426
|
-
return {
|
|
427
|
-
clause: body.length > 0 ? ` WHERE ${body}` : "",
|
|
428
|
-
params
|
|
429
|
-
};
|
|
430
|
-
}
|
|
431
|
-
function remapExistsSql(sql, existsParams, params) {
|
|
432
|
-
const offset = params.length;
|
|
433
|
-
params.push(...existsParams);
|
|
434
|
-
return sql.replace(/\$(\d+)/g, (_match, index) => `$${offset + Number(index)}`);
|
|
435
|
-
}
|
|
436
|
-
function buildWhereNodeClause(tableName, node, params) {
|
|
437
|
-
if ("exists" in node) {
|
|
438
|
-
const body = remapExistsSql(node.exists.sql, node.exists.params, params);
|
|
439
|
-
return `${node.exists.not ? "NOT " : ""}EXISTS (${body})`;
|
|
440
|
-
}
|
|
441
|
-
if ("where" in node) {
|
|
442
|
-
return appendWhereParts(tableName, node.where, params);
|
|
443
|
-
}
|
|
444
|
-
const grouped = buildWhereGroupClause(tableName, node.group, params);
|
|
445
|
-
if (!grouped) {
|
|
446
|
-
return "";
|
|
447
|
-
}
|
|
448
|
-
return grouped.includes(" OR ") ? `(${grouped})` : grouped;
|
|
449
|
-
}
|
|
450
|
-
function buildWhereGroupClause(tableName, nodes, params) {
|
|
451
|
-
let result = "";
|
|
452
|
-
for (const node of nodes) {
|
|
453
|
-
const part = buildWhereNodeClause(tableName, node, params);
|
|
454
|
-
if (!part) {
|
|
455
|
-
continue;
|
|
456
|
-
}
|
|
457
|
-
if (!result) {
|
|
458
|
-
result = part;
|
|
459
|
-
continue;
|
|
460
|
-
}
|
|
461
|
-
result = node.kind === "or" ? `${result} OR ${part}` : `${result} AND ${part}`;
|
|
462
|
-
}
|
|
463
|
-
if (!result) {
|
|
464
|
-
return "";
|
|
465
|
-
}
|
|
466
|
-
return result;
|
|
467
|
-
}
|
|
468
|
-
function buildAdvancedWhereClause(tableName, where = {}, whereNodes = [], params = []) {
|
|
469
|
-
const nodes = [];
|
|
470
|
-
if (Object.keys(where).length > 0) {
|
|
471
|
-
nodes.push({ kind: "and", where });
|
|
472
|
-
}
|
|
473
|
-
nodes.push(...whereNodes);
|
|
474
|
-
const combined = buildWhereGroupClause(tableName, nodes, params);
|
|
475
|
-
return {
|
|
476
|
-
clause: combined ? ` WHERE ${combined}` : "",
|
|
477
|
-
params
|
|
478
|
-
};
|
|
479
|
-
}
|
|
480
|
-
function resolveSoftDeleteColumn(table) {
|
|
481
|
-
if (!table.softDeletes) {
|
|
482
|
-
return null;
|
|
483
|
-
}
|
|
484
|
-
if (table.softDeletes === true) {
|
|
485
|
-
return "deleted_at";
|
|
486
|
-
}
|
|
487
|
-
return table.softDeletes.column ?? "deleted_at";
|
|
488
|
-
}
|
|
489
|
-
function appendSoftDeleteScope(table, options, clauses) {
|
|
490
|
-
const column = resolveSoftDeleteColumn(table);
|
|
491
|
-
if (!column) {
|
|
492
|
-
return;
|
|
493
|
-
}
|
|
494
|
-
const qualifiedColumn = qualifyColumn(table.name, column);
|
|
495
|
-
if (options.onlyTrashed) {
|
|
496
|
-
clauses.push(`${qualifiedColumn} IS NOT NULL`);
|
|
497
|
-
return;
|
|
498
|
-
}
|
|
499
|
-
if (!options.withTrashed) {
|
|
500
|
-
clauses.push(`${qualifiedColumn} IS NULL`);
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
function buildQueryWhereClause(table, options = {}, whereNodes = [], params = []) {
|
|
504
|
-
const { clause, params: whereParams } = buildAdvancedWhereClause(table.name, options.where ?? {}, whereNodes, params);
|
|
505
|
-
const softDeleteClauses = [];
|
|
506
|
-
appendSoftDeleteScope(table, options, softDeleteClauses);
|
|
507
|
-
if (softDeleteClauses.length === 0) {
|
|
508
|
-
return { clause, params: whereParams };
|
|
509
|
-
}
|
|
510
|
-
const base = clause.replace(/^ WHERE /, "");
|
|
511
|
-
const scope = softDeleteClauses.join(" AND ");
|
|
512
|
-
return {
|
|
513
|
-
clause: base ? ` WHERE (${base}) AND ${scope}` : ` WHERE ${scope}`,
|
|
514
|
-
params: whereParams
|
|
515
|
-
};
|
|
516
|
-
}
|
|
517
|
-
function isQueryOrder(value) {
|
|
518
|
-
return "column" in value;
|
|
519
|
-
}
|
|
520
|
-
function normalizeOrderBy(orderBy) {
|
|
521
|
-
if (!orderBy) {
|
|
522
|
-
return [];
|
|
523
|
-
}
|
|
524
|
-
if (Array.isArray(orderBy)) {
|
|
525
|
-
return orderBy;
|
|
526
|
-
}
|
|
527
|
-
if (isQueryOrder(orderBy)) {
|
|
528
|
-
return [orderBy];
|
|
529
|
-
}
|
|
530
|
-
return Object.entries(orderBy).map(([column, direction]) => ({
|
|
531
|
-
column,
|
|
532
|
-
direction
|
|
533
|
-
}));
|
|
534
|
-
}
|
|
535
|
-
function buildOrderByClause(tableName, orderBy) {
|
|
536
|
-
const parts = normalizeOrderBy(orderBy).map(({ column, direction }) => {
|
|
537
|
-
return `${resolveQualifiedColumn(tableName, column)} ${normalizeDirection(direction)}`;
|
|
538
|
-
});
|
|
539
|
-
return parts.length > 0 ? ` ORDER BY ${parts.join(", ")}` : "";
|
|
540
|
-
}
|
|
541
|
-
function buildGroupByClause(tableName, groupBy) {
|
|
542
|
-
if (!groupBy) {
|
|
543
|
-
return "";
|
|
544
|
-
}
|
|
545
|
-
const columns = (Array.isArray(groupBy) ? groupBy : [groupBy]).map((column) => String(column));
|
|
546
|
-
const parts = columns.map((column) => resolveQualifiedColumn(tableName, column));
|
|
547
|
-
return parts.length > 0 ? ` GROUP BY ${parts.join(", ")}` : "";
|
|
548
|
-
}
|
|
549
|
-
function buildHavingClause(tableName, having, params) {
|
|
550
|
-
if (!having) {
|
|
551
|
-
return "";
|
|
552
|
-
}
|
|
553
|
-
const body = appendWhereParts(tableName, having, params);
|
|
554
|
-
return body.length > 0 ? ` HAVING ${body}` : "";
|
|
555
|
-
}
|
|
556
|
-
function buildJoinClause(joins = []) {
|
|
557
|
-
return joins.map((join) => {
|
|
558
|
-
const joinType = join.type === "left" ? "LEFT JOIN" : "INNER JOIN";
|
|
559
|
-
const onClause = join.on.map(({ left, right }) => `${qualifyColumn(left.table, left.column)} = ${qualifyColumn(right.table, right.column)}`).join(" AND ");
|
|
560
|
-
return ` ${joinType} ${quoteIdentifier(join.table)} ON ${onClause}`;
|
|
561
|
-
}).join("");
|
|
562
|
-
}
|
|
563
|
-
function buildLimitClause(limit) {
|
|
564
|
-
if (limit === undefined) {
|
|
565
|
-
return "";
|
|
566
|
-
}
|
|
567
|
-
if (!Number.isInteger(limit) || limit <= 0) {
|
|
568
|
-
throw new Error("Query limit must be a positive integer.");
|
|
569
|
-
}
|
|
570
|
-
return ` LIMIT ${limit}`;
|
|
571
|
-
}
|
|
572
|
-
function buildOffsetClause(offset) {
|
|
573
|
-
if (offset === undefined) {
|
|
574
|
-
return "";
|
|
575
|
-
}
|
|
576
|
-
if (!Number.isInteger(offset) || offset < 0) {
|
|
577
|
-
throw new Error("Query offset must be a non-negative integer.");
|
|
578
|
-
}
|
|
579
|
-
return ` OFFSET ${offset}`;
|
|
580
|
-
}
|
|
581
|
-
function buildReturningColumns(table) {
|
|
582
|
-
return table.columns.map((column) => qualifyColumn(table.name, column)).join(", ");
|
|
583
|
-
}
|
|
584
|
-
function buildSelectList(table, select, params = []) {
|
|
585
|
-
if (!select || select.length === 0) {
|
|
586
|
-
return buildReturningColumns(table);
|
|
587
|
-
}
|
|
588
|
-
return select.map((item) => {
|
|
589
|
-
if (item.kind === "column") {
|
|
590
|
-
const column = qualifyColumn(item.table, item.column);
|
|
591
|
-
return item.as ? `${column} AS ${quoteIdentifier(item.as)}` : column;
|
|
592
|
-
}
|
|
593
|
-
if (item.kind === "literalText") {
|
|
594
|
-
return `${currentSqlDialect().castToText(pushParam(params, item.value))} AS ${quoteIdentifier(item.as)}`;
|
|
595
|
-
}
|
|
596
|
-
if (item.kind === "subqueryCount") {
|
|
597
|
-
const body = remapExistsSql(item.sql, item.params, params);
|
|
598
|
-
return `(${body}) AS ${quoteIdentifier(item.as)}`;
|
|
599
|
-
}
|
|
600
|
-
const column = qualifyColumn(item.table, item.column);
|
|
601
|
-
const placeholder = pushParam(params, item.query);
|
|
602
|
-
return `ts_rank(${column}, plainto_tsquery('english', ${placeholder})) AS ${quoteIdentifier(item.as)}`;
|
|
603
|
-
}).join(", ");
|
|
604
|
-
}
|
|
605
|
-
function getDefinedColumnEntries(table, values, options = {}) {
|
|
606
|
-
const record = values;
|
|
607
|
-
const excluded = new Set(options.exclude ?? []);
|
|
608
|
-
return table.columns.flatMap((column) => {
|
|
609
|
-
if (excluded.has(column) || !Object.hasOwn(record, column)) {
|
|
610
|
-
return [];
|
|
611
|
-
}
|
|
612
|
-
const value = record[column];
|
|
613
|
-
if (value === undefined) {
|
|
614
|
-
return [];
|
|
615
|
-
}
|
|
616
|
-
return [[column, value]];
|
|
617
|
-
});
|
|
618
|
-
}
|
|
619
|
-
function buildSelectQuery(table, options = {}, whereNodes = []) {
|
|
620
|
-
const params = [];
|
|
621
|
-
const columns = buildSelectList(table, options.select, params);
|
|
622
|
-
const { clause } = buildQueryWhereClause(table, options, whereNodes, params);
|
|
623
|
-
const joins = buildJoinClause(options.joins);
|
|
624
|
-
const groupBy = buildGroupByClause(table.name, options.groupBy);
|
|
625
|
-
const havingClause = buildHavingClause(table.name, options.having, params);
|
|
626
|
-
const orderBy = buildOrderByClause(table.name, options.orderBy ?? table.defaultOrderBy);
|
|
627
|
-
const limit = buildLimitClause(options.limit);
|
|
628
|
-
const offset = buildOffsetClause(options.offset);
|
|
629
|
-
return {
|
|
630
|
-
text: `SELECT ${columns} FROM ${quoteIdentifier(table.name)}${joins}${clause}${groupBy}${havingClause}${orderBy}${limit}${offset}`,
|
|
631
|
-
params
|
|
632
|
-
};
|
|
633
|
-
}
|
|
634
|
-
function buildCountQuery(table, where = {}, options = {}, whereNodes = []) {
|
|
635
|
-
const params = [];
|
|
636
|
-
const { clause, params: whereParams } = buildQueryWhereClause(table, {
|
|
637
|
-
where,
|
|
638
|
-
withTrashed: options.withTrashed,
|
|
639
|
-
onlyTrashed: options.onlyTrashed
|
|
640
|
-
}, whereNodes);
|
|
641
|
-
params.push(...whereParams);
|
|
642
|
-
const joins = buildJoinClause(options.joins);
|
|
643
|
-
const groupBy = buildGroupByClause(table.name, options.groupBy);
|
|
644
|
-
return {
|
|
645
|
-
text: `SELECT COUNT(*) AS count FROM ${quoteIdentifier(table.name)}${joins}${clause}${groupBy}`,
|
|
646
|
-
params
|
|
647
|
-
};
|
|
648
|
-
}
|
|
649
|
-
function buildProjectionQuery(table, expression, alias, options = {}, whereNodes = []) {
|
|
650
|
-
assertSafeProjectionExpression(expression);
|
|
651
|
-
const params = [];
|
|
652
|
-
const { clause, params: whereParams } = buildQueryWhereClause(table, options, whereNodes);
|
|
653
|
-
params.push(...whereParams);
|
|
654
|
-
const joins = buildJoinClause(options.joins);
|
|
655
|
-
const groupBy = buildGroupByClause(table.name, options.groupBy);
|
|
656
|
-
const orderBy = buildOrderByClause(table.name, options.orderBy);
|
|
657
|
-
const limit = buildLimitClause(options.limit);
|
|
658
|
-
return {
|
|
659
|
-
text: `SELECT ${expression} AS ${quoteIdentifier(alias)} FROM ${quoteIdentifier(table.name)}${joins}${clause}${groupBy}${orderBy}${limit}`,
|
|
660
|
-
params
|
|
661
|
-
};
|
|
662
|
-
}
|
|
663
|
-
var SAFE_PROJECTION_EXPRESSION = /^(COUNT\(\*\)|(?:"[\w_]+"(?:\."[\w_]+")?)|(?:AVG|SUM|MIN|MAX)\((?:"[\w_]+"(?:\."[\w_]+")?)\))$/;
|
|
664
|
-
function assertSafeProjectionExpression(expression) {
|
|
665
|
-
if (!SAFE_PROJECTION_EXPRESSION.test(expression.trim())) {
|
|
666
|
-
throw new Error(`Unsafe projection expression: ${expression}`);
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
function buildGroupedCountQuery(table, column, where = {}, options = {}) {
|
|
670
|
-
const qualifiedColumn = qualifyColumn(table.name, column);
|
|
671
|
-
const { clause, params } = buildQueryWhereClause(table, {
|
|
672
|
-
where,
|
|
673
|
-
...options
|
|
674
|
-
});
|
|
675
|
-
return {
|
|
676
|
-
text: `SELECT ${qualifiedColumn} AS ${quoteIdentifier("value")}, COUNT(*) AS ${quoteIdentifier("count")} FROM ${quoteIdentifier(table.name)}${clause} GROUP BY ${qualifiedColumn} ORDER BY ${qualifiedColumn} ASC`,
|
|
677
|
-
params
|
|
678
|
-
};
|
|
679
|
-
}
|
|
680
|
-
function buildInsertQuery(table, values) {
|
|
681
|
-
const entries = getDefinedColumnEntries(table, values);
|
|
682
|
-
if (entries.length === 0) {
|
|
683
|
-
throw new Error(`Cannot insert into ${table.name} without any column values.`);
|
|
684
|
-
}
|
|
685
|
-
const params = [];
|
|
686
|
-
const columns = entries.map(([column]) => quoteIdentifier(column)).join(", ");
|
|
687
|
-
const placeholders = entries.map(([, value]) => pushParam(params, value)).join(", ");
|
|
688
|
-
const returningColumns = buildReturningColumns(table);
|
|
689
|
-
return {
|
|
690
|
-
text: `INSERT INTO ${quoteIdentifier(table.name)} (${columns}) VALUES (${placeholders})${returningSuffix(returningColumns)}`,
|
|
691
|
-
params
|
|
692
|
-
};
|
|
693
|
-
}
|
|
694
|
-
function buildUpsertQuery(table, values, conflictColumns, updateColumns) {
|
|
695
|
-
const entries = getDefinedColumnEntries(table, values);
|
|
696
|
-
if (entries.length === 0) {
|
|
697
|
-
throw new Error(`Cannot upsert into ${table.name} without any column values.`);
|
|
698
|
-
}
|
|
699
|
-
if (conflictColumns.length === 0) {
|
|
700
|
-
throw new Error(`Cannot upsert into ${table.name} without any conflict columns.`);
|
|
701
|
-
}
|
|
702
|
-
const insertable = new Set(entries.map(([column]) => column));
|
|
703
|
-
const conflict = new Set(conflictColumns);
|
|
704
|
-
const updatable = (updateColumns ?? entries.map(([column]) => column).filter((c) => !conflict.has(c))).filter((column) => insertable.has(column));
|
|
705
|
-
const params = [];
|
|
706
|
-
const columns = entries.map(([column]) => quoteIdentifier(column)).join(", ");
|
|
707
|
-
const placeholders = entries.map(([, value]) => pushParam(params, value)).join(", ");
|
|
708
|
-
const returningColumns = buildReturningColumns(table);
|
|
709
|
-
const suffix = currentSqlDialect().upsertSuffix(conflictColumns, updatable);
|
|
710
|
-
return {
|
|
711
|
-
text: `INSERT INTO ${quoteIdentifier(table.name)} (${columns}) VALUES (${placeholders})${suffix}${returningSuffix(returningColumns)}`,
|
|
712
|
-
params
|
|
713
|
-
};
|
|
714
|
-
}
|
|
715
|
-
function buildIncrementQuery(table, id, column, amount, extra = {}) {
|
|
716
|
-
if (!Number.isFinite(amount)) {
|
|
717
|
-
throw new Error("Increment amount must be a finite number.");
|
|
718
|
-
}
|
|
719
|
-
if (!table.columns.includes(column)) {
|
|
720
|
-
throw new Error(`Unknown column ${String(column)} on ${table.name}.`);
|
|
721
|
-
}
|
|
722
|
-
const params = [];
|
|
723
|
-
const target = quoteIdentifier(column);
|
|
724
|
-
const assignments = [`${target} = ${target} + ${pushParam(params, amount)}`];
|
|
725
|
-
for (const [name, value] of getDefinedColumnEntries(table, extra, {
|
|
726
|
-
exclude: [table.primaryKey, column]
|
|
727
|
-
})) {
|
|
728
|
-
assignments.push(`${quoteIdentifier(name)} = ${pushParam(params, value)}`);
|
|
729
|
-
}
|
|
730
|
-
const primaryKeyPlaceholder = pushParam(params, id);
|
|
731
|
-
const returningColumns = buildReturningColumns(table);
|
|
732
|
-
const scopeClauses = [];
|
|
733
|
-
appendSoftDeleteScope(table, {}, scopeClauses);
|
|
734
|
-
const scopeSuffix = scopeClauses.length > 0 ? ` AND ${scopeClauses.join(" AND ")}` : "";
|
|
735
|
-
return {
|
|
736
|
-
text: `UPDATE ${quoteIdentifier(table.name)} SET ${assignments.join(", ")} WHERE ${quoteIdentifier(table.primaryKey)} = ${primaryKeyPlaceholder}${scopeSuffix}${returningSuffix(returningColumns)}`,
|
|
737
|
-
params
|
|
738
|
-
};
|
|
739
|
-
}
|
|
740
|
-
function buildUpdateQuery(table, id, changes) {
|
|
741
|
-
const entries = getDefinedColumnEntries(table, changes, {
|
|
742
|
-
exclude: [table.primaryKey]
|
|
743
|
-
});
|
|
744
|
-
if (entries.length === 0) {
|
|
745
|
-
throw new Error(`Cannot update ${table.name} without any changed column values.`);
|
|
746
|
-
}
|
|
747
|
-
const params = [];
|
|
748
|
-
const setClause = entries.map(([column, value]) => `${quoteIdentifier(column)} = ${pushParam(params, value)}`).join(", ");
|
|
749
|
-
const primaryKeyPlaceholder = pushParam(params, id);
|
|
750
|
-
const returningColumns = buildReturningColumns(table);
|
|
751
|
-
const scopeClauses = [];
|
|
752
|
-
appendSoftDeleteScope(table, {}, scopeClauses);
|
|
753
|
-
const scopeSuffix = scopeClauses.length > 0 ? ` AND ${scopeClauses.join(" AND ")}` : "";
|
|
754
|
-
return {
|
|
755
|
-
text: `UPDATE ${quoteIdentifier(table.name)} SET ${setClause} WHERE ${quoteIdentifier(table.primaryKey)} = ${primaryKeyPlaceholder}${scopeSuffix}${returningSuffix(returningColumns)}`,
|
|
756
|
-
params
|
|
757
|
-
};
|
|
758
|
-
}
|
|
759
|
-
function buildSoftDeleteByIdQuery(table, id, deletedAt) {
|
|
760
|
-
const deletedAtColumn = resolveSoftDeleteColumn(table);
|
|
761
|
-
if (!deletedAtColumn) {
|
|
762
|
-
throw new Error(`Table ${table.name} does not support soft deletes.`);
|
|
763
|
-
}
|
|
764
|
-
const returningColumns = buildReturningColumns(table);
|
|
765
|
-
const scopeClauses = [];
|
|
766
|
-
appendSoftDeleteScope(table, {}, scopeClauses);
|
|
767
|
-
const scopeSuffix = scopeClauses.length > 0 ? ` AND ${scopeClauses.join(" AND ")}` : "";
|
|
768
|
-
const params = [];
|
|
769
|
-
const deletedAtPlaceholder = pushParam(params, deletedAt);
|
|
770
|
-
const idPlaceholder = pushParam(params, id);
|
|
771
|
-
return {
|
|
772
|
-
text: `UPDATE ${quoteIdentifier(table.name)} SET ${quoteIdentifier(deletedAtColumn)} = ${deletedAtPlaceholder} WHERE ${quoteIdentifier(table.primaryKey)} = ${idPlaceholder}${scopeSuffix}${returningSuffix(returningColumns)}`,
|
|
773
|
-
params
|
|
774
|
-
};
|
|
775
|
-
}
|
|
776
|
-
function buildRestoreByIdQuery(table, id) {
|
|
777
|
-
const deletedAtColumn = resolveSoftDeleteColumn(table);
|
|
778
|
-
if (!deletedAtColumn) {
|
|
779
|
-
throw new Error(`Table ${table.name} does not support soft deletes.`);
|
|
780
|
-
}
|
|
781
|
-
const returningColumns = buildReturningColumns(table);
|
|
782
|
-
const params = [];
|
|
783
|
-
const deletedAtPlaceholder = pushParam(params, null);
|
|
784
|
-
const idPlaceholder = pushParam(params, id);
|
|
785
|
-
return {
|
|
786
|
-
text: `UPDATE ${quoteIdentifier(table.name)} SET ${quoteIdentifier(deletedAtColumn)} = ${deletedAtPlaceholder} WHERE ${quoteIdentifier(table.primaryKey)} = ${idPlaceholder} AND ${qualifyColumn(table.name, deletedAtColumn)} IS NOT NULL${returningSuffix(returningColumns)}`,
|
|
787
|
-
params
|
|
788
|
-
};
|
|
789
|
-
}
|
|
790
|
-
function buildDeleteByIdQuery(table, id) {
|
|
791
|
-
const params = [];
|
|
792
|
-
const idPlaceholder = pushParam(params, id);
|
|
793
|
-
const returning = returningSuffix(`${quoteIdentifier(table.primaryKey)} AS ${quoteIdentifier("deleted_id")}`);
|
|
794
|
-
return {
|
|
795
|
-
text: `DELETE FROM ${quoteIdentifier(table.name)} WHERE ${quoteIdentifier(table.primaryKey)} = ${idPlaceholder}${returning}`,
|
|
796
|
-
params
|
|
797
|
-
};
|
|
798
|
-
}
|
|
483
|
+
// ../../src/core/database/schema/grammars/compileStatements.ts
|
|
484
|
+
init_dialect();
|
|
799
485
|
|
|
800
486
|
// ../../src/core/database/schema/grammars/grammar.ts
|
|
801
487
|
function compileColumnType(driver, column) {
|
|
@@ -836,7 +522,7 @@ function compileIdType(driver) {
|
|
|
836
522
|
function compileStringType(driver, length) {
|
|
837
523
|
switch (driver) {
|
|
838
524
|
case "pgsql":
|
|
839
|
-
return "TEXT";
|
|
525
|
+
return length ? `VARCHAR(${length})` : "TEXT";
|
|
840
526
|
case "mysql":
|
|
841
527
|
return length ? `VARCHAR(${length})` : "VARCHAR(255)";
|
|
842
528
|
case "sqlite":
|
|
@@ -914,15 +600,30 @@ function compileJsonbType(driver) {
|
|
|
914
600
|
}
|
|
915
601
|
|
|
916
602
|
// ../../src/core/database/schema/grammars/compileStatements.ts
|
|
603
|
+
function compileTableConstraints(driver, blueprint) {
|
|
604
|
+
const quoteIdentifier = dialectFor(driver).quoteIdentifier;
|
|
605
|
+
const named = (name) => name ? `CONSTRAINT ${quoteIdentifier(name)} ` : "";
|
|
606
|
+
const columns = (names) => names.map(quoteIdentifier).join(", ");
|
|
607
|
+
return [
|
|
608
|
+
...blueprint.foreignKeys.map((key) => {
|
|
609
|
+
const deletion = key.onDelete ? ` ON DELETE ${key.onDelete.toUpperCase()}` : "";
|
|
610
|
+
return `${named(key.name)}FOREIGN KEY (${columns(key.columns)}) REFERENCES ${quoteIdentifier(key.referencesTable)}(${columns(key.referencesColumns)})${deletion}`;
|
|
611
|
+
}),
|
|
612
|
+
...blueprint.checks.map((check) => `${named(check.name)}CHECK (${check.expression})`)
|
|
613
|
+
];
|
|
614
|
+
}
|
|
917
615
|
function compileCreateTable(driver, blueprint) {
|
|
616
|
+
const quoteIdentifier = dialectFor(driver).quoteIdentifier;
|
|
918
617
|
const table = quoteIdentifier(blueprint.table);
|
|
919
618
|
const parts = blueprint.columns.map((column) => compileColumn(driver, column, "create"));
|
|
920
619
|
for (const index of blueprint.indexes) {
|
|
921
|
-
if (index.kind === "unique"
|
|
620
|
+
if (index.kind === "unique") {
|
|
922
621
|
const columns = index.columns.map((column) => quoteIdentifier(column)).join(", ");
|
|
923
|
-
|
|
622
|
+
const name = index.name ? `CONSTRAINT ${quoteIdentifier(index.name)} ` : "";
|
|
623
|
+
parts.push(`${name}UNIQUE (${columns})`);
|
|
924
624
|
}
|
|
925
625
|
}
|
|
626
|
+
parts.push(...compileTableConstraints(driver, blueprint));
|
|
926
627
|
const statements = [`CREATE TABLE IF NOT EXISTS ${table} (
|
|
927
628
|
${parts.join(`,
|
|
928
629
|
`)}
|
|
@@ -940,6 +641,7 @@ function compileCreateTable(driver, blueprint) {
|
|
|
940
641
|
return statements;
|
|
941
642
|
}
|
|
942
643
|
function compileAlterTable(driver, blueprint) {
|
|
644
|
+
const quoteIdentifier = dialectFor(driver).quoteIdentifier;
|
|
943
645
|
const statements = [];
|
|
944
646
|
const table = quoteIdentifier(blueprint.table);
|
|
945
647
|
for (const column of blueprint.columns) {
|
|
@@ -947,12 +649,20 @@ function compileAlterTable(driver, blueprint) {
|
|
|
947
649
|
statements.push(`ALTER TABLE ${table}
|
|
948
650
|
${addPrefix} ${compileColumn(driver, column, "alter")}`);
|
|
949
651
|
}
|
|
652
|
+
if (blueprint.foreignKeys.length || blueprint.checks.length) {
|
|
653
|
+
if (driver === "sqlite") {
|
|
654
|
+
throw new UnsupportedSchemaFeatureError("adding table constraints; rebuild the SQLite table", driver);
|
|
655
|
+
}
|
|
656
|
+
for (const constraint of compileTableConstraints(driver, blueprint)) {
|
|
657
|
+
statements.push(`ALTER TABLE ${table} ADD ${constraint}`);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
950
660
|
for (const columnName of blueprint.droppedColumns) {
|
|
951
661
|
const dropPrefix = driver === "pgsql" ? "DROP COLUMN IF EXISTS" : "DROP COLUMN";
|
|
952
662
|
statements.push(`ALTER TABLE ${table} ${dropPrefix} ${quoteIdentifier(columnName)}`);
|
|
953
663
|
}
|
|
954
664
|
for (const indexName of blueprint.droppedIndexes) {
|
|
955
|
-
statements.push(`DROP INDEX IF EXISTS ${quoteIdentifier(indexName)}`);
|
|
665
|
+
statements.push(driver === "mysql" ? `DROP INDEX ${quoteIdentifier(indexName)} ON ${table}` : `DROP INDEX IF EXISTS ${quoteIdentifier(indexName)}`);
|
|
956
666
|
}
|
|
957
667
|
for (const index of blueprint.indexes) {
|
|
958
668
|
if (index.kind === "index" || index.kind === "unique") {
|
|
@@ -964,10 +674,12 @@ ${addPrefix} ${compileColumn(driver, column, "alter")}`);
|
|
|
964
674
|
return statements;
|
|
965
675
|
}
|
|
966
676
|
function compileDropTable(driver, tableName) {
|
|
677
|
+
const quoteIdentifier = dialectFor(driver).quoteIdentifier;
|
|
967
678
|
const cascade = driver === "pgsql" ? " CASCADE" : "";
|
|
968
679
|
return [`DROP TABLE IF EXISTS ${quoteIdentifier(tableName)}${cascade}`];
|
|
969
680
|
}
|
|
970
681
|
function compileColumn(driver, column, mode) {
|
|
682
|
+
const quoteIdentifier = dialectFor(driver).quoteIdentifier;
|
|
971
683
|
const parts = [quoteIdentifier(column.name), compileColumnType(driver, column)];
|
|
972
684
|
if (column.autoIncrement && driver === "mysql") {
|
|
973
685
|
parts[1] = `${parts[1]} AUTO_INCREMENT`;
|
|
@@ -1005,7 +717,8 @@ function compileColumn(driver, column, mode) {
|
|
|
1005
717
|
}
|
|
1006
718
|
return parts.join(" ");
|
|
1007
719
|
}
|
|
1008
|
-
function compileIndex(
|
|
720
|
+
function compileIndex(driver, tableName, index) {
|
|
721
|
+
const quoteIdentifier = dialectFor(driver).quoteIdentifier;
|
|
1009
722
|
const indexName = index.name ?? defaultIndexName(tableName, index.columns, index.kind === "unique" ? "unique" : "index");
|
|
1010
723
|
const columns = index.columns.map((column) => {
|
|
1011
724
|
const quoted = quoteIdentifier(column);
|
|
@@ -1015,9 +728,11 @@ function compileIndex(_driver, tableName, index) {
|
|
|
1015
728
|
return quoted;
|
|
1016
729
|
}).join(", ");
|
|
1017
730
|
const unique = index.kind === "unique" ? "UNIQUE " : "";
|
|
1018
|
-
|
|
731
|
+
const existence = driver === "mysql" ? "" : "IF NOT EXISTS ";
|
|
732
|
+
return `CREATE ${unique}INDEX ${existence}${quoteIdentifier(indexName)} ON ${quoteIdentifier(tableName)}(${columns})`;
|
|
1019
733
|
}
|
|
1020
734
|
function compileSpecialIndex(driver, tableName, index) {
|
|
735
|
+
const quoteIdentifier = dialectFor(driver).quoteIdentifier;
|
|
1021
736
|
const indexName = index.name ?? defaultIndexName(tableName, index.columns, index.kind);
|
|
1022
737
|
const columns = index.columns.map((column) => quoteIdentifier(column)).join(", ");
|
|
1023
738
|
switch (index.kind) {
|
package/dist/index.js
CHANGED
|
@@ -7733,6 +7733,8 @@ class Blueprint {
|
|
|
7733
7733
|
action;
|
|
7734
7734
|
columns = [];
|
|
7735
7735
|
indexes = [];
|
|
7736
|
+
foreignKeys = [];
|
|
7737
|
+
checks = [];
|
|
7736
7738
|
droppedColumns = [];
|
|
7737
7739
|
droppedIndexes = [];
|
|
7738
7740
|
constructor(table, action) {
|
|
@@ -7747,6 +7749,9 @@ class Blueprint {
|
|
|
7747
7749
|
return column;
|
|
7748
7750
|
}
|
|
7749
7751
|
string(name, length) {
|
|
7752
|
+
if (length !== undefined && (!Number.isSafeInteger(length) || length <= 0)) {
|
|
7753
|
+
throw new Error("String length must be a positive safe integer.");
|
|
7754
|
+
}
|
|
7750
7755
|
const column = new ColumnDefinition(name, "string");
|
|
7751
7756
|
column.length = length;
|
|
7752
7757
|
column.notNullable();
|
|
@@ -7824,6 +7829,22 @@ class Blueprint {
|
|
|
7824
7829
|
kind: "unique"
|
|
7825
7830
|
});
|
|
7826
7831
|
}
|
|
7832
|
+
foreignKey(columns, referencesTable, referencesColumns, options = {}) {
|
|
7833
|
+
if (!columns.length || columns.length !== referencesColumns.length) {
|
|
7834
|
+
throw new Error("Foreign key columns must be nonempty and match referenced columns.");
|
|
7835
|
+
}
|
|
7836
|
+
this.foreignKeys.push({
|
|
7837
|
+
columns: [...columns],
|
|
7838
|
+
referencesTable,
|
|
7839
|
+
referencesColumns: [...referencesColumns],
|
|
7840
|
+
...options
|
|
7841
|
+
});
|
|
7842
|
+
}
|
|
7843
|
+
check(expression, name) {
|
|
7844
|
+
if (!expression.trim())
|
|
7845
|
+
throw new Error("Check expression must not be empty.");
|
|
7846
|
+
this.checks.push({ expression, name });
|
|
7847
|
+
}
|
|
7827
7848
|
index(columns, options = {}) {
|
|
7828
7849
|
this.indexes.push({
|
|
7829
7850
|
name: options.name,
|
|
@@ -7867,6 +7888,9 @@ class UnsupportedSchemaFeatureError extends Error {
|
|
|
7867
7888
|
this.name = "UnsupportedSchemaFeatureError";
|
|
7868
7889
|
}
|
|
7869
7890
|
}
|
|
7891
|
+
// ../../src/core/database/schema/grammars/compileStatements.ts
|
|
7892
|
+
init_dialect();
|
|
7893
|
+
|
|
7870
7894
|
// ../../src/core/database/schema/grammars/grammar.ts
|
|
7871
7895
|
function compileColumnType(driver, column) {
|
|
7872
7896
|
switch (column.kind) {
|
|
@@ -7906,7 +7930,7 @@ function compileIdType(driver) {
|
|
|
7906
7930
|
function compileStringType(driver, length) {
|
|
7907
7931
|
switch (driver) {
|
|
7908
7932
|
case "pgsql":
|
|
7909
|
-
return "TEXT";
|
|
7933
|
+
return length ? `VARCHAR(${length})` : "TEXT";
|
|
7910
7934
|
case "mysql":
|
|
7911
7935
|
return length ? `VARCHAR(${length})` : "VARCHAR(255)";
|
|
7912
7936
|
case "sqlite":
|
|
@@ -7984,15 +8008,30 @@ function compileJsonbType(driver) {
|
|
|
7984
8008
|
}
|
|
7985
8009
|
|
|
7986
8010
|
// ../../src/core/database/schema/grammars/compileStatements.ts
|
|
8011
|
+
function compileTableConstraints(driver, blueprint) {
|
|
8012
|
+
const quoteIdentifier = dialectFor(driver).quoteIdentifier;
|
|
8013
|
+
const named = (name) => name ? `CONSTRAINT ${quoteIdentifier(name)} ` : "";
|
|
8014
|
+
const columns = (names) => names.map(quoteIdentifier).join(", ");
|
|
8015
|
+
return [
|
|
8016
|
+
...blueprint.foreignKeys.map((key) => {
|
|
8017
|
+
const deletion = key.onDelete ? ` ON DELETE ${key.onDelete.toUpperCase()}` : "";
|
|
8018
|
+
return `${named(key.name)}FOREIGN KEY (${columns(key.columns)}) REFERENCES ${quoteIdentifier(key.referencesTable)}(${columns(key.referencesColumns)})${deletion}`;
|
|
8019
|
+
}),
|
|
8020
|
+
...blueprint.checks.map((check) => `${named(check.name)}CHECK (${check.expression})`)
|
|
8021
|
+
];
|
|
8022
|
+
}
|
|
7987
8023
|
function compileCreateTable(driver, blueprint) {
|
|
8024
|
+
const quoteIdentifier = dialectFor(driver).quoteIdentifier;
|
|
7988
8025
|
const table = quoteIdentifier(blueprint.table);
|
|
7989
8026
|
const parts = blueprint.columns.map((column) => compileColumn(driver, column, "create"));
|
|
7990
8027
|
for (const index of blueprint.indexes) {
|
|
7991
|
-
if (index.kind === "unique"
|
|
8028
|
+
if (index.kind === "unique") {
|
|
7992
8029
|
const columns = index.columns.map((column) => quoteIdentifier(column)).join(", ");
|
|
7993
|
-
|
|
8030
|
+
const name = index.name ? `CONSTRAINT ${quoteIdentifier(index.name)} ` : "";
|
|
8031
|
+
parts.push(`${name}UNIQUE (${columns})`);
|
|
7994
8032
|
}
|
|
7995
8033
|
}
|
|
8034
|
+
parts.push(...compileTableConstraints(driver, blueprint));
|
|
7996
8035
|
const statements = [`CREATE TABLE IF NOT EXISTS ${table} (
|
|
7997
8036
|
${parts.join(`,
|
|
7998
8037
|
`)}
|
|
@@ -8010,6 +8049,7 @@ function compileCreateTable(driver, blueprint) {
|
|
|
8010
8049
|
return statements;
|
|
8011
8050
|
}
|
|
8012
8051
|
function compileAlterTable(driver, blueprint) {
|
|
8052
|
+
const quoteIdentifier = dialectFor(driver).quoteIdentifier;
|
|
8013
8053
|
const statements = [];
|
|
8014
8054
|
const table = quoteIdentifier(blueprint.table);
|
|
8015
8055
|
for (const column of blueprint.columns) {
|
|
@@ -8017,12 +8057,20 @@ function compileAlterTable(driver, blueprint) {
|
|
|
8017
8057
|
statements.push(`ALTER TABLE ${table}
|
|
8018
8058
|
${addPrefix} ${compileColumn(driver, column, "alter")}`);
|
|
8019
8059
|
}
|
|
8060
|
+
if (blueprint.foreignKeys.length || blueprint.checks.length) {
|
|
8061
|
+
if (driver === "sqlite") {
|
|
8062
|
+
throw new UnsupportedSchemaFeatureError("adding table constraints; rebuild the SQLite table", driver);
|
|
8063
|
+
}
|
|
8064
|
+
for (const constraint of compileTableConstraints(driver, blueprint)) {
|
|
8065
|
+
statements.push(`ALTER TABLE ${table} ADD ${constraint}`);
|
|
8066
|
+
}
|
|
8067
|
+
}
|
|
8020
8068
|
for (const columnName of blueprint.droppedColumns) {
|
|
8021
8069
|
const dropPrefix = driver === "pgsql" ? "DROP COLUMN IF EXISTS" : "DROP COLUMN";
|
|
8022
8070
|
statements.push(`ALTER TABLE ${table} ${dropPrefix} ${quoteIdentifier(columnName)}`);
|
|
8023
8071
|
}
|
|
8024
8072
|
for (const indexName of blueprint.droppedIndexes) {
|
|
8025
|
-
statements.push(`DROP INDEX IF EXISTS ${quoteIdentifier(indexName)}`);
|
|
8073
|
+
statements.push(driver === "mysql" ? `DROP INDEX ${quoteIdentifier(indexName)} ON ${table}` : `DROP INDEX IF EXISTS ${quoteIdentifier(indexName)}`);
|
|
8026
8074
|
}
|
|
8027
8075
|
for (const index of blueprint.indexes) {
|
|
8028
8076
|
if (index.kind === "index" || index.kind === "unique") {
|
|
@@ -8034,10 +8082,12 @@ ${addPrefix} ${compileColumn(driver, column, "alter")}`);
|
|
|
8034
8082
|
return statements;
|
|
8035
8083
|
}
|
|
8036
8084
|
function compileDropTable(driver, tableName) {
|
|
8085
|
+
const quoteIdentifier = dialectFor(driver).quoteIdentifier;
|
|
8037
8086
|
const cascade = driver === "pgsql" ? " CASCADE" : "";
|
|
8038
8087
|
return [`DROP TABLE IF EXISTS ${quoteIdentifier(tableName)}${cascade}`];
|
|
8039
8088
|
}
|
|
8040
8089
|
function compileColumn(driver, column, mode) {
|
|
8090
|
+
const quoteIdentifier = dialectFor(driver).quoteIdentifier;
|
|
8041
8091
|
const parts = [quoteIdentifier(column.name), compileColumnType(driver, column)];
|
|
8042
8092
|
if (column.autoIncrement && driver === "mysql") {
|
|
8043
8093
|
parts[1] = `${parts[1]} AUTO_INCREMENT`;
|
|
@@ -8075,7 +8125,8 @@ function compileColumn(driver, column, mode) {
|
|
|
8075
8125
|
}
|
|
8076
8126
|
return parts.join(" ");
|
|
8077
8127
|
}
|
|
8078
|
-
function compileIndex(
|
|
8128
|
+
function compileIndex(driver, tableName, index) {
|
|
8129
|
+
const quoteIdentifier = dialectFor(driver).quoteIdentifier;
|
|
8079
8130
|
const indexName = index.name ?? defaultIndexName(tableName, index.columns, index.kind === "unique" ? "unique" : "index");
|
|
8080
8131
|
const columns = index.columns.map((column) => {
|
|
8081
8132
|
const quoted = quoteIdentifier(column);
|
|
@@ -8085,9 +8136,11 @@ function compileIndex(_driver, tableName, index) {
|
|
|
8085
8136
|
return quoted;
|
|
8086
8137
|
}).join(", ");
|
|
8087
8138
|
const unique = index.kind === "unique" ? "UNIQUE " : "";
|
|
8088
|
-
|
|
8139
|
+
const existence = driver === "mysql" ? "" : "IF NOT EXISTS ";
|
|
8140
|
+
return `CREATE ${unique}INDEX ${existence}${quoteIdentifier(indexName)} ON ${quoteIdentifier(tableName)}(${columns})`;
|
|
8089
8141
|
}
|
|
8090
8142
|
function compileSpecialIndex(driver, tableName, index) {
|
|
8143
|
+
const quoteIdentifier = dialectFor(driver).quoteIdentifier;
|
|
8091
8144
|
const indexName = index.name ?? defaultIndexName(tableName, index.columns, index.kind);
|
|
8092
8145
|
const columns = index.columns.map((column) => quoteIdentifier(column)).join(", ");
|
|
8093
8146
|
switch (index.kind) {
|