Управление версиями базы данных в установленных приложениях с использованием Delphi

StackOverflow https://stackoverflow.com/questions/1804464

Вопрос

Я работаю над рядом приложений Delphi, которым потребуется обновить свои собственные структуры баз данных в полевых условиях, когда будут выпущены новые версии и когда пользователи решат установить дополнительные модули.Приложения используют множество встроенных баз данных (в настоящее время DBISAM и Jet, но это может измениться).

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

Я вижу два способа реализации этого:сохранение номера версии в базе данных и использование скриптов DDL для перехода от старых версий к более новым версиям или сохранение эталонной версии структуры базы данных внутри приложения, сравнение ссылки с базой данных при запуске и генерация приложением команд DDL для обновления базы данных.

Я думаю, что мне, вероятно, придется реализовать части и того, и другого.Я не хочу, чтобы приложение сравнивало базу данных со структурой ссылок каждый раз при запуске приложения (слишком медленно), поэтому мне понадобится номер версии структуры базы данных, чтобы определить, использует ли пользователь устаревшую структуру.Однако я не уверен, что могу доверять предварительно написанным скриптам для выполнения структурного обновления, когда база данных могла быть частично обновлена в прошлом или когда пользователь, возможно, сам изменил структуру базы данных, поэтому я склонен использовать ссылочный diff для фактического обновления.

Исследуя вопрос, я нашел пару инструментов управления версиями базы данных, но все они, похоже, ориентированы на SQL Server и реализованы вне реального приложения.Я ищу процесс, который был бы тесно интегрирован в мое приложение и который можно было бы адаптировать к различным требованиям базы данных (я знаю, что мне придется писать адаптеры, пользовательские классы-потомки или код события для обработки различий в DDL для различных баз данных, меня это не беспокоит).

Кто-нибудь знает о чем-нибудь готовом, что делает это или не удается сделать это, есть ли у кого-нибудь какие-нибудь мысли по этому поводу:

  1. Лучший способ сохранить эталонную версию общей структуры реляционной базы данных внутри приложения.

  2. Лучший способ сравнить ссылку с реальной базой данных.

  3. Лучший способ сгенерировать DDL для обновления базы данных.

Это было полезно?

Решение

У меня здесь есть запись в блоге о том, как я делаю управление версиями базы данных dbisam и sql сервер.

Важными частями являются:

Поскольку dbisam не поддерживает представления, номер версии сохраняется (вместе с кучей другой информации) в ini файле в каталоге базы данных.

У меня есть datamodule, TdmodCheckDatabase.У этого есть компонент TdbisamTable для каждой таблицы в базе данных.Компонент таблицы содержит все поля таблицы и обновляется при каждом изменении таблицы .

Для внесения изменений баз данных, был использован следующий процесс :

  1. Увеличьте номер версии в приложении
  2. Вносите и тестируйте изменения в БД.
  3. Обновите затронутые таблицы в базе данных TdmodCheckDatabase
  4. При необходимости (редко) добавьте дополнительные запросы на обновление в TdmodCheckDatabase.Например.чтобы задать значения новых полей или добавить новые строки данных.
  5. Создайте скрипт модуля CreateDatabase, используя прилагаемые инструменты базы данных .
  6. Обновите модульные тесты в соответствии с новой базой данных

Когда приложение запускается, оно проходит через следующий процесс

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

Примером кода является

class procedure TdmodCheckDatabase.UpgradeDatabase(databasePath: string; currentVersion, newVersion: integer);
var
module: TdmodCheckDatabase;
f: integer;
begin
module:= TdmodCheckDatabase.create(nil);
try
  module.OpenDatabase( databasePath );

  for f:= 0 to module.ComponentCount -1  do
  begin
    if module.Components[f] is TDBISAMTable then
    begin
      try
        // if we need to upgrade table to dbisam 4
        if currentVersion <= DB_VERSION_FOR_DBISAM4 then
          TDBISAMTable(module.Components[f]).UpgradeTable;

        module.UpgradeTable(TDBISAMTable(module.Components[f]));
      except
       // logging and error stuff removed
      end;
    end;
  end;

  for f:= currentVersion + 1 to newVersion do
    module.RunUpgradeScripts(f);

  module.sqlMakeIndexes.ExecSQL; // have to create additional indexes manually
 finally
  module.DBISAMDatabase1.Close;
  module.free;
end;
end;


procedure TdmodCheckDatabase.UpgradeTable(table: TDBISAMTable);
var
 fieldIndex: integer;
 needsRestructure: boolean;
 canonical: TField;
