Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions fuzz/fuzz_seeds/postgres_create_foreign_table_inherits.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CREATE FOREIGN TABLE child () INHERITS (parent, public.other) SERVER s
9 changes: 8 additions & 1 deletion src/ast/ddl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6305,6 +6305,8 @@ pub struct CreateForeignTable {
/// PostgreSQL's grammar accepts these here, but rejects primary key, unique,
/// foreign key and exclusion constraints on a foreign table at execution.
pub constraints: Vec<TableConstraint>,
/// `INHERITS ( parent_table [, ...] )`
pub inherits: Option<Vec<ObjectName>>,
/// The `SERVER server_name` clause.
pub server_name: Ident,
/// Optional `OPTIONS (key 'value', ...)` clause at the table level.
Expand All @@ -6328,7 +6330,11 @@ impl fmt::Display for CreateForeignTable {
write!(f, ", ")?;
}
write!(f, "{}", display_comma_separated(&self.constraints))?;
write!(f, ") SERVER {}", self.server_name)?;
f.write_str(")")?;
if let Some(inherits) = &self.inherits {
write!(f, " INHERITS ({})", display_comma_separated(inherits))?;
}
write!(f, " SERVER {}", self.server_name)?;
if let Some(options) = &self.options {
write!(f, " OPTIONS ({})", display_comma_separated(options))?;
}
Expand Down Expand Up @@ -6404,6 +6410,7 @@ impl Spanned for CreateForeignTable {
core::iter::once(self.name.span())
.chain(self.columns.iter().map(|column| column.span()))
.chain(self.constraints.iter().map(|constraint| constraint.span()))
.chain(self.inherits.iter().flatten().map(|parent| parent.span()))
.chain(core::iter::once(self.server_name.span))
.chain(
self.options
Expand Down
9 changes: 7 additions & 2 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21343,8 +21343,7 @@ impl<'a> Parser<'a> {

/// Parse a `CREATE FOREIGN TABLE` statement.
///
/// Per-column `OPTIONS ( ... )`, `INHERITS`, and the `PARTITION OF` form are
/// not parsed yet.
/// Per-column `OPTIONS ( ... )` and the `PARTITION OF` form are not parsed yet.
///
/// See <https://www.postgresql.org/docs/current/sql-createforeigntable.html>
pub fn parse_create_foreign_table(&mut self) -> Result<CreateForeignTable, ParserError> {
Expand All @@ -21357,6 +21356,11 @@ impl<'a> Parser<'a> {
);
}
let (columns, constraints) = self.parse_columns()?;
let inherits = if self.parse_keyword(Keyword::INHERITS) {
Some(self.parse_parenthesized_qualified_column_list(IsOptional::Mandatory, false)?)
} else {
None
};
self.expect_keyword_is(Keyword::SERVER)?;
let server_name = self.parse_identifier()?;
let options = self.parse_pg_options_clause()?;
Expand All @@ -21366,6 +21370,7 @@ impl<'a> Parser<'a> {
if_not_exists,
columns,
constraints,
inherits,
server_name,
options,
})
Expand Down
77 changes: 77 additions & 0 deletions tests/sqlparser_postgres.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11405,3 +11405,80 @@ fn parse_non_decimal_integer_token_with_invalid_digits() {
ParserError::ParserError("Could not parse '0xZZ' as an integer".to_string())
);
}

#[test]
fn parse_create_foreign_table_inherits() {
let sql = "CREATE FOREIGN TABLE child (id INT) INHERITS (parent, public.other) SERVER s OPTIONS (schema_name 'public')";
let Statement::CreateForeignTable(stmt) = pg_and_generic().verified_stmt(sql) else {
unreachable!()
};
assert_eq!(
stmt.inherits,
Some(vec![
ObjectName::from(vec![Ident::new("parent")]),
ObjectName::from(vec![Ident::new("public"), Ident::new("other")]),
])
);

// As written by pg_dump.
pg_and_generic().one_statement_parses_to(
"CREATE FOREIGN TABLE public.child (\n)\nINHERITS (public.parent)\nSERVER s",
"CREATE FOREIGN TABLE public.child () INHERITS (public.parent) SERVER s",
);

for (sql, expected) in [
(
"CREATE FOREIGN TABLE child INHERITS (parent) SERVER s",
"Expected: '(' before the column list of CREATE FOREIGN TABLE, found: INHERITS",
),
(
"CREATE FOREIGN TABLE child () INHERITS () SERVER s",
"Expected: identifier, found: )",
),
(
"CREATE FOREIGN TABLE child () INHERITS parent SERVER s",
"Expected: a list of columns in parentheses, found: parent",
),
(
"CREATE FOREIGN TABLE child () INHERITS (parent) OPTIONS (schema_name 'public')",
"Expected: SERVER, found: OPTIONS",
),
(
"CREATE FOREIGN TABLE child () SERVER s INHERITS (parent)",
"Expected: end of statement, found: INHERITS",
),
] {
assert_eq!(
pg_and_generic()
.parse_sql_statements(sql)
.unwrap_err()
.to_string(),
format!("sql parser error: {expected}"),
"{sql}"
);
}
}

#[test]
fn display_create_foreign_table_inherits_propagates_formatter_errors() {
use core::fmt::{self, Write};

struct RejectingWriter(&'static str);

impl Write for RejectingWriter {
fn write_str(&mut self, value: &str) -> fmt::Result {
if value == self.0 {
Err(fmt::Error)
} else {
Ok(())
}
}
}

let stmt =
pg_and_generic().verified_stmt("CREATE FOREIGN TABLE child () INHERITS (parent) SERVER s");
for rejected in [")", " INHERITS (", " SERVER "] {
let mut writer = RejectingWriter(rejected);
assert_eq!(write!(writer, "{stmt}"), Err(fmt::Error), "{rejected:?}");
}
}
Loading