「TAURI」でデータベースを使ってみよう

2024年5月10日(金)
大西 武 (オオニシ タケシ)
第15回の今回は「TAURI」でオープンソースのデータベース「SQLite3」を使用して、テーブル表に表示する解説をしていきます。

RustでSQL文の実行

データベースを実行するにはSQL文を実行する必要があります。SQL文はシンプルなプログラムの文字列をテキスト形式で書くだけです。

SQL文の最も基本的なものに「CRUD」の4つがあります。「C」は「Create」の頭文字で「作成」、「R」は「Read」の頭文字で「読み出し」、「U」は「Update」の頭文字で「更新」、「D」は「Delete」の頭文字で「削除」です。具体的なSQL文は、作成には「CREATE」文を、読み出しには「SELECT」文を、更新には「UPDATE」文を、削除には「DELETE」文を使います。

ここからバックエンド側のRustをプログラミングしていきます。SQL文を実行する処理がメインになります。サンプルコードではID番号と動物名、動物の種類を1行の「レコード」としてテーブルに追加したり読み込んだり、更新したり削除したりします。

SQL文は一種のプログラミング言語のようなもので「"」で囲った文字列で表現しますが、Rustとは文法が違います。今回解説するSQL文はとても短いものなので、テーブル名やカラム名を変えただけでも使いまわせると思います。

TABLEを作成するSQL文

データベースの「TABLE」とHTMLの「table」タグは全く別のものですが、テーブル表に表すとそっくりです。下図のように「行(レコード)」と「列(カラム)」でできている二次元配列のようなものです。

テーブルは「CREATE TABLE テーブル名 (カラム名 型,カラム名 型);」文で作成しますが、作成したい名前のテーブルが既に存在する場合はエラーになります。そこで「IF NOT EXISTS テーブル名」でテーブル名のテーブルが存在していない場合だけ作成します。ここでは「$ cargo-tauri dev」コマンドを実行すると「id」カラムと「name」カラムと「category」カラムのある「animal」テーブルを作成します。

図2:データベースのTABLEの行(レコード)と列(カラム)

・「src-tauri」→「src」→「main.rs」ファイルのサンプルコード
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
// データベースファイル名
const TAURI_DB:&str = "../tauri.db";
// SQL文でTABLE作成
fn create() {
  let connection = sqlite::open(TAURI_DB).unwrap();
  let query = "
    CREATE TABLE IF NOT EXISTS animal (id INTEGER PRIMARY KEY AUTOINCREMENT,name TEXT,category TEXT);
  ";
  connection.execute(query).unwrap();
}
// メイン関数
fn main() {
  // TABLE作成
  create();
  tauri::Builder::default()
    .invoke_handler(tauri::generate_handler![])
    .run(tauri::generate_context!())
    .expect("error while running tauri application");
}

【サンプルコードの解説】
「TAURI_DB」定数がデータベースのファイル名です。最初にCREATE TABLEした際に"tauri.db"というファイルが作成されます。リリースビルドする際は保存するパスを変更すべきでしょう。
「create」関数で「sqlite」クレートの「open」関数でデータベースを開いて「connection」変数に代入し、「query」変数がTABLEを作成するSQL文の「execute」メソッドでそれを実行します。
メイン関数でcreate関数を呼び出します。

行を挿入するSQL文

「INSERT INTO テーブル名(カラム名,カラム名) VALUES (値,値);」のSQL文でテーブルに値を入れたカラムのレコードを追加します。ここでは「$ cargo-tauri dev」コマンドを実行すると、animalテーブルのnameカラムに鯛をcategoryカラムに魚類を入れるなどしています。idカラムは「オートインクリメント」される「主キー」なので自動で1から1ずつ増えた正の整数が入力されます。

他のプログラミング言語の場合、SQLite3のSQL文を実行する際にexecuteメソッドでデータを別の引数で渡せるのですが、sqliteクレートでは見つからなかったので、仕方なく「format!」マクロでSQL文の文字列にデータの変数を挿入しました。

