relq 1.0.4 → 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 +150 -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 +147 -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
|
@@ -78,6 +78,8 @@ function generateChangeSQL(change) {
|
|
|
78
78
|
return generateTableCommentSQL(change);
|
|
79
79
|
case 'COLUMN_COMMENT':
|
|
80
80
|
return generateColumnCommentSQL(change);
|
|
81
|
+
case 'INDEX_COMMENT':
|
|
82
|
+
return generateIndexCommentSQL(change);
|
|
81
83
|
case 'VIEW':
|
|
82
84
|
return generateViewSQL(change);
|
|
83
85
|
case 'MATERIALIZED_VIEW':
|
|
@@ -222,6 +224,13 @@ function generateColumnSQL(change) {
|
|
|
222
224
|
else if (change.type === 'DROP') {
|
|
223
225
|
return `ALTER TABLE "${tableName}" DROP COLUMN IF EXISTS "${change.objectName}" CASCADE;`;
|
|
224
226
|
}
|
|
227
|
+
else if (change.type === 'RENAME' && data) {
|
|
228
|
+
const before = change.before;
|
|
229
|
+
if (before?.name) {
|
|
230
|
+
return `ALTER TABLE "${tableName}" RENAME COLUMN "${before.name}" TO "${data.name}";`;
|
|
231
|
+
}
|
|
232
|
+
return '';
|
|
233
|
+
}
|
|
225
234
|
else if (change.type === 'ALTER' && data) {
|
|
226
235
|
const before = change.before;
|
|
227
236
|
const lines = [];
|
|
@@ -262,6 +271,13 @@ function generateIndexSQL(change) {
|
|
|
262
271
|
else if (change.type === 'DROP') {
|
|
263
272
|
return `DROP INDEX IF EXISTS "${change.objectName}";`;
|
|
264
273
|
}
|
|
274
|
+
else if (change.type === 'RENAME' && data) {
|
|
275
|
+
const before = change.before;
|
|
276
|
+
if (before?.name) {
|
|
277
|
+
return `ALTER INDEX "${before.name}" RENAME TO "${data.name}";`;
|
|
278
|
+
}
|
|
279
|
+
return '';
|
|
280
|
+
}
|
|
265
281
|
return '';
|
|
266
282
|
}
|
|
267
283
|
function generateConstraintSQL(change) {
|
|
@@ -317,6 +333,19 @@ function generateColumnCommentSQL(change) {
|
|
|
317
333
|
}
|
|
318
334
|
return '';
|
|
319
335
|
}
|
|
336
|
+
function generateIndexCommentSQL(change) {
|
|
337
|
+
const data = change.after;
|
|
338
|
+
if ((change.type === 'CREATE' || change.type === 'ALTER') && data) {
|
|
339
|
+
const escaped = data.comment.replace(/'/g, "''");
|
|
340
|
+
return `COMMENT ON INDEX "${data.indexName}" IS '${escaped}';`;
|
|
341
|
+
}
|
|
342
|
+
else if (change.type === 'DROP') {
|
|
343
|
+
const beforeData = change.before;
|
|
344
|
+
const indexName = beforeData?.indexName || change.objectName;
|
|
345
|
+
return `COMMENT ON INDEX "${indexName}" IS NULL;`;
|
|
346
|
+
}
|
|
347
|
+
return '';
|
|
348
|
+
}
|
|
320
349
|
function generateViewSQL(change) {
|
|
321
350
|
const data = change.after;
|
|
322
351
|
if (change.type === 'CREATE' && data) {
|
|
@@ -444,7 +473,8 @@ function getChangeDisplayName(change) {
|
|
|
444
473
|
const prefix = change.type === 'CREATE' ? '+' :
|
|
445
474
|
change.type === 'DROP' ? '-' :
|
|
446
475
|
change.type === 'ALTER' ? '~' :
|
|
447
|
-
'
|
|
476
|
+
change.type === 'RENAME' ? '→' :
|
|
477
|
+
'>';
|
|
448
478
|
if (change.objectType === 'INDEX') {
|
|
449
479
|
const data = change.after;
|
|
450
480
|
const tableName = data?.tableName || change.parentName;
|
|
@@ -452,6 +482,14 @@ function getChangeDisplayName(change) {
|
|
|
452
482
|
return `${prefix} ${change.objectType} ${change.objectName} on ${tableName}`;
|
|
453
483
|
}
|
|
454
484
|
}
|
|
485
|
+
if (change.objectType === 'INDEX_COMMENT') {
|
|
486
|
+
const data = (change.after || change.before);
|
|
487
|
+
const tableName = data?.tableName || change.parentName;
|
|
488
|
+
if (tableName) {
|
|
489
|
+
return `${prefix} INDEX COMMENT ${change.objectName} on ${tableName}`;
|
|
490
|
+
}
|
|
491
|
+
return `${prefix} INDEX COMMENT ${change.objectName}`;
|
|
492
|
+
}
|
|
455
493
|
if (change.objectType === 'PARTITION') {
|
|
456
494
|
const data = change.after;
|
|
457
495
|
if (data?.tableName && data?.type && data?.key) {
|
|
@@ -481,6 +519,16 @@ function getChangeDisplayName(change) {
|
|
|
481
519
|
}
|
|
482
520
|
return `${prefix} ${actionWord} ${change.objectType.replace('_', ' ')} ${change.objectName}`;
|
|
483
521
|
}
|
|
522
|
+
if (change.type === 'RENAME') {
|
|
523
|
+
const before = change.before;
|
|
524
|
+
const after = change.after;
|
|
525
|
+
if (before?.name && after?.name) {
|
|
526
|
+
if (change.parentName) {
|
|
527
|
+
return `${prefix} RENAME ${change.objectType} ${change.parentName}.${before.name} → ${after.name}`;
|
|
528
|
+
}
|
|
529
|
+
return `${prefix} RENAME ${change.objectType} ${before.name} → ${after.name}`;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
484
532
|
if (change.parentName) {
|
|
485
533
|
return `${prefix} ${change.objectType} ${change.parentName}.${change.objectName}`;
|
|
486
534
|
}
|
|
@@ -503,6 +551,7 @@ function sortChangesByDependency(changes) {
|
|
|
503
551
|
'COLUMN': 13,
|
|
504
552
|
'COLUMN_COMMENT': 12,
|
|
505
553
|
'INDEX': 13,
|
|
554
|
+
'INDEX_COMMENT': 13,
|
|
506
555
|
'CONSTRAINT': 14,
|
|
507
556
|
'PRIMARY_KEY': 14,
|
|
508
557
|
'FOREIGN_KEY': 15,
|
|
@@ -32,6 +32,9 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
32
32
|
return result;
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
35
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
39
|
exports.colors = void 0;
|
|
37
40
|
exports.createSpinner = createSpinner;
|
|
@@ -46,8 +49,11 @@ exports.formatBytes = formatBytes;
|
|
|
46
49
|
exports.formatDuration = formatDuration;
|
|
47
50
|
exports.progressBar = progressBar;
|
|
48
51
|
exports.requireInit = requireInit;
|
|
52
|
+
exports.createMultiProgress = createMultiProgress;
|
|
49
53
|
const readline = __importStar(require("readline"));
|
|
54
|
+
const cli_spinners_1 = __importDefault(require("cli-spinners"));
|
|
50
55
|
const isColorSupported = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
56
|
+
const isTTY = process.stdout.isTTY;
|
|
51
57
|
exports.colors = {
|
|
52
58
|
reset: '\x1b[0m',
|
|
53
59
|
red: (s) => isColorSupported ? `\x1b[31m${s}\x1b[0m` : s,
|
|
@@ -215,3 +221,148 @@ function requireInit(isInitialized, projectRoot) {
|
|
|
215
221
|
fatal('not a relq repository (or any of the parent directories): .relq', "run 'relq init' to initialize a repository");
|
|
216
222
|
}
|
|
217
223
|
}
|
|
224
|
+
function createMultiProgress() {
|
|
225
|
+
const spinner = cli_spinners_1.default.dots;
|
|
226
|
+
let items = [];
|
|
227
|
+
let frameIndex = 0;
|
|
228
|
+
let interval = null;
|
|
229
|
+
let isRunning = false;
|
|
230
|
+
let lineCount = 0;
|
|
231
|
+
let lastPrintedStatus = new Map();
|
|
232
|
+
const moveCursorUp = (lines) => {
|
|
233
|
+
if (isTTY && lines > 0) {
|
|
234
|
+
process.stdout.write(`\x1b[${lines}A`);
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
const clearLine = () => {
|
|
238
|
+
if (isTTY) {
|
|
239
|
+
process.stdout.write('\x1b[2K\r');
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
const padNumber = (n, width = 4) => {
|
|
243
|
+
return n.toString().padStart(width);
|
|
244
|
+
};
|
|
245
|
+
const render = () => {
|
|
246
|
+
if (!isRunning || !isTTY)
|
|
247
|
+
return;
|
|
248
|
+
if (lineCount > 0) {
|
|
249
|
+
moveCursorUp(lineCount);
|
|
250
|
+
}
|
|
251
|
+
const frame = spinner.frames[frameIndex];
|
|
252
|
+
frameIndex = (frameIndex + 1) % spinner.frames.length;
|
|
253
|
+
for (const item of items) {
|
|
254
|
+
clearLine();
|
|
255
|
+
let prefix;
|
|
256
|
+
let statusText;
|
|
257
|
+
let countStr = padNumber(item.count);
|
|
258
|
+
if (item.status === 'pending') {
|
|
259
|
+
prefix = exports.colors.gray('[ ]');
|
|
260
|
+
statusText = exports.colors.gray(`${countStr} ${item.label} waiting`);
|
|
261
|
+
}
|
|
262
|
+
else if (item.status === 'fetching') {
|
|
263
|
+
prefix = exports.colors.cyan(`[${frame}]`);
|
|
264
|
+
statusText = exports.colors.white(`${countStr} ${item.label} fetching`);
|
|
265
|
+
}
|
|
266
|
+
else if (item.status === 'done') {
|
|
267
|
+
prefix = exports.colors.green('[+]');
|
|
268
|
+
statusText = exports.colors.green(`${countStr} ${item.label} fetched`);
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
prefix = exports.colors.red('[x]');
|
|
272
|
+
statusText = exports.colors.red(`${countStr} ${item.label} error`);
|
|
273
|
+
}
|
|
274
|
+
process.stdout.write(`${prefix} ${statusText}\n`);
|
|
275
|
+
}
|
|
276
|
+
lineCount = items.length;
|
|
277
|
+
};
|
|
278
|
+
const printStaticLine = (item) => {
|
|
279
|
+
let prefix;
|
|
280
|
+
let statusText;
|
|
281
|
+
let countStr = padNumber(item.count);
|
|
282
|
+
if (item.status === 'error') {
|
|
283
|
+
prefix = '[x]';
|
|
284
|
+
statusText = `${countStr} ${item.label} error`;
|
|
285
|
+
}
|
|
286
|
+
else if (item.status === 'done' && item.count > 0) {
|
|
287
|
+
prefix = '[+]';
|
|
288
|
+
statusText = `${countStr} ${item.label} fetched`;
|
|
289
|
+
}
|
|
290
|
+
else if (item.status === 'done') {
|
|
291
|
+
prefix = '[+]';
|
|
292
|
+
statusText = `${countStr} ${item.label} fetched`;
|
|
293
|
+
}
|
|
294
|
+
else if (item.status === 'fetching') {
|
|
295
|
+
prefix = '[~]';
|
|
296
|
+
statusText = `${countStr} ${item.label} fetching`;
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
prefix = '[ ]';
|
|
300
|
+
statusText = `${countStr} ${item.label} waiting`;
|
|
301
|
+
}
|
|
302
|
+
console.log(`${prefix} ${statusText}`);
|
|
303
|
+
};
|
|
304
|
+
return {
|
|
305
|
+
setItems(newItems) {
|
|
306
|
+
items = [...newItems];
|
|
307
|
+
lastPrintedStatus.clear();
|
|
308
|
+
},
|
|
309
|
+
updateItem(id, updates) {
|
|
310
|
+
const item = items.find(i => i.id === id);
|
|
311
|
+
if (item) {
|
|
312
|
+
const prevStatus = item.status;
|
|
313
|
+
Object.assign(item, updates);
|
|
314
|
+
if (!isTTY && isRunning) {
|
|
315
|
+
const statusKey = `${item.id}:${item.status}:${item.count}`;
|
|
316
|
+
if (lastPrintedStatus.get(item.id) !== statusKey) {
|
|
317
|
+
printStaticLine(item);
|
|
318
|
+
lastPrintedStatus.set(item.id, statusKey);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
},
|
|
323
|
+
start() {
|
|
324
|
+
if (isRunning)
|
|
325
|
+
return;
|
|
326
|
+
isRunning = true;
|
|
327
|
+
frameIndex = 0;
|
|
328
|
+
lineCount = 0;
|
|
329
|
+
lastPrintedStatus.clear();
|
|
330
|
+
if (isTTY) {
|
|
331
|
+
interval = setInterval(render, spinner.interval);
|
|
332
|
+
render();
|
|
333
|
+
}
|
|
334
|
+
},
|
|
335
|
+
stop() {
|
|
336
|
+
if (interval) {
|
|
337
|
+
clearInterval(interval);
|
|
338
|
+
interval = null;
|
|
339
|
+
}
|
|
340
|
+
isRunning = false;
|
|
341
|
+
},
|
|
342
|
+
complete() {
|
|
343
|
+
this.stop();
|
|
344
|
+
if (isTTY && lineCount > 0) {
|
|
345
|
+
moveCursorUp(lineCount);
|
|
346
|
+
for (const item of items) {
|
|
347
|
+
clearLine();
|
|
348
|
+
let prefix;
|
|
349
|
+
let statusText;
|
|
350
|
+
let countStr = padNumber(item.count);
|
|
351
|
+
if (item.status === 'error') {
|
|
352
|
+
prefix = exports.colors.red('[x]');
|
|
353
|
+
statusText = exports.colors.red(`${countStr} ${item.label} error`);
|
|
354
|
+
}
|
|
355
|
+
else if (item.status === 'done' && item.count > 0) {
|
|
356
|
+
prefix = exports.colors.green('[+]');
|
|
357
|
+
statusText = exports.colors.green(`${countStr} ${item.label} fetched`);
|
|
358
|
+
}
|
|
359
|
+
else {
|
|
360
|
+
prefix = exports.colors.dim('[+]');
|
|
361
|
+
statusText = exports.colors.dim(`${countStr} ${item.label} fetched`);
|
|
362
|
+
}
|
|
363
|
+
process.stdout.write(`${prefix} ${statusText}\n`);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
}
|
|
@@ -47,7 +47,7 @@ function parseOptionsArray(options) {
|
|
|
47
47
|
return result;
|
|
48
48
|
}
|
|
49
49
|
async function fastIntrospectDatabase(connection, onProgress, options) {
|
|
50
|
-
const { includeFunctions = false, includeTriggers = false } = options || {};
|
|
50
|
+
const { includeFunctions = false, includeTriggers = false, onDetailedProgress } = options || {};
|
|
51
51
|
const { Pool } = await Promise.resolve().then(() => __importStar(require("../../addon/pg/index.cjs")));
|
|
52
52
|
onProgress?.('connecting', connection.database);
|
|
53
53
|
const pool = new Pool({
|
|
@@ -61,6 +61,7 @@ async function fastIntrospectDatabase(connection, onProgress, options) {
|
|
|
61
61
|
});
|
|
62
62
|
try {
|
|
63
63
|
onProgress?.('fetching_tables');
|
|
64
|
+
onDetailedProgress?.({ step: 'tables', count: 0, status: 'fetching' });
|
|
64
65
|
const tablesResult = await pool.query(`
|
|
65
66
|
SELECT
|
|
66
67
|
c.relname as table_name,
|
|
@@ -68,6 +69,7 @@ async function fastIntrospectDatabase(connection, onProgress, options) {
|
|
|
68
69
|
c.reltuples::bigint as row_count,
|
|
69
70
|
c.relispartition as is_partition,
|
|
70
71
|
c.relkind = 'p' as is_partitioned,
|
|
72
|
+
obj_description(c.oid, 'pg_class') as table_comment,
|
|
71
73
|
CASE
|
|
72
74
|
WHEN pt.partstrat = 'l' THEN 'LIST'
|
|
73
75
|
WHEN pt.partstrat = 'r' THEN 'RANGE'
|
|
@@ -88,7 +90,9 @@ async function fastIntrospectDatabase(connection, onProgress, options) {
|
|
|
88
90
|
AND NOT c.relispartition
|
|
89
91
|
ORDER BY c.relname;
|
|
90
92
|
`);
|
|
93
|
+
onDetailedProgress?.({ step: 'tables', count: tablesResult.rows.length, status: 'done' });
|
|
91
94
|
onProgress?.('fetching_columns');
|
|
95
|
+
onDetailedProgress?.({ step: 'columns', count: 0, status: 'fetching' });
|
|
92
96
|
const columnsResult = await pool.query(`
|
|
93
97
|
SELECT
|
|
94
98
|
c.table_name,
|
|
@@ -110,26 +114,43 @@ async function fastIntrospectDatabase(connection, onProgress, options) {
|
|
|
110
114
|
WHERE c.table_schema = 'public'
|
|
111
115
|
ORDER BY c.table_name, c.ordinal_position;
|
|
112
116
|
`);
|
|
117
|
+
onDetailedProgress?.({ step: 'columns', count: columnsResult.rows.length, status: 'done' });
|
|
118
|
+
onDetailedProgress?.({ step: 'constraints', count: 0, status: 'fetching' });
|
|
113
119
|
const constraintsResult = await pool.query(`
|
|
114
120
|
SELECT
|
|
115
121
|
tc.table_name,
|
|
116
122
|
tc.constraint_name,
|
|
117
123
|
tc.constraint_type,
|
|
118
|
-
|
|
124
|
+
(
|
|
125
|
+
SELECT array_agg(kcu.column_name ORDER BY kcu.ordinal_position)
|
|
126
|
+
FROM information_schema.key_column_usage kcu
|
|
127
|
+
WHERE kcu.constraint_name = tc.constraint_name
|
|
128
|
+
AND kcu.table_schema = tc.table_schema
|
|
129
|
+
AND kcu.table_name = tc.table_name
|
|
130
|
+
) as columns,
|
|
119
131
|
ccu.table_name as referenced_table,
|
|
120
|
-
|
|
132
|
+
(
|
|
133
|
+
SELECT array_agg(a.attname ORDER BY array_position(con.confkey, a.attnum))
|
|
134
|
+
FROM pg_constraint con
|
|
135
|
+
JOIN pg_class rel ON con.conrelid = rel.oid
|
|
136
|
+
JOIN pg_namespace nsp ON rel.relnamespace = nsp.oid
|
|
137
|
+
JOIN pg_attribute a ON a.attrelid = con.confrelid AND a.attnum = ANY(con.confkey)
|
|
138
|
+
WHERE con.conname = tc.constraint_name
|
|
139
|
+
AND nsp.nspname = tc.table_schema
|
|
140
|
+
AND rel.relname = tc.table_name
|
|
141
|
+
) as referenced_columns,
|
|
121
142
|
rc.update_rule as on_update,
|
|
122
143
|
rc.delete_rule as on_delete
|
|
123
144
|
FROM information_schema.table_constraints tc
|
|
124
|
-
JOIN information_schema.key_column_usage kcu
|
|
125
|
-
ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema
|
|
126
145
|
LEFT JOIN information_schema.constraint_column_usage ccu
|
|
127
146
|
ON tc.constraint_name = ccu.constraint_name AND tc.constraint_type = 'FOREIGN KEY'
|
|
128
147
|
LEFT JOIN information_schema.referential_constraints rc
|
|
129
148
|
ON tc.constraint_name = rc.constraint_name AND tc.table_schema = rc.constraint_schema
|
|
130
149
|
WHERE tc.table_schema = 'public'
|
|
131
|
-
GROUP BY tc.table_name, tc.constraint_name, tc.constraint_type, ccu.table_name, rc.update_rule, rc.delete_rule;
|
|
150
|
+
GROUP BY tc.table_schema, tc.table_name, tc.constraint_name, tc.constraint_type, ccu.table_name, rc.update_rule, rc.delete_rule;
|
|
132
151
|
`);
|
|
152
|
+
onDetailedProgress?.({ step: 'constraints', count: constraintsResult.rows.length, status: 'done' });
|
|
153
|
+
onDetailedProgress?.({ step: 'indexes', count: 0, status: 'fetching' });
|
|
133
154
|
const indexesResult = await pool.query(`
|
|
134
155
|
SELECT
|
|
135
156
|
t.relname as table_name,
|
|
@@ -140,6 +161,7 @@ async function fastIntrospectDatabase(connection, onProgress, options) {
|
|
|
140
161
|
pg_get_indexdef(ix.indexrelid) as index_definition,
|
|
141
162
|
pg_get_expr(ix.indpred, ix.indrelid) as where_clause,
|
|
142
163
|
pg_get_expr(ix.indexprs, ix.indrelid) as expression,
|
|
164
|
+
obj_description(i.oid, 'pg_class') as index_comment,
|
|
143
165
|
array_agg(a.attname ORDER BY array_position(ix.indkey, a.attnum)) FILTER (WHERE a.attnum > 0) as columns
|
|
144
166
|
FROM pg_index ix
|
|
145
167
|
JOIN pg_class t ON t.oid = ix.indrelid
|
|
@@ -149,8 +171,10 @@ async function fastIntrospectDatabase(connection, onProgress, options) {
|
|
|
149
171
|
LEFT JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = ANY(ix.indkey)
|
|
150
172
|
WHERE n.nspname = 'public'
|
|
151
173
|
AND NOT ix.indisprimary
|
|
152
|
-
GROUP BY t.relname, i.relname, am.amname, ix.indisunique, ix.indisprimary, ix.indexrelid, ix.indpred, ix.indexprs, ix.indrelid;
|
|
174
|
+
GROUP BY t.relname, i.relname, i.oid, am.amname, ix.indisunique, ix.indisprimary, ix.indexrelid, ix.indpred, ix.indexprs, ix.indrelid;
|
|
153
175
|
`);
|
|
176
|
+
onDetailedProgress?.({ step: 'indexes', count: indexesResult.rows.length, status: 'done' });
|
|
177
|
+
onDetailedProgress?.({ step: 'checks', count: 0, status: 'fetching' });
|
|
154
178
|
const checksResult = await pool.query(`
|
|
155
179
|
SELECT
|
|
156
180
|
c.relname as table_name,
|
|
@@ -161,7 +185,9 @@ async function fastIntrospectDatabase(connection, onProgress, options) {
|
|
|
161
185
|
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
162
186
|
WHERE n.nspname = 'public' AND con.contype = 'c';
|
|
163
187
|
`);
|
|
188
|
+
onDetailedProgress?.({ step: 'checks', count: checksResult.rows.length, status: 'done' });
|
|
164
189
|
onProgress?.('fetching_enums');
|
|
190
|
+
onDetailedProgress?.({ step: 'enums', count: 0, status: 'fetching' });
|
|
165
191
|
const enumsResult = await pool.query(`
|
|
166
192
|
SELECT
|
|
167
193
|
t.typname as enum_name,
|
|
@@ -174,6 +200,7 @@ async function fastIntrospectDatabase(connection, onProgress, options) {
|
|
|
174
200
|
GROUP BY t.typname, n.nspname
|
|
175
201
|
ORDER BY t.typname;
|
|
176
202
|
`);
|
|
203
|
+
onDetailedProgress?.({ step: 'enums', count: enumsResult.rows.length, status: 'done' });
|
|
177
204
|
const enums = enumsResult.rows.map(row => ({
|
|
178
205
|
name: row.enum_name,
|
|
179
206
|
schema: row.enum_schema,
|
|
@@ -209,32 +236,46 @@ async function fastIntrospectDatabase(connection, onProgress, options) {
|
|
|
209
236
|
if (!constraintsByTable.has(con.table_name)) {
|
|
210
237
|
constraintsByTable.set(con.table_name, new Map());
|
|
211
238
|
}
|
|
239
|
+
let constraintColumns;
|
|
240
|
+
if (Array.isArray(con.columns)) {
|
|
241
|
+
constraintColumns = con.columns;
|
|
242
|
+
}
|
|
243
|
+
else if (typeof con.columns === 'string') {
|
|
244
|
+
constraintColumns = con.columns.replace(/^\{|\}$/g, '').split(',').filter(Boolean);
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
constraintColumns = [];
|
|
248
|
+
}
|
|
212
249
|
constraintsByTable.get(con.table_name).set(con.constraint_name, {
|
|
213
250
|
type: con.constraint_type,
|
|
214
|
-
columns:
|
|
251
|
+
columns: constraintColumns,
|
|
252
|
+
referenced_table: con.referenced_table,
|
|
253
|
+
referenced_columns: con.referenced_columns,
|
|
254
|
+
on_delete: con.on_delete,
|
|
255
|
+
on_update: con.on_update,
|
|
215
256
|
});
|
|
216
257
|
const columns = columnsByTable.get(con.table_name);
|
|
217
258
|
if (columns) {
|
|
218
|
-
for (const
|
|
219
|
-
|
|
259
|
+
for (const rawColName of constraintColumns) {
|
|
260
|
+
if (!rawColName)
|
|
261
|
+
continue;
|
|
262
|
+
const normalizedColName = rawColName.replace(/^"|"$/g, '').toLowerCase();
|
|
263
|
+
const col = columns.find(c => c.name.toLowerCase() === normalizedColName);
|
|
220
264
|
if (col) {
|
|
221
265
|
if (con.constraint_type === 'PRIMARY KEY')
|
|
222
266
|
col.isPrimaryKey = true;
|
|
223
|
-
if (con.constraint_type === 'UNIQUE')
|
|
224
|
-
col.isUnique = true;
|
|
225
|
-
if (con.constraint_type === 'FOREIGN KEY' && con.referenced_table) {
|
|
226
|
-
col.references = {
|
|
227
|
-
table: con.referenced_table,
|
|
228
|
-
column: con.referenced_columns?.[0] || 'id',
|
|
229
|
-
onDelete: con.on_delete || 'NO ACTION',
|
|
230
|
-
onUpdate: con.on_update || 'NO ACTION',
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
267
|
}
|
|
234
268
|
}
|
|
235
269
|
}
|
|
236
270
|
}
|
|
237
271
|
for (const idx of indexesResult.rows) {
|
|
272
|
+
const tableConstraints = constraintsByTable.get(idx.table_name);
|
|
273
|
+
if (tableConstraints?.has(idx.index_name)) {
|
|
274
|
+
const con = tableConstraints.get(idx.index_name);
|
|
275
|
+
if (con?.type === 'UNIQUE' || con?.type === 'PRIMARY KEY') {
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
238
279
|
if (!indexesByTable.has(idx.table_name)) {
|
|
239
280
|
indexesByTable.set(idx.table_name, []);
|
|
240
281
|
}
|
|
@@ -250,6 +291,7 @@ async function fastIntrospectDatabase(connection, onProgress, options) {
|
|
|
250
291
|
whereClause: idx.where_clause || null,
|
|
251
292
|
expression: idx.expression || null,
|
|
252
293
|
operatorClasses: operatorClasses.length > 0 ? operatorClasses : undefined,
|
|
294
|
+
comment: idx.index_comment || null,
|
|
253
295
|
});
|
|
254
296
|
}
|
|
255
297
|
for (const chk of checksResult.rows) {
|
|
@@ -268,19 +310,87 @@ async function fastIntrospectDatabase(connection, onProgress, options) {
|
|
|
268
310
|
const tableName = row.table_name;
|
|
269
311
|
if (tableName.startsWith('_relq') || tableName.startsWith('_kuery'))
|
|
270
312
|
continue;
|
|
313
|
+
const allConstraints = [
|
|
314
|
+
...(checksByTable.get(tableName) || []),
|
|
315
|
+
];
|
|
316
|
+
const tableConstraints = constraintsByTable.get(tableName);
|
|
317
|
+
if (tableConstraints) {
|
|
318
|
+
for (const [conName, con] of tableConstraints) {
|
|
319
|
+
let cols = [];
|
|
320
|
+
if (Array.isArray(con.columns)) {
|
|
321
|
+
cols = con.columns;
|
|
322
|
+
}
|
|
323
|
+
else if (typeof con.columns === 'string') {
|
|
324
|
+
cols = con.columns.replace(/^\{|\}$/g, '').split(',').filter(Boolean);
|
|
325
|
+
}
|
|
326
|
+
if (con.type === 'PRIMARY KEY' && cols.length > 0) {
|
|
327
|
+
const colList = cols.map(c => `"${c}"`).join(', ');
|
|
328
|
+
allConstraints.push({
|
|
329
|
+
name: conName,
|
|
330
|
+
type: 'PRIMARY KEY',
|
|
331
|
+
columns: cols,
|
|
332
|
+
definition: `PRIMARY KEY (${colList})`,
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
else if (con.type === 'UNIQUE' && cols.length > 0) {
|
|
336
|
+
const colList = cols.map(c => `"${c}"`).join(', ');
|
|
337
|
+
allConstraints.push({
|
|
338
|
+
name: conName,
|
|
339
|
+
type: 'UNIQUE',
|
|
340
|
+
columns: cols,
|
|
341
|
+
definition: `UNIQUE (${colList})`,
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
else if (con.type === 'FOREIGN KEY' && cols.length > 0) {
|
|
345
|
+
const refTable = con.referenced_table;
|
|
346
|
+
const rawRefCols = con.referenced_columns;
|
|
347
|
+
const onDelete = con.on_delete;
|
|
348
|
+
const onUpdate = con.on_update;
|
|
349
|
+
let refCols = [];
|
|
350
|
+
if (Array.isArray(rawRefCols)) {
|
|
351
|
+
refCols = rawRefCols;
|
|
352
|
+
}
|
|
353
|
+
else if (typeof rawRefCols === 'string') {
|
|
354
|
+
refCols = rawRefCols.replace(/^\{|\}$/g, '').split(',').filter(Boolean);
|
|
355
|
+
}
|
|
356
|
+
const colList = cols.map(c => `"${c}"`).join(', ');
|
|
357
|
+
const refColList = refCols.map((c) => `"${c}"`).join(', ');
|
|
358
|
+
let definition = `FOREIGN KEY (${colList}) REFERENCES "${refTable}" (${refColList})`;
|
|
359
|
+
if (onDelete && onDelete !== 'NO ACTION')
|
|
360
|
+
definition += ` ON DELETE ${onDelete}`;
|
|
361
|
+
if (onUpdate && onUpdate !== 'NO ACTION')
|
|
362
|
+
definition += ` ON UPDATE ${onUpdate}`;
|
|
363
|
+
allConstraints.push({
|
|
364
|
+
name: conName,
|
|
365
|
+
type: 'FOREIGN KEY',
|
|
366
|
+
columns: cols,
|
|
367
|
+
definition,
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
let partitionKey = [];
|
|
373
|
+
if (Array.isArray(row.partition_key)) {
|
|
374
|
+
partitionKey = row.partition_key;
|
|
375
|
+
}
|
|
376
|
+
else if (typeof row.partition_key === 'string') {
|
|
377
|
+
partitionKey = row.partition_key.replace(/^\{|\}$/g, '').split(',').filter(Boolean);
|
|
378
|
+
}
|
|
271
379
|
tables.push({
|
|
272
380
|
name: tableName,
|
|
273
381
|
schema: row.table_schema,
|
|
274
382
|
columns: columnsByTable.get(tableName) || [],
|
|
275
383
|
indexes: indexesByTable.get(tableName) || [],
|
|
276
|
-
constraints:
|
|
384
|
+
constraints: allConstraints,
|
|
277
385
|
rowCount: parseInt(row.row_count) || 0,
|
|
278
386
|
isPartitioned: row.is_partitioned || false,
|
|
279
387
|
partitionType: row.partition_type,
|
|
280
|
-
partitionKey:
|
|
388
|
+
partitionKey: partitionKey,
|
|
389
|
+
comment: row.table_comment || null,
|
|
281
390
|
});
|
|
282
391
|
}
|
|
283
392
|
onProgress?.('fetching_partitions');
|
|
393
|
+
onDetailedProgress?.({ step: 'partitions', count: 0, status: 'fetching' });
|
|
284
394
|
const partitionsResult = await pool.query(`
|
|
285
395
|
SELECT
|
|
286
396
|
child.relname as partition_name,
|
|
@@ -308,19 +418,26 @@ async function fastIntrospectDatabase(connection, onProgress, options) {
|
|
|
308
418
|
}
|
|
309
419
|
partitionsByParent.get(partition.parentTable).push(partition);
|
|
310
420
|
}
|
|
421
|
+
let attachedPartitionCount = 0;
|
|
311
422
|
for (const table of tables) {
|
|
312
423
|
if (table.isPartitioned) {
|
|
313
|
-
|
|
424
|
+
const childPartitions = partitionsByParent.get(table.name) || [];
|
|
425
|
+
table.childPartitions = childPartitions;
|
|
426
|
+
attachedPartitionCount += childPartitions.length;
|
|
314
427
|
}
|
|
315
428
|
}
|
|
429
|
+
onDetailedProgress?.({ step: 'partitions', count: attachedPartitionCount, status: 'done' });
|
|
316
430
|
onProgress?.('fetching_extensions');
|
|
431
|
+
onDetailedProgress?.({ step: 'extensions', count: 0, status: 'fetching' });
|
|
317
432
|
const extensionsResult = await pool.query(`
|
|
318
433
|
SELECT extname FROM pg_extension WHERE extname != 'plpgsql';
|
|
319
434
|
`);
|
|
320
435
|
const extensions = extensionsResult.rows.map(r => r.extname);
|
|
436
|
+
onDetailedProgress?.({ step: 'extensions', count: extensions.length, status: 'done' });
|
|
321
437
|
let functions = [];
|
|
322
438
|
if (includeFunctions) {
|
|
323
439
|
onProgress?.('fetching_functions');
|
|
440
|
+
onDetailedProgress?.({ step: 'functions', count: 0, status: 'fetching' });
|
|
324
441
|
const functionsResult = await pool.query(`
|
|
325
442
|
SELECT
|
|
326
443
|
p.proname as name,
|
|
@@ -348,10 +465,12 @@ async function fastIntrospectDatabase(connection, onProgress, options) {
|
|
|
348
465
|
isAggregate: f.is_aggregate || false,
|
|
349
466
|
volatility: f.volatility === 'i' ? 'IMMUTABLE' : f.volatility === 's' ? 'STABLE' : 'VOLATILE',
|
|
350
467
|
}));
|
|
468
|
+
onDetailedProgress?.({ step: 'functions', count: functions.length, status: 'done' });
|
|
351
469
|
}
|
|
352
470
|
let triggers = [];
|
|
353
471
|
if (includeTriggers) {
|
|
354
472
|
onProgress?.('fetching_triggers');
|
|
473
|
+
onDetailedProgress?.({ step: 'triggers', count: 0, status: 'fetching' });
|
|
355
474
|
const triggersResult = await pool.query(`
|
|
356
475
|
SELECT
|
|
357
476
|
t.tgname as name,
|
|
@@ -387,8 +506,10 @@ async function fastIntrospectDatabase(connection, onProgress, options) {
|
|
|
387
506
|
definition: t.definition || '',
|
|
388
507
|
isEnabled: t.is_enabled,
|
|
389
508
|
}));
|
|
509
|
+
onDetailedProgress?.({ step: 'triggers', count: triggers.length, status: 'done' });
|
|
390
510
|
}
|
|
391
511
|
onProgress?.('fetching_collations');
|
|
512
|
+
onDetailedProgress?.({ step: 'collations', count: 0, status: 'fetching' });
|
|
392
513
|
const collationsResult = await pool.query(`
|
|
393
514
|
SELECT
|
|
394
515
|
c.collname as name,
|
|
@@ -410,7 +531,9 @@ async function fastIntrospectDatabase(connection, onProgress, options) {
|
|
|
410
531
|
lcCtype: c.lc_ctype,
|
|
411
532
|
deterministic: c.deterministic,
|
|
412
533
|
}));
|
|
534
|
+
onDetailedProgress?.({ step: 'collations', count: collations.length, status: 'done' });
|
|
413
535
|
onProgress?.('fetching_foreign_servers');
|
|
536
|
+
onDetailedProgress?.({ step: 'foreign_servers', count: 0, status: 'fetching' });
|
|
414
537
|
const foreignServersResult = await pool.query(`
|
|
415
538
|
SELECT
|
|
416
539
|
s.srvname as name,
|
|
@@ -425,7 +548,9 @@ async function fastIntrospectDatabase(connection, onProgress, options) {
|
|
|
425
548
|
foreignDataWrapper: s.fdw,
|
|
426
549
|
options: parseOptionsArray(s.options),
|
|
427
550
|
}));
|
|
551
|
+
onDetailedProgress?.({ step: 'foreign_servers', count: foreignServers.length, status: 'done' });
|
|
428
552
|
onProgress?.('fetching_foreign_tables');
|
|
553
|
+
onDetailedProgress?.({ step: 'foreign_tables', count: 0, status: 'fetching' });
|
|
429
554
|
const foreignTablesResult = await pool.query(`
|
|
430
555
|
SELECT
|
|
431
556
|
c.relname as name,
|
|
@@ -481,6 +606,7 @@ async function fastIntrospectDatabase(connection, onProgress, options) {
|
|
|
481
606
|
})),
|
|
482
607
|
options: parseOptionsArray(t.options),
|
|
483
608
|
}));
|
|
609
|
+
onDetailedProgress?.({ step: 'foreign_tables', count: foreignTables.length, status: 'done' });
|
|
484
610
|
onProgress?.('complete');
|
|
485
611
|
return {
|
|
486
612
|
tables,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";
|