From 2e5f3562f063ce35c31d81511c433878f36fb200 Mon Sep 17 00:00:00 2001 From: Sergey Olontsev Date: Mon, 30 Jun 2025 08:14:00 +0300 Subject: [PATCH 01/12] Support for Postgres's CREATE SERVER --- src/ast/mod.rs | 56 +++++++++++++++++++++++++++++ src/ast/spans.rs | 1 + src/keywords.rs | 2 ++ src/parser/mod.rs | 54 +++++++++++++++++++++++++++- tests/sqlparser_postgres.rs | 72 +++++++++++++++++++++++++++++++++++++ 5 files changed, 184 insertions(+), 1 deletion(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 19966d21c..123a0683d 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -3316,6 +3316,18 @@ pub enum Statement { options: Vec, }, /// ```sql + /// CREATE SERVER + /// ``` + /// See [PostgreSQL](https://www.postgresql.org/docs/current/sql-createserver.html) + CreateServer { + name: ObjectName, + if_not_exists: bool, + server_type: Option, + version: Option, + fdw_name: ObjectName, + options: Option>, + }, + /// ```sql /// CREATE POLICY /// ``` /// See [PostgreSQL](https://www.postgresql.org/docs/current/sql-createpolicy.html) @@ -5175,6 +5187,36 @@ impl fmt::Display for Statement { write!(f, " )")?; Ok(()) } + Statement::CreateServer { + name, + if_not_exists, + server_type, + version, + fdw_name, + options + } => { + 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 {fdw_name}")?; + + if let Some(o) = options { + write!(f, " OPTIONS ({o})", o = display_comma_separated(o))?; + } + + Ok(()) + } Statement::CreatePolicy { name, table_name, @@ -7973,6 +8015,20 @@ impl fmt::Display for SecretOption { } } +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub struct ServerOption { + pub key: Ident, + pub value: Ident, +} + +impl fmt::Display for ServerOption { + 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 78ed772bd..6b7b4738d 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 a8bbca3d3..81ad68b6f 100644 --- a/src/keywords.rs +++ b/src/keywords.rs @@ -816,6 +816,7 @@ define_keywords!( SERDE, SERDEPROPERTIES, SERIALIZABLE, + SERVER, SERVICE, SESSION, SESSION_USER, @@ -1016,6 +1017,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 adf50a8f0..2c6afc71e 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -33,7 +33,7 @@ use IsLateral::*; use IsOptional::*; use crate::ast::helpers::stmt_create_table::{CreateTableBuilder, CreateTableConfiguration}; -use crate::ast::Statement::CreatePolicy; +use crate::ast::Statement::{CreatePolicy, CreateServer}; use crate::ast::*; use crate::dialect::*; use crate::keywords::{Keyword, ALL_KEYWORDS}; @@ -4662,6 +4662,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) && dialect_of!(self is PostgreSqlDialect | GenericDialect) { + self.parse_pg_create_server() } else { self.expected("an object type after CREATE", self.peek_token()) } @@ -15806,6 +15808,56 @@ impl<'a> Parser<'a> { Ok(sequence_options) } + /// ```sql + /// CREATE SERVER [ IF NOT EXISTS ] server_name [ TYPE 'server_type' ] [ VERSION 'server_version' ] + /// FOREIGN DATA WRAPPER fdw_name + /// [ OPTIONS ( option 'value' [, ... ] ) ] + /// ``` + /// + /// [PostgreSQL Documentation](https://www.postgresql.org/docs/current/sql-createserver.html) + pub fn parse_pg_create_server(&mut self) -> Result { + 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_keyword_is(Keyword::FOREIGN)?; + self.expect_keyword_is(Keyword::DATA)?; + self.expect_keyword_is(Keyword::WRAPPER)?; + + let fdw_name = 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(ServerOption { key, value }) + })?); + self.expect_token(&Token::RParen)?; + } + + Ok(CreateServer { + name, + if_not_exists: ine, + server_type, + version, + fdw_name, + 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 7b0a8c5da..897ae9e78 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -6231,3 +6231,75 @@ fn parse_ts_datatypes() { _ => unreachable!(), } } + +#[test] +fn parse_create_server() { + assert_eq!( + pg_and_generic().verified_stmt("CREATE SERVER myserver FOREIGN DATA WRAPPER postgres_fdw"), + Statement::CreateServer { + name: ObjectName::from(vec!["myserver".into()]), + if_not_exists: false, + server_type: None, + version: None, + fdw_name: ObjectName::from(vec!["postgres_fdw".into()]), + options: None, + } + ); + + assert_eq!( + pg_and_generic().verified_stmt("CREATE SERVER IF NOT EXISTS myserver TYPE 'server_type' VERSION 'server_version' FOREIGN DATA WRAPPER postgres_fdw"), + Statement::CreateServer { + 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(), + }), + fdw_name: ObjectName::from(vec!["postgres_fdw".into()]), + options: None, + } + ); + + assert_eq!( + pg_and_generic().verified_stmt("CREATE SERVER myserver2 FOREIGN DATA WRAPPER postgres_fdw OPTIONS (host 'foo', dbname 'foodb', port '5432')"), + Statement::CreateServer { + name: ObjectName::from(vec!["myserver2".into()]), + if_not_exists: false, + server_type: None, + version: None, + fdw_name: ObjectName::from(vec!["postgres_fdw".into()]), + options: Some(vec![ + ServerOption { + key: "host".into(), + value: Ident { + value: "foo".to_string(), + quote_style: Some('\''), + span: Span::empty(), + }, + }, + ServerOption { + key: "dbname".into(), + value: Ident { + value: "foodb".to_string(), + quote_style: Some('\''), + span: Span::empty(), + }, + }, + ServerOption { + key: "port".into(), + value: Ident { + value: "5432".to_string(), + quote_style: Some('\''), + span: Span::empty(), + }, + }, + ]), + } + ) +} From d9cc504c32b5d1df8d270cb21aa668dd6ba510cd Mon Sep 17 00:00:00 2001 From: Sergey Olontsev Date: Mon, 30 Jun 2025 08:16:17 +0300 Subject: [PATCH 02/12] fmt fix --- src/ast/mod.rs | 10 +++++----- src/parser/mod.rs | 4 +++- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 123a0683d..1fecbe75f 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -5193,24 +5193,24 @@ impl fmt::Display for Statement { server_type, version, fdw_name, - options + options, } => { 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 {fdw_name}")?; - + if let Some(o) = options { write!(f, " OPTIONS ({o})", o = display_comma_separated(o))?; } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 2c6afc71e..13fc0bb69 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -4662,7 +4662,9 @@ 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) && dialect_of!(self is PostgreSqlDialect | GenericDialect) { + } else if self.parse_keyword(Keyword::SERVER) + && dialect_of!(self is PostgreSqlDialect | GenericDialect) + { self.parse_pg_create_server() } else { self.expected("an object type after CREATE", self.peek_token()) From 9d68a5663dcdb0357ee52bc351dd99a5905fbec5 Mon Sep 17 00:00:00 2001 From: Sergey Olontsev Date: Mon, 30 Jun 2025 08:22:11 +0300 Subject: [PATCH 03/12] minor improvement --- src/parser/mod.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 13fc0bb69..30e25632e 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -15833,10 +15833,7 @@ impl<'a> Parser<'a> { None }; - self.expect_keyword_is(Keyword::FOREIGN)?; - self.expect_keyword_is(Keyword::DATA)?; - self.expect_keyword_is(Keyword::WRAPPER)?; - + self.expect_keywords(&[Keyword::FOREIGN, Keyword::DATA, Keyword::WRAPPER])?; let fdw_name = self.parse_object_name(false)?; let mut options = None; From e84c844a9db4db2b85305627a81710531e89882f Mon Sep 17 00:00:00 2001 From: Sergey Olontsev Date: Wed, 2 Jul 2025 05:24:03 +0100 Subject: [PATCH 04/12] let the parser be permissive Co-authored-by: Ifeanyi Ubah --- src/parser/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 30e25632e..3323587c6 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -4663,7 +4663,6 @@ impl<'a> Parser<'a> { } else if self.parse_keyword(Keyword::CONNECTOR) { self.parse_create_connector() } else if self.parse_keyword(Keyword::SERVER) - && dialect_of!(self is PostgreSqlDialect | GenericDialect) { self.parse_pg_create_server() } else { From cd78c51bfe1b0ca012a9c3f3ec62be8a81a6a730 Mon Sep 17 00:00:00 2001 From: Sergey Olontsev Date: Wed, 2 Jul 2025 05:25:53 +0100 Subject: [PATCH 05/12] ServerOption -> CreateServerOption Co-authored-by: Ifeanyi Ubah --- src/ast/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 1fecbe75f..4d6a3a06c 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -8018,7 +8018,7 @@ impl fmt::Display for SecretOption { #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] -pub struct ServerOption { +pub struct CreateServerOption { pub key: Ident, pub value: Ident, } From 1b916b8781adddcf2d770d9fe9d423da4535f3ae Mon Sep 17 00:00:00 2001 From: Sergey Olontsev Date: Wed, 2 Jul 2025 07:31:13 +0300 Subject: [PATCH 06/12] fdw_name -> foreign_data_wrapper + fmt fix --- src/ast/mod.rs | 6 +++--- src/parser/mod.rs | 7 +++---- tests/sqlparser_postgres.rs | 6 +++--- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 4d6a3a06c..c7a897671 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -3324,7 +3324,7 @@ pub enum Statement { if_not_exists: bool, server_type: Option, version: Option, - fdw_name: ObjectName, + foreign_data_wrapper: ObjectName, options: Option>, }, /// ```sql @@ -5192,7 +5192,7 @@ impl fmt::Display for Statement { if_not_exists, server_type, version, - fdw_name, + foreign_data_wrapper, options, } => { write!( @@ -5209,7 +5209,7 @@ impl fmt::Display for Statement { write!(f, "VERSION {v} ")?; } - write!(f, "FOREIGN DATA WRAPPER {fdw_name}")?; + write!(f, "FOREIGN DATA WRAPPER {foreign_data_wrapper}")?; if let Some(o) = options { write!(f, " OPTIONS ({o})", o = display_comma_separated(o))?; diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 3323587c6..3a814a521 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -4662,8 +4662,7 @@ 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) - { + } else if self.parse_keyword(Keyword::SERVER) { self.parse_pg_create_server() } else { self.expected("an object type after CREATE", self.peek_token()) @@ -15833,7 +15832,7 @@ impl<'a> Parser<'a> { }; self.expect_keywords(&[Keyword::FOREIGN, Keyword::DATA, Keyword::WRAPPER])?; - let fdw_name = self.parse_object_name(false)?; + let foreign_data_wrapper = self.parse_object_name(false)?; let mut options = None; if self.parse_keyword(Keyword::OPTIONS) { @@ -15851,7 +15850,7 @@ impl<'a> Parser<'a> { if_not_exists: ine, server_type, version, - fdw_name, + foreign_data_wrapper, options, }) } diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 897ae9e78..2abc1ceff 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -6241,7 +6241,7 @@ fn parse_create_server() { if_not_exists: false, server_type: None, version: None, - fdw_name: ObjectName::from(vec!["postgres_fdw".into()]), + foreign_data_wrapper: ObjectName::from(vec!["postgres_fdw".into()]), options: None, } ); @@ -6261,7 +6261,7 @@ fn parse_create_server() { quote_style: Some('\''), span: Span::empty(), }), - fdw_name: ObjectName::from(vec!["postgres_fdw".into()]), + foreign_data_wrapper: ObjectName::from(vec!["postgres_fdw".into()]), options: None, } ); @@ -6273,7 +6273,7 @@ fn parse_create_server() { if_not_exists: false, server_type: None, version: None, - fdw_name: ObjectName::from(vec!["postgres_fdw".into()]), + foreign_data_wrapper: ObjectName::from(vec!["postgres_fdw".into()]), options: Some(vec![ ServerOption { key: "host".into(), From 77b868b0a21aced8cee7b2bf84f07a53f4a44eb6 Mon Sep 17 00:00:00 2001 From: Sergey Olontsev Date: Wed, 2 Jul 2025 08:05:34 +0300 Subject: [PATCH 07/12] improvements after review --- src/ast/mod.rs | 106 ++++++++++++++++++++--------------- src/lib.rs | 1 + src/parser/mod.rs | 6 +- tests/sqlparser_postgres.rs | 107 +++++++++++++++++++----------------- 4 files changed, 124 insertions(+), 96 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index c7a897671..a7a130f36 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -29,12 +29,12 @@ use helpers::{ }; use core::cmp::Ordering; +use core::fmt::Formatter; use core::ops::Deref; use core::{ fmt::{self, Display}, hash, }; - #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -3315,18 +3315,8 @@ pub enum Statement { secret_type: Ident, options: Vec, }, - /// ```sql - /// CREATE SERVER - /// ``` - /// See [PostgreSQL](https://www.postgresql.org/docs/current/sql-createserver.html) - CreateServer { - name: ObjectName, - if_not_exists: bool, - server_type: Option, - version: Option, - foreign_data_wrapper: ObjectName, - options: Option>, - }, + /// A `CREATE SERVER` statement. + CreateServer(CreateServerStatement), /// ```sql /// CREATE POLICY /// ``` @@ -5187,35 +5177,8 @@ impl fmt::Display for Statement { write!(f, " )")?; Ok(()) } - Statement::CreateServer { - name, - if_not_exists, - server_type, - version, - foreign_data_wrapper, - options, - } => { - 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(()) + Statement::CreateServer(stmt) => { + write!(f, "{stmt}") } Statement::CreatePolicy { name, @@ -8015,6 +7978,63 @@ impl fmt::Display for SecretOption { } } +/// A `CREATE SERVER` statement. +/// +/// Examples: +/// ```sql +/// CREATE SERVER [ IF NOT EXISTS ] server_name [ TYPE 'server_type' ] [ VERSION 'server_version' ] +/// FOREIGN DATA WRAPPER fdw_name +/// [ OPTIONS ( option 'value' [, ... ] ) ] +/// ``` +/// +/// [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, + pub version: Option, + pub foreign_data_wrapper: ObjectName, + pub options: Option>, +} + +impl fmt::Display for CreateServerStatement { + fn fmt(&self, f: &mut 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))] @@ -8023,7 +8043,7 @@ pub struct CreateServerOption { pub value: Ident, } -impl fmt::Display for ServerOption { +impl fmt::Display for CreateServerOption { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{} {}", self.key, self.value) } diff --git a/src/lib.rs b/src/lib.rs index dbfd1791a..1c9605b76 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -163,6 +163,7 @@ extern crate alloc; #[macro_use] #[cfg(test)] extern crate pretty_assertions; +extern crate core; pub mod ast; #[macro_use] diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 3a814a521..f44855c0d 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -15840,19 +15840,19 @@ impl<'a> Parser<'a> { options = Some(self.parse_comma_separated(|p| { let key = p.parse_identifier()?; let value = p.parse_identifier()?; - Ok(ServerOption { key, value }) + Ok(CreateServerOption { key, value }) })?); self.expect_token(&Token::RParen)?; } - Ok(CreateServer { + Ok(CreateServer(CreateServerStatement { name, if_not_exists: ine, server_type, version, foreign_data_wrapper, options, - }) + })) } /// The index of the first unprocessed token. diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 2abc1ceff..39adaaeac 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -6234,21 +6234,21 @@ fn parse_ts_datatypes() { #[test] fn parse_create_server() { - assert_eq!( - pg_and_generic().verified_stmt("CREATE SERVER myserver FOREIGN DATA WRAPPER postgres_fdw"), - Statement::CreateServer { - 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, - } - ); - - assert_eq!( - pg_and_generic().verified_stmt("CREATE SERVER IF NOT EXISTS myserver TYPE 'server_type' VERSION 'server_version' FOREIGN DATA WRAPPER postgres_fdw"), - Statement::CreateServer { + 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 { @@ -6264,42 +6264,49 @@ fn parse_create_server() { foreign_data_wrapper: ObjectName::from(vec!["postgres_fdw".into()]), options: None, } - ); - - assert_eq!( - pg_and_generic().verified_stmt("CREATE SERVER myserver2 FOREIGN DATA WRAPPER postgres_fdw OPTIONS (host 'foo', dbname 'foodb', port '5432')"), - Statement::CreateServer { - 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![ - ServerOption { - key: "host".into(), - value: Ident { - value: "foo".to_string(), - quote_style: Some('\''), - span: Span::empty(), + ), + ( + "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(), + }, }, - }, - ServerOption { - key: "dbname".into(), - value: Ident { - value: "foodb".to_string(), - quote_style: Some('\''), - span: Span::empty(), + CreateServerOption { + key: "dbname".into(), + value: Ident { + value: "foodb".to_string(), + quote_style: Some('\''), + span: Span::empty(), + }, }, - }, - ServerOption { - key: "port".into(), - value: Ident { - value: "5432".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); + } } From f141ca08ac717d8aec94e23900aa436648a672fe Mon Sep 17 00:00:00 2001 From: Sergey Olontsev Date: Wed, 2 Jul 2025 08:08:23 +0300 Subject: [PATCH 08/12] create server statement example is removed, use a doc link for more information --- src/ast/mod.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index a7a130f36..e78f7e650 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -7980,13 +7980,6 @@ impl fmt::Display for SecretOption { /// A `CREATE SERVER` statement. /// -/// Examples: -/// ```sql -/// CREATE SERVER [ IF NOT EXISTS ] server_name [ TYPE 'server_type' ] [ VERSION 'server_version' ] -/// FOREIGN DATA WRAPPER fdw_name -/// [ OPTIONS ( option 'value' [, ... ] ) ] -/// ``` -/// /// [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))] From 69a060f64fe5339f067c547bf5333d4817ec50ac Mon Sep 17 00:00:00 2001 From: Sergey Olontsev Date: Wed, 2 Jul 2025 06:14:11 +0100 Subject: [PATCH 09/12] Remove unnecessary syntax coment Co-authored-by: Ifeanyi Ubah --- src/parser/mod.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index f44855c0d..adcbc49d1 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -15808,13 +15808,9 @@ impl<'a> Parser<'a> { Ok(sequence_options) } - /// ```sql - /// CREATE SERVER [ IF NOT EXISTS ] server_name [ TYPE 'server_type' ] [ VERSION 'server_version' ] - /// FOREIGN DATA WRAPPER fdw_name - /// [ OPTIONS ( option 'value' [, ... ] ) ] - /// ``` + /// Parse a `CREATE SERVER` statement. /// - /// [PostgreSQL Documentation](https://www.postgresql.org/docs/current/sql-createserver.html) + /// See [Statement::CreateServer] pub fn parse_pg_create_server(&mut self) -> Result { let ine = self.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]); let name = self.parse_object_name(false)?; From b475fbb932284e1c43cb014eda1c1ccd40915972 Mon Sep 17 00:00:00 2001 From: Sergey Olontsev Date: Wed, 2 Jul 2025 08:12:25 +0300 Subject: [PATCH 10/12] code cleanup --- src/ast/mod.rs | 3 +-- src/lib.rs | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index e78f7e650..0f2bbb7ee 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -29,7 +29,6 @@ use helpers::{ }; use core::cmp::Ordering; -use core::fmt::Formatter; use core::ops::Deref; use core::{ fmt::{self, Display}, @@ -7994,7 +7993,7 @@ pub struct CreateServerStatement { } impl fmt::Display for CreateServerStatement { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let CreateServerStatement { name, if_not_exists, diff --git a/src/lib.rs b/src/lib.rs index 1c9605b76..dbfd1791a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -163,7 +163,6 @@ extern crate alloc; #[macro_use] #[cfg(test)] extern crate pretty_assertions; -extern crate core; pub mod ast; #[macro_use] From efa1f953b4a161893197aa540a5ac3526192f2c5 Mon Sep 17 00:00:00 2001 From: Sergey Olontsev Date: Wed, 2 Jul 2025 08:17:13 +0300 Subject: [PATCH 11/12] code cleanup --- src/ast/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 0f2bbb7ee..2894b5637 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -34,6 +34,7 @@ use core::{ fmt::{self, Display}, hash, }; + #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; From d0c7c7eccfc84c98e326f05bb2c958deede2b2bd Mon Sep 17 00:00:00 2001 From: Sergey Olontsev Date: Wed, 2 Jul 2025 08:20:44 +0300 Subject: [PATCH 12/12] code cleanup --- src/parser/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index adcbc49d1..d2a2255f4 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -33,7 +33,7 @@ use IsLateral::*; use IsOptional::*; use crate::ast::helpers::stmt_create_table::{CreateTableBuilder, CreateTableConfiguration}; -use crate::ast::Statement::{CreatePolicy, CreateServer}; +use crate::ast::Statement::CreatePolicy; use crate::ast::*; use crate::dialect::*; use crate::keywords::{Keyword, ALL_KEYWORDS}; @@ -15841,7 +15841,7 @@ impl<'a> Parser<'a> { self.expect_token(&Token::RParen)?; } - Ok(CreateServer(CreateServerStatement { + Ok(Statement::CreateServer(CreateServerStatement { name, if_not_exists: ine, server_type,