pub trait DatabaseBackend:
Send
+ Sync
+ 'static {
// Required methods
fn capabilities(&self) -> CapabilitySet;
fn catalog(
&self,
request: CatalogRequest,
) -> impl Future<Output = Result<CatalogResponse, StorageError>> + Send + '_;
fn data(
&self,
request: DataRequest,
) -> impl Future<Output = Result<DataResponse, StorageError>> + Send + '_;
fn query(
&self,
request: QueryRequest,
) -> impl Future<Output = Result<QueryResponse, StorageError>> + Send + '_;
fn index(
&self,
request: IndexRequest,
) -> impl Future<Output = Result<IndexResponse, StorageError>> + Send + '_;
}Expand description
Portable data, catalog, query, and index behavior.
Implement this trait directly on a caller-owned type; Clone is not
required. Operations are authoritative: an optional capability bit does not
guarantee that an arbitrary request is supported. Unsupported operations
fail before mutation, and responses preserve real ids, counts and score
semantics. Supporting DTOs live in crate::database.
§Examples
use storage_kit::{Database, DatabaseBackend};
use storage_kit::database::config::{DatabaseConfig, LanceConfig};
use storage_kit::database::CapabilitySet;
let database = Database::connect(DatabaseConfig::lance(LanceConfig::embedded_path("memory://"))).await?;
let capabilities = database.capabilities();
assert!(capabilities.contains(CapabilitySet::FILTERING));§Caller-owned exhaustive composition
A caller can choose a database once and keep dispatch concrete. Here Primary
and Archive are application roles over real supplied stores, not new backend
capabilities. Every request and error is delegated unchanged. The same pattern
can contain different concrete implementations of this trait; there is no
normalization constructor and no obligation for the caller root to be Clone.
use storage_kit::{Database, DatabaseBackend, StorageError};
use storage_kit::database::CapabilitySet;
use storage_kit::database::catalog::{CatalogRequest, CatalogResponse, ListCollectionsRequest};
use storage_kit::database::data::{DataRequest, DataResponse};
use storage_kit::database::query::{QueryRequest, QueryResponse};
use storage_kit::database::index::{IndexRequest, IndexResponse};
use storage_kit::database::config::{DatabaseConfig, LanceConfig};
enum ApplicationDatabase { Primary(Database), Archive(Database) }
impl DatabaseBackend for ApplicationDatabase {
fn capabilities(&self) -> CapabilitySet {
match self { Self::Primary(db) => db.capabilities(), Self::Archive(db) => db.capabilities() }
}
async fn catalog(&self, request: CatalogRequest) -> Result<CatalogResponse, StorageError> {
match self { Self::Primary(db) => db.catalog(request).await, Self::Archive(db) => db.catalog(request).await }
}
async fn data(&self, request: DataRequest) -> Result<DataResponse, StorageError> {
match self { Self::Primary(db) => db.data(request).await, Self::Archive(db) => db.data(request).await }
}
async fn query(&self, request: QueryRequest) -> Result<QueryResponse, StorageError> {
match self { Self::Primary(db) => db.query(request).await, Self::Archive(db) => db.query(request).await }
}
async fn index(&self, request: IndexRequest) -> Result<IndexResponse, StorageError> {
match self { Self::Primary(db) => db.index(request).await, Self::Archive(db) => db.index(request).await }
}
}
let primary = ApplicationDatabase::Primary(Database::connect(
DatabaseConfig::lance(LanceConfig::embedded_path("memory://")),
).await?);
let archive = ApplicationDatabase::Archive(Database::connect(
DatabaseConfig::lance(LanceConfig::embedded_path("memory://")),
).await?);
for selected in [&primary, &archive] {
let CatalogResponse::Collections(page) = selected.catalog(CatalogRequest::ListCollections(
ListCollectionsRequest { namespace: Vec::new(), limit: Some(10), start_after: None },
)).await? else { panic!("catalog listing returns collections") };
assert!(page.is_empty());
}
drop((primary, archive));Required Methods§
Sourcefn capabilities(&self) -> CapabilitySet
fn capabilities(&self) -> CapabilitySet
Returns optional retained capabilities.
Use this to discover optional operation families, not to identify the implementation or assume an arbitrary request will succeed.
§Examples
use storage_kit::{Database, DatabaseBackend};
use storage_kit::database::config::{DatabaseConfig, LanceConfig};
use storage_kit::database::CapabilitySet;
let database = Database::connect(DatabaseConfig::lance(LanceConfig::embedded_path("memory://"))).await?;
let optional = database.capabilities();
assert!(optional.contains(CapabilitySet::FILTERING));Sourcefn catalog(
&self,
request: CatalogRequest,
) -> impl Future<Output = Result<CatalogResponse, StorageError>> + Send + '_
fn catalog( &self, request: CatalogRequest, ) -> impl Future<Output = Result<CatalogResponse, StorageError>> + Send + '_
Executes a retained catalog operation.
Creation, opening, listing, deletion, and schema evolution remain distinct from data writes and StateStore transactions.
§Errors
Returns StorageError for invalid scope or request data, unsupported
operations, and native connection, deadline, or execution failures. An
unsupported operation must fail before mutation.
§Examples
use storage_kit::{Database, DatabaseBackend};
use storage_kit::database::config::{DatabaseConfig, LanceConfig};
use storage_kit::database::catalog::{CatalogRequest, CatalogResponse, ListCollectionsRequest};
let database = Database::connect(DatabaseConfig::lance(LanceConfig::embedded_path("memory://"))).await?;
let response = database.catalog(CatalogRequest::ListCollections(ListCollectionsRequest {
namespace: Vec::new(), limit: Some(10), start_after: None,
})).await?;
assert!(matches!(response, CatalogResponse::Collections(_)));Sourcefn data(
&self,
request: DataRequest,
) -> impl Future<Output = Result<DataResponse, StorageError>> + Send + '_
fn data( &self, request: DataRequest, ) -> impl Future<Output = Result<DataResponse, StorageError>> + Send + '_
Executes a retained data operation.
Ordinary writes are not rollback-capable multi-request transactions. A native failure or timeout can leave an uncertain or partial outcome and must not trigger automatic replay. Partition replacement retains its explicit atomic contract.
§Errors
Returns StorageError for invalid input, unsupported request combinations,
read-only policy, serialization failures, and native execution/deadline
failures. Unsupported behavior must be rejected before mutation.
§Examples
use storage_kit::{Database, DatabaseBackend};
use storage_kit::database::config::{DatabaseConfig, LanceConfig};
use storage_kit::database::StorageLocator;
use storage_kit::database::catalog::{CatalogRequest, CreateCollectionRequest, CreateCollectionMode};
use storage_kit::database::data::{DataRecord, DataRequest, DataResponse, CountRequest};
let database = Database::connect(DatabaseConfig::lance(LanceConfig::embedded_path("memory://"))).await?;
let locator = StorageLocator::new("documents");
database.catalog(CatalogRequest::CreateCollection(CreateCollectionRequest {
locator: locator.clone(), schema: None,
records: vec![DataRecord::without_id().json_field("text", serde_json::json!("example"))],
mode: CreateCollectionMode::Create,
})).await?;
let response = database.data(DataRequest::Count(CountRequest { locator, filter: None })).await?;
assert!(matches!(response, DataResponse::Count(1)));Sourcefn query(
&self,
request: QueryRequest,
) -> impl Future<Output = Result<QueryResponse, StorageError>> + Send + '_
fn query( &self, request: QueryRequest, ) -> impl Future<Output = Result<QueryResponse, StorageError>> + Send + '_
Executes a retained query operation.
Return continuation tokens unchanged to the same applicable operation. Scores preserve their explicit kind; a missing score is not zero.
§Errors
Returns StorageError for invalid scope, vectors, fields, filters, or
tuning, unsupported query combinations, and native execution/deadline
failures. Optional capability bits do not override request validation.
§Examples
use storage_kit::{Database, DatabaseBackend};
use storage_kit::database::config::{DatabaseConfig, LanceConfig};
use storage_kit::database::StorageLocator;
use storage_kit::database::catalog::{CatalogRequest, CreateCollectionRequest, CreateCollectionMode};
use storage_kit::database::data::DataRecord;
use storage_kit::database::query::{QueryRequest, ScrollRequest, Projection};
let database = Database::connect(DatabaseConfig::lance(LanceConfig::embedded_path("memory://"))).await?;
let locator = StorageLocator::new("documents");
database.catalog(CatalogRequest::CreateCollection(CreateCollectionRequest {
locator: locator.clone(), schema: None,
records: vec![DataRecord::without_id().json_field("text", serde_json::json!("example"))],
mode: CreateCollectionMode::Create,
})).await?;
let response = database.query(QueryRequest::Scroll(ScrollRequest {
locator, filter: None, projection: Projection::All, limit: Some(10),
offset: None, page_token: None,
})).await?;
assert_eq!(response.hits.len(), 1);Sourcefn index(
&self,
request: IndexRequest,
) -> impl Future<Output = Result<IndexResponse, StorageError>> + Send + '_
fn index( &self, request: IndexRequest, ) -> impl Future<Output = Result<IndexResponse, StorageError>> + Send + '_
Executes a retained index operation.
Creating an index and observing its readiness are separate operations. Readiness requests must finish within both their requested wait bound and the enclosing configured operation deadline.
§Errors
Returns StorageError for malformed index requests, unsupported kinds,
read-only policy, missing targets, unconfirmed completion, or native
execution/readiness deadline failures.
§Examples
use storage_kit::{Database, DatabaseBackend};
use storage_kit::database::config::{DatabaseConfig, LanceConfig};
use storage_kit::database::StorageLocator;
use storage_kit::database::catalog::{CatalogRequest, CreateCollectionRequest, CreateCollectionMode};
use storage_kit::database::data::DataRecord;
use storage_kit::database::index::{IndexRequest, IndexResponse};
let database = Database::connect(DatabaseConfig::lance(LanceConfig::embedded_path("memory://"))).await?;
let locator = StorageLocator::new("documents");
database.catalog(CatalogRequest::CreateCollection(CreateCollectionRequest {
locator: locator.clone(), schema: None,
records: vec![DataRecord::without_id().json_field("text", serde_json::json!("example"))],
mode: CreateCollectionMode::Create,
})).await?;
let response = database.index(IndexRequest::List { locator }).await?;
assert!(matches!(response, IndexResponse::Indexes(_)));Dyn Compatibility§
This trait is not dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".