relq 1.0.5 → 1.0.7
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 +257 -17
- package/dist/cjs/cli/commands/commit.cjs +13 -2
- 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/config/config.cjs +29 -10
- 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 +147 -2
- package/dist/esm/cli/commands/add.js +255 -18
- package/dist/esm/cli/commands/commit.js +13 -2
- 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/config/config.js +29 -10
- 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
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { parseFunctions, parseTriggers, parseComments } from "../utils/sql-parser.js";
|
|
4
|
+
import { generateTypeScriptFromAST } from "../utils/ast-codegen.js";
|
|
5
|
+
import { parseSQL, normalizedToParsedSchema } from "../utils/ast-transformer.js";
|
|
5
6
|
import { saveSnapshot, loadSnapshot, isInitialized, initRepository, stageChanges, addUnstagedChanges } from "../utils/repo-manager.js";
|
|
6
7
|
import { compareSchemas } from "../utils/schema-comparator.js";
|
|
7
8
|
import { getChangeDisplayName } from "../utils/change-tracker.js";
|
|
8
|
-
import { loadRelqignore, validateIgnoreDependencies, isTableIgnored, isColumnIgnored, isIndexIgnored, isConstraintIgnored, isEnumIgnored, isDomainIgnored, isSequenceIgnored,
|
|
9
|
+
import { loadRelqignore, validateIgnoreDependencies, isTableIgnored, isColumnIgnored, isIndexIgnored, isConstraintIgnored, isEnumIgnored, isDomainIgnored, isSequenceIgnored, isFunctionIgnored, } from "../utils/relqignore.js";
|
|
9
10
|
import { colors, fatal, warning, hint, getWorkingTreeStatus, printDirtyWorkingTreeError, printMergeStrategyHelp, readSQLFile, createSpinner, formatBytes, } from "../utils/git-utils.js";
|
|
10
11
|
export async function importCommand(sqlFilePath, options = {}, projectRoot = process.cwd()) {
|
|
11
12
|
const { includeFunctions = false, includeTriggers = false, force = false, dryRun = false } = options;
|
|
@@ -66,7 +67,7 @@ export async function importCommand(sqlFilePath, options = {}, projectRoot = pro
|
|
|
66
67
|
}
|
|
67
68
|
}
|
|
68
69
|
spinner.start('Parsing SQL schema');
|
|
69
|
-
const parsedSchema =
|
|
70
|
+
const parsedSchema = await parseSQL(sqlContent);
|
|
70
71
|
const functions = includeFunctions ? parseFunctions(sqlContent) : [];
|
|
71
72
|
const triggers = includeTriggers ? parseTriggers(sqlContent) : [];
|
|
72
73
|
const comments = parseComments(sqlContent);
|
|
@@ -154,13 +155,6 @@ export async function importCommand(sqlFilePath, options = {}, projectRoot = pro
|
|
|
154
155
|
}
|
|
155
156
|
return true;
|
|
156
157
|
});
|
|
157
|
-
const filteredCompositeTypes = parsedSchema.compositeTypes.filter(c => {
|
|
158
|
-
if (isCompositeTypeIgnored(c.name, ignorePatterns).ignored) {
|
|
159
|
-
ignoredCount++;
|
|
160
|
-
return false;
|
|
161
|
-
}
|
|
162
|
-
return true;
|
|
163
|
-
});
|
|
164
158
|
const filteredSequences = parsedSchema.sequences.filter(s => {
|
|
165
159
|
if (isSequenceIgnored(s.name, ignorePatterns).ignored) {
|
|
166
160
|
ignoredCount++;
|
|
@@ -179,15 +173,11 @@ export async function importCommand(sqlFilePath, options = {}, projectRoot = pro
|
|
|
179
173
|
tables: filteredTables,
|
|
180
174
|
enums: filteredEnums,
|
|
181
175
|
domains: filteredDomains,
|
|
182
|
-
compositeTypes: filteredCompositeTypes,
|
|
183
176
|
sequences: filteredSequences,
|
|
184
|
-
collations: parsedSchema.collations,
|
|
185
|
-
foreignTables: parsedSchema.foreignTables,
|
|
186
177
|
views: parsedSchema.views,
|
|
187
|
-
|
|
188
|
-
|
|
178
|
+
functions: parsedSchema.functions,
|
|
179
|
+
triggers: parsedSchema.triggers,
|
|
189
180
|
extensions: parsedSchema.extensions,
|
|
190
|
-
partitions: parsedSchema.partitions,
|
|
191
181
|
};
|
|
192
182
|
if (ignoredCount > 0) {
|
|
193
183
|
console.log(`${ignoredCount} object(s) ignored by .relqignore`);
|
|
@@ -197,14 +187,14 @@ export async function importCommand(sqlFilePath, options = {}, projectRoot = pro
|
|
|
197
187
|
name: t.name,
|
|
198
188
|
columns: t.columns.map(c => ({
|
|
199
189
|
name: c.name,
|
|
200
|
-
type: c.
|
|
190
|
+
type: c.type,
|
|
201
191
|
default: c.defaultValue,
|
|
202
192
|
})),
|
|
203
193
|
})),
|
|
204
194
|
enums: parsedSchema.enums,
|
|
205
195
|
domains: parsedSchema.domains,
|
|
206
196
|
sequences: parsedSchema.sequences,
|
|
207
|
-
compositeTypes:
|
|
197
|
+
compositeTypes: [],
|
|
208
198
|
}, ignorePatterns);
|
|
209
199
|
if (dependencyErrors.length > 0) {
|
|
210
200
|
spinner.stop();
|
|
@@ -213,12 +203,6 @@ export async function importCommand(sqlFilePath, options = {}, projectRoot = pro
|
|
|
213
203
|
return;
|
|
214
204
|
}
|
|
215
205
|
spinner.start('Generating TypeScript schema');
|
|
216
|
-
const dbSchema = convertToDbSchema(filteredSchema, filteredFunctions, triggers, comments);
|
|
217
|
-
const typescriptContent = generateTypeScript(dbSchema, {
|
|
218
|
-
camelCase: true,
|
|
219
|
-
includeFunctions,
|
|
220
|
-
includeTriggers,
|
|
221
|
-
});
|
|
222
206
|
spinner.succeed('Generated TypeScript schema');
|
|
223
207
|
const incomingSchema = convertToNormalizedSchema(filteredSchema, filteredFunctions, triggers);
|
|
224
208
|
const existingSnapshot = loadSnapshot(projectRoot);
|
|
@@ -282,8 +266,91 @@ export async function importCommand(sqlFilePath, options = {}, projectRoot = pro
|
|
|
282
266
|
changes = [];
|
|
283
267
|
console.log(`${colors.cyan('First import')} - creating initial snapshot`);
|
|
284
268
|
}
|
|
285
|
-
const
|
|
286
|
-
|
|
269
|
+
const astSchema = normalizedToParsedSchema({
|
|
270
|
+
tables: mergedSchema.tables.map(t => ({
|
|
271
|
+
name: t.name,
|
|
272
|
+
schema: t.schema,
|
|
273
|
+
columns: t.columns.map(c => ({
|
|
274
|
+
name: c.name,
|
|
275
|
+
type: c.type,
|
|
276
|
+
nullable: c.nullable,
|
|
277
|
+
default: c.default,
|
|
278
|
+
primaryKey: c.primaryKey,
|
|
279
|
+
unique: c.unique,
|
|
280
|
+
check: c.check,
|
|
281
|
+
checkName: c.checkName,
|
|
282
|
+
references: c.references,
|
|
283
|
+
comment: c.comment,
|
|
284
|
+
isGenerated: c.isGenerated,
|
|
285
|
+
generatedExpression: c.generationExpression,
|
|
286
|
+
generatedExpressionAst: c.generatedExpressionAst,
|
|
287
|
+
})),
|
|
288
|
+
indexes: t.indexes.map(i => ({
|
|
289
|
+
name: i.name,
|
|
290
|
+
columns: i.columns,
|
|
291
|
+
unique: i.unique,
|
|
292
|
+
type: i.type,
|
|
293
|
+
definition: i.definition,
|
|
294
|
+
whereClause: i.whereClause,
|
|
295
|
+
whereClauseAst: i.whereClauseAst,
|
|
296
|
+
})),
|
|
297
|
+
constraints: t.constraints?.map(c => ({
|
|
298
|
+
name: c.name,
|
|
299
|
+
type: c.type,
|
|
300
|
+
columns: c.columns,
|
|
301
|
+
definition: c.definition,
|
|
302
|
+
references: c.references,
|
|
303
|
+
})),
|
|
304
|
+
isPartitioned: t.isPartitioned,
|
|
305
|
+
partitionType: t.partitionType,
|
|
306
|
+
partitionKey: t.partitionKey,
|
|
307
|
+
comment: t.comment,
|
|
308
|
+
})),
|
|
309
|
+
enums: mergedSchema.enums.map(e => ({
|
|
310
|
+
name: e.name,
|
|
311
|
+
schema: e.schema,
|
|
312
|
+
values: e.values,
|
|
313
|
+
})),
|
|
314
|
+
domains: mergedSchema.domains?.map(d => ({
|
|
315
|
+
name: d.name,
|
|
316
|
+
baseType: d.baseType,
|
|
317
|
+
notNull: d.notNull,
|
|
318
|
+
default: d.default ?? undefined,
|
|
319
|
+
check: d.check ?? undefined,
|
|
320
|
+
checkName: d.checkName,
|
|
321
|
+
})),
|
|
322
|
+
sequences: mergedSchema.sequences?.map(s => ({
|
|
323
|
+
name: s.name,
|
|
324
|
+
start: s.start,
|
|
325
|
+
increment: s.increment,
|
|
326
|
+
minValue: s.minValue,
|
|
327
|
+
maxValue: s.maxValue,
|
|
328
|
+
cache: s.cache,
|
|
329
|
+
cycle: s.cycle,
|
|
330
|
+
})),
|
|
331
|
+
extensions: mergedSchema.extensions?.map(e => e.name),
|
|
332
|
+
functions: includeFunctions ? mergedSchema.functions?.map(f => ({
|
|
333
|
+
name: f.name,
|
|
334
|
+
schema: f.schema,
|
|
335
|
+
args: f.args || [],
|
|
336
|
+
returnType: f.returnType,
|
|
337
|
+
language: f.language,
|
|
338
|
+
body: f.body,
|
|
339
|
+
volatility: f.volatility,
|
|
340
|
+
isStrict: f.isStrict,
|
|
341
|
+
securityDefiner: f.securityDefiner,
|
|
342
|
+
})) : [],
|
|
343
|
+
triggers: includeTriggers ? mergedSchema.triggers?.map(t => ({
|
|
344
|
+
name: t.name,
|
|
345
|
+
table: t.table,
|
|
346
|
+
timing: t.timing,
|
|
347
|
+
events: t.events,
|
|
348
|
+
level: t.level,
|
|
349
|
+
functionName: t.functionName,
|
|
350
|
+
when: t.when,
|
|
351
|
+
})) : [],
|
|
352
|
+
});
|
|
353
|
+
const finalTypescriptContent = generateTypeScriptFromAST(astSchema, {
|
|
287
354
|
camelCase: true,
|
|
288
355
|
includeFunctions,
|
|
289
356
|
includeTriggers,
|
|
@@ -311,6 +378,7 @@ export async function importCommand(sqlFilePath, options = {}, projectRoot = pro
|
|
|
311
378
|
}
|
|
312
379
|
fs.writeFileSync(absoluteOutputPath, finalTypescriptContent, 'utf-8');
|
|
313
380
|
console.log(`Written ${colors.cyan(absoluteOutputPath)} ${colors.gray(`(${formatBytes(finalTypescriptContent.length)})`)}`);
|
|
381
|
+
applyTrackingIdsToSnapshot(finalTypescriptContent, mergedSchema);
|
|
314
382
|
saveSnapshot(mergedSchema, projectRoot);
|
|
315
383
|
if (changes.length > 0) {
|
|
316
384
|
addUnstagedChanges(changes, projectRoot);
|
|
@@ -359,36 +427,50 @@ function convertToDbSchema(parsed, functions = [], triggers = [], comments = [])
|
|
|
359
427
|
return {
|
|
360
428
|
tables: parsed.tables.map(t => ({
|
|
361
429
|
name: t.name,
|
|
362
|
-
schema: t.schema,
|
|
430
|
+
schema: t.schema || 'public',
|
|
363
431
|
comment: tableComments.get(t.name) || null,
|
|
364
|
-
columns: t.columns
|
|
432
|
+
columns: t.columns
|
|
433
|
+
.filter(c => {
|
|
434
|
+
if (!c.type) {
|
|
435
|
+
console.warn(`Warning: Skipping column ${t.name}.${c.name} (no type)`);
|
|
436
|
+
return false;
|
|
437
|
+
}
|
|
438
|
+
return true;
|
|
439
|
+
})
|
|
440
|
+
.map(c => ({
|
|
365
441
|
name: c.name,
|
|
366
|
-
dataType: c.
|
|
367
|
-
udtName: c.
|
|
442
|
+
dataType: c.type,
|
|
443
|
+
udtName: c.type,
|
|
368
444
|
isNullable: c.isNullable,
|
|
369
|
-
defaultValue: c.defaultValue,
|
|
445
|
+
defaultValue: c.defaultValue ?? null,
|
|
370
446
|
isPrimaryKey: c.isPrimaryKey,
|
|
371
447
|
isUnique: c.isUnique,
|
|
372
|
-
maxLength: c.
|
|
373
|
-
precision: c.precision,
|
|
374
|
-
scale: c.scale,
|
|
448
|
+
maxLength: c.typeParams?.length ?? null,
|
|
449
|
+
precision: c.typeParams?.precision ?? null,
|
|
450
|
+
scale: c.typeParams?.scale ?? null,
|
|
375
451
|
references: c.references ? `${c.references.table}.${c.references.column}` : null,
|
|
376
452
|
comment: columnComments.get(`${t.name}.${c.name}`) || c.comment || null,
|
|
377
453
|
isGenerated: c.isGenerated || false,
|
|
378
|
-
generationExpression: c.
|
|
454
|
+
generationExpression: c.generatedExpression || null,
|
|
379
455
|
})),
|
|
380
456
|
indexes: t.indexes.map(idx => ({
|
|
381
457
|
name: idx.name,
|
|
382
458
|
columns: idx.columns,
|
|
383
459
|
isUnique: idx.isUnique,
|
|
384
|
-
isPrimary:
|
|
385
|
-
type: idx.
|
|
386
|
-
definition: idx.
|
|
460
|
+
isPrimary: false,
|
|
461
|
+
type: idx.method || 'btree',
|
|
462
|
+
definition: idx.expressions?.join(', ') || idx.columns.join(', '),
|
|
387
463
|
whereClause: idx.whereClause,
|
|
388
|
-
|
|
464
|
+
whereClauseAst: idx.whereClauseAst,
|
|
465
|
+
expression: idx.expressions?.[0],
|
|
389
466
|
comment: indexComments.get(idx.name) || null,
|
|
390
467
|
})),
|
|
391
|
-
constraints: t.constraints
|
|
468
|
+
constraints: t.constraints.map(c => ({
|
|
469
|
+
name: c.name,
|
|
470
|
+
type: c.type,
|
|
471
|
+
columns: c.columns,
|
|
472
|
+
definition: c.expression || c.columns.join(', '),
|
|
473
|
+
})),
|
|
392
474
|
rowCount: 0,
|
|
393
475
|
isPartitioned: t.isPartitioned,
|
|
394
476
|
partitionType: t.partitionType,
|
|
@@ -398,34 +480,31 @@ function convertToDbSchema(parsed, functions = [], triggers = [], comments = [])
|
|
|
398
480
|
name: e.name,
|
|
399
481
|
values: e.values,
|
|
400
482
|
})),
|
|
401
|
-
domains: parsed.domains
|
|
483
|
+
domains: parsed.domains.map(d => ({
|
|
402
484
|
name: d.name,
|
|
403
485
|
baseType: d.baseType,
|
|
404
486
|
isNotNull: d.notNull || false,
|
|
405
|
-
defaultValue: d.
|
|
406
|
-
checkExpression: d.
|
|
407
|
-
check: d.
|
|
408
|
-
default: d.
|
|
487
|
+
defaultValue: d.defaultValue || null,
|
|
488
|
+
checkExpression: d.checkExpression || null,
|
|
489
|
+
check: d.checkExpression,
|
|
490
|
+
default: d.defaultValue,
|
|
409
491
|
notNull: d.notNull,
|
|
410
|
-
}))
|
|
411
|
-
compositeTypes:
|
|
412
|
-
|
|
413
|
-
attributes: c.attributes || [],
|
|
414
|
-
})) || [],
|
|
415
|
-
sequences: parsed.sequences?.map((s) => ({
|
|
492
|
+
})),
|
|
493
|
+
compositeTypes: [],
|
|
494
|
+
sequences: parsed.sequences.map(s => ({
|
|
416
495
|
name: s.name,
|
|
417
|
-
dataType:
|
|
418
|
-
start: s.
|
|
496
|
+
dataType: 'bigint',
|
|
497
|
+
start: s.startValue,
|
|
419
498
|
increment: s.increment,
|
|
420
499
|
minValue: s.minValue,
|
|
421
500
|
maxValue: s.maxValue,
|
|
422
501
|
cache: s.cache,
|
|
423
502
|
cycle: s.cycle,
|
|
424
|
-
ownedBy: s.ownedBy,
|
|
425
|
-
}))
|
|
503
|
+
ownedBy: s.ownedBy ? `${s.ownedBy.table}.${s.ownedBy.column}` : undefined,
|
|
504
|
+
})),
|
|
426
505
|
collations: [],
|
|
427
506
|
extensions: parsed.extensions,
|
|
428
|
-
partitions:
|
|
507
|
+
partitions: [],
|
|
429
508
|
functions: functions.map(f => ({
|
|
430
509
|
...f,
|
|
431
510
|
comment: functionComments.get(f.name) || null,
|
|
@@ -440,79 +519,95 @@ function convertToDbSchema(parsed, functions = [], triggers = [], comments = [])
|
|
|
440
519
|
};
|
|
441
520
|
}
|
|
442
521
|
function convertToNormalizedSchema(parsed, functions = [], triggers = []) {
|
|
522
|
+
const regularViews = parsed.views.filter(v => !v.isMaterialized);
|
|
523
|
+
const materializedViews = parsed.views.filter(v => v.isMaterialized);
|
|
443
524
|
return {
|
|
444
525
|
extensions: parsed.extensions.map(ext => ({ name: ext })),
|
|
445
526
|
enums: parsed.enums.map(e => ({
|
|
446
527
|
name: e.name,
|
|
447
|
-
schema: 'public',
|
|
528
|
+
schema: e.schema || 'public',
|
|
448
529
|
values: e.values,
|
|
449
530
|
})),
|
|
450
531
|
domains: parsed.domains.map(d => ({
|
|
451
532
|
name: d.name,
|
|
452
|
-
schema: 'public',
|
|
533
|
+
schema: d.schema || 'public',
|
|
453
534
|
baseType: d.baseType,
|
|
454
535
|
notNull: d.notNull || false,
|
|
455
|
-
default: d.
|
|
456
|
-
check: d.
|
|
457
|
-
})),
|
|
458
|
-
compositeTypes: parsed.compositeTypes.map(c => ({
|
|
459
|
-
name: c.name,
|
|
460
|
-
schema: 'public',
|
|
461
|
-
attributes: c.attributes,
|
|
536
|
+
default: d.defaultValue || null,
|
|
537
|
+
check: d.checkExpression || null,
|
|
462
538
|
})),
|
|
539
|
+
compositeTypes: [],
|
|
463
540
|
sequences: parsed.sequences.map(s => ({
|
|
464
541
|
name: s.name,
|
|
465
|
-
schema: 'public',
|
|
542
|
+
schema: s.schema || 'public',
|
|
466
543
|
dataType: 'bigint',
|
|
467
|
-
startValue: s.
|
|
544
|
+
startValue: s.startValue || 1,
|
|
468
545
|
increment: s.increment || 1,
|
|
469
546
|
minValue: s.minValue || null,
|
|
470
547
|
maxValue: s.maxValue || null,
|
|
471
|
-
cache: 1,
|
|
472
|
-
cycle: false,
|
|
473
|
-
ownedBy: null,
|
|
548
|
+
cache: s.cache || 1,
|
|
549
|
+
cycle: s.cycle || false,
|
|
550
|
+
ownedBy: s.ownedBy ? `${s.ownedBy.table}.${s.ownedBy.column}` : null,
|
|
474
551
|
})),
|
|
475
552
|
collations: [],
|
|
476
553
|
tables: parsed.tables.map(t => {
|
|
477
|
-
const childPartitions = parsed.partitions.filter(p => p.parentTable === t.name);
|
|
478
554
|
return {
|
|
479
555
|
name: t.name,
|
|
480
|
-
schema: t.schema,
|
|
481
|
-
columns: t.columns.map(c =>
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
556
|
+
schema: t.schema || 'public',
|
|
557
|
+
columns: t.columns.map(c => {
|
|
558
|
+
const col = {
|
|
559
|
+
name: c.name,
|
|
560
|
+
tsName: toCamelCase(c.name),
|
|
561
|
+
type: c.type,
|
|
562
|
+
nullable: c.isNullable,
|
|
563
|
+
default: c.defaultValue,
|
|
564
|
+
primaryKey: c.isPrimaryKey,
|
|
565
|
+
unique: c.isUnique,
|
|
566
|
+
isGenerated: c.isGenerated,
|
|
567
|
+
generationExpression: c.generatedExpression,
|
|
568
|
+
generatedExpressionAst: c.generatedExpressionAst,
|
|
569
|
+
};
|
|
570
|
+
return col;
|
|
571
|
+
}),
|
|
495
572
|
indexes: t.indexes.map(idx => ({
|
|
496
573
|
name: idx.name,
|
|
497
574
|
columns: idx.columns,
|
|
498
575
|
unique: idx.isUnique,
|
|
499
|
-
type: idx.
|
|
500
|
-
definition: idx.
|
|
576
|
+
type: idx.method || 'btree',
|
|
577
|
+
definition: idx.expressions?.join(', ') || idx.columns.join(', '),
|
|
501
578
|
whereClause: idx.whereClause,
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
constraints: t.constraints.map(c => ({
|
|
505
|
-
name: c.name,
|
|
506
|
-
type: c.type,
|
|
507
|
-
definition: c.definition,
|
|
579
|
+
whereClauseAst: idx.whereClauseAst,
|
|
580
|
+
expression: idx.expressions?.[0],
|
|
508
581
|
})),
|
|
582
|
+
constraints: t.constraints.map(c => {
|
|
583
|
+
const base = {
|
|
584
|
+
name: c.name,
|
|
585
|
+
type: c.type,
|
|
586
|
+
definition: c.expression || c.columns.join(', '),
|
|
587
|
+
columns: c.columns,
|
|
588
|
+
};
|
|
589
|
+
if (c.type === 'FOREIGN KEY' && c.references) {
|
|
590
|
+
base.referencedTable = c.references.table;
|
|
591
|
+
base.referencedColumns = c.references.columns;
|
|
592
|
+
if (c.references.onDelete)
|
|
593
|
+
base.onDelete = c.references.onDelete;
|
|
594
|
+
if (c.references.onUpdate)
|
|
595
|
+
base.onUpdate = c.references.onUpdate;
|
|
596
|
+
if (c.references.match)
|
|
597
|
+
base.matchType = c.references.match;
|
|
598
|
+
if (c.references.deferrable)
|
|
599
|
+
base.deferrable = c.references.deferrable;
|
|
600
|
+
if (c.references.initiallyDeferred)
|
|
601
|
+
base.initiallyDeferred = c.references.initiallyDeferred;
|
|
602
|
+
}
|
|
603
|
+
if (c.type === 'CHECK' && c.expression) {
|
|
604
|
+
base.checkExpression = c.expression;
|
|
605
|
+
}
|
|
606
|
+
return base;
|
|
607
|
+
}),
|
|
509
608
|
isPartitioned: t.isPartitioned,
|
|
510
609
|
partitionType: t.partitionType,
|
|
511
610
|
partitionKey: t.partitionKey,
|
|
512
|
-
partitions: childPartitions.length > 0 ? childPartitions.map(p => ({
|
|
513
|
-
name: p.name,
|
|
514
|
-
bound: p.partitionBound || '',
|
|
515
|
-
})) : undefined,
|
|
516
611
|
};
|
|
517
612
|
}),
|
|
518
613
|
functions: functions.map(f => ({
|
|
@@ -529,16 +624,16 @@ function convertToNormalizedSchema(parsed, functions = [], triggers = []) {
|
|
|
529
624
|
forEach: 'STATEMENT',
|
|
530
625
|
functionName: t.functionName || '',
|
|
531
626
|
})),
|
|
532
|
-
views:
|
|
627
|
+
views: regularViews.map(v => ({
|
|
533
628
|
name: v.name,
|
|
534
629
|
schema: v.schema || 'public',
|
|
535
630
|
definition: v.definition,
|
|
536
631
|
})),
|
|
537
|
-
materializedViews:
|
|
632
|
+
materializedViews: materializedViews.map(mv => ({
|
|
538
633
|
name: mv.name,
|
|
539
634
|
schema: mv.schema || 'public',
|
|
540
635
|
definition: mv.definition,
|
|
541
|
-
withData:
|
|
636
|
+
withData: true,
|
|
542
637
|
})),
|
|
543
638
|
};
|
|
544
639
|
}
|
|
@@ -650,6 +745,7 @@ function snapshotToDbSchemaForGeneration(snapshot) {
|
|
|
650
745
|
type: i.type || 'btree',
|
|
651
746
|
definition: i.definition,
|
|
652
747
|
whereClause: i.whereClause,
|
|
748
|
+
whereClauseAst: i.whereClauseAst,
|
|
653
749
|
comment: null,
|
|
654
750
|
})),
|
|
655
751
|
constraints: t.constraints?.map(c => ({
|
|
@@ -749,4 +845,27 @@ function mergeSchemas(existing, incoming, replaceAll = false) {
|
|
|
749
845
|
foreignTables: mergeByName(existing.foreignTables || [], incoming.foreignTables || []),
|
|
750
846
|
};
|
|
751
847
|
}
|
|
848
|
+
function applyTrackingIdsToSnapshot(typescript, snapshot) {
|
|
849
|
+
for (const table of snapshot.tables) {
|
|
850
|
+
const tablePattern = new RegExp(`defineTable\\s*\\(\\s*['"]${table.name}['"]\\s*,\\s*\\{[^}]+\\}\\s*,\\s*\\{[^}]*\\$trackingId:\\s*['"]([^'"]+)['"]`, 's');
|
|
851
|
+
const tableMatch = typescript.match(tablePattern);
|
|
852
|
+
if (tableMatch) {
|
|
853
|
+
table.trackingId = tableMatch[1];
|
|
854
|
+
}
|
|
855
|
+
for (const col of table.columns) {
|
|
856
|
+
const colPattern = new RegExp(`(?:${col.tsName}|${col.name}):\\s*\\w+\\([^)]*\\)[^\\n]*\\.\\\$id\\(['"]([^'"]+)['"]\\)`);
|
|
857
|
+
const colMatch = typescript.match(colPattern);
|
|
858
|
+
if (colMatch) {
|
|
859
|
+
col.trackingId = colMatch[1];
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
for (const idx of table.indexes) {
|
|
863
|
+
const idxPattern = new RegExp(`index\\s*\\(\\s*['"]${idx.name}['"]\\s*\\)[^\\n]*\\.\\\$id\\(['"]([^'"]+)['"]\\)`);
|
|
864
|
+
const idxMatch = typescript.match(idxPattern);
|
|
865
|
+
if (idxMatch) {
|
|
866
|
+
idx.trackingId = idxMatch[1];
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
}
|
|
752
871
|
export default importCommand;
|
|
@@ -48,21 +48,69 @@ function checkEnvVars() {
|
|
|
48
48
|
DATABASE_NAME: process.env.DATABASE_NAME,
|
|
49
49
|
DATABASE_USER: process.env.DATABASE_USER,
|
|
50
50
|
DATABASE_PASSWORD: process.env.DATABASE_PASSWORD,
|
|
51
|
+
DATABASE_REGION: process.env.DATABASE_REGION,
|
|
52
|
+
AWS_DATABASE_HOST: process.env.AWS_DATABASE_HOST,
|
|
53
|
+
AWS_ACCESS_KEY_ID: process.env.AWS_ACCESS_KEY_ID,
|
|
54
|
+
AWS_DATABASE_USER: process.env.AWS_DATABASE_USER,
|
|
55
|
+
AWS_DATABASE_NAME: process.env.AWS_DATABASE_NAME,
|
|
56
|
+
AWS_DATABASE_PORT: process.env.AWS_DATABASE_PORT,
|
|
57
|
+
AWS_SECRET_ACCESS_KEY: process.env.AWS_SECRET_ACCESS_KEY,
|
|
51
58
|
RELQ_PG_CONN_URL: process.env.RELQ_PG_CONN_URL,
|
|
52
59
|
};
|
|
53
60
|
const hasConnUrl = !!vars.RELQ_PG_CONN_URL;
|
|
54
|
-
const hasIndividual = !!(vars.DATABASE_HOST && vars.DATABASE_NAME);
|
|
61
|
+
const hasIndividual = !!(vars.DATABASE_HOST && vars.DATABASE_NAME && vars.DATABASE_USER && vars.DATABASE_PASSWORD);
|
|
62
|
+
const hasAws = !!(vars.AWS_ACCESS_KEY_ID && vars.AWS_SECRET_ACCESS_KEY && vars.DATABASE_REGION && vars.AWS_DATABASE_NAME && vars.AWS_DATABASE_HOST);
|
|
55
63
|
return {
|
|
56
|
-
found: hasConnUrl || hasIndividual,
|
|
64
|
+
found: hasConnUrl || hasIndividual || hasAws,
|
|
65
|
+
hasConnUrl,
|
|
66
|
+
hasIndividual,
|
|
67
|
+
hasAws,
|
|
57
68
|
vars,
|
|
58
69
|
};
|
|
59
70
|
}
|
|
71
|
+
async function askConnectionType(rl, options) {
|
|
72
|
+
const choices = [];
|
|
73
|
+
if (options.hasConnUrl) {
|
|
74
|
+
choices.push({ key: String(choices.length + 1), label: 'Connection URL (RELQ_PG_CONN_URL)', value: 'url' });
|
|
75
|
+
}
|
|
76
|
+
if (options.hasIndividual) {
|
|
77
|
+
choices.push({ key: String(choices.length + 1), label: 'Individual Config (DATABASE_HOST, DATABASE_NAME, ...)', value: 'individual' });
|
|
78
|
+
}
|
|
79
|
+
if (options.hasAws) {
|
|
80
|
+
choices.push({ key: String(choices.length + 1), label: 'AWS DSQL (AWS_ACCESS_KEY_ID, DATABASE_REGION, ...)', value: 'aws' });
|
|
81
|
+
}
|
|
82
|
+
if (choices.length === 1) {
|
|
83
|
+
return choices[0].value;
|
|
84
|
+
}
|
|
85
|
+
console.log('Multiple connection options found:');
|
|
86
|
+
console.log('');
|
|
87
|
+
for (const choice of choices) {
|
|
88
|
+
console.log(` ${colors.cyan(choice.key)}. ${choice.label}`);
|
|
89
|
+
}
|
|
90
|
+
console.log('');
|
|
91
|
+
const answer = await ask(rl, `Which connection type? [${choices.map(c => c.key).join('/')}]`, '1');
|
|
92
|
+
const selected = choices.find(c => c.key === answer);
|
|
93
|
+
return selected?.value ?? choices[0].value;
|
|
94
|
+
}
|
|
60
95
|
function generateConfig(options) {
|
|
61
96
|
let connectionBlock;
|
|
62
97
|
if (options.useEnv) {
|
|
63
|
-
if (
|
|
98
|
+
if (options.connectionType === 'url') {
|
|
64
99
|
connectionBlock = ` connection: {
|
|
65
100
|
url: process.env.RELQ_PG_CONN_URL,
|
|
101
|
+
},`;
|
|
102
|
+
}
|
|
103
|
+
else if (options.connectionType === 'aws') {
|
|
104
|
+
connectionBlock = ` connection: {
|
|
105
|
+
database: process.env.AWS_DATABASE_NAME,
|
|
106
|
+
aws: {
|
|
107
|
+
hostname: process.env.AWS_DATABASE_HOST!,
|
|
108
|
+
region: process.env.DATABASE_REGION!,
|
|
109
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
|
110
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
|
|
111
|
+
user: process.env.AWS_DATABASE_USER || 'admin',
|
|
112
|
+
port: parseInt(process.env.AWS_DATABASE_PORT || '5432'),
|
|
113
|
+
},
|
|
66
114
|
},`;
|
|
67
115
|
}
|
|
68
116
|
else {
|
|
@@ -287,30 +335,53 @@ export async function initCommand(context) {
|
|
|
287
335
|
console.log('');
|
|
288
336
|
const envCheck = checkEnvVars();
|
|
289
337
|
let useEnv = false;
|
|
338
|
+
let connectionType = 'individual';
|
|
290
339
|
let host = 'localhost';
|
|
291
340
|
let port = '5432';
|
|
292
341
|
let database = '';
|
|
293
342
|
let user = 'postgres';
|
|
294
343
|
let password = '';
|
|
295
344
|
if (envCheck.found) {
|
|
296
|
-
console.log('Found database
|
|
297
|
-
|
|
298
|
-
|
|
345
|
+
console.log('Found database connection options:');
|
|
346
|
+
console.log('');
|
|
347
|
+
if (envCheck.hasConnUrl) {
|
|
348
|
+
console.log(` ${colors.cyan('Connection URL')}`);
|
|
349
|
+
console.log(` RELQ_PG_CONN_URL: ${envCheck.vars.RELQ_PG_CONN_URL?.substring(0, 40)}...`);
|
|
299
350
|
}
|
|
300
|
-
|
|
351
|
+
if (envCheck.hasIndividual) {
|
|
352
|
+
console.log(` ${colors.cyan('Individual Config')}`);
|
|
301
353
|
if (envCheck.vars.DATABASE_HOST)
|
|
302
|
-
console.log(`
|
|
303
|
-
if (envCheck.vars.DATABASE_PORT)
|
|
304
|
-
console.log(` DATABASE_PORT: ${envCheck.vars.DATABASE_PORT}`);
|
|
354
|
+
console.log(` DATABASE_HOST: ${envCheck.vars.DATABASE_HOST}`);
|
|
305
355
|
if (envCheck.vars.DATABASE_NAME)
|
|
306
|
-
console.log(`
|
|
356
|
+
console.log(` DATABASE_NAME: ${envCheck.vars.DATABASE_NAME}`);
|
|
307
357
|
if (envCheck.vars.DATABASE_USER)
|
|
308
|
-
console.log(`
|
|
358
|
+
console.log(` DATABASE_USER: ${envCheck.vars.DATABASE_USER}`);
|
|
309
359
|
if (envCheck.vars.DATABASE_PASSWORD)
|
|
310
|
-
console.log('
|
|
360
|
+
console.log(' DATABASE_PASSWORD: ***');
|
|
361
|
+
}
|
|
362
|
+
if (envCheck.hasAws) {
|
|
363
|
+
console.log(` ${colors.cyan('AWS DSQL')}`);
|
|
364
|
+
if (envCheck.vars.AWS_DATABASE_HOST)
|
|
365
|
+
console.log(` AWS_DATABASE_HOST: ${envCheck.vars.AWS_DATABASE_HOST}`);
|
|
366
|
+
if (envCheck.vars.DATABASE_REGION)
|
|
367
|
+
console.log(` DATABASE_REGION: ${envCheck.vars.DATABASE_REGION}`);
|
|
368
|
+
if (envCheck.vars.AWS_DATABASE_NAME)
|
|
369
|
+
console.log(` AWS_DATABASE_NAME: ${envCheck.vars.AWS_DATABASE_NAME}`);
|
|
370
|
+
if (envCheck.vars.AWS_ACCESS_KEY_ID)
|
|
371
|
+
console.log(` AWS_ACCESS_KEY_ID: ${envCheck.vars.AWS_ACCESS_KEY_ID?.substring(0, 8)}...`);
|
|
372
|
+
console.log(' AWS_SECRET_ACCESS_KEY: ***');
|
|
311
373
|
}
|
|
312
374
|
console.log('');
|
|
313
|
-
|
|
375
|
+
const optionCount = [envCheck.hasConnUrl, envCheck.hasIndividual, envCheck.hasAws].filter(Boolean).length;
|
|
376
|
+
if (optionCount > 1) {
|
|
377
|
+
connectionType = await askConnectionType(rl, envCheck);
|
|
378
|
+
console.log('');
|
|
379
|
+
useEnv = await askYesNo(rl, `Use ${connectionType === 'aws' ? 'AWS DSQL' : connectionType === 'url' ? 'Connection URL' : 'Individual Config'} from environment?`, true);
|
|
380
|
+
}
|
|
381
|
+
else {
|
|
382
|
+
connectionType = envCheck.hasConnUrl ? 'url' : envCheck.hasAws ? 'aws' : 'individual';
|
|
383
|
+
useEnv = await askYesNo(rl, 'Use these environment variables?', true);
|
|
384
|
+
}
|
|
314
385
|
console.log('');
|
|
315
386
|
}
|
|
316
387
|
if (!useEnv) {
|
|
@@ -344,6 +415,7 @@ export async function initCommand(context) {
|
|
|
344
415
|
if (!fs.existsSync(configPath)) {
|
|
345
416
|
const configContent = generateConfig({
|
|
346
417
|
useEnv,
|
|
418
|
+
connectionType,
|
|
347
419
|
host,
|
|
348
420
|
port,
|
|
349
421
|
database,
|