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
+114 -15
View File
@@ -19,17 +19,65 @@ import (
"gorm.io/gorm/logger"
"gorm.io/gorm/migrator"
"gorm.io/gorm/schema"
"git.charlienet.top/go/oracle/driver_adapter"
)
const RowNumberAliasForOracle11 = "ROW_NUM"
// Oracle 版本主版本号常量(对应各版本引入的数据库特性)
const (
OracleVersion10 = 10 // Oracle 10g
OracleVersion11 = 11 // Oracle 11g(不含 IDENTITY 列、OFFSET/FETCH 分页)
OracleVersion12 = 12 // Oracle 12c(引入 IDENTITY 列、OFFSET/FETCH 分页;12.1 起支持 Extended 32k VARCHAR2
OracleVersion18 = 18 // Oracle 18c12.2 的再版)
OracleVersion19 = 19 // Oracle 19c
OracleVersion21 = 21 // Oracle 21c(引入原生 BOOLEAN 列类型)
OracleVersion23 = 23 // Oracle 23ai(引入 VECTOR 类型)
)
// oracleMajor 返回数据库版本的主版本号;解析失败返回 0。
// 支持格式如 "11.2.0.4.0"、"19.0.0.0.0"、"23.0.0.0.0"。
func oracleMajor(dbVer string) int {
major, _ := strconv.Atoi(strings.Split(dbVer, ".")[0])
return major
}
// supportsIdentity 是否支持 IDENTITY 列(12c+ 支持 GENERATED ... AS IDENTITY
// 11g 及以下需用序列 + BEFORE INSERT 触发器模拟自增)
func supportsIdentity(dbVer string) bool { return oracleMajor(dbVer) >= OracleVersion12 }
// supportsFetchOffset 是否支持 OFFSET/FETCH 分页语法(12c+ 引入;
// 11g 需改写为 ROWNUM 分页)
func supportsFetchOffset(dbVer string) bool { return oracleMajor(dbVer) >= OracleVersion12 }
// supportsNativeBoolean 是否支持原生 BOOLEAN 列类型(21c+ 引入;
// 更早版本需用 NUMBER(1) 模拟)
func supportsNativeBoolean(dbVer string) bool { return oracleMajor(dbVer) >= OracleVersion21 }
// supportsExtendedString 是否支持 Extended 32k VARCHAR212.2+ 默认开启;
// 12.1 需 MAX_STRING_SIZE=EXTENDED)。保守判定:主版本 >= 12 视为可能支持,
// 具体是否生效依赖数据库参数。
func supportsExtendedString(dbVer string) bool { return oracleMajor(dbVer) >= OracleVersion12 }
// supportsVector 是否支持 VECTOR 类型(23ai 引入,用于 AI Vector Search
func supportsVector(dbVer string) bool { return oracleMajor(dbVer) >= OracleVersion23 }
// isOracle11g 判断当前数据库是否低于 12c(11g 及以下不支持 IDENTITY 列)。
// 保留该函数以兼容既有调用,内部委托 supportsIdentity 取反。
func isOracle11g(dbVer string) bool {
return !supportsIdentity(dbVer)
}
type Config struct {
DriverName string
DSN string
Conn gorm.ConnPool //*sql.DB
DefaultStringSize uint
DBName string
DBVer string
DriverName string
DSN string
Conn gorm.ConnPool //*sql.DB
DefaultStringSize uint
DBName string
DBVer string
DriverType driver_adapter.DriverType // 新增:驱动类型(go-ora 或 godror
SkipQuoteIdentifiers bool // 新增:是否跳过标识符引用
}
type Dialector struct {
@@ -90,6 +138,21 @@ func (d Dialector) Initialize(db *gorm.DB) (err error) {
return
}
// 注册 Update 回调
if err = db.Callback().Update().Replace("gorm:update", Update); err != nil {
return
}
// 注册 Delete 回调
if err = db.Callback().Delete().Replace("gorm:delete", Delete); err != nil {
return
}
// 注册 Query 回调
if err = db.Callback().Query().Replace("gorm:query", Query); err != nil {
return
}
for k, v := range d.ClauseBuilders() {
db.ClauseBuilders[k] = v
}
@@ -256,12 +319,16 @@ func (d Dialector) BindVarTo(writer clause.Writer, stmt *gorm.Statement, v inter
}
func (d Dialector) QuoteTo(writer clause.Writer, str string) {
if d.SkipQuoteIdentifiers {
writer.WriteString(str)
return
}
if str != "" && IsReservedWord(str) {
writer.WriteByte('"')
writer.WriteString(str)
writer.WriteByte('"')
} else {
writer.WriteString(str)
}
}
@@ -288,15 +355,24 @@ func (d Dialector) DataTypeOf(field *schema.Field) string {
var sqlType string
switch field.DataType {
case schema.Bool, schema.Int, schema.Uint, schema.Float:
case schema.Bool:
// Oracle 21c+ 支持原生 BOOLEAN 列;更早版本用 NUMBER(1) 模拟
if supportsNativeBoolean(d.DBVer) {
sqlType = "BOOLEAN"
} else {
sqlType = "NUMBER(1)"
}
case schema.Int, schema.Uint:
sqlType = "INTEGER"
switch {
case field.DataType == schema.Float:
sqlType = "FLOAT"
case field.Size <= 8:
if field.Size <= 8 {
sqlType = "SMALLINT"
}
// Oracle 12c+ 支持 IDENTITY 列;Oracle 11g 需要在迁移时创建序列 + 触发器
if field.AutoIncrement && supportsIdentity(d.DBVer) {
sqlType += " GENERATED BY DEFAULT AS IDENTITY"
}
case schema.Float:
sqlType = "FLOAT"
if val, ok := field.TagSettings["AUTOINCREMENT"]; ok && utils.CheckTruth(val) {
sqlType += " GENERATED BY DEFAULT AS IDENTITY"
@@ -317,14 +393,30 @@ func (d Dialector) DataTypeOf(field *schema.Field) string {
}
}
if size >= 2000 {
// Oracle 12c+Extended)支持最长 32767 字节的 VARCHAR232k 特性);
// 11g 及未开启 Extended 的库超过 4000 必须用 CLOB。
// 保守策略:
// - size 在 2000~4000 之间维持 CLOB 不变(保持历史行为)
// - size > 4000 且版本 >= 12 → VARCHAR2(size)(利用 32k 特性)
// - size > 4000 且 11g → CLOB(保持现状)
if size > 4000 {
if supportsExtendedString(d.DBVer) {
sqlType = fmt.Sprintf("VARCHAR2(%d)", size)
} else {
sqlType = "CLOB"
}
} else if size >= 2000 {
sqlType = "CLOB"
} else {
sqlType = fmt.Sprintf("VARCHAR2(%d)", size)
}
case schema.Time:
sqlType = "TIMESTAMP WITH TIME ZONE"
if field.Precision > 0 {
sqlType = fmt.Sprintf("TIMESTAMP(%d) WITH TIME ZONE", field.Precision)
} else {
sqlType = "TIMESTAMP WITH TIME ZONE"
}
case schema.Bytes:
sqlType = "BLOB"
@@ -353,3 +445,10 @@ func (d Dialector) RollbackTo(tx *gorm.DB, name string) error {
tx.Exec("ROLLBACK TO SAVEPOINT " + name)
return tx.Error
}
func (d Dialector) GetAdapter() driver_adapter.Adapter {
if d.DriverType == "" {
d.DriverType = driver_adapter.DriverGoOra
}
return driver_adapter.Get(d.DriverType)
}