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
+210
View File
@@ -0,0 +1,210 @@
package oracle
import (
"database/sql"
"fmt"
"reflect"
"github.com/thoas/go-funk"
"gorm.io/gorm"
"gorm.io/gorm/clause"
gormSchema "gorm.io/gorm/schema"
)
func Update(db *gorm.DB) {
stmt := db.Statement
if stmt == nil {
return
}
schema := stmt.Schema
if schema == nil {
return
}
boundVars := make(map[string]int)
hasDefaultValues := len(schema.FieldsWithDefaultDBValue) > 0
if !stmt.Unscoped {
for _, c := range schema.UpdateClauses {
stmt.AddClause(c)
}
}
// 注入主键 WHERE 条件(GORM 默认回调会做这一步)
pkValues := addPrimaryKeyWhere(stmt, schema)
// 多行更新时 Oracle 不支持单行 RETURNING INTO,只有单行更新才启用 RETURNING
if pkValues != 1 {
hasDefaultValues = false
}
// WHERE 安全检查
where, hasWhere := stmt.Clauses["WHERE"].Expression.(clause.Where)
if hasWhere {
if checkMissingWhereConditions(where.Exprs, schema) {
db.AddError(fmt.Errorf("missing WHERE condition in UPDATE"))
return
}
} else {
// 没有 WHERE 子句,且模型主键没有可用的值
db.AddError(fmt.Errorf("missing WHERE condition in UPDATE"))
return
}
if stmt.SQL.String() == "" {
// 构建 UPDATE 语句
stmt.AddClauseIfNotExists(clause.Update{Table: clause.Table{Name: stmt.Schema.Table}})
// 构建 SET 子句
_, hasSet := stmt.Clauses["SET"].Expression.(clause.Set)
if !hasSet {
// 获取要更新的值
// 从 stmt.Dest 获取待更新的数据
reflectValue := reflect.ValueOf(stmt.Dest)
if reflectValue.Kind() == reflect.Ptr {
reflectValue = reflectValue.Elem()
}
// 构建 SET 表达式
sets := make(clause.Set, 0)
switch reflectValue.Kind() {
case reflect.Struct:
for _, field := range schema.Fields {
if !field.PrimaryKey && field.Updatable {
if fieldValue, isZero := field.ValueOf(stmt.Context, reflectValue); !isZero {
// 转换值为 Oracle 兼容格式
convertedValue := convertValue(fieldValue, field)
sets = append(sets, clause.Assignment{Column: clause.Column{Name: field.DBName}, Value: convertedValue})
}
}
}
case reflect.Map:
// 处理 map 类型的更新
for _, mapKey := range reflectValue.MapKeys() {
key := mapKey.String()
if field := schema.LookUpField(key); field != nil {
if !field.PrimaryKey && field.Updatable {
value := reflectValue.MapIndex(mapKey).Interface()
// 转换值为 Oracle 兼容格式
convertedValue := convertValue(value, field)
sets = append(sets, clause.Assignment{Column: clause.Column{Name: field.DBName}, Value: convertedValue})
}
}
}
}
stmt.AddClause(clause.Set(sets))
}
// 添加 RETURNING 子句(如果有默认值字段)
if hasDefaultValues {
stmt.AddClauseIfNotExists(clause.Returning{
Columns: funk.Map(schema.FieldsWithDefaultDBValue, func(field *gormSchema.Field) clause.Column {
return clause.Column{Name: field.DBName}
}).([]clause.Column),
})
}
// 构建语句
stmt.Build("UPDATE", "SET", "WHERE", "RETURNING")
// 如果有 RETURNING 子句,添加 INTO 子句
if hasDefaultValues {
stmt.WriteString(" INTO ")
for idx, field := range schema.FieldsWithDefaultDBValue {
if idx > 0 {
stmt.WriteByte(',')
}
boundVars[field.Name] = len(stmt.Vars)
stmt.AddVar(stmt, sql.Out{Dest: reflect.New(field.FieldType).Interface()})
}
}
}
if !db.DryRun {
// 开启事务以确保更新的一致性
var tx *sql.Tx
var err error
var isTransaction bool = false
// 检查是否已经在一个事务中
if sqlTx, ok := stmt.ConnPool.(*sql.Tx); ok {
tx = sqlTx
isTransaction = true
} else if sqlDb, ok := stmt.ConnPool.(*sql.DB); ok {
tx, err = sqlDb.Begin()
if err != nil {
db.AddError(err)
return
}
defer func() {
if db.Error != nil && !isTransaction {
_ = tx.Rollback()
} else if !isTransaction {
_ = tx.Commit()
}
}()
} else {
db.AddError(fmt.Errorf("unsupported connection pool type"))
return
}
// 执行更新操作
var execConn *sql.Tx
if isTransaction {
execConn = tx // 已经在事务中,直接使用原事务
} else {
execConn = tx // 使用新创建的事务
}
result, err := execConn.ExecContext(stmt.Context, stmt.SQL.String(), stmt.Vars...)
if err != nil {
db.AddError(err)
// 如果不是在已有事务中,则回滚我们创建的事务
if !isTransaction {
_ = tx.Rollback()
}
return
}
db.RowsAffected, _ = result.RowsAffected()
// 处理 RETURNING 返回值
if hasDefaultValues {
updateTo := stmt.ReflectValue
switch updateTo.Kind() {
case reflect.Slice, reflect.Array:
// 对于切片或数组,只更新第一个元素
if updateTo.Len() > 0 {
updateTo = updateTo.Index(0)
}
}
// 绑定返回值到模型字段
funk.ForEach(
funk.Filter(schema.FieldsWithDefaultDBValue, func(field *gormSchema.Field) bool {
return funk.Contains(boundVars, field.Name)
}),
func(field *gormSchema.Field) {
switch updateTo.Kind() {
case reflect.Struct:
if err = field.Set(stmt.Context, updateTo, stmt.Vars[boundVars[field.Name]].(sql.Out).Dest); err != nil {
db.AddError(err)
}
case reflect.Map:
// 设置Map类型的值
mapValue := reflect.ValueOf(updateTo.Interface())
if mapValue.IsValid() && mapValue.Type().Key().Kind() == reflect.String {
keyValue := reflect.ValueOf(field.DBName)
destValue := reflect.ValueOf(stmt.Vars[boundVars[field.Name]].(sql.Out).Dest)
if destValue.Kind() == reflect.Ptr {
destValue = destValue.Elem()
}
mapValue.SetMapIndex(keyValue, destValue)
}
}
},
)
}
}
}