diff --git a/src/main/java/net/sf/jsqlparser/parser/feature/Feature.java b/src/main/java/net/sf/jsqlparser/parser/feature/Feature.java index 349ab0725..e231a446a 100644 --- a/src/main/java/net/sf/jsqlparser/parser/feature/Feature.java +++ b/src/main/java/net/sf/jsqlparser/parser/feature/Feature.java @@ -261,6 +261,10 @@ public enum Feature { * "FOR UPDATE NOWAIT" */ selectForUpdateNoWait, + /** + * "FOR UPDATE SKIP LOCKED" + */ + selectForUpdateSkipLocked, /** diff --git a/src/main/java/net/sf/jsqlparser/statement/select/PlainSelect.java b/src/main/java/net/sf/jsqlparser/statement/select/PlainSelect.java index edc36af79..4e082e83d 100644 --- a/src/main/java/net/sf/jsqlparser/statement/select/PlainSelect.java +++ b/src/main/java/net/sf/jsqlparser/statement/select/PlainSelect.java @@ -48,6 +48,7 @@ public class PlainSelect extends ASTNodeAccessImpl implements SelectBody { private boolean oracleSiblings = false; private boolean forUpdate = false; private Table forUpdateTable = null; + private boolean skipLocked; private boolean useBrackets = false; private Wait wait; private boolean mySqlSqlCalcFoundRows = false; @@ -349,6 +350,14 @@ public void setWindowDefinitions(List windowDefinitions) { this.windowDefinitions = windowDefinitions; } + public boolean isSkipLocked() { + return skipLocked; + } + + public void setSkipLocked(boolean skipLocked) { + this.skipLocked = skipLocked; + } + @Override @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.ExcessiveMethodLength", "PMD.NPathComplexity"}) public String toString() { @@ -464,6 +473,8 @@ public String toString() { if (isNoWait()) { sql.append(" NOWAIT"); + } else if (isSkipLocked()) { + sql.append(" SKIP LOCKED"); } } if (optimizeFor != null) { @@ -720,6 +731,11 @@ public PlainSelect withNoWait(boolean noWait) { return this; } + public PlainSelect withSkipLocked(boolean skipLocked) { + this.setSkipLocked(skipLocked); + return this; + } + public PlainSelect withHaving(Expression having) { this.setHaving(having); return this; 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 a997c5056..490649b80 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java @@ -217,6 +217,8 @@ public void visit(PlainSelect plainSelect) { } if (plainSelect.isNoWait()) { buffer.append(" NOWAIT"); + } else if (plainSelect.isSkipLocked()) { + buffer.append(" SKIP LOCKED"); } } if (plainSelect.getOptimizeFor() != null) { diff --git a/src/main/java/net/sf/jsqlparser/util/validation/feature/MariaDbVersion.java b/src/main/java/net/sf/jsqlparser/util/validation/feature/MariaDbVersion.java index 6a1cba1a4..118febfd4 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/feature/MariaDbVersion.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/feature/MariaDbVersion.java @@ -35,7 +35,10 @@ public enum MariaDbVersion implements Version { Feature.selectHaving, Feature.limit, Feature.limitOffset, Feature.offset, Feature.offsetParam, Feature.orderBy, - Feature.selectForUpdate, Feature.selectForUpdateWait, Feature.selectForUpdateNoWait, + Feature.selectForUpdate, + Feature.selectForUpdateWait, + Feature.selectForUpdateNoWait, + Feature.selectForUpdateSkipLocked, // https://mariadb.com/kb/en/join-syntax/ Feature.join, Feature.joinSimple, Feature.joinRight, Feature.joinNatural, Feature.joinLeft, diff --git a/src/main/java/net/sf/jsqlparser/util/validation/feature/MySqlVersion.java b/src/main/java/net/sf/jsqlparser/util/validation/feature/MySqlVersion.java index c13abaa3e..23fdb74cb 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/feature/MySqlVersion.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/feature/MySqlVersion.java @@ -33,7 +33,10 @@ public enum MySqlVersion implements Version { Feature.select, Feature.selectGroupBy, Feature.selectHaving, Feature.limit, Feature.limitOffset, Feature.offset, Feature.offsetParam, Feature.orderBy, - Feature.selectForUpdate, Feature.selectForUpdateOfTable, Feature.selectForUpdateNoWait, + Feature.selectForUpdate, + Feature.selectForUpdateOfTable, + Feature.selectForUpdateNoWait, + Feature.selectForUpdateSkipLocked, Feature.distinct, Feature.setOperation, diff --git a/src/main/java/net/sf/jsqlparser/util/validation/feature/OracleVersion.java b/src/main/java/net/sf/jsqlparser/util/validation/feature/OracleVersion.java index e3c344e30..26d4e678e 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/feature/OracleVersion.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/feature/OracleVersion.java @@ -83,7 +83,9 @@ public enum OracleVersion implements Version { // https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/SELECT.html // see "for_update_clause" Feature.selectForUpdate, - Feature.selectForUpdateWait, Feature.selectForUpdateNoWait, + Feature.selectForUpdateWait, + Feature.selectForUpdateNoWait, + Feature.selectForUpdateSkipLocked, // https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/INSERT.html Feature.insert, diff --git a/src/main/java/net/sf/jsqlparser/util/validation/feature/PostgresqlVersion.java b/src/main/java/net/sf/jsqlparser/util/validation/feature/PostgresqlVersion.java index 369a48f42..c5d3d898a 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/feature/PostgresqlVersion.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/feature/PostgresqlVersion.java @@ -76,6 +76,7 @@ public enum PostgresqlVersion implements Version { Feature.selectForUpdate, Feature.selectForUpdateOfTable, Feature.selectForUpdateNoWait, + Feature.selectForUpdateSkipLocked, // https://www.postgresql.org/docs/current/queries-union.html Feature.setOperation, 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 52e15262d..8c5df9c1b 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 @@ -86,6 +86,7 @@ public void visit(PlainSelect plainSelect) { validateOptionalFeature(c, plainSelect.getForUpdateTable(), Feature.selectForUpdateOfTable); validateOptionalFeature(c, plainSelect.getWait(), Feature.selectForUpdateWait); validateFeature(c, plainSelect.isNoWait(), Feature.selectForUpdateNoWait); + validateFeature(c, plainSelect.isSkipLocked(), Feature.selectForUpdateSkipLocked); } validateOptionalFeature(c, plainSelect.getForXmlPath(), Feature.selectForXmlPath); diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index b97073e78..144683e4d 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -274,6 +274,7 @@ TOKEN: /* SQL Keywords. prefixed with K_ to avoid name clashes */ | | | +| | | | @@ -2076,7 +2077,8 @@ PlainSelect PlainSelect() #PlainSelect: [LOOKAHEAD(2) { plainSelect.setForUpdate(true); } [ updateTable = Table() { plainSelect.setForUpdateTable(updateTable); } ] [ LOOKAHEAD() wait = Wait() { plainSelect.setWait(wait); } ] - [ { plainSelect.setNoWait(true); } ] ] + [ { plainSelect.setNoWait(true); } + | { plainSelect.setSkipLocked(true); } ] ] [LOOKAHEAD() optimize = OptimizeFor() { plainSelect.setOptimizeFor(optimize); } ] diff --git a/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java b/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java index 8d890b265..18aa2d208 100644 --- a/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java @@ -5265,4 +5265,37 @@ public void testOracleDBLink() throws JSQLParserException { assertEquals("tablename", table.getName()); assertEquals("dblink", table.getDBLinkName()); } + + @Test + public void testSelectStatementWithForUpdateAndSkipLockedTokens() throws JSQLParserException { + String sql = "SELECT * FROM test FOR UPDATE SKIP LOCKED"; + assertSqlCanBeParsedAndDeparsed(sql); + + Select select = (Select) CCJSqlParserUtil.parse(sql); + PlainSelect plainSelect = (PlainSelect) select.getSelectBody(); + assertTrue(plainSelect.isForUpdate()); + assertTrue(plainSelect.isSkipLocked()); + } + + @Test + public void testSelectStatementWithForUpdateButWithoutSkipLockedTokens() throws JSQLParserException { + String sql = "SELECT * FROM test FOR UPDATE"; + assertSqlCanBeParsedAndDeparsed(sql); + + Select select = (Select) CCJSqlParserUtil.parse(sql); + PlainSelect plainSelect = (PlainSelect) select.getSelectBody(); + assertTrue(plainSelect.isForUpdate()); + assertFalse(plainSelect.isSkipLocked()); + } + + @Test + public void testSelectStatementWithoutForUpdateAndSkipLockedTokens() throws JSQLParserException { + String sql = "SELECT * FROM test"; + assertSqlCanBeParsedAndDeparsed(sql); + + Select select = (Select) CCJSqlParserUtil.parse(sql); + PlainSelect plainSelect = (PlainSelect) select.getSelectBody(); + assertFalse(plainSelect.isForUpdate()); + assertFalse(plainSelect.isSkipLocked()); + } } diff --git a/src/test/resources/net/sf/jsqlparser/statement/select/oracle-tests/for_update06.sql b/src/test/resources/net/sf/jsqlparser/statement/select/oracle-tests/for_update06.sql index 76efc0e1d..7d12ec290 100644 --- a/src/test/resources/net/sf/jsqlparser/statement/select/oracle-tests/for_update06.sql +++ b/src/test/resources/net/sf/jsqlparser/statement/select/oracle-tests/for_update06.sql @@ -11,4 +11,5 @@ select employee_id from (select employee_id+1 as employee_id from employees) for update of employee_id skip locked ---@FAILURE: Encountered unexpected token: "skip" "SKIP" recorded first on Aug 3, 2021, 7:20:08 AM \ No newline at end of file +--@FAILURE: Encountered unexpected token: "skip" "SKIP" recorded first on Aug 3, 2021, 7:20:08 AM +--@SUCCESSFULLY_PARSED_AND_DEPARSED first on Oct 19, 2022, 6:34:12 PM \ No newline at end of file