1 个不稳定版本

使用旧的 Rust 2015

0.1.0 2015年7月23日

#1298数据库接口

AML 许可证

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

无运行时依赖项