Merge branch 'main' into feature/go-transactions

This commit is contained in:
Jonathan Ness
2025-05-07 12:48:20 -07:00
committed by GitHub
9 changed files with 248 additions and 97 deletions

View File

@@ -6,14 +6,40 @@ This driver uses the awesome [purego](https://github.com/ebitengine/purego) libr
## Embedded Library Support
This driver now includes an embedded library feature that allows you to distribute a single binary without requiring users to set environment variables. The library for your platform is automatically embedded, extracted at runtime, and loaded dynamically.
This driver includes an embedded library feature that allows you to distribute a single binary without requiring users to set environment variables. The library for your platform is automatically embedded, extracted at runtime, and loaded dynamically.
To build with embedded library support:
```
# From the bindings/go directory
### Building from Source
To build with embedded library support, follow these steps:
```bash
# Clone the repository
git clone https://github.com/tursodatabase/limbo
# Navigate to the Go bindings directory
cd limbo/bindings/go
# Build the library (defaults to release build)
./build_lib.sh
# Alternatively, for faster builds during development:
./build_lib.sh debug
```
### Build Options:
* Release Build (default): ./build_lib.sh or ./build_lib.sh release
- Optimized for performance and smaller binary size
- Takes longer to compile and requires more system resources
- Recommended for production use
* Debug Build: ./build_lib.sh debug
- Faster compilation times with less resource usage
- Larger binary size and slower runtime performance
- Recommended during development or if release build fails
If the embedded library cannot be found or extracted, the driver will fall back to the traditional method of finding the library in the system paths.
## To use: (_UNSTABLE_ testing or development purposes only)

View File

@@ -3,7 +3,10 @@
set -e
echo "Building Limbo Go library for current platform..."
# Accept build type as parameter, default to release
BUILD_TYPE=${1:-release}
echo "Building Limbo Go library for current platform (build type: $BUILD_TYPE)..."
# Determine platform-specific details
case "$(uname -s)" in
@@ -43,11 +46,25 @@ esac
OUTPUT_DIR="libs/${PLATFORM}"
mkdir -p "$OUTPUT_DIR"
# Set cargo build arguments based on build type
if [ "$BUILD_TYPE" == "debug" ]; then
CARGO_ARGS=""
TARGET_DIR="debug"
echo "NOTE: Debug builds are faster to compile but less efficient at runtime."
echo " For production use, consider using a release build with: ./build_lib.sh release"
else
CARGO_ARGS="--release"
TARGET_DIR="release"
echo "NOTE: Release builds may take longer to compile and require more system resources."
echo " If this build fails or takes too long, try a debug build with: ./build_lib.sh debug"
fi
# Build the library
cargo build --package limbo-go
echo "Running cargo build ${CARGO_ARGS} --package limbo-go"
cargo build ${CARGO_ARGS} --package limbo-go
# Copy to the appropriate directory
echo "Copying $OUTPUT_NAME to $OUTPUT_DIR/"
cp "../../target/debug/$OUTPUT_NAME" "$OUTPUT_DIR/"
cp "../../target/${TARGET_DIR}/$OUTPUT_NAME" "$OUTPUT_DIR/"
echo "Library built successfully for $PLATFORM"
echo "Library built successfully for $PLATFORM ($BUILD_TYPE build)"

View File

@@ -20,7 +20,7 @@ use super::expr::{translate_condition_expr, translate_expr, ConditionMetadata};
use super::group_by::{emit_group_by, init_group_by, GroupByMetadata};
use super::main_loop::{close_loop, emit_loop, init_loop, open_loop, LeftJoinMetadata, LoopLabels};
use super::order_by::{emit_order_by, init_order_by, SortMetadata};
use super::plan::{Operation, SelectPlan, TableReference, UpdatePlan};
use super::plan::{JoinOrderMember, Operation, SelectPlan, TableReference, UpdatePlan};
use super::subquery::emit_subqueries;
#[derive(Debug)]
@@ -285,7 +285,11 @@ pub fn emit_query<'a>(
OperationMode::SELECT,
)?;
for where_term in plan.where_clause.iter().filter(|wt| wt.is_constant()) {
for where_term in plan
.where_clause
.iter()
.filter(|wt| wt.is_constant(&plan.join_order))
{
let jump_target_when_true = program.allocate_label();
let condition_metadata = ConditionMetadata {
jump_if_condition_is_true: false,
@@ -303,13 +307,19 @@ pub fn emit_query<'a>(
}
// Set up main query execution loop
open_loop(program, t_ctx, &plan.table_references, &plan.where_clause)?;
open_loop(
program,
t_ctx,
&plan.table_references,
&plan.join_order,
&plan.where_clause,
)?;
// Process result columns and expressions in the inner loop
emit_loop(program, t_ctx, plan)?;
// Clean up and close the main execution loop
close_loop(program, t_ctx, &plan.table_references)?;
close_loop(program, t_ctx, &plan.table_references, &plan.join_order)?;
program.preassign_label_to_next_insn(after_main_loop_label);
let mut order_by_necessary = plan.order_by.is_some() && !plan.contains_constant_false_condition;
@@ -374,6 +384,7 @@ fn emit_program_for_delete(
program,
&mut t_ctx,
&plan.table_references,
&[JoinOrderMember::default()],
&plan.where_clause,
)?;
emit_delete_insns(
@@ -385,7 +396,12 @@ fn emit_program_for_delete(
)?;
// Clean up and close the main execution loop
close_loop(program, &mut t_ctx, &plan.table_references)?;
close_loop(
program,
&mut t_ctx,
&plan.table_references,
&[JoinOrderMember::default()],
)?;
program.preassign_label_to_next_insn(after_main_loop_label);
// Finalize program
@@ -571,10 +587,16 @@ fn emit_program_for_update(
program,
&mut t_ctx,
&plan.table_references,
&[JoinOrderMember::default()],
&plan.where_clause,
)?;
emit_update_insns(&plan, &t_ctx, program, index_cursors)?;
close_loop(program, &mut t_ctx, &plan.table_references)?;
close_loop(
program,
&mut t_ctx,
&plan.table_references,
&[JoinOrderMember::default()],
)?;
program.preassign_label_to_next_insn(after_main_loop_label);
// Finalize program
@@ -615,7 +637,11 @@ fn emit_update_insns(
_ => return Ok(()),
};
for cond in plan.where_clause.iter().filter(|c| c.is_constant()) {
for cond in plan
.where_clause
.iter()
.filter(|c| c.is_constant(&[JoinOrderMember::default()]))
{
let jump_target = program.allocate_label();
let meta = ConditionMetadata {
jump_if_condition_is_true: false,
@@ -664,7 +690,11 @@ fn emit_update_insns(
});
}
for cond in plan.where_clause.iter().filter(|c| c.is_constant()) {
for cond in plan
.where_clause
.iter()
.filter(|c| c.is_constant(&[JoinOrderMember::default()]))
{
let meta = ConditionMetadata {
jump_if_condition_is_true: false,
jump_target_when_true: BranchOffset::Placeholder,

View File

@@ -26,8 +26,8 @@ use super::{
optimizer::Optimizable,
order_by::{order_by_sorter_insert, sorter_insert},
plan::{
convert_where_to_vtab_constraint, IterationDirection, Operation, Search, SeekDef,
SelectPlan, SelectQueryType, TableReference, WhereTerm,
convert_where_to_vtab_constraint, IterationDirection, JoinOrderMember, Operation, Search,
SeekDef, SelectPlan, SelectQueryType, TableReference, WhereTerm,
},
};
@@ -199,9 +199,12 @@ pub fn open_loop(
program: &mut ProgramBuilder,
t_ctx: &mut TranslateCtx,
tables: &[TableReference],
join_order: &[JoinOrderMember],
predicates: &[WhereTerm],
) -> Result<()> {
for (table_index, table) in tables.iter().enumerate() {
for (join_index, join) in join_order.iter().enumerate() {
let table_index = join.table_no;
let table = &tables[table_index];
let LoopLabels {
loop_start,
loop_end,
@@ -253,7 +256,7 @@ pub fn open_loop(
for cond in predicates
.iter()
.filter(|cond| cond.should_eval_at_loop(table_index))
.filter(|cond| cond.should_eval_at_loop(join_index, join_order))
{
let jump_target_when_true = program.allocate_label();
let condition_metadata = ConditionMetadata {
@@ -306,7 +309,7 @@ pub fn open_loop(
// We then materialise the RHS/LHS into registers before issuing VFilter.
let converted_constraints = predicates
.iter()
.filter(|p| p.should_eval_at_loop(table_index))
.filter(|p| p.should_eval_at_loop(join_index, join_order))
.enumerate()
.filter_map(|(i, p)| {
// Build ConstraintInfo from the predicates
@@ -408,7 +411,8 @@ pub fn open_loop(
}
for (_, cond) in predicates.iter().enumerate().filter(|(i, cond)| {
cond.should_eval_at_loop(table_index) && !t_ctx.omit_predicates.contains(i)
cond.should_eval_at_loop(join_index, join_order)
&& !t_ctx.omit_predicates.contains(i)
}) {
let jump_target_when_true = program.allocate_label();
let condition_metadata = ConditionMetadata {
@@ -516,7 +520,7 @@ pub fn open_loop(
for cond in predicates
.iter()
.filter(|cond| cond.should_eval_at_loop(table_index))
.filter(|cond| cond.should_eval_at_loop(join_index, join_order))
{
let jump_target_when_true = program.allocate_label();
let condition_metadata = ConditionMetadata {
@@ -792,6 +796,7 @@ pub fn close_loop(
program: &mut ProgramBuilder,
t_ctx: &mut TranslateCtx,
tables: &[TableReference],
join_order: &[JoinOrderMember],
) -> Result<()> {
// We close the loops for all tables in reverse order, i.e. innermost first.
// OPEN t1
@@ -801,8 +806,9 @@ pub fn close_loop(
// CLOSE t3
// CLOSE t2
// CLOSE t1
for (idx, table) in tables.iter().rev().enumerate() {
let table_index = tables.len() - idx - 1;
for join in join_order.iter().rev() {
let table_index = join.table_no;
let table = &tables[table_index];
let loop_labels = *t_ctx
.labels_main_loop
.get(table_index)

View File

@@ -13,8 +13,8 @@ use crate::{
use super::{
emitter::Resolver,
plan::{
DeletePlan, EvalAt, GroupBy, IterationDirection, Operation, Plan, Search, SeekDef, SeekKey,
SelectPlan, TableReference, UpdatePlan, WhereTerm,
DeletePlan, EvalAt, GroupBy, IterationDirection, JoinOrderMember, Operation, Plan, Search,
SeekDef, SeekKey, SelectPlan, TableReference, UpdatePlan, WhereTerm,
},
planner::determine_where_to_eval_expr,
};
@@ -281,6 +281,15 @@ fn use_indexes(
let did_eliminate_orderby =
eliminate_unnecessary_orderby(table_references, available_indexes, order_by, group_by)?;
let join_order = table_references
.iter()
.enumerate()
.map(|(i, t)| JoinOrderMember {
table_no: i,
is_outer: t.join_info.as_ref().map_or(false, |j| j.outer),
})
.collect::<Vec<_>>();
// Try to use indexes for WHERE conditions
for (table_index, table_reference) in table_references.iter_mut().enumerate() {
if matches!(table_reference.op, Operation::Scan { .. }) {
@@ -303,6 +312,7 @@ fn use_indexes(
table_index,
table_reference,
&available_indexes,
&join_order,
)? {
table_reference.op = Operation::Search(search);
}
@@ -317,6 +327,7 @@ fn use_indexes(
&mut where_clause[i],
table_index,
table_reference,
&join_order,
)? {
where_clause.remove(i);
table_reference.op = Operation::Search(search);
@@ -341,6 +352,7 @@ fn use_indexes(
table_index,
table_reference,
usable_indexes_ref,
&join_order,
)? {
table_reference.op = Operation::Search(search);
}
@@ -389,7 +401,7 @@ fn eliminate_constant_conditions(
} else if predicate.expr.is_always_false()? {
// any false predicate in a list of conjuncts (AND-ed predicates) will make the whole list false,
// except an outer join condition, because that just results in NULLs, not skipping the whole loop
if predicate.from_outer_join {
if predicate.from_outer_join.is_some() {
i += 1;
continue;
}
@@ -872,6 +884,7 @@ pub fn try_extract_index_search_from_where_clause(
table_index: usize,
table_reference: &TableReference,
table_indexes: &[Arc<Index>],
join_order: &[JoinOrderMember],
) -> Result<Option<Search>> {
// If there are no WHERE terms, we can't extract a search
if where_clause.is_empty() {
@@ -901,7 +914,13 @@ pub fn try_extract_index_search_from_where_clause(
for index in table_indexes {
// Check how many terms in the where clause constrain the index in column order
find_index_constraints(where_clause, table_index, index, &mut constraints_cur)?;
find_index_constraints(
where_clause,
table_index,
index,
join_order,
&mut constraints_cur,
)?;
// naive scoring since we don't have statistics: prefer the index where we can use the most columns
// e.g. if we can use all columns of an index on (a,b), it's better than an index of (c,d,e) where we can only use c.
let cost = dumb_cost_estimator(
@@ -925,7 +944,7 @@ pub fn try_extract_index_search_from_where_clause(
// let's see if building an ephemeral index would be better.
if best_index.index.is_none() {
let (ephemeral_cost, constraints_with_col_idx, mut constraints_without_col_idx) =
ephemeral_index_estimate_cost(where_clause, table_reference, table_index);
ephemeral_index_estimate_cost(where_clause, table_reference, table_index, join_order);
if ephemeral_cost < best_index.cost {
// ephemeral index makes sense, so let's build it now.
// ephemeral columns are: columns from the table_reference, constraints first, then the rest
@@ -970,11 +989,12 @@ fn ephemeral_index_estimate_cost(
where_clause: &mut Vec<WhereTerm>,
table_reference: &TableReference,
table_index: usize,
join_order: &[JoinOrderMember],
) -> (f64, Vec<(usize, IndexConstraint)>, Vec<IndexConstraint>) {
let mut constraints_with_col_idx: Vec<(usize, IndexConstraint)> = where_clause
.iter()
.enumerate()
.filter(|(_, term)| is_potential_index_constraint(term, table_index))
.filter(|(_, term)| is_potential_index_constraint(term, table_index, join_order))
.filter_map(|(i, term)| {
let Ok(ast::Expr::Binary(lhs, operator, rhs)) = unwrap_parens(&term.expr) else {
panic!("expected binary expression");
@@ -1179,9 +1199,13 @@ fn get_column_position_in_index(
Ok(index.column_table_pos_to_index_pos(*column))
}
fn is_potential_index_constraint(term: &WhereTerm, table_index: usize) -> bool {
fn is_potential_index_constraint(
term: &WhereTerm,
table_index: usize,
join_order: &[JoinOrderMember],
) -> bool {
// Skip terms that cannot be evaluated at this table's loop level
if !term.should_eval_at_loop(table_index) {
if !term.should_eval_at_loop(table_index, join_order) {
return false;
}
// Skip terms that are not binary comparisons
@@ -1206,10 +1230,10 @@ fn is_potential_index_constraint(term: &WhereTerm, table_index: usize) -> bool {
// - WHERE t.x > t.y
// - WHERE t.x + 1 > t.y - 5
// - WHERE t.x = (t.x)
let Ok(eval_at_left) = determine_where_to_eval_expr(&lhs) else {
let Ok(eval_at_left) = determine_where_to_eval_expr(&lhs, join_order) else {
return false;
};
let Ok(eval_at_right) = determine_where_to_eval_expr(&rhs) else {
let Ok(eval_at_right) = determine_where_to_eval_expr(&rhs, join_order) else {
return false;
};
if eval_at_left == EvalAt::Loop(table_index) && eval_at_right == EvalAt::Loop(table_index) {
@@ -1226,12 +1250,13 @@ fn find_index_constraints(
where_clause: &mut Vec<WhereTerm>,
table_index: usize,
index: &Arc<Index>,
join_order: &[JoinOrderMember],
out_constraints: &mut Vec<IndexConstraint>,
) -> Result<()> {
for position_in_index in 0..index.columns.len() {
let mut found = false;
for (position_in_where_clause, term) in where_clause.iter().enumerate() {
if !is_potential_index_constraint(term, table_index) {
if !is_potential_index_constraint(term, table_index, join_order) {
continue;
}
@@ -1748,13 +1773,14 @@ pub fn try_extract_rowid_search_expression(
cond: &mut WhereTerm,
table_index: usize,
table_reference: &TableReference,
join_order: &[JoinOrderMember],
) -> Result<Option<Search>> {
let iter_dir = if let Operation::Scan { iter_dir, .. } = &table_reference.op {
*iter_dir
} else {
return Ok(None);
};
if !cond.should_eval_at_loop(table_index) {
if !cond.should_eval_at_loop(table_index, join_order) {
return Ok(None);
}
match &mut cond.expr {
@@ -1764,8 +1790,8 @@ pub fn try_extract_rowid_search_expression(
// - WHERE t.x > t.y
// - WHERE t.x + 1 > t.y - 5
// - WHERE t.x = (t.x)
if determine_where_to_eval_expr(lhs)? == EvalAt::Loop(table_index)
&& determine_where_to_eval_expr(rhs)? == EvalAt::Loop(table_index)
if determine_where_to_eval_expr(lhs, join_order)? == EvalAt::Loop(table_index)
&& determine_where_to_eval_expr(rhs, join_order)? == EvalAt::Loop(table_index)
{
return Ok(None);
}
@@ -1777,7 +1803,6 @@ pub fn try_extract_rowid_search_expression(
cmp_expr: WhereTerm {
expr: rhs_owned,
from_outer_join: cond.from_outer_join,
eval_at: cond.eval_at,
},
}));
}
@@ -1805,7 +1830,6 @@ pub fn try_extract_rowid_search_expression(
cmp_expr: WhereTerm {
expr: lhs_owned,
from_outer_join: cond.from_outer_join,
eval_at: cond.eval_at,
},
}));
}

View File

@@ -23,7 +23,7 @@ use crate::{
util::can_pushdown_predicate,
};
use super::emitter::OperationMode;
use super::{emitter::OperationMode, planner::determine_where_to_eval_term};
#[derive(Debug, Clone)]
pub struct ResultSetColumn {
@@ -77,21 +77,30 @@ pub struct GroupBy {
pub struct WhereTerm {
/// The original condition expression.
pub expr: ast::Expr,
/// Is this condition originally from an OUTER JOIN?
/// Is this condition originally from an OUTER JOIN, and which table number in the plan's [TableReference] vector?
/// If so, we need to evaluate it at the loop of the right table in that JOIN,
/// regardless of which tables it references.
/// We also cannot e.g. short circuit the entire query in the optimizer if the condition is statically false.
pub from_outer_join: bool,
pub eval_at: EvalAt,
pub from_outer_join: Option<usize>,
}
impl WhereTerm {
pub fn is_constant(&self) -> bool {
self.eval_at == EvalAt::BeforeLoop
pub fn is_constant(&self, join_order: &[JoinOrderMember]) -> bool {
let Ok(eval_at) = self.eval_at(join_order) else {
return false;
};
eval_at == EvalAt::BeforeLoop
}
pub fn should_eval_at_loop(&self, loop_idx: usize) -> bool {
self.eval_at == EvalAt::Loop(loop_idx)
pub fn should_eval_at_loop(&self, loop_idx: usize, join_order: &[JoinOrderMember]) -> bool {
let Ok(eval_at) = self.eval_at(join_order) else {
return false;
};
eval_at == EvalAt::Loop(loop_idx)
}
fn eval_at(&self, join_order: &[JoinOrderMember]) -> Result<EvalAt> {
determine_where_to_eval_term(&self, join_order)
}
}
@@ -141,7 +150,7 @@ pub fn convert_where_to_vtab_constraint(
table_index: usize,
pred_idx: usize,
) -> Option<ConstraintInfo> {
if term.from_outer_join {
if term.from_outer_join.is_some() {
return None;
}
let Expr::Binary(lhs, op, rhs) = &term.expr else {
@@ -255,10 +264,29 @@ pub enum SelectQueryType {
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct JoinOrderMember {
/// The index of the table in the plan's vector of [TableReference]
pub table_no: usize,
/// Whether this member is the right side of an OUTER JOIN
pub is_outer: bool,
}
impl Default for JoinOrderMember {
fn default() -> Self {
Self {
table_no: 0,
is_outer: false,
}
}
}
#[derive(Debug, Clone)]
pub struct SelectPlan {
/// List of table references in loop order, outermost first.
pub table_references: Vec<TableReference>,
/// The order in which the tables are joined. Tables have usize Ids (their index in table_references)
pub join_order: Vec<JoinOrderMember>,
/// the columns inside SELECT ... FROM
pub result_columns: Vec<ResultSetColumn>,
/// where clause split into a vec at 'AND' boundaries. all join conditions also get shoved in here,

View File

@@ -1,7 +1,7 @@
use super::{
plan::{
Aggregate, ColumnUsedMask, EvalAt, IterationDirection, JoinInfo, Operation, Plan,
ResultSetColumn, SelectPlan, SelectQueryType, TableReference, WhereTerm,
Aggregate, ColumnUsedMask, EvalAt, IterationDirection, JoinInfo, JoinOrderMember,
Operation, Plan, ResultSetColumn, SelectPlan, SelectQueryType, TableReference, WhereTerm,
},
select::prepare_select_plan,
SymbolTable,
@@ -554,11 +554,9 @@ pub fn parse_where(
bind_column_references(expr, table_references, result_columns)?;
}
for expr in predicates {
let eval_at = determine_where_to_eval_expr(&expr)?;
out_where_clause.push(WhereTerm {
expr,
from_outer_join: false,
eval_at,
from_outer_join: None,
});
}
Ok(())
@@ -574,15 +572,38 @@ pub fn parse_where(
For expressions not referencing any tables (e.g. constants), this is before the main loop is
opened, because they do not need any table data.
*/
pub fn determine_where_to_eval_expr<'a>(predicate: &'a ast::Expr) -> Result<EvalAt> {
pub fn determine_where_to_eval_term(
term: &WhereTerm,
join_order: &[JoinOrderMember],
) -> Result<EvalAt> {
if let Some(table_no) = term.from_outer_join {
return Ok(EvalAt::Loop(
join_order
.iter()
.position(|t| t.table_no == table_no)
.unwrap(),
));
}
return determine_where_to_eval_expr(&term.expr, join_order);
}
pub fn determine_where_to_eval_expr<'a>(
expr: &'a Expr,
join_order: &[JoinOrderMember],
) -> Result<EvalAt> {
let mut eval_at: EvalAt = EvalAt::BeforeLoop;
match predicate {
match expr {
ast::Expr::Binary(e1, _, e2) => {
eval_at = eval_at.max(determine_where_to_eval_expr(e1)?);
eval_at = eval_at.max(determine_where_to_eval_expr(e2)?);
eval_at = eval_at.max(determine_where_to_eval_expr(e1, join_order)?);
eval_at = eval_at.max(determine_where_to_eval_expr(e2, join_order)?);
}
ast::Expr::Column { table, .. } | ast::Expr::RowId { table, .. } => {
eval_at = eval_at.max(EvalAt::Loop(*table));
let join_idx = join_order
.iter()
.position(|t| t.table_no == *table)
.unwrap();
eval_at = eval_at.max(EvalAt::Loop(join_idx));
}
ast::Expr::Id(_) => {
/* Id referring to column will already have been rewritten as an Expr::Column */
@@ -593,30 +614,30 @@ pub fn determine_where_to_eval_expr<'a>(predicate: &'a ast::Expr) -> Result<Eval
}
ast::Expr::Literal(_) => {}
ast::Expr::Like { lhs, rhs, .. } => {
eval_at = eval_at.max(determine_where_to_eval_expr(lhs)?);
eval_at = eval_at.max(determine_where_to_eval_expr(rhs)?);
eval_at = eval_at.max(determine_where_to_eval_expr(lhs, join_order)?);
eval_at = eval_at.max(determine_where_to_eval_expr(rhs, join_order)?);
}
ast::Expr::FunctionCall {
args: Some(args), ..
} => {
for arg in args {
eval_at = eval_at.max(determine_where_to_eval_expr(arg)?);
eval_at = eval_at.max(determine_where_to_eval_expr(arg, join_order)?);
}
}
ast::Expr::InList { lhs, rhs, .. } => {
eval_at = eval_at.max(determine_where_to_eval_expr(lhs)?);
eval_at = eval_at.max(determine_where_to_eval_expr(lhs, join_order)?);
if let Some(rhs_list) = rhs {
for rhs_expr in rhs_list {
eval_at = eval_at.max(determine_where_to_eval_expr(rhs_expr)?);
eval_at = eval_at.max(determine_where_to_eval_expr(rhs_expr, join_order)?);
}
}
}
Expr::Between {
lhs, start, end, ..
} => {
eval_at = eval_at.max(determine_where_to_eval_expr(lhs)?);
eval_at = eval_at.max(determine_where_to_eval_expr(start)?);
eval_at = eval_at.max(determine_where_to_eval_expr(end)?);
eval_at = eval_at.max(determine_where_to_eval_expr(lhs, join_order)?);
eval_at = eval_at.max(determine_where_to_eval_expr(start, join_order)?);
eval_at = eval_at.max(determine_where_to_eval_expr(end, join_order)?);
}
Expr::Case {
base,
@@ -624,21 +645,21 @@ pub fn determine_where_to_eval_expr<'a>(predicate: &'a ast::Expr) -> Result<Eval
else_expr,
} => {
if let Some(base) = base {
eval_at = eval_at.max(determine_where_to_eval_expr(base)?);
eval_at = eval_at.max(determine_where_to_eval_expr(base, join_order)?);
}
for (when, then) in when_then_pairs {
eval_at = eval_at.max(determine_where_to_eval_expr(when)?);
eval_at = eval_at.max(determine_where_to_eval_expr(then)?);
eval_at = eval_at.max(determine_where_to_eval_expr(when, join_order)?);
eval_at = eval_at.max(determine_where_to_eval_expr(then, join_order)?);
}
if let Some(else_expr) = else_expr {
eval_at = eval_at.max(determine_where_to_eval_expr(else_expr)?);
eval_at = eval_at.max(determine_where_to_eval_expr(else_expr, join_order)?);
}
}
Expr::Cast { expr, .. } => {
eval_at = eval_at.max(determine_where_to_eval_expr(expr)?);
eval_at = eval_at.max(determine_where_to_eval_expr(expr, join_order)?);
}
Expr::Collate(expr, _) => {
eval_at = eval_at.max(determine_where_to_eval_expr(expr)?);
eval_at = eval_at.max(determine_where_to_eval_expr(expr, join_order)?);
}
Expr::DoublyQualified(_, _, _) => {
unreachable!("DoublyQualified should be resolved to a Column before resolving eval_at")
@@ -648,7 +669,7 @@ pub fn determine_where_to_eval_expr<'a>(predicate: &'a ast::Expr) -> Result<Eval
}
Expr::FunctionCall { args, .. } => {
for arg in args.as_ref().unwrap_or(&vec![]).iter() {
eval_at = eval_at.max(determine_where_to_eval_expr(arg)?);
eval_at = eval_at.max(determine_where_to_eval_expr(arg, join_order)?);
}
}
Expr::FunctionCallStar { .. } => {}
@@ -659,15 +680,15 @@ pub fn determine_where_to_eval_expr<'a>(predicate: &'a ast::Expr) -> Result<Eval
todo!("in table not supported yet")
}
Expr::IsNull(expr) => {
eval_at = eval_at.max(determine_where_to_eval_expr(expr)?);
eval_at = eval_at.max(determine_where_to_eval_expr(expr, join_order)?);
}
Expr::Name(_) => {}
Expr::NotNull(expr) => {
eval_at = eval_at.max(determine_where_to_eval_expr(expr)?);
eval_at = eval_at.max(determine_where_to_eval_expr(expr, join_order)?);
}
Expr::Parenthesized(exprs) => {
for expr in exprs.iter() {
eval_at = eval_at.max(determine_where_to_eval_expr(expr)?);
eval_at = eval_at.max(determine_where_to_eval_expr(expr, join_order)?);
}
}
Expr::Raise(_, _) => {
@@ -677,7 +698,7 @@ pub fn determine_where_to_eval_expr<'a>(predicate: &'a ast::Expr) -> Result<Eval
todo!("subquery not supported yet")
}
Expr::Unary(_, expr) => {
eval_at = eval_at.max(determine_where_to_eval_expr(expr)?);
eval_at = eval_at.max(determine_where_to_eval_expr(expr, join_order)?);
}
Expr::Variable(_) => {}
}
@@ -765,16 +786,13 @@ fn parse_join<'a>(
bind_column_references(predicate, &mut scope.tables, None)?;
}
for pred in preds {
let cur_table_idx = scope.tables.len() - 1;
let eval_at = if outer {
EvalAt::Loop(cur_table_idx)
} else {
determine_where_to_eval_expr(&pred)?
};
out_where_clause.push(WhereTerm {
expr: pred,
from_outer_join: outer,
eval_at,
from_outer_join: if outer {
Some(scope.tables.len() - 1)
} else {
None
},
});
}
}
@@ -841,15 +859,9 @@ fn parse_join<'a>(
left_table.mark_column_used(left_col_idx);
let right_table = scope.tables.get_mut(cur_table_idx).unwrap();
right_table.mark_column_used(right_col_idx);
let eval_at = if outer {
EvalAt::Loop(cur_table_idx)
} else {
determine_where_to_eval_expr(&expr)?
};
out_where_clause.push(WhereTerm {
expr,
from_outer_join: outer,
eval_at,
from_outer_join: if outer { Some(cur_table_idx) } else { None },
});
}
using = Some(distinct_names);

View File

@@ -1,5 +1,5 @@
use super::emitter::emit_program;
use super::plan::{select_star, Operation, Search, SelectQueryType};
use super::plan::{select_star, JoinOrderMember, Operation, Search, SelectQueryType};
use super::planner::Scope;
use crate::function::{AggFunc, ExtFunc, Func};
use crate::translate::optimizer::optimize_plan;
@@ -87,6 +87,14 @@ pub fn prepare_select_plan<'a>(
);
let mut plan = SelectPlan {
join_order: table_references
.iter()
.enumerate()
.map(|(i, t)| JoinOrderMember {
table_no: i,
is_outer: t.join_info.as_ref().map_or(false, |j| j.outer),
})
.collect(),
table_references,
result_columns,
where_clause: where_predicates,

View File

@@ -1050,13 +1050,13 @@ pub fn insn_to_str(
rowid_reg,
prev_largest_reg,
} => (
"NewRowId",
"NewRowid",
*cursor as i32,
*rowid_reg as i32,
*prev_largest_reg as i32,
OwnedValue::build_text(""),
0,
"".to_string(),
format!("r[{}]=rowid", rowid_reg),
),
Insn::MustBeInt { reg } => (
"MustBeInt",