@smartive/graphql-magic 23.7.0 → 23.8.0-next.2

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 CHANGED
@@ -1,5 +1,5 @@
1
- ## [23.7.0](https://github.com/smartive/graphql-magic/compare/v23.6.1...v23.7.0) (2026-03-04)
1
+ ## [23.8.0-next.2](https://github.com/smartive/graphql-magic/compare/v23.8.0-next.1...v23.8.0-next.2) (2026-03-04)
2
2
 
3
- ### Features
3
+ ### Bug Fixes
4
4
 
5
- * Enhance check constraint handling in migration generator ([#430](https://github.com/smartive/graphql-magic/issues/430)) ([b23a885](https://github.com/smartive/graphql-magic/commit/b23a8858ec46182b90cdb220e444c782f46201fc))
5
+ * normalize EXCLUDE defs so identical constraints produce empty migration ([0f42eb8](https://github.com/smartive/graphql-magic/commit/0f42eb8b9df1d1ce40a1905b6f52332165540943))
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.getCheckConstraintName(model, entry, i);
1195
- const expression = entry.expression;
1265
+ const constraintName = this.getConstraintName(model, entry, i);
1266
+ up.push(() => {
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);
1196
1279
  up.push(() => {
1197
- this.addCheckConstraint(table, constraintName, expression);
1280
+ this.addConstraintTrigger(table, constraintName, entry);
1198
1281
  });
1199
1282
  }
1200
1283
  }
@@ -1234,36 +1317,84 @@ 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.getCheckConstraintName(model, entry, i);
1245
- const existingConstraint = this.findExistingConstraint(table, entry, constraintName);
1246
- if (!existingConstraint) {
1247
- up.push(() => {
1248
- this.addCheckConstraint(table, constraintName, entry.expression);
1249
- });
1250
- down.push(() => {
1251
- this.dropCheckConstraint(table, constraintName);
1252
- });
1253
- } else if (!await this.equalExpressions(
1254
- table,
1255
- existingConstraint.constraintName,
1256
- existingConstraint.expression,
1257
- entry.expression
1258
- )) {
1259
- up.push(() => {
1260
- this.dropCheckConstraint(table, existingConstraint.constraintName);
1261
- this.addCheckConstraint(table, constraintName, entry.expression);
1262
- });
1263
- down.push(() => {
1264
- this.dropCheckConstraint(table, constraintName);
1265
- this.addCheckConstraint(table, existingConstraint.constraintName, existingConstraint.expression);
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(table, existingConstraint.constraintName, existingConstraint.expression);
1349
+ });
1350
+ }
1351
+ } else if (entry.kind === "exclude") {
1352
+ validateExcludeConstraint(model, entry);
1353
+ const newDef = this.normalizeExcludeDef(this.buildExcludeDef(entry));
1354
+ const existing = existingExcludeMap?.get(constraintName);
1355
+ if (existing === void 0) {
1356
+ up.push(() => {
1357
+ this.addExcludeConstraint(table, constraintName, entry);
1358
+ });
1359
+ down.push(() => {
1360
+ this.dropExcludeConstraint(table, constraintName);
1361
+ });
1362
+ } else if (existing.normalized !== newDef) {
1363
+ up.push(() => {
1364
+ this.dropExcludeConstraint(table, constraintName);
1365
+ this.addExcludeConstraint(table, constraintName, entry);
1366
+ });
1367
+ down.push(() => {
1368
+ this.dropExcludeConstraint(table, constraintName);
1369
+ const escaped = this.escapeExpressionForRaw(existing.raw);
1370
+ this.writer.writeLine(
1371
+ `await knex.raw(\`ALTER TABLE "${table}" ADD CONSTRAINT "${constraintName}" ${escaped}\`);`
1372
+ );
1373
+ this.writer.blankLine();
1374
+ });
1375
+ }
1376
+ } else if (entry.kind === "constraint_trigger") {
1377
+ const newDef = this.normalizeTriggerDef(this.buildConstraintTriggerDef(table, constraintName, entry));
1378
+ const existing = existingTriggerMap?.get(constraintName);
1379
+ if (existing === void 0) {
1380
+ up.push(() => {
1381
+ this.addConstraintTrigger(table, constraintName, entry);
1382
+ });
1383
+ down.push(() => {
1384
+ this.dropConstraintTrigger(table, constraintName);
1385
+ });
1386
+ } else if (existing.normalized !== newDef) {
1387
+ up.push(() => {
1388
+ this.dropConstraintTrigger(table, constraintName);
1389
+ this.addConstraintTrigger(table, constraintName, entry);
1390
+ });
1391
+ down.push(() => {
1392
+ this.dropConstraintTrigger(table, constraintName);
1393
+ const escaped = existing.raw.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$/g, "\\$");
1394
+ this.writer.writeLine(`await knex.raw(\`${escaped}\`);`);
1395
+ this.writer.blankLine();
1396
+ });
1397
+ }
1267
1398
  }
1268
1399
  }
1269
1400
  }
