Skip to content

Commit 4acc89f

Browse files
authored
Support typed transaction starts, chaining and savepoint release (#2729)
* Model transaction starts, chaining, and savepoint release Signed-off-by: minleejae <mmj9808@gmail.com> * Reject contradictory MySQL transaction access modes Signed-off-by: minleejae <mmj9808@gmail.com> --------- Signed-off-by: minleejae <mmj9808@gmail.com>
1 parent 8cf8404 commit 4acc89f

18 files changed

Lines changed: 921 additions & 55 deletions

‎src/main/java/net/sf/jsqlparser/parser/feature/Feature.java‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -605,6 +605,18 @@ public enum Feature {
605605
* @see Commit
606606
*/
607607
commit,
608+
/** START TRANSACTION and dialect-specific BEGIN transaction statements. */
609+
startTransaction,
610+
/** RELEASE [SAVEPOINT] statements. */
611+
releaseSavepoint,
612+
/** Explicit AND [NO] CHAIN transaction completion. */
613+
transactionChain,
614+
/** Isolation level specified directly on BEGIN or START TRANSACTION. */
615+
transactionIsolationLevel,
616+
/** PostgreSQL [NOT] DEFERRABLE transaction characteristic. */
617+
transactionDeferrable,
618+
/** MySQL WITH CONSISTENT SNAPSHOT transaction characteristic. */
619+
transactionConsistentSnapshot,
608620
/**
609621
* SQL "COMMENT ON" statement is allowed
610622
*

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

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,54 @@
1010
package net.sf.jsqlparser.statement;
1111

1212
public class Commit implements Statement {
13+
private TransactionKeyword keyword;
14+
private TransactionChain chain;
15+
16+
public TransactionKeyword getKeyword() {
17+
return keyword;
18+
}
19+
20+
public void setKeyword(TransactionKeyword keyword) {
21+
this.keyword = keyword;
22+
}
23+
24+
public Commit withKeyword(TransactionKeyword keyword) {
25+
setKeyword(keyword);
26+
return this;
27+
}
28+
29+
public TransactionChain getChain() {
30+
return chain;
31+
}
32+
33+
public void setChain(TransactionChain chain) {
34+
this.chain = chain;
35+
}
36+
37+
public Commit withChain(TransactionChain chain) {
38+
setChain(chain);
39+
return this;
40+
}
41+
42+
1343
@Override
1444
public <T, S> T accept(StatementVisitor<T> statementVisitor, S context) {
1545
return statementVisitor.visit(this, context);
1646
}
1747

48+
public StringBuilder appendTo(StringBuilder builder) {
49+
builder.append("COMMIT");
50+
if (keyword != null) {
51+
builder.append(' ').append(keyword);
52+
}
53+
if (chain != null) {
54+
builder.append(' ').append(chain);
55+
}
56+
return builder;
57+
}
58+
1859
@Override
1960
public String toString() {
20-
return "COMMIT";
61+
return appendTo(new StringBuilder()).toString();
2162
}
2263
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
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 java.util.Objects;
13+
14+
/** RELEASE [SAVEPOINT] name. */
15+
public class ReleaseSavepointStatement implements Statement {
16+
private String savepointName;
17+
private boolean usingSavepointKeyword = true;
18+
19+
public ReleaseSavepointStatement(String savepointName) {
20+
setSavepointName(savepointName);
21+
}
22+
23+
public String getSavepointName() {
24+
return savepointName;
25+
}
26+
27+
public void setSavepointName(String savepointName) {
28+
this.savepointName = Objects.requireNonNull(savepointName, "savepointName");
29+
}
30+
31+
public ReleaseSavepointStatement withSavepointName(String savepointName) {
32+
setSavepointName(savepointName);
33+
return this;
34+
}
35+
36+
public boolean isUsingSavepointKeyword() {
37+
return usingSavepointKeyword;
38+
}
39+
40+
public void setUsingSavepointKeyword(boolean usingSavepointKeyword) {
41+
this.usingSavepointKeyword = usingSavepointKeyword;
42+
}
43+
44+
public ReleaseSavepointStatement withUsingSavepointKeyword(boolean usingSavepointKeyword) {
45+
setUsingSavepointKeyword(usingSavepointKeyword);
46+
return this;
47+
}
48+
49+
public StringBuilder appendTo(StringBuilder builder) {
50+
return builder.append("RELEASE ").append(usingSavepointKeyword ? "SAVEPOINT " : "")
51+
.append(savepointName);
52+
}
53+
54+
@Override
55+
public String toString() {
56+
return appendTo(new StringBuilder()).toString();
57+
}
58+
59+
@Override
60+
public <T, S> T accept(StatementVisitor<T> visitor, S context) {
61+
return visitor.visit(this, context);
62+
}
63+
}

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

Lines changed: 54 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -29,21 +29,52 @@
2929
* @author are
3030
*/
3131
public class RollbackStatement implements Statement {
32-
private boolean usingWorkKeyword = false;
32+
private TransactionKeyword keyword;
33+
private TransactionChain chain;
3334
private boolean usingSavepointKeyword = false;
3435
private String savepointName = null;
3536
private String forceDistributedTransactionIdentifier = null;
3637

38+
public TransactionKeyword getKeyword() {
39+
return keyword;
40+
}
41+
42+
public void setKeyword(TransactionKeyword keyword) {
43+
this.keyword = keyword;
44+
}
45+
46+
public RollbackStatement withKeyword(TransactionKeyword keyword) {
47+
setKeyword(keyword);
48+
return this;
49+
}
50+
51+
public TransactionChain getChain() {
52+
return chain;
53+
}
54+
55+
public void setChain(TransactionChain chain) {
56+
this.chain = chain;
57+
}
58+
59+
public RollbackStatement withChain(TransactionChain chain) {
60+
setChain(chain);
61+
return this;
62+
}
63+
3764
public boolean isUsingWorkKeyword() {
38-
return usingWorkKeyword;
65+
return keyword == TransactionKeyword.WORK;
3966
}
4067

4168
public void setUsingWorkKeyword(boolean usingWorkKeyword) {
42-
this.usingWorkKeyword = usingWorkKeyword;
69+
if (usingWorkKeyword) {
70+
keyword = TransactionKeyword.WORK;
71+
} else if (keyword == TransactionKeyword.WORK) {
72+
keyword = null;
73+
}
4374
}
4475

4576
public RollbackStatement withUsingWorkKeyword(boolean usingWorkKeyword) {
46-
this.usingWorkKeyword = usingWorkKeyword;
77+
setUsingWorkKeyword(usingWorkKeyword);
4778
return this;
4879
}
4980

@@ -88,22 +119,27 @@ public RollbackStatement withForceDistributedTransactionIdentifier(
88119
return this;
89120
}
90121

122+
public StringBuilder appendTo(StringBuilder builder) {
123+
builder.append("ROLLBACK");
124+
if (keyword != null) {
125+
builder.append(' ').append(keyword);
126+
}
127+
if (savepointName != null && !savepointName.trim().isEmpty()) {
128+
builder.append(" TO ").append(usingSavepointKeyword ? "SAVEPOINT " : "")
129+
.append(savepointName);
130+
} else if (forceDistributedTransactionIdentifier != null
131+
&& !forceDistributedTransactionIdentifier.trim().isEmpty()) {
132+
builder.append(" FORCE ").append(forceDistributedTransactionIdentifier);
133+
}
134+
if (chain != null) {
135+
builder.append(' ').append(chain);
136+
}
137+
return builder;
138+
}
139+
91140
@Override
92141
public String toString() {
93-
return "ROLLBACK "
94-
+ (usingWorkKeyword
95-
? "WORK "
96-
: "")
97-
+ (savepointName != null && !savepointName.trim().isEmpty()
98-
? "TO " + (usingSavepointKeyword
99-
? "SAVEPOINT "
100-
: "") + savepointName
101-
: forceDistributedTransactionIdentifier != null
102-
&& !forceDistributedTransactionIdentifier.trim().isEmpty()
103-
? "FORCE " + forceDistributedTransactionIdentifier
104-
: ""
105-
106-
);
142+
return appendTo(new StringBuilder()).toString();
107143
}
108144

109145
@Override
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
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 java.util.ArrayList;
13+
import java.util.List;
14+
import java.util.Objects;
15+
16+
/** START TRANSACTION, or BEGIN when a transaction dialect is selected. */
17+
public class StartTransaction implements Statement {
18+
public enum Command {
19+
START_TRANSACTION, BEGIN
20+
}
21+
22+
public enum Mode {
23+
ISOLATION_LEVEL_SERIALIZABLE(
24+
"ISOLATION LEVEL SERIALIZABLE"), ISOLATION_LEVEL_REPEATABLE_READ(
25+
"ISOLATION LEVEL REPEATABLE READ"), ISOLATION_LEVEL_READ_COMMITTED(
26+
"ISOLATION LEVEL READ COMMITTED"), ISOLATION_LEVEL_READ_UNCOMMITTED(
27+
"ISOLATION LEVEL READ UNCOMMITTED"), READ_WRITE(
28+
"READ WRITE"), READ_ONLY("READ ONLY"), DEFERRABLE(
29+
"DEFERRABLE"), NOT_DEFERRABLE(
30+
"NOT DEFERRABLE"), WITH_CONSISTENT_SNAPSHOT(
31+
"WITH CONSISTENT SNAPSHOT");
32+
33+
private final String sql;
34+
35+
Mode(String sql) {
36+
this.sql = sql;
37+
}
38+
39+
@Override
40+
public String toString() {
41+
return sql;
42+
}
43+
}
44+
45+
private Command command = Command.START_TRANSACTION;
46+
private TransactionKeyword keyword;
47+
private final List<Mode> modes = new ArrayList<>();
48+
49+
public Command getCommand() {
50+
return command;
51+
}
52+
53+
public void setCommand(Command command) {
54+
this.command = Objects.requireNonNull(command, "command");
55+
}
56+
57+
public StartTransaction withCommand(Command command) {
58+
setCommand(command);
59+
return this;
60+
}
61+
62+
public TransactionKeyword getKeyword() {
63+
return keyword;
64+
}
65+
66+
public void setKeyword(TransactionKeyword keyword) {
67+
this.keyword = keyword;
68+
}
69+
70+
public StartTransaction withKeyword(TransactionKeyword keyword) {
71+
setKeyword(keyword);
72+
return this;
73+
}
74+
75+
/** Mutable, ordered transaction characteristics. */
76+
public List<Mode> getModes() {
77+
return modes;
78+
}
79+
80+
public StartTransaction addMode(Mode mode) {
81+
modes.add(Objects.requireNonNull(mode, "mode"));
82+
return this;
83+
}
84+
85+
public StringBuilder appendTo(StringBuilder builder) {
86+
builder.append(command == Command.BEGIN ? "BEGIN" : "START TRANSACTION");
87+
if (command == Command.BEGIN && keyword != null) {
88+
builder.append(' ').append(keyword);
89+
}
90+
for (int i = 0; i < modes.size(); i++) {
91+
builder.append(i == 0 ? " " : ", ").append(modes.get(i));
92+
}
93+
return builder;
94+
}
95+
96+
@Override
97+
public String toString() {
98+
return appendTo(new StringBuilder()).toString();
99+
}
100+
101+
@Override
102+
public <T, S> T accept(StatementVisitor<T> visitor, S context) {
103+
return visitor.visit(this, context);
104+
}
105+
}

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -781,6 +781,16 @@ public <S> Void visit(IfElseStatement ifElseStatement, S context) {
781781

782782
// ---- session / transaction --------------------------------------------------------------
783783

784+
@Override
785+
public <S> Void visit(StartTransaction statement, S context) {
786+
return transactionOnly();
787+
}
788+
789+
@Override
790+
public <S> Void visit(ReleaseSavepointStatement statement, S context) {
791+
return transactionOnly();
792+
}
793+
784794
@Override
785795
public <S> Void visit(Commit commit, S context) {
786796
return transactionOnly();

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,22 @@ default void visit(Comment comment) {
125125
this.visit(comment, null);
126126
}
127127

128+
default <S> T visit(StartTransaction statement, S context) {
129+
return null;
130+
}
131+
132+
default void visit(StartTransaction statement) {
133+
visit(statement, null);
134+
}
135+
136+
default <S> T visit(ReleaseSavepointStatement statement, S context) {
137+
return null;
138+
}
139+
140+
default void visit(ReleaseSavepointStatement statement) {
141+
visit(statement, null);
142+
}
143+
128144
<S> T visit(Commit commit, S context);
129145

130146
default void visit(Commit commit) {

0 commit comments

Comments
 (0)