Skip to content

Commit 5a18fb3

Browse files
committed
feat: parse MySQL server option values and INSTALL/UNINSTALL PLUGIN and COMPONENT
MySQL CREATE SERVER and ALTER SERVER failed on a numeric option value such as PORT 3306 and on a server or wrapper name written as a string, and INSTALL/UNINSTALL PLUGIN and COMPONENT did not parse. ForeignDataOption keeps its value as an expression: a string, or the number of a MySQL server option; getValue() derives the string from it. Numbers and quoted names are read in CREATE SERVER and ALTER SERVER with the MySQL and MariaDB dialects or none. The MySQL dialect follows the server grammar: USER, HOST, DATABASE, OWNER, PASSWORD and SOCKET take a string, PORT a number, and the PostgreSQL clauses are rejected. The plugin and component statements are one PluginStatement with an Action and an ObjectType; INSTALL COMPONENT ... SET assignments are ComponentVariable items with a SetScope. They are read with the MySQL and MariaDB dialects or none; MariaDB has no components. DuckDB INSTALL extension keeps its reading outside those dialects, and UNINSTALL, PLUGIN, COMPONENT and SONAME stay usable as names. Wire the statement into the deparser, visitors, table finder, feature analysis and validation, with new installPlugin and installComponent features. Signed-off-by: minleejae <mmj9808@gmail.com>
1 parent cf057dd commit 5a18fb3

20 files changed

Lines changed: 1497 additions & 15 deletions

File tree

