Primary key constraint working

This commit is contained in:
pedrocarlo 2025-04-23 16:44:13 -03:00
parent c09e4d1d38
commit b6036cc79d
5 changed files with 142 additions and 15 deletions

View file

@ -283,19 +283,7 @@ pub fn translate_insert(
}
_ => (),
}
// Create and insert the record
program.emit_insn(Insn::MakeRecord {
start_reg: column_registers_start,
count: num_cols,
dest_reg: record_register,
});
program.emit_insn(Insn::Insert {
cursor: cursor_id,
key_reg: rowid_reg,
record_reg: record_register,
flag: 0,
});
for index_col_mapping in index_col_mappings.iter() {
// find which cursor we opened earlier for this index
let idx_cursor_id = idx_cursors
@ -332,6 +320,43 @@ pub fn translate_insert(
dest_reg: record_reg,
});
let make_record_label = program.allocate_label();
program.emit_insn(Insn::NoConflict {
cursor_id: idx_cursor_id,
target_pc: make_record_label,
record_reg: idx_start_reg,
num_regs: num_cols,
});
let mut column_names = Vec::new();
for (index, ..) in index_col_mapping.columns.iter() {
let name = btree_table
.columns
.get(*index)
.unwrap()
.name
.as_ref()
.expect("column name is None");
column_names.push(format!("{}.{name}", btree_table.name));
}
let column_names =
column_names
.into_iter()
.enumerate()
.fold(String::new(), |mut accum, (idx, name)| {
if idx % 2 == 1 {
accum.push(',');
}
accum.push_str(&name);
accum
});
program.emit_insn(Insn::Halt {
err_code: SQLITE_CONSTRAINT_PRIMARYKEY,
description: format!("{}.{}", table_name.0, column_names),
});
program.resolve_label(make_record_label, program.offset());
// now do the actual index insertion using the unpacked registers
program.emit_insn(Insn::IdxInsert {
cursor_id: idx_cursor_id,
@ -342,6 +367,21 @@ pub fn translate_insert(
flags: IdxInsertFlags::new(),
});
}
// Create and insert the record
program.emit_insn(Insn::MakeRecord {
start_reg: column_registers_start,
count: num_cols,
dest_reg: record_register,
});
program.emit_insn(Insn::Insert {
cursor: cursor_id,
key_reg: rowid_reg,
record_reg: record_register,
flag: 0,
});
if inserting_multiple_rows {
// For multiple rows, loop back
program.emit_insn(Insn::Goto {
@ -472,7 +512,7 @@ fn resolve_columns_for_insert<'a>(
/// Represents how a column in an index should be populated during an INSERT.
/// Similar to ColumnMapping above but includes the index name, as well as multiple
/// possible value indices for each.
#[derive(Default)]
#[derive(Debug, Default)]
struct IndexColMapping {
idx_name: String,
columns: Vec<(usize, IndexColumn)>,

View file

@ -440,6 +440,9 @@ impl ProgramBuilder {
Insn::VFilter { pc_if_empty, .. } => {
resolve(pc_if_empty, "VFilter");
}
Insn::NoConflict { target_pc, .. } => {
resolve(target_pc, "NoConflict");
}
_ => {}
}
}

View file

@ -3,6 +3,7 @@ use crate::numeric::{NullableInteger, Numeric};
use crate::storage::database::FileMemoryStorage;
use crate::storage::page_cache::DumbLruPageCache;
use crate::storage::pager::CreateBTreeFlags;
use crate::types::ImmutableRecord;
use crate::{
error::{LimboError, SQLITE_CONSTRAINT, SQLITE_CONSTRAINT_PRIMARYKEY},
ext::ExtValue,
@ -3895,6 +3896,67 @@ pub fn op_soft_null(
Ok(InsnFunctionStepResult::Step)
}
pub fn op_no_conflict(
program: &Program,
state: &mut ProgramState,
insn: &Insn,
pager: &Rc<Pager>,
mv_store: Option<&Rc<MvStore>>,
) -> Result<InsnFunctionStepResult> {
let Insn::NoConflict {
cursor_id,
target_pc,
record_reg,
num_regs,
} = insn
else {
unreachable!("unexpected Insn {:?}", insn)
};
let found = {
let mut cursor = state.get_cursor(*cursor_id);
let cursor = cursor.as_btree_mut();
let any_fn = |record: &ImmutableRecord| {
for val in record.values.iter() {
if matches!(val, RefValue::Null) {
return false;
}
}
true
};
let record = if *num_regs == 0 {
let record = match &state.registers[*record_reg] {
Register::Record(r) => r,
_ => {
return Err(LimboError::InternalError(
"NoConflict: exepected a record in the register".into(),
));
}
};
record
} else {
&make_record(&state.registers, record_reg, num_regs)
};
// Should early return and jump if any of the values in the record is NULL
let found = any_fn(record);
if found {
return_if_io!(cursor.seek(SeekKey::IndexKey(record), SeekOp::EQ))
} else {
found
}
};
if found {
state.pc += 1;
} else {
state.pc = target_pc.to_offset_int();
}
Ok(InsnFunctionStepResult::Step)
}
pub fn op_not_exists(
program: &Program,
state: &mut ProgramState,

View file

@ -569,13 +569,13 @@ pub fn insn_to_str(
),
Insn::Halt {
err_code,
description: _,
description,
} => (
"Halt",
*err_code as i32,
0,
0,
OwnedValue::build_text(""),
OwnedValue::build_text(&description),
0,
"".to_string(),
),
@ -1068,6 +1068,20 @@ pub fn insn_to_str(
0,
"".to_string(),
),
Insn::NoConflict {
cursor_id,
target_pc,
record_reg,
num_regs,
} => (
"NoConflict",
*cursor_id as i32,
target_pc.to_debug_int(),
*record_reg as i32,
OwnedValue::build_text(&format!("{num_regs}")),
0,
format!("key=r[{}]", record_reg),
),
Insn::NotExists {
cursor,
rowid_reg,

View file

@ -664,6 +664,13 @@ pub enum Insn {
reg: usize,
},
NoConflict {
cursor_id: CursorID, // P1 index cursor
target_pc: BranchOffset, // P2 jump target
record_reg: usize,
num_regs: usize,
},
NotExists {
cursor: CursorID,
rowid_reg: usize,
@ -922,6 +929,7 @@ impl Insn {
Insn::NewRowid { .. } => execute::op_new_rowid,
Insn::MustBeInt { .. } => execute::op_must_be_int,
Insn::SoftNull { .. } => execute::op_soft_null,
Insn::NoConflict { .. } => execute::op_no_conflict,
Insn::NotExists { .. } => execute::op_not_exists,
Insn::OffsetLimit { .. } => execute::op_offset_limit,
Insn::OpenWrite { .. } => execute::op_open_write,