Drift (Moor) — とは何か、リアクティブORMとデータベース操作

著者: IT Sectr 公開日: 2026-03-13 読了時間: 9 分

Drift(旧Moor)は、独自のDSLを持つ、SQLite上に構築されたFlutterおよびDart用のリアクティブORMです。伝統的なORMとは異なり、Driftはビルド時にDartクエリをSQLにコンパイルし、ランタイムエラーを排除します。Drift Docs, 2024によると、Driftは手動SQLクエリよりも40%多いコードを生成しますが、手動でのSQL記述を完全になくし、型セーフなDart構文で置き換えます。

ポイント

  • Drift — ビルド時にクエリをSQLにコンパイルするリアクティブORM
  • DSLクエリ — SQL文字列を書かずにDartで書けるフルーエントインターフェース
  • リアクティビティ — リアルタイムUI更新のためのStreamおよび自動更新クエリ
  • クロスプラットフォーム — Android, iOS, Web, macOS, Linux, Windows
  • マイグレーション — SQLを通じた自動バージョン管理と手動マイグレーション

Driftとは?

DriftはDartおよびFlutter用のORMで、旧名はMoorです。2019年にSimon Binderによって開発され、それ以来いくつかのメジャーバージョンを経てきました。Driftはdrift_devbuild_runnerを使用してビルド時にDartクエリをSQLにコンパイルし、完全な型安全性を提供し、ランタイムでのSQL構文エラーを排除します。

Floorとは異なり、Driftはクエリ構築のために独自のDSL(ドメイン特有言語)を使用します—開発者はDartで書き、ジェネレーターがそれをSQLに翻訳します。これにより、IDEが構文をチェックし、テーブルフィールドを自動補完し、クエリを破壊する恐れなくデータモデルをリファクタリングできます。

Drift (2024)によると、このライブラリは8000以上のFlutterプロジェクトで使用されています。以下の人気プラットフォームをサポートしています:Android(sqflite)、iOS(sqflite)、Web(sqlite3 WASM)、デスクトップ(sqlite3ネイティブドライバ)。

改名の経緯:Moor → Drift

Moorはバージョン2.0(2022年)でDriftに改名されました。理由は他のプロジェクトとの名称衝突および古いコードからの離脱です。APIは互換性を維持しています:マイグレーションには、importをmoorからdriftに変更し、依存関係を更新するだけです。

主な機能

Driftが提供する機能:データ変更時に自動更新されるビルトインStreamクエリ、ロールバック付きトランザクションサポート、rawQueryを通じたカスタムSQLクエリ、ロジック・カプセル化のためのDAOパターン、クロスプラットフォームマイグレーション、およびdrift_riverpodおよびdrift_blocパッケージを通じたRiverpodおよびBLoCとの連携。

Driftの仕組み

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 DSL vs SQL:アプローチ比較

Driftはクエリを書く2つの方法を提供しています:Dart DSL(ネイティブ)とラウSQL(複雑な場合用)。90%のシナリオではDSLが推奨されます:安全で、読みやすく、リファクタリングをサポートします。ラウSQLはDSLが必要な構造をカバーできない場合にのみ使用されます。

項目Drift DSLDriftのラウSQL
型安全性完全(コンパイル時)なし(ランタイム)
自動補完あり(IDE)sqlファイルのみ
リファクタリング自動手動テキスト検索
複雑なJOINサポート済完全な自由度
ウィンドウ関数制限あり完全サポート
リアクティビティビルトイン(Stream).watch()を通じて

DSLを使うべき場合

Drift DSLは主な作業方法です。SELECT、INSERT、UPDATE、DELETE、WHERE、ORDER BY、LIMIT、JOIN、グルーピングをカバーします。すべての一般的なCRUDクエリでは、DSLを使用してください:短く、安全で、変更時にStreamを自動更新します。

ラウSQLを使うべき場合

DriftでのラウSQLは以下の場合に必要です:カスタムSQLite関数(FTS5、JSON1)、EXISTSを使用した複雑なサブクエリ、INSERT OR REPLACE、CASEを使用した一括UPDATE、およびパフォーマンスが重要でDSLが最適な実行プランを生成できないクエリ。ラウSQLはdrift_devを通じた型付けサポート付きで.sqlファイルに書くことができます。

Driftコード例

DriftはTableを拡張するクラスまたは@DataClass注釈を使用します。以下は、DSL、ラウSQL、およびリアクティブ更新を使用したTaskモデルの完全な例です。build_runnerを実行すると、生成されたすべてのクラスが使用可能になります。

テーブルとデータベースの定義

TasksクラスはTableを拡張し、カラムを定義します。各カラムはColumn<T>型の式です。パラメータ:withDefault()でデフォルト値を設定し、autoIncrement()で自動インクリメントを設定します。データベースは$DriftDatabaseを拡張する抽象クラスです。

dart
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);
}

DSLを通じたCRUD操作

