diff --git a/COMPAT.md b/COMPAT.md index 213841cb1..a7baaca83 100644 --- a/COMPAT.md +++ b/COMPAT.md @@ -160,6 +160,10 @@ Feature support of [sqlite expr syntax](https://www.sqlite.org/lang_expr.html). | upper(X) | Yes | | | zeroblob(N) | Yes | | + + + + ### Mathematical functions | Function | Status | Comment | @@ -449,3 +453,16 @@ Feature support of [sqlite expr syntax](https://www.sqlite.org/lang_expr.html). | Variable | No | | VerifyCookie | No | | Yield | Yes | + + + + +| LibSql Compatibility / Extensions| | | +| ---------------------------- | ------ | ------- | +| **UUID** | | UUID's in limbo are `blobs` by default| +| uuid4() | Yes | uuid version 4 | +| uuid4_str() | Yes | uuid v4 string alias `gen_random_uuid()` for PG compatibility| +| uuid7(X?) | Yes | uuid version 7, Optional arg for seconds since epoch| +| uuid7_timestamp_ms(X) | Yes | Convert a uuid v7 to milliseconds since epoch| +| uuid_str(X) | Yes | Convert a valid uuid to string| +| uuid_blob(X) | Yes | Convert a valid uuid to blob| diff --git a/Cargo.lock b/Cargo.lock index 53b97bfc1..0c84456f9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1157,6 +1157,7 @@ dependencies = [ "sqlite3-parser", "tempfile", "thiserror 1.0.69", + "uuid", ] [[package]] @@ -2277,6 +2278,9 @@ name = "uuid" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" +dependencies = [ + "getrandom", +] [[package]] name = "vcpkg" diff --git a/core/Cargo.toml b/core/Cargo.toml index 58783855a..4ef87b469 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -14,13 +14,14 @@ name = "limbo_core" path = "lib.rs" [features] -default = ["fs", "json"] +default = ["fs", "json", "uuid"] fs = [] json = [ "dep:jsonb", "dep:pest", "dep:pest_derive", ] +uuid = ["dep:uuid"] [target.'cfg(target_os = "linux")'.dependencies] io-uring = "0.6.1" @@ -54,6 +55,7 @@ pest_derive = { version = "2.0", optional = true } rand = "0.8.5" bumpalo = { version = "3.16.0", features = ["collections", "boxed"] } macros = { path = "../macros" } +uuid = { version = "1.11.0", features = ["v4", "v7"], optional = true } [target.'cfg(not(target_family = "windows"))'.dev-dependencies] pprof = { version = "0.14.0", features = ["criterion", "flamegraph"] } diff --git a/core/ext/mod.rs b/core/ext/mod.rs new file mode 100644 index 000000000..312ebfcea --- /dev/null +++ b/core/ext/mod.rs @@ -0,0 +1,30 @@ +#[cfg(feature = "uuid")] +mod uuid; +#[cfg(feature = "uuid")] +pub use uuid::{exec_ts_from_uuid7, exec_uuid, exec_uuidblob, exec_uuidstr, UuidFunc}; + +#[derive(Debug, Clone, PartialEq)] +pub enum ExtFunc { + #[cfg(feature = "uuid")] + Uuid(UuidFunc), +} + +impl std::fmt::Display for ExtFunc { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + #[cfg(feature = "uuid")] + ExtFunc::Uuid(uuidfn) => write!(f, "{}", uuidfn), + _ => write!(f, "unknown"), + } + } +} + +impl ExtFunc { + pub fn resolve_function(name: &str, num_args: usize) -> Option { + match name { + #[cfg(feature = "uuid")] + name => UuidFunc::resolve_function(name, num_args), + _ => None, + } + } +} diff --git a/core/ext/uuid.rs b/core/ext/uuid.rs new file mode 100644 index 000000000..00ce23d9b --- /dev/null +++ b/core/ext/uuid.rs @@ -0,0 +1,341 @@ +use super::ExtFunc; +use crate::{ + types::{LimboText, OwnedValue}, + LimboError, +}; +use std::rc::Rc; +use uuid::{ContextV7, Timestamp, Uuid}; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum UuidFunc { + Uuid4Str, + Uuid4, + Uuid7, + Uuid7TS, + UuidStr, + UuidBlob, +} + +impl UuidFunc { + pub fn resolve_function(name: &str, num_args: usize) -> Option { + match name { + "uuid4_str" => Some(ExtFunc::Uuid(UuidFunc::Uuid4Str)), + "uuid4" => Some(ExtFunc::Uuid(UuidFunc::Uuid4)), + "uuid7" if num_args < 2 => Some(ExtFunc::Uuid(UuidFunc::Uuid7)), + "uuid_str" if num_args == 1 => Some(ExtFunc::Uuid(UuidFunc::UuidStr)), + "uuid_blob" if num_args == 1 => Some(ExtFunc::Uuid(UuidFunc::UuidBlob)), + "uuid7_timestamp_ms" if num_args == 1 => Some(ExtFunc::Uuid(UuidFunc::Uuid7TS)), + // postgres_compatability + "gen_random_uuid" => Some(ExtFunc::Uuid(UuidFunc::Uuid4Str)), + _ => None, + } + } +} + +impl std::fmt::Display for UuidFunc { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + UuidFunc::Uuid4Str => write!(f, "uuid4_str"), + UuidFunc::Uuid4 => write!(f, "uuid4"), + UuidFunc::Uuid7 => write!(f, "uuid7"), + UuidFunc::Uuid7TS => write!(f, "uuid7_timestamp_ms"), + UuidFunc::UuidStr => write!(f, "uuid_str"), + UuidFunc::UuidBlob => write!(f, "uuid_blob"), + } + } +} + +pub fn exec_uuid(var: &UuidFunc, sec: Option<&OwnedValue>) -> crate::Result { + match var { + UuidFunc::Uuid4 => Ok(OwnedValue::Blob(Rc::new( + Uuid::new_v4().into_bytes().to_vec(), + ))), + UuidFunc::Uuid4Str => Ok(OwnedValue::Text(LimboText::new(Rc::new( + Uuid::new_v4().to_string(), + )))), + UuidFunc::Uuid7 => { + let uuid = match sec { + Some(OwnedValue::Integer(ref seconds)) => { + let ctx = ContextV7::new(); + if *seconds < 0 { + // not valid unix timestamp, error or null? + return Ok(OwnedValue::Null); + } + Uuid::new_v7(Timestamp::from_unix(ctx, *seconds as u64, 0)) + } + _ => Uuid::now_v7(), + }; + Ok(OwnedValue::Blob(Rc::new(uuid.into_bytes().to_vec()))) + } + _ => unreachable!(), + } +} + +pub fn exec_uuidstr(reg: &OwnedValue) -> crate::Result { + match reg { + OwnedValue::Blob(blob) => { + let uuid = Uuid::from_slice(blob).map_err(|e| LimboError::ParseError(e.to_string()))?; + Ok(OwnedValue::Text(LimboText::new(Rc::new(uuid.to_string())))) + } + OwnedValue::Text(ref val) => { + let uuid = + Uuid::parse_str(&val.value).map_err(|e| LimboError::ParseError(e.to_string()))?; + Ok(OwnedValue::Text(LimboText::new(Rc::new(uuid.to_string())))) + } + OwnedValue::Null => Ok(OwnedValue::Null), + _ => Err(LimboError::ParseError( + "Invalid argument type for UUID function".to_string(), + )), + } +} + +pub fn exec_uuidblob(reg: &OwnedValue) -> crate::Result { + match reg { + OwnedValue::Text(val) => { + let uuid = + Uuid::parse_str(&val.value).map_err(|e| LimboError::ParseError(e.to_string()))?; + Ok(OwnedValue::Blob(Rc::new(uuid.as_bytes().to_vec()))) + } + OwnedValue::Blob(blob) => { + let uuid = Uuid::from_slice(blob).map_err(|e| LimboError::ParseError(e.to_string()))?; + Ok(OwnedValue::Blob(Rc::new(uuid.as_bytes().to_vec()))) + } + OwnedValue::Null => Ok(OwnedValue::Null), + _ => Err(LimboError::ParseError( + "Invalid argument type for UUID function".to_string(), + )), + } +} + +pub fn exec_ts_from_uuid7(reg: &OwnedValue) -> OwnedValue { + let uuid = match reg { + OwnedValue::Blob(blob) => { + Uuid::from_slice(blob).map_err(|e| LimboError::ParseError(e.to_string())) + } + OwnedValue::Text(val) => { + Uuid::parse_str(&val.value).map_err(|e| LimboError::ParseError(e.to_string())) + } + _ => Err(LimboError::ParseError( + "Invalid argument type for UUID function".to_string(), + )), + }; + match uuid { + Ok(uuid) => OwnedValue::Integer(uuid_to_unix(uuid.as_bytes()) as i64), + // display error? sqlean seems to set value to null + Err(_) => OwnedValue::Null, + } +} + +#[inline(always)] +fn uuid_to_unix(uuid: &[u8; 16]) -> u64 { + ((uuid[0] as u64) << 40) + | ((uuid[1] as u64) << 32) + | ((uuid[2] as u64) << 24) + | ((uuid[3] as u64) << 16) + | ((uuid[4] as u64) << 8) + | (uuid[5] as u64) +} + +#[cfg(test)] +#[cfg(feature = "uuid")] +pub mod test { + use super::UuidFunc; + use crate::types::OwnedValue; + #[test] + fn test_exec_uuid_v4blob() { + use super::exec_uuid; + use uuid::Uuid; + let func = UuidFunc::Uuid4; + let owned_val = exec_uuid(&func, None); + match owned_val { + Ok(OwnedValue::Blob(blob)) => { + assert_eq!(blob.len(), 16); + let uuid = Uuid::from_slice(&blob); + assert!(uuid.is_ok()); + assert_eq!(uuid.unwrap().get_version_num(), 4); + } + _ => panic!("exec_uuid did not return a Blob variant"), + } + } + + #[test] + fn test_exec_uuid_v4str() { + use super::{exec_uuid, UuidFunc}; + use uuid::Uuid; + let func = UuidFunc::Uuid4Str; + let owned_val = exec_uuid(&func, None); + match owned_val { + Ok(OwnedValue::Text(v4str)) => { + assert_eq!(v4str.value.len(), 36); + let uuid = Uuid::parse_str(&v4str.value); + assert!(uuid.is_ok()); + assert_eq!(uuid.unwrap().get_version_num(), 4); + } + _ => panic!("exec_uuid did not return a Blob variant"), + } + } + + #[test] + fn test_exec_uuid_v7_now() { + use super::{exec_uuid, UuidFunc}; + use uuid::Uuid; + let func = UuidFunc::Uuid7; + let owned_val = exec_uuid(&func, None); + match owned_val { + Ok(OwnedValue::Blob(blob)) => { + assert_eq!(blob.len(), 16); + let uuid = Uuid::from_slice(&blob); + assert!(uuid.is_ok()); + assert_eq!(uuid.unwrap().get_version_num(), 7); + } + _ => panic!("exec_uuid did not return a Blob variant"), + } + } + + #[test] + fn test_exec_uuid_v7_with_input() { + use super::{exec_uuid, UuidFunc}; + use uuid::Uuid; + let func = UuidFunc::Uuid7; + let owned_val = exec_uuid(&func, Some(&OwnedValue::Integer(946702800))); + match owned_val { + Ok(OwnedValue::Blob(blob)) => { + assert_eq!(blob.len(), 16); + let uuid = Uuid::from_slice(&blob); + assert!(uuid.is_ok()); + assert_eq!(uuid.unwrap().get_version_num(), 7); + } + _ => panic!("exec_uuid did not return a Blob variant"), + } + } + + #[test] + fn test_exec_uuid_v7_now_to_timestamp() { + use super::{exec_ts_from_uuid7, exec_uuid, UuidFunc}; + use uuid::Uuid; + let func = UuidFunc::Uuid7; + let owned_val = exec_uuid(&func, None); + match owned_val { + Ok(OwnedValue::Blob(ref blob)) => { + assert_eq!(blob.len(), 16); + let uuid = Uuid::from_slice(blob); + assert!(uuid.is_ok()); + assert_eq!(uuid.unwrap().get_version_num(), 7); + } + _ => panic!("exec_uuid did not return a Blob variant"), + } + let result = exec_ts_from_uuid7(&owned_val.expect("uuid7")); + if let OwnedValue::Integer(ref ts) = result { + let unixnow = (std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_secs() + * 1000) as i64; + assert!(*ts >= unixnow - 1000); + } + } + + #[test] + fn test_exec_uuid_v7_to_timestamp() { + use super::{exec_ts_from_uuid7, exec_uuid, UuidFunc}; + use uuid::Uuid; + let func = UuidFunc::Uuid7; + let owned_val = exec_uuid(&func, Some(&OwnedValue::Integer(946702800))); + match owned_val { + Ok(OwnedValue::Blob(ref blob)) => { + assert_eq!(blob.len(), 16); + let uuid = Uuid::from_slice(blob); + assert!(uuid.is_ok()); + assert_eq!(uuid.unwrap().get_version_num(), 7); + } + _ => panic!("exec_uuid did not return a Blob variant"), + } + let result = exec_ts_from_uuid7(&owned_val.expect("uuid7")); + assert_eq!(result, OwnedValue::Integer(946702800 * 1000)); + if let OwnedValue::Integer(ts) = result { + let time = chrono::DateTime::from_timestamp(ts / 1000, 0); + assert_eq!( + time.unwrap(), + "2000-01-01T05:00:00Z" + .parse::>() + .unwrap() + ); + } + } + + #[test] + fn test_exec_uuid_v4_str_to_blob() { + use super::{exec_uuid, exec_uuidblob, UuidFunc}; + use uuid::Uuid; + let owned_val = exec_uuidblob( + &exec_uuid(&UuidFunc::Uuid4Str, None).expect("uuid v4 string to generate"), + ); + match owned_val { + Ok(OwnedValue::Blob(blob)) => { + assert_eq!(blob.len(), 16); + let uuid = Uuid::from_slice(&blob); + assert!(uuid.is_ok()); + assert_eq!(uuid.unwrap().get_version_num(), 4); + } + _ => panic!("exec_uuid did not return a Blob variant"), + } + } + + #[test] + fn test_exec_uuid_v7_str_to_blob() { + use super::{exec_uuid, exec_uuidblob, exec_uuidstr, UuidFunc}; + use uuid::Uuid; + // convert a v7 blob to a string then back to a blob + let owned_val = exec_uuidblob( + &exec_uuidstr(&exec_uuid(&UuidFunc::Uuid7, None).expect("uuid v7 blob to generate")) + .expect("uuid v7 string to generate"), + ); + match owned_val { + Ok(OwnedValue::Blob(blob)) => { + assert_eq!(blob.len(), 16); + let uuid = Uuid::from_slice(&blob); + assert!(uuid.is_ok()); + assert_eq!(uuid.unwrap().get_version_num(), 7); + } + _ => panic!("exec_uuid did not return a Blob variant"), + } + } + + #[test] + fn test_exec_uuid_v4_blob_to_str() { + use super::{exec_uuid, exec_uuidstr, UuidFunc}; + use uuid::Uuid; + // convert a v4 blob to a string + let owned_val = + exec_uuidstr(&exec_uuid(&UuidFunc::Uuid4, None).expect("uuid v7 blob to generate")); + match owned_val { + Ok(OwnedValue::Text(v4str)) => { + assert_eq!(v4str.value.len(), 36); + let uuid = Uuid::parse_str(&v4str.value); + assert!(uuid.is_ok()); + assert_eq!(uuid.unwrap().get_version_num(), 4); + } + _ => panic!("exec_uuid did not return a Blob variant"), + } + } + + #[test] + fn test_exec_uuid_v7_blob_to_str() { + use super::{exec_uuid, exec_uuidstr}; + use uuid::Uuid; + // convert a v7 blob to a string + let owned_val = exec_uuidstr( + &exec_uuid(&UuidFunc::Uuid7, Some(&OwnedValue::Integer(123456789))) + .expect("uuid v7 blob to generate"), + ); + match owned_val { + Ok(OwnedValue::Text(v7str)) => { + assert_eq!(v7str.value.len(), 36); + let uuid = Uuid::parse_str(&v7str.value); + assert!(uuid.is_ok()); + assert_eq!(uuid.unwrap().get_version_num(), 7); + } + _ => panic!("exec_uuid did not return a Blob variant"), + } + } +} diff --git a/core/function.rs b/core/function.rs index 86c88a1e4..8681a4fdf 100644 --- a/core/function.rs +++ b/core/function.rs @@ -1,6 +1,6 @@ +use crate::ext::ExtFunc; use std::fmt; use std::fmt::Display; - #[cfg(feature = "json")] #[derive(Debug, Clone, PartialEq)] pub enum JsonFunc { @@ -256,13 +256,14 @@ impl Display for MathFunc { } } -#[derive(Debug)] +#[derive(Debug, Clone, PartialEq)] pub enum Func { Agg(AggFunc), Scalar(ScalarFunc), Math(MathFunc), #[cfg(feature = "json")] Json(JsonFunc), + Extension(ExtFunc), } impl Display for Func { @@ -273,6 +274,7 @@ impl Display for Func { Func::Math(math_func) => write!(f, "{}", math_func), #[cfg(feature = "json")] Func::Json(json_func) => write!(f, "{}", json_func), + Func::Extension(ext_func) => write!(f, "{}", ext_func), } } } @@ -366,7 +368,10 @@ impl Func { "tan" => Ok(Func::Math(MathFunc::Tan)), "tanh" => Ok(Func::Math(MathFunc::Tanh)), "trunc" => Ok(Func::Math(MathFunc::Trunc)), - _ => Err(()), + _ => match ExtFunc::resolve_function(name, arg_count) { + Some(ext_func) => Ok(Func::Extension(ext_func)), + None => Err(()), + }, } } } diff --git a/core/io/generic.rs b/core/io/generic.rs index 0c35eaf52..17f51d792 100644 --- a/core/io/generic.rs +++ b/core/io/generic.rs @@ -15,7 +15,11 @@ impl GenericIO { impl IO for GenericIO { fn open_file(&self, path: &str, flags: OpenFlags, _direct: bool) -> Result> { trace!("open_file(path = {})", path); - let file = std::fs::File::open(path)?; + let file = std::fs::OpenOptions::new() + .read(true) + .write(true) + .create(matches!(flags, OpenFlags::Create)) + .open(path)?; Ok(Rc::new(GenericFile { file: RefCell::new(file), })) diff --git a/core/lib.rs b/core/lib.rs index f49d8bd3b..a5044fc9a 100644 --- a/core/lib.rs +++ b/core/lib.rs @@ -1,4 +1,5 @@ mod error; +mod ext; mod function; mod io; #[cfg(feature = "json")] diff --git a/core/translate/expr.rs b/core/translate/expr.rs index 363d96a99..734dbb98e 100644 --- a/core/translate/expr.rs +++ b/core/translate/expr.rs @@ -1,10 +1,12 @@ use sqlite3_parser::ast::{self, UnaryOperator}; +#[cfg(feature = "uuid")] +use crate::ext::{ExtFunc, UuidFunc}; #[cfg(feature = "json")] use crate::function::JsonFunc; use crate::function::{AggFunc, Func, FuncCtx, MathFuncArity, ScalarFunc}; use crate::schema::Type; -use crate::util::normalize_ident; +use crate::util::{exprs_are_equivalent, normalize_ident}; use crate::vdbe::{builder::ProgramBuilder, BranchOffset, Insn}; use crate::Result; @@ -554,10 +556,7 @@ pub fn translate_expr( ) -> Result { if let Some(precomputed_exprs_to_registers) = precomputed_exprs_to_registers { for (precomputed_expr, reg) in precomputed_exprs_to_registers.iter() { - // TODO: implement a custom equality check for expressions - // there are lots of examples where this breaks, even simple ones like - // sum(x) != SUM(x) - if expr == *precomputed_expr { + if exprs_are_equivalent(expr, precomputed_expr) { program.emit_insn(Insn::Copy { src_reg: *reg, dst_reg: target_register, @@ -1613,6 +1612,92 @@ pub fn translate_expr( } } } + Func::Extension(ext_func) => match ext_func { + #[cfg(feature = "uuid")] + ExtFunc::Uuid(ref uuid_fn) => match uuid_fn { + UuidFunc::UuidStr | UuidFunc::UuidBlob | UuidFunc::Uuid7TS => { + let args = if let Some(args) = args { + if args.len() != 1 { + crate::bail_parse_error!( + "{} function with not exactly 1 argument", + ext_func.to_string() + ); + } + args + } else { + crate::bail_parse_error!( + "{} function with no arguments", + ext_func.to_string() + ); + }; + + let regs = program.alloc_register(); + translate_expr( + program, + referenced_tables, + &args[0], + regs, + precomputed_exprs_to_registers, + )?; + program.emit_insn(Insn::Function { + constant_mask: 0, + start_reg: regs, + dest: target_register, + func: func_ctx, + }); + Ok(target_register) + } + UuidFunc::Uuid4 | UuidFunc::Uuid4Str => { + if args.is_some() { + crate::bail_parse_error!( + "{} function with arguments", + ext_func.to_string() + ); + } + let regs = program.alloc_register(); + program.emit_insn(Insn::Function { + constant_mask: 0, + start_reg: regs, + dest: target_register, + func: func_ctx, + }); + Ok(target_register) + } + UuidFunc::Uuid7 => { + let args = match args { + Some(args) if args.len() > 1 => crate::bail_parse_error!( + "{} function with more than 1 argument", + ext_func.to_string() + ), + Some(args) => args, + None => &vec![], + }; + let mut start_reg = None; + if let Some(arg) = args.first() { + let reg = program.alloc_register(); + start_reg = Some(reg); + translate_expr( + program, + referenced_tables, + arg, + reg, + precomputed_exprs_to_registers, + )?; + if let ast::Expr::Literal(_) = arg { + program.mark_last_insn_constant() + } + } + program.emit_insn(Insn::Function { + constant_mask: 0, + start_reg: start_reg.unwrap_or(target_register), + dest: target_register, + func: func_ctx, + }); + Ok(target_register) + } + }, + _ => unreachable!("{ext_func} not implemented yet"), + }, Func::Math(math_func) => match math_func.arity() { MathFuncArity::Nullary => { if args.is_some() { diff --git a/core/translate/planner.rs b/core/translate/planner.rs index 8a20a4029..8ca09efe6 100644 --- a/core/translate/planner.rs +++ b/core/translate/planner.rs @@ -2,10 +2,13 @@ use super::plan::{ Aggregate, BTreeTableReference, DeletePlan, Direction, GroupBy, Plan, ResultSetColumn, SelectPlan, SourceOperator, }; -use crate::{bail_parse_error, function::Func, schema::Schema, util::normalize_ident, Result}; -use sqlite3_parser::ast::{ - self, Expr, FromClause, JoinType, Limit, QualifiedName, ResultColumn, SortedColumn, +use crate::{ + function::Func, + schema::Schema, + util::{exprs_are_equivalent, normalize_ident}, + Result, }; +use sqlite3_parser::ast::{self, Expr, FromClause, JoinType, Limit, QualifiedName, ResultColumn}; pub struct OperatorIdCounter { id: usize, @@ -23,7 +26,10 @@ impl OperatorIdCounter { } fn resolve_aggregates(expr: &ast::Expr, aggs: &mut Vec) -> bool { - if aggs.iter().any(|a| a.original_expr == *expr) { + if aggs + .iter() + .any(|a| exprs_are_equivalent(&a.original_expr, expr)) + { return true; } match expr { diff --git a/core/vdbe/mod.rs b/core/vdbe/mod.rs index 26f125675..0ab196afc 100644 --- a/core/vdbe/mod.rs +++ b/core/vdbe/mod.rs @@ -24,6 +24,8 @@ pub mod sorter; mod datetime; use crate::error::{LimboError, SQLITE_CONSTRAINT_PRIMARYKEY}; +#[cfg(feature = "uuid")] +use crate::ext::{exec_ts_from_uuid7, exec_uuid, exec_uuidblob, exec_uuidstr, ExtFunc, UuidFunc}; use crate::function::{AggFunc, FuncCtx, MathFunc, MathFuncArity, ScalarFunc}; use crate::pseudo::PseudoCursor; use crate::schema::Table; @@ -37,42 +39,23 @@ use crate::util::parse_schema_rows; use crate::{function::JsonFunc, json::get_json, json::json_array}; use crate::{Connection, Result, TransactionState}; use crate::{Rows, DATABASE_VERSION}; +use macros::Description; use datetime::{exec_date, exec_time, exec_unixepoch}; use rand::distributions::{Distribution, Uniform}; use rand::{thread_rng, Rng}; use regex::Regex; -use std::borrow::BorrowMut; +use std::borrow::{Borrow, BorrowMut}; use std::cell::RefCell; use std::collections::{BTreeMap, HashMap}; -use std::fmt::Display; use std::rc::{Rc, Weak}; + pub type BranchOffset = i64; -use macros::Description; pub type CursorID = usize; pub type PageIdx = usize; -#[allow(dead_code)] -#[derive(Debug)] -pub enum Func { - Scalar(ScalarFunc), - #[cfg(feature = "json")] - Json(JsonFunc), -} - -impl Display for Func { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let str = match self { - Func::Scalar(scalar_func) => scalar_func.to_string(), - #[cfg(feature = "json")] - Func::Json(json_func) => json_func.to_string(), - }; - write!(f, "{}", str) - } -} - #[derive(Description, Debug)] pub enum Insn { // Initialize the program state and jump to the given PC. @@ -2539,6 +2522,38 @@ impl Program { state.registers[*dest] = exec_replace(source, pattern, replacement); } }, + crate::function::Func::Extension(extfn) => match extfn { + #[cfg(feature = "uuid")] + ExtFunc::Uuid(uuidfn) => match uuidfn { + UuidFunc::Uuid4 | UuidFunc::Uuid4Str => { + state.registers[*dest] = exec_uuid(uuidfn, None)? + } + UuidFunc::Uuid7 => match arg_count { + 0 => { + state.registers[*dest] = + exec_uuid(uuidfn, None).unwrap_or(OwnedValue::Null); + } + 1 => { + let reg_value = state.registers[*start_reg].borrow(); + state.registers[*dest] = exec_uuid(uuidfn, Some(reg_value)) + .unwrap_or(OwnedValue::Null); + } + _ => unreachable!(), + }, + _ => { + // remaining accept 1 arg + let reg_value = state.registers[*start_reg].borrow(); + state.registers[*dest] = match uuidfn { + UuidFunc::Uuid7TS => Some(exec_ts_from_uuid7(reg_value)), + UuidFunc::UuidStr => exec_uuidstr(reg_value).ok(), + UuidFunc::UuidBlob => exec_uuidblob(reg_value).ok(), + _ => unreachable!(), + } + .unwrap_or(OwnedValue::Null); + } + }, + _ => unreachable!(), // when more extension types are added + }, crate::function::Func::Math(math_func) => match math_func.arity() { MathFuncArity::Nullary => match math_func { MathFunc::Pi => {