С переносом метаданных все понятно - с помощью IBExpert извлекаем метаданные (команда Extract Metadata) из базы под FB 1.5 и создаем новенькую пустую базу на FB 2.5. А вот как перетаскивать сами данные?
Несмотря на то, что в IBExpert есть множество средств импорта/экспорта данных, готового инструмента для переноса данных между базами - нет. К счастью, IBExpert поддерживает мощный скриптовый язык IBEBlock. И на нем достаточно просто создать скрипт с требуемой функциональностью. Далее расскажу подробнее о получившемся скрипте и опишу итоговую процедуру переноса базы с FB1.5 на FB2.5.
Расширенный INSERT INTO в SQL Editor
Честно говоря, начал я не с IBEBlock, а с расширенного синтаксиса INSERT INTO, который поддерживает IBExpert (см. раздел Moving data between databases в документации IBExpert). SQL Editor поддерживает такой синтаксис:INSERT INTO <database_alias>.<table_name>[(<columns_list>)]<select_statement>С помощью этой команды можно перенести таблицу из одной базы в другую. Недолго думая, я написал скрипт вида:
INSERT INTO [db25].TABLE1 SELECT * FROM TABLE1; INSERT INTO [db25].TABLE2 SELECT * FROM TABLE2; ... INSERT INTO [db25].TABLE120 SELECT * FROM TABLE120;и сразу воткнулся в три проблемы.
- Расширенный вариант INSERT INTO <database_alias>.<table_name> работает только в SQL Editor и не работает в SQL Executor. На практике это означает, что за раз можно выполнить только одну команду INSERT INTO. А выполнить все команды оптом - нельзя. Крайне неудобно, если перенос данных между базами требуется провести не раз и не два.
- Расширенный вариант INSERT INTO не учитывает вычисляемые read-only поля, созданные через
computed by
. Если в таблице есть вычисляемое поле, INSERT INTO выдает ошибку. - Многие таблицы связаны между собой зависимостями через foreign keys. Поэтому переноситься они должны в определенном порядке. Так, чтобы при загрузке очередной таблицы, все данные, на которые она ссылается, в базе уже были. Порядок загрузки таблиц нужно определить - либо экспериментальным путем, либо теоретическим, изучив структуру базы. В худшем случае зависимости могут оказаться циклическими и тут без танцев с бубном не обойтись - простого способа автоматизации переноса данных в этом случае нет, придется переносить данные частыми, создавать Update-скрипты и т.д. В моем случае таких проблем, к счастью, не возникло.
Скрипт на IBEBlock
А вот IBEBlock подходит для переноса данных идеально. В документации так и написано - With EXECUTE IBEBLOCK you will be able to: ... Move (copy) data from one database to another. Более того. В примерах использования IBEBlock есть функция Copy Table, предназначенная для копирования таблиц из базы в базу (она, кстати, прекрасно распознает вычисляемые поля и исключает их из INSERT запросов).Я взял эту функцию за основу и, немного повозившись, получил скрипт с нужным функционалом:
EXECUTE IBEBLOCK AS BEGIN FuncCopyTableData = 'execute ibeblock ( SrcObjectName variant = '''' comment ''Table name to be copied'', DestObjectName variant = '''' comment ''Destination table name, leave empty if no changes need'') as begin SrcDBPassword = ''masterkey''; SrcDBUserName = ''SYSDBA''; DestDBUserName = ''SYSDBA''; DestDBPassword = ''masterkey''; SrcDBCharset = ''WIN1251''; DestDBCharset = ''WIN1251''; SrcDBClientLib = ''C:\Program Files (x86)\Firebird\Firebird_1_5\bin\fbclient.dll''; DestDBClientLib = ''C:\Program Files (x86)\Firebird\Firebird_2_5\bin\fbclient.dll''; SrcDBConnStr = ''127.0.0.1:z:\db\db15.fdb''; DestDBConnStr = ''127.0.0.1/3051:z:\db\db25.fdb''; Time1 = ibec_GetTickCount(); CRLF = ibec_CRLF(); BS = ibec_Chr(8); Success = BS + '' Successfull.''; Failed = BS + '' FAILED!''; SrcTableName = SrcObjectName; DestTableName = DestObjectName; SrcDBParams = ''DBName='' + SrcDBConnStr + '';'' + ''User='' + SrcDBUserName + '';'' + ''Password='' + SrcDBPassword + '';'' + ''Names='' + SrcDBCharset + '';'' + ''ClientLib='' + SrcDBClientLib; DestDBParams = ''DBName='' + DestDBConnStr + '';'' + ''User='' + DestDBUserName + '';'' + ''Password='' + DestDBPassword + '';'' + ''Names='' + DestDBCharset + '';'' + ''ClientLib='' + DestDBClientLib; try try ibec_Progress(''Connecting to '' + SrcDBConnStr + ''...''); SrcDB = ibec_CreateConnection(__ctFirebird, SrcDBParams); ibec_Progress(Success); SrcDBSQLDialect = ibec_GetConnectionProp(SrcDB, ''DBSQLDialect''); except ibec_Progress(Failed); raise; Exit; end; try ibec_Progress(''Connecting to '' + DestDBConnStr + ''...''); DestDB = ibec_CreateConnection(__ctFirebird, DestDBParams); ibec_Progress(Success); DestDBSQLDialect = ibec_GetConnectionProp(DestDB, ''DBSQLDialect''); except ibec_Progress(Failed); raise; Exit; end; ibec_UseConnection(SrcDB); select rdb$relation_name, rdb$system_flag, rdb$external_file, rdb$description from rdb$relations where (rdb$relation_name = :SrcTableName) and (rdb$view_blr is null) into :SrcTableData; if (SrcTableData[''RDB$RELATION_NAME''] is null) then exception cant_find_table ''There is no such table ('' + :SrcTableName + '') in the source database.''; IsSys = SrcTableData[''RDB$SYSTEM_FLAG''] = 1; if (IsSys) then exception cant_copy_system_table ''Cannot copy a system table.''; if ((DestTableName is null) or (DestTableName = '''')) then DestTableName = SrcTableName; DestTableNameFmt = ibec_IIF(DestDBSQLDialect = 3, ibec_QuotedStr(:DestTableName, ''"''), ibec_AnsiUpperCase(:DestTableName)); SrcTableNameFmt = ibec_IIF(SrcDBSQLDialect = 3, ibec_QuotedStr(:SrcTableName, ''"''), ibec_AnsiUpperCase(:SrcTableName)); select rdb$field_name from rdb$relation_fields where (rdb$relation_name = ''RDB$FIELDS'') and (rdb$field_name = ''RDB$FIELD_PRECISION'') into :bPrecision; bPrecision = ibec_IIF(:bPrecision is NULL, FALSE, TRUE); SelStmt = ''select rf.rdb$field_name as fld_name,'' + ''rf.rdb$field_source as fld_domain,'' + ''rf.rdb$null_flag as fld_null_flag,'' + ''rf.rdb$default_source as fld_default,'' + ''rf.rdb$description as fld_description,'' + ''f.rdb$field_type as dom_type,'' + ''f.rdb$field_length as dom_length,'' + ''f.rdb$field_sub_type as dom_subtype,'' + ''f.rdb$field_scale as dom_scale,'' + ''f.rdb$null_flag as dom_null_flag,'' + ''f.rdb$character_length as dom_charlen,'' + ''f.rdb$segment_length as dom_seglen,'' + ''f.rdb$system_flag as dom_system_flag,'' + ''f.rdb$computed_source as dom_computedby,'' + ''f.rdb$default_source as dom_default,'' + ''f.rdb$dimensions as dom_dims,'' + ''f.rdb$description as dom_description,'' + ''ch.rdb$character_set_name as dom_charset,'' + ''ch.rdb$bytes_per_character as charset_bytes,'' + ''dco.rdb$collation_name as dom_collation,'' + ''fco.rdb$collation_name as fld_collation''; if (bPrecision) then SelStmt = SelStmt + '', f.rdb$field_precision as dom_precision''; SelStmt = SelStmt + CRLF + ''from rdb$relation_fields rf '' + CRLF + ''left join rdb$fields f on rf.rdb$field_source = f.rdb$field_name'' + CRLF + ''left join rdb$character_sets ch on f.rdb$character_set_id = ch.rdb$character_set_id'' + CRLF + ''left join rdb$collations dco on ((f.rdb$collation_id = dco.rdb$collation_id) and (f.rdb$character_set_id = dco.rdb$character_set_id))'' + CRLF + ''left join rdb$collations fco on ((rf.rdb$collation_id = fco.rdb$collation_id) and (f.rdb$character_set_id = fco.rdb$character_set_id))'' + CRLF + ''where rf.rdb$relation_name = '' + ibec_QuotedStr(:SrcTableName, '''''''') + CRLF + ''order by rf.rdb$field_position''; ibec_Progress(''Collecting fields info...''); i = 0; iUserDomainCount = 0; for execute statement SelStmt into :FldData do begin s = ibec_Trim(FldData[''FLD_DOMAIN'']); aDomains[i] = ibec_IIF(ibec_Copy(s, 1, 4) = ''RDB$'', null, s); if (aDomains[i] is not null) then iUserDomainCount = iUserDomainCount + 1; aFields[i] = ibec_Trim(FldData[''FLD_NAME'']); sType = ibec_IBTypeToStr(FldData[''DOM_TYPE''], FldData[''DOM_SUBTYPE''], FldData[''DOM_LENGTH''], FldData[''DOM_SCALE''], FldData[''DOM_SEGLEN''], FldData[''DOM_CHARLEN''], FldData[''DOM_PRECISION''], DestDBSQLDialect); aTypes[i] = sType; aFieldsNotNull[i] = ibec_IIF(FldData[''FLD_NULL_FLAG''] = 1, '' NOT NULL'', ''''); aFieldsDefault[i] = ibec_IIF(FldData[''FLD_DEFAULT''] is null, '''', '' '' + ibec_Trim(FldData[''FLD_DEFAULT''])); aFieldsComment[i] = FldData[''FLD_DESCRIPTION'']; aFieldsCharset[i] = ibec_IIF(FldData[''DOM_CHARSET''] is null, '''', ibec_Trim(FldData[''DOM_CHARSET''])); aFieldsCollate[i] = ibec_IIF(FldData[''FLD_COLLATION''] is null, '''', ibec_Trim(FldData[''FLD_COLLATION''])); aDomainsComputedBy[i] = FldData[''DOM_COMPUTEDBY'']; i = i + 1; end ibec_UseConnection(DestDB); ------------------------------------------------------------- -- TRANSFER TABLE DATA -------------------------------------- ------------------------------------------------------------- sFields = ''''; sValues = ''''; foreach (aFields as FldName key FldKey) do begin if (aDomainsComputedBy[FldKey] is null) then begin if (sFields <> '''') then begin sFields .= '', ''; sValues .= '', ''; end; FldNameFmt = ibec_IIF(DestDBSQLDialect = 3, ibec_QuotedStr(:FldName, ''"''), ibec_AnsiUpperCase(:FldName)); sFields .= FldNameFmt; sValues .= '':'' + FldNameFmt; end; end; SelectStmt = ''SELECT '' + sFields + '' FROM '' + SrcTableNameFmt; InsertStmt = ''INSERT INTO '' + DestTableNameFmt + '' ('' + sFields + '') VALUES ('' + sValues + '')''; ibec_UseConnection(SrcDB); i = 0; ibec_Progress(''Copying table data...''); for execute statement :SelectStmt into :Data do begin ibec_UseConnection(DestDB); execute statement :InsertStmt values :Data; i = i + 1; if (ibec_mod(i, 500) = 0) then begin commit; ibec_Progress('' '' + ibec_cast(i, __typeString) + '' records copied...''); end; end; ibec_Progress(''Totally '' + ibec_cast(i, __typeString) + '' records copied.''); ibec_UseConnection(DestDB); commit; finally if (SrcDB is not null) then begin ibec_Progress(''Closing connection to '' + SrcDBConnStr + ''...''); ibec_CloseConnection(SrcDB); end; if (DestDB is not null) then begin ibec_Progress(''Closing connection to '' + DestDBConnStr + ''...''); ibec_CloseConnection(DestDB); end; Time2 = ibec_GetTickCount(); sTime = ibec_div((Time2 - Time1), 1000) || ''.'' ||ibec_mod((Time2 - Time1), 1000); ibec_Progress(''Finished.''); ibec_Progress(''Total time spent: '' || sTime || '' seconds''); ibec_Progress(''That''''s all, folks!''); end; end;'; EXECUTE IBEBLOCK FuncCopyTableData ('TABLE1', 'TABLE1'); EXECUTE IBEBLOCK FuncCopyTableData ('TABLE2', 'TABLE2'); ... EXECUTE IBEBLOCK FuncCopyTableData ('TABLE120', 'TABLE120'); end;Команды
EXECUTE IBEBLOCK
в конце скрипта расположены в том порядке, в котором нужно переносить таблицы из базы в базу, чтобы не нарушались связи между данными.P.s. Обращаю внимание на ошибку в примере Copy Table на сайте IBExpert: вместо
ibec_QuotedStr(:SrcTableName, )
нужно писать ibec_QuotedStr(:SrcTableName, '''')
, иначе скрипт не работает.Итоговая процедура переноса базы с Firebird 1.5 на Firebird 2.5
Итоговый процесс перевода базы выглядел следующим образом. Подготовка:- С помощью IBExpert\Tools\Extract Metadata извлечь под FB1.5 метаданные в виде скрипта.
- Выполнить этот скрипт под FB2.5 - убедиться, что все триггеры, процедуры и т.д. создаются без проблем.
- Определить правильный порядок переноса таблиц из базы в базу. Для этого, например, можно вручную перенести все таблицы (одну за одной) из базы в базу с помощью INSERT INTO в SQL Edtitor.
- Деактивировать триггеры в исходной базе. Для этого открыть исходную базу в IBExpert, в DBExplorer щелкнуть правой кнопкой мыши по Triggers и дать команду Deactivate triggers.
- Извлечь метаданные (с отключенными триггерами) под FB1.5 в виде скрипта.
- С помощью полученного скрипта создать чистую пустую базу под FB2.5.
- Создать скрипт переноса данных на IBEBlock - по аналогии со скриптом, приведенным выше. Важно соблюсти правильный порядок следования таблиц в конце скрипта - при загрузке данных не должны нарушаться связи между данными.
- Выполнить скрипт, сделать Commit.
- Активировать триггеры в итоговой базе. Для этого открыть результирующую базу в IBExpert, в DBExplorer щелкнуть правой кнопкой мыши по Triggers и дать команду Activate triggers.
Комментариев нет:
Отправить комментарий