diff --git a/src/main/java/net/sf/jsqlparser/schema/TableReference.java b/src/main/java/net/sf/jsqlparser/schema/TableReference.java new file mode 100644 index 0000000000..1584d1dc21 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/schema/TableReference.java @@ -0,0 +1,91 @@ +/*- + * #%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.schema; + +import java.io.Serializable; + +/** A relation target with optional PostgreSQL inheritance scope, shared by LOCK and TRUNCATE. */ +public class TableReference implements Serializable { + private Table table; + private boolean only; + private boolean includeDescendants; + private boolean parenthesized; + + public TableReference(Table table) { + this.table = table; + } + + public Table getTable() { + return table; + } + + public void setTable(Table table) { + this.table = table; + } + + public boolean isOnly() { + return only; + } + + public void setOnly(boolean only) { + this.only = only; + if (only) { + includeDescendants = false; + } else { + parenthesized = false; + } + } + + public boolean isIncludeDescendants() { + return includeDescendants; + } + + public void setIncludeDescendants(boolean includeDescendants) { + this.includeDescendants = includeDescendants; + if (includeDescendants) { + only = false; + parenthesized = false; + } + } + + public boolean isParenthesized() { + return parenthesized; + } + + public void setParenthesized(boolean parenthesized) { + this.parenthesized = parenthesized; + if (parenthesized) { + only = true; + includeDescendants = false; + } + } + + public StringBuilder appendTo(StringBuilder builder) { + if (only) { + builder.append("ONLY "); + } + if (parenthesized) { + builder.append('('); + } + builder.append(table.getFullyQualifiedName()); + if (parenthesized) { + builder.append(')'); + } + if (includeDescendants) { + builder.append(" *"); + } + return builder; + } + + @Override + public String toString() { + return appendTo(new StringBuilder()).toString(); + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/lock/LockStatement.java b/src/main/java/net/sf/jsqlparser/statement/lock/LockStatement.java index ffdcbed52d..e7a592c3db 100644 --- a/src/main/java/net/sf/jsqlparser/statement/lock/LockStatement.java +++ b/src/main/java/net/sf/jsqlparser/statement/lock/LockStatement.java @@ -34,34 +34,20 @@ public enum Scope { DEFAULT, ONLY, INCLUDING_DESCENDANTS } - public static class Target { - private Table table; - private Scope scope = Scope.DEFAULT; - + public static class Target extends net.sf.jsqlparser.schema.TableReference { public Target(Table table) { - this.table = table; - } - - public Table getTable() { - return table; - } - - public void setTable(Table table) { - this.table = table; + super(table); } public Scope getScope() { - return scope; + return isOnly() ? Scope.ONLY + : isIncludeDescendants() ? Scope.INCLUDING_DESCENDANTS : Scope.DEFAULT; } public void setScope(Scope scope) { - this.scope = Objects.requireNonNull(scope); - } - - @Override - public String toString() { - return (scope == Scope.ONLY ? "ONLY " : "") + table.getFullyQualifiedName() - + (scope == Scope.INCLUDING_DESCENDANTS ? " *" : ""); + Objects.requireNonNull(scope); + setOnly(scope == Scope.ONLY); + setIncludeDescendants(scope == Scope.INCLUDING_DESCENDANTS); } } diff --git a/src/main/java/net/sf/jsqlparser/statement/truncate/Truncate.java b/src/main/java/net/sf/jsqlparser/statement/truncate/Truncate.java index 58442d38ee..4ffc75b2fe 100644 --- a/src/main/java/net/sf/jsqlparser/statement/truncate/Truncate.java +++ b/src/main/java/net/sf/jsqlparser/statement/truncate/Truncate.java @@ -9,113 +9,186 @@ */ package net.sf.jsqlparser.statement.truncate; -import static java.util.stream.Collectors.joining; - +import java.util.AbstractList; +import java.util.ArrayList; import java.util.List; import net.sf.jsqlparser.schema.Table; +import net.sf.jsqlparser.schema.TableReference; import net.sf.jsqlparser.statement.Statement; import net.sf.jsqlparser.statement.StatementVisitor; public class Truncate implements Statement { + public enum IdentityOption { + RESTART, CONTINUE + } + public enum DropBehavior { + CASCADE, RESTRICT + } - boolean cascade; // to support TRUNCATE TABLE ... CASCADE - boolean tableToken; // to support TRUNCATE without TABLE - boolean only; // to support TRUNCATE with ONLY - private Table table; - private List tables; + private boolean tableToken; + private boolean only; + private final List targets = new ArrayList<>(); + private IdentityOption identityOption; + private DropBehavior dropBehavior; @Override public T accept(StatementVisitor statementVisitor, S context) { return statementVisitor.visit(this, context); } - public Table getTable() { - return table; + public List getTargets() { + return targets; } - public List
getTables() { - return tables; + public void setTargets(List references) { + List replacement = new ArrayList<>(references); + targets.clear(); + targets.addAll(replacement); + } + + /** Preserves the legacy parser's singular accessor for the last target. */ + public Table getTable() { + return targets.isEmpty() ? null : targets.get(targets.size() - 1).getTable(); } public void setTable(Table table) { - this.table = table; + if (targets.isEmpty()) { + TableReference target = new TableReference(table); + target.setOnly(only); + targets.add(target); + } else { + targets.get(targets.size() - 1).setTable(table); + } + } + + /** Mutable table projection; replacing a table preserves its target's inheritance scope. */ + public List
getTables() { + return new AbstractList
() { + @Override + public Table get(int index) { + return targets.get(index).getTable(); + } + + @Override + public int size() { + return targets.size(); + } + + @Override + public Table set(int index, Table table) { + Table previous = get(index); + targets.get(index).setTable(table); + return previous; + } + + @Override + public void add(int index, Table table) { + targets.add(index, new TableReference(table)); + } + + @Override + public Table remove(int index) { + return targets.remove(index).getTable(); + } + }; } public void setTables(List
tables) { - this.tables = tables; + List
replacement = tables == null ? new ArrayList<>() : new ArrayList<>(tables); + boolean firstOnly = isOnly(); + targets.clear(); + replacement.forEach(table -> targets.add(new TableReference(table))); + setOnly(firstOnly); } - public boolean getCascade() { - return cascade; + public IdentityOption getIdentityOption() { + return identityOption; } - public void setCascade(boolean c) { - cascade = c; + public void setIdentityOption(IdentityOption option) { + identityOption = option; } - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("TRUNCATE"); - if (tableToken) { - sb.append(" TABLE"); - } - if (only) { - sb.append(" ONLY"); - } - sb.append(" "); - if (tables != null && !tables.isEmpty()) { - sb.append(tables.stream() - .map(Table::toString) - .collect(joining(", "))); - } else { - sb.append(table); - } - if (cascade) { - sb.append(" CASCADE"); - } - return sb.toString(); + public DropBehavior getDropBehavior() { + return dropBehavior; + } + + public void setDropBehavior(DropBehavior behavior) { + dropBehavior = behavior; + } + + public boolean getCascade() { + return dropBehavior == DropBehavior.CASCADE; + } + + public void setCascade(boolean cascade) { + dropBehavior = cascade ? DropBehavior.CASCADE : null; } public boolean isTableToken() { return tableToken; } - public void setTableToken(boolean hasTable) { - this.tableToken = hasTable; + public void setTableToken(boolean tableToken) { + this.tableToken = tableToken; } + /** Legacy ONLY accessor describes the first target. */ public boolean isOnly() { - return only; + return targets.isEmpty() ? only : targets.get(0).isOnly(); } public void setOnly(boolean only) { this.only = only; + if (!targets.isEmpty()) { + targets.get(0).setOnly(only); + } } - public Truncate withTableToken(boolean hasTableToken) { - this.setTableToken(hasTableToken); + public StringBuilder appendTo(StringBuilder builder) { + builder.append(tableToken ? "TRUNCATE TABLE " : "TRUNCATE "); + for (int i = 0; i < targets.size(); i++) { + if (i > 0) { + builder.append(", "); + } + targets.get(i).appendTo(builder); + } + if (identityOption != null) { + builder.append(' ').append(identityOption).append(" IDENTITY"); + } + if (dropBehavior != null) { + builder.append(' ').append(dropBehavior); + } + return builder; + } + + @Override + public String toString() { + return appendTo(new StringBuilder()).toString(); + } + + public Truncate withTableToken(boolean value) { + setTableToken(value); return this; } - public Truncate withTable(Table table) { - this.setTable(table); + public Truncate withTable(Table value) { + setTable(value); return this; } - public Truncate withTables(List
tables) { - this.setTables(tables); + public Truncate withTables(List
value) { + setTables(value); return this; } - public Truncate withCascade(boolean cascade) { - this.setCascade(cascade); + public Truncate withCascade(boolean value) { + setCascade(value); return this; } - public Truncate withOnly(boolean only) { - this.setOnly(only); + public Truncate withOnly(boolean value) { + setOnly(value); 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 9d21d83770..703a4abe34 100644 --- a/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java +++ b/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java @@ -1612,7 +1612,7 @@ public void visit(Drop drop) { @Override public Void visit(Truncate truncate, S context) { - visit(truncate.getTable(), context); + truncate.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 d949c3e623..306ac059d1 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/StatementDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/StatementDeParser.java @@ -38,9 +38,7 @@ import java.lang.reflect.InvocationTargetException; import java.util.List; -import java.util.stream.Collectors; -import net.sf.jsqlparser.schema.Table; import net.sf.jsqlparser.statement.Block; import net.sf.jsqlparser.statement.Commit; import net.sf.jsqlparser.statement.CreateFunctionalStatement; @@ -301,26 +299,7 @@ public StringBuilder visit(Select select, S context) { @Override public StringBuilder visit(Truncate truncate, S context) { - builder.append("TRUNCATE"); - if (truncate.isTableToken()) { - builder.append(" TABLE"); - } - if (truncate.isOnly()) { - builder.append(" ONLY"); - } - builder.append(" "); - if (truncate.getTables() != null && !truncate.getTables().isEmpty()) { - builder.append(truncate.getTables().stream() - .map(Table::toString) - .collect(Collectors.joining(", "))); - } else { - builder.append(truncate.getTable()); - } - if (truncate.getCascade()) { - builder.append(" CASCADE"); - } - - return builder; + return truncate.appendTo(builder); } @Override 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 0b8d2765d5..839664dbfd 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 @@ -208,7 +208,7 @@ public Void visit(Select select, S context) { @Override public Void visit(Truncate truncate, S context) { validateFeature(Feature.truncate); - validateOptionalFromItem(truncate.getTable()); + truncate.getTables().forEach(this::validateOptionalFromItem); return null; } diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index 4b505dab0d..c617489c38 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -3653,18 +3653,39 @@ LockStatement LockStatement(): { { return statement; } } -LockStatement.Target LockTarget(): { +LockStatement.Target LockTarget(): +{ TableReference reference; LockStatement.Target target; } +{ + reference=RelationTarget() + { + target = new LockStatement.Target(reference.getTable()); + target.setOnly(reference.isOnly()); + target.setIncludeDescendants(reference.isIncludeDescendants()); + target.setParenthesized(reference.isParenthesized()); + return target; + } +} + +/** Shared PostgreSQL relation_expr target, including ONLY (qualified_name). */ +TableReference RelationTarget(): +{ Table table; boolean only = false; boolean descendants = false; - LockStatement.Target target; -} { - [ { only = true; } ] table=Table() [ "*" { descendants = true; } ] + boolean parenthesized = false; + TableReference target; +} +{ + [ { only = true; } ] + ( "(" table=Table() ")" { parenthesized = true; } | table=Table() ) + [ "*" { descendants = true; } ] { - requireDdlSyntax(!only || !descendants, "ONLY and * cannot describe the same lock target"); - target = new LockStatement.Target(table); - target.setScope(only ? LockStatement.Scope.ONLY - : descendants ? LockStatement.Scope.INCLUDING_DESCENDANTS : LockStatement.Scope.DEFAULT); + requireDdlSyntax(!only || !descendants, "ONLY and * cannot describe the same relation target"); + requireDdlSyntax(!parenthesized || only, "Parenthesized relation targets require ONLY"); + target = new TableReference(table); + target.setOnly(only); + target.setIncludeDescendants(descendants); + target.setParenthesized(parenthesized); return target; } } @@ -16826,38 +16847,18 @@ Drop Drop(): } Truncate Truncate(): +{ Truncate truncate = new Truncate(); TableReference target; } { - Truncate truncate = new Truncate(); - Table table; - List
tables = new ArrayList
(); - boolean only = false; - boolean cascade = false; -} -{ -/** -* TRUNCATE can be followed directly by the table name in Postgresql -* See: https://www.postgresql.org/docs/current/sql-truncate.html -* -* TRUNCATE [ TABLE ] [ ONLY ] name [ * ] [, ... ] -* [ RESTART IDENTITY | CONTINUE IDENTITY ] [ CASCADE | RESTRICT ] -* -*/ - [LOOKAHEAD(2) {truncate.setTableToken(true);}] - [ { only = true; }] - table=Table() { tables.add(table); } (LOOKAHEAD(2) "," table=Table() { tables.add(table); } )* - [ { cascade = true; }] - { - if (only && tables.size() > 1 ) { - throw new ParseException("Cannot TRUNCATE ONLY with multiple tables"); - } else { - return truncate - .withTables(tables) - .withTable(table) - .withOnly(only) - .withCascade(cascade); - } - } + [ LOOKAHEAD(2) { truncate.setTableToken(true); } ] + target=RelationTarget() { truncate.getTargets().add(target); } + ( LOOKAHEAD(2) "," target=RelationTarget() { truncate.getTargets().add(target); } )* + [ ( { truncate.setIdentityOption(Truncate.IdentityOption.RESTART); } + | ContextualKeyword("CONTINUE") { truncate.setIdentityOption(Truncate.IdentityOption.CONTINUE); } ) + ] + [ { truncate.setDropBehavior(Truncate.DropBehavior.CASCADE); } + | { truncate.setDropBehavior(Truncate.DropBehavior.RESTRICT); } ] + { return truncate; } } /** diff --git a/src/test/java/net/sf/jsqlparser/statement/truncate/PostgreSqlTruncateTest.java b/src/test/java/net/sf/jsqlparser/statement/truncate/PostgreSqlTruncateTest.java new file mode 100644 index 0000000000..a0ef7a0dbf --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/truncate/PostgreSqlTruncateTest.java @@ -0,0 +1,104 @@ +/*- + * #%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.truncate; + +import static org.junit.jupiter.api.Assertions.*; +import java.util.List; +import java.util.Set; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.schema.Table; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.lock.LockStatement; +import net.sf.jsqlparser.util.TablesNamesFinder; +import net.sf.jsqlparser.util.deparser.StatementDeParser; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class PostgreSqlTruncateTest { + @ParameterizedTest + @ValueSource(strings = {"t", "ONLY t", "t *", "ONLY (public.t)", + "ONLY t, public.u *", "t, ONLY u", "ONLY t, ONLY u"}) + void targetsAndOptionalClausesRoundTrip(String targets) throws JSQLParserException { + for (String identity : new String[] {"", " RESTART IDENTITY", " CONTINUE IDENTITY"}) { + for (String behavior : new String[] {"", " CASCADE", " RESTRICT"}) { + for (String tableKeyword : new String[] {"", " TABLE"}) { + String sql = "TRUNCATE" + tableKeyword + " " + targets + identity + behavior; + Truncate statement = (Truncate) CCJSqlParserUtil.parse(sql); + assertEquals(sql, statement.toString()); + assertEquals(targets.contains(",") ? 2 : 1, statement.getTargets().size()); + assertEquals(identity.contains("RESTART") ? Truncate.IdentityOption.RESTART + : identity.contains("CONTINUE") ? Truncate.IdentityOption.CONTINUE + : null, + statement.getIdentityOption()); + assertEquals(behavior.contains("CASCADE") ? Truncate.DropBehavior.CASCADE + : behavior.contains("RESTRICT") ? Truncate.DropBehavior.RESTRICT : null, + statement.getDropBehavior()); + roundTrip(statement); + assertEquals(2, CCJSqlParserUtil.parseStatements(sql + "; SELECT 1").size()); + } + } + } + } + + @Test + void visitsEveryTableAndKeepsLegacyMutationsLive() throws JSQLParserException { + Truncate statement = (Truncate) CCJSqlParserUtil.parse( + "TRUNCATE ONLY a, b *, ONLY c RESTART IDENTITY CASCADE"); + assertEquals(Set.of("a", "b", "c"), new TablesNamesFinder().getTables(statement)); + assertEquals("c", statement.getTable().getName()); + statement.setTable(new Table("x")); + statement.getTables().set(1, new Table("y")); + assertTrue(statement.getTargets().get(1).isIncludeDescendants()); + statement.getTargets().get(2).setOnly(false); + statement.getTables().add(new Table("z")); + statement.setCascade(false); + statement.setDropBehavior(Truncate.DropBehavior.RESTRICT); + assertFalse(statement.getCascade()); + assertEquals("TRUNCATE ONLY a, y *, x, z RESTART IDENTITY RESTRICT", statement.toString()); + assertEquals(Set.of("a", "y", "x", "z"), new TablesNamesFinder().getTables(statement)); + statement.getTables().remove(2); + roundTrip(statement); + statement.setTables(statement.getTables()); + assertEquals(3, statement.getTargets().size()); + statement.setTables(List.of(new Table("replacement"))); + assertTrue(statement.isOnly()); + assertEquals("replacement", statement.getTable().getName()); + } + + @Test + void lockAndTruncateShareRelationTargets() throws JSQLParserException { + LockStatement lock = (LockStatement) CCJSqlParserUtil.parse( + "LOCK TABLE ONLY (public.t), u * IN ACCESS SHARE MODE"); + assertEquals(LockStatement.Scope.ONLY, lock.getTargets().get(0).getScope()); + assertTrue(lock.getTargets().get(0).isParenthesized()); + assertEquals(LockStatement.Scope.INCLUDING_DESCENDANTS, + lock.getTargets().get(1).getScope()); + lock.getTargets().get(0).setScope(LockStatement.Scope.DEFAULT); + assertFalse(lock.getTargets().get(0).isParenthesized()); + roundTrip(lock); + } + + @ParameterizedTest + @ValueSource(strings = {"ONLY t *", "(t)", "t RESTART", "t CONTINUE", + "t RESTART IDENTITY CONTINUE IDENTITY", "t CASCADE RESTRICT", "t,", + "t RESTRICT RESTART IDENTITY"}) + void rejectsMalformedTargetsAndClauses(String sql) { + assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parse("TRUNCATE " + sql)); + } + + private static void roundTrip(Statement statement) throws JSQLParserException { + StringBuilder output = new StringBuilder(); + statement.accept(new StatementDeParser(output)); + assertEquals(statement.toString(), output.toString()); + assertEquals(output.toString(), CCJSqlParserUtil.parse(output.toString()).toString()); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/truncate/TruncateMultipleTablesTest.java b/src/test/java/net/sf/jsqlparser/statement/truncate/TruncateMultipleTablesTest.java index 18894c8ca0..494a0cdec4 100644 --- a/src/test/java/net/sf/jsqlparser/statement/truncate/TruncateMultipleTablesTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/truncate/TruncateMultipleTablesTest.java @@ -13,7 +13,6 @@ import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.StringReader; @@ -86,8 +85,8 @@ public void testTruncateDeparse() throws JSQLParserException { String statement = "TRUNCATE TABLE foo, bar"; assertSqlCanBeParsedAndDeparsed(statement); assertDeparse(new Truncate() - .withTables(List.of(new Table("foo"), new Table("bar"))) - .withTableToken(true), statement); + .withTables(List.of(new Table("foo"), new Table("bar"))) + .withTableToken(true), statement); } @Test @@ -95,16 +94,18 @@ public void testTruncateCascadeDeparse() throws JSQLParserException { String statement = "TRUNCATE TABLE foo, bar CASCADE"; assertSqlCanBeParsedAndDeparsed(statement); assertDeparse(new Truncate() - .withTables(List.of(new Table("foo"), new Table("bar"))) - .withTableToken(true) - .withCascade(true), statement); + .withTables(List.of(new Table("foo"), new Table("bar"))) + .withTableToken(true) + .withCascade(true), statement); } @Test - public void testTruncateDoesNotAllowOnlyWithMultipleTables() { + public void testTruncateAllowsOnlyForAnIndividualTarget() throws JSQLParserException { String statement = "TRUNCATE TABLE ONLY foo, bar"; - assertThrows(JSQLParserException.class, - () -> parserManager.parse(new StringReader(statement))); + Truncate truncate = (Truncate) parserManager.parse(new StringReader(statement)); + assertEquals(statement, truncate.toString()); + assertTrue(truncate.getTargets().get(0).isOnly()); + assertEquals(2, truncate.getTargets().size()); } } diff --git a/src/test/java/net/sf/jsqlparser/statement/truncate/TruncateTest.java b/src/test/java/net/sf/jsqlparser/statement/truncate/TruncateTest.java index 698d2a5b84..129b3f43f0 100644 --- a/src/test/java/net/sf/jsqlparser/statement/truncate/TruncateTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/truncate/TruncateTest.java @@ -18,7 +18,6 @@ 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 static org.junit.jupiter.api.Assertions.assertThrows; import org.junit.jupiter.api.Test; @@ -72,8 +71,8 @@ public void testTruncateDeparse() throws JSQLParserException { String statement = "TRUNCATE TABLE foo"; assertSqlCanBeParsedAndDeparsed(statement); assertDeparse(new Truncate() - .withTable(new Table("foo")) - .withTableToken(true), statement); + .withTable(new Table("foo")) + .withTableToken(true), statement); } @Test @@ -81,9 +80,9 @@ public void testTruncateCascadeDeparse() throws JSQLParserException { String statement = "TRUNCATE TABLE foo CASCADE"; assertSqlCanBeParsedAndDeparsed(statement); assertDeparse(new Truncate() - .withTable(new Table("foo")) - .withTableToken(true) - .withCascade(true), statement); + .withTable(new Table("foo")) + .withTableToken(true) + .withCascade(true), statement); } @Test @@ -91,9 +90,9 @@ public void testTruncateOnlyDeparse() throws JSQLParserException { String statement = "TRUNCATE TABLE ONLY foo"; assertSqlCanBeParsedAndDeparsed(statement); assertDeparse(new Truncate() - .withTable(new Table("foo")) - .withTableToken(true) - .withOnly(true), statement); + .withTable(new Table("foo")) + .withTableToken(true) + .withOnly(true), statement); } @Test @@ -101,16 +100,15 @@ public void testTruncateOnlyAndCascadeDeparse() throws JSQLParserException { String statement = "TRUNCATE ONLY foo CASCADE"; assertSqlCanBeParsedAndDeparsed(statement); assertDeparse(new Truncate() - .withTable(new Table("foo")) - .withCascade(true) - .withOnly(true), statement); + .withTable(new Table("foo")) + .withCascade(true) + .withOnly(true), statement); } @Test - public void throwsParseWhenOnlyUsedWithMultipleTables() { + public void onlyAppliesToItsOwnTarget() throws JSQLParserException { String statement = "TRUNCATE TABLE ONLY foo, bar"; - assertThrows(JSQLParserException.class, - () -> parserManager.parse(new StringReader(statement))); + assertSqlCanBeParsedAndDeparsed(statement); } }