Drift(旧Moor)は、独自のDSLを持つ、SQLite上に構築されたFlutterおよびDart用のリアクティブORMです。伝統的なORMとは異なり、Driftはビルド時にDartクエリをSQLにコンパイルし、ランタイムエラーを排除します。Drift Docs, 2024によると、Driftは手動SQLクエリよりも40%多いコードを生成しますが、手動でのSQL記述を完全になくし、型セーフなDart構文で置き換えます。
ポイント
DriftはDartおよびFlutter用のORMで、旧名はMoorです。2019年にSimon Binderによって開発され、それ以来いくつかのメジャーバージョンを経てきました。Driftはdrift_devとbuild_runnerを使用してビルド時にDartクエリをSQLにコンパイルし、完全な型安全性を提供し、ランタイムでのSQL構文エラーを排除します。
Floorとは異なり、Driftはクエリ構築のために独自のDSL(ドメイン特有言語)を使用します—開発者はDartで書き、ジェネレーターがそれをSQLに翻訳します。これにより、IDEが構文をチェックし、テーブルフィールドを自動補完し、クエリを破壊する恐れなくデータモデルをリファクタリングできます。
Drift (2024)によると、このライブラリは8000以上のFlutterプロジェクトで使用されています。以下の人気プラットフォームをサポートしています:Android(sqflite)、iOS(sqflite)、Web(sqlite3 WASM)、デスクトップ(sqlite3ネイティブドライバ)。
Moorはバージョン2.0(2022年)でDriftに改名されました。理由は他のプロジェクトとの名称衝突および古いコードからの離脱です。APIは互換性を維持しています:マイグレーションには、importをmoorからdriftに変更し、依存関係を更新するだけです。
Driftが提供する機能:データ変更時に自動更新されるビルトインStreamクエリ、ロールバック付きトランザクションサポート、rawQueryを通じたカスタムSQLクエリ、ロジック・カプセル化のためのDAOパターン、クロスプラットフォームマイグレーション、およびdrift_riverpodおよびdrift_blocパッケージを通じたRiverpodおよびBLoCとの連携。
Driftはコンパイル時にコード生成を使用します。開発者は@DataClass注釈またはTableを拡張するDartクラスを通じてテーブルを記述します。ジェネレーターはヘルパークラスを生成します:Companion(挿入/更新時のnull可能フィールド用)、DriftDatabase(エントリポイント)、およびDAO実装。
DriftはSQLiteクエリを直接実行しません。代わりに、開発者はDartで記述します:select(tasks).where(tasks.priority.greaterThan(3)).build()。ジェネレーターがこれをSQLに翻訳し、ランタイムではDriftが完成したSQLクエリをSQLiteに送信するだけです。これにより、Dart構文の便利さとネイティブSQLのパフォーマンスを組み合わせます。
Driftは2つのクエリモードをサポートしています:DSL(推奨)とラウSQL。DSLクエリはより安全です—コンパイラがフィールド名、型、互換性をチェックします。ラウSQLはDSLでカバーできない複雑なクエリ(ウィンドウ関数、再帰的CTE、特定のSQLite拡張)に必要です。
Driftはクエリを書く2つの方法を提供しています:Dart DSL(ネイティブ)とラウSQL(複雑な場合用)。90%のシナリオではDSLが推奨されます:安全で、読みやすく、リファクタリングをサポートします。ラウSQLはDSLが必要な構造をカバーできない場合にのみ使用されます。
| 項目 | Drift DSL | DriftのラウSQL |
|---|---|---|
| 型安全性 | 完全(コンパイル時) | なし(ランタイム) |
| 自動補完 | あり(IDE) | sqlファイルのみ |
| リファクタリング | 自動 | 手動テキスト検索 |
| 複雑なJOIN | サポート済 | 完全な自由度 |
| ウィンドウ関数 | 制限あり | 完全サポート |
| リアクティビティ | ビルトイン(Stream) | .watch()を通じて |
Drift DSLは主な作業方法です。SELECT、INSERT、UPDATE、DELETE、WHERE、ORDER BY、LIMIT、JOIN、グルーピングをカバーします。すべての一般的なCRUDクエリでは、DSLを使用してください:短く、安全で、変更時にStreamを自動更新します。
DriftでのラウSQLは以下の場合に必要です:カスタムSQLite関数(FTS5、JSON1)、EXISTSを使用した複雑なサブクエリ、INSERT OR REPLACE、CASEを使用した一括UPDATE、およびパフォーマンスが重要でDSLが最適な実行プランを生成できないクエリ。ラウSQLはdrift_devを通じた型付けサポート付きで.sqlファイルに書くことができます。
DriftはTableを拡張するクラスまたは@DataClass注釈を使用します。以下は、DSL、ラウSQL、およびリアクティブ更新を使用したTaskモデルの完全な例です。build_runnerを実行すると、生成されたすべてのクラスが使用可能になります。
TasksクラスはTableを拡張し、カラムを定義します。各カラムはColumn<T>型の式です。パラメータ:withDefault()でデフォルト値を設定し、autoIncrement()で自動インクリメントを設定します。データベースは$DriftDatabaseを拡張する抽象クラスです。
class Tasks extends Table {
IntColumn get id => integer().autoIncrement();
TextColumn get title => text().withDefault(const Constant(''))();
BoolColumn get isCompleted => boolean().withDefault(const Constant(false))();
IntColumn get priority => integer().withDefault(const Constant(0))();
}
@DriftDatabase(tables: [Tasks])
class AppDatabase extends $AppDatabase {
AppDatabase(QueryExecutor e) : super(e);
}
Driftはテーブルに対してinto(tasks).insert()、select(tasks)、update(tasks)、delete(tasks)メソッドを生成します。すべての操作はFutureを返します—SQLiteとの作業は非同期です。変更をトラックするには、.get()の代わりに.watch()を使用します。
// 挿入
await into(tasks).insert(TasksCompanion.insert(
title: Value('食料品を買う'),
priority: Value(3),
));
// フィルター付き読み出し
final highPriority = await (select(tasks)
..where((t) => t.priority.greaterThan(2))
..orderBy([(t) => OrderingTerm(expression: t.priority, mode: OrderingMode.desc)]))
.get();
// リアクティブ監視
select(tasks).watch().listen((tasksList) {
// tasksList — List, テーブル変更のたびに更新される
updateUi(tasksList);
});
複雑なクエリでは、Driftは型付けを維持したままラウSQLを書くことができます。customSelectメソッドはクエリ文字列を受け取り、コードジェネレーターを通じて型付け結果を返します。このアプローチは、SQLの柔軟性とDriftの型安全性を組み合わせます。
final result = await customSelect(
'SELECT title, COUNT(*) as cnt FROM tasks GROUP BY title',
readsFrom: { tasks },
).get();
for (final row in result) {
print('${row.readString("title")}: ${row.readInt("cnt")}');
}
Driftは簡単な変更に対する自動マイグレーションと複雑な変換に対する手動マイグレーションの両方をサポートしています。データベースのバージョンはAppDatabaseコンストラクタで設定します。バージョンが一致しない場合、Driftはべての未完了のマイグレーションを順番に適用します。
デフォルト値を持つカラムを追加する場合、DriftはMigrationStrategyを通じて自動でマイグレーションを生成できます。変更が既存データを破壊しない場合(null可能フィールドの追加)は、バージョン確認とALTER TABLEの実行を伴うbeforeOpenを使用できます。
複雑な変更(テーブルの名称変更、データの結合、カラム型の変更)には、Driftは手動SQLマイグレーションが必要です。マイグレーションは、データベースクラスのmigrationsパラメーターで指定します。各マイグレーションはfrom/to番号とSQLクエリを持つオブジェクトです。
DriftはNativeDatabase.memory()を通じてのテストモードでの実行をサポートしています。メモリ内データベースは各テストの前にスクラッチから作成され、後に破棄されます。モックには、モックされたQueryExecutorを伴うmocktailパッケージを使用します。Driftはまた、マイグレーションとクエリの検証を行うインテグレーションテスト用のDatabaseTestHelperも提供しています。
Driftは@DriftAccessor注釈を付した抽象クラスを通じてDAO(データアクセスオブジェクト)をサポートしています。DAOは1つまたは複数のテーブルに対するクエリをカプセル化し、データベースから独立してテストできます。Databaseを通じた直接クエリとは異なり、DAOはアプリケーションの異なる部分でクエリロジックを再利用でき、単体テストを簡素化します。
@DriftDatabase(tables: [Tasks])
class AppDatabase extends $AppDatabase {
AppDatabase(QueryExecutor e) : super(e) {
migrations.add(Migration(1, 2, (m) async {
await m.addColumn(tasks, tasks.dueDate);
await m.createIndex(tasks.idxPriority);
}));
}
}
よくある質問
DriftはSQL文字列の代わりに独自のDSLを使用し、完全な型安全性とIDEでの自動補完を提供します。Floorは@Query注釈でSQL文字列を使用します。Driftはより多くのプラットフォーム(webを含む)をサポートし、Streamを通じたリアクティビティが組み込まれていますが、FloorではStreamを手動で宣言する必要があります。
はい、Driftはデータを保存したマイグレーションをサポートしています。カラムを追加するには、MigrationでaddColumnを使用します。複雑な変換(名称変更、結合)には、マイグレーション内でラウSQLを書きます。マイグレーションが指定されていない場合、スキーマが一致しないと、Driftはデータを失ってデータベースを再作成します。
Driftはbuild_runnerおよびdrift_devを通じたコード生成が必要です。生成なしでは、型付けクエリを作成できません。ただし、小さなプロジェクトでは、Driftはsqlparserをサポートしています—自動型付けで手動でSQLファイルを書く方法ですが、これでも生成ステップが必要です。
Riverpodとの連携には、drift_riverpodパッケージを使用します。これは、Database、DAO、Streamクエリのプロバイダを提供します。例:final tasksProvider = databaseProvider.select((db) => db.select(db.tasks).watch()) — データが変わると自動でUIが再構築されます。
Driftには組み込みの暗号化機能はありませんが、SEE(SQLite Encryption Extension)を使用したカスタムsqlite3ライブラリの接続をサポートしています。モバイルプラットフォームでは、QueryExecutorとしてsqflite_sqlcipherを使用します—Driftは抽象QueryExecutorを通じて任意のSQLite実装で動作します。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。