如何在Rust Diesel ORM中适配PostgreSQL jsonpath类型
在Diesel ORM中使用PostgreSQL的
jsonb_path_exists函数 问题背景
最初尝试定义jsonb_path_exists函数时,将第二个参数指定为Text类型:
diesel::sql_function! { /// https://www.postgresql.org/docs/current/functions-json.html fn jsonb_path_exists( jsonb: diesel::sql_types::Nullable<diesel::sql_types::Jsonb>, path: diesel::sql_types::Text, ) -> diesel::sql_types::Bool; }
调用时触发运行时错误:
mytable::table.filter( jsonb_path_exists( activities::activity_object, format!(r#"$.**.id ? (@ == "some-uuid")"#), ), )
错误信息:diesel error: function jsonb_path_exists(jsonb, text) does not exist
核心原因是PostgreSQL原生的jsonb_path_exists签名中,第二个参数类型为jsonpath而非text:
jsonb_path_exists ( target jsonb, path jsonpath [, vars jsonb [, silent boolean ]] ) → boolean
解决方案
1. 定义对应PostgreSQL的jsonpath SQL类型
先创建Diesel可识别的JsonPathType,对应PostgreSQL的jsonpath类型(OID可通过SELECT typname, oid, typarray FROM pg_type WHERE typname = 'jsonpath';查询):
#[derive(Debug, Clone, Copy, Default, QueryId, SqlType)] #[diesel(postgres_type(oid = 4072, array_oid = 4073))] pub struct JsonPathType;
2. 实现JsonPath包装结构体的表达式转换与序列化
创建JsonPath结构体包裹路径字符串,并实现Diesel所需的AsExpression和ToSql trait:
use diesel::{ deserialize::{self, FromSql}, expression::{AsExpression, Expression}, pg::Pg, serialize::{self, Output, ToSql}, sql_types, QueryId, }; #[derive(Debug, Clone, PartialEq, Eq, QueryId)] pub struct JsonPath(pub String); // 实现AsExpression,让JsonPath可作为JsonPathType的表达式 impl AsExpression<JsonPathType> for JsonPath { type Expression = diesel::expression::bound::Bound<JsonPathType, Self>; fn as_expression(self) -> Self::Expression { diesel::expression::bound::Bound::new(self) } } impl<'a> AsExpression<JsonPathType> for &'a JsonPath { type Expression = diesel::expression::bound::Bound<JsonPathType, &'a JsonPath>; fn as_expression(self) -> Self::Expression { diesel::expression::bound::Bound::new(self) } } // 实现ToSql,让Diesel将JsonPath序列化为PostgreSQL的jsonpath类型 impl ToSql<JsonPathType, Pg> for JsonPath { fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Pg>) -> serialize::Result { out.write_all(self.0.as_bytes())?; Ok(serialize::IsNull::No) } } // 可选:如需从数据库读取jsonpath类型,实现FromSql impl FromSql<JsonPathType, Pg> for JsonPath { fn from_sql(bytes: diesel::backend::RawValue<'_, Pg>) -> deserialize::Result<Self> { let s = String::from_utf8(bytes.as_bytes().to_vec())?; Ok(JsonPath(s)) } }
3. 重新定义jsonb_path_exists函数
使用正确的JsonPathType作为第二个参数类型:
diesel::sql_function! { fn jsonb_path_exists( jsonb: diesel::sql_types::Nullable<diesel::sql_types::Jsonb>, path: JsonPathType, ) -> diesel::sql_types::Bool; }
4. 调用示例
现在可正常使用jsonb_path_exists函数,传入JsonPath包装后的路径:
mytable::table.filter( jsonb_path_exists( activities::activity_object, JsonPath(format!(r#"$.**.id ? (@ == "some-uuid")"#)), ), )
内容的提问来源于stack exchange,提问作者porkbrain
相关产品推荐
相关产品推荐

