Skip to content

Commit bc177f4

Browse files
committed
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
1 parent 6c5fc6c commit bc177f4

3 files changed

Lines changed: 109 additions & 3 deletions

File tree

‎src/main/java/net/sf/jsqlparser/statement/alter/AlterExpression.java‎

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,9 @@ public enum TableRenameKeyword {
9090

9191
private boolean useBrackets = false;
9292

93+
// T-SQL/Snowflake ADD lists items comma-separated, without brackets.
94+
private boolean bareAddList = false;
95+
9396
private boolean useIfNotExists = false;
9497

9598
private String partitionType;
@@ -137,6 +140,14 @@ public void useBrackets(boolean useBrackets) {
137140
this.useBrackets = useBrackets;
138141
}
139142

143+
public boolean isBareAddList() {
144+
return bareAddList;
145+
}
146+
147+
public void setBareAddList(boolean bareAddList) {
148+
this.bareAddList = bareAddList;
149+
}
150+
140151
public void hasColumn(boolean hasColumn) {
141152
this.hasColumn = hasColumn;
142153
}
@@ -1638,7 +1649,7 @@ private void toStringGeneral(StringBuilder b, Consumer<ColumnDataType> columnPri
16381649
b.append(optionalSpecifier).append(" ");
16391650
}
16401651
b.append(columnOldName).append(" ");
1641-
} else if (colDataTypeList.size() > 1) {
1652+
} else if (colDataTypeList.size() > 1 && !bareAddList) {
16421653
b.append("(");
16431654
} else {
16441655
if (hasColumn) {
@@ -1664,9 +1675,14 @@ private void toStringGeneral(StringBuilder b, Consumer<ColumnDataType> columnPri
16641675
if (useBrackets && colDataTypeList.size() == 1) {
16651676
b.append(')');
16661677
}
1667-
if (colDataTypeList.size() > 1) {
1678+
if (colDataTypeList.size() > 1 && !bareAddList) {
16681679
b.append(")");
16691680
}
1681+
if (constraint != null && bareAddList) {
1682+
// T-SQL/Snowflake ADD lists may append a table constraint after
1683+
// the column definitions.
1684+
b.append(", ").append(constraint);
1685+
}
16701686
} else if (getColumnSetNotNullList() != null) {
16711687
b.append("COLUMN ");
16721688
b.append(PlainSelect.getStringList(columnSetNotNullList));

‎src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt‎

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2459,7 +2459,36 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
24592459
return true;
24602460
default:
24612461
return false;
2462-
}
2462+
2463+
* True when the item after a comma inside a T-SQL/Snowflake ADD list is
2464+
* another column definition or table constraint rather than the start of a
2465+
* new alter action, which the enclosing productions handle.
2466+
*/
2467+
private boolean isAlterAddListItemAhead() {
2468+
switch (getToken(1).kind) {
2469+
case K_CONSTRAINT:
2470+
case K_CHECK:
2471+
case K_PRIMARY:
2472+
case K_UNIQUE:
2473+
case K_FOREIGN:
2474+
return true;
2475+
case K_ADD:
2476+
case K_ALTER:
2477+
case K_CHANGE:
2478+
case K_MODIFY:
2479+
case K_DROP:
2480+
case K_FORCE:
2481+
case K_NO:
2482+
case K_ENABLE:
2483+
case K_DISABLE:
2484+
case K_ALGORITHM:
2485+
case K_LOCK:
2486+
case K_CONVERT:
2487+
case K_PARTITION:
2488+
case K_ORDER:
2489+
return false;
2490+
default:
2491+
return true; }
24632492
}
24642493

24652494
/**
@@ -20568,6 +20597,24 @@ AlterExpression AlterExpressionAddAlterModify():
2056820597
|
2056920598
LOOKAHEAD(2) alterExpressionColumnDataType = AlterExpressionColumnDataType()
2057020599
{ alterExp.addColDataType(alterExpressionColumnDataType); }
20600+
// T-SQL and Snowflake let one ADD list several columns and table
20601+
// constraints without repeating ADD.
20602+
(
20603+
LOOKAHEAD(",", { alterExp.getOperation() == AlterOperation.ADD
20604+
&& (Dialect.SQLSERVER.name().equals(getAsString(Feature.dialect))
20605+
|| Dialect.SNOWFLAKE.name().equals(getAsString(Feature.dialect)))
20606+
&& isAlterAddListItemAhead() })
20607+
"," { alterExp.setBareAddList(true); }
20608+
(
20609+
LOOKAHEAD({ getToken(1).kind == K_CONSTRAINT || getToken(1).kind == K_CHECK
20610+
|| getToken(1).kind == K_PRIMARY || getToken(1).kind == K_UNIQUE
20611+
|| getToken(1).kind == K_FOREIGN })
20612+
element=TableConstraint(false) { setAlterTableElement(alterExp, element); }
20613+
|
20614+
alterExpressionColumnDataType = AlterExpressionColumnDataType()
20615+
{ alterExp.addColDataType(alterExpressionColumnDataType); }
20616+
)
20617+
)*
2057120618
)
2057220619
)
2057320620
|

‎src/test/java/net/sf/jsqlparser/statement/alter/AlterTest.java‎

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,49 @@ public void testAlterTableAddColumn_ColumnKeyWordImplicit() throws JSQLParserExc
9797
assertEquals("varchar (255)", colDataTypes.get(0).getColDataType().toString());
9898
}
9999

100+
@Test
101+
public void testAlterTableAddColumnList_TSql() throws JSQLParserException {
102+
// T-SQL and Snowflake let one ADD list several columns without repeating ADD
103+
Alter alter = (Alter) CCJSqlParserUtil.parse(
104+
"ALTER TABLE t ADD a int, b varchar(10) NOT NULL, c int",
105+
parser -> parser.withDialect(Dialect.SQLSERVER));
106+
assertEquals(1, alter.getAlterExpressions().size());
107+
AlterExpression alterExp = alter.getAlterExpressions().get(0);
108+
assertEquals(AlterOperation.ADD, alterExp.getOperation());
109+
List<ColumnDataType> colDataTypes = alterExp.getColDataTypeList();
110+
assertEquals(3, colDataTypes.size());
111+
assertEquals("a", colDataTypes.get(0).getColumnName());
112+
assertEquals("b", colDataTypes.get(1).getColumnName());
113+
assertEquals("varchar (10)", colDataTypes.get(1).getColDataType().toString());
114+
assertEquals("c", colDataTypes.get(2).getColumnName());
115+
assertDeparse(alter, "ALTER TABLE t ADD a int, b varchar (10) NOT NULL, c int");
116+
}
117+
118+
@Test
119+
public void testAlterTableAddColumnListWithConstraint_TSql() throws JSQLParserException {
120+
// T-SQL mixes column definitions and table constraints in one ADD list
121+
Alter alter = (Alter) CCJSqlParserUtil.parse(
122+
"ALTER TABLE t ADD a int, CONSTRAINT ck CHECK (a > 0)",
123+
parser -> parser.withDialect(Dialect.SQLSERVER));
124+
assertEquals(1, alter.getAlterExpressions().size());
125+
AlterExpression alterExp = alter.getAlterExpressions().get(0);
126+
assertEquals(AlterOperation.ADD, alterExp.getOperation());
127+
assertEquals(1, alterExp.getColDataTypeList().size());
128+
assertNotNull(alterExp.getConstraint());
129+
}
130+
131+
@Test
132+
public void testAlterTableAddColumnList_Snowflake() throws JSQLParserException {
133+
Alter alter = (Alter) CCJSqlParserUtil.parse(
134+
"ALTER TABLE t ADD COLUMN a int, b int",
135+
parser -> parser.withDialect(Dialect.SNOWFLAKE));
136+
assertEquals(1, alter.getAlterExpressions().size());
137+
List<ColumnDataType> colDataTypes =
138+
alter.getAlterExpressions().get(0).getColDataTypeList();
139+
assertEquals(2, colDataTypes.size());
140+
assertDeparse(alter, "ALTER TABLE t ADD COLUMN a int, b int");
141+
}
142+
100143
@Test
101144
public void testAlterTableBackBrackets() throws JSQLParserException {
102145
String sql = "ALTER TABLE tablename add column (field string comment 'aaaaa')";

0 commit comments

Comments
 (0)