1 个不稳定版本
使用旧的 Rust 2015
0.1.0 | 2015年7月23日 |
---|
#1298 在 数据库接口
4KB
宝藏 ORM
** 此项目已由 Buildix 取代 - sqlx 的查询构建器 **
!!!! 宝藏 ORM 正处于实验阶段 !!!
Rust ORM 库(或者说是一个概念验证,接下来将进行大量开发),灵感来自令人惊叹的 django 框架。
这仍然是一个实验,我可能从 ORM 应该开始的另一端开始的,但我认为定义模型及其列的简便性是每个 ORM 的“卖点”。之后是查询语言。
宝藏 ORM 将首先在 Postgres 上进行开发,但考虑到多种可用的方言。
所有 ORM 都使用某种形式的反射来获取有关所有列的信息,这在动态语言中很容易实现,但在静态编译语言中应该以另一种方式完成。Rust 有一个健康的宏系统,其中宝藏使用语法表达式来生成所需的代码。我知道你说“这不是 Rust 的惯用方法”,但它有助于避免很多问题。让我展示我的想法,模型定义应该如何看起来。现在我将省略 ForeignKey、ManyToMany 和 OneToOne,我现在还在为这些进行设计决策。
#![feature(custom_attribute,plugin)]
#![plugin(treasure)]
extern crate treasure;
use treasure::models::model::Model;
#[model(db_name="custom_user",primary_key="id",unique(email,test),unique(some,other),index(some,other)]
struct User {
#[column(db_name="ID",primary_key)]
pub id: i32,
#[column(unique)]
pub username: String,
#[column]
pub password: Option<String>,
#[column]
pub email: String,
#[column]
pub some: String,
#[column(db_name="custom_other")]
pub other: String,
}
宝藏将生成 Model trait 实现方法,例如
fn model_options(&self) -> ModelOptions;
它返回有关模型的检查信息(将来为提高速度存储为静态?)
fn init_new() -> Self;
这是一个构造函数方法,用于创建新的模型实例。
现在你可以尝试运行来查看 Treasure orm 当前正在做什么
cargo run --example simple --verbose
出于调试/实现目的,宝藏将生成的实现输出到 stdout。
设计
每个可持久化的结构都应该实现 trait Model(将此结构称为模型)。此结构必须使用“model”属性进行注释,以告诉宝藏检查此模型并生成所需的 Model trait 方法。模型中注释为“column”属性的所有字段都将被视为数据库列。其他未注释的字段将在选项列表中标记为“未使用”。
模型属性
目前模型有以下可能的注释
- db_name - 数据库表名,如果没有提供,则使用结构的蛇形名称
- primary_key - 主键列的名称,如果设置不正确,编译器必须引发合理的错误。主键也可以设置为列属性“primary_key”
- unique - 具有唯一性的列列表。可能存在多个实例。这不会经常使用,但在数据库迁移(我们将在未来支持)中会使用。
- index - 应该属于索引的字段列表。也可能存在多个实例。(用于迁移)
- managed - Treasure 是否应该处理数据库中模型(未来)的创建。主要目标是当出现错误时编写详尽的编译器错误。我们必须有很多验证!我们可以做更多!
需要做出决定:添加 inline_options 注解以内联 model_options() 方法
列属性
你想要持久化到数据库表的每个结构字段都必须使用 "column" 注解。对于列,有以下可能的注解:
- db_name - 数据库表列名称,如果没有给出,则使用结构字段名称。
- primary_key - 列是 primary_key 的信息。
- unique - 将列视为唯一(将在数据库迁移中使用)。
- index - 将索引附加到列(在数据库迁移中)。
- not_persist - 不要将此字段持久化到数据库。
这些已实现并添加到 ColumnInfo,它包含有关列的所有信息。你可以看到我们有一个 Column 特征,所有 POD 类型都必须实现。此外,未来的 Option(表示可空列)、ForeignKey、ManyToMany、OneToOne 将实现。这为我们定义未来的新字段提供了有趣的方法(例如 postgres 数组)。
列属性将是可扩展的,因此每个 Column 实现者都可以有自己的附加注解属性集,例如,对于数字类型,我们可以实现 min、max、default 等。天空是无限的。
列验证
在设计决策中!每个模型都将有自己的可能性来提供 validation_fn 在模型注解中调用。此外,Column 特征将有一个 validate 方法,Treasure 将使用参数:列值、给定列的 ColumnOptions 实例来调用。
代码生成
Treasure 正在进行大量的代码生成,以便易于使用而无需重复代码。此外,对于未来的查询语言,我们需要有关模型及其字段的检查信息。这就是为什么 Treasure 为每个模型生成支持方法,这提供了所有模型信息。Treasure 还为每个模型生成 init_new 函数,其中调用 Column::init,带有 ColumnOptions 参数,以便它可以返回适当的值(默认值?)
查询
Treasure ORM 提供一组宏来简化模型查询。这部分仍在制作中,已经编写了一些 select 宏的小部分,但现在它们需要连接到实际的 Builder。Treasure 将提供两个 builder:Builder - 此 builder 在表和列上工作,ModelBuilder - 此 builder 将紧密耦合到模型,并具有返回 Builder 的方法,该 Builder 将从给定模型的数据中填充。
Builder 还将具有将结果“映射”到对象的能力,可能是一个接受闭包并具有参数 rows(单行模式)的函数。这些 rows 不会是数据库引擎的直接 rows,而是它们之上的抽象,因为我们还有为模型中定义的列(db_name)定义“别名”的可能性。
查询宏
查询宏的名称与 SQL 对应物相同。
- select - 执行选择查询的宏
- update - 执行更新查询的宏(单个实例或多行)
- delete - 执行删除查询的宏(单个实例或多行)
- insert - 将模型实例插入到数据库的宏
每个宏都有一个标识符作为第一个参数,后跟 "[",其中指定查询的所有部分,以 "]" 结尾。选择查询有两种可能:
- many: - 这是用于选择多个对象
select![many:User[<query_parts>]]
- one: - 这是从数据库中选择一个对象。(待办:DoesNotExist、MultipleObjectsReturned 异常)
select![one:User[<query_parts>]]
示例中显示的 <query_parts> 部分是所有修饰符设置的地方。这些修饰符的定义方式如下:[]。您可以看到,修饰符之间没有用逗号分隔,这是宏的特性,而不是它们的值被方括号包围,这使得它们非常易于阅读。在下面的示例中您可以看到这一点。
示例
select![many:User[
filter[
["age__lt" => 10]
]
limit[1, 10]
]]
在接下来的部分中,我将尝试解释查询中的每个 query_part
过滤器
过滤器适用于以下查询:select、update(mass)、delete(mass)
在过滤器中,您可以指定单独的子句,例如
["username" => "phonkee"]
首先是一个模型列的名称,后面跟着 => 和值。列名可以有字段查找(例如在 django 中)。查找的形式如下:["field__lookuptype" => value]
。如果没有指定查找类型,则使用 "__exact"。计划支持以下查找类型:
- 精确匹配
- 不精确匹配
- 包含
- 不包含
- 在...之内
- 大于
- 大于等于
- 小于
- 小于等于
- 以...开始
- 以...开始
- 以...结束
- 以...结束
- 范围
- 年份
- 月份
- 日期
- 星期几
- 为空
- 搜索
- 正则表达式
- 不区分大小写的正则表达式
以及其他...
@TODO:添加 not[....] 修饰符。
过滤器还支持 AND 和 OR 条件。它们都有这种格式和 [...],或 [...]。您可以随意堆叠它们。如果您在过滤器中不提供单个 "and" 或 "or",它们将默认被 AND 子句包裹。
select!(many:User[
filter[
["name__icontains" => "Peter"]
["age__gte" => 30]
]
])
将自动包裹到 AND 子句中,并将等于这个
select!(many:User[
filter[
and [
["name__icontains" => "Peter"]
["age__gte" => 30]
]
]
])
AND 和 OR 子句可以堆叠,因此您可以创建非常复杂的子句
select!(many:User[
filter[
or [
["something__icontains" => "ehm"]
]
and [
["name__icontains" => "Peter"]
["age__gte" => 30]
]
["one__in" => ["one", "two", "three"]
]
])
将等于这个
select!(many:User[
filter[
and [
or [
["something__icontains" => "ehm"]
]
and [
["name__icontains" => "Peter"]
["age__gte" => 30]
]
["one__in" => ["one", "two", "three"]
]
]
])
一个很好的例子是 示例
这难道不漂亮吗?我希望您像我一样喜欢这种查询语言,以后还有更多的更新,还有更多的事情要实现,是的,我指的是这些查询宏中仅仅只是冰山一角。比如更新已经实例化的查询构建器,添加额外的过滤器、限制等...
TODO:定义其他宏
选择
选择宏实例化选择查询构建器,并用模型选项预先填充。
// selecting data from database
select!(many:User[
filter[
["username" => "phonkee"]
["age__gte" => 30]
]
]).collect(db)
更新
更新宏有两种调用方式
- update!(model_instance[...]) - 这将更新模型实例
- update!(many:User[...]) - 这将对数据库进行批量更新(TODO:尚未实现)
示例
// example of update of single model instance
let _user = User::init_new();
let _qb = update!(user[
columns[
"count_logins",
"last_logged"
]
]);
// example of update of single model instance
// @TODO: implement, find out how to do additions...
let _qb = update!(many:User[
set[
["quote" => "something"]
]
]);
信号
添加对信号的支持,这些信号可能出现在注释中,例如
#[model(pre_insert="pre_insert")]
struct User {
}
支持的信号
- post_load - 对象从数据库加载后
- pre_insert - 在向数据库插入之前
- post_insert - 在向数据库插入之后
- pre_update - 在更新模型实例到数据库之前
- post_update - 在更新到数据库之后
这将生成更多的代码....
数据库连接
需要设计决策!编写结果包装器(所有方言都可以实现)。
贡献
如果您想贡献想法和/或代码,我将非常高兴!
作者:Peter Vrba (phonkee) 许可证:MIT