GolangでOracle(その2)
はじめに
先日、Golang で Oracle の各種データ型を扱った場合の評価を行いました。
今回は、日本語の識別子、データを扱った場合の評価を行います。
Oracle
データベース
前回と同様、Amazon RDS 上に構築した Oracle サーバを使用します。
テスト用のテーブル
対象とするデータ型
前回は、全てのデータ型を対象としましたが、今回は文字データを保持するものを対象とします。
コード | データ型 | 説明(抜粋) |
---|---|---|
1 | VARCHAR2(size [BYTE | CHAR]) | 最大長がsizeバイトまたは文字の可変長文字列。 |
1 | NVARCHAR2(size) | 最大長がsize文字の可変長Unicode文字列。 |
8 | LONG | 最大2GB(231から1を引いたバイト数)の可変長文字データ。 |
96 | CHAR [(size [BYTE | CHAR])] | 長さsizeバイトまたは文字の固定長文字データ。 |
96 | NCHAR[(size)] | 長さsize文字の固定長文字データ。 |
112 | CLOB | シングルバイト文字またはマルチバイト・キャラクタを含むキャラクタ・ラージ・オブジェクト。 |
112 | NCLOB | Unicodeキャラクタを含むキャラクタ・ラージ・オブジェクト。 |
非推奨の LONG 以外は、N なしと N ありの 可変長文字列、固定長文字列、文字型ラージオブジェクト になります。
テスト用のテーブル
ちょっとアレな感じの CREATE TABLE 文にします。
CREATE TABLE 日ほンゴ表 (
列1 VARCHAR2(10 CHAR),
れつ2 NVARCHAR2(10),
レツ3 LONG,
レツ4 CHAR(10 CHAR),
Retsu5 NCHAR(10),
列6 CLOB,
列7 NCLOB,
CONSTRAINT pk_日ほンゴ表 PRIMARY KEY(列1)
);
この CREATE TABLE 文の場合は、sqlplus で実行する前に NLS_LANG の設定が必要になります。Amazon Linux 2 上で動かす場合は、以下のようにします。
$ export NLS_LANG=Japanese_Japan.UTF8
$ sqlplus agra@oracle.■■■■.ap-northeast-1.rds.amazonaws.com/orcl
SQL*Plus: Release 19.0.0.0.0 - Production on 木 5月 25 10:18:47 2023
Version 19.19.0.0.0
Copyright (c) 1982, 2022, Oracle. All rights reserved.
パスワードを入力してください:
最終正常ログイン時間: 木 5月 25 2023 10:09:56 +00:00
Oracle Database 19c Standard Edition 2 Release 19.0.0.0.0 - Production
Version 19.18.0.0.0
に接続されました。
SQL> @testdata/create_table.sql
Golang
処理系とライブラリ
前回と同様、Golang は、1.18.6。github.com/sijms/go-ora/v2 は、v2.7.6 を使用します。
今回はタイムスタンプは、検証の対象ではありませんので、ドライバ付属の独自型の問題はありません(気になる方は前回の記事を参照ください)。
アプリケーション
前回と同様ですが、Golang 側の識別子も日本語にしてみます。ただし、Golang の識別子の先頭文字は、パッケージ外への export の可否の識別に使われるため、先頭文字は英大文字にしておきます。
package main
import (
"database/sql"
"log"
"os"
"reflect"
"github.com/google/go-cmp/cmp"
"github.com/jmoiron/sqlx"
_ "github.com/sijms/go-ora/v2"
)
// S日ほンゴ表 は、日本語テストテーブル
type S日ほンゴ表 struct {
F列1 sql.NullString `db:"列1"` // varchar2
Fれつ2 sql.NullString `db:"れつ2"` // nvarchar2
Fレツ3 sql.NullString `db:"レツ3"` // long
Fレツ4 sql.NullString `db:"レツ4"` // char(10)
FRetsu5 sql.NullString `db:"RETSU5"` // nchar(10) 全角でも大文字
F列6 sql.NullString `db:"列6"` // clob
F列7 sql.NullString `db:"列7"` // nclob
}
// Key は、日本語テストテーブルのキー
type Key struct {
Col01 string `db:"COL01"`
}
func main() {
sqlx.BindDriver("oracle", sqlx.NAMED)
dsn := os.Getenv("DSN")
db, err := sqlx.Open("oracle", 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},
Fレツ4: sql.NullString{String: "コテイ長文字列 ", Valid: true},
FRetsu5: sql.NullString{String: "コテイ長文字列 ", Valid: true},
F列6: sql.NullString{String: "あぁアアガAa漢〇€㈱ー~―‐-", Valid: true},
F列7: sql.NullString{String: "あぁアアガAa漢〇€㈱ー~―‐-", Valid: true},
}
_, err = db.NamedExec(`
INSERT INTO 日ほンゴ表 (
列1, れつ2, レツ3, レツ4, RETSU5, 列6, 列7
) VALUES (
:列1, :れつ2, :レツ3, :レツ4, :RETSU5, :列6, :列7
)`,
src,
)
if err != nil {
log.Printf("db.Exec error %s", err)
}
dst := S日ほンゴ表{}
query, args, err := db.BindNamed(`
SELECT
列1, れつ2, レツ3, レツ4, RETSU5, 列6, 列7
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 のカラム名の戻しが upper case なのは、全角文字でも同様みたいなので、StructScan で受けるためには、上記のように CREATE TABLE 文で「Retsu5」あっても、db タグは「RETSU5」にしないといけませんでした。
sqlx の問題(再掲)
以前の PostgreSQL の評価時に明らかになったのですが、sqlx の Named Query は、マルチバイト Unicode 対応できていません。
このため、このプログラムを動かすには、sqlx を修正する必要があります。今回の修正は、sqlx には プルリク 済みです(が、休眠しているようなので、取り込まれないかもしれません)。
この修正を適用して動かすためには、以下のリポジトリを clone して、go.mod で replace する必要があります。
おわりに
今回の評価では、Oracle 特有の日本語の問題は発見できませんでした。
Discussion