Drift (Moor) — cos’è, ORM reattivo e lavoro con database

Autore: IT Sectr Pubblicato: 2026-03-13 Tempo di lettura: 9 min

Drift (precedentemente Moor) è un ORM reattivo per Flutter e Dart, costruito su SQLite con un proprio DSL per le query. A differenza degli ORM tradizionali, Drift compila le query Dart in SQL in fase di build, eliminando gli errori runtime. Secondo Drift Docs, 2024, Drift genera fino al 40% in più di codice rispetto alle query SQL manuali, ma elimina completamente la scrittura manuale di SQL, sostituendola con sintassi Dart type-safe.

Punti chiave

  • Drift — ORM reattivo con compilazione delle query in SQL in fase di build
  • Query DSL — interfaccia fluida in Dart senza scrivere stringhe SQL
  • Reattività — Stream e query ad aggiornamento automatico per UI in tempo reale
  • Multipiattaforma — Android, iOS, Web, macOS, Linux, Windows
  • Migrazioni — versionamento automatico e migrazioni manuali tramite SQL

Cos’è Drift?

Drift è un ORM per Dart e Flutter, precedentemente noto come Moor. È stato sviluppato da Simon Binder nel 2019 e da allora ha attraversato diverse versioni principali. Drift compila le query Dart in SQL in fase di build utilizzando drift_dev e build_runner, fornendo completa sicurezza dei tipi ed eliminando gli errori di sintassi SQL in fase di esecuzione.

A differenza di Floor, Drift utilizza un proprio DSL (Domain-Specific Language) per costruire le query — lo sviluppatore scrive in Dart e il generatore lo traduce in SQL. Ciò consente all’IDE di verificare la sintassi, autocompletare i campi delle tabelle e rifattorizzare il modello dati senza timore di rompere le query.

Secondo Drift (2024), la libreria è utilizzata in oltre 8000 progetti Flutter. Supporta tutte le piattaforme popolari: Android tramite sqflite, iOS tramite sqflite, web tramite sqlite3 WASM, desktop tramite driver nativo sqlite3.

Storia della ridenominazione: Moor → Drift

Moor è stato rinominato in Drift nella versione 2.0 (2022). Il motivo era un conflitto di nomi con altri progetti e il desiderio di prendere le distanze dal vecchio codice. L’API è rimasta compatibile: per migrare, è sufficiente sostituire l’import da moor a drift e aggiornare le dipendenze.

Caratteristiche principali

Drift fornisce: query Stream integrate con aggiornamento automatico al variare dei dati, supporto alle transazioni con rollback, query SQL personalizzate tramite rawQuery, pattern DAO per l’incapsulamento della logica, migrazioni multipiattaforma e integrazione con Riverpod e BLoC tramite i pacchetti drift_riverpod e drift_bloc.

Come funziona Drift?

Drift utilizza la generazione di codice in fase di compilazione. Lo sviluppatore descrive le tabelle tramite annotazioni @DataClass o classi Dart che estendono Table. Il generatore crea classi helper: Companion (per campi nullable durante inserimento/aggiornamento), DriftDatabase (punto di ingresso) e implementazioni DAO.

Drift non esegue query SQLite direttamente. Invece, lo sviluppatore scrive in Dart: select(tasks).where(tasks.priority.greaterThan(3)).build(). Il generatore traduce ciò in SQL, e in fase di esecuzione Drift invia semplicemente la query SQL pronta a SQLite. Ciò unisce la comodità della sintassi Dart con le prestazioni del SQL nativo.

Architettura delle query

Drift supporta due modalità di query: DSL (consigliato) e SQL puro. Le query DSL sono più sicure da scrivere — il compilatore verifica nomi dei campi, tipi e compatibilità. L’SQL puro è necessario per query complesse non coperte dal DSL: funzioni finestra, CTE ricorsive, estensioni SQLite specifiche.

Drift DSL vs SQL: confronto degli approcci

Drift offre due modi per scrivere query: Dart DSL (nativo) e SQL puro (per casi complessi). Il DSL è preferibile per il 90% degli scenari: è più sicuro, più leggibile e supporta il refactoring. L’SQL puro viene utilizzato solo quando il DSL non copre la costruzione necessaria.

AspettoDrift DSLSQL puro in Drift
Sicurezza dei tipiCompleta (compilazione)No (runtime)
AutocompletamentoSì (IDE)Solo nei file sql
RefactoringAutomaticoRicerca manuale nelle stringhe
JOIN complessiSupportatiPiena libertà
Funzioni finestraLimitatoSupporto completo
ReattivitàIntegrata (Stream)Tramite .watch()

Quando usare DSL

Drift DSL è la modalità di lavoro principale. Copre SELECT, INSERT, UPDATE, DELETE, WHERE, ORDER BY, LIMIT, JOIN e raggruppamento. Per tutte le query CRUD tipiche, usa DSL: è più breve, più sicuro e aggiorna automaticamente lo Stream alle modifiche.

Quando usare SQL puro

SQL puro in Drift è necessario per: funzioni SQLite personalizzate (FTS5, JSON1), sottoquery complesse con EXISTS, INSERT OR REPLACE, UPDATE massivo con CASE, nonché query dove le prestazioni sono critiche e il DSL non genera il piano di esecuzione ottimale. L’SQL puro può essere scritto in file .sql con supporto alla tipizzazione tramite drift_dev.

Esempi di codice con Drift

Drift utilizza classi che estendono Table o l’annotazione @DataClass. Di seguito è riportato un esempio completo del modello Task con query tramite DSL, SQL puro e aggiornamento reattivo. Dopo aver eseguito build_runner, tutte le classi generate sono pronte.