‎README.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ and missing syntax gets added on demand — [open an issue](https://github.com/J
152152
| **PostgreSQL maintenance** | `VACUUM` · `ANALYZE` · `CLUSTER` · `CHECKPOINT` |
153153
| **MySQL locks** | `LOCK TABLES` · `UNLOCK TABLES` · `LOCK INSTANCE FOR BACKUP` · `UNLOCK INSTANCE` |
154154
| **MySQL resource groups** | `CREATE`/`ALTER`/`DROP RESOURCE GROUP` · `SET RESOURCE GROUP` |
155+
| **MySQL servers, plugins and components** | `CREATE SERVER` · `ALTER SERVER` with `OPTIONS (… PORT 3306)` · `INSTALL`/`UNINSTALL PLUGIN` · `INSTALL`/`UNINSTALL COMPONENT` |
155156
| **PostgreSQL session statements** | `DISCARD` · `LISTEN` · `UNLISTEN` · `SHOW`/`RESET TIME ZONE`/`TRANSACTION ISOLATION LEVEL`/`SESSION AUTHORIZATION` · `COPY` options from before 9.0 |
156157
| **MySQL server administration** | `FLUSH` · `KILL` · `RESET PERSIST` · `HANDLER … OPEN`/`READ`/`CLOSE` |
157158
| **MySQL account management** | `CREATE`/`ALTER`/`RENAME USER` · `CREATE ROLE` · `SET ROLE` · `SET DEFAULT ROLE` · `SET PASSWORD` |

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import net.sf.jsqlparser.statement.DeclareStatement;
2323
import net.sf.jsqlparser.statement.DescribeStatement;
2424
import net.sf.jsqlparser.statement.ExplainStatement;
25+
import net.sf.jsqlparser.statement.PluginStatement;
2526
import net.sf.jsqlparser.statement.PrepareTransactionStatement;
2627
import net.sf.jsqlparser.statement.ResetStatement;
2728
import net.sf.jsqlparser.statement.SetConstraintsStatement;
@@ -810,6 +811,18 @@ public enum Feature {
810811
* @see XaStatement
811812
*/
812813
xa,
814+
/**
815+
* MySQL and MariaDB INSTALL PLUGIN and UNINSTALL PLUGIN
816+
*
817+
* @see PluginStatement
818+
*/
819+
installPlugin,
820+
/**
821+
* MySQL INSTALL COMPONENT and UNINSTALL COMPONENT
822+
*
823+
* @see PluginStatement
824+
*/
825+
installComponent,
813826
/**
814827
* SQL "LOCK TABLE" statement is allowed
815828
*
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
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;
11+
12+
import java.io.Serializable;
13+
import java.util.Objects;
14+
import java.util.function.Consumer;
15+
import net.sf.jsqlparser.expression.Expression;
16+
17+
/**
18+
* One assignment of the {@code SET} clause of MySQL's {@code INSTALL COMPONENT}:
19+
* {@code [GLOBAL | PERSIST] variable {= | :=} value}. The value is an expression, or the word
20+
* {@code ON}, which is read as a column.
21+
*
22+
* @see PluginStatement
23+
*/
24+
public class ComponentVariable implements Serializable {
25+
private SetScope scope = SetScope.NONE;
26+
private String name;
27+
private SetStatement.AssignmentOperator operator = SetStatement.AssignmentOperator.EQUALS;
28+
private Expression value;
29+
30+
public ComponentVariable(String name, Expression value) {
31+
this.name = Objects.requireNonNull(name, "name");
32+
this.value = Objects.requireNonNull(value, "value");
33+
}
34+
35+
/** {@link SetScope#GLOBAL}, {@link SetScope#PERSIST}, or {@link SetScope#NONE} if not given. */
36+
public SetScope getScope() {
37+
return scope;
38+
}
39+
40+
/**
41+
* Sets the scope keyword; null means none.
42+
*
43+
* @throws IllegalArgumentException for a scope other than GLOBAL, PERSIST or NONE
44+
*/
45+
public void setScope(SetScope scope) {
46+
SetScope value = scope == null ? SetScope.NONE : scope;
47+
if (value != SetScope.NONE && value != SetScope.GLOBAL && value != SetScope.PERSIST) {
48+
throw new IllegalArgumentException("INSTALL COMPONENT SET does not accept " + value);
49+
}
50+
this.scope = value;
51+
}
52+
53+
public ComponentVariable withScope(SetScope scope) {
54+
setScope(scope);
55+
return this;
56+
}
57+
58+
/** The variable name as written: {@code variable} or {@code component.variable}. */
59+
public String getName() {
60+
return name;
61+
}
62+
63+
public void setName(String name) {
64+
this.name = Objects.requireNonNull(name, "name");
65+
}
66+
67+
/** {@code =} or {@code :=}. */
68+
public SetStatement.AssignmentOperator getOperator() {
69+
return operator;
70+
}
71+
72+
/**
73+
* Sets the assignment operator.
74+
*
75+
* @throws IllegalArgumentException for an operator other than EQUALS or COLON_EQUALS
76+
*/
77+
public void setOperator(SetStatement.AssignmentOperator operator) {
78+
if (operator != SetStatement.AssignmentOperator.EQUALS
79+
&& operator != SetStatement.AssignmentOperator.COLON_EQUALS) {
80+
throw new IllegalArgumentException("INSTALL COMPONENT SET assigns with = or :=");
81+
}
82+
this.operator = operator;
83+
}
84+
85+
public ComponentVariable withOperator(SetStatement.AssignmentOperator operator) {
86+
setOperator(operator);
87+
return this;
88+
}
89+
90+
public Expression getValue() {
91+
return value;
92+
}
93+
94+
public void setValue(Expression value) {
95+
this.value = Objects.requireNonNull(value, "value");
96+
}
97+
98+
public StringBuilder appendTo(StringBuilder builder, Consumer<Expression> expressionPrinter) {
99+
if (scope != SetScope.NONE) {
100+
builder.append(scope.getKeyword()).append(' ');
101+
}
102+
builder.append(name).append(' ').append(operator.getToken()).append(' ');
103+
expressionPrinter.accept(value);
104+
return builder;
105+
}
106+
107+
@Override
108+
public String toString() {
109+
StringBuilder builder = new StringBuilder();
110+
return appendTo(builder, builder::append).toString();
111+
}
112+
}
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
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;
11+
12+
import java.util.ArrayList;
13+
import java.util.List;
14+
import java.util.Objects;
15+
import java.util.function.Consumer;
16+
import net.sf.jsqlparser.expression.Expression;
17+
import net.sf.jsqlparser.expression.StringValue;
18+
import net.sf.jsqlparser.parser.ASTNodeAccessImpl;
19+
20+
/**
21+
* The MySQL statements that load and unload server plugins and components:
22+
* {@code INSTALL PLUGIN name SONAME 'library'}, {@code UNINSTALL PLUGIN name},
23+
* {@code INSTALL COMPONENT 'urn'[, ...] [SET [GLOBAL | PERSIST] variable = value[, ...]]} and
24+
* {@code UNINSTALL COMPONENT 'urn'[, ...]}. DuckDB's {@code INSTALL extension} is an
25+
* {@link ExtensionStatement}.
26+
*
27+
* @see <a href="https://dev.mysql.com/doc/refman/8.4/en/component-statements.html">MySQL Plugin and
28+
* Component Statements</a>
29+
*/
30+
public class PluginStatement extends ASTNodeAccessImpl implements Statement {
31+
/** The statement's first word. */
32+
public enum Action {
33+
INSTALL, UNINSTALL
34+
}
35+
36+
/** What the statement loads or unloads. */
37+
public enum ObjectType {
38+
PLUGIN, COMPONENT
39+
}
40+
41+
private Action action;
42+
private ObjectType objectType;
43+
private String pluginName;
44+
private StringValue library;
45+
private List<StringValue> components = new ArrayList<>();
46+
private List<ComponentVariable> variables = new ArrayList<>();
47+
48+
public PluginStatement(Action action, ObjectType objectType) {
49+
this.action = Objects.requireNonNull(action, "action");
50+
this.objectType = Objects.requireNonNull(objectType, "objectType");
51+
}
52+
53+
public Action getAction() {
54+
return action;
55+
}
56+
57+
public void setAction(Action action) {
58+
this.action = Objects.requireNonNull(action, "action");
59+
}
60+
61+
public ObjectType getObjectType() {
62+
return objectType;
63+
}
64+
65+
public void setObjectType(ObjectType objectType) {
66+
this.objectType = Objects.requireNonNull(objectType, "objectType");
67+
}
68+
69+
/** The plugin name as written, or null for a component statement. */
70+
public String getPluginName() {
71+
return pluginName;
72+
}
73+
74+
public void setPluginName(String pluginName) {
75+
this.pluginName = pluginName;
76+
}
77+
78+
public PluginStatement withPluginName(String pluginName) {
79+
setPluginName(pluginName);
80+
return this;
81+
}
82+
83+
/** The plugin library file after SONAME; only INSTALL PLUGIN names one. */
84+
public StringValue getLibrary() {
85+
return library;
86+
}
87+
88+
public void setLibrary(StringValue library) {
89+
this.library = library;
90+
}
91+
92+
public PluginStatement withLibrary(StringValue library) {
93+
setLibrary(library);
94+
return this;
95+
}
96+
97+
/** The component URNs, in the order written; empty for a plugin statement. */
98+
public List<StringValue> getComponents() {
99+
return components;
100+
}
101+
102+
public void setComponents(List<StringValue> components) {
103+
this.components = Objects.requireNonNull(components, "components");
104+
}
105+
106+
public PluginStatement addComponents(StringValue... components) {
107+
for (StringValue component : components) {
108+
this.components.add(component);
109+
}
110+
return this;
111+
}
112+
113+
/** The assignments after SET; only INSTALL COMPONENT has them. */
114+
public List<ComponentVariable> getVariables() {
115+
return variables;
116+
}
117+
118+
public void setVariables(List<ComponentVariable> variables) {
119+
this.variables = Objects.requireNonNull(variables, "variables");
120+
}
121+
122+
public PluginStatement addVariables(ComponentVariable... variables) {
123+
for (ComponentVariable variable : variables) {
124+
this.variables.add(variable);
125+
}
126+
return this;
127+
}
128+
129+
/** Passes the literals and the assigned values to {@code visitor}. */
130+
public void visitExpressions(Consumer<Expression> visitor) {
131+
if (objectType == ObjectType.PLUGIN) {
132+
if (library != null) {
133+
visitor.accept(library);
134+
}
135+
return;
136+
}
137+
components.forEach(visitor);
138+
for (ComponentVariable variable : variables) {
139+
visitor.accept(variable.getValue());
140+
}
141+
}
142+
143+
public StringBuilder appendTo(StringBuilder builder, Consumer<Expression> expressionPrinter) {
144+
builder.append(action).append(' ').append(objectType);
145+
if (objectType == ObjectType.PLUGIN) {
146+
builder.append(' ').append(pluginName);
147+
if (library != null) {
148+
builder.append(" SONAME ");
149+
expressionPrinter.accept(library);
150+
}
151+
return builder;
152+
}
153+
for (int i = 0; i < components.size(); i++) {
154+
builder.append(i == 0 ? " " : ", ");
155+
expressionPrinter.accept(components.get(i));
156+
}
157+
for (int i = 0; i < variables.size(); i++) {
158+
builder.append(i == 0 ? " SET " : ", ");
159+
variables.get(i).appendTo(builder, expressionPrinter);
160+
}
161+
return builder;
162+
}
163+
164+
@Override
165+
public String toString() {
166+
StringBuilder builder = new StringBuilder();
167+
return appendTo(builder, builder::append).toString();
168+
}
169+
170+
@Override
171+
public <T, S> T accept(StatementVisitor<T> statementVisitor, S context) {
172+
return statementVisitor.visit(this, context);
173+
}
174+
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,14 @@ public <S> Void visit(ExtensionStatement extensionStatement, S context) {
611611
return null;
612612
}
613613

614+
@Override
615+
public <S> Void visit(PluginStatement statement, S context) {
616+
// loads or unloads server code, like DuckDB's INSTALL; no data or schema changes
617+
analysis.claimTopLevel();
618+
analysis.certain(StmtFeature.MODIFIES_SESSION);
619+
return null;
620+
}
621+
614622
@Override
615623
public <S> Void visit(AttachStatement attachStatement, S context) {
616624
analysis.claimTopLevel();

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,14 @@ default void visit(SetResourceGroup statement) {
228228
visit(statement, null);
229229
}
230230

231+
default <S> T visit(PluginStatement statement, S context) {
232+
return null;
233+
}
234+
235+
default void visit(PluginStatement statement) {
236+
visit(statement, null);
237+
}
238+
231239
<S> T visit(Commit commit, S context);
232240

233241
default void visit(Commit commit) {

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,11 @@ public <S> T visit(SetResourceGroup statement, S context) {
284284
return null;
285285
}
286286

287+
@Override
288+
public <S> T visit(PluginStatement statement, S context) {
289+
return null;
290+
}
291+
287292
@Override
288293
public <S> T visit(Commit commit, S context) {
289294

0 commit comments

Comments
 (0)