# GORM Oracle Driver 基于 [go-ora](https://github.com/sijms/go-ora) 实现的 GORM Oracle 数据库驱动,内置**版本感知**能力、**驱动抽象层**(go-ora / godror 可切换)、自定义回调体系(INSERT/UPDATE/DELETE/QUERY)与完整测试套件。 ## 特性 - **版本感知**:自动识别 Oracle 数据库版本,按版本适配 SQL 语法与类型 - 12c+:原生 `IDENTITY` 列、`OFFSET / FETCH` 分页、`DEFAULT .NEXTVAL` - 11g:自动创建序列 + `BEFORE INSERT` 触发器模拟自增,分页改写为 `ROWNUM` - 21c+:原生 `BOOLEAN` 列类型(更早版本用 `NUMBER(1)` 模拟) - 23ai:支持 `VECTOR` 类型(AI Vector Search) - 12c+(Extended):`VARCHAR2` 支持最大 32k 字节,超出自动降级为 `CLOB` - **驱动抽象层**(`driver_adapter`):统一 go-ora(纯 Go)与 godror(基于 ODPI-C)的差异,通过 `Config.DriverType` 一键切换 - **自定义回调体系**: - `INSERT ... RETURNING INTO`:支持默认值/自增字段回填,批量插入逐行执行保证一致性与返回值正确性 - `ON CONFLICT` → `MERGE INTO`:自动改写为 Oracle `MERGE` 语法 - 单行 `UPDATE/DELETE ... RETURNING INTO` - **WHERE 安全检查**:无有效 WHERE 条件时拒绝执行 `UPDATE`/`DELETE`,避免全表误操作(软删除条件除外) - 软删除支持 - **数据迁移**(Migrator): - 表/列/索引/约束的增删改查,列名大小写自动映射(Oracle 返回大写列名) - 从数据字典(`USER_TAB_COLUMNS`)获取真实数据类型,避免 AutoMigrate 误判触发多余 ALTER - Oracle 不支持原生 `ON UPDATE` 外键操作,自动生成触发器模拟 `CASCADE` / `SET NULL` - Oracle 保留字自动加引号 - 命名策略统一转换为大写 ## 环境要求 - Oracle 11g、12c 及以上(支持 21c、23ai 等新版本) - Golang 1.22+ - GORM v1.31.2+ - 底层驱动默认使用 [go-ora v2](https://github.com/sijms/go-ora)(纯 Go 实现,**无需**安装 [ODPI-C](https://oracle.github.io/odpi/doc/installation.html)) ## 安装 ```bash go get git.charlienet.top/go/oracle ``` ## 快速开始 ```go import ( "gorm.io/gorm" oracle "git.charlienet.top/go/oracle" ) func main() { dsn := "oracle://user:password@127.0.0.1:1521/XE?SSL=false" db, err := gorm.Open(oracle.Open(dsn), &gorm.Config{}) if err != nil { panic(err) } // do something... } ``` ### DSN 格式 DSN 使用 go-ora 的 URL 格式: ``` oracle://user:password@host:port/service?SSL=false&CONNECTION TIMEOUT=90&SOCKET TIMEOUT=90 ``` 常用参数: - `SSL`:是否启用 TLS 加密 - `CONNECTION TIMEOUT`:连接建立超时(秒),go-ora v2.9.0 起该参数只控制连接建立 - `SOCKET TIMEOUT`:socket 读写超时(秒),需要读超时保护时配合 `CONNECTION TIMEOUT` 一起设置 - `LANGUAGE` / `TERRITORY`:会话语言与地区,如 `LANGUAGE=SIMPLIFIED+CHINESE&TERRITORY=CHINA` ## 配置项 使用 `oracle.New(Config{})` 可获得更多配置能力: ```go import ( "gorm.io/gorm" oracle "git.charlienet.top/go/oracle" "git.charlienet.top/go/oracle/driver_adapter" ) db, err := gorm.Open(oracle.New(oracle.Config{ DSN: "oracle://user:password@127.0.0.1:1521/XE", DriverType: driver_adapter.DriverGoOra, // 驱动类型:go-ora(默认)或 godror SkipQuoteIdentifiers: false, // 是否跳过标识符引用 DBName: "SCOTT", // 指定 Schema(表名将带 Schema 前缀) // Conn: 传入已存在的 *sql.DB 连接池 }), &gorm.Config{}) ``` | 配置项 | 说明 | | --- | --- | | `DSN` | 连接串 | | `DriverType` | 底层驱动类型:`driver_adapter.DriverGoOra`(默认)/ `driver_adapter.DriverGodror` | | `SkipQuoteIdentifiers` | 为 `true` 时不引用标识符 | | `DBName` | 指定 Schema 名,开启后表名自动带上 `SCHEMA.TABLE` 前缀 | | `Conn` | 直接传入已建立的连接池(`*sql.DB`),此时忽略 DSN | | `DefaultStringSize` | 字符串字段未指定大小时的默认长度(默认 1024) | ### 驱动抽象层 `driver_adapter` 包统一了不同 Oracle 驱动的差异(输出参数、LOB、批量数据、多行 RETURNING 等能力探测),默认使用 go-ora(纯 Go,无需本地依赖)。如需切换到 godror: 1. 在代码中显式指定 `DriverType: driver_adapter.DriverGodror` 2. 引入 godror 依赖并使用 `-tags godror` 构建(`driver_adapter/godror.go` 受 `go:build godror` 约束) ## 版本适配行为 | 特性 | Oracle 11g | Oracle 12c+ | Oracle 21c+ | Oracle 23ai | | --- | --- | --- | --- | --- | | 自增主键 | 序列 + BEFORE INSERT 触发器 | `GENERATED BY DEFAULT AS IDENTITY` | 同 12c | 同 12c | | `DEFAULT .NEXTVAL` | 建表后创建触发器实现 | 原生 `DEFAULT` 子句 | 同 12c | 同 12c | | 分页(Limit/Offset) | `ROWNUM` 改写 | `OFFSET n ROWS FETCH NEXT n ROWS ONLY` | 同 12c | 同 12c | | `BOOLEAN` 列 | `NUMBER(1)` | `NUMBER(1)` | 原生 `BOOLEAN` | 原生 `BOOLEAN` | | 超长字符串(>4000) | `CLOB` | `VARCHAR2(n)`(32k) | 同 12c | 同 12c | | `VECTOR` 类型 | 不支持 | 不支持 | 不支持 | 支持 | > 版本通过连接后执行 `select version from product_component_version where rownum = 1` 自动探测,无需手动配置。 ## 常用操作示例 ```go // 创建(自动回填自增主键/默认值字段) user := User{Name: "Alice", Email: "alice@example.com"} db.Create(&user) // user.ID 自动回填 // 批量插入 users := []User{{Name: "A"}, {Name: "B"}} db.Create(&users) // Upsert(自动改写为 MERGE INTO) db.Clauses(clause.OnConflict{DoUpdates: clause.AssignmentColumns([]string{"name"})}). Create(&user) // 更新(无 WHERE 条件会被拒绝) db.Model(&User{}).Where("id = ?", 1).Update("name", "Bob") // 删除(无 WHERE 条件会被拒绝;带 DeletedAt 字段时自动软删除) db.Delete(&User{}, 1) db.Unscoped().Delete(&User{}, 1) // 强制物理删除 ``` ## 注意事项与已知限制 - `UPDATE` / `DELETE` 执行了 WHERE 安全检查:缺少有效条件(包括仅有软删除条件)时返回 `missing WHERE condition` 错误 - Oracle 仅支持单行 `RETURNING`:多行 `UPDATE` 不启用 RETURNING 回填;go-ora 不支持批量 `INSERT + RETURNING`,驱动采用逐行插入保证返回值正确 - 创建表时若关联关系声明了 `ON UPDATE CASCADE / SET NULL`,驱动会自动生成同名触发器;删除表时需先删除依赖的表或使用 `CASCADE CONSTRAINTS`(已内置) - 11g 下通过序列 + 触发器模拟自增时,触发器和序列按 `SEQ_` / `TRG_
` 命名,删除表会级联清理 - 布尔值在写入时转换为 `1/0`,读取时转换回 Go `bool` ## 测试 `tests/` 目录下为集成测试套件(创建、查询、更新、删除、软删除、Hook、迁移、序列、MERGE 等),需要真实的 Oracle 数据库: ```bash ORACLE_DSN="oracle://user:password@host:1521/service" go test ./tests/... ``` 驱动单元测试(无需数据库): ```bash go test ./... ``` ## License 见 [License](License)。