Drift (Moor) — co to jest, reaktywny ORM i praca z bazami danych

Autor: IT Sectr Opublikowano: 2026-03-13 Czas czytania: 9 min

Drift (dawniej Moor) — reaktywny ORM dla Flutter i Dart, zbudowany na SQLite z własnym DSL do zapytań. W przeciwieństwie do tradycyjnych ORM, Drift kompiluje zapytania Dart do SQL na etapie budowania, eliminując błędy runtime. Według Drift Docs, 2024, Drift generuje do 40% więcej kodu niż ręczne zapytania SQL, ale całkowicie eliminuje ręczne pisanie SQL, zastępując go bezpiecznym typowo składnią Dart.

Najważniejsze

  • Drift — reaktywny ORM z kompilacją zapytań do SQL na etapie budowania
  • Zapytania DSL — interfejs fluent w Dart bez pisania łańcuchów SQL
  • Reaktywność — Stream i zapytania auto-updating do aktualizacji UI w czasie rzeczywistym
  • Wieloplatformowość — Android, iOS, Web, macOS, Linux, Windows
  • Migracje — automatyczne wersjonowanie i ręczne migracje przez SQL

Czym jest Drift?

Drift — ORM dla Dart i Flutter, wcześniej znany jako Moor. Został opracowany przez Simona Binder w 2019 roku i przeszedł kilka głównych wersji. Drift kompiluje zapytania Dart do SQL na etapie budowania za pomocą drift_dev i build_runner, co zapewnia pełną bezpieczeństwo typowe i eliminuje błędy składniowe SQL w runtime.

W przeciwieństwie do Floor, Drift używa własnego DSL (Domain-Specific Language) do budowania zapytań — programista pisze w Dart, a generator tłumaczy to na SQL. Pozwala to IDE sprawdzać składnię, autouzupełniać pola tabel i refaktorować model danych bez obawy o zepsucie zapytania.

Według Drift (2024), biblioteka jest używana w ponad 8000 projektów Flutter. Obsługuje wszystkie popularne platformy: Android przez sqflite, iOS przez sqflite, web przez sqlite3 WASM, desktop przez natywny sterownik sqlite3.

Historia zmiany nazwy: Moor → Drift

Moor został przemianowany na Drift w wersji 2.0 (2022). Powodem był konflikt nazw z innymi projektami i chęć zdystansowania się od starego kodu. API pozostało kompatybilne: do migracji wystarczy zastąpić import z moor na drift i zaktualizować zależności.

Kluczowe możliwości

Drift zapewnia: wbudowane zapytania Stream z automatyczną aktualizacją przy zmianie danych, obsługę transakcji z wycofywaniem, niestandardowe zapytania SQL przez rawQuery, wzorzec DAO do enkapsulacji logiki, wieloplatformowe migracje oraz integrację z Riverpod i BLoC przez pakiety drift_riverpod i drift_bloc.

Jak działa Drift?

Drift używa generowania kodu na etapie kompilacji. Programista opisuje tabele przez adnotacje @DataClass lub klasy Dart rozszerzające Table. Generator tworzy klasy pomocnicze: Companion (dla pól nullable przy wstawianiu/aktualizacji), DriftDatabase (punkt wejścia) i implementacje DAO.

Zapytania SQLite Drift nie wykonuje bezpośrednio. Zamiast tego programista pisze w Dart: select(tasks).where(tasks.priority.greaterThan(3)).build(). Generator tłumaczy to na SQL, a w czasie wykonania Drift po prostu wysyła gotowe zapytanie SQL do SQLite. Łączy to wygodę składni Dart z wydajnością natywnego SQL.

Architektura zapytań

Drift obsługuje dwa tryby zapytań: DSL (zalecany) i raw SQL. Zapytania DSL są bezpieczniejsze w pisaniu — kompilator sprawdza nazwy pól, typy i kompatybilność. Raw SQL jest potrzebny do złożonych zapytań nieobjętych DSL: funkcje okienne, rekurencyjne CTE, specyficzne rozszerzenia SQLite.

Drift DSL vs SQL: porównanie podejść

