Skip to content

Commit 4ae2499

Browse files
hayssamsclaude
andcommitted
Support PREPARE and DEALLOCATE statements
Adds PREPARE name AS statement and DEALLOCATE [PREPARE] name. EXECUTE of a prepared statement already parsed. The prepared statement is a nested Statement, so TablesNamesFinder reaches its tables. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EqinwHBKuAtPEmtXr3b5P2
1 parent 003b9dc commit 4ae2499

10 files changed

Lines changed: 316 additions & 0 deletions

File tree

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
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+
/**
13+
* {@code DEALLOCATE [PREPARE] name}, which drops a statement prepared by
14+
* {@link PrepareStatement}.
15+
*/
16+
public class DeallocateStatement implements Statement {
17+
private String name;
18+
private boolean usingPrepareKeyword;
19+
20+
public DeallocateStatement() {}
21+
22+
public DeallocateStatement(String name) {
23+
this.name = name;
24+
}
25+
26+
public String getName() {
27+
return name;
28+
}
29+
30+
public void setName(String name) {
31+
this.name = name;
32+
}
33+
34+
public DeallocateStatement withName(String name) {
35+
setName(name);
36+
return this;
37+
}
38+
39+
public boolean isUsingPrepareKeyword() {
40+
return usingPrepareKeyword;
41+
}
42+
43+
public void setUsingPrepareKeyword(boolean usingPrepareKeyword) {
44+
this.usingPrepareKeyword = usingPrepareKeyword;
45+
}
46+
47+
public DeallocateStatement withUsingPrepareKeyword(boolean usingPrepareKeyword) {
48+
setUsingPrepareKeyword(usingPrepareKeyword);
49+
return this;
50+
}
51+
52+
public StringBuilder appendTo(StringBuilder builder) {
53+
builder.append("DEALLOCATE ");
54+
if (usingPrepareKeyword) {
55+
builder.append("PREPARE ");
56+
}
57+
builder.append(name);
58+
return builder;
59+
}
60+
61+
@Override
62+
public String toString() {
63+
return appendTo(new StringBuilder()).toString();
64+
}
65+
66+
@Override
67+
public <T, S> T accept(StatementVisitor<T> statementVisitor, S context) {
68+
return statementVisitor.visit(this, context);
69+
}
70+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
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+
/**
13+
* {@code PREPARE name AS statement}, which stores a parameterised statement for later
14+
* {@code EXECUTE}.
15+
*
16+
* @see <a href="https://duckdb.org/docs/stable/sql/query_syntax/prepared_statements">Prepared
17+
* statements</a>
18+
*/
19+
public class PrepareStatement implements Statement {
20+
private String name;
21+
private Statement statement;
22+
23+
public PrepareStatement() {}
24+
25+
public PrepareStatement(String name, Statement statement) {
26+
this.name = name;
27+
this.statement = statement;
28+
}
29+
30+
public String getName() {
31+
return name;
32+
}
33+
34+
public void setName(String name) {
35+
this.name = name;
36+
}
37+
38+
public PrepareStatement withName(String name) {
39+
setName(name);
40+
return this;
41+
}
42+
43+
public Statement getStatement() {
44+
return statement;
45+
}
46+
47+
public void setStatement(Statement statement) {
48+
this.statement = statement;
49+
}
50+
51+
public PrepareStatement withStatement(Statement statement) {
52+
setStatement(statement);
53+
return this;
54+
}
55+
56+
public StringBuilder appendTo(StringBuilder builder) {
57+
builder.append("PREPARE ").append(name).append(" AS ").append(statement);
58+
return builder;
59+
}
60+
61+
@Override
62+
public String toString() {
63+
return appendTo(new StringBuilder()).toString();
64+
}
65+
66+
@Override
67+
public <T, S> T accept(StatementVisitor<T> statementVisitor, S context) {
68+
return statementVisitor.visit(this, context);
69+
}
70+
}

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,20 @@ public <S> Void visit(DisconnectStatement disconnectStatement, S context) {
563563
return null;
564564
}
565565

566+
@Override
567+
public <S> Void visit(PrepareStatement prepareStatement, S context) {
568+
analysis.claimTopLevel();
569+
analysis.certain(StmtFeature.MODIFIES_SESSION);
570+
return null;
571+
}
572+
573+
@Override
574+
public <S> Void visit(DeallocateStatement deallocateStatement, S context) {
575+
analysis.claimTopLevel();
576+
analysis.certain(StmtFeature.MODIFIES_SESSION);
577+
return null;
578+
}
579+
566580
@Override
567581
public <S> Void visit(PurgeStatement purgeStatement, S context) {
568582
analysis.claimTopLevel();

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,18 @@ default void visit(DisconnectStatement disconnectStatement) {
407407
this.visit(disconnectStatement, null);
408408
}
409409

410+
<S> T visit(PrepareStatement prepareStatement, S context);
411+
412+
default void visit(PrepareStatement prepareStatement) {
413+
this.visit(prepareStatement, null);
414+
}
415+
416+
<S> T visit(DeallocateStatement deallocateStatement, S context);
417+
418+
default void visit(DeallocateStatement deallocateStatement) {
419+
this.visit(deallocateStatement, null);
420+
}
421+
410422
<S> T visit(PurgeStatement purgeStatement, S context);
411423

412424
default void visit(PurgeStatement purgeStatement) {

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -648,6 +648,16 @@ public <S> T visit(DisconnectStatement disconnectStatement, S context) {
648648
return null;
649649
}
650650

651+
@Override
652+
public <S> T visit(PrepareStatement prepareStatement, S context) {
653+
return null;
654+
}
655+
656+
@Override
657+
public <S> T visit(DeallocateStatement deallocateStatement, S context) {
658+
return null;
659+
}
660+
651661
@Override
652662
public <S> T visit(PurgeStatement purgeStatement, S context) {
653663
return null;

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,8 @@
215215
import net.sf.jsqlparser.statement.DetachStatement;
216216
import net.sf.jsqlparser.statement.ConnectStatement;
217217
import net.sf.jsqlparser.statement.DisconnectStatement;
218+
import net.sf.jsqlparser.statement.PrepareStatement;
219+
import net.sf.jsqlparser.statement.DeallocateStatement;
218220

219221

220222
/**
@@ -2540,6 +2542,28 @@ public void visit(DisconnectStatement disconnectStatement) {
25402542
StatementVisitor.super.visit(disconnectStatement);
25412543
}
25422544

2545+
@Override
2546+
public <S> Void visit(PrepareStatement prepareStatement, S context) {
2547+
prepareStatement.getStatement().accept(this, context);
2548+
return null;
2549+
}
2550+
2551+
@Override
2552+
public void visit(PrepareStatement prepareStatement) {
2553+
StatementVisitor.super.visit(prepareStatement);
2554+
}
2555+
2556+
@Override
2557+
public <S> Void visit(DeallocateStatement deallocateStatement, S context) {
2558+
// no tables involved in this statement
2559+
return null;
2560+
}
2561+
2562+
@Override
2563+
public void visit(DeallocateStatement deallocateStatement) {
2564+
StatementVisitor.super.visit(deallocateStatement);
2565+
}
2566+
25432567
@Override
25442568
public <S> Void visit(PurgeStatement purgeStatement, S context) {
25452569
if (purgeStatement.getPurgeObjectType() == PurgeObjectType.TABLE) {

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@
103103
import net.sf.jsqlparser.statement.DetachStatement;
104104
import net.sf.jsqlparser.statement.ConnectStatement;
105105
import net.sf.jsqlparser.statement.DisconnectStatement;
106+
import net.sf.jsqlparser.statement.PrepareStatement;
107+
import net.sf.jsqlparser.statement.DeallocateStatement;
106108

107109
public class StatementDeParser extends AbstractDeParser<Statement>
108110
implements StatementVisitor<StringBuilder> {
@@ -598,6 +600,18 @@ public <S> StringBuilder visit(DisconnectStatement disconnectStatement, S contex
598600
return builder;
599601
}
600602

603+
@Override
604+
public <S> StringBuilder visit(PrepareStatement prepareStatement, S context) {
605+
prepareStatement.appendTo(builder);
606+
return builder;
607+
}
608+
609+
@Override
610+
public <S> StringBuilder visit(DeallocateStatement deallocateStatement, S context) {
611+
deallocateStatement.appendTo(builder);
612+
return builder;
613+
}
614+
601615
@Override
602616
public <S> StringBuilder visit(PurgeStatement purgeStatement, S context) {
603617
purgeStatement.appendTo(builder);

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@
105105
import net.sf.jsqlparser.statement.DetachStatement;
106106
import net.sf.jsqlparser.statement.ConnectStatement;
107107
import net.sf.jsqlparser.statement.DisconnectStatement;
108+
import net.sf.jsqlparser.statement.PrepareStatement;
109+
import net.sf.jsqlparser.statement.DeallocateStatement;
108110

109111
/**
110112
* @author gitmotte
@@ -519,6 +521,18 @@ public <S> Void visit(DisconnectStatement disconnectStatement, S context) {
519521
return null;
520522
}
521523

524+
@Override
525+
public <S> Void visit(PrepareStatement prepareStatement, S context) {
526+
// TODO: not yet implemented
527+
return null;
528+
}
529+
530+
@Override
531+
public <S> Void visit(DeallocateStatement deallocateStatement, S context) {
532+
// TODO: not yet implemented
533+
return null;
534+
}
535+
522536
@Override
523537
public <S> Void visit(PurgeStatement purgeStatement, S context) {
524538
// TODO: not yet implemented
@@ -759,6 +773,14 @@ public void visit(DisconnectStatement disconnectStatement) {
759773
visit(disconnectStatement, null);
760774
}
761775

776+
public void visit(PrepareStatement prepareStatement) {
777+
visit(prepareStatement, null);
778+
}
779+
780+
public void visit(DeallocateStatement deallocateStatement) {
781+
visit(deallocateStatement, null);
782+
}
783+
762784
public void visit(PurgeStatement purgeStatement) {
763785
visit(purgeStatement, null);
764786
}

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

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2020,6 +2020,7 @@ String NonReservedWord() :
20202020
| tk=<K_DBA_RECYCLEBIN: "DBA_RECYCLEBIN">
20212021
| tk=<K_DEFAULTS: "DEFAULTS">
20222022
| tk=<K_DEPTH: "DEPTH">
2023+
| tk=<K_DEALLOCATE: "DEALLOCATE">
20232024
| tk=<K_DEFERRABLE : "DEFERRABLE">
20242025
| tk=<K_DELAYED : "DELAYED">
20252026
| tk=<K_DELETE:"DELETE">
@@ -2204,6 +2205,7 @@ String NonReservedWord() :
22042205
| tk=<K_PLAN:"PLAN">
22052206
| tk=<K_PLUS:"PLUS">
22062207
| tk=<K_PRAGMA: "PRAGMA">
2208+
| tk=<K_PREPARE: "PREPARE">
22072209
| tk=<K_PRECEDING: "PRECEDING">
22082210
| tk=<K_PRIMARY:"PRIMARY">
22092211
| tk=<K_POLICY:"POLICY">
@@ -2958,6 +2960,10 @@ Statement SingleStatement() :
29582960
LOOKAHEAD({ getToken(1).kind == K_DISCONNECT && getToken(2).kind != K_SESSION })
29592961
stm = DisconnectStatement()
29602962
|
2963+
LOOKAHEAD({ getToken(1).kind == K_PREPARE }) stm = PrepareStatement()
2964+
|
2965+
LOOKAHEAD({ getToken(1).kind == K_DEALLOCATE }) stm = DeallocateStatement()
2966+
|
29612967
stm = PurgeStatement()
29622968
|
29632969
stm = SessionStatement()
@@ -4359,6 +4365,37 @@ RenameTableStatement RenameTableStatement(): {
43594365
}
43604366
}
43614367

4368+
/**
4369+
* PREPARE name AS statement, storing a parameterised statement for later EXECUTE.
4370+
*/
4371+
PrepareStatement PrepareStatement() #PrepareStatement:
4372+
{
4373+
PrepareStatement prepareStatement = new PrepareStatement();
4374+
ObjectNames name;
4375+
Statement statement;
4376+
}
4377+
{
4378+
<K_PREPARE> name=RelObjectNames()
4379+
{ prepareStatement.setName(String.join(".", name.getNames())); }
4380+
<K_AS> statement=SingleStatement() { prepareStatement.setStatement(statement); }
4381+
{ return prepareStatement; }
4382+
}
4383+
4384+
/**
4385+
* DEALLOCATE [PREPARE] name.
4386+
*/
4387+
DeallocateStatement DeallocateStatement() #DeallocateStatement:
4388+
{
4389+
DeallocateStatement deallocateStatement = new DeallocateStatement();
4390+
ObjectNames name;
4391+
}
4392+
{
4393+
<K_DEALLOCATE>
4394+
[ LOOKAHEAD(2) <K_PREPARE> { deallocateStatement.setUsingPrepareKeyword(true); } ]
4395+
name=RelObjectNames() { deallocateStatement.setName(String.join(".", name.getNames())); }
4396+
{ return deallocateStatement; }
4397+
}
4398+
43624399
/**
43634400
* DuckDB's CONNECT target [AS alias], routing the session to a remote database.
43644401
*/

0 commit comments

Comments
 (0)