Skip to content

Commit 2d9629f

Browse files
PostgreSQL: Support CREATE USER MAPPING (#2751)
1 parent a368259 commit 2d9629f

7 files changed

Lines changed: 291 additions & 11 deletions

File tree

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
CREATE USER MAPPING IF NOT EXISTS FOR app SERVER s OPTIONS ("user" 'remote', password 'secret');
2+
CREATE USER MAPPING FOR PUBLIC SERVER s

‎src/ast/ddl.rs‎

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6649,3 +6649,78 @@ impl Spanned for CreateForeignDataWrapper {
66496649
)
66506650
}
66516651
}
6652+
6653+
/// The user of a `CREATE USER MAPPING` statement.
6654+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
6655+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
6656+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
6657+
pub enum UserMappingUser {
6658+
/// A role name.
6659+
Name(Ident),
6660+
/// `USER`, the current user.
6661+
User,
6662+
/// `CURRENT_ROLE`
6663+
CurrentRole,
6664+
/// `CURRENT_USER`
6665+
CurrentUser,
6666+
/// `SESSION_USER`
6667+
SessionUser,
6668+
/// `PUBLIC`, every role without a mapping of its own.
6669+
Public,
6670+
}
6671+
6672+
impl fmt::Display for UserMappingUser {
6673+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
6674+
match self {
6675+
UserMappingUser::Name(name) => write!(f, "{name}"),
6676+
UserMappingUser::User => f.write_str("USER"),
6677+
UserMappingUser::CurrentRole => f.write_str("CURRENT_ROLE"),
6678+
UserMappingUser::CurrentUser => f.write_str("CURRENT_USER"),
6679+
UserMappingUser::SessionUser => f.write_str("SESSION_USER"),
6680+
UserMappingUser::Public => f.write_str("PUBLIC"),
6681+
}
6682+
}
6683+
}
6684+
6685+
/// A `CREATE USER MAPPING` statement.
6686+
///
6687+
/// See [PostgreSQL](https://www.postgresql.org/docs/current/sql-createusermapping.html)
6688+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
6689+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
6690+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
6691+
pub struct CreateUserMapping {
6692+
/// Whether `IF NOT EXISTS` was specified.
6693+
pub if_not_exists: bool,
6694+
/// The user the mapping is for.
6695+
pub user: UserMappingUser,
6696+
/// The `SERVER server_name` clause.
6697+
pub server_name: Ident,
6698+
/// Optional `OPTIONS (key 'value', ...)` clause.
6699+
pub options: Option<Vec<CreateServerOption>>,
6700+
}
6701+
6702+
impl fmt::Display for CreateUserMapping {
6703+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
6704+
write!(
6705+
f,
6706+
"CREATE USER MAPPING {if_not_exists}FOR {user} SERVER {server_name}",
6707+
if_not_exists = if self.if_not_exists {
6708+
"IF NOT EXISTS "
6709+
} else {
6710+
""
6711+
},
6712+
user = self.user,
6713+
server_name = self.server_name,
6714+
)?;
6715+
if let Some(options) = &self.options {
6716+
write!(f, " OPTIONS ({})", display_comma_separated(options))?;
6717+
}
6718+
Ok(())
6719+
}
6720+
}
6721+
6722+
impl From<CreateUserMapping> for crate::ast::Statement {
6723+
fn from(v: CreateUserMapping) -> Self {
6724+
crate::ast::Statement::CreateUserMapping(v)
6725+
}
6726+
}

