Skip to content

Commit 03aeae4

Browse files
MOD-11277 implement RLookupRow::write_key_by_name porting C RLookup_WriteKeyByName function
Co-authored-by: Tim Janus <tim@janus.rs>
1 parent c954350 commit 03aeae4

4 files changed

Lines changed: 160 additions & 10 deletions

File tree

src/redisearch_rs/rlookup/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ mod row;
1515

1616
pub use bindings::IndexSpecCache;
1717
pub use lookup::{
18-
RLookup, RLookupKey, RLookupKeyFlag, RLookupKeyFlags, RLookupOption, RLookupOptions,
18+
Cursor, CursorMut, RLookup, RLookupKey, RLookupKeyFlag, RLookupKeyFlags, RLookupOption,
19+
RLookupOptions,
1920
};
2021
pub use row::RLookupRow;

src/redisearch_rs/rlookup/src/lookup.rs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -230,13 +230,13 @@ struct KeyList<'a> {
230230
rowlen: u32,
231231
}
232232

233-
/// A cursor over a [`KeyList`].
233+
/// A cursor over an [`RLookup`] key list.
234234
pub struct Cursor<'list, 'a> {
235235
_rlookup: &'list KeyList<'a>,
236236
current: Option<NonNull<RLookupKey<'a>>>,
237237
}
238238

