Skip to main content

cratestack_rusqlite/value/
bind.rs

1//! `SqlValue` → rusqlite binding adapter.
2//!
3//! TEXT-as-canonical storage for Uuid / DateTime / Json / Decimal. See the
4//! parent module docs for the storage-class map.
5
6use cratestack_core::Value;
7use cratestack_sql::SqlValue;
8use rusqlite::ToSql;
9use rusqlite::types::{ToSqlOutput, ValueRef};
10
11/// Adapter that lets a borrowed `SqlValue` be bound by rusqlite.
12pub struct SqlValueParam<'a>(pub &'a SqlValue);
13
14impl<'a> ToSql for SqlValueParam<'a> {
15    fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
16        use rusqlite::types::Value as RV;
17
18        let value: ToSqlOutput<'_> = match self.0 {
19            SqlValue::Bool(v) => ToSqlOutput::Owned(RV::Integer(i64::from(*v))),
20            SqlValue::Int(v) => ToSqlOutput::Owned(RV::Integer(*v)),
21            SqlValue::Float(v) => ToSqlOutput::Owned(RV::Real(*v)),
22            SqlValue::String(v) => ToSqlOutput::Borrowed(ValueRef::Text(v.as_bytes())),
23            SqlValue::Bytes(v) => ToSqlOutput::Borrowed(ValueRef::Blob(v.as_slice())),
24            SqlValue::Uuid(v) => ToSqlOutput::Owned(RV::Text(v.hyphenated().to_string())),
25            SqlValue::DateTime(v) => ToSqlOutput::Owned(RV::Text(format_datetime(v))),
26            SqlValue::Json(v) => ToSqlOutput::Owned(RV::Text(format_json(v))),
27            SqlValue::Decimal(v) => ToSqlOutput::Owned(RV::Text(format_decimal(v))),
28            SqlValue::NullBool
29            | SqlValue::NullInt
30            | SqlValue::NullFloat
31            | SqlValue::NullString
32            | SqlValue::NullBytes
33            | SqlValue::NullUuid
34            | SqlValue::NullDateTime
35            | SqlValue::NullJson
36            | SqlValue::NullDecimal => ToSqlOutput::Owned(RV::Null),
37            // `Vector(n)` is a Postgres-only (`pgvector`) capability —
38            // `include_embedded_schema!` rejects it unconditionally at
39            // macro-expansion time (see
40            // `cratestack-macros/src/include/extensions.rs`), so no
41            // generated rusqlite code can ever actually produce one of
42            // these. A structured conversion failure rather than a
43            // panic, since `ToSql::to_sql` already has a `Result` to
44            // report through.
45            SqlValue::Vector(_) | SqlValue::NullVector => {
46                return Err(rusqlite::Error::ToSqlConversionFailure(
47                    "Vector(n) fields are not supported by the embedded (rusqlite) backend".into(),
48                ));
49            }
50        };
51        Ok(value)
52    }
53}
54
55fn format_datetime(value: &chrono::DateTime<chrono::Utc>) -> String {
56    value.to_rfc3339_opts(chrono::SecondsFormat::Micros, true)
57}
58
59/// Serializes the **plain, untagged** JSON shape (cratestack#162,
60/// cratestack#395) — never `Value`'s own derived, externally-tagged
61/// `Serialize` impl, which would store `{"Map": {}}` instead of `{}`.
62fn format_json(value: &Value) -> String {
63    serde_json::to_string(&value.to_plain_json()).unwrap_or_else(|_| "null".to_string())
64}
65
66fn format_decimal(value: &cratestack_core::Decimal) -> String {
67    value.to_string()
68}