From fcab0ae299d6baaaf1120a7a518172aae19d1e36 Mon Sep 17 00:00:00 2001 From: PThorpe92 Date: Thu, 19 Dec 2024 11:45:43 -0500 Subject: [PATCH 1/8] Add uuid support for v4 and v7 --- Cargo.lock | 4 ++ core/Cargo.toml | 4 +- core/function.rs | 18 +++++++++ core/translate/expr.rs | 35 ++++++++++++++++- core/vdbe/mod.rs | 88 +++++++++++++++++++++++++++++++++++++++++- 5 files changed, 145 insertions(+), 4 deletions(-) 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..9051508df 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -14,8 +14,9 @@ name = "limbo_core" path = "lib.rs" [features] -default = ["fs", "json"] +default = ["fs", "json", "uuid"] fs = [] +uuid = ["dep:uuid"] json = [ "dep:jsonb", "dep:pest", @@ -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/function.rs b/core/function.rs index 86c88a1e4..a158c1dce 100644 --- a/core/function.rs +++ b/core/function.rs @@ -91,6 +91,12 @@ pub enum ScalarFunc { ZeroBlob, LastInsertRowid, Replace, + Uuid4, + Uuid4Str, + UuidStr, + UuidBlob, + Uuid7, + Uuid7Str, } impl Display for ScalarFunc { @@ -136,6 +142,12 @@ impl Display for ScalarFunc { ScalarFunc::ZeroBlob => "zeroblob".to_string(), ScalarFunc::LastInsertRowid => "last_insert_rowid".to_string(), ScalarFunc::Replace => "replace".to_string(), + ScalarFunc::Uuid4 => "uuid4".to_string(), + ScalarFunc::UuidStr => "uuid_str".to_string(), + ScalarFunc::UuidBlob => "uuid_blob".to_string(), + ScalarFunc::Uuid7 => "uuid7".to_string(), + ScalarFunc::Uuid7Str => "uuid7_str".to_string(), + ScalarFunc::Uuid4Str => "uuid4_str".to_string(), }; write!(f, "{}", str) } @@ -325,6 +337,12 @@ impl Func { "typeof" => Ok(Func::Scalar(ScalarFunc::Typeof)), "last_insert_rowid" => Ok(Func::Scalar(ScalarFunc::LastInsertRowid)), "unicode" => Ok(Func::Scalar(ScalarFunc::Unicode)), + "uuid4" => Ok(Func::Scalar(ScalarFunc::Uuid4)), + "uuid7" => Ok(Func::Scalar(ScalarFunc::Uuid7)), + "uuid4_str" => Ok(Func::Scalar(ScalarFunc::Uuid4Str)), + "uuid7_str" => Ok(Func::Scalar(ScalarFunc::Uuid7Str)), + "uuid_str" => Ok(Func::Scalar(ScalarFunc::UuidStr)), + "uuid_blob" => Ok(Func::Scalar(ScalarFunc::UuidBlob)), "quote" => Ok(Func::Scalar(ScalarFunc::Quote)), "sqlite_version" => Ok(Func::Scalar(ScalarFunc::SqliteVersion)), "replace" => Ok(Func::Scalar(ScalarFunc::Replace)), diff --git a/core/translate/expr.rs b/core/translate/expr.rs index 363d96a99..9bdd297ac 100644 --- a/core/translate/expr.rs +++ b/core/translate/expr.rs @@ -1194,7 +1194,9 @@ pub fn translate_expr( | ScalarFunc::RandomBlob | ScalarFunc::Sign | ScalarFunc::Soundex - | ScalarFunc::ZeroBlob => { + | ScalarFunc::ZeroBlob + | ScalarFunc::UuidStr + | ScalarFunc::UuidBlob => { let args = if let Some(args) = args { if args.len() != 1 { crate::bail_parse_error!( @@ -1226,7 +1228,36 @@ pub fn translate_expr( }); Ok(target_register) } - ScalarFunc::Random => { + ScalarFunc::Uuid7 | ScalarFunc::Uuid7Str => { + if let Some(args) = args { + // can take optional time arg + if args.len() > 1 { + crate::bail_parse_error!( + "{} function with more than 1 argument", + srf.to_string() + ); + } + if let Some(arg) = args.first() { + let regs = program.alloc_register(); + translate_expr( + program, + referenced_tables, + arg, + regs, + precomputed_exprs_to_registers, + )?; + } + } + 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) + } + ScalarFunc::Random | ScalarFunc::Uuid4 | ScalarFunc::Uuid4Str => { if args.is_some() { crate::bail_parse_error!( "{} function with arguments", diff --git a/core/vdbe/mod.rs b/core/vdbe/mod.rs index 362f60042..b8fc85119 100644 --- a/core/vdbe/mod.rs +++ b/core/vdbe/mod.rs @@ -48,6 +48,8 @@ use std::cell::RefCell; use std::collections::{BTreeMap, HashMap}; use std::fmt::Display; use std::rc::{Rc, Weak}; + +use uuid::{ContextV7, Timestamp, Uuid}; pub type BranchOffset = i64; use macros::Description; pub type CursorID = usize; @@ -2395,7 +2397,9 @@ impl Program { | ScalarFunc::RandomBlob | ScalarFunc::Sign | ScalarFunc::Soundex - | ScalarFunc::ZeroBlob => { + | ScalarFunc::ZeroBlob + | ScalarFunc::UuidStr + | ScalarFunc::UuidBlob => { let reg_value = state.registers[*start_reg].borrow_mut(); let result = match scalar_func { ScalarFunc::Sign => exec_sign(reg_value), @@ -2410,6 +2414,8 @@ impl Program { ScalarFunc::RandomBlob => Some(exec_randomblob(reg_value)), ScalarFunc::ZeroBlob => Some(exec_zeroblob(reg_value)), ScalarFunc::Soundex => Some(exec_soundex(reg_value)), + ScalarFunc::UuidStr => Some(exec_uuidstr(reg_value)?), + ScalarFunc::UuidBlob => Some(exec_uuidblob(reg_value)?), _ => unreachable!(), }; state.registers[*dest] = result.unwrap_or(OwnedValue::Null); @@ -2428,6 +2434,9 @@ impl Program { ScalarFunc::Random => { state.registers[*dest] = exec_random(); } + ScalarFunc::Uuid4 | ScalarFunc::Uuid4Str => { + state.registers[*dest] = exec_uuid(scalar_func); + } ScalarFunc::Trim => { let reg_value = state.registers[*start_reg].clone(); let pattern_value = state.registers.get(*start_reg + 1).cloned(); @@ -3093,6 +3102,83 @@ fn exec_random() -> OwnedValue { OwnedValue::Integer(random_number) } +enum UuidType { + V4Blob, + V4Str, + V7Blob, + V7Str, +} + +fn exec_uuid(var: &ScalarFunc, time: Option<&OwnedValue>) -> OwnedValue { + match var { + ScalarFunc::Uuid4Str => OwnedValue::Text(Rc::new(Uuid::new_v4().to_string())), + ScalarFunc::Uuid4 => OwnedValue::Blob(Rc::new(Uuid::new_v4().into_bytes().to_vec())), + ScalarFunc::Uuid7 | ScalarFunc::Uuid7Str => match time { + Some(OwnedValue::Integer(i)) => { + let ctx = ContextV7::new(); + if *i < 0 { + // not valid unix timestamp + return OwnedValue::Null; + } + let uuid = Uuid::new_v7(Timestamp::from_unix(ctx, *i as u64, 0)); + match var { + ScalarFunc::Uuid7Str => OwnedValue::Text(Rc::new(uuid.to_string())), + ScalarFunc::Uuid7 => OwnedValue::Blob(Rc::new(uuid.as_bytes().to_vec())), + _ => unreachable!(), + } + } + Some(OwnedValue::Text(t)) => { + let uuid = Uuid::new_v7(); + match var { + ScalarFunc::Uuid7Str => OwnedValue::Text(Rc::new(uuid.to_string())), + ScalarFunc::Uuid7 => OwnedValue::Blob(Rc::new(uuid.as_bytes().to_vec())), + _ => unreachable!(), + } + } + _ => match var { + ScalarFunc::Uuid7Str => OwnedValue::Text(Rc::new(Uuid::now_v7().to_string())), + ScalarFunc::Uuid7 => OwnedValue::Blob(Rc::new(Uuid::now_v7().as_bytes().to_vec())), + _ => unreachable!(), + }, + }, + _ => unreachable!(), + } +} + +fn exec_uuidstr(reg: &OwnedValue) -> Result { + match reg { + OwnedValue::Blob(blob) => { + let uuid = Uuid::from_slice(blob).map_err(|e| LimboError::ParseError(e.to_string()))?; + Ok(OwnedValue::Text(Rc::new(uuid.to_string()))) + } + OwnedValue::Text(val) => { + let uuid = Uuid::parse_str(val).map_err(|e| LimboError::ParseError(e.to_string()))?; + Ok(OwnedValue::Text(Rc::new(uuid.to_string()))) + } + OwnedValue::Null => Ok(OwnedValue::Null), + _ => Err(LimboError::ParseError( + "Invalid argument type for UUID function".to_string(), + )), + } +} + +fn exec_uuidblob(reg: &OwnedValue) -> Result { + match reg { + OwnedValue::Text(val) => { + let uuid = Uuid::parse_str(val).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(), + )), + } +} + fn exec_randomblob(reg: &OwnedValue) -> OwnedValue { let length = match reg { OwnedValue::Integer(i) => *i, From b207f7ded516471164c062616f3d898e914fdec8 Mon Sep 17 00:00:00 2001 From: PThorpe92 Date: Thu, 19 Dec 2024 13:13:31 -0500 Subject: [PATCH 2/8] Give uuidv7 optional unix time arg --- core/vdbe/mod.rs | 65 +++++++++++++++++++++++++++--------------------- 1 file changed, 37 insertions(+), 28 deletions(-) diff --git a/core/vdbe/mod.rs b/core/vdbe/mod.rs index b8fc85119..517b839ed 100644 --- a/core/vdbe/mod.rs +++ b/core/vdbe/mod.rs @@ -2420,6 +2420,24 @@ impl Program { }; state.registers[*dest] = result.unwrap_or(OwnedValue::Null); } + ScalarFunc::Uuid7Str | ScalarFunc::Uuid7 => match arg_count { + 0 => { + state.registers[*dest] = + exec_uuid(scalar_func, None).unwrap_or(OwnedValue::Null); + } + 1 => { + let reg_value = state.registers[*start_reg].borrow_mut(); + state.registers[*dest] = + exec_uuid(scalar_func, Some(reg_value)) + .unwrap_or(OwnedValue::Null); + } + _ => { + return Err(LimboError::ParseError(format!( + "Invalid number of arguments for Uuid7 function: {}", + arg_count + ))); + } + }, ScalarFunc::Hex => { let reg_value = state.registers[*start_reg].borrow_mut(); let result = exec_hex(reg_value); @@ -2435,7 +2453,7 @@ impl Program { state.registers[*dest] = exec_random(); } ScalarFunc::Uuid4 | ScalarFunc::Uuid4Str => { - state.registers[*dest] = exec_uuid(scalar_func); + state.registers[*dest] = exec_uuid(scalar_func, None)?; } ScalarFunc::Trim => { let reg_value = state.registers[*start_reg].clone(); @@ -3109,38 +3127,29 @@ enum UuidType { V7Str, } -fn exec_uuid(var: &ScalarFunc, time: Option<&OwnedValue>) -> OwnedValue { +fn exec_uuid(var: &ScalarFunc, time: Option<&OwnedValue>) -> Result { match var { - ScalarFunc::Uuid4Str => OwnedValue::Text(Rc::new(Uuid::new_v4().to_string())), - ScalarFunc::Uuid4 => OwnedValue::Blob(Rc::new(Uuid::new_v4().into_bytes().to_vec())), - ScalarFunc::Uuid7 | ScalarFunc::Uuid7Str => match time { - Some(OwnedValue::Integer(i)) => { + ScalarFunc::Uuid4Str => Ok(OwnedValue::Text(Rc::new(Uuid::new_v4().to_string()))), + ScalarFunc::Uuid4 => Ok(OwnedValue::Blob(Rc::new( + Uuid::new_v4().into_bytes().to_vec(), + ))), + ScalarFunc::Uuid7 | ScalarFunc::Uuid7Str => { + let uuid = if let Some(OwnedValue::Integer(ref i)) = time { let ctx = ContextV7::new(); if *i < 0 { - // not valid unix timestamp - return OwnedValue::Null; + // not valid unix timestamp, error or null? + return Ok(OwnedValue::Null); } - let uuid = Uuid::new_v7(Timestamp::from_unix(ctx, *i as u64, 0)); - match var { - ScalarFunc::Uuid7Str => OwnedValue::Text(Rc::new(uuid.to_string())), - ScalarFunc::Uuid7 => OwnedValue::Blob(Rc::new(uuid.as_bytes().to_vec())), - _ => unreachable!(), - } - } - Some(OwnedValue::Text(t)) => { - let uuid = Uuid::new_v7(); - match var { - ScalarFunc::Uuid7Str => OwnedValue::Text(Rc::new(uuid.to_string())), - ScalarFunc::Uuid7 => OwnedValue::Blob(Rc::new(uuid.as_bytes().to_vec())), - _ => unreachable!(), - } - } - _ => match var { - ScalarFunc::Uuid7Str => OwnedValue::Text(Rc::new(Uuid::now_v7().to_string())), - ScalarFunc::Uuid7 => OwnedValue::Blob(Rc::new(Uuid::now_v7().as_bytes().to_vec())), + Uuid::new_v7(Timestamp::from_unix(ctx, *i as u64, 0)) + } else { + Uuid::now_v7() + }; + return match var { + ScalarFunc::Uuid7Str => Ok(OwnedValue::Text(Rc::new(uuid.to_string()))), + ScalarFunc::Uuid7 => Ok(OwnedValue::Blob(Rc::new(uuid.into_bytes().to_vec()))), _ => unreachable!(), - }, - }, + }; + } _ => unreachable!(), } } From c1561ecbb0d705bdeb913fbe3577b4ba3a9a1b2d Mon Sep 17 00:00:00 2001 From: PThorpe92 Date: Thu, 19 Dec 2024 20:21:33 -0500 Subject: [PATCH 3/8] Tests for uuid funcitons, add compat docs --- COMPAT.md | 13 ++ core/function.rs | 10 +- core/translate/expr.rs | 45 +++---- core/vdbe/mod.rs | 280 ++++++++++++++++++++++++++++++++++++----- 4 files changed, 289 insertions(+), 59 deletions(-) diff --git a/COMPAT.md b/COMPAT.md index 213841cb1..38d475680 100644 --- a/COMPAT.md +++ b/COMPAT.md @@ -160,6 +160,19 @@ Feature support of [sqlite expr syntax](https://www.sqlite.org/lang_expr.html). | upper(X) | Yes | | | zeroblob(N) | Yes | | + +|-------------------------------------------------| +| LibSql / sqlean Scalar | | | +| ---------------------------- | ------ | ------- | +| uuid4() | Yes | uuid version 4 **uuid's are `blob` by default** | +| 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| + + + ### Mathematical functions | Function | Status | Comment | diff --git a/core/function.rs b/core/function.rs index a158c1dce..b17d388bb 100644 --- a/core/function.rs +++ b/core/function.rs @@ -96,7 +96,7 @@ pub enum ScalarFunc { UuidStr, UuidBlob, Uuid7, - Uuid7Str, + Uuid7TS, } impl Display for ScalarFunc { @@ -146,8 +146,8 @@ impl Display for ScalarFunc { ScalarFunc::UuidStr => "uuid_str".to_string(), ScalarFunc::UuidBlob => "uuid_blob".to_string(), ScalarFunc::Uuid7 => "uuid7".to_string(), - ScalarFunc::Uuid7Str => "uuid7_str".to_string(), ScalarFunc::Uuid4Str => "uuid4_str".to_string(), + ScalarFunc::Uuid7TS => "uuid7_timestamp_ms".to_string(), }; write!(f, "{}", str) } @@ -337,12 +337,14 @@ impl Func { "typeof" => Ok(Func::Scalar(ScalarFunc::Typeof)), "last_insert_rowid" => Ok(Func::Scalar(ScalarFunc::LastInsertRowid)), "unicode" => Ok(Func::Scalar(ScalarFunc::Unicode)), + "uuid4_str" => Ok(Func::Scalar(ScalarFunc::Uuid4Str)), "uuid4" => Ok(Func::Scalar(ScalarFunc::Uuid4)), "uuid7" => Ok(Func::Scalar(ScalarFunc::Uuid7)), - "uuid4_str" => Ok(Func::Scalar(ScalarFunc::Uuid4Str)), - "uuid7_str" => Ok(Func::Scalar(ScalarFunc::Uuid7Str)), "uuid_str" => Ok(Func::Scalar(ScalarFunc::UuidStr)), "uuid_blob" => Ok(Func::Scalar(ScalarFunc::UuidBlob)), + "uuid7_timestamp_ms" => Ok(Func::Scalar(ScalarFunc::Uuid7TS)), + // postgres_compatability + "gen_random_uuid" => Ok(Func::Scalar(ScalarFunc::Uuid4Str)), "quote" => Ok(Func::Scalar(ScalarFunc::Quote)), "sqlite_version" => Ok(Func::Scalar(ScalarFunc::SqliteVersion)), "replace" => Ok(Func::Scalar(ScalarFunc::Replace)), diff --git a/core/translate/expr.rs b/core/translate/expr.rs index 9bdd297ac..25c986739 100644 --- a/core/translate/expr.rs +++ b/core/translate/expr.rs @@ -1196,7 +1196,8 @@ pub fn translate_expr( | ScalarFunc::Soundex | ScalarFunc::ZeroBlob | ScalarFunc::UuidStr - | ScalarFunc::UuidBlob => { + | ScalarFunc::UuidBlob + | ScalarFunc::Uuid7TS => { let args = if let Some(args) = args { if args.len() != 1 { crate::bail_parse_error!( @@ -1228,30 +1229,30 @@ pub fn translate_expr( }); Ok(target_register) } - ScalarFunc::Uuid7 | ScalarFunc::Uuid7Str => { - if let Some(args) = args { - // can take optional time arg - if args.len() > 1 { - crate::bail_parse_error!( - "{} function with more than 1 argument", - srf.to_string() - ); - } - if let Some(arg) = args.first() { - let regs = program.alloc_register(); - translate_expr( - program, - referenced_tables, - arg, - regs, - precomputed_exprs_to_registers, - )?; - } + ScalarFunc::Uuid7 => { + let args = match args { + Some(args) if args.len() > 3 => crate::bail_parse_error!( + "{} function with more than 2 arguments", + srf.to_string() + ), + Some(args) => args, + None => &vec![], + }; + let mut start_reg = None; + for arg in args.iter() { + let reg = program.alloc_register(); + start_reg = Some(start_reg.unwrap_or(reg)); + translate_expr( + program, + referenced_tables, + arg, + reg, + precomputed_exprs_to_registers, + )?; } - let regs = program.alloc_register(); program.emit_insn(Insn::Function { constant_mask: 0, - start_reg: regs, + start_reg: start_reg.unwrap_or(target_register), dest: target_register, func: func_ctx, }); diff --git a/core/vdbe/mod.rs b/core/vdbe/mod.rs index 517b839ed..68cfa264f 100644 --- a/core/vdbe/mod.rs +++ b/core/vdbe/mod.rs @@ -43,7 +43,7 @@ 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; @@ -2399,7 +2399,8 @@ impl Program { | ScalarFunc::Soundex | ScalarFunc::ZeroBlob | ScalarFunc::UuidStr - | ScalarFunc::UuidBlob => { + | ScalarFunc::UuidBlob + | ScalarFunc::Uuid7TS => { let reg_value = state.registers[*start_reg].borrow_mut(); let result = match scalar_func { ScalarFunc::Sign => exec_sign(reg_value), @@ -2416,27 +2417,23 @@ impl Program { ScalarFunc::Soundex => Some(exec_soundex(reg_value)), ScalarFunc::UuidStr => Some(exec_uuidstr(reg_value)?), ScalarFunc::UuidBlob => Some(exec_uuidblob(reg_value)?), + ScalarFunc::Uuid7TS => Some(exec_ts_from_uuid7(reg_value)), _ => unreachable!(), }; state.registers[*dest] = result.unwrap_or(OwnedValue::Null); } - ScalarFunc::Uuid7Str | ScalarFunc::Uuid7 => match arg_count { + ScalarFunc::Uuid7 => match arg_count { 0 => { state.registers[*dest] = exec_uuid(scalar_func, None).unwrap_or(OwnedValue::Null); } 1 => { - let reg_value = state.registers[*start_reg].borrow_mut(); + let reg_value = state.registers[*start_reg].borrow(); state.registers[*dest] = exec_uuid(scalar_func, Some(reg_value)) .unwrap_or(OwnedValue::Null); } - _ => { - return Err(LimboError::ParseError(format!( - "Invalid number of arguments for Uuid7 function: {}", - arg_count - ))); - } + _ => unreachable!(), }, ScalarFunc::Hex => { let reg_value = state.registers[*start_reg].borrow_mut(); @@ -3120,35 +3117,25 @@ fn exec_random() -> OwnedValue { OwnedValue::Integer(random_number) } -enum UuidType { - V4Blob, - V4Str, - V7Blob, - V7Str, -} - -fn exec_uuid(var: &ScalarFunc, time: Option<&OwnedValue>) -> Result { +fn exec_uuid(var: &ScalarFunc, sec: Option<&OwnedValue>) -> Result { match var { - ScalarFunc::Uuid4Str => Ok(OwnedValue::Text(Rc::new(Uuid::new_v4().to_string()))), ScalarFunc::Uuid4 => Ok(OwnedValue::Blob(Rc::new( Uuid::new_v4().into_bytes().to_vec(), ))), - ScalarFunc::Uuid7 | ScalarFunc::Uuid7Str => { - let uuid = if let Some(OwnedValue::Integer(ref i)) = time { - let ctx = ContextV7::new(); - if *i < 0 { - // not valid unix timestamp, error or null? - return Ok(OwnedValue::Null); + ScalarFunc::Uuid4Str => Ok(OwnedValue::Text(Rc::new(Uuid::new_v4().to_string()))), + ScalarFunc::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::new_v7(Timestamp::from_unix(ctx, *i as u64, 0)) - } else { - Uuid::now_v7() - }; - return match var { - ScalarFunc::Uuid7Str => Ok(OwnedValue::Text(Rc::new(uuid.to_string()))), - ScalarFunc::Uuid7 => Ok(OwnedValue::Blob(Rc::new(uuid.into_bytes().to_vec()))), - _ => unreachable!(), + _ => Uuid::now_v7(), }; + Ok(OwnedValue::Blob(Rc::new(uuid.into_bytes().to_vec()))) } _ => unreachable!(), } @@ -3188,6 +3175,35 @@ fn exec_uuidblob(reg: &OwnedValue) -> Result { } } +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).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) +} + fn exec_randomblob(reg: &OwnedValue) -> OwnedValue { let length = match reg { OwnedValue::Integer(i) => *i, @@ -4954,4 +4970,202 @@ mod tests { expected_str ); } + + #[test] + fn test_exec_uuid_v4blob() { + use super::{exec_uuid, ScalarFunc}; + use uuid::Uuid; + let func = ScalarFunc::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, ScalarFunc}; + use uuid::Uuid; + let func = ScalarFunc::Uuid4Str; + let owned_val = exec_uuid(&func, None); + match owned_val { + Ok(OwnedValue::Text(v4str)) => { + assert_eq!(v4str.len(), 36); + let uuid = Uuid::parse_str(&v4str); + 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, ScalarFunc}; + use uuid::Uuid; + let func = ScalarFunc::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, ScalarFunc}; + use uuid::Uuid; + let func = ScalarFunc::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, ScalarFunc}; + use uuid::Uuid; + let func = ScalarFunc::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, ScalarFunc}; + use uuid::Uuid; + let func = ScalarFunc::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, ScalarFunc}; + use uuid::Uuid; + let owned_val = exec_uuidblob( + &exec_uuid(&ScalarFunc::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, ScalarFunc}; + use uuid::Uuid; + // convert a v7 blob to a string then back to a blob + let owned_val = exec_uuidblob( + &exec_uuidstr(&exec_uuid(&ScalarFunc::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, ScalarFunc}; + use uuid::Uuid; + // convert a v4 blob to a string + let owned_val = + exec_uuidstr(&exec_uuid(&ScalarFunc::Uuid4, None).expect("uuid v7 blob to generate")); + match owned_val { + Ok(OwnedValue::Text(v4str)) => { + assert_eq!(v4str.len(), 36); + let uuid = Uuid::parse_str(&v4str); + 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, ScalarFunc}; + use uuid::Uuid; + // convert a v7 blob to a string + let owned_val = exec_uuidstr( + &exec_uuid(&ScalarFunc::Uuid7, Some(&OwnedValue::Integer(123456789))) + .expect("uuid v7 blob to generate"), + ); + match owned_val { + Ok(OwnedValue::Text(v7str)) => { + assert_eq!(v7str.len(), 36); + let uuid = Uuid::parse_str(&v7str); + assert!(uuid.is_ok()); + assert_eq!(uuid.unwrap().get_version_num(), 7); + } + _ => panic!("exec_uuid did not return a Blob variant"), + } + } } From f96f2896097356fcfaba80dc82d7da13fc72c82b Mon Sep 17 00:00:00 2001 From: PThorpe92 Date: Thu, 19 Dec 2024 20:25:52 -0500 Subject: [PATCH 4/8] Remove unnecessary nanos arg from uuid7, add insn const --- COMPAT.md | 22 +++++++++++++--------- core/translate/expr.rs | 11 +++++++---- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/COMPAT.md b/COMPAT.md index 38d475680..a7baaca83 100644 --- a/COMPAT.md +++ b/COMPAT.md @@ -161,15 +161,6 @@ Feature support of [sqlite expr syntax](https://www.sqlite.org/lang_expr.html). | zeroblob(N) | Yes | | -|-------------------------------------------------| -| LibSql / sqlean Scalar | | | -| ---------------------------- | ------ | ------- | -| uuid4() | Yes | uuid version 4 **uuid's are `blob` by default** | -| 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| @@ -462,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/core/translate/expr.rs b/core/translate/expr.rs index 25c986739..ea81d9ead 100644 --- a/core/translate/expr.rs +++ b/core/translate/expr.rs @@ -1231,17 +1231,17 @@ pub fn translate_expr( } ScalarFunc::Uuid7 => { let args = match args { - Some(args) if args.len() > 3 => crate::bail_parse_error!( - "{} function with more than 2 arguments", + Some(args) if args.len() > 1 => crate::bail_parse_error!( + "{} function with more than 1 argument", srf.to_string() ), Some(args) => args, None => &vec![], }; let mut start_reg = None; - for arg in args.iter() { + if let Some(arg) = args.first() { let reg = program.alloc_register(); - start_reg = Some(start_reg.unwrap_or(reg)); + start_reg = Some(reg); translate_expr( program, referenced_tables, @@ -1249,6 +1249,9 @@ pub fn translate_expr( reg, precomputed_exprs_to_registers, )?; + if let ast::Expr::Literal(_) = arg { + program.mark_last_insn_constant() + } } program.emit_insn(Insn::Function { constant_mask: 0, From 2fcae80902bfe9af004548311e690702b7b21c6f Mon Sep 17 00:00:00 2001 From: PThorpe92 Date: Fri, 20 Dec 2024 15:17:50 -0500 Subject: [PATCH 5/8] Create ext directory for outside funcs, add uuid to ext dir --- core/Cargo.toml | 1 + core/ext/mod.rs | 30 ++++ core/ext/uuid.rs | 334 +++++++++++++++++++++++++++++++++++++++ core/function.rs | 31 +--- core/lib.rs | 1 + core/translate/expr.rs | 127 ++++++++++----- core/vdbe/mod.rs | 347 +++++------------------------------------ 7 files changed, 501 insertions(+), 370 deletions(-) create mode 100644 core/ext/mod.rs create mode 100644 core/ext/uuid.rs diff --git a/core/Cargo.toml b/core/Cargo.toml index 9051508df..25a0c6c90 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -22,6 +22,7 @@ json = [ "dep:pest", "dep:pest_derive", ] +uuid = ["dep:uuid"] [target.'cfg(target_os = "linux")'.dependencies] io-uring = "0.6.1" diff --git a/core/ext/mod.rs b/core/ext/mod.rs new file mode 100644 index 000000000..fea543869 --- /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) -> Result { + match name { + #[cfg(feature = "uuid")] + name => UuidFunc::resolve_function(name, num_args), + _ => Err(()), + } + } +} diff --git a/core/ext/uuid.rs b/core/ext/uuid.rs new file mode 100644 index 000000000..aa717c13d --- /dev/null +++ b/core/ext/uuid.rs @@ -0,0 +1,334 @@ +use super::ExtFunc; +use crate::{types::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) -> Result { + match name { + "uuid4_str" => Ok(ExtFunc::Uuid(UuidFunc::Uuid4Str)), + "uuid4" => Ok(ExtFunc::Uuid(UuidFunc::Uuid4)), + "uuid7" if num_args < 2 => Ok(ExtFunc::Uuid(UuidFunc::Uuid7)), + "uuid_str" if num_args == 1 => Ok(ExtFunc::Uuid(UuidFunc::UuidStr)), + "uuid_blob" if num_args == 1 => Ok(ExtFunc::Uuid(UuidFunc::UuidBlob)), + "uuid7_timestamp_ms" if num_args == 1 => Ok(ExtFunc::Uuid(UuidFunc::Uuid7TS)), + // postgres_compatability + "gen_random_uuid" => Ok(ExtFunc::Uuid(UuidFunc::Uuid4Str)), + _ => Err(()), + } + } +} + +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(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(Rc::new(uuid.to_string()))) + } + OwnedValue::Text(val) => { + let uuid = Uuid::parse_str(val).map_err(|e| LimboError::ParseError(e.to_string()))?; + Ok(OwnedValue::Text(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).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).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.len(), 36); + let uuid = Uuid::parse_str(&v4str); + 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.len(), 36); + let uuid = Uuid::parse_str(&v4str); + 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.len(), 36); + let uuid = Uuid::parse_str(&v7str); + 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 b17d388bb..7d97432ca 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 { @@ -91,12 +91,6 @@ pub enum ScalarFunc { ZeroBlob, LastInsertRowid, Replace, - Uuid4, - Uuid4Str, - UuidStr, - UuidBlob, - Uuid7, - Uuid7TS, } impl Display for ScalarFunc { @@ -142,12 +136,6 @@ impl Display for ScalarFunc { ScalarFunc::ZeroBlob => "zeroblob".to_string(), ScalarFunc::LastInsertRowid => "last_insert_rowid".to_string(), ScalarFunc::Replace => "replace".to_string(), - ScalarFunc::Uuid4 => "uuid4".to_string(), - ScalarFunc::UuidStr => "uuid_str".to_string(), - ScalarFunc::UuidBlob => "uuid_blob".to_string(), - ScalarFunc::Uuid7 => "uuid7".to_string(), - ScalarFunc::Uuid4Str => "uuid4_str".to_string(), - ScalarFunc::Uuid7TS => "uuid7_timestamp_ms".to_string(), }; write!(f, "{}", str) } @@ -268,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), + Extention(ExtFunc), } impl Display for Func { @@ -285,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::Extention(ext_func) => write!(f, "{}", ext_func), } } } @@ -337,14 +327,6 @@ impl Func { "typeof" => Ok(Func::Scalar(ScalarFunc::Typeof)), "last_insert_rowid" => Ok(Func::Scalar(ScalarFunc::LastInsertRowid)), "unicode" => Ok(Func::Scalar(ScalarFunc::Unicode)), - "uuid4_str" => Ok(Func::Scalar(ScalarFunc::Uuid4Str)), - "uuid4" => Ok(Func::Scalar(ScalarFunc::Uuid4)), - "uuid7" => Ok(Func::Scalar(ScalarFunc::Uuid7)), - "uuid_str" => Ok(Func::Scalar(ScalarFunc::UuidStr)), - "uuid_blob" => Ok(Func::Scalar(ScalarFunc::UuidBlob)), - "uuid7_timestamp_ms" => Ok(Func::Scalar(ScalarFunc::Uuid7TS)), - // postgres_compatability - "gen_random_uuid" => Ok(Func::Scalar(ScalarFunc::Uuid4Str)), "quote" => Ok(Func::Scalar(ScalarFunc::Quote)), "sqlite_version" => Ok(Func::Scalar(ScalarFunc::SqliteVersion)), "replace" => Ok(Func::Scalar(ScalarFunc::Replace)), @@ -386,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) { + Ok(ext_func) => Ok(Func::Extention(ext_func)), + Err(_) => Err(()), + }, } } } diff --git a/core/lib.rs b/core/lib.rs index 1f5668d76..79e06abfb 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 ea81d9ead..523be6e51 100644 --- a/core/translate/expr.rs +++ b/core/translate/expr.rs @@ -1,5 +1,7 @@ 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}; @@ -1194,10 +1196,7 @@ pub fn translate_expr( | ScalarFunc::RandomBlob | ScalarFunc::Sign | ScalarFunc::Soundex - | ScalarFunc::ZeroBlob - | ScalarFunc::UuidStr - | ScalarFunc::UuidBlob - | ScalarFunc::Uuid7TS => { + | ScalarFunc::ZeroBlob => { let args = if let Some(args) = args { if args.len() != 1 { crate::bail_parse_error!( @@ -1229,39 +1228,7 @@ pub fn translate_expr( }); Ok(target_register) } - ScalarFunc::Uuid7 => { - let args = match args { - Some(args) if args.len() > 1 => crate::bail_parse_error!( - "{} function with more than 1 argument", - srf.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) - } - ScalarFunc::Random | ScalarFunc::Uuid4 | ScalarFunc::Uuid4Str => { + ScalarFunc::Random => { if args.is_some() { crate::bail_parse_error!( "{} function with arguments", @@ -1648,6 +1615,92 @@ pub fn translate_expr( } } } + Func::Extention(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/vdbe/mod.rs b/core/vdbe/mod.rs index 68cfa264f..b5eb9d60f 100644 --- a/core/vdbe/mod.rs +++ b/core/vdbe/mod.rs @@ -24,7 +24,9 @@ pub mod sorter; mod datetime; use crate::error::{LimboError, SQLITE_CONSTRAINT_PRIMARYKEY}; -use crate::function::{AggFunc, FuncCtx, MathFunc, MathFuncArity, ScalarFunc}; +#[cfg(feature = "uuid")] +use crate::ext::{exec_ts_from_uuid7, exec_uuid, exec_uuidblob, exec_uuidstr, ExtFunc, UuidFunc}; +use crate::function::{AggFunc, Func, FuncCtx, MathFunc, MathFuncArity, ScalarFunc}; use crate::pseudo::PseudoCursor; use crate::schema::Table; use crate::storage::sqlite3_ondisk::DatabaseHeader; @@ -46,10 +48,10 @@ use regex::Regex; use std::borrow::{Borrow, BorrowMut}; use std::cell::RefCell; use std::collections::{BTreeMap, HashMap}; -use std::fmt::Display; use std::rc::{Rc, Weak}; use uuid::{ContextV7, Timestamp, Uuid}; + pub type BranchOffset = i64; use macros::Description; pub type CursorID = usize; @@ -2397,10 +2399,7 @@ impl Program { | ScalarFunc::RandomBlob | ScalarFunc::Sign | ScalarFunc::Soundex - | ScalarFunc::ZeroBlob - | ScalarFunc::UuidStr - | ScalarFunc::UuidBlob - | ScalarFunc::Uuid7TS => { + | ScalarFunc::ZeroBlob => { let reg_value = state.registers[*start_reg].borrow_mut(); let result = match scalar_func { ScalarFunc::Sign => exec_sign(reg_value), @@ -2415,26 +2414,10 @@ impl Program { ScalarFunc::RandomBlob => Some(exec_randomblob(reg_value)), ScalarFunc::ZeroBlob => Some(exec_zeroblob(reg_value)), ScalarFunc::Soundex => Some(exec_soundex(reg_value)), - ScalarFunc::UuidStr => Some(exec_uuidstr(reg_value)?), - ScalarFunc::UuidBlob => Some(exec_uuidblob(reg_value)?), - ScalarFunc::Uuid7TS => Some(exec_ts_from_uuid7(reg_value)), _ => unreachable!(), }; state.registers[*dest] = result.unwrap_or(OwnedValue::Null); } - ScalarFunc::Uuid7 => match arg_count { - 0 => { - state.registers[*dest] = - exec_uuid(scalar_func, None).unwrap_or(OwnedValue::Null); - } - 1 => { - let reg_value = state.registers[*start_reg].borrow(); - state.registers[*dest] = - exec_uuid(scalar_func, Some(reg_value)) - .unwrap_or(OwnedValue::Null); - } - _ => unreachable!(), - }, ScalarFunc::Hex => { let reg_value = state.registers[*start_reg].borrow_mut(); let result = exec_hex(reg_value); @@ -2449,9 +2432,6 @@ impl Program { ScalarFunc::Random => { state.registers[*dest] = exec_random(); } - ScalarFunc::Uuid4 | ScalarFunc::Uuid4Str => { - state.registers[*dest] = exec_uuid(scalar_func, None)?; - } ScalarFunc::Trim => { let reg_value = state.registers[*start_reg].clone(); let pattern_value = state.registers.get(*start_reg + 1).cloned(); @@ -2555,6 +2535,38 @@ impl Program { state.registers[*dest] = exec_replace(source, pattern, replacement); } }, + Func::Extention(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 => { @@ -3117,93 +3129,6 @@ fn exec_random() -> OwnedValue { OwnedValue::Integer(random_number) } -fn exec_uuid(var: &ScalarFunc, sec: Option<&OwnedValue>) -> Result { - match var { - ScalarFunc::Uuid4 => Ok(OwnedValue::Blob(Rc::new( - Uuid::new_v4().into_bytes().to_vec(), - ))), - ScalarFunc::Uuid4Str => Ok(OwnedValue::Text(Rc::new(Uuid::new_v4().to_string()))), - ScalarFunc::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!(), - } -} - -fn exec_uuidstr(reg: &OwnedValue) -> Result { - match reg { - OwnedValue::Blob(blob) => { - let uuid = Uuid::from_slice(blob).map_err(|e| LimboError::ParseError(e.to_string()))?; - Ok(OwnedValue::Text(Rc::new(uuid.to_string()))) - } - OwnedValue::Text(val) => { - let uuid = Uuid::parse_str(val).map_err(|e| LimboError::ParseError(e.to_string()))?; - Ok(OwnedValue::Text(Rc::new(uuid.to_string()))) - } - OwnedValue::Null => Ok(OwnedValue::Null), - _ => Err(LimboError::ParseError( - "Invalid argument type for UUID function".to_string(), - )), - } -} - -fn exec_uuidblob(reg: &OwnedValue) -> Result { - match reg { - OwnedValue::Text(val) => { - let uuid = Uuid::parse_str(val).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(), - )), - } -} - -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).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) -} - fn exec_randomblob(reg: &OwnedValue) -> OwnedValue { let length = match reg { OwnedValue::Integer(i) => *i, @@ -4970,202 +4895,4 @@ mod tests { expected_str ); } - - #[test] - fn test_exec_uuid_v4blob() { - use super::{exec_uuid, ScalarFunc}; - use uuid::Uuid; - let func = ScalarFunc::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, ScalarFunc}; - use uuid::Uuid; - let func = ScalarFunc::Uuid4Str; - let owned_val = exec_uuid(&func, None); - match owned_val { - Ok(OwnedValue::Text(v4str)) => { - assert_eq!(v4str.len(), 36); - let uuid = Uuid::parse_str(&v4str); - 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, ScalarFunc}; - use uuid::Uuid; - let func = ScalarFunc::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, ScalarFunc}; - use uuid::Uuid; - let func = ScalarFunc::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, ScalarFunc}; - use uuid::Uuid; - let func = ScalarFunc::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, ScalarFunc}; - use uuid::Uuid; - let func = ScalarFunc::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, ScalarFunc}; - use uuid::Uuid; - let owned_val = exec_uuidblob( - &exec_uuid(&ScalarFunc::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, ScalarFunc}; - use uuid::Uuid; - // convert a v7 blob to a string then back to a blob - let owned_val = exec_uuidblob( - &exec_uuidstr(&exec_uuid(&ScalarFunc::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, ScalarFunc}; - use uuid::Uuid; - // convert a v4 blob to a string - let owned_val = - exec_uuidstr(&exec_uuid(&ScalarFunc::Uuid4, None).expect("uuid v7 blob to generate")); - match owned_val { - Ok(OwnedValue::Text(v4str)) => { - assert_eq!(v4str.len(), 36); - let uuid = Uuid::parse_str(&v4str); - 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, ScalarFunc}; - use uuid::Uuid; - // convert a v7 blob to a string - let owned_val = exec_uuidstr( - &exec_uuid(&ScalarFunc::Uuid7, Some(&OwnedValue::Integer(123456789))) - .expect("uuid v7 blob to generate"), - ); - match owned_val { - Ok(OwnedValue::Text(v7str)) => { - assert_eq!(v7str.len(), 36); - let uuid = Uuid::parse_str(&v7str); - assert!(uuid.is_ok()); - assert_eq!(uuid.unwrap().get_version_num(), 7); - } - _ => panic!("exec_uuid did not return a Blob variant"), - } - } } From c06c4115f1e5ec6ed59f1f0daddf296847b7fb71 Mon Sep 17 00:00:00 2001 From: PThorpe92 Date: Fri, 20 Dec 2024 16:03:16 -0500 Subject: [PATCH 6/8] Adapt OwnedValues in uuid ext to new LimboText --- core/Cargo.toml | 1 - core/ext/mod.rs | 4 ++-- core/ext/uuid.rs | 53 ++++++++++++++++++++++++------------------ core/function.rs | 8 +++---- core/translate/expr.rs | 2 +- core/vdbe/mod.rs | 27 +++------------------ 6 files changed, 40 insertions(+), 55 deletions(-) diff --git a/core/Cargo.toml b/core/Cargo.toml index 25a0c6c90..4ef87b469 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -16,7 +16,6 @@ path = "lib.rs" [features] default = ["fs", "json", "uuid"] fs = [] -uuid = ["dep:uuid"] json = [ "dep:jsonb", "dep:pest", diff --git a/core/ext/mod.rs b/core/ext/mod.rs index fea543869..312ebfcea 100644 --- a/core/ext/mod.rs +++ b/core/ext/mod.rs @@ -20,11 +20,11 @@ impl std::fmt::Display for ExtFunc { } impl ExtFunc { - pub fn resolve_function(name: &str, num_args: usize) -> Result { + pub fn resolve_function(name: &str, num_args: usize) -> Option { match name { #[cfg(feature = "uuid")] name => UuidFunc::resolve_function(name, num_args), - _ => Err(()), + _ => None, } } } diff --git a/core/ext/uuid.rs b/core/ext/uuid.rs index aa717c13d..00ce23d9b 100644 --- a/core/ext/uuid.rs +++ b/core/ext/uuid.rs @@ -1,5 +1,8 @@ use super::ExtFunc; -use crate::{types::OwnedValue, LimboError}; +use crate::{ + types::{LimboText, OwnedValue}, + LimboError, +}; use std::rc::Rc; use uuid::{ContextV7, Timestamp, Uuid}; @@ -14,17 +17,17 @@ pub enum UuidFunc { } impl UuidFunc { - pub fn resolve_function(name: &str, num_args: usize) -> Result { + pub fn resolve_function(name: &str, num_args: usize) -> Option { match name { - "uuid4_str" => Ok(ExtFunc::Uuid(UuidFunc::Uuid4Str)), - "uuid4" => Ok(ExtFunc::Uuid(UuidFunc::Uuid4)), - "uuid7" if num_args < 2 => Ok(ExtFunc::Uuid(UuidFunc::Uuid7)), - "uuid_str" if num_args == 1 => Ok(ExtFunc::Uuid(UuidFunc::UuidStr)), - "uuid_blob" if num_args == 1 => Ok(ExtFunc::Uuid(UuidFunc::UuidBlob)), - "uuid7_timestamp_ms" if num_args == 1 => Ok(ExtFunc::Uuid(UuidFunc::Uuid7TS)), + "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" => Ok(ExtFunc::Uuid(UuidFunc::Uuid4Str)), - _ => Err(()), + "gen_random_uuid" => Some(ExtFunc::Uuid(UuidFunc::Uuid4Str)), + _ => None, } } } @@ -47,7 +50,9 @@ pub fn exec_uuid(var: &UuidFunc, sec: Option<&OwnedValue>) -> crate::Result Ok(OwnedValue::Blob(Rc::new( Uuid::new_v4().into_bytes().to_vec(), ))), - UuidFunc::Uuid4Str => Ok(OwnedValue::Text(Rc::new(Uuid::new_v4().to_string()))), + 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)) => { @@ -70,11 +75,12 @@ 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(Rc::new(uuid.to_string()))) + Ok(OwnedValue::Text(LimboText::new(Rc::new(uuid.to_string())))) } - OwnedValue::Text(val) => { - let uuid = Uuid::parse_str(val).map_err(|e| LimboError::ParseError(e.to_string()))?; - Ok(OwnedValue::Text(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( @@ -86,7 +92,8 @@ pub fn exec_uuidstr(reg: &OwnedValue) -> crate::Result { pub fn exec_uuidblob(reg: &OwnedValue) -> crate::Result { match reg { OwnedValue::Text(val) => { - let uuid = Uuid::parse_str(val).map_err(|e| LimboError::ParseError(e.to_string()))?; + 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) => { @@ -106,7 +113,7 @@ pub fn exec_ts_from_uuid7(reg: &OwnedValue) -> OwnedValue { Uuid::from_slice(blob).map_err(|e| LimboError::ParseError(e.to_string())) } OwnedValue::Text(val) => { - Uuid::parse_str(val).map_err(|e| LimboError::ParseError(e.to_string())) + Uuid::parse_str(&val.value).map_err(|e| LimboError::ParseError(e.to_string())) } _ => Err(LimboError::ParseError( "Invalid argument type for UUID function".to_string(), @@ -159,8 +166,8 @@ pub mod test { let owned_val = exec_uuid(&func, None); match owned_val { Ok(OwnedValue::Text(v4str)) => { - assert_eq!(v4str.len(), 36); - let uuid = Uuid::parse_str(&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); } @@ -303,8 +310,8 @@ pub mod test { exec_uuidstr(&exec_uuid(&UuidFunc::Uuid4, None).expect("uuid v7 blob to generate")); match owned_val { Ok(OwnedValue::Text(v4str)) => { - assert_eq!(v4str.len(), 36); - let uuid = Uuid::parse_str(&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); } @@ -323,8 +330,8 @@ pub mod test { ); match owned_val { Ok(OwnedValue::Text(v7str)) => { - assert_eq!(v7str.len(), 36); - let uuid = Uuid::parse_str(&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); } diff --git a/core/function.rs b/core/function.rs index 7d97432ca..8681a4fdf 100644 --- a/core/function.rs +++ b/core/function.rs @@ -263,7 +263,7 @@ pub enum Func { Math(MathFunc), #[cfg(feature = "json")] Json(JsonFunc), - Extention(ExtFunc), + Extension(ExtFunc), } impl Display for Func { @@ -274,7 +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::Extention(ext_func) => write!(f, "{}", ext_func), + Func::Extension(ext_func) => write!(f, "{}", ext_func), } } } @@ -369,8 +369,8 @@ impl Func { "tanh" => Ok(Func::Math(MathFunc::Tanh)), "trunc" => Ok(Func::Math(MathFunc::Trunc)), _ => match ExtFunc::resolve_function(name, arg_count) { - Ok(ext_func) => Ok(Func::Extention(ext_func)), - Err(_) => Err(()), + Some(ext_func) => Ok(Func::Extension(ext_func)), + None => Err(()), }, } } diff --git a/core/translate/expr.rs b/core/translate/expr.rs index 523be6e51..512e8e394 100644 --- a/core/translate/expr.rs +++ b/core/translate/expr.rs @@ -1615,7 +1615,7 @@ pub fn translate_expr( } } } - Func::Extention(ext_func) => match ext_func { + Func::Extension(ext_func) => match ext_func { #[cfg(feature = "uuid")] ExtFunc::Uuid(ref uuid_fn) => match uuid_fn { UuidFunc::UuidStr | UuidFunc::UuidBlob | UuidFunc::Uuid7TS => { diff --git a/core/vdbe/mod.rs b/core/vdbe/mod.rs index b5eb9d60f..520100463 100644 --- a/core/vdbe/mod.rs +++ b/core/vdbe/mod.rs @@ -26,7 +26,7 @@ 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, Func, FuncCtx, MathFunc, MathFuncArity, ScalarFunc}; +use crate::function::{AggFunc, FuncCtx, MathFunc, MathFuncArity, ScalarFunc}; use crate::pseudo::PseudoCursor; use crate::schema::Table; use crate::storage::sqlite3_ondisk::DatabaseHeader; @@ -39,6 +39,7 @@ 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}; @@ -50,33 +51,11 @@ use std::cell::RefCell; use std::collections::{BTreeMap, HashMap}; use std::rc::{Rc, Weak}; -use uuid::{ContextV7, Timestamp, Uuid}; - 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. @@ -2535,7 +2514,7 @@ impl Program { state.registers[*dest] = exec_replace(source, pattern, replacement); } }, - Func::Extention(extfn) => match extfn { + crate::function::Func::Extension(extfn) => match extfn { #[cfg(feature = "uuid")] ExtFunc::Uuid(uuidfn) => match uuidfn { UuidFunc::Uuid4 | UuidFunc::Uuid4Str => { From bea49549ed5bcea11e3dafdc25cb130318d0fae8 Mon Sep 17 00:00:00 2001 From: Dezhi Wu Date: Sun, 22 Dec 2024 10:01:42 +0800 Subject: [PATCH 7/8] feat(core/io): Add support for file creation in `open_file` function `cargo test` is always failing on FreeBSD, the following is one of the errors: ``` ---- tests::test_simple_overflow_page stdout ---- thread 'tests::test_simple_overflow_page' panicked at test/src/lib.rs:32:84: called `Result::unwrap()` on an `Err` value: IOError(Os { code: 2, kind: NotFound, message: "No such file or directory" }) ``` After some digging, I found that the `open_file` function in `core/io/generic.rs` does not respect the `OpenFlags::Create` flag. This commit adds support for file creation in the `open_file` function. `cargo test` now passes on FreeBSD. --- core/io/generic.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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), })) From fbf42458b89f3f89c25e1f23c6255453ca1b8220 Mon Sep 17 00:00:00 2001 From: PThorpe92 Date: Sun, 22 Dec 2024 21:43:29 -0500 Subject: [PATCH 8/8] Use custom expr equality check in translation and planner --- core/translate/expr.rs | 7 ++----- core/translate/planner.rs | 12 ++++++++++-- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/core/translate/expr.rs b/core/translate/expr.rs index 363d96a99..94a61beaa 100644 --- a/core/translate/expr.rs +++ b/core/translate/expr.rs @@ -4,7 +4,7 @@ use sqlite3_parser::ast::{self, UnaryOperator}; 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 +554,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, diff --git a/core/translate/planner.rs b/core/translate/planner.rs index 14757e00a..0bdc447f3 100644 --- a/core/translate/planner.rs +++ b/core/translate/planner.rs @@ -4,7 +4,12 @@ use super::{ Aggregate, BTreeTableReference, Direction, GroupBy, Plan, ResultSetColumn, SourceOperator, }, }; -use crate::{function::Func, schema::Schema, util::normalize_ident, Result}; +use crate::{ + function::Func, + schema::Schema, + util::{exprs_are_equivalent, normalize_ident}, + Result, +}; use sqlite3_parser::ast::{self, FromClause, JoinType, ResultColumn}; pub struct OperatorIdCounter { @@ -23,7 +28,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 {