今日もプログラミング

IT技術とかプログラミングのこととか特にJavaを中心に書いていきます

jnr-ffiでJavaからCを呼び出すときに構造体を扱いたい

構造体ポインタを引数にとるCの関数をJavaから呼び出したい

以前、jnr-ffiを使ってJavaからCのライブラリを呼び出してみた。

jnr-ffiでJavaからCを呼び出す (Windows)

 

このときは、intとかStringなどの基本的な型しか使っていなかったが、構造体の場合はどうするのだろう?

という訳で、いろいろ調べてみた。

 

シンプルな構造体

C側はこんな感じ。

struct S1
{
    int n1;
    int n2;
};

extern "C" __declspec(dllexport) int test1(struct S1* s1) 
{
    s1->n2 = s1->n1 + 1;
    return 0;
}

 

jnr-ffiには、Structというクラスがあり、これを継承すれば構造体を表せるようだ。

また、構造体のメンバに対応するクラスUnsigned8、Signed32、UTF8StringなどがStructクラスの内部クラスとして定義されている。

以下のようにして、呼び出すことができた。

public class S1 extends Struct {

    public Signed32 n1 = new Signed32();
    public Signed32 n2 = new Signed32();

    public S1(Runtime runtime) {
        super(runtime);
    }
}

public interface TestLib {
    int test1(S1 s1);
}

public class Test {
    public static void main(String[] args) {
        TestLib testLib = LibraryLoader.create(TestLib.class).load("test-lib");

        S1 s1 = new S1(Runtime.getSystemRuntime());
        s1.n1.set(99);
        testLib.test1(s1);
        System.out.println(s1.n2.get());
    }
}

構造体のメンバにアクセスするには、getとかsetを使わなくてはいけないのでちょっとめんどい。

 

配列をメンバに持つ構造体

このように配列をメンバに持つ構造体の場合はどうなるか?

struct S2
{
    int n1[4];
    int n2[4];
};

結論から言えば、Java側もメンバを配列で持てばいいだけ。

Structクラスは配列メンバを初期化するためのメソッドを用意しているので、それを使うと便利だ。

public class S2 extends Struct {

    public Signed32[] n1 = array(new Signed32[4]);
    public Signed32[] n2 = array(new Signed32[4]);

    public S2(Runtime runtime) {
        super(runtime);
    }
}

単に new Signed32[4] するだけだと配列の要素はnullのままだが、arrayを呼び出すと、各要素を new Signed32() で初期化してくれる。

 

構造体をメンバに持つ構造体

このように構造体が入れ子になっている場合はどうなるか?

struct S3
{
    int n;
    struct S1 s1;
    int x;
};

単に、S3クラスにS1メンバ型の変数を持たせてもうまくいかない。

Structクラスは内部で各メンバの情報を管理しており、そこに登録してやる必要があるからだ。

Signed32クラスなどは、newされると自動的に登録されるようになっている。

 

調べてみたところ、Structクラスにinnerというメソッドがあり、それによりた構造体をメンバとして登録できることが分かった。

public class S3 extends Struct {

    public Signed32 n;
    public S1 s1;
    public Signed32 x;

    public S3(Runtime runtime) {
        super(runtime);

        n = new Signed32();
        s1 = inner(new S1(runtime));
        x = new Signed32();
    }
}

new Signed32() とか inner を呼び出した順にメンバが登録されるので注意。

 

構造体ポインタをメンバに持つ構造体

構造体のポインタがメンバの場合はどうだろう?

struct S3
{
    int n;
    struct S1* s1;
};

StructクラスにはPointerという内部クラスがあり(jnr.ffi.Pointerとは別のクラスなので注意!)、これを使えばよいようだ。

結論から言うと、以下のようにしてうまくいった。

public class S4 extends Struct {

    public Signed32 n = new Signed32();
    private Pointer s1p = new Pointer();
    public S1 s1;

    public S4(Runtime runtime) {
        super(runtime);

        s1 = new S1(runtime);
        s1p.set(getMemory(s1, ParameterFlags.DIRECT));
    }
}

