From 8ad8363d6e71e056f850244e486c53aa568e1977 Mon Sep 17 00:00:00 2001 From: Aleksandar Maksimovic Date: Wed, 5 Aug 2026 16:11:13 -0700 Subject: [PATCH] feat(dsql): parse ALTER COLUMN SET STORAGE --- Cargo.toml | 4 ++-- examples/cli.rs | 4 ++-- sqlparser_bench/Cargo.toml | 2 +- src/ast/ddl.rs | 39 +++++++++++++++++++++++++++++- src/ast/mod.rs | 47 +++++++++++++++++++------------------ src/ast/query.rs | 2 +- src/ast/spans.rs | 2 ++ src/parser/mod.rs | 33 +++++++++++++++++++++----- tests/sqlparser_common.rs | 6 ++--- tests/sqlparser_postgres.rs | 32 +++++++++++++++++++++---- 10 files changed, 128 insertions(+), 43 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7476521fc9..ab9c88e20a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,8 +17,8 @@ [package] name = "sqlparser-dsql" -description = "SQL parser fork with Aurora DSQL extensions (CREATE INDEX ASYNC, ALTER TABLE ASYNC, order-independent CREATE SEQUENCE, INCLUDE on table constraints). Based on sqlparser 0.62.0." -version = "0.62.2" +description = "SQL parser fork with Aurora DSQL extensions (CREATE INDEX ASYNC, ALTER TABLE ASYNC, ALTER COLUMN SET STORAGE, order-independent CREATE SEQUENCE, INCLUDE on table constraints). Based on sqlparser 0.62.0." +version = "0.62.3" authors = [ "Apache DataFusion ", "Amazon Web Services", diff --git a/examples/cli.rs b/examples/cli.rs index 3c4299b209..51a63a7ea1 100644 --- a/examples/cli.rs +++ b/examples/cli.rs @@ -71,9 +71,9 @@ $ cargo run --example cli - [--dialectname] .expect("failed to read from stdin"); String::from_utf8(buf).expect("stdin content wasn't valid utf8") } else { - println!("Parsing from file '{}' using {:?}", &filename, dialect); + println!("Parsing from file '{}' using {:?}", filename, dialect); fs::read_to_string(&filename) - .unwrap_or_else(|_| panic!("Unable to read the file {}", &filename)) + .unwrap_or_else(|_| panic!("Unable to read the file {}", filename)) }; let without_bom = if contents.chars().next().unwrap() as u64 != 0xfeff { contents.as_str() diff --git a/sqlparser_bench/Cargo.toml b/sqlparser_bench/Cargo.toml index 687d399739..1bdd035e7a 100644 --- a/sqlparser_bench/Cargo.toml +++ b/sqlparser_bench/Cargo.toml @@ -23,7 +23,7 @@ authors = ["Dandandan "] edition = "2018" [dependencies] -sqlparser = { path = "../" } +sqlparser = { package = "sqlparser-dsql", path = "../" } [dev-dependencies] criterion = "0.8" diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index de216400ae..b95d74639a 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -1278,6 +1278,11 @@ pub enum AlterColumnOperation { }, /// `DROP DEFAULT` DropDefault, + /// `SET STORAGE { PLAIN | EXTERNAL | EXTENDED | MAIN | DEFAULT }` + SetStorage { + /// PostgreSQL column storage strategy. + storage: AlterColumnStorage, + }, /// `[SET DATA] TYPE [USING ]` SetDataType { /// Target data type for the column. @@ -1310,6 +1315,9 @@ impl fmt::Display for AlterColumnOperation { AlterColumnOperation::DropDefault => { write!(f, "DROP DEFAULT") } + AlterColumnOperation::SetStorage { storage } => { + write!(f, "SET STORAGE {storage}") + } AlterColumnOperation::SetDataType { data_type, using, @@ -1350,6 +1358,35 @@ impl fmt::Display for AlterColumnOperation { } } +/// PostgreSQL column storage strategy used by `ALTER COLUMN ... SET STORAGE`. +#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub enum AlterColumnStorage { + /// No compression or out-of-line storage. + Plain, + /// Out-of-line storage without compression. + External, + /// Compression and out-of-line storage. + Extended, + /// Compression with a preference for in-line storage. + Main, + /// Reset to the data type's default storage strategy. + Default, +} + +impl fmt::Display for AlterColumnStorage { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + AlterColumnStorage::Plain => write!(f, "PLAIN"), + AlterColumnStorage::External => write!(f, "EXTERNAL"), + AlterColumnStorage::Extended => write!(f, "EXTENDED"), + AlterColumnStorage::Main => write!(f, "MAIN"), + AlterColumnStorage::Default => write!(f, "DEFAULT"), + } + } +} + /// Representation whether a definition can can contains the KEY or INDEX keywords with the same /// meaning. /// @@ -4772,7 +4809,7 @@ impl fmt::Display for AlterTable { if self.only { write!(f, "ONLY ")?; } - write!(f, "{} ", &self.name)?; + write!(f, "{} ", self.name)?; if let Some(cluster) = &self.on_cluster { write!(f, "ON CLUSTER {cluster} ")?; } diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 42850cab21..a97a311406 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -60,28 +60,29 @@ pub use self::dcl::{ SetConfigValue, Use, }; pub use self::ddl::{ - Alignment, AlterCollation, AlterCollationOperation, AlterColumnOperation, AlterConnectorOwner, - AlterFunction, AlterFunctionAction, AlterFunctionKind, AlterFunctionOperation, - AlterIndexOperation, AlterOperator, AlterOperatorClass, AlterOperatorClassOperation, - AlterOperatorFamily, AlterOperatorFamilyOperation, AlterOperatorOperation, AlterPolicy, - AlterPolicyOperation, AlterSchema, AlterSchemaOperation, AlterTable, AlterTableAlgorithm, - AlterTableLock, AlterTableOperation, AlterTableType, AlterType, AlterTypeAddValue, - AlterTypeAddValuePosition, AlterTypeOperation, AlterTypeRename, AlterTypeRenameValue, - ClusteredBy, ColumnDef, ColumnOption, ColumnOptionDef, ColumnOptions, ColumnPolicy, - ColumnPolicyProperty, ConstraintCharacteristics, CreateCollation, CreateCollationDefinition, - CreateConnector, CreateDomain, CreateExtension, CreateFunction, CreateIndex, CreateOperator, - CreateOperatorClass, CreateOperatorFamily, CreatePolicy, CreatePolicyCommand, CreatePolicyType, - CreateTable, CreateTrigger, CreateView, Deduplicate, DeferrableInitial, DistStyle, - DropBehavior, DropExtension, DropFunction, DropOperator, DropOperatorClass, DropOperatorFamily, - DropOperatorSignature, DropPolicy, DropTrigger, ForValues, FunctionReturnType, GeneratedAs, - GeneratedExpressionMode, IdentityParameters, IdentityProperty, IdentityPropertyFormatKind, - IdentityPropertyKind, IdentityPropertyOrder, IndexColumn, IndexOption, IndexType, - KeyOrIndexDisplay, Msck, NullsDistinctOption, OperatorArgTypes, OperatorClassItem, - OperatorFamilyDropItem, OperatorFamilyItem, OperatorOption, OperatorPurpose, Owner, Partition, - PartitionBoundValue, ProcedureParam, ReferentialAction, RenameTableNameKind, ReplicaIdentity, - TagsColumnOption, TriggerObjectKind, Truncate, UserDefinedTypeCompositeAttributeDef, - UserDefinedTypeInternalLength, UserDefinedTypeRangeOption, UserDefinedTypeRepresentation, - UserDefinedTypeSqlDefinitionOption, UserDefinedTypeStorage, ViewColumnDef, WithData, + Alignment, AlterCollation, AlterCollationOperation, AlterColumnOperation, AlterColumnStorage, + AlterConnectorOwner, AlterFunction, AlterFunctionAction, AlterFunctionKind, + AlterFunctionOperation, AlterIndexOperation, AlterOperator, AlterOperatorClass, + AlterOperatorClassOperation, AlterOperatorFamily, AlterOperatorFamilyOperation, + AlterOperatorOperation, AlterPolicy, AlterPolicyOperation, AlterSchema, AlterSchemaOperation, + AlterTable, AlterTableAlgorithm, AlterTableLock, AlterTableOperation, AlterTableType, + AlterType, AlterTypeAddValue, AlterTypeAddValuePosition, AlterTypeOperation, AlterTypeRename, + AlterTypeRenameValue, ClusteredBy, ColumnDef, ColumnOption, ColumnOptionDef, ColumnOptions, + ColumnPolicy, ColumnPolicyProperty, ConstraintCharacteristics, CreateCollation, + CreateCollationDefinition, CreateConnector, CreateDomain, CreateExtension, CreateFunction, + CreateIndex, CreateOperator, CreateOperatorClass, CreateOperatorFamily, CreatePolicy, + CreatePolicyCommand, CreatePolicyType, CreateTable, CreateTrigger, CreateView, Deduplicate, + DeferrableInitial, DistStyle, DropBehavior, DropExtension, DropFunction, DropOperator, + DropOperatorClass, DropOperatorFamily, DropOperatorSignature, DropPolicy, DropTrigger, + ForValues, FunctionReturnType, GeneratedAs, GeneratedExpressionMode, IdentityParameters, + IdentityProperty, IdentityPropertyFormatKind, IdentityPropertyKind, IdentityPropertyOrder, + IndexColumn, IndexOption, IndexType, KeyOrIndexDisplay, Msck, NullsDistinctOption, + OperatorArgTypes, OperatorClassItem, OperatorFamilyDropItem, OperatorFamilyItem, + OperatorOption, OperatorPurpose, Owner, Partition, PartitionBoundValue, ProcedureParam, + ReferentialAction, RenameTableNameKind, ReplicaIdentity, TagsColumnOption, TriggerObjectKind, + Truncate, UserDefinedTypeCompositeAttributeDef, UserDefinedTypeInternalLength, + UserDefinedTypeRangeOption, UserDefinedTypeRepresentation, UserDefinedTypeSqlDefinitionOption, + UserDefinedTypeStorage, ViewColumnDef, WithData, }; pub use self::dml::{ Delete, Insert, Merge, MergeAction, MergeClause, MergeClauseKind, MergeInsertExpr, @@ -11745,7 +11746,7 @@ impl fmt::Display for AlterUser { let has_props = !self.set_props.options.is_empty(); if has_props { write!(f, " SET")?; - write!(f, " {}", &self.set_props)?; + write!(f, " {}", self.set_props)?; } if !self.unset_props.is_empty() { write!(f, " UNSET {}", display_comma_separated(&self.unset_props))?; diff --git a/src/ast/query.rs b/src/ast/query.rs index 1de0e0e9db..aeeced6349 100644 --- a/src/ast/query.rs +++ b/src/ast/query.rs @@ -3545,7 +3545,7 @@ pub struct LockClause { impl fmt::Display for LockClause { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "FOR {}", &self.lock_type)?; + write!(f, "FOR {}", self.lock_type)?; if let Some(ref of) = self.of { write!(f, " OF {of}")?; } diff --git a/src/ast/spans.rs b/src/ast/spans.rs index 81d75b7cac..1910a94909 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -891,6 +891,7 @@ impl Spanned for Analyze { /// - [AlterColumnOperation::SetNotNull] /// - [AlterColumnOperation::DropNotNull] /// - [AlterColumnOperation::DropDefault] +/// - [AlterColumnOperation::SetStorage] /// - [AlterColumnOperation::AddGenerated] impl Spanned for AlterColumnOperation { fn span(&self) -> Span { @@ -899,6 +900,7 @@ impl Spanned for AlterColumnOperation { AlterColumnOperation::DropNotNull => Span::empty(), AlterColumnOperation::SetDefault { value } => value.span(), AlterColumnOperation::DropDefault => Span::empty(), + AlterColumnOperation::SetStorage { .. } => Span::empty(), AlterColumnOperation::SetDataType { data_type: _, using, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index e3b49b7091..5202202cb2 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -4744,7 +4744,7 @@ impl<'a> Parser<'a> { if self.parse_keyword(expected) { Ok(self.get_current_token().clone()) } else { - self.expected_ref(format!("{:?}", &expected).as_str(), self.peek_token_ref()) + self.expected_ref(format!("{:?}", expected).as_str(), self.peek_token_ref()) } } @@ -4757,7 +4757,7 @@ impl<'a> Parser<'a> { if self.parse_keyword(expected) { Ok(()) } else { - self.expected_ref(format!("{:?}", &expected).as_str(), self.peek_token_ref()) + self.expected_ref(format!("{:?}", expected).as_str(), self.peek_token_ref()) } } @@ -10611,6 +10611,27 @@ impl<'a> Parser<'a> { } } else if self.parse_keywords(&[Keyword::DROP, Keyword::DEFAULT]) { AlterColumnOperation::DropDefault {} + } else if self.parse_keywords(&[Keyword::SET, Keyword::STORAGE]) { + let storage = match self.parse_one_of_keywords(&[ + Keyword::PLAIN, + Keyword::EXTERNAL, + Keyword::EXTENDED, + Keyword::MAIN, + Keyword::DEFAULT, + ]) { + Some(Keyword::PLAIN) => AlterColumnStorage::Plain, + Some(Keyword::EXTERNAL) => AlterColumnStorage::External, + Some(Keyword::EXTENDED) => AlterColumnStorage::Extended, + Some(Keyword::MAIN) => AlterColumnStorage::Main, + Some(Keyword::DEFAULT) => AlterColumnStorage::Default, + _ => { + return self.expected_ref( + "storage value (PLAIN, EXTERNAL, EXTENDED, MAIN, or DEFAULT)", + self.peek_token_ref(), + ) + } + }; + AlterColumnOperation::SetStorage { storage } } else if self.parse_keywords(&[Keyword::SET, Keyword::DATA, Keyword::TYPE]) { self.parse_set_data_type(true)? } else if self.parse_keyword(Keyword::TYPE) { @@ -10640,9 +10661,9 @@ impl<'a> Parser<'a> { } } else { let message = if is_postgresql { - "SET/DROP NOT NULL, SET DEFAULT, SET DATA TYPE, or ADD GENERATED after ALTER COLUMN" + "SET/DROP NOT NULL, SET DEFAULT, SET STORAGE, SET DATA TYPE, or ADD GENERATED after ALTER COLUMN" } else { - "SET/DROP NOT NULL, SET DEFAULT, or SET DATA TYPE after ALTER COLUMN" + "SET/DROP NOT NULL, SET DEFAULT, SET STORAGE, or SET DATA TYPE after ALTER COLUMN" }; return self.expected_ref(message, self.peek_token_ref()); @@ -13437,7 +13458,7 @@ impl<'a> Parser<'a> { } Token::EOF => break, token => { - return Err(ParserError::ParserError(format!( + Err(ParserError::ParserError(format!( "Unexpected token in identifier: {token}" )))?; } @@ -16696,7 +16717,7 @@ impl<'a> Parser<'a> { where_clause = Some(self.parse_expr()?); } else { let tok = self.peek_token_ref(); - return parser_err!( + parser_err!( format!( "Expected one of DIMENSIONS, METRICS, FACTS or WHERE, got {}", tok.token diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 1ba55111cc..5cc063c995 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -1719,7 +1719,7 @@ fn parse_json_ops_without_colon() { ]; for (str_op, op, dialects) in binary_ops { - let select = dialects.verified_only_select(&format!("SELECT a {} b", &str_op)); + let select = dialects.verified_only_select(&format!("SELECT a {} b", str_op)); assert_eq!( SelectItem::UnnamedExpr(Expr::BinaryOp { left: Box::new(Expr::Identifier(Ident::new("a"))), @@ -2415,7 +2415,7 @@ fn parse_bitwise_ops() { ]; for (str_op, op, dialects) in bitwise_ops { - let select = dialects.verified_only_select(&format!("SELECT a {} b", &str_op)); + let select = dialects.verified_only_select(&format!("SELECT a {} b", str_op)); assert_eq!( SelectItem::UnnamedExpr(Expr::BinaryOp { left: Box::new(Expr::Identifier(Ident::new("a"))), @@ -18770,7 +18770,7 @@ fn parse_generic_unary_ops() { ("+", UnaryOperator::Plus), ]; for (str_op, op) in unary_ops { - let select = verified_only_select(&format!("SELECT {}expr", &str_op)); + let select = verified_only_select(&format!("SELECT {}expr", str_op)); assert_eq!( UnnamedExpr(UnaryOp { op: *op, diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 9458dab3e4..e803bcd9a6 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -1085,6 +1085,31 @@ fn parse_alter_table_alter_column() { } } +#[test] +fn parse_alter_table_alter_column_set_storage() { + for storage in ["PLAIN", "EXTERNAL", "EXTENDED", "MAIN", "DEFAULT"] { + pg_and_generic().verified_stmt(&format!( + "ALTER TABLE tab ALTER COLUMN payload SET STORAGE {storage}" + )); + } + + match alter_table_op(pg().one_statement_parses_to( + "ALTER TABLE tab ALTER payload SET STORAGE DEFAULT", + "ALTER TABLE tab ALTER COLUMN payload SET STORAGE DEFAULT", + )) { + AlterTableOperation::AlterColumn { column_name, op } => { + assert_eq!("payload", column_name.to_string()); + assert_eq!( + op, + AlterColumnOperation::SetStorage { + storage: AlterColumnStorage::Default, + } + ); + } + _ => unreachable!(), + } +} + #[test] fn parse_alter_table_alter_column_add_generated() { pg_and_generic() @@ -2505,7 +2530,7 @@ fn parse_pg_unary_ops() { ("@", UnaryOperator::PGAbs), ]; for (str_op, op) in pg_unary_ops { - let select = pg().verified_only_select(&format!("SELECT {}a", &str_op)); + let select = pg().verified_only_select(&format!("SELECT {}a", str_op)); assert_eq!( SelectItem::UnnamedExpr(Expr::UnaryOp { op: *op, @@ -2521,7 +2546,7 @@ fn parse_pg_postfix_factorial() { let postfix_factorial = &[("!", UnaryOperator::PGPostfixFactorial)]; for (str_op, op) in postfix_factorial { - let select = pg().verified_only_select(&format!("SELECT a{}", &str_op)); + let select = pg().verified_only_select(&format!("SELECT a{}", str_op)); assert_eq!( SelectItem::UnnamedExpr(Expr::UnaryOp { op: *op, @@ -7251,8 +7276,7 @@ fn parse_alter_table_async_validate_constraint() { } // ASYNC composes with IF EXISTS and ONLY. - pg_and_generic() - .verified_stmt("ALTER TABLE ASYNC IF EXISTS ONLY foo VALIDATE CONSTRAINT bar"); + pg_and_generic().verified_stmt("ALTER TABLE ASYNC IF EXISTS ONLY foo VALIDATE CONSTRAINT bar"); } #[test]