Проблема: мне нужно обновить пользователей до моей новой схемы базы данных в React Native.

Когда вы работаете с SQLite в React Native, вы неизбежно будете вносить изменения в структуру базы данных, которую вы сделали изначально. Вы добавили/удалили таблицы или поля или обновили типы данных полей. Теперь вопрос заключается в том, что вы можете сделать, чтобы обновить существующих пользователей вашего приложения до новой схемы, не уничтожая их данные из базы данных.

Прежде чем мы настроим все, важно понять некоторые общие вещи:

  1. Мы не должны предоставлять новую базу данных или обновленную базу данных, так как это приведет к тому, что существующая база данных пользователей будет удалена вместе со всеми ее данными.
  2. Мы должны подключиться к существующей базе данных пользователя и применить к ней сценарии обновления.
  3. Приложение должно понимать, что пользователь, возможно, не обновлял приложение какое-то время и мог иметь несколько версий схемы. Это означает, что ваш код обновления базы данных должен точно знать, какие сценарии обновления относятся к версии базы данных пользователя, и применять только их.
  4. Приложение должно понимать, что некоторые обновления базы данных предполагают наличие последнего обновления. Это означает, что ваши сценарии обновления должны применяться в порядке от самого старого к самому последнему.

Имея в виду эти предметы, давайте начнем!

Решение

Примечание. В этих примерах я использую react-native-sqlite-storage, но не имеет значения, что вы используете. Понятия все те же.

Кроме того, я буду писать исходя из предположения, что у вас уже есть база данных, настроенная в вашем проекте, и вы ищете способ ее обновить. В таком случае я придерживаюсь чего-то более похожего на псевдокод, который вы должны модифицировать для своих целей. Я думаю, что общая логика/идея здесь — это то, что я хочу донести больше всего остального.

Предположим, у нас есть локальная база данных версии = 1, и мы хотим обновить ее с помощью набора скриптов до последней версии — версии 5.

Обзор логики

Наша общая логика ниже:

  1. Загрузите файл конфигурации обновления базы данных
  2. Откройте существующую базу данных
  3. Получить текущую версию базы данных
  4. Получите целевую версию обновления конфигурации
  5. Если версия ниже, чем целевая версия обновления вашего приложения, запустите сценарии обновления с версии сразу после старой версии базы данных до целевой версии обновления.

Файл конфигурации

Этот файл будет отвечать за хранение версии базы данных, до которой вы хотите обновить свое приложение. Он также будет содержать сценарии обновления для каждой версии приложения. Вот пример:

{ 
  "version": 5,
  "upgrades": {
    "to_v2": [
      ["CREATE TABLE [IF NOT EXISTS] table_name1 column_1 data_type PRIMARY KEY, column_2 data_type NOT NULL, column_3 data_type DEFAULT 0;"]],
    "to_v3": [["CREATE TABLE [IF NOT EXISTS] table_name2 column_1 data_type PRIMARY KEY, column_2 data_type NOT NULL, column_3 data_type DEFAULT 0;"]],
    "to_v4": [["CREATE TABLE [IF NOT EXISTS] table_name3 column_1 data_type PRIMARY KEY, column_2 data_type NOT NULL, column_3 data_type DEFAULT 0;"]],
    "to_v5": [["CREATE TABLE [IF NOT EXISTS] table_name4 column_1 data_type PRIMARY KEY, column_2 data_type NOT NULL, column_3 data_type DEFAULT 0;"]
    ]
  }
}

Что делает файл конфигурации?

В приведенном выше примере показано, что ваша новая предполагаемая версия базы данных — это версия 5, и логика здесь будет заключаться в том, что сценарии в поле обновлений будут запускаться от версии базы данных приложения до сценария, который обновляет вашу базу данных до версии 5.

Структура папок

Предположим, мы будем работать с файлом с именем database.js, расположенным в /app/db. Давайте поместим файл конфигурации, который мы создали ранее, также в /app/db и назовем его db-upgrade.json, поэтому мы просто будем работать в той же папке.

Загрузите файл конфигурации

В database.js импортируйте файл json:

import dbUpgrade from './db-upgrade.json';

Откройте существующую базу данных

Мы предполагаем, конечно, что база данных уже создана в вашем собственном проекте, и вы хотите ее обновить. Как я уже сказал, я буду использовать react-native-sqlite-storage, но подойдет любая библиотека, позволяющая использовать sqlite.

import dbUpgrade from './db-upgrade.json';
import SQLite from 'react-native-sqlite-storage';
export const open = () => {
  const db;
  SQLite.openDatabase(
    {
      name: 'my-existing-data.db',
      createFromLocation: '~data/my-existing-data.db'
    })
    .then(instance => db = instance)
    .catch(error => console.error(error));
}

Получить текущую версию базы данных

Давайте добавим запрос, который даст нам версию базы данных:

