Skip to content

Commit 0933c5c

Browse files
committed
split out the sql to json code
1 parent 77733d4 commit 0933c5c

File tree

2 files changed

+137
-133
lines changed

2 files changed

+137
-133
lines changed

src/webserver/database/mod.rs

Lines changed: 5 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
mod sql;
22
mod sql_pseudofunctions;
3+
mod sql_to_json;
34

45
use anyhow::{anyhow, Context};
56
use futures_util::stream::Stream;
67
use futures_util::StreamExt;
7-
use serde_json::{Map, Value};
8+
use serde_json::Value;
89
use std::borrow::Cow;
910
use std::fmt::{Display, Formatter};
1011
use std::path::Path;
1112
use std::time::Duration;
1213

1314
use crate::app_config::AppConfig;
1415
pub use crate::file_cache::FileCache;
15-
use crate::utils::add_value_to_map;
16+
1617
use crate::webserver::database::sql_pseudofunctions::extract_req_param;
1718
use crate::webserver::http::RequestInfo;
1819
use crate::MIGRATIONS_DIR;
@@ -24,10 +25,7 @@ use sqlx::any::{
2425
use sqlx::migrate::Migrator;
2526
use sqlx::pool::{PoolConnection, PoolOptions};
2627
use sqlx::query::Query;
27-
use sqlx::{
28-
Any, AnyConnection, AnyPool, Arguments, Column, ConnectOptions, Decode, Either, Executor, Row,
29-
Statement, TypeInfo, ValueRef,
30-
};
28+
use sqlx::{Any, AnyConnection, AnyPool, Arguments, ConnectOptions, Either, Executor, Statement};
3129

3230
use self::sql::ParsedSQLStatement;
3331

@@ -160,7 +158,7 @@ async fn take_connection<'a, 'b>(
160158
#[inline]
161159
fn parse_single_sql_result(res: sqlx::Result<Either<AnyQueryResult, AnyRow>>) -> DbItem {
162160
match res {
163-
Ok(Either::Right(r)) => DbItem::Row(row_to_json(&r)),
161+
Ok(Either::Right(r)) => DbItem::Row(sql_to_json::row_to_json(&r)),
164162
Ok(Either::Left(res)) => {
165163
log::debug!("Finished query with result: {:?}", res);
166164
DbItem::FinishedQuery
@@ -202,105 +200,6 @@ pub enum DbItem {
202200
Error(anyhow::Error),
203201
}
204202

205-
macro_rules! try_decode_with {
206-
($raw_value:expr, [$ty0:ty], $fn:expr) => {
207-
<$ty0 as Decode<sqlx::any::Any>>::decode($raw_value).map($fn)
208-
};
209-
($raw_value:expr, [$ty0:ty, $($ty:ty),+], $fn:expr) => {
210-
match try_decode_with!($raw_value, [$ty0], $fn) {
211-
Ok(value) => Ok(value),
212-
Err(_) => try_decode_with!($raw_value, [$($ty),+], $fn),
213-
}
214-
};
215-
}
216-
217-
fn row_to_json(row: &AnyRow) -> Value {
218-
use Value::Object;
219-
220-
let columns = row.columns();
221-
let mut map = Map::new();
222-
for col in columns {
223-
let key = col.name().to_string();
224-
let value: Value = sql_to_json(row, col);
225-
map = add_value_to_map(map, (key, value));
226-
}
227-
Object(map)
228-
}
229-
230-
fn sql_to_json(row: &AnyRow, col: &sqlx::any::AnyColumn) -> Value {
231-
let raw_value_result = row.try_get_raw(col.ordinal());
232-
match raw_value_result {
233-
Ok(raw_value) if !raw_value.is_null() => {
234-
let mut raw_value = Some(raw_value);
235-
log::trace!("Decoding a value of type {:?}", col.type_info().name());
236-
let decoded = sql_nonnull_to_json(|| {
237-
raw_value
238-
.take()
239-
.unwrap_or_else(|| row.try_get_raw(col.ordinal()).unwrap())
240-
});
241-
log::trace!("Decoded value: {:?}", decoded);
242-
decoded
243-
}
244-
Ok(_null) => Value::Null,
245-
Err(e) => {
246-
log::warn!("Unable to extract value from row: {:?}", e);
247-
Value::Null
248-
}
249-
}
250-
}
251-
252-
fn sql_nonnull_to_json<'r>(mut get_ref: impl FnMut() -> sqlx::any::AnyValueRef<'r>) -> Value {
253-
let raw_value = get_ref();
254-
match raw_value.type_info().name() {
255-
"REAL" | "FLOAT" | "NUMERIC" | "DECIMAL" | "FLOAT4" | "FLOAT8" | "DOUBLE" => {
256-
<f64 as Decode<sqlx::any::Any>>::decode(raw_value)
257-
.unwrap_or(f64::NAN)
258-
.into()
259-
}
260-
"INT8" | "BIGINT" | "INTEGER" => <i64 as Decode<sqlx::any::Any>>::decode(raw_value)
261-
.unwrap_or_default()
262-
.into(),
263-
"INT" | "INT4" => <i32 as Decode<sqlx::any::Any>>::decode(raw_value)
264-
.unwrap_or_default()
265-
.into(),
266-
"INT2" | "SMALLINT" => <i16 as Decode<sqlx::any::Any>>::decode(raw_value)
267-
.unwrap_or_default()
268-
.into(),
269-
"BOOL" | "BOOLEAN" => <bool as Decode<sqlx::any::Any>>::decode(raw_value)
270-
.unwrap_or_default()
271-
.into(),
272-
"DATE" => <chrono::NaiveDate as Decode<sqlx::any::Any>>::decode(raw_value)
273-
.as_ref()
274-
.map_or_else(std::string::ToString::to_string, ToString::to_string)
275-
.into(),
276-
"TIME" => <chrono::NaiveTime as Decode<sqlx::any::Any>>::decode(raw_value)
277-
.as_ref()
278-
.map_or_else(ToString::to_string, ToString::to_string)
279-
.into(),
280-
"DATETIME" | "DATETIME2" | "DATETIMEOFFSET" | "TIMESTAMP" | "TIMESTAMPTZ" => {
281-
try_decode_with!(
282-
get_ref(),
283-
[chrono::NaiveDateTime, chrono::DateTime<chrono::Utc>],
284-
|v| dbg!(v).to_string()
285-
)
286-
.unwrap_or_else(|e| format!("Unable to decode date: {e:?}"))
287-
.into()
288-
}
289-
"JSON" | "JSON[]" | "JSONB" | "JSONB[]" => {
290-
<&[u8] as Decode<sqlx::any::Any>>::decode(raw_value)
291-
.and_then(|rv| {
292-
serde_json::from_slice::<Value>(rv)
293-
.map_err(|e| Box::new(e) as Box<dyn std::error::Error + Sync + Send>)
294-
})
295-
.unwrap_or_default()
296-
}
297-
// Deserialize as a string by default
298-
_ => <String as Decode<sqlx::any::Any>>::decode(raw_value)
299-
.unwrap_or_default()
300-
.into(),
301-
}
302-
}
303-
304203
impl Database {
305204
pub async fn init(config: &AppConfig) -> anyhow::Result<Self> {
306205
let database_url = &config.database_url;
@@ -380,30 +279,3 @@ impl Display for PreparedStatement {
380279
write!(f, "{}", self.statement.sql())
381280
}
382281
}
383-
384-
#[actix_web::test]
385-
async fn test_row_to_json() -> anyhow::Result<()> {
386-
use sqlx::Connection;
387-
let mut c = sqlx::AnyConnection::connect("sqlite://:memory:").await?;
388-
let row = sqlx::query(
389-
"SELECT \
390-
123.456 as one_value, \
391-
1 as two_values, \
392-
2 as two_values, \
393-
'x' as three_values, \
394-
'y' as three_values, \
395-
'z' as three_values \
396-
",
397-
)
398-
.fetch_one(&mut c)
399-
.await?;
400-
assert_eq!(
401-
row_to_json(&row),
402-
serde_json::json!({
403-
"one_value": 123.456,
404-
"two_values": [1,2],
405-
"three_values": ["x","y","z"],
406-
})
407-
);
408-
Ok(())
409-
}

src/webserver/database/sql_to_json.rs

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
pub use crate::file_cache::FileCache;
2+
use crate::utils::add_value_to_map;
3+
use serde_json::{self, Map, Value};
4+
use sqlx::any::AnyRow;
5+
use sqlx::Decode;
6+
use sqlx::{Column, Row, TypeInfo, ValueRef};
7+
8+
pub fn row_to_json(row: &AnyRow) -> Value {
9+
use Value::Object;
10+
11+
let columns = row.columns();
12+
let mut map = Map::new();
13+
for col in columns {
14+
let key = col.name().to_string();
15+
let value: Value = sql_to_json(row, col);
16+
map = add_value_to_map(map, (key, value));
17+
}
18+
Object(map)
19+
}
20+
21+
pub fn sql_to_json(row: &AnyRow, col: &sqlx::any::AnyColumn) -> Value {
22+
let raw_value_result = row.try_get_raw(col.ordinal());
23+
match raw_value_result {
24+
Ok(raw_value) if !raw_value.is_null() => {
25+
let mut raw_value = Some(raw_value);
26+
log::trace!("Decoding a value of type {:?}", col.type_info().name());
27+
let decoded = sql_nonnull_to_json(|| {
28+
raw_value
29+
.take()
30+
.unwrap_or_else(|| row.try_get_raw(col.ordinal()).unwrap())
31+
});
32+
log::trace!("Decoded value: {:?}", decoded);
33+
decoded
34+
}
35+
Ok(_null) => Value::Null,
36+
Err(e) => {
37+
log::warn!("Unable to extract value from row: {:?}", e);
38+
Value::Null
39+
}
40+
}
41+
}
42+
43+
macro_rules! try_decode_with {
44+
($raw_value:expr, [$ty0:ty], $fn:expr) => {
45+
<$ty0 as Decode<sqlx::any::Any>>::decode($raw_value).map($fn)
46+
};
47+
($raw_value:expr, [$ty0:ty, $($ty:ty),+], $fn:expr) => {
48+
match try_decode_with!($raw_value, [$ty0], $fn) {
49+
Ok(value) => Ok(value),
50+
Err(_) => try_decode_with!($raw_value, [$($ty),+], $fn),
51+
}
52+
};
53+
}
54+
55+
pub fn sql_nonnull_to_json<'r>(mut get_ref: impl FnMut() -> sqlx::any::AnyValueRef<'r>) -> Value {
56+
let raw_value = get_ref();
57+
match raw_value.type_info().name() {
58+
"REAL" | "FLOAT" | "NUMERIC" | "DECIMAL" | "FLOAT4" | "FLOAT8" | "DOUBLE" => {
59+
<f64 as Decode<sqlx::any::Any>>::decode(raw_value)
60+
.unwrap_or(f64::NAN)
61+
.into()
62+
}
63+
"INT8" | "BIGINT" | "INTEGER" => <i64 as Decode<sqlx::any::Any>>::decode(raw_value)
64+
.unwrap_or_default()
65+
.into(),
66+
"INT" | "INT4" => <i32 as Decode<sqlx::any::Any>>::decode(raw_value)
67+
.unwrap_or_default()
68+
.into(),
69+
"INT2" | "SMALLINT" => <i16 as Decode<sqlx::any::Any>>::decode(raw_value)
70+
.unwrap_or_default()
71+
.into(),
72+
"BOOL" | "BOOLEAN" => <bool as Decode<sqlx::any::Any>>::decode(raw_value)
73+
.unwrap_or_default()
74+
.into(),
75+
"DATE" => <chrono::NaiveDate as Decode<sqlx::any::Any>>::decode(raw_value)
76+
.as_ref()
77+
.map_or_else(std::string::ToString::to_string, ToString::to_string)
78+
.into(),
79+
"TIME" => <chrono::NaiveTime as Decode<sqlx::any::Any>>::decode(raw_value)
80+
.as_ref()
81+
.map_or_else(ToString::to_string, ToString::to_string)
82+
.into(),
83+
"DATETIME" | "DATETIME2" | "DATETIMEOFFSET" | "TIMESTAMP" | "TIMESTAMPTZ" => {
84+
try_decode_with!(
85+
get_ref(),
86+
[chrono::NaiveDateTime, chrono::DateTime<chrono::Utc>],
87+
|v| dbg!(v).to_string()
88+
)
89+
.unwrap_or_else(|e| format!("Unable to decode date: {e:?}"))
90+
.into()
91+
}
92+
"JSON" | "JSON[]" | "JSONB" | "JSONB[]" => {
93+
<&[u8] as Decode<sqlx::any::Any>>::decode(raw_value)
94+
.and_then(|rv| {
95+
serde_json::from_slice::<Value>(rv)
96+
.map_err(|e| Box::new(e) as Box<dyn std::error::Error + Sync + Send>)
97+
})
98+
.unwrap_or_default()
99+
}
100+
// Deserialize as a string by default
101+
_ => <String as Decode<sqlx::any::Any>>::decode(raw_value)
102+
.unwrap_or_default()
103+
.into(),
104+
}
105+
}
106+
107+
#[actix_web::test]
108+
async fn test_row_to_json() -> anyhow::Result<()> {
109+
use sqlx::Connection;
110+
let mut c = sqlx::AnyConnection::connect("sqlite://:memory:").await?;
111+
let row = sqlx::query(
112+
"SELECT \
113+
123.456 as one_value, \
114+
1 as two_values, \
115+
2 as two_values, \
116+
'x' as three_values, \
117+
'y' as three_values, \
118+
'z' as three_values \
119+
",
120+
)
121+
.fetch_one(&mut c)
122+
.await?;
123+
assert_eq!(
124+
row_to_json(&row),
125+
serde_json::json!({
126+
"one_value": 123.456,
127+
"two_values": [1,2],
128+
"three_values": ["x","y","z"],
129+
})
130+
);
131+
Ok(())
132+
}

0 commit comments

Comments
 (0)