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..6039790044 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -2462,6 +2462,38 @@ public class CCJSqlParser extends AbstractJSqlParser { } } + /** + * 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; } + } + /** * True when the pending {@code ON} opens the ON CONFLICT clause of the enclosing * INSERT rather than another trailing join condition: after a join, the trailing @@ -20568,6 +20600,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..6472bcb306 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 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", + 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 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)", + 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 testAlterTableAddColumnListSnowflake() 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')";