Drift (παλαιότερα Moor) — reactive ORM για Flutter και Dart, χτισμένο πάνω στην SQLite με δικό του DSL για ερωτήματα. Σε αντίθεση με τα παραδοσιακά ORM, το Drift μεταγλωττίζει τα ερωτήματα Dart σε SQL κατά τη φάση κατασκευής, εξαλείφοντας σφάλματα χρόνου εκτέλεσης. Σύμφωνα με το Drift Docs, 2024, το Drift παράγει έως και 40% περισσότερο κώδικα από τα χειροκίνητα SQL ερωτήματα, αλλά εξαλείφει πλήρως τη χειροκίνητη γραφή SQL, αντικαθιστώντας την με type-safe Dart σύνταξη.
Βασικά Σημεία
Drift — ORM για Dart και Flutter, παλαιότερα γνωστό ως Moor. Αναπτύχθηκε από τον Simon Binder το 2019 και από τότε πέρασε από αρκετές major εκδόσεις. Το Drift μεταγλωττίζει τα Dart ερωτήματα σε SQL κατά τη φάση κατασκευής με τη βοήθεια των drift_dev και build_runner, παρέχοντας πλήρη type-safety και εξαλείφοντας συντακτικά σφάλματα SQL κατά το χρόνο εκτέλεσης.
Σε αντίθεση με το Floor, το Drift χρησιμοποιεί το δικό του DSL (Domain-Specific Language) για την κατασκευή ερωτημάτων — ο προγραμματιστής γράφει σε Dart και ο γεννήτρια το μεταφράζει σε SQL. Αυτό επιτρέπει στο IDE να ελέγχει τη σύνταξη, να συμπληρώνει αυτόματα πεδία πινάκων και να αναδιαμορφώνει το μοντέλο δεδομένων χωρίς φόβο να σπάσει το ερώτημα.
Σύμφωνα με το Drift (2024), η βιβλιοθήκη χρησιμοποιείται σε περισσότερα από 8000 Flutter έργα. Υποστηρίζει όλες τις δημοφιλείς πλατφόρμες: Android μέσω sqflite, iOS μέσω sqflite, web μέσω sqlite3 WASM, desktop μέσω sqlite3 native driver.
Moor μετονομάστηκε σε Drift στην έκδοση 2.0 (2022). Ο λόγος — σύγκρουση ονομάτων με άλλα έργα και επιθυμία απομάκρυνσης από τον παλιό κώδικα. Το API παρέμεινε συμβατό: για μετεγκατάσταση αρκεί η αλλαγή του import από moor σε drift και η ενημέρωση των dependencies.
Drift παρέχει: ενσωματωμένα Stream ερωτήματα με αυτόματη ενημέρωση κατά την αλλαγή δεδομένων, υποστήριξη συναλλαγών με αναίρεση, προσαρμοσμένα SQL ερωτήματα μέσω rawQuery, μοτίβο DAO για ενθυλάκωση λογικής, cross-platform μεταναστεύσεις και ενσωμάτωση με Riverpod και BLoC μέσω των πακέτων drift_riverpod και drift_bloc.
Drift χρησιμοποιεί δημιουργία κώδικα κατά τη φάση μεταγλώττισης. Ο προγραμματιστής περιγράφει τους πίνακες μέσω @DataClass annotations ή Dart κλάσεων που επεκτείνουν το Table. Ο γεννήτρια δημιουργεί βοηθητικές κλάσεις: Companion (για nullable πεδία κατά την εισαγωγή/ενημέρωση), DriftDatabase (σημείο εισόδου) και υλοποιήσεις DAO.
Το Drift δεν εκτελεί απευθείας ερωτήματα SQLite. Αντίθετα, ο προγραμματιστής γράφει σε Dart: select(tasks).where(tasks.priority.greaterThan(3)).build(). Ο γεννήτρια το μεταφράζει σε SQL και κατά το χρόνο εκτέλεσης το Drift απλώς στέλνει το έτοιμο SQL ερώτημα στην SQLite. Αυτό συνδυάζει την ευκολία της Dart σύνταξης με την απόδοση της native SQL.
Drift υποστηρίζει δύο τρόπους ερωτημάτων: DSL (συνιστάται) και raw SQL. Τα DSL ερωτήματα είναι ασφαλέστερα — ο μεταγλωττιστής ελέγχει ονόματα πεδίων, τύπους και συμβατότητα. Το Raw SQL χρειάζεται για σύνθετα ερωτήματα που δεν καλύπτονται από το DSL: συναρτήσεις παραθύρου, recursive CTE, συγκεκριμένες επεκτάσεις SQLite.
Drift προσφέρει δύο τρόπους γραφής ερωτημάτων: Dart DSL (native) και raw SQL (για σύνθετες περιπτώσεις). Το DSL είναι προτιμότερο για το 90% των σεναρίων: είναι ασφαλέστερο, πιο ευανάγνωστο και υποστηρίζει αναδιάρθρωση. Το Raw SQL χρησιμοποιείται μόνο όταν το DSL δεν καλύπτει την απαιτούμενη κατασκευή.
| Πτυχή | Drift DSL | Raw SQL στο Drift |
|---|---|---|
| Type-safety | Πλήρης (μεταγλώττιση) | Όχι (runtime) |
| Αυτοσυμπλήρωση | Ναι (IDE) | Μόνο σε .sql αρχεία |
| Αναδιάρθρωση | Αυτόματη | Χειροκίνητη αναζήτηση σε συμβολοσειρές |
| Σύνθετα JOIN | Υποστηρίζονται | Πλήρης ελευθερία |
| Συναρτήσεις παραθύρου | Περιορισμένα | Πλήρης υποστήριξη |
| Αντιδραστικότητα | Ενσωματωμένη (Stream) | Μέσω .watch() |
Drift DSL — ο κύριος τρόπος εργασίας. Καλύπτει SELECT, INSERT, UPDATE, DELETE, WHERE, ORDER BY, LIMIT, JOIN και ομαδοποίηση. Για όλα τα τυπικά CRUD ερωτήματα χρησιμοποιήστε DSL: είναι συντομότερο, ασφαλέστερο και ενημερώνει αυτόματα το Stream σε αλλαγές.
Raw SQL στο Drift χρειάζεται για: προσαρμοσμένες συναρτήσεις SQLite (FTS5, JSON1), σύνθετα υποερωτήματα με EXISTS, INSERT OR REPLACE, μαζικές ενημερώσεις με CASE, καθώς και για ερωτήματα όπου η απόδοση είναι κρίσιμη και το DSL δεν παράγει βέλτιστο σχέδιο εκτέλεσης. Το Raw SQL μπορεί να γραφτεί σε .sql αρχεία με υποστήριξη τύπων μέσω drift_dev.
Drift χρησιμοποιεί κλάσεις που επεκτείνουν το Table ή την annotation @DataClass. Παρακάτω είναι ένα πλήρες παράδειγμα μοντέλου Task με ερωτήματα μέσω DSL, raw SQL και αντιδραστική ενημέρωση. Μετά την εκτέλεση του build_runner, όλες οι παραγόμενες κλάσεις είναι έτοιμες.
Η κλάση Tasks επεκτείνει το Table και ορίζει στήλες. Κάθε στήλη είναι μια έκφραση τύπου Column<T>. Παράμετροι: το withDefault() ορίζει προεπιλεγμένη τιμή, το autoIncrement() — αυτόματη αύξηση. Η βάση δεδομένων είναι μια abstract κλάση που επεκτείνει το $DriftDatabase.
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);
}
Το Drift παράγει μεθόδους into(tasks).insert(), select(tasks), update(tasks) και delete(tasks) για τους πίνακες. Όλες οι λειτουργίες επιστρέφουν Future — η εργασία με SQLite είναι ασύγχρονη. Για παρακολούθηση αλλαγών χρησιμοποιήστε .watch() αντί για .get().
// Εισαγωγή
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);
});
Για σύνθετα ερωτήματα, το Drift επιτρέπει τη γραφή raw SQL διατηρώντας την τυποποίηση. Η μέθοδος customSelect δέχεται μια συμβολοσειρά ερωτήματος και επιστρέφει τυποποιημένο αποτέλεσμα μέσω του γεννήτρια κώδικα. Αυτή η προσέγγιση συνδυάζει την ευελιξία της SQL με την ασφάλεια τύπων του Drift.
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 υποστηρίζει τόσο αυτόματες μεταναστεύσεις (για απλές αλλαγές) όσο και χειροκίνητες (για σύνθετους μετασχηματισμούς). Η έκδοση της βάσης δεδομένων ορίζεται στον κατασκευαστή του AppDatabase. Σε περίπτωση αναντιστοιχίας έκδοσης, το Drift εφαρμόζει όλες τις εκκρεμείς μεταναστεύσεις διαδοχικά.
Για προσθήκη στήλης με προεπιλεγμένη τιμή, το Drift μπορεί να δημιουργήσει αυτόματα μετεγκατάσταση μέσω MigrationStrategy. Αν η αλλαγή δεν παραβιάζει υπάρχοντα δεδομένα (προσθήκη nullable πεδίου), μπορείτε να χρησιμοποιήσετε το beforeOpen με έλεγχο έκδοσης και εκτέλεση ALTER TABLE.
Για σύνθετες αλλαγές (μετονομασία πίνακα, συγχώνευση δεδομένων, αλλαγή τύπου στήλης) το Drift απαιτεί χειροκίνητη SQL μετεγκατάσταση. Οι μεταναστεύσεις ορίζονται μέσω της παραμέτρου migrations στην κλάση της βάσης δεδομένων. Κάθε μετεγκατάσταση είναι ένα αντικείμενο με αριθμούς from/to και SQL ερωτήματα.
Drift υποστηρίζει εκτέλεση σε λειτουργία δοκιμής μέσω NativeDatabase.memory(). Η βάση δεδομένων στη μνήμη δημιουργείται από την αρχή πριν από κάθε δοκιμή και καταστρέφεται μετά. Για προσομοίωση χρησιμοποιήστε το πακέτο mocktail με mocked QueryExecutor. Το Drift παρέχει επίσης DatabaseTestHelper για δοκιμές ενσωμάτωσης με έλεγχο μεταναστεύσεων και ερωτημάτων.
Drift υποστηρίζει το μοτίβο DAO (Data Access Object) μέσω abstract κλάσεων με annotation @DriftAccessor. Το DAO ενθυλακώνει ερωτήματα σε έναν ή περισσότερους πίνακες και μπορεί να δοκιμαστεί ανεξάρτητα από τη βάση δεδομένων. Σε αντίθεση με τα άμεσα ερωτήματα μέσω Database, το DAO επιτρέπει την επαναχρησιμοποίηση λογικής ερωτημάτων μεταξύ διαφορετικών τμημάτων της εφαρμογής και απλοποιεί τις μοναδιαίες δοκιμές.
@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 χρησιμοποιεί το δικό του DSL αντί για SQL συμβολοσειρές, παρέχοντας πλήρη type-safety και αυτοσυμπλήρωση στο IDE. Το Floor χρησιμοποιεί SQL συμβολοσειρές στην annotation @Query. Το Drift υποστηρίζει επίσης περισσότερες πλατφόρμες (συμπεριλαμβανομένου του web) και διαθέτει ενσωματωμένη αντιδραστικότητα μέσω Stream, ενώ στο Floor το Stream πρέπει να δηλωθεί χειροκίνητα.
Ναι, το Drift υποστηρίζει μεταναστεύσεις με διατήρηση δεδομένων. Για προσθήκη στηλών χρησιμοποιήστε το addColumn στο Migration. Για σύνθετους μετασχηματισμούς (μετονομασία, συγχώνευση) γράψτε raw SQL μέσα στη μετεγκατάσταση. Εάν δεν οριστεί μετεγκατάσταση, το Drift αναδημιουργεί τη βάση με απώλεια δεδομένων όταν το σχήμα δεν ταιριάζει.
Drift απαιτεί δημιουργία κώδικα μέσω build_runner και drift_dev. Χωρίς δημιουργία, δεν είναι δυνατή η δημιουργία τυποποιημένων ερωτημάτων. Ωστόσο, για μικρά έργα το Drift υποστηρίζει sqlparser — χειροκίνητη γραφή SQL αρχείων με αυτόματη τυποποίηση, αλλά αυτό εξακολουθεί να απαιτεί το στάδιο δημιουργίας.
Για ενσωμάτωση με Riverpod χρησιμοποιήστε το πακέτο drift_riverpod. Παρέχει providers για Database, DAO και Stream ερωτήματα. Παράδειγμα: final tasksProvider = databaseProvider.select((db) => db.select(db.tasks).watch()) — το UI αναδομείται αυτόματα όταν αλλάζουν τα δεδομένα.
Drift δεν διαθέτει ενσωματωμένη κρυπτογράφηση, αλλά υποστηρίζει σύνδεση προσαρμοσμένων βιβλιοθηκών sqlite3 με SEE (SQLite Encryption Extension). Για πλατφόρμες κινητών χρησιμοποιήστε το sqflite_sqlcipher ως QueryExecutor — το Drift λειτουργεί με οποιαδήποτε υλοποίηση SQLite μέσω του αφηρημένου QueryExecutor.
Σύνοψη
Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης