From d0da7307be40e0ff915cb5e9abea3dfc088c78d6 Mon Sep 17 00:00:00 2001 From: Jussi Saurio Date: Tue, 15 Apr 2025 20:33:57 +0300 Subject: [PATCH 1/8] Index: add new field ephemeral: bool --- core/schema.rs | 3 +++ core/translate/index.rs | 1 + 2 files changed, 4 insertions(+) diff --git a/core/schema.rs b/core/schema.rs index 0a5a8d80f..dd09671ab 100644 --- a/core/schema.rs +++ b/core/schema.rs @@ -692,6 +692,7 @@ pub struct Index { pub root_page: usize, pub columns: Vec, pub unique: bool, + pub ephemeral: bool, } #[allow(dead_code)] @@ -741,6 +742,7 @@ impl Index { root_page, columns: index_columns, unique, + ephemeral: false, }) } _ => todo!("Expected create index statement"), @@ -783,6 +785,7 @@ impl Index { root_page, columns: index_columns, unique: true, // Primary key indexes are always unique + ephemeral: false, }) } diff --git a/core/translate/index.rs b/core/translate/index.rs index de79aed23..55222e40f 100644 --- a/core/translate/index.rs +++ b/core/translate/index.rs @@ -62,6 +62,7 @@ pub fn translate_create_index( }) .collect(), unique: unique_if_not_exists.0, + ephemeral: false, }); // Allocate the necessary cursors: From 09ad6d8f0149020fe1a5c81832e021ad095782a5 Mon Sep 17 00:00:00 2001 From: Jussi Saurio Date: Wed, 16 Apr 2025 14:23:13 +0300 Subject: [PATCH 2/8] vdbe: resolve labels for Insn::Once --- core/vdbe/builder.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/core/vdbe/builder.rs b/core/vdbe/builder.rs index 648044d1d..05fdc4938 100644 --- a/core/vdbe/builder.rs +++ b/core/vdbe/builder.rs @@ -363,6 +363,12 @@ impl ProgramBuilder { Insn::Next { pc_if_next, .. } => { resolve(pc_if_next, "Next"); } + Insn::Once { + target_pc_when_reentered, + .. + } => { + resolve(target_pc_when_reentered, "Once"); + } Insn::Prev { pc_if_prev, .. } => { resolve(pc_if_prev, "Prev"); } From c1b2dfc32b17fcd75f756b16a909dd4581b3c213 Mon Sep 17 00:00:00 2001 From: Jussi Saurio Date: Sat, 19 Apr 2025 12:20:25 +0300 Subject: [PATCH 3/8] TableReference: add method column_is_used() --- core/translate/plan.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/translate/plan.rs b/core/translate/plan.rs index 07a8de392..46d4d38da 100644 --- a/core/translate/plan.rs +++ b/core/translate/plan.rs @@ -590,6 +590,10 @@ impl TableReference { }; self.index_is_covering(index.as_ref()) } + + pub fn column_is_used(&self, index: usize) -> bool { + self.col_used_mask.get(index) + } } /// A definition of a rowid/index search. From af21f60887bc3fce20930c7c152def5c0b619450 Mon Sep 17 00:00:00 2001 From: Jussi Saurio Date: Wed, 16 Apr 2025 14:22:54 +0300 Subject: [PATCH 4/8] translate/main_loop: create autoindex when index.ephemeral=true --- core/translate/main_loop.rs | 135 ++++++++++++++++++++++++++++++------ core/translate/plan.rs | 5 +- 2 files changed, 119 insertions(+), 21 deletions(-) diff --git a/core/translate/main_loop.rs b/core/translate/main_loop.rs index 7354eb4a1..c56680446 100644 --- a/core/translate/main_loop.rs +++ b/core/translate/main_loop.rs @@ -1,14 +1,16 @@ use limbo_ext::VTabKind; use limbo_sqlite3_parser::ast; +use std::sync::Arc; + use crate::{ - schema::Table, + schema::{Index, Table}, translate::result_row::emit_select_result, types::SeekOp, vdbe::{ builder::ProgramBuilder, - insn::{CmpInsFlags, Insn}, - BranchOffset, + insn::{CmpInsFlags, IdxInsertFlags, Insn}, + BranchOffset, CursorID, }, Result, }; @@ -156,23 +158,26 @@ pub fn init_loop( index: Some(index), .. } = search { - match mode { - OperationMode::SELECT => { - program.emit_insn(Insn::OpenRead { - cursor_id: index_cursor_id - .expect("index cursor is always opened in Seek with index"), - root_page: index.root_page, - }); - } - OperationMode::UPDATE | OperationMode::DELETE => { - program.emit_insn(Insn::OpenWrite { - cursor_id: index_cursor_id - .expect("index cursor is always opened in Seek with index"), - root_page: index.root_page.into(), - }); - } - _ => { - unimplemented!() + // Ephemeral index cursor are opened ad-hoc when needed. + if !index.ephemeral { + match mode { + OperationMode::SELECT => { + program.emit_insn(Insn::OpenRead { + cursor_id: index_cursor_id + .expect("index cursor is always opened in Seek with index"), + root_page: index.root_page, + }); + } + OperationMode::UPDATE | OperationMode::DELETE => { + program.emit_insn(Insn::OpenWrite { + cursor_id: index_cursor_id + .expect("index cursor is always opened in Seek with index"), + root_page: index.root_page.into(), + }); + } + _ => { + unimplemented!() + } } } } @@ -437,6 +442,32 @@ pub fn open_loop( }); } else { // Otherwise, it's an index/rowid scan, i.e. first a seek is performed and then a scan until the comparison expression is not satisfied anymore. + if let Search::Seek { + index: Some(index), .. + } = search + { + if index.ephemeral { + let table_has_rowid = if let Table::BTree(btree) = &table.table { + btree.has_rowid + } else { + false + }; + Some(emit_autoindex( + program, + &index, + table_cursor_id + .expect("an ephemeral index must have a source table cursor"), + index_cursor_id + .expect("an ephemeral index must have an index cursor"), + table_has_rowid, + )?) + } else { + index_cursor_id + } + } else { + index_cursor_id + }; + let is_index = index_cursor_id.is_some(); let seek_cursor_id = index_cursor_id.unwrap_or_else(|| { table_cursor_id.expect("Either index or table cursor must be opened") @@ -1125,3 +1156,67 @@ fn emit_seek_termination( Ok(()) } + +/// Open an ephemeral index cursor and build an automatic index on a table. +/// This is used as a last-resort to avoid a nested full table scan +/// Returns the cursor id of the ephemeral index cursor. +fn emit_autoindex( + program: &mut ProgramBuilder, + index: &Arc, + table_cursor_id: CursorID, + index_cursor_id: CursorID, + table_has_rowid: bool, +) -> Result { + assert!(index.ephemeral, "Index {} is not ephemeral", index.name); + let label_ephemeral_build_end = program.allocate_label(); + // Since this typically happens in an inner loop, we only build it once. + program.emit_insn(Insn::Once { + target_pc_when_reentered: label_ephemeral_build_end, + }); + program.emit_insn(Insn::OpenAutoindex { + cursor_id: index_cursor_id, + }); + // Rewind source table + program.emit_insn(Insn::Rewind { + cursor_id: table_cursor_id, + pc_if_empty: label_ephemeral_build_end, + }); + let offset_ephemeral_build_loop_start = program.offset(); + // Emit all columns from source table that are needed in the ephemeral index. + // Also reserve a register for the rowid if the source table has rowids. + let num_regs_to_reserve = index.columns.len() + table_has_rowid as usize; + let ephemeral_cols_start_reg = program.alloc_registers(num_regs_to_reserve); + for (i, col) in index.columns.iter().enumerate() { + let reg = ephemeral_cols_start_reg + i; + program.emit_insn(Insn::Column { + cursor_id: table_cursor_id, + column: col.pos_in_table, + dest: reg, + }); + } + if table_has_rowid { + program.emit_insn(Insn::RowId { + cursor_id: table_cursor_id, + dest: ephemeral_cols_start_reg + index.columns.len(), + }); + } + let record_reg = program.alloc_register(); + program.emit_insn(Insn::MakeRecord { + start_reg: ephemeral_cols_start_reg, + count: num_regs_to_reserve, + dest_reg: record_reg, + }); + program.emit_insn(Insn::IdxInsert { + cursor_id: index_cursor_id, + record_reg, + unpacked_start: Some(ephemeral_cols_start_reg), + unpacked_count: Some(num_regs_to_reserve as u16), + flags: IdxInsertFlags::new().use_seek(false), + }); + program.emit_insn(Insn::Next { + cursor_id: table_cursor_id, + pc_if_next: offset_ephemeral_build_loop_start, + }); + program.resolve_label(label_ephemeral_build_end, program.offset()); + Ok(index_cursor_id) +} diff --git a/core/translate/plan.rs b/core/translate/plan.rs index 46d4d38da..51bc3f7c6 100644 --- a/core/translate/plan.rs +++ b/core/translate/plan.rs @@ -509,7 +509,10 @@ impl TableReference { match &self.table { Table::BTree(btree) => { let use_covering_index = self.utilizes_covering_index(); - let table_cursor_id = if use_covering_index && mode == OperationMode::SELECT { + let index_is_ephemeral = index.map_or(false, |index| index.ephemeral); + let table_not_required = + OperationMode::SELECT == mode && use_covering_index && !index_is_ephemeral; + let table_cursor_id = if table_not_required { None } else { Some(program.alloc_cursor_id( From a50fa03d247af77c6f19d885f585939f126b8412 Mon Sep 17 00:00:00 2001 From: Jussi Saurio Date: Sat, 19 Apr 2025 12:21:34 +0300 Subject: [PATCH 5/8] optimizer: allow calling try_extract_index... without any persistent indexes --- core/translate/optimizer.rs | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/core/translate/optimizer.rs b/core/translate/optimizer.rs index fe764ee50..8e796c175 100644 --- a/core/translate/optimizer.rs +++ b/core/translate/optimizer.rs @@ -355,15 +355,18 @@ fn use_indexes( // but we just don't do that yet. continue; } + let placeholder = vec![]; + let mut usable_indexes_ref = &placeholder; if let Some(indexes) = available_indexes.get(table_name) { - if let Some(search) = try_extract_index_search_from_where_clause( - where_clause, - table_index, - table_reference, - indexes, - )? { - table_reference.op = Operation::Search(search); - } + usable_indexes_ref = indexes; + } + if let Some(search) = try_extract_index_search_from_where_clause( + where_clause, + table_index, + table_reference, + usable_indexes_ref, + )? { + table_reference.op = Operation::Search(search); } } } @@ -730,10 +733,6 @@ pub fn try_extract_index_search_from_where_clause( if where_clause.is_empty() { return Ok(None); } - // If there are no indexes, we can't extract a search - if table_indexes.is_empty() { - return Ok(None); - } let iter_dir = if let Operation::Scan { iter_dir, .. } = &table_reference.op { *iter_dir From 6924424f11bc5c5f02c3da816e49c96d5cd32e66 Mon Sep 17 00:00:00 2001 From: Jussi Saurio Date: Sat, 19 Apr 2025 12:22:40 +0300 Subject: [PATCH 6/8] optimizer: add highly unintelligent heuristics-based cost estimation --- core/translate/optimizer.rs | 160 ++++++++++++++++++++++++++++-------- 1 file changed, 125 insertions(+), 35 deletions(-) diff --git a/core/translate/optimizer.rs b/core/translate/optimizer.rs index 8e796c175..872758257 100644 --- a/core/translate/optimizer.rs +++ b/core/translate/optimizer.rs @@ -713,14 +713,80 @@ fn opposite_cmp_op(op: ast::Operator) -> ast::Operator { } /// Struct used for scoring index scans -/// Currently we just score by the number of index columns that can be utilized -/// in the scan, i.e. no statistics are used. +/// Currently we just estimate cost in a really dumb way, +/// i.e. no statistics are used. struct IndexScore { index: Option>, - score: usize, + cost: f64, constraints: Vec, } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +struct IndexInfo { + unique: bool, + column_count: usize, +} + +const ESTIMATED_HARDCODED_ROWS_PER_TABLE: f64 = 1000.0; + +/// Unbelievably dumb cost estimate for rows scanned by an index scan. +fn dumb_cost_estimator( + index_info: Option, + constraints: &[IndexConstraint], + is_inner_loop: bool, + is_ephemeral: bool, +) -> f64 { + // assume that the outer table always does a full table scan :) + // this discourages building ephemeral indexes on the outer table + // (since a scan reads TABLE_ROWS rows, so an ephemeral index on the outer table would both read TABLE_ROWS rows to build the index and then seek the index) + // but encourages building it on the inner table because it's only built once but the inner loop is run as many times as the outer loop has iterations. + let loop_multiplier = if is_inner_loop { + ESTIMATED_HARDCODED_ROWS_PER_TABLE + } else { + 1.0 + }; + + // If we are building an ephemeral index, we assume we will scan the entire source table to build it. + // Non-ephemeral indexes don't need to be built. + let cost_to_build_index = is_ephemeral as usize as f64 * ESTIMATED_HARDCODED_ROWS_PER_TABLE; + + let Some(index_info) = index_info else { + return cost_to_build_index + ESTIMATED_HARDCODED_ROWS_PER_TABLE * loop_multiplier; + }; + + let final_constraint_is_range = constraints + .last() + .map_or(false, |c| c.operator != ast::Operator::Equals); + let equalities_count = constraints + .iter() + .take(if final_constraint_is_range { + constraints.len() - 1 + } else { + constraints.len() + }) + .count() as f64; + + let selectivity = match ( + index_info.unique, + index_info.column_count as f64, + equalities_count, + ) { + // no equalities: let's assume range query selectivity is 0.4. if final constraint is not range and there are no equalities, it means full table scan incoming + (_, _, 0.0) => { + if final_constraint_is_range { + 0.4 + } else { + 1.0 + } + } + // on an unique index if we have equalities across all index columns, assume very high selectivity + (true, index_cols, eq_count) if eq_count == index_cols => 0.01 * eq_count, + // some equalities: let's assume each equality has a selectivity of 0.1 and range query selectivity is 0.4 + (_, _, eq_count) => (eq_count * 0.1) * if final_constraint_is_range { 0.4 } else { 1.0 }, + }; + cost_to_build_index + selectivity * ESTIMATED_HARDCODED_ROWS_PER_TABLE * loop_multiplier +} + /// Try to extract an index search from the WHERE clause /// Returns an optional [Search] struct if an index search can be extracted, otherwise returns None. pub fn try_extract_index_search_from_where_clause( @@ -747,10 +813,11 @@ pub fn try_extract_index_search_from_where_clause( // 3. constrain the index columns in the order that they appear in the index // - e.g. if the index is on (a,b,c) then we can use all of "a = 1 AND b = 2 AND c = 3" to constrain the index scan, // - but if the where clause is "a = 1 and c = 3" then we can only use "a = 1". + let cost_of_full_table_scan = dumb_cost_estimator(None, &[], table_index != 0, false); let mut constraints_cur = vec![]; let mut best_index = IndexScore { index: None, - score: 0, + cost: cost_of_full_table_scan, constraints: vec![], }; @@ -759,10 +826,18 @@ pub fn try_extract_index_search_from_where_clause( find_index_constraints(where_clause, table_index, index, &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 score = constraints_cur.len(); - if score > best_index.score { + let cost = dumb_cost_estimator( + Some(IndexInfo { + unique: index.unique, + column_count: index.columns.len(), + }), + &constraints_cur, + table_index != 0, + false, + ); + if cost < best_index.cost { best_index.index = Some(Arc::clone(index)); - best_index.score = score; + best_index.cost = cost; best_index.constraints.clear(); best_index.constraints.append(&mut constraints_cur); } @@ -873,6 +948,45 @@ 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 { + // Skip terms that cannot be evaluated at this table's loop level + if !term.should_eval_at_loop(table_index) { + return false; + } + // Skip terms that are not binary comparisons + let Ok(ast::Expr::Binary(lhs, operator, rhs)) = unwrap_parens(&term.expr) else { + return false; + }; + // Only consider index scans for binary ops that are comparisons + if !matches!( + *operator, + ast::Operator::Equals + | ast::Operator::Greater + | ast::Operator::GreaterEquals + | ast::Operator::Less + | ast::Operator::LessEquals + ) { + return false; + } + + // If both lhs and rhs refer to columns from this table, we can't use this constraint + // because we can't use the index to satisfy the condition. + // Examples: + // - 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 { + return false; + }; + let Ok(eval_at_right) = determine_where_to_eval_expr(&rhs) else { + return false; + }; + if eval_at_left == EvalAt::Loop(table_index) && eval_at_right == EvalAt::Loop(table_index) { + return false; + } + true +} + /// Find all [IndexConstraint]s for a given WHERE clause /// Constraints are appended as long as they constrain the index in column order. /// E.g. for index (a,b,c) to be fully used, there must be a [WhereTerm] for each of a, b, and c. @@ -886,37 +1000,13 @@ fn find_index_constraints( for position_in_index in 0..index.columns.len() { let mut found = false; for (position_in_where_clause, term) in where_clause.iter().enumerate() { - // Skip terms that cannot be evaluated at this table's loop level - if !term.should_eval_at_loop(table_index) { - continue; - } - // Skip terms that are not binary comparisons - let ast::Expr::Binary(lhs, operator, rhs) = unwrap_parens(&term.expr)? else { - continue; - }; - // Only consider index scans for binary ops that are comparisons - if !matches!( - *operator, - ast::Operator::Equals - | ast::Operator::Greater - | ast::Operator::GreaterEquals - | ast::Operator::Less - | ast::Operator::LessEquals - ) { + if !is_potential_index_constraint(term, table_index) { continue; } - // If both lhs and rhs refer to columns from this table, we can't use this constraint - // because we can't use the index to satisfy the condition. - // Examples: - // - 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) - { - continue; - } + let ast::Expr::Binary(lhs, operator, rhs) = unwrap_parens(&term.expr)? else { + panic!("expected binary expression"); + }; // Check if lhs is a column that is in the i'th position of the index if Some(position_in_index) == get_column_position_in_index(lhs, table_index, index)? { From 3b44b269a31eb00be79a9fe74fd9daf2a5bf58ae Mon Sep 17 00:00:00 2001 From: Jussi Saurio Date: Sat, 19 Apr 2025 12:22:57 +0300 Subject: [PATCH 7/8] optimizer: try to build ephemeral index to avoid nested table scan --- core/translate/optimizer.rs | 157 +++++++++++++++++++++++++++++++++++- 1 file changed, 155 insertions(+), 2 deletions(-) diff --git a/core/translate/optimizer.rs b/core/translate/optimizer.rs index 872758257..41e34418e 100644 --- a/core/translate/optimizer.rs +++ b/core/translate/optimizer.rs @@ -1,9 +1,9 @@ -use std::{collections::HashMap, sync::Arc}; +use std::{cmp::Ordering, collections::HashMap, sync::Arc}; use limbo_sqlite3_parser::ast::{self, Expr, SortOrder}; use crate::{ - schema::{Index, Schema}, + schema::{Index, IndexColumn, Schema}, translate::plan::TerminationKey, types::SeekOp, util::exprs_are_equivalent, @@ -843,6 +843,25 @@ pub fn try_extract_index_search_from_where_clause( } } + // We haven't found a persistent btree index that is any better than a full table scan; + // 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); + 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 + let ephemeral_index = + ephemeral_index_build(table_reference, table_index, &constraints_with_col_idx); + best_index.index = Some(Arc::new(ephemeral_index)); + best_index.cost = ephemeral_cost; + best_index.constraints.clear(); + best_index + .constraints + .append(&mut constraints_without_col_idx); + } + } + if best_index.index.is_none() { return Ok(None); } @@ -869,6 +888,140 @@ pub fn try_extract_index_search_from_where_clause( })); } +fn ephemeral_index_estimate_cost( + where_clause: &mut Vec, + table_reference: &TableReference, + table_index: usize, +) -> (f64, Vec<(usize, IndexConstraint)>, Vec) { + let mut constraints_with_col_idx: Vec<(usize, IndexConstraint)> = where_clause + .iter() + .enumerate() + .filter(|(_, term)| is_potential_index_constraint(term, table_index)) + .filter_map(|(i, term)| { + let Ok(ast::Expr::Binary(lhs, operator, rhs)) = unwrap_parens(&term.expr) else { + panic!("expected binary expression"); + }; + if let ast::Expr::Column { table, column, .. } = lhs.as_ref() { + if *table == table_index { + return Some(( + *column, + IndexConstraint { + position_in_where_clause: (i, BinaryExprSide::Rhs), + operator: *operator, + index_column_sort_order: SortOrder::Asc, + }, + )); + } + } + if let ast::Expr::Column { table, column, .. } = rhs.as_ref() { + if *table == table_index { + return Some(( + *column, + IndexConstraint { + position_in_where_clause: (i, BinaryExprSide::Lhs), + operator: opposite_cmp_op(*operator), + index_column_sort_order: SortOrder::Asc, + }, + )); + } + } + None + }) + .collect(); + // sort equalities first + constraints_with_col_idx.sort_by(|a, _| { + if a.1.operator == ast::Operator::Equals { + Ordering::Less + } else { + Ordering::Equal + } + }); + // drop everything after the first inequality + constraints_with_col_idx.truncate( + constraints_with_col_idx + .iter() + .position(|c| c.1.operator != ast::Operator::Equals) + .unwrap_or(constraints_with_col_idx.len()), + ); + + let ephemeral_column_count = table_reference + .columns() + .iter() + .enumerate() + .filter(|(i, _)| table_reference.column_is_used(*i)) + .count(); + + let constraints_without_col_idx = constraints_with_col_idx + .iter() + .cloned() + .map(|(_, c)| c) + .collect::>(); + let ephemeral_cost = dumb_cost_estimator( + Some(IndexInfo { + unique: false, + column_count: ephemeral_column_count, + }), + &constraints_without_col_idx, + table_index != 0, + true, + ); + ( + ephemeral_cost, + constraints_with_col_idx, + constraints_without_col_idx, + ) +} + +fn ephemeral_index_build( + table_reference: &TableReference, + table_index: usize, + index_constraints: &[(usize, IndexConstraint)], +) -> Index { + let mut ephemeral_columns: Vec = table_reference + .columns() + .iter() + .enumerate() + .map(|(i, c)| IndexColumn { + name: c.name.clone().unwrap(), + order: SortOrder::Asc, + pos_in_table: i, + }) + // only include columns that are used in the query + .filter(|c| table_reference.column_is_used(c.pos_in_table)) + .collect(); + // sort so that constraints first, then rest in whatever order they were in in the table + ephemeral_columns.sort_by(|a, b| { + let a_constraint = index_constraints + .iter() + .enumerate() + .find(|(_, c)| c.0 == a.pos_in_table); + let b_constraint = index_constraints + .iter() + .enumerate() + .find(|(_, c)| c.0 == b.pos_in_table); + match (a_constraint, b_constraint) { + (Some(_), None) => Ordering::Less, + (None, Some(_)) => Ordering::Greater, + (Some((a_idx, _)), Some((b_idx, _))) => a_idx.cmp(&b_idx), + (None, None) => Ordering::Equal, + } + }); + let ephemeral_index = Index { + name: format!( + "ephemeral_{}_{}", + table_reference.table.get_name(), + table_index + ), + columns: ephemeral_columns, + unique: false, + ephemeral: true, + table_name: table_reference.table.get_name().to_string(), + root_page: 0, + }; + + ephemeral_index +} + #[derive(Debug, Clone)] /// A representation of an expression in a [WhereTerm] that can potentially be used as part of an index seek key. /// For example, if there is an index on table T(x,y) and another index on table U(z), and the where clause is "WHERE x > 10 AND 20 = z", From f256fb46fd5eeda8459f91d40ec3cde10e757458 Mon Sep 17 00:00:00 2001 From: Jussi Saurio Date: Sat, 19 Apr 2025 12:46:11 +0300 Subject: [PATCH 8/8] remove print spam from index insert --- core/vdbe/execute.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/core/vdbe/execute.rs b/core/vdbe/execute.rs index de871f54c..0869491d6 100644 --- a/core/vdbe/execute.rs +++ b/core/vdbe/execute.rs @@ -3766,7 +3766,6 @@ pub fn op_idx_insert( pager: &Rc, mv_store: Option<&Rc>, ) -> Result { - dbg!("op_idx_insert_"); if let Insn::IdxInsert { cursor_id, record_reg, @@ -3807,7 +3806,6 @@ pub fn op_idx_insert( } }; - dbg!(moved_before); // Start insertion of row. This might trigger a balance procedure which will take care of moving to different pages, // therefore, we don't want to seek again if that happens, meaning we don't want to return on io without moving to the following opcode // because it could trigger a movement to child page after a balance root which will leave the current page as the root page.