332 lines
19 KiB
Markdown
332 lines
19 KiB
Markdown
|
|
---
|
|||
|
|
name: JSON Path 查询支持
|
|||
|
|
overview: 在现有 Dialect 接口和 WHERE 条件解析引擎上扩展,使 MySQL 和 SQLite 支持类似 PgSQL 的 `data["a"]["b"]["c"]` JSON 路径查询与操作语法,所有三种数据库共享统一的上层 API。
|
|||
|
|
todos:
|
|||
|
|
- id: dialect-json-interface
|
|||
|
|
content: 在 db/dialect.go 的 Dialect 接口新增 JSONExtract / JSONSet / JSONContains / JSONArrayLength 四个方法
|
|||
|
|
status: pending
|
|||
|
|
- id: dialect-json-mysql
|
|||
|
|
content: 实现 MySQLDialect 的三个 JSON 方法,处理 JSON_EXTRACT / JSON_SET / JSON_UNQUOTE 等
|
|||
|
|
status: pending
|
|||
|
|
- id: dialect-json-sqlite
|
|||
|
|
content: 实现 SQLiteDialect 的三个 JSON 方法,使用小写 json_extract / json_set
|
|||
|
|
status: pending
|
|||
|
|
- id: dialect-json-pgsql
|
|||
|
|
content: "实现 PostgreSQLDialect 的三个 JSON 方法,使用 -> / #>> / jsonb_set 语法"
|
|||
|
|
status: pending
|
|||
|
|
- id: where-json-parse
|
|||
|
|
content: 新增 parseJSONPathKey()/parseJSONPathFromName() 工具函数,同时支持 col["key"] 和 col['key'] 两种引号格式,以及数字索引 [0],解析出列名、[]string 路径键、操作符后缀
|
|||
|
|
status: pending
|
|||
|
|
- id: where-json-cond
|
|||
|
|
content: 在 db/where.go 的 varCond() 开头最优先调用 parseJSONPathKey,分发到 jsonPathCond();jsonPathCond 对现有操作符复用比较逻辑,新增 [?]/[!?]/[@]/[!@]/[len]/[len>] 等分支
|
|||
|
|
status: pending
|
|||
|
|
- id: crud-json-update
|
|||
|
|
content: 在 db/crud.go 的 Update() SET 构建循环中检测 JSON 路径列名生成 JSONSet 表达式,在 Select() 的 Slice 字段循环中检测 JSON 路径生成 JSONExtract 表达式(修复两处 ProcessColumnNoPrefix 误处理)
|
|||
|
|
status: pending
|
|||
|
|
isProject: false
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
# MySQL/SQLite JSON 路径查询支持方案
|
|||
|
|
|
|||
|
|
## 背景与现状
|
|||
|
|
|
|||
|
|
现有 ORM 已有 `Dialect` 接口抽象三种数据库差异,`where.go` 用 `[operator]` 后缀语法解析条件。扩展的核心思路:**在解析 key 时识别 JSON 路径记法,委托给 Dialect 生成对应函数调用**。
|
|||
|
|
|
|||
|
|
## 三种数据库 JSON 能力深度对比
|
|||
|
|
|
|||
|
|
### 1. JSON 值提取(WHERE 比较场景)
|
|||
|
|
|
|||
|
|
|
|||
|
|
| | MySQL 5.7+ | SQLite 3.9+ | PostgreSQL |
|
|||
|
|
| ---------- | ------------------------------------------------------- | ------------------------------ | ----------------- |
|
|||
|
|
| 单层提取(返回文本) | `JSON_UNQUOTE(JSON_EXTRACT(col,'$.a'))` 或 `col->>'$.a'` | `json_extract(col,'$.a')` | `col->>'a'` |
|
|||
|
|
| 多层嵌套(返回文本) | `JSON_UNQUOTE(JSON_EXTRACT(col,'$.a.b'))` | `json_extract(col,'$.a.b')` | `col#>>'{a,b}'` |
|
|||
|
|
| 数组索引 | `JSON_EXTRACT(col,'$.arr[0]')` | `json_extract(col,'$.arr[0]')` | `col#>>'{arr,0}'` |
|
|||
|
|
| 路径格式 | `$.a.b` | `$.a.b`(与MySQL相同) | `{a,b}`(#>> 方式) |
|
|||
|
|
|
|||
|
|
|
|||
|
|
**关键差异**:MySQL 的 `JSON_EXTRACT` 对字符串返回值带外层双引号(如 `"北京"` 而非 `北京`),与普通字符串 `= ?` 比较会失败,**必须包一层 `JSON_UNQUOTE()`**。SQLite 和 PostgreSQL(`->>`/`#>>`)则直接返回文本,无此问题。
|
|||
|
|
|
|||
|
|
### 2. JSON 局部更新(UPDATE SET 场景)
|
|||
|
|
|
|||
|
|
|
|||
|
|
| | MySQL 5.7+ | SQLite 3.9+ | PostgreSQL |
|
|||
|
|
| ----- | ------------------------- | ------------------------- | ------------------------------------------ |
|
|||
|
|
| 语法 | `JSON_SET(col,'$.a.b',?)` | `json_set(col,'$.a.b',?)` | `jsonb_set(col,'{a,b}',to_jsonb(?::text))` |
|
|||
|
|
| 路径格式 | `$.a.b` | `$.a.b`(与MySQL相同) | `'{a,b}'` 数组字面量 |
|
|||
|
|
| 值的类型 | 直接绑定参数 | 直接绑定参数 | 字符串需 `to_jsonb(?::text)`,数字可 `?::jsonb` |
|
|||
|
|
| 列类型限制 | 无(TEXT 也行) | 无(TEXT 列) | **必须是 `jsonb` 列**,`json` 列无 `jsonb_set` |
|
|||
|
|
|
|||
|
|
|
|||
|
|
**关键差异**:MySQL 与 SQLite 的语法几乎完全一致(函数名大小写不同而已)。PostgreSQL 只有 `jsonb_set`,要求列类型为 `jsonb`,且字符串值必须转为 JSON 格式(`to_jsonb(?::text)` 或 `'"value"'::jsonb`)。
|
|||
|
|
|
|||
|
|
### 3. JSON 数组包含(`[@]` 操作符)
|
|||
|
|
|
|||
|
|
|
|||
|
|
| | MySQL 5.7+ | SQLite 3.9+ | PostgreSQL |
|
|||
|
|
| ------ | --------------------------------------------------------- | ------------------------------------------------------------- | ------------------------------------ |
|
|||
|
|
| SQL 形式 | `JSON_CONTAINS(JSON_EXTRACT(col,'$.path'),JSON_QUOTE(?))` | `EXISTS(SELECT 1 FROM json_each(col,'$.path') WHERE value=?)` | `col#>'{path}' @> to_jsonb(?::text)` |
|
|||
|
|
| 结构类型 | 函数调用(内联) | **EXISTS 子查询**(结构不同) | 中缀操作符(内联) |
|
|||
|
|
|
|||
|
|
|
|||
|
|
**最大兼容难点**:SQLite 没有内联的包含函数,必须生成 EXISTS 子查询,SQL 结构与另外两种完全不同。`JSONContains()` 方法在 SQLite 下返回的是一段 `EXISTS(...)` 表达式。
|
|||
|
|
|
|||
|
|
### 4. 路径格式统一转换规则
|
|||
|
|
|
|||
|
|
ORM 内部统一使用 `[]string{"a","b","c"}` 表示路径键(数字索引用字符串 `"0"` 表示),各 Dialect 自行转换:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
内部路径: ["address","city"]
|
|||
|
|
→ MySQL/SQLite: $.address.city
|
|||
|
|
→ PostgreSQL: {address,city}
|
|||
|
|
|
|||
|
|
内部路径: ["tags","0"](数组索引)
|
|||
|
|
→ MySQL/SQLite: $.tags[0]
|
|||
|
|
→ PostgreSQL: {tags,0}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 统一 API 语法设计
|
|||
|
|
|
|||
|
|
### Key 写法(单引号/双引号两种都支持)
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
// 双引号风格(JSON 标准,推荐,需用反引号字符串)
|
|||
|
|
common.Map{ `profile["addr"]["city"]`: "北京" }
|
|||
|
|
|
|||
|
|
// 单引号风格(普通字符串也能写)
|
|||
|
|
common.Map{ "profile['addr']['city']": "北京" }
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
两种写法解析结果完全一致,内部统一转为 `$.addr.city`。
|
|||
|
|
|
|||
|
|
### 操作符设计:复用现有 + 新增 JSON 专属
|
|||
|
|
|
|||
|
|
**所有现有操作符均可接在 JSON 路径后直接使用**(`=` / `>` / `<` / `>=` / `<=` / `!=` / `LIKE` / `IN` / `NOT IN` / `BETWEEN` / `IS NULL`):
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
db.Select("user", common.Map{
|
|||
|
|
`profile["age"]`: 18, // = 18
|
|||
|
|
`profile["age"][>]`: 18, // > 18
|
|||
|
|
`profile["age"][<>]`: []int{18, 30}, // BETWEEN
|
|||
|
|
`profile["name"][~]`: "张", // LIKE %张%
|
|||
|
|
`profile["tags"][0]`: "vip", // 数组第一个元素 = vip
|
|||
|
|
`profile["addr"]["city"]`: "北京", // 多级嵌套
|
|||
|
|
`profile["score"]`: nil, // IS NULL
|
|||
|
|
})
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**新增 JSON 专属操作符**(现有体系无对应):
|
|||
|
|
|
|||
|
|
|
|||
|
|
| 操作符 | 含义 | MySQL | SQLite | PgSQL |
|
|||
|
|
| --------- | ----------- | -------------------------------- | ------------------------ | ------------------------- |
|
|||
|
|
| `[?]` | 路径存在(非NULL) | `IS NOT NULL` | `IS NOT NULL` | `? ?` |
|
|||
|
|
| `[!?]` | 路径不存在 | `IS NULL` | `IS NULL` | `NOT (? ?)` |
|
|||
|
|
| `[@]` | 数组/对象包含某值 | `JSON_CONTAINS(col,?,'$.path')` | `json_each` 子查询 | `col @> ?::jsonb` |
|
|||
|
|
| `[!@]` | 不包含 | `NOT JSON_CONTAINS(...)` | 子查询 NOT EXISTS | `NOT (col @> ?)` |
|
|||
|
|
| `[len]` | 数组长度 = ? | `JSON_LENGTH(JSON_EXTRACT(...))` | `json_array_length(...)` | `jsonb_array_length(...)` |
|
|||
|
|
| `[len>]` | 数组长度 > ? | 同上 | 同上 | 同上 |
|
|||
|
|
| `[len<]` | 数组长度 < ? | 同上 | 同上 | 同上 |
|
|||
|
|
| `[len>=]` | 数组长度 >= ? | 同上 | 同上 | 同上 |
|
|||
|
|
| `[len<=]` | 数组长度 <= ? | 同上 | 同上 | 同上 |
|
|||
|
|
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
// 新操作符使用示例
|
|||
|
|
db.Select("user", common.Map{
|
|||
|
|
`profile["vip"][?]`: nil, // vip 字段存在
|
|||
|
|
`profile["tags"][@]`: "admin", // tags 数组包含 "admin"
|
|||
|
|
`profile["friends"][len>]`: 5, // friends 数组长度 > 5
|
|||
|
|
`profile["items"][len]`: 3, // items 数组长度 = 3
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
// UPDATE 局部更新 JSON 字段
|
|||
|
|
db.Update("user", common.Map{
|
|||
|
|
`profile["age"]`: 20,
|
|||
|
|
`profile["addr"]["city"]`: "上海",
|
|||
|
|
}, common.Map{"id": 1})
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 与现有代码的兼容性分析
|
|||
|
|
|
|||
|
|
### 三处具体冲突点
|
|||
|
|
|
|||
|
|
**冲突 1 — WHERE `varCond` 的 `[...]` 分支**(`[db/where.go:225](db/where.go)`)
|
|||
|
|
|
|||
|
|
现有逻辑:只要 key 含 `[` 且末尾是 `]` 就进分支,取末尾 3/4 字符匹配已知操作符。
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
profile["age"] → 末尾3字符 e"] → 无匹配 → handleDefaultCondition
|
|||
|
|
→ ProcessColumn("profile[\"age\"]") → 被加引号 → WRONG SQL
|
|||
|
|
|
|||
|
|
profile["age"][>] → 末尾3字符 [>] → MATCH,strips → ProcessColumn("profile[\"age\"]")
|
|||
|
|
→ 被加引号 → WRONG SQL
|
|||
|
|
|
|||
|
|
profile["age"][@] → 末尾3字符 [@] → 无匹配(新操作符) → handleDefaultCondition
|
|||
|
|
→ ProcessColumn("profile[\"age\"][@]") → WRONG SQL
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**冲突 2 — UPDATE `ProcessColumnNoPrefix` 调用**(`[db/crud.go:629](db/crud.go)`)
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
query += processor.ProcessColumnNoPrefix(k) + "=" + vstr // 第629行
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`k = profile["age"]` → `QuoteIdentifier` 加引号 → ``profile["age"]`=?` → 无效 SQL。
|
|||
|
|
|
|||
|
|
**冲突 3 — SELECT slice 字段**(`[db/crud.go:109](db/crud.go)`)
|
|||
|
|
|
|||
|
|
无 `.` 且无 `AS` 的字段走 `ProcessColumnNoPrefix`,`profile["age"]` 被当成普通列名加引号。
|
|||
|
|
|
|||
|
|
### 已有格式不受影响的验证
|
|||
|
|
|
|||
|
|
- `profile[>]` — 末尾 `[>]` 匹配现有 switch → 先 strips 再调 `ProcessColumn("profile")` → **正常**
|
|||
|
|
- `user.name` — `ProcessColumn` 有 `.` 分支 → **正常**
|
|||
|
|
- ``user`.name` — `stripQuotes` 后同上 → **正常**
|
|||
|
|
- `"name,age"` 字符串字段 — `ProcessFieldList` 的正则不会错误匹配无 `.` 的字段 → **正常**
|
|||
|
|
- `Slice{"name","age"}` — 无 JSON 路径字符 → **正常**
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 实现方案
|
|||
|
|
|
|||
|
|
### 核心设计:`parseJSONPathKey` 最先拦截
|
|||
|
|
|
|||
|
|
区分 JSON 路径括号和操作符括号的关键:**括号内容**
|
|||
|
|
|
|||
|
|
- JSON 路径段:`["xxx"]`、`['xxx']`、`[0]`(引号字符串或纯数字)
|
|||
|
|
- 操作符段:`[>]`、`[@]`、`[len>]`(无引号,含符号字符)
|
|||
|
|
|
|||
|
|
**第一步:新增路径工具函数(`[db/where.go](db/where.go)` 顶部或独立 `jsonpath.go`)**
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
// parseJSONPathKey 解析 JSON 路径 key,识别规则:
|
|||
|
|
// ^(\w[\w.]*) 列名
|
|||
|
|
// ((?:\["[^"]*"\]|\['[^']*'\]|\[\d+\])+) JSON 路径段(双引号/单引号/数字索引)
|
|||
|
|
// (\[.*\])?$ 可选尾部操作符 [>] [@] [len>] 等
|
|||
|
|
//
|
|||
|
|
// 示例:
|
|||
|
|
// profile["age"][>] → col=profile, keys=["age"], op="[>]", ok=true
|
|||
|
|
// profile['addr']['city'] → col=profile, keys=["addr","city"], op="", ok=true
|
|||
|
|
// profile["tags"][0][@] → col=profile, keys=["tags","0"], op="[@]", ok=true
|
|||
|
|
// profile[>] → ok=false(操作符括号,非 JSON 路径)
|
|||
|
|
func parseJSONPathKey(k string) (col string, pathKeys []string, opSuffix string, ok bool)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**第二步:`varCond()` 开头最先调用**(`[db/where.go](db/where.go)`)
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
func (that *HoTimeDB) varCond(k string, v interface{}) (string, []interface{}) {
|
|||
|
|
// ★ 最优先:JSON 路径检测,完全绕过现有 [...] 分支
|
|||
|
|
if col, pathKeys, opSuffix, ok := parseJSONPathKey(k); ok {
|
|||
|
|
return that.jsonPathCond(col, pathKeys, opSuffix, v)
|
|||
|
|
}
|
|||
|
|
// 以下全部保持不变 ↓
|
|||
|
|
...
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`jsonPathCond` 根据 opSuffix 分发:
|
|||
|
|
|
|||
|
|
|
|||
|
|
| opSuffix | 动作 |
|
|||
|
|
| --------------------------------------------------------------------------------------- | ---------------------------------------------------------------- |
|
|||
|
|
| `""` / 现有操作符(`[>]`/`[<]`/`[>=]`/`[<=]`/`[!]`/`[~]`/`[!~]`/`[~!]`/`[<>]`/`[><]`/`[#]` 等) | 调用 `dialect.JSONExtract(col, pathKeys)` 得到提取表达式,替换原来的列名,复用现有比较逻辑 |
|
|||
|
|
| `[?]` | `JSONExtract(...) IS NOT NULL` |
|
|||
|
|
| `[!?]` | `JSONExtract(...) IS NULL` |
|
|||
|
|
| `[@]` | `dialect.JSONContains(col, pathKeys, "?")` |
|
|||
|
|
| `[!@]` | `NOT` + JSONContains |
|
|||
|
|
| `[len]` | `dialect.JSONArrayLength(col, pathKeys)` + `=?` |
|
|||
|
|
| `[len>]` / `[len<]` / `[len>=]` / `[len<=]` | JSONArrayLength + 对应比较符 |
|
|||
|
|
|
|||
|
|
|
|||
|
|
**第三步:扩展 `Dialect` 接口(`[db/dialect.go](db/dialect.go)`)**
|
|||
|
|
|
|||
|
|
新增 4 个方法,内部路径统一用 `[]string`(数字索引用字符串 `"0"`):
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
// MySQL: JSON_UNQUOTE(JSON_EXTRACT(`col`, '$.a.b'))
|
|||
|
|
// SQLite: json_extract("col", '$.a.b')
|
|||
|
|
// PgSQL: "col"#>>'{a,b}'
|
|||
|
|
JSONExtract(quotedColumn string, pathKeys []string) string
|
|||
|
|
|
|||
|
|
// MySQL: JSON_SET(`col`, '$.a.b', ?)
|
|||
|
|
// SQLite: json_set("col", '$.a.b', ?)
|
|||
|
|
// PgSQL: jsonb_set("col", '{a,b}', to_jsonb($N::text)) -- isStr=true
|
|||
|
|
// jsonb_set("col", '{a,b}', $N::jsonb) -- isStr=false
|
|||
|
|
JSONSet(quotedColumn string, pathKeys []string, placeholder string, isStr bool) string
|
|||
|
|
|
|||
|
|
// MySQL: JSON_CONTAINS(JSON_EXTRACT(`col`,'$.path'), JSON_QUOTE(?))
|
|||
|
|
// SQLite: EXISTS (SELECT 1 FROM json_each("col", '$.path') WHERE value=?)
|
|||
|
|
// PgSQL: "col"#>'{path}' @> to_jsonb($N::text)
|
|||
|
|
JSONContains(quotedColumn string, pathKeys []string, placeholder string) string
|
|||
|
|
|
|||
|
|
// MySQL: JSON_LENGTH(JSON_EXTRACT(`col`, '$.path'))
|
|||
|
|
// SQLite: json_array_length("col", '$.path')
|
|||
|
|
// PgSQL: jsonb_array_length("col"#>'{path}')
|
|||
|
|
JSONArrayLength(quotedColumn string, pathKeys []string) string
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**第四步:修复 UPDATE SET(`[db/crud.go:621](db/crud.go)`)**
|
|||
|
|
|
|||
|
|
在 `[#]` 检查之后、`ProcessColumnNoPrefix` 之前增加 JSON 路径检测:
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
} else if col, pathKeys, ok := parseJSONPathFromName(k); ok {
|
|||
|
|
quotedCol := processor.ProcessColumnNoPrefix(col)
|
|||
|
|
isStr := v != nil && reflect.TypeOf(v).Kind() == reflect.String
|
|||
|
|
ph := that.Dialect.Placeholder(len(qs) + 1)
|
|||
|
|
query += quotedCol + "=" + that.Dialect.JSONSet(quotedCol, pathKeys, ph, isStr)
|
|||
|
|
qs = append(qs, v)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**第五步:修复 SELECT slice 字段(`[db/crud.go:101](db/crud.go)`)**
|
|||
|
|
|
|||
|
|
在 `Slice` 字段循环中,对每个字段先做 JSON 路径检测:
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
if col, pathKeys, ok := parseJSONPathFromName(stripAlias(k)); ok {
|
|||
|
|
alias := extractAlias(k)
|
|||
|
|
quotedCol := processor.ProcessColumnNoPrefix(col)
|
|||
|
|
query += " " + that.Dialect.JSONExtract(quotedCol, pathKeys) + alias + " "
|
|||
|
|
} else if strings.Contains(k, " AS ") || strings.Contains(k, ".") {
|
|||
|
|
query += " " + processor.ProcessFieldList(k) + " "
|
|||
|
|
} else {
|
|||
|
|
query += " " + processor.ProcessColumnNoPrefix(k) + " "
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
字符串字段(如 `"profile[\"age\"] AS age, name"`)暂不处理 JSON 路径,用户可用原有原始 SQL 写法。
|
|||
|
|
|
|||
|
|
## 支持的能力范围
|
|||
|
|
|
|||
|
|
- **WHERE 条件**:复用全部现有 14 种操作符(`=` / `!=` / `>` / `<` / `>=` / `<=` / `LIKE` / `IN` / `NOT IN` / `BETWEEN` / `IS NULL` 等)+ 新增 `[?]` / `[!?]` / `[@]` / `[!@]` / `[len]` / `[len>]` / `[len<]` / `[len>=]` / `[len<=]`
|
|||
|
|
- **UPDATE SET**:JSON 路径局部更新,原列其他字段保持不变
|
|||
|
|
- **SELECT slice 字段**:`Slice{profile["age"] AS score}` 支持 JSON 路径
|
|||
|
|
- **SELECT 字符串字段**:暂不处理,用户写原始 SQL 或 Slice 形式
|
|||
|
|
- **数组索引**:`["arr"][0]` → `$.arr[0]`
|
|||
|
|
- **多级嵌套**:任意深度
|
|||
|
|
- **引号兼容**:`["key"]` 和 `['key']` 两种写法完全等效
|
|||
|
|
- **原有格式零影响**:`user.name` / ``user`.name` / `"name,age"` / `Slice{"name","age"}` 全部不变
|
|||
|
|
|
|||
|
|
## 兼容性注意事项
|
|||
|
|
|
|||
|
|
|
|||
|
|
| 级别 | 问题 | 处理方式 |
|
|||
|
|
| --- | --------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
|
|||
|
|
| 高 | **MySQL 必须 JSON_UNQUOTE**:`JSON_EXTRACT` 返回字符串含外层引号,直接 `= ?` 失败 | `MySQLDialect.JSONExtract()` 统一包 `JSON_UNQUOTE(JSON_EXTRACT(...))` |
|
|||
|
|
| 高 | **PostgreSQL 只支持 `jsonb` 列**:`jsonb_set` 不支持 `json` 类型列 | 文档约定:使用 JSON 路径操作的列需建为 `jsonb` 类型;或调用方在列名后加 `::jsonb` 强转 |
|
|||
|
|
| 高 | **SQLite `[@]` 生成 EXISTS 子查询**:结构与 MySQL/PgSQL 完全不同 | `SQLiteDialect.JSONContains()` 返回 `EXISTS(SELECT 1 FROM json_each(...) WHERE value=?)` 片段,在 `jsonPathCond` 里直接拼入 WHERE |
|
|||
|
|
| 中 | **PgSQL 更新时值的类型转换**:字符串需 `to_jsonb(?::text)`,数值/布尔用 `?::jsonb` | `JSONSet()` 接受 `isStr bool` 参数,由 `jsonPathCond` 根据 Go 值类型传入 |
|
|||
|
|
| 中 | **MySQL 版本**:JSON 函数需 5.7+,`JSON_OVERLAPS` 需 8.0+ | 先实现 5.7 兼容的基础操作符,`[!@]` 用 `NOT JSON_CONTAINS` 实现 |
|
|||
|
|
| 低 | **SQLite 版本**:需 3.9+,`go-sqlite3` 默认已启用 json1 扩展 | 无需特殊处理 |
|
|||
|
|
| 低 | **路径不存在返回 NULL**:三种数据库行为一致 | 调用方自行处理 NULL 判断,或用 `[?]` 操作符先做存在性检查 |
|
|||
|
|
|
|||
|
|
|
|||
|
|
## 涉及文件
|
|||
|
|
|
|||
|
|
- `[db/dialect.go](db/dialect.go)` — 接口扩展 + 三种方言实现(主要改动)
|
|||
|
|
- `[db/where.go](db/where.go)` — 新增 JSON 路径 key 检测与条件生成
|
|||
|
|
- `[db/crud.go](db/crud.go)` — UPDATE SET 子句 JSON 路径支持
|
|||
|
|
|