Implement basic not null constraint checks

This commit is contained in:
Anton Harniakou
2025-06-05 19:02:31 +03:00
parent 9f17be8162
commit fb86476525
5 changed files with 116 additions and 2 deletions

View File

@@ -88,3 +88,4 @@ impl From<limbo_ext::ResultCode> for LimboError {
pub const SQLITE_CONSTRAINT: usize = 19;
pub const SQLITE_CONSTRAINT_PRIMARYKEY: usize = SQLITE_CONSTRAINT | (6 << 8);
pub const SQLITE_CONSTRAINT_NOTNULL: usize = SQLITE_CONSTRAINT | (5 << 8);

View File

@@ -4,7 +4,7 @@ use limbo_sqlite3_parser::ast::{
DistinctNames, Expr, InsertBody, OneSelect, QualifiedName, ResolveType, ResultColumn, With,
};
use crate::error::SQLITE_CONSTRAINT_PRIMARYKEY;
use crate::error::{SQLITE_CONSTRAINT_NOTNULL, SQLITE_CONSTRAINT_PRIMARYKEY};
use crate::schema::{IndexColumn, Table};
use crate::util::normalize_ident;
use crate::vdbe::builder::{ProgramBuilderOpts, QueryMode};
@@ -508,6 +508,25 @@ pub fn translate_insert(
});
}
for (i, col) in column_mappings
.iter()
.enumerate()
.filter(|(_, col)| col.column.notnull)
{
let target_reg = i + column_registers_start;
program.emit_insn(Insn::HaltIfNull {
target_reg,
err_code: SQLITE_CONSTRAINT_NOTNULL,
description: format!(
"{}.{}",
table_name,
col.column
.name
.as_ref()
.expect("Column name must be present")
),
});
}
// Create and insert the record
program.emit_insn(Insn::MakeRecord {
start_reg: column_registers_start,

View File

@@ -7,7 +7,9 @@ use crate::storage::pager::CreateBTreeFlags;
use crate::storage::wal::DummyWAL;
use crate::types::ImmutableRecord;
use crate::{
error::{LimboError, SQLITE_CONSTRAINT, SQLITE_CONSTRAINT_PRIMARYKEY},
error::{
LimboError, SQLITE_CONSTRAINT, SQLITE_CONSTRAINT_NOTNULL, SQLITE_CONSTRAINT_PRIMARYKEY,
},
ext::ExtValue,
function::{AggFunc, ExtFunc, MathFunc, MathFuncArity, ScalarFunc, VectorFunc},
functions::{
@@ -1595,6 +1597,48 @@ pub fn op_prev(
Ok(InsnFunctionStepResult::Step)
}
pub fn halt(
program: &Program,
state: &mut ProgramState,
pager: &Rc<Pager>,
mv_store: Option<&Rc<MvStore>>,
err_code: usize,
description: &str,
) -> Result<InsnFunctionStepResult> {
if err_code > 0 {
// invalidate page cache in case of error
pager.clear_page_cache();
}
match err_code {
0 => {}
SQLITE_CONSTRAINT_PRIMARYKEY => {
return Err(LimboError::Constraint(format!(
"UNIQUE constraint failed: {} (19)",
description
)));
}
SQLITE_CONSTRAINT_NOTNULL => {
return Err(LimboError::Constraint(format!(
"NOTNULL constraint failed: {} (19)",
description
)));
}
_ => {
return Err(LimboError::Constraint(format!(
"undocumented halt error code {}",
description
)));
}
}
match program.commit_txn(pager.clone(), state, mv_store)? {
StepResult::Done => Ok(InsnFunctionStepResult::Done),
StepResult::IO => Ok(InsnFunctionStepResult::IO),
StepResult::Row => Ok(InsnFunctionStepResult::Row),
StepResult::Interrupt => Ok(InsnFunctionStepResult::Interrupt),
StepResult::Busy => Ok(InsnFunctionStepResult::Busy),
}
}
pub fn op_halt(
program: &Program,
state: &mut ProgramState,
@@ -1621,6 +1665,12 @@ pub fn op_halt(
description
)));
}
SQLITE_CONSTRAINT_NOTNULL => {
return Err(LimboError::Constraint(format!(
"NOTNULL constraint failed: {} (19)",
description
)));
}
_ => {
return Err(LimboError::Constraint(format!(
"undocumented halt error code {}",
@@ -1637,6 +1687,29 @@ pub fn op_halt(
}
}
pub fn op_halt_if_null(
program: &Program,
state: &mut ProgramState,
insn: &Insn,
pager: &Rc<Pager>,
mv_store: Option<&Rc<MvStore>>,
) -> Result<InsnFunctionStepResult> {
let Insn::HaltIfNull {
target_reg,
err_code,
description,
} = insn
else {
unreachable!("unexpected Insn {:?}", insn)
};
if state.registers[*target_reg].get_owned_value() == &Value::Null {
halt(program, state, pager, mv_store, *err_code, &description)
} else {
state.pc += 1;
Ok(InsnFunctionStepResult::Step)
}
}
pub fn op_transaction(
program: &Program,
state: &mut ProgramState,

View File

@@ -634,6 +634,19 @@ pub fn insn_to_str(
0,
"".to_string(),
),
Insn::HaltIfNull {
err_code,
target_reg,
description,
} => (
"HalfIfNull",
*err_code as i32,
0,
*target_reg as i32,
Value::build_text(&description),
0,
"".to_string(),
),
Insn::Transaction { write } => (
"Transaction",
0,

View File

@@ -406,6 +406,13 @@ pub enum Insn {
description: String,
},
/// Halt the program if P3 is null.
HaltIfNull {
target_reg: usize, // P3
description: String, // p4
err_code: usize, // p1
},
/// Start a transaction.
Transaction {
write: bool,
@@ -957,6 +964,7 @@ impl Insn {
Insn::Next { .. } => execute::op_next,
Insn::Prev { .. } => execute::op_prev,
Insn::Halt { .. } => execute::op_halt,
Insn::HaltIfNull { .. } => execute::op_halt_if_null,
Insn::Transaction { .. } => execute::op_transaction,
Insn::AutoCommit { .. } => execute::op_auto_commit,
Insn::Goto { .. } => execute::op_goto,