Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add sqlite functions json and jsonb #4388

Merged
merged 6 commits into from
Dec 13, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions diesel/src/expression/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ pub(crate) mod dsl {
#[cfg(feature = "postgres_backend")]
pub use crate::pg::expression::dsl::*;

#[cfg(feature = "sqlite")]
pub use crate::sqlite::expression::dsl::*;

/// The return type of [`count(expr)`](crate::dsl::count())
pub type count<Expr> = super::count::count<SqlTypeOf<Expr>, Expr>;

Expand Down
46 changes: 46 additions & 0 deletions diesel/src/sqlite/expression/expression_methods.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
//! Sqlite specific expression methods.

pub(in crate::sqlite) use self::private::{
BinaryOrNullableBinary, MaybeNullableValue, TextOrNullableText,
};
use super::operators::*;
use crate::dsl;
use crate::expression::grouped::Grouped;
Expand Down Expand Up @@ -82,3 +85,46 @@ pub trait SqliteExpressionMethods: Expression + Sized {
}

impl<T: Expression> SqliteExpressionMethods for T {}

pub(in crate::sqlite) mod private {
use crate::sql_types::{Binary, MaybeNullableType, Nullable, SingleValue, Text};

/*
pub trait JsonOrNullableJsonOrJsonbOrNullableJsonb {}
impl JsonOrNullableJsonOrJsonbOrNullableJsonb for Json {}
impl JsonOrNullableJsonOrJsonbOrNullableJsonb for Nullable<Json> {}
impl JsonOrNullableJsonOrJsonbOrNullableJsonb for Jsonb {}
impl JsonOrNullableJsonOrJsonbOrNullableJsonb for Nullable<Jsonb> {}
*/

weiznich marked this conversation as resolved.
Show resolved Hide resolved
#[diagnostic::on_unimplemented(
message = "`{Self}` is neither `diesel::sql_types::Text` nor `diesel::sql_types::Nullable<Text>`",
note = "try to provide an expression that produces one of the expected sql types"
)]
pub trait TextOrNullableText {}

impl TextOrNullableText for Text {}
impl TextOrNullableText for Nullable<Text> {}

#[diagnostic::on_unimplemented(
message = "`{Self}` is neither `diesel::sql_types::Binary` nor `diesel::sql_types::Nullable<Binary>`",
note = "try to provide an expression that produces one of the expected sql types"
)]
pub trait BinaryOrNullableBinary {}

impl BinaryOrNullableBinary for Binary {}
impl BinaryOrNullableBinary for Nullable<Binary> {}

pub trait MaybeNullableValue<T>: SingleValue {
type Out: SingleValue;
}

impl<T, O> MaybeNullableValue<O> for T
where
T: SingleValue,
T::IsNull: MaybeNullableType<O>,
<T::IsNull as MaybeNullableType<O>>::Out: SingleValue,
{
type Out = <T::IsNull as MaybeNullableType<O>>::Out;
}
}
107 changes: 107 additions & 0 deletions diesel/src/sqlite/expression/functions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
//! SQLite specific functions
use crate::expression::functions::define_sql_function;
use crate::sql_types::*;
use crate::sqlite::expression::expression_methods::BinaryOrNullableBinary;
use crate::sqlite::expression::expression_methods::MaybeNullableValue;
use crate::sqlite::expression::expression_methods::TextOrNullableText;

#[cfg(feature = "sqlite")]
define_sql_function! {
/// Verifies that its argument is a valid JSON string or JSONB blob and returns a minified
/// version of that JSON string with all unnecessary whitespace removed.
///
/// # Example
///
/// ```rust
/// # include!("../../doctest_setup.rs");
/// #
/// # fn main() {
/// # #[cfg(feature = "serde_json")]
/// # run_test().unwrap();
/// # }
/// #
/// # #[cfg(feature = "serde_json")]
/// # fn run_test() -> QueryResult<()> {
/// # use diesel::dsl::json;
/// # use serde_json::{json, Value};
/// # use diesel::sql_types::{Text, Nullable};
/// # let connection = &mut establish_connection();
///
/// let result = diesel::select(json::<Text, _>(r#"{"a": "b", "c": 1}"#))
/// .get_result::<Value>(connection)?;
///
/// assert_eq!(json!({"a":"b","c":1}), result);
///
/// let result = diesel::select(json::<Text, _>(r#"{ "this" : "is", "a": [ "test" ] }"#))
/// .get_result::<Value>(connection)?;
///
/// assert_eq!(json!({"a":["test"],"this":"is"}), result);
///
/// let result = diesel::select(json::<Nullable<Text>, _>(None::<&str>))
/// .get_result::<Option<Value>>(connection)?;
///
/// assert!(result.is_none());
///
/// # Ok(())
/// # }
/// ```
fn json<E: TextOrNullableText + MaybeNullableValue<Json>>(e: E) -> E::Out;
}

