Skip to content

Commit 5f02f60

Browse files
committed
feat: parse MySQL EXPLAIN options, prepared statements and DO
MySQL's EXPLAIN FORMAT = name, EXPLAIN ANALYZE FORMAT = name, EXPLAIN ... INTO @var, EXPLAIN FOR CONNECTION and FOR SCHEMA, their DESCRIBE and DESC spellings, DESCRIBE t col, PREPARE ... FROM, EXECUTE ... USING, DROP PREPARE and DO expr failed to parse. Extend the existing models. ExplainStatement keeps ANALYZE and FORMAT as options, marks FORMAT = name with useEqual and reads it as a Format enum, and gains the INTO variable, the connection id, the schema, a column pattern and a derived TargetKind. DescribeStatement gains the column pattern, PrepareStatement the FROM source, Execute the USING variables, DeallocateStatement a DROP keyword and DoStatement MySQL's select-list items. One ExplainableStatement production reads the explained statement for both EXPLAIN paths, and the user variables reuse MySqlIntoVariable. The forms are read with the MySQL and MariaDB dialects and without a dialect, except DESCRIBE TABLE t, DESCRIBE t col and DO before a string, which other products read differently. Other dialects are unchanged. Wire the deparser, visitors, table finder (EXPLAIN t now reports t, PREPARE ... FROM raises like EXECUTE), validation (prepare, executeExecute, executeUsing and doStatement in the MySQL and MariaDB capabilities) and feature analysis. Signed-off-by: minleejae <mmj9808@gmail.com>
1 parent 4a970a3 commit 5f02f60

23 files changed

Lines changed: 1477 additions & 83 deletions