Definizione di tabella e database

La classe Tasks estende Table e definisce le colonne. Ogni colonna è un’espressione di tipo Column<T>. Parametri: withDefault() imposta un valore predefinito, autoIncrement() imposta l’auto-incremento. Il database è una classe astratta che estende $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);
}

Operazioni CRUD tramite DSL

Drift genera i metodi into(tasks).insert(), select(tasks), update(tasks) e delete(tasks) per le tabelle. Tutte le operazioni restituiscono Future — lavorare con SQLite è asincrono. Per tracciare le modifiche, usa .watch() invece di .get().

dart
// Inserisci
await into(tasks).insert(TasksCompanion.insert(
    title: Value('Comprare generi alimentari'),
    priority: Value(3),
));

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

// Osservazione reattiva
select(tasks).watch().listen((tasksList) {
    // tasksList — List, si aggiorna a ogni modifica della tabella
    updateUi(tasksList);
});

SQL puro con risultato tipizzato

Per query complesse, Drift consente di scrivere SQL puro mantenendo la tipizzazione. Il metodo customSelect accetta una stringa di query e restituisce un risultato tipizzato tramite il generatore di codice. Questo approccio combina la flessibilità di SQL con la sicurezza dei tipi di 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")}');
}

Migrazioni e versionamento in Drift

Drift supporta sia migrazioni automatiche (per modifiche semplici) che manuali (per trasformazioni complesse). La versione del database viene impostata nel costruttore di AppDatabase. Se la versione non corrisponde, Drift applica tutte le migrazioni in sospeso in sequenza.

Migrazioni automatiche

Per aggiungere una colonna con un valore predefinito, Drift può generare automaticamente una migrazione tramite MigrationStrategy. Se la modifica non danneggia i dati esistenti (aggiunta di un campo nullable), puoi usare beforeOpen con controllo della versione ed esecuzione di ALTER TABLE.

Migrazioni manuali

Per modifiche complesse (rinominare una tabella, unire dati, cambiare il tipo di una colonna), Drift richiede una migrazione SQL manuale. Le migrazioni vengono specificate tramite il parametro migrations nella classe del database. Ogni migrazione è un oggetto con numeri from/to e query SQL.

Drift e test

Drift supporta l’esecuzione in modalità test tramite NativeDatabase.memory(). Il database in memoria viene creato da zero prima di ogni test e distrutto dopo. Per il mocking, usa il pacchetto mocktail con un QueryExecutor simulato. Drift fornisce anche DatabaseTestHelper per test di integrazione con verifica di migrazioni e query.

Pattern DAO in Drift

Drift supporta DAO (Data Access Object) tramite classi astratte con l’annotazione @DriftAccessor. DAO incapsula le query a una o più tabelle e può essere testato separatamente dal database. A differenza delle query dirette tramite Database, DAO consente di riutilizzare la logica delle query tra diverse parti dell’applicazione e semplifica i test unitari.

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

Domande frequenti

In cosa differisce Drift da Floor?

Drift utilizza un proprio DSL invece di stringhe SQL, fornendo completa sicurezza dei tipi e autocompletamento nell’IDE. Floor utilizza stringhe SQL nell’annotazione @Query. Drift supporta anche più piattaforme (incluso il web) e ha reattività integrata tramite Stream, mentre in Floor lo Stream deve essere dichiarato manualmente.

Drift supporta migrazioni senza perdita di dati?

Sì, Drift supporta migrazioni con conservazione dei dati. Per aggiungere colonne, usa addColumn in Migration. Per trasformazioni complesse (rinominare, unire), scrivi SQL puro all’interno della migrazione. Se non viene specificata alcuna migrazione, Drift ricrea il database con perdita di dati quando lo schema non corrisponde.

Si può usare Drift senza build_runner?

Drift richiede la generazione di codice tramite build_runner e drift_dev. Senza generazione, è impossibile creare query tipizzate. Tuttavia, per progetti piccoli, Drift supporta sqlparser — scrittura manuale di file SQL con tipizzazione automatica, ma richiede comunque un passaggio di generazione.

Come integrare Drift con Riverpod?

Per l’integrazione con Riverpod, usa il pacchetto drift_riverpod. Fornisce provider per Database, DAO e query Stream. Esempio: final tasksProvider = databaseProvider.select((db) => db.select(db.tasks).watch()) — l’UI si ricostruisce automaticamente quando i dati cambiano.

Drift supporta la crittografia SQLite?

Drift non ha crittografia integrata, ma supporta la connessione di librerie sqlite3 personalizzate con SEE (SQLite Encryption Extension). Per piattaforme mobili, usa sqflite_sqlcipher come QueryExecutor — Drift funziona con qualsiasi implementazione SQLite tramite l’QueryExecutor astratto.

Riepilogo

  • Drift — un ORM reattivo per Flutter e Dart con compilazione delle query in SQL in fase di build
  • Sintassi DSL — query Dart con completa sicurezza dei tipi e autocompletamento nell’IDE
  • Reattività — Stream e query ad aggiornamento automatico per UI in tempo reale
  • Multipiattaforma — Android, iOS, Web (WASM), macOS, Linux, Windows
  • Migrazioni — automatiche per modifiche semplici e SQL manuale per quelle complesse
  • Ecosistema — integrazione con Riverpod (drift_riverpod) e BLoC (drift_bloc)
  • Raccomandazione — scegli Drift per progetti dove reattività, sicurezza dei tipi e supporto per tutte le piattaforme Flutter sono importanti

Svilupperemo un'applicazione mobile chiavi in mano

IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.

Discuti il progetto

Leggi anche