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]