Méthodes conseillées pour la migration de firebase database in-app pour Sqlite

J’utilise sqlite pour mon iPhone et je prévois que le schéma de la firebase database pourrait changer avec le temps. Quels sont les pièges, conventions de nommage et choses à surveiller pour réussir une migration à chaque fois?

Par exemple, j’ai pensé à append une version au nom de la firebase database (par exemple, Database_v1).

Je maintiens une application qui doit périodiquement mettre à jour une firebase database sqlite et migrer les anciennes bases de données vers le nouveau schéma et voici ce que je fais:

Pour suivre la version de la firebase database, j’utilise la variable utilisateur intégrée fournie par sqlite (sqlite ne fait rien avec cette variable, vous êtes libre de l’utiliser comme bon vous semble). Il commence à 0 et vous pouvez obtenir / définir cette variable avec les instructions sqlite suivantes:

> PRAGMA user_version; > PRAGMA user_version = 1; 

Lorsque l’application démarre, je vérifie la version utilisateur actuelle, applique les modifications nécessaires pour mettre le schéma à jour, puis met à jour la version utilisateur. J’emballe les mises à jour dans une transaction afin que, en cas de problème, les modifications ne soient pas validées.

Pour effectuer des modifications de schéma, sqlite prend en charge la syntaxe “ALTER TABLE” pour certaines opérations (renommer la table ou append une colonne). C’est un moyen facile de mettre à jour des tables existantes sur place. Voir la documentation ici: http://www.sqlite.org/lang_altertable.html . Pour supprimer des colonnes ou d’autres modifications non sockets en charge par la syntaxe “ALTER TABLE”, je crée une nouvelle table, y migre la date, supprime l’ancienne table et renomme la nouvelle table en nom d’origine.

La réponse de Just Curious est sans réponse (vous avez compris mon point!), Et c’est ce que nous utilisons pour suivre la version du schéma de firebase database actuellement dans l’application.

Pour exécuter les migrations nécessaires pour que user_version corresponde à la version de schéma attendue de l’application, nous utilisons une instruction switch. Voici un exemple de ce que cela ressemble dans notre application Ssortingp :

 - (void) migrateToSchemaFromVersion:(NSInteger)fromVersion toVersion:(NSInteger)toVersion { // allow migrations to fall thru switch cases to do a complete run // start with current version + 1 [self beginTransaction]; switch (fromVersion + 1) { case 3: // change pin type to mode 'pin' for keyboard handling changes // removing types from previous schema sqlite3_exec(db, "DELETE FROM types;", NULL, NULL, NULL); NSLog(@"installing current types"); [self loadInitialData]; case 4: //adds support for recent view tracking sqlite3_exec(db, "ALTER TABLE ensortinges ADD COLUMN touched_at TEXT;", NULL, NULL, NULL); case 5: { sqlite3_exec(db, "ALTER TABLE categories ADD COLUMN image TEXT;", NULL, NULL, NULL); sqlite3_exec(db, "ALTER TABLE categories ADD COLUMN entry_count INTEGER;", NULL, NULL, NULL); sqlite3_exec(db, "CREATE INDEX IF NOT EXISTS categories_id_idx ON categories(id);", NULL, NULL, NULL); sqlite3_exec(db, "CREATE INDEX IF NOT EXISTS categories_name_id ON categories(name);", NULL, NULL, NULL); sqlite3_exec(db, "CREATE INDEX IF NOT EXISTS ensortinges_id_idx ON ensortinges(id);", NULL, NULL, NULL); // etc... } } [self setSchemaVersion]; [self endTransaction]; } 

Permettez-moi de partager un code de migration avec FMDB et MBProgressHUD.

Voici comment lire et écrire le numéro de version du schéma (cela fait probablement partie d’une classe de modèle, dans mon cas, c’est une classe singleton appelée Database):

 - (int)databaseSchemaVersion { FMResultSet *resultSet = [[self database] executeQuery:@"PRAGMA user_version"]; int version = 0; if ([resultSet next]) { version = [resultSet intForColumnIndex:0]; } return version; } - (void)setDatabaseSchemaVersion:(int)version { // FMDB cannot execute this query because FMDB sortinges to use prepared statements sqlite3_exec([self database].sqliteHandle, [[NSSsortingng ssortingngWithFormat:@"PRAGMA user_version = %d", DatabaseSchemaVersionLatest] UTF8Ssortingng], NULL, NULL, NULL); } 

Voici la méthode [self database] qui ouvre la firebase database paresseusement:

 - (FMDatabase *)database { if (!_databaseOpen) { _databaseOpen = YES; NSSsortingng *documentsDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]; NSSsortingng *databaseName = [NSSsortingng ssortingngWithFormat:@"userdata.sqlite"]; _database = [[FMDatabase alloc] initWithPath:[documentsDir ssortingngByAppendingPathComponent:databaseName]]; _database.logsErrors = YES; if (![_database openWithFlags:SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FILEPROTECTION_COMPLETE]) { _database = nil; } else { NSLog(@"Database schema version is %d", [self databaseSchemaVersion]); } } return _database; } 

Et voici les méthodes de migration appelées depuis le contrôleur de vue:

 - (BOOL)databaseNeedsMigration { return [self databaseSchemaVersion] < databaseSchemaVersionLatest; } - (void)migrateDatabase { int version = [self databaseSchemaVersion]; if (version >= databaseSchemaVersionLatest) return; NSLog(@"Migrating database schema from version %d to version %d", version, databaseSchemaVersionLatest); // ...the actual migration code... if (version < 1) { [[self database] executeUpdate:@"CREATE TABLE foo (...)"]; } [self setDatabaseSchemaVersion:DatabaseSchemaVersionLatest]; NSLog(@"Database schema version after migration is %d", [self databaseSchemaVersion]); } 

Et voici le code du contrôleur de vue racine qui appelle la migration, en utilisant MBProgressHUD pour afficher un cadre de progression:

 - (void)viewDidAppear { [super viewDidAppear]; if ([[Database sharedDatabase] userDatabaseNeedsMigration]) { MBProgressHUD *hud = [[MBProgressHUD alloc] initWithView:self.view.window]; [self.view.window addSubview:hud]; hud.removeFromSuperViewOnHide = YES; hud.graceTime = 0.2; hud.minShowTime = 0.5; hud.labelText = @"Upgrading data"; hud.taskInProgress = YES; [[UIApplication sharedApplication] beginIgnoringInteractionEvents]; [hud showAnimated:YES whileExecutingBlock:^{ [[Database sharedDatabase] migrateUserDatabase]; } onQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0) completionBlock:^{ [[UIApplication sharedApplication] endIgnoringInteractionEvents]; }]; } } 

La meilleure solution IMO consiste à créer une structure de mise à niveau SQLite. J’ai eu le même problème (dans le monde C #) et j’ai construit mon propre framework. Vous pouvez lire à ce sujet ici . Cela fonctionne parfaitement et fait que mes mises à jour (autrefois cauchemardesques) fonctionnent avec un minimum d’effort de ma part.

Bien que la bibliothèque soit implémentée en C #, les idées présentées ici devraient également fonctionner dans votre cas.

Si vous modifiez le schéma de la firebase database et tout le code qui l’utilise de manière séquentielle, comme ce sera probablement le cas dans les applications intégrées et téléphoniques, le problème est bien maîsortingsé (rien de comparable au cauchemar de la migration de schéma sur une firebase database d’entreprise) cela peut servir des centaines d’applications – pas toutes sous le contrôle du DBA ;-).

Quelques conseils…

1) Je recommande de mettre tout le code pour migrer votre firebase database dans un NSOperation et de l’exécuter dans le thread d’arrière-plan. Vous pouvez afficher un UIAlertView personnalisé avec un spinner pendant la migration de la firebase database.

