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
30 changes: 30 additions & 0 deletions src/main/java/net/sf/jsqlparser/statement/select/Select.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public abstract class Select extends ASTNodeAccessImpl implements Statement, Exp
private Wait wait;
private boolean noWait = false;
private boolean forUpdateBeforeOrderBy = false;
private List<ForUpdateClause> additionalForUpdateClauses = null;
Alias alias;
Pivot pivot;
UnPivot unPivot;
Expand Down Expand Up @@ -416,6 +417,30 @@ public ForUpdateClause getForUpdate() {
return clause;
}

/**
* Returns the locking clauses that follow the first one, as in PostgreSQL's
* {@code FOR UPDATE OF t FOR SHARE OF u}, or {@code null} if there are none. The first clause
* is the one {@link #getForMode()}, {@link #getForUpdateTables()}, {@link #getWait()},
* {@link #isNoWait()} and {@link #isSkipLocked()} describe.
*
* @return the locking clauses after the first one, or {@code null}
*/
public List<ForUpdateClause> getAdditionalForUpdateClauses() {
return additionalForUpdateClauses;
}

public void setAdditionalForUpdateClauses(List<ForUpdateClause> additionalForUpdateClauses) {
this.additionalForUpdateClauses = additionalForUpdateClauses;
}

public Select addAdditionalForUpdateClause(ForUpdateClause forUpdateClause) {
if (additionalForUpdateClauses == null) {
additionalForUpdateClauses = new ArrayList<>();
}
additionalForUpdateClauses.add(forUpdateClause);
return this;
}

