ぽにょろん

思いついたこととメモ

ADO.NET provider 5.0.0.0 が出てた

FB3対応の「ADO.NET provider 5.0.0.0」がリリースされてました。

ADO.NET provider 5.0.0.0 for Firebird is ready | Jiří {x2} Činčura

Release Notes - .NET Data provider - Version 5.0.0.0 - Text format - Firebird RDBMS Issue Tracker

これで自前ビルドせず、NuGetから持ってくればよくなりました。

使用方法はこれまでと変わらず、以下の通りで可能です。

ponyoth.hateblo.jp

同一プログラムからFirebird2.5と3.0のDBにアクセスしたい

同一プログラムからFB3とFB2.5にアクセスするシナリオを考え、サンプルを作ってみました。

目次

環境

  • C# (.NET Framwork 4.5.2)
  • VS2015
  • Firebird .NET Data Provider (5a17a58)
  • Embedded

課題

  • DBにアクセスする前に、ファイルのODSバージョンを知りたい
  • FB2.5とFB3.0のDLLの共存

ODSバージョンの取得

Firebirdのデータベースファイルは一つのバイナリです。
ググった結果、このバイナリを読んで、ヘッダーページにアクセスすれば良さそうということが分かりました。

データベースファイルの内部構造を探ってみた(2/3) − @IT

ただ、情報が古そうなのでソースコードを探してみることに。

github.com

atmarkitの記事と相違はありそうですが、大体よさそう。

ということで、2パターン試してみた。

structを使うパターン

定義を写経して、

[StructLayout(LayoutKind.Sequential)]
public struct HeaderPage
{
    public Pag hdr_header;
    // Page size of database
    [MarshalAs(UnmanagedType.U2)]
    public ushort hdr_page_size;
    // Version of on-disk structure
    [MarshalAs(UnmanagedType.U2)]
    public ushort hdr_ods_version;
    // Page number of PAGES relation
    [MarshalAs(UnmanagedType.U4)]
    public uint hdr_PAGES;
    // Page number of next hdr page
    [MarshalAs(UnmanagedType.U4)]
    public uint hdr_next_page;
....

Marshalのメソッドをつくり、

public static T RawDataToObject<T>(byte[] rawData) where T : struct
{
    var pinnedData = GCHandle.Alloc(rawData, GCHandleType.Pinned);
    try
    {
        return (T)Marshal.PtrToStructure(pinnedData.AddrOfPinnedObject(), typeof(T));
    }
    finally
    {
        pinnedData.Free();
    }
}

ファイル読んで、完成。

private const ushort FirebirdFlag = 0x8000;
....

using (FileStream stream = new FileStream(path, FileMode.Open))
{
    var buf = new byte[1024];
    stream.Read(buf, 0, buf.Length);
    var header = RawDataToObject<HeaderPage>(buf);
    var odsVersion = header.hdr_ods_version & ~FirebirdFlag;
    Console.WriteLine($"ODSVer: {odsVersion}");
    Console.WriteLine($"ODSMinorVer: {header.hdr_ods_minor}");
}

必要なところだけ変換

private const ushort FirebirdFlag = 0x8000;
....
using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read))
{
    int fileSize = (int)fs.Length;
    byte[] buf = new byte[1024];
    fs.Read(buf, 0, 1024);
    var obsHex = buf.Skip(0x12).Take(2).Select(x => x.ToString("X2")).Reverse().Aggregate("", (x, y) => x + y);
    var minor = buf.Skip(0x40).Take(2).Select(x => x.ToString("X2")).Reverse().Aggregate("", (x, y) => x + y);
    Console.WriteLine($"ODSVer: {Convert.ToInt32(obsHex, 16) & ~FirebirdFlag}");
    Console.WriteLine($"ODSMinorVer: {Convert.ToInt32(minor, 16)}");
}

比較

どちらでも行けますが、個人的にはstructでMarshallする方がよいのではと思ってます。*1
結局、定義とにらめっこしたり、リトルエンディアンの処理する必要がありますし。

注意点として、OdsVersionはそのままの値ではだめで、0x8000とのANDをとってあげる必要があります。

firebird/pag.cpp at B3_0_Release · FirebirdSQL/firebird · GitHub

※バイナリ眺めてるときにちゃんとやるなら上記みたいにやらないとだめだけど、
ODSも2桁だし、ODS_MINORも1桁だし普通にリトルエンディアンとか放置でも行けるじゃんって思いました。

FB3とFB2.5の共存

まず必要なDLLをプロジェクトに突っ込んでおきます。
f:id:kowill:20160425224959p:plain

あとは、FBConnectionのClientLibraryに使いたい方のDLLを指定してあげるだけ。

var builder = new FbConnectionStringBuilder();
builder.DataSource = "localhost";
builder.Database = @"D:\DB\TESTFB3.FDB";
builder.ServerType = FbServerType.Embedded;
builder.ClientLibrary = @"dll\fb3\fbclient";
....
var builder = new FbConnectionStringBuilder();
builder.DataSource = "localhost";
builder.Database = @"D:\DB\TESTFB2.FDB";
builder.ServerType = FbServerType.Embedded;
builder.ClientLibrary = @"dll\fb25\fbembed";
....

