use super::RocksDb as DB;
use dashmap::DashMap;
use std::sync::{Arc, RwLock};
use std::time::Duration;
use super::collection::Collection;
use super::columnar::*;
use super::engine::tuned_cf_options;
use super::pending_drops::{Claim, PendingCfDrops};
use crate::error::{DbError, DbResult};
use serde_json::Value;
#[derive(Clone)]
pub struct Database {
pub name: String,
db: Arc<DB>,
cf_lock: Arc<RwLock<()>>,
collections: Arc<DashMap<String, Collection>>,
pending_cf_drops: Arc<PendingCfDrops>,
}
impl std::fmt::Debug for Database {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Database")
.field("name", &self.name)
.finish()
}
}
impl Database {
pub fn new(name: String, db: Arc<DB>, pending_cf_drops: Arc<PendingCfDrops>) -> Self {
Self {
name,
db,
cf_lock: Arc::new(RwLock::new(())),
collections: Arc::new(DashMap::new()),
pending_cf_drops,
}
}
pub fn create_collection(
&self,
collection_name: String,
collection_type: Option<String>,
) -> DbResult<()> {
let cf_name = self.collection_cf_name(&collection_name);
let type_ = collection_type.unwrap_or_else(|| "document".to_string());
{
let _cf_guard = self.cf_lock.write().unwrap();
match self.pending_cf_drops.claim_for_recreate(&cf_name) {
Claim::Claimed => {
if self.db.cf_handle(&cf_name).is_some() {
if let Err(e) = super::cf_ops::timed(|| self.db.drop_cf(&cf_name)) {
self.pending_cf_drops.release_claim(&cf_name);
return Err(DbError::InternalError(format!(
"Failed to reclaim pending collection: {}",
e
)));
}
}
self.pending_cf_drops.complete(&self.db, &cf_name);
}
Claim::InProgress => {
self.pending_cf_drops
.wait_until_dropped(&cf_name, Duration::from_secs(30))?;
}
Claim::NotPending => {
if self.db.cf_handle(&cf_name).is_some() {
return Err(DbError::CollectionAlreadyExists(collection_name));
}
}
}
super::cf_ops::timed(|| self.db.create_cf(&cf_name, &tuned_cf_options())).map_err(
|e| DbError::InternalError(format!("Failed to create collection: {}", e)),
)?;
}
super::collection::index_meta::invalidate_index_meta(&self.db, &cf_name);
if let Some(cf) = self.db.cf_handle(&cf_name) {
self.db
.put_cf(&cf, "_stats:type".as_bytes(), type_.as_bytes())
.map_err(|e| {
DbError::InternalError(format!("Failed to set collection type: {}", e))
})?;
}
if type_ == "edge" {
if let Ok(coll) = self.get_collection(&collection_name) {
let probe = serde_json::Value::String(String::new());
for (idx_name, field) in [("_edge_from_idx", "_from"), ("_edge_to_idx", "_to")] {
if coll.index_lookup_eq(field, &probe).is_none() {
let _ = coll.create_index(
idx_name.to_string(),
vec![field.to_string()],
crate::storage::IndexType::Persistent,
false,
);
}
}
}
}
Ok(())
}
pub fn delete_collection(&self, collection_name: &str) -> DbResult<()> {
let cf_name = self.collection_cf_name(collection_name);
if self.pending_cf_drops.contains(&cf_name) {
return Err(DbError::CollectionNotFound(collection_name.to_string()));
}
if self.db.cf_handle(&cf_name).is_none() {
return Err(DbError::CollectionNotFound(collection_name.to_string()));
}
super::cf_ops::timed(|| self.db.drop_cf(&cf_name))
.map_err(|e| DbError::InternalError(format!("Failed to delete collection: {}", e)))?;
self.collections.remove(collection_name);
super::collection::index_meta::invalidate_index_meta(&self.db, &cf_name);
Ok(())
}
pub fn list_collections(&self) -> Vec<String> {
let prefix = format!("{}:", self.name);
let mut collections = Vec::new();
for cf_name in self.db.cf_names() {
if self.pending_cf_drops.contains(&cf_name) {
continue;
}
if let Some(name) = cf_name.strip_prefix(&prefix) {
collections.push(name.to_string());
}
}
collections
}
pub fn get_collection(&self, collection_name: &str) -> DbResult<Collection> {
if crate::storage::is_protected_collection(collection_name) {
return Err(crate::storage::protected_collection_error(collection_name));
}
self.system_collection(collection_name)
}
pub fn system_collection(&self, collection_name: &str) -> DbResult<Collection> {
if let Some(collection) = self.collections.get(collection_name) {
return Ok(collection.clone());
}
let cf_name = self.collection_cf_name(collection_name);
if self.pending_cf_drops.contains(&cf_name) {
return Err(DbError::CollectionNotFound(collection_name.to_string()));
}
if self.db.cf_handle(&cf_name).is_none() {
return Err(DbError::CollectionNotFound(collection_name.to_string()));
}
let collection = Collection::new(cf_name, self.db.clone());
self.collections
.insert(collection_name.to_string(), collection.clone());
Ok(collection)
}
pub fn get_or_create_collection(&self, collection_name: &str) -> DbResult<Collection> {
if crate::storage::is_protected_collection(collection_name) {
return Err(crate::storage::protected_collection_error(collection_name));
}
self.get_or_create_system_collection(collection_name)
}
pub fn get_or_create_system_collection(&self, collection_name: &str) -> DbResult<Collection> {
match self.system_collection(collection_name) {
Ok(collection) => Ok(collection),
Err(DbError::CollectionNotFound(_)) => {
self.create_collection(collection_name.to_string(), None)?;
self.system_collection(collection_name)
}
Err(e) => Err(e),
}
}
fn collection_cf_name(&self, collection_name: &str) -> String {
format!("{}:{}", self.name, collection_name)
}
pub fn db_arc(&self) -> Arc<DB> {
self.db.clone()
}
pub fn create_columnar(&self, name: String, columns: Vec<Value>) -> DbResult<()> {
let cols: Vec<ColumnDef> = columns
.into_iter()
.map(serde_json::from_value)
.collect::<Result<Vec<_>, _>>()
.map_err(|e| DbError::BadRequest(format!("Invalid column definition: {}", e)))?;
ColumnarCollection::new(
name,
&self.name,
self.db.clone(),
cols,
CompressionType::Lz4,
)?;
Ok(())
}
pub fn list_columnar(&self) -> Vec<String> {
let prefix = format!("{}:col_meta:", self.name);
let mut collections = Vec::new();
let iter = self.db.prefix_iterator(prefix.as_bytes());
for (key, _) in iter.flatten() {
let key_str = String::from_utf8_lossy(&key);
if let Some(name) = key_str.strip_prefix(&prefix) {
collections.push(name.to_string());
}
}
collections
}
pub fn get_columnar(&self, name: &str) -> DbResult<ColumnarCollectionMeta> {
let coll = ColumnarCollection::load(name.to_string(), &self.name, self.db.clone())?;
coll.metadata()
}
pub fn delete_columnar(&self, name: &str) -> DbResult<()> {
let coll = ColumnarCollection::load(name.to_string(), &self.name, self.db.clone())?;
coll.drop()
}
pub fn insert_columnar(&self, name: &str, rows: Vec<Value>) -> DbResult<usize> {
let coll = ColumnarCollection::load(name.to_string(), &self.name, self.db.clone())?;
let ids = coll.insert_rows(rows)?;
Ok(ids.len())
}
pub fn aggregate_columnar(
&self,
name: &str,
aggregations: Vec<Value>,
group_by: Option<Vec<String>>,
filter: Option<String>,
) -> DbResult<Vec<Value>> {
let coll = ColumnarCollection::load(name.to_string(), &self.name, self.db.clone())?;
if filter.is_some() {
return Err(DbError::OperationNotSupported(
"Filtering in aggregation not yet supported via driver".to_string(),
));
}
if let Some(groups) = group_by {
let group_cols: Vec<GroupByColumn> =
groups.into_iter().map(GroupByColumn::Simple).collect();
if let Some(first_agg) = aggregations.first() {
if let Some(obj) = first_agg.as_object() {
if let (Some(col), Some(op_str)) = (
obj.get("column").and_then(|v| v.as_str()),
obj.get("op").and_then(|v| v.as_str()),
) {
if let Some(op) = AggregateOp::from_str(op_str) {
return coll.group_by(&group_cols, col, op);
}
}
}
}
return Err(DbError::OperationNotSupported(
"Complex aggregation not supported".to_string(),
));
}
let mut result = serde_json::Map::new();
for agg in aggregations {
if let Some(obj) = agg.as_object() {
if let (Some(col), Some(op_str)) = (
obj.get("column").and_then(|v| v.as_str()),
obj.get("op").and_then(|v| v.as_str()),
) {
if let Some(op) = AggregateOp::from_str(op_str) {
let val = coll.aggregate(col, op)?;
result.insert(format!("{}_{}", col, op_str.to_lowercase()), val);
}
}
}
}
Ok(vec![Value::Object(result)])
}
pub fn query_columnar(
&self,
name: &str,
columns: Option<Vec<String>>,
filter: Option<String>,
_order_by: Option<String>,
limit: Option<usize>,
) -> DbResult<Vec<Value>> {
let coll = ColumnarCollection::load(name.to_string(), &self.name, self.db.clone())?;
let cols_to_read = if let Some(cols) = columns {
cols
} else {
let meta = coll.metadata()?;
meta.columns.into_iter().map(|c| c.name).collect()
};
let cols_refs: Vec<&str> = cols_to_read.iter().map(|s| s.as_str()).collect();
if filter.is_some() {
return Err(DbError::OperationNotSupported(
"Filtering in query not yet supported via driver".to_string(),
));
}
let mut results = coll.read_columns(&cols_refs, None)?;
if let Some(l) = limit {
if l > 0 {
results.truncate(l);
}
}
Ok(results)
}
pub fn create_columnar_index(&self, collection: &str, column: &str) -> DbResult<()> {
let coll = ColumnarCollection::load(collection.to_string(), &self.name, self.db.clone())?;
coll.create_index(column, ColumnarIndexType::Sorted) }
pub fn list_columnar_indexes(&self, collection: &str) -> DbResult<Vec<ColumnarIndexMeta>> {
let coll = ColumnarCollection::load(collection.to_string(), &self.name, self.db.clone())?;
coll.list_indexes()
}
pub fn delete_columnar_index(&self, collection: &str, column: &str) -> DbResult<()> {
let coll = ColumnarCollection::load(collection.to_string(), &self.name, self.db.clone())?;
coll.drop_index(column)
}
fn columnar_cf_name(&self, collection_name: &str) -> String {
format!("{}:_columnar_{}", self.name, collection_name)
}
pub fn is_columnar_collection(&self, collection_name: &str) -> bool {
let cf_name = self.columnar_cf_name(collection_name);
self.db.cf_handle(&cf_name).is_some() && !self.pending_cf_drops.contains(&cf_name)
}
pub fn list_columnar_collections(&self) -> Vec<String> {
vec![]
}
pub fn db_name(&self) -> &str {
&self.name
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
fn create_test_db() -> (Arc<DB>, TempDir) {
let temp_dir = TempDir::new().unwrap();
let db = DB::open_default(temp_dir.path()).unwrap();
(Arc::new(db), temp_dir)
}
#[test]
fn test_create_collection() {
let (db, _dir) = create_test_db();
let database = Database::new("testdb".to_string(), db, PendingCfDrops::new());
assert!(database
.create_collection("users".to_string(), None)
.is_ok());
assert!(database.list_collections().contains(&"users".to_string()));
}
#[test]
fn test_create_duplicate_collection() {
let (db, _dir) = create_test_db();
let database = Database::new("testdb".to_string(), db, PendingCfDrops::new());
database
.create_collection("users".to_string(), None)
.unwrap();
assert!(database
.create_collection("users".to_string(), None)
.is_err());
}
#[test]
fn test_delete_collection() {
let (db, _dir) = create_test_db();
let database = Database::new("testdb".to_string(), db, PendingCfDrops::new());
database
.create_collection("users".to_string(), None)
.unwrap();
assert!(database.delete_collection("users").is_ok());
assert!(!database.list_collections().contains(&"users".to_string()));
}
#[test]
fn test_list_collections() {
let (db, _dir) = create_test_db();
let database = Database::new("testdb".to_string(), db, PendingCfDrops::new());
database
.create_collection("users".to_string(), None)
.unwrap();
database
.create_collection("products".to_string(), None)
.unwrap();
let collections = database.list_collections();
assert_eq!(collections.len(), 2);
assert!(collections.contains(&"users".to_string()));
assert!(collections.contains(&"products".to_string()));
}
}