Driftはテーブルに対してinto(tasks).insert()select(tasks)update(tasks)delete(tasks)メソッドを生成します。すべての操作はFutureを返します—SQLiteとの作業は非同期です。変更をトラックするには、.get()の代わりに.watch()を使用します。

dart
// 挿入
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);
});

型付け結果を得られるラウSQL

複雑なクエリでは、Driftは型付けを維持したままラウSQLを書くことができます。customSelectメソッドはクエリ文字列を受け取り、コードジェネレーターを通じて型付け結果を返します。このアプローチは、SQLの柔軟性とDriftの型安全性を組み合わせます。

dart
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におけるマイグレーションとバージョン管理

Driftは簡単な変更に対する自動マイグレーションと複雑な変換に対する手動マイグレーションの両方をサポートしています。データベースのバージョンはAppDatabaseコンストラクタで設定します。バージョンが一致しない場合、Driftはべての未完了のマイグレーションを順番に適用します。

自動マイグレーション

デフォルト値を持つカラムを追加する場合、DriftはMigrationStrategyを通じて自動でマイグレーションを生成できます。変更が既存データを破壊しない場合(null可能フィールドの追加)は、バージョン確認とALTER TABLEの実行を伴うbeforeOpenを使用できます。

手動マイグレーション

複雑な変更(テーブルの名称変更、データの結合、カラム型の変更)には、Driftは手動SQLマイグレーションが必要です。マイグレーションは、データベースクラスのmigrationsパラメーターで指定します。各マイグレーションはfrom/to番号とSQLクエリを持つオブジェクトです。

Driftとテスト

DriftNativeDatabase.memory()を通じてのテストモードでの実行をサポートしています。メモリ内データベースは各テストの前にスクラッチから作成され、後に破棄されます。モックには、モックされたQueryExecutorを伴うmocktailパッケージを使用します。Driftはまた、マイグレーションとクエリの検証を行うインテグレーションテスト用のDatabaseTestHelperも提供しています。

DriftにおけるDAOパターン

Drift@DriftAccessor注釈を付した抽象クラスを通じてDAO(データアクセスオブジェクト)をサポートしています。DAOは1つまたは複数のテーブルに対するクエリをカプセル化し、データベースから独立してテストできます。Databaseを通じた直接クエリとは異なり、DAOはアプリケーションの異なる部分でクエリロジックを再利用でき、単体テストを簡素化します。

dart
@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とFloorの違いは?

DriftはSQL文字列の代わりに独自のDSLを使用し、完全な型安全性とIDEでの自動補完を提供します。Floorは@Query注釈でSQL文字列を使用します。Driftはより多くのプラットフォーム(webを含む)をサポートし、Streamを通じたリアクティビティが組み込まれていますが、FloorではStreamを手動で宣言する必要があります。

Driftはデータ失わずにマイグレーションできますか?

はい、Driftはデータを保存したマイグレーションをサポートしています。カラムを追加するには、MigrationでaddColumnを使用します。複雑な変換(名称変更、結合)には、マイグレーション内でラウSQLを書きます。マイグレーションが指定されていない場合、スキーマが一致しないと、Driftはデータを失ってデータベースを再作成します。

build_runnerなしでDriftを使用できますか?

Driftはbuild_runnerおよびdrift_devを通じたコード生成が必要です。生成なしでは、型付けクエリを作成できません。ただし、小さなプロジェクトでは、Driftはsqlparserをサポートしています—自動型付けで手動でSQLファイルを書く方法ですが、これでも生成ステップが必要です。

DriftをRiverpodと連携する方法は?

Riverpodとの連携には、drift_riverpodパッケージを使用します。これは、Database、DAO、Streamクエリのプロバイダを提供します。例:final tasksProvider = databaseProvider.select((db) => db.select(db.tasks).watch()) — データが変わると自動でUIが再構築されます。

DriftはSQLite暗号化をサポートしていますか?

Driftには組み込みの暗号化機能はありませんが、SEE(SQLite Encryption Extension)を使用したカスタムsqlite3ライブラリの接続をサポートしています。モバイルプラットフォームでは、QueryExecutorとしてsqflite_sqlcipherを使用します—Driftは抽象QueryExecutorを通じて任意のSQLite実装で動作します。

まとめ

  • Drift — ビルド時にクエリをSQLにコンパイルするFlutter、Dart用リアクティブORM
  • DSL構文 — 完全な型安全性とIDEでの自動補完を備えたDartクエリ
  • リアクティビティ — 自動UI更新のためのStreamおよび自動更新クエリ
  • クロスプラットフォーム — Android, iOS, Web (WASM), macOS, Linux, Windows
  • マイグレーション — 簡単な変更は自動、複雑な変更は手動SQL
  • エコシステム — Riverpod (drift_riverpod)、BLoC (drift_bloc)との連携
  • 推奨 — リアクティビティ、型安全性、すべてのFlutterプラットフォームサポートが重要なプロジェクトにDriftをおすすめします

ターンキー方式のモバイルアプリケーションを開発します

IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。

プロジェクトについて相談

こちらもお読みください