やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

Rust自習(diesel 7 SQLite3 ORM serde,chrono)

 dieselでchronoの日付型を扱う。

成果物

コード

 前回の続き。マイグレーションで日付を入れるpublished列を追加した。

 マイグレーション実行によってschema.rsファイルなどが変更されたはず。だが、そのままでは動かない。以下のように修正する。

shema.rs

table! {
    posts (id) {
        id -> Integer,
        title -> Text,
        body -> Text,
        is_published -> Bool,
        published -> Nullable<Timestamp>,
    }
}

models.rs

use super::schema::posts;
#[derive(Queryable)]
pub struct Post {
    pub id: i32,
    pub title: String,
    pub body: String,
    pub is_published: bool,
    pub published: Option<chrono::NaiveDateTime>,
}
#[derive(Insertable)]
#[table_name = "posts"]
pub struct NewPost<'a> {
    pub title: &'a str,
    pub body: &'a str,
}

 ちなみに、SQLテーブル定義は以下。

$ sqlite3 ./db.sqlite3
...
sqlite> select * from sqlite_master;
...
table|posts|posts|5|CREATE TABLE posts (
    id integer primary key,
    title text not null,
    body text not null,
    is_published text not null default 'f'
, published text)

実行

 できた!

Displaying 1 posts
記事のタイトル
----------

記事の本文。
複数行を書く。
Ctrl+dキーで終了。
最終行。

失敗ログ

失敗ログ

schema.rs

 見てみるとNullable<Text>になっていた。is_publishedTextに戻ってしまっている。

before

table! {
    posts (id) {
        id -> Nullable<Integer>,
        title -> Text,
        body -> Text,
        is_published -> Text,
        published -> Nullable<Text>,
    }
}

 日付型に変えてみた。

after

table! {
    posts (id) {
        id -> Integer,
        title -> Text,
        body -> Text,
        is_published -> Bool,
        published -> chrono::DateTime<chrono::Local>,
    }
}

 実行。エラー。

$ cargo run --bin show_posts
...
error[E0277]: the trait bound `bool: diesel::Expression` is not satisfied
  --> src/bin/show_posts.rs:13:30
   |
13 |         .filter(is_published.eq(true))
   |                              ^^ the trait `diesel::Expression` is not implemented for `bool`
   |
   = note: required because of the requirements on the impl of `diesel::expression::AsExpression<diesel::sql_types::Text>` for `bool`

error[E0277]: the trait bound `bool: diesel::Expression` is not satisfied
  --> src/bin/show_posts.rs:13:10
   |
13 |         .filter(is_published.eq(true))
   |          ^^^^^^ the trait `diesel::Expression` is not implemented for `bool`
   |
   = note: required because of the requirements on the impl of `diesel::Expression` for `diesel::expression::operators::Eq<diesel_hello::schema::posts::is_published, bool>`
   = note: required because of the requirements on the impl of `diesel::query_dsl::filter_dsl::FilterDsl<diesel::expression::operators::Eq<diesel_hello::schema::posts::is_published, bool>>` for `diesel::query_builder::SelectStatement<diesel_hello::schema::posts::table>`

error[E0277]: the trait bound `bool: diesel::expression::NonAggregate` is not satisfied
  --> src/bin/show_posts.rs:13:10
   |
13 |         .filter(is_published.eq(true))
   |          ^^^^^^ the trait `diesel::expression::NonAggregate` is not implemented for `bool`
   |
   = note: required because of the requirements on the impl of `diesel::expression::NonAggregate` for `diesel::expression::operators::Eq<diesel_hello::schema::posts::is_published, bool>`
   = note: required because of the requirements on the impl of `diesel::query_dsl::filter_dsl::FilterDsl<diesel::expression::operators::Eq<diesel_hello::schema::posts::is_published, bool>>` for `diesel::query_builder::SelectStatement<diesel_hello::schema::posts::table>`