S4クラスには、Pointer s1p と S1 s1 の2つを持たせている。

実際にS4構造体のメンバとなるのはs1pの方。

s1の方はS4構造体のメンバで、別のメモリ上に存在するので、innerでは登録しない。

s1pはポインタなので、s1のアドレスを設定する必要がある。

getMemoryメソッドを呼び出す際、ParameterFlags.DIRECTを指定しないと、実際に物理的メモリが割り当てられず、適切なアドレスが返らないようだ。

embulk-output-sqlserverのパフォーマンスを計測したら結構速かった

embulk-output-sqlserverとは

embulkとは、オープンソースのバルクデータ転送ツールである。

そして、embulk-output-sqlserverとは、embulkのプラグインの1つで、SQL Serverにデータをロードするためのものである。

これを使うと、CSVファイルとか他のデータベースから、SQL Serverにデータをロードすることができる。

 

データベースとバルクロード

データベースに大量にデータ投入するとき、INSERT文で1レコードずつ入れていくと非常に時間が掛かる。

そのため、各DBMSはバルクロード用のツールを用意している。

OracleであればSQL*Loader、SQL Serverであればbcp、などだ。

 

embulk-output-jdbcのデフォルトの実装では、JDBC経由でレコードをINSERTする。

embulkはマルチスレッドで実行されるが、それでもかなり時間が掛かる。

そこで、DBMSのバルクロード用のAPIを利用し、高速化を図っている。

今のところ、OracleSQL Serverでこの手法を実現している。

 

計測結果

計測環境は、サーバとクライアントともにAWSのc3.2xlargeインスタンス(8 vCPU、15GiB メモリ、SSD)。

使用した各ソフトウェアのバージョンは下表の通り。

Windows Server 2012 R2 Standard edition (64bit)
SQL Server 2016 R2 Standard edition
embulk 0.8.12
embulk-input-filesplit 0.1.3
embulk-output-sqlserver 0.6.3

 

入力は、約10GB(50,000,000レコード)のCSVファイル。

テーブル定義は、こんな感じ。

CREATE TABLE EXAMPLE1 (
    ID DECIMAL(12,0),
    NUM DECIMAL(12,0),
    MONTH CHAR(2),
    VALUE1 VARCHAR(20),
    VALUE2 VARCHAR(20),
    VALUE3 VARCHAR(20),
    VALUE4 VARCHAR(20),
    VALUE5 VARCHAR(20),
    VALUE6 VARCHAR(20),
    VALUE7 VARCHAR(20),
    VALUE8 VARCHAR(20),
    VALUE9 VARCHAR(20),
    VALUE10 VARCHAR(20),
    PRIMARY KEY(ID)
);

embulik-input-filesplitは、ファイルを並列で読み込むためのプラグインだ。

embulk-output-sqlserverには、JDBCでINSERTするモード(normal)と、バルクロードのAPISQL Server Native Client)を利用するモード(native)があり、両方測ってみた。

また、embulkはマルチスレッド動作するが、タスク数も変えて測ってみた。

比較のため、bcpでも測っている。

ログを見ると1,000行ずつ送信していて、遅そうだったので、-b 10000を指定してみたら、速くなった。

 

ツールタスク数時間(分)
embulk-output-sqlserver(normal) + embukl-input-filesplit 8 219.0
embulk-output-sqlserver(native) + embukl-input-filesplit 1 27.8
2 18.1
4 15.4
8 9.6
bcp - 17.6
bcp -b 10000 - 11.7

 

まとめ

normalモードが遅いのは予想通りとして、nativeモードが予想以上に速かった。

bcpも、デフォルトのままでなく適切に-bオプションを設定すると結構速くなるようだ。

もちろん、環境とかテーブルぞ構造とかによってパフォーマンスは変わるが、embulk-output-sqlserverは十分速いと言えそうだ。

 

embulk-output-oracleのパフォーマンスを向上する!

背景

ここでも書いたが、embulk-output-oracleを業務システムで使うには、もう少し速くしたい。