@@ -1638,9 +1769,113 @@ var MigrationGenerator = class {
1638
1769
  renameColumn(from, to) {
1639
1770
  this.writer.writeLine(`table.renameColumn('${from}', '${to}');`);
1640
1771
  }
1641
- getCheckConstraintName(model, entry, index) {
1772
+ getConstraintName(model, entry, index) {
1642
1773
  return `${model.name}_${entry.name}_${entry.kind}_${index}`;
1643
1774
  }
1775
+ static SQL_KEYWORDS = /* @__PURE__ */ new Set([
1776
+ "and",
1777
+ "or",
1778
+ "not",
1779
+ "in",
1780
+ "is",
1781
+ "null",
1782
+ "true",
1783
+ "false",
1784
+ "between",
1785
+ "like",
1786
+ "exists",
1787
+ "all",
1788
+ "any",
1789
+ "asc",
1790
+ "desc",
1791
+ "with",
1792
+ "using",
1793
+ "as",
1794
+ "on",
1795
+ "infinity",
1796
+ "extract",
1797
+ "current_date",
1798
+ "current_timestamp"
1799
+ ]);
1800
+ static LITERAL_PLACEHOLDER = "\uE000";
1801
+ static IDENT_PLACEHOLDER = "\uE001";
1802
+ normalizeSqlIdentifiers(s) {
1803
+ const literals = [];
1804
+ let result = s.replace(/'([^']|'')*'/g, (lit) => {
1805
+ literals.push(lit);
1806
+ return `${_MigrationGenerator.LITERAL_PLACEHOLDER}${literals.length - 1}${_MigrationGenerator.LITERAL_PLACEHOLDER}`;
1807
+ });
1808
+ const quotedIdents = [];
1809
+ result = result.replace(/"([^"]*)"/g, (_, ident) => {
1810
+ quotedIdents.push(`"${ident.toLowerCase()}"`);
1811
+ return `${_MigrationGenerator.IDENT_PLACEHOLDER}${quotedIdents.length - 1}${_MigrationGenerator.IDENT_PLACEHOLDER}`;
1812
+ });
1813
+ result = result.replace(
1814
+ /\b([a-zA-Z_][a-zA-Z0-9_]*)\b(?!\s*\()/g,
1815
+ (match) => _MigrationGenerator.SQL_KEYWORDS.has(match.toLowerCase()) ? match : `"${match.toLowerCase()}"`
1816
+ );
1817
+ for (let i = 0; i < quotedIdents.length; i++) {
1818
+ result = result.replace(
1819
+ new RegExp(`${_MigrationGenerator.IDENT_PLACEHOLDER}${i}${_MigrationGenerator.IDENT_PLACEHOLDER}`, "g"),
1820
+ quotedIdents[i]
1821
+ );
1822
+ }
1823
+ for (let i = 0; i < literals.length; i++) {
1824
+ result = result.replace(
1825
+ new RegExp(`${_MigrationGenerator.LITERAL_PLACEHOLDER}${i}${_MigrationGenerator.LITERAL_PLACEHOLDER}`, "g"),
1826
+ literals[i]
1827
+ );
1828
+ }
1829
+ return result;
1830
+ }
1831
+ normalizeExcludeDef(def) {
1832
+ let s = def.replace(/\s+/g, " ").replace(/\s*\(\s*/g, "(").replace(/\s*\)\s*/g, ")").replace(/::\s*timestamp\s+with\s+time\s+zone\b/gi, "::timestamptz").replace(/::\s*timestamp\s+without\s+time\s+zone\b/gi, "::timestamp").trim();
1833
+ const whereMatch = s.match(/\bWHERE\s*\(/i);
1834
+ if (whereMatch) {
1835
+ const openParen = (whereMatch.index ?? 0) + whereMatch[0].length - 1;
1836
+ const closeParen = this.findMatchingParen(s, openParen);
1837
+ if (closeParen !== -1) {
1838
+ let cond = s.slice(openParen + 1, closeParen).trim();
1839
+ while (this.isWrappedByOuterParentheses(cond)) {
1840
+ cond = cond.slice(1, -1).trim();
1841
+ }
1842
+ s = s.slice(0, openParen + 1) + cond + s.slice(closeParen);
1843
+ }
1844
+ }
1845
+ return this.normalizeSqlIdentifiers(s);
1846
+ }
1847
+ findMatchingParen(s, openIndex) {
1848
+ let depth = 1;
1849
+ let inSingleQuote = false;
1850
+ for (let i = openIndex + 1; i < s.length; i++) {
1851
+ const char = s[i];
1852
+ const next = s[i + 1];
1853
+ if (char === "'") {
1854
+ if (inSingleQuote && next === "'") {
1855
+ i++;
1856
+ continue;
1857
+ }
1858
+ inSingleQuote = !inSingleQuote;
1859
+ continue;
1860
+ }
1861
+ if (inSingleQuote) {
1862
+ continue;
1863
+ }
1864
+ if (char === "(") {
1865
+ depth++;
1866
+ } else if (char === ")") {
1867
+ depth--;
1868
+ if (depth === 0) {
1869
+ return i;
1870
+ }
1871
+ }
1872
+ }
1873
+ return -1;
1874
+ }
1875
+ normalizeTriggerDef(def) {
1876
+ 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();
1877
+ return this.normalizeSqlIdentifiers(s);
1878
+ }
1644
1879
  normalizeCheckExpression(expr) {
1645
1880
  let normalized = expr.replace(/\s+/g, " ").trim();
1646
1881
  while (this.isWrappedByOuterParentheses(normalized)) {
@@ -1776,10 +2011,11 @@ var MigrationGenerator = class {
1776
2011
  escapeExpressionForRaw(expr) {
1777
2012
  return expr.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$/g, "\\$");
1778
2013
  }
1779
- addCheckConstraint(table, constraintName, expression) {
2014
+ addCheckConstraint(table, constraintName, expression, deferrable) {
1780
2015
  const escaped = this.escapeExpressionForRaw(expression);
2016
+ const deferrableClause = deferrable ? ` DEFERRABLE ${deferrable}` : "";
1781
2017
  this.writer.writeLine(
1782
- `await knex.raw(\`ALTER TABLE "${table}" ADD CONSTRAINT "${constraintName}" CHECK (${escaped})\`);`
2018
+ `await knex.raw(\`ALTER TABLE "${table}" ADD CONSTRAINT "${constraintName}" CHECK (${escaped})${deferrableClause}\`);`
1783
2019
  );
1784
2020
  this.writer.blankLine();
1785
2021
  }
@@ -1787,6 +2023,43 @@ var MigrationGenerator = class {
1787
2023
  this.writer.writeLine(`await knex.raw('ALTER TABLE "${table}" DROP CONSTRAINT "${constraintName}"');`);
1788
2024
  this.writer.blankLine();
1789
2025
  }
2026
+ buildExcludeDef(entry) {
2027
+ const elementsStr = entry.elements.map((el) => "column" in el ? `"${el.column}" WITH ${el.operator}` : `${el.expression} WITH ${el.operator}`).join(", ");
2028
+ const whereClause = entry.where ? ` WHERE (${entry.where})` : "";
2029
+ const deferrableClause = entry.deferrable ? ` DEFERRABLE ${entry.deferrable}` : "";
2030
+ return `EXCLUDE USING ${entry.using} (${elementsStr})${whereClause}${deferrableClause}`;
2031
+ }
2032
+ addExcludeConstraint(table, constraintName, entry) {
2033
+ const def = this.buildExcludeDef(entry);
2034
+ const escaped = this.escapeExpressionForRaw(def);
2035
+ this.writer.writeLine(`await knex.raw(\`ALTER TABLE "${table}" ADD CONSTRAINT "${constraintName}" ${escaped}\`);`);
2036
+ this.writer.blankLine();
2037
+ }
2038
+ dropExcludeConstraint(table, constraintName) {
2039
+ this.writer.writeLine(`await knex.raw('ALTER TABLE "${table}" DROP CONSTRAINT "${constraintName}"');`);
2040
+ this.writer.blankLine();
2041
+ }
2042
+ buildConstraintTriggerDef(table, constraintName, entry) {
2043
+ const eventsStr = entry.events.join(" OR ");
2044
+ const deferrableClause = entry.deferrable ? ` DEFERRABLE ${entry.deferrable}` : "";
2045
+ const argsStr = entry.function.args?.length ? entry.function.args.map((a) => `"${a}"`).join(", ") : "";
2046
+ const executeClause = argsStr ? `EXECUTE FUNCTION ${entry.function.name}(${argsStr})` : `EXECUTE FUNCTION ${entry.function.name}()`;
2047
+ return `CREATE CONSTRAINT TRIGGER "${constraintName}" ${entry.when} ${eventsStr} ON "${table}"${deferrableClause} FOR EACH ${entry.forEach} ${executeClause}`;
2048
+ }
2049
+ addConstraintTrigger(table, constraintName, entry) {
2050
+ const eventsStr = entry.events.join(" OR ");
2051
+ const deferrableClause = entry.deferrable ? ` DEFERRABLE ${entry.deferrable}` : "";
2052
+ const argsStr = entry.function.args?.length ? entry.function.args.map((a) => `"${a}"`).join(", ") : "";
2053
+ const executeClause = argsStr ? `EXECUTE FUNCTION ${entry.function.name}(${argsStr})` : `EXECUTE FUNCTION ${entry.function.name}()`;
2054
+ this.writer.writeLine(
2055
+ `await knex.raw(\`CREATE CONSTRAINT TRIGGER "${constraintName}" ${entry.when} ${eventsStr} ON "${table}"${deferrableClause} FOR EACH ${entry.forEach} ${executeClause}\`);`
2056
+ );
2057
+ this.writer.blankLine();
2058
+ }
2059
+ dropConstraintTrigger(table, constraintName) {
2060
+ this.writer.writeLine(`await knex.raw('ALTER TABLE "${table}" DROP CONSTRAINT "${constraintName}"');`);
2061
+ this.writer.blankLine();
2062
+ }
1790
2063
  value(value2) {
1791
2064
  if (typeof value2 === "string") {
1792
2065
  return `'${value2}'`;