Drift oferuje dwa sposoby pisania zapytań: Dart DSL (natywny) i raw SQL (dla złożonych przypadków). DSL jest preferowany w 90% scenariuszy: jest bezpieczniejszy, bardziej czytelny i obsługuje refaktoryzację. Raw SQL jest używany tylko wtedy, gdy DSL nie obejmuje wymaganej konstrukcji.

AspektDrift DSLRaw SQL w Drift
Bezpieczeństwo typówPełne (kompilacja)Brak (runtime)
AutouzupełnianieTak (IDE)Tylko w plikach sql
RefaktoryzacjaAutomatycznaRęczne wyszukiwanie w łańcuchach
Złożone JOINObsługiwanePełna swoboda
Funkcje okienneOgraniczonePełne wsparcie
ReaktywnośćWbudowana (Stream)Przez .watch()

Kiedy używać DSL

Drift DSL — główny sposób pracy. Obejmuje SELECT, INSERT, UPDATE, DELETE, WHERE, ORDER BY, LIMIT, JOIN i grupowanie. Do wszystkich typowych zapytań CRUD używaj DSL: jest krótszy, bezpieczniejszy i automatycznie aktualizuje Stream przy zmianach.

Kiedy używać raw SQL

Raw SQL w Drift jest potrzebny do: niestandardowych funkcji SQLite (FTS5, JSON1), złożonych podzapytań z EXISTS, INSERT OR REPLACE, masowych UPDATE z CASE, a także do zapytań, w których wydajność jest krytyczna i DSL nie generuje optymalnego planu wykonania. Raw SQL można pisać w plikach .sql z obsługą typizacji przez drift_dev.

Przykłady kodu z Drift

Drift używa klas rozszerzających Table lub adnotacji @DataClass. Poniżej — pełny przykład modelu Task z zapytaniami przez DSL, raw SQL i reaktywną aktualizacją. Po uruchomieniu build_runner, wszystkie wygenerowane klasy są gotowe.

Definiowanie tabeli i bazy danych

Klasa Tasks rozszerza Table i definiuje kolumny. Każda kolumna to wyrażenie typu Column<T>. Parametry: withDefault() ustawia wartość domyślną, autoIncrement() — autoinkrementację. Baza danych to abstrakcyjna klasa rozszerzająca $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);
}

Operacje CRUD przez DSL

Drift generuje metody into(tasks).insert(), select(tasks), update(tasks) i delete(tasks) dla tabel. Wszystkie operacje zwracają Future — praca z SQLite jest asynchroniczna. Do śledzenia zmian używaj .watch() zamiast .get().

dart
// Zapis
await into(tasks).insert(TasksCompanion.insert(
    title: Value('Kup produkty'),
    priority: Value(3),
));

// Odczyt z filtrem
final highPriority = await (select(tasks)
    ..where((t) => t.priority.greaterThan(2))
    ..orderBy([(t) => OrderingTerm(expression: t.priority, mode: OrderingMode.desc)]))
    .get();

// Obserwacja reaktywna
select(tasks).watch().listen((tasksList) {
    // tasksList — List, aktualizowana przy każdej zmianie tabeli
    updateUi(tasksList);
});

Raw SQL z typowanym wynikiem

Dla złożonych zapytań Drift pozwala pisać raw SQL, zachowując typowanie. Metoda customSelect przyjmuje łańcuch zapytania i zwraca typowany wynik przez generator kodu. Takie podejście łączy elastyczność SQL z bezpieczeństwem typów 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")}');
}

Migracje i wersjonowanie w Drift

Drift obsługuje zarówno automatyczne migracje (dla prostych zmian), jak i ręczne (dla złożonych transformacji). Wersja bazy jest ustawiana w konstruktorze AppDatabase. W przypadku niezgodności wersji, Drift stosuje wszystkie niezamknięte migracje sekwencyjnie.

Automatyczne migracje

Do dodania kolumny z wartością domyślną Drift może wygenerować migrację automatycznie przez MigrationStrategy. Jeśli zmiana nie narusza istniejących danych (dodanie pola nullable), można użyć beforeOpen ze sprawdzeniem wersji i wykonaniem ALTER TABLE.

