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:
2026-08-08 10:36:57 +08:00
parent 1f6c8a18a3
commit 9154afab6b
35 changed files with 4956 additions and 37 deletions
+109
View File
@@ -0,0 +1,109 @@
package oracle
import (
"reflect"
"testing"
"gorm.io/gorm"
"gorm.io/gorm/migrator"
"gorm.io/gorm/schema"
)
// noopDialector 内嵌真实 Dialector 但跳过 Initialize 的数据库连接,
// 用于在无需真实连接的情况下构造合法的 *gorm.DBcacheStore 会被 gorm.Open 初始化)。
type noopDialector struct {
Dialector
}
func (noopDialector) Initialize(db *gorm.DB) error {
return nil
}
// relParent 含关联关系的模型,用于测试 TryRemoveOnUpdate
type relParent struct {
ID uint `gorm:"primaryKey"`
Kids []relKid `gorm:"foreignKey:ParentID;constraint:OnUpdate:CASCADE"`
}
type relKid struct {
ID uint `gorm:"primaryKey"`
ParentID uint
}
// newTestMigrator 构造一个不依赖真实连接的 Migrator
func newTestMigrator() Migrator {
d := &Dialector{Config: &Config{DBVer: "12.1.0.2.0", DefaultStringSize: 1024}}
db, err := gorm.Open(noopDialector{Dialector: *d}, &gorm.Config{})
if err != nil {
panic(err)
}
return Migrator{Migrator: migrator.Migrator{Config: migrator.Config{
DB: db,
Dialector: d,
CreateIndexAfterCreateTable: true,
}}}
}
// TestTryRemoveOnUpdate 验证从关系约束标签中移除 ON UPDATE 片段
func TestTryRemoveOnUpdate(t *testing.T) {
m := newTestMigrator()
if err := m.TryRemoveOnUpdate(&relParent{}); err != nil {
t.Fatalf("TryRemoveOnUpdate returned error: %v", err)
}
}
// TestTryRemoveOnUpdateWithoutRelations 验证无关联关系的模型不报错
func TestTryRemoveOnUpdateWithoutRelations(t *testing.T) {
m := newTestMigrator()
if err := m.TryRemoveOnUpdate(&limitModel{}); err != nil {
t.Fatalf("TryRemoveOnUpdate returned error: %v", err)
}
}
// TestTryQuotifyReservedWords 验证处理保留字列名不报错
func TestTryQuotifyReservedWords(t *testing.T) {
m := newTestMigrator()
type reservedModel struct {
ID uint `gorm:"primaryKey"`
Select string `gorm:"column:select"`
}
if err := m.TryQuotifyReservedWords(&reservedModel{}); err != nil {
t.Fatalf("TryQuotifyReservedWords returned error: %v", err)
}
}
// TestSequenceAndTriggerNaming 验证序列/触发器名称规则
func TestSequenceAndTriggerNaming(t *testing.T) {
m := newTestMigrator()
seq := m.sequenceName("TEST_USERS")
if seq != "SEQ_TEST_USERS" {
t.Errorf("sequenceName() = %q, want %q", seq, "SEQ_TEST_USERS")
}
trg := m.triggerName("TEST_USERS")
if trg != "TRG_TEST_USERS" {
t.Errorf("triggerName() = %q, want %q", trg, "TRG_TEST_USERS")
}
}
// TestMigratorDelegateMethods 验证 Migrator 的 DataTypeOf 委托
func TestMigratorDelegateMethods(t *testing.T) {
m := newTestMigrator()
f := testField(schema.Int)
f.Size = 64
f.FieldType = reflect.TypeOf(int(0))
f.IndirectFieldType = f.FieldType
if got := m.DataTypeOf(f); got != "INTEGER" {
t.Errorf("Migrator.DataTypeOf() = %q, want %q", got, "INTEGER")
}
}
// TestNoopDialectorImplementsGormDialector 编译期验证 noopDialector 实现了 gorm.Dialector
var _ gorm.Dialector = (*noopDialector)(nil)