LucaCappelletti94 commented on code in PR #2317:
URL: 
https://github.com/apache/datafusion-sqlparser-rs/pull/2317#discussion_r4294232999


##########
src/ast/ddl.rs:
##########
@@ -6056,6 +6056,60 @@ impl fmt::Display for CreateForeignTable {
     }
 }
 
+/// The handler/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 {
+    /// A named function, e.g. `HANDLER myhandler` or `VALIDATOR myvalidator`.
+    Function(ObjectName),
+    /// The `NO HANDLER` / `NO VALIDATOR` form.
+    Absent,
+}
+
+impl ForeignDataWrapperRoutineClause {
+    fn fmt_with_label(&self, f: &mut fmt::Formatter<'_>, label: &str) -> 
fmt::Result {
+        match self {
+            ForeignDataWrapperRoutineClause::Function(name) => write!(f, " 
{label} {name}"),
+            ForeignDataWrapperRoutineClause::Absent => write!(f, " NO 
{label}"),
+        }
+    }
+}

Review Comment:
   You should make each clause a variant, mirroring the four `fdw_option` 
productions, so a list can hold repeats and either order.
   
   ```suggestion
   /// 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"),
           }
       }
   }
   ```



##########
src/parser/mod.rs:
##########
@@ -20756,6 +20763,53 @@ impl<'a> Parser<'a> {
         })
     }
 
+    /// Parse a `CREATE FOREIGN DATA WRAPPER` statement.
+    ///
+    /// See 
<https://www.postgresql.org/docs/current/sql-createforeigndatawrapper.html>
+    pub fn parse_create_foreign_data_wrapper(
+        &mut self,
+    ) -> Result<CreateForeignDataWrapper, ParserError> {
+        let name = self.parse_identifier()?;
+
+        // PostgreSQL accepts HANDLER and VALIDATOR in either order, so they 
are
+        // consumed in a loop rather than positionally.
+        let mut handler = None;
+        let mut validator = None;
+        loop {
+            let loc = self.peek_token_ref().span.start;
+            let (slot, clause) = if self.parse_keyword(Keyword::HANDLER) {
+                (
+                    &mut handler,
+                    
ForeignDataWrapperRoutineClause::Function(self.parse_object_name(false)?),
+                )
+            } else if self.parse_keywords(&[Keyword::NO, Keyword::HANDLER]) {
+                (&mut handler, ForeignDataWrapperRoutineClause::Absent)
+            } else if self.parse_keyword(Keyword::VALIDATOR) {
+                (
+                    &mut validator,
+                    
ForeignDataWrapperRoutineClause::Function(self.parse_object_name(false)?),
+                )
+            } else if self.parse_keywords(&[Keyword::NO, Keyword::VALIDATOR]) {
+                (&mut validator, ForeignDataWrapperRoutineClause::Absent)
+            } else {
+                break;
+            };
+            if slot.is_some() {
+                return parser_err!("conflicting or redundant options", loc);
+            }
+            *slot = Some(clause);
+        }
+
+        let options = self.parse_pg_options_clause()?;
+
+        Ok(CreateForeignDataWrapper {
+            name,
+            handler,
+            validator,
+            options,
+        })

Review Comment:
   You should collect the clauses in source order instead of rejecting repeats. 
PostgreSQL's grammar takes `fdw_options` as a plain list and raises 
"conflicting or redundant options" only in `parse_func_options` when the 
statement executes. `pg_query` 6.2 accepts `CREATE FOREIGN DATA WRAPPER w 
HANDLER h1 HANDLER h2`, which this loop rejects, so `fuzz_postgres_accepts` 
panics on it and on any mutation of the new seeds that repeats a clause.
   
   ```suggestion
           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,
           })
   ```



##########
src/ast/ddl.rs:
##########
@@ -6056,6 +6056,60 @@ impl fmt::Display for CreateForeignTable {
     }
 }
 
+/// The handler/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 {
+    /// A named function, e.g. `HANDLER myhandler` or `VALIDATOR myvalidator`.
+    Function(ObjectName),
+    /// The `NO HANDLER` / `NO VALIDATOR` form.
+    Absent,
+}
+
+impl ForeignDataWrapperRoutineClause {
+    fn fmt_with_label(&self, f: &mut fmt::Formatter<'_>, label: &str) -> 
fmt::Result {
+        match self {
+            ForeignDataWrapperRoutineClause::Function(name) => write!(f, " 
{label} {name}"),
+            ForeignDataWrapperRoutineClause::Absent => write!(f, " NO 
{label}"),
+        }
+    }
+}
+
+/// 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,
+    /// Optional `HANDLER handler_function` or `NO HANDLER` clause.
+    pub handler: Option<ForeignDataWrapperRoutineClause>,
+    /// Optional `VALIDATOR validator_function` or `NO VALIDATOR` clause.
+    pub validator: Option<ForeignDataWrapperRoutineClause>,

Review Comment:
   ```suggestion
       /// `HANDLER` and `VALIDATOR` clauses in source order. PostgreSQL's 
grammar
       /// accepts repeated clauses and rejects them only when executing the 
statement.
       pub routines: Vec<ForeignDataWrapperRoutineClause>,
   ```



##########
src/ast/ddl.rs:
##########
@@ -6078,3 +6132,29 @@ impl Spanned for CreateForeignTable {
         )
     }
 }
+
+impl From<CreateForeignDataWrapper> for crate::ast::Statement {
+    fn from(v: CreateForeignDataWrapper) -> Self {
+        crate::ast::Statement::CreateForeignDataWrapper(v)
+    }
+}
+
+impl Spanned for CreateForeignDataWrapper {
+    fn span(&self) -> Span {
+        let routine_span = |clause: &Option<ForeignDataWrapperRoutineClause>| 
match clause {
+            Some(ForeignDataWrapperRoutineClause::Function(name)) => 
Some(name.span()),
+            _ => None,
+        };
+        Span::union_iter(
+            core::iter::once(self.name.span)
+                .chain(routine_span(&self.handler))
+                .chain(routine_span(&self.validator))

Review Comment:
   You should take the spans from the list.
   
   ```suggestion
           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,
                   }))
   ```



##########
tests/sqlparser_postgres.rs:
##########
@@ -10154,3 +10154,112 @@ fn parse_bitstring_literal_escaping() {
     pg_and_generic().verified_stmt("SELECT B''''");
     pg_and_generic().verified_stmt("SELECT B'it''s'");
 }
+
+#[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.handler.is_none());
+    assert!(stmt.validator.is_none());
+    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.handler,
+        Some(ForeignDataWrapperRoutineClause::Function(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.handler, Some(ForeignDataWrapperRoutineClause::Absent));
+
+    let sql = "CREATE FOREIGN DATA WRAPPER myfdw NO VALIDATOR";
+    let Statement::CreateForeignDataWrapper(stmt) = 
pg_and_generic().verified_stmt(sql) else {
+        unreachable!()
+    };
+    assert_eq!(
+        stmt.validator,
+        Some(ForeignDataWrapperRoutineClause::Absent)
+    );
+
+    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.handler, Some(ForeignDataWrapperRoutineClause::Absent));
+    assert_eq!(
+        stmt.validator,
+        Some(ForeignDataWrapperRoutineClause::Absent)
+    );
+
+    // A schema-qualified name is not valid: FDW names are bare identifiers.
+    assert!(matches!(
+        pg_and_generic().parse_sql_statements("CREATE FOREIGN DATA WRAPPER 
myschema.myfdw"),
+        Err(ParserError::ParserError(_))
+    ));
+
+    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 parse_create_foreign_data_wrapper_clause_order() {
+    // PostgreSQL takes these in either order; Display normalizes to HANDLER 
first.
+    let canonical = "CREATE FOREIGN DATA WRAPPER myfdw HANDLER myhandler 
VALIDATOR myvalidator";
+    pg_and_generic().one_statement_parses_to(
+        "CREATE FOREIGN DATA WRAPPER myfdw VALIDATOR myvalidator HANDLER 
myhandler",
+        canonical,
+    );
+    pg_and_generic().one_statement_parses_to(
+        "CREATE FOREIGN DATA WRAPPER myfdw NO VALIDATOR NO HANDLER",
+        "CREATE FOREIGN DATA WRAPPER myfdw NO HANDLER NO VALIDATOR",
+    );
+
+    for sql in [
+        "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",
+    ] {
+        let err = pg_and_generic().parse_sql_statements(sql).unwrap_err();
+        assert!(
+            err.to_string().contains("conflicting or redundant options"),
+            "unexpected error for {sql}: {err}"
+        );
+    }
+}

Review Comment:
   You should pin that repeated and reordered clauses round-trip as `pg_query` 
accepts all five of these.
   
   ```suggestion
   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);
       }
   }
   ```



##########
src/ast/ddl.rs:
##########
@@ -6056,6 +6056,60 @@ impl fmt::Display for CreateForeignTable {
     }
 }
 
+/// The handler/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 {
+    /// A named function, e.g. `HANDLER myhandler` or `VALIDATOR myvalidator`.
+    Function(ObjectName),
+    /// The `NO HANDLER` / `NO VALIDATOR` form.
+    Absent,
+}
+
+impl ForeignDataWrapperRoutineClause {
+    fn fmt_with_label(&self, f: &mut fmt::Formatter<'_>, label: &str) -> 
fmt::Result {
+        match self {
+            ForeignDataWrapperRoutineClause::Function(name) => write!(f, " 
{label} {name}"),
+            ForeignDataWrapperRoutineClause::Absent => write!(f, " NO 
{label}"),
+        }
+    }
+}
+
+/// 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,
+    /// Optional `HANDLER handler_function` or `NO HANDLER` clause.
+    pub handler: Option<ForeignDataWrapperRoutineClause>,
+    /// Optional `VALIDATOR validator_function` or `NO VALIDATOR` clause.
+    pub validator: Option<ForeignDataWrapperRoutineClause>,
+    /// Optional `OPTIONS (key 'value', ...)` clause.
+    pub options: Option<Vec<CreateServerOption>>,
+}
+
+impl fmt::Display for CreateForeignDataWrapper {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "CREATE FOREIGN DATA WRAPPER {}", self.name)?;
+        if let Some(handler) = &self.handler {
+            handler.fmt_with_label(f, "HANDLER")?;
+        }
+        if let Some(validator) = &self.validator {
+            validator.fmt_with_label(f, "VALIDATOR")?;
+        }

Review Comment:
   You should render the clauses in the order they were parsed, which also 
makes every accepted statement round-trip unchanged.
   
   ```suggestion
           for routine in &self.routines {
               write!(f, " {routine}")?;
           }
   ```



##########
tests/sqlparser_postgres.rs:
##########
@@ -10154,3 +10154,112 @@ fn parse_bitstring_literal_escaping() {
     pg_and_generic().verified_stmt("SELECT B''''");
     pg_and_generic().verified_stmt("SELECT B'it''s'");
 }
+
+#[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.handler.is_none());
+    assert!(stmt.validator.is_none());
+    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.handler,
+        Some(ForeignDataWrapperRoutineClause::Function(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.handler, Some(ForeignDataWrapperRoutineClause::Absent));
+
+    let sql = "CREATE FOREIGN DATA WRAPPER myfdw NO VALIDATOR";
+    let Statement::CreateForeignDataWrapper(stmt) = 
pg_and_generic().verified_stmt(sql) else {
+        unreachable!()
+    };
+    assert_eq!(
+        stmt.validator,
+        Some(ForeignDataWrapperRoutineClause::Absent)
+    );
+
+    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.handler, Some(ForeignDataWrapperRoutineClause::Absent));
+    assert_eq!(
+        stmt.validator,
+        Some(ForeignDataWrapperRoutineClause::Absent)
+    );

Review Comment:
   You should assert on `routines` here.
   
   ```suggestion
       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
           ]
       );
   ```



##########
tests/sqlparser_postgres.rs:
##########
@@ -10154,3 +10154,112 @@ fn parse_bitstring_literal_escaping() {
     pg_and_generic().verified_stmt("SELECT B''''");
     pg_and_generic().verified_stmt("SELECT B'it''s'");
 }
+
+#[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.handler.is_none());
+    assert!(stmt.validator.is_none());
+    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.handler,
+        Some(ForeignDataWrapperRoutineClause::Function(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.handler, Some(ForeignDataWrapperRoutineClause::Absent));
+
+    let sql = "CREATE FOREIGN DATA WRAPPER myfdw NO VALIDATOR";
+    let Statement::CreateForeignDataWrapper(stmt) = 
pg_and_generic().verified_stmt(sql) else {
+        unreachable!()
+    };
+    assert_eq!(
+        stmt.validator,
+        Some(ForeignDataWrapperRoutineClause::Absent)
+    );
+
+    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.handler, Some(ForeignDataWrapperRoutineClause::Absent));
+    assert_eq!(
+        stmt.validator,
+        Some(ForeignDataWrapperRoutineClause::Absent)
+    );
+
+    // A schema-qualified name is not valid: FDW names are bare identifiers.
+    assert!(matches!(
+        pg_and_generic().parse_sql_statements("CREATE FOREIGN DATA WRAPPER 
myschema.myfdw"),
+        Err(ParserError::ParserError(_))
+    ));

Review Comment:
   You should pin the exact error, as #2628 does for the other PostgreSQL 
negative tests. `matches!(.., Err(ParserError::ParserError(_)))` still passes 
if the statement starts failing earlier for an unrelated reason.
   
   ```suggestion
       assert_eq!(
           pg_and_generic().parse_sql_statements("CREATE FOREIGN DATA WRAPPER 
myschema.myfdw"),
           Err(ParserError::ParserError(
               "Expected: end of statement, found: .".to_string()
           ))
       );
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to