use crate::error::{Error, Result};
use bytes::Bytes;
use feoxdb::FeoxStore;
use std::sync::Arc;
const INITIAL_POSITION: i64 = 1_000_000_000;
const MAX_RETRIES: usize = 10;
#[derive(Clone)]
pub struct ListOperations {
store: Arc<FeoxStore>,
}
impl ListOperations {
pub fn new(store: Arc<FeoxStore>) -> Self {
Self { store }
}
fn parse_metadata(data: &[u8]) -> (i64, i64, u64) {
if data.len() < 24 {
return (INITIAL_POSITION, INITIAL_POSITION, 0);
}
let head = i64::from_le_bytes(data[0..8].try_into().unwrap());
let tail = i64::from_le_bytes(data[8..16].try_into().unwrap());
let count = u64::from_le_bytes(data[16..24].try_into().unwrap());
(head, tail, count)
}
fn build_metadata(head: i64, tail: i64, count: u64) -> Vec<u8> {
let mut meta = Vec::with_capacity(24);
meta.extend_from_slice(&head.to_le_bytes());
meta.extend_from_slice(&tail.to_le_bytes());
meta.extend_from_slice(&count.to_le_bytes());
meta
}
pub fn lpush(&self, key: &[u8], values: Vec<Bytes>) -> Result<i64> {
let meta_key = format!("L:{}:meta", String::from_utf8_lossy(key));
for value in values {
let mut retries = 0;
loop {
let meta_result = self.store.get_bytes(meta_key.as_bytes());
let (meta_bytes, is_new) = match meta_result {
Ok(bytes) => (bytes, false),
Err(_) => {
let initial_meta =
Self::build_metadata(INITIAL_POSITION, INITIAL_POSITION, 0);
match self.store.insert(meta_key.as_bytes(), &initial_meta) {
Ok(_) => (Bytes::from(initial_meta), true),
Err(_) => {
continue;
}
}
}
};
let (head, tail, count) = Self::parse_metadata(&meta_bytes);
let new_head = head - 1;
let new_tail = if count == 0 { head } else { tail }; let new_count = count + 1;
let new_meta = Self::build_metadata(new_head, new_tail, new_count);
let cas_success = if is_new {
self.store.insert(meta_key.as_bytes(), &new_meta).is_ok()
} else {
self.store
.compare_and_swap(meta_key.as_bytes(), &meta_bytes, &new_meta)?
};
if cas_success {
let value_key = format!("L:{}:{}", String::from_utf8_lossy(key), new_head);
self.store.insert(value_key.as_bytes(), &value)?;
break;
}
retries += 1;
if retries >= MAX_RETRIES {
return Err(Error::System(
"Operation failed due to contention".to_string(),
));
}
std::thread::yield_now();
}
}
let meta_bytes = self.store.get_bytes(meta_key.as_bytes())?;
let (_, _, count) = Self::parse_metadata(&meta_bytes);
Ok(count as i64)
}
pub fn rpush(&self, key: &[u8], values: Vec<Bytes>) -> Result<i64> {
let meta_key = format!("L:{}:meta", String::from_utf8_lossy(key));
for value in values {
let mut retries = 0;
loop {
let meta_result = self.store.get_bytes(meta_key.as_bytes());
let (meta_bytes, is_new) = match meta_result {
Ok(bytes) => (bytes, false),
Err(_) => {
let initial_meta =
Self::build_metadata(INITIAL_POSITION, INITIAL_POSITION, 0);
match self.store.insert(meta_key.as_bytes(), &initial_meta) {
Ok(_) => (Bytes::from(initial_meta), true),
Err(_) => {
continue;
}
}
}
};
let (head, tail, count) = Self::parse_metadata(&meta_bytes);
let insert_pos = tail;
let new_head = if count == 0 { tail } else { head }; let new_tail = tail + 1;
let new_count = count + 1;
let new_meta = Self::build_metadata(new_head, new_tail, new_count);
let cas_success = if is_new {
self.store.insert(meta_key.as_bytes(), &new_meta).is_ok()
} else {
self.store
.compare_and_swap(meta_key.as_bytes(), &meta_bytes, &new_meta)?
};
if cas_success {
let value_key = format!("L:{}:{}", String::from_utf8_lossy(key), insert_pos);
self.store.insert(value_key.as_bytes(), &value)?;
break;
}
retries += 1;
if retries >= MAX_RETRIES {
return Err(Error::System(
"Operation failed due to contention".to_string(),
));
}
std::thread::yield_now();
}
}
let meta_bytes = self.store.get_bytes(meta_key.as_bytes())?;
let (_, _, count) = Self::parse_metadata(&meta_bytes);
Ok(count as i64)
}
pub fn lpop(&self, key: &[u8], count: Option<usize>) -> Result<Vec<Bytes>> {
let meta_key = format!("L:{}:meta", String::from_utf8_lossy(key));
let count = count.unwrap_or(1);
let mut popped = Vec::new();
for _ in 0..count {
let mut retries = 0;
loop {
let meta_bytes = match self.store.get_bytes(meta_key.as_bytes()) {
Ok(bytes) => bytes,
Err(_) => return Ok(popped), };
let (head, tail, list_count) = Self::parse_metadata(&meta_bytes);
if list_count == 0 || head >= tail {
return Ok(popped); }
let new_head = head + 1;
let new_count = list_count - 1;
let new_meta = Self::build_metadata(new_head, tail, new_count);
if self
.store
.compare_and_swap(meta_key.as_bytes(), &meta_bytes, &new_meta)?
{
let value_key = format!("L:{}:{}", String::from_utf8_lossy(key), head);
match self.store.get_bytes(value_key.as_bytes()) {
Ok(value) => {
self.store.delete(value_key.as_bytes())?;
popped.push(value);
break;
}
Err(_) => {
continue;
}
}
}
retries += 1;
if retries >= MAX_RETRIES {
return Ok(popped); }
std::thread::yield_now();
}
}
Ok(popped)
}
pub fn rpop(&self, key: &[u8], count: Option<usize>) -> Result<Vec<Bytes>> {
let meta_key = format!("L:{}:meta", String::from_utf8_lossy(key));
let count = count.unwrap_or(1);
let mut popped = Vec::new();
for _ in 0..count {
let mut retries = 0;
loop {
let meta_bytes = match self.store.get_bytes(meta_key.as_bytes()) {
Ok(bytes) => bytes,
Err(_) => return Ok(popped),
};
let (head, tail, list_count) = Self::parse_metadata(&meta_bytes);
if list_count == 0 || head >= tail {
return Ok(popped);
}
let new_tail = tail - 1;
let new_count = list_count - 1;
let new_meta = Self::build_metadata(head, new_tail, new_count);
if self
.store
.compare_and_swap(meta_key.as_bytes(), &meta_bytes, &new_meta)?
{
let value_key = format!("L:{}:{}", String::from_utf8_lossy(key), new_tail);
match self.store.get_bytes(value_key.as_bytes()) {
Ok(value) => {
self.store.delete(value_key.as_bytes())?;
popped.push(value);
break;
}
Err(_) => continue,
}
}
retries += 1;
if retries >= MAX_RETRIES {
return Ok(popped);
}
std::thread::yield_now();
}
}
Ok(popped)
}
pub fn llen(&self, key: &[u8]) -> Result<i64> {
let meta_key = format!("L:{}:meta", String::from_utf8_lossy(key));
match self.store.get_bytes(meta_key.as_bytes()) {
Ok(meta_bytes) => {
let (_, _, count) = Self::parse_metadata(&meta_bytes);
Ok(count as i64)
}
Err(_) => Ok(0),
}
}
pub fn lrange(&self, key: &[u8], start: i64, stop: i64) -> Result<Vec<Bytes>> {
let meta_key = format!("L:{}:meta", String::from_utf8_lossy(key));
let meta_bytes = match self.store.get_bytes(meta_key.as_bytes()) {
Ok(bytes) => bytes,
Err(_) => return Ok(vec![]),
};
let (head, tail, count) = Self::parse_metadata(&meta_bytes);
if count == 0 {
return Ok(vec![]);
}
let len = tail - head;
let start = if start < 0 {
(len + start).max(0)
} else {
start
};
let stop = if stop < 0 { len + stop } else { stop };
if start >= len || stop < 0 {
return Ok(vec![]);
}
let start = start.max(0).min(len - 1);
let stop = stop.max(0).min(len - 1);
if start > stop {
return Ok(vec![]);
}
let mut results = Vec::new();
for i in start..=stop {
let pos = head + i;
let value_key = format!("L:{}:{}", String::from_utf8_lossy(key), pos);
if let Ok(value) = self.store.get_bytes(value_key.as_bytes()) {
results.push(value);
}
}
Ok(results)
}
pub fn lindex(&self, key: &[u8], index: i64) -> Result<Option<Bytes>> {
let meta_key = format!("L:{}:meta", String::from_utf8_lossy(key));
let meta_bytes = match self.store.get_bytes(meta_key.as_bytes()) {
Ok(bytes) => bytes,
Err(_) => return Ok(None),
};
let (head, tail, count) = Self::parse_metadata(&meta_bytes);
if count == 0 {
return Ok(None);
}
let len = tail - head;
let actual_index = if index < 0 {
let positive_index = len + index;
if positive_index < 0 {
return Ok(None);
}
positive_index
} else {
if index >= len {
return Ok(None);
}
index
};
let pos = head + actual_index;
let value_key = format!("L:{}:{}", String::from_utf8_lossy(key), pos);
match self.store.get_bytes(value_key.as_bytes()) {
Ok(value) => Ok(Some(value)),
Err(_) => Ok(None), }
}
}