Skip to content

Commit a6156e3

Browse files
author
DvirDukhan
authored
Merge pull request #362 from RedisLabsModules/dvirdu_add_openkey_flags
open key with flags
2 parents 12ba3dd + a5f47ab commit a6156e3

File tree

11 files changed

+214
-6
lines changed

11 files changed

+214
-6
lines changed

.circleci/config.yml

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ commands:
7171
parameters:
7272
redis_version:
7373
type: string
74-
default: "7.2-rc1"
74+
default: "7.2"
7575
getredis_params:
7676
type: string
7777
default: ""
@@ -101,7 +101,7 @@ commands:
101101
default: ""
102102
redis_version:
103103
type: string
104-
default: "7.2-rc1"
104+
default: "7.2"
105105
getredis_params:
106106
type: string
107107
default: ""
@@ -111,6 +111,11 @@ commands:
111111
- install-prerequisites:
112112
redis_version: <<parameters.redis_version>>
113113
getredis_params: <<parameters.getredis_params>>
114+
- run:
115+
name: patch macos tests # Avoid AVX with Regex with CircleCI since virtualization layer doesn't support it. Use sed to replace the relevant entry in cargo.toml
116+
command: |
117+
if [[ $(uname -s) == Darwin ]]; then sed -i 's/regex = "1"/regex = { version = "1", features = ["perf", "unicode"] }/g' Cargo.toml; fi
118+
cat Cargo.toml
114119
- restore_cache:
115120
keys:
116121
- v3-dependencies-{{ arch }}-{{ checksum "Cargo.toml" }}
@@ -203,7 +208,7 @@ jobs:
203208

204209
build-macos-x64:
205210
macos:
206-
xcode: 13.2.1
211+
xcode: 13.4.1
207212
resource_class: macos.x86.medium.gen2
208213
steps:
209214
- build-steps

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,5 @@ dump.rdb
2121

2222
# Debugger-related files:
2323
.gdb_history
24+
25+
venv/

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,10 @@ crate-type = ["cdylib"]
112112
name = "response"
113113
crate-type = ["cdylib"]
114114

115+
[[example]]
116+
name = "open_key_with_flags"
117+
crate-type = ["cdylib"]
118+
115119
[dependencies]
116120
bitflags = "2"
117121
libc = "0.2"

build.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
#!/usr/bin/env sh
2-
cargo build --features experimental-api,test --all --all-targets
2+
cargo build --all --all-targets --no-default-features
33

44

examples/open_key_with_flags.rs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
use redis_module::{
2+
key::KeyFlags, raw, redis_module, Context, NextArg, RedisError, RedisResult, RedisString,
3+
RedisValue,
4+
};
5+
use redis_module_macros::command;
6+
7+
#[command(
8+
{
9+
name: "open_key_with_flags.read",
10+
flags: [Write, DenyOOM],
11+
arity: 2,
12+
key_spec: [
13+
{
14+
flags: [ReadOnly, Access],
15+
begin_search: Index({ index : 1 }),
16+
find_keys: Range({ last_key : 1, steps : 1, limit : 1}),
17+
}
18+
]
19+
20+
}
21+
)]
22+
fn read(ctx: &Context, args: Vec<RedisString>) -> RedisResult {
23+
if args.len() < 2 {
24+
return Err(RedisError::WrongArity);
25+
}
26+
27+
let mut args = args.into_iter().skip(1);
28+
let key_name = args.next_arg()?;
29+
let _ = ctx.open_key_with_flags(&key_name, KeyFlags::NOEFFECTS);
30+
Ok(RedisValue::SimpleStringStatic("OK"))
31+
}
32+
33+
#[command(
34+
{
35+
name: "open_key_with_flags.write",
36+
flags: [Write, DenyOOM],
37+
arity: 2,
38+
key_spec: [
39+
{
40+
flags: [ReadWrite, Access],
41+
begin_search: Index({ index : 1 }),
42+
find_keys: Range({ last_key : 1, steps : 1, limit : 1}),
43+
}
44+
]
45+
46+
}
47+
)]
48+
fn write(ctx: &Context, args: Vec<RedisString>) -> RedisResult {
49+
if args.len() < 2 {
50+
return Err(RedisError::WrongArity);
51+
}
52+
53+
let mut args = args.into_iter().skip(1);
54+
let key_name = args.next_arg()?;
55+
let _ = ctx.open_key_writable_with_flags(&key_name, KeyFlags::NOEFFECTS);
56+
Ok(RedisValue::SimpleStringStatic("OK"))
57+
}
58+
59+
//////////////////////////////////////////////////////
60+
61+
redis_module! {
62+
name: "open_key_with_flags",
63+
version: 1,
64+
allocator: (redis_module::alloc::RedisAlloc, redis_module::alloc::RedisAlloc),
65+
data_types: [],
66+
commands: [],
67+
}