こうすれば特に問題なく共存が可能です。
Process.GetCurrentProcess().Modules で見てみるとこんな感じになります。
f:id:kowill:20160425225600p:plain

解決

これで課題は解決できたので、あとは必要に応じて処理を組み込めば、同一プログラムからの呼び出しは問題なしです。
与えられたDBファイルから判断して、処理を変えることも可能です。

実際に自分のプロジェクトでやろうとしていたんですが、諸事情でサスペンドしてますw
本当に問題がないのかは不明ですが、今回はこんな感じで終了です。

一応、サンプルを。

github.com

*1:使い方大丈夫なのだろうか

Firebird3.0でEmbedded試してみた

昨日(4月19日)、Firebird3.0(以下FB3)が正式にReleaseされました。

www.firebirdsql.org

せっかくなので、FB3でのEmbedded接続を試してみました。
C#です。

目次

今回試した環境

FB2.5からの大きな変更点

これまでWindows版ではEmbedded用のパッケージが別に用意されていましたが、
FB3からは一緒になったようです。

キムラデービーブログさんに詳しくあるので、そちらを参照してください。

blog.kimuradb.com

Firebirdのバイナリを取得

www.firebirdsql.org
上記から、目的のプラットフォームに合わせて取得。

必要なDLLをコピー

今回必要なものを以下の2つ。
* fbclient.dll
* plugins/engine12.dll

最終的に、以下のようなフォルダ階層になればOK。
f:id:kowill:20160421010106p:plain

ソースコード

gist.github.com

プロジェクトのプラットフォームターゲットの変更に注意。
x86バイナリなら、x86に。x64ならそのように。

実行結果

f:id:kowill:20160421010052p:plain

rank や boolean型を使えていることが確認できます。
Hello Firebird 3.0!完了!

注意事項

  • .NET Data Provider
    2016/4/20時点でNugetから取得した、FirebirdSql.Data.FirebirdClientは
    FB3から対応したBoolean型の型変換できずエラーになります。
    Window関数やその他DBの機能に関してはほとんど動きそうですが、
    Boolean型を試したい場合は、素直にCloneして自前で構築しましょう。*1

  • Firebird2.5で作成したDBについて
    ODS(On-Disk Structure)が変更になっているため、上記の手順で参照するとエラーになります。
    マイグレーションする必要があるようです。
    ツールとかで両バージョン参照したいとかは一工夫(?)いりそうですね。。

  • サポートライブラリ
    上記手順では、サポートライブラリ*2入れてませんが、
    入れておかないと、firebird.logに読み込み失敗ログが出ます。
    ちゃんとした環境で使うときは入れて下さい。

*1:masterをcloneしてくればOK

*2:icu*.dll, ib_util.dll

Firebirdのシステムデータにアクセスしたい場合

DBを扱うプログラムを書く際に、テーブルやカラム、インデックスやトリガー等の一覧を取得したい場合がありませんか?
何かと検索しているので、Firebirdの場合をメモしておきます。

目次

環境

Firebird Embedded 2.5.4
FirebirdSql.Data.FirebirdClient 4.10.0.0
C#

.NET Provider使う場合

FirebirdSql.Data.FirebirdClient使える場合は、非常に簡単です。
GetSchemaしましょう。

f:id:kowill:20160307181701p:plain:w500

テーブル一覧が必要であれば、

using (var con = new FbConnection(接続文字列))
using (var command = con.CreateCommand())
{
    con.Open();
    con.GetSchema("Tables");
}

とすれば、
f:id:kowill:20160307181820p:plain:w500
みたいに取れます。

引数を追加してあげれば、

con.GetSchema("Tables", new[] { null, null, null, "TABLE" });

f:id:kowill:20160307182401p:plain:w500
ユーザーテーブル一覧を取得できます。
制限しない場合は、System Tables(RDB$xxx)とかMonitoring Tables(MON$xxx)とかViewとかもれなくついてきます。

スキーマの引数一覧

そんな便利なものは見つけてません。(ノ゚ω゚)ノ*
あきらめてソースコードから引っ張りましょう。

FirebirdSql.Data.FirebirdClient/Provider/src/FirebirdSql.Data.FirebirdClient/Schema at master · cincuranet/FirebirdSql.Data.FirebirdClient · GitHub

大抵のケースで、
* restrictions [0] : TABLE_CATALOG
* restrictions [1] : TABLE_SCHEMA
* restrictions [2] : TABLE_NAME
* それ以降、個別
となっているっぽいです。
(※上記、new[] { null, null, null, "TABLE" }の部分。)

SQLを自力で書く場合

Referenceとにらめっこしましょう。

Appendix D: System Tables

.NET Providerのソースコードから、必要なSQLの参考にするのもありかなと思います。

テーブル一覧

select rdb$relation_name AS Name  
 from rdb$relations  
 where rdb$relation_type = 0 and rdb$system_flag = 0  
 order by rdb$relation_name asc

列の一覧取る際に、型名,PK,FK等の付属情報つけようとすると、SQLが複雑になって、めんどかったです。
ほかのRDBも似たような感じなんだろうか。。