relq 1.0.5 → 1.0.6
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 +252 -12
- package/dist/cjs/cli/commands/commit.cjs +12 -1
- package/dist/cjs/cli/commands/export.cjs +25 -19
- package/dist/cjs/cli/commands/import.cjs +219 -100
- package/dist/cjs/cli/commands/init.cjs +86 -14
- package/dist/cjs/cli/commands/pull.cjs +104 -23
- package/dist/cjs/cli/commands/push.cjs +38 -3
- package/dist/cjs/cli/index.cjs +9 -1
- package/dist/cjs/cli/utils/ast/codegen/builder.cjs +297 -0
- package/dist/cjs/cli/utils/ast/codegen/constraints.cjs +185 -0
- package/dist/cjs/cli/utils/ast/codegen/defaults.cjs +311 -0
- package/dist/cjs/cli/utils/ast/codegen/index.cjs +24 -0
- package/dist/cjs/cli/utils/ast/codegen/type-map.cjs +116 -0
- package/dist/cjs/cli/utils/ast/codegen/utils.cjs +69 -0
- package/dist/cjs/cli/utils/ast/index.cjs +19 -0
- package/dist/cjs/cli/utils/ast/transformer/helpers.cjs +154 -0
- package/dist/cjs/cli/utils/ast/transformer/index.cjs +25 -0
- package/dist/cjs/cli/utils/ast/types.cjs +2 -0
- package/dist/cjs/cli/utils/ast-codegen.cjs +949 -0
- package/dist/cjs/cli/utils/ast-transformer.cjs +916 -0
- package/dist/cjs/cli/utils/change-tracker.cjs +50 -1
- package/dist/cjs/cli/utils/cli-utils.cjs +151 -0
- package/dist/cjs/cli/utils/fast-introspect.cjs +149 -23
- package/dist/cjs/cli/utils/pg-parser.cjs +1 -0
- package/dist/cjs/cli/utils/repo-manager.cjs +121 -4
- package/dist/cjs/cli/utils/schema-comparator.cjs +98 -14
- package/dist/cjs/cli/utils/schema-introspect.cjs +56 -19
- package/dist/cjs/cli/utils/snapshot-manager.cjs +0 -1
- package/dist/cjs/cli/utils/sql-generator.cjs +353 -64
- package/dist/cjs/cli/utils/type-generator.cjs +114 -15
- package/dist/cjs/core/relq-client.cjs +22 -6
- package/dist/cjs/schema-definition/column-types.cjs +149 -13
- package/dist/cjs/schema-definition/defaults.cjs +72 -0
- package/dist/cjs/schema-definition/index.cjs +15 -1
- package/dist/cjs/schema-definition/introspection.cjs +7 -3
- package/dist/cjs/schema-definition/pg-relations.cjs +169 -0
- package/dist/cjs/schema-definition/pg-view.cjs +30 -0
- package/dist/cjs/schema-definition/table-definition.cjs +110 -4
- package/dist/cjs/types/config-types.cjs +13 -4
- package/dist/cjs/utils/aws-dsql.cjs +177 -0
- package/dist/config.d.ts +146 -1
- package/dist/esm/cli/commands/add.js +250 -13
- package/dist/esm/cli/commands/commit.js +12 -1
- package/dist/esm/cli/commands/export.js +25 -19
- package/dist/esm/cli/commands/import.js +221 -102
- package/dist/esm/cli/commands/init.js +86 -14
- package/dist/esm/cli/commands/pull.js +106 -25
- package/dist/esm/cli/commands/push.js +39 -4
- package/dist/esm/cli/index.js +9 -1
- package/dist/esm/cli/utils/ast/codegen/builder.js +291 -0
- package/dist/esm/cli/utils/ast/codegen/constraints.js +176 -0
- package/dist/esm/cli/utils/ast/codegen/defaults.js +305 -0
- package/dist/esm/cli/utils/ast/codegen/index.js +6 -0
- package/dist/esm/cli/utils/ast/codegen/type-map.js +111 -0
- package/dist/esm/cli/utils/ast/codegen/utils.js +60 -0
- package/dist/esm/cli/utils/ast/index.js +3 -0
- package/dist/esm/cli/utils/ast/transformer/helpers.js +141 -0
- package/dist/esm/cli/utils/ast/transformer/index.js +2 -0
- package/dist/esm/cli/utils/ast/types.js +1 -0
- package/dist/esm/cli/utils/ast-codegen.js +945 -0
- package/dist/esm/cli/utils/ast-transformer.js +907 -0
- package/dist/esm/cli/utils/change-tracker.js +50 -1
- package/dist/esm/cli/utils/cli-utils.js +147 -0
- package/dist/esm/cli/utils/fast-introspect.js +149 -23
- package/dist/esm/cli/utils/pg-parser.js +1 -0
- package/dist/esm/cli/utils/repo-manager.js +114 -4
- package/dist/esm/cli/utils/schema-comparator.js +98 -14
- package/dist/esm/cli/utils/schema-introspect.js +56 -19
- package/dist/esm/cli/utils/snapshot-manager.js +0 -1
- package/dist/esm/cli/utils/sql-generator.js +353 -64
- package/dist/esm/cli/utils/type-generator.js +114 -15
- package/dist/esm/core/relq-client.js +23 -7
- package/dist/esm/schema-definition/column-types.js +146 -12
- package/dist/esm/schema-definition/defaults.js +69 -0
- package/dist/esm/schema-definition/index.js +3 -0
- package/dist/esm/schema-definition/introspection.js +7 -3
- package/dist/esm/schema-definition/pg-relations.js +161 -0
- package/dist/esm/schema-definition/pg-view.js +24 -0
- package/dist/esm/schema-definition/table-definition.js +110 -4
- package/dist/esm/types/config-types.js +12 -4
- package/dist/esm/utils/aws-dsql.js +139 -0
- package/dist/index.d.ts +159 -1
- package/dist/schema-builder.d.ts +1314 -32
- package/package.json +1 -1
|
@@ -38,7 +38,8 @@ const fs = __importStar(require("fs"));
|
|
|
38
38
|
const path = __importStar(require("path"));
|
|
39
39
|
const config_loader_1 = require("../utils/config-loader.cjs");
|
|
40
40
|
const fast_introspect_1 = require("../utils/fast-introspect.cjs");
|
|
41
|
-
const
|
|
41
|
+
const ast_transformer_1 = require("../utils/ast-transformer.cjs");
|
|
42
|
+
const ast_codegen_1 = require("../utils/ast-codegen.cjs");
|
|
42
43
|
const env_loader_1 = require("../utils/env-loader.cjs");
|
|
43
44
|
const cli_utils_1 = require("../utils/cli-utils.cjs");
|
|
44
45
|
const relqignore_1 = require("../utils/relqignore.cjs");
|
|
@@ -181,8 +182,8 @@ async function pullCommand(context) {
|
|
|
181
182
|
const author = config.author || 'Relq CLI';
|
|
182
183
|
const schemaPathRaw = typeof config.schema === 'string' ? config.schema : './db/schema.ts';
|
|
183
184
|
const schemaPath = path.resolve(projectRoot, schemaPathRaw);
|
|
184
|
-
const includeFunctions = config.
|
|
185
|
-
const includeTriggers = config.
|
|
185
|
+
const includeFunctions = config.includeFunctions ?? true;
|
|
186
|
+
const includeTriggers = config.includeTriggers ?? true;
|
|
186
187
|
const spinner = (0, cli_utils_1.createSpinner)();
|
|
187
188
|
const startTime = Date.now();
|
|
188
189
|
console.log('');
|
|
@@ -238,12 +239,36 @@ async function pullCommand(context) {
|
|
|
238
239
|
(0, repo_manager_1.setFetchHead)(remoteHead, projectRoot);
|
|
239
240
|
}
|
|
240
241
|
spinner.succeed(`Fetched ${remoteCommits.length} remote commits`);
|
|
241
|
-
|
|
242
|
+
console.log('');
|
|
243
|
+
console.log(cli_utils_1.colors.bold('Introspecting database...'));
|
|
244
|
+
console.log('');
|
|
245
|
+
const progress = (0, cli_utils_1.createMultiProgress)();
|
|
246
|
+
const progressItems = [
|
|
247
|
+
{ id: 'tables', label: 'tables', status: 'pending', count: 0 },
|
|
248
|
+
{ id: 'columns', label: 'columns', status: 'pending', count: 0 },
|
|
249
|
+
{ id: 'constraints', label: 'constraints', status: 'pending', count: 0 },
|
|
250
|
+
{ id: 'indexes', label: 'indexes', status: 'pending', count: 0 },
|
|
251
|
+
{ id: 'checks', label: 'checks', status: 'pending', count: 0 },
|
|
252
|
+
{ id: 'enums', label: 'enums', status: 'pending', count: 0 },
|
|
253
|
+
{ id: 'partitions', label: 'partitions', status: 'pending', count: 0 },
|
|
254
|
+
{ id: 'extensions', label: 'extensions', status: 'pending', count: 0 },
|
|
255
|
+
...(includeFunctions ? [{ id: 'functions', label: 'functions', status: 'pending', count: 0 }] : []),
|
|
256
|
+
...(includeTriggers ? [{ id: 'triggers', label: 'triggers', status: 'pending', count: 0 }] : []),
|
|
257
|
+
{ id: 'collations', label: 'collations', status: 'pending', count: 0 },
|
|
258
|
+
{ id: 'foreign_servers', label: 'foreign servers', status: 'pending', count: 0 },
|
|
259
|
+
{ id: 'foreign_tables', label: 'foreign tables', status: 'pending', count: 0 },
|
|
260
|
+
];
|
|
261
|
+
progress.setItems(progressItems);
|
|
262
|
+
progress.start();
|
|
242
263
|
const dbSchema = await (0, fast_introspect_1.fastIntrospectDatabase)(connection, undefined, {
|
|
243
264
|
includeFunctions,
|
|
244
265
|
includeTriggers,
|
|
266
|
+
onDetailedProgress: (update) => {
|
|
267
|
+
progress.updateItem(update.step, { status: update.status, count: update.count });
|
|
268
|
+
},
|
|
245
269
|
});
|
|
246
|
-
|
|
270
|
+
progress.complete();
|
|
271
|
+
console.log('');
|
|
247
272
|
const ignorePatterns = (0, relqignore_1.loadRelqignore)(projectRoot);
|
|
248
273
|
const filteredTables = dbSchema.tables
|
|
249
274
|
.filter(t => !(0, relqignore_1.isTableIgnored)(t.name, ignorePatterns).ignored)
|
|
@@ -277,12 +302,21 @@ async function pullCommand(context) {
|
|
|
277
302
|
default: c.defaultValue,
|
|
278
303
|
primaryKey: c.isPrimaryKey,
|
|
279
304
|
unique: c.isUnique,
|
|
305
|
+
maxLength: c.maxLength ?? null,
|
|
306
|
+
precision: c.precision ?? null,
|
|
307
|
+
scale: c.scale ?? null,
|
|
308
|
+
comment: c.comment || undefined,
|
|
309
|
+
isGenerated: c.isGenerated || false,
|
|
310
|
+
generationExpression: c.generationExpression ?? null,
|
|
280
311
|
})),
|
|
281
312
|
indexes: t.indexes.map(i => ({
|
|
282
313
|
name: i.name,
|
|
283
314
|
columns: i.columns,
|
|
284
315
|
unique: i.isUnique,
|
|
285
316
|
type: i.type,
|
|
317
|
+
definition: i.definition || undefined,
|
|
318
|
+
whereClause: i.whereClause || undefined,
|
|
319
|
+
comment: i.comment || undefined,
|
|
286
320
|
})),
|
|
287
321
|
constraints: t.constraints.map(c => ({
|
|
288
322
|
name: c.name,
|
|
@@ -292,6 +326,12 @@ async function pullCommand(context) {
|
|
|
292
326
|
isPartitioned: t.isPartitioned,
|
|
293
327
|
partitionType: t.partitionType,
|
|
294
328
|
partitionKey: normalizePartitionKey(t.partitionKey),
|
|
329
|
+
comment: t.comment || undefined,
|
|
330
|
+
partitions: t.childPartitions?.map((p) => ({
|
|
331
|
+
name: p.name,
|
|
332
|
+
bound: p.partitionBound || '',
|
|
333
|
+
boundType: p.partitionType,
|
|
334
|
+
})) || undefined,
|
|
295
335
|
})),
|
|
296
336
|
enums: filteredEnums.map(e => ({
|
|
297
337
|
name: e.name,
|
|
@@ -395,24 +435,43 @@ async function pullCommand(context) {
|
|
|
395
435
|
}
|
|
396
436
|
}
|
|
397
437
|
else if (!schemaExists) {
|
|
398
|
-
console.log('
|
|
438
|
+
console.log(cli_utils_1.colors.bold('Creating schema') + cli_utils_1.colors.dim(' (first pull)'));
|
|
399
439
|
console.log('');
|
|
440
|
+
const tableCount = filteredTables.length;
|
|
441
|
+
const columnCount = filteredTables.reduce((sum, t) => sum + t.columns.length, 0);
|
|
400
442
|
const indexCount = filteredTables.reduce((sum, t) => sum + (t.indexes?.filter(i => !i.isPrimary).length || 0), 0);
|
|
401
|
-
const
|
|
443
|
+
const partitionedCount = filteredTables.filter(t => t.isPartitioned).length;
|
|
444
|
+
const childPartitionCount = filteredTables.reduce((sum, t) => sum + (t.childPartitions?.length || 0), 0);
|
|
402
445
|
const tableCommentCount = filteredTables.filter(t => t.comment).length;
|
|
403
446
|
const columnCommentCount = filteredTables.reduce((sum, t) => sum + t.columns.filter(c => c.comment).length, 0);
|
|
404
|
-
|
|
405
|
-
|
|
447
|
+
const indexCommentCount = filteredTables.reduce((sum, t) => sum + t.indexes.filter(i => i.comment).length, 0);
|
|
448
|
+
const summaryItems = [];
|
|
449
|
+
summaryItems.push({ count: tableCount, label: 'tables' });
|
|
450
|
+
summaryItems.push({ count: columnCount, label: 'columns' });
|
|
406
451
|
if (indexCount > 0)
|
|
407
|
-
|
|
408
|
-
if (
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
452
|
+
summaryItems.push({ count: indexCount, label: 'indexes' });
|
|
453
|
+
if (partitionedCount > 0) {
|
|
454
|
+
const partLabel = childPartitionCount > 0
|
|
455
|
+
? `partitioned (${childPartitionCount} child partitions)`
|
|
456
|
+
: 'partitioned';
|
|
457
|
+
summaryItems.push({ count: partitionedCount, label: partLabel });
|
|
458
|
+
}
|
|
459
|
+
if (tableCommentCount > 0 || columnCommentCount > 0 || indexCommentCount > 0) {
|
|
460
|
+
const commentParts = [];
|
|
461
|
+
if (tableCommentCount > 0)
|
|
462
|
+
commentParts.push(`${tableCommentCount} table`);
|
|
463
|
+
if (columnCommentCount > 0)
|
|
464
|
+
commentParts.push(`${columnCommentCount} column`);
|
|
465
|
+
if (indexCommentCount > 0)
|
|
466
|
+
commentParts.push(`${indexCommentCount} index`);
|
|
467
|
+
summaryItems.push({ count: tableCommentCount + columnCommentCount + indexCommentCount, label: `comments (${commentParts.join(', ')})` });
|
|
468
|
+
}
|
|
414
469
|
if (dbSchema.extensions.length > 0)
|
|
415
|
-
|
|
470
|
+
summaryItems.push({ count: dbSchema.extensions.length, label: 'extensions' });
|
|
471
|
+
for (const item of summaryItems) {
|
|
472
|
+
const countStr = String(item.count).padStart(4);
|
|
473
|
+
console.log(`${cli_utils_1.colors.green('[+]')} ${cli_utils_1.colors.green(countStr)} ${item.label}`);
|
|
474
|
+
}
|
|
416
475
|
console.log('');
|
|
417
476
|
}
|
|
418
477
|
if (dryRun) {
|
|
@@ -421,13 +480,10 @@ async function pullCommand(context) {
|
|
|
421
480
|
return;
|
|
422
481
|
}
|
|
423
482
|
spinner.start('Generating TypeScript schema...');
|
|
424
|
-
const
|
|
425
|
-
|
|
426
|
-
includeSchema: true,
|
|
427
|
-
includeIndexes: true,
|
|
428
|
-
includeFunctions,
|
|
429
|
-
includeTriggers,
|
|
483
|
+
const parsedSchema = await (0, ast_transformer_1.introspectedToParsedSchema)(dbSchema);
|
|
484
|
+
const typescript = (0, ast_codegen_1.generateTypeScriptFromAST)(parsedSchema, {
|
|
430
485
|
camelCase: config.generate?.camelCase ?? true,
|
|
486
|
+
importPath: 'relq/schema-builder',
|
|
431
487
|
});
|
|
432
488
|
spinner.succeed('Generated TypeScript schema');
|
|
433
489
|
const schemaDir = path.dirname(schemaPath);
|
|
@@ -530,6 +586,7 @@ async function pullCommand(context) {
|
|
|
530
586
|
triggers: filteredTriggers || [],
|
|
531
587
|
};
|
|
532
588
|
const schemaChanges = (0, schema_comparator_1.compareSchemas)(beforeSchema, afterSchema);
|
|
589
|
+
applyTrackingIdsToSnapshot(typescript, currentSchema);
|
|
533
590
|
(0, repo_manager_1.saveSnapshot)(currentSchema, projectRoot);
|
|
534
591
|
const duration = Date.now() - startTime;
|
|
535
592
|
if (noCommit) {
|
|
@@ -552,6 +609,7 @@ async function pullCommand(context) {
|
|
|
552
609
|
const connectionDesc = (0, env_loader_1.getConnectionDescription)(connection);
|
|
553
610
|
const commitMessage = `pull: sync from ${connectionDesc}`;
|
|
554
611
|
const commit = (0, repo_manager_1.createCommit)(currentSchema, author, commitMessage, projectRoot);
|
|
612
|
+
(0, repo_manager_1.markCommitAsPulled)(commit.hash, connection, projectRoot);
|
|
555
613
|
(0, repo_manager_1.clearWorkingState)(projectRoot);
|
|
556
614
|
console.log('');
|
|
557
615
|
console.log(`Pull completed in ${(0, cli_utils_1.formatDuration)(duration)}`);
|
|
@@ -634,3 +692,26 @@ function detectObjectConflicts(local, remote) {
|
|
|
634
692
|
}
|
|
635
693
|
return conflicts;
|
|
636
694
|
}
|
|
695
|
+
function applyTrackingIdsToSnapshot(typescript, snapshot) {
|
|
696
|
+
for (const table of snapshot.tables) {
|
|
697
|
+
const tablePattern = new RegExp(`defineTable\\s*\\(\\s*['"]${table.name}['"]\\s*,\\s*\\{[^}]+\\}\\s*,\\s*\\{[^}]*\\$trackingId:\\s*['"]([^'"]+)['"]`, 's');
|
|
698
|
+
const tableMatch = typescript.match(tablePattern);
|
|
699
|
+
if (tableMatch) {
|
|
700
|
+
table.trackingId = tableMatch[1];
|
|
701
|
+
}
|
|
702
|
+
for (const col of table.columns) {
|
|
703
|
+
const colPattern = new RegExp(`(?:${col.tsName}|${col.name}):\\s*\\w+\\([^)]*\\)[^\\n]*\\.\\\$id\\(['"]([^'"]+)['"]\\)`);
|
|
704
|
+
const colMatch = typescript.match(colPattern);
|
|
705
|
+
if (colMatch) {
|
|
706
|
+
col.trackingId = colMatch[1];
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
for (const idx of table.indexes) {
|
|
710
|
+
const idxPattern = new RegExp(`index\\s*\\(\\s*['"]${idx.name}['"]\\s*\\)[^\\n]*\\.\\\$id\\(['"]([^'"]+)['"]\\)`);
|
|
711
|
+
const idxMatch = typescript.match(idxPattern);
|
|
712
|
+
if (idxMatch) {
|
|
713
|
+
idx.trackingId = idxMatch[1];
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
}
|
|
@@ -77,7 +77,15 @@ async function pushCommand(context) {
|
|
|
77
77
|
const localCommits = (0, repo_manager_1.getAllCommits)(projectRoot);
|
|
78
78
|
const remoteHashes = new Set(remoteCommits.map(c => c.hash));
|
|
79
79
|
const localHashes = new Set(localCommits.map(c => c.hash));
|
|
80
|
-
const toPush = localCommits.filter(c =>
|
|
80
|
+
const toPush = localCommits.filter(c => {
|
|
81
|
+
if (remoteHashes.has(c.hash))
|
|
82
|
+
return false;
|
|
83
|
+
const syncStatus = (0, repo_manager_1.isCommitSyncedWith)(c, connection);
|
|
84
|
+
if (syncStatus.synced) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
return true;
|
|
88
|
+
});
|
|
81
89
|
const remoteMissing = remoteCommits.filter(c => !localHashes.has(c.hash));
|
|
82
90
|
spinner.succeed('Checked remote commits');
|
|
83
91
|
spinner.start('Introspecting remote database...');
|
|
@@ -167,9 +175,10 @@ async function pushCommand(context) {
|
|
|
167
175
|
const commitsToProcess = [...toPush].reverse();
|
|
168
176
|
spinner.start(`Pushing ${toPush.length} commit(s)...`);
|
|
169
177
|
for (const commit of commitsToProcess) {
|
|
170
|
-
await (0, repo_manager_1.pushCommit)(connection, commit);
|
|
178
|
+
await (0, repo_manager_1.pushCommit)(connection, commit, projectRoot);
|
|
179
|
+
(0, repo_manager_1.markCommitAsPushed)(commit.hash, connection, projectRoot);
|
|
171
180
|
}
|
|
172
|
-
spinner.succeed(`Pushed ${toPush.length} commit(s)`);
|
|
181
|
+
spinner.succeed(`Pushed ${toPush.length} commit(s) to ${(0, repo_manager_1.getConnectionLabel)(connection)}`);
|
|
173
182
|
}
|
|
174
183
|
if (applySQL) {
|
|
175
184
|
spinner.start('Applying SQL changes...');
|
|
@@ -209,6 +218,32 @@ async function pushCommand(context) {
|
|
|
209
218
|
}
|
|
210
219
|
await client.query('COMMIT');
|
|
211
220
|
spinner.succeed(`Applied ${statementsRun} statement(s) from ${sqlExecuted} commit(s)`);
|
|
221
|
+
let hasRenameOperations = false;
|
|
222
|
+
for (const commit of commitsToProcess) {
|
|
223
|
+
const commitPath = path.join(projectRoot, '.relq', 'commits', `${commit.hash}.json`);
|
|
224
|
+
if (fs.existsSync(commitPath)) {
|
|
225
|
+
const enhancedCommit = JSON.parse(fs.readFileSync(commitPath, 'utf-8'));
|
|
226
|
+
if (enhancedCommit.changes?.some((c) => c.type === 'RENAME')) {
|
|
227
|
+
hasRenameOperations = true;
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
if (hasRenameOperations) {
|
|
233
|
+
spinner.start('Updating snapshot after RENAME...');
|
|
234
|
+
try {
|
|
235
|
+
const updatedSchema = await (0, fast_introspect_1.fastIntrospectDatabase)(connection, undefined, {
|
|
236
|
+
includeFunctions,
|
|
237
|
+
includeTriggers,
|
|
238
|
+
});
|
|
239
|
+
const snapshotPath = path.join(projectRoot, '.relq', 'snapshot.json');
|
|
240
|
+
fs.writeFileSync(snapshotPath, JSON.stringify(updatedSchema, null, 2));
|
|
241
|
+
spinner.succeed('Snapshot updated with renamed objects');
|
|
242
|
+
}
|
|
243
|
+
catch (e) {
|
|
244
|
+
spinner.warn('Could not update snapshot after RENAME - run "relq pull" manually');
|
|
245
|
+
}
|
|
246
|
+
}
|
|
212
247
|
}
|
|
213
248
|
catch (error) {
|
|
214
249
|
try {
|
package/dist/cjs/cli/index.cjs
CHANGED
|
@@ -98,7 +98,14 @@ function parseArgs(argv) {
|
|
|
98
98
|
flags[arg.slice(2, eqIndex)] = arg.slice(eqIndex + 1);
|
|
99
99
|
}
|
|
100
100
|
else {
|
|
101
|
-
|
|
101
|
+
const nextArg = argv[i + 1];
|
|
102
|
+
if (nextArg && !nextArg.startsWith('-')) {
|
|
103
|
+
flags[arg.slice(2)] = nextArg;
|
|
104
|
+
i++;
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
flags[arg.slice(2)] = true;
|
|
108
|
+
}
|
|
102
109
|
}
|
|
103
110
|
}
|
|
104
111
|
else if (arg.startsWith('-') && arg.length === 2) {
|
|
@@ -211,6 +218,7 @@ async function main() {
|
|
|
211
218
|
printHelp();
|
|
212
219
|
process.exit(1);
|
|
213
220
|
}
|
|
221
|
+
console.log('Command:', command);
|
|
214
222
|
let config = null;
|
|
215
223
|
let resolvedProjectRoot = process.cwd();
|
|
216
224
|
if (requiresConfig(command)) {
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.KNOWN_BUILDER_FUNCTIONS = void 0;
|
|
4
|
+
exports.mapFunctionToBuilder = mapFunctionToBuilder;
|
|
5
|
+
exports.astToBuilder = astToBuilder;
|
|
6
|
+
exports.formatGeneratedExpression = formatGeneratedExpression;
|
|
7
|
+
const utils_1 = require("./utils.cjs");
|
|
8
|
+
exports.KNOWN_BUILDER_FUNCTIONS = {
|
|
9
|
+
'setweight': 'setweight',
|
|
10
|
+
'to_tsvector': 'toTsvector',
|
|
11
|
+
'to_tsquery': 'toTsquery',
|
|
12
|
+
'plainto_tsquery': 'plainToTsquery',
|
|
13
|
+
'phraseto_tsquery': 'phraseToTsquery',
|
|
14
|
+
'websearch_to_tsquery': 'websearchToTsquery',
|
|
15
|
+
'ts_headline': 'tsHeadline',
|
|
16
|
+
'ts_rank': 'tsRank',
|
|
17
|
+
'ts_rank_cd': 'tsRankCd',
|
|
18
|
+
'coalesce': 'coalesce',
|
|
19
|
+
'nullif': 'nullif',
|
|
20
|
+
'greatest': 'greatest',
|
|
21
|
+
'least': 'least',
|
|
22
|
+
'concat': 'concat',
|
|
23
|
+
'concat_ws': 'concatWs',
|
|
24
|
+
'lower': 'lower',
|
|
25
|
+
'upper': 'upper',
|
|
26
|
+
'trim': 'trim',
|
|
27
|
+
'ltrim': 'ltrim',
|
|
28
|
+
'rtrim': 'rtrim',
|
|
29
|
+
'length': 'length',
|
|
30
|
+
'substring': 'substring',
|
|
31
|
+
'replace': 'replace',
|
|
32
|
+
'regexp_replace': 'regexpReplace',
|
|
33
|
+
'split_part': 'splitPart',
|
|
34
|
+
'now': 'now',
|
|
35
|
+
'current_timestamp': 'currentTimestamp',
|
|
36
|
+
'current_date': 'currentDate',
|
|
37
|
+
'current_time': 'currentTime',
|
|
38
|
+
'date_trunc': 'dateTrunc',
|
|
39
|
+
'date_part': 'datePart',
|
|
40
|
+
'extract': 'extract',
|
|
41
|
+
'age': 'age',
|
|
42
|
+
'gen_random_uuid': 'genRandomUuid',
|
|
43
|
+
'uuid_generate_v4': 'uuidGenerateV4',
|
|
44
|
+
'jsonb_build_object': 'jsonbBuildObject',
|
|
45
|
+
'jsonb_agg': 'jsonbAgg',
|
|
46
|
+
'json_agg': 'jsonAgg',
|
|
47
|
+
'array_agg': 'arrayAgg',
|
|
48
|
+
'string_agg': 'stringAgg',
|
|
49
|
+
'count': 'count',
|
|
50
|
+
'sum': 'sum',
|
|
51
|
+
'avg': 'avg',
|
|
52
|
+
'min': 'min',
|
|
53
|
+
'max': 'max',
|
|
54
|
+
'abs': 'abs',
|
|
55
|
+
'ceil': 'ceil',
|
|
56
|
+
'floor': 'floor',
|
|
57
|
+
'round': 'round',
|
|
58
|
+
'sqrt': 'sqrt',
|
|
59
|
+
'power': 'power',
|
|
60
|
+
'mod': 'mod',
|
|
61
|
+
'random': 'random',
|
|
62
|
+
};
|
|
63
|
+
function mapFunctionToBuilder(funcName) {
|
|
64
|
+
return exports.KNOWN_BUILDER_FUNCTIONS[funcName] || null;
|
|
65
|
+
}
|
|
66
|
+
function astToBuilder(node, prefixOrOptions = 'g') {
|
|
67
|
+
const opts = typeof prefixOrOptions === 'string'
|
|
68
|
+
? { prefix: prefixOrOptions }
|
|
69
|
+
: prefixOrOptions;
|
|
70
|
+
const { prefix = 'g', useCamelCase = false, useTableRef = false } = opts;
|
|
71
|
+
if (!node)
|
|
72
|
+
return "''";
|
|
73
|
+
if (node.FuncCall) {
|
|
74
|
+
const func = node.FuncCall;
|
|
75
|
+
const funcName = func.funcname?.map((n) => n.String?.sval).filter(Boolean).join('.') || '';
|
|
76
|
+
const args = (func.args || []).map((a) => astToBuilder(a, opts));
|
|
77
|
+
const builderMethod = mapFunctionToBuilder(funcName.toLowerCase());
|
|
78
|
+
if (builderMethod) {
|
|
79
|
+
return `${prefix}.${builderMethod}(${args.join(', ')})`;
|
|
80
|
+
}
|
|
81
|
+
throw new Error(`Unsupported function in generated expression: "${funcName}". Add this function to KNOWN_BUILDER_FUNCTIONS in astToBuilder.`);
|
|
82
|
+
}
|
|
83
|
+
if (node.CoalesceExpr) {
|
|
84
|
+
const args = (node.CoalesceExpr.args || []).map((a) => astToBuilder(a, opts));
|
|
85
|
+
return `${prefix}.coalesce(${args.join(', ')})`;
|
|
86
|
+
}
|
|
87
|
+
if (node.A_Expr) {
|
|
88
|
+
const expr = node.A_Expr;
|
|
89
|
+
const op = expr.name?.[0]?.String?.sval || '';
|
|
90
|
+
const left = astToBuilder(expr.lexpr, opts);
|
|
91
|
+
const right = astToBuilder(expr.rexpr, opts);
|
|
92
|
+
switch (op) {
|
|
93
|
+
case '||':
|
|
94
|
+
return `__CONCAT__[${left}, ${right}]`;
|
|
95
|
+
case '->>':
|
|
96
|
+
return `${prefix}.jsonbExtractText(${left}, ${right})`;
|
|
97
|
+
case '->':
|
|
98
|
+
return `${prefix}.jsonbExtract(${left}, ${right})`;
|
|
99
|
+
case '@@':
|
|
100
|
+
return `${prefix}.tsMatch(${left}, ${right})`;
|
|
101
|
+
case '+':
|
|
102
|
+
return `${prefix}.add(${left}, ${right})`;
|
|
103
|
+
case '-':
|
|
104
|
+
return `${prefix}.subtract(${left}, ${right})`;
|
|
105
|
+
case '*':
|
|
106
|
+
return `${prefix}.multiply(${left}, ${right})`;
|
|
107
|
+
case '/':
|
|
108
|
+
return `${prefix}.divide(${left}, ${right})`;
|
|
109
|
+
case '%':
|
|
110
|
+
return `${prefix}.mod(${left}, ${right})`;
|
|
111
|
+
case '=':
|
|
112
|
+
case '<>':
|
|
113
|
+
case '!=':
|
|
114
|
+
case '<':
|
|
115
|
+
case '>':
|
|
116
|
+
case '<=':
|
|
117
|
+
case '>=':
|
|
118
|
+
if (useTableRef) {
|
|
119
|
+
const methodMap = {
|
|
120
|
+
'=': 'eq', '<>': 'ne', '!=': 'neq',
|
|
121
|
+
'<': 'lt', '>': 'gt', '<=': 'lte', '>=': 'gte'
|
|
122
|
+
};
|
|
123
|
+
return `${left}.${methodMap[op]}(${right})`;
|
|
124
|
+
}
|
|
125
|
+
return `${prefix}.compare(${left}, '${op}', ${right})`;
|
|
126
|
+
case '~':
|
|
127
|
+
case '~*':
|
|
128
|
+
case '!~':
|
|
129
|
+
case '!~*':
|
|
130
|
+
return `${prefix}.regex(${left}, '${op}', ${right})`;
|
|
131
|
+
default:
|
|
132
|
+
throw new Error(`Unsupported operator in generated expression: "${op}". Add explicit handling for this operator in astToBuilder.`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (node.ColumnRef) {
|
|
136
|
+
const fields = (node.ColumnRef.fields || [])
|
|
137
|
+
.map((f) => f.String?.sval)
|
|
138
|
+
.filter(Boolean);
|
|
139
|
+
if (fields.length === 1) {
|
|
140
|
+
const colName = useCamelCase ? (0, utils_1.toCamelCase)(fields[0]) : fields[0];
|
|
141
|
+
return `table.${colName}`;
|
|
142
|
+
}
|
|
143
|
+
else if (fields.length === 2) {
|
|
144
|
+
const colName = useCamelCase ? (0, utils_1.toCamelCase)(fields[1]) : fields[1];
|
|
145
|
+
return `table.${colName}`;
|
|
146
|
+
}
|
|
147
|
+
const colName = useCamelCase ? (0, utils_1.toCamelCase)(fields[fields.length - 1]) : fields[fields.length - 1];
|
|
148
|
+
return `table.${colName}`;
|
|
149
|
+
}
|
|
150
|
+
if (node.A_Const) {
|
|
151
|
+
const val = node.A_Const;
|
|
152
|
+
if (val.sval !== undefined) {
|
|
153
|
+
const s = val.sval?.sval ?? val.sval;
|
|
154
|
+
if (s === '' || s === ' ') {
|
|
155
|
+
return s === '' ? `${prefix}.asText()` : `${prefix}.asText(' ')`;
|
|
156
|
+
}
|
|
157
|
+
return `'${(0, utils_1.escapeString)(String(s))}'`;
|
|
158
|
+
}
|
|
159
|
+
else if (val.ival !== undefined) {
|
|
160
|
+
const i = val.ival?.ival ?? val.ival;
|
|
161
|
+
return String(i);
|
|
162
|
+
}
|
|
163
|
+
else if (val.fval !== undefined) {
|
|
164
|
+
const f = val.fval?.fval ?? val.fval;
|
|
165
|
+
return String(f);
|
|
166
|
+
}
|
|
167
|
+
else if (val.boolval !== undefined) {
|
|
168
|
+
return val.boolval?.boolval ? 'true' : 'false';
|
|
169
|
+
}
|
|
170
|
+
return `${prefix}.asText()`;
|
|
171
|
+
}
|
|
172
|
+
if (node.TypeCast) {
|
|
173
|
+
const arg = astToBuilder(node.TypeCast.arg, opts);
|
|
174
|
+
const typeName = node.TypeCast.typeName?.names
|
|
175
|
+
?.map((n) => n.String?.sval)
|
|
176
|
+
.filter(Boolean)
|
|
177
|
+
.join('.') || '';
|
|
178
|
+
if (typeName === 'regconfig' || typeName === 'pg_catalog.regconfig') {
|
|
179
|
+
return arg;
|
|
180
|
+
}
|
|
181
|
+
if (typeName === 'text' || typeName === 'pg_catalog.text') {
|
|
182
|
+
return `${prefix}.asText(${arg})`;
|
|
183
|
+
}
|
|
184
|
+
if (typeName === 'varchar' || typeName === 'pg_catalog.varchar' || typeName.includes('character varying')) {
|
|
185
|
+
return arg;
|
|
186
|
+
}
|
|
187
|
+
if (typeName === 'char' || typeName === 'bpchar' || typeName === 'pg_catalog.bpchar') {
|
|
188
|
+
return arg;
|
|
189
|
+
}
|
|
190
|
+
if (typeName === 'numeric' || typeName === 'pg_catalog.numeric') {
|
|
191
|
+
return arg;
|
|
192
|
+
}
|
|
193
|
+
if (typeName === 'int4' || typeName === 'integer' || typeName === 'pg_catalog.int4') {
|
|
194
|
+
return arg;
|
|
195
|
+
}
|
|
196
|
+
return `${prefix}.cast(${arg}, '${typeName}')`;
|
|
197
|
+
}
|
|
198
|
+
if (node.NullTest) {
|
|
199
|
+
const arg = astToBuilder(node.NullTest.arg, opts);
|
|
200
|
+
const isNull = node.NullTest.nulltesttype === 'IS_NULL';
|
|
201
|
+
return isNull ? `${prefix}.isNull(${arg})` : `${prefix}.isNotNull(${arg})`;
|
|
202
|
+
}
|
|
203
|
+
if (node.BoolExpr) {
|
|
204
|
+
const boolexpr = node.BoolExpr;
|
|
205
|
+
const args = (boolexpr.args || []).map((a) => astToBuilder(a, opts));
|
|
206
|
+
if (useTableRef && args.length >= 2) {
|
|
207
|
+
switch (boolexpr.boolop) {
|
|
208
|
+
case 'AND_EXPR': return args.reduce((acc, arg) => `${acc}.and(${arg})`);
|
|
209
|
+
case 'OR_EXPR': return args.reduce((acc, arg) => `${acc}.or(${arg})`);
|
|
210
|
+
case 'NOT_EXPR': return `${args[0]}.not()`;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
switch (boolexpr.boolop) {
|
|
214
|
+
case 'AND_EXPR': return `${prefix}.and(${args.join(', ')})`;
|
|
215
|
+
case 'OR_EXPR': return `${prefix}.or(${args.join(', ')})`;
|
|
216
|
+
case 'NOT_EXPR': return `${prefix}.not(${args[0]})`;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
if (node.CaseExpr) {
|
|
220
|
+
const caseExpr = node.CaseExpr;
|
|
221
|
+
let chain = `${prefix}.case()`;
|
|
222
|
+
for (const w of (caseExpr.args || [])) {
|
|
223
|
+
if (w.CaseWhen) {
|
|
224
|
+
const condition = astToBuilder(w.CaseWhen.expr, opts);
|
|
225
|
+
const result = astToBuilder(w.CaseWhen.result, opts);
|
|
226
|
+
chain += `.when(${condition}, ${result})`;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
if (caseExpr.defresult) {
|
|
230
|
+
const defResult = astToBuilder(caseExpr.defresult, opts);
|
|
231
|
+
chain += `.else(${defResult})`;
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
chain += '.end()';
|
|
235
|
+
}
|
|
236
|
+
return chain;
|
|
237
|
+
}
|
|
238
|
+
const nodeType = Object.keys(node)[0];
|
|
239
|
+
throw new Error(`Unsupported AST node type in generated expression: "${nodeType}". Add explicit handling for this node type in astToBuilder.`);
|
|
240
|
+
}
|
|
241
|
+
function extractConcatItems(expr) {
|
|
242
|
+
if (expr.startsWith('__CONCAT__[')) {
|
|
243
|
+
const inner = expr.slice('__CONCAT__['.length, -1);
|
|
244
|
+
const items = [];
|
|
245
|
+
let depth = 0;
|
|
246
|
+
let current = '';
|
|
247
|
+
let inQuote = false;
|
|
248
|
+
for (let i = 0; i < inner.length; i++) {
|
|
249
|
+
const c = inner[i];
|
|
250
|
+
if (c === "'" && inner[i - 1] !== '\\') {
|
|
251
|
+
inQuote = !inQuote;
|
|
252
|
+
}
|
|
253
|
+
if (!inQuote) {
|
|
254
|
+
if (c === '[' || c === '(')
|
|
255
|
+
depth++;
|
|
256
|
+
if (c === ']' || c === ')')
|
|
257
|
+
depth--;
|
|
258
|
+
if (c === ',' && depth === 0) {
|
|
259
|
+
items.push(current.trim());
|
|
260
|
+
current = '';
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
current += c;
|
|
265
|
+
}
|
|
266
|
+
if (current.trim())
|
|
267
|
+
items.push(current.trim());
|
|
268
|
+
return items.flatMap(item => extractConcatItems(item));
|
|
269
|
+
}
|
|
270
|
+
return [expr];
|
|
271
|
+
}
|
|
272
|
+
function cleanAllConcats(expr) {
|
|
273
|
+
let result = expr;
|
|
274
|
+
let iterations = 0;
|
|
275
|
+
const maxIterations = 50;
|
|
276
|
+
while (result.includes('__CONCAT__[') && iterations < maxIterations) {
|
|
277
|
+
const match = result.match(/__CONCAT__\[([^\[\]]*)\]/);
|
|
278
|
+
if (match) {
|
|
279
|
+
const replacement = `F.concat(${match[1]})`;
|
|
280
|
+
result = result.replace(match[0], replacement);
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
285
|
+
iterations++;
|
|
286
|
+
}
|
|
287
|
+
return result;
|
|
288
|
+
}
|
|
289
|
+
function formatGeneratedExpression(expr) {
|
|
290
|
+
if (expr.startsWith('__CONCAT__[')) {
|
|
291
|
+
const items = extractConcatItems(expr);
|
|
292
|
+
const cleanedItems = items.map(item => cleanAllConcats(item));
|
|
293
|
+
return { isArray: true, items: cleanedItems };
|
|
294
|
+
}
|
|
295
|
+
const cleaned = cleanAllConcats(expr);
|
|
296
|
+
return { isArray: false, items: [cleaned] };
|
|
297
|
+
}
|