Skip to content

Commit 8787ba1

Browse files
hayssamsclaude
andcommitted
Support BigQuery ASSERT statement
Adds ASSERT expression [AS description] as a statement, wired through the statement visitors, deparser and table-name finder. ASSERT stays a non-reserved keyword. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EqinwHBKuAtPEmtXr3b5P2
1 parent 27a2785 commit 8787ba1

9 files changed

Lines changed: 173 additions & 0 deletions

File tree

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*-
2+
* #%L
3+
* JSQLParser library
4+
* %%
5+
* Copyright (C) 2004 - 2026 JSQLParser
6+
* %%
7+
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
8+
* #L%
9+
*/
10+
package net.sf.jsqlparser.statement;
11+
12+
import net.sf.jsqlparser.expression.Expression;
13+
import net.sf.jsqlparser.expression.StringValue;
14+
15+
/**
16+
* BigQuery's {@code ASSERT expression [AS description]}, which fails the script when the expression
17+
* does not evaluate to {@code TRUE}.
18+
*
19+
* @see <a href=
20+
* "https://cloud.google.com/bigquery/docs/reference/standard-sql/debugging-statements">Debugging
21+
* statements</a>
22+
*/
23+
public class AssertStatement implements Statement {
24+
private Expression expression;
25+
private StringValue description;
26+
27+
public AssertStatement() {}
28+
29+
public AssertStatement(Expression expression) {
30+
this.expression = expression;
31+
}
32+
33+
public AssertStatement(Expression expression, StringValue description) {
34+
this.expression = expression;
35+
this.description = description;
36+
}
37+
38+
public Expression getExpression() {
39+
return expression;
40+
}
41+
42+
public void setExpression(Expression expression) {
43+
this.expression = expression;
44+
}
45+
46+
public AssertStatement withExpression(Expression expression) {
47+
setExpression(expression);
48+
return this;
49+
}
50+
51+
public StringValue getDescription() {
52+
return description;
53+
}
54+
55+
public void setDescription(StringValue description) {
56+
this.description = description;
57+
}
58+
59+
public AssertStatement withDescription(StringValue description) {
60+
setDescription(description);
61+
return this;
62+
}
63+
64+
public StringBuilder appendTo(StringBuilder builder) {
65+
builder.append("ASSERT ").append(expression);
66+
if (description != null) {
67+
builder.append(" AS ").append(description);
68+
}
69+
return builder;
70+
}
71+
72+
@Override
73+
public String toString() {
74+
return appendTo(new StringBuilder()).toString();
75+
}
76+
77+
@Override
78+
public <T, S> T accept(StatementVisitor<T> statementVisitor, S context) {
79+
return statementVisitor.visit(this, context);
80+
}
81+
}

src/main/java/net/sf/jsqlparser/statement/StatementFeatureVisitor.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,13 @@ public <S> Void visit(Drop drop, S context) {
521521
return null;
522522
}
523523