そこで、embulk-output-oracleのパフォーマンスを向上することにした!

 

embulk-output-oracleの現状のパフォーマンス

まずは、現状(embulk 0.8.12 + embulk-output-oracle 0.6.1 + embulk-input-filesplit 0.1.3)のパフォーマンスを確認しておく。

AWS上で以下の2つの環境で確認した。なお、OSはWindows 2012 Serverである。

No.OracleClient入力データ
1 c3.xlarge (4 vCPU, Magnetic disk) c3.xlarge (4 vCPU, Magnetic disk) 50,000,000レコード (10GB)
2 c3.2xlarge (8 vCPU, SSD) c3.2xlarge (8 vCPU, SSD) 200,000,000レコード (40GB)

 

計測結果は、以下の通り。

なお、比較のため、名前は言えないが高速な某ツールでも計測している。

ちなみに、環境は異なるが、以前は某ツールより2割ほど遅かった。

[環境1]

ツール時間
某ツール 11.1m
embulk-output-oracle + embulk-input-filesplit 11.2m

[環境2]

ツール 
某ツール 10.6m
embulk-output-oracle + embulk-input-filesplit 22.0m

 

現状のパフォーマンスに関する考察

環境2の方が4倍も入力が多いのに、時間があまり変わらないのは、SSDのおかげだ。

実際、環境1でも2でも、ディスクI/Oがかなり高負荷になっていた。

 

環境1では、embulkは某ツールと同じくらいだが、環境2では倍も時間が掛かっている。

つまり、環境1ではディスクが遅いためOracle側がボトルネックになっているが、環境2ではembulk側がボトルネックになっている。

改善せねば!

 

embulk-output-oracleのパフォーマンス改善

いろいろと計測したり分析したり試行錯誤した結果、修正したのは以下の2点。

なお、プルリクエストまでは作成したが、まだマージはされていない。

ネイティブライブラリ呼び出し回数を減らす

embulk-output-oracleは、Oracleのネイティブライブラリを利用することにより高速化している。

しかし、Java→ネイティブライブラリの呼び出し回数が非常に多いため、オーバーヘッドが大きくなっていた。

そこで、embulk-output-oracleOracleライブラリの間に挟むネイティブライブラリを開発し、呼び出し回数を減らした。

詳細はこちら

 

ロード処理回数を減らす

embulk-output-oracleの中の挙動をよく調べてみると、Oracleライブラリによるロード処理は約200レコード毎に行われていた。

これだとロード回数が非常に多いため、オーバーヘッドが大きくなっていたようである。

そこで、Oracleのネイティブライブラリのバッファサイズを大きくしたりして、ロード間隔をうまいこと調整するようにした(いろいろな条件によって変わるが、環境2の場合は4,000レコード毎くらい)。

 

embulk-output-oracleのパフォーマンス改善結果

改善版を使って、パフォーマンスを計測してみた。

タスク数(並列数)を変えたり、embulk-input-filesplit無しでも測ってみた。

[環境1]

ツール時間
某ツール 11.1m
embulk-output-oracle + embulk-input-filesplit 4タスク 11.3m
2タスク 11.3m
1タスク 12.2m
embulk-output-oracleのみ 11.5m

[環境2]

ツール時間
某ツール 10.6m
embulk-output-oracle + embulk-input-filesplit 8タスク 10.1m
4タスク 10.9m
2タスク 20.1m
1タスク 36.7m
embulk-output-oracleのみ 25.6m

 

環境1の方は、Oracle側が非力なので、どれで測っても大差ない。

さすがに1タスクだとちょっと遅くなるが。

 

環境2の方は、パフォーマンス改善の効果が顕著に出ている!

4タスク以上であれば某ツールと互角だ。

embulk-input-filesplit無しの場合、出力タスクは並列化されるが、入力タスクが並列化されないため、パフォーマンスが落ちるのだろう。

 

まとめ

もちろん、環境やテーブル設計にもよるので一概には言えないが、embulk-output-oracleが某ツールと互角のパフォーマンスを出せたのは非常に嬉しい結果である。

