@lyku/lockstep-pg 1.3.1 → 1.5.0
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/README.md +1 -59
- package/package.json +31 -31
- package/pipeline.svg +1 -0
- package/src/buildTableIndexCommand.d.ts.map +1 -1
- package/src/buildTableIndexCommand.js +21 -6
- package/src/buildTableIndexCommand.js.map +1 -1
- package/src/buildTableTriggerCommands.d.ts +13 -1
- package/src/buildTableTriggerCommands.d.ts.map +1 -1
- package/src/buildTableTriggerCommands.js +33 -14
- package/src/buildTableTriggerCommands.js.map +1 -1
- package/src/createTable.d.ts.map +1 -1
- package/src/createTable.js +16 -2
- package/src/createTable.js.map +1 -1
- package/src/dateToPostgresString.js +2 -2
- package/src/dateToPostgresString.js.map +1 -1
- package/src/diff.d.ts +12 -0
- package/src/diff.d.ts.map +1 -1
- package/src/diff.js +113 -6
- package/src/diff.js.map +1 -1
- package/src/drift.d.ts +8 -11
- package/src/drift.d.ts.map +1 -1
- package/src/drift.js +1 -298
- package/src/drift.js.map +1 -1
- package/src/form.d.ts.map +1 -1
- package/src/form.js +43 -2
- package/src/form.js.map +1 -1
- package/src/generateSql.d.ts.map +1 -1
- package/src/generateSql.js +69 -8
- package/src/generateSql.js.map +1 -1
- package/src/index.d.ts +3 -3
- package/src/index.d.ts.map +1 -1
- package/src/index.js +2 -4
- package/src/index.js.map +1 -1
- package/src/introspect.d.ts +12 -0
- package/src/introspect.d.ts.map +1 -1
- package/src/introspect.js +195 -34
- package/src/introspect.js.map +1 -1
- package/src/mapColumnType.d.ts.map +1 -1
- package/src/mapColumnType.js +6 -0
- package/src/mapColumnType.js.map +1 -1
- package/src/mapSnowflakeType.d.ts +9 -0
- package/src/mapSnowflakeType.d.ts.map +1 -0
- package/src/mapSnowflakeType.js +19 -0
- package/src/mapSnowflakeType.js.map +1 -0
- package/src/migrate.d.ts +11 -9
- package/src/migrate.d.ts.map +1 -1
- package/src/migrate.js +28 -345
- package/src/migrate.js.map +1 -1
- package/src/seed.d.ts.map +1 -1
- package/src/seed.js +6 -1
- package/src/seed.js.map +1 -1
package/src/diff.js
CHANGED
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Diff two table models and generate migration operations
|
|
3
3
|
*/
|
|
4
|
+
/**
|
|
5
|
+
* Normalize a default value for comparison.
|
|
6
|
+
* PostgreSQL reports defaults with type casts (e.g., 'CURRENT_TIMESTAMP'::timestamptz),
|
|
7
|
+
* so we strip those for comparison.
|
|
8
|
+
*/
|
|
9
|
+
function normalizeDefault(value) {
|
|
10
|
+
if (value === null || value === undefined)
|
|
11
|
+
return null;
|
|
12
|
+
// Strip type casts including multi-word types, e.g. ::regclass, ::text,
|
|
13
|
+
// ::character varying, ::timestamp with time zone, ::double precision.
|
|
14
|
+
return value
|
|
15
|
+
.replace(/::"?[\w ]+"?(\[\])?/g, '')
|
|
16
|
+
.trim()
|
|
17
|
+
.toLowerCase();
|
|
18
|
+
}
|
|
4
19
|
/**
|
|
5
20
|
* Check if two sets of columns match (order-independent)
|
|
6
21
|
*/
|
|
@@ -53,6 +68,23 @@ function diffColumn(tableName, dbCol, codeCol) {
|
|
|
53
68
|
fromType: dbCol.type,
|
|
54
69
|
toType: codeCol.type,
|
|
55
70
|
toArrayItemType: codeCol.arrayItemType,
|
|
71
|
+
toLength: codeCol.maxLength ?? null,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
else if ((codeCol.type === 'char' || codeCol.type === 'varchar') &&
|
|
75
|
+
codeCol.maxLength != null &&
|
|
76
|
+
dbCol.maxLength != null &&
|
|
77
|
+
codeCol.maxLength !== dbCol.maxLength) {
|
|
78
|
+
// Same base type, different length (char(4) -> char(6), varchar(50) -> varchar(255)).
|
|
79
|
+
// Without this the engine is blind to length drift.
|
|
80
|
+
ops.push({
|
|
81
|
+
type: 'alter_column_type',
|
|
82
|
+
tableName,
|
|
83
|
+
columnName: dbCol.name,
|
|
84
|
+
fromType: `${dbCol.type}(${dbCol.maxLength ?? ''})`,
|
|
85
|
+
toType: codeCol.type,
|
|
86
|
+
toArrayItemType: codeCol.arrayItemType,
|
|
87
|
+
toLength: codeCol.maxLength ?? null,
|
|
56
88
|
});
|
|
57
89
|
}
|
|
58
90
|
// Nullable mismatch
|
|
@@ -64,6 +96,47 @@ function diffColumn(tableName, dbCol, codeCol) {
|
|
|
64
96
|
nullable: codeCol.nullable,
|
|
65
97
|
});
|
|
66
98
|
}
|
|
99
|
+
// Default mismatch
|
|
100
|
+
if (normalizeDefault(dbCol.default) !== normalizeDefault(codeCol.default)) {
|
|
101
|
+
// If the NEW default is a nextval() sequence, create the sequence
|
|
102
|
+
// before SET DEFAULT references it (LYK-1214). Otherwise the
|
|
103
|
+
// generator emits `SET DEFAULT nextval('x_id_seq')` for a sequence
|
|
104
|
+
// that doesn't exist (e.g. a serial model whose backing sequence was
|
|
105
|
+
// dropped), which fails with "relation does not exist". IF NOT
|
|
106
|
+
// EXISTS keeps it a no-op when the sequence is already present.
|
|
107
|
+
if (codeCol.default?.includes('nextval(')) {
|
|
108
|
+
const seqMatch = codeCol.default.match(/nextval\('([^']+)'/);
|
|
109
|
+
if (seqMatch) {
|
|
110
|
+
ops.push({
|
|
111
|
+
type: 'create_sequence',
|
|
112
|
+
tableName,
|
|
113
|
+
columnName: dbCol.name,
|
|
114
|
+
sequenceName: seqMatch[1],
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
ops.push({
|
|
119
|
+
type: 'alter_column_default',
|
|
120
|
+
tableName,
|
|
121
|
+
columnName: dbCol.name,
|
|
122
|
+
defaultValue: codeCol.default,
|
|
123
|
+
});
|
|
124
|
+
// If the old default was a nextval() sequence and the new default is null,
|
|
125
|
+
// the sequence is now orphaned and should be dropped
|
|
126
|
+
if (dbCol.default &&
|
|
127
|
+
dbCol.default.includes('nextval(') &&
|
|
128
|
+
codeCol.default === null) {
|
|
129
|
+
const seqMatch = dbCol.default.match(/nextval\('([^']+)'/);
|
|
130
|
+
if (seqMatch) {
|
|
131
|
+
ops.push({
|
|
132
|
+
type: 'drop_sequence',
|
|
133
|
+
tableName,
|
|
134
|
+
columnName: dbCol.name,
|
|
135
|
+
sequenceName: seqMatch[1],
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
67
140
|
// Enum values mismatch (CHECK constraint)
|
|
68
141
|
if (!enumValuesMatch(dbCol.enumValues, codeCol.enumValues)) {
|
|
69
142
|
ops.push({
|
|
@@ -86,27 +159,57 @@ function diffIndexes(tableName, dbIndexes, codeIndexes) {
|
|
|
86
159
|
const dbNonPk = dbIndexes.filter((i) => !i.name.endsWith('_pkey') && !i.name.includes('_pkey'));
|
|
87
160
|
// Find indexes in code that don't exist in DB
|
|
88
161
|
for (const codeIdx of codeIndexes) {
|
|
89
|
-
const found = dbNonPk.some((dbIdx) =>
|
|
162
|
+
const found = dbNonPk.some((dbIdx) => dbIdx.name === codeIdx.name ||
|
|
163
|
+
columnsMatch(dbIdx.columns, codeIdx.columns));
|
|
90
164
|
if (!found) {
|
|
91
165
|
ops.push({ type: 'add_index', tableName, index: codeIdx });
|
|
92
166
|
}
|
|
93
167
|
}
|
|
94
|
-
//
|
|
95
|
-
//
|
|
168
|
+
// Drop indexes present in the DB but absent from the model. Categorized destructive
|
|
169
|
+
// (reported, never auto-applied) so legitimate hand-added perf indexes aren't silently
|
|
170
|
+
// nuked — but they ARE surfaced as drift. Exclude PK and constraint-backed indexes
|
|
171
|
+
// (*_pkey / *_unique / *_key), which are handled as constraints, not standalone indexes.
|
|
172
|
+
for (const dbIdx of dbNonPk) {
|
|
173
|
+
if (dbIdx.name.endsWith('_unique') || dbIdx.name.endsWith('_key'))
|
|
174
|
+
continue;
|
|
175
|
+
const found = codeIndexes.some((codeIdx) => codeIdx.name === dbIdx.name ||
|
|
176
|
+
columnsMatch(codeIdx.columns, dbIdx.columns));
|
|
177
|
+
if (!found) {
|
|
178
|
+
ops.push({ type: 'drop_index', tableName, indexName: dbIdx.name });
|
|
179
|
+
}
|
|
180
|
+
}
|
|
96
181
|
return ops;
|
|
97
182
|
}
|
|
98
183
|
/**
|
|
99
184
|
* Diff unique constraints
|
|
100
185
|
*/
|
|
101
|
-
function diffUniqueConstraints(tableName, dbUnique, codeUnique) {
|
|
186
|
+
function diffUniqueConstraints(tableName, dbUnique, codeUnique, dbNames) {
|
|
102
187
|
const ops = [];
|
|
103
|
-
// Find constraints in code that don't exist in DB
|
|
188
|
+
// Find constraints in code that don't exist in DB -> add
|
|
104
189
|
for (const codeCols of codeUnique) {
|
|
105
190
|
const found = dbUnique.some((dbCols) => columnsMatch(dbCols, codeCols));
|
|
106
191
|
if (!found) {
|
|
107
192
|
ops.push({ type: 'add_unique_constraint', tableName, columns: codeCols });
|
|
108
193
|
}
|
|
109
194
|
}
|
|
195
|
+
// Find unique constraints in the DB that aren't in the model -> drop. Categorized
|
|
196
|
+
// destructive (surfaced, never auto-applied), but DETECTED — otherwise the engine is
|
|
197
|
+
// blind to extra/wrong constraints (e.g. the phantom single-column uniques) and can't
|
|
198
|
+
// surface them for remediation. A diff that only adds isn't a diff.
|
|
199
|
+
for (const dbCols of dbUnique) {
|
|
200
|
+
const found = codeUnique.some((codeCols) => columnsMatch(dbCols, codeCols));
|
|
201
|
+
if (!found) {
|
|
202
|
+
// Use the real DB constraint name so the DROP actually matches (Postgres names
|
|
203
|
+
// inline/column-level uniques "<table>_<col>_key", not "<table>_<col>_unique").
|
|
204
|
+
const constraintName = dbNames?.find((n) => columnsMatch(n.columns, dbCols))?.name;
|
|
205
|
+
ops.push({
|
|
206
|
+
type: 'drop_unique_constraint',
|
|
207
|
+
tableName,
|
|
208
|
+
columns: dbCols,
|
|
209
|
+
...(constraintName ? { constraintName } : {}),
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
}
|
|
110
213
|
return ops;
|
|
111
214
|
}
|
|
112
215
|
/**
|
|
@@ -140,7 +243,7 @@ export function diffTable(dbTable, codeTable) {
|
|
|
140
243
|
// Diff indexes
|
|
141
244
|
ops.push(...diffIndexes(tableName, dbTable.indexes, codeTable.indexes));
|
|
142
245
|
// Diff unique constraints
|
|
143
|
-
ops.push(...diffUniqueConstraints(tableName, dbTable.uniqueConstraints, codeTable.uniqueConstraints));
|
|
246
|
+
ops.push(...diffUniqueConstraints(tableName, dbTable.uniqueConstraints, codeTable.uniqueConstraints, dbTable.uniqueConstraintNames));
|
|
144
247
|
// Diff foreign keys
|
|
145
248
|
const dbFks = dbTable.foreignKeys ?? [];
|
|
146
249
|
const codeFks = codeTable.foreignKeys ?? [];
|
|
@@ -196,12 +299,16 @@ export function categorizeOperations(ops) {
|
|
|
196
299
|
case 'add_index':
|
|
197
300
|
case 'add_unique_constraint':
|
|
198
301
|
case 'update_check_constraint':
|
|
302
|
+
case 'add_foreign_key':
|
|
303
|
+
case 'create_sequence':
|
|
199
304
|
safe.push(op);
|
|
200
305
|
break;
|
|
201
306
|
case 'drop_table':
|
|
202
307
|
case 'drop_column':
|
|
203
308
|
case 'drop_index':
|
|
204
309
|
case 'drop_unique_constraint':
|
|
310
|
+
case 'drop_sequence':
|
|
311
|
+
case 'drop_foreign_key':
|
|
205
312
|
destructive.push(op);
|
|
206
313
|
break;
|
|
207
314
|
case 'alter_column_type':
|
package/src/diff.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"diff.js","sourceRoot":"","sources":["../../../../libs/lockstep-pg/src/diff.ts"],"names":[],"mappings":"AAAA;;GAEG;
|
|
1
|
+
{"version":3,"file":"diff.js","sourceRoot":"","sources":["../../../../libs/lockstep-pg/src/diff.ts"],"names":[],"mappings":"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"}
|
package/src/drift.d.ts
CHANGED
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
import type { PostgresRecordModel, PostgresTableModel } from '@lyku/lockstep-core';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
docId?: string | number;
|
|
11
|
-
}
|
|
2
|
+
/**
|
|
3
|
+
* The aggregated schema config (all table models) the migration engine diffs the live DB against.
|
|
4
|
+
*
|
|
5
|
+
* NOTE: the legacy narrow drift detector (`detectDrift`) and its SQL generator (`driftToSql`) were
|
|
6
|
+
* removed. There is now a single migration/drift engine — introspect → `diffDatabase` →
|
|
7
|
+
* `generateMigrationSql` (see `migrate.ts` and `apps/dataform/src/schemaDiff.ts`). This file keeps
|
|
8
|
+
* only the shared config type so existing `import { DataformConfig } from './drift'` sites are stable.
|
|
9
|
+
*/
|
|
12
10
|
export interface DataformConfig {
|
|
13
11
|
tables: Record<string, PostgresTableModel<PostgresRecordModel>>;
|
|
14
12
|
}
|
|
15
|
-
export declare function detectDrift(connectionString: string, config: DataformConfig): Promise<Drift[]>;
|
|
16
13
|
//# sourceMappingURL=drift.d.ts.map
|
package/src/drift.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"drift.d.ts","sourceRoot":"","sources":["../../../../libs/lockstep-pg/src/drift.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"drift.d.ts","sourceRoot":"","sources":["../../../../libs/lockstep-pg/src/drift.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,mBAAmB,EACnB,kBAAkB,EAClB,MAAM,qBAAqB,CAAC;AAE7B;;;;;;;GAOG;AACH,MAAM,WAAW,cAAc;IAC9B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,mBAAmB,CAAC,CAAC,CAAC;CAChE"}
|
package/src/drift.js
CHANGED
|
@@ -1,299 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
// Normalize postgres type names for comparison
|
|
3
|
-
function normalizeType(pgType) {
|
|
4
|
-
const lower = pgType.toLowerCase();
|
|
5
|
-
if (lower === 'bigint' || lower === 'int8')
|
|
6
|
-
return 'bigint';
|
|
7
|
-
if (lower === 'integer' || lower === 'int' || lower === 'int4')
|
|
8
|
-
return 'integer';
|
|
9
|
-
if (lower === 'smallint' || lower === 'int2')
|
|
10
|
-
return 'smallint';
|
|
11
|
-
if (lower === 'boolean' || lower === 'bool')
|
|
12
|
-
return 'boolean';
|
|
13
|
-
if (lower.startsWith('character varying') || lower === 'varchar')
|
|
14
|
-
return 'text';
|
|
15
|
-
if (lower.startsWith('character(') ||
|
|
16
|
-
lower.startsWith('character') ||
|
|
17
|
-
lower === 'bpchar' ||
|
|
18
|
-
lower === 'char')
|
|
19
|
-
return 'text';
|
|
20
|
-
if (lower === 'text')
|
|
21
|
-
return 'text';
|
|
22
|
-
if (lower === 'double precision' || lower === 'float8')
|
|
23
|
-
return 'double precision';
|
|
24
|
-
if (lower === 'timestamp with time zone' || lower === 'timestamptz')
|
|
25
|
-
return 'timestamptz';
|
|
26
|
-
if (lower === 'timestamp without time zone' || lower === 'timestamp')
|
|
27
|
-
return 'timestamp';
|
|
28
|
-
if (lower === 'jsonb')
|
|
29
|
-
return 'jsonb';
|
|
30
|
-
if (lower === 'numeric' || lower === 'decimal')
|
|
31
|
-
return 'numeric';
|
|
32
|
-
if (lower === 'array' || lower.endsWith('[]'))
|
|
33
|
-
return 'array';
|
|
34
|
-
// user-defined types in Postgres (custom enums) are stored as text with CHECK
|
|
35
|
-
if (lower === 'user-defined')
|
|
36
|
-
return 'text';
|
|
37
|
-
return lower;
|
|
38
|
-
}
|
|
39
|
-
// Get expected type from schema
|
|
40
|
-
function getExpectedType(columnSchema) {
|
|
41
|
-
const t = columnSchema.type;
|
|
42
|
-
if (t === 'bigserial')
|
|
43
|
-
return 'bigint'; // bigserial is stored as bigint
|
|
44
|
-
if (t === 'serial')
|
|
45
|
-
return 'integer'; // serial is stored as integer
|
|
46
|
-
if (t === 'enum')
|
|
47
|
-
return 'text'; // enums are stored as TEXT with CHECK constraint
|
|
48
|
-
if (t === 'char' || t === 'varchar')
|
|
49
|
-
return 'text'; // char/varchar normalize to text
|
|
50
|
-
return normalizeType(t);
|
|
51
|
-
}
|
|
52
|
-
export async function detectDrift(connectionString, config) {
|
|
53
|
-
const client = new Client({
|
|
54
|
-
connectionString,
|
|
55
|
-
ssl: true,
|
|
56
|
-
connectionTimeoutMillis: 30000,
|
|
57
|
-
});
|
|
58
|
-
const drifts = [];
|
|
59
|
-
try {
|
|
60
|
-
await client.connect();
|
|
61
|
-
// Get all tables from database
|
|
62
|
-
const tablesResult = await client.query(`
|
|
63
|
-
SELECT table_name
|
|
64
|
-
FROM information_schema.tables
|
|
65
|
-
WHERE table_schema = 'public'
|
|
66
|
-
AND table_type = 'BASE TABLE'
|
|
67
|
-
`);
|
|
68
|
-
const actualTables = new Set(tablesResult.rows.map((r) => r.table_name));
|
|
69
|
-
const expectedTables = Object.keys(config.tables);
|
|
70
|
-
// Check for missing tables
|
|
71
|
-
for (const tableName of expectedTables) {
|
|
72
|
-
if (!actualTables.has(tableName)) {
|
|
73
|
-
drifts.push({
|
|
74
|
-
type: 'missing_table',
|
|
75
|
-
table: tableName,
|
|
76
|
-
details: `Table "${tableName}" exists in schema but not in database`,
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
// Check for extra tables (not in schema)
|
|
81
|
-
for (const tableName of actualTables) {
|
|
82
|
-
if (!expectedTables.includes(tableName)) {
|
|
83
|
-
drifts.push({
|
|
84
|
-
type: 'extra_table',
|
|
85
|
-
table: tableName,
|
|
86
|
-
details: `Table "${tableName}" exists in database but not in schema`,
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
// For each expected table that exists, check columns
|
|
91
|
-
for (const [tableName, tableModel] of Object.entries(config.tables)) {
|
|
92
|
-
if (!actualTables.has(tableName))
|
|
93
|
-
continue;
|
|
94
|
-
const schema = tableModel.schema;
|
|
95
|
-
if (!('properties' in schema))
|
|
96
|
-
continue;
|
|
97
|
-
// Get actual columns
|
|
98
|
-
const columnsResult = await client.query(`
|
|
99
|
-
SELECT column_name, data_type, is_nullable, column_default, character_maximum_length
|
|
100
|
-
FROM information_schema.columns
|
|
101
|
-
WHERE table_schema = 'public' AND table_name = $1
|
|
102
|
-
`, [tableName]);
|
|
103
|
-
const actualColumns = new Map(columnsResult.rows.map((r) => [r.column_name, r]));
|
|
104
|
-
const expectedColumns = Object.entries(schema.properties);
|
|
105
|
-
const required = 'required' in schema ? schema.required : [];
|
|
106
|
-
// Check for missing columns
|
|
107
|
-
for (const [columnName, columnSchema] of expectedColumns) {
|
|
108
|
-
const actualCol = actualColumns.get(columnName);
|
|
109
|
-
if (!actualCol) {
|
|
110
|
-
drifts.push({
|
|
111
|
-
type: 'missing_column',
|
|
112
|
-
table: tableName,
|
|
113
|
-
column: columnName,
|
|
114
|
-
details: `Column "${columnName}" missing from table`,
|
|
115
|
-
});
|
|
116
|
-
continue;
|
|
117
|
-
}
|
|
118
|
-
// Check type
|
|
119
|
-
const expectedType = getExpectedType(columnSchema);
|
|
120
|
-
const actualType = normalizeType(actualCol.data_type);
|
|
121
|
-
if (expectedType !== actualType) {
|
|
122
|
-
drifts.push({
|
|
123
|
-
type: 'column_type_mismatch',
|
|
124
|
-
table: tableName,
|
|
125
|
-
column: columnName,
|
|
126
|
-
details: `Column "${columnName}" type mismatch`,
|
|
127
|
-
expected: expectedType,
|
|
128
|
-
actual: actualType,
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
// Check nullable
|
|
132
|
-
const expectedNullable = !required.includes(columnName);
|
|
133
|
-
const actualNullable = actualCol.is_nullable === 'YES';
|
|
134
|
-
if (expectedNullable !== actualNullable) {
|
|
135
|
-
drifts.push({
|
|
136
|
-
type: 'nullable_mismatch',
|
|
137
|
-
table: tableName,
|
|
138
|
-
column: columnName,
|
|
139
|
-
details: `Column "${columnName}" nullable mismatch`,
|
|
140
|
-
expected: expectedNullable ? 'nullable' : 'not null',
|
|
141
|
-
actual: actualNullable ? 'nullable' : 'not null',
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
actualColumns.delete(columnName);
|
|
145
|
-
}
|
|
146
|
-
// Extra columns (in DB but not schema)
|
|
147
|
-
for (const [columnName] of actualColumns) {
|
|
148
|
-
drifts.push({
|
|
149
|
-
type: 'extra_column',
|
|
150
|
-
table: tableName,
|
|
151
|
-
column: columnName,
|
|
152
|
-
details: `Column "${columnName}" exists in database but not in schema`,
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
// Check CHECK constraints for enum columns
|
|
156
|
-
const checkConstraintsResult = await client.query(`
|
|
157
|
-
SELECT
|
|
158
|
-
con.conname AS constraint_name,
|
|
159
|
-
att.attname AS column_name,
|
|
160
|
-
pg_get_constraintdef(con.oid) AS check_clause
|
|
161
|
-
FROM pg_constraint con
|
|
162
|
-
JOIN pg_attribute att ON att.attnum = ANY(con.conkey) AND att.attrelid = con.conrelid
|
|
163
|
-
WHERE con.conrelid = $1::regclass
|
|
164
|
-
AND con.contype = 'c'
|
|
165
|
-
`, [`"${tableName}"`]);
|
|
166
|
-
const actualCheckConstraints = new Map();
|
|
167
|
-
for (const row of checkConstraintsResult.rows) {
|
|
168
|
-
if (row.check_clause.includes(' IN (') ||
|
|
169
|
-
row.check_clause.includes('ANY (ARRAY[') ||
|
|
170
|
-
row.check_clause.includes('ANY(ARRAY[')) {
|
|
171
|
-
actualCheckConstraints.set(row.column_name, row);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
// Compare enum columns with their CHECK constraints
|
|
175
|
-
for (const [columnName, columnSchema] of expectedColumns) {
|
|
176
|
-
const colSchema = columnSchema;
|
|
177
|
-
if (colSchema.type !== 'enum' || !colSchema.enum)
|
|
178
|
-
continue;
|
|
179
|
-
const actualConstraint = actualCheckConstraints.get(columnName);
|
|
180
|
-
if (!actualConstraint) {
|
|
181
|
-
drifts.push({
|
|
182
|
-
type: 'missing_constraint',
|
|
183
|
-
table: tableName,
|
|
184
|
-
column: columnName,
|
|
185
|
-
details: `Missing CHECK constraint for enum column "${columnName}"`,
|
|
186
|
-
expected: colSchema.enum.join(', '),
|
|
187
|
-
});
|
|
188
|
-
continue;
|
|
189
|
-
}
|
|
190
|
-
const checkClause = actualConstraint.check_clause;
|
|
191
|
-
const actualValues = [];
|
|
192
|
-
const valueMatches = checkClause.matchAll(/'([^']+)'/g);
|
|
193
|
-
for (const match of valueMatches) {
|
|
194
|
-
if (!match[1].includes('::')) {
|
|
195
|
-
actualValues.push(match[1]);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
const expectedSet = new Set(colSchema.enum);
|
|
199
|
-
const actualSet = new Set(actualValues);
|
|
200
|
-
const missing = colSchema.enum.filter((v) => !actualSet.has(v));
|
|
201
|
-
const extra = actualValues.filter((v) => !expectedSet.has(v));
|
|
202
|
-
if (missing.length > 0 || extra.length > 0) {
|
|
203
|
-
drifts.push({
|
|
204
|
-
type: 'check_constraint_mismatch',
|
|
205
|
-
table: tableName,
|
|
206
|
-
column: columnName,
|
|
207
|
-
constraintName: actualConstraint.constraint_name,
|
|
208
|
-
details: `CHECK constraint values mismatch for "${columnName}"`,
|
|
209
|
-
expected: colSchema.enum.join(', '),
|
|
210
|
-
actual: actualValues.join(', '),
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
// Check for missing stock documents
|
|
215
|
-
const docs = tableModel.docs;
|
|
216
|
-
if (docs && Array.isArray(docs) && docs.length > 0) {
|
|
217
|
-
const primaryKey = tableModel.primaryKey || 'id';
|
|
218
|
-
for (const doc of docs) {
|
|
219
|
-
if (doc &&
|
|
220
|
-
typeof doc === 'object' &&
|
|
221
|
-
!Array.isArray(doc) &&
|
|
222
|
-
'id' in doc) {
|
|
223
|
-
const docRecord = doc;
|
|
224
|
-
const docId = docRecord['id'];
|
|
225
|
-
const checkResult = await client.query(`SELECT 1 FROM "${tableName}" WHERE "${String(primaryKey)}" = $1 LIMIT 1`, [docId]);
|
|
226
|
-
if (checkResult.rows.length === 0) {
|
|
227
|
-
const docTitle = 'title' in doc
|
|
228
|
-
? docRecord['title']
|
|
229
|
-
: 'name' in doc
|
|
230
|
-
? docRecord['name']
|
|
231
|
-
: 'slug' in doc
|
|
232
|
-
? docRecord['slug']
|
|
233
|
-
: undefined;
|
|
234
|
-
drifts.push({
|
|
235
|
-
type: 'missing_stock_doc',
|
|
236
|
-
table: tableName,
|
|
237
|
-
docId: typeof docId === 'bigint'
|
|
238
|
-
? docId.toString()
|
|
239
|
-
: docId,
|
|
240
|
-
details: `Stock document ${String(primaryKey)}=${docId}${docTitle ? ` (${docTitle})` : ''} missing from table`,
|
|
241
|
-
});
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
// Check indexes
|
|
247
|
-
const indexesResult = await client.query(`
|
|
248
|
-
SELECT indexname, indexdef
|
|
249
|
-
FROM pg_indexes
|
|
250
|
-
WHERE schemaname = 'public' AND tablename = $1
|
|
251
|
-
`, [tableName]);
|
|
252
|
-
const actualIndexes = new Map(indexesResult.rows.map((r) => [r.indexname, r.indexdef]));
|
|
253
|
-
// Expected indexes from model
|
|
254
|
-
const expectedIndexColumns = tableModel.indexes || [];
|
|
255
|
-
for (const indexCol of expectedIndexColumns) {
|
|
256
|
-
let cols;
|
|
257
|
-
if (typeof indexCol === 'string') {
|
|
258
|
-
cols = [indexCol];
|
|
259
|
-
}
|
|
260
|
-
else if (Array.isArray(indexCol)) {
|
|
261
|
-
cols = indexCol;
|
|
262
|
-
}
|
|
263
|
-
else if (typeof indexCol === 'object' && indexCol !== null) {
|
|
264
|
-
const objIndex = indexCol;
|
|
265
|
-
cols = Array.isArray(objIndex.columns)
|
|
266
|
-
? objIndex.columns
|
|
267
|
-
: [objIndex.columns];
|
|
268
|
-
}
|
|
269
|
-
else {
|
|
270
|
-
continue;
|
|
271
|
-
}
|
|
272
|
-
let found = false;
|
|
273
|
-
for (const [, indexDef] of actualIndexes) {
|
|
274
|
-
const defLower = indexDef.toLowerCase();
|
|
275
|
-
if (cols.every((c) => {
|
|
276
|
-
const colLower = c.toLowerCase().split(' ')[0];
|
|
277
|
-
return (defLower.includes(`"${colLower}"`) ||
|
|
278
|
-
defLower.includes(colLower));
|
|
279
|
-
})) {
|
|
280
|
-
found = true;
|
|
281
|
-
break;
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
if (!found) {
|
|
285
|
-
drifts.push({
|
|
286
|
-
type: 'missing_index',
|
|
287
|
-
table: tableName,
|
|
288
|
-
details: `Missing index on column(s): ${cols.join(', ')}`,
|
|
289
|
-
});
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
return drifts;
|
|
294
|
-
}
|
|
295
|
-
finally {
|
|
296
|
-
await client.end();
|
|
297
|
-
}
|
|
298
|
-
}
|
|
1
|
+
export {};
|
|
299
2
|
//# sourceMappingURL=drift.js.map
|
package/src/drift.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"drift.js","sourceRoot":"","sources":["../../../../libs/lockstep-pg/src/drift.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"drift.js","sourceRoot":"","sources":["../../../../libs/lockstep-pg/src/drift.ts"],"names":[],"mappings":""}
|
package/src/form.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"form.d.ts","sourceRoot":"","sources":["../../../../libs/lockstep-pg/src/form.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"form.d.ts","sourceRoot":"","sources":["../../../../libs/lockstep-pg/src/form.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AA0C9C,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAStE"}
|
package/src/form.js
CHANGED
|
@@ -1,8 +1,49 @@
|
|
|
1
1
|
import { setupTable } from './setupTable';
|
|
2
|
+
/**
|
|
3
|
+
* Order tables so a table's foreign-key targets are created before it (FKs are emitted inline
|
|
4
|
+
* by buildTableCreationCommand). DFS topological sort; cycles are broken gracefully by emitting
|
|
5
|
+
* the remaining tables in their original order (true FK cycles would need deferred ALTERs).
|
|
6
|
+
*/
|
|
7
|
+
function orderTablesByDependencies(config) {
|
|
8
|
+
const names = Object.keys(config.tables);
|
|
9
|
+
const deps = new Map();
|
|
10
|
+
for (const name of names) {
|
|
11
|
+
// eslint-disable-next-line security/detect-object-injection -- name is from Object.keys
|
|
12
|
+
const model = config.tables[name];
|
|
13
|
+
const refs = new Set();
|
|
14
|
+
for (const ref of Object.values(model?.foreignKeys ?? {})) {
|
|
15
|
+
if (ref && typeof ref === 'object') {
|
|
16
|
+
for (const refTable of Object.keys(ref)) {
|
|
17
|
+
// eslint-disable-next-line security/detect-object-injection -- key from FK refs
|
|
18
|
+
if (refTable !== name && config.tables[refTable])
|
|
19
|
+
refs.add(refTable);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
deps.set(name, refs);
|
|
24
|
+
}
|
|
25
|
+
const ordered = [];
|
|
26
|
+
const visited = new Set();
|
|
27
|
+
const inProgress = new Set();
|
|
28
|
+
const visit = (name) => {
|
|
29
|
+
if (visited.has(name) || inProgress.has(name))
|
|
30
|
+
return;
|
|
31
|
+
inProgress.add(name);
|
|
32
|
+
for (const dep of deps.get(name) ?? [])
|
|
33
|
+
visit(dep);
|
|
34
|
+
inProgress.delete(name);
|
|
35
|
+
visited.add(name);
|
|
36
|
+
ordered.push(name);
|
|
37
|
+
};
|
|
38
|
+
for (const name of names)
|
|
39
|
+
visit(name);
|
|
40
|
+
return ordered;
|
|
41
|
+
}
|
|
2
42
|
export function generateCreateTablesSql(config) {
|
|
3
43
|
const outputs = [];
|
|
4
|
-
for (const
|
|
5
|
-
|
|
44
|
+
for (const tableName of orderTablesByDependencies(config)) {
|
|
45
|
+
// eslint-disable-next-line security/detect-object-injection -- tableName from config keys
|
|
46
|
+
outputs.push(setupTable(tableName, config.tables[tableName]));
|
|
6
47
|
}
|
|
7
48
|
return outputs.join('\n\n');
|
|
8
49
|
}
|
package/src/form.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"form.js","sourceRoot":"","sources":["../../../../libs/lockstep-pg/src/form.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAG1C,MAAM,
|
|
1
|
+
{"version":3,"file":"form.js","sourceRoot":"","sources":["../../../../libs/lockstep-pg/src/form.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAG1C;;;;GAIG;AACH,SAAS,yBAAyB,CAAC,MAAsB;IACxD,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACzC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAuB,CAAC;IAC5C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QAC1B,wFAAwF;QACxF,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAE/B,CAAC;QACF,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;QAC/B,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,WAAW,IAAI,EAAE,CAAC,EAAE,CAAC;YAC3D,IAAI,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;gBACpC,KAAK,MAAM,QAAQ,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;oBACzC,gFAAgF;oBAChF,IAAI,QAAQ,KAAK,IAAI,IAAI,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC;wBAAE,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;gBACtE,CAAC;YACF,CAAC;QACF,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACtB,CAAC;IAED,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;IAClC,MAAM,UAAU,GAAG,IAAI,GAAG,EAAU,CAAC;IACrC,MAAM,KAAK,GAAG,CAAC,IAAY,EAAQ,EAAE;QACpC,IAAI,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,OAAO;QACtD,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACrB,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE;YAAE,KAAK,CAAC,GAAG,CAAC,CAAC;QACnD,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACxB,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAClB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACpB,CAAC,CAAC;IACF,KAAK,MAAM,IAAI,IAAI,KAAK;QAAE,KAAK,CAAC,IAAI,CAAC,CAAC;IACtC,OAAO,OAAO,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,MAAsB;IAC7D,MAAM,OAAO,GAAa,EAAE,CAAC;IAE7B,KAAK,MAAM,SAAS,IAAI,yBAAyB,CAAC,MAAM,CAAC,EAAE,CAAC;QAC3D,0FAA0F;QAC1F,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IAC/D,CAAC;IAED,OAAO,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AAC7B,CAAC"}
|
package/src/generateSql.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generateSql.d.ts","sourceRoot":"","sources":["../../../../libs/lockstep-pg/src/generateSql.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"generateSql.d.ts","sourceRoot":"","sources":["../../../../libs/lockstep-pg/src/generateSql.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AAyL5C;;GAEG;AACH,wBAAgB,cAAc,CAAC,EAAE,EAAE,aAAa,GAAG,MAAM,EAAE,CAiL1D;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CACnC,GAAG,EAAE,aAAa,EAAE,EACpB,mBAAmB,UAAQ,GACzB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CA0DvC"}
|