Skip to content

Commit 34ab7f1

Browse files
authored
PostgreSQL: AlterTypeOperation owner/schema/attribute ops and Statement::AlterDefaultPrivileges (#26)
* PostgreSQL: AlterTypeOperation owner/schema/attribute ops and Statement::AlterDefaultPrivileges Postgres rejects `ALTER TYPE ... OWNER TO ...`, `ALTER TYPE ... SET SCHEMA ...`, `ALTER TYPE ... { ADD | DROP | ALTER | RENAME } ATTRIBUTE ...`, and every `ALTER DEFAULT PRIVILEGES ...` form because the AST has no shape for them. This forces downstreams (e.g. pgmold) to strip those statements before parsing. `AlterTypeOperation` gains six variants (`OwnerTo`, `SetSchema`, `AddAttribute`, `DropAttribute`, `AlterAttribute`, `RenameAttribute`). The parser branches accept the matching grammar including optional `IF [NOT] EXISTS`, `COLLATE`, and `CASCADE | RESTRICT`. A new `Statement::AlterDefaultPrivileges` carries `for_roles`, `in_schemas`, and an `AlterDefaultPrivilegesAction { Grant | Revoke }` body that reuses `Privileges` and `Grantee`. The `ON <kind>` target uses a new `AlterDefaultPrivilegesObjectType` enum (`TABLES | SEQUENCES | FUNCTIONS | ROUTINES | TYPES | SCHEMAS`). New keywords: `ATTRIBUTE`, `ROUTINES`, `TYPES`. The `parse_alter` dispatch now accepts `DEFAULT` as the leading word. Note: both `AlterTypeOperation` and `Statement` are not `non_exhaustive`, so adding variants is technically a breaking change for downstream consumers that match exhaustively without `_`. Recommend bumping the next release to 0.61.0. Verified: cargo fmt --all cargo clippy --all-targets --all-features -- -D warnings cargo test --all-features cargo check --no-default-features --target thumbv6m-none-eabi (cd sqlparser_bench && cargo clippy --all-targets --all-features -- -D warnings) * PostgreSQL: fix ALTER TYPE ADD ATTRIBUTE grammar, Grantee PUBLIC Display Review fixes for PR #26: 1. Remove IF NOT EXISTS from AlterTypeOperation::AddAttribute. PostgreSQL's grammar for ADD ATTRIBUTE does not include IF NOT EXISTS; only ADD VALUE (for enum types) does. The parser was accepting SQL that PostgreSQL itself rejects. 2. Fix pre-existing trailing-space bug in Grantee Display: GranteesType::Public wrote "PUBLIC " (with trailing space) then fell through to the name-formatting path, yielding "PUBLIC " for bare PUBLIC grantees. Early-return with "PUBLIC" instead. Adds regression tests for GRANT ... TO PUBLIC and ALTER DEFAULT PRIVILEGES ... TO PUBLIC round-trips. 3. Document that AlterDefaultPrivileges::for_roles is keyword-agnostic: the parser accepts both FOR ROLE and FOR USER (PG synonyms) but Display canonicalises to FOR ROLE.
1 parent 4b24f9e commit 34ab7f1

8 files changed

Lines changed: 811 additions & 32 deletions

File tree

src/ast/dcl.rs

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,3 +526,167 @@ impl From<Revoke> for crate::ast::Statement {
526526
crate::ast::Statement::Revoke(v)
527527
}
528528
}
529+
530+
/// Object kinds accepted by `ALTER DEFAULT PRIVILEGES ... ON <kind>`.
531+
///
532+
/// PostgreSQL restricts this clause to a fixed set of plural kinds; the
533+
/// abbreviated grant/revoke that follows applies to *future* objects of
534+
/// that kind that are created in the affected role/schema scope.
535+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
536+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
537+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
538+
pub enum AlterDefaultPrivilegesObjectType {
539+
/// `TABLES`
540+
Tables,
541+
/// `SEQUENCES`
542+
Sequences,
543+
/// `FUNCTIONS`
544+
Functions,
545+
/// `ROUTINES`
546+
Routines,
547+
/// `TYPES`
548+
Types,
549+
/// `SCHEMAS`
550+
Schemas,
551+
}
552+
553+
impl fmt::Display for AlterDefaultPrivilegesObjectType {
554+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
555+
f.write_str(match self {
556+
AlterDefaultPrivilegesObjectType::Tables => "TABLES",
557+
AlterDefaultPrivilegesObjectType::Sequences => "SEQUENCES",
558+
AlterDefaultPrivilegesObjectType::Functions => "FUNCTIONS",
559+
AlterDefaultPrivilegesObjectType::Routines => "ROUTINES",
560+
AlterDefaultPrivilegesObjectType::Types => "TYPES",
561+
AlterDefaultPrivilegesObjectType::Schemas => "SCHEMAS",
562+
})
563+
}
564+
}
565+
566+
/// The abbreviated GRANT/REVOKE body of `ALTER DEFAULT PRIVILEGES`.
567+
///
568+
/// PostgreSQL spells out two body forms; this enum mirrors them while
569+
/// reusing [`Privileges`] and [`Grantee`] so consumers don't have to
570+
/// special-case the abbreviated syntax.
571+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
572+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
573+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
574+
pub enum AlterDefaultPrivilegesAction {
575+
/// `GRANT <privileges> ON <object_type> TO <grantees> [WITH GRANT OPTION]`
576+
Grant {
577+
/// Privileges granted (`ALL [PRIVILEGES]` or a specific action list).
578+
privileges: Privileges,
579+
/// Kind of objects the defaults apply to.
580+
object_type: AlterDefaultPrivilegesObjectType,
581+
/// Grantees receiving the default privileges.
582+
grantees: Vec<Grantee>,
583+
/// Whether `WITH GRANT OPTION` is present.
584+
with_grant_option: bool,
585+
},
586+
/// `REVOKE [GRANT OPTION FOR] <privileges> ON <object_type> FROM <grantees> [CASCADE | RESTRICT]`
587+
Revoke {
588+
/// Whether `GRANT OPTION FOR` was specified.
589+
grant_option_for: bool,
590+
/// Privileges being revoked.
591+
privileges: Privileges,
592+
/// Kind of objects the defaults apply to.
593+
object_type: AlterDefaultPrivilegesObjectType,
594+
/// Grantees affected by the revoke.
595+
grantees: Vec<Grantee>,
596+
/// Optional `CASCADE | RESTRICT` modifier.
597+
cascade: Option<CascadeOption>,
598+
},
599+
}
600+
601+
impl fmt::Display for AlterDefaultPrivilegesAction {
602+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
603+
match self {
604+
AlterDefaultPrivilegesAction::Grant {
605+
privileges,
606+
object_type,
607+
grantees,
608+
with_grant_option,
609+
} => {
610+
write!(
611+
f,
612+
"GRANT {privileges} ON {object_type} TO {grantees}",
613+
grantees = display_comma_separated(grantees),
614+
)?;
615+
if *with_grant_option {
616+
write!(f, " WITH GRANT OPTION")?;
617+
}
618+
Ok(())
619+
}
620+
AlterDefaultPrivilegesAction::Revoke {
621+
grant_option_for,
622+
privileges,
623+
object_type,
624+
grantees,
625+
cascade,
626+
} => {
627+
write!(f, "REVOKE ")?;
628+
if *grant_option_for {
629+
write!(f, "GRANT OPTION FOR ")?;
630+
}
631+
write!(
632+
f,
633+
"{privileges} ON {object_type} FROM {grantees}",
634+
grantees = display_comma_separated(grantees),
635+
)?;
636+
if let Some(cascade) = cascade {
637+
write!(f, " {cascade}")?;
638+
}
639+
Ok(())
640+
}
641+
}
642+
}
643+
}
644+
645+
/// `ALTER DEFAULT PRIVILEGES` statement.
646+
///
647+
/// See <https://www.postgresql.org/docs/current/sql-alterdefaultprivileges.html>.
648+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
649+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
650+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
651+
pub struct AlterDefaultPrivileges {
652+
/// Optional `FOR { ROLE | USER } target_role [, ...]` list.
653+
///
654+
/// This field is keyword-agnostic: the parser accepts both `FOR ROLE` and
655+
/// `FOR USER` (PostgreSQL treats them as synonyms), but the `Display` impl
656+
/// always emits the canonical `FOR ROLE` form. Downstream consumers cannot
657+
/// distinguish which keyword the user wrote.
658+
pub for_roles: Vec<Ident>,
659+
/// Optional `IN SCHEMA schema_name [, ...]` list.
660+
pub in_schemas: Vec<Ident>,
661+
/// The abbreviated GRANT or REVOKE body.
662+
pub action: AlterDefaultPrivilegesAction,
663+
}
664+
665+
impl fmt::Display for AlterDefaultPrivileges {
666+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
667+
write!(f, "ALTER DEFAULT PRIVILEGES")?;
668+
if !self.for_roles.is_empty() {
669+
write!(f, " FOR ROLE {}", display_comma_separated(&self.for_roles))?;
670+
}
671+
if !self.in_schemas.is_empty() {
672+
write!(
673+
f,
674+
" IN SCHEMA {}",
675+
display_comma_separated(&self.in_schemas)
676+
)?;
677+
}
678+
write!(f, " {}", self.action)
679+
}
680+
}
681+
682+
impl Spanned for AlterDefaultPrivileges {
683+
fn span(&self) -> Span {
684+
Span::empty()
685+
}
686+
}
687+
688+
impl From<AlterDefaultPrivileges> for crate::ast::Statement {
689+
fn from(v: AlterDefaultPrivileges) -> Self {
690+
crate::ast::Statement::AlterDefaultPrivileges(v)
691+
}
692+
}