#[cfg(feature = "sqlite")]
define_sql_function! {
/// The jsonb(X) function returns the binary JSONB representation of the JSON provided as argument X.
///
weiznich marked this conversation as resolved.
Show resolved Hide resolved
/// # Example
///
/// ```rust
/// # include!("../../doctest_setup.rs");
/// #
/// # fn main() {
/// # #[cfg(feature = "serde_json")]
/// # run_test().unwrap();
/// # }
/// #
/// # #[cfg(feature = "serde_json")]
/// # fn run_test() -> QueryResult<()> {
/// # use diesel::dsl::{sql, jsonb};
/// # use serde_json::{json, Value};
/// # use diesel::sql_types::{Text, Binary, Nullable};
/// # let connection = &mut establish_connection();
///
/// let version = diesel::select(sql::<Text>("sqlite_version();"))
/// .get_result::<String>(connection)?;
///
/// // Querying SQLite version should not fail.
/// let version_components: Vec<&str> = version.split('.').collect();
/// let major: u32 = version_components[0].parse().unwrap();
/// let minor: u32 = version_components[1].parse().unwrap();
/// let patch: u32 = version_components[2].parse().unwrap();
///
/// if major > 3 || (major == 3 && minor >= 45) {
/// /* Valid sqlite version, do nothing */
/// } else {
/// println!("SQLite version is too old, skipping the test.");
/// return Ok(());
/// }
///
/// let result = diesel::select(jsonb::<Binary, _>(br#"{"a": "b", "c": 1}"#))
/// .get_result::<Value>(connection)?;
///
/// assert_eq!(json!({"a": "b", "c": 1}), result);
///
/// let result = diesel::select(jsonb::<Binary, _>(br#"{"this":"is","a":["test"]}"#))
/// .get_result::<Value>(connection)?;
///
/// assert_eq!(json!({"this":"is","a":["test"]}), result);
///
/// let result = diesel::select(jsonb::<Nullable<Binary>, _>(None::<Vec<u8>>))
/// .get_result::<Option<Value>>(connection)?;
///
/// assert!(result.is_none());
///
/// # Ok(())
/// # }
/// ```
fn jsonb<E: BinaryOrNullableBinary + MaybeNullableValue<Jsonb>>(e: E) -> E::Out;
}
12 changes: 11 additions & 1 deletion diesel/src/sqlite/expression/helper_types.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
use crate::dsl::AsExpr;
use crate::dsl::{AsExpr, SqlTypeOf};
use crate::expression::grouped::Grouped;

/// The return type of `lhs.is(rhs)`.
pub type Is<Lhs, Rhs> = Grouped<super::operators::Is<Lhs, AsExpr<Rhs, Lhs>>>;

/// The return type of `lhs.is_not(rhs)`.
pub type IsNot<Lhs, Rhs> = Grouped<super::operators::IsNot<Lhs, AsExpr<Rhs, Lhs>>>;

/// Return type of [`json(json)`](super::functions::json())
#[allow(non_camel_case_types)]
#[cfg(feature = "sqlite")]
pub type json<E> = super::functions::json<SqlTypeOf<E>, E>;

/// Return type of [`jsonb(json)`](super::functions::jsonb())
#[allow(non_camel_case_types)]
#[cfg(feature = "sqlite")]
pub type jsonb<E> = super::functions::jsonb<SqlTypeOf<E>, E>;
11 changes: 11 additions & 0 deletions diesel/src/sqlite/expression/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,16 @@
//! kept separate purely for documentation purposes.

pub(crate) mod expression_methods;
pub mod functions;
pub(crate) mod helper_types;
mod operators;

/// SQLite specific expression DSL methods.
///
/// This module will be glob imported by
/// [`diesel::dsl`](crate::dsl) when compiled with the `feature =
/// "sqlite"` flag.
pub mod dsl {
#[doc(inline)]
pub use super::functions::*;
}
2 changes: 1 addition & 1 deletion diesel/src/sqlite/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

pub(crate) mod backend;
mod connection;
pub(crate) mod expression;
pub mod expression;

pub mod query_builder;

Expand Down
17 changes: 17 additions & 0 deletions diesel_derives/tests/auto_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,17 @@ table! {
}
}

#[cfg(feature = "sqlite")]
table! {
sqlite_extras {
id -> Integer,
text -> Text,
blob -> Binary,
json -> Json,
jsonb -> Jsonb,
}
}

joinable!(posts -> users(user_id));
joinable!(posts2 -> users(user_id));
joinable!(posts3 -> users(user_id));
Expand Down Expand Up @@ -465,6 +476,12 @@ fn postgres_functions() -> _ {
)
}

#[cfg(feature = "sqlite")]
#[auto_type]
fn sqlite_functions() -> _ {
(json(sqlite_extras::text), jsonb(sqlite_extras::blob))
}

#[auto_type]
fn with_lifetime<'a>(name: &'a str) -> _ {
users::table.filter(users::name.eq(name))
Expand Down
Loading