This is an automated email from the ASF dual-hosted git repository.

iffyio pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion-sqlparser-rs.git


The following commit(s) were added to refs/heads/main by this push:
     new 239e30a9 Support for Postgres `CREATE SERVER` (#1914)
239e30a9 is described below

commit 239e30a97c5f088d39f1747fb108c8d8783a2e5c
Author: Sergey Olontsev <[email protected]>
AuthorDate: Thu Jul 3 18:04:32 2025 +0100

    Support for Postgres `CREATE SERVER` (#1914)
    
    Co-authored-by: Ifeanyi Ubah <[email protected]>
---
 src/ast/mod.rs              | 69 +++++++++++++++++++++++++++++++++++++++
 src/ast/spans.rs            |  1 +
 src/keywords.rs             |  2 ++
 src/parser/mod.rs           | 45 ++++++++++++++++++++++++++
 tests/sqlparser_postgres.rs | 79 +++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 196 insertions(+)

diff --git a/src/ast/mod.rs b/src/ast/mod.rs
index d7e342bd..cffd3292 100644
--- a/src/ast/mod.rs
+++ b/src/ast/mod.rs
@@ -3318,6 +3318,8 @@ pub enum Statement {
         secret_type: Ident,
         options: Vec<SecretOption>,
     },
+    /// A `CREATE SERVER` statement.
+    CreateServer(CreateServerStatement),
     /// ```sql
     /// CREATE POLICY
     /// ```
@@ -5178,6 +5180,9 @@ impl fmt::Display for Statement {
                 write!(f, " )")?;
                 Ok(())
             }
+            Statement::CreateServer(stmt) => {
+                write!(f, "{stmt}")
+            }
             Statement::CreatePolicy {
                 name,
                 table_name,
@@ -7976,6 +7981,70 @@ impl fmt::Display for SecretOption {
     }
 }
 
+/// A `CREATE SERVER` statement.
+///
+/// [PostgreSQL 
Documentation](https://www.postgresql.org/docs/current/sql-createserver.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 CreateServerStatement {
+    pub name: ObjectName,
+    pub if_not_exists: bool,
+    pub server_type: Option<Ident>,
+    pub version: Option<Ident>,
+    pub foreign_data_wrapper: ObjectName,
+    pub options: Option<Vec<CreateServerOption>>,
+}
+
+impl fmt::Display for CreateServerStatement {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        let CreateServerStatement {
+            name,
+            if_not_exists,
+            server_type,
+            version,
+            foreign_data_wrapper,
+            options,
+        } = self;
+
+        write!(
+            f,
+            "CREATE SERVER {if_not_exists}{name} ",
+            if_not_exists = if *if_not_exists { "IF NOT EXISTS " } else { "" },
+        )?;
+
+        if let Some(st) = server_type {
+            write!(f, "TYPE {st} ")?;
+        }
+
+        if let Some(v) = version {
+            write!(f, "VERSION {v} ")?;
+        }
+
+        write!(f, "FOREIGN DATA WRAPPER {foreign_data_wrapper}")?;
+
+        if let Some(o) = options {
+            write!(f, " OPTIONS ({o})", o = display_comma_separated(o))?;
+        }
+
+        Ok(())
+    }
+}
+
+#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub struct CreateServerOption {
+    pub key: Ident,
+    pub value: Ident,
+}
+
+impl fmt::Display for CreateServerOption {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "{} {}", self.key, self.value)
+    }
+}
+
 #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
 #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