src/ast/ddl.rs

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1144,6 +1144,82 @@ pub enum AlterTypeOperation {
11441144
AddValue(AlterTypeAddValue),
11451145
/// Rename an existing value of the type.
11461146
RenameValue(AlterTypeRenameValue),
1147+
/// Change the type owner.
1148+
///
1149+
/// ```sql
1150+
/// ALTER TYPE name OWNER TO { new_owner | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
1151+
/// ```
1152+
OwnerTo {
1153+
/// New owner specification.
1154+
new_owner: Owner,
1155+
},
1156+
/// Move the type to a new schema.
1157+
///
1158+
/// ```sql
1159+
/// ALTER TYPE name SET SCHEMA new_schema
1160+
/// ```
1161+
SetSchema {
1162+
/// Target schema name.
1163+
new_schema: ObjectName,
1164+
},
1165+
/// Add an attribute to a composite type.
1166+
///
1167+
/// ```sql
1168+
/// ALTER TYPE name ADD ATTRIBUTE attribute_name data_type
1169+
/// [COLLATE collation] [CASCADE | RESTRICT]
1170+
/// ```
1171+
AddAttribute {
1172+
/// Attribute name being added.
1173+
name: Ident,
1174+
/// Attribute data type.
1175+
data_type: DataType,
1176+
/// Optional `COLLATE` clause.
1177+
collation: Option<ObjectName>,
1178+
/// Optional `CASCADE | RESTRICT` modifier.
1179+
drop_behavior: Option<DropBehavior>,
1180+
},
1181+
/// Drop an attribute from a composite type.
1182+
///
1183+
/// ```sql
1184+
/// ALTER TYPE name DROP ATTRIBUTE [IF EXISTS] attribute_name [CASCADE | RESTRICT]
1185+
/// ```
1186+
DropAttribute {
1187+
/// Whether `IF EXISTS` was specified.
1188+
if_exists: bool,
1189+
/// Attribute being dropped.
1190+
name: Ident,
1191+
/// Optional `CASCADE | RESTRICT` modifier.
1192+
drop_behavior: Option<DropBehavior>,
1193+
},
1194+
/// Alter an attribute of a composite type.
1195+
///
1196+
/// ```sql
1197+
/// ALTER TYPE name ALTER ATTRIBUTE attribute_name [SET DATA] TYPE data_type
1198+
/// [COLLATE collation] [CASCADE | RESTRICT]
1199+
/// ```
1200+
AlterAttribute {
1201+
/// Attribute being altered.
1202+
name: Ident,
1203+
/// New attribute data type.
1204+
data_type: DataType,
1205+
/// Optional `COLLATE` clause.
1206+
collation: Option<ObjectName>,
1207+
/// Optional `CASCADE | RESTRICT` modifier.
1208+
drop_behavior: Option<DropBehavior>,
1209+
},
1210+
/// Rename an attribute of a composite type.
1211+
///
1212+
/// ```sql
1213+
/// ALTER TYPE name RENAME ATTRIBUTE old_name TO new_name [CASCADE | RESTRICT]
1214+
/// ```
1215+
RenameAttribute {
1216+
/// Existing attribute name.
1217+
old_name: Ident,
1218+
/// New attribute name.
1219+
new_name: Ident,
1220+
/// Optional `CASCADE | RESTRICT` modifier.
1221+
drop_behavior: Option<DropBehavior>,
1222+
},
11471223
}
11481224

