GolangでDb2(その2)

2023/12/30に公開

はじめに

前回は Golang で Db2 の各種データ型を扱った場合の評価を行いました。

https://zenn.dev/robon/articles/4af27dcff0d46b

今回は、日本語の識別子、データを扱った場合の評価を行います。

Db2

データベース

前回と同様、Db2 for Docker 上の Db2 を使用します。

テスト用のテーブル

対象とするデータ型

前回は全てのデータ型を対象としましたが、今回は文字データを保持するものを対象とします。

型名 範囲
CHAR 固定長文字列(1~255byte or 1~63CODEUNIT32)
VARCHAR 可変長文字列(1~32,672byte or 1~8,168CODEUNIT32)
CLOB 可変長文字列(1~2,147,483,647byte or 1~536,870,911CODEUNIT32)
GRAPHIC 固定長グラフィック(2byte文字)文字列(1~127グラフィック文字 or 1~63CODEUNIT32)
VARGRAPHIC 可変長グラフィック文字列(1~16,336グラフィック文字 or 1~8,168CODEUNI32)
DBCLOB 可変長グラフィック文字列(1~1,073,741,823グラフィック文字 or 1~536,870,911CODEUNIT32)
XML 整形式のXMLドキュメント

この中で、前回の評価で文字列として扱うことが困難だった DBCLOBXML はギブアップすることにします。

テスト用のテーブル

ちょっとアレな感じの CREATE TABLE 文にします。

create table 日ほンゴ表 (
    列1 char(30) NOT NULL, 
    れつ2 varchar(30), 
    レツ3 clob(30),
    レツ4 graphic(10),
    Retsu5 vargraphic(20),
    CONSTRAINT pk_datatypes PRIMARY KEY(列1)
) ccsid unicode;

ccsid unicode については、以下のページを参照ください。

https://www.ibm.com/docs/ja/db2-for-zos/11?topic=data-creating-unicode-table

Golang

処理系とライブラリ

前回と同様、Golang は、1.20.7。github.com/ibmdb/go_ibm_db は、v0.4.5 を使用します。

アプリケーション

前回と同様ですが、Golang 側の識別子も日本語にしてみます。ただし、Golang の識別子の先頭文字は、パッケージ外への export の可否の識別に使われるため、先頭文字は英大文字にしておきます。

package main

import (
	"database/sql"
	"log"
	"os"
	"reflect"

	"github.com/google/go-cmp/cmp"
	_ "github.com/ibmdb/go_ibm_db"
	"github.com/roboninc/sqlx"
)

// S日ほンゴ表 は、日本語テストテーブル
type S日ほンゴ表 struct {
	F列1     sql.NullString `db:"列1"`     // char(30)
	Fれつ2    sql.NullString `db:"れつ2"`    // varchar(30)
	Fレツ3    sql.NullString `db:"レツ3"`    // clob(30)
	Fレツ4    sql.NullString `db:"レツ4"`    // graphic(10)
	FRetsu5 sql.NullString `db:"RETSU5"` // vargraphic(10)
}

// Key は、日本語テストテーブルのキー
type Key struct {
	Col01 string `db:"col01"`
}

func main() {
	sqlx.BindDriver("go_ibm_db", sqlx.QUESTION)

	dsn := os.Getenv("DSN")
	db, err := sqlx.Open("go_ibm_db", dsn)
	if err != nil {
		log.Printf("sql.Open error %s", err)
	}

	key := Key{"コテイ長文字列         "}
	src := S日ほンゴ表{
		F列1:  sql.NullString{String: "コテイ長文字列         ", Valid: true},
		Fれつ2: sql.NullString{String: "カヘン長文字列", Valid: true},
		// Fレツ3: sql.NullString{String: "あぁアアガAa漢〇㈱ー~―‐-", Valid: true}, // clob は日本語NGみたい
		Fレツ4:    sql.NullString{String: "コテイ長文字列   ", Valid: true},
		FRetsu5: sql.NullString{String: "あぁアアガAa漢〇€㈱ー~―‐-", Valid: true},
	}

	_, err = db.NamedExec(`
		INSERT INTO 日ほンゴ表 (
			列1, れつ2, レツ3, レツ4, RETSU5
		) VALUES (
			:列1, :れつ2, :レツ3, :レツ4, :RETSU5
		)`,
		src,
	)
	if err != nil {
		log.Printf("db.Exec error %s", err)
	}

	dst := S日ほンゴ表{}
	query, args, err := db.BindNamed(`
		SELECT 
			列1, れつ2, レツ3, レツ4, RETSU5
		FROM 日ほンゴ表 
		WHERE 列1 = :col01`,
		key,
	)
	if err != nil {
		log.Printf("db.BindNamed error %s", err)
	}
	err = db.QueryRowx(query,
		args...,
	).StructScan(
		&dst,
	)
	if err != nil {
		log.Printf("db.QueryRow error %s", err)
	}
	if !reflect.DeepEqual(src, dst) {
		// log.Printf("\nsrc = %#v\ndst = %#v\n", src, dst)
		diff := cmp.Diff(src, dst)
		if len(diff) > 0 {
			log.Print(diff)
		}
	}

	_, err = db.NamedExec(`
		DELETE FROM 日ほンゴ表
		WHERE 列1 = :col01`,
		key,
	)
	if err != nil {
		log.Printf("db.Exec error %s", err)
	}
}

問題ではありませんが、Oracle と同じで、Db2 のカラム名の戻しが upper case なのは、全角文字でも同様みたいなので、StructScan で受けるためには、上記のように CREATE TABLE 文で「Retsu5」あっても、db タグは「RETSU5」にしないといけませんでした。

sqlx の問題(再掲というかアップデート)

以前の PostgreSQL の評価時に明らかになったのですが、sqlx の Named Query は、マルチバイト Unicode 対応できていません。

このため、このプログラムを動かすには、sqlx を修正する必要があります。(ここまで再掲)

この修正をプルリクしたのですが、反映というか更新そのものが停止しているようなので、当社で fork した sqlx をメンテナンスすることにします。(上のアプリケーションもこちらを import しています。)

https://github.com/roboninc/sqlx

このリポジトリについては、どこかで記事にしたいと思います。

おわりに

今回の評価では、Db2 特有の日本語の問題は発見できませんでしたが、DBCLOBCLOB の振る舞いを見ると、文字コードセットの設定のバリエーションによって動きが異なる可能性もあります。

GitHubで編集を提案
株式会社ROBONの技術ブログ

Discussion