diff --git a/README.md b/README.md index b78f967..3d1ce80 100644 --- a/README.md +++ b/README.md @@ -1,40 +1,171 @@ # GORM Oracle Driver +基于 [go-ora](https://github.com/sijms/go-ora) 实现的 GORM Oracle 数据库驱动,内置**版本感知**能力、**驱动抽象层**(go-ora / godror 可切换)、自定义回调体系(INSERT/UPDATE/DELETE/QUERY)与完整测试套件。 -## Description +## 特性 -GORM Oracle driver for connect Oracle DB and Manage Oracle DB, Based on [CengSin/oracle](https://github.com/CengSin/oracle) -,not recommended for use in a production environment +- **版本感知**:自动识别 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 保留字自动加引号 +- 命名策略统一转换为大写 -## Required dependency Install +## 环境要求 -- Oracle 12C+ -- Oracle 11g -- Golang 1.13+ -- see [ODPI-C Installation.](https://oracle.github.io/odpi/doc/installation.html) -- gorm 1.24.0+ +- 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)) + +## 安装 -## Quick Start -### how to install ```bash -go get github.com/dzwvip/oracle +go get git.charlienet.top/go/oracle ``` -### usage + +## 快速开始 ```go import ( - "fmt" - "github.com/dzwvip/oracle" - "gorm.io/gorm" - "log" + "gorm.io/gorm" + + oracle "git.charlienet.top/go/oracle" ) func main() { - db, err := gorm.Open(oracle.Open("system/oracle@127.0.0.1:1521/XE"), &gorm.Config{}) + 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 error or log error info - } - - // do somethings + 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)。