1use crate::soch_ql::SochValue;
10use crate::sql::ast::{BinaryOperator, ColumnRef, Expr, FunctionCall, Literal, UnaryOperator};
11use super::types::{Row, Schema};
12use sochdb_core::Result;
13
14pub fn eval_expr(expr: &Expr, row: &Row, schema: &Schema) -> Result<SochValue> {
16 match expr {
17 Expr::Literal(lit) => Ok(eval_literal(lit)),
18
19 Expr::Column(col_ref) => eval_column(col_ref, row, schema),
20
21 Expr::BinaryOp { left, op, right } => {
22 let lv = eval_expr(left, row, schema)?;
23 match op {
25 BinaryOperator::And => {
26 if !value_is_truthy(&lv) {
27 return Ok(SochValue::Bool(false));
28 }
29 let rv = eval_expr(right, row, schema)?;
30 return Ok(SochValue::Bool(value_is_truthy(&rv)));
31 }
32 BinaryOperator::Or => {
33 if value_is_truthy(&lv) {
34 return Ok(SochValue::Bool(true));
35 }
36 let rv = eval_expr(right, row, schema)?;
37 return Ok(SochValue::Bool(value_is_truthy(&rv)));
38 }
39 _ => {}
40 }
41 let rv = eval_expr(right, row, schema)?;
42 eval_binary_op(&lv, *op, &rv)
43 }
44
45 Expr::UnaryOp { op, expr: inner } => {
46 let v = eval_expr(inner, row, schema)?;
47 eval_unary_op(*op, &v)
48 }
49
50 Expr::Function(func) => eval_function(func, row, schema),
51
52 Expr::IsNull { expr: inner, negated } => {
53 let v = eval_expr(inner, row, schema)?;
54 let is_null = matches!(v, SochValue::Null);
55 Ok(SochValue::Bool(if *negated { !is_null } else { is_null }))
56 }
57
58 Expr::Between { expr: inner, low, high, negated } => {
59 let v = eval_expr(inner, row, schema)?;
60 let lo = eval_expr(low, row, schema)?;
61 let hi = eval_expr(high, row, schema)?;
62 let in_range = compare_values(&v, &lo) != Some(std::cmp::Ordering::Less)
63 && compare_values(&v, &hi) != Some(std::cmp::Ordering::Greater);
64 Ok(SochValue::Bool(if *negated { !in_range } else { in_range }))
65 }
66
67 Expr::InList { expr: inner, list, negated } => {
68 let v = eval_expr(inner, row, schema)?;
69 let mut found = false;
70 for item in list {
71 let iv = eval_expr(item, row, schema)?;
72 if compare_values(&v, &iv) == Some(std::cmp::Ordering::Equal) {
73 found = true;
74 break;
75 }
76 }
77 Ok(SochValue::Bool(if *negated { !found } else { found }))
78 }
79
80 Expr::Like { expr: inner, pattern, negated, .. } => {
81 let v = eval_expr(inner, row, schema)?;
82 let p = eval_expr(pattern, row, schema)?;
83 let matched = match (&v, &p) {
84 (SochValue::Text(s), SochValue::Text(pat)) => like_match(s, pat),
85 _ => false,
86 };
87 Ok(SochValue::Bool(if *negated { !matched } else { matched }))
88 }
89
90 Expr::Case { operand, conditions, else_result } => {
91 eval_case(operand.as_deref(), conditions, else_result.as_deref(), row, schema)
92 }
93
94 Expr::Cast { expr: inner, data_type: _ } => {
95 eval_expr(inner, row, schema)
98 }
99
100 Expr::Array(elements) => {
101 let mut vals = Vec::with_capacity(elements.len());
102 for e in elements {
103 vals.push(eval_expr(e, row, schema)?);
104 }
105 Ok(SochValue::Array(vals))
106 }
107
108 Expr::Tuple(elements) => {
109 let mut vals = Vec::with_capacity(elements.len());
110 for e in elements {
111 vals.push(eval_expr(e, row, schema)?);
112 }
113 Ok(SochValue::Array(vals))
114 }
115
116 Expr::Placeholder(_) => Err(sochdb_core::SochDBError::Internal(
118 "Unresolved placeholder in expression".into(),
119 )),
120
121 Expr::Vector(floats) => {
123 Ok(SochValue::Array(
124 floats.iter().map(|f| SochValue::Float(*f as f64)).collect(),
125 ))
126 }
127
128 Expr::Subquery(_)
130 | Expr::Exists(_)
131 | Expr::InSubquery { .. }
132 | Expr::VectorSearch { .. }
133 | Expr::JsonAccess { .. }
134 | Expr::ContextWindow { .. }
135 | Expr::Subscript { .. }
136 | Expr::RecordId { .. } => Err(sochdb_core::SochDBError::Internal(
137 format!("Expression type not yet supported in executor: {:?}", std::mem::discriminant(expr)),
138 )),
139 }
140}
141
142pub fn eval_predicate(expr: &Expr, row: &Row, schema: &Schema) -> Result<bool> {
144 let v = eval_expr(expr, row, schema)?;
145 Ok(value_is_truthy(&v))
146}
147
148fn eval_literal(lit: &Literal) -> SochValue {
153 match lit {
154 Literal::Null => SochValue::Null,
155 Literal::Boolean(b) => SochValue::Bool(*b),
156 Literal::Integer(i) => SochValue::Int(*i),
157 Literal::Float(f) => SochValue::Float(*f),
158 Literal::String(s) => SochValue::Text(s.clone()),
159 Literal::Blob(b) => SochValue::Binary(b.clone()),
160 }
161}
162
163fn eval_column(col: &ColumnRef, row: &Row, schema: &Schema) -> Result<SochValue> {
164 let idx = schema
165 .index_of_qualified(col.table.as_deref(), &col.column)
166 .ok_or_else(|| {
167 sochdb_core::SochDBError::Internal(format!(
168 "Column '{}' not found in schema {:?}",
169 col.column,
170 schema.column_names()
171 ))
172 })?;
173 Ok(row.get(idx).cloned().unwrap_or(SochValue::Null))
174}
175
176fn eval_binary_op(lv: &SochValue, op: BinaryOperator, rv: &SochValue) -> Result<SochValue> {
177 if matches!(lv, SochValue::Null) || matches!(rv, SochValue::Null) {
179 match op {
180 BinaryOperator::Eq | BinaryOperator::Ne | BinaryOperator::Lt
181 | BinaryOperator::Le | BinaryOperator::Gt | BinaryOperator::Ge => {
182 return Ok(SochValue::Null);
184 }
185 _ => return Ok(SochValue::Null),
186 }
187 }
188
189 match op {
190 BinaryOperator::Eq => Ok(SochValue::Bool(
192 compare_values(lv, rv) == Some(std::cmp::Ordering::Equal),
193 )),
194 BinaryOperator::Ne => Ok(SochValue::Bool(
195 compare_values(lv, rv) != Some(std::cmp::Ordering::Equal),
196 )),
197 BinaryOperator::Lt => Ok(SochValue::Bool(
198 compare_values(lv, rv) == Some(std::cmp::Ordering::Less),
199 )),
200 BinaryOperator::Le => Ok(SochValue::Bool(
201 compare_values(lv, rv) != Some(std::cmp::Ordering::Greater),
202 )),
203 BinaryOperator::Gt => Ok(SochValue::Bool(
204 compare_values(lv, rv) == Some(std::cmp::Ordering::Greater),
205 )),
206 BinaryOperator::Ge => Ok(SochValue::Bool(
207 compare_values(lv, rv) != Some(std::cmp::Ordering::Less),
208 )),
209
210 BinaryOperator::Plus => eval_arithmetic(lv, rv, |a, b| a + b, |a, b| a + b),
212 BinaryOperator::Minus => eval_arithmetic(lv, rv, |a, b| a - b, |a, b| a - b),
213 BinaryOperator::Multiply => eval_arithmetic(lv, rv, |a, b| a * b, |a, b| a * b),
214 BinaryOperator::Divide => {
215 match rv {
217 SochValue::Int(0) | SochValue::UInt(0) => {
218 return Err(sochdb_core::SochDBError::Internal("Division by zero".into()));
219 }
220 SochValue::Float(f) if *f == 0.0 => {
221 return Err(sochdb_core::SochDBError::Internal("Division by zero".into()));
222 }
223 _ => {}
224 }
225 eval_arithmetic(lv, rv, |a, b| a / b, |a, b| a / b)
226 }
227 BinaryOperator::Modulo => {
228 match rv {
229 SochValue::Int(0) | SochValue::UInt(0) => {
230 return Err(sochdb_core::SochDBError::Internal("Division by zero".into()));
231 }
232 _ => {}
233 }
234 eval_arithmetic(lv, rv, |a, b| a % b, |a, b| a % b)
235 }
236
237 BinaryOperator::Concat => {
239 let ls = value_to_string(lv);
240 let rs = value_to_string(rv);
241 Ok(SochValue::Text(format!("{}{}", ls, rs)))
242 }
243
244 BinaryOperator::Like => {
246 match (lv, rv) {
247 (SochValue::Text(s), SochValue::Text(p)) => {
248 Ok(SochValue::Bool(like_match(s, p)))
249 }
250 _ => Ok(SochValue::Bool(false)),
251 }
252 }
253
254 BinaryOperator::BitAnd => eval_bitwise(lv, rv, |a, b| a & b),
256 BinaryOperator::BitOr => eval_bitwise(lv, rv, |a, b| a | b),
257 BinaryOperator::BitXor => eval_bitwise(lv, rv, |a, b| a ^ b),
258 BinaryOperator::LeftShift => eval_bitwise(lv, rv, |a, b| a << b),
259 BinaryOperator::RightShift => eval_bitwise(lv, rv, |a, b| a >> b),
260
261 BinaryOperator::And | BinaryOperator::Or => unreachable!(),
263
264 BinaryOperator::GraphRight | BinaryOperator::GraphLeft | BinaryOperator::GraphBi => {
266 Err(sochdb_core::SochDBError::Internal(
267 "Graph traversal operators (-> <- <->) not yet supported in scalar evaluation".into(),
268 ))
269 }
270 }
271}
272
273fn eval_unary_op(op: UnaryOperator, v: &SochValue) -> Result<SochValue> {
274 match op {
275 UnaryOperator::Minus => match v {
276 SochValue::Int(i) => Ok(SochValue::Int(-i)),
277 SochValue::Float(f) => Ok(SochValue::Float(-f)),
278 SochValue::Null => Ok(SochValue::Null),
279 _ => Err(sochdb_core::SochDBError::Internal("Cannot negate non-numeric value".into())),
280 },
281 UnaryOperator::Plus => Ok(v.clone()),
282 UnaryOperator::Not => Ok(SochValue::Bool(!value_is_truthy(v))),
283 UnaryOperator::BitNot => match v {
284 SochValue::Int(i) => Ok(SochValue::Int(!i)),
285 SochValue::Null => Ok(SochValue::Null),
286 _ => Err(sochdb_core::SochDBError::Internal("Cannot bitwise-NOT non-integer".into())),
287 },
288 }
289}
290
291fn eval_function(func: &FunctionCall, row: &Row, schema: &Schema) -> Result<SochValue> {
292 let name = func.name.name().to_uppercase();
293
294 let args: Vec<SochValue> = func
296 .args
297 .iter()
298 .map(|a| eval_expr(a, row, schema))
299 .collect::<Result<Vec<_>>>()?;
300
301 match name.as_str() {
302 "COALESCE" => {
303 for a in &args {
304 if !matches!(a, SochValue::Null) {
305 return Ok(a.clone());
306 }
307 }
308 Ok(SochValue::Null)
309 }
310 "NULLIF" => {
311 if args.len() == 2 && compare_values(&args[0], &args[1]) == Some(std::cmp::Ordering::Equal) {
312 Ok(SochValue::Null)
313 } else {
314 Ok(args.into_iter().next().unwrap_or(SochValue::Null))
315 }
316 }
317 "ABS" => match args.first() {
318 Some(SochValue::Int(i)) => Ok(SochValue::Int(i.abs())),
319 Some(SochValue::Float(f)) => Ok(SochValue::Float(f.abs())),
320 _ => Ok(SochValue::Null),
321 },
322 "LENGTH" | "LEN" => match args.first() {
323 Some(SochValue::Text(s)) => Ok(SochValue::Int(s.len() as i64)),
324 Some(SochValue::Binary(b)) => Ok(SochValue::Int(b.len() as i64)),
325 _ => Ok(SochValue::Null),
326 },
327 "UPPER" => match args.first() {
328 Some(SochValue::Text(s)) => Ok(SochValue::Text(s.to_uppercase())),
329 _ => Ok(SochValue::Null),
330 },
331 "LOWER" => match args.first() {
332 Some(SochValue::Text(s)) => Ok(SochValue::Text(s.to_lowercase())),
333 _ => Ok(SochValue::Null),
334 },
335 "TRIM" => match args.first() {
336 Some(SochValue::Text(s)) => Ok(SochValue::Text(s.trim().to_string())),
337 _ => Ok(SochValue::Null),
338 },
339 "SUBSTR" | "SUBSTRING" => {
340 match (args.get(0), args.get(1), args.get(2)) {
341 (Some(SochValue::Text(s)), Some(SochValue::Int(start)), len) => {
342 let start_idx = (*start as usize).saturating_sub(1); let slice = if let Some(SochValue::Int(l)) = len {
344 let end = start_idx + (*l as usize);
345 &s[start_idx..end.min(s.len())]
346 } else {
347 &s[start_idx..]
348 };
349 Ok(SochValue::Text(slice.to_string()))
350 }
351 _ => Ok(SochValue::Null),
352 }
353 }
354 "CONCAT" => {
355 let s: String = args.iter().map(value_to_string).collect::<Vec<_>>().join("");
356 Ok(SochValue::Text(s))
357 }
358 "REPLACE" => {
359 match (args.get(0), args.get(1), args.get(2)) {
360 (Some(SochValue::Text(s)), Some(SochValue::Text(from)), Some(SochValue::Text(to))) => {
361 Ok(SochValue::Text(s.replace(from.as_str(), to.as_str())))
362 }
363 _ => Ok(SochValue::Null),
364 }
365 }
366 "ROUND" => {
367 match (args.get(0), args.get(1)) {
368 (Some(SochValue::Float(f)), Some(SochValue::Int(digits))) => {
369 let factor = 10f64.powi(*digits as i32);
370 Ok(SochValue::Float((f * factor).round() / factor))
371 }
372 (Some(SochValue::Float(f)), None) => Ok(SochValue::Float(f.round())),
373 (Some(SochValue::Int(i)), _) => Ok(SochValue::Int(*i)),
374 _ => Ok(SochValue::Null),
375 }
376 }
377 "FLOOR" => match args.first() {
378 Some(SochValue::Float(f)) => Ok(SochValue::Float(f.floor())),
379 Some(v @ SochValue::Int(_)) => Ok(v.clone()),
380 _ => Ok(SochValue::Null),
381 },
382 "CEIL" | "CEILING" => match args.first() {
383 Some(SochValue::Float(f)) => Ok(SochValue::Float(f.ceil())),
384 Some(v @ SochValue::Int(_)) => Ok(v.clone()),
385 _ => Ok(SochValue::Null),
386 },
387 "GREATEST" => {
388 let mut best: Option<SochValue> = None;
389 for a in &args {
390 if matches!(a, SochValue::Null) { continue; }
391 best = Some(match &best {
392 None => a.clone(),
393 Some(b) => {
394 if compare_values(a, b) == Some(std::cmp::Ordering::Greater) {
395 a.clone()
396 } else {
397 b.clone()
398 }
399 }
400 });
401 }
402 Ok(best.unwrap_or(SochValue::Null))
403 }
404 "LEAST" => {
405 let mut best: Option<SochValue> = None;
406 for a in &args {
407 if matches!(a, SochValue::Null) { continue; }
408 best = Some(match &best {
409 None => a.clone(),
410 Some(b) => {
411 if compare_values(a, b) == Some(std::cmp::Ordering::Less) {
412 a.clone()
413 } else {
414 b.clone()
415 }
416 }
417 });
418 }
419 Ok(best.unwrap_or(SochValue::Null))
420 }
421 "COUNT" | "SUM" | "AVG" | "MIN" | "MAX" | "COUNT_DISTINCT" => {
424 Err(sochdb_core::SochDBError::Internal(format!(
425 "Aggregate function {}() cannot be used outside of GROUP BY context",
426 name,
427 )))
428 }
429 _ => Err(sochdb_core::SochDBError::Internal(format!(
430 "Unknown function: {}",
431 name,
432 ))),
433 }
434}
435
436fn eval_case(
437 operand: Option<&Expr>,
438 conditions: &[(Expr, Expr)],
439 else_result: Option<&Expr>,
440 row: &Row,
441 schema: &Schema,
442) -> Result<SochValue> {
443 if let Some(op) = operand {
444 let op_val = eval_expr(op, row, schema)?;
446 for (when_expr, then_expr) in conditions {
447 let when_val = eval_expr(when_expr, row, schema)?;
448 if compare_values(&op_val, &when_val) == Some(std::cmp::Ordering::Equal) {
449 return eval_expr(then_expr, row, schema);
450 }
451 }
452 } else {
453 for (when_expr, then_expr) in conditions {
455 if eval_predicate(when_expr, row, schema)? {
456 return eval_expr(then_expr, row, schema);
457 }
458 }
459 }
460 match else_result {
461 Some(e) => eval_expr(e, row, schema),
462 None => Ok(SochValue::Null),
463 }
464}
465
466pub fn compare_values(a: &SochValue, b: &SochValue) -> Option<std::cmp::Ordering> {
472 use std::cmp::Ordering;
473
474 match (a, b) {
475 (SochValue::Null, SochValue::Null) => Some(Ordering::Equal),
476 (SochValue::Null, _) | (_, SochValue::Null) => None,
477 (SochValue::Bool(a), SochValue::Bool(b)) => a.partial_cmp(b),
478 (SochValue::Int(a), SochValue::Int(b)) => a.partial_cmp(b),
479 (SochValue::UInt(a), SochValue::UInt(b)) => a.partial_cmp(b),
480 (SochValue::Float(a), SochValue::Float(b)) => a.partial_cmp(b),
481 (SochValue::Text(a), SochValue::Text(b)) => a.partial_cmp(b),
482
483 (SochValue::Int(a), SochValue::Float(b)) => (*a as f64).partial_cmp(b),
485 (SochValue::Float(a), SochValue::Int(b)) => a.partial_cmp(&(*b as f64)),
486 (SochValue::Int(a), SochValue::UInt(b)) => (*a as i128).partial_cmp(&(*b as i128)),
487 (SochValue::UInt(a), SochValue::Int(b)) => (*a as i128).partial_cmp(&(*b as i128)),
488 (SochValue::UInt(a), SochValue::Float(b)) => (*a as f64).partial_cmp(b),
489 (SochValue::Float(a), SochValue::UInt(b)) => a.partial_cmp(&(*b as f64)),
490
491 _ => None,
492 }
493}
494
495fn like_match(s: &str, pattern: &str) -> bool {
497 let s_chars: Vec<char> = s.chars().collect();
498 let p_chars: Vec<char> = pattern.chars().collect();
499 like_match_impl(&s_chars, &p_chars, 0, 0)
500}
501
502fn like_match_impl(s: &[char], p: &[char], si: usize, pi: usize) -> bool {
503 if pi == p.len() {
504 return si == s.len();
505 }
506 match p[pi] {
507 '%' => {
508 for i in si..=s.len() {
510 if like_match_impl(s, p, i, pi + 1) {
511 return true;
512 }
513 }
514 false
515 }
516 '_' => {
517 si < s.len() && like_match_impl(s, p, si + 1, pi + 1)
519 }
520 c => {
521 si < s.len()
522 && (s[si] == c || s[si].to_lowercase().eq(c.to_lowercase()))
523 && like_match_impl(s, p, si + 1, pi + 1)
524 }
525 }
526}
527
528pub fn value_is_truthy(v: &SochValue) -> bool {
530 match v {
531 SochValue::Bool(b) => *b,
532 SochValue::Int(i) => *i != 0,
533 SochValue::UInt(u) => *u != 0,
534 SochValue::Float(f) => *f != 0.0,
535 SochValue::Text(s) => !s.is_empty(),
536 SochValue::Null => false,
537 _ => true,
538 }
539}
540
541fn value_to_string(v: &SochValue) -> String {
543 match v {
544 SochValue::Null => "NULL".to_string(),
545 SochValue::Bool(b) => b.to_string(),
546 SochValue::Int(i) => i.to_string(),
547 SochValue::UInt(u) => u.to_string(),
548 SochValue::Float(f) => f.to_string(),
549 SochValue::Text(s) => s.clone(),
550 SochValue::Binary(b) => format!("0x{}", hex::encode(b)),
551 SochValue::Array(a) => format!("[{}]", a.iter().map(value_to_string).collect::<Vec<_>>().join(",")),
552 }
553}
554
555fn eval_arithmetic<F, G>(lv: &SochValue, rv: &SochValue, int_op: F, float_op: G) -> Result<SochValue>
556where
557 F: Fn(i64, i64) -> i64,
558 G: Fn(f64, f64) -> f64,
559{
560 match (lv, rv) {
561 (SochValue::Int(a), SochValue::Int(b)) => Ok(SochValue::Int(int_op(*a, *b))),
562 (SochValue::Float(a), SochValue::Float(b)) => Ok(SochValue::Float(float_op(*a, *b))),
563 (SochValue::Int(a), SochValue::Float(b)) => Ok(SochValue::Float(float_op(*a as f64, *b))),
564 (SochValue::Float(a), SochValue::Int(b)) => Ok(SochValue::Float(float_op(*a, *b as f64))),
565 (SochValue::UInt(a), SochValue::UInt(b)) => Ok(SochValue::Int(int_op(*a as i64, *b as i64))),
566 (SochValue::Int(a), SochValue::UInt(b)) => Ok(SochValue::Int(int_op(*a, *b as i64))),
567 (SochValue::UInt(a), SochValue::Int(b)) => Ok(SochValue::Int(int_op(*a as i64, *b))),
568 (SochValue::UInt(a), SochValue::Float(b)) => Ok(SochValue::Float(float_op(*a as f64, *b))),
569 (SochValue::Float(a), SochValue::UInt(b)) => Ok(SochValue::Float(float_op(*a, *b as f64))),
570 _ => Err(sochdb_core::SochDBError::Internal(format!(
571 "Cannot perform arithmetic on {:?} and {:?}",
572 std::mem::discriminant(lv),
573 std::mem::discriminant(rv),
574 ))),
575 }
576}
577
578fn eval_bitwise<F>(lv: &SochValue, rv: &SochValue, op: F) -> Result<SochValue>
579where
580 F: Fn(i64, i64) -> i64,
581{
582 match (lv, rv) {
583 (SochValue::Int(a), SochValue::Int(b)) => Ok(SochValue::Int(op(*a, *b))),
584 (SochValue::UInt(a), SochValue::UInt(b)) => Ok(SochValue::Int(op(*a as i64, *b as i64))),
585 _ => Err(sochdb_core::SochDBError::Internal("Bitwise ops require integer operands".into())),
586 }
587}
588
589mod hex {
591 pub fn encode(data: &[u8]) -> String {
592 data.iter().map(|b| format!("{:02x}", b)).collect()
593 }
594}