use crate::model::{
AggregateOptions, AggregateResult, CollectionSchema, IndexDefinition, ListEventsOptions,
ListObjectsOptions, MigrationRecord, PutObjectOptions, SchemaOptions, StoredSchema,
TimeSeriesOptions, TimeSeriesResult,
};
use crate::{
MemoryEvent, MemoryObject, QueueClaimOptions, QueueJob, QueueNackOptions, ThingdError,
ThingdResult, VectorSearchHit, VectorSearchOptions,
};
#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct StorageDiagnostics {
pub objects: u64,
pub events: u64,
pub links: u64,
pub queues: u64,
pub active_jobs: u64,
pub dead_jobs: u64,
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RetentionOptions {
pub before_unix_ms: i64,
#[serde(default)]
pub dry_run: bool,
#[serde(default)]
pub compact: bool,
#[serde(default)]
pub include_replication: bool,
}
#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RetentionReport {
pub events: u64,
pub completed_jobs: u64,
pub dead_jobs: u64,
pub skipped_replication_events: u64,
pub safe_replication_cursor: Option<u64>,
pub compacted: bool,
pub dry_run: bool,
}
pub trait ObjectStore {
fn put_object(&mut self, object: MemoryObject) -> ThingdResult<MemoryObject>;
fn put_objects_batch(&mut self, objects: Vec<MemoryObject>) -> ThingdResult<Vec<MemoryObject>> {
let mut results = Vec::with_capacity(objects.len());
for object in objects {
results.push(self.put_object(object)?);
}
Ok(results)
}
fn put_object_with_options(
&mut self,
object: MemoryObject,
options: PutObjectOptions,
) -> ThingdResult<MemoryObject> {
let _ = options;
self.put_object(object)
}
fn put_object_with_source_metadata(
&mut self,
object: MemoryObject,
options: PutObjectOptions,
) -> ThingdResult<MemoryObject> {
self.put_object_with_options(object, options)
}
fn get_object(&self, collection: &str, id: &str) -> ThingdResult<Option<MemoryObject>>;
fn get_objects_batch(
&self,
collection: &str,
ids: &[String],
) -> ThingdResult<Vec<Option<MemoryObject>>> {
let mut results = Vec::with_capacity(ids.len());
for id in ids {
results.push(self.get_object(collection, id)?);
}
Ok(results)
}
fn list_objects(
&self,
collections: Option<&[String]>,
options: &ListObjectsOptions,
) -> ThingdResult<Vec<MemoryObject>>;
fn delete_object(&mut self, collection: &str, id: &str) -> ThingdResult<bool>;
fn delete_objects_batch(&mut self, keys: &[(String, String)]) -> ThingdResult<u64> {
let mut count = 0u64;
for (collection, id) in keys {
if self.delete_object(collection, id)? {
count += 1;
}
}
Ok(count)
}
fn count_objects(&self) -> ThingdResult<u64>;
fn retain(&mut self, _options: RetentionOptions) -> ThingdResult<RetentionReport> {
Err(ThingdError::Storage(
"retention is not supported by this adapter".to_string(),
))
}
fn count_objects_in_collection(&self, _collection: &str) -> ThingdResult<u64> {
self.count_objects()
}
fn list_collections(&self) -> ThingdResult<Vec<String>>;
fn create_index(&mut self, _collection: &str, _field: &str) -> ThingdResult<()> {
Ok(())
}
fn list_indexes(&self) -> ThingdResult<Vec<(String, String)>> {
Ok(vec![])
}
fn create_index_definition(&mut self, index: IndexDefinition) -> ThingdResult<()> {
if index.collection.is_empty() || index.field.is_empty() {
return Err(ThingdError::InvalidInput(
"index collection and field are required".to_string(),
));
}
if index.unique {
return Err(ThingdError::InvalidInput(
"unique indexes are not supported by this adapter".to_string(),
));
}
self.create_index(&index.collection, &index.field)
}
fn delete_index(&mut self, _collection: &str, _field: &str) -> ThingdResult<bool> {
Ok(false)
}
fn list_index_definitions(&self) -> ThingdResult<Vec<IndexDefinition>> {
Ok(self
.list_indexes()?
.into_iter()
.map(|(collection, field)| IndexDefinition {
collection,
field,
unique: false,
})
.collect())
}
fn schema(
&self,
collection: Option<&str>,
options: &SchemaOptions,
) -> ThingdResult<Vec<CollectionSchema>>;
}
pub trait EventLog {
fn is_protected_stream(&self, stream: &str) -> bool {
let _ = stream;
false
}
fn append_event(&mut self, event: MemoryEvent) -> ThingdResult<MemoryEvent>;
fn append_events_batch(&mut self, events: Vec<MemoryEvent>) -> ThingdResult<Vec<MemoryEvent>> {
let mut results = Vec::with_capacity(events.len());
for event in events {
results.push(self.append_event(event)?);
}
Ok(results)
}
fn list_events(
&self,
stream: Option<&str>,
options: ListEventsOptions,
) -> ThingdResult<Vec<MemoryEvent>>;
fn delete_last_event(&mut self, stream: &str) -> ThingdResult<Option<MemoryEvent>> {
if self.is_protected_stream(stream) {
return Err(ThingdError::Protected(format!(
"stream '{stream}' is protected and cannot be modified"
)));
}
Err(ThingdError::Storage(
"delete_last_event is not supported by this adapter".into(),
))
}
fn delete_stream(&mut self, stream: &str) -> ThingdResult<u64> {
if self.is_protected_stream(stream) {
return Err(ThingdError::Protected(format!(
"stream '{stream}' is protected and cannot be modified"
)));
}
Err(ThingdError::Storage(
"delete_stream is not supported by this adapter".into(),
))
}
fn count_events(&self) -> ThingdResult<u64>;
fn list_streams(&self) -> ThingdResult<Vec<String>>;
}
pub trait QueueStore {
fn push_job(&mut self, job: QueueJob) -> ThingdResult<QueueJob>;
fn push_jobs_batch(&mut self, jobs: Vec<QueueJob>) -> ThingdResult<Vec<QueueJob>> {
let mut results = Vec::with_capacity(jobs.len());
for job in jobs {
results.push(self.push_job(job)?);
}
Ok(results)
}
fn claim_job(&mut self, queue: &str) -> ThingdResult<Option<QueueJob>> {
self.claim_job_with_options(queue, QueueClaimOptions::default())
}
fn claim_job_with_options(
&mut self,
queue: &str,
options: QueueClaimOptions,
) -> ThingdResult<Option<QueueJob>>;
fn ack_job(&mut self, queue: &str, id: &str) -> ThingdResult<Option<QueueJob>>;
fn claim_and_ack(
&mut self,
queue: &str,
options: QueueClaimOptions,
) -> ThingdResult<Option<QueueJob>> {
if let Some(job) = self.claim_job_with_options(queue, options)? {
self.ack_job(queue, &job.id)
} else {
Ok(None)
}
}
fn nack_job(&mut self, queue: &str, id: &str) -> ThingdResult<Option<QueueJob>> {
self.nack_job_with_options(queue, id, QueueNackOptions::default())
}
fn nack_job_with_options(
&mut self,
queue: &str,
id: &str,
options: QueueNackOptions,
) -> ThingdResult<Option<QueueJob>>;
fn list_jobs(&self, queue: &str) -> ThingdResult<Vec<QueueJob>>;
fn list_dead_jobs(&self, queue: &str) -> ThingdResult<Vec<QueueJob>>;
fn list_queues(&self) -> ThingdResult<Vec<String>>;
fn count_active_jobs(&self) -> ThingdResult<u64>;
fn count_dead_jobs(&self) -> ThingdResult<u64>;
}
pub trait Searcher {
fn search(
&self,
query: &str,
options: crate::SearchOptions,
) -> ThingdResult<Vec<crate::SearchHit>>;
}
pub trait LinkStore {
fn create_link(&mut self, link: crate::Link) -> ThingdResult<crate::Link>;
fn delete_link(&mut self, id: &str) -> ThingdResult<bool>;
fn get_link(&self, id: &str) -> ThingdResult<Option<crate::Link>>;
fn get_neighbors(
&self,
reference: &str,
direction: crate::LinkDirection,
options: crate::LinkQueryOptions,
) -> ThingdResult<Vec<crate::Link>>;
fn count_links(&self) -> ThingdResult<u64>;
}
pub trait AggregateStore {
fn aggregate(
&self,
collection: &str,
options: &AggregateOptions,
) -> ThingdResult<AggregateResult>;
fn timeseries(
&self,
collection: &str,
options: &TimeSeriesOptions,
) -> ThingdResult<TimeSeriesResult>;
}
pub trait VectorStore {
fn vector_search(
&self,
collection: &str,
query_vector: &[f32],
options: VectorSearchOptions,
) -> ThingdResult<Vec<VectorSearchHit>>;
fn add_vector(&mut self, collection: &str, id: &str, vector: &[f32]) -> ThingdResult<()>;
fn remove_vector(&mut self, collection: &str, id: &str) -> ThingdResult<()>;
}
pub trait ThingStore:
EventLog
+ ObjectStore
+ QueueStore
+ Searcher
+ LinkStore
+ AggregateStore
+ VectorStore
+ SchemaStore
{
fn search_rebuild_required(&self) -> bool {
false
}
fn search_rebuild_step(&mut self, _batch_size: usize) -> ThingdResult<bool> {
Ok(true)
}
#[cfg(feature = "persistent")]
fn search_rebuild_status(&self) -> Option<crate::SearchRebuildStatus> {
None
}
fn storage_diagnostics(&self) -> ThingdResult<StorageDiagnostics> {
Ok(StorageDiagnostics {
objects: self.count_objects()?,
events: self.count_events()?,
links: self.count_links()?,
queues: self.list_queues()?.len() as u64,
active_jobs: self.count_active_jobs()?,
dead_jobs: self.count_dead_jobs()?,
})
}
}
impl ThingStore for crate::MemoryEngine {}
#[cfg(feature = "persistent")]
impl ThingStore for crate::PersistentEngine {
fn search_rebuild_required(&self) -> bool {
self.search_rebuild_required()
}
fn search_rebuild_step(&mut self, batch_size: usize) -> ThingdResult<bool> {
self.search_rebuild_step(batch_size)
}
fn search_rebuild_status(&self) -> Option<crate::SearchRebuildStatus> {
self.search_rebuild_status()
}
}
pub trait SchemaStore {
fn get_schema_document(&self) -> ThingdResult<Option<StoredSchema>>;
fn put_schema_document(&mut self, schema: StoredSchema) -> ThingdResult<()>;
fn list_migrations(&self) -> ThingdResult<Vec<MigrationRecord>>;
fn record_migration(&mut self, migration: MigrationRecord) -> ThingdResult<()>;
}