From 10c69b910ec3fbb74036b282b7b16bd3b68bc1b3 Mon Sep 17 00:00:00 2001 From: PThorpe92 Date: Thu, 16 Oct 2025 16:39:10 -0400 Subject: [PATCH 1/3] Prevent ambiguous self-join table reference --- core/translate/select.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/core/translate/select.rs b/core/translate/select.rs index 59b6ff6cb..376745f77 100644 --- a/core/translate/select.rs +++ b/core/translate/select.rs @@ -225,6 +225,16 @@ fn prepare_one_select_plan( &mut table_references, connection, )?; + // Validate that all table references have unique identifiers + let mut seen_identifiers = std::collections::HashSet::new(); + for table in table_references.joined_tables().iter() { + if !seen_identifiers.insert(&table.identifier) { + crate::bail_parse_error!( + "table name {} specified more than once - use aliases to distinguish multiple references", + table.identifier + ); + } + } // Preallocate space for the result columns let result_columns = Vec::with_capacity( From 79c5234122ccb4c22e49930c28d1a8ca7bbc74fa Mon Sep 17 00:00:00 2001 From: PThorpe92 Date: Thu, 16 Oct 2025 16:43:08 -0400 Subject: [PATCH 2/3] Add TCL test for self ambiguous join --- testing/select.test | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/testing/select.test b/testing/select.test index 72688913c..c42e38e42 100755 --- a/testing/select.test +++ b/testing/select.test @@ -1074,3 +1074,23 @@ do_execsql_test_on_specific_db {:memory:} rowid-select-from-clause-subquery-expl SELECT rowid,a FROM (SELECT rowid,a FROM t); } {1|abc} +# https://github.com/tursodatabase/turso/issues/3505 regression test +do_execsql_test_in_memory_any_error ambiguous-self-join { + CREATE TABLE T(a); + INSERT INTO t VALUES (1), (2), (3); + SELECT * fROM t JOIN t; +} + +do_execsql_test_on_specific_db {:memory:} unambiguous-self-join { + CREATE TABLE T(a); + INSERT INTO t VALUES (1), (2), (3); + SELECT * fROM t as ta JOIN t order by ta.a; +} {1|1 +1|2 +1|3 +2|1 +2|2 +2|3 +3|1 +3|2 +3|3} From ddd674c34012e1a587a4c44006c4fd97e254775e Mon Sep 17 00:00:00 2001 From: PThorpe92 Date: Thu, 16 Oct 2025 18:32:48 -0400 Subject: [PATCH 3/3] Move duplicate table identifier checking to parse_join to allow for natural joins --- core/translate/planner.rs | 22 +++++++++++++++++++++- core/translate/select.rs | 10 ---------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/core/translate/planner.rs b/core/translate/planner.rs index 08936abf3..f7d930bd6 100644 --- a/core/translate/planner.rs +++ b/core/translate/planner.rs @@ -986,9 +986,29 @@ fn parse_join( crate::bail_parse_error!("NATURAL JOIN cannot be combined with ON or USING clause"); } + // this is called once for each join, so we only need to check the rightmost table + // against all previous tables for duplicates + let rightmost_table = table_references.joined_tables().last().unwrap(); + let has_duplicate = table_references + .joined_tables() + .iter() + .take(table_references.joined_tables().len() - 1) + .any(|t| t.identifier == rightmost_table.identifier); + + if has_duplicate + && !natural + && constraint + .as_ref() + .is_none_or(|c| !matches!(c, ast::JoinConstraint::Using(_))) + { + // Duplicate table names are only allowed for NATURAL or USING joins + crate::bail_parse_error!( + "table name {} specified more than once - use an alias to disambiguate", + rightmost_table.identifier + ); + } let constraint = if natural { assert!(table_references.joined_tables().len() >= 2); - let rightmost_table = table_references.joined_tables().last().unwrap(); // NATURAL JOIN is first transformed into a USING join with the common columns let mut distinct_names: Vec = vec![]; // TODO: O(n^2) maybe not great for large tables or big multiway joins diff --git a/core/translate/select.rs b/core/translate/select.rs index 376745f77..59b6ff6cb 100644 --- a/core/translate/select.rs +++ b/core/translate/select.rs @@ -225,16 +225,6 @@ fn prepare_one_select_plan( &mut table_references, connection, )?; - // Validate that all table references have unique identifiers - let mut seen_identifiers = std::collections::HashSet::new(); - for table in table_references.joined_tables().iter() { - if !seen_identifiers.insert(&table.identifier) { - crate::bail_parse_error!( - "table name {} specified more than once - use aliases to distinguish multiple references", - table.identifier - ); - } - } // Preallocate space for the result columns let result_columns = Vec::with_capacity(