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

MySQL learning notes: data engine

View the engines supported by the current databas...

How to use Docker-compose to deploy Django applications offline

Table of contents Install Docker-ce for the devel...

js array entries() Get iteration method

Table of contents 1. Detailed syntax of entires()...

jQuery implements accordion small case

This article shares the specific code of jQuery t...

WeChat applet calculator example

WeChat applet calculator example, for your refere...

MySQL implements an example method of logging in without a password

Specific method: Step 1: Stop the mysql service /...

Summary of MySQL lock related knowledge

Locks in MySQL Locks are a means to resolve resou...

How to shrink the log file in MYSQL SERVER

The transaction log records the operations on the...

JavaScript to implement a simple clock

This article example shares the specific code for...

Mybatis mysql delete in operation can only delete the first data method

Bugs As shown in the figure, I started to copy th...

How to safely shut down MySQL

When shutting down the MySQL server, various prob...

CentOS7 deployment Flask (Apache, mod_wsgi, Python36, venv)

1. Install Apache # yum install -y httpd httpd-de...

Implementing a distributed lock using MySQL

introduce In a distributed system, distributed lo...

How to quickly query 10 million records in Mysql

Table of contents Normal paging query How to opti...