use rustc_hash::FxHashMap;
use std::collections::HashMap;
use std::sync::Arc;
use chrono::{DateTime, NaiveDate, Utc};
use radixdb_core::SmartString;
use radixdb_core::{Result, Value};
pub use radixdb_core::ParamVec;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct DecimalValue {
unscaled: i128,
precision: u8,
scale: u8,
}
impl DecimalValue {
pub fn try_new(unscaled: i128, precision: u8, scale: u8) -> Result<Self> {
Value::try_decimal(unscaled, precision, scale)?;
Ok(Self {
unscaled,
precision,
scale,
})
}
pub fn unscaled(self) -> i128 {
self.unscaled
}
pub fn precision(self) -> u8 {
self.precision
}
pub fn scale(self) -> u8 {
self.scale
}
}
pub trait ToParam {
fn to_param(&self) -> Value;
}
impl ToParam for i64 {
fn to_param(&self) -> Value {
Value::Integer(*self)
}
}
impl ToParam for i32 {
fn to_param(&self) -> Value {
Value::Integer(*self as i64)
}
}
impl ToParam for i16 {
fn to_param(&self) -> Value {
Value::Integer(*self as i64)
}
}
impl ToParam for i8 {
fn to_param(&self) -> Value {
Value::Integer(*self as i64)
}
}
impl ToParam for u32 {
fn to_param(&self) -> Value {
Value::Integer(*self as i64)
}
}
impl ToParam for u16 {
fn to_param(&self) -> Value {
Value::Integer(*self as i64)
}
}
impl ToParam for u8 {
fn to_param(&self) -> Value {
Value::Integer(*self as i64)
}
}
impl ToParam for f64 {
fn to_param(&self) -> Value {
Value::Float(*self)
}
}
impl ToParam for f32 {
fn to_param(&self) -> Value {
Value::Float(*self as f64)
}
}
impl ToParam for bool {
fn to_param(&self) -> Value {
Value::Boolean(*self)
}
}
impl ToParam for String {
fn to_param(&self) -> Value {
Value::Text(SmartString::new(self))
}
}
impl ToParam for &str {
fn to_param(&self) -> Value {
Value::Text(SmartString::from(*self))
}
}
impl ToParam for Arc<str> {
fn to_param(&self) -> Value {
Value::Text(SmartString::from(Arc::clone(self)))
}
}
impl ToParam for DateTime<Utc> {
fn to_param(&self) -> Value {
Value::Timestamp(*self)
}
}
impl ToParam for NaiveDate {
fn to_param(&self) -> Value {
let epoch = NaiveDate::from_ymd_opt(1970, 1, 1).expect("valid Unix epoch date");
let days = self.signed_duration_since(epoch).num_days();
Value::date(i32::try_from(days).expect("chrono date range fits SQL DATE domain"))
}
}
impl ToParam for Vec<u8> {
fn to_param(&self) -> Value {
Value::bytes(self.clone())
}
}
impl ToParam for Vec<f32> {
fn to_param(&self) -> Value {
Value::vector(self.clone())
}
}
impl ToParam for uuid::Uuid {
fn to_param(&self) -> Value {
Value::uuid(*self.as_bytes())
}
}
impl ToParam for serde_json::Value {
fn to_param(&self) -> Value {
Value::try_json(self.to_string()).expect("serialized serde_json::Value is valid JSON")
}
}
impl ToParam for DecimalValue {
fn to_param(&self) -> Value {
Value::try_decimal(self.unscaled, self.precision, self.scale)
.expect("DecimalValue validates its invariant at construction")
}
}
impl ToParam for Value {
fn to_param(&self) -> Value {
self.clone()
}
}
impl<T: ToParam> ToParam for Option<T> {
fn to_param(&self) -> Value {
match self {
Some(v) => v.to_param(),
None => Value::null_unknown(),
}
}
}
impl<T: ToParam> ToParam for &T {
fn to_param(&self) -> Value {
(*self).to_param()
}
}
pub trait Params {
fn into_params(self) -> ParamVec;
}
impl Params for () {
fn into_params(self) -> ParamVec {
ParamVec::new()
}
}
impl Params for &[Value] {
fn into_params(self) -> ParamVec {
self.iter().cloned().collect()
}
}
impl Params for Vec<Value> {
fn into_params(self) -> ParamVec {
self.into_iter().collect()
}
}
impl Params for ParamVec {
fn into_params(self) -> ParamVec {
self
}
}
impl<const N: usize> Params for [Value; N] {
fn into_params(self) -> ParamVec {
self.into_iter().collect()
}
}
macro_rules! impl_params_for_tuple {
($($idx:tt: $T:ident),+) => {
impl<$($T: ToParam),+> Params for ($($T,)+) {
fn into_params(self) -> ParamVec {
smallvec::smallvec![$(self.$idx.to_param()),+]
}
}
};
}
impl_params_for_tuple!(0: T0);
impl_params_for_tuple!(0: T0, 1: T1);
impl_params_for_tuple!(0: T0, 1: T1, 2: T2);
impl_params_for_tuple!(0: T0, 1: T1, 2: T2, 3: T3);
impl_params_for_tuple!(0: T0, 1: T1, 2: T2, 3: T3, 4: T4);
impl_params_for_tuple!(0: T0, 1: T1, 2: T2, 3: T3, 4: T4, 5: T5);
impl_params_for_tuple!(0: T0, 1: T1, 2: T2, 3: T3, 4: T4, 5: T5, 6: T6);
impl_params_for_tuple!(0: T0, 1: T1, 2: T2, 3: T3, 4: T4, 5: T5, 6: T6, 7: T7);
impl_params_for_tuple!(0: T0, 1: T1, 2: T2, 3: T3, 4: T4, 5: T5, 6: T6, 7: T7, 8: T8);
impl_params_for_tuple!(0: T0, 1: T1, 2: T2, 3: T3, 4: T4, 5: T5, 6: T6, 7: T7, 8: T8, 9: T9);
impl_params_for_tuple!(0: T0, 1: T1, 2: T2, 3: T3, 4: T4, 5: T5, 6: T6, 7: T7, 8: T8, 9: T9, 10: T10);
impl_params_for_tuple!(0: T0, 1: T1, 2: T2, 3: T3, 4: T4, 5: T5, 6: T6, 7: T7, 8: T8, 9: T9, 10: T10, 11: T11);
#[macro_export]
macro_rules! params {
() => {
$crate::ParamVec::new()
};
($($param:expr),+ $(,)?) => {
{
let mut params = $crate::ParamVec::new();
$(params.push($crate::ToParam::to_param(&$param));)+
params
}
};
}
#[derive(Debug, Clone, Default)]
pub struct NamedParams {
params: FxHashMap<String, Value>,
}
impl NamedParams {
pub fn new() -> Self {
Self {
params: FxHashMap::default(),
}
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
params: FxHashMap::with_capacity_and_hasher(capacity, Default::default()),
}
}
pub fn add<T: ToParam>(mut self, name: impl Into<String>, value: T) -> Self {
self.params.insert(name.into(), value.to_param());
self
}
pub fn insert<T: ToParam>(&mut self, name: impl Into<String>, value: T) {
self.params.insert(name.into(), value.to_param());
}
pub fn into_inner(self) -> FxHashMap<String, Value> {
self.params
}
pub fn as_map(&self) -> &FxHashMap<String, Value> {
&self.params
}
}
impl From<HashMap<String, Value>> for NamedParams {
fn from(params: HashMap<String, Value>) -> Self {
Self {
params: params.into_iter().collect(),
}
}
}
#[macro_export]
macro_rules! named_params {
() => {
$crate::NamedParams::new()
};
($($name:ident : $value:expr),+ $(,)?) => {
{
let mut params = $crate::NamedParams::new();
$(
params.insert(stringify!($name), $value);
)+
params
}
};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_to_param_integers() {
assert_eq!(42i64.to_param(), Value::Integer(42));
assert_eq!(42i32.to_param(), Value::Integer(42));
assert_eq!(42i16.to_param(), Value::Integer(42));
assert_eq!(42i8.to_param(), Value::Integer(42));
assert_eq!(42u32.to_param(), Value::Integer(42));
assert_eq!(42u16.to_param(), Value::Integer(42));
assert_eq!(42u8.to_param(), Value::Integer(42));
}
#[test]
fn test_to_param_floats() {
assert_eq!(3.5f64.to_param(), Value::Float(3.5));
assert_eq!(3.5f32.to_param(), Value::Float(3.5f32 as f64));
}
#[test]
fn test_to_param_strings() {
assert_eq!("hello".to_param(), Value::text("hello"));
assert_eq!(String::from("world").to_param(), Value::text("world"));
}
#[test]
fn test_arc_str_boundary_preserves_allocation() {
let arc: Arc<str> = Arc::from("a heap string longer than the inline boundary");
let ptr = arc.as_ptr();
let smart = SmartString::from(Arc::clone(&arc));
assert_eq!(smart.as_str().as_ptr(), ptr);
assert_eq!(Arc::strong_count(&arc), 2);
drop(smart);
let value = Value::text_arc(Arc::clone(&arc));
let Value::Text(text) = &value else {
panic!("text_arc must produce Value::Text");
};
assert_eq!(text.as_str().as_ptr(), ptr);
assert_eq!(Arc::strong_count(&arc), 2);
drop(value);
let param = arc.to_param();
let Value::Text(text) = ¶m else {
panic!("Arc<str>::to_param must produce Value::Text");
};
assert_eq!(text.as_str().as_ptr(), ptr);
assert_eq!(Arc::strong_count(&arc), 2);
}
#[test]
fn test_to_param_bool() {
assert_eq!(true.to_param(), Value::Boolean(true));
assert_eq!(false.to_param(), Value::Boolean(false));
}
#[test]
fn test_to_param_option() {
assert_eq!(Some(42i64).to_param(), Value::Integer(42));
assert!(Option::<i64>::None.to_param().is_null());
}
#[test]
fn test_params_empty() {
let params: ParamVec = ().into_params();
assert!(params.is_empty());
}
#[test]
fn test_params_tuple() {
let params = (1i64, "hello", 3.5f64).into_params();
assert_eq!(params.len(), 3);
assert_eq!(params[0], Value::Integer(1));
assert_eq!(params[1], Value::text("hello"));
assert_eq!(params[2], Value::Float(3.5));
}
#[test]
fn test_params_macro() {
let p = params![1, "hello", 3.5];
let params = p.into_params();
assert_eq!(params.len(), 3);
assert_eq!(params[0], Value::Integer(1));
assert_eq!(params[1], Value::text("hello"));
assert_eq!(params[2], Value::Float(3.5));
}
#[test]
fn test_params_macro_empty() {
let p = params![];
let params: ParamVec = p.into_params();
assert!(params.is_empty());
}
#[test]
fn test_params_with_option() {
let name: Option<&str> = Some("Alice");
let age: Option<i32> = None;
let params = (1i64, name, age).into_params();
assert_eq!(params.len(), 3);
assert_eq!(params[0], Value::Integer(1));
assert_eq!(params[1], Value::text("Alice"));
assert!(params[2].is_null());
}
#[test]
fn test_params_from_param_vec() {
let mut pv = ParamVec::new();
pv.push(Value::Integer(1));
pv.push(Value::text("hello"));
pv.push(Value::Float(3.5));
let result = pv.into_params();
assert_eq!(result.len(), 3);
assert_eq!(result[0], Value::Integer(1));
assert_eq!(result[1], Value::text("hello"));
assert_eq!(result[2], Value::Float(3.5));
}
#[test]
fn test_params_from_empty_param_vec() {
let pv = ParamVec::new();
let result = pv.into_params();
assert!(result.is_empty());
}
}