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_2742_create_extension_schema.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CREATE EXTENSION hstore VERSION '1.8' SCHEMA exts;
23 changes: 12 additions & 11 deletions src/ast/ddl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4616,6 +4616,8 @@ pub struct CreateExtension {
pub name: Ident,
/// Whether `IF NOT EXISTS` was specified for the CREATE EXTENSION.
pub if_not_exists: bool,
/// Whether the optional `WITH` keyword preceded the options.
pub with_keyword: bool,
/// Whether `CASCADE` was specified for the CREATE EXTENSION.
pub cascade: bool,
/// Optional schema name for the extension.
Expand All @@ -4636,18 +4638,17 @@ impl fmt::Display for CreateExtension {
},
name = self.name
)?;
if self.cascade || self.schema.is_some() || self.version.is_some() {
if self.with_keyword {
write!(f, " WITH")?;

if let Some(name) = &self.schema {
write!(f, " SCHEMA {name}")?;
}
if let Some(version) = &self.version {
write!(f, " VERSION {version}")?;
}
if self.cascade {
write!(f, " CASCADE")?;
}
}
if let Some(name) = &self.schema {
write!(f, " SCHEMA {name}")?;
}
if let Some(version) = &self.version {
write!(f, " VERSION {version}")?;
}
if self.cascade {
write!(f, " CASCADE")?;
}

Ok(())
Expand Down
36 changes: 19 additions & 17 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8932,29 +8932,31 @@ impl<'a> Parser<'a> {
let if_not_exists = self.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]);
let name = self.parse_identifier()?;

let (schema, version, cascade) = if self.parse_keyword(Keyword::WITH) {
let schema = if self.parse_keyword(Keyword::SCHEMA) {
Some(self.parse_identifier()?)
} else {
None
let with_keyword = self.parse_keyword(Keyword::WITH);
let mut schema = None;
let mut version = None;
let mut cascade = false;
loop {
let loc = self.peek_token_ref().span.start;
let Some(keyword) =
self.parse_one_of_keywords(&[Keyword::SCHEMA, Keyword::VERSION, Keyword::CASCADE])
else {
break;
};

let version = if self.parse_keyword(Keyword::VERSION) {
Some(self.parse_identifier()?)
} else {
None
let already_set = match keyword {
Keyword::SCHEMA => schema.replace(self.parse_identifier()?).is_some(),
Keyword::VERSION => version.replace(self.parse_identifier()?).is_some(),
_ => core::mem::replace(&mut cascade, true),
};

let cascade = self.parse_keyword(Keyword::CASCADE);

(schema, version, cascade)
} else {
(None, None, false)
};
if already_set {
return parser_err!(format!("{keyword:?} specified more than once"), loc);
}
}

Ok(CreateExtension {
name,
if_not_exists,
with_keyword,
schema,
version,
cascade,
Expand Down
88 changes: 88 additions & 0 deletions tests/sqlparser_postgres.rs
Original file line number Diff line number Diff line change
Expand Up @@ -886,6 +886,94 @@ fn parse_create_extension() {
pg_and_generic().verified_stmt("CREATE EXTENSION extension_name WITH VERSION version CASCADE");
pg_and_generic()
.verified_stmt("CREATE EXTENSION extension_name WITH SCHEMA schema_name VERSION version");

pg_and_generic().verified_stmt("CREATE EXTENSION extension_name WITH");
pg_and_generic().verified_stmt("CREATE EXTENSION extension_name SCHEMA schema_name");
pg_and_generic().verified_stmt("CREATE EXTENSION extension_name VERSION version");
pg_and_generic().verified_stmt("CREATE EXTENSION extension_name CASCADE");
pg_and_generic().verified_stmt(
"CREATE EXTENSION IF NOT EXISTS extension_name SCHEMA schema_name VERSION version CASCADE",
);
pg_and_generic().verified_stmt("CREATE EXTENSION \"uuid-ossp\" VERSION '1.1'");
pg_and_generic().one_statement_parses_to(
"CREATE EXTENSION pgcrypto WITH VERSION '1.3' SCHEMA exts",
"CREATE EXTENSION pgcrypto WITH SCHEMA exts VERSION '1.3'",
);
pg_and_generic().one_statement_parses_to(
"CREATE EXTENSION pgcrypto CASCADE SCHEMA exts",
"CREATE EXTENSION pgcrypto SCHEMA exts CASCADE",
);

for sql in [
"CREATE EXTENSION extension_name WITH SCHEMA schema_name",
"CREATE EXTENSION extension_name SCHEMA schema_name",
] {
match pg_and_generic().verified_stmt(sql) {
Statement::CreateExtension(CreateExtension { schema, .. }) => {
assert_eq!(schema, Some(Ident::new("schema_name")));
}
other => panic!("unexpected statement: {other:?}"),
}
}

for (sql, error) in [
(
"CREATE EXTENSION extension_name WITH WITH SCHEMA schema_name",
"Expected: end of statement, found: WITH",
),
("CREATE EXTENSION", "Expected: identifier, found: EOF"),
(
"CREATE EXTENSION e SCHEMA",
"Expected: identifier, found: EOF",
),
(
"CREATE EXTENSION e VERSION",
"Expected: identifier, found: EOF",
),
] {
assert_eq!(
pg_and_generic().parse_sql_statements(sql).unwrap_err(),
ParserError::ParserError(error.to_string()),
"{sql}"
);
}
for (sql, keyword) in [
("CREATE EXTENSION e SCHEMA a SCHEMA b", "SCHEMA"),
("CREATE EXTENSION e VERSION a CASCADE VERSION b", "VERSION"),
(
"CREATE EXTENSION e WITH CASCADE SCHEMA a CASCADE",
"CASCADE",
),
] {
assert_eq!(
pg_and_generic().parse_sql_statements(sql).unwrap_err(),
ParserError::ParserError(format!("{keyword} specified more than once")),
);
}
}

#[test]
fn display_create_extension_propagates_write_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.contains(self.0) {
Err(fmt::Error)
} else {
Ok(())
}
}
}

let statement =
pg_and_generic().verified_stmt("CREATE EXTENSION e WITH SCHEMA s VERSION v CASCADE");
for rejected in ["CREATE", "WITH", "SCHEMA", "VERSION", "CASCADE"] {
let mut writer = RejectingWriter(rejected);
assert_eq!(write!(writer, "{statement}"), Err(fmt::Error), "{rejected}");
}
}

#[test]
Expand Down
Loading