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:
@@ -3,6 +3,7 @@ package oracle
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/thoas/go-funk"
|
||||
@@ -96,6 +97,33 @@ func Create(db *gorm.DB) {
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
for idx, vals := range values.Values {
|
||||
// HACK HACK: replace values one by one, assuming its value layout will be the same all the time, i.e. aligned
|
||||
for idx, val := range vals {
|
||||
@@ -113,13 +141,18 @@ func Create(db *gorm.DB) {
|
||||
// and then we insert each row one by one then put the returning values back (i.e. last return id => smart insert)
|
||||
// we keep track of the index so that the sub-reflected value is also correct
|
||||
|
||||
// BIG BUG: what if any of the transactions failed? some result might already be inserted that oracle is so
|
||||
// sneaky that some transaction inserts will exceed the buffer and so will be pushed at unknown point,
|
||||
// resulting in dangling row entries, so we might need to delete them if an error happens
|
||||
var execConn *sql.Tx
|
||||
if isTransaction {
|
||||
execConn = tx // 已经在事务中,直接使用原事务
|
||||
} else {
|
||||
execConn = tx // 使用新创建的事务
|
||||
}
|
||||
|
||||
switch result, err := stmt.ConnPool.ExecContext(stmt.Context, stmt.SQL.String(), stmt.Vars...); err {
|
||||
switch result, err := execConn.ExecContext(stmt.Context, stmt.SQL.String(), stmt.Vars...); err {
|
||||
case nil: // success
|
||||
db.RowsAffected, _ = result.RowsAffected()
|
||||
// 批量插入时累加每个单行插入的受影响行数
|
||||
rowsAffected, _ := result.RowsAffected()
|
||||
db.RowsAffected += rowsAffected
|
||||
|
||||
insertTo := stmt.ReflectValue
|
||||
switch insertTo.Kind() {
|
||||
@@ -140,13 +173,27 @@ func Create(db *gorm.DB) {
|
||||
db.AddError(err)
|
||||
}
|
||||
case reflect.Map:
|
||||
// todo 设置id的值
|
||||
// 设置Map类型的ID值
|
||||
mapValue := reflect.ValueOf(insertTo.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)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
default: // failure
|
||||
db.AddError(err)
|
||||
// 如果不是在已有事务中,则回滚我们创建的事务
|
||||
if !isTransaction {
|
||||
_ = tx.Rollback()
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user