Skip to main content

hawdb_expression/sql/
numeric.rs

1// Copyright 2026 Nowledge
2// SPDX-License-Identifier: Apache-2.0
3
4//! Bounded binding of constant numeric SQL expressions. Column evaluation stays
5//! with the row owner; all arithmetic and conversion use the core kernel.
6
7use super::{Expr, ExprKind, SqlValue};
8use hawdb_core::numeric::{cast_numeric, evaluate_numeric, negate_numeric};
9use hawdb_core::{HawDBError, Result, Value};
10
11const MAX_DEPTH: usize = 64;
12const MAX_NODES: usize = 4096;
13
14pub fn bind_sql_value(value: &SqlValue, parameters: &[Value]) -> Result<Value> {
15    Binder {
16        parameters,
17        remaining: MAX_NODES,
18    }
19    .value(value, 0)
20}
21
22pub fn bind_constant_numeric(expression: &Expr, parameters: &[Value]) -> Result<Value> {
23    Binder {
24        parameters,
25        remaining: MAX_NODES,
26    }
27    .expression(expression, 0)
28}
29
30struct Binder<'a> {
31    parameters: &'a [Value],
32    remaining: usize,
33}
34
35impl Binder<'_> {
36    fn admit(&mut self, depth: usize) -> Result<()> {
37        if depth > MAX_DEPTH || self.remaining == 0 {
38            return Err(HawDBError::Semantic(
39                "numeric expression exceeds 64 levels or 4096 nodes".into(),
40            ));
41        }
42        self.remaining -= 1;
43        Ok(())
44    }
45
46    fn value(&mut self, value: &SqlValue, depth: usize) -> Result<Value> {
47        self.admit(depth)?;
48        match value {
49            SqlValue::Literal(value) => Ok(value.clone()),
50            SqlValue::Parameter(position) => position
51                .checked_sub(1)
52                .and_then(|index| self.parameters.get(index))
53                .cloned()
54                .ok_or_else(|| {
55                    HawDBError::Semantic(format!("missing PostgreSQL parameter ${position}"))
56                }),
57            SqlValue::Expression(expression) => self.expression(expression, depth + 1),
58        }
59    }
60
61    fn expression(&mut self, expression: &Expr, depth: usize) -> Result<Value> {
62        self.admit(depth)?;
63        match &expression.kind {
64            ExprKind::Value(value) => self.value(value, depth + 1),
65            ExprKind::Arithmetic { left, op, right } => {
66                let left = self.expression(left, depth + 1)?;
67                let right = self.expression(right, depth + 1)?;
68                evaluate_numeric(left.as_ref(), *op, right.as_ref())
69            }
70            ExprKind::Negate(expression) => {
71                negate_numeric(self.expression(expression, depth + 1)?.as_ref())
72            }
73            ExprKind::Cast { expression, target } => {
74                cast_numeric(self.expression(expression, depth + 1)?.as_ref(), *target)
75            }
76            _ => Err(HawDBError::Semantic(
77                "expected a constant numeric expression".into(),
78            )),
79        }
80    }
81}