Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ and missing syntax gets added on demand — [open an issue](https://github.com/J
| **DDL** | `CREATE …` · `ALTER …` · `DROP …` |
| **Oracle vector types** | `VECTOR(*, *)` · `VECTOR(*, FLOAT32)` · `VECTOR(3, *)`, including dense and sparse storage formats |
| **PostgreSQL ownership** | `DROP OWNED` · `REASSIGN OWNED` |
| **Two-phase commit** | PostgreSQL `PREPARE TRANSACTION` · `COMMIT PREPARED` · `ROLLBACK PREPARED` · MySQL `XA START`/`END`/`PREPARE`/`COMMIT`/`ROLLBACK`/`RECOVER` |
| **MySQL table maintenance** | `ANALYZE` · `CHECK` · `CHECKSUM` · `OPTIMIZE` · `REPAIR TABLE` |
| **PostgreSQL maintenance** | `VACUUM` · `ANALYZE` · `CLUSTER` · `CHECKPOINT` |
| **MySQL locks** | `LOCK TABLES` · `UNLOCK TABLES` · `LOCK INSTANCE FOR BACKUP` · `UNLOCK INSTANCE` |
Expand Down
14 changes: 14 additions & 0 deletions src/main/java/net/sf/jsqlparser/parser/feature/Feature.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import net.sf.jsqlparser.statement.DeclareStatement;
import net.sf.jsqlparser.statement.DescribeStatement;
import net.sf.jsqlparser.statement.ExplainStatement;
import net.sf.jsqlparser.statement.PrepareTransactionStatement;
import net.sf.jsqlparser.statement.ResetStatement;
import net.sf.jsqlparser.statement.SetConstraintsStatement;
import net.sf.jsqlparser.statement.SetStatement;
Expand All @@ -30,6 +31,7 @@
import net.sf.jsqlparser.statement.ShowStatement;
import net.sf.jsqlparser.statement.TableMaintenanceStatement;
import net.sf.jsqlparser.statement.UseStatement;
import net.sf.jsqlparser.statement.XaStatement;
import net.sf.jsqlparser.statement.alter.Alter;
import net.sf.jsqlparser.statement.alter.AlterEventTrigger;
import net.sf.jsqlparser.statement.alter.sequence.AlterSequence;
Expand Down Expand Up @@ -764,6 +766,18 @@ public enum Feature {
* @see SetConstraintsStatement
*/
setConstraints,
/**
* PostgreSQL two-phase commit: PREPARE TRANSACTION, COMMIT PREPARED and ROLLBACK PREPARED
*
* @see PrepareTransactionStatement
*/
twoPhaseCommit,
/**
* MySQL and MariaDB XA statements: XA START, END, PREPARE, COMMIT, ROLLBACK and RECOVER
*
* @see XaStatement
*/
xa,
/**
* SQL "LOCK TABLE" statement is allowed
*
Expand Down
15 changes: 14 additions & 1 deletion src/main/java/net/sf/jsqlparser/statement/Commit.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@

import java.util.Objects;

/** COMMIT, or its PostgreSQL spelling END. */
/**
* COMMIT, or its PostgreSQL spelling END. PostgreSQL {@code COMMIT PREPARED 'id'} commits a
* prepared transaction instead of the current one.
*/
public class Commit extends TransactionCompletionStatement {
/** The keyword that starts the statement; both commit the current transaction. */
public enum Command {
Expand Down Expand Up @@ -48,6 +51,15 @@ public Commit withRelease(TransactionRelease release) {
return this;
}

/**
* Names the prepared transaction of PostgreSQL {@code COMMIT PREPARED 'id'}; null commits the
* current transaction.
*/
public Commit withPreparedTransaction(TransactionId id) {
setPreparedTransaction(PreparedTransactionKeyword.PREPARED, id);
return this;
}

@Override
public boolean isPostgresSpelling() {
return command == Command.END;
Expand All @@ -62,6 +74,7 @@ public <T, S> T accept(StatementVisitor<T> statementVisitor, S context) {
public StringBuilder appendTo(StringBuilder builder) {
builder.append(command);
appendKeyword(builder);
appendPreparedTransaction(builder);
return appendCompletion(builder);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*-
* #%L
* JSQLParser library
* %%
* Copyright (C) 2004 - 2026 JSQLParser
* %%
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
* #L%
*/
package net.sf.jsqlparser.statement;

import java.util.Objects;
import net.sf.jsqlparser.parser.ASTNodeAccessImpl;

/**
* PostgreSQL {@code PREPARE TRANSACTION 'id'}, which prepares the current transaction for two-phase
* commit. {@link Commit} and {@link RollbackStatement} complete it later with
* {@code COMMIT PREPARED 'id'} and {@code ROLLBACK PREPARED 'id'}. Not to be confused with
* {@link PrepareStatement}, which prepares a statement.
*
* @see <a href="https://www.postgresql.org/docs/current/sql-prepare-transaction.html">PostgreSQL
* PREPARE TRANSACTION</a>
*/
public class PrepareTransactionStatement extends ASTNodeAccessImpl implements Statement {
private TransactionId transactionId;

public PrepareTransactionStatement(TransactionId transactionId) {
setTransactionId(transactionId);
}

/** The identifier that COMMIT PREPARED and ROLLBACK PREPARED name later. */
public TransactionId getTransactionId() {
return transactionId;
}

public void setTransactionId(TransactionId transactionId) {
this.transactionId = Objects.requireNonNull(transactionId, "transactionId");
}

public PrepareTransactionStatement withTransactionId(TransactionId transactionId) {
setTransactionId(transactionId);
return this;
}

public StringBuilder appendTo(StringBuilder builder) {
return transactionId.appendTo(builder.append("PREPARE TRANSACTION "));
}

@Override
public String toString() {
return appendTo(new StringBuilder()).toString();
}

@Override
public <T, S> T accept(StatementVisitor<T> statementVisitor, S context) {
return statementVisitor.visit(this, context);
}
}
53 changes: 44 additions & 9 deletions src/main/java/net/sf/jsqlparser/statement/RollbackStatement.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,22 +26,23 @@
package net.sf.jsqlparser.statement;

import java.util.Objects;
import net.sf.jsqlparser.expression.StringValue;

/**
* ROLLBACK, or its PostgreSQL spelling ABORT.
* ROLLBACK, or its PostgreSQL spelling ABORT. PostgreSQL {@code ROLLBACK PREPARED 'id'} and Oracle
* {@code ROLLBACK FORCE 'id'} roll back a prepared transaction instead of the current one.
*
* @author are
*/
public class RollbackStatement extends TransactionCompletionStatement {
/** The keyword that starts the statement; ABORT has no savepoint or FORCE form. */
/** The keyword that starts the statement; ABORT has no savepoint, FORCE or PREPARED form. */
public enum Command {
ROLLBACK, ABORT
}

private Command command = Command.ROLLBACK;
private boolean usingSavepointKeyword = false;
private String savepointName = null;
private String forceDistributedTransactionIdentifier = null;

public Command getCommand() {
return command;
Expand Down Expand Up @@ -71,6 +72,16 @@ public RollbackStatement withRelease(TransactionRelease release) {
return this;
}

/**
* Names the prepared transaction of PostgreSQL {@code ROLLBACK PREPARED 'id'} or Oracle
* {@code ROLLBACK FORCE 'id'}; a null identifier rolls back the current transaction.
*/
public RollbackStatement withPreparedTransaction(PreparedTransactionKeyword keyword,
TransactionId id) {
setPreparedTransaction(keyword, id);
return this;
}

public boolean isUsingWorkKeyword() {
return getKeyword() == TransactionKeyword.WORK;
}
Expand Down Expand Up @@ -114,18 +125,43 @@ public RollbackStatement withSavepointName(String savepointName) {
return this;
}

/**
* The identifier of Oracle {@code ROLLBACK FORCE 'id'} as written, with its quotes, or null.
* Derived from {@link #getPreparedTransactionId()} when the keyword is
* {@link PreparedTransactionKeyword#FORCE}.
*/
public String getForceDistributedTransactionIdentifier() {
return forceDistributedTransactionIdentifier;
return getPreparedTransactionKeyword() == PreparedTransactionKeyword.FORCE
? getPreparedTransactionId().toString()
: null;
}

/**
* Sets the identifier of Oracle {@code ROLLBACK FORCE 'id'} as it is to be written. It is
* stored as the {@link StringValue} of {@link #getPreparedTransactionId()}; text that is not a
* string literal is kept verbatim. Null or blank removes a FORCE identifier.
*/
public void setForceDistributedTransactionIdentifier(
String forceDistributedTransactionIdentifier) {
this.forceDistributedTransactionIdentifier = forceDistributedTransactionIdentifier;
if (forceDistributedTransactionIdentifier != null
&& !forceDistributedTransactionIdentifier.trim().isEmpty()) {
setPreparedTransaction(PreparedTransactionKeyword.FORCE,
new TransactionId(asWritten(forceDistributedTransactionIdentifier)));
} else if (getPreparedTransactionKeyword() == PreparedTransactionKeyword.FORCE) {
setPreparedTransaction(null, null);
}
}

/** The literal {@code text} spells, or the text itself when it is not a string literal. */
private static StringValue asWritten(String text) {
StringValue literal = new StringValue(text);
return literal.toString().equals(text) ? literal
: new StringValue().withValue(text).setQuoteStr("");
}

public RollbackStatement withForceDistributedTransactionIdentifier(
String forceDistributedTransactionIdentifier) {
this.forceDistributedTransactionIdentifier = forceDistributedTransactionIdentifier;
setForceDistributedTransactionIdentifier(forceDistributedTransactionIdentifier);
return this;
}

Expand All @@ -141,9 +177,8 @@ public StringBuilder appendTo(StringBuilder builder) {
if (savepointName != null && !savepointName.trim().isEmpty()) {
builder.append(" TO ").append(usingSavepointKeyword ? "SAVEPOINT " : "")
.append(savepointName);
} else if (forceDistributedTransactionIdentifier != null
&& !forceDistributedTransactionIdentifier.trim().isEmpty()) {
builder.append(" FORCE ").append(forceDistributedTransactionIdentifier);
} else {
appendPreparedTransaction(builder);
}
return appendCompletion(builder);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -915,6 +915,18 @@ public <S> Void visit(SetConstraintsStatement statement, S context) {
return transactionOnly();
}

@Override
public <S> Void visit(PrepareTransactionStatement statement, S context) {
return transactionOnly();
}

/** XA RECOVER lists the prepared transactions; the other actions control a branch. */
@Override
public <S> Void visit(XaStatement statement, S context) {
return statement.getAction() == XaStatement.Action.RECOVER ? metadataOnly()
: transactionOnly();
}

@Override
public <S> Void visit(Commit commit, S context) {
return transactionOnly();
Expand Down
16 changes: 16 additions & 0 deletions src/main/java/net/sf/jsqlparser/statement/StatementVisitor.java
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,22 @@ default void visit(SetConstraintsStatement statement) {
visit(statement, null);
}

default <S> T visit(PrepareTransactionStatement statement, S context) {
return null;
}

default void visit(PrepareTransactionStatement statement) {
visit(statement, null);
}

default <S> T visit(XaStatement statement, S context) {
return null;
}

default void visit(XaStatement statement) {
visit(statement, null);
}

<S> T visit(Commit commit, S context);

default void visit(Commit commit) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,16 @@ public <S> T visit(SetConstraintsStatement statement, S context) {
return null;
}

@Override
public <S> T visit(PrepareTransactionStatement statement, S context) {
return null;
}

@Override
public <S> T visit(XaStatement statement, S context) {
return null;
}

@Override
public <S> T visit(Commit commit, S context) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,31 @@
*/
package net.sf.jsqlparser.statement;

import java.util.Objects;
import net.sf.jsqlparser.parser.ASTNodeAccessImpl;

/**
* The clauses shared by {@link Commit} and {@link RollbackStatement}:
* {@code [WORK | TRANSACTION] ... [AND [NO] CHAIN] [[NO] RELEASE]}. Null fields are omitted.
* {@code [WORK | TRANSACTION] ... [AND [NO] CHAIN] [[NO] RELEASE]}, or the identifier of a prepared
* transaction that the statement completes instead of the current one. Null fields are omitted.
*/
public abstract class TransactionCompletionStatement extends ASTNodeAccessImpl
implements Statement {
/** The keyword before the identifier of a prepared transaction. */
public enum PreparedTransactionKeyword {
/** PostgreSQL {@code COMMIT PREPARED 'id'} and {@code ROLLBACK PREPARED 'id'}. */
PREPARED,
/**
* Oracle {@code ROLLBACK [WORK] FORCE 'id'}, naming an in-doubt distributed transaction.
*/
FORCE
}

private TransactionKeyword keyword;
private TransactionChain chain;
private TransactionRelease release;
private PreparedTransactionKeyword preparedTransactionKeyword;
private TransactionId preparedTransactionId;

public TransactionKeyword getKeyword() {
return keyword;
Expand All @@ -45,6 +59,31 @@ public void setRelease(TransactionRelease release) {
this.release = release;
}

/** {@code PREPARED} or {@code FORCE} when a prepared transaction is named, otherwise null. */
public PreparedTransactionKeyword getPreparedTransactionKeyword() {
return preparedTransactionKeyword;
}

/**
* The identifier of the prepared transaction to complete, written after
* {@link #getPreparedTransactionKeyword()}, or null when the current transaction completes.
*/
public TransactionId getPreparedTransactionId() {
return preparedTransactionId;
}

/**
* Names the prepared transaction to complete. A null identifier completes the current
* transaction and clears the keyword.
*
* @throws NullPointerException if an identifier is given without a keyword
*/
public void setPreparedTransaction(PreparedTransactionKeyword keyword, TransactionId id) {
this.preparedTransactionKeyword =
id == null ? null : Objects.requireNonNull(keyword, "keyword");
this.preparedTransactionId = id;
}

/** Whether the statement is spelled END or ABORT, PostgreSQL's names of COMMIT and ROLLBACK. */
public abstract boolean isPostgresSpelling();

Expand All @@ -55,6 +94,15 @@ protected StringBuilder appendKeyword(StringBuilder builder) {
return keyword == null ? builder : builder.append(' ').append(keyword);
}

/** Appends {@code PREPARED 'id'} or {@code FORCE 'id'} when a prepared transaction is named. */
protected StringBuilder appendPreparedTransaction(StringBuilder builder) {
if (preparedTransactionId != null) {
builder.append(' ').append(preparedTransactionKeyword).append(' ');
preparedTransactionId.appendTo(builder);
}
return builder;
}

/** Appends {@code AND [NO] CHAIN} and {@code [NO] RELEASE}, which end the statement. */
protected StringBuilder appendCompletion(StringBuilder builder) {
if (chain != null) {
Expand Down
Loading
Loading