LoginSignup
15
5

More than 5 years have passed since last update.

TQL (compile-time Rust ORM) を使ってみたメモ

Last updated at Posted at 2017-12-21

RustなORMとして有名なのはDieselですが、もう少し手軽に使えるものがないかなと思っていました。

生のクエリは書きたくないけど、手軽には使い始めたい。
そんなあなたにtqlです。

READMEに記載されている例になりますが、以下のような形でデータベースにアクセスできます。

fn main() {
    let _ = sql!(Model.create());

    let text = String::new();

    let id = sql!(Model.insert(text = &text, date_added = Utc::now())).unwrap();

    let result = sql!(Model.get(id).update(text = "new-text"));

    let result = sql!(Model.get(id).delete());

    let items = sql!(Model.sort(-date_added)[..10]);
}

なかなか良さげです。

ただし現時点ではPostgreSQLしかサポートしていないので、SQLiteやMySQLでは使えません。
SQLiteサポートについてはGitHub Issueにあがっているので、近いうちに使えるようになるのではないかと淡い期待を寄せています。今の所は、SQLiteであればrusqlite、MySQLであればrust-mysql-simpleあたりをつかうのが良い気がします。

TQLを使ってみる

さっそく使ってみます。
特に記載がなければnightlyチャネル前提です。

まずはcreatedbコマンドでデータベース作っておきます。
tql_exampleという名前にします。(特に制約はありません)

$ createdb tql_example
$ psql -l
                                        List of databases
      Name       |   Owner   | Encoding |   Collate   |    Ctype    |      Access privileges
-----------------+-----------+----------+-------------+-------------+-----------------------------
 tql_example     | hogera    | UTF8     | ja_JP.UTF-8 | ja_JP.UTF-8 |

ここからRustでアクセスしていきます。

TQLはcrates.ioに登録されているバージョンが古いので、GitHubリポジトリの最新版を使います。

Cargo.toml
[dependencies]
chrono = "^0.4.0"
tql = { git = "https://github.com/antoyo/tql" }
tql_macros = { git = "https://github.com/antoyo/tql" }

[dependencies.postgres]
features = ["with-chrono"]
version = "^0.15.1"

chronoは時刻データ型をいい感じに扱えるので、一緒にCargo.tomlに追加しておくと良いです。

あとは必要なクレイトを読み込んでおきます。

src/main.rs
#![feature(proc_macro)]

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, to_sql};
use chrono::DateTime;
use chrono::offset::Utc;

スキーマ定義とテーブル作成

以下のようなPersonテーブルを定義することにします。

カラム名 その他
id integer プライマリキー
name varchar not null
age integer nullable
created timestamp not null

Personテーブルにアクセスするために、Person構造体をつくります。

src/main.rs
#[derive(SqlTable)]
struct Person {
    id: PrimaryKey,
    name: String,
    age: Option<i32>,
    created: DateTime<Utc>,
}

データベース接続する関数。

src/main.rs
fn get_connection() -> Connection {
    Connection::connect("postgres://user:password@hostname/tql_example", TlsMode::None).unwrap()
}

テーブルを作ってみます。
ここはイマイチなところなのですが、get_connection()で取得するデータベースコネクションオブジェクトは、今の所必ずconnection変数にする必要があります。
クエリ実行するsql!()マクロに接続オブジェクトを渡すことができず固定で処理しているようです。
TODOになってるのでそのうち変わりそうな気がします。

src/main.rs
fn main() {
    let connection = get_connection();

    let ret = sql!(Person.create());
    println!("ret={:?}", ret);
}

実行

$ cargo +nightly run --release
ret=Ok(0)
$ psql tql_example -c "\d person"
                                  Table "public.person"
 Column  |           Type           |                      Modifiers
---------+--------------------------+-----------------------------------------------------
 age     | integer                  |
 created | timestamp with time zone | not null
 id      | integer                  | not null default nextval('person_id_seq'::regclass)
 name    | character varying        | not null
Indexes:
    "person_pkey" PRIMARY KEY, btree (id)

