30个版本
新版本 0.4.0 | 2024年8月23日 |
---|---|
0.4.0-rc.8 | 2024年7月26日 |
0.4.0-rc.7 | 2024年6月13日 |
0.4.0-rc.3 | 2024年3月7日 |
0.0.2 | 2022年6月11日 |
#89 in 数据结构
668 每月下载量
在 2 crates 中使用
89KB
2K SLoC
modql
modql 是一套类型和实用工具,旨在以结构化方式表达模型查询过滤器(例如,$eq: ..
$startsWith: ..
,$containsIn: [..]
)和列表选项(例如,offset
,limit
,order_bys
)。这些可以轻松地表示为JSON。
本质上,它提供了一种类似MongoDB的过滤语法,不受存储限制,内置对sea-query的支持,并且可以以JSON或Rust类型表达。
重要: v0.4.0 已发布:此版本包括重大重构(保持相同的功能,但具有更清晰的命名,并与sea-query解耦)以允许
derive(Fields)
提供字段名称和引用,无需要求with-sea-query
功能。有关更多信息,请参阅 CHANGELOG.md 和 MIGRATION-v03x-v04x.md。
谢谢
- Marc Cámara 为添加不区分大小写的支持和Postgresql的
with-ilike
支持表示感谢。 PR #3 - Andrii Yermakov 为修复“数字的null操作符” PR #2
快速概述
/// 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
,它包含特定类型的特定操作以及关联的模式值。
依赖项
~0.7–5MB
~102K SLoC