diff --git a/core/incremental/operator.rs b/core/incremental/operator.rs index 5d577a5523..d22c20e1b0 100644 --- a/core/incremental/operator.rs +++ b/core/incremental/operator.rs @@ -71,6 +71,8 @@ pub fn create_dbsp_state_index(root_page: i64) -> Index { has_rowid: true, where_clause: None, index_method: None, + is_primary_key: false, + n_key_col: 3, } } diff --git a/core/schema.rs b/core/schema.rs index 4990b4b3c8..38cb63a3c9 100644 --- a/core/schema.rs +++ b/core/schema.rs @@ -751,7 +751,28 @@ impl Schema { } } + let has_rowid = table.has_rowid; + let table_name_for_index = table.name.clone(); + let root_page_for_index = table.root_page; + self.add_btree_table(Arc::new(table))?; + + if !has_rowid { + tracing::debug!( + "WITHOUT ROWID table '{}' found. Synthesizing primary key index.", + &table_name_for_index + ); + let table_ref = self.get_btree_table(&table_name_for_index).unwrap(); + let pk_index = Index::automatic_from_primary_key( + &table_ref, + ( + format!("sqlite_autoindex_{}_1", &table_name_for_index), + root_page_for_index, + ), + table_ref.primary_key_columns.len(), + )?; + self.add_index(Arc::new(pk_index))?; + } } } "index" => { @@ -2393,6 +2414,8 @@ pub struct Index { pub has_rowid: bool, pub where_clause: Option>, pub index_method: Option>, + pub is_primary_key: bool, + pub n_key_col: usize, } #[allow(dead_code)] @@ -2431,7 +2454,33 @@ impl Index { .. })) => { let index_name = normalize_ident(idx_name.name.as_str()); - let index_columns = resolve_sorted_columns(table, &columns)?; + let mut index_columns = resolve_sorted_columns(table, &columns)?; + + if !table.has_rowid { + let existing_index_cols: HashSet = + index_columns.iter().map(|c| c.name.clone()).collect(); + + for (pk_col_name, _order) in &table.primary_key_columns { + if !existing_index_cols.contains(pk_col_name.as_str()) { + let (pos_in_table, column) = + table.get_column(pk_col_name).ok_or_else(|| { + LimboError::InternalError(format!( + "Could not find PK column '{}' in table '{}'", + pk_col_name, table.name + )) + })?; + + index_columns.push(IndexColumn { + name: pk_col_name.clone(), + order: SortOrder::Asc, + pos_in_table, + collation: column.collation_opt(), + default: column.default.clone(), + }); + } + } + } + if let Some(using) = using { if where_clause.is_some() { bail_parse_error!("custom index module do not support partial indices"); @@ -2459,7 +2508,9 @@ impl Index { ephemeral: false, has_rowid: table.has_rowid, where_clause: None, + is_primary_key: false, index_method: Some(descriptor), + n_key_col: columns.len(), }) } else { Ok(Index { @@ -2472,6 +2523,8 @@ impl Index { has_rowid: table.has_rowid, where_clause, index_method: None, + is_primary_key: false, + n_key_col: columns.len(), }) } } @@ -2496,36 +2549,59 @@ impl Index { assert!(has_primary_key_index); let (index_name, root_page) = auto_index; - let mut primary_keys = Vec::with_capacity(column_count); + let mut index_columns = Vec::new(); + let mut key_column_positions = std::collections::HashSet::new(); + for (col_name, order) in table.primary_key_columns.iter() { - let Some((pos_in_table, _)) = table.get_column(col_name) else { + let Some((pos_in_table, column)) = table.get_column(col_name) else { return Err(crate::LimboError::ParseError(format!( "Column {} not found in table {}", col_name, table.name ))); }; - let (_, column) = table.get_column(col_name).unwrap(); - primary_keys.push(IndexColumn { + index_columns.push(IndexColumn { name: normalize_ident(col_name), order: *order, pos_in_table, collation: column.collation_opt(), default: column.default.clone(), }); + key_column_positions.insert(pos_in_table); } - assert!(primary_keys.len() == column_count); + assert!( + index_columns.len() == column_count, + "Mismatch in primary key column count" + ); + + if !table.has_rowid { + for (pos_in_table, column) in table.columns.iter().enumerate() { + // TODO: when we support generated cols look at this + + if !key_column_positions.contains(&pos_in_table) { + index_columns.push(IndexColumn { + name: normalize_ident(column.name.as_ref().unwrap()), + order: SortOrder::Asc, + pos_in_table, + collation: None, + default: column.default.clone(), + }); + } + } + } Ok(Index { name: normalize_ident(index_name.as_str()), table_name: table.name.clone(), root_page, - columns: primary_keys, + columns: index_columns, unique: true, ephemeral: false, has_rowid: table.has_rowid, where_clause: None, index_method: None, + is_primary_key: true, + n_key_col: column_count, }) } @@ -2554,6 +2630,7 @@ impl Index { }) .collect::>(); + let n_key_col = unique_cols.len(); Ok(Index { name: normalize_ident(index_name.as_str()), table_name: table.name.clone(), @@ -2564,6 +2641,8 @@ impl Index { has_rowid: table.has_rowid, where_clause: None, index_method: None, + is_primary_key: false, + n_key_col, }) } diff --git a/core/storage/btree.rs b/core/storage/btree.rs index 84bfc4992b..74e861877f 100644 --- a/core/storage/btree.rs +++ b/core/storage/btree.rs @@ -5044,6 +5044,26 @@ impl CursorTrait for BTreeCursor { #[instrument(skip_all, level = Level::DEBUG)] fn insert(&mut self, key: &BTreeKey) -> Result> { + // remove this after getting sanity, before merging + + match key { + BTreeKey::TableRowId((rowid, record)) => { + tracing::debug!( + "BTreeCursor::insert: root_page={}, key_type=TableRowId, rowid={}, record_size={}", + self.root_page, + rowid, + record.map_or(0, |r| r.get_payload().len()) + ); + } + BTreeKey::IndexKey(record) => { + tracing::debug!( + "BTreeCursor::insert: root_page={}, key_type=IndexKey, record_size={}", + self.root_page, + record.get_payload().len() + ); + } + } + tracing::debug!(valid_state = ?self.valid_state, cursor_state = ?self.state, is_write_in_progress = self.is_write_in_progress()); return_if_io!(self.insert_into_page(key)); if key.maybe_rowid().is_some() { @@ -8558,6 +8578,7 @@ mod tests { ephemeral: false, has_rowid: false, index_method: None, + is_primary_key: false, }; let num_columns = index_def.columns.len(); let mut cursor = @@ -8718,6 +8739,7 @@ mod tests { ephemeral: false, has_rowid: false, index_method: None, + is_primary_key: false, }; let mut cursor = BTreeCursor::new_index(pager.clone(), index_root_page, &index_def, 1); diff --git a/core/translate/compound_select.rs b/core/translate/compound_select.rs index 96f1afda2f..ee5cb248bf 100644 --- a/core/translate/compound_select.rs +++ b/core/translate/compound_select.rs @@ -437,7 +437,7 @@ fn create_dedupe_index( }; column.collation = collation; } - + let n_key_col = dedupe_columns.len(); let dedupe_index = Arc::new(Index { columns: dedupe_columns, name: "compound_dedupe".to_string(), @@ -448,6 +448,8 @@ fn create_dedupe_index( has_rowid: false, where_clause: None, index_method: None, + is_primary_key: false, + n_key_col, }); let cursor_id = program.alloc_cursor_id(CursorType::BTreeIndex(dedupe_index.clone())); program.emit_insn(Insn::OpenEphemeral { diff --git a/core/translate/index.rs b/core/translate/index.rs index 44e8efbb84..f19373dc8e 100644 --- a/core/translate/index.rs +++ b/core/translate/index.rs @@ -149,6 +149,8 @@ pub fn translate_create_index( // before translating, and it cannot reference a table alias where_clause: where_clause.clone(), index_method: index_method.clone(), + is_primary_key: false, + n_key_col: columns.len(), }); if !idx.validate_where_expr(table) { diff --git a/core/translate/insert.rs b/core/translate/insert.rs index 880e47399e..34cff50429 100644 --- a/core/translate/insert.rs +++ b/core/translate/insert.rs @@ -44,7 +44,7 @@ use super::plan::QueryDestination; use super::select::translate_select; /// Validate anything with this insert statement that should throw an early parse error -fn validate(table_name: &str, resolver: &Resolver, table: &Table) -> Result<()> { +fn validate(table_name: &str, resolver: &Resolver, _table: &Table) -> Result<()> { // Check if this is a system table that should be protected from direct writes if crate::schema::is_system_table(table_name) { crate::bail_parse_error!("table {} may not be modified", table_name); @@ -74,9 +74,9 @@ fn validate(table_name: &str, resolver: &Resolver, table: &Table) -> Result<()> "INSERT to table with indexes is disabled. Omit the `--experimental-indexes=false` flag to enable this feature." ); } - if table.btree().is_some_and(|t| !t.has_rowid) { - crate::bail_parse_error!("INSERT into WITHOUT ROWID table is not supported"); - } + // if table.btree().is_some_and(|t| !t.has_rowid) { + // crate::bail_parse_error!("INSERT into WITHOUT ROWID table is not supported"); + // } Ok(()) } @@ -221,6 +221,18 @@ pub fn translate_insert( crate::bail_parse_error!("no such table: {}", table_name); }; + if !btree_table.has_rowid { + return translate_without_rowid_insert( + resolver, + on_conflict, + &btree_table, + columns, + body, + returning, + program, + connection, + ); + } let BoundInsertResult { mut values, mut upsert_actions, @@ -2616,3 +2628,370 @@ pub fn emit_parent_side_fk_decrement_on_insert( } Ok(()) } + +fn build_without_rowid_insertion<'a>( + program: &mut ProgramBuilder, + table: &'a BTreeTable, + columns: &'a [ast::Name], + num_values: usize, +) -> Result> { + let table_columns = &table.columns; + // for without rowid, there is no separate rowid, so we allocate a dummy register. + // the real key is the composite primary key. + let dummy_rowid_register = program.alloc_register(); + + let mut column_mappings = table_columns + .iter() + .map(|c| ColMapping { + column: c, + value_index: None, + register: program.alloc_register(), + }) + .collect::>(); + + if columns.is_empty() { + if num_values != table_columns.len() { + crate::bail_parse_error!( + "table {} has {} columns but {} values were supplied", + &table.name, + table_columns.len(), + num_values + ); + } + for (i, _col) in table_columns.iter().enumerate() { + column_mappings[i].value_index = Some(i); + } + } else { + if num_values != columns.len() { + crate::bail_parse_error!( + "table {} has {} columns but {} values were supplied", + &table.name, + columns.len(), + num_values + ); + } + for (value_index, column_name) in columns.iter().enumerate() { + let column_name_str = normalize_ident(column_name.as_str()); + if let Some((idx_in_table, _)) = table.get_column(&column_name_str) { + column_mappings[idx_in_table].value_index = Some(value_index); + } else { + crate::bail_parse_error!( + "table {} has no column named {}", + &table.name, + column_name_str + ); + } + } + } + + Ok(Insertion { + key: InsertionKey::Autogenerated { + register: dummy_rowid_register, + }, + col_mappings: column_mappings, + record_reg: program.alloc_register(), + }) +} + +#[allow(clippy::too_many_arguments)] +fn translate_without_rowid_insert( + resolver: &Resolver, + on_conflict: Option, + table: &Arc, + columns: Vec, + mut body: InsertBody, + mut returning: Vec, + mut program: ProgramBuilder, + connection: &Arc, +) -> Result { + tracing::debug!( + "Beginning translation for WITHOUT ROWID INSERT into table '{}'", + table.name + ); + + let BoundInsertResult { + values, + upsert_actions, + inserting_multiple_rows, + } = bind_insert( + &mut program, + resolver, + &Table::BTree(table.clone()), + &mut body, + connection, + on_conflict.unwrap_or(ResolveType::Abort), + )?; + + if inserting_multiple_rows { + crate::bail_parse_error!( + "Multi-row INSERT into WITHOUT ROWID tables is not yet implemented." + ); + } + // TODO: Add support for UPSERT clauses with WITHOUT ROWID tables. + if !upsert_actions.is_empty() { + crate::bail_parse_error!( + "ON CONFLICT clause is not yet supported for WITHOUT ROWID tables." + ); + } + + let cdc_table = prepare_cdc_if_necessary(&mut program, resolver.schema, table.name.as_str())?; + let (result_columns, _) = process_returning_clause( + &mut returning, + &Table::BTree(table.clone()), + table.name.as_str(), + &mut program, + connection, + )?; + + let mut ctx = InsertEmitCtx::new( + &mut program, + resolver, + table, + on_conflict, + cdc_table, + values.len(), + None, + )?; + + let pk_index = resolver + .schema + .get_indices(&table.name) + .find(|idx| idx.is_primary_key) + .ok_or_else(|| { + crate::error::LimboError::InternalError(format!( + "WITHOUT ROWID table {} has no primary key index", + table.name + )) + })?; + + tracing::debug!( + "Found WITHOUT ROWID primary key index '{}' with root page {} and {} key columns.", + pk_index.name, + pk_index.root_page, + pk_index.n_key_col + ); + + let pk_cursor_id = program.alloc_cursor_index(None, pk_index)?; + ctx.cursor_id = pk_cursor_id; + + program.emit_insn(Insn::OpenWrite { + cursor_id: pk_cursor_id, + root_page: RegisterOrLiteral::Literal(pk_index.root_page), + db: 0, + }); + tracing::debug!( + "Emitted OP_OpenWrite for PK index on cursor {}", + pk_cursor_id + ); + + for (name, root_page, cursor_id) in &ctx.idx_cursors { + if *root_page != pk_index.root_page { + program.emit_insn(Insn::OpenWrite { + cursor_id: *cursor_id, + root_page: RegisterOrLiteral::Literal(*root_page), + db: 0, + }); + tracing::debug!( + "Emitted OP_OpenWrite for secondary index '{}' on cursor {}", + name, + cursor_id + ); + } + } + + let insertion = build_without_rowid_insertion(&mut program, table, &columns, values.len())?; + translate_rows_single(&mut program, &values, &insertion, resolver)?; + tracing::debug!( + "Translated values into registers starting at {}", + insertion.first_col_register() + ); + + emit_notnulls(&mut program, &ctx, &insertion); + + let num_pk_cols = pk_index.n_key_col; + let pk_registers_start = program.alloc_registers(num_pk_cols); + tracing::debug!( + "Allocated registers {}-{} for PK uniqueness check", + pk_registers_start, + pk_registers_start + num_pk_cols - 1 + ); + + for (i, pk_col) in pk_index.columns.iter().take(num_pk_cols).enumerate() { + let mapping = insertion.get_col_mapping_by_name(&pk_col.name).unwrap(); + program.emit_insn(Insn::Copy { + src_reg: mapping.register, + dst_reg: pk_registers_start + i, + extra_amount: 0, + }); + } + + let ok_to_insert_label = program.allocate_label(); + program.emit_insn(Insn::NoConflict { + cursor_id: pk_cursor_id, + target_pc: ok_to_insert_label, + record_reg: pk_registers_start, + num_regs: num_pk_cols, + }); + tracing::debug!( + "Emitted OP_NoConflict to check for key uniqueness on cursor {}", + pk_cursor_id + ); + + program.emit_insn(Insn::Halt { + err_code: SQLITE_CONSTRAINT_UNIQUE, + description: format_unique_violation_desc(&table.name, pk_index), + }); + + program.preassign_label_to_next_insn(ok_to_insert_label); + tracing::debug!("Uniqueness check passed. Continuing with insertion."); + + let physically_ordered_cols: Vec<&Column> = { + let mut ordered = Vec::with_capacity(table.columns.len()); + let mut pk_cols_added = std::collections::HashSet::new(); + + for pk_col_def in pk_index.columns.iter().take(pk_index.n_key_col) { + let (_, col) = table.get_column(&pk_col_def.name).unwrap(); + ordered.push(col); + pk_cols_added.insert(col.name.as_ref().unwrap().as_str()); + tracing::trace!( + " Physical col {}: {}", + ordered.len() - 1, + col.name.as_ref().unwrap() + ); + } + + for col in table.columns.iter() { + if !pk_cols_added.contains(col.name.as_ref().unwrap().as_str()) { + ordered.push(col); + tracing::trace!( + " Physical col {}: {}", + ordered.len() - 1, + col.name.as_ref().unwrap() + ); + } + } + assert_eq!( + ordered.len(), + table.columns.len(), + "Physical column ordering failed: count mismatch." + ); + ordered + }; + + let reordered_start_reg = program.alloc_registers(insertion.col_mappings.len()); + tracing::debug!( + "Reordering columns into physical order. New registers start at {}", + reordered_start_reg + ); + + for (i, physical_col) in physically_ordered_cols.iter().enumerate() { + let logical_mapping = insertion + .get_col_mapping_by_name(physical_col.name.as_ref().unwrap()) + .unwrap(); + program.emit_insn(Insn::Copy { + src_reg: logical_mapping.register, + dst_reg: reordered_start_reg + i, + extra_amount: 0, + }); + } + + let full_record_reg = program.alloc_register(); + let affinity_str = physically_ordered_cols + .iter() + .map(|col| col.affinity().aff_mask()) + .collect::(); + + program.emit_insn(Insn::MakeRecord { + start_reg: reordered_start_reg, + count: insertion.col_mappings.len(), + dest_reg: full_record_reg, + index_name: Some(pk_index.name.clone()), + affinity_str: Some(affinity_str), + }); + + program.emit_insn(Insn::IdxInsert { + cursor_id: pk_cursor_id, + record_reg: full_record_reg, + unpacked_start: Some(reordered_start_reg), + unpacked_count: Some(insertion.col_mappings.len() as u16), + flags: IdxInsertFlags::new().nchange(true), + }); + tracing::debug!( + "Emitted OP_IdxInsert to write record from reg {} into cursor {}", + full_record_reg, + pk_cursor_id + ); + + tracing::debug!("Beginning update of secondary indices."); + for index in resolver.schema.get_indices(table.name.as_str()) { + if index.is_primary_key { + continue; + } + + let idx_cursor_id = ctx + .idx_cursors + .iter() + .find(|(name, _, _)| *name == &index.name) + .map(|(_, _, c_id)| *c_id) + .expect("no cursor found for secondary index"); + + tracing::debug!("Processing secondary index '{}'", index.name); + + let num_total_cols = index.columns.len(); + let key_start_reg = program.alloc_registers(num_total_cols); + + for (i, idx_col) in index.columns.iter().enumerate() { + let mapping = insertion + .get_col_mapping_by_name(&idx_col.name) + .unwrap_or_else(|| { + panic!( + "Could not find column mapping for '{}' in index '{}'", + idx_col.name, index.name + ) + }); + program.emit_insn(Insn::Copy { + src_reg: mapping.register, + dst_reg: key_start_reg + i, + extra_amount: 0, + }); + } + + let record_reg = program.alloc_register(); + program.emit_insn(Insn::MakeRecord { + start_reg: key_start_reg, + count: num_total_cols, + dest_reg: record_reg, + index_name: Some(index.name.clone()), + affinity_str: None, + }); + tracing::debug!( + "Emitted OP_MakeRecord for secondary index '{}' key into register {}", + index.name, + record_reg + ); + + program.emit_insn(Insn::IdxInsert { + cursor_id: idx_cursor_id, + record_reg, + unpacked_start: Some(key_start_reg), + unpacked_count: Some(num_total_cols as u16), + flags: IdxInsertFlags::new().nchange(true), + }); + tracing::debug!("Emitted OP_IdxInsert for secondary index '{}'", index.name); + } + + // TODO? + if !result_columns.is_empty() { + crate::bail_parse_error!("RETURNING clause is not yet supported for WITHOUT ROWID tables."); + } + + program.emit_insn(Insn::Goto { + target_pc: ctx.row_done_label, + }); + + emit_epilogue(&mut program, &ctx, inserting_multiple_rows); + + program.set_needs_stmt_subtransactions(true); + tracing::debug!("Finished translation for WITHOUT ROWID INSERT."); + Ok(program) +} diff --git a/core/translate/integrity_check.rs b/core/translate/integrity_check.rs index c4ee30289f..f39656e33d 100644 --- a/core/translate/integrity_check.rs +++ b/core/translate/integrity_check.rs @@ -12,10 +12,14 @@ pub fn translate_integrity_check( program: &mut ProgramBuilder, ) -> crate::Result<()> { let mut root_pages = Vec::with_capacity(schema.tables.len() + schema.indexes.len()); + // Collect root pages to run integrity check on for table in schema.tables.values() { if let crate::schema::Table::BTree(table) = table.as_ref() { - root_pages.push(table.root_page); + if table.has_rowid { + root_pages.push(table.root_page); + } + if let Some(indexes) = schema.indexes.get(table.name.as_str()) { for index in indexes.iter() { if index.root_page > 0 { diff --git a/core/translate/main_loop.rs b/core/translate/main_loop.rs index 14424ea7fb..d69d77dab1 100644 --- a/core/translate/main_loop.rs +++ b/core/translate/main_loop.rs @@ -108,6 +108,8 @@ pub fn init_distinct(program: &mut ProgramBuilder, plan: &SelectPlan) -> Result< has_rowid: false, where_clause: None, index_method: None, + is_primary_key: false, + n_key_col: plan.result_columns.len(), }); let cursor_id = program.alloc_cursor_id(CursorType::BTreeIndex(index.clone())); let ctx = DistinctCtx { @@ -186,6 +188,12 @@ pub fn init_loop( unique: false, where_clause: None, index_method: None, + is_primary_key: false, + n_key_col: if let Some(gb) = group_by.as_ref() { + gb.exprs.len() + } else { + 0 + }, }); let cursor_id = program.alloc_cursor_id(CursorType::BTreeIndex(index.clone())); if group_by.is_none() { diff --git a/core/translate/optimizer/join.rs b/core/translate/optimizer/join.rs index 70a7b8ac32..72eb84309e 100644 --- a/core/translate/optimizer/join.rs +++ b/core/translate/optimizer/join.rs @@ -688,6 +688,7 @@ mod tests { root_page: 1, has_rowid: true, index_method: None, + is_primary_key: false, }); available_indexes.insert("test_table".to_string(), VecDeque::from([index])); @@ -762,6 +763,7 @@ mod tests { root_page: 1, has_rowid: true, index_method: None, + is_primary_key: false, }); available_indexes.insert("table1".to_string(), VecDeque::from([index1])); @@ -884,6 +886,7 @@ mod tests { root_page: 1, has_rowid: true, index_method: None, + is_primary_key: false, }); available_indexes.insert(table_name.to_string(), VecDeque::from([index])); }); @@ -903,6 +906,7 @@ mod tests { root_page: 1, has_rowid: true, index_method: None, + is_primary_key: false, }); let order_id_idx = Arc::new(Index { name: "order_items_order_id_idx".to_string(), @@ -920,6 +924,7 @@ mod tests { root_page: 1, has_rowid: true, index_method: None, + is_primary_key: false, }); available_indexes @@ -1360,6 +1365,7 @@ mod tests { ephemeral: false, has_rowid: true, index_method: None, + is_primary_key: false, }); let mut available_indexes = HashMap::new(); @@ -1459,6 +1465,7 @@ mod tests { ephemeral: false, has_rowid: true, index_method: None, + is_primary_key: false, }); available_indexes.insert("t1".to_string(), VecDeque::from([index])); @@ -1576,6 +1583,7 @@ mod tests { has_rowid: true, unique: false, index_method: None, + is_primary_key: false, }); available_indexes.insert("t1".to_string(), VecDeque::from([index])); diff --git a/core/translate/optimizer/mod.rs b/core/translate/optimizer/mod.rs index cc706b898d..ad6315668d 100644 --- a/core/translate/optimizer/mod.rs +++ b/core/translate/optimizer/mod.rs @@ -1292,6 +1292,7 @@ fn ephemeral_index_build( (None, None) => Ordering::Equal, } }); + let n_key_col = ephemeral_columns.len(); let ephemeral_index = Index { name: format!( "ephemeral_{}_{}", @@ -1309,6 +1310,8 @@ fn ephemeral_index_build( .btree() .is_some_and(|btree| btree.has_rowid), index_method: None, + is_primary_key: false, + n_key_col, }; ephemeral_index diff --git a/core/translate/order_by.rs b/core/translate/order_by.rs index c450c80df4..35e94ba33a 100644 --- a/core/translate/order_by.rs +++ b/core/translate/order_by.rs @@ -100,6 +100,7 @@ pub fn init_order_by( default: None, }) } + let n_key_col = index_columns.len(); let index = Arc::new(Index { name: index_name.clone(), table_name: String::new(), @@ -110,6 +111,8 @@ pub fn init_order_by( has_rowid: false, where_clause: None, index_method: None, + is_primary_key: false, + n_key_col, }); program.alloc_cursor_id(CursorType::BTreeIndex(index)) } else { diff --git a/core/translate/schema.rs b/core/translate/schema.rs index 6356f049ac..575de0c65e 100644 --- a/core/translate/schema.rs +++ b/core/translate/schema.rs @@ -187,10 +187,25 @@ pub fn translate_create_table( // TODO: SetCookie let table_root_reg = program.alloc_register(); + + let btree_flags = if let ast::CreateTableBody::ColumnsAndConstraints { options, .. } = &body { + if options.contains(ast::TableOptions::WITHOUT_ROWID) { + tracing::debug!( + "CREATE TABLE: `{}` is a WITHOUT ROWID table, using index b-tree flags.", + normalized_tbl_name + ); + CreateBTreeFlags::new_index() + } else { + CreateBTreeFlags::new_table() + } + } else { + CreateBTreeFlags::new_table() + }; + program.emit_insn(Insn::CreateBtree { db: 0, root: table_root_reg, - flags: CreateBTreeFlags::new_table(), + flags: btree_flags, }); // Create an automatic index B-tree if needed @@ -414,6 +429,17 @@ fn collect_autoindexes( program: &mut ProgramBuilder, tbl_name: &str, ) -> Result>> { + let is_without_rowid = if let ast::CreateTableBody::ColumnsAndConstraints { options, .. } = body + { + options.contains(ast::TableOptions::WITHOUT_ROWID) + } else { + false + }; + + if is_without_rowid { + tracing::debug!("Without rowid table, no need for autoindex'{}'", tbl_name); + } + let table = create_table(tbl_name, body, 0)?; let mut regs: Vec = Vec::new(); @@ -426,7 +452,7 @@ fn collect_autoindexes( }; let needs_index = if us.is_primary_key { - !(col.primary_key() && col.is_rowid_alias()) + !(is_without_rowid || (col.primary_key() && col.is_rowid_alias())) } else { // UNIQUE single needs an index true @@ -437,7 +463,11 @@ fn collect_autoindexes( } } - for _us in table.unique_sets.iter().filter(|us| us.columns.len() > 1) { + for _us in table + .unique_sets + .iter() + .filter(|us| us.columns.len() > 1 && (!us.is_primary_key || !is_without_rowid)) + { regs.push(program.alloc_register()); } if regs.is_empty() { diff --git a/core/translate/subquery.rs b/core/translate/subquery.rs index 51999b9c4e..99b97fa841 100644 --- a/core/translate/subquery.rs +++ b/core/translate/subquery.rs @@ -382,6 +382,7 @@ fn get_subquery_parser<'a>( )?; } + let n_key_col = columns.len(); let ephemeral_index = Arc::new(Index { columns, name: format!("ephemeral_index_where_sub_{subquery_id}"), @@ -392,6 +393,8 @@ fn get_subquery_parser<'a>( unique: false, where_clause: None, index_method: None, + is_primary_key: false, + n_key_col, }); let cursor_id = diff --git a/core/vdbe/execute.rs b/core/vdbe/execute.rs index bba8b9cc98..c09e919f86 100644 --- a/core/vdbe/execute.rs +++ b/core/vdbe/execute.rs @@ -6690,6 +6690,17 @@ pub fn op_idx_insert( *insn ); + // remove this after getting sanity, before merging + let (_, cursor_type) = program.cursor_ref.get(cursor_id).unwrap(); + if let CursorType::BTreeIndex(index_meta) = cursor_type { + tracing::debug!( + "Executing OP_IdxInsert: cursor={}, index='{}', record_reg={}", + cursor_id, + index_meta.name, + record_reg + ); + } + if let Some(Cursor::IndexMethod(cursor)) = &mut state.cursors[cursor_id] { let Some(start) = unpacked_start else { return Err(LimboError::InternalError(