diff --git a/src/main/java/net/sf/jsqlparser/schema/Column.java b/src/main/java/net/sf/jsqlparser/schema/Column.java index 3f7721ca3..a4d94eb8e 100644 --- a/src/main/java/net/sf/jsqlparser/schema/Column.java +++ b/src/main/java/net/sf/jsqlparser/schema/Column.java @@ -9,6 +9,8 @@ */ package net.sf.jsqlparser.schema; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; import net.sf.jsqlparser.expression.ArrayConstructor; import net.sf.jsqlparser.expression.Expression; @@ -24,6 +26,7 @@ public class Column extends ASTNodeAccessImpl implements Expression, MultiPartNa private String columnName; private String commentText; private ArrayConstructor arrayConstructor; + private String tableDelimiter = "."; public Column() {} @@ -33,8 +36,16 @@ public Column(Table table, String columnName) { } public Column(List nameParts) { - this(nameParts.size() > 1 ? new Table(nameParts.subList(0, nameParts.size() - 1)) : null, + this(nameParts, nameParts.size() > 1 ? Collections.nCopies(nameParts.size() - 1, ".") + : new ArrayList<>()); + } + + public Column(List nameParts, List delimiters) { + this( + nameParts.size() > 1 ? new Table(nameParts.subList(0, nameParts.size() - 1), + delimiters.subList(0, delimiters.size() - 1)) : null, nameParts.get(nameParts.size() - 1)); + setTableDelimiter(delimiters.isEmpty() ? "." : delimiters.get(delimiters.size() - 1)); } public Column(String columnName) { @@ -92,6 +103,14 @@ public void setColumnName(String string) { columnName = string; } + public String getTableDelimiter() { + return tableDelimiter; + } + + public void setTableDelimiter(String tableDelimiter) { + this.tableDelimiter = tableDelimiter; + } + @Override public String getFullyQualifiedName() { return getFullyQualifiedName(false); @@ -108,7 +127,7 @@ public String getFullyQualifiedName(boolean aliases) { } } if (fqn.length() > 0) { - fqn.append('.'); + fqn.append(tableDelimiter); } if (columnName != null) { fqn.append(columnName); @@ -157,6 +176,11 @@ public Column withCommentText(String commentText) { return this; } + public Column withTableDelimiter(String delimiter) { + this.setTableDelimiter(delimiter); + return this; + } + public void setCommentText(String commentText) { this.commentText = commentText; } diff --git a/src/main/java/net/sf/jsqlparser/schema/Table.java b/src/main/java/net/sf/jsqlparser/schema/Table.java index 744010ca4..983f5e011 100644 --- a/src/main/java/net/sf/jsqlparser/schema/Table.java +++ b/src/main/java/net/sf/jsqlparser/schema/Table.java @@ -41,6 +41,8 @@ public class Table extends ASTNodeAccessImpl implements FromItem, MultiPartName private List partItems = new ArrayList<>(); + private List partDelimiters = new ArrayList<>(); + private Alias alias; private SampleClause sampleClause; @@ -75,6 +77,17 @@ public Table(List partItems) { Collections.reverse(this.partItems); } + public Table(List partItems, List partDelimiters) { + if (partDelimiters.size() != partItems.size() - 1) { + throw new IllegalArgumentException( + "the length of the delimiters list must be 1 less than nameParts"); + } + this.partItems = new ArrayList<>(partItems); + this.partDelimiters = new ArrayList<>(partDelimiters); + Collections.reverse(this.partItems); + Collections.reverse(this.partDelimiters); + } + public Database getDatabase() { return new Database(getIndex(DATABASE_IDX)); } @@ -177,7 +190,7 @@ public String getFullyQualifiedName() { } fqn.append(part); if (i != 0) { - fqn.append("."); + fqn.append(partDelimiters.isEmpty() ? "." : partDelimiters.get(i - 1)); } } @@ -299,4 +312,8 @@ public Table withSqlServerHints(SQLServerHints sqlServerHints) { public List getNameParts() { return partItems; } + + public List getNamePartDelimiters() { + return partDelimiters; + } } diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/ExpressionDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/ExpressionDeParser.java index eb1a44e69..310e7a56c 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/ExpressionDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/ExpressionDeParser.java @@ -571,7 +571,7 @@ public void visit(Column tableColumn) { } } if (tableName != null && !tableName.isEmpty()) { - buffer.append(tableName).append("."); + buffer.append(tableName).append(tableColumn.getTableDelimiter()); } buffer.append(tableColumn.getColumnName()); diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index 6880916b7..3f0cb1f4c 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -102,6 +102,25 @@ public class CCJSqlParser extends AbstractJSqlParser { public Node getASTRoot() { return jjtree.rootNode(); } + + private static class ObjectNames { + + private final List names; + private final List delimiters; + + public ObjectNames(List names, List delimiters) { + this.names = names; + this.delimiters = delimiters; + } + + public List getNames() { + return names; + } + + public List getDelimiters() { + return delimiters; + } + } } PARSER_END(CCJSqlParser) @@ -1886,33 +1905,38 @@ MergeOperation MergeWhenNotMatched() : { { return mi; } } -List RelObjectNameList() : { +ObjectNames RelObjectNames() : { String token = null; + Token delimiter = null; List data = new ArrayList(); + List delimiters = new ArrayList(); } { token = RelObjectNameExt() { data.add(token); } - ( LOOKAHEAD (2) ("." | ":") ("." { data.add(null); })* token = RelObjectNameExt2() { data.add(token); } ) * + ( + LOOKAHEAD (2) ( delimiter = "." | delimiter = ":" ) { delimiters.add(delimiter.image); } (( delimiter = "." | delimiter = ":" ) { data.add(null); delimiters.add(delimiter.image); })* + token = RelObjectNameExt2() { data.add(token); } + ) * - { return data; } + { return new ObjectNames(data, delimiters); } } // See: http://technet.microsoft.com/en-us/library/ms187879%28v=sql.105%29.aspx Column Column() #Column : { - List data = new ArrayList(); + ObjectNames data = null; ArrayConstructor arrayConstructor = null; Token tk = null; } { - data = RelObjectNameList() + data = RelObjectNames() [ LOOKAHEAD(2) tk= ] // @todo: we better should return a SEQUENCE instead of a COLUMN - [ "." { data.add("nextval"); } ] + [ "." { data.getNames().add("nextval"); } ] [ LOOKAHEAD(2) arrayConstructor = ArrayConstructor(false) ] { - Column col = new Column(data); + Column col = new Column(data.getNames(), data.getDelimiters()); if (tk != null) { col.withCommentText(tk.image); } if (arrayConstructor!=null) { col.setArrayConstructor(arrayConstructor); @@ -2004,13 +2028,13 @@ String RelObjectNameExt2(): Table Table() #TableName : { //String serverName = null, databaseName = null, schemaName = null, tableName = null; - List data = new ArrayList(); + ObjectNames data = null; } { - data = RelObjectNameList() + data = RelObjectNames() { - Table table = new Table(data); + Table table = new Table(data.getNames()); linkAST(table,jjtThis); return table; } @@ -4545,13 +4569,13 @@ ConnectByRootOperator ConnectByRootOperator() #ConnectByRootOperator: { } NextValExpression NextValExpression() : { - List data = new ArrayList(); + ObjectNames data = null; Token token; } { - token= data = RelObjectNameList() + token= data = RelObjectNames() { - return new NextValExpression(data, token.image); + return new NextValExpression(data.getNames(), token.image); } } @@ -5350,7 +5374,7 @@ EqualsTo VariableExpression(): { Execute Execute(): { Token token; - List funcName; + ObjectNames funcName; ExpressionList expressionList = null; Execute execute = new Execute(); List namedExprList; @@ -5361,7 +5385,7 @@ Execute Execute(): { | { execute.setExecType(Execute.ExecType.EXECUTE); } | { execute.setExecType(Execute.ExecType.CALL); } ) - funcName=RelObjectNameList() { execute.setName(funcName); } + funcName=RelObjectNames() { execute.setName(funcName.getNames()); } ( LOOKAHEAD(2) expressionList=ExpressionList() { execute.setExprList(expressionList); } @@ -5483,13 +5507,13 @@ Function SpecialStringFunctionWithNamedParameters() : Function SimpleFunction(): { Function function = new Function(); - List name; + ObjectNames name; Expression expr=null; Expression attributeExpression = null; Column attributeColumn = null; } { - name = RelObjectNameList() + name = RelObjectNames() "(" [ ( @@ -5508,7 +5532,7 @@ Function SimpleFunction(): ] ")" { - function.setName(name); + function.setName(name.getNames()); if (expr!=null) { function.setParameters(expr); } @@ -5533,7 +5557,7 @@ Function InternalFunction(boolean escaped): { Token prefixToken = null; Function retval = new Function(); - List funcName; + ObjectNames funcName; ExpressionList expressionList = null; KeepExpression keep = null; Expression expr = null; @@ -5544,7 +5568,7 @@ Function InternalFunction(boolean escaped): } { [ LOOKAHEAD(2) prefixToken = ] - funcName = RelObjectNameList() { if (prefixToken!=null) funcName.add(0, prefixToken.image ); } + funcName = RelObjectNames() { if (prefixToken!=null) funcName.getNames().add(0, prefixToken.image ); } "(" [ @@ -5601,7 +5625,7 @@ Function InternalFunction(boolean escaped): { retval.setEscaped(escaped); retval.setParameters(expressionList); - retval.setName(funcName); + retval.setName(funcName.getNames()); retval.setKeep(keep); return retval; } @@ -5698,10 +5722,10 @@ List ColumnNamesWithParamsList() : { } Index Index(): { - List name; + ObjectNames name; } { - name= RelObjectNameList() { return new Index().withName(name).withType(""); } + name= RelObjectNames() { return new Index().withName(name.getNames()).withType(""); } } CreateIndex CreateIndex(): @@ -7311,7 +7335,7 @@ Grant Grant(): ArrayList privileges = new ArrayList(); List users; Token tk = null; - List objName; + ObjectNames objName; } { @@ -7320,7 +7344,7 @@ Grant Grant(): [readGrantTypes(privileges) ( readGrantTypes(privileges))*] ( - objName=RelObjectNameList() { grant.setObjectName(objName); } + objName=RelObjectNames() { grant.setObjectName(objName.getNames()); } ) ) | @@ -7365,13 +7389,13 @@ void readGrantTypes(ArrayList privileges): Sequence Sequence() #Sequence : { - List data = new ArrayList(); + ObjectNames data = null; String serverName = null, databaseName = null, schemaName = null, sequenceName = null; } { - data = RelObjectNameList() + data = RelObjectNames() { - Sequence sequence = new Sequence(data); + Sequence sequence = new Sequence(data.getNames()); linkAST(sequence,jjtThis); return sequence; } @@ -7575,29 +7599,29 @@ CreateSynonym CreateSynonym(boolean isUsingOrReplace): CreateSynonym createSynonym = new CreateSynonym(); Synonym synonym; boolean publicSynonym = false; - List data = new ArrayList(); + ObjectNames data = null; } { [ { publicSynonym = true; } ] synonym=Synonym() { createSynonym.setSynonym(synonym); } - data = RelObjectNameList() + data = RelObjectNames() { createSynonym.setOrReplace(isUsingOrReplace); createSynonym.setPublicSynonym(publicSynonym); - createSynonym.setForList(data); + createSynonym.setForList(data.getNames()); return createSynonym; } } Synonym Synonym() #Synonym : { - List data = new ArrayList(); + ObjectNames data = null; String serverName = null, databaseName = null, schemaName = null, sequenceName = null; } { - data = RelObjectNameList() + data = RelObjectNames() { - Synonym synonym = new Synonym(data); + Synonym synonym = new Synonym(data.getNames()); linkAST(synonym,jjtThis); return synonym; } diff --git a/src/test/java/net/sf/jsqlparser/schema/ColumnTest.java b/src/test/java/net/sf/jsqlparser/schema/ColumnTest.java index 953a7ca9a..4c8469429 100644 --- a/src/test/java/net/sf/jsqlparser/schema/ColumnTest.java +++ b/src/test/java/net/sf/jsqlparser/schema/ColumnTest.java @@ -9,9 +9,13 @@ */ package net.sf.jsqlparser.schema; -import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; + /** * * @author tw @@ -30,4 +34,24 @@ public String toString() { assertEquals("anonymous class", myColumn.toString()); } + @Test + public void testConstructorNameParts() { + Column column = new Column(List.of("schema", "table", "column")); + assertThat(column.getColumnName()).isEqualTo("column"); + + Table table = column.getTable(); + assertThat(table.getNameParts()).containsExactly("table", "schema"); + assertThat(table.getNamePartDelimiters()).containsExactly("."); + } + + @Test + public void testConstructorNamePartsAndDelimiters() { + Column column = new Column(List.of("a", "b", "c", "d"), List.of(":", ".", ":")); + assertThat(column.getColumnName()).isEqualTo("d"); + + Table table = column.getTable(); + assertThat(table.getNameParts()).containsExactly("c", "b", "a"); + assertThat(table.getNamePartDelimiters()).containsExactly(".", ":"); + } + } diff --git a/src/test/java/net/sf/jsqlparser/schema/TableTest.java b/src/test/java/net/sf/jsqlparser/schema/TableTest.java index 29569f6e0..fc81ce9b2 100644 --- a/src/test/java/net/sf/jsqlparser/schema/TableTest.java +++ b/src/test/java/net/sf/jsqlparser/schema/TableTest.java @@ -17,7 +17,10 @@ import net.sf.jsqlparser.util.deparser.SelectDeParser; import org.junit.jupiter.api.Test; +import java.util.List; + import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertEquals; /** @@ -73,4 +76,13 @@ public void testTableRemoveNameParts() { table.setSchemaName(null); assertThat(table.getFullyQualifiedName()).isEqualTo("DICTIONARY"); } + + @Test + public void testConstructorDelimitersInappropriateSize() { + assertThatThrownBy( + () -> new Table(List.of("a", "b", "c"), List.of("too", "many", "delimiters"))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining( + "the length of the delimiters list must be 1 less than nameParts"); + } } diff --git a/src/test/java/net/sf/jsqlparser/statement/select/ExpressionDelimiterTest.java b/src/test/java/net/sf/jsqlparser/statement/select/ExpressionDelimiterTest.java new file mode 100644 index 000000000..cb4a63c97 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/select/ExpressionDelimiterTest.java @@ -0,0 +1,40 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2024 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.select; + +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.schema.Column; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class ExpressionDelimiterTest { + + @Test + public void testColumnWithDifferentDelimiters() throws JSQLParserException { + String statement = "SELECT mytable.mycolumn:parent:child FROM mytable"; + PlainSelect parsed = (PlainSelect) assertSqlCanBeParsedAndDeparsed(statement); + Column column = parsed.getSelectItem(0).getExpression(Column.class); + assertEquals(":", column.getTableDelimiter()); + assertEquals(List.of(":", "."), column.getTable().getNamePartDelimiters()); + } + + @Test + public void testColumnWithEmptyNameParts() throws JSQLParserException { + String statement = "SELECT mytable.:.child FROM mytable"; + PlainSelect parsed = (PlainSelect) assertSqlCanBeParsedAndDeparsed(statement); + Column column = parsed.getSelectItem(0).getExpression(Column.class); + assertEquals(".", column.getTableDelimiter()); + assertEquals(List.of(":", "."), column.getTable().getNamePartDelimiters()); + } +}