Skip to content

Commit 9134634

Browse files
committed
refactor: simplify create view visitor
1 parent 209e604 commit 9134634

8 files changed

Lines changed: 159 additions & 178 deletions

File tree

packages/dbml-core/src/export/DbmlExporter.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { groupBy, isEmpty, reduce } from 'lodash-es';
2-
import { addDoubleQuoteIfNeeded, escapeString, formatRecordValue } from '@dbml/parse';
2+
import { addDoubleQuoteIfNeeded, escapeString, formatRecordValue, normalizeQualifiedName } from '@dbml/parse';
33
import { shouldPrintSchema } from './utils';
44
import { DEFAULT_SCHEMA_NAME } from '../model_structure/config';
55
import type { NormalizedModel, RecordValue } from '../../types/model_structure/database';
@@ -444,7 +444,7 @@ class DbmlExporter {
444444

445445
// Build table reference
446446
const tableRef = schemaName
447-
? `${addDoubleQuoteIfNeeded(schemaName)}.${addDoubleQuoteIfNeeded(tableName)}`
447+
? normalizeQualifiedName(schemaName, tableName)
448448
: addDoubleQuoteIfNeeded(tableName);
449449

450450
// Collect all unique columns in order

packages/dbml-core/src/parse/ANTLR/ASTGeneration/mssql/MssqlASTGen.js

Lines changed: 31 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,20 @@
11
import {
22
first, flatten, flattenDepth, last, nth,
33
} from 'lodash-es';
4+
import { ParseTreeListener, ParseTreeWalker } from 'antlr4';
45
import TSqlParserVisitor from '../../parsers/mssql/TSqlParserVisitor';
6+
import TSqlParser from '../../parsers/mssql/TSqlParser';
57
import {
68
Field, Index, Table, TableRecord,
79
} from '../AST';
810
import { COLUMN_CONSTRAINT_KIND, DATA_TYPE, TABLE_CONSTRAINT_KIND } from '../constants';
911
import { getOriginalText } from '../helpers';
12+
import { normalizeQualifiedName } from '@dbml/parse';
1013

1114
const ADD_DESCRIPTION_FUNCTION_NAME = 'sp_addextendedproperty';
1215

1316
const DEFAULT_SCHEMA = 'dbo';
1417

15-
function extractSourceTablesFromMssqlCtx (ctx) {
16-
if (!ctx) return [];
17-
const seen = new Map();
18-
const stack = [ctx];
19-
while (stack.length > 0) {
20-
const node = stack.pop();
21-
if (!node) continue;
22-
const className = node.constructor && node.constructor.name;
23-
if (className === 'Full_table_nameContext') {
24-
const text = (node.getText && node.getText()) || '';
25-
const cleaned = text.replace(/\[|\]|"|`/g, '').split(/\s+/)[0];
26-
const parts = cleaned.split('.');
27-
const tableName = parts[parts.length - 1];
28-
const schemaName = parts.length > 1 ? parts[parts.length - 2] : undefined;
29-
const key = `${schemaName || DEFAULT_SCHEMA}.${tableName}`;
30-
if (tableName && !seen.has(key)) {
31-
seen.set(key, { name: tableName, schemaName });
32-
}
33-
}
34-
const childCount = typeof node.getChildCount === 'function' ? node.getChildCount() : 0;
35-
for (let i = 0; i < childCount; i++) {
36-
stack.push(node.getChild(i));
37-
}
38-
}
39-
return Array.from(seen.values());
40-
}
41-
4218
const getSchemaAndTableName = (names) => {
4319
const tableName = last(names);
4420
const schemaName = names.length > 1 ? nth(names, -2) : undefined;
@@ -158,29 +134,45 @@ export default class MssqlASTGen extends TSqlParserVisitor {
158134
headerColor: null,
159135
custom: { query: ddl, kind: 'view' },
160136
});
161-
this._emitDepsFromViewSelect(ctx, tableName, schemaName);
162-
}
137+
// Walk the SELECT body to find source tables and emit dep edges
138+
const selectCtx = ctx.select_statement_standalone();
139+
if (!selectCtx) return;
163140

164-
_emitDepsFromViewSelect (ctx, viewName, viewSchemaName) {
165-
const sources = extractSourceTablesFromMssqlCtx(ctx);
166-
sources.forEach((src) => {
167-
if (src.name === viewName && (src.schemaName || DEFAULT_SCHEMA) === (viewSchemaName || DEFAULT_SCHEMA)) return;
168-
if (!this.findTable(src.schemaName, src.name)) return;
141+
const seenTables = new Set();
142+
const viewKey = normalizeQualifiedName(schemaName ?? DEFAULT_SCHEMA, tableName);
143+
144+
// Collect all deps of this view
145+
const collector = new ParseTreeListener();
146+
collector.enterEveryRule = (node) => {
147+
// Only handle full table name
148+
if (!(node instanceof TSqlParser.Full_table_nameContext)) return;
149+
150+
const names = node.accept(this);
151+
const { tableName: srcTable, schemaName: srcSchema } = getSchemaAndTableName(names);
152+
153+
// Skip self-references and duplicates
154+
const key = normalizeQualifiedName(srcSchema ?? DEFAULT_SCHEMA, srcTable);
155+
if (key === viewKey) return;
156+
if (seenTables.has(key)) return;
157+
if (!this.findTable(srcSchema, srcTable)) return;
158+
159+
seenTables.add(key);
169160
this.data.deps.push({
170161
edges: [{
171-
upstream: { schemaName: src.schemaName, tableName: src.name, fieldNames: [] },
172-
downstream: { schemaName: viewSchemaName, tableName: viewName, fieldNames: [] },
162+
upstream: { schemaName: srcSchema, tableName: srcTable, fieldNames: [] },
163+
downstream: { schemaName, tableName, fieldNames: [] },
173164
}],
174165
note: null,
175166
custom: {},
176167
});
177-
});
168+
};
169+
ParseTreeWalker.DEFAULT.walk(collector, selectCtx);
178170
}
179171

180172
findTable (schemaName, tableName) {
181-
const realSchemaName = schemaName || DEFAULT_SCHEMA;
173+
const realSchemaName = schemaName ?? DEFAULT_SCHEMA;
182174
const table = this.data.tables.find((t) => {
183-
const targetSchemaName = t.schemaName || DEFAULT_SCHEMA;
175+
const targetSchemaName = t.schemaName ?? DEFAULT_SCHEMA;
184176
return targetSchemaName === realSchemaName && t.name === tableName;
185177
});
186178
return table;

packages/dbml-core/src/parse/ANTLR/ASTGeneration/mysql/MySQLASTGen.js

Lines changed: 31 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { flatten, flattenDepth, last } from 'lodash-es';
2+
import { ParseTreeListener, ParseTreeWalker } from 'antlr4';
23
import MySQLParserVisitor from '../../parsers/mysql/MySqlParserVisitor';
4+
import MySqlParser from '../../parsers/mysql/MySqlParser';
35
import {
46
Endpoint, Enum, Field, Index, Ref, Table,
57
TableRecord,
@@ -8,33 +10,8 @@ import {
810
COLUMN_CONSTRAINT_KIND, CONSTRAINT_TYPE, DATA_TYPE, TABLE_CONSTRAINT_KIND,
911
} from '../constants';
1012
import { getOriginalText } from '../helpers';
11-
12-
function extractSourceTablesFromCtx (ctx, refClassNames) {
13-
if (!ctx) return [];
14-
const seen = new Map();
15-
const stack = [ctx];
16-
while (stack.length > 0) {
17-
const node = stack.pop();
18-
if (!node) continue;
19-
const className = node.constructor && node.constructor.name;
20-
if (className && refClassNames.includes(className)) {
21-
const text = (node.getText && node.getText()) || '';
22-
const cleaned = text.replace(/`/g, '').replace(/"/g, '').split(/\s+/)[0];
23-
const parts = cleaned.split('.');
24-
const tableName = parts[parts.length - 1];
25-
const schemaName = parts.length > 1 ? parts[parts.length - 2] : undefined;
26-
const key = `${schemaName || 'public'}.${tableName}`;
27-
if (tableName && !seen.has(key)) {
28-
seen.set(key, { name: tableName, schemaName });
29-
}
30-
}
31-
const childCount = typeof node.getChildCount === 'function' ? node.getChildCount() : 0;
32-
for (let i = 0; i < childCount; i++) {
33-
stack.push(node.getChild(i));
34-
}
35-
}
36-
return Array.from(seen.values());
37-
}
13+
import { DEFAULT_SCHEMA_NAME } from '../../../../model_structure/config';
14+
import { normalizeQualifiedName } from '@dbml/parse';
3815

3916
const TABLE_OPTIONS_KIND = {
4017
NOTE: 'note',
@@ -104,30 +81,44 @@ export default class MySQLASTGen extends MySQLParserVisitor {
10481
headerColor: null,
10582
custom: { query: ddl, kind: 'view' },
10683
});
107-
this._emitDepsFromViewSelect(ctx, tableName, schemaName);
108-
}
84+
// Walk the SELECT body to find source tables and emit dep edges
85+
const selectCtx = ctx.selectStatement();
86+
if (!selectCtx) return;
10987

110-
_emitDepsFromViewSelect (ctx, viewName, viewSchemaName) {
111-
const sources = extractSourceTablesFromCtx(ctx, ['TableNameContext', 'TableSourceItemContext']);
112-
sources.forEach((src) => {
113-
if (src.name === viewName && (src.schemaName || 'public') === (viewSchemaName || 'public')) return;
114-
if (!this.findTable(src.schemaName, src.name)) return;
88+
const seenTables = new Set();
89+
90+
// Collect all deps of this view
91+
const collector = new ParseTreeListener();
92+
collector.enterEveryRule = (node) => {
93+
// Only handle table name
94+
if (!(node instanceof MySqlParser.TableNameContext)) return;
95+
96+
const names = node.accept(this);
97+
const { tableName: srcTable, schemaName: srcSchema } = getTableNames(names);
98+
99+
// Skip self-references and duplicates
100+
const key = normalizeQualifiedName(srcSchema ?? DEFAULT_SCHEMA_NAME, srcTable);
101+
if (key === normalizeQualifiedName(schemaName ?? DEFAULT_SCHEMA_NAME, tableName)) return;
102+
if (seenTables.has(key)) return;
103+
if (!this.findTable(srcSchema, srcTable)) return;
104+
105+
seenTables.add(key);
115106
this.data.deps.push({
116107
edges: [{
117-
upstream: { schemaName: src.schemaName, tableName: src.name, fieldNames: [] },
118-
downstream: { schemaName: viewSchemaName, tableName: viewName, fieldNames: [] },
108+
upstream: { schemaName: srcSchema, tableName: srcTable, fieldNames: [] },
109+
downstream: { schemaName, tableName, fieldNames: [] },
119110
}],
120111
note: null,
121112
custom: {},
122113
});
123-
});
114+
};
115+
ParseTreeWalker.DEFAULT.walk(collector, selectCtx);
124116
}
125117

126-
// TODO: support configurable default schema name other than 'public'
127118
findTable (schemaName, tableName) {
128-
const realSchemaName = schemaName || 'public';
119+
const realSchemaName = schemaName ?? DEFAULT_SCHEMA_NAME;
129120
const table = this.data.tables.find((t) => {
130-
const targetSchemaName = t.schemaName || 'public';
121+
const targetSchemaName = t.schemaName ?? DEFAULT_SCHEMA_NAME;
131122
return targetSchemaName === realSchemaName && t.name === tableName;
132123
});
133124
return table;

packages/dbml-core/src/parse/ANTLR/ASTGeneration/oraclesql/OracleSQLASTGen.js

Lines changed: 43 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { flatten, last } from 'lodash-es';
2+
import { ParseTreeListener, ParseTreeWalker } from 'antlr4';
23
import { CompilerError } from '../../../error';
34
import OracleSqlParserVisitor from '../../parsers/oraclesql/OracleSqlParserVisitor';
5+
import OracleSqlParser from '../../parsers/oraclesql/OracleSqlParser';
46
import {
57
Endpoint,
68
Field,
@@ -14,33 +16,7 @@ import {
1416
DATA_TYPE,
1517
} from '../constants';
1618
import { getOriginalText } from '../helpers';
17-
18-
function extractSourceTablesFromOracleCtx (ctx) {
19-
if (!ctx) return [];
20-
const seen = new Map();
21-
const stack = [ctx];
22-
while (stack.length > 0) {
23-
const node = stack.pop();
24-
if (!node) continue;
25-
const className = node.constructor && node.constructor.name;
26-
if (className === 'Tableview_nameContext' || className === 'DML_table_expression_clauseContext') {
27-
const text = (node.getText && node.getText()) || '';
28-
const cleaned = text.replace(/"/g, '').split(/\s+/)[0];
29-
const parts = cleaned.split('.');
30-
const tableName = parts[parts.length - 1];
31-
const schemaName = parts.length > 1 ? parts[parts.length - 2] : undefined;
32-
const key = `${schemaName || ''}.${tableName}`;
33-
if (tableName && !seen.has(key)) {
34-
seen.set(key, { name: tableName, schemaName });
35-
}
36-
}
37-
const childCount = typeof node.getChildCount === 'function' ? node.getChildCount() : 0;
38-
for (let i = 0; i < childCount; i++) {
39-
stack.push(node.getChild(i));
40-
}
41-
}
42-
return Array.from(seen.values());
43-
}
19+
import { normalizeQualifiedName } from '@dbml/parse';
4420

4521
// We cannot use TABLE_CONSTRAINT_KIND and COLUMN_CONSTRAINT_KIND from '../constants' as their values are indistinguishable from each other
4622
// For example: TABLE_CONSTRAINT_KIND.UNIQUE === COLUMN_CONSTRAINT_KIND.UNIQUE
@@ -68,9 +44,9 @@ const COLUMN_CONSTRAINT_KIND = {
6844
};
6945

7046
const findTable = (tables, schemaName, tableName) => {
71-
const realSchemaName = schemaName || null;
47+
const realSchemaName = schemaName ?? null;
7248
const table = tables.find((table) => {
73-
const targetSchemaName = table.schemaName || null;
49+
const targetSchemaName = table.schemaName ?? null;
7450
return targetSchemaName === realSchemaName && table.name === tableName;
7551
});
7652
return table;
@@ -170,7 +146,7 @@ export default class OracleSqlASTGen extends OracleSqlParserVisitor {
170146
headerColor: null,
171147
custom: { query: ddl, kind: 'view' },
172148
});
173-
this._emitDepsFromViewSelect(ctx, tableName, schemaName);
149+
collectViewDeps(this, ctx.select_only_statement(), tableName, schemaName);
174150
}
175151

176152
visitCreate_materialized_view (ctx) {
@@ -190,23 +166,7 @@ export default class OracleSqlASTGen extends OracleSqlParserVisitor {
190166
headerColor: null,
191167
custom: { query: ddl, kind: 'materialized_view', materialized: true },
192168
});
193-
this._emitDepsFromViewSelect(ctx, tableName, schemaName);
194-
}
195-
196-
_emitDepsFromViewSelect (ctx, viewName, viewSchemaName) {
197-
const sources = extractSourceTablesFromOracleCtx(ctx);
198-
sources.forEach((src) => {
199-
if (src.name === viewName && (src.schemaName || '') === (viewSchemaName || '')) return;
200-
if (!findTable(this.data.tables, src.schemaName, src.name)) return;
201-
this.data.deps.push({
202-
edges: [{
203-
upstream: { schemaName: src.schemaName, tableName: src.name, fieldNames: [] },
204-
downstream: { schemaName: viewSchemaName, tableName: viewName, fieldNames: [] },
205-
}],
206-
note: null,
207-
custom: {},
208-
});
209-
});
169+
collectViewDeps(this, ctx.select_only_statement(), tableName, schemaName);
210170
}
211171

212172
// sql_script
@@ -1144,3 +1104,39 @@ export default class OracleSqlASTGen extends OracleSqlParserVisitor {
11441104
return unquoteString(getOriginalText(ctx), '"');
11451105
}
11461106
}
1107+
1108+
// Walk a SELECT subtree to find source tables and emit dep edges
1109+
function collectViewDeps (visitor, selectCtx, viewName, viewSchemaName) {
1110+
if (!selectCtx) return;
1111+
1112+
const seenTables = new Set();
1113+
const viewKey = normalizeQualifiedName(viewSchemaName ?? '', viewName);
1114+
1115+
// Collect all deps of this view
1116+
const collector = new ParseTreeListener();
1117+
collector.enterEveryRule = (node) => {
1118+
// Only handle table/view name references
1119+
if (!(node instanceof OracleSqlParser.Tableview_nameContext)) return;
1120+
1121+
const names = node.accept(visitor);
1122+
const srcTable = Array.isArray(names) ? last(names) : names;
1123+
const srcSchema = Array.isArray(names) && names.length > 1 ? names[names.length - 2] : undefined;
1124+
1125+
// Skip self-references and duplicates
1126+
const key = normalizeQualifiedName(srcSchema ?? '', srcTable);
1127+
if (key === viewKey) return;
1128+
if (seenTables.has(key)) return;
1129+
if (!findTable(visitor.data.tables, srcSchema, srcTable)) return;
1130+
1131+
seenTables.add(key);
1132+
visitor.data.deps.push({
1133+
edges: [{
1134+
upstream: { schemaName: srcSchema, tableName: srcTable, fieldNames: [] },
1135+
downstream: { schemaName: viewSchemaName, tableName: viewName, fieldNames: [] },
1136+
}],
1137+
note: null,
1138+
custom: {},
1139+
});
1140+
};
1141+
ParseTreeWalker.DEFAULT.walk(collector, selectCtx);
1142+
}

0 commit comments

Comments
 (0)