Skip to content
Open
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ and missing syntax gets added on demand — [open an issue](https://github.com/J
| **MySQL table maintenance** | `ANALYZE` · `CHECK` · `CHECKSUM` · `OPTIMIZE` · `REPAIR TABLE` |
| **PostgreSQL maintenance** | `VACUUM` · `ANALYZE` · `CLUSTER` · `CHECKPOINT` |
| **MySQL locks** | `LOCK TABLES` · `UNLOCK TABLES` · `LOCK INSTANCE FOR BACKUP` · `UNLOCK INSTANCE` |
| **MySQL servers, plugins and components** | `CREATE SERVER` · `ALTER SERVER` with `OPTIONS (… PORT 3306)` · `INSTALL`/`UNINSTALL PLUGIN` · `INSTALL`/`UNINSTALL COMPONENT` |
| **PostgreSQL session statements** | `DISCARD` · `LISTEN` · `UNLISTEN` · `SHOW`/`RESET TIME ZONE`/`TRANSACTION ISOLATION LEVEL`/`SESSION AUTHORIZATION` · `COPY` options from before 9.0 |
| **MySQL account management** | `CREATE`/`ALTER`/`RENAME USER` · `CREATE ROLE` · `SET ROLE` · `SET DEFAULT ROLE` · `SET PASSWORD` |
| **Tablespaces** | MySQL and PostgreSQL `CREATE TABLESPACE` · `ALTER TABLESPACE` |
Expand Down
13 changes: 13 additions & 0 deletions src/main/java/net/sf/jsqlparser/parser/feature/Feature.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import net.sf.jsqlparser.statement.DeclareStatement;
import net.sf.jsqlparser.statement.DescribeStatement;
import net.sf.jsqlparser.statement.ExplainStatement;
import net.sf.jsqlparser.statement.PluginStatement;
import net.sf.jsqlparser.statement.PrepareTransactionStatement;
import net.sf.jsqlparser.statement.ResetStatement;
import net.sf.jsqlparser.statement.SetConstraintsStatement;
Expand Down Expand Up @@ -778,6 +779,18 @@ public enum Feature {
* @see XaStatement
*/
xa,
/**
* MySQL and MariaDB INSTALL PLUGIN and UNINSTALL PLUGIN
*
* @see PluginStatement
*/
installPlugin,
/**
* MySQL INSTALL COMPONENT and UNINSTALL COMPONENT
*
* @see PluginStatement
*/
installComponent,
/**
* SQL "LOCK TABLE" statement is allowed
*
Expand Down
112 changes: 112 additions & 0 deletions src/main/java/net/sf/jsqlparser/statement/ComponentVariable.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/*-
* #%L
* JSQLParser library
* %%
* Copyright (C) 2004 - 2026 JSQLParser
* %%
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
* #L%
*/
package net.sf.jsqlparser.statement;

import java.io.Serializable;
import java.util.Objects;
import java.util.function.Consumer;
import net.sf.jsqlparser.expression.Expression;

/**
* One assignment of the {@code SET} clause of MySQL's {@code INSTALL COMPONENT}:
* {@code [GLOBAL | PERSIST] variable {= | :=} value}. The value is an expression, or the word
* {@code ON}, which is read as a column.
*
* @see PluginStatement
*/
public class ComponentVariable implements Serializable {
private SetScope scope = SetScope.NONE;
private String name;
private SetStatement.AssignmentOperator operator = SetStatement.AssignmentOperator.EQUALS;
private Expression value;

public ComponentVariable(String name, Expression value) {
this.name = Objects.requireNonNull(name, "name");
this.value = Objects.requireNonNull(value, "value");
}

/** {@link SetScope#GLOBAL}, {@link SetScope#PERSIST}, or {@link SetScope#NONE} if not given. */
public SetScope getScope() {
return scope;
}

/**
* Sets the scope keyword; null means none.
*
* @throws IllegalArgumentException for a scope other than GLOBAL, PERSIST or NONE
*/
public void setScope(SetScope scope) {
SetScope value = scope == null ? SetScope.NONE : scope;
if (value != SetScope.NONE && value != SetScope.GLOBAL && value != SetScope.PERSIST) {
throw new IllegalArgumentException("INSTALL COMPONENT SET does not accept " + value);
}
this.scope = value;
}

public ComponentVariable withScope(SetScope scope) {
setScope(scope);
return this;
}

/** The variable name as written: {@code variable} or {@code component.variable}. */
public String getName() {
return name;
}

public void setName(String name) {
this.name = Objects.requireNonNull(name, "name");
}

/** {@code =} or {@code :=}. */
public SetStatement.AssignmentOperator getOperator() {
return operator;
}

/**
* Sets the assignment operator.
*
* @throws IllegalArgumentException for an operator other than EQUALS or COLON_EQUALS
*/
public void setOperator(SetStatement.AssignmentOperator operator) {
if (operator != SetStatement.AssignmentOperator.EQUALS
&& operator != SetStatement.AssignmentOperator.COLON_EQUALS) {
throw new IllegalArgumentException("INSTALL COMPONENT SET assigns with = or :=");
}
this.operator = operator;
}

public ComponentVariable withOperator(SetStatement.AssignmentOperator operator) {
setOperator(operator);
return this;
}

public Expression getValue() {
return value;
}

public void setValue(Expression value) {
this.value = Objects.requireNonNull(value, "value");
}

public StringBuilder appendTo(StringBuilder builder, Consumer<Expression> expressionPrinter) {
if (scope != SetScope.NONE) {
builder.append(scope.getKeyword()).append(' ');
}
builder.append(name).append(' ').append(operator.getToken()).append(' ');
expressionPrinter.accept(value);
return builder;
}

@Override
public String toString() {
StringBuilder builder = new StringBuilder();
return appendTo(builder, builder::append).toString();
}
}
174 changes: 174 additions & 0 deletions src/main/java/net/sf/jsqlparser/statement/PluginStatement.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
/*-
* #%L
* JSQLParser library
* %%
* Copyright (C) 2004 - 2026 JSQLParser
* %%
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
* #L%
*/
package net.sf.jsqlparser.statement;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.StringValue;
import net.sf.jsqlparser.parser.ASTNodeAccessImpl;

/**
* The MySQL statements that load and unload server plugins and components:
* {@code INSTALL PLUGIN name SONAME 'library'}, {@code UNINSTALL PLUGIN name},
* {@code INSTALL COMPONENT 'urn'[, ...] [SET [GLOBAL | PERSIST] variable = value[, ...]]} and
* {@code UNINSTALL COMPONENT 'urn'[, ...]}. DuckDB's {@code INSTALL extension} is an
* {@link ExtensionStatement}.
*
* @see <a href="https://dev.mysql.com/doc/refman/8.4/en/component-statements.html">MySQL Plugin and
* Component Statements</a>
*/
public class PluginStatement extends ASTNodeAccessImpl implements Statement {
/** The statement's first word. */
public enum Action {
INSTALL, UNINSTALL
}

/** What the statement loads or unloads. */
public enum ObjectType {
PLUGIN, COMPONENT
}

private Action action;
private ObjectType objectType;
private String pluginName;
private StringValue library;
private List<StringValue> components = new ArrayList<>();
private List<ComponentVariable> variables = new ArrayList<>();

public PluginStatement(Action action, ObjectType objectType) {
this.action = Objects.requireNonNull(action, "action");
this.objectType = Objects.requireNonNull(objectType, "objectType");
}

public Action getAction() {
return action;
}

public void setAction(Action action) {
this.action = Objects.requireNonNull(action, "action");
}

public ObjectType getObjectType() {
return objectType;
}

public void setObjectType(ObjectType objectType) {
this.objectType = Objects.requireNonNull(objectType, "objectType");
}

/** The plugin name as written, or null for a component statement. */
public String getPluginName() {
return pluginName;
}

public void setPluginName(String pluginName) {
this.pluginName = pluginName;
}

public PluginStatement withPluginName(String pluginName) {
setPluginName(pluginName);
return this;
}

/** The plugin library file after SONAME; only INSTALL PLUGIN names one. */
public StringValue getLibrary() {
return library;
}

public void setLibrary(StringValue library) {
this.library = library;
}

public PluginStatement withLibrary(StringValue library) {
setLibrary(library);
return this;
}

/** The component URNs, in the order written; empty for a plugin statement. */
public List<StringValue> getComponents() {
return components;
}

public void setComponents(List<StringValue> components) {
this.components = Objects.requireNonNull(components, "components");
}

public PluginStatement addComponents(StringValue... components) {
for (StringValue component : components) {
this.components.add(component);
}
return this;
}

/** The assignments after SET; only INSTALL COMPONENT has them. */
public List<ComponentVariable> getVariables() {
return variables;
}

public void setVariables(List<ComponentVariable> variables) {
this.variables = Objects.requireNonNull(variables, "variables");
}

public PluginStatement addVariables(ComponentVariable... variables) {
for (ComponentVariable variable : variables) {
this.variables.add(variable);
}
return this;
}

/** Passes the literals and the assigned values to {@code visitor}. */
public void visitExpressions(Consumer<Expression> visitor) {
if (objectType == ObjectType.PLUGIN) {
if (library != null) {
visitor.accept(library);
}
return;
}
components.forEach(visitor);
for (ComponentVariable variable : variables) {
visitor.accept(variable.getValue());
}
}

public StringBuilder appendTo(StringBuilder builder, Consumer<Expression> expressionPrinter) {
builder.append(action).append(' ').append(objectType);
if (objectType == ObjectType.PLUGIN) {
builder.append(' ').append(pluginName);
if (library != null) {
builder.append(" SONAME ");
expressionPrinter.accept(library);
}
return builder;
}
for (int i = 0; i < components.size(); i++) {
builder.append(i == 0 ? " " : ", ");
expressionPrinter.accept(components.get(i));
}
for (int i = 0; i < variables.size(); i++) {
builder.append(i == 0 ? " SET " : ", ");
variables.get(i).appendTo(builder, expressionPrinter);
}
return builder;
}

@Override
public String toString() {
StringBuilder builder = new StringBuilder();
return appendTo(builder, builder::append).toString();
}

@Override
public <T, S> T accept(StatementVisitor<T> statementVisitor, S context) {
return statementVisitor.visit(this, context);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,14 @@ public <S> Void visit(ExtensionStatement extensionStatement, S context) {
return null;
}

@Override
public <S> Void visit(PluginStatement statement, S context) {
// loads or unloads server code, like DuckDB's INSTALL; no data or schema changes
analysis.claimTopLevel();
analysis.certain(StmtFeature.MODIFIES_SESSION);
return null;
}

@Override
public <S> Void visit(AttachStatement attachStatement, S context) {
analysis.claimTopLevel();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,14 @@ default void visit(XaStatement statement) {
visit(statement, null);
}

default <S> T visit(PluginStatement statement, S context) {
return null;
}

default void visit(PluginStatement statement) {
visit(statement, null);
}

<S> T visit(Commit commit, S context);

default void visit(Commit commit) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,11 @@ public <S> T visit(XaStatement statement, S context) {
return null;
}

@Override
public <S> T visit(PluginStatement statement, S context) {
return null;
}

@Override
public <S> T visit(Commit commit, S context) {

Expand Down
Loading
Loading