personテーブルが作成できました。

INSERT

src/main.rs
fn main() {
    let connection = get_connection();
    for (name, age) in vec!(("たろう", Some(20)), ("はなこ", Some(30)), ("さとし", None),
                            ("せつこ", Some(50)), ("むが", Some(60))) {
        let name = String::from(name);
        let id = match age {
            Some(v) => sql!(Person.insert(name=&name, age=v, created=Utc::now())).expect("fail insert(Person)"),
            None =>  sql!(Person.insert(name=&name, created=Utc::now())).expect("fail insert(Person)"),
        };
        println!("id={:?}", id);
    }
}

実行してみます。

$ cargo +nightly run --release
id=1
id=2
id=3
id=4
id=5

データ挿入した時のidを返します。psqlコマンドでデータ挿入されたかどうか確認します。

$ psql tql_example -c "select * from person"
 age |            created            | id |  name
-----+-------------------------------+----+--------
  20 | 2017-12-21 19:35:32.29468+09  |  1 | たろう
  30 | 2017-12-21 19:35:32.297931+09 |  2 | はなこ
     | 2017-12-21 19:35:32.299607+09 |  3 | さとし
  50 | 2017-12-21 19:35:32.301274+09 |  4 | せつこ
  60 | 2017-12-21 19:35:32.302847+09 |  5 | むが

おっけーそうです。

SELECT

おもむろにデータ取得してみます。

src/main.rs
fn main() {
    let connection = get_connection();
    for row in sql!(Person[2..5]) {
        println!("row={:?}", row);
    }
$ cargo +nightly run --release
row=Person { id: 3, name: "さとし", age: None, created: 2017-12-21T10:35:32.299607Z }
row=Person { id: 4, name: "せつこ", age: Some(50), created: 2017-12-21T10:35:32.301274Z }
row=Person { id: 5, name: "むが", age: Some(60), created: 2017-12-21T10:35:32.302847Z }

どんなクエリ発行してるか見たい場合は、to_sql!()マクロで構築されたSQLクエリを返してくれるので、それを出力することで確認することができます。

src/main.rs
fn main() {
    let connection = get_connection();
    let query = to_sql!(Person[2..5]);
    println!("query='{}'", query);
}
$ cargo +nightly run --release
query='SELECT Person.age, Person.created, Person.id, Person.name FROM Person OFFSET 2 LIMIT 3'

その他sql!()マクロで使える構文

このあたりに一通りまとまっています。

stableチャネルで使う

どうしてもstableチャネルで使いたい場合は、以下の変更で使うことができます。

extern crate tql#[macro_use]を追加。

src/main.rs
#[macro_use]
extern crate tql;

以下の行を削除。

src/main.rs
#![feature(proc_macro)]

use tql_macros::sql;

Cargo.tomlの依存関係のtqlの箇所にdefault-features=false`を追加

Cargo.toml
tql = { git = "https://github.com/antoyo/tql", default-features = false }

stableとnightlyでエラーメッセージが以下のように異なります。

stable
error: mismatched types:
 expected `Option<i32>`,
    found `floating-point variable`
note: in this expansion of sql! (defined in tql)
  --> src/main.rs:32:14
   |
32 |     let id = sql!(Person.insert(name=&name, age=30.1, created=Utc::now())).expect("fail insert(Person)");
   |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: this error originates in a macro outside of the current crate
nightly
error: mismatched types:
 expected `Option<i32>`,
    found `floating-point variable`
  --> src/main.rs:35:49
   |
35 |     let id = sql!(Person.insert(name=&name, age=30.1, created=Utc::now())).expect("fail insert(Person)");
   |                                                 ^^^^
   |
   = note: in this expansion of sql! (defined in tql)

nightlyチャネルの場合、ピンポイントで誤りのある箇所を指摘してくれます。よいですね。

おわりに

わりとREADMEなぞっただけの内容になってしまいましたが、できることは一通り紹介してみました。
まだまだTODO等残っておりAPI変わりそうな感じもしますが、一度試してみてもよいのではないでしょうか。

15
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
5