{"id":42053346,"url":"https://github.com/xuesongtao/spellsql","last_synced_at":"2026-01-26T07:07:04.481Z","repository":{"id":41286887,"uuid":"484357037","full_name":"xuesongtao/spellsql","owner":"xuesongtao","description":"🚀🚀🚀1. 高性能sql拼接工具(使用到 sync.Pool, strings.Builder 等） 2. 主要场景是需要原生 sql, 个人认为能让代码更加整洁，优雅，可控的打印sql log 3. 自带curd的orm操作，性能接近原生","archived":false,"fork":false,"pushed_at":"2025-08-25T06:47:56.000Z","size":532,"stargazers_count":8,"open_issues_count":0,"forks_count":2,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-08-25T09:24:28.169Z","etag":null,"topics":["fast-orm","light-weight-orm","orm","spellsql","sql"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/xuesongtao.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2022-04-22T08:33:04.000Z","updated_at":"2025-08-25T06:47:59.000Z","dependencies_parsed_at":"2024-06-01T14:46:10.281Z","dependency_job_id":"94e32b8b-72f2-43f9-a9ad-94cb943c2080","html_url":"https://github.com/xuesongtao/spellsql","commit_stats":{"total_commits":379,"total_committers":1,"mean_commits":379.0,"dds":0.0,"last_synced_commit":"681a268b409b2a1539cabeeaf616913a5330c9e6"},"previous_names":[],"tags_count":51,"template":false,"template_full_name":null,"purl":"pkg:github/xuesongtao/spellsql","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xuesongtao%2Fspellsql","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xuesongtao%2Fspellsql/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xuesongtao%2Fspellsql/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xuesongtao%2Fspellsql/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/xuesongtao","download_url":"https://codeload.github.com/xuesongtao/spellsql/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xuesongtao%2Fspellsql/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28769287,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-26T06:37:25.426Z","status":"ssl_error","status_checked_at":"2026-01-26T06:37:23.039Z","response_time":59,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["fast-orm","light-weight-orm","orm","spellsql","sql"],"created_at":"2026-01-26T07:06:59.951Z","updated_at":"2026-01-26T07:07:04.472Z","avatar_url":"https://github.com/xuesongtao.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# [spellsql](https://gitee.com/xuesongtao/spellsql)\n\n#### 🚀🚀🚀 项目背景\n\n* 公司选择了一波 `orm` 框架, 大多数框架都比较重, 重和性能相互, 最终放弃 `orm`;\n* 决定用原生 `database/sql`, 优势: 性能好, bug容易定位, 使用成本低等; 劣势: 代码拼接, 代码量很多, NULL处理等;\n\n* 为了解决 `sql` 拼接实现了 `spellsql`:\n    \u003e 1.使用 `sync.Pool`, `strings.Builder` 等提高 `sql` 拼接工具的性能  \n    \u003e 2.💯覆盖使用场景  \n    \u003e 3.支持 可控打印 `sql` 最终的 `log`; 非法字符自动转义; 支持格式化 `sql` 等  \n\n* 为了解决满足性能和释放双手添加了 `orm` 功能, 支持: **mysql|pg**\n    \u003e 1.新增/更新: 支持通过 `struct` 解析值进行操作; 支持对字段进行 **序列化** 操作; 支持设置**别名, 设置默认值**等  \n    \u003e 2.删除: 支持通过 `struct` 解析值进行  \n    \u003e 3.查询: 支持单表/多表查询; 支持对结果进行回调处理; 查询性能接近原生; 支持对结果映射到 `struct/map/slice/单字段`等\n\n#### 1. 使用介绍\n\n* 安装:  \n\n```go\ngo get -u gitee.com/xuesongtao/spellsql\n```\n\n#### 2. 占位符\n\n* 目前支持占位符 `?, ?d, ?v`, 说明如下:\n\n##### 2.1 占位符 ?\n\n* 直接根据 args 中类型来自动推动 arg 的类型, 使用如下:\n\n1.第一种用法: 根据 args 中类型来自动推动 arg 的类型  \n\n```go\n如: NewCacheSql(\"SELECT username, password FROM sys_user WHERE username = ? AND password = ?\", \"test\", 123).GetSqlStr()\n=\u003e SELECT username, password FROM sys_user WHERE username = \"test\" AND password = 123\n```\n\n2.第二种用法: 当 arg 为 []int8/int 等\n  \n```go  \n如: NewCacheSql(\"SELECT username, password FROM sys_user WHERE id IN (?)\", []int{1, 2, 3}).GetSqlStr()\n=\u003e SELECT username, password FROM sys_user WHERE id IN (1,2,3)\n```\n\n##### 2.2 占位符 ?d\n\n* 只会把数字型的字符串转为数字型, 如果是字母的话会被转义为 **0**, 如: `\"123\" =\u003e 123`; `[]string{\"1\", \"2\", \"3\"} =\u003e 1,2,3`, 如下:\n第一种用法: 当 arg 为字符串时, 又想不加双引号就用这个  \n\n```go  \n如: NewCacheSql(\"SELECT username, password FROM sys_user WHERE id = ?d\", \"123\").GetSqlStr()\n=\u003e SELECT username, password FROM sys_user WHERE id = 123\n```\n\n第二种用法: 当 arg 为 []string, 又想把解析后的单个元素不加引号  \n\n```go  \n如: NewCacheSql(\"SELECT username, password FROM sys_user WHERE id IN (?d)\", []string{\"1\", \"2\", \"3\"}).GetSqlStr()\n=\u003e SELECT username, password FROM sys_user WHERE id IN (1,2,3)\n```\n\n##### 2.3 占位符为: ?v\n\n* 这样会让字符串类型不加引号, 原样输出, 如: \"test\" =\u003e test;\n第一种用法: 当 arg 为字符串时, 又想不加双引号就用这个, 注: 只支持 arg 为字符串类型  \n\n```go  \n如: NewCacheSql(\"SELECT username, password FROM ?v WHERE id = ?d\", \"sys_user\", \"123\").GetSqlStr()\n=\u003e SELECT username, password FROM sys_user WHERE id = 123\n```\n\n第二种用法: 子查询  \n\n```go  \n如: NewCacheSql(\"SELECT u.username, u.password FROM sys_user su LEFT JOIN user u ON su.id = u.id WHERE u.id IN (?v)\", FmtSqlStr(\"SELECT id FROM user WHERE name=?\", \"test\").GetSqlStr()\n=\u003e SELECT u.username, u.password FROM sys_user su LEFT JOIN user u ON su.id = u.id WHERE u.id IN (SELECT id FROM user WHERE name=\"test\");\n```\n\n* **注:** 由于 `?v` 这种不会进行转义处理, 所有这种不推荐直接用于请求输入(外部非法输入)的内容, 会出现 **SQL 注入风险**; 当我们明确知道参数是干什么的可以使用会简化我们代码, 这里就不进行演示.\n\n#### 3. spellsql 使用\n\n* 可以参考 `getsqlstr_test.go` 和 `example_spellsql_test.go` 里的测试方法\n\n##### 3.1 新增  \n\n```go  \ns := NewCacheSql(\"INSERT INTO sys_user (username, password, name)\")\ns.SetInsertValues(\"xuesongtao\", \"123456\", \"阿桃\")\ns.SetInsertValues(\"xuesongtao\", \"123456\", \"阿桃\")\ns.GetSqlStr()\n\n// Output:\n// INSERT INTO sys_user (username, password, name) VALUES (\"test\", 123456, \"阿涛\"), (\"xuesongtao\", \"123456\", \"阿桃\"), (\"xuesongtao\", \"123456\", \"阿桃\");\n```\n\n##### 3.2 删除  \n\n```go  \ns := NewCacheSql(\"DELETE FROM sys_user WHERE id = ?\", 123)\nif true {\n    s.SetWhere(\"name\", \"test\")\n}\ns.GetSqlStr()\n// Output:\n// DELETE FROM sys_user WHERE id = 123 AND name = \"test\";\n```\n\n##### 3.3 查询  \n\n```go  \ns := NewCacheSql(\"SELECT * FROM user u LEFT JOIN role r ON u.id = r.user_id\")\ns.SetOrWhere(\"u.name\", \"xue\")\ns.SetOrWhereArgs(\"(r.id IN (?d))\", []string{\"1\", \"2\"})\ns.SetWhere(\"u.age\", \"\u003e\", 20)\ns.SetWhereArgs(\"u.addr = ?\", \"南部\")\ns.GetTotalSqlStr()\ns.SetLimit(1, 10)\ns.GetSqlStr()\n\n// Output:\n// sqlTotalStr: SELECT COUNT(*) FROM user u LEFT JOIN role r ON u.id = r.user_id WHERE u.name = \"xue\" OR (r.id IN (1,2)) AND u.age \u003e 20 AND u.addr = \"南部\";\n// sqlStr: SELECT * FROM user u LEFT JOIN role r ON u.id = r.user_id WHERE u.name = \"xue\" OR (r.id IN (1,2)) AND u.age \u003e 20 AND u.addr = \"南部\" LIMIT 0, 10;\n```\n\n##### 3.4 修改  \n\n```go  \ns := NewCacheSql(\"UPDATE sys_user SET\")\nidsStr := []string{\"1\", \"2\", \"3\", \"4\", \"5\"}\ns.SetUpdateValue(\"name\", \"xue\")\ns.SetUpdateValueArgs(\"age = ?, score = ?\", 18, 90.5)\ns.SetWhereArgs(\"id IN (?d) AND name = ?\", idsStr, \"tao\")\ns.GetSqlStr()\n\n// Output:\n// UPDATE sys_user SET name = \"xue\", age = 18, score = 90.50 WHERE id IN (1,2,3,4,5) AND name = \"tao\";\n```\n\n#### 3.5 追加  \n\n```go  \ns := NewCacheSql(\"INSERT INTO sys_user (username, password, age)\")\ns.SetInsertValuesArgs(\"?, ?, ?d\", \"xuesongtao\", \"123\", \"20\")\ns.Append(\"ON DUPLICATE KEY UPDATE username=VALUES(username)\")\ns.GetSqlStr()\n\n// Output:\n// INSERT INTO sys_user (username, password, age) VALUES (\"xuesongtao\", \"123\", 20) ON DUPLICATE KEY UPDATE username=VALUES(username);\n```\n\n##### 3.6 复用\n\n* 1.  `NewCacheSql()` 获取的对象在调用 `GetSqlStr()` 后会重置并放入内存池, 是不能对结果进行再进行 `GetSqlStr()`, 当然你是可以对结果作为 `NewCacheSql()` 的入参进行使用以此达到复用, 这样代码看起来不是多优雅, 分页处理案例如下:  \n\n```go  \nsqlObj := NewCacheSql(\"SELECT * FROM user_info WHERE status = 1\")\nhandleFn := func(obj *SqlStrObj, page, size int32) {\n    // 业务代码\n    fmt.Println(obj.SetLimit(page, size).SetPrintLog(false).GetSqlStr())\n}\n\n// 每次同步大小\nvar (\n    totalNum int32 = 30\n    page int32 = 1\n    size int32 = 10\n    totalPage int32 = int32(math.Ceil(float64(totalNum / size)))\n)\n\nsqlStr := sqlObj.SetPrintLog(false).GetSqlStr(\"\", \"\")\nfor page \u003c= totalPage {\n    handleFn(NewCacheSql(sqlStr), page, size)\n    page++\n}\n\n// Output:\n// SELECT * FROM user_info WHERE u_status = 1 LIMIT 0, 10;\n// SELECT * FROM user_info WHERE u_status = 1 LIMIT 10, 10;\n// SELECT * FROM user_info WHERE u_status = 1 LIMIT 20, 10;\n```\n\n* `NewSql()` 的产生的对象不会放入内存池, 可以进行多次调用 `GetSqlStr()`, 对应上面的示例可以使用 `NewSql()` 再调用 `Clone()` 进行处理, 如下:  \n\n```go  \nsqlObj := NewSql(\"SELECT u_name, phone, account_id FROM user_info WHERE u_status = 1\")\nhandleFn := func(obj *SqlStrObj, page, size int32) {\n    // 业务代码\n    fmt.Println(obj.SetLimit(page, size).SetPrintLog(false).GetSqlStr())\n}\n\n// 每次同步大小\nvar (\n    totalNum int32 = 30\n    page int32 = 1\n    size int32 = 10\n    totalPage int32 = int32(math.Ceil(float64(totalNum / size)))\n)\n\nfor page \u003c= totalPage {\n    handleFn(sqlObj.Clone(), page, size)\n    page++\n}\n\n// Output:\n// SELECT * FROM user_info WHERE u_status = 1 LIMIT 0, 10;\n// SELECT * FROM user_info WHERE u_status = 1 LIMIT 10, 10;\n// SELECT * FROM user_info WHERE u_status = 1 LIMIT 20, 10;\n```\n\n#### 4 orm使用介绍\n\n* `spellsql_orm` 能够高效的处理单表 `CURD`. 在查询方面的性能接近原生(orm_test.go 里有测试数据), 可以在 `dev` 分支上测试\n* 支持自定义 `tag`, 默认 `json`  \n\n```go  \ntype Man struct {\n    Id int32 `json:\"id,omitempty\"`\n    Name string `json:\"name,omitempty\"`\n    Age int32 `json:\"age,omitempty\"`\n    Addr string `json:\"addr,omitempty\"`\n}\n\n```\n\n##### 4.1 新增  \n\n```go  \nm := Man{\n    Name: \"xue1234\",\n    Age: 18,\n    Addr: \"成都市\",\n}\n\n// 1\nrows, _ = InsertForObj(db, \"man\", m)\nt.Log(rows.LastInsertId())\n\n// 3\nsqlObj := NewCacheSql(\"INSERT INTO man (name,age,addr) VALUES (?, ?, ?)\", m.Name, m.Age, m.Addr)\nrows, _ = ExecForSql(db, sqlObj)\nt.Log(rows.LastInsertId())\n```\n\n##### 4.2 删除  \n\n```go  \nm := Man{\n    Id: 9,\n}\n\n// 1\nrows, _ := NewTable(db).Delete(m).Exec()\nt.Log(rows.LastInsertId())\n\n// 2\nrows, _ = DeleteWhere(db, \"man\", \"id=?\", 9)\nt.Log(rows.LastInsertId())\n\n// 3\nsqlObj := NewCacheSql(\"DELETE FROM man WHERE id=?\", 9)\nrows, _ = ExecForSql(db, sqlObj)\nt.Log(rows.LastInsertId())\n```\n\n##### 4.3 修改  \n\n```go  \nm := Man{\n    Name: \"xue12\",\n    Age: 20,\n    Addr: \"测试\",\n}\n\n// 1\nrows, _ := NewTable(db).Update(m, \"id=?\", 7).Exec()\nt.Log(rows.LastInsertId())\n\n// 2\nsqlObj := NewCacheSql(\"UPDATE man SET name=?,age=?,addr=? WHERE id=?\", m.Name, m.Age, m.Addr, 7)\nrows, _ = ExecForSql(db, sqlObj)\nt.Log(rows.LastInsertId())\n```\n\n##### 4.4 查询\n\n###### 4.4.1 单查询  \n\n```go  \nvar m Man\n// 1\n_ = NewTable(db, \"man\").Select(\"name,age\").Where(\"id=?\", 1).FindOne(\u0026m)\nt.Log(m)\n\n// 2\n_ = NewTable(db).SelectAuto(\"name,age\", \"man\").Where(\"id=?\", 1).FindOne(\u0026m)\nt.Log(m)\n\n// 3\n_ = FindOne(db, NewCacheSql(\"SELECT name,age FROM man WHERE id=?\", 1), \u0026m)\nt.Log(m)\n\n// 4, 对查询结果进行内容修改\n_ = FindOneFn(db, NewCacheSql(\"SELECT name,age FROM man WHERE id=?\", 1), \u0026m, func(_row interface{}) error {\n    v := _row.(*Man)\n    v.Name = \"被修改了哦\"\n    v.Age = 100000\n    return nil\n})\nt.Log(m)\n\n// 5\n_ = FindWhere(db, \"man\", \u0026m, \"id=?\", 1)\nt.Log(m)\n\n// 6\nvar b map[string]string\n_ = FindWhere(db, \"man\", \u0026b, \"id=?\", 1)\nt.Log(b)\n```\n\n* 查询结果支持: `struct`, `map`, `单字段`\n* 数据库返回的 `NULL` 类型, 不需要处理, `orm` 会自行处理, 如果传入空类型值会报错(如: sql.NullString)\n\n###### 4.4.2 多条记录查询\n\n```go  \nvar m []*Man\nerr := NewTable(db, \"man\").Select(\"id,name,age,addr\").Where(\"id\u003e?\", 1).FindAll(\u0026m, func(_row interface{}) error {\n    v := _row.(*Man)\n    if v.Id == 5 {\n        v.Name = \"test\"\n    }\n    fmt.Println(v.Id, v.Name, v.Age)\n    return nil\n})\nif err != nil {\n    t.Fatal(err)\n}\nt.Logf(\"%+v\", m)\n```\n\n* 查询结果支持的切片类型: `struct`, `map`, `单字段`\n* 数据库返回的 `NULL` 类型, 不需要处理, `orm` 会自行处理, 如果传入空类型值会报错(如: sql.NullString)\n\n###### 4.4.3 别名查询\n\n```go  \ntype Tmp struct {\n    Name1 string `json:\"name_1,omitempty\"`\n    Age1 int32 `json:\"age_1,omitempty\"`\n}\n\nvar m Tmp\nerr := NewTable(db).\nTagAlias(map[string]string{\"name_1\": \"name\", \"age_1\": \"age\"}).\nSelect(\"name,age\").\nFrom(\"man\").\nFindWhere(\u0026m, \"id=?\", 1)\nif err != nil {\n    t.Fatal(err)\n}\n```\n\n###### 4.4.3 其他\n\n* 使用可以参考 `orm_test.go` 和 `example_orm_test.go`\n* 在连表查询时, 如果两个表的列名相同查询结果会出现错误, 我们可以通过根据别名来区分, 或者直接调用 `Query` 来自行对结果进行处理(注: 调用 `Query` 时需要处理 `Null` 类型)\n\n#### 其他\n\n* 欢迎大佬们指正, 希望大佬给❤️，to [gitee](https://gitee.com/xuesongtao/spellsql) [github](https://github.com/xuesongtao/spellsql)\n* 在线 SQL 转 GO [gotool](https://gotool.top): sql to gorm/xorm/ent/struct; json to struct","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxuesongtao%2Fspellsql","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fxuesongtao%2Fspellsql","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxuesongtao%2Fspellsql/lists"}