Skip to content

Commit 243996d

Browse files
committed
Merge remote-tracking branch 'github/main' into fix/coverage-receipt-join
2 parents 9d732b9 + c793813 commit 243996d

5 files changed

Lines changed: 78 additions & 6 deletions

File tree

‎crates/sqlquack-syntax/src/expressions/schema_objects/models.rs‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,9 @@ impl AlterIndex {
282282
pub struct CreateSchema {
283283
/// Schema name parts, possibly dot-qualified (e.g. [mydb, hr] for "mydb.hr")
284284
pub name: Vec<Identifier>,
285+
/// OR REPLACE (Snowflake)
286+
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
287+
pub or_replace: bool,
285288
pub if_not_exists: bool,
286289
pub authorization: Option<Identifier>,
287290
/// CLONE source parts, possibly dot-qualified
@@ -302,6 +305,7 @@ impl CreateSchema {
302305
pub fn new(name: impl Into<String>) -> Self {
303306
Self {
304307
name: vec![Identifier::new(name)],
308+
or_replace: false,
305309
if_not_exists: false,
306310
authorization: None,
307311
clone_from: None,
@@ -356,6 +360,9 @@ impl DropNamespace {
356360
#[derive(sqlquack_syntax_derive::AstNode, Debug, Clone, PartialEq, Serialize, Deserialize)]
357361
pub struct CreateDatabase {
358362
pub name: Identifier,
363+
/// OR REPLACE (Snowflake)
364+
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
365+
pub or_replace: bool,
359366
pub if_not_exists: bool,
360367
pub options: Vec<DatabaseOption>,
361368
/// Snowflake CLONE source
@@ -384,6 +391,7 @@ impl CreateDatabase {
384391
pub fn new(name: impl Into<String>) -> Self {
385392
Self {
386393
name: Identifier::new(name),
394+
or_replace: false,
387395
if_not_exists: false,
388396
options: Vec::new(),
389397
clone_from: None,

‎crates/sqlquack-syntax/src/parser/_helpers/ddl/commands.rs‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1209,6 +1209,7 @@ impl Parser {
12091209
pub(crate) fn parse_create_schema(
12101210
&mut self,
12111211
leading_comments: Vec<String>,
1212+
or_replace: bool,
12121213
) -> Result<Expression> {
12131214
self.expect_token(TokenType::Schema)?;
12141215

@@ -1271,6 +1272,7 @@ impl Parser {
12711272

12721273
Ok(Expression::CreateSchema(Box::new(CreateSchema {
12731274
name,
1275+
or_replace,
12741276
if_not_exists,
12751277
authorization,
12761278
clone_from,
@@ -1299,12 +1301,16 @@ impl Parser {
12991301
}
13001302

13011303
/// Parse CREATE DATABASE statement
1302-
pub(crate) fn parse_create_database(&mut self) -> Result<Expression> {
1304+
pub(crate) fn parse_create_database(&mut self, or_replace: bool) -> Result<Expression> {
13031305
self.expect_token(TokenType::Database)?;
13041306

13051307
let if_not_exists =
13061308
self.match_keywords(&[TokenType::If, TokenType::Not, TokenType::Exists]);
1307-
let name = self.parse_unquoted_identifier(Self::expect_identifier)?;
1309+
let name = if self.is_snowflake() {
1310+
self.parse_quoting_identifier(Self::expect_identifier)?
1311+
} else {
1312+
self.parse_unquoted_identifier(Self::expect_identifier)?
1313+
};
13081314

13091315
let clone_from = if self.match_identifier("CLONE") {
13101316
Some(self.expect_identifier_or_safe_keyword_with_quoted()?)
@@ -1378,6 +1384,7 @@ impl Parser {
13781384

13791385
Ok(Expression::CreateDatabase(Box::new(CreateDatabase {
13801386
name,
1387+
or_replace,
13811388
if_not_exists,
13821389
options,
13831390
clone_from,

‎crates/sqlquack-syntax/src/parser/_helpers/ddl/snowflake_create_clone.rs‎

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ impl Parser {
5757
if self.match_token(TokenType::Eq) {
5858
self.expect_token(TokenType::LParen)?;
5959
let mut kinds: Vec<Expression> = Vec::new();
60-
loop {
60+
while !self.check(TokenType::RParen) || !kinds.is_empty() {
6161
let kind =
6262
self.expect_alter_words(&[&["COMMENTS"], &["TAGS"], &["GRANTS"]])?;
6363
kinds.push(Expression::Identifier(Identifier::new(kind)));
@@ -234,7 +234,8 @@ impl Parser {
234234
None if !temporary && self.match_text_seq(&["FILE", "FORMAT"]) => {
235235
("FILE FORMAT", CloneOptions::Bare)
236236
}
237-
None if !temporary && self.match_text_seq(&["DATABASE", "ROLE"]) => {
237+
None if !temporary && self.names_a_database_role() => {
238+
self.match_text_seq(&["DATABASE", "ROLE"]);
238239
("DATABASE ROLE", CloneOptions::Bare)
239240
}
240241
_ => {
@@ -293,6 +294,14 @@ impl Parser {
293294
}))))
294295
}
295296

297+
/// Whether DATABASE ROLE starts a role; alone or before CLONE, ROLE names a database.
298+
fn names_a_database_role(&self) -> bool {
299+
self.check_text_seq(&["DATABASE", "ROLE"])
300+
&& self.tokens.get(self.current + 2).is_some_and(|t| {
301+
t.token_type != TokenType::Semicolon && !t.text.eq_ignore_ascii_case("CLONE")
302+
})
303+
}
304+
296305
/// Whether the rest of the statement contains REPLICA or AS OF, which make it a replica.
297306
fn statement_names_a_replica(&self) -> bool {
298307
self.tokens[self.current..]

‎crates/sqlquack-syntax/src/parser/_helpers/statements/dml.rs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -357,8 +357,8 @@ impl Parser {
357357
}
358358
}
359359
TokenType::Index => self.parse_create_index_with_clustered(false, None),
360-
TokenType::Schema => self.parse_create_schema(leading_comments),
361-
TokenType::Database => self.parse_create_database(),
360+
TokenType::Schema => self.parse_create_schema(leading_comments, or_replace),
361+
TokenType::Database => self.parse_create_database(or_replace),
362362
TokenType::Function => {
363363
self.parse_create_function(or_replace, or_alter, temporary, false)
364364
}

‎crates/sqlquack-syntax/tests/parser/test_snowflake_create_clone.rs‎

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,30 @@ fn given_a_snowflake_clone_or_replica_when_parsing_then_only_documented_forms_ar
3232
dialect: Dialect::Snowflake,
3333
expected_accepted: true,
3434
},
35+
AcceptanceTestCase {
36+
description: "CREATE DATABASE ROLE alone creates a database named ROLE (recorded success)",
37+
sql: "CREATE DATABASE ROLE ;",
38+
dialect: Dialect::Snowflake,
39+
expected_accepted: true,
40+
},
41+
AcceptanceTestCase {
42+
description: "a database named ROLE clones (recorded errno 2003, after parsing)",
43+
sql: "CREATE DATABASE ROLE CLONE v1 ;",
44+
dialect: Dialect::Snowflake,
45+
expected_accepted: true,
46+
},
47+
AcceptanceTestCase {
48+
description: "COPY = ( ) names no kind (recorded errno 1422, after parsing)",
49+
sql: "CREATE STREAM v1 CLONE v1 COPY = ( ) ;",
50+
dialect: Dialect::Snowflake,
51+
expected_accepted: true,
52+
},
53+
AcceptanceTestCase {
54+
description: "a COPY list does not end in a comma",
55+
sql: "CREATE TABLE v1 CLONE v1 COPY = ( TAGS , ) ;",
56+
dialect: Dialect::Snowflake,
57+
expected_accepted: false,
58+
},
3559
AcceptanceTestCase {
3660
description: "a role has no CLONE (recorded errno 1003)",
3761
sql: "CREATE ROLE v1 CLONE v1 ;",
@@ -179,6 +203,30 @@ fn given_a_snowflake_clone_or_replica_when_parsing_then_the_tree_keeps_its_parts
179203
pointer: "/0/create_object/source",
180204
expected_value: json!("REPLICA"),
181205
},
206+
ProjectionTestCase {
207+
description: "a database named ROLE clones as a database",
208+
sql: "CREATE DATABASE ROLE CLONE d2",
209+
pointer: "/0/create_database/name/name",
210+
expected_value: json!("ROLE"),
211+
},
212+
ProjectionTestCase {
213+
description: "a database clone keeps OR REPLACE",
214+
sql: "CREATE OR REPLACE DATABASE d1 CLONE d2",
215+
pointer: "/0/create_database/or_replace",
216+
expected_value: json!(true),
217+
},
218+
ProjectionTestCase {
219+
description: "a database clone keeps its name's quoting",
220+
sql: "CREATE DATABASE \"v1 Q\" CLONE d2",
221+
pointer: "/0/create_database/name/quoted",
222+
expected_value: json!(true),
223+
},
224+
ProjectionTestCase {
225+
description: "a schema clone keeps OR REPLACE",
226+
sql: "CREATE OR REPLACE SCHEMA s1 CLONE s2",
227+
pointer: "/0/create_schema/or_replace",
228+
expected_value: json!(true),
229+
},
182230
];
183231

184232
for test_case in test_cases {

0 commit comments

Comments
 (0)