pg-migration-engine 0.1.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/LICENSE +35 -0
- package/README.md +304 -0
- package/package.json +52 -0
- package/src/behavioral/behavioral-applier.js +374 -0
- package/src/behavioral/behavioral-extractor.js +266 -0
- package/src/behavioral/behavioral-puller.js +35 -0
- package/src/behavioral/index.js +4 -0
- package/src/behavioral/phase-sorter.js +24 -0
- package/src/constants.js +97 -0
- package/src/ddl-generator/alter-generator.js +1512 -0
- package/src/ddl-generator/comment-generator.js +95 -0
- package/src/ddl-generator/create-generator.js +1426 -0
- package/src/ddl-generator/drop-generator.js +194 -0
- package/src/ddl-generator/generator.js +78 -0
- package/src/ddl-generator/grant-generator.js +31 -0
- package/src/ddl-generator/index.js +3 -0
- package/src/ddl-generator/pg-version.js +15 -0
- package/src/ddl-generator/rename-generator.js +88 -0
- package/src/ddl-generator/safe-patterns.js +241 -0
- package/src/differ/change-classifier.js +192 -0
- package/src/differ/dependency-resolver.js +523 -0
- package/src/differ/index.js +12 -0
- package/src/differ/object-matcher.js +510 -0
- package/src/differ/property-differ.js +1134 -0
- package/src/differ/risk-tagger.js +561 -0
- package/src/differ/schema-differ.js +309 -0
- package/src/differ/utils/levenshtein.js +210 -0
- package/src/differ/utils/path-builder.js +198 -0
- package/src/differ/utils/type-compatibility.js +472 -0
- package/src/errors.js +147 -0
- package/src/executor/drift-detector.js +221 -0
- package/src/executor/index.js +7 -0
- package/src/executor/lock-manager.js +308 -0
- package/src/executor/migration-executor.js +1249 -0
- package/src/executor/progress-tracker.js +81 -0
- package/src/executor/recovery-manager.js +47 -0
- package/src/executor/snapshot-manager.js +22 -0
- package/src/executor/sql-splitter.js +120 -0
- package/src/executor/transaction-manager.js +288 -0
- package/src/index.js +483 -0
- package/src/introspection/index.js +4 -0
- package/src/introspection/introspector.js +250 -0
- package/src/introspection/queries/access-methods.js +29 -0
- package/src/introspection/queries/casts.js +38 -0
- package/src/introspection/queries/collations.js +51 -0
- package/src/introspection/queries/comments.js +66 -0
- package/src/introspection/queries/constraints.js +65 -0
- package/src/introspection/queries/conversions.js +39 -0
- package/src/introspection/queries/databases.js +43 -0
- package/src/introspection/queries/default-privileges.js +37 -0
- package/src/introspection/queries/event-triggers.js +43 -0
- package/src/introspection/queries/extensions.js +21 -0
- package/src/introspection/queries/foreign-data.js +136 -0
- package/src/introspection/queries/functions.js +75 -0
- package/src/introspection/queries/grants.js +38 -0
- package/src/introspection/queries/index.js +33 -0
- package/src/introspection/queries/indexes.js +99 -0
- package/src/introspection/queries/inheritance.js +24 -0
- package/src/introspection/queries/multiranges.js +37 -0
- package/src/introspection/queries/operators.js +180 -0
- package/src/introspection/queries/partitions.js +42 -0
- package/src/introspection/queries/pg18-19.js +43 -0
- package/src/introspection/queries/policies.js +35 -0
- package/src/introspection/queries/procedural-languages.js +36 -0
- package/src/introspection/queries/publications.js +114 -0
- package/src/introspection/queries/roles.js +66 -0
- package/src/introspection/queries/rules.js +49 -0
- package/src/introspection/queries/sequences.js +37 -0
- package/src/introspection/queries/statistics.js +74 -0
- package/src/introspection/queries/subscriptions.js +99 -0
- package/src/introspection/queries/tables.js +151 -0
- package/src/introspection/queries/tablespaces.js +36 -0
- package/src/introspection/queries/text-search.js +146 -0
- package/src/introspection/queries/triggers.js +61 -0
- package/src/introspection/queries/types.js +106 -0
- package/src/introspection/queries/views.js +146 -0
- package/src/introspection/translator.js +1283 -0
- package/src/introspection/version-detector.js +25 -0
- package/src/planner/backfill-planner.js +36 -0
- package/src/planner/dry-run.js +21 -0
- package/src/planner/index.js +6 -0
- package/src/planner/migration-planner.js +356 -0
- package/src/planner/smart-migrator.js +82 -0
- package/src/planner/step-sequencer.js +61 -0
- package/src/planner/type-registry.js +47 -0
- package/src/risk/compatibility-checker.js +29 -0
- package/src/risk/data-loss-checker.js +32 -0
- package/src/risk/destructive-checker.js +38 -0
- package/src/risk/index.js +6 -0
- package/src/risk/lock-analyzer.js +39 -0
- package/src/risk/recommendations.js +44 -0
- package/src/risk/risk-engine.js +25 -0
- package/src/storage/index.js +5 -0
- package/src/storage/memory-storage.js +87 -0
- package/src/storage/migration-table.js +423 -0
- package/src/storage/rollback-generator.js +344 -0
- package/src/types/changes.js +136 -0
- package/src/types/connection.js +17 -0
- package/src/types/execution.js +107 -0
- package/src/types/index.js +1 -0
- package/src/types/migration.js +67 -0
- package/src/types/risk.js +32 -0
- package/src/types/schema.d.ts +1004 -0
- package/src/types/schema.js +561 -0
|
@@ -0,0 +1,1426 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate CREATE DDL for any object type.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export function generateCreateSql(change) {
|
|
6
|
+
const obj = change.after;
|
|
7
|
+
if (!obj) return '';
|
|
8
|
+
|
|
9
|
+
let sql = generateBaseCreateSql(change);
|
|
10
|
+
if (!sql || sql.startsWith('--')) {
|
|
11
|
+
return sql;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (!sql.endsWith(';')) {
|
|
15
|
+
sql += ';';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const s = obj.schema || change.schema;
|
|
19
|
+
const name = obj.name;
|
|
20
|
+
const t = obj.table || obj.tableName || change.table || change.tableName;
|
|
21
|
+
|
|
22
|
+
const objName = (() => {
|
|
23
|
+
switch (change.objectType) {
|
|
24
|
+
case 'column':
|
|
25
|
+
return `${ident(s || 'public')}.${ident(t)}.${ident(name)}`;
|
|
26
|
+
case 'constraint':
|
|
27
|
+
return `${ident(name)} ON ${ident(s || 'public')}.${ident(t)}`;
|
|
28
|
+
case 'trigger':
|
|
29
|
+
case 'rule':
|
|
30
|
+
case 'policy':
|
|
31
|
+
return `${ident(name)} ON ${ident(s || 'public')}.${ident(t)}`;
|
|
32
|
+
case 'function':
|
|
33
|
+
case 'procedure':
|
|
34
|
+
const args = (obj.argumentTypes || obj.arguments || []).join(', ');
|
|
35
|
+
return `${ident(s || 'public')}.${ident(name)}(${args})`;
|
|
36
|
+
case 'operator':
|
|
37
|
+
return `${ident(s || 'public')}.${name} (${obj.leftType || 'NONE'}, ${obj.rightType || 'NONE'})`;
|
|
38
|
+
case 'operatorClass':
|
|
39
|
+
case 'operatorFamily':
|
|
40
|
+
return `${ident(s || 'public')}.${ident(name)} USING ${obj.accessMethod || 'btree'}`;
|
|
41
|
+
case 'schema':
|
|
42
|
+
case 'language':
|
|
43
|
+
case 'eventTrigger':
|
|
44
|
+
case 'foreignServer':
|
|
45
|
+
case 'foreignDataWrapper':
|
|
46
|
+
case 'publication':
|
|
47
|
+
case 'subscription':
|
|
48
|
+
return ident(name);
|
|
49
|
+
default:
|
|
50
|
+
return s ? `${ident(s)}.${ident(name)}` : ident(name);
|
|
51
|
+
}
|
|
52
|
+
})();
|
|
53
|
+
|
|
54
|
+
const typeMap = {
|
|
55
|
+
table: 'TABLE',
|
|
56
|
+
view: 'VIEW',
|
|
57
|
+
materializedView: 'MATERIALIZED VIEW',
|
|
58
|
+
function: 'FUNCTION',
|
|
59
|
+
procedure: 'PROCEDURE',
|
|
60
|
+
sequence: 'SEQUENCE',
|
|
61
|
+
schema: 'SCHEMA',
|
|
62
|
+
type: 'TYPE',
|
|
63
|
+
domain: 'DOMAIN',
|
|
64
|
+
foreignTable: 'FOREIGN TABLE',
|
|
65
|
+
foreignDataWrapper: 'FOREIGN DATA WRAPPER',
|
|
66
|
+
foreignServer: 'SERVER',
|
|
67
|
+
publication: 'PUBLICATION',
|
|
68
|
+
subscription: 'SUBSCRIPTION',
|
|
69
|
+
textSearchConfig: 'TEXT SEARCH CONFIGURATION',
|
|
70
|
+
textSearchDict: 'TEXT SEARCH DICTIONARY',
|
|
71
|
+
conversion: 'CONVERSION',
|
|
72
|
+
language: 'LANGUAGE',
|
|
73
|
+
operator: 'OPERATOR',
|
|
74
|
+
operatorClass: 'OPERATOR CLASS',
|
|
75
|
+
operatorFamily: 'OPERATOR FAMILY',
|
|
76
|
+
eventTrigger: 'EVENT TRIGGER',
|
|
77
|
+
policy: 'POLICY',
|
|
78
|
+
rule: 'RULE',
|
|
79
|
+
trigger: 'TRIGGER',
|
|
80
|
+
constraint: 'CONSTRAINT',
|
|
81
|
+
index: 'INDEX',
|
|
82
|
+
collation: 'COLLATION',
|
|
83
|
+
cast: 'CAST'
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const typeKeyword = typeMap[change.objectType];
|
|
87
|
+
|
|
88
|
+
if (typeKeyword) {
|
|
89
|
+
const skipOwner = ['cast', 'accessMethod', 'defaultPrivileges', 'constraint', 'policy', 'rule', 'trigger'];
|
|
90
|
+
if (obj.owner && !skipOwner.includes(change.objectType) && !sql.includes('OWNER TO')) {
|
|
91
|
+
sql += `\nALTER ${typeKeyword} ${objName} OWNER TO ${ident(obj.owner)};`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (obj.comment && !sql.includes('COMMENT ON ')) {
|
|
95
|
+
sql += `\nCOMMENT ON ${typeKeyword} ${objName} IS '${escapeString(obj.comment)}';`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const supportsGrants = [
|
|
99
|
+
'table', 'column', 'view', 'materializedView', 'function', 'procedure',
|
|
100
|
+
'sequence', 'type', 'schema', 'foreignTable', 'foreignDataWrapper',
|
|
101
|
+
'foreignServer', 'publication', 'subscription', 'textSearchConfig',
|
|
102
|
+
'textSearchDict', 'conversion', 'language'
|
|
103
|
+
];
|
|
104
|
+
if (supportsGrants.includes(change.objectType) && obj.privileges && obj.privileges.length > 0 && !sql.includes('GRANT ')) {
|
|
105
|
+
for (const g of obj.privileges) {
|
|
106
|
+
let grantSql = `GRANT ${g.privilege} ON ${typeKeyword} ${objName} TO ${ident(g.grantee)}`;
|
|
107
|
+
if (g.isGrantable) grantSql += ' WITH GRANT OPTION';
|
|
108
|
+
sql += `\n${grantSql};`;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return sql;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function generateBaseCreateSql(change) {
|
|
117
|
+
const obj = change.after;
|
|
118
|
+
if (!obj) return '';
|
|
119
|
+
|
|
120
|
+
switch (change.objectType) {
|
|
121
|
+
case 'schema':
|
|
122
|
+
return `CREATE SCHEMA IF NOT EXISTS ${ident(obj.name)};`;
|
|
123
|
+
|
|
124
|
+
case 'table':
|
|
125
|
+
return generateCreateTableSql(obj);
|
|
126
|
+
|
|
127
|
+
case 'view':
|
|
128
|
+
return generateCreateViewSql(obj, change);
|
|
129
|
+
|
|
130
|
+
case 'materializedView':
|
|
131
|
+
return generateCreateMaterializedViewSql(obj);
|
|
132
|
+
|
|
133
|
+
case 'function':
|
|
134
|
+
case 'procedure':
|
|
135
|
+
return generateCreateFunctionSql(obj, change.objectType === 'procedure');
|
|
136
|
+
|
|
137
|
+
case 'trigger':
|
|
138
|
+
return generateCreateTriggerSql(obj);
|
|
139
|
+
|
|
140
|
+
case 'index':
|
|
141
|
+
return generateCreateIndexSql(obj);
|
|
142
|
+
|
|
143
|
+
case 'constraint':
|
|
144
|
+
return generateCreateConstraintSql(obj);
|
|
145
|
+
|
|
146
|
+
case 'sequence':
|
|
147
|
+
return generateCreateSequenceSql(obj);
|
|
148
|
+
|
|
149
|
+
case 'extension':
|
|
150
|
+
return generateCreateExtensionSql(obj);
|
|
151
|
+
|
|
152
|
+
case 'policy':
|
|
153
|
+
return generateCreatePolicySql(obj);
|
|
154
|
+
|
|
155
|
+
case 'type':
|
|
156
|
+
return generateCreateTypeSql(obj);
|
|
157
|
+
|
|
158
|
+
case 'domain':
|
|
159
|
+
return generateCreateDomainSql(obj);
|
|
160
|
+
|
|
161
|
+
case 'rule':
|
|
162
|
+
return generateCreateRuleSql(obj);
|
|
163
|
+
|
|
164
|
+
case 'aggregate':
|
|
165
|
+
return generateCreateAggregateSql(obj);
|
|
166
|
+
|
|
167
|
+
case 'textSearchParser':
|
|
168
|
+
return generateCreateTextSearchParserSql(obj);
|
|
169
|
+
|
|
170
|
+
case 'textSearchTemplate':
|
|
171
|
+
return generateCreateTextSearchTemplateSql(obj);
|
|
172
|
+
|
|
173
|
+
case 'textSearchDict':
|
|
174
|
+
return generateCreateTextSearchDictSql(obj);
|
|
175
|
+
|
|
176
|
+
case 'textSearchConfig':
|
|
177
|
+
return generateCreateTextSearchConfigSql(obj);
|
|
178
|
+
|
|
179
|
+
case 'operator':
|
|
180
|
+
return generateCreateOperatorSql(obj);
|
|
181
|
+
|
|
182
|
+
case 'operatorClass':
|
|
183
|
+
return generateCreateOperatorClassSql(obj);
|
|
184
|
+
|
|
185
|
+
case 'operatorFamily':
|
|
186
|
+
return generateCreateOperatorFamilySql(obj);
|
|
187
|
+
|
|
188
|
+
case 'publication':
|
|
189
|
+
return generateCreatePublicationSql(obj);
|
|
190
|
+
|
|
191
|
+
case 'subscription':
|
|
192
|
+
return generateCreateSubscriptionSql(obj);
|
|
193
|
+
|
|
194
|
+
case 'statistics':
|
|
195
|
+
return generateCreateStatisticsSql(obj);
|
|
196
|
+
|
|
197
|
+
case 'collation':
|
|
198
|
+
return generateCreateCollationSql(obj);
|
|
199
|
+
|
|
200
|
+
case 'cast':
|
|
201
|
+
return generateCreateCastSql(obj);
|
|
202
|
+
|
|
203
|
+
case 'foreignServer':
|
|
204
|
+
return generateCreateForeignServerSql(obj);
|
|
205
|
+
|
|
206
|
+
case 'foreignDataWrapper':
|
|
207
|
+
return generateCreateForeignDataWrapperSql(obj);
|
|
208
|
+
|
|
209
|
+
case 'userMapping':
|
|
210
|
+
return generateCreateUserMappingSql(obj);
|
|
211
|
+
|
|
212
|
+
case 'foreignTable':
|
|
213
|
+
return generateCreateForeignTableSql(obj);
|
|
214
|
+
|
|
215
|
+
case 'eventTrigger':
|
|
216
|
+
return generateCreateEventTriggerSql(obj);
|
|
217
|
+
|
|
218
|
+
case 'role':
|
|
219
|
+
return generateCreateRoleSql(obj);
|
|
220
|
+
|
|
221
|
+
case 'database':
|
|
222
|
+
return generateCreateDatabaseSql(obj);
|
|
223
|
+
|
|
224
|
+
case 'tablespace':
|
|
225
|
+
return generateCreateTablespaceSql(obj);
|
|
226
|
+
|
|
227
|
+
case 'column':
|
|
228
|
+
return generateCreateColumnSql(change);
|
|
229
|
+
|
|
230
|
+
case 'language':
|
|
231
|
+
return generateCreateLanguageSql(obj);
|
|
232
|
+
|
|
233
|
+
case 'defaultPrivileges':
|
|
234
|
+
return generateCreateDefaultPrivilegesSql(obj);
|
|
235
|
+
|
|
236
|
+
case 'conversion':
|
|
237
|
+
return generateCreateConversionSql(obj);
|
|
238
|
+
|
|
239
|
+
case 'accessMethod':
|
|
240
|
+
return generateCreateAccessMethodSql(obj);
|
|
241
|
+
|
|
242
|
+
default:
|
|
243
|
+
return `-- Unsupported CREATE for ${change.objectType}`;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function generateCreateColumnSql(change) {
|
|
248
|
+
const col = change.after || change.desired || {};
|
|
249
|
+
const parts = change.objectKey.split('.');
|
|
250
|
+
const colName = ident(parts.pop());
|
|
251
|
+
const tableName = parts.map(ident).join('.');
|
|
252
|
+
|
|
253
|
+
let sql = `ALTER TABLE ${tableName} ADD COLUMN ${colName} ${col.dataType}`;
|
|
254
|
+
|
|
255
|
+
if (col.collation) {
|
|
256
|
+
sql += ` COLLATE ${col.collation}`;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (col.nullable === false || col.isNullable === false) {
|
|
260
|
+
sql += ' NOT NULL';
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (col.defaultValue !== undefined && col.defaultValue !== null) {
|
|
264
|
+
sql += ` DEFAULT ${col.defaultValue}`;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (col.isIdentity) {
|
|
268
|
+
const rawMode = col.identityMode || col.identityType || (col.isAlwaysIdentity ? 'ALWAYS' : 'BY DEFAULT');
|
|
269
|
+
const mode = rawMode === 'BY_DEFAULT' ? 'BY DEFAULT' : rawMode;
|
|
270
|
+
sql += ` GENERATED ${mode} AS IDENTITY`;
|
|
271
|
+
const seqOpts = [];
|
|
272
|
+
if (col.identityStart != null) seqOpts.push(`START ${col.identityStart}`);
|
|
273
|
+
if (col.identityIncrement != null) seqOpts.push(`INCREMENT ${col.identityIncrement}`);
|
|
274
|
+
if (col.identityMinimum != null || col.identityMin != null) {
|
|
275
|
+
seqOpts.push(`MINVALUE ${col.identityMinimum ?? col.identityMin}`);
|
|
276
|
+
}
|
|
277
|
+
if (col.identityMaximum != null || col.identityMax != null) {
|
|
278
|
+
seqOpts.push(`MAXVALUE ${col.identityMaximum ?? col.identityMax}`);
|
|
279
|
+
}
|
|
280
|
+
if (col.identityCycle) seqOpts.push('CYCLE');
|
|
281
|
+
if (col.identityCache != null) seqOpts.push(`CACHE ${col.identityCache}`);
|
|
282
|
+
if (seqOpts.length > 0) sql += ` (${seqOpts.join(', ')})`;
|
|
283
|
+
} else if (col.generatedExpression) {
|
|
284
|
+
sql += ` GENERATED ALWAYS AS (${col.generatedExpression}) STORED`;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
sql += ';';
|
|
288
|
+
|
|
289
|
+
if (col.comment) {
|
|
290
|
+
sql += `\nCOMMENT ON COLUMN ${tableName}.${colName} IS '${escapeString(col.comment)}';`;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return sql;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function generateCreateTableSql(table) {
|
|
297
|
+
const tableKey = `${ident(table.schema)}.${ident(table.name)}`;
|
|
298
|
+
|
|
299
|
+
// Helper to generate replica identity DDL
|
|
300
|
+
function getReplicaIdentitySql(replIdent) {
|
|
301
|
+
if (!replIdent) return '';
|
|
302
|
+
const ri = replIdent.toLowerCase();
|
|
303
|
+
if (ri === 'default') {
|
|
304
|
+
return `\nALTER TABLE ${tableKey} REPLICA IDENTITY DEFAULT;`;
|
|
305
|
+
} else if (ri === 'full') {
|
|
306
|
+
return `\nALTER TABLE ${tableKey} REPLICA IDENTITY FULL;`;
|
|
307
|
+
} else if (ri === 'nothing') {
|
|
308
|
+
return `\nALTER TABLE ${tableKey} REPLICA IDENTITY NOTHING;`;
|
|
309
|
+
} else if (ri.startsWith('index:')) {
|
|
310
|
+
const idxName = replIdent.split(':')[1];
|
|
311
|
+
return `\nALTER TABLE ${tableKey} REPLICA IDENTITY USING INDEX ${ident(idxName)};`;
|
|
312
|
+
} else {
|
|
313
|
+
return `\nALTER TABLE ${tableKey} REPLICA IDENTITY ${replIdent.toUpperCase()};`;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Handle child partition creation syntax (does not define columns directly)
|
|
318
|
+
if (table.isPartition && table.partitionParent && table.partitionBound) {
|
|
319
|
+
const parentParts = table.partitionParent.split('.');
|
|
320
|
+
const parentRef = parentParts.map(ident).join('.');
|
|
321
|
+
let sql = `CREATE TABLE ${tableKey} PARTITION OF ${parentRef} ${table.partitionBound}`;
|
|
322
|
+
if (table.accessMethod) {
|
|
323
|
+
sql += ` USING ${ident(table.accessMethod)}`;
|
|
324
|
+
}
|
|
325
|
+
if (table.tablespace) sql += ` TABLESPACE ${ident(table.tablespace)}`;
|
|
326
|
+
sql += ';';
|
|
327
|
+
|
|
328
|
+
if (table.owner) {
|
|
329
|
+
sql += `\nALTER TABLE ${tableKey} OWNER TO ${ident(table.owner)};`;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (table.comment) {
|
|
333
|
+
sql += `\nCOMMENT ON TABLE ${tableKey} IS '${table.comment.replace(/'/g, "''")}';`;
|
|
334
|
+
}
|
|
335
|
+
if (table.privileges && table.privileges.length > 0) {
|
|
336
|
+
for (const g of table.privileges) {
|
|
337
|
+
let grantSql = `GRANT ${g.privilege} ON TABLE ${tableKey} TO ${ident(g.grantee)}`;
|
|
338
|
+
if (g.isGrantable) grantSql += ' WITH GRANT OPTION';
|
|
339
|
+
sql += `\n${grantSql};`;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// RLS handling for partition child
|
|
344
|
+
if (table.rlsEnabled) {
|
|
345
|
+
sql += `\nALTER TABLE ${tableKey} ENABLE ROW LEVEL SECURITY;`;
|
|
346
|
+
}
|
|
347
|
+
if (table.rlsForced) {
|
|
348
|
+
sql += `\nALTER TABLE ${tableKey} FORCE ROW LEVEL SECURITY;`;
|
|
349
|
+
}
|
|
350
|
+
if (table.rlsForced && table.rlsForcedRoles && table.rlsForcedRoles.length > 0) {
|
|
351
|
+
for (const role of table.rlsForcedRoles) {
|
|
352
|
+
sql += `\nALTER TABLE ${tableKey} FORCE ROW LEVEL SECURITY FOR ${ident(role)};`;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Replica Identity handling for partition child
|
|
357
|
+
if (table.replicaIdentity) {
|
|
358
|
+
sql += getReplicaIdentitySql(table.replicaIdentity);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return sql;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const colDefs = (table.columns || []).map(c => {
|
|
365
|
+
let def = `${ident(c.name)} ${c.dataType}`;
|
|
366
|
+
if (!c.isNullable) def += ' NOT NULL';
|
|
367
|
+
if (c.defaultValue !== undefined && c.defaultValue !== null) {
|
|
368
|
+
def += ` DEFAULT ${c.defaultValue}`;
|
|
369
|
+
}
|
|
370
|
+
if (c.isGenerated && c.generatedExpression) {
|
|
371
|
+
def += ` GENERATED ALWAYS AS (${c.generatedExpression}) STORED`;
|
|
372
|
+
} else if (c.isIdentity) {
|
|
373
|
+
// identityMode: 'ALWAYS' | 'BY_DEFAULT' (Differ/types); map BY_DEFAULT → SQL "BY DEFAULT"
|
|
374
|
+
const rawMode = c.identityMode || c.identityType || (c.isAlwaysIdentity ? 'ALWAYS' : 'BY DEFAULT');
|
|
375
|
+
const mode = rawMode === 'BY_DEFAULT' ? 'BY DEFAULT' : rawMode;
|
|
376
|
+
def += ` GENERATED ${mode} AS IDENTITY`;
|
|
377
|
+
const seqOpts = [];
|
|
378
|
+
if (c.identityStart != null) seqOpts.push(`START ${c.identityStart}`);
|
|
379
|
+
if (c.identityIncrement != null) seqOpts.push(`INCREMENT ${c.identityIncrement}`);
|
|
380
|
+
if (c.identityMinimum != null) seqOpts.push(`MINVALUE ${c.identityMinimum}`);
|
|
381
|
+
if (c.identityMaximum != null) seqOpts.push(`MAXVALUE ${c.identityMaximum}`);
|
|
382
|
+
if (c.identityCycle) seqOpts.push('CYCLE');
|
|
383
|
+
if (c.identityCache != null) seqOpts.push(`CACHE ${c.identityCache}`);
|
|
384
|
+
if (seqOpts.length > 0) def += ` (${seqOpts.join(', ')})`;
|
|
385
|
+
}
|
|
386
|
+
if (c.collation) def += ` COLLATE ${c.collation}`;
|
|
387
|
+
return def;
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
const constraints = table.constraints || [];
|
|
391
|
+
for (const con of constraints) {
|
|
392
|
+
const conDef = generateInlineConstraint(con);
|
|
393
|
+
if (conDef) colDefs.push(conDef);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Handle UNLOGGED and TEMPORARY keywords placement
|
|
397
|
+
let typePrefix = 'TABLE';
|
|
398
|
+
if (table.isTemporary) {
|
|
399
|
+
typePrefix = 'TEMP TABLE';
|
|
400
|
+
} else if (table.unlogged || table.isUnlogged || table.rlsEnabled || table.rlsForced) {
|
|
401
|
+
if (table.unlogged || table.isUnlogged) typePrefix = 'UNLOGGED TABLE';
|
|
402
|
+
} else if (table.isLogged === false) {
|
|
403
|
+
typePrefix = 'UNLOGGED TABLE';
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
let sql = `CREATE ${typePrefix} ${tableKey} (\n ${colDefs.join(',\n ')}\n)`;
|
|
407
|
+
|
|
408
|
+
// WITH storage options
|
|
409
|
+
const withOpts = [];
|
|
410
|
+
if (table.storageParameters) {
|
|
411
|
+
for (const [k, v] of Object.entries(table.storageParameters)) {
|
|
412
|
+
withOpts.push(`${k}=${v}`);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
if (table.hasOids) {
|
|
416
|
+
withOpts.push('oids=true');
|
|
417
|
+
}
|
|
418
|
+
if (withOpts.length > 0) {
|
|
419
|
+
sql += ` WITH (${withOpts.join(', ')})`;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// PARTITION BY
|
|
423
|
+
if (table.partitionKeyDef) {
|
|
424
|
+
sql += ` PARTITION BY ${table.partitionKeyDef}`;
|
|
425
|
+
} else if (table.partitionStrategy && table.partitionColumns && table.partitionColumns.length > 0) {
|
|
426
|
+
sql += ` PARTITION BY ${table.partitionStrategy} (${table.partitionColumns.map(ident).join(', ')})`;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// INHERITS
|
|
430
|
+
if (table.inheritsFrom && table.inheritsFrom.length > 0) {
|
|
431
|
+
sql += ` INHERITS (${table.inheritsFrom.join(', ')})`;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
if (table.accessMethod) {
|
|
435
|
+
sql += ` USING ${ident(table.accessMethod)}`;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
if (table.tablespace) {
|
|
439
|
+
sql += ` TABLESPACE ${ident(table.tablespace)}`;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
sql += ';';
|
|
443
|
+
|
|
444
|
+
if (table.owner) {
|
|
445
|
+
sql += `\nALTER TABLE ${tableKey} OWNER TO ${ident(table.owner)};`;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// Appending table comment
|
|
449
|
+
if (table.comment) {
|
|
450
|
+
sql += `\nCOMMENT ON TABLE ${tableKey} IS '${table.comment.replace(/'/g, "''")}';`;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Appending privileges/grants
|
|
454
|
+
if (table.privileges && table.privileges.length > 0) {
|
|
455
|
+
for (const g of table.privileges) {
|
|
456
|
+
let grantSql = `GRANT ${g.privilege} ON TABLE ${tableKey} TO ${ident(g.grantee)}`;
|
|
457
|
+
if (g.isGrantable) grantSql += ' WITH GRANT OPTION';
|
|
458
|
+
sql += `\n${grantSql};`;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// RLS handling
|
|
463
|
+
if (table.rlsEnabled) {
|
|
464
|
+
sql += `\nALTER TABLE ${tableKey} ENABLE ROW LEVEL SECURITY;`;
|
|
465
|
+
}
|
|
466
|
+
if (table.rlsForced) {
|
|
467
|
+
sql += `\nALTER TABLE ${tableKey} FORCE ROW LEVEL SECURITY;`;
|
|
468
|
+
}
|
|
469
|
+
if (table.rlsForced && table.rlsForcedRoles && table.rlsForcedRoles.length > 0) {
|
|
470
|
+
for (const role of table.rlsForcedRoles) {
|
|
471
|
+
sql += `\nALTER TABLE ${tableKey} FORCE ROW LEVEL SECURITY FOR ${ident(role)};`;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Replica Identity handling
|
|
476
|
+
if (table.replicaIdentity) {
|
|
477
|
+
sql += getReplicaIdentitySql(table.replicaIdentity);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
return sql;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function generateInlineConstraint(con) {
|
|
484
|
+
if (!con.name) return null;
|
|
485
|
+
|
|
486
|
+
switch (con.constraintType) {
|
|
487
|
+
case 'PRIMARY KEY':
|
|
488
|
+
case 'PRIMARY_KEY':
|
|
489
|
+
return `CONSTRAINT ${ident(con.name)} PRIMARY KEY (${(con.columns || []).map(ident).join(', ')})`;
|
|
490
|
+
case 'UNIQUE':
|
|
491
|
+
return `CONSTRAINT ${ident(con.name)} UNIQUE (${(con.columns || []).map(ident).join(', ')})`;
|
|
492
|
+
case 'CHECK':
|
|
493
|
+
return `CONSTRAINT ${ident(con.name)} CHECK (${con.definition})`;
|
|
494
|
+
default:
|
|
495
|
+
return null;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function generateCreateViewSql(view, change = {}) {
|
|
500
|
+
const isRecursive = view.isRecursive === true;
|
|
501
|
+
const viewKeyword = isRecursive ? 'RECURSIVE VIEW' : 'VIEW';
|
|
502
|
+
let sql = `CREATE OR REPLACE ${viewKeyword} ${ident(view.schema)}.${ident(view.name)}`;
|
|
503
|
+
|
|
504
|
+
if (view.columns && view.columns.length > 0) {
|
|
505
|
+
const colNames = view.columns.map(c => typeof c === 'string' ? c : c.name || c);
|
|
506
|
+
sql += ` (${colNames.map(ident).join(', ')})`;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
const withOpts = [];
|
|
510
|
+
if (view.securityBarrier !== undefined) {
|
|
511
|
+
withOpts.push(`security_barrier = ${view.securityBarrier}`);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
const pgVersion = change.pgVersion || change.metadata?.pgVersion;
|
|
515
|
+
if (view.securityInvoker !== undefined) {
|
|
516
|
+
if (!pgVersion || pgVersion >= 150000) {
|
|
517
|
+
withOpts.push(`security_invoker = ${view.securityInvoker}`);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
if (view.checkOption && (view.checkOption.toLowerCase() === 'local' || view.checkOption.toLowerCase() === 'cascaded')) {
|
|
522
|
+
withOpts.push(`check_option = '${view.checkOption.toLowerCase()}'`);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
if (withOpts.length > 0) {
|
|
526
|
+
sql += ` WITH (${withOpts.join(', ')})`;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
sql += ` AS ${view.definition}`;
|
|
530
|
+
|
|
531
|
+
if (view.checkOption && view.checkOption !== 'NONE' && withOpts.length === 0) {
|
|
532
|
+
const opt = view.checkOption.toUpperCase();
|
|
533
|
+
sql += ` WITH ${opt} CHECK OPTION`;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
sql += ';';
|
|
537
|
+
|
|
538
|
+
const viewKey = `${ident(view.schema)}.${ident(view.name)}`;
|
|
539
|
+
if (view.owner) {
|
|
540
|
+
sql += `\nALTER VIEW ${viewKey} OWNER TO ${ident(view.owner)};`;
|
|
541
|
+
}
|
|
542
|
+
if (view.comment) {
|
|
543
|
+
sql += `\nCOMMENT ON VIEW ${viewKey} IS '${escapeString(view.comment)}';`;
|
|
544
|
+
}
|
|
545
|
+
if (view.privileges && view.privileges.length > 0) {
|
|
546
|
+
for (const g of view.privileges) {
|
|
547
|
+
let grantSql = `GRANT ${g.privilege} ON VIEW ${viewKey} TO ${ident(g.grantee)}`;
|
|
548
|
+
if (g.isGrantable) grantSql += ' WITH GRANT OPTION';
|
|
549
|
+
sql += `\n${grantSql};`;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
return sql;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
function generateCreateFunctionSql(fn, isProcedure = false) {
|
|
557
|
+
const keyword = isProcedure ? 'PROCEDURE' : 'FUNCTION';
|
|
558
|
+
let args = '';
|
|
559
|
+
if (fn.argumentTypes && fn.argumentTypes.length > 0) {
|
|
560
|
+
args = fn.argumentTypes.map((a, i) => `$${i + 1} ${a}`).join(', ');
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
let sql = `CREATE OR REPLACE ${keyword} ${ident(fn.schema)}.${ident(fn.name)}(${args})`;
|
|
564
|
+
|
|
565
|
+
if (!isProcedure && fn.returnType) {
|
|
566
|
+
sql += ` RETURNS ${fn.returnType}`;
|
|
567
|
+
} else if (!isProcedure) {
|
|
568
|
+
sql += ' RETURNS void';
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
sql += ` LANGUAGE ${fn.language || 'sql'}`;
|
|
572
|
+
|
|
573
|
+
if (fn.volatility === 'IMMUTABLE' || fn.volatility === 'STABLE') {
|
|
574
|
+
sql += ` ${fn.volatility}`;
|
|
575
|
+
}
|
|
576
|
+
if (fn.isNullCall === true) sql += ' CALLED ON NULL INPUT';
|
|
577
|
+
if (fn.isNullCall === false) sql += ' RETURNS NULL ON NULL INPUT';
|
|
578
|
+
if (fn.securityType === 'DEFINER') sql += ' SECURITY DEFINER';
|
|
579
|
+
if (fn.cost) sql += ` COST ${fn.cost}`;
|
|
580
|
+
if (fn.parallelSafety) sql += ` PARALLEL ${fn.parallelSafety}`;
|
|
581
|
+
if (fn.rowsEstimate) sql += ` ROWS ${fn.rowsEstimate}`;
|
|
582
|
+
|
|
583
|
+
sql += ` AS $$${fn.source || ''}$$;`;
|
|
584
|
+
|
|
585
|
+
return sql;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
function generateCreateTriggerSql(trig) {
|
|
589
|
+
let sql = `CREATE TRIGGER ${ident(trig.name)}\n `;
|
|
590
|
+
|
|
591
|
+
sql += trig.timing;
|
|
592
|
+
if (Array.isArray(trig.events)) {
|
|
593
|
+
sql += ` ${trig.events.join(' OR ')}`;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
const tName = trig.tableName || trig.table || '';
|
|
597
|
+
let tableRef = '';
|
|
598
|
+
if (tName.includes('.')) {
|
|
599
|
+
tableRef = tName.split('.').map(ident).join('.');
|
|
600
|
+
} else {
|
|
601
|
+
tableRef = `${ident(trig.schema || 'public')}.${ident(tName)}`;
|
|
602
|
+
}
|
|
603
|
+
sql += ` ON ${tableRef}`;
|
|
604
|
+
|
|
605
|
+
if (trig.isConstraint) {
|
|
606
|
+
sql += ' FOR EACH ROW';
|
|
607
|
+
if (trig.constraint) sql += ` FROM ${trig.constraint}`;
|
|
608
|
+
if (trig.isDeferrable) {
|
|
609
|
+
sql += ` DEFERRABLE INITIALLY ${trig.deferred ? 'DEFERRED' : 'IMMEDIATE'}`;
|
|
610
|
+
}
|
|
611
|
+
} else {
|
|
612
|
+
sql += ` FOR EACH ${trig.isForEachRow ? 'ROW' : 'STATEMENT'}`;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
if (trig.condition) sql += ` WHEN (${trig.condition})`;
|
|
616
|
+
|
|
617
|
+
const funcName = trig.function || '';
|
|
618
|
+
let funcRef = '';
|
|
619
|
+
if (funcName.includes('.')) {
|
|
620
|
+
funcRef = funcName.split('.').map(ident).join('.');
|
|
621
|
+
} else {
|
|
622
|
+
funcRef = `${ident(trig.schema || 'public')}.${ident(funcName)}`;
|
|
623
|
+
}
|
|
624
|
+
sql += ` EXECUTE FUNCTION ${funcRef}`;
|
|
625
|
+
|
|
626
|
+
if (trig.functionArguments) {
|
|
627
|
+
sql += `(${trig.functionArguments})`;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
sql += ';';
|
|
631
|
+
|
|
632
|
+
return sql;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
function generateCreateIndexSql(idx) {
|
|
636
|
+
let sql = '';
|
|
637
|
+
if (idx.definition) {
|
|
638
|
+
sql = idx.definition;
|
|
639
|
+
if (!sql.endsWith(';')) sql += ';';
|
|
640
|
+
} else {
|
|
641
|
+
sql = 'CREATE ';
|
|
642
|
+
if (idx.isUnique) sql += 'UNIQUE ';
|
|
643
|
+
|
|
644
|
+
sql += 'INDEX';
|
|
645
|
+
if (idx.isConcurrent) sql += ' CONCURRENTLY';
|
|
646
|
+
|
|
647
|
+
sql += ` ${ident(idx.name)} ON ${ident(idx.schema)}.${ident(idx.table)}`;
|
|
648
|
+
|
|
649
|
+
if (idx.accessMethod && idx.accessMethod !== 'btree') {
|
|
650
|
+
sql += ` USING ${idx.accessMethod}`;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
const cols = (idx.columns || []).map(c => {
|
|
654
|
+
let col = c.expression || ident(c.name);
|
|
655
|
+
if (c.collation) col += ` COLLATE ${c.collation}`;
|
|
656
|
+
if (c.opclass) col += ` ${c.opclass}`;
|
|
657
|
+
if (c.isAscending === false) col += ' DESC';
|
|
658
|
+
if (c.isNullsFirst) col += ' NULLS FIRST';
|
|
659
|
+
if (c.isNullsLast) col += ' NULLS LAST';
|
|
660
|
+
return col;
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
sql += ` (${cols.join(', ')})`;
|
|
664
|
+
|
|
665
|
+
const includeCols = idx.include || idx.includeColumns;
|
|
666
|
+
if (includeCols && includeCols.length > 0) {
|
|
667
|
+
sql += ` INCLUDE (${includeCols.map(ident).join(', ')})`;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
if (idx.where || idx.whereClause) {
|
|
671
|
+
sql += ` WHERE ${idx.where || idx.whereClause}`;
|
|
672
|
+
}
|
|
673
|
+
if (idx.tablespace) sql += ` TABLESPACE ${ident(idx.tablespace)}`;
|
|
674
|
+
if (idx.fillfactor) sql += ` WITH (fillfactor=${idx.fillfactor})`;
|
|
675
|
+
|
|
676
|
+
sql += ';';
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
const idxKey = `${ident(idx.schema)}.${ident(idx.name)}`;
|
|
680
|
+
if (idx.owner) {
|
|
681
|
+
sql += `\nALTER INDEX ${idxKey} OWNER TO ${ident(idx.owner)};`;
|
|
682
|
+
}
|
|
683
|
+
if (idx.comment) {
|
|
684
|
+
sql += `\nCOMMENT ON INDEX ${idxKey} IS '${escapeString(idx.comment)}';`;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
return sql;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
function generateCreateConstraintSql(con) {
|
|
691
|
+
const table = con.tableKey || `${ident(con.schema)}.${ident(con.table)}`;
|
|
692
|
+
const constraintType = con.constraintType || con.type;
|
|
693
|
+
const isDeferred = con.initiallyDeferred || con.deferred || con.isDeferred;
|
|
694
|
+
const deferClause = con.deferrable ? (isDeferred ? ' DEFERRABLE INITIALLY DEFERRED' : ' DEFERRABLE INITIALLY IMMEDIATE') : '';
|
|
695
|
+
const enforceClause = con.enforced === false ? ' NOT ENFORCED' : '';
|
|
696
|
+
|
|
697
|
+
let sql = '';
|
|
698
|
+
switch (constraintType) {
|
|
699
|
+
case 'PRIMARY KEY':
|
|
700
|
+
case 'PRIMARY_KEY':
|
|
701
|
+
sql = `ALTER TABLE ${table} ADD CONSTRAINT ${ident(con.name)} PRIMARY KEY (${(con.columns || []).map(ident).join(', ')})${deferClause}${enforceClause};`;
|
|
702
|
+
break;
|
|
703
|
+
|
|
704
|
+
case 'FOREIGN KEY':
|
|
705
|
+
case 'FOREIGN_KEY': {
|
|
706
|
+
sql = `ALTER TABLE ${table} ADD CONSTRAINT ${ident(con.name)} FOREIGN KEY (${(con.columns || []).map(ident).join(', ')}) REFERENCES ${ident(con.referencedTable)} (${(con.referencedColumns || []).map(ident).join(', ')})`;
|
|
707
|
+
if (con.onDelete) sql += ` ON DELETE ${con.onDelete}`;
|
|
708
|
+
if (con.onUpdate) sql += ` ON UPDATE ${con.onUpdate}`;
|
|
709
|
+
sql += `${deferClause}${enforceClause};`;
|
|
710
|
+
break;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
case 'UNIQUE':
|
|
714
|
+
sql = `ALTER TABLE ${table} ADD CONSTRAINT ${ident(con.name)} UNIQUE (${(con.columns || []).map(ident).join(', ')})${deferClause}${enforceClause};`;
|
|
715
|
+
break;
|
|
716
|
+
|
|
717
|
+
case 'CHECK':
|
|
718
|
+
sql = `ALTER TABLE ${table} ADD CONSTRAINT ${ident(con.name)} CHECK (${con.definition})${enforceClause};`;
|
|
719
|
+
break;
|
|
720
|
+
|
|
721
|
+
case 'EXCLUSION':
|
|
722
|
+
sql = `ALTER TABLE ${table} ADD CONSTRAINT ${ident(con.name)} EXCLUDE ${con.accessMethod || 'GIST'} (${con.exclusions?.map(e => `${e.expression} WITH ${e.operator}`).join(', ') || ''})${con.where ? ` WHERE ${con.where}` : ''}${deferClause};`;
|
|
723
|
+
break;
|
|
724
|
+
|
|
725
|
+
default:
|
|
726
|
+
return `-- Unsupported constraint type: ${constraintType}`;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
if (con.comment) {
|
|
730
|
+
sql += `\nCOMMENT ON CONSTRAINT ${ident(con.name)} ON ${table} IS '${escapeString(con.comment)}';`;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
return sql;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
function generateCreateSequenceSql(seq) {
|
|
737
|
+
let sql = `CREATE SEQUENCE ${ident(seq.schema)}.${ident(seq.name)}`;
|
|
738
|
+
|
|
739
|
+
const increment = seq.increment;
|
|
740
|
+
const minValue = seq.minimumValue ?? seq.minValue;
|
|
741
|
+
const maxValue = seq.maximumValue ?? seq.maxValue;
|
|
742
|
+
const startValue = seq.startValue;
|
|
743
|
+
const cache = seq.cacheSize ?? seq.cache;
|
|
744
|
+
const cycle = seq.isCycled ?? seq.cycle;
|
|
745
|
+
|
|
746
|
+
if (increment) sql += ` INCREMENT ${increment}`;
|
|
747
|
+
if (minValue !== undefined) sql += ` MINVALUE ${minValue}`;
|
|
748
|
+
if (maxValue !== undefined) sql += ` MAXVALUE ${maxValue}`;
|
|
749
|
+
if (startValue !== undefined) sql += ` START ${startValue}`;
|
|
750
|
+
if (cache !== undefined) sql += ` CACHE ${cache}`;
|
|
751
|
+
if (cycle) sql += ' CYCLE';
|
|
752
|
+
|
|
753
|
+
const ownedBy = seq.ownedBy || (seq.ownerTable && seq.ownerColumn ? `${seq.ownerTable}.${seq.ownerColumn}` : undefined);
|
|
754
|
+
if (ownedBy && ownedBy !== 'NONE') {
|
|
755
|
+
const ownedParts = ownedBy.split('.');
|
|
756
|
+
const formattedOwnedBy = ownedParts.map(ident).join('.');
|
|
757
|
+
sql += ` OWNED BY ${formattedOwnedBy}`;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
sql += ';';
|
|
761
|
+
|
|
762
|
+
const seqKey = `${ident(seq.schema)}.${ident(seq.name)}`;
|
|
763
|
+
if (seq.owner) {
|
|
764
|
+
sql += `\nALTER SEQUENCE ${seqKey} OWNER TO ${ident(seq.owner)};`;
|
|
765
|
+
}
|
|
766
|
+
if (seq.comment) {
|
|
767
|
+
sql += `\nCOMMENT ON SEQUENCE ${seqKey} IS '${escapeString(seq.comment)}';`;
|
|
768
|
+
}
|
|
769
|
+
if (seq.privileges && seq.privileges.length > 0) {
|
|
770
|
+
for (const g of seq.privileges) {
|
|
771
|
+
let grantSql = `GRANT ${g.privilege} ON SEQUENCE ${seqKey} TO ${ident(g.grantee)}`;
|
|
772
|
+
if (g.isGrantable) grantSql += ' WITH GRANT OPTION';
|
|
773
|
+
sql += `\n${grantSql};`;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
return sql;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
function generateCreateExtensionSql(ext) {
|
|
781
|
+
let sql = `CREATE EXTENSION IF NOT EXISTS ${ident(ext.name)}`;
|
|
782
|
+
if (ext.schema) sql += ` SCHEMA ${ident(ext.schema)}`;
|
|
783
|
+
if (ext.version) sql += ` VERSION ${ext.version}`;
|
|
784
|
+
return sql + ';';
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
function generateCreatePolicySql(pol) {
|
|
788
|
+
let sql = '';
|
|
789
|
+
const isPermissive = pol.isPermissive !== false;
|
|
790
|
+
|
|
791
|
+
sql += `CREATE POLICY ${ident(pol.name)} ON ${ident(pol.schema)}.${ident(pol.table)}`;
|
|
792
|
+
|
|
793
|
+
if (!isPermissive) sql += ' AS RESTRICTIVE';
|
|
794
|
+
|
|
795
|
+
if (pol.command) sql += ` FOR ${pol.command}`;
|
|
796
|
+
|
|
797
|
+
const roles = pol.roles && Array.isArray(pol.roles) ? pol.roles : ['PUBLIC'];
|
|
798
|
+
if (roles.length > 0) {
|
|
799
|
+
sql += ` TO ${roles.map(r => r === 'PUBLIC' ? 'PUBLIC' : ident(r)).join(', ')}`;
|
|
800
|
+
} else {
|
|
801
|
+
sql += ' TO PUBLIC';
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
if (pol.using) sql += ` USING (${pol.using})`;
|
|
805
|
+
if (pol.withCheck) sql += ` WITH CHECK (${pol.withCheck})`;
|
|
806
|
+
|
|
807
|
+
return sql + ';';
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
function generateCreateTypeSql(type) {
|
|
811
|
+
if (type.kind === 'ENUM' || type.enumValues) {
|
|
812
|
+
const values = (type.enumValues || []).map(v => {
|
|
813
|
+
const val = typeof v === 'string' ? v : v.value;
|
|
814
|
+
return `'${val}'`;
|
|
815
|
+
}).join(', ');
|
|
816
|
+
return `CREATE TYPE ${ident(type.schema)}.${ident(type.name)} AS ENUM (${values});`;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
if (type.kind === 'COMPOSITE') {
|
|
820
|
+
if (!type.attributes) return `-- Composite type ${type.name} has no attributes`;
|
|
821
|
+
return `CREATE TYPE ${ident(type.schema)}.${ident(type.name)} AS (${type.attributes.map(a => `${ident(a.name)} ${a.dataType}`).join(', ')});`;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
if (type.kind === 'RANGE') {
|
|
825
|
+
return `CREATE TYPE ${ident(type.schema)}.${ident(type.name)} AS RANGE (SUBTYPE = ${type.subtype}${type.subtypeOpclass ? `, SUBTYPE_OPCLASS = ${type.subtypeOpclass}` : ''}${type.canonicalFunction ? `, CANONICAL = ${type.canonicalFunction}` : ''}${type.subtypeDiff ? `, SUBTYPE_DIFF = ${type.subtypeDiff}` : ''});`;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
return `-- Unsupported type creation: ${type.kind}`;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
function generateCreateDomainSql(domain) {
|
|
832
|
+
let sql = `CREATE DOMAIN ${ident(domain.schema)}.${ident(domain.name)} AS ${domain.baseType}`;
|
|
833
|
+
if (domain.default) sql += ` DEFAULT ${domain.default}`;
|
|
834
|
+
if (domain.notNull) sql += ' NOT NULL';
|
|
835
|
+
if (domain.check) sql += ` CHECK (${domain.check})`;
|
|
836
|
+
return sql + ';';
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
function generateCreateRuleSql(rule) {
|
|
840
|
+
let sql = `CREATE RULE ${ident(rule.name)} AS`;
|
|
841
|
+
const isInstead = rule.isInstead !== undefined ? rule.isInstead : rule.instead;
|
|
842
|
+
if (isInstead) sql += ' ON INSTEAD';
|
|
843
|
+
else sql += ' ON';
|
|
844
|
+
|
|
845
|
+
if (rule.events) {
|
|
846
|
+
sql += ` ${rule.events.join(' OR ')}`;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
const tName = rule.tableName || rule.table || '';
|
|
850
|
+
let tableRef = '';
|
|
851
|
+
if (tName.includes('.')) {
|
|
852
|
+
tableRef = tName.split('.').map(ident).join('.');
|
|
853
|
+
} else {
|
|
854
|
+
tableRef = `${ident(rule.schema || 'public')}.${ident(tName)}`;
|
|
855
|
+
}
|
|
856
|
+
sql += ` TO ${tableRef}`;
|
|
857
|
+
|
|
858
|
+
if (rule.condition) sql += ` WHERE ${rule.condition}`;
|
|
859
|
+
|
|
860
|
+
if (rule.commands) {
|
|
861
|
+
sql += ' DO ' + (rule.commands.length > 1 ? `(${rule.commands.join('; ')})` : rule.commands[0]);
|
|
862
|
+
} else {
|
|
863
|
+
sql += ' DO INSTEAD NOTHING';
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
return sql + ';';
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
function generateCreateAggregateSql(agg) {
|
|
870
|
+
const args = (agg.argumentTypes || []).join(', ');
|
|
871
|
+
let sql = `CREATE AGGREGATE ${ident(agg.schema)}.${ident(agg.name)}(${args}) (`;
|
|
872
|
+
const parts = [];
|
|
873
|
+
if (agg.sfunc) parts.push(`SFUNC = ${agg.sfunc}`);
|
|
874
|
+
if (agg.stype) parts.push(`STYPE = ${agg.stype}`);
|
|
875
|
+
if (agg.finalfunc) parts.push(`FINALFUNC = ${agg.finalfunc}`);
|
|
876
|
+
if (agg.combinefunc) parts.push(`COMBINEFUNC = ${agg.combinefunc}`);
|
|
877
|
+
if (agg.serialfunc) parts.push(`SERIALFUNC = ${agg.serialfunc}`);
|
|
878
|
+
if (agg.deserializefunc) parts.push(`DESERIALFUNC = ${agg.deserializefunc}`);
|
|
879
|
+
if (agg.initcond) parts.push(`INITCOND = '${agg.initcond}'`);
|
|
880
|
+
if (agg.mtransType) parts.push(`STYPE = ${agg.mtransType}`);
|
|
881
|
+
if (agg.msfunc) parts.push(`MSTYPE = ${agg.msfunc}`);
|
|
882
|
+
if (agg.mfinalfunc) parts.push(`MSTYPE = ${agg.mfinalfunc}`);
|
|
883
|
+
if (agg.sortOp) parts.push(`SORTOP = ${agg.sortOp}`);
|
|
884
|
+
sql += parts.join(', ') + ');';
|
|
885
|
+
return sql;
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
function generateCreateTextSearchParserSql(parser) {
|
|
889
|
+
let sql = `CREATE TEXT SEARCH PARSER ${ident(parser.schema)}.${ident(parser.name)} (`;
|
|
890
|
+
const parts = [];
|
|
891
|
+
if (parser.start) parts.push(`START = ${parser.start}`);
|
|
892
|
+
if (parser.getToken) parts.push(`GETTOKEN = ${parser.getToken}`);
|
|
893
|
+
if (parser.end) parts.push(`END = ${parser.end}`);
|
|
894
|
+
if (parser.lextypes) parts.push(`LEXTYPES = ${parser.lextypes}`);
|
|
895
|
+
if (parser.headline) parts.push(`HEADLINE = ${parser.headline}`);
|
|
896
|
+
sql += parts.join(', ') + ');';
|
|
897
|
+
return sql;
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
function generateCreateTextSearchTemplateSql(template) {
|
|
901
|
+
let sql = `CREATE TEXT SEARCH TEMPLATE ${ident(template.schema)}.${ident(template.name)} (`;
|
|
902
|
+
const parts = [];
|
|
903
|
+
if (template.lexize) parts.push(`LEXIZE = ${template.lexize}`);
|
|
904
|
+
if (template.init) parts.push(`INIT = ${template.init}`);
|
|
905
|
+
sql += parts.join(', ') + ');';
|
|
906
|
+
return sql;
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
function generateCreateTextSearchDictSql(dict) {
|
|
910
|
+
let sql = `CREATE TEXT SEARCH DICTIONARY ${ident(dict.schema)}.${ident(dict.name)} (`;
|
|
911
|
+
sql += `TEMPLATE = ${dict.template}`;
|
|
912
|
+
if (dict.options && Object.keys(dict.options).length > 0) {
|
|
913
|
+
sql += ', ' + Object.entries(dict.options).map(([k, v]) => `${k} = '${v}'`).join(', ');
|
|
914
|
+
}
|
|
915
|
+
sql += ');';
|
|
916
|
+
return sql;
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
function generateCreateTextSearchConfigSql(config) {
|
|
920
|
+
let sql = `CREATE TEXT SEARCH CONFIGURATION ${ident(config.schema)}.${ident(config.name)} (`;
|
|
921
|
+
sql += `PARSER = ${config.parser || 'default'}`;
|
|
922
|
+
sql += ');';
|
|
923
|
+
|
|
924
|
+
if (config.tokenMappings && config.tokenMappings.length > 0) {
|
|
925
|
+
for (const mapping of config.tokenMappings) {
|
|
926
|
+
sql += `\nALTER TEXT SEARCH CONFIGURATION ${ident(config.schema)}.${ident(config.name)} ADD MAPPING FOR ${mapping.tokens.join(', ')} WITH ${mapping.dictionaries.join(', ')};`;
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
return sql;
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
function generateCreateOperatorSql(op) {
|
|
934
|
+
let sql = `CREATE OPERATOR ${ident(op.schema)}.${ident(op.name)} (`;
|
|
935
|
+
const parts = [];
|
|
936
|
+
const left = op.leftarg || op.leftType;
|
|
937
|
+
const right = op.rightarg || op.rightType;
|
|
938
|
+
const proc = op.procedure || op.proc;
|
|
939
|
+
const restrict = op.restrict || op.restrictFunction;
|
|
940
|
+
const join = op.join || op.joinFunction;
|
|
941
|
+
const hashes = op.hashes !== undefined ? op.hashes : op.canHash;
|
|
942
|
+
const merges = op.merges !== undefined ? op.merges : op.canMerge;
|
|
943
|
+
|
|
944
|
+
if (left) parts.push(`LEFTARG = ${left}`);
|
|
945
|
+
if (right) parts.push(`RIGHTARG = ${right}`);
|
|
946
|
+
if (proc) parts.push(`PROCEDURE = ${proc}`);
|
|
947
|
+
if (op.commutator) parts.push(`COMMUTATOR = ${op.commutator}`);
|
|
948
|
+
if (op.negator) parts.push(`NEGATOR = ${op.negator}`);
|
|
949
|
+
if (restrict) parts.push(`RESTRICT = ${restrict}`);
|
|
950
|
+
if (join) parts.push(`JOIN = ${join}`);
|
|
951
|
+
if (hashes) parts.push('HASHES');
|
|
952
|
+
if (merges) parts.push('MERGES');
|
|
953
|
+
sql += parts.join(', ') + ');';
|
|
954
|
+
return sql;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
function generateCreateOperatorClassSql(opClass) {
|
|
958
|
+
let sql = `CREATE OPERATOR CLASS ${ident(opClass.schema)}.${ident(opClass.name)}`;
|
|
959
|
+
if (opClass.isDefault) sql += ' DEFAULT';
|
|
960
|
+
const forType = opClass.inputType || opClass.forType;
|
|
961
|
+
sql += ` FOR TYPE ${forType} USING ${opClass.accessMethod || 'btree'}`;
|
|
962
|
+
if (opClass.family) sql += ` FAMILY ${opClass.family}`;
|
|
963
|
+
sql += ' AS ';
|
|
964
|
+
|
|
965
|
+
const items = [];
|
|
966
|
+
if (opClass.operators) {
|
|
967
|
+
for (const op of opClass.operators) {
|
|
968
|
+
items.push(`OPERATOR ${op.number} ${op.name}${op.recheck ? ' RECHECK' : ''}`);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
if (opClass.functions) {
|
|
972
|
+
for (const fn of opClass.functions) {
|
|
973
|
+
items.push(`FUNCTION ${fn.number} ${fn.name}`);
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
if (opClass.storages) {
|
|
977
|
+
for (const st of opClass.storages) {
|
|
978
|
+
items.push(`STORAGE ${st.type}`);
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
sql += items.join(', ') + ';';
|
|
983
|
+
return sql;
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
function generateCreateOperatorFamilySql(opFamily) {
|
|
987
|
+
return `CREATE OPERATOR FAMILY ${ident(opFamily.schema)}.${ident(opFamily.name)} USING ${opFamily.accessMethod || 'btree'};`;
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
function generateCreatePublicationSql(pub) {
|
|
991
|
+
let sql = `CREATE PUBLICATION ${ident(pub.name)}`;
|
|
992
|
+
|
|
993
|
+
if (pub.tables && pub.tables.length > 0) {
|
|
994
|
+
sql += ' FOR TABLE ' + pub.tables.map(t => {
|
|
995
|
+
let tableRef = t.schema ? `${ident(t.schema)}.${ident(t.name)}` : ident(t.name);
|
|
996
|
+
if (t.columns) tableRef += ` (${t.columns.map(c => ident(c)).join(', ')})`;
|
|
997
|
+
return tableRef;
|
|
998
|
+
}).join(', ');
|
|
999
|
+
} else if (pub.forAllTables) {
|
|
1000
|
+
sql += ' FOR ALL TABLES';
|
|
1001
|
+
} else {
|
|
1002
|
+
sql += ' FOR ALL TABLES';
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
const withOpts = [];
|
|
1006
|
+
if (pub.publishInsert !== false) withOpts.push('publish = \'insert\'');
|
|
1007
|
+
if (pub.publishUpdate !== false) withOpts.push('publish = \'update\'');
|
|
1008
|
+
if (pub.publishDelete !== false) withOpts.push('publish = \'delete\'');
|
|
1009
|
+
if (pub.publishTruncate !== false) withOpts.push('publish = \'truncate\'');
|
|
1010
|
+
|
|
1011
|
+
if (withOpts.length > 0) {
|
|
1012
|
+
sql += ' WITH (' + withOpts.join(', ') + ')';
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
return sql + ';';
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
function generateCreateSubscriptionSql(sub) {
|
|
1019
|
+
let sql = `CREATE SUBSCRIPTION ${ident(sub.name)}`;
|
|
1020
|
+
const conn = sub.conninfo || sub.connectionString || sub.connection;
|
|
1021
|
+
sql += ` CONNECTION '${conn}'`;
|
|
1022
|
+
sql += ` PUBLICATION ${Array.isArray(sub.publications) ? sub.publications.join(', ') : sub.publications}`;
|
|
1023
|
+
|
|
1024
|
+
const withOpts = [];
|
|
1025
|
+
if (sub.enabled !== false) withOpts.push('enabled = true');
|
|
1026
|
+
if (sub.createSlot !== false) withOpts.push('create_slot = true');
|
|
1027
|
+
if (sub.slotName) withOpts.push(`slot_name = '${sub.slotName}'`);
|
|
1028
|
+
|
|
1029
|
+
const sync = sub.syncCommit || sub.synchronousCommit;
|
|
1030
|
+
if (sync !== undefined) {
|
|
1031
|
+
withOpts.push(`synchronous_commit = ${typeof sync === 'boolean' ? (sync ? "'on'" : "'off'") : `'${sync}'`}`);
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
const binary = sub.binaryTransfer || sub.binary;
|
|
1035
|
+
if (binary !== undefined) {
|
|
1036
|
+
withOpts.push(`binary = ${binary ? 'true' : 'false'}`);
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
const streaming = sub.streaming;
|
|
1040
|
+
if (streaming !== undefined) {
|
|
1041
|
+
withOpts.push(`streaming = ${typeof streaming === 'boolean' ? (streaming ? "'on'" : "'off'") : `'${streaming}'`}`);
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
if (sub.twoPhase !== undefined) {
|
|
1045
|
+
withOpts.push(`two_phase = ${sub.twoPhase ? 'true' : 'false'}`);
|
|
1046
|
+
}
|
|
1047
|
+
if (sub.disableOnError !== undefined) {
|
|
1048
|
+
withOpts.push(`disable_on_error = ${sub.disableOnError ? 'true' : 'false'}`);
|
|
1049
|
+
}
|
|
1050
|
+
if (sub.origin !== undefined) {
|
|
1051
|
+
withOpts.push(`origin = ${typeof sub.origin === 'boolean' ? (sub.origin ? "'any'" : "'none'") : `'${sub.origin}'`}`);
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
if (withOpts.length > 0) {
|
|
1055
|
+
sql += ' WITH (' + withOpts.join(', ') + ')';
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
return sql + ';';
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
function generateCreateStatisticsSql(stat) {
|
|
1062
|
+
if (stat.definition) {
|
|
1063
|
+
let sql = stat.definition;
|
|
1064
|
+
if (!sql.endsWith(';')) sql += ';';
|
|
1065
|
+
return sql;
|
|
1066
|
+
}
|
|
1067
|
+
let sql = `CREATE STATISTICS ${ident(stat.schema)}.${ident(stat.name)}`;
|
|
1068
|
+
if (stat.kinds && stat.kinds.length > 0) {
|
|
1069
|
+
sql += ` (${stat.kinds.join(', ')})`;
|
|
1070
|
+
}
|
|
1071
|
+
sql += ` ON ${stat.columns.map(c => ident(c)).join(', ')}`;
|
|
1072
|
+
const formattedTable = stat.table.includes('.') ? stat.table.split('.').map(ident).join('.') : ident(stat.table);
|
|
1073
|
+
sql += ` FROM ${formattedTable}`;
|
|
1074
|
+
return sql + ';';
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
function generateCreateCollationSql(coll) {
|
|
1078
|
+
let sql = `CREATE COLLATION ${ident(coll.schema)}.${ident(coll.name)}`;
|
|
1079
|
+
|
|
1080
|
+
if (coll.fromCollation) {
|
|
1081
|
+
sql += ` FROM ${coll.fromCollation}`;
|
|
1082
|
+
} else {
|
|
1083
|
+
const params = [];
|
|
1084
|
+
if (coll.locale) params.push(`locale = '${coll.locale}'`);
|
|
1085
|
+
if (coll.lcCollate) params.push(`lc_collate = '${coll.lcCollate}'`);
|
|
1086
|
+
if (coll.lcCtype) params.push(`lc_ctype = '${coll.lcCtype}'`);
|
|
1087
|
+
if (coll.provider) params.push(`provider = ${coll.provider}`);
|
|
1088
|
+
|
|
1089
|
+
const det = coll.isDeterministic !== undefined ? coll.isDeterministic : coll.deterministic;
|
|
1090
|
+
if (det !== undefined) params.push(`deterministic = ${det}`);
|
|
1091
|
+
|
|
1092
|
+
if (coll.version) params.push(`version = '${coll.version}'`);
|
|
1093
|
+
if (coll.encoding) params.push(`encoding = '${coll.encoding}'`);
|
|
1094
|
+
|
|
1095
|
+
if (params.length > 0) {
|
|
1096
|
+
sql += ' (' + params.join(', ') + ')';
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
return sql + ';';
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
function generateCreateCastSql(cast) {
|
|
1104
|
+
let sql = `CREATE CAST (${cast.sourceType} AS ${cast.targetType})`;
|
|
1105
|
+
|
|
1106
|
+
if (cast.function) {
|
|
1107
|
+
sql += ` WITH FUNCTION ${cast.function}`;
|
|
1108
|
+
} else if (cast.inout) {
|
|
1109
|
+
sql += ' WITH INOUT';
|
|
1110
|
+
} else {
|
|
1111
|
+
sql += ' WITHOUT FUNCTION';
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
if (cast.context === 'IMPLICIT') {
|
|
1115
|
+
sql += ' AS IMPLICIT';
|
|
1116
|
+
} else if (cast.context === 'ASSIGNMENT') {
|
|
1117
|
+
sql += ' AS ASSIGNMENT';
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
return sql + ';';
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
function generateCreateForeignServerSql(server) {
|
|
1124
|
+
let sql = `CREATE SERVER ${ident(server.name)}`;
|
|
1125
|
+
if (server.type) sql += ` TYPE '${server.type}'`;
|
|
1126
|
+
if (server.version) sql += ` VERSION '${server.version}'`;
|
|
1127
|
+
sql += ` FOREIGN DATA WRAPPER ${server.foreignDataWrapper}`;
|
|
1128
|
+
|
|
1129
|
+
if (server.options && Object.keys(server.options).length > 0) {
|
|
1130
|
+
sql += ' OPTIONS (' + Object.entries(server.options).map(([k, v]) => `${k} '${v}'`).join(', ') + ')';
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
return sql + ';';
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
function generateCreateForeignDataWrapperSql(fdw) {
|
|
1137
|
+
let sql = `CREATE FOREIGN DATA WRAPPER ${ident(fdw.name)}`;
|
|
1138
|
+
|
|
1139
|
+
if (fdw.handler) {
|
|
1140
|
+
sql += ` HANDLER ${fdw.handler}`;
|
|
1141
|
+
}
|
|
1142
|
+
if (fdw.validator) {
|
|
1143
|
+
sql += ` VALIDATOR ${fdw.validator}`;
|
|
1144
|
+
}
|
|
1145
|
+
if (fdw.options && Object.keys(fdw.options).length > 0) {
|
|
1146
|
+
sql += ' OPTIONS (' + Object.entries(fdw.options).map(([k, v]) => `${k} '${v}'`).join(', ') + ')';
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
return sql + ';';
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
function generateCreateUserMappingSql(mapping) {
|
|
1153
|
+
let sql = `CREATE USER MAPPING FOR ${mapping.user === 'PUBLIC' ? 'PUBLIC' : ident(mapping.user)}`;
|
|
1154
|
+
sql += ` SERVER ${ident(mapping.server)}`;
|
|
1155
|
+
|
|
1156
|
+
if (mapping.options && Object.keys(mapping.options).length > 0) {
|
|
1157
|
+
sql += ' OPTIONS (' + Object.entries(mapping.options).map(([k, v]) => {
|
|
1158
|
+
if (k === 'password') return `${k} '[REDACTED]'`;
|
|
1159
|
+
return `${k} '${v}'`;
|
|
1160
|
+
}).join(', ') + ')';
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
return sql + ';';
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
function generateCreateForeignTableSql(ft) {
|
|
1167
|
+
let sql = `CREATE FOREIGN TABLE ${ident(ft.schema)}.${ident(ft.name)} (`;
|
|
1168
|
+
sql += (ft.columns || []).map(c => {
|
|
1169
|
+
let col = `${ident(c.name)} ${c.dataType}`;
|
|
1170
|
+
if (c.collation) col += ` COLLATE ${c.collation}`;
|
|
1171
|
+
return col;
|
|
1172
|
+
}).join(', ');
|
|
1173
|
+
sql += `) SERVER ${ident(ft.server)}`;
|
|
1174
|
+
|
|
1175
|
+
if (ft.options && Object.keys(ft.options).length > 0) {
|
|
1176
|
+
sql += ' OPTIONS (' + Object.entries(ft.options).map(([k, v]) => `${k} '${v}'`).join(', ') + ')';
|
|
1177
|
+
}
|
|
1178
|
+
sql += ';';
|
|
1179
|
+
|
|
1180
|
+
const key = `${ident(ft.schema)}.${ident(ft.name)}`;
|
|
1181
|
+
if (ft.rlsEnabled) {
|
|
1182
|
+
sql += `\nALTER FOREIGN TABLE ${key} ENABLE ROW LEVEL SECURITY;`;
|
|
1183
|
+
}
|
|
1184
|
+
if (ft.rlsForced) {
|
|
1185
|
+
sql += `\nALTER FOREIGN TABLE ${key} FORCE ROW LEVEL SECURITY;`;
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
return sql;
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
function generateCreateEventTriggerSql(et) {
|
|
1192
|
+
let sql = `CREATE EVENT TRIGGER ${ident(et.name)} ON ${et.event}`;
|
|
1193
|
+
|
|
1194
|
+
if (et.tags && et.tags.length > 0) {
|
|
1195
|
+
sql += ` WHEN TAG IN (${et.tags.map(t => `'${t}'`).join(', ')})`;
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
sql += ` EXECUTE FUNCTION ${et.function}`;
|
|
1199
|
+
if (et.functionArguments) {
|
|
1200
|
+
sql += `(${et.functionArguments})`;
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
return sql + ';';
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
function generateCreateRoleSql(role) {
|
|
1207
|
+
let sql = `CREATE ROLE ${ident(role.name)}`;
|
|
1208
|
+
const opts = [];
|
|
1209
|
+
if (role.superuser) opts.push('SUPERUSER');
|
|
1210
|
+
if (role.createdb) opts.push('CREATEDB');
|
|
1211
|
+
if (role.createrole) opts.push('CREATEROLE');
|
|
1212
|
+
if (role.inherit !== false) opts.push('INHERIT');
|
|
1213
|
+
if (role.login) opts.push('LOGIN');
|
|
1214
|
+
if (role.replication) opts.push('REPLICATION');
|
|
1215
|
+
if (role.connectionLimit !== undefined) opts.push(`CONNECTION LIMIT ${role.connectionLimit}`);
|
|
1216
|
+
if (role.password) opts.push(`PASSWORD '[REDACTED]'`);
|
|
1217
|
+
if (role.validUntil) opts.push(`VALID UNTIL '${role.validUntil}'`);
|
|
1218
|
+
|
|
1219
|
+
if (opts.length > 0) {
|
|
1220
|
+
sql += ' ' + opts.join(' ');
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
return sql + ';';
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
function generateCreateDatabaseSql(db) {
|
|
1227
|
+
let sql = `CREATE DATABASE ${ident(db.name)}`;
|
|
1228
|
+
if (db.owner) sql += ` OWNER ${ident(db.owner)}`;
|
|
1229
|
+
if (db.template) sql += ` TEMPLATE ${ident(db.template)}`;
|
|
1230
|
+
if (db.encoding) sql += ` ENCODING '${db.encoding}'`;
|
|
1231
|
+
if (db.collation) sql += ` COLLATE ${db.collation}`;
|
|
1232
|
+
if (db.tablespace) sql += ` TABLESPACE ${ident(db.tablespace)}`;
|
|
1233
|
+
if (db.connectionLimit !== undefined) sql += ` CONNECTION LIMIT ${db.connectionLimit}`;
|
|
1234
|
+
|
|
1235
|
+
return sql + ';';
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
function generateCreateTablespaceSql(ts) {
|
|
1239
|
+
let sql = `CREATE TABLESPACE ${ident(ts.name)}`;
|
|
1240
|
+
if (ts.owner) sql += ` OWNER ${ident(ts.owner)}`;
|
|
1241
|
+
sql += ` LOCATION '${ts.location}'`;
|
|
1242
|
+
|
|
1243
|
+
if (ts.options && Object.keys(ts.options).length > 0) {
|
|
1244
|
+
sql += ' WITH (' + Object.entries(ts.options).map(([k, v]) => `${k} = ${v}`).join(', ') + ')';
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
return sql + ';';
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
function generateCreateLanguageSql(lang) {
|
|
1251
|
+
let sql = `CREATE LANGUAGE ${ident(lang.name)}`;
|
|
1252
|
+
|
|
1253
|
+
if (lang.isTrusted) {
|
|
1254
|
+
sql += ' TRUSTED';
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
if (lang.handler) {
|
|
1258
|
+
sql += ` HANDLER ${lang.handler}`;
|
|
1259
|
+
}
|
|
1260
|
+
if (lang.inline) {
|
|
1261
|
+
sql += ` INLINE ${lang.inline}`;
|
|
1262
|
+
}
|
|
1263
|
+
if (lang.validator) {
|
|
1264
|
+
sql += ` VALIDATOR ${lang.validator}`;
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
return sql + ';';
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
function parseAclItem(item, objectType) {
|
|
1271
|
+
const parts = item.split('/');
|
|
1272
|
+
const left = parts[0];
|
|
1273
|
+
const eqIdx = left.indexOf('=');
|
|
1274
|
+
let grantee = 'PUBLIC';
|
|
1275
|
+
let privsStr = left;
|
|
1276
|
+
if (eqIdx !== -1) {
|
|
1277
|
+
grantee = left.substring(0, eqIdx) || 'PUBLIC';
|
|
1278
|
+
privsStr = left.substring(eqIdx + 1);
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
const charMap = {
|
|
1282
|
+
r: 'SELECT',
|
|
1283
|
+
w: 'UPDATE',
|
|
1284
|
+
a: 'INSERT',
|
|
1285
|
+
d: 'DELETE',
|
|
1286
|
+
D: 'TRUNCATE',
|
|
1287
|
+
x: 'REFERENCES',
|
|
1288
|
+
t: 'TRIGGER',
|
|
1289
|
+
X: 'EXECUTE',
|
|
1290
|
+
U: 'USAGE',
|
|
1291
|
+
C: 'CREATE',
|
|
1292
|
+
c: 'CONNECT',
|
|
1293
|
+
T: 'TEMPORARY'
|
|
1294
|
+
};
|
|
1295
|
+
|
|
1296
|
+
const privs = [];
|
|
1297
|
+
let isGrantable = false;
|
|
1298
|
+
for (let i = 0; i < privsStr.length; i++) {
|
|
1299
|
+
const c = privsStr[i];
|
|
1300
|
+
if (c === '*') {
|
|
1301
|
+
isGrantable = true;
|
|
1302
|
+
continue;
|
|
1303
|
+
}
|
|
1304
|
+
const mapped = charMap[c];
|
|
1305
|
+
if (mapped) privs.push(mapped);
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
return { grantee, privileges: privs.join(', '), isGrantable };
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
function generateCreateDefaultPrivilegesSql(dp) {
|
|
1312
|
+
let sqlPrefix = 'ALTER DEFAULT PRIVILEGES';
|
|
1313
|
+
|
|
1314
|
+
if (dp.schema && dp.schema !== 'public') {
|
|
1315
|
+
sqlPrefix += ` IN SCHEMA ${ident(dp.schema)}`;
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
const role = dp.forRole || dp.role;
|
|
1319
|
+
if (role) {
|
|
1320
|
+
sqlPrefix += ` FOR ROLE ${ident(role)}`;
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
const objType = (dp.objectType || dp.object_type || 'TABLES').toUpperCase();
|
|
1324
|
+
const acl = dp.acl || [];
|
|
1325
|
+
|
|
1326
|
+
if (acl.length > 0) {
|
|
1327
|
+
const stmts = [];
|
|
1328
|
+
for (const item of acl) {
|
|
1329
|
+
const { grantee, privileges, isGrantable } = parseAclItem(item, objType);
|
|
1330
|
+
let stmt = `${sqlPrefix} GRANT ${privileges || 'ALL'} ON ${objType} TO ${ident(grantee)}`;
|
|
1331
|
+
if (isGrantable) stmt += ' WITH GRANT OPTION';
|
|
1332
|
+
stmts.push(stmt + ';');
|
|
1333
|
+
}
|
|
1334
|
+
return stmts.join('\n');
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
const grantees = dp.grantees || dp.grantee || 'PUBLIC';
|
|
1338
|
+
const privs = dp.privileges || dp.privilege || 'ALL';
|
|
1339
|
+
let stmt = `${sqlPrefix} GRANT ${privs} ON ${objType} TO ${Array.isArray(grantees) ? grantees.map(g => ident(g)).join(', ') : ident(grantees)}`;
|
|
1340
|
+
if (dp.withGrantOption) stmt += ' WITH GRANT OPTION';
|
|
1341
|
+
return stmt + ';';
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
function generateCreateConversionSql(conv) {
|
|
1345
|
+
// PostgreSQL: CREATE [ DEFAULT ] CONVERSION name FOR 'src' TO 'dest' FROM func
|
|
1346
|
+
const defaultKw = conv.isDefault ? 'DEFAULT ' : '';
|
|
1347
|
+
const target = conv.targetEncoding || conv.destEncoding;
|
|
1348
|
+
const proc = conv.proc || conv.function;
|
|
1349
|
+
let sql = `CREATE ${defaultKw}CONVERSION ${ident(conv.schema || 'public')}.${ident(conv.name)}`;
|
|
1350
|
+
sql += ` FOR '${conv.sourceEncoding}' TO '${target}' FROM ${proc}`;
|
|
1351
|
+
return sql + ';';
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
function generateCreateAccessMethodSql(am) {
|
|
1355
|
+
let sql = `CREATE ACCESS METHOD ${ident(am.name)}`;
|
|
1356
|
+
sql += ` TYPE ${am.type.toUpperCase()} HANDLER ${am.handler}`;
|
|
1357
|
+
return sql + ';';
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
/**
|
|
1361
|
+
* CREATE MATERIALIZED VIEW — PostgreSQL clause order:
|
|
1362
|
+
* [ (columns) ] [ WITH (storage) ] [ TABLESPACE ts ] AS query [ WITH [ NO ] DATA ]
|
|
1363
|
+
* Accepts both property aliases used across the codebase
|
|
1364
|
+
* (withData/isWithData, storageParameters/storageOptions/storageSettings).
|
|
1365
|
+
*/
|
|
1366
|
+
function generateCreateMaterializedViewSql(mv) {
|
|
1367
|
+
let sql = `CREATE MATERIALIZED VIEW ${ident(mv.schema || 'public')}.${ident(mv.name)}`;
|
|
1368
|
+
|
|
1369
|
+
if (mv.columns && mv.columns.length > 0) {
|
|
1370
|
+
sql += ` (${mv.columns.map(c => ident(typeof c === 'string' ? c : c.name)).join(', ')})`;
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
const storage = mv.storageParameters || mv.storageOptions || mv.storageSettings;
|
|
1374
|
+
if (storage) {
|
|
1375
|
+
if (typeof storage === 'string') {
|
|
1376
|
+
sql += ` WITH (${storage})`;
|
|
1377
|
+
} else if (Object.keys(storage).length > 0) {
|
|
1378
|
+
sql += ' WITH (' + Object.entries(storage).map(([k, v]) => `${k} = ${v}`).join(', ') + ')';
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
if (mv.tablespace) {
|
|
1383
|
+
sql += ` TABLESPACE ${ident(mv.tablespace)}`;
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
sql += ` AS ${mv.definition}`;
|
|
1387
|
+
|
|
1388
|
+
const withData = mv.withData !== undefined ? mv.withData : mv.isWithData;
|
|
1389
|
+
if (withData === false) {
|
|
1390
|
+
sql += ' WITH NO DATA';
|
|
1391
|
+
} else {
|
|
1392
|
+
sql += ' WITH DATA';
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
sql += ';';
|
|
1396
|
+
|
|
1397
|
+
const mvKey = `${ident(mv.schema || 'public')}.${ident(mv.name)}`;
|
|
1398
|
+
if (mv.owner) {
|
|
1399
|
+
sql += `\nALTER MATERIALIZED VIEW ${mvKey} OWNER TO ${ident(mv.owner)};`;
|
|
1400
|
+
}
|
|
1401
|
+
if (mv.comment) {
|
|
1402
|
+
sql += `\nCOMMENT ON MATERIALIZED VIEW ${mvKey} IS '${escapeString(mv.comment)}';`;
|
|
1403
|
+
}
|
|
1404
|
+
if (mv.privileges && mv.privileges.length > 0) {
|
|
1405
|
+
for (const g of mv.privileges) {
|
|
1406
|
+
let grantSql = `GRANT ${g.privilege} ON MATERIALIZED VIEW ${mvKey} TO ${ident(g.grantee)}`;
|
|
1407
|
+
if (g.isGrantable) grantSql += ' WITH GRANT OPTION';
|
|
1408
|
+
sql += `\n${grantSql};`;
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
return sql;
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
function ident(name) {
|
|
1416
|
+
if (!name) return '';
|
|
1417
|
+
if (name.includes('"') || name.includes(' ')) {
|
|
1418
|
+
return `"${name.replace(/"/g, '""')}"`;
|
|
1419
|
+
}
|
|
1420
|
+
return `"${name}"`;
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
function escapeString(str) {
|
|
1424
|
+
if (typeof str !== 'string') str = String(str);
|
|
1425
|
+
return str.replace(/'/g, "''");
|
|
1426
|
+
}
|