Skip to content

Commit 3e72bb5

Browse files
committed
Alias PostgreSQL CREATE/ALTER USER to CREATE/ALTER ROLE
In PostgreSQL, `CREATE USER` and `ALTER USER` are aliases of `CREATE ROLE` and `ALTER ROLE` (with `LOGIN` defaulting to `true` for `USER`). The library rejects them with `"Expected: an object type after CREATE, found: USER"`. Our previous approach added a `RoleKeyword` discriminator field to `CreateRole` / `AlterRole`, which upstream maintainers declined. A proper upstream fix would require unifying Snowflake's `CreateUser` with PostgreSQL's, which is too large a refactor for our needs. We only need the statements to parse. Parsing them into `CreateRole` is semantically correct (they are aliases), and our downstream extractors already pattern-match on `CreateRole` for password redaction, so no AST changes or extractor changes are needed. The only observable difference is that round-tripping a parsed `CREATE USER` statement renders it back as `CREATE ROLE`, but we do not rely on round-trip fidelity anywhere (and datafusion-sqlparser-rs doesn't guarantee round-trips anyway).
1 parent 234503c commit 3e72bb5

1 file changed

Lines changed: 22 additions & 2 deletions

File tree

src/parser/mod.rs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5172,7 +5172,20 @@ impl<'a> Parser<'a> {
51725172
} else if self.parse_keyword(Keyword::SECRET) {
51735173
self.parse_create_secret(or_replace, temporary, persistent)
51745174
} else if self.parse_keyword(Keyword::USER) {
5175-
self.parse_create_user(or_replace).map(Into::into)
5175+
if dialect_of!(self is PostgreSqlDialect) {
5176+
// PostgreSQL: `CREATE USER` is an alias of `CREATE ROLE` with
5177+
// `LOGIN` defaulting to true.
5178+
self.parse_create_role()
5179+
.map(|mut role| {
5180+
if role.login.is_none() {
5181+
role.login = Some(true);
5182+
}
5183+
role
5184+
})
5185+
.map(Into::into)
5186+
} else {
5187+
self.parse_create_user(or_replace).map(Into::into)
5188+
}
51765189
} else if or_replace {
51775190
self.expected_ref(
51785191
"[EXTERNAL] TABLE or [MATERIALIZED] VIEW or FUNCTION after CREATE OR REPLACE",
@@ -10767,7 +10780,14 @@ impl<'a> Parser<'a> {
1076710780
Keyword::ROLE => self.parse_alter_role(),
1076810781
Keyword::POLICY => self.parse_alter_policy().map(Into::into),
1076910782
Keyword::CONNECTOR => self.parse_alter_connector(),
10770-
Keyword::USER => self.parse_alter_user().map(Into::into),
10783+
Keyword::USER => {
10784+
if dialect_of!(self is PostgreSqlDialect) {
10785+
// PostgreSQL: `ALTER USER` is an alias of `ALTER ROLE`.
10786+
self.parse_alter_role()
10787+
} else {
10788+
self.parse_alter_user().map(Into::into)
10789+
}
10790+
}
1077110791
// unreachable because expect_one_of_keywords used above
1077210792
unexpected_keyword => Err(ParserError::ParserError(
1077310793
format!("Internal parser error: expected any of {{VIEW, TYPE, COLLATION, TABLE, INDEX, FUNCTION, AGGREGATE, ROLE, POLICY, CONNECTOR, ICEBERG, SCHEMA, USER, OPERATOR}}, got {unexpected_keyword:?}"),

0 commit comments

Comments
 (0)