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
|
@@ -0,0 +1,907 @@
|
|
|
1
|
+
import { parse } from 'pgsql-parser';
|
|
2
|
+
import { deparse } from 'pgsql-deparser';
|
|
3
|
+
function extractTypeName(typeName) {
|
|
4
|
+
if (!typeName)
|
|
5
|
+
return { name: 'text', isArray: false };
|
|
6
|
+
const names = typeName.names?.map((n) => n.String?.sval).filter(Boolean) || [];
|
|
7
|
+
let name = names.length > 1 ? names[names.length - 1] : names[0] || 'text';
|
|
8
|
+
if (names[0] === 'pg_catalog') {
|
|
9
|
+
name = names[1] || 'text';
|
|
10
|
+
}
|
|
11
|
+
const isArray = (typeName.arrayBounds?.length ?? 0) > 0;
|
|
12
|
+
const arrayDims = typeName.arrayBounds?.length;
|
|
13
|
+
let params;
|
|
14
|
+
if (typeName.typmods && typeName.typmods.length > 0) {
|
|
15
|
+
const mods = typeName.typmods.map((m) => {
|
|
16
|
+
if (m.A_Const?.ival?.ival !== undefined)
|
|
17
|
+
return m.A_Const.ival.ival;
|
|
18
|
+
if (m.A_Const?.sval?.sval !== undefined)
|
|
19
|
+
return m.A_Const.sval.sval;
|
|
20
|
+
return undefined;
|
|
21
|
+
}).filter((v) => v !== undefined);
|
|
22
|
+
if (mods.length === 1) {
|
|
23
|
+
if (['varchar', 'character varying', 'char', 'character', 'bit', 'varbit'].includes(name.toLowerCase())) {
|
|
24
|
+
params = { length: mods[0] };
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
params = { precision: mods[0] };
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
else if (mods.length === 2) {
|
|
31
|
+
params = { precision: mods[0], scale: mods[1] };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return { name, params, isArray, arrayDims };
|
|
35
|
+
}
|
|
36
|
+
function extractConstraintType(contype) {
|
|
37
|
+
const types = {
|
|
38
|
+
0: 'NULL',
|
|
39
|
+
1: 'NOT NULL',
|
|
40
|
+
2: 'DEFAULT',
|
|
41
|
+
3: 'IDENTITY',
|
|
42
|
+
4: 'GENERATED',
|
|
43
|
+
5: 'CHECK',
|
|
44
|
+
6: 'PRIMARY KEY',
|
|
45
|
+
7: 'UNIQUE',
|
|
46
|
+
8: 'EXCLUSION',
|
|
47
|
+
9: 'FOREIGN KEY',
|
|
48
|
+
};
|
|
49
|
+
return types[contype] || 'UNKNOWN';
|
|
50
|
+
}
|
|
51
|
+
function extractFkAction(action) {
|
|
52
|
+
if (!action)
|
|
53
|
+
return undefined;
|
|
54
|
+
const actions = {
|
|
55
|
+
'a': 'NO ACTION',
|
|
56
|
+
'r': 'RESTRICT',
|
|
57
|
+
'c': 'CASCADE',
|
|
58
|
+
'n': 'SET NULL',
|
|
59
|
+
'd': 'SET DEFAULT',
|
|
60
|
+
};
|
|
61
|
+
return actions[action];
|
|
62
|
+
}
|
|
63
|
+
function extractFkMatch(matchType) {
|
|
64
|
+
if (!matchType)
|
|
65
|
+
return undefined;
|
|
66
|
+
const matches = {
|
|
67
|
+
's': 'SIMPLE',
|
|
68
|
+
'f': 'FULL',
|
|
69
|
+
};
|
|
70
|
+
return matches[matchType];
|
|
71
|
+
}
|
|
72
|
+
async function parseForeignKeyDefinition(definition) {
|
|
73
|
+
try {
|
|
74
|
+
const wrappedSQL = `CREATE TABLE _fk_parse_temp (dummy int, ${definition});`;
|
|
75
|
+
const result = await parse(wrappedSQL);
|
|
76
|
+
const createStmt = result.stmts?.[0]?.stmt?.CreateStmt;
|
|
77
|
+
if (!createStmt)
|
|
78
|
+
return null;
|
|
79
|
+
for (const elt of createStmt.tableElts || []) {
|
|
80
|
+
const constraint = elt?.Constraint;
|
|
81
|
+
if (constraint && constraint.contype === 'CONSTR_FOREIGN') {
|
|
82
|
+
const pktable = constraint.pktable;
|
|
83
|
+
const pkColumns = (constraint.pk_attrs || [])
|
|
84
|
+
.map((a) => a.String?.sval)
|
|
85
|
+
.filter(Boolean);
|
|
86
|
+
return {
|
|
87
|
+
table: pktable?.relname || '',
|
|
88
|
+
columns: pkColumns,
|
|
89
|
+
onDelete: extractFkAction(constraint.fk_del_action),
|
|
90
|
+
onUpdate: extractFkAction(constraint.fk_upd_action),
|
|
91
|
+
match: extractFkMatch(constraint.fk_matchtype),
|
|
92
|
+
deferrable: constraint.deferrable ?? false,
|
|
93
|
+
initiallyDeferred: constraint.initdeferred ?? false,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
export async function parseExpressionToAst(expr) {
|
|
104
|
+
try {
|
|
105
|
+
const sql = `SELECT ${expr};`;
|
|
106
|
+
const result = await parse(sql);
|
|
107
|
+
const selectStmt = result.stmts?.[0]?.stmt?.SelectStmt;
|
|
108
|
+
if (!selectStmt)
|
|
109
|
+
return null;
|
|
110
|
+
const resTarget = selectStmt.targetList?.[0]?.ResTarget;
|
|
111
|
+
if (!resTarget)
|
|
112
|
+
return null;
|
|
113
|
+
return resTarget.val || null;
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
async function deparseNode(node) {
|
|
120
|
+
try {
|
|
121
|
+
const result = await deparse([node]);
|
|
122
|
+
return result.trim();
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return '';
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
async function parseColumnDef(colDef) {
|
|
129
|
+
const typeInfo = extractTypeName(colDef.typeName);
|
|
130
|
+
const column = {
|
|
131
|
+
name: colDef.colname || '',
|
|
132
|
+
type: typeInfo.name,
|
|
133
|
+
typeParams: typeInfo.params,
|
|
134
|
+
isNullable: true,
|
|
135
|
+
isPrimaryKey: false,
|
|
136
|
+
isUnique: false,
|
|
137
|
+
hasDefault: false,
|
|
138
|
+
isGenerated: false,
|
|
139
|
+
isArray: typeInfo.isArray,
|
|
140
|
+
arrayDimensions: typeInfo.arrayDims,
|
|
141
|
+
};
|
|
142
|
+
if (colDef.constraints) {
|
|
143
|
+
for (const c of colDef.constraints) {
|
|
144
|
+
const constraint = c.Constraint;
|
|
145
|
+
if (!constraint)
|
|
146
|
+
continue;
|
|
147
|
+
const contype = constraint.contype;
|
|
148
|
+
switch (contype) {
|
|
149
|
+
case 'CONSTR_NOTNULL':
|
|
150
|
+
column.isNullable = false;
|
|
151
|
+
break;
|
|
152
|
+
case 'CONSTR_DEFAULT':
|
|
153
|
+
column.hasDefault = true;
|
|
154
|
+
if (constraint.raw_expr) {
|
|
155
|
+
column.defaultValue = await deparseNode(constraint.raw_expr);
|
|
156
|
+
}
|
|
157
|
+
break;
|
|
158
|
+
case 'CONSTR_GENERATED':
|
|
159
|
+
column.isGenerated = true;
|
|
160
|
+
if (constraint.raw_expr) {
|
|
161
|
+
column.generatedExpression = await deparseNode(constraint.raw_expr);
|
|
162
|
+
column.generatedExpressionAst = constraint.raw_expr;
|
|
163
|
+
}
|
|
164
|
+
break;
|
|
165
|
+
case 'CONSTR_CHECK':
|
|
166
|
+
if (constraint.raw_expr) {
|
|
167
|
+
column.checkConstraint = {
|
|
168
|
+
name: constraint.conname || '',
|
|
169
|
+
expression: await deparseNode(constraint.raw_expr),
|
|
170
|
+
expressionAst: constraint.raw_expr,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
break;
|
|
174
|
+
case 'CONSTR_PRIMARY':
|
|
175
|
+
column.isPrimaryKey = true;
|
|
176
|
+
column.isNullable = false;
|
|
177
|
+
break;
|
|
178
|
+
case 'CONSTR_UNIQUE':
|
|
179
|
+
column.isUnique = true;
|
|
180
|
+
break;
|
|
181
|
+
case 'CONSTR_FOREIGN':
|
|
182
|
+
const pktable = constraint.pktable;
|
|
183
|
+
const pkColumns = (constraint.pk_attrs || []).map((a) => a.String?.sval).filter(Boolean);
|
|
184
|
+
column.references = {
|
|
185
|
+
table: pktable?.relname || '',
|
|
186
|
+
column: pkColumns[0] || '',
|
|
187
|
+
onDelete: extractFkAction(constraint.fk_del_action),
|
|
188
|
+
onUpdate: extractFkAction(constraint.fk_upd_action),
|
|
189
|
+
match: extractFkMatch(constraint.fk_matchtype),
|
|
190
|
+
deferrable: constraint.deferrable ?? false,
|
|
191
|
+
initiallyDeferred: constraint.initdeferred ?? false,
|
|
192
|
+
};
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return column;
|
|
198
|
+
}
|
|
199
|
+
async function parseTableConstraint(constraint) {
|
|
200
|
+
const c = constraint.Constraint;
|
|
201
|
+
if (!c)
|
|
202
|
+
return null;
|
|
203
|
+
const contype = c.contype;
|
|
204
|
+
const columns = (c.keys || []).map((k) => k.String?.sval).filter(Boolean);
|
|
205
|
+
const result = {
|
|
206
|
+
name: c.conname || '',
|
|
207
|
+
type: 'CHECK',
|
|
208
|
+
columns,
|
|
209
|
+
};
|
|
210
|
+
switch (contype) {
|
|
211
|
+
case 'CONSTR_CHECK':
|
|
212
|
+
result.type = 'CHECK';
|
|
213
|
+
if (c.raw_expr) {
|
|
214
|
+
result.expression = await deparseNode(c.raw_expr);
|
|
215
|
+
result.expressionAst = c.raw_expr;
|
|
216
|
+
}
|
|
217
|
+
break;
|
|
218
|
+
case 'CONSTR_PRIMARY':
|
|
219
|
+
result.type = 'PRIMARY KEY';
|
|
220
|
+
break;
|
|
221
|
+
case 'CONSTR_UNIQUE':
|
|
222
|
+
result.type = 'UNIQUE';
|
|
223
|
+
break;
|
|
224
|
+
case 'CONSTR_EXCLUSION':
|
|
225
|
+
result.type = 'EXCLUDE';
|
|
226
|
+
if (c.exclusions) {
|
|
227
|
+
result.expression = await deparseNode({ exclusions: c.exclusions });
|
|
228
|
+
}
|
|
229
|
+
break;
|
|
230
|
+
case 'CONSTR_FOREIGN':
|
|
231
|
+
result.type = 'FOREIGN KEY';
|
|
232
|
+
const pktable = c.pktable;
|
|
233
|
+
const pkColumns = (c.pk_attrs || []).map((a) => a.String?.sval).filter(Boolean);
|
|
234
|
+
result.references = {
|
|
235
|
+
table: pktable?.relname || '',
|
|
236
|
+
columns: pkColumns,
|
|
237
|
+
onDelete: extractFkAction(c.fk_del_action),
|
|
238
|
+
onUpdate: extractFkAction(c.fk_upd_action),
|
|
239
|
+
match: extractFkMatch(c.fk_matchtype),
|
|
240
|
+
deferrable: c.deferrable ?? false,
|
|
241
|
+
initiallyDeferred: c.initdeferred ?? false,
|
|
242
|
+
};
|
|
243
|
+
break;
|
|
244
|
+
default:
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
return result;
|
|
248
|
+
}
|
|
249
|
+
async function parseCreateStmt(stmt) {
|
|
250
|
+
const relation = stmt.relation;
|
|
251
|
+
const table = {
|
|
252
|
+
name: relation?.relname || '',
|
|
253
|
+
schema: relation?.schemaname,
|
|
254
|
+
columns: [],
|
|
255
|
+
constraints: [],
|
|
256
|
+
indexes: [],
|
|
257
|
+
isPartitioned: !!stmt.partspec,
|
|
258
|
+
};
|
|
259
|
+
if (stmt.partspec) {
|
|
260
|
+
const partSpec = stmt.partspec;
|
|
261
|
+
const strategy = partSpec.strategy;
|
|
262
|
+
table.partitionType = strategy === 'r' ? 'RANGE' : strategy === 'l' ? 'LIST' : strategy === 'h' ? 'HASH' : undefined;
|
|
263
|
+
table.partitionKey = (partSpec.partParams || []).map((p) => p.PartitionElem?.name).filter(Boolean);
|
|
264
|
+
}
|
|
265
|
+
if (stmt.partbound) {
|
|
266
|
+
table.partitionOf = (stmt.inhRelations || [])[0]?.RangeVar?.relname;
|
|
267
|
+
const partBound = stmt.partbound;
|
|
268
|
+
if (partBound) {
|
|
269
|
+
table.partitionBound = await deparseNode(partBound);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
if (stmt.inhRelations && !stmt.partbound) {
|
|
273
|
+
table.inherits = (stmt.inhRelations || [])
|
|
274
|
+
.map((r) => r.RangeVar?.relname)
|
|
275
|
+
.filter(Boolean);
|
|
276
|
+
}
|
|
277
|
+
for (const elt of stmt.tableElts || []) {
|
|
278
|
+
if (elt.ColumnDef) {
|
|
279
|
+
const col = await parseColumnDef(elt.ColumnDef);
|
|
280
|
+
table.columns.push(col);
|
|
281
|
+
}
|
|
282
|
+
else if (elt.Constraint) {
|
|
283
|
+
const constraint = await parseTableConstraint(elt);
|
|
284
|
+
if (constraint) {
|
|
285
|
+
table.constraints.push(constraint);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return table;
|
|
290
|
+
}
|
|
291
|
+
async function parseIndexStmt(stmt) {
|
|
292
|
+
const index = {
|
|
293
|
+
name: stmt.idxname || '',
|
|
294
|
+
columns: [],
|
|
295
|
+
isUnique: stmt.unique || false,
|
|
296
|
+
method: stmt.accessMethod,
|
|
297
|
+
};
|
|
298
|
+
for (const elem of stmt.indexParams || []) {
|
|
299
|
+
const idxElem = elem.IndexElem;
|
|
300
|
+
if (idxElem) {
|
|
301
|
+
if (idxElem.name) {
|
|
302
|
+
index.columns.push(idxElem.name);
|
|
303
|
+
}
|
|
304
|
+
else if (idxElem.expr) {
|
|
305
|
+
index.isExpression = true;
|
|
306
|
+
const expr = await deparseNode(idxElem.expr);
|
|
307
|
+
index.expressions = index.expressions || [];
|
|
308
|
+
index.expressions.push(expr);
|
|
309
|
+
index.columns.push(expr);
|
|
310
|
+
}
|
|
311
|
+
if (idxElem.opclass && idxElem.opclass.length > 0) {
|
|
312
|
+
index.opclass = idxElem.opclass.map((o) => o.String?.sval).filter(Boolean).join('_');
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
if (stmt.whereClause) {
|
|
317
|
+
index.whereClause = await deparseNode(stmt.whereClause);
|
|
318
|
+
index.whereClauseAst = stmt.whereClause;
|
|
319
|
+
}
|
|
320
|
+
if (stmt.indexIncludingParams) {
|
|
321
|
+
index.includeColumns = (stmt.indexIncludingParams || [])
|
|
322
|
+
.map((p) => p.IndexElem?.name)
|
|
323
|
+
.filter(Boolean);
|
|
324
|
+
}
|
|
325
|
+
return index;
|
|
326
|
+
}
|
|
327
|
+
function parseCreateEnumStmt(stmt) {
|
|
328
|
+
const names = (stmt.typeName || []).map((n) => n.String?.sval).filter(Boolean);
|
|
329
|
+
const values = (stmt.vals || []).map((v) => v.String?.sval).filter(Boolean);
|
|
330
|
+
return {
|
|
331
|
+
name: names.length > 1 ? names[1] : names[0] || '',
|
|
332
|
+
schema: names.length > 1 ? names[0] : undefined,
|
|
333
|
+
values,
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
async function parseCreateDomainStmt(stmt) {
|
|
337
|
+
const names = (stmt.domainname || []).map((n) => n.String?.sval).filter(Boolean);
|
|
338
|
+
const typeInfo = extractTypeName(stmt.typeName);
|
|
339
|
+
const domain = {
|
|
340
|
+
name: names.length > 1 ? names[1] : names[0] || '',
|
|
341
|
+
schema: names.length > 1 ? names[0] : undefined,
|
|
342
|
+
baseType: typeInfo.name,
|
|
343
|
+
notNull: false,
|
|
344
|
+
};
|
|
345
|
+
for (const c of stmt.constraints || []) {
|
|
346
|
+
const constraint = c.Constraint;
|
|
347
|
+
if (!constraint)
|
|
348
|
+
continue;
|
|
349
|
+
const contype = constraint.contype;
|
|
350
|
+
if (contype === 1) {
|
|
351
|
+
domain.notNull = true;
|
|
352
|
+
}
|
|
353
|
+
else if (contype === 5 && constraint.raw_expr) {
|
|
354
|
+
domain.checkExpression = await deparseNode(constraint.raw_expr);
|
|
355
|
+
domain.checkName = constraint.conname;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
if (stmt.collClause?.defval) {
|
|
359
|
+
domain.defaultValue = await deparseNode(stmt.collClause.defval);
|
|
360
|
+
}
|
|
361
|
+
return domain;
|
|
362
|
+
}
|
|
363
|
+
async function parseViewStmt(stmt, isMaterialized = false) {
|
|
364
|
+
const view = stmt.view;
|
|
365
|
+
return {
|
|
366
|
+
name: view?.relname || '',
|
|
367
|
+
schema: view?.schemaname,
|
|
368
|
+
definition: stmt.query ? await deparseNode(stmt.query) : '',
|
|
369
|
+
isMaterialized,
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
async function parseCreateFunctionStmt(stmt) {
|
|
373
|
+
const names = (stmt.funcname || []).map((n) => n.String?.sval).filter(Boolean);
|
|
374
|
+
const func = {
|
|
375
|
+
name: names.length > 1 ? names[1] : names[0] || '',
|
|
376
|
+
schema: names.length > 1 ? names[0] : undefined,
|
|
377
|
+
args: [],
|
|
378
|
+
returnType: '',
|
|
379
|
+
language: 'sql',
|
|
380
|
+
body: '',
|
|
381
|
+
isStrict: false,
|
|
382
|
+
securityDefiner: false,
|
|
383
|
+
};
|
|
384
|
+
for (const param of stmt.parameters || []) {
|
|
385
|
+
const fp = param.FunctionParameter;
|
|
386
|
+
if (fp) {
|
|
387
|
+
const typeInfo = extractTypeName(fp.argType);
|
|
388
|
+
func.args.push({
|
|
389
|
+
name: fp.name,
|
|
390
|
+
type: typeInfo.name,
|
|
391
|
+
mode: fp.mode === 'i' ? 'IN' : fp.mode === 'o' ? 'OUT' : fp.mode === 'b' ? 'INOUT' : undefined,
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
if (stmt.returnType) {
|
|
396
|
+
const typeInfo = extractTypeName(stmt.returnType);
|
|
397
|
+
func.returnType = typeInfo.name;
|
|
398
|
+
}
|
|
399
|
+
for (const opt of stmt.options || []) {
|
|
400
|
+
const defElem = opt.DefElem;
|
|
401
|
+
if (!defElem)
|
|
402
|
+
continue;
|
|
403
|
+
if (defElem.defname === 'language') {
|
|
404
|
+
func.language = defElem.arg?.String?.sval || 'sql';
|
|
405
|
+
}
|
|
406
|
+
else if (defElem.defname === 'as') {
|
|
407
|
+
const parts = (defElem.arg || []).map((a) => a.String?.sval).filter(Boolean);
|
|
408
|
+
func.body = parts[0] || '';
|
|
409
|
+
}
|
|
410
|
+
else if (defElem.defname === 'volatility') {
|
|
411
|
+
func.volatility = defElem.arg?.String?.sval?.toUpperCase();
|
|
412
|
+
}
|
|
413
|
+
else if (defElem.defname === 'strict' && defElem.arg?.Boolean?.boolval) {
|
|
414
|
+
func.isStrict = true;
|
|
415
|
+
}
|
|
416
|
+
else if (defElem.defname === 'security' && defElem.arg?.Boolean?.boolval) {
|
|
417
|
+
func.securityDefiner = true;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
return func;
|
|
421
|
+
}
|
|
422
|
+
function parseCreateTrigStmt(stmt) {
|
|
423
|
+
const events = [];
|
|
424
|
+
if (stmt.events && typeof stmt.events === 'number') {
|
|
425
|
+
if (stmt.events & 4)
|
|
426
|
+
events.push('INSERT');
|
|
427
|
+
if (stmt.events & 8)
|
|
428
|
+
events.push('DELETE');
|
|
429
|
+
if (stmt.events & 16)
|
|
430
|
+
events.push('UPDATE');
|
|
431
|
+
if (stmt.events & 32)
|
|
432
|
+
events.push('TRUNCATE');
|
|
433
|
+
}
|
|
434
|
+
return {
|
|
435
|
+
name: stmt.trigname || '',
|
|
436
|
+
table: stmt.relation?.relname || '',
|
|
437
|
+
timing: stmt.timing === 2 ? 'BEFORE' : stmt.timing === 64 ? 'INSTEAD OF' : 'AFTER',
|
|
438
|
+
events,
|
|
439
|
+
forEach: stmt.row ? 'ROW' : 'STATEMENT',
|
|
440
|
+
functionName: (stmt.funcname || []).map((n) => n.String?.sval).filter(Boolean).join('.'),
|
|
441
|
+
isConstraint: stmt.isconstraint || false,
|
|
442
|
+
deferrable: stmt.deferrable,
|
|
443
|
+
initiallyDeferred: stmt.initdeferred,
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
function parseCreateSeqStmt(stmt) {
|
|
447
|
+
const relation = stmt.sequence;
|
|
448
|
+
const seq = {
|
|
449
|
+
name: relation?.relname || '',
|
|
450
|
+
schema: relation?.schemaname,
|
|
451
|
+
cycle: false,
|
|
452
|
+
};
|
|
453
|
+
for (const opt of stmt.options || []) {
|
|
454
|
+
const defElem = opt.DefElem;
|
|
455
|
+
if (!defElem)
|
|
456
|
+
continue;
|
|
457
|
+
const name = defElem.defname;
|
|
458
|
+
const val = defElem.arg?.Integer?.ival ?? defElem.arg?.Float?.fval;
|
|
459
|
+
if (name === 'start')
|
|
460
|
+
seq.startValue = val;
|
|
461
|
+
else if (name === 'increment')
|
|
462
|
+
seq.increment = val;
|
|
463
|
+
else if (name === 'minvalue')
|
|
464
|
+
seq.minValue = val;
|
|
465
|
+
else if (name === 'maxvalue')
|
|
466
|
+
seq.maxValue = val;
|
|
467
|
+
else if (name === 'cache')
|
|
468
|
+
seq.cache = val;
|
|
469
|
+
else if (name === 'cycle')
|
|
470
|
+
seq.cycle = true;
|
|
471
|
+
}
|
|
472
|
+
return seq;
|
|
473
|
+
}
|
|
474
|
+
export async function parseSQL(sql) {
|
|
475
|
+
const result = await parse(sql);
|
|
476
|
+
const schema = {
|
|
477
|
+
enums: [],
|
|
478
|
+
domains: [],
|
|
479
|
+
sequences: [],
|
|
480
|
+
tables: [],
|
|
481
|
+
views: [],
|
|
482
|
+
functions: [],
|
|
483
|
+
triggers: [],
|
|
484
|
+
extensions: [],
|
|
485
|
+
};
|
|
486
|
+
for (const stmtWrapper of result.stmts || []) {
|
|
487
|
+
const stmt = stmtWrapper.stmt;
|
|
488
|
+
if (!stmt)
|
|
489
|
+
continue;
|
|
490
|
+
const stmtType = Object.keys(stmt)[0];
|
|
491
|
+
const stmtBody = stmt[stmtType];
|
|
492
|
+
try {
|
|
493
|
+
switch (stmtType) {
|
|
494
|
+
case 'CreateStmt':
|
|
495
|
+
const table = await parseCreateStmt(stmtBody);
|
|
496
|
+
schema.tables.push(table);
|
|
497
|
+
break;
|
|
498
|
+
case 'IndexStmt':
|
|
499
|
+
const index = await parseIndexStmt(stmtBody);
|
|
500
|
+
const tableName = stmtBody.relation?.relname;
|
|
501
|
+
const targetTable = schema.tables.find(t => t.name === tableName);
|
|
502
|
+
if (targetTable) {
|
|
503
|
+
targetTable.indexes.push(index);
|
|
504
|
+
}
|
|
505
|
+
break;
|
|
506
|
+
case 'CreateEnumStmt':
|
|
507
|
+
schema.enums.push(parseCreateEnumStmt(stmtBody));
|
|
508
|
+
break;
|
|
509
|
+
case 'CreateDomainStmt':
|
|
510
|
+
schema.domains.push(await parseCreateDomainStmt(stmtBody));
|
|
511
|
+
break;
|
|
512
|
+
case 'ViewStmt':
|
|
513
|
+
schema.views.push(await parseViewStmt(stmtBody, false));
|
|
514
|
+
break;
|
|
515
|
+
case 'CreateTableAsStmt':
|
|
516
|
+
if (stmtBody.objtype === 1) {
|
|
517
|
+
const mv = await parseViewStmt(stmtBody, true);
|
|
518
|
+
schema.views.push(mv);
|
|
519
|
+
}
|
|
520
|
+
break;
|
|
521
|
+
case 'CreateFunctionStmt':
|
|
522
|
+
schema.functions.push(await parseCreateFunctionStmt(stmtBody));
|
|
523
|
+
break;
|
|
524
|
+
case 'CreateTrigStmt':
|
|
525
|
+
schema.triggers.push(parseCreateTrigStmt(stmtBody));
|
|
526
|
+
break;
|
|
527
|
+
case 'CreateSeqStmt':
|
|
528
|
+
schema.sequences.push(parseCreateSeqStmt(stmtBody));
|
|
529
|
+
break;
|
|
530
|
+
case 'CreateExtensionStmt':
|
|
531
|
+
schema.extensions.push(stmtBody.extname || '');
|
|
532
|
+
break;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
catch (error) {
|
|
536
|
+
console.error(`Failed to parse ${stmtType}:`, error);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
return schema;
|
|
540
|
+
}
|
|
541
|
+
export async function deparseSchema(schema) {
|
|
542
|
+
const parts = [];
|
|
543
|
+
for (const ext of schema.extensions) {
|
|
544
|
+
parts.push(`CREATE EXTENSION IF NOT EXISTS "${ext}";`);
|
|
545
|
+
}
|
|
546
|
+
return parts.join('\n\n');
|
|
547
|
+
}
|
|
548
|
+
export async function introspectedToParsedSchema(schema) {
|
|
549
|
+
const parsed = {
|
|
550
|
+
tables: [],
|
|
551
|
+
enums: [],
|
|
552
|
+
domains: [],
|
|
553
|
+
sequences: [],
|
|
554
|
+
views: [],
|
|
555
|
+
functions: [],
|
|
556
|
+
triggers: [],
|
|
557
|
+
extensions: schema.extensions || [],
|
|
558
|
+
};
|
|
559
|
+
for (const e of schema.enums || []) {
|
|
560
|
+
parsed.enums.push({
|
|
561
|
+
name: e.name,
|
|
562
|
+
values: e.values,
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
for (const d of schema.domains || []) {
|
|
566
|
+
parsed.domains.push({
|
|
567
|
+
name: d.name,
|
|
568
|
+
baseType: d.baseType,
|
|
569
|
+
notNull: d.isNotNull || false,
|
|
570
|
+
defaultValue: d.defaultValue,
|
|
571
|
+
checkExpression: d.checkExpression,
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
for (const t of schema.tables || []) {
|
|
575
|
+
const columns = [];
|
|
576
|
+
for (const c of t.columns) {
|
|
577
|
+
const col = {
|
|
578
|
+
name: c.name,
|
|
579
|
+
type: normalizeTypeName(c.dataType, c.udtName),
|
|
580
|
+
typeParams: extractTypeParams(c),
|
|
581
|
+
isNullable: c.isNullable,
|
|
582
|
+
isPrimaryKey: c.isPrimaryKey || false,
|
|
583
|
+
isUnique: c.isUnique || false,
|
|
584
|
+
hasDefault: c.defaultValue != null,
|
|
585
|
+
defaultValue: c.defaultValue || undefined,
|
|
586
|
+
isGenerated: c.isGenerated || false,
|
|
587
|
+
generatedExpression: c.generationExpression || undefined,
|
|
588
|
+
isArray: c.dataType.endsWith('[]') || c.dataType === 'ARRAY' || c.dataType.startsWith('_'),
|
|
589
|
+
comment: c.comment,
|
|
590
|
+
};
|
|
591
|
+
if (c.isGenerated && c.generationExpression) {
|
|
592
|
+
const ast = await parseExpressionToAst(c.generationExpression);
|
|
593
|
+
if (ast) {
|
|
594
|
+
col.generatedExpressionAst = ast;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
if (c.references) {
|
|
598
|
+
col.references = {
|
|
599
|
+
table: c.references.table,
|
|
600
|
+
column: c.references.column,
|
|
601
|
+
onDelete: c.references.onDelete,
|
|
602
|
+
onUpdate: c.references.onUpdate,
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
columns.push(col);
|
|
606
|
+
}
|
|
607
|
+
const constraints = [];
|
|
608
|
+
const processedConstraints = new Set();
|
|
609
|
+
for (const c of t.constraints || []) {
|
|
610
|
+
if (processedConstraints.has(c.name))
|
|
611
|
+
continue;
|
|
612
|
+
processedConstraints.add(c.name);
|
|
613
|
+
const constraint = {
|
|
614
|
+
name: c.name,
|
|
615
|
+
type: c.type,
|
|
616
|
+
columns: c.columns || extractColumnsFromDefinition(c.definition),
|
|
617
|
+
};
|
|
618
|
+
if (c.type === 'CHECK') {
|
|
619
|
+
constraint.expression = extractCheckExpression(c.definition);
|
|
620
|
+
}
|
|
621
|
+
if (c.type === 'EXCLUDE') {
|
|
622
|
+
constraint.expression = extractExcludeExpression(c.definition);
|
|
623
|
+
}
|
|
624
|
+
if (c.type === 'FOREIGN KEY' && c.definition) {
|
|
625
|
+
try {
|
|
626
|
+
const fkAst = await parseForeignKeyDefinition(c.definition);
|
|
627
|
+
if (fkAst) {
|
|
628
|
+
constraint.references = fkAst;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
catch {
|
|
632
|
+
const fkMatch = c.definition.match(/REFERENCES\s+"?(\w+)"?\s*\(([^)]+)\)/i);
|
|
633
|
+
if (fkMatch) {
|
|
634
|
+
const refColumns = fkMatch[2].split(',').map(s => s.trim().replace(/^"|"$/g, ''));
|
|
635
|
+
constraint.references = {
|
|
636
|
+
table: fkMatch[1],
|
|
637
|
+
columns: refColumns,
|
|
638
|
+
};
|
|
639
|
+
const onDeleteMatch = c.definition.match(/ON DELETE\s+(\w+(?:\s+\w+)?)/i);
|
|
640
|
+
const onUpdateMatch = c.definition.match(/ON UPDATE\s+(\w+(?:\s+\w+)?)/i);
|
|
641
|
+
if (onDeleteMatch)
|
|
642
|
+
constraint.references.onDelete = onDeleteMatch[1].toUpperCase();
|
|
643
|
+
if (onUpdateMatch)
|
|
644
|
+
constraint.references.onUpdate = onUpdateMatch[1].toUpperCase();
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
constraints.push(constraint);
|
|
649
|
+
}
|
|
650
|
+
const indexes = (t.indexes || [])
|
|
651
|
+
.filter(i => !i.isPrimary)
|
|
652
|
+
.map(i => {
|
|
653
|
+
let cols;
|
|
654
|
+
if (Array.isArray(i.columns)) {
|
|
655
|
+
cols = i.columns;
|
|
656
|
+
}
|
|
657
|
+
else if (typeof i.columns === 'string') {
|
|
658
|
+
cols = i.columns.replace(/^\{|\}$/g, '').split(',').filter(Boolean);
|
|
659
|
+
}
|
|
660
|
+
else {
|
|
661
|
+
cols = [];
|
|
662
|
+
}
|
|
663
|
+
return {
|
|
664
|
+
name: i.name,
|
|
665
|
+
columns: cols,
|
|
666
|
+
isUnique: i.isUnique,
|
|
667
|
+
method: i.type,
|
|
668
|
+
whereClause: i.whereClause,
|
|
669
|
+
};
|
|
670
|
+
});
|
|
671
|
+
parsed.tables.push({
|
|
672
|
+
name: t.name,
|
|
673
|
+
schema: t.schema,
|
|
674
|
+
columns,
|
|
675
|
+
constraints,
|
|
676
|
+
indexes,
|
|
677
|
+
isPartitioned: t.isPartitioned || false,
|
|
678
|
+
partitionType: t.partitionType,
|
|
679
|
+
partitionKey: t.partitionKey,
|
|
680
|
+
comment: t.comment,
|
|
681
|
+
childPartitions: t.childPartitions,
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
for (const f of schema.functions || []) {
|
|
685
|
+
parsed.functions.push({
|
|
686
|
+
name: f.name,
|
|
687
|
+
args: parseArgTypes(f.argTypes),
|
|
688
|
+
returnType: f.returnType,
|
|
689
|
+
language: f.language,
|
|
690
|
+
body: f.definition || '',
|
|
691
|
+
isStrict: false,
|
|
692
|
+
securityDefiner: false,
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
for (const t of schema.triggers || []) {
|
|
696
|
+
parsed.triggers.push({
|
|
697
|
+
name: t.name,
|
|
698
|
+
table: t.tableName,
|
|
699
|
+
timing: t.timing,
|
|
700
|
+
events: [t.event],
|
|
701
|
+
forEach: 'STATEMENT',
|
|
702
|
+
functionName: t.functionName || '',
|
|
703
|
+
isConstraint: false,
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
return parsed;
|
|
707
|
+
}
|
|
708
|
+
function normalizeTypeName(dataType, udtName) {
|
|
709
|
+
const type = dataType.replace('[]', '').toLowerCase();
|
|
710
|
+
const typeMap = {
|
|
711
|
+
'character varying': 'varchar',
|
|
712
|
+
'character': 'char',
|
|
713
|
+
'integer': 'integer',
|
|
714
|
+
'bigint': 'bigint',
|
|
715
|
+
'smallint': 'smallint',
|
|
716
|
+
'boolean': 'boolean',
|
|
717
|
+
'text': 'text',
|
|
718
|
+
'timestamp with time zone': 'timestamptz',
|
|
719
|
+
'timestamp without time zone': 'timestamp',
|
|
720
|
+
'date': 'date',
|
|
721
|
+
'time with time zone': 'timetz',
|
|
722
|
+
'time without time zone': 'time',
|
|
723
|
+
'jsonb': 'jsonb',
|
|
724
|
+
'json': 'json',
|
|
725
|
+
'uuid': 'uuid',
|
|
726
|
+
'numeric': 'numeric',
|
|
727
|
+
'real': 'real',
|
|
728
|
+
'double precision': 'double precision',
|
|
729
|
+
'bytea': 'bytea',
|
|
730
|
+
'inet': 'inet',
|
|
731
|
+
'cidr': 'cidr',
|
|
732
|
+
'macaddr': 'macaddr',
|
|
733
|
+
'tsvector': 'tsvector',
|
|
734
|
+
'tsquery': 'tsquery',
|
|
735
|
+
'_text': 'text',
|
|
736
|
+
'_jsonb': 'jsonb',
|
|
737
|
+
'_json': 'json',
|
|
738
|
+
'_int4': 'integer',
|
|
739
|
+
'_int8': 'bigint',
|
|
740
|
+
'_int2': 'smallint',
|
|
741
|
+
'_bool': 'boolean',
|
|
742
|
+
'_float4': 'real',
|
|
743
|
+
'_float8': 'double precision',
|
|
744
|
+
'_numeric': 'numeric',
|
|
745
|
+
'_varchar': 'varchar',
|
|
746
|
+
'_char': 'char',
|
|
747
|
+
'_uuid': 'uuid',
|
|
748
|
+
'_bytea': 'bytea',
|
|
749
|
+
'_date': 'date',
|
|
750
|
+
'_timestamp': 'timestamp',
|
|
751
|
+
'_timestamptz': 'timestamptz',
|
|
752
|
+
'_time': 'time',
|
|
753
|
+
'_timetz': 'timetz',
|
|
754
|
+
'_inet': 'inet',
|
|
755
|
+
'_cidr': 'cidr',
|
|
756
|
+
'_macaddr': 'macaddr',
|
|
757
|
+
'_tsvector': 'tsvector',
|
|
758
|
+
'_tsquery': 'tsquery',
|
|
759
|
+
};
|
|
760
|
+
return typeMap[type] || udtName || type;
|
|
761
|
+
}
|
|
762
|
+
function extractTypeParams(col) {
|
|
763
|
+
if (col.maxLength)
|
|
764
|
+
return { length: col.maxLength };
|
|
765
|
+
if (col.precision != null)
|
|
766
|
+
return { precision: col.precision, scale: col.scale || undefined };
|
|
767
|
+
return undefined;
|
|
768
|
+
}
|
|
769
|
+
function extractColumnsFromDefinition(definition) {
|
|
770
|
+
const match = definition.match(/\(([^)]+)\)/);
|
|
771
|
+
if (!match)
|
|
772
|
+
return [];
|
|
773
|
+
return match[1].split(',').map(c => c.trim().replace(/"/g, ''));
|
|
774
|
+
}
|
|
775
|
+
function extractCheckExpression(definition) {
|
|
776
|
+
const match = definition.match(/CHECK\s*\((.+)\)/i);
|
|
777
|
+
return match ? match[1] : definition;
|
|
778
|
+
}
|
|
779
|
+
function extractExcludeExpression(definition) {
|
|
780
|
+
const excludeMatch = definition.match(/EXCLUDE\s+(.+)$/is);
|
|
781
|
+
return excludeMatch ? excludeMatch[1].trim() : definition;
|
|
782
|
+
}
|
|
783
|
+
function parseArgTypes(argTypes) {
|
|
784
|
+
if (!argTypes)
|
|
785
|
+
return [];
|
|
786
|
+
const parts = Array.isArray(argTypes) ? argTypes : argTypes.split(',');
|
|
787
|
+
return parts.map(arg => ({
|
|
788
|
+
type: typeof arg === 'string' ? arg.trim() : String(arg),
|
|
789
|
+
}));
|
|
790
|
+
}
|
|
791
|
+
export { parse, deparse };
|
|
792
|
+
export function normalizedToParsedSchema(schema) {
|
|
793
|
+
return {
|
|
794
|
+
extensions: (schema.extensions || []).map(e => typeof e === 'string' ? e : e.name),
|
|
795
|
+
enums: (schema.enums || []).map(e => ({
|
|
796
|
+
name: e.name,
|
|
797
|
+
schema: e.schema,
|
|
798
|
+
values: e.values,
|
|
799
|
+
})),
|
|
800
|
+
domains: (schema.domains || []).map(d => ({
|
|
801
|
+
name: d.name,
|
|
802
|
+
baseType: d.baseType,
|
|
803
|
+
notNull: d.notNull ?? false,
|
|
804
|
+
defaultValue: d.default,
|
|
805
|
+
checkExpression: d.check,
|
|
806
|
+
checkName: d.checkName,
|
|
807
|
+
})),
|
|
808
|
+
sequences: (schema.sequences || []).map(s => ({
|
|
809
|
+
name: s.name,
|
|
810
|
+
startValue: s.start,
|
|
811
|
+
increment: s.increment,
|
|
812
|
+
minValue: s.minValue,
|
|
813
|
+
maxValue: s.maxValue,
|
|
814
|
+
cache: s.cache,
|
|
815
|
+
cycle: s.cycle ?? false,
|
|
816
|
+
})),
|
|
817
|
+
tables: schema.tables.map(t => ({
|
|
818
|
+
name: t.name,
|
|
819
|
+
schema: t.schema,
|
|
820
|
+
columns: t.columns.map(c => {
|
|
821
|
+
const isArray = c.type.endsWith('[]');
|
|
822
|
+
const baseType = isArray ? c.type.slice(0, -2) : c.type;
|
|
823
|
+
return {
|
|
824
|
+
name: c.name,
|
|
825
|
+
type: baseType,
|
|
826
|
+
isNullable: c.nullable ?? true,
|
|
827
|
+
isPrimaryKey: c.primaryKey ?? false,
|
|
828
|
+
isUnique: c.unique ?? false,
|
|
829
|
+
hasDefault: c.default != null,
|
|
830
|
+
defaultValue: c.default ?? undefined,
|
|
831
|
+
isGenerated: c.isGenerated ?? false,
|
|
832
|
+
generatedExpression: c.generatedExpression ?? undefined,
|
|
833
|
+
generatedExpressionAst: c.generatedExpressionAst ?? undefined,
|
|
834
|
+
isArray,
|
|
835
|
+
checkConstraint: c.check ? { name: c.checkName || `check_${c.name}`, expression: c.check } : undefined,
|
|
836
|
+
references: c.references,
|
|
837
|
+
comment: c.comment,
|
|
838
|
+
};
|
|
839
|
+
}),
|
|
840
|
+
constraints: (t.constraints || []).map(c => ({
|
|
841
|
+
name: c.name,
|
|
842
|
+
type: c.type,
|
|
843
|
+
columns: c.columns || [],
|
|
844
|
+
expression: c.definition,
|
|
845
|
+
references: c.references ? {
|
|
846
|
+
table: c.references.table,
|
|
847
|
+
columns: c.references.columns,
|
|
848
|
+
onDelete: c.references.onDelete,
|
|
849
|
+
onUpdate: c.references.onUpdate,
|
|
850
|
+
match: c.references.match,
|
|
851
|
+
deferrable: c.references.deferrable,
|
|
852
|
+
initiallyDeferred: c.references.initiallyDeferred,
|
|
853
|
+
} : undefined,
|
|
854
|
+
})),
|
|
855
|
+
indexes: t.indexes.map(i => {
|
|
856
|
+
let cols;
|
|
857
|
+
if (Array.isArray(i.columns)) {
|
|
858
|
+
cols = i.columns;
|
|
859
|
+
}
|
|
860
|
+
else if (typeof i.columns === 'string') {
|
|
861
|
+
cols = i.columns.replace(/^\{|\}$/g, '').split(',').filter(Boolean);
|
|
862
|
+
}
|
|
863
|
+
else {
|
|
864
|
+
cols = [];
|
|
865
|
+
}
|
|
866
|
+
return {
|
|
867
|
+
name: i.name,
|
|
868
|
+
columns: cols,
|
|
869
|
+
isUnique: i.unique ?? false,
|
|
870
|
+
method: i.type,
|
|
871
|
+
whereClause: i.whereClause,
|
|
872
|
+
whereClauseAst: i.whereClauseAst,
|
|
873
|
+
};
|
|
874
|
+
}),
|
|
875
|
+
isPartitioned: t.isPartitioned ?? false,
|
|
876
|
+
partitionType: t.partitionType,
|
|
877
|
+
partitionKey: t.partitionKey,
|
|
878
|
+
comment: t.comment,
|
|
879
|
+
})),
|
|
880
|
+
views: (schema.views || []).map(v => ({
|
|
881
|
+
name: v.name,
|
|
882
|
+
definition: v.definition,
|
|
883
|
+
isMaterialized: v.isMaterialized ?? false,
|
|
884
|
+
})),
|
|
885
|
+
functions: (schema.functions || []).map(f => ({
|
|
886
|
+
name: f.name,
|
|
887
|
+
schema: f.schema,
|
|
888
|
+
args: f.args || [],
|
|
889
|
+
returnType: f.returnType || 'void',
|
|
890
|
+
language: f.language || 'plpgsql',
|
|
891
|
+
body: f.body || '',
|
|
892
|
+
volatility: f.volatility || 'VOLATILE',
|
|
893
|
+
isStrict: f.isStrict ?? false,
|
|
894
|
+
securityDefiner: f.securityDefiner ?? false,
|
|
895
|
+
})),
|
|
896
|
+
triggers: (schema.triggers || []).map(t => ({
|
|
897
|
+
name: t.name,
|
|
898
|
+
table: t.table,
|
|
899
|
+
timing: t.timing || 'AFTER',
|
|
900
|
+
events: t.events || ['INSERT'],
|
|
901
|
+
forEach: t.level || 'ROW',
|
|
902
|
+
functionName: t.functionName || '',
|
|
903
|
+
whenClause: t.when,
|
|
904
|
+
isConstraint: false,
|
|
905
|
+
})),
|
|
906
|
+
};
|
|
907
|
+
}
|