如何为Rust的diesel ORM实现Postgresql jsonpath类型

zzwlnbp8  于 4个月前  发布在  PostgreSQL
关注(0)|答案(1)|浏览(89)

我想使用PostgreSQL的jsonb_path_exists函数和diesel
我从编译的函数定义开始。

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
看看jsonb_path_exists的签名,我发现问题出在第二个参数:diesel::sql_types::Text。它应该是一个jsonpath而不是text。

jsonb_path_exists ( target jsonb, path jsonpath [, vars jsonb [, silent boolean ]] ) → boolean


我通过调试打印查询,用单引号替换双引号,并对数据库手动运行它来证实这一点。
我不知道如何定义一个新的diesel SQL类型,使diesel正确地将String视为Postgres path类型并发出单引号。查看现有类型,我没有找到任何可以类似地实现的东西。
编辑:
添加一个新的SqlType是一个开始。然而,如何为一些struct JsonPath(String)实现AsExpression<JsonPathType>还不清楚。

/// Q: How do we know the oid?
/// A: `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;

zengzsys

zengzsys1#

在weiznich对gitter的帮助下,我解决了转换问题,概括地说,二进制格式是路径字符串作为字节加上一个版本。

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: custom_sql_types::JsonPath,
    ) -> diesel::sql_types::Bool;
}

pub mod custom_sql_types {
    /// Q: How do we know the oid?
    /// A: `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 JsonPath;
}

#[derive(Debug, Clone, AsExpression)]
#[diesel(sql_type = custom_sql_types::JsonPath)]
pub struct JsonPath(pub String);

impl ToSql<custom_sql_types::JsonPath, Pg> for JsonPath {
    fn to_sql<'b>(
        &'b self,
        out: &mut diesel::serialize::Output<'b, '_, Pg>,
    ) -> diesel::serialize::Result {
        use std::io::Write;

        // > The type is sent as text in binary mode, so this is almost the same
        // > as the input function, but it's prefixed with a version number so
        // > we can change the binary format sent in future if necessary.
        // > For now, only version 1 is supported.
        // https://github.com/postgres/postgres/blob/d053a879bb360fb72c46de2a013e741d3f7c9e8d/src/backend/utils/adt/jsonpath.c#L113
        const VERSION_NUMBER: u8 = 1;

        let payload = [&[VERSION_NUMBER], self.0.as_bytes()].concat();
        out.write_all(&payload)
            .map(|_| diesel::serialize::IsNull::No)
            .map_err(|e| {
                Box::new(e) as Box<dyn std::error::Error + Send + Sync>
            })
    }
}

字符串
我在postgres代码库中找到了正确的版本:

/*
 * jsonpath type recv function
 *
 * The type is sent as text in binary mode, so this is almost the same
 * as the input function, but it's prefixed with a version number so we
 * can change the binary format sent in future if necessary. For now,
 * only version 1 is supported.
 */
Datum
jsonpath_recv(PG_FUNCTION_ARGS)
{
    StringInfo  buf = (StringInfo) PG_GETARG_POINTER(0);
    int         version = pq_getmsgint(buf, 1);
    char       *str;
    int         nbytes;

    if (version == JSONPATH_VERSION)
        str = pq_getmsgtext(buf, buf->len - buf->cursor, &nbytes);
    else
        elog(ERROR, "unsupported jsonpath version number: %d", version);

    return jsonPathFromCstring(str, nbytes, NULL);
}

相关问题