diff --git a/COMPAT.md b/COMPAT.md index 765a46a9e..fe0d3315f 100644 --- a/COMPAT.md +++ b/COMPAT.md @@ -62,6 +62,7 @@ This document describes the SQLite compatibility status of Limbo: | SELECT ... INNER JOIN | Partial | | | SELECT ... OUTER JOIN | Partial | | | SELECT ... JOIN USING | Yes | | +| SELECT ... NATURAL JOIN | Yes | | | UPDATE | No | | | UPSERT | No | | | VACUUM | No | | diff --git a/core/translate/planner.rs b/core/translate/planner.rs index aee72a954..fdafc2e3c 100644 --- a/core/translate/planner.rs +++ b/core/translate/planner.rs @@ -582,19 +582,54 @@ fn parse_join( tables.push(table.clone()); - let outer = match operator { + let (outer, natural) = match operator { ast::JoinOperator::TypedJoin(Some(join_type)) => { - if join_type == JoinType::LEFT | JoinType::OUTER { - true - } else { - join_type == JoinType::RIGHT | JoinType::OUTER - } + let is_outer = join_type.contains(JoinType::OUTER); + let is_natural = join_type.contains(JoinType::NATURAL); + (is_outer, is_natural) } - _ => false, + _ => (false, false), }; let mut using = None; let mut predicates = None; + + if natural && constraint.is_some() { + crate::bail_parse_error!("NATURAL JOIN cannot be combined with ON or USING clause"); + } + + let constraint = if natural { + // NATURAL JOIN is first transformed into a USING join with the common columns + let left_table = &tables[table_index - 1]; + let right_table = &tables[table_index]; + let left_cols = &left_table.table.columns; + let right_cols = &right_table.table.columns; + let mut distinct_names = None; + // TODO: O(n^2) maybe not great for large tables + for left_col in left_cols.iter() { + for right_col in right_cols.iter() { + if left_col.name == right_col.name { + if distinct_names.is_none() { + distinct_names = + Some(ast::DistinctNames::new(ast::Name(left_col.name.clone()))); + } else { + distinct_names + .as_mut() + .unwrap() + .insert(ast::Name(left_col.name.clone())) + .unwrap(); + } + } + } + } + if distinct_names.is_none() { + crate::bail_parse_error!("No columns found to NATURAL join on"); + } + Some(ast::JoinConstraint::Using(distinct_names.unwrap())) + } else { + constraint + }; + if let Some(constraint) = constraint { match constraint { ast::JoinConstraint::On(expr) => { diff --git a/testing/join.test b/testing/join.test index 2bbf1b4fa..2bb3a872d 100755 --- a/testing/join.test +++ b/testing/join.test @@ -220,4 +220,11 @@ do_execsql_test join-using { select * from users join products using (id) limit 3; } {"1|Jamie|Foster|dylan00@example.com|496-522-9493|62375 Johnson Rest Suite 322|West Lauriestad|IL|35865|94|hat|79.0 2|Cindy|Salazar|williamsrebecca@example.com|287-934-1135|75615 Stacey Shore|South Stephanie|NC|85181|37|cap|82.0 +3|Tommy|Perry|warechristopher@example.org|001-288-554-8139x0276|2896 Paul Fall Apt. 972|Michaelborough|VA|15691|18|shirt|18.0"} + +# NATURAL JOIN desugars to JOIN USING (common_column1, common_column2...) +do_execsql_test join-using { + select * from users natural join products limit 3; +} {"1|Jamie|Foster|dylan00@example.com|496-522-9493|62375 Johnson Rest Suite 322|West Lauriestad|IL|35865|94|hat|79.0 +2|Cindy|Salazar|williamsrebecca@example.com|287-934-1135|75615 Stacey Shore|South Stephanie|NC|85181|37|cap|82.0 3|Tommy|Perry|warechristopher@example.org|001-288-554-8139x0276|2896 Paul Fall Apt. 972|Michaelborough|VA|15691|18|shirt|18.0"} \ No newline at end of file