import dbUpgrade from './db-upgrade.json';
import SQLite from 'react-native-sqlite-storage';
export const open = () => {
  const db;
  SQLite.openDatabase(
    {
      name: 'my-existing-data.db',
      createFromLocation: '~data/my-existing-data.db'
    })
    .then(instance => {
      db = instance;
      db.executeSql('SELECT max(version) FROM version')
      .then(results => {
        let version = results[0];
        if (version < dbUpgrade.version) {
          //Call upgrade scripts 
        }
    })
    .catch(error => console.error(error)); })
    .catch(error => console.error(error)); })

Поскольку для этого примера мы предположили, что локальная БД имеет версию 1, запрос SELECT max(version) FROM version в этом случае даст нам значение 1

Функция обновления

Давайте создадим функцию, которая будет отвечать за логику запуска наших скриптов SQLite, на основе предыдущей версии базы данных:

export const upgradeFrom = (db, previousVersion) => {
  let statements = [];
  let version = dbUpgrade.version - (dbUpgrade.version - previousVersion) + 1;
  let length = Object.keys(dbUpgrade.upgrades).length;
  for (let i = 0; i < length; i += 1) {
    let upgrade = dbUpgrade.upgrades[`to_v${version}`];
    if (upgrade) {
      statements = [...statements, ...upgrade];
    } else {
      break;
    }
    version++;
  }
  statements = [...statements, ...[['REPLACE into version (version)       VALUES (?);', [dbUpgrade.version]]]];
  return db.sqlBatch(statements)
    .then(() => console.log('Success!'))
    .catch(error => console.log('Error:', error));
}

Логика функции обновления

Давайте разберем логику функции обновления. Первым делом определяемся с версией

let version = dbUpgrade.version - (dbUpgrade.version - previousVersion) + 1;

Мы получаем версию, хранящуюся в нашем файле db-upgrade.json ( dbUpgrade.version), и вычитаем из нее previousVersion. previousVersion — это версия, которую мы получили из нашей локальной базы данных (устаревшая база данных, которую вы хотите обновить):

(dbUpgrade.version - previousVersion)

Это даст нам 4, так как db-upgrade.json установлено в 5, а наша локальная база данных возвращает 1. Это число говорит нам, насколько сильно отстает база данных. Отстает на 4 версии. Прежде чем мы сможем начать запускать сценарии, нам нужно знать, с чего мы начинаем. Мы не хотим запускать сценарии, которые база данных уже запускала. Отсюда следующая часть выражения:

dbUpgrade.version - (dbUpgrade.version - previousVersion)

Здесь мы снова используем версию db-upgrade.json (5), чтобы установить, где мы хотим закончить, и мы вычитаем 4, чтобы установить, где находится версия нашей локальной базы данных (1). Если бы мы запустили сценарии сейчас, у нас возникла бы проблема, потому что, как я уже сказал, мы не хотим запускать сценарии, которые уже есть в нашей текущей базе данных. Мы должны начать работать не с версии 1, а с версии 2. Поэтому мы:

dbUpgrade.version - (dbUpgrade.version - previousVersion) + 1

Как видите, мы добавили 1 в конце. Теперь начнем с правильного скрипта 🙂

Наш следующий блок кодов запускает наши скрипты. В основном мы перебираем каждый сценарий и используем значение версии для доступа к нужному сценарию, мы сохраняем сценарий в переменной операторов и увеличиваем номер версии, чтобы на следующей итерации мы могли получить следующий сценарий. Как только мы видим, что у нас больше нет скриптов для обновления, мы разрываем цикл.

...
let length = Object.keys(dbUpgrade.upgrades).length;
for (let i = 0; i < length; i += 1) {
  let upgrade = dbUpgrade.upgrades[`to_v${version}`];
  if (upgrade) {
    statements = [...statements, ...upgrade];
  } else {
    break; //we found no more scripts, break
  }
  version++; //increment version from 2, to the end (5)
}

Затем мы добавляем сценарий обновления версии базы данных, чтобы убедиться, что наши новые сценарии установят версию локальной базы данных на 5. И в то же время мы добавляем сценарии, которые мы извлекли из db-upgrade.json, используя оператор распространения ...statements:

statements = [...statements, ...[['REPLACE into version (version) VALUES (?);', [dbUpgrade.version]]]];

Заканчивать

Теперь, когда мы понимаем, как работает функция обновления, давайте используем ее в нашем коде. Вот весь код с добавленным вызовом:

import dbUpgrade from './db-upgrade.json';
import SQLite from 'react-native-sqlite-storage';
export const open = () => {
  const db;
  SQLite.openDatabase({
    name: 'my-existing-data.db',
    createFromLocation: '~data/my-existing-data.db'
  })
  .then(instance => {
    db = instance;
    db.executeSql('SELECT max(version) FROM version')
    .then(results => {
      let version = results[0];
      if (version < dbUpgrade.version) {
        upgradeFrom(db, version);
      }
    }).catch(error => console.error(error));
  }).catch(error => console.error(error));;
}

Теперь база данных вашего приложения готова к обновлению. Удачного кодирования 🎉

Первоначально опубликовано на http://www.embusinessproducts.com 9 апреля 2018 г.