diff --git a/src/main/java/net/sf/jsqlparser/statement/select/MySqlProcedureAnalyse.java b/src/main/java/net/sf/jsqlparser/statement/select/MySqlProcedureAnalyse.java
new file mode 100644
index 000000000..b70f2fdc4
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/statement/select/MySqlProcedureAnalyse.java
@@ -0,0 +1,75 @@
+/*-
+ * #%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.select;
+
+import java.io.Serializable;
+import net.sf.jsqlparser.expression.LongValue;
+import net.sf.jsqlparser.parser.ASTNodeAccessImpl;
+
+/**
+ * Models the legacy MySQL {@code PROCEDURE ANALYSE()} clause.
+ *
+ * @see MySQL 5.7 Reference
+ * Manual
+ */
+public class MySqlProcedureAnalyse extends ASTNodeAccessImpl implements Serializable {
+
+ private LongValue maxElements;
+ private LongValue maxMemory;
+
+ public MySqlProcedureAnalyse() {}
+
+ public MySqlProcedureAnalyse(LongValue maxElements, LongValue maxMemory) {
+ this.maxElements = maxElements;
+ this.maxMemory = maxMemory;
+ }
+
+ public LongValue getMaxElements() {
+ return maxElements;
+ }
+
+ public void setMaxElements(LongValue maxElements) {
+ this.maxElements = maxElements;
+ }
+
+ public MySqlProcedureAnalyse withMaxElements(LongValue maxElements) {
+ setMaxElements(maxElements);
+ return this;
+ }
+
+ public LongValue getMaxMemory() {
+ return maxMemory;
+ }
+
+ public void setMaxMemory(LongValue maxMemory) {
+ this.maxMemory = maxMemory;
+ }
+
+ public MySqlProcedureAnalyse withMaxMemory(LongValue maxMemory) {
+ setMaxMemory(maxMemory);
+ return this;
+ }
+
+ public StringBuilder appendTo(StringBuilder builder) {
+ builder.append(" PROCEDURE ANALYSE(");
+ if (maxElements != null) {
+ builder.append(maxElements);
+ if (maxMemory != null) {
+ builder.append(", ").append(maxMemory);
+ }
+ }
+ return builder.append(")");
+ }
+
+ @Override
+ public String toString() {
+ return appendTo(new StringBuilder()).toString();
+ }
+}
diff --git a/src/main/java/net/sf/jsqlparser/statement/select/Select.java b/src/main/java/net/sf/jsqlparser/statement/select/Select.java
index 501e21728..d3750c98e 100644
--- a/src/main/java/net/sf/jsqlparser/statement/select/Select.java
+++ b/src/main/java/net/sf/jsqlparser/statement/select/Select.java
@@ -31,6 +31,7 @@ public abstract class Select extends ASTNodeAccessImpl implements Statement, Exp
Offset offset;
Fetch fetch;
WithIsolation isolation;
+ MySqlProcedureAnalyse mySqlProcedureAnalyse;
boolean oracleSiblings = false;
ForClause forClause = null;
@@ -322,6 +323,19 @@ public Select withIsolation(WithIsolation isolation) {
return this;
}
+ public MySqlProcedureAnalyse getMySqlProcedureAnalyse() {
+ return mySqlProcedureAnalyse;
+ }
+
+ public void setMySqlProcedureAnalyse(MySqlProcedureAnalyse mySqlProcedureAnalyse) {
+ this.mySqlProcedureAnalyse = mySqlProcedureAnalyse;
+ }
+
+ public Select withMySqlProcedureAnalyse(MySqlProcedureAnalyse mySqlProcedureAnalyse) {
+ setMySqlProcedureAnalyse(mySqlProcedureAnalyse);
+ return this;
+ }
+
public ForMode getForMode() {
return this.forMode;
}
@@ -521,6 +535,9 @@ public StringBuilder appendTo(StringBuilder builder) {
if (offset != null) {
builder.append(offset);
}
+ if (mySqlProcedureAnalyse != null) {
+ mySqlProcedureAnalyse.appendTo(builder);
+ }
if (fetch != null) {
builder.append(fetch);
}
diff --git a/src/main/java/net/sf/jsqlparser/statement/select/SelectVisitorAdapter.java b/src/main/java/net/sf/jsqlparser/statement/select/SelectVisitorAdapter.java
index 9b0eb33a6..962e73719 100644
--- a/src/main/java/net/sf/jsqlparser/statement/select/SelectVisitorAdapter.java
+++ b/src/main/java/net/sf/jsqlparser/statement/select/SelectVisitorAdapter.java
@@ -200,6 +200,12 @@ public T visit(PlainSelect plainSelect, S context) {
if (plainSelect.getOffset() != null) {
expressionVisitor.visitExpression(plainSelect.getOffset().getOffset(), context);
}
+ if (plainSelect.getMySqlProcedureAnalyse() != null) {
+ expressionVisitor.visitExpression(
+ plainSelect.getMySqlProcedureAnalyse().getMaxElements(), context);
+ expressionVisitor.visitExpression(
+ plainSelect.getMySqlProcedureAnalyse().getMaxMemory(), context);
+ }
if (plainSelect.getFetch() != null) {
expressionVisitor.visitExpression(plainSelect.getFetch().getExpression(), context);
}
diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java
index 066d40d06..24fea616a 100644
--- a/src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java
+++ b/src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java
@@ -365,6 +365,9 @@ public StringBuilder visit(PlainSelect plainSelect, S context) {
if (plainSelect.getOffset() != null) {
visit(plainSelect.getOffset());
}
+ if (plainSelect.getMySqlProcedureAnalyse() != null) {
+ plainSelect.getMySqlProcedureAnalyse().appendTo(builder);
+ }
if (plainSelect.getFetch() != null) {
visit(plainSelect.getFetch());
}
diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
index f6c400a45..4e2092ec0 100644
--- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
+++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
@@ -819,11 +819,12 @@ public class CCJSqlParser extends AbstractJSqlParser {
switch (kind) {
// Safe as implicit identifiers (no clause collision)
- case K_TABLES: case K_OPTIMIZE: case K_PROCEDURE: case K_PUBLIC:
+ case K_TABLES: case K_OPTIMIZE: case K_PUBLIC:
case K_CASEWHEN: case K_IIF:
return true;
// Safe when the follower doesn't form a clause
+ case K_PROCEDURE: return nextKind != K_ANALYSE;
case K_GROUP: return nextKind != K_BY;
case K_ORDER: return nextKind != K_BY && nextKind != K_SIBLINGS;
case K_CONNECT: return nextKind != K_BY;
@@ -1250,6 +1251,7 @@ String NonReservedWord() :
| tk=
| tk=
| tk=
+ | tk=
| tk=
| tk=
| tk=
@@ -5451,6 +5453,7 @@ PlainSelect PlainSelect() #PlainSelect:
boolean partitionByBrackets = false;
List intoTables = null;
MySqlSelectIntoClause mySqlSelectIntoClause = null;
+ MySqlProcedureAnalyse mySqlProcedureAnalyse = null;
Table updateTable = null;
List updateTables = new ArrayList();
Wait wait = null;
@@ -5580,6 +5583,10 @@ PlainSelect PlainSelect() #PlainSelect:
} ]
[ LOOKAHEAD() offset = Offset() { plainSelect.setOffset(offset); } ]
[ LOOKAHEAD(, { limit==null }) limit = LimitWithOffset() { plainSelect.setLimit(limit); } ]
+ [ LOOKAHEAD( )
+ mySqlProcedureAnalyse = MySqlProcedureAnalyse()
+ { plainSelect.setMySqlProcedureAnalyse(mySqlProcedureAnalyse); }
+ ]
[ LOOKAHEAD() fetch = Fetch() { plainSelect.setFetch(fetch); } ]
[ LOOKAHEAD( ) withIsolation = WithIsolation() { plainSelect.setIsolation(withIsolation); } ]
[ LOOKAHEAD(2)
@@ -6226,6 +6233,30 @@ MySqlSelectIntoClause MySqlSelectIntoClause(MySqlSelectIntoClause.Position posit
}
}
+MySqlProcedureAnalyse MySqlProcedureAnalyse():
+{
+ MySqlProcedureAnalyse procedureAnalyse = new MySqlProcedureAnalyse();
+ Token maxElements = null;
+ Token maxMemory = null;
+}
+{
+ "("
+ [
+ maxElements=
+ [ "," maxMemory= ]
+ ]
+ ")"
+ {
+ if (maxElements != null) {
+ procedureAnalyse.setMaxElements(new LongValue(maxElements.image));
+ }
+ if (maxMemory != null) {
+ procedureAnalyse.setMaxMemory(new LongValue(maxMemory.image));
+ }
+ return procedureAnalyse;
+ }
+}
+
void MySqlSelectIntoOutfileTail(MySqlSelectIntoClause intoClause):
{
Token token;
diff --git a/src/test/java/net/sf/jsqlparser/statement/select/MySqlProcedureAnalyseTest.java b/src/test/java/net/sf/jsqlparser/statement/select/MySqlProcedureAnalyseTest.java
new file mode 100644
index 000000000..7d90bdeb9
--- /dev/null
+++ b/src/test/java/net/sf/jsqlparser/statement/select/MySqlProcedureAnalyseTest.java
@@ -0,0 +1,72 @@
+/*-
+ * #%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.select;
+
+import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import net.sf.jsqlparser.JSQLParserException;
+import net.sf.jsqlparser.parser.CCJSqlParserUtil;
+import org.junit.jupiter.api.Test;
+
+class MySqlProcedureAnalyseTest {
+
+ @Test
+ void parseWithoutParameters() throws JSQLParserException {
+ Select select = (Select) assertSqlCanBeParsedAndDeparsed(
+ "SELECT col1, col2 FROM heavy_table PROCEDURE ANALYSE()");
+
+ MySqlProcedureAnalyse procedureAnalyse = select.getMySqlProcedureAnalyse();
+ assertNotNull(procedureAnalyse);
+ assertNull(procedureAnalyse.getMaxElements());
+ assertNull(procedureAnalyse.getMaxMemory());
+ }
+
+ @Test
+ void parseOneParameter() throws JSQLParserException {
+ Select select = (Select) assertSqlCanBeParsedAndDeparsed(
+ "SELECT col1 FROM heavy_table PROCEDURE ANALYSE(10)");
+
+ MySqlProcedureAnalyse procedureAnalyse = select.getMySqlProcedureAnalyse();
+ assertEquals("10", procedureAnalyse.getMaxElements().getStringValue());
+ assertNull(procedureAnalyse.getMaxMemory());
+ }
+
+ @Test
+ void parseTwoParametersAfterLimit() throws JSQLParserException {
+ Select select = (Select) assertSqlCanBeParsedAndDeparsed(
+ "SELECT col1 FROM heavy_table LIMIT 20 PROCEDURE ANALYSE(10, 256)");
+
+ MySqlProcedureAnalyse procedureAnalyse = select.getMySqlProcedureAnalyse();
+ assertEquals("10", procedureAnalyse.getMaxElements().getStringValue());
+ assertEquals("256", procedureAnalyse.getMaxMemory().getStringValue());
+ }
+
+ @Test
+ void keepAnalyseAndProcedureAvailableAsIdentifiers() throws JSQLParserException {
+ assertSqlCanBeParsedAndDeparsed("SELECT analyse FROM heavy_table");
+ assertSqlCanBeParsedAndDeparsed("SELECT * FROM heavy_table procedure");
+ }
+
+ @Test
+ void rejectInvalidForms() {
+ assertThrows(JSQLParserException.class,
+ () -> CCJSqlParserUtil.parse("SELECT * FROM t PROCEDURE ANALYSE"));
+ assertThrows(JSQLParserException.class,
+ () -> CCJSqlParserUtil.parse("SELECT * FROM t PROCEDURE ANALYSE(-1)"));
+ assertThrows(JSQLParserException.class,
+ () -> CCJSqlParserUtil.parse("SELECT * FROM t PROCEDURE ANALYSE(1, 2, 3)"));
+ assertThrows(JSQLParserException.class,
+ () -> CCJSqlParserUtil.parse("SELECT * FROM t PROCEDURE ANALYZE()"));
+ }
+}