relq 1.0.48 → 1.0.50

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.
@@ -271,31 +271,33 @@ function parseSchemaFileForComparison(schemaPath) {
271
271
  definition: tableCheckMatch[2].trim(),
272
272
  });
273
273
  }
274
- const checkRegexNew = /check\.constraint\s*\(\s*['"]([^'"]+)['"]/g;
274
+ const checkRegexNew = /check\.constraint\s*\(\s*['"]([^'"]+)['"]\s*,\s*(?:sql`([^`]*)`|([^)]+\)))/g;
275
275
  let newCheckMatch;
276
276
  while ((newCheckMatch = checkRegexNew.exec(optionsBlock)) !== null) {
277
277
  const constraintName = newCheckMatch[1];
278
+ const expression = (newCheckMatch[2] || newCheckMatch[3] || '').trim();
278
279
  if (!constraints.some(c => c.name === constraintName)) {
279
280
  constraints.push({
280
281
  name: constraintName,
281
282
  type: 'CHECK',
282
283
  columns: [],
283
- definition: '',
284
+ definition: expression ? `CHECK (${expression})` : '',
284
285
  });
285
286
  }
286
287
  }
287
288
  const checkConstraintsBlockMatch = optionsBlock.match(/checkConstraints:\s*\([^)]+\)\s*=>\s*\[([^\]]+)\]/s);
288
289
  if (checkConstraintsBlockMatch) {
289
290
  const checkBlock = checkConstraintsBlockMatch[1];
290
- const constraintNameMatches = checkBlock.matchAll(/check\.constraint\s*\(\s*['"]([^'"]+)['"]/g);
291
- for (const match of constraintNameMatches) {
291
+ const constraintMatches = checkBlock.matchAll(/check\.constraint\s*\(\s*['"]([^'"]+)['"]\s*,\s*(?:sql`([^`]*)`|([^)]+\)))/g);
292
+ for (const match of constraintMatches) {
292
293
  const constraintName = match[1];
294
+ const expression = (match[2] || match[3] || '').trim();
293
295
  if (!constraints.some(c => c.name === constraintName)) {
294
296
  constraints.push({
295
297
  name: constraintName,
296
298
  type: 'CHECK',
297
299
  columns: [],
298
- definition: '',
300
+ definition: expression ? `CHECK (${expression})` : '',
299
301
  });
300
302
  }
301
303
  }
