diff --git a/core/benches/json_benchmark.rs b/core/benches/json_benchmark.rs index c0b04d5e0..087a55f1e 100644 --- a/core/benches/json_benchmark.rs +++ b/core/benches/json_benchmark.rs @@ -485,9 +485,167 @@ fn bench(criterion: &mut Criterion) { } } +fn bench_sequential_jsonb(criterion: &mut Criterion) { + // Flag to disable rusqlite benchmarks if needed + let enable_rusqlite = std::env::var("DISABLE_RUSQLITE_BENCHMARK").is_err(); + + #[allow(clippy::arc_with_non_send_sync)] + let io = Arc::new(PlatformIO::new().unwrap()); + let db = Database::open_file(io.clone(), "../testing/testing.db", false).unwrap(); + let limbo_conn = db.connect().unwrap(); + + // Select a subset of JSON payloads to use in the sequential test + let json_payloads = [ + ("Small", r#"{"id": 1, "name": "Test"}"#), + ( + "Medium", + r#"{"id": 1, "name": "Test", "attributes": {"color": "blue", "size": "medium", "tags": ["tag1", "tag2", "tag3"]}}"#, + ), + ( + "Real world json #1", + r#"{ + "user": { + "id": "usr_7f8d3a2e", + "name": "Jane Smith", + "email": "jane.smith@example.com", + "verified": true, + "created_at": "2023-05-12T15:42:31Z", + "preferences": { + "theme": "dark", + "notifications": { + "email": true, + "push": false, + "sms": true + }, + "language": "en-US" + } + } + }"#, + ), + ( + "Real world json #2", + r#"{ + "feed": { + "user_id": "u_12345", + "posts": [ + { + "id": "post_001", + "author": "user_789", + "content": "Just launched our new product! Check it out at example.com/new", + "timestamp": "2024-03-13T14:27:32Z", + "likes": 24, + "comments": [ + { + "id": "comment_001", + "author": "user_456", + "content": "Looks amazing! Cant wait to try it.", + "timestamp": "2024-03-13T14:35:12Z", + "likes": 3 + }, + { + "id": "comment_002", + "author": "user_789", + "content": "Thanks! Let me know what you think after youve tried it.", + "timestamp": "2024-03-13T14:42:45Z", + "likes": 1 + } + ], + "tags": ["product", "launch", "technology"] + }, + { + "id": "post_002", + "author": "user_123", + "content": "Beautiful day for hiking! #nature #outdoors", + "timestamp": "2024-03-13T11:15:22Z", + "likes": 57, + "comments": [ + { + "id": "comment_003", + "author": "user_345", + "content": "Where is this? So beautiful!", + "timestamp": "2024-03-13T11:22:05Z", + "likes": 2 + }, + { + "id": "comment_004", + "author": "user_123", + "content": "Mount Rainier National Park!", + "timestamp": "2024-03-13T11:30:16Z", + "likes": 3 + } + ], + "tags": ["nature", "outdoors", "hiking"], + "location": { + "name": "Mount Rainier National Park", + "latitude": 46.8800, + "longitude": -121.7269 + } + } + ], + "has_more": true, + "next_cursor": "cursor_xyz123" + } + }"#, + ), + ]; + + // Create a query that calls jsonb() multiple times in sequence + let query = format!( + "SELECT jsonb('{}'), jsonb('{}'), jsonb('{}'), jsonb('{}'), jsonb('{}'), jsonb('{}'), jsonb('{}'), jsonb('{}')", + json_payloads[0].1.replace("'", "\\'"), + json_payloads[1].1.replace("'", "\\'"), + json_payloads[2].1.replace("'", "\\'"), + json_payloads[3].1.replace("'", "\\'"), + json_payloads[0].1.replace("'", "\\'"), + json_payloads[1].1.replace("'", "\\'"), + json_payloads[2].1.replace("'", "\\'"), + json_payloads[3].1.replace("'", "\\'"), + ); + + let mut group = criterion.benchmark_group("Sequential JSONB Calls"); + + group.bench_function("Limbo - Sequential", |b| { + let mut stmt = limbo_conn.prepare(&query).unwrap(); + let io = io.clone(); + b.iter(|| { + loop { + match stmt.step().unwrap() { + limbo_core::StepResult::Row => {} + limbo_core::StepResult::IO => { + let _ = io.run_once(); + } + limbo_core::StepResult::Done => { + break; + } + limbo_core::StepResult::Interrupt | limbo_core::StepResult::Busy => { + unreachable!(); + } + } + } + stmt.reset(); + }); + }); + + if enable_rusqlite { + let sqlite_conn = rusqlite_open(); + + group.bench_function("Sqlite3 - Sequential", |b| { + let mut stmt = sqlite_conn.prepare(&query).unwrap(); + b.iter(|| { + let mut rows = stmt.raw_query(); + while let Some(row) = rows.next().unwrap() { + black_box(row); + } + }); + }); + } + + group.finish(); +} + criterion_group! { name = benches; config = Criterion::default().with_profiler(PProfProfiler::new(100, Output::Flamegraph(Some(Options::default())))); - targets = bench + targets = bench, bench_sequential_jsonb } criterion_main!(benches); diff --git a/core/json/json_cache.rs b/core/json/json_cache.rs new file mode 100644 index 000000000..a96c7ce19 --- /dev/null +++ b/core/json/json_cache.rs @@ -0,0 +1,435 @@ +use std::cell::{Cell, UnsafeCell}; + +use crate::OwnedValue; + +use super::jsonb::Jsonb; + +const JSON_CACHE_SIZE: usize = 4; + +#[derive(Debug)] +pub struct JsonCache { + entries: [Option<(OwnedValue, Jsonb)>; JSON_CACHE_SIZE], + age: [usize; JSON_CACHE_SIZE], + used: usize, + counter: usize, +} + +impl JsonCache { + pub fn new() -> Self { + Self { + entries: [None, None, None, None], + age: [0, 0, 0, 0], + used: 0, + counter: 0, + } + } + + fn find_oldest_entry(&self) -> usize { + let mut oldest_idx = 0; + let mut oldest_age = self.age[0]; + + for i in 1..self.used { + if self.age[i] < oldest_age { + oldest_idx = i; + oldest_age = self.age[i]; + } + } + + oldest_idx + } + + pub fn insert(&mut self, key: &OwnedValue, value: &Jsonb) { + if self.used < JSON_CACHE_SIZE { + self.entries[self.used] = Some((key.clone(), value.clone())); + self.age[self.used] = self.counter; + self.counter += 1; + self.used += 1 + } else { + let id = self.find_oldest_entry(); + + self.entries[id] = Some((key.clone(), value.clone())); + self.age[id] = self.counter; + self.counter += 1; + } + } + + pub fn lookup(&mut self, key: &OwnedValue) -> Option { + for i in (0..self.used).rev() { + if let Some((stored_key, value)) = &self.entries[i] { + if key == stored_key { + self.age[i] = self.counter; + self.counter += 1; + let json = value.clone(); + + return Some(json); + } + } + } + None + } + + pub fn clear(&mut self) { + self.counter = 0; + self.used = 0; + } +} + +#[derive(Debug)] +pub struct JsonCacheCell { + inner: UnsafeCell>, + accessed: Cell, +} + +impl JsonCacheCell { + pub fn new() -> Self { + Self { + inner: UnsafeCell::new(None), + accessed: Cell::new(false), + } + } + + #[cfg(test)] + pub fn lookup(&self, key: &OwnedValue) -> Option { + assert_eq!(self.accessed.get(), false); + + self.accessed.set(true); + + let result = unsafe { + let cache_ptr = self.inner.get(); + if (*cache_ptr).is_none() { + *cache_ptr = Some(JsonCache::new()); + } + + if let Some(cache) = &mut (*cache_ptr) { + cache.lookup(key) + } else { + None + } + }; + + self.accessed.set(false); + result + } + + pub fn get_or_insert_with( + &self, + key: &OwnedValue, + value: impl Fn(&OwnedValue) -> crate::Result, + ) -> crate::Result { + assert_eq!(self.accessed.get(), false); + + self.accessed.set(true); + let result = unsafe { + let cache_ptr = self.inner.get(); + if (*cache_ptr).is_none() { + *cache_ptr = Some(JsonCache::new()); + } + + if let Some(cache) = &mut (*cache_ptr) { + if let Some(jsonb) = cache.lookup(key) { + Ok(jsonb) + } else { + let result = value(key); + match result { + Ok(json) => { + cache.insert(key, &json); + Ok(json) + } + Err(e) => Err(e), + } + } + } else { + let result = value(key); + result + } + }; + self.accessed.set(false); + + result + } + + pub fn clear(&mut self) { + assert_eq!(self.accessed.get(), false); + self.accessed.set(true); + unsafe { + let cache_ptr = self.inner.get(); + if (*cache_ptr).is_none() { + self.accessed.set(false); + return; + } + + if let Some(cache) = &mut (*cache_ptr) { + cache.clear() + } + } + self.accessed.set(false); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::str::FromStr; + + // Helper function to create test OwnedValue and Jsonb from JSON string + fn create_test_pair(json_str: &str) -> (OwnedValue, Jsonb) { + // Create OwnedValue as text representation of JSON + let key = OwnedValue::build_text(json_str); + + // Create Jsonb from the same JSON string + let value = Jsonb::from_str(json_str).unwrap(); + + (key, value) + } + + #[test] + fn test_json_cache_new() { + let cache = JsonCache::new(); + assert_eq!(cache.used, 0); + assert_eq!(cache.counter, 0); + assert_eq!(cache.age, [0, 0, 0, 0]); + assert!(cache.entries.iter().all(|entry| entry.is_none())); + } + + #[test] + fn test_json_cache_insert_and_lookup() { + let mut cache = JsonCache::new(); + let json_str = "{\"test\": \"value\"}"; + let (key, value) = create_test_pair(json_str); + + // Insert a value + cache.insert(&key, &value); + + // Verify it was inserted + assert_eq!(cache.used, 1); + assert_eq!(cache.counter, 1); + + // Look it up + let result = cache.lookup(&key); + assert!(result.is_some()); + assert_eq!(result.unwrap(), value); + + // Counter should be incremented after lookup + assert_eq!(cache.counter, 2); + } + + #[test] + fn test_json_cache_lookup_nonexistent() { + let mut cache = JsonCache::new(); + let (key, _) = create_test_pair("{\"id\": 123}"); + + // Look up a non-existent key + let result = cache.lookup(&key); + assert!(result.is_none()); + + // Counter should remain unchanged + assert_eq!(cache.counter, 0); + } + + #[test] + fn test_json_cache_multiple_entries() { + let mut cache = JsonCache::new(); + + // Insert multiple entries + let (key1, value1) = create_test_pair("{\"id\": 1}"); + let (key2, value2) = create_test_pair("{\"id\": 2}"); + let (key3, value3) = create_test_pair("{\"id\": 3}"); + + cache.insert(&key1, &value1); + cache.insert(&key2, &value2); + cache.insert(&key3, &value3); + + // Verify they were all inserted + assert_eq!(cache.used, 3); + assert_eq!(cache.counter, 3); + + // Look them up in reverse order + let result3 = cache.lookup(&key3); + let result2 = cache.lookup(&key2); + let result1 = cache.lookup(&key1); + + assert_eq!(result3.unwrap(), value3); + assert_eq!(result2.unwrap(), value2); + assert_eq!(result1.unwrap(), value1); + + // Counter should be incremented for each lookup + assert_eq!(cache.counter, 6); + } + + #[test] + fn test_json_cache_eviction() { + let mut cache = JsonCache::new(); + + // Insert more than JSON_CACHE_SIZE entries + let (key1, value1) = create_test_pair("{\"id\": 1}"); + let (key2, value2) = create_test_pair("{\"id\": 2}"); + let (key3, value3) = create_test_pair("{\"id\": 3}"); + let (key4, value4) = create_test_pair("{\"id\": 4}"); + let (key5, value5) = create_test_pair("{\"id\": 5}"); + + cache.insert(&key1, &value1); + cache.insert(&key2, &value2); + cache.insert(&key3, &value3); + cache.insert(&key4, &value4); + + // Cache is now full + assert_eq!(cache.used, 4); + + // Look up key1 to make it the most recently used + let _ = cache.lookup(&key1); + + // Insert one more entry - should evict the oldest (key2) + cache.insert(&key5, &value5); + + // Cache size should still be JSON_CACHE_SIZE + assert_eq!(cache.used, 4); + + // key2 should have been evicted + let result2 = cache.lookup(&key2); + assert!(result2.is_none()); + + // Other entries should still be present + assert!(cache.lookup(&key1).is_some()); + assert!(cache.lookup(&key3).is_some()); + assert!(cache.lookup(&key4).is_some()); + assert!(cache.lookup(&key5).is_some()); + } + + #[test] + fn test_json_cache_find_oldest_entry() { + let mut cache = JsonCache::new(); + + // Insert entries + let (key1, value1) = create_test_pair("{\"id\": 1}"); + let (key2, value2) = create_test_pair("{\"id\": 2}"); + let (key3, value3) = create_test_pair("{\"id\": 3}"); + + cache.insert(&key1, &value1); + cache.insert(&key2, &value2); + cache.insert(&key3, &value3); + + // key1 should be the oldest + assert_eq!(cache.find_oldest_entry(), 0); + + // Access key1 to make it the newest + let _ = cache.lookup(&key1); + + // Now key2 should be the oldest + assert_eq!(cache.find_oldest_entry(), 1); + } + + // Tests for JsonCacheCell + + #[test] + fn test_json_cache_cell_new() { + let cache_cell = JsonCacheCell::new(); + + // Access flag should be false initially + assert_eq!(cache_cell.accessed.get(), false); + + // Inner cache should be None initially + unsafe { + let inner = &*cache_cell.inner.get(); + assert!(inner.is_none()); + } + } + + #[test] + fn test_json_cache_cell_lookup() { + let cache_cell = JsonCacheCell::new(); + let (key, value) = create_test_pair("{\"test\": \"value\"}"); + + // First lookup should return None since cache is empty + let result = cache_cell.lookup(&key); + assert!(result.is_none()); + + // Cache should be initialized after first lookup + unsafe { + let inner = &*cache_cell.inner.get(); + assert!(inner.is_some()); + } + + // Access flag should be reset to false + assert_eq!(cache_cell.accessed.get(), false); + + // Insert the value using get_or_insert_with + let insert_result = cache_cell.get_or_insert_with(&key, |k| { + // Verify that k is the same as our key + assert_eq!(k, &key); + Ok(value.clone()) + }); + + assert!(insert_result.is_ok()); + assert_eq!(insert_result.unwrap(), value); + + // Access flag should be reset to false + assert_eq!(cache_cell.accessed.get(), false); + + // Lookup should now return the value + let lookup_result = cache_cell.lookup(&key); + assert!(lookup_result.is_some()); + assert_eq!(lookup_result.unwrap(), value); + } + + #[test] + fn test_json_cache_cell_get_or_insert_with_existing() { + let cache_cell = JsonCacheCell::new(); + let (key, value) = create_test_pair("{\"test\": \"value\"}"); + + // Insert a value + let _ = cache_cell.get_or_insert_with(&key, |_| Ok(value.clone())); + + // Counter indicating if the closure was called + let closure_called = Cell::new(false); + + // Try to insert again with the same key + let result = cache_cell.get_or_insert_with(&key, |_| { + closure_called.set(true); + Ok(Jsonb::from_str("{\"test\": \"value\"}").unwrap()) + }); + + // The closure should not have been called + assert!(!closure_called.get()); + + // Should return the original value + assert_eq!(result.unwrap(), value); + } + + #[test] + #[should_panic] + fn test_json_cache_cell_double_access() { + let cache_cell = JsonCacheCell::new(); + let (key, _) = create_test_pair("{\"test\": \"value\"}"); + + // Access the cache + + // Set accessed flag to true manually + cache_cell.accessed.set(true); + + // This should panic due to double access + + let _ = cache_cell.lookup(&key); + } + + #[test] + fn test_json_cache_cell_get_or_insert_error_handling() { + let cache_cell = JsonCacheCell::new(); + let (key, _) = create_test_pair("{\"test\": \"value\"}"); + + // Test error handling + let error_result = cache_cell.get_or_insert_with(&key, |_| { + // Return an error + Err(crate::LimboError::Constraint("Test error".to_string())) + }); + + // Should propagate the error + assert!(error_result.is_err()); + + // Access flag should be reset to false + assert_eq!(cache_cell.accessed.get(), false); + + // The entry should not be cached + let lookup_result = cache_cell.lookup(&key); + assert!(lookup_result.is_none()); + } +} diff --git a/core/json/json_operations.rs b/core/json/json_operations.rs index 7700c2f6f..63484ab1d 100644 --- a/core/json/json_operations.rs +++ b/core/json/json_operations.rs @@ -3,10 +3,10 @@ use std::{collections::VecDeque, rc::Rc}; use crate::types::OwnedValue; use super::{ - convert_dbtype_to_jsonb, convert_json_to_db_type, get_json_value, json_path_from_owned_value, - json_string_to_db_type, + convert_dbtype_to_jsonb, convert_json_to_db_type, curry_convert_dbtype_to_jsonb, + get_json_value, json_path_from_owned_value, json_string_to_db_type, jsonb::{DeleteOperation, InsertOperation, ReplaceOperation}, - Conv, OutputVariant, Val, + Conv, JsonCacheCell, OutputVariant, Val, }; /// Represents a single patch operation in the merge queue. @@ -152,12 +152,13 @@ impl JsonPatcher { } } -pub fn json_remove(args: &[OwnedValue]) -> crate::Result { +pub fn json_remove(args: &[OwnedValue], json_cache: &JsonCacheCell) -> crate::Result { if args.is_empty() { return Ok(OwnedValue::Null); } - let mut json = convert_dbtype_to_jsonb(&args[0], Conv::Strict)?; + let make_jsonb_fn = curry_convert_dbtype_to_jsonb(Conv::Strict); + let mut json = json_cache.get_or_insert_with(&args[0], make_jsonb_fn)?; for arg in &args[1..] { if let Some(path) = json_path_from_owned_value(arg, true)? { let mut op = DeleteOperation::new(); @@ -170,12 +171,13 @@ pub fn json_remove(args: &[OwnedValue]) -> crate::Result { json_string_to_db_type(json, el_type, OutputVariant::String) } -pub fn jsonb_remove(args: &[OwnedValue]) -> crate::Result { +pub fn jsonb_remove(args: &[OwnedValue], json_cache: &JsonCacheCell) -> crate::Result { if args.is_empty() { return Ok(OwnedValue::Null); } - let mut json = convert_dbtype_to_jsonb(&args[0], Conv::Strict)?; + let make_jsonb_fn = curry_convert_dbtype_to_jsonb(Conv::Strict); + let mut json = json_cache.get_or_insert_with(&args[0], make_jsonb_fn)?; for arg in &args[1..] { if let Some(path) = json_path_from_owned_value(arg, true)? { let mut op = DeleteOperation::new(); @@ -186,12 +188,13 @@ pub fn jsonb_remove(args: &[OwnedValue]) -> crate::Result { Ok(OwnedValue::Blob(Rc::new(json.data()))) } -pub fn json_replace(args: &[OwnedValue]) -> crate::Result { +pub fn json_replace(args: &[OwnedValue], json_cache: &JsonCacheCell) -> crate::Result { if args.is_empty() { return Ok(OwnedValue::Null); } - let mut json = convert_dbtype_to_jsonb(&args[0], Conv::Strict)?; + let make_jsonb_fn = curry_convert_dbtype_to_jsonb(Conv::Strict); + let mut json = json_cache.get_or_insert_with(&args[0], make_jsonb_fn)?; let other = args[1..].chunks_exact(2); for chunk in other { let path = json_path_from_owned_value(&chunk[0], true)?; @@ -209,12 +212,13 @@ pub fn json_replace(args: &[OwnedValue]) -> crate::Result { json_string_to_db_type(json, el_type, super::OutputVariant::String) } -pub fn jsonb_replace(args: &[OwnedValue]) -> crate::Result { +pub fn jsonb_replace(args: &[OwnedValue], json_cache: &JsonCacheCell) -> crate::Result { if args.is_empty() { return Ok(OwnedValue::Null); } - let mut json = convert_dbtype_to_jsonb(&args[0], Conv::Strict)?; + let make_jsonb_fn = curry_convert_dbtype_to_jsonb(Conv::Strict); + let mut json = json_cache.get_or_insert_with(&args[0], make_jsonb_fn)?; let other = args[1..].chunks_exact(2); for chunk in other { let path = json_path_from_owned_value(&chunk[0], true)?; @@ -231,12 +235,13 @@ pub fn jsonb_replace(args: &[OwnedValue]) -> crate::Result { json_string_to_db_type(json, el_type, OutputVariant::Binary) } -pub fn json_insert(args: &[OwnedValue]) -> crate::Result { +pub fn json_insert(args: &[OwnedValue], json_cache: &JsonCacheCell) -> crate::Result { if args.is_empty() { return Ok(OwnedValue::Null); } - let mut json = convert_dbtype_to_jsonb(&args[0], Conv::Strict)?; + let make_jsonb_fn = curry_convert_dbtype_to_jsonb(Conv::Strict); + let mut json = json_cache.get_or_insert_with(&args[0], make_jsonb_fn)?; let other = args[1..].chunks_exact(2); for chunk in other { let path = json_path_from_owned_value(&chunk[0], true)?; @@ -253,12 +258,13 @@ pub fn json_insert(args: &[OwnedValue]) -> crate::Result { json_string_to_db_type(json, el_type, OutputVariant::String) } -pub fn jsonb_insert(args: &[OwnedValue]) -> crate::Result { +pub fn jsonb_insert(args: &[OwnedValue], json_cache: &JsonCacheCell) -> crate::Result { if args.is_empty() { return Ok(OwnedValue::Null); } - let mut json = convert_dbtype_to_jsonb(&args[0], Conv::Strict)?; + let make_jsonb_fn = curry_convert_dbtype_to_jsonb(Conv::Strict); + let mut json = json_cache.get_or_insert_with(&args[0], make_jsonb_fn)?; let other = args[1..].chunks_exact(2); for chunk in other { let path = json_path_from_owned_value(&chunk[0], true)?; @@ -596,14 +602,16 @@ mod tests { #[test] fn test_json_remove_empty_args() { let args = vec![]; - assert_eq!(json_remove(&args).unwrap(), OwnedValue::Null); + let json_cache = JsonCacheCell::new(); + assert_eq!(json_remove(&args, &json_cache).unwrap(), OwnedValue::Null); } #[test] fn test_json_remove_array_element() { let args = vec![create_json(r#"[1,2,3,4,5]"#), create_text("$[2]")]; - let result = json_remove(&args).unwrap(); + let json_cache = JsonCacheCell::new(); + let result = json_remove(&args, &json_cache).unwrap(); match result { OwnedValue::Text(t) => assert_eq!(t.as_str(), "[1,2,4,5]"), _ => panic!("Expected Text value"), @@ -618,7 +626,8 @@ mod tests { create_text("$.c"), ]; - let result = json_remove(&args).unwrap(); + let json_cache = JsonCacheCell::new(); + let result = json_remove(&args, &json_cache).unwrap(); match result { OwnedValue::Text(t) => assert_eq!(t.as_str(), r#"{"b":2}"#), _ => panic!("Expected Text value"), @@ -632,7 +641,8 @@ mod tests { create_text("$.a.b.c"), ]; - let result = json_remove(&args).unwrap(); + let json_cache = JsonCacheCell::new(); + let result = json_remove(&args, &json_cache).unwrap(); match result { OwnedValue::Text(t) => assert_eq!(t.as_str(), r#"{"a":{"b":{"d":2}}}"#), _ => panic!("Expected Text value"), @@ -646,7 +656,8 @@ mod tests { create_text("$.a"), ]; - let result = json_remove(&args).unwrap(); + let json_cache = JsonCacheCell::new(); + let result = json_remove(&args, &json_cache).unwrap(); match result { OwnedValue::Text(t) => assert_eq!(t.as_str(), r#"{"a":2,"a":3}"#), _ => panic!("Expected Text value"), @@ -660,7 +671,8 @@ mod tests { OwnedValue::Integer(42), // Invalid path type ]; - assert!(json_remove(&args).is_err()); + let json_cache = JsonCacheCell::new(); + assert!(json_remove(&args, &json_cache).is_err()); } #[test] @@ -672,7 +684,8 @@ mod tests { create_text("$.c[0].y"), ]; - let result = json_remove(&args).unwrap(); + let json_cache = JsonCacheCell::new(); + let result = json_remove(&args, &json_cache).unwrap(); match result { OwnedValue::Text(t) => { let value = t.as_str(); diff --git a/core/json/jsonb.rs b/core/json/jsonb.rs index 2567d40c0..a77ae994a 100644 --- a/core/json/jsonb.rs +++ b/core/json/jsonb.rs @@ -171,7 +171,7 @@ const fn make_character_type_ok_table() -> [u8; 256] { static CHARACTER_TYPE_OK: [u8; 256] = make_character_type_ok_table(); -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct Jsonb { data: Vec, } diff --git a/core/json/mod.rs b/core/json/mod.rs index 2f45f8cc1..8d798ceb7 100644 --- a/core/json/mod.rs +++ b/core/json/mod.rs @@ -1,5 +1,6 @@ mod de; mod error; +mod json_cache; mod json_operations; mod json_path; mod jsonb; @@ -15,6 +16,7 @@ use crate::json::json_path::{json_path, JsonPath, PathElement}; pub use crate::json::ser::to_string; use crate::types::{OwnedValue, OwnedValueType, Text, TextSubtype}; use crate::{bail_parse_error, json::de::ordered_object}; +pub use json_cache::JsonCacheCell; use jsonb::{ElementType, Jsonb, JsonbHeader, PathOperationMode, SearchOperation, SetOperation}; use ser::to_string_pretty; use serde::{Deserialize, Serialize}; @@ -36,7 +38,8 @@ pub enum Val { Object(Vec<(String, Val)>), } -enum Conv { +#[derive(Debug, Clone, Copy)] +pub enum Conv { Strict, NotStrict, ToString, @@ -86,8 +89,10 @@ pub fn get_json(json_value: &OwnedValue, indent: Option<&str>) -> crate::Result< } } -pub fn jsonb(json_value: &OwnedValue) -> crate::Result { - let jsonbin = convert_dbtype_to_jsonb(json_value, Conv::Strict); +pub fn jsonb(json_value: &OwnedValue, cache: &JsonCacheCell) -> crate::Result { + let json_conv_fn = curry_convert_dbtype_to_jsonb(Conv::Strict); + + let jsonbin = cache.get_or_insert_with(json_value, json_conv_fn); match jsonbin { Ok(jsonbin) => Ok(OwnedValue::Blob(Rc::new(jsonbin.data()))), Err(_) => { @@ -140,6 +145,10 @@ fn convert_dbtype_to_jsonb(val: &OwnedValue, strict: Conv) -> crate::Result impl Fn(&OwnedValue) -> crate::Result { + move |val| convert_dbtype_to_jsonb(val, strict) +} + fn get_json_value(json_value: &OwnedValue) -> crate::Result { match json_value { OwnedValue::Text(ref t) => match from_str::(t.as_str()) { @@ -189,17 +198,19 @@ pub fn jsonb_array(values: &[OwnedValue]) -> crate::Result { } pub fn json_array_length( - json_value: &OwnedValue, - json_path: Option<&OwnedValue>, + value: &OwnedValue, + path: Option<&OwnedValue>, + json_cache: &JsonCacheCell, ) -> crate::Result { - let mut json = convert_dbtype_to_jsonb(json_value, Conv::Strict)?; + let make_jsonb_fn = curry_convert_dbtype_to_jsonb(Conv::Strict); + let mut json = json_cache.get_or_insert_with(value, make_jsonb_fn)?; - if json_path.is_none() { + if path.is_none() { let len = json.array_len()?; return Ok(OwnedValue::Integer(len as i64)); } - let path = json_path_from_owned_value(json_path.expect("We already checked none"), true)?; + let path = json_path_from_owned_value(path.expect("We already checked none"), true)?; if let Some(path) = path { let mut op = SearchOperation::new(json.len() / 2); @@ -211,12 +222,13 @@ pub fn json_array_length( Ok(OwnedValue::Null) } -pub fn json_set(args: &[OwnedValue]) -> crate::Result { +pub fn json_set(args: &[OwnedValue], json_cache: &JsonCacheCell) -> crate::Result { if args.is_empty() { return Ok(OwnedValue::Null); } - let mut json = convert_dbtype_to_jsonb(&args[0], Conv::Strict)?; + let make_jsonb_fn = curry_convert_dbtype_to_jsonb(Conv::Strict); + let mut json = json_cache.get_or_insert_with(&args[0], make_jsonb_fn)?; let other = args[1..].chunks_exact(2); for chunk in other { @@ -236,13 +248,18 @@ pub fn json_set(args: &[OwnedValue]) -> crate::Result { /// Implements the -> operator. Always returns a proper JSON value. /// https://sqlite.org/json1.html#the_and_operators -pub fn json_arrow_extract(value: &OwnedValue, path: &OwnedValue) -> crate::Result { +pub fn json_arrow_extract( + value: &OwnedValue, + path: &OwnedValue, + json_cache: &JsonCacheCell, +) -> crate::Result { if let OwnedValue::Null = value { return Ok(OwnedValue::Null); } if let Some(path) = json_path_from_owned_value(path, false)? { - let mut json = convert_dbtype_to_jsonb(value, Conv::Strict)?; + let make_jsonb_fn = curry_convert_dbtype_to_jsonb(Conv::Strict); + let mut json = json_cache.get_or_insert_with(value, make_jsonb_fn)?; let mut op = SearchOperation::new(json.len()); let res = json.operate_on_path(&path, &mut op); let extracted = op.result(); @@ -261,12 +278,14 @@ pub fn json_arrow_extract(value: &OwnedValue, path: &OwnedValue) -> crate::Resul pub fn json_arrow_shift_extract( value: &OwnedValue, path: &OwnedValue, + json_cache: &JsonCacheCell, ) -> crate::Result { if let OwnedValue::Null = value { return Ok(OwnedValue::Null); } if let Some(path) = json_path_from_owned_value(path, false)? { - let mut json = convert_dbtype_to_jsonb(value, Conv::Strict)?; + let make_jsonb_fn = curry_convert_dbtype_to_jsonb(Conv::Strict); + let mut json = json_cache.get_or_insert_with(value, make_jsonb_fn)?; let mut op = SearchOperation::new(json.len()); let res = json.operate_on_path(&path, &mut op); let extracted = op.result(); @@ -292,7 +311,11 @@ pub fn json_arrow_shift_extract( /// Extracts a JSON value from a JSON object or array. /// If there's only a single path, the return value might be either a TEXT or a database type. /// https://sqlite.org/json1.html#the_json_extract_function -pub fn json_extract(value: &OwnedValue, paths: &[OwnedValue]) -> crate::Result { +pub fn json_extract( + value: &OwnedValue, + paths: &[OwnedValue], + json_cache: &JsonCacheCell, +) -> crate::Result { if let OwnedValue::Null = value { return Ok(OwnedValue::Null); } @@ -300,14 +323,20 @@ pub fn json_extract(value: &OwnedValue, paths: &[OwnedValue]) -> crate::Result crate::Result { +pub fn jsonb_extract( + value: &OwnedValue, + paths: &[OwnedValue], + json_cache: &JsonCacheCell, +) -> crate::Result { if let OwnedValue::Null = value { return Ok(OwnedValue::Null); } @@ -315,21 +344,23 @@ pub fn jsonb_extract(value: &OwnedValue, paths: &[OwnedValue]) -> crate::Result< if paths.is_empty() { return Ok(OwnedValue::Null); } + let convert_to_jsonb = curry_convert_dbtype_to_jsonb(Conv::Strict); + let jsonb = json_cache.get_or_insert_with(value, convert_to_jsonb)?; - let (json, element_type) = jsonb_extract_internal(value, paths)?; + let (json, element_type) = jsonb_extract_internal(jsonb, paths)?; let result = json_string_to_db_type(json, element_type, OutputVariant::ElementType)?; Ok(result) } fn jsonb_extract_internal( - value: &OwnedValue, + value: Jsonb, paths: &[OwnedValue], ) -> crate::Result<(Jsonb, ElementType)> { let null = Jsonb::from_raw_data(JsonbHeader::make_null().into_bytes().as_bytes()); if paths.len() == 1 { if let Some(path) = json_path_from_owned_value(&paths[0], true)? { - let mut json = convert_dbtype_to_jsonb(value, Conv::Strict)?; + let mut json = value; let mut op = SearchOperation::new(json.len()); let res = json.operate_on_path(&path, &mut op); @@ -348,7 +379,7 @@ fn jsonb_extract_internal( } } - let mut json = convert_dbtype_to_jsonb(value, Conv::Strict)?; + let mut json = value; let mut result = Jsonb::make_empty_array(json.len()); // TODO: make an op to avoid creating new json for every path element @@ -818,7 +849,8 @@ mod tests { #[test] fn test_json_array_length() { let input = OwnedValue::build_text("[1,2,3,4]"); - let result = json_array_length(&input, None).unwrap(); + let json_cache = JsonCacheCell::new(); + let result = json_array_length(&input, None, &json_cache).unwrap(); if let OwnedValue::Integer(res) = result { assert_eq!(res, 4); } else { @@ -829,7 +861,8 @@ mod tests { #[test] fn test_json_array_length_empty() { let input = OwnedValue::build_text("[]"); - let result = json_array_length(&input, None).unwrap(); + let json_cache = JsonCacheCell::new(); + let result = json_array_length(&input, None, &json_cache).unwrap(); if let OwnedValue::Integer(res) = result { assert_eq!(res, 0); } else { @@ -840,7 +873,9 @@ mod tests { #[test] fn test_json_array_length_root() { let input = OwnedValue::build_text("[1,2,3,4]"); - let result = json_array_length(&input, Some(&OwnedValue::build_text("$"))).unwrap(); + let json_cache = JsonCacheCell::new(); + let result = + json_array_length(&input, Some(&OwnedValue::build_text("$")), &json_cache).unwrap(); if let OwnedValue::Integer(res) = result { assert_eq!(res, 4); } else { @@ -851,7 +886,8 @@ mod tests { #[test] fn test_json_array_length_not_array() { let input = OwnedValue::build_text("{one: [1,2,3,4]}"); - let result = json_array_length(&input, None).unwrap(); + let json_cache = JsonCacheCell::new(); + let result = json_array_length(&input, None, &json_cache).unwrap(); if let OwnedValue::Integer(res) = result { assert_eq!(res, 0); } else { @@ -862,7 +898,9 @@ mod tests { #[test] fn test_json_array_length_via_prop() { let input = OwnedValue::build_text("{one: [1,2,3,4]}"); - let result = json_array_length(&input, Some(&OwnedValue::build_text("$.one"))).unwrap(); + let json_cache = JsonCacheCell::new(); + let result = + json_array_length(&input, Some(&OwnedValue::build_text("$.one")), &json_cache).unwrap(); if let OwnedValue::Integer(res) = result { assert_eq!(res, 4); } else { @@ -873,7 +911,9 @@ mod tests { #[test] fn test_json_array_length_via_index() { let input = OwnedValue::build_text("[[1,2,3,4]]"); - let result = json_array_length(&input, Some(&OwnedValue::build_text("$[0]"))).unwrap(); + let json_cache = JsonCacheCell::new(); + let result = + json_array_length(&input, Some(&OwnedValue::build_text("$[0]")), &json_cache).unwrap(); if let OwnedValue::Integer(res) = result { assert_eq!(res, 4); } else { @@ -884,7 +924,9 @@ mod tests { #[test] fn test_json_array_length_via_index_not_array() { let input = OwnedValue::build_text("[1,2,3,4]"); - let result = json_array_length(&input, Some(&OwnedValue::build_text("$[2]"))).unwrap(); + let json_cache = JsonCacheCell::new(); + let result = + json_array_length(&input, Some(&OwnedValue::build_text("$[2]")), &json_cache).unwrap(); if let OwnedValue::Integer(res) = result { assert_eq!(res, 0); } else { @@ -895,15 +937,18 @@ mod tests { #[test] fn test_json_array_length_via_index_bad_prop() { let input = OwnedValue::build_text("{one: [1,2,3,4]}"); - let result = json_array_length(&input, Some(&OwnedValue::build_text("$.two"))).unwrap(); + let json_cache = JsonCacheCell::new(); + let result = + json_array_length(&input, Some(&OwnedValue::build_text("$.two")), &json_cache).unwrap(); assert_eq!(OwnedValue::Null, result); } #[test] fn test_json_array_length_simple_json_subtype() { let input = OwnedValue::build_text("[1,2,3]"); + let json_cache = JsonCacheCell::new(); let wrapped = get_json(&input, None).unwrap(); - let result = json_array_length(&wrapped, None).unwrap(); + let result = json_array_length(&wrapped, None, &json_cache).unwrap(); if let OwnedValue::Integer(res) = result { assert_eq!(res, 3); @@ -914,9 +959,11 @@ mod tests { #[test] fn test_json_extract_missing_path() { + let json_cache = JsonCacheCell::new(); let result = json_extract( &OwnedValue::build_text("{\"a\":2}"), &[OwnedValue::build_text("$.x")], + &json_cache, ); match result { @@ -926,7 +973,12 @@ mod tests { } #[test] fn test_json_extract_null_path() { - let result = json_extract(&OwnedValue::build_text("{\"a\":2}"), &[OwnedValue::Null]); + let json_cache = JsonCacheCell::new(); + let result = json_extract( + &OwnedValue::build_text("{\"a\":2}"), + &[OwnedValue::Null], + &json_cache, + ); match result { Ok(OwnedValue::Null) => (), @@ -936,9 +988,11 @@ mod tests { #[test] fn test_json_path_invalid() { + let json_cache = JsonCacheCell::new(); let result = json_extract( &OwnedValue::build_text("{\"a\":2}"), &[OwnedValue::Float(1.1)], + &json_cache, ); match result { @@ -1263,11 +1317,15 @@ mod tests { #[test] fn test_json_set_field_empty_object() { - let result = json_set(&[ - OwnedValue::build_text("{}"), - OwnedValue::build_text("$.field"), - OwnedValue::build_text("value"), - ]); + let json_cache = JsonCacheCell::new(); + let result = json_set( + &[ + OwnedValue::build_text("{}"), + OwnedValue::build_text("$.field"), + OwnedValue::build_text("value"), + ], + &json_cache, + ); assert!(result.is_ok()); @@ -1276,11 +1334,15 @@ mod tests { #[test] fn test_json_set_replace_field() { - let result = json_set(&[ - OwnedValue::build_text(r#"{"field":"old_value"}"#), - OwnedValue::build_text("$.field"), - OwnedValue::build_text("new_value"), - ]); + let json_cache = JsonCacheCell::new(); + let result = json_set( + &[ + OwnedValue::build_text(r#"{"field":"old_value"}"#), + OwnedValue::build_text("$.field"), + OwnedValue::build_text("new_value"), + ], + &json_cache, + ); assert!(result.is_ok()); @@ -1292,11 +1354,15 @@ mod tests { #[test] fn test_json_set_set_deeply_nested_key() { - let result = json_set(&[ - OwnedValue::build_text("{}"), - OwnedValue::build_text("$.object.doesnt.exist"), - OwnedValue::build_text("value"), - ]); + let json_cache = JsonCacheCell::new(); + let result = json_set( + &[ + OwnedValue::build_text("{}"), + OwnedValue::build_text("$.object.doesnt.exist"), + OwnedValue::build_text("value"), + ], + &json_cache, + ); assert!(result.is_ok()); @@ -1308,11 +1374,15 @@ mod tests { #[test] fn test_json_set_add_value_to_empty_array() { - let result = json_set(&[ - OwnedValue::build_text("[]"), - OwnedValue::build_text("$[0]"), - OwnedValue::build_text("value"), - ]); + let json_cache = JsonCacheCell::new(); + let result = json_set( + &[ + OwnedValue::build_text("[]"), + OwnedValue::build_text("$[0]"), + OwnedValue::build_text("value"), + ], + &json_cache, + ); assert!(result.is_ok()); @@ -1321,11 +1391,15 @@ mod tests { #[test] fn test_json_set_add_value_to_nonexistent_array() { - let result = json_set(&[ - OwnedValue::build_text("{}"), - OwnedValue::build_text("$.some_array[0]"), - OwnedValue::Integer(123), - ]); + let json_cache = JsonCacheCell::new(); + let result = json_set( + &[ + OwnedValue::build_text("{}"), + OwnedValue::build_text("$.some_array[0]"), + OwnedValue::Integer(123), + ], + &json_cache, + ); assert!(result.is_ok()); @@ -1337,11 +1411,15 @@ mod tests { #[test] fn test_json_set_add_value_to_array() { - let result = json_set(&[ - OwnedValue::build_text("[123]"), - OwnedValue::build_text("$[1]"), - OwnedValue::Integer(456), - ]); + let json_cache = JsonCacheCell::new(); + let result = json_set( + &[ + OwnedValue::build_text("[123]"), + OwnedValue::build_text("$[1]"), + OwnedValue::Integer(456), + ], + &json_cache, + ); assert!(result.is_ok()); @@ -1350,11 +1428,15 @@ mod tests { #[test] fn test_json_set_add_value_to_array_out_of_bounds() { - let result = json_set(&[ - OwnedValue::build_text("[123]"), - OwnedValue::build_text("$[200]"), - OwnedValue::Integer(456), - ]); + let json_cache = JsonCacheCell::new(); + let result = json_set( + &[ + OwnedValue::build_text("[123]"), + OwnedValue::build_text("$[200]"), + OwnedValue::Integer(456), + ], + &json_cache, + ); assert!(result.is_ok()); @@ -1363,11 +1445,15 @@ mod tests { #[test] fn test_json_set_replace_value_in_array() { - let result = json_set(&[ - OwnedValue::build_text("[123]"), - OwnedValue::build_text("$[0]"), - OwnedValue::Integer(456), - ]); + let json_cache = JsonCacheCell::new(); + let result = json_set( + &[ + OwnedValue::build_text("[123]"), + OwnedValue::build_text("$[0]"), + OwnedValue::Integer(456), + ], + &json_cache, + ); assert!(result.is_ok()); @@ -1376,11 +1462,15 @@ mod tests { #[test] fn test_json_set_null_path() { - let result = json_set(&[ - OwnedValue::build_text("{}"), - OwnedValue::Null, - OwnedValue::Integer(456), - ]); + let json_cache = JsonCacheCell::new(); + let result = json_set( + &[ + OwnedValue::build_text("{}"), + OwnedValue::Null, + OwnedValue::Integer(456), + ], + &json_cache, + ); assert!(result.is_ok()); @@ -1389,13 +1479,17 @@ mod tests { #[test] fn test_json_set_multiple_keys() { - let result = json_set(&[ - OwnedValue::build_text("[123]"), - OwnedValue::build_text("$[0]"), - OwnedValue::Integer(456), - OwnedValue::build_text("$[1]"), - OwnedValue::Integer(789), - ]); + let json_cache = JsonCacheCell::new(); + let result = json_set( + &[ + OwnedValue::build_text("[123]"), + OwnedValue::build_text("$[0]"), + OwnedValue::Integer(456), + OwnedValue::build_text("$[1]"), + OwnedValue::Integer(789), + ], + &json_cache, + ); assert!(result.is_ok()); @@ -1404,11 +1498,15 @@ mod tests { #[test] fn test_json_set_add_array_in_nested_object() { - let result = json_set(&[ - OwnedValue::build_text("{}"), - OwnedValue::build_text("$.object[0].field"), - OwnedValue::Integer(123), - ]); + let json_cache = JsonCacheCell::new(); + let result = json_set( + &[ + OwnedValue::build_text("{}"), + OwnedValue::build_text("$.object[0].field"), + OwnedValue::Integer(123), + ], + &json_cache, + ); assert!(result.is_ok()); @@ -1420,11 +1518,15 @@ mod tests { #[test] fn test_json_set_add_array_in_array_in_nested_object() { - let result = json_set(&[ - OwnedValue::build_text("{}"), - OwnedValue::build_text("$.object[0][0]"), - OwnedValue::Integer(123), - ]); + let json_cache = JsonCacheCell::new(); + let result = json_set( + &[ + OwnedValue::build_text("{}"), + OwnedValue::build_text("$.object[0][0]"), + OwnedValue::Integer(123), + ], + &json_cache, + ); assert!(result.is_ok()); @@ -1433,13 +1535,17 @@ mod tests { #[test] fn test_json_set_add_array_in_array_in_nested_object_out_of_bounds() { - let result = json_set(&[ - OwnedValue::build_text("{}"), - OwnedValue::build_text("$.object[123].another"), - OwnedValue::build_text("value"), - OwnedValue::build_text("$.field"), - OwnedValue::build_text("value"), - ]); + let json_cache = JsonCacheCell::new(); + let result = json_set( + &[ + OwnedValue::build_text("{}"), + OwnedValue::build_text("$.object[123].another"), + OwnedValue::build_text("value"), + OwnedValue::build_text("$.field"), + OwnedValue::build_text("value"), + ], + &json_cache, + ); assert!(result.is_ok()); diff --git a/core/vdbe/builder.rs b/core/vdbe/builder.rs index cebdf94fd..273c6c1bb 100644 --- a/core/vdbe/builder.rs +++ b/core/vdbe/builder.rs @@ -445,6 +445,7 @@ impl ProgramBuilder { self.constant_insns.is_empty(), "constant_insns is not empty when build() is called, did you forget to call emit_constant_insns()?" ); + self.parameters.list.dedup(); Program { max_registers: self.next_free_register, diff --git a/core/vdbe/mod.rs b/core/vdbe/mod.rs index e3cf91a0e..04bd7453a 100644 --- a/core/vdbe/mod.rs +++ b/core/vdbe/mod.rs @@ -31,6 +31,7 @@ use crate::functions::datetime::{ exec_date, exec_datetime_full, exec_julianday, exec_strftime, exec_time, exec_unixepoch, }; use crate::functions::printf::exec_printf; + use crate::pseudo::PseudoCursor; use crate::result::LimboResult; use crate::schema::{affinity, Affinity}; @@ -56,7 +57,7 @@ use crate::{ json::json_error_position, json::json_extract, json::json_insert, json::json_object, json::json_patch, json::json_quote, json::json_remove, json::json_replace, json::json_set, json::json_type, json::jsonb, json::jsonb_array, json::jsonb_extract, json::jsonb_insert, - json::jsonb_object, json::jsonb_remove, json::jsonb_replace, + json::jsonb_object, json::jsonb_remove, json::jsonb_replace, json::JsonCacheCell, }; use crate::{ resolve_ext_path, Connection, MvCursor, MvStore, Result, TransactionState, DATABASE_VERSION, @@ -244,6 +245,8 @@ pub struct ProgramState { interrupted: bool, parameters: HashMap, OwnedValue>, halt_state: Option, + #[cfg(feature = "json")] + json_cache: JsonCacheCell, } impl ProgramState { @@ -264,6 +267,8 @@ impl ProgramState { interrupted: false, parameters: HashMap::new(), halt_state: None, + #[cfg(feature = "json")] + json_cache: JsonCacheCell::new(), } } @@ -303,6 +308,8 @@ impl ProgramState { self.regex_cache.like.clear(); self.interrupted = false; self.parameters.clear(); + #[cfg(feature = "json")] + self.json_cache.clear() } pub fn get_cursor<'a>(&'a self, cursor_id: CursorID) -> std::cell::RefMut<'a, Cursor> { @@ -2123,6 +2130,7 @@ impl Program { dest, } => { let arg_count = func.arg_count; + match &func.func { #[cfg(feature = "json")] crate::function::Func::Json(json_func) => match json_func { @@ -2136,7 +2144,7 @@ impl Program { } JsonFunc::Jsonb => { let json_value = &state.registers[*start_reg]; - let json_blob = jsonb(json_value); + let json_blob = jsonb(json_value, &state.json_cache); match json_blob { Ok(json) => state.registers[*dest] = json, Err(e) => return Err(e), @@ -2165,13 +2173,13 @@ impl Program { } JsonFunc::JsonExtract => { let result = match arg_count { - 0 => json_extract(&OwnedValue::Null, &[]), + 0 => Ok(OwnedValue::Null), _ => { let val = &state.registers[*start_reg]; let reg_values = &state.registers [*start_reg + 1..*start_reg + arg_count]; - json_extract(val, reg_values) + json_extract(val, reg_values, &state.json_cache) } }; @@ -2182,13 +2190,13 @@ impl Program { } JsonFunc::JsonbExtract => { let result = match arg_count { - 0 => jsonb_extract(&OwnedValue::Null, &[]), + 0 => Ok(OwnedValue::Null), _ => { let val = &state.registers[*start_reg]; let reg_values = &state.registers [*start_reg + 1..*start_reg + arg_count]; - jsonb_extract(val, reg_values) + jsonb_extract(val, reg_values, &state.json_cache) } }; @@ -2207,7 +2215,7 @@ impl Program { JsonFunc::JsonArrowShiftExtract => json_arrow_shift_extract, _ => unreachable!(), }; - let json_str = json_func(json, path); + let json_str = json_func(json, path, &state.json_cache); match json_str { Ok(json) => state.registers[*dest] = json, Err(e) => return Err(e), @@ -2222,7 +2230,7 @@ impl Program { }; let func_result = match json_func { JsonFunc::JsonArrayLength => { - json_array_length(json_value, path_value) + json_array_length(json_value, path_value, &state.json_cache) } JsonFunc::JsonType => json_type(json_value, path_value), _ => unreachable!(), @@ -2254,6 +2262,7 @@ impl Program { JsonFunc::JsonRemove => { if let Ok(json) = json_remove( &state.registers[*start_reg..*start_reg + arg_count], + &state.json_cache, ) { state.registers[*dest] = json; } else { @@ -2263,6 +2272,7 @@ impl Program { JsonFunc::JsonbRemove => { if let Ok(json) = jsonb_remove( &state.registers[*start_reg..*start_reg + arg_count], + &state.json_cache, ) { state.registers[*dest] = json; } else { @@ -2272,6 +2282,7 @@ impl Program { JsonFunc::JsonReplace => { if let Ok(json) = json_replace( &state.registers[*start_reg..*start_reg + arg_count], + &state.json_cache, ) { state.registers[*dest] = json; } else { @@ -2281,6 +2292,7 @@ impl Program { JsonFunc::JsonbReplace => { if let Ok(json) = jsonb_replace( &state.registers[*start_reg..*start_reg + arg_count], + &state.json_cache, ) { state.registers[*dest] = json; } else { @@ -2290,6 +2302,7 @@ impl Program { JsonFunc::JsonInsert => { if let Ok(json) = json_insert( &state.registers[*start_reg..*start_reg + arg_count], + &state.json_cache, ) { state.registers[*dest] = json; } else { @@ -2299,6 +2312,7 @@ impl Program { JsonFunc::JsonbInsert => { if let Ok(json) = jsonb_insert( &state.registers[*start_reg..*start_reg + arg_count], + &state.json_cache, ) { state.registers[*dest] = json; } else { @@ -2348,7 +2362,7 @@ impl Program { let reg_values = &state.registers[*start_reg..*start_reg + arg_count]; - let json_result = json_set(reg_values); + let json_result = json_set(reg_values, &state.json_cache); match json_result { Ok(json) => state.registers[*dest] = json,