・「src-tauri」→「src」→「main.rs」ファイルのサンプルコード
(前略)
// SQL文で行挿入
fn insert(name:&str,category:&str) {
  let connection = sqlite::open(TAURI_DB).unwrap();
  let query = format!("
    INSERT INTO animal(name,category) VALUES (\"{}\",\"{}\");
  ",name,category);
  connection.execute(query).unwrap();
}
// メイン関数
fn main() {
  // 行挿入
  insert("鯛","魚類");
  insert("カエル","両生類");
  insert("トカゲ","爬虫類");
  insert("鷹","鳥類");
  insert("トイプードル","哺乳類");
  tauri::Builder::default()
    .invoke_handler(tauri::generate_handler![])
    .run(tauri::generate_context!())
    .expect("error while running tauri application");
}

【サンプルコードの解説】
「insert」関数で「sqlite」クレートの「open」関数でデータベースを開き、「connection」変数に代入して「query」変数がレコードを挿入するSQL文の「execute」メソッドでそれを実行します。
メイン関数でinsert関数を呼び出します。

行を取得するSQL文

「SELECT カラム名 FROM テーブル名;」のSQL文でテーブル名のカラム名のレコードを取得します。ここでは「$ cargo-tauri dev」コマンドを実行するとanimalテーブルの全てのカラム(*)を取得します。

ここでSQL文を実行するのに「prepare」メソッドを使いました。prepareメソッドはレコードを取得するだけでデータベースに変更を加えない場合に使うのに対し、executeメソッドはデータベースに変更を加える場合に使います。

図3:データベースからデータを読み込んでテーブル表を表示

・「src-tauri」→「src」→「main.rs」ファイルのサンプルコード
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
// データベース名
const TAURI_DB:&str = "../tauri.db";
// TAURIコマンドget_data関数
#[tauri::command]
fn select() -> Vec<(i64,String,String)> {
  let connection = sqlite::open(TAURI_DB).unwrap();
  let query = "SELECT * FROM animal";
  let mut data = Vec::new();
  // SQL文で行取得
  for row in connection
    .prepare(query)
    .unwrap()
    .into_iter()
    .map(|row| row.unwrap())
  {
    let id = row.read::<i64, _>("id");
    let name = row.read::<&str, _>("name").to_string();
    let category = row.read::<&str, _>("category").to_string();
    data.push((id.clone(),name.clone(),category.clone()));
    println!("id = {},name = {},category = {}",id,name,category);
  }
  data
}
(中略)
// メイン関数
fn main() {
  // 行データ取得
  select();
  // TAURIコマンドselect関数の登録
  tauri::Builder::default()
    .invoke_handler(tauri::generate_handler![select])
    .run(tauri::generate_context!())
    .expect("error while running tauri application");
}

【サンプルコードの解説】
TAURIコマンド「select」関数で「sqlite」クレートの「open」関数でデータベースを開いて「connection」変数に代入し、「query」変数がレコードを取得するSQL文の「prepare」メソッドでそれを実行します。
メイン関数でTAURIコマンドselect関数を呼び出し、TAURIビルダーにTAURIコマンドselect関数を登録します。

行を更新するSQL文

「UPDATE テーブル名 SET カラム名=値,カラム名=値 WHERE 条件;」のSQL文で、条件に当てはまるレコードのテーブルのカラムに値を入れ替えます。ここでは「$ cargo-tauri dev」コマンドを実行するとanimalテーブルのidカラムが1のnameカラムを秋刀魚に、categoryカラムを魚類に更新します。

ここで「WHERE」に続く条件文で指定したレコードだけ値を変更します。idカラムはオートインクリメントする一意の値なので、必ず1つのレコードだけを指定できます。

・「src-tauri」→「src」→「main.rs」ファイルのサンプルコード
(前略)
// SQL文で行更新
fn update(id:i64,name:&str,category:&str) {
  let connection = sqlite::open(TAURI_DB).unwrap();
  let query = format!("
    UPDATE animal SET name=\"{}\",category=\"{}\" WHERE id = {};
  ",name,category,id);
  connection.execute(query).unwrap();
}
// メイン関数
fn main() {
  // 1行目を書き換え
  update(1,"秋刀魚","魚類");
  select();
  tauri::Builder::default()
    .invoke_handler(tauri::generate_handler![select])
    .run(tauri::generate_context!())
    .expect("error while running tauri application");
}

【サンプルコードの解説】
「update」関数で「sqlite」クレートの「open」関数でデータベースを開いて「connection」変数に代入し、「query」変数がレコードを更新するSQL文の「execute」メソッドでそれを実行します。
メイン関数でupdate関数を呼び出します。

行を削除するSQL文

「DELETE FROM テーブル名 WHERE 条件;」のSQL文で条件に当てはまるレコードをテーブルから削除します。ここでは「$ cargo-tauri dev」コマンドを実行すると、animalテーブルのidカラムが2のレコードを削除します。

このSQL文は全て開発者が操作しているため改ざんや誤動作させる悪意のあるSQL文にはなりませんが、もし値をユーザーが操作できるようにした場合は悪意のあるデータにも対処しなければなりません。

・「src-tauri」→「src」→「main.rs」ファイルのサンプルコード
(前略)
// SQL文で行削除
fn delete(id:i64) {
  let connection = sqlite::open(TAURI_DB).unwrap();
  let query = format!("
    DELETE FROM animal WHERE id = {};
  ",id);
  connection.execute(query).unwrap();
}
// メイン関数
fn main() {
  // id=2の行削除
  delete(2);
  select();
  tauri::Builder::default()
    .invoke_handler(tauri::generate_handler![select])
    .run(tauri::generate_context!())
    .expect("error while running tauri application");
}

【サンプルコードの解説】
「delete」関数で「sqlite」クレートの「open」関数でデータベースを開いて「connection」変数に代入し、「query」変数がレコードを削除するSQL文の「execute」メソッドでそれを実行します。
メイン関数でdelete関数を呼び出します。

【コラム】「SQL文の大文字と小文字」

どんなデータベースでもSQL文には大文字も小文字も使えますが、筆者は文法のCREATEやSELECTなどは全て大文字で、テーブル名やカラム名などの名前は全て小文字で書くことで、SQL文を見やすくするように心がけています。

おわりに

最終回の今回は、「SQLite3」を「sqlite」クレートからデータベースを扱う解説をしました。SQL文はほとんどのデータベースと同じような文法でした。

今回で、約9ヶ月間にわたって解説した「TAURI」+「Rust」ではじめるデスクトップアプリ開発の連載も終了となります。長い間お付き合いいただき、ありがとうございました。TAURIはRustだけでなくWebアプリの知識も必要ですが、Think ITの読者にはWebインフラに強い方が多いようなので、むしろとっつきやすかったかも知れませんね。

それでは、また次の連載でお会いしましょう!

著者
大西 武 (オオニシ タケシ)
1975年香川県生まれ。大阪大学経済学部経営学科中退。プログラミング入門書など30冊以上を商業出版。Microsoftで大賞やNTTドコモでグランプリなど20回以上全国区のコンテストに入賞。オリジナルの間違い探し「3Dクイズ」が全国放送のTVで約10回出題。

連載バックナンバー

開発言語技術解説
第15回

「TAURI」でデータベースを使ってみよう

2024/5/10
第15回の今回は「TAURI」でオープンソースのデータベース「SQLite3」を使用して、テーブル表に表示する解説をしていきます。
開発言語技術解説
第14回

「TAURI」で気象庁の「CSVデータ」を解析する

2024/5/1
第14回の今回は気象庁のWebサイトから指定した地域の1年間の気象データをダウンロードして「TAURI」で解析していきます。
開発言語技術解説
第13回

「TAURI」で「簡易RSSリーダー」を開発してみよう

2024/4/16
第13回の今回は「TAURI」で「RSSフィード」を読み込んでWebページに一覧表示し、リンクのページを開くための新規ウィンドウを作成するところまでを解説します。

Think ITメルマガ会員登録受付中

Think ITでは、技術情報が詰まったメールマガジン「Think IT Weekly」の配信サービスを提供しています。メルマガ会員登録を済ませれば、メルマガだけでなく、さまざまな限定特典を入手できるようになります。

Think ITメルマガ会員のサービス内容を見る

他にもこの記事が読まれています