use std::collections::HashMap;
use crate::error::{DbError, DbResult};
use crate::sharding::ShardCoordinator;
use crate::storage::StorageEngine;
use crate::sync::log::SyncLog;
mod aggregation;
pub mod builtins;
mod catalog;
mod data_source;
mod evaluate;
mod execution;
mod explain;
mod expression;
mod helpers;
mod index_opt;
mod materialized_views;
pub mod phonetic;
pub mod types;
pub mod utils;
mod window;
pub use helpers::{
compare_key_rows, compare_values, evaluate_binary_op, evaluate_unary_op, get_field_ref,
get_field_value, hash_value, to_bool, values_equal, ValueSet,
};
pub use types::*;
pub use utils::*;
pub use window::{contains_window_functions, extract_window_functions, generate_window_key};
pub const DEFAULT_MAX_INTERMEDIATE_ROWS: usize = 5_000_000;
fn default_max_intermediate_rows() -> usize {
static LIMIT: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
*LIMIT.get_or_init(|| {
std::env::var("SOLIDB_MAX_INTERMEDIATE_ROWS")
.ok()
.and_then(|v| v.parse().ok())
.filter(|n| *n > 0)
.unwrap_or(DEFAULT_MAX_INTERMEDIATE_ROWS)
})
}
pub struct QueryExecutor<'a> {
pub(super) storage: &'a StorageEngine,
pub(super) bind_vars: BindVars,
pub(super) database: Option<String>,
pub(super) replication: Option<&'a SyncLog>,
pub(super) shard_coordinator: Option<std::sync::Arc<ShardCoordinator>>,
pub(super) principal: Option<QueryPrincipal>,
pub(super) deadline: Option<std::time::Instant>,
pub(super) max_intermediate_rows: usize,
}
impl<'a> QueryExecutor<'a> {
pub fn new(storage: &'a StorageEngine) -> Self {
Self {
storage,
bind_vars: HashMap::new(),
database: None,
replication: None,
shard_coordinator: None,
principal: None,
deadline: None,
max_intermediate_rows: default_max_intermediate_rows(),
}
}
pub fn with_bind_vars(storage: &'a StorageEngine, bind_vars: BindVars) -> Self {
Self {
storage,
bind_vars,
database: None,
replication: None,
shard_coordinator: None,
principal: None,
deadline: None,
max_intermediate_rows: default_max_intermediate_rows(),
}
}
pub fn with_database(storage: &'a StorageEngine, database: String) -> Self {
Self {
storage,
bind_vars: HashMap::new(),
database: Some(database),
replication: None,
shard_coordinator: None,
principal: None,
deadline: None,
max_intermediate_rows: default_max_intermediate_rows(),
}
}
pub fn with_database_and_bind_vars(
storage: &'a StorageEngine,
database: String,
bind_vars: BindVars,
) -> Self {
Self {
storage,
bind_vars,
database: Some(database),
replication: None,
shard_coordinator: None,
principal: None,
deadline: None,
max_intermediate_rows: default_max_intermediate_rows(),
}
}
pub fn with_replication(mut self, replication: &'a SyncLog) -> Self {
self.replication = Some(replication);
self
}
pub fn with_shard_coordinator(mut self, coordinator: std::sync::Arc<ShardCoordinator>) -> Self {
self.shard_coordinator = Some(coordinator);
self
}
pub fn with_principal(mut self, principal: QueryPrincipal) -> Self {
self.principal = Some(principal);
self
}
pub fn with_timeout(mut self, timeout: std::time::Duration) -> Self {
self.deadline = Some(std::time::Instant::now() + timeout);
self
}
pub fn with_max_intermediate_rows(mut self, rows: usize) -> Self {
self.max_intermediate_rows = rows.max(1);
self
}
pub fn max_intermediate_rows(&self) -> usize {
self.max_intermediate_rows
}
pub fn scan_cap(&self) -> Option<usize> {
Some(self.max_intermediate_rows.saturating_add(1))
}
pub fn scan_bounded(
&self,
collection: &crate::storage::collection::Collection,
) -> DbResult<Vec<serde_json::Value>> {
let docs = collection.scan_values(self.scan_cap());
self.check_budget(docs.len())?;
Ok(docs)
}
pub fn check_budget(&self, rows: usize) -> DbResult<()> {
if rows > self.max_intermediate_rows {
return Err(DbError::ExecutionError(format!(
"Query exceeded the intermediate row limit ({} > {}). Add a \
FILTER or LIMIT, or raise SOLIDB_MAX_INTERMEDIATE_ROWS.",
rows, self.max_intermediate_rows
)));
}
if let Some(deadline) = self.deadline {
if std::time::Instant::now() >= deadline {
return Err(DbError::ExecutionError(
"Query exceeded its time limit".to_string(),
));
}
}
Ok(())
}
}