use anyhow::Error;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::BTreeMap;
use crate::entity::Entity;
use super::{Database, DbResult, query::Query};
pub type EntityID = String;
#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize)]
pub struct Index {
pub name: String,
pub keys: Vec<String>,
pub options: Option<IndexOptions>,
}
#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize)]
pub struct IndexOptions {
pub unique: bool,
pub sparse: bool,
pub case_insensitive: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum IndexKey {
Single(ValueKey),
Compound(Vec<ValueKey>),
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum ValueKey {
Null,
Bool(bool),
Number(i64),
String(String),
}
#[derive(Debug, Clone)]
pub struct BuiltIndex {
pub keys: Vec<String>,
pub map: BTreeMap<IndexKey, Vec<EntityID>>,
}
#[derive(Debug, Clone)]
pub struct IndexStore {
pub indexes: Vec<BuiltIndex>,
}
pub fn value_to_key(value: &Value) -> Option<ValueKey> {
match value {
Value::String(s) => Some(ValueKey::String(s.clone())),
Value::Number(n) => n.as_i64().map(ValueKey::Number),
Value::Bool(b) => Some(ValueKey::Bool(*b)),
_ => None,
}
}
impl Database {
pub fn build_index(&mut self, entity: &Entity) -> DbResult<()> {
let mut built_indexes = Vec::<BuiltIndex>::new();
log::debug!("BUILD INDEX");
let documents = self.find_many(entity, Query::All, None).unwrap_or(vec![]);
for index_def in &entity.indexes {
let keys = &index_def.keys;
if keys.is_empty() {
continue;
}
let mut map = BTreeMap::new();
for document in &documents {
let mut key_parts = Vec::new();
let mut skip = false;
for col in keys {
match document.get(col).and_then(value_to_key) {
Some(part) => key_parts.push(part),
None => {
skip = true;
break;
}
}
}
if skip {
continue;
}
let key = if key_parts.len() == 1 {
IndexKey::Single(key_parts[0].clone())
} else {
IndexKey::Compound(key_parts)
};
if let Some(_id) = document.get("_id").and_then(|v| v.as_str()) {
map.entry(key)
.or_insert_with(Vec::new)
.push(_id.to_string());
}
}
built_indexes.push(BuiltIndex {
keys: keys.to_vec(),
map,
});
}
let instance = self
.get_instance_by_entity_mut(entity)
.ok_or(Error::msg("Failed to find instance while indexing."))?;
let index_store = IndexStore {
indexes: built_indexes,
};
instance.indexes.insert(entity.name.clone(), index_store);
Ok(())
}
pub fn append_indexes(&mut self, entity: &Entity, inserted: &[Value]) -> DbResult<()> {
let instance = self
.get_instance_by_entity_mut(entity)
.ok_or_else(|| Error::msg("Entity not found for indexing"))?;
let index_store = instance
.indexes
.entry(entity.name.clone())
.or_insert_with(|| IndexStore { indexes: vec![] });
for index_def in &entity.indexes {
let keys = &index_def.keys;
if keys.is_empty() {
continue;
}
let built_index = index_store.indexes.iter_mut().find(|idx| idx.keys == *keys);
let index_map = if let Some(existing) = built_index {
&mut existing.map
} else {
index_store.indexes.push(BuiltIndex {
keys: keys.clone(),
map: BTreeMap::new(),
});
&mut index_store.indexes.last_mut().unwrap().map
};
for document in inserted {
let mut key_parts = Vec::new();
let mut skip = false;
for col in keys {
match document.get(col).and_then(value_to_key) {
Some(part) => key_parts.push(part),
None => {
skip = true;
break;
}
}
}
if skip {
continue;
}
let key = if key_parts.len() == 1 {
IndexKey::Single(key_parts[0].clone())
} else {
IndexKey::Compound(key_parts)
};
if let Some(_id) = document.get("_id").and_then(|v| v.as_str()) {
index_map
.entry(key)
.or_insert_with(Vec::new)
.push(_id.to_string());
}
}
}
Ok(())
}
pub fn update_indexes(
&mut self,
entity: &Entity,
old_value: &Value,
new_value: &Value,
) -> DbResult<()> {
let instance = self
.get_instance_by_entity_mut(entity)
.ok_or_else(|| Error::msg("Entity not found for indexing"))?;
let index_store = instance
.indexes
.entry(entity.name.clone())
.or_insert_with(|| IndexStore { indexes: vec![] });
for index_def in &entity.indexes {
let keys = &index_def.keys;
if keys.is_empty() {
continue;
}
let built_index = match index_store.indexes.iter_mut().find(|idx| &idx.keys == keys) {
Some(idx) => idx,
None => continue, };
let mut old_key_parts = Vec::new();
let mut skip_old = false;
for col in keys {
match old_value.get(col).and_then(value_to_key) {
Some(part) => old_key_parts.push(part),
None => {
skip_old = true;
break;
}
}
}
if !skip_old {
let old_key = if old_key_parts.len() == 1 {
IndexKey::Single(old_key_parts[0].clone())
} else {
IndexKey::Compound(old_key_parts)
};
if let Some(ids) = built_index.map.get_mut(&old_key) {
if let Some(pos) = ids.iter().position(|id| {
id == old_value
.get("_id")
.and_then(|v| v.as_str())
.unwrap_or_default()
}) {
ids.remove(pos);
}
}
}
let mut new_key_parts = Vec::new();
let mut skip_new = false;
for col in keys {
match new_value.get(col).and_then(value_to_key) {
Some(part) => new_key_parts.push(part),
None => {
skip_new = true;
break;
}
}
}
if !skip_new {
let new_key = if new_key_parts.len() == 1 {
IndexKey::Single(new_key_parts[0].clone())
} else {
IndexKey::Compound(new_key_parts)
};
if let Some(_id) = new_value.get("_id").and_then(|v| v.as_str()) {
built_index
.map
.entry(new_key)
.or_insert_with(Vec::new)
.push(_id.to_string());
}
}
}
Ok(())
}
pub fn delete_indexes(&mut self, entity: &Entity, deleted: &[Value]) -> DbResult<()> {
let instance = self
.get_instance_by_entity_mut(entity)
.ok_or_else(|| Error::msg("Entity not found for indexing"))?;
let index_store = match instance.indexes.get_mut(&entity.name) {
Some(store) => store,
None => return Ok(()), };
for document in deleted {
for index_def in &entity.indexes {
let keys = &index_def.keys;
if keys.is_empty() {
continue;
}
let built_index = match index_store.indexes.iter_mut().find(|idx| &idx.keys == keys)
{
Some(idx) => idx,
None => continue, };
let mut key_parts = Vec::new();
let mut skip = false;
for col in keys {
match document.get(col).and_then(value_to_key) {
Some(part) => key_parts.push(part),
None => {
skip = true;
break;
}
}
}
if skip {
continue;
}
let key = if key_parts.len() == 1 {
IndexKey::Single(key_parts[0].clone())
} else {
IndexKey::Compound(key_parts)
};
if let Some(ids) = built_index.map.get_mut(&key) {
if let Some(id_to_delete) = document.get("_id").and_then(|v| v.as_str()) {
if let Some(pos) = ids.iter().position(|id| id == id_to_delete) {
ids.remove(pos);
}
if ids.is_empty() {
built_index.map.remove(&key);
}
}
}
}
}
Ok(())
}
}