Skip to content

Commit aff2ffd

Browse files
authored
Merge pull request #8 from fmguerreiro/feat/foreign-table-fdw
feat: parse CREATE FOREIGN DATA WRAPPER and CREATE FOREIGN TABLE
2 parents 7b14532 + c7262b0 commit aff2ffd

6 files changed

Lines changed: 313 additions & 7 deletions

File tree

src/ast/ddl.rs

Lines changed: 103 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ use crate::ast::{
4242
UniqueConstraint,
4343
},
4444
ArgMode, AttachedToken, CommentDef, ConditionalStatements, CreateFunctionBody,
45-
CreateFunctionUsing, CreateTableLikeKind, CreateTableOptions, CreateViewParams, DataType, Expr,
45+
CreateFunctionUsing, CreateServerOption, CreateTableLikeKind, CreateTableOptions,
46+
CreateViewParams, DataType, Expr,
4647
FileFormat, FunctionBehavior, FunctionCalledOnNull, FunctionDefinitionSetParam, FunctionDesc,
4748
FunctionDeterminismSpecifier, FunctionParallel, FunctionSecurity, HiveDistributionStyle,
4849
HiveFormat, HiveIOFormat, HiveRowFormat, HiveSetLocation, Ident, InitializeKind,
@@ -5758,6 +5759,107 @@ impl From<AlterPolicy> for crate::ast::Statement {
57585759
}
57595760
}
57605761

5762+
/// The handler/validator clause of a `CREATE FOREIGN DATA WRAPPER` statement.
5763+
///
5764+
/// Specifies either a named function or the absence of a function.
5765+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
5766+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
5767+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
5768+
pub enum FdwRoutineClause {
5769+
/// A named function, e.g. `HANDLER myhandler` or `VALIDATOR myvalidator`.
5770+
Function(ObjectName),
5771+
/// The `NO HANDLER` or `NO VALIDATOR` form.
5772+
NoFunction,
5773+
}
5774+
5775+
/// A `CREATE FOREIGN DATA WRAPPER` statement.
5776+
///
5777+
/// See [PostgreSQL](https://www.postgresql.org/docs/current/sql-createforeigndatawrapper.html)
5778+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
5779+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
5780+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
5781+
pub struct CreateForeignDataWrapper {
5782+
/// The name of the foreign-data wrapper.
5783+
pub name: Ident,
5784+
/// Optional `HANDLER handler_function` or `NO HANDLER` clause.
5785+
pub handler: Option<FdwRoutineClause>,
5786+
/// Optional `VALIDATOR validator_function` or `NO VALIDATOR` clause.
5787+
pub validator: Option<FdwRoutineClause>,
5788+
/// Optional `OPTIONS (key 'value', ...)` clause.
5789+
pub options: Option<Vec<CreateServerOption>>,
5790+
}
5791+
5792+
impl fmt::Display for CreateForeignDataWrapper {
5793+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
5794+
write!(f, "CREATE FOREIGN DATA WRAPPER {}", self.name)?;
5795+
if let Some(handler) = &self.handler {
5796+
match handler {
5797+
FdwRoutineClause::Function(name) => write!(f, " HANDLER {name}")?,
5798+
FdwRoutineClause::NoFunction => write!(f, " NO HANDLER")?,
5799+
}
5800+
}
5801+
if let Some(validator) = &self.validator {
5802+
match validator {
5803+
FdwRoutineClause::Function(name) => write!(f, " VALIDATOR {name}")?,
5804+
FdwRoutineClause::NoFunction => write!(f, " NO VALIDATOR")?,
5805+
}
5806+
}
5807+
if let Some(options) = &self.options {
5808+
write!(f, " OPTIONS ({})", display_comma_separated(options))?;
5809+
}
5810+
Ok(())
5811+
}
5812+
}
5813+
5814+
impl From<CreateForeignDataWrapper> for crate::ast::Statement {
5815+
fn from(v: CreateForeignDataWrapper) -> Self {
5816+
crate::ast::Statement::CreateForeignDataWrapper(v)
5817+
}
5818+
}
5819+
5820+
/// A `CREATE FOREIGN TABLE` statement.
5821+
///
5822+
/// See [PostgreSQL](https://www.postgresql.org/docs/current/sql-createforeigntable.html)
5823+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
5824+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
5825+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
5826+
pub struct CreateForeignTable {
5827+
/// The foreign table name.
5828+
#[cfg_attr(feature = "visitor", visit(with = "visit_relation"))]
5829+
pub name: ObjectName,
5830+
/// Whether `IF NOT EXISTS` was specified.
5831+
pub if_not_exists: bool,
5832+
/// Column definitions.
5833+
pub columns: Vec<ColumnDef>,
5834+
/// The `SERVER server_name` clause.
5835+
pub server_name: Ident,
5836+
/// Optional `OPTIONS (key 'value', ...)` clause at the table level.
5837+
pub options: Option<Vec<CreateServerOption>>,
5838+
}
5839+
5840+
impl fmt::Display for CreateForeignTable {
5841+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
5842+
write!(
5843+
f,
5844+
"CREATE FOREIGN TABLE {if_not_exists}{name} ({columns}) SERVER {server_name}",
5845+
if_not_exists = if self.if_not_exists { "IF NOT EXISTS " } else { "" },
5846+
name = self.name,
5847+
columns = display_comma_separated(&self.columns),
5848+
server_name = self.server_name,
5849+
)?;
5850+
if let Some(options) = &self.options {
5851+
write!(f, " OPTIONS ({})", display_comma_separated(options))?;
5852+
}
5853+
Ok(())
5854+
}
5855+
}
5856+
5857+
impl From<CreateForeignTable> for crate::ast::Statement {
5858+
fn from(v: CreateForeignTable) -> Self {
5859+
crate::ast::Statement::CreateForeignTable(v)
5860+
}
5861+
}
5862+
57615863
/// CREATE AGGREGATE statement.
57625864
/// See <https://www.postgresql.org/docs/current/sql-createaggregate.html>
57635865
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]

