Detailed example of IOS database upgrade data migration

Detailed example of IOS database upgrade data migration

Detailed example of IOS database upgrade data migration

summary:

A long time ago, I encountered a reference scenario of database version upgrade. At that time, the approach was to simply delete the old database files and rebuild the database and table structure. This violent upgrade method would lead to the loss of old data. Now it seems that this is not an elegant solution. Now a new project uses the database again, and I have to reconsider this issue. I hope to solve this problem in a more elegant way. We will encounter similar scenarios in the future, and we all want to do better, don’t we?

The ideal situation is: when the database is upgraded, the table structure, primary key and constraints change. After the new table structure is established, data will be automatically retrieved from the old table, and the same fields will be mapped and migrated. In most business scenarios, database version upgrades only involve adding or removing fields and modifying primary key constraints. Therefore, the solution to be implemented below is also to implement it based on the most basic and most commonly used business scenarios. As for more complex scenarios, you can expand on this basis to meet your expectations.

Selection and finalization

After searching online, I found no simple and complete solution for database upgrade and data migration. I found some ideas.

1. Clear old data and rebuild the table

Pros: Simple Cons: Data loss

2. Modify the table structure based on the existing table

Advantages: Ability to retain data Disadvantages: The rules are relatively complicated. You need to create a database field configuration file, then read the configuration file, execute SQL to modify the table structure, constraints, primary keys, etc. It becomes cumbersome and troublesome to upgrade the database across multiple versions.

3. Create a temporary table, copy the old data to the temporary table, then delete the old data table and set the temporary table as the data table.

Advantages: Ability to retain data, support modification of table structure, changes to constraints and primary keys, relatively simple to implement Disadvantages: requires many steps to implement

Taking all factors into consideration, the third method is a more reliable solution.

Main steps

Based on this idea, the main steps of database upgrade are analyzed as follows:

  • Get the old table in the database
  • Modify the table name, add the suffix "_bak", and use the old table as a backup table
  • Create a new table
  • Get the newly created table
  • Traverse the old table and the new table, compare and extract the fields of the table that needs to be migrated
  • Data migration processing
  • Delete the backup table

Analysis of SQL statements used

These operations are all related to database operations, so the key to the problem is the SQL statements of the corresponding steps. The following is an analysis of the main SQL statements used:

Get the old table in the database

SELECT * from sqlite_master WHERE type='table'

The result is as follows. You can see that there are database fields such as type | name | tbl_name | rootpage | sql. We only need to use the name field, which is the database name.

sqlite> SELECT * from sqlite_master WHERE type='table'
 ...> ;
+-------+---------------+---------------+----------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| type | name | tbl_name | rootpage | sql |
+-------+---------------+---------------+----------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| table | t_message_bak | t_message_bak | 2 | CREATE TABLE "t_message_bak" (messageID TEXT, messageType INTEGER, messageJsonContent TEXT, retriveTimeString INTEGER, postTimeString INTEGER, readState INTEGER, PRIMARY KEY(messageID)) |
| table | t_message | t_message | 4 | CREATE TABLE t_message (
 messageID TEXT, 
 messageType INTEGER,
 messageJsonContent TEXT, 
 retriveTimeString INTEGER, 
 postTimeString INTEGER, 
 readState INTEGER, 
 addColumn INTEGER,
 PRIMARY KEY(messageID)
) |
+-------+---------------+---------------+----------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
2 rows in data set (0.03 seconds)

Modify the table name, add the suffix "_bak", and use the old table as a backup table

-- Change the t_message table to the t_message_bak table ALTER TABLE t_message RENAME TO t_message_bak

Get table field information

-- Get the field information of the t_message_bak table PRAGMA table_info('t_message_bak')

The obtained table field information is as follows. You can see | cid | name | type | notnull | dflt_value | pk | These database fields, we only need to use the name field, which is the field name.

sqlite> PRAGMA table_info('t_message_bak');
+------+--------------------+---------+---------+------------+------+
| cid | name | type | notnull | dflt_value | pk |
+------+--------------------+---------+---------+------------+------+
| 0 | messageID | TEXT | 0 | NULL | 1 |
| 1 | messageType | INTEGER | 0 | NULL | 0 |
| 2 | messageJsonContent | TEXT | 0 | NULL | 0 |
| 3 | retriveTimeString | INTEGER | 0 | NULL | 0 |
| 4 | postTimeString | INTEGER | 0 | NULL | 0 |
| 5 | readState | INTEGER | 0 | NULL | 0 |
+------+--------------------+---------+---------+------------+------+
6 rows in data set (0.01 seconds)

