Skip to content

Commit

Permalink
Support BigQuery SAFE_CAST (#1622) (#1634)
Browse files Browse the repository at this point in the history
Co-authored-by: Zhang, Dequn <deqzhang@paypal.com>
  • Loading branch information
dequn and Zhang, Dequn authored Sep 20, 2022
1 parent d221277 commit d9985ae
Show file tree
Hide file tree
Showing 8 changed files with 180 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@ public interface ExpressionVisitor {

void visit(TryCastExpression cast);

void visit(SafeCastExpression cast);

void visit(Modulo modulo);

void visit(AnalyticExpression aexpr);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,11 @@ public void visit(TryCastExpression expr) {
expr.getLeftExpression().accept(this);
}

@Override
public void visit(SafeCastExpression expr) {
expr.getLeftExpression().accept(this);
}

@Override
public void visit(Modulo expr) {
visitBinaryExpression(expr);
Expand Down
95 changes: 95 additions & 0 deletions src/main/java/net/sf/jsqlparser/expression/SafeCastExpression.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*-
* #%L
* JSQLParser library
* %%
* Copyright (C) 2004 - 2019 JSQLParser
* %%
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
* #L%
*/
package net.sf.jsqlparser.expression;

import net.sf.jsqlparser.parser.ASTNodeAccessImpl;
import net.sf.jsqlparser.statement.create.table.ColDataType;

public class SafeCastExpression 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 SafeCastExpression withRowConstructor(RowConstructor rowConstructor) {
setRowConstructor(rowConstructor);
return this;
}

public ColDataType getType() {
return type;
}

public void setType(ColDataType type) {
this.type = type;
this.rowConstructor = null;
}

public Expression getLeftExpression() {
return leftExpression;
}

public void setLeftExpression(Expression expression) {
leftExpression = expression;
}

@Override
public void accept(ExpressionVisitor expressionVisitor) {
expressionVisitor.visit(this);
}

public boolean isUseCastKeyword() {
return useCastKeyword;
}

public void setUseCastKeyword(boolean useCastKeyword) {
this.useCastKeyword = useCastKeyword;
}

@Override
public String toString() {
if (useCastKeyword) {
return rowConstructor!=null
? "SAFE_CAST(" + leftExpression + " AS " + rowConstructor.toString() + ")"
: "SAFE_CAST(" + leftExpression + " AS " + type.toString() + ")";
} else {
return leftExpression + "::" + type.toString();
}
}

public SafeCastExpression withType(ColDataType type) {
this.setType(type);
return this;
}

public SafeCastExpression withUseCastKeyword(boolean useCastKeyword) {
this.setUseCastKeyword(useCastKeyword);
return this;
}

public SafeCastExpression withLeftExpression(Expression leftExpression) {
this.setLeftExpression(leftExpression);
return this;
}

public <E extends Expression> E getLeftExpression(Class<E> type) {
return type.cast(getLeftExpression());
}
}
5 changes: 5 additions & 0 deletions src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,11 @@ public void visit(TryCastExpression cast) {
cast.getLeftExpression().accept(this);
}

@Override
public void visit(SafeCastExpression cast) {
cast.getLeftExpression().accept(this);
}

@Override
public void visit(Modulo modulo) {
visitBinaryExpression(modulo);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,22 @@ public void visit(TryCastExpression cast) {
}
}

@Override
public void visit(SafeCastExpression cast) {
if (cast.isUseCastKeyword()) {
buffer.append("SAFE_CAST(");
cast.getLeftExpression().accept(this);
buffer.append(" AS ");
buffer.append(cast.getRowConstructor() != null ? cast.getRowConstructor() : cast.getType());
buffer.append(")");
} else {
cast.getLeftExpression().accept(this);
buffer.append("::");
buffer.append(cast.getType());
}

}

@Override
public void visit(Modulo modulo) {
visitBinaryExpression(modulo, " % ");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,12 @@ public void visit(TryCastExpression cast) {
cast.getLeftExpression().accept(this);
}

@Override
public void visit(SafeCastExpression cast) {
cast.getLeftExpression().accept(this);

}

@Override
public void visit(Modulo modulo) {
visitBinaryExpression(modulo, " % ");
Expand Down
29 changes: 28 additions & 1 deletion src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ TOKEN: /* SQL Keywords. prefixed with K_ to avoid name clashes */
| <K_CASEWHEN:"CASEWHEN"> /* H2 casewhen function */
| <K_CAST: "CAST">
| <K_TRY_CAST: "TRY_CAST">
| <K_SAFE_CAST: "SAFE_CAST">
| <K_CHARACTER:"CHARACTER">
| <K_CHANGE:"CHANGE">
| <K_CHANGES:"CHANGES">
Expand Down Expand Up @@ -1821,7 +1822,7 @@ String RelObjectNameWithoutValue() :
(tk=<S_IDENTIFIER> | tk=<S_QUOTED_IDENTIFIER>
| tk=<K_ALGORITHM> | tk=<K_AT>
| tk=<K_BYTE> | tk=<K_CHAR> | tk=<K_CHANGE> | tk=<K_CHARACTER>
| tk=<K_CAST> | tk =<K_TRY_CAST> | tk=<K_COMMENT> | tk=<K_COSTS> | tk=<K_DISABLE> | tk=<K_DESC>
| tk=<K_CAST> | tk =<K_TRY_CAST> | tk =<K_SAFE_CAST> | tk=<K_COMMENT> | tk=<K_COSTS> | tk=<K_DISABLE> | tk=<K_DESC>
| tk=<K_DO> | tk=<K_DEFAULT> | tk=<K_EXTRACT> | tk=<K_FILTER> | tk=<K_FIRST> | tk=<K_FOLLOWING> | tk=<K_JSON>
| tk=<K_LAST> | tk=<K_LEADING> | tk=<K_MATERIALIZED> | tk=<K_NULLS> | tk=<K_PARTITION> | tk=<K_RANGE>
| tk=<K_ROW> | tk=<K_ROWS> | tk=<K_SIBLINGS> | tk=<K_XML>
Expand Down Expand Up @@ -4002,6 +4003,8 @@ Expression PrimaryExpression() #PrimaryExpression:

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

| LOOKAHEAD(2, {!interrupted}) retval=SafeCastExpression()

//| LOOKAHEAD(2) retval=RowConstructor()

// support timestamp expressions
Expand Down Expand Up @@ -4641,6 +4644,30 @@ TryCastExpression TryCastExpression():
}
}

SafeCastExpression SafeCastExpression():
{
SafeCastExpression retval = new SafeCastExpression();
ColDataType type = null;
RowConstructor rowConstructor = null;
Expression expression = null;
boolean useCastKeyword;
}
{
<K_SAFE_CAST>
"("
expression=SimpleExpression()
<K_AS> { retval.setUseCastKeyword(true); }
(
LOOKAHEAD(3) rowConstructor = RowConstructor() { retval.setRowConstructor(rowConstructor); }
| type=ColDataType() { retval.setType(type); }
)
")"

{
retval.setLeftExpression(expression);
return retval;
}
}
Expression CaseWhenExpression() #CaseWhenExpression:
{
CaseExpression caseExp = new CaseExpression();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*-
* #%L
* JSQLParser library
* %%
* Copyright (C) 2004 - 2022 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.jupiter.api.Test;

public class SafeCastExpressionTest {

@Test
public void testSafeCast() throws JSQLParserException {
TestUtils.assertExpressionCanBeParsedAndDeparsed("SAFE_CAST(ROW(dataid, value, calcMark) AS ROW(datapointid CHAR, value CHAR, calcMark CHAR))", true);
TestUtils.assertExpressionCanBeParsedAndDeparsed("SAFE_CAST(ROW(dataid, value, calcMark) AS testcol)", true);
}
}

0 comments on commit d9985ae

Please sign in to comment.