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
|
@@ -67,6 +67,81 @@ export function generateHash(schema) {
|
|
|
67
67
|
export function shortHash(hash) {
|
|
68
68
|
return hash.substring(0, 7);
|
|
69
69
|
}
|
|
70
|
+
export function getConnectionId(connection) {
|
|
71
|
+
const parts = [
|
|
72
|
+
connection.host || 'localhost',
|
|
73
|
+
String(connection.port || 5432),
|
|
74
|
+
connection.database || 'postgres',
|
|
75
|
+
connection.user || 'postgres',
|
|
76
|
+
];
|
|
77
|
+
return crypto.createHash('sha256').update(parts.join('/')).digest('hex').substring(0, 12);
|
|
78
|
+
}
|
|
79
|
+
export function getConnectionLabel(connection) {
|
|
80
|
+
const user = connection.user || 'postgres';
|
|
81
|
+
const host = connection.host || 'localhost';
|
|
82
|
+
const db = connection.database || 'postgres';
|
|
83
|
+
return `${user}@${host}/${db}`;
|
|
84
|
+
}
|
|
85
|
+
export function getRemoteRef(connection) {
|
|
86
|
+
return {
|
|
87
|
+
id: getConnectionId(connection),
|
|
88
|
+
label: getConnectionLabel(connection),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
export function isCommitSyncedWith(commit, connection) {
|
|
92
|
+
const connectionId = getConnectionId(connection);
|
|
93
|
+
if (commit.remotes?.pushed?.some((r) => r.id === connectionId)) {
|
|
94
|
+
return { synced: true, type: 'pushed' };
|
|
95
|
+
}
|
|
96
|
+
if (commit.remotes?.pulled?.some((r) => r.id === connectionId)) {
|
|
97
|
+
return { synced: true, type: 'pulled' };
|
|
98
|
+
}
|
|
99
|
+
if (commit.message?.startsWith('pull: sync from ')) {
|
|
100
|
+
const pullTarget = commit.message.replace('pull: sync from ', '');
|
|
101
|
+
const currentLabel = getConnectionLabel(connection);
|
|
102
|
+
const legacyLabel = `${connection.user}@${connection.host}:${connection.port || 5432}/${connection.database}`;
|
|
103
|
+
if (pullTarget === currentLabel || pullTarget === legacyLabel) {
|
|
104
|
+
return { synced: true, type: 'pulled' };
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return { synced: false, type: null };
|
|
108
|
+
}
|
|
109
|
+
export function markCommitAsPushed(commitHash, connection, projectRoot = process.cwd()) {
|
|
110
|
+
const commitPath = path.join(projectRoot, RELQ_DIR, COMMITS_DIR, `${commitHash}.json`);
|
|
111
|
+
if (!fs.existsSync(commitPath)) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const commit = JSON.parse(fs.readFileSync(commitPath, 'utf-8'));
|
|
115
|
+
const remoteRef = getRemoteRef(connection);
|
|
116
|
+
if (!commit.remotes) {
|
|
117
|
+
commit.remotes = {};
|
|
118
|
+
}
|
|
119
|
+
if (!commit.remotes.pushed) {
|
|
120
|
+
commit.remotes.pushed = [];
|
|
121
|
+
}
|
|
122
|
+
if (!commit.remotes.pushed.some((r) => r.id === remoteRef.id)) {
|
|
123
|
+
commit.remotes.pushed.push(remoteRef);
|
|
124
|
+
}
|
|
125
|
+
fs.writeFileSync(commitPath, JSON.stringify(commit, null, 2), 'utf-8');
|
|
126
|
+
}
|
|
127
|
+
export function markCommitAsPulled(commitHash, connection, projectRoot = process.cwd()) {
|
|
128
|
+
const commitPath = path.join(projectRoot, RELQ_DIR, COMMITS_DIR, `${commitHash}.json`);
|
|
129
|
+
if (!fs.existsSync(commitPath)) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const commit = JSON.parse(fs.readFileSync(commitPath, 'utf-8'));
|
|
133
|
+
const remoteRef = getRemoteRef(connection);
|
|
134
|
+
if (!commit.remotes) {
|
|
135
|
+
commit.remotes = {};
|
|
136
|
+
}
|
|
137
|
+
if (!commit.remotes.pulled) {
|
|
138
|
+
commit.remotes.pulled = [];
|
|
139
|
+
}
|
|
140
|
+
if (!commit.remotes.pulled.some((r) => r.id === remoteRef.id)) {
|
|
141
|
+
commit.remotes.pulled.push(remoteRef);
|
|
142
|
+
}
|
|
143
|
+
fs.writeFileSync(commitPath, JSON.stringify(commit, null, 2), 'utf-8');
|
|
144
|
+
}
|
|
70
145
|
export function saveCommit(commit, projectRoot = process.cwd()) {
|
|
71
146
|
const commitsPath = path.join(projectRoot, RELQ_DIR, COMMITS_DIR);
|
|
72
147
|
const commitPath = path.join(commitsPath, `${commit.hash}.json`);
|
|
@@ -269,7 +344,9 @@ export function stageChanges(patterns, projectRoot = process.cwd()) {
|
|
|
269
344
|
remaining.push(change);
|
|
270
345
|
}
|
|
271
346
|
}
|
|
272
|
-
|
|
347
|
+
const newKeys = new Set(staged.map(c => `${c.objectType}:${c.objectName}:${c.parentName || ''}`));
|
|
348
|
+
const keptStaged = state.staged.filter(c => !newKeys.has(`${c.objectType}:${c.objectName}:${c.parentName || ''}`));
|
|
349
|
+
state.staged = [...keptStaged, ...staged];
|
|
273
350
|
state.unstaged = remaining;
|
|
274
351
|
state.timestamp = new Date().toISOString();
|
|
275
352
|
saveWorkingState(state, projectRoot);
|
|
@@ -310,6 +387,18 @@ export function getStagedChanges(projectRoot = process.cwd()) {
|
|
|
310
387
|
const state = loadWorkingState(projectRoot);
|
|
311
388
|
return state?.staged || [];
|
|
312
389
|
}
|
|
390
|
+
export function cleanupStagedChanges(validChanges, projectRoot = process.cwd()) {
|
|
391
|
+
const state = getOrCreateWorkingState(projectRoot);
|
|
392
|
+
const validKeys = new Set(validChanges.map(c => `${c.objectType}:${c.objectName}:${c.parentName || ''}`));
|
|
393
|
+
const originalCount = state.staged.length;
|
|
394
|
+
state.staged = state.staged.filter(c => validKeys.has(`${c.objectType}:${c.objectName}:${c.parentName || ''}`));
|
|
395
|
+
const removedCount = originalCount - state.staged.length;
|
|
396
|
+
if (removedCount > 0) {
|
|
397
|
+
state.timestamp = new Date().toISOString();
|
|
398
|
+
saveWorkingState(state, projectRoot);
|
|
399
|
+
}
|
|
400
|
+
return removedCount;
|
|
401
|
+
}
|
|
313
402
|
export function getUnstagedChanges(projectRoot = process.cwd()) {
|
|
314
403
|
const state = loadWorkingState(projectRoot);
|
|
315
404
|
return state?.unstaged || [];
|
|
@@ -388,7 +477,7 @@ export async function getRemoteHead(connection) {
|
|
|
388
477
|
const commits = await fetchRemoteCommits(connection, 1);
|
|
389
478
|
return commits.length > 0 ? commits[0].hash : null;
|
|
390
479
|
}
|
|
391
|
-
export async function pushCommit(connection, commit) {
|
|
480
|
+
export async function pushCommit(connection, commit, projectRoot = process.cwd()) {
|
|
392
481
|
const { Pool } = await import("../../addon/pg/index.js");
|
|
393
482
|
const pool = new Pool({
|
|
394
483
|
host: connection.host,
|
|
@@ -400,6 +489,27 @@ export async function pushCommit(connection, commit) {
|
|
|
400
489
|
ssl: connection.ssl,
|
|
401
490
|
});
|
|
402
491
|
try {
|
|
492
|
+
let schemaSnapshot = null;
|
|
493
|
+
if (commit.schema) {
|
|
494
|
+
schemaSnapshot = commit.schema;
|
|
495
|
+
}
|
|
496
|
+
else {
|
|
497
|
+
schemaSnapshot = loadSnapshot(projectRoot);
|
|
498
|
+
}
|
|
499
|
+
if (!schemaSnapshot) {
|
|
500
|
+
throw new Error(`Cannot push commit ${commit.hash}: No schema snapshot available. Run 'relq pull' first.`);
|
|
501
|
+
}
|
|
502
|
+
const stats = commit.stats || {
|
|
503
|
+
tables: 0,
|
|
504
|
+
columns: 0,
|
|
505
|
+
indexes: 0,
|
|
506
|
+
enums: 0,
|
|
507
|
+
domains: 0,
|
|
508
|
+
compositeTypes: 0,
|
|
509
|
+
sequences: 0,
|
|
510
|
+
functions: 0,
|
|
511
|
+
triggers: 0,
|
|
512
|
+
};
|
|
403
513
|
await pool.query(`
|
|
404
514
|
INSERT INTO _relq_commits (hash, parent_hash, author, message, schema_snapshot, stats)
|
|
405
515
|
VALUES ($1, $2, $3, $4, $5, $6)
|
|
@@ -409,8 +519,8 @@ export async function pushCommit(connection, commit) {
|
|
|
409
519
|
commit.parentHash,
|
|
410
520
|
commit.author,
|
|
411
521
|
commit.message,
|
|
412
|
-
JSON.stringify(
|
|
413
|
-
JSON.stringify(
|
|
522
|
+
JSON.stringify(schemaSnapshot),
|
|
523
|
+
JSON.stringify(stats),
|
|
414
524
|
]);
|
|
415
525
|
}
|
|
416
526
|
finally {
|
|
@@ -191,6 +191,7 @@ function compareTables(before, after) {
|
|
|
191
191
|
changes.push(...compareConstraints(beforeTable.constraints || [], table.constraints || [], name));
|
|
192
192
|
changes.push(...compareTableComments(beforeTable, table, name));
|
|
193
193
|
changes.push(...compareColumnComments(beforeTable.columns, table.columns, name));
|
|
194
|
+
changes.push(...compareIndexComments(beforeTable.indexes || [], table.indexes || [], name));
|
|
194
195
|
changes.push(...comparePartitions(beforeTable, table, name));
|
|
195
196
|
}
|
|
196
197
|
}
|
|
@@ -224,21 +225,46 @@ function tableToChangeData(table) {
|
|
|
224
225
|
}
|
|
225
226
|
function compareColumns(before, after, tableName) {
|
|
226
227
|
const changes = [];
|
|
227
|
-
const
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
|
|
228
|
+
const beforeByName = new Map(before.map(c => [c.name, c]));
|
|
229
|
+
const afterByName = new Map(after.map(c => [c.name, c]));
|
|
230
|
+
const beforeByTrackingId = new Map(before.filter(c => c.trackingId).map(c => [c.trackingId, c]));
|
|
231
|
+
const afterByTrackingId = new Map(after.filter(c => c.trackingId).map(c => [c.trackingId, c]));
|
|
232
|
+
const processedBefore = new Set();
|
|
233
|
+
const processedAfter = new Set();
|
|
234
|
+
for (const [trackingId, afterCol] of afterByTrackingId) {
|
|
235
|
+
const beforeCol = beforeByTrackingId.get(trackingId);
|
|
236
|
+
if (beforeCol && beforeCol.name !== afterCol.name) {
|
|
237
|
+
changes.push(createChange('RENAME', 'COLUMN', afterCol.name, columnToChangeData(beforeCol), columnToChangeData(afterCol), tableName));
|
|
238
|
+
processedBefore.add(beforeCol.name);
|
|
239
|
+
processedAfter.add(afterCol.name);
|
|
240
|
+
}
|
|
241
|
+
else if (beforeCol && beforeCol.name === afterCol.name) {
|
|
242
|
+
if (hasColumnChanged(beforeCol, afterCol)) {
|
|
243
|
+
changes.push(createChange('ALTER', 'COLUMN', afterCol.name, columnToChangeData(beforeCol), columnToChangeData(afterCol), tableName));
|
|
244
|
+
}
|
|
245
|
+
processedBefore.add(beforeCol.name);
|
|
246
|
+
processedAfter.add(afterCol.name);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
for (const [name, col] of afterByName) {
|
|
250
|
+
if (processedAfter.has(name))
|
|
251
|
+
continue;
|
|
252
|
+
if (!beforeByName.has(name)) {
|
|
231
253
|
changes.push(createChange('CREATE', 'COLUMN', name, null, columnToChangeData(col), tableName));
|
|
232
254
|
}
|
|
233
255
|
else {
|
|
234
|
-
const beforeCol =
|
|
235
|
-
if (hasColumnChanged(beforeCol, col)) {
|
|
256
|
+
const beforeCol = beforeByName.get(name);
|
|
257
|
+
if (!processedBefore.has(name) && hasColumnChanged(beforeCol, col)) {
|
|
236
258
|
changes.push(createChange('ALTER', 'COLUMN', name, columnToChangeData(beforeCol), columnToChangeData(col), tableName));
|
|
237
259
|
}
|
|
260
|
+
processedBefore.add(name);
|
|
238
261
|
}
|
|
262
|
+
processedAfter.add(name);
|
|
239
263
|
}
|
|
240
|
-
for (const [name, col] of
|
|
241
|
-
if (
|
|
264
|
+
for (const [name, col] of beforeByName) {
|
|
265
|
+
if (processedBefore.has(name))
|
|
266
|
+
continue;
|
|
267
|
+
if (!afterByName.has(name)) {
|
|
242
268
|
changes.push(createChange('DROP', 'COLUMN', name, columnToChangeData(col), null, tableName));
|
|
243
269
|
}
|
|
244
270
|
}
|
|
@@ -255,6 +281,7 @@ function columnToChangeData(col) {
|
|
|
255
281
|
maxLength: col.maxLength,
|
|
256
282
|
precision: col.precision,
|
|
257
283
|
scale: col.scale,
|
|
284
|
+
trackingId: col.trackingId,
|
|
258
285
|
};
|
|
259
286
|
}
|
|
260
287
|
function normalizeDataType(type) {
|
|
@@ -321,10 +348,28 @@ function hasColumnChanged(before, after) {
|
|
|
321
348
|
}
|
|
322
349
|
function compareIndexes(before, after, tableName) {
|
|
323
350
|
const changes = [];
|
|
324
|
-
const
|
|
325
|
-
const
|
|
326
|
-
|
|
327
|
-
|
|
351
|
+
const beforeByName = new Map(before.map(i => [i.name, i]));
|
|
352
|
+
const afterByName = new Map(after.map(i => [i.name, i]));
|
|
353
|
+
const beforeByTrackingId = new Map(before.filter(i => i.trackingId).map(i => [i.trackingId, i]));
|
|
354
|
+
const afterByTrackingId = new Map(after.filter(i => i.trackingId).map(i => [i.trackingId, i]));
|
|
355
|
+
const processedBefore = new Set();
|
|
356
|
+
const processedAfter = new Set();
|
|
357
|
+
for (const [trackingId, afterIdx] of afterByTrackingId) {
|
|
358
|
+
const beforeIdx = beforeByTrackingId.get(trackingId);
|
|
359
|
+
if (beforeIdx && beforeIdx.name !== afterIdx.name && !beforeIdx.isPrimary && !afterIdx.isPrimary) {
|
|
360
|
+
changes.push(createChange('RENAME', 'INDEX', afterIdx.name, { name: beforeIdx.name, tableName, columns: beforeIdx.columns }, { name: afterIdx.name, tableName, columns: afterIdx.columns, isUnique: afterIdx.isUnique, type: afterIdx.type }));
|
|
361
|
+
processedBefore.add(beforeIdx.name);
|
|
362
|
+
processedAfter.add(afterIdx.name);
|
|
363
|
+
}
|
|
364
|
+
else if (beforeIdx && beforeIdx.name === afterIdx.name) {
|
|
365
|
+
processedBefore.add(beforeIdx.name);
|
|
366
|
+
processedAfter.add(afterIdx.name);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
for (const [name, idx] of afterByName) {
|
|
370
|
+
if (processedAfter.has(name) || idx.isPrimary)
|
|
371
|
+
continue;
|
|
372
|
+
if (!beforeByName.has(name)) {
|
|
328
373
|
changes.push(createChange('CREATE', 'INDEX', name, null, {
|
|
329
374
|
name: idx.name,
|
|
330
375
|
tableName,
|
|
@@ -333,9 +378,12 @@ function compareIndexes(before, after, tableName) {
|
|
|
333
378
|
type: idx.type,
|
|
334
379
|
}));
|
|
335
380
|
}
|
|
381
|
+
processedAfter.add(name);
|
|
336
382
|
}
|
|
337
|
-
for (const [name, idx] of
|
|
338
|
-
if (
|
|
383
|
+
for (const [name, idx] of beforeByName) {
|
|
384
|
+
if (processedBefore.has(name) || idx.isPrimary)
|
|
385
|
+
continue;
|
|
386
|
+
if (!afterByName.has(name)) {
|
|
339
387
|
changes.push(createChange('DROP', 'INDEX', name, {
|
|
340
388
|
name: idx.name,
|
|
341
389
|
tableName,
|
|
@@ -501,6 +549,42 @@ function compareColumnComments(before, after, tableName) {
|
|
|
501
549
|
}
|
|
502
550
|
return changes;
|
|
503
551
|
}
|
|
552
|
+
function compareIndexComments(before, after, tableName) {
|
|
553
|
+
const changes = [];
|
|
554
|
+
const beforeMap = new Map(before.map(i => [i.name, i]));
|
|
555
|
+
const afterMap = new Map(after.map(i => [i.name, i]));
|
|
556
|
+
for (const [name, afterIdx] of afterMap) {
|
|
557
|
+
const beforeIdx = beforeMap.get(name);
|
|
558
|
+
const afterComment = afterIdx.comment || null;
|
|
559
|
+
const beforeComment = beforeIdx ? beforeIdx.comment || null : null;
|
|
560
|
+
if (afterComment && !beforeComment) {
|
|
561
|
+
changes.push(createChange('CREATE', 'INDEX_COMMENT', name, null, {
|
|
562
|
+
tableName,
|
|
563
|
+
indexName: name,
|
|
564
|
+
comment: afterComment,
|
|
565
|
+
}, tableName));
|
|
566
|
+
}
|
|
567
|
+
else if (!afterComment && beforeComment) {
|
|
568
|
+
changes.push(createChange('DROP', 'INDEX_COMMENT', name, {
|
|
569
|
+
tableName,
|
|
570
|
+
indexName: name,
|
|
571
|
+
comment: beforeComment,
|
|
572
|
+
}, null, tableName));
|
|
573
|
+
}
|
|
574
|
+
else if (afterComment && beforeComment && afterComment !== beforeComment) {
|
|
575
|
+
changes.push(createChange('ALTER', 'INDEX_COMMENT', name, {
|
|
576
|
+
tableName,
|
|
577
|
+
indexName: name,
|
|
578
|
+
comment: beforeComment,
|
|
579
|
+
}, {
|
|
580
|
+
tableName,
|
|
581
|
+
indexName: name,
|
|
582
|
+
comment: afterComment,
|
|
583
|
+
}, tableName));
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
return changes;
|
|
587
|
+
}
|
|
504
588
|
function comparePartitions(before, after, tableName) {
|
|
505
589
|
const changes = [];
|
|
506
590
|
const beforePartitioned = before.isPartitioned;
|
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
function mapInternalToFriendlyType(internalType) {
|
|
2
|
+
const typeMap = {
|
|
3
|
+
'int2': 'smallint',
|
|
4
|
+
'int4': 'integer',
|
|
5
|
+
'int8': 'bigint',
|
|
6
|
+
'float4': 'real',
|
|
7
|
+
'float8': 'double precision',
|
|
8
|
+
'bool': 'boolean',
|
|
9
|
+
'timestamptz': 'timestamp with time zone',
|
|
10
|
+
'timetz': 'time with time zone',
|
|
11
|
+
'varchar': 'character varying',
|
|
12
|
+
'bpchar': 'character',
|
|
13
|
+
'varbit': 'bit varying',
|
|
14
|
+
};
|
|
15
|
+
return typeMap[internalType] || internalType;
|
|
16
|
+
}
|
|
1
17
|
export async function introspectDatabase(connection, onProgress, options) {
|
|
2
18
|
const { includeFunctions = false, includeTriggers = false } = options || {};
|
|
3
19
|
const { Pool } = await import("../../addon/pg/index.js");
|
|
@@ -17,7 +33,8 @@ export async function introspectDatabase(connection, onProgress, options) {
|
|
|
17
33
|
SELECT
|
|
18
34
|
t.table_name,
|
|
19
35
|
t.table_schema,
|
|
20
|
-
(SELECT reltuples::bigint FROM pg_class WHERE relname = t.table_name) as row_count
|
|
36
|
+
(SELECT reltuples::bigint FROM pg_class WHERE relname = t.table_name) as row_count,
|
|
37
|
+
obj_description((quote_ident(t.table_schema) || '.' || quote_ident(t.table_name))::regclass, 'pg_class') as table_comment
|
|
21
38
|
FROM information_schema.tables t
|
|
22
39
|
WHERE t.table_schema = 'public'
|
|
23
40
|
AND t.table_type = 'BASE TABLE'
|
|
@@ -74,11 +91,17 @@ export async function introspectDatabase(connection, onProgress, options) {
|
|
|
74
91
|
WHERE tc.table_name = $1 AND tc.constraint_type = 'PRIMARY KEY'
|
|
75
92
|
) pk ON pk.column_name = c.column_name
|
|
76
93
|
LEFT JOIN (
|
|
94
|
+
-- Only mark as unique if column is the ONLY column in the unique constraint
|
|
95
|
+
-- (not part of a composite unique constraint)
|
|
77
96
|
SELECT kcu.column_name, true as is_unique
|
|
78
97
|
FROM information_schema.table_constraints tc
|
|
79
98
|
JOIN information_schema.key_column_usage kcu
|
|
80
99
|
ON tc.constraint_name = kcu.constraint_name
|
|
81
100
|
WHERE tc.table_name = $1 AND tc.constraint_type = 'UNIQUE'
|
|
101
|
+
AND (
|
|
102
|
+
SELECT COUNT(*) FROM information_schema.key_column_usage kcu2
|
|
103
|
+
WHERE kcu2.constraint_name = tc.constraint_name
|
|
104
|
+
) = 1
|
|
82
105
|
) uq ON uq.column_name = c.column_name
|
|
83
106
|
LEFT JOIN (
|
|
84
107
|
SELECT
|
|
@@ -95,29 +118,41 @@ export async function introspectDatabase(connection, onProgress, options) {
|
|
|
95
118
|
WHERE c.table_name = $1 AND c.table_schema = $2
|
|
96
119
|
ORDER BY c.ordinal_position;
|
|
97
120
|
`, [tableName, tableSchema]);
|
|
98
|
-
const columns = columnsResult.rows.map(col =>
|
|
99
|
-
|
|
100
|
-
dataType
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
121
|
+
const columns = columnsResult.rows.map(col => {
|
|
122
|
+
let dataType = col.data_type;
|
|
123
|
+
if (dataType === 'ARRAY' && col.udt_name) {
|
|
124
|
+
const baseType = col.udt_name.startsWith('_') ? col.udt_name.slice(1) : col.udt_name;
|
|
125
|
+
const friendlyBase = mapInternalToFriendlyType(baseType);
|
|
126
|
+
dataType = `${friendlyBase}[]`;
|
|
127
|
+
}
|
|
128
|
+
else if (dataType === 'USER-DEFINED' && col.udt_name) {
|
|
129
|
+
dataType = col.udt_name;
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
name: col.column_name,
|
|
133
|
+
dataType,
|
|
134
|
+
isNullable: col.is_nullable === 'YES',
|
|
135
|
+
defaultValue: col.column_default,
|
|
136
|
+
isPrimaryKey: col.is_primary_key,
|
|
137
|
+
isUnique: col.is_unique,
|
|
138
|
+
maxLength: col.character_maximum_length,
|
|
139
|
+
precision: col.numeric_precision,
|
|
140
|
+
scale: col.numeric_scale,
|
|
141
|
+
references: col.foreign_table ? {
|
|
142
|
+
table: col.foreign_table,
|
|
143
|
+
column: col.foreign_column,
|
|
144
|
+
} : null,
|
|
145
|
+
comment: col.column_comment || undefined,
|
|
146
|
+
};
|
|
147
|
+
});
|
|
114
148
|
const indexesResult = await pool.query(`
|
|
115
149
|
SELECT
|
|
116
150
|
i.relname as index_name,
|
|
117
151
|
array_agg(a.attname ORDER BY k.n) as columns,
|
|
118
152
|
ix.indisunique as is_unique,
|
|
119
153
|
ix.indisprimary as is_primary,
|
|
120
|
-
am.amname as index_type
|
|
154
|
+
am.amname as index_type,
|
|
155
|
+
obj_description(i.oid, 'pg_class') as index_comment
|
|
121
156
|
FROM pg_index ix
|
|
122
157
|
JOIN pg_class t ON t.oid = ix.indrelid
|
|
123
158
|
JOIN pg_class i ON i.oid = ix.indexrelid
|
|
@@ -125,7 +160,7 @@ export async function introspectDatabase(connection, onProgress, options) {
|
|
|
125
160
|
JOIN LATERAL unnest(ix.indkey) WITH ORDINALITY AS k(attnum, n) ON true
|
|
126
161
|
JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = k.attnum
|
|
127
162
|
WHERE t.relname = $1
|
|
128
|
-
GROUP BY i.relname, ix.indisunique, ix.indisprimary, am.amname
|
|
163
|
+
GROUP BY i.relname, i.oid, ix.indisunique, ix.indisprimary, am.amname
|
|
129
164
|
ORDER BY i.relname;
|
|
130
165
|
`, [tableName]);
|
|
131
166
|
const indexes = indexesResult.rows.map(idx => ({
|
|
@@ -134,6 +169,7 @@ export async function introspectDatabase(connection, onProgress, options) {
|
|
|
134
169
|
isUnique: idx.is_unique,
|
|
135
170
|
isPrimary: idx.is_primary,
|
|
136
171
|
type: idx.index_type,
|
|
172
|
+
comment: idx.index_comment || null,
|
|
137
173
|
}));
|
|
138
174
|
const constraintsResult = await pool.query(`
|
|
139
175
|
SELECT
|
|
@@ -201,6 +237,7 @@ export async function introspectDatabase(connection, onProgress, options) {
|
|
|
201
237
|
isPartitioned: partitionInfo.is_partitioned || false,
|
|
202
238
|
partitionType: partitionInfo.partition_type,
|
|
203
239
|
partitionKey,
|
|
240
|
+
comment: row.table_comment || null,
|
|
204
241
|
});
|
|
205
242
|
}
|
|
206
243
|
onProgress?.('fetching_extensions');
|