/**
* 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).
Expand Down Expand Up @@ -572,6 +597,11 @@ public StringBuilder appendTo(StringBuilder builder) {
} else if (isSkipLocked()) {
builder.append(" SKIP LOCKED");
}
if (additionalForUpdateClauses != null) {
for (ForUpdateClause forUpdateClause : additionalForUpdateClauses) {
forUpdateClause.appendTo(builder);
}
}
}

if (forUpdateBeforeOrderBy) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
import net.sf.jsqlparser.statement.select.Distinct;
import net.sf.jsqlparser.statement.select.Fetch;
import net.sf.jsqlparser.statement.select.First;
import net.sf.jsqlparser.statement.select.ForUpdateClause;
import net.sf.jsqlparser.statement.select.FromItem;
import net.sf.jsqlparser.statement.select.FromItemVisitor;
import net.sf.jsqlparser.statement.select.InterpolateElement;
Expand Down Expand Up @@ -313,6 +314,12 @@ public <S> StringBuilder visit(PlainSelect plainSelect, S context) {
} else if (plainSelect.isSkipLocked()) {
builder.append(" SKIP LOCKED");
}
if (plainSelect.getAdditionalForUpdateClauses() != null) {
for (ForUpdateClause forUpdateClause : plainSelect
.getAdditionalForUpdateClauses()) {
forUpdateClause.appendTo(builder);
}
}
}

if (plainSelect.isForUpdateBeforeOrderBy()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import net.sf.jsqlparser.statement.select.ExceptOp;
import net.sf.jsqlparser.statement.select.Fetch;
import net.sf.jsqlparser.statement.select.ForMode;
import net.sf.jsqlparser.statement.select.ForUpdateClause;
import net.sf.jsqlparser.statement.select.FromItemVisitor;
import net.sf.jsqlparser.statement.select.InterpolateElement;
import net.sf.jsqlparser.statement.select.IntersectOp;
Expand Down Expand Up @@ -142,6 +143,29 @@ public <S> Void visit(PlainSelect plainSelect, S context) {
validateOptionalFeature(c, plainSelect.getWait(), Feature.selectForUpdateWait);
validateFeature(c, plainSelect.isNoWait(), Feature.selectForUpdateNoWait);
validateFeature(c, plainSelect.isSkipLocked(), Feature.selectForUpdateSkipLocked);

if (plainSelect.getAdditionalForUpdateClauses() != null) {
for (ForUpdateClause forUpdateClause : plainSelect
.getAdditionalForUpdateClauses()) {
validateFeature(c, forUpdateClause.getMode() == ForMode.KEY_SHARE,
Feature.selectForKeyShare);
validateFeature(c, forUpdateClause.getMode() == ForMode.NO_KEY_UPDATE,
Feature.selectForNoKeyUpdate);
validateFeature(c, forUpdateClause.getMode() == ForMode.SHARE,
Feature.selectForShare);
if (forUpdateClause.getTables() != null) {
forUpdateClause.getTables()
.forEach(t -> validateOptionalFeature(c, t,
Feature.selectForUpdateOfTable));
}
validateOptionalFeature(c, forUpdateClause.getWait(),
Feature.selectForUpdateWait);
validateFeature(c, forUpdateClause.isNoWait(),
Feature.selectForUpdateNoWait);
validateFeature(c, forUpdateClause.isSkipLocked(),
Feature.selectForUpdateSkipLocked);
}
}
}

validateOptionalFeature(c, plainSelect.getForXmlPath(), Feature.selectForXmlPath);
Expand Down
34 changes: 34 additions & 0 deletions src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
Original file line number Diff line number Diff line change
Expand Up @@ -12517,6 +12517,34 @@ void MySqlSelectModifier(PlainSelect select):
}
}

ForUpdateClause AdditionalForUpdateClause():
{
ForUpdateClause clause = new ForUpdateClause();
List<Table> tables = new ArrayList<Table>();
Table table;
Wait wait;
}
{
<K_FOR>
(
<K_UPDATE> { clause.setMode(ForMode.UPDATE); }
| <K_SHARE> { clause.setMode(ForMode.SHARE); }
| (<K_NO> <K_KEY> <K_UPDATE> { clause.setMode(ForMode.NO_KEY_UPDATE); })
| (<K_KEY> <K_SHARE> { clause.setMode(ForMode.KEY_SHARE); })
)
[ LOOKAHEAD(2) <K_OF>
table = Table() { tables.add(table); }
( LOOKAHEAD(2) "," table = Table() { tables.add(table); } )*
{ clause.setTables(tables); }
]
[ LOOKAHEAD(1, <K_WAIT>) wait = Wait() { clause.setWait(wait); } ]
[ LOOKAHEAD(2) (<K_NOWAIT> { clause.setNoWait(true); }
| <K_SKIP> <K_LOCKED> { clause.setSkipLocked(true); }) ]
{
return clause;
}
}

ForClause ForClause() #ForClause:
{
Token token = null;
Expand Down Expand Up @@ -12653,6 +12681,7 @@ PlainSelect PlainSelect() #PlainSelect:
MySqlProcedureAnalyse mySqlProcedureAnalyse = null;
Table updateTable = null;
List<Table> updateTables = new ArrayList<Table>();
ForUpdateClause additionalForUpdateClause;
Wait wait = null;
Token token;
KSQLWindow ksqlWindow = null;
Expand Down Expand Up @@ -12819,6 +12848,11 @@ PlainSelect PlainSelect() #PlainSelect:
[ LOOKAHEAD(1, <K_WAIT>) wait = Wait() { plainSelect.setWait(wait); } ]
[ LOOKAHEAD(2) (<K_NOWAIT> { plainSelect.setNoWait(true); }
| <K_SKIP> <K_LOCKED> { plainSelect.setSkipLocked(true); }) ]
// PostgreSQL: more locking clauses, e.g. FOR UPDATE OF t FOR SHARE OF u
( LOOKAHEAD(<K_FOR> (<K_UPDATE> | <K_SHARE> | <K_NO> <K_KEY> | <K_KEY> <K_SHARE>))
additionalForUpdateClause = AdditionalForUpdateClause()
{ plainSelect.addAdditionalForUpdateClause(additionalForUpdateClause); }
)*
[ LOOKAHEAD(<K_ORDER> <K_BY>) orderByElements = OrderByElements()
{ plainSelect.setOrderByElements(orderByElements); plainSelect.setForUpdateBeforeOrderBy(true); }
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,13 @@

import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.test.TestUtils;
import org.junit.jupiter.api.Test;

import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;

public class ForUpdateTest {
Expand Down Expand Up @@ -145,4 +148,50 @@ void testForUpdateWait() throws JSQLParserException {
assertThat(forUpdate.getWait()).isNotNull();
assertThat(forUpdate.getWait().getTimeout()).isEqualTo(10L);
}

@Test
void testPostgresMultipleLockingClausesIssue2926() throws JSQLParserException {
String sqlStr = "SELECT * FROM t JOIN u ON u.t_id = t.id FOR UPDATE OF t FOR SHARE OF u";
Statement stmt = TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true);
PlainSelect plainSelect = (PlainSelect) stmt;

assertThat(plainSelect.getForMode()).isEqualTo(ForMode.UPDATE);
assertThat(plainSelect.getForUpdateTables()).extracting(Table::getName)
.containsExactly("t");

assertThat(plainSelect.getAdditionalForUpdateClauses()).hasSize(1);
ForUpdateClause second = plainSelect.getAdditionalForUpdateClauses().get(0);
assertThat(second.isForShare()).isTrue();
assertThat(second.getTables()).extracting(Table::getName).containsExactly("u");
}

@Test
void testPostgresMultipleLockingClausesWithOptions() throws JSQLParserException {
String sqlStr = "SELECT * FROM a, b, c, d"
+ " FOR NO KEY UPDATE OF a NOWAIT"
+ " FOR KEY SHARE OF b, c SKIP LOCKED"
+ " FOR SHARE"
+ " FOR UPDATE OF d NOWAIT";
Statement stmt = TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true);
PlainSelect plainSelect = (PlainSelect) stmt;

assertThat(plainSelect.getForMode()).isEqualTo(ForMode.NO_KEY_UPDATE);
assertThat(plainSelect.isNoWait()).isTrue();

List<ForUpdateClause> more = plainSelect.getAdditionalForUpdateClauses();
assertThat(more).extracting(ForUpdateClause::getMode)
.containsExactly(ForMode.KEY_SHARE, ForMode.SHARE, ForMode.UPDATE);
assertThat(more.get(0).getTables()).extracting(Table::getName)
.containsExactly("b", "c");
assertThat(more.get(0).isSkipLocked()).isTrue();
assertThat(more.get(1).getTables()).isNull();
assertThat(more.get(2).isNoWait()).isTrue();
}

@Test
void testSingleLockingClauseHasNoAdditionalClauses() throws JSQLParserException {
PlainSelect plainSelect =
(PlainSelect) CCJSqlParserUtil.parse("SELECT * FROM t FOR UPDATE OF t");
assertThat(plainSelect.getAdditionalForUpdateClauses()).isNull();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,16 @@ public void testValidationForPostgresShare() throws JSQLParserException {
Feature.selectForNoKeyUpdate);
}

@Test
public void testValidationMultipleLockingClauses() throws JSQLParserException {
String sql = "SELECT * FROM t, u FOR UPDATE OF t FOR SHARE OF u";
validateNoErrors(sql, 1, DatabaseType.POSTGRESQL);

String sql2 = "SELECT * FROM t FOR UPDATE FOR NO KEY UPDATE";
validateNotSupported(sql2, 1, 1, DatabaseType.ORACLE,
Feature.selectForNoKeyUpdate);
}

@Test
public void testValidationForUpdateNoWait() throws JSQLParserException {
String sql = "SELECT * FROM mytable FOR UPDATE NOWAIT";
Expand Down
Loading