Skip to content

feat: parse MySQL server option values and INSTALL/UNINSTALL PLUGIN and COMPONENT - #2908

Open
minleejae wants to merge 1 commit into
JSQLParser:masterfrom
minleejae:feat/mysql-server-component-plugin-20261110
Open

minleejae wants to merge 1 commit into
JSQLParser:masterfrom
minleejae:feat/mysql-server-component-plugin-20261110

Conversation

@minleejae

Copy link
Copy Markdown
Contributor

Purpose

Parse the MySQL statements that manage federated servers, plugins and components, following the MySQL 8.4 grammar:

CREATE SERVER s FOREIGN DATA WRAPPER mysql OPTIONS (USER 'u', HOST 'h', DATABASE 'd', PORT 3306);
CREATE SERVER `s-1` FOREIGN DATA WRAPPER 'mysql' OPTIONS (USER 'u');
ALTER SERVER s OPTIONS (USER 'u2', PORT 3307);
INSTALL PLUGIN myplugin SONAME 'somelib.so';
UNINSTALL PLUGIN myplugin;
INSTALL COMPONENT 'file://component1', 'file://component2'
    SET GLOBAL component1.var1 = 12 + 3, PERSIST component1.var3 = 'x';
UNINSTALL COMPONENT 'file://component1';

Current design (As-is)

  • CreateServer and AlterServer exist for PostgreSQL. ForeignDataOption holds a StringValue, and the option production reads a string literal only, so OPTIONS (PORT 3306) fails at the number. Server and wrapper names are identifiers only, so FOREIGN DATA WRAPPER 'mysql' fails.
  • With Dialect.MYSQL the PostgreSQL-only clauses (IF NOT EXISTS, TYPE, VERSION, ADD/SET/DROP options, arbitrary option names, RENAME TO, OWNER TO, no OPTIONS) are accepted although MySQL reports a syntax error for them.
  • INSTALL starts only DuckDB's [FORCE] INSTALL extension [FROM repository] (ExtensionStatement). INSTALL PLUGIN name SONAME 'lib' and INSTALL COMPONENT 'urn' fail, and UNINSTALL starts no statement.

Target design (To-be)

Server options

  • ForeignDataOption keeps one value as an Expression: getValueExpression() returns the StringValue, or the number of a MySQL server option as written (LongValue, DoubleValue, HexValue, MySQL's ulong_num). getValue() keeps its signature and derives the string from it (null for a number).
  • A new ServerOptions production shares the option list with the other foreign-data statements and is used only by CREATE SERVER and ALTER SERVER. Numbers and string-literal names (ident_or_text, kept as written) are read with the MySQL and MariaDB dialects and without a dialect. PostgreSQL, the other dialects and every other option list (CREATE USER MAPPING, foreign tables, foreign-data wrappers) keep requiring string constants.
  • Dialect.MYSQL follows the server grammar: USER, HOST, DATABASE, OWNER, PASSWORD and SOCKET take a string, PORT a number, OPTIONS is required, and the PostgreSQL-only clauses are rejected. This is a behaviour change for input that MySQL itself rejects. MariaDB and the default configuration keep the generic option names and clauses.
  • DROP SERVER [IF EXISTS] name stays the generic Drop.

Plugins and components

  • One PluginStatement with an Action (INSTALL, UNINSTALL) and an ObjectType (PLUGIN, COMPONENT): plugin name and SONAME library, or the component URNs and the SET assignments.
  • Each assignment is a ComponentVariable: SetScope (NONE, GLOBAL, PERSIST), the name as written (variable, component.variable, DEFAULT.variable), = or := (SetStatement.AssignmentOperator) and the value expression; the word ON is read as a column. As in MySQL, the scope keywords are not variable names, GLOBAL./LOCAL./SESSION. are not qualifiers, and a value is neither DEFAULT nor a subquery.
  • No new token and no reserved word: UNINSTALL, PLUGIN, COMPONENT and SONAME stay identifiers and a lookahead routes the statement.
    • With the MySQL and MariaDB dialects every INSTALL is this statement (the DuckDB form is rejected there, as the server does). MariaDB has no components.
    • Without a dialect DuckDB's INSTALL plugin / INSTALL component [FROM …] keep producing an ExtensionStatement; SONAME after the plugin name or a component URN selects the MySQL statement.
    • Other dialects are unchanged.
  • Wired into StatementVisitor (default method), StatementVisitorAdapter, StatementDeParser, TablesNamesFinder, StatementFeatureVisitor (MODIFIES_SESSION) and StatementValidator with the new features installPlugin (MySQL, MariaDB) and installComponent (MySQL). createServer and alterServer are added to the MySQL and MariaDB capabilities. README, index.rst and usage.rst describe the statements.

Validation

  • New tests: PluginStatementTest (120 cases) and MySqlServerTest (81 cases), plus entries in ASTNodeCoverageTest and StatementValidatorTest. They cover each form with and without the dialect, toString, the deparser, reparse, AST fields and node spans, rejected forms, readings kept in other dialects, statement boundaries in scripts, visitors, TablesNamesFinder and validation features.

  • ./gradlew spotlessApply cleanTest check jar on Java 17: 13,542 tests, 0 failures, 25 skipped; checkGrammarAmbiguity, Checkstyle, PMD, SpotBugs and JaCoCo pass.

  • Compared with master (e27929f76) on the MySQL and PostgreSQL DDL/DCL and non-DDL statement corpora (parsed with the dialect and without): no regression and no changed result.

  • MySQL 8.4.11 accepts 62 of the checked statements; the branch parses 60 of them, 45 of which master rejects. They fail only when run, for example Can't open shared library or an unknown server. The two it does not parse are values that the shared expression grammar does not read on master either (SET v = BINARY 'x' and the adjacent string literals 'a' 'b'). MySQL reports a syntax error (1064) for 96 statements; the branch rejects 94 of them, including PORT '3306', USER 1, an unknown option name, IF NOT EXISTS, OPTIONS (), INSTALL PLUGIN without SONAME, SET SESSION v = 1, SET v = DEFAULT and a subquery value. The two it still accepts are DROP SERVER IF EXISTS a, b and DROP SERVER db.s, which the generic Drop reads for every object type, as on master.

AI assistance: prepared with Claude Code.

…nd 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>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant