2个不稳定版本
使用旧Rust 2015
0.1.0 | 2018年2月10日 |
---|---|
0.0.1 | 2015年12月30日 |
#162 在 #稳定版
用于 tql
235KB
5K SLoC
TQL
:source-highlighter: pygments
编译时ORM,受Django ORM启发,用Rust编写。Tql作为过程宏实现,甚至在Rust的稳定版本上也能工作(查看此示例了解如何在稳定版上使用tql)。
此库处于alpha阶段:它尚未经过充分测试,其API可能随时更改。
要求
目前,tql
仅支持PostgreSQL
和SQLite
数据库(未来将支持更多数据库)。因此,您需要安装PostgreSQL
(和/或libsqlite3-sys
),以便使用此crate。
用法
首先,将以下内容添加到您的Cargo.toml
[source,toml]
[dependencies]
chrono = "^0.4.0"
tql_macros = "0.1"
[dependencies.tql]
features = ["chrono", "pg"]
version = "0.1"
[dependencies.postgres]
features = ["with-chrono"]
version = "^0.15.1"
(如果您不希望使用模型中的日期和时间类型,可以删除chrono
相关内容。)
接下来,将以下内容添加到您的crate中
[source,rust]
#![feature(proc_macro_hygiene)]
extern crate chrono;
extern crate postgres;
extern crate tql;
#[macro_use]
extern crate tql_macros;
use postgres::{Connection, TlsMode};
use tql::PrimaryKey;
use tql_macros::sql;
然后,创建您的模型
[source,rust]
use chrono::DateTime;
use chrono::offset::Utc;
#[derive(SqlTable)]
struct Model {
id: PrimaryKey,
text: String,
date_added: DateTime<Utc>,
// …
}
接下来,创建一个连接访问器
[source,rust]
fn get_connection() -> Connection {
Connection::connect("postgres://test:test@localhost/database", TlsMode::None).unwrap()
}
最后,我们可以使用sql!
宏执行一个SQL查询
[source,rust]
fn main() {
let connection = get_connection();
// We first create the table.
// (You might not want to execute this query every time.)
let _ = sql!(Model.create());
// Insert a row in the table.
let text = String::new();
let id = sql!(Model.insert(text = text, date_added = Utc::now())).unwrap();
// Update a row.
let result = sql!(Model.get(id).update(text = "new-text"));
// Delete a row.
let result = sql!(Model.get(id).delete());
// Query some rows from the table:
// get the last 10 rows sorted by date_added descending.
let items = sql!(Model.sort(-date_added)[..10]);
}
sql!()
宏默认使用标识符connection
。
查看以下表格以获取更多示例。
使用SQLite的用法
首先,将postgres
依赖项更改为以下内容
[source,toml]
rusqlite = "^0.13.0"
然后,更改tql
依赖项的功能
[source,toml]
[dependencies.tql]
features = ["sqlite"]
version = "0.1"
在Rust代码中,连接现在需要来自rusqlite
[source,rust]
use rusqlite::Connection;
fn get_connection() -> Connection {
Connection::open("database.db").unwrap()
}
其余部分相同。
在稳定Rust中使用
如果您想在稳定版Rust中使用tql
,需要进行一些更改才能使其工作
首先,删除以下行
[source,rust]
#![feature(proc_macro_hygiene)]
// …
use tql_macros::sql;
然后在extern crate tql
之前添加以下行
[source,rust]
#[macro_use]
这是文件开始的样子
[source,rust]
extern crate chrono;
extern crate postgres;
#[macro_use]
extern crate tql;
#[macro_use]
extern crate tql_macros;
use postgres::{Connection, TlsMode};
use tql::PrimaryKey;
最后,通过更新tql
依赖项来禁用unstable
功能
[source,toml]
[dependencies.tql]
default-features = false
features = ["chrono", "pg"]
version = "0.1"
通过这个小小的改动,我们可以使用 sql!()
,但现在您需要指定连接。
let date_added = Utc::now();
let id = sql!(connection, Model.insert(text = text, date_added = date_added)).unwrap();
另外,由于稳定编译器的限制,您不能再使用表达式作为参数:这就是为什么我们现在创建了一个变量 date_added
。目前,如果您在稳定版上使用 tql
,您需要使用标识符或字面量作为参数。
为什么不总是使用稳定版呢?
过程宏目前不支持在稳定版中在特定位置生成错误,因此使用此版本时,您将获得不太有用的错误,如下所示
[source]
error[E0308]: mismatched types
--> src/main.rs:47:18
|
47 | let result = sql!(Model.insert(text = text, date_added = Utc::now(), done = false));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected &str, found struct `std::string::String`
|
= note: expected type `&str`
found type `std::string::String`
= help: try with `&sql!(Model.insert(text = text, date_added = Utc::now(), done = false))`
= note: this error originates in a macro outside of the current crate
而您将得到以下更友好的错误,当您使用Rust的夜间版本时
[source]
error[E0308]: mismatched types
--> examples/todo.rs:49:46
|
49 | let result = sql!(Model.insert(text = text, date_added = Utc::now(), done = false));
| ^^^^
| |
| expected &str, found struct `std::string::String`
| help: consider borrowing here: `&text`
|
= note: expected type `&str`
found type `std::string::String`
因此,一个良好的工作流程是在夜间版本上开发,然后在稳定版上发布。这样,您可以获得两全其美的好处:您有友好的错误,并且可以使用编译器的稳定版进行部署。这根本不是问题,因为当您准备部署时,不应该有任何编译器错误(您仍然可以看到错误)。
注意:使用以下命令编译以获取更好的错误消息 RUSTFLAGS="--cfg procmacro2_semver_exempt"
语法表
左侧显示生成的SQL,右侧显示您可以使用 tql
使用的语法。
[cols="1a,1a", options="header"] |=== | SQL | Rust
| [source, sql]
SELECT * FROM Table
| [source, rust]
Table.all()
| [source, sql]
SELECT * FROM Table WHERE field1 = 'value1'
| [source, rust]
Table.filter(field1 == "value1")
| [source, sql]
SELECT * FROM Table WHERE primary_key = 42
| [source, rust]
Table.get(42)
// Shortcut for:
Table.filter(primary_key == 42)[0..1];
| [source, sql]
SELECT * FROM Table WHERE field1 = 'value1'
| [source, rust]
Table.get(field1 == "value1")
// Shortcut for:
Table.filter(field1 == "value1")[0..1];
| [source, sql]
SELECT * FROM Table WHERE field1 = 'value1' AND field2 < 100
| [source, rust]
Table.filter(field1 == "value1" && field2 < 100)
| [source, sql]
SELECT * FROM Table WHERE field1 = 'value1' OR field2 < 100
| [source, rust]
Table.filter(field1 == "value1" \|\| field2 < 100)
| [source, sql]
SELECT * FROM Table ORDER BY field1
| [source, rust]
Table.sort(field1)
| [source, sql]
SELECT * FROM Table ORDER BY field1 DESC
| [source, rust]
Table.sort(-field1)
| [source, sql]
SELECT * FROM Table LIMIT 0, 20
| [source, rust]
Table[0..20]
| [source, sql]
SELECT * FROM Table
WHERE field1 = 'value1'
AND field2 < 100
ORDER BY field2 DESC
LIMIT 10, 20
| [source, rust]
Table.filter(field1 == "value1" && field2 < 100)
.sort(-field2)[10..20]
| [source, sql]
INSERT INTO Table(field1, field2) VALUES('value1', 55)
| [source, rust]
Table.insert(field1 = "value1", field2 = 55)
| [source, sql]
UPDATE Table SET field1 = 'value1', field2 = 55 WHERE id = 1
| [source, rust]
Table.get(1).update(field1 = "value1", field2 = 55);
// or
Table.filter(id == 1).update(field1 = "value1", field2 = 55);
| [source, sql]
DELETE FROM Table WHERE id = 1
| [source, rust]
Table.get(1).delete();
// ou
Table.filter(id == 1).delete()
| [source, sql]
SELECT AVG(field2) FROM Table
| [source, rust]
Table.aggregate(avg(field2))
| [source, sql]
SELECT AVG(field1) FROM Table1 GROUP BY field2
| [source, rust]
Table1.values(field2).annotate(avg(field1))
| [source, sql]
SELECT AVG(field1) as average FROM Table1
GROUP BY field2
HAVING average > 5
| [source, rust]
Table1.values(field2).annotate(average = avg(field1))
.filter(average > 5)
| [source, sql]
SELECT AVG(field1) as average FROM Table1
WHERE field1 < 10
GROUP BY field2
HAVING average > 5
| [source, rust]
Table1.filter(field1 < 10).values(field2)
.annotate(average = avg(field1)).filter(average > 5)
| [source, sql]
SELECT Table1.field1, Table2.field1 FROM Table1
INNER JOIN Table2 ON Table1.pk = Table2.fk
| [source, rust]
#[derive(SqlTable)]
struct Table1 {
pk: PrimaryKey,
field1: i32,
}
#[derive(SqlTable)]
struct Table2 {
field1: i32,
fk: ForeignKey<Table1>,
}
Table1.all().join(Table2)
| [source, sql]
SELECT * FROM Table1 WHERE YEAR(date) = 2015
| [source, rust]
Table1.filter(date.year() == 2015)
| [source, sql]
SELECT * FROM Table1 WHERE INSTR(field1, 'string') > 0
| [source, rust]
Table1.filter(field1.contains("string"))
| [source, sql]
SELECT * FROM Table1 WHERE field1 LIKE 'string%'
| [source, rust]
Table1.filter(field1.starts_with("string"))
| [source, sql]
SELECT * FROM Table1 WHERE field1 LIKE '%string'
| [source, rust]
Table1.filter(field1.ends_with("string"))
| [source, sql]
SELECT * FROM Table1 WHERE field1 IS NULL
| [source, rust]
Table1.filter(field1.is_none())
| [source, sql]
SELECT * FROM Table1 WHERE field1 REGEXP BINARY '\^[a-d]'
| [source, rust]
Table1.filter(field1.regex(r"\^[a-d]"))
| [source, sql]
SELECT * FROM Table1 WHERE field1 REGEXP '\^[a-d]'
| [source, rust]
Table1.filter(field1.iregex(r"\^[a-d]"))
| [source, sql]
CREATE TABLE IF NOT EXISTS Table1 (
pk INTEGER NOT NULL AUTO_INCREMENT,
field1 INTEGER,
PRIMARY KEY (pk)
)
| [source, rust]
#[derive(SqlTable)]
struct Table1 {
pk: PrimaryKey,
field1: i32,
}
Table1.create()
|===
捐赠
如果您喜欢这个项目并且希望实现新功能,请在我在Patreon上支持我。
依赖关系
~2.5MB
~53K SLoC