src/context/mod.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use std::os::raw::{c_char, c_int, c_long, c_longlong};
77
use std::ptr::{self, NonNull};
88
use std::sync::atomic::{AtomicPtr, Ordering};
99

10-
use crate::key::{RedisKey, RedisKeyWritable};
10+
use crate::key::{KeyFlags, RedisKey, RedisKeyWritable};
1111
use crate::logging::RedisLogLevel;
1212
use crate::raw::{ModuleOptions, Version};
1313
use crate::redisvalue::RedisValueKey;
@@ -614,11 +614,25 @@ impl Context {
614614
RedisKey::open(self.ctx, key)
615615
}
616616

617+
#[must_use]
618+
pub fn open_key_with_flags(&self, key: &RedisString, flags: KeyFlags) -> RedisKey {
619+
RedisKey::open_with_flags(self.ctx, key, flags)
620+
}
621+
617622
#[must_use]
618623
pub fn open_key_writable(&self, key: &RedisString) -> RedisKeyWritable {
619624
RedisKeyWritable::open(self.ctx, key)
620625
}
621626

627+
#[must_use]
628+
pub fn open_key_writable_with_flags(
629+
&self,
630+
key: &RedisString,
631+
flags: KeyFlags,
632+
) -> RedisKeyWritable {
633+
RedisKeyWritable::open_with_flags(self.ctx, key, flags)
634+
}
635+
622636
pub fn replicate_verbatim(&self) {
623637
raw::replicate_verbatim(self.ctx);
624638
}

src/key.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,19 @@ use std::ptr::NonNull;
77
use std::time::Duration;
88

99
use libc::size_t;
10+
use std::os::raw::c_int;
1011

1112
use raw::KeyType;
1213

1314
use crate::native_types::RedisType;
1415
use crate::raw;
1516
use crate::redismodule::REDIS_OK;
17+
pub use crate::redisraw::bindings::*;
1618
use crate::stream::StreamIterator;
1719
use crate::RedisError;
1820
use crate::RedisResult;
1921
use crate::RedisString;
22+
use bitflags::bitflags;
2023

2124
/// `RedisKey` is an abstraction over a Redis key that allows readonly
2225
/// operations.
@@ -32,6 +35,21 @@ pub enum KeyMode {
3235
ReadWrite,
3336
}
3437

