1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

古い FileMaker から MySQL / MariaDB へのマイグレーション

Posted at

はじめに

FileMaker 15 (2016年~)で蓄積された多量のデータ(とアプリ)を、MariaDB(とBlazor)へ移すための、試行錯誤の記録です。
まだ途上であり、移行の必要なデータ(とアプリ)が多数残っているので、将来のために記録を残します。
なお、この記事では、データベースのマイグレーションのみを扱い、Blazorでの開発には触れません。

環境

  • FileMaker Pro 15 (Advanced)
    • Windows 11
  • MariaDB 10.6
    • Ubuntu WSL2

経緯

古いFileMakerのアプリ群をBlazor + MariaDBで再構築しています。

最初のアプリの移行では、小さなCSVを経由して手作業で移し、大きな障害は生じませんでした。
元のアプリは、fmp12ファイル305MB強で、さほど大きくありませんでした。
また、大部分のデータは、オリジナルのネットソースから直接取り込み直しました。

なお、上記は、あくまでもBlazorでの構築例として書かれていて、FileMakerからの移行については触れていません。

二つ目のアプリの移行を試行したところ、CSVでのエクスポート中にFileMakerが異常終了しました。
何度試みても最後まで書き出せず、最終的に断念しました。
このアプリは、移行対象の中では最大級のもので、fmp12ファイルで10GB弱あります。

この記事は、このデータの移行の顛末です。

マイグレーションの選択肢

方法1: 別名で保存する

最も簡便な方法で、最初のアプリデータの移行で採用しました。
残念ながら、今回は使えませんでした。

手法

CSVで書き出して、LOAD DATA INFILE ~で取り込みます。
テーブルの構成を変えない場合は、実テーブルに直接取り込んで、内容を調整します。
構成を新しくする場合は、いったん、一時テーブルに取り込み、その後、実テーブルへ振り分けます。

利点

標準の機能を利用するので、書き出しの際に手間がありません。

課題

取り込み後に変換が必要

データ形式や値の範囲など、そのまま使用すると馴染まない部分が生じますので、ある程度の変換を要します。
例えば、日時の区切り文字や、空欄をNULLに置き換えるなどは鉄板です。
文字列で持っているプライマリキーをシリアル番号に振り直すこともあるかも知れません。

エクスポート中にFileMakerが異常終了する

FileMakerで、巨大なデータを書き出そうとしたときには、よくあることです。(よね?)

方法2: 直接接続する

この記事では詳しく扱いません。

手法1: ODBC

FileMakerのODBCのドライバを導入して、MariaDBからFileMakerをデータソースとして参照します。

資料

FileMaker 15 ODBCとJDBCガイド [PDF]

https://help.claris.com/archive/docs/15/ja/fm15_odbc_jdbc_guide.pdf

FileMaker クライアントドライバを使用する場合は、ポート 2399 を予約する必要があります。

 = ODBCのデータソース参照先ポート

ソフトウェア アップデート:FileMaker xDBC クライアントドライバ

https://support.claris.com/s/article/Software-Update-FileMaker-xDBC-client-drivers-for-FileMaker-1503692806454?language=ja

Linux からの Windows ネットワーク アプリへのアクセス (ホスト IP)

https://learn.microsoft.com/ja-jp/windows/wsl/networking#accessing-windows-networking-apps-from-linux-host-ip

Linux ディストリビューションから次のコマンドを実行して、ホスト マシンの IP アドレスを取得します: ip route show | grep -i default | awk '{ print $3}'

 = ODBCのデータソース参照先IPアドレス

ストレージエンジン FederatedX について

https://mariadb.com/kb/en/about-federatedx/

MariaDB
> INSTALL SONAME 'ha_federatedx'; -- プラグインを(動的に)導入
> CREATE SERVER '<server>' foreign data wrapper 'mysql' options (~); -- 接続先サーバを定義
> CREATE TABLE ~ ENGINE=FEDERATED CONNECTTION='<server>/<database>'; -- 接続先テーブルを作成

FileMaker 15 SQLリファレンスガイド [PDF]

https://help.claris.com/archive/docs/15/ja/fm15_sql_reference.pdf

参考

ODBCで逆向きに接続する場合
外部 SQL データソース(ESS)に対してサポートされている ODBC ドライバ

