use std::{collections::HashSet, io::Cursor};
use wobject::set::set_object::SetObject as WoSetObject;
use crate::{
arg_slice::ArgSlice,
input_header::RespInputHeader,
inputs::ObjectInput,
objects::{
set::{
set_object::{SetObject, SetOperation},
set_object_impl::NO_COUNT,
},
types::object_output::ObjectOutput,
},
resp::{
cmd_strings as cs,
cmd_strings::write_error_raw,
objects::object_store_utils::{OBJ_TAG_SET, SyncObj, obj_load_sync, obj_save_or_gc_sync},
parser::resp_ext::{RespSliceExt, RespVecExt},
resp_server_session::RespServerSession,
},
session_parse_state::SessionParseState,
types::{GarnetObjectType, RespInputFlags},
};
const RESP_VERSION: u8 = 2;
pub(crate) fn set_from_blob(raw: &[u8]) -> SetObject {
if let Ok(wo) = WoSetObject::deserialize(&mut Cursor::new(raw)) {
return SetObject::from_members(wo.get_keys());
}
SetObject::deserialize(&mut Cursor::new(raw)).unwrap_or_default()
}
pub(crate) fn set_to_blob(obj: &SetObject) -> Vec<u8> {
let wo = WoSetObject::new();
{
let pin = wo.set.pin();
for member in obj.to_members() {
pin.insert(member);
}
}
let mut out = Vec::new();
if wo.serialize(&mut out).is_err() {
for member in obj.to_members() {
out.extend_from_slice(&(member.len() as u32).to_le_bytes());
out.extend_from_slice(&member);
}
}
out
}
fn make_input(
op: SetOperation,
args: &[&[u8]],
arg1: i32,
arg2: i32,
) -> (ObjectInput, Vec<Vec<u8>>) {
let backing: Vec<Vec<u8>> = args.iter().map(|a| a.to_vec()).collect();
let slices: Vec<ArgSlice> = backing
.iter()
.map(|b| ArgSlice::new(b.as_ptr(), b.len()))
.collect();
let mut parse_state = SessionParseState::new();
parse_state.initialize_with_args(&slices);
let mut header = RespInputHeader::new_with_type(GarnetObjectType::Set, RespInputFlags::empty());
header.set_sub_id(op as u8);
(
ObjectInput::new_with_state(header, &mut parse_state, arg1, arg2),
backing,
)
}
fn run_operate(
obj: &mut SetObject,
op: SetOperation,
args: &[&[u8]],
arg1: i32,
arg2: i32,
) -> ObjectOutput {
let (input, _backing) = make_input(op, args, arg1, arg2);
let mut obj_out = ObjectOutput::new();
obj.operate(&input, &mut obj_out, RESP_VERSION);
obj_out
}
pub(crate) enum SetLoad {
Degrade,
Error,
Missing,
Present(SetObject),
}
pub(crate) fn set_load_sync(
store: &wkv::BatchStoreSession<impl wdev::Device>,
key: &[u8],
output: &mut Vec<u8>,
) -> SetLoad {
match obj_load_sync(store, key, OBJ_TAG_SET) {
Ok(None) => SetLoad::Degrade,
Ok(Some(SyncObj::Missing)) => SetLoad::Missing,
Ok(Some(SyncObj::WrongType)) => {
write_error_raw(output, cs::RESP_ERR_WRONG_TYPE);
SetLoad::Error
}
Ok(Some(SyncObj::Present(p))) => SetLoad::Present(set_from_blob(&p)),
Err(_) => {
output.write_resp_error("generic error");
SetLoad::Error
}
}
}
pub(crate) fn set_save_or_gc(
store: &wkv::BatchStoreSession<impl wdev::Device>,
key: &[u8],
obj: &SetObject,
) -> Result<bool, ()> {
let payload = set_to_blob(obj);
obj_save_or_gc_sync(store, key, OBJ_TAG_SET, &payload, obj.set.is_empty()).map_err(|_| ())
}
enum Rmw {
Degrade,
Error,
Done { result1: i64, payload_written: bool },
}
fn rmw(
store: &wkv::BatchStoreSession<impl wdev::Device>,
key: &[u8],
op: SetOperation,
args: &[&[u8]],
arg1: i32,
arg2: i32,
output: &mut Vec<u8>,
) -> Rmw {
let (mut obj, existed) = match set_load_sync(store, key, output) {
SetLoad::Degrade => return Rmw::Degrade,
SetLoad::Error => return Rmw::Error,
SetLoad::Missing => (SetObject::new(), false),
SetLoad::Present(o) => (o, true),
};
let obj_out = run_operate(&mut obj, op, args, arg1, arg2);
let result1 = obj_out.result1;
if should_write_back(op, &obj_out, &obj, existed) {
match set_save_or_gc(store, key, &obj) {
Ok(true) => {}
Ok(false) => return Rmw::Degrade,
Err(()) => {
output.write_resp_error("generic error");
return Rmw::Error;
}
}
}
output.extend_from_slice(&obj_out.payload);
Rmw::Done {
result1,
payload_written: !obj_out.payload.is_empty(),
}
}
fn should_write_back(op: SetOperation, out: &ObjectOutput, obj: &SetObject, existed: bool) -> bool {
if is_read_only(op)
|| out.has_wrong_type()
|| out.payload.first() == Some(&b'-')
|| (!existed && obj.set.is_empty())
{
return false;
}
match op {
SetOperation::Srem => out.result1 > 0,
_ => true,
}
}
fn is_read_only(op: SetOperation) -> bool {
matches!(
op,
SetOperation::Scard
| SetOperation::Smembers
| SetOperation::Sismember
| SetOperation::Smismember
| SetOperation::Srandmember
| SetOperation::Sscan
)
}
fn load_many(
store: &wkv::BatchStoreSession<impl wdev::Device>,
keys: &[&[u8]],
output: &mut Vec<u8>,
) -> Result<Option<Vec<SetObject>>, ()> {
let mut objs = Vec::with_capacity(keys.len());
for key in keys {
match set_load_sync(store, key, output) {
SetLoad::Degrade => return Ok(None),
SetLoad::Error => return Err(()),
SetLoad::Missing => objs.push(SetObject::new()),
SetLoad::Present(o) => objs.push(o),
}
}
Ok(Some(objs))
}
impl RespServerSession {
pub fn set_add<'a, D: wdev::Device>(
&mut self,
parse_state: &[&[u8]],
store: &wkv::BatchStoreSession<'a, D>,
output: &mut Vec<u8>,
) -> wresp::Result<bool> {
if parse_state.len() < 2 {
cs::abort_with_wrong_number_of_arguments(output, "SADD");
return Ok(true);
}
let key = parse_state[0];
match rmw(
store,
key,
SetOperation::Sadd,
&parse_state[1..],
0,
0,
output,
) {
Rmw::Degrade => return Ok(false),
Rmw::Error => {}
Rmw::Done {
result1,
payload_written,
} => {
if !payload_written {
output.write_resp_int(result1);
}
}
}
Ok(true)
}
pub fn set_remove<'a, D: wdev::Device>(
&mut self,
parse_state: &[&[u8]],
store: &wkv::BatchStoreSession<'a, D>,
output: &mut Vec<u8>,
) -> wresp::Result<bool> {
if parse_state.len() < 2 {
cs::abort_with_wrong_number_of_arguments(output, "SREM");
return Ok(true);
}
let key = parse_state[0];
match set_load_sync(store, key, output) {
SetLoad::Degrade => return Ok(false),
SetLoad::Error => {}
SetLoad::Missing => output.extend_from_slice(cs::RESP_RETURN_VAL_0),
SetLoad::Present(mut obj) => {
let obj_out = run_operate(&mut obj, SetOperation::Srem, &parse_state[1..], 0, 0);
if obj_out.result1 > 0 {
match set_save_or_gc(store, key, &obj) {
Ok(true) => {}
Ok(false) => return Ok(false),
Err(()) => {
output.write_resp_error("generic error");
return Ok(true);
}
}
}
output.write_resp_int(obj_out.result1);
}
}
Ok(true)
}
pub fn set_length<'a, D: wdev::Device>(
&mut self,
parse_state: &[&[u8]],
store: &wkv::BatchStoreSession<'a, D>,
output: &mut Vec<u8>,
) -> wresp::Result<bool> {
if parse_state.len() != 1 {
cs::abort_with_wrong_number_of_arguments(output, "SCARD");
return Ok(true);
}
let key = parse_state[0];
match set_load_sync(store, key, output) {
SetLoad::Degrade => return Ok(false),
SetLoad::Error => {}
SetLoad::Missing => output.extend_from_slice(cs::RESP_RETURN_VAL_0),
SetLoad::Present(mut obj) => {
let obj_out = run_operate(&mut obj, SetOperation::Scard, &[], 0, 0);
output.write_resp_int(obj_out.result1);
}
}
Ok(true)
}
pub fn set_members<'a, D: wdev::Device>(
&mut self,
parse_state: &[&[u8]],
store: &wkv::BatchStoreSession<'a, D>,
output: &mut Vec<u8>,
) -> wresp::Result<bool> {
if parse_state.len() != 1 {
cs::abort_with_wrong_number_of_arguments(output, "SMEMBERS");
return Ok(true);
}
let key = parse_state[0];
match set_load_sync(store, key, output) {
SetLoad::Degrade => return Ok(false),
SetLoad::Error => {}
SetLoad::Missing => output.extend_from_slice(cs::RESP_EMPTYLIST),
SetLoad::Present(mut obj) => {
let obj_out = run_operate(&mut obj, SetOperation::Smembers, &[], 0, 0);
output.extend_from_slice(&obj_out.payload);
}
}
Ok(true)
}
pub fn set_is_member<'a, D: wdev::Device>(
&mut self,
parse_state: &[&[u8]],
store: &wkv::BatchStoreSession<'a, D>,
output: &mut Vec<u8>,
) -> wresp::Result<bool> {
if parse_state.len() != 2 {
cs::abort_with_wrong_number_of_arguments(output, "SISMEMBER");
return Ok(true);
}
let key = parse_state[0];
match set_load_sync(store, key, output) {
SetLoad::Degrade => return Ok(false),
SetLoad::Error => {}
SetLoad::Missing => output.extend_from_slice(cs::RESP_RETURN_VAL_0),
SetLoad::Present(mut obj) => {
let obj_out = run_operate(&mut obj, SetOperation::Sismember, &parse_state[1..], 0, 0);
output.extend_from_slice(&obj_out.payload);
}
}
Ok(true)
}
pub fn set_multi_is_member<'a, D: wdev::Device>(
&mut self,
parse_state: &[&[u8]],
store: &wkv::BatchStoreSession<'a, D>,
output: &mut Vec<u8>,
) -> wresp::Result<bool> {
if parse_state.len() < 2 {
cs::abort_with_wrong_number_of_arguments(output, "SMISMEMBER");
return Ok(true);
}
let key = parse_state[0];
match set_load_sync(store, key, output) {
SetLoad::Degrade => return Ok(false),
SetLoad::Error => {}
SetLoad::Missing => {
output.write_resp_array_len(parse_state.len() - 1);
for _ in 1..parse_state.len() {
output.extend_from_slice(cs::RESP_RETURN_VAL_0);
}
}
SetLoad::Present(mut obj) => {
let obj_out = run_operate(&mut obj, SetOperation::Smismember, &parse_state[1..], 0, 0);
output.extend_from_slice(&obj_out.payload);
}
}
Ok(true)
}
pub fn set_pop<'a, D: wdev::Device>(
&mut self,
parse_state: &[&[u8]],
store: &wkv::BatchStoreSession<'a, D>,
output: &mut Vec<u8>,
) -> wresp::Result<bool> {
if parse_state.is_empty() || parse_state.len() > 2 {
cs::abort_with_wrong_number_of_arguments(output, "SPOP");
return Ok(true);
}
let key = parse_state[0];
let count_parameter = if parse_state.len() == 2 {
match parse_state[1].try_parse_i64() {
Some(c) if (0..=i64::from(i32::MAX)).contains(&c) => c as i32,
_ => {
cs::abort_with_error_message(output, cs::RESP_ERR_GENERIC_VALUE_IS_NOT_INTEGER);
return Ok(true);
}
}
} else {
NO_COUNT
};
if count_parameter == 0 {
output.extend_from_slice(cs::RESP_EMPTYLIST);
return Ok(true);
}
match set_load_sync(store, key, output) {
SetLoad::Degrade => return Ok(false),
SetLoad::Error => {}
SetLoad::Missing => output.write_resp_null(),
SetLoad::Present(mut obj) => {
let obj_out = run_operate(&mut obj, SetOperation::Spop, &[], count_parameter, 0);
match set_save_or_gc(store, key, &obj) {
Ok(true) => {}
Ok(false) => return Ok(false),
Err(()) => {
output.write_resp_error("generic error");
return Ok(true);
}
}
output.extend_from_slice(&obj_out.payload);
}
}
Ok(true)
}
pub fn set_random_member<'a, D: wdev::Device>(
&mut self,
parse_state: &[&[u8]],
store: &wkv::BatchStoreSession<'a, D>,
output: &mut Vec<u8>,
) -> wresp::Result<bool> {
if parse_state.is_empty() || parse_state.len() > 2 {
cs::abort_with_wrong_number_of_arguments(output, "SRANDMEMBER");
return Ok(true);
}
let key = parse_state[0];
let count_parameter = if parse_state.len() == 2 {
match parse_state[1].try_parse_i64() {
Some(c) if (i64::from(i32::MIN)..=i64::from(i32::MAX)).contains(&c) => c as i32,
_ => {
cs::abort_with_error_message(output, cs::RESP_ERR_GENERIC_VALUE_IS_NOT_INTEGER);
return Ok(true);
}
}
} else {
NO_COUNT
};
if count_parameter == 0 {
output.extend_from_slice(cs::RESP_EMPTYLIST);
return Ok(true);
}
let seed = fastrand::i32(..);
match set_load_sync(store, key, output) {
SetLoad::Degrade => return Ok(false),
SetLoad::Error => {}
SetLoad::Missing => {
if parse_state.len() == 2 {
output.extend_from_slice(cs::RESP_EMPTYLIST);
} else {
output.write_resp_null();
}
}
SetLoad::Present(mut obj) => {
let obj_out = run_operate(
&mut obj,
SetOperation::Srandmember,
&[],
count_parameter,
seed,
);
output.extend_from_slice(&obj_out.payload);
}
}
Ok(true)
}
pub fn set_move<'a, D: wdev::Device>(
&mut self,
parse_state: &[&[u8]],
store: &wkv::BatchStoreSession<'a, D>,
output: &mut Vec<u8>,
) -> wresp::Result<bool> {
if parse_state.len() != 3 {
cs::abort_with_wrong_number_of_arguments(output, "SMOVE");
return Ok(true);
}
let source_key = parse_state[0];
let destination_key = parse_state[1];
let member = parse_state[2];
let mut src = match set_load_sync(store, source_key, output) {
SetLoad::Degrade => return Ok(false),
SetLoad::Missing => {
output.extend_from_slice(cs::RESP_RETURN_VAL_0);
return Ok(true);
}
SetLoad::Error => return Ok(true),
SetLoad::Present(o) => o,
};
if source_key == destination_key {
output.extend_from_slice(cs::RESP_RETURN_VAL_0);
return Ok(true);
}
let mut dst = match set_load_sync(store, destination_key, output) {
SetLoad::Degrade => return Ok(false),
SetLoad::Error => return Ok(true),
SetLoad::Missing => SetObject::new(),
SetLoad::Present(o) => o,
};
if !src.set.remove(member) {
output.extend_from_slice(cs::RESP_RETURN_VAL_0);
return Ok(true);
}
src.update_size(member, false);
dst.set.insert(member.to_vec());
dst.update_size(member, true);
match set_save_or_gc(store, source_key, &src) {
Ok(true) => {}
Ok(false) => return Ok(false),
Err(()) => {
output.write_resp_error("generic error");
return Ok(true);
}
}
match set_save_or_gc(store, destination_key, &dst) {
Ok(true) => {}
Ok(false) => return Ok(false),
Err(()) => {
output.write_resp_error("generic error");
return Ok(true);
}
}
output.extend_from_slice(cs::RESP_RETURN_VAL_1);
Ok(true)
}
pub fn set_intersect<'a, D: wdev::Device>(
&mut self,
parse_state: &[&[u8]],
store: &wkv::BatchStoreSession<'a, D>,
output: &mut Vec<u8>,
) -> wresp::Result<bool> {
if parse_state.is_empty() {
cs::abort_with_wrong_number_of_arguments(output, "SINTER");
return Ok(true);
}
let objs = match load_many(store, parse_state, output) {
Ok(Some(objs)) => objs,
Ok(None) => return Ok(false),
Err(()) => return Ok(true),
};
let result = intersect_sets(&objs);
write_set_members(&result, output);
Ok(true)
}
pub fn set_intersect_store<'a, D: wdev::Device>(
&mut self,
parse_state: &[&[u8]],
store: &wkv::BatchStoreSession<'a, D>,
output: &mut Vec<u8>,
) -> wresp::Result<bool> {
if parse_state.len() < 2 {
cs::abort_with_wrong_number_of_arguments(output, "SINTERSTORE");
return Ok(true);
}
let dst = parse_state[0];
let objs = match load_many(store, &parse_state[1..], output) {
Ok(Some(objs)) => objs,
Ok(None) => return Ok(false),
Err(()) => return Ok(true),
};
let result = intersect_sets(&objs);
combine_store(self, dst, &result, store, output)
}
pub fn set_intersect_length<'a, D: wdev::Device>(
&mut self,
parse_state: &[&[u8]],
store: &wkv::BatchStoreSession<'a, D>,
output: &mut Vec<u8>,
) -> wresp::Result<bool> {
if parse_state.len() < 2 {
cs::abort_with_wrong_number_of_arguments(output, "SINTERCARD");
return Ok(true);
}
let Some(num_keys) = parse_state[0].try_parse_i64() else {
cs::abort_with_error_message(output, cs::RESP_ERR_GENERIC_VALUE_IS_NOT_INTEGER);
return Ok(true);
};
if num_keys < 1 {
cs::abort_with_error_message(output, "ERR numkeys should be greater than 0");
return Ok(true);
}
if parse_state.len() < num_keys as usize + 1 {
cs::abort_with_error_message(output, "ERR numkeys should be greater than 0");
return Ok(true);
}
let mut limit: Option<i64> = None;
if parse_state.len() > num_keys as usize + 1 {
if !parse_state[num_keys as usize + 1].eq_ignore_ascii_case(b"LIMIT")
|| parse_state.len() != num_keys as usize + 3
{
cs::abort_with_error_message(output, cs::RESP_ERR_GENERIC_SYNTAX_ERROR);
return Ok(true);
}
let Some(limit_val) = parse_state[num_keys as usize + 2].try_parse_i64() else {
cs::abort_with_error_message(output, cs::RESP_ERR_GENERIC_VALUE_IS_NOT_INTEGER);
return Ok(true);
};
if limit_val < 0 {
cs::abort_with_error_message(output, "ERR LIMIT can't be negative");
return Ok(true);
}
limit = Some(limit_val);
}
let keys = &parse_state[1..=num_keys as usize];
let objs = match load_many(store, keys, output) {
Ok(Some(objs)) => objs,
Ok(None) => return Ok(false),
Err(()) => return Ok(true),
};
let mut card = intersect_sets(&objs).set.len() as i64;
if let Some(limit) = limit
&& limit > 0
{
card = card.min(limit);
}
output.write_resp_int(card);
Ok(true)
}
pub fn set_union<'a, D: wdev::Device>(
&mut self,
parse_state: &[&[u8]],
store: &wkv::BatchStoreSession<'a, D>,
output: &mut Vec<u8>,
) -> wresp::Result<bool> {
if parse_state.is_empty() {
cs::abort_with_wrong_number_of_arguments(output, "SUNION");
return Ok(true);
}
let objs = match load_many(store, parse_state, output) {
Ok(Some(objs)) => objs,
Ok(None) => return Ok(false),
Err(()) => return Ok(true),
};
let mut result = SetObject::new();
for obj in &objs {
for member in obj.to_members() {
result.set.insert(member);
}
}
write_set_members(&result, output);
Ok(true)
}
pub fn set_union_store<'a, D: wdev::Device>(
&mut self,
parse_state: &[&[u8]],
store: &wkv::BatchStoreSession<'a, D>,
output: &mut Vec<u8>,
) -> wresp::Result<bool> {
if parse_state.len() < 2 {
cs::abort_with_wrong_number_of_arguments(output, "SUNIONSTORE");
return Ok(true);
}
let dst = parse_state[0];
let objs = match load_many(store, &parse_state[1..], output) {
Ok(Some(objs)) => objs,
Ok(None) => return Ok(false),
Err(()) => return Ok(true),
};
let mut result = SetObject::new();
for obj in &objs {
for member in obj.to_members() {
result.set.insert(member);
}
}
combine_store(self, dst, &result, store, output)
}
pub fn set_diff<'a, D: wdev::Device>(
&mut self,
parse_state: &[&[u8]],
store: &wkv::BatchStoreSession<'a, D>,
output: &mut Vec<u8>,
) -> wresp::Result<bool> {
if parse_state.is_empty() {
cs::abort_with_wrong_number_of_arguments(output, "SDIFF");
return Ok(true);
}
let objs = match load_many(store, parse_state, output) {
Ok(Some(objs)) => objs,
Ok(None) => return Ok(false),
Err(()) => return Ok(true),
};
let result = diff_sets(&objs);
write_set_members(&result, output);
Ok(true)
}
pub fn set_diff_store<'a, D: wdev::Device>(
&mut self,
parse_state: &[&[u8]],
store: &wkv::BatchStoreSession<'a, D>,
output: &mut Vec<u8>,
) -> wresp::Result<bool> {
if parse_state.len() < 2 {
cs::abort_with_wrong_number_of_arguments(output, "SDIFFSTORE");
return Ok(true);
}
let dst = parse_state[0];
let objs = match load_many(store, &parse_state[1..], output) {
Ok(Some(objs)) => objs,
Ok(None) => return Ok(false),
Err(()) => return Ok(true),
};
let result = diff_sets(&objs);
combine_store(self, dst, &result, store, output)
}
}
fn intersect_sets(objs: &[SetObject]) -> SetObject {
let mut result = SetObject::new();
let Some(first) = objs.first() else {
return result;
};
for member in first.to_members() {
result.set.insert(member);
}
for obj in &objs[1..] {
if result.set.is_empty() {
break;
}
let members: HashSet<&[u8]> = obj.set.iter().map(|m| m.as_slice()).collect();
result.set.retain(|m| members.contains(m.as_slice()));
}
result
}
fn diff_sets(objs: &[SetObject]) -> SetObject {
let mut result = SetObject::new();
let Some(first) = objs.first() else {
return result;
};
for member in first.to_members() {
result.set.insert(member);
}
for obj in &objs[1..] {
let members: HashSet<&[u8]> = obj.set.iter().map(|m| m.as_slice()).collect();
result.set.retain(|m| !members.contains(m.as_slice()));
}
result
}
fn combine_store<'a, D: wdev::Device>(
_session: &mut RespServerSession,
dst: &[u8],
result: &SetObject,
store: &wkv::BatchStoreSession<'a, D>,
output: &mut Vec<u8>,
) -> wresp::Result<bool> {
match set_save_or_gc(store, dst, result) {
Ok(true) => output.write_resp_int(result.set.len() as i64),
Ok(false) => return Ok(false),
Err(()) => output.write_resp_error("generic error"),
}
Ok(true)
}
fn write_set_members(result: &SetObject, output: &mut Vec<u8>) {
let members = result.to_members();
output.write_resp_array_len(members.len());
for member in members {
output.write_resp_bulk_string(&member);
}
}
#[cfg(test)]
mod tests {
use std::{io::Cursor, str, sync::Arc};
use tempfile::{TempDir, tempdir};
use wdev::SegmentedDevice;
use wkv::{StoreConfig, WedbStore};
use wobject::set::set_object::SetObject as WoSetObject;
use super::{
super::object_store_utils::{OBJ_TAG_SET, obj_encode},
*,
};
type TestSession = wkv::StoreSession<SegmentedDevice>;
fn fixture(tag: &str) -> (TempDir, Arc<WedbStore<SegmentedDevice>>, TestSession) {
let dir = tempdir().unwrap();
let device = Arc::new(SegmentedDevice::single_file(dir.path().join(tag)).unwrap());
let config = StoreConfig::new(16384, 65536, 64, 0.5).unwrap();
let store = Arc::new(WedbStore::open(config, device).unwrap());
let session = store.new_session().unwrap();
(dir, store, session)
}
const WRONGTYPE: &[u8] =
b"-WRONGTYPE Operation against a key holding the wrong kind of value.\r\n";
#[test]
fn rmw_writeback_contract() {
let (_dir, _store, session) = fixture("setwb.db");
let batch = session.enter_batch();
let mut sess = RespServerSession::default();
let mut out = Vec::new();
sess
.set_add(&[b"st", b"a", b"b", b"a"], &batch, &mut out)
.unwrap();
assert_eq!(out, b":2\r\n");
out.clear();
sess.set_length(&[b"st"], &batch, &mut out).unwrap();
assert_eq!(out, b":2\r\n");
out.clear();
sess
.set_is_member(&[b"st", b"a"], &batch, &mut out)
.unwrap();
assert_eq!(out, b":1\r\n");
out.clear();
sess
.set_is_member(&[b"st", b"nx"], &batch, &mut out)
.unwrap();
assert_eq!(out, b":0\r\n");
out.clear();
sess
.set_is_member(&[b"nk", b"a"], &batch, &mut out)
.unwrap();
assert_eq!(out, b":0\r\n");
out.clear();
sess
.set_multi_is_member(&[b"st", b"a", b"nx"], &batch, &mut out)
.unwrap();
assert_eq!(out, b"*2\r\n:1\r\n:0\r\n");
out.clear();
sess
.set_multi_is_member(&[b"nk", b"a", b"b"], &batch, &mut out)
.unwrap();
assert_eq!(out, b"*2\r\n:0\r\n:0\r\n");
out.clear();
sess.set_members(&[b"st"], &batch, &mut out).unwrap();
let items = parse_bulk_array(&out);
let mut sorted = items.clone();
sorted.sort();
assert_eq!(sorted, vec![b"a".to_vec(), b"b".to_vec()]);
out.clear();
sess.set_members(&[b"nk"], &batch, &mut out).unwrap();
assert_eq!(out, b"*0\r\n");
out.clear();
sess
.set_remove(&[b"st", b"a", b"nx"], &batch, &mut out)
.unwrap();
assert_eq!(out, b":1\r\n");
out.clear();
sess.set_remove(&[b"st", b"b"], &batch, &mut out).unwrap();
assert_eq!(out, b":1\r\n");
assert!(
batch
.try_read_sync(b"st", |v| v.to_vec())
.ok()
.flatten()
.flatten()
.is_none()
);
out.clear();
sess.set_remove(&[b"st", b"b"], &batch, &mut out).unwrap();
assert_eq!(out, b":0\r\n");
let _ = batch.try_upsert_sync(b"str", b"plain-value");
out.clear();
sess.set_add(&[b"str", b"m"], &batch, &mut out).unwrap();
assert_eq!(out, WRONGTYPE);
assert_eq!(
batch
.try_read_sync(b"str", |v| v.to_vec())
.ok()
.flatten()
.flatten(),
Some(b"plain-value".to_vec())
);
sess
.set_add(&[b"env", b"m"], &batch, &mut Vec::new())
.unwrap();
let raw = batch
.try_read_sync(b"env", |v| v.to_vec())
.ok()
.flatten()
.flatten()
.expect("envelope value");
assert_eq!(raw[0], OBJ_TAG_SET);
let from_storage = WoSetObject::deserialize(&mut Cursor::new(&raw[1..])).unwrap();
assert!(from_storage.set.pin().contains(b"m".as_slice()));
let ext = WoSetObject::new();
ext.set.pin().insert(b"pv".to_vec());
let mut payload = Vec::new();
ext.serialize(&mut payload).unwrap();
let _ = batch.try_upsert_sync(b"fromstore", &obj_encode(OBJ_TAG_SET, &payload));
out.clear();
sess
.set_is_member(&[b"fromstore", b"pv"], &batch, &mut out)
.unwrap();
assert_eq!(out, b":1\r\n");
}
#[test]
fn pop_and_random() {
let (_dir, _store, session) = fixture("setpop.db");
let batch = session.enter_batch();
let mut sess = RespServerSession::default();
let mut out = Vec::new();
sess
.set_add(&[b"st", b"a", b"b"], &batch, &mut Vec::new())
.unwrap();
out.clear();
sess.set_pop(&[b"st", b"0"], &batch, &mut out).unwrap();
assert_eq!(out, b"*0\r\n");
out.clear();
sess.set_pop(&[b"st", b"-1"], &batch, &mut out).unwrap();
assert_eq!(out, b"-ERR value is not an integer or out of range.\r\n");
out.clear();
sess.set_pop(&[b"st", b"10"], &batch, &mut out).unwrap();
assert!(out.starts_with(b"*2\r\n"));
assert!(
batch
.try_read_sync(b"st", |v| v.to_vec())
.ok()
.flatten()
.flatten()
.is_none()
);
out.clear();
sess.set_pop(&[b"nk"], &batch, &mut out).unwrap();
assert_eq!(out, b"$-1\r\n");
out.clear();
sess.set_pop(&[b"nk", b"5"], &batch, &mut out).unwrap();
assert_eq!(out, b"$-1\r\n");
sess
.set_add(&[b"r", b"m1", b"m2", b"m3"], &batch, &mut Vec::new())
.unwrap();
out.clear();
sess
.set_random_member(&[b"r", b"0"], &batch, &mut out)
.unwrap();
assert_eq!(out, b"*0\r\n");
out.clear();
sess
.set_random_member(&[b"r", b"2"], &batch, &mut out)
.unwrap();
assert!(out.starts_with(b"*2\r\n"));
out.clear();
sess.set_length(&[b"r"], &batch, &mut out).unwrap();
assert_eq!(out, b":3\r\n");
out.clear();
sess
.set_random_member(&[b"r", b"-4"], &batch, &mut out)
.unwrap();
assert!(out.starts_with(b"*4\r\n"));
out.clear();
sess.set_random_member(&[b"r"], &batch, &mut out).unwrap();
assert!(out.starts_with(b"$2\r\n"));
out.clear();
sess.set_random_member(&[b"nk"], &batch, &mut out).unwrap();
assert_eq!(out, b"$-1\r\n");
out.clear();
sess
.set_random_member(&[b"nk", b"2"], &batch, &mut out)
.unwrap();
assert_eq!(out, b"*0\r\n");
}
#[test]
fn multi_key_ops() {
let (_dir, _store, session) = fixture("setmulti.db");
let batch = session.enter_batch();
let mut sess = RespServerSession::default();
let mut out = Vec::new();
sess
.set_add(&[b"a", b"m1", b"m2", b"m3"], &batch, &mut Vec::new())
.unwrap();
sess
.set_add(&[b"b", b"m2", b"m4"], &batch, &mut Vec::new())
.unwrap();
out.clear();
sess.set_intersect(&[b"a", b"b"], &batch, &mut out).unwrap();
assert_eq!(out, b"*1\r\n$2\r\nm2\r\n");
out.clear();
sess
.set_intersect_length(&[b"2", b"a", b"b"], &batch, &mut out)
.unwrap();
assert_eq!(out, b":1\r\n");
out.clear();
sess
.set_intersect_length(&[b"2", b"a", b"b", b"LIMIT", b"0"], &batch, &mut out)
.unwrap();
assert_eq!(out, b":1\r\n");
out.clear();
sess
.set_intersect_length(&[b"2", b"a", b"b", b"LIMIT", b"9"], &batch, &mut out)
.unwrap();
assert_eq!(out, b":1\r\n");
out.clear();
sess
.set_intersect_length(&[b"2", b"a", b"b", b"LIM", b"0"], &batch, &mut out)
.unwrap();
assert_eq!(out, b"-ERR syntax error\r\n");
out.clear();
sess
.set_intersect_length(&[b"0", b"a"], &batch, &mut out)
.unwrap();
assert_eq!(out, b"-ERR numkeys should be greater than 0\r\n");
out.clear();
sess.set_union(&[b"a", b"b"], &batch, &mut out).unwrap();
let items = parse_bulk_array(&out);
let mut sorted = items.clone();
sorted.sort();
assert_eq!(
sorted,
vec![
b"m1".to_vec(),
b"m2".to_vec(),
b"m3".to_vec(),
b"m4".to_vec()
]
);
out.clear();
sess.set_diff(&[b"a", b"b"], &batch, &mut out).unwrap();
let items = parse_bulk_array(&out);
let mut sorted = items.clone();
sorted.sort();
assert_eq!(sorted, vec![b"m1".to_vec(), b"m3".to_vec()]);
out.clear();
sess
.set_intersect_store(&[b"i_dst", b"a", b"b"], &batch, &mut out)
.unwrap();
assert_eq!(out, b":1\r\n");
out.clear();
sess
.set_union_store(&[b"u_dst", b"a", b"b"], &batch, &mut out)
.unwrap();
assert_eq!(out, b":4\r\n");
out.clear();
sess
.set_diff_store(&[b"d_dst", b"a", b"b"], &batch, &mut out)
.unwrap();
assert_eq!(out, b":2\r\n");
out.clear();
sess.set_length(&[b"i_dst"], &batch, &mut out).unwrap();
assert_eq!(out, b":1\r\n");
sess
.set_add(&[b"only_x", b"x"], &batch, &mut Vec::new())
.unwrap();
out.clear();
sess
.set_diff_store(&[b"i_dst", b"only_x", b"only_x"], &batch, &mut out)
.unwrap();
assert_eq!(out, b":0\r\n");
assert!(
batch
.try_read_sync(b"i_dst", |v| v.to_vec())
.ok()
.flatten()
.flatten()
.is_none()
);
sess
.set_add(&[b"s1", b"m", b"keep"], &batch, &mut Vec::new())
.unwrap();
out.clear();
sess
.set_move(&[b"s1", b"s2", b"m"], &batch, &mut out)
.unwrap();
assert_eq!(out, b":1\r\n");
out.clear();
sess
.set_is_member(&[b"s2", b"m"], &batch, &mut out)
.unwrap();
assert_eq!(out, b":1\r\n");
out.clear();
sess
.set_move(&[b"s1", b"s2", b"keep"], &batch, &mut out)
.unwrap();
assert_eq!(out, b":1\r\n");
assert!(
batch
.try_read_sync(b"s1", |v| v.to_vec())
.ok()
.flatten()
.flatten()
.is_none()
);
out.clear();
sess
.set_move(&[b"s2", b"s3", b"nx"], &batch, &mut out)
.unwrap();
assert_eq!(out, b":0\r\n");
out.clear();
sess
.set_move(&[b"nk", b"s2", b"m"], &batch, &mut out)
.unwrap();
assert_eq!(out, b":0\r\n");
out.clear();
sess
.set_move(&[b"s2", b"s2", b"m"], &batch, &mut out)
.unwrap();
assert_eq!(out, b":0\r\n");
}
fn parse_bulk_array(frame: &[u8]) -> Vec<Vec<u8>> {
let mut items = Vec::new();
let mut pos = frame.iter().position(|&b| b == b'\n').unwrap() + 1;
while pos < frame.len() {
assert_eq!(frame[pos], b'$');
let len_end = frame[pos..].iter().position(|&b| b == b'\n').unwrap() + pos;
let len: usize = str::from_utf8(&frame[pos + 1..len_end - 1])
.unwrap()
.parse()
.unwrap();
let start = len_end + 1;
items.push(frame[start..start + len].to_vec());
pos = start + len + 2;
}
items
}
}