storage_kit/database/mod.rs
1//! Collection storage, retrieval, and index request/response types.
2//!
3//! Open a supplied [`crate::Database`] with [`config::DatabaseConfig`] and
4//! import [`crate::DatabaseBackend`] to execute requests. [`catalog`] owns
5//! collections and schema evolution, [`data`] owns records and mutations,
6//! [`query`] owns search, and [`index`] owns index lifecycle. [`StorageLocator`]
7//! is a logical address, not a native connection configuration.
8//!
9//! Optional [`CapabilitySet`] bits are hints about retained operation families;
10//! each call validates the actual request and may reject unsupported options.
11//! Database writes do not share the transactional guarantees of
12//! [`crate::StateStore`].
13
14//! # Local collection, retrieval and cleanup
15//!
16//! This executable example uses a tiny process-local LanceDB collection. Logical
17//! identity lives in an application field: this LanceDB adapter does not support
18//! portable `DataRecord.id` or ID lookup. A scalar index works at this scale; a vector query
19//! does not require a trained vector index. Other backends can reject unsupported
20//! index kinds or options explicitly. Do not interpret every score as similarity.
21//!
22//! ```
23//! use storage_kit::{Database, DatabaseBackend};
24//! use storage_kit::database::{StorageLocator, config::{DatabaseConfig, LanceConfig}};
25//! use storage_kit::database::catalog::{CatalogRequest, CreateCollectionMode, CreateCollectionRequest};
26//! use storage_kit::database::data::{DataRecord, DataRequest, DataResponse, DeleteRequest,
27//! DenseVector, FilterExpr, InsertRequest, ScanRequest, CountRequest};
28//! use storage_kit::database::query::{DistanceMetric, NearestRequest, Projection,
29//! QueryRequest, QueryScope, ScopedQuery, ScoreKind, VectorQueryOptions, VectorSearchParams};
30//! use storage_kit::database::index::{CreateIndexRequest, IndexKind, IndexRequest,
31//! IndexResponse, WaitForIndexRequest};
32//! # tokio::runtime::Runtime::new()?.block_on(async {
33//! let database = Database::connect(DatabaseConfig::lance(LanceConfig::embedded_path("memory://"))).await?;
34//! let collection = StorageLocator::new("articles");
35//! let first = DataRecord::without_id().field("article", "intro")?.field("title", "Introduction")?
36//! .field("published", true)?.vector("embedding", DenseVector::F32(vec![1.0, 0.0]));
37//! database.catalog(CatalogRequest::CreateCollection(CreateCollectionRequest {
38//! locator: collection.clone(), schema: None, records: vec![first],
39//! mode: CreateCollectionMode::Create,
40//! })).await?;
41//! database.data(DataRequest::Insert(InsertRequest::append(collection.clone(), vec![
42//! DataRecord::without_id().field("article", "draft")?.field("title", "Draft")?.field("published", false)?
43//! .vector("embedding", DenseVector::F32(vec![0.0, 1.0])),
44//! ]))).await?;
45//! let page = database.data(DataRequest::Scan(ScanRequest {
46//! locator: collection.clone(), filter: Some(FilterExpr::eq("published", true)),
47//! projection: Projection::Columns(vec!["title".into()]), limit: Some(10),
48//! offset: None, page_token: None,
49//! })).await?;
50//! let DataResponse::Records(page) = page else { panic!("scan returns records") };
51//! assert_eq!(page.records.len(), 1);
52//! assert_eq!(page.records[0].fields["title"], "Introduction");
53//!
54//! let nearest = database.query(QueryRequest::Nearest(NearestRequest {
55//! query: ScopedQuery::new(QueryScope::new(collection.clone()), VectorQueryOptions {
56//! vector_field: Some("embedding".into()),
57//! params: VectorSearchParams { limit: 1, distance: Some(DistanceMetric::L2), ..Default::default() },
58//! }),
59//! vector: DenseVector::F32(vec![1.0, 0.0]),
60//! })).await?;
61//! assert_eq!(nearest.hits.len(), 1);
62//! assert_eq!(nearest.hits[0].record.fields["article"], "intro");
63//! assert_eq!(nearest.hits[0].score_kind, ScoreKind::Distance);
64//! assert_eq!(nearest.hits[0].raw_score, Some(0.0));
65//!
66//! database.index(IndexRequest::Create(CreateIndexRequest {
67//! locator: collection.clone(), columns: vec!["title".into()], kind: IndexKind::Scalar,
68//! wait_timeout_ms: None,
69//! })).await?;
70//! let IndexResponse::Indexes(indexes) = database.index(IndexRequest::List {
71//! locator: collection.clone(),
72//! }).await? else { panic!("index listing returns metadata") };
73//! let name = indexes.iter().find(|index| index.columns == ["title"])
74//! .and_then(|index| index.name.clone()).expect("LanceDB reports its scalar index name");
75//! database.index(IndexRequest::WaitFor(WaitForIndexRequest {
76//! locator: collection.clone(), index_names: vec![name.clone()], timeout_ms: 5_000,
77//! })).await?;
78//! database.index(IndexRequest::Drop { locator: collection.clone(), name }).await?;
79//!
80//! database.data(DataRequest::Delete(DeleteRequest {
81//! locator: collection.clone(), filter: FilterExpr::eq("published", false),
82//! })).await?;
83//! let DataResponse::Records(remaining) = database.data(DataRequest::Scan(ScanRequest {
84//! locator: collection.clone(), filter: Some(FilterExpr::eq("article", "intro")),
85//! projection: Projection::All, limit: Some(10), offset: None, page_token: None,
86//! })).await? else { panic!("scan returns records") };
87//! assert_eq!(remaining.records.len(), 1);
88//! assert!(matches!(database.data(DataRequest::Count(CountRequest {
89//! locator: collection.clone(), filter: None,
90//! })).await?, DataResponse::Count(1)));
91//! database.catalog(CatalogRequest::DropCollection {
92//! locator: collection, ignore_missing: false,
93//! }).await?;
94//! drop(database);
95//! # Ok::<(), storage_kit::StorageError>(())
96//! # })?;
97//! # Ok::<(), Box<dyn std::error::Error>>(())
98//! ```
99//!
100//! Database has no multi-operation transaction or explicit shutdown method. A
101//! failed ordinary write can have an uncertain outcome; reconcile before replaying.
102//! Drop all owners before removing a caller-owned directory. For control-plane
103//! atomicity, use the separate [StateStore transaction workflow](crate::state).
104
105/// Built-in database construction data.
106///
107/// Constructors build configuration without I/O. Validation and connection
108/// reject invalid settings; [`crate::Database::connect`] opens the selected
109/// backend.
110pub mod config {
111 pub use crate::credentials::Password;
112 pub use crate::databases::config::{
113 CommonConfig, DatabaseConfig, LanceConfig, SurrealConfig, SurrealModeConfig,
114 };
115}
116
117/// Portable collection catalog and schema alteration.
118///
119/// Execute [`CatalogRequest`](crate::database::catalog::CatalogRequest) through [`crate::DatabaseBackend::catalog`].
120/// Catalog overwrite recreates the collection; replacing records while keeping
121/// its schema and indexes belongs to [`WriteMode::Overwrite`](crate::database::data::WriteMode::Overwrite).
122pub mod catalog {
123 pub use crate::core::{
124 AddColumnsRequest, AlterColumnsRequest, CatalogRequest, CatalogResponse, CollectionInfo,
125 CollectionSchema, ColumnAlterationSpec, ComputedColumn, CreateCollectionMode,
126 CreateCollectionRequest, DropColumnsRequest, FieldSchema, FieldType,
127 ListCollectionsRequest, SchemaEvolutionRequest, VectorElementType,
128 };
129}
130
131/// Portable records and data mutations.
132///
133/// Execute [`DataRequest`](crate::database::data::DataRequest) through [`crate::DatabaseBackend::data`]. Optional
134/// mutation counts and continuation values preserve backend observations.
135/// Ordinary writes may have uncertain outcomes after native failures;
136/// [`ReplacePartitionRequest`](crate::database::data::ReplacePartitionRequest) has its own stronger atomic contract.
137pub mod data {
138 pub use crate::core::{
139 ComparisonOp, CountRequest, DataRecord, DataRequest, DataResponse, DeleteRequest,
140 DenseVector, FilterExpr, InsertRequest, MutationResult, PageToken, Projection, RecordId,
141 RecordsPage, ReplacePartitionRequest, ScanRequest, UpdateAssignment, UpdateRequest,
142 WriteMode,
143 };
144}
145
146/// Portable retained search operations.
147///
148/// Build a [`QueryScope`](crate::database::query::QueryScope) and [`ScopedQuery`](crate::database::query::ScopedQuery) with vector or text options, then
149/// execute a [`QueryRequest`](crate::database::query::QueryRequest) through [`crate::DatabaseBackend::query`]. Read
150/// [`ScoredRecord::score_kind`](crate::database::query::ScoredRecord::score_kind) before interpreting scores and return page
151/// tokens unchanged to the same applicable operation.
152pub mod query {
153 pub use crate::core::{
154 DenseVector, DistanceMetric, NearestRequest, Projection, QueryRequest, QueryResponse,
155 QueryScope, ScopedQuery, ScoreKind, ScoredRecord, ScrollRequest, TextQueryOptions,
156 TextSearchRequest, VectorCoercionPolicy, VectorQueryOptions, VectorSearchParams,
157 };
158}
159
160/// Portable index lifecycle.
161///
162/// Execute [`IndexRequest`](crate::database::index::IndexRequest) through [`crate::DatabaseBackend::index`]. Creation
163/// and readiness observation are separate; unsupported kinds fail explicitly
164/// and optional metadata remains absent when it cannot be reported.
165pub mod index {
166 pub use crate::core::{
167 CreateIndexRequest, DistanceMetric, IndexInfo, IndexKind, IndexRequest, IndexResponse,
168 WaitForIndexRequest,
169 };
170}
171
172pub use crate::core::{CapabilitySet, StorageLocator};