From bd9800eb5b319afc93a228795e58729f1451be33 Mon Sep 17 00:00:00 2001 From: Huy-DNA Date: Tue, 27 Jan 2026 16:52:02 +0700 Subject: [PATCH 1/4] feat: add support for alter table with check in mssql ast generator --- .../input/with_check_add_constraint.in.sql | 23 ++++ .../output/with_check_add_constraint.out.json | 100 ++++++++++++++++++ .../ANTLR/ASTGeneration/mssql/MssqlASTGen.js | 38 +++++++ 3 files changed, 161 insertions(+) create mode 100644 packages/dbml-core/__tests__/examples/parser/mssql-parse/input/with_check_add_constraint.in.sql create mode 100644 packages/dbml-core/__tests__/examples/parser/mssql-parse/output/with_check_add_constraint.out.json diff --git a/packages/dbml-core/__tests__/examples/parser/mssql-parse/input/with_check_add_constraint.in.sql b/packages/dbml-core/__tests__/examples/parser/mssql-parse/input/with_check_add_constraint.in.sql new file mode 100644 index 000000000..285a7cbfc --- /dev/null +++ b/packages/dbml-core/__tests__/examples/parser/mssql-parse/input/with_check_add_constraint.in.sql @@ -0,0 +1,23 @@ +CREATE TABLE [users] ( + [id] int PRIMARY KEY, + [name] varchar(255) +) +GO + +CREATE TABLE [orders] ( + [id] int PRIMARY KEY, + [user_id] int +) +GO + +CREATE TABLE [audit_logs] ( + [id] int PRIMARY KEY, + [order_id] int +) +GO + +ALTER TABLE [orders] WITH CHECK ADD CONSTRAINT [FK_orders_users] FOREIGN KEY ([user_id]) REFERENCES [users] ([id]) ON DELETE CASCADE ON UPDATE SET NULL +GO + +ALTER TABLE [audit_logs] WITH NOCHECK ADD CONSTRAINT [FK_audit_logs_orders] FOREIGN KEY ([order_id]) REFERENCES [orders] ([id]) +GO diff --git a/packages/dbml-core/__tests__/examples/parser/mssql-parse/output/with_check_add_constraint.out.json b/packages/dbml-core/__tests__/examples/parser/mssql-parse/output/with_check_add_constraint.out.json new file mode 100644 index 000000000..1b42e2099 --- /dev/null +++ b/packages/dbml-core/__tests__/examples/parser/mssql-parse/output/with_check_add_constraint.out.json @@ -0,0 +1,100 @@ +{ + "tables": [ + { + "name": "users", + "fields": [ + { + "name": "id", + "type": { + "type_name": "int" + }, + "pk": true + }, + { + "name": "name", + "type": { + "type_name": "varchar(255)" + } + } + ] + }, + { + "name": "orders", + "fields": [ + { + "name": "id", + "type": { + "type_name": "int" + }, + "pk": true + }, + { + "name": "user_id", + "type": { + "type_name": "int" + } + } + ] + }, + { + "name": "audit_logs", + "fields": [ + { + "name": "id", + "type": { + "type_name": "int" + }, + "pk": true + }, + { + "name": "order_id", + "type": { + "type_name": "int" + } + } + ] + } + ], + "refs": [ + { + "name": "FK_orders_users", + "endpoints": [ + { + "tableName": "orders", + "fieldNames": [ + "user_id" + ], + "relation": "*" + }, + { + "tableName": "users", + "fieldNames": [ + "id" + ], + "relation": "1" + } + ], + "onDelete": "CASCADE", + "onUpdate": "SET NULL" + }, + { + "name": "FK_audit_logs_orders", + "endpoints": [ + { + "tableName": "audit_logs", + "fieldNames": [ + "order_id" + ], + "relation": "*" + }, + { + "tableName": "orders", + "fieldNames": [ + "id" + ], + "relation": "1" + } + ] + } + ] +} diff --git a/packages/dbml-core/src/parse/ANTLR/ASTGeneration/mssql/MssqlASTGen.js b/packages/dbml-core/src/parse/ANTLR/ASTGeneration/mssql/MssqlASTGen.js index d4c383030..fa83794c0 100644 --- a/packages/dbml-core/src/parse/ANTLR/ASTGeneration/mssql/MssqlASTGen.js +++ b/packages/dbml-core/src/parse/ANTLR/ASTGeneration/mssql/MssqlASTGen.js @@ -1076,6 +1076,44 @@ export default class MssqlASTGen extends TSqlParserVisitor { } table.checks.push(checkObj); }); + + // Handle WITH CHECK/NOCHECK ADD CONSTRAINT FK + if (ctx.WITH() && ctx.FOREIGN()) { + const constraintName = ctx.constraint ? ctx.constraint.accept(this) : ''; + const localColumns = ctx.fk.accept(this); + + // table_name()[1] is the referenced table (table_name()[0] is the table being altered) + const refTableNames = ctx.table_name()[1].accept(this); + const { schemaName: refSchemaName, tableName: refTableName } = getSchemaAndTableName(refTableNames); + + // pk is optional - if not specified, assume same column names as fk + const refColumns = ctx.pk ? ctx.pk.accept(this) : localColumns; + + const onDelete = ctx.on_delete().length > 0 ? ctx.on_delete()[0].accept(this) : null; + const onUpdate = ctx.on_update().length > 0 ? ctx.on_update()[0].accept(this) : null; + + const ref = { + name: constraintName, + endpoints: [ + { + tableName, + schemaName, + fieldNames: localColumns, + relation: '*', + }, + { + tableName: refTableName, + schemaName: refSchemaName, + fieldNames: refColumns, + relation: '1', + }, + ], + onDelete, + onUpdate, + }; + + this.data.refs.push(ref); + } } // create_index From 8ef45977e81c1acd64704fe49868882cab2cf243 Mon Sep 17 00:00:00 2001 From: Huy-DNA Date: Tue, 27 Jan 2026 19:18:29 +0700 Subject: [PATCH 2/4] fix: more precise checking for WITH CHECK/NOCHECK --- .../src/parse/ANTLR/ASTGeneration/mssql/MssqlASTGen.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dbml-core/src/parse/ANTLR/ASTGeneration/mssql/MssqlASTGen.js b/packages/dbml-core/src/parse/ANTLR/ASTGeneration/mssql/MssqlASTGen.js index fa83794c0..32fb5d244 100644 --- a/packages/dbml-core/src/parse/ANTLR/ASTGeneration/mssql/MssqlASTGen.js +++ b/packages/dbml-core/src/parse/ANTLR/ASTGeneration/mssql/MssqlASTGen.js @@ -1078,7 +1078,7 @@ export default class MssqlASTGen extends TSqlParserVisitor { }); // Handle WITH CHECK/NOCHECK ADD CONSTRAINT FK - if (ctx.WITH() && ctx.FOREIGN()) { + if (ctx.WITH() && (ctx.CHECK() || ctx.NOCHECK()) && ctx.FOREIGN()) { const constraintName = ctx.constraint ? ctx.constraint.accept(this) : ''; const localColumns = ctx.fk.accept(this); From 4ecbf3a715b9b025e9488734396c0a7be4fbb2b1 Mon Sep 17 00:00:00 2001 From: Huy-DNA Date: Tue, 27 Jan 2026 19:21:49 +0700 Subject: [PATCH 3/4] fix: WITH NOCHECK/CHECK FK name should be undefined if not defined --- .../src/parse/ANTLR/ASTGeneration/mssql/MssqlASTGen.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dbml-core/src/parse/ANTLR/ASTGeneration/mssql/MssqlASTGen.js b/packages/dbml-core/src/parse/ANTLR/ASTGeneration/mssql/MssqlASTGen.js index 32fb5d244..dd478127d 100644 --- a/packages/dbml-core/src/parse/ANTLR/ASTGeneration/mssql/MssqlASTGen.js +++ b/packages/dbml-core/src/parse/ANTLR/ASTGeneration/mssql/MssqlASTGen.js @@ -1079,7 +1079,7 @@ export default class MssqlASTGen extends TSqlParserVisitor { // Handle WITH CHECK/NOCHECK ADD CONSTRAINT FK if (ctx.WITH() && (ctx.CHECK() || ctx.NOCHECK()) && ctx.FOREIGN()) { - const constraintName = ctx.constraint ? ctx.constraint.accept(this) : ''; + const constraintName = ctx.constraint ? ctx.constraint.accept(this) : undefined; const localColumns = ctx.fk.accept(this); // table_name()[1] is the referenced table (table_name()[0] is the table being altered) From 4c4940b0344a52c3b176e92169a1f4d7ac1f64b6 Mon Sep 17 00:00:00 2001 From: Huy-DNA Date: Tue, 27 Jan 2026 19:24:02 +0700 Subject: [PATCH 4/4] fix: return in visit CHECK/NOCHECK --- .../ANTLR/ASTGeneration/mssql/MssqlASTGen.js | 65 ++++++++++--------- 1 file changed, 33 insertions(+), 32 deletions(-) diff --git a/packages/dbml-core/src/parse/ANTLR/ASTGeneration/mssql/MssqlASTGen.js b/packages/dbml-core/src/parse/ANTLR/ASTGeneration/mssql/MssqlASTGen.js index dd478127d..e09e65f12 100644 --- a/packages/dbml-core/src/parse/ANTLR/ASTGeneration/mssql/MssqlASTGen.js +++ b/packages/dbml-core/src/parse/ANTLR/ASTGeneration/mssql/MssqlASTGen.js @@ -1045,38 +1045,6 @@ export default class MssqlASTGen extends TSqlParserVisitor { const table = this.data.tables.find((t) => t.name === tableName && t.schemaName === schemaName); if (!table) return; // ALTER TABLE should appear after CREATE TABLE, so skip if table is not created yet - const columnDefTableConstraints = ctx.column_def_table_constraints() ? ctx.column_def_table_constraints().accept(this) : []; - const { - fieldsData, indexes, tableRefs, columnDefaults, checkConstraints, - } = splitColumnDefTableConstraints(columnDefTableConstraints); - - const { inlineRefs, fields } = parseFieldsAndInlineRefsFromFieldsData(fieldsData, tableName, schemaName); - this.data.refs.push(...flatten(inlineRefs)); - - this.data.refs.push(...tableRefs.map((tableRef) => { - tableRef.endpoints[0].tableName = tableName; - tableRef.endpoints[0].schemaName = schemaName; - return tableRef; - })); - - table.fields.push(...fields); - table.indexes.push(...indexes); - - columnDefaults.forEach((columnDefault) => { - const field = table.fields.find((f) => f.name === columnDefault.column); - - if (!field) return; - field.dbdefault = columnDefault.defaultValue; - }); - - checkConstraints.forEach((checkConstraint) => { - const checkObj = { expression: checkConstraint.expression }; - if (checkConstraint.name) { - checkObj.name = checkConstraint.name; - } - table.checks.push(checkObj); - }); - // Handle WITH CHECK/NOCHECK ADD CONSTRAINT FK if (ctx.WITH() && (ctx.CHECK() || ctx.NOCHECK()) && ctx.FOREIGN()) { const constraintName = ctx.constraint ? ctx.constraint.accept(this) : undefined; @@ -1113,7 +1081,40 @@ export default class MssqlASTGen extends TSqlParserVisitor { }; this.data.refs.push(ref); + return; } + + const columnDefTableConstraints = ctx.column_def_table_constraints() ? ctx.column_def_table_constraints().accept(this) : []; + const { + fieldsData, indexes, tableRefs, columnDefaults, checkConstraints, + } = splitColumnDefTableConstraints(columnDefTableConstraints); + + const { inlineRefs, fields } = parseFieldsAndInlineRefsFromFieldsData(fieldsData, tableName, schemaName); + this.data.refs.push(...flatten(inlineRefs)); + + this.data.refs.push(...tableRefs.map((tableRef) => { + tableRef.endpoints[0].tableName = tableName; + tableRef.endpoints[0].schemaName = schemaName; + return tableRef; + })); + + table.fields.push(...fields); + table.indexes.push(...indexes); + + columnDefaults.forEach((columnDefault) => { + const field = table.fields.find((f) => f.name === columnDefault.column); + + if (!field) return; + field.dbdefault = columnDefault.defaultValue; + }); + + checkConstraints.forEach((checkConstraint) => { + const checkObj = { expression: checkConstraint.expression }; + if (checkConstraint.name) { + checkObj.name = checkConstraint.name; + } + table.checks.push(checkObj); + }); } // create_index