Drift (Moor) — qué es, ORM reactivo y trabajo con bases de datos

Autor: IT Sectr Publicado: 2026-03-13 Tiempo de lectura: 9 min

Drift (anteriormente Moor) es un ORM reactivo para Flutter y Dart, construido sobre SQLite con su propio DSL para consultas. A diferencia de los ORM tradicionales, Drift compila consultas Dart a SQL en tiempo de compilación, eliminando errores en tiempo de ejecución. Según Drift Docs, 2024, Drift genera hasta un 40% más de código que las consultas SQL manuales, pero elimina por completo la escritura manual de SQL, reemplazándola con sintaxis Dart con seguridad de tipos.

Puntos clave

  • Drift — ORM reactivo con compilación de consultas a SQL en tiempo de compilación
  • Consultas DSL — interfaz fluida en Dart sin escribir cadenas SQL
  • Reactividad — Stream y consultas de actualización automática para UI en tiempo real
  • Multiplataforma — Android, iOS, Web, macOS, Linux, Windows
  • Migraciones — versionado automático y migraciones manuales mediante SQL

¿Qué es Drift?

Drift es un ORM para Dart y Flutter, anteriormente conocido como Moor. Fue desarrollado por Simon Binder en 2019 y desde entonces ha pasado por varias versiones importantes. Drift compila consultas Dart a SQL en tiempo de compilación usando drift_dev y build_runner, lo que proporciona seguridad total de tipos y elimina errores de sintaxis SQL en tiempo de ejecución.

A diferencia de Floor, Drift utiliza su propio DSL (Domain-Specific Language) para construir consultas — el desarrollador escribe en Dart y el generador lo traduce a SQL. Esto permite que el IDE verifique la sintaxis, autocomplete los campos de la tabla y refactorice el modelo de datos sin miedo a romper consultas.

Según Drift (2024), la biblioteca se utiliza en más de 8000 proyectos Flutter. Soporta todas las plataformas populares: Android mediante sqflite, iOS mediante sqflite, web mediante sqlite3 WASM, escritorio mediante el controlador nativo sqlite3.

Historia del cambio de nombre: Moor → Drift

Moor fue renombrado a Drift en la versión 2.0 (2022). El motivo fue un conflicto de nombres con otros proyectos y el deseo de distanciarse del código antiguo. La API se mantuvo compatible: para migrar, simplemente reemplaza el import de moor a drift y actualiza las dependencias.

Características clave

Drift proporciona: consultas Stream integradas con actualización automática al cambiar los datos, soporte de transacciones con reversión, consultas SQL personalizadas mediante rawQuery, patrón DAO para encapsulación de lógica, migraciones multiplataforma e integración con Riverpod y BLoC a través de los paquetes drift_riverpod y drift_bloc.

¿Cómo funciona Drift?

Drift utiliza generación de código en tiempo de compilación. El desarrollador describe las tablas mediante anotaciones @DataClass o clases Dart que extienden Table. El generador crea clases auxiliares: Companion (para campos anulables durante inserción/actualización), DriftDatabase (punto de entrada) e implementaciones DAO.

Drift no ejecuta consultas SQLite directamente. En su lugar, el desarrollador escribe en Dart: select(tasks).where(tasks.priority.greaterThan(3)).build(). El generador traduce esto a SQL, y en tiempo de ejecución Drift simplemente envía la consulta SQL lista a SQLite. Esto combina la comodidad de la sintaxis Dart con el rendimiento de SQL nativo.

Arquitectura de consultas

Drift soporta dos modos de consulta: DSL (recomendado) y SQL puro. Las consultas DSL son más seguras de escribir — el compilador verifica nombres de campos, tipos y compatibilidad. El SQL puro es necesario para consultas complejas no cubiertas por DSL: funciones de ventana, CTE recursivas, extensiones SQLite específicas.

Drift DSL vs SQL: comparación de enfoques

Drift ofrece dos formas de escribir consultas: Dart DSL (nativo) y SQL puro (para casos complejos). DSL es preferible para el 90% de los escenarios: es más seguro, más legible y soporta refactorización. El SQL puro se usa solo cuando DSL no cubre la construcción requerida.

AspectoDrift DSLSQL puro en Drift
Seguridad de tiposCompleta (compilación)No (ejecución)
AutocompletadoSí (IDE)Solo en archivos sql
RefactorizaciónAutomáticaBúsqueda manual en cadenas
JOIN complejosSoportadosLibertad total
Funciones de ventanaLimitadoSoporte completo
ReactividadIntegrada (Stream)Mediante .watch()

Cuándo usar DSL

Drift DSL es la forma principal de trabajo. Cubre SELECT, INSERT, UPDATE, DELETE, WHERE, ORDER BY, LIMIT, JOIN y agrupación. Para todas las consultas CRUD típicas, usa DSL: es más corto, más seguro y actualiza automáticamente Stream en los cambios.

Cuándo usar SQL puro

SQL puro en Drift es necesario para: funciones SQLite personalizadas (FTS5, JSON1), subconsultas complejas con EXISTS, INSERT OR REPLACE, UPDATE masivo con CASE, así como consultas donde el rendimiento es crítico y DSL no genera el plan de ejecución óptimo. El SQL puro se puede escribir en archivos .sql con soporte de tipificación mediante drift_dev.

Ejemplos de código con Drift

Drift utiliza clases que extienden Table o la anotación @DataClass. A continuación se muestra un ejemplo completo del modelo Task con consultas mediante DSL, SQL puro y actualización reactiva. Después de ejecutar build_runner, todas las clases generadas están listas.

