[Rust] RusqliteでSqliteを操作する

2022.05.30

Introduction

Rustでsqliteを操作するためのcrateはたくさんありますが、
今回はRusqliteというcrateを使って
Sqliteにアクセスしてみました。

RustからPostgreSQLにアクセスするcrate、rust-postgresをベースに作成されており、 
似たようなインターフェイスで使うことが可能になっています。

Environment

  • MacBook Pro (13-inch, M1, 2020)
  • OS : MacOS 11.3.1
  • rust : 1.61.0

Try

Sqliteのセットアップ

まずはsqlite3をHomebrewでインストールします。

% brew install sqlite3

インストールできたらexample_db.db3と名前指定して起動。

% sqlite3 example_db.db3
sqlite>

テーブル作成とテストデータを登録しておきましょう。

create table if not exists person (
            id integer primary key,
            department text not null,
            name text not null unique,
            salary integer not null
);

insert into person values(1,'development','Taro',120);
insert into person values(2,'development','Hanako',200);
insert into person values(3,'development','Syuta',260);
insert into person values(4,'sales','Mike',160);
insert into person values(5,'sales','Takeshi',240);
insert into person values(6,'sales','Kyousuke',100);
insert into person values(7,'sales','Teru',220);
insert into person values(8,'management','Hide',400);
insert into person values(9,'management','Marry',200);
insert into person values(10,'management','Kim',300);

Rust実装

Cargoでプロジェクト作成後、Cargo.tomlでライブラリの指定をします。

% cargo new rusqlite-example
% cd rusqlite-example
[dependencies]
rusqlite = "*"

main.rsにsqliteへアクセスするためのコードを書いていきます。
まずはuseと構造体の定義。
さきほどcreate tableしたpersonテーブルに対応する構造体を定義します。
avg_salaryは、部署ごとの平均salaryを示すフィールドです。

use rusqlite::{params, Connection, Result};

#[derive(Debug)]
struct Person{
    id: u16,
    department:String,
    name: String,
    salary: u32,
    avg_salary:Option<f32>
}

次はコネクション取得関数を定義。
Connection::openでsqliteファイルを渡せばOK。
なお、Connection::open_in_memory関数を使えばインメモリで起動します。

fn open_my_db() -> Result<Connection,rusqlite::Error> {
    let path = "./example_db.db3";
    let con = Connection::open(&path)?;
    println!("{}", con.is_autocommit());
    Ok(con)
}

データinsert用関数はこんな感じです。
シンプル。

fn insert_person(con:&Connection,p:&Person) -> Result<usize,rusqlite::Error> {
  return Ok(con.execute(
      "insert into person (department,name, salary) values (?1, ?2,?3)",
      params![p.department,p.name, p.salary]
  )?);
}

personテーブル全件取得してprintlnする関数です。
query_mapでPerson配列をつくってます。

fn select_all(con:&Connection){
    let mut stmt = con.prepare("select id,department,name,salary from person").unwrap();
    let persons = stmt.query_map(params![], |row| {
      Ok(Person {
          id: row.get(0).unwrap(),
          department: row.get(1).unwrap(),
          name: row.get(2).unwrap(),
          salary: row.get(3).unwrap(),
          avg_salary:None
      })
    }).unwrap();

    for p in persons {
      println!("{:?}", p.unwrap());
    }

}

sqliteをすっごく久しぶりにさわったので、window関数が実装されてたのとか知りませんでした。
(3.25から実装とのこと)  

こんな感じでsql書いて、departmentごとの平均salaryを各レコードに計算してます。

fn select_window(con:&Connection) {
    let mut stmt = con.prepare("select id,department,name,salary,avg(salary) over defw from person window defw as (partition by department)").unwrap();
    let persons = stmt.query_map(params![], |row| {
      Ok(Person {
          id: row.get_unwrap(0),
          department: row.get_unwrap(1),
          name: row.get_unwrap(2),
          salary: row.get_unwrap(3),
          avg_salary:Some(row.get_unwrap(4))
      })
    }).unwrap();

    for p in persons {
      println!("{:?}", p.unwrap());
    }
}

fn main() {
    let con = open_my_db().unwrap();
    select_window(&con);
}

Summary

とても簡単にsqliteにアクセスできました。
sqliteは今後お世話になる可能性があるので、使い方をおさらいしておきたいところです。

References