30个版本

新版本 0.4.0 2024年8月23日
0.4.0-rc.82024年7月26日
0.4.0-rc.72024年6月13日
0.4.0-rc.32024年3月7日
0.0.2 2022年6月11日

#89 in 数据结构

Download history 221/week @ 2024-05-03 173/week @ 2024-05-10 137/week @ 2024-05-17 109/week @ 2024-05-24 156/week @ 2024-05-31 248/week @ 2024-06-07 193/week @ 2024-06-14 234/week @ 2024-06-21 123/week @ 2024-06-28 58/week @ 2024-07-05 159/week @ 2024-07-12 245/week @ 2024-07-19 376/week @ 2024-07-26 116/week @ 2024-08-02 79/week @ 2024-08-09 56/week @ 2024-08-16

668 每月下载量
2 crates 中使用

MIT/Apache

89KB
2K SLoC

modql

modql 是一套类型和实用工具,旨在以结构化方式表达模型查询过滤器(例如,$eq: .. $startsWith: ..$containsIn: [..])和列表选项(例如,offsetlimitorder_bys)。这些可以轻松地表示为JSON。

本质上,它提供了一种类似MongoDB的过滤语法,不受存储限制,内置对sea-query的支持,并且可以以JSON或Rust类型表达。

重要v0.4.0 已发布:此版本包括重大重构(保持相同的功能,但具有更清晰的命名,并与sea-query解耦)以允许 derive(Fields) 提供字段名称和引用,无需要求 with-sea-query 功能。

有关更多信息,请参阅 CHANGELOG.mdMIGRATION-v03x-v04x.md

谢谢

快速概述

/// This is the model entity, annotated with Fields.
#[derive(Debug, Clone, modql::field::Fields, FromRow, Serialize)]
pub struct Task {
    pub id: i64,
    pub project_id: i64,

    pub title: String,
    pub done: bool,
}

/// This is a Filter, with the modql::filter::OpVals... properties
#[derive(modql::filter::FilterNodes, Deserialize, Default, Debug)]
pub struct TaskFilter {
    project_id: Option<OpValsInt64>,
    title: Option<OpValsString>,
    done: Option<OpValsBool>,
}

// -- Parsing JSON representation to TaskFilter
// This condition requires all of these rules to match (AND).
let list_filter: TaskFilter = serde_json::from_value(json! ({
    "project_id": 123,
    "title": {"$startsWith": "Hello", "$contains": "World"} ,
}))?;

// -- modql ListOptions
let list_options: modql::filter::ListOptions =
    serde_json::from_value(json! ({
        "offset": 0,
        "limit": 2,
        "order_bys": "!title" // ! for descending
    }))?;

// -- Building a sea-query select query with those condition
// Convert the TaskFilter into sea-query condition
let cond: sea_query::Condition = filter.try_into()?;
let mut query = sea_query::Query::select();

// Select only the columns corresponding to the task type.
// This is determined by the modql::field::Fields annotation.
query.from(task_table).columns(Task::sea_column_refs());

// Add the condition from the filter
query.cond_where(cond);

// Apply the list options
list_options.apply_to_sea_query(&mut query);

// and execute query
let (sql, values) = query.build_sqlx(PostgresQueryBuilder);
let entities = sqlx::query_as_with::<_, E, _>(&sql, values)
    .fetch_all(db)
    .await?;

此crate对JSON-RPC或其他类型的模型API(例如,joql模式)非常有用。

重要 v0.3.x代表modql的新版本,具有with-sea-query功能集。它在rust10x网络应用程序生产代码蓝图第02集中得到了应用。这个版本与v0.2.x版本在一定程度上不兼容,主要是由于模块重组。如果您正在使用rust10x/awesomeapp桌面应用程序,请暂时继续使用v0.2.x版本。我计划不久后将代码库升级到v0.3.x版本。

变更日志

OpVal[Type] 条件运算符

OpVal[Type]是一个过滤器单元,允许对指定类型的给定值表达运算符。

对应的OpVals[Type](带有“s”),通常用于过滤属性,因为它允许对同一字段使用多个运算符。

OpVal[Type]的基本JSON表示形式遵循{field_name: {$operator1: value1, $operator2: value2}}格式。例如

{
    "title": {"$startsWith": "Hello", "$contains": "World"}
}

这表示“startsWith”和“contains”都必须满足的条件。

以下表格显示了每种类型的可能运算符列表。

OpValString 运算符

