Drift (Moor) — что это, реактивная ORM и работа с базами данных

Автор: IT Sectr Опубликовано: 2026-03-13 Время чтения: 9 мин

Drift (ранее Moor) — реактивная ORM для Flutter и Dart, построенная поверх SQLite с собственным DSL для запросов. В отличие от традиционных ORM, Drift компилирует Dart-запросы в SQL на этапе сборки, исключая runtime-ошибки. По данным Drift Docs, 2024, Drift генерирует до 40% больше кода, чем ручные SQL-запросы, но полностью исключает ручное написание SQL, заменяя его типобезопасным Dart-синтаксисом.

Главное

  • Drift — реактивная ORM с компиляцией запросов в SQL на этапе сборки
  • DSL-запросы — fluent-интерфейс на Dart без написания SQL-строк
  • Реактивность — Stream и auto-updating запросы для обновления UI в реальном времени
  • Кросс-платформенность — Android, iOS, Web, macOS, Linux, Windows
  • Миграции — автоматическое версионирование и ручные миграции через SQL

Что такое Drift?

Drift — ORM для Dart и Flutter, ранее известная как Moor. Разработана Симоном Биндером в 2019 году и с тех пор прошла несколько мажорных версий. Drift компилирует Dart-запросы в SQL на этапе сборки с помощью drift_dev и build_runner, что даёт полную типобезопасность и исключает синтаксические ошибки SQL в runtime.

В отличие от Floor, Drift использует собственный DSL (Domain-Specific Language) для построения запросов — разработчик пишет на Dart, а генератор транслирует это в SQL. Это позволяет IDE проверять синтаксис, автодополнять поля таблиц и рефакторить модель данных без страха сломать запрос.

По данным Drift (2024), библиотека используется более чем в 8000 Flutter-проектах. Она поддерживает все популярные платформы: Android через sqflite, iOS через sqflite, web через sqlite3 WASM, десктоп через sqlite3 нативный драйвер.

История переименования: Moor → Drift

Moor был переименован в Drift в версии 2.0 (2022). Причина — конфликт названий с другими проектами и желание дистанцироваться от старого кода. API осталось совместимым: для миграции достаточно заменить import с moor на drift и обновить зависимости.

Ключевые возможности

Drift предоставляет: встроенные Stream-запросы с автоматическим обновлением при изменении данных, поддержку транзакций с откатом, кастомные SQL-запросы через rawQuery, DAO-паттерн для инкапсуляции логики, кросс-платформенные миграции и интеграцию с Riverpod и BLoC через пакеты drift_riverpod и drift_bloc.

Как работает Drift?

Drift использует генерацию кода на этапе компиляции. Разработчик описывает таблицы через @DataClass аннотации или Dart-классы, расширяющие Table. Генератор создаёт вспомогательные классы: Companion (для nullable полей при вставке/обновлении), DriftDatabase (точка входа) и DAO-реализации.

SQLite-запросы Drift не выполняет напрямую. Вместо этого разработчик пишет на Dart: select(tasks).where(tasks.priority.greaterThan(3)).build(). Генератор транслирует это в SQL, а во время выполнения Drift просто отправляет готовый SQL-запрос в SQLite. Это сочетает удобство Dart-синтаксиса с производительностью нативного SQL.

Архитектура запросов

Drift поддерживает два режима запросов: DSL (рекомендуемый) и raw SQL. DSL-запросы писать безопаснее — компилятор проверяет имена полей, типы и совместимость. Raw SQL нужен для сложных запросов, не покрываемых DSL: оконные функции, recursive CTE, специфические SQLite-расширения.

Drift DSL vs SQL: сравнение подходов

Drift предлагает два способа написания запросов: Dart DSL (нативный) и raw SQL (для сложных случаев). DSL предпочтительнее для 90% сценариев: он безопаснее, читаемее и поддерживает рефакторинг. Raw SQL используется только когда DSL не покрывает требуемую конструкцию.

АспектDrift DSLRaw SQL в Drift
ТипобезопасностьПолная (компиляция)Нет (runtime)
АвтодополнениеДа (IDE)Только в sql-файлах
РефакторингАвтоматическийРучной поиск по строкам
Сложные JOINПоддерживаютсяПолная свобода
Оконные функцииОграниченоПолная поддержка
РеактивностьВстроенная (Stream)Через .watch()

Когда использовать DSL

