Skip to content

Commit c89cf21

Browse files
Fixes #1267 Cast into RowConstructor (#1274)
* Fixes #1267 Cast into RowConstructor * Improve Test Coverage * Improve Test Coverage * Improve Test Coverage Co-authored-by: Tobias <t.warneke@gmx.net>
1 parent c074a21 commit c89cf21

File tree

11 files changed

+164
-21
lines changed

11 files changed

+164
-21
lines changed

src/main/java/net/sf/jsqlparser/expression/CastExpression.java

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,30 @@ public class CastExpression extends ASTNodeAccessImpl implements Expression {
1616

1717
private Expression leftExpression;
1818
private ColDataType type;
19+
private RowConstructor rowConstructor;
1920
private boolean useCastKeyword = true;
21+
22+
public RowConstructor getRowConstructor() {
23+
return rowConstructor;
24+
}
25+
26+
public void setRowConstructor(RowConstructor rowConstructor) {
27+
this.rowConstructor = rowConstructor;
28+
this.type = null;
29+
}
30+
31+
public CastExpression withRowConstructor(RowConstructor rowConstructor) {
32+
setRowConstructor(rowConstructor);
33+
return this;
34+
}
2035

2136
public ColDataType getType() {
2237
return type;
2338
}
2439

2540
public void setType(ColDataType type) {
2641
this.type = type;
42+
this.rowConstructor = null;
2743
}
2844

2945
public Expression getLeftExpression() {
@@ -50,7 +66,9 @@ public void setUseCastKeyword(boolean useCastKeyword) {
5066
@Override
5167
public String toString() {
5268
if (useCastKeyword) {
53-
return "CAST(" + leftExpression + " AS " + type.toString() + ")";
69+
return rowConstructor!=null
70+
? "CAST(" + leftExpression + " AS " + rowConstructor.toString() + ")"
71+
: "CAST(" + leftExpression + " AS " + type.toString() + ")";
5472
} else {
5573
return leftExpression + "::" + type.toString();
5674
}

src/main/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapter.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import net.sf.jsqlparser.expression.operators.conditional.XorExpression;
1616
import net.sf.jsqlparser.expression.operators.relational.*;
1717
import net.sf.jsqlparser.schema.Column;
18+
import net.sf.jsqlparser.statement.create.table.ColumnDefinition;
1819
import net.sf.jsqlparser.statement.select.AllColumns;
1920
import net.sf.jsqlparser.statement.select.AllTableColumns;
2021
import net.sf.jsqlparser.statement.select.ExpressionListItem;
@@ -503,8 +504,14 @@ public void visit(SelectExpressionItem selectExpressionItem) {
503504

504505
@Override
505506
public void visit(RowConstructor rowConstructor) {
506-
for (Expression expr : rowConstructor.getExprList().getExpressions()) {
507-
expr.accept(this);
507+
if (rowConstructor.getColumnDefinitions().isEmpty()) {
508+
for (Expression expression: rowConstructor.getExprList().getExpressions()) {
509+
expression.accept(this);
510+
}
511+
} else {
512+
for (ColumnDefinition columnDefinition : rowConstructor.getColumnDefinitions()) {
513+
columnDefinition.accept(this);
514+
}
508515
}
509516
}
510517

@@ -605,4 +612,8 @@ public void visit(JsonFunction expression) {
605612
expr.getExpression().accept(this);
606613
}
607614
}
615+
616+
public void visit(ColumnDefinition columnDefinition) {
617+
columnDefinition.accept(this);
618+
}
608619
}

src/main/java/net/sf/jsqlparser/expression/RowConstructor.java

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,26 @@
99
*/
1010
package net.sf.jsqlparser.expression;
1111

12+
import java.util.ArrayList;
1213
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
1314
import net.sf.jsqlparser.parser.ASTNodeAccessImpl;
15+
import net.sf.jsqlparser.statement.create.table.ColumnDefinition;
1416

1517
public class RowConstructor extends ASTNodeAccessImpl implements Expression {
16-
1718
private ExpressionList exprList;
19+
private ArrayList<ColumnDefinition> columnDefinitions = new ArrayList<>();
1820
private String name = null;
1921

2022
public RowConstructor() {
2123
}
24+
25+
public ArrayList<ColumnDefinition> getColumnDefinitions() {
26+
return columnDefinitions;
27+
}
28+
29+
public boolean addColumnDefinition(ColumnDefinition columnDefinition) {
30+
return columnDefinitions.add(columnDefinition);
31+
}
2232

2333
public ExpressionList getExprList() {
2434
return exprList;
@@ -43,6 +53,17 @@ public void accept(ExpressionVisitor expressionVisitor) {
4353

4454
@Override
4555
public String toString() {
56+
if (columnDefinitions.size()>0) {
57+
StringBuilder builder = new StringBuilder(name != null ? name : "");
58+
builder.append("(");
59+
int i = 0;
60+
for (ColumnDefinition columnDefinition:columnDefinitions) {
61+
builder.append(i>0 ? ", " : "").append(columnDefinition.toString());
62+
i++;
63+
}
64+
builder.append(")");
65+
return builder.toString();
66+
}
4667
return (name != null ? name : "") + exprList.toString();
4768
}
4869

src/main/java/net/sf/jsqlparser/statement/create/table/ColumnDefinition.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import java.util.Collections;
1515
import java.util.List;
1616
import java.util.Optional;
17+
import net.sf.jsqlparser.expression.ExpressionVisitorAdapter;
1718
import net.sf.jsqlparser.statement.select.PlainSelect;
1819

1920
/**
@@ -99,4 +100,8 @@ public ColumnDefinition addColumnSpecs(Collection<String> columnSpecs) {
99100
collection.addAll(columnSpecs);
100101
return this.withColumnSpecs(collection);
101102
}
103+
104+
public void accept(ExpressionVisitorAdapter expressionVisitor) {
105+
expressionVisitor.visit(this);
106+
}
102107
}

src/main/java/net/sf/jsqlparser/util/deparser/ExpressionDeParser.java

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@
101101
import net.sf.jsqlparser.expression.operators.relational.SupportsOldOracleJoinSyntax;
102102
import net.sf.jsqlparser.schema.Column;
103103
import net.sf.jsqlparser.schema.Table;
104+
import net.sf.jsqlparser.statement.create.table.ColumnDefinition;
104105
import net.sf.jsqlparser.statement.select.OrderByElement;
105106
import net.sf.jsqlparser.statement.select.PlainSelect;
106107
import net.sf.jsqlparser.statement.select.SelectVisitor;
@@ -668,7 +669,7 @@ public void visit(CastExpression cast) {
668669
buffer.append("CAST(");
669670
cast.getLeftExpression().accept(this);
670671
buffer.append(" AS ");
671-
buffer.append(cast.getType());
672+
buffer.append( cast.getRowConstructor()!=null ? cast.getRowConstructor() : cast.getType() );
672673
buffer.append(")");
673674
} else {
674675
cast.getLeftExpression().accept(this);
@@ -872,14 +873,25 @@ public void visit(RowConstructor rowConstructor) {
872873
buffer.append(rowConstructor.getName());
873874
}
874875
buffer.append("(");
875-
boolean first = true;
876-
for (Expression expr : rowConstructor.getExprList().getExpressions()) {
877-
if (first) {
878-
first = false;
879-
} else {
880-
buffer.append(", ");
876+
877+
if (rowConstructor.getColumnDefinitions().size()>0) {
878+
buffer.append("(");
879+
int i = 0;
880+
for (ColumnDefinition columnDefinition:rowConstructor.getColumnDefinitions()) {
881+
buffer.append(i>0 ? ", " : "").append(columnDefinition.toString());
882+
i++;
883+
}
884+
buffer.append(")");
885+
} else {
886+
boolean first = true;
887+
for (Expression expr : rowConstructor.getExprList().getExpressions()) {
888+
if (first) {
889+
first = false;
890+
} else {
891+
buffer.append(", ");
892+
}
893+
expr.accept(this);
881894
}
882-
expr.accept(this);
883895
}
884896
buffer.append(")");
885897
}

src/main/java/net/sf/jsqlparser/util/validation/validator/ExpressionValidator.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@
9595
import net.sf.jsqlparser.expression.operators.relational.SupportsOldOracleJoinSyntax;
9696
import net.sf.jsqlparser.parser.feature.Feature;
9797
import net.sf.jsqlparser.schema.Column;
98+
import net.sf.jsqlparser.statement.create.table.ColumnDefinition;
9899
import net.sf.jsqlparser.statement.select.SubSelect;
99100
import net.sf.jsqlparser.util.validation.ValidationCapability;
100101
import net.sf.jsqlparser.util.validation.metadata.NamedObject;
@@ -499,7 +500,13 @@ public void visit(ValueListExpression valueList) {
499500

500501
@Override
501502
public void visit(RowConstructor rowConstructor) {
502-
validateOptionalExpressionList(rowConstructor.getExprList());
503+
if (rowConstructor.getColumnDefinitions().isEmpty()) {
504+
validateOptionalExpressionList(rowConstructor.getExprList());
505+
} else {
506+
for (ColumnDefinition columnDefinition: rowConstructor.getColumnDefinitions()) {
507+
validateName(NamedObject.column, columnDefinition.getColumnName());
508+
}
509+
}
503510
}
504511

505512
@Override

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

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3169,7 +3169,8 @@ ExpressionList SimpleExpressionList(boolean outerBrackets) #ExpressionList:
31693169
Expression expr = null;
31703170
}
31713171
{
3172-
expr=SimpleExpression() { expressions.add(expr); } ("," expr=SimpleExpression() { expressions.add(expr); })*
3172+
expr=SimpleExpression() { expressions.add(expr); }
3173+
( LOOKAHEAD(2) "," expr=SimpleExpression() { expressions.add(expr); } )*
31733174
{
31743175
retval.setExpressions(expressions);
31753176
return retval;
@@ -3552,6 +3553,8 @@ Expression PrimaryExpression() #PrimaryExpression:
35523553

35533554
| LOOKAHEAD(2) retval=CastExpression()
35543555

3556+
//| LOOKAHEAD(2) retval=RowConstructor()
3557+
35553558
// support timestamp expressions
35563559
| (token=<K_TIME_KEY_EXPR> | token=<K_CURRENT>) { retval = new TimeKeyExpression(token.image); }
35573560

@@ -4096,15 +4099,23 @@ CastExpression CastExpression():
40964099
{
40974100
CastExpression retval = new CastExpression();
40984101
ColDataType type = null;
4102+
RowConstructor rowConstructor = null;
40994103
Expression expression = null;
41004104
boolean useCastKeyword;
41014105
}
41024106
{
4103-
<K_CAST> "(" expression=SimpleExpression() <K_AS> type=ColDataType() ")" { retval.setUseCastKeyword(true); }
4107+
<K_CAST>
4108+
"("
4109+
expression=SimpleExpression()
4110+
<K_AS> { retval.setUseCastKeyword(true); }
4111+
(
4112+
LOOKAHEAD(3) rowConstructor = RowConstructor() { retval.setRowConstructor(rowConstructor); }
4113+
| type=ColDataType() { retval.setType(type); }
4114+
)
4115+
")"
41044116

41054117
{
41064118
retval.setLeftExpression(expression);
4107-
retval.setType(type);
41084119
return retval;
41094120
}
41104121
}
@@ -4168,16 +4179,20 @@ WhenClause WhenThenSearchCondition():
41684179
}*/
41694180

41704181
RowConstructor RowConstructor(): {
4171-
ExpressionList list = null;
41724182
RowConstructor rowConstructor = new RowConstructor();
4183+
ColumnDefinition columnDefinition = null;
41734184
} {
41744185
[ <K_ROW> { rowConstructor.setName("ROW");} ]
41754186
"("
4176-
list = SimpleExpressionList(true)
4177-
")"
4187+
columnDefinition = ColumnDefinition() { rowConstructor.addColumnDefinition(columnDefinition); }
4188+
(
4189+
","
4190+
columnDefinition = ColumnDefinition() { rowConstructor.addColumnDefinition(columnDefinition); }
4191+
)*
4192+
")"
4193+
41784194

41794195
{
4180-
rowConstructor.setExprList(list);
41814196
return rowConstructor;
41824197
}
41834198
}
@@ -4541,7 +4556,7 @@ ColumnDefinition ColumnDefinition(): {
45414556

45424557
colDataType = ColDataType()
45434558

4544-
( parameter=CreateParameter() { columnSpecs.addAll(parameter); } )*
4559+
( LOOKAHEAD(2) parameter=CreateParameter() { columnSpecs.addAll(parameter); } )*
45454560

45464561
{
45474562
coldef = new ColumnDefinition();
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*-
2+
* #%L
3+
* JSQLParser library
4+
* %%
5+
* Copyright (C) 2004 - 2021 JSQLParser
6+
* %%
7+
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
8+
* #L%
9+
*/
10+
11+
package net.sf.jsqlparser.expression;
12+
13+
import net.sf.jsqlparser.JSQLParserException;
14+
import net.sf.jsqlparser.test.TestUtils;
15+
import org.junit.Test;
16+
17+
/**
18+
*
19+
* @author <a href="mailto:andreas@manticore-projects.com">Andreas Reichel</a>
20+
*/
21+
public class CastExpressionTest {
22+
@Test
23+
public void testCastToRowConstructorIssue1267() throws JSQLParserException {
24+
TestUtils.assertExpressionCanBeParsedAndDeparsed("CAST(ROW(dataid, value, calcMark) AS ROW(datapointid CHAR, value CHAR, calcMark CHAR))", true);
25+
TestUtils.assertExpressionCanBeParsedAndDeparsed("CAST(ROW(dataid, value, calcMark) AS testcol)", true);
26+
}
27+
}

src/test/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapterTest.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,4 +256,12 @@ public void testJsonAggregateFunction() throws JSQLParserException {
256256
.parseExpression("JSON_ARRAYAGG( a FORMAT JSON ABSENT ON NULL ) FILTER( WHERE name = 'Raj' ) OVER( PARTITION BY name )")
257257
.accept(adapter);
258258
}
259+
260+
@Test
261+
public void testRowConstructor() throws JSQLParserException {
262+
ExpressionVisitorAdapter adapter = new ExpressionVisitorAdapter();
263+
CCJSqlParserUtil
264+
.parseExpression("CAST(ROW(dataid, value, calcMark) AS ROW(datapointid CHAR, value CHAR, calcMark CHAR))")
265+
.accept(adapter);
266+
}
259267
}

src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4666,6 +4666,17 @@ public void testKeywordFilterIssue1255() throws JSQLParserException {
46664666
}
46674667

46684668
@Test
4669+
public void testUnionLimitOrderByIssue1268() throws JSQLParserException {
4670+
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)";
4671+
assertSqlCanBeParsedAndDeparsed(sqlStr, true);
4672+
}
4673+
4674+
@Test
4675+
public void testCastToRowConstructorIssue1267() throws JSQLParserException {
4676+
String sqlStr = "SELECT CAST(ROW(dataid, value, calcMark) AS ROW(datapointid CHAR, value CHAR, calcMark CHAR)) AS datapoints";
4677+
assertSqlCanBeParsedAndDeparsed(sqlStr, true);
4678+
}
4679+
46694680
public void testCollisionWithSpecialStringFunctionsIssue1284() throws JSQLParserException {
46704681
assertSqlCanBeParsedAndDeparsed(
46714682
"SELECT test( a in (1) AND 2=2) ", true);

src/test/java/net/sf/jsqlparser/util/validation/ValidationTest.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,14 @@ public void testFeatureSetName() {
146146
assertEquals("UPDATE + SELECT + feature set",
147147
FeaturesAllowed.UPDATE.copy().add(new FeaturesAllowed(Feature.commit)).getName());
148148
}
149+
150+
@Test
151+
public void testRowConstructorValidation() throws JSQLParserException {
152+
String stmt = "SELECT CAST(ROW(dataid, value, calcMark) AS ROW(datapointid CHAR, value CHAR, calcMark CHAR))";
153+
List<ValidationError> errors = Validation.validate(
154+
Arrays.asList(DatabaseType.ANSI_SQL, FeaturesAllowed.SELECT), stmt);
155+
assertErrorsSize(errors, 0);
156+
}
149157

150158

151159
}

0 commit comments

Comments
 (0)