relq 1.0.41 → 1.0.42

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.
@@ -149,7 +149,7 @@ function parseSchemaFileForComparison(schemaPath) {
149
149
  else {
150
150
  const strDefaultMatch = colDef.match(/\.default\(\s*(['"])([^'"]*)\1\s*\)/);
151
151
  if (strDefaultMatch) {
152
- defaultValue = strDefaultMatch[2];
152
+ defaultValue = `'${strDefaultMatch[2]}'`;
153
153
  }
154
154
  else {
155
155
  const boolDefaultMatch = colDef.match(/\.default\(\s*(true|false)\s*\)/);
@@ -178,17 +178,19 @@ function parseSchemaFileForComparison(schemaPath) {
178
178
  }
179
179
  }
180
180
  if (colDef.includes('.check(')) {
181
- const checkValuesMatch = colDef.match(/\.check\(([^)]+)\)/);
182
- if (checkValuesMatch) {
183
- const valuesStr = checkValuesMatch[1];
181
+ const checkMatch = colDef.match(/\.check\(\s*['"]([^'"]+)['"]\s*,\s*\[([^\]]+)\]\s*\)/);
182
+ if (checkMatch) {
183
+ const constraintName = checkMatch[1];
184
+ const valuesStr = checkMatch[2];
184
185
  const values = valuesStr.match(/['"]([^'"]+)['"]/g)?.map(v => v.replace(/['"]/g, '')) || [];
185
186
  if (values.length > 0) {
186
- const constraintName = `${tableName}_${dbColName}_check`;
187
+ const valuesQuoted = values.map(v => `'${v}'`).join(', ');
188
+ const definition = `CHECK ("${dbColName}" IN (${valuesQuoted}))`;
187
189
  constraints.push({
188
190
  name: constraintName,
189
191
  type: 'CHECK',
190
192
  columns: [dbColName],
191
- definition: '',
193
+ definition,
192
194
  });
193
195
  }
194
196
  }
@@ -322,7 +324,6 @@ function parseSchemaFileForComparison(schemaPath) {
322
324
  for (const col of columns) {
323
325
  if (col.isUnique && !col.isPrimaryKey) {
324
326
  const uniqueName = `${tableName}_${col.name}_key`;
325
- col.isUnique = false;
326
327
  if (!constraints.some(c => c.name === uniqueName)) {
327
328
  constraints.push({
328
329
  name: uniqueName,
@@ -331,6 +332,7 @@ function parseSchemaFileForComparison(schemaPath) {
331
332
  definition: '',
332
333
  });
333
334
  }
335
+ col.isUnique = false;
334
336
  }
335
337
  }
336
338
  const uniqueConstraintRegex = /constraint\.unique\s*\(\s*\{\s*name:\s*['"]([^'"]+)['"]\s*,\s*columns:\s*\[([^\]]+)\]/g;
@@ -441,7 +443,7 @@ function parseSchemaFileForComparison(schemaPath) {
441
443
  }
442
444
  }
443
445
  if (relationsBlock) {
444
- const tableEntryRegex = /(\w+):\s*tables\.\1\s*\(\s*\([^)]*\)\s*=>\s*\(\{/g;
446
+ const tableEntryRegex = /(\w+):\s*tables\.\1\s*\(\s*\([^)]*\)\s*=>\s*\[/g;
445
447
  let tableMatch;
446
448
  while ((tableMatch = tableEntryRegex.exec(relationsBlock)) !== null) {
447
449
  const tableTsName = tableMatch[1];
@@ -450,9 +452,9 @@ function parseSchemaFileForComparison(schemaPath) {
450
452
  let depth = 1;
451
453
  let j = entryStart;
452
454
  while (j < relationsBlock.length && depth > 0) {
453
- if (relationsBlock[j] === '{')
455
+ if (relationsBlock[j] === '[')
454
456
  depth++;
455
- else if (relationsBlock[j] === '}')
457
+ else if (relationsBlock[j] === ']')
456
458
  depth--;
457
459
  j++;
458
460
  }
@@ -460,35 +462,110 @@ function parseSchemaFileForComparison(schemaPath) {
460
462
  const table = tables.find(t => t.name === tableDbName);
461
463
  if (!table)
462
464
  continue;
463
- const fkRegex = /(\w+):\s*r\.referenceTo\.(\w+)\s*\(\s*\w*\s*=>\s*\(\{([^}]*)\}\)/g;
465
+ const fkRegex = /r\.referenceTo\.(\w+)\s*\(\s*\w*\s*=>\s*\(\{([^}]*(?:\{[^}]*\}[^}]*)*)\}\)/g;
464
466
  let fkMatch;
465
467
  while ((fkMatch = fkRegex.exec(tableRelationsContent)) !== null) {
466
- const colTsName = fkMatch[1];
467
- const refTableTsName = fkMatch[2];
468
- const fkOptionsStr = fkMatch[3];
469
- const colDbName = toSnakeCase(colTsName);
468
+ const refTableTsName = fkMatch[1];
469
+ const fkOptionsStr = fkMatch[2];
470
470
  const refTableDbName = toSnakeCase(refTableTsName);
471
- let onDelete;
472
- let onUpdate;
471
+ const singleColMatch = fkOptionsStr.match(/columns:\s*['"](\w+)['"]/);
472
+ const compositeColMatch = fkOptionsStr.match(/columns:\s*\[([^\]]+)\]/);
473
+ let sourceColumns = [];
474
+ let isComposite = false;
475
+ if (compositeColMatch) {
476
+ isComposite = true;
477
+ const colsStr = compositeColMatch[1];
478
+ const colMatches = colsStr.matchAll(/['"](\w+)['"]/g);
479
+ for (const m of colMatches) {
480
+ sourceColumns.push(toSnakeCase(m[1]));
481
+ }
482
+ }
483
+ else if (singleColMatch) {
484
+ sourceColumns = [toSnakeCase(singleColMatch[1])];
485
+ }
486
+ else {
487
+ continue;
488
+ }
489
+ let refColumns;
490
+ const singleRefMatch = fkOptionsStr.match(/references:\s*t\.(\w+)(?![.\[])/);
491
+ const compositeRefMatch = fkOptionsStr.match(/references:\s*\[([^\]]+)\]/);
492
+ if (compositeRefMatch) {
493
+ refColumns = [];
494
+ const refsStr = compositeRefMatch[1];
495
+ const refMatches = refsStr.matchAll(/t\.(\w+)/g);
496
+ for (const m of refMatches) {
497
+ refColumns.push(toSnakeCase(m[1]));
498
+ }
499
+ }
500
+ else if (singleRefMatch) {
501
+ refColumns = [toSnakeCase(singleRefMatch[1])];
502
+ }
473
503
  const nameMatch = fkOptionsStr.match(/name:\s*['"]([^'"]+)['"]/);
474
504
  const onDeleteMatch = fkOptionsStr.match(/onDelete:\s*['"]([^'"]+)['"]/);
475
505
  const onUpdateMatch = fkOptionsStr.match(/onUpdate:\s*['"]([^'"]+)['"]/);
476
- if (onDeleteMatch)
477
- onDelete = onDeleteMatch[1];
478
- if (onUpdateMatch)
479
- onUpdate = onUpdateMatch[1];
480
- const fkName = nameMatch ? nameMatch[1] : `${tableDbName}_${colDbName}_fkey`;
481
- if (!table.constraints.some(c => c.name === fkName || (c.type === 'FOREIGN KEY' && c.columns?.includes(colDbName)))) {
482
- table.constraints.push({
483
- name: fkName,
484
- type: 'FOREIGN KEY',
485
- columns: [colDbName],
486
- definition: '',
487
- referencedTable: refTableDbName,
488
- referencedColumns: ['id'],
489
- onDelete,
490
- onUpdate,
491
- });
506
+ const matchMatch = fkOptionsStr.match(/match:\s*['"]([^'"]+)['"]/);
507
+ const deferrableMatch = fkOptionsStr.match(/deferrable:\s*(true|false)/);
508
+ const initiallyDeferredMatch = fkOptionsStr.match(/initiallyDeferred:\s*(true|false)/);
509
+ const onDelete = onDeleteMatch ? onDeleteMatch[1] : undefined;
510
+ const onUpdate = onUpdateMatch ? onUpdateMatch[1] : undefined;
511
+ const match = matchMatch ? matchMatch[1] : undefined;
512
+ const deferrable = deferrableMatch ? deferrableMatch[1] === 'true' : undefined;
513
+ const initiallyDeferred = initiallyDeferredMatch ? initiallyDeferredMatch[1] === 'true' : undefined;
514
+ if (isComposite) {
515
+ const fkName = nameMatch
516
+ ? nameMatch[1]
517
+ : `${tableDbName}_${sourceColumns.join('_')}_fkey`;
518
+ const colsQuoted = sourceColumns.map(c => `"${c}"`).join(', ');
519
+ let definition = `FOREIGN KEY (${colsQuoted}) REFERENCES "${refTableDbName}"`;
520
+ if (refColumns && refColumns.length > 0) {
521
+ definition += `(${refColumns.map(c => `"${c}"`).join(', ')})`;
522
+ }
523
+ if (onDelete)
524
+ definition += ` ON DELETE ${onDelete.toUpperCase()}`;
525
+ if (onUpdate)
526
+ definition += ` ON UPDATE ${onUpdate.toUpperCase()}`;
527
+ if (match)
528
+ definition += ` MATCH ${match.toUpperCase()}`;
529
+ if (deferrable) {
530
+ definition += ' DEFERRABLE';
531
+ if (initiallyDeferred)
532
+ definition += ' INITIALLY DEFERRED';
533
+ }
534
+ const alreadyExists = table.constraints.some(c => c.name === fkName ||
535
+ (c.type === 'FOREIGN KEY' && c.columns?.join(',') === sourceColumns.join(',')));
536
+ if (!alreadyExists) {
537
+ table.constraints.push({
538
+ name: fkName,
539
+ type: 'FOREIGN KEY',
540
+ columns: sourceColumns,
541
+ definition,
542
+ });
543
+ }
544
+ }
545
+ else {
546
+ const colDbName = sourceColumns[0];
547
+ const fkName = nameMatch
548
+ ? nameMatch[1]
549
+ : `${tableDbName}_${colDbName}_fkey`;
550
+ const column = table.columns.find((c) => c.name === colDbName);
551
+ if (column && !column.references) {
552
+ column.references = {
553
+ table: refTableDbName,
554
+ column: refColumns?.[0] || '',
555
+ onDelete,
556
+ onUpdate,
557
+ };
558
+ }
559
+ const alreadyExists = table.constraints.some(c => c.name === fkName ||
560
+ (c.type === 'FOREIGN KEY' && c.columns?.join(',') === sourceColumns.join(',')));
561
+ if (!alreadyExists) {
562
+ table.constraints.push({
563
+ name: fkName,
564
+ type: 'FOREIGN KEY',
565
+ columns: sourceColumns,
566
+ definition: '',
567
+ });
568
+ }
492
569
  }
493
570
  }
494
571
  }
@@ -849,7 +926,25 @@ async function addCommand(context) {
849
926
  console.log('');
850
927
  return;
851
928
  }
852
- console.log(`Staged ${stagedNow.length} change(s):`);
929
+ let totalCount = stagedNow.length;
930
+ for (const change of stagedNow) {
931
+ if (change.type === 'CREATE' && change.objectType === 'TABLE') {
932
+ const tableData = change.after;
933
+ if (tableData?.columns) {
934
+ for (const col of tableData.columns) {
935
+ if (col.references?.table)
936
+ totalCount++;
937
+ }
938
+ }
939
+ if (tableData?.constraints) {
940
+ for (const con of tableData.constraints) {
941
+ if (con.definition && con.definition.startsWith('CHECK'))
942
+ totalCount++;
943
+ }
944
+ }
945
+ }
946
+ }
947
+ console.log(`Staged ${totalCount} change(s):`);
853
948
  console.log('');
854
949
  for (const change of stagedNow) {
855
950
  const display = (0, change_tracker_1.getChangeDisplayName)(change);
@@ -857,6 +952,26 @@ async function addCommand(context) {
857
952
  change.type === 'DROP' ? spinner_1.colors.red :
858
953
  spinner_1.colors.yellow;
859
954
  console.log(` ${color(display)}`);
955
+ if (change.type === 'CREATE' && change.objectType === 'TABLE') {
956
+ const tableData = change.after;
957
+ if (tableData?.columns) {
958
+ for (const col of tableData.columns) {
959
+ if (col.references?.table) {
960
+ let fkDisplay = `+ FK ${tableData.name}.${col.name} → ${col.references.table}`;
961
+ if (col.references.onDelete)
962
+ fkDisplay += ` ON DELETE ${col.references.onDelete.toUpperCase()}`;
963
+ console.log(` ${spinner_1.colors.green(fkDisplay)}`);
964
+ }
965
+ }
966
+ }
967
+ if (tableData?.constraints) {
968
+ for (const con of tableData.constraints) {
969
+ if (con.definition && con.definition.startsWith('CHECK')) {
970
+ console.log(` ${spinner_1.colors.green(`+ CHECK ${con.name} on ${tableData.name}`)}`);
971
+ }
972
+ }
973
+ }
974
+ }
860
975
  }
861
976
  console.log('');
862
977
  const remainingUnstaged = (0, repo_manager_1.getUnstagedChanges)(projectRoot);
@@ -192,7 +192,52 @@ async function pushCommand(context) {
192
192
  spinner.succeed('Types in sync');
193
193
  }
194
194
  }
195
- if (toPush.length === 0 && !hasObjectsToDrop) {
195
+ const pushedButNotApplied = await (0, repo_manager_1.getPushedButNotAppliedCommits)(connection, [...localHashes]);
196
+ if (pushedButNotApplied.length > 0 && !metadataOnly && !dryRun) {
197
+ spinner.start(`Recovering ${pushedButNotApplied.length} commit(s) with pending SQL...`);
198
+ const pg = await Promise.resolve().then(() => __importStar(require("../../addon/pg/index.cjs")));
199
+ const client = new pg.Client({
200
+ host: connection.host,
201
+ port: connection.port,
202
+ database: connection.database,
203
+ user: connection.user,
204
+ password: connection.password,
205
+ });
206
+ try {
207
+ await client.connect();
208
+ await client.query('BEGIN');
209
+ let sqlExecuted = 0;
210
+ for (const hash of pushedButNotApplied) {
211
+ const commitPath = path.join(projectRoot, '.relq', 'commits', `${hash}.json`);
212
+ if (fs.existsSync(commitPath)) {
213
+ const enhancedCommit = JSON.parse(fs.readFileSync(commitPath, 'utf-8'));
214
+ if (enhancedCommit.sql && enhancedCommit.sql.trim()) {
215
+ await client.query(enhancedCommit.sql);
216
+ sqlExecuted++;
217
+ }
218
+ }
219
+ }
220
+ await client.query('COMMIT');
221
+ spinner.succeed(`Applied pending SQL from ${sqlExecuted} commit(s)`);
222
+ for (const hash of pushedButNotApplied) {
223
+ await (0, repo_manager_1.markCommitAsApplied)(connection, hash);
224
+ }
225
+ }
226
+ catch (error) {
227
+ try {
228
+ await client.query('ROLLBACK');
229
+ spinner.fail('SQL recovery failed - rolled back');
230
+ }
231
+ catch {
232
+ spinner.fail('SQL recovery failed');
233
+ }
234
+ throw error;
235
+ }
236
+ finally {
237
+ await client.end();
238
+ }
239
+ }
240
+ if (toPush.length === 0 && !hasObjectsToDrop && pushedButNotApplied.length === 0) {
196
241
  if (!typesSynced) {
197
242
  console.log('Everything up-to-date');
198
243
  }
@@ -256,16 +301,10 @@ async function pushCommand(context) {
256
301
  console.log('');
257
302
  return;
258
303
  }
259
- if (toPush.length > 0) {
260
- const commitsToProcess = [...toPush].reverse();
261
- spinner.start(`Pushing ${toPush.length} commit(s)...`);
262
- for (const commit of commitsToProcess) {
263
- await (0, repo_manager_1.pushCommit)(connection, commit, projectRoot);
264
- (0, repo_manager_1.markCommitAsPushed)(commit.hash, connection, projectRoot);
265
- }
266
- spinner.succeed(`Pushed ${toPush.length} commit(s) to ${(0, repo_manager_1.getConnectionLabel)(connection)}`);
267
- }
268
- if (!metadataOnly && !dryRun) {
304
+ let sqlApplied = false;
305
+ let commitsToProcess = [];
306
+ if (!metadataOnly && !dryRun && (toPush.length > 0 || (hasObjectsToDrop && force))) {
307
+ commitsToProcess = [...toPush].reverse();
269
308
  spinner.start('Applying schema changes...');
270
309
  const pg = await Promise.resolve().then(() => __importStar(require("../../addon/pg/index.cjs")));
271
310
  const client = new pg.Client({
@@ -289,7 +328,6 @@ async function pushCommand(context) {
289
328
  }
290
329
  }
291
330
  }
292
- const commitsToProcess = [...toPush].reverse();
293
331
  for (const commit of commitsToProcess) {
294
332
  const commitPath = path.join(projectRoot, '.relq', 'commits', `${commit.hash}.json`);
295
333
  if (fs.existsSync(commitPath)) {
@@ -303,6 +341,7 @@ async function pushCommand(context) {
303
341
  }
304
342
  await client.query('COMMIT');
305
343
  spinner.succeed(`Applied ${statementsRun} statement(s) from ${sqlExecuted} commit(s)`);
344
+ sqlApplied = true;
306
345
  for (const commit of commitsToProcess) {
307
346
  await (0, repo_manager_1.markCommitAsApplied)(connection, commit.hash);
308
347
  }
@@ -336,7 +375,7 @@ async function pushCommand(context) {
336
375
  catch (error) {
337
376
  try {
338
377
  await client.query('ROLLBACK');
339
- spinner.fail('SQL execution failed - rolled back');
378
+ spinner.fail('SQL execution failed - rolled back. No commits pushed.');
340
379
  }
341
380
  catch {
342
381
  spinner.fail('SQL execution failed');
@@ -347,6 +386,16 @@ async function pushCommand(context) {
347
386
  await client.end();
348
387
  }
349
388
  }
389
+ if (toPush.length > 0) {
390
+ if (sqlApplied || metadataOnly || dryRun) {
391
+ spinner.start(`Pushing ${toPush.length} commit(s)...`);
392
+ for (const commit of commitsToProcess.length > 0 ? commitsToProcess : [...toPush].reverse()) {
393
+ await (0, repo_manager_1.pushCommit)(connection, commit, projectRoot);
394
+ (0, repo_manager_1.markCommitAsPushed)(commit.hash, connection, projectRoot);
395
+ }
396
+ spinner.succeed(`Pushed ${toPush.length} commit(s) to ${(0, repo_manager_1.getConnectionLabel)(connection)}`);
397
+ }
398
+ }
350
399
  const oldHash = remoteHead ? (0, repo_manager_1.shortHash)(remoteHead) : '(none)';
351
400
  const newHash = (0, repo_manager_1.shortHash)(localHead);
352
401
  console.log(` ${oldHash}..${newHash} ${cli_utils_1.colors.muted('main -> main')}`);
@@ -1021,41 +1021,42 @@ function generateRelationsCode(schema, camelCase) {
1021
1021
  }
1022
1022
  usedNames.add(finalRelationName);
1023
1023
  const options = [];
1024
- if (fk.constraintName) {
1025
- options.push(`name: '${fk.constraintName}'`);
1026
- }
1027
- const isValidColumn = fk.toColumn &&
1028
- fk.toColumn.length > 0 &&
1029
- /^[a-zA-Z_]/.test(fk.toColumn) &&
1030
- fk.toColumn.toLowerCase() !== 'id';
1031
- if (isValidColumn) {
1032
- options.push(`references: t.${toColName}`);
1033
- }
1034
- if (fk.onDelete && fk.onDelete !== 'NO ACTION') {
1035
- options.push(`onDelete: '${fk.onDelete}'`);
1036
- }
1037
- if (fk.onUpdate && fk.onUpdate !== 'NO ACTION') {
1038
- options.push(`onUpdate: '${fk.onUpdate}'`);
1039
- }
1040
- if (fk.match && fk.match !== 'SIMPLE') {
1041
- options.push(`match: '${fk.match}'`);
1042
- }
1043
- if (fk.deferrable) {
1044
- options.push(`deferrable: true`);
1045
- }
1046
- if (fk.initiallyDeferred) {
1047
- options.push(`initiallyDeferred: true`);
1048
- }
1049
1024
  if (fk.isComposite && fk.fromColumns && fk.toColumns) {
1050
- const fromCols = fk.fromColumns.map(c => `r.${fromTableName}.${camelCase ? (0, utils_1.toCamelCase)(c) : c}`).join(', ');
1025
+ const fromCols = fk.fromColumns.map(c => `'${camelCase ? (0, utils_1.toCamelCase)(c) : c}'`).join(', ');
1051
1026
  const toCols = fk.toColumns.map(c => `t.${camelCase ? (0, utils_1.toCamelCase)(c) : c}`).join(', ');
1052
- const hasName = fk.constraintName;
1053
- options.length = 0;
1054
- if (hasName) {
1055
- options.push(`name: '${fk.constraintName}'`);
1056
- }
1057
1027
  options.push(`columns: [${fromCols}]`);
1058
1028
  options.push(`references: [${toCols}]`);
1029
+ if (fk.constraintName) {
1030
+ options.push(`name: '${fk.constraintName}'`);
1031
+ }
1032
+ if (fk.onDelete && fk.onDelete !== 'NO ACTION') {
1033
+ options.push(`onDelete: '${fk.onDelete}'`);
1034
+ }
1035
+ if (fk.onUpdate && fk.onUpdate !== 'NO ACTION') {
1036
+ options.push(`onUpdate: '${fk.onUpdate}'`);
1037
+ }
1038
+ if (fk.match && fk.match !== 'SIMPLE') {
1039
+ options.push(`match: '${fk.match}'`);
1040
+ }
1041
+ if (fk.deferrable) {
1042
+ options.push(`deferrable: true`);
1043
+ }
1044
+ if (fk.initiallyDeferred) {
1045
+ options.push(`initiallyDeferred: true`);
1046
+ }
1047
+ }
1048
+ else {
1049
+ options.push(`columns: '${fromColName}'`);
1050
+ if (fk.constraintName) {
1051
+ options.push(`name: '${fk.constraintName}'`);
1052
+ }
1053
+ const isValidColumn = fk.toColumn &&
1054
+ fk.toColumn.length > 0 &&
1055
+ /^[a-zA-Z_]/.test(fk.toColumn) &&
1056
+ fk.toColumn.toLowerCase() !== 'id';
1057
+ if (isValidColumn) {
1058
+ options.push(`references: t.${toColName}`);
1059
+ }
1059
1060
  if (fk.onDelete && fk.onDelete !== 'NO ACTION') {
1060
1061
  options.push(`onDelete: '${fk.onDelete}'`);
1061
1062
  }
@@ -1078,16 +1079,10 @@ function generateRelationsCode(schema, camelCase) {
1078
1079
  if (sqlComment) {
1079
1080
  options.push(`comment: '${(0, utils_1.escapeString)(sqlComment)}'`);
1080
1081
  }
1081
- let code;
1082
- if (options.length === 0) {
1083
- code = `${finalRelationName}: r.referenceTo.${toTableName}(t => ({}))`;
1084
- }
1085
- else {
1086
- const optionsStr = options.join(',\n ');
1087
- code = `${finalRelationName}: r.referenceTo.${toTableName}(t => ({
1082
+ const optionsStr = options.join(',\n ');
1083
+ const code = `r.referenceTo.${toTableName}(t => ({
1088
1084
  ${optionsStr},
1089
1085
  }))`;
1090
- }
1091
1086
  if (!relationsByTable.has(fromTableName)) {
1092
1087
  relationsByTable.set(fromTableName, []);
1093
1088
  }
@@ -1111,16 +1106,16 @@ function generateRelationsCode(schema, camelCase) {
1111
1106
  const sortedTables = [...relationsByTable.keys()].sort();
1112
1107
  for (const tableName of sortedTables) {
1113
1108
  const tableRelations = relationsByTable.get(tableName);
1114
- lines.push(` ${tableName}: tables.${tableName}((r) => ({`);
1109
+ lines.push(` ${tableName}: tables.${tableName}((r) => [`);
1115
1110
  for (const rel of tableRelations) {
1116
1111
  lines.push(` ${rel.code},`);
1117
1112
  }
1118
- lines.push(' })),');
1113
+ lines.push(' ]),');
1119
1114
  }
1120
1115
  lines.push('}));');
1121
1116
  return lines.join('\n');
1122
1117
  }
1123
- function generateFKSQLComment(fk, camelCase) {
1118
+ function generateFKSQLComment(fk, _camelCase) {
1124
1119
  const parts = [];
1125
1120
  const isValidCol = (col) => col && col.length > 0 && /^[a-zA-Z_]/.test(col);
1126
1121
  if (fk.isComposite && fk.fromColumns) {
@@ -187,11 +187,20 @@ function generateTableSQL(change) {
187
187
  if (col.defaultValue)
188
188
  def += ` DEFAULT ${col.defaultValue}`;
189
189
  if (col.references) {
190
- def += ` REFERENCES "${col.references.table}"("${col.references.column}")`;
190
+ def += ` REFERENCES "${col.references.table}"`;
191
+ if (col.references.column) {
192
+ def += `("${col.references.column}")`;
193
+ }
194
+ if (col.references.onDelete)
195
+ def += ` ON DELETE ${col.references.onDelete.toUpperCase()}`;
196
+ if (col.references.onUpdate)
197
+ def += ` ON UPDATE ${col.references.onUpdate.toUpperCase()}`;
191
198
  }
192
199
  colDefs.push(def);
193
200
  }
194
201
  for (const con of data.constraints || []) {
202
+ if (!con.definition || !con.definition.trim())
203
+ continue;
195
204
  constraintDefs.push(` CONSTRAINT "${con.name}" ${con.definition}`);
196
205
  }
197
206
  const allDefs = [...colDefs, ...constraintDefs].join(',\n');
@@ -78,6 +78,7 @@ exports.hasUncommittedChanges = hasUncommittedChanges;
78
78
  exports.ensureRemoteTable = ensureRemoteTable;
79
79
  exports.markCommitAsApplied = markCommitAsApplied;
80
80
  exports.markCommitAsRolledBack = markCommitAsRolledBack;
81
+ exports.getPushedButNotAppliedCommits = getPushedButNotAppliedCommits;
81
82
  exports.fetchRemoteCommits = fetchRemoteCommits;
82
83
  exports.getRemoteHead = getRemoteHead;
83
84
  exports.pushCommit = pushCommit;
@@ -624,6 +625,33 @@ async function markCommitAsRolledBack(connection, commitHash) {
624
625
  await pool.end();
625
626
  }
626
627
  }
628
+ async function getPushedButNotAppliedCommits(connection, localHashes) {
629
+ if (localHashes.length === 0)
630
+ return [];
631
+ const { Pool } = await Promise.resolve().then(() => __importStar(require("../../addon/pg/index.cjs")));
632
+ const pool = new Pool({
633
+ host: connection.host,
634
+ port: connection.port || 5432,
635
+ database: connection.database,
636
+ user: connection.user,
637
+ password: connection.password,
638
+ connectionString: connection.url,
639
+ ssl: connection.ssl,
640
+ });
641
+ try {
642
+ const result = await pool.query(`
643
+ SELECT hash FROM _relq_commits
644
+ WHERE hash = ANY($1::varchar[])
645
+ AND applied_at IS NULL
646
+ AND (rolled_back_at IS NULL OR rolled_back_at < NOW() - INTERVAL '1 hour')
647
+ ORDER BY created_at ASC
648
+ `, [localHashes]);
649
+ return result.rows.map(row => row.hash);
650
+ }
651
+ finally {
652
+ await pool.end();
653
+ }
654
+ }
627
655
  async function fetchRemoteCommits(connection, limit = 100) {
628
656
  const { Pool } = await Promise.resolve().then(() => __importStar(require("../../addon/pg/index.cjs")));
629
657
  const pool = new Pool({
@@ -454,6 +454,9 @@ function compareConstraints(before, after, tableName) {
454
454
  const enumMatch = definition.match(/\((\w+)\)::text\s*=\s*ANY/i);
455
455
  if (enumMatch)
456
456
  return enumMatch[1].toLowerCase();
457
+ const inMatch = definition.match(/["\(](\w+)["]\s+IN\s*\(/i);
458
+ if (inMatch)
459
+ return inMatch[1].toLowerCase();
457
460
  const compMatch = definition.match(/\(\(?(\w+)\s*(?:>=?|<=?|<>|!=|=)/i);
458
461
  if (compMatch)
459
462
  return compMatch[1].toLowerCase();