Utrzymuję aplikację, która musi okresowo aktualizować bazę danych sqlite i migrować stare bazy danych do nowego schematu, a oto co robię:
Do śledzenia wersji bazy danych używam wbudowanej zmiennej wersji użytkownika, którą zapewnia sqlite (sqlite nic nie robi z tą zmienną, możesz jej używać w dowolny sposób). Zaczyna się od 0 i możesz pobrać / ustawić tę zmienną za pomocą następujących instrukcji sqlite:
> PRAGMA user_version;
> PRAGMA user_version = 1;
Po uruchomieniu aplikacji sprawdzam bieżącą wersję użytkownika, wprowadzam wszelkie zmiany potrzebne do zaktualizowania schematu, a następnie aktualizuję wersję użytkownika. Zawijam aktualizacje w transakcję, więc jeśli coś pójdzie nie tak, zmiany nie zostaną zatwierdzone.
Aby dokonać zmian schematu, sqlite obsługuje składnię "ALTER TABLE" dla niektórych operacji (zmiana nazwy tabeli lub dodanie kolumny). Jest to łatwy sposób aktualizowania istniejących tabel w miejscu. Zobacz dokumentację tutaj: http://www.sqlite.org/lang_altertable.html . Aby usunąć kolumny lub inne zmiany, które nie są obsługiwane przez składnię „ALTER TABLE”, tworzę nową tabelę, przenoszę do niej datę, usuwam starą tabelę i zmieniam nazwę nowej tabeli na oryginalną.
application_id
jest dodatkowym bitem do identyfikowania formatu plikufile
na przykład według narzędzia, a nie dla wersji bazy danych.Odpowiedź z Just Curious jest ostateczna (zrozumiałeś!) I to jest to, czego używamy do śledzenia wersji schematu bazy danych, która jest obecnie w aplikacji.
Aby przejść przez migracje, które muszą wystąpić, aby uzyskać user_version pasującą do oczekiwanej wersji schematu aplikacji, używamy instrukcji switch. Oto przykładowy przykład tego, jak to wygląda w naszym pasku aplikacji :
- (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]; }
źródło
toVersion
w swoim kodzie? Jak to jest obsługiwane, gdy jesteś w wersji 0, a po niej są jeszcze dwie wersje. Oznacza to, że musisz przeprowadzić migrację z 0 do 1 iz 1 do 2. Jak sobie z tym radzisz?break
instrukcji w plikuswitch
, więc wszystkie kolejne migracje również będą miały miejsce.Pozwólcie, że podzielę się kodem migracji z FMDB i MBProgressHUD.
Oto jak odczytujesz i zapisujesz numer wersji schematu (prawdopodobnie jest to część klasy modelu, w moim przypadku jest to klasa pojedyncza o nazwie 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 tries to use prepared statements sqlite3_exec([self database].sqliteHandle, [[NSString stringWithFormat:@"PRAGMA user_version = %d", DatabaseSchemaVersionLatest] UTF8String], NULL, NULL, NULL); }
Oto
[self database]
metoda, która leniwie otwiera bazę danych:A oto metody migracji wywoływane z kontrolera widoku:
- (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]); }
A oto kod kontrolera widoku głównego, który wywołuje migrację, używając MBProgressHUD do wyświetlania ramki postępu:
źródło
schema_version
pragma zwykle nie jest czymś, z czym ludzie mają do czynienia.Najlepszym rozwiązaniem IMO jest zbudowanie struktury aktualizacji SQLite. Miałem ten sam problem (w świecie C #) i zbudowałem własny taki framework. Możesz o tym przeczytać tutaj . Działa doskonale i sprawia, że moje (wcześniej koszmarne) aktualizacje działają przy minimalnym wysiłku z mojej strony.
Chociaż biblioteka została zaimplementowana w C #, przedstawione tam pomysły powinny działać dobrze również w Twoim przypadku.
źródło
1
. Utwórz/migrations
folder z listą migracji opartych na SQL, gdzie każda migracja wygląda mniej więcej tak:/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
. Utwórz tabelę bazy danych zawierającą listę zastosowanych migracji, na przykład:CREATE TABLE Migration (name TEXT);
3
. Zaktualizuj logikę ładowania początkowego aplikacji, aby przed jej uruchomieniem pobierała listę migracji z/migrations
folderu i uruchamiała migracje, które nie zostały jeszcze zastosowane.Oto przykład zaimplementowany w JavaScript: Klient SQLite dla aplikacji Node.js.
źródło
Kilka porad...
1) Zalecam umieszczenie całego kodu do migracji bazy danych do operacji NSO i uruchomienie jej w wątku w tle. Podczas migracji bazy danych można wyświetlić niestandardowy widok UIAlertView z pokrętłem.
2) Upewnij się, że kopiujesz bazę danych z pakietu do dokumentów aplikacji i używasz jej z tej lokalizacji, w przeciwnym razie po prostu nadpisujesz całą bazę danych przy każdej aktualizacji aplikacji, a następnie migrujesz nową, pustą bazę danych.
3) FMDB jest świetny, ale jego metoda executeQuery z jakiegoś powodu nie może wykonywać zapytań PRAGMA. Będziesz musiał napisać własną metodę, która bezpośrednio korzysta z sqlite3, jeśli chcesz sprawdzić wersję schematu za pomocą PRAGMA user_version.
4) Ta struktura kodu zapewni, że aktualizacje są wykonywane w kolejności i że wszystkie aktualizacje są wykonywane, bez względu na to, jak długo użytkownik przechodzi między aktualizacjami aplikacji. Można go dalej refaktoryzować, ale jest to bardzo prosty sposób spojrzenia na to. Ta metoda może być bezpiecznie uruchamiana za każdym razem, gdy tworzony jest pojedynczy element danych, i kosztuje tylko jedno małe zapytanie bazy danych, które występuje tylko raz na sesję, jeśli poprawnie skonfigurujesz singleton danych.
- (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]; } }
źródło
Jeśli zmienisz schemat bazy danych i cały kod, który używa go w trybie lockstep, jak może się zdarzyć w przypadku aplikacji osadzonych i zlokalizowanych na telefonie, problem jest właściwie pod kontrolą (nic nie jest porównywalne z koszmarem, jakim jest migracja schematu w korporacyjnej bazie danych które mogą obsługiwać setki aplikacji - także nie wszystkie pod kontrolą administratora danych ;-).
źródło
W przypadku .net możesz użyć lib:
EntityFrameworkCore.Sqlite.Migrations
Jest to proste, więc na każdej innej platformie możesz łatwo zaimplementować to samo zachowanie, co w lib.
źródło