use std::hash::{Hash, Hasher};
use std::num::NonZeroUsize;
use std::sync::Arc;
use lru::LruCache;
use parking_lot::Mutex;
use radixdb_core::ParamVec;
use radixdb_core::{CompactArc, StringMap};
use rustc_hash::{FxHashMap, FxHasher};
use super::compiler::{CompileContext, ExprCompiler};
use super::execution_context::ExecuteContext;
use super::program::Program;
use super::vm::ExprVM;
use radixdb_core::{Error, Result, Row, Value};
use radixdb_functions::{global_registry, FunctionRegistry};
use radixdb_sql::ast::Expression;
use crate::context::{ExecutionContext, StoredFunctionInvoker};
const PROGRAM_CACHE_SIZE: usize = 256;
#[derive(Clone)]
struct ProgramCacheEntry {
expression: Expression,
columns: Vec<String>,
registry_generation: u64,
program: SharedProgram,
}
#[derive(Clone)]
struct LocalProgramCacheEntry {
expression: Expression,
registry_generation: u64,
program: SharedProgram,
}
static PROGRAM_CACHE: Mutex<Option<LruCache<u64, ProgramCacheEntry>>> = Mutex::new(None);
pub fn clear_program_cache() {
let mut guard = PROGRAM_CACHE.lock();
*guard = None;
}
fn checked_alias_map(aliases: &[(String, usize)]) -> Result<StringMap<u16>> {
aliases
.iter()
.map(|(name, index)| {
let index = u16::try_from(*index).map_err(|_| {
Error::invalid_argument(format!(
"expression alias '{}' index {} exceeds the u16 bytecode limit",
name, index
))
})?;
Ok((name.to_lowercase(), index))
})
.collect()
}
fn compute_cache_key(expr: &Expression, columns: &[String], registry_generation: u64) -> u64 {
let mut hasher = FxHasher::default();
hash_expression(expr, &mut hasher);
columns.hash(&mut hasher);
registry_generation.hash(&mut hasher);
hasher.finish()
}
#[inline]
pub fn compute_expression_hash(expr: &Expression) -> u64 {
let mut hasher = FxHasher::default();
hash_expression(expr, &mut hasher);
hasher.finish()
}
fn hash_expression(expr: &Expression, hasher: &mut FxHasher) {
std::mem::discriminant(expr).hash(hasher);
match expr {
Expression::Identifier(id) => {
id.value_lower.hash(hasher);
}
Expression::QualifiedIdentifier(qid) => {
qid.qualifier.value_lower.hash(hasher);
qid.name.value_lower.hash(hasher);
}
Expression::IntegerLiteral(lit) => {
lit.value.hash(hasher);
}
Expression::FloatLiteral(lit) => {
lit.value.to_bits().hash(hasher);
}
Expression::StringLiteral(lit) => {
lit.value.hash(hasher);
lit.type_hint.hash(hasher);
}
Expression::BooleanLiteral(lit) => {
lit.value.hash(hasher);
}
Expression::NullLiteral(_) => {
}
Expression::BoundValue(value) => {
value.hash(hasher);
}
Expression::IntervalLiteral(lit) => {
lit.value.hash(hasher);
lit.unit.hash(hasher);
}
Expression::Parameter(param) => {
param.index.hash(hasher);
param.name.hash(hasher);
}
Expression::Prefix(prefix) => {
std::mem::discriminant(&prefix.op_type).hash(hasher);
hash_expression(&prefix.right, hasher);
}
Expression::Infix(infix) => {
std::mem::discriminant(&infix.op_type).hash(hasher);
hash_expression(&infix.left, hasher);
hash_expression(&infix.right, hasher);
}
Expression::List(list) => {
list.elements.len().hash(hasher);
for val in &list.elements {
hash_expression(val, hasher);
}
}
Expression::Distinct(dist) => {
hash_expression(&dist.expr, hasher);
}
Expression::Exists(exists) => {
(exists.subquery.as_ref() as *const _ as usize).hash(hasher);
}
Expression::AllAny(aa) => {
aa.operator.hash(hasher);
std::mem::discriminant(&aa.all_any_type).hash(hasher);
hash_expression(&aa.left, hasher);
(aa.subquery.as_ref() as *const _ as usize).hash(hasher);
}
Expression::In(in_expr) => {
in_expr.not.hash(hasher);
hash_expression(&in_expr.left, hasher);
hash_expression(&in_expr.right, hasher);
}
Expression::InHashSet(in_hash) => {
in_hash.not.hash(hasher);
hash_expression(&in_hash.column, hasher);
let mut values: Vec<&Value> = in_hash.values.iter().collect();
values.sort_unstable();
values.hash(hasher);
}
Expression::Between(between) => {
between.not.hash(hasher);
hash_expression(&between.expr, hasher);
hash_expression(&between.lower, hasher);
hash_expression(&between.upper, hasher);
}
Expression::Like(like) => {
like.operator.hash(hasher);
hash_expression(&like.left, hasher);
hash_expression(&like.pattern, hasher);
if let Some(ref escape) = like.escape {
true.hash(hasher);
hash_expression(escape, hasher);
} else {
false.hash(hasher);
}
}
Expression::ScalarSubquery(sq) => {
(sq.subquery.as_ref() as *const _ as usize).hash(hasher);
}
Expression::ExpressionList(list) => {
list.expressions.len().hash(hasher);
for e in &list.expressions {
hash_expression(e, hasher);
}
}
Expression::Case(case) => {
if let Some(ref val) = case.value {
true.hash(hasher);
hash_expression(val, hasher);
} else {
false.hash(hasher);
}
case.when_clauses.len().hash(hasher);
for when_clause in &case.when_clauses {
hash_expression(&when_clause.condition, hasher);
hash_expression(&when_clause.then_result, hasher);
}
if let Some(ref else_val) = case.else_value {
true.hash(hasher);
hash_expression(else_val, hasher);
} else {
false.hash(hasher);
}
}
Expression::Cast(cast) => {
hash_expression(&cast.expr, hasher);
cast.type_name.hash(hasher);
}
Expression::FunctionCall(func) => {
func.function.hash(hasher);
func.is_distinct.hash(hasher);
func.arguments.len().hash(hasher);
for arg in &func.arguments {
hash_expression(arg, hasher);
}
if let Some(ref filter) = func.filter {
true.hash(hasher);
hash_expression(filter, hasher);
} else {
false.hash(hasher);
}
}
Expression::Aliased(aliased) => {
aliased.alias.value_lower.hash(hasher);
hash_expression(&aliased.expression, hasher);
}
Expression::Window(window) => {
window.function.function.hash(hasher);
window.function.is_distinct.hash(hasher);
window.function.arguments.len().hash(hasher);
for arg in &window.function.arguments {
hash_expression(arg, hasher);
}
window.partition_by.len().hash(hasher);
for e in &window.partition_by {
hash_expression(e, hasher);
}
window.order_by.len().hash(hasher);
for order in &window.order_by {
hash_expression(&order.expression, hasher);
order.ascending.hash(hasher);
order.nulls_first.hash(hasher);
}
}
Expression::TableSource(ts) => {
ts.name.value_lower.hash(hasher);
if let Some(ref alias) = ts.alias {
true.hash(hasher);
alias.value_lower.hash(hasher);
} else {
false.hash(hasher);
}
}
Expression::JoinSource(js) => {
(js.as_ref() as *const _ as usize).hash(hasher);
}
Expression::SubquerySource(sq) => {
if let Some(ref alias) = sq.alias {
true.hash(hasher);
alias.value_lower.hash(hasher);
} else {
false.hash(hasher);
}
(sq.subquery.as_ref() as *const _ as usize).hash(hasher);
}
Expression::ValuesSource(vs) => {
if let Some(ref alias) = vs.alias {
true.hash(hasher);
alias.value_lower.hash(hasher);
} else {
false.hash(hasher);
}
vs.rows.len().hash(hasher);
}
Expression::CteReference(cte) => {
cte.name.value_lower.hash(hasher);
}
Expression::FunctionTableSource(fts) => {
fts.function.value_lower.hash(hasher);
for arg in &fts.arguments {
hash_expression(arg, hasher);
}
}
Expression::Star(_) => {
}
Expression::QualifiedStar(qs) => {
qs.qualifier.hash(hasher);
}
Expression::Default(_) => {
}
}
}
fn compile_expression_cached(expr: &Expression, columns: &[String]) -> Result<SharedProgram> {
let registry = global_registry();
let registry_generation = registry.generation();
let cache_key = compute_cache_key(expr, columns, registry_generation);
{
let mut guard = PROGRAM_CACHE.lock();
let cache = guard.get_or_insert_with(|| {
LruCache::new(NonZeroUsize::new(PROGRAM_CACHE_SIZE).unwrap())
});
if let Some(entry) = cache.get(&cache_key) {
if entry.registry_generation == registry_generation
&& entry.expression == *expr
&& entry.columns == columns
{
return Ok(entry.program.clone());
}
}
}
let ctx = CompileContext::new(columns, registry);
let compiler = ExprCompiler::new(&ctx);
let program: SharedProgram = compiler
.compile(expr)
.map(CompactArc::new)
.map_err(|e| Error::internal(format!("Compile error: {}", e)))?;
{
let mut guard = PROGRAM_CACHE.lock();
let cache = guard
.get_or_insert_with(|| LruCache::new(NonZeroUsize::new(PROGRAM_CACHE_SIZE).unwrap()));
cache.put(
cache_key,
ProgramCacheEntry {
expression: expr.clone(),
columns: columns.to_vec(),
registry_generation,
program: program.clone(),
},
);
}
Ok(program)
}
pub fn compile_expression(expr: &Expression, columns: &[String]) -> Result<SharedProgram> {
compile_expression_cached(expr, columns)
}
pub fn try_eval_constant_expr(expr: &Expression) -> Option<Value> {
use std::cell::RefCell;
if contains_context_dependent_function(expr) {
return None;
}
thread_local! {
static EVAL_VM: RefCell<ExprVM> = RefCell::new(ExprVM::new());
static EVAL_ROW: Row = Row::new();
}
let empty_cols: &[String] = &[];
let ctx = CompileContext::with_global_registry(empty_cols);
let compiler = ExprCompiler::new(&ctx);
let program = compiler.compile(expr).ok()?;
EVAL_ROW.with(|empty_row| {
let exec_ctx = ExecuteContext::new(empty_row);
EVAL_VM.with(|vm_cell| {
let mut vm = vm_cell.borrow_mut();
vm.execute(&program, &exec_ctx).ok()
})
})
}
fn contains_context_dependent_function(expr: &Expression) -> bool {
match expr {
Expression::FunctionCall(func) => {
func.function.eq_ignore_ascii_case("CURRENT_TRANSACTION_ID")
|| func
.arguments
.iter()
.any(contains_context_dependent_function)
}
Expression::Infix(infix) => {
contains_context_dependent_function(&infix.left)
|| contains_context_dependent_function(&infix.right)
}
Expression::Prefix(prefix) => contains_context_dependent_function(&prefix.right),
Expression::Cast(cast) => contains_context_dependent_function(&cast.expr),
Expression::Case(case) => {
case.value
.as_ref()
.is_some_and(|v| contains_context_dependent_function(v))
|| case.when_clauses.iter().any(|w| {
contains_context_dependent_function(&w.condition)
|| contains_context_dependent_function(&w.then_result)
})
|| case
.else_value
.as_ref()
.is_some_and(|v| contains_context_dependent_function(v))
}
Expression::Between(between) => {
contains_context_dependent_function(&between.expr)
|| contains_context_dependent_function(&between.lower)
|| contains_context_dependent_function(&between.upper)
}
Expression::In(in_expr) => {
contains_context_dependent_function(&in_expr.left)
|| contains_context_dependent_function(&in_expr.right)
}
Expression::Like(like) => {
contains_context_dependent_function(&like.left)
|| contains_context_dependent_function(&like.pattern)
|| like
.escape
.as_ref()
.is_some_and(|e| contains_context_dependent_function(e))
}
Expression::List(list) => list
.elements
.iter()
.any(contains_context_dependent_function),
Expression::ExpressionList(list) => list
.expressions
.iter()
.any(contains_context_dependent_function),
Expression::Aliased(aliased) => contains_context_dependent_function(&aliased.expression),
Expression::Distinct(distinct) => contains_context_dependent_function(&distinct.expr),
Expression::AllAny(all_any) => contains_context_dependent_function(&all_any.left),
Expression::InHashSet(in_hash) => contains_context_dependent_function(&in_hash.column),
_ => false,
}
}
pub fn compile_expression_with_context(
expr: &Expression,
columns: &[String],
outer_columns: Option<&[String]>,
function_registry: &FunctionRegistry,
) -> Result<SharedProgram> {
let mut ctx = CompileContext::new(columns, function_registry);
if let Some(outer_cols) = outer_columns {
ctx = ctx.with_outer_columns(outer_cols);
}
let compiler = ExprCompiler::new(&ctx);
compiler
.compile(expr)
.map(CompactArc::new)
.map_err(|e| Error::internal(format!("Compile error: {}", e)))
}
#[derive(Clone)]
pub struct RowFilter {
program: SharedProgram,
params: CompactArc<ParamVec>,
named_params: Arc<FxHashMap<String, Value>>,
transaction_id: Option<u64>,
stored_function_invoker: Option<Arc<dyn StoredFunctionInvoker>>,
outer_row: Option<Arc<FxHashMap<CompactArc<str>, Value>>>,
}
impl RowFilter {
pub fn new(expr: &Expression, columns: &[String]) -> Result<Self> {
let program = compile_expression(expr, columns)?;
Ok(Self {
program,
params: CompactArc::new(ParamVec::new()),
named_params: Arc::new(FxHashMap::default()),
transaction_id: None,
stored_function_invoker: None,
outer_row: None,
})
}
pub fn with_aliases(
expr: &Expression,
columns: &[String],
aliases: &[(String, usize)],
) -> Result<Self> {
let alias_map = checked_alias_map(aliases)?;
let ctx = CompileContext::with_global_registry(columns).with_expression_aliases(alias_map);
let compiler = ExprCompiler::new(&ctx);
let program = compiler
.compile(expr)
.map(CompactArc::new)
.map_err(|e| Error::internal(format!("Compile error: {}", e)))?;
Ok(Self {
program,
params: CompactArc::new(ParamVec::new()),
named_params: Arc::new(FxHashMap::default()),
transaction_id: None,
stored_function_invoker: None,
outer_row: None,
})
}
pub fn with_aliases_and_context(
expr: &Expression,
columns: &[String],
aliases: &[(String, usize)],
execution: &ExecutionContext,
) -> Result<Self> {
let alias_map = checked_alias_map(aliases)?;
let mut context =
CompileContext::with_global_registry(columns).with_expression_aliases(alias_map);
if let Some(outer) = execution.outer_row() {
let outer_columns: Vec<String> = outer.keys().map(ToString::to_string).collect();
context = context.with_outer_columns(&outer_columns);
}
let compiler = ExprCompiler::new(&context);
let program = compiler
.compile(expr)
.map(CompactArc::new)
.map_err(|error| Error::internal(format!("Compile error: {error}")))?;
Ok(Self {
program,
params: CompactArc::clone(execution.params_arc()),
named_params: Arc::clone(execution.named_params_arc()),
transaction_id: execution.transaction_id(),
stored_function_invoker: execution.stored_function_invoker().cloned(),
outer_row: execution.outer_row().cloned().map(Arc::new),
})
}
pub fn with_params(mut self, params: ParamVec) -> Self {
self.params = CompactArc::new(params);
self
}
pub fn with_named_params(mut self, named_params: FxHashMap<String, Value>) -> Self {
self.named_params = Arc::new(named_params);
self
}
pub fn with_context(mut self, ctx: &ExecutionContext) -> Self {
self.params = CompactArc::clone(ctx.params_arc());
self.named_params = Arc::clone(ctx.named_params_arc());
self.transaction_id = ctx.transaction_id();
self.stored_function_invoker = ctx.stored_function_invoker().cloned();
self.outer_row = ctx.outer_row().cloned().map(Arc::new);
self
}
pub fn from_program(program: SharedProgram) -> Self {
Self {
program,
params: CompactArc::new(ParamVec::new()),
named_params: Arc::new(FxHashMap::default()),
transaction_id: None,
stored_function_invoker: None,
outer_row: None,
}
}
#[inline]
pub fn matches(&self, row: &Row) -> Result<bool> {
thread_local! {
static VM: std::cell::RefCell<ExprVM> = std::cell::RefCell::new(ExprVM::new());
}
VM.with(|vm| {
let mut ctx = ExecuteContext::new(row);
if !self.params.is_empty() {
ctx = ctx.with_params(&self.params);
}
if !self.named_params.is_empty() {
ctx = ctx.with_named_params(&self.named_params);
}
if let Some(outer_row) = self.outer_row.as_deref() {
ctx = ctx.with_outer_row(outer_row);
}
ctx = ctx
.with_transaction_id(self.transaction_id)
.with_stored_function_invoker(self.stored_function_invoker.as_ref());
if let Ok(mut borrowed_vm) = vm.try_borrow_mut() {
borrowed_vm.execute_bool(&self.program, &ctx)
} else {
let mut temp_vm = ExprVM::new();
temp_vm.execute_bool(&self.program, &ctx)
}
})
}
#[inline]
pub fn matches_checked(&self, row: &Row) -> Result<bool> {
thread_local! {
static VM: std::cell::RefCell<ExprVM> = std::cell::RefCell::new(ExprVM::new());
}
VM.with(|vm| {
let mut ctx = ExecuteContext::new(row);
if !self.params.is_empty() {
ctx = ctx.with_params(&self.params);
}
if !self.named_params.is_empty() {
ctx = ctx.with_named_params(&self.named_params);
}
if let Some(outer_row) = self.outer_row.as_deref() {
ctx = ctx.with_outer_row(outer_row);
}
ctx = ctx
.with_transaction_id(self.transaction_id)
.with_stored_function_invoker(self.stored_function_invoker.as_ref());
if let Ok(mut borrowed_vm) = vm.try_borrow_mut() {
borrowed_vm.execute_bool_checked(&self.program, &ctx)
} else {
let mut temp_vm = ExprVM::new();
temp_vm.execute_bool_checked(&self.program, &ctx)
}
})
}
#[inline]
pub fn matches_deferred_checked(&self, row: &radixdb_storage::DeferredRow) -> Result<bool> {
thread_local! {
static VM: std::cell::RefCell<ExprVM> = std::cell::RefCell::new(ExprVM::new());
}
VM.with(|vm| {
let mut ctx = ExecuteContext::for_deferred(row);
if !self.params.is_empty() {
ctx = ctx.with_params(&self.params);
}
if !self.named_params.is_empty() {
ctx = ctx.with_named_params(&self.named_params);
}
if let Some(outer_row) = self.outer_row.as_deref() {
ctx = ctx.with_outer_row(outer_row);
}
ctx = ctx
.with_transaction_id(self.transaction_id)
.with_stored_function_invoker(self.stored_function_invoker.as_ref());
if let Ok(mut borrowed_vm) = vm.try_borrow_mut() {
borrowed_vm.execute_bool_checked(&self.program, &ctx)
} else {
let mut temp_vm = ExprVM::new();
temp_vm.execute_bool_checked(&self.program, &ctx)
}
})
}
#[inline]
pub fn matches_row_ref_checked(&self, row: &crate::operator::RowRef) -> Result<bool> {
thread_local! {
static VM: std::cell::RefCell<ExprVM> = std::cell::RefCell::new(ExprVM::new());
}
VM.with(|vm| {
let mut ctx = ExecuteContext::for_row_ref(row);
if !self.params.is_empty() {
ctx = ctx.with_params(&self.params);
}
if !self.named_params.is_empty() {
ctx = ctx.with_named_params(&self.named_params);
}
if let Some(outer_row) = self.outer_row.as_deref() {
ctx = ctx.with_outer_row(outer_row);
}
ctx = ctx
.with_transaction_id(self.transaction_id)
.with_stored_function_invoker(self.stored_function_invoker.as_ref());
if let Ok(mut borrowed_vm) = vm.try_borrow_mut() {
borrowed_vm.execute_bool_checked(&self.program, &ctx)
} else {
ExprVM::new().execute_bool_checked(&self.program, &ctx)
}
})
}
pub fn retain_checked(&self, rows: &mut radixdb_core::RowVec) -> Result<()> {
let mut error: Option<radixdb_core::Error> = None;
rows.retain(|(_, row)| {
if error.is_some() {
return false;
}
match self.matches_checked(row) {
Ok(b) => b,
Err(e) => {
error = Some(e);
false
}
}
});
match error {
Some(e) => Err(e),
None => Ok(()),
}
}
#[inline]
pub fn evaluate(&self, row: &Row) -> Result<Value> {
thread_local! {
static VM: std::cell::RefCell<ExprVM> = std::cell::RefCell::new(ExprVM::new());
}
VM.with(|vm| {
let mut ctx = ExecuteContext::new(row);
if !self.params.is_empty() {
ctx = ctx.with_params(&self.params);
}
if !self.named_params.is_empty() {
ctx = ctx.with_named_params(&self.named_params);
}
ctx = ctx
.with_transaction_id(self.transaction_id)
.with_stored_function_invoker(self.stored_function_invoker.as_ref());
if let Ok(mut borrowed_vm) = vm.try_borrow_mut() {
borrowed_vm.execute_cow(&self.program, &ctx)
} else {
let mut temp_vm = ExprVM::new();
temp_vm.execute_cow(&self.program, &ctx)
}
})
}
pub fn program(&self) -> &SharedProgram {
&self.program
}
}
const _: () = {
const fn assert_send_sync<T: Send + Sync>() {}
let _ = assert_send_sync::<RowFilter>;
};
#[derive(Clone)]
pub struct JoinFilter {
program: SharedProgram,
params: CompactArc<ParamVec>,
named_params: Arc<FxHashMap<String, Value>>,
transaction_id: Option<u64>,
stored_function_invoker: Option<Arc<dyn StoredFunctionInvoker>>,
}
impl JoinFilter {
pub fn new(
expr: &Expression,
left_columns: &[String],
right_columns: &[String],
function_registry: &FunctionRegistry,
) -> Result<Self> {
let ctx =
CompileContext::new(left_columns, function_registry).with_second_row(right_columns);
let compiler = ExprCompiler::new(&ctx);
let program = compiler
.compile(expr)
.map_err(|e| Error::internal(format!("Compile error: {}", e)))?;
Ok(Self {
program: CompactArc::new(program),
params: CompactArc::new(ParamVec::new()),
named_params: Arc::new(FxHashMap::default()),
transaction_id: None,
stored_function_invoker: None,
})
}
#[inline]
pub fn with_context(mut self, ctx: &ExecutionContext) -> Self {
self.params = CompactArc::clone(ctx.params_arc());
self.named_params = Arc::clone(ctx.named_params_arc());
self.transaction_id = ctx.transaction_id();
self.stored_function_invoker = ctx.stored_function_invoker().cloned();
self
}
#[inline]
pub fn matches(&self, left_row: &Row, right_row: &Row) -> Result<bool> {
thread_local! {
static VM: std::cell::RefCell<ExprVM> = std::cell::RefCell::new(ExprVM::new());
}
VM.with(|vm| {
let mut ctx = ExecuteContext::for_join(left_row, right_row)
.with_transaction_id(self.transaction_id)
.with_stored_function_invoker(self.stored_function_invoker.as_ref());
if !self.params.is_empty() {
ctx = ctx.with_params(&self.params);
}
if !self.named_params.is_empty() {
ctx = ctx.with_named_params(&self.named_params);
}
if let Ok(mut borrowed_vm) = vm.try_borrow_mut() {
borrowed_vm.execute_bool(&self.program, &ctx)
} else {
let mut temp_vm = ExprVM::new();
temp_vm.execute_bool(&self.program, &ctx)
}
})
}
#[inline]
pub fn matches_checked(&self, left_row: &Row, right_row: &Row) -> Result<bool> {
thread_local! {
static VM: std::cell::RefCell<ExprVM> = std::cell::RefCell::new(ExprVM::new());
}
VM.with(|vm| {
let mut ctx = ExecuteContext::for_join(left_row, right_row)
.with_transaction_id(self.transaction_id)
.with_stored_function_invoker(self.stored_function_invoker.as_ref());
if !self.params.is_empty() {
ctx = ctx.with_params(&self.params);
}
if !self.named_params.is_empty() {
ctx = ctx.with_named_params(&self.named_params);
}
if let Ok(mut borrowed_vm) = vm.try_borrow_mut() {
borrowed_vm.execute_bool_checked(&self.program, &ctx)
} else {
ExprVM::new().execute_bool_checked(&self.program, &ctx)
}
})
}
#[inline]
pub fn matches_row_ref_checked(
&self,
left_row: &crate::operator::RowRef,
right_row: &Row,
) -> Result<bool> {
thread_local! {
static VM: std::cell::RefCell<ExprVM> = std::cell::RefCell::new(ExprVM::new());
}
VM.with(|vm| {
let mut ctx = ExecuteContext::for_join_ref(left_row, right_row)
.with_transaction_id(self.transaction_id)
.with_stored_function_invoker(self.stored_function_invoker.as_ref());
if !self.params.is_empty() {
ctx = ctx.with_params(&self.params);
}
if !self.named_params.is_empty() {
ctx = ctx.with_named_params(&self.named_params);
}
if let Ok(mut borrowed_vm) = vm.try_borrow_mut() {
borrowed_vm.execute_bool_checked(&self.program, &ctx)
} else {
ExprVM::new().execute_bool_checked(&self.program, &ctx)
}
})
}
#[inline]
pub fn matches_row_refs_checked(
&self,
left_row: &crate::operator::RowRef,
right_row: &crate::operator::RowRef,
) -> Result<bool> {
thread_local! {
static VM: std::cell::RefCell<ExprVM> = std::cell::RefCell::new(ExprVM::new());
}
VM.with(|vm| {
let mut ctx = ExecuteContext::for_join_refs(left_row, right_row)
.with_transaction_id(self.transaction_id)
.with_stored_function_invoker(self.stored_function_invoker.as_ref());
if !self.params.is_empty() {
ctx = ctx.with_params(&self.params);
}
if !self.named_params.is_empty() {
ctx = ctx.with_named_params(&self.named_params);
}
if let Ok(mut borrowed_vm) = vm.try_borrow_mut() {
borrowed_vm.execute_bool_checked(&self.program, &ctx)
} else {
ExprVM::new().execute_bool_checked(&self.program, &ctx)
}
})
}
pub fn program(&self) -> &SharedProgram {
&self.program
}
}
const _: () = {
const fn assert_send_sync<T: Send + Sync>() {}
let _ = assert_send_sync::<JoinFilter>;
};
pub struct ExpressionEval {
program: SharedProgram,
vm: ExprVM,
params: CompactArc<ParamVec>,
named_params: Arc<FxHashMap<String, Value>>,
outer_row: Option<FxHashMap<CompactArc<str>, Value>>,
transaction_id: Option<u64>,
stored_function_invoker: Option<Arc<dyn StoredFunctionInvoker>>,
}
impl ExpressionEval {
pub fn compile(expr: &Expression, columns: &[String]) -> Result<Self> {
let program = compile_expression(expr, columns)?;
Ok(Self {
program,
vm: ExprVM::new(),
params: CompactArc::new(ParamVec::new()),
named_params: Arc::new(FxHashMap::default()),
outer_row: None,
transaction_id: None,
stored_function_invoker: None,
})
}
pub fn compile_with_aliases(
expr: &Expression,
columns: &[String],
aliases: &[(String, usize)],
) -> Result<Self> {
let alias_map = checked_alias_map(aliases)?;
Self::compile_with_options(
expr,
columns,
None,
None,
Some(alias_map),
global_registry(),
)
}
pub fn compile_with_options(
expr: &Expression,
columns: &[String],
columns2: Option<&[String]>,
outer_columns: Option<&[String]>,
expression_aliases: Option<StringMap<u16>>,
function_registry: &FunctionRegistry,
) -> Result<Self> {
let mut ctx = CompileContext::new(columns, function_registry);
if let Some(cols2) = columns2 {
ctx = ctx.with_second_row(cols2);
}
if let Some(outer) = outer_columns {
ctx = ctx.with_outer_columns(outer);
}
if let Some(aliases) = expression_aliases {
ctx = ctx.with_expression_aliases(aliases);
}
let compiler = ExprCompiler::new(&ctx);
let program = compiler
.compile(expr)
.map(CompactArc::new)
.map_err(|e| Error::internal(format!("Compile error: {}", e)))?;
Ok(Self {
program,
vm: ExprVM::new(),
params: CompactArc::new(ParamVec::new()),
named_params: Arc::new(FxHashMap::default()),
outer_row: None,
transaction_id: None,
stored_function_invoker: None,
})
}
pub fn from_program(program: SharedProgram) -> Self {
Self {
program,
vm: ExprVM::new(),
params: CompactArc::new(ParamVec::new()),
named_params: Arc::new(FxHashMap::default()),
outer_row: None,
transaction_id: None,
stored_function_invoker: None,
}
}
pub fn with_params(mut self, params: ParamVec) -> Self {
self.params = CompactArc::new(params);
self
}
pub fn with_named_params(mut self, named_params: FxHashMap<String, Value>) -> Self {
self.named_params = Arc::new(named_params);
self
}
pub fn with_context(mut self, ctx: &ExecutionContext) -> Self {
self.params = CompactArc::clone(ctx.params_arc());
self.named_params = Arc::clone(ctx.named_params_arc());
if let Some(outer) = ctx.outer_row() {
let arc_map = outer.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
self.outer_row = Some(arc_map);
}
self.transaction_id = ctx.transaction_id();
self.stored_function_invoker = ctx.stored_function_invoker().cloned();
self
}
pub fn with_transaction_id(mut self, txn_id: Option<u64>) -> Self {
self.transaction_id = txn_id;
self
}
pub fn set_outer_row(&mut self, outer: &FxHashMap<CompactArc<str>, Value>) {
let map = outer.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
self.outer_row = Some(map);
}
pub fn clear_outer_row(&mut self) {
self.outer_row = None;
}
#[inline]
pub fn eval(&mut self, row: &Row) -> Result<Value> {
let mut ctx = ExecuteContext::new(row);
if !self.params.is_empty() {
ctx = ctx.with_params(&self.params);
}
if !self.named_params.is_empty() {
ctx = ctx.with_named_params(&self.named_params);
}
if let Some(ref outer) = self.outer_row {
ctx = ctx.with_outer_row(outer);
}
ctx = ctx
.with_transaction_id(self.transaction_id)
.with_stored_function_invoker(self.stored_function_invoker.as_ref());
self.vm.execute_cow(&self.program, &ctx)
}
#[inline]
pub fn eval_bool(&mut self, row: &Row) -> Result<bool> {
let mut ctx = ExecuteContext::new(row);
if !self.params.is_empty() {
ctx = ctx.with_params(&self.params);
}
if !self.named_params.is_empty() {
ctx = ctx.with_named_params(&self.named_params);
}
if let Some(ref outer) = self.outer_row {
ctx = ctx.with_outer_row(outer);
}
ctx = ctx
.with_transaction_id(self.transaction_id)
.with_stored_function_invoker(self.stored_function_invoker.as_ref());
self.vm.execute_bool(&self.program, &ctx)
}
#[inline]
pub fn eval_bool_checked(&mut self, row: &Row) -> Result<bool> {
let mut ctx = ExecuteContext::new(row);
if !self.params.is_empty() {
ctx = ctx.with_params(&self.params);
}
if !self.named_params.is_empty() {
ctx = ctx.with_named_params(&self.named_params);
}
if let Some(ref outer) = self.outer_row {
ctx = ctx.with_outer_row(outer);
}
ctx = ctx
.with_transaction_id(self.transaction_id)
.with_stored_function_invoker(self.stored_function_invoker.as_ref());
self.vm.execute_bool_checked(&self.program, &ctx)
}
#[inline]
pub fn eval_join(&mut self, left: &Row, right: &Row) -> Result<Value> {
let ctx = ExecuteContext::for_join(left, right)
.with_transaction_id(self.transaction_id)
.with_stored_function_invoker(self.stored_function_invoker.as_ref());
self.vm.execute_cow(&self.program, &ctx)
}
#[inline]
pub fn eval_join_bool(&mut self, left: &Row, right: &Row) -> Result<bool> {
let ctx = ExecuteContext::for_join(left, right)
.with_transaction_id(self.transaction_id)
.with_stored_function_invoker(self.stored_function_invoker.as_ref());
self.vm.execute_bool(&self.program, &ctx)
}
#[inline]
pub fn eval_slice(&mut self, row: &Row) -> Result<Value> {
let mut ctx = ExecuteContext::new(row);
if !self.params.is_empty() {
ctx = ctx.with_params(&self.params);
}
if !self.named_params.is_empty() {
ctx = ctx.with_named_params(&self.named_params);
}
if let Some(ref outer) = self.outer_row {
ctx = ctx.with_outer_row(outer);
}
ctx = ctx
.with_transaction_id(self.transaction_id)
.with_stored_function_invoker(self.stored_function_invoker.as_ref());
self.vm.execute_cow(&self.program, &ctx)
}
#[inline]
pub fn eval_slice_bool(&mut self, row: &Row) -> Result<bool> {
let mut ctx = ExecuteContext::new(row);
if !self.params.is_empty() {
ctx = ctx.with_params(&self.params);
}
if !self.named_params.is_empty() {
ctx = ctx.with_named_params(&self.named_params);
}
if let Some(ref outer) = self.outer_row {
ctx = ctx.with_outer_row(outer);
}
ctx = ctx
.with_transaction_id(self.transaction_id)
.with_stored_function_invoker(self.stored_function_invoker.as_ref());
self.vm.execute_bool(&self.program, &ctx)
}
pub fn program(&self) -> &SharedProgram {
&self.program
}
}
pub struct MultiExpressionEval {
programs: Vec<SharedProgram>,
vm: ExprVM,
params: CompactArc<ParamVec>,
named_params: Arc<FxHashMap<String, Value>>,
transaction_id: Option<u64>,
stored_function_invoker: Option<Arc<dyn StoredFunctionInvoker>>,
}
impl MultiExpressionEval {
pub fn compile(exprs: &[Expression], columns: &[String]) -> Result<Self> {
let ctx = CompileContext::with_global_registry(columns);
let compiler = ExprCompiler::new(&ctx);
let programs = exprs
.iter()
.map(|expr| {
compiler
.compile(expr)
.map(CompactArc::new)
.map_err(|e| Error::internal(format!("Compile error: {}", e)))
})
.collect::<Result<Vec<_>>>()?;
Ok(Self {
programs,
vm: ExprVM::new(),
params: CompactArc::new(ParamVec::new()),
named_params: Arc::new(FxHashMap::default()),
transaction_id: None,
stored_function_invoker: None,
})
}
pub fn compile_with_aliases(
exprs: &[Expression],
columns: &[String],
aliases: &[(String, usize)],
) -> Result<Self> {
let alias_map = checked_alias_map(aliases)?;
let ctx = CompileContext::with_global_registry(columns).with_expression_aliases(alias_map);
let compiler = ExprCompiler::new(&ctx);
let programs = exprs
.iter()
.map(|expr| {
compiler
.compile(expr)
.map(CompactArc::new)
.map_err(|e| Error::internal(format!("Compile error: {}", e)))
})
.collect::<Result<Vec<_>>>()?;
Ok(Self {
programs,
vm: ExprVM::new(),
params: CompactArc::new(ParamVec::new()),
named_params: Arc::new(FxHashMap::default()),
transaction_id: None,
stored_function_invoker: None,
})
}
pub fn with_params(mut self, params: ParamVec) -> Self {
self.params = CompactArc::new(params);
self
}
pub fn with_context(mut self, ctx: &ExecutionContext) -> Self {
self.params = CompactArc::clone(ctx.params_arc());
self.named_params = Arc::clone(ctx.named_params_arc());
self.transaction_id = ctx.transaction_id();
self.stored_function_invoker = ctx.stored_function_invoker().cloned();
self
}
#[inline]
pub fn eval_all(&mut self, row: &Row) -> Result<Vec<Value>> {
let mut ctx = ExecuteContext::new(row);
if !self.params.is_empty() {
ctx = ctx.with_params(&self.params);
}
if !self.named_params.is_empty() {
ctx = ctx.with_named_params(&self.named_params);
}
ctx = ctx
.with_transaction_id(self.transaction_id)
.with_stored_function_invoker(self.stored_function_invoker.as_ref());
self.programs
.iter()
.map(|prog| self.vm.execute_cow(prog, &ctx))
.collect()
}
#[inline]
pub fn eval_into(&mut self, row: &Row, output: &mut Vec<Value>) -> Result<()> {
let mut ctx = ExecuteContext::new(row);
if !self.params.is_empty() {
ctx = ctx.with_params(&self.params);
}
if !self.named_params.is_empty() {
ctx = ctx.with_named_params(&self.named_params);
}
ctx = ctx
.with_transaction_id(self.transaction_id)
.with_stored_function_invoker(self.stored_function_invoker.as_ref());
output.clear();
for prog in &self.programs {
output.push(self.vm.execute_cow(prog, &ctx)?);
}
Ok(())
}
pub fn len(&self) -> usize {
self.programs.len()
}
pub fn is_empty(&self) -> bool {
self.programs.is_empty()
}
}
pub type SharedProgram = CompactArc<Program>;
pub struct CompiledEvaluator<'a> {
function_registry: &'a FunctionRegistry,
columns: CompactArc<Vec<String>>,
columns2: Option<Vec<String>>,
outer_columns: Option<Vec<String>>,
params: CompactArc<ParamVec>,
named_params: Arc<FxHashMap<String, Value>>,
outer_row: Option<FxHashMap<CompactArc<str>, Value>>,
transaction_id: Option<u64>,
stored_function_invoker: Option<Arc<dyn StoredFunctionInvoker>>,
expression_aliases: StringMap<u16>,
column_aliases: StringMap<String>,
vm: ExprVM,
local_cache: FxHashMap<u64, LocalProgramCacheEntry>,
context_errors: Vec<String>,
current_row: Option<Row>,
current_row2: Option<Row>,
}
impl<'a> CompiledEvaluator<'a> {
pub fn new(function_registry: &'a FunctionRegistry) -> Self {
Self {
function_registry,
columns: CompactArc::new(Vec::new()),
columns2: None,
outer_columns: None,
params: CompactArc::new(ParamVec::new()),
named_params: Arc::new(FxHashMap::default()),
outer_row: None,
transaction_id: None,
stored_function_invoker: None,
expression_aliases: StringMap::new(),
column_aliases: StringMap::new(),
vm: ExprVM::new(),
local_cache: FxHashMap::default(),
context_errors: Vec::new(),
current_row: None,
current_row2: None,
}
}
pub fn with_defaults() -> CompiledEvaluator<'static> {
CompiledEvaluator::new(global_registry())
}
fn column_limit_error(label: &str, len: usize) -> Option<String> {
(len > (u16::MAX as usize + 1)).then(|| {
format!(
"{label} has {len} columns; expression bytecode supports at most {}",
u16::MAX as usize + 1
)
})
}
pub fn clear(&mut self) {
self.columns = CompactArc::new(Vec::new());
self.columns2 = None;
self.outer_columns = None;
self.params = CompactArc::new(ParamVec::new());
self.named_params = Arc::new(FxHashMap::default());
self.outer_row = None;
self.transaction_id = None;
self.stored_function_invoker = None;
self.expression_aliases.clear();
self.column_aliases.clear();
self.local_cache.clear();
self.context_errors.clear();
self.current_row = None;
self.current_row2 = None;
}
fn replace_context_error(&mut self, label: &str, error: Option<String>) {
self.context_errors
.retain(|existing| !existing.starts_with(label));
if let Some(error) = error {
self.context_errors.push(error);
}
}
fn context_error(&self) -> Option<String> {
(!self.context_errors.is_empty()).then(|| self.context_errors.join("; "))
}
pub fn set_transaction_id(&mut self, txn_id: u64) {
self.transaction_id = Some(txn_id);
}
pub fn with_params(mut self, params: ParamVec) -> Self {
self.params = CompactArc::new(params);
self
}
pub fn with_named_params(mut self, named_params: FxHashMap<String, Value>) -> Self {
self.named_params = Arc::new(named_params);
self
}
pub fn with_context(mut self, ctx: &ExecutionContext) -> Self {
self.params = CompactArc::clone(ctx.params_arc());
self.named_params = Arc::clone(ctx.named_params_arc());
if let Some(outer) = ctx.outer_row() {
let arc_map = outer.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
let outer_cols: Vec<String> = outer.keys().map(|k| k.to_string()).collect();
self.outer_row = Some(arc_map);
if !outer_cols.is_empty() {
self.outer_columns = Some(outer_cols);
let error = Self::column_limit_error(
"outer row",
self.outer_columns.as_ref().map_or(0, Vec::len),
);
self.replace_context_error("outer row", error);
self.local_cache.clear();
}
}
self.transaction_id = ctx.transaction_id();
self.stored_function_invoker = ctx.stored_function_invoker().cloned();
self
}
pub fn with_row(mut self, row: Row, columns: &[String]) -> Self {
self.init_columns(columns);
self.current_row = Some(row);
self.current_row2 = None;
self
}
pub fn init_columns(&mut self, columns: &[String]) {
if self.columns.as_ref() == columns {
return;
}
self.columns = CompactArc::new(columns.to_vec());
let error = Self::column_limit_error("primary row", columns.len());
self.replace_context_error("primary row", error);
self.local_cache.clear();
}
#[inline]
pub fn init_columns_arc(&mut self, columns: CompactArc<Vec<String>>) {
if self.columns.as_ref() == columns.as_ref() {
return;
}
let error = Self::column_limit_error("primary row", columns.len());
self.replace_context_error("primary row", error);
self.columns = columns;
self.local_cache.clear();
}
pub fn add_aggregate_aliases(&mut self, aliases: &[(String, usize)]) {
for (expr_name, idx) in aliases {
let lower = expr_name.to_lowercase();
match u16::try_from(*idx) {
Ok(index) => {
self.expression_aliases.insert(lower, index);
}
Err(_) => {
self.context_errors.push(format!(
"expression alias '{}' index {} exceeds the u16 bytecode limit",
expr_name, idx
));
}
}
}
self.local_cache.clear();
}
pub fn add_expression_aliases(&mut self, aliases: &[(String, usize)]) {
for (expr_str, idx) in aliases {
let lower = expr_str.to_lowercase();
match u16::try_from(*idx) {
Ok(index) => {
self.expression_aliases.insert(lower, index);
}
Err(_) => {
self.context_errors.push(format!(
"expression alias '{}' index {} exceeds the u16 bytecode limit",
expr_str, idx
));
}
}
}
self.local_cache.clear();
}
#[inline]
pub fn set_row_array(&mut self, row: &Row) {
self.current_row = Some(row.clone());
self.current_row2 = None;
}
#[inline]
pub fn set_outer_row(&mut self, outer_row: Option<&FxHashMap<CompactArc<str>, Value>>) {
if let Some(outer) = outer_row {
let map = outer.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
self.outer_row = Some(map);
} else {
self.outer_row = None;
}
}
#[inline]
pub fn set_outer_row_owned(&mut self, outer_row: FxHashMap<CompactArc<str>, Value>) {
let outer_cols: Vec<String> = outer_row.keys().map(|k| k.to_string()).collect();
self.outer_row = Some(outer_row);
if !outer_cols.is_empty() {
let mut sorted_cols = outer_cols;
sorted_cols.sort();
self.outer_columns = Some(sorted_cols);
let error = Self::column_limit_error(
"outer row",
self.outer_columns.as_ref().map_or(0, Vec::len),
);
self.replace_context_error("outer row", error);
self.local_cache.clear();
}
}
#[inline]
pub fn take_outer_row(&mut self) -> FxHashMap<CompactArc<str>, Value> {
self.outer_row.take().unwrap_or_default()
}
#[inline]
pub fn clear_outer_row(&mut self) {
self.outer_row = None;
}
#[inline]
fn expr_hash(&self, expr: &Expression) -> u64 {
let mut hasher = FxHasher::default();
Self::hash_expression(expr, &mut hasher);
hasher.finish()
}
fn hash_expression(expr: &Expression, hasher: &mut FxHasher) {
std::mem::discriminant(expr).hash(hasher);
match expr {
Expression::Identifier(id) => {
id.value_lower.hash(hasher);
}
Expression::QualifiedIdentifier(qid) => {
qid.qualifier.value_lower.hash(hasher);
qid.name.value_lower.hash(hasher);
}
Expression::IntegerLiteral(lit) => {
lit.value.hash(hasher);
}
Expression::FloatLiteral(lit) => {
lit.value.to_bits().hash(hasher);
}
Expression::StringLiteral(lit) => {
lit.value.hash(hasher);
lit.type_hint.hash(hasher);
}
Expression::BooleanLiteral(lit) => {
lit.value.hash(hasher);
}
Expression::NullLiteral(_) => {
}
Expression::BoundValue(value) => {
value.hash(hasher);
}
Expression::IntervalLiteral(lit) => {
lit.value.hash(hasher);
lit.unit.hash(hasher);
}
Expression::Parameter(param) => {
param.index.hash(hasher);
param.name.hash(hasher);
}
Expression::Prefix(prefix) => {
std::mem::discriminant(&prefix.op_type).hash(hasher);
Self::hash_expression(&prefix.right, hasher);
}
Expression::Infix(infix) => {
std::mem::discriminant(&infix.op_type).hash(hasher);
Self::hash_expression(&infix.left, hasher);
Self::hash_expression(&infix.right, hasher);
}
Expression::List(list) => {
list.elements.len().hash(hasher);
for val in &list.elements {
Self::hash_expression(val, hasher);
}
}
Expression::Distinct(dist) => {
Self::hash_expression(&dist.expr, hasher);
}
Expression::Exists(exists) => {
(exists.subquery.as_ref() as *const _ as usize).hash(hasher);
}
Expression::AllAny(aa) => {
aa.operator.hash(hasher);
std::mem::discriminant(&aa.all_any_type).hash(hasher);
Self::hash_expression(&aa.left, hasher);
(aa.subquery.as_ref() as *const _ as usize).hash(hasher);
}
Expression::In(in_expr) => {
in_expr.not.hash(hasher);
Self::hash_expression(&in_expr.left, hasher);
Self::hash_expression(&in_expr.right, hasher);
}
Expression::InHashSet(in_hash) => {
in_hash.not.hash(hasher);
Self::hash_expression(&in_hash.column, hasher);
let mut values: Vec<&Value> = in_hash.values.iter().collect();
values.sort_unstable();
values.hash(hasher);
}
Expression::Between(between) => {
between.not.hash(hasher);
Self::hash_expression(&between.expr, hasher);
Self::hash_expression(&between.lower, hasher);
Self::hash_expression(&between.upper, hasher);
}
Expression::Like(like) => {
like.operator.hash(hasher);
Self::hash_expression(&like.left, hasher);
Self::hash_expression(&like.pattern, hasher);
if let Some(ref escape) = like.escape {
true.hash(hasher);
Self::hash_expression(escape, hasher);
} else {
false.hash(hasher);
}
}
Expression::ScalarSubquery(sq) => {
(sq.subquery.as_ref() as *const _ as usize).hash(hasher);
}
Expression::ExpressionList(list) => {
list.expressions.len().hash(hasher);
for expr in &list.expressions {
Self::hash_expression(expr, hasher);
}
}
Expression::Case(case) => {
if let Some(ref val) = case.value {
true.hash(hasher);
Self::hash_expression(val, hasher);
} else {
false.hash(hasher);
}
case.when_clauses.len().hash(hasher);
for when_clause in &case.when_clauses {
Self::hash_expression(&when_clause.condition, hasher);
Self::hash_expression(&when_clause.then_result, hasher);
}
if let Some(ref else_val) = case.else_value {
true.hash(hasher);
Self::hash_expression(else_val, hasher);
} else {
false.hash(hasher);
}
}
Expression::Cast(cast) => {
Self::hash_expression(&cast.expr, hasher);
cast.type_name.hash(hasher);
}
Expression::FunctionCall(func) => {
func.function.hash(hasher);
func.is_distinct.hash(hasher);
func.arguments.len().hash(hasher);
for arg in &func.arguments {
Self::hash_expression(arg, hasher);
}
if let Some(ref filter) = func.filter {
true.hash(hasher);
Self::hash_expression(filter, hasher);
} else {
false.hash(hasher);
}
}
Expression::Aliased(aliased) => {
aliased.alias.value_lower.hash(hasher);
Self::hash_expression(&aliased.expression, hasher);
}
Expression::Window(window) => {
window.function.function.hash(hasher);
window.function.is_distinct.hash(hasher);
window.function.arguments.len().hash(hasher);
for arg in &window.function.arguments {
Self::hash_expression(arg, hasher);
}
window.partition_by.len().hash(hasher);
for expr in &window.partition_by {
Self::hash_expression(expr, hasher);
}
window.order_by.len().hash(hasher);
for order in &window.order_by {
Self::hash_expression(&order.expression, hasher);
order.ascending.hash(hasher);
order.nulls_first.hash(hasher);
}
}
Expression::TableSource(ts) => {
ts.name.value_lower.hash(hasher);
if let Some(ref alias) = ts.alias {
true.hash(hasher);
alias.value_lower.hash(hasher);
} else {
false.hash(hasher);
}
}
Expression::JoinSource(js) => {
(js.as_ref() as *const _ as usize).hash(hasher);
}
Expression::SubquerySource(sq) => {
if let Some(ref alias) = sq.alias {
true.hash(hasher);
alias.value_lower.hash(hasher);
} else {
false.hash(hasher);
}
(sq.subquery.as_ref() as *const _ as usize).hash(hasher);
}
Expression::ValuesSource(vs) => {
if let Some(ref alias) = vs.alias {
true.hash(hasher);
alias.value_lower.hash(hasher);
} else {
false.hash(hasher);
}
vs.rows.len().hash(hasher);
}
Expression::CteReference(cte) => {
cte.name.value_lower.hash(hasher);
}
Expression::FunctionTableSource(fts) => {
fts.function.value_lower.hash(hasher);
for arg in &fts.arguments {
Self::hash_expression(arg, hasher);
}
}
Expression::Star(_) => {
}
Expression::QualifiedStar(qs) => {
qs.qualifier.hash(hasher);
}
Expression::Default(_) => {
}
}
}
fn get_or_compile(&mut self, expr: &Expression) -> Result<SharedProgram> {
if let Some(error) = self.context_error() {
return Err(Error::invalid_argument(error));
}
let registry_generation = self.function_registry.generation();
let mut expr_key = self.expr_hash(expr);
expr_key ^= registry_generation.rotate_left(17);
if let Some(entry) = self.local_cache.get(&expr_key) {
if entry.registry_generation == registry_generation && entry.expression == *expr {
return Ok(CompactArc::clone(&entry.program));
}
}
let program = CompactArc::new(self.compile_expression(expr)?);
self.local_cache.insert(
expr_key,
LocalProgramCacheEntry {
expression: expr.clone(),
registry_generation,
program: CompactArc::clone(&program),
},
);
Ok(program)
}
fn compile_expression(&self, expr: &Expression) -> Result<Program> {
if let Some(error) = self.context_error() {
return Err(Error::invalid_argument(error));
}
let mut ctx = CompileContext::new(&self.columns, self.function_registry);
if let Some(ref cols2) = self.columns2 {
ctx = ctx.with_second_row(cols2);
}
if let Some(ref outer_cols) = self.outer_columns {
ctx = ctx.with_outer_columns(outer_cols);
}
if !self.expression_aliases.is_empty() {
ctx = ctx.with_expression_aliases(self.expression_aliases.clone());
}
if !self.column_aliases.is_empty() {
ctx = ctx.with_column_aliases(self.column_aliases.clone());
}
let compiler = ExprCompiler::new(&ctx);
compiler
.compile(expr)
.map_err(|e| Error::internal(format!("Compile error: {}", e)))
}
pub fn evaluate(&mut self, expr: &Expression) -> Result<Value> {
let program = self.get_or_compile(expr)?;
static EMPTY_ROW: std::sync::LazyLock<Row> = std::sync::LazyLock::new(Row::new);
let row = self.current_row.as_ref().unwrap_or(&EMPTY_ROW);
let row2 = self.current_row2.as_ref();
let mut ctx = if let Some(r2) = row2 {
ExecuteContext::for_join(row, r2)
} else {
ExecuteContext::new(row)
};
if !self.params.is_empty() {
ctx = ctx.with_params(&self.params);
}
if !self.named_params.is_empty() {
ctx = ctx.with_named_params(&self.named_params);
}
if let Some(ref outer) = self.outer_row {
ctx = ctx.with_outer_row(outer);
}
ctx = ctx
.with_transaction_id(self.transaction_id)
.with_stored_function_invoker(self.stored_function_invoker.as_ref());
self.vm.execute_cow(&program, &ctx)
}
pub fn evaluate_bool(&mut self, expr: &Expression) -> Result<bool> {
let program = self.get_or_compile(expr)?;
static EMPTY_ROW: std::sync::LazyLock<Row> = std::sync::LazyLock::new(Row::new);
let row = self.current_row.as_ref().unwrap_or(&EMPTY_ROW);
let row2 = self.current_row2.as_ref();
let mut ctx = if let Some(r2) = row2 {
ExecuteContext::for_join(row, r2)
} else {
ExecuteContext::new(row)
};
if !self.params.is_empty() {
ctx = ctx.with_params(&self.params);
}
if !self.named_params.is_empty() {
ctx = ctx.with_named_params(&self.named_params);
}
if let Some(ref outer) = self.outer_row {
ctx = ctx.with_outer_row(outer);
}
ctx = ctx
.with_transaction_id(self.transaction_id)
.with_stored_function_invoker(self.stored_function_invoker.as_ref());
self.vm.execute_bool_checked(&program, &ctx)
}
}
impl Default for CompiledEvaluator<'static> {
fn default() -> Self {
Self::with_defaults()
}
}
#[cfg(test)]
mod tests {
use super::*;
use radixdb_sql::ast::{
Expression, FunctionCall, Identifier, InfixExpression, InfixOperator, IntegerLiteral,
};
use radixdb_sql::token::{Position, Token, TokenType};
#[derive(Default)]
struct MutableScalarOne;
#[derive(Default)]
struct MutableScalarTwo;
macro_rules! mutable_scalar {
($type:ty, $value:expr) => {
impl radixdb_functions::ScalarFunction for $type {
fn name(&self) -> &str {
"MUTABLE_TEST"
}
fn info(&self) -> radixdb_functions::FunctionInfo {
radixdb_functions::FunctionInfo::new(
"MUTABLE_TEST",
radixdb_functions::FunctionType::Scalar,
"cache generation test",
radixdb_functions::FunctionSignature::new(
radixdb_functions::FunctionDataType::Integer,
vec![],
0,
0,
),
)
}
fn evaluate(&self, _args: &[Value]) -> Result<Value> {
Ok(Value::Integer($value))
}
}
};
}
mutable_scalar!(MutableScalarOne, 1);
mutable_scalar!(MutableScalarTwo, 2);
fn dummy_token() -> Token {
Token::new(TokenType::Eof, "", Position::default())
}
fn make_identifier(name: &str) -> Expression {
Expression::Identifier(Identifier {
token: dummy_token(),
value: name.into(),
value_lower: name.to_lowercase().into(),
})
}
fn make_int_literal(value: i64) -> Expression {
Expression::IntegerLiteral(IntegerLiteral {
token: dummy_token(),
value,
})
}
fn make_infix(left: Expression, op: InfixOperator, right: Expression) -> Expression {
let op_str = match op {
InfixOperator::GreaterThan => ">",
InfixOperator::LessThan => "<",
InfixOperator::Equal => "=",
InfixOperator::Add => "+",
InfixOperator::Multiply => "*",
_ => "?",
};
Expression::Infix(InfixExpression {
token: dummy_token(),
left: Box::new(left),
operator: op_str.into(),
op_type: op,
right: Box::new(right),
})
}
fn make_function(name: &str) -> Expression {
Expression::FunctionCall(Box::new(FunctionCall {
token: dummy_token(),
function: name.into(),
arguments: Vec::new(),
is_distinct: false,
order_by: Vec::new(),
filter: None,
}))
}
#[test]
fn test_compute_expression_hash_same_expr() {
let expr1 = make_int_literal(42);
let expr2 = make_int_literal(42);
assert_eq!(
compute_expression_hash(&expr1),
compute_expression_hash(&expr2)
);
}
#[test]
fn test_compute_expression_hash_different_expr() {
let expr1 = make_int_literal(42);
let expr2 = make_int_literal(43);
assert_ne!(
compute_expression_hash(&expr1),
compute_expression_hash(&expr2)
);
}
#[test]
fn test_compute_expression_hash_complex() {
let expr1 = make_infix(
make_identifier("col"),
InfixOperator::GreaterThan,
make_int_literal(5),
);
let expr2 = make_infix(
make_identifier("col"),
InfixOperator::GreaterThan,
make_int_literal(5),
);
assert_eq!(
compute_expression_hash(&expr1),
compute_expression_hash(&expr2)
);
}
#[test]
fn test_compile_expression_basic() {
let expr = make_infix(
make_identifier("col"),
InfixOperator::GreaterThan,
make_int_literal(5),
);
let columns = vec!["col".to_string()];
let program = compile_expression(&expr, &columns);
assert!(program.is_ok());
}
#[test]
fn test_compile_expression_unknown_column() {
let expr = make_infix(
make_identifier("unknown_col"),
InfixOperator::GreaterThan,
make_int_literal(5),
);
let columns = vec!["col".to_string()];
let program = compile_expression(&expr, &columns);
assert!(program.is_err());
}
#[test]
fn test_row_filter_new() {
let expr = make_infix(
make_identifier("col"),
InfixOperator::GreaterThan,
make_int_literal(5),
);
let columns = vec!["col".to_string()];
let filter = RowFilter::new(&expr, &columns);
assert!(filter.is_ok());
}
#[test]
fn test_row_filter_matches_true() {
let expr = make_infix(
make_identifier("col"),
InfixOperator::GreaterThan,
make_int_literal(5),
);
let columns = vec!["col".to_string()];
let filter = RowFilter::new(&expr, &columns).unwrap();
let row = Row::from(vec![Value::Integer(10)]);
assert!(filter.matches(&row).unwrap());
}
#[test]
fn test_row_filter_matches_false() {
let expr = make_infix(
make_identifier("col"),
InfixOperator::GreaterThan,
make_int_literal(5),
);
let columns = vec!["col".to_string()];
let filter = RowFilter::new(&expr, &columns).unwrap();
let row = Row::from(vec![Value::Integer(3)]);
assert!(!filter.matches(&row).unwrap());
}
#[test]
fn test_row_filter_evaluate() {
let expr = make_infix(
make_identifier("col"),
InfixOperator::Add,
make_int_literal(10),
);
let columns = vec!["col".to_string()];
let filter = RowFilter::new(&expr, &columns).unwrap();
let row = Row::from(vec![Value::Integer(5)]);
let result = filter.evaluate(&row).unwrap();
assert_eq!(result, Value::Integer(15));
}
#[test]
fn test_row_filter_clone() {
let expr = make_infix(
make_identifier("col"),
InfixOperator::GreaterThan,
make_int_literal(5),
);
let columns = vec!["col".to_string()];
let filter = RowFilter::new(&expr, &columns).unwrap();
let cloned = filter.clone();
let row = Row::from(vec![Value::Integer(10)]);
assert!(filter.matches(&row).unwrap());
assert!(cloned.matches(&row).unwrap());
}
#[test]
fn test_expression_eval_compile() {
let expr = make_infix(
make_identifier("col"),
InfixOperator::GreaterThan,
make_int_literal(5),
);
let columns = vec!["col".to_string()];
let eval = ExpressionEval::compile(&expr, &columns);
assert!(eval.is_ok());
}
#[test]
fn test_expression_eval_eval() {
let expr = make_infix(
make_identifier("col"),
InfixOperator::Add,
make_int_literal(10),
);
let columns = vec!["col".to_string()];
let mut eval = ExpressionEval::compile(&expr, &columns).unwrap();
let row = Row::from(vec![Value::Integer(5)]);
let result = eval.eval(&row).unwrap();
assert_eq!(result, Value::Integer(15));
}
#[test]
fn test_expression_eval_eval_bool() {
let expr = make_infix(
make_identifier("col"),
InfixOperator::GreaterThan,
make_int_literal(5),
);
let columns = vec!["col".to_string()];
let mut eval = ExpressionEval::compile(&expr, &columns).unwrap();
let row = Row::from(vec![Value::Integer(10)]);
assert!(eval.eval_bool(&row).unwrap());
let row = Row::from(vec![Value::Integer(3)]);
assert!(!eval.eval_bool(&row).unwrap());
}
#[test]
fn test_multi_expression_eval_compile() {
let expr1 = make_infix(
make_identifier("col"),
InfixOperator::Add,
make_int_literal(10),
);
let expr2 = make_infix(
make_identifier("col"),
InfixOperator::Multiply,
make_int_literal(2),
);
let columns = vec!["col".to_string()];
let eval = MultiExpressionEval::compile(&[expr1, expr2], &columns);
assert!(eval.is_ok());
assert_eq!(eval.unwrap().len(), 2);
}
#[test]
fn test_multi_expression_eval_all() {
let expr1 = make_infix(
make_identifier("col"),
InfixOperator::Add,
make_int_literal(10),
);
let expr2 = make_infix(
make_identifier("col"),
InfixOperator::Multiply,
make_int_literal(2),
);
let columns = vec!["col".to_string()];
let mut eval = MultiExpressionEval::compile(&[expr1, expr2], &columns).unwrap();
let row = Row::from(vec![Value::Integer(5)]);
let results = eval.eval_all(&row).unwrap();
assert_eq!(results.len(), 2);
assert_eq!(results[0], Value::Integer(15)); assert_eq!(results[1], Value::Integer(10)); }
#[test]
fn test_compiled_evaluator_with_defaults() {
let eval = CompiledEvaluator::with_defaults();
assert!(eval.columns.is_empty());
}
#[test]
fn test_compiled_evaluator_init_columns() {
let mut eval = CompiledEvaluator::with_defaults();
eval.init_columns(&["col1".to_string(), "col2".to_string()]);
assert_eq!(eval.columns.len(), 2);
}
#[test]
fn compiled_evaluator_with_row_binds_owned_row_for_value_and_bool_evaluation() {
let columns = vec!["col".to_string()];
let mut eval = CompiledEvaluator::with_defaults()
.with_row(Row::from(vec![Value::Integer(10)]), &columns);
assert_eq!(
eval.evaluate(&make_identifier("col")).unwrap(),
Value::Integer(10)
);
assert!(eval
.evaluate_bool(&make_infix(
make_identifier("col"),
InfixOperator::GreaterThan,
make_int_literal(5),
))
.unwrap());
}
#[test]
fn test_compiled_evaluator_evaluate_bool() {
let mut eval = CompiledEvaluator::with_defaults();
eval.init_columns(&["col".to_string()]);
let row = Row::from(vec![Value::Integer(10)]);
eval.set_row_array(&row);
let expr = make_infix(
make_identifier("col"),
InfixOperator::GreaterThan,
make_int_literal(5),
);
let result = eval.evaluate_bool(&expr);
assert!(result.is_ok());
assert!(result.unwrap());
}
#[test]
fn test_compiled_evaluator_evaluate() {
let mut eval = CompiledEvaluator::with_defaults();
eval.init_columns(&["col".to_string()]);
let row = Row::from(vec![Value::Integer(5)]);
eval.set_row_array(&row);
let expr = make_infix(
make_identifier("col"),
InfixOperator::Add,
make_int_literal(10),
);
let result = eval.evaluate(&expr);
assert!(result.is_ok());
assert_eq!(result.unwrap(), Value::Integer(15));
}
#[test]
fn test_compiled_evaluator_default() {
let eval = CompiledEvaluator::default();
assert!(eval.columns.is_empty());
}
#[test]
fn compiled_evaluator_rebinds_semantically_changed_schemas() {
let mut eval = CompiledEvaluator::with_defaults();
let expr = make_identifier("a");
eval.init_columns(&["a".to_string(), "b".to_string()]);
eval.set_row_array(&Row::from(vec![Value::Integer(1), Value::Integer(2)]));
assert_eq!(eval.evaluate(&expr).unwrap(), Value::Integer(1));
eval.init_columns(&["b".to_string(), "a".to_string()]);
eval.set_row_array(&Row::from(vec![Value::Integer(1), Value::Integer(2)]));
assert_eq!(eval.evaluate(&expr).unwrap(), Value::Integer(2));
}
#[test]
fn compiled_evaluator_rejects_wide_schema_without_losing_other_errors() {
let mut eval = CompiledEvaluator::with_defaults();
let columns = (0..=u16::MAX as usize + 1)
.map(|index| format!("c{index}"))
.collect::<Vec<_>>();
eval.init_columns(&columns);
assert!(eval.evaluate(&make_int_literal(1)).is_err());
eval.init_columns(&["c".to_string()]);
eval.set_row_array(&Row::from(vec![Value::Integer(1)]));
assert_eq!(
eval.evaluate(&make_int_literal(1)).unwrap(),
Value::Integer(1)
);
}
#[test]
fn join_filter_reads_deferred_projection_without_materializing_it() {
let expression = make_infix(
make_identifier("left_id"),
InfixOperator::Equal,
make_identifier("right_id"),
);
let filter = JoinFilter::new(
&expression,
&["left_id".to_string()],
&["right_id".to_string()],
global_registry(),
)
.unwrap();
let left = crate::operator::RowRef::projected(
crate::operator::RowRef::owned(Row::from_values(vec![
Value::Integer(99),
Value::Integer(7),
])),
crate::operator::RowRef::owned(Row::new()),
CompactArc::from(vec![crate::operator::ColumnSource::Outer(1)]),
);
assert!(left.is_deferred());
assert!(filter
.matches_row_ref_checked(&left, &Row::from_values(vec![Value::Integer(7)]))
.unwrap());
assert!(left.is_deferred());
}
#[test]
fn row_filter_reads_portable_deferred_projection_without_materializing_it() {
let expression = make_infix(
make_identifier("status"),
InfixOperator::Equal,
make_int_literal(7),
);
let filter = RowFilter::new(&expression, &["status".to_string()]).unwrap();
let row = radixdb_storage::DeferredRow::projected(
radixdb_storage::DeferredRow::owned(Row::from_values(vec![
Value::Integer(99),
Value::Integer(7),
])),
radixdb_storage::DeferredRow::owned(Row::new()),
CompactArc::from(vec![radixdb_storage::DeferredColumnSource::Left(1)]),
);
assert!(row.is_deferred());
assert!(filter.matches_deferred_checked(&row).unwrap());
assert!(row.is_deferred());
}
#[test]
fn row_filter_reads_executor_projection_without_materializing_it() {
let expression = make_infix(
make_identifier("status"),
InfixOperator::Equal,
make_int_literal(7),
);
let filter = RowFilter::new(&expression, &["status".to_string()]).unwrap();
let row = crate::operator::RowRef::projected(
crate::operator::RowRef::owned(Row::from_values(vec![
Value::Integer(99),
Value::Integer(7),
])),
crate::operator::RowRef::owned(Row::new()),
CompactArc::from(vec![crate::operator::ColumnSource::Outer(1)]),
);
assert!(row.is_deferred());
assert!(filter.matches_row_ref_checked(&row).unwrap());
assert!(row.is_deferred());
}
#[test]
fn registry_generation_invalidates_embedded_function_programs() {
let registry = FunctionRegistry::new();
registry.register_scalar::<MutableScalarOne>();
let mut eval = CompiledEvaluator::new(®istry);
eval.set_row_array(&Row::new());
let expr = make_function("MUTABLE_TEST");
assert_eq!(eval.evaluate(&expr).unwrap(), Value::Integer(1));
registry.register_scalar::<MutableScalarTwo>();
assert_eq!(eval.evaluate(&expr).unwrap(), Value::Integer(2));
}
}