mirror of
https://github.com/aljazceru/turso.git
synced 2026-02-23 17:05:36 +01:00
Merge 'Add support for DELETE query planning ' from Kim Seon Woo
### Purpose of this PR Support for DELETE query execution planning ### Implementation Details The main entry point for planning DELETE queries is the `translate_delete` function. It is composed of three primary steps: - `prepare_delete_plan`: - Reuses the existing SELECT query's WHERE clause parsing logic to interpret and construct the initial delete plan. - `optimize_delete_plan`: - eliminating BETWEEN expressions - usage of indexes - `emit_program_for_delete`: - Add instructions for delete operation I've tried to reuse existing logic(mostly on SELECT operations) as much as I can, so that we can automatically incorporate new changes automatically. ### Delete planning debug I've used `println!(...)` to specify the rows to delete. Example below <img width="374" alt="image" src="https://github.com/user- attachments/assets/f109e1c6-6b69-43b9-bb23-4bee3a835767" /> ### Bytecode compatibility `EXPLAIN DELETE FROM users WHERE id = 1;` <img width="1724" alt="image" src="https://github.com/user- attachments/assets/ce2995d7-6947-493e-ad3d-224df7f4e7c2" /> `EXPLAIN DELETE FROM users WHERE id > 3` <img width="1726" alt="image" src="https://github.com/user- attachments/assets/ac516bd2-fe80-44c5-9a4b-8e35d574c47d" /> `EXPLAIN DELETE FROM users WHERE id < 3` <img width="1711" alt="image" src="https://github.com/user- attachments/assets/29d0ccba-c373-483e-bb6b-9344289cae02" /> ### TODO(future works) - Add support for `Clear` opcode - Add test when `delete` is implemented in `Cursor` <img width="1728" alt="image" src="https://github.com/user- attachments/assets/28d371fc-90f5-42e5-8add-d5218f830234" /> Closes #538
This commit is contained in:
@@ -36,12 +36,12 @@ pub use storage::wal::WalFile;
|
||||
pub use storage::wal::WalFileShared;
|
||||
use util::parse_schema_rows;
|
||||
|
||||
use translate::optimizer::optimize_plan;
|
||||
use translate::planner::prepare_select_plan;
|
||||
|
||||
pub use error::LimboError;
|
||||
pub type Result<T> = std::result::Result<T, error::LimboError>;
|
||||
|
||||
use crate::translate::optimizer::optimize_plan;
|
||||
pub use io::OpenFlags;
|
||||
#[cfg(feature = "fs")]
|
||||
pub use io::PlatformIO;
|
||||
|
||||
@@ -79,6 +79,10 @@ impl Cursor for PseudoCursor {
|
||||
Ok(CursorResult::Ok(()))
|
||||
}
|
||||
|
||||
fn delete(&mut self) -> Result<CursorResult<()>> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn get_null_flag(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
@@ -1885,6 +1885,11 @@ impl Cursor for BTreeCursor {
|
||||
Ok(CursorResult::Ok(()))
|
||||
}
|
||||
|
||||
fn delete(&mut self) -> Result<CursorResult<()>> {
|
||||
println!("rowid: {:?}", self.rowid.borrow());
|
||||
Ok(CursorResult::Ok(()))
|
||||
}
|
||||
|
||||
fn set_null_flag(&mut self, flag: bool) {
|
||||
self.null_flag = flag;
|
||||
}
|
||||
|
||||
21
core/translate/delete.rs
Normal file
21
core/translate/delete.rs
Normal file
@@ -0,0 +1,21 @@
|
||||
use crate::translate::emitter::emit_program;
|
||||
use crate::translate::optimizer::optimize_plan;
|
||||
use crate::translate::planner::prepare_delete_plan;
|
||||
use crate::{schema::Schema, storage::sqlite3_ondisk::DatabaseHeader, vdbe::Program};
|
||||
use crate::{Connection, Result};
|
||||
use sqlite3_parser::ast::{Expr, Limit, QualifiedName};
|
||||
use std::rc::Weak;
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
pub fn translate_delete(
|
||||
schema: &Schema,
|
||||
tbl_name: &QualifiedName,
|
||||
where_clause: Option<Expr>,
|
||||
limit: Option<Limit>,
|
||||
database_header: Rc<RefCell<DatabaseHeader>>,
|
||||
connection: Weak<Connection>,
|
||||
) -> Result<Program> {
|
||||
let delete_plan = prepare_delete_plan(schema, tbl_name, where_clause, limit)?;
|
||||
let optimized_plan = optimize_plan(delete_plan)?;
|
||||
emit_program(database_header, optimized_plan, connection)
|
||||
}
|
||||
@@ -9,7 +9,7 @@ use sqlite3_parser::ast::{self};
|
||||
|
||||
use crate::schema::{Column, PseudoTable, Table};
|
||||
use crate::storage::sqlite3_ondisk::DatabaseHeader;
|
||||
use crate::translate::plan::{IterationDirection, Search};
|
||||
use crate::translate::plan::{DeletePlan, IterationDirection, Plan, Search};
|
||||
use crate::types::{OwnedRecord, OwnedValue};
|
||||
use crate::util::exprs_are_equivalent;
|
||||
use crate::vdbe::builder::ProgramBuilder;
|
||||
@@ -20,7 +20,7 @@ use super::expr::{
|
||||
translate_aggregation, translate_aggregation_groupby, translate_condition_expr, translate_expr,
|
||||
ConditionMetadata,
|
||||
};
|
||||
use super::plan::{Aggregate, BTreeTableReference, Direction, GroupBy, Plan};
|
||||
use super::plan::{Aggregate, BTreeTableReference, Direction, GroupBy, SelectPlan};
|
||||
use super::plan::{ResultSetColumn, SourceOperator};
|
||||
|
||||
// Metadata for handling LEFT JOIN operations
|
||||
@@ -101,6 +101,15 @@ pub struct Metadata {
|
||||
pub result_columns_to_skip_in_orderby_sorter: Option<Vec<usize>>,
|
||||
}
|
||||
|
||||
/// Used to distinguish database operations
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum OperationMode {
|
||||
SELECT,
|
||||
INSERT,
|
||||
UPDATE,
|
||||
DELETE,
|
||||
}
|
||||
|
||||
/// Initialize the program with basic setup and return initial metadata and labels
|
||||
fn prologue() -> Result<(ProgramBuilder, Metadata, BranchOffset, BranchOffset)> {
|
||||
let mut program = ProgramBuilder::new();
|
||||
@@ -166,6 +175,17 @@ pub fn emit_program(
|
||||
database_header: Rc<RefCell<DatabaseHeader>>,
|
||||
mut plan: Plan,
|
||||
connection: Weak<Connection>,
|
||||
) -> Result<Program> {
|
||||
match plan {
|
||||
Plan::Select(plan) => emit_program_for_select(database_header, plan, connection),
|
||||
Plan::Delete(plan) => emit_program_for_delete(database_header, plan, connection),
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_program_for_select(
|
||||
database_header: Rc<RefCell<DatabaseHeader>>,
|
||||
mut plan: SelectPlan,
|
||||
connection: Weak<Connection>,
|
||||
) -> Result<Program> {
|
||||
let (mut program, mut metadata, init_label, start_offset) = prologue()?;
|
||||
|
||||
@@ -201,7 +221,12 @@ pub fn emit_program(
|
||||
if let Some(ref mut group_by) = plan.group_by {
|
||||
init_group_by(&mut program, group_by, &plan.aggregates, &mut metadata)?;
|
||||
}
|
||||
init_source(&mut program, &plan.source, &mut metadata)?;
|
||||
init_source(
|
||||
&mut program,
|
||||
&plan.source,
|
||||
&mut metadata,
|
||||
&OperationMode::SELECT,
|
||||
)?;
|
||||
|
||||
// Set up main query execution loop
|
||||
open_loop(
|
||||
@@ -272,6 +297,63 @@ pub fn emit_program(
|
||||
Ok(program.build(database_header, connection))
|
||||
}
|
||||
|
||||
fn emit_program_for_delete(
|
||||
database_header: Rc<RefCell<DatabaseHeader>>,
|
||||
mut plan: DeletePlan,
|
||||
connection: Weak<Connection>,
|
||||
) -> Result<Program> {
|
||||
let (mut program, mut metadata, init_label, start_offset) = prologue()?;
|
||||
|
||||
// No rows will be read from source table loops if there is a constant false condition eg. WHERE 0
|
||||
let skip_loops_label = if plan.contains_constant_false_condition {
|
||||
let skip_loops_label = program.allocate_label();
|
||||
program.emit_insn_with_label_dependency(
|
||||
Insn::Goto {
|
||||
target_pc: skip_loops_label,
|
||||
},
|
||||
skip_loops_label,
|
||||
);
|
||||
Some(skip_loops_label)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Initialize cursors and other resources needed for query execution
|
||||
init_source(
|
||||
&mut program,
|
||||
&plan.source,
|
||||
&mut metadata,
|
||||
&OperationMode::DELETE,
|
||||
)?;
|
||||
|
||||
// Set up main query execution loop
|
||||
open_loop(
|
||||
&mut program,
|
||||
&mut plan.source,
|
||||
&plan.referenced_tables,
|
||||
&mut metadata,
|
||||
)?;
|
||||
|
||||
emit_delete_insns(&mut program, &plan.source, &plan.limit, &metadata)?;
|
||||
|
||||
// Clean up and close the main execution loop
|
||||
close_loop(
|
||||
&mut program,
|
||||
&plan.source,
|
||||
&mut metadata,
|
||||
&plan.referenced_tables,
|
||||
)?;
|
||||
|
||||
if let Some(skip_loops_label) = skip_loops_label {
|
||||
program.resolve_label(skip_loops_label, program.offset());
|
||||
}
|
||||
|
||||
// Finalize program
|
||||
epilogue(&mut program, &mut metadata, init_label, start_offset)?;
|
||||
|
||||
Ok(program.build(database_header, connection))
|
||||
}
|
||||
|
||||
/// Initialize resources needed for ORDER BY processing
|
||||
fn init_order_by(
|
||||
program: &mut ProgramBuilder,
|
||||
@@ -385,6 +467,7 @@ fn init_source(
|
||||
program: &mut ProgramBuilder,
|
||||
source: &SourceOperator,
|
||||
metadata: &mut Metadata,
|
||||
mode: &OperationMode,
|
||||
) -> Result<()> {
|
||||
match source {
|
||||
SourceOperator::Join {
|
||||
@@ -402,10 +485,10 @@ fn init_source(
|
||||
};
|
||||
metadata.left_joins.insert(*id, lj_metadata);
|
||||
}
|
||||
init_source(program, left, metadata)?;
|
||||
init_source(program, right, metadata)?;
|
||||
init_source(program, left, metadata, mode)?;
|
||||
init_source(program, right, metadata, mode)?;
|
||||
|
||||
return Ok(());
|
||||
Ok(())
|
||||
}
|
||||
SourceOperator::Scan {
|
||||
id,
|
||||
@@ -419,13 +502,28 @@ fn init_source(
|
||||
let root_page = table_reference.table.root_page;
|
||||
let next_row_label = program.allocate_label();
|
||||
metadata.next_row_labels.insert(*id, next_row_label);
|
||||
program.emit_insn(Insn::OpenReadAsync {
|
||||
cursor_id,
|
||||
root_page,
|
||||
});
|
||||
program.emit_insn(Insn::OpenReadAwait);
|
||||
|
||||
return Ok(());
|
||||
match mode {
|
||||
OperationMode::SELECT => {
|
||||
program.emit_insn(Insn::OpenReadAsync {
|
||||
cursor_id,
|
||||
root_page,
|
||||
});
|
||||
program.emit_insn(Insn::OpenReadAwait {});
|
||||
}
|
||||
OperationMode::DELETE => {
|
||||
program.emit_insn(Insn::OpenWriteAsync {
|
||||
cursor_id,
|
||||
root_page,
|
||||
});
|
||||
program.emit_insn(Insn::OpenWriteAwait {});
|
||||
}
|
||||
_ => {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
SourceOperator::Search {
|
||||
id,
|
||||
@@ -442,27 +540,54 @@ fn init_source(
|
||||
|
||||
metadata.next_row_labels.insert(*id, next_row_label);
|
||||
|
||||
program.emit_insn(Insn::OpenReadAsync {
|
||||
cursor_id: table_cursor_id,
|
||||
root_page: table_reference.table.root_page,
|
||||
});
|
||||
program.emit_insn(Insn::OpenReadAwait);
|
||||
match mode {
|
||||
OperationMode::SELECT => {
|
||||
program.emit_insn(Insn::OpenReadAsync {
|
||||
cursor_id: table_cursor_id,
|
||||
root_page: table_reference.table.root_page,
|
||||
});
|
||||
program.emit_insn(Insn::OpenReadAwait {});
|
||||
}
|
||||
OperationMode::DELETE => {
|
||||
program.emit_insn(Insn::OpenWriteAsync {
|
||||
cursor_id: table_cursor_id,
|
||||
root_page: table_reference.table.root_page,
|
||||
});
|
||||
program.emit_insn(Insn::OpenWriteAwait {});
|
||||
}
|
||||
_ => {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
if let Search::IndexSearch { index, .. } = search {
|
||||
let index_cursor_id = program
|
||||
.alloc_cursor_id(Some(index.name.clone()), Some(Table::Index(index.clone())));
|
||||
program.emit_insn(Insn::OpenReadAsync {
|
||||
cursor_id: index_cursor_id,
|
||||
root_page: index.root_page,
|
||||
});
|
||||
program.emit_insn(Insn::OpenReadAwait);
|
||||
|
||||
match mode {
|
||||
OperationMode::SELECT => {
|
||||
program.emit_insn(Insn::OpenReadAsync {
|
||||
cursor_id: index_cursor_id,
|
||||
root_page: index.root_page,
|
||||
});
|
||||
program.emit_insn(Insn::OpenReadAwait);
|
||||
}
|
||||
OperationMode::DELETE => {
|
||||
program.emit_insn(Insn::OpenWriteAsync {
|
||||
cursor_id: index_cursor_id,
|
||||
root_page: index.root_page,
|
||||
});
|
||||
program.emit_insn(Insn::OpenWriteAwait {});
|
||||
}
|
||||
_ => {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
SourceOperator::Nothing => {
|
||||
return Ok(());
|
||||
Ok(())
|
||||
}
|
||||
SourceOperator::Nothing => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -811,7 +936,7 @@ pub enum InnerLoopEmitTarget<'a> {
|
||||
/// At this point the cursors for all tables have been opened and rewound.
|
||||
fn inner_loop_emit(
|
||||
program: &mut ProgramBuilder,
|
||||
plan: &mut Plan,
|
||||
plan: &mut SelectPlan,
|
||||
metadata: &mut Metadata,
|
||||
) -> Result<()> {
|
||||
// if we have a group by, we emit a record into the group by sorter.
|
||||
@@ -1121,6 +1246,60 @@ fn close_loop(
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_delete_insns(
|
||||
program: &mut ProgramBuilder,
|
||||
source: &SourceOperator,
|
||||
limit: &Option<usize>,
|
||||
metadata: &Metadata,
|
||||
) -> Result<()> {
|
||||
let cursor_id = match source {
|
||||
SourceOperator::Scan {
|
||||
table_reference, ..
|
||||
} => program.resolve_cursor_id(&table_reference.table_identifier),
|
||||
SourceOperator::Search {
|
||||
table_reference,
|
||||
search,
|
||||
..
|
||||
} => match search {
|
||||
Search::RowidEq { .. } | Search::RowidSearch { .. } => {
|
||||
program.resolve_cursor_id(&table_reference.table_identifier)
|
||||
}
|
||||
Search::IndexSearch { index, .. } => program.resolve_cursor_id(&index.name),
|
||||
},
|
||||
_ => return Ok(()),
|
||||
};
|
||||
|
||||
// Emit the instructions to delete the row
|
||||
let key_reg = program.alloc_register();
|
||||
program.emit_insn(Insn::RowId {
|
||||
cursor_id,
|
||||
dest: key_reg,
|
||||
});
|
||||
program.emit_insn(Insn::DeleteAsync { cursor_id });
|
||||
program.emit_insn(Insn::DeleteAwait { cursor_id });
|
||||
if let Some(limit) = limit {
|
||||
let limit_reg = program.alloc_register();
|
||||
program.emit_insn(Insn::Integer {
|
||||
value: *limit as i64,
|
||||
dest: limit_reg,
|
||||
});
|
||||
program.mark_last_insn_constant();
|
||||
let jump_label_on_limit_reached = metadata
|
||||
.termination_label_stack
|
||||
.last()
|
||||
.expect("termination_label_stack should not be empty.");
|
||||
program.emit_insn_with_label_dependency(
|
||||
Insn::DecrJumpZero {
|
||||
reg: limit_reg,
|
||||
target_pc: *jump_label_on_limit_reached,
|
||||
},
|
||||
*jump_label_on_limit_reached,
|
||||
)
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Emits the bytecode for processing a GROUP BY clause.
|
||||
/// This is called when the main query execution loop has finished processing,
|
||||
/// and we now have data in the GROUP BY sorter.
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
//! a SELECT statement will be translated into a sequence of instructions that
|
||||
//! will read rows from the database and filter them according to a WHERE clause.
|
||||
|
||||
pub(crate) mod delete;
|
||||
pub(crate) mod emitter;
|
||||
pub(crate) mod expr;
|
||||
pub(crate) mod insert;
|
||||
@@ -23,6 +24,7 @@ use std::str::FromStr;
|
||||
use crate::schema::Schema;
|
||||
use crate::storage::pager::Pager;
|
||||
use crate::storage::sqlite3_ondisk::{DatabaseHeader, MIN_PAGE_CACHE_SIZE};
|
||||
use crate::translate::delete::translate_delete;
|
||||
use crate::vdbe::{builder::ProgramBuilder, Insn, Program};
|
||||
use crate::{bail_parse_error, Connection, Result};
|
||||
use insert::translate_insert;
|
||||
@@ -68,7 +70,22 @@ pub fn translate(
|
||||
ast::Stmt::CreateVirtualTable { .. } => {
|
||||
bail_parse_error!("CREATE VIRTUAL TABLE not supported yet")
|
||||
}
|
||||
ast::Stmt::Delete { .. } => bail_parse_error!("DELETE not supported yet"),
|
||||
ast::Stmt::Delete {
|
||||
with,
|
||||
tbl_name,
|
||||
indexed,
|
||||
where_clause,
|
||||
returning,
|
||||
order_by,
|
||||
limit,
|
||||
} => translate_delete(
|
||||
schema,
|
||||
&tbl_name,
|
||||
where_clause,
|
||||
limit,
|
||||
database_header,
|
||||
connection,
|
||||
),
|
||||
ast::Stmt::Detach(_) => bail_parse_error!("DETACH not supported yet"),
|
||||
ast::Stmt::DropIndex { .. } => bail_parse_error!("DROP INDEX not supported yet"),
|
||||
ast::Stmt::DropTable { .. } => bail_parse_error!("DROP TABLE not supported yet"),
|
||||
|
||||
@@ -6,39 +6,68 @@ use crate::{schema::Index, Result};
|
||||
|
||||
use super::plan::{
|
||||
get_table_ref_bitmask_for_ast_expr, get_table_ref_bitmask_for_operator, BTreeTableReference,
|
||||
Direction, IterationDirection, Plan, Search, SourceOperator,
|
||||
DeletePlan, Direction, IterationDirection, Plan, Search, SelectPlan, SourceOperator,
|
||||
};
|
||||
|
||||
pub fn optimize_plan(mut plan: Plan) -> Result<Plan> {
|
||||
match plan {
|
||||
Plan::Select(plan) => optimize_select_plan(plan).map(Plan::Select),
|
||||
Plan::Delete(plan) => optimize_delete_plan(plan).map(Plan::Delete),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a few passes over the plan to optimize it.
|
||||
* TODO: these could probably be done in less passes,
|
||||
* but having them separate makes them easier to understand
|
||||
*/
|
||||
pub fn optimize_plan(mut select_plan: Plan) -> Result<Plan> {
|
||||
eliminate_between(&mut select_plan.source, &mut select_plan.where_clause)?;
|
||||
fn optimize_select_plan(mut plan: SelectPlan) -> Result<SelectPlan> {
|
||||
eliminate_between(&mut plan.source, &mut plan.where_clause)?;
|
||||
if let ConstantConditionEliminationResult::ImpossibleCondition =
|
||||
eliminate_constants(&mut select_plan.source, &mut select_plan.where_clause)?
|
||||
eliminate_constants(&mut plan.source, &mut plan.where_clause)?
|
||||
{
|
||||
select_plan.contains_constant_false_condition = true;
|
||||
return Ok(select_plan);
|
||||
plan.contains_constant_false_condition = true;
|
||||
return Ok(plan);
|
||||
}
|
||||
|
||||
push_predicates(
|
||||
&mut select_plan.source,
|
||||
&mut select_plan.where_clause,
|
||||
&select_plan.referenced_tables,
|
||||
&mut plan.source,
|
||||
&mut plan.where_clause,
|
||||
&plan.referenced_tables,
|
||||
)?;
|
||||
|
||||
use_indexes(
|
||||
&mut select_plan.source,
|
||||
&select_plan.referenced_tables,
|
||||
&select_plan.available_indexes,
|
||||
&mut plan.source,
|
||||
&plan.referenced_tables,
|
||||
&plan.available_indexes,
|
||||
)?;
|
||||
|
||||
eliminate_unnecessary_orderby(
|
||||
&mut select_plan.source,
|
||||
&mut select_plan.order_by,
|
||||
&select_plan.referenced_tables,
|
||||
&select_plan.available_indexes,
|
||||
&mut plan.source,
|
||||
&mut plan.order_by,
|
||||
&plan.referenced_tables,
|
||||
&plan.available_indexes,
|
||||
)?;
|
||||
Ok(select_plan)
|
||||
|
||||
Ok(plan)
|
||||
}
|
||||
|
||||
fn optimize_delete_plan(mut plan: DeletePlan) -> Result<DeletePlan> {
|
||||
eliminate_between(&mut plan.source, &mut plan.where_clause)?;
|
||||
if let ConstantConditionEliminationResult::ImpossibleCondition =
|
||||
eliminate_constants(&mut plan.source, &mut plan.where_clause)?
|
||||
{
|
||||
plan.contains_constant_false_condition = true;
|
||||
return Ok(plan);
|
||||
}
|
||||
|
||||
use_indexes(
|
||||
&mut plan.source,
|
||||
&plan.referenced_tables,
|
||||
&plan.available_indexes,
|
||||
)?;
|
||||
|
||||
Ok(plan)
|
||||
}
|
||||
|
||||
fn _operator_is_already_ordered_by(
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
use core::fmt;
|
||||
use sqlite3_parser::ast;
|
||||
use std::ptr::write;
|
||||
use std::{
|
||||
fmt::{Display, Formatter},
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
use sqlite3_parser::ast;
|
||||
|
||||
use crate::translate::plan::Plan::{Delete, Select};
|
||||
use crate::{
|
||||
function::AggFunc,
|
||||
schema::{BTreeTable, Column, Index},
|
||||
@@ -27,7 +28,13 @@ pub struct GroupBy {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Plan {
|
||||
pub enum Plan {
|
||||
Select(SelectPlan),
|
||||
Delete(DeletePlan),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SelectPlan {
|
||||
/// A tree of sources (tables).
|
||||
pub source: SourceOperator,
|
||||
/// the columns inside SELECT ... FROM
|
||||
@@ -50,9 +57,32 @@ pub struct Plan {
|
||||
pub contains_constant_false_condition: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DeletePlan {
|
||||
/// A tree of sources (tables).
|
||||
pub source: SourceOperator,
|
||||
/// the columns inside SELECT ... FROM
|
||||
pub result_columns: Vec<ResultSetColumn>,
|
||||
/// where clause split into a vec at 'AND' boundaries.
|
||||
pub where_clause: Option<Vec<ast::Expr>>,
|
||||
/// order by clause
|
||||
pub order_by: Option<Vec<(ast::Expr, Direction)>>,
|
||||
/// limit clause
|
||||
pub limit: Option<usize>,
|
||||
/// all the tables referenced in the query
|
||||
pub referenced_tables: Vec<BTreeTableReference>,
|
||||
/// all the indexes available
|
||||
pub available_indexes: Vec<Rc<Index>>,
|
||||
/// query contains a constant condition that is always false
|
||||
pub contains_constant_false_condition: bool,
|
||||
}
|
||||
|
||||
impl Display for Plan {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.source)
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Select(select_plan) => write!(f, "{}", select_plan.source),
|
||||
Delete(delete_plan) => write!(f, "{}", delete_plan.source),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use super::plan::{
|
||||
Aggregate, BTreeTableReference, Direction, GroupBy, Plan, ResultSetColumn, SourceOperator,
|
||||
Aggregate, BTreeTableReference, DeletePlan, Direction, GroupBy, Plan, ResultSetColumn,
|
||||
SelectPlan, SourceOperator,
|
||||
};
|
||||
use crate::{
|
||||
function::Func,
|
||||
@@ -7,7 +8,7 @@ use crate::{
|
||||
util::{exprs_are_equivalent, normalize_ident},
|
||||
Result,
|
||||
};
|
||||
use sqlite3_parser::ast::{self, FromClause, JoinType, ResultColumn};
|
||||
use sqlite3_parser::ast::{self, Expr, FromClause, JoinType, Limit, QualifiedName, ResultColumn};
|
||||
|
||||
pub struct OperatorIdCounter {
|
||||
id: usize,
|
||||
@@ -277,7 +278,7 @@ pub fn prepare_select_plan<'a>(schema: &Schema, select: ast::Select) -> Result<P
|
||||
// Parse the FROM clause
|
||||
let (source, referenced_tables) = parse_from(schema, from, &mut operator_id_counter)?;
|
||||
|
||||
let mut plan = Plan {
|
||||
let mut plan = SelectPlan {
|
||||
source,
|
||||
result_columns: vec![],
|
||||
where_clause: None,
|
||||
@@ -291,14 +292,7 @@ pub fn prepare_select_plan<'a>(schema: &Schema, select: ast::Select) -> Result<P
|
||||
};
|
||||
|
||||
// Parse the WHERE clause
|
||||
if let Some(w) = where_clause {
|
||||
let mut predicates = vec![];
|
||||
break_predicate_at_and_boundaries(w, &mut predicates);
|
||||
for expr in predicates.iter_mut() {
|
||||
bind_column_references(expr, &plan.referenced_tables)?;
|
||||
}
|
||||
plan.where_clause = Some(predicates);
|
||||
}
|
||||
plan.where_clause = parse_where(where_clause, &plan.referenced_tables)?;
|
||||
|
||||
let mut aggregate_expressions = Vec::new();
|
||||
for column in columns.clone() {
|
||||
@@ -482,23 +476,58 @@ pub fn prepare_select_plan<'a>(schema: &Schema, select: ast::Select) -> Result<P
|
||||
}
|
||||
|
||||
// Parse the LIMIT clause
|
||||
if let Some(limit) = &select.limit {
|
||||
plan.limit = match &limit.expr {
|
||||
ast::Expr::Literal(ast::Literal::Numeric(n)) => {
|
||||
let l = n.parse()?;
|
||||
Some(l)
|
||||
}
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
plan.limit = select.limit.and_then(|limit| parse_limit(limit));
|
||||
|
||||
// Return the unoptimized query plan
|
||||
Ok(plan)
|
||||
Ok(Plan::Select(plan))
|
||||
}
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prepare_delete_plan(
|
||||
schema: &Schema,
|
||||
tbl_name: &QualifiedName,
|
||||
where_clause: Option<Expr>,
|
||||
limit: Option<Limit>,
|
||||
) -> Result<Plan> {
|
||||
let table = match schema.get_table(tbl_name.name.0.as_str()) {
|
||||
Some(table) => table,
|
||||
None => crate::bail_corrupt_error!("Parse error: no such table: {}", tbl_name),
|
||||
};
|
||||
|
||||
let table_ref = BTreeTableReference {
|
||||
table: table.clone(),
|
||||
table_identifier: table.name.clone(),
|
||||
table_index: 0,
|
||||
};
|
||||
let referenced_tables = vec![table_ref.clone()];
|
||||
|
||||
// Parse the WHERE clause
|
||||
let resolved_where_clauses = parse_where(where_clause, &[table_ref.clone()])?;
|
||||
|
||||
// Parse the LIMIT clause
|
||||
let resolved_limit = limit.and_then(|limit| parse_limit(limit));
|
||||
|
||||
let plan = DeletePlan {
|
||||
source: SourceOperator::Scan {
|
||||
id: 0,
|
||||
table_reference: table_ref.clone(),
|
||||
predicates: resolved_where_clauses.clone(),
|
||||
iter_dir: None,
|
||||
},
|
||||
result_columns: vec![],
|
||||
where_clause: resolved_where_clauses,
|
||||
order_by: None,
|
||||
limit: resolved_limit,
|
||||
referenced_tables,
|
||||
available_indexes: vec![],
|
||||
contains_constant_false_condition: false,
|
||||
};
|
||||
|
||||
Ok(Plan::Delete(plan))
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn parse_from(
|
||||
schema: &Schema,
|
||||
@@ -560,6 +589,22 @@ fn parse_from(
|
||||
Ok((operator, tables))
|
||||
}
|
||||
|
||||
fn parse_where(
|
||||
where_clause: Option<Expr>,
|
||||
referenced_tables: &[BTreeTableReference],
|
||||
) -> Result<Option<Vec<Expr>>> {
|
||||
if let Some(where_expr) = where_clause {
|
||||
let mut predicates = vec![];
|
||||
break_predicate_at_and_boundaries(where_expr, &mut predicates);
|
||||
for expr in predicates.iter_mut() {
|
||||
bind_column_references(expr, referenced_tables)?;
|
||||
}
|
||||
Ok(Some(predicates))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_join(
|
||||
schema: &Schema,
|
||||
join: ast::JoinedSelectTable,
|
||||
@@ -743,6 +788,14 @@ fn parse_join(
|
||||
))
|
||||
}
|
||||
|
||||
fn parse_limit(limit: Limit) -> Option<usize> {
|
||||
if let Expr::Literal(ast::Literal::Numeric(n)) = limit.expr {
|
||||
n.parse().ok()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn break_predicate_at_and_boundaries(predicate: ast::Expr, out_predicates: &mut Vec<ast::Expr>) {
|
||||
match predicate {
|
||||
ast::Expr::Binary(left, ast::Operator::And, right) => {
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
use std::rc::Weak;
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
use super::emitter::emit_program;
|
||||
use super::planner::prepare_select_plan;
|
||||
use crate::storage::sqlite3_ondisk::DatabaseHeader;
|
||||
use crate::translate::optimizer::optimize_plan;
|
||||
use crate::Connection;
|
||||
use crate::{schema::Schema, vdbe::Program, Result};
|
||||
use sqlite3_parser::ast;
|
||||
|
||||
use super::emitter::emit_program;
|
||||
use super::optimizer::optimize_plan;
|
||||
use super::planner::prepare_select_plan;
|
||||
|
||||
pub fn translate_select(
|
||||
schema: &Schema,
|
||||
select: ast::Select,
|
||||
|
||||
@@ -554,6 +554,7 @@ pub trait Cursor {
|
||||
record: &OwnedRecord,
|
||||
moved_before: bool, /* Tells inserter that it doesn't need to traverse in order to find leaf page */
|
||||
) -> Result<CursorResult<()>>; //
|
||||
fn delete(&mut self) -> Result<CursorResult<()>>;
|
||||
fn exists(&mut self, key: &OwnedValue) -> Result<CursorResult<bool>>;
|
||||
fn set_null_flag(&mut self, flag: bool);
|
||||
fn get_null_flag(&self) -> bool;
|
||||
|
||||
@@ -140,6 +140,17 @@ impl ProgramBuilder {
|
||||
.push((label, insn_reference));
|
||||
}
|
||||
|
||||
/// Resolve unresolved labels to a specific offset in the instruction list.
|
||||
///
|
||||
/// This function updates all instructions that reference the given label
|
||||
/// to point to the specified offset. It ensures that the label and offset
|
||||
/// are valid and updates the target program counter (PC) of each instruction
|
||||
/// that references the label.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `label` - The label to resolve.
|
||||
/// * `to_offset` - The offset to which the labeled instructions should be resolved to.
|
||||
pub fn resolve_label(&mut self, label: BranchOffset, to_offset: BranchOffset) {
|
||||
assert!(label < 0);
|
||||
assert!(to_offset >= 0);
|
||||
|
||||
@@ -833,6 +833,24 @@ pub fn insn_to_str(
|
||||
0,
|
||||
"".to_string(),
|
||||
),
|
||||
Insn::DeleteAsync { cursor_id } => (
|
||||
"DeleteAsync",
|
||||
*cursor_id as i32,
|
||||
0,
|
||||
0,
|
||||
OwnedValue::build_text(Rc::new("".to_string())),
|
||||
0,
|
||||
"".to_string(),
|
||||
),
|
||||
Insn::DeleteAwait { cursor_id } => (
|
||||
"DeleteAwait",
|
||||
*cursor_id as i32,
|
||||
0,
|
||||
0,
|
||||
OwnedValue::build_text(Rc::new("".to_string())),
|
||||
0,
|
||||
"".to_string(),
|
||||
),
|
||||
Insn::NewRowid {
|
||||
cursor,
|
||||
rowid_reg,
|
||||
|
||||
@@ -468,6 +468,14 @@ pub enum Insn {
|
||||
cursor_id: usize,
|
||||
},
|
||||
|
||||
DeleteAsync {
|
||||
cursor_id: CursorID,
|
||||
},
|
||||
|
||||
DeleteAwait {
|
||||
cursor_id: CursorID,
|
||||
},
|
||||
|
||||
NewRowid {
|
||||
cursor: CursorID, // P1
|
||||
rowid_reg: usize, // P2 Destination register to store the new rowid
|
||||
@@ -2686,6 +2694,16 @@ impl Program {
|
||||
}
|
||||
state.pc += 1;
|
||||
}
|
||||
Insn::DeleteAsync { cursor_id } => {
|
||||
let cursor = cursors.get_mut(cursor_id).unwrap();
|
||||
return_if_io!(cursor.delete());
|
||||
state.pc += 1;
|
||||
}
|
||||
Insn::DeleteAwait { cursor_id } => {
|
||||
let cursor = cursors.get_mut(cursor_id).unwrap();
|
||||
cursor.wait_for_completion()?;
|
||||
state.pc += 1;
|
||||
}
|
||||
Insn::NewRowid {
|
||||
cursor, rowid_reg, ..
|
||||
} => {
|
||||
@@ -3916,6 +3934,10 @@ mod tests {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn delete(&mut self) -> Result<CursorResult<()>> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn wait_for_completion(&mut self) -> Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
@@ -96,6 +96,10 @@ impl Cursor for Sorter {
|
||||
Ok(CursorResult::Ok(()))
|
||||
}
|
||||
|
||||
fn delete(&mut self) -> Result<CursorResult<()>> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn set_null_flag(&mut self, _flag: bool) {
|
||||
todo!();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user