diff --git a/&1 b/&1 new file mode 100644 index 000000000..e69de29bb diff --git a/aqlitedb b/aqlitedb new file mode 100644 index 000000000..5fe5a8df9 Binary files /dev/null and b/aqlitedb differ diff --git a/cli/app.rs b/cli/app.rs index 08c6bd5e2..f3ed8e56d 100644 --- a/cli/app.rs +++ b/cli/app.rs @@ -438,6 +438,7 @@ impl Limbo { } self.reset_input(); + } fn print_query_performance_stats(&mut self, start: Instant, stats: QueryStatistics) { diff --git a/core/incremental/compiler.rs b/core/incremental/compiler.rs index 9cd2e3702..3687bdad1 100644 --- a/core/incremental/compiler.rs +++ b/core/incremental/compiler.rs @@ -981,6 +981,7 @@ mod tests { ], has_rowid: true, is_strict: false, + has_autoincrement: false, unique_sets: None, }; schema.add_btree_table(Arc::new(users_table)); @@ -2352,6 +2353,7 @@ mod tests { ], has_rowid: true, is_strict: false, + has_autoincrement: false, unique_sets: None, }; schema.add_btree_table(Arc::new(sales_table)); diff --git a/core/lib.rs b/core/lib.rs index 373e89c81..03532f41b 100644 --- a/core/lib.rs +++ b/core/lib.rs @@ -1169,6 +1169,8 @@ impl Connection { if self.closed.get() { return Err(LimboError::InternalError("Connection closed".to_string())); } + + // self.maybe_update_schema()?; let syms = self.syms.borrow(); let pager = self.pager.borrow().clone(); match cmd { @@ -1336,6 +1338,7 @@ impl Connection { if matches!(self.transaction_state.get(), TransactionState::None) && current_schema_version != schema.schema_version { + // if current_schema_version != schema.schema_version { self.schema.replace(schema.clone()); } @@ -1346,10 +1349,24 @@ impl Connection { #[cfg(all(feature = "fs", feature = "conn_raw_api"))] pub fn read_schema_version(&self) -> Result { let pager = self.pager.borrow(); - pager + let was_in_tx = self.transaction_state.get() != TransactionState::None; + if !was_in_tx { + pager.begin_read_tx()?; + } + + let result = pager .io - .block(|| pager.with_header(|header| header.schema_cookie)) - .map(|version| version.get()) + .block(|| pager.with_header(|header| header.schema_cookie)); + + if !was_in_tx { + pager.end_read_tx()?; + } + + match result { + Ok(version) => Ok(version.get()), + Err(LimboError::Page1NotAlloc) => Ok(0), + Err(e) => Err(e), + } } /// Update schema version to the new value within opened write transaction @@ -2084,6 +2101,7 @@ impl Statement { pub fn new(program: vdbe::Program, mv_store: Option>, pager: Rc) -> Self { let accesses_db = program.accesses_db; let state = vdbe::ProgramState::new(program.max_registers, program.cursor_ref.len()); + let schema_version = program.connection.schema.borrow().schema_version; Self { program, state, @@ -2193,6 +2211,7 @@ impl Statement { Cmd::ExplainQueryPlan(_stmt) => todo!(), } }; + // Save parameters before they are reset let parameters = std::mem::take(&mut self.state.parameters); self.reset(); diff --git a/core/schema.rs b/core/schema.rs index a37de64a6..f984b9d26 100644 --- a/core/schema.rs +++ b/core/schema.rs @@ -672,6 +672,7 @@ pub struct BTreeTable { pub columns: Vec, pub has_rowid: bool, pub is_strict: bool, + pub has_autoincrement: bool, pub unique_sets: Option>>, } @@ -830,6 +831,7 @@ fn create_table( let table_name = normalize_ident(tbl_name.name.as_str()); trace!("Creating table {}", table_name); let mut has_rowid = true; + let mut has_autoincrement = false; let mut primary_key_columns = vec![]; let mut cols = vec![]; let is_strict: bool; @@ -922,12 +924,25 @@ fn create_table( let mut collation = None; for c_def in constraints { match c_def.constraint { - ast::ColumnConstraint::PrimaryKey { order: o, .. } => { + ast::ColumnConstraint::PrimaryKey { + order: o, + auto_increment, + .. + } => { primary_key = true; + if auto_increment { + has_autoincrement = true; + } if let Some(o) = o { order = o; } } + // ast::ColumnConstraint::PrimaryKey { order: o, .. } => { + // primary_key = true; + // if let Some(o) = o { + // order = o; + // } + // } ast::ColumnConstraint::NotNull { nullable, .. } => { notnull = !nullable; } @@ -986,6 +1001,7 @@ fn create_table( name: table_name, has_rowid, primary_key_columns, + has_autoincrement, columns: cols, is_strict, unique_sets: if unique_sets.is_empty() { @@ -1302,6 +1318,7 @@ pub fn sqlite_schema_table() -> BTreeTable { name: "sqlite_schema".to_string(), has_rowid: true, is_strict: false, + has_autoincrement: false, primary_key_columns: vec![], columns: vec![ Column { @@ -1984,6 +2001,7 @@ mod tests { name: "t1".to_string(), has_rowid: true, is_strict: false, + has_autoincrement: false, primary_key_columns: vec![("nonexistent".to_string(), SortOrder::Asc)], columns: vec![Column { name: Some("a".to_string()), diff --git a/core/translate/insert.rs b/core/translate/insert.rs index dc4a0a0eb..9c5694c0a 100644 --- a/core/translate/insert.rs +++ b/core/translate/insert.rs @@ -34,6 +34,7 @@ use super::expr::{translate_expr, translate_expr_no_constant_opt, NoConstantOptR use super::optimizer::rewrite_expr; use super::plan::QueryDestination; use super::select::translate_select; +use crate::schema::sqlite_schema_table; struct TempTableCtx { cursor_id: usize, @@ -362,11 +363,28 @@ pub fn translate_insert( } // Create new rowid if a) not provided by user or b) provided by user but is NULL - program.emit_insn(Insn::NewRowid { - cursor: cursor_id, - rowid_reg: insertion.key_register(), - prev_largest_reg: 0, - }); + // program.emit_insn(Insn::NewRowid { + // cursor: cursor_id, + // rowid_reg: insertion.key_register(), + // prev_largest_reg: 0, + // }); + + if btree_table.has_autoincrement { + // Table has AUTOINCREMENT, use the sqlite_sequence logic. + emit_autoincrement_logic( + &mut program, + schema, + &btree_table, + cursor_id, + insertion.key_register(), + )?; + } else { + program.emit_insn(Insn::NewRowid { + cursor: cursor_id, + rowid_reg: insertion.key_register(), + prev_largest_reg: 0, + }); + } if let Some(must_be_int_label) = check_rowid_is_integer_label { program.resolve_label(must_be_int_label, program.offset()); @@ -1132,3 +1150,191 @@ fn translate_virtual_table_insert( Ok(program) } + +fn emit_autoincrement_logic( + program: &mut ProgramBuilder, + schema: &Schema, + table: &schema::BTreeTable, + main_cursor_id: usize, + key_register: usize, +) -> Result<()> { + let seq_table = schema.get_btree_table("sqlite_sequence").ok_or_else(|| { + crate::error::LimboError::InternalError("sqlite_sequence table not found".to_string()) + })?; + + let seq_cursor_id = program.alloc_cursor_id(CursorType::BTreeTable(seq_table.clone())); + program.emit_insn(Insn::OpenWrite { + cursor_id: seq_cursor_id, + root_page: seq_table.root_page.into(), + db: 0, + }); + + let table_name_reg = program.emit_string8_new_reg(table.name.clone()); + let r_seq = program.alloc_register(); + let r_max = program.alloc_register(); + let r_seq_rowid = program.alloc_register(); + + program.emit_insn(Insn::Integer { + dest: r_seq, + value: 0, + }); + + program.emit_insn(Insn::Null { + dest: r_seq_rowid, + dest_end: None, + }); + + let loop_start_label = program.allocate_label(); + let loop_end_label = program.allocate_label(); + let found_label = program.allocate_label(); + + program.emit_insn(Insn::Rewind { + cursor_id: seq_cursor_id, + pc_if_empty: loop_end_label, + }); + program.preassign_label_to_next_insn(loop_start_label); + + let name_col_reg = program.alloc_register(); + + // Read 'name' column (index 0) + + program.emit_column_or_rowid(seq_cursor_id, 0, name_col_reg); + + program.emit_insn(Insn::Ne { + // If name != our_table, continue + lhs: table_name_reg, + rhs: name_col_reg, + target_pc: found_label, + flags: Default::default(), + collation: None, + }); + + // Read 'seq' column (index 1) + program.emit_column_or_rowid(seq_cursor_id, 1, r_seq); + program.emit_insn(Insn::RowId { + cursor_id: seq_cursor_id, + dest: r_seq_rowid, + }); + program.emit_insn(Insn::Goto { + target_pc: loop_end_label, + }); + + program.preassign_label_to_next_insn(found_label); + program.emit_insn(Insn::Next { + cursor_id: seq_cursor_id, + pc_if_next: loop_start_label, + }); + program.preassign_label_to_next_insn(loop_end_label); + + // get the largest + let dummy_reg = program.alloc_register(); + program.emit_insn(Insn::NewRowid { + cursor: main_cursor_id, + rowid_reg: dummy_reg, + prev_largest_reg: r_max, + }); + + let r_max_is_bigger_label = program.allocate_label(); + let continue_label = program.allocate_label(); + program.emit_insn(Insn::Ge { + lhs: r_max, + rhs: r_seq, + target_pc: r_max_is_bigger_label, + flags: Default::default(), + collation: None, + }); + program.emit_insn(Insn::Copy { + src_reg: r_seq, + dst_reg: key_register, + extra_amount: 0, + }); + program.emit_insn(Insn::Goto { + target_pc: continue_label, + }); + + program.preassign_label_to_next_insn(r_max_is_bigger_label); + program.emit_insn(Insn::Copy { + src_reg: r_max, + dst_reg: key_register, + extra_amount: 0, + }); + program.preassign_label_to_next_insn(continue_label); + + program.emit_insn(Insn::AddImm { + register: key_register, + value: 1, + }); + + let record_reg = program.alloc_register(); + let record_start_reg = program.alloc_registers(2); + program.emit_insn(Insn::Copy { + src_reg: table_name_reg, + dst_reg: record_start_reg, + extra_amount: 0, + }); + program.emit_insn(Insn::Copy { + src_reg: key_register, + dst_reg: record_start_reg + 1, + extra_amount: 0, + }); + + program.emit_insn(Insn::MakeRecord { + start_reg: record_start_reg, + count: 2, + dest_reg: record_reg, + index_name: None, + }); + + let update_existing_label = program.allocate_label(); + + let end_update_label = program.allocate_label(); + program.emit_insn(Insn::NotNull { + reg: r_seq_rowid, + target_pc: update_existing_label, + }); + + program.emit_insn(Insn::NewRowid { + cursor: seq_cursor_id, + rowid_reg: r_seq_rowid, + prev_largest_reg: 0, + }); + program.emit_insn(Insn::Insert { + cursor: seq_cursor_id, + key_reg: r_seq_rowid, + record_reg, + flag: InsertFlags::new(), + table_name: "sqlite_sequence".to_string(), + }); + program.emit_insn(Insn::Goto { + target_pc: end_update_label, + }); + + program.preassign_label_to_next_insn(update_existing_label); + program.emit_insn(Insn::Insert { + cursor: seq_cursor_id, + key_reg: r_seq_rowid, // Use the rowid we found + record_reg, + flag: InsertFlags(turso_parser::ast::ResolveType::Replace.bit_value() as u8), + table_name: "sqlite_sequence".to_string(), + }); + + // program.emit_insn(Insn::Goto { target_pc: end_update_label }); + + // program.preassign_label_to_next_insn(insert_new_label); + // program.emit_insn(Insn::NewRowid { cursor: seq_cursor_id, rowid_reg: r_seq_rowid, prev_largest_reg: 0 }); + // program.emit_insn(Insn::Insert { + // cursor: seq_cursor_id, + // key_reg: r_seq_rowid, + // record_reg, + // flag: InsertFlags::new(), + // table_name: "sqlite_sequence".to_string(), + // }); + + program.preassign_label_to_next_insn(end_update_label); + + program.emit_insn(Insn::Close { + cursor_id: seq_cursor_id, + }); + + Ok(()) +} diff --git a/core/translate/logical.rs b/core/translate/logical.rs index cd4e089e8..c19fa42aa 100644 --- a/core/translate/logical.rs +++ b/core/translate/logical.rs @@ -1903,6 +1903,7 @@ mod tests { ], has_rowid: true, is_strict: false, + has_autoincrement: false, unique_sets: None, }; schema.add_btree_table(Arc::new(users_table)); @@ -1964,6 +1965,7 @@ mod tests { ], has_rowid: true, is_strict: false, + has_autoincrement: false, unique_sets: None, }; schema.add_btree_table(Arc::new(orders_table)); diff --git a/core/translate/mod.rs b/core/translate/mod.rs index 288580494..bc5e3b5ed 100644 --- a/core/translate/mod.rs +++ b/core/translate/mod.rs @@ -185,12 +185,12 @@ pub fn translate_inner( } => translate_create_table( tbl_name, temporary, - body, if_not_exists, + body, schema, syms, - connection, program, + connection, )?, ast::Stmt::CreateTrigger { .. } => bail_parse_error!("CREATE TRIGGER not supported yet"), ast::Stmt::CreateView { diff --git a/core/translate/optimizer/join.rs b/core/translate/optimizer/join.rs index 2a6a12439..98eb2b917 100644 --- a/core/translate/optimizer/join.rs +++ b/core/translate/optimizer/join.rs @@ -1650,6 +1650,7 @@ mod tests { Arc::new(BTreeTable { root_page: 1, // Page number doesn't matter for tests name: name.to_string(), + has_autoincrement: false, primary_key_columns: vec![], columns, has_rowid: true, diff --git a/core/translate/pragma.rs b/core/translate/pragma.rs index e6c731cd5..707f53b89 100644 --- a/core/translate/pragma.rs +++ b/core/translate/pragma.rs @@ -274,25 +274,26 @@ fn update_pragma( // but for now, let's keep it as is... let opts = CaptureDataChangesMode::parse(&value)?; if let Some(table) = &opts.table() { - // make sure that we have table created - program = translate_create_table( - QualifiedName { - db_name: None, - name: ast::Name::new(table), - alias: None, - }, - false, - ast::CreateTableBody::ColumnsAndConstraints { - columns: turso_cdc_table_columns(), - constraints: vec![], - options: ast::TableOptions::NONE, - }, - true, - schema, - syms, - &connection, - program, - )?; + if schema.get_table(table).is_none() { + program = translate_create_table( + QualifiedName { + db_name: None, + name: ast::Name::new(table), + alias: None, + }, + false, + true, // if_not_exists + ast::CreateTableBody::ColumnsAndConstraints { + columns: turso_cdc_table_columns(), + constraints: vec![], + options: ast::TableOptions::NONE, + }, + schema, + syms, + program, + &connection, + )?; + } } connection.set_capture_data_changes(opts); Ok((program, TransactionMode::Write)) diff --git a/core/translate/schema.rs b/core/translate/schema.rs index 03025651c..c886d2dda 100644 --- a/core/translate/schema.rs +++ b/core/translate/schema.rs @@ -23,6 +23,7 @@ use crate::util::PRIMARY_KEY_AUTOMATIC_INDEX_NAME_PREFIX; use crate::vdbe::builder::CursorType; use crate::vdbe::insn::Cookie; use crate::vdbe::insn::{CmpInsFlags, InsertFlags, Insn}; +use crate::Connection; use crate::LimboError; use crate::SymbolTable; use crate::{bail_parse_error, Result}; @@ -33,13 +34,14 @@ use turso_ext::VTabKind; pub fn translate_create_table( tbl_name: ast::QualifiedName, temporary: bool, - body: ast::CreateTableBody, if_not_exists: bool, + body: ast::CreateTableBody, schema: &Schema, syms: &SymbolTable, - connection: &Arc, mut program: ProgramBuilder, + connection: &Connection, ) -> Result { + let normalized_tbl_name = normalize_ident(tbl_name.name.as_str()); if temporary { bail_parse_error!("TEMPORARY table not supported yet"); } @@ -59,7 +61,6 @@ pub fn translate_create_table( approx_num_labels: 1, }; program.extend(&opts); - let normalized_tbl_name = normalize_ident(tbl_name.name.as_str()); if schema.get_table(&normalized_tbl_name).is_some() { if if_not_exists { return Ok(program); @@ -67,6 +68,79 @@ pub fn translate_create_table( bail_parse_error!("Table {} already exists", normalized_tbl_name); } + let mut has_autoincrement = false; + if let ast::CreateTableBody::ColumnsAndConstraints { + columns, + constraints, + .. + } = &body + { + for col in columns { + for constraint in &col.constraints { + if let ast::ColumnConstraint::PrimaryKey { auto_increment, .. } = + constraint.constraint + { + if auto_increment { + has_autoincrement = true; + break; + } + } + } + if has_autoincrement { + break; + } + } + if !has_autoincrement { + for constraint in constraints { + if let ast::TableConstraint::PrimaryKey { auto_increment, .. } = + constraint.constraint + { + if auto_increment { + has_autoincrement = true; + break; + } + } + } + } + } + + let schema_master_table = schema.get_btree_table(SQLITE_TABLEID).unwrap(); + let sqlite_schema_cursor_id = + program.alloc_cursor_id(CursorType::BTreeTable(schema_master_table.clone())); + program.emit_insn(Insn::OpenWrite { + cursor_id: sqlite_schema_cursor_id, + root_page: 1usize.into(), + db: 0, + }); + let resolver = Resolver::new(schema, syms); + let cdc_table = prepare_cdc_if_necessary(&mut program, schema, SQLITE_TABLEID)?; + + let created_sequence_table = + if has_autoincrement && schema.get_table("sqlite_sequence").is_none() { + let seq_table_root_reg = program.alloc_register(); + program.emit_insn(Insn::CreateBtree { + db: 0, + root: seq_table_root_reg, + flags: CreateBTreeFlags::new_table(), + }); + + let seq_sql = "CREATE TABLE sqlite_sequence(name,seq)"; + emit_schema_entry( + &mut program, + &resolver, + sqlite_schema_cursor_id, + cdc_table.as_ref().map(|x| x.0), + SchemaEntryType::Table, + "sqlite_sequence", + "sqlite_sequence", + seq_table_root_reg, + Some(seq_sql.to_string()), + )?; + true + } else { + false + }; + let sql = create_table_body_to_str(&tbl_name, &body); let parse_schema_label = program.allocate_label(); @@ -75,7 +149,6 @@ pub fn translate_create_table( // TODO: SetCookie // TODO: SetCookie - // Create the table B-tree let table_root_reg = program.alloc_register(); program.emit_insn(Insn::CreateBtree { db: 0, @@ -110,7 +183,7 @@ pub fn translate_create_table( let index_regs = check_automatic_pk_index_required(&body, &mut program, tbl_name.name.as_str())?; if let Some(index_regs) = index_regs.as_ref() { - if !schema.indexes_enabled() { + if !schema.indexes_enabled() { bail_parse_error!("Constraints UNIQUE and PRIMARY KEY (unless INTEGER PRIMARY KEY) on table are not supported without indexes"); } for index_reg in index_regs.clone() { @@ -122,22 +195,11 @@ pub fn translate_create_table( } } - let table = schema.get_btree_table(SQLITE_TABLEID).unwrap(); - let sqlite_schema_cursor_id = program.alloc_cursor_id(CursorType::BTreeTable(table.clone())); - program.emit_insn(Insn::OpenWrite { - cursor_id: sqlite_schema_cursor_id, - root_page: 1usize.into(), - db: 0, - }); - - let cdc_table = prepare_cdc_if_necessary(&mut program, schema, SQLITE_TABLEID)?; - let resolver = Resolver::new(schema, syms); - // Add the table entry to sqlite_schema emit_schema_entry( &mut program, &resolver, sqlite_schema_cursor_id, - cdc_table.map(|x| x.0), + cdc_table.as_ref().map(|x| x.0), SchemaEntryType::Table, &normalized_tbl_name, &normalized_tbl_name, @@ -145,15 +207,9 @@ pub fn translate_create_table( Some(sql), )?; - // If we need an automatic index, add its entry to sqlite_schema if let Some(index_regs) = index_regs { - for (idx, index_reg) in index_regs.into_iter().enumerate() { - let index_name = format!( - "{}{}_{}", - PRIMARY_KEY_AUTOMATIC_INDEX_NAME_PREFIX, - tbl_name.name.as_str(), - idx + 1 - ); + for (i, index_reg) in index_regs.enumerate() { + let index_name = format!("sqlite_autoindex_{}_{}", tbl_name.name.as_str(), i + 1); emit_schema_entry( &mut program, &resolver, @@ -176,9 +232,14 @@ pub fn translate_create_table( value: schema.schema_version as i32 + 1, p5: 0, }); + // TODO: remove format, it sucks for performance but is convenient - let parse_schema_where_clause = + let mut parse_schema_where_clause = format!("tbl_name = '{normalized_tbl_name}' AND type != 'trigger'"); + if created_sequence_table { + parse_schema_where_clause.push_str(" OR tbl_name = 'sqlite_sequence'"); + } + program.emit_insn(Insn::ParseSchema { db: sqlite_schema_cursor_id, where_clause: Some(parse_schema_where_clause), @@ -230,16 +291,16 @@ pub fn emit_schema_entry( program.emit_string8_new_reg(name.to_string()); program.emit_string8_new_reg(tbl_name.to_string()); - let rootpage_reg = program.alloc_register(); + let table_root_reg = program.alloc_register(); if root_page_reg == 0 { program.emit_insn(Insn::Integer { - dest: rootpage_reg, + dest: table_root_reg, value: 0, // virtual tables in sqlite always have rootpage=0 }); } else { program.emit_insn(Insn::Copy { src_reg: root_page_reg, - dst_reg: rootpage_reg, + dst_reg: table_root_reg, extra_amount: 0, }); } @@ -300,7 +361,7 @@ struct PrimaryKeyColumnInfo<'a> { /// An automatic PRIMARY KEY index is not required if: /// - The table has no PRIMARY KEY /// - The table has a single-column PRIMARY KEY whose typename is _exactly_ "INTEGER" e.g. not "INT". -/// In this case, the PRIMARY KEY column becomes an alias for the rowid. +/// In this case, the PRIMARY KEY column becomes an alias for the rowid. /// /// Otherwise, an automatic PRIMARY KEY index is required. fn check_automatic_pk_index_required( @@ -675,8 +736,9 @@ pub fn translate_drop_table( "DROP TABLE with indexes on the table is disabled by default. Omit the `--experimental-indexes=false` flag to enable this feature." ); } + let opts = ProgramBuilderOpts { - num_cursors: 3, + num_cursors: 4, approx_num_insns: 40, approx_num_labels: 4, }; @@ -722,7 +784,7 @@ pub fn translate_drop_table( }); program.preassign_label_to_next_insn(metadata_loop); - // start loop on schema table + // start loop on schema table program.emit_column_or_rowid( sqlite_schema_cursor_id_0, 2, @@ -753,7 +815,7 @@ pub fn translate_drop_table( dest: row_id_reg, }); if let Some((cdc_cursor_id, _)) = cdc_table { - let table_type = program.emit_string8_new_reg("table".to_string()); // r4 + let table_type = program.emit_string8_new_reg("table".to_string()); // r4 program.mark_last_insn_constant(); let skip_cdc_label = program.allocate_label(); @@ -802,7 +864,7 @@ pub fn translate_drop_table( pc_if_next: metadata_loop, }); program.preassign_label_to_next_insn(end_metadata_label); - // end of loop on schema table + // end of loop on schema table // 2. Destroy the indices within a loop let indices = schema.get_indices(tbl_name.name.as_str()); @@ -851,17 +913,18 @@ pub fn translate_drop_table( let schema_row_id_register = program.alloc_register(); program.emit_null(schema_data_register, Some(schema_row_id_register)); - // All of the following processing needs to be done only if the table is not a virtual table + // All of the following processing needs to be done only if the table is not a virtual table if table.btree().is_some() { - // 4. Open an ephemeral table, and read over the entry from the schema table whose root page was moved in the destroy operation + // 4. Open an ephemeral table, and read over the entry from the schema table whose root page was moved in the destroy operation - // cursor id 1 + // cursor id 1 let sqlite_schema_cursor_id_1 = program.alloc_cursor_id(CursorType::BTreeTable(schema_table.clone())); let simple_table_rc = Arc::new(BTreeTable { root_page: 0, // Not relevant for ephemeral table definition name: "ephemeral_scratch".to_string(), has_rowid: true, + has_autoincrement: false, primary_key_columns: vec![], columns: vec![Column { name: Some("rowid".to_string()), @@ -878,7 +941,7 @@ pub fn translate_drop_table( is_strict: false, unique_sets: None, }); - // cursor id 2 + // cursor id 2 let ephemeral_cursor_id = program.alloc_cursor_id(CursorType::BTreeTable(simple_table_rc)); program.emit_insn(Insn::OpenEphemeral { cursor_id: ephemeral_cursor_id, @@ -913,9 +976,9 @@ pub fn translate_drop_table( pc_if_empty: copy_schema_to_temp_table_loop_end_label, }); program.preassign_label_to_next_insn(copy_schema_to_temp_table_loop); - // start loop on schema table + // start loop on schema table program.emit_column_or_rowid(sqlite_schema_cursor_id_1, 3, prev_root_page_register); - // The label and Insn::Ne are used to skip over any rows in the schema table that don't have the root page that was moved + // The label and Insn::Ne are used to skip over any rows in the schema table that don't have the root page that was moved let next_label = program.allocate_label(); program.emit_insn(Insn::Ne { lhs: prev_root_page_register, @@ -942,18 +1005,18 @@ pub fn translate_drop_table( pc_if_next: copy_schema_to_temp_table_loop, }); program.preassign_label_to_next_insn(copy_schema_to_temp_table_loop_end_label); - // End loop to copy over row id's from the schema table for rows that have the same root page as the one that was moved + // End loop to copy over row id's from the schema table for rows that have the same root page as the one that was moved program.resolve_label(if_not_label, program.offset()); - // 5. Open a write cursor to the schema table and re-insert the records placed in the ephemeral table but insert the correct root page now + // 5. Open a write cursor to the schema table and re-insert the records placed in the ephemeral table but insert the correct root page now program.emit_insn(Insn::OpenWrite { cursor_id: sqlite_schema_cursor_id_1, root_page: 1usize.into(), db: 0, }); - // Loop to copy over row id's from the ephemeral table and then re-insert into the schema table with the correct root page + // Loop to copy over row id's from the ephemeral table and then re-insert into the schema table with the correct root page let copy_temp_table_to_schema_loop_end_label = program.allocate_label(); let copy_temp_table_to_schema_loop = program.allocate_label(); program.emit_insn(Insn::Rewind { @@ -1009,10 +1072,59 @@ pub fn translate_drop_table( pc_if_next: copy_temp_table_to_schema_loop, }); program.preassign_label_to_next_insn(copy_temp_table_to_schema_loop_end_label); - // End loop to copy over row id's from the ephemeral table and then re-insert into the schema table with the correct root page + // End loop to copy over row id's from the ephemeral table and then re-insert into the schema table with the correct root page } - // Drop the in-memory structures for the table + // if drops table, sequence table should reset. + if let Some(seq_table) = schema.get_table("sqlite_sequence").and_then(|t| t.btree()) { + let seq_cursor_id = program.alloc_cursor_id(CursorType::BTreeTable(seq_table.clone())); + let seq_table_name_reg = program.alloc_register(); + let dropped_table_name_reg = + program.emit_string8_new_reg(tbl_name.name.as_str().to_string()); + program.mark_last_insn_constant(); + + program.emit_insn(Insn::OpenWrite { + cursor_id: seq_cursor_id, + root_page: seq_table.root_page.into(), + db: 0, + }); + + let end_loop_label = program.allocate_label(); + let loop_start_label = program.allocate_label(); + + program.emit_insn(Insn::Rewind { + cursor_id: seq_cursor_id, + pc_if_empty: end_loop_label, + }); + + program.preassign_label_to_next_insn(loop_start_label); + + program.emit_column_or_rowid(seq_cursor_id, 0, seq_table_name_reg); + + let continue_loop_label = program.allocate_label(); + program.emit_insn(Insn::Ne { + lhs: seq_table_name_reg, + rhs: dropped_table_name_reg, + target_pc: continue_loop_label, + flags: CmpInsFlags::default(), + collation: None, + }); + + program.emit_insn(Insn::Delete { + cursor_id: seq_cursor_id, + table_name: "sqlite_sequence".to_string(), + }); + + program.resolve_label(continue_loop_label, program.offset()); + program.emit_insn(Insn::Next { + cursor_id: seq_cursor_id, + pc_if_next: loop_start_label, + }); + + program.preassign_label_to_next_insn(end_loop_label); + } + + // Drop the in-memory structures for the table program.emit_insn(Insn::DropTable { db: 0, _p2: 0, diff --git a/core/translate/update.rs b/core/translate/update.rs index f94ebe118..1294160da 100644 --- a/core/translate/update.rs +++ b/core/translate/update.rs @@ -258,6 +258,7 @@ pub fn prepare_update_plan( root_page: 0, // Not relevant for ephemeral table definition name: "ephemeral_scratch".to_string(), has_rowid: true, + has_autoincrement: false, primary_key_columns: vec![], columns: vec![Column { name: Some("rowid".to_string()), diff --git a/core/vdbe/execute.rs b/core/vdbe/execute.rs index ec7ab3408..ffa2a447c 100644 --- a/core/vdbe/execute.rs +++ b/core/vdbe/execute.rs @@ -6493,6 +6493,10 @@ pub fn op_parse_schema( conn.is_nested_stmt.set(false); conn.auto_commit.set(previous_auto_commit); maybe_nested_stmt_err?; + + // let updated_local_schema = conn.schema.borrow().clone(); + // conn._db.update_schema_if_newer(updated_local_schema)?; + state.pc += 1; Ok(InsnFunctionStepResult::Step) }