2) Assurez-vous de copier votre firebase database depuis le bundle dans les documents de l’application et de l’utiliser à partir de cet emplacement. Sinon, vous écraserez toute la firebase database avec chaque mise à jour d’application, puis migrez la nouvelle firebase database vide.

3) FMDB est génial, mais sa méthode executeQuery ne permet pas les requêtes PRAGMA pour une raison quelconque. Vous devrez écrire votre propre méthode qui utilise directement sqlite3 si vous souhaitez vérifier la version du schéma à l’aide de PRAGMA user_version.

4) Cette structure de code garantira que vos mises à jour sont exécutées dans l’ordre et que toutes les mises à jour sont exécutées, quel que soit le temps écoulé entre les mises à jour des applications. Il pourrait être remanié plus avant, mais c’est un moyen très simple de le regarder. Cette méthode peut être exécutée en toute sécurité chaque fois que votre singleton de données est instancié et ne coûte qu’une petite requête de firebase database qui ne se produit qu’une fois par session si vous configurez correctement votre singleton de données.

 - (void)upgradeDatabaseIfNeeded { if ([self databaseSchemaVersion] < 3) { if ([self databaseSchemaVersion] < 2) { if ([self databaseSchemaVersion] < 1) { // run statements to upgrade from 0 to 1 } // run statements to upgrade from 1 to 2 } // run statements to upgrade from 2 to 3 // and so on... // set this to the latest version number [self setDatabaseSchemaVersion:3]; } } 

1 . Créer un dossier /migrations avec la liste des migrations basées sur SQL, où chaque migration ressemble à ceci:

/migrations/001-categories.sql

 -- Up CREATE TABLE Category (id INTEGER PRIMARY KEY, name TEXT); INSERT INTO Category (id, name) VALUES (1, 'Test'); -- Down DROP TABLE User; 

/migrations/002-posts.sql

 -- Up CREATE TABLE Post (id INTEGER PRIMARY KEY, categoryId INTEGER, text TEXT); -- Down DROP TABLE Post; 

2 . Créez une table de firebase database contenant la liste des migrations appliquées, par exemple:

 CREATE TABLE Migration (name TEXT); 

3 . Mettez à jour la logique de démarrage de l’application pour qu’elle prenne en compte la liste des migrations du dossier /migrations avant de démarrer et exécute les migrations qui n’ont pas encore été appliquées.

Voici un exemple implémenté avec JavaScript: Client SQLite pour Node.js Apps