diff --git a/src/ast/spans.rs b/src/ast/spans.rs
index 0895b844..1d790ab6 100644
--- a/src/ast/spans.rs
+++ b/src/ast/spans.rs
@@ -423,6 +423,7 @@ impl Spanned for Statement {
             Statement::CreateIndex(create_index) => create_index.span(),
             Statement::CreateRole { .. } => Span::empty(),
             Statement::CreateSecret { .. } => Span::empty(),
+            Statement::CreateServer { .. } => Span::empty(),
             Statement::CreateConnector { .. } => Span::empty(),
             Statement::AlterTable {
                 name,
diff --git a/src/keywords.rs b/src/keywords.rs
index 49a54e8a..73865150 100644
--- a/src/keywords.rs
+++ b/src/keywords.rs
@@ -816,6 +816,7 @@ define_keywords!(
     SERDE,
     SERDEPROPERTIES,
     SERIALIZABLE,
+    SERVER,
     SERVICE,
     SESSION,
     SESSION_USER,
@@ -1017,6 +1018,7 @@ define_keywords!(
     WITHOUT,
     WITHOUT_ARRAY_WRAPPER,
     WORK,
+    WRAPPER,
     WRITE,
     XML,
     XMLNAMESPACES,
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index f38a360e..289dd6b3 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -4674,6 +4674,8 @@ impl<'a> Parser<'a> {
             self.parse_create_procedure(or_alter)
         } else if self.parse_keyword(Keyword::CONNECTOR) {
             self.parse_create_connector()
+        } else if self.parse_keyword(Keyword::SERVER) {
+            self.parse_pg_create_server()
         } else {
             self.expected("an object type after CREATE", self.peek_token())
         }
@@ -16009,6 +16011,49 @@ impl<'a> Parser<'a> {
         Ok(sequence_options)
     }
 
+    ///   Parse a `CREATE SERVER` statement.
+    ///
+    ///  See [Statement::CreateServer]
+    pub fn parse_pg_create_server(&mut self) -> Result<Statement, ParserError> 
{
+        let ine = self.parse_keywords(&[Keyword::IF, Keyword::NOT, 
Keyword::EXISTS]);
+        let name = self.parse_object_name(false)?;
+
+        let server_type = if self.parse_keyword(Keyword::TYPE) {
+            Some(self.parse_identifier()?)
+        } else {
+            None
+        };
+
+        let version = if self.parse_keyword(Keyword::VERSION) {
+            Some(self.parse_identifier()?)
+        } else {
+            None
+        };
+
+        self.expect_keywords(&[Keyword::FOREIGN, Keyword::DATA, 
Keyword::WRAPPER])?;
+        let foreign_data_wrapper = self.parse_object_name(false)?;
+
+        let mut options = None;
+        if self.parse_keyword(Keyword::OPTIONS) {
+            self.expect_token(&Token::LParen)?;
+            options = Some(self.parse_comma_separated(|p| {
+                let key = p.parse_identifier()?;
+                let value = p.parse_identifier()?;
+                Ok(CreateServerOption { key, value })
+            })?);
+            self.expect_token(&Token::RParen)?;
+        }
+
+        Ok(Statement::CreateServer(CreateServerStatement {
+            name,
+            if_not_exists: ine,
+            server_type,
+            version,
+            foreign_data_wrapper,
+            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 db5c1611..48792025 100644
--- a/tests/sqlparser_postgres.rs
+++ b/tests/sqlparser_postgres.rs
@@ -6273,3 +6273,82 @@ fn parse_alter_table_validate_constraint() {
         _ => unreachable!(),
     }
 }
+
+#[test]
+fn parse_create_server() {
+    let test_cases = vec![
+        (
+            "CREATE SERVER myserver FOREIGN DATA WRAPPER postgres_fdw",
+            CreateServerStatement {
+                name: ObjectName::from(vec!["myserver".into()]),
+                if_not_exists: false,
+                server_type: None,
+                version: None,
+                foreign_data_wrapper: 
ObjectName::from(vec!["postgres_fdw".into()]),
+                options: None,
+            },
+        ),
+        (
+            "CREATE SERVER IF NOT EXISTS myserver TYPE 'server_type' VERSION 
'server_version' FOREIGN DATA WRAPPER postgres_fdw",
+            CreateServerStatement {
+            name: ObjectName::from(vec!["myserver".into()]),
+            if_not_exists: true,
+            server_type: Some(Ident {
+                value: "server_type".to_string(),
+                quote_style: Some('\''),
+                span: Span::empty(),
+            }),
+            version: Some(Ident {
+                value: "server_version".to_string(),
+                quote_style: Some('\''),
+                span: Span::empty(),
+            }),
+            foreign_data_wrapper: 
ObjectName::from(vec!["postgres_fdw".into()]),
+            options: None,
+        }
+        ),
+        (
+            "CREATE SERVER myserver2 FOREIGN DATA WRAPPER postgres_fdw OPTIONS 
(host 'foo', dbname 'foodb', port '5432')",
+            CreateServerStatement {
+                name: ObjectName::from(vec!["myserver2".into()]),
+                if_not_exists: false,
+                server_type: None,
+                version: None,
+                foreign_data_wrapper: 
ObjectName::from(vec!["postgres_fdw".into()]),
+                options: Some(vec![
+                    CreateServerOption {
+                        key: "host".into(),
+                        value: Ident {
+                            value: "foo".to_string(),
+                            quote_style: Some('\''),
+                            span: Span::empty(),
+                        },
+                    },
+                    CreateServerOption {
+                        key: "dbname".into(),
+                        value: Ident {
+                            value: "foodb".to_string(),
+                            quote_style: Some('\''),
+                            span: Span::empty(),
+                        },
+                    },
+                    CreateServerOption {
+                        key: "port".into(),
+                        value: Ident {
+                            value: "5432".to_string(),
+                            quote_style: Some('\''),
+                            span: Span::empty(),
+                        },
+                    },
+                ]),
+            }
+        )
+    ];
+
+    for (sql, expected) in test_cases {
+        let Statement::CreateServer(stmt) = 
pg_and_generic().verified_stmt(sql) else {
+            unreachable!()
+        };
+        assert_eq!(stmt, expected);
+    }
+}


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

Reply via email to