@smartive/graphql-magic 23.7.0 → 23.8.0-next.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -2
- package/dist/bin/gqm.cjs +260 -34
- package/dist/cjs/index.cjs +262 -34
- package/dist/esm/migrations/generate-functions.js +2 -0
- package/dist/esm/migrations/generate-functions.js.map +1 -1
- package/dist/esm/migrations/generate.d.ts +16 -1
- package/dist/esm/migrations/generate.js +241 -29
- package/dist/esm/migrations/generate.js.map +1 -1
- package/dist/esm/migrations/update-functions.js +2 -0
- package/dist/esm/migrations/update-functions.js.map +1 -1
- package/dist/esm/models/model-definitions.d.ts +27 -2
- package/dist/esm/models/models.d.ts +1 -5
- package/dist/esm/models/models.js +4 -1
- package/dist/esm/models/models.js.map +1 -1
- package/dist/esm/models/utils.d.ts +10 -0
- package/dist/esm/models/utils.js +11 -0
- package/dist/esm/models/utils.js.map +1 -1
- package/docs/docs/2-models.md +18 -4
- package/docs/docs/5-migrations.md +11 -5
- package/package.json +1 -1
- package/src/bin/gqm/parse-knexfile.ts +1 -0
- package/src/bin/gqm/settings.ts +4 -0
- package/src/migrations/generate-functions.ts +2 -0
- package/src/migrations/generate.ts +322 -35
- package/src/migrations/update-functions.ts +2 -0
- package/src/models/model-definitions.ts +20 -1
- package/src/models/models.ts +4 -1
- package/src/models/utils.ts +20 -0
- package/tests/unit/constraints.spec.ts +98 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
|
-
## [23.
|
|
1
|
+
## [23.8.0-next.1](https://github.com/smartive/graphql-magic/compare/v23.7.0...v23.8.0-next.1) (2026-03-04)
|
|
2
2
|
|
|
3
3
|
### Features
|
|
4
4
|
|
|
5
|
-
*
|
|
5
|
+
* Exclude constraints and triggers ([12f5d85](https://github.com/smartive/graphql-magic/commit/12f5d85470bdf1107e023700d5a11012df69d098))
|
|
6
|
+
|
|
7
|
+
### Bug Fixes
|
|
8
|
+
|
|
9
|
+
* constraint normalization for empty migration when schema in sync ([77a8283](https://github.com/smartive/graphql-magic/commit/77a828367abc78c07708556d7d751d780b785a9a))
|
|
10
|
+
* gen ([80bb12d](https://github.com/smartive/graphql-magic/commit/80bb12da6f7eb0aa231fb13c22019995f5811a6c))
|
|
11
|
+
* normalize identifier quoting in constraint comparison ([689136b](https://github.com/smartive/graphql-magic/commit/689136b1ef07874b24986c4596aae37acc92d74b))
|
|
12
|
+
* produce empty migration when schema is in sync ([e8e8e88](https://github.com/smartive/graphql-magic/commit/e8e8e8825a1d4ac833886f993a702c187364ea6f))
|
package/dist/bin/gqm.cjs
CHANGED
|
@@ -389,6 +389,8 @@ var EntityModel = class extends Model {
|
|
|
389
389
|
for (const constraint of this.constraints) {
|
|
390
390
|
if (constraint.kind === "check") {
|
|
391
391
|
validateCheckConstraint(this, constraint);
|
|
392
|
+
} else if (constraint.kind === "exclude") {
|
|
393
|
+
validateExcludeConstraint(this, constraint);
|
|
392
394
|
}
|
|
393
395
|
}
|
|
394
396
|
}
|
|
@@ -644,6 +646,19 @@ var validateCheckConstraint = (model, constraint) => {
|
|
|
644
646
|
}
|
|
645
647
|
}
|
|
646
648
|
};
|
|
649
|
+
var validateExcludeConstraint = (model, constraint) => {
|
|
650
|
+
const validColumnNames = new Set(model.fields.map((f) => getColumnName(f)));
|
|
651
|
+
for (const el of constraint.elements) {
|
|
652
|
+
if ("column" in el) {
|
|
653
|
+
if (!validColumnNames.has(el.column)) {
|
|
654
|
+
const validList = [...validColumnNames].sort().join(", ");
|
|
655
|
+
throw new Error(
|
|
656
|
+
`Exclude constraint "${constraint.name}" references column "${el.column}" which does not exist on model ${model.name}. Valid columns: ${validList}`
|
|
657
|
+
);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
};
|
|
647
662
|
|
|
648
663
|
// src/db/generate.ts
|
|
649
664
|
var import_code_block_writer = __toESM(require("code-block-writer"), 1);
|
|
@@ -792,6 +807,7 @@ var generateFunctionsFromDatabase = async (knex2) => {
|
|
|
792
807
|
JOIN pg_namespace n ON p.pronamespace = n.oid
|
|
793
808
|
WHERE n.nspname = 'public'
|
|
794
809
|
AND NOT EXISTS (SELECT 1 FROM pg_aggregate a WHERE a.aggfnoid = p.oid)
|
|
810
|
+
AND NOT EXISTS (SELECT 1 FROM pg_depend d WHERE d.objid = p.oid AND d.deptype = 'e')
|
|
795
811
|
ORDER BY p.proname, pg_get_function_identity_arguments(p.oid)
|
|
796
812
|
`);
|
|
797
813
|
const aggregateFunctions = await knex2.raw(`
|
|
@@ -806,6 +822,7 @@ var generateFunctionsFromDatabase = async (knex2) => {
|
|
|
806
822
|
JOIN pg_aggregate a ON p.oid = a.aggfnoid
|
|
807
823
|
JOIN pg_namespace n ON p.pronamespace = n.oid
|
|
808
824
|
WHERE n.nspname = 'public'
|
|
825
|
+
AND NOT EXISTS (SELECT 1 FROM pg_depend d WHERE d.objid = p.oid AND d.deptype = 'e')
|
|
809
826
|
ORDER BY p.proname, pg_get_function_identity_arguments(p.oid)
|
|
810
827
|
`);
|
|
811
828
|
const functions = [];
|
|
@@ -910,6 +927,7 @@ var getDatabaseFunctions = async (knex2) => {
|
|
|
910
927
|
JOIN pg_namespace n ON p.pronamespace = n.oid
|
|
911
928
|
WHERE n.nspname = 'public'
|
|
912
929
|
AND NOT EXISTS (SELECT 1 FROM pg_aggregate a WHERE a.aggfnoid = p.oid)
|
|
930
|
+
AND NOT EXISTS (SELECT 1 FROM pg_depend d WHERE d.objid = p.oid AND d.deptype = 'e')
|
|
913
931
|
ORDER BY p.proname, pg_get_function_identity_arguments(p.oid)
|
|
914
932
|
`);
|
|
915
933
|
const aggregateFunctions = await knex2.raw(`
|
|
@@ -925,6 +943,7 @@ var getDatabaseFunctions = async (knex2) => {
|
|
|
925
943
|
JOIN pg_aggregate a ON p.oid = a.aggfnoid
|
|
926
944
|
JOIN pg_namespace n ON p.pronamespace = n.oid
|
|
927
945
|
WHERE n.nspname = 'public'
|
|
946
|
+
AND NOT EXISTS (SELECT 1 FROM pg_depend d WHERE d.objid = p.oid AND d.deptype = 'e')
|
|
928
947
|
ORDER BY p.proname, pg_get_function_identity_arguments(p.oid)
|
|
929
948
|
`);
|
|
930
949
|
const result = [];
|
|
@@ -1068,7 +1087,7 @@ Summary: ${updatedCount} updated, ${skippedCount} skipped`);
|
|
|
1068
1087
|
};
|
|
1069
1088
|
|
|
1070
1089
|
// src/migrations/generate.ts
|
|
1071
|
-
var MigrationGenerator = class {
|
|
1090
|
+
var MigrationGenerator = class _MigrationGenerator {
|
|
1072
1091
|
constructor(knex2, models, parsedFunctions) {
|
|
1073
1092
|
this.models = models;
|
|
1074
1093
|
this.parsedFunctions = parsedFunctions;
|
|
@@ -1084,6 +1103,10 @@ var MigrationGenerator = class {
|
|
|
1084
1103
|
columns = {};
|
|
1085
1104
|
/** table name -> constraint name -> check clause expression */
|
|
1086
1105
|
existingCheckConstraints = {};
|
|
1106
|
+
/** table name -> constraint name -> { normalized, raw } */
|
|
1107
|
+
existingExcludeConstraints = {};
|
|
1108
|
+
/** table name -> constraint name -> { normalized, raw } */
|
|
1109
|
+
existingConstraintTriggers = {};
|
|
1087
1110
|
uuidUsed;
|
|
1088
1111
|
nowUsed;
|
|
1089
1112
|
needsMigration = false;
|
|
@@ -1111,8 +1134,56 @@ var MigrationGenerator = class {
|
|
|
1111
1134
|
}
|
|
1112
1135
|
this.existingCheckConstraints[tableName].set(row.constraint_name, row.check_clause);
|
|
1113
1136
|
}
|
|
1137
|
+
const excludeResult = await schema.knex.raw(
|
|
1138
|
+
`SELECT c.conrelid::regclass::text as table_name, c.conname as constraint_name, pg_get_constraintdef(c.oid) as constraint_def
|
|
1139
|
+
FROM pg_constraint c
|
|
1140
|
+
JOIN pg_namespace n ON c.connamespace = n.oid
|
|
1141
|
+
WHERE n.nspname = 'public' AND c.contype = 'x'`
|
|
1142
|
+
);
|
|
1143
|
+
const excludeRows = "rows" in excludeResult && Array.isArray(excludeResult.rows) ? excludeResult.rows : [];
|
|
1144
|
+
for (const row of excludeRows) {
|
|
1145
|
+
const tableName = row.table_name.split(".").pop()?.replace(/^"|"$/g, "") ?? row.table_name;
|
|
1146
|
+
if (!this.existingExcludeConstraints[tableName]) {
|
|
1147
|
+
this.existingExcludeConstraints[tableName] = /* @__PURE__ */ new Map();
|
|
1148
|
+
}
|
|
1149
|
+
this.existingExcludeConstraints[tableName].set(row.constraint_name, {
|
|
1150
|
+
normalized: this.normalizeExcludeDef(row.constraint_def),
|
|
1151
|
+
raw: row.constraint_def
|
|
1152
|
+
});
|
|
1153
|
+
}
|
|
1154
|
+
const triggerResult = await schema.knex.raw(
|
|
1155
|
+
`SELECT c.conrelid::regclass::text as table_name, c.conname as constraint_name, pg_get_triggerdef(t.oid) as trigger_def
|
|
1156
|
+
FROM pg_constraint c
|
|
1157
|
+
JOIN pg_trigger t ON t.tgconstraint = c.oid
|
|
1158
|
+
JOIN pg_namespace n ON c.connamespace = n.oid
|
|
1159
|
+
WHERE n.nspname = 'public' AND c.contype = 't'`
|
|
1160
|
+
);
|
|
1161
|
+
const triggerRows = "rows" in triggerResult && Array.isArray(triggerResult.rows) ? triggerResult.rows : [];
|
|
1162
|
+
for (const row of triggerRows) {
|
|
1163
|
+
const tableName = row.table_name.split(".").pop()?.replace(/^"|"$/g, "") ?? row.table_name;
|
|
1164
|
+
if (!this.existingConstraintTriggers[tableName]) {
|
|
1165
|
+
this.existingConstraintTriggers[tableName] = /* @__PURE__ */ new Map();
|
|
1166
|
+
}
|
|
1167
|
+
this.existingConstraintTriggers[tableName].set(row.constraint_name, {
|
|
1168
|
+
normalized: this.normalizeTriggerDef(row.trigger_def),
|
|
1169
|
+
raw: row.trigger_def
|
|
1170
|
+
});
|
|
1171
|
+
}
|
|
1114
1172
|
const up = [];
|
|
1115
1173
|
const down = [];
|
|
1174
|
+
const wantsBtreeGist = models.entities.some(
|
|
1175
|
+
(model) => model.constraints?.some((c) => c.kind === "exclude" && c.elements.some((el) => "column" in el && el.operator === "="))
|
|
1176
|
+
);
|
|
1177
|
+
if (wantsBtreeGist) {
|
|
1178
|
+
const extResult = await schema.knex("pg_extension").where("extname", "btree_gist").select("oid").first();
|
|
1179
|
+
const btreeGistInstalled = !!extResult;
|
|
1180
|
+
if (!btreeGistInstalled) {
|
|
1181
|
+
up.unshift(() => {
|
|
1182
|
+
this.writer.writeLine(`await knex.raw('CREATE EXTENSION IF NOT EXISTS btree_gist');`);
|
|
1183
|
+
this.writer.blankLine();
|
|
1184
|
+
});
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1116
1187
|
this.createEnums(
|
|
1117
1188
|
this.models.enums.filter((enm2) => !enums.includes((0, import_lowerFirst.default)(enm2.name))),
|
|
1118
1189
|
up,
|
|
@@ -1191,10 +1262,22 @@ var MigrationGenerator = class {
|
|
|
1191
1262
|
if (entry.kind === "check") {
|
|
1192
1263
|
validateCheckConstraint(model, entry);
|
|
1193
1264
|
const table = model.name;
|
|
1194
|
-
const constraintName = this.
|
|
1195
|
-
const expression = entry.expression;
|
|
1265
|
+
const constraintName = this.getConstraintName(model, entry, i);
|
|
1196
1266
|
up.push(() => {
|
|
1197
|
-
this.addCheckConstraint(table, constraintName, expression);
|
|
1267
|
+
this.addCheckConstraint(table, constraintName, entry.expression, entry.deferrable);
|
|
1268
|
+
});
|
|
1269
|
+
} else if (entry.kind === "exclude") {
|
|
1270
|
+
validateExcludeConstraint(model, entry);
|
|
1271
|
+
const table = model.name;
|
|
1272
|
+
const constraintName = this.getConstraintName(model, entry, i);
|
|
1273
|
+
up.push(() => {
|
|
1274
|
+
this.addExcludeConstraint(table, constraintName, entry);
|
|
1275
|
+
});
|
|
1276
|
+
} else if (entry.kind === "constraint_trigger") {
|
|
1277
|
+
const table = model.name;
|
|
1278
|
+
const constraintName = this.getConstraintName(model, entry, i);
|
|
1279
|
+
up.push(() => {
|
|
1280
|
+
this.addConstraintTrigger(table, constraintName, entry);
|
|
1198
1281
|
});
|
|
1199
1282
|
}
|
|
1200
1283
|
}
|
|
@@ -1234,36 +1317,88 @@ var MigrationGenerator = class {
|
|
|
1234
1317
|
);
|
|
1235
1318
|
this.updateFields(model, existingFields, up, down);
|
|
1236
1319
|
if (model.constraints?.length) {
|
|
1320
|
+
const existingExcludeMap = this.existingExcludeConstraints[model.name];
|
|
1321
|
+
const existingTriggerMap = this.existingConstraintTriggers[model.name];
|
|
1237
1322
|
for (let i = 0; i < model.constraints.length; i++) {
|
|
1238
1323
|
const entry = model.constraints[i];
|
|
1239
|
-
if (entry.kind !== "check") {
|
|
1240
|
-
continue;
|
|
1241
|
-
}
|
|
1242
|
-
validateCheckConstraint(model, entry);
|
|
1243
1324
|
const table = model.name;
|
|
1244
|
-
const constraintName = this.
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1325
|
+
const constraintName = this.getConstraintName(model, entry, i);
|
|
1326
|
+
if (entry.kind === "check") {
|
|
1327
|
+
validateCheckConstraint(model, entry);
|
|
1328
|
+
const existingConstraint = this.findExistingConstraint(table, entry, constraintName);
|
|
1329
|
+
if (!existingConstraint) {
|
|
1330
|
+
up.push(() => {
|
|
1331
|
+
this.addCheckConstraint(table, constraintName, entry.expression, entry.deferrable);
|
|
1332
|
+
});
|
|
1333
|
+
down.push(() => {
|
|
1334
|
+
this.dropCheckConstraint(table, constraintName);
|
|
1335
|
+
});
|
|
1336
|
+
} else if (!await this.equalExpressions(
|
|
1337
|
+
table,
|
|
1338
|
+
existingConstraint.constraintName,
|
|
1339
|
+
existingConstraint.expression,
|
|
1340
|
+
entry.expression
|
|
1341
|
+
)) {
|
|
1342
|
+
up.push(() => {
|
|
1343
|
+
this.dropCheckConstraint(table, existingConstraint.constraintName);
|
|
1344
|
+
this.addCheckConstraint(table, constraintName, entry.expression, entry.deferrable);
|
|
1345
|
+
});
|
|
1346
|
+
down.push(() => {
|
|
1347
|
+
this.dropCheckConstraint(table, constraintName);
|
|
1348
|
+
this.addCheckConstraint(
|
|
1349
|
+
table,
|
|
1350
|
+
existingConstraint.constraintName,
|
|
1351
|
+
existingConstraint.expression
|
|
1352
|
+
);
|
|
1353
|
+
});
|
|
1354
|
+
}
|
|
1355
|
+
} else if (entry.kind === "exclude") {
|
|
1356
|
+
validateExcludeConstraint(model, entry);
|
|
1357
|
+
const newDef = this.normalizeExcludeDef(this.buildExcludeDef(entry));
|
|
1358
|
+
const existing = existingExcludeMap?.get(constraintName);
|
|
1359
|
+
if (existing === void 0) {
|
|
1360
|
+
up.push(() => {
|
|
1361
|
+
this.addExcludeConstraint(table, constraintName, entry);
|
|
1362
|
+
});
|
|
1363
|
+
down.push(() => {
|
|
1364
|
+
this.dropExcludeConstraint(table, constraintName);
|
|
1365
|
+
});
|
|
1366
|
+
} else if (existing.normalized !== newDef) {
|
|
1367
|
+
up.push(() => {
|
|
1368
|
+
this.dropExcludeConstraint(table, constraintName);
|
|
1369
|
+
this.addExcludeConstraint(table, constraintName, entry);
|
|
1370
|
+
});
|
|
1371
|
+
down.push(() => {
|
|
1372
|
+
this.dropExcludeConstraint(table, constraintName);
|
|
1373
|
+
const escaped = this.escapeExpressionForRaw(existing.raw);
|
|
1374
|
+
this.writer.writeLine(
|
|
1375
|
+
`await knex.raw(\`ALTER TABLE "${table}" ADD CONSTRAINT "${constraintName}" ${escaped}\`);`
|
|
1376
|
+
);
|
|
1377
|
+
this.writer.blankLine();
|
|
1378
|
+
});
|
|
1379
|
+
}
|
|
1380
|
+
} else if (entry.kind === "constraint_trigger") {
|
|
1381
|
+
const newDef = this.normalizeTriggerDef(this.buildConstraintTriggerDef(table, constraintName, entry));
|
|
1382
|
+
const existing = existingTriggerMap?.get(constraintName);
|
|
1383
|
+
if (existing === void 0) {
|
|
1384
|
+
up.push(() => {
|
|
1385
|
+
this.addConstraintTrigger(table, constraintName, entry);
|
|
1386
|
+
});
|
|
1387
|
+
down.push(() => {
|
|
1388
|
+
this.dropConstraintTrigger(table, constraintName);
|
|
1389
|
+
});
|
|
1390
|
+
} else if (existing.normalized !== newDef) {
|
|
1391
|
+
up.push(() => {
|
|
1392
|
+
this.dropConstraintTrigger(table, constraintName);
|
|
1393
|
+
this.addConstraintTrigger(table, constraintName, entry);
|
|
1394
|
+
});
|
|
1395
|
+
down.push(() => {
|
|
1396
|
+
this.dropConstraintTrigger(table, constraintName);
|
|
1397
|
+
const escaped = existing.raw.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$/g, "\\$");
|
|
1398
|
+
this.writer.writeLine(`await knex.raw(\`${escaped}\`);`);
|
|
1399
|
+
this.writer.blankLine();
|
|
1400
|
+
});
|
|
1401
|
+
}
|
|
1267
1402
|
}
|
|
1268
1403
|
}
|
|
1269
1404
|
}
|
|
@@ -1638,9 +1773,62 @@ var MigrationGenerator = class {
|
|
|
1638
1773
|
renameColumn(from, to) {
|
|
1639
1774
|
this.writer.writeLine(`table.renameColumn('${from}', '${to}');`);
|
|
1640
1775
|
}
|
|
1641
|
-
|
|
1776
|
+
getConstraintName(model, entry, index) {
|
|
1642
1777
|
return `${model.name}_${entry.name}_${entry.kind}_${index}`;
|
|
1643
1778
|
}
|
|
1779
|
+
static SQL_KEYWORDS = /* @__PURE__ */ new Set([
|
|
1780
|
+
"and",
|
|
1781
|
+
"or",
|
|
1782
|
+
"not",
|
|
1783
|
+
"in",
|
|
1784
|
+
"is",
|
|
1785
|
+
"null",
|
|
1786
|
+
"true",
|
|
1787
|
+
"false",
|
|
1788
|
+
"between",
|
|
1789
|
+
"like",
|
|
1790
|
+
"exists",
|
|
1791
|
+
"all",
|
|
1792
|
+
"any",
|
|
1793
|
+
"asc",
|
|
1794
|
+
"desc",
|
|
1795
|
+
"with",
|
|
1796
|
+
"using",
|
|
1797
|
+
"as",
|
|
1798
|
+
"on",
|
|
1799
|
+
"infinity",
|
|
1800
|
+
"extract",
|
|
1801
|
+
"current_date",
|
|
1802
|
+
"current_timestamp"
|
|
1803
|
+
]);
|
|
1804
|
+
static LITERAL_PLACEHOLDER = "\uE000";
|
|
1805
|
+
normalizeSqlIdentifiers(s) {
|
|
1806
|
+
const literals = [];
|
|
1807
|
+
let result = s.replace(/'([^']|'')*'/g, (lit) => {
|
|
1808
|
+
literals.push(lit);
|
|
1809
|
+
return `${_MigrationGenerator.LITERAL_PLACEHOLDER}${literals.length - 1}${_MigrationGenerator.LITERAL_PLACEHOLDER}`;
|
|
1810
|
+
});
|
|
1811
|
+
result = result.replace(/"([^"]*)"/g, (_, ident) => `"${ident.toLowerCase()}"`);
|
|
1812
|
+
result = result.replace(
|
|
1813
|
+
/\b([a-zA-Z_][a-zA-Z0-9_]*)\b(?!\s*\()/g,
|
|
1814
|
+
(match) => _MigrationGenerator.SQL_KEYWORDS.has(match.toLowerCase()) ? match : `"${match.toLowerCase()}"`
|
|
1815
|
+
);
|
|
1816
|
+
for (let i = 0; i < literals.length; i++) {
|
|
1817
|
+
result = result.replace(
|
|
1818
|
+
new RegExp(`${_MigrationGenerator.LITERAL_PLACEHOLDER}${i}${_MigrationGenerator.LITERAL_PLACEHOLDER}`, "g"),
|
|
1819
|
+
literals[i]
|
|
1820
|
+
);
|
|
1821
|
+
}
|
|
1822
|
+
return result;
|
|
1823
|
+
}
|
|
1824
|
+
normalizeExcludeDef(def) {
|
|
1825
|
+
const s = def.replace(/\s+/g, " ").replace(/\s*\(\s*/g, "(").replace(/\s*\)\s*/g, ")").trim();
|
|
1826
|
+
return this.normalizeSqlIdentifiers(s);
|
|
1827
|
+
}
|
|
1828
|
+
normalizeTriggerDef(def) {
|
|
1829
|
+
const s = def.replace(/\s+/g, " ").replace(/\s*\(\s*/g, "(").replace(/\s*\)\s*/g, ")").replace(/\bON\s+[a-zA-Z_][a-zA-Z0-9_]*\./gi, "ON ").trim();
|
|
1830
|
+
return this.normalizeSqlIdentifiers(s);
|
|
1831
|
+
}
|
|
1644
1832
|
normalizeCheckExpression(expr) {
|
|
1645
1833
|
let normalized = expr.replace(/\s+/g, " ").trim();
|
|
1646
1834
|
while (this.isWrappedByOuterParentheses(normalized)) {
|
|
@@ -1776,10 +1964,11 @@ var MigrationGenerator = class {
|
|
|
1776
1964
|
escapeExpressionForRaw(expr) {
|
|
1777
1965
|
return expr.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$/g, "\\$");
|
|
1778
1966
|
}
|
|
1779
|
-
addCheckConstraint(table, constraintName, expression) {
|
|
1967
|
+
addCheckConstraint(table, constraintName, expression, deferrable) {
|
|
1780
1968
|
const escaped = this.escapeExpressionForRaw(expression);
|
|
1969
|
+
const deferrableClause = deferrable ? ` DEFERRABLE ${deferrable}` : "";
|
|
1781
1970
|
this.writer.writeLine(
|
|
1782
|
-
`await knex.raw(\`ALTER TABLE "${table}" ADD CONSTRAINT "${constraintName}" CHECK (${escaped})\`);`
|
|
1971
|
+
`await knex.raw(\`ALTER TABLE "${table}" ADD CONSTRAINT "${constraintName}" CHECK (${escaped})${deferrableClause}\`);`
|
|
1783
1972
|
);
|
|
1784
1973
|
this.writer.blankLine();
|
|
1785
1974
|
}
|
|
@@ -1787,6 +1976,43 @@ var MigrationGenerator = class {
|
|
|
1787
1976
|
this.writer.writeLine(`await knex.raw('ALTER TABLE "${table}" DROP CONSTRAINT "${constraintName}"');`);
|
|
1788
1977
|
this.writer.blankLine();
|
|
1789
1978
|
}
|
|
1979
|
+
buildExcludeDef(entry) {
|
|
1980
|
+
const elementsStr = entry.elements.map((el) => "column" in el ? `"${el.column}" WITH ${el.operator}` : `${el.expression} WITH ${el.operator}`).join(", ");
|
|
1981
|
+
const whereClause = entry.where ? ` WHERE (${entry.where})` : "";
|
|
1982
|
+
const deferrableClause = entry.deferrable ? ` DEFERRABLE ${entry.deferrable}` : "";
|
|
1983
|
+
return `EXCLUDE USING ${entry.using} (${elementsStr})${whereClause}${deferrableClause}`;
|
|
1984
|
+
}
|
|
1985
|
+
addExcludeConstraint(table, constraintName, entry) {
|
|
1986
|
+
const def = this.buildExcludeDef(entry);
|
|
1987
|
+
const escaped = this.escapeExpressionForRaw(def);
|
|
1988
|
+
this.writer.writeLine(`await knex.raw(\`ALTER TABLE "${table}" ADD CONSTRAINT "${constraintName}" ${escaped}\`);`);
|
|
1989
|
+
this.writer.blankLine();
|
|
1990
|
+
}
|
|
1991
|
+
dropExcludeConstraint(table, constraintName) {
|
|
1992
|
+
this.writer.writeLine(`await knex.raw('ALTER TABLE "${table}" DROP CONSTRAINT "${constraintName}"');`);
|
|
1993
|
+
this.writer.blankLine();
|
|
1994
|
+
}
|
|
1995
|
+
buildConstraintTriggerDef(table, constraintName, entry) {
|
|
1996
|
+
const eventsStr = entry.events.join(" OR ");
|
|
1997
|
+
const deferrableClause = entry.deferrable ? ` DEFERRABLE ${entry.deferrable}` : "";
|
|
1998
|
+
const argsStr = entry.function.args?.length ? entry.function.args.map((a) => `"${a}"`).join(", ") : "";
|
|
1999
|
+
const executeClause = argsStr ? `EXECUTE FUNCTION ${entry.function.name}(${argsStr})` : `EXECUTE FUNCTION ${entry.function.name}()`;
|
|
2000
|
+
return `CREATE CONSTRAINT TRIGGER "${constraintName}" ${entry.when} ${eventsStr} ON "${table}"${deferrableClause} FOR EACH ${entry.forEach} ${executeClause}`;
|
|
2001
|
+
}
|
|
2002
|
+
addConstraintTrigger(table, constraintName, entry) {
|
|
2003
|
+
const eventsStr = entry.events.join(" OR ");
|
|
2004
|
+
const deferrableClause = entry.deferrable ? ` DEFERRABLE ${entry.deferrable}` : "";
|
|
2005
|
+
const argsStr = entry.function.args?.length ? entry.function.args.map((a) => `"${a}"`).join(", ") : "";
|
|
2006
|
+
const executeClause = argsStr ? `EXECUTE FUNCTION ${entry.function.name}(${argsStr})` : `EXECUTE FUNCTION ${entry.function.name}()`;
|
|
2007
|
+
this.writer.writeLine(
|
|
2008
|
+
`await knex.raw(\`CREATE CONSTRAINT TRIGGER "${constraintName}" ${entry.when} ${eventsStr} ON "${table}"${deferrableClause} FOR EACH ${entry.forEach} ${executeClause}\`);`
|
|
2009
|
+
);
|
|
2010
|
+
this.writer.blankLine();
|
|
2011
|
+
}
|
|
2012
|
+
dropConstraintTrigger(table, constraintName) {
|
|
2013
|
+
this.writer.writeLine(`await knex.raw('ALTER TABLE "${table}" DROP CONSTRAINT "${constraintName}"');`);
|
|
2014
|
+
this.writer.blankLine();
|
|
2015
|
+
}
|
|
1790
2016
|
value(value2) {
|
|
1791
2017
|
if (typeof value2 === "string") {
|
|
1792
2018
|
return `'${value2}'`;
|