diff --git a/src/codegen/stmt/insert.rs b/src/codegen/stmt/insert.rs index 281d2486..4446cf85 100644 --- a/src/codegen/stmt/insert.rs +++ b/src/codegen/stmt/insert.rs @@ -2,11 +2,22 @@ // SPDX-License-Identifier: Apache-2.0 //! `Insert` AST -> `Program` compilation (#195): builds on #194's write //! opcodes (`NewRowid`/`MakeRecord`/`Insert`/`Delete`) plus constraint -//! checks (NOT NULL, PRIMARY KEY/rowid, CHECK, DEFAULT). Table -//! constraints aren't cached anywhere (`TableSchema` is deliberately -//! naive — see `src/schema/ddl_reader.rs`), so this module re-parses -//! `schema.sql` with the real parser to recover them, the same trick -//! `rowid_alias_from_sql` already uses for the PK-rowid-alias fact. +//! checks (NOT NULL, PRIMARY KEY/rowid, CHECK, DEFAULT). `TableSchema` +//! is deliberately naive (see `src/schema/ddl_reader.rs`, spec +//! 002 Requirement 5 — the minimal DDL reader must not depend on the +//! full parser) and doesn't store constraint info structurally, so +//! this module recovers it by parsing `schema.sql` with the real +//! parser — but only once per distinct DDL text: [`cached_create_table`] +//! memoizes the parsed `CreateTable` in a process-wide, content-addressed +//! cache (#643), so a schema reused across many INSERT/UPDATE compiles +//! (e.g. `exec.rs`'s multi-statement script mode) only pays the +//! tokenize+parse cost once. Measured in isolation this reparse costs +//! ~5µs — negligible next to the ~14ms/iter of I/O-dominated execution +//! `tests/performance/crud.rs`'s `insert_single`/`update_pk` benchmarks +//! spend most of their time on (and that bench compiles the program once +//! outside its timed loop besides), so this cache doesn't move those +//! numbers; it's still correct and worth having for repeated-compile +//! workloads. //! //! Secondary indexes are maintained on every row (#196): each index on //! the table gets its own write cursor, and once a row is inserted the @@ -65,6 +76,9 @@ //! merely evaluates to `NULL` at runtime is not distinguished from //! an ordinary `NOT NULL` violation. +use std::collections::HashMap; +use std::sync::{Arc, Mutex, OnceLock}; + use crate::codegen::expr::{column_index, compile_cond, compile_value}; use crate::codegen::index_maintenance::{ emit_index_key_ops, open_index_cursors, valid_table_root_page, @@ -75,14 +89,50 @@ use crate::codegen::select::{ }; use crate::codegen::{CondTargets, Emitter, Label, NullTarget, RegAlloc, Target}; use crate::parser::ast::{ - ColumnConstraint, ConflictAction, DefaultValue, Expr, ExprKind, Insert, InsertSource, Literal, - TableConstraint, TableRef, + ColumnConstraint, ConflictAction, CreateTable, DefaultValue, Expr, ExprKind, Insert, + InsertSource, Literal, TableConstraint, TableRef, }; use crate::parser::error::ParseOutcome; use crate::parser::parse_create_table; use crate::schema::TableSchema; use crate::vdbe::{affinity_of, Instruction, Opcode, Program, P4}; +/// Process-wide cache of parsed `CREATE TABLE` DDL, keyed by the exact +/// `schema.sql` text — content-addressed, since the parse result depends +/// only on that text. Populated by [`cached_create_table`], the shared +/// entry point `compile_insert`/`compile_update_with_catalog` use instead +/// of calling `parse_create_table` directly (#643). Unbounded: a process +/// with many distinct, short-lived `CREATE TABLE` texts (e.g. repeated +/// create/drop of differently-named tables) will grow this map without +/// eviction, the same tradeoff `src/vfs/shm.rs`'s `SHM_FILES` cache makes. +static CREATE_TABLE_CACHE: OnceLock>>> = OnceLock::new(); + +/// Parses `schema.sql` into a [`CreateTable`], reusing a cached parse for +/// the same DDL text instead of re-tokenizing/re-parsing it on every call +/// (#643 — this ran once per INSERT/UPDATE compile, i.e. once per +/// single-row statement). +pub(crate) fn cached_create_table(schema: &TableSchema) -> Result, CodegenError> { + let cache = CREATE_TABLE_CACHE.get_or_init(|| Mutex::new(HashMap::new())); + let mut cache = cache + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner); + if let Some(create) = cache.get(&schema.sql) { + return Ok(create.clone()); + } + + let create = match parse_create_table(&schema.sql) { + ParseOutcome::Accepted(create) => Arc::new(*create), + ParseOutcome::Unsupported { message, .. } | ParseOutcome::Invalid { message, .. } => { + return Err(CodegenError::Unsupported { + reason: format!("could not recover constraints from schema DDL: {message}"), + }) + } + }; + + cache.insert(schema.sql.clone(), create.clone()); + Ok(create) +} + const TABLE_CURSOR: i32 = 0; const CHECK_CURSOR: i32 = 1; const FIRST_INDEX_CURSOR: i32 = 2; @@ -197,14 +247,7 @@ pub fn compile_insert( }); } - let create = match parse_create_table(&schema.sql) { - ParseOutcome::Accepted(create) => *create, - ParseOutcome::Unsupported { message, .. } | ParseOutcome::Invalid { message, .. } => { - return Err(CodegenError::Unsupported { - reason: format!("could not recover constraints from schema DDL: {message}"), - }) - } - }; + let create = cached_create_table(schema)?; let rowid_alias = schema.rowid_alias; let plans = column_plans(schema, &create, rowid_alias); diff --git a/src/codegen/stmt/update.rs b/src/codegen/stmt/update.rs index fcf818a9..f63fd9f7 100644 --- a/src/codegen/stmt/update.rs +++ b/src/codegen/stmt/update.rs @@ -32,6 +32,11 @@ //! own #336 fast path (a single top-level equality, nothing compound), //! reusing the exact same per-row body (`col_regs` construction, //! constraint checks, index maintenance) either way. +//! +//! Constraint recovery reuses `insert.rs`'s [`cached_create_table`] +//! (#643) instead of calling `parse_create_table` directly, so the DDL +//! text is only tokenized/parsed once across however many +//! INSERT/UPDATE compiles reuse the same schema. use crate::codegen::expr::{column_index, compile_cond, compile_value, emit_column_read}; use crate::codegen::index_maintenance::{ @@ -39,14 +44,13 @@ use crate::codegen::index_maintenance::{ }; use crate::codegen::select::{is_rowid_reference, top_level_equality_operands, CodegenError}; use crate::codegen::stmt::insert::{ - column_plans, emit_constraint_violation, SQLITE_CONSTRAINT_CHECK, SQLITE_CONSTRAINT_NOTNULL, + cached_create_table, column_plans, emit_constraint_violation, SQLITE_CONSTRAINT_CHECK, + SQLITE_CONSTRAINT_NOTNULL, }; use crate::codegen::{CondTargets, Emitter, NullTarget, RegAlloc, Target}; use crate::parser::ast::{ ConflictAction, Expr, ExprKind, Literal, ParamKind, TableConstraint, Update, }; -use crate::parser::error::ParseOutcome; -use crate::parser::parse_create_table; use crate::schema::TableSchema; use crate::vdbe::{affinity_of, Instruction, Opcode, Program, P4}; @@ -77,14 +81,7 @@ pub fn compile_update_with_catalog( }); } - let create = match parse_create_table(&schema.sql) { - ParseOutcome::Accepted(create) => *create, - ParseOutcome::Unsupported { message, .. } | ParseOutcome::Invalid { message, .. } => { - return Err(CodegenError::Unsupported { - reason: format!("could not recover constraints from schema DDL: {message}"), - }) - } - }; + let create = cached_create_table(schema)?; let rowid_alias = schema.rowid_alias; let plans = column_plans(schema, &create, rowid_alias); diff --git a/src/vdbe/exec.rs b/src/vdbe/exec.rs index ce096765..11603747 100644 --- a/src/vdbe/exec.rs +++ b/src/vdbe/exec.rs @@ -1458,32 +1458,32 @@ mod tests { assert_eq!(*vm.register(1).unwrap(), Value::Text("a".into())); } - /// #368 tagged MC/DC vector (obligation `exec_444`, decision + /// #368 tagged MC/DC vector (obligation `exec_463`, decision /// `reg < 0 || reg as usize > MAX_REGISTERS`): leaf A (`reg < 0`) true. #[test] #[allow(non_snake_case)] - fn mcdc__exec_444__v1_negative_register() { + fn mcdc__exec_463__v1_negative_register() { assert!(matches!( Vm::index("Test", -1), Err(ExecError::RegisterOutOfRange { index: -1, .. }) )); } - /// #368 tagged MC/DC vector (obligation `exec_444`): both leaves false. + /// #368 tagged MC/DC vector (obligation `exec_463`): both leaves false. /// Independence pair for A against - /// `mcdc__exec_444__v1_negative_register`. + /// `mcdc__exec_463__v1_negative_register`. #[test] #[allow(non_snake_case)] - fn mcdc__exec_444__v2_in_range() { + fn mcdc__exec_463__v2_in_range() { assert_eq!(Vm::index("Test", 5).unwrap(), 5); } - /// #368 tagged MC/DC vector (obligation `exec_444`): leaf B + /// #368 tagged MC/DC vector (obligation `exec_463`): leaf B /// (`reg as usize > MAX_REGISTERS`) true, leaf A false. Independence - /// pair for B against `mcdc__exec_444__v2_in_range`. + /// pair for B against `mcdc__exec_463__v2_in_range`. #[test] #[allow(non_snake_case)] - fn mcdc__exec_444__v3_over_max_registers() { + fn mcdc__exec_463__v3_over_max_registers() { let over = (MAX_REGISTERS as i32).saturating_add(1); assert!(matches!( Vm::index("Test", over), @@ -1491,12 +1491,12 @@ mod tests { )); } - /// #368 tagged MC/DC vector (obligation `exec_664`, decision + /// #368 tagged MC/DC vector (obligation `exec_647`, decision /// `matches!(a, Value::Null) || matches!(b, Value::Null)`): leaf A /// true. #[test] #[allow(non_snake_case)] - fn mcdc__exec_664__v1_left_operand_null() { + fn mcdc__exec_647__v1_left_operand_null() { let mut vm = Vm::new(); vm.set_register(0, Value::Null).unwrap(); vm.set_register(1, Value::Integer(1)).unwrap(); @@ -1507,12 +1507,12 @@ mod tests { ); } - /// #368 tagged MC/DC vector (obligation `exec_664`): both leaves + /// #368 tagged MC/DC vector (obligation `exec_647`): both leaves /// false. Independence pair for A against - /// `mcdc__exec_664__v1_left_operand_null`. + /// `mcdc__exec_647__v1_left_operand_null`. #[test] #[allow(non_snake_case)] - fn mcdc__exec_664__v2_neither_operand_null() { + fn mcdc__exec_647__v2_neither_operand_null() { let mut vm = Vm::new(); vm.set_register(0, Value::Integer(1)).unwrap(); vm.set_register(1, Value::Integer(1)).unwrap(); @@ -1523,12 +1523,12 @@ mod tests { ); } - /// #368 tagged MC/DC vector (obligation `exec_664`): leaf B true, + /// #368 tagged MC/DC vector (obligation `exec_647`): leaf B true, /// leaf A false. Independence pair for B against - /// `mcdc__exec_664__v2_neither_operand_null`. + /// `mcdc__exec_647__v2_neither_operand_null`. #[test] #[allow(non_snake_case)] - fn mcdc__exec_664__v3_right_operand_null() { + fn mcdc__exec_647__v3_right_operand_null() { let mut vm = Vm::new(); vm.set_register(0, Value::Integer(1)).unwrap(); vm.set_register(1, Value::Null).unwrap(); diff --git a/tests/mcdc/obligations.json b/tests/mcdc/obligations.json index 5b4e24b9..66ac8db8 100644 --- a/tests/mcdc/obligations.json +++ b/tests/mcdc/obligations.json @@ -4617,198 +4617,171 @@ "compiler_void": true }, { - "id": "exec_444", + "id": "exec_463", "file": "src/vdbe/exec.rs", - "line": 444, + "line": 463, "decision": "reg < 0 || reg as usize > MAX_REGISTERS", "conditions": 2, "vectors_required": 3, "compiler_void": false }, { - "id": "exec_455", + "id": "exec_474", "file": "src/vdbe/exec.rs", - "line": 455, + "line": 474, "decision": "!(0..=MAX_REGISTERS as i32).contains(&count)", "conditions": 1, "vectors_required": 2, "compiler_void": false }, { - "id": "exec_476", + "id": "exec_495", "file": "src/vdbe/exec.rs", - "line": 476, + "line": 495, "decision": "idx >= self.registers.len()", "conditions": 1, "vectors_required": 2, "compiler_void": false }, { - "id": "exec_510", + "id": "exec_529", "file": "src/vdbe/exec.rs", - "line": 510, + "line": 529, "decision": "match self.registers.get_mut(idx) {\n Some(slot) => std::mem::replace(slot, Value::Null),\n None => Value::Null,\n }", "conditions": 0, "vectors_required": 0, "compiler_void": true }, { - "id": "exec_543", + "id": "exec_562", "file": "src/vdbe/exec.rs", - "line": 543, + "line": 562, "decision": "idx >= self.cursors.len()", "conditions": 1, "vectors_required": 2, "compiler_void": false }, { - "id": "exec_577", + "id": "exec_596", "file": "src/vdbe/exec.rs", - "line": 577, + "line": 596, "decision": "idx >= self.agg_contexts.len()", "conditions": 1, "vectors_required": 2, "compiler_void": false }, { - "id": "exec_611", - "file": "src/vdbe/exec.rs", - "line": 611, - "decision": "idx >= self.filters.len()", - "conditions": 1, - "vectors_required": 2, - "compiler_void": false - }, - { - "id": "exec_664", + "id": "exec_647", "file": "src/vdbe/exec.rs", - "line": 664, + "line": 647, "decision": "matches!(a, Value::Null) || matches!(b, Value::Null)", "conditions": 2, "vectors_required": 3, "compiler_void": false }, { - "id": "exec_667", + "id": "exec_650", "file": "src/vdbe/exec.rs", - "line": 667, + "line": 650, "decision": "match &instr.p4 {\n P4::CollSeq {\n collation,\n affinity,\n } => (*collation, Affinity::from_p4_byte(*affinity)),\n _ => (Collation::Binary, Affinity::Blob),\n }", "conditions": 0, "vectors_required": 0, "compiler_void": true }, { - "id": "exec_679", + "id": "exec_662", "file": "src/vdbe/exec.rs", - "line": 679, + "line": 662, "decision": "matches!(affinity, Affinity::Blob)", "conditions": 1, "vectors_required": 2, "compiler_void": false }, { - "id": "exec_688", + "id": "exec_671", "file": "src/vdbe/exec.rs", - "line": 688, + "line": 671, "decision": "holds(ord)", "conditions": 1, "vectors_required": 2, "compiler_void": false }, { - "id": "exec_736", + "id": "exec_719", "file": "src/vdbe/exec.rs", - "line": 736, - "decision": "match instr.opcode {\n Init => control::init(instr),\n Goto => control::goto(instr),\n Once => control::once(vm, pc, instr),\n BeginSubrtn => control::begin_subrtn(),\n Return => control::r#return(vm, instr),\n Halt => control::halt(instr),\n Transaction => control::transaction(vm, instr),\n AutoCommit => control::auto_commit(vm, instr),\n SetJournalMode => pragma::set_journal_mode(vm, instr),\n IntegrityCheck => pragma::integrity_check(vm, instr),\n IfNot => control::if_not(vm, instr),\n IfNotZero => control::if_not_zero(vm, instr),\n IfPos => control::if_pos(vm, instr),\n DecrJumpZero => control::decr_jump_zero(vm, instr),\n IsNull => control::is_null(vm, instr),\n NotNull => control::not_null(vm, instr),\n MustBeInt => control::must_be_int(vm, instr),\n OffsetLimit => control::offset_limit(vm, instr),\n\n Eq => compare_jump(vm, instr, |o| o == Ordering::Equal),\n Ge => compare_jump(vm, instr, |o| o != Ordering::Less),\n Gt => compare_jump(vm, instr, |o| o == Ordering::Greater),\n Le => compare_jump(vm, instr, |o| o != Ordering::Greater),\n Lt => compare_jump(vm, instr, |o| o == Ordering::Less),\n RealAffinity => real_affinity(vm, instr),\n Cast => cast(vm, instr),\n\n Add => arithmetic::add(vm, instr),\n Subtract => arithmetic::subtract(vm, instr),\n Multiply => arithmetic::multiply(vm, instr),\n Divide => arithmetic::divide(vm, instr),\n Remainder => arithmetic::remainder(vm, instr),\n Not => arithmetic::not(vm, instr),\n BitAnd => arithmetic::bit_and(vm, instr),\n BitOr => arithmetic::bit_or(vm, instr),\n ShiftLeft => arithmetic::shift_left(vm, instr),\n ShiftRight => arithmetic::shift_right(vm, instr),\n BitNot => arithmetic::bit_not(vm, instr),\n Concat => arithmetic::concat(vm, instr),\n\n Integer => result::integer(vm, instr),\n Int64 => result::int64(vm, instr),\n Real => result::real(vm, instr),\n Blob => result::blob(vm, instr),\n Null => result::null(vm, instr),\n String8 => result::string8(vm, instr),\n Variable => result::variable(vm, instr),\n MakeRecord => result::make_record(vm, instr),\n ResultRow => result::result_row(vm, instr),\n Copy => result::copy(vm, instr),\n\n OpenRead => cursor::open_read(vm, instr),\n OpenWrite => cursor::open_write(vm, instr),\n OpenEphemeral => cursor::open_ephemeral(vm, instr),\n OpenDup => cursor::open_dup(vm, instr),\n OpenPseudo => cursor::open_pseudo(vm, instr),\n Rewind => cursor::rewind(vm, instr),\n Last => cursor::last(vm, instr),\n Next => cursor::next(vm, instr),\n Column => cursor::column(vm, instr),\n Rowid => cursor::rowid(vm, instr),\n SeekRowid => cursor::seek_rowid(vm, instr),\n SeekIndexEq => cursor::seek_index_eq(vm, instr),\n IdxRowid => cursor::idx_rowid(vm, instr),\n IdxRewind => cursor::idx_rewind(vm, instr),\n IdxLast => cursor::idx_last(vm, instr),\n IdxNext => cursor::idx_next(vm, instr),\n IdxPrev => cursor::idx_prev(vm, instr),\n NullRow => cursor::null_row(vm, instr),\n Sequence => cursor::sequence(vm, instr),\n Found => cursor::found(vm, instr),\n IdxInsert => cursor::idx_insert(vm, instr),\n IdxDelete => cursor::idx_delete(vm, instr),\n Count => cursor::count(vm, instr),\n AutoIndexInsert => cursor::auto_index_insert(vm, instr),\n AutoIndexSeek => cursor::auto_index_seek(vm, instr),\n AutoIndexRowid => cursor::auto_index_rowid(vm, instr),\n AutoIndexNext => cursor::auto_index_next(vm, instr),\n IdxLE => cursor::idx_le(vm, instr),\n NoConflict => cursor::no_conflict(vm, instr),\n Delete => cursor::delete(vm, instr),\n Insert => cursor::insert(vm, instr),\n NewRowid => cursor::new_rowid(vm, instr),\n CreateTable => cursor::create_table(vm, instr),\n CreateView => cursor::create_view(vm, instr),\n DropTable => cursor::drop_table(vm, instr),\n CreateIndex => cursor::create_index(vm, instr),\n DropIndex => cursor::drop_index(vm, instr),\n Analyze => cursor::analyze(vm, instr),\n\n SorterOpen => sorter::sorter_open(vm, instr),\n SorterInsert => sorter::sorter_insert(vm, instr),\n SorterSort | Sort => sorter::sorter_sort(vm, instr),\n SorterNext => sorter::sorter_next(vm, instr),\n HashAggOpen => hash_agg::hash_agg_open(vm, instr),\n HashAggFind => hash_agg::hash_agg_find(vm, instr),\n HashAggStep => hash_agg::hash_agg_step(vm, instr),\n HashAggRewind => hash_agg::hash_agg_rewind(vm, instr),\n HashAggData => hash_agg::hash_agg_data(vm, instr),\n HashAggNext => hash_agg::hash_agg_next(vm, instr),\n SorterData => sorter::sorter_data(vm, instr),\n\n Function => function(vm, instr),\n AggStep => agg_step(vm, instr),\n AggFinal => agg_final(vm, instr),\n\n FilterAdd => filter_add(vm, instr),\n Filter => filter_check(vm, instr),\n }", - "conditions": 0, - "vectors_required": 0, - "compiler_void": true - }, - { - "id": "exec_852", - "file": "src/vdbe/exec.rs", - "line": 852, - "decision": "match &instr.p4 {\n P4::Int(n) => u64::try_from(*n).unwrap_or(0),\n _ => 0,\n }", + "line": 719, + "decision": "match instr.opcode {\n Init => control::init(instr),\n Goto => control::goto(instr),\n Once => control::once(vm, pc, instr),\n BeginSubrtn => control::begin_subrtn(),\n Return => control::r#return(vm, instr),\n Halt => control::halt(instr),\n Transaction => control::transaction(vm, instr),\n AutoCommit => control::auto_commit(vm, instr),\n SetJournalMode => pragma::set_journal_mode(vm, instr),\n IntegrityCheck => pragma::integrity_check(vm, instr),\n IfNot => control::if_not(vm, instr),\n IfNotZero => control::if_not_zero(vm, instr),\n IfPos => control::if_pos(vm, instr),\n DecrJumpZero => control::decr_jump_zero(vm, instr),\n IsNull => control::is_null(vm, instr),\n NotNull => control::not_null(vm, instr),\n MustBeInt => control::must_be_int(vm, instr),\n OffsetLimit => control::offset_limit(vm, instr),\n\n Eq => compare_jump(vm, instr, |o| o == Ordering::Equal),\n Ge => compare_jump(vm, instr, |o| o != Ordering::Less),\n Gt => compare_jump(vm, instr, |o| o == Ordering::Greater),\n Le => compare_jump(vm, instr, |o| o != Ordering::Greater),\n Lt => compare_jump(vm, instr, |o| o == Ordering::Less),\n RealAffinity => real_affinity(vm, instr),\n Cast => cast(vm, instr),\n\n Add => arithmetic::add(vm, instr),\n Subtract => arithmetic::subtract(vm, instr),\n Multiply => arithmetic::multiply(vm, instr),\n Divide => arithmetic::divide(vm, instr),\n Remainder => arithmetic::remainder(vm, instr),\n Not => arithmetic::not(vm, instr),\n BitAnd => arithmetic::bit_and(vm, instr),\n BitOr => arithmetic::bit_or(vm, instr),\n ShiftLeft => arithmetic::shift_left(vm, instr),\n ShiftRight => arithmetic::shift_right(vm, instr),\n BitNot => arithmetic::bit_not(vm, instr),\n Concat => arithmetic::concat(vm, instr),\n\n Integer => result::integer(vm, instr),\n Int64 => result::int64(vm, instr),\n Real => result::real(vm, instr),\n Blob => result::blob(vm, instr),\n Null => result::null(vm, instr),\n String8 => result::string8(vm, instr),\n Variable => result::variable(vm, instr),\n MakeRecord => result::make_record(vm, instr),\n ResultRow => result::result_row(vm, instr),\n Copy => result::copy(vm, instr),\n\n OpenRead => cursor::open_read(vm, instr),\n OpenWrite => cursor::open_write(vm, instr),\n OpenEphemeral => cursor::open_ephemeral(vm, instr),\n OpenDup => cursor::open_dup(vm, instr),\n OpenPseudo => cursor::open_pseudo(vm, instr),\n Rewind => cursor::rewind(vm, instr),\n Last => cursor::last(vm, instr),\n Next => cursor::next(vm, instr),\n Column => cursor::column(vm, instr),\n Rowid => cursor::rowid(vm, instr),\n SeekRowid => cursor::seek_rowid(vm, instr),\n SeekIndexEq => cursor::seek_index_eq(vm, instr),\n SeekIndexGE => cursor::seek_index_ge(vm, instr),\n IdxCompareGT => cursor::idx_compare_gt(vm, instr),\n IdxRowid => cursor::idx_rowid(vm, instr),\n IdxRewind => cursor::idx_rewind(vm, instr),\n IdxLast => cursor::idx_last(vm, instr),\n IdxNext => cursor::idx_next(vm, instr),\n IdxPrev => cursor::idx_prev(vm, instr),\n NullRow => cursor::null_row(vm, instr),\n Sequence => cursor::sequence(vm, instr),\n Found => cursor::found(vm, instr),\n IdxInsert => cursor::idx_insert(vm, instr),\n IdxDelete => cursor::idx_delete(vm, instr),\n Count => cursor::count(vm, instr),\n AutoIndexInsert => cursor::auto_index_insert(vm, instr),\n AutoIndexSeek => cursor::auto_index_seek(vm, instr),\n AutoIndexRowid => cursor::auto_index_rowid(vm, instr),\n AutoIndexNext => cursor::auto_index_next(vm, instr),\n IdxLE => cursor::idx_le(vm, instr),\n NoConflict => cursor::no_conflict(vm, instr),\n Delete => cursor::delete(vm, instr),\n Insert => cursor::insert(vm, instr),\n NewRowid => cursor::new_rowid(vm, instr),\n CreateTable => cursor::create_table(vm, instr),\n CreateView => cursor::create_view(vm, instr),\n DropTable => cursor::drop_table(vm, instr),\n CreateIndex => cursor::create_index(vm, instr),\n DropIndex => cursor::drop_index(vm, instr),\n Analyze => cursor::analyze(vm, instr),\n\n SorterOpen => sorter::sorter_open(vm, instr),\n SorterInsert => sorter::sorter_insert(vm, instr),\n SorterSort | Sort => sorter::sorter_sort(vm, instr),\n SorterNext => sorter::sorter_next(vm, instr),\n HashAggOpen => hash_agg::hash_agg_open(vm, instr),\n HashAggFind => hash_agg::hash_agg_find(vm, instr),\n HashAggStep => hash_agg::hash_agg_step(vm, instr),\n HashAggRewind => hash_agg::hash_agg_rewind(vm, instr),\n HashAggData => hash_agg::hash_agg_data(vm, instr),\n HashAggNext => hash_agg::hash_agg_next(vm, instr),\n SorterData => sorter::sorter_data(vm, instr),\n\n Function => function(vm, instr),\n AggStep => agg_step(vm, instr),\n AggFinal => agg_final(vm, instr),\n }", "conditions": 0, "vectors_required": 0, "compiler_void": true }, { - "id": "exec_866", + "id": "exec_837", "file": "src/vdbe/exec.rs", - "line": 866, - "decision": "vm.filter_might_contain(instr.p1, &value)?", - "conditions": 1, - "vectors_required": 2, - "compiler_void": false - }, - { - "id": "exec_880", - "file": "src/vdbe/exec.rs", - "line": 880, + "line": 837, "decision": "match &instr.p4 {\n P4::Str(s) => s.as_str(),\n other => {\n return Err(ExecError::MalformedInstruction {\n opcode: \"Function\",\n reason: format!(\"expected a \\\"name(arity)\\\" string P4, got {other:?}\"),\n })\n }\n }", "conditions": 0, "vectors_required": 0, "compiler_void": true }, { - "id": "exec_932", + "id": "exec_889", "file": "src/vdbe/exec.rs", - "line": 932, + "line": 889, "decision": "match &instr.p4 {\n P4::AggFunc {\n name,\n arity,\n collation,\n } => (name.as_str(), *arity, *collation),\n other => {\n return Err(ExecError::MalformedInstruction {\n opcode: \"AggStep\",\n reason: format!(\"expected an AggFunc P4, got {other:?}\"),\n })\n }\n }", "conditions": 0, "vectors_required": 0, "compiler_void": true }, { - "id": "exec_961", + "id": "exec_918", "file": "src/vdbe/exec.rs", - "line": 961, + "line": 918, "decision": "instr.p5 == 0", "conditions": 1, "vectors_required": 2, "compiler_void": false }, { - "id": "exec_996", + "id": "exec_953", "file": "src/vdbe/exec.rs", - "line": 996, + "line": 953, "decision": "match &instr.p4 {\n P4::Str(s) => s.as_str(),\n other => {\n return Err(ExecError::MalformedInstruction {\n opcode: \"AggFinal\",\n reason: format!(\"expected a \\\"name(arity)\\\" string P4, got {other:?}\"),\n })\n }\n }", "conditions": 0, "vectors_required": 0, "compiler_void": true }, { - "id": "exec_1026", + "id": "exec_983", "file": "src/vdbe/exec.rs", - "line": 1026, + "line": 983, "decision": "!descriptor.ends_with(')')", "conditions": 1, "vectors_required": 2, "compiler_void": false }, { - "id": "exec_1139", + "id": "exec_1096", "file": "src/vdbe/exec.rs", - "line": 1139, + "line": 1096, "decision": "steps > MAX_STEPS", "conditions": 1, "vectors_required": 2, "compiler_void": false }, { - "id": "exec_1145", + "id": "exec_1102", "file": "src/vdbe/exec.rs", - "line": 1145, + "line": 1102, "decision": "match dispatch(&mut vm, pc, instr)? {\n Step::Next => {\n pc = pc.saturating_add(1);\n }\n Step::Jump(target) => pc = target,\n Step::Halt { code: 0, .. } => {\n // A program with no explicit `Transaction` (#194's\n // original behavior, unchanged) treats a successful\n // `Halt` as an implicit commit, flushing any pending\n // write-opcode changes before returning. A `Vm::with_db`\n // (read-only) or a writable `Vm` that never actually\n // wrote anything both take the cheap\n // `writer.is_none()`/`dirty.is_empty()` no-op path.\n //\n // #360: a program that opened an explicit transaction\n // (`Transaction` opcode, `vm.autocommit == false`) and\n // hasn't reached a matching `AutoCommit` yet does\n // neither — one SQL statement is one `Program`/`Vm`\n // (see `execute_transaction_step`), so `BEGIN`'s own\n // `Halt` running with `autocommit == false` is the\n // normal, expected case: the transaction stays open,\n // `vm.autocommit` carries that forward to whichever\n // `Vm` runs the next statement on this same `Pager`.\n if let Some(db) = &vm.db {\n if vm.autocommit {\n if let Some(writer) = &db.writer {\n writer.borrow_mut().flush()?;\n }\n }\n }\n return Ok((vm.rows, vm.autocommit));\n }\n Step::Halt { code, message } => return Err(ExecError::Halted { code, message }),\n }", "conditions": 0, "vectors_required": 0, "compiler_void": true }, { - "id": "exec_1169", + "id": "exec_1126", "file": "src/vdbe/exec.rs", - "line": 1169, + "line": 1126, "decision": "vm.autocommit", "conditions": 1, "vectors_required": 2, @@ -4977,19 +4950,19 @@ "compiler_void": true }, { - "id": "encode_232", + "id": "encode_238", "file": "src/record/encode.rs", - "line": 232, + "line": 238, "decision": "varint_len(header_len as u64).saturating_add(header_body_len) != header_len", "conditions": 1, "vectors_required": 2, "compiler_void": false }, { - "id": "program_512", + "id": "program_509", "file": "src/vdbe/program.rs", - "line": 512, - "decision": "match o {\n Opcode::Init\n | Opcode::Goto\n | Opcode::Once\n | Opcode::BeginSubrtn\n | Opcode::Return\n | Opcode::Halt\n | Opcode::Transaction\n | Opcode::AutoCommit\n | Opcode::SetJournalMode\n | Opcode::IntegrityCheck\n | Opcode::IfNot\n | Opcode::IfNotZero\n | Opcode::IfPos\n | Opcode::DecrJumpZero\n | Opcode::IsNull\n | Opcode::NotNull\n | Opcode::MustBeInt\n | Opcode::OffsetLimit\n | Opcode::OpenRead\n | Opcode::OpenWrite\n | Opcode::OpenEphemeral\n | Opcode::OpenDup\n | Opcode::OpenPseudo\n | Opcode::Rewind\n | Opcode::Last\n | Opcode::Next\n | Opcode::Column\n | Opcode::Rowid\n | Opcode::SeekRowid\n | Opcode::NullRow\n | Opcode::Sequence\n | Opcode::Found\n | Opcode::IdxInsert\n | Opcode::IdxLE\n | Opcode::Delete\n | Opcode::Insert\n | Opcode::NewRowid\n | Opcode::IdxDelete\n | Opcode::Count\n | Opcode::NoConflict\n | Opcode::SeekIndexEq\n | Opcode::IdxRowid\n | Opcode::IdxRewind\n | Opcode::IdxLast\n | Opcode::IdxNext\n | Opcode::IdxPrev\n | Opcode::AutoIndexInsert\n | Opcode::AutoIndexSeek\n | Opcode::AutoIndexRowid\n | Opcode::AutoIndexNext\n | Opcode::CreateTable\n | Opcode::CreateView\n | Opcode::DropTable\n | Opcode::CreateIndex\n | Opcode::DropIndex\n | Opcode::Analyze\n | Opcode::Eq\n | Opcode::Ge\n | Opcode::Gt\n | Opcode::Le\n | Opcode::Lt\n | Opcode::RealAffinity\n | Opcode::Add\n | Opcode::Subtract\n | Opcode::Multiply\n | Opcode::Divide\n | Opcode::Remainder\n | Opcode::Not\n | Opcode::BitAnd\n | Opcode::BitOr\n | Opcode::ShiftLeft\n | Opcode::ShiftRight\n | Opcode::BitNot\n | Opcode::Concat\n | Opcode::Cast\n | Opcode::Function\n | Opcode::AggStep\n | Opcode::AggFinal\n | Opcode::Integer\n | Opcode::Int64\n | Opcode::Real\n | Opcode::Blob\n | Opcode::Null\n | Opcode::String8\n | Opcode::Variable\n | Opcode::MakeRecord\n | Opcode::ResultRow\n | Opcode::Copy\n | Opcode::SorterOpen\n | Opcode::SorterInsert\n | Opcode::SorterSort\n | Opcode::SorterNext\n | Opcode::SorterData\n | Opcode::Sort\n | Opcode::HashAggOpen\n | Opcode::HashAggFind\n | Opcode::HashAggStep\n | Opcode::HashAggRewind\n | Opcode::HashAggData\n | Opcode::HashAggNext\n | Opcode::FilterAdd\n | Opcode::Filter => {}\n }", + "line": 509, + "decision": "match o {\n Opcode::Init\n | Opcode::Goto\n | Opcode::Once\n | Opcode::BeginSubrtn\n | Opcode::Return\n | Opcode::Halt\n | Opcode::Transaction\n | Opcode::AutoCommit\n | Opcode::SetJournalMode\n | Opcode::IntegrityCheck\n | Opcode::IfNot\n | Opcode::IfNotZero\n | Opcode::IfPos\n | Opcode::DecrJumpZero\n | Opcode::IsNull\n | Opcode::NotNull\n | Opcode::MustBeInt\n | Opcode::OffsetLimit\n | Opcode::OpenRead\n | Opcode::OpenWrite\n | Opcode::OpenEphemeral\n | Opcode::OpenDup\n | Opcode::OpenPseudo\n | Opcode::Rewind\n | Opcode::Last\n | Opcode::Next\n | Opcode::Column\n | Opcode::Rowid\n | Opcode::SeekRowid\n | Opcode::NullRow\n | Opcode::Sequence\n | Opcode::Found\n | Opcode::IdxInsert\n | Opcode::IdxLE\n | Opcode::Delete\n | Opcode::Insert\n | Opcode::NewRowid\n | Opcode::IdxDelete\n | Opcode::Count\n | Opcode::NoConflict\n | Opcode::SeekIndexEq\n | Opcode::IdxRowid\n | Opcode::SeekIndexGE\n | Opcode::IdxCompareGT\n | Opcode::IdxRewind\n | Opcode::IdxLast\n | Opcode::IdxNext\n | Opcode::IdxPrev\n | Opcode::AutoIndexInsert\n | Opcode::AutoIndexSeek\n | Opcode::AutoIndexRowid\n | Opcode::AutoIndexNext\n | Opcode::CreateTable\n | Opcode::CreateView\n | Opcode::DropTable\n | Opcode::CreateIndex\n | Opcode::DropIndex\n | Opcode::Analyze\n | Opcode::Eq\n | Opcode::Ge\n | Opcode::Gt\n | Opcode::Le\n | Opcode::Lt\n | Opcode::RealAffinity\n | Opcode::Add\n | Opcode::Subtract\n | Opcode::Multiply\n | Opcode::Divide\n | Opcode::Remainder\n | Opcode::Not\n | Opcode::BitAnd\n | Opcode::BitOr\n | Opcode::ShiftLeft\n | Opcode::ShiftRight\n | Opcode::BitNot\n | Opcode::Concat\n | Opcode::Cast\n | Opcode::Function\n | Opcode::AggStep\n | Opcode::AggFinal\n | Opcode::Integer\n | Opcode::Int64\n | Opcode::Real\n | Opcode::Blob\n | Opcode::Null\n | Opcode::String8\n | Opcode::Variable\n | Opcode::MakeRecord\n | Opcode::ResultRow\n | Opcode::Copy\n | Opcode::SorterOpen\n | Opcode::SorterInsert\n | Opcode::SorterSort\n | Opcode::SorterNext\n | Opcode::SorterData\n | Opcode::Sort\n | Opcode::HashAggOpen\n | Opcode::HashAggFind\n | Opcode::HashAggStep\n | Opcode::HashAggRewind\n | Opcode::HashAggData\n | Opcode::HashAggNext => {}\n }", "conditions": 0, "vectors_required": 0, "compiler_void": true