move repos into monorepo
This commit is contained in:
140
lib/osiris/core/index/field_index.rs
Normal file
140
lib/osiris/core/index/field_index.rs
Normal file
@@ -0,0 +1,140 @@
|
||||
use crate::error::Result;
|
||||
use crate::store::{HeroDbClient, OsirisObject};
|
||||
|
||||
/// Field indexing for fast filtering by tags and metadata
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FieldIndex {
|
||||
client: HeroDbClient,
|
||||
}
|
||||
|
||||
impl FieldIndex {
|
||||
/// Create a new field index
|
||||
pub fn new(client: HeroDbClient) -> Self {
|
||||
Self { client }
|
||||
}
|
||||
|
||||
/// Index an object (add to field indexes)
|
||||
pub async fn index_object(&self, obj: &OsirisObject) -> Result<()> {
|
||||
// Index tags
|
||||
for (key, value) in &obj.meta.tags {
|
||||
let field_key = format!("field:tag:{}={}", key, value);
|
||||
self.client.sadd(&field_key, &obj.id).await?;
|
||||
}
|
||||
|
||||
// Index MIME type if present
|
||||
if let Some(mime) = &obj.meta.mime {
|
||||
let field_key = format!("field:mime:{}", mime);
|
||||
self.client.sadd(&field_key, &obj.id).await?;
|
||||
}
|
||||
|
||||
// Index title if present (for exact match)
|
||||
if let Some(title) = &obj.meta.title {
|
||||
let field_key = format!("field:title:{}", title);
|
||||
self.client.sadd(&field_key, &obj.id).await?;
|
||||
}
|
||||
|
||||
// Add to scan index for text search
|
||||
self.client.sadd("scan:index", &obj.id).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Remove an object from indexes
|
||||
pub async fn deindex_object(&self, obj: &OsirisObject) -> Result<()> {
|
||||
// Remove from tag indexes
|
||||
for (key, value) in &obj.meta.tags {
|
||||
let field_key = format!("field:tag:{}={}", key, value);
|
||||
self.client.srem(&field_key, &obj.id).await?;
|
||||
}
|
||||
|
||||
// Remove from MIME index
|
||||
if let Some(mime) = &obj.meta.mime {
|
||||
let field_key = format!("field:mime:{}", mime);
|
||||
self.client.srem(&field_key, &obj.id).await?;
|
||||
}
|
||||
|
||||
// Remove from title index
|
||||
if let Some(title) = &obj.meta.title {
|
||||
let field_key = format!("field:title:{}", title);
|
||||
self.client.srem(&field_key, &obj.id).await?;
|
||||
}
|
||||
|
||||
// Remove from scan index
|
||||
self.client.srem("scan:index", &obj.id).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Update object indexes (remove old, add new)
|
||||
pub async fn reindex_object(&self, old_obj: &OsirisObject, new_obj: &OsirisObject) -> Result<()> {
|
||||
self.deindex_object(old_obj).await?;
|
||||
self.index_object(new_obj).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get all IDs matching a tag filter
|
||||
pub async fn get_ids_by_tag(&self, key: &str, value: &str) -> Result<Vec<String>> {
|
||||
let field_key = format!("field:tag:{}={}", key, value);
|
||||
self.client.smembers(&field_key).await
|
||||
}
|
||||
|
||||
/// Get all IDs matching a MIME type
|
||||
pub async fn get_ids_by_mime(&self, mime: &str) -> Result<Vec<String>> {
|
||||
let field_key = format!("field:mime:{}", mime);
|
||||
self.client.smembers(&field_key).await
|
||||
}
|
||||
|
||||
/// Get all IDs matching a title
|
||||
pub async fn get_ids_by_title(&self, title: &str) -> Result<Vec<String>> {
|
||||
let field_key = format!("field:title:{}", title);
|
||||
self.client.smembers(&field_key).await
|
||||
}
|
||||
|
||||
/// Get all IDs in the scan index
|
||||
pub async fn get_all_ids(&self) -> Result<Vec<String>> {
|
||||
self.client.smembers("scan:index").await
|
||||
}
|
||||
|
||||
/// Get intersection of multiple field filters
|
||||
pub async fn get_ids_by_filters(&self, filters: &[(String, String)]) -> Result<Vec<String>> {
|
||||
if filters.is_empty() {
|
||||
return self.get_all_ids().await;
|
||||
}
|
||||
|
||||
let keys: Vec<String> = filters
|
||||
.iter()
|
||||
.map(|(k, v)| {
|
||||
if k == "mime" {
|
||||
format!("field:mime:{}", v)
|
||||
} else if k == "title" {
|
||||
format!("field:title:{}", v)
|
||||
} else {
|
||||
format!("field:tag:{}={}", k, v)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
self.client.sinter(&keys).await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn test_index_object() {
|
||||
let client = HeroDbClient::new("redis://localhost:6379", 1).unwrap();
|
||||
let index = FieldIndex::new(client);
|
||||
|
||||
let mut obj = OsirisObject::new("test".to_string(), Some("Hello".to_string()));
|
||||
obj.set_tag("topic".to_string(), "rust".to_string());
|
||||
obj.set_mime(Some("text/plain".to_string()));
|
||||
|
||||
index.index_object(&obj).await.unwrap();
|
||||
|
||||
let ids = index.get_ids_by_tag("topic", "rust").await.unwrap();
|
||||
assert!(ids.contains(&obj.id));
|
||||
}
|
||||
}
|
||||
3
lib/osiris/core/index/mod.rs
Normal file
3
lib/osiris/core/index/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
pub mod field_index;
|
||||
|
||||
pub use field_index::FieldIndex;
|
||||
Reference in New Issue
Block a user