Skip to content

Commit 8ade7b0

Browse files
Parse T-SQL and Snowflake multi-item ADD lists (#2839)
* 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 * style: rename new tests to camelCase for Codacy
1 parent d1de84c commit 8ade7b0

3 files changed

Lines changed: 111 additions & 2 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: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2510,6 +2510,38 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
25102510
}
25112511
}
25122512

2513+
/**
2514+
* True when the item after a comma inside a T-SQL/Snowflake ADD list is
2515+
* another column definition or table constraint rather than the start of a
2516+
* new alter action, which the enclosing productions handle.
2517+
*/
2518+
private boolean isAlterAddListItemAhead() {
2519+
switch (getToken(1).kind) {
2520+
case K_CONSTRAINT:
2521+
case K_CHECK:
2522+
case K_PRIMARY:
2523+
case K_UNIQUE:
2524+
case K_FOREIGN:
2525+
return true;
2526+
case K_ADD:
2527+
case K_ALTER:
2528+
case K_CHANGE:
2529+
case K_MODIFY:
2530+
case K_DROP:
2531+
case K_FORCE:
2532+
case K_NO:
2533+
case K_ENABLE:
2534+
case K_DISABLE:
2535+
case K_ALGORITHM:
2536+
case K_LOCK:
2537+
case K_CONVERT:
2538+
case K_PARTITION:
2539+
case K_ORDER:
2540+
return false;
2541+
default:
2542+
return true; }
2543+
}
2544+
25132545
/**
25142546
* True when the pending {@code ON} opens the ON CONFLICT clause of the enclosing
25152547
* INSERT rather than another trailing join condition: after a join, the trailing
@@ -20802,6 +20834,24 @@ AlterExpression AlterExpressionAddAlterModify():
2080220834
|
2080320835
LOOKAHEAD(2) alterExpressionColumnDataType = AlterExpressionColumnDataType()
2080420836
{ alterExp.addColDataType(alterExpressionColumnDataType); }
20837+
// T-SQL and Snowflake let one ADD list several columns and table
20838+
// constraints without repeating ADD.
20839+
(
20840+
LOOKAHEAD(",", { alterExp.getOperation() == AlterOperation.ADD
20841+
&& (Dialect.SQLSERVER.name().equals(getAsString(Feature.dialect))
20842+
|| Dialect.SNOWFLAKE.name().equals(getAsString(Feature.dialect)))
20843+
&& isAlterAddListItemAhead() })
20844+
"," { alterExp.setBareAddList(true); }
20845+
(
20846+
LOOKAHEAD({ getToken(1).kind == K_CONSTRAINT || getToken(1).kind == K_CHECK
20847+
|| getToken(1).kind == K_PRIMARY || getToken(1).kind == K_UNIQUE
20848+
|| getToken(1).kind == K_FOREIGN })
20849+
element=TableConstraint(false) { setAlterTableElement(alterExp, element); }
20850+
|
20851+
alterExpressionColumnDataType = AlterExpressionColumnDataType()
20852+
{ alterExp.addColDataType(alterExpressionColumnDataType); }
20853+
)
20854+
)*
2080520855
)
2080620856
)
2080720857
|

‎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 testAlterTableAddColumnListTSql() 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 testAlterTableAddColumnListWithConstraintTSql() 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 testAlterTableAddColumnListSnowflake() 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)