运算符 含义 示例
$eq 与一个值完全匹配 {name: {"$eq": "Jon Doe"}} 等同于 {name: "Jon Doe"}
$in 与列表中的值完全匹配 {name: {"$in": ["Alice", "Jon Doe"]}}
$not 排除任何完全匹配项 {name: {"$not": "Jon Doe"}}
$notIn 排除任何列表中的完全匹配项 {name: {"$notIn": ["Jon Doe"]}}
$contains 对于字符串,是否包含 {name: {"$contains": "Doe"}}
$containsAny 对于字符串,如果包含在任意项中则匹配 {name: {"$containsAny": ["Doe", "Ali"]}}
$containsAll 对于字符串,如果所有项都在源中则匹配 {name: {"$containsAll": ["Hello", "World"]}}
$notContains 不包含 {name: {"$notContains": "Doe"}}
$notContainsAny 不包含任何项 {name: {"$notContainsAny": ["Doe", "Ali"]}}
$startsWith 对于字符串,是否以开头 {name: {"$startsWith": "Jon"}}
$startsWithAny 对于字符串,如果以任意项开头则匹配 {name: {"$startsWithAny": ["Jon", "Al"]}}
$notStartsWith 不以开头 {name: {"$notStartsWith": "Jon"}}
$notStartsWithAny 不以任意项开头 {name: {"$notStartsWithAny": ["Jon", "Al"]}}
$endsWith 对于字符串,是否以结尾 {name: {"$endsWithAny": "Doe"}}
$endsWithAny 对于字符串,是否包含(或) {name: {"$endsWithAny": ["Doe", "ice"]}}
$notEndsWith 不以结尾 {name: {"$notEndsWithAny": "Doe"}}
$notEndsWithAny 不以任意项结尾 {name: {"$notEndsWithAny": ["Doe", "ice"]}}
$lt 小于 {name: {"$lt": "C"}}
$lte 小于或等于 {name: {"$lte": "C"}}
$gt 大于 {name: {"$gt": "J"}}
$gte 大于或等于 {name: {"$gte": "J"}}
$null 如果值为null {name: {"$null": }}
$containsCi 对于字符串,是否不区分大小写地包含 {name: {"$containsCi": "doe"}}
$notContainsCi 不区分大小写地不包含 {name: {"$notContainsCi": "doe"}}
$startsWithCi 对于字符串,是否不区分大小写地以...开头 {name: {"$startsWithCi": "jon"}}
$notStartsWithCi 不区分大小写地不以...开头 {name: {"$notStartsWithCi": "jon"}}
$endsWithCi 对于字符串,是否不区分大小写地以...结尾 {name: {"$endsWithCi": "doe"}}
$notEndsWithCi 不区分大小写地不以...结尾 {name: {"$notEndsWithCi": "doe"}}
$ilike 对于字符串,是否不区分大小写地包含。需要在Cargo.toml中启用with-ilike标志 {name: {"$ilike": "DoE"}}

OpValInt32, OpValInt64, OpValFloat64运算符

运算符 含义 示例
$eq 与一个值完全匹配 {age: {"$eq": 24}}等同于{age: 24}
$in 与列表中的值完全匹配 {age: {"$in": [23, 24]}}
$not 排除任何完全匹配项 {age: {"$not": 24}}
$notIn 排除任何列表中的完全匹配项 {age: {"$notIn": [24]}}
$lt 小于 {age: {"$lt": 30}}
$lte 小于或等于 {age: {"$lte": 30}}
$gt 大于 {age: {"$gt": 30}}
$gte 大于或等于 {age: {"$gte": 30}}
$null 如果值为null {name: {"$null": }}

OpValBool运算符

运算符 含义 示例
$eq 与一个值完全匹配 {dev: {"$eq": true}}等同于{dev: true}
$not 排除任何完全匹配项 {dev: {"$not": }}
$null 如果值为null {name: {"$null": }}

更多信息

  • modql::filter - 提供一个可从JSON反序列化的声明性结构。
  • modql::field - 提供在结构上获取字段信息的方法。启用with-sea-query特性会从标准结构和派生中添加与sea-query兼容的数据结构。

#[derive(modql::field::Fields)提供以下功能

  • Task::field_names()返回结构体的属性名。它可以由#[field(name="another_name")]属性属性重写。
  • Task::field_refs() 返回用于属性的 FieldRef { name: &'static str, rel: Option<&'static str>}。其中,rel 作用如同表名,可以在结构体级别设置为 #[modql(rel="some_table_name")],或在字段级别设置为 #[field(rel="special_rel_name")]

当使用 with-sea-query 功能编译时,这些附加功能在结构体中可用

  • Task::sea_column_refs(): 构建使用 sea-query 的选择查询(以 rel 作为表,以及 name 作为列名)。
  • Task::sea_idens(): 构建适用于简单情况的 sea-query 选择查询。(类似于 ::field_names(),但返回 sea-query 的 DynIden)。
  • task.all_sea_fields().for_sea_insert(): 用于 sea-query 插入。
  • task.all_sea_fields().for_sea_update(): 用于 sea-query 更新。

此外,还提供

  • task.not_none_fields():与上面类似,但仅针对其Option不是None的字段。

Rust类型

在Rust方面,可以表示为以下形式

pub type Result<T> = core::result::Result<T, Error>;
pub type Error = Box<dyn std::error::Error>; // For early dev.
use modql::filter::{FilterGroups, FilterNode, OpValtring};

fn main() -> Result<()> {
    let filter_nodes: Vec<FilterNode> = vec![
        (
            "title",
            OpValtring::ContainsAny(vec!["Hello".to_string(), "welcome".to_string()]),
        )
            .into(),
        ("done", true).into(),
    ];
    let filter_groups: FilterGroups = filter_nodes.into();

    println!("filter_groups:\n{filter_groups:#?}");

    Ok(())
}

模型或存储层可以获取filter_groups并将它们序列化为其DSL(例如,数据库的SQL)。

过滤器结构如下

  • FilterGroups是顶层,由多个FilterGroup元素组成。FilterGroup元素旨在通过它们之间的OR运算来执行。
  • 每个FilterGroup包含一个FilterNode元素的向量,这些元素旨在通过AND运算来执行。
  • FilterNode包含一个rel(尚未使用),name代表值来源的属性名称,以及一个Vec<OpVal>,表示操作值。
  • OpVal是特定类型的枚举OpVal[Type]实体,例如OpValString,它包含特定类型的特定操作以及关联的模式值。

GitHub仓库

依赖项

~0.7–5MB
~102K SLoC