6

I have developed an android app in react-native and expo. I have also published the app on google play.

Now, I have made some modifications on my SQLite DB tables locally.

Suppose, before the schema of a table was like this:

CREATE TABLE expenditures (id integer primary key, max_amount REAL not null);

And now I would like to change it to this:

CREATE TABLE expenditures (id integer primary key, max_amount TEXT not null);

Is there any way to run a method after a new update/upgrade on a production app (google play store)? That way I can alter the tables only once after the upgrade, and other newly installed users won't be affected by this function. I found two methods on native android:

  1. onCreate: Called for the first time when creation of tables are needed.
  2. onUpgrade: This method is called when database version is upgraded.

But since I have developed my app with react-native and expo, I can't use the above methods. Although I have found onUpgrade in the expo code, I am not sure how to use this feature in expo.

Or is there any better way to handle database migrations on a published app in react-native and expo?

Kakar
  • 5,354
  • 10
  • 55
  • 93
  • 1
    You'd need to store a `version` in the DB to use it for version-specific migration. If you use [redux-persist](https://github.com/rt2zz/redux-persist), it has a convenient API for persisted state migration. – Roy Wang Dec 21 '18 at 16:15
  • @riwu Hi riwu, I am not using redux, I am using mobx. – Kakar Dec 21 '18 at 17:52
  • https://stackoverflow.com/questions/19793004/android-sqlite-database-why-drop-table-and-recreate-on-upgrade/19836980#19836980 – danny117 Dec 21 '18 at 22:30
  • 2
    You'll hurt your self. Going from real to text you'll loose all the operations that work on real like order by, math ops , such things. – danny117 Dec 21 '18 at 22:32

2 Answers2

2

I don't think you can really use the versioning stuff you linked to, as that will drop your db and recreate it from scratch, so you would lose your data.

A simple solution to this is to manually keep track of migrations you've already executed in a table. Then you can create this table if it doesn't exist yet (which can be done in a very dumb way by first trying to query it, and if that fails, create it). If you have a list of all known migrations in order, you can just drop items that already have an entry in the table and run the remaining ones.

From an old Cordova application I wrote this code (yeah it's really old, it's still using Require JS to define the module):

/**
 * Provide access to an SQL database, using the SQLite plugin for
 * Cordova devices so we aren't limited in how much data we can store,
 * and falling back to browser native support on desktop.
 *
 * Unfortunately webSQL is deprecated and slowly being phased out.
 */
define(['require', 'module', 'deviceReady!'], function(require, module, isCordova) {
    'use strict';

    var dbRootObject = isCordova ? window.sqlitePlugin : window,
    config = module.config();

    if (typeof dbRootObject.openDatabase == 'undefined') {
        window.alert('Your browser has no SQL support!  Please try a Webkit-based browser');
        return null;
    } else {
        var db = dbRootObject.openDatabase(config.dbName, '', 'Direct Result database', null),
        transaction = function(callback) {
            // We go through this trouble to automatically provide
            // error reporting and auto-rollback.
            var makeFacade = function(t) {
                return {
                    sql: function(sql, args, okCallback, errorCallback) {
                        var okFn, errFn;
                        if (okCallback) {
                            okFn = function(t, r) { return okCallback(makeFacade(t), r); };
                        } else {
                            okFn = null;
                        }
                        if (errorCallback) {
                            errFn = function(t, e) { console.log('SQL error: '+sql, e); return errorCallback(makeFacade(t), e); };
                        } else {
                            errFn = function(t, e) {
                                // It's important we throw an exn,
                                // else the txn won't be aborted!
                                window.alert(e.message + ' sql: '+sql);
                                throw(e.message + ' sql: '+sql);
                            };
                        }
                        return t.executeSql(sql, args, okFn, errFn);
                    }
                };
            };
            return db.transaction(function(t) {
                return callback(makeFacade(t));
            }, function(e) { console.log('error'); console.log(e); });
        },

        // We're going to have to create or own migrations, because
        // both the Cordova SQLite plugin and the Firefox WebSQL
        // extension don't implement versioning in their WebSQL API.
        migrate = function(version, upFn, done, txn) { // "Down" migrations are currently not supported
            var doIt = function(t) {
                t.sql('SELECT NOT EXISTS (SELECT version FROM sqldb_migrations WHERE version = ?) AS missing',
                      [version], function(t, r) {
                          if (r.rows.item(0).missing == '1') {
                              upFn(t, function() {
                                  t.sql('INSERT INTO sqldb_migrations (version)'+
                                        'VALUES (?)', [version], done);
                              });
                          } else {
                              done(t);
                          }
                      });
            };
            if (txn) doIt(txn);
            else transaction(doIt);
        },

        maybeRunMigrations = function(callback) {
            var migrations = [],
            addMigration = function(name, migration) {
                migrations.push([name, migration]);
            },
            runMigrations = function(t) {
                if (migrations.length === 0) {
                    callback(t);
                } else {
                    var m = migrations.shift(),
                    name = m[0],
                    migration = m[1];
                    migrate(name, migration, runMigrations, t);
                }
            };

            // ADD MIGRATIONS HERE. The idea is you can just add migrations
            // in a queue and they'll be run in sequence.

            // Here are two example migrations
            addMigration('1', function (t, done) {
                t.sql('CREATE TABLE people ('+
                      '  id integer PRIMARY KEY NOT NULL, '+
                      '  initials text NOT NULL, '+
                      '  first_name text NOT NULL, '+
                      '  family_name text NOT NULL, '+
                      '  email text NOT NULL, ', [], done);
            });
            addMigration('2', function(t, done) {
                t.sql('ALTER TABLE people ADD COLUMN phone_number text', [], done);
            });

            transaction(function(t) {
                t.sql('CREATE TABLE IF NOT EXISTS sqldb_migrations ('+
                      '  version int UNIQUE, '+
                      '  timestamp_applied text NOT NULL DEFAULT CURRENT_TIMESTAMP '+
                      ')', [], function (t, r) { runMigrations(t, migrations); });
            });
        };

        // Expose "migrate" just in case
        return {transaction: transaction, migrate: migrate, maybeRunMigrations: maybeRunMigrations};
    }
});

You'll also need to take a lot of care, as I found out the hard way you cannot actually alter or even drop columns with SQLite (or at least not with the Cordova plugin at the time I wrote this code)! So also be very careful with constraints or you'll end up painting yourself into a corner.

I have not tried it, but it might be possible if you rename the old table, create the new one again with the changed columns and then copy over the data.

sjamaan
  • 2,282
  • 10
  • 19
0

you can put the sql alteration files in assets folder android/app/src/main/assets/ like

<version>.sql -> 1.sql or 2.sql

and these file can contain the migration query like

alter table NOTE add NAME TEXT;

and trigger these query according to version of app in onUpgrade() method

Rahul
  • 1,380
  • 1
  • 10
  • 24