diff --git a/README.md b/README.md
index d39e81ea68..32e6f19f8b 100644
--- a/README.md
+++ b/README.md
@@ -145,6 +145,7 @@ and missing syntax gets added on demand — [open an issue](https://github.com/J
| **DDL** | `CREATE …` · `ALTER …` · `DROP …` |
| **MySQL table maintenance** | `ANALYZE` · `CHECK` · `OPTIMIZE` · `REPAIR TABLE` |
| **PostgreSQL RLS** | `CREATE POLICY` · `ALTER TABLE … ENABLE`/`DISABLE`/`FORCE`/`NO FORCE ROW LEVEL SECURITY` |
+| **MySQL metadata statements** | `SHOW [FULL] COLUMNS`/`FIELDS` · `SHOW TABLE STATUS` · `SHOW CREATE TABLE`/`VIEW`/`PROCEDURE`/`FUNCTION`/`TRIGGER`/`EVENT`/`DATABASE`/`USER` |
| **Informix constraints** | `ALTER TABLE … ADD CONSTRAINT` with trailing constraint names for primary, unique, foreign and check constraints; enable with `parser.withDialect(Dialect.INFORMIX)` |
| **Salesforce SOQL** | `INCLUDES` · `EXCLUDES` |
diff --git a/src/main/java/net/sf/jsqlparser/parser/feature/Feature.java b/src/main/java/net/sf/jsqlparser/parser/feature/Feature.java
index 0d3d080fbb..5fb07df29b 100644
--- a/src/main/java/net/sf/jsqlparser/parser/feature/Feature.java
+++ b/src/main/java/net/sf/jsqlparser/parser/feature/Feature.java
@@ -683,6 +683,18 @@ public enum Feature {
* @see ShowIndexStatement
*/
showIndex,
+ /**
+ * MySQL/MariaDB "SHOW TABLE STATUS" statement is allowed
+ *
+ * @see ShowTableStatusStatement
+ */
+ showTableStatus,
+ /**
+ * MySQL/MariaDB "SHOW CREATE" statement is allowed
+ *
+ * @see ShowCreateStatement
+ */
+ showCreate,
/**
* @see UseStatement
*/
diff --git a/src/main/java/net/sf/jsqlparser/statement/ShowColumnsStatement.java b/src/main/java/net/sf/jsqlparser/statement/ShowColumnsStatement.java
index 51f1ed26d9..b5ec8324d4 100644
--- a/src/main/java/net/sf/jsqlparser/statement/ShowColumnsStatement.java
+++ b/src/main/java/net/sf/jsqlparser/statement/ShowColumnsStatement.java
@@ -9,29 +9,148 @@
*/
package net.sf.jsqlparser.statement;
+import net.sf.jsqlparser.expression.Expression;
+import net.sf.jsqlparser.schema.Table;
+import net.sf.jsqlparser.statement.show.ShowTablesStatement.SelectionMode;
+
+/**
+ * A MySQL {@code SHOW [EXTENDED] [FULL] {COLUMNS | FIELDS} {FROM | IN} table [{FROM | IN} db]
+ * [LIKE 'pattern' | WHERE expr]} statement.
+ *
+ * @see MySQL SHOW COLUMNS
+ */
public class ShowColumnsStatement implements Statement {
- private String tableName;
+ private boolean extended;
+ private boolean full;
+ private boolean fieldsKeyword;
+ private SelectionMode tableSelectionMode = SelectionMode.FROM;
+ private Table table;
+ private SelectionMode dbSelectionMode = SelectionMode.FROM;
+ private String dbName;
+ private Expression likeExpression;
+ private Expression whereCondition;
public ShowColumnsStatement() {
// empty constructor
}
public ShowColumnsStatement(String tableName) {
- this.tableName = tableName;
+ setTableName(tableName);
+ }
+
+ public ShowColumnsStatement(Table table) {
+ this.table = table;
+ }
+
+ public boolean isExtended() {
+ return extended;
+ }
+
+ public void setExtended(boolean extended) {
+ this.extended = extended;
+ }
+
+ public boolean isFull() {
+ return full;
+ }
+
+ public void setFull(boolean full) {
+ this.full = full;
+ }
+
+ /** Whether the statement is spelled with {@code FIELDS} instead of {@code COLUMNS}. */
+ public boolean isFieldsKeyword() {
+ return fieldsKeyword;
+ }
+
+ public void setFieldsKeyword(boolean fieldsKeyword) {
+ this.fieldsKeyword = fieldsKeyword;
+ }
+
+ /** The keyword before the table name, {@code FROM} unless {@code IN} was written. */
+ public SelectionMode getTableSelectionMode() {
+ return tableSelectionMode;
+ }
+
+ public void setTableSelectionMode(SelectionMode tableSelectionMode) {
+ this.tableSelectionMode = tableSelectionMode;
+ }
+
+ public Table getTable() {
+ return table;
}
+ public void setTable(Table table) {
+ this.table = table;
+ }
+
+ /** The table name as written, including any qualifier; derived from {@link #getTable()}. */
public String getTableName() {
- return tableName;
+ return table == null ? null : table.getFullyQualifiedName();
}
public void setTableName(String tableName) {
- this.tableName = tableName;
+ this.table = tableName == null ? null : new Table(tableName);
+ }
+
+ /** The keyword before the database name, {@code FROM} unless {@code IN} was written. */
+ public SelectionMode getDbSelectionMode() {
+ return dbSelectionMode;
+ }
+
+ public void setDbSelectionMode(SelectionMode dbSelectionMode) {
+ this.dbSelectionMode = dbSelectionMode;
+ }
+
+ /** The database of the trailing {@code FROM db} or {@code IN db} clause, or null. */
+ public String getDbName() {
+ return dbName;
+ }
+
+ public void setDbName(String dbName) {
+ this.dbName = dbName;
+ }
+
+ public Expression getLikeExpression() {
+ return likeExpression;
+ }
+
+ public void setLikeExpression(Expression likeExpression) {
+ this.likeExpression = likeExpression;
+ }
+
+ public Expression getWhereCondition() {
+ return whereCondition;
+ }
+
+ public void setWhereCondition(Expression whereCondition) {
+ this.whereCondition = whereCondition;
}
@Override
public String toString() {
- return "SHOW COLUMNS FROM " + tableName;
+ StringBuilder builder = new StringBuilder("SHOW");
+ if (extended) {
+ builder.append(" EXTENDED");
+ }
+ if (full) {
+ builder.append(" FULL");
+ }
+ builder.append(fieldsKeyword ? " FIELDS " : " COLUMNS ")
+ .append(tableSelectionMode == null ? SelectionMode.FROM : tableSelectionMode)
+ .append(' ').append(table);
+ if (dbName != null) {
+ builder.append(' ')
+ .append(dbSelectionMode == null ? SelectionMode.FROM : dbSelectionMode)
+ .append(' ').append(dbName);
+ }
+ if (likeExpression != null) {
+ builder.append(" LIKE ").append(likeExpression);
+ } else if (whereCondition != null) {
+ builder.append(" WHERE ").append(whereCondition);
+ }
+ return builder.toString();
}
@Override
@@ -43,4 +162,49 @@ public ShowColumnsStatement withTableName(String tableName) {
this.setTableName(tableName);
return this;
}
+
+ public ShowColumnsStatement withTable(Table table) {
+ this.setTable(table);
+ return this;
+ }
+
+ public ShowColumnsStatement withExtended(boolean extended) {
+ this.setExtended(extended);
+ return this;
+ }
+
+ public ShowColumnsStatement withFull(boolean full) {
+ this.setFull(full);
+ return this;
+ }
+
+ public ShowColumnsStatement withFieldsKeyword(boolean fieldsKeyword) {
+ this.setFieldsKeyword(fieldsKeyword);
+ return this;
+ }
+
+ public ShowColumnsStatement withTableSelectionMode(SelectionMode tableSelectionMode) {
+ this.setTableSelectionMode(tableSelectionMode);
+ return this;
+ }
+
+ public ShowColumnsStatement withDbSelectionMode(SelectionMode dbSelectionMode) {
+ this.setDbSelectionMode(dbSelectionMode);
+ return this;
+ }
+
+ public ShowColumnsStatement withDbName(String dbName) {
+ this.setDbName(dbName);
+ return this;
+ }
+
+ public ShowColumnsStatement withLikeExpression(Expression likeExpression) {
+ this.setLikeExpression(likeExpression);
+ return this;
+ }
+
+ public ShowColumnsStatement withWhereCondition(Expression whereCondition) {
+ this.setWhereCondition(whereCondition);
+ return this;
+ }
}
diff --git a/src/main/java/net/sf/jsqlparser/statement/StatementFeatureVisitor.java b/src/main/java/net/sf/jsqlparser/statement/StatementFeatureVisitor.java
index 91b2721249..5298d37080 100644
--- a/src/main/java/net/sf/jsqlparser/statement/StatementFeatureVisitor.java
+++ b/src/main/java/net/sf/jsqlparser/statement/StatementFeatureVisitor.java
@@ -109,7 +109,9 @@
import net.sf.jsqlparser.statement.select.SelectVisitorAdapter;
import net.sf.jsqlparser.statement.select.TableStatement;
import net.sf.jsqlparser.statement.select.WithItem;
+import net.sf.jsqlparser.statement.show.ShowCreateStatement;
import net.sf.jsqlparser.statement.show.ShowIndexStatement;
+import net.sf.jsqlparser.statement.show.ShowTableStatusStatement;
import net.sf.jsqlparser.statement.show.ShowTablesStatement;
import net.sf.jsqlparser.statement.truncate.Truncate;
import net.sf.jsqlparser.statement.update.Update;
@@ -923,6 +925,16 @@ public Void visit(ShowTablesStatement showTables, S context) {
return metadataOnly();
}
+ @Override
+ public Void visit(ShowTableStatusStatement showTableStatus, S context) {
+ return metadataOnly();
+ }
+
+ @Override
+ public Void visit(ShowCreateStatement showCreate, S context) {
+ return metadataOnly();
+ }
+
@Override
public Void visit(DescribeStatement describe, S context) {
return metadataOnly();
diff --git a/src/main/java/net/sf/jsqlparser/statement/StatementVisitor.java b/src/main/java/net/sf/jsqlparser/statement/StatementVisitor.java
index bf0bf3e8b5..9910767a06 100644
--- a/src/main/java/net/sf/jsqlparser/statement/StatementVisitor.java
+++ b/src/main/java/net/sf/jsqlparser/statement/StatementVisitor.java
@@ -84,7 +84,9 @@
import net.sf.jsqlparser.statement.merge.Merge;
import net.sf.jsqlparser.statement.refresh.RefreshMaterializedViewStatement;
import net.sf.jsqlparser.statement.select.Select;
+import net.sf.jsqlparser.statement.show.ShowCreateStatement;
import net.sf.jsqlparser.statement.show.ShowIndexStatement;
+import net.sf.jsqlparser.statement.show.ShowTableStatusStatement;
import net.sf.jsqlparser.statement.show.ShowTablesStatement;
import net.sf.jsqlparser.statement.truncate.Truncate;
import net.sf.jsqlparser.statement.update.ParenthesedUpdate;
@@ -311,6 +313,22 @@ default void visit(ShowTablesStatement showTables) {
this.visit(showTables, null);
}
+ default T visit(ShowTableStatusStatement showTableStatus, S context) {
+ return null;
+ }
+
+ default void visit(ShowTableStatusStatement showTableStatus) {
+ this.visit(showTableStatus, null);
+ }
+
+ default T visit(ShowCreateStatement showCreate, S context) {
+ return null;
+ }
+
+ default void visit(ShowCreateStatement showCreate) {
+ this.visit(showCreate, null);
+ }
+
T visit(Merge merge, S context);
default void visit(Merge merge) {
diff --git a/src/main/java/net/sf/jsqlparser/statement/StatementVisitorAdapter.java b/src/main/java/net/sf/jsqlparser/statement/StatementVisitorAdapter.java
index 8a80763ca3..2c22fc97df 100644
--- a/src/main/java/net/sf/jsqlparser/statement/StatementVisitorAdapter.java
+++ b/src/main/java/net/sf/jsqlparser/statement/StatementVisitorAdapter.java
@@ -98,7 +98,9 @@
import net.sf.jsqlparser.statement.select.SelectVisitor;
import net.sf.jsqlparser.statement.select.SelectVisitorAdapter;
import net.sf.jsqlparser.statement.select.WithItem;
+import net.sf.jsqlparser.statement.show.ShowCreateStatement;
import net.sf.jsqlparser.statement.show.ShowIndexStatement;
+import net.sf.jsqlparser.statement.show.ShowTableStatusStatement;
import net.sf.jsqlparser.statement.show.ShowTablesStatement;
import net.sf.jsqlparser.statement.truncate.Truncate;
import net.sf.jsqlparser.statement.update.ParenthesedUpdate;
@@ -567,6 +569,16 @@ public T visit(ShowTablesStatement showTables, S context) {
return null;
}
+ @Override
+ public T visit(ShowTableStatusStatement showTableStatus, S context) {
+ return null;
+ }
+
+ @Override
+ public T visit(ShowCreateStatement showCreate, S context) {
+ return null;
+ }
+
@Override
public T visit(DeclareStatement declareStatement, S context) {
return null;
diff --git a/src/main/java/net/sf/jsqlparser/statement/show/ShowCreateStatement.java b/src/main/java/net/sf/jsqlparser/statement/show/ShowCreateStatement.java
new file mode 100644
index 0000000000..fa36ea1717
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/statement/show/ShowCreateStatement.java
@@ -0,0 +1,191 @@
+/*-
+ * #%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.show;
+
+import net.sf.jsqlparser.expression.StringValue;
+import net.sf.jsqlparser.schema.Table;
+import net.sf.jsqlparser.statement.Statement;
+import net.sf.jsqlparser.statement.StatementVisitor;
+import net.sf.jsqlparser.statement.create.user.UserAccount;
+
+/**
+ * A MySQL {@code SHOW CREATE} statement, which returns the definition of one object.
+ *
+ *
+ * - {@code TABLE}, {@code VIEW}, {@code PROCEDURE}, {@code FUNCTION}, {@code TRIGGER} and
+ * {@code EVENT} name the object with {@link #getName()}.
+ * - {@code DATABASE} (also spelled {@code SCHEMA}) uses {@link #getDatabaseName()} and may carry
+ * {@code IF NOT EXISTS}.
+ * - {@code USER} uses {@link #getAccount()}, which is {@code CURRENT_USER} for the session
+ * account.
+ *
+ *
+ * @see MySQL SHOW CREATE
+ * TABLE
+ */
+public class ShowCreateStatement implements Statement {
+
+ public enum ObjectType {
+ TABLE, VIEW, PROCEDURE, FUNCTION, TRIGGER, EVENT, DATABASE, USER;
+
+ /** Whether objects of this type are named by {@link ShowCreateStatement#getName()}. */
+ public boolean isNamedByTable() {
+ return this != DATABASE && this != USER;
+ }
+ }
+
+ private ObjectType objectType;
+ private boolean schemaKeyword;
+ private boolean ifNotExists;
+ private Table name;
+ private String databaseName;
+ private UserAccount account;
+ private boolean currentUserParentheses;
+
+ public ShowCreateStatement() {
+ // empty constructor
+ }
+
+ public ShowCreateStatement(ObjectType objectType, Table name) {
+ this.objectType = objectType;
+ this.name = name;
+ }
+
+ public ObjectType getObjectType() {
+ return objectType;
+ }
+
+ public void setObjectType(ObjectType objectType) {
+ this.objectType = objectType;
+ }
+
+ /** Whether {@code DATABASE} was spelled {@code SCHEMA}. */
+ public boolean isSchemaKeyword() {
+ return schemaKeyword;
+ }
+
+ public void setSchemaKeyword(boolean schemaKeyword) {
+ this.schemaKeyword = schemaKeyword;
+ }
+
+ /** Whether {@code SHOW CREATE DATABASE IF NOT EXISTS} was written. */
+ public boolean isIfNotExists() {
+ return ifNotExists;
+ }
+
+ public void setIfNotExists(boolean ifNotExists) {
+ this.ifNotExists = ifNotExists;
+ }
+
+ /** The table, view, routine, trigger or event name; null for DATABASE and USER. */
+ public Table getName() {
+ return name;
+ }
+
+ public void setName(Table name) {
+ this.name = name;
+ }
+
+ /** The database name of {@code SHOW CREATE DATABASE}; null otherwise. */
+ public String getDatabaseName() {
+ return databaseName;
+ }
+
+ public void setDatabaseName(String databaseName) {
+ this.databaseName = databaseName;
+ }
+
+ /** The account of {@code SHOW CREATE USER}; null otherwise. */
+ public UserAccount getAccount() {
+ return account;
+ }
+
+ public void setAccount(UserAccount account) {
+ this.account = account;
+ }
+
+ /** Whether the account was written as {@code CURRENT_USER()}. */
+ public boolean isCurrentUserParentheses() {
+ return currentUserParentheses;
+ }
+
+ public void setCurrentUserParentheses(boolean currentUserParentheses) {
+ this.currentUserParentheses = currentUserParentheses;
+ }
+
+ /** Whether the account is {@code CURRENT_USER} or {@code CURRENT_USER()}. */
+ public boolean isCurrentUser() {
+ if (account == null || account.getHost() != null || account.getUser() == null) {
+ return false;
+ }
+ StringValue user = account.getUser();
+ return "".equals(user.getQuoteStr()) && "CURRENT_USER".equalsIgnoreCase(user.getValue());
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder("SHOW CREATE ");
+ if (objectType == ObjectType.DATABASE) {
+ builder.append(schemaKeyword ? "SCHEMA" : "DATABASE");
+ if (ifNotExists) {
+ builder.append(" IF NOT EXISTS");
+ }
+ builder.append(' ').append(databaseName);
+ } else if (objectType == ObjectType.USER) {
+ builder.append("USER ").append(account);
+ if (currentUserParentheses) {
+ builder.append("()");
+ }
+ } else {
+ builder.append(objectType).append(' ').append(name);
+ }
+ return builder.toString();
+ }
+
+ @Override
+ public T accept(StatementVisitor statementVisitor, S context) {
+ return statementVisitor.visit(this, context);
+ }
+
+ public ShowCreateStatement withObjectType(ObjectType objectType) {
+ this.setObjectType(objectType);
+ return this;
+ }
+
+ public ShowCreateStatement withSchemaKeyword(boolean schemaKeyword) {
+ this.setSchemaKeyword(schemaKeyword);
+ return this;
+ }
+
+ public ShowCreateStatement withIfNotExists(boolean ifNotExists) {
+ this.setIfNotExists(ifNotExists);
+ return this;
+ }
+
+ public ShowCreateStatement withName(Table name) {
+ this.setName(name);
+ return this;
+ }
+
+ public ShowCreateStatement withDatabaseName(String databaseName) {
+ this.setDatabaseName(databaseName);
+ return this;
+ }
+
+ public ShowCreateStatement withAccount(UserAccount account) {
+ this.setAccount(account);
+ return this;
+ }
+
+ public ShowCreateStatement withCurrentUserParentheses(boolean currentUserParentheses) {
+ this.setCurrentUserParentheses(currentUserParentheses);
+ return this;
+ }
+}
diff --git a/src/main/java/net/sf/jsqlparser/statement/show/ShowTableStatusStatement.java b/src/main/java/net/sf/jsqlparser/statement/show/ShowTableStatusStatement.java
new file mode 100644
index 0000000000..d9a2385e84
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/statement/show/ShowTableStatusStatement.java
@@ -0,0 +1,102 @@
+/*-
+ * #%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.show;
+
+import net.sf.jsqlparser.expression.Expression;
+import net.sf.jsqlparser.statement.Statement;
+import net.sf.jsqlparser.statement.StatementVisitor;
+import net.sf.jsqlparser.statement.show.ShowTablesStatement.SelectionMode;
+
+/**
+ * A MySQL {@code SHOW TABLE STATUS [{FROM | IN} db] [LIKE 'pattern' | WHERE expr]} statement.
+ *
+ * @see MySQL SHOW TABLE
+ * STATUS
+ */
+public class ShowTableStatusStatement implements Statement {
+
+ private SelectionMode selectionMode = SelectionMode.FROM;
+ private String dbName;
+ private Expression likeExpression;
+ private Expression whereCondition;
+
+ /** The keyword before the database name, {@code FROM} unless {@code IN} was written. */
+ public SelectionMode getSelectionMode() {
+ return selectionMode;
+ }
+
+ public void setSelectionMode(SelectionMode selectionMode) {
+ this.selectionMode = selectionMode;
+ }
+
+ public String getDbName() {
+ return dbName;
+ }
+
+ public void setDbName(String dbName) {
+ this.dbName = dbName;
+ }
+
+ public Expression getLikeExpression() {
+ return likeExpression;
+ }
+
+ public void setLikeExpression(Expression likeExpression) {
+ this.likeExpression = likeExpression;
+ }
+
+ public Expression getWhereCondition() {
+ return whereCondition;
+ }
+
+ public void setWhereCondition(Expression whereCondition) {
+ this.whereCondition = whereCondition;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder("SHOW TABLE STATUS");
+ if (dbName != null) {
+ builder.append(' ').append(selectionMode == null ? SelectionMode.FROM : selectionMode)
+ .append(' ').append(dbName);
+ }
+ if (likeExpression != null) {
+ builder.append(" LIKE ").append(likeExpression);
+ } else if (whereCondition != null) {
+ builder.append(" WHERE ").append(whereCondition);
+ }
+ return builder.toString();
+ }
+
+ @Override
+ public T accept(StatementVisitor statementVisitor, S context) {
+ return statementVisitor.visit(this, context);
+ }
+
+ public ShowTableStatusStatement withSelectionMode(SelectionMode selectionMode) {
+ this.setSelectionMode(selectionMode);
+ return this;
+ }
+
+ public ShowTableStatusStatement withDbName(String dbName) {
+ this.setDbName(dbName);
+ return this;
+ }
+
+ public ShowTableStatusStatement withLikeExpression(Expression likeExpression) {
+ this.setLikeExpression(likeExpression);
+ return this;
+ }
+
+ public ShowTableStatusStatement withWhereCondition(Expression whereCondition) {
+ this.setWhereCondition(whereCondition);
+ return this;
+ }
+}
diff --git a/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java b/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java
index aeecdb89d8..89c6b31eea 100644
--- a/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java
+++ b/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java
@@ -243,7 +243,9 @@
import net.sf.jsqlparser.statement.select.UnPivotQuery;
import net.sf.jsqlparser.statement.select.Values;
import net.sf.jsqlparser.statement.select.WithItem;
+import net.sf.jsqlparser.statement.show.ShowCreateStatement;
import net.sf.jsqlparser.statement.show.ShowIndexStatement;
+import net.sf.jsqlparser.statement.show.ShowTableStatusStatement;
import net.sf.jsqlparser.statement.show.ShowTablesStatement;
import net.sf.jsqlparser.statement.truncate.Truncate;
import net.sf.jsqlparser.statement.update.ParenthesedUpdate;
@@ -1879,7 +1881,14 @@ public void visit(ResetStatement reset) {
@Override
public Void visit(ShowColumnsStatement showColumnsStatement, S context) {
- throwUnsupported(showColumnsStatement);
+ Table table = showColumnsStatement.getTable();
+ if (table != null && showColumnsStatement.getDbName() != null) {
+ // MySQL: the trailing FROM db names the database of the table
+ table = new Table(showColumnsStatement.getDbName(), table.getName());
+ }
+ if (table != null) {
+ visit(table, context);
+ }
return null;
}
@@ -2336,6 +2345,32 @@ public void visit(ShowTablesStatement showTables) {
StatementVisitor.super.visit(showTables);
}
+ @Override
+ public Void visit(ShowTableStatusStatement showTableStatus, S context) {
+ // reads the status of the tables of a database, but names no table
+ return null;
+ }
+
+ @Override
+ public void visit(ShowTableStatusStatement showTableStatus) {
+ StatementVisitor.super.visit(showTableStatus);
+ }
+
+ @Override
+ public Void visit(ShowCreateStatement showCreate, S context) {
+ ShowCreateStatement.ObjectType type = showCreate.getObjectType();
+ if ((type == ShowCreateStatement.ObjectType.TABLE
+ || type == ShowCreateStatement.ObjectType.VIEW) && showCreate.getName() != null) {
+ visit(showCreate.getName(), context);
+ }
+ return null;
+ }
+
+ @Override
+ public void visit(ShowCreateStatement showCreate) {
+ StatementVisitor.super.visit(showCreate);
+ }
+
@Override
public Void visit(TSQLLeftJoin tsqlLeftJoin, S context) {
visitBinaryExpression(tsqlLeftJoin);
diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/ShowColumnsStatementDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/ShowColumnsStatementDeParser.java
index 9fc744010a..1bc8534a53 100644
--- a/src/main/java/net/sf/jsqlparser/util/deparser/ShowColumnsStatementDeParser.java
+++ b/src/main/java/net/sf/jsqlparser/util/deparser/ShowColumnsStatementDeParser.java
@@ -19,6 +19,6 @@ public ShowColumnsStatementDeParser(StringBuilder buffer) {
@Override
public void deParse(ShowColumnsStatement show) {
- builder.append("SHOW COLUMNS FROM ").append(show.getTableName());
+ builder.append(show);
}
}
diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/StatementDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/StatementDeParser.java
index 2490baa3ec..1bbe47c9c1 100644
--- a/src/main/java/net/sf/jsqlparser/util/deparser/StatementDeParser.java
+++ b/src/main/java/net/sf/jsqlparser/util/deparser/StatementDeParser.java
@@ -114,7 +114,9 @@
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.select.SelectVisitor;
import net.sf.jsqlparser.statement.select.WithItem;
+import net.sf.jsqlparser.statement.show.ShowCreateStatement;
import net.sf.jsqlparser.statement.show.ShowIndexStatement;
+import net.sf.jsqlparser.statement.show.ShowTableStatusStatement;
import net.sf.jsqlparser.statement.show.ShowTablesStatement;
import net.sf.jsqlparser.statement.truncate.Truncate;
import net.sf.jsqlparser.statement.update.ParenthesedUpdate;
@@ -454,6 +456,18 @@ public StringBuilder visit(ShowTablesStatement showTables, S context) {
return builder;
}
+ @Override
+ public StringBuilder visit(ShowTableStatusStatement showTableStatus, S context) {
+ builder.append(showTableStatus);
+ return builder;
+ }
+
+ @Override
+ public StringBuilder visit(ShowCreateStatement showCreate, S context) {
+ builder.append(showCreate);
+ return builder;
+ }
+
@Override
public StringBuilder visit(Block block, S context) {
return block.appendTo(builder, statements -> statements.accept(this, context));
diff --git a/src/main/java/net/sf/jsqlparser/util/validation/feature/MariaDbVersion.java b/src/main/java/net/sf/jsqlparser/util/validation/feature/MariaDbVersion.java
index de84d3f823..3f49533302 100644
--- a/src/main/java/net/sf/jsqlparser/util/validation/feature/MariaDbVersion.java
+++ b/src/main/java/net/sf/jsqlparser/util/validation/feature/MariaDbVersion.java
@@ -122,7 +122,9 @@ public enum MariaDbVersion implements Version {
Feature.show, // https://mariadb.com/kb/en/show-tables/
Feature.showTables, // https://mariadb.com/kb/en/show-columns/
Feature.showColumns, // https://mariadb.com/kb/en/show-index/
- Feature.showIndex, // https://mariadb.com/kb/en/use/
+ Feature.showIndex, // https://mariadb.com/kb/en/show-table-status/
+ Feature.showTableStatus, // https://mariadb.com/kb/en/show-create-table/
+ Feature.showCreate, // https://mariadb.com/kb/en/use/
Feature.use, // https://mariadb.com/kb/en/grant/
Feature.grant, // https://mariadb.com/kb/en/commit/
Feature.commit, // https://mariadb.com/kb/en/optimizer-hints/
diff --git a/src/main/java/net/sf/jsqlparser/util/validation/feature/MySqlVersion.java b/src/main/java/net/sf/jsqlparser/util/validation/feature/MySqlVersion.java
index 6aabf1fe0d..1a25274ea9 100644
--- a/src/main/java/net/sf/jsqlparser/util/validation/feature/MySqlVersion.java
+++ b/src/main/java/net/sf/jsqlparser/util/validation/feature/MySqlVersion.java
@@ -111,7 +111,9 @@ public enum MySqlVersion implements Version {
Feature.show, // https://dev.mysql.com/doc/refman/8.0/en/show-tables.html
Feature.showTables, // https://dev.mysql.com/doc/refman/8.0/en/show-columns.html
Feature.showColumns, // https://dev.mysql.com/doc/refman/8.0/en/show-index.html
- Feature.showIndex, // https://dev.mysql.com/doc/refman/8.0/en/grant.html
+ Feature.showIndex, // https://dev.mysql.com/doc/refman/8.0/en/show-table-status.html
+ Feature.showTableStatus, // https://dev.mysql.com/doc/refman/8.0/en/show-create-table.html
+ Feature.showCreate, // https://dev.mysql.com/doc/refman/8.0/en/grant.html
Feature.grant, // https://dev.mysql.com/doc/refman/8.0/en/use.html
Feature.use, // https://dev.mysql.com/doc/refman/8.0/en/commit.html
Feature.commit, Feature.startTransaction, Feature.releaseSavepoint,
diff --git a/src/main/java/net/sf/jsqlparser/util/validation/validator/ShowColumnsStatementValidator.java b/src/main/java/net/sf/jsqlparser/util/validation/validator/ShowColumnsStatementValidator.java
index e917d9e8ce..4cb91dbf45 100644
--- a/src/main/java/net/sf/jsqlparser/util/validation/validator/ShowColumnsStatementValidator.java
+++ b/src/main/java/net/sf/jsqlparser/util/validation/validator/ShowColumnsStatementValidator.java
@@ -20,6 +20,11 @@ public class ShowColumnsStatementValidator extends AbstractValidator {
+
+ @Override
+ public void validate(ShowCreateStatement show) {
+ for (ValidationCapability c : getCapabilities()) {
+ validateFeature(c, Feature.showCreate);
+ NamedObject namedObject = namedObject(show.getObjectType());
+ if (namedObject == NamedObject.database) {
+ validateOptionalName(c, namedObject, show.getDatabaseName());
+ } else if (namedObject != null && show.getName() != null) {
+ validateName(c, namedObject, show.getName().getFullyQualifiedName());
+ }
+ }
+ }
+
+ /** The metadata kind checked for the shown object, or null if there is none. */
+ private static NamedObject namedObject(ShowCreateStatement.ObjectType type) {
+ if (type == null) {
+ return null;
+ }
+ switch (type) {
+ case TABLE:
+ return NamedObject.table;
+ case VIEW:
+ return NamedObject.view;
+ case PROCEDURE:
+ return NamedObject.procedure;
+ case DATABASE:
+ return NamedObject.database;
+ default:
+ // functions, triggers, events and accounts are not looked up
+ return null;
+ }
+ }
+}
diff --git a/src/main/java/net/sf/jsqlparser/util/validation/validator/ShowTableStatusStatementValidator.java b/src/main/java/net/sf/jsqlparser/util/validation/validator/ShowTableStatusStatementValidator.java
new file mode 100644
index 0000000000..1903f358b9
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/util/validation/validator/ShowTableStatusStatementValidator.java
@@ -0,0 +1,27 @@
+/*-
+ * #%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.util.validation.validator;
+
+import net.sf.jsqlparser.parser.feature.Feature;
+import net.sf.jsqlparser.statement.show.ShowTableStatusStatement;
+import net.sf.jsqlparser.util.validation.ValidationCapability;
+import net.sf.jsqlparser.util.validation.metadata.NamedObject;
+
+public class ShowTableStatusStatementValidator
+ extends AbstractValidator {
+
+ @Override
+ public void validate(ShowTableStatusStatement show) {
+ for (ValidationCapability c : getCapabilities()) {
+ validateFeature(c, Feature.showTableStatus);
+ validateOptionalName(c, NamedObject.database, show.getDbName());
+ }
+ }
+}
diff --git a/src/main/java/net/sf/jsqlparser/util/validation/validator/StatementValidator.java b/src/main/java/net/sf/jsqlparser/util/validation/validator/StatementValidator.java
index 1de8380a75..fbe7846c53 100644
--- a/src/main/java/net/sf/jsqlparser/util/validation/validator/StatementValidator.java
+++ b/src/main/java/net/sf/jsqlparser/util/validation/validator/StatementValidator.java
@@ -114,7 +114,9 @@
import net.sf.jsqlparser.statement.refresh.RefreshMaterializedViewStatement;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.select.SelectVisitor;
+import net.sf.jsqlparser.statement.show.ShowCreateStatement;
import net.sf.jsqlparser.statement.show.ShowIndexStatement;
+import net.sf.jsqlparser.statement.show.ShowTableStatusStatement;
import net.sf.jsqlparser.statement.show.ShowTablesStatement;
import net.sf.jsqlparser.statement.truncate.Truncate;
import net.sf.jsqlparser.statement.update.ParenthesedUpdate;
@@ -383,6 +385,18 @@ public Void visit(ShowTablesStatement showTables, S context) {
return null;
}
+ @Override
+ public Void visit(ShowTableStatusStatement showTableStatus, S context) {
+ getValidator(ShowTableStatusStatementValidator.class).validate(showTableStatus);
+ return null;
+ }
+
+ @Override
+ public Void visit(ShowCreateStatement showCreate, S context) {
+ getValidator(ShowCreateStatementValidator.class).validate(showCreate);
+ return null;
+ }
+
@Override
public Void visit(Block block, S context) {
validateFeature(Feature.block);
@@ -774,6 +788,16 @@ public void visit(ShowTablesStatement showTables) {
visit(showTables, null);
}
+ @Override
+ public void visit(ShowTableStatusStatement showTableStatus) {
+ visit(showTableStatus, null);
+ }
+
+ @Override
+ public void visit(ShowCreateStatement showCreate) {
+ visit(showCreate, null);
+ }
+
public void visit(Block block) {
visit(block, null);
}
diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
index 2210dbe9d8..6deb75742e 100644
--- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
+++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
@@ -6203,12 +6203,20 @@ Statement Show():
{
(
- LOOKAHEAD(2) statement = ShowColumns()
+ // A typed form must span the whole statement; other dialects' trailing clauses
+ // (e.g. Snowflake IN SCHEMA s, ClickHouse FORMAT f) still fall back to captureRest().
+ LOOKAHEAD(ShowColumns() ShowStatementEnd()) statement = ShowColumns()
|
LOOKAHEAD(2) statement = ShowIndex()
|
LOOKAHEAD(2) statement = ShowTables()
|
+ LOOKAHEAD(ShowTableStatus() ShowStatementEnd(),
+ { getToken(1).kind == K_TABLE && "STATUS".equalsIgnoreCase(getToken(2).image) })
+ statement = ShowTableStatus()
+ |
+ LOOKAHEAD(ShowCreate() ShowStatementEnd()) statement = ShowCreate()
+ |
// any of the RDBMS specific SHOW syntax
captureRest = captureRest()
{
@@ -6224,13 +6232,135 @@ Statement Show():
}
}
+/**
+ * Only used inside the SHOW lookaheads: a typed SHOW statement ends at a semicolon, the input end,
+ * the ELSE or END of an enclosing IF or block, or the start of the next statement, as in
+ * Statements().
+ */
+void ShowStatementEnd():
+{}
+{
+
+ |
+
+ |
+
+ |
+
+ |
+ LOOKAHEAD({ getToken(1).kind != K_FROM && getToken(1).kind != K_PIVOT
+ && getToken(1).kind != K_UNPIVOT && isStatementStartAhead() }) {}
+}
+
+// https://dev.mysql.com/doc/refman/8.4/en/show-columns.html
ShowColumnsStatement ShowColumns(): {
- String tableName;
+ ShowColumnsStatement statement = new ShowColumnsStatement();
+ Table table;
+ String dbName;
+ Expression expression;
+}
+{
+ [ { statement.setExtended(true); } ]
+ [ { statement.setFull(true); } ]
+ ( | { statement.setFieldsKeyword(true); } )
+ (
+
+ |
+ { statement.setTableSelectionMode(ShowTablesStatement.SelectionMode.IN); }
+ )
+ table = Table() { statement.setTable(table); }
+ [
+ LOOKAHEAD(2)
+ (
+
+ |
+ { statement.setDbSelectionMode(ShowTablesStatement.SelectionMode.IN); }
+ )
+ dbName = RelObjectName() { statement.setDbName(dbName); }
+ ]
+ [
+ expression = SimpleExpression() { statement.setLikeExpression(expression); }
+ |
+ expression = Expression() { statement.setWhereCondition(expression); }
+ ]
+ {
+ return statement;
+ }
+}
+
+// https://dev.mysql.com/doc/refman/8.4/en/show-table-status.html
+ShowTableStatusStatement ShowTableStatus(): {
+ ShowTableStatusStatement statement = new ShowTableStatusStatement();
+ String dbName;
+ Expression expression;
+}
+{
+ ContextualKeyword("STATUS")
+ [
+ LOOKAHEAD(2)
+ (
+
+ |
+ { statement.setSelectionMode(ShowTablesStatement.SelectionMode.IN); }
+ )
+ dbName = RelObjectName() { statement.setDbName(dbName); }
+ ]
+ [
+ expression = SimpleExpression() { statement.setLikeExpression(expression); }
+ |
+ expression = Expression() { statement.setWhereCondition(expression); }
+ ]
+ {
+ return statement;
+ }
+}
+
+/**
+ * MySQL SHOW CREATE {TABLE | VIEW | PROCEDURE | FUNCTION | TRIGGER | EVENT} name,
+ * SHOW CREATE {DATABASE | SCHEMA} [IF NOT EXISTS] db and SHOW CREATE USER account.
+ */
+ShowCreateStatement ShowCreate(): {
+ ShowCreateStatement statement = new ShowCreateStatement();
+ Table name;
+ String databaseName;
+ String currentUser;
+ UserAccount account = new UserAccount();
}
{
- tableName = RelObjectName()
+
+ (
+ (
+ { statement.setObjectType(ShowCreateStatement.ObjectType.TABLE); }
+ |
+ { statement.setObjectType(ShowCreateStatement.ObjectType.VIEW); }
+ |
+ { statement.setObjectType(ShowCreateStatement.ObjectType.PROCEDURE); }
+ |
+ { statement.setObjectType(ShowCreateStatement.ObjectType.FUNCTION); }
+ |
+ { statement.setObjectType(ShowCreateStatement.ObjectType.TRIGGER); }
+ |
+ { statement.setObjectType(ShowCreateStatement.ObjectType.EVENT); }
+ )
+ name = Table() { statement.setName(name); }
+ |
+ ( | { statement.setSchemaKeyword(true); } )
+ { statement.setObjectType(ShowCreateStatement.ObjectType.DATABASE); }
+ [ LOOKAHEAD(3) { statement.setIfNotExists(true); } ]
+ databaseName = RelObjectName() { statement.setDatabaseName(databaseName); }
+ |
+ { statement.setObjectType(ShowCreateStatement.ObjectType.USER); }
+ (
+ LOOKAHEAD({ isKeywordAhead("CURRENT_USER") }) currentUser = RelObjectName()
+ { account.setUser(new StringValue(currentUser).setQuoteStr("")); }
+ [ LOOKAHEAD(2) "(" ")" { statement.setCurrentUserParentheses(true); } ]
+ |
+ account = MySqlAccountName()
+ )
+ { statement.setAccount(account); }
+ )
{
- return new ShowColumnsStatement(tableName);
+ return statement;
}
}
@@ -15502,11 +15632,66 @@ StringValue MySqlAccountNamePart():
(
token= { value = stringValue(token); }
|
- name=RelObjectName() { value = new StringValue(name).setQuoteStr(""); }
+ name=RelObjectName()
+ {
+ // a double-quoted or backquoted part keeps its quote character separately from its
+ // value, like a string literal; new StringValue(name) would drop the double quotes
+ value = name.length() > 1 && (name.charAt(0) == '"' || name.charAt(0) == '`')
+ && name.charAt(name.length() - 1) == name.charAt(0)
+ ? new StringValue().withValue(name.substring(1, name.length() - 1))
+ .setQuoteStr(name.substring(0, 1))
+ : new StringValue().withValue(name).setQuoteStr("");
+ }
)
{ return value; }
}
+/**
+ * A MySQL account name, user[@host], shared by CREATE USER, DEFINER and the account statements.
+ * Each part is unquoted or quoted with ', " or `. The lexer reads an unquoted user@host, and the
+ * user@ in front of a quoted host, as one identifier, and @host after a quoted user as one
+ * at-identifier, so those spellings are split here.
+ */
+UserAccount MySqlAccountName():
+{
+ UserAccount account = new UserAccount();
+ StringValue part;
+ Token token;
+ int at;
+}
+{
+ (
+ // user@ followed by a quoted host
+ LOOKAHEAD({ getToken(1).kind == S_IDENTIFIER && getToken(1).image.indexOf('@') > 0
+ && getToken(1).image.indexOf('@') == getToken(1).image.length() - 1 })
+ token=
+ { account.setUser(new StringValue().withValue(token.image.substring(0, token.image.length() - 1))
+ .setQuoteStr("")); }
+ part=MySqlAccountNamePart() { account.setHost(part); }
+ |
+ // unquoted user@host
+ LOOKAHEAD({ getToken(1).kind == S_IDENTIFIER && getToken(1).image.indexOf('@') > 0
+ && getToken(1).image.indexOf('@') == getToken(1).image.lastIndexOf('@') })
+ token=
+ {
+ at = token.image.indexOf('@');
+ account.setUser(new StringValue().withValue(token.image.substring(0, at)).setQuoteStr(""));
+ account.setHost(new StringValue().withValue(token.image.substring(at + 1)).setQuoteStr(""));
+ }
+ |
+ part=MySqlAccountNamePart() { account.setUser(part); }
+ [
+ part=MySqlAccountNamePart() { account.setHost(part); }
+ |
+ // a quoted user followed by an unquoted @host
+ LOOKAHEAD({ getToken(1).kind == S_AT_IDENTIFIER && !getToken(1).image.startsWith("@@") })
+ token=
+ { account.setHost(new StringValue().withValue(token.image.substring(1)).setQuoteStr("")); }
+ ]
+ )
+ { return account; }
+}
+
UserAuthentication MySqlUserAuthentication():
{
UserAuthentication authentication = new UserAuthentication();
@@ -15557,14 +15742,11 @@ UserAuthentication MySqlUserAuthentication():
UserAccount MySqlUserAccount():
{
- UserAccount account = new UserAccount();
- StringValue user = null;
- StringValue host = null;
+ UserAccount account;
UserAuthentication authentication = null;
}
{
- user=MySqlAccountNamePart() { account.setUser(user); }
- [ host=MySqlAccountNamePart() { account.setHost(host); } ]
+ account=MySqlAccountName()
[ authentication=MySqlUserAuthentication() { account.setAuthentication(authentication); } ]
{ return account; }
}
@@ -15710,8 +15892,7 @@ AlterEvent AlterEvent():
/** Account spelling is shared, including CURRENT_USER and CURRENT_USER(). */
void MySqlDefiner(MySqlDefiner definer):
{
- StringValue user;
- StringValue host;
+ UserAccount account;
String name;
}
{
@@ -15721,8 +15902,7 @@ void MySqlDefiner(MySqlDefiner definer):
{ definer.setUser(new StringValue(name).setQuoteStr("")); }
[ "(" ")" { definer.setCurrentUserParentheses(true); } ]
|
- user=MySqlAccountNamePart() { definer.setUser(user); }
- [ host=MySqlAccountNamePart() { definer.setHost(host); } ]
+ account=MySqlAccountName() { definer.setUser(account.getUser()); definer.setHost(account.getHost()); }
)
}
diff --git a/src/site/sphinx/index.rst b/src/site/sphinx/index.rst
index 5f3b0157b4..55962c1f30 100644
--- a/src/site/sphinx/index.rst
+++ b/src/site/sphinx/index.rst
@@ -160,6 +160,8 @@ One grammar covers the SQL standard plus all major RDBMS. Missing syntax gets ad
- ``ANALYZE`` · ``CHECK`` · ``OPTIMIZE`` · ``REPAIR TABLE``
* - **PostgreSQL RLS**
- ``CREATE POLICY`` · ``ALTER TABLE … ENABLE``/``DISABLE``/``FORCE``/``NO FORCE ROW LEVEL SECURITY``
+ * - **MySQL metadata statements**
+ - ``SHOW [FULL] COLUMNS``/``FIELDS`` · ``SHOW TABLE STATUS`` · ``SHOW CREATE TABLE``/``VIEW``/``PROCEDURE``/``FUNCTION``/``TRIGGER``/``EVENT``/``DATABASE``/``USER``
* - **Salesforce SOQL**
- ``INCLUDES`` · ``EXCLUDES``
diff --git a/src/site/sphinx/usage.rst b/src/site/sphinx/usage.rst
index 901608d62c..3a13967601 100644
--- a/src/site/sphinx/usage.rst
+++ b/src/site/sphinx/usage.rst
@@ -1718,6 +1718,20 @@ the PostgreSQL ``CreateRole`` AST for this ambiguous form::
"CREATE USER app",
parser -> parser.withDialect(AbstractJSqlParser.Dialect.POSTGRESQL));
+MySQL account names (``user[@host]``) of ``CREATE USER`` and of view, trigger and
+routine ``DEFINER`` clauses share one grammar rule and are read into separate user
+and host ``StringValue`` parts (``UserAccount`` and ``MySqlDefiner``). Every MySQL
+spelling is accepted, including ``app@localhost``, ``app@'%'`` and
+``'app'@localhost``. A part quoted with ``'``, ``"`` or a backquote keeps its quote
+character in ``getQuoteStr()`` and its text in ``getValue()``::
+
+ CreateUser createUser = (CreateUser) CCJSqlParserUtil.parse(
+ "CREATE USER `app`@'%' IDENTIFIED BY 'secret'");
+ UserAccount account = createUser.getAccounts().get(0);
+ account.getUser().getValue(); // app
+ account.getUser().getQuoteStr(); // `
+ account.getHost().getValue(); // %
+
``Grant`` retains its string-based getters, setters and fluent methods. Its
``PrivilegeClause`` exposes typed ``Privilege`` items, column lists,
``PrivilegeTarget`` kinds, multiple role memberships and grantor information.
@@ -2039,3 +2053,47 @@ The validator checks the new statement and mode capabilities. PostgreSQL
``AND [NO] CHAIN`` is enabled beginning with its version 12 capability set.
This support does not include MySQL transaction completion ``[NO] RELEASE``,
PostgreSQL ``COMMIT PREPARED`` / ``ROLLBACK PREPARED``, or XA commands.
+
+MySQL SHOW statements
+=====================
+
+The MySQL statements that read table, column and object definitions parse to typed
+statements in every dialect:
+
+* ``SHOW [EXTENDED] [FULL] {COLUMNS | FIELDS} {FROM | IN} t [{FROM | IN} db]
+ [LIKE 'p' | WHERE expr]`` extends ``ShowColumnsStatement``. ``getTable()`` returns the
+ ``Table``; ``getTableName()`` and ``setTableName(String)`` remain and are derived from it.
+ ``isExtended()``, ``isFull()`` and ``isFieldsKeyword()`` record the modifiers and the
+ ``FIELDS`` spelling, ``getTableSelectionMode()`` and ``getDbSelectionMode()`` the
+ ``FROM``/``IN`` spelling, and ``getDbName()``, ``getLikeExpression()`` and
+ ``getWhereCondition()`` the optional clauses.
+* ``SHOW TABLE STATUS [{FROM | IN} db] [LIKE 'p' | WHERE expr]`` parses to
+ ``ShowTableStatusStatement``, with the same accessors as ``ShowTablesStatement``.
+* ``SHOW CREATE`` parses to ``ShowCreateStatement``. ``getObjectType()`` is ``TABLE``,
+ ``VIEW``, ``PROCEDURE``, ``FUNCTION``, ``TRIGGER``, ``EVENT``, ``DATABASE`` or ``USER``.
+ The first six name the object with ``getName()``, a ``Table``. ``DATABASE`` uses
+ ``getDatabaseName()``, ``isSchemaKeyword()`` and ``isIfNotExists()``. ``USER`` uses
+ ``getAccount()``, a ``UserAccount`` without authentication; ``isCurrentUser()`` and
+ ``isCurrentUserParentheses()`` describe ``CURRENT_USER`` and ``CURRENT_USER()``.
+
+.. code-block:: java
+
+ ShowColumnsStatement columns = (ShowColumnsStatement) CCJSqlParserUtil.parse(
+ "SHOW FULL COLUMNS FROM orders FROM shop LIKE 'c%'");
+ columns.getTableName(); // orders
+ columns.getDbName(); // shop
+
+ ShowCreateStatement create = (ShowCreateStatement) CCJSqlParserUtil.parse(
+ "SHOW CREATE USER 'app'@'%'");
+ create.getAccount().getHost().getNotExcapedValue(); // %
+
+A typed statement is used only when it covers the whole statement. Other dialects'
+``SHOW`` forms, such as ``SHOW COLUMNS IN SCHEMA s`` or ``SHOW CREATE TABLE t FORMAT f``,
+remain ``UnsupportedStatement``.
+
+Table visitors report the table of ``SHOW COLUMNS`` (qualified by the ``FROM db`` clause)
+and the table or view of ``SHOW CREATE TABLE`` and ``SHOW CREATE VIEW``; until now
+``SHOW COLUMNS`` raised ``UnsupportedOperationException``. ``SHOW TABLE STATUS`` names no
+table. Validators report the ``showColumns``, ``showTableStatus`` and ``showCreate``
+features, which the MySQL and MariaDB capabilities enable. Feature analysis reports
+``RETURNS_RESULT_SET``.
diff --git a/src/test/java/net/sf/jsqlparser/statement/ShowColumnsStatementTest.java b/src/test/java/net/sf/jsqlparser/statement/ShowColumnsStatementTest.java
index 825a18def0..eb07f57d62 100644
--- a/src/test/java/net/sf/jsqlparser/statement/ShowColumnsStatementTest.java
+++ b/src/test/java/net/sf/jsqlparser/statement/ShowColumnsStatementTest.java
@@ -9,9 +9,28 @@
*/
package net.sf.jsqlparser.statement;
-import net.sf.jsqlparser.JSQLParserException;
+import static net.sf.jsqlparser.test.TestUtils.assertDeparse;
import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.List;
+import java.util.function.Consumer;
+import net.sf.jsqlparser.JSQLParserException;
+import net.sf.jsqlparser.expression.StringValue;
+import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect;
+import net.sf.jsqlparser.parser.CCJSqlParser;
+import net.sf.jsqlparser.parser.CCJSqlParserUtil;
+import net.sf.jsqlparser.schema.Table;
+import net.sf.jsqlparser.statement.show.ShowTablesStatement.SelectionMode;
+import net.sf.jsqlparser.util.TablesNamesFinder;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
/**
*
@@ -19,8 +38,154 @@
*/
public class ShowColumnsStatementTest {
+ private static final List> DIALECTS = List.of(parser -> {
+ }, parser -> parser.withDialect(Dialect.MYSQL),
+ parser -> parser.withDialect(Dialect.MARIADB));
+
+ /** Parses without a dialect and with MySQL and MariaDB, then deparses and reparses. */
+ private static ShowColumnsStatement roundTrip(String sql) throws JSQLParserException {
+ ShowColumnsStatement result = null;
+ for (Consumer dialect : DIALECTS) {
+ Statement statement = assertSqlCanBeParsedAndDeparsed(sql, false, dialect);
+ assertInstanceOf(ShowColumnsStatement.class, statement);
+ assertDeparse(statement, sql);
+ assertEquals(sql, CCJSqlParserUtil.parse(statement.toString(), dialect).toString());
+ result = (ShowColumnsStatement) statement;
+ }
+ return result;
+ }
+
@Test
public void testSimpleUse() throws JSQLParserException {
assertSqlCanBeParsedAndDeparsed("SHOW COLUMNS FROM mydatabase");
}
+
+ @Test
+ void plainFormKeepsTheTableNameApi() throws JSQLParserException {
+ ShowColumnsStatement show = roundTrip("SHOW COLUMNS FROM mytable");
+
+ assertEquals("mytable", show.getTableName());
+ assertEquals("mytable", show.getTable().getName());
+ assertFalse(show.isExtended());
+ assertFalse(show.isFull());
+ assertFalse(show.isFieldsKeyword());
+ assertEquals(SelectionMode.FROM, show.getTableSelectionMode());
+ assertNull(show.getDbName());
+ assertNull(show.getLikeExpression());
+ assertNull(show.getWhereCondition());
+ }
+
+ @Test
+ void fullColumnsWithDatabaseAndLike() throws JSQLParserException {
+ ShowColumnsStatement show = roundTrip("SHOW FULL COLUMNS FROM gc20 FROM v LIKE 'n%'");
+
+ assertTrue(show.isFull());
+ assertEquals("gc20", show.getTableName());
+ assertEquals(SelectionMode.FROM, show.getDbSelectionMode());
+ assertEquals("v", show.getDbName());
+ assertEquals("n%", ((StringValue) show.getLikeExpression()).getValue());
+ }
+
+ @Test
+ void extendedFullFieldsInWithWhere() throws JSQLParserException {
+ ShowColumnsStatement show =
+ roundTrip("SHOW EXTENDED FULL FIELDS IN db.t IN `other` WHERE `Key` = 'PRI'");
+
+ assertTrue(show.isExtended());
+ assertTrue(show.isFull());
+ assertTrue(show.isFieldsKeyword());
+ assertEquals(SelectionMode.IN, show.getTableSelectionMode());
+ assertEquals("db", show.getTable().getSchemaName());
+ assertEquals("db.t", show.getTableName());
+ assertEquals(SelectionMode.IN, show.getDbSelectionMode());
+ assertEquals("`other`", show.getDbName());
+ assertEquals("`Key` = 'PRI'", show.getWhereCondition().toString());
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {
+ "SHOW FIELDS FROM t",
+ "SHOW FULL FIELDS FROM gc20",
+ "SHOW COLUMNS IN t",
+ "SHOW COLUMNS FROM `db`.`t`",
+ "SHOW COLUMNS FROM t IN db",
+ "SHOW EXTENDED COLUMNS FROM t",
+ "SHOW COLUMNS FROM t LIKE 'a%'",
+ "SHOW FULL COLUMNS FROM t WHERE Comment <> ''"
+ })
+ void parsesEveryMySqlForm(String sql) throws JSQLParserException {
+ roundTrip(sql);
+ }
+
+ @Test
+ void keywordsAreCaseInsensitive() throws JSQLParserException {
+ assertEquals("SHOW FULL FIELDS IN t FROM db",
+ CCJSqlParserUtil.parse("show full fields in t from db").toString());
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {
+ "SHOW COLUMNS IN SCHEMA s",
+ "SHOW COLUMNS IN TABLE t",
+ "SHOW COLUMNS LIKE 'x' IN TABLE t",
+ "SHOW COLUMNS FROM t NOT LIKE 'x'",
+ "SHOW COLUMNS FROM t FROM db LIMIT 3",
+ "SHOW FULL PROCESSLIST"
+ })
+ void otherDialectFormsRemainUnsupported(String sql) throws JSQLParserException {
+ Statement statement = assertSqlCanBeParsedAndDeparsed(sql, true);
+ assertInstanceOf(UnsupportedStatement.class, statement);
+ }
+
+ @Test
+ void endsAtTheStatementBoundary() throws JSQLParserException {
+ Statements statements =
+ CCJSqlParserUtil.parseStatements("SHOW FULL COLUMNS FROM t;\nSELECT 1");
+
+ assertEquals(2, statements.size());
+ assertInstanceOf(ShowColumnsStatement.class, statements.get(0));
+
+ IfElseStatement ifElse = (IfElseStatement) CCJSqlParserUtil
+ .parse("IF a = 1 SHOW COLUMNS FROM t ELSE SHOW TABLE STATUS");
+ assertInstanceOf(ShowColumnsStatement.class, ifElse.getIfStatement());
+ assertEquals("SHOW TABLE STATUS", ifElse.getElseStatement().toString());
+ }
+
+ @Test
+ void findsTheTable() throws JSQLParserException {
+ assertThat(TablesNamesFinder.findTables("SHOW FULL COLUMNS FROM s.t"))
+ .containsExactly("s.t");
+ // the trailing FROM db names the database of the table
+ assertThat(TablesNamesFinder.findTables("SHOW COLUMNS FROM t FROM db"))
+ .containsExactly("db.t");
+ }
+
+ @Test
+ void legacyTableNameAccessors() {
+ ShowColumnsStatement show = new ShowColumnsStatement("db.t");
+
+ assertEquals("db", show.getTable().getSchemaName());
+ assertEquals("db.t", show.getTableName());
+ assertEquals("SHOW COLUMNS FROM db.t", show.toString());
+ assertEquals("SHOW COLUMNS FROM t2", show.withTableName("t2").toString());
+ show.setTableName(null);
+ assertNull(show.getTable());
+ }
+
+ @Test
+ void buildsAndDeparses() {
+ ShowColumnsStatement show = new ShowColumnsStatement(new Table("t")).withFull(true)
+ .withFieldsKeyword(true).withTableSelectionMode(SelectionMode.IN)
+ .withDbName("db").withLikeExpression(new StringValue("a%"));
+
+ assertDeparse(show, "SHOW FULL FIELDS IN t FROM db LIKE 'a%'");
+ }
+
+ @Test
+ void reportsMetadataFeatures() throws JSQLParserException {
+ StatementFeatures features =
+ CCJSqlParserUtil.parse("SHOW FULL COLUMNS FROM t").getFeatures();
+
+ assertThat(features.getCertain()).containsExactly(StmtFeature.RETURNS_RESULT_SET);
+ }
}
diff --git a/src/test/java/net/sf/jsqlparser/statement/ShowStatementTest.java b/src/test/java/net/sf/jsqlparser/statement/ShowStatementTest.java
index 7f10338964..23830cbc9e 100644
--- a/src/test/java/net/sf/jsqlparser/statement/ShowStatementTest.java
+++ b/src/test/java/net/sf/jsqlparser/statement/ShowStatementTest.java
@@ -10,6 +10,7 @@
package net.sf.jsqlparser.statement;
import net.sf.jsqlparser.JSQLParserException;
+import net.sf.jsqlparser.statement.show.ShowCreateStatement;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
@@ -43,6 +44,6 @@ void testShowCreateTable() throws JSQLParserException {
String sqlStr =
"show create table my_table";
Statement statement = assertSqlCanBeParsedAndDeparsed(sqlStr, true);
- Assertions.assertTrue(statement instanceof UnsupportedStatement);
+ Assertions.assertTrue(statement instanceof ShowCreateStatement);
}
}
diff --git a/src/test/java/net/sf/jsqlparser/statement/create/user/MySqlAccountNameTest.java b/src/test/java/net/sf/jsqlparser/statement/create/user/MySqlAccountNameTest.java
new file mode 100644
index 0000000000..455f18b6a5
--- /dev/null
+++ b/src/test/java/net/sf/jsqlparser/statement/create/user/MySqlAccountNameTest.java
@@ -0,0 +1,123 @@
+/*-
+ * #%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.create.user;
+
+import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.util.function.Consumer;
+import net.sf.jsqlparser.JSQLParserException;
+import net.sf.jsqlparser.expression.StringValue;
+import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect;
+import net.sf.jsqlparser.parser.CCJSqlParser;
+import net.sf.jsqlparser.parser.CCJSqlParserUtil;
+import net.sf.jsqlparser.statement.MySqlDefiner;
+import net.sf.jsqlparser.statement.create.trigger.CreateTrigger;
+import net.sf.jsqlparser.statement.create.view.CreateView;
+import net.sf.jsqlparser.util.deparser.StatementDeParser;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.junit.jupiter.params.provider.ValueSource;
+import java.util.stream.Stream;
+
+class MySqlAccountNameTest {
+
+ private static final Consumer MYSQL = p -> p.withDialect(Dialect.MYSQL);
+ private static final Consumer NO_DIALECT = p -> {
+ };
+
+ /** SQL spelling, user value, user quote, host value, host quote. */
+ static Stream accounts() {
+ return Stream.of(
+ Arguments.of("app", "app", "", null, null),
+ Arguments.of("app@localhost", "app", "", "localhost", ""),
+ Arguments.of("app@'%'", "app", "", "%", "'"),
+ Arguments.of("app@'10.0.0.%'", "app", "", "10.0.0.%", "'"),
+ Arguments.of("'app'@localhost", "app", "'", "localhost", ""),
+ Arguments.of("'app'@'%'", "app", "'", "%", "'"),
+ Arguments.of("`app`@`%`", "app", "`", "%", "`"),
+ Arguments.of("\"app\"@\"%\"", "app", "\"", "%", "\""),
+ Arguments.of("'app'", "app", "'", null, null),
+ Arguments.of("`my app`@'%'", "my app", "`", "%", "'"));
+ }
+
+ @ParameterizedTest
+ @MethodSource("accounts")
+ void createUserSplitsEveryAccountSpelling(String account, String user, String userQuote,
+ String host, String hostQuote) throws JSQLParserException {
+ for (Consumer mode : java.util.Arrays.asList(MYSQL, NO_DIALECT)) {
+ String sql = "CREATE USER " + account + " IDENTIFIED BY 'x'";
+ CreateUser createUser = assertInstanceOf(CreateUser.class,
+ assertSqlCanBeParsedAndDeparsed(sql, true, mode));
+ assertEquals(sql, createUser.toString());
+ assertEquals(sql, deparse(createUser));
+ assertPart(createUser.getAccounts().get(0).getUser(), user, userQuote);
+ assertPart(createUser.getAccounts().get(0).getHost(), host, hostQuote);
+ }
+ }
+
+ @ParameterizedTest
+ @MethodSource("accounts")
+ void definerSplitsEveryAccountSpelling(String account, String user, String userQuote,
+ String host, String hostQuote) throws JSQLParserException {
+ String view = "CREATE DEFINER = " + account + " VIEW v AS SELECT 1";
+ CreateView createView = assertInstanceOf(CreateView.class,
+ assertSqlCanBeParsedAndDeparsed(view, true, MYSQL));
+ MySqlDefiner definer = createView.getMySqlOptions().getDefiner();
+ assertPart(definer.getUser(), user, userQuote);
+ assertPart(definer.getHost(), host, hostQuote);
+
+ String trigger = "CREATE DEFINER = " + account
+ + " TRIGGER trg BEFORE INSERT ON t FOR EACH ROW SET NEW.a = 1";
+ CreateTrigger createTrigger = assertInstanceOf(CreateTrigger.class,
+ assertSqlCanBeParsedAndDeparsed(trigger, true, MYSQL));
+ assertEquals(trigger, createTrigger.toString());
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"CREATE USER app@", "CREATE USER @host", "CREATE USER 'app'@"})
+ void rejectsMalformedAccounts(String sql) {
+ assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parse(sql, MYSQL));
+ }
+
+ @org.junit.jupiter.api.Test
+ void keepsDoubleQuotesWithoutDialect() throws JSQLParserException {
+ // before, the double quotes were dropped and the account rendered as app@%
+ String sql = "CREATE USER \"app\"@\"%\" IDENTIFIED BY 'x'";
+ assertEquals(sql, CCJSqlParserUtil.parse(sql).toString());
+ assertEquals(sql, CCJSqlParserUtil.parse(sql, MYSQL).toString());
+ }
+
+ @org.junit.jupiter.api.Test
+ void definerCurrentUserIsUnchanged() throws JSQLParserException {
+ CreateView view = assertInstanceOf(CreateView.class, assertSqlCanBeParsedAndDeparsed(
+ "CREATE DEFINER = CURRENT_USER() VIEW v AS SELECT 1", true, MYSQL));
+ assertNull(view.getMySqlOptions().getDefiner().getHost());
+ }
+
+ private static void assertPart(StringValue part, String value, String quote) {
+ if (value == null) {
+ assertNull(part);
+ return;
+ }
+ assertEquals(value, part.getValue());
+ assertEquals(quote, part.getQuoteStr());
+ }
+
+ private static String deparse(CreateUser createUser) {
+ StringBuilder builder = new StringBuilder();
+ createUser.accept(new StatementDeParser(builder), null);
+ return builder.toString();
+ }
+}
diff --git a/src/test/java/net/sf/jsqlparser/statement/show/ShowCreateStatementTest.java b/src/test/java/net/sf/jsqlparser/statement/show/ShowCreateStatementTest.java
new file mode 100644
index 0000000000..2d82488e10
--- /dev/null
+++ b/src/test/java/net/sf/jsqlparser/statement/show/ShowCreateStatementTest.java
@@ -0,0 +1,212 @@
+/*-
+ * #%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.show;
+
+import static net.sf.jsqlparser.test.TestUtils.assertDeparse;
+import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.List;
+import java.util.function.Consumer;
+import net.sf.jsqlparser.JSQLParserException;
+import net.sf.jsqlparser.expression.StringValue;
+import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect;
+import net.sf.jsqlparser.parser.CCJSqlParser;
+import net.sf.jsqlparser.parser.CCJSqlParserUtil;
+import net.sf.jsqlparser.schema.Table;
+import net.sf.jsqlparser.statement.Statement;
+import net.sf.jsqlparser.statement.StatementFeatures;
+import net.sf.jsqlparser.statement.Statements;
+import net.sf.jsqlparser.statement.StmtFeature;
+import net.sf.jsqlparser.statement.UnsupportedStatement;
+import net.sf.jsqlparser.statement.create.user.UserAccount;
+import net.sf.jsqlparser.statement.show.ShowCreateStatement.ObjectType;
+import net.sf.jsqlparser.util.TablesNamesFinder;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+class ShowCreateStatementTest {
+
+ private static final List> DIALECTS = List.of(parser -> {
+ }, parser -> parser.withDialect(Dialect.MYSQL),
+ parser -> parser.withDialect(Dialect.MARIADB));
+
+ /** Parses without a dialect and with MySQL and MariaDB, then deparses and reparses. */
+ private static ShowCreateStatement roundTrip(String sql) throws JSQLParserException {
+ ShowCreateStatement result = null;
+ for (Consumer dialect : DIALECTS) {
+ Statement statement = assertSqlCanBeParsedAndDeparsed(sql, false, dialect);
+ assertInstanceOf(ShowCreateStatement.class, statement);
+ assertDeparse(statement, sql);
+ assertEquals(sql, CCJSqlParserUtil.parse(statement.toString(), dialect).toString());
+ result = (ShowCreateStatement) statement;
+ }
+ return result;
+ }
+
+ @Test
+ void createTable() throws JSQLParserException {
+ ShowCreateStatement show = roundTrip("SHOW CREATE TABLE c20");
+
+ assertEquals(ObjectType.TABLE, show.getObjectType());
+ assertEquals("c20", show.getName().getFullyQualifiedName());
+ assertNull(show.getDatabaseName());
+ assertNull(show.getAccount());
+ }
+
+ @ParameterizedTest
+ @CsvSource({
+ "SHOW CREATE TABLE `db`.`t`, TABLE, `db`.`t`",
+ "SHOW CREATE VIEW db.v, VIEW, db.v",
+ "SHOW CREATE PROCEDURE gp13, PROCEDURE, gp13",
+ "SHOW CREATE FUNCTION db.f, FUNCTION, db.f",
+ "SHOW CREATE TRIGGER tr, TRIGGER, tr",
+ "SHOW CREATE EVENT db.e, EVENT, db.e"
+ })
+ void namedObjects(String sql, ObjectType type, String name) throws JSQLParserException {
+ ShowCreateStatement show = roundTrip(sql);
+
+ assertEquals(type, show.getObjectType());
+ assertTrue(type.isNamedByTable());
+ assertEquals(name, show.getName().getFullyQualifiedName());
+ }
+
+ @Test
+ void createDatabase() throws JSQLParserException {
+ ShowCreateStatement show = roundTrip("SHOW CREATE DATABASE `shop`");
+
+ assertEquals(ObjectType.DATABASE, show.getObjectType());
+ assertFalse(show.isSchemaKeyword());
+ assertFalse(show.isIfNotExists());
+ assertEquals("`shop`", show.getDatabaseName());
+ assertNull(show.getName());
+ }
+
+ @Test
+ void createSchemaIfNotExists() throws JSQLParserException {
+ ShowCreateStatement show = roundTrip("SHOW CREATE SCHEMA IF NOT EXISTS shop");
+
+ assertEquals(ObjectType.DATABASE, show.getObjectType());
+ assertTrue(show.isSchemaKeyword());
+ assertTrue(show.isIfNotExists());
+ assertEquals("shop", show.getDatabaseName());
+ }
+
+ @Test
+ void createUserWithHost() throws JSQLParserException {
+ ShowCreateStatement show = roundTrip("SHOW CREATE USER 'app'@'%'");
+
+ assertEquals(ObjectType.USER, show.getObjectType());
+ assertEquals("app", show.getAccount().getUser().getNotExcapedValue());
+ assertEquals("%", show.getAccount().getHost().getNotExcapedValue());
+ assertNull(show.getAccount().getAuthentication());
+ assertFalse(show.isCurrentUser());
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {
+ "SHOW CREATE USER app",
+ "SHOW CREATE USER 'app'",
+ "SHOW CREATE USER `app`@`localhost`",
+ "SHOW CREATE USER app@localhost",
+ "SHOW CREATE USER app@'%'",
+ "SHOW CREATE USER 'app'@localhost",
+ "SHOW CREATE USER \"app\"@\"%\""
+ })
+ void accountSpellings(String sql) throws JSQLParserException {
+ ShowCreateStatement show = roundTrip(sql);
+
+ assertEquals(ObjectType.USER, show.getObjectType());
+ assertFalse(show.isCurrentUser());
+ assertEquals("app", show.getAccount().getUser().getValue());
+ }
+
+ @Test
+ void currentUser() throws JSQLParserException {
+ ShowCreateStatement show = roundTrip("SHOW CREATE USER CURRENT_USER");
+ assertTrue(show.isCurrentUser());
+ assertFalse(show.isCurrentUserParentheses());
+
+ show = roundTrip("SHOW CREATE USER CURRENT_USER()");
+ assertTrue(show.isCurrentUser());
+ assertTrue(show.isCurrentUserParentheses());
+ assertNull(show.getAccount().getHost());
+ }
+
+ @Test
+ void keywordsAreCaseInsensitive() throws JSQLParserException {
+ assertEquals("SHOW CREATE SCHEMA IF NOT EXISTS db",
+ CCJSqlParserUtil.parse("show create schema if not exists db").toString());
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {
+ "SHOW CREATE TEMPORARY TABLE t",
+ "SHOW CREATE TABLE t FORMAT TabSeparated",
+ "SHOW CREATE TABLE t AS SERDE",
+ "SHOW CREATE SEQUENCE s",
+ "SHOW CREATE PACKAGE BODY p",
+ "SHOW CREATE VIEW IF NOT EXISTS v",
+ "SHOW CREATE DATABASE IF NOT EXISTS",
+ "SHOW CREATE USER u IDENTIFIED BY 'x'",
+ "SHOW CREATE TABLE"
+ })
+ void otherFormsRemainUnsupported(String sql) throws JSQLParserException {
+ Statement statement = assertSqlCanBeParsedAndDeparsed(sql, true);
+ assertInstanceOf(UnsupportedStatement.class, statement);
+ }
+
+ @Test
+ void endsAtTheStatementBoundary() throws JSQLParserException {
+ Statements statements = CCJSqlParserUtil.parseStatements(
+ "SHOW CREATE TABLE a;\nSHOW CREATE USER CURRENT_USER();\nSELECT 1",
+ parser -> parser.withDialect(Dialect.MYSQL));
+
+ assertEquals(3, statements.size());
+ assertInstanceOf(ShowCreateStatement.class, statements.get(0));
+ assertInstanceOf(ShowCreateStatement.class, statements.get(1));
+ }
+
+ @Test
+ void findsTablesAndViewsOnly() throws JSQLParserException {
+ assertThat(TablesNamesFinder.findTables("SHOW CREATE TABLE s.t")).containsExactly("s.t");
+ assertThat(TablesNamesFinder.findTables("SHOW CREATE VIEW v")).containsExactly("v");
+ assertThat(TablesNamesFinder.findTables("SHOW CREATE TRIGGER tr")).isEmpty();
+ assertThat(TablesNamesFinder.findTables("SHOW CREATE DATABASE db")).isEmpty();
+ assertThat(TablesNamesFinder.findTables("SHOW CREATE USER u")).isEmpty();
+ }
+
+ @Test
+ void buildsAndDeparses() {
+ assertDeparse(new ShowCreateStatement(ObjectType.VIEW, new Table("s", "v")),
+ "SHOW CREATE VIEW s.v");
+ assertDeparse(new ShowCreateStatement().withObjectType(ObjectType.DATABASE)
+ .withSchemaKeyword(true).withIfNotExists(true).withDatabaseName("db"),
+ "SHOW CREATE SCHEMA IF NOT EXISTS db");
+ UserAccount account = new UserAccount().withUser(new StringValue("'app'"))
+ .withHost(new StringValue("'localhost'"));
+ assertDeparse(new ShowCreateStatement().withObjectType(ObjectType.USER)
+ .withAccount(account), "SHOW CREATE USER 'app'@'localhost'");
+ }
+
+ @Test
+ void reportsMetadataFeatures() throws JSQLParserException {
+ StatementFeatures features = CCJSqlParserUtil.parse("SHOW CREATE TABLE t").getFeatures();
+
+ assertThat(features.getCertain()).containsExactly(StmtFeature.RETURNS_RESULT_SET);
+ }
+}
diff --git a/src/test/java/net/sf/jsqlparser/statement/show/ShowTableStatusStatementTest.java b/src/test/java/net/sf/jsqlparser/statement/show/ShowTableStatusStatementTest.java
new file mode 100644
index 0000000000..0905f32853
--- /dev/null
+++ b/src/test/java/net/sf/jsqlparser/statement/show/ShowTableStatusStatementTest.java
@@ -0,0 +1,143 @@
+/*-
+ * #%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.show;
+
+import static net.sf.jsqlparser.test.TestUtils.assertDeparse;
+import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import java.util.List;
+import java.util.function.Consumer;
+import net.sf.jsqlparser.JSQLParserException;
+import net.sf.jsqlparser.expression.StringValue;
+import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect;
+import net.sf.jsqlparser.parser.CCJSqlParser;
+import net.sf.jsqlparser.parser.CCJSqlParserUtil;
+import net.sf.jsqlparser.statement.ShowStatement;
+import net.sf.jsqlparser.statement.Statement;
+import net.sf.jsqlparser.statement.StatementFeatures;
+import net.sf.jsqlparser.statement.Statements;
+import net.sf.jsqlparser.statement.StmtFeature;
+import net.sf.jsqlparser.statement.UnsupportedStatement;
+import net.sf.jsqlparser.statement.show.ShowTablesStatement.SelectionMode;
+import net.sf.jsqlparser.util.TablesNamesFinder;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+class ShowTableStatusStatementTest {
+
+ private static final List> DIALECTS = List.of(parser -> {
+ }, parser -> parser.withDialect(Dialect.MYSQL),
+ parser -> parser.withDialect(Dialect.MARIADB));
+
+ /** Parses without a dialect and with MySQL and MariaDB, then deparses and reparses. */
+ private static ShowTableStatusStatement roundTrip(String sql) throws JSQLParserException {
+ ShowTableStatusStatement result = null;
+ for (Consumer dialect : DIALECTS) {
+ Statement statement = assertSqlCanBeParsedAndDeparsed(sql, false, dialect);
+ assertInstanceOf(ShowTableStatusStatement.class, statement);
+ assertDeparse(statement, sql);
+ assertEquals(sql, CCJSqlParserUtil.parse(statement.toString(), dialect).toString());
+ result = (ShowTableStatusStatement) statement;
+ }
+ return result;
+ }
+
+ @Test
+ void currentDatabase() throws JSQLParserException {
+ ShowTableStatusStatement show = roundTrip("SHOW TABLE STATUS");
+
+ assertNull(show.getDbName());
+ assertNull(show.getLikeExpression());
+ assertNull(show.getWhereCondition());
+ }
+
+ @Test
+ void likePattern() throws JSQLParserException {
+ ShowTableStatusStatement show = roundTrip("SHOW TABLE STATUS LIKE 'c0%'");
+
+ assertEquals("c0%", ((StringValue) show.getLikeExpression()).getValue());
+ }
+
+ @Test
+ void databaseAndWhere() throws JSQLParserException {
+ ShowTableStatusStatement show = roundTrip("SHOW TABLE STATUS FROM v WHERE Comment <> ''");
+
+ assertEquals(SelectionMode.FROM, show.getSelectionMode());
+ assertEquals("v", show.getDbName());
+ assertEquals("Comment <> ''", show.getWhereCondition().toString());
+ }
+
+ @Test
+ void inDatabaseAndLike() throws JSQLParserException {
+ ShowTableStatusStatement show = roundTrip("SHOW TABLE STATUS IN `db` LIKE 'a%'");
+
+ assertEquals(SelectionMode.IN, show.getSelectionMode());
+ assertEquals("`db`", show.getDbName());
+ }
+
+ @Test
+ void keywordsAreCaseInsensitive() throws JSQLParserException {
+ assertEquals("SHOW TABLE STATUS IN db",
+ CCJSqlParserUtil.parse("show table status in db").toString());
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {
+ "SHOW TABLE foo",
+ "SHOW TABLE EXTENDED IN db LIKE 'x'",
+ "SHOW TABLE STATUS FROM db LIMIT 3"
+ })
+ void otherTableFormsRemainUnsupported(String sql) throws JSQLParserException {
+ Statement statement = assertSqlCanBeParsedAndDeparsed(sql, true);
+ assertInstanceOf(UnsupportedStatement.class, statement);
+ }
+
+ @Test
+ void neighbouringShowFormsAreUnchanged() throws JSQLParserException {
+ assertInstanceOf(ShowTablesStatement.class, CCJSqlParserUtil.parse("SHOW TABLES"));
+ assertInstanceOf(ShowStatement.class, CCJSqlParserUtil.parse("SHOW STATUS"));
+ assertSqlCanBeParsedAndDeparsed("SELECT status FROM t WHERE status = 1");
+ }
+
+ @Test
+ void endsAtTheStatementBoundary() throws JSQLParserException {
+ Statements statements =
+ CCJSqlParserUtil.parseStatements("SHOW TABLE STATUS;\nSHOW TABLES");
+
+ assertEquals(2, statements.size());
+ assertInstanceOf(ShowTableStatusStatement.class, statements.get(0));
+ }
+
+ @Test
+ void namesNoTable() throws JSQLParserException {
+ assertThat(TablesNamesFinder.findTables("SHOW TABLE STATUS FROM db LIKE 't%'")).isEmpty();
+ }
+
+ @Test
+ void buildsAndDeparses() {
+ ShowTableStatusStatement show = new ShowTableStatusStatement()
+ .withSelectionMode(SelectionMode.IN).withDbName("db")
+ .withLikeExpression(new StringValue("t%"));
+
+ assertDeparse(show, "SHOW TABLE STATUS IN db LIKE 't%'");
+ }
+
+ @Test
+ void reportsMetadataFeatures() throws JSQLParserException {
+ StatementFeatures features = CCJSqlParserUtil.parse("SHOW TABLE STATUS").getFeatures();
+
+ assertThat(features.getCertain()).containsExactly(StmtFeature.RETURNS_RESULT_SET);
+ }
+}
diff --git a/src/test/java/net/sf/jsqlparser/util/validation/validator/ShowColumnsStatementValidatorTest.java b/src/test/java/net/sf/jsqlparser/util/validation/validator/ShowColumnsStatementValidatorTest.java
index d0d2f3ac94..24e0ffac59 100644
--- a/src/test/java/net/sf/jsqlparser/util/validation/validator/ShowColumnsStatementValidatorTest.java
+++ b/src/test/java/net/sf/jsqlparser/util/validation/validator/ShowColumnsStatementValidatorTest.java
@@ -9,27 +9,55 @@
*/
package net.sf.jsqlparser.util.validation.validator;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
import net.sf.jsqlparser.parser.feature.Feature;
import net.sf.jsqlparser.util.validation.ValidationTestAsserts;
import net.sf.jsqlparser.util.validation.feature.DatabaseType;
import net.sf.jsqlparser.util.validation.feature.FeaturesAllowed;
+import net.sf.jsqlparser.util.validation.metadata.AbstractDatabaseMetaDataCapability;
+import net.sf.jsqlparser.util.validation.metadata.Named;
+import net.sf.jsqlparser.util.validation.metadata.NamesLookup;
import org.junit.jupiter.api.Test;
public class ShowColumnsStatementValidatorTest extends ValidationTestAsserts {
@Test
public void testValidationShowTables() throws Exception {
- for (String sql : Arrays.asList("SHOW COLUMNS FROM mytable")) {
+ for (String sql : Arrays.asList("SHOW COLUMNS FROM mytable",
+ "SHOW FULL COLUMNS FROM mytable FROM db LIKE 'a%'",
+ "SHOW EXTENDED FIELDS IN db.mytable WHERE `Key` = 'PRI'")) {
validateNoErrors(sql, 1, DatabaseType.MARIADB, DatabaseType.MYSQL);
}
}
@Test
public void testValidationShowTablesNotAllowed() throws Exception {
- for (String sql : Arrays.asList("SHOW COLUMNS FROM mytable")) {
+ for (String sql : Arrays.asList("SHOW COLUMNS FROM mytable",
+ "SHOW FULL FIELDS FROM mytable")) {
validateNotAllowed(sql, 1, 1, FeaturesAllowed.DML, Feature.showColumns);
}
}
+ @Test
+ public void testMetadataUsesTheDatabaseClause() throws Exception {
+ List tables = new ArrayList<>();
+ AbstractDatabaseMetaDataCapability metadata =
+ new AbstractDatabaseMetaDataCapability(null, NamesLookup.NO_TRANSFORMATION) {
+ @Override
+ protected boolean tableExists(Map results, Named named) {
+ tables.add(named.getFqn());
+ return true;
+ }
+ };
+
+ validateNoErrors("SHOW COLUMNS FROM t FROM db", 1, metadata);
+ validateNoErrors("SHOW FULL COLUMNS FROM s.t2", 1, metadata);
+ assertEquals(Arrays.asList("db.t", "s.t2"), tables);
+ }
+
}
diff --git a/src/test/java/net/sf/jsqlparser/util/validation/validator/ShowCreateStatementValidatorTest.java b/src/test/java/net/sf/jsqlparser/util/validation/validator/ShowCreateStatementValidatorTest.java
new file mode 100644
index 0000000000..107b78cdcf
--- /dev/null
+++ b/src/test/java/net/sf/jsqlparser/util/validation/validator/ShowCreateStatementValidatorTest.java
@@ -0,0 +1,77 @@
+/*-
+ * #%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.util.validation.validator;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import net.sf.jsqlparser.JSQLParserException;
+import net.sf.jsqlparser.parser.feature.Feature;
+import net.sf.jsqlparser.util.validation.ValidationTestAsserts;
+import net.sf.jsqlparser.util.validation.feature.DatabaseType;
+import net.sf.jsqlparser.util.validation.feature.FeaturesAllowed;
+import net.sf.jsqlparser.util.validation.feature.MariaDbVersion;
+import net.sf.jsqlparser.util.validation.feature.MySqlVersion;
+import net.sf.jsqlparser.util.validation.feature.PostgresqlVersion;
+import net.sf.jsqlparser.util.validation.metadata.AbstractDatabaseMetaDataCapability;
+import net.sf.jsqlparser.util.validation.metadata.Named;
+import net.sf.jsqlparser.util.validation.metadata.NamesLookup;
+import org.junit.jupiter.api.Test;
+
+public class ShowCreateStatementValidatorTest extends ValidationTestAsserts {
+
+ @Test
+ public void testAllowedForMySqlAndMariaDb() throws JSQLParserException {
+ for (String sql : new String[] {"SHOW CREATE TABLE t", "SHOW CREATE VIEW db.v",
+ "SHOW CREATE PROCEDURE p", "SHOW CREATE FUNCTION f", "SHOW CREATE TRIGGER tr",
+ "SHOW CREATE EVENT e", "SHOW CREATE DATABASE IF NOT EXISTS db",
+ "SHOW CREATE USER 'u'@'h'"}) {
+ validateNoErrors(sql, 1, MySqlVersion.V8_0, MariaDbVersion.V10_5_4,
+ DatabaseType.MYSQL, DatabaseType.MARIADB);
+ }
+ }
+
+ @Test
+ public void testNotAllowedForPostgresql() throws JSQLParserException {
+ validate("SHOW CREATE TABLE t", 1, 1, PostgresqlVersion.V14);
+ }
+
+ @Test
+ public void testFeatureNotAllowed() throws JSQLParserException {
+ validateNotAllowed("SHOW CREATE TABLE t", 1, 1, FeaturesAllowed.DML, Feature.showCreate);
+ }
+
+ @Test
+ public void testMetadataLooksUpTablesAndViews() throws JSQLParserException {
+ List names = new ArrayList<>();
+ AbstractDatabaseMetaDataCapability metadata =
+ new AbstractDatabaseMetaDataCapability(null, NamesLookup.NO_TRANSFORMATION) {
+ @Override
+ protected boolean tableExists(Map results, Named named) {
+ names.add("table " + named.getFqn());
+ return true;
+ }
+
+ @Override
+ protected boolean viewExists(Map results, Named named) {
+ names.add("view " + named.getFqn());
+ return true;
+ }
+ };
+
+ validateNoErrors("SHOW CREATE TABLE s.t", 1, metadata);
+ validateNoErrors("SHOW CREATE VIEW v", 1, metadata);
+ validateNoErrors("SHOW CREATE TRIGGER tr", 1, metadata);
+ assertEquals(Arrays.asList("table s.t", "view v"), names);
+ }
+}
diff --git a/src/test/java/net/sf/jsqlparser/util/validation/validator/ShowTableStatusStatementValidatorTest.java b/src/test/java/net/sf/jsqlparser/util/validation/validator/ShowTableStatusStatementValidatorTest.java
new file mode 100644
index 0000000000..c5412d8c0e
--- /dev/null
+++ b/src/test/java/net/sf/jsqlparser/util/validation/validator/ShowTableStatusStatementValidatorTest.java
@@ -0,0 +1,43 @@
+/*-
+ * #%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.util.validation.validator;
+
+import net.sf.jsqlparser.JSQLParserException;
+import net.sf.jsqlparser.parser.feature.Feature;
+import net.sf.jsqlparser.util.validation.ValidationTestAsserts;
+import net.sf.jsqlparser.util.validation.feature.DatabaseType;
+import net.sf.jsqlparser.util.validation.feature.FeaturesAllowed;
+import net.sf.jsqlparser.util.validation.feature.MariaDbVersion;
+import net.sf.jsqlparser.util.validation.feature.MySqlVersion;
+import net.sf.jsqlparser.util.validation.feature.PostgresqlVersion;
+import org.junit.jupiter.api.Test;
+
+public class ShowTableStatusStatementValidatorTest extends ValidationTestAsserts {
+
+ @Test
+ public void testAllowedForMySqlAndMariaDb() throws JSQLParserException {
+ for (String sql : new String[] {"SHOW TABLE STATUS", "SHOW TABLE STATUS LIKE 'a%'",
+ "SHOW TABLE STATUS IN db WHERE Comment <> ''"}) {
+ validateNoErrors(sql, 1, MySqlVersion.V8_0, MariaDbVersion.V10_5_4,
+ DatabaseType.MYSQL, DatabaseType.MARIADB);
+ }
+ }
+
+ @Test
+ public void testNotAllowedForPostgresql() throws JSQLParserException {
+ validate("SHOW TABLE STATUS", 1, 1, PostgresqlVersion.V14);
+ }
+
+ @Test
+ public void testFeatureNotAllowed() throws JSQLParserException {
+ validateNotAllowed("SHOW TABLE STATUS FROM db", 1, 1, FeaturesAllowed.DML,
+ Feature.showTableStatus);
+ }
+}