他の環境(より高性能な環境とか、高いが…)でも計測してみたい。

と言うか、早く実システムでも使ってみたい!

 

Embulkを業務システムで使った話

背景

自分はSIerのエンジニアである。

いろいろなお客様の、いろいろな業務システムと格闘するのがお仕事である。

また、今はembulk-input-jdbcとかembulk-output-jdbcのコミッタもやっている。

 

業務システムとRDBとテキストファイル

業務システムでは、たいていRDBを使っている。

そして、サーバ間でデータを連携するために、RDBからテキストファイルにエクスポートしたり、テキストファイルをRDBにインポートしたりすることが結構ある。

そんなときどうするかと言うと、RDB付属のツールを使うことが多い。

例えば、OracleへのインポートであればSQL*Loader、MySQLへのインポートであればmysqlimport、というように。

 

RDB付属のツールの問題点

いろいろなお客様がいて、いろいろなシステムがあるので、RDBもいろいろである。

ちなみに、うちの会社では、OracleSQL ServerMySQLが多い。

なので、RDB毎にいろいろなエクスポート/インポートツールを使い分けなくてはならず、これが結構大変。

ツールごとに仕様が違うし、制約も異なる。

特定の文字コードにしか対応していなかったり、特定の区切り文字にしか対応していなかったり、特殊文字のエスケープが不十分だったり、1行の文字数に制限があったり、…。

調べるのが大変、というだけならまだいいが、結局付属ツールでは対応できず、自作の変換ツールで一旦変換して…、みたいなこともある。

 

Embulkに注目した理由

Embulkとは、OSSのバルクデータ転送ツールである。

プラグイン機構により、様々な入出力に対応している。

もちろんCSVやTSVなどのファイルの入出力もできるし、RDBからの入力RDBへの出力もできる。

Embulkの良いところは、RDBの種類によらず基本的に使い方が同じことだ。

これにより、RDB毎に仕様が異なる煩わしさから解放される(ただしEmbulkプラグイン自体の開発者を除く)

そして、一番嬉しいのは、あらゆるファイルフォーマットに対応できることだ。

Embulk標準のCSV Parser/Formatterも柔軟だが、それでだめなら自分で新たなプラグインを書くこともできる。

この柔軟さはRDB付属のツールには無い。

 

実際に使ったEmbulkプラグイン、使わなかったプラグイン

この業務システムでは、Oracleからタブ区切りファイルへのエクスポート、タブ区切りファイルからOracleへのインポートが必要だった。

そして、エクスポートの方にembulk-input-oracleプラグインを使った。

インポートにはembulk-output-oracleプラグインは使わず、SQL*Loaderを使った。

SQL*Loaderの方が高速だったし、パフォーマンス要件も厳しかったから。

(ただ、embulk-output-oracleも近い将来SQL*Loaderに追いつくと思う)

エクスポートの方は、SQL*PlusでSPOOLを使う方法と比べたが、embulk-input-oracleの方が高速だった。

 

Embulkを使った際の工夫点

EmbulkはJRubyを使っているのだが、これの初期化に結構時間が掛かる。

と言っても1~2秒だが。

で、エクスポートしたいテーブルは数十個あって、データ量もエクスポートするタイミングもいろいろである。

で、件数の少ない10個のテーブルを同時にエクスポート、というとき、件数の割に数十秒も掛かってしまい、Embulk遅いよ、ということになってしまう。

そこで、Embulkをプロセスと起動するのではなく、EmbulkEmbedを使ってクラスライブラリとして呼び出すことにした。

つまり、1つのEmbulkEmbedインスタンスで10回のエクスポートを行うのである。

これなら、初期化は1回で済む。

 

また、エクスポートした件数をログに出して欲しい、という要望があり、簡単なフィルタプラグインを作って対応した。

これもEmbulkだからこそ簡単にできる芸当である。

 

Embulkの稼働状況

開発時に、closeが正しく呼ばれない、というバグがあったが、これは修正済み。

それ以外では、テスト、本番稼働後も、一度も落ちることなく、非常に安定して動作している。

 

