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,322 @@
|
||||
package oracle
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/thoas/go-funk"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
gormSchema "gorm.io/gorm/schema"
|
||||
)
|
||||
|
||||
func Delete(db *gorm.DB) {
|
||||
stmt := db.Statement
|
||||
if stmt == nil {
|
||||
return
|
||||
}
|
||||
schema := stmt.Schema
|
||||
if schema == nil {
|
||||
return
|
||||
}
|
||||
|
||||
boundVars := make(map[string]int)
|
||||
|
||||
// 注入主键 WHERE 条件(GORM 默认回调会做这一步)
|
||||
addPrimaryKeyWhere(stmt, schema)
|
||||
|
||||
// 1. WHERE 安全检查(最重要)
|
||||
if where, ok := stmt.Clauses["WHERE"].Expression.(clause.Where); ok {
|
||||
if checkMissingWhereConditions(where.Exprs, schema) {
|
||||
db.AddError(fmt.Errorf("missing WHERE condition in DELETE"))
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// 没有 WHERE 子句
|
||||
db.AddError(fmt.Errorf("missing WHERE condition in DELETE"))
|
||||
return
|
||||
}
|
||||
|
||||
// 2. 检查软删除
|
||||
var softDeleteField *gormSchema.Field
|
||||
for _, field := range schema.Fields {
|
||||
if (field.DBName == "deleted_at" || field.Name == "DeletedAt") &&
|
||||
field.GORMDataType == "time" {
|
||||
softDeleteField = field
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if softDeleteField != nil && !stmt.Unscoped {
|
||||
// 软删除:转换为 UPDATE
|
||||
performSoftDelete(db, softDeleteField, boundVars)
|
||||
} else {
|
||||
// 硬删除:执行 DELETE(Unscoped 时强制硬删除)
|
||||
performHardDelete(db, boundVars)
|
||||
}
|
||||
}
|
||||
|
||||
func performSoftDelete(db *gorm.DB, field *gormSchema.Field, boundVars map[string]int) {
|
||||
stmt := db.Statement
|
||||
schema := stmt.Schema
|
||||
|
||||
hasDefaultValues := len(schema.FieldsWithDefaultDBValue) > 0
|
||||
|
||||
if !stmt.Unscoped {
|
||||
for _, c := range schema.DeleteClauses {
|
||||
stmt.AddClause(c)
|
||||
}
|
||||
}
|
||||
|
||||
if stmt.SQL.String() == "" {
|
||||
// 构建 UPDATE 语句而不是 DELETE
|
||||
stmt.AddClauseIfNotExists(clause.Update{Table: clause.Table{Name: stmt.Schema.Table}})
|
||||
|
||||
// 构建 SET 子句,设置 deleted_at 为当前时间
|
||||
now := time.Now()
|
||||
convertedNow := convertValue(now, field)
|
||||
set := clause.Set{clause.Assignment{Column: clause.Column{Name: field.DBName}, Value: convertedNow}}
|
||||
stmt.AddClause(set)
|
||||
|
||||
// 添加 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)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func performHardDelete(db *gorm.DB, boundVars map[string]int) {
|
||||
stmt := db.Statement
|
||||
schema := stmt.Schema
|
||||
|
||||
hasDefaultValues := len(schema.FieldsWithDefaultDBValue) > 0
|
||||
|
||||
if !stmt.Unscoped {
|
||||
for _, c := range schema.DeleteClauses {
|
||||
stmt.AddClause(c)
|
||||
}
|
||||
}
|
||||
|
||||
if stmt.SQL.String() == "" {
|
||||
// 构建 DELETE 语句
|
||||
stmt.AddClauseIfNotExists(clause.Delete{})
|
||||
stmt.AddClauseIfNotExists(clause.From{Tables: []clause.Table{{Name: stmt.Schema.Table}}})
|
||||
|
||||
// 添加 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("DELETE", "FROM", "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 {
|
||||
deleteTo := stmt.ReflectValue
|
||||
switch deleteTo.Kind() {
|
||||
case reflect.Slice, reflect.Array:
|
||||
// 对于切片或数组,只处理第一个元素
|
||||
if deleteTo.Len() > 0 {
|
||||
deleteTo = deleteTo.Index(0)
|
||||
}
|
||||
}
|
||||
|
||||
// 绑定返回值到模型字段
|
||||
funk.ForEach(
|
||||
funk.Filter(schema.FieldsWithDefaultDBValue, func(field *gormSchema.Field) bool {
|
||||
return funk.Contains(boundVars, field.Name)
|
||||
}),
|
||||
func(field *gormSchema.Field) {
|
||||
switch deleteTo.Kind() {
|
||||
case reflect.Struct:
|
||||
if err = field.Set(stmt.Context, deleteTo, stmt.Vars[boundVars[field.Name]].(sql.Out).Dest); err != nil {
|
||||
db.AddError(err)
|
||||
}
|
||||
case reflect.Map:
|
||||
// 设置Map类型的值
|
||||
mapValue := reflect.ValueOf(deleteTo.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)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user