Drift DSL — основной способ работы. Он покрывает SELECT, INSERT, UPDATE, DELETE, WHERE, ORDER BY, LIMIT, JOIN и группировку. Для всех типовых запросов CRUD используйте DSL: он короче, безопаснее и автоматически обновляет Stream при изменениях.

Когда использовать raw SQL

Raw SQL в Drift нужен для: кастомных SQLite-функций (FTS5, JSON1), сложных подзапросов с EXISTS, INSERT OR REPLACE, массовых UPDATE с CASE, а также для запросов, где производительность критична и DSL не генерирует оптимальный план выполнения. Raw SQL можно писать в .sql-файлах с поддержкой типизации через drift_dev.

Примеры кода с Drift

Drift использует классы, расширяющие Table, или аннотацию @DataClass. Ниже — полный пример модели Task с запросами через DSL, raw SQL и реактивным обновлением. После сборки run build_runner, все сгенерированные классы готовы.

Определение таблицы и базы данных

Класс Tasks расширяет Table и определяет столбцы. Каждый столбец — это выражение типа Column. Параметры: 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);
}

CRUD-операции через DSL

Drift генерирует методы into(tasks).insert(), select(tasks), update(tasks) и delete(tasks) для таблиц. Все операции возвращают Future — работа с SQLite асинхронна. Для отслеживания изменений используйте .watch() вместо .get().

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

Raw SQL с типизированным результатом

Для сложных запросов Drift позволяет писать raw 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 применяет все незакрытые миграции последовательно.

Автоматические миграции

Для добавления столбца с default-значением Drift может сгенерировать миграцию автоматически через MigrationStrategy. Если изменение не нарушает существующие данные (добавление nullable-поля), можно использовать beforeOpen с проверкой версии и выполнением ALTER TABLE.

Ручные миграции

Для сложных изменений (переименование таблицы, объединение данных, изменение типа столбца) Drift требует ручной SQL-миграции. Миграции задаются через параметр migrations в классе базы данных. Каждая миграция — это объект с номерами from/to и SQL-запросами.

Drift и тестирование

Drift поддерживает запуск в тестовом режиме через NativeDatabase.memory(). База в памяти создаётся с нуля перед каждым тестом и уничтожается после. Для мокирования используйте пакет mocktail с замоканным QueryExecutor. Drift также предоставляет DatabaseTestHelper для интеграционных тестов с проверкой миграций и запросов.

DAO-паттерн в Drift

Drift поддерживает DAO (Data Access Object) через абстрактные классы с аннотацией @DriftAccessor. DAO инкапсулирует запросы к одной или нескольким таблицам и может быть протестирован отдельно от базы данных. В отличие от прямых запросов через 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 использует собственный DSL вместо SQL-строк, что даёт полную типобезопасность и автодополнение в IDE. Floor использует SQL-строки в аннотации @Query. Drift также поддерживает больше платформ (включая web) и имеет встроенную реактивность через Stream, тогда как в Floor Stream нужно объявлять вручную.

Поддерживает ли Drift миграции без потери данных?

Да, Drift поддерживает миграции с сохранением данных. Для добавления столбцов используйте addColumn в Migration. Для сложных трансформаций (переименование, объединение) пишите raw SQL внутри миграции. Если миграция не указана, Drift пересоздаёт базу с потерей данных при несовпадении схемы.

Можно ли использовать Drift без build_runner?

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 не имеет встроенного шифрования, но поддерживает подключение кастомных sqlite3-библиотек с SEE (SQLite Encryption Extension). Для мобильных платформ используйте sqflite_sqlcipher в качестве QueryExecutor — Drift работает с любой реализацией SQLite через абстрактный QueryExecutor.

Итоги

  • Drift — реактивная ORM для Flutter и Dart с компиляцией запросов в SQL на этапе сборки
  • DSL-синтаксис — Dart-запросы с полной типобезопасностью и автодополнением в IDE
  • Реактивность — Stream и auto-updating запросы для автоматического обновления UI
  • Кросс-платформенность — Android, iOS, Web (WASM), macOS, Linux, Windows
  • Миграции — автоматические для простых изменений и ручные SQL для сложных
  • Экосистема — интеграция с Riverpod (drift_riverpod) и BLoC (drift_bloc)
  • Рекомендация — выбирайте Drift для проектов, где важны реактивность, типобезопасность и поддержка всех платформ Flutter

Мы разработаем мобильное приложение под ключ

IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

Читайте также