今後Embulk(というかembulk-output-jdbc)に必要なこと

バルクローダに求められるのは、メンテナンス性とか柔軟性もあるが、やはりパフォーマンスが最重要であることが多い。

という訳で、embulk-output-jdbcのパフォーマンスを向上すべく、いろいろと試行錯誤している今日この頃である。

 

パフォーマンスとメンテナンス性で揺れるembulk-output-oracle

embulk-output-oracle

embulkとはバルクでのデータ転送ツールだ。

embulk-output-oracleはそのプラグインの1つで、Oracleにデータをロードするためのものだ。

 

OCIの利用

OCIとは、Oracle Call Interfaceのことで、要するにOracleのネイティブライブラリAPIだ。

embulk-output-oracleは、JDBC経由でINSERT文によりデータをロードしていたが、これでは遅い。

そこで、OCIのダイレクトロード機能SQL*Loaderのようなもの)を利用することにより高速化することにした。

JDBCを使うかOCIを使うかは選べるようになっている)

 

これでずいぶん速くなったのだが…、ネイティブライブラリを使うためにCとかJNIのコードを書いたり、しかもCはWindows版とLinux版とを用意したりして、メンテナンス性は悪化してしまった。

 

JNIをjnr-ffi

Javaからネイティブライブラリを呼び出す標準的な手法はJNIだが、jnr-ffiという便利なフレームワークもある。

