# sqlparser **Repository Path**: daoshangsoft/sqlparser ## Basic Information - **Project Name**: sqlparser - **Description**: Simple parse sql params , delete unknow where sql params :param for variable $param for Sys variable in sql replace element's param value for params return new sql and sqlparams - **Primary Language**: Go - **License**: Not specified - **Default Branch**: v2 - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2019-05-20 - **Last Updated**: 2026-11-13 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # sqlparser v2 **SQL `:VAR` 变量格式化组件** —— 把 SQL 中的 `:param` / `&sysvar` 替换为数据库驱动占位符(`?` 或 `$n`)并产出有序参数表;变量无值时按**标准布尔语义**自动裁剪所属查询条件(动态 WHERE 的核心能力)。 v2 是对 v1.0.3 的完整重写:以 **Lexer → 布尔树结构解析 → span 原位渲染** 三段流水线替代 v1 的"正则 + 占位 token + 全串多轮替换",修复 v1 全部 9 项已定位缺陷(P1–P9),纯 Go 标准库、无 cgo、O(n) 单遍扫描。 - 模块路径:`gitee.com/daoshangsoft/sqlparser/v2` - 仓库:(`v2` 分支 / `v2.1.0` tag;v1 源码归档于 `v1.0.3` tag) - 要求:Go 1.22+,零第三方依赖 --- ## 目录 - [核心特性](#核心特性) - [变量语法与取值范围](#变量语法与取值范围) - [安装](#安装) - [快速开始](#快速开始) - [条件裁剪语义](#条件裁剪语义) - [API 参考](#api-参考) - [方言与占位符](#方言与占位符) - [性能基准](#性能基准) - [从 v1 迁移](#从-v1-迁移) - [架构](#架构) - [测试与开发](#测试与开发) - [文档](#文档) - [版本里程碑](#版本里程碑) --- ## 核心特性 | 特性 | 说明 | |------|------| | 参数替换 | `:param` → `?`(MySQL 等)或 `$1..$n`(PostgreSQL),产出有序 `[]SQLParam` 参数表 | | 标准布尔裁剪 | 缺参谓词恒 FALSE;AND/OR 统一"删 FALSE 子、留 TRUE 子、全 FALSE 整链删",按标准优先级建树(AND 高于 OR) | | 不裁剪模式 | `SimpleParse` 族:无值变量写 NULL 参数、占位符保留(与 v1 一致) | | 多值展开 | `IN (:course)` 多值参数自动展开为 `IN (?,?,?)`,参数按序落表 | | 系统变量 | `&sysvar` 经 `SysVariableCtrl` 注册替换,未注册视同无值 | | 注释保护 | `--`、`/* */` 注释原样保留且不参与解析;注释内引号/冒号不干扰(修复 P4) | | `::` 类型转换 | PG `col::varchar` 的 `::` 识别为 Cast 操作符,不再误解析为参数(修复 P9,全方言生效) | | 排序安全渲染 | FR-9:标识符校验 → 表达式白名单 → 方向白名单 → NULLS(仅 PG)→ 列数上限 → 方言引号,杜绝 ORDER BY 注入面 | | 逐字节保留 | 除变量 span 与裁剪区间外,原文逐字节保留(空白、换行、大小写、注释) | | 畸形降级 | 结构畸形 SQL 不 panic:非 Strict 降级为"不裁剪仅替换",`Strict=true` 返回 error(NFR-3) | | 并发安全 | `Parser` 构造后配置只读、零全局可变状态,可并发复用(`-race` 验证) | | 高性能 | 64KB 报表 SQL 两路径均 <5ms,约为 v1 的 49–64 倍(见[性能基准](#性能基准)) | ## 变量语法与取值范围 ### 业务变量 `:name` 与系统变量 `&name` 两者共享相同的标识符扫描规则(FR-1.1 / FR-1.2): | 规则 | 说明 | |------|------| | 首字符 | `[a-zA-Z0-9_]` —— 字母、数字、下划线均可起始 | | 续字符 | `[a-zA-Z0-9_.]` —— 首字符规则 + 点号(支持 `:a.b` 形式) | | 大小写 | 敏感:`:Name` 与 `:name` 是不同变量 | | 无值判定 | 未注册或值为空串 → 缺参(Parse 裁剪 / SimpleParse 落 NULL) | | 不允许的字符 | `$`、`|`、`-`、空格等均不属于变量名(v1 续字符意外含 `|`,v2 已修正) | **有效示例**:`:x`、`:user_id`、`:a.b`、`:123`、`:123abc`、`:_x9`、`&TODAY`、`&123` > **注意**:`:123` 是**有效**变量名(数字属于 `\w`,FR-1.1)。若业务上需禁止纯数字变量名,应在应用层参数注册时校验,Lexer 不做此限制。 ### 非变量场景(不识别为参数) | 写法 | 实际解析 | 原因 | |------|----------|------| | `::varchar` | Cast 操作符 + 标识符 | `::` 为类型转换(FR-1.4,全方言生效,修复 P9) | | `:::b` | `::` + `:b`(报畸形) | ≥3 连续冒号触发 NFR-3 降级 | | `: `、`:-`、`:` | 标点(KindPunct) | 冒号后非 word 字符,不构成变量 | | `'...:x...'` / `"...:x..."` | 字符串字面量 | 引号串内不识别变量(FR-1.3) | | `-- :x` / `/* :x */` | 注释 | 注释内不识别变量(FR-8.2) | | `a:::b` | `a` + `::` + `:b` | 贪心拆分:Cast 优先,余下为参数 | ### 系统变量补充规则(FR-3) - `&name` 经 `SysVariableCtrl.AppendVariable` 注册后替换;未注册或空串视同无值。 - `ctrl=nil` 时所有 `&var` 视同无值(与 v1 一致)。 - 系统变量与业务变量共享标识符规则,但走独立的 `SysVariableCtrl` 参数表。 ## 安装 ```bash go get gitee.com/daoshangsoft/sqlparser/v2@v2.1.0 ``` ```go import "gitee.com/daoshangsoft/sqlparser/v2" ``` > v1 用户迁移:仅需 `go get .../v2` + 改 import 前缀,函数签名完全兼容,详见[从 v1 迁移](#从-v1-迁移)。 ## 快速开始 ### 裁剪模式(Parse):缺参条件自动删除 ```go package main import ( "fmt" sqlparser "gitee.com/daoshangsoft/sqlparser/v2" ) func main() { p := sqlparser.NewParser(sqlparser.Options{DBType: sqlparser.MYSQL}) e := sqlparser.NewSQLParamElement() e.AddElement("a", "1", sqlparser.INT, "F") e.AddElement("f", "9", sqlparser.INT, "F") // :c 未注册 → 缺参,所属条件被裁剪 sql, params := p.Parse( "select * from t where a=:a and (c=:c or d=:c or f=:f)", e) fmt.Println(sql) // select * from t where a=? and (f=?) for _, param := range params { fmt.Println(param.GetIntValue()) // {1 true} {9 true} } } ``` ### 不裁剪模式(SimpleParse):无值写 NULL 参数 ```go e := sqlparser.NewSQLParamElement() e.AddElement("a", "1", sqlparser.INT, "F") // :b 无值 → 占位符保留,落 NULL 参数(不裁剪) sql, params := p.SimpleParse("select * from t where a=:a and b=:b", e) // sql: select * from t where a=? and b=? // params: [INT "1", NULL ""] ``` ### 多值参数展开 ```go e := sqlparser.NewSQLParamElement() e.AddElement("course", "chinese,english,math", sqlparser.STRING, "T") sql, params := p.Parse("select * from t where name in (:course)", e) // sql: select * from t where name in (?,?,?) // params: 3 个 STRING 单值参数(按序) ``` ### JSON 批量入参(AddElements) ```go e := sqlparser.NewSQLParamElement() err := e.AddElements(`{ "name": "alice", "age": 30, "course": ["chinese", "english"], "sort": [{"property": "age", "direction": "DESC"}] }`) // 标量自动推断类型(int→INT、float→FLOAT、bool→BOOL、string→STRING、null→无值) // 数组 → 逗号连接多值且 multiple=T;sort 键路由为排序项 ``` **防实参冲突(v2.1.0)**:若业务 JSON 含名为 `sort` 的实参,可按调用改名或禁用路由: ```go // 改名:仅 "__sort" 路由为排序项,"sort" 作为普通参数保留 e.AddElementsWithSortKey(`{"__sort":[{"property":"age","direction":"DESC"}],"sort":"业务值"}`, "__sort") // 禁用:空串关闭路由,所有键(含 sort)均入参数表 e.AddElementsWithSortKey(`{"sort":"业务值","name":"alice"}`, "") ``` ### 系统变量(&sysvar) ```go ctrl := sqlparser.NewSysVariableCtrl() ctrl.AppendVariable("TODAY", "2026-01-01", sqlparser.STRING, "F") e := sqlparser.NewSQLParamElement() e.AddElement("x", "1", sqlparser.INT, "F") sql, params := p.ParseSV("select * from t where a=:x and d=&TODAY", e, ctrl) // sql: select * from t where a=? and d=? ``` ### PostgreSQL 方言($n 占位符) ```go p := sqlparser.NewParser(sqlparser.Options{DBType: sqlparser.POSTGRES}) e := sqlparser.NewSQLParamElement() e.AddElement("id", "7", sqlparser.INT, "F") e.AddElement("v", "x", sqlparser.STRING, "F") sql, _ := p.Parse("select * from t where id=:id and col::varchar=:v", e) // sql: select * from t where id=$1 and col::varchar=$2 // :: 为类型转换操作符,不会被误解析为参数 ``` ### 排序校验与安全渲染(FR-9) ```go p := sqlparser.NewParser(sqlparser.Options{ DBType: sqlparser.POSTGRES, MaxSortColumns: 8, AllowedSortExprs: []string{"lower(name)"}, // 表达式精确白名单 SortColumnResolver: func(prop string) (string, bool) { // 应用层列映射钩子(如接列布局/字典表) return prop, true }, }) e := sqlparser.NewSQLParamElement() e.AddSort("name", "DESC") e.AddSortWithNulls("age", "ASC", "LAST") // NULLS 仅 PG 渲染 order, err := p.SortSQL(e) // order: "name" DESC, "age" ASC NULLS LAST // 非法标识符(注入尝试)被丢弃或报错;Parse* 不自动注入 ORDER BY, // 调用方先 SortSQL 得安全串再拼 wrapper SQL。 ``` ### v1 兼容的包级入口 ```go sqlparser.InitDbType(sqlparser.MYSQL) // Deprecated:推荐 NewParser e := sqlparser.NewSQLParamElement() e.AddElement("x", "1", sqlparser.INT, "F") sql, params := sqlparser.Parse("select * from t where a=:x", e) // 包级 SimpleParse/SimpleParseSV/Parse/ParseSV 签名与 v1 完全一致 ``` ## 条件裁剪语义 v2 采用**标准布尔裁剪**(原非标准决策 D1/D2 经评审废止): 1. 缺参谓词(变量未注册或值为空串)求值为 **FALSE**; 2. **AND 链**:删 FALSE 子、留 TRUE 子;全 FALSE → 整链删; 3. **OR 链**:与 AND 对称——删 FALSE 子、留 TRUE 子;全 FALSE → 整链删; 4. **括号组**:递归求值,子树全删则整组 FALSE; 5. 过滤段主体全删 → 连同 `WHERE`/`HAVING` 关键字一起删除;`JOIN ... ON` 主体全删 → 保留 JOIN、删除 ON 子句; 6. 删除后自动清理相邻连接词,不产生悬挂 `AND`/`OR`; 7. 参数表顺序 = 裁剪后 SQL 中占位符的**文本出现顺序**。 **示例**:`select * from t where a=:a and (c=:c or d=:c or f=:f)` | 参数状态 | 输出 | |----------|------| | `:c` 缺、`:f` 有 | `select * from t where a=? and (f=?)` | | `:c` 有、`:f` 缺 | `select * from t where a=? and (c=? or d=?)` | | `:c` 缺、`:f` 缺 | `select * from t where a=?` | | 均有值 | `select * from t where a=? and (c=? or d=? or f=?)` | 混合优先级按标准树求值:`a=1 and b=2 or c=:x and d=4`(`:x` 缺)→ `a=1 and b=2 or d=4`(右链删缺参 `c=:x`、保留 `d=4`)。 ## API 参考 ### 入口(Parser 实例,推荐) | 函数 | 模式 | 说明 | |------|------|------| | `NewParser(Options) *Parser` | — | 构造实例;配置只读,可并发复用 | | `(*Parser).SimpleParse(sql, e)` | 不裁剪 | 无值写 NULL 参数,占位符保留 | | `(*Parser).SimpleParseSV(sql, e, ctrl)` | 不裁剪 | 同上 + 系统变量 | | `(*Parser).Parse(sql, e)` | 裁剪 | 缺参条件按布尔树裁剪 | | `(*Parser).ParseSV(sql, e, ctrl)` | 裁剪 | 同上 + 系统变量 | | `(*Parser).ParseSVE(sql, e, ctrl)` | 裁剪 | v2 增量:额外返回 error(仅 Strict 且畸形时非 nil) | | `(*Parser).SortSQL(e) (string, error)` | — | FR-9 排序校验与方言引号渲染 | 包级同名函数(`SimpleParse/SimpleParseSV/Parse/ParseSV`)签名兼容 v1,方言取 `InitDbType` 设置的全局默认(Deprecated,推荐实例路径)。 ### Options | 字段 | 默认 | 说明 | |------|------|------| | `DBType` | `MYSQL` | 方言:`ORACLE/MYSQL/SQLSERVER/SQLITE/POSTGRES`(大写归一,未知→MYSQL) | | `Strict` | `false` | 结构畸形返回 error(NFR-3);排序非法项报错而非丢弃 | | `SortProperty` | `"sort"` | 保留字段(v2.0.0 兼容);生效 sort 键见 `SetSortProperty` / `AddElementsWithSortKey` | | `MaxSortColumns` | `8` | 排序列数上限,超出截断并返回诊断 error | | `AllowedSortExprs` | 空 | 表达式排序精确匹配白名单(禁止前缀/包含匹配) | | `SortColumnResolver` | nil | property→列名映射钩子,应用层提供元数据 | ### 参数表(SQLParamElement) | 函数 | 说明 | |------|------| | `NewSQLParamElement()` | 创建参数表 | | `AddElement(name, text, type, multiple)` | 添加参数;type ∈ `STRING/INT/FLOAT/BOOL/NULL`,multiple ∈ `T/F`;同名后写覆盖先写 | | `AddElements(json string \| map)` | JSON/map 批量入参,标量类型推断、数组→多值、sort 键路由 | | `AddElementsWithSortKey(params, sortKey)` | v2.1.0:按调用指定 sort 路由键(防实参冲突);空串禁用路由 | | `GetElement(name) / GetElements()` | 读取参数 | | `AddSort(property, direction)` | 添加排序项(direction 仅 DESC 生效,其余回退 ASC) | | `AddSortWithNulls(property, direction, nulls)` | v2 增量:NULLS FIRST/LAST(仅 PG 渲染) | | `SetSortProperty(key)` | 显式设置本实例 sort 键 | | `SortString()` | v1 格式排序串(已过标识符校验与列数上限;完整方言渲染请用 `SortSQL`) | `SQLParam` 取值:`GetIntValue() sql.NullInt64`、`GetFloatValue() sql.NullFloat64`、`GetStringValue() sql.NullString`、`GetBoolValue() sql.NullBool`、`GetType()`、`IsMultiple()`。NULL 类型或解析失败 → `Valid=false`。 ### 系统变量(SysVariableCtrl) `NewSysVariableCtrl()` / `AppendVariable(name, text, type, multiple)` / `GetVariable(name)`;空串视同无值,`ctrl=nil` 时所有 `&var` 视同无值。 ## 方言与占位符 | DBType | 占位符 | 排序引号 | NULLS 渲染 | |--------|--------|----------|-----------| | `MYSQL`(默认) | `?` | 反引号 | 忽略 | | `POSTGRES` | `$1..$n`(渲染期直接编号) | 双引号 | FIRST/LAST | | `ORACLE` / `SQLSERVER` / `SQLITE` | `?` | 双引号 | 忽略 | ## 性能基准 64KB 报表 SQL(~500 个 union-all 段各带 WHERE 的压力构造),Apple M 系列 -4 机型: | 路径 | v2 | v1 | 提速 | 内存 v2 / v1 | |------|-----|-----|------|--------------| | SimpleParse64KB | ≈2.4–3.3ms ✓ | ≈161ms | ~49× | 2.9MB / 241MB | | Parse64KB | ≈3.5–4.8ms ✓ | ≈310ms | ~64× | 3.2MB / 455MB | - 两路径均满足 <5ms 验收目标(需求 §9.3);O(n) 单遍扫描 + span 原位拼装,无全串多轮 Replace。 - 关键优化:栈缓冲 ASCII 关键字比较(消除 `strings.ToUpper` 分配)、类型化排序(消除反射)。 - 复现:`go test -bench='Parse64KB' -run='^$' -benchtime=1s .` ## 从 v1 迁移 ### 零改动迁移(API 兼容) 现有调用点(`AddElement` + `Parse` 族)仅需两步: ```bash go get gitee.com/daoshangsoft/sqlparser/v2@v2.1.0 ``` ```diff - import "gitee.com/daoshangsoft/sqlparser" + import sqlparser "gitee.com/daoshangsoft/sqlparser/v2" ``` v1/v2 模块路径不同,可共存同一二进制双跑灰度。`InitDbType`/`SetSortProperty` 保留为 deprecated 转发(修复了 v1 小写不归一的 bug),推荐迁移到 `NewParser(Options{...})`。 ### 有意行为差异(修 bug / 安全加固,均有用例固化) | 场景 | v1 | v2 | |------|-----|-----| | 混合 `AND/OR` 裁剪 | 平铺切分,结果错误(P3) | 标准优先级树 + 标准裁剪 | | PG `col::varchar=:v` | `:varchar` 误判为参数,条件误删(P9) | `::` 为 Cast,正常落参 | | 含冒号/引号的注释 | 可能误解析(P4) | 原样保留、不参与解析 | | `AddElements` 数组 | multiple 恒 F(P5) | 逗号连接多值且 multiple=T | | 排序 property 注入 | 仅剥引号原样拼入 | FR-9 校验丢弃/报错 + 方言引号 | | `InitDbType("postgres")` | 小写不生效(P7) | 大写归一 | | 双 JOIN 独立裁剪 | 误删后续 JOIN(p28) | 各 ON 段独立评估(FR-6.5) | 完整矩阵见[需求文档 §7](docs/sqlparser重写技术需求.md)与[设计文档 §10](docs/sqlparser-v2-详细设计.md)。 ## 架构 ``` src string → [Stage1 Lexer] 单遍字节扫描 → []Token{Kind, Span} 字符串/注释/::Cast 天然识别(替代 v1 魔改 regexp) → [Stage2 Structure] 过滤段识别(WHERE/HAVING/JOIN ON)→ 布尔树 OR > AND > 括号组优先级;节点携带原文 span → [Stage3 Render] 自根裁剪求值(标准布尔语义)→ 按 span 原位拼装 未删参数 → 占位符 + 落参;删除区间跳过(扣除注释保护) POSTGRES 在输出游标直接编号 $n ``` | 文件 | 职责 | |------|------| | `lexer.go` | 手写扫描器、Token/Kind、关键字表(无分配 ASCII 比较) | | `node.go` | 布尔树节点(pred/group/and/or)与裁剪求值 | | `filter_parse.go` | 过滤段识别、建树、删除区间合并与注释保护 | | `render.go` | span 原位渲染、占位符落参、PG 编号 | | `element.go` | SQLParam / SQLParamElement(API 兼容 v1) | | `sqlparser.go` | Parser / Options / 包级兼容入口 | | `sysvar.go` | SysVariableCtrl | | `sort.go` | FR-9 排序校验流水线与 SortSQL 渲染 | ## 测试与开发 ```bash go vet ./... # 零告警 go test -race ./... -count=1 # 55 黄金用例 + 500 属性对拍 + 单测/并发 go test -bench='Parse64KB' -run='^$' -benchtime=1s . # 64KB 基准 ``` - **黄金用例**(`sqlparser_test.go` c01–c21 + `parse_golden_test.go` p01–p34):期望值经 v1.0.3 捕获核对后固化为字面量,覆盖需求 §7 兼容性矩阵全部场景。 - **属性对拍**(`property_test.go`):随机布尔组合 × 独立参考求值器 500 例(固定种子),断言占位符数=落参数、无残留冒号、无悬挂连接词。因纯标准库/无 cgo 约束,以纯 Go 参考求值器替代需求原文的 sqlite 对拍(偏差已记入设计文档 §8.1)。 - **并发**:`-race` 下多 goroutine 复用同一 Parser 实例。 ## 文档 - [重写技术需求(基线)](docs/sqlparser重写技术需求.md) —— P1–P9 缺陷、FR-1–FR-9 行为规格、NFR、兼容性矩阵、验收标准 - [V2 详细设计](docs/sqlparser-v2-详细设计.md) —— 三段流水线契约、裁剪算法、黄金用例映射、性能基准、有意差异清单 ## 版本里程碑 | 版本 | 内容 | |------|------| | M1 | 手写 Lexer + SimpleParse 族兼容 + 包级入口,修复 P1/P2/P4/P5/P7/P8/P9 | | M2 | 布尔树裁剪(Parse 族)+ FR-9 排序校验 + NFR-3 Strict 降级 | | M3 | 黄金用例扩至 55 条 + 属性对拍 + 64KB 基准达标 + 无分配优化 | | 语义修订 | D1/D2 非标准 OR 整链删经评审废止,改标准布尔裁剪(AND/OR 删恒假留恒真) | | **v2.0.0** | 移除 legacy/ v1 归档(源码见 `v1.0.3` tag),发布清理完成 | | **v2.1.0** | `AddElementsWithSortKey`(按调用指定/禁用 sort 路由,防实参冲突);`Options.SortProperty` 澄清为保留字段;README 新增变量语法与取值范围章节(`:name`/`&name` 有效字符、非变量场景、`::` Cast 规则) | --- *v1 维护线冻结于 `master` 分支(v1.0.3);v2 开发在 `v2` 分支。*