use drizzle_core::error::DrizzleError;
use drizzle_core::expr::{Expr, NonNull, Nullability, Scalar};
use drizzle_core::json::{Json, JsonColumnOperand, JsonColumnValue};
use drizzle_core::types::DataType;
use drizzle_core::{SQL, SQLParam, ToSQL};
use serde::Serialize;
use super::{PostgresInsertValue, PostgresUpdateValue, PostgresValue, ValueWrapper};
mod private {
pub trait Sealed {}
}
pub trait PostgresJsonType: DataType + private::Sealed {
const JSONB: bool;
}
impl private::Sealed for crate::types::Json {}
impl PostgresJsonType for crate::types::Json {
const JSONB: bool = false;
}
impl private::Sealed for crate::types::Jsonb {}
impl PostgresJsonType for crate::types::Jsonb {
const JSONB: bool = true;
}
const fn document<'a, Target: PostgresJsonType>(value: serde_json::Value) -> PostgresValue<'a> {
if Target::JSONB {
PostgresValue::Jsonb(value)
} else {
PostgresValue::Json(value)
}
}
impl PostgresValue<'_> {
pub fn try_from_json<Target: PostgresJsonType, T: Serialize>(
value: &Json<T>,
) -> Result<Self, DrizzleError> {
value.to_json_value().map(document::<Target>)
}
}
impl<'a, T: Serialize> TryFrom<Json<T>> for PostgresValue<'a> {
type Error = DrizzleError;
fn try_from(value: Json<T>) -> Result<Self, Self::Error> {
Self::try_from_json::<crate::types::Jsonb, T>(&value)
}
}
impl<'a, T: Serialize> ToSQL<'a, PostgresValue<'a>> for Json<T> {
fn to_sql(&self) -> SQL<'a, PostgresValue<'a>> {
SQL::param(PostgresValue::Jsonb(self.encode_json_value()))
}
}
impl<'a, T: Serialize> Expr<'a, PostgresValue<'a>> for Json<T> {
type SQLType = crate::types::Jsonb;
type Nullable = NonNull;
type Aggregate = Scalar;
}
impl<'a, T: Serialize> PostgresInsertValue<'a, PostgresValue<'a>, T> {
#[must_use]
pub fn json<Target: PostgresJsonType>(value: Json<T>) -> Self {
Self::Value(ValueWrapper::<PostgresValue<'a>, T>::new(SQL::param(
document::<Target>(value.encode_json_value()),
)))
}
}
impl<'a, T, Target, TargetNull> JsonColumnValue<T>
for PostgresUpdateValue<'a, PostgresValue<'a>, T, Target, TargetNull>
where
T: Serialize,
Target: PostgresJsonType,
TargetNull: Nullability,
{
fn from_json(value: Json<T>) -> Self {
Self::Value(ValueWrapper::<PostgresValue<'a>, T>::new(SQL::param(
document::<Target>(value.encode_json_value()),
)))
}
}
impl<V: SQLParam, T> JsonColumnOperand for PostgresInsertValue<'_, V, T> {}
impl<V, T, Target, TargetNull> JsonColumnOperand
for PostgresUpdateValue<'_, V, T, Target, TargetNull>
where
V: SQLParam,
Target: DataType,
TargetNull: Nullability,
{
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::{Json as JsonType, Jsonb};
#[test]
fn column_type_selects_the_json_variant() {
let payload = Json(serde_json::json!({ "a": 1 }));
assert_eq!(
PostgresValue::try_from_json::<JsonType, _>(&payload).unwrap(),
PostgresValue::Json(serde_json::json!({ "a": 1 }))
);
assert_eq!(
PostgresValue::try_from_json::<Jsonb, _>(&payload).unwrap(),
PostgresValue::Jsonb(serde_json::json!({ "a": 1 }))
);
}
#[test]
fn expressions_bind_jsonb() {
let sql = Json(vec![1, 2]).to_sql();
let params: Vec<_> = sql.params().collect();
assert_eq!(params, [&PostgresValue::Jsonb(serde_json::json!([1, 2]))]);
}
#[test]
fn serialization_failures_are_errors() {
let mut map = std::collections::BTreeMap::new();
map.insert((1, 2), 3);
assert!(matches!(
PostgresValue::try_from_json::<Jsonb, _>(&Json(map)),
Err(DrizzleError::JsonError(_))
));
}
}