これを使うと、CとかJNIのコードを書かなくても、Javaから直接的な感じでネイティブライブラリを呼べるのだ!(使い方はこちら

JNIをjnr-ffiに置き換えることにより、Cのコードが無くなり、かなりすっきりした。

 

jnr-ffi化によるパフォーマンスの低下

久しぶりにembulk-output-oracleのパフォーマンスを測ってみると、なんか遅くなっている?

ので、分析してみた。

 

OCIによるロードは、おおざっぱに言うと

① ロードする値を設定 (OCIDirPathColArrayEntrySet)

Oracleにロード (OCIDirPathLoadStream)

の手順である。

 

試しに13列×2億行のデータをロードしてみたところ、

① 合計10分くらい

② 合計8分くらい

という結果だった。

②の処理は、実際にデータベースにアクセスするし、SQL*Loaderの処理時間と比べてみても妥当な感じだ。

 

しかし、①の処理は、単にメモリ上で値を設定しているだけのはずなのに、ずいぶん時間が掛かっている!

OCIDirPathColArrayEntrySetは列数×行数回呼び出すので、26億回の呼び出しだ。

jnr-ffiは軽いと思うが、さすがに26億回も呼び出すとかなりのオーバーヘッドになるのだろう。

 

やはり一部をC+JNIに戻そうか

で、試しに一部をC+JNIに戻して測ってみた。

具体的には、OCIDirPathColArrayEntrySetをまとめて呼び出すCのコードを書いてみた。

Javaから1万行分のデータをそいつに渡し、そいつがOCIDirPathColArrayEntrySetを13万回呼び出す。

これにより、jnr-ffi経由の呼び出しは13万分の1の2万回に減る。

 

実際測ってみると、①の処理時間が10分くらいから2分くらいに大幅に短縮された。

全体で見ても、処理時間が半分くらいになる。

jnr-ffi化前の水準だ。

 

まとめ

やはり遅いのはロードツールとしてはまずいので、部分的にCに戻そうと思う。

それでも、OCIまわりを全てCで書いていた頃よりは、メンテナンスし易いはずだ。

 

それから…、コードを大きく変えたときはパフォーマンスをきちんと確認しなくてはいけないと、反省。。

データベースの列名で大文字と小文字は区別される?

データベースのテーブル名で大文字と小文字は区別される? と同じ結果になると思われるが、一応確認してみた。

Oracle

SQL> create table case_column_test (c1 char(2), C1 char(2));
create table case_column_test (c1 char(2), C1 char(2))
                                           *
行1でエラーが発生しました。:
ORA-00957: 列名が重複しています。


SQL> create table case_column_test ("c1" char(2), C1 char(2), c2 char(2));

表が作成されました。

SQL> desc case_column_test;
 名前                                      NULL?    型
 ----------------------------------------- -------- ----------------------------

 c1                                                 CHAR(2)
 C1                                                 CHAR(2)
 C2                                                 CHAR(2)

SQL>

デフォルトでは大文字/小文字は区別されないが、引用符を付けると区別される。

 

MySQL

Windows

mysql> create table case_column_test (`c1` char(2), `C1` char(2));
ERROR 1060 (42S21): Duplicate column name 'C1'
mysql>

Windowsでは、大文字/小文字は区別されない。

 

Linux

mysql> create table case_test (id1 char(2));
Query OK, 0 rows affected (0.03 sec)

mysql> create table CASE_TEST (id1 char(2));
Query OK, 0 rows affected (0.02 sec)

mysql> show tables;
+----------------+
| Tables_in_test |
+----------------+
| CASE_TEST      |
| case_test      |
+----------------+
2 rows in set (0.00 sec)

mysql> create table case_column_test (c1 char(2), C1 char(2));
ERROR 1060 (42S21): Duplicate column name 'C1'
mysql> create table case_column_test (`c1` char(2), `C1` char(2));
ERROR 1060 (42S21): Duplicate column name 'C1'

Linuxでは、テーブル名は大文字と小文字は区別される。

列名も区別されるかと思ったら…、列名はそ引用符で囲んだとしても区別されないようだ。

 

PostgreSQL

testdb=# create table case_column_test (c1 char(2), C1 char(2));
ERROR:  列"c1"が複数指定されました
testdb=# create table case_column_test ("c1" char(2), "C1" char(2), C2 char(2));

CREATE TABLE
testdb=# \d case_column_test
テーブル "public.case_column_test"
 列 |      型      | 修飾語
----+--------------+--------
 c1 | character(2) |
 C1 | character(2) |
 c2 | character(2) |

testdb=#

Oracleと同様、デフォルトでは大文字/小文字は区別されないが、引用符を付けると区別される。

 

SQL Server

照合順序CI

1> create table case_column_test (c1 char(2), C2 char(2));
2> go
1> sp_help case_column_test
2> go
 Name
...
 case_column_test
...
 Column_name
...
 c1
...
 C2
...
1> create table case_column_test ([c1] char(2), [C1] char(2))
2> go
メッセージ 2705、レベル 16、状態 3、サーバー xxxxxxxx\SQLEXPRESS、行 1
各テーブルの列名は一意にしてください。テーブル 'case_column_test' の列名 'C1' が
複数指定されています。
1>

照合順序がCIの場合は、大文字/小文字が区別されない。

 

照合順序CS

1> create table case_test (id1 char(2))
2> go
1> create table CASE_TEST (id1 char(2))
2> go
1> create table case_column_test (c1 char(2), C1 char(2))
2> go
1> sp_help case_column_test
2> go
 Name
...
 case_column_test
...
 Column_name
...
 c1
...
 C1
...

照合順序がCSの場合は、大文字/小文字が区別される。

データベースのテーブル名は大文字と小文字のどちらで管理される?

以前、「データベースのテーブル名で大文字と小文字は区別される?」という記事を書いたが、もう少し調査を進めてみた。

 

Oracle

SQL> create table "case_test" (id1 char(2));

表が作成されました。

SQL> select table_name from user_tables;

TABLE_NAME
--------------------------------------------------------------------------------

case_test

1行が選択されました。

SQL> create table case_test (id2 char(2));

表が作成されました。

SQL> select table_name from user_tables;

TABLE_NAME
--------------------------------------------------------------------------------

case_test
CASE_TEST

2行が選択されました。

引用符で囲まない場合は大文字/小文字は区別されないが、大文字に変換されているようだ。

なお、java.sql.DatabaseMetaData#getColumnsでは、大文字/小文字は区別されていた。

(つまり、case_testではCASE_TESTのテーブル情報を取得できなかった)

 

MySQL (Windows)

mysql> create table CASE_TEST (id1 char(2));
Query OK, 0 rows affected (0.09 sec)

mysql> show tables;
+------------------+
| Tables_in_testdb |
+------------------+
| case_test        |
+------------------+
1 rows in set (0.00 sec)

mysql> create table `CASE_TEST` (id2 char(2));
ERROR 1050 (42S01): Table 'case_test' already exists
mysql>

MySQLでは、Windowsの場合は、大文字/小文字が区別されないが、小文字に変換されているようだ。

引用符を付けても、大文字/小文字は区別されない。

なお、java.sql.DatabaseMetaData#getColumnsでは、大文字/小文字は区別されなかった。

 

PostgreSQL

testdb=# create table CASE_TEST (id1 char(2));
CREATE TABLE
testdb=# \d
                        リレーションの一覧
 スキーマ |              名前              |    型    |  所有者
----------+--------------------------------+----------+-----------
 public   | case_test                      | テーブル | test_user
testdb=# create table "CASE_TEST" (id2 char(2));
CREATE TABLE
testdb=# \d
                        リレーションの一覧
 スキーマ |              名前              |    型    |  所有者
----------+--------------------------------+----------+-----------
 public   | CASE_TEST                      | テーブル | test_user
 public   | case_test                      | テーブル | test_user
testdb=#

引用符で囲まない場合は大文字/小文字は区別されないが、小文字に変換されているようだ。

なお、java.sql.DatabaseMetaData#getColumnsでは、大文字/小文字は区別されていた。

(つまり、case_testではCASE_TESTのテーブル情報を取得できなかった)

 

SQL Server (照合順序CI)

1> create table case_test (id1 char(2))
2> go
1> create table [CASE_TEST] (id2 char(2))
2> go
メッセージ 2714、レベル 16、状態 6、サーバー xxxxxxxx\SQLEXPRESS、行 1
データベースに 'CASE_TEST' という名前のオブジェクトが既に存在します。
1> select name from sys.tables
2> go
 name

 -------------------------------------------------------------------------------

        -------------------------------------------------

 case_test

(1 行処理されました)
1>

1> create table CASE_TEST2 (id3 char(2))
2> go
1> create table [case_test2] (id4 char(2))
2> go
メッセージ 2714、レベル 16、状態 6、サーバー xxxxxxxx\SQLEXPRESS、行 1
データベースに 'case_test2' という名前のオブジェクトが既に存在します。
1> select name from sys.tables
2> go
 name

 -------------------------------------------------------------------------------

        -------------------------------------------------

 case_test

 CASE_TEST2

(2 行処理されました)
1>

SQL Serverでは、照合順序がCIを含む場合は、大文字/小文字が区別されない。

引用符で囲んだ場合でも、大文字/小文字は区別されない。

大文字でテーブルを作成すれば大文字のまま、小文字でテーブルを作成すれば小文字のままで、変換はされていないようだ。

なお、java.sql.DatabaseMetaData#getColumnsでは、大文字/小文字は区別されなかった。

 

まとめ

DBMSDBMS内でのテーブル名引用符で囲んだ場合java.sql.DatabaseMetaData #getColumns
Oracle 大文字 大文字/小文字は区別される 大文字/小文字は区別される
MySQL (Windows) 小文字 *1 大文字/小文字は区別されない 大文字/小文字は区別されない
PostgreSQL 小文字 大文字/小文字は区別される 大文字/小文字は区別される
SQL Server (照合順序CI) CREATE文に指定したまま 大文字/小文字は区別されない 大文字/小文字は区別されない

*1 MySQL 5.6 リファレンスマニュアルによると、lower_case_table_names システム変数により大文字にも変えられるようだ。

OraclePostgreSQLは、引用符で囲めば大文字と小文字のテーブル名は混在可能だ。

従って、java.sql.DatabaseMetaData#getColumnsでも大文字と小文字は区別される。

実験はしていないが、MySQL(Linux)やSQL Server(照合順序CIでない)も大文字と小文字は区別されるはずだ。

 

Oracle 12c
MySQL 5.6
PostgreSQL 9.4
SQL Server 2012