https://support.claris.com/s/article/%E5%A4%96%E9%83%A8-SQL-%E3%83%87%E3%83%BC%E3%82%BF%E3%82%BD%E3%83%BC%E3%82%B9%E3%81%AB%E5%AF%BE%E3%81%97%E3%81%A6%E3%82%B5%E3%83%9D%E3%83%BC%E3%83%88%E3%81%95%E3%82%8C%E3%81%A6%E3%81%84%E3%82%8B-ODBC-%E3%83%89%E3%83%A9%E3%82%A4%E3%83%90-1503692938179?language=ja

「MySQL Community Server 5.6.12:Windows 用 | MySQL ODBC 5.2 Unicode Driver バージョン 5.2.7 (32 ビットおよび 64 ビット)」

MySQL Connector/ODBC (Archived Versions)

https://downloads.mysql.com/archives/c-odbc/

MariaDB ODBC Connector

https://mariadb.com/kb/en/mariadb-connector-odbc/

手法2: JDBC

FileMakerのJDBCのドライバを導入して移行する記事がありました。

試行

ちょっと触った限りでは、SQL Workbench/Jの最新版では、FileMaker 15のJDBCドライバが認識されませんでした。
FileMaker 15 のガイドには「JDK 1.6以降」と書かれているので、古いバージョンの「works with Java 7」辺りを試すことで、適合するものを見付けられるかも知れません。

利点

接続後は、SQLだけで全部できます。

課題

ドライバの導入と設定、SQLでの開発が必要です。
特に、「接続する」こと自体の敷居が高く、至れませんでした。

方法3: 別形式に変換する

今回、採用した方法です。

手法

FileMakerで生成したSQLをエクスポートして、sudo mariadb db_name < exported.sqlで取り込みます。

利点

あらかじめ、適切な変換を施せるため、移行後の手間がありません。
また、移行時に手作業を挟まないため、再現が容易で、試行錯誤しやすいです。

課題

FileMakerでの開発が必要ですが、SQLに比べると制限が多いです。
特に、カスタム関数でループさせる手段が再帰呼び出ししかないのは厳しいです。

SQLをエクスポートする

題材 (参考)

移行するテーブルは、設定、書籍、ページの3つです。
対象のアプリは、インターネットで公開されているテキストを取り込んで、epubに変換し、kindleのパーソナル・ドキュメントに送信するものです。

開発のサイクル

以下の手順を回しました。

  1. 移行先のDBスキーマを設計
  2. 移行先DBを仮作成してdump
  3. 移行元DBのテーブル毎にsql計算フィールドを作成
  4. スキーマとテーブル毎のsqlフィールドを小刻みにエクスポートして、ひとつのファイルにまとめるスクリプトを作成
  5. スクリプトを走らせてSQLを生成
  6. 生成されたSQLをMariaDBで実行
  7. 移行先DBを確認してフィードバック

移行先のスキーマ

生成したSQLの冒頭で実行することを前提に書かれています。

DROP TABLE IF EXISTS `books`;
CREATE TABLE `books` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `version` int(11) NOT NULL DEFAULT 0,
  `created` datetime NOT NULL DEFAULT current_timestamp(),
  `modified` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
  `url1` varchar(255) NOT NULL DEFAULT '',
  `url2` varchar(255) NOT NULL DEFAULT '',
  `html` longtext DEFAULT NULL,
  `site` INT(11) NOT NULL DEFAULT 0,
  `novel_code` varchar(255) DEFAULT NULL,
  `series_title` longtext DEFAULT NULL,
  `novel_title` longtext DEFAULT NULL,
  `novel_writername` longtext DEFAULT NULL,
  `novel_ex` longtext DEFAULT NULL,
  `ruby_enable` bit(1) NOT NULL DEFAULT b'0',
  `count_of_sheets` int(20) DEFAULT NULL,
  `number_of_published` int(20) DEFAULT NULL,
  `published_at` datetime DEFAULT NULL,
  `read` bit(1) NOT NULL DEFAULT b'0',
  `memorandum` varchar(255) DEFAULT NULL,
  `status` varchar(50) NOT NULL DEFAULT '',
  `html_backup` longtext DEFAULT NULL,
  `errata` longtext DEFAULT NULL,
  `wish` bit(1) NOT NULL DEFAULT b'0',
  `memo` varchar(255) DEFAULT NULL,
  `bookmark` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
DELIMITER ;;
CREATE TRIGGER `version_check_before_update_on_books` BEFORE UPDATE ON `books` FOR EACH ROW begin
    if new.version <= old.version then
        signal SQLSTATE '45000'
        set MESSAGE_TEXT = 'Version mismatch detected.';
    end if;