Ręczne migracje

Do złożonych zmian (zmiana nazwy tabeli, łączenie danych, zmiana typu kolumny) Drift wymaga ręcznej migracji SQL. Migracje są określane przez parametr migrations w klasie bazy danych. Każda migracja to obiekt z numerami from/to i zapytaniami SQL.

Drift i testowanie

Drift obsługuje uruchamianie w trybie testowym przez NativeDatabase.memory(). Baza w pamięci jest tworzona od nowa przed każdym testem i niszczona po. Do mockowania używaj pakietu mocktail z zamockowanym QueryExecutor. Drift udostępnia również DatabaseTestHelper do testów integracyjnych z weryfikacją migracji i zapytań.

Wzorzec DAO w Drift

Drift obsługuje DAO (Data Access Object) przez abstrakcyjne klasy z adnotacją @DriftAccessor. DAO enkapsuluje zapytania do jednej lub wielu tabel i może być testowany oddzielnie od bazy danych. W przeciwieństwie do bezpośrednich zapytań przez Database, DAO pozwala ponownie używać logiki zapytań między różnymi częściami aplikacji i upraszcza testowanie modułowe.

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

Często zadawane pytania

Czym różni się Drift od Floor?

Drift używa własnego DSL zamiast łańcuchów SQL, co zapewnia pełną bezpieczeństwo typowe i autouzupełnianie w IDE. Floor używa łańcuchów SQL w adnotacji @Query. Drift obsługuje również więcej platform (w tym web) i ma wbudowaną reaktywność przez Stream, podczas gdy w Floor Stream trzeba deklarować ręcznie.

Czy Drift obsługuje migracje bez utraty danych?

Tak, Drift obsługuje migracje z zachowaniem danych. Do dodawania kolumn używaj addColumn w Migration. Do złożonych transformacji (zmiana nazwy, łączenie) pisz raw SQL wewnątrz migracji. Jeśli migracja nie jest określona, Drift odtwarza bazę z utratą danych przy niezgodności schematu.

Czy można używać Drift bez build_runner?

Drift wymaga generowania kodu przez build_runner i drift_dev. Bez generacji nie można tworzyć typowanych zapytań. Jednak dla małych projektów Drift obsługuje sqlparser — ręczne pisanie plików SQL z automatyczną typizacją, ale to wciąż wymaga etapu generacji.

Jak zintegrować Drift z Riverpod?

Do integracji z Riverpod używaj pakietu drift_riverpod. Udostępnia on providerów dla Database, DAO i zapytań Stream. Przykład: final tasksProvider = databaseProvider.select((db) => db.select(db.tasks).watch()) — UI automatycznie przebudowuje się przy zmianie danych.

Czy Drift obsługuje szyfrowanie SQLite?

Drift nie ma wbudowanego szyfrowania, ale obsługuje podłączanie niestandardowych bibliotek sqlite3 z SEE (SQLite Encryption Extension). Dla platform mobilnych używaj sqflite_sqlcipher jako QueryExecutor — Drift działa z dowolną implementacją SQLite przez abstrakcyjny QueryExecutor.

Podsumowanie

  • Drift — reaktywny ORM dla Flutter i Dart z kompilacją zapytań do SQL na etapie budowania
  • Składnia DSL — zapytania Dart z pełnym bezpieczeństwem typów i autouzupełnianiem w IDE
  • Reaktywność — Stream i zapytania auto-updating do automatycznej aktualizacji UI
  • Wieloplatformowość — Android, iOS, Web (WASM), macOS, Linux, Windows
  • Migracje — automatyczne dla prostych zmian i ręczne SQL dla złożonych
  • Ekosystem — integracja z Riverpod (drift_riverpod) i BLoC (drift_bloc)
  • Zalecenie — wybierz Drift do projektów, gdzie ważne są reaktywność, bezpieczeństwo typów i obsługa wszystkich platform Flutter

Opracujemy aplikację mobilną pod klucz

IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.

Omów projekt

Przeczytaj również