#[cfg(feature = "postgres")]
use sqlx::postgres::PgArguments;
#[cfg(feature = "postgres")]
use sqlx::query::Query;
#[cfg(feature = "postgres")]
use super::bind_query;
#[cfg(feature = "mysql")]
use super::bind_query_my;
#[cfg(feature = "sqlite")]
use super::bind_query_sqlite;
use super::ExecError;
use crate::core::SelectQuery;
use crate::sql::Pool;
use crate::hex::hex_encode;
#[cfg(any(feature = "postgres", feature = "mysql"))]
fn row_to_json_generic<'r, R>(
row: &'r R,
fields: &[&'static crate::core::FieldSchema],
) -> serde_json::Value
where
R: sqlx::Row,
&'r str: sqlx::ColumnIndex<R>,
i16: sqlx::Decode<'r, R::Database> + sqlx::Type<R::Database>,
i32: sqlx::Decode<'r, R::Database> + sqlx::Type<R::Database>,
i64: sqlx::Decode<'r, R::Database> + sqlx::Type<R::Database>,
f32: sqlx::Decode<'r, R::Database> + sqlx::Type<R::Database>,
f64: sqlx::Decode<'r, R::Database> + sqlx::Type<R::Database>,
bool: sqlx::Decode<'r, R::Database> + sqlx::Type<R::Database>,
String: sqlx::Decode<'r, R::Database> + sqlx::Type<R::Database>,
chrono::NaiveDate: sqlx::Decode<'r, R::Database> + sqlx::Type<R::Database>,
chrono::NaiveTime: sqlx::Decode<'r, R::Database> + sqlx::Type<R::Database>,
chrono::DateTime<chrono::Utc>: sqlx::Decode<'r, R::Database> + sqlx::Type<R::Database>,
uuid::Uuid: sqlx::Decode<'r, R::Database> + sqlx::Type<R::Database>,
serde_json::Value: sqlx::Decode<'r, R::Database> + sqlx::Type<R::Database>,
rust_decimal::Decimal: sqlx::Decode<'r, R::Database> + sqlx::Type<R::Database>,
Vec<u8>: sqlx::Decode<'r, R::Database> + sqlx::Type<R::Database>,
{
use crate::core::FieldType;
use serde_json::{json, Value};
let mut map = serde_json::Map::new();
for field in fields {
let value = match field.ty {
FieldType::I16 => row
.try_get::<i16, _>(field.column)
.map(|n| json!(n))
.unwrap_or(Value::Null),
FieldType::I32 => row
.try_get::<i32, _>(field.column)
.map(|n| json!(n))
.unwrap_or(Value::Null),
FieldType::I64 => row
.try_get::<i64, _>(field.column)
.map(|n| json!(n))
.unwrap_or(Value::Null),
FieldType::F32 => row
.try_get::<f32, _>(field.column)
.map(|n| json!(n))
.unwrap_or(Value::Null),
FieldType::F64 => row
.try_get::<f64, _>(field.column)
.map(|n| json!(n))
.unwrap_or(Value::Null),
FieldType::Bool => row
.try_get::<bool, _>(field.column)
.map(|b| json!(b))
.unwrap_or(Value::Null),
FieldType::String => row
.try_get::<String, _>(field.column)
.map(|s| json!(s))
.unwrap_or(Value::Null),
FieldType::Date => row
.try_get::<chrono::NaiveDate, _>(field.column)
.map(|d| json!(d.to_string()))
.unwrap_or(Value::Null),
FieldType::DateTime => row
.try_get::<chrono::DateTime<chrono::Utc>, _>(field.column)
.map(|dt| json!(dt.to_rfc3339()))
.unwrap_or(Value::Null),
FieldType::Uuid => row
.try_get::<uuid::Uuid, _>(field.column)
.map(|u| json!(u.to_string()))
.unwrap_or(Value::Null),
FieldType::Json => row
.try_get::<serde_json::Value, _>(field.column)
.unwrap_or(Value::Null),
FieldType::Decimal => row
.try_get::<rust_decimal::Decimal, _>(field.column)
.map(|d| json!(d.to_string()))
.unwrap_or(Value::Null),
FieldType::Binary => row
.try_get::<Vec<u8>, _>(field.column)
.map(|b| json!(hex_encode(&b)))
.unwrap_or(Value::Null),
FieldType::Time => row
.try_get::<chrono::NaiveTime, _>(field.column)
.map(|t| json!(t.to_string()))
.unwrap_or(Value::Null),
FieldType::Array(_) => Value::Null,
FieldType::Range(_) => Value::Null,
FieldType::HStore => Value::Null,
FieldType::Vector(_) => Value::Null,
FieldType::Geometry(_) => Value::Null,
};
map.insert(field.name.to_owned(), value);
}
Value::Object(map)
}
#[must_use]
#[cfg(feature = "postgres")]
pub fn row_to_json(
row: &sqlx::postgres::PgRow,
fields: &[&'static crate::core::FieldSchema],
) -> serde_json::Value {
row_to_json_generic(row, fields)
}
#[cfg(feature = "mysql")]
#[must_use]
pub fn row_to_json_my(
row: &sqlx::mysql::MySqlRow,
fields: &[&'static crate::core::FieldSchema],
) -> serde_json::Value {
row_to_json_generic(row, fields)
}
#[cfg(feature = "sqlite")]
#[must_use]
pub fn row_to_json_sqlite(
row: &sqlx::sqlite::SqliteRow,
fields: &[&'static crate::core::FieldSchema],
) -> serde_json::Value {
use crate::core::FieldType;
use serde_json::{json, Value};
use sqlx::Row as _;
let mut map = serde_json::Map::new();
for field in fields {
let value = match field.ty {
FieldType::I16 => row
.try_get::<i16, _>(field.column)
.map(|n| json!(n))
.unwrap_or(Value::Null),
FieldType::I32 => row
.try_get::<i32, _>(field.column)
.map(|n| json!(n))
.unwrap_or(Value::Null),
FieldType::I64 => row
.try_get::<i64, _>(field.column)
.map(|n| json!(n))
.unwrap_or(Value::Null),
FieldType::F32 => row
.try_get::<f32, _>(field.column)
.map(|n| json!(n))
.unwrap_or(Value::Null),
FieldType::F64 => row
.try_get::<f64, _>(field.column)
.map(|n| json!(n))
.unwrap_or(Value::Null),
FieldType::Bool => row
.try_get::<bool, _>(field.column)
.map(|b| json!(b))
.unwrap_or(Value::Null),
FieldType::String => row
.try_get::<String, _>(field.column)
.map(|s| json!(s))
.unwrap_or(Value::Null),
FieldType::Date => row
.try_get::<chrono::NaiveDate, _>(field.column)
.map(|d| json!(d.to_string()))
.unwrap_or_else(|_| {
row.try_get::<String, _>(field.column)
.map(|s| json!(s))
.unwrap_or(Value::Null)
}),
FieldType::DateTime => row
.try_get::<chrono::DateTime<chrono::Utc>, _>(field.column)
.map(|dt| json!(dt.to_rfc3339()))
.unwrap_or_else(|_| {
row.try_get::<String, _>(field.column)
.map(|s| json!(s))
.unwrap_or(Value::Null)
}),
FieldType::Uuid => row
.try_get::<String, _>(field.column)
.map(|u| json!(u))
.unwrap_or(Value::Null),
FieldType::Json => {
match row.try_get::<String, _>(field.column) {
Ok(s) => serde_json::from_str(&s).unwrap_or(Value::String(s)),
Err(_) => Value::Null,
}
}
FieldType::Decimal => {
row.try_get::<String, _>(field.column)
.map(|s| json!(s))
.or_else(|_| {
row.try_get::<f64, _>(field.column)
.map(|n| json!(n.to_string()))
})
.unwrap_or(Value::Null)
}
FieldType::Binary => row
.try_get::<Vec<u8>, _>(field.column)
.map(|b| json!(hex_encode(&b)))
.unwrap_or(Value::Null),
FieldType::Time => row
.try_get::<chrono::NaiveTime, _>(field.column)
.map(|t| json!(t.to_string()))
.unwrap_or_else(|_| {
row.try_get::<String, _>(field.column)
.map(|s| json!(s))
.unwrap_or(Value::Null)
}),
FieldType::Array(_) => Value::Null,
FieldType::Range(_) => Value::Null,
FieldType::HStore => Value::Null,
FieldType::Vector(_) => Value::Null,
FieldType::Geometry(_) => Value::Null,
};
map.insert(field.name.to_owned(), value);
}
Value::Object(map)
}
pub async fn select_rows_as_json(
pool: &Pool,
query: &SelectQuery,
fields: &[&'static crate::core::FieldSchema],
) -> Result<Vec<serde_json::Value>, ExecError> {
crate::test_assertions::query_counter::bump();
let stmt = pool.dialect().compile_select(query)?;
match pool {
#[cfg(feature = "postgres")]
Pool::Postgres(pg) => {
let mut q: Query<'_, sqlx::Postgres, PgArguments> = sqlx::query(&stmt.sql);
for v in stmt.params {
q = bind_query(q, v);
}
let rows = q.fetch_all(pg).await?;
Ok(rows
.iter()
.map(|r| {
let mut json = row_to_json(r, fields);
augment_joined_columns_pg(&mut json, r, &query.joins);
json
})
.collect())
}
#[cfg(feature = "mysql")]
Pool::Mysql(my) => {
let mut q: sqlx::query::Query<'_, sqlx::MySql, sqlx::mysql::MySqlArguments> =
sqlx::query(&stmt.sql);
for v in stmt.params {
q = bind_query_my(q, v);
}
let rows = q.fetch_all(my).await?;
Ok(rows
.iter()
.map(|r| {
let mut json = row_to_json_my(r, fields);
augment_joined_columns_my(&mut json, r, &query.joins);
json
})
.collect())
}
#[cfg(feature = "sqlite")]
Pool::Sqlite(sq) => {
let mut q: sqlx::query::Query<'_, sqlx::Sqlite, sqlx::sqlite::SqliteArguments<'_>> =
sqlx::query(&stmt.sql);
for v in stmt.params {
q = bind_query_sqlite(q, v);
}
let rows = q.fetch_all(sq).await?;
Ok(rows
.iter()
.map(|r| {
let mut json = row_to_json_sqlite(r, fields);
augment_joined_columns_sqlite(&mut json, r, &query.joins);
json
})
.collect())
}
}
}
#[cfg(feature = "postgres")]
fn augment_joined_columns_pg(
out: &mut serde_json::Value,
row: &sqlx::postgres::PgRow,
joins: &[crate::core::Join],
) {
use sqlx::Row as _;
let Some(map) = out.as_object_mut() else {
return;
};
for join in joins {
for col in &join.project {
let key = format!("{}__{}", join.alias, col);
let v = row
.try_get::<Option<String>, _>(key.as_str())
.ok()
.flatten();
map.insert(
key,
v.map(serde_json::Value::String)
.unwrap_or(serde_json::Value::Null),
);
}
}
}
#[cfg(feature = "mysql")]
fn augment_joined_columns_my(
out: &mut serde_json::Value,
row: &sqlx::mysql::MySqlRow,
joins: &[crate::core::Join],
) {
use sqlx::Row as _;
let Some(map) = out.as_object_mut() else {
return;
};
for join in joins {
for col in &join.project {
let key = format!("{}__{}", join.alias, col);
let v = row
.try_get::<Option<String>, _>(key.as_str())
.ok()
.flatten();
map.insert(
key,
v.map(serde_json::Value::String)
.unwrap_or(serde_json::Value::Null),
);
}
}
}
#[cfg(feature = "sqlite")]
fn augment_joined_columns_sqlite(
out: &mut serde_json::Value,
row: &sqlx::sqlite::SqliteRow,
joins: &[crate::core::Join],
) {
use sqlx::Row as _;
let Some(map) = out.as_object_mut() else {
return;
};
for join in joins {
for col in &join.project {
let key = format!("{}__{}", join.alias, col);
let v = row
.try_get::<Option<String>, _>(key.as_str())
.ok()
.flatten();
map.insert(
key,
v.map(serde_json::Value::String)
.unwrap_or(serde_json::Value::Null),
);
}
}
}
pub async fn select_one_row_as_json(
pool: &Pool,
query: &SelectQuery,
fields: &[&'static crate::core::FieldSchema],
) -> Result<Option<serde_json::Value>, ExecError> {
crate::test_assertions::query_counter::bump();
let stmt = pool.dialect().compile_select(query)?;
match pool {
#[cfg(feature = "postgres")]
Pool::Postgres(pg) => {
let mut q: Query<'_, sqlx::Postgres, PgArguments> = sqlx::query(&stmt.sql);
for v in stmt.params {
q = bind_query(q, v);
}
Ok(q.fetch_optional(pg).await?.as_ref().map(|r| {
let mut json = row_to_json(r, fields);
augment_joined_columns_pg(&mut json, r, &query.joins);
json
}))
}
#[cfg(feature = "mysql")]
Pool::Mysql(my) => {
let mut q: sqlx::query::Query<'_, sqlx::MySql, sqlx::mysql::MySqlArguments> =
sqlx::query(&stmt.sql);
for v in stmt.params {
q = bind_query_my(q, v);
}
Ok(q.fetch_optional(my).await?.as_ref().map(|r| {
let mut json = row_to_json_my(r, fields);
augment_joined_columns_my(&mut json, r, &query.joins);
json
}))
}
#[cfg(feature = "sqlite")]
Pool::Sqlite(sq) => {
let mut q: sqlx::query::Query<'_, sqlx::Sqlite, sqlx::sqlite::SqliteArguments<'_>> =
sqlx::query(&stmt.sql);
for v in stmt.params {
q = bind_query_sqlite(q, v);
}
Ok(q.fetch_optional(sq).await?.as_ref().map(|r| {
let mut json = row_to_json_sqlite(r, fields);
augment_joined_columns_sqlite(&mut json, r, &query.joins);
json
}))
}
}
}