Skip to content

Commit

Permalink
Merge pull request #4388 from xuehaonan27/add_sqlite_json_functions
Browse files Browse the repository at this point in the history
Add sqlite functions `json` and `jsonb`
  • Loading branch information
weiznich authored Dec 13, 2024
2 parents 907efb9 + 683a8af commit b705023
Show file tree
Hide file tree
Showing 7 changed files with 188 additions and 2 deletions.
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
38 changes: 38 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,38 @@ 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};

#[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.
///
/// # 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 @@ -58,6 +58,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 @@ -472,6 +483,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

0 comments on commit b705023

Please sign in to comment.