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.
- package/dist/cjs/cli/commands/add.cjs +149 -34
- package/dist/cjs/cli/commands/push.cjs +62 -13
- package/dist/cjs/cli/utils/ast-codegen.cjs +37 -42
- package/dist/cjs/cli/utils/change-tracker.cjs +10 -1
- package/dist/cjs/cli/utils/repo-manager.cjs +28 -0
- package/dist/cjs/cli/utils/schema-comparator.cjs +3 -0
- package/dist/cjs/schema-definition/pg-relations.cjs +50 -48
- package/dist/esm/cli/commands/add.js +149 -34
- package/dist/esm/cli/commands/push.js +63 -14
- package/dist/esm/cli/utils/ast-codegen.js +37 -42
- package/dist/esm/cli/utils/change-tracker.js +10 -1
- package/dist/esm/cli/utils/repo-manager.js +27 -0
- package/dist/esm/cli/utils/schema-comparator.js +3 -0
- package/dist/esm/schema-definition/pg-relations.js +50 -48
- package/dist/index.d.ts +2 -2
- package/dist/schema-builder.d.ts +92 -275
- package/package.json +1 -1
|
@@ -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
|
|
182
|
-
if (
|
|
183
|
-
const
|
|
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
|
|
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*\
|
|
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 = /
|
|
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
|
|
467
|
-
const
|
|
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
|
-
|
|
472
|
-
|
|
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
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
const
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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 => `
|
|
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
|
-
|
|
1082
|
-
|
|
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,
|
|
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}"
|
|
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();
|