From 00eb5cf1a9517ec72c7d6590e4090bee4979b120 Mon Sep 17 00:00:00 2001 From: Andreas Reichel Date: Tue, 20 Jul 2021 10:24:05 +0700 Subject: [PATCH 1/4] Fixes #1267 Cast into RowConstructor --- .../jsqlparser/expression/CastExpression.java | 20 +++++++++++- .../expression/ExpressionVisitorAdapter.java | 5 +-- .../jsqlparser/expression/RowConstructor.java | 23 +++++++++++++- .../create/table/ColumnDefinition.java | 5 +++ .../util/deparser/ExpressionDeParser.java | 28 ++++++++++++----- .../validator/ExpressionValidator.java | 9 +++++- .../net/sf/jsqlparser/parser/JSqlParserCC.jjt | 31 ++++++++++++++----- .../expression/CastExpressionTest.java | 27 ++++++++++++++++ .../statement/select/SelectTest.java | 12 +++++++ 9 files changed, 139 insertions(+), 21 deletions(-) create mode 100644 src/test/java/net/sf/jsqlparser/expression/CastExpressionTest.java diff --git a/src/main/java/net/sf/jsqlparser/expression/CastExpression.java b/src/main/java/net/sf/jsqlparser/expression/CastExpression.java index e5f12f7d8..519eb4ef6 100644 --- a/src/main/java/net/sf/jsqlparser/expression/CastExpression.java +++ b/src/main/java/net/sf/jsqlparser/expression/CastExpression.java @@ -16,7 +16,22 @@ public class CastExpression extends ASTNodeAccessImpl implements Expression { private Expression leftExpression; private ColDataType type; + private RowConstructor rowConstructor; private boolean useCastKeyword = true; + + public RowConstructor getRowConstructor() { + return rowConstructor; + } + + public void setRowConstructor(RowConstructor rowConstructor) { + this.rowConstructor = rowConstructor; + this.type = null; + } + + public CastExpression withRowConstructor(RowConstructor rowConstructor) { + setRowConstructor(rowConstructor); + return this; + } public ColDataType getType() { return type; @@ -24,6 +39,7 @@ public ColDataType getType() { public void setType(ColDataType type) { this.type = type; + this.rowConstructor = null; } public Expression getLeftExpression() { @@ -50,7 +66,9 @@ public void setUseCastKeyword(boolean useCastKeyword) { @Override public String toString() { if (useCastKeyword) { - return "CAST(" + leftExpression + " AS " + type.toString() + ")"; + return rowConstructor!=null + ? "CAST(" + leftExpression + " AS " + rowConstructor.toString() + ")" + : "CAST(" + leftExpression + " AS " + type.toString() + ")"; } else { return leftExpression + "::" + type.toString(); } diff --git a/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapter.java b/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapter.java index 443f14500..df53e71e4 100644 --- a/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapter.java +++ b/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapter.java @@ -15,6 +15,7 @@ import net.sf.jsqlparser.expression.operators.conditional.XorExpression; import net.sf.jsqlparser.expression.operators.relational.*; import net.sf.jsqlparser.schema.Column; +import net.sf.jsqlparser.statement.create.table.ColumnDefinition; import net.sf.jsqlparser.statement.select.AllColumns; import net.sf.jsqlparser.statement.select.AllTableColumns; import net.sf.jsqlparser.statement.select.ExpressionListItem; @@ -503,8 +504,8 @@ public void visit(SelectExpressionItem selectExpressionItem) { @Override public void visit(RowConstructor rowConstructor) { - for (Expression expr : rowConstructor.getExprList().getExpressions()) { - expr.accept(this); + for (ColumnDefinition columnDefinition : rowConstructor.getColumnDefinitions()) { + columnDefinition.accept(this); } } diff --git a/src/main/java/net/sf/jsqlparser/expression/RowConstructor.java b/src/main/java/net/sf/jsqlparser/expression/RowConstructor.java index fb499dee1..12891b915 100644 --- a/src/main/java/net/sf/jsqlparser/expression/RowConstructor.java +++ b/src/main/java/net/sf/jsqlparser/expression/RowConstructor.java @@ -9,16 +9,26 @@ */ package net.sf.jsqlparser.expression; +import java.util.ArrayList; import net.sf.jsqlparser.expression.operators.relational.ExpressionList; import net.sf.jsqlparser.parser.ASTNodeAccessImpl; +import net.sf.jsqlparser.statement.create.table.ColumnDefinition; public class RowConstructor extends ASTNodeAccessImpl implements Expression { - private ExpressionList exprList; + private ArrayList columnDefinitions = new ArrayList<>(); private String name = null; public RowConstructor() { } + + public ArrayList getColumnDefinitions() { + return columnDefinitions; + } + + public boolean addColumnDefinition(ColumnDefinition columnDefinition) { + return columnDefinitions.add(columnDefinition); + } public ExpressionList getExprList() { return exprList; @@ -43,6 +53,17 @@ public void accept(ExpressionVisitor expressionVisitor) { @Override public String toString() { + if (columnDefinitions.size()>0) { + StringBuilder builder = new StringBuilder(name != null ? name : ""); + builder.append("("); + int i = 0; + for (ColumnDefinition columnDefinition:columnDefinitions) { + builder.append(i>0 ? ", " : "").append(columnDefinition.toString()); + i++; + } + builder.append(")"); + return builder.toString(); + } return (name != null ? name : "") + exprList.toString(); } diff --git a/src/main/java/net/sf/jsqlparser/statement/create/table/ColumnDefinition.java b/src/main/java/net/sf/jsqlparser/statement/create/table/ColumnDefinition.java index 76a1be877..f7ddeb3aa 100644 --- a/src/main/java/net/sf/jsqlparser/statement/create/table/ColumnDefinition.java +++ b/src/main/java/net/sf/jsqlparser/statement/create/table/ColumnDefinition.java @@ -14,6 +14,7 @@ import java.util.Collections; import java.util.List; import java.util.Optional; +import net.sf.jsqlparser.expression.ExpressionVisitorAdapter; import net.sf.jsqlparser.statement.select.PlainSelect; /** @@ -99,4 +100,8 @@ public ColumnDefinition addColumnSpecs(Collection columnSpecs) { collection.addAll(columnSpecs); return this.withColumnSpecs(collection); } + + public void accept(ExpressionVisitorAdapter aThis) { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } } 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 3b7eb9f27..8d3417246 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/ExpressionDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/ExpressionDeParser.java @@ -101,6 +101,7 @@ import net.sf.jsqlparser.expression.operators.relational.SupportsOldOracleJoinSyntax; import net.sf.jsqlparser.schema.Column; import net.sf.jsqlparser.schema.Table; +import net.sf.jsqlparser.statement.create.table.ColumnDefinition; import net.sf.jsqlparser.statement.select.OrderByElement; import net.sf.jsqlparser.statement.select.PlainSelect; import net.sf.jsqlparser.statement.select.SelectVisitor; @@ -668,7 +669,7 @@ public void visit(CastExpression cast) { buffer.append("CAST("); cast.getLeftExpression().accept(this); buffer.append(" AS "); - buffer.append(cast.getType()); + buffer.append( cast.getRowConstructor()!=null ? cast.getRowConstructor() : cast.getType() ); buffer.append(")"); } else { cast.getLeftExpression().accept(this); @@ -872,14 +873,25 @@ public void visit(RowConstructor rowConstructor) { buffer.append(rowConstructor.getName()); } buffer.append("("); - boolean first = true; - for (Expression expr : rowConstructor.getExprList().getExpressions()) { - if (first) { - first = false; - } else { - buffer.append(", "); + + if (rowConstructor.getColumnDefinitions().size()>0) { + buffer.append("("); + int i = 0; + for (ColumnDefinition columnDefinition:rowConstructor.getColumnDefinitions()) { + buffer.append(i>0 ? ", " : "").append(columnDefinition.toString()); + i++; + } + buffer.append(")"); + } else { + boolean first = true; + for (Expression expr : rowConstructor.getExprList().getExpressions()) { + if (first) { + first = false; + } else { + buffer.append(", "); + } + expr.accept(this); } - expr.accept(this); } buffer.append(")"); } diff --git a/src/main/java/net/sf/jsqlparser/util/validation/validator/ExpressionValidator.java b/src/main/java/net/sf/jsqlparser/util/validation/validator/ExpressionValidator.java index 5b2ea3606..93514c1ce 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/validator/ExpressionValidator.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/validator/ExpressionValidator.java @@ -95,6 +95,7 @@ import net.sf.jsqlparser.expression.operators.relational.SupportsOldOracleJoinSyntax; import net.sf.jsqlparser.parser.feature.Feature; import net.sf.jsqlparser.schema.Column; +import net.sf.jsqlparser.statement.create.table.ColumnDefinition; import net.sf.jsqlparser.statement.select.SubSelect; import net.sf.jsqlparser.util.validation.ValidationCapability; import net.sf.jsqlparser.util.validation.metadata.NamedObject; @@ -499,7 +500,13 @@ public void visit(ValueListExpression valueList) { @Override public void visit(RowConstructor rowConstructor) { - validateOptionalExpressionList(rowConstructor.getExprList()); + if (rowConstructor.getColumnDefinitions().isEmpty()) { + validateOptionalExpressionList(rowConstructor.getExprList()); + } else { + for (ColumnDefinition columnDefinition: rowConstructor.getColumnDefinitions()) { + validateName(NamedObject.column, columnDefinition.getColumnName()); + } + } } @Override diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index 667484dda..fc06519ae 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -3086,7 +3086,8 @@ ExpressionList SimpleExpressionList(boolean outerBrackets) #ExpressionList: Expression expr = null; } { - expr=SimpleExpression() { expressions.add(expr); } ("," expr=SimpleExpression() { expressions.add(expr); })* + expr=SimpleExpression() { expressions.add(expr); } + ( LOOKAHEAD(2) "," expr=SimpleExpression() { expressions.add(expr); } )* { retval.setExpressions(expressions); return retval; @@ -3469,6 +3470,8 @@ Expression PrimaryExpression() #PrimaryExpression: | LOOKAHEAD(2) retval=CastExpression() + //| LOOKAHEAD(2) retval=RowConstructor() + // support timestamp expressions | (token= | token=) { retval = new TimeKeyExpression(token.image); } @@ -4013,15 +4016,23 @@ CastExpression CastExpression(): { CastExpression retval = new CastExpression(); ColDataType type = null; + RowConstructor rowConstructor = null; Expression expression = null; boolean useCastKeyword; } { - "(" expression=SimpleExpression() type=ColDataType() ")" { retval.setUseCastKeyword(true); } + + "(" + expression=SimpleExpression() + { retval.setUseCastKeyword(true); } + ( + LOOKAHEAD(3) rowConstructor = RowConstructor() { retval.setRowConstructor(rowConstructor); } + | type=ColDataType() { retval.setType(type); } + ) + ")" { retval.setLeftExpression(expression); - retval.setType(type); return retval; } } @@ -4085,16 +4096,20 @@ WhenClause WhenThenSearchCondition(): }*/ RowConstructor RowConstructor(): { - ExpressionList list = null; RowConstructor rowConstructor = new RowConstructor(); + ColumnDefinition columnDefinition = null; } { [ { rowConstructor.setName("ROW");} ] "(" - list = SimpleExpressionList(true) - ")" + columnDefinition = ColumnDefinition() { rowConstructor.addColumnDefinition(columnDefinition); } + ( + "," + columnDefinition = ColumnDefinition() { rowConstructor.addColumnDefinition(columnDefinition); } + )* + ")" + { - rowConstructor.setExprList(list); return rowConstructor; } } @@ -4434,7 +4449,7 @@ ColumnDefinition ColumnDefinition(): { colDataType = ColDataType() - ( parameter=CreateParameter() { columnSpecs.addAll(parameter); } )* + ( LOOKAHEAD(2) parameter=CreateParameter() { columnSpecs.addAll(parameter); } )* { coldef = new ColumnDefinition(); diff --git a/src/test/java/net/sf/jsqlparser/expression/CastExpressionTest.java b/src/test/java/net/sf/jsqlparser/expression/CastExpressionTest.java new file mode 100644 index 000000000..a0fdfd212 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/expression/CastExpressionTest.java @@ -0,0 +1,27 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2021 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ + +package net.sf.jsqlparser.expression; + +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.test.TestUtils; +import org.junit.Test; + +/** + * + * @author Andreas Reichel + */ +public class CastExpressionTest { + @Test + public void testCastToRowConstructorIssue1267() throws JSQLParserException { + TestUtils.assertExpressionCanBeParsedAndDeparsed("CAST(ROW(dataid, value, calcMark) AS ROW(datapointid CHAR, value CHAR, calcMark CHAR))", true); + TestUtils.assertExpressionCanBeParsedAndDeparsed("CAST(ROW(dataid, value, calcMark) AS testcol)", true); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java b/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java index 696e4a162..c67fa3484 100644 --- a/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java @@ -4664,4 +4664,16 @@ public void testDB2SpecialRegisterDateTimeIssue1249() throws JSQLParserException public void testKeywordFilterIssue1255() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed("SELECT col1 AS filter FROM table"); } + + @Test + public void testUnionLimitOrderByIssue1268() throws JSQLParserException { + String sqlStr = "(SELECT __time FROM traffic_protocol_stat_log LIMIT 1) UNION ALL (SELECT __time FROM traffic_protocol_stat_log ORDER BY __time LIMIT 1)"; + assertSqlCanBeParsedAndDeparsed(sqlStr, true); + } + + @Test + public void testCastToRowConstructorIssue1267() throws JSQLParserException { + String sqlStr = "SELECT CAST(ROW(dataid, value, calcMark) AS ROW(datapointid CHAR, value CHAR, calcMark CHAR)) AS datapoints"; + assertSqlCanBeParsedAndDeparsed(sqlStr, true); + } } From 88486a768edf3919fe36f0e184267e220ba33b33 Mon Sep 17 00:00:00 2001 From: Andreas Reichel Date: Tue, 20 Jul 2021 10:50:21 +0700 Subject: [PATCH 2/4] Improve Test Coverage --- .../expression/ExpressionVisitorAdapter.java | 14 ++++++++++++-- .../statement/create/table/ColumnDefinition.java | 6 +++--- .../jsqlparser/util/validation/ValidationTest.java | 8 ++++++++ 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapter.java b/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapter.java index df53e71e4..743117238 100644 --- a/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapter.java +++ b/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapter.java @@ -504,8 +504,14 @@ public void visit(SelectExpressionItem selectExpressionItem) { @Override public void visit(RowConstructor rowConstructor) { - for (ColumnDefinition columnDefinition : rowConstructor.getColumnDefinitions()) { - columnDefinition.accept(this); + if (rowConstructor.getColumnDefinitions().isEmpty()) { + for (Expression expression: rowConstructor.getExprList().getExpressions()) { + expression.accept(this); + } + } else { + for (ColumnDefinition columnDefinition : rowConstructor.getColumnDefinitions()) { + columnDefinition.accept(this); + } } } @@ -606,4 +612,8 @@ public void visit(JsonFunction expression) { expr.getExpression().accept(this); } } + + public void visit(ColumnDefinition aThis) { + + } } diff --git a/src/main/java/net/sf/jsqlparser/statement/create/table/ColumnDefinition.java b/src/main/java/net/sf/jsqlparser/statement/create/table/ColumnDefinition.java index f7ddeb3aa..793e5adc2 100644 --- a/src/main/java/net/sf/jsqlparser/statement/create/table/ColumnDefinition.java +++ b/src/main/java/net/sf/jsqlparser/statement/create/table/ColumnDefinition.java @@ -101,7 +101,7 @@ public ColumnDefinition addColumnSpecs(Collection columnSpecs) { return this.withColumnSpecs(collection); } - public void accept(ExpressionVisitorAdapter aThis) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. - } + public void accept(ExpressionVisitorAdapter expressionVisitor) { + expressionVisitor.visit(this); + } } diff --git a/src/test/java/net/sf/jsqlparser/util/validation/ValidationTest.java b/src/test/java/net/sf/jsqlparser/util/validation/ValidationTest.java index f55f2030f..178843293 100644 --- a/src/test/java/net/sf/jsqlparser/util/validation/ValidationTest.java +++ b/src/test/java/net/sf/jsqlparser/util/validation/ValidationTest.java @@ -146,6 +146,14 @@ public void testFeatureSetName() { assertEquals("UPDATE + SELECT + feature set", FeaturesAllowed.UPDATE.copy().add(new FeaturesAllowed(Feature.commit)).getName()); } + + @Test + public void testRowConstructorValidation() throws JSQLParserException { + String stmt = "SELECT CAST(ROW(dataid, value, calcMark) AS ROW(datapointid CHAR, value CHAR, calcMark CHAR))"; + List errors = Validation.validate( + Arrays.asList(DatabaseType.ANSI_SQL, FeaturesAllowed.SELECT), stmt); + assertErrorsSize(errors, 0); + } } \ No newline at end of file From 7d280d0c5f6c5388acb93e12350c2e79b26b1f59 Mon Sep 17 00:00:00 2001 From: Andreas Reichel Date: Tue, 20 Jul 2021 11:06:31 +0700 Subject: [PATCH 3/4] Improve Test Coverage --- .../expression/ExpressionVisitorAdapterTest.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/test/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapterTest.java b/src/test/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapterTest.java index 32cbb3016..e739d579f 100644 --- a/src/test/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapterTest.java +++ b/src/test/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapterTest.java @@ -256,4 +256,12 @@ public void testJsonAggregateFunction() throws JSQLParserException { .parseExpression("JSON_ARRAYAGG( a FORMAT JSON ABSENT ON NULL ) FILTER( WHERE name = 'Raj' ) OVER( PARTITION BY name )") .accept(adapter); } + + @Test + public void testRowConstructor() throws JSQLParserException { + ExpressionVisitorAdapter adapter = new ExpressionVisitorAdapter(); + CCJSqlParserUtil + .parseExpression("CAST(ROW(dataid, value, calcMark) AS ROW(datapointid CHAR, value CHAR, calcMark CHAR))") + .accept(adapter); + } } From fb183083bd1e4f80ab3ffaac03d2d525feeb73a0 Mon Sep 17 00:00:00 2001 From: Andreas Reichel Date: Tue, 20 Jul 2021 11:12:09 +0700 Subject: [PATCH 4/4] Improve Test Coverage --- .../sf/jsqlparser/expression/ExpressionVisitorAdapter.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapter.java b/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapter.java index 743117238..7b28cd191 100644 --- a/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapter.java +++ b/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapter.java @@ -613,7 +613,7 @@ public void visit(JsonFunction expression) { } } - public void visit(ColumnDefinition aThis) { - - } + public void visit(ColumnDefinition columnDefinition) { + columnDefinition.accept(this); + } }