From ab2819a3afb0e389bc12daeb58f7dfa13aed9baf Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 11 Apr 2026 16:07:50 +0000
Subject: [PATCH] feat: add ForUpdateClause class with multi-table and ORDER BY
support
Agent-Logs-Url: https://github.com/rrobetti/JSqlParser/sessions/1a91a42d-ed37-490e-88b9-25c45e621b64
Co-authored-by: rrobetti <7221783+rrobetti@users.noreply.github.com>
---
.../statement/select/ForUpdateClause.java | 135 ++++++++++++++++++
.../jsqlparser/statement/select/Select.java | 105 +++++++++++++-
.../util/deparser/SelectDeParser.java | 19 ++-
.../validation/validator/SelectValidator.java | 5 +
.../net/sf/jsqlparser/parser/JSqlParserCC.jjt | 10 +-
.../statement/select/ForUpdateTest.java | 101 +++++++++++++
.../statement/select/SpecialOracleTest.java | 1 +
.../select/oracle-tests/for_update07.sql | 3 +-
.../select/oracle-tests/for_update08.sql | 3 +-
9 files changed, 370 insertions(+), 12 deletions(-)
create mode 100644 src/main/java/net/sf/jsqlparser/statement/select/ForUpdateClause.java
diff --git a/src/main/java/net/sf/jsqlparser/statement/select/ForUpdateClause.java b/src/main/java/net/sf/jsqlparser/statement/select/ForUpdateClause.java
new file mode 100644
index 000000000..499324551
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/statement/select/ForUpdateClause.java
@@ -0,0 +1,135 @@
+/*-
+ * #%L
+ * JSQLParser library
+ * %%
+ * Copyright (C) 2004 - 2024 JSQLParser
+ * %%
+ * Dual licensed under GNU LGPL 2.1 or Apache License 2.0
+ * #L%
+ */
+package net.sf.jsqlparser.statement.select;
+
+import java.util.List;
+import net.sf.jsqlparser.schema.Table;
+
+/**
+ * Represents a FOR UPDATE / FOR SHARE locking clause in a SELECT statement.
+ *
+ *
+ * Supports all common SQL dialects:
+ *
+ * - {@code FOR UPDATE} – standard row locking
+ * - {@code FOR UPDATE OF t1, t2} – table-specific locking (Oracle, PostgreSQL)
+ * - {@code FOR UPDATE NOWAIT} – fail immediately if rows are locked (Oracle, PostgreSQL)
+ * - {@code FOR UPDATE WAIT n} – wait up to n seconds (Oracle)
+ * - {@code FOR UPDATE SKIP LOCKED} – skip locked rows (Oracle, PostgreSQL)
+ * - {@code FOR SHARE} – shared row lock (PostgreSQL)
+ * - {@code FOR KEY SHARE} – key-level shared lock (PostgreSQL)
+ * - {@code FOR NO KEY UPDATE} – non-key exclusive lock (PostgreSQL)
+ *
+ *
+ */
+public class ForUpdateClause {
+
+ private ForMode mode;
+ private List tables;
+ private Wait wait;
+ private boolean noWait;
+ private boolean skipLocked;
+
+ public ForMode getMode() {
+ return mode;
+ }
+
+ public ForUpdateClause setMode(ForMode mode) {
+ this.mode = mode;
+ return this;
+ }
+
+ public List getTables() {
+ return tables;
+ }
+
+ public ForUpdateClause setTables(List tables) {
+ this.tables = tables;
+ return this;
+ }
+
+ /**
+ * Returns the first table from the OF clause, or {@code null} if none was specified.
+ *
+ * @return the first table, or {@code null}
+ */
+ public Table getFirstTable() {
+ return (tables != null && !tables.isEmpty()) ? tables.get(0) : null;
+ }
+
+ public Wait getWait() {
+ return wait;
+ }
+
+ public ForUpdateClause setWait(Wait wait) {
+ this.wait = wait;
+ return this;
+ }
+
+ public boolean isNoWait() {
+ return noWait;
+ }
+
+ public ForUpdateClause setNoWait(boolean noWait) {
+ this.noWait = noWait;
+ return this;
+ }
+
+ public boolean isSkipLocked() {
+ return skipLocked;
+ }
+
+ public ForUpdateClause setSkipLocked(boolean skipLocked) {
+ this.skipLocked = skipLocked;
+ return this;
+ }
+
+ /** Returns {@code true} when the mode is {@link ForMode#UPDATE}. */
+ public boolean isForUpdate() {
+ return mode == ForMode.UPDATE;
+ }
+
+ /** Returns {@code true} when the mode is {@link ForMode#SHARE}. */
+ public boolean isForShare() {
+ return mode == ForMode.SHARE;
+ }
+
+ /** Returns {@code true} when at least one table was listed in the OF clause. */
+ public boolean hasTableList() {
+ return tables != null && !tables.isEmpty();
+ }
+
+ public StringBuilder appendTo(StringBuilder builder) {
+ builder.append(" FOR ").append(mode.getValue());
+ if (tables != null && !tables.isEmpty()) {
+ builder.append(" OF ");
+ for (int i = 0; i < tables.size(); i++) {
+ if (i > 0) {
+ builder.append(", ");
+ }
+ builder.append(tables.get(i));
+ }
+ }
+ if (wait != null) {
+ builder.append(wait);
+ }
+ if (noWait) {
+ builder.append(" NOWAIT");
+ } else if (skipLocked) {
+ builder.append(" SKIP LOCKED");
+ }
+ return builder;
+ }
+
+ @Override
+ public String toString() {
+ return appendTo(new StringBuilder()).toString();
+ }
+}
diff --git a/src/main/java/net/sf/jsqlparser/statement/select/Select.java b/src/main/java/net/sf/jsqlparser/statement/select/Select.java
index 08fed9dee..737608020 100644
--- a/src/main/java/net/sf/jsqlparser/statement/select/Select.java
+++ b/src/main/java/net/sf/jsqlparser/statement/select/Select.java
@@ -24,7 +24,7 @@
import net.sf.jsqlparser.statement.StatementVisitor;
public abstract class Select extends ASTNodeAccessImpl implements Statement, Expression, FromItem {
- protected Table forUpdateTable = null;
+ protected List forUpdateTables = null;
protected List> withItemsList;
Limit limitBy;
Limit limit;
@@ -40,6 +40,7 @@ public abstract class Select extends ASTNodeAccessImpl implements Statement, Exp
private boolean skipLocked;
private Wait wait;
private boolean noWait = false;
+ private boolean forUpdateBeforeOrderBy = false;
Alias alias;
Pivot pivot;
UnPivot unPivot;
@@ -291,12 +292,92 @@ public void setForMode(ForMode forMode) {
this.forMode = forMode;
}
+ /**
+ * Returns the first table from the {@code FOR UPDATE OF} clause, or {@code null} if no table
+ * was specified. Use {@link #getForUpdateTables()} to retrieve all tables.
+ *
+ * @return the first table, or {@code null}
+ */
public Table getForUpdateTable() {
- return this.forUpdateTable;
+ return (forUpdateTables != null && !forUpdateTables.isEmpty()) ? forUpdateTables.get(0)
+ : null;
}
+ /**
+ * Sets a single table for the {@code FOR UPDATE OF} clause.
+ *
+ * @param forUpdateTable the table, or {@code null} to clear
+ */
public void setForUpdateTable(Table forUpdateTable) {
- this.forUpdateTable = forUpdateTable;
+ if (forUpdateTable == null) {
+ this.forUpdateTables = null;
+ } else {
+ this.forUpdateTables = new ArrayList<>();
+ this.forUpdateTables.add(forUpdateTable);
+ }
+ }
+
+ /**
+ * Returns the list of tables named in the {@code FOR UPDATE OF t1, t2, ...} clause, or
+ * {@code null} if no OF clause was present.
+ *
+ * @return list of tables, or {@code null}
+ */
+ public List getForUpdateTables() {
+ return forUpdateTables;
+ }
+
+ /**
+ * Sets the list of tables for the {@code FOR UPDATE OF t1, t2, ...} clause.
+ *
+ * @param forUpdateTables list of tables
+ */
+ public void setForUpdateTables(List forUpdateTables) {
+ this.forUpdateTables = forUpdateTables;
+ }
+
+ public Select withForUpdateTables(List forUpdateTables) {
+ this.setForUpdateTables(forUpdateTables);
+ return this;
+ }
+
+ /**
+ * Builds and returns a {@link ForUpdateClause} representing the current FOR UPDATE / FOR SHARE
+ * state of this SELECT, or {@code null} if no FOR clause is present.
+ *
+ * @return a {@link ForUpdateClause} view, or {@code null}
+ */
+ public ForUpdateClause getForUpdate() {
+ if (forMode == null) {
+ return null;
+ }
+ ForUpdateClause clause = new ForUpdateClause();
+ clause.setMode(forMode);
+ clause.setTables(forUpdateTables);
+ clause.setWait(wait);
+ clause.setNoWait(noWait);
+ clause.setSkipLocked(skipLocked);
+ return clause;
+ }
+
+ /**
+ * Returns {@code true} when the {@code FOR UPDATE} clause appears before the {@code ORDER BY}
+ * clause in the original SQL (non-standard ordering supported by some databases).
+ *
+ * @return {@code true} if FOR UPDATE precedes ORDER BY
+ */
+ public boolean isForUpdateBeforeOrderBy() {
+ return forUpdateBeforeOrderBy;
+ }
+
+ /**
+ * Indicates whether the {@code FOR UPDATE} clause precedes the {@code ORDER BY} clause in the
+ * SQL output.
+ *
+ * @param forUpdateBeforeOrderBy {@code true} to emit FOR UPDATE before ORDER BY
+ */
+ public void setForUpdateBeforeOrderBy(boolean forUpdateBeforeOrderBy) {
+ this.forUpdateBeforeOrderBy = forUpdateBeforeOrderBy;
}
/**
@@ -380,7 +461,9 @@ public StringBuilder appendTo(StringBuilder builder) {
appendTo(builder, alias, null, pivot, unPivot);
- builder.append(orderByToString(oracleSiblings, orderByElements));
+ if (!forUpdateBeforeOrderBy) {
+ builder.append(orderByToString(oracleSiblings, orderByElements));
+ }
if (forClause != null) {
forClause.appendTo(builder);
@@ -405,8 +488,14 @@ public StringBuilder appendTo(StringBuilder builder) {
builder.append(" FOR ");
builder.append(forMode.getValue());
- if (getForUpdateTable() != null) {
- builder.append(" OF ").append(forUpdateTable);
+ if (forUpdateTables != null && !forUpdateTables.isEmpty()) {
+ builder.append(" OF ");
+ for (int i = 0; i < forUpdateTables.size(); i++) {
+ if (i > 0) {
+ builder.append(", ");
+ }
+ builder.append(forUpdateTables.get(i));
+ }
}
if (wait != null) {
@@ -421,6 +510,10 @@ public StringBuilder appendTo(StringBuilder builder) {
}
}
+ if (forUpdateBeforeOrderBy) {
+ builder.append(orderByToString(oracleSiblings, orderByElements));
+ }
+
return builder;
}
diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java
index 73366ce28..69f9412ac 100644
--- a/src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java
+++ b/src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java
@@ -335,7 +335,9 @@ public StringBuilder visit(PlainSelect plainSelect, S context) {
unpivot.accept(this, context);
}
- deparseOrderByElementsClause(plainSelect, plainSelect.getOrderByElements());
+ if (!plainSelect.isForUpdateBeforeOrderBy()) {
+ deparseOrderByElementsClause(plainSelect, plainSelect.getOrderByElements());
+ }
if (plainSelect.getForClause() != null) {
plainSelect.getForClause().appendTo(builder);
@@ -363,8 +365,15 @@ public StringBuilder visit(PlainSelect plainSelect, S context) {
builder.append(" FOR ");
builder.append(plainSelect.getForMode().getValue());
- if (plainSelect.getForUpdateTable() != null) {
- builder.append(" OF ").append(plainSelect.getForUpdateTable());
+ List forUpdateTables = plainSelect.getForUpdateTables();
+ if (forUpdateTables != null && !forUpdateTables.isEmpty()) {
+ builder.append(" OF ");
+ for (int i = 0; i < forUpdateTables.size(); i++) {
+ if (i > 0) {
+ builder.append(", ");
+ }
+ builder.append(forUpdateTables.get(i));
+ }
}
if (plainSelect.getWait() != null) {
// wait's toString will do the formatting for us
@@ -376,6 +385,10 @@ public StringBuilder visit(PlainSelect plainSelect, S context) {
builder.append(" SKIP LOCKED");
}
}
+
+ if (plainSelect.isForUpdateBeforeOrderBy()) {
+ deparseOrderByElementsClause(plainSelect, plainSelect.getOrderByElements());
+ }
if (plainSelect.getMySqlSelectIntoClause() != null
&& plainSelect.getMySqlSelectIntoClause()
.getPosition() == MySqlSelectIntoClause.Position.TRAILING) {
diff --git a/src/main/java/net/sf/jsqlparser/util/validation/validator/SelectValidator.java b/src/main/java/net/sf/jsqlparser/util/validation/validator/SelectValidator.java
index d680e9faa..5cab04030 100644
--- a/src/main/java/net/sf/jsqlparser/util/validation/validator/SelectValidator.java
+++ b/src/main/java/net/sf/jsqlparser/util/validation/validator/SelectValidator.java
@@ -101,6 +101,11 @@ public Void visit(PlainSelect plainSelect, S context) {
validateOptionalFeature(c, plainSelect.getForUpdateTable(),
Feature.selectForUpdateOfTable);
+ if (plainSelect.getForUpdateTables() != null) {
+ plainSelect.getForUpdateTables()
+ .forEach(t -> validateOptionalFeature(c, t,
+ Feature.selectForUpdateOfTable));
+ }
validateOptionalFeature(c, plainSelect.getWait(), Feature.selectForUpdateWait);
validateFeature(c, plainSelect.isNoWait(), Feature.selectForUpdateNoWait);
validateFeature(c, plainSelect.isSkipLocked(), Feature.selectForUpdateSkipLocked);
diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
index d290ced5e..9d3946191 100644
--- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
+++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
@@ -4958,6 +4958,7 @@ PlainSelect PlainSelect() #PlainSelect:
List intoTables = null;
MySqlSelectIntoClause mySqlSelectIntoClause = null;
Table updateTable = null;
+ List updateTables = new ArrayList();
Wait wait = null;
boolean mySqlSqlCalcFoundRows = false;
Token token;
@@ -5084,10 +5085,17 @@ PlainSelect PlainSelect() #PlainSelect:
| ( { plainSelect.setForMode(ForMode.READ_ONLY); })
| ( { plainSelect.setForMode(ForMode.FETCH_ONLY); })
)
- [ LOOKAHEAD(2) updateTable = Table() { plainSelect.setForUpdateTable(updateTable); } ]
+ [ LOOKAHEAD(2)
+ updateTable = Table() { updateTables.add(updateTable); }
+ ( LOOKAHEAD(2) "," updateTable = Table() { updateTables.add(updateTable); } )*
+ { plainSelect.setForUpdateTables(updateTables); }
+ ]
[ LOOKAHEAD() wait = Wait() { plainSelect.setWait(wait); } ]
[ LOOKAHEAD(2) ( { plainSelect.setNoWait(true); }
| { plainSelect.setSkipLocked(true); }) ]
+ [ LOOKAHEAD( ) orderByElements = OrderByElements()
+ { plainSelect.setOrderByElements(orderByElements); plainSelect.setForUpdateBeforeOrderBy(true); }
+ ]
]
[ LOOKAHEAD( ( | ))
mySqlSelectIntoClause = MySqlSelectIntoClause(MySqlSelectIntoClause.Position.TRAILING)
diff --git a/src/test/java/net/sf/jsqlparser/statement/select/ForUpdateTest.java b/src/test/java/net/sf/jsqlparser/statement/select/ForUpdateTest.java
index ecd216217..23eb3d229 100644
--- a/src/test/java/net/sf/jsqlparser/statement/select/ForUpdateTest.java
+++ b/src/test/java/net/sf/jsqlparser/statement/select/ForUpdateTest.java
@@ -10,9 +10,13 @@
package net.sf.jsqlparser.statement.select;
import net.sf.jsqlparser.JSQLParserException;
+import net.sf.jsqlparser.parser.CCJSqlParserUtil;
+import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.test.TestUtils;
import org.junit.jupiter.api.Test;
+import static org.assertj.core.api.Assertions.assertThat;
+
public class ForUpdateTest {
@Test
@@ -44,4 +48,101 @@ void testMySqlIssue1995() throws JSQLParserException {
TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true);
}
+
+ @Test
+ void testForUpdateMultipleTables() throws JSQLParserException {
+ String sqlStr =
+ "select employee_id from (select employee_id+1 as employee_id from employees)"
+ + " for update of a, b.c, d skip locked";
+
+ Statement stmt = TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true);
+ PlainSelect plainSelect = (PlainSelect) stmt;
+
+ assertThat(plainSelect.getForMode()).isEqualTo(ForMode.UPDATE);
+ assertThat(plainSelect.getForUpdateTables()).hasSize(3);
+ assertThat(plainSelect.isSkipLocked()).isTrue();
+
+ ForUpdateClause forUpdate = plainSelect.getForUpdate();
+ assertThat(forUpdate).isNotNull();
+ assertThat(forUpdate.isForUpdate()).isTrue();
+ assertThat(forUpdate.getTables()).hasSize(3);
+ assertThat(forUpdate.isSkipLocked()).isTrue();
+ }
+
+ @Test
+ void testForUpdateOrderByAfter() throws JSQLParserException {
+ String sqlStr =
+ "select su.ttype, su.cid, su.s_id, sessiontimezone from sku su"
+ + " where (nvl(su.up, 'n') = 'n' and su.ttype = :b0)"
+ + " for update of su.up order by su.d";
+
+ Statement stmt = TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true);
+ PlainSelect plainSelect = (PlainSelect) stmt;
+
+ assertThat(plainSelect.getForMode()).isEqualTo(ForMode.UPDATE);
+ assertThat(plainSelect.getForUpdateTables()).hasSize(1);
+ assertThat(plainSelect.getOrderByElements()).hasSize(1);
+ assertThat(plainSelect.isForUpdateBeforeOrderBy()).isTrue();
+ }
+
+ @Test
+ void testForUpdateDetection() throws JSQLParserException {
+ Statement stmt = CCJSqlParserUtil.parse("SELECT * FROM users FOR UPDATE");
+ PlainSelect plainSelect = (PlainSelect) stmt;
+
+ // ForMode is set for FOR UPDATE
+ assertThat(plainSelect.getForMode()).isEqualTo(ForMode.UPDATE);
+
+ // getForUpdate() returns a ForUpdateClause
+ ForUpdateClause forUpdate = plainSelect.getForUpdate();
+ assertThat(forUpdate).isNotNull();
+ assertThat(forUpdate.isForUpdate()).isTrue();
+ assertThat(forUpdate.isForShare()).isFalse();
+ assertThat(forUpdate.getTables()).isNull();
+ }
+
+ @Test
+ void testForShare() throws JSQLParserException {
+ Statement stmt = CCJSqlParserUtil.parse("SELECT * FROM users FOR SHARE");
+ PlainSelect plainSelect = (PlainSelect) stmt;
+
+ assertThat(plainSelect.getForMode()).isEqualTo(ForMode.SHARE);
+
+ ForUpdateClause forUpdate = plainSelect.getForUpdate();
+ assertThat(forUpdate).isNotNull();
+ assertThat(forUpdate.isForShare()).isTrue();
+ assertThat(forUpdate.isForUpdate()).isFalse();
+ }
+
+ @Test
+ void testForUpdateNowait() throws JSQLParserException {
+ String sqlStr =
+ "select employee_id from (select employee_id+1 as employee_id from employees)"
+ + " for update of employee_id nowait";
+ Statement stmt = TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true);
+ PlainSelect plainSelect = (PlainSelect) stmt;
+
+ assertThat(plainSelect.getForMode()).isEqualTo(ForMode.UPDATE);
+ assertThat(plainSelect.isNoWait()).isTrue();
+
+ ForUpdateClause forUpdate = plainSelect.getForUpdate();
+ assertThat(forUpdate.isNoWait()).isTrue();
+ assertThat(forUpdate.isSkipLocked()).isFalse();
+ }
+
+ @Test
+ void testForUpdateWait() throws JSQLParserException {
+ String sqlStr =
+ "select employee_id from (select employee_id+1 as employee_id from employees)"
+ + " for update of employee_id wait 10";
+ Statement stmt = TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true);
+ PlainSelect plainSelect = (PlainSelect) stmt;
+
+ assertThat(plainSelect.getWait()).isNotNull();
+ assertThat(plainSelect.getWait().getTimeout()).isEqualTo(10L);
+
+ ForUpdateClause forUpdate = plainSelect.getForUpdate();
+ assertThat(forUpdate.getWait()).isNotNull();
+ assertThat(forUpdate.getWait().getTimeout()).isEqualTo(10L);
+ }
}
diff --git a/src/test/java/net/sf/jsqlparser/statement/select/SpecialOracleTest.java b/src/test/java/net/sf/jsqlparser/statement/select/SpecialOracleTest.java
index 2b2f1b382..4c95872f8 100644
--- a/src/test/java/net/sf/jsqlparser/statement/select/SpecialOracleTest.java
+++ b/src/test/java/net/sf/jsqlparser/statement/select/SpecialOracleTest.java
@@ -89,6 +89,7 @@ public class SpecialOracleTest {
"datetime02.sql", "datetime04.sql", "datetime05.sql", "datetime06.sql", "dblink01.sql",
"for_update01.sql", "for_update02.sql", "for_update03.sql", "function04.sql",
"function05.sql", "for_update04.sql", "for_update05.sql", "for_update06.sql",
+ "for_update07.sql", "for_update08.sql",
"function01.sql", "function02.sql", "function03.sql",
"function06.sql", "function07.sql",
"groupby01.sql",
diff --git a/src/test/resources/net/sf/jsqlparser/statement/select/oracle-tests/for_update07.sql b/src/test/resources/net/sf/jsqlparser/statement/select/oracle-tests/for_update07.sql
index da5b94826..ddd448b75 100644
--- a/src/test/resources/net/sf/jsqlparser/statement/select/oracle-tests/for_update07.sql
+++ b/src/test/resources/net/sf/jsqlparser/statement/select/oracle-tests/for_update07.sql
@@ -12,4 +12,5 @@ select employee_id from (select employee_id+1 as employee_id from employees)
--@FAILURE: Encountered unexpected token: "," "," recorded first on Aug 3, 2021, 7:20:08 AM
---@FAILURE: Encountered: / ",", at line 11, column 19, in lexical state DEFAULT. recorded first on 15 May 2025, 16:24:08
\ No newline at end of file
+--@FAILURE: Encountered: / ",", at line 11, column 19, in lexical state DEFAULT. recorded first on 15 May 2025, 16:24:08
+--@SUCCESSFULLY_PARSED_AND_DEPARSED first on Apr 11, 2026, 4:05:21 PM
\ No newline at end of file
diff --git a/src/test/resources/net/sf/jsqlparser/statement/select/oracle-tests/for_update08.sql b/src/test/resources/net/sf/jsqlparser/statement/select/oracle-tests/for_update08.sql
index 5a482f11b..9408f4acf 100644
--- a/src/test/resources/net/sf/jsqlparser/statement/select/oracle-tests/for_update08.sql
+++ b/src/test/resources/net/sf/jsqlparser/statement/select/oracle-tests/for_update08.sql
@@ -14,4 +14,5 @@ for update of su.up
order by su.d
--@FAILURE: select su.ttype,su.cid,su.s_id,sessiontimezone from sku su where(nvl(su.up,'n')='n' and su.ttype=:b0)order by su.d for update of su.up recorded first on 20 Apr 2024, 15:59:32
---@FAILURE: Encountered unexpected token: "order" "ORDER" recorded first on Feb 13, 2025, 10:16:06 AM
\ No newline at end of file
+--@FAILURE: Encountered unexpected token: "order" "ORDER" recorded first on Feb 13, 2025, 10:16:06 AM
+--@SUCCESSFULLY_PARSED_AND_DEPARSED first on Apr 11, 2026, 4:05:22 PM
\ No newline at end of file