Support for Postgres's CREATE SERVER

This commit is contained in:
Sergey Olontsev 2025-06-30 08:14:00 +03:00
parent 50c605a471
commit 2e5f3562f0
No known key found for this signature in database
GPG key ID: 67DDC06CA52E8A39
5 changed files with 184 additions and 1 deletions

View file

@ -3316,6 +3316,18 @@ pub enum Statement {
options: Vec<SecretOption>,
},
/// ```sql
/// CREATE SERVER
/// ```
/// See [PostgreSQL](https://www.postgresql.org/docs/current/sql-createserver.html)
CreateServer {
name: ObjectName,
if_not_exists: bool,
server_type: Option<Ident>,
version: Option<Ident>,
fdw_name: ObjectName,
options: Option<Vec<ServerOption>>,
},
/// ```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))]

View file

@ -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,

View file

@ -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,

View file

@ -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<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_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

View file

@ -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(),
},
},
]),
}
)
}