@dbsp/core 1.10.0 → 1.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +3485 -3447
- package/dist/index.js +1110 -960
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -58,6 +58,9 @@ __reExport(intent_ast_exports, types_star);
|
|
|
58
58
|
import * as types_star from "@dbsp/types";
|
|
59
59
|
|
|
60
60
|
// src/model-impl.ts
|
|
61
|
+
function hasExternalSchema(schemaName) {
|
|
62
|
+
return schemaName != null && schemaName.trim().length > 0;
|
|
63
|
+
}
|
|
61
64
|
var ModelIRImpl = class {
|
|
62
65
|
tables;
|
|
63
66
|
externalTables;
|
|
@@ -178,7 +181,7 @@ var ModelIRImpl = class {
|
|
|
178
181
|
}
|
|
179
182
|
for (const table of this.tables.values()) {
|
|
180
183
|
for (const fk of table.foreignKeys) {
|
|
181
|
-
if (!this.tables.has(fk.references.table) && !this.externalTables.has(fk.references.table)) {
|
|
184
|
+
if (!hasExternalSchema(fk.references.schema) && !this.tables.has(fk.references.table) && !this.externalTables.has(fk.references.table)) {
|
|
182
185
|
errors.push(
|
|
183
186
|
`Table "${table.name}" has FK referencing non-existent table "${fk.references.table}"`
|
|
184
187
|
);
|
|
@@ -623,11 +626,25 @@ function createTablesProxy(model, tableNames, options) {
|
|
|
623
626
|
}
|
|
624
627
|
|
|
625
628
|
// src/dx/schema.ts
|
|
629
|
+
function normalizeReferenceSchema(schemaName) {
|
|
630
|
+
if (schemaName == null) return void 0;
|
|
631
|
+
const t = schemaName.trim();
|
|
632
|
+
return t.length > 0 ? t : void 0;
|
|
633
|
+
}
|
|
634
|
+
function hasExternalSchema2(schemaName) {
|
|
635
|
+
return normalizeReferenceSchema(schemaName) !== void 0;
|
|
636
|
+
}
|
|
637
|
+
function normalizeRefOptions(options) {
|
|
638
|
+
const schemaName = normalizeReferenceSchema(options.schema);
|
|
639
|
+
if (schemaName === options.schema) return options;
|
|
640
|
+
const { schema: _schema, ...rest } = options;
|
|
641
|
+
return schemaName === void 0 ? rest : { ...rest, schema: schemaName };
|
|
642
|
+
}
|
|
626
643
|
function ref(target, options) {
|
|
627
644
|
return {
|
|
628
645
|
__brand: "ref",
|
|
629
646
|
target,
|
|
630
|
-
options: options
|
|
647
|
+
options: options ? normalizeRefOptions(options) : {}
|
|
631
648
|
};
|
|
632
649
|
}
|
|
633
650
|
function isRef(value) {
|
|
@@ -653,6 +670,7 @@ function schema(definition, constraints, options, extras) {
|
|
|
653
670
|
);
|
|
654
671
|
return {
|
|
655
672
|
definition,
|
|
673
|
+
...constraints !== void 0 ? { constraints } : {},
|
|
656
674
|
model,
|
|
657
675
|
tableNames,
|
|
658
676
|
tables,
|
|
@@ -721,21 +739,21 @@ function validateRefs(definition, tableNames) {
|
|
|
721
739
|
for (const [tableName, tableDef] of Object.entries(definition)) {
|
|
722
740
|
for (const [columnName, columnDef] of Object.entries(tableDef)) {
|
|
723
741
|
if (isRef(columnDef)) {
|
|
724
|
-
if (!tableSet.has(columnDef.target)) {
|
|
742
|
+
if (!hasExternalSchema2(columnDef.options.schema) && !tableSet.has(columnDef.target)) {
|
|
725
743
|
throw new SchemaValidationError(
|
|
726
744
|
`Foreign key '${columnName}' references non-existent table '${columnDef.target}'`,
|
|
727
745
|
tableName,
|
|
728
746
|
columnName
|
|
729
747
|
);
|
|
730
748
|
}
|
|
731
|
-
if (columnDef.target === tableName && !columnDef.options.roles) {
|
|
749
|
+
if (!hasExternalSchema2(columnDef.options.schema) && columnDef.target === tableName && !columnDef.options.roles) {
|
|
732
750
|
throw new SchemaValidationError(
|
|
733
751
|
`Self-referential FK '${columnName}' must have 'roles' option with parent/children names`,
|
|
734
752
|
tableName,
|
|
735
753
|
columnName
|
|
736
754
|
);
|
|
737
755
|
}
|
|
738
|
-
if (columnDef.target !== tableName && columnDef.options.roles) {
|
|
756
|
+
if ((hasExternalSchema2(columnDef.options.schema) || columnDef.target !== tableName) && columnDef.options.roles) {
|
|
739
757
|
throw new SchemaValidationError(
|
|
740
758
|
`'roles' option is only valid for self-referential FKs, but '${columnName}' references '${columnDef.target}'`,
|
|
741
759
|
tableName,
|
|
@@ -751,14 +769,6 @@ function validateFkTargets(tables) {
|
|
|
751
769
|
const tableMap = new Map(tables.map((t) => [t.name, t]));
|
|
752
770
|
for (const table of tables) {
|
|
753
771
|
for (const fk of table.foreignKeys) {
|
|
754
|
-
const target = tableMap.get(fk.references.table);
|
|
755
|
-
if (!target) {
|
|
756
|
-
throw new SchemaValidationError(
|
|
757
|
-
`Foreign key in '${table.name}' references non-existent table '${fk.references.table}'`,
|
|
758
|
-
table.name,
|
|
759
|
-
fk.columns[0]
|
|
760
|
-
);
|
|
761
|
-
}
|
|
762
772
|
for (const srcCol of fk.columns) {
|
|
763
773
|
if (!table.columns.some((c) => c.name === srcCol)) {
|
|
764
774
|
throw new SchemaValidationError(
|
|
@@ -782,6 +792,15 @@ function validateFkTargets(tables) {
|
|
|
782
792
|
fk.columns[0]
|
|
783
793
|
);
|
|
784
794
|
}
|
|
795
|
+
if (hasExternalSchema2(fk.references.schema)) continue;
|
|
796
|
+
const target = tableMap.get(fk.references.table);
|
|
797
|
+
if (!target) {
|
|
798
|
+
throw new SchemaValidationError(
|
|
799
|
+
`Foreign key in '${table.name}' references non-existent table '${fk.references.table}'`,
|
|
800
|
+
table.name,
|
|
801
|
+
fk.columns[0]
|
|
802
|
+
);
|
|
803
|
+
}
|
|
785
804
|
for (const refCol of fk.references.columns) {
|
|
786
805
|
if (!target.columns.some((c) => c.name === refCol)) {
|
|
787
806
|
throw new SchemaValidationError(
|
|
@@ -955,6 +974,14 @@ function getReferencedColumnType(targetDef, referencedCol, definition, visited)
|
|
|
955
974
|
}
|
|
956
975
|
function buildRefColumn(columnName, columnDef, definition) {
|
|
957
976
|
const targetDef = definition[columnDef.target];
|
|
977
|
+
const externalSchema = normalizeReferenceSchema(columnDef.options.schema);
|
|
978
|
+
if (externalSchema !== void 0) {
|
|
979
|
+
throw new SchemaValidationError(
|
|
980
|
+
`Cannot infer column type for foreign key column '${columnName}' to external table '${externalSchema}.${columnDef.target}'. Declare the source column with an explicit type and add the external foreign key at the table level with SchemaConstraints.foreignKeys.`,
|
|
981
|
+
void 0,
|
|
982
|
+
columnName
|
|
983
|
+
);
|
|
984
|
+
}
|
|
958
985
|
const targetCol = columnDef.options.references?.length === 1 ? columnDef.options.references[0] : void 0;
|
|
959
986
|
const inferredType = targetCol && targetDef ? getReferencedColumnType(targetDef, targetCol, definition) ?? getTargetPkType(definition, columnDef.target) : getTargetPkType(definition, columnDef.target);
|
|
960
987
|
const col2 = {
|
|
@@ -972,7 +999,8 @@ function buildRefColumn(columnName, columnDef, definition) {
|
|
|
972
999
|
columns: [columnName],
|
|
973
1000
|
references: {
|
|
974
1001
|
table: columnDef.target,
|
|
975
|
-
columns: columnDef.options.references ?? ["id"]
|
|
1002
|
+
columns: columnDef.options.references ?? ["id"],
|
|
1003
|
+
...externalSchema !== void 0 ? { schema: externalSchema } : {}
|
|
976
1004
|
}
|
|
977
1005
|
};
|
|
978
1006
|
if (columnDef.options.onDelete) fk.onDelete = columnDef.options.onDelete;
|
|
@@ -1028,7 +1056,7 @@ function buildPseudoColumns(tableName, refsByTable, finalPk) {
|
|
|
1028
1056
|
const refs = refsByTable.get(tableName) ?? [];
|
|
1029
1057
|
const pkColumn = typeof finalPk === "string" ? finalPk : finalPk?.[0] ?? "id";
|
|
1030
1058
|
for (const ref3 of refs) {
|
|
1031
|
-
if (ref3.options.roles && ref3.target === tableName) {
|
|
1059
|
+
if (!hasExternalSchema2(ref3.options.schema) && ref3.options.roles && ref3.target === tableName) {
|
|
1032
1060
|
pseudoColumns.push(
|
|
1033
1061
|
createPseudoColumnMetadata(
|
|
1034
1062
|
tableName,
|
|
@@ -1095,11 +1123,13 @@ function buildTableConstraints(tableName, constraints) {
|
|
|
1095
1123
|
tableName
|
|
1096
1124
|
);
|
|
1097
1125
|
}
|
|
1126
|
+
const externalSchema = normalizeReferenceSchema(fkRef.options.schema);
|
|
1098
1127
|
const fk = {
|
|
1099
1128
|
columns: [...fkRef.options.columns],
|
|
1100
1129
|
references: {
|
|
1101
1130
|
table: fkRef.target,
|
|
1102
|
-
columns: fkRef.options.references ? [...fkRef.options.references] : ["id"]
|
|
1131
|
+
columns: fkRef.options.references ? [...fkRef.options.references] : ["id"],
|
|
1132
|
+
...externalSchema !== void 0 ? { schema: externalSchema } : {}
|
|
1103
1133
|
}
|
|
1104
1134
|
};
|
|
1105
1135
|
if (fkRef.options.onDelete) fk.onDelete = fkRef.options.onDelete;
|
|
@@ -1147,6 +1177,7 @@ function buildRelations(_definition, refsByTable, tableNames, constraints) {
|
|
|
1147
1177
|
for (const tableName of tableNames) {
|
|
1148
1178
|
const refs = refsByTable.get(tableName) || [];
|
|
1149
1179
|
for (const ref3 of refs) {
|
|
1180
|
+
if (hasExternalSchema2(ref3.options.schema)) continue;
|
|
1150
1181
|
if (ref3.options.roles) {
|
|
1151
1182
|
const roles = ref3.options.roles;
|
|
1152
1183
|
relations.push({
|
|
@@ -1267,6 +1298,7 @@ function addCompositeConstraintRelations(relations, tableNames, constraints) {
|
|
|
1267
1298
|
const foreignKeys = constraints[tableName]?.foreignKeys;
|
|
1268
1299
|
if (!foreignKeys) continue;
|
|
1269
1300
|
for (const fkRef of foreignKeys) {
|
|
1301
|
+
if (hasExternalSchema2(fkRef.options.schema)) continue;
|
|
1270
1302
|
if (!tableSet.has(fkRef.target)) continue;
|
|
1271
1303
|
const columns = fkRef.options.columns;
|
|
1272
1304
|
if (!columns?.length) continue;
|
|
@@ -1338,38 +1370,102 @@ async function getSchemaFromDb(adapter, options) {
|
|
|
1338
1370
|
const model = introspectionResult;
|
|
1339
1371
|
const introspectedAt = introspectionResult.introspectedAt;
|
|
1340
1372
|
const fkLookup = /* @__PURE__ */ new Map();
|
|
1373
|
+
const compositeExternalFkLookup = /* @__PURE__ */ new Map();
|
|
1341
1374
|
for (const table of model.tables.values()) {
|
|
1342
1375
|
const tableFks = /* @__PURE__ */ new Map();
|
|
1343
1376
|
for (const fk of table.foreignKeys) {
|
|
1377
|
+
const externalSchema = normalizeReferenceSchema(fk.references.schema);
|
|
1378
|
+
if (externalSchema !== void 0 && fk.columns.length > 1) {
|
|
1379
|
+
const tableCompositeFks = compositeExternalFkLookup.get(table.name) ?? [];
|
|
1380
|
+
tableCompositeFks.push({
|
|
1381
|
+
...fk,
|
|
1382
|
+
references: {
|
|
1383
|
+
...fk.references,
|
|
1384
|
+
schema: externalSchema
|
|
1385
|
+
}
|
|
1386
|
+
});
|
|
1387
|
+
compositeExternalFkLookup.set(table.name, tableCompositeFks);
|
|
1388
|
+
continue;
|
|
1389
|
+
}
|
|
1344
1390
|
const fkColumn = fk.columns[0];
|
|
1345
1391
|
if (!fkColumn) continue;
|
|
1346
1392
|
const column = table.columns.find((c) => c.name === fkColumn);
|
|
1347
1393
|
tableFks.set(fkColumn, {
|
|
1348
1394
|
target: fk.references.table,
|
|
1395
|
+
refs: fk.references.columns,
|
|
1396
|
+
...externalSchema !== void 0 ? { schema: externalSchema } : {},
|
|
1349
1397
|
nullable: column?.nullable ?? true,
|
|
1350
|
-
unique: column?.unique ?? false
|
|
1398
|
+
unique: column?.unique ?? false,
|
|
1399
|
+
...fk.onDelete !== void 0 ? { onDelete: fk.onDelete } : {},
|
|
1400
|
+
...fk.onUpdate !== void 0 ? { onUpdate: fk.onUpdate } : {}
|
|
1351
1401
|
});
|
|
1352
1402
|
}
|
|
1353
1403
|
fkLookup.set(table.name, tableFks);
|
|
1354
1404
|
}
|
|
1355
1405
|
const definition = {};
|
|
1406
|
+
const constraints = {};
|
|
1356
1407
|
for (const table of model.tables.values()) {
|
|
1357
1408
|
const tableDef = {};
|
|
1358
1409
|
const tableFks = fkLookup.get(table.name) ?? /* @__PURE__ */ new Map();
|
|
1410
|
+
const compositeExternalFks = compositeExternalFkLookup.get(table.name) ?? [];
|
|
1359
1411
|
for (const column of table.columns) {
|
|
1360
1412
|
const fk = tableFks.get(column.name);
|
|
1361
1413
|
if (fk) {
|
|
1414
|
+
if (fk.schema !== void 0) {
|
|
1415
|
+
const sourceColumn = {
|
|
1416
|
+
type: columnTypeToJsType(column.type),
|
|
1417
|
+
nullable: fk.nullable
|
|
1418
|
+
};
|
|
1419
|
+
if (fk.unique) sourceColumn.unique = true;
|
|
1420
|
+
tableDef[column.name] = sourceColumn;
|
|
1421
|
+
constraints[table.name] ??= {};
|
|
1422
|
+
const tableConstraints = constraints[table.name];
|
|
1423
|
+
tableConstraints.foreignKeys ??= [];
|
|
1424
|
+
const foreignKeys = tableConstraints.foreignKeys;
|
|
1425
|
+
foreignKeys.push(
|
|
1426
|
+
ref(fk.target, {
|
|
1427
|
+
schema: fk.schema,
|
|
1428
|
+
columns: [column.name],
|
|
1429
|
+
references: fk.refs,
|
|
1430
|
+
...fk.onDelete !== void 0 ? { onDelete: fk.onDelete } : {},
|
|
1431
|
+
...fk.onUpdate !== void 0 ? { onUpdate: fk.onUpdate } : {}
|
|
1432
|
+
})
|
|
1433
|
+
);
|
|
1434
|
+
continue;
|
|
1435
|
+
}
|
|
1436
|
+
const usesDefaultReferences = fk.refs.length === 1 && fk.refs[0] === "id";
|
|
1362
1437
|
tableDef[column.name] = ref(fk.target, {
|
|
1363
1438
|
nullable: fk.nullable,
|
|
1364
|
-
unique: fk.unique
|
|
1439
|
+
unique: fk.unique,
|
|
1440
|
+
...!usesDefaultReferences ? { references: fk.refs } : {},
|
|
1441
|
+
...fk.onDelete !== void 0 && fk.onDelete !== "NO ACTION" ? { onDelete: fk.onDelete } : {},
|
|
1442
|
+
...fk.onUpdate !== void 0 && fk.onUpdate !== "NO ACTION" ? { onUpdate: fk.onUpdate } : {}
|
|
1365
1443
|
});
|
|
1366
1444
|
} else {
|
|
1367
1445
|
tableDef[column.name] = columnTypeToJsType(column.type);
|
|
1368
1446
|
}
|
|
1369
1447
|
}
|
|
1448
|
+
for (const fk of compositeExternalFks) {
|
|
1449
|
+
const externalSchema = fk.references.schema;
|
|
1450
|
+
if (externalSchema === void 0) continue;
|
|
1451
|
+
constraints[table.name] ??= {};
|
|
1452
|
+
const tableConstraints = constraints[table.name];
|
|
1453
|
+
tableConstraints.foreignKeys ??= [];
|
|
1454
|
+
const foreignKeys = tableConstraints.foreignKeys;
|
|
1455
|
+
foreignKeys.push(
|
|
1456
|
+
ref(fk.references.table, {
|
|
1457
|
+
schema: externalSchema,
|
|
1458
|
+
columns: fk.columns,
|
|
1459
|
+
references: fk.references.columns,
|
|
1460
|
+
...fk.onDelete !== void 0 ? { onDelete: fk.onDelete } : {},
|
|
1461
|
+
...fk.onUpdate !== void 0 ? { onUpdate: fk.onUpdate } : {}
|
|
1462
|
+
})
|
|
1463
|
+
);
|
|
1464
|
+
}
|
|
1370
1465
|
definition[table.name] = tableDef;
|
|
1371
1466
|
}
|
|
1372
1467
|
const tableNames = Object.keys(definition);
|
|
1468
|
+
const generatedConstraints = Object.keys(constraints).length > 0 ? constraints : void 0;
|
|
1373
1469
|
const tables = createTablesProxy(
|
|
1374
1470
|
model,
|
|
1375
1471
|
tableNames
|
|
@@ -1380,6 +1476,9 @@ async function getSchemaFromDb(adapter, options) {
|
|
|
1380
1476
|
tableNames,
|
|
1381
1477
|
tables
|
|
1382
1478
|
};
|
|
1479
|
+
if (generatedConstraints !== void 0) {
|
|
1480
|
+
result.constraints = generatedConstraints;
|
|
1481
|
+
}
|
|
1383
1482
|
if (adapter.dbCasing !== void 0) {
|
|
1384
1483
|
result.dbCasing = adapter.dbCasing;
|
|
1385
1484
|
}
|
|
@@ -1464,7 +1563,7 @@ function detectForeignKeys(tableName, table, conventions, tableNames) {
|
|
|
1464
1563
|
for (const [colName, colDef] of Object.entries(table)) {
|
|
1465
1564
|
if (colDef.references) {
|
|
1466
1565
|
const targetTable = colDef.references.table;
|
|
1467
|
-
if (tableNames.has(targetTable)) {
|
|
1566
|
+
if (colDef.references.schema === void 0 && tableNames.has(targetTable)) {
|
|
1468
1567
|
const inferredName = colName.endsWith("Id") ? colName.slice(0, -2) : singularize(targetTable);
|
|
1469
1568
|
const isSelfRef = targetTable === tableName;
|
|
1470
1569
|
const detectedFK = {
|
|
@@ -3219,367 +3318,780 @@ function batchValues(data, columns, types, opts) {
|
|
|
3219
3318
|
});
|
|
3220
3319
|
}
|
|
3221
3320
|
|
|
3222
|
-
// src/dx/
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
function toExpressionIntent(input) {
|
|
3227
|
-
if (input instanceof ExpressionRef) return input.intent;
|
|
3228
|
-
if (typeof input === "object" && input !== null && "__expr" in input && input.__expr === true && "intent" in input) {
|
|
3229
|
-
return input.intent;
|
|
3321
|
+
// src/dx/column-utils.ts
|
|
3322
|
+
function getColumnName(field) {
|
|
3323
|
+
if (typeof field === "string") {
|
|
3324
|
+
return field;
|
|
3230
3325
|
}
|
|
3231
|
-
|
|
3232
|
-
|
|
3326
|
+
const colName = field[COLUMN_META];
|
|
3327
|
+
if (colName === void 0) {
|
|
3328
|
+
throw new Error("Invalid ColumnRef: missing COLUMN_META");
|
|
3233
3329
|
}
|
|
3234
|
-
return
|
|
3330
|
+
return colName;
|
|
3235
3331
|
}
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
constructor(
|
|
3240
|
-
this.
|
|
3332
|
+
|
|
3333
|
+
// src/dx/window-functions.ts
|
|
3334
|
+
var WindowBuilder = class _WindowBuilder {
|
|
3335
|
+
constructor(fnKind, partitions = [], orders = []) {
|
|
3336
|
+
this.fnKind = fnKind;
|
|
3337
|
+
this.partitions = partitions;
|
|
3338
|
+
this.orders = orders;
|
|
3241
3339
|
}
|
|
3340
|
+
fnKind;
|
|
3341
|
+
partitions;
|
|
3342
|
+
orders;
|
|
3242
3343
|
/**
|
|
3243
|
-
*
|
|
3244
|
-
*
|
|
3344
|
+
* Create a ranking window builder (row_number, rank, dense_rank)
|
|
3345
|
+
* @internal Use factory functions instead: rowNumber(), rank(), denseRank()
|
|
3245
3346
|
*/
|
|
3246
|
-
|
|
3247
|
-
return new
|
|
3248
|
-
...this.intent,
|
|
3249
|
-
as: alias
|
|
3250
|
-
});
|
|
3251
|
-
}
|
|
3252
|
-
/** WHERE: expr = value */
|
|
3253
|
-
eq(value) {
|
|
3254
|
-
return { kind: "expression", expr: this.intent, operator: "eq", value };
|
|
3255
|
-
}
|
|
3256
|
-
/** WHERE: expr != value */
|
|
3257
|
-
neq(value) {
|
|
3258
|
-
return { kind: "expression", expr: this.intent, operator: "neq", value };
|
|
3347
|
+
static ranking(fn2) {
|
|
3348
|
+
return new _WindowBuilder({ type: "ranking", fn: fn2 });
|
|
3259
3349
|
}
|
|
3260
|
-
/**
|
|
3261
|
-
|
|
3262
|
-
|
|
3350
|
+
/**
|
|
3351
|
+
* Create an aggregate window builder (sum, avg, count, min, max)
|
|
3352
|
+
* @internal Use factory functions instead: sum(field), avg(field), etc.
|
|
3353
|
+
*/
|
|
3354
|
+
static aggregate(fn2, field) {
|
|
3355
|
+
return new _WindowBuilder({
|
|
3356
|
+
type: "aggregate",
|
|
3357
|
+
fn: fn2,
|
|
3358
|
+
...field !== void 0 ? { field } : {}
|
|
3359
|
+
});
|
|
3263
3360
|
}
|
|
3264
|
-
/**
|
|
3265
|
-
|
|
3266
|
-
|
|
3361
|
+
/**
|
|
3362
|
+
* Create an offset window builder (lag, lead)
|
|
3363
|
+
* @internal Use factory functions instead: lag(field), lead(field)
|
|
3364
|
+
*/
|
|
3365
|
+
static offset(fn2, field) {
|
|
3366
|
+
return new _WindowBuilder({ type: "offset", fn: fn2, field });
|
|
3267
3367
|
}
|
|
3268
|
-
/**
|
|
3269
|
-
|
|
3270
|
-
|
|
3368
|
+
/**
|
|
3369
|
+
* Add partition field(s) to the OVER clause.
|
|
3370
|
+
* Multiple calls APPEND fields (not replace).
|
|
3371
|
+
* Supports both string field names and ColumnRef (DX-040).
|
|
3372
|
+
*
|
|
3373
|
+
* @example
|
|
3374
|
+
* sum('amount').partitionBy('user_id').partitionBy('category')
|
|
3375
|
+
* // → PARTITION BY "user_id", "category"
|
|
3376
|
+
*
|
|
3377
|
+
* @example DX-040 with ColumnRef
|
|
3378
|
+
* rank().partitionBy(users.dept).orderBy(users.salary, 'desc')
|
|
3379
|
+
*/
|
|
3380
|
+
partitionBy(...fields) {
|
|
3381
|
+
const fieldNames = fields.map(
|
|
3382
|
+
(f) => typeof f === "string" ? f : getColumnName(f)
|
|
3383
|
+
);
|
|
3384
|
+
return new _WindowBuilder(
|
|
3385
|
+
this.fnKind,
|
|
3386
|
+
[...this.partitions, ...fieldNames],
|
|
3387
|
+
this.orders
|
|
3388
|
+
);
|
|
3271
3389
|
}
|
|
3272
|
-
/**
|
|
3273
|
-
|
|
3274
|
-
|
|
3390
|
+
/**
|
|
3391
|
+
* Add order field to the OVER clause.
|
|
3392
|
+
* Multiple calls APPEND fields (not replace).
|
|
3393
|
+
* Supports both string field names and ColumnRef (DX-040).
|
|
3394
|
+
*
|
|
3395
|
+
* @param field - Column name or ColumnRef to order by
|
|
3396
|
+
* @param direction - Sort direction: 'asc' (default) or 'desc'
|
|
3397
|
+
*
|
|
3398
|
+
* @example
|
|
3399
|
+
* rowNumber().orderBy('created_at').orderBy('id', 'desc')
|
|
3400
|
+
* // → ORDER BY "created_at" ASC, "id" DESC
|
|
3401
|
+
*
|
|
3402
|
+
* @example DX-040 with ColumnRef
|
|
3403
|
+
* rank().partitionBy(users.dept).orderBy(users.salary, 'desc')
|
|
3404
|
+
*/
|
|
3405
|
+
orderBy(field, direction = "asc") {
|
|
3406
|
+
const fieldName = typeof field === "string" ? field : getColumnName(field);
|
|
3407
|
+
return new _WindowBuilder(this.fnKind, this.partitions, [
|
|
3408
|
+
...this.orders,
|
|
3409
|
+
{ field: fieldName, direction }
|
|
3410
|
+
]);
|
|
3275
3411
|
}
|
|
3276
3412
|
/**
|
|
3277
|
-
*
|
|
3278
|
-
*
|
|
3279
|
-
* Returns a new ExpressionRef — does not mutate.
|
|
3413
|
+
* Finalize the window expression with an alias.
|
|
3414
|
+
* Returns ExpressionSpec for use in columns().
|
|
3280
3415
|
*
|
|
3281
|
-
* @
|
|
3282
|
-
*
|
|
3283
|
-
* @
|
|
3416
|
+
* @param alias - Required alias for the result column
|
|
3417
|
+
*
|
|
3418
|
+
* @example
|
|
3419
|
+
* columns(['id', rowNumber().orderBy('date').as('rn')])
|
|
3284
3420
|
*/
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3421
|
+
as(alias) {
|
|
3422
|
+
return {
|
|
3423
|
+
__expr: true,
|
|
3424
|
+
intent: this.toWindowIntent(alias)
|
|
3425
|
+
};
|
|
3426
|
+
}
|
|
3427
|
+
/**
|
|
3428
|
+
* Convert builder state to WindowIntent — produces the correct discriminated branch.
|
|
3429
|
+
* - ranking → RankingWindowIntent (no field)
|
|
3430
|
+
* - aggregate → AggregateWindowIntent (field required; COUNT uses '*' when omitted)
|
|
3431
|
+
* - offset → OffsetWindowIntent (field required)
|
|
3432
|
+
*/
|
|
3433
|
+
toWindowIntent(alias) {
|
|
3434
|
+
const over = {};
|
|
3435
|
+
if (this.partitions.length > 0) {
|
|
3436
|
+
over.partitionBy = this.partitions;
|
|
3290
3437
|
}
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3438
|
+
if (this.orders.length > 0) {
|
|
3439
|
+
over.orderBy = this.orders;
|
|
3440
|
+
}
|
|
3441
|
+
if (this.fnKind.type === "ranking") {
|
|
3442
|
+
return {
|
|
3443
|
+
kind: "window",
|
|
3444
|
+
function: this.fnKind.fn,
|
|
3445
|
+
alias,
|
|
3446
|
+
over
|
|
3447
|
+
};
|
|
3448
|
+
}
|
|
3449
|
+
if (this.fnKind.type === "offset") {
|
|
3450
|
+
return {
|
|
3451
|
+
kind: "window",
|
|
3452
|
+
function: this.fnKind.fn,
|
|
3453
|
+
field: this.fnKind.field,
|
|
3454
|
+
alias,
|
|
3455
|
+
over
|
|
3456
|
+
};
|
|
3457
|
+
}
|
|
3458
|
+
const { field } = this.fnKind;
|
|
3459
|
+
return {
|
|
3460
|
+
kind: "window",
|
|
3461
|
+
function: this.fnKind.fn,
|
|
3462
|
+
...field !== void 0 && { field },
|
|
3463
|
+
alias,
|
|
3464
|
+
over
|
|
3465
|
+
};
|
|
3295
3466
|
}
|
|
3296
3467
|
};
|
|
3297
|
-
function
|
|
3298
|
-
return
|
|
3299
|
-
kind: "ref",
|
|
3300
|
-
column
|
|
3301
|
-
});
|
|
3468
|
+
function rowNumber() {
|
|
3469
|
+
return WindowBuilder.ranking("row_number");
|
|
3302
3470
|
}
|
|
3303
|
-
function
|
|
3304
|
-
return
|
|
3305
|
-
kind: "param",
|
|
3306
|
-
value
|
|
3307
|
-
});
|
|
3471
|
+
function rank() {
|
|
3472
|
+
return WindowBuilder.ranking("rank");
|
|
3308
3473
|
}
|
|
3309
|
-
function
|
|
3310
|
-
|
|
3311
|
-
throw new Error(`Invalid type name: ${typeName}`);
|
|
3312
|
-
}
|
|
3313
|
-
return new ExpressionRef({
|
|
3314
|
-
kind: "cast",
|
|
3315
|
-
expr: expr.intent,
|
|
3316
|
-
typeName
|
|
3317
|
-
});
|
|
3474
|
+
function denseRank() {
|
|
3475
|
+
return WindowBuilder.ranking("dense_rank");
|
|
3318
3476
|
}
|
|
3319
|
-
function
|
|
3320
|
-
|
|
3321
|
-
throw new Error(`Invalid operator: ${operator}`);
|
|
3322
|
-
}
|
|
3323
|
-
return new ExpressionRef({
|
|
3324
|
-
kind: "customOp",
|
|
3325
|
-
operator,
|
|
3326
|
-
left: toExpressionIntent(left),
|
|
3327
|
-
right: toExpressionIntent(right)
|
|
3328
|
-
});
|
|
3477
|
+
function wSum(field) {
|
|
3478
|
+
return WindowBuilder.aggregate("sum", field);
|
|
3329
3479
|
}
|
|
3330
|
-
function
|
|
3331
|
-
|
|
3332
|
-
throw new Error(`Invalid function name: ${name}`);
|
|
3333
|
-
}
|
|
3334
|
-
const regularArgs = [];
|
|
3335
|
-
const orderByArgs = [];
|
|
3336
|
-
for (const arg of args) {
|
|
3337
|
-
if (isAggOrderByArg(arg)) {
|
|
3338
|
-
orderByArgs.push(arg);
|
|
3339
|
-
} else {
|
|
3340
|
-
regularArgs.push(toExpressionIntent(arg));
|
|
3341
|
-
}
|
|
3342
|
-
}
|
|
3343
|
-
const intent = {
|
|
3344
|
-
kind: "customFn",
|
|
3345
|
-
name,
|
|
3346
|
-
args: regularArgs,
|
|
3347
|
-
...orderByArgs.length > 0 ? { aggOrderBy: orderByArgs } : {}
|
|
3348
|
-
};
|
|
3349
|
-
return new ExpressionRef(intent);
|
|
3480
|
+
function wAvg(field) {
|
|
3481
|
+
return WindowBuilder.aggregate("avg", field);
|
|
3350
3482
|
}
|
|
3351
|
-
function
|
|
3352
|
-
return
|
|
3353
|
-
kind: "literal",
|
|
3354
|
-
value
|
|
3355
|
-
});
|
|
3483
|
+
function wCount(field) {
|
|
3484
|
+
return WindowBuilder.aggregate("count", field);
|
|
3356
3485
|
}
|
|
3357
|
-
function
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
return
|
|
3362
|
-
|
|
3486
|
+
function wMin(field) {
|
|
3487
|
+
return WindowBuilder.aggregate("min", field);
|
|
3488
|
+
}
|
|
3489
|
+
function wMax(field) {
|
|
3490
|
+
return WindowBuilder.aggregate("max", field);
|
|
3491
|
+
}
|
|
3492
|
+
function lag(field) {
|
|
3493
|
+
return WindowBuilder.offset("lag", field);
|
|
3494
|
+
}
|
|
3495
|
+
function lead(field) {
|
|
3496
|
+
return WindowBuilder.offset("lead", field);
|
|
3497
|
+
}
|
|
3498
|
+
|
|
3499
|
+
// src/dx/filters.ts
|
|
3500
|
+
function distinct(field) {
|
|
3501
|
+
return { field, distinct: true };
|
|
3502
|
+
}
|
|
3503
|
+
function isDistinctField(value) {
|
|
3504
|
+
return typeof value === "object" && value !== null && "field" in value && typeof value.field === "string" && "distinct" in value && value.distinct === true;
|
|
3505
|
+
}
|
|
3506
|
+
function createComparisonFilter(operator) {
|
|
3507
|
+
return (field, value) => ({
|
|
3508
|
+
kind: "comparison",
|
|
3509
|
+
field: getColumnName(field),
|
|
3363
3510
|
operator,
|
|
3364
|
-
|
|
3511
|
+
value
|
|
3365
3512
|
});
|
|
3366
3513
|
}
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3514
|
+
var eq = createComparisonFilter("eq");
|
|
3515
|
+
var neq = createComparisonFilter("neq");
|
|
3516
|
+
var gt = createComparisonFilter("gt");
|
|
3517
|
+
var gte = createComparisonFilter("gte");
|
|
3518
|
+
var lt = createComparisonFilter("lt");
|
|
3519
|
+
var lte = createComparisonFilter("lte");
|
|
3520
|
+
var isDistinctFrom = createComparisonFilter("isDistinctFrom");
|
|
3521
|
+
function like(field, pattern, options) {
|
|
3522
|
+
const caseInsensitive = typeof options === "boolean" ? options : options?.caseInsensitive;
|
|
3523
|
+
const escapeChar = typeof options === "object" ? options.escape : void 0;
|
|
3524
|
+
const intent = {
|
|
3525
|
+
kind: "like",
|
|
3526
|
+
field: getColumnName(field),
|
|
3527
|
+
pattern
|
|
3528
|
+
};
|
|
3529
|
+
const withCi = caseInsensitive !== void 0 ? { ...intent, caseInsensitive } : intent;
|
|
3530
|
+
if (escapeChar !== void 0) {
|
|
3531
|
+
return { ...withCi, escape: escapeChar };
|
|
3370
3532
|
}
|
|
3371
|
-
return
|
|
3372
|
-
kind: "namedArg",
|
|
3373
|
-
name,
|
|
3374
|
-
value: toExpressionIntent(value)
|
|
3375
|
-
});
|
|
3533
|
+
return withCi;
|
|
3376
3534
|
}
|
|
3377
|
-
function
|
|
3378
|
-
return
|
|
3535
|
+
function inArray(field, values) {
|
|
3536
|
+
return { kind: "in", field: getColumnName(field), values };
|
|
3379
3537
|
}
|
|
3380
|
-
function
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3538
|
+
function inSubquery(field, query) {
|
|
3539
|
+
const expr = "build" in query ? query.build() : query;
|
|
3540
|
+
return {
|
|
3541
|
+
kind: "in",
|
|
3542
|
+
field: getColumnName(field),
|
|
3543
|
+
subquery: expr.toIntent()
|
|
3544
|
+
};
|
|
3385
3545
|
}
|
|
3386
|
-
function
|
|
3387
|
-
return
|
|
3546
|
+
function any(field, values) {
|
|
3547
|
+
return { kind: "any", field: getColumnName(field), values };
|
|
3388
3548
|
}
|
|
3389
|
-
function
|
|
3390
|
-
return {
|
|
3549
|
+
function isNull(field) {
|
|
3550
|
+
return { kind: "null", field: getColumnName(field), operator: "isNull" };
|
|
3391
3551
|
}
|
|
3392
|
-
function
|
|
3393
|
-
|
|
3394
|
-
return fn("array_agg", colExpr, ...rest);
|
|
3552
|
+
function isNotNull(field) {
|
|
3553
|
+
return { kind: "null", field: getColumnName(field), operator: "isNotNull" };
|
|
3395
3554
|
}
|
|
3396
|
-
function
|
|
3397
|
-
const
|
|
3398
|
-
return
|
|
3555
|
+
function and(...conditions) {
|
|
3556
|
+
const flatConditions = conditions.length === 1 && Array.isArray(conditions[0]) ? conditions[0] : conditions;
|
|
3557
|
+
return { kind: "and", conditions: flatConditions };
|
|
3399
3558
|
}
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3559
|
+
function or(...conditions) {
|
|
3560
|
+
const flatConditions = conditions.length === 1 && Array.isArray(conditions[0]) ? conditions[0] : conditions;
|
|
3561
|
+
return { kind: "or", conditions: flatConditions };
|
|
3562
|
+
}
|
|
3563
|
+
function not(condition) {
|
|
3564
|
+
return { kind: "not", condition };
|
|
3565
|
+
}
|
|
3566
|
+
function exists(relation, options) {
|
|
3567
|
+
const result = { kind: "exists", relation };
|
|
3568
|
+
if (options?.where !== void 0) {
|
|
3569
|
+
result.where = options.where;
|
|
3405
3570
|
}
|
|
3406
|
-
if (
|
|
3407
|
-
|
|
3571
|
+
if (options?.recursive !== void 0) {
|
|
3572
|
+
result.recursive = options.recursive;
|
|
3408
3573
|
}
|
|
3409
|
-
if (
|
|
3410
|
-
|
|
3574
|
+
if (options?.include !== void 0) {
|
|
3575
|
+
result.include = options.include;
|
|
3411
3576
|
}
|
|
3412
|
-
return
|
|
3577
|
+
return result;
|
|
3413
3578
|
}
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3579
|
+
function notExists(relation, options) {
|
|
3580
|
+
const result = {
|
|
3581
|
+
kind: "notExists",
|
|
3582
|
+
relation
|
|
3583
|
+
};
|
|
3584
|
+
if (options?.where !== void 0) {
|
|
3585
|
+
result.where = options.where;
|
|
3418
3586
|
}
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
...this.branches,
|
|
3422
|
-
{ condition, result: toResultIntent(thenValue) }
|
|
3423
|
-
]);
|
|
3587
|
+
if (options?.recursive !== void 0) {
|
|
3588
|
+
result.recursive = options.recursive;
|
|
3424
3589
|
}
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
kind: "case",
|
|
3428
|
-
when: this.branches,
|
|
3429
|
-
else: toResultIntent(elseValue)
|
|
3430
|
-
};
|
|
3431
|
-
return new ExpressionRef(intent);
|
|
3590
|
+
if (options?.include !== void 0) {
|
|
3591
|
+
result.include = options.include;
|
|
3432
3592
|
}
|
|
3433
|
-
|
|
3434
|
-
|
|
3593
|
+
return result;
|
|
3594
|
+
}
|
|
3595
|
+
function rawExists(sq) {
|
|
3596
|
+
const intent = "buildIntent" in sq ? sq.buildIntent() : sq.build().toIntent();
|
|
3597
|
+
return { kind: "rawExists", subquery: intent };
|
|
3598
|
+
}
|
|
3599
|
+
function rawNotExists(sq) {
|
|
3600
|
+
const intent = "buildIntent" in sq ? sq.buildIntent() : sq.build().toIntent();
|
|
3601
|
+
return { kind: "rawNotExists", subquery: intent };
|
|
3602
|
+
}
|
|
3603
|
+
function getRelationName(rel) {
|
|
3604
|
+
const meta = rel[RELATION_META];
|
|
3605
|
+
if (!meta) {
|
|
3606
|
+
throw new Error("Invalid RelationRef: missing RELATION_META");
|
|
3435
3607
|
}
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3608
|
+
return meta.target;
|
|
3609
|
+
}
|
|
3610
|
+
function every(relation, filter) {
|
|
3611
|
+
const relationName = getRelationName(relation);
|
|
3612
|
+
const where = filter(relation);
|
|
3613
|
+
return {
|
|
3614
|
+
kind: "relationFilter",
|
|
3615
|
+
relation: relationName,
|
|
3616
|
+
where,
|
|
3617
|
+
mode: "every"
|
|
3618
|
+
};
|
|
3619
|
+
}
|
|
3620
|
+
function none(relation, filter) {
|
|
3621
|
+
const relationName = getRelationName(relation);
|
|
3622
|
+
const where = filter(relation);
|
|
3623
|
+
return {
|
|
3624
|
+
kind: "relationFilter",
|
|
3625
|
+
relation: relationName,
|
|
3626
|
+
where,
|
|
3627
|
+
mode: "none"
|
|
3628
|
+
};
|
|
3629
|
+
}
|
|
3630
|
+
function some(relation, filter) {
|
|
3631
|
+
const relationName = getRelationName(relation);
|
|
3632
|
+
const where = filter(relation);
|
|
3633
|
+
return {
|
|
3634
|
+
kind: "relationFilter",
|
|
3635
|
+
relation: relationName,
|
|
3636
|
+
where,
|
|
3637
|
+
mode: "some"
|
|
3638
|
+
};
|
|
3639
|
+
}
|
|
3640
|
+
function coalesce(fields, as) {
|
|
3641
|
+
if (fields.length === 0) {
|
|
3642
|
+
throw new Error("coalesce() requires at least one field");
|
|
3442
3643
|
}
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3644
|
+
if (!as || as.trim() === "") {
|
|
3645
|
+
throw new Error("coalesce() requires a non-empty alias");
|
|
3646
|
+
}
|
|
3647
|
+
for (const f of fields) {
|
|
3648
|
+
validateIdentifier(f, "column");
|
|
3649
|
+
}
|
|
3650
|
+
validateIdentifier(as, "column");
|
|
3651
|
+
return {
|
|
3652
|
+
__expr: true,
|
|
3653
|
+
intent: { kind: "coalesce", fields, as }
|
|
3654
|
+
};
|
|
3446
3655
|
}
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
function requireAdapter(adapter, operationName) {
|
|
3450
|
-
if (!adapter) {
|
|
3656
|
+
function raw(sqlFragment, as) {
|
|
3657
|
+
if (typeof as !== "string" || as.trim().length === 0) {
|
|
3451
3658
|
throw new InvalidOperationError(
|
|
3452
|
-
|
|
3453
|
-
"
|
|
3659
|
+
"raw",
|
|
3660
|
+
"raw() requires an alias as second argument, e.g. raw('COUNT(*)', 'count')"
|
|
3454
3661
|
);
|
|
3455
3662
|
}
|
|
3456
|
-
|
|
3457
|
-
|
|
3663
|
+
validateIdentifier(as, "column");
|
|
3664
|
+
return {
|
|
3665
|
+
__expr: true,
|
|
3666
|
+
intent: { kind: "raw", sql: sqlFragment, as }
|
|
3667
|
+
};
|
|
3668
|
+
}
|
|
3669
|
+
function col(column, alias) {
|
|
3670
|
+
validateIdentifier(column, "column");
|
|
3671
|
+
validateIdentifier(alias, "column");
|
|
3672
|
+
return {
|
|
3673
|
+
__expr: true,
|
|
3674
|
+
intent: { kind: "columnAlias", column, alias }
|
|
3675
|
+
};
|
|
3676
|
+
}
|
|
3677
|
+
function relationColumn(relation, column, as) {
|
|
3678
|
+
if (!relation) {
|
|
3679
|
+
throw new Error("relationColumn() requires a non-empty relation path");
|
|
3680
|
+
}
|
|
3681
|
+
for (const segment of relation.split(".")) {
|
|
3682
|
+
validateIdentifier(segment, "relation");
|
|
3683
|
+
}
|
|
3684
|
+
if (column !== "*") {
|
|
3685
|
+
validateIdentifier(column, "column");
|
|
3686
|
+
}
|
|
3687
|
+
validateIdentifier(as, "column");
|
|
3688
|
+
return {
|
|
3689
|
+
__expr: true,
|
|
3690
|
+
intent: { kind: "relationColumn", relation, column, as }
|
|
3691
|
+
};
|
|
3692
|
+
}
|
|
3693
|
+
var SQL_RAW_MARKER = /* @__PURE__ */ Symbol.for("dbsp:raw-sql");
|
|
3694
|
+
function sql(sqlFragment) {
|
|
3695
|
+
if (!sqlFragment || sqlFragment.trim() === "") {
|
|
3696
|
+
throw new Error("sql() requires a non-empty SQL fragment");
|
|
3697
|
+
}
|
|
3698
|
+
return { [SQL_RAW_MARKER]: true, sql: sqlFragment };
|
|
3699
|
+
}
|
|
3700
|
+
function isSqlRaw(value) {
|
|
3701
|
+
return typeof value === "object" && value !== null && SQL_RAW_MARKER in value && value[SQL_RAW_MARKER] === true;
|
|
3702
|
+
}
|
|
3458
3703
|
|
|
3459
|
-
// src/dx/
|
|
3460
|
-
var
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
this.cteName = name;
|
|
3468
|
-
this.adapter = adapter;
|
|
3469
|
-
this.schemaName = schemaName;
|
|
3704
|
+
// src/dx/expressions.ts
|
|
3705
|
+
var OPERATOR_PATTERN = /^[a-zA-Z_<>=!@#%^&|~*+\-/.]+$/;
|
|
3706
|
+
var FUNCTION_NAME_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]*)*$/;
|
|
3707
|
+
var TYPE_NAME_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_ ]*(\[\])?$/;
|
|
3708
|
+
function toExpressionIntent(input) {
|
|
3709
|
+
if (input instanceof ExpressionRef) return input.intent;
|
|
3710
|
+
if (typeof input === "object" && input !== null && "__expr" in input && input.__expr === true && "intent" in input) {
|
|
3711
|
+
return input.intent;
|
|
3470
3712
|
}
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
"withCte",
|
|
3482
|
-
`Array length mismatch in CTE unnest columns: lengths are [${lengths.join(", ")}]`
|
|
3483
|
-
);
|
|
3484
|
-
}
|
|
3485
|
-
this.unnestColumns = columns;
|
|
3486
|
-
return this;
|
|
3713
|
+
if (typeof input === "string") {
|
|
3714
|
+
return { kind: "ref", column: input };
|
|
3715
|
+
}
|
|
3716
|
+
return { kind: "param", value: input };
|
|
3717
|
+
}
|
|
3718
|
+
var ExpressionRef = class _ExpressionRef {
|
|
3719
|
+
__expr = true;
|
|
3720
|
+
intent;
|
|
3721
|
+
constructor(intent) {
|
|
3722
|
+
this.intent = intent;
|
|
3487
3723
|
}
|
|
3488
3724
|
/**
|
|
3489
|
-
*
|
|
3490
|
-
*
|
|
3491
|
-
* @param columnName - The name of the index column in the CTE.
|
|
3725
|
+
* Set an alias for this expression in SELECT.
|
|
3726
|
+
* Returns a new ExpressionRef — does not mutate.
|
|
3492
3727
|
*/
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3728
|
+
as(alias) {
|
|
3729
|
+
return new _ExpressionRef({
|
|
3730
|
+
...this.intent,
|
|
3731
|
+
as: alias
|
|
3732
|
+
});
|
|
3733
|
+
}
|
|
3734
|
+
/** WHERE: expr = value */
|
|
3735
|
+
eq(value) {
|
|
3736
|
+
return { kind: "expression", expr: this.intent, operator: "eq", value };
|
|
3737
|
+
}
|
|
3738
|
+
/** WHERE: expr != value */
|
|
3739
|
+
neq(value) {
|
|
3740
|
+
return { kind: "expression", expr: this.intent, operator: "neq", value };
|
|
3741
|
+
}
|
|
3742
|
+
/** WHERE: expr > value */
|
|
3743
|
+
gt(value) {
|
|
3744
|
+
return { kind: "expression", expr: this.intent, operator: "gt", value };
|
|
3745
|
+
}
|
|
3746
|
+
/** WHERE: expr >= value */
|
|
3747
|
+
gte(value) {
|
|
3748
|
+
return { kind: "expression", expr: this.intent, operator: "gte", value };
|
|
3749
|
+
}
|
|
3750
|
+
/** WHERE: expr < value */
|
|
3751
|
+
lt(value) {
|
|
3752
|
+
return { kind: "expression", expr: this.intent, operator: "lt", value };
|
|
3753
|
+
}
|
|
3754
|
+
/** WHERE: expr <= value */
|
|
3755
|
+
lte(value) {
|
|
3756
|
+
return { kind: "expression", expr: this.intent, operator: "lte", value };
|
|
3496
3757
|
}
|
|
3497
3758
|
/**
|
|
3498
|
-
*
|
|
3759
|
+
* Add a FILTER (WHERE ...) clause to this function expression.
|
|
3760
|
+
* Valid on `fn()` expressions (customFn kind), including DISTINCT calls
|
|
3761
|
+
* produced by `fn(aggName, distinct(field))` (still customFn kind).
|
|
3762
|
+
* Returns a new ExpressionRef — does not mutate.
|
|
3499
3763
|
*
|
|
3500
|
-
*
|
|
3764
|
+
* NOTE: the FILTER clause is only emitted when the expression is used in
|
|
3765
|
+
* column position (`.columns([...])`) — the adapter patches it in at that
|
|
3766
|
+
* one SELECT-list branch (compiler.ts). Using `.filter()` inside
|
|
3767
|
+
* `.orderBy()`, `.having()`, `cast()`, or any other nested context compiles
|
|
3768
|
+
* successfully but silently drops the FILTER clause (tracked separately,
|
|
3769
|
+
* not specific to DISTINCT).
|
|
3770
|
+
*
|
|
3771
|
+
* @example fn('array_agg', ref('name')).filter(eq('active', true))
|
|
3772
|
+
* → array_agg("name") FILTER (WHERE "active" = $1) // in .columns()
|
|
3773
|
+
* @example fn('count', distinct('id')).filter(eq('active', true))
|
|
3774
|
+
* → count(DISTINCT id) FILTER (WHERE "active" = $1) // in .columns()
|
|
3775
|
+
* @throws Error if called on any other expression kind
|
|
3501
3776
|
*/
|
|
3502
|
-
|
|
3503
|
-
if (
|
|
3504
|
-
throw new
|
|
3505
|
-
|
|
3506
|
-
"CTE requires a data source \u2014 call .fromUnnest() first"
|
|
3777
|
+
filter(condition) {
|
|
3778
|
+
if (this.intent.kind !== "customFn") {
|
|
3779
|
+
throw new Error(
|
|
3780
|
+
`filter() can only be used on function expressions created with fn(). Got kind: '${this.intent.kind}'`
|
|
3507
3781
|
);
|
|
3508
3782
|
}
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
...this.indexColumnName !== void 0 && {
|
|
3514
|
-
indexColumn: this.indexColumnName
|
|
3515
|
-
}
|
|
3516
|
-
};
|
|
3517
|
-
return new CteQueryBuilder(
|
|
3518
|
-
cteIntent,
|
|
3519
|
-
selectBuilder,
|
|
3520
|
-
this.adapter,
|
|
3521
|
-
this.schemaName
|
|
3522
|
-
);
|
|
3783
|
+
return new _ExpressionRef({
|
|
3784
|
+
...this.intent,
|
|
3785
|
+
filter: condition
|
|
3786
|
+
});
|
|
3523
3787
|
}
|
|
3524
3788
|
};
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
}
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
const queryIntent = this.outerBuilder.buildIntent();
|
|
3541
|
-
return {
|
|
3542
|
-
kind: "cteQuery",
|
|
3543
|
-
ctes: [this.cteIntent],
|
|
3544
|
-
query: queryIntent
|
|
3545
|
-
};
|
|
3789
|
+
function ref2(column) {
|
|
3790
|
+
return new ExpressionRef({
|
|
3791
|
+
kind: "ref",
|
|
3792
|
+
column
|
|
3793
|
+
});
|
|
3794
|
+
}
|
|
3795
|
+
function param(value) {
|
|
3796
|
+
return new ExpressionRef({
|
|
3797
|
+
kind: "param",
|
|
3798
|
+
value
|
|
3799
|
+
});
|
|
3800
|
+
}
|
|
3801
|
+
function cast(expr, typeName) {
|
|
3802
|
+
if (!TYPE_NAME_PATTERN.test(typeName)) {
|
|
3803
|
+
throw new Error(`Invalid type name: ${typeName}`);
|
|
3546
3804
|
}
|
|
3547
|
-
|
|
3548
|
-
|
|
3805
|
+
return new ExpressionRef({
|
|
3806
|
+
kind: "cast",
|
|
3807
|
+
expr: expr.intent,
|
|
3808
|
+
typeName
|
|
3809
|
+
});
|
|
3810
|
+
}
|
|
3811
|
+
function op(operator, left, right) {
|
|
3812
|
+
if (!operator || !OPERATOR_PATTERN.test(operator)) {
|
|
3813
|
+
throw new Error(`Invalid operator: ${operator}`);
|
|
3549
3814
|
}
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
);
|
|
3561
|
-
return {
|
|
3562
|
-
sql: compiled.sql,
|
|
3563
|
-
params: compiled.parameters,
|
|
3564
|
-
intent
|
|
3565
|
-
};
|
|
3815
|
+
return new ExpressionRef({
|
|
3816
|
+
kind: "customOp",
|
|
3817
|
+
operator,
|
|
3818
|
+
left: toExpressionIntent(left),
|
|
3819
|
+
right: toExpressionIntent(right)
|
|
3820
|
+
});
|
|
3821
|
+
}
|
|
3822
|
+
function fn(name, ...args) {
|
|
3823
|
+
if (!name || !FUNCTION_NAME_PATTERN.test(name)) {
|
|
3824
|
+
throw new Error(`Invalid function name: ${name}`);
|
|
3566
3825
|
}
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
const
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3826
|
+
const regularArgs = [];
|
|
3827
|
+
const orderByArgs = [];
|
|
3828
|
+
let distinct2 = false;
|
|
3829
|
+
for (let i = 0; i < args.length; i++) {
|
|
3830
|
+
const arg = args[i];
|
|
3831
|
+
if (isDistinctField2(arg)) {
|
|
3832
|
+
if (i !== 0) {
|
|
3833
|
+
throw new Error(
|
|
3834
|
+
`fn('${name}', ...): distinct() is only supported as the first argument \u2014 SQL DISTINCT applies to the whole argument list, not a single position.`
|
|
3835
|
+
);
|
|
3836
|
+
}
|
|
3837
|
+
const field = arg.field;
|
|
3838
|
+
if (field === "*") {
|
|
3839
|
+
throw new Error(
|
|
3840
|
+
`fn('${name}', distinct('*')): DISTINCT on '*' is not valid SQL \u2014 '*' has no columns to deduplicate on. Provide a specific column, or omit distinct() (e.g. fn('${name}', star())) for a plain star call.`
|
|
3841
|
+
);
|
|
3842
|
+
}
|
|
3843
|
+
distinct2 = true;
|
|
3844
|
+
regularArgs.push(toExpressionIntent(field));
|
|
3845
|
+
} else if (isAggOrderByArg(arg)) {
|
|
3846
|
+
orderByArgs.push(arg);
|
|
3847
|
+
} else {
|
|
3848
|
+
regularArgs.push(toExpressionIntent(arg));
|
|
3849
|
+
}
|
|
3579
3850
|
}
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3851
|
+
const intent = {
|
|
3852
|
+
kind: "customFn",
|
|
3853
|
+
name,
|
|
3854
|
+
args: regularArgs,
|
|
3855
|
+
...distinct2 ? { distinct: true } : {},
|
|
3856
|
+
...orderByArgs.length > 0 ? { aggOrderBy: orderByArgs } : {}
|
|
3857
|
+
};
|
|
3858
|
+
return new ExpressionRef(intent);
|
|
3859
|
+
}
|
|
3860
|
+
function literal(value) {
|
|
3861
|
+
return new ExpressionRef({
|
|
3862
|
+
kind: "literal",
|
|
3863
|
+
value
|
|
3864
|
+
});
|
|
3865
|
+
}
|
|
3866
|
+
function unary(operator, expr) {
|
|
3867
|
+
if (!operator || !OPERATOR_PATTERN.test(operator)) {
|
|
3868
|
+
throw new Error(`Invalid operator: ${operator}`);
|
|
3869
|
+
}
|
|
3870
|
+
return new ExpressionRef({
|
|
3871
|
+
kind: "unary",
|
|
3872
|
+
operator,
|
|
3873
|
+
operand: toExpressionIntent(expr)
|
|
3874
|
+
});
|
|
3875
|
+
}
|
|
3876
|
+
function namedArg(name, value) {
|
|
3877
|
+
if (!name || !FUNCTION_NAME_PATTERN.test(name)) {
|
|
3878
|
+
throw new Error(`namedArg: invalid argument name: ${name}`);
|
|
3879
|
+
}
|
|
3880
|
+
return new ExpressionRef({
|
|
3881
|
+
kind: "namedArg",
|
|
3882
|
+
name,
|
|
3883
|
+
value: toExpressionIntent(value)
|
|
3884
|
+
});
|
|
3885
|
+
}
|
|
3886
|
+
function star() {
|
|
3887
|
+
return new ExpressionRef({ kind: "star" });
|
|
3888
|
+
}
|
|
3889
|
+
function array(...items) {
|
|
3890
|
+
return new ExpressionRef({
|
|
3891
|
+
kind: "array",
|
|
3892
|
+
elements: items.map(toExpressionIntent)
|
|
3893
|
+
});
|
|
3894
|
+
}
|
|
3895
|
+
function isDistinctField2(input) {
|
|
3896
|
+
return !Array.isArray(input) && !(input instanceof ExpressionRef) && isDistinctField(input);
|
|
3897
|
+
}
|
|
3898
|
+
function isAggOrderByArg(input) {
|
|
3899
|
+
return typeof input === "object" && input !== null && !Array.isArray(input) && !(input instanceof ExpressionRef) && "__aggOrderBy" in input && input.__aggOrderBy === true;
|
|
3900
|
+
}
|
|
3901
|
+
function aggOrderBy(field, direction = "asc") {
|
|
3902
|
+
return { __aggOrderBy: true, field, direction };
|
|
3903
|
+
}
|
|
3904
|
+
function arrayAgg(col2, ...rest) {
|
|
3905
|
+
const colExpr = typeof col2 === "string" ? ref2(col2) : col2;
|
|
3906
|
+
return fn("array_agg", colExpr, ...rest);
|
|
3907
|
+
}
|
|
3908
|
+
function stringAgg(col2, separator, ...rest) {
|
|
3909
|
+
const colExpr = typeof col2 === "string" ? ref2(col2) : col2;
|
|
3910
|
+
return fn("string_agg", colExpr, separator, ...rest);
|
|
3911
|
+
}
|
|
3912
|
+
|
|
3913
|
+
// src/dx/case-when-builder.ts
|
|
3914
|
+
function toResultIntent(value) {
|
|
3915
|
+
if (value instanceof ExpressionRef) {
|
|
3916
|
+
return value.intent;
|
|
3917
|
+
}
|
|
3918
|
+
if (value === null || value === void 0) {
|
|
3919
|
+
return { kind: "literal", value: null };
|
|
3920
|
+
}
|
|
3921
|
+
if (typeof value === "string") {
|
|
3922
|
+
return { kind: "ref", column: value };
|
|
3923
|
+
}
|
|
3924
|
+
return { kind: "literal", value };
|
|
3925
|
+
}
|
|
3926
|
+
var CaseBuilder = class _CaseBuilder {
|
|
3927
|
+
branches;
|
|
3928
|
+
constructor(branches) {
|
|
3929
|
+
this.branches = branches;
|
|
3930
|
+
}
|
|
3931
|
+
when(condition, thenValue) {
|
|
3932
|
+
return new _CaseBuilder([
|
|
3933
|
+
...this.branches,
|
|
3934
|
+
{ condition, result: toResultIntent(thenValue) }
|
|
3935
|
+
]);
|
|
3936
|
+
}
|
|
3937
|
+
else(elseValue) {
|
|
3938
|
+
const intent = {
|
|
3939
|
+
kind: "case",
|
|
3940
|
+
when: this.branches,
|
|
3941
|
+
else: toResultIntent(elseValue)
|
|
3942
|
+
};
|
|
3943
|
+
return new ExpressionRef(intent);
|
|
3944
|
+
}
|
|
3945
|
+
as(alias) {
|
|
3946
|
+
return this.toExpr().as(alias);
|
|
3947
|
+
}
|
|
3948
|
+
toExpr() {
|
|
3949
|
+
const intent = {
|
|
3950
|
+
kind: "case",
|
|
3951
|
+
when: this.branches
|
|
3952
|
+
};
|
|
3953
|
+
return new ExpressionRef(intent);
|
|
3954
|
+
}
|
|
3955
|
+
};
|
|
3956
|
+
function caseWhen(condition, thenValue) {
|
|
3957
|
+
return new CaseBuilder([{ condition, result: toResultIntent(thenValue) }]);
|
|
3958
|
+
}
|
|
3959
|
+
|
|
3960
|
+
// src/dx/builder-utils.ts
|
|
3961
|
+
function requireAdapter(adapter, operationName) {
|
|
3962
|
+
if (!adapter) {
|
|
3963
|
+
throw new InvalidOperationError(
|
|
3964
|
+
operationName,
|
|
3965
|
+
"This operation requires an adapter \u2014 pass an adapter when creating the ORM"
|
|
3966
|
+
);
|
|
3967
|
+
}
|
|
3968
|
+
return adapter;
|
|
3969
|
+
}
|
|
3970
|
+
|
|
3971
|
+
// src/dx/cte-builder.ts
|
|
3972
|
+
var CteBuilder = class {
|
|
3973
|
+
cteName;
|
|
3974
|
+
adapter;
|
|
3975
|
+
schemaName;
|
|
3976
|
+
unnestColumns;
|
|
3977
|
+
indexColumnName;
|
|
3978
|
+
constructor(name, adapter, schemaName) {
|
|
3979
|
+
this.cteName = name;
|
|
3980
|
+
this.adapter = adapter;
|
|
3981
|
+
this.schemaName = schemaName;
|
|
3982
|
+
}
|
|
3983
|
+
/**
|
|
3984
|
+
* Provide the column arrays for the unnest() CTE.
|
|
3985
|
+
* All arrays must have the same length.
|
|
3986
|
+
*
|
|
3987
|
+
* @param columns - A record of column name → array of values.
|
|
3988
|
+
*/
|
|
3989
|
+
fromUnnest(columns) {
|
|
3990
|
+
const lengths = Object.values(columns).map((arr) => arr.length);
|
|
3991
|
+
if (lengths.length > 1 && !lengths.every((l) => l === lengths[0])) {
|
|
3992
|
+
throw new InvalidOperationError(
|
|
3993
|
+
"withCte",
|
|
3994
|
+
`Array length mismatch in CTE unnest columns: lengths are [${lengths.join(", ")}]`
|
|
3995
|
+
);
|
|
3996
|
+
}
|
|
3997
|
+
this.unnestColumns = columns;
|
|
3998
|
+
return this;
|
|
3999
|
+
}
|
|
4000
|
+
/**
|
|
4001
|
+
* Add a 0-based ordinality index column (uses WITH ORDINALITY).
|
|
4002
|
+
*
|
|
4003
|
+
* @param columnName - The name of the index column in the CTE.
|
|
4004
|
+
*/
|
|
4005
|
+
withIndex(columnName) {
|
|
4006
|
+
this.indexColumnName = columnName;
|
|
4007
|
+
return this;
|
|
4008
|
+
}
|
|
4009
|
+
/**
|
|
4010
|
+
* Attach an outer query and produce a CteQueryBuilder for execution.
|
|
4011
|
+
*
|
|
4012
|
+
* @param selectBuilder - A QueryBuilder (from orm.select(...))
|
|
4013
|
+
*/
|
|
4014
|
+
query(selectBuilder) {
|
|
4015
|
+
if (!this.unnestColumns) {
|
|
4016
|
+
throw new InvalidOperationError(
|
|
4017
|
+
"withCte",
|
|
4018
|
+
"CTE requires a data source \u2014 call .fromUnnest() first"
|
|
4019
|
+
);
|
|
4020
|
+
}
|
|
4021
|
+
const cteIntent = {
|
|
4022
|
+
kind: "unnestCte",
|
|
4023
|
+
name: this.cteName,
|
|
4024
|
+
columns: this.unnestColumns,
|
|
4025
|
+
...this.indexColumnName !== void 0 && {
|
|
4026
|
+
indexColumn: this.indexColumnName
|
|
4027
|
+
}
|
|
4028
|
+
};
|
|
4029
|
+
return new CteQueryBuilder(
|
|
4030
|
+
cteIntent,
|
|
4031
|
+
selectBuilder,
|
|
4032
|
+
this.adapter,
|
|
4033
|
+
this.schemaName
|
|
4034
|
+
);
|
|
4035
|
+
}
|
|
4036
|
+
};
|
|
4037
|
+
var CteQueryBuilder = class {
|
|
4038
|
+
cteIntent;
|
|
4039
|
+
outerBuilder;
|
|
4040
|
+
adapter;
|
|
4041
|
+
schemaName;
|
|
4042
|
+
constructor(cteIntent, outerBuilder, adapter, schemaName) {
|
|
4043
|
+
this.cteIntent = cteIntent;
|
|
4044
|
+
this.outerBuilder = outerBuilder;
|
|
4045
|
+
this.adapter = adapter;
|
|
4046
|
+
this.schemaName = schemaName;
|
|
4047
|
+
}
|
|
4048
|
+
/**
|
|
4049
|
+
* Build the CteQueryIntent AST.
|
|
4050
|
+
*/
|
|
4051
|
+
buildIntent() {
|
|
4052
|
+
const queryIntent = this.outerBuilder.buildIntent();
|
|
4053
|
+
return {
|
|
4054
|
+
kind: "cteQuery",
|
|
4055
|
+
ctes: [this.cteIntent],
|
|
4056
|
+
query: queryIntent
|
|
4057
|
+
};
|
|
4058
|
+
}
|
|
4059
|
+
requireAdapter() {
|
|
4060
|
+
return requireAdapter(this.adapter, "withCte");
|
|
4061
|
+
}
|
|
4062
|
+
/**
|
|
4063
|
+
* Compile to SQL and return an observability dump.
|
|
4064
|
+
*/
|
|
4065
|
+
dump() {
|
|
4066
|
+
const adapter = this.requireAdapter();
|
|
4067
|
+
const intent = this.buildIntent();
|
|
4068
|
+
const compileOptions = this.schemaName ? { schemaName: this.schemaName } : void 0;
|
|
4069
|
+
const compiled = adapter.compileCteQuery(
|
|
4070
|
+
intent,
|
|
4071
|
+
compileOptions
|
|
4072
|
+
);
|
|
4073
|
+
return {
|
|
4074
|
+
sql: compiled.sql,
|
|
4075
|
+
params: compiled.parameters,
|
|
4076
|
+
intent
|
|
4077
|
+
};
|
|
4078
|
+
}
|
|
4079
|
+
/**
|
|
4080
|
+
* Execute the CTE query and return all results.
|
|
4081
|
+
*/
|
|
4082
|
+
async all() {
|
|
4083
|
+
const adapter = this.requireAdapter();
|
|
4084
|
+
const intent = this.buildIntent();
|
|
4085
|
+
const compileOptions = this.schemaName ? { schemaName: this.schemaName } : void 0;
|
|
4086
|
+
const compiled = adapter.compileCteQuery(
|
|
4087
|
+
intent,
|
|
4088
|
+
compileOptions
|
|
4089
|
+
);
|
|
4090
|
+
return adapter.execute(compiled);
|
|
4091
|
+
}
|
|
4092
|
+
/**
|
|
4093
|
+
* Alias for all().
|
|
4094
|
+
*/
|
|
3583
4095
|
execute() {
|
|
3584
4096
|
return this.all();
|
|
3585
4097
|
}
|
|
@@ -3621,628 +4133,245 @@ var DEFAULT_FEATURE_CHECKERS = Object.freeze([
|
|
|
3621
4133
|
capability: "supportsDDLPartitioning",
|
|
3622
4134
|
feature: "partition",
|
|
3623
4135
|
detectUsage(model) {
|
|
3624
|
-
if (!model.tables) return [];
|
|
3625
|
-
const usages = [];
|
|
3626
|
-
for (const [tableName, table] of model.tables) {
|
|
3627
|
-
if (table.partition) {
|
|
3628
|
-
usages.push({ table: tableName, detail: tableName });
|
|
3629
|
-
}
|
|
3630
|
-
}
|
|
3631
|
-
return usages;
|
|
3632
|
-
}
|
|
3633
|
-
},
|
|
3634
|
-
{
|
|
3635
|
-
capability: "supportsDDLComments",
|
|
3636
|
-
feature: "comment",
|
|
3637
|
-
detectUsage(model) {
|
|
3638
|
-
if (!model.tables) return [];
|
|
3639
|
-
const usages = [];
|
|
3640
|
-
for (const [tableName, table] of model.tables) {
|
|
3641
|
-
if (table.comment) {
|
|
3642
|
-
usages.push({ table: tableName, detail: `${tableName} (table)` });
|
|
3643
|
-
}
|
|
3644
|
-
for (const col2 of table.columns) {
|
|
3645
|
-
if (col2.comment) {
|
|
3646
|
-
usages.push({
|
|
3647
|
-
table: tableName,
|
|
3648
|
-
column: col2.name,
|
|
3649
|
-
detail: `${tableName}.${col2.name} (column)`
|
|
3650
|
-
});
|
|
3651
|
-
}
|
|
3652
|
-
}
|
|
3653
|
-
}
|
|
3654
|
-
return usages;
|
|
3655
|
-
}
|
|
3656
|
-
},
|
|
3657
|
-
{
|
|
3658
|
-
capability: "supportsDDLCheckConstraints",
|
|
3659
|
-
feature: "checkConstraint",
|
|
3660
|
-
detectUsage(model) {
|
|
3661
|
-
if (!model.tables) return [];
|
|
3662
|
-
const usages = [];
|
|
3663
|
-
for (const [tableName, table] of model.tables) {
|
|
3664
|
-
if (table.checkConstraints?.length) {
|
|
3665
|
-
for (const chk of table.checkConstraints) {
|
|
3666
|
-
usages.push({
|
|
3667
|
-
table: tableName,
|
|
3668
|
-
detail: `${tableName}.${chk.name}`
|
|
3669
|
-
});
|
|
3670
|
-
}
|
|
3671
|
-
}
|
|
3672
|
-
}
|
|
3673
|
-
return usages;
|
|
3674
|
-
}
|
|
3675
|
-
},
|
|
3676
|
-
{
|
|
3677
|
-
capability: "supportsDDLOnUpdateFK",
|
|
3678
|
-
feature: "onUpdateFK",
|
|
3679
|
-
detectUsage(model) {
|
|
3680
|
-
if (!model.tables) return [];
|
|
3681
|
-
const usages = [];
|
|
3682
|
-
for (const [tableName, table] of model.tables) {
|
|
3683
|
-
for (const fk of table.foreignKeys) {
|
|
3684
|
-
if (fk.onUpdate && fk.onUpdate !== "NO ACTION") {
|
|
3685
|
-
usages.push({
|
|
3686
|
-
table: tableName,
|
|
3687
|
-
detail: `${tableName} FK \u2192 ${fk.references.table}`
|
|
3688
|
-
});
|
|
3689
|
-
}
|
|
3690
|
-
}
|
|
3691
|
-
}
|
|
3692
|
-
return usages;
|
|
3693
|
-
}
|
|
3694
|
-
},
|
|
3695
|
-
{
|
|
3696
|
-
capability: "supportsDDLDeferredFK",
|
|
3697
|
-
feature: "deferredFK",
|
|
3698
|
-
detectUsage(model) {
|
|
3699
|
-
if (!model.tables) return [];
|
|
3700
|
-
const usages = [];
|
|
3701
|
-
for (const [tableName, table] of model.tables) {
|
|
3702
|
-
for (const fk of table.foreignKeys) {
|
|
3703
|
-
if (fk.deferred) {
|
|
3704
|
-
usages.push({
|
|
3705
|
-
table: tableName,
|
|
3706
|
-
detail: `${tableName} FK \u2192 ${fk.references.table}`
|
|
3707
|
-
});
|
|
3708
|
-
}
|
|
3709
|
-
}
|
|
3710
|
-
}
|
|
3711
|
-
return usages;
|
|
3712
|
-
}
|
|
3713
|
-
},
|
|
3714
|
-
// -----------------------------------------------------------------------
|
|
3715
|
-
// Column-level features
|
|
3716
|
-
// -----------------------------------------------------------------------
|
|
3717
|
-
{
|
|
3718
|
-
capability: "supportsDDLIdentityColumns",
|
|
3719
|
-
feature: "identity",
|
|
3720
|
-
detectUsage(model) {
|
|
3721
|
-
if (!model.tables) return [];
|
|
3722
|
-
const usages = [];
|
|
3723
|
-
for (const [tableName, table] of model.tables) {
|
|
3724
|
-
for (const col2 of table.columns) {
|
|
3725
|
-
if (col2.identity) {
|
|
3726
|
-
usages.push({
|
|
3727
|
-
table: tableName,
|
|
3728
|
-
column: col2.name,
|
|
3729
|
-
detail: `${tableName}.${col2.name}`
|
|
3730
|
-
});
|
|
3731
|
-
}
|
|
3732
|
-
}
|
|
3733
|
-
}
|
|
3734
|
-
return usages;
|
|
3735
|
-
}
|
|
3736
|
-
},
|
|
3737
|
-
{
|
|
3738
|
-
capability: "supportsDDLCollation",
|
|
3739
|
-
feature: "collation",
|
|
3740
|
-
detectUsage(model) {
|
|
3741
|
-
if (!model.tables) return [];
|
|
3742
|
-
const usages = [];
|
|
3743
|
-
for (const [tableName, table] of model.tables) {
|
|
3744
|
-
for (const col2 of table.columns) {
|
|
3745
|
-
if (col2.collation) {
|
|
3746
|
-
usages.push({
|
|
3747
|
-
table: tableName,
|
|
3748
|
-
column: col2.name,
|
|
3749
|
-
detail: `${tableName}.${col2.name}`
|
|
3750
|
-
});
|
|
3751
|
-
}
|
|
3752
|
-
}
|
|
3753
|
-
}
|
|
3754
|
-
return usages;
|
|
3755
|
-
}
|
|
3756
|
-
},
|
|
3757
|
-
// -----------------------------------------------------------------------
|
|
3758
|
-
// Index-level features
|
|
3759
|
-
// -----------------------------------------------------------------------
|
|
3760
|
-
{
|
|
3761
|
-
capability: "supportsDDLIndexMethods",
|
|
3762
|
-
feature: "indexMethod",
|
|
3763
|
-
detectUsage(model) {
|
|
3764
|
-
if (!model.tables) return [];
|
|
3765
|
-
const usages = [];
|
|
3766
|
-
for (const [tableName, table] of model.tables) {
|
|
3767
|
-
for (const idx of table.indexes) {
|
|
3768
|
-
if (idx.method && idx.method !== "btree") {
|
|
3769
|
-
const idxName = idx.name ?? `idx on ${tableName}`;
|
|
3770
|
-
usages.push({ table: tableName, detail: idxName });
|
|
3771
|
-
}
|
|
3772
|
-
}
|
|
3773
|
-
}
|
|
3774
|
-
return usages;
|
|
3775
|
-
}
|
|
3776
|
-
},
|
|
3777
|
-
{
|
|
3778
|
-
capability: "supportsDDLIndexOpclass",
|
|
3779
|
-
feature: "indexOpclass",
|
|
3780
|
-
detectUsage(model) {
|
|
3781
|
-
if (!model.tables) return [];
|
|
3782
|
-
const usages = [];
|
|
3783
|
-
for (const [tableName, table] of model.tables) {
|
|
3784
|
-
for (const idx of table.indexes) {
|
|
3785
|
-
if (idx.opclass && Object.keys(idx.opclass).length > 0) {
|
|
3786
|
-
const idxName = idx.name ?? `idx on ${tableName}`;
|
|
3787
|
-
usages.push({ table: tableName, detail: idxName });
|
|
3788
|
-
}
|
|
3789
|
-
}
|
|
3790
|
-
}
|
|
3791
|
-
return usages;
|
|
3792
|
-
}
|
|
3793
|
-
},
|
|
3794
|
-
{
|
|
3795
|
-
capability: "supportsDDLIndexInclude",
|
|
3796
|
-
feature: "indexInclude",
|
|
3797
|
-
detectUsage(model) {
|
|
3798
|
-
if (!model.tables) return [];
|
|
3799
|
-
const usages = [];
|
|
3800
|
-
for (const [tableName, table] of model.tables) {
|
|
3801
|
-
for (const idx of table.indexes) {
|
|
3802
|
-
if (idx.include?.length) {
|
|
3803
|
-
const idxName = idx.name ?? `idx on ${tableName}`;
|
|
3804
|
-
usages.push({ table: tableName, detail: idxName });
|
|
3805
|
-
}
|
|
3806
|
-
}
|
|
3807
|
-
}
|
|
3808
|
-
return usages;
|
|
3809
|
-
}
|
|
3810
|
-
},
|
|
3811
|
-
{
|
|
3812
|
-
capability: "supportsDDLPartialIndexes",
|
|
3813
|
-
feature: "partialIndex",
|
|
3814
|
-
detectUsage(model) {
|
|
3815
|
-
if (!model.tables) return [];
|
|
3816
|
-
const usages = [];
|
|
3817
|
-
for (const [tableName, table] of model.tables) {
|
|
3818
|
-
for (const idx of table.indexes) {
|
|
3819
|
-
if (idx.where) {
|
|
3820
|
-
const idxName = idx.name ?? `idx on ${tableName}`;
|
|
3821
|
-
usages.push({ table: tableName, detail: idxName });
|
|
3822
|
-
}
|
|
3823
|
-
}
|
|
3824
|
-
}
|
|
3825
|
-
return usages;
|
|
3826
|
-
}
|
|
3827
|
-
},
|
|
3828
|
-
{
|
|
3829
|
-
capability: "supportsDDLExpressionIndexes",
|
|
3830
|
-
feature: "expressionIndex",
|
|
3831
|
-
detectUsage(model) {
|
|
3832
|
-
if (!model.tables) return [];
|
|
3833
|
-
const usages = [];
|
|
3834
|
-
for (const [tableName, table] of model.tables) {
|
|
3835
|
-
for (const idx of table.indexes) {
|
|
3836
|
-
if (idx.expressions?.length) {
|
|
3837
|
-
const idxName = idx.name ?? `idx on ${tableName}`;
|
|
3838
|
-
usages.push({ table: tableName, detail: idxName });
|
|
3839
|
-
}
|
|
3840
|
-
}
|
|
3841
|
-
}
|
|
3842
|
-
return usages;
|
|
3843
|
-
}
|
|
3844
|
-
},
|
|
3845
|
-
// -----------------------------------------------------------------------
|
|
3846
|
-
// Row-Level Security
|
|
3847
|
-
// -----------------------------------------------------------------------
|
|
3848
|
-
{
|
|
3849
|
-
capability: "supportsDDLRowLevelSecurity",
|
|
3850
|
-
feature: "rowLevelSecurity",
|
|
3851
|
-
detectUsage(model) {
|
|
3852
|
-
if (!model.tables) return [];
|
|
3853
|
-
const usages = [];
|
|
3854
|
-
for (const [tableName, table] of model.tables) {
|
|
3855
|
-
if (table.rlsEnabled || table.policies?.length) {
|
|
3856
|
-
usages.push({ table: tableName, detail: tableName });
|
|
3857
|
-
}
|
|
3858
|
-
}
|
|
3859
|
-
return usages;
|
|
3860
|
-
}
|
|
3861
|
-
}
|
|
3862
|
-
]);
|
|
3863
|
-
|
|
3864
|
-
// src/dx/column-utils.ts
|
|
3865
|
-
function getColumnName(field) {
|
|
3866
|
-
if (typeof field === "string") {
|
|
3867
|
-
return field;
|
|
3868
|
-
}
|
|
3869
|
-
const colName = field[COLUMN_META];
|
|
3870
|
-
if (colName === void 0) {
|
|
3871
|
-
throw new Error("Invalid ColumnRef: missing COLUMN_META");
|
|
3872
|
-
}
|
|
3873
|
-
return colName;
|
|
3874
|
-
}
|
|
3875
|
-
|
|
3876
|
-
// src/dx/window-functions.ts
|
|
3877
|
-
var WindowBuilder = class _WindowBuilder {
|
|
3878
|
-
constructor(fnKind, partitions = [], orders = []) {
|
|
3879
|
-
this.fnKind = fnKind;
|
|
3880
|
-
this.partitions = partitions;
|
|
3881
|
-
this.orders = orders;
|
|
3882
|
-
}
|
|
3883
|
-
fnKind;
|
|
3884
|
-
partitions;
|
|
3885
|
-
orders;
|
|
3886
|
-
/**
|
|
3887
|
-
* Create a ranking window builder (row_number, rank, dense_rank)
|
|
3888
|
-
* @internal Use factory functions instead: rowNumber(), rank(), denseRank()
|
|
3889
|
-
*/
|
|
3890
|
-
static ranking(fn2) {
|
|
3891
|
-
return new _WindowBuilder({ type: "ranking", fn: fn2 });
|
|
3892
|
-
}
|
|
3893
|
-
/**
|
|
3894
|
-
* Create an aggregate window builder (sum, avg, count, min, max)
|
|
3895
|
-
* @internal Use factory functions instead: sum(field), avg(field), etc.
|
|
3896
|
-
*/
|
|
3897
|
-
static aggregate(fn2, field) {
|
|
3898
|
-
return new _WindowBuilder({
|
|
3899
|
-
type: "aggregate",
|
|
3900
|
-
fn: fn2,
|
|
3901
|
-
...field !== void 0 ? { field } : {}
|
|
3902
|
-
});
|
|
3903
|
-
}
|
|
3904
|
-
/**
|
|
3905
|
-
* Create an offset window builder (lag, lead)
|
|
3906
|
-
* @internal Use factory functions instead: lag(field), lead(field)
|
|
3907
|
-
*/
|
|
3908
|
-
static offset(fn2, field) {
|
|
3909
|
-
return new _WindowBuilder({ type: "offset", fn: fn2, field });
|
|
3910
|
-
}
|
|
3911
|
-
/**
|
|
3912
|
-
* Add partition field(s) to the OVER clause.
|
|
3913
|
-
* Multiple calls APPEND fields (not replace).
|
|
3914
|
-
* Supports both string field names and ColumnRef (DX-040).
|
|
3915
|
-
*
|
|
3916
|
-
* @example
|
|
3917
|
-
* sum('amount').partitionBy('user_id').partitionBy('category')
|
|
3918
|
-
* // → PARTITION BY "user_id", "category"
|
|
3919
|
-
*
|
|
3920
|
-
* @example DX-040 with ColumnRef
|
|
3921
|
-
* rank().partitionBy(users.dept).orderBy(users.salary, 'desc')
|
|
3922
|
-
*/
|
|
3923
|
-
partitionBy(...fields) {
|
|
3924
|
-
const fieldNames = fields.map(
|
|
3925
|
-
(f) => typeof f === "string" ? f : getColumnName(f)
|
|
3926
|
-
);
|
|
3927
|
-
return new _WindowBuilder(
|
|
3928
|
-
this.fnKind,
|
|
3929
|
-
[...this.partitions, ...fieldNames],
|
|
3930
|
-
this.orders
|
|
3931
|
-
);
|
|
3932
|
-
}
|
|
3933
|
-
/**
|
|
3934
|
-
* Add order field to the OVER clause.
|
|
3935
|
-
* Multiple calls APPEND fields (not replace).
|
|
3936
|
-
* Supports both string field names and ColumnRef (DX-040).
|
|
3937
|
-
*
|
|
3938
|
-
* @param field - Column name or ColumnRef to order by
|
|
3939
|
-
* @param direction - Sort direction: 'asc' (default) or 'desc'
|
|
3940
|
-
*
|
|
3941
|
-
* @example
|
|
3942
|
-
* rowNumber().orderBy('created_at').orderBy('id', 'desc')
|
|
3943
|
-
* // → ORDER BY "created_at" ASC, "id" DESC
|
|
3944
|
-
*
|
|
3945
|
-
* @example DX-040 with ColumnRef
|
|
3946
|
-
* rank().partitionBy(users.dept).orderBy(users.salary, 'desc')
|
|
3947
|
-
*/
|
|
3948
|
-
orderBy(field, direction = "asc") {
|
|
3949
|
-
const fieldName = typeof field === "string" ? field : getColumnName(field);
|
|
3950
|
-
return new _WindowBuilder(this.fnKind, this.partitions, [
|
|
3951
|
-
...this.orders,
|
|
3952
|
-
{ field: fieldName, direction }
|
|
3953
|
-
]);
|
|
3954
|
-
}
|
|
3955
|
-
/**
|
|
3956
|
-
* Finalize the window expression with an alias.
|
|
3957
|
-
* Returns ExpressionSpec for use in columns().
|
|
3958
|
-
*
|
|
3959
|
-
* @param alias - Required alias for the result column
|
|
3960
|
-
*
|
|
3961
|
-
* @example
|
|
3962
|
-
* columns(['id', rowNumber().orderBy('date').as('rn')])
|
|
3963
|
-
*/
|
|
3964
|
-
as(alias) {
|
|
3965
|
-
return {
|
|
3966
|
-
__expr: true,
|
|
3967
|
-
intent: this.toWindowIntent(alias)
|
|
3968
|
-
};
|
|
3969
|
-
}
|
|
3970
|
-
/**
|
|
3971
|
-
* Convert builder state to WindowIntent — produces the correct discriminated branch.
|
|
3972
|
-
* - ranking → RankingWindowIntent (no field)
|
|
3973
|
-
* - aggregate → AggregateWindowIntent (field required; COUNT uses '*' when omitted)
|
|
3974
|
-
* - offset → OffsetWindowIntent (field required)
|
|
3975
|
-
*/
|
|
3976
|
-
toWindowIntent(alias) {
|
|
3977
|
-
const over = {};
|
|
3978
|
-
if (this.partitions.length > 0) {
|
|
3979
|
-
over.partitionBy = this.partitions;
|
|
3980
|
-
}
|
|
3981
|
-
if (this.orders.length > 0) {
|
|
3982
|
-
over.orderBy = this.orders;
|
|
3983
|
-
}
|
|
3984
|
-
if (this.fnKind.type === "ranking") {
|
|
3985
|
-
return {
|
|
3986
|
-
kind: "window",
|
|
3987
|
-
function: this.fnKind.fn,
|
|
3988
|
-
alias,
|
|
3989
|
-
over
|
|
3990
|
-
};
|
|
3991
|
-
}
|
|
3992
|
-
if (this.fnKind.type === "offset") {
|
|
3993
|
-
return {
|
|
3994
|
-
kind: "window",
|
|
3995
|
-
function: this.fnKind.fn,
|
|
3996
|
-
field: this.fnKind.field,
|
|
3997
|
-
alias,
|
|
3998
|
-
over
|
|
3999
|
-
};
|
|
4000
|
-
}
|
|
4001
|
-
const { field } = this.fnKind;
|
|
4002
|
-
return {
|
|
4003
|
-
kind: "window",
|
|
4004
|
-
function: this.fnKind.fn,
|
|
4005
|
-
...field !== void 0 && { field },
|
|
4006
|
-
alias,
|
|
4007
|
-
over
|
|
4008
|
-
};
|
|
4009
|
-
}
|
|
4010
|
-
};
|
|
4011
|
-
function rowNumber() {
|
|
4012
|
-
return WindowBuilder.ranking("row_number");
|
|
4013
|
-
}
|
|
4014
|
-
function rank() {
|
|
4015
|
-
return WindowBuilder.ranking("rank");
|
|
4016
|
-
}
|
|
4017
|
-
function denseRank() {
|
|
4018
|
-
return WindowBuilder.ranking("dense_rank");
|
|
4019
|
-
}
|
|
4020
|
-
function wSum(field) {
|
|
4021
|
-
return WindowBuilder.aggregate("sum", field);
|
|
4022
|
-
}
|
|
4023
|
-
function wAvg(field) {
|
|
4024
|
-
return WindowBuilder.aggregate("avg", field);
|
|
4025
|
-
}
|
|
4026
|
-
function wCount(field) {
|
|
4027
|
-
return WindowBuilder.aggregate("count", field);
|
|
4028
|
-
}
|
|
4029
|
-
function wMin(field) {
|
|
4030
|
-
return WindowBuilder.aggregate("min", field);
|
|
4031
|
-
}
|
|
4032
|
-
function wMax(field) {
|
|
4033
|
-
return WindowBuilder.aggregate("max", field);
|
|
4034
|
-
}
|
|
4035
|
-
function lag(field) {
|
|
4036
|
-
return WindowBuilder.offset("lag", field);
|
|
4037
|
-
}
|
|
4038
|
-
function lead(field) {
|
|
4039
|
-
return WindowBuilder.offset("lead", field);
|
|
4040
|
-
}
|
|
4041
|
-
|
|
4042
|
-
// src/dx/filters.ts
|
|
4043
|
-
function distinct(field) {
|
|
4044
|
-
return { field, distinct: true };
|
|
4045
|
-
}
|
|
4046
|
-
function isDistinctField(value) {
|
|
4047
|
-
return typeof value === "object" && value !== null && "field" in value && "distinct" in value && value.distinct === true;
|
|
4048
|
-
}
|
|
4049
|
-
function createComparisonFilter(operator) {
|
|
4050
|
-
return (field, value) => ({
|
|
4051
|
-
kind: "comparison",
|
|
4052
|
-
field: getColumnName(field),
|
|
4053
|
-
operator,
|
|
4054
|
-
value
|
|
4055
|
-
});
|
|
4056
|
-
}
|
|
4057
|
-
var eq = createComparisonFilter("eq");
|
|
4058
|
-
var neq = createComparisonFilter("neq");
|
|
4059
|
-
var gt = createComparisonFilter("gt");
|
|
4060
|
-
var gte = createComparisonFilter("gte");
|
|
4061
|
-
var lt = createComparisonFilter("lt");
|
|
4062
|
-
var lte = createComparisonFilter("lte");
|
|
4063
|
-
var isDistinctFrom = createComparisonFilter("isDistinctFrom");
|
|
4064
|
-
function like(field, pattern, options) {
|
|
4065
|
-
const caseInsensitive = typeof options === "boolean" ? options : options?.caseInsensitive;
|
|
4066
|
-
const escapeChar = typeof options === "object" ? options.escape : void 0;
|
|
4067
|
-
const intent = {
|
|
4068
|
-
kind: "like",
|
|
4069
|
-
field: getColumnName(field),
|
|
4070
|
-
pattern
|
|
4071
|
-
};
|
|
4072
|
-
const withCi = caseInsensitive !== void 0 ? { ...intent, caseInsensitive } : intent;
|
|
4073
|
-
if (escapeChar !== void 0) {
|
|
4074
|
-
return { ...withCi, escape: escapeChar };
|
|
4075
|
-
}
|
|
4076
|
-
return withCi;
|
|
4077
|
-
}
|
|
4078
|
-
function inArray(field, values) {
|
|
4079
|
-
return { kind: "in", field: getColumnName(field), values };
|
|
4080
|
-
}
|
|
4081
|
-
function inSubquery(field, query) {
|
|
4082
|
-
const expr = "build" in query ? query.build() : query;
|
|
4083
|
-
return {
|
|
4084
|
-
kind: "in",
|
|
4085
|
-
field: getColumnName(field),
|
|
4086
|
-
subquery: expr.toIntent()
|
|
4087
|
-
};
|
|
4088
|
-
}
|
|
4089
|
-
function any(field, values) {
|
|
4090
|
-
return { kind: "any", field: getColumnName(field), values };
|
|
4091
|
-
}
|
|
4092
|
-
function isNull(field) {
|
|
4093
|
-
return { kind: "null", field: getColumnName(field), operator: "isNull" };
|
|
4094
|
-
}
|
|
4095
|
-
function isNotNull(field) {
|
|
4096
|
-
return { kind: "null", field: getColumnName(field), operator: "isNotNull" };
|
|
4097
|
-
}
|
|
4098
|
-
function and(...conditions) {
|
|
4099
|
-
const flatConditions = conditions.length === 1 && Array.isArray(conditions[0]) ? conditions[0] : conditions;
|
|
4100
|
-
return { kind: "and", conditions: flatConditions };
|
|
4101
|
-
}
|
|
4102
|
-
function or(...conditions) {
|
|
4103
|
-
const flatConditions = conditions.length === 1 && Array.isArray(conditions[0]) ? conditions[0] : conditions;
|
|
4104
|
-
return { kind: "or", conditions: flatConditions };
|
|
4105
|
-
}
|
|
4106
|
-
function not(condition) {
|
|
4107
|
-
return { kind: "not", condition };
|
|
4108
|
-
}
|
|
4109
|
-
function exists(relation, options) {
|
|
4110
|
-
const result = { kind: "exists", relation };
|
|
4111
|
-
if (options?.where !== void 0) {
|
|
4112
|
-
result.where = options.where;
|
|
4113
|
-
}
|
|
4114
|
-
if (options?.recursive !== void 0) {
|
|
4115
|
-
result.recursive = options.recursive;
|
|
4116
|
-
}
|
|
4117
|
-
if (options?.include !== void 0) {
|
|
4118
|
-
result.include = options.include;
|
|
4119
|
-
}
|
|
4120
|
-
return result;
|
|
4121
|
-
}
|
|
4122
|
-
function notExists(relation, options) {
|
|
4123
|
-
const result = {
|
|
4124
|
-
kind: "notExists",
|
|
4125
|
-
relation
|
|
4126
|
-
};
|
|
4127
|
-
if (options?.where !== void 0) {
|
|
4128
|
-
result.where = options.where;
|
|
4129
|
-
}
|
|
4130
|
-
if (options?.recursive !== void 0) {
|
|
4131
|
-
result.recursive = options.recursive;
|
|
4132
|
-
}
|
|
4133
|
-
if (options?.include !== void 0) {
|
|
4134
|
-
result.include = options.include;
|
|
4135
|
-
}
|
|
4136
|
-
return result;
|
|
4137
|
-
}
|
|
4138
|
-
function rawExists(sq) {
|
|
4139
|
-
const intent = "buildIntent" in sq ? sq.buildIntent() : sq.build().toIntent();
|
|
4140
|
-
return { kind: "rawExists", subquery: intent };
|
|
4141
|
-
}
|
|
4142
|
-
function rawNotExists(sq) {
|
|
4143
|
-
const intent = "buildIntent" in sq ? sq.buildIntent() : sq.build().toIntent();
|
|
4144
|
-
return { kind: "rawNotExists", subquery: intent };
|
|
4145
|
-
}
|
|
4146
|
-
function getRelationName(rel) {
|
|
4147
|
-
const meta = rel[RELATION_META];
|
|
4148
|
-
if (!meta) {
|
|
4149
|
-
throw new Error("Invalid RelationRef: missing RELATION_META");
|
|
4150
|
-
}
|
|
4151
|
-
return meta.target;
|
|
4152
|
-
}
|
|
4153
|
-
function every(relation, filter) {
|
|
4154
|
-
const relationName = getRelationName(relation);
|
|
4155
|
-
const where = filter(relation);
|
|
4156
|
-
return {
|
|
4157
|
-
kind: "relationFilter",
|
|
4158
|
-
relation: relationName,
|
|
4159
|
-
where,
|
|
4160
|
-
mode: "every"
|
|
4161
|
-
};
|
|
4162
|
-
}
|
|
4163
|
-
function none(relation, filter) {
|
|
4164
|
-
const relationName = getRelationName(relation);
|
|
4165
|
-
const where = filter(relation);
|
|
4166
|
-
return {
|
|
4167
|
-
kind: "relationFilter",
|
|
4168
|
-
relation: relationName,
|
|
4169
|
-
where,
|
|
4170
|
-
mode: "none"
|
|
4171
|
-
};
|
|
4172
|
-
}
|
|
4173
|
-
function some(relation, filter) {
|
|
4174
|
-
const relationName = getRelationName(relation);
|
|
4175
|
-
const where = filter(relation);
|
|
4176
|
-
return {
|
|
4177
|
-
kind: "relationFilter",
|
|
4178
|
-
relation: relationName,
|
|
4179
|
-
where,
|
|
4180
|
-
mode: "some"
|
|
4181
|
-
};
|
|
4182
|
-
}
|
|
4183
|
-
function coalesce(fields, as) {
|
|
4184
|
-
if (fields.length === 0) {
|
|
4185
|
-
throw new Error("coalesce() requires at least one field");
|
|
4186
|
-
}
|
|
4187
|
-
if (!as || as.trim() === "") {
|
|
4188
|
-
throw new Error("coalesce() requires a non-empty alias");
|
|
4189
|
-
}
|
|
4190
|
-
for (const f of fields) {
|
|
4191
|
-
validateIdentifier(f, "column");
|
|
4192
|
-
}
|
|
4193
|
-
validateIdentifier(as, "column");
|
|
4194
|
-
return {
|
|
4195
|
-
__expr: true,
|
|
4196
|
-
intent: { kind: "coalesce", fields, as }
|
|
4197
|
-
};
|
|
4198
|
-
}
|
|
4199
|
-
function raw(sqlFragment, as) {
|
|
4200
|
-
if (typeof as !== "string" || as.trim().length === 0) {
|
|
4201
|
-
throw new InvalidOperationError(
|
|
4202
|
-
"raw",
|
|
4203
|
-
"raw() requires an alias as second argument, e.g. raw('COUNT(*)', 'count')"
|
|
4204
|
-
);
|
|
4205
|
-
}
|
|
4206
|
-
validateIdentifier(as, "column");
|
|
4207
|
-
return {
|
|
4208
|
-
__expr: true,
|
|
4209
|
-
intent: { kind: "raw", sql: sqlFragment, as }
|
|
4210
|
-
};
|
|
4211
|
-
}
|
|
4212
|
-
function col(column, alias) {
|
|
4213
|
-
validateIdentifier(column, "column");
|
|
4214
|
-
validateIdentifier(alias, "column");
|
|
4215
|
-
return {
|
|
4216
|
-
__expr: true,
|
|
4217
|
-
intent: { kind: "columnAlias", column, alias }
|
|
4218
|
-
};
|
|
4219
|
-
}
|
|
4220
|
-
function relationColumn(relation, column, as) {
|
|
4221
|
-
if (!relation) {
|
|
4222
|
-
throw new Error("relationColumn() requires a non-empty relation path");
|
|
4223
|
-
}
|
|
4224
|
-
for (const segment of relation.split(".")) {
|
|
4225
|
-
validateIdentifier(segment, "relation");
|
|
4226
|
-
}
|
|
4227
|
-
if (column !== "*") {
|
|
4228
|
-
validateIdentifier(column, "column");
|
|
4229
|
-
}
|
|
4230
|
-
validateIdentifier(as, "column");
|
|
4231
|
-
return {
|
|
4232
|
-
__expr: true,
|
|
4233
|
-
intent: { kind: "relationColumn", relation, column, as }
|
|
4234
|
-
};
|
|
4235
|
-
}
|
|
4236
|
-
var SQL_RAW_MARKER = /* @__PURE__ */ Symbol.for("dbsp:raw-sql");
|
|
4237
|
-
function sql(sqlFragment) {
|
|
4238
|
-
if (!sqlFragment || sqlFragment.trim() === "") {
|
|
4239
|
-
throw new Error("sql() requires a non-empty SQL fragment");
|
|
4136
|
+
if (!model.tables) return [];
|
|
4137
|
+
const usages = [];
|
|
4138
|
+
for (const [tableName, table] of model.tables) {
|
|
4139
|
+
if (table.partition) {
|
|
4140
|
+
usages.push({ table: tableName, detail: tableName });
|
|
4141
|
+
}
|
|
4142
|
+
}
|
|
4143
|
+
return usages;
|
|
4144
|
+
}
|
|
4145
|
+
},
|
|
4146
|
+
{
|
|
4147
|
+
capability: "supportsDDLComments",
|
|
4148
|
+
feature: "comment",
|
|
4149
|
+
detectUsage(model) {
|
|
4150
|
+
if (!model.tables) return [];
|
|
4151
|
+
const usages = [];
|
|
4152
|
+
for (const [tableName, table] of model.tables) {
|
|
4153
|
+
if (table.comment) {
|
|
4154
|
+
usages.push({ table: tableName, detail: `${tableName} (table)` });
|
|
4155
|
+
}
|
|
4156
|
+
for (const col2 of table.columns) {
|
|
4157
|
+
if (col2.comment) {
|
|
4158
|
+
usages.push({
|
|
4159
|
+
table: tableName,
|
|
4160
|
+
column: col2.name,
|
|
4161
|
+
detail: `${tableName}.${col2.name} (column)`
|
|
4162
|
+
});
|
|
4163
|
+
}
|
|
4164
|
+
}
|
|
4165
|
+
}
|
|
4166
|
+
return usages;
|
|
4167
|
+
}
|
|
4168
|
+
},
|
|
4169
|
+
{
|
|
4170
|
+
capability: "supportsDDLCheckConstraints",
|
|
4171
|
+
feature: "checkConstraint",
|
|
4172
|
+
detectUsage(model) {
|
|
4173
|
+
if (!model.tables) return [];
|
|
4174
|
+
const usages = [];
|
|
4175
|
+
for (const [tableName, table] of model.tables) {
|
|
4176
|
+
if (table.checkConstraints?.length) {
|
|
4177
|
+
for (const chk of table.checkConstraints) {
|
|
4178
|
+
usages.push({
|
|
4179
|
+
table: tableName,
|
|
4180
|
+
detail: `${tableName}.${chk.name}`
|
|
4181
|
+
});
|
|
4182
|
+
}
|
|
4183
|
+
}
|
|
4184
|
+
}
|
|
4185
|
+
return usages;
|
|
4186
|
+
}
|
|
4187
|
+
},
|
|
4188
|
+
{
|
|
4189
|
+
capability: "supportsDDLOnUpdateFK",
|
|
4190
|
+
feature: "onUpdateFK",
|
|
4191
|
+
detectUsage(model) {
|
|
4192
|
+
if (!model.tables) return [];
|
|
4193
|
+
const usages = [];
|
|
4194
|
+
for (const [tableName, table] of model.tables) {
|
|
4195
|
+
for (const fk of table.foreignKeys) {
|
|
4196
|
+
if (fk.onUpdate && fk.onUpdate !== "NO ACTION") {
|
|
4197
|
+
usages.push({
|
|
4198
|
+
table: tableName,
|
|
4199
|
+
detail: `${tableName} FK \u2192 ${fk.references.table}`
|
|
4200
|
+
});
|
|
4201
|
+
}
|
|
4202
|
+
}
|
|
4203
|
+
}
|
|
4204
|
+
return usages;
|
|
4205
|
+
}
|
|
4206
|
+
},
|
|
4207
|
+
{
|
|
4208
|
+
capability: "supportsDDLDeferredFK",
|
|
4209
|
+
feature: "deferredFK",
|
|
4210
|
+
detectUsage(model) {
|
|
4211
|
+
if (!model.tables) return [];
|
|
4212
|
+
const usages = [];
|
|
4213
|
+
for (const [tableName, table] of model.tables) {
|
|
4214
|
+
for (const fk of table.foreignKeys) {
|
|
4215
|
+
if (fk.deferred) {
|
|
4216
|
+
usages.push({
|
|
4217
|
+
table: tableName,
|
|
4218
|
+
detail: `${tableName} FK \u2192 ${fk.references.table}`
|
|
4219
|
+
});
|
|
4220
|
+
}
|
|
4221
|
+
}
|
|
4222
|
+
}
|
|
4223
|
+
return usages;
|
|
4224
|
+
}
|
|
4225
|
+
},
|
|
4226
|
+
// -----------------------------------------------------------------------
|
|
4227
|
+
// Column-level features
|
|
4228
|
+
// -----------------------------------------------------------------------
|
|
4229
|
+
{
|
|
4230
|
+
capability: "supportsDDLIdentityColumns",
|
|
4231
|
+
feature: "identity",
|
|
4232
|
+
detectUsage(model) {
|
|
4233
|
+
if (!model.tables) return [];
|
|
4234
|
+
const usages = [];
|
|
4235
|
+
for (const [tableName, table] of model.tables) {
|
|
4236
|
+
for (const col2 of table.columns) {
|
|
4237
|
+
if (col2.identity) {
|
|
4238
|
+
usages.push({
|
|
4239
|
+
table: tableName,
|
|
4240
|
+
column: col2.name,
|
|
4241
|
+
detail: `${tableName}.${col2.name}`
|
|
4242
|
+
});
|
|
4243
|
+
}
|
|
4244
|
+
}
|
|
4245
|
+
}
|
|
4246
|
+
return usages;
|
|
4247
|
+
}
|
|
4248
|
+
},
|
|
4249
|
+
{
|
|
4250
|
+
capability: "supportsDDLCollation",
|
|
4251
|
+
feature: "collation",
|
|
4252
|
+
detectUsage(model) {
|
|
4253
|
+
if (!model.tables) return [];
|
|
4254
|
+
const usages = [];
|
|
4255
|
+
for (const [tableName, table] of model.tables) {
|
|
4256
|
+
for (const col2 of table.columns) {
|
|
4257
|
+
if (col2.collation) {
|
|
4258
|
+
usages.push({
|
|
4259
|
+
table: tableName,
|
|
4260
|
+
column: col2.name,
|
|
4261
|
+
detail: `${tableName}.${col2.name}`
|
|
4262
|
+
});
|
|
4263
|
+
}
|
|
4264
|
+
}
|
|
4265
|
+
}
|
|
4266
|
+
return usages;
|
|
4267
|
+
}
|
|
4268
|
+
},
|
|
4269
|
+
// -----------------------------------------------------------------------
|
|
4270
|
+
// Index-level features
|
|
4271
|
+
// -----------------------------------------------------------------------
|
|
4272
|
+
{
|
|
4273
|
+
capability: "supportsDDLIndexMethods",
|
|
4274
|
+
feature: "indexMethod",
|
|
4275
|
+
detectUsage(model) {
|
|
4276
|
+
if (!model.tables) return [];
|
|
4277
|
+
const usages = [];
|
|
4278
|
+
for (const [tableName, table] of model.tables) {
|
|
4279
|
+
for (const idx of table.indexes) {
|
|
4280
|
+
if (idx.method && idx.method !== "btree") {
|
|
4281
|
+
const idxName = idx.name ?? `idx on ${tableName}`;
|
|
4282
|
+
usages.push({ table: tableName, detail: idxName });
|
|
4283
|
+
}
|
|
4284
|
+
}
|
|
4285
|
+
}
|
|
4286
|
+
return usages;
|
|
4287
|
+
}
|
|
4288
|
+
},
|
|
4289
|
+
{
|
|
4290
|
+
capability: "supportsDDLIndexOpclass",
|
|
4291
|
+
feature: "indexOpclass",
|
|
4292
|
+
detectUsage(model) {
|
|
4293
|
+
if (!model.tables) return [];
|
|
4294
|
+
const usages = [];
|
|
4295
|
+
for (const [tableName, table] of model.tables) {
|
|
4296
|
+
for (const idx of table.indexes) {
|
|
4297
|
+
if (idx.opclass && Object.keys(idx.opclass).length > 0) {
|
|
4298
|
+
const idxName = idx.name ?? `idx on ${tableName}`;
|
|
4299
|
+
usages.push({ table: tableName, detail: idxName });
|
|
4300
|
+
}
|
|
4301
|
+
}
|
|
4302
|
+
}
|
|
4303
|
+
return usages;
|
|
4304
|
+
}
|
|
4305
|
+
},
|
|
4306
|
+
{
|
|
4307
|
+
capability: "supportsDDLIndexInclude",
|
|
4308
|
+
feature: "indexInclude",
|
|
4309
|
+
detectUsage(model) {
|
|
4310
|
+
if (!model.tables) return [];
|
|
4311
|
+
const usages = [];
|
|
4312
|
+
for (const [tableName, table] of model.tables) {
|
|
4313
|
+
for (const idx of table.indexes) {
|
|
4314
|
+
if (idx.include?.length) {
|
|
4315
|
+
const idxName = idx.name ?? `idx on ${tableName}`;
|
|
4316
|
+
usages.push({ table: tableName, detail: idxName });
|
|
4317
|
+
}
|
|
4318
|
+
}
|
|
4319
|
+
}
|
|
4320
|
+
return usages;
|
|
4321
|
+
}
|
|
4322
|
+
},
|
|
4323
|
+
{
|
|
4324
|
+
capability: "supportsDDLPartialIndexes",
|
|
4325
|
+
feature: "partialIndex",
|
|
4326
|
+
detectUsage(model) {
|
|
4327
|
+
if (!model.tables) return [];
|
|
4328
|
+
const usages = [];
|
|
4329
|
+
for (const [tableName, table] of model.tables) {
|
|
4330
|
+
for (const idx of table.indexes) {
|
|
4331
|
+
if (idx.where) {
|
|
4332
|
+
const idxName = idx.name ?? `idx on ${tableName}`;
|
|
4333
|
+
usages.push({ table: tableName, detail: idxName });
|
|
4334
|
+
}
|
|
4335
|
+
}
|
|
4336
|
+
}
|
|
4337
|
+
return usages;
|
|
4338
|
+
}
|
|
4339
|
+
},
|
|
4340
|
+
{
|
|
4341
|
+
capability: "supportsDDLExpressionIndexes",
|
|
4342
|
+
feature: "expressionIndex",
|
|
4343
|
+
detectUsage(model) {
|
|
4344
|
+
if (!model.tables) return [];
|
|
4345
|
+
const usages = [];
|
|
4346
|
+
for (const [tableName, table] of model.tables) {
|
|
4347
|
+
for (const idx of table.indexes) {
|
|
4348
|
+
if (idx.expressions?.length) {
|
|
4349
|
+
const idxName = idx.name ?? `idx on ${tableName}`;
|
|
4350
|
+
usages.push({ table: tableName, detail: idxName });
|
|
4351
|
+
}
|
|
4352
|
+
}
|
|
4353
|
+
}
|
|
4354
|
+
return usages;
|
|
4355
|
+
}
|
|
4356
|
+
},
|
|
4357
|
+
// -----------------------------------------------------------------------
|
|
4358
|
+
// Row-Level Security
|
|
4359
|
+
// -----------------------------------------------------------------------
|
|
4360
|
+
{
|
|
4361
|
+
capability: "supportsDDLRowLevelSecurity",
|
|
4362
|
+
feature: "rowLevelSecurity",
|
|
4363
|
+
detectUsage(model) {
|
|
4364
|
+
if (!model.tables) return [];
|
|
4365
|
+
const usages = [];
|
|
4366
|
+
for (const [tableName, table] of model.tables) {
|
|
4367
|
+
if (table.rlsEnabled || table.policies?.length) {
|
|
4368
|
+
usages.push({ table: tableName, detail: tableName });
|
|
4369
|
+
}
|
|
4370
|
+
}
|
|
4371
|
+
return usages;
|
|
4372
|
+
}
|
|
4240
4373
|
}
|
|
4241
|
-
|
|
4242
|
-
}
|
|
4243
|
-
function isSqlRaw(value) {
|
|
4244
|
-
return typeof value === "object" && value !== null && SQL_RAW_MARKER in value && value[SQL_RAW_MARKER] === true;
|
|
4245
|
-
}
|
|
4374
|
+
]);
|
|
4246
4375
|
|
|
4247
4376
|
// src/dx/full-text-search.ts
|
|
4248
4377
|
function fullTextSearch({
|
|
@@ -10299,16 +10428,13 @@ function buildIndexAPI(tableName, schemaName, adapter, requireAdapter2) {
|
|
|
10299
10428
|
"indexes.list() requires an adapter."
|
|
10300
10429
|
);
|
|
10301
10430
|
}
|
|
10302
|
-
if (adapter.listIndexes) {
|
|
10303
|
-
|
|
10304
|
-
|
|
10305
|
-
|
|
10306
|
-
|
|
10307
|
-
const rows = await adapter.executeRaw(sql2, []);
|
|
10308
|
-
return rows;
|
|
10431
|
+
if (!adapter.listIndexes) {
|
|
10432
|
+
throw new InvalidOperationError(
|
|
10433
|
+
"indexes.list",
|
|
10434
|
+
"indexes.list() requires an adapter that implements listIndexes()."
|
|
10435
|
+
);
|
|
10309
10436
|
}
|
|
10310
|
-
|
|
10311
|
-
return [];
|
|
10437
|
+
return adapter.listIndexes(tableName, schemaName, options);
|
|
10312
10438
|
},
|
|
10313
10439
|
async exists(name) {
|
|
10314
10440
|
if (!adapter) {
|
|
@@ -10933,6 +11059,14 @@ function rangeContainedBy(fieldOrColumn, valueOrRange, rangeType = "daterange")
|
|
|
10933
11059
|
// src/dx/schema-bridge.ts
|
|
10934
11060
|
import { buildRelationKeyFields, toColumnList as toColumnList3 } from "@dbsp/types";
|
|
10935
11061
|
import * as v from "valibot";
|
|
11062
|
+
function normalizeReferenceSchema2(schemaName) {
|
|
11063
|
+
if (schemaName == null) return void 0;
|
|
11064
|
+
const t = schemaName.trim();
|
|
11065
|
+
return t.length > 0 ? t : void 0;
|
|
11066
|
+
}
|
|
11067
|
+
function hasExternalSchema3(schemaName) {
|
|
11068
|
+
return normalizeReferenceSchema2(schemaName) !== void 0;
|
|
11069
|
+
}
|
|
10936
11070
|
function generatedTypeToColumnType(genType) {
|
|
10937
11071
|
switch (genType) {
|
|
10938
11072
|
case "string":
|
|
@@ -11005,11 +11139,11 @@ function findRelationForeignKey(sourceTable, genRelation, tables) {
|
|
|
11005
11139
|
switch (genRelation.kind) {
|
|
11006
11140
|
case "belongsTo":
|
|
11007
11141
|
return tables.get(sourceTable)?.foreignKeys.find(
|
|
11008
|
-
(fk) => fk.references.table === genRelation.target && columnListsEqual(fk.columns, genRelation.foreignKey)
|
|
11142
|
+
(fk) => !hasExternalSchema3(fk.references.schema) && fk.references.table === genRelation.target && columnListsEqual(fk.columns, genRelation.foreignKey)
|
|
11009
11143
|
);
|
|
11010
11144
|
case "hasMany":
|
|
11011
11145
|
return tables.get(genRelation.target)?.foreignKeys.find(
|
|
11012
|
-
(fk) => fk.references.table === sourceTable && columnListsEqual(fk.columns, genRelation.foreignKey)
|
|
11146
|
+
(fk) => !hasExternalSchema3(fk.references.schema) && fk.references.table === sourceTable && columnListsEqual(fk.columns, genRelation.foreignKey)
|
|
11013
11147
|
);
|
|
11014
11148
|
case "manyToMany":
|
|
11015
11149
|
return void 0;
|
|
@@ -11044,11 +11178,13 @@ function buildTableIRFromDefinition(tableName, genTable, fkAutoIndex) {
|
|
|
11044
11178
|
primaryKeys.push(colName);
|
|
11045
11179
|
}
|
|
11046
11180
|
if (colDef.references) {
|
|
11181
|
+
const externalSchema = normalizeReferenceSchema2(colDef.references.schema);
|
|
11047
11182
|
const fk = {
|
|
11048
11183
|
columns: [colName],
|
|
11049
11184
|
references: {
|
|
11050
11185
|
table: colDef.references.table,
|
|
11051
|
-
columns: [colDef.references.column ?? "id"]
|
|
11186
|
+
columns: [colDef.references.column ?? "id"],
|
|
11187
|
+
...externalSchema !== void 0 ? { schema: externalSchema } : {}
|
|
11052
11188
|
}
|
|
11053
11189
|
};
|
|
11054
11190
|
if (colDef.references.onDelete) {
|
|
@@ -11078,7 +11214,7 @@ function buildTableIRFromDefinition(tableName, genTable, fkAutoIndex) {
|
|
|
11078
11214
|
for (const fk of foreignKeys) {
|
|
11079
11215
|
const fkColumn = fk.columns[0];
|
|
11080
11216
|
const targetColumn = fk.references.columns[0];
|
|
11081
|
-
if (fk.references.table === tableName && fkColumn !== void 0 && targetColumn !== void 0) {
|
|
11217
|
+
if (fk.references.table === tableName && !hasExternalSchema3(fk.references.schema) && fkColumn !== void 0 && targetColumn !== void 0) {
|
|
11082
11218
|
const colDef = tableColumns[fkColumn];
|
|
11083
11219
|
const inferredName = fkColumn.endsWith("Id") ? fkColumn.slice(0, -2) : "parent";
|
|
11084
11220
|
const parentRole = colDef?.references?.parentRole ?? inferredName;
|
|
@@ -11111,11 +11247,13 @@ function buildTableIRFromDefinition(tableName, genTable, fkAutoIndex) {
|
|
|
11111
11247
|
}
|
|
11112
11248
|
if (isGeneratedTableWithConfig(genTable)) {
|
|
11113
11249
|
for (const fk of genTable.foreignKeys ?? []) {
|
|
11250
|
+
const externalSchema = normalizeReferenceSchema2(fk.references.schema);
|
|
11114
11251
|
const foreignKey = {
|
|
11115
11252
|
columns: [...fk.columns],
|
|
11116
11253
|
references: {
|
|
11117
11254
|
table: fk.references.table,
|
|
11118
|
-
columns: [...fk.references.columns]
|
|
11255
|
+
columns: [...fk.references.columns],
|
|
11256
|
+
...externalSchema !== void 0 ? { schema: externalSchema } : {}
|
|
11119
11257
|
}
|
|
11120
11258
|
};
|
|
11121
11259
|
if (fk.onDelete) foreignKey.onDelete = fk.onDelete;
|
|
@@ -11332,12 +11470,25 @@ function safeRecord(valueSchema) {
|
|
|
11332
11470
|
v.record(v.string(), valueSchema)
|
|
11333
11471
|
);
|
|
11334
11472
|
}
|
|
11473
|
+
var OptionalReferenceSchemaNameSchema = v.optional(
|
|
11474
|
+
v.pipe(
|
|
11475
|
+
v.string(),
|
|
11476
|
+
v.transform((schemaName) => normalizeReferenceSchema2(schemaName))
|
|
11477
|
+
)
|
|
11478
|
+
);
|
|
11479
|
+
var ForeignKeyActionValues = [
|
|
11480
|
+
"CASCADE",
|
|
11481
|
+
"SET NULL",
|
|
11482
|
+
"SET DEFAULT",
|
|
11483
|
+
"RESTRICT",
|
|
11484
|
+
"NO ACTION"
|
|
11485
|
+
];
|
|
11486
|
+
var ForeignKeyActionSchema = v.picklist(ForeignKeyActionValues);
|
|
11335
11487
|
var ForeignKeyReferenceSchema = v.object({
|
|
11488
|
+
schema: OptionalReferenceSchemaNameSchema,
|
|
11336
11489
|
table: v.string(),
|
|
11337
11490
|
column: v.optional(v.string()),
|
|
11338
|
-
onDelete: v.optional(
|
|
11339
|
-
v.picklist(["CASCADE", "SET NULL", "RESTRICT", "NO ACTION"])
|
|
11340
|
-
),
|
|
11491
|
+
onDelete: v.optional(ForeignKeyActionSchema),
|
|
11341
11492
|
index: v.optional(v.boolean()),
|
|
11342
11493
|
parentRole: v.optional(v.string()),
|
|
11343
11494
|
childRole: v.optional(v.string())
|
|
@@ -11362,15 +11513,12 @@ var TableDefWithConfigSchema = v.object({
|
|
|
11362
11513
|
v.object({
|
|
11363
11514
|
columns: v.array(v.string()),
|
|
11364
11515
|
references: v.object({
|
|
11516
|
+
schema: OptionalReferenceSchemaNameSchema,
|
|
11365
11517
|
table: v.string(),
|
|
11366
11518
|
columns: v.array(v.string())
|
|
11367
11519
|
}),
|
|
11368
|
-
onDelete: v.optional(
|
|
11369
|
-
|
|
11370
|
-
),
|
|
11371
|
-
onUpdate: v.optional(
|
|
11372
|
-
v.picklist(["CASCADE", "SET NULL", "RESTRICT", "NO ACTION"])
|
|
11373
|
-
)
|
|
11520
|
+
onDelete: v.optional(ForeignKeyActionSchema),
|
|
11521
|
+
onUpdate: v.optional(ForeignKeyActionSchema)
|
|
11374
11522
|
})
|
|
11375
11523
|
)
|
|
11376
11524
|
),
|
|
@@ -11504,8 +11652,10 @@ function convertColumn(col2) {
|
|
|
11504
11652
|
result.default = col2.default;
|
|
11505
11653
|
}
|
|
11506
11654
|
if (col2.references) {
|
|
11655
|
+
const externalSchema = normalizeReferenceSchema2(col2.references.schema);
|
|
11507
11656
|
result.references = {
|
|
11508
11657
|
table: col2.references.table,
|
|
11658
|
+
...externalSchema !== void 0 ? { schema: externalSchema } : {},
|
|
11509
11659
|
...col2.references.column !== void 0 ? { column: col2.references.column } : {},
|
|
11510
11660
|
...col2.references.onDelete ? { onDelete: col2.references.onDelete } : {},
|
|
11511
11661
|
...col2.references.index !== void 0 ? { index: col2.references.index } : {},
|