回答:
定期的にsqliteデータベースを更新し、古いデータベースを新しいスキーマに移行する必要があるアプリケーションを維持しています。
データベースのバージョンを追跡するために、私はsqliteが提供する組み込みのユーザーバージョン変数を使用します(sqliteはこの変数を使用せず、自由に使用できます)。これは0から始まり、次のsqliteステートメントでこの変数を取得/設定できます。
> PRAGMA user_version;
> PRAGMA user_version = 1;
アプリが起動したら、現在のユーザーバージョンを確認し、スキーマを最新の状態にするために必要な変更を適用してから、ユーザーバージョンを更新します。更新がトランザクションにラップされるので、何か問題が発生しても、変更はコミットされません。
スキーマの変更を行うために、sqliteは特定の操作(テーブルの名前変更または列の追加)の「ALTER TABLE」構文をサポートしています。これは、既存のテーブルをインプレースで更新する簡単な方法です。こちらのドキュメントを参照してください:http : //www.sqlite.org/lang_altertable.html。「ALTER TABLE」構文でサポートされていない列やその他の変更を削除するには、新しいテーブルを作成し、日付をそのテーブルに移行し、古いテーブルを削除して、新しいテーブルの名前を元の名前に変更します。
Just Curiousからの答えは完全に正解です(あなたは私のポイントを手に入れました!)。それは、現在アプリにあるデータベーススキーマのバージョンを追跡するために使用するものです。
アプリの予期されるスキーマバージョンと一致するuser_versionを取得するために必要な移行を実行するには、switchステートメントを使用します。これが私たちのアプリストリップでこれがどのように見えるかのカットアップ例です:
- (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 entries 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 entries_id_idx ON entries(id);", NULL, NULL, NULL);
// etc...
}
}
[self setSchemaVersion];
[self endTransaction];
}
toVersion
あなたがあなたのコードでどこを使うのか見ませんでしたか?バージョン0を使用していて、その後にさらに2つのバージョンがある場合の処理方法。つまり、0から1および1から2に移行する必要があります。これをどのように処理しますか?
break
ステートメントがないswitch
ため、後続のすべての移行も行われます。
いくつかの移行コードをFMDBおよびMBProgressHUDと共有させてください。
スキーマのバージョン番号を読み書きする方法は次のとおりです(これはおそらくモデルクラスの一部です。私の場合は、データベースと呼ばれるシングルトンクラスです)。
- (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 tries to use prepared statements
sqlite3_exec([self database].sqliteHandle, [[NSString stringWithFormat:@"PRAGMA user_version = %d", DatabaseSchemaVersionLatest] UTF8String], NULL, NULL, NULL);
}
ここでの[self database]
遅延したデータベースを開く方法:
- (FMDatabase *)database {
if (!_databaseOpen) {
_databaseOpen = YES;
NSString *documentsDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *databaseName = [NSString stringWithFormat:@"userdata.sqlite"];
_database = [[FMDatabase alloc] initWithPath:[documentsDir stringByAppendingPathComponent: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;
}
そして、View Controllerから呼び出される移行メソッドは次のとおりです:
- (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]);
}
次に、MBProgressHUDを使用してプログレスベゼルを表示する、移行を呼び出すルートビューコントローラーコードを示します。
- (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];
}];
}
}
schema_version
プラグマは通常、人々が扱うものではありません。
1
。/migrations
SQLベースの移行のリストを含むフォルダーを作成します。各移行は次のようになります。
/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
。適用された移行のリストを含むdbテーブルを作成します。次に例を示します。
CREATE TABLE Migration (name TEXT);
3
。アプリケーションのブートストラップロジックを更新して、開始前に/migrations
フォルダーから移行のリストを取得し、まだ適用されていない移行を実行するようにします。
JavaScriptで実装した例を次に示します。Node.jsアプリ用のSQLiteクライアント
いくつかのヒント...
1)データベースをNSOperationに移行するためのすべてのコードを配置し、バックグラウンドスレッドで実行することをお勧めします。データベースの移行中に、スピナーを使用してカスタムUIAlertViewを表示できます。
2)バンドルからアプリのドキュメントにデータベースをコピーし、その場所からそれを使用していることを確認してください。それ以外の場合は、アプリの更新ごとにデータベース全体を上書きし、新しい空のデータベースを移行します。
3)FMDBは素晴らしいですが、そのexecuteQueryメソッドは何らかの理由でPRAGMAクエリを実行できません。PRAGMA user_versionを使用してスキーマバージョンを確認する場合は、sqlite3を直接使用する独自のメソッドを作成する必要があります。
4)このコード構造により、ユーザーがアプリの更新をどの程度行っても、更新が順番に実行され、すべての更新が実行されます。さらにリファクタリングすることもできますが、これは非常に簡単な方法です。このメソッドは、データシングルトンがインスタンス化されるたびに安全に実行でき、データシングルトンを適切に設定した場合、セッションごとに1回だけ発生する小さなdbクエリが1つだけかかります。
- (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];
}
}
データベーススキーマと、それをロックステップで使用しているすべてのコードを変更すると、組み込みアプリや電話に配置されたアプリの場合のように、問題は実際にはうまく制御されます(エンタープライズDBでのスキーマの移行という悪夢に匹敵するものはありません)それは何百ものアプリにサービスを提供しているかもしれません-DBAの制御下にあるすべてではありません;-)。
.netの場合、libを使用できます。
EntityFrameworkCore.Sqlite.Migrations
それは単純なので、他のどのプラットフォームでも、libと同じ動作を簡単に実装できます。