@rawsql-ts/ddl-docs-cli 0.2.2
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/AGENTS.md +27 -0
- package/README.md +86 -0
- package/dist/src/analyzer/columnConcepts.d.ts +12 -0
- package/dist/src/analyzer/columnConcepts.js +185 -0
- package/dist/src/analyzer/columnConcepts.js.map +1 -0
- package/dist/src/analyzer/dictionary.d.ts +3 -0
- package/dist/src/analyzer/dictionary.js +39 -0
- package/dist/src/analyzer/dictionary.js.map +1 -0
- package/dist/src/analyzer/typeNormalization.d.ts +7 -0
- package/dist/src/analyzer/typeNormalization.js +76 -0
- package/dist/src/analyzer/typeNormalization.js.map +1 -0
- package/dist/src/cli.d.ts +8 -0
- package/dist/src/cli.js +264 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/commands/generate.d.ts +5 -0
- package/dist/src/commands/generate.js +266 -0
- package/dist/src/commands/generate.js.map +1 -0
- package/dist/src/commands/prune.d.ts +2 -0
- package/dist/src/commands/prune.js +17 -0
- package/dist/src/commands/prune.js.map +1 -0
- package/dist/src/config.d.ts +5 -0
- package/dist/src/config.js +57 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +9 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/parser/snapshotTableDocs.d.ts +6 -0
- package/dist/src/parser/snapshotTableDocs.js +1001 -0
- package/dist/src/parser/snapshotTableDocs.js.map +1 -0
- package/dist/src/render/columnPages.d.ts +6 -0
- package/dist/src/render/columnPages.js +226 -0
- package/dist/src/render/columnPages.js.map +1 -0
- package/dist/src/render/indexPages.d.ts +6 -0
- package/dist/src/render/indexPages.js +156 -0
- package/dist/src/render/indexPages.js.map +1 -0
- package/dist/src/render/referencesPage.d.ts +6 -0
- package/dist/src/render/referencesPage.js +51 -0
- package/dist/src/render/referencesPage.js.map +1 -0
- package/dist/src/render/tableMarkdown.d.ts +36 -0
- package/dist/src/render/tableMarkdown.js +243 -0
- package/dist/src/render/tableMarkdown.js.map +1 -0
- package/dist/src/render/types.d.ts +7 -0
- package/dist/src/render/types.js +3 -0
- package/dist/src/render/types.js.map +1 -0
- package/dist/src/state/manifest.d.ts +14 -0
- package/dist/src/state/manifest.js +153 -0
- package/dist/src/state/manifest.js.map +1 -0
- package/dist/src/types.d.ts +193 -0
- package/dist/src/types.js +3 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/utils/ddlInputDedupe.d.ts +9 -0
- package/dist/src/utils/ddlInputDedupe.js +25 -0
- package/dist/src/utils/ddlInputDedupe.js.map +1 -0
- package/dist/src/utils/fs.d.ts +13 -0
- package/dist/src/utils/fs.js +187 -0
- package/dist/src/utils/fs.js.map +1 -0
- package/dist/src/utils/io.d.ts +2 -0
- package/dist/src/utils/io.js +13 -0
- package/dist/src/utils/io.js.map +1 -0
- package/dist/src/utils/markdown.d.ts +12 -0
- package/dist/src/utils/markdown.js +34 -0
- package/dist/src/utils/markdown.js.map +1 -0
- package/dist/src/utils/pgDumpFilter.d.ts +8 -0
- package/dist/src/utils/pgDumpFilter.js +104 -0
- package/dist/src/utils/pgDumpFilter.js.map +1 -0
- package/dist/src/utils/slug.d.ts +1 -0
- package/dist/src/utils/slug.js +13 -0
- package/dist/src/utils/slug.js.map +1 -0
- package/package.json +43 -0
|
@@ -0,0 +1,1001 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.snapshotTableDocs = snapshotTableDocs;
|
|
4
|
+
const rawsql_ts_1 = require("rawsql-ts");
|
|
5
|
+
const typeNormalization_1 = require("../analyzer/typeNormalization");
|
|
6
|
+
const slug_1 = require("../utils/slug");
|
|
7
|
+
const formatter = new rawsql_ts_1.SqlFormatter({
|
|
8
|
+
preset: 'postgres',
|
|
9
|
+
keywordCase: 'lower',
|
|
10
|
+
indentSize: 2,
|
|
11
|
+
indentChar: ' ',
|
|
12
|
+
newline: '\n',
|
|
13
|
+
commaBreak: 'after',
|
|
14
|
+
exportComment: 'none',
|
|
15
|
+
identifierEscape: 'quote',
|
|
16
|
+
});
|
|
17
|
+
function snapshotTableDocs(sources, schemaSettings, options) {
|
|
18
|
+
const registry = new Map();
|
|
19
|
+
const comments = {
|
|
20
|
+
tableComments: new Map(),
|
|
21
|
+
columnComments: new Map(),
|
|
22
|
+
};
|
|
23
|
+
const triggerAccumulator = { items: [] };
|
|
24
|
+
const warnings = [];
|
|
25
|
+
for (const source of sources) {
|
|
26
|
+
// Strip psql meta-commands (\connect, \restrict, etc.) that pg_dump emits
|
|
27
|
+
const cleanedSql = source.sql
|
|
28
|
+
.split('\n')
|
|
29
|
+
.map(line => (/^\s*\\/.test(line) ? '' : line))
|
|
30
|
+
.join('\n');
|
|
31
|
+
const statements = rawsql_ts_1.MultiQuerySplitter.split(cleanedSql).queries;
|
|
32
|
+
for (let statementIndex = 0; statementIndex < statements.length; statementIndex += 1) {
|
|
33
|
+
const statement = statements[statementIndex];
|
|
34
|
+
if (statement.isEmpty) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
const sql = statement.sql.trim();
|
|
38
|
+
if (!sql) {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
// Silently skip statements that are intentionally out of scope
|
|
42
|
+
const sqlLower = sql.toLowerCase().replace(/\/\*[\s\S]*?\*\//g, '').replace(/--[^\n]*/g, '').trim();
|
|
43
|
+
if (sqlLower.startsWith('grant ') || sqlLower.startsWith('revoke ')) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
// Silently skip DDL that is outside table structure scope:
|
|
47
|
+
// - Ownership (OWNER TO) from pg_dump
|
|
48
|
+
// - Session settings (SET, SELECT set_config) from pg_dump
|
|
49
|
+
// - Object types not tracked in table docs (SEQUENCE, FUNCTION, VIEW, SCHEMA, TYPE, DOMAIN, EXTENSION)
|
|
50
|
+
if (sqlLower.startsWith('set ') ||
|
|
51
|
+
sqlLower.startsWith('select pg_catalog.set_config(') ||
|
|
52
|
+
sqlLower.startsWith('create sequence ') ||
|
|
53
|
+
sqlLower.startsWith('alter sequence ') ||
|
|
54
|
+
sqlLower.startsWith('create schema ') ||
|
|
55
|
+
sqlLower.startsWith('create function ') ||
|
|
56
|
+
sqlLower.startsWith('create or replace function ') ||
|
|
57
|
+
sqlLower.startsWith('create procedure ') ||
|
|
58
|
+
sqlLower.startsWith('create or replace procedure ') ||
|
|
59
|
+
sqlLower.startsWith('create view ') ||
|
|
60
|
+
sqlLower.startsWith('create or replace view ') ||
|
|
61
|
+
sqlLower.startsWith('create materialized view ') ||
|
|
62
|
+
sqlLower.startsWith('create type ') ||
|
|
63
|
+
sqlLower.startsWith('create or replace type ') ||
|
|
64
|
+
sqlLower.startsWith('create domain ') ||
|
|
65
|
+
sqlLower.startsWith('create extension ') ||
|
|
66
|
+
sqlLower.startsWith('alter function ') ||
|
|
67
|
+
sqlLower.startsWith('alter procedure ') ||
|
|
68
|
+
sqlLower.startsWith('alter view ') ||
|
|
69
|
+
sqlLower.startsWith('alter materialized view ') ||
|
|
70
|
+
sqlLower.startsWith('alter schema ') ||
|
|
71
|
+
sqlLower.startsWith('alter type ') ||
|
|
72
|
+
sqlLower.startsWith('alter default privileges ')) {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
// ALTER TABLE/SEQUENCE ... OWNER TO ... (pg_dump ownership statement)
|
|
76
|
+
if (sqlLower.includes(' owner to ')) {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
// ALTER TABLE ... ATTACH PARTITION (partitioned table child attachment)
|
|
80
|
+
if (sqlLower.includes(' attach partition ')) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
// ALTER TABLE ... ENABLE [ALWAYS] TRIGGER (trigger state management)
|
|
84
|
+
if (sqlLower.startsWith('alter table ') && sqlLower.includes(' enable ') && sqlLower.includes(' trigger ')) {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
// NOTE: CREATE TRIGGER is handled here via regex as an intentional exception.
|
|
88
|
+
// Ideally, trigger parsing should be implemented in the rawsql-ts core parser
|
|
89
|
+
// (SqlParser + DDLStatements model), but that work has not been done yet.
|
|
90
|
+
// Regex parsing is used as a pragmatic workaround until core support is added.
|
|
91
|
+
if (sqlLower.startsWith('create trigger ') ||
|
|
92
|
+
sqlLower.startsWith('create or replace trigger ') ||
|
|
93
|
+
sqlLower.startsWith('create constraint trigger ')) {
|
|
94
|
+
collectTrigger(sql, source.instance, schemaSettings, triggerAccumulator);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
// Silently skip DROP/ALTER TRIGGER
|
|
98
|
+
if (sqlLower.startsWith('drop trigger ') || sqlLower.startsWith('alter trigger ')) {
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
const commentState = applyCommentStatement(sql, schemaSettings, comments);
|
|
102
|
+
if (commentState === 'handled') {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (commentState === 'ambiguous') {
|
|
106
|
+
const w = {
|
|
107
|
+
kind: 'AMBIGUOUS',
|
|
108
|
+
message: 'COMMENT ON statement could not be fully resolved.',
|
|
109
|
+
statementPreview: previewStatement(sql),
|
|
110
|
+
source: { filePath: source.path, statementIndex: statementIndex + 1 },
|
|
111
|
+
};
|
|
112
|
+
console.warn(` [WARN] ${w.kind}: ${w.message} (${w.source.filePath}#${w.source.statementIndex})`);
|
|
113
|
+
warnings.push(w);
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
let parsed;
|
|
117
|
+
try {
|
|
118
|
+
parsed = rawsql_ts_1.SqlParser.parse(sql);
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
122
|
+
const w = {
|
|
123
|
+
kind: 'PARSE_FAILED',
|
|
124
|
+
message,
|
|
125
|
+
statementPreview: previewStatement(sql),
|
|
126
|
+
source: { filePath: source.path, statementIndex: statementIndex + 1 },
|
|
127
|
+
};
|
|
128
|
+
console.warn(` [WARN] ${w.kind}: ${w.message} (${w.source.filePath}#${w.source.statementIndex})\n ${w.statementPreview}`);
|
|
129
|
+
warnings.push(w);
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (parsed instanceof rawsql_ts_1.CreateTableQuery) {
|
|
133
|
+
applyCreateTable(parsed, source, schemaSettings, registry);
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
if (parsed instanceof rawsql_ts_1.AlterTableStatement) {
|
|
137
|
+
applyAlterTable(parsed, source, schemaSettings, registry);
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (parsed instanceof rawsql_ts_1.CommentOnStatement) {
|
|
141
|
+
applyParsedCommentOn(parsed, schemaSettings, comments);
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (parsed instanceof rawsql_ts_1.CreateIndexStatement) {
|
|
145
|
+
applyCreateIndex(parsed, source, schemaSettings, registry);
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
const w = {
|
|
149
|
+
kind: 'UNSUPPORTED_DDL',
|
|
150
|
+
message: `Unsupported statement type: ${resolveConstructorName(parsed)}`,
|
|
151
|
+
statementPreview: previewStatement(sql),
|
|
152
|
+
source: { filePath: source.path, statementIndex: statementIndex + 1 },
|
|
153
|
+
};
|
|
154
|
+
console.warn(` [WARN] ${w.kind}: ${w.message} (${w.source.filePath}#${w.source.statementIndex})`);
|
|
155
|
+
warnings.push(w);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
applyCommentsToRegistry(registry, comments);
|
|
159
|
+
applyTriggersToRegistry(registry, triggerAccumulator);
|
|
160
|
+
const tables = finalizeTables(registry, options);
|
|
161
|
+
inferSuggestedReferences(tables);
|
|
162
|
+
resolveIncomingReferences(tables);
|
|
163
|
+
return {
|
|
164
|
+
tables,
|
|
165
|
+
warnings: warnings.sort(sortWarnings),
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
function applyCreateTable(query, source, schemaSettings, registry) {
|
|
169
|
+
var _a, _b;
|
|
170
|
+
const tableKey = buildTableKey((_a = query.namespaces) !== null && _a !== void 0 ? _a : [], query.tableName.name, schemaSettings);
|
|
171
|
+
const registryKey = buildRegistryKey(source.instance, tableKey);
|
|
172
|
+
const table = (_b = registry.get(registryKey)) !== null && _b !== void 0 ? _b : createWorkingTable(tableKey, source.instance);
|
|
173
|
+
table.sourceFiles.add(source.path);
|
|
174
|
+
const definition = (0, rawsql_ts_1.createTableDefinitionFromCreateTableQuery)(query);
|
|
175
|
+
const definitionColumns = new Map(definition.columns.map((column) => [column.name, column]));
|
|
176
|
+
for (const column of query.columns) {
|
|
177
|
+
const definitionColumn = definitionColumns.get(column.name.name);
|
|
178
|
+
const typeName = normalizeTypeName(column.dataType, definitionColumn === null || definitionColumn === void 0 ? void 0 : definitionColumn.typeName);
|
|
179
|
+
const normalizedType = (0, typeNormalization_1.normalizePostgresType)(typeName);
|
|
180
|
+
const nullable = !column.constraints.some((constraint) => constraint.kind === 'not-null' || constraint.kind === 'primary-key');
|
|
181
|
+
const defaultConstraint = column.constraints.find((constraint) => constraint.kind === 'default');
|
|
182
|
+
const workingColumn = {
|
|
183
|
+
ordinal: columnIndex(table, column.name.name),
|
|
184
|
+
name: column.name.name,
|
|
185
|
+
concept: normalizeIdentifier(column.name.name),
|
|
186
|
+
conceptSlug: (0, slug_1.slugifyIdentifier)(normalizeIdentifier(column.name.name)),
|
|
187
|
+
typeName,
|
|
188
|
+
canonicalType: normalizedType.canonicalType,
|
|
189
|
+
typeKey: normalizedType.typeKey,
|
|
190
|
+
unknownType: normalizedType.unknown,
|
|
191
|
+
nullable,
|
|
192
|
+
defaultValue: (defaultConstraint === null || defaultConstraint === void 0 ? void 0 : defaultConstraint.defaultValue) ? renderExpression(defaultConstraint.defaultValue) : '',
|
|
193
|
+
isPrimaryKey: column.constraints.some((constraint) => constraint.kind === 'primary-key'),
|
|
194
|
+
comment: '',
|
|
195
|
+
};
|
|
196
|
+
table.columns.set(workingColumn.name, workingColumn);
|
|
197
|
+
applyColumnConstraints(table, tableKey, column.name.name, column.constraints, schemaSettings);
|
|
198
|
+
}
|
|
199
|
+
for (const constraint of query.tableConstraints) {
|
|
200
|
+
applyTableConstraint(table, tableKey, constraint, schemaSettings);
|
|
201
|
+
}
|
|
202
|
+
table.constraints = dedupeConstraints(table.constraints);
|
|
203
|
+
table.outgoingReferences = dedupeReferences(table.outgoingReferences);
|
|
204
|
+
registry.set(registryKey, table);
|
|
205
|
+
}
|
|
206
|
+
function applyAlterTable(statement, source, schemaSettings, registry) {
|
|
207
|
+
var _a, _b;
|
|
208
|
+
const tableKey = buildTableKey((_a = statement.table.namespaces) !== null && _a !== void 0 ? _a : [], statement.table.name, schemaSettings);
|
|
209
|
+
const registryKey = buildRegistryKey(source.instance, tableKey);
|
|
210
|
+
const table = (_b = registry.get(registryKey)) !== null && _b !== void 0 ? _b : createWorkingTable(tableKey, source.instance);
|
|
211
|
+
table.sourceFiles.add(source.path);
|
|
212
|
+
for (const action of statement.actions) {
|
|
213
|
+
if (action instanceof rawsql_ts_1.AlterTableAlterColumnDefault && action.setDefault !== null) {
|
|
214
|
+
const columnName = normalizeIdentifier(action.columnName);
|
|
215
|
+
const column = table.columns.get(columnName);
|
|
216
|
+
if (column) {
|
|
217
|
+
column.defaultValue = renderExpression(action.setDefault);
|
|
218
|
+
}
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
if (action instanceof rawsql_ts_1.AlterTableAlterColumnDefault && action.dropDefault) {
|
|
222
|
+
const columnName = normalizeIdentifier(action.columnName);
|
|
223
|
+
const column = table.columns.get(columnName);
|
|
224
|
+
if (column) {
|
|
225
|
+
column.defaultValue = '';
|
|
226
|
+
}
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
if (!(action instanceof rawsql_ts_1.AlterTableAddConstraint)) {
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
applyTableConstraint(table, tableKey, action.constraint, schemaSettings);
|
|
233
|
+
}
|
|
234
|
+
table.constraints = dedupeConstraints(table.constraints);
|
|
235
|
+
table.outgoingReferences = dedupeReferences(table.outgoingReferences);
|
|
236
|
+
registry.set(registryKey, table);
|
|
237
|
+
}
|
|
238
|
+
function applyColumnConstraints(table, tableKey, columnName, constraints, schemaSettings) {
|
|
239
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
240
|
+
for (const constraint of constraints) {
|
|
241
|
+
if (constraint.kind === 'primary-key') {
|
|
242
|
+
const column = table.columns.get(columnName);
|
|
243
|
+
if (column) {
|
|
244
|
+
column.isPrimaryKey = true;
|
|
245
|
+
column.nullable = false;
|
|
246
|
+
}
|
|
247
|
+
table.constraints.push({
|
|
248
|
+
kind: 'PK',
|
|
249
|
+
name: (_b = (_a = constraint.constraintName) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : '',
|
|
250
|
+
expression: `${columnName}`,
|
|
251
|
+
});
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
if (constraint.kind === 'unique') {
|
|
255
|
+
table.constraints.push({
|
|
256
|
+
kind: 'UK',
|
|
257
|
+
name: (_d = (_c = constraint.constraintName) === null || _c === void 0 ? void 0 : _c.name) !== null && _d !== void 0 ? _d : '',
|
|
258
|
+
expression: `${columnName}`,
|
|
259
|
+
});
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
if (constraint.kind === 'check' && constraint.checkExpression) {
|
|
263
|
+
table.constraints.push({
|
|
264
|
+
kind: 'CHECK',
|
|
265
|
+
name: (_f = (_e = constraint.constraintName) === null || _e === void 0 ? void 0 : _e.name) !== null && _f !== void 0 ? _f : '',
|
|
266
|
+
expression: renderExpression(constraint.checkExpression),
|
|
267
|
+
});
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
if (constraint.kind === 'references' && constraint.reference) {
|
|
271
|
+
const targetSchema = resolveTargetSchema((_g = constraint.reference.targetTable.namespaces) !== null && _g !== void 0 ? _g : [], schemaSettings);
|
|
272
|
+
const targetTable = normalizeIdentifier(constraint.reference.targetTable.name);
|
|
273
|
+
const targetColumns = ((_h = constraint.reference.columns) !== null && _h !== void 0 ? _h : []).map((entry) => normalizeIdentifier(entry.name));
|
|
274
|
+
const onDeleteAction = (_j = constraint.reference.onDelete) !== null && _j !== void 0 ? _j : null;
|
|
275
|
+
const onUpdateAction = (_k = constraint.reference.onUpdate) !== null && _k !== void 0 ? _k : null;
|
|
276
|
+
table.constraints.push({
|
|
277
|
+
kind: 'FK',
|
|
278
|
+
name: (_m = (_l = constraint.constraintName) === null || _l === void 0 ? void 0 : _l.name) !== null && _m !== void 0 ? _m : '',
|
|
279
|
+
expression: formatReferenceExpression([columnName], `${targetSchema}.${targetTable}`, targetColumns, onDeleteAction, onUpdateAction),
|
|
280
|
+
});
|
|
281
|
+
table.outgoingReferences.push({
|
|
282
|
+
fromTableKey: tableKey,
|
|
283
|
+
fromColumns: [columnName],
|
|
284
|
+
targetTableKey: `${targetSchema}.${targetTable}`,
|
|
285
|
+
targetColumns,
|
|
286
|
+
onDeleteAction,
|
|
287
|
+
onUpdateAction,
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
function applyTableConstraint(table, tableKey, constraint, schemaSettings) {
|
|
293
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r;
|
|
294
|
+
if (constraint.kind === 'primary-key') {
|
|
295
|
+
for (const identifier of (_a = constraint.columns) !== null && _a !== void 0 ? _a : []) {
|
|
296
|
+
const column = table.columns.get(identifier.name);
|
|
297
|
+
if (!column) {
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
column.isPrimaryKey = true;
|
|
301
|
+
column.nullable = false;
|
|
302
|
+
}
|
|
303
|
+
table.constraints.push({
|
|
304
|
+
kind: 'PK',
|
|
305
|
+
name: (_c = (_b = constraint.constraintName) === null || _b === void 0 ? void 0 : _b.name) !== null && _c !== void 0 ? _c : '',
|
|
306
|
+
expression: ((_d = constraint.columns) !== null && _d !== void 0 ? _d : []).map((entry) => entry.name).sort().join(', '),
|
|
307
|
+
});
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
if (constraint.kind === 'unique') {
|
|
311
|
+
table.constraints.push({
|
|
312
|
+
kind: 'UK',
|
|
313
|
+
name: (_f = (_e = constraint.constraintName) === null || _e === void 0 ? void 0 : _e.name) !== null && _f !== void 0 ? _f : '',
|
|
314
|
+
expression: ((_g = constraint.columns) !== null && _g !== void 0 ? _g : []).map((entry) => entry.name).sort().join(', '),
|
|
315
|
+
});
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
if (constraint.kind === 'check' && constraint.checkExpression) {
|
|
319
|
+
table.constraints.push({
|
|
320
|
+
kind: 'CHECK',
|
|
321
|
+
name: (_j = (_h = constraint.constraintName) === null || _h === void 0 ? void 0 : _h.name) !== null && _j !== void 0 ? _j : '',
|
|
322
|
+
expression: renderExpression(constraint.checkExpression),
|
|
323
|
+
});
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
if (constraint.kind === 'foreign-key' && constraint.reference) {
|
|
327
|
+
const fromColumns = ((_k = constraint.columns) !== null && _k !== void 0 ? _k : []).map((entry) => entry.name);
|
|
328
|
+
const targetSchema = resolveTargetSchema((_l = constraint.reference.targetTable.namespaces) !== null && _l !== void 0 ? _l : [], schemaSettings);
|
|
329
|
+
const targetTable = normalizeIdentifier(constraint.reference.targetTable.name);
|
|
330
|
+
const targetColumns = ((_m = constraint.reference.columns) !== null && _m !== void 0 ? _m : []).map((entry) => normalizeIdentifier(entry.name));
|
|
331
|
+
const onDeleteAction = (_o = constraint.reference.onDelete) !== null && _o !== void 0 ? _o : null;
|
|
332
|
+
const onUpdateAction = (_p = constraint.reference.onUpdate) !== null && _p !== void 0 ? _p : null;
|
|
333
|
+
table.constraints.push({
|
|
334
|
+
kind: 'FK',
|
|
335
|
+
name: (_r = (_q = constraint.constraintName) === null || _q === void 0 ? void 0 : _q.name) !== null && _r !== void 0 ? _r : '',
|
|
336
|
+
expression: formatReferenceExpression(fromColumns, `${targetSchema}.${targetTable}`, targetColumns, onDeleteAction, onUpdateAction),
|
|
337
|
+
});
|
|
338
|
+
table.outgoingReferences.push({
|
|
339
|
+
fromTableKey: tableKey,
|
|
340
|
+
fromColumns,
|
|
341
|
+
targetTableKey: `${targetSchema}.${targetTable}`,
|
|
342
|
+
targetColumns,
|
|
343
|
+
onDeleteAction,
|
|
344
|
+
onUpdateAction,
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
function createWorkingTable(tableKey, instance = '') {
|
|
349
|
+
const [schema, table] = tableKey.split('.');
|
|
350
|
+
return {
|
|
351
|
+
schema,
|
|
352
|
+
table,
|
|
353
|
+
schemaSlug: (0, slug_1.slugifyIdentifier)(schema),
|
|
354
|
+
tableSlug: (0, slug_1.slugifyIdentifier)(table),
|
|
355
|
+
instance,
|
|
356
|
+
tableComment: '',
|
|
357
|
+
sourceFiles: new Set(),
|
|
358
|
+
columns: new Map(),
|
|
359
|
+
constraints: [],
|
|
360
|
+
triggers: [],
|
|
361
|
+
outgoingReferences: [],
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
function normalizeTypeName(dataType, fallbackType) {
|
|
365
|
+
if (dataType instanceof rawsql_ts_1.TypeValue) {
|
|
366
|
+
return dataType.getTypeName();
|
|
367
|
+
}
|
|
368
|
+
if (dataType instanceof rawsql_ts_1.RawString) {
|
|
369
|
+
return dataType.value;
|
|
370
|
+
}
|
|
371
|
+
return fallbackType !== null && fallbackType !== void 0 ? fallbackType : '';
|
|
372
|
+
}
|
|
373
|
+
function renderExpression(component) {
|
|
374
|
+
return formatter.format(component).formattedSql.trim();
|
|
375
|
+
}
|
|
376
|
+
function applyCreateIndex(statement, source, schemaSettings, registry) {
|
|
377
|
+
var _a;
|
|
378
|
+
const tableKey = buildTableKey((_a = statement.tableName.namespaces) !== null && _a !== void 0 ? _a : [], statement.tableName.name, schemaSettings);
|
|
379
|
+
const registryKey = buildRegistryKey(source.instance, tableKey);
|
|
380
|
+
const table = registry.get(registryKey);
|
|
381
|
+
if (!table) {
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
const kind = statement.unique ? 'UK' : 'INDEX';
|
|
385
|
+
const name = normalizeIdentifier(statement.indexName.name);
|
|
386
|
+
const expression = statement.columns.map((col) => renderExpression(col.expression)).join(', ');
|
|
387
|
+
table.constraints.push({ kind, name, expression, isIndex: true });
|
|
388
|
+
table.constraints = dedupeConstraints(table.constraints);
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Parses a CREATE TRIGGER statement using regex and stores it in the accumulator.
|
|
392
|
+
*
|
|
393
|
+
* NOTE: This is an intentional exception to the design philosophy of this package,
|
|
394
|
+
* which normally delegates all SQL parsing to the rawsql-ts core (SqlParser).
|
|
395
|
+
* CREATE TRIGGER is not yet supported by the core parser, so regex is used here
|
|
396
|
+
* as a pragmatic workaround. Once core support is added, this function should be
|
|
397
|
+
* replaced with a proper SqlParser-based approach.
|
|
398
|
+
*/
|
|
399
|
+
function collectTrigger(sql, instance, schemaSettings, accumulator) {
|
|
400
|
+
const normalized = sql.replace(/\s+/g, ' ').trim();
|
|
401
|
+
const nameMatch = normalized.match(/\btrigger\s+"?(\w+)"?\s/i);
|
|
402
|
+
if (!nameMatch)
|
|
403
|
+
return;
|
|
404
|
+
const name = nameMatch[1].toLowerCase();
|
|
405
|
+
const timingMatch = normalized.match(/\b(before|after|instead\s+of)\b/i);
|
|
406
|
+
const timing = timingMatch ? timingMatch[1].toUpperCase().replace(/\s+/g, ' ') : '';
|
|
407
|
+
const onMatch = normalized.match(/\bon\s+([\w."]+(?:\.[\w."]+)*)/i);
|
|
408
|
+
if (!onMatch)
|
|
409
|
+
return;
|
|
410
|
+
const rawTableRef = onMatch[1].replace(/"/g, '').toLowerCase();
|
|
411
|
+
const parts = rawTableRef.split('.');
|
|
412
|
+
const tableKey = parts.length >= 2
|
|
413
|
+
? `${parts[parts.length - 2]}.${parts[parts.length - 1]}`
|
|
414
|
+
: `${schemaSettings.defaultSchema}.${parts[0]}`;
|
|
415
|
+
const registryKey = buildRegistryKey(instance, tableKey);
|
|
416
|
+
const eventsMatch = normalized.match(/\b(?:before|after|instead\s+of)\b\s+(.*?)\s+\bon\b/i);
|
|
417
|
+
const events = [];
|
|
418
|
+
if (eventsMatch) {
|
|
419
|
+
const eventsStr = eventsMatch[1];
|
|
420
|
+
if (/\binsert\b/i.test(eventsStr))
|
|
421
|
+
events.push('INSERT');
|
|
422
|
+
if (/\bupdate\b/i.test(eventsStr))
|
|
423
|
+
events.push('UPDATE');
|
|
424
|
+
if (/\bdelete\b/i.test(eventsStr))
|
|
425
|
+
events.push('DELETE');
|
|
426
|
+
if (/\btruncate\b/i.test(eventsStr))
|
|
427
|
+
events.push('TRUNCATE');
|
|
428
|
+
}
|
|
429
|
+
const forEachMatch = normalized.match(/\bfor\s+(?:each\s+)?(row|statement)\b/i);
|
|
430
|
+
const forEach = forEachMatch ? forEachMatch[1].toUpperCase() : 'ROW';
|
|
431
|
+
const funcMatch = normalized.match(/\bexecute\s+(?:function|procedure)\s+([\w."]+(?:\.[\w."]+)*\s*\([^)]*\))/i);
|
|
432
|
+
const functionName = funcMatch ? funcMatch[1].replace(/"/g, '').toLowerCase().replace(/\s+/g, '') : '';
|
|
433
|
+
const rawSql = normalized.endsWith(';') ? normalized : `${normalized};`;
|
|
434
|
+
accumulator.items.push({ tableKey: registryKey, trigger: { name, timing, events, forEach, functionName, rawSql } });
|
|
435
|
+
}
|
|
436
|
+
function applyTriggersToRegistry(registry, accumulator) {
|
|
437
|
+
for (const { tableKey, trigger } of accumulator.items) {
|
|
438
|
+
const table = registry.get(tableKey);
|
|
439
|
+
if (!table)
|
|
440
|
+
continue;
|
|
441
|
+
const already = table.triggers.some((t) => t.name === trigger.name);
|
|
442
|
+
if (!already) {
|
|
443
|
+
table.triggers.push(trigger);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
function buildTableKey(namespaces, tableName, schemaSettings) {
|
|
448
|
+
const normalizedTable = normalizeIdentifier(tableName);
|
|
449
|
+
if (namespaces.length > 0) {
|
|
450
|
+
const schema = normalizeIdentifier(namespaces[namespaces.length - 1]);
|
|
451
|
+
return `${schema}.${normalizedTable}`;
|
|
452
|
+
}
|
|
453
|
+
return `${schemaSettings.defaultSchema}.${normalizedTable}`;
|
|
454
|
+
}
|
|
455
|
+
function buildRegistryKey(instance, tableKey) {
|
|
456
|
+
return `${instance !== null && instance !== void 0 ? instance : ''}\u0000${tableKey}`;
|
|
457
|
+
}
|
|
458
|
+
function normalizeIdentifier(value) {
|
|
459
|
+
const token = typeof value === 'string' ? value : 'name' in value ? value.name : value.value;
|
|
460
|
+
return token.replace(/^"|"$/g, '').toLowerCase();
|
|
461
|
+
}
|
|
462
|
+
function resolveTargetSchema(namespaces, schemaSettings) {
|
|
463
|
+
if (!namespaces || namespaces.length === 0) {
|
|
464
|
+
return schemaSettings.defaultSchema;
|
|
465
|
+
}
|
|
466
|
+
return normalizeIdentifier(namespaces[namespaces.length - 1]);
|
|
467
|
+
}
|
|
468
|
+
function applyParsedCommentOn(statement, schemaSettings, comments) {
|
|
469
|
+
var _a;
|
|
470
|
+
const commentText = statement.comment !== null
|
|
471
|
+
? parseCommentLiteral(renderExpression(statement.comment))
|
|
472
|
+
: null;
|
|
473
|
+
if (commentText === null)
|
|
474
|
+
return;
|
|
475
|
+
const namespaces = (_a = statement.target.namespaces) !== null && _a !== void 0 ? _a : [];
|
|
476
|
+
const name = normalizeIdentifier(statement.target.name);
|
|
477
|
+
if (statement.targetKind === 'table') {
|
|
478
|
+
const schema = namespaces.length > 0
|
|
479
|
+
? normalizeIdentifier(namespaces[namespaces.length - 1])
|
|
480
|
+
: schemaSettings.defaultSchema;
|
|
481
|
+
comments.tableComments.set(`${schema}.${name}`, commentText);
|
|
482
|
+
}
|
|
483
|
+
else {
|
|
484
|
+
// column: namespaces = [schema, table] or [table]
|
|
485
|
+
if (namespaces.length >= 2) {
|
|
486
|
+
const schema = normalizeIdentifier(namespaces[namespaces.length - 2]);
|
|
487
|
+
const table = normalizeIdentifier(namespaces[namespaces.length - 1]);
|
|
488
|
+
comments.columnComments.set(`${schema}.${table}.${name}`, commentText);
|
|
489
|
+
}
|
|
490
|
+
else if (namespaces.length === 1) {
|
|
491
|
+
const table = normalizeIdentifier(namespaces[0]);
|
|
492
|
+
comments.columnComments.set(`${schemaSettings.defaultSchema}.${table}.${name}`, commentText);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
/** Returns true if the lowercase COMMENT ON statement targets an object type we don't track. */
|
|
497
|
+
function isUnsupportedCommentOnTarget(lower) {
|
|
498
|
+
return (lower.startsWith('comment on constraint ') ||
|
|
499
|
+
lower.startsWith('comment on view ') ||
|
|
500
|
+
lower.startsWith('comment on materialized view ') ||
|
|
501
|
+
lower.startsWith('comment on index ') ||
|
|
502
|
+
lower.startsWith('comment on sequence ') ||
|
|
503
|
+
lower.startsWith('comment on function ') ||
|
|
504
|
+
lower.startsWith('comment on procedure ') ||
|
|
505
|
+
lower.startsWith('comment on type ') ||
|
|
506
|
+
lower.startsWith('comment on schema ') ||
|
|
507
|
+
lower.startsWith('comment on extension '));
|
|
508
|
+
}
|
|
509
|
+
function applyCommentStatement(sql, schemaSettings, comments) {
|
|
510
|
+
const normalized = sql.trim();
|
|
511
|
+
const lower = normalized.toLowerCase();
|
|
512
|
+
if (!lower.startsWith('comment on ')) {
|
|
513
|
+
// The SQL may have pg_dump section-header comments (-- -- Name: ...) prepended.
|
|
514
|
+
// Strip them to detect the actual statement type, but only for classification.
|
|
515
|
+
// TABLE/COLUMN handling is left to SqlParser (via the 'ignored' return path) so
|
|
516
|
+
// that multi-line comment values and keyword column names are handled correctly.
|
|
517
|
+
const strippedLower = normalized.replace(/^(?:--[^\n]*\n)+/g, '').trim().toLowerCase();
|
|
518
|
+
if (!strippedLower.startsWith('comment on ')) {
|
|
519
|
+
return 'ignored';
|
|
520
|
+
}
|
|
521
|
+
// Silently skip COMMENT ON for object types we don't track in table docs.
|
|
522
|
+
if (isUnsupportedCommentOnTarget(strippedLower)) {
|
|
523
|
+
return 'handled';
|
|
524
|
+
}
|
|
525
|
+
// TABLE/COLUMN COMMENT with pg_dump headers → let SqlParser handle it.
|
|
526
|
+
return 'ignored';
|
|
527
|
+
}
|
|
528
|
+
// Direct COMMENT ON (no pg_dump headers).
|
|
529
|
+
// Silently skip unsupported object types.
|
|
530
|
+
if (isUnsupportedCommentOnTarget(lower)) {
|
|
531
|
+
return 'handled';
|
|
532
|
+
}
|
|
533
|
+
const tableMatch = normalized.match(/^comment\s+on\s+table\s+(.+?)\s+is\s+(.+?);?$/i);
|
|
534
|
+
if (tableMatch) {
|
|
535
|
+
const parsedComment = parseCommentLiteral(tableMatch[2]);
|
|
536
|
+
if (parsedComment === null) {
|
|
537
|
+
return 'handled';
|
|
538
|
+
}
|
|
539
|
+
const target = parseTableTarget(tableMatch[1], schemaSettings);
|
|
540
|
+
comments.tableComments.set(target, parsedComment);
|
|
541
|
+
return 'handled';
|
|
542
|
+
}
|
|
543
|
+
const columnMatch = normalized.match(/^comment\s+on\s+column\s+(.+?)\s+is\s+(.+?);?$/i);
|
|
544
|
+
if (!columnMatch) {
|
|
545
|
+
return 'ambiguous';
|
|
546
|
+
}
|
|
547
|
+
const parsedComment = parseCommentLiteral(columnMatch[2]);
|
|
548
|
+
if (parsedComment === null) {
|
|
549
|
+
return 'handled';
|
|
550
|
+
}
|
|
551
|
+
const target = parseColumnTarget(columnMatch[1], schemaSettings);
|
|
552
|
+
if (!target) {
|
|
553
|
+
return 'ambiguous';
|
|
554
|
+
}
|
|
555
|
+
comments.columnComments.set(target, parsedComment);
|
|
556
|
+
return 'handled';
|
|
557
|
+
}
|
|
558
|
+
function parseCommentLiteral(rawValue) {
|
|
559
|
+
const trimmed = rawValue.trim().replace(/;$/, '');
|
|
560
|
+
if (/^null$/i.test(trimmed)) {
|
|
561
|
+
return null;
|
|
562
|
+
}
|
|
563
|
+
if (!trimmed.startsWith("'") || !trimmed.endsWith("'")) {
|
|
564
|
+
return trimmed;
|
|
565
|
+
}
|
|
566
|
+
const body = trimmed.slice(1, -1);
|
|
567
|
+
return body.replace(/''/g, "'");
|
|
568
|
+
}
|
|
569
|
+
function parseTableTarget(rawTarget, schemaSettings) {
|
|
570
|
+
var _a;
|
|
571
|
+
const parts = parseQualifiedParts(rawTarget);
|
|
572
|
+
if (parts.length >= 2) {
|
|
573
|
+
return `${parts[parts.length - 2]}.${parts[parts.length - 1]}`;
|
|
574
|
+
}
|
|
575
|
+
return `${schemaSettings.defaultSchema}.${(_a = parts[0]) !== null && _a !== void 0 ? _a : 'unknown'}`;
|
|
576
|
+
}
|
|
577
|
+
function parseColumnTarget(rawTarget, schemaSettings) {
|
|
578
|
+
const parts = parseQualifiedParts(rawTarget);
|
|
579
|
+
if (parts.length < 2) {
|
|
580
|
+
return null;
|
|
581
|
+
}
|
|
582
|
+
if (parts.length >= 3) {
|
|
583
|
+
return `${parts[parts.length - 3]}.${parts[parts.length - 2]}.${parts[parts.length - 1]}`;
|
|
584
|
+
}
|
|
585
|
+
return `${schemaSettings.defaultSchema}.${parts[0]}.${parts[1]}`;
|
|
586
|
+
}
|
|
587
|
+
function parseQualifiedParts(input) {
|
|
588
|
+
const parts = [];
|
|
589
|
+
let current = '';
|
|
590
|
+
let inQuote = false;
|
|
591
|
+
for (let index = 0; index < input.length; index += 1) {
|
|
592
|
+
const char = input[index];
|
|
593
|
+
if (char === '"') {
|
|
594
|
+
inQuote = !inQuote;
|
|
595
|
+
continue;
|
|
596
|
+
}
|
|
597
|
+
if (char === '.' && !inQuote) {
|
|
598
|
+
if (current.trim().length > 0) {
|
|
599
|
+
parts.push(normalizeIdentifier(current.trim()));
|
|
600
|
+
}
|
|
601
|
+
current = '';
|
|
602
|
+
continue;
|
|
603
|
+
}
|
|
604
|
+
current += char;
|
|
605
|
+
}
|
|
606
|
+
if (current.trim().length > 0) {
|
|
607
|
+
parts.push(normalizeIdentifier(current.trim()));
|
|
608
|
+
}
|
|
609
|
+
return parts;
|
|
610
|
+
}
|
|
611
|
+
function applyCommentsToRegistry(registry, comments) {
|
|
612
|
+
for (const [tableKey, comment] of comments.tableComments.entries()) {
|
|
613
|
+
const [schema, tableName] = tableKey.split('.');
|
|
614
|
+
if (!schema || !tableName) {
|
|
615
|
+
continue;
|
|
616
|
+
}
|
|
617
|
+
const tables = findTablesBySchemaAndName(registry, schema, tableName);
|
|
618
|
+
for (const table of tables) {
|
|
619
|
+
table.tableComment = comment;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
for (const [columnKey, comment] of comments.columnComments.entries()) {
|
|
623
|
+
const [schema, tableName, columnName] = columnKey.split('.');
|
|
624
|
+
if (!schema || !tableName || !columnName) {
|
|
625
|
+
continue;
|
|
626
|
+
}
|
|
627
|
+
const tables = findTablesBySchemaAndName(registry, schema, tableName);
|
|
628
|
+
for (const table of tables) {
|
|
629
|
+
const column = table.columns.get(columnName);
|
|
630
|
+
if (!column) {
|
|
631
|
+
continue;
|
|
632
|
+
}
|
|
633
|
+
column.comment = comment;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
function finalizeTables(registry, options) {
|
|
638
|
+
return Array.from(registry.values())
|
|
639
|
+
.map((entry) => {
|
|
640
|
+
const columns = Array.from(entry.columns.values())
|
|
641
|
+
.sort((a, b) => sortColumns(a, b, options.columnOrder))
|
|
642
|
+
.map((column) => ({
|
|
643
|
+
name: column.name,
|
|
644
|
+
concept: column.concept,
|
|
645
|
+
conceptSlug: column.conceptSlug,
|
|
646
|
+
typeName: column.typeName,
|
|
647
|
+
canonicalType: column.canonicalType,
|
|
648
|
+
typeKey: column.typeKey,
|
|
649
|
+
nullable: column.nullable,
|
|
650
|
+
defaultValue: column.defaultValue,
|
|
651
|
+
isPrimaryKey: column.isPrimaryKey,
|
|
652
|
+
comment: column.comment,
|
|
653
|
+
checks: [],
|
|
654
|
+
unknownType: column.unknownType,
|
|
655
|
+
}));
|
|
656
|
+
const primaryKey = columns.filter((column) => column.isPrimaryKey).map((column) => column.name).sort();
|
|
657
|
+
const outgoingReferences = entry.outgoingReferences
|
|
658
|
+
.map((reference) => {
|
|
659
|
+
var _a, _b, _c, _d, _e, _f;
|
|
660
|
+
return ({
|
|
661
|
+
direction: 'outgoing',
|
|
662
|
+
source: 'ddl',
|
|
663
|
+
fromTableKey: reference.fromTableKey,
|
|
664
|
+
fromTableComment: entry.tableComment,
|
|
665
|
+
targetTableKey: reference.targetTableKey,
|
|
666
|
+
targetTableComment: (_b = (_a = findTableByTableKey(registry, reference.targetTableKey, entry.instance)) === null || _a === void 0 ? void 0 : _a.tableComment) !== null && _b !== void 0 ? _b : '',
|
|
667
|
+
fromColumns: [...reference.fromColumns],
|
|
668
|
+
targetColumns: [...reference.targetColumns],
|
|
669
|
+
onDeleteAction: reference.onDeleteAction,
|
|
670
|
+
onUpdateAction: reference.onUpdateAction,
|
|
671
|
+
fromSchemaSlug: (0, slug_1.slugifyIdentifier)((_c = reference.fromTableKey.split('.')[0]) !== null && _c !== void 0 ? _c : ''),
|
|
672
|
+
fromTableSlug: (0, slug_1.slugifyIdentifier)((_d = reference.fromTableKey.split('.')[1]) !== null && _d !== void 0 ? _d : ''),
|
|
673
|
+
targetSchemaSlug: (0, slug_1.slugifyIdentifier)((_e = reference.targetTableKey.split('.')[0]) !== null && _e !== void 0 ? _e : ''),
|
|
674
|
+
targetTableSlug: (0, slug_1.slugifyIdentifier)((_f = reference.targetTableKey.split('.')[1]) !== null && _f !== void 0 ? _f : ''),
|
|
675
|
+
expression: formatReferenceExpression(reference.fromColumns, reference.targetTableKey, reference.targetColumns),
|
|
676
|
+
});
|
|
677
|
+
})
|
|
678
|
+
.sort(sortReferences);
|
|
679
|
+
return {
|
|
680
|
+
schema: entry.schema,
|
|
681
|
+
table: entry.table,
|
|
682
|
+
schemaSlug: entry.schemaSlug,
|
|
683
|
+
tableSlug: entry.tableSlug,
|
|
684
|
+
instance: entry.instance,
|
|
685
|
+
tableComment: entry.tableComment,
|
|
686
|
+
sourceFiles: Array.from(entry.sourceFiles).sort(),
|
|
687
|
+
columns,
|
|
688
|
+
primaryKey,
|
|
689
|
+
constraints: [...entry.constraints].sort(sortConstraints),
|
|
690
|
+
triggers: [...entry.triggers].sort((a, b) => a.name.localeCompare(b.name)),
|
|
691
|
+
outgoingReferences,
|
|
692
|
+
incomingReferences: [],
|
|
693
|
+
normalizedSql: buildNormalizedSql(entry, columns),
|
|
694
|
+
};
|
|
695
|
+
})
|
|
696
|
+
.sort((a, b) => `${a.schema}.${a.table}`.localeCompare(`${b.schema}.${b.table}`));
|
|
697
|
+
}
|
|
698
|
+
function findTablesBySchemaAndName(registry, schema, tableName) {
|
|
699
|
+
return Array.from(registry.values()).filter((table) => table.schema === schema && table.table === tableName);
|
|
700
|
+
}
|
|
701
|
+
function findTableByTableKey(registry, tableKey, instance) {
|
|
702
|
+
const [schema, tableName] = tableKey.split('.');
|
|
703
|
+
if (!schema || !tableName) {
|
|
704
|
+
return undefined;
|
|
705
|
+
}
|
|
706
|
+
const sameInstanceMatch = Array.from(registry.values()).find((table) => table.schema === schema && table.table === tableName && table.instance === instance);
|
|
707
|
+
if (sameInstanceMatch) {
|
|
708
|
+
return sameInstanceMatch;
|
|
709
|
+
}
|
|
710
|
+
return Array.from(registry.values()).find((table) => table.schema === schema && table.table === tableName);
|
|
711
|
+
}
|
|
712
|
+
function sortColumns(left, right, mode) {
|
|
713
|
+
if (mode === 'name') {
|
|
714
|
+
const byName = left.name.localeCompare(right.name);
|
|
715
|
+
if (byName !== 0) {
|
|
716
|
+
return byName;
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
return left.ordinal - right.ordinal;
|
|
720
|
+
}
|
|
721
|
+
function columnIndex(table, columnName) {
|
|
722
|
+
const existing = table.columns.get(columnName);
|
|
723
|
+
if (existing) {
|
|
724
|
+
return existing.ordinal;
|
|
725
|
+
}
|
|
726
|
+
return table.columns.size;
|
|
727
|
+
}
|
|
728
|
+
function buildNormalizedSql(entry, columns) {
|
|
729
|
+
// --- Definition block: CREATE TABLE + ALTER TABLE constraints + CREATE [UNIQUE] INDEX ---
|
|
730
|
+
const defStatements = [];
|
|
731
|
+
const columnLines = columns.map((column) => {
|
|
732
|
+
const segments = [` ${column.name} ${column.typeName || 'text'}`];
|
|
733
|
+
if (!column.nullable) {
|
|
734
|
+
segments.push('NOT NULL');
|
|
735
|
+
}
|
|
736
|
+
if (column.defaultValue.trim()) {
|
|
737
|
+
segments.push(`DEFAULT ${column.defaultValue}`);
|
|
738
|
+
}
|
|
739
|
+
return segments.join(' ');
|
|
740
|
+
});
|
|
741
|
+
defStatements.push(`CREATE TABLE ${entry.schema}.${entry.table} (\n${columnLines.join(',\n')}\n);`);
|
|
742
|
+
const constraints = [...entry.constraints].sort(sortConstraints);
|
|
743
|
+
for (const constraint of constraints) {
|
|
744
|
+
const prefix = constraint.name ? `CONSTRAINT ${constraint.name} ` : '';
|
|
745
|
+
if (constraint.kind === 'PK') {
|
|
746
|
+
defStatements.push(`ALTER TABLE ${entry.schema}.${entry.table} ADD ${prefix}PRIMARY KEY (${constraint.expression});`);
|
|
747
|
+
continue;
|
|
748
|
+
}
|
|
749
|
+
if (constraint.kind === 'UK' && !constraint.isIndex) {
|
|
750
|
+
defStatements.push(`ALTER TABLE ${entry.schema}.${entry.table} ADD ${prefix}UNIQUE (${constraint.expression});`);
|
|
751
|
+
continue;
|
|
752
|
+
}
|
|
753
|
+
if (constraint.kind === 'UK' && constraint.isIndex) {
|
|
754
|
+
defStatements.push(`CREATE UNIQUE INDEX ${constraint.name} ON ${entry.schema}.${entry.table} (${constraint.expression});`);
|
|
755
|
+
continue;
|
|
756
|
+
}
|
|
757
|
+
if (constraint.kind === 'CHECK') {
|
|
758
|
+
defStatements.push(`ALTER TABLE ${entry.schema}.${entry.table} ADD ${prefix}CHECK (${constraint.expression});`);
|
|
759
|
+
continue;
|
|
760
|
+
}
|
|
761
|
+
if (constraint.kind === 'FK') {
|
|
762
|
+
const [left, right] = constraint.expression.split('->').map((part) => part.trim());
|
|
763
|
+
if (left && right) {
|
|
764
|
+
defStatements.push(`ALTER TABLE ${entry.schema}.${entry.table} ADD ${prefix}FOREIGN KEY (${left}) REFERENCES ${right};`);
|
|
765
|
+
}
|
|
766
|
+
continue;
|
|
767
|
+
}
|
|
768
|
+
if (constraint.kind === 'INDEX') {
|
|
769
|
+
defStatements.push(`CREATE INDEX ${constraint.name} ON ${entry.schema}.${entry.table} (${constraint.expression});`);
|
|
770
|
+
continue;
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
const defLines = ['-- normalized: v1 dialect=postgres'];
|
|
774
|
+
for (const statement of defStatements) {
|
|
775
|
+
const formattedStatement = formatNormalizedStatement(statement);
|
|
776
|
+
if (formattedStatement) {
|
|
777
|
+
defLines.push(formattedStatement);
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
// --- Comments block: COMMENT ON TABLE + COMMENT ON COLUMN ---
|
|
781
|
+
const commentStatements = [];
|
|
782
|
+
if (entry.tableComment.trim()) {
|
|
783
|
+
commentStatements.push(`COMMENT ON TABLE ${entry.schema}.${entry.table} IS '${entry.tableComment.replace(/'/g, "''")}';`);
|
|
784
|
+
}
|
|
785
|
+
for (const column of columns) {
|
|
786
|
+
if (!column.comment.trim()) {
|
|
787
|
+
continue;
|
|
788
|
+
}
|
|
789
|
+
commentStatements.push(`COMMENT ON COLUMN ${entry.schema}.${entry.table}.${column.name} IS '${column.comment.replace(/'/g, "''")}';`);
|
|
790
|
+
}
|
|
791
|
+
let comments = '';
|
|
792
|
+
if (commentStatements.length > 0) {
|
|
793
|
+
const commentLines = ['-- normalized: v1 dialect=postgres'];
|
|
794
|
+
for (const statement of commentStatements) {
|
|
795
|
+
const formattedStatement = formatNormalizedStatement(statement);
|
|
796
|
+
if (formattedStatement) {
|
|
797
|
+
commentLines.push(formattedStatement);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
comments = commentLines.join('\n');
|
|
801
|
+
}
|
|
802
|
+
// --- Triggers block: raw CREATE TRIGGER statements (not normalized) ---
|
|
803
|
+
let triggers = '';
|
|
804
|
+
if (entry.triggers.length > 0) {
|
|
805
|
+
const triggerLines = ['-- raw (not normalized)'];
|
|
806
|
+
for (const trigger of entry.triggers) {
|
|
807
|
+
triggerLines.push(trigger.rawSql);
|
|
808
|
+
}
|
|
809
|
+
triggers = triggerLines.join('\n');
|
|
810
|
+
}
|
|
811
|
+
return {
|
|
812
|
+
definition: defLines.join('\n'),
|
|
813
|
+
comments,
|
|
814
|
+
triggers,
|
|
815
|
+
};
|
|
816
|
+
}
|
|
817
|
+
function formatNormalizedStatement(statement) {
|
|
818
|
+
const trimmed = statement.trim();
|
|
819
|
+
if (!trimmed) {
|
|
820
|
+
return '';
|
|
821
|
+
}
|
|
822
|
+
try {
|
|
823
|
+
const parsed = rawsql_ts_1.SqlParser.parse(trimmed);
|
|
824
|
+
const formatted = formatter.format(parsed).formattedSql.trim().replace(/;$/, '');
|
|
825
|
+
return `${formatted};`;
|
|
826
|
+
}
|
|
827
|
+
catch {
|
|
828
|
+
return trimmed.endsWith(';') ? trimmed : `${trimmed};`;
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
function resolveIncomingReferences(tables) {
|
|
832
|
+
var _a, _b;
|
|
833
|
+
const incomingMap = new Map();
|
|
834
|
+
for (const table of tables) {
|
|
835
|
+
for (const outgoing of table.outgoingReferences) {
|
|
836
|
+
const incoming = (_a = incomingMap.get(outgoing.targetTableKey)) !== null && _a !== void 0 ? _a : [];
|
|
837
|
+
incoming.push({
|
|
838
|
+
direction: 'incoming',
|
|
839
|
+
source: outgoing.source,
|
|
840
|
+
fromTableKey: outgoing.fromTableKey,
|
|
841
|
+
fromTableComment: outgoing.fromTableComment,
|
|
842
|
+
targetTableKey: outgoing.targetTableKey,
|
|
843
|
+
targetTableComment: outgoing.targetTableComment,
|
|
844
|
+
fromColumns: [...outgoing.fromColumns],
|
|
845
|
+
targetColumns: [...outgoing.targetColumns],
|
|
846
|
+
onDeleteAction: outgoing.onDeleteAction,
|
|
847
|
+
onUpdateAction: outgoing.onUpdateAction,
|
|
848
|
+
fromSchemaSlug: outgoing.fromSchemaSlug,
|
|
849
|
+
fromTableSlug: outgoing.fromTableSlug,
|
|
850
|
+
targetSchemaSlug: outgoing.targetSchemaSlug,
|
|
851
|
+
targetTableSlug: outgoing.targetTableSlug,
|
|
852
|
+
matchRule: outgoing.matchRule,
|
|
853
|
+
expression: `${outgoing.fromTableKey}: ${formatReferenceExpression(outgoing.fromColumns, outgoing.targetTableKey, outgoing.targetColumns)}`,
|
|
854
|
+
});
|
|
855
|
+
incomingMap.set(outgoing.targetTableKey, incoming);
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
for (const table of tables) {
|
|
859
|
+
table.incomingReferences = ((_b = incomingMap.get(`${table.schema}.${table.table}`)) !== null && _b !== void 0 ? _b : []).sort(sortReferences);
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
function inferSuggestedReferences(tables) {
|
|
863
|
+
var _a, _b, _c, _d;
|
|
864
|
+
const tableByKey = new Map(tables.map((table) => [`${table.schema}.${table.table}`, table]));
|
|
865
|
+
const definedKeys = new Set();
|
|
866
|
+
for (const table of tables) {
|
|
867
|
+
for (const reference of table.outgoingReferences) {
|
|
868
|
+
if (reference.source !== 'ddl') {
|
|
869
|
+
continue;
|
|
870
|
+
}
|
|
871
|
+
definedKeys.add(referenceIdentity(reference.fromTableKey, reference.fromColumns, reference.targetTableKey, reference.targetColumns));
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
const pkTargets = tables
|
|
875
|
+
.filter((table) => table.primaryKey.length === 1)
|
|
876
|
+
.map((table) => ({
|
|
877
|
+
tableKey: `${table.schema}.${table.table}`,
|
|
878
|
+
pkColumn: table.primaryKey[0],
|
|
879
|
+
schemaSlug: table.schemaSlug,
|
|
880
|
+
tableSlug: table.tableSlug,
|
|
881
|
+
instance: table.instance,
|
|
882
|
+
}))
|
|
883
|
+
.sort((a, b) => `${a.tableKey}|${a.pkColumn}`.localeCompare(`${b.tableKey}|${b.pkColumn}`));
|
|
884
|
+
for (const table of tables) {
|
|
885
|
+
const fromTableKey = `${table.schema}.${table.table}`;
|
|
886
|
+
for (const column of table.columns) {
|
|
887
|
+
if (column.isPrimaryKey) {
|
|
888
|
+
continue;
|
|
889
|
+
}
|
|
890
|
+
for (const target of pkTargets) {
|
|
891
|
+
if (column.name !== target.pkColumn) {
|
|
892
|
+
continue;
|
|
893
|
+
}
|
|
894
|
+
// Skip cross-instance suggestions: tables from different DB instances
|
|
895
|
+
// should not be suggested as FK candidates.
|
|
896
|
+
if (table.instance && target.instance && table.instance !== target.instance) {
|
|
897
|
+
continue;
|
|
898
|
+
}
|
|
899
|
+
const identity = referenceIdentity(fromTableKey, [column.name], target.tableKey, [target.pkColumn]);
|
|
900
|
+
if (definedKeys.has(identity)) {
|
|
901
|
+
continue;
|
|
902
|
+
}
|
|
903
|
+
if (tableByKey.get(target.tableKey) == null) {
|
|
904
|
+
continue;
|
|
905
|
+
}
|
|
906
|
+
const suggested = {
|
|
907
|
+
direction: 'outgoing',
|
|
908
|
+
source: 'suggested',
|
|
909
|
+
fromTableKey,
|
|
910
|
+
fromTableComment: (_b = (_a = tableByKey.get(fromTableKey)) === null || _a === void 0 ? void 0 : _a.tableComment) !== null && _b !== void 0 ? _b : '',
|
|
911
|
+
targetTableKey: target.tableKey,
|
|
912
|
+
targetTableComment: (_d = (_c = tableByKey.get(target.tableKey)) === null || _c === void 0 ? void 0 : _c.tableComment) !== null && _d !== void 0 ? _d : '',
|
|
913
|
+
fromColumns: [column.name],
|
|
914
|
+
targetColumns: [target.pkColumn],
|
|
915
|
+
onDeleteAction: null,
|
|
916
|
+
onUpdateAction: null,
|
|
917
|
+
fromSchemaSlug: table.schemaSlug,
|
|
918
|
+
fromTableSlug: table.tableSlug,
|
|
919
|
+
targetSchemaSlug: target.schemaSlug,
|
|
920
|
+
targetTableSlug: target.tableSlug,
|
|
921
|
+
matchRule: 'exact',
|
|
922
|
+
expression: formatReferenceExpression([column.name], target.tableKey, [target.pkColumn]),
|
|
923
|
+
};
|
|
924
|
+
table.outgoingReferences.push(suggested);
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
table.outgoingReferences = dedupeDocReferences(table.outgoingReferences).sort(sortReferences);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
function dedupeDocReferences(references) {
|
|
931
|
+
var _a, _b;
|
|
932
|
+
const map = new Map();
|
|
933
|
+
for (const reference of references) {
|
|
934
|
+
const key = `${reference.source}|${reference.fromTableKey}|${reference.fromColumns.join(',')}|${reference.targetTableKey}|${reference.targetColumns.join(',')}|${(_a = reference.onDeleteAction) !== null && _a !== void 0 ? _a : ''}|${(_b = reference.onUpdateAction) !== null && _b !== void 0 ? _b : ''}`;
|
|
935
|
+
map.set(key, reference);
|
|
936
|
+
}
|
|
937
|
+
return Array.from(map.values());
|
|
938
|
+
}
|
|
939
|
+
function referenceIdentity(fromTableKey, fromColumns, targetTableKey, targetColumns) {
|
|
940
|
+
return `${fromTableKey}|${fromColumns.join(',')}|${targetTableKey}|${targetColumns.join(',')}`;
|
|
941
|
+
}
|
|
942
|
+
function formatReferenceExpression(fromColumns, targetTableKey, targetColumns, onDeleteAction = null, onUpdateAction = null) {
|
|
943
|
+
const clauses = [];
|
|
944
|
+
if (onDeleteAction) {
|
|
945
|
+
clauses.push(`ON DELETE ${onDeleteAction.toUpperCase()}`);
|
|
946
|
+
}
|
|
947
|
+
if (onUpdateAction) {
|
|
948
|
+
clauses.push(`ON UPDATE ${onUpdateAction.toUpperCase()}`);
|
|
949
|
+
}
|
|
950
|
+
const actionSuffix = clauses.length > 0 ? ` ${clauses.join(' ')}` : '';
|
|
951
|
+
return `${fromColumns.join(', ')} -> ${targetTableKey}(${targetColumns.join(', ') || '?'})${actionSuffix}`;
|
|
952
|
+
}
|
|
953
|
+
function dedupeConstraints(constraints) {
|
|
954
|
+
const map = new Map();
|
|
955
|
+
for (const constraint of constraints) {
|
|
956
|
+
const key = `${constraint.kind}|${constraint.name}|${constraint.expression}`;
|
|
957
|
+
map.set(key, constraint);
|
|
958
|
+
}
|
|
959
|
+
return Array.from(map.values()).sort(sortConstraints);
|
|
960
|
+
}
|
|
961
|
+
function dedupeReferences(references) {
|
|
962
|
+
var _a, _b;
|
|
963
|
+
const map = new Map();
|
|
964
|
+
for (const reference of references) {
|
|
965
|
+
const key = `${reference.fromTableKey}|${reference.fromColumns.join(',')}|${reference.targetTableKey}|${reference.targetColumns.join(',')}|${(_a = reference.onDeleteAction) !== null && _a !== void 0 ? _a : ''}|${(_b = reference.onUpdateAction) !== null && _b !== void 0 ? _b : ''}`;
|
|
966
|
+
map.set(key, reference);
|
|
967
|
+
}
|
|
968
|
+
return Array.from(map.values()).sort((a, b) => {
|
|
969
|
+
var _a, _b, _c, _d;
|
|
970
|
+
const left = `${a.fromTableKey}|${a.fromColumns.join(',')}|${a.targetTableKey}|${a.targetColumns.join(',')}|${(_a = a.onDeleteAction) !== null && _a !== void 0 ? _a : ''}|${(_b = a.onUpdateAction) !== null && _b !== void 0 ? _b : ''}`;
|
|
971
|
+
const right = `${b.fromTableKey}|${b.fromColumns.join(',')}|${b.targetTableKey}|${b.targetColumns.join(',')}|${(_c = b.onDeleteAction) !== null && _c !== void 0 ? _c : ''}|${(_d = b.onUpdateAction) !== null && _d !== void 0 ? _d : ''}`;
|
|
972
|
+
return left.localeCompare(right);
|
|
973
|
+
});
|
|
974
|
+
}
|
|
975
|
+
function sortConstraints(left, right) {
|
|
976
|
+
return `${left.kind}|${left.name}|${left.expression}`.localeCompare(`${right.kind}|${right.name}|${right.expression}`);
|
|
977
|
+
}
|
|
978
|
+
function sortWarnings(left, right) {
|
|
979
|
+
var _a, _b;
|
|
980
|
+
const leftKey = `${left.source.filePath}|${(_a = left.source.statementIndex) !== null && _a !== void 0 ? _a : 0}|${left.kind}|${left.message}`;
|
|
981
|
+
const rightKey = `${right.source.filePath}|${(_b = right.source.statementIndex) !== null && _b !== void 0 ? _b : 0}|${right.kind}|${right.message}`;
|
|
982
|
+
return leftKey.localeCompare(rightKey);
|
|
983
|
+
}
|
|
984
|
+
function sortReferences(left, right) {
|
|
985
|
+
const leftOther = left.direction === 'outgoing' ? left.targetTableKey : left.fromTableKey;
|
|
986
|
+
const rightOther = right.direction === 'outgoing' ? right.targetTableKey : right.fromTableKey;
|
|
987
|
+
return `${left.direction}|${leftOther}`.localeCompare(`${right.direction}|${rightOther}`);
|
|
988
|
+
}
|
|
989
|
+
function previewStatement(sql) {
|
|
990
|
+
const compact = sql.replace(/\s+/g, ' ').trim();
|
|
991
|
+
return compact.length > 200 ? `${compact.slice(0, 200)}...` : compact;
|
|
992
|
+
}
|
|
993
|
+
function resolveConstructorName(value) {
|
|
994
|
+
var _a;
|
|
995
|
+
if (value && typeof value === 'object' && 'constructor' in value) {
|
|
996
|
+
const ctor = value.constructor;
|
|
997
|
+
return (_a = ctor === null || ctor === void 0 ? void 0 : ctor.name) !== null && _a !== void 0 ? _a : 'Unknown';
|
|
998
|
+
}
|
|
999
|
+
return typeof value;
|
|
1000
|
+
}
|
|
1001
|
+
//# sourceMappingURL=snapshotTableDocs.js.map
|