use sqlparser::ast::{Array, DataType, Expr, Interval, TypedString, Value, ValueWithSpan};
use crate::error::{Result, SqlError};
use crate::resolver::ColumnScope;
use crate::resolver::expr::value::{convert_value, parse_interval_to_micros};
use crate::types::*;
use super::entry::convert_expr_depth;
pub(super) fn convert_value_expr(val: &ValueWithSpan) -> Result<SqlExpr> {
Ok(SqlExpr::Literal(convert_value(&val.value)?))
}
pub(super) fn convert_typed_string(ts: &TypedString) -> Result<SqlExpr> {
let type_str = format!("{}", ts.data_type).to_ascii_uppercase();
let raw = match &ts.value.value {
Value::SingleQuotedString(s) => s.clone(),
other => {
return Err(SqlError::Unsupported {
detail: format!("typed string value: {other}"),
});
}
};
match type_str.as_str() {
"TIMESTAMP" => {
let dt = nodedb_types::NdbDateTime::parse(&raw).ok_or_else(|| SqlError::Parse {
detail: format!("cannot parse TIMESTAMP literal: '{raw}'"),
})?;
return Ok(SqlExpr::Literal(SqlValue::Timestamp(dt)));
}
"TIMESTAMPTZ" | "TIMESTAMP WITH TIME ZONE" => {
let dt = nodedb_types::NdbDateTime::parse(&raw).ok_or_else(|| SqlError::Parse {
detail: format!("cannot parse TIMESTAMPTZ literal: '{raw}'"),
})?;
return Ok(SqlExpr::Literal(SqlValue::Timestamptz(dt)));
}
_ => {}
}
Ok(SqlExpr::Literal(SqlValue::String(raw)))
}
pub(super) fn convert_cast(
expr: &Expr,
data_type: &DataType,
depth: &mut usize,
scope: &ColumnScope<'_>,
) -> Result<SqlExpr> {
let type_str = format!("{data_type}").to_ascii_lowercase();
if type_str == "tsvector" || type_str == "tsquery" {
return convert_expr_depth(expr, depth, scope);
}
let upper = type_str.to_uppercase();
if (upper == "TIMESTAMP" || upper == "TIMESTAMPTZ" || upper == "TIMESTAMP WITH TIME ZONE")
&& let Expr::Value(v) = expr
&& let Value::SingleQuotedString(s) = &v.value
{
let dt = nodedb_types::NdbDateTime::parse(s).ok_or_else(|| SqlError::Parse {
detail: format!("cannot parse timestamp cast: '{s}'"),
})?;
return Ok(SqlExpr::Literal(if upper == "TIMESTAMP" {
SqlValue::Timestamp(dt)
} else {
SqlValue::Timestamptz(dt)
}));
}
Ok(SqlExpr::Cast {
expr: Box::new(convert_expr_depth(expr, depth, scope)?),
to_type: format!("{data_type}"),
})
}
pub(super) fn convert_array(
array: &Array,
depth: &mut usize,
scope: &ColumnScope<'_>,
) -> Result<SqlExpr> {
let elems = array
.elem
.iter()
.map(|e| convert_expr_depth(e, depth, scope))
.collect::<Result<_>>()?;
Ok(SqlExpr::ArrayLiteral(elems))
}
pub(super) fn convert_wildcard() -> Result<SqlExpr> {
Ok(SqlExpr::Wildcard)
}
pub(super) fn convert_interval(interval: &Interval) -> Result<SqlExpr> {
let interval_str = match interval.value.as_ref() {
Expr::Value(v) => match &v.value {
Value::SingleQuotedString(s) => s.clone(),
Value::Number(n, _) => {
if let Some(ref field) = interval.leading_field {
format!("{n} {field}")
} else {
n.clone()
}
}
_ => {
return Err(SqlError::Unsupported {
detail: format!("INTERVAL value: {}", interval.value),
});
}
},
_ => {
return Err(SqlError::Unsupported {
detail: format!("INTERVAL expression: {}", interval.value),
});
}
};
let full_str = if interval_str.chars().all(|c| c.is_ascii_digit())
&& let Some(ref field) = interval.leading_field
{
format!("{interval_str} {field}")
} else {
interval_str
};
let micros = parse_interval_to_micros(&full_str).ok_or_else(|| SqlError::Parse {
detail: format!("cannot parse INTERVAL '{full_str}'"),
})?;
Ok(SqlExpr::Literal(SqlValue::Int(micros)))
}
#[cfg(test)]
mod tests {
use sqlparser::ast::{Expr, Value};
use crate::error::SqlError;
use crate::resolver::expr::convert::entry::tests::{first_select_expr, select_expr_lowered};
use crate::resolver::expr::value::convert_value;
use crate::types::*;
#[test]
fn single_quoted_is_string_literal() {
let expr = first_select_expr("SELECT 'userId' FROM users");
match &expr {
Expr::Value(v) => match &v.value {
Value::SingleQuotedString(s) => assert_eq!(s, "userId"),
other => panic!("expected SingleQuotedString, got {other:?}"),
},
other => panic!("expected Value, got {other:?}"),
}
let Expr::Value(v) = expr else { unreachable!() };
assert!(matches!(
convert_value(&v.value),
Ok(SqlValue::String(s)) if s == "userId"
));
}
#[test]
fn double_quoted_string_value_unsupported() {
let val = Value::DoubleQuotedString("userId".into());
assert!(
matches!(convert_value(&val), Err(SqlError::Unsupported { .. })),
"DoubleQuotedString should be Unsupported, not silently accepted"
);
}
#[test]
fn tsvector_cast_elided() {
let expr = select_expr_lowered("SELECT 'foo'::tsvector FROM t");
assert!(
matches!(expr, SqlExpr::Literal(SqlValue::String(ref s)) if s == "foo"),
"expected Literal(\"foo\"), got {expr:?}"
);
}
#[test]
fn tsquery_cast_elided() {
let expr = select_expr_lowered("SELECT 'rust'::tsquery FROM t");
assert!(
matches!(expr, SqlExpr::Literal(SqlValue::String(ref s)) if s == "rust"),
"expected Literal(\"rust\"), got {expr:?}"
);
}
}