参考前文,Ubuntu 安装 Oracle Instant Client, 并测试 Golang Gorm 读取 Oracle 数据库 搞定了 Oracle Instant Client 和 GORM 连接 Oracle 数据库的环境后,我以为马上就能开始写逻辑了。但是,测试了一下,发现了一个严重的问题。GORM 默认的实现方案,并不支持低版本 Oracle 的分页语法,准确的说是不支持 Oracle 11g 及更低版本。
FETCH NEXT xx ROWS ONLY
在 admin 管理后台,实现分页查询是非常常见的需求。但是,如果使用 GORM 的分页语法:
db.Select("*").
Limit(limit).
Offset((page - 1) * limit).
Find(&items)
会报错:
dpiStmt_execute: ORA-00933: SQL command not properly ended
[1.344ms] [rows:0] SELECT * FROM "xxx"."some_table" ORDER BY id desc FETCH NEXT 20 ROWS ONLY
FETCH NEXT 20 ROWS ONLY 是 Oracle 12c 及更高版本才引入的语法。Oracle 11g,无法识别这段代码,从而报出 ORA-00933。
我尝试了让 AI 写一个分页查询的 SQL,我发现太麻烦了,而且可读性也很差。我感觉还是得用 ORM 框架来做。
纯 Golang 实现的 godoes/gorm-oracle
https://github.com/godoes/gorm-oracle
这个库文档里明确说明了支持 Oracle 11g。可以看到配置里有个专门的配置:
// RowNumberAliasForOracle11 is the alias for ROW_NUMBER() in Oracle 11g,
// defaulting to ROW_NUM
RowNumberAliasForOracle11: "ROW_NUM",
看了一下 github 上的代码实现:
https://github.com/godoes/gorm-oracle/blob/main/oracle.go#L290
里面的 RewriteLimit11 方法,重写了 GORM 的分页语法,改成了 Oracle 11g 支持的分页语法。
// RewriteLimit11 rewrite the LIMIT clause in the query to accommodate pagination requirements for Oracle 11g and lower database versions
//
// # Limit and Offset
//
// SELECT * FROM (SELECT T.*, ROW_NUMBER() OVER (ORDER BY column) AS ROW_NUM FROM table_name T)
// WHERE ROW_NUM BETWEEN offset+1 AND offset+limit
//
// # Only Limit
//
// SELECT * FROM table_name WHERE ROWNUM <= limit ORDER BY column
//
// # Only Offset
//
// SELECT * FROM table_name WHERE ROWNUM > offset ORDER BY column
func (d Dialector) RewriteLimit11(c clause.Clause, builder clause.Builder) {
limit, ok := c.Expression.(clause.Limit)
if !ok {
return
}
offsetRows := limit.Offset
hasOffset := offsetRows > 0
limitRows, hasLimit := d.getLimitRows(limit)
if !hasOffset && !hasLimit {
return
}
var stmt *gorm.Statement
if stmt, ok = builder.(*gorm.Statement); !ok {
return
}
if hasLimit && hasOffset {
// 使用 ROW_NUMBER() 和子查询实现分页查询
if d.RowNumberAliasForOracle11 == "" {
d.RowNumberAliasForOracle11 = "ROW_NUM"
}
subQuerySQL := fmt.Sprintf(
"SELECT * FROM (SELECT T.*, ROW_NUMBER() OVER (ORDER BY %s) AS %s FROM (%s) T) WHERE %s BETWEEN %d AND %d",
d.getOrderByColumns(stmt),
d.RowNumberAliasForOracle11,
strings.TrimSpace(stmt.SQL.String()),
d.RowNumberAliasForOracle11,
offsetRows+1,
offsetRows+limitRows,
)
stmt.SQL.Reset()
stmt.SQL.WriteString(subQuerySQL)
} else if hasLimit {
// 只有 Limit 的情况
d.rewriteRownumStmt(stmt, builder, " <= ", limitRows)
} else {
// 只有 Offset 的情况
d.rewriteRownumStmt(stmt, builder, " > ", offsetRows)
}
}
测试了一下,果然可以正常分页查询了!
而 gorm 默认的 Oracle 驱动,是不支持 Oracle 11g 的分页语法的:
github.com/oracle-samples/gorm-oracle
从这个山寨的名字就感觉不太靠谱。
大小写问题
在 Oracle 中,当你给标识符(表名、列名、Schema 名)加上双引号时,Oracle 会变得严格区分大小写。因为 GORM 默认会给表名和列名加上双引号,所以你会遇到下面的问题:
dpiStmt_execute: ORA-00942: table or view does not exist
[14.539ms] [rows:0] SELECT count(*) FROM "some_schema"."some_table"
解决方法就是,把表名和 Schema 名都改成大写。
Oracle 11g 是哪年发布的?
Oracle 11g 于 2007 年发布,我也是服了,公司买这套 MES 系统,也是祖传架构了。。。
关于作者 🌱
我是来自山东烟台的一名开发者,有感兴趣的话题,或者软件开发需求,欢迎加微信 zhongwei 聊聊,或者关注我的个人公众号“大象工具”, 查看更多联系方式