error[E0277]: the trait bound `(i32, std::string::String, std::string::String, bool): diesel::Queryable<(diesel::sql_types::Nullable<diesel::sql_types::Integer>, diesel::sql_types::Text, diesel::sql_types::Text, diesel::sql_types::Text, diesel::sql_types::Nullable<diesel::sql_types::Text>), _>` is not satisfied
  --> src/bin/show_posts.rs:15:10
   |
15 |         .load::<Post>(&connection)
   |          ^^^^ the trait `diesel::Queryable<(diesel::sql_types::Nullable<diesel::sql_types::Integer>, diesel::sql_types::Text, diesel::sql_types::Text, diesel::sql_types::Text, diesel::sql_types::Nullable<diesel::sql_types::Text>), _>` is not implemented for `(i32, std::string::String, std::string::String, bool)`
   |
   = help: the following implementations were found:
             <(A, B, C, D) as diesel::Queryable<(SA, SB, SC, SD), __DB>>
   = note: required because of the requirements on the impl of `diesel::Queryable<(diesel::sql_types::Nullable<diesel::sql_types::Integer>, diesel::sql_types::Text, diesel::sql_types::Text, diesel::sql_types::Text, diesel::sql_types::Nullable<diesel::sql_types::Text>), _>` for `diesel_hello::models::Post`
   = note: required because of the requirements on the impl of `diesel::query_dsl::LoadQuery<_, diesel_hello::models::Post>` for `diesel::query_builder::SelectStatement<diesel_hello::schema::posts::table, diesel::query_builder::select_clause::DefaultSelectClause, diesel::query_builder::distinct_clause::NoDistinctClause, diesel::query_builder::where_clause::WhereClause<diesel::expression::operators::Eq<diesel_hello::schema::posts::is_published, bool>>, diesel::query_builder::order_clause::NoOrderClause, diesel::query_builder::limit_clause::LimitClause<diesel::expression::bound::Bound<diesel::sql_types::BigInt, i64>>>`

error[E0277]: the trait bound `bool: diesel::AppearsOnTable<diesel_hello::schema::posts::table>` is not satisfied
  --> src/bin/show_posts.rs:15:10
   |
15 |         .load::<Post>(&connection)
   |          ^^^^ the trait `diesel::AppearsOnTable<diesel_hello::schema::posts::table>` is not implemented for `bool`
   |
   = note: required because of the requirements on the impl of `diesel::AppearsOnTable<diesel_hello::schema::posts::table>` for `diesel::expression::operators::Eq<diesel_hello::schema::posts::is_published, bool>`
   = note: required because of the requirements on the impl of `diesel::query_builder::where_clause::ValidWhereClause<diesel_hello::schema::posts::table>` for `diesel::query_builder::where_clause::WhereClause<diesel::expression::operators::Eq<diesel_hello::schema::posts::is_published, bool>>`
   = note: required because of the requirements on the impl of `diesel::query_builder::Query` for `diesel::query_builder::SelectStatement<diesel_hello::schema::posts::table, diesel::query_builder::select_clause::DefaultSelectClause, diesel::query_builder::distinct_clause::NoDistinctClause, diesel::query_builder::where_clause::WhereClause<diesel::expression::operators::Eq<diesel_hello::schema::posts::is_published, bool>>, diesel::query_builder::order_clause::NoOrderClause, diesel::query_builder::limit_clause::LimitClause<diesel::expression::bound::Bound<diesel::sql_types::BigInt, i64>>>`
   = note: required because of the requirements on the impl of `diesel::query_dsl::LoadQuery<_, diesel_hello::models::Post>` for `diesel::query_builder::SelectStatement<diesel_hello::schema::posts::table, diesel::query_builder::select_clause::DefaultSelectClause, diesel::query_builder::distinct_clause::NoDistinctClause, diesel::query_builder::where_clause::WhereClause<diesel::expression::operators::Eq<diesel_hello::schema::posts::is_published, bool>>, diesel::query_builder::order_clause::NoOrderClause, diesel::query_builder::limit_clause::LimitClause<diesel::expression::bound::Bound<diesel::sql_types::BigInt, i64>>>`