begin
 needsRestructure:= false;

 table.FieldDefs.Update;

 // add any new fields to the FieldDefs
 if table.FieldDefs.Count < table.FieldCount then
 begin
   for fieldIndex := table.FieldDefs.Count to table.Fields.Count -1 do
   begin
     table.FieldDefs.Add(fieldIndex + 1, table.Fields[fieldIndex].FieldName, table.Fields[fieldIndex].DataType, table.Fields[fieldIndex].Size, table.Fields[fieldIndex].Required);
   end;
   needsRestructure:= true;
 end;

 // make sure we have correct size for string fields
 for fieldIndex := 0 to table.FieldDefs.Count -1 do
 begin
   if (table.FieldDefs[fieldIndex].DataType = ftString) then
   begin
     canonical:= table.FindField(table.FieldDefs[fieldIndex].Name);
     if assigned(canonical) and (table.FieldDefs[fieldIndex].Size <> canonical.Size) then
   begin
     // field size has changed
     needsRestructure:= true;
     table.FieldDefs[fieldIndex].Size:= canonical.Size;
   end;
   end;
 end;

 if needsRestructure then
   table.AlterTable(); // upgrades table using the new FieldDef values
end;

procedure TdmodCheckDatabase.RunUpgradeScripts(newVersion: integer);
begin
 case newVersion of
   3: sqlVersion3.ExecSQL;
   9: sqlVersion9.ExecSQL;
   11: begin  // change to DBISAM 4
         sqlVersion11a.ExecSQL;
         sqlVersion11b.ExecSQL;
         sqlVersion11c.ExecSQL;
         sqlVersion11d.ExecSQL;
         sqlVersion11e.ExecSQL;
       end;
   19: sqlVersion19.ExecSQL;
   20: sqlVersion20.ExecSQL;
 end;
end;

Другие советы

Похожая история и здесь.Мы сохраняем номер версии базы данных в таблице "system" и проверяем это при запуске.(Если таблица / поле / значение не существует, то мы знаем, что это версия 0, в которую мы забыли добавить этот бит!)

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

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

Суть в том, что вместо использования инструментов на основе графического интерфейса для изменения таблиц специальным или "случайным" образом мы фактически сразу пишем DDL.Это значительно упрощает, когда придет время, создание полного сценария обновления.И изменение структуры не требуется.

Я использую ADO для своих баз данных.Я также использую схему номеров версий, но только для проверки работоспособности.У меня есть разработанная мной программа, которая использует Connection.GetTableNames и Connection.GETFIELDDNAMES для выявления любых несоответствий XML-документу, который описывает "основную" базу данных.Если есть несоответствие, то я создаю соответствующий SQL для создания недостающих полей.Я никогда не отбрасываю дополнительные.

Затем у меня есть таблица dbpatch, которая содержит список исправлений, идентифицированных уникальным именем.Если отсутствуют определенные исправления, то они применяются и соответствующая запись добавляется в таблицу dbpatch.Чаще всего это новые сохраненные процедуры, или изменение размера полей, или индексы

Я также поддерживаю min-db-version, которая также проверяется, поскольку я разрешаю пользователям использовать более старую версию клиента, я разрешаю им использовать только версию >= min-db-version и <= текущая версия базы данных.

Что я делаю, так это сохраняю номер версии в базе данных и номер версии в приложении.Каждый раз, когда мне нужно изменить структуру базы данных, я создаю некоторый код, обновляющий структуру базы данных и увеличивающий номер версии в приложении.Когда приложение запускается, оно сравнивает, вычисляет и, при необходимости, запускает некоторый код для обновления структуры базы данных И обновите номер версии базы данных.Таким образом, база данных приложения теперь находится в актуальном состоянии.Мой код выглядит примерно так

if DBVersion < AppVersion then
begin
  for i := DBVersion+1 to AppVersion do
    UpdateStructure(i);
end
else
  if DBVersion > AppVersion then
    raise EWrongVersion.Create('Wrong application for this database');

UpdateStructure просто запускает необходимый код, что-то вроде :

procedure UpdateStructure(const aVersion : Integer);
begin
  case aVersion of
    1 : //some db code
    2 : //some more db code
    ...
    ...
  end;
  UpdateDatabaseVersion(aVersion);
end;  

На самом деле вы можете использовать тот же код для создания базы данных с нуля

CreateDatabase;
for i := 1 to AppVersion do
  UpdateStructure(i);
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top