mirror of
https://github.com/aljazceru/turso.git
synced 2025-12-30 22:44:21 +01:00
Merge 'JSON cache' from Ihor Andrianov
SQLite uses a similar approach for operations where up to 4 JSON objects are accessed multiple times in a single query. `SELECT json_extact(a, 'some_path'), json_remove(a, 'some_path') json_set(a, 'some_path', 'some_value') from t;` Closes #1163
This commit is contained in:
@@ -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);
|
||||
|
||||
435
core/json/json_cache.rs
Normal file
435
core/json/json_cache.rs
Normal file
@@ -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<Jsonb> {
|
||||
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<Option<JsonCache>>,
|
||||
accessed: Cell<bool>,
|
||||
}
|
||||
|
||||
impl JsonCacheCell {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
inner: UnsafeCell::new(None),
|
||||
accessed: Cell::new(false),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn lookup(&self, key: &OwnedValue) -> Option<Jsonb> {
|
||||
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<Jsonb>,
|
||||
) -> crate::Result<Jsonb> {
|
||||
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());
|
||||
}
|
||||
}
|
||||
@@ -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<OwnedValue> {
|
||||
pub fn json_remove(args: &[OwnedValue], json_cache: &JsonCacheCell) -> crate::Result<OwnedValue> {
|
||||
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<OwnedValue> {
|
||||
json_string_to_db_type(json, el_type, OutputVariant::String)
|
||||
}
|
||||
|
||||
pub fn jsonb_remove(args: &[OwnedValue]) -> crate::Result<OwnedValue> {
|
||||
pub fn jsonb_remove(args: &[OwnedValue], json_cache: &JsonCacheCell) -> crate::Result<OwnedValue> {
|
||||
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<OwnedValue> {
|
||||
Ok(OwnedValue::Blob(Rc::new(json.data())))
|
||||
}
|
||||
|
||||
pub fn json_replace(args: &[OwnedValue]) -> crate::Result<OwnedValue> {
|
||||
pub fn json_replace(args: &[OwnedValue], json_cache: &JsonCacheCell) -> crate::Result<OwnedValue> {
|
||||
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<OwnedValue> {
|
||||
json_string_to_db_type(json, el_type, super::OutputVariant::String)
|
||||
}
|
||||
|
||||
pub fn jsonb_replace(args: &[OwnedValue]) -> crate::Result<OwnedValue> {
|
||||
pub fn jsonb_replace(args: &[OwnedValue], json_cache: &JsonCacheCell) -> crate::Result<OwnedValue> {
|
||||
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<OwnedValue> {
|
||||
json_string_to_db_type(json, el_type, OutputVariant::Binary)
|
||||
}
|
||||
|
||||
pub fn json_insert(args: &[OwnedValue]) -> crate::Result<OwnedValue> {
|
||||
pub fn json_insert(args: &[OwnedValue], json_cache: &JsonCacheCell) -> crate::Result<OwnedValue> {
|
||||
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<OwnedValue> {
|
||||
json_string_to_db_type(json, el_type, OutputVariant::String)
|
||||
}
|
||||
|
||||
pub fn jsonb_insert(args: &[OwnedValue]) -> crate::Result<OwnedValue> {
|
||||
pub fn jsonb_insert(args: &[OwnedValue], json_cache: &JsonCacheCell) -> crate::Result<OwnedValue> {
|
||||
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();
|
||||
|
||||
@@ -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<u8>,
|
||||
}
|
||||
|
||||
304
core/json/mod.rs
304
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<OwnedValue> {
|
||||
let jsonbin = convert_dbtype_to_jsonb(json_value, Conv::Strict);
|
||||
pub fn jsonb(json_value: &OwnedValue, cache: &JsonCacheCell) -> crate::Result<OwnedValue> {
|
||||
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<Json
|
||||
}
|
||||
}
|
||||
|
||||
pub fn curry_convert_dbtype_to_jsonb(strict: Conv) -> impl Fn(&OwnedValue) -> crate::Result<Jsonb> {
|
||||
move |val| convert_dbtype_to_jsonb(val, strict)
|
||||
}
|
||||
|
||||
fn get_json_value(json_value: &OwnedValue) -> crate::Result<Val> {
|
||||
match json_value {
|
||||
OwnedValue::Text(ref t) => match from_str::<Val>(t.as_str()) {
|
||||
@@ -189,17 +198,19 @@ pub fn jsonb_array(values: &[OwnedValue]) -> crate::Result<OwnedValue> {
|
||||
}
|
||||
|
||||
pub fn json_array_length(
|
||||
json_value: &OwnedValue,
|
||||
json_path: Option<&OwnedValue>,
|
||||
value: &OwnedValue,
|
||||
path: Option<&OwnedValue>,
|
||||
json_cache: &JsonCacheCell,
|
||||
) -> crate::Result<OwnedValue> {
|
||||
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<OwnedValue> {
|
||||
pub fn json_set(args: &[OwnedValue], json_cache: &JsonCacheCell) -> crate::Result<OwnedValue> {
|
||||
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<OwnedValue> {
|
||||
|
||||
/// 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<OwnedValue> {
|
||||
pub fn json_arrow_extract(
|
||||
value: &OwnedValue,
|
||||
path: &OwnedValue,
|
||||
json_cache: &JsonCacheCell,
|
||||
) -> crate::Result<OwnedValue> {
|
||||
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<OwnedValue> {
|
||||
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<OwnedValue> {
|
||||
pub fn json_extract(
|
||||
value: &OwnedValue,
|
||||
paths: &[OwnedValue],
|
||||
json_cache: &JsonCacheCell,
|
||||
) -> crate::Result<OwnedValue> {
|
||||
if let OwnedValue::Null = value {
|
||||
return Ok(OwnedValue::Null);
|
||||
}
|
||||
@@ -300,14 +323,20 @@ pub fn json_extract(value: &OwnedValue, paths: &[OwnedValue]) -> crate::Result<O
|
||||
if paths.is_empty() {
|
||||
return Ok(OwnedValue::Null);
|
||||
}
|
||||
let (json, element_type) = jsonb_extract_internal(value, paths)?;
|
||||
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(jsonb, paths)?;
|
||||
|
||||
let result = json_string_to_db_type(json, element_type, OutputVariant::ElementType)?;
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn jsonb_extract(value: &OwnedValue, paths: &[OwnedValue]) -> crate::Result<OwnedValue> {
|
||||
pub fn jsonb_extract(
|
||||
value: &OwnedValue,
|
||||
paths: &[OwnedValue],
|
||||
json_cache: &JsonCacheCell,
|
||||
) -> crate::Result<OwnedValue> {
|
||||
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());
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<NonZero<usize>, OwnedValue>,
|
||||
halt_state: Option<HaltState>,
|
||||
#[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,
|
||||
|
||||
Reference in New Issue
Block a user