#![forbid(unsafe_code)]
#![warn(missing_docs)]
pub mod ft;
pub mod ft_filter;
pub mod query_fsm;
pub mod registry;
pub mod schema;
pub mod sugest;
pub mod sugest_registry;
pub mod wire;
use std::sync::Arc;
use dynomite::embed::{CommandExtension, HsetOutcome, ServerBuilder};
use dynomite::msg::MsgType;
pub use crate::registry::{
RegistryError, TextFieldIndex, TextHit, TextRegexApproxResult, TextRegexResult, VectorRegistry,
VectorTable, VectorTableInfo,
};
pub use crate::schema::{
DistanceMetric, IndexAlgorithm, MetadataField, MetadataFieldType, VectorSchema, VectorType,
};
pub use crate::sugest::{SuggestionDict, SuggestionEntry, SuggestionHit};
pub use crate::sugest_registry::SuggestionRegistry;
#[derive(Clone, Debug)]
pub struct SearchExtension {
registry: Arc<VectorRegistry>,
suggestions: Arc<SuggestionRegistry>,
}
impl SearchExtension {
#[must_use]
pub fn new(registry: Arc<VectorRegistry>) -> Self {
Self {
registry,
suggestions: Arc::new(SuggestionRegistry::new()),
}
}
#[must_use]
pub fn with_suggestions(
registry: Arc<VectorRegistry>,
suggestions: Arc<SuggestionRegistry>,
) -> Self {
Self {
registry,
suggestions,
}
}
#[must_use]
pub fn registry(&self) -> &Arc<VectorRegistry> {
&self.registry
}
#[must_use]
pub fn suggestions(&self) -> &Arc<SuggestionRegistry> {
&self.suggestions
}
}
impl Default for SearchExtension {
fn default() -> Self {
Self {
registry: Arc::new(VectorRegistry::new()),
suggestions: Arc::new(SuggestionRegistry::new()),
}
}
}
impl CommandExtension for SearchExtension {
fn handles_msg_type(&self, ty: MsgType) -> bool {
matches!(
ty,
MsgType::ReqRedisFtCreate
| MsgType::ReqRedisFtSearch
| MsgType::ReqRedisFtInfo
| MsgType::ReqRedisFtList
| MsgType::ReqRedisFtDropindex
| MsgType::ReqRedisFtRegex
| MsgType::ReqRedisFtSugadd
| MsgType::ReqRedisFtSugget
| MsgType::ReqRedisFtSugdel
| MsgType::ReqRedisFtSuglen
| MsgType::ReqRedisFtUnknown
)
}
fn try_dispatch(&self, args: &[&[u8]]) -> Option<Vec<u8>> {
if let Some(head) = args.first() {
let mut upper = [0u8; 16];
let n = head.len().min(upper.len());
for (i, &b) in head.iter().take(n).enumerate() {
upper[i] = b.to_ascii_uppercase();
}
if matches!(
&upper[..n],
b"FT.SUGADD" | b"FT.SUGGET" | b"FT.SUGDEL" | b"FT.SUGLEN"
) {
return Some(crate::ft::dispatch_sugest(&self.suggestions, args));
}
}
Some(crate::ft::dispatch(&self.registry, args))
}
fn try_intercept_hset(&self, args: &[&[u8]]) -> HsetOutcome {
match crate::ft::maybe_index_hset(&self.registry, args) {
Ok(Some(_)) => HsetOutcome::Absorbed,
Ok(None) => HsetOutcome::NotIndexed,
Err(e) => HsetOutcome::Error(format!("{e}")),
}
}
}
pub fn install(builder: &mut ServerBuilder) -> Arc<VectorRegistry> {
let ext = SearchExtension::default();
let registry = Arc::clone(ext.registry());
builder.set_command_extension(Arc::new(ext));
registry
}
#[must_use]
pub fn install_owned(builder: ServerBuilder) -> (ServerBuilder, Arc<VectorRegistry>) {
let ext = SearchExtension::default();
let registry = Arc::clone(ext.registry());
let builder = builder.with_command_extension(Arc::new(ext));
(builder, registry)
}