Skip to main content

alibi_sqlx/
additional_fields.rs

1//! Raw configured values staged on actual model columns.
2
3use crate::pool::Exec;
4use crate::sql::Sql;
5use crate::value::{ColumnKind, SqlValue};
6use alibi_core::store::adapter::RawFieldValue;
7use alibi_core::{AuthError, AuthResult, utils::json::JsValue};
8
9/// Called by generated model bindings before any backend affinity conversion.
10///
11/// # Errors
12///
13/// Returns an error if an object or array cannot be serialized as JSON.
14pub fn raw_value(value: &JsValue) -> AuthResult<SqlValue> {
15    Ok(match RawFieldValue::from_js(value)? {
16        RawFieldValue::Null => SqlValue::Text(None),
17        RawFieldValue::Bool(value) => SqlValue::Bool(Some(value)),
18        RawFieldValue::Integer(value) => SqlValue::BigInt(Some(value)),
19        RawFieldValue::Number(value) => SqlValue::Double(Some(value)),
20        RawFieldValue::Text(value) => SqlValue::Text(Some(value)),
21        RawFieldValue::Json(value) => SqlValue::Json(Some(Box::new(value))),
22    })
23}
24
25pub(crate) async fn prepare_string_value(
26    exec: Exec<'_>,
27    value: SqlValue,
28) -> AuthResult<Option<String>> {
29    if let SqlValue::Text(value) | SqlValue::BpChar(value) = value {
30        return Ok(value);
31    }
32    if matches!(&value, SqlValue::Json(_)) {
33        return Err(AuthError::internal(
34            "object cannot bind to a scalar session field",
35        ));
36    }
37    let mut sql = Sql::with(exec.engine(), "SELECT CAST(");
38    sql.bind(value);
39    sql.push(" AS TEXT) AS value");
40    exec.fetch_scalar::<Option<String>>(sql)
41        .await?
42        .ok_or_else(|| AuthError::internal("session TEXT affinity returned no row"))
43}
44
45#[expect(
46    clippy::as_conversions,
47    clippy::cast_possible_truncation,
48    clippy::cast_precision_loss,
49    reason = "JavaScript-compatible numbers deliberately retain IEEE754 rounding and guarded integer coercion at the wire or adapter boundary"
50)]
51pub(crate) async fn prepare_value(
52    exec: Exec<'_>,
53    kind: ColumnKind,
54    value: SqlValue,
55) -> AuthResult<SqlValue> {
56    let backend = exec.engine();
57    match kind {
58        ColumnKind::Text | ColumnKind::BpChar => {
59            Ok(SqlValue::Text(prepare_string_value(exec, value).await?))
60        }
61        ColumnKind::Json => {
62            let json = match value {
63                SqlValue::Bool(Some(value)) => serde_json::json!(value),
64                SqlValue::BigInt(Some(value)) => serde_json::json!(value),
65                SqlValue::Double(Some(value)) => serde_json::json!(value),
66                SqlValue::Text(Some(value)) => serde_json::json!(value),
67                SqlValue::Json(Some(value)) => *value,
68                SqlValue::Bool(None)
69                | SqlValue::Int(_)
70                | SqlValue::BigInt(None)
71                | SqlValue::Float(_)
72                | SqlValue::Double(None)
73                | SqlValue::Text(None)
74                | SqlValue::BpChar(_)
75                | SqlValue::Bytes(_)
76                | SqlValue::Json(None)
77                | SqlValue::Timestamp(_)
78                | SqlValue::NaiveTimestamp(_)
79                | SqlValue::Uuid(_) => serde_json::Value::Null,
80            };
81            Ok(crate::value::SqlxValue::into_sql_value(
82                crate::JsonMetadata::for_backend(json, backend)?,
83            ))
84        }
85        ColumnKind::Double => Ok(match value {
86            SqlValue::BigInt(Some(value)) => SqlValue::Double(Some(value as f64)),
87            SqlValue::Text(None) => SqlValue::Double(None),
88            other => other,
89        }),
90        ColumnKind::Float => Ok(match value {
91            SqlValue::BigInt(Some(value)) => SqlValue::Float(Some(value as f32)),
92            SqlValue::Double(Some(value)) => SqlValue::Float(Some(value as f32)),
93            SqlValue::Text(None) => SqlValue::Float(None),
94            other => other,
95        }),
96        ColumnKind::Boolean => Ok(match value {
97            SqlValue::Text(None) => SqlValue::Bool(None),
98            other => other,
99        }),
100        ColumnKind::Int | ColumnKind::BigInt | ColumnKind::Other | ColumnKind::NaiveTimestamp => {
101            Ok(value)
102        }
103    }
104}