Skip to content

Commit b705023

Browse files
authored
Merge pull request #4388 from xuehaonan27/add_sqlite_json_functions
Add sqlite functions `json` and `jsonb`
2 parents 907efb9 + 683a8af commit b705023

File tree

7 files changed

+188
-2
lines changed

7 files changed

+188
-2
lines changed

diesel/src/expression/mod.rs

+3
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ pub(crate) mod dsl {
7474
#[cfg(feature = "postgres_backend")]
7575
pub use crate::pg::expression::dsl::*;
7676

77+
#[cfg(feature = "sqlite")]
78+
pub use crate::sqlite::expression::dsl::*;
79+
7780
/// The return type of [`count(expr)`](crate::dsl::count())
7881
pub type count<Expr> = super::count::count<SqlTypeOf<Expr>, Expr>;
7982

diesel/src/sqlite/expression/expression_methods.rs

+38
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
//! Sqlite specific expression methods.
22
3+
pub(in crate::sqlite) use self::private::{
4+
BinaryOrNullableBinary, MaybeNullableValue, TextOrNullableText,
5+
};
36
use super::operators::*;
47
use crate::dsl;
58
use crate::expression::grouped::Grouped;
@@ -82,3 +85,38 @@ pub trait SqliteExpressionMethods: Expression + Sized {
8285
}
8386

8487
impl<T: Expression> SqliteExpressionMethods for T {}
88+
89+
pub(in crate::sqlite) mod private {
90+
use crate::sql_types::{Binary, MaybeNullableType, Nullable, SingleValue, Text};
91+
92+
#[diagnostic::on_unimplemented(
93+
message = "`{Self}` is neither `diesel::sql_types::Text` nor `diesel::sql_types::Nullable<Text>`",
94+
note = "try to provide an expression that produces one of the expected sql types"
95+
)]
96+
pub trait TextOrNullableText {}
97+
98+
impl TextOrNullableText for Text {}
99+
impl TextOrNullableText for Nullable<Text> {}
100+
101+
#[diagnostic::on_unimplemented(
102+
message = "`{Self}` is neither `diesel::sql_types::Binary` nor `diesel::sql_types::Nullable<Binary>`",
103+
note = "try to provide an expression that produces one of the expected sql types"
104+
)]
105+
pub trait BinaryOrNullableBinary {}
106+
107+
impl BinaryOrNullableBinary for Binary {}
108+
impl BinaryOrNullableBinary for Nullable<Binary> {}
109+
110+
pub trait MaybeNullableValue<T>: SingleValue {
111+
type Out: SingleValue;
112+
}
113+
114+
impl<T, O> MaybeNullableValue<O> for T
115+
where
116+
T: SingleValue,
117+
T::IsNull: MaybeNullableType<O>,
118+
<T::IsNull as MaybeNullableType<O>>::Out: SingleValue,
119+
{
120+
type Out = <T::IsNull as MaybeNullableType<O>>::Out;
121+
}
122+
}
+107
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
//! SQLite specific functions
2+
use crate::expression::functions::define_sql_function;
3+
use crate::sql_types::*;
4+
use crate::sqlite::expression::expression_methods::BinaryOrNullableBinary;
5+
use crate::sqlite::expression::expression_methods::MaybeNullableValue;
6+
use crate::sqlite::expression::expression_methods::TextOrNullableText;
7+
8+
#[cfg(feature = "sqlite")]
9+
define_sql_function! {
10+
/// Verifies that its argument is a valid JSON string or JSONB blob and returns a minified
11+
/// version of that JSON string with all unnecessary whitespace removed.
12+
///
13+
/// # Example
14+
///
15+
/// ```rust
16+
/// # include!("../../doctest_setup.rs");
17+
/// #
18+
/// # fn main() {
19+
/// # #[cfg(feature = "serde_json")]
20+
/// # run_test().unwrap();
21+
/// # }
22+
/// #
23+
/// # #[cfg(feature = "serde_json")]
24+
/// # fn run_test() -> QueryResult<()> {
25+
/// # use diesel::dsl::json;
26+
/// # use serde_json::{json, Value};
27+
/// # use diesel::sql_types::{Text, Nullable};
28+
/// # let connection = &mut establish_connection();
29+
///
30+
/// let result = diesel::select(json::<Text, _>(r#"{"a": "b", "c": 1}"#))
31+
/// .get_result::<Value>(connection)?;
32+
///
33+
/// assert_eq!(json!({"a":"b","c":1}), result);
34+
///
35+
/// let result = diesel::select(json::<Text, _>(r#"{ "this" : "is", "a": [ "test" ] }"#))
36+
/// .get_result::<Value>(connection)?;
37+
///
38+
/// assert_eq!(json!({"a":["test"],"this":"is"}), result);
39+
///
40+
/// let result = diesel::select(json::<Nullable<Text>, _>(None::<&str>))
41+
/// .get_result::<Option<Value>>(connection)?;
42+
///
43+
/// assert!(result.is_none());
44+
///
45+
/// # Ok(())
46+
/// # }
47+
/// ```
48+
fn json<E: TextOrNullableText + MaybeNullableValue<Json>>(e: E) -> E::Out;
49+
}
50+
51+
#[cfg(feature = "sqlite")]
52+
define_sql_function! {
53+
/// The jsonb(X) function returns the binary JSONB representation of the JSON provided as argument X.
54+
///
55+
/// # Example
56+
///
57+
/// ```rust
58+
/// # include!("../../doctest_setup.rs");
59+
/// #
60+
/// # fn main() {
61+
/// # #[cfg(feature = "serde_json")]
62+
/// # run_test().unwrap();
63+
/// # }
64+
/// #
65+
/// # #[cfg(feature = "serde_json")]
66+
/// # fn run_test() -> QueryResult<()> {
67+
/// # use diesel::dsl::{sql, jsonb};
68+
/// # use serde_json::{json, Value};
69+
/// # use diesel::sql_types::{Text, Binary, Nullable};
70+
/// # let connection = &mut establish_connection();
71+
///
72+
/// let version = diesel::select(sql::<Text>("sqlite_version();"))
73+
/// .get_result::<String>(connection)?;
74+
///
75+
/// // Querying SQLite version should not fail.
76+
/// let version_components: Vec<&str> = version.split('.').collect();
77+
/// let major: u32 = version_components[0].parse().unwrap();
78+
/// let minor: u32 = version_components[1].parse().unwrap();
79+
/// let patch: u32 = version_components[2].parse().unwrap();
80+
///
81+
/// if major > 3 || (major == 3 && minor >= 45) {
82+
/// /* Valid sqlite version, do nothing */
83+
/// } else {
84+
/// println!("SQLite version is too old, skipping the test.");
85+
/// return Ok(());
86+
/// }
87+
///
88+
/// let result = diesel::select(jsonb::<Binary, _>(br#"{"a": "b", "c": 1}"#))
89+
/// .get_result::<Value>(connection)?;
90+
///
91+
/// assert_eq!(json!({"a": "b", "c": 1}), result);
92+
///
93+
/// let result = diesel::select(jsonb::<Binary, _>(br#"{"this":"is","a":["test"]}"#))
94+
/// .get_result::<Value>(connection)?;
95+
///
96+
/// assert_eq!(json!({"this":"is","a":["test"]}), result);
97+
///
98+
/// let result = diesel::select(jsonb::<Nullable<Binary>, _>(None::<Vec<u8>>))
99+
/// .get_result::<Option<Value>>(connection)?;
100+
///
101+
/// assert!(result.is_none());
102+
///
103+
/// # Ok(())
104+
/// # }
105+
/// ```
106+
fn jsonb<E: BinaryOrNullableBinary + MaybeNullableValue<Jsonb>>(e: E) -> E::Out;
107+
}
+11-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,18 @@
1-
use crate::dsl::AsExpr;
1+
use crate::dsl::{AsExpr, SqlTypeOf};
22
use crate::expression::grouped::Grouped;
33

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

