🪄

UUIDって何?SQLiteで苦戦したデータベース初心者の体験記

に公開

はじめに

インターンシップの中でデータベースを扱う中で、データを一意に識別するにはIDが必要だと学び、そこでUUID(Universally Unique Identifier) に出会いました。
本記事では、まずUUIDについて簡単に解説し、その後で実際にSQLiteでDMLを作成してUUIDを導入しようとしたときに経験した苦戦と学びをまとめます。特にSQLiteはPostgreSQLやMySQLと比べるとUUIDの扱いが独特で、初心者がハマりやすいポイントが多いと感じました。これから同じように挑戦する方の参考になれば幸いです。

UUIDとは何か

UUIDとは Universally Unique Identifier(汎用一意識別子) の略で、コンピュータ・システム内の情報を識別するために使用される128ビットの数値で構成される一意な識別子です。人が読むときには16進数で表現され、0から9までの数字とAからFまでの文字を使用し、4つのハイフンで区切られ、例えば、以下のように表示されます。

550e8400-e29b-41d4-a716-446655440000

本当に重複しないの?
128ビットなので、2^128 通りの組み合わせが可能であり、天文学的に大きな数ですが、理論上は有限なので重複する可能性がありますが、実際に重複する確率が極めて低いため、実用上は重複しないとされているようです。

UUIDのバージョン

UUIDにはいくつかのバージョンがあり、それぞれ生成方法と利用シーンが異なります。代表的なのは以下です。

  • v1: 時間 + クロックシーケンス + MACアドレスベースで生成(MACアドレスが含まれるため、プライバシー懸念あり)

    • 16進表記をすると、
    • TTTTTTTT-TTTT-1TTT-sSSS-AAAAAAAAAAAA
      
      のような構造で、T:タイムスタンプ、S:クロックシーケンス、A:ノードからなる
  • v3/v5: 名前空間識別子 + その名前空間内で一意な名前(文字列)を連結し、ハッシュアルゴリズムでハッシュ値を計算して(v3 は MD5 ハッシュアルゴリズム / v5 は SHA-1 ハッシュアルゴリズム)

    • 16進表記をすると、
    • HHHHHHHH-HHHH-3HHH-hHHH-HHHHHHHHHHHH
      HHHHHHHH-HHHH-5HHH-hHHH-HHHHHHHHHHHH
      
      のような構造で、H:ハッシュ値
  • v4: ランダムベース(最も広く利用されている)

    • 16進表記をすると、
    • RRRRRRRR-RRRR-4RRR-rRRR-RRRRRRRRRRRR
      
      のような構造で、R:ランダム値
  • v7: 時間 + ランダムベース(最近注目)

    • 16進表記をすると、
    • TTTTTTTT-TTTT-7RRR-rRRR-RRRRRRRRRRRR
      
      のような構造で、T:タイムスタンプ、R:ランダム値

つまりUUIDは「ただのランダム文字列」ではなく、バージョンによって内部構造や用途が異なるのです。

UUID v7 をターミナルで生成してみる

最近のUUID v7はターミナルから簡単に試せます。私は Node.js 環境だったので、pnpm dlx を使ってサクッと生成してみました。

$ pnpm dlx uuidv7
0198d449-c550-7856-b07b-4f8129f9513b

SQLiteでのUUID導入経験

やりたかったこと

今回私がやりたかったのは、アプリの初期データを投入するためのDML(Data Manipulation Language)ファイルを作ることでした。
たとえば以下のように、ユーザーテーブルにあらかじめレコードを登録しておきたかったのです。

insert into "users" ("id", "username", "email") values
  ('XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX', 'alice', 'alice@example.com'),
  ('XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX', 'bob', 'bob@example.com'),
  ('XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX', 'charlie', 'charlie@example.com');

id カラムに UUID を使うことで、アプリ側でレコードを生成するたびに衝突の心配なく一意なIDを持たせたい、という狙いがありました。
しかし、PostgreSQL などなら gen_random_uuid() として UUID 型を使えますが、SQLite には UUID を自動生成する仕組みがないため 、アプリケーション側で生成して INSERT する必要がありました。

対策アプローチ(Drizzle経由で)

私は Node.js 環境で uuidv7 パッケージを使い、Drizzle 経由で drizzle-orm を用いて初期データを INSERT するスクリプト(sample.ts)を書きました。drizzle-orm は、JavaScript / TypeScript で使えるデータベース操作をラクにするライブラリで、簡単に言うと「テーブルやレコードをコード上のオブジェクトとして扱えるようにするもの」です。今回の方法では、以下のライブラリを使用しました(公式サイト):

pnpm add drizzle-orm @libsql/client

@libsql/client は SQLite に接続するためのクライアントライブラリで、データベースの URL を指定するだけで簡単に接続できます。

書いたスクリプト(sample.ts)の実行は以下のようにします:

tsx ./sample.ts

スクリプトコード(sample.ts):

import { createClient } from "@libsql/client";
import { drizzle } from "drizzle-orm/libsql";
import { uuidv7 } from "uuidv7";
import { users } from "./schema";

const DATABASE_URL =
  "file:/Example/Example/......"; // 自分の SQLite ファイルパスに書き換えてください

const client = createClient({ url: DATABASE_URL });
const db = drizzle(client);

const usersList = [
  { username: "alice", email: "alice@example.com" },
  { username: "bob", email: "bob@example.com" },
  { username: "charlie", email: "charlie@example.com" },
];

await db.insert(users).values(
  usersList.map((item) => ({
    id: uuidv7(),
    username: item.username,
    email: item.email,
  })),
);

こうすることで、SQLiteでもUUIDを使った初期データ投入ができました。Node.js 側で UUID を生成して挿入するので、手で一つずつ作る必要がなくなり、作業がぐっと楽になります。

SQLファイルを生成してみた

さらに、sample.ts を少し工夫して SQLファイル(DML)を生成してみました。

import { writeFileSync } from "fs";

const valuesToInsert = usersList.map((item) => ({
  id: uuidv7(),
  username: item.username,
  email: item.email,
}));

// SQL を文字列として取得
const { sql, params } = db.insert(users).values(valuesToInsert).toSQL();

// プレースホルダ (?) を実際の値に置き換える
let finalSQL = sql;
params.forEach((param) => {
  const escaped = typeof param === "string" ? `'${param}'` : param;
  finalSQL = finalSQL.replace("?", String(escaped));
});

// ファイルに出力
writeFileSync("sample.sql", finalSQL);

これを実行すると、sample.sql ファイルが生成されます。中身はこんな感じで、すべて 1 行に展開されます。

insert into "users" ("id", "username", "email") values ('0198d508-8296-7d03-8213-4109031b87de', 'alice', 'alice@example.com'), ('0198d449-c550-7856-b07b-4f8129f9513b', 'bob', 'bob@example.com'), ('0198d509-018b-79e8-9552-a264958696a8', 'charlie', 'charlie@example.com');

見ての通り、今の状態だと 1 行にずらーっと並んでしまうのでちょっと読みにくいですが、コードを少し変えれば、各レコードを改行して整形することもできます。(ただしこの記事では省略します)

最後に

インターンでの実装を通して、「UUID をどう扱うか」という点でたくさん学びがありました。特に SQLite では自動生成がないため、アプリ側での工夫が必要なのが大きな発見でした。
これから SQLite で UUID を使った初期データ作成に挑戦する人に、この記事が少しでも参考になれば嬉しいです。

Emoba Tech Blog

Discussion