1use serde_json::Value as Json;
21use sqlx::any::AnyTypeInfoKind;
22use sqlx::{Column, Row, ValueRef};
23use sqlx_core::any::AnyValueKind;
24
25pub fn row_to_json(row: &sqlx::any::AnyRow) -> Json {
30 let mut map = serde_json::Map::with_capacity(row.columns().len());
31 for (i, col) in row.columns().iter().enumerate() {
32 let value = match row.try_get_raw(i) {
33 Ok(raw) => any_value_kind_to_json(ValueRef::to_owned(&raw).kind),
34 Err(_) => Json::Null,
35 };
36 map.insert(col.name().to_string(), value);
37 }
38 Json::Object(map)
39}
40
41fn any_value_kind_to_json(kind: AnyValueKind<'static>) -> Json {
42 match kind {
43 AnyValueKind::Null(_) => Json::Null,
44 AnyValueKind::Bool(b) => Json::Bool(b),
45 AnyValueKind::SmallInt(n) => Json::from(n),
46 AnyValueKind::Integer(n) => Json::from(n),
47 AnyValueKind::BigInt(n) => Json::from(n),
48 AnyValueKind::Real(f) => Json::from(f),
54 AnyValueKind::Double(f) => Json::from(f),
55 AnyValueKind::Text(s) => Json::String(s.into_owned()),
56 AnyValueKind::Blob(b) => Json::String(format!("<blob, {} bytes>", b.len())),
57 other => {
61 tracing::warn!(
62 ?other,
63 "unrecognized AnyValueKind variant; rendering as null"
64 );
65 Json::Null
66 }
67 }
68}
69
70pub fn json_to_any_value(value: &Json, declared: AnyTypeInfoKind) -> AnyValueKind<'static> {
81 if value.is_null() {
82 return AnyValueKind::Null(declared);
83 }
84 match declared {
85 AnyTypeInfoKind::Bool => AnyValueKind::Bool(json_as_bool(value)),
86 AnyTypeInfoKind::SmallInt => AnyValueKind::SmallInt(json_as_i64(value) as i16),
87 AnyTypeInfoKind::Integer => AnyValueKind::Integer(json_as_i64(value) as i32),
88 AnyTypeInfoKind::BigInt => AnyValueKind::BigInt(json_as_i64(value)),
89 AnyTypeInfoKind::Real => AnyValueKind::Real(json_as_f64(value) as f32),
90 AnyTypeInfoKind::Double => AnyValueKind::Double(json_as_f64(value)),
91 AnyTypeInfoKind::Text | AnyTypeInfoKind::Blob | AnyTypeInfoKind::Null => {
96 AnyValueKind::Text(json_as_string(value).into())
97 }
98 }
99}
100
101fn json_as_bool(value: &Json) -> bool {
102 match value {
103 Json::Bool(b) => *b,
104 Json::Number(n) => n.as_i64().is_some_and(|n| n != 0),
105 Json::String(s) => s == "true" || s == "1",
106 _ => false,
107 }
108}
109
110fn json_as_i64(value: &Json) -> i64 {
111 match value {
112 Json::Number(n) => n.as_i64().unwrap_or(0),
113 Json::Bool(b) => i64::from(*b),
114 Json::String(s) => s.parse().unwrap_or(0),
115 _ => 0,
116 }
117}
118
119fn json_as_f64(value: &Json) -> f64 {
120 match value {
121 Json::Number(n) => n.as_f64().unwrap_or(0.0),
122 Json::String(s) => s.parse().unwrap_or(0.0),
123 _ => 0.0,
124 }
125}
126
127fn json_as_string(value: &Json) -> String {
128 match value {
129 Json::String(s) => s.clone(),
130 other => other.to_string(),
131 }
132}
133
134pub fn bind_any<'q>(
142 query: sqlx::query::Query<'q, sqlx::any::Any, sqlx::any::AnyArguments<'q>>,
143 value: AnyValueKind<'q>,
144) -> sqlx::query::Query<'q, sqlx::any::Any, sqlx::any::AnyArguments<'q>> {
145 match value {
146 AnyValueKind::Null(AnyTypeInfoKind::Bool) => query.bind(Option::<bool>::None),
147 AnyValueKind::Null(AnyTypeInfoKind::SmallInt) => query.bind(Option::<i16>::None),
148 AnyValueKind::Null(AnyTypeInfoKind::Integer) => query.bind(Option::<i32>::None),
149 AnyValueKind::Null(AnyTypeInfoKind::BigInt) => query.bind(Option::<i64>::None),
150 AnyValueKind::Null(AnyTypeInfoKind::Real) => query.bind(Option::<f32>::None),
151 AnyValueKind::Null(AnyTypeInfoKind::Double) => query.bind(Option::<f64>::None),
152 AnyValueKind::Null(AnyTypeInfoKind::Blob) => query.bind(Option::<Vec<u8>>::None),
153 AnyValueKind::Null(AnyTypeInfoKind::Text | AnyTypeInfoKind::Null) => {
154 query.bind(Option::<String>::None)
155 }
156 AnyValueKind::Bool(b) => query.bind(b),
157 AnyValueKind::SmallInt(n) => query.bind(n),
158 AnyValueKind::Integer(n) => query.bind(n),
159 AnyValueKind::BigInt(n) => query.bind(n),
160 AnyValueKind::Real(f) => query.bind(f),
161 AnyValueKind::Double(f) => query.bind(f),
162 AnyValueKind::Text(s) => query.bind(s.into_owned()),
163 AnyValueKind::Blob(b) => query.bind(b.into_owned()),
164 other => {
167 tracing::warn!(?other, "unrecognized AnyValueKind variant; binding null");
168 query.bind(Option::<String>::None)
169 }
170 }
171}
172
173#[cfg(test)]
174mod tests {
175 use super::*;
176
177 #[test]
178 fn json_to_any_value_maps_null_to_the_declared_kind() {
179 assert!(matches!(
180 json_to_any_value(&Json::Null, AnyTypeInfoKind::BigInt),
181 AnyValueKind::Null(AnyTypeInfoKind::BigInt)
182 ));
183 }
184
185 #[test]
186 fn json_to_any_value_coerces_a_bool_into_an_integer_column() {
187 assert!(matches!(
191 json_to_any_value(&Json::Bool(true), AnyTypeInfoKind::BigInt),
192 AnyValueKind::BigInt(1)
193 ));
194 assert!(matches!(
195 json_to_any_value(&Json::Bool(false), AnyTypeInfoKind::Integer),
196 AnyValueKind::Integer(0)
197 ));
198 }
199
200 #[test]
201 fn json_to_any_value_parses_a_numeric_string_for_a_numeric_column() {
202 assert!(matches!(
203 json_to_any_value(&Json::String("42".to_string()), AnyTypeInfoKind::BigInt),
204 AnyValueKind::BigInt(42)
205 ));
206 }
207
208 #[test]
209 fn json_to_any_value_stringifies_anything_for_a_text_column() {
210 match json_to_any_value(&Json::from(42), AnyTypeInfoKind::Text) {
211 AnyValueKind::Text(s) => assert_eq!(s, "42"),
212 other => panic!("expected Text, got {other:?}"),
213 }
214 }
215
216 #[test]
217 fn any_value_kind_to_json_round_trips_every_variant() {
218 assert_eq!(
219 any_value_kind_to_json(AnyValueKind::Null(AnyTypeInfoKind::Text)),
220 Json::Null
221 );
222 assert_eq!(
223 any_value_kind_to_json(AnyValueKind::Bool(true)),
224 Json::Bool(true)
225 );
226 assert_eq!(
227 any_value_kind_to_json(AnyValueKind::BigInt(7)),
228 Json::from(7)
229 );
230 assert_eq!(
231 any_value_kind_to_json(AnyValueKind::Text("hi".into())),
232 Json::String("hi".to_string())
233 );
234 assert_eq!(
235 any_value_kind_to_json(AnyValueKind::Blob(vec![1, 2, 3].into())),
236 Json::String("<blob, 3 bytes>".to_string())
237 );
238 }
239}