239-
/// A cursor over a [`KeyList`] with editing operations.
239+
/// A cursor over an [`RLookup`] key list with editing operations.
240240
pub struct CursorMut<'list, 'a> {
241241
_rlookup: &'list mut KeyList<'a>,
242242
current: Option<NonNull<RLookupKey<'a>>>,
@@ -721,7 +721,7 @@ impl Drop for KeyList<'_> {
721721
// ===== impl Cursor =====
722722

723723
impl<'list, 'a> Cursor<'list, 'a> {
724-
/// Move the cursor to the next [`RLookupKey`] in the [`KeyList`].
724+
/// Move the cursor to the next [`RLookupKey`] in the key list.
725725
///
726726
/// Note that contrary to [`Self::next`] this **does not** skip over hidden keys.
727727
pub fn move_next(&mut self) {
@@ -761,7 +761,7 @@ impl<'list, 'a> Cursor<'list, 'a> {
761761
impl<'list, 'a> Iterator for Cursor<'list, 'a> {
762762
type Item = &'list RLookupKey<'a>;
763763

764-
/// Advances the [`Cursor`] to the next [`RLookupKey`] in the [`KeyList`] and returns it.
764+
/// Advances the [`Cursor`] to the next [`RLookupKey`] in the key list and returns it.
765765
///
766766
/// This will automatically skip over any keys with the [`RLookupKeyFlag::Hidden`] flag.
767767
fn next(&mut self) -> Option<Self::Item> {
@@ -879,7 +879,7 @@ impl<'list, 'a> CursorMut<'list, 'a> {
879879
impl<'list, 'a> Iterator for CursorMut<'list, 'a> {
880880
type Item = Pin<&'list mut RLookupKey<'a>>;
881881

882-
/// Advances the [`CursorMut`] to the next [`RLookupKey`] in the [`KeyList`] and returns it.
882+
/// Advances the [`CursorMut`] to the next [`RLookupKey`] in the key list and returns it.
883883
///
884884
/// This will automatically skip over any keys with the [`RLookupKeyFlag::Hidden`] flag.
885885
fn next(&mut self) -> Option<Self::Item> {
@@ -938,6 +938,12 @@ impl<'a> RLookup<'a> {
938938
self.index_spec_cache = spcache;
939939
}
940940

941+
/// Find a [`RLookupKey`] in this `RLookup`'s key list by its `name`
942+
/// and return a [`Cursor`] pointing to the key if found.
943+
pub fn find_by_name(&self, name: &CStr) -> Option<Cursor<'_, 'a>> {
944+
self.keys.find_by_name(name)
945+
}
946+
941947
// ===== Get key for reading (create only if in schema and sortable) =====
942948

943949
/// Gets a key by its name from the lookup table, if not found it uses the schema as a fallback to search the key.
@@ -955,7 +961,8 @@ impl<'a> RLookup<'a> {
955961

956962
let available = self.keys.find_by_name(&name).is_some();
957963
if available {
958-
// FIXME: Duplication because of borrow-checker false positive. Duplication means performance implications.
964+
// FIXME: We cannot use let-some above because of a borrow-checker false positive.
965+
// This duplication might have performance implications.
959966
// See <https://github.com/rust-lang/rust/issues/54663>
960967
return self.keys.find_by_name(&name).unwrap().into_current();
961968
}

src/redisearch_rs/rlookup/src/row.rs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@
77
* GNU Affero General Public License v3 (AGPLv3).
88
*/
99

10+
use crate::{RLookup, RLookupKey, RLookupKeyFlag, RLookupKeyFlags};
1011
use sorting_vector::RSSortingVector;
12+
use std::{borrow::Cow, ffi::CStr};
1113
use value::RSValueTrait;
1214

13-
use crate::{RLookupKey, RLookupKeyFlag};
14-
1515
/// Row data for a lookup key. This abstracts the question of if the data comes from a borrowed [RSSortingVector]
1616
/// or from dynamic values stored in the row during processing.
1717
///
@@ -127,6 +127,26 @@ impl<'a, T: RSValueTrait> RLookupRow<'a, T> {
127127
prev
128128
}
129129

130+
/// Write a value to the lookup table *by-name*. This is useful for 'dynamic' keys
131+
/// for which it is not necessary to use the boilerplate of getting an explicit
132+
/// key.
133+
pub fn write_key_by_name(
134+
&mut self,
135+
rlookup: &mut RLookup<'a>,
136+
name: impl Into<Cow<'a, CStr>>,
137+
val: T,
138+
) {
139+
let name = name.into();
140+
let key = if let Some(cursor) = rlookup.find_by_name(&name) {
141+
cursor.into_current().expect("the cursor returned by `Keys::find_by_name` must have a current key. This is a bug!")
142+
} else {
143+
rlookup
144+
.get_key_write(name, RLookupKeyFlags::empty())
145+
.expect("`RLookup::get_key_write` must never return None for non-existent keys. This is a bug!")
146+
};
147+
self.write_key(key, val);
148+
}
149+
130150
/// Wipes the row, retaining its memory but decrementing the ref count of any included instance of `T`.
131151
/// This does not free all the memory consumed by the row, but simply resets
132152
/// the row data (preserving any caches) so that it may be refilled.

src/redisearch_rs/rlookup/tests/row.rs

Lines changed: 123 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,14 @@
77
* GNU Affero General Public License v3 (AGPLv3).
88
*/
99

10-
use rlookup::{RLookupKey, RLookupKeyFlag, RLookupKeyFlags, RLookupRow};
10+
use rlookup::{RLookup, RLookupKey, RLookupKeyFlag, RLookupKeyFlags, RLookupRow};
1111
use sorting_vector::RSSortingVector;
1212
use std::{
1313
ffi::CString,
14+
mem::offset_of,
1415
ops::{Deref, DerefMut},
16+
ptr::NonNull,
17+
sync::atomic::{AtomicUsize, Ordering},
1518
};
1619
use value::{RSValueMock, RSValueTrait};
1720

@@ -388,6 +391,125 @@ fn test_rlookup_get_item_priority_dynamic_over_static() {
388391
assert_eq!(result.unwrap().as_str(), Some("dynamic_value"));
389392
}
390393

394+
#[test]
395+
fn test_write_key_by_name_new_key() {
396+
// Test case: name is not yet part of the lookup and gets created
397+
let mut lookup = RLookup::new();
398+
let mut row = RLookupRow::new();
399+
400+
let key_name = CString::new("new_key").unwrap();
401+
let value = RSValueMock::create_string("test_value".to_string());
402+
403+
// Initially, row should be empty
404+
assert_eq!(row.len(), 0);
405+
406+
// Write the key
407+
row.write_key_by_name(&mut lookup, key_name.to_owned(), value.clone());
408+
409+
// Verify we can find the key by name
410+
let cursor = lookup.find_by_name(&key_name);
411+
assert!(cursor.is_some());
412+
413+
// Verify the rlookup row is in correct state
414+
assert_eq!(row.len(), 1);
415+
assert!(row.dyn_values()[0].is_some());
416+
assert_eq!(
417+
row.dyn_values()[0].as_ref().unwrap().as_str(),
418+
Some("test_value")
419+
);
420+
}
421+
422+
#[test]
423+
fn test_write_key_by_name_existing_key_overwrite() {
424+
// Test case: name is part of the lookup and its value gets overwritten
425+
let mut lookup = RLookup::new();
426+
let mut row = RLookupRow::new();
427+
428+
let key_name = CString::new("existing_key").unwrap();
429+
let initial_value = RSValueMock::create_string("initial_value".to_string());
430+
let new_value = RSValueMock::create_string("new_value".to_string());
431+
432+
// Write initial value
433+
row.write_key_by_name(&mut lookup, key_name.to_owned(), initial_value.clone());
434+
435+
// Verify initial state
436+
let cursor = lookup.find_by_name(&key_name).unwrap();
437+
assert!(cursor.into_current().is_some());
438+
assert_eq!(row.len(), 1);
439+
assert_eq!(
440+
row.dyn_values()[0].as_ref().unwrap().as_str(),
441+
initial_value.as_str()
442+
);
443+
444+
// Overwrite with new value - key count should not increase
445+
row.write_key_by_name(&mut lookup, key_name.to_owned(), new_value.clone());
446+
447+
let cursor = lookup.find_by_name(&key_name).unwrap();
448+
assert!(cursor.into_current().is_some());
449+
assert_eq!(row.len(), 1);
450+
assert_eq!(
451+
row.dyn_values()[0].as_ref().unwrap().as_str(),
452+
new_value.as_str()
453+
);
454+
}
455+
456+
#[test]
457+
fn test_write_multiple_different_keys() {
458+
// Test case: writing multiple different keys
459+
let mut lookup = RLookup::new();
460+
let mut row = RLookupRow::new();
461+
462+
let key1_name = CString::new("key1").unwrap();
463+
let key2_name = CString::new("key2").unwrap();
464+
let key3_name = CString::new("key3").unwrap();
465+
466+
let value1 = RSValueMock::create_string("value1".to_string());
467+
let value2 = RSValueMock::create_string("value2".to_string());
468+
let value3 = RSValueMock::create_string("value3".to_string());
469+
470+
// Write multiple keys
471+
row.write_key_by_name(&mut lookup, key1_name.to_owned(), value1.clone());
472+
row.write_key_by_name(&mut lookup, key2_name.to_owned(), value2.clone());
473+
row.write_key_by_name(&mut lookup, key3_name.to_owned(), value3.clone());
474+
475+
// Verify all keys were added
476+
assert_eq!(row.len(), 3);
477+
478+
for (key_name, value) in [
479+
(&key1_name, value1),
480+
(&key2_name, value2),
481+
(&key3_name, value3),
482+
] {
483+
let cursor = lookup.find_by_name(key_name);
484+
let key = cursor.unwrap().into_current().unwrap();
485+
assert!(row.dyn_values()[key.dstidx as usize].is_some());
486+
assert_eq!(
487+
row.dyn_values()[key.dstidx as usize]
488+
.as_ref()
489+
.unwrap()
490+
.as_str(),
491+
value.as_str(),
492+
);
493+
}
494+
}
495+
496+
/// Mock implementation of `IndexSpecCache_Decref` from spec.h for testing purposes
497+
#[unsafe(no_mangle)]
498+
extern "C" fn IndexSpecCache_Decref(spcache: Option<NonNull<ffi::IndexSpecCache>>) {
499+
let spcache = spcache.expect("`spcache` must not be null");
500+
let refcount = unsafe {
501+
spcache
502+
.byte_add(offset_of!(ffi::IndexSpecCache, refcount))
503+
.cast::<usize>()
504+
};
505+
506+
let refcount = unsafe { AtomicUsize::from_ptr(refcount.as_ptr()) };
507+
508+
if refcount.fetch_sub(1, Ordering::Relaxed) == 1 {
509+
drop(unsafe { Box::from_raw(spcache.as_ptr()) });
510+
}
511+
}
512+
391513
fn create_test_key(dstidx: u16, svidx: u16, flags: RLookupKeyFlags) -> RLookupKey<'static> {
392514
let str = format!("mock_key_{}_{}", dstidx, svidx);
393515
let cstring = CString::new(str).unwrap();

0 commit comments

Comments
 (0)