src/ast/mod.rs

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,13 @@ pub use self::ddl::{
7070
AggregateModifyKind, ClusteredBy, ColumnDef, ColumnOption, ColumnOptionDef, ColumnOptions,
7171
ColumnPolicy, ColumnPolicyProperty, ConstraintCharacteristics, CreateAggregate,
7272
CreateAggregateOption, CreateCollation, CreateCollationDefinition, CreateConnector,
73-
CreateDomain, CreateExtension, CreateFunction, CreateIndex, CreateOperator, CreateOperatorClass,
74-
CreateOperatorFamily, CreatePolicy, CreatePolicyCommand, CreatePolicyType, CreateTable,
75-
CreateTextSearchConfiguration, CreateTextSearchDictionary, CreateTextSearchParser,
76-
CreateTextSearchTemplate, CreateTrigger, CreateView, Deduplicate, DeferrableInitial, DistStyle,
77-
DropBehavior, DropExtension, DropFunction, DropOperator, DropOperatorClass, DropOperatorFamily,
78-
DropOperatorSignature, DropPolicy, DropTrigger, ForValues, FunctionReturnType, GeneratedAs,
73+
CreateDomain, CreateExtension, CreateForeignDataWrapper, CreateForeignTable, CreateFunction,
74+
CreateIndex, CreateOperator, CreateOperatorClass, CreateOperatorFamily, CreatePolicy,
75+
CreatePolicyCommand, CreatePolicyType, CreateTable, CreateTextSearchConfiguration,
76+
CreateTextSearchDictionary, CreateTextSearchParser, CreateTextSearchTemplate, CreateTrigger,
77+
CreateView, Deduplicate, DeferrableInitial, DistStyle, DropBehavior, DropExtension,
78+
DropFunction, DropOperator, DropOperatorClass, DropOperatorFamily, DropOperatorSignature,
79+
DropPolicy, DropTrigger, FdwRoutineClause, ForValues, FunctionReturnType, GeneratedAs,
7980
GeneratedExpressionMode, IdentityParameters, IdentityProperty, IdentityPropertyFormatKind,
8081
IdentityPropertyKind, IdentityPropertyOrder, IndexColumn, IndexOption, IndexType,
8182
KeyOrIndexDisplay, Msck, NullsDistinctOption, OperatorArgTypes, OperatorClassItem,
@@ -3701,6 +3702,16 @@ pub enum Statement {
37013702
/// A `CREATE SERVER` statement.
37023703
CreateServer(CreateServerStatement),
37033704
/// ```sql
3705+
/// CREATE FOREIGN DATA WRAPPER
3706+
/// ```
3707+
/// See [PostgreSQL](https://www.postgresql.org/docs/current/sql-createforeigndatawrapper.html)
3708+
CreateForeignDataWrapper(CreateForeignDataWrapper),
3709+
/// ```sql
3710+
/// CREATE FOREIGN TABLE
3711+
/// ```
3712+
/// See [PostgreSQL](https://www.postgresql.org/docs/current/sql-createforeigntable.html)
3713+
CreateForeignTable(CreateForeignTable),
3714+
/// ```sql
37043715
/// CREATE POLICY
37053716
/// ```
37063717
/// See [PostgreSQL](https://www.postgresql.org/docs/current/sql-createpolicy.html)
@@ -5539,6 +5550,8 @@ impl fmt::Display for Statement {
55395550
Statement::CreateServer(stmt) => {
55405551
write!(f, "{stmt}")
55415552
}
5553+
Statement::CreateForeignDataWrapper(stmt) => write!(f, "{stmt}"),
5554+
Statement::CreateForeignTable(stmt) => write!(f, "{stmt}"),
55425555
Statement::CreatePolicy(policy) => write!(f, "{policy}"),
55435556
Statement::CreateConnector(create_connector) => create_connector.fmt(f),
55445557
Statement::CreateOperator(create_operator) => create_operator.fmt(f),

src/ast/spans.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,8 @@ impl Spanned for Statement {
394394
Statement::DropOperatorClass(drop_operator_class) => drop_operator_class.span(),
395395
Statement::CreateSecret { .. } => Span::empty(),
396396
Statement::CreateServer { .. } => Span::empty(),
397+
Statement::CreateForeignDataWrapper { .. } => Span::empty(),
398+
Statement::CreateForeignTable { .. } => Span::empty(),
397399
Statement::CreateConnector { .. } => Span::empty(),
398400
Statement::CreateOperator(create_operator) => create_operator.span(),
399401
Statement::CreateOperatorFamily(create_operator_family) => {

src/keywords.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,7 @@ define_keywords!(
478478
GROUPING,
479479
GROUPS,
480480
GZIP,
481+
HANDLER,
481482
HASH,
482483
HASHES,
483484
HAVING,
@@ -1134,6 +1135,7 @@ define_keywords!(
11341135
VALID,
11351136
VALIDATE,
11361137
VALIDATION_MODE,
1138+
VALIDATOR,
11371139
VALUE,
11381140
VALUES,
11391141
VALUE_OF,

src/parser/mod.rs

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5215,6 +5215,17 @@ impl<'a> Parser<'a> {
52155215
}
52165216
} else if self.parse_keyword(Keyword::SERVER) {
52175217
self.parse_pg_create_server()
5218+
} else if self.parse_keyword(Keyword::FOREIGN) {
5219+
if self.parse_keywords(&[Keyword::DATA, Keyword::WRAPPER]) {
5220+
self.parse_create_foreign_data_wrapper().map(Into::into)
5221+
} else if self.parse_keyword(Keyword::TABLE) {
5222+
self.parse_create_foreign_table().map(Into::into)
5223+
} else {
5224+
self.expected_ref(
5225+
"DATA WRAPPER or TABLE after CREATE FOREIGN",
5226+
self.peek_token_ref(),
5227+
)
5228+
}
52185229
} else if self.parse_keywords(&[Keyword::TEXT, Keyword::SEARCH]) {
52195230
self.parse_create_text_search()
52205231
} else {
@@ -19992,6 +20003,86 @@ impl<'a> Parser<'a> {
1999220003
}))
1999320004
}
1999420005

20006+
/// Parse a `CREATE FOREIGN DATA WRAPPER` statement.
20007+
///
20008+
/// See <https://www.postgresql.org/docs/current/sql-createforeigndatawrapper.html>
20009+
pub fn parse_create_foreign_data_wrapper(
20010+
&mut self,
20011+
) -> Result<CreateForeignDataWrapper, ParserError> {
20012+
let name = self.parse_identifier()?;
20013+
20014+
let handler = if self.parse_keyword(Keyword::HANDLER) {
20015+
Some(FdwRoutineClause::Function(self.parse_object_name(false)?))
20016+
} else if self.parse_keywords(&[Keyword::NO, Keyword::HANDLER]) {
20017+
Some(FdwRoutineClause::NoFunction)
20018+
} else {
20019+
None
20020+
};
20021+
20022+
let validator = if self.parse_keyword(Keyword::VALIDATOR) {
20023+
Some(FdwRoutineClause::Function(self.parse_object_name(false)?))
20024+
} else if self.parse_keywords(&[Keyword::NO, Keyword::VALIDATOR]) {
20025+
Some(FdwRoutineClause::NoFunction)
20026+
} else {
20027+
None
20028+
};
20029+
20030+
let options = if self.parse_keyword(Keyword::OPTIONS) {
20031+
self.expect_token(&Token::LParen)?;
20032+
let opts = self.parse_comma_separated(|p| {
20033+
let key = p.parse_identifier()?;
20034+
let value = p.parse_identifier()?;
20035+
Ok(CreateServerOption { key, value })
20036+
})?;
20037+
self.expect_token(&Token::RParen)?;
20038+
Some(opts)
20039+
} else {
20040+
None
20041+
};
20042+
20043+
Ok(CreateForeignDataWrapper {
20044+
name,
20045+
handler,
20046+
validator,
20047+
options,
20048+
})
20049+
}
20050+
20051+
/// Parse a `CREATE FOREIGN TABLE` statement.
20052+
///
20053+
/// See <https://www.postgresql.org/docs/current/sql-createforeigntable.html>
20054+
pub fn parse_create_foreign_table(
20055+
&mut self,
20056+
) -> Result<CreateForeignTable, ParserError> {
20057+
let if_not_exists =
20058+
self.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]);
20059+
let name = self.parse_object_name(false)?;
20060+
let (columns, _constraints) = self.parse_columns()?;
20061+
self.expect_keyword_is(Keyword::SERVER)?;
20062+
let server_name = self.parse_identifier()?;
20063+
20064+
let options = if self.parse_keyword(Keyword::OPTIONS) {
20065+
self.expect_token(&Token::LParen)?;
20066+
let opts = self.parse_comma_separated(|p| {
20067+
let key = p.parse_identifier()?;
20068+
let value = p.parse_identifier()?;
20069+
Ok(CreateServerOption { key, value })
20070+
})?;
20071+
self.expect_token(&Token::RParen)?;
20072+
Some(opts)
20073+
} else {
20074+
None
20075+
};
20076+
20077+
Ok(CreateForeignTable {
20078+
name,
20079+
if_not_exists,
20080+
columns,
20081+
server_name,
20082+
options,
20083+
})
20084+
}
20085+
1999520086
/// The index of the first unprocessed token.
1999620087
pub fn index(&self) -> usize {
1999720088
self.index

tests/sqlparser_postgres.rs

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9460,6 +9460,102 @@ fn parse_lock_table() {
94609460
}
94619461
}
94629462

9463+
#[test]
9464+
fn parse_create_foreign_data_wrapper() {
9465+
// Minimal: name only.
9466+
let sql = "CREATE FOREIGN DATA WRAPPER myfdw";
9467+
let Statement::CreateForeignDataWrapper(stmt) = pg().verified_stmt(sql) else {
9468+
unreachable!()
9469+
};
9470+
assert_eq!(stmt.name.value, "myfdw");
9471+
assert!(stmt.handler.is_none());
9472+
assert!(stmt.validator.is_none());
9473+
assert!(stmt.options.is_none());
9474+
9475+
// With HANDLER.
9476+
let sql = "CREATE FOREIGN DATA WRAPPER myfdw HANDLER myhandler";
9477+
let Statement::CreateForeignDataWrapper(stmt) = pg().verified_stmt(sql) else {
9478+
unreachable!()
9479+
};
9480+
assert_eq!(
9481+
stmt.handler,
9482+
Some(FdwRoutineClause::Function(ObjectName::from(vec![
9483+
"myhandler".into()
9484+
])))
9485+
);
9486+
9487+
// With NO HANDLER.
9488+
let sql = "CREATE FOREIGN DATA WRAPPER myfdw NO HANDLER";
9489+
let Statement::CreateForeignDataWrapper(stmt) = pg().verified_stmt(sql) else {
9490+
unreachable!()
9491+
};
9492+
assert_eq!(stmt.handler, Some(FdwRoutineClause::NoFunction));
9493+
9494+
// With NO VALIDATOR.
9495+
let sql = "CREATE FOREIGN DATA WRAPPER myfdw NO VALIDATOR";
9496+
let Statement::CreateForeignDataWrapper(stmt) = pg().verified_stmt(sql) else {
9497+
unreachable!()
9498+
};
9499+
assert_eq!(stmt.validator, Some(FdwRoutineClause::NoFunction));
9500+
9501+
// With HANDLER, VALIDATOR, and OPTIONS.
9502+
let sql = "CREATE FOREIGN DATA WRAPPER myfdw HANDLER myhandler VALIDATOR myvalidator OPTIONS (debug 'true')";
9503+
let Statement::CreateForeignDataWrapper(stmt) = pg().verified_stmt(sql) else {
9504+
unreachable!()
9505+
};
9506+
assert_eq!(
9507+
stmt.handler,
9508+
Some(FdwRoutineClause::Function(ObjectName::from(vec![
9509+
"myhandler".into()
9510+
])))
9511+
);
9512+
assert_eq!(
9513+
stmt.validator,
9514+
Some(FdwRoutineClause::Function(ObjectName::from(vec![
9515+
"myvalidator".into()
9516+
])))
9517+
);
9518+
let options = stmt.options.unwrap();
9519+
assert_eq!(options.len(), 1);
9520+
assert_eq!(options[0].key.value, "debug");
9521+
assert_eq!(options[0].value.value, "true");
9522+
}
9523+
9524+
#[test]
9525+
fn parse_create_foreign_table() {
9526+
// Basic: columns and SERVER.
9527+
let sql = "CREATE FOREIGN TABLE ft1 (id INTEGER, name TEXT) SERVER myserver";
9528+
let Statement::CreateForeignTable(stmt) = pg().verified_stmt(sql) else {
9529+
unreachable!()
9530+
};
9531+
assert_eq!(stmt.name.to_string(), "ft1");
9532+
assert!(!stmt.if_not_exists);
9533+
assert_eq!(stmt.columns.len(), 2);
9534+
assert_eq!(stmt.columns[0].name.value, "id");
9535+
assert_eq!(stmt.columns[1].name.value, "name");
9536+
assert_eq!(stmt.server_name.value, "myserver");
9537+
assert!(stmt.options.is_none());
9538+
9539+
// With IF NOT EXISTS.
9540+
let sql = "CREATE FOREIGN TABLE IF NOT EXISTS ft2 (col INTEGER) SERVER remoteserver";
9541+
let Statement::CreateForeignTable(stmt) = pg().verified_stmt(sql) else {
9542+
unreachable!()
9543+
};
9544+
assert!(stmt.if_not_exists);
9545+
assert_eq!(stmt.name.to_string(), "ft2");
9546+
9547+
// With table-level OPTIONS.
9548+
let sql =
9549+
"CREATE FOREIGN TABLE ft3 (col INTEGER) SERVER remoteserver OPTIONS (schema_name 'public')";
9550+
let Statement::CreateForeignTable(stmt) = pg().verified_stmt(sql) else {
9551+
unreachable!()
9552+
};
9553+
let options = stmt.options.unwrap();
9554+
assert_eq!(options.len(), 1);
9555+
assert_eq!(options[0].key.value, "schema_name");
9556+
assert_eq!(options[0].value.value, "public");
9557+
}
9558+
94639559
#[test]
94649560
fn parse_create_aggregate_basic() {
94659561
let sql = "CREATE AGGREGATE myavg (NUMERIC) (SFUNC = numeric_avg_accum, STYPE = internal, FINALFUNC = numeric_avg, INITCOND = '0')";

0 commit comments

Comments
 (0)