‎src/ast/mod.rs‎

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,8 @@ pub use self::ddl::{
7878
CreateForeignDataWrapper, CreateForeignTable, CreateFunction, CreateIndex, CreateOperator,
7979
CreateOperatorClass, CreateOperatorFamily, CreatePolicy, CreatePolicyCommand, CreatePolicyType,
8080
CreateRule, CreateRuleAction, CreateRuleDoKind, CreateRuleEvent, CreateTable, CreateTextSearch,
81-
CreateTrigger, CreateView, Deduplicate, DeferrableInitial, DistStyle, DropBehavior,
82-
DropExtension, DropFunction, DropOperator, DropOperatorClass, DropOperatorFamily,
81+
CreateTrigger, CreateUserMapping, CreateView, Deduplicate, DeferrableInitial, DistStyle,
82+
DropBehavior, DropExtension, DropFunction, DropOperator, DropOperatorClass, DropOperatorFamily,
8383
DropOperatorSignature, DropPolicy, DropTrigger, ForValues, ForeignDataWrapperRoutineClause,
8484
FunctionReturnType, GeneratedAs, GeneratedExpressionMode, IdentityParameters, IdentityProperty,
8585
IdentityPropertyFormatKind, IdentityPropertyKind, IdentityPropertyOrder, IndexColumn,
@@ -89,7 +89,8 @@ pub use self::ddl::{
8989
ReplicaIdentity, SqliteTableOption, SqliteTableOptions, TagsColumnOption, TextSearchObjectType,
9090
TriggerObjectKind, Truncate, UserDefinedTypeCompositeAttributeDef,
9191
UserDefinedTypeInternalLength, UserDefinedTypeRangeOption, UserDefinedTypeRepresentation,
92-
UserDefinedTypeSqlDefinitionOption, UserDefinedTypeStorage, ViewColumnDef, WithData,
92+
UserDefinedTypeSqlDefinitionOption, UserDefinedTypeStorage, UserMappingUser, ViewColumnDef,
93+
WithData,
9394
};
9495
pub use self::dml::{
9596
Delete, Insert, InsertOverriding, Merge, MergeAction, MergeClause, MergeClauseKind,
@@ -3847,6 +3848,11 @@ pub enum Statement {
38473848
/// See [PostgreSQL](https://www.postgresql.org/docs/current/sql-createforeigndatawrapper.html)
38483849
CreateForeignDataWrapper(CreateForeignDataWrapper),
38493850
/// ```sql
3851+
/// CREATE USER MAPPING
3852+
/// ```
3853+
/// See [PostgreSQL](https://www.postgresql.org/docs/current/sql-createusermapping.html)
3854+
CreateUserMapping(CreateUserMapping),
3855+
/// ```sql
38503856
/// CREATE POLICY
38513857
/// ```
38523858
/// See [PostgreSQL](https://www.postgresql.org/docs/current/sql-createpolicy.html)
@@ -5729,6 +5735,7 @@ impl fmt::Display for Statement {
57295735
}
57305736
Statement::CreateForeignTable(stmt) => write!(f, "{stmt}"),
57315737
Statement::CreateForeignDataWrapper(stmt) => write!(f, "{stmt}"),
5738+
Statement::CreateUserMapping(stmt) => write!(f, "{stmt}"),
57325739
Statement::CreatePolicy(policy) => write!(f, "{policy}"),
57335740
Statement::CreateRule(rule) => write!(f, "{rule}"),
57345741
Statement::CreateConnector(create_connector) => create_connector.fmt(f),

‎src/ast/spans.rs‎

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,12 @@ use super::{
3333
Assignment, AssignmentTarget, AttachedToken, BeginEndStatements, CaseStatement, CloseCursor,
3434
ClusteredIndex, ColumnDef, ColumnOption, ColumnOptionDef, ConditionalStatementBlock,
3535
ConditionalStatements, ConflictTarget, ConnectByKind, ConstraintCharacteristics, CopySource,
36-
CreateIndex, CreateRule, CreateRuleAction, CreateTable, CreateTableOptions, Cte, Delete,
37-
DoUpdate, ExceptSelectItem, ExcludeConstraintElement, ExcludeSelectItem, Expr, ExprWithAlias,
38-
Fetch, ForValues, FromTable, Function, FunctionArg, FunctionArgExpr, FunctionArgumentClause,
39-
FunctionArgumentList, FunctionArguments, GroupByExpr, HavingBound, IfStatement,
40-
IlikeSelectItem, InTarget, IndexColumn, Insert, Interpolate, InterpolateExpr, Join,
41-
JoinConstraint, JoinOperator, JsonPath, JsonPathElem, LateralView, LimitClause,
36+
CreateIndex, CreateRule, CreateRuleAction, CreateTable, CreateTableOptions, CreateUserMapping,
37+
Cte, Delete, DoUpdate, ExceptSelectItem, ExcludeConstraintElement, ExcludeSelectItem, Expr,
38+
ExprWithAlias, Fetch, ForValues, FromTable, Function, FunctionArg, FunctionArgExpr,
39+
FunctionArgumentClause, FunctionArgumentList, FunctionArguments, GroupByExpr, HavingBound,
40+
IfStatement, IlikeSelectItem, InTarget, IndexColumn, Insert, Interpolate, InterpolateExpr,
41+
Join, JoinConstraint, JoinOperator, JsonPath, JsonPathElem, LateralView, LimitClause,
4242
MatchRecognizePattern, Measure, Merge, MergeAction, MergeClause, MergeInsertExpr,
4343
MergeInsertKind, MergeUpdateExpr, MergeUpdateKind, NamedParenthesizedList,
4444
NamedWindowDefinition, ObjectName, ObjectNamePart, Offset, OnConflict, OnConflictAction,
@@ -47,8 +47,9 @@ use super::{
4747
RaiseStatement, RaiseStatementValue, ReferentialAction, RenameSelectItem, ReplaceSelectElement,
4848
ReplaceSelectItem, Select, SelectInto, SelectItem, SetExpr, Setting, SqlOption, Statement,
4949
Subscript, SymbolDefinition, TableAlias, TableAliasColumnDef, TableConstraint, TableFactor,
50-
TableObject, TableOptionsClustered, TableWithJoins, Update, UpdateTableFromKind, Use, Values,
51-
ViewColumnDef, WhileStatement, WildcardAdditionalOptions, With, WithFill,
50+
TableObject, TableOptionsClustered, TableWithJoins, Update, UpdateTableFromKind, Use,
51+
UserMappingUser, Values, ViewColumnDef, WhileStatement, WildcardAdditionalOptions, With,
52+
WithFill,
5253
};
5354

5455
/// Given an iterator of spans, return the [Span::union] of all spans.
@@ -399,6 +400,7 @@ impl Spanned for Statement {
399400
Statement::CreateServer { .. } => Span::empty(),
400401
Statement::CreateForeignTable(stmt) => stmt.span(),
401402
Statement::CreateForeignDataWrapper(stmt) => stmt.span(),
403+
Statement::CreateUserMapping(stmt) => stmt.span(),
402404
Statement::CreateConnector { .. } => Span::empty(),
403405
Statement::CreateOperator(create_operator) => create_operator.span(),
404406
Statement::CreateOperatorFamily(create_operator_family) => {
@@ -2777,6 +2779,23 @@ impl Spanned for comments::CommentWithSpan {
27772779
}
27782780
}
27792781

2782+
impl Spanned for CreateUserMapping {
2783+
fn span(&self) -> Span {
2784+
let user = match &self.user {
2785+
UserMappingUser::Name(name) => name.span,
2786+
_ => Span::empty(),
2787+
};
2788+
Span::union_iter(
2789+
[user, self.server_name.span].into_iter().chain(
2790+
self.options
2791+
.iter()
2792+
.flatten()
2793+
.flat_map(|option| [option.key.span, option.value.span]),
2794+
),
2795+
)
2796+
}
2797+
}
2798+
27802799
#[cfg(test)]
27812800
pub mod tests {
27822801
use crate::ast::Value;

‎src/keywords.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -634,6 +634,7 @@ define_keywords!(
634634
MANAGEDLOCATION,
635635
MANIFEST,
636636
MAP,
637+
MAPPING,
637638
MASKING,
638639
MATCH,
639640
MATCHED,

‎src/parser/mod.rs‎

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5520,6 +5520,24 @@ impl<'a> Parser<'a> {
55205520
self.parse_create_macro(or_replace, temporary)
55215521
} else if self.parse_keyword(Keyword::SECRET) {
55225522
self.parse_create_secret(or_replace, temporary, persistent)
5523+
} else if self.peek_keywords(&[Keyword::USER, Keyword::MAPPING, Keyword::FOR])
5524+
|| self.peek_keywords(&[
5525+
Keyword::USER,
5526+
Keyword::MAPPING,
5527+
Keyword::IF,
5528+
Keyword::NOT,
5529+
Keyword::EXISTS,
5530+
])
5531+
{
5532+
if has_modifier {
5533+
return parser_err!(
5534+
"CREATE USER MAPPING does not accept this modifier",
5535+
modifier_loc
5536+
);
5537+
}
5538+
self.advance_token(); // USER
5539+
self.advance_token(); // MAPPING
5540+
self.parse_create_user_mapping().map(Into::into)
55235541
} else if self.parse_keyword(Keyword::USER) {
55245542
self.parse_create_user(or_replace).map(Into::into)
55255543
} else if self.parse_keyword(Keyword::AGGREGATE) {
@@ -21458,6 +21476,38 @@ impl<'a> Parser<'a> {
2145821476
})
2145921477
}
2146021478

21479+
/// Parse a `CREATE USER MAPPING` statement, after `CREATE USER MAPPING`.
21480+
///
21481+
/// See <https://www.postgresql.org/docs/current/sql-createusermapping.html>
21482+
pub fn parse_create_user_mapping(&mut self) -> Result<CreateUserMapping, ParserError> {
21483+
let if_not_exists = self.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]);
21484+
self.expect_keyword_is(Keyword::FOR)?;
21485+
let user = match self.parse_one_of_keywords(&[
21486+
Keyword::USER,
21487+
Keyword::CURRENT_ROLE,
21488+
Keyword::CURRENT_USER,
21489+
Keyword::SESSION_USER,
21490+
Keyword::PUBLIC,
21491+
]) {
21492+
Some(Keyword::USER) => UserMappingUser::User,
21493+
Some(Keyword::CURRENT_ROLE) => UserMappingUser::CurrentRole,
21494+
Some(Keyword::CURRENT_USER) => UserMappingUser::CurrentUser,
21495+
Some(Keyword::SESSION_USER) => UserMappingUser::SessionUser,
21496+
Some(Keyword::PUBLIC) => UserMappingUser::Public,
21497+
_ => UserMappingUser::Name(self.parse_identifier()?),
21498+
};
21499+
self.expect_keyword_is(Keyword::SERVER)?;
21500+
let server_name = self.parse_identifier()?;
21501+
let options = self.parse_pg_options_clause()?;
21502+
21503+
Ok(CreateUserMapping {
21504+
if_not_exists,
21505+
user,
21506+
server_name,
21507+
options,
21508+
})
21509+
}
21510+
2146121511
/// The index of the first unprocessed token.
2146221512
pub fn index(&self) -> usize {
2146321513
self.index

‎tests/sqlparser_postgres.rs‎

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12004,3 +12004,129 @@ fn display_table_inheritance_propagates_write_errors() {
1200412004
assert_eq!(write!(writer, "{stmt}"), Err(fmt::Error), "{sql}");
1200512005
}
1200612006
}
12007+
12008+
#[test]
12009+
fn parse_create_user_mapping() {
12010+
let sql =
12011+
r#"CREATE USER MAPPING FOR app SERVER s OPTIONS ("user" 'remote', password 'secret')"#;
12012+
let Statement::CreateUserMapping(stmt) = pg_and_generic().verified_stmt(sql) else {
12013+
unreachable!()
12014+
};
12015+
assert_eq!(
12016+
stmt,
12017+
CreateUserMapping {
12018+
if_not_exists: false,
12019+
user: UserMappingUser::Name(Ident::new("app")),
12020+
server_name: Ident::new("s"),
12021+
options: Some(vec![
12022+
CreateServerOption {
12023+
key: Ident::with_quote('"', "user"),
12024+
value: Ident::with_quote('\'', "remote"),
12025+
},
12026+
CreateServerOption {
12027+
key: Ident::new("password"),
12028+
value: Ident::with_quote('\'', "secret"),
12029+
},
12030+
]),
12031+
}
12032+
);
12033+
12034+
for (sql, user) in [
12035+
(
12036+
"CREATE USER MAPPING IF NOT EXISTS FOR USER SERVER s",
12037+
UserMappingUser::User,
12038+
),
12039+
(
12040+
"CREATE USER MAPPING FOR CURRENT_ROLE SERVER s",
12041+
UserMappingUser::CurrentRole,
12042+
),
12043+
(
12044+
"CREATE USER MAPPING FOR CURRENT_USER SERVER s",
12045+
UserMappingUser::CurrentUser,
12046+
),
12047+
(
12048+
"CREATE USER MAPPING FOR SESSION_USER SERVER s",
12049+
UserMappingUser::SessionUser,
12050+
),
12051+
(
12052+
"CREATE USER MAPPING FOR PUBLIC SERVER s",
12053+
UserMappingUser::Public,
12054+
),
12055+
] {
12056+
let Statement::CreateUserMapping(stmt) = pg_and_generic().verified_stmt(sql) else {
12057+
unreachable!()
12058+
};
12059+
assert_eq!(stmt.user, user);
12060+
}
12061+
// As written by pg_dump.
12062+
pg_and_generic().one_statement_parses_to(
12063+
"CREATE USER MAPPING FOR public SERVER s",
12064+
"CREATE USER MAPPING FOR PUBLIC SERVER s",
12065+
);
12066+
12067+
// A user named `mapping`.
12068+
assert!(matches!(
12069+
pg_and_generic().verified_stmt("CREATE USER mapping"),
12070+
Statement::CreateUser(_)
12071+
));
12072+
12073+
for (sql, expected) in [
12074+
(
12075+
"CREATE USER MAPPING FOR app",
12076+
"Expected: SERVER, found: EOF",
12077+
),
12078+
(
12079+
"CREATE USER MAPPING FOR",
12080+
"Expected: identifier, found: EOF",
12081+
),
12082+
(
12083+
"CREATE USER MAPPING FOR app SERVER",
12084+
"Expected: identifier, found: EOF",
12085+
),
12086+
(
12087+
"CREATE USER MAPPING IF NOT EXISTS app SERVER s",
12088+
"Expected: FOR, found: app",
12089+
),
12090+
(
12091+
"CREATE USER MAPPING FOR app SERVER s OPTIONS (password)",
12092+
"Expected: identifier, found: )",
12093+
),
12094+
(
12095+
"CREATE OR REPLACE USER MAPPING FOR app SERVER s",
12096+
"CREATE USER MAPPING does not accept this modifier",
12097+
),
12098+
] {
12099+
assert_eq!(
12100+
pg_and_generic()
12101+
.parse_sql_statements(sql)
12102+
.unwrap_err()
12103+
.to_string(),
12104+
format!("sql parser error: {expected}"),
12105+
"{sql}"
12106+
);
12107+
}
12108+
}
12109+
12110+
#[test]
12111+
fn display_create_user_mapping_propagates_formatter_errors() {
12112+
use core::fmt::{self, Write};
12113+
12114+
struct RejectingWriter(&'static str);
12115+
12116+
impl Write for RejectingWriter {
12117+
fn write_str(&mut self, value: &str) -> fmt::Result {
12118+
if value == self.0 {
12119+
Err(fmt::Error)
12120+
} else {
12121+
Ok(())
12122+
}
12123+
}
12124+
}
12125+
12126+
let stmt = pg_and_generic()
12127+
.verified_stmt("CREATE USER MAPPING FOR app SERVER s OPTIONS (password 'secret')");
12128+
for rejected in ["CREATE USER MAPPING ", " OPTIONS ("] {
12129+
let mut writer = RejectingWriter(rejected);
12130+
assert_eq!(write!(writer, "{stmt}"), Err(fmt::Error), "{rejected:?}");
12131+
}
12132+
}

0 commit comments

Comments
 (0)