From bc177f4a78c6065039bfde85a04ecda529b20a44 Mon Sep 17 00:00:00 2001 From: princejain756 Date: Wed, 21 Oct 2026 09:34:28 +0530 Subject: [PATCH 1/2] Parse T-SQL and Snowflake multi-item ADD lists ALTER TABLE t ADD a int, b int parsed only the first column; the rest fell into a single UNSPECIFIC expression of raw text. T-SQL and Snowflake let one ADD list several column definitions and table constraints without repeating ADD. The ADD branch now loops on comma-separated items under the SQLSERVER and SNOWFLAKE dialects: each item is a column definition or a table constraint. A new bareAddList flag keeps the deparser from re-adding brackets T-SQL does not accept, and lets it emit a trailing constraint after the column list. Fixes #2837 --- .../statement/alter/AlterExpression.java | 20 +++++++- .../net/sf/jsqlparser/parser/JSqlParserCC.jjt | 49 ++++++++++++++++++- .../jsqlparser/statement/alter/AlterTest.java | 43 ++++++++++++++++ 3 files changed, 109 insertions(+), 3 deletions(-) diff --git a/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpression.java b/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpression.java index 0aaeb07970..c82a12100d 100644 --- a/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpression.java +++ b/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpression.java @@ -90,6 +90,9 @@ public enum TableRenameKeyword { private boolean useBrackets = false; + // T-SQL/Snowflake ADD lists items comma-separated, without brackets. + private boolean bareAddList = false; + private boolean useIfNotExists = false; private String partitionType; @@ -137,6 +140,14 @@ public void useBrackets(boolean useBrackets) { this.useBrackets = useBrackets; } + public boolean isBareAddList() { + return bareAddList; + } + + public void setBareAddList(boolean bareAddList) { + this.bareAddList = bareAddList; + } + public void hasColumn(boolean hasColumn) { this.hasColumn = hasColumn; } @@ -1638,7 +1649,7 @@ private void toStringGeneral(StringBuilder b, Consumer columnPri b.append(optionalSpecifier).append(" "); } b.append(columnOldName).append(" "); - } else if (colDataTypeList.size() > 1) { + } else if (colDataTypeList.size() > 1 && !bareAddList) { b.append("("); } else { if (hasColumn) { @@ -1664,9 +1675,14 @@ private void toStringGeneral(StringBuilder b, Consumer columnPri if (useBrackets && colDataTypeList.size() == 1) { b.append(')'); } - if (colDataTypeList.size() > 1) { + if (colDataTypeList.size() > 1 && !bareAddList) { b.append(")"); } + if (constraint != null && bareAddList) { + // T-SQL/Snowflake ADD lists may append a table constraint after + // the column definitions. + b.append(", ").append(constraint); + } } else if (getColumnSetNotNullList() != null) { b.append("COLUMN "); b.append(PlainSelect.getStringList(columnSetNotNullList)); diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index 97856f1072..4dc3e7fe4c 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -2459,7 +2459,36 @@ public class CCJSqlParser extends AbstractJSqlParser { return true; default: return false; - } + + * True when the item after a comma inside a T-SQL/Snowflake ADD list is + * another column definition or table constraint rather than the start of a + * new alter action, which the enclosing productions handle. + */ + private boolean isAlterAddListItemAhead() { + switch (getToken(1).kind) { + case K_CONSTRAINT: + case K_CHECK: + case K_PRIMARY: + case K_UNIQUE: + case K_FOREIGN: + return true; + case K_ADD: + case K_ALTER: + case K_CHANGE: + case K_MODIFY: + case K_DROP: + case K_FORCE: + case K_NO: + case K_ENABLE: + case K_DISABLE: + case K_ALGORITHM: + case K_LOCK: + case K_CONVERT: + case K_PARTITION: + case K_ORDER: + return false; + default: + return true; } } /** @@ -20568,6 +20597,24 @@ AlterExpression AlterExpressionAddAlterModify(): | LOOKAHEAD(2) alterExpressionColumnDataType = AlterExpressionColumnDataType() { alterExp.addColDataType(alterExpressionColumnDataType); } + // T-SQL and Snowflake let one ADD list several columns and table + // constraints without repeating ADD. + ( + LOOKAHEAD(",", { alterExp.getOperation() == AlterOperation.ADD + && (Dialect.SQLSERVER.name().equals(getAsString(Feature.dialect)) + || Dialect.SNOWFLAKE.name().equals(getAsString(Feature.dialect))) + && isAlterAddListItemAhead() }) + "," { alterExp.setBareAddList(true); } + ( + LOOKAHEAD({ getToken(1).kind == K_CONSTRAINT || getToken(1).kind == K_CHECK + || getToken(1).kind == K_PRIMARY || getToken(1).kind == K_UNIQUE + || getToken(1).kind == K_FOREIGN }) + element=TableConstraint(false) { setAlterTableElement(alterExp, element); } + | + alterExpressionColumnDataType = AlterExpressionColumnDataType() + { alterExp.addColDataType(alterExpressionColumnDataType); } + ) + )* ) ) | diff --git a/src/test/java/net/sf/jsqlparser/statement/alter/AlterTest.java b/src/test/java/net/sf/jsqlparser/statement/alter/AlterTest.java index 55baedf754..90f6a89237 100644 --- a/src/test/java/net/sf/jsqlparser/statement/alter/AlterTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/alter/AlterTest.java @@ -97,6 +97,49 @@ public void testAlterTableAddColumn_ColumnKeyWordImplicit() throws JSQLParserExc assertEquals("varchar (255)", colDataTypes.get(0).getColDataType().toString()); } + @Test + public void testAlterTableAddColumnList_TSql() throws JSQLParserException { + // T-SQL and Snowflake let one ADD list several columns without repeating ADD + Alter alter = (Alter) CCJSqlParserUtil.parse( + "ALTER TABLE t ADD a int, b varchar(10) NOT NULL, c int", + parser -> parser.withDialect(Dialect.SQLSERVER)); + assertEquals(1, alter.getAlterExpressions().size()); + AlterExpression alterExp = alter.getAlterExpressions().get(0); + assertEquals(AlterOperation.ADD, alterExp.getOperation()); + List colDataTypes = alterExp.getColDataTypeList(); + assertEquals(3, colDataTypes.size()); + assertEquals("a", colDataTypes.get(0).getColumnName()); + assertEquals("b", colDataTypes.get(1).getColumnName()); + assertEquals("varchar (10)", colDataTypes.get(1).getColDataType().toString()); + assertEquals("c", colDataTypes.get(2).getColumnName()); + assertDeparse(alter, "ALTER TABLE t ADD a int, b varchar (10) NOT NULL, c int"); + } + + @Test + public void testAlterTableAddColumnListWithConstraint_TSql() throws JSQLParserException { + // T-SQL mixes column definitions and table constraints in one ADD list + Alter alter = (Alter) CCJSqlParserUtil.parse( + "ALTER TABLE t ADD a int, CONSTRAINT ck CHECK (a > 0)", + parser -> parser.withDialect(Dialect.SQLSERVER)); + assertEquals(1, alter.getAlterExpressions().size()); + AlterExpression alterExp = alter.getAlterExpressions().get(0); + assertEquals(AlterOperation.ADD, alterExp.getOperation()); + assertEquals(1, alterExp.getColDataTypeList().size()); + assertNotNull(alterExp.getConstraint()); + } + + @Test + public void testAlterTableAddColumnList_Snowflake() throws JSQLParserException { + Alter alter = (Alter) CCJSqlParserUtil.parse( + "ALTER TABLE t ADD COLUMN a int, b int", + parser -> parser.withDialect(Dialect.SNOWFLAKE)); + assertEquals(1, alter.getAlterExpressions().size()); + List colDataTypes = + alter.getAlterExpressions().get(0).getColDataTypeList(); + assertEquals(2, colDataTypes.size()); + assertDeparse(alter, "ALTER TABLE t ADD COLUMN a int, b int"); + } + @Test public void testAlterTableBackBrackets() throws JSQLParserException { String sql = "ALTER TABLE tablename add column (field string comment 'aaaaa')"; From c3774c206e84ab6a75ba0fbd70b7572367f1f3ab Mon Sep 17 00:00:00 2001 From: princejain756 Date: Wed, 21 Oct 2026 10:52:29 +0530 Subject: [PATCH 2/2] style: rename new tests to camelCase for Codacy --- src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt | 3 +++ .../java/net/sf/jsqlparser/statement/alter/AlterTest.java | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index 4dc3e7fe4c..6039790044 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -2459,7 +2459,10 @@ public class CCJSqlParser extends AbstractJSqlParser { return true; default: return false; + } + } + /** * True when the item after a comma inside a T-SQL/Snowflake ADD list is * another column definition or table constraint rather than the start of a * new alter action, which the enclosing productions handle. diff --git a/src/test/java/net/sf/jsqlparser/statement/alter/AlterTest.java b/src/test/java/net/sf/jsqlparser/statement/alter/AlterTest.java index 90f6a89237..6472bcb306 100644 --- a/src/test/java/net/sf/jsqlparser/statement/alter/AlterTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/alter/AlterTest.java @@ -98,7 +98,7 @@ public void testAlterTableAddColumn_ColumnKeyWordImplicit() throws JSQLParserExc } @Test - public void testAlterTableAddColumnList_TSql() throws JSQLParserException { + public void testAlterTableAddColumnListTSql() throws JSQLParserException { // T-SQL and Snowflake let one ADD list several columns without repeating ADD Alter alter = (Alter) CCJSqlParserUtil.parse( "ALTER TABLE t ADD a int, b varchar(10) NOT NULL, c int", @@ -116,7 +116,7 @@ public void testAlterTableAddColumnList_TSql() throws JSQLParserException { } @Test - public void testAlterTableAddColumnListWithConstraint_TSql() throws JSQLParserException { + public void testAlterTableAddColumnListWithConstraintTSql() throws JSQLParserException { // T-SQL mixes column definitions and table constraints in one ADD list Alter alter = (Alter) CCJSqlParserUtil.parse( "ALTER TABLE t ADD a int, CONSTRAINT ck CHECK (a > 0)", @@ -129,7 +129,7 @@ public void testAlterTableAddColumnListWithConstraint_TSql() throws JSQLParserEx } @Test - public void testAlterTableAddColumnList_Snowflake() throws JSQLParserException { + public void testAlterTableAddColumnListSnowflake() throws JSQLParserException { Alter alter = (Alter) CCJSqlParserUtil.parse( "ALTER TABLE t ADD COLUMN a int, b int", parser -> parser.withDialect(Dialect.SNOWFLAKE));