Skip to content

Commit a9bab8d

Browse files
committed
feat: parse MySQL LOCK TABLES, UNLOCK TABLES and the backup lock statements
mysqldump wraps the rows of each table in LOCK TABLES ... WRITE and UNLOCK TABLES, so a dump script failed at its first table. LOCK INSTANCE FOR BACKUP and UNLOCK INSTANCE failed as well. LOCK TABLES extends LockStatement: each target keeps its lock type (TableLockType) and its table keeps the alias, and isTablesKeyword() records the TABLES spelling. PostgreSQL and Oracle LOCK keep their reading, since neither follows a table name with an alias or a lock type. MariaDB's WRITE CONCURRENT is a lock type, and its WAIT and NOWAIT reuse the wait clause of the statement. UNLOCK TABLES and the backup lock name no table, so they are the new UnlockStatement and BackupLockStatement. UNLOCK becomes a non-reserved keyword and remains usable as a name. Signed-off-by: minleejae <mmj9808@gmail.com>
1 parent 7123d25 commit a9bab8d

21 files changed

Lines changed: 1057 additions & 14 deletions

File tree

‎README.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ and missing syntax gets added on demand — [open an issue](https://github.com/J
148148
| **Oracle vector types** | `VECTOR(*, *)` · `VECTOR(*, FLOAT32)` · `VECTOR(3, *)`, including dense and sparse storage formats |
149149
| **PostgreSQL ownership** | `DROP OWNED` · `REASSIGN OWNED` |
150150
| **MySQL table maintenance** | `ANALYZE` · `CHECK` · `CHECKSUM` · `OPTIMIZE` · `REPAIR TABLE` |
151+
| **MySQL locks** | `LOCK TABLES` · `UNLOCK TABLES` · `LOCK INSTANCE FOR BACKUP` · `UNLOCK INSTANCE` |
151152
| **MySQL account management** | `CREATE`/`ALTER`/`RENAME USER` · `CREATE ROLE` · `SET ROLE` · `SET DEFAULT ROLE` · `SET PASSWORD` |
152153
| **Tablespaces** | MySQL and PostgreSQL `CREATE TABLESPACE` · `ALTER TABLESPACE` |
153154
| **PostgreSQL RLS** | `CREATE POLICY` · `ALTER TABLE … ENABLE`/`DISABLE`/`FORCE`/`NO FORCE ROW LEVEL SECURITY` |

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@
4848
import net.sf.jsqlparser.statement.drop.Drop;
4949
import net.sf.jsqlparser.statement.execute.Execute;
5050
import net.sf.jsqlparser.statement.grant.Grant;
51+
import net.sf.jsqlparser.statement.lock.BackupLockStatement;
52+
import net.sf.jsqlparser.statement.lock.LockStatement;
53+
import net.sf.jsqlparser.statement.lock.UnlockStatement;
5154
import net.sf.jsqlparser.statement.merge.Merge;
5255
import net.sf.jsqlparser.statement.refresh.RefreshMaterializedViewStatement;
5356
import net.sf.jsqlparser.statement.reindex.Reindex;
@@ -676,6 +679,25 @@ public enum Feature {
676679
transactionDeferrable,
677680
/** MySQL WITH CONSISTENT SNAPSHOT transaction characteristic. */
678681
transactionConsistentSnapshot,
682+
/**
683+
* "LOCK [TABLE | TABLES]" statement is allowed: PostgreSQL and Oracle lock modes, and MySQL and
684+
* MariaDB lock types
685+
*
686+
* @see LockStatement
687+
*/
688+
lockTables,
689+
/**
690+
* MySQL/MariaDB "UNLOCK TABLES" statement is allowed
691+
*
692+
* @see UnlockStatement
693+
*/
694+
unlockTables,
695+
/**
696+
* MySQL "LOCK INSTANCE FOR BACKUP" and "UNLOCK INSTANCE" statements are allowed
697+
*
698+
* @see BackupLockStatement
699+
*/
700+
backupLock,
679701
/**
680702
* SQL "COMMENT ON" statement is allowed
681703
*

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,9 @@
9696
import net.sf.jsqlparser.statement.insert.Insert;
9797
import net.sf.jsqlparser.statement.insert.InsertBulk;
9898
import net.sf.jsqlparser.statement.load.LoadDataStatement;
99+
import net.sf.jsqlparser.statement.lock.BackupLockStatement;
99100
import net.sf.jsqlparser.statement.lock.LockStatement;
101+
import net.sf.jsqlparser.statement.lock.UnlockStatement;
100102
import net.sf.jsqlparser.statement.merge.Merge;
101103
import net.sf.jsqlparser.statement.oracle.OracleAssignment;
102104
import net.sf.jsqlparser.statement.oracle.OracleBlock;
@@ -866,6 +868,16 @@ public <S> Void visit(LockStatement lock, S context) {
866868
return transactionOnly();
867869
}
868870

871+
@Override
872+
public <S> Void visit(UnlockStatement unlock, S context) {
873+
return transactionOnly();
874+
}
875+
876+
@Override
877+
public <S> Void visit(BackupLockStatement backupLock, S context) {
878+
return transactionOnly();
879+
}
880+
869881
private Void transactionOnly() {
870882
analysis.claimTopLevel();
871883
analysis.certain(StmtFeature.MODIFIES_TRANSACTION);

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,9 @@
9090
import net.sf.jsqlparser.statement.insert.Insert;
9191
import net.sf.jsqlparser.statement.insert.InsertBulk;
9292
import net.sf.jsqlparser.statement.insert.ParenthesedInsert;
93+
import net.sf.jsqlparser.statement.lock.BackupLockStatement;
9394
import net.sf.jsqlparser.statement.lock.LockStatement;
95+
import net.sf.jsqlparser.statement.lock.UnlockStatement;
9496
import net.sf.jsqlparser.statement.merge.Merge;
9597
import net.sf.jsqlparser.statement.refresh.RefreshMaterializedViewStatement;
9698
import net.sf.jsqlparser.statement.reindex.Reindex;
@@ -675,6 +677,22 @@ default void visit(LockStatement lock) {
675677
this.visit(lock, null);
676678
}
677679

680+
default <S> T visit(UnlockStatement unlock, S context) {
681+
return null;
682+
}
683+
684+
default void visit(UnlockStatement unlock) {
685+
visit(unlock, null);
686+
}
687+
688+
default <S> T visit(BackupLockStatement backupLock, S context) {
689+
return null;
690+
}
691+
692+
default void visit(BackupLockStatement backupLock) {
693+
visit(backupLock, null);
694+
}
695+
678696
<S> T visit(CreatePolicy createPolicy, S context);
679697

680698
default void visit(CreatePolicy createPolicy) {

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,9 @@
9393
import net.sf.jsqlparser.statement.insert.InsertBulk;
9494
import net.sf.jsqlparser.statement.insert.InsertConflictAction;
9595
import net.sf.jsqlparser.statement.insert.ParenthesedInsert;
96+
import net.sf.jsqlparser.statement.lock.BackupLockStatement;
9697
import net.sf.jsqlparser.statement.lock.LockStatement;
98+
import net.sf.jsqlparser.statement.lock.UnlockStatement;
9799
import net.sf.jsqlparser.statement.merge.Merge;
98100
import net.sf.jsqlparser.statement.merge.MergeOperationVisitor;
99101
import net.sf.jsqlparser.statement.merge.MergeOperationVisitorAdapter;
@@ -544,6 +546,16 @@ public <S> T visit(LockStatement lock, S context) {
544546
return null;
545547
}
546548

549+
@Override
550+
public <S> T visit(UnlockStatement unlock, S context) {
551+
return null;
552+
}
553+
554+
@Override
555+
public <S> T visit(BackupLockStatement backupLock, S context) {
556+
return null;
557+
}
558+
547559
@Override
548560
public <S> T visit(CreatePolicy createPolicy, S context) {
549561
fromItemVisitor.visitFromItem(createPolicy.getTable(), context);
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
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.lock;
11+
12+
import java.util.Objects;
13+
import net.sf.jsqlparser.parser.ASTNodeAccessImpl;
14+
import net.sf.jsqlparser.statement.Statement;
15+
import net.sf.jsqlparser.statement.StatementVisitor;
16+
17+
/**
18+
* MySQL {@code LOCK INSTANCE FOR BACKUP} and {@code UNLOCK INSTANCE}, which take and release the
19+
* instance-level backup lock. It locks no table, unlike {@link LockStatement}.
20+
*
21+
* @see <a href="https://dev.mysql.com/doc/refman/8.4/en/lock-instance-for-backup.html">MySQL LOCK
22+
* INSTANCE FOR BACKUP and UNLOCK INSTANCE</a>
23+
*/
24+
public class BackupLockStatement extends ASTNodeAccessImpl implements Statement {
25+
private Action action = Action.LOCK;
26+
27+
public enum Action {
28+
/** LOCK INSTANCE FOR BACKUP */
29+
LOCK("LOCK INSTANCE FOR BACKUP"),
30+
/** UNLOCK INSTANCE */
31+
UNLOCK("UNLOCK INSTANCE");
32+
33+
private final String sql;
34+
35+
Action(String sql) {
36+
this.sql = sql;
37+
}
38+
39+
public String getSql() {
40+
return sql;
41+
}
42+
}
43+
44+
public BackupLockStatement() {}
45+
46+
public BackupLockStatement(Action action) {
47+
setAction(action);
48+
}
49+
50+
public Action getAction() {
51+
return action;
52+
}
53+
54+
public void setAction(Action action) {
55+
this.action = Objects.requireNonNull(action);
56+
}
57+
58+
public StringBuilder appendTo(StringBuilder builder) {
59+
return builder.append(action.getSql());
60+
}
61+
62+
@Override
63+
public String toString() {
64+
return appendTo(new StringBuilder()).toString();
65+
}
66+
67+
@Override
68+
public <T, S> T accept(StatementVisitor<T> statementVisitor, S context) {
69+
return statementVisitor.visit(this, context);
70+
}
71+
}

‎src/main/java/net/sf/jsqlparser/statement/lock/LockStatement.java‎

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,25 @@
1212
import java.util.ArrayList;
1313
import java.util.List;
1414
import java.util.Objects;
15+
import net.sf.jsqlparser.expression.Alias;
1516
import net.sf.jsqlparser.parser.ASTNodeAccessImpl;
1617
import net.sf.jsqlparser.schema.Table;
1718
import net.sf.jsqlparser.statement.Statement;
1819
import net.sf.jsqlparser.statement.StatementVisitor;
1920

2021
/**
2122
* Statement to lock one or more tables.<br>
22-
* Example:<br>
23+
* Examples:<br>
2324
* LOCK TABLE t IN EXCLUSIVE MODE<br>
25+
* LOCK TABLES t READ, u AS a WRITE (MySQL and MariaDB, where each target has a
26+
* {@link TableLockType})<br>
2427
* <br>
2528
*/
2629
public class LockStatement extends ASTNodeAccessImpl implements Statement {
2730

2831
private final List<Target> targets = new ArrayList<>();
2932
private boolean useTableKeyword = true;
33+
private boolean tablesKeyword;
3034
private LockMode lockMode;
3135
private boolean noWait;
3236
private Long waitSeconds;
@@ -35,11 +39,44 @@ public enum Scope {
3539
DEFAULT, ONLY, INCLUDING_DESCENDANTS
3640
}
3741

42+
/**
43+
* A locked table. A MySQL or MariaDB target also has a lock type and may have an alias, which
44+
* is the {@link Table#getAlias() alias of its table}.
45+
*/
3846
public static class Target extends net.sf.jsqlparser.schema.TableReference {
47+
private TableLockType lockType;
48+
3949
public Target(Table table) {
4050
super(table);
4151
}
4252

53+
/** Returns the MySQL or MariaDB lock type, or null for a PostgreSQL or Oracle target. */
54+
public TableLockType getLockType() {
55+
return lockType;
56+
}
57+
58+
public void setLockType(TableLockType lockType) {
59+
this.lockType = lockType;
60+
}
61+
62+
public Target withLockType(TableLockType lockType) {
63+
setLockType(lockType);
64+
return this;
65+
}
66+
67+
@Override
68+
public StringBuilder appendTo(StringBuilder builder) {
69+
super.appendTo(builder);
70+
Alias alias = getTable().getAlias();
71+
if (alias != null) {
72+
builder.append(alias);
73+
}
74+
if (lockType != null) {
75+
builder.append(' ').append(lockType.getValue());
76+
}
77+
return builder;
78+
}
79+
4380
public Scope getScope() {
4481
return isOnly() ? Scope.ONLY
4582
: isIncludeDescendants() ? Scope.INCLUDING_DESCENDANTS : Scope.DEFAULT;
@@ -100,8 +137,25 @@ public boolean isUseTableKeyword() {
100137
return useTableKeyword;
101138
}
102139

140+
/** Setting false also clears {@link #isTablesKeyword()}. */
103141
public void setUseTableKeyword(boolean useTableKeyword) {
104142
this.useTableKeyword = useTableKeyword;
143+
if (!useTableKeyword) {
144+
tablesKeyword = false;
145+
}
146+
}
147+
148+
/** Whether the statement is spelled LOCK TABLES, as MySQL and MariaDB allow. */
149+
public boolean isTablesKeyword() {
150+
return tablesKeyword;
151+
}
152+
153+
/** Setting true also sets {@link #isUseTableKeyword()}. */
154+
public void setTablesKeyword(boolean tablesKeyword) {
155+
this.tablesKeyword = tablesKeyword;
156+
if (tablesKeyword) {
157+
useTableKeyword = true;
158+
}
105159
}
106160

107161
/** Returns null when IN ... MODE was omitted. */
@@ -150,12 +204,17 @@ public Long getWaitSeconds() {
150204
}
151205

152206
public StringBuilder appendTo(StringBuilder builder) {
153-
builder.append(useTableKeyword ? "LOCK TABLE " : "LOCK ");
207+
builder.append("LOCK ");
208+
if (tablesKeyword) {
209+
builder.append("TABLES ");
210+
} else if (useTableKeyword) {
211+
builder.append("TABLE ");
212+
}
154213
for (int i = 0; i < targets.size(); i++) {
155214
if (i > 0) {
156215
builder.append(", ");
157216
}
158-
builder.append(targets.get(i));
217+
targets.get(i).appendTo(builder);
159218
}
160219
if (lockMode != null) {
161220
builder.append(" IN ").append(lockMode.getValue()).append(" MODE");
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
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.lock;
11+
12+
/**
13+
* The lock type that MySQL and MariaDB give each table of {@code LOCK TABLES}. MySQL 8.4 has
14+
* {@code READ [LOCAL] | WRITE}.
15+
*
16+
* @see <a href="https://dev.mysql.com/doc/refman/8.4/en/lock-tables.html">MySQL LOCK TABLES</a>
17+
* @see <a href="https://mariadb.com/kb/en/lock-tables/">MariaDB LOCK TABLES</a>
18+
*/
19+
public enum TableLockType {
20+
READ("READ"), READ_LOCAL("READ LOCAL"), WRITE("WRITE"),
21+
22+
/**
23+
* MySQL 8.0, where it is deprecated and has no effect, and MariaDB. MySQL 8.4 rejects it.
24+
*
25+
* @see <a href="https://dev.mysql.com/doc/refman/8.0/en/lock-tables.html">MySQL 8.0 LOCK
26+
* TABLES</a>
27+
*/
28+
LOW_PRIORITY_WRITE("LOW_PRIORITY WRITE"),
29+
30+
/** MariaDB only. */
31+
WRITE_CONCURRENT("WRITE CONCURRENT");
32+
33+
private final String value;
34+
35+
TableLockType(String value) {
36+
this.value = value;
37+
}
38+
39+
public String getValue() {
40+
return value;
41+
}
42+
}

0 commit comments

Comments
 (0)