@@ -867,7 +869,7 @@ async function addCommand(context) {
867
869
  const snapshot = (0, repo_manager_1.loadSnapshot)(projectRoot);
868
870
  if (currentSchema && snapshot) {
869
871
  const snapshotAsDbSchema = snapshotToDatabaseSchema(snapshot);
870
- const schemaChanges = (0, schema_comparator_1.compareSchemas)(snapshotAsDbSchema, currentSchema);
872
+ const schemaChanges = await (0, schema_comparator_1.compareSchemas)(snapshotAsDbSchema, currentSchema);
871
873
  (0, repo_manager_1.cleanupStagedChanges)(schemaChanges, projectRoot);
872
874
  if (schemaChanges.length > 0) {
873
875
  (0, repo_manager_1.clearUnstagedChanges)(projectRoot);
@@ -278,7 +278,7 @@ async function importCommand(sqlFilePath, options = {}, projectRoot = process.cw
278
278
  mergedSchema = mergeSchemas(existingSnapshot, incomingSchema, replaceAll);
279
279
  const beforeSchema = snapshotToDbSchema(existingSnapshot);
280
280
  const afterSchema = snapshotToDbSchema(mergedSchema);
281
- changes = (0, schema_comparator_1.compareSchemas)(beforeSchema, afterSchema);
281
+ changes = await (0, schema_comparator_1.compareSchemas)(beforeSchema, afterSchema);
282
282
  spinner.stop();
283
283
  if (changes.length > 0) {
284
284
  console.log('');
@@ -180,7 +180,12 @@ async function pullCommand(context) {
180
180
  const merge = flags['merge'] === true;
181
181
  const autoCommit = flags['commit'] === true;
182
182
  const dryRun = flags['dry-run'] === true;
183
+ const theirs = flags['theirs'] === true;
184
+ const ours = flags['ours'] === true;
183
185
  const author = config.author || 'Relq CLI';
186
+ if (theirs && ours) {
187
+ (0, cli_utils_1.fatal)('Cannot use both --theirs and --ours', 'Choose one conflict resolution strategy.');
188
+ }
184
189
  const schemaPathRaw = (0, config_loader_1.getSchemaPath)(config);
185
190
  const schemaPath = path.resolve(projectRoot, schemaPathRaw);
186
191
  const includeFunctions = config.includeFunctions ?? true;
@@ -239,6 +244,14 @@ async function pullCommand(context) {
239
244
  if (remoteHead) {
240
245
  (0, repo_manager_1.setFetchHead)(remoteHead, projectRoot);
241
246
  }
247
+ const localCommits = (0, repo_manager_1.getAllCommits)(projectRoot);
248
+ const localHashes = new Set(localCommits.map(c => c.hash));
249
+ const missingCommits = remoteCommits.filter(c => !localHashes.has(c.hash));
250
+ if (missingCommits.length > 0) {
251
+ for (const commit of missingCommits.reverse()) {
252
+ (0, repo_manager_1.saveCommit)(commit, projectRoot);
253
+ }
254
+ }
242
255
  spinner.succeed(`Fetched ${remoteCommits.length} remote commits`);
243
256
  console.log('');
244
257
  console.log(cli_utils_1.colors.bold('Introspecting database...'));
@@ -418,7 +431,16 @@ async function pullCommand(context) {
418
431
  comment: t.comment,
419
432
  })),
420
433
  functions: localSnapshot.functions || [],
421
- triggers: localSnapshot.triggers || [],
434
+ triggers: (localSnapshot.triggers || []).map(t => ({
435
+ name: t.name,
436
+ tableName: t.table,
437
+ event: t.events?.[0] || 'UPDATE',
438
+ timing: t.timing,
439
+ forEach: t.forEach || 'STATEMENT',
440
+ functionName: t.functionName,
441
+ definition: '',
442
+ isEnabled: true,
443
+ })),
422
444
  };
423
445
  const remoteForCompare = {
424
446
  extensions: dbSchema.extensions || [],
@@ -460,7 +482,7 @@ async function pullCommand(context) {
460
482
  functions: filteredFunctions || [],
461
483
  triggers: filteredTriggers || [],
462
484
  };
463
- const allChanges = (0, schema_comparator_1.compareSchemas)(localForCompare, remoteForCompare);
485
+ const allChanges = await (0, schema_comparator_1.compareSchemas)(localForCompare, remoteForCompare);
464
486
  const changeDisplays = [];
465
487
  for (const change of allChanges) {
466
488
  const objType = change.objectType;
@@ -642,11 +664,44 @@ async function pullCommand(context) {
642
664
  console.log(` ${cli_utils_1.colors.cyan(schemaPath)}`);
643
665
  console.log('');
644
666
  if (!dryRun) {
645
- const proceed = await (0, cli_utils_1.confirm)(`${cli_utils_1.colors.bold('Overwrite local schema?')}`, false);
646
- if (!proceed) {
647
- (0, cli_utils_1.fatal)('Operation cancelled by user', `Run ${cli_utils_1.colors.cyan('relq status')} to see current state.`);
667
+ if (theirs) {
668
+ console.log(`${cli_utils_1.colors.green('Using --theirs:')} Overwriting local schema with database version`);
669
+ console.log('');
670
+ }
671
+ else if (ours) {
672
+ console.log(`${cli_utils_1.colors.yellow('Using --ours:')} Keeping local schema, syncing snapshot from database`);
673
+ console.log('');
674
+ (0, repo_manager_1.saveSnapshot)(currentSchema, projectRoot);
675
+ console.log(`Snapshot synced from database`);
676
+ console.log('');
677
+ console.log(`hint: run ${cli_utils_1.colors.cyan("'relq add .'")} to detect differences between local schema and database`);
678
+ console.log('');
679
+ return;
680
+ }
681
+ else {
682
+ console.log('How would you like to handle this?');
683
+ console.log('');
684
+ console.log(` ${cli_utils_1.colors.cyan('--theirs')} Use database version (overwrite local schema)`);
685
+ console.log(` ${cli_utils_1.colors.cyan('--ours')} Keep local schema (sync snapshot only)`);
686
+ console.log('');
687
+ const choiceIndex = await (0, cli_utils_1.select)('Choose resolution strategy:', [
688
+ 'Use database version (--theirs)',
689
+ 'Keep local schema (--ours)',
690
+ 'Cancel',
691
+ ]);
692
+ if (choiceIndex === 2) {
693
+ (0, cli_utils_1.fatal)('Operation cancelled by user', `Run ${cli_utils_1.colors.cyan('relq status')} to see current state.`);
694
+ }
695
+ else if (choiceIndex === 1) {
696
+ (0, repo_manager_1.saveSnapshot)(currentSchema, projectRoot);
697
+ console.log('');
698
+ console.log(`Snapshot synced from database`);
699
+ console.log(`hint: run ${cli_utils_1.colors.cyan("'relq add .'")} to detect differences between local schema and database`);
700
+ console.log('');
701
+ return;
702
+ }
703
+ console.log('');
648
704
  }
649
- console.log('');
650
705
  }
651
706
  }
652
707
  else if (!schemaExists) {
@@ -788,6 +843,7 @@ async function pullCommand(context) {
788
843
  }
789
844
  }
790
845
  const oldSnapshot = (0, repo_manager_1.loadSnapshot)(projectRoot);
846
+ const hadPreviousSnapshot = oldSnapshot !== null;
791
847
  const beforeSchema = oldSnapshot ? {
792
848
  extensions: oldSnapshot.extensions?.map(e => e.name) || [],
793
849
  enums: oldSnapshot.enums || [],
@@ -825,7 +881,16 @@ async function pullCommand(context) {
825
881
  comment: t.comment,
826
882
  })),
827
883
  functions: oldSnapshot.functions || [],
828
- triggers: oldSnapshot.triggers || [],
884
+ triggers: (oldSnapshot.triggers || []).map(t => ({
885
+ name: t.name,
886
+ tableName: t.table,
887
+ event: t.events?.[0] || 'UPDATE',
888
+ timing: t.timing,
889
+ forEach: t.forEach || 'STATEMENT',
890
+ functionName: t.functionName,
891
+ definition: '',
892
+ isEnabled: true,
893
+ })),
829
894
  } : {
830
895
  extensions: [],
831
896
  enums: [],
@@ -876,22 +941,28 @@ async function pullCommand(context) {
876
941
  functions: filteredFunctions || [],
877
942
  triggers: filteredTriggers || [],
878
943
  };
879
- const schemaChanges = (0, schema_comparator_1.compareSchemas)(beforeSchema, afterSchema);
944
+ const schemaChanges = await (0, schema_comparator_1.compareSchemas)(beforeSchema, afterSchema);
880
945
  (0, ast_codegen_1.copyTrackingIdsToNormalized)(parsedSchema, currentSchema);
881
946
  (0, repo_manager_1.saveSnapshot)(currentSchema, projectRoot);
882
947
  const duration = Date.now() - startTime;
883
948
  if (!autoCommit) {
884
- if (schemaChanges.length > 0) {
949
+ if (remoteHead) {
950
+ (0, repo_manager_1.setHead)(remoteHead, projectRoot);
951
+ }
952
+ if (hadPreviousSnapshot && schemaChanges.length > 0) {
885
953
  (0, repo_manager_1.addUnstagedChanges)(schemaChanges, projectRoot);
886
954
  spinner.succeed(`Detected ${schemaChanges.length} schema change(s)`);
887
955
  }
888
956
  console.log('');
889
957
  console.log(`Pull completed in ${(0, cli_utils_1.formatDuration)(duration)}`);
890
- if (schemaChanges.length > 0) {
958
+ if (hadPreviousSnapshot && schemaChanges.length > 0) {
891
959
  console.log('');
892
960
  console.log(`${cli_utils_1.colors.green(String(schemaChanges.length))} change(s) ready to stage`);
893
961
  console.log(`hint: run ${cli_utils_1.colors.cyan("'relq add .'")} to stage all changes`);
894
962
  }
963
+ else if (!hadPreviousSnapshot) {
964
+ console.log('Schema synced from database');
965
+ }
895
966
  else {
896
967
  console.log('Already up to date');
897
968
  }
@@ -328,14 +328,32 @@ async function pushCommand(context) {
328
328
  }
329
329
  }
330
330
  }
331
+ let statementIndex = 0;
331
332
  for (const commit of commitsToProcess) {
332
333
  const commitPath = path.join(projectRoot, '.relq', 'commits', `${commit.hash}.json`);
333
334
  if (fs.existsSync(commitPath)) {
334
335
  const enhancedCommit = JSON.parse(fs.readFileSync(commitPath, 'utf-8'));
335
336
  if (enhancedCommit.sql && enhancedCommit.sql.trim()) {
336
- await client.query(enhancedCommit.sql);
337
+ const statements = enhancedCommit.sql
338
+ .split(';')
339
+ .map(s => s.trim())
340
+ .filter(s => s.length > 0);
341
+ for (const stmt of statements) {
342
+ statementIndex++;
343
+ try {
344
+ await client.query(stmt);
345
+ statementsRun++;
346
+ }
347
+ catch (stmtError) {
348
+ const err = new Error(stmtError.message);
349
+ err.failedStatement = stmt;
350
+ err.commitHash = commit.hash;
351
+ err.commitMessage = commit.message;
352
+ err.statementIndex = statementIndex;
353
+ throw err;
354
+ }
355
+ }
337
356
  sqlExecuted++;
338
- statementsRun += enhancedCommit.sql.split(';').filter(s => s.trim()).length;
339
357
  }
340
358
  }
341
359
  }
@@ -375,23 +393,29 @@ async function pushCommand(context) {
375
393
  catch (error) {
376
394
  try {
377
395
  await client.query('ROLLBACK');
378
- spinner.fail('SQL execution failed - rolled back. No commits pushed.');
379
396
  }
380
397
  catch {
381
- spinner.fail('SQL execution failed');
382
398
  }
383
399
  const dbError = error?.message || String(error);
384
- const enhancedError = new Error(`${dbError}\n\n` +
385
- `${cli_utils_1.colors.muted('This usually means:')}\n` +
386
- ` • The SQL statements were generated in the wrong order\n` +
387
- ` • A column/table is referenced before it's created\n` +
388
- ` • The schema is out of sync with the database\n\n` +
389
- `${cli_utils_1.colors.muted('To fix:')}\n` +
390
- ` 1. Run ${cli_utils_1.colors.cyan('relq reset --hard HEAD~1')} to undo the commit\n` +
391
- ` 2. Run ${cli_utils_1.colors.cyan('relq pull')} to sync with database\n` +
392
- ` 3. Run ${cli_utils_1.colors.cyan('relq add')} and ${cli_utils_1.colors.cyan('relq commit')} again\n\n` +
393
- `${cli_utils_1.colors.muted('To debug, run:')} ${cli_utils_1.colors.cyan('relq push --dry-run')} ${cli_utils_1.colors.muted('to see the SQL')}`);
394
- throw enhancedError;
400
+ let errorMsg = `${cli_utils_1.colors.red('SQL Error:')} ${dbError}\n`;
401
+ if (error.failedStatement) {
402
+ errorMsg += `\n${cli_utils_1.colors.yellow('Failed Statement:')}\n`;
403
+ errorMsg += ` ${error.failedStatement}\n`;
404
+ }
405
+ if (error.commitHash) {
406
+ errorMsg += `\n${cli_utils_1.colors.yellow('In Commit:')} ${(0, repo_manager_1.shortHash)(error.commitHash)}`;
407
+ if (error.commitMessage) {
408
+ errorMsg += ` - ${error.commitMessage}`;
409
+ }
410
+ errorMsg += `\n`;
411
+ }
412
+ if (error.statementIndex) {
413
+ errorMsg += `${cli_utils_1.colors.yellow('Statement #:')} ${error.statementIndex}\n`;
414
+ }
415
+ errorMsg += `\n${cli_utils_1.colors.muted('All changes rolled back. No commits pushed.')}\n`;
416
+ errorMsg += `\n${cli_utils_1.colors.cyan('To fix:')} Run ${cli_utils_1.colors.yellow('relq reset --hard HEAD~1')} then ${cli_utils_1.colors.yellow('relq pull')} to resync`;
417
+ spinner.fail('SQL execution failed');
418
+ throw new Error(errorMsg);
395
419
  }
396
420
  finally {
397
421
  await client.end();
@@ -407,6 +431,20 @@ async function pushCommand(context) {
407
431
  spinner.succeed(`Pushed ${toPush.length} commit(s) to ${(0, repo_manager_1.getConnectionLabel)(connection)}`);
408
432
  }
409
433
  }
434
+ if (sqlApplied && !dryRun) {
435
+ const latestCommitPath = path.join(projectRoot, '.relq', 'commits', `${localHead}.json`);
436
+ if (fs.existsSync(latestCommitPath)) {
437
+ const latestCommit = JSON.parse(fs.readFileSync(latestCommitPath, 'utf-8'));
438
+ const newSnapshot = latestCommit.schema || latestCommit.snapshot;
439
+ if (newSnapshot) {
440
+ const snapshotPath = path.join(projectRoot, '.relq', 'snapshot.json');
441
+ fs.writeFileSync(snapshotPath, JSON.stringify(newSnapshot, null, 2));
442
+ }
443
+ }
444
+ const { saveFileHash, hashFileContent } = await Promise.resolve().then(() => __importStar(require("../utils/repo-manager.cjs")));
445
+ const schemaContent = fs.readFileSync(schemaPath, 'utf-8');
446
+ saveFileHash(hashFileContent(schemaContent), projectRoot);
447
+ }
410
448
  const oldHash = remoteHead ? (0, repo_manager_1.shortHash)(remoteHead) : '(none)';
411
449
  const newHash = (0, repo_manager_1.shortHash)(localHead);
412
450
  console.log(` ${oldHash}..${newHash} ${cli_utils_1.colors.muted('main -> main')}`);
@@ -12,6 +12,7 @@ const builder_1 = require("./ast/codegen/builder.cjs");
12
12
  const type_map_1 = require("./ast/codegen/type-map.cjs");
13
13
  const defaults_1 = require("./ast/codegen/defaults.cjs");
14
14
  const constraints_1 = require("./ast/codegen/constraints.cjs");
15
+ const pg_parser_1 = require("./pg-parser.cjs");
15
16
  let needsDefaultImport = false;
16
17
  let needsSqlImport = false;
17
18
  let trackingIdCounter = 0;
@@ -459,14 +460,14 @@ function generateTableCode(table, useCamelCase, enumNames, domainNames, columnTy
459
460
  const columnChecks = new Map();
460
461
  for (const c of table.constraints) {
461
462
  if (c.type === 'CHECK' && c.expression) {
462
- for (const col of table.columns) {
463
- const colPattern = new RegExp(`\\(${col.name}\\)|\\b${col.name}\\b`, 'i');
464
- if (colPattern.test(c.expression)) {
463
+ const extractedCol = (0, pg_parser_1.extractColumnFromCheck)(c.expression);
464
+ if (extractedCol) {
465
+ const matchingCol = table.columns.find(col => col.name.toLowerCase() === extractedCol);
466
+ if (matchingCol) {
465
467
  const values = (0, constraints_1.extractEnumValues)(c.expression);
466
468
  if (values && values.length > 0) {
467
- columnChecks.set(col.name, { name: c.name, values });
469
+ columnChecks.set(matchingCol.name, { name: c.name, values });
468
470
  }
469
- break;
470
471
  }
471
472
  }
472
473
  }
@@ -137,8 +137,10 @@ async function deparseNode(node) {
137
137
  }
138
138
  async function parseColumnDef(colDef) {
139
139
  const typeInfo = extractTypeName(colDef.typeName);
140
+ const colName = colDef.colname || '';
140
141
  const column = {
141
- name: colDef.colname || '',
142
+ name: colName,
143
+ tsName: colName,
142
144
  type: typeInfo.name,
143
145
  typeParams: typeInfo.params,
144
146
  isNullable: true,
@@ -597,6 +599,7 @@ async function introspectedToParsedSchema(schema) {
597
599
  for (const c of t.columns) {
598
600
  const col = {
599
601
  name: c.name,
602
+ tsName: c.name,
600
603
  type: normalizeTypeName(c.dataType, c.udtName),
601
604
  typeParams: extractTypeParams(c),
602
605
  isNullable: c.isNullable,
@@ -857,6 +860,7 @@ function normalizedToParsedSchema(schema) {
857
860
  const baseType = isArray ? c.type.slice(0, -2) : c.type;
858
861
  return {
859
862
  name: c.name,
863
+ tsName: c.name,
860
864
  type: baseType,
861
865
  isNullable: c.nullable ?? true,
862
866
  isPrimaryKey: c.primaryKey ?? false,
@@ -296,8 +296,24 @@ function generateConstraintSQL(change) {
296
296
  return '';
297
297
  const data = change.after;
298
298
  if (change.type === 'CREATE' && data) {
299
+ if (!data.definition || data.definition.trim() === '') {
300
+ if (data.name.endsWith('_key')) {
301
+ return `-- Skipping ${data.name}: UNIQUE constraint already defined on column`;
302
+ }
303
+ return `-- Skipping ${data.name}: empty constraint definition`;
304
+ }
299
305
  return `ALTER TABLE "${tableName}" ADD CONSTRAINT "${data.name}" ${data.definition};`;
300
306
  }
307
+ else if (change.type === 'ALTER' && data) {
308
+ const beforeData = change.before;
309
+ const oldName = beforeData?.name || change.objectName;
310
+ if (!data.definition || data.definition.trim() === '') {
311
+ return `-- Skipping ALTER ${data.name}: empty constraint definition`;
312
+ }
313
+ const dropSQL = `ALTER TABLE "${tableName}" DROP CONSTRAINT IF EXISTS "${oldName}";`;
314
+ const addSQL = `ALTER TABLE "${tableName}" ADD CONSTRAINT "${data.name}" ${data.definition};`;
315
+ return `${dropSQL}\n${addSQL}`;
316
+ }
301
317
  else if (change.type === 'DROP') {
302
318
  return `ALTER TABLE "${tableName}" DROP CONSTRAINT IF EXISTS "${change.objectName}";`;
303
319
  }
@@ -1 +1,258 @@
1
1
  "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseSQL = parseSQL;
4
+ exports.normalizeSQL = normalizeSQL;
5
+ exports.compareSQLByAST = compareSQLByAST;
6
+ exports.normalizeCheckConstraint = normalizeCheckConstraint;
7
+ exports.compareCheckConstraintsAsync = compareCheckConstraintsAsync;
8
+ exports.compareCheckConstraints = compareCheckConstraints;
9
+ exports.normalizeFunctionBodyAST = normalizeFunctionBodyAST;
10
+ exports.compareFunctionBodiesAsync = compareFunctionBodiesAsync;
11
+ exports.compareFunctionBodies = compareFunctionBodies;
12
+ exports.normalizeCreateTable = normalizeCreateTable;
13
+ exports.extractColumnFromCheck = extractColumnFromCheck;
14
+ exports.compareTriggers = compareTriggers;
15
+ const pgsql_parser_1 = require("pgsql-parser");
16
+ const pgsql_deparser_1 = require("pgsql-deparser");
17
+ async function parseSQL(sql) {
18
+ try {
19
+ const result = await (0, pgsql_parser_1.parse)(sql);
20
+ return result;
21
+ }
22
+ catch (err) {
23
+ return null;
24
+ }
25
+ }
26
+ async function normalizeSQL(sql) {
27
+ try {
28
+ const ast = await (0, pgsql_parser_1.parse)(sql);
29
+ if (!ast || ast.length === 0)
30
+ return null;
31
+ return await (0, pgsql_deparser_1.deparse)(ast);
32
+ }
33
+ catch (err) {
34
+ return null;
35
+ }
36
+ }
37
+ async function compareSQLByAST(sql1, sql2) {
38
+ try {
39
+ const [ast1, ast2] = await Promise.all([(0, pgsql_parser_1.parse)(sql1), (0, pgsql_parser_1.parse)(sql2)]);
40
+ if (!ast1 || !ast2)
41
+ return false;
42
+ const [normalized1, normalized2] = await Promise.all([(0, pgsql_deparser_1.deparse)(ast1), (0, pgsql_deparser_1.deparse)(ast2)]);
43
+ return normalized1 === normalized2;
44
+ }
45
+ catch (err) {
46
+ return false;
47
+ }
48
+ }
49
+ async function normalizeCheckConstraint(definition) {
50
+ if (!definition)
51
+ return null;
52
+ let sqlToparse = definition.trim();
53
+ if (sqlToparse.toUpperCase().startsWith('CHECK')) {
54
+ sqlToparse = sqlToparse.slice(5).trim();
55
+ }
56
+ while (sqlToparse.startsWith('(') && sqlToparse.endsWith(')')) {
57
+ let depth = 0;
58
+ let innerIsSame = true;
59
+ for (let i = 0; i < sqlToparse.length - 1; i++) {
60
+ if (sqlToparse[i] === '(')
61
+ depth++;
62
+ else if (sqlToparse[i] === ')') {
63
+ depth--;
64
+ if (depth === 0 && i < sqlToparse.length - 1) {
65
+ innerIsSame = false;
66
+ break;
67
+ }
68
+ }
69
+ }
70
+ if (innerIsSame) {
71
+ sqlToparse = sqlToparse.slice(1, -1).trim();
72
+ }
73
+ else {
74
+ break;
75
+ }
76
+ }
77
+ try {
78
+ const wrappedSQL = `SELECT * FROM t WHERE ${sqlToparse}`;
79
+ const ast = await (0, pgsql_parser_1.parse)(wrappedSQL);
80
+ if (ast && ast.length > 0) {
81
+ const normalized = await (0, pgsql_deparser_1.deparse)(ast);
82
+ const whereIdx = normalized.toUpperCase().indexOf('WHERE ');
83
+ if (whereIdx >= 0) {
84
+ return normalized.slice(whereIdx + 6).trim();
85
+ }
86
+ }
87
+ }
88
+ catch (err) {
89
+ }
90
+ const wrapStrategies = [
91
+ `SELECT CASE WHEN ${sqlToparse} THEN 1 END`,
92
+ `CREATE TABLE t (x INT CHECK (${sqlToparse}))`,
93
+ ];
94
+ for (const wrapped of wrapStrategies) {
95
+ try {
96
+ const ast = await (0, pgsql_parser_1.parse)(wrapped);
97
+ if (ast && ast.length > 0) {
98
+ return await (0, pgsql_deparser_1.deparse)(ast);
99
+ }
100
+ }
101
+ catch {
102
+ continue;
103
+ }
104
+ }
105
+ return null;
106
+ }
107
+ async function compareCheckConstraintsAsync(def1, def2) {
108
+ if (!def1 && !def2)
109
+ return true;
110
+ if (!def1 || !def2)
111
+ return false;
112
+ const [norm1, norm2] = await Promise.all([
113
+ normalizeCheckConstraint(def1),
114
+ normalizeCheckConstraint(def2)
115
+ ]);
116
+ if (norm1 && norm2) {
117
+ return norm1 === norm2;
118
+ }
119
+ return compareCheckValues(def1, def2);
120
+ }
121
+ async function compareCheckConstraints(def1, def2) {
122
+ if (!def1 && !def2)
123
+ return true;
124
+ if (!def1 || !def2)
125
+ return false;
126
+ const [norm1, norm2] = await Promise.all([
127
+ normalizeCheckConstraint(def1),
128
+ normalizeCheckConstraint(def2)
129
+ ]);
130
+ if (norm1 && norm2) {
131
+ return norm1 === norm2;
132
+ }
133
+ return compareCheckValues(def1, def2);
134
+ }
135
+ function compareCheckValues(def1, def2) {
136
+ const values1 = extractCheckValues(def1);
137
+ const values2 = extractCheckValues(def2);
138
+ if (values1.length !== values2.length)
139
+ return false;
140
+ const sorted1 = [...values1].sort();
141
+ const sorted2 = [...values2].sort();
142
+ return sorted1.every((v, i) => v === sorted2[i]);
143
+ }
144
+ function extractCheckValues(definition) {
145
+ if (!definition)
146
+ return [];
147
+ const arrayMatch = definition.match(/ARRAY\[([^\]]+)\]/i);
148
+ if (arrayMatch) {
149
+ const valuesStr = arrayMatch[1];
150
+ const values = valuesStr.match(/'([^']+)'/g)?.map(v => v.replace(/'/g, '').toLowerCase()) || [];
151
+ return values;
152
+ }
153
+ const inMatch = definition.match(/IN\s*\(([^)]+)\)/i);
154
+ if (inMatch) {
155
+ const valuesStr = inMatch[1];
156
+ const values = valuesStr.match(/'([^']+)'/g)?.map(v => v.replace(/'/g, '').toLowerCase()) || [];
157
+ return values;
158
+ }
159
+ return [];
160
+ }
161
+ async function normalizeFunctionBodyAST(body) {
162
+ if (!body)
163
+ return null;
164
+ try {
165
+ const asBlock = `DO $$ ${body} $$`;
166
+ const ast = await (0, pgsql_parser_1.parse)(asBlock);
167
+ if (ast && ast.length > 0) {
168
+ return await (0, pgsql_deparser_1.deparse)(ast);
169
+ }
170
+ }
171
+ catch {
172
+ }
173
+ try {
174
+ const ast = await (0, pgsql_parser_1.parse)(body);
175
+ if (ast && ast.length > 0) {
176
+ return await (0, pgsql_deparser_1.deparse)(ast);
177
+ }
178
+ }
179
+ catch {
180
+ }
181
+ try {
182
+ const asFunc = `CREATE FUNCTION _tmp() RETURNS void AS $$ ${body} $$ LANGUAGE plpgsql`;
183
+ const ast = await (0, pgsql_parser_1.parse)(asFunc);
184
+ if (ast && ast.length > 0) {
185
+ return await (0, pgsql_deparser_1.deparse)(ast);
186
+ }
187
+ }
188
+ catch {
189
+ }
190
+ return null;
191
+ }
192
+ async function compareFunctionBodiesAsync(body1, body2) {
193
+ if (!body1 && !body2)
194
+ return true;
195
+ if (!body1 || !body2)
196
+ return false;
197
+ const [norm1, norm2] = await Promise.all([
198
+ normalizeFunctionBodyAST(body1),
199
+ normalizeFunctionBodyAST(body2)
200
+ ]);
201
+ if (norm1 && norm2) {
202
+ return norm1 === norm2;
203
+ }
204
+ return normalizeString(body1) === normalizeString(body2);
205
+ }
206
+ async function compareFunctionBodies(body1, body2) {
207
+ if (!body1 && !body2)
208
+ return true;
209
+ if (!body1 || !body2)
210
+ return false;
211
+ const [norm1, norm2] = await Promise.all([
212
+ normalizeFunctionBodyAST(body1),
213
+ normalizeFunctionBodyAST(body2)
214
+ ]);
215
+ if (norm1 && norm2) {
216
+ return norm1 === norm2;
217
+ }
218
+ return normalizeString(body1) === normalizeString(body2);
219
+ }
220
+ function normalizeString(str) {
221
+ if (!str)
222
+ return '';
223
+ return str.trim().replace(/\s+/g, ' ').toLowerCase();
224
+ }
225
+ async function normalizeCreateTable(sql) {
226
+ try {
227
+ const ast = await (0, pgsql_parser_1.parse)(sql);
228
+ if (ast && ast.length > 0) {
229
+ return await (0, pgsql_deparser_1.deparse)(ast);
230
+ }
231
+ }
232
+ catch {
233
+ return null;
234
+ }
235
+ return null;
236
+ }
237
+ function extractColumnFromCheck(definition) {
238
+ if (!definition)
239
+ return null;
240
+ const enumMatch = definition.match(/\((\w+)\)::text\s*=\s*ANY/i);
241
+ if (enumMatch)
242
+ return enumMatch[1].toLowerCase();
243
+ const inMatch = definition.match(/["\(](\w+)["]\s+IN\s*\(/i);
244
+ if (inMatch)
245
+ return inMatch[1].toLowerCase();
246
+ const compMatch = definition.match(/\(\(?(\w+)\s*(?:>=?|<=?|<>|!=|=)/i);
247
+ if (compMatch)
248
+ return compMatch[1].toLowerCase();
249
+ return null;
250
+ }
251
+ function compareTriggers(trigger1, trigger2) {
252
+ if (!trigger1 || !trigger2)
253
+ return false;
254
+ return (normalizeString(trigger1.timing) === normalizeString(trigger2.timing) &&
255
+ normalizeString(trigger1.event) === normalizeString(trigger2.event) &&
256
+ normalizeString(trigger1.functionName) === normalizeString(trigger2.functionName) &&
257
+ normalizeString(trigger1.tableName) === normalizeString(trigger2.tableName));
258
+ }