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

working on json_path_exists #4286

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions diesel/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ r2d2 = { version = ">= 0.8.2, < 0.9.0", optional = true }
itoa = { version = "1.0.0", optional = true }
time = { version = "0.3.9", optional = true, features = ["macros"] }
downcast-rs = "1.2.1"
jsonpath-rust = "0.7.1"

[dependencies.diesel_derives]
version = "~2.2.0"
Expand Down
30 changes: 30 additions & 0 deletions diesel/src/pg/expression/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2279,3 +2279,33 @@ define_sql_function! {
#[sql_name = "row_to_json"]
fn row_to_json<R: RecordOrNullableRecord + MaybeNullableValue<Json>>(record: R) -> R::Out;
}

#[cfg(feature = "postgres_backend")]
define_sql_function! {
/// This form of jsonb_object takes keys and values pairwise from two separate arrays.
/// In all other respects it is identical to the one-argument form.
///
/// # Example
///
/// ```rust
/// # include!("../../doctest_setup.rs");
/// #
/// # fn main() {
/// # run_test().unwrap();
/// # }
/// #
/// # fn run_test() -> QueryResult<()> {
/// # use diesel::dsl::{jsonb_path_exists};
/// # use diesel::sql_types::{Array, Nullable, Text,Jsonb, Jsonpath};
/// # use serde_json::Value;
/// # use jsonpath_rust::JsonPath;
/// # let connection = &mut establish_connection();
/// let jsonb:Value = serde_json::json!({"a":[1,2,3,4,5]});
/// let json_path = jsonpath_rust::path!("$.a[ ? (@ >= 2 && @ <= 4)]");
/// let exists = jsonb_path_exists::<Jsonb,Jsonpath,_,_>(jsonb,json_path).get_result::<bool>(connection)?;
Copy link
Member

@weiznich weiznich Nov 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You want to write something like that here:

Suggested change
/// let exists = jsonb_path_exists::<Jsonb,Jsonpath,_,_>(jsonb,json_path).get_result::<bool>(connection)?;
/// let exists = diesel::select(jsonb_path_exists::<Jsonb,Jsonpath,_,_>(jsonb,json_path)).get_result::<bool>(connection)?;

The underlying issue is that just calling the sql function only creates a query fragment that only represents the function, not a full SELECT statement. By using diesel::select you explicitly construct a SELECT statement without from cause, while using whatever you provided as select clause.

That should resolve the compiler error

/// assert!(exists);
/// # Ok(())
/// # }
/// ```
fn jsonb_path_exists<J: JsonbOrNullableJsonb + SingleValue, P: MaybeNullableValue<Jsonpath>>(jsonb: J, path: P) -> Bool;
}
55 changes: 55 additions & 0 deletions diesel/src/pg/types/json_path.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use std::error::Error;
use std::io::Write;
use std::str;
use std::str::FromStr;

use crate::{deserialize, serialize, sql_types};
use crate::deserialize::FromSql;
use crate::pg::{backend::Pg, PgValue};
use crate::serialize::{IsNull, Output, ToSql};

impl FromSql<sql_types::Jsonpath, Pg> for jsonpath_rust::JsonPath {
fn from_sql(value: PgValue<'_>) -> deserialize::Result<Self> {
println!("{:?}", value.as_bytes());
let str = str::from_utf8(value.as_bytes())
.map_err(|_| <&str as Into<Box<dyn Error + Send + Sync>>>::into("Invalid path"))?;
println!("{}", str);
jsonpath_rust::JsonPath::from_str(str).map_err(|_| "Invalid path".into())
}
}

impl ToSql<sql_types::Jsonpath, Pg> for jsonpath_rust::JsonPath {
fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Pg>) -> serialize::Result {
println!("{}", self);
out.write_all(self.to_string().as_bytes())
.map(|_| IsNull::No)
.map_err(Into::into)
}
}

#[cfg(test)]
mod tests {
use jsonpath_rust::JsonPath;

use crate::pg::PgValue;
use crate::query_builder::bind_collector::ByteWrapper;

use super::*;

#[test]
fn json_to_sql() {
let mut buffer = Vec::new();
let mut bytes = Output::test(ByteWrapper(&mut buffer));
let test_json_path = JsonPath::from_str("$.a[?(@ >= 2 && @ <= 4)]").unwrap();
ToSql::<sql_types::Jsonpath, Pg>::to_sql(&test_json_path, &mut bytes).unwrap();
assert_eq!(buffer, b"$.'a'[?(@ >= 2 && @ <= 4)]");
}

#[test]
fn path_from_sql() {
let path = b"$.abc";
let path: JsonPath =
FromSql::<sql_types::Jsonpath, Pg>::from_sql(PgValue::for_test(path)).unwrap();
assert_eq!(path, JsonPath::from_str("$.abc").unwrap());
}
}
7 changes: 7 additions & 0 deletions diesel/src/pg/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ mod integers;
mod ipnet_address;
#[cfg(feature = "serde_json")]
mod json;
mod json_path;
mod mac_addr;
mod mac_addr_8;
#[doc(hidden)]
Expand Down Expand Up @@ -644,6 +645,12 @@ pub mod sql_types {
#[derive(Debug, Clone, Copy, Default, QueryId, SqlType)]
#[diesel(postgres_type(name = "citext"))]
pub struct Citext;

/// The [`Jsonpath`] SQL type. This is a PostgreSQL specific type.
#[cfg(feature = "postgres_backend")]
#[derive(Debug, Clone, Copy, Default, QueryId, SqlType)]
#[diesel(postgres_type(oid = 4072, array_oid = 4073))]
pub struct Jsonpath;
}

mod ops {
Expand Down
7 changes: 7 additions & 0 deletions diesel/src/type_impls/json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,16 @@ use crate::expression::AsExpression;
use crate::sql_types::Json;
#[cfg(any(feature = "postgres_backend", feature = "sqlite"))]
use crate::sql_types::Jsonb;
use crate::sql_types::Jsonpath;

#[derive(AsExpression, FromSqlRow)]
#[diesel(foreign_derive)]
#[diesel(sql_type = Json)]
#[cfg_attr(any(feature = "postgres_backend", feature = "sqlite"), diesel(sql_type = Jsonb))]
struct SerdeJsonValueProxy(serde_json::Value);


#[derive(AsExpression, FromSqlRow)]
#[diesel(foreign_derive)]
#[cfg_attr(any(feature = "postgres_backend"), diesel(sql_type = Jsonpath))]
struct JsonpathProxy(jsonpath_rust::JsonPath);
Loading