38+
bitflags! {
39+
pub struct KeyFlags: c_int {
40+
/// Avoid touching the LRU/LFU of the key when opened.
41+
const NOTOUCH = REDISMODULE_OPEN_KEY_NOTOUCH as c_int;
42+
/// Don't trigger keyspace event on key misses.
43+
const NONOTIFY = REDISMODULE_OPEN_KEY_NONOTIFY as c_int;
44+
/// Don't update keyspace hits/misses counters.
45+
const NOSTATS = REDISMODULE_OPEN_KEY_NOSTATS as c_int;
46+
/// Avoid deleting lazy expired keys.
47+
const NOEXPIRE = REDISMODULE_OPEN_KEY_NOEXPIRE as c_int;
48+
/// Avoid any effects from fetching the key.
49+
const NOEFFECTS = REDISMODULE_OPEN_KEY_NOEFFECTS as c_int;
50+
}
51+
}
52+
3553
#[derive(Debug)]
3654
pub struct RedisKey {
3755
pub(crate) ctx: *mut raw::RedisModuleCtx,
@@ -50,6 +68,16 @@ impl RedisKey {
5068
Self { ctx, key_inner }
5169
}
5270

71+
pub(crate) fn open_with_flags(
72+
ctx: *mut raw::RedisModuleCtx,
73+
key: &RedisString,
74+
flags: KeyFlags,
75+
) -> Self {
76+
let key_inner =
77+
raw::open_key_with_flags(ctx, key.inner, to_raw_mode(KeyMode::Read), flags.bits());
78+
Self { ctx, key_inner }
79+
}
80+
5381
pub(crate) const fn from_raw_parts(
5482
ctx: *mut raw::RedisModuleCtx,
5583
key_inner: *mut raw::RedisModuleKey,
@@ -176,6 +204,20 @@ impl RedisKeyWritable {
176204
Self { ctx, key_inner }
177205
}
178206

207+
pub(crate) fn open_with_flags(
208+
ctx: *mut raw::RedisModuleCtx,
209+
key: &RedisString,
210+
flags: KeyFlags,
211+
) -> Self {
212+
let key_inner = raw::open_key_with_flags(
213+
ctx,
214+
key.inner,
215+
to_raw_mode(KeyMode::ReadWrite),
216+
flags.bits(),
217+
);
218+
Self { ctx, key_inner }
219+
}
220+
179221
/// Detects whether the value stored in a Redis key is empty.
180222
///
181223
/// Note that an empty key can be reliably detected by looking for a null

src/raw.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,19 @@ pub fn open_key(
348348
unsafe { RedisModule_OpenKey.unwrap()(ctx, keyname, mode.bits()).cast::<RedisModuleKey>() }
349349
}
350350

351+
#[allow(clippy::not_unsafe_ptr_arg_deref)]
352+
#[inline]
353+
pub(crate) fn open_key_with_flags(
354+
ctx: *mut RedisModuleCtx,
355+
keyname: *mut RedisModuleString,
356+
mode: KeyMode,
357+
flags: c_int,
358+
) -> *mut RedisModuleKey {
359+
unsafe {
360+
RedisModule_OpenKey.unwrap()(ctx, keyname, mode.bits() | flags).cast::<RedisModuleKey>()
361+
}
362+
}
363+
351364
#[allow(clippy::not_unsafe_ptr_arg_deref)]
352365
#[inline]
353366
pub fn reply_with_array(ctx: *mut RedisModuleCtx, len: c_long) -> Status {

test.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
#!/usr/bin/env sh
2-
cargo test --all-targets --features test,experimental-api
2+
cargo test --all --all-targets --no-default-features

tests/integration.rs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
use std::thread;
2+
use std::time::Duration;
3+
14
use crate::utils::{get_redis_connection, start_redis_server_with_module};
25
use anyhow::Context;
36
use anyhow::Result;
@@ -646,3 +649,59 @@ fn test_call_blocking() -> Result<()> {
646649

647650
Ok(())
648651
}
652+
653+
#[test]
654+
fn test_open_key_with_flags() -> Result<()> {
655+
let port: u16 = 6501;
656+
let _guards = vec![start_redis_server_with_module("open_key_with_flags", port)
657+
.with_context(|| "failed to start redis server")?];
658+
let mut con =
659+
get_redis_connection(port).with_context(|| "failed to connect to redis server")?;
660+
661+
// Avoid active expriation
662+
redis::cmd("DEBUG")
663+
.arg(&["SET-ACTIVE-EXPIRE", "0"])
664+
.query(&mut con)
665+
.with_context(|| "failed to run DEBUG SET-ACTIVE-EXPIRE")?;
666+
667+
for cmd in ["open_key_with_flags.write", "open_key_with_flags.read"].into_iter() {
668+
redis::cmd("set")
669+
.arg(&["x", "1"])
670+
.query(&mut con)
671+
.with_context(|| "failed to run string.set")?;
672+
673+
// Set experition time to 1 second.
674+
redis::cmd("pexpire")
675+
.arg(&["x", "1"])
676+
.query(&mut con)
677+
.with_context(|| "failed to run expire")?;
678+
679+
// Sleep for 2 seconds, ensure expiration time has passed.
680+
thread::sleep(Duration::from_millis(500));
681+
682+
// Open key as read only or ReadWrite with NOEFFECTS flag.
683+
let res = redis::cmd(cmd).arg(&["x"]).query(&mut con);
684+
assert_eq!(res, Ok(()));
685+
686+
// Get the number of expired keys.
687+
let stats: String = redis::cmd("info").arg(&["stats"]).query(&mut con)?;
688+
689+
// Find the number of expired keys, x, according to the substring "expired_keys:{x}"
690+
let expired_keys = stats
691+
.match_indices("expired_keys:")
692+
.next()
693+
.map(|(i, _)| &stats[i..i + "expired_keys:".len() + 1])
694+
.and_then(|s| s.split(':').nth(1))
695+
.and_then(|s| s.parse::<i32>().ok())
696+
.unwrap_or(-1);
697+
698+
// Ensure that no keys were expired.
699+
assert_eq!(expired_keys, 0);
700+
701+
// Delete key and reset stats
702+
redis::cmd("del").arg(&["x"]).query(&mut con)?;
703+
redis::cmd("config").arg(&["RESETSTAT"]).query(&mut con)?;
704+
}
705+
706+
Ok(())
707+
}

0 commit comments

Comments
 (0)