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:
@@ -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 18c(12.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 VARCHAR2(12.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 字节的 VARCHAR2(32k 特性);
|
||||
// 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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user