feat: Oracle 驱动完整化——回调体系、版本感知、驱动抽象层与测试套件
- 新增驱动抽象层 driver_adapter(go-ora/godror 双驱动切换) - 新增 Update/Delete/Query 回调,完善 RETURNING INTO 子句 - 修复 create.go 事务悬挂行 BUG、批量 RowsAffected、主键 WHERE 注入 - 版本感知体系:分页/自增/序列默认值/BOOLEAN/32k VARCHAR2 分级能力判定 - 11g 序列+触发器自增、ON UPDATE 触发器、默认值智能转换 - 升级 go-ora v2.8.19 → v2.9.0 - 单元测试+模块测试+集成测试共 96 个,真实 Oracle 11g 全绿 - 测试 DSN 密码移除,改为 ORACLE_DSN 环境变量注入
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
// Package driver_adapter 提供 Oracle 驱动抽象层
|
||||
// 支持 go-ora 和 godror 两种底层驱动的切换
|
||||
package driver_adapter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
)
|
||||
|
||||
// DriverType 驱动类型枚举
|
||||
type DriverType string
|
||||
|
||||
const (
|
||||
// DriverGoOra 使用纯 Go 实现的 go-ora 驱动
|
||||
DriverGoOra DriverType = "go-ora"
|
||||
// DriverGodror 使用基于 ODPI-C 的 godror 驱动
|
||||
DriverGodror DriverType = "godror"
|
||||
)
|
||||
|
||||
// OutParam 输出参数接口,用于 RETURNING INTO 子句
|
||||
type OutParam interface {
|
||||
// GetDest 返回目标指针
|
||||
GetDest() interface{}
|
||||
// SetSize 设置缓冲区大小(用于字符串类型)
|
||||
SetSize(size int)
|
||||
// GetSize 获取缓冲区大小
|
||||
GetSize() int
|
||||
}
|
||||
|
||||
// LobData LOB 数据接口
|
||||
type LobData interface {
|
||||
// IsCLOB 是否为 CLOB 类型
|
||||
IsCLOB() bool
|
||||
// IsBLOB 是否为 BLOB 类型
|
||||
IsBLOB() bool
|
||||
// GetString 获取字符串值(CLOB)
|
||||
GetString() string
|
||||
// GetBytes 获取字节值(BLOB)
|
||||
GetBytes() []byte
|
||||
// IsValid 是否有效
|
||||
IsValid() bool
|
||||
}
|
||||
|
||||
// BatchData 批量数据接口
|
||||
type BatchData interface {
|
||||
// Len 返回数据长度
|
||||
Len() int
|
||||
// GetValues 返回所有值
|
||||
GetValues() []interface{}
|
||||
}
|
||||
|
||||
// Adapter 驱动适配器接口
|
||||
// 封装了不同 Oracle 驱动的差异,提供统一的 API
|
||||
type Adapter interface {
|
||||
// Name 返回驱动名称
|
||||
Name() string
|
||||
|
||||
// Type 返回驱动类型
|
||||
Type() DriverType
|
||||
|
||||
// Open 打开数据库连接
|
||||
Open(dsn string) (*sql.DB, error)
|
||||
|
||||
// CreateOutParam 创建输出参数(用于 RETURNING INTO)
|
||||
// dest: 目标指针
|
||||
// size: 缓冲区大小(字符串类型需要)
|
||||
CreateOutParam(dest interface{}, size int) OutParam
|
||||
|
||||
// CreateClob 创建 CLOB 数据
|
||||
CreateClob(value string) LobData
|
||||
|
||||
// CreateBlob 创建 BLOB 数据
|
||||
CreateBlob(value []byte) LobData
|
||||
|
||||
// CreateBatch 创建批量数据
|
||||
CreateBatch(values []interface{}) BatchData
|
||||
|
||||
// NeedsSizeForOut 返回输出参数是否需要指定 Size
|
||||
// go-ora 对字符串类型的 Out 参数需要指定 Size
|
||||
// godror 通常不需要
|
||||
NeedsSizeForOut() bool
|
||||
|
||||
// SupportsReturningMultiRow 返回是否支持多行 RETURNING
|
||||
// go-ora 不支持批量 INSERT + RETURNING
|
||||
// godror 支持
|
||||
SupportsReturningMultiRow() bool
|
||||
|
||||
// SupportsBulkCopy 返回是否支持 BulkCopy
|
||||
SupportsBulkCopy() bool
|
||||
|
||||
// WrapClobForInsert 包装 CLOB 值用于插入
|
||||
// 某些驱动需要特殊包装
|
||||
WrapClobForInsert(value string) interface{}
|
||||
|
||||
// WrapBlobForInsert 包装 BLOB 值用于插入
|
||||
WrapBlobForInsert(value []byte) interface{}
|
||||
|
||||
// UnwrapQueryResult 解包查询结果
|
||||
// 将驱动特定的类型转换为标准 Go 类型
|
||||
UnwrapQueryResult(value interface{}, typeName string) interface{}
|
||||
|
||||
// GetConnection 获取底层连接(用于高级操作)
|
||||
GetConnection(db *sql.DB) (interface{}, error)
|
||||
|
||||
// Ping 检查连接是否可用
|
||||
Ping(ctx context.Context, db *sql.DB) error
|
||||
}
|
||||
|
||||
// Registry 驱动适配器注册表
|
||||
var registry = map[DriverType]func() Adapter{}
|
||||
|
||||
// Register 注册驱动适配器
|
||||
func Register(driverType DriverType, factory func() Adapter) {
|
||||
registry[driverType] = factory
|
||||
}
|
||||
|
||||
// Get 获取驱动适配器
|
||||
func Get(driverType DriverType) Adapter {
|
||||
if factory, ok := registry[driverType]; ok {
|
||||
return factory()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListDrivers 列出所有已注册的驱动
|
||||
func ListDrivers() []DriverType {
|
||||
types := make([]DriverType, 0, len(registry))
|
||||
for t := range registry {
|
||||
types = append(types, t)
|
||||
}
|
||||
return types
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package driver_adapter
|
||||
|
||||
import "testing"
|
||||
|
||||
// TestRegistryRegisterAndGet 验证 Register 后 Get 能返回对应适配器
|
||||
func TestRegistryRegisterAndGet(t *testing.T) {
|
||||
const key DriverType = "test-registry-driver"
|
||||
|
||||
callCount := 0
|
||||
Register(key, func() Adapter {
|
||||
callCount++
|
||||
return &GoOraAdapter{}
|
||||
})
|
||||
defer delete(registry, key)
|
||||
|
||||
adapter := Get(key)
|
||||
if adapter == nil {
|
||||
t.Fatalf("Get(%q) 返回 nil,期望返回已注册的适配器", key)
|
||||
}
|
||||
if _, ok := adapter.(*GoOraAdapter); !ok {
|
||||
t.Fatalf("Get(%q) 返回类型 = %T,期望 *GoOraAdapter", key, adapter)
|
||||
}
|
||||
if callCount != 1 {
|
||||
t.Errorf("factory 调用次数 = %d,期望 1", callCount)
|
||||
}
|
||||
|
||||
// 每次 Get 都应调用 factory 返回新实例
|
||||
if Get(key) == nil {
|
||||
t.Error("第二次 Get 返回 nil")
|
||||
}
|
||||
if callCount != 2 {
|
||||
t.Errorf("factory 调用次数 = %d,期望 2(每次 Get 都应调用 factory)", callCount)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRegistryGetUnknown 验证 Get 未注册的 DriverType 返回 nil
|
||||
func TestRegistryGetUnknown(t *testing.T) {
|
||||
if adapter := Get(DriverType("no-such-driver")); adapter != nil {
|
||||
t.Fatalf("Get(未注册类型) 返回 %v,期望 nil", adapter)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRegistryListDrivers 验证 ListDrivers 返回包含 DriverGoOra
|
||||
func TestRegistryListDrivers(t *testing.T) {
|
||||
drivers := ListDrivers()
|
||||
if len(drivers) == 0 {
|
||||
t.Fatal("ListDrivers() 返回空列表")
|
||||
}
|
||||
for _, d := range drivers {
|
||||
if d == DriverGoOra {
|
||||
return
|
||||
}
|
||||
}
|
||||
t.Errorf("ListDrivers() = %v,应包含 DriverGoOra", drivers)
|
||||
}
|
||||
|
||||
// TestRegistryGodrorNotCompiled 验证默认构建(无 godror build tag)下不包含 DriverGodror
|
||||
func TestRegistryGodrorNotCompiled(t *testing.T) {
|
||||
for _, d := range ListDrivers() {
|
||||
if d == DriverGodror {
|
||||
t.Errorf("ListDrivers() = %v,默认构建不应包含 DriverGodror(godror.go 带有 //go:build godror 标签)", d)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestDriverTypeConstants 验证驱动类型常量值
|
||||
func TestDriverTypeConstants(t *testing.T) {
|
||||
if DriverGoOra != "go-ora" {
|
||||
t.Errorf("DriverGoOra = %q,期望 %q", DriverGoOra, "go-ora")
|
||||
}
|
||||
if DriverGodror != "godror" {
|
||||
t.Errorf("DriverGodror = %q,期望 %q", DriverGodror, "godror")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
//go:build godror
|
||||
|
||||
package driver_adapter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// GodrorAdapter godror 驱动适配器
|
||||
type GodrorAdapter struct{}
|
||||
|
||||
// godrorOutParam godror 输出参数包装
|
||||
type godrorOutParam struct {
|
||||
dest interface{}
|
||||
size int
|
||||
}
|
||||
|
||||
func (p *godrorOutParam) GetDest() interface{} {
|
||||
return p.dest
|
||||
}
|
||||
|
||||
func (p *godrorOutParam) SetSize(size int) {
|
||||
p.size = size
|
||||
}
|
||||
|
||||
func (p *godrorOutParam) GetSize() int {
|
||||
return p.size
|
||||
}
|
||||
|
||||
// godrorLobData godror LOB 数据包装
|
||||
type godrorLobData struct {
|
||||
isClob bool
|
||||
strVal string
|
||||
byteVal []byte
|
||||
valid bool
|
||||
}
|
||||
|
||||
func (l *godrorLobData) IsCLOB() bool {
|
||||
return l.isClob
|
||||
}
|
||||
|
||||
func (l *godrorLobData) IsBLOB() bool {
|
||||
return !l.isClob
|
||||
}
|
||||
|
||||
func (l *godrorLobData) GetString() string {
|
||||
return l.strVal
|
||||
}
|
||||
|
||||
func (l *godrorLobData) GetBytes() []byte {
|
||||
return l.byteVal
|
||||
}
|
||||
|
||||
func (l *godrorLobData) IsValid() bool {
|
||||
return l.valid
|
||||
}
|
||||
|
||||
// godrorBatchData godror 批量数据包装
|
||||
type godrorBatchData struct {
|
||||
values []interface{}
|
||||
}
|
||||
|
||||
func (b *godrorBatchData) Len() int {
|
||||
return len(b.values)
|
||||
}
|
||||
|
||||
func (b *godrorBatchData) GetValues() []interface{} {
|
||||
return b.values
|
||||
}
|
||||
|
||||
// Name 返回驱动名称
|
||||
func (a *GodrorAdapter) Name() string {
|
||||
return "godror"
|
||||
}
|
||||
|
||||
// Type 返回驱动类型
|
||||
func (a *GodrorAdapter) Type() DriverType {
|
||||
return DriverGodror
|
||||
}
|
||||
|
||||
// Open 打开数据库连接
|
||||
func (a *GodrorAdapter) Open(dsn string) (*sql.DB, error) {
|
||||
db, err := sql.Open("godror", dsn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open connection with godror: %w", err)
|
||||
}
|
||||
return db, nil
|
||||
}
|
||||
|
||||
// CreateOutParam 创建输出参数(用于 RETURNING INTO)
|
||||
func (a *GodrorAdapter) CreateOutParam(dest interface{}, size int) OutParam {
|
||||
return &godrorOutParam{
|
||||
dest: dest,
|
||||
size: size,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateClob 创建 CLOB 数据
|
||||
func (a *GodrorAdapter) CreateClob(value string) LobData {
|
||||
return &godrorLobData{
|
||||
isClob: true,
|
||||
strVal: value,
|
||||
valid: true,
|
||||
byteVal: nil,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateBlob 创建 BLOB 数据
|
||||
func (a *GodrorAdapter) CreateBlob(value []byte) LobData {
|
||||
return &godrorLobData{
|
||||
isClob: false,
|
||||
byteVal: value,
|
||||
valid: true,
|
||||
strVal: "",
|
||||
}
|
||||
}
|
||||
|
||||
// CreateBatch 创建批量数据
|
||||
func (a *GodrorAdapter) CreateBatch(values []interface{}) BatchData {
|
||||
return &godrorBatchData{
|
||||
values: values,
|
||||
}
|
||||
}
|
||||
|
||||
// NeedsSizeForOut 返回输出参数是否需要指定 Size
|
||||
func (a *GodrorAdapter) NeedsSizeForOut() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// SupportsReturningMultiRow 返回是否支持多行 RETURNING
|
||||
func (a *GodrorAdapter) SupportsReturningMultiRow() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// SupportsBulkCopy 返回是否支持 BulkCopy
|
||||
func (a *GodrorAdapter) SupportsBulkCopy() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// WrapClobForInsert 包装 CLOB 值用于插入
|
||||
func (a *GodrorAdapter) WrapClobForInsert(value string) interface{} {
|
||||
// godror 可以直接处理字符串作为 CLOB
|
||||
return value
|
||||
}
|
||||
|
||||
// WrapBlobForInsert 包装 BLOB 值用于插入
|
||||
func (a *GodrorAdapter) WrapBlobForInsert(value []byte) interface{} {
|
||||
// godror 可以直接处理字节数组作为 BLOB
|
||||
return value
|
||||
}
|
||||
|
||||
// UnwrapQueryResult 解包查询结果
|
||||
func (a *GodrorAdapter) UnwrapQueryResult(value interface{}, typeName string) interface{} {
|
||||
// godror 通常返回标准 Go 类型,无需特殊处理
|
||||
// 但如果遇到特定类型,可以在这里进行转换
|
||||
switch v := value.(type) {
|
||||
case *string:
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
return *v
|
||||
case *[]byte:
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
return *v
|
||||
default:
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
// GetConnection 获取底层连接(用于高级操作)
|
||||
func (a *GodrorAdapter) GetConnection(db *sql.DB) (interface{}, error) {
|
||||
// 从 sql.DB 获取原始连接
|
||||
conn, err := db.Conn(context.Background())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// 返回原始连接
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// Ping 检查连接是否可用
|
||||
func (a *GodrorAdapter) Ping(ctx context.Context, db *sql.DB) error {
|
||||
return db.PingContext(ctx)
|
||||
}
|
||||
|
||||
// init 注册 godror 驱动适配器
|
||||
func init() {
|
||||
Register(DriverGodror, func() Adapter {
|
||||
return &GodrorAdapter{}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
// Package driver_adapter 提供 Oracle 驱动抽象层
|
||||
// 支持 go-ora 和 godror 两种底层驱动的切换
|
||||
package driver_adapter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
go_ora "github.com/sijms/go-ora/v2"
|
||||
)
|
||||
|
||||
// GoOraAdapter go-ora 驱动适配器
|
||||
type GoOraAdapter struct{}
|
||||
|
||||
// goOraOutParam 包装 go_ora.Out
|
||||
type goOraOutParam struct {
|
||||
out go_ora.Out
|
||||
}
|
||||
|
||||
// GetDest 返回目标指针
|
||||
func (o *goOraOutParam) GetDest() interface{} {
|
||||
return o.out.Dest
|
||||
}
|
||||
|
||||
// SetSize 设置缓冲区大小(用于字符串类型)
|
||||
func (o *goOraOutParam) SetSize(size int) {
|
||||
o.out.Size = size
|
||||
}
|
||||
|
||||
// GetSize 获取缓冲区大小
|
||||
func (o *goOraOutParam) GetSize() int {
|
||||
return o.out.Size
|
||||
}
|
||||
|
||||
// goOraLobData 包装 go_ora.Clob 和 go_ora.Blob
|
||||
type goOraLobData struct {
|
||||
isClob bool
|
||||
strVal string
|
||||
byteVal []byte
|
||||
valid bool
|
||||
}
|
||||
|
||||
// IsCLOB 是否为 CLOB 类型
|
||||
func (l *goOraLobData) IsCLOB() bool {
|
||||
return l.isClob
|
||||
}
|
||||
|
||||
// IsBLOB 是否为 BLOB 类型
|
||||
func (l *goOraLobData) IsBLOB() bool {
|
||||
return !l.isClob
|
||||
}
|
||||
|
||||
// GetString 获取字符串值(CLOB)
|
||||
func (l *goOraLobData) GetString() string {
|
||||
return l.strVal
|
||||
}
|
||||
|
||||
// GetBytes 获取字节值(BLOB)
|
||||
func (l *goOraLobData) GetBytes() []byte {
|
||||
return l.byteVal
|
||||
}
|
||||
|
||||
// IsValid 是否有效
|
||||
func (l *goOraLobData) IsValid() bool {
|
||||
return l.valid
|
||||
}
|
||||
|
||||
// goOraBatchData 包装批量数据
|
||||
type goOraBatchData struct {
|
||||
values []interface{}
|
||||
}
|
||||
|
||||
// Len 返回数据长度
|
||||
func (b *goOraBatchData) Len() int {
|
||||
return len(b.values)
|
||||
}
|
||||
|
||||
// GetValues 返回所有值
|
||||
func (b *goOraBatchData) GetValues() []interface{} {
|
||||
return b.values
|
||||
}
|
||||
|
||||
// Name 返回驱动名称
|
||||
func (a *GoOraAdapter) Name() string {
|
||||
return "go-ora"
|
||||
}
|
||||
|
||||
// Type 返回驱动类型
|
||||
func (a *GoOraAdapter) Type() DriverType {
|
||||
return DriverGoOra
|
||||
}
|
||||
|
||||
// Open 打开数据库连接
|
||||
func (a *GoOraAdapter) Open(dsn string) (*sql.DB, error) {
|
||||
return sql.Open("oracle", dsn)
|
||||
}
|
||||
|
||||
// CreateOutParam 创建输出参数(用于 RETURNING INTO)
|
||||
func (a *GoOraAdapter) CreateOutParam(dest interface{}, size int) OutParam {
|
||||
return &goOraOutParam{
|
||||
out: go_ora.Out{Dest: dest, Size: size},
|
||||
}
|
||||
}
|
||||
|
||||
// CreateClob 创建 CLOB 数据
|
||||
func (a *GoOraAdapter) CreateClob(value string) LobData {
|
||||
return &goOraLobData{
|
||||
isClob: true,
|
||||
strVal: value,
|
||||
byteVal: nil,
|
||||
valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateBlob 创建 BLOB 数据
|
||||
func (a *GoOraAdapter) CreateBlob(value []byte) LobData {
|
||||
return &goOraLobData{
|
||||
isClob: false,
|
||||
strVal: "",
|
||||
byteVal: value,
|
||||
valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateBatch 创建批量数据
|
||||
func (a *GoOraAdapter) CreateBatch(values []interface{}) BatchData {
|
||||
return &goOraBatchData{
|
||||
values: values,
|
||||
}
|
||||
}
|
||||
|
||||
// NeedsSizeForOut 返回输出参数是否需要指定 Size
|
||||
func (a *GoOraAdapter) NeedsSizeForOut() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// SupportsReturningMultiRow 返回是否支持多行 RETURNING
|
||||
func (a *GoOraAdapter) SupportsReturningMultiRow() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// SupportsBulkCopy 返回是否支持 BulkCopy
|
||||
func (a *GoOraAdapter) SupportsBulkCopy() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// WrapClobForInsert 包装 CLOB 值用于插入
|
||||
func (a *GoOraAdapter) WrapClobForInsert(value string) interface{} {
|
||||
return go_ora.Clob{String: value, Valid: true}
|
||||
}
|
||||
|
||||
// WrapBlobForInsert 包装 BLOB 值用于插入
|
||||
func (a *GoOraAdapter) WrapBlobForInsert(value []byte) interface{} {
|
||||
return go_ora.Blob{Data: value, Valid: true}
|
||||
}
|
||||
|
||||
// UnwrapQueryResult 解包查询结果
|
||||
func (a *GoOraAdapter) UnwrapQueryResult(value interface{}, typeName string) interface{} {
|
||||
// 根据需要处理 go-ora 特有的返回类型转换
|
||||
// 这里简单返回原始值,可根据实际需求扩展
|
||||
return value
|
||||
}
|
||||
|
||||
// GetConnection 获取底层连接(用于高级操作)
|
||||
func (a *GoOraAdapter) GetConnection(db *sql.DB) (interface{}, error) {
|
||||
conn, err := db.Conn(context.Background())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
var rawConn interface{}
|
||||
err = conn.Raw(func(driverConn interface{}) error {
|
||||
rawConn = driverConn
|
||||
return nil
|
||||
})
|
||||
|
||||
return rawConn, err
|
||||
}
|
||||
|
||||
// Ping 检查连接是否可用
|
||||
func (a *GoOraAdapter) Ping(ctx context.Context, db *sql.DB) error {
|
||||
return db.PingContext(ctx)
|
||||
}
|
||||
|
||||
// init 函数中注册驱动
|
||||
func init() {
|
||||
Register(DriverGoOra, func() Adapter {
|
||||
return &GoOraAdapter{}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
package driver_adapter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
go_ora "github.com/sijms/go-ora/v2"
|
||||
)
|
||||
|
||||
// invalidDSN 用于无需真实连接的场景:
|
||||
// 空字符串在 go-ora 的 dsn 解析阶段即返回错误,避免触发真实 TCP 连接导致测试挂起
|
||||
const invalidDSN = ""
|
||||
|
||||
// newTestAdapter 创建 GoOraAdapter 测试实例
|
||||
func newTestAdapter() *GoOraAdapter {
|
||||
return &GoOraAdapter{}
|
||||
}
|
||||
|
||||
// TestGoOraAdapterBasics 验证适配器基本属性
|
||||
func TestGoOraAdapterBasics(t *testing.T) {
|
||||
a := newTestAdapter()
|
||||
|
||||
if got := a.Name(); got != "go-ora" {
|
||||
t.Errorf("Name() = %q,期望 %q", got, "go-ora")
|
||||
}
|
||||
if got := a.Type(); got != DriverGoOra {
|
||||
t.Errorf("Type() = %q,期望 %q", got, DriverGoOra)
|
||||
}
|
||||
if !a.NeedsSizeForOut() {
|
||||
t.Error("NeedsSizeForOut() = false,期望 true")
|
||||
}
|
||||
if a.SupportsReturningMultiRow() {
|
||||
t.Error("SupportsReturningMultiRow() = true,期望 false")
|
||||
}
|
||||
if !a.SupportsBulkCopy() {
|
||||
t.Error("SupportsBulkCopy() = false,期望 true")
|
||||
}
|
||||
}
|
||||
|
||||
// TestGoOraCreateOutParam 验证输出参数创建
|
||||
func TestGoOraCreateOutParam(t *testing.T) {
|
||||
a := newTestAdapter()
|
||||
var id int
|
||||
|
||||
out := a.CreateOutParam(&id, 100)
|
||||
if out == nil {
|
||||
t.Fatal("CreateOutParam 返回 nil")
|
||||
}
|
||||
if got := out.GetDest(); got != &id {
|
||||
t.Errorf("GetDest() = %v,期望 %v", got, &id)
|
||||
}
|
||||
if got := out.GetSize(); got != 100 {
|
||||
t.Errorf("GetSize() = %d,期望 100", got)
|
||||
}
|
||||
|
||||
out.SetSize(200)
|
||||
if got := out.GetSize(); got != 200 {
|
||||
t.Errorf("SetSize 后 GetSize() = %d,期望 200", got)
|
||||
}
|
||||
|
||||
if _, ok := out.(*goOraOutParam); !ok {
|
||||
t.Errorf("返回类型 = %T,期望 *goOraOutParam", out)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGoOraCreateClob 验证 CLOB 创建
|
||||
func TestGoOraCreateClob(t *testing.T) {
|
||||
a := newTestAdapter()
|
||||
|
||||
lob := a.CreateClob("text")
|
||||
if lob == nil {
|
||||
t.Fatal("CreateClob 返回 nil")
|
||||
}
|
||||
if !lob.IsCLOB() {
|
||||
t.Error("IsCLOB() = false,期望 true")
|
||||
}
|
||||
if lob.IsBLOB() {
|
||||
t.Error("IsBLOB() = true,期望 false")
|
||||
}
|
||||
if got := lob.GetString(); got != "text" {
|
||||
t.Errorf("GetString() = %q,期望 %q", got, "text")
|
||||
}
|
||||
if !lob.IsValid() {
|
||||
t.Error("IsValid() = false,期望 true")
|
||||
}
|
||||
if _, ok := lob.(*goOraLobData); !ok {
|
||||
t.Errorf("返回类型 = %T,期望 *goOraLobData", lob)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGoOraCreateBlob 验证 BLOB 创建
|
||||
func TestGoOraCreateBlob(t *testing.T) {
|
||||
a := newTestAdapter()
|
||||
want := []byte{1, 2, 3}
|
||||
|
||||
lob := a.CreateBlob(want)
|
||||
if lob == nil {
|
||||
t.Fatal("CreateBlob 返回 nil")
|
||||
}
|
||||
if !lob.IsBLOB() {
|
||||
t.Error("IsBLOB() = false,期望 true")
|
||||
}
|
||||
if lob.IsCLOB() {
|
||||
t.Error("IsCLOB() = true,期望 false")
|
||||
}
|
||||
if got := lob.GetBytes(); !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("GetBytes() = %v,期望 %v", got, want)
|
||||
}
|
||||
if !lob.IsValid() {
|
||||
t.Error("IsValid() = false,期望 true")
|
||||
}
|
||||
if _, ok := lob.(*goOraLobData); !ok {
|
||||
t.Errorf("返回类型 = %T,期望 *goOraLobData", lob)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGoOraCreateBatch 验证批量数据创建
|
||||
func TestGoOraCreateBatch(t *testing.T) {
|
||||
a := newTestAdapter()
|
||||
want := []interface{}{1, "a"}
|
||||
|
||||
batch := a.CreateBatch(want)
|
||||
if batch == nil {
|
||||
t.Fatal("CreateBatch 返回 nil")
|
||||
}
|
||||
if got := batch.Len(); got != 2 {
|
||||
t.Errorf("Len() = %d,期望 2", got)
|
||||
}
|
||||
got := batch.GetValues()
|
||||
if len(got) != len(want) {
|
||||
t.Fatalf("GetValues() 长度 = %d,期望 %d", len(got), len(want))
|
||||
}
|
||||
for i := range want {
|
||||
if !reflect.DeepEqual(got[i], want[i]) {
|
||||
t.Errorf("GetValues()[%d] = %v,期望 %v", i, got[i], want[i])
|
||||
}
|
||||
}
|
||||
if _, ok := batch.(*goOraBatchData); !ok {
|
||||
t.Errorf("返回类型 = %T,期望 *goOraBatchData", batch)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGoOraWrapForInsert 验证插入包装
|
||||
func TestGoOraWrapForInsert(t *testing.T) {
|
||||
a := newTestAdapter()
|
||||
|
||||
// CLOB 包装
|
||||
clob, ok := a.WrapClobForInsert("text").(go_ora.Clob)
|
||||
if !ok {
|
||||
t.Fatalf("WrapClobForInsert 返回类型 = %T,期望 go_ora.Clob", a.WrapClobForInsert("text"))
|
||||
}
|
||||
if clob.String != "text" {
|
||||
t.Errorf("Clob.String = %q,期望 %q", clob.String, "text")
|
||||
}
|
||||
if !clob.Valid {
|
||||
t.Error("Clob.Valid = false,期望 true")
|
||||
}
|
||||
|
||||
// BLOB 包装
|
||||
blob, ok := a.WrapBlobForInsert([]byte{1}).(go_ora.Blob)
|
||||
if !ok {
|
||||
t.Fatalf("WrapBlobForInsert 返回类型 = %T,期望 go_ora.Blob", a.WrapBlobForInsert([]byte{1}))
|
||||
}
|
||||
if !reflect.DeepEqual(blob.Data, []byte{1}) {
|
||||
t.Errorf("Blob.Data = %v,期望 [1]", blob.Data)
|
||||
}
|
||||
if !blob.Valid {
|
||||
t.Error("Blob.Valid = false,期望 true")
|
||||
}
|
||||
}
|
||||
|
||||
// TestGoOraOpen 验证 Open 只检查驱动名注册,不会真正建立连接
|
||||
func TestGoOraOpen(t *testing.T) {
|
||||
a := newTestAdapter()
|
||||
|
||||
db, err := a.Open("oracle://user:pass@localhost:1521/service")
|
||||
if err != nil {
|
||||
// sql.Open 只在驱动未注册时报错;go-ora 包的 init 已注册 "oracle" 驱动名
|
||||
t.Fatalf("Open() 返回错误: %v(需要 go-ora 包 init 注册 \"oracle\" 驱动名)", err)
|
||||
}
|
||||
if db == nil {
|
||||
t.Fatal("Open() 返回 db == nil")
|
||||
}
|
||||
defer db.Close()
|
||||
}
|
||||
|
||||
// TestGoOraPing 验证对未连接数据库的 Ping 返回错误而非 panic
|
||||
func TestGoOraPing(t *testing.T) {
|
||||
a := newTestAdapter()
|
||||
|
||||
db, err := a.Open(invalidDSN)
|
||||
if err != nil {
|
||||
t.Fatalf("Open() 返回错误: %v", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := a.Ping(ctx, db); err == nil {
|
||||
t.Error("Ping(未连接数据库) 返回 nil,期望返回错误")
|
||||
}
|
||||
}
|
||||
|
||||
// TestGoOraUnwrapQueryResult 验证查询结果原样返回
|
||||
func TestGoOraUnwrapQueryResult(t *testing.T) {
|
||||
a := newTestAdapter()
|
||||
|
||||
cases := []interface{}{
|
||||
42,
|
||||
"hello",
|
||||
[]byte{1, 2, 3},
|
||||
3.14,
|
||||
nil,
|
||||
}
|
||||
for _, in := range cases {
|
||||
if got := a.UnwrapQueryResult(in, ""); !reflect.DeepEqual(got, in) {
|
||||
t.Errorf("UnwrapQueryResult(%v, \"\") = %v,期望原样返回", in, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestGoOraGetConnection 验证获取底层连接不 panic(未连接数据库时返回错误即可)
|
||||
func TestGoOraGetConnection(t *testing.T) {
|
||||
a := newTestAdapter()
|
||||
|
||||
db, err := a.Open(invalidDSN)
|
||||
if err != nil {
|
||||
t.Fatalf("Open() 返回错误: %v", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
raw, err := a.GetConnection(db)
|
||||
if err != nil {
|
||||
t.Logf("GetConnection 返回预期错误(未连接数据库): %v", err)
|
||||
return
|
||||
}
|
||||
if raw == nil {
|
||||
t.Error("GetConnection 返回 nil raw 且无错误")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user