Skip to content

Commit 01ce8cc

Browse files
committed
worked version
1 parent 77321ed commit 01ce8cc

File tree

6 files changed

+355
-37
lines changed

6 files changed

+355
-37
lines changed

Cargo.lock

Lines changed: 49 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "php_rocksdb_rc"
3-
version = "0.4.0"
3+
version = "0.5.0"
44
edition = "2021"
55

66
[lib]
@@ -9,7 +9,9 @@ crate-type = ["cdylib"]
99

1010
[dependencies]
1111
ext-php-rs = "*"
12+
json-patch = "2.0.0"
1213
rust-rocksdb = { version = "^0.26", features = ["multi-threaded-cf"] }
14+
serde_json = "1.0.117"
1315

1416
[profile.integration-test]
1517
inherits = "dev"

README.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -371,16 +371,18 @@ echo $value; // Outputs: value2
371371
```
372372

373373
#### `merge(key: String, value: String, cf_name: Option<String>)`
374-
Merges a value into the database.
374+
Merges a value into the database using JSON Patch.
375375

376376
```php
377377
<?php
378-
$db->merge("json_obj_key", "{ employees: [ {first_name: john, last_name: doe}, {first_name: adam, last_name: smith}] }");
379-
$db->merge("json_obj_key", "employees[1].first_name = lucy");
380-
$db->merge("json_obj_key", "employees[0].last_name = dow");
378+
$db->merge("json_obj_key", '[ { "op": "replace", "path": "/employees/1/first_name", "value": "lucy" } ]');
379+
$db->merge("json_obj_key", '[ { "op": "replace", "path": "/employees/0/last_name", "value": "dow" } ]');
381380
?>
382381
```
383382

383+
384+
This method uses JSON Patch to update the JSON object in the database. For more details on JSON Patch, refer to [RFC 6902](https://datatracker.ietf.org/doc/html/rfc6902).
385+
384386
#### `delete(key: String, cf_name: Option<String>)`
385387
Deletes the key-value pair associated with the given key.
386388

src/lib.rs

Lines changed: 52 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,42 @@ use ext_php_rs::convert::IntoZval;
66
use ext_php_rs::error::Error;
77
use ext_php_rs::prelude::*;
88
use ext_php_rs::types::{ZendHashTable, Zval};
9-
use rust_rocksdb::{Options, DB};
9+
use json_patch::Patch;
10+
use rust_rocksdb::{
11+
ColumnFamilyDescriptor, DBWithThreadMode, MergeOperands, Options, SingleThreaded, DB,
12+
};
13+
use serde_json::{from_value, Value};
1014
use std::collections::HashMap;
1115
use std::time::Duration;
1216

1317
use crate::backup::RocksDBBackup;
1418
use crate::transaction::RocksDBTransaction;
1519
use crate::write_batch::RocksDBWriteBatch;
1620

21+
fn json_merge(
22+
_new_key: &[u8],
23+
existing_val: Option<&[u8]>,
24+
operands: &MergeOperands,
25+
) -> Option<Vec<u8>> {
26+
// Decode the existing value
27+
let mut doc: Value = if let Some(val) = existing_val {
28+
serde_json::from_slice(val).unwrap_or(Value::Array(vec![]))
29+
} else {
30+
Value::Array(vec![])
31+
};
32+
33+
// Process each operand
34+
for op in operands {
35+
if let Ok(patch) = serde_json::from_slice::<Value>(op) {
36+
let p: Patch = from_value(patch).unwrap();
37+
json_patch::patch(&mut doc, &p).unwrap();
38+
}
39+
}
40+
41+
// Serialize the updated JSON object back to bytes
42+
Some(serde_json::to_vec(&doc).unwrap())
43+
}
44+
1745
#[derive(Debug)]
1846
pub struct KeyValueResult {
1947
pub key: Option<String>,
@@ -34,7 +62,7 @@ impl IntoZval for KeyValueResult {
3462

3563
#[php_class]
3664
pub struct RocksDB {
37-
pub db: DB,
65+
pub db: DBWithThreadMode<SingleThreaded>,
3866
position: Option<Vec<u8>>,
3967
}
4068

@@ -46,13 +74,29 @@ impl RocksDB {
4674
opts.create_if_missing(true);
4775
opts.set_max_open_files(1000);
4876
opts.set_log_level(rust_rocksdb::LogLevel::Warn);
77+
opts.set_merge_operator_associative("json_merge", json_merge);
78+
79+
let cf_names = DB::list_cf(&opts, &path).unwrap_or(vec!["default".to_string()]);
80+
let cf_descriptors: Vec<ColumnFamilyDescriptor> = cf_names
81+
.iter()
82+
.map(|name| {
83+
let mut cf_opts = Options::default();
84+
cf_opts.set_merge_operator_associative("json_merge", json_merge);
85+
ColumnFamilyDescriptor::new(name, cf_opts)
86+
})
87+
.collect();
4988

5089
let db = match ttl_secs {
5190
Some(ttl) => {
5291
let duration = Duration::from_secs(ttl);
53-
DB::open_with_ttl(&opts, &path, duration)
92+
DBWithThreadMode::open_cf_descriptors_with_ttl(
93+
&opts,
94+
&path,
95+
cf_descriptors,
96+
duration,
97+
)
5498
}
55-
None => DB::open(&opts, &path),
99+
None => DBWithThreadMode::open_cf_descriptors(&opts, &path, cf_descriptors),
56100
};
57101

58102
match db {
@@ -146,14 +190,15 @@ impl RocksDB {
146190
}
147191
}
148192

149-
pub fn create_column_family(&self, cf_name: String) -> PhpResult<()> {
150-
let opts = Options::default();
193+
pub fn create_column_family(&mut self, cf_name: String) -> PhpResult<()> {
194+
let mut opts = Options::default();
195+
opts.set_merge_operator_associative("json_merge", json_merge);
151196
self.db
152197
.create_cf(&cf_name, &opts)
153198
.map_err(|e| e.to_string().into())
154199
}
155200

156-
pub fn drop_column_family(&self, cf_name: String) -> PhpResult<()> {
201+
pub fn drop_column_family(&mut self, cf_name: String) -> PhpResult<()> {
157202
self.db.drop_cf(&cf_name).map_err(|e| e.to_string().into())
158203
}
159204

tests/common/mod.rs

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,16 +38,21 @@ pub fn php_request(code: &str) -> String {
3838
}
3939

4040
pub fn php_request_file(script_filename: &str) -> String {
41-
let output = Command::new("php")
42-
// .arg(format!(
43-
// "-dextension={}/target/debug/lib{}.{}",
44-
// env::current_dir().unwrap().to_str().unwrap(),
45-
// env!("CARGO_PKG_NAME"),
46-
// std::env::consts::DLL_EXTENSION,
47-
// ))
48-
.arg(script_filename)
49-
.output()
50-
.expect("failed to execute PHP script");
41+
let mut command = Command::new("php");
42+
43+
// Check if the environment variable is set to disable the extension argument
44+
if env::var("DISABLE_EXTENSION_ARG").is_err() {
45+
command.arg(format!(
46+
"-dextension={}/target/debug/lib{}.{}",
47+
env::current_dir().unwrap().to_str().unwrap(),
48+
env!("CARGO_PKG_NAME"),
49+
std::env::consts::DLL_EXTENSION,
50+
));
51+
}
52+
53+
command.arg(script_filename);
54+
55+
let output = command.output().expect("failed to execute PHP script");
5156

5257
if output.status.success() {
5358
String::from_utf8(output.stdout).unwrap()

0 commit comments

Comments
 (0)