BREAKING CHANGE(core): replace the TypeScript database engine with a Rust-backed embedded server and bridge

This commit is contained in:
2026-03-26 19:48:27 +00:00
parent 8ec2046908
commit e23a951dbe
106 changed files with 11567 additions and 10678 deletions

View File

@@ -0,0 +1,239 @@
use std::collections::HashSet;
use bson::{Bson, Document};
use tracing::debug;
use crate::engine::IndexEngine;
/// The execution plan for a query.
#[derive(Debug, Clone)]
pub enum QueryPlan {
/// Full collection scan - no suitable index found.
CollScan,
/// Index scan with exact/equality matches.
IxScan {
/// Name of the index used.
index_name: String,
/// Candidate document IDs from the index.
candidate_ids: HashSet<String>,
},
/// Index scan with range-based matches.
IxScanRange {
/// Name of the index used.
index_name: String,
/// Candidate document IDs from the range scan.
candidate_ids: HashSet<String>,
},
}
/// Plans query execution by selecting the best available index.
pub struct QueryPlanner;
impl QueryPlanner {
/// Analyze a filter and the available indexes to produce a query plan.
pub fn plan(filter: &Document, engine: &IndexEngine) -> QueryPlan {
if filter.is_empty() {
debug!("Empty filter -> CollScan");
return QueryPlan::CollScan;
}
let indexes = engine.list_indexes();
let mut best_plan: Option<QueryPlan> = None;
let mut best_score: f64 = 0.0;
for idx_info in &indexes {
let index_fields: Vec<String> = idx_info.key.keys().map(|k| k.to_string()).collect();
let mut matched = false;
let mut score: f64 = 0.0;
let mut is_range = false;
for field in &index_fields {
if let Some(condition) = filter.get(field) {
matched = true;
let field_score = Self::score_condition(condition);
score += field_score;
if Self::is_range_condition(condition) {
is_range = true;
}
}
}
if !matched {
continue;
}
// Unique index bonus
if idx_info.unique {
score += 0.5;
}
if score > best_score {
best_score = score;
// Try to get candidates from the engine
// We build a sub-filter with only the fields this index covers
let mut sub_filter = Document::new();
for field in &index_fields {
if let Some(val) = filter.get(field) {
sub_filter.insert(field.clone(), val.clone());
}
}
if let Some(candidates) = engine.find_candidate_ids(&sub_filter) {
if is_range {
best_plan = Some(QueryPlan::IxScanRange {
index_name: idx_info.name.clone(),
candidate_ids: candidates,
});
} else {
best_plan = Some(QueryPlan::IxScan {
index_name: idx_info.name.clone(),
candidate_ids: candidates,
});
}
}
}
}
match best_plan {
Some(plan) => {
debug!(score = best_score, "Selected index plan");
plan
}
None => {
debug!("No suitable index found -> CollScan");
QueryPlan::CollScan
}
}
}
/// Score a filter condition for index selectivity.
/// Higher scores indicate more selective (better) index usage.
fn score_condition(condition: &Bson) -> f64 {
match condition {
Bson::Document(doc) if Self::has_operators(doc) => {
let mut score: f64 = 0.0;
for (op, _) in doc {
score += match op.as_str() {
"$eq" => 2.0,
"$in" => 1.5,
"$gt" | "$gte" | "$lt" | "$lte" => 1.0,
_ => 0.0,
};
}
score
}
// Direct equality
_ => 2.0,
}
}
/// Check if a condition involves range operators.
fn is_range_condition(condition: &Bson) -> bool {
match condition {
Bson::Document(doc) => {
doc.keys().any(|k| matches!(k.as_str(), "$gt" | "$gte" | "$lt" | "$lte"))
}
_ => false,
}
}
fn has_operators(doc: &Document) -> bool {
doc.keys().any(|k| k.starts_with('$'))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::engine::IndexOptions;
use bson::oid::ObjectId;
#[test]
fn test_empty_filter_collscan() {
let engine = IndexEngine::new();
let plan = QueryPlanner::plan(&bson::doc! {}, &engine);
assert!(matches!(plan, QueryPlan::CollScan));
}
#[test]
fn test_id_equality_ixscan() {
let mut engine = IndexEngine::new();
let oid = ObjectId::new();
let doc = bson::doc! { "_id": oid.clone(), "name": "Alice" };
engine.on_insert(&doc).unwrap();
let filter = bson::doc! { "_id": oid };
let plan = QueryPlanner::plan(&filter, &engine);
assert!(matches!(plan, QueryPlan::IxScan { .. }));
}
#[test]
fn test_indexed_field_ixscan() {
let mut engine = IndexEngine::new();
engine.create_index(
bson::doc! { "status": 1 },
IndexOptions::default(),
).unwrap();
let doc = bson::doc! { "_id": ObjectId::new(), "status": "active" };
engine.on_insert(&doc).unwrap();
let filter = bson::doc! { "status": "active" };
let plan = QueryPlanner::plan(&filter, &engine);
assert!(matches!(plan, QueryPlan::IxScan { .. }));
}
#[test]
fn test_unindexed_field_collscan() {
let engine = IndexEngine::new();
let filter = bson::doc! { "unindexed_field": "value" };
let plan = QueryPlanner::plan(&filter, &engine);
assert!(matches!(plan, QueryPlan::CollScan));
}
#[test]
fn test_range_query_ixscan_range() {
let mut engine = IndexEngine::new();
engine.create_index(
bson::doc! { "age": 1 },
IndexOptions::default(),
).unwrap();
let doc = bson::doc! { "_id": ObjectId::new(), "age": 30 };
engine.on_insert(&doc).unwrap();
let filter = bson::doc! { "age": { "$gte": 25, "$lt": 35 } };
let plan = QueryPlanner::plan(&filter, &engine);
assert!(matches!(plan, QueryPlan::IxScanRange { .. }));
}
#[test]
fn test_unique_index_preferred() {
let mut engine = IndexEngine::new();
engine.create_index(
bson::doc! { "email": 1 },
IndexOptions { unique: true, ..Default::default() },
).unwrap();
engine.create_index(
bson::doc! { "email": 1, "name": 1 },
IndexOptions { name: Some("email_name".to_string()), ..Default::default() },
).unwrap();
let doc = bson::doc! { "_id": ObjectId::new(), "email": "a@b.com", "name": "Alice" };
engine.on_insert(&doc).unwrap();
let filter = bson::doc! { "email": "a@b.com" };
let plan = QueryPlanner::plan(&filter, &engine);
// The unique index on email should be preferred (higher score)
match plan {
QueryPlan::IxScan { index_name, .. } => {
assert_eq!(index_name, "email_1");
}
_ => panic!("Expected IxScan"),
}
}
}