‎README.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ and missing syntax gets added on demand — [open an issue](https://github.com/J
154154
| **PostgreSQL RLS** | `CREATE POLICY` · `ALTER TABLE … ENABLE`/`DISABLE`/`FORCE`/`NO FORCE ROW LEVEL SECURITY` |
155155
| **Routine changes** | `ALTER FUNCTION` · `ALTER PROCEDURE` · `ALTER ROUTINE` · PostgreSQL `ALTER TRIGGER` |
156156
| **MySQL metadata statements** | `SHOW [FULL] COLUMNS`/`FIELDS` · `SHOW TABLE STATUS` · `SHOW CREATE TABLE`/`VIEW`/`PROCEDURE`/`FUNCTION`/`TRIGGER`/`EVENT`/`DATABASE`/`USER` |
157+
| **MySQL utility statements** | `EXPLAIN`/`DESCRIBE [ANALYZE] FORMAT = …` · `EXPLAIN FOR CONNECTION` · `DESCRIBE t col` · `PREPARE … FROM` · `EXECUTE … USING` · `DROP PREPARE` · `DO` |
157158
| **PostgreSQL administration** | `CREATE`/`ALTER EVENT TRIGGER` · `REINDEX` · `SECURITY LABEL` |
158159
| **Informix constraints** | `ALTER TABLE … ADD CONSTRAINT` with trailing constraint names for primary, unique, foreign and check constraints; enable with `parser.withDialect(Dialect.INFORMIX)` |
159160
| **Salesforce SOQL** | `INCLUDES` · `EXCLUDES` |

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -533,6 +533,11 @@ public enum Feature {
533533
*/
534534
executeStatementImmediate,
535535

536+
/**
537+
* MySQL "EXECUTE name USING @variable, ..." of a prepared statement is allowed
538+
*
539+
* @see Execute#getUsingVariables()
540+
*/
536541
executeUsing,
537542
/**
538543
* "PREPARE" and "DEALLOCATE" of a prepared statement are allowed

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

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,14 @@
1212
import net.sf.jsqlparser.parser.ASTNodeAccessImpl;
1313

1414
/**
15-
* {@code DEALLOCATE [PREPARE] name}, which drops a statement prepared by {@link PrepareStatement}.
15+
* {@code DEALLOCATE [PREPARE] name}, or MySQL's {@code DROP PREPARE name}, which drops a statement
16+
* prepared by {@link PrepareStatement}.
17+
*
18+
* @see <a href="https://dev.mysql.com/doc/refman/8.4/en/deallocate-prepare.html">MySQL DEALLOCATE
19+
* PREPARE</a>
1620
*/
1721
public class DeallocateStatement extends ASTNodeAccessImpl implements Statement {
22+
private Keyword keyword = Keyword.DEALLOCATE;
1823
private String name;
1924
private boolean usingPrepareKeyword;
2025

@@ -37,8 +42,23 @@ public DeallocateStatement withName(String name) {
3742
return this;
3843
}
3944

45+
/** The leading keyword, {@code DEALLOCATE} unless MySQL's {@code DROP} was written. */
46+
public Keyword getKeyword() {
47+
return keyword;
48+
}
49+
50+
public void setKeyword(Keyword keyword) {
51+
this.keyword = keyword == null ? Keyword.DEALLOCATE : keyword;
52+
}
53+
54+
public DeallocateStatement withKeyword(Keyword keyword) {
55+
setKeyword(keyword);
56+
return this;
57+
}
58+
59+
/** Whether {@code PREPARE} follows the keyword; {@code DROP} always takes it. */
4060
public boolean isUsingPrepareKeyword() {
41-
return usingPrepareKeyword;
61+
return usingPrepareKeyword || keyword == Keyword.DROP;
4262
}
4363

4464
public void setUsingPrepareKeyword(boolean usingPrepareKeyword) {
@@ -51,8 +71,8 @@ public DeallocateStatement withUsingPrepareKeyword(boolean usingPrepareKeyword)
5171
}
5272

5373
public StringBuilder appendTo(StringBuilder builder) {
54-
builder.append("DEALLOCATE ");
55-
if (usingPrepareKeyword) {
74+
builder.append(keyword).append(' ');
75+
if (isUsingPrepareKeyword()) {
5676
builder.append("PREPARE ");
5777
}
5878
builder.append(name);
@@ -68,4 +88,9 @@ public String toString() {
6888
public <T, S> T accept(StatementVisitor<T> statementVisitor, S context) {
6989
return statementVisitor.visit(this, context);
7090
}
91+
92+
/** The keyword that starts the statement. */
93+
public enum Keyword {
94+
DEALLOCATE, DROP
95+
}
7196
}

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

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,23 @@
99
*/
1010
package net.sf.jsqlparser.statement;
1111

12+
import java.util.function.Consumer;
13+
import net.sf.jsqlparser.expression.Expression;
1214
import net.sf.jsqlparser.parser.ASTNodeAccessImpl;
1315
import net.sf.jsqlparser.schema.Table;
1416
import net.sf.jsqlparser.statement.select.Select;
1517

18+
/**
19+
* {@code DESCRIBE} or {@code DESC} of a table, or, in DuckDB, of a query's result.
20+
*
21+
* @see <a href="https://dev.mysql.com/doc/refman/8.4/en/explain.html">MySQL DESCRIBE</a>
22+
*/
1623
public class DescribeStatement extends ASTNodeAccessImpl implements Statement {
1724

1825
private Table table;
1926
private Select select;
2027
private String describeType;
28+
private Expression columnPattern;
2129

2230
public DescribeStatement() {
2331
// empty constructor
@@ -49,10 +57,43 @@ public DescribeStatement withSelect(Select select) {
4957
return this;
5058
}
5159

60+
/**
61+
* The MySQL column name ({@link net.sf.jsqlparser.schema.Column}) or wildcard string that
62+
* limits the described columns to those it matches, or null.
63+
*/
64+
public Expression getColumnPattern() {
65+
return columnPattern;
66+
}
67+
68+
public void setColumnPattern(Expression columnPattern) {
69+
this.columnPattern = columnPattern;
70+
}
71+
72+
public DescribeStatement withColumnPattern(Expression columnPattern) {
73+
setColumnPattern(columnPattern);
74+
return this;
75+
}
76+
5277
@Override
5378
public String toString() {
54-
return this.describeType + " "
55-
+ (select != null ? select.toString() : table.getFullyQualifiedName());
79+
StringBuilder builder = new StringBuilder();
80+
return appendTo(builder, builder::append, builder::append).toString();
81+
}
82+
83+
/** Shared by SQL rendering and statement deparsers. */
84+
public StringBuilder appendTo(StringBuilder builder, Consumer<Select> selectPrinter,
85+
Consumer<Expression> expressionPrinter) {
86+
builder.append(describeType).append(" ");
87+
if (select != null) {
88+
selectPrinter.accept(select);
89+
} else {
90+
builder.append(table);
91+
if (columnPattern != null) {
92+
builder.append(" ");
93+
expressionPrinter.accept(columnPattern);
94+
}
95+
}
96+
return builder;
5697
}
5798

5899
@Override

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

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,26 @@
99
*/
1010
package net.sf.jsqlparser.statement;
1111

12+
import java.util.List;
1213
import java.util.function.Consumer;
14+
import net.sf.jsqlparser.expression.Expression;
1315
import net.sf.jsqlparser.expression.StringValue;
1416
import net.sf.jsqlparser.parser.ASTNodeAccessImpl;
17+
import net.sf.jsqlparser.statement.select.SelectItem;
1518

16-
/** A PostgreSQL anonymous routine. The language-specific body remains a string literal. */
19+
/**
20+
* A {@code DO} statement: a PostgreSQL anonymous routine, whose language-specific body remains a
21+
* string literal, or MySQL's {@code DO expr [, expr] ...}, which evaluates expressions without
22+
* returning a result. MySQL reads the expressions as a select list, so they can carry aliases.
23+
*
24+
* @see <a href="https://www.postgresql.org/docs/18/sql-do.html">PostgreSQL DO</a>
25+
* @see <a href="https://dev.mysql.com/doc/refman/8.4/en/do.html">MySQL DO</a>
26+
*/
1727
public class DoStatement extends ASTNodeAccessImpl implements Statement {
1828
private StringValue code;
1929
private String language;
2030
private boolean languageBeforeCode;
31+
private List<SelectItem<?>> selectItems;
2132

2233
public StringValue getCode() {
2334
return code;
@@ -54,8 +65,48 @@ public void setLanguageBeforeCode(boolean languageBeforeCode) {
5465
this.languageBeforeCode = languageBeforeCode;
5566
}
5667

68+
/** The expressions of MySQL's form, or null for a PostgreSQL code block. */
69+
public List<SelectItem<?>> getSelectItems() {
70+
return selectItems;
71+
}
72+
73+
public void setSelectItems(List<SelectItem<?>> selectItems) {
74+
this.selectItems = selectItems;
75+
}
76+
77+
public DoStatement withSelectItems(List<SelectItem<?>> selectItems) {
78+
setSelectItems(selectItems);
79+
return this;
80+
}
81+
82+
/** Visits the code block, or the expression of every select item. */
83+
public void visitExpressions(Consumer<Expression> visitor) {
84+
if (selectItems != null) {
85+
for (SelectItem<?> item : selectItems) {
86+
visitor.accept(item.getExpression());
87+
}
88+
} else if (code != null) {
89+
visitor.accept(code);
90+
}
91+
}
92+
5793
public StringBuilder appendTo(StringBuilder builder, Consumer<StringValue> codePrinter) {
94+
return appendTo(builder, codePrinter, builder::append);
95+
}
96+
97+
/** Renders the code block or the select items through the caller's writers. */
98+
public StringBuilder appendTo(StringBuilder builder, Consumer<StringValue> codePrinter,
99+
Consumer<SelectItem<?>> itemPrinter) {
58100
builder.append("DO ");
101+
if (selectItems != null) {
102+
for (int i = 0; i < selectItems.size(); i++) {
103+
if (i > 0) {
104+
builder.append(", ");
105+
}
106+
itemPrinter.accept(selectItems.get(i));
107+
}
108+
return builder;
109+
}
59110
if (languageBeforeCode && language != null) {
60111
builder.append("LANGUAGE ").append(language).append(' ');
61112
}

0 commit comments

Comments
 (0)