use nodedb_types::columnar::{FloatWidth, IntWidth};
use sqlparser::ast::{Expr, Value, ValueWithSpan};
use crate::types::ColumnInfo;
use crate::types_expr::SqlDataType;
#[derive(Debug, Clone, PartialEq)]
pub struct InferredParamType {
pub data_type: SqlDataType,
pub int_width: Option<IntWidth>,
pub float_width: Option<FloatWidth>,
}
impl InferredParamType {
pub(super) fn from_sql_type(data_type: SqlDataType) -> Self {
Self {
data_type,
int_width: None,
float_width: None,
}
}
pub(super) fn from_column(column: &ColumnInfo) -> Self {
Self {
data_type: column.data_type.clone(),
int_width: column.int_width,
float_width: column.float_width,
}
}
}
#[derive(Clone, PartialEq)]
enum Slot {
Unresolved,
Resolved(InferredParamType),
Conflicted,
}
#[derive(Default)]
pub(super) struct InferenceContext {
slots: Vec<Slot>,
}
const MAX_PLACEHOLDER_INDEX: usize = u16::MAX as usize;
impl InferenceContext {
fn slot_mut(&mut self, index_1based: usize) -> Option<&mut Slot> {
let zero_based = index_1based.checked_sub(1)?;
if zero_based >= MAX_PLACEHOLDER_INDEX {
return None;
}
if self.slots.len() <= zero_based {
self.slots.resize(zero_based + 1, Slot::Unresolved);
}
self.slots.get_mut(zero_based)
}
pub(super) fn observe(&mut self, index_1based: usize) {
let _ = self.slot_mut(index_1based);
}
pub(super) fn record(&mut self, index_1based: usize, ty: InferredParamType) {
let Some(slot) = self.slot_mut(index_1based) else {
return;
};
let next = match slot {
Slot::Unresolved => Slot::Resolved(ty),
Slot::Resolved(existing) if *existing == ty => Slot::Resolved(ty),
Slot::Resolved(_) | Slot::Conflicted => Slot::Conflicted,
};
*slot = next;
}
pub(super) fn record_if_placeholder(&mut self, expr: &Expr, ty: InferredParamType) {
if let Some(index) = placeholder_index(expr) {
self.record(index, ty);
}
}
pub(super) fn finish(self) -> Vec<Option<InferredParamType>> {
self.slots
.into_iter()
.map(|slot| match slot {
Slot::Resolved(ty) => Some(ty),
Slot::Unresolved | Slot::Conflicted => None,
})
.collect()
}
}
pub(super) fn placeholder_index(expr: &Expr) -> Option<usize> {
match expr {
Expr::Value(ValueWithSpan {
value: Value::Placeholder(body),
..
}) => parse_placeholder_body(body),
Expr::Nested(inner) => placeholder_index(inner),
_ => None,
}
}
pub(super) fn parse_placeholder_body(body: &str) -> Option<usize> {
let digits = body.strip_prefix('$')?;
let index: usize = digits.parse().ok()?;
(index > 0).then_some(index)
}