diff --git a/src/main/java/net/sf/jsqlparser/statement/alter/IdentityAlteration.java b/src/main/java/net/sf/jsqlparser/statement/alter/IdentityAlteration.java index fdc8949e6..c5c918fc5 100644 --- a/src/main/java/net/sf/jsqlparser/statement/alter/IdentityAlteration.java +++ b/src/main/java/net/sf/jsqlparser/statement/alter/IdentityAlteration.java @@ -61,6 +61,7 @@ public void setParameters(List parameters) { this.parameters = parameters == null ? null : new ArrayList<>(parameters); } + /** Returns the restart value, or null for a bare RESTART using the sequence start value. */ public Long getRestartWith() { return restartWith; } @@ -69,6 +70,11 @@ public void setRestartWith(Long restartWith) { this.restartWith = restartWith; } + public IdentityAlteration withRestartWith(Long restartWith) { + setRestartWith(restartWith); + return this; + } + public boolean isIfExists() { return ifExists; } diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index 1411612de..fc1fae909 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -16306,7 +16306,7 @@ IdentityAlteration ColumnIdentityAlteration(): } ) | - [ LOOKAHEAD(2) restart=SequenceParameterValue() ] { + [ LOOKAHEAD(1) [ ] restart=SequenceParameterValue() ] { alteration = new IdentityAlteration(IdentityAlteration.Kind.RESTART); alteration.setRestartWith(restart); } diff --git a/src/site/sphinx/usage.rst b/src/site/sphinx/usage.rst index 6e4c96feb..61c5a5d64 100644 --- a/src/site/sphinx/usage.rst +++ b/src/site/sphinx/usage.rst @@ -293,6 +293,13 @@ Table constraints expose ``Index.getNullsDistinct()``, ``getIncludeColumns()``, Identity alterations are available as ``ColumnDataType.getIdentityAlterations()``. Sequence ownership is shared by ``CreateSequence`` and ``AlterSequence`` through ``Sequence.getOwnership()``: ``null`` means omitted, ``isNone()`` means explicit ``OWNED BY NONE``, and ``getColumn()`` identifies an owner. ``TablesNamesFinder`` includes ``LIKE`` sources and sequence owners without treating sequence or type names as tables. See `ALTER TABLE `_ and `ALTER SEQUENCE `_. +For identity columns, ``RESTART 20`` and ``RESTART WITH 20`` produce the same +``IdentityAlteration`` with kind ``RESTART`` and ``getRestartWith() == 20L``. +The renderer consistently uses ``RESTART WITH 20``. A null restart value means +bare ``RESTART``, which uses the sequence's configured start value. Call +``setRestartWith`` to edit a parsed action or construct one with +``new IdentityAlteration(Kind.RESTART).withRestartWith(20L)``. + Structured column attributes ============================ diff --git a/src/test/java/net/sf/jsqlparser/statement/alter/IdentityRestartValueTest.java b/src/test/java/net/sf/jsqlparser/statement/alter/IdentityRestartValueTest.java new file mode 100644 index 000000000..c73b2fc10 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/alter/IdentityRestartValueTest.java @@ -0,0 +1,84 @@ +/*- + * #%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.alter; + +import static org.junit.jupiter.api.Assertions.*; +import java.util.List; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +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 IdentityRestartValueTest { + @ParameterizedTest + @ValueSource(strings = {"20", "WITH 20", "+20", "-20", "WITH -20", "9223372036854775807"}) + void representsOptionalWithUsingTheSameValue(String value) throws JSQLParserException { + for (Dialect dialect : new Dialect[] {null, Dialect.POSTGRESQL}) { + Alter alter = parse("ALTER TABLE t ALTER COLUMN id RESTART " + value, dialect); + IdentityAlteration restart = identity(alter).get(0); + assertEquals(IdentityAlteration.Kind.RESTART, restart.getKind()); + assertEquals(Long.valueOf(value.replace("WITH ", "")), restart.getRestartWith()); + roundTrip(alter, dialect); + } + } + + @Test + void editsAndConstructsRestartAndPreservesBareRestart() throws JSQLParserException { + Alter alter = parse("ALTER TABLE t ALTER COLUMN id RESTART", Dialect.POSTGRESQL); + assertNull(identity(alter).get(0).getRestartWith()); + identity(alter).get(0).setRestartWith(20L); + assertEquals("ALTER TABLE t ALTER COLUMN id RESTART WITH 20", alter.toString()); + roundTrip(alter, Dialect.POSTGRESQL); + alter.getAlterExpressions().get(0).getColDataTypeList().get(0).setIdentityAlterations( + List.of(new IdentityAlteration(IdentityAlteration.Kind.RESTART) + .withRestartWith(40L))); + assertEquals("ALTER TABLE t ALTER COLUMN id RESTART WITH 40", alter.toString()); + roundTrip(alter, Dialect.POSTGRESQL); + identity(alter).get(0).setRestartWith(null); + assertEquals("ALTER TABLE t ALTER COLUMN id RESTART", alter.toString()); + } + + @Test + void respectsIdentityAndAlterActionBoundaries() throws JSQLParserException { + Alter alter = parse( + "ALTER TABLE t ALTER COLUMN id SET CACHE 10 RESTART 20 SET NO CYCLE, ADD COLUMN extra INT", + Dialect.POSTGRESQL); + assertEquals(3, identity(alter).size()); + assertEquals(2, alter.getAlterExpressions().size()); + roundTrip(alter, Dialect.POSTGRESQL); + roundTrip(parse("ALTER TABLE t ALTER COLUMN id RESTART SET CACHE 10", Dialect.POSTGRESQL), + Dialect.POSTGRESQL); + assertThrows(JSQLParserException.class, + () -> parse("ALTER TABLE t ALTER COLUMN id RESTART WITH", Dialect.POSTGRESQL)); + } + + private static List identity(Alter alter) { + return alter.getAlterExpressions().get(0).getColDataTypeList().get(0) + .getIdentityAlterations(); + } + + private static Alter parse(String sql, Dialect dialect) throws JSQLParserException { + return (Alter) CCJSqlParserUtil.parse(sql, p -> { + if (dialect != null) { + p.withDialect(dialect); + } + }); + } + + private static void roundTrip(Alter alter, Dialect dialect) throws JSQLParserException { + StringBuilder sql = new StringBuilder(); + alter.accept(new StatementDeParser(sql), null); + assertEquals(alter.toString(), sql.toString()); + assertEquals(sql.toString(), parse(sql.toString(), dialect).toString()); + } +}