Files
charlie 9154afab6b 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 环境变量注入
2026-08-08 10:36:57 +08:00

322 lines
8.4 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 {
// 硬删除:执行 DELETEUnscoped 时强制硬删除)
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)
}
}
},
)
}
}
}