Skip to content
Merged
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 CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
* **format:** keep a sign touching a modulo, which DuckDB reads as one operator ([50734ca](https://github.com/sqlstack-io/sqlquack/commit/50734ca952e0864bc16b81492372438312b32557))
* **format:** keep the extra indent of a tighter operator under a looser one ([bab166c](https://github.com/sqlstack-io/sqlquack/commit/bab166c50a5b714bb485f051b500f9a96cec752b))
* **semantics:** leave every set operation's outputs without a range ([23c1099](https://github.com/sqlstack-io/sqlquack/commit/23c10991e3994b0f612f5828664f58b0ddf06580))
* **syntax-coverage:** fail on a not-submitted entry whose case has a receipt ([cde63cd](https://github.com/sqlstack-io/sqlquack/commit/cde63cdf46fbcdaace287b6be8f208649e7c01f5))
* **syntax-receipts:** count a run-database packet's repeated template once ([ee638f5](https://github.com/sqlstack-io/sqlquack/commit/ee638f5a8f02ada8f4634293ffe13879447f764d))
* **syntax-receipts:** document the phase rules in order and keep timeouts refused ([c3a5599](https://github.com/sqlstack-io/sqlquack/commit/c3a5599d6aeb63a0b5dafba320cccb8eca81c9cd))
* **syntax-receipts:** import execute runs that have no run database ([01ace82](https://github.com/sqlstack-io/sqlquack/commit/01ace826bc50e5d04b5ab967cac3d74a106fa59b))
Expand Down
1 change: 1 addition & 0 deletions coverage/sql-size/baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -6300,6 +6300,7 @@
"relational/cross_join_then_outer_join.toml#cross_join_then_full_join_on_first_table": 1177,
"relational/cross_join_then_outer_join.toml#cross_join_then_right_join_on": 1178,
"relational/cross_join_then_outer_join.toml#join_without_on_then_full_join_on": 1177,
"relational/cross_join_then_outer_join.toml#join_without_on_then_full_join_using": 1265,
"relational/fixture_shapes.toml#not_in_subquery_of_literal_rows_in_case": 1076,
"relational/fixture_shapes.toml#null_cast_to_semi_structured": 1773,
"relational/fixture_shapes.toml#union_all_null_columns": 630,
Expand Down
176 changes: 176 additions & 0 deletions coverage/syntax/baseline.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions coverage/syntax/marks/security_admin.jsonl
Original file line number Diff line number Diff line change
Expand Up @@ -813,6 +813,7 @@
{"case_id":"sf.security_admin.create-catalog-integration-snowflake-postgres.kd3b6289631ac","mark":"settled accept","obligations":["sf.security_admin.create-catalog-integration-snowflake-postgres.o021"],"sql_sha256":"d3b6289631acb213c0549960c0bf113f5ca25ffb358dc67eca72b8861772c643"}
{"case_id":"sf.security_admin.create-catalog-integration-snowflake-postgres.kf2017157f5d9","mark":"settled accept","obligations":["sf.security_admin.create-catalog-integration-snowflake-postgres.o016"],"sql_sha256":"f2017157f5d97633d7284b5310278388c16f61a4c0e0bd7311e0118e1286abea"}
{"case_id":"sf.security_admin.create-connection.connection-as-replica-of.k4421eb8bc37c","mark":"settled accept","obligations":["sf.security_admin.create-connection.connection-as-replica-of.o020"],"sql_sha256":"4421eb8bc37c2c0a0f21f7f8aa9a3dd9a551e65e33d133493a41dcfbd21b5b0b"}
{"case_id":"sf.security_admin.create-connection.connection.kfb03d64ed639","mark":"settled accept","obligations":["sf.security_admin.create-connection.connection.o011"],"sql_sha256":"fb03d64ed6396ba3e71dcca206a39f86e34c736fc2fa3f94e9e402d42eea746f"}
{"case_id":"sf.security_admin.create-data-movement-policy.k1ba1f56f7a1d","mark":"settled accept","obligations":["sf.security_admin.create-data-movement-policy.o027"],"sql_sha256":"1ba1f56f7a1d3fc09ef44f74f6066f6aa6b57beb4dbf81d8f4896bd1f59317c7"}
{"case_id":"sf.security_admin.create-data-movement-policy.k41b47a952ca8","mark":"settled accept","obligations":["sf.security_admin.create-data-movement-policy.o043"],"sql_sha256":"41b47a952ca8e059f82047c50208787ea3e30e4b8067a2125b211c329fe3b9b8"}
{"case_id":"sf.security_admin.create-data-movement-policy.ke3831a274fa4","mark":"settled accept","obligations":["sf.security_admin.create-data-movement-policy.o044"],"sql_sha256":"e3831a274fa46de682a0ee45afffec43175bdee441d59c8edaef34af7b09b53b"}
Expand Down
1,846 changes: 317 additions & 1,529 deletions coverage/syntax/unobservable/security_admin.toml

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions coverage/translation/results-baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -6649,6 +6649,8 @@
"relational/cross_join_then_outer_join.toml#cross_join_then_full_join_on_first_table": "matching",
"relational/cross_join_then_outer_join.toml#cross_join_then_right_join_on": "matching",
"relational/cross_join_then_outer_join.toml#join_without_on_then_full_join_on": "matching",
"relational/cross_join_then_outer_join.toml#join_without_on_then_full_join_using": "matching",
"relational/cross_join_then_outer_join.toml#join_without_on_then_right_join_using": "refused",
"relational/fixture_shapes.toml#not_in_subquery_filtered_not_null_in_case": "matching_rejection",
"relational/fixture_shapes.toml#not_in_subquery_in_case": "matching_rejection",
"relational/fixture_shapes.toml#not_in_subquery_of_literal_rows_in_case": "matching",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ pub const RULES: &[RuleSpec] = &[
"join:left",
"join:full",
],
conditions: "No condition: CROSS JOIN, a comma, or JOIN, INNER JOIN, LEFT JOIN or FULL JOIN without ON, which Snowflake runs as a cross join, with no rows when either side is empty, as recorded; a later RIGHT or FULL JOIN with ON applies to the whole join before it, as recorded; one before a FULL JOIN with USING refuses, as that is unrecorded.",
conditions: "No condition: CROSS JOIN, a comma, or JOIN, INNER JOIN, LEFT JOIN or FULL JOIN without ON, which Snowflake runs as a cross join, with no rows when either side is empty, as recorded; a later RIGHT or FULL JOIN with ON, or FULL JOIN with USING, applies to the whole join before it, as recorded.",
failures: &[],
},
RuleSpec {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,12 +123,10 @@ pub(crate) fn convert_from(
sql.push_str(&source_sql);
relations.push(relation);
}
let joins = list(select, "joins");
for (index, join) in joins.iter().enumerate() {
for join in list(select, "joins") {
let before = events();
let merging = RefCell::new(None);
let using_follows = joins[index + 1..].iter().any(full_join_using);
let joined = convert_join(join, &relations, (outer, &merging, using_follows), context)
let joined = convert_join(join, &relations, (outer, &merging), context)
.map_err(|refusal| context.locator.placed(refusal, join));
let source = join.get("this").unwrap_or(&Value::Null);
let (join_sql, relation) = recovered(joined, source, (before, relations.len()))?;
Expand Down Expand Up @@ -170,19 +168,10 @@ struct Merging {
relation: Option<ScopeRelation>,
}

/// Whether `join` is a FULL JOIN with USING, whose binding after a join without ON is unrecorded.
fn full_join_using(join: &Value) -> bool {
join.get("kind").and_then(Value::as_str) == Some("Full")
&& join
.get("using")
.and_then(Value::as_array)
.is_some_and(|using| !using.is_empty())
}

fn convert_join(
join: &Value,
earlier: &[ScopeRelation],
(outer, merging, using_follows): (Option<&Scope<'_>>, &RefCell<Option<Merging>>, bool),
(outer, merging): (Option<&Scope<'_>>, &RefCell<Option<Merging>>),
context: &Context<'_>,
) -> Result<(String, ScopeRelation), Refusal> {
let join_construct = node_construct("join");
Expand Down Expand Up @@ -234,14 +223,6 @@ fn convert_join(
("Left", Some(_)) => "LEFT JOIN",
("Right", Some(_)) => "RIGHT JOIN",
("Full", Some(_)) => "FULL JOIN",
("Inner" | "Left" | "Full", None) if using_follows => {
return Err(missing(
Construct::new(ConstructFamily::Join, kind.to_ascii_lowercase()),
format!(
"a {kind} join without ON before a FULL JOIN with USING, which no receipt records"
),
));
}
("Cross" | "Inner" | "Left" | "Full", None) => "CROSS JOIN",
("Implicit", None) => ",",
_ => {
Expand Down
30 changes: 13 additions & 17 deletions crates/sqlquack/tests/cli/test_joins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ fn given_join_without_on_when_converted_and_run_on_duckdb_then_it_is_a_cross_joi
LEFT JOIN (SELECT 1 AS k) r RIGHT JOIN (SELECT 1 AS k2 UNION ALL SELECT 9) w ON w.k2 = r.k",
expected_value: "3",
},
ValueTestCase {
description: "a FULL JOIN with USING after a JOIN without ON applies to the whole join so far, as recorded",
sql: "SELECT TO_VARCHAR(COUNT(*)) AS n FROM (SELECT 1 AS x UNION ALL SELECT 2) a \
JOIN (SELECT 1 AS k) r FULL JOIN (SELECT 1 AS k UNION ALL SELECT 9) w USING (k)",
expected_value: "3",
},
ValueTestCase {
description: "FULL JOIN without ON over two non-empty sides pairs every row",
sql: "SELECT TO_VARCHAR(COUNT(*)) AS n FROM (SELECT 1 AS x UNION ALL SELECT 2) l \
Expand All @@ -71,23 +77,13 @@ fn given_join_without_on_when_converted_and_run_on_duckdb_then_it_is_a_cross_joi

#[test]
fn given_a_join_without_on_snowflake_has_not_recorded_when_converting_then_it_refuses() {
let test_cases = [
ConvertTestCase {
description: "RIGHT JOIN without ON, which no receipt records, refuses",
sql: "SELECT l.x AS x FROM (SELECT 1 AS x) l RIGHT JOIN (SELECT 1 AS y) r",
flags: &[],
mention: "no rule converts a Right join of this shape",
expected_outcome: convert_outcome(1, false),
},
ConvertTestCase {
description: "a JOIN without ON before a FULL JOIN with USING, which no receipt records, refuses",
sql: "SELECT a.x AS x FROM (SELECT 1 AS x) a JOIN (SELECT 1 AS k) r \
FULL JOIN (SELECT 1 AS k) w USING (k)",
flags: &[],
mention: "before a FULL JOIN with USING",
expected_outcome: convert_outcome(1, false),
},
];
let test_cases = [ConvertTestCase {
description: "RIGHT JOIN without ON, which no receipt records, refuses",
sql: "SELECT l.x AS x FROM (SELECT 1 AS x) l RIGHT JOIN (SELECT 1 AS y) r",
flags: &[],
mention: "no rule converts a Right join of this shape",
expected_outcome: convert_outcome(1, false),
}];
for test_case in test_cases {
assert_eq!(
run_convert(test_case.sql, test_case.flags, test_case.mention),
Expand Down
4 changes: 2 additions & 2 deletions evidence.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# The evidence SQLQuack's checks read: one commit of the evidence repository, and the SHA-256 of its files.
repository = "https://github.com/sqlstack-io/sqlquack-evidence.git"
commit = "742cb3eb170d447b0a87577ab54b10caa3c7c97d"
content_sha256 = "58cdcf0d1f0b565742fa4fdf46050bd6125917bd3f78bf92f0d0d7f277dce4c1"
commit = "d3db2e7f19b9e9da6aebc888b03f8a269b5ee8e9"
content_sha256 = "5c64d568da7a32156b12c973524fb07ea36d54cf2fde522b73ed385b65599065"
Loading