77
/// The return type of `lhs.is_not(rhs)`.
88
pub type IsNot<Lhs, Rhs> = Grouped<super::operators::IsNot<Lhs, AsExpr<Rhs, Lhs>>>;
9+
10+
/// Return type of [`json(json)`](super::functions::json())
11+
#[allow(non_camel_case_types)]
12+
#[cfg(feature = "sqlite")]
13+
pub type json<E> = super::functions::json<SqlTypeOf<E>, E>;
14+
15+
/// Return type of [`jsonb(json)`](super::functions::jsonb())
16+
#[allow(non_camel_case_types)]
17+
#[cfg(feature = "sqlite")]
18+
pub type jsonb<E> = super::functions::jsonb<SqlTypeOf<E>, E>;

diesel/src/sqlite/expression/mod.rs

+11
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,16 @@
55
//! kept separate purely for documentation purposes.
66
77
pub(crate) mod expression_methods;
8+
pub mod functions;
89
pub(crate) mod helper_types;
910
mod operators;
11+
12+
/// SQLite specific expression DSL methods.
13+
///
14+
/// This module will be glob imported by
15+
/// [`diesel::dsl`](crate::dsl) when compiled with the `feature =
16+
/// "sqlite"` flag.
17+
pub mod dsl {
18+
#[doc(inline)]
19+
pub use super::functions::*;
20+
}

diesel/src/sqlite/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
77
pub(crate) mod backend;
88
mod connection;
9-
pub(crate) mod expression;
9+
pub mod expression;
1010

1111
pub mod query_builder;
1212

diesel_derives/tests/auto_type.rs

+17
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,17 @@ table! {
5858
}
5959
}
6060

61+
#[cfg(feature = "sqlite")]
62+
table! {
63+
sqlite_extras {
64+
id -> Integer,
65+
text -> Text,
66+
blob -> Binary,
67+
json -> Json,
68+
jsonb -> Jsonb,
69+
}
70+
}
71+
6172
joinable!(posts -> users(user_id));
6273
joinable!(posts2 -> users(user_id));
6374
joinable!(posts3 -> users(user_id));
@@ -472,6 +483,12 @@ fn postgres_functions() -> _ {
472483
)
473484
}
474485

486+
#[cfg(feature = "sqlite")]
487+
#[auto_type]
488+
fn sqlite_functions() -> _ {
489+
(json(sqlite_extras::text), jsonb(sqlite_extras::blob))
490+
}
491+
475492
#[auto_type]
476493
fn with_lifetime<'a>(name: &'a str) -> _ {
477494
users::table.filter(users::name.eq(name))

0 commit comments

Comments
 (0)