From 1eafd6e85f410f95485f3ac029e8ee7552435251 Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Sat, 7 Nov 2026 23:57:02 +0100 Subject: [PATCH] PostgreSQL: Support `RESTART` in `ALTER SEQUENCE` --- fuzz/fuzz_seeds/postgres_alter_sequence.sql | 2 + src/ast/mod.rs | 24 ++++++++++-- src/parser/mod.rs | 18 +++++++-- tests/sqlparser_common.rs | 3 ++ tests/sqlparser_postgres.rs | 42 +++++++++++++++++++++ 5 files changed, 81 insertions(+), 8 deletions(-) diff --git a/fuzz/fuzz_seeds/postgres_alter_sequence.sql b/fuzz/fuzz_seeds/postgres_alter_sequence.sql index 8087699b0f..0a931c1c1d 100644 --- a/fuzz/fuzz_seeds/postgres_alter_sequence.sql +++ b/fuzz/fuzz_seeds/postgres_alter_sequence.sql @@ -4,3 +4,5 @@ ALTER SEQUENCE IF EXISTS s RENAME TO s2; ALTER SEQUENCE s SET UNLOGGED; ALTER SEQUENCE s SET LOGGED; ALTER SEQUENCE s AS bigint INCREMENT BY -2 NO MINVALUE MAXVALUE 100 START WITH 5 CACHE 10 NO CYCLE OWNED BY public.t.c; +ALTER SEQUENCE s RESTART; +ALTER SEQUENCE s RESTART WITH 7 INCREMENT 3; diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 78e5e687a8..fa3eb1bf52 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -6641,17 +6641,19 @@ impl fmt::Display for Statement { } } -/// Can use to describe options in create sequence or table column type identity +/// Can use to describe options in create sequence, alter sequence, or table +/// column type identity /// ```sql /// [ AS data_type ] [ INCREMENT [ BY ] increment ] /// [ MINVALUE minvalue | NO MINVALUE ] [ MAXVALUE maxvalue | NO MAXVALUE ] -/// [ START [ WITH ] start ] [ CACHE cache ] [ [ NO ] CYCLE ] +/// [ START [ WITH ] start ] [ RESTART [ [ WITH ] restart ] ] +/// [ CACHE cache ] [ [ NO ] CYCLE ] /// [ OWNED BY { table_name.column_name | NONE } ] /// ``` /// /// The options form an unordered list, so they are stored in the order written. -/// `AS` and `OWNED BY` are only accepted by `CREATE SEQUENCE`, not by an -/// identity column. +/// `AS` and `OWNED BY` are only accepted by `CREATE SEQUENCE` and `ALTER +/// SEQUENCE`, not by an identity column. #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] @@ -6666,6 +6668,9 @@ pub enum SequenceOptions { MaxValue(Option), /// `START [WITH] `; second value indicates presence of `WITH`. StartWith(Expr, bool), + /// `RESTART [ [ WITH ] restart ]`, `None` restarting from the start value. + /// The second value indicates presence of `WITH`. + Restart(Option, bool), /// `CACHE ` option. Cache(Expr), /// `CYCLE` or `NO CYCLE` option. @@ -6708,6 +6713,17 @@ impl fmt::Display for SequenceOptions { start = start ) } + SequenceOptions::Restart(None, _) => { + write!(f, " RESTART") + } + SequenceOptions::Restart(Some(restart), with) => { + write!( + f, + " RESTART{with} {restart}", + with = if *with { " WITH" } else { "" }, + restart = restart + ) + } SequenceOptions::Cache(cache) => { write!(f, " CACHE {}", *cache) } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 04dfa6db66..06811c0a9c 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -21711,11 +21711,12 @@ impl<'a> Parser<'a> { }) } - /// Parse the sequence options shared by `CREATE SEQUENCE` and identity - /// columns. The options form an unordered list, each allowed at most once. + /// Parse the sequence options shared by `CREATE SEQUENCE`, `ALTER SEQUENCE` + /// and identity columns. The options form an unordered list, each allowed + /// at most once. /// - /// `AS ` and `OWNED BY` are options of `CREATE SEQUENCE` only, so - /// `allow_type_and_owner` gates them off for an identity column. + /// `AS ` and `OWNED BY` are not options of an identity column, so + /// `allow_type_and_owner` gates them off there. fn parse_sequence_options( &mut self, allow_type_and_owner: bool, @@ -21752,6 +21753,15 @@ impl<'a> Parser<'a> { SequenceOptions::StartWith(self.parse_number()?, with), "START", ) + } else if self.parse_keyword(Keyword::RESTART) { + // [ RESTART [ [ WITH ] restart ] ] + let with = self.parse_keyword(Keyword::WITH); + let restart = if with { + Some(self.parse_number()?) + } else { + self.maybe_parse(|parser| parser.parse_number())? + }; + (SequenceOptions::Restart(restart, with), "RESTART") } else if self.parse_keyword(Keyword::CACHE) { //[ CACHE cache ] (SequenceOptions::Cache(self.parse_number()?), "CACHE") diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 89ba64d61e..2375cf2011 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -6176,8 +6176,11 @@ fn parse_sequence_options_in_any_order() { "CREATE SEQUENCE si OWNED BY NONE INCREMENT BY 2", "CREATE SEQUENCE sj CACHE 1 AS BIGINT OWNED BY a.b START WITH 3", "CREATE SEQUENCE sk OWNED BY none.c", + "CREATE SEQUENCE sl RESTART WITH 5 CACHE 2", "CREATE TABLE t (id INT GENERATED ALWAYS AS IDENTITY ( START WITH 10 INCREMENT BY 1 CYCLE ))", "ALTER TABLE t ALTER COLUMN id ADD GENERATED AS IDENTITY ( CACHE 2 INCREMENT 1 )", + "CREATE TABLE t (id INT GENERATED ALWAYS AS IDENTITY ( RESTART START WITH 3 ))", + "ALTER TABLE t ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( RESTART 7 )", ] { dialects.verified_stmt(sql); } diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index be5dfa19b9..15ce097e76 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -13522,6 +13522,10 @@ fn parse_case_ending_in_end_keyword_is_not_silently_dropped() { #[test] fn parse_alter_sequence() { let dialects = pg_and_generic(); + dialects.verified_stmt("ALTER SEQUENCE s RESTART"); + dialects.verified_stmt("ALTER SEQUENCE s RESTART WITH 5"); + dialects.verified_stmt("ALTER SEQUENCE s RESTART 7"); + dialects.verified_stmt("ALTER SEQUENCE IF EXISTS s RESTART"); dialects.verified_stmt("ALTER SEQUENCE s AS BIGINT"); dialects.verified_stmt("ALTER SEQUENCE s AS DOUBLE PRECISION"); dialects.verified_stmt("ALTER SEQUENCE s INCREMENT BY 2"); @@ -13556,6 +13560,7 @@ fn parse_alter_sequence() { dialects.verified_stmt( "ALTER SEQUENCE s INCREMENT BY 2 MINVALUE -5 MAXVALUE 50 START WITH 100 CACHE 3 NO CYCLE", ); + dialects.verified_stmt("ALTER SEQUENCE s NO CYCLE NO MAXVALUE OWNED BY NONE RESTART WITH 20"); dialects.verified_stmt("ALTER SEQUENCE s AS DOUBLE PRECISION INCREMENT 3 NO MINVALUE CYCLE"); } @@ -13577,6 +13582,27 @@ fn parse_alter_sequence_ast() { .parse_sql_statements("ALTER SEQUENCE s SET SCHEMA other") .unwrap(); assert_eq!(statements[0].span(), Span::empty()); + assert_eq!( + pg().parse_sql_statements("ALTER SEQUENCE s RESTART WITH 20 INCREMENT BY 3") + .unwrap(), + vec![Statement::AlterSequence(AlterSequence { + if_exists: false, + name: ObjectName::from(vec!["s".into()]), + operation: AlterSequenceOperation::Options(vec![ + SequenceOptions::Restart(Some(Expr::value(number("20"))), true), + SequenceOptions::IncrementBy(Expr::value(number("3")), true), + ]), + })] + ); + assert_eq!( + pg().parse_sql_statements("ALTER SEQUENCE s RESTART") + .unwrap(), + vec![Statement::AlterSequence(AlterSequence { + if_exists: false, + name: ObjectName::from(vec!["s".into()]), + operation: AlterSequenceOperation::Options(vec![SequenceOptions::Restart(None, false)]), + })] + ); assert_eq!( pg().parse_sql_statements("ALTER SEQUENCE s SET UNLOGGED") .unwrap(), @@ -13608,6 +13634,10 @@ fn parse_alter_sequence_errors() { "ALTER SEQUENCE s INCREMENT BY 2 INCREMENT BY 3", "INCREMENT specified more than once", ), + ( + "ALTER SEQUENCE s RESTART RESTART WITH 2", + "RESTART specified more than once", + ), ( "ALTER SEQUENCE s SET SCHEMA", "Expected: identifier, found: EOF", @@ -13624,6 +13654,18 @@ fn parse_alter_sequence_errors() { "ALTER SEQUENCE s INCREMENT", "Expected: a value, found: EOF", ), + ( + "ALTER SEQUENCE s RESTART WITH", + "Expected: a value, found: EOF", + ), + ( + "ALTER SEQUENCE s RESTART +", + "Expected: end of statement, found: +", + ), + ( + "ALTER SEQUENCE s RESTART x", + "Expected: end of statement, found: x", + ), ] { assert_eq!( pg_and_generic().parse_sql_statements(sql).unwrap_err(),