SQLite データベース操作 (CRUD)

Recipe ID: db-001

Tauri アプリで SQLite データベースを利用するためのプラグイン導入から、テーブル作成、データの追加・参照・更新・削除 (CRUD) までの基本操作をまとめて解説します。

1. セットアップ

インストール手順

Rust 側と JavaScript 側の両方に依存関係を追加し、プラグインを初期化します。

1. コマンドで追加

Tauri CLI を使用して、プラグインをプロジェクトに追加します。
このコマンドを実行すると、Rust 側の依存関係(cargo)と JavaScript 側の依存関係(npm)の両方が自動的にインストールされます。

npm run tauri add sql
補足: Rust 側を個別にインストールしたい場合は、src-tauri ディレクトリで cargo add tauri-plugin-sql --features sqlite を実行してください。
2. Cargo.toml の確認 (Rust)

src-tauri/Cargo.toml を確認し、featuressqlite が含まれていることを確認してください。

[dependencies]
tauri-plugin-sql = { version = "2", features = ["sqlite"] }
3. プラグインの初期化 (Rust)

src-tauri/src/lib.rs ファイルを開き、plugin メソッドを使用してプラグインを登録します。

// src-tauri/src/lib.rs

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .plugin(tauri_plugin_sql::Builder::default().build()) // ここに追加
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

権限設定 (Capabilities)

src-tauri/capabilities/default.json でデータベースへのアクセスを許可します。

{
  "permissions": [
    ...,
    "sql:default",
    "sql:allow-load",
    "sql:allow-execute",
    "sql:allow-select"
  ]
}
注意: sql.execute not allowed などのエラーが出る場合は、上記のように個別の権限(allow-executeallow-select)を明示的に追加してください。セキュリティ向上のため、本番環境では必要な権限のみに絞ることが推奨されます。

2. データベースへの接続

フロントエンドからデータベースファイルを作成・接続します。
SQLite の場合、指定したパスにファイルがなければ自動的に作成されます。

import Database from '@tauri-apps/plugin-sql';

// データベースを開く(なければ作成)
// ファイル名 "myapp.db" の場合、OSごとの標準的なアプリデータフォルダに保存されます
const db = await Database.load('sqlite:myapp.db');

console.log('Database connected');

3. テーブルの作成

初期化時などに CREATE TABLE を実行します。IF NOT EXISTS を使うと安全です。

// テーブル作成
await db.execute(`
  CREATE TABLE IF NOT EXISTS users (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    email TEXT UNIQUE,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
  )
`);

4. データの操作 (CRUD)

追加 (INSERT)

SQL インジェクションを防ぐため、必ずプレースホルダー($1, $2...)を使用してください。

async function addUser(name: string, email: string) {
  // 戻り値は { rowsAffected: number, lastInsertId: number }
  const result = await db.execute(
    'INSERT INTO users (name, email) VALUES ($1, $2)',
    [name, email]
  );
  console.log(`Inserted ID: ${result.lastInsertId}`);
}

検索 (SELECT)

戻り値を型指定して受け取ることができます。

interface User {
  id: number;
  name: string;
  email: string | null;
}

async function searchUser(keyword: string): Promise<User[]> {
  // 部分一致検索
  const users = await db.select<User[]>(
    'SELECT * FROM users WHERE name LIKE $1 ORDER BY id DESC',
    [`%${keyword}%`]
  );
  return users;
}

更新 (UPDATE)

async function updateUserName(id: number, newName: string) {
  const result = await db.execute(
    'UPDATE users SET name = $1 WHERE id = $2',
    [newName, id]
  );
  console.log(`Updated ${result.rowsAffected} row(s).`);
}

削除 (DELETE)

async function deleteUser(id: number) {
  const result = await db.execute(
    'DELETE FROM users WHERE id = $1',
    [id]
  );
  console.log(`Deleted ${result.rowsAffected} user(s).`);
}

5. トランザクション

複数の操作をまとめて行う場合は、トランザクションを使用します。

await db.execute('BEGIN TRANSACTION');
try {
  // 複数の処理...
  await db.execute('INSERT INTO users (name) VALUES ($1)', ['Alice']);
  await db.execute('INSERT INTO users (name) VALUES ($1)', ['Bob']);
  
  await db.execute('COMMIT');
} catch (e) {
  await db.execute('ROLLBACK');
  throw e;
}