Definición de tabla y base de datos

La clase Tasks extiende Table y define columnas. Cada columna es una expresión de tipo Column<T>. Parámetros: withDefault() establece un valor predeterminado, autoIncrement() establece auto-incremento. La base de datos es una clase abstracta que extiende $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);
}

Operaciones CRUD mediante DSL

Drift genera los métodos into(tasks).insert(), select(tasks), update(tasks) y delete(tasks) para las tablas. Todas las operaciones devuelven Future — trabajar con SQLite es asíncrono. Para rastrear cambios, usa .watch() en lugar de .get().

dart
// Insertar
await into(tasks).insert(TasksCompanion.insert(
    title: Value('Comprar alimentos'),
    priority: Value(3),
));

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

// Observación reactiva
select(tasks).watch().listen((tasksList) {
    // tasksList — List, se actualiza con cada cambio de la tabla
    updateUi(tasksList);
});

SQL puro con resultado tipificado

Para consultas complejas, Drift permite escribir SQL puro manteniendo la tipificación. El método customSelect toma una cadena de consulta y devuelve un resultado tipificado mediante el generador de código. Este enfoque combina la flexibilidad de SQL con la seguridad de tipos de 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")}');
}

Migraciones y versionado en Drift

Drift soporta tanto migraciones automáticas (para cambios simples) como manuales (para transformaciones complejas). La versión de la base de datos se establece en el constructor de AppDatabase. Si la versión no coincide, Drift aplica todas las migraciones pendientes secuencialmente.

Migraciones automáticas

Para agregar una columna con un valor predeterminado, Drift puede generar una migración automáticamente mediante MigrationStrategy. Si el cambio no afecta los datos existentes (agregar un campo anulable), puedes usar beforeOpen con verificación de versión y ejecución de ALTER TABLE.

Migraciones manuales

Para cambios complejos (renombrar una tabla, fusionar datos, cambiar el tipo de una columna), Drift requiere migración SQL manual. Las migraciones se especifican mediante el parámetro migrations en la clase de base de datos. Cada migración es un objeto con números from/to y consultas SQL.

Drift y pruebas

Drift soporta la ejecución en modo de prueba mediante NativeDatabase.memory(). La base de datos en memoria se crea desde cero antes de cada prueba y se destruye después. Para simular, usa el paquete mocktail con un QueryExecutor simulado. Drift también proporciona DatabaseTestHelper para pruebas de integración con verificación de migraciones y consultas.

Patrón DAO en Drift

Drift soporta DAO (Data Access Object) mediante clases abstractas con la anotación @DriftAccessor. DAO encapsula consultas a una o más tablas y se puede probar por separado de la base de datos. A diferencia de las consultas directas a través de Database, DAO permite reutilizar la lógica de consultas entre diferentes partes de la aplicación y simplifica las pruebas unitarias.

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

Preguntas frecuentes

¿En qué se diferencia Drift de Floor?

Drift utiliza su propio DSL en lugar de cadenas SQL, lo que proporciona seguridad total de tipos y autocompletado en el IDE. Floor usa cadenas SQL en la anotación @Query. Drift también soporta más plataformas (incluyendo web) y tiene reactividad integrada mediante Stream, mientras que en Floor Stream debe declararse manualmente.

¿Drift soporta migraciones sin pérdida de datos?

Sí, Drift soporta migraciones con conservación de datos. Para agregar columnas, usa addColumn en Migration. Para transformaciones complejas (renombrar, fusionar), escribe SQL puro dentro de la migración. Si no se especifica una migración, Drift recrea la base de datos con pérdida de datos cuando el esquema no coincide.

¿Se puede usar Drift sin build_runner?

Drift requiere generación de código mediante build_runner y drift_dev. Sin generación, es imposible crear consultas tipificadas. Sin embargo, para proyectos pequeños, Drift soporta sqlparser — escritura manual de archivos SQL con tipificación automática, pero esto aún requiere un paso de generación.

¿Cómo integrar Drift con Riverpod?

Para la integración con Riverpod, usa el paquete drift_riverpod. Proporciona proveedores para Database, DAO y consultas Stream. Ejemplo: final tasksProvider = databaseProvider.select((db) => db.select(db.tasks).watch()) — la UI se reconstruye automáticamente cuando los datos cambian.

¿Drift soporta cifrado SQLite?

Drift no tiene cifrado integrado, pero soporta la conexión de bibliotecas sqlite3 personalizadas con SEE (SQLite Encryption Extension). Para plataformas móviles, usa sqflite_sqlcipher como QueryExecutor — Drift funciona con cualquier implementación de SQLite a través del QueryExecutor abstracto.

Resumen

  • Drift — un ORM reactivo para Flutter y Dart con compilación de consultas a SQL en tiempo de compilación
  • Sintaxis DSL — consultas Dart con seguridad total de tipos y autocompletado en el IDE
  • Reactividad — Stream y consultas de actualización automática para UI en tiempo real
  • Multiplataforma — Android, iOS, Web (WASM), macOS, Linux, Windows
  • Migraciones — automáticas para cambios simples y SQL manual para complejos
  • Ecosistema — integración con Riverpod (drift_riverpod) y BLoC (drift_bloc)
  • Recomendación — elige Drift para proyectos donde la reactividad, la seguridad de tipos y el soporte para todas las plataformas Flutter sean importantes

Desarrollaremos una aplicación móvil llave en mano

IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.

Discutir el proyecto

Lea también