Skip to content

Commit 0d813f0

Browse files
feat: Google BigQuery CAST with FORMAT clause
Signed-off-by: Andreas Reichel <andreas@manticore-projects.com>
1 parent 236793a commit 0d813f0

File tree

5 files changed

+201
-2
lines changed

5 files changed

+201
-2
lines changed

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

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ public class CastExpression extends ASTNodeAccessImpl implements Expression {
2323
private ColDataType colDataType = null;
2424
private ArrayList<ColumnDefinition> columnDefinitions = new ArrayList<>();
2525

26+
// BigQuery specific FORMAT clause:
27+
// https://cloud.google.com/bigquery/docs/reference/standard-sql/conversion_functions#cast_as_date
28+
private String format = null;
29+
2630
public CastExpression(String keyword, Expression leftExpression, String dataType) {
2731
this.keyword = keyword;
2832
this.leftExpression = leftExpression;
@@ -89,13 +93,26 @@ public void setUseCastKeyword(boolean useCastKeyword) {
8993
}
9094
}
9195

96+
public String getFormat() {
97+
return format;
98+
}
99+
100+
public CastExpression setFormat(String format) {
101+
this.format = format;
102+
return this;
103+
}
104+
92105
@Override
93106
public String toString() {
107+
String formatStr = format != null && !format.isEmpty()
108+
? " FORMAT " + format
109+
: "";
94110
if (keyword != null && !keyword.isEmpty()) {
95111
return columnDefinitions.size() > 1
96112
? keyword + "(" + leftExpression + " AS ROW("
97-
+ Select.getStringList(columnDefinitions) + "))"
98-
: keyword + "(" + leftExpression + " AS " + colDataType.toString() + ")";
113+
+ Select.getStringList(columnDefinitions) + ")" + formatStr + ")"
114+
: keyword + "(" + leftExpression + " AS " + colDataType.toString() + formatStr
115+
+ ")";
99116
} else {
100117
return leftExpression + "::" + colDataType.toString();
101118
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -704,13 +704,18 @@ public void visit(BitwiseXor bitwiseXor) {
704704
@Override
705705
public void visit(CastExpression cast) {
706706
if (cast.isUseCastKeyword()) {
707+
String formatStr = cast.getFormat() != null && !cast.getFormat().isEmpty()
708+
? " FORMAT " + cast.getFormat()
709+
: "";
710+
707711
buffer.append(cast.keyword).append("(");
708712
cast.getLeftExpression().accept(this);
709713
buffer.append(" AS ");
710714
buffer.append(
711715
cast.getColumnDefinitions().size() > 1
712716
? "ROW(" + Select.getStringList(cast.getColumnDefinitions()) + ")"
713717
: cast.getColDataType().toString());
718+
buffer.append(formatStr);
714719
buffer.append(")");
715720
} else {
716721
cast.getLeftExpression().accept(this);

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5033,6 +5033,7 @@ CastExpression CastExpression():
50335033
ColDataType type;
50345034
Expression expression = null;
50355035
boolean useCastKeyword;
5036+
Token formatCharLiteral;
50365037
}
50375038
{
50385039
(
@@ -5061,6 +5062,11 @@ CastExpression CastExpression():
50615062
|
50625063
type=ColDataType() { retval.setColDataType(type); }
50635064
)
5065+
5066+
// BigQuery FORMAT clause
5067+
// https://cloud.google.com/bigquery/docs/reference/standard-sql/conversion_functions#cast_as_date
5068+
[ <K_FORMAT> formatCharLiteral=<S_CHAR_LITERAL> { retval.setFormat(formatCharLiteral.image); } ]
5069+
50645070
")"
50655071

50665072
{

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,4 +160,10 @@ void testPerformanceIssue1889() throws JSQLParserException {
160160

161161
TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true);
162162
}
163+
164+
@Test
165+
void testFormatClause() throws JSQLParserException {
166+
String sqlStr = "SELECT CAST('18-12-03' AS DATE FORMAT 'YY-MM-DD') AS string_to_date";
167+
TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true);
168+
}
163169
}

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

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
package net.sf.jsqlparser.statement.select;
1111

1212
import net.sf.jsqlparser.JSQLParserException;
13+
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
1314
import org.junit.jupiter.api.Disabled;
1415
import org.junit.jupiter.api.Test;
1516
import org.junit.jupiter.api.Timeout;
@@ -353,4 +354,168 @@ public void testDeepFunctionParameters() throws JSQLParserException {
353354

354355
assertSqlCanBeParsedAndDeparsed(sqlStr, true);
355356
}
357+
358+
@Test
359+
@Disabled
360+
void testIssue1983() throws JSQLParserException {
361+
String sqlStr = "INSERT INTO\n" +
362+
"C01_INDIV_TELBK_CUST_INFO_H_T2 (PARTY_ID, PARTY_SIGN_STAT_CD, SIGN_TM, CLOSE_TM)\n"
363+
+
364+
"SELECT\n" +
365+
"A1.PARTY_ID,\n" +
366+
"A1.PARTY_SIGN_STAT_CD,\n" +
367+
"CAST(\n" +
368+
"(\n" +
369+
"CASE\n" +
370+
"WHEN A1.SIGN_TM IS NULL\n" +
371+
"OR A1.SIGN_TM = '' THEN CAST(\n" +
372+
"CAST(\n" +
373+
"CAST('ATkkIVQJZm' AS DATE FORMAT 'YYYYMMDD') AS DATE\n" +
374+
") || ' 00:00:00' AS TIMESTAMP\n" +
375+
")\n" +
376+
"WHEN CHARACTERS (TRIM(A1.SIGN_TM)) <> 19\n" +
377+
"OR SUBSTR (TRIM(A1.SIGN_TM), 1, 1) < '0'\n" +
378+
"OR SUBSTR (TRIM(A1.SIGN_TM), 1, 1) > '9'\n" +
379+
"OR SUBSTR (TRIM(A1.SIGN_TM), 2, 1) < '0'\n" +
380+
"OR SUBSTR (TRIM(A1.SIGN_TM), 2, 1) > '9'\n" +
381+
"OR SUBSTR (TRIM(A1.SIGN_TM), 3, 1) < '0'\n" +
382+
"OR SUBSTR (TRIM(A1.SIGN_TM), 3, 1) > '9'\n" +
383+
"OR SUBSTR (TRIM(A1.SIGN_TM), 4, 1) < '0'\n" +
384+
"OR SUBSTR (TRIM(A1.SIGN_TM), 4, 1) > '9'\n" +
385+
"OR SUBSTR (TRIM(A1.SIGN_TM), 6, 1) < '0'\n" +
386+
"OR SUBSTR (TRIM(A1.SIGN_TM), 6, 1) > '1'\n" +
387+
"OR SUBSTR (TRIM(A1.SIGN_TM), 7, 1) < '0'\n" +
388+
"OR SUBSTR (TRIM(A1.SIGN_TM), 7, 1) > '9'\n" +
389+
"OR SUBSTR (TRIM(A1.SIGN_TM), 9, 1) < '0'\n" +
390+
"OR SUBSTR (TRIM(A1.SIGN_TM), 9, 1) > '3'\n" +
391+
"OR SUBSTR (TRIM(A1.SIGN_TM), 10, 1) < '0'\n" +
392+
"OR SUBSTR (TRIM(A1.SIGN_TM), 10, 1) > '9'\n" +
393+
"OR SUBSTR (TRIM(A1.SIGN_TM), 1, 4) = '0000'\n" +
394+
"OR SUBSTR (TRIM(A1.SIGN_TM), 6, 2) = '00'\n" +
395+
"OR SUBSTR (TRIM(A1.SIGN_TM), 9, 2) = '00'\n" +
396+
"OR SUBSTR (TRIM(A1.SIGN_TM), 1, 1) = '0' THEN CAST(\n" +
397+
"CAST(\n" +
398+
"CAST('cDXtwdFyky' AS DATE FORMAT 'YYYYMMDD') AS DATE\n" +
399+
") || ' 00:00:00' AS TIMESTAMP\n" +
400+
")\n" +
401+
"ELSE (\n" +
402+
"CASE\n" +
403+
"WHEN (\n" +
404+
"CAST(SUBSTR (TRIM(A1.SIGN_TM), 9, 2) AS INTEGER) < 29\n" +
405+
"AND SUBSTR (TRIM(A1.SIGN_TM), 6, 2) = '02'\n" +
406+
")\n" +
407+
"OR (\n" +
408+
"CAST(SUBSTR (TRIM(A1.SIGN_TM), 9, 2) AS INTEGER) < 31\n" +
409+
"AND SUBSTR (TRIM(A1.SIGN_TM), 6, 2) <> '02'\n" +
410+
"AND SUBSTR (TRIM(A1.SIGN_TM), 6, 2) <= 12\n" +
411+
")\n" +
412+
"OR (\n" +
413+
"CAST(SUBSTR (TRIM(A1.SIGN_TM), 9, 2) AS INTEGER) = 31\n" +
414+
"AND SUBSTR (TRIM(A1.SIGN_TM), 6, 2) IN ('01', '03', '05', '07', '08', '10', '12')\n"
415+
+
416+
") THEN CAST(A1.SIGN_TM AS TIMESTAMP)\n" +
417+
"WHEN SUBSTR (TRIM(A1.SIGN_TM), 6, 2) || SUBSTR (TRIM(A1.SIGN_TM), 9, 2) = '0229'\n"
418+
+
419+
"AND (\n" +
420+
"CAST(SUBSTR (TRIM(A1.SIGN_TM), 1, 4) AS INTEGER) MOD 400 = 0\n" +
421+
"OR (\n" +
422+
"CAST(SUBSTR (TRIM(A1.SIGN_TM), 1, 4) AS INTEGER) MOD 4 = 0\n" +
423+
"AND CAST(SUBSTR (TRIM(A1.SIGN_TM), 1, 4) AS INTEGER) MOD 100 <> 0\n" +
424+
")\n" +
425+
") THEN CAST(A1.SIGN_TM AS TIMESTAMP)\n" +
426+
"ELSE CAST(\n" +
427+
"CAST(\n" +
428+
"CAST('cDXtwdFyky' AS DATE FORMAT 'YYYYMMDD') AS DATE\n" +
429+
") || ' 00:00:00' AS TIMESTAMP\n" +
430+
")\n" +
431+
"END\n" +
432+
")\n" +
433+
"END\n" +
434+
") AS DATE FORMAT 'YYYYMMDD'\n" +
435+
"),\n" +
436+
"CAST(\n" +
437+
"(\n" +
438+
"CASE\n" +
439+
"WHEN A1.CLOSE_TM IS NULL\n" +
440+
"OR A1.CLOSE_TM = '' THEN CAST(\n" +
441+
"CAST(\n" +
442+
"CAST('ATkkIVQJZm' AS DATE FORMAT 'YYYYMMDD') AS DATE\n" +
443+
") || ' 00:00:00' AS TIMESTAMP\n" +
444+
")\n" +
445+
"WHEN CHARACTERS (TRIM(A1.CLOSE_TM)) <> 19\n" +
446+
"OR SUBSTR (TRIM(A1.CLOSE_TM), 1, 1) < '0'\n" +
447+
"OR SUBSTR (TRIM(A1.CLOSE_TM), 1, 1) > '9'\n" +
448+
"OR SUBSTR (TRIM(A1.CLOSE_TM), 2, 1) < '0'\n" +
449+
"OR SUBSTR (TRIM(A1.CLOSE_TM), 2, 1) > '9'\n" +
450+
"OR SUBSTR (TRIM(A1.CLOSE_TM), 3, 1) < '0'\n" +
451+
"OR SUBSTR (TRIM(A1.CLOSE_TM), 3, 1) > '9'\n" +
452+
"OR SUBSTR (TRIM(A1.CLOSE_TM), 4, 1) < '0'\n" +
453+
"OR SUBSTR (TRIM(A1.CLOSE_TM), 4, 1) > '9'\n" +
454+
"OR SUBSTR (TRIM(A1.CLOSE_TM), 6, 1) < '0'\n" +
455+
"OR SUBSTR (TRIM(A1.CLOSE_TM), 6, 1) > '1'\n" +
456+
"OR SUBSTR (TRIM(A1.CLOSE_TM), 7, 1) < '0'\n" +
457+
"OR SUBSTR (TRIM(A1.CLOSE_TM), 7, 1) > '9'\n" +
458+
"OR SUBSTR (TRIM(A1.CLOSE_TM), 9, 1) < '0'\n" +
459+
"OR SUBSTR (TRIM(A1.CLOSE_TM), 9, 1) > '3'\n" +
460+
"OR SUBSTR (TRIM(A1.CLOSE_TM), 10, 1) < '0'\n" +
461+
"OR SUBSTR (TRIM(A1.CLOSE_TM), 10, 1) > '9'\n" +
462+
"OR SUBSTR (TRIM(A1.CLOSE_TM), 1, 4) = '0000'\n" +
463+
"OR SUBSTR (TRIM(A1.CLOSE_TM), 6, 2) = '00'\n" +
464+
"OR SUBSTR (TRIM(A1.CLOSE_TM), 9, 2) = '00'\n" +
465+
"OR SUBSTR (TRIM(A1.CLOSE_TM), 1, 1) = '0' THEN CAST(\n" +
466+
"CAST(\n" +
467+
"CAST('cDXtwdFyky' AS DATE FORMAT 'YYYYMMDD') AS DATE\n" +
468+
") || ' 00:00:00' AS TIMESTAMP\n" +
469+
")\n" +
470+
"ELSE (\n" +
471+
"CASE\n" +
472+
"WHEN (\n" +
473+
"CAST(SUBSTR (TRIM(A1.CLOSE_TM), 9, 2) AS INTEGER) < 29\n" +
474+
"AND SUBSTR (TRIM(A1.CLOSE_TM), 6, 2) = '02'\n" +
475+
")\n" +
476+
"OR (\n" +
477+
"CAST(SUBSTR (TRIM(A1.CLOSE_TM), 9, 2) AS INTEGER) < 31\n" +
478+
"AND SUBSTR (TRIM(A1.CLOSE_TM), 6, 2) <> '02'\n" +
479+
"AND SUBSTR (TRIM(A1.CLOSE_TM), 6, 2) <= 12\n" +
480+
")\n" +
481+
"OR (\n" +
482+
"CAST(SUBSTR (TRIM(A1.CLOSE_TM), 9, 2) AS INTEGER) = 31\n" +
483+
"AND SUBSTR (TRIM(A1.CLOSE_TM), 6, 2) IN ('01', '03', '05', '07', '08', '10', '12')\n"
484+
+
485+
") THEN CAST(A1.CLOSE_TM AS TIMESTAMP)\n" +
486+
"WHEN SUBSTR (TRIM(A1.CLOSE_TM), 6, 2) || SUBSTR (TRIM(A1.CLOSE_TM), 9, 2) = '0229'\n"
487+
+
488+
"AND (\n" +
489+
"CAST(SUBSTR (TRIM(A1.CLOSE_TM), 1, 4) AS INTEGER) MOD 400 = 0\n" +
490+
"OR (\n" +
491+
"CAST(SUBSTR (TRIM(A1.CLOSE_TM), 1, 4) AS INTEGER) MOD 4 = 0\n" +
492+
"AND CAST(SUBSTR (TRIM(A1.CLOSE_TM), 1, 4) AS INTEGER) MOD 100 <> 0\n" +
493+
")\n" +
494+
") THEN CAST(A1.CLOSE_TM AS TIMESTAMP)\n" +
495+
"ELSE CAST(\n" +
496+
"CAST(\n" +
497+
"CAST('cDXtwdFyky' AS DATE FORMAT 'YYYYMMDD') AS DATE\n" +
498+
") || ' 00:00:00' AS TIMESTAMP\n" +
499+
")\n" +
500+
"END\n" +
501+
")\n" +
502+
"END\n" +
503+
") AS DATE FORMAT 'YYYYMMDD'\n" +
504+
")\n" +
505+
"FROM\n" +
506+
"T01_PTY_SIGN_H_T1 A1\n" +
507+
"WHERE\n" +
508+
"A1.PARTY_SIGN_TYPE_CD = 'CD_021'\n" +
509+
"AND A1.ST_DT <= CAST('LDBCGtCIyo' AS DATE FORMAT 'YYYYMMDD')\n" +
510+
"AND A1.END_DT > CAST('LDBCGtCIyo' AS DATE FORMAT 'YYYYMMDD')\n" +
511+
"GROUP BY\n" +
512+
"1,\n" +
513+
"2,\n" +
514+
"3,\n" +
515+
"4";
516+
CCJSqlParserUtil.parse(sqlStr, parser -> parser
517+
.withSquareBracketQuotation(false)
518+
.withAllowComplexParsing(true)
519+
.withTimeOut(60000));
520+
}
356521
}

0 commit comments

Comments
 (0)