mirror of
https://github.com/tursodatabase/limbo.git
synced 2025-08-04 18:18:03 +00:00
Primary key constraint working
This commit is contained in:
parent
c09e4d1d38
commit
b6036cc79d
5 changed files with 142 additions and 15 deletions
|
@ -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)>,
|
||||
|
|
|
@ -440,6 +440,9 @@ impl ProgramBuilder {
|
|||
Insn::VFilter { pc_if_empty, .. } => {
|
||||
resolve(pc_if_empty, "VFilter");
|
||||
}
|
||||
Insn::NoConflict { target_pc, .. } => {
|
||||
resolve(target_pc, "NoConflict");
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue