mirror of
https://github.com/aljazceru/turso.git
synced 2026-02-07 01:04:26 +01:00
Support join USING
This commit is contained in:
@@ -61,6 +61,7 @@ This document describes the SQLite compatibility status of Limbo:
|
||||
| SELECT ... CROSS JOIN | Partial | |
|
||||
| SELECT ... INNER JOIN | Partial | |
|
||||
| SELECT ... OUTER JOIN | Partial | |
|
||||
| SELECT ... JOIN USING | Yes | |
|
||||
| UPDATE | No | |
|
||||
| UPSERT | No | |
|
||||
| VACUUM | No | |
|
||||
|
||||
@@ -8,7 +8,7 @@ use sqlite3_parser::ast;
|
||||
|
||||
use crate::{
|
||||
function::AggFunc,
|
||||
schema::{BTreeTable, Index},
|
||||
schema::{BTreeTable, Column, Index},
|
||||
Result,
|
||||
};
|
||||
|
||||
@@ -60,6 +60,64 @@ pub enum IterationDirection {
|
||||
Backwards,
|
||||
}
|
||||
|
||||
impl SourceOperator {
|
||||
pub fn select_star(&self, out_columns: &mut Vec<ResultSetColumn>) {
|
||||
for (table_ref, col, idx) in self.select_star_helper() {
|
||||
out_columns.push(ResultSetColumn {
|
||||
expr: ast::Expr::Column {
|
||||
database: None,
|
||||
table: table_ref.table_index,
|
||||
column: idx,
|
||||
is_rowid_alias: col.primary_key,
|
||||
},
|
||||
contains_aggregates: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// All this ceremony is required to deduplicate columns when joining with USING
|
||||
fn select_star_helper(&self) -> Vec<(&BTreeTableReference, &Column, usize)> {
|
||||
match self {
|
||||
SourceOperator::Join {
|
||||
left, right, using, ..
|
||||
} => {
|
||||
let mut columns = left.select_star_helper();
|
||||
|
||||
// Join columns are filtered out from the right side
|
||||
// in the case of a USING join.
|
||||
if let Some(using_cols) = using {
|
||||
let right_columns = right.select_star_helper();
|
||||
|
||||
for (table_ref, col, idx) in right_columns {
|
||||
if !using_cols
|
||||
.iter()
|
||||
.any(|using_col| col.name.eq_ignore_ascii_case(&using_col.0))
|
||||
{
|
||||
columns.push((table_ref, col, idx));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
columns.extend(right.select_star_helper());
|
||||
}
|
||||
columns
|
||||
}
|
||||
SourceOperator::Scan {
|
||||
table_reference, ..
|
||||
}
|
||||
| SourceOperator::Search {
|
||||
table_reference, ..
|
||||
} => table_reference
|
||||
.table
|
||||
.columns
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, col)| (table_reference, col, i))
|
||||
.collect(),
|
||||
SourceOperator::Nothing => Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
A SourceOperator is a Node in the query plan that reads data from a table.
|
||||
*/
|
||||
@@ -75,6 +133,7 @@ pub enum SourceOperator {
|
||||
right: Box<SourceOperator>,
|
||||
predicates: Option<Vec<ast::Expr>>,
|
||||
outer: bool,
|
||||
using: Option<ast::DistinctNames>,
|
||||
},
|
||||
// Scan operator
|
||||
// This operator is used to scan a table.
|
||||
|
||||
@@ -281,19 +281,7 @@ pub fn prepare_select_plan<'a>(schema: &Schema, select: ast::Select) -> Result<P
|
||||
for column in columns.clone() {
|
||||
match column {
|
||||
ast::ResultColumn::Star => {
|
||||
for table_reference in plan.referenced_tables.iter() {
|
||||
for (idx, col) in table_reference.table.columns.iter().enumerate() {
|
||||
plan.result_columns.push(ResultSetColumn {
|
||||
expr: ast::Expr::Column {
|
||||
database: None, // TODO: support different databases
|
||||
table: table_reference.table_index,
|
||||
column: idx,
|
||||
is_rowid_alias: col.primary_key,
|
||||
},
|
||||
contains_aggregates: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
plan.source.select_star(&mut plan.result_columns);
|
||||
}
|
||||
ast::ResultColumn::TableStar(name) => {
|
||||
let name_normalized = normalize_ident(name.0.as_str());
|
||||
@@ -538,13 +526,14 @@ fn parse_from(
|
||||
|
||||
let mut table_index = 1;
|
||||
for join in from.joins.unwrap_or_default().into_iter() {
|
||||
let (right, outer, predicates) =
|
||||
let (right, outer, using, predicates) =
|
||||
parse_join(schema, join, operator_id_counter, &mut tables, table_index)?;
|
||||
operator = SourceOperator::Join {
|
||||
left: Box::new(operator),
|
||||
right: Box::new(right),
|
||||
predicates,
|
||||
outer,
|
||||
using,
|
||||
id: operator_id_counter.get_next_id(),
|
||||
};
|
||||
table_index += 1;
|
||||
@@ -559,7 +548,12 @@ fn parse_join(
|
||||
operator_id_counter: &mut OperatorIdCounter,
|
||||
tables: &mut Vec<BTreeTableReference>,
|
||||
table_index: usize,
|
||||
) -> Result<(SourceOperator, bool, Option<Vec<ast::Expr>>)> {
|
||||
) -> Result<(
|
||||
SourceOperator,
|
||||
bool,
|
||||
Option<ast::DistinctNames>,
|
||||
Option<Vec<ast::Expr>>,
|
||||
)> {
|
||||
let ast::JoinedSelectTable {
|
||||
operator,
|
||||
table,
|
||||
@@ -599,6 +593,7 @@ fn parse_join(
|
||||
_ => false,
|
||||
};
|
||||
|
||||
let mut using = None;
|
||||
let mut predicates = None;
|
||||
if let Some(constraint) = constraint {
|
||||
match constraint {
|
||||
@@ -610,7 +605,60 @@ fn parse_join(
|
||||
}
|
||||
predicates = Some(preds);
|
||||
}
|
||||
ast::JoinConstraint::Using(_) => todo!("USING joins not supported yet"),
|
||||
ast::JoinConstraint::Using(distinct_names) => {
|
||||
// USING join is replaced with a list of equality predicates
|
||||
let mut using_predicates = vec![];
|
||||
for distinct_name in distinct_names.iter() {
|
||||
let name_normalized = normalize_ident(distinct_name.0.as_str());
|
||||
let left_table = &tables[table_index - 1];
|
||||
let right_table = &tables[table_index];
|
||||
let left_col = left_table
|
||||
.table
|
||||
.columns
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, col)| col.name == name_normalized);
|
||||
if left_col.is_none() {
|
||||
crate::bail_parse_error!(
|
||||
"Column {}.{} not found",
|
||||
left_table.table_identifier,
|
||||
distinct_name.0
|
||||
);
|
||||
}
|
||||
let right_col = right_table
|
||||
.table
|
||||
.columns
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, col)| col.name == name_normalized);
|
||||
if right_col.is_none() {
|
||||
crate::bail_parse_error!(
|
||||
"Column {}.{} not found",
|
||||
right_table.table_identifier,
|
||||
distinct_name.0
|
||||
);
|
||||
}
|
||||
let (left_col_idx, left_col) = left_col.unwrap();
|
||||
let (right_col_idx, right_col) = right_col.unwrap();
|
||||
using_predicates.push(ast::Expr::Binary(
|
||||
Box::new(ast::Expr::Column {
|
||||
database: None,
|
||||
table: left_table.table_index,
|
||||
column: left_col_idx,
|
||||
is_rowid_alias: left_col.primary_key,
|
||||
}),
|
||||
ast::Operator::Equals,
|
||||
Box::new(ast::Expr::Column {
|
||||
database: None,
|
||||
table: right_table.table_index,
|
||||
column: right_col_idx,
|
||||
is_rowid_alias: right_col.primary_key,
|
||||
}),
|
||||
));
|
||||
}
|
||||
predicates = Some(using_predicates);
|
||||
using = Some(distinct_names);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -622,6 +670,7 @@ fn parse_join(
|
||||
iter_dir: None,
|
||||
},
|
||||
outer,
|
||||
using,
|
||||
predicates,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -212,4 +212,12 @@ do_execsql_test join-utilizing-both-seekrowid-and-secondary-index {
|
||||
select u.first_name, p.name from users u join products p on u.id = p.id and u.age > 70;
|
||||
} {Matthew|boots
|
||||
Nicholas|shorts
|
||||
Jamie|hat}
|
||||
Jamie|hat}
|
||||
|
||||
# important difference between regular SELECT * join and a SELECT * USING join is that the join keys are deduplicated
|
||||
# from the result in the USING case.
|
||||
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"}
|
||||
Reference in New Issue
Block a user