11491225
/// See [AlterTypeOperation::Rename]
@@ -1220,6 +1296,68 @@ impl fmt::Display for AlterTypeOperation {
12201296
Self::RenameValue(AlterTypeRenameValue { from, to }) => {
12211297
write!(f, "RENAME VALUE {from} TO {to}")
12221298
}
1299+
Self::OwnerTo { new_owner } => {
1300+
write!(f, "OWNER TO {new_owner}")
1301+
}
1302+
Self::SetSchema { new_schema } => {
1303+
write!(f, "SET SCHEMA {new_schema}")
1304+
}
1305+
Self::AddAttribute {
1306+
name,
1307+
data_type,
1308+
collation,
1309+
drop_behavior,
1310+
} => {
1311+
write!(f, "ADD ATTRIBUTE {name} {data_type}")?;
1312+
if let Some(collation) = collation {
1313+
write!(f, " COLLATE {collation}")?;
1314+
}
1315+
if let Some(drop_behavior) = drop_behavior {
1316+
write!(f, " {drop_behavior}")?;
1317+
}
1318+
Ok(())
1319+
}
1320+
Self::DropAttribute {
1321+
if_exists,
1322+
name,
1323+
drop_behavior,
1324+
} => {
1325+
write!(f, "DROP ATTRIBUTE")?;
1326+
if *if_exists {
1327+
write!(f, " IF EXISTS")?;
1328+
}
1329+
write!(f, " {name}")?;
1330+
if let Some(drop_behavior) = drop_behavior {
1331+
write!(f, " {drop_behavior}")?;
1332+
}
1333+
Ok(())
1334+
}
1335+
Self::AlterAttribute {
1336+
name,
1337+
data_type,
1338+
collation,
1339+
drop_behavior,
1340+
} => {
1341+
write!(f, "ALTER ATTRIBUTE {name} SET DATA TYPE {data_type}")?;
1342+
if let Some(collation) = collation {
1343+
write!(f, " COLLATE {collation}")?;
1344+
}
1345+
if let Some(drop_behavior) = drop_behavior {
1346+
write!(f, " {drop_behavior}")?;
1347+
}
1348+
Ok(())
1349+
}
1350+
Self::RenameAttribute {
1351+
old_name,
1352+
new_name,
1353+
drop_behavior,
1354+
} => {
1355+
write!(f, "RENAME ATTRIBUTE {old_name} TO {new_name}")?;
1356+
if let Some(drop_behavior) = drop_behavior {
1357+
write!(f, " {drop_behavior}")?;
1358+
}
1359+
Ok(())
1360+
}
12231361
}
12241362
}
12251363
}