Using subqueries for data migration

INSERT INTO t_message(messageID, messageType, messageJsonContent, retriveTimeString,
 postTimeString, readState) SELECT messageID, messageType, messageJsonContent, retriveTimeString,
 postTimeString, readState FROM t_message_bak

Copy the values ​​of the fields messageID, messageType, messageJsonContent, retriveTimeString, postTimeString, and readState in the t_message_bak table to the t_message table

Code Implementation

Next comes the code implementation step.

// Create a new temporary table, import the data into the temporary table, and then replace the original table with the temporary table - (void)baseDBVersionControl {
 NSString * version_old = ValueOrEmpty(MMUserDefault.dbVersion);
 NSString * version_new = [NSString stringWithFormat:@"%@", DB_Version];
 NSLog(@"dbVersionControl before: %@ after: %@",version_old,version_new);

 // Database version upgrade if (version_old != nil && ![version_new isEqualToString:version_old]) {

  // Get the old tables in the database NSArray* existsTables = [self sqliteExistsTables];
  NSMutableArray* tmpExistsTables = [NSMutableArray array];

  // Modify the table name, add the suffix "_bak", and use the old table as a backup table for (NSString* tablename in existsTables) {
   [tmpExistsTables addObject:[NSString stringWithFormat:@"%@_bak", tablename]];
   [self.databaseQueue inDatabase:^(FMDatabase *db) {
    NSString* sql = [NSString stringWithFormat:@"ALTER TABLE %@ RENAME TO %@_bak", tablename, tablename];
    [db executeUpdate:sql];
   }];
  }
  existsTables = tmpExistsTables;

  // Create a new table [self initTables];

  // Get the newly created table NSArray* newAddedTables = [self sqliteNewAddedTables];

  // Traverse the old table and the new table, compare and extract the fields of the table that needs to be migrated NSDictionary* migrationInfos = [self generateMigrationInfosWithOldTables:existsTables newTables:newAddedTables];

  // Data migration processing [migrationInfos enumerateKeysAndObjectsUsingBlock:^(NSString* newTableName, NSArray* publicColumns, BOOL * _Nonnull stop) {
   NSMutableString* colunmsString = [NSMutableString new];
   for (int i = 0; i<publicColumns.count; i++) {
    [colunmsString appendString:publicColumns[i]];
    if (i != publicColumns.count-1) {
     [colunmsString appendString:@", "];
    }
   }
   NSMutableString* sql = [NSMutableString new];
   [sql appendString:@"INSERT INTO "];
   [sql appendString:newTableName];
   [sql appendString:@"("];
   [sql appendString:colunmsString];
   [sql appendString:@")"];
   [sql appendString:@" SELECT "];
   [sql appendString:colunmsString];
   [sql appendString:@" FROM "];
   [sql appendFormat:@"%@_bak", newTableName];

   [self.databaseQueue inDatabase:^(FMDatabase *db) {
    [db executeUpdate:sql];
   }];
  }];

  // Delete the backup table [self.databaseQueue inDatabase:^(FMDatabase *db) {
   [db beginTransaction];
   for (NSString* oldTableName in existsTables) {
    NSString* sql = [NSString stringWithFormat:@"DROP TABLE IF EXISTS %@", oldTableName];
    [db executeUpdate:sql];
   }
   [db commit];
  }];

  MMUserDefault.dbVersion = version_new;

 } else {
  MMUserDefault.dbVersion = version_new;
 }
}

- (NSDictionary*)generateMigrationInfosWithOldTables:(NSArray*)oldTables newTables:(NSArray*)newTables {
 NSMutableDictionary<NSString*, NSArray* >* migrationInfos = [NSMutableDictionary dictionary];
 for (NSString* newTableName in newTables) {
  NSString* oldTableName = [NSString stringWithFormat:@"%@_bak", newTableName];
  if ([oldTables containsObject:oldTableName]) {
   // Get table database field information NSArray* oldTableColumns = [self sqliteTableColumnsWithTableName:oldTableName];
   NSArray* newTableColumns = [self sqliteTableColumnsWithTableName:newTableName];
   NSArray* publicColumns = [self publicColumnsWithOldTableColumns:oldTableColumns newTableColumns:newTableColumns];

   if (publicColumns.count > 0) {
    [migrationInfos setObject:publicColumns forKey:newTableName];
   }
  }
 }
 return migrationInfos;
}

