mirror of
https://github.com/aljazceru/turso.git
synced 2026-02-22 16:35:30 +01:00
Implement basic not null constraint checks
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user