mirror of
https://github.com/aljazceru/turso.git
synced 2026-01-07 10:14:21 +01:00
Merge 'Use the SetCookie opcode to implement user_version pragma' from meteorgan
1. implement the `SetCookie` opcode for `user_version` 2. reimplement `PRAGMA user_version=N` functionality using the `SetCookie` opcode **sqlite** ``` sqlite> explain pragma user_version=10; addr opcode p1 p2 p3 p4 p5 comment ---- ------------- ---- ---- ---- ------------- -- ------------- 0 Init 0 5 0 0 Start at 5 1 Expire 1 1 0 0 2 Transaction 0 1 0 0 3 SetCookie 0 6 10 1 4 Halt 0 0 0 0 5 Goto 0 1 0 0 ``` **limbo** ``` limbo> explain pragma user_version=10; addr opcode p1 p2 p3 p4 p5 comment ---- ----------------- ---- ---- ---- ------------- -- ------- 0 Init 0 3 0 0 Start at 3 1 SetCookie 0 6 10 1 2 Halt 0 0 0 0 3 Transaction 0 1 0 0 write=true 4 Goto 0 1 0 0 ``` To fully align the opcodes with SQLite, we still need to implement the `Expire` opcode, which I plan to address in the next PR. Reviewed-by: Jussi Saurio <jussi.saurio@gmail.com> Closes #1590
This commit is contained in:
@@ -10,10 +10,10 @@ use crate::fast_lock::SpinLock;
|
||||
use crate::schema::Schema;
|
||||
use crate::storage::sqlite3_ondisk::{DatabaseHeader, MIN_PAGE_CACHE_SIZE};
|
||||
use crate::storage::wal::CheckpointMode;
|
||||
use crate::util::normalize_ident;
|
||||
use crate::util::{normalize_ident, parse_signed_number};
|
||||
use crate::vdbe::builder::{ProgramBuilder, ProgramBuilderOpts, QueryMode};
|
||||
use crate::vdbe::insn::{Cookie, Insn};
|
||||
use crate::{bail_parse_error, Pager};
|
||||
use crate::{bail_parse_error, Pager, Value};
|
||||
use std::str::FromStr;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
@@ -142,27 +142,19 @@ fn update_pragma(
|
||||
Ok(())
|
||||
}
|
||||
PragmaName::UserVersion => {
|
||||
let version_value = match value {
|
||||
ast::Expr::Literal(ast::Literal::Numeric(numeric_value)) => {
|
||||
numeric_value.parse::<i32>()?
|
||||
}
|
||||
ast::Expr::Unary(ast::UnaryOperator::Negative, expr) => match *expr {
|
||||
ast::Expr::Literal(ast::Literal::Numeric(numeric_value)) => {
|
||||
-numeric_value.parse::<i32>()?
|
||||
}
|
||||
_ => bail_parse_error!("Not a valid value"),
|
||||
},
|
||||
_ => bail_parse_error!("Not a valid value"),
|
||||
let data = parse_signed_number(&value)?;
|
||||
let version_value = match data {
|
||||
Value::Integer(i) => i as i32,
|
||||
Value::Float(f) => f as i32,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let mut header_guard = header.lock();
|
||||
|
||||
// update in-memory
|
||||
header_guard.user_version = version_value;
|
||||
|
||||
// update in disk
|
||||
pager.write_database_header(&header_guard);
|
||||
|
||||
program.emit_insn(Insn::SetCookie {
|
||||
db: 0,
|
||||
cookie: Cookie::UserVersion,
|
||||
value: version_value,
|
||||
p5: 1,
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
PragmaName::SchemaVersion => {
|
||||
|
||||
25
core/util.rs
25
core/util.rs
@@ -4,7 +4,9 @@ use crate::{
|
||||
types::{Value, ValueType},
|
||||
LimboError, OpenFlags, Result, Statement, StepResult, SymbolTable, IO,
|
||||
};
|
||||
use limbo_sqlite3_parser::ast::{self, CreateTableBody, Expr, FunctionTail, Literal};
|
||||
use limbo_sqlite3_parser::ast::{
|
||||
self, CreateTableBody, Expr, FunctionTail, Literal, UnaryOperator,
|
||||
};
|
||||
use std::{rc::Rc, sync::Arc};
|
||||
|
||||
pub trait RoundToPrecision {
|
||||
@@ -1011,6 +1013,27 @@ pub fn parse_numeric_literal(text: &str) -> Result<Value> {
|
||||
Ok(Value::Float(float_value))
|
||||
}
|
||||
|
||||
pub fn parse_signed_number(expr: &Expr) -> Result<Value> {
|
||||
match expr {
|
||||
Expr::Literal(Literal::Numeric(num)) => parse_numeric_literal(num),
|
||||
Expr::Unary(op, expr) => match (op, expr.as_ref()) {
|
||||
(UnaryOperator::Negative, Expr::Literal(Literal::Numeric(num))) => {
|
||||
let data = "-".to_owned() + &num.to_string();
|
||||
parse_numeric_literal(&data)
|
||||
}
|
||||
(UnaryOperator::Positive, Expr::Literal(Literal::Numeric(num))) => {
|
||||
parse_numeric_literal(num)
|
||||
}
|
||||
_ => Err(LimboError::InvalidArgument(
|
||||
"signed-number must follow the format: ([+|-] numeric-literal)".to_string(),
|
||||
)),
|
||||
},
|
||||
_ => Err(LimboError::InvalidArgument(
|
||||
"signed-number must follow the format: ([+|-] numeric-literal)".to_string(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
// for TVF's we need these at planning time so we cannot emit translate_expr
|
||||
pub fn vtable_args(args: &[ast::Expr]) -> Vec<limbo_ext::Value> {
|
||||
let mut vtable_args = Vec::new();
|
||||
|
||||
@@ -4520,6 +4520,37 @@ pub fn op_read_cookie(
|
||||
Ok(InsnFunctionStepResult::Step)
|
||||
}
|
||||
|
||||
pub fn op_set_cookie(
|
||||
program: &Program,
|
||||
state: &mut ProgramState,
|
||||
insn: &Insn,
|
||||
pager: &Rc<Pager>,
|
||||
mv_store: Option<&Rc<MvStore>>,
|
||||
) -> Result<InsnFunctionStepResult> {
|
||||
let Insn::SetCookie {
|
||||
db,
|
||||
cookie,
|
||||
value,
|
||||
p5,
|
||||
} = insn
|
||||
else {
|
||||
unreachable!("unexpected Insn {:?}", insn)
|
||||
};
|
||||
if *db > 0 {
|
||||
todo!("temp databases not implemented yet");
|
||||
}
|
||||
match cookie {
|
||||
Cookie::UserVersion => {
|
||||
let mut header_guard = pager.db_header.lock();
|
||||
header_guard.user_version = *value;
|
||||
pager.write_database_header(&*header_guard);
|
||||
}
|
||||
cookie => todo!("{cookie:?} is not yet implement for SetCookie"),
|
||||
}
|
||||
state.pc += 1;
|
||||
Ok(InsnFunctionStepResult::Step)
|
||||
}
|
||||
|
||||
pub fn op_shift_right(
|
||||
program: &Program,
|
||||
state: &mut ProgramState,
|
||||
|
||||
@@ -1373,6 +1373,20 @@ pub fn insn_to_str(
|
||||
0,
|
||||
"".to_string(),
|
||||
),
|
||||
Insn::SetCookie {
|
||||
db,
|
||||
cookie,
|
||||
value,
|
||||
p5,
|
||||
} => (
|
||||
"SetCookie",
|
||||
*db as i32,
|
||||
*cookie as i32,
|
||||
*value,
|
||||
Value::build_text(""),
|
||||
*p5,
|
||||
"".to_string(),
|
||||
),
|
||||
Insn::AutoCommit {
|
||||
auto_commit,
|
||||
rollback,
|
||||
|
||||
@@ -844,6 +844,14 @@ pub enum Insn {
|
||||
dest: usize,
|
||||
cookie: Cookie,
|
||||
},
|
||||
/// Write the value in register P3 into cookie number P2 of database P1.
|
||||
/// If P2 is the SCHEMA_VERSION cookie (cookie number 1) then the internal schema version is set to P3-P5
|
||||
SetCookie {
|
||||
db: usize,
|
||||
cookie: Cookie,
|
||||
value: i32,
|
||||
p5: u16,
|
||||
},
|
||||
/// Open a new cursor P1 to a transient table.
|
||||
OpenEphemeral {
|
||||
cursor_id: usize,
|
||||
@@ -1010,6 +1018,7 @@ impl Insn {
|
||||
Insn::Noop => execute::op_noop,
|
||||
Insn::PageCount { .. } => execute::op_page_count,
|
||||
Insn::ReadCookie { .. } => execute::op_read_cookie,
|
||||
Insn::SetCookie { .. } => execute::op_set_cookie,
|
||||
Insn::OpenEphemeral { .. } | Insn::OpenAutoindex { .. } => execute::op_open_ephemeral,
|
||||
Insn::Once { .. } => execute::op_once,
|
||||
Insn::Found { .. } | Insn::NotFound { .. } => execute::op_found,
|
||||
|
||||
@@ -55,3 +55,13 @@ do_execsql_test_on_specific_db ":memory:" pragma-user-version-update {
|
||||
PRAGMA user_version = 42;
|
||||
PRAGMA user_version;
|
||||
} {42}
|
||||
|
||||
do_execsql_test_on_specific_db ":memory:" pragma-user-version-negative-value {
|
||||
PRAGMA user_version = -10;
|
||||
PRAGMA user_version;
|
||||
} {-10}
|
||||
|
||||
do_execsql_test_on_specific_db ":memory:" pragma-user-version-float-value {
|
||||
PRAGMA user_version = 10.9;
|
||||
PRAGMA user_version;
|
||||
} {10}
|
||||
|
||||
Reference in New Issue
Block a user