524+
@Override
525+
public <S> Void visit(AssertStatement assertStatement, S context) {
526+
analysis.claimTopLevel();
527+
analysis.certain(StmtFeature.READS_DATA);
528+
return null;
529+
}
530+
524531
@Override
525532
public <S> Void visit(PurgeStatement purgeStatement, S context) {
526533
analysis.claimTopLevel();

src/main/java/net/sf/jsqlparser/statement/StatementVisitor.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,12 @@ default void visit(RenameTableStatement renameTableStatement) {
371371
this.visit(renameTableStatement, null);
372372
}
373373

374+
<S> T visit(AssertStatement assertStatement, S context);
375+
376+
default void visit(AssertStatement assertStatement) {
377+
this.visit(assertStatement, null);
378+
}
379+
374380
<S> T visit(PurgeStatement purgeStatement, S context);
375381

376382
default void visit(PurgeStatement purgeStatement) {

src/main/java/net/sf/jsqlparser/statement/StatementVisitorAdapter.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -618,6 +618,11 @@ public <S> T visit(RenameTableStatement renameTableStatement, S context) {
618618
return null;
619619
}
620620

621+
@Override
622+
public <S> T visit(AssertStatement assertStatement, S context) {
623+
return null;
624+
}
625+
621626
@Override
622627
public <S> T visit(PurgeStatement purgeStatement, S context) {
623628
return null;

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@
209209
import net.sf.jsqlparser.statement.update.Update;
210210
import net.sf.jsqlparser.statement.update.UpdateSet;
211211
import net.sf.jsqlparser.statement.upsert.Upsert;
212+
import net.sf.jsqlparser.statement.AssertStatement;
212213

213214

214215
/**
@@ -2464,6 +2465,17 @@ public void visit(RenameTableStatement renameTableStatement) {
24642465
StatementVisitor.super.visit(renameTableStatement);
24652466
}
24662467

2468+
@Override
2469+
public <S> Void visit(AssertStatement assertStatement, S context) {
2470+
assertStatement.getExpression().accept(this, context);
2471+
return null;
2472+
}
2473+
2474+
@Override
2475+
public void visit(AssertStatement assertStatement) {
2476+
StatementVisitor.super.visit(assertStatement);
2477+
}
2478+
24672479
@Override
24682480
public <S> Void visit(PurgeStatement purgeStatement, S context) {
24692481
if (purgeStatement.getPurgeObjectType() == PurgeObjectType.TABLE) {

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@
9797
import net.sf.jsqlparser.statement.update.ParenthesedUpdate;
9898
import net.sf.jsqlparser.statement.update.Update;
9999
import net.sf.jsqlparser.statement.upsert.Upsert;
100+
import net.sf.jsqlparser.statement.AssertStatement;
100101

101102
public class StatementDeParser extends AbstractDeParser<Statement>
102103
implements StatementVisitor<StringBuilder> {
@@ -552,6 +553,12 @@ public <S> StringBuilder visit(RenameTableStatement renameTableStatement, S cont
552553
return builder;
553554
}
554555

556+
@Override
557+
public <S> StringBuilder visit(AssertStatement assertStatement, S context) {
558+
assertStatement.appendTo(builder);
559+
return builder;
560+
}
561+
555562
@Override
556563
public <S> StringBuilder visit(PurgeStatement purgeStatement, S context) {
557564
purgeStatement.appendTo(builder);

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@
9999
import net.sf.jsqlparser.statement.upsert.Upsert;
100100
import net.sf.jsqlparser.util.validation.ValidationCapability;
101101
import net.sf.jsqlparser.util.validation.metadata.NamedObject;
102+
import net.sf.jsqlparser.statement.AssertStatement;
102103

103104
/**
104105
* @author gitmotte
@@ -477,6 +478,12 @@ public <S> Void visit(RenameTableStatement renameTableStatement, S context) {
477478
return null;
478479
}
479480

481+
@Override
482+
public <S> Void visit(AssertStatement assertStatement, S context) {
483+
// TODO: not yet implemented
484+
return null;
485+
}
486+
480487
@Override
481488
public <S> Void visit(PurgeStatement purgeStatement, S context) {
482489
// TODO: not yet implemented
@@ -693,6 +700,10 @@ public void visit(RenameTableStatement renameTableStatement) {
693700
visit(renameTableStatement, null);
694701
}
695702

703+
public void visit(AssertStatement assertStatement) {
704+
visit(assertStatement, null);
705+
}
706+
696707
public void visit(PurgeStatement purgeStatement) {
697708
visit(purgeStatement, null);
698709
}

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1933,6 +1933,7 @@ String NonReservedWord() :
19331933
| tk=<K_APPLY:"APPLY">
19341934
| tk=<K_APPROXIMATE:"APPROXIMATE">
19351935
| tk=<K_ARCHIVE: "ARCHIVE">
1936+
| tk=<K_ASSERT: "ASSERT">
19361937
| tk=<K_ARRAY_LITERAL: "ARRAY" >
19371938
| tk=<K_ASYMMETRIC: "ASYMMETRIC">
19381939
| tk=<K_AT: "AT">
@@ -2912,6 +2913,8 @@ Statement SingleStatement() :
29122913
|
29132914
LOOKAHEAD({ isKeywordAhead("REVOKE") }) stm = Revoke(false)
29142915
|
2916+
LOOKAHEAD({ getToken(1).kind == K_ASSERT }) stm = AssertStatement()
2917+
|
29152918
stm = PurgeStatement()
29162919
|
29172920
stm = SessionStatement()
@@ -4313,6 +4316,22 @@ RenameTableStatement RenameTableStatement(): {
43134316
}
43144317
}
43154318

4319+
/**
4320+
* BigQuery's ASSERT expression [AS description] debugging statement.
4321+
*/
4322+
AssertStatement AssertStatement() #AssertStatement:
4323+
{
4324+
AssertStatement assertStatement = new AssertStatement();
4325+
Expression expression;
4326+
Token token;
4327+
}
4328+
{
4329+
<K_ASSERT> expression=Expression() { assertStatement.setExpression(expression); }
4330+
[ <K_AS> token=<S_CHAR_LITERAL>
4331+
{ assertStatement.setDescription(new StringValue(token.image)); } ]
4332+
{ return assertStatement; }
4333+
}
4334+
43164335
PurgeStatement PurgeStatement(): {
43174336
PurgeStatement purgeStatement = null;
43184337
Table table;

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
import net.sf.jsqlparser.JSQLParserException;
1313
import net.sf.jsqlparser.expression.ArrayExpression;
14+
import net.sf.jsqlparser.statement.AssertStatement;
1415
import net.sf.jsqlparser.statement.create.table.CreateTable;
1516
import net.sf.jsqlparser.statement.create.table.TablePartitioning;
1617
import net.sf.jsqlparser.test.TestUtils;
@@ -214,4 +215,28 @@ void testCreateTablePartitionByHashStillParses() throws JSQLParserException {
214215
Assertions.assertEquals(TablePartitioning.Type.HASH,
215216
createTable.getPartitioning().getType());
216217
}
218+
219+
@Test
220+
void testAssertStatementWithDescription() throws JSQLParserException {
221+
String sqlStr = "ASSERT (SELECT COUNT(*) FROM t) > 0 AS 'table is empty'";
222+
AssertStatement assertStatement =
223+
(AssertStatement) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true);
224+
225+
Assertions.assertEquals("'table is empty'", assertStatement.getDescription().toString());
226+
}
227+
228+
@Test
229+
void testAssertStatementWithoutDescription() throws JSQLParserException {
230+
String sqlStr = "ASSERT EXISTS(SELECT 1 FROM t)";
231+
AssertStatement assertStatement =
232+
(AssertStatement) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true);
233+
234+
Assertions.assertNull(assertStatement.getDescription());
235+
}
236+
237+
@Test
238+
void testAssertRemainsUsableAsIdentifier() throws JSQLParserException {
239+
String sqlStr = "SELECT assert FROM t";
240+
TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true);
241+
}
217242
}

0 commit comments

Comments
 (0)