contrib/drivers/gaussdb: Save/Replace/InsertIgnore 对所有非文本列失败(MERGE 的 USING 分支缺类型信息)
问题
gaussdb 驱动的 Save() / Replace() / InsertIgnore() 都走 MERGE
(gaussdb_do_insert.go 的 doMergeInsert),它把每一列写进 USING 子查询:
MERGE INTO "t" AS T1
USING (SELECT 1 AS "id", 'n1' AS "nickname", '{1.5,2.5}' AS "val") AS T2
ON (T1."id" = T2."id")
WHEN MATCHED THEN UPDATE SET T1."val" = T2."val"这个子查询没有任何类型上下文,PostgreSQL / openGauss 对其中的值一律按 text
处理,再赋回强类型列就会失败。
实测(master 41baf1e28,openGauss 7.0.0-RC1):
val numeric[] = '{1.5,2.5}' -> pq: column "val" is of type numeric[] but expression is of type text
val jsonb = '{"a":1}' -> pq: column "val" is of type jsonb but expression is of type text
val boolean = 'true' -> pq: column "val" is of type boolean but expression is of type text
val uuid = '550e8400-e29b-41d4-a716-44665544' -> pq: column "val" is of type uuid but expression is of type text
val int4 = 42 -> 通过(整数字面量恰好被推断为 int)复现
// 建表:id bigserial PRIMARY KEY, nickname varchar(45), val numeric[]
db.Model(table).Data(g.Map{"id": 1, "nickname": "n1", "val": "{1.5,2.5}"}).Insert() // OK
one, _ := db.Model(table).Where("id", 1).One()
data := one.Map()
data["nickname"] = "n1-updated"
_, err := db.Model(table).Data(data).Save() // 失败Insert() 不受影响,它走普通 INSERT,值是绑定参数;只有 MERGE 这条路径有问题。
因此任何「读出整行 → 改一个字段 → 整行写回」的用法,只要表里有一个非文本列就会挂。
与 #4840 的关系
PR #4840 修了其中「值为 NULL」的一支:给 NULL 占位符补上 ::<列类型> 的 cast。
但如上所示,非 NULL 的值同样失败,两者是同一个根因,只是 #4840 的范围没覆盖到。
可能的修法
对 USING 分支的每一列都套上真实列类型的 cast,而不只是 NULL 列。
类型表达式建议直接取自 format_type(a.atttypid, a.atttypmod),它给出的就是可
直接用于 cast 的完整形式,并且会在需要时自动加引号:
| 列定义 | gf 现在拼的 TableField.Type |
format_type |
|---|---|---|
int4 |
int4(32)(cast 非法) |
integer |
varchar(45) |
varchar(45) |
character varying(45) |
numeric[] |
_numeric |
numeric[] |
"MyEnum" |
MyEnum(未加引号会被折成小写) |
"MyEnum" |
timestamp |
timestamp |
timestamp without time zone |
gaussdb_table_fields.go 目前把 COALESCE(character_maximum_length, numeric_precision, -1) 拼回类型名,int4 因此变成 int4(32) —— 这是 gf 自己
造出来的形式,PG 不接受用它做 cast。改用 format_type 可以连带免掉现在剥离
类型修饰符的处理。
代价是 gdb.TableField 没有存放该表达式的字段,需要驱动侧自行查询并缓存。
环境
- master (41baf1e28)
- openGauss 7.0.0-RC1.B023(CI 镜像)
Source: gogf/gf