error[E0277]: the trait bound `bool: diesel::query_builder::QueryFragment<_>` is not satisfied
  --> src/bin/show_posts.rs:15:10
   |
15 |         .load::<Post>(&connection)
   |          ^^^^ the trait `diesel::query_builder::QueryFragment<_>` is not implemented for `bool`
   |
   = note: required because of the requirements on the impl of `diesel::query_builder::QueryFragment<_>` for `diesel::expression::operators::Eq<diesel_hello::schema::posts::is_published, bool>`
   = note: required because of the requirements on the impl of `diesel::query_builder::QueryFragment<_>` for `diesel::query_builder::where_clause::WhereClause<diesel::expression::operators::Eq<diesel_hello::schema::posts::is_published, bool>>`
   = note: required because of the requirements on the impl of `diesel::query_builder::QueryFragment<_>` for `diesel::query_builder::SelectStatement<diesel_hello::schema::posts::table, diesel::query_builder::select_clause::DefaultSelectClause, diesel::query_builder::distinct_clause::NoDistinctClause, diesel::query_builder::where_clause::WhereClause<diesel::expression::operators::Eq<diesel_hello::schema::posts::is_published, bool>>, diesel::query_builder::order_clause::NoOrderClause, diesel::query_builder::limit_clause::LimitClause<diesel::expression::bound::Bound<diesel::sql_types::BigInt, i64>>>`
   = note: required because of the requirements on the impl of `diesel::query_dsl::LoadQuery<_, diesel_hello::models::Post>` for `diesel::query_builder::SelectStatement<diesel_hello::schema::posts::table, diesel::query_builder::select_clause::DefaultSelectClause, diesel::query_builder::distinct_clause::NoDistinctClause, diesel::query_builder::where_clause::WhereClause<diesel::expression::operators::Eq<diesel_hello::schema::posts::is_published, bool>>, diesel::query_builder::order_clause::NoOrderClause, diesel::query_builder::limit_clause::LimitClause<diesel::expression::bound::Bound<diesel::sql_types::BigInt, i64>>>`

error[E0277]: the trait bound `bool: diesel::query_builder::QueryId` is not satisfied
  --> src/bin/show_posts.rs:15:10
   |
15 |         .load::<Post>(&connection)
   |          ^^^^ the trait `diesel::query_builder::QueryId` is not implemented for `bool`
   |
   = note: required because of the requirements on the impl of `diesel::query_builder::QueryId` for `diesel::expression::operators::Eq<diesel_hello::schema::posts::is_published, bool>`
   = note: required because of the requirements on the impl of `diesel::query_builder::QueryId` for `diesel::query_builder::where_clause::WhereClause<diesel::expression::operators::Eq<diesel_hello::schema::posts::is_published, bool>>`
   = note: required because of the requirements on the impl of `diesel::query_builder::QueryId` for `diesel::query_builder::SelectStatement<diesel_hello::schema::posts::table, diesel::query_builder::select_clause::DefaultSelectClause, diesel::query_builder::distinct_clause::NoDistinctClause, diesel::query_builder::where_clause::WhereClause<diesel::expression::operators::Eq<diesel_hello::schema::posts::is_published, bool>>, diesel::query_builder::order_clause::NoOrderClause, diesel::query_builder::limit_clause::LimitClause<diesel::expression::bound::Bound<diesel::sql_types::BigInt, i64>>>`
   = note: required because of the requirements on the impl of `diesel::query_dsl::LoadQuery<_, diesel_hello::models::Post>` for `diesel::query_builder::SelectStatement<diesel_hello::schema::posts::table, diesel::query_builder::select_clause::DefaultSelectClause, diesel::query_builder::distinct_clause::NoDistinctClause, diesel::query_builder::where_clause::WhereClause<diesel::expression::operators::Eq<diesel_hello::schema::posts::is_published, bool>>, diesel::query_builder::order_clause::NoOrderClause, diesel::query_builder::limit_clause::LimitClause<diesel::expression::bound::Bound<diesel::sql_types::BigInt, i64>>>`

error: aborting due to 7 previous errors

 これを以下のように編集する。

schema.rs

table! {
    posts (id) {
        id -> Integer,
        title -> Text,
        body -> Text,
        is_published -> Bool,
        published -> chrono::DateTime<chrono::Local>,
    }
}

models.rs

// ...
pub struct Post {
    pub id: i32,
    pub title: String,
    pub body: String,
    pub is_published: bool,
    pub published: chrono::DateTime<chrono::Local>,
}
// ...

 エラー。

error[E0277]: the trait bound `chrono::datetime::DateTime<chrono::offset::local::Local>: diesel::deserialize::FromSql<chrono::datetime::DateTime<chrono::offset::local::Local>, diesel::sqlite::Sqlite>` is not satisfied
  --> src/bin/show_posts.rs:15:10
   |
15 |         .load::<Post>(&connection)
   |          ^^^^ the trait `diesel::deserialize::FromSql<chrono::datetime::DateTime<chrono::offset::local::Local>, diesel::sqlite::Sqlite>` is not implemented for `chrono::datetime::DateTime<chrono::offset::local::Local>`
   |
   = note: required because of the requirements on the impl of `diesel::Queryable<chrono::datetime::DateTime<chrono::offset::local::Local>, diesel::sqlite::Sqlite>` for `chrono::datetime::DateTime<chrono::offset::local::Local>`
   = note: required because of the requirements on the impl of `diesel::Queryable<(diesel::sql_types::Integer, diesel::sql_types::Text, diesel::sql_types::Text, diesel::sql_types::Bool, chrono::datetime::DateTime<chrono::offset::local::Local>), diesel::sqlite::Sqlite>` for `(i32, std::string::String, std::string::String, bool, chrono::datetime::DateTime<chrono::offset::local::Local>)`
   = note: required because of the requirements on the impl of `diesel::Queryable<(diesel::sql_types::Integer, diesel::sql_types::Text, diesel::sql_types::Text, diesel::sql_types::Bool, chrono::datetime::DateTime<chrono::offset::local::Local>), diesel::sqlite::Sqlite>` for `diesel_hello::models::Post`
   = note: required because of the requirements on the impl of `diesel::query_dsl::LoadQuery<_, diesel_hello::models::Post>` for `diesel::query_builder::SelectStatement<diesel_hello::schema::posts::table, diesel::query_builder::select_clause::DefaultSelectClause, diesel::query_builder::distinct_clause::NoDistinctClause, diesel::query_builder::where_clause::WhereClause<diesel::expression::operators::Eq<diesel_hello::schema::posts::is_published, diesel::expression::bound::Bound<diesel::sql_types::Bool, bool>>>, diesel::query_builder::order_clause::NoOrderClause, diesel::query_builder::limit_clause::LimitClause<diesel::expression::bound::Bound<diesel::sql_types::BigInt, i64>>>`

error[E0277]: the trait bound `diesel::sqlite::Sqlite: diesel::sql_types::HasSqlType<chrono::datetime::DateTime<chrono::offset::local::Local>>` is not satisfied
  --> src/bin/show_posts.rs:15:10
   |
15 |         .load::<Post>(&connection)
   |          ^^^^ the trait `diesel::sql_types::HasSqlType<chrono::datetime::DateTime<chrono::offset::local::Local>>` is not implemented for `diesel::sqlite::Sqlite`
   |
   = help: the following implementations were found:
             <diesel::sqlite::Sqlite as diesel::sql_types::HasSqlType<diesel::sql_types::BigInt>>
             <diesel::sqlite::Sqlite as diesel::sql_types::HasSqlType<diesel::sql_types::Binary>>
             <diesel::sqlite::Sqlite as diesel::sql_types::HasSqlType<diesel::sql_types::Bool>>
             <diesel::sqlite::Sqlite as diesel::sql_types::HasSqlType<diesel::sql_types::Date>>
           and 8 others
   = note: required because of the requirements on the impl of `diesel::sql_types::HasSqlType<(diesel::sql_types::Integer, diesel::sql_types::Text, diesel::sql_types::Text, diesel::sql_types::Bool, chrono::datetime::DateTime<chrono::offset::local::Local>)>` for `diesel::sqlite::Sqlite`
   = note: required because of the requirements on the impl of `diesel::query_dsl::LoadQuery<diesel::SqliteConnection, diesel_hello::models::Post>` for `diesel::query_builder::SelectStatement<diesel_hello::schema::posts::table, diesel::query_builder::select_clause::DefaultSelectClause, diesel::query_builder::distinct_clause::NoDistinctClause, diesel::query_builder::where_clause::WhereClause<diesel::expression::operators::Eq<diesel_hello::schema::posts::is_published, diesel::expression::bound::Bound<diesel::sql_types::Bool, bool>>>, diesel::query_builder::order_clause::NoOrderClause, diesel::query_builder::limit_clause::LimitClause<diesel::expression::bound::Bound<diesel::sql_types::BigInt, i64>>>`

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0277`.
error: Could not compile `diesel_hello`.
warning: build failed, waiting for other jobs to finish...
error[E0277]: the trait bound `diesel::sqlite::Sqlite: diesel::sql_types::HasSqlType<chrono::datetime::DateTime<chrono::offset::local::Local>>` is not satisfied
  --> src/bin/publish_post.rs:25:10
   |
25 |         .first(&connection)
   |          ^^^^^ the trait `diesel::sql_types::HasSqlType<chrono::datetime::DateTime<chrono::offset::local::Local>>` is not implemented for `diesel::sqlite::Sqlite`
   |
   = help: the following implementations were found:
             <diesel::sqlite::Sqlite as diesel::sql_types::HasSqlType<diesel::sql_types::BigInt>>
             <diesel::sqlite::Sqlite as diesel::sql_types::HasSqlType<diesel::sql_types::Binary>>
             <diesel::sqlite::Sqlite as diesel::sql_types::HasSqlType<diesel::sql_types::Bool>>
             <diesel::sqlite::Sqlite as diesel::sql_types::HasSqlType<diesel::sql_types::Date>>
           and 8 others
   = note: required because of the requirements on the impl of `diesel::sql_types::HasSqlType<(diesel::sql_types::Integer, diesel::sql_types::Text, diesel::sql_types::Text, diesel::sql_types::Bool, chrono::datetime::DateTime<chrono::offset::local::Local>)>` for `diesel::sqlite::Sqlite`
   = note: required because of the requirements on the impl of `diesel::query_dsl::LoadQuery<diesel::SqliteConnection, _>` for `diesel::query_builder::SelectStatement<diesel_hello::schema::posts::table, diesel::query_builder::select_clause::DefaultSelectClause, diesel::query_builder::distinct_clause::NoDistinctClause, diesel::query_builder::where_clause::WhereClause<diesel::expression::operators::Eq<diesel_hello::schema::posts::id, diesel::expression::bound::Bound<diesel::sql_types::Integer, i32>>>, diesel::query_builder::order_clause::NoOrderClause, diesel::query_builder::limit_clause::LimitClause<diesel::expression::bound::Bound<diesel::sql_types::BigInt, i64>>>`

error[E0277]: the trait bound `chrono::datetime::DateTime<chrono::offset::local::Local>: diesel::deserialize::FromSql<chrono::datetime::DateTime<chrono::offset::local::Local>, diesel::sqlite::Sqlite>` is not satisfied
  --> src/bin/publish_post.rs:25:10
   |
25 |         .first(&connection)
   |          ^^^^^ the trait `diesel::deserialize::FromSql<chrono::datetime::DateTime<chrono::offset::local::Local>, diesel::sqlite::Sqlite>` is not implemented for `chrono::datetime::DateTime<chrono::offset::local::Local>`
   |
   = note: required because of the requirements on the impl of `diesel::Queryable<chrono::datetime::DateTime<chrono::offset::local::Local>, diesel::sqlite::Sqlite>` for `chrono::datetime::DateTime<chrono::offset::local::Local>`
   = note: required because of the requirements on the impl of `diesel::Queryable<(diesel::sql_types::Integer, diesel::sql_types::Text, diesel::sql_types::Text, diesel::sql_types::Bool, chrono::datetime::DateTime<chrono::offset::local::Local>), diesel::sqlite::Sqlite>` for `(i32, std::string::String, std::string::String, bool, chrono::datetime::DateTime<chrono::offset::local::Local>)`
   = note: required because of the requirements on the impl of `diesel::Queryable<(diesel::sql_types::Integer, diesel::sql_types::Text, diesel::sql_types::Text, diesel::sql_types::Bool, chrono::datetime::DateTime<chrono::offset::local::Local>), diesel::sqlite::Sqlite>` for `diesel_hello::models::Post`
   = note: required because of the requirements on the impl of `diesel::query_dsl::LoadQuery<diesel::SqliteConnection, diesel_hello::models::Post>` for `diesel::query_builder::SelectStatement<diesel_hello::schema::posts::table, diesel::query_builder::select_clause::DefaultSelectClause, diesel::query_builder::distinct_clause::NoDistinctClause, diesel::query_builder::where_clause::WhereClause<diesel::expression::operators::Eq<diesel_hello::schema::posts::id, diesel::expression::bound::Bound<diesel::sql_types::Integer, i32>>>, diesel::query_builder::order_clause::NoOrderClause, diesel::query_builder::limit_clause::LimitClause<diesel::expression::bound::Bound<diesel::sql_types::BigInt, i64>>>`

調査

 ググってみる。

 とうやら以下2つの型と互換性があるらしい。

  • [std::time::SystemTime](https://doc.rust-lang.org/nightly/std/time/struct.SystemTime.html)
  • [chrono::NaiveDateTime](https://erickt.github.io/chrono/naive/datetime/struct.NaiveDateTime.html) with feature = "chrono"

 UTC時刻が使えないだと……。

 まあいいや。以下のように修正する。

schema.rs

table! {
    posts (id) {
        id -> Integer,
        title -> Text,
        body -> Text,
        is_published -> Bool,
        published -> Timestamp,
    }
}

models.rs

// ...
pub struct Post {
    pub id: i32,
    pub title: String,
    pub body: String,
    pub is_published: bool,
    pub published: chrono::NaiveDateTime,
}
// ...

 実行。cargo buildはできたが、以下はパニックした。

$ cargo run --bin show_posts
...
thread 'main' panicked at 'Error loading posts: DeserializationError(UnexpectedNullError)', src/libcore/result.rs:999:5
note: Run with `RUST_BACKTRACE=1` environment variable to display a backtrace.

 「UnexpectedNullError」とある。published列はNULLを許容する。それをコードに反映させねばならないのだと思う。たぶんNullable<T>だろう。rust側はOption<T>かな?

shema.rs

table! {
    posts (id) {
        // ...
        published -> Nullable<Timestamp>,
    }
}

models.rs

pub struct Post {
    // ...
    pub published: Option<chrono::NaiveDateTime>,
}

 実行。できた!

Displaying 1 posts
記事のタイトル
----------

記事の本文。
複数行を書く。
Ctrl+dキーで終了。
最終行。

対象環境

$ uname -a
Linux raspberrypi 4.19.42-v7+ #1219 SMP Tue May 14 21:20:58 BST 2019 armv7l GNU/Linux

前回まで