use std::sync::Arc;
use nodedb_types::Namespace;
use nodedb_types::result::QueryResult;
use crate::error::LiteError;
use crate::index::rebuild::build_index;
use crate::index::{
IndexDef, IndexEngine, IndexPredicate, canonical_field, default_index_name, field_spec,
};
use crate::nodedb::collection::CollectionMeta;
use crate::query::engine::LiteQueryEngine;
use crate::storage::engine::StorageEngine;
pub async fn register<S: StorageEngine>(
engine: &LiteQueryEngine<S>,
collection: &str,
storage_mode: &nodedb_physical::physical_plan::document::types::StorageMode,
) -> Result<QueryResult, LiteError> {
use nodedb_physical::physical_plan::document::types::StorageMode;
match storage_mode {
StorageMode::Strict { schema } => {
engine
.strict
.create_collection(collection, schema.clone())
.await?;
}
StorageMode::Schemaless => {
}
}
Ok(affected(0))
}
pub struct CreateIndexRequest<'a> {
pub name: Option<&'a str>,
pub collection: &'a str,
pub field: &'a str,
pub unique: bool,
pub case_insensitive: bool,
pub predicate: Option<&'a str>,
pub if_not_exists: bool,
}
fn affected(n: u64) -> QueryResult {
QueryResult {
columns: Vec::new(),
rows: Vec::new(),
rows_affected: n,
command: None,
}
}
async fn index_engine_for<S: StorageEngine>(
engine: &LiteQueryEngine<S>,
collection: &str,
) -> Result<IndexEngine, LiteError> {
if engine.strict.schema(collection).is_some() {
return Ok(IndexEngine::Strict);
}
if engine.columnar.schema(collection).is_some() {
return Err(LiteError::Unsupported {
detail: format!(
"CREATE INDEX on columnar collection '{collection}': columnar collections \
are filtered by block statistics, not secondary indexes"
),
});
}
let meta_key = format!("collection:{collection}");
if let Some(bytes) = engine
.storage
.get(Namespace::Meta, meta_key.as_bytes())
.await?
{
let meta: CollectionMeta =
sonic_rs::from_slice(&bytes).map_err(|e| LiteError::Serialization {
detail: format!("metadata of collection '{collection}' does not decode: {e}"),
})?;
return Ok(match meta.collection_type.as_str() {
"kv" => IndexEngine::KeyValue,
"columnar" | "timeseries" | "spatial" => {
return Err(LiteError::Unsupported {
detail: format!(
"CREATE INDEX on {} collection '{collection}' is not supported",
meta.collection_type
),
});
}
_ => IndexEngine::Document,
});
}
let known = engine
.crdt
.lock()
.map_err(|_| LiteError::LockPoisoned)?
.collection_names()
.iter()
.any(|n| n == collection);
if known {
Ok(IndexEngine::Document)
} else {
Err(LiteError::BadRequest {
detail: format!("CREATE INDEX: collection '{collection}' does not exist"),
})
}
}
pub async fn create_index<S: StorageEngine>(
engine: &LiteQueryEngine<S>,
req: CreateIndexRequest<'_>,
) -> Result<QueryResult, LiteError> {
let index_engine = index_engine_for(engine, req.collection).await?;
declare_index(engine, req, index_engine).await
}
pub(crate) async fn declare_index<S: StorageEngine>(
engine: &LiteQueryEngine<S>,
req: CreateIndexRequest<'_>,
index_engine: IndexEngine,
) -> Result<QueryResult, LiteError> {
let (path, is_array) = canonical_field(req.field);
let name = req
.name
.map(str::to_string)
.unwrap_or_else(|| default_index_name(req.collection, req.field));
if let Some(existing) = engine.indexes.def_named(&name) {
if req.if_not_exists {
return Ok(affected(0));
}
return Err(LiteError::BadRequest {
detail: format!("index '{name}' already exists on '{}'", existing.collection),
});
}
let spec = field_spec(&path, is_array);
if let Some(existing) = engine.indexes.def_on_field(req.collection, &spec) {
if req.if_not_exists {
return Ok(affected(0));
}
return Err(LiteError::BadRequest {
detail: format!(
"collection '{}' already has index '{}' on {spec}: \
drop it before indexing the field again",
req.collection, existing.name
),
});
}
let def = Arc::new(IndexDef {
name,
collection: req.collection.to_string(),
path,
unique: req.unique,
case_insensitive: req.case_insensitive,
is_array,
predicate: req.predicate.map(IndexPredicate::parse).transpose()?,
engine: index_engine,
});
Ok(affected(rebuild_index(engine, def).await?))
}
pub async fn drop_index<S: StorageEngine>(
engine: &LiteQueryEngine<S>,
name: &str,
if_exists: bool,
) -> Result<QueryResult, LiteError> {
match engine.indexes.drop_index(&*engine.storage, name).await? {
Some(_) => Ok(affected(0)),
None if if_exists => Ok(affected(0)),
None => Err(LiteError::BadRequest {
detail: format!("index '{name}' does not exist"),
}),
}
}
pub async fn drop_field_index<S: StorageEngine>(
engine: &LiteQueryEngine<S>,
collection: &str,
field: &str,
) -> Result<QueryResult, LiteError> {
let (path, is_array) = canonical_field(field);
let name = engine
.indexes
.def_on_field(collection, &field_spec(&path, is_array))
.map(|def| def.name.clone())
.unwrap_or_else(|| field.to_string());
drop_index(engine, &name, true).await
}
pub async fn rebuild_index<S: StorageEngine>(
engine: &LiteQueryEngine<S>,
def: Arc<IndexDef>,
) -> Result<u64, LiteError> {
build_index(
&engine.indexes,
&*engine.storage,
&engine.crdt,
&engine.strict,
def,
)
.await
}
pub async fn reindex<S: StorageEngine>(
engine: &LiteQueryEngine<S>,
collection: &str,
name: Option<&str>,
) -> Result<QueryResult, LiteError> {
let defs: Vec<Arc<IndexDef>> = match name {
Some(name) => {
let def = engine
.indexes
.def_named(name)
.ok_or_else(|| LiteError::BadRequest {
detail: format!("index '{name}' does not exist"),
})?;
vec![def]
}
None => engine
.indexes
.defs()
.into_iter()
.filter(|d| d.collection == collection)
.collect(),
};
let mut built = 0;
for def in defs {
built += rebuild_index(engine, def).await?;
}
Ok(affected(built))
}
pub async fn backfill_index<S: StorageEngine>(
engine: &LiteQueryEngine<S>,
req: CreateIndexRequest<'_>,
) -> Result<QueryResult, LiteError> {
let (path, is_array) = canonical_field(req.field);
match engine
.indexes
.def_on_field(req.collection, &field_spec(&path, is_array))
{
Some(def) => Ok(affected(rebuild_index(engine, def).await?)),
None => create_index(engine, req).await,
}
}