use std::ffi::c_void;
use std::ffi::{CStr, CString};
use std::ptr;
use num_traits::ToPrimitive;
use crate::raw::{self, RSFieldID, RSResultsIterator, GC_POLICY_FORK, GC_POLICY_NONE};
use crate::{Document, FieldType};
use redis_module::RedisError;
use std::os::raw::{c_char, c_int};
pub struct Field<'a> {
index: &'a Index,
field_id: RSFieldID,
}
pub struct Index {
inner: *mut raw::RSIndex,
}
pub struct TagOptions {
tag_separator: Option<char>,
tag_case_sensitive: bool,
}
impl Default for TagOptions {
fn default() -> Self {
Self {
tag_separator: None,
tag_case_sensitive: false,
}
}
}
impl Index {
pub fn create(name: &str) -> Self {
let name = CString::new(name).unwrap();
let index = unsafe { raw::RediSearch_CreateIndex(name.as_ptr(), ptr::null()) };
Self { inner: index }
}
pub fn create_with_options(name: &str, options: IndexOptions) -> Self {
let index_options =
unsafe { raw::RediSearch_CreateIndexOptions().as_mut() }.expect("null IndexOptions");
index_options.gcPolicy = options.gc_policy.to_i32().unwrap();
let name = CString::new(name).unwrap();
let index = unsafe { raw::RediSearch_CreateIndex(name.as_ptr(), index_options) };
unsafe { raw::RediSearch_FreeIndexOptions(index_options) };
Self { inner: index }
}
pub fn create_field(&self, name: &str, weight: f64, tag_options: TagOptions) -> Field {
let name = CString::new(name).unwrap();
let ftype = FieldType::FULLTEXT | FieldType::NUMERIC | FieldType::TAG;
let fopt = raw::RSFLDOPT_NONE;
let field_id =
unsafe { raw::RediSearch_CreateField(self.inner, name.as_ptr(), ftype.bits, fopt) };
unsafe {
raw::RediSearch_TextFieldSetWeight(self.inner, field_id, weight);
if let Some(separator) = tag_options.tag_separator {
raw::RediSearch_TagFieldSetSeparator(self.inner, field_id, separator as c_char);
}
if tag_options.tag_case_sensitive {
raw::RediSearch_TagFieldSetCaseSensitive(
self.inner,
field_id,
tag_options.tag_case_sensitive as c_int,
);
}
}
Field {
index: self,
field_id,
}
}
pub fn add_document(&self, doc: &Document) -> Result<(), RedisError> {
let status = unsafe {
raw::RediSearch_IndexAddDocument(
self.inner,
doc.inner,
raw::REDISEARCH_ADD_REPLACE as i32,
ptr::null_mut(), )
};
if status == redis_module::raw::REDISMODULE_ERR as i32 {
Err(RedisError::Str("error adding document"))
} else {
Ok(())
}
}
pub fn del_document(&self, key: &str) -> Result<(), RedisError> {
let status = unsafe {
raw::RediSearch_DeleteDocument(
self.inner,
CString::new(key).unwrap().as_ptr() as *const c_void,
key.len(),
)
};
if status == redis_module::raw::REDISMODULE_ERR as i32 {
Err(RedisError::Str("error deleting document"))
} else {
Ok(())
}
}
pub fn search(&self, query_string: &str) -> Result<ResultsIterator, RedisError> {
let c_query = CString::new(query_string).unwrap();
let mut err_ptr = ptr::null_mut();
let results_iter = unsafe {
raw::RediSearch_IterateQuery(
self.inner,
c_query.as_ptr(),
query_string.len(),
&mut err_ptr,
)
};
if !err_ptr.is_null() {
let message = unsafe { CStr::from_ptr(err_ptr) }.to_str()?.to_owned();
return Err(RedisError::String(message));
}
Ok(ResultsIterator::from_raw(results_iter, self)?)
}
}
impl Drop for Index {
fn drop(&mut self) {
unsafe { raw::RediSearch_DropIndex(self.inner) }
}
}
pub struct ResultsIterator<'idx> {
inner: *mut RSResultsIterator,
index: &'idx Index,
}
impl<'idx> ResultsIterator<'idx> {
fn from_raw(
results_iter: *mut RSResultsIterator,
index: &'idx Index,
) -> Result<Self, RedisError> {
Ok(Self {
inner: results_iter,
index,
})
}
}
impl Iterator for ResultsIterator<'_> {
type Item = String;
fn next(&mut self) -> Option<Self::Item> {
if self.inner.is_null() {
return None;
}
let mut len = 0;
let key = unsafe {
let raw_key =
raw::RediSearch_ResultsIteratorNext(self.inner, self.index.inner, &mut len)
as *const c_char;
if raw_key.is_null() {
return None;
}
CStr::from_ptr(raw_key)
.to_str()
.expect("invalid UTF-8 data for key")
};
Some(key.to_owned())
}
}
impl Drop for ResultsIterator<'_> {
fn drop(&mut self) {
if self.inner.is_null() {
return;
}
unsafe {
raw::RediSearch_ResultsIteratorFree(self.inner);
};
}
}
const GC_POLICY_NONE_ISIZE: isize = GC_POLICY_NONE as isize;
const GC_POLICY_FORK_ISIZE: isize = GC_POLICY_FORK as isize;
#[derive(Primitive, Debug, PartialEq)]
pub enum GcPolicy {
None = GC_POLICY_NONE_ISIZE,
Fork = GC_POLICY_FORK_ISIZE,
}
pub struct IndexOptions {
gc_policy: GcPolicy,
}