刹那(せつな)の瞬き

Willkömmen! Ich heiße Setsuna. Haben Sie etwas Zeit für mich?

UbuntuでnanodbcからSQLServerに接続する #2 - テスト編

前回の記事では Ubuntu 環境下で nanodbc の構築とテストを実施しました。

C++ODBC 接続するのに便利な nanodbc は、そのテスト範囲も広かったです。
もう少し nanodbc で用意されているテストについて掘り下げてみます。

1. テスト内容

SQL Server 用のテスト mssql_test.cpp の内容を簡単にまとめてみました。
次の表は MS 版 ODBC ドライバでのmake testの実行結果です。

# テスト項目 内 容 結果
1 test_driver 接続文字列"DRIVER"の確認 OK
2 test_affected_rows DDL/DMLを発行してresult::affected_rows()の確認 OK
3 test_batch_insert_integral int型の配列の値をまとめてINSERTした後、SELECTして確認 OK
4 test_batch_insert_string std::string型の配列の値をまとめて、varchar(60)にINSERTする OK
5 test_batch_insert_mixed int, std::string, float型の配列の値をまとめて、int, varchar(60), float に INSERTする OK
6 test_batch_insert_describe
_param
statement::describe_param()で値をまとめてINSERTする OK
7 test_blob バイト列をvarbinary(max)で格納 OK
8 test_large_blob ファイルをvarbinary(max)で格納 OK
9 test_large_blob_geometry STGeomFromText()でgeometryインスタンスを作成して格納 OK
10 test_large_blob_geometry
_with_bind_statement
STGeomFromText()でgeometryインスタンスを作成してプレースホルダを経由 OK
11 test_blob_with_varchar 文字列をvarbinary(max)で格納 OK
12 test_block_cursor_with
_nvarchar
2行INSERTした後、SELECT時にrowset_size=2として動作確認 OK
13 test_block_cursor_with
_nvarchar_and_first_row
_null
1行目がNULL値で2行INSERTした後、SELECT時にrowset_size=2として動作確認 OK
14 test_block_cursor_with
_nvarchar_and_second
_row_null
2行目がNULL値で2行INSERTした後、SELECT時にrowset_size=2として動作確認  OK
15 test_blob_retrieve_out_of
_order
"Invalid Description Index"の発生を確認
※元ネタはこちらとの事。
OK
16 test_catalog_list_catalogs catalog::list_catalogs()の確認
データベース一覧
OK
17 test_catalog_list_schemas catalog::list_schemas()の確認
スキーマ一覧
OK
18 test_catalog_columns calalog::find_columns()の確認
カラム情報
OK
19 test_catalog_primary_keys catalog::find_primary_keys()の確認
主キー情報
OK
20 test_catalog_tables catalog::find_tables()の確認
テーブル情報
OK
21 test_catalog_table
_privileges
catalog::find_table_privileges()の確認
テーブルの権限
OK
22 test_column_descriptor result::column_name(), result::column_datatype()等の確認 OK
23 test_connection
_environment
connectionオブジェクトと接続環境の情報確認 OK
24 test_dbms_info connection::dbms_name(), connection::dbms_version()の確認 OK
25 test_get_info connection::get_info<T>()の確認 OK
26 test_decimal_conversion decimal型の変換精度の確認 OK
27 test_exception 実行時例外発生の確認 OK
28 test_execute_multiple
_transaction
statement::prepare()で複数回実行
transaction付き
OK
29 test_execute_multiple statement::prepare()で複数回実行 OK
30 test_integral int型, float型, double precision型の確認
double precisionはfloat(53)のシノニム
OK
31 test_move connectionのstd::move()を確認 OK
32 test_null NULL値のバインド、NULL値判別の確認 OK
33 test_nullptr_nulls statement::bind()の第3パラメータにnullptrを指定時の動作確認 OK
34 test_result_iterator resultの各種イテレーション確認 OK
35 test_simple 接続から結果解析まで一連のテスト OK
36 test_string std::stringとvarchar型の確認 OK
37 test_string_with_nvarchar
_max
std::stringとnvarchar(max)型の確認 OK
38 test_string_with_varchar
_max
std::stringとvarchar(max)型の確認 OK
39 test_string_with_ntext std::stringとntext型の確認 OK
40 test_string_with_text std::stringとtext型の確認 OK
41 test_string_vector std::vector<std::string>で用意したパラメータをstatemet::bind_strings()でバインドする OK
42 test_batch_binary std::vector<std::vector<uint8_t>>で用意した複数行のバイト列をバインドする OK
43 test_time nanodbc::timeとtime型の確認 OK
44 test_date nanodbc::dateとdate型の確認 OK
45 test_datetime nanodbc::timestampとdatetime型の確認 OK
46 test_decimal decimal(19,4)型の精度確認 OK
47 test_money money型の精度確認 OK
48 test_datetime2 datetime2型の精度確認 OK
49 test_datetimeoffset datetimeoffset型の精度確認 Failed
50 test_statement_with
_empty_connection
statementオブジェクトに空の接続を指定した場合の動作確認 OK
51 test_transaction transaction付きの一連の動作確認 OK
52 test_while_not_end
_iteration
result::at_end()で判断してからresult::next()して処理 OK
53 test_while_next_iteration result::next()の戻り値で判断して処理 OK
- test_async Windowsのみ。 -
- test_bind_variant Windowsのみ。 -

