diff --git a/src/main/java/net/sf/jsqlparser/statement/analyze/Analyze.java b/src/main/java/net/sf/jsqlparser/statement/analyze/Analyze.java index ab25cf3b4..558de06ad 100644 --- a/src/main/java/net/sf/jsqlparser/statement/analyze/Analyze.java +++ b/src/main/java/net/sf/jsqlparser/statement/analyze/Analyze.java @@ -9,13 +9,32 @@ */ package net.sf.jsqlparser.statement.analyze; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import net.sf.jsqlparser.schema.Column; import net.sf.jsqlparser.schema.Table; import net.sf.jsqlparser.statement.Statement; import net.sf.jsqlparser.statement.StatementVisitor; public class Analyze implements Statement { - private Table table; + public enum Modifier { + NO_WRITE_TO_BINLOG, LOCAL + } + + public enum HistogramAction { + UPDATE, DROP + } + + private final List tables = new ArrayList<>(); + private boolean tableKeyword; + private Modifier modifier; + private HistogramAction histogramAction; + private final List histogramColumns = new ArrayList<>(); + private Integer histogramBucketCount; @Override public T accept(StatementVisitor statementVisitor, S context) { @@ -23,20 +42,134 @@ public T accept(StatementVisitor statementVisitor, S context) { } public Table getTable() { - return table; + return tables.isEmpty() ? null : tables.get(0); } public void setTable(Table table) { - this.table = table; + tables.clear(); + if (table != null) { + tables.add(table); + } + } + + public List
getTables() { + return Collections.unmodifiableList(tables); + } + + public void setTables(Collection tables) { + this.tables.clear(); + if (tables != null) { + this.tables.addAll(tables); + } + } + + public boolean isTableKeyword() { + return tableKeyword; + } + + public void setTableKeyword(boolean tableKeyword) { + this.tableKeyword = tableKeyword; + } + + public Modifier getModifier() { + return modifier; + } + + public void setModifier(Modifier modifier) { + this.modifier = modifier; + } + + public HistogramAction getHistogramAction() { + return histogramAction; + } + + public void setHistogramAction(HistogramAction histogramAction) { + this.histogramAction = histogramAction; + } + + public List getHistogramColumns() { + return Collections.unmodifiableList(histogramColumns); + } + + public void setHistogramColumns(Collection columns) { + histogramColumns.clear(); + if (columns != null) { + histogramColumns.addAll(columns); + } + } + + public Integer getHistogramBucketCount() { + return histogramBucketCount; + } + + public void setHistogramBucketCount(Integer histogramBucketCount) { + this.histogramBucketCount = histogramBucketCount; } @Override public String toString() { - return "ANALYZE " + table.toString(); + StringBuilder builder = new StringBuilder("ANALYZE"); + if (modifier != null) { + builder.append(" ").append(modifier); + } + if (tableKeyword) { + builder.append(" TABLE"); + } + builder.append(" ").append(tables.stream().map(Table::toString) + .collect(Collectors.joining(", "))); + if (histogramAction != null) { + builder.append(" ").append(histogramAction).append(" HISTOGRAM ON ") + .append(histogramColumns.stream().map(Column::toString) + .collect(Collectors.joining(", "))); + if (histogramBucketCount != null) { + builder.append(" WITH ").append(histogramBucketCount).append(" BUCKETS"); + } + } + return builder.toString(); } public Analyze withTable(Table table) { this.setTable(table); return this; } + + public Analyze withTables(Collection tables) { + setTables(tables); + return this; + } + + public Analyze addTables(Table... tables) { + Collections.addAll(this.tables, tables); + return this; + } + + public Analyze withTableKeyword(boolean tableKeyword) { + setTableKeyword(tableKeyword); + return this; + } + + public Analyze withModifier(Modifier modifier) { + setModifier(modifier); + return this; + } + + public Analyze withHistogramAction(HistogramAction histogramAction) { + setHistogramAction(histogramAction); + return this; + } + + public Analyze withHistogramColumns(Collection columns) { + setHistogramColumns(columns); + return this; + } + + public Analyze addHistogramColumns(Column... columns) { + Collections.addAll(histogramColumns, columns); + return this; + } + + public Analyze withHistogramBucketCount(Integer histogramBucketCount) { + setHistogramBucketCount(histogramBucketCount); + 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 b3d1463d0..6fe18f914 100644 --- a/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java +++ b/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java @@ -1491,7 +1491,7 @@ public void visit(Insert insert) { @Override public Void visit(Analyze analyze, S context) { - visit(analyze.getTable(), context); + analyze.getTables().forEach(table -> visit(table, context)); return null; } 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 51d06c7f1..bd256c02e 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/StatementDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/StatementDeParser.java @@ -263,8 +263,7 @@ public StringBuilder visit(Update update, S context) { } public StringBuilder visit(Analyze analyzer, S context) { - builder.append("ANALYZE "); - builder.append(analyzer.getTable()); + builder.append(analyzer); return builder; } diff --git a/src/main/java/net/sf/jsqlparser/util/validation/validator/AnalyzeValidator.java b/src/main/java/net/sf/jsqlparser/util/validation/validator/AnalyzeValidator.java index ac5de0c19..f2f3a1b58 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/validator/AnalyzeValidator.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/validator/AnalyzeValidator.java @@ -19,7 +19,8 @@ public class AnalyzeValidator extends AbstractValidator { public void validate(Analyze analyze) { for (ValidationCapability c : getCapabilities()) { validateFeature(c, Feature.analyze); - validateName(c, NamedObject.table, analyze.getTable().getFullyQualifiedName(), true); + analyze.getTables().forEach(table -> validateName(c, NamedObject.table, + table.getFullyQualifiedName(), true)); } } } diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index 52994191b..c8003f881 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -1488,6 +1488,7 @@ String NonReservedWord() : | tk= | tk= | tk= + | tk= | tk= | tk= | tk= @@ -1592,6 +1593,7 @@ String NonReservedWord() : | tk= | tk= | tk= + | tk= | tk= | tk= | tk= @@ -1666,6 +1668,7 @@ String NonReservedWord() : | tk= | tk= | tk= + | tk= | tk= | tk= | tk= @@ -11958,14 +11961,40 @@ Analyze Analyze(): { Analyze analyze = new Analyze(); Table table = null; + Column column = null; + Token modifier = null; + Token histogramAction = null; + Token bucketCount = null; } { - table=Table() + [ + LOOKAHEAD(2) + (modifier= | modifier=) + { analyze.setModifier(Analyze.Modifier.valueOf(modifier.image.toUpperCase(Locale.ROOT))); } + ] + [ LOOKAHEAD(2) { analyze.setTableKeyword(true); } ] + table=Table() { analyze.addTables(table); } + ( "," table=Table() { analyze.addTables(table); } )* + [ + LOOKAHEAD(2) + (histogramAction= | histogramAction=) + { + analyze.setHistogramAction(Analyze.HistogramAction.valueOf( + histogramAction.image.toUpperCase(Locale.ROOT))); + } + + column=Column() { analyze.addHistogramColumns(column); } + ( "," column=Column() { analyze.addHistogramColumns(column); } )* + [ + LOOKAHEAD(3) + bucketCount= + { analyze.setHistogramBucketCount(Integer.valueOf(bucketCount.image)); } + ] + ] { - analyze.setTable(table); - return analyze; + return analyze; } } diff --git a/src/test/java/net/sf/jsqlparser/statement/analyze/AnalyzeTest.java b/src/test/java/net/sf/jsqlparser/statement/analyze/AnalyzeTest.java index 7f19370f4..578e68fd7 100644 --- a/src/test/java/net/sf/jsqlparser/statement/analyze/AnalyzeTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/analyze/AnalyzeTest.java @@ -9,17 +9,19 @@ */ package net.sf.jsqlparser.statement.analyze; +import static net.sf.jsqlparser.test.TestUtils.assertDeparse; +import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.StringReader; +import java.util.List; +import java.util.stream.Collectors; import net.sf.jsqlparser.JSQLParserException; import net.sf.jsqlparser.parser.CCJSqlParserManager; +import net.sf.jsqlparser.schema.Column; import net.sf.jsqlparser.schema.Table; import org.junit.jupiter.api.Test; -import java.io.StringReader; - -import static net.sf.jsqlparser.test.TestUtils.assertDeparse; -import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed; -import static org.junit.jupiter.api.Assertions.assertEquals; - public class AnalyzeTest { private final CCJSqlParserManager parserManager = new CCJSqlParserManager(); @@ -38,4 +40,47 @@ public void testAnalyze() throws JSQLParserException { public void testAnalyze2() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed("ANALYZE mytable"); } + + @Test + public void testMySqlAnalyzeTableList() throws JSQLParserException { + String sql = "ANALYZE TABLE t2, t3"; + Analyze analyze = (Analyze) parserManager.parse(new StringReader(sql)); + + assertEquals(List.of("t2", "t3"), analyze.getTables().stream() + .map(Table::getFullyQualifiedName).collect(Collectors.toList())); + assertEquals("t2", analyze.getTable().getFullyQualifiedName()); + assertEquals(sql, analyze.toString()); + + assertDeparse(new Analyze().withTableKeyword(true) + .withTables(List.of(new Table("t2"), new Table("t3"))), sql); + } + + @Test + public void testMySqlAnalyzeUpdateHistogram() throws JSQLParserException { + String sql = "ANALYZE TABLE t1 UPDATE HISTOGRAM ON c1, c2 WITH 64 BUCKETS"; + Analyze analyze = (Analyze) assertSqlCanBeParsedAndDeparsed(sql); + + assertEquals(Analyze.HistogramAction.UPDATE, analyze.getHistogramAction()); + assertEquals(List.of("c1", "c2"), analyze.getHistogramColumns().stream() + .map(Column::getFullyQualifiedName).collect(Collectors.toList())); + assertEquals(64, analyze.getHistogramBucketCount()); + } + + @Test + public void testMySqlAnalyzeDropHistogram() throws JSQLParserException { + String sql = "ANALYZE LOCAL TABLE t2 DROP HISTOGRAM ON c1"; + Analyze analyze = (Analyze) assertSqlCanBeParsedAndDeparsed(sql); + + assertEquals(Analyze.Modifier.LOCAL, analyze.getModifier()); + assertEquals(Analyze.HistogramAction.DROP, analyze.getHistogramAction()); + assertEquals("c1", analyze.getHistogramColumns().get(0).getFullyQualifiedName()); + } + + @Test + public void testMySqlAnalyzeNoWriteToBinlog() throws JSQLParserException { + String sql = "ANALYZE NO_WRITE_TO_BINLOG TABLE t1"; + Analyze analyze = (Analyze) assertSqlCanBeParsedAndDeparsed(sql); + + assertEquals(Analyze.Modifier.NO_WRITE_TO_BINLOG, analyze.getModifier()); + } }