diff --git a/fuzz/fuzz_seeds/create_foreign_data_wrapper.sql b/fuzz/fuzz_seeds/create_foreign_data_wrapper.sql new file mode 100644 index 0000000000..c576d746d4 --- /dev/null +++ b/fuzz/fuzz_seeds/create_foreign_data_wrapper.sql @@ -0,0 +1 @@ +CREATE FOREIGN DATA WRAPPER postgres_fdw HANDLER postgres_fdw_handler VALIDATOR postgres_fdw_validator diff --git a/fuzz/fuzz_seeds/create_foreign_data_wrapper_no_handler.sql b/fuzz/fuzz_seeds/create_foreign_data_wrapper_no_handler.sql new file mode 100644 index 0000000000..08bd8f72fc --- /dev/null +++ b/fuzz/fuzz_seeds/create_foreign_data_wrapper_no_handler.sql @@ -0,0 +1 @@ +CREATE FOREIGN DATA WRAPPER myfdw NO HANDLER NO VALIDATOR diff --git a/fuzz/fuzz_seeds/create_foreign_data_wrapper_options.sql b/fuzz/fuzz_seeds/create_foreign_data_wrapper_options.sql new file mode 100644 index 0000000000..9dcc5226d3 --- /dev/null +++ b/fuzz/fuzz_seeds/create_foreign_data_wrapper_options.sql @@ -0,0 +1 @@ +CREATE FOREIGN DATA WRAPPER myfdw HANDLER myhandler VALIDATOR myvalidator OPTIONS (debug 'true') diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index 5d5b964d17..019be34495 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -6090,6 +6090,62 @@ impl fmt::Display for CreateForeignTable { } } +/// A handler or validator clause of a `CREATE FOREIGN DATA WRAPPER` statement. +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub enum ForeignDataWrapperRoutineClause { + /// `HANDLER handler_function` + Handler(ObjectName), + /// `NO HANDLER` + NoHandler, + /// `VALIDATOR validator_function` + Validator(ObjectName), + /// `NO VALIDATOR` + NoValidator, +} + +impl fmt::Display for ForeignDataWrapperRoutineClause { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ForeignDataWrapperRoutineClause::Handler(name) => write!(f, "HANDLER {name}"), + ForeignDataWrapperRoutineClause::NoHandler => f.write_str("NO HANDLER"), + ForeignDataWrapperRoutineClause::Validator(name) => write!(f, "VALIDATOR {name}"), + ForeignDataWrapperRoutineClause::NoValidator => f.write_str("NO VALIDATOR"), + } + } +} + +/// A `CREATE FOREIGN DATA WRAPPER` statement. +/// +/// See [PostgreSQL](https://www.postgresql.org/docs/current/sql-createforeigndatawrapper.html) +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub struct CreateForeignDataWrapper { + /// The name of the foreign-data wrapper. Per PostgreSQL this is a bare + /// identifier and cannot be schema-qualified. + pub name: Ident, + /// `HANDLER` and `VALIDATOR` clauses in source order. PostgreSQL's grammar + /// accepts repeated clauses and rejects them only when executing the statement. + pub routines: Vec, + /// Optional `OPTIONS (key 'value', ...)` clause. + pub options: Option>, +} + +impl fmt::Display for CreateForeignDataWrapper { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "CREATE FOREIGN DATA WRAPPER {}", self.name)?; + for routine in &self.routines { + write!(f, " {routine}")?; + } + if let Some(options) = &self.options { + write!(f, " OPTIONS ({})", display_comma_separated(options))?; + } + Ok(()) + } +} + impl From for crate::ast::Statement { fn from(v: CreateForeignTable) -> Self { crate::ast::Statement::CreateForeignTable(v) @@ -6112,3 +6168,28 @@ impl Spanned for CreateForeignTable { ) } } + +impl From for crate::ast::Statement { + fn from(v: CreateForeignDataWrapper) -> Self { + crate::ast::Statement::CreateForeignDataWrapper(v) + } +} + +impl Spanned for CreateForeignDataWrapper { + fn span(&self) -> Span { + Span::union_iter( + core::iter::once(self.name.span) + .chain(self.routines.iter().filter_map(|routine| match routine { + ForeignDataWrapperRoutineClause::Handler(name) + | ForeignDataWrapperRoutineClause::Validator(name) => Some(name.span()), + _ => None, + })) + .chain( + self.options + .iter() + .flatten() + .flat_map(|option| [option.key.span, option.value.span]), + ), + ) + } +} diff --git a/src/ast/mod.rs b/src/ast/mod.rs index f38ff0f17a..19bd069657 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -72,12 +72,13 @@ pub use self::ddl::{ AlterTypeOperation, AlterTypeRename, AlterTypeRenameValue, ClusteredBy, ColumnDef, ColumnOption, ColumnOptionDef, ColumnOptions, ColumnPolicy, ColumnPolicyProperty, ConstraintCharacteristics, CreateCollation, CreateCollationDefinition, CreateConnector, - CreateDomain, CreateExtension, CreateForeignTable, CreateFunction, CreateIndex, CreateOperator, - CreateOperatorClass, CreateOperatorFamily, CreatePolicy, CreatePolicyCommand, CreatePolicyType, - CreateTable, CreateTextSearch, CreateTrigger, CreateView, Deduplicate, DeferrableInitial, - DistStyle, DropBehavior, DropExtension, DropFunction, DropOperator, DropOperatorClass, - DropOperatorFamily, DropOperatorSignature, DropPolicy, DropTrigger, ForValues, - FunctionReturnType, GeneratedAs, GeneratedExpressionMode, IdentityParameters, IdentityProperty, + CreateDomain, CreateExtension, CreateForeignDataWrapper, CreateForeignTable, CreateFunction, + CreateIndex, CreateOperator, CreateOperatorClass, CreateOperatorFamily, CreatePolicy, + CreatePolicyCommand, CreatePolicyType, CreateTable, CreateTextSearch, CreateTrigger, + CreateView, Deduplicate, DeferrableInitial, DistStyle, DropBehavior, DropExtension, + DropFunction, DropOperator, DropOperatorClass, DropOperatorFamily, DropOperatorSignature, + DropPolicy, DropTrigger, ForValues, ForeignDataWrapperRoutineClause, FunctionReturnType, + GeneratedAs, GeneratedExpressionMode, IdentityParameters, IdentityProperty, IdentityPropertyFormatKind, IdentityPropertyKind, IdentityPropertyOrder, IndexColumn, IndexOption, IndexType, KeyOrIndexDisplay, Msck, NullsDistinctOption, OperatorArgTypes, OperatorClassItem, OperatorFamilyDropItem, OperatorFamilyItem, OperatorOption, OperatorPurpose, @@ -3821,6 +3822,11 @@ pub enum Statement { /// See [PostgreSQL](https://www.postgresql.org/docs/current/sql-createforeigntable.html) CreateForeignTable(CreateForeignTable), /// ```sql + /// CREATE FOREIGN DATA WRAPPER + /// ``` + /// See [PostgreSQL](https://www.postgresql.org/docs/current/sql-createforeigndatawrapper.html) + CreateForeignDataWrapper(CreateForeignDataWrapper), + /// ```sql /// CREATE POLICY /// ``` /// See [PostgreSQL](https://www.postgresql.org/docs/current/sql-createpolicy.html) @@ -5684,6 +5690,7 @@ impl fmt::Display for Statement { write!(f, "{stmt}") } Statement::CreateForeignTable(stmt) => write!(f, "{stmt}"), + Statement::CreateForeignDataWrapper(stmt) => write!(f, "{stmt}"), Statement::CreatePolicy(policy) => write!(f, "{policy}"), Statement::CreateConnector(create_connector) => create_connector.fmt(f), Statement::CreateOperator(create_operator) => create_operator.fmt(f), diff --git a/src/ast/spans.rs b/src/ast/spans.rs index eab9f71a37..93d18b3431 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -395,6 +395,7 @@ impl Spanned for Statement { Statement::CreateSecret { .. } => Span::empty(), Statement::CreateServer { .. } => Span::empty(), Statement::CreateForeignTable(stmt) => stmt.span(), + Statement::CreateForeignDataWrapper(stmt) => stmt.span(), Statement::CreateConnector { .. } => Span::empty(), Statement::CreateOperator(create_operator) => create_operator.span(), Statement::CreateOperatorFamily(create_operator_family) => { diff --git a/src/keywords.rs b/src/keywords.rs index 7e93822416..3982762172 100644 --- a/src/keywords.rs +++ b/src/keywords.rs @@ -485,6 +485,7 @@ define_keywords!( GROUPING, GROUPS, GZIP, + HANDLER, HASH, HASHES, HAVING, @@ -1146,6 +1147,7 @@ define_keywords!( VALID, VALIDATE, VALIDATION_MODE, + VALIDATOR, VALUE, VALUES, VALUE_OF, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index db54296fce..2fc6379d7c 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -5371,6 +5371,16 @@ impl<'a> Parser<'a> { let persistent = dialect_of!(self is DuckDbDialect) && self.parse_one_of_keywords(&[Keyword::PERSISTENT]).is_some(); let create_view_params = self.parse_create_view_params()?; + // `or_replace` is caught by an earlier arm. It stays so that reordering cannot bypass it. + let has_modifier = or_replace + || or_alter + || temporary + || global.is_some() + || transient + || volatile + || multiset.is_some() + || persistent + || create_view_params.is_some(); if self.peek_keywords(&[Keyword::SNAPSHOT, Keyword::TABLE]) { self.parse_create_snapshot_table().map(Into::into) } else if self.peek_keywords(&[Keyword::TEXT, Keyword::SEARCH]) { @@ -5452,24 +5462,21 @@ impl<'a> Parser<'a> { } else if self.parse_keyword(Keyword::SERVER) { self.parse_pg_create_server() } else if self.parse_keywords(&[Keyword::FOREIGN, Keyword::TABLE]) { - // `or_replace` cannot reach here today, since the arm above catches it. - // It stays so that reordering the arms cannot make it fall through. - if or_replace - || or_alter - || temporary - || global.is_some() - || transient - || volatile - || multiset.is_some() - || persistent - || create_view_params.is_some() - { + if has_modifier { return parser_err!( "CREATE FOREIGN TABLE does not accept this modifier", modifier_loc ); } self.parse_create_foreign_table().map(Into::into) + } else if self.parse_keywords(&[Keyword::FOREIGN, Keyword::DATA, Keyword::WRAPPER]) { + if has_modifier { + return parser_err!( + "CREATE FOREIGN DATA WRAPPER does not accept this modifier", + modifier_loc + ); + } + self.parse_create_foreign_data_wrapper().map(Into::into) } else { self.expected_ref("an object type after CREATE", self.peek_token_ref()) } @@ -20800,6 +20807,39 @@ impl<'a> Parser<'a> { }) } + /// Parse a `CREATE FOREIGN DATA WRAPPER` statement. + /// + /// See + pub fn parse_create_foreign_data_wrapper( + &mut self, + ) -> Result { + let name = self.parse_identifier()?; + + let mut routines = Vec::new(); + loop { + let routine = if self.parse_keyword(Keyword::HANDLER) { + ForeignDataWrapperRoutineClause::Handler(self.parse_object_name(false)?) + } else if self.parse_keywords(&[Keyword::NO, Keyword::HANDLER]) { + ForeignDataWrapperRoutineClause::NoHandler + } else if self.parse_keyword(Keyword::VALIDATOR) { + ForeignDataWrapperRoutineClause::Validator(self.parse_object_name(false)?) + } else if self.parse_keywords(&[Keyword::NO, Keyword::VALIDATOR]) { + ForeignDataWrapperRoutineClause::NoValidator + } else { + break; + }; + routines.push(routine); + } + + let options = self.parse_pg_options_clause()?; + + Ok(CreateForeignDataWrapper { + name, + routines, + options, + }) + } + /// The index of the first unprocessed token. pub fn index(&self) -> usize { self.index diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 1e00288673..f6c5c84078 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -10143,6 +10143,7 @@ fn parse_bitstring_literal_escaping() { } #[test] + fn parse_join_using_alias() { let sql = "SELECT * FROM t1 JOIN t2 USING (id) AS joined_cols"; let dialect = PostgreSqlDialect {}; @@ -10202,3 +10203,143 @@ fn parse_string_literal_alias_rejected() { } pg().verified_stmt(r#"SELECT a AS "x" FROM t AS "y""#); } + +#[test] +fn parse_create_foreign_data_wrapper() { + let sql = "CREATE FOREIGN DATA WRAPPER myfdw"; + let Statement::CreateForeignDataWrapper(stmt) = pg_and_generic().verified_stmt(sql) else { + unreachable!() + }; + assert_eq!(stmt.name.to_string(), "myfdw"); + assert!(stmt.routines.is_empty()); + assert!(stmt.options.is_none()); + + let sql = "CREATE FOREIGN DATA WRAPPER myfdw HANDLER myhandler"; + let Statement::CreateForeignDataWrapper(stmt) = pg_and_generic().verified_stmt(sql) else { + unreachable!() + }; + assert_eq!( + stmt.routines, + vec![ForeignDataWrapperRoutineClause::Handler(ObjectName::from( + vec!["myhandler".into()] + ))] + ); + + let sql = "CREATE FOREIGN DATA WRAPPER myfdw NO HANDLER"; + let Statement::CreateForeignDataWrapper(stmt) = pg_and_generic().verified_stmt(sql) else { + unreachable!() + }; + assert_eq!( + stmt.routines, + vec![ForeignDataWrapperRoutineClause::NoHandler] + ); + + let sql = "CREATE FOREIGN DATA WRAPPER myfdw NO VALIDATOR"; + let Statement::CreateForeignDataWrapper(stmt) = pg_and_generic().verified_stmt(sql) else { + unreachable!() + }; + assert_eq!( + stmt.routines, + vec![ForeignDataWrapperRoutineClause::NoValidator] + ); + + let sql = "CREATE FOREIGN DATA WRAPPER myfdw HANDLER myhandler VALIDATOR myvalidator OPTIONS (debug 'true')"; + let Statement::CreateForeignDataWrapper(stmt) = pg_and_generic().verified_stmt(sql) else { + unreachable!() + }; + assert_eq!( + stmt.options, + Some(vec![CreateServerOption { + key: "debug".into(), + value: Ident { + value: "true".to_string(), + quote_style: Some('\''), + span: Span::empty(), + }, + }]) + ); + + let sql = "CREATE FOREIGN DATA WRAPPER myfdw NO HANDLER NO VALIDATOR"; + let Statement::CreateForeignDataWrapper(stmt) = pg_and_generic().verified_stmt(sql) else { + unreachable!() + }; + assert_eq!( + stmt.routines, + vec![ + ForeignDataWrapperRoutineClause::NoHandler, + ForeignDataWrapperRoutineClause::NoValidator + ] + ); + + assert_eq!( + pg_and_generic().parse_sql_statements("CREATE FOREIGN DATA WRAPPER myschema.myfdw"), + Err(ParserError::ParserError( + "Expected: end of statement, found: .".to_string() + )) + ); + + for sql in [ + "CREATE TEMPORARY FOREIGN DATA WRAPPER myfdw", + "CREATE OR ALTER FOREIGN DATA WRAPPER myfdw", + "CREATE GLOBAL TEMPORARY FOREIGN DATA WRAPPER myfdw", + ] { + let err = pg_and_generic().parse_sql_statements(sql).unwrap_err(); + assert!( + err.to_string() + .contains("CREATE FOREIGN DATA WRAPPER does not accept this modifier"), + "unexpected error for {sql}: {err}" + ); + } +} + +#[test] +fn foreign_data_wrapper_display_and_span() { + let ident = |value: &str, start: u64, end: u64| Ident { + value: value.to_string(), + quote_style: None, + span: Span::new(Location::new(1, start), Location::new(1, end)), + }; + let stmt = Statement::CreateForeignDataWrapper(CreateForeignDataWrapper { + name: ident("myfdw", 29, 34), + routines: vec![ + ForeignDataWrapperRoutineClause::Handler(ObjectName::from(ident("myhandler", 43, 52))), + ForeignDataWrapperRoutineClause::NoHandler, + ForeignDataWrapperRoutineClause::Validator(ObjectName::from(ident( + "myvalidator", + 74, + 85, + ))), + ForeignDataWrapperRoutineClause::NoValidator, + ], + options: Some(vec![CreateServerOption { + key: ident("debug", 108, 113), + value: Ident { + value: "true".to_string(), + quote_style: Some('\''), + span: Span::new(Location::new(1, 114), Location::new(1, 120)), + }, + }]), + }); + + assert_eq!( + stmt.to_string(), + "CREATE FOREIGN DATA WRAPPER myfdw HANDLER myhandler NO HANDLER VALIDATOR myvalidator NO VALIDATOR OPTIONS (debug 'true')" + ); + assert_eq!( + stmt.span(), + Span::new(Location::new(1, 29), Location::new(1, 120)) + ); +} + +#[test] +fn parse_create_foreign_data_wrapper_clause_order() { + for sql in [ + "CREATE FOREIGN DATA WRAPPER myfdw VALIDATOR myvalidator HANDLER myhandler", + "CREATE FOREIGN DATA WRAPPER myfdw NO VALIDATOR NO HANDLER", + "CREATE FOREIGN DATA WRAPPER myfdw HANDLER h1 HANDLER h2", + "CREATE FOREIGN DATA WRAPPER myfdw NO HANDLER HANDLER h", + "CREATE FOREIGN DATA WRAPPER myfdw VALIDATOR v NO VALIDATOR", + ] { + pg_and_generic().verified_stmt(sql); + } +}