Linux 環境での mssql_tests のテスト内容は、以上の 53 項目です。
意訳/異訳/省略してる箇所もありますが、テストの内容はこんな感じです。

2. test_datetimeoffset の失敗について

ソースコードを読むと、現在の nanodbc の仕様では、datetimeoffset 型の値は SQL_C_SS_TIMESTAMPOFFSET ではなく、SQL_C_TIMESTAMP として扱うようです。

その際、タイムゾーンに基づいたローカルタイムへの変換を引き起こす、との事。

テストでは datetimeoffset 型に '2006-12-30T13:45:12.345-08:00' をINSERTします。
この値は、日本のタイムゾーンに従って変換されると、日付が変わってしまいます。

・datetimeoffset_test.sql :

SET NOCOUNT ON
DECLARE @d as datetimeoffset = '2006-12-30T13:45:12.345-08:00'
DECLARE @a table (tz varchar(3), dt datetimeoffset)
INSERT INTO @a VALUES ('', @d)
INSERT INTO @a VALUES ('PST', @d AT TIME ZONE 'Pacific Standard Time')
INSERT INTO @a VALUES ('UTC', @d AT TIME ZONE 'UTC')
INSERT INTO @a VALUES ('JST', @d AT TIME ZONE 'Tokyo Standard Time')
SELECT * FROM @a

このクエリの実行結果がこちらです。

$ sqlcmd -Slocalhost -Usa -Pabcd1234$ -i datetimeoffset_test.sql 
tz  dt                                           
--- ---------------------------------------------
               2006-12-30 13:45:12.3450000 -08:00
PST            2006-12-30 13:45:12.3450000 -08:00
UTC            2006-12-30 21:45:12.3450000 +00:00
JST            2006-12-31 06:45:12.3450000 +09:00

これは、nanodbc というより Time Zone / Local Time の解釈の程度問題かと。
テストを通過するには、ソースコードの当該 REQUIRE を改変するしかないです。

3. 補足 : FreeTDS の ODBC ドライバの場合

Ubuntu 18.04 LTS のパッケージ tdsodbc (1.00.82) でも試してみました。
詳細は省きますが、テストの 53 項目中 23 項目が失敗します。

試してませんが、本家 FreeTDS stable版 1.1.26 だと、結果が異なるかもしれません。

....

FreeTDS の名誉の為に書きますが、私はこの結果をあまり問題視していません。

テストの REQUIRE と実行結果の差異は、nanodbc 利用時の挙動を確認する手段でしかない、という認識です。

実際、 tdsodbc は細かい部分で、MS 版 ODBC ドライバとは挙動が異なります。

例えば、#46 test_deciaml の件では、数値型 decimal(19,4) を受ける際、64bit 固定小数で扱える最大値・最小値を評価します。

MS 版 ODBC は文字列と同じですが、tdsodbc では数値として double で丸めた後に文字列化した値と同じになり、評価としては小数部の一部が欠落したように見えます。

C++のプリミティブ型に 64bit 固定小数型が存在しない以上、受けた値をどのように扱うかは Coder 次第かなと。 

続きはこちら