- (NSArray*)publicColumnsWithOldTableColumns:(NSArray*)oldTableColumns newTableColumns:(NSArray*)newTableColumns {
 NSMutableArray* publicColumns = [NSMutableArray array];
 for (NSString* oldTableColumn in oldTableColumns) {
  if ([newTableColumns containsObject:oldTableColumn]) {
   [publicColumns addObject:oldTableColumn];
  }
 }
 return publicColumns;
}

- (NSArray*)sqliteTableColumnsWithTableName:(NSString*)tableName {
 __block NSMutableArray<NSString*>* tableColumes = [NSMutableArray array];
 [self.databaseQueue inDatabase:^(FMDatabase *db) {
  NSString* sql = [NSString stringWithFormat:@"PRAGMA table_info('%@')", tableName];
  FMResultSet *rs = [db executeQuery:sql];
  while ([rs next]) {
   NSString* columnName = [rs stringForColumn:@"name"];
   [tableColumes addObject:columnName];
  }
 }];
 return tableColumes;
}

- (NSArray*)sqliteExistsTables {
 __block NSMutableArray<NSString*>* existsTables = [NSMutableArray array];
 [self.databaseQueue inDatabase:^(FMDatabase *db) {
  NSString* sql = @"SELECT * from sqlite_master WHERE type='table'";
  FMResultSet *rs = [db executeQuery:sql];
  while ([rs next]) {
   NSString* tablename = [rs stringForColumn:@"name"];
   [existsTables addObject:tablename];
  }
 }];
 return existsTables;
}

- (NSArray*)sqliteNewAddedTables {
 __block NSMutableArray<NSString*>* newAddedTables = [NSMutableArray array];
 [self.databaseQueue inDatabase:^(FMDatabase *db) {
  NSString* sql = @"SELECT * from sqlite_master WHERE type='table' AND name NOT LIKE '%_bak'";
  FMResultSet *rs = [db executeQuery:sql];
  while ([rs next]) {
   NSString* tablename = [rs stringForColumn:@"name"];
   [newAddedTables addObject:tablename];
  }
 }];
 return newAddedTables;
}

question

The problem of sqlite deleting table files without changing their size

If you have any questions, please leave a message or come to the community to discuss. Thank you for reading and I hope it can help you. Thank you for your support of this site!

You may also be interested in:
  • Detailed explanation of using HTTP library Axios method in VUE
  • Detailed explanation of the underlying notification framework library example of iOS system
  • A brief discussion on the powerful and easy-to-use iOS routing library FFRouter that supports URL Rewrite
  • iOS development notes: keyboard, static library, animation and crash positioning
  • Detailed usage examples based on iOS Realm database
  • How to elegantly debug the database in iOS development
  • Detailed explanation of the creation of .a and .framework static libraries and the use of .bundle resource packages in iOS
  • IOS UIImagePickerController gets pictures from camera, gallery, and album
  • Examples of adding, deleting, modifying and checking FMDB database in iOS
  • The difference between iOS dynamic library and static library

<<:  Customization Method of Linux Peripheral File System

>>:  Example of how to implement value transfer between WeChat mini program pages

Recommend

Echarts Basic Introduction: General Configuration of Bar Chart and Line Chart

1Basic steps of echarts Four Steps 1 Find the DOM...

How to completely uninstall mysql under CentOS

This article records the complete uninstallation ...

Native JS to implement hover drop-down menu

JS implements a hover drop-down menu. This is a s...

Bootstrap 3.0 study notes grid system case

Preface In the previous article, we mainly learne...

Linux uses join -a1 to merge two files

To merge the following two files, merge them toge...

What are your principles for designing indexes? How to avoid index failure?

Table of contents Primary key index Create indexe...

How to enable slow query log in MySQL

1.1 Introduction By enabling the slow query log, ...

A record of the pitfalls of the WeChat applet component life cycle

The component lifecycle is usually where our busi...

MySQL 8.0.15 installation and configuration tutorial under Win10

What I have been learning recently involves knowl...

The easiest way to reset mysql root password

My mysql version is MYSQL V5.7.9, please use the ...

Explanation of the working mechanism of namenode and secondarynamenode in Hadoop

1) Process 2) FSImage and Edits Nodenode is the b...

ReactJs Basics Tutorial - Essential Edition

Table of contents 1. Introduction to ReactJS 2. U...

MySQL 8.0.15 installation tutorial for Windows 64-bit

First go to the official website to download and ...

Introduction to Vue life cycle and detailed explanation of hook functions

Table of contents Vue life cycle introduction and...