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_foreign_table_column_options.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CREATE FOREIGN TABLE ft (id INT OPTIONS (column_name 'remote_id') NOT NULL) SERVER s

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are these files supposed to have an ending new line? I see a mix of both.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both is fine, they are read as plain text basically.

8 changes: 8 additions & 0 deletions src/ast/ddl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2052,6 +2052,11 @@ pub enum ColumnOption {
/// [1]: https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#view_column_option_list
/// [2]: https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#column_option_list
Options(Vec<SqlOption>),
/// PostgreSQL `OPTIONS ( option 'value' [, ...] )` of a column, passed to the
/// foreign-data wrapper of a foreign table.
///
/// [PostgreSQL](https://www.postgresql.org/docs/current/sql-createforeigntable.html)
GenericOptions(Vec<CreateServerOption>),
/// Creates an identity or an autoincrement column in a table.
/// Syntax
/// ```sql
Expand Down Expand Up @@ -2225,6 +2230,9 @@ impl fmt::Display for ColumnOption {
Options(options) => {
write!(f, "OPTIONS({})", display_comma_separated(options))
}
GenericOptions(options) => {
write!(f, "OPTIONS ({})", display_comma_separated(options))
}
Identity(parameters) => {
write!(f, "{parameters}")
}
Expand Down
5 changes: 5 additions & 0 deletions src/ast/spans.rs
Original file line number Diff line number Diff line change
Expand Up @@ -901,6 +901,11 @@ impl Spanned for ColumnOption {
ColumnOption::OnUpdate(expr) => expr.span(),
ColumnOption::Generated { .. } => Span::empty(),
ColumnOption::Options(vec) => union_spans(vec.iter().map(|i| i.span())),
ColumnOption::GenericOptions(options) => union_spans(
options
.iter()
.map(|option| option.key.span.union(&option.value.span)),
),
ColumnOption::Identity(..) => Span::empty(),
ColumnOption::OnConflict(..) => Span::empty(),
ColumnOption::Policy(..) => Span::empty(),
Expand Down
7 changes: 7 additions & 0 deletions src/dialect/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1624,6 +1624,13 @@ pub trait Dialect: Debug + Any {
false
}

/// Returns true if the dialect supports `OPTIONS ( option 'value' [, ...] )`
/// in a column definition.
///
/// <https://www.postgresql.org/docs/current/sql-createforeigntable.html>
fn supports_column_generic_options(&self) -> bool {
false
}
/// Returns true if the specified keyword is reserved and cannot be
/// used as an identifier without special handling like quoting.
fn is_reserved_for_identifier(&self, kw: Keyword) -> bool {
Expand Down
5 changes: 5 additions & 0 deletions src/dialect/postgresql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -618,4 +618,9 @@ impl Dialect for PostgreSqlDialect {
fn supports_table_inheritance_markers(&self) -> bool {
true
}

/// See <https://www.postgresql.org/docs/current/sql-createforeigntable.html>
fn supports_column_generic_options(&self) -> bool {
true
}
}
8 changes: 7 additions & 1 deletion src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10384,6 +10384,12 @@ impl<'a> Parser<'a> {
Ok(Some(ColumnOption::OnUpdate(expr)))
} else if self.parse_keyword(Keyword::GENERATED) {
self.parse_optional_column_option_generated()
} else if self.dialect.supports_column_generic_options()
&& self.peek_keyword(Keyword::OPTIONS)
{
Ok(self
.parse_pg_options_clause()?
.map(ColumnOption::GenericOptions))
} else if dialect_of!(self is BigQueryDialect | GenericDialect)
&& self.parse_keyword(Keyword::OPTIONS)
{
Expand Down Expand Up @@ -21544,7 +21550,7 @@ impl<'a> Parser<'a> {

/// Parse a `CREATE FOREIGN TABLE` statement.
///
/// Per-column `OPTIONS ( ... )` and the `PARTITION OF` form are not parsed yet.
/// The `PARTITION OF` form is 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 Down
58 changes: 58 additions & 0 deletions tests/sqlparser_postgres.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12291,3 +12291,61 @@ fn parse_create_table_partition_of_untyped_columns() {
);
}
}

#[test]
fn parse_column_generic_options() {
let sql = r#"CREATE FOREIGN TABLE ft (id INT OPTIONS (column_name 'remote_id') NOT NULL, b TEXT OPTIONS (column_name 'rb', "x" 'y') COLLATE "C") SERVER s"#;
let Statement::CreateForeignTable(stmt) = pg().verified_stmt(sql) else {
unreachable!()
};
assert_eq!(
stmt.columns[0].options,
vec![
ColumnOptionDef {
name: None,
option: ColumnOption::GenericOptions(vec![CreateServerOption {
key: Ident::new("column_name"),
value: Ident::with_quote('\'', "remote_id"),
}]),
},
ColumnOptionDef {
name: None,
option: ColumnOption::NotNull,
},
]
);
assert_eq!(
stmt.columns[1].options[0].option,
ColumnOption::GenericOptions(vec![
CreateServerOption {
key: Ident::new("column_name"),
value: Ident::with_quote('\'', "rb"),
},
CreateServerOption {
key: Ident::with_quote('"', "x"),
value: Ident::with_quote('\'', "y"),
},
])
);

// PostgreSQL rejects column options on a table that is not foreign only at execution.
pg().verified_stmt("CREATE TABLE t (id INT OPTIONS (column_name 'remote_id'))");
pg().verified_stmt("ALTER TABLE t ADD COLUMN note TEXT OPTIONS (column_name 'memo')");

for (sql, expected) in [
(
"CREATE FOREIGN TABLE ft (id INT OPTIONS (column_name)) SERVER s",
"Expected: identifier, found: )",
),
(
"CREATE FOREIGN TABLE ft (id INT OPTIONS ()) SERVER s",
"Expected: identifier, found: )",
),
] {
assert_eq!(
pg().parse_sql_statements(sql).unwrap_err().to_string(),
format!("sql parser error: {expected}"),
"{sql}"
);
}
}
Loading