Skip to content

Commit d9985ae

Browse files
dequnZhang, Dequn
andauthored
Support BigQuery SAFE_CAST (#1622) (#1634)
Co-authored-by: Zhang, Dequn <deqzhang@paypal.com>
1 parent d221277 commit d9985ae

File tree

8 files changed

+180
-1
lines changed

8 files changed

+180
-1
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@ public interface ExpressionVisitor {
119119

120120
void visit(TryCastExpression cast);
121121

122+
void visit(SafeCastExpression cast);
123+
122124
void visit(Modulo modulo);
123125

124126
void visit(AnalyticExpression aexpr);

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,11 @@ public void visit(TryCastExpression expr) {
315315
expr.getLeftExpression().accept(this);
316316
}
317317

318+
@Override
319+
public void visit(SafeCastExpression expr) {
320+
expr.getLeftExpression().accept(this);
321+
}
322+
318323
@Override
319324
public void visit(Modulo expr) {
320325
visitBinaryExpression(expr);
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*-
2+
* #%L
3+
* JSQLParser library
4+
* %%
5+
* Copyright (C) 2004 - 2019 JSQLParser
6+
* %%
7+
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
8+
* #L%
9+
*/
10+
package net.sf.jsqlparser.expression;
11+
12+
import net.sf.jsqlparser.parser.ASTNodeAccessImpl;
13+
import net.sf.jsqlparser.statement.create.table.ColDataType;
14+
15+
public class SafeCastExpression extends ASTNodeAccessImpl implements Expression {
16+
17+
private Expression leftExpression;
18+
private ColDataType type;
19+
private RowConstructor rowConstructor;
20+
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 SafeCastExpression withRowConstructor(RowConstructor rowConstructor) {
32+
setRowConstructor(rowConstructor);
33+
return this;
34+
}
35+
36+
public ColDataType getType() {
37+
return type;
38+
}
39+
40+
public void setType(ColDataType type) {
41+
this.type = type;
42+
this.rowConstructor = null;
43+
}
44+
45+
public Expression getLeftExpression() {
46+
return leftExpression;
47+
}
48+
49+
public void setLeftExpression(Expression expression) {
50+
leftExpression = expression;
51+
}
52+
53+
@Override
54+
public void accept(ExpressionVisitor expressionVisitor) {
55+
expressionVisitor.visit(this);
56+
}
57+
58+
public boolean isUseCastKeyword() {
59+
return useCastKeyword;
60+
}
61+
62+
public void setUseCastKeyword(boolean useCastKeyword) {
63+
this.useCastKeyword = useCastKeyword;
64+
}
65+
66+
@Override
67+
public String toString() {
68+
if (useCastKeyword) {
69+
return rowConstructor!=null
70+
? "SAFE_CAST(" + leftExpression + " AS " + rowConstructor.toString() + ")"
71+
: "SAFE_CAST(" + leftExpression + " AS " + type.toString() + ")";
72+
} else {
73+
return leftExpression + "::" + type.toString();
74+
}
75+
}
76+
77+
public SafeCastExpression withType(ColDataType type) {
78+
this.setType(type);
79+
return this;
80+
}
81+
82+
public SafeCastExpression withUseCastKeyword(boolean useCastKeyword) {
83+
this.setUseCastKeyword(useCastKeyword);
84+
return this;
85+
}
86+
87+
public SafeCastExpression withLeftExpression(Expression leftExpression) {
88+
this.setLeftExpression(leftExpression);
89+
return this;
90+
}
91+
92+
public <E extends Expression> E getLeftExpression(Class<E> type) {
93+
return type.cast(getLeftExpression());
94+
}
95+
}

src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,11 @@ public void visit(TryCastExpression cast) {
474474
cast.getLeftExpression().accept(this);
475475
}
476476

477+
@Override
478+
public void visit(SafeCastExpression cast) {
479+
cast.getLeftExpression().accept(this);
480+
}
481+
477482
@Override
478483
public void visit(Modulo modulo) {
479484
visitBinaryExpression(modulo);

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -622,6 +622,22 @@ public void visit(TryCastExpression cast) {
622622
}
623623
}
624624

625+
@Override
626+
public void visit(SafeCastExpression cast) {
627+
if (cast.isUseCastKeyword()) {
628+
buffer.append("SAFE_CAST(");
629+
cast.getLeftExpression().accept(this);
630+
buffer.append(" AS ");
631+
buffer.append(cast.getRowConstructor() != null ? cast.getRowConstructor() : cast.getType());
632+
buffer.append(")");
633+
} else {
634+
cast.getLeftExpression().accept(this);
635+
buffer.append("::");
636+
buffer.append(cast.getType());
637+
}
638+
639+
}
640+
625641
@Override
626642
public void visit(Modulo modulo) {
627643
visitBinaryExpression(modulo, " % ");

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,12 @@ public void visit(TryCastExpression cast) {
362362
cast.getLeftExpression().accept(this);
363363
}
364364

365+
@Override
366+
public void visit(SafeCastExpression cast) {
367+
cast.getLeftExpression().accept(this);
368+
369+
}
370+
365371
@Override
366372
public void visit(Modulo modulo) {
367373
visitBinaryExpression(modulo, " % ");

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

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ TOKEN: /* SQL Keywords. prefixed with K_ to avoid name clashes */
158158
| <K_CASEWHEN:"CASEWHEN"> /* H2 casewhen function */
159159
| <K_CAST: "CAST">
160160
| <K_TRY_CAST: "TRY_CAST">
161+
| <K_SAFE_CAST: "SAFE_CAST">
161162
| <K_CHARACTER:"CHARACTER">
162163
| <K_CHANGE:"CHANGE">
163164
| <K_CHANGES:"CHANGES">
@@ -1821,7 +1822,7 @@ String RelObjectNameWithoutValue() :
18211822
(tk=<S_IDENTIFIER> | tk=<S_QUOTED_IDENTIFIER>
18221823
| tk=<K_ALGORITHM> | tk=<K_AT>
18231824
| tk=<K_BYTE> | tk=<K_CHAR> | tk=<K_CHANGE> | tk=<K_CHARACTER>
1824-
| tk=<K_CAST> | tk =<K_TRY_CAST> | tk=<K_COMMENT> | tk=<K_COSTS> | tk=<K_DISABLE> | tk=<K_DESC>
1825+
| tk=<K_CAST> | tk =<K_TRY_CAST> | tk =<K_SAFE_CAST> | tk=<K_COMMENT> | tk=<K_COSTS> | tk=<K_DISABLE> | tk=<K_DESC>
18251826
| tk=<K_DO> | tk=<K_DEFAULT> | tk=<K_EXTRACT> | tk=<K_FILTER> | tk=<K_FIRST> | tk=<K_FOLLOWING> | tk=<K_JSON>
18261827
| tk=<K_LAST> | tk=<K_LEADING> | tk=<K_MATERIALIZED> | tk=<K_NULLS> | tk=<K_PARTITION> | tk=<K_RANGE>
18271828
| tk=<K_ROW> | tk=<K_ROWS> | tk=<K_SIBLINGS> | tk=<K_XML>
@@ -4002,6 +4003,8 @@ Expression PrimaryExpression() #PrimaryExpression:
40024003

40034004
| LOOKAHEAD(2, {!interrupted}) retval=TryCastExpression()
40044005

4006+
| LOOKAHEAD(2, {!interrupted}) retval=SafeCastExpression()
4007+
40054008
//| LOOKAHEAD(2) retval=RowConstructor()
40064009

40074010
// support timestamp expressions
@@ -4641,6 +4644,30 @@ TryCastExpression TryCastExpression():
46414644
}
46424645
}
46434646

4647+
SafeCastExpression SafeCastExpression():
4648+
{
4649+
SafeCastExpression retval = new SafeCastExpression();
4650+
ColDataType type = null;
4651+
RowConstructor rowConstructor = null;
4652+
Expression expression = null;
4653+
boolean useCastKeyword;
4654+
}
4655+
{
4656+
<K_SAFE_CAST>
4657+
"("
4658+
expression=SimpleExpression()
4659+
<K_AS> { retval.setUseCastKeyword(true); }
4660+
(
4661+
LOOKAHEAD(3) rowConstructor = RowConstructor() { retval.setRowConstructor(rowConstructor); }
4662+
| type=ColDataType() { retval.setType(type); }
4663+
)
4664+
")"
4665+
4666+
{
4667+
retval.setLeftExpression(expression);
4668+
return retval;
4669+
}
4670+
}
46444671
Expression CaseWhenExpression() #CaseWhenExpression:
46454672
{
46464673
CaseExpression caseExp = new CaseExpression();
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*-
2+
* #%L
3+
* JSQLParser library
4+
* %%
5+
* Copyright (C) 2004 - 2022 JSQLParser
6+
* %%
7+
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
8+
* #L%
9+
*/
10+
package net.sf.jsqlparser.expression;
11+
12+
import net.sf.jsqlparser.JSQLParserException;
13+
import net.sf.jsqlparser.test.TestUtils;
14+
import org.junit.jupiter.api.Test;
15+
16+
public class SafeCastExpressionTest {
17+
18+
@Test
19+
public void testSafeCast() throws JSQLParserException {
20+
TestUtils.assertExpressionCanBeParsedAndDeparsed("SAFE_CAST(ROW(dataid, value, calcMark) AS ROW(datapointid CHAR, value CHAR, calcMark CHAR))", true);
21+
TestUtils.assertExpressionCanBeParsedAndDeparsed("SAFE_CAST(ROW(dataid, value, calcMark) AS testcol)", true);
22+
}
23+
}

0 commit comments

Comments
 (0)