SQLite本家によるwasmサポートが開始されており、ブラウザ上でもSQLiteが使えるようになっています。
wasm版のSQLiteは1MBを切るという軽量具合なので、Webアプリで使うには嬉しいですね☺️
Flutter Webでももちろん使えるので、やり方メモを残します。
導入
Dart用のORMであるDriftを作られている方による、sqlite.wasmのビルド済みバイナリがあります。
ありがてぇ、ありがてぇ。
そのままDriftを使うという道もありますが、build_runnerを使う仕組みなので一定規模以下だとメンドクセェ_:(´ཀ`」 ∠):_
ちょろっとDB使いたいだけの時にORMは大袈裟なので、sqlite3パッケージで直接SQL叩く方が簡単だと思います(プレースホルダが使えます)
sqlite3パッケージはプロジェクトフォルダ内で次のコマンドで追加します。
> flutter pub add sqlite3
sqlite3.wasmはライブラリなのでlib下(のwebの下)に配置します。
利用方法
基本的な流れとしては
- sqlite3.wasmを読み込む
- ファイル置き場を決める(メモリ or ストレージ)
- ファイル名を決めてopen
となります。
インメモリDBで使用する
読み込みも保存もしなくて本当にちょっと使いたい時用です。
基本的には動作確認で使用することになると思います。
// sqlite3.wasmを読み込む
final sqlite3 = await WasmSqlite3.loadFromUrl(Uri.parse('sqlite3.wasm'));
// ファイル置き場を決める(メモリ)
sqlite3.registerVirtualFileSystem(InMemoryFileSystem(), makeDefault: true);
// ファイル名を決めてopen(":memory:"(SQLite規定値)。ラッパー関数が使える)
var db = sqlite3.openInMemory();
ブラウザのストレージに保存する
ユーザーデータなどを置くのはここになると思います。
// sqlite3.wasmを読み込む
final sqlite3 = await WasmSqlite3.loadFromUrl(Uri.parse('sqlite3.wasm'));
// ファイル置き場を決める(IndexedDB)
final fileSystem = await IndexedDbFileSystem.open(dbName: 'indexedDBの名前');
sqlite3.registerVirtualFileSystem(fileSystem, makeDefault: true);
// ファイル名を決めてopen
var db = sqlite3.open("ファイル名");
こんな感じで保存されます。
Web上のファイルを読み込む
作成済みのデータを読み込む時はこのやり方になると思います。
メモリ上にファイルを作ってデータをコピーしてから使います。
// 作成済みデータの配置場所
var path = "assets/パス名/ファイル名";
// データファイルの読み込み
ByteData data = await rootBundle.load(path);
Uint8List bytes =
data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);
// InMemoryFileSystem上にファイルを作成して書き込む
var fileSystem = InMemoryFileSystem();
var file = fileSystem
.xOpen(Sqlite3Filename(fileSystem.xFullPathName(path)), // とりあえず同じpathで
SqlFlag.SQLITE_OPEN_READWRITE | SqlFlag.SQLITE_OPEN_CREATE)
.file;
file.xTruncate(0);
file.xWrite(bytes, 0);
file.xClose();
// sqlite3.wasmを読み込む
final sqlite3 = await WasmSqlite3.loadFromUrl(Uri.parse('sqlite3.wasm'));
// ファイル置き場を決める(メモリ)
sqlite3.registerVirtualFileSystem(fileSystem, makeDefault: true);
// ファイル名を決めてopen(InMemoryFileSystem上に作成したファイル)
return sqlite3.open(path);
※実際に使うときは例外処理等を忘れないように
クエリの発行
プレースホルダには?を使います。
文字列操作、ダメ、絶対。
戻り値が不要なクエリ
execute関数を使います。
db.execute("SQLを記述", ["置換する値"]);
// prepare版
var prepared = db.prepare("SQLを記述");
prepared.execute(["置換する値"]);
結果が必要なクエリ
どんなSQLだろうとselect関数を使います。
var result = db.select("SQLを記述", ["置換する値"]);
result.forEach(print);
// prepare版
var prepared = db.prepare("SQLを記述");
var result = prepared.select(["置換する値"]);
結果はResultSet型で、forやforEachでアクセスできます。
もう少し突っ込んで使ってみる
複数のDBをまとめる
データの管理の都合上sqliteのファイルを複数に分けたくなる場合があります。
同じFileSystem上に作成したファイルならattachで開けます。
// DBのロード
Future<void> loadDB(
CommonDatabase db,
InMemoryFileSystem fileSystem,
String dbName,
String path
) async {
// assetからデータの読み込み
ByteData data = await rootBundle.load(path);
Uint8List bytes =
data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);
// SQLite3のInMemoryFileSystemに書き込む
var file = fileSystem
.xOpen(Sqlite3Filename(fileSystem.xFullPathName(path)),
SqlFlag.SQLITE_OPEN_READWRITE | SqlFlag.SQLITE_OPEN_CREATE)
.file;
file.xTruncate(0);
file.xWrite(bytes, 0);
file.xClose();
// Attach Open
db.execute("""attach database "$path" as $dbName""");
}
アクセス時にtable名
の前にdb名
を付けてdb名.table名
のようにアクセスします。
テーブル作成用SQLを拾う
sqlite_masterにスキーマが格納されています。
IndexedDB上にDBを作るときとか、外部でテーブルを作っておけて便利です。
var schemas =
memoryDB.select("SELECT * FROM user.sqlite_master WHERE type='table'");
for (var schema in schemas) {
localStorageDB.execute((schema["sql"] as String)
.replaceFirst("CREATE TABLE", "CREATE TABLE IF NOT EXISTS"));
}