Skip to content

Commit f8f4fcc

Browse files
gkorlandoshadmi
andauthored
Add support for Copy (RedisJSON#758)
* add support for Copy Co-authored-by: oshadmi <omer.shadmi@redislabs.com>
1 parent bfa26d4 commit f8f4fcc

File tree

3 files changed

+83
-2
lines changed

3 files changed

+83
-2
lines changed

src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ pub static REDIS_JSON_TYPE: RedisType = RedisType::new(
5959

6060
free_effort: None,
6161
unlink: None,
62-
copy: None,
62+
copy: Some(redisjson::type_methods::copy),
6363
defrag: None,
6464
},
6565
);

src/redisjson.rs

+23-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
// User-provided JSON is converted to a tree. This tree is stored transparently in Redis.
55
// It can be operated on (e.g. INCR) and serialized back to JSON.
66

7-
use redis_module::raw::{self};
7+
use redis_module::raw;
8+
89
use std::os::raw::{c_int, c_void};
910

1011
use crate::backward;
@@ -232,6 +233,27 @@ pub mod type_methods {
232233
raw::save_string(rdb, cjson.to_str().unwrap());
233234
}
234235

236+
/// # Safety
237+
#[allow(non_snake_case, unused)]
238+
pub unsafe extern "C" fn copy(
239+
fromkey: *mut raw::RedisModuleString,
240+
tokey: *mut raw::RedisModuleString,
241+
value: *const c_void,
242+
) -> *mut c_void {
243+
match get_manager_type() {
244+
ManagerType::SerdeValue => {
245+
let v = unsafe { &*value.cast::<RedisJSON<serde_json::Value>>() };
246+
let value = v.data.clone();
247+
Box::into_raw(Box::new(value)).cast::<c_void>()
248+
}
249+
ManagerType::IValue => {
250+
let v = unsafe { &*value.cast::<RedisJSON<ijson::IValue>>() };
251+
let value = v.data.clone();
252+
Box::into_raw(Box::new(value)).cast::<c_void>()
253+
}
254+
}
255+
}
256+
235257
/// # Safety
236258
#[allow(non_snake_case, unused)]
237259
pub unsafe extern "C" fn mem_usage(value: *const c_void) -> usize {

tests/pytest/test.py

+59
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# -*- coding: utf-8 -*-
22

3+
from functools import reduce
34
import sys
45
import os
56
import redis
@@ -1053,6 +1054,64 @@ def testInfoEverything(env):
10531054
res = r.execute_command('INFO', 'EVERYTHING')
10541055
r.assertFalse(res['modules'] is None)
10551056

1057+
def testCopyCommand(env):
1058+
"""Test COPY command and make sure behavior of json keys is similar to hash keys"""
1059+
1060+
env.skipOnCluster()
1061+
r = env
1062+
1063+
values = {"foo": "bar", "fu": "wunderbar"}
1064+
1065+
### Copy json to a new key (from json1 to json2)
1066+
r.assertOk(r.execute_command('JSON.SET', 'json1', '$', json.dumps(values)))
1067+
r.assertTrue(r.execute_command('COPY', 'json1', 'json2'))
1068+
# Check new values
1069+
res = r.execute_command('JSON.GET', 'json1', '$')
1070+
r.assertEqual(json.loads(res), [values])
1071+
res = r.execute_command('JSON.GET', 'json2', '$')
1072+
r.assertEqual(json.loads(res), [values])
1073+
1074+
### Copy hash to a new key (from hash1 to hash2)
1075+
hash_values = list(reduce(lambda acc, v: acc + v, values.items()))
1076+
r.assertEqual(r.execute_command('HSET', 'hash1', *hash_values), int(len(hash_values) / 2))
1077+
r.assertTrue(r.execute_command('COPY', 'hash1', 'hash2'))
1078+
# Check new values
1079+
r.assertEqual(r.execute_command('HGETALL', 'hash1'), values)
1080+
r.assertEqual(r.execute_command('HGETALL', 'hash2'), values)
1081+
1082+
new_values = {"ganz": "neue"}
1083+
1084+
### Copy hash to an existing key
1085+
hash_values = list(reduce(lambda acc, v: acc + v, new_values.items()))
1086+
r.assertEqual(r.execute_command('HSET', 'hash3', *hash_values), int(len(hash_values) / 2))
1087+
# Do not overwrite without REPLACE (from hash to hash)
1088+
r.assertFalse(r.execute_command('COPY', 'hash3', 'hash2'))
1089+
# Do not overwrite without REPLACE (from hash to json)
1090+
r.assertFalse(r.execute_command('COPY', 'hash3', 'json2'))
1091+
# Overwrite with REPLACE (from hash to hash)
1092+
r.assertTrue(r.execute_command('COPY', 'hash3', 'hash2', 'REPLACE'))
1093+
# Overwrite with REPLACE (from hash to json)
1094+
r.assertTrue(r.execute_command('COPY', 'hash3', 'json2', 'REPLACE'))
1095+
# Check new values
1096+
r.assertEqual(r.execute_command('HGETALL', 'hash2'), new_values)
1097+
r.assertEqual(r.execute_command('HGETALL', 'json2'), new_values)
1098+
1099+
### Copy json to an existing key
1100+
r.assertOk(r.execute_command('JSON.SET', 'json3', '$', json.dumps(new_values)))
1101+
# Do not overwrite without REPLACE (from json to json)
1102+
r.assertFalse(r.execute_command('COPY', 'json3', 'json2'))
1103+
# Do not overwrite without REPLACE (from json to hash)
1104+
r.assertFalse(r.execute_command('COPY', 'json3', 'hash2'))
1105+
# Overwrite with REPLACE (from json to json)
1106+
r.assertTrue(r.execute_command('COPY', 'json3', 'json2', 'REPLACE'))
1107+
# Overwrite with REPLACE (from json to hash)
1108+
r.assertTrue(r.execute_command('COPY', 'json3', 'hash2', 'REPLACE'))
1109+
# Check new values
1110+
res = r.execute_command('JSON.GET', 'json2', '$')
1111+
r.assertEqual(json.loads(res), [new_values])
1112+
res = r.execute_command('JSON.GET', 'hash2', '$')
1113+
r.assertEqual(json.loads(res), [new_values])
1114+
10561115

10571116
# class CacheTestCase(BaseReJSONTest):
10581117
# @property

0 commit comments

Comments
 (0)