src/ast/mod.rs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ pub use self::data_type::{
5656
ExactNumberInfo, IntervalFields, StructBracketKind, TimezoneInfo,
5757
};
5858
pub use self::dcl::{
59+
AlterDefaultPrivileges, AlterDefaultPrivilegesAction, AlterDefaultPrivilegesObjectType,
5960
AlterRoleOperation, CreateRole, Grant, ResetConfig, Revoke, RoleOption, SecondaryRoles,
6061
SetConfigValue, Use,
6162
};
@@ -3823,6 +3824,14 @@ pub enum Statement {
38233824
/// See [PostgreSQL](https://www.postgresql.org/docs/current/sql-altercollation.html)
38243825
AlterCollation(AlterCollation),
38253826
/// ```sql
3827+
/// ALTER DEFAULT PRIVILEGES
3828+
/// [ FOR { ROLE | USER } target_role [, ...] ]
3829+
/// [ IN SCHEMA schema_name [, ...] ]
3830+
/// abbreviated_grant_or_revoke
3831+
/// ```
3832+
/// See [PostgreSQL](https://www.postgresql.org/docs/current/sql-alterdefaultprivileges.html)
3833+
AlterDefaultPrivileges(AlterDefaultPrivileges),
3834+
/// ```sql
38263835
/// ALTER OPERATOR
38273836
/// ```
38283837
/// See [PostgreSQL](https://www.postgresql.org/docs/current/sql-alteroperator.html)
@@ -5721,6 +5730,9 @@ impl fmt::Display for Statement {
57215730
write!(f, "ALTER TYPE {name} {operation}")
57225731
}
57235732
Statement::AlterCollation(alter_collation) => write!(f, "{alter_collation}"),
5733+
Statement::AlterDefaultPrivileges(alter_default_privileges) => {
5734+
write!(f, "{alter_default_privileges}")
5735+
}
57245736
Statement::AlterOperator(alter_operator) => write!(f, "{alter_operator}"),
57255737
Statement::AlterOperatorFamily(alter_operator_family) => {
57265738
write!(f, "{alter_operator_family}")
@@ -7574,6 +7586,9 @@ pub struct Grantee {
75747586

75757587
impl fmt::Display for Grantee {
75767588
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
7589+
if matches!(self.grantee_type, GranteesType::Public) {
7590+
return write!(f, "PUBLIC");
7591+
}
75777592
match self.grantee_type {
75787593
GranteesType::Role => {
75797594
write!(f, "ROLE ")?;
@@ -7587,9 +7602,7 @@ impl fmt::Display for Grantee {
75877602
GranteesType::Group => {
75887603
write!(f, "GROUP ")?;
75897604
}
7590-
GranteesType::Public => {
7591-
write!(f, "PUBLIC ")?;
7592-
}
7605+
GranteesType::Public => {}
75937606
GranteesType::DatabaseRole => {
75947607
write!(f, "DATABASE ROLE ")?;
75957608
}

src/ast/spans.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,7 @@ impl Spanned for Values {
270270
/// - [Statement::CreateTextSearchParser]
271271
/// - [Statement::CreateTextSearchTemplate]
272272
/// - [Statement::AlterCollation]
273+
/// - [Statement::AlterDefaultPrivileges]
273274
/// - [Statement::Fetch]
274275
/// - [Statement::Flush]
275276
/// - [Statement::Discard]
@@ -435,6 +436,7 @@ impl Spanned for Statement {
435436
Statement::AlterFunction { .. } => Span::empty(),
436437
Statement::AlterType { .. } => Span::empty(),
437438
Statement::AlterCollation { .. } => Span::empty(),
439+
Statement::AlterDefaultPrivileges { .. } => Span::empty(),
438440
Statement::AlterOperator { .. } => Span::empty(),
439441
Statement::AlterOperatorFamily { .. } => Span::empty(),
440442
Statement::AlterOperatorClass { .. } => Span::empty(),

0 commit comments

Comments
 (0)