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:
Pekka Enberg
2024-12-27 18:34:02 +02:00
15 changed files with 469 additions and 76 deletions

View File

@@ -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;

View File

@@ -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
}

View File

@@ -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
View 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)
}

View File

@@ -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.

View File

@@ -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"),

View File

@@ -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(

View File

@@ -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),
}
}
}

View File

@@ -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) => {

View File

@@ -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,

View File

@@ -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;

View File

@@ -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);

View File

@@ -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,

View File

@@ -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!()
}

View File

@@ -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!();
}