END ;;
DELIMITER ;
DROP TABLE IF EXISTS `settings`;
CREATE TABLE `settings` (
  `id` bigint(20) NOT NULL DEFAULT 1,
  `version` int(11) NOT NULL DEFAULT 0,
  `created` datetime NOT NULL DEFAULT current_timestamp(),
  `modified` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
  `aozoraEpub3_command` varchar(255) NOT NULL DEFAULT '',
  `personal_document_path` varchar(255) NOT NULL DEFAULT '',
  `personal_document_limit_size` varchar(255) NOT NULL DEFAULT '',
  `smtp_account` varchar(255) NOT NULL DEFAULT '',
  `smtp_mailaddress` varchar(255) NOT NULL DEFAULT '',
  `smtp_replyto` varchar(255) NOT NULL DEFAULT '',
  `smtp_server` varchar(255) NOT NULL DEFAULT '',
  `smtp_port` varchar(255) NOT NULL DEFAULT '',
  `smtp_username` varchar(255) NOT NULL DEFAULT '',
  `smtp_password` varchar(255) NOT NULL DEFAULT '',
  `smtp_mailto` varchar(255) NOT NULL DEFAULT '',
  `smtp_cc` varchar(255) NOT NULL DEFAULT '',
  `smtp_bcc` varchar(255) NOT NULL DEFAULT '',
  `smtp_subject` varchar(255) NOT NULL DEFAULT '',
  `smtp_body` varchar(255) NOT NULL DEFAULT '',
  `import_log` longtext NOT NULL DEFAULT '',
  `memo` varchar(255) NOT NULL DEFAULT '',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
DELIMITER ;;
CREATE TRIGGER `version_check_before_update_on_settings` BEFORE UPDATE ON `settings` FOR EACH ROW begin
    if new.version <= old.version then
        signal SQLSTATE '45000'
        set MESSAGE_TEXT = 'Version mismatch detected.';
    end if;
END ;;
DELIMITER ;
DROP TABLE IF EXISTS `sheets`;
CREATE TABLE `sheets` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `version` int(11) NOT NULL DEFAULT 0,
  `created` datetime NOT NULL DEFAULT current_timestamp(),
  `modified` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
  `book_id` bigint(20) NOT NULL,
  `url` varchar(255) NOT NULL DEFAULT '',
  `html` longtext DEFAULT NULL,
  `sheet_update` datetime DEFAULT NULL,
  `novel_no` INT(20) NOT NULL DEFAULT 0,
  `errata` longtext DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE,
  CONSTRAINT `fk_bookid_books_id` FOREIGN KEY (`book_id`) REFERENCES `books` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
DELIMITER ;;
CREATE TRIGGER `version_check_before_update_on_sheets` BEFORE UPDATE ON `sheets` FOR EACH ROW begin
    if new.version <= old.version then
        signal SQLSTATE '45000'
        set MESSAGE_TEXT = 'Version mismatch detected.';
    end if;
END ;;
DELIMITER ;

テーブル毎のSQL生成フィールド

初期化されたテーブルに挿入される前提のSQLを生成します。

Book

計算フィールド 'Book::sql'
List ( 
"INSERT INTO `books` VALUES ("
 & record_id // Id
 & ",0," //Version
 & sql.Column ( created ; "''" ) & ","
 & sql.Column ( modified ; "''" ) & ","
 & sql.Column ( url[1] ; "''" ) & ","
 & sql.Column ( url[2] ; "''" ) & ","
 & sql.NullableColumn ( If ( site = 0 ; direct_content ; html ) ) & ","
 & sql.Column ( site ; "0" ) & ","
 & sql.NullableColumn ( novel_code ) & ","
 & sql.NullableColumn ( series_title ) & ","
 & sql.NullableColumn ( If ( site = 0 and not IsEmpty ( direct_title_writername ) ; GetValue ( direct_title_writername ; 1 ) ; novel_title ) ) & ","
 & sql.NullableColumn ( If ( site = 0 and not IsEmpty ( direct_title_writername ) ; GetValue ( direct_title_writername ; 2 ) ; novel_writername ) ) & ","
 & sql.NullableColumn ( novel_ex ) & ","
 & sql.Boolean ( ruby_enable ) & ","
 & sql.NullableColumn ( count_of_sheets ) & ","
 & sql.NullableColumn ( number_of_published ) & ","
 & sql.NullableColumn ( published_at ) & ","
 & sql.Boolean ( read ) & ","
 & sql.NullableColumn ( memorandum ) & ","
 & sql.Column ( status ; "''" ) & ","
 & sql.NullableColumn ( html_backup ) & ","
 & sql.NullableColumn ( errata ) & ","
 & sql.Boolean ( wish ) & ","
 & sql.NullableColumn ( memo ) & ","
 & sql.NullableColumn ( bookmark ) & ");" ;
List ( UnLimit Sheet::sql ) ;
Char(10) )

Sheet

計算フィールド 'Sheet::sql'
"INSERT INTO `sheets` VALUES ("
 & Get ( レコード ID ) // Id
 & ",0," //Version
 & sql.Column ( created ; "''" ) & ","
 & sql.Column ( modified ; "''" ) & ","
 & sql.Column ( Book::record_id ; "''" ) & "," // BookId
 & sql.Column ( url ; "''" ) & ","
 & sql.NullableColumn ( If ( site = 0 ; direct_content ; html ) ) & ","
 & sql.NullableColumn ( sheet_update ) & ","
 & sql.Column ( novel_no ; "0" ) & ","
 & sql.NullableColumn ( errata ) & ");"

Setting

計算フィールド 'Setting::sql'
"INSERT INTO `settings` VALUES ("
 & "1," // Id
 & "0," // Version
 & sql.Column ( created ; "''" ) & ","
 & sql.Column ( modified ; "''" ) & ","
 & sql.Column ( AozoraEpub3_command ; "''" ) & ","
 & sql.Column ( personal_document_path ; "''" ) & ","
 & sql.Column ( personal_document_limit_size ; "''" ) & ","
 & sql.Column ( smtp_account ; "''" ) & ","
 & sql.Column ( smtp_mailaddress ; "''" ) & ","
 & sql.Column ( smtp_replyto ; "''" ) & ","
 & sql.Column ( smtp_server ; "''" ) & ","
 & sql.Column ( smtp_port ; "''" ) & ","
 & sql.Column ( smtp_username ; "''" ) & ","
 & sql.Column ( smtp_password ; "''" ) & ","
 & sql.Column ( smtp_mailto ; "''" ) & ","
 & sql.Column ( smtp_cc ; "''" ) & ","
 & sql.Column ( smtp_bcc ; "''" ) & ","
 & sql.Column ( smtp_subject ; "''" ) & ","
 & sql.Column ( smtp_body ; "''" ) & ","
 & sql.Column ( import_log ; "''" ) & ","
 & sql.Column ( memo ; "''" ) & ");"
 & Char(10)

カスタム関数

SQLの生成で使用している関数群です。

型に合わせた出力

カスタム関数 'sql.NullableInt ( value )'
// NULL許容数値
If ( IsEmpty(value) ; "NULL" ; value )

カスタム関数 'sql.Boolean ( value )'
// 真偽値
If ( value ; "TRUE" ; "FALSE" )	
カスタム関数 'sql.NullableColumn ( field )'
// NULL許容列
If ( IsEmpty( field ) ; "NULL" ; sql.Column ( field; "" ) )
カスタム関数 'sql.Column ( field ; default )'
// NULLでない列を型に合わせた形式にする、defaultが空の場合は0または''を使う
Let ([
  type = MiddleWords( FieldType( Get( ファイル名 ) ; GetFieldName( field ) ) ; 2 ; 1 );
  number =  type = "Number";
  datetime = type = "Timestamp";
  blob = type = "Container"
];
  Case (
    IsEmpty ( field )  and IsEmpty ( default ) ; If ( number ; 0 ; "''" ) ;
    IsEmpty ( field ) ; default ;
    number ; field ; 
//  blob ; "_binary 0x" & HexEncode ( field );
    blob ; "load_file('" & GetContainerAttribute ( field ; "filename" ) & "')" ;
    sql.Quote ( Case (
      datetime ; Substitute( field ; "/" ; "-" ) ;
    field ) )
  )
)

SQLのエスケープ

カスタム関数 'sql.Quote ( string )'
// 引用表現
"'" & Substitute( string ; 
  ["\\" ; "\\\\"] ; 
  ["'" ; "\'"] ; 
  ["\"" ; "\\\""] ; 
  [Char(13)&Char(10) ; Char(10)] ; 
  [Char(13) ; Char(10)] ; 
  [Char(11) ; Char(10)] ; 
  [Char(10) ; "\\"&"n"] 
) & "'"

本来は、"\\n"と書けば良いハズですが、FileMakerが"\n"に置き換えてしまうので、別文字列にして連結しています。

パス形式の変換

カスタム関数 'dosPath ( path )'
// FileMakerの内部パス表現をWindowsで使用可能な表現に変換する
Substitute( 
    Case(
        Left( path ; 9 ) = "filewin:/" ; Right( path ; Length( path ) - 9 ) ;
        Left( path ; 9 ) = "filemac:/" ; Right( path ; Length( path ) - 9 ) ;
        Left( path ; 6 ) = "file:/" ; Right( path ; Length( path ) - 6 ) ;
        Left( path ; 1 ) = "/" and Left( path ; 2 )  "//" ; Right( path ; Length( path ) - 1 ) ;
        path
    )
; ["/" ; "\\"] )

SQLを保存するスクリプト

一度に多量のエクスポートを行うとFileMakerが異常終了するので、小刻みに出力して、最後に連結します。
また、フィールドのエクスポートは、utf-16になるので、連結後、utf-8に変換します。

スクリプト 'SQLを保存'
If [ Get ( ウインドウモード ) = 0 ]
	ウインドウの固定
	変数を設定 [ $outfile; 値:"exported.sql" ]
	変数を設定 [ $tempfile1; 値:Get(テンポラリパス) &"temporary_0000000.sql" ]
	変数を設定 [ $tempfile2; 値:Get(テンポラリパス) &"temporary2.sql" ]
	Event を送信 [ ファイル/アプリケーションを開く; "cmd.exe /cdel " & dosPath ( Get (テンポラリパス) & "temporary*.sql" ) ]
	ディレクトリの取得 [ フォルダの作成を許可; $outpath; ダイアログのタイトル: "「" & $outfile & "」の出力先フォルダを選択"; デフォルトの場所: Get ( デスクトップパス ) ]
	ユーザによる強制終了を許可 [ オフ ]
	#schema
	フィールド内容のエクスポート [ Setting::schema_sql_export;$tempfile1]
	#data
	変数を設定 [ $tempfile; 値:Get ( テンポラリパス ) & "temporary_0000001.sql" ]
	フィールド内容のエクスポート [ Setting::sql;$tempfile]
	レイアウト切り替え [ 「Book表示」 (Book) ]
	全レコードを表示
	レコード/検索条件/ページへ移動 [ 最初の ]
	変数を設定 [ $count; 値:1 ]
	ユーザによる強制終了を許可 [ オン ]
	Loop
		変数を設定 [ $tempfile; 値:Get ( テンポラリパス ) & "temporary_" & Right ( "000000" & Book::record_id ; 6 ) & ".sql" ]
		ユーザによる強制終了を許可 [ オフ ]
		フィールド内容のエクスポート [ Book::sql;$tempfile]
		ユーザによる強制終了を許可 [ オン ]
		レコード/検索条件/ページへ移動 [ 次の; 最後まできたら終了 ]
		変数を設定 [ $count; 値:$count + 1 ]
	End Loop
	Event を送信 [ ファイル/アプリケーションを開く; "cmd.exe /ccopy " & dosPath ( Get(テンポラリパス) & "temporary_*.sql" ) & " " & dosPath ( $tempfile2 ) & "&&emeditor.exe \"" & dosPath ( $tempfile2 ) & "\" /cp 65537 /cps 65001 /ss- /sa \"" & dosPath ( $outpath & $outfile ) & "\"" // ref: https://www.emeditor.org/ja/howto/file/file_commandline.html ]
	レイアウト切り替え [ 元のレイアウト ]
End If

課題と解決

オブジェクトフィールドを blob に受け渡す

今回の題材ではオブジェクトフィールドは使われていませんが、一応、検証してみました。

試行1: HexEncode を実装する

FM16で実装されたHexEncode(container)が使えれば、16進リテラル(_binary 0x~)で出力できます。
同関数を自作して、既出のカスタム関数sql.Columnのコメントアウトされている箇所で使用を試みたのですが、メモリ不足で、まともに動きませんでした。

HexEncodeの実装
カスタム関数 'HexEncode ( container )'
// オブジェクトをHEX形式に変換する
base64.HexDump ( Substitute ( Base64Encode ( container ) ; [Char(10) ; ""] ; [Char(13) ; ""] ; [Char(11) ; ""] ) ; 0 ; GetContainerAttribute ( container ; "fileSize" ) )
カスタム関数 'base64.HexDump ( base64 ; start ; size )'
// base64文字列から任意範囲のバイト列を16進ダンプする
If ( start >= size ; "" ;
    Let (
        byte = base64.GetByte ( base64 ; start )
    ;
        Hex ( bitwise.ShiftRight ( byte ; 4 ) ) & Hex ( bitwise.And ( byte ; 15 ) )
         & base64.HexDump ( base64 ; start + 1 ; size )
    )
)
カスタム関数 'base64.GetByte ( base64 ; index )'
// base64文字列から任意の1byteを取得する
Let ([
    sb = index * 8;
    sc = base64.Decode ( Middle ( base64 ; Int ( sb / 6 ) + 1 ; 1 ) );
    scb = Mod ( sb ; 6 );
    eb = sb + 7;
    ec = base64.Decode ( Middle ( base64 ; Int ( eb / 6 ) + 1 ; 1 ) );
    ecb = Mod ( eb ; 6 )
];
    bitwise.ShiftLeft ( bitwise.And ( sc ; bitwise.ShiftLeft ( 1 ; 6 - scb ) - 1 ) ; ecb + 1 )
     + bitwise.ShiftRight ( ec ; 5 - ecb )
)
カスタム関数 'Hex ( nibble )'
// 4bitを16進表現化
Case (
    nibble < 10 ; GetAsText ( nibble ) ;
    nibble = 10 ; "A" ;
    nibble = 11 ; "B" ;
    nibble = 12 ; "C" ;
    nibble = 13 ; "D" ;
    nibble = 14 ; "E" ;
    nibble = 15 ; "F" ;
)
カスタム関数 'bitwise.And ( a ; b )'
// ビット毎の論理積
If ( a = 0 or b = 0 ; 0 ;
Let ([
    a0 = Mod ( a ; 2 );
    b0 = Mod ( b ; 2 );
    as = Int ( a / 2 );
    bs = Int ( b / 2 )
];
    If ( a0 = 1 and b0 = 1 ; 1 ; 0 ) + bitwise.And ( as; bs ) * 2 )
)
カスタム関数 'bitwise.ShiftLeft ( a ; b )'
// 左シフト
If ( b = 0 ; a ; bitwise.ShiftLeft ( a * 2; b - 1 ) )	
カスタム関数 'bitwise.ShiftRight ( a ; b )'
// 右シフト
If ( b = 0 ; a ; bitwise.ShiftRight ( Int( a / 2 ); b - 1 ) )
カスタム関数 'base64.Decode ( letter )'
// base64の任意の1文字をデコードする
Let ( c = Left( letter ; 1 ) ; Case (
    Exact ( c ; "A" ) ; 0;
    Exact ( c ; "B" ) ; 1;
//  ~ ~ ~
    Exact ( c ; "+" ) ; 62;
    Exact ( c ; "/" ) ; 63;
    -1
) )

試行2: 外部ファイルで受け渡す

フィールド毎にエクスポートして、SQL実行時(または事後)にインポートします。

MariaDB
-- 他のカラムとともに挿入
insert into '<table>' ('<column>') values (load_file('<path>'));
-- 他のカラムの後で更新
update '<table>' set '<column>'=load_file('<path>') where Id=<id>;
-- (参考) blobカラムからファイルに書き出す
select '<column>' from '<table>' where Id=<id> into dumpfile '<path>';

フィールドをエクスポートするとUTF-16になる

仕方がないので、出力後に強引に変換しました。

Eventを送信
"emeditor.exe \"" & dosPath ( $infile ) & "\" /cp 65537 /cps 65001 /ss- /sa \"" & dosPath ( $outpath & $outfile ) & "\""

ファイルの連結順序を制御する

copy source*.sql destination.sql みたいなことをした際に、複数のソースファイルが連結される順序は、ファイル名の昇順になるようです。

おわりに

まだ、データの移行ができたところで、アプリの移植はこれからです。
進むにつれてデータの構成が変化したり、さらに移行済みアプリが増えるに従って、手法が追加される可能性もあります。(あるいは、基本方針から覆る可能性も…)

将来、忘れた頃に読んでも解るように書いたつもりですが、まだ、自分にしか解らないようなところがあるかもしれません。
ご不明な点があれば、ご遠慮なくコメントをお寄せください。
あるいは、識者のご助言をいただければ助かります。
最後までお読みいただきありがとうございました。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?