Files
oracle/README.md
T

172 lines
7.0 KiB
Markdown
Raw 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.
# 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 <seq>.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 <seq>.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_<table>` / `TRG_<table>` 命名,删除表会级联清理
- 布尔值在写入时转换为 `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)。