Insert

データベースにレコードを新しく登録する方法です。

環境は Postgres のバージョン 11 を使います。またその環境は Docker を用いて以下のコマンドで建てたサーバーを使います。

docker run \
  --name postgres \
  --detach \
  --publish 5432:5432 \
  --env POSTGRES_HOST_AUTH_METHOD=trust \
  postgres:11

準備

Query 時と同じようにマイグレーションファイルを作り、いくつか適当なレコードが入ってる環境を作ります。

diesel migration generate create_users

作られたup.sqldown.sqlをそれぞれ以下のように編集します。

-- Your SQL goes here

CREATE TABLE IF NOT EXISTS users (
    id SERIAL PRIMARY KEY,
    name VARCHAR(30) NOT NULL
);

INSERT INTO users (name) VALUES
  ('foo'),
  ('bar');
-- This file should undo anything in `up.sql`

DROP TABLE IF EXISTS users;

以下のコマンドでデータを流し込んで完了です。

diesel migration run \
  --database-url postgres://postgres:@localhost:5432
  
psql postgres://postgres:@localhost:5432 \
  --tuples-only \
  --command \
    'select json_agg(users) from users' \
  | tr -d '\n+' \
  | jq .
[
  {
    "id": 1,
    "name": "foo"
  },
  {
    "id": 2,
    "name": "bar"
  }
]

src/schema.rsができているので、いつものsrc/lib.rsを雛形の形にしておきます。

#[macro_use]
extern crate diesel;

pub mod schema;

挿入用の構造体を作る

取得する時の構造体ではQueryableトレイトを継承しましたが、挿入用のものにはInsertableトレイトを継承させます。その構造体ではDEFAULT値が設定されているカラムなど、いらない挿入時には要らない情報を落とて定義します。

また#[table_name = schema.rs にあるテーブル名]で実際のテーブル名の指定が必要になります。その際にはschema.rsから指定したテーブル名の名前空間をスコープに持ってこなければなりません。

// #[table_name="users"] を使うため
use schema::users;

#[derive(Insertable)]
#[table_name="users"]
pub struct NewUser {
   name: String,
}

これをsrc/lib.rsの最後に追記しておきます。

データを挿入する

src/main.rsを書いていきます。

データの挿入にはdiesel::insert_into関数を使います。

use diesel::pg::PgConnection;
use diesel::prelude::*;
use get_started_diesel_query::{schema, NewUser};

const DATABASE_URL: &'static str = "postgres://postgres:@localhost:5432";

fn main() {
  let connection =
    PgConnection::establish(DATABASE_URL).expect(&format!("Error connecting to {}", DATABASE_URL));

  let new_user = NewUser { name: "baz".into() };

  diesel::insert_into(schema::users::table)
    .values(&new_user)
    .execute(&connection)
    .expect("Error saving new user");
}

これをcargo runで実行し以下の結果が得られれば完了です。

psql postgres://postgres:@localhost:5432 \
  --tuples-only \
  --command \
    'select json_agg(users) from users' \
  | tr -d '\n+' \
  | jq .
[
  {
    "id": 1,
    "name": "foo"
  },
  {
    "id": 2,
    "name": "bar"
  },
  {
    "id": 3,
    "name": "baz"
  }
]

挿入時に取得も行う

RETURNINGを含んだINSERT INTOも行えます。その場合executeメソッドではなくget_resultメソッドを呼び出します。またその際はQueryableトレイトを継承した構造体の指定が必要です。

src/lib.rsに取得用の構造体を定義します。

#[derive(Queryable, Debug)]
pub struct User {
  pub id: i32,
  pub name: String,
}

これをsrc/main.rsで読み込み、またdiesel::insert_into箇所を以下のように修正します。

fn main() {
  // ... 略
  let user = diesel::insert_into(schema::users::table)
    .values(&new_user)
    .get_result::<User>(&connection)
    .expect("Error saving new user");
    
  println!("{:#?}", user);
  // User {
  //   id: 3,
  //   name: "baz",
  // }
}

複数のデータを挿入する

.valuesメソッドに Vector で複数のNewUserを渡すことで 1 つのINSERT INTOで複数の値を挿入できます。複数時には.get_result.get_resultsと複数形に代わります。
勿論戻り値が必要ないのであれば.executeも使えます。

fn main() {
  // ... 略

  let baz_user = NewUser { name: "baz".into() };
  let qux_user = NewUser { name: "qux".into() };

  let users = diesel::insert_into(schema::users::table)
    .values(vec![baz_user, qux_user])
    .get_results::<User>(&connection)
    .expect("Error saving new users");
  println!("{:#?}", users);
  // [
  //     User {
  //         id: 6,
  //         name: "baz",
  //     },
  //     User {
  //         id: 7,
  //         name: "qux",
  //     },
  // ]
}

サンプルコード

動作確認できるコードは nju33-com/get-started-diesel-insert に置かれています。