feat(request): 实现请求参数获取方法

- 完成 ReqParam/ReqParams 方法实现,用于获取 URL 参数并返回 *Obj
- 完成 ReqForm/ReqForms 方法实现,用于获取表单数据并返回 *Obj
- 完成 ReqJson/ReqJsons 方法实现,用于获取 JSON Body 并返回 *Obj
- 完成 ReqFile/ReqFiles 方法实现,用于获取上传文件
- 完成 ReqData/ReqDatas 方法实现,用于统一封装请求数据获取并返回 *Obj
- 更新计划文件状态,标记所有相关功能模块为已完成
This commit is contained in:
hoteas 2026-01-22 04:59:53 +08:00
parent b755519fc6
commit f2f1fcc9aa
16 changed files with 2881 additions and 2441 deletions

View File

@ -7,27 +7,27 @@ todos:
status: completed status: completed
- id: impl-params - id: impl-params
content: 实现 ReqParam/ReqParams 方法(获取 URL 参数,返回 *Obj content: 实现 ReqParam/ReqParams 方法(获取 URL 参数,返回 *Obj
status: in_progress status: completed
dependencies: dependencies:
- add-imports - add-imports
- id: impl-form - id: impl-form
content: 实现 ReqForm/ReqForms 方法(获取表单数据,返回 *Obj content: 实现 ReqForm/ReqForms 方法(获取表单数据,返回 *Obj
status: pending status: completed
dependencies: dependencies:
- add-imports - add-imports
- id: impl-json - id: impl-json
content: 实现 ReqJson/ReqJsons 方法(获取 JSON Body返回 *Obj content: 实现 ReqJson/ReqJsons 方法(获取 JSON Body返回 *Obj
status: pending status: completed
dependencies: dependencies:
- add-imports - add-imports
- id: impl-file - id: impl-file
content: 实现 ReqFile/ReqFiles 方法(获取上传文件) content: 实现 ReqFile/ReqFiles 方法(获取上传文件)
status: pending status: completed
dependencies: dependencies:
- add-imports - add-imports
- id: impl-reqdata - id: impl-reqdata
content: 实现 ReqData/ReqDatas 方法(统一获取,返回 *Obj content: 实现 ReqData/ReqDatas 方法(统一获取,返回 *Obj
status: pending status: completed
dependencies: dependencies:
- impl-params - impl-params
- impl-form - impl-form

2
.gitignore vendored
View File

@ -2,3 +2,5 @@
.idea .idea
/example/config/app.json /example/config/app.json
/example/tpt/demo/ /example/tpt/demo/
/example/config/
/*.exe

View File

@ -1,12 +1,17 @@
package hotime package hotime
import ( import (
. "code.hoteas.com/golang/hotime/common" "bytes"
. "code.hoteas.com/golang/hotime/db"
"encoding/json" "encoding/json"
"io"
"mime/multipart"
"net/http" "net/http"
"strings" "strings"
"sync"
"time" "time"
. "code.hoteas.com/golang/hotime/common"
. "code.hoteas.com/golang/hotime/db"
) )
type Context struct { type Context struct {
@ -23,6 +28,10 @@ type Context struct {
SessionIns SessionIns
DataSize int DataSize int
HandlerStr string //复写请求url HandlerStr string //复写请求url
// 请求参数缓存
reqJsonCache Map // JSON Body 缓存
reqMu sync.Once // 确保 JSON 只解析一次
} }
// Mtd 唯一标志 // Mtd 唯一标志
@ -100,5 +109,97 @@ func (that *Context) View() {
that.DataSize = len(d) that.DataSize = len(d)
that.RespData = nil that.RespData = nil
that.Resp.Write(d) that.Resp.Write(d)
}
// ==================== 请求参数获取方法 ====================
// ReqParam 获取 URL 查询参数,返回 *Obj 支持链式调用
// 用法: that.ReqParam("id").ToInt()
func (that *Context) ReqParam(key string) *Obj {
v := that.Req.URL.Query().Get(key)
if v == "" {
return &Obj{Data: nil}
}
return &Obj{Data: v}
}
// ReqForm 获取表单参数,返回 *Obj 支持链式调用
// 用法: that.ReqForm("name").ToStr()
func (that *Context) ReqForm(key string) *Obj {
v := that.Req.FormValue(key)
if v == "" {
return &Obj{Data: nil}
}
return &Obj{Data: v}
}
// ReqJson 获取 JSON Body 中的字段,返回 *Obj 支持链式调用
// 用法: that.ReqJson("data").ToMap()
func (that *Context) ReqJson(key string) *Obj {
that.parseJsonBody()
if that.reqJsonCache == nil {
return &Obj{Data: nil}
}
return &Obj{Data: that.reqJsonCache.Get(key)}
}
// parseJsonBody 解析 JSON Body只解析一次并发安全
func (that *Context) parseJsonBody() {
that.reqMu.Do(func() {
if that.Req.Body == nil {
return return
} }
body, err := io.ReadAll(that.Req.Body)
if err != nil || len(body) == 0 {
return
}
// 恢复 Body 以便后续代码可以再次读取
that.Req.Body = io.NopCloser(bytes.NewBuffer(body))
that.reqJsonCache = ObjToMap(string(body))
})
}
// ReqData 统一获取请求参数,优先级: JSON > Form > URL
// 用法: that.ReqData("id").ToInt()
func (that *Context) ReqData(key string) *Obj {
// 1. 优先从 JSON Body 获取
that.parseJsonBody()
if that.reqJsonCache != nil {
if v := that.reqJsonCache.Get(key); v != nil {
return &Obj{Data: v}
}
}
// 2. 其次从 Form 获取
if v := that.Req.FormValue(key); v != "" {
return &Obj{Data: v}
}
// 3. 最后从 URL 参数获取
if v := that.Req.URL.Query().Get(key); v != "" {
return &Obj{Data: v}
}
return &Obj{Data: nil}
}
// ReqFile 获取单个上传文件
// 用法: file, header, err := that.ReqFile("avatar")
func (that *Context) ReqFile(name string) (multipart.File, *multipart.FileHeader, error) {
return that.Req.FormFile(name)
}
// ReqFiles 获取多个同名上传文件(批量上传场景)
// 用法: files, err := that.ReqFiles("images")
func (that *Context) ReqFiles(name string) ([]*multipart.FileHeader, error) {
if that.Req.MultipartForm == nil {
if err := that.Req.ParseMultipartForm(32 << 20); err != nil {
return nil, err
}
}
if that.Req.MultipartForm == nil || that.Req.MultipartForm.File == nil {
return nil, http.ErrMissingFile
}
files, ok := that.Req.MultipartForm.File[name]
if !ok {
return nil, http.ErrMissingFile
}
return files, nil
}

View File

@ -1,17 +1,18 @@
# HoTimeDB API 快速参考 # HoTimeDB API 快速参考
## ⚠️ 重要语法说明 ## 条件查询语法规则
**条件查询语法规则** **新版本改进**
- 单个条件可以直接写在Map中 - 多条件自动用 AND 连接,无需手动包装
- 多个条件:必须使用`AND``OR`包装 - 关键字支持大小写(如 `LIMIT``limit` 都有效)
- 特殊条件:`ORDER``GROUP``LIMIT`与条件同级 - 新增 `HAVING` 和独立 `OFFSET` 支持
```go ```go
// ✅ 正确:单个条件 // ✅ 推荐:简化语法(多条件自动 AND
Map{"status": 1} Map{"status": 1, "age[>]": 18}
// 生成: WHERE `status`=? AND `age`>?
// ✅ 正确多个条件用AND包装 // ✅ 仍然支持:显式 AND 包装(向后兼容)
Map{ Map{
"AND": Map{ "AND": Map{
"status": 1, "status": 1,
@ -19,20 +20,12 @@ Map{
}, },
} }
// ✅ 正确:条件 + 特殊参数 // ✅ 混合条件和特殊关键字
Map{ Map{
"AND": Map{
"status": 1, "status": 1,
"age[>]": 18, "age[>]": 18,
}, "ORDER": "id DESC", // 或 "order": "id DESC"
"ORDER": "id DESC", "LIMIT": 10, // 或 "limit": 10
"LIMIT": 10,
}
// ❌ 错误多个条件不用AND包装
Map{
"status": 1,
"age[>]": 18, // 这样写不支持!
} }
``` ```
@ -40,14 +33,14 @@ Map{
### 数据库连接 ### 数据库连接
```go ```go
db.SetConnect(func() (master, slave *sql.DB) { ... }) database.SetConnect(func() (master, slave *sql.DB) { ... })
db.InitDb() database.InitDb()
``` ```
### 链式查询构建器 ### 链式查询构建器
```go ```go
// 创建查询构建器 // 创建查询构建器
builder := db.Table("tablename") builder := database.Table("tablename")
// 设置条件 // 设置条件
builder.Where(key, value) builder.Where(key, value)
@ -65,9 +58,11 @@ builder.Join(map) // 通用JOIN
builder.Order(fields...) builder.Order(fields...)
builder.Group(fields...) builder.Group(fields...)
builder.Limit(args...) builder.Limit(args...)
builder.Having(map) // 新增
// 分页 // 分页
builder.Page(page, pageSize) builder.Page(page, pageSize)
builder.Offset(offset) // 新增
// 执行查询 // 执行查询
builder.Select(fields...) // 返回 []Map builder.Select(fields...) // 返回 []Map
@ -82,36 +77,66 @@ builder.Delete() // 返回 int64
### 查询 (Select) ### 查询 (Select)
```go ```go
// 基本查询 // 基本查询
data := db.Select("table") data := database.Select("table")
data := db.Select("table", "field1,field2") data := database.Select("table", "field1,field2")
data := db.Select("table", []string{"field1", "field2"}) data := database.Select("table", []string{"field1", "field2"})
data := db.Select("table", "*", whereMap) data := database.Select("table", "*", whereMap)
// 带JOIN查询 // 带JOIN查询
data := db.Select("table", joinSlice, "fields", whereMap) data := database.Select("table", joinSlice, "fields", whereMap)
``` ```
### 获取单条 (Get) ### 获取单条 (Get)
```go ```go
// 自动添加 LIMIT 1 // 自动添加 LIMIT 1
row := db.Get("table", "fields", whereMap) row := database.Get("table", "fields", whereMap)
``` ```
### 插入 (Insert) ### 插入 (Insert)
```go ```go
id := db.Insert("table", dataMap) id := database.Insert("table", dataMap)
// 返回新插入记录的ID // 返回新插入记录的ID
``` ```
### 批量插入 (BatchInsert) - 新增
```go
// 使用 []Map 格式,更直观简洁
affected := database.BatchInsert("table", []Map{
{"col1": "val1", "col2": "val2", "col3": "val3"},
{"col1": "val4", "col2": "val5", "col3": "val6"},
})
// 返回受影响的行数
// 支持 [#] 标记直接 SQL
affected := database.BatchInsert("log", []Map{
{"user_id": 1, "created_time[#]": "NOW()"},
{"user_id": 2, "created_time[#]": "NOW()"},
})
```
### 更新 (Update) ### 更新 (Update)
```go ```go
affected := db.Update("table", dataMap, whereMap) affected := database.Update("table", dataMap, whereMap)
// 返回受影响的行数
```
### Upsert - 新增
```go
// 使用 Slice 格式
affected := database.Upsert("table",
dataMap, // 插入数据
Slice{"unique_key"}, // 唯一键
Slice{"col1", "col2"}, // 冲突时更新的字段
)
// 也支持可变参数
affected := database.Upsert("table", dataMap, Slice{"id"}, "col1", "col2")
// 返回受影响的行数 // 返回受影响的行数
``` ```
### 删除 (Delete) ### 删除 (Delete)
```go ```go
affected := db.Delete("table", whereMap) affected := database.Delete("table", whereMap)
// 返回删除的行数 // 返回删除的行数
``` ```
@ -119,25 +144,42 @@ affected := db.Delete("table", whereMap)
### 计数 ### 计数
```go ```go
count := db.Count("table") count := database.Count("table")
count := db.Count("table", whereMap) count := database.Count("table", whereMap)
count := db.Count("table", joinSlice, whereMap) count := database.Count("table", joinSlice, whereMap)
``` ```
### 求和 ### 求和
```go ```go
sum := db.Sum("table", "column") sum := database.Sum("table", "column")
sum := db.Sum("table", "column", whereMap) sum := database.Sum("table", "column", whereMap)
sum := db.Sum("table", "column", joinSlice, whereMap) ```
### 平均值 - 新增
```go
avg := database.Avg("table", "column")
avg := database.Avg("table", "column", whereMap)
```
### 最大值 - 新增
```go
max := database.Max("table", "column")
max := database.Max("table", "column", whereMap)
```
### 最小值 - 新增
```go
min := database.Min("table", "column")
min := database.Min("table", "column", whereMap)
``` ```
## 分页查询 ## 分页查询
```go ```go
// 设置分页 // 设置分页
db.Page(page, pageSize) database.Page(page, pageSize)
// 分页查询 // 分页查询
data := db.Page(page, pageSize).PageSelect("table", "fields", whereMap) data := database.Page(page, pageSize).PageSelect("table", "fields", whereMap)
``` ```
## 条件语法参考 ## 条件语法参考
@ -189,6 +231,14 @@ data := db.Page(page, pageSize).PageSelect("table", "fields", whereMap)
### AND 条件 ### AND 条件
```go ```go
// 简化语法(推荐)
whereMap := Map{
"status": 1,
"age[>]": 18,
}
// 生成: WHERE `status`=? AND `age`>?
// 显式 AND向后兼容
whereMap := Map{ whereMap := Map{
"AND": Map{ "AND": Map{
"status": 1, "status": 1,
@ -249,7 +299,7 @@ Map{
} }
// 或 // 或
Map{ Map{
"ORDER": "created_time DESC", "order": "created_time DESC", // 支持小写
} }
``` ```
@ -260,7 +310,17 @@ Map{
} }
// 或 // 或
Map{ Map{
"GROUP": "department", "group": "department", // 支持小写
}
```
### HAVING - 新增
```go
Map{
"GROUP": "dept_id",
"HAVING": Map{
"COUNT(*).[>]": 5,
},
} }
``` ```
@ -271,13 +331,21 @@ Map{
} }
// 或 // 或
Map{ Map{
"LIMIT": 20, // limit 20 "limit": 20, // limit 20支持小写
}
```
### OFFSET - 新增
```go
Map{
"LIMIT": 10,
"OFFSET": 20, // 独立的 OFFSET
} }
``` ```
## 事务处理 ## 事务处理
```go ```go
success := db.Action(func(tx HoTimeDB) bool { success := database.Action(func(tx HoTimeDB) bool {
// 在这里执行数据库操作 // 在这里执行数据库操作
// 返回 true 提交事务 // 返回 true 提交事务
// 返回 false 回滚事务 // 返回 false 回滚事务
@ -300,40 +368,55 @@ success := db.Action(func(tx HoTimeDB) bool {
### 查询 ### 查询
```go ```go
results := db.Query("SELECT * FROM user WHERE age > ?", 18) results := database.Query("SELECT * FROM user WHERE age > ?", 18)
``` ```
### 执行 ### 执行
```go ```go
result, err := db.Exec("UPDATE user SET status = ? WHERE id = ?", 1, 100) result, err := database.Exec("UPDATE user SET status = ? WHERE id = ?", 1, 100)
affected, _ := result.RowsAffected() affected, _ := result.RowsAffected()
``` ```
## PostgreSQL 支持 - 新增
```go
// 配置 PostgreSQL
database := &db.HoTimeDB{
Type: "postgres", // 设置类型
}
// 框架自动处理差异:
// - 占位符: ? -> $1, $2, $3...
// - 引号: `name` -> "name"
// - Upsert: ON DUPLICATE KEY -> ON CONFLICT
```
## 错误处理 ## 错误处理
```go ```go
// 检查最后的错误 // 检查最后的错误
if db.LastErr.GetError() != nil { if database.LastErr.GetError() != nil {
fmt.Println("错误:", db.LastErr.GetError()) fmt.Println("错误:", database.LastErr.GetError())
} }
// 查看最后执行的SQL // 查看最后执行的SQL
fmt.Println("SQL:", db.LastQuery) fmt.Println("SQL:", database.LastQuery)
fmt.Println("参数:", db.LastData) fmt.Println("参数:", database.LastData)
``` ```
## 工具方法 ## 工具方法
### 数据库信息 ### 数据库信息
```go ```go
prefix := db.GetPrefix() // 获取表前缀 prefix := database.GetPrefix() // 获取表前缀
dbType := db.GetType() // 获取数据库类型 dbType := database.GetType() // 获取数据库类型
dialect := database.GetDialect() // 获取方言适配器
``` ```
### 设置模式 ### 设置模式
```go ```go
db.Mode = 0 // 生产模式 database.Mode = 0 // 生产模式
db.Mode = 1 // 测试模式 database.Mode = 1 // 测试模式
db.Mode = 2 // 开发模式输出SQL日志 database.Mode = 2 // 开发模式输出SQL日志
``` ```
## 常用查询模式 ## 常用查询模式
@ -341,10 +424,10 @@ db.Mode = 2 // 开发模式输出SQL日志
### 分页列表查询 ### 分页列表查询
```go ```go
// 获取总数 // 获取总数
total := db.Count("user", Map{"status": 1}) total := database.Count("user", Map{"status": 1})
// 分页数据 // 分页数据
users := db.Table("user"). users := database.Table("user").
Where("status", 1). Where("status", 1).
Order("created_time DESC"). Order("created_time DESC").
Page(page, pageSize). Page(page, pageSize).
@ -356,7 +439,7 @@ totalPages := (total + pageSize - 1) / pageSize
### 关联查询 ### 关联查询
```go ```go
orders := db.Table("order"). orders := database.Table("order").
LeftJoin("user", "order.user_id = user.id"). LeftJoin("user", "order.user_id = user.id").
LeftJoin("product", "order.product_id = product.id"). LeftJoin("product", "order.product_id = product.id").
Where("order.status", "paid"). Where("order.status", "paid").
@ -369,41 +452,38 @@ orders := db.Table("order").
### 统计查询 ### 统计查询
```go ```go
stats := db.Select("order", stats := database.Select("order",
"user_id, COUNT(*) as order_count, SUM(amount) as total_amount", "user_id, COUNT(*) as order_count, SUM(amount) as total_amount",
Map{ Map{
"AND": Map{
"status": "paid", "status": "paid",
"created_time[>]": "2023-01-01", "created_time[>]": "2023-01-01",
},
"GROUP": "user_id", "GROUP": "user_id",
"ORDER": "total_amount DESC", "ORDER": "total_amount DESC",
}) })
``` ```
### 条件组合查询 ### 批量操作
```go ```go
products := db.Table("product"). // 批量插入(使用 []Map 格式)
Where("status", 1). affected := database.BatchInsert("user", []Map{
And(Map{ {"name": "用户1", "email": "user1@example.com", "status": 1},
"OR": Map{ {"name": "用户2", "email": "user2@example.com", "status": 1},
"category_id": []int{1, 2, 3}, {"name": "用户3", "email": "user3@example.com", "status": 1},
"tags[~]": "热销", })
},
}). // Upsert插入或更新使用 Slice 格式)
And(Map{ affected := database.Upsert("user",
"price[<>]": []float64{10.0, 1000.0}, Map{"id": 1, "name": "新名称", "email": "new@example.com"},
}). Slice{"id"},
Order("sort DESC", "created_time DESC"). Slice{"name", "email"},
Limit(0, 20). )
Select()
``` ```
## 链式调用完整示例 ## 链式调用完整示例
```go ```go
// 复杂查询链式调用 // 复杂查询链式调用
result := db.Table("order"). result := database.Table("order").
LeftJoin("user", "order.user_id = user.id"). LeftJoin("user", "order.user_id = user.id").
LeftJoin("product", "order.product_id = product.id"). LeftJoin("product", "order.product_id = product.id").
Where("order.status", "paid"). Where("order.status", "paid").
@ -415,6 +495,7 @@ result := db.Table("order").
}, },
}). }).
Group("user.id"). Group("user.id").
Having(Map{"total_amount[>]": 500}).
Order("total_amount DESC"). Order("total_amount DESC").
Page(1, 20). Page(1, 20).
Select(` Select(`
@ -428,4 +509,5 @@ result := db.Table("order").
--- ---
*快速参考版本: 1.0* *快速参考版本: 2.0*
*更新日期: 2026年1月*

View File

@ -2,7 +2,7 @@
## 概述 ## 概述
HoTimeDB是一个基于Golang实现的轻量级ORM框架参考PHP Medoo设计提供简洁的数据库操作接口。支持MySQL、SQLite等数据库并集成了缓存、事务、链式查询等功能。 HoTimeDB是一个基于Golang实现的轻量级ORM框架参考PHP Medoo设计提供简洁的数据库操作接口。支持MySQL、SQLite、PostgreSQL等数据库,并集成了缓存、事务、链式查询等功能。
## 目录 ## 目录
@ -12,7 +12,9 @@ HoTimeDB是一个基于Golang实现的轻量级ORM框架参考PHP Medoo设计
- [查询(Select)](#查询select) - [查询(Select)](#查询select)
- [获取单条记录(Get)](#获取单条记录get) - [获取单条记录(Get)](#获取单条记录get)
- [插入(Insert)](#插入insert) - [插入(Insert)](#插入insert)
- [批量插入(BatchInsert)](#批量插入batchinsert)
- [更新(Update)](#更新update) - [更新(Update)](#更新update)
- [Upsert操作](#upsert操作)
- [删除(Delete)](#删除delete) - [删除(Delete)](#删除delete)
- [链式查询构建器](#链式查询构建器) - [链式查询构建器](#链式查询构建器)
- [条件查询语法](#条件查询语法) - [条件查询语法](#条件查询语法)
@ -21,6 +23,7 @@ HoTimeDB是一个基于Golang实现的轻量级ORM框架参考PHP Medoo设计
- [聚合函数](#聚合函数) - [聚合函数](#聚合函数)
- [事务处理](#事务处理) - [事务处理](#事务处理)
- [缓存机制](#缓存机制) - [缓存机制](#缓存机制)
- [PostgreSQL支持](#postgresql支持)
- [高级特性](#高级特性) - [高级特性](#高级特性)
## 快速开始 ## 快速开始
@ -44,8 +47,10 @@ func createConnection() (master, slave *sql.DB) {
} }
// 初始化HoTimeDB // 初始化HoTimeDB
db := &db.HoTimeDB{} database := &db.HoTimeDB{
db.SetConnect(createConnection) Type: "mysql", // 可选mysql, sqlite3, postgres
}
database.SetConnect(createConnection)
``` ```
## 数据库配置 ## 数据库配置
@ -59,7 +64,7 @@ type HoTimeDB struct {
DBName string DBName string
*cache.HoTimeCache *cache.HoTimeCache
Log *logrus.Logger Log *logrus.Logger
Type string // 数据库类型 Type string // 数据库类型mysql, sqlite3, postgres
Prefix string // 表前缀 Prefix string // 表前缀
LastQuery string // 最后执行的SQL LastQuery string // 最后执行的SQL
LastData []interface{} // 最后的参数 LastData []interface{} // 最后的参数
@ -69,19 +74,20 @@ type HoTimeDB struct {
*sql.Tx // 事务对象 *sql.Tx // 事务对象
SlaveDB *sql.DB // 从数据库 SlaveDB *sql.DB // 从数据库
Mode int // 0生产模式,1测试模式,2开发模式 Mode int // 0生产模式,1测试模式,2开发模式
Dialect Dialect // 数据库方言适配器
} }
``` ```
### 设置表前缀 ### 设置表前缀
```go ```go
db.Prefix = "app_" database.Prefix = "app_"
``` ```
### 设置运行模式 ### 设置运行模式
```go ```go
db.Mode = 2 // 开发模式会输出SQL日志 database.Mode = 2 // 开发模式会输出SQL日志
``` ```
## 基本操作 ## 基本操作
@ -92,35 +98,40 @@ db.Mode = 2 // 开发模式会输出SQL日志
```go ```go
// 查询所有字段 // 查询所有字段
users := db.Select("user") users := database.Select("user")
// 查询指定字段 // 查询指定字段
users := db.Select("user", "id,name,email") users := database.Select("user", "id,name,email")
// 查询指定字段(数组形式) // 查询指定字段(数组形式)
users := db.Select("user", []string{"id", "name", "email"}) users := database.Select("user", []string{"id", "name", "email"})
// 单条件查询 // 单条件查询
users := db.Select("user", "*", common.Map{ users := database.Select("user", "*", common.Map{
"status": 1, "status": 1,
}) })
// 多条件查询必须使用AND包装 // 多条件查询(自动用 AND 连接)
users = db.Select("user", "*", common.Map{ users := database.Select("user", "*", common.Map{
"AND": common.Map{
"status": 1, "status": 1,
"age[>]": 18, "age[>]": 18,
},
}) })
// 生成: WHERE `status`=? AND `age`>?
``` ```
#### 复杂条件查询 #### 复杂条件查询
**重要说明多个条件必须使用AND或OR包装不能直接在根Map中写多个字段条件**
```go ```go
// AND条件多个条件必须用AND包装 // 简化语法:多条件自动用 AND 连接
users := db.Select("user", "*", common.Map{ users := database.Select("user", "*", common.Map{
"status": 1,
"age[>]": 18,
"name[~]": "张",
})
// 生成: WHERE `status`=? AND `age`>? AND `name` LIKE ?
// 显式 AND 条件(与上面等效)
users := database.Select("user", "*", common.Map{
"AND": common.Map{ "AND": common.Map{
"status": 1, "status": 1,
"age[>]": 18, "age[>]": 18,
@ -129,7 +140,7 @@ users := db.Select("user", "*", common.Map{
}) })
// OR 条件 // OR 条件
users := db.Select("user", "*", common.Map{ users := database.Select("user", "*", common.Map{
"OR": common.Map{ "OR": common.Map{
"status": 1, "status": 1,
"type": 2, "type": 2,
@ -137,7 +148,7 @@ users := db.Select("user", "*", common.Map{
}) })
// 混合条件(嵌套 AND/OR // 混合条件(嵌套 AND/OR
users := db.Select("user", "*", common.Map{ users := database.Select("user", "*", common.Map{
"AND": common.Map{ "AND": common.Map{
"status": 1, "status": 1,
"OR": common.Map{ "OR": common.Map{
@ -147,18 +158,16 @@ users := db.Select("user", "*", common.Map{
}, },
}) })
// 带ORDER BY、LIMIT等特殊条件 // 带 ORDER BY、LIMIT 等特殊条件(关键字支持大小写)
users := db.Select("user", "*", common.Map{ users := database.Select("user", "*", common.Map{
"AND": common.Map{
"status": 1, "status": 1,
"age[>]": 18, "age[>]": 18,
}, "ORDER": "id DESC", // 或 "order": "id DESC"
"ORDER": "id DESC", "LIMIT": 10, // 或 "limit": 10
"LIMIT": 10,
}) })
// 带多个特殊条件 // 带多个特殊条件
users := db.Select("user", "*", common.Map{ users := database.Select("user", "*", common.Map{
"OR": common.Map{ "OR": common.Map{
"level": "vip", "level": "vip",
"balance[>]": 1000, "balance[>]": 1000,
@ -167,18 +176,33 @@ users := db.Select("user", "*", common.Map{
"GROUP": "department", "GROUP": "department",
"LIMIT": []int{0, 20}, // offset 0, limit 20 "LIMIT": []int{0, 20}, // offset 0, limit 20
}) })
// 使用 HAVING 过滤分组结果
users := database.Select("user", "dept_id, COUNT(*) as cnt", common.Map{
"GROUP": "dept_id",
"HAVING": common.Map{
"cnt[>]": 5,
},
})
// 使用独立 OFFSET
users := database.Select("user", "*", common.Map{
"status": 1,
"LIMIT": 10,
"OFFSET": 20,
})
``` ```
### 获取单条记录(Get) ### 获取单条记录(Get)
```go ```go
// 获取单个用户 // 获取单个用户
user := db.Get("user", "*", common.Map{ user := database.Get("user", "*", common.Map{
"id": 1, "id": 1,
}) })
// 获取指定字段 // 获取指定字段
user := db.Get("user", "id,name,email", common.Map{ user := database.Get("user", "id,name,email", common.Map{
"status": 1, "status": 1,
}) })
``` ```
@ -187,7 +211,7 @@ user := db.Get("user", "id,name,email", common.Map{
```go ```go
// 基本插入 // 基本插入
id := db.Insert("user", common.Map{ id := database.Insert("user", common.Map{
"name": "张三", "name": "张三",
"email": "zhangsan@example.com", "email": "zhangsan@example.com",
"age": 25, "age": 25,
@ -199,11 +223,31 @@ id := db.Insert("user", common.Map{
fmt.Println("插入的用户ID:", id) fmt.Println("插入的用户ID:", id)
``` ```
### 批量插入(BatchInsert)
```go
// 批量插入多条记录(使用 []Map 格式,更直观)
affected := database.BatchInsert("user", []common.Map{
{"name": "张三", "email": "zhang@example.com", "age": 25},
{"name": "李四", "email": "li@example.com", "age": 30},
{"name": "王五", "email": "wang@example.com", "age": 28},
})
// 生成: INSERT INTO `user` (`age`, `email`, `name`) VALUES (?, ?, ?), (?, ?, ?), (?, ?, ?)
fmt.Printf("批量插入 %d 条记录\n", affected)
// 支持 [#] 标记直接插入 SQL 表达式
affected := database.BatchInsert("log", []common.Map{
{"user_id": 1, "action": "login", "created_time[#]": "NOW()"},
{"user_id": 2, "action": "logout", "created_time[#]": "NOW()"},
})
```
### 更新(Update) ### 更新(Update)
```go ```go
// 基本更新 // 基本更新
affected := db.Update("user", common.Map{ affected := database.Update("user", common.Map{
"name": "李四", "name": "李四",
"email": "lisi@example.com", "email": "lisi@example.com",
"updated_time[#]": "NOW()", "updated_time[#]": "NOW()",
@ -211,8 +255,8 @@ affected := db.Update("user", common.Map{
"id": 1, "id": 1,
}) })
// 条件更新 // 条件更新(多条件自动 AND 连接)
affected := db.Update("user", common.Map{ affected := database.Update("user", common.Map{
"status": 0, "status": 0,
}, common.Map{ }, common.Map{
"age[<]": 18, "age[<]": 18,
@ -222,16 +266,60 @@ affected := db.Update("user", common.Map{
fmt.Println("更新的记录数:", affected) fmt.Println("更新的记录数:", affected)
``` ```
### Upsert操作
Upsert插入或更新如果记录存在则更新不存在则插入。
```go
// Upsert 操作(使用 Slice 格式)
affected := database.Upsert("user",
common.Map{
"id": 1,
"name": "张三",
"email": "zhang@example.com",
"login_count": 1,
},
common.Slice{"id"}, // 唯一键(用于冲突检测)
common.Slice{"name", "email", "login_count"}, // 冲突时更新的字段
)
// MySQL 生成:
// INSERT INTO user (id,name,email,login_count) VALUES (?,?,?,?)
// ON DUPLICATE KEY UPDATE name=VALUES(name), email=VALUES(email), login_count=VALUES(login_count)
// PostgreSQL 生成:
// INSERT INTO "user" (id,name,email,login_count) VALUES ($1,$2,$3,$4)
// ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, email=EXCLUDED.email, login_count=EXCLUDED.login_count
// 使用 [#] 标记直接 SQL 更新
affected := database.Upsert("user",
common.Map{
"id": 1,
"name": "张三",
"login_count[#]": "login_count + 1", // 直接 SQL 表达式
},
common.Slice{"id"},
common.Slice{"name", "login_count"},
)
// 也支持可变参数形式
affected := database.Upsert("user",
common.Map{"id": 1, "name": "张三"},
common.Slice{"id"},
"name", "email", // 可变参数
)
```
### 删除(Delete) ### 删除(Delete)
```go ```go
// 根据ID删除 // 根据ID删除
affected := db.Delete("user", common.Map{ affected := database.Delete("user", common.Map{
"id": 1, "id": 1,
}) })
// 条件删除 // 条件删除(多条件自动 AND 连接)
affected := db.Delete("user", common.Map{ affected := database.Delete("user", common.Map{
"status": 0, "status": 0,
"created_time[<]": "2023-01-01", "created_time[<]": "2023-01-01",
}) })
@ -245,7 +333,7 @@ HoTimeDB提供了链式查询构建器让查询更加直观
```go ```go
// 基本链式查询 // 基本链式查询
users := db.Table("user"). users := database.Table("user").
Where("status", 1). Where("status", 1).
And("age[>]", 18). And("age[>]", 18).
Order("created_time DESC"). Order("created_time DESC").
@ -253,12 +341,12 @@ users := db.Table("user").
Select() Select()
// 链式获取单条记录 // 链式获取单条记录
user := db.Table("user"). user := database.Table("user").
Where("id", 1). Where("id", 1).
Get() Get()
// 链式更新 // 链式更新
affected := db.Table("user"). affected := database.Table("user").
Where("id", 1). Where("id", 1).
Update(common.Map{ Update(common.Map{
"name": "新名称", "name": "新名称",
@ -266,12 +354,12 @@ affected := db.Table("user").
}) })
// 链式删除 // 链式删除
affected := db.Table("user"). affected := database.Table("user").
Where("status", 0). Where("status", 0).
Delete() Delete()
// 链式统计 // 链式统计
count := db.Table("user"). count := database.Table("user").
Where("status", 1). Where("status", 1).
Count() Count()
``` ```
@ -280,7 +368,7 @@ count := db.Table("user").
```go ```go
// 复杂条件组合 // 复杂条件组合
users := db.Table("user"). users := database.Table("user").
Where("status", 1). Where("status", 1).
And("age[>=]", 18). And("age[>=]", 18).
Or(common.Map{ Or(common.Map{
@ -289,7 +377,9 @@ users := db.Table("user").
}). }).
Order("created_time DESC", "id ASC"). Order("created_time DESC", "id ASC").
Group("department"). Group("department").
Having(common.Map{"COUNT(*).[>]": 5}). // 新增 HAVING 支持
Limit(0, 20). Limit(0, 20).
Offset(10). // 新增独立 OFFSET 支持
Select("id,name,email,age") Select("id,name,email,age")
``` ```
@ -384,24 +474,24 @@ HoTimeDB支持丰富的条件查询语法类似于Medoo
```go ```go
// LEFT JOIN // LEFT JOIN
users := db.Table("user"). users := database.Table("user").
LeftJoin("profile", "user.id = profile.user_id"). LeftJoin("profile", "user.id = profile.user_id").
LeftJoin("department", "user.dept_id = department.id"). LeftJoin("department", "user.dept_id = department.id").
Where("user.status", 1). Where("user.status", 1).
Select("user.*, profile.avatar, department.name AS dept_name") Select("user.*, profile.avatar, department.name AS dept_name")
// RIGHT JOIN // RIGHT JOIN
users := db.Table("user"). users := database.Table("user").
RightJoin("order", "user.id = order.user_id"). RightJoin("order", "user.id = order.user_id").
Select() Select()
// INNER JOIN // INNER JOIN
users := db.Table("user"). users := database.Table("user").
InnerJoin("profile", "user.id = profile.user_id"). InnerJoin("profile", "user.id = profile.user_id").
Select() Select()
// FULL JOIN // FULL JOIN
users := db.Table("user"). users := database.Table("user").
FullJoin("profile", "user.id = profile.user_id"). FullJoin("profile", "user.id = profile.user_id").
Select() Select()
``` ```
@ -409,7 +499,7 @@ users := db.Table("user").
### 传统JOIN语法 ### 传统JOIN语法
```go ```go
users := db.Select("user", users := database.Select("user",
common.Slice{ common.Slice{
common.Map{"[>]profile": "user.id = profile.user_id"}, common.Map{"[>]profile": "user.id = profile.user_id"},
common.Map{"[>]department": "user.dept_id = department.id"}, common.Map{"[>]department": "user.dept_id = department.id"},
@ -434,12 +524,12 @@ users := db.Select("user",
```go ```go
// 设置分页页码3每页20条 // 设置分页页码3每页20条
users := db.Page(3, 20).PageSelect("user", "*", common.Map{ users := database.Page(3, 20).PageSelect("user", "*", common.Map{
"status": 1, "status": 1,
}) })
// 链式分页 // 链式分页
users := db.Table("user"). users := database.Table("user").
Where("status", 1). Where("status", 1).
Page(2, 15). // 第2页每页15条 Page(2, 15). // 第2页每页15条
Select() Select()
@ -449,7 +539,7 @@ users := db.Table("user").
```go ```go
// 获取总数 // 获取总数
total := db.Count("user", common.Map{ total := database.Count("user", common.Map{
"status": 1, "status": 1,
}) })
@ -468,15 +558,15 @@ fmt.Printf("总记录数: %d, 总页数: %d, 当前页: %d\n", total, totalPages
```go ```go
// 总数统计 // 总数统计
total := db.Count("user") total := database.Count("user")
// 条件统计 // 条件统计
activeUsers := db.Count("user", common.Map{ activeUsers := database.Count("user", common.Map{
"status": 1, "status": 1,
}) })
// JOIN统计 // JOIN统计
count := db.Count("user", count := database.Count("user",
common.Slice{ common.Slice{
common.Map{"[>]profile": "user.id = profile.user_id"}, common.Map{"[>]profile": "user.id = profile.user_id"},
}, },
@ -491,31 +581,47 @@ count := db.Count("user",
```go ```go
// 基本求和 // 基本求和
totalAmount := db.Sum("order", "amount") totalAmount := database.Sum("order", "amount")
// 条件求和 // 条件求和
paidAmount := db.Sum("order", "amount", common.Map{ paidAmount := database.Sum("order", "amount", common.Map{
"status": "paid", "status": "paid",
"created_time[>]": "2023-01-01", "created_time[>]": "2023-01-01",
}) })
```
// JOIN求和 ### 平均值
sum := db.Sum("order", "amount",
common.Slice{ ```go
common.Map{"[>]user": "order.user_id = user.id"}, // 基本平均值
}, avgAge := database.Avg("user", "age")
common.Map{
"user.level": "vip", // 条件平均值
"order.status": "paid", avgAge := database.Avg("user", "age", common.Map{
}, "status": 1,
) })
```
### 最大值/最小值
```go
// 最大值
maxAge := database.Max("user", "age")
// 最小值
minAge := database.Min("user", "age")
// 条件最大值
maxBalance := database.Max("user", "balance", common.Map{
"status": 1,
})
``` ```
## 事务处理 ## 事务处理
```go ```go
// 事务操作 // 事务操作
success := db.Action(func(tx db.HoTimeDB) bool { success := database.Action(func(tx db.HoTimeDB) bool {
// 在事务中执行多个操作 // 在事务中执行多个操作
// 扣减用户余额 // 扣减用户余额
@ -541,18 +647,6 @@ success := db.Action(func(tx db.HoTimeDB) bool {
return false // 回滚 return false // 回滚
} }
// 添加订单详情
detailId := tx.Insert("order_detail", common.Map{
"order_id": orderId,
"product_id": 1001,
"quantity": 1,
"price": 100,
})
if detailId == 0 {
return false // 回滚
}
return true // 提交 return true // 提交
}) })
@ -560,7 +654,7 @@ if success {
fmt.Println("事务执行成功") fmt.Println("事务执行成功")
} else { } else {
fmt.Println("事务回滚") fmt.Println("事务回滚")
fmt.Println("错误:", db.LastErr.GetError()) fmt.Println("错误:", database.LastErr.GetError())
} }
``` ```
@ -574,7 +668,7 @@ HoTimeDB集成了缓存功能可以自动缓存查询结果
import "code.hoteas.com/golang/hotime/cache" import "code.hoteas.com/golang/hotime/cache"
// 设置缓存 // 设置缓存
db.HoTimeCache = &cache.HoTimeCache{ database.HoTimeCache = &cache.HoTimeCache{
// 缓存配置 // 缓存配置
} }
``` ```
@ -590,21 +684,55 @@ db.HoTimeCache = &cache.HoTimeCache{
```go ```go
// 手动清除表缓存 // 手动清除表缓存
db.HoTimeCache.Db("user*", nil) // 清除user表所有缓存 database.HoTimeCache.Db("user*", nil) // 清除user表所有缓存
``` ```
## PostgreSQL支持
HoTimeDB 支持 PostgreSQL 数据库,自动处理语法差异。
### PostgreSQL 配置
```go
import (
"code.hoteas.com/golang/hotime/db"
_ "github.com/lib/pq" // PostgreSQL 驱动
)
database := &db.HoTimeDB{
Type: "postgres", // 设置数据库类型为 postgres
Prefix: "app_",
}
database.SetConnect(func(err ...*common.Error) (master, slave *sql.DB) {
dsn := "host=localhost port=5432 user=postgres password=secret dbname=mydb sslmode=disable"
master, _ = sql.Open("postgres", dsn)
return master, master
})
```
### 主要差异
| 特性 | MySQL | PostgreSQL |
|------|-------|------------|
| 标识符引号 | \`name\` | "name" |
| 占位符 | ? | $1, $2, $3... |
| Upsert | ON DUPLICATE KEY UPDATE | ON CONFLICT DO UPDATE |
所有这些差异由框架自动处理,无需手动调整代码。
## 高级特性 ## 高级特性
### 调试模式 ### 调试模式
```go ```go
// 设置调试模式 // 设置调试模式
db.Mode = 2 database.Mode = 2
// 查看最后执行的SQL // 查看最后执行的SQL
fmt.Println("最后的SQL:", db.LastQuery) fmt.Println("最后的SQL:", database.LastQuery)
fmt.Println("参数:", db.LastData) fmt.Println("参数:", database.LastData)
fmt.Println("错误:", db.LastErr.GetError()) fmt.Println("错误:", database.LastErr.GetError())
``` ```
### 主从分离 ### 主从分离
@ -620,7 +748,7 @@ func createConnection() (master, slave *sql.DB) {
return master, slave return master, slave
} }
db.SetConnect(createConnection) database.SetConnect(createConnection)
// 查询会自动使用从库,增删改使用主库 // 查询会自动使用从库,增删改使用主库
``` ```
@ -628,97 +756,16 @@ db.SetConnect(createConnection)
```go ```go
// 执行查询SQL // 执行查询SQL
results := db.Query("SELECT * FROM user WHERE age > ? AND status = ?", 18, 1) results := database.Query("SELECT * FROM user WHERE age > ? AND status = ?", 18, 1)
// 执行更新SQL // 执行更新SQL
result, err := db.Exec("UPDATE user SET last_login = NOW() WHERE id = ?", 1) result, err := database.Exec("UPDATE user SET last_login = NOW() WHERE id = ?", 1)
if err.GetError() == nil { if err.GetError() == nil {
affected, _ := result.RowsAffected() affected, _ := result.RowsAffected()
fmt.Println("影响行数:", affected) fmt.Println("影响行数:", affected)
} }
``` ```
### 数据类型处理
```go
// 时间戳插入
db.Insert("log", common.Map{
"user_id": 1,
"action": "login",
"created_time[#]": "UNIX_TIMESTAMP()",
})
// JSON数据
db.Insert("user", common.Map{
"name": "张三",
"preferences": `{"theme": "dark", "language": "zh-CN"}`,
})
```
### 数据行处理
HoTimeDB内部的`Row`方法会自动处理不同数据类型:
```go
// 自动转换[]uint8为字符串
// 保持其他类型的原始值
// 处理NULL值
```
## 错误处理
```go
// 检查错误
users := db.Select("user", "*", common.Map{"status": 1})
if db.LastErr.GetError() != nil {
fmt.Println("查询错误:", db.LastErr.GetError())
return
}
// 插入时检查错误
id := db.Insert("user", common.Map{
"name": "test",
"email": "test@example.com",
})
if id == 0 && db.LastErr.GetError() != nil {
fmt.Println("插入失败:", db.LastErr.GetError())
}
```
## 性能优化
### IN查询优化
HoTimeDB会自动优化IN查询将连续的数字转换为BETWEEN查询以提高性能
```go
// 这个查询会被自动优化单个IN条件
users := db.Select("user", "*", common.Map{
"id": []int{1, 2, 3, 4, 5, 10, 11, 12},
// 自动转为 (id BETWEEN 1 AND 5) OR (id BETWEEN 10 AND 12)
})
```
### 批量操作
```go
// 使用事务进行批量插入
success := db.Action(func(tx db.HoTimeDB) bool {
for i := 0; i < 1000; i++ {
id := tx.Insert("user", common.Map{
"name": fmt.Sprintf("User%d", i),
"email": fmt.Sprintf("user%d@example.com", i),
"created_time[#]": "NOW()",
})
if id == 0 {
return false
}
}
return true
})
```
## 特殊语法详解 ## 特殊语法详解
### 条件标记符说明 ### 条件标记符说明
@ -741,150 +788,43 @@ success := db.Action(func(tx db.HoTimeDB) bool {
| `[#!]` | 不等于直接SQL | `"status[#!]": "1"` | `status != 1` | | `[#!]` | 不等于直接SQL | `"status[#!]": "1"` | `status != 1` |
| `[!#]` | 不等于直接SQL | `"status[!#]": "1"` | `status != 1` | | `[!#]` | 不等于直接SQL | `"status[!#]": "1"` | `status != 1` |
## 与PHP Medoo的差异 ### 特殊关键字(支持大小写)
1. **类型系统**: Golang的强类型要求使用`common.Map``common.Slice` | 关键字 | 功能 | 示例 |
2. **语法差异**: 某些条件语法可能略有不同 |--------|------|------|
3. **错误处理**: 使用Golang的错误处理模式 | `ORDER` / `order` | 排序 | `"ORDER": "id DESC"` |
4. **并发安全**: 需要注意并发使用时的安全性 | `GROUP` / `group` | 分组 | `"GROUP": "dept_id"` |
5. **缓存集成**: 内置了缓存功能 | `LIMIT` / `limit` | 限制 | `"LIMIT": 10` |
6. **链式调用**: 提供了更丰富的链式API | `OFFSET` / `offset` | 偏移 | `"OFFSET": 20` |
| `HAVING` / `having` | 分组过滤 | `"HAVING": Map{"cnt[>]": 5}` |
## 完整示例 | `DISTINCT` / `distinct` | 去重 | 在 SELECT 中使用 |
```go
package main
import (
"fmt"
"database/sql"
"code.hoteas.com/golang/hotime/db"
"code.hoteas.com/golang/hotime/common"
_ "github.com/go-sql-driver/mysql"
)
func main() {
// 初始化数据库
database := &db.HoTimeDB{
Prefix: "app_",
Mode: 2, // 开发模式
}
database.SetConnect(func(err ...*common.Error) (master, slave *sql.DB) {
master, _ = sql.Open("mysql", "root:password@tcp(localhost:3306)/testdb")
return master, master
})
// 查询用户列表
users := database.Table("user").
Where("status", 1).
And("age[>=]", 18).
Order("created_time DESC").
Limit(0, 10).
Select("id,name,email,age")
for _, user := range users {
fmt.Printf("用户: %s, 邮箱: %s, 年龄: %v\n",
user.GetString("name"),
user.GetString("email"),
user.Get("age"))
}
// 创建新用户
userId := database.Insert("user", common.Map{
"name": "新用户",
"email": "new@example.com",
"age": 25,
"status": 1,
"created_time[#]": "NOW()",
})
fmt.Printf("创建用户ID: %d\n", userId)
// 更新用户
affected := database.Table("user").
Where("id", userId).
Update(common.Map{
"last_login[#]": "NOW()",
"login_count[#]": "login_count + 1",
})
fmt.Printf("更新记录数: %d\n", affected)
// 复杂查询示例
orders := database.Table("order").
LeftJoin("user", "order.user_id = user.id").
LeftJoin("product", "order.product_id = product.id").
Where("order.status", "paid").
And("order.created_time[>]", "2023-01-01").
And(common.Map{
"OR": common.Map{
"user.level": "vip",
"order.amount[>]": 1000,
},
}).
Order("order.created_time DESC").
Group("user.id").
Select("user.name, user.email, SUM(order.amount) as total_amount, COUNT(*) as order_count")
// 事务示例
success := database.Action(func(tx db.HoTimeDB) bool {
// 在事务中执行操作
orderId := tx.Insert("order", common.Map{
"user_id": userId,
"total": 100.50,
"status": "pending",
"created_time[#]": "NOW()",
})
if orderId == 0 {
return false
}
detailId := tx.Insert("order_detail", common.Map{
"order_id": orderId,
"product_id": 1,
"quantity": 2,
"price": 50.25,
})
return detailId > 0
})
if success {
fmt.Println("订单创建成功")
} else {
fmt.Println("订单创建失败:", database.LastErr.GetError())
}
// 统计示例
totalUsers := database.Count("user", common.Map{"status": 1})
totalAmount := database.Sum("order", "amount", common.Map{"status": "paid"})
fmt.Printf("活跃用户数: %d, 总交易额: %.2f\n", totalUsers, totalAmount)
}
```
## 常见问题 ## 常见问题
### Q1: 如何处理事务中的错误 ### Q1: 多条件查询需要用 AND 包装吗?
A1: `Action`函数中返回`false`即可触发回滚,所有操作都会被撤销 A1: 不再需要!现在多条件会自动用 AND 连接。当然,使用 `AND` 包装仍然有效(向后兼容)。
### Q2: 缓存何时会被清除 ### Q2: 如何处理事务中的错误?
A2: 执行`Insert``Update``Delete`操作时会自动清除对应表的缓存 A2: 在 `Action` 函数中返回 `false` 即可触发回滚,所有操作都会被撤销。
### Q3: 如何执行复杂的原生SQL ### Q3: 缓存何时会被清除?
A3: 使用`Query`方法执行查询,使用`Exec`方法执行更新操作 A3: 执行 `Insert``Update``Delete``Upsert``BatchInsert` 操作时会自动清除对应表的缓存。
### Q4: 主从分离如何工作 ### Q4: 如何执行复杂的原生SQL
A4: 查询操作自动使用从库(如果配置了),增删改操作使用主库 A4: 使用 `Query` 方法执行查询,使用 `Exec` 方法执行更新操作。
### Q5: 如何处理NULL值 ### Q5: 主从分离如何工作?
A5: 使用`nil`作为值,查询时使用`"field": nil`表示`IS NULL` A5: 查询操作自动使用从库(如果配置了),增删改操作使用主库。
### Q6: 如何处理NULL值
A6: 使用 `nil` 作为值,查询时使用 `"field": nil` 表示 `IS NULL`
### Q7: PostgreSQL 和 MySQL 语法有区别吗?
A7: 框架会自动处理差异(占位符、引号等),代码无需修改。
--- ---
*文档版本: 1.0* *文档版本: 2.0*
*最后更新: 2024年* *最后更新: 2026年1月*
> 本文档基于HoTimeDB源码分析生成如有疑问请参考源码实现。该ORM框架参考了PHP Medoo的设计理念但根据Golang语言特性进行了适配和优化。 > 本文档基于HoTimeDB源码分析生成如有疑问请参考源码实现。该ORM框架参考了PHP Medoo的设计理念但根据Golang语言特性进行了适配和优化。

115
db/aggregate.go Normal file
View File

@ -0,0 +1,115 @@
package db
import (
. "code.hoteas.com/golang/hotime/common"
)
// Count 计数
func (that *HoTimeDB) Count(table string, qu ...interface{}) int {
var req = []interface{}{}
if len(qu) == 2 {
req = append(req, qu[0])
req = append(req, "COUNT(*)")
req = append(req, qu[1])
} else {
req = append(req, "COUNT(*)")
req = append(req, qu...)
}
data := that.Select(table, req...)
if len(data) == 0 {
return 0
}
res := ObjToStr(data[0]["COUNT(*)"])
count, _ := StrToInt(res)
return count
}
// Sum 求和
func (that *HoTimeDB) Sum(table string, column string, qu ...interface{}) float64 {
var req = []interface{}{}
if len(qu) == 2 {
req = append(req, qu[0])
req = append(req, "SUM("+column+")")
req = append(req, qu[1])
} else {
req = append(req, "SUM("+column+")")
req = append(req, qu...)
}
data := that.Select(table, req...)
if len(data) == 0 {
return 0
}
res := ObjToStr(data[0]["SUM("+column+")"])
sum := ObjToFloat64(res)
return sum
}
// Avg 平均值
func (that *HoTimeDB) Avg(table string, column string, qu ...interface{}) float64 {
var req = []interface{}{}
if len(qu) == 2 {
req = append(req, qu[0])
req = append(req, "AVG("+column+")")
req = append(req, qu[1])
} else {
req = append(req, "AVG("+column+")")
req = append(req, qu...)
}
data := that.Select(table, req...)
if len(data) == 0 {
return 0
}
res := ObjToStr(data[0]["AVG("+column+")"])
avg := ObjToFloat64(res)
return avg
}
// Max 最大值
func (that *HoTimeDB) Max(table string, column string, qu ...interface{}) float64 {
var req = []interface{}{}
if len(qu) == 2 {
req = append(req, qu[0])
req = append(req, "MAX("+column+")")
req = append(req, qu[1])
} else {
req = append(req, "MAX("+column+")")
req = append(req, qu...)
}
data := that.Select(table, req...)
if len(data) == 0 {
return 0
}
res := ObjToStr(data[0]["MAX("+column+")"])
max := ObjToFloat64(res)
return max
}
// Min 最小值
func (that *HoTimeDB) Min(table string, column string, qu ...interface{}) float64 {
var req = []interface{}{}
if len(qu) == 2 {
req = append(req, qu[0])
req = append(req, "MIN("+column+")")
req = append(req, qu[1])
} else {
req = append(req, "MIN("+column+")")
req = append(req, qu...)
}
data := that.Select(table, req...)
if len(data) == 0 {
return 0
}
res := ObjToStr(data[0]["MIN("+column+")"])
min := ObjToFloat64(res)
return min
}

93
db/backup.go Normal file
View File

@ -0,0 +1,93 @@
package db
import (
. "code.hoteas.com/golang/hotime/common"
"os"
"strings"
)
// backupSave 保存备份
// path: 备份文件路径
// tt: 表名
// code: 备份类型 0=全部, 1=仅数据, 2=仅DDL
func (that *HoTimeDB) backupSave(path string, tt string, code int) {
fd, _ := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
defer fd.Close()
str := "\r\n"
if code == 0 || code == 2 {
str += that.backupDdl(tt)
}
if code == 0 || code == 1 {
str += "insert into `" + tt + "`\r\n\r\n("
str += that.backupCol(tt)
}
_, _ = fd.Write([]byte(str))
}
// backupDdl 备份表结构DDL
func (that *HoTimeDB) backupDdl(tt string) string {
data := that.Query("show create table " + tt)
if len(data) == 0 {
return ""
}
return ObjToStr(data[0]["Create Table"]) + ";\r\n\r\n"
}
// backupCol 备份表数据
func (that *HoTimeDB) backupCol(tt string) string {
str := ""
data := that.Select(tt, "*")
lthData := len(data)
if lthData == 0 {
return str
}
lthCol := len(data[0])
col := make([]string, lthCol)
tempLthData := 0
for k := range data[0] {
if tempLthData == lthCol-1 {
str += "`" + k + "`) "
} else {
str += "`" + k + "`,"
}
col[tempLthData] = k
tempLthData++
}
str += " values"
for j := 0; j < lthData; j++ {
for m := 0; m < lthCol; m++ {
if m == 0 {
str += "("
}
v := "NULL"
if data[j][col[m]] != nil {
v = "'" + strings.Replace(ObjToStr(data[j][col[m]]), "'", `\'`, -1) + "'"
}
if m == lthCol-1 {
str += v + ") "
} else {
str += v + ","
}
}
if j == lthData-1 {
str += ";\r\n\r\n"
} else {
str += ",\r\n\r\n"
}
}
return str
}

273
db/builder.go Normal file
View File

@ -0,0 +1,273 @@
package db
import (
. "code.hoteas.com/golang/hotime/common"
)
// HotimeDBBuilder 链式查询构建器
type HotimeDBBuilder struct {
HoTimeDB *HoTimeDB
table string
selects []interface{}
join Slice
where Map
lastWhere Map
page int
pageRow int
}
// Table 创建链式查询构建器
func (that *HoTimeDB) Table(table string) *HotimeDBBuilder {
return &HotimeDBBuilder{HoTimeDB: that, table: table, where: Map{}}
}
// Get 获取单条记录
func (that *HotimeDBBuilder) Get(qu ...interface{}) Map {
return that.HoTimeDB.Get(that.table, that.join, qu, that.where)
}
// Count 统计数量
func (that *HotimeDBBuilder) Count() int {
return that.HoTimeDB.Count(that.table, that.join, that.where)
}
// Page 设置分页
func (that *HotimeDBBuilder) Page(page, pageRow int) *HotimeDBBuilder {
that.page = page
that.pageRow = pageRow
return that
}
// Select 查询多条记录
func (that *HotimeDBBuilder) Select(qu ...interface{}) []Map {
if that.page != 0 {
return that.HoTimeDB.Page(that.page, that.pageRow).PageSelect(that.table, that.join, qu, that.where)
}
return that.HoTimeDB.Select(that.table, that.join, qu, that.where)
}
// Update 更新记录
func (that *HotimeDBBuilder) Update(qu ...interface{}) int64 {
lth := len(qu)
if lth == 0 {
return 0
}
data := Map{}
if lth == 1 {
data = ObjToMap(qu[0])
}
if lth > 1 {
for k := 1; k < lth; k++ {
data[ObjToStr(qu[k-1])] = qu[k]
k++
}
}
return that.HoTimeDB.Update(that.table, data, that.where)
}
// Delete 删除记录
func (that *HotimeDBBuilder) Delete() int64 {
return that.HoTimeDB.Delete(that.table, that.where)
}
// LeftJoin 左连接
func (that *HotimeDBBuilder) LeftJoin(table, joinStr string) *HotimeDBBuilder {
that.Join(Map{"[>]" + table: joinStr})
return that
}
// RightJoin 右连接
func (that *HotimeDBBuilder) RightJoin(table, joinStr string) *HotimeDBBuilder {
that.Join(Map{"[<]" + table: joinStr})
return that
}
// InnerJoin 内连接
func (that *HotimeDBBuilder) InnerJoin(table, joinStr string) *HotimeDBBuilder {
that.Join(Map{"[><]" + table: joinStr})
return that
}
// FullJoin 全连接
func (that *HotimeDBBuilder) FullJoin(table, joinStr string) *HotimeDBBuilder {
that.Join(Map{"[<>]" + table: joinStr})
return that
}
// Join 通用连接
func (that *HotimeDBBuilder) Join(qu ...interface{}) *HotimeDBBuilder {
lth := len(qu)
if lth == 0 {
return that
}
data := Map{}
if lth == 1 {
data = ObjToMap(qu[0])
}
if lth > 1 {
for k := 1; k < lth; k++ {
data[ObjToStr(qu[k-1])] = qu[k]
k++
}
}
if that.join == nil {
that.join = Slice{}
}
if data == nil {
return that
}
that.join = append(that.join, data)
return that
}
// And 添加 AND 条件
func (that *HotimeDBBuilder) And(qu ...interface{}) *HotimeDBBuilder {
lth := len(qu)
if lth == 0 {
return that
}
var where Map
if lth == 1 {
where = ObjToMap(qu[0])
}
if lth > 1 {
where = Map{}
for k := 1; k < lth; k++ {
where[ObjToStr(qu[k-1])] = qu[k]
k++
}
}
if where == nil {
return that
}
if that.lastWhere != nil {
that.lastWhere["AND"] = where
that.lastWhere = where
return that
}
that.lastWhere = where
that.where = Map{"AND": where}
return that
}
// Or 添加 OR 条件
func (that *HotimeDBBuilder) Or(qu ...interface{}) *HotimeDBBuilder {
lth := len(qu)
if lth == 0 {
return that
}
var where Map
if lth == 1 {
where = ObjToMap(qu[0])
}
if lth > 1 {
where = Map{}
for k := 1; k < lth; k++ {
where[ObjToStr(qu[k-1])] = qu[k]
k++
}
}
if where == nil {
return that
}
if that.lastWhere != nil {
that.lastWhere["OR"] = where
that.lastWhere = where
return that
}
that.lastWhere = where
that.where = Map{"Or": where}
return that
}
// Where 设置 WHERE 条件
func (that *HotimeDBBuilder) Where(qu ...interface{}) *HotimeDBBuilder {
lth := len(qu)
if lth == 0 {
return that
}
var where Map
if lth == 1 {
where = ObjToMap(qu[0])
}
if lth > 1 {
where = Map{}
for k := 1; k < lth; k++ {
where[ObjToStr(qu[k-1])] = qu[k]
k++
}
}
if where == nil {
return that
}
if that.lastWhere != nil {
that.lastWhere["AND"] = where
that.lastWhere = where
return that
}
that.lastWhere = where
that.where = Map{"AND": that.lastWhere}
return that
}
// From 设置表名
func (that *HotimeDBBuilder) From(table string) *HotimeDBBuilder {
that.table = table
return that
}
// Order 设置排序
func (that *HotimeDBBuilder) Order(qu ...interface{}) *HotimeDBBuilder {
that.where["ORDER"] = ObjToSlice(qu)
return that
}
// Limit 设置限制
func (that *HotimeDBBuilder) Limit(qu ...interface{}) *HotimeDBBuilder {
that.where["LIMIT"] = ObjToSlice(qu)
return that
}
// Group 设置分组
func (that *HotimeDBBuilder) Group(qu ...interface{}) *HotimeDBBuilder {
that.where["GROUP"] = ObjToSlice(qu)
return that
}
// Having 设置 HAVING 条件
func (that *HotimeDBBuilder) Having(qu ...interface{}) *HotimeDBBuilder {
lth := len(qu)
if lth == 0 {
return that
}
var having Map
if lth == 1 {
having = ObjToMap(qu[0])
}
if lth > 1 {
having = Map{}
for k := 1; k < lth; k++ {
having[ObjToStr(qu[k-1])] = qu[k]
k++
}
}
that.where["HAVING"] = having
return that
}
// Offset 设置偏移量
func (that *HotimeDBBuilder) Offset(offset int) *HotimeDBBuilder {
that.where["OFFSET"] = offset
return that
}

660
db/crud.go Normal file
View File

@ -0,0 +1,660 @@
package db
import (
"reflect"
"sort"
"strings"
. "code.hoteas.com/golang/hotime/common"
)
// Page 设置分页参数
// page: 页码从1开始
// pageRow: 每页数量
func (that *HoTimeDB) Page(page, pageRow int) *HoTimeDB {
if page < 1 {
page = 1
}
if pageRow < 1 {
pageRow = 10
}
offset := (page - 1) * pageRow
that.limitMu.Lock()
that.limit = Slice{offset, pageRow}
that.limitMu.Unlock()
return that
}
// PageSelect 分页查询
func (that *HoTimeDB) PageSelect(table string, qu ...interface{}) []Map {
that.limitMu.Lock()
limit := that.limit
that.limit = nil // 使用后清空,避免影响下次调用
that.limitMu.Unlock()
if limit == nil {
return that.Select(table, qu...)
}
// 根据参数数量处理 LIMIT 注入
switch len(qu) {
case 0:
// PageSelect("user") -> 只有表名,添加 LIMIT
qu = append(qu, "*", Map{"LIMIT": limit})
case 1:
// PageSelect("user", "*") 或 PageSelect("user", Map{...})
if reflect.ValueOf(qu[0]).Kind() == reflect.Map {
// 是 where 条件
temp := DeepCopyMap(qu[0]).(Map)
temp["LIMIT"] = limit
qu[0] = temp
} else {
// 是字段选择
qu = append(qu, Map{"LIMIT": limit})
}
case 2:
// PageSelect("user", "*", Map{...}) 或 PageSelect("user", joinSlice, "*")
if reflect.ValueOf(qu[1]).Kind() == reflect.Map {
temp := DeepCopyMap(qu[1]).(Map)
temp["LIMIT"] = limit
qu[1] = temp
} else {
// join 模式,需要追加 where
qu = append(qu, Map{"LIMIT": limit})
}
case 3:
// PageSelect("user", joinSlice, "*", Map{...})
temp := DeepCopyMap(qu[2]).(Map)
temp["LIMIT"] = limit
qu[2] = temp
}
return that.Select(table, qu...)
}
// Select 查询多条记录
func (that *HoTimeDB) Select(table string, qu ...interface{}) []Map {
query := "SELECT"
where := Map{}
qs := make([]interface{}, 0)
intQs, intWhere := 0, 1
join := false
if len(qu) == 3 {
intQs = 1
intWhere = 2
join = true
}
if len(qu) > 0 {
if reflect.ValueOf(qu[intQs]).Type().String() == "string" {
query += " " + qu[intQs].(string)
} else {
data := ObjToSlice(qu[intQs])
for i := 0; i < len(data); i++ {
k := data.GetString(i)
if strings.Contains(k, " AS ") || strings.Contains(k, ".") {
query += " " + k + " "
} else {
query += " `" + k + "` "
}
if i+1 != len(data) {
query = query + ", "
}
}
}
} else {
query += " *"
}
if !strings.Contains(table, ".") && !strings.Contains(table, " AS ") {
query += " FROM `" + that.Prefix + table + "` "
} else {
query += " FROM " + that.Prefix + table + " "
}
if join {
query += that.buildJoin(qu[0])
}
if len(qu) > 1 {
where = qu[intWhere].(Map)
}
temp, resWhere := that.where(where)
query += temp + ";"
qs = append(qs, resWhere...)
md5 := that.md5(query, qs...)
if that.HoTimeCache != nil && table != "cached" {
// 如果缓存有则从缓存取
cacheData := that.HoTimeCache.Db(table + ":" + md5)
if cacheData != nil && cacheData.Data != nil {
return cacheData.ToMapArray()
}
}
// 无缓存则数据库取
res := that.Query(query, qs...)
if res == nil {
res = []Map{}
}
// 缓存
if that.HoTimeCache != nil && table != "cached" {
_ = that.HoTimeCache.Db(table+":"+md5, res)
}
return res
}
// buildJoin 构建 JOIN 语句
func (that *HoTimeDB) buildJoin(joinData interface{}) string {
query := ""
var testQu = []string{}
testQuData := Map{}
if reflect.ValueOf(joinData).Type().String() == "common.Map" {
testQuData = joinData.(Map)
for key := range testQuData {
testQu = append(testQu, key)
}
}
if reflect.ValueOf(joinData).Type().String() == "common.Slice" || strings.Contains(reflect.ValueOf(joinData).Type().String(), "[]") {
qu0 := ObjToSlice(joinData)
for key := range qu0 {
v := qu0.GetMap(key)
for k1, v1 := range v {
testQu = append(testQu, k1)
testQuData[k1] = v1
}
}
}
sort.Strings(testQu)
for _, k := range testQu {
v := testQuData[k]
switch Substr(k, 0, 3) {
case "[>]":
func() {
table := Substr(k, 3, len(k)-3)
if !strings.Contains(table, " ") {
table = "`" + table + "`"
}
query += " LEFT JOIN " + table + " ON " + v.(string) + " "
}()
case "[<]":
func() {
table := Substr(k, 3, len(k)-3)
if !strings.Contains(table, " ") {
table = "`" + table + "`"
}
query += " RIGHT JOIN " + table + " ON " + v.(string) + " "
}()
}
switch Substr(k, 0, 4) {
case "[<>]":
func() {
table := Substr(k, 4, len(k)-4)
if !strings.Contains(table, " ") {
table = "`" + table + "`"
}
query += " FULL JOIN " + table + " ON " + v.(string) + " "
}()
case "[><]":
func() {
table := Substr(k, 4, len(k)-4)
if !strings.Contains(table, " ") {
table = "`" + table + "`"
}
query += " INNER JOIN " + table + " ON " + v.(string) + " "
}()
}
}
return query
}
// Get 获取单条记录
func (that *HoTimeDB) Get(table string, qu ...interface{}) Map {
if len(qu) == 1 {
qu = append(qu, Map{"LIMIT": 1})
}
if len(qu) == 2 {
temp := qu[1].(Map)
temp["LIMIT"] = 1
qu[1] = temp
}
if len(qu) == 3 {
temp := qu[2].(Map)
temp["LIMIT"] = 1
qu[2] = temp
}
data := that.Select(table, qu...)
if len(data) == 0 {
return nil
}
return data[0]
}
// Insert 插入新数据
func (that *HoTimeDB) Insert(table string, data map[string]interface{}) int64 {
values := make([]interface{}, 0)
queryString := " ("
valueString := " ("
lens := len(data)
tempLen := 0
for k, v := range data {
tempLen++
vstr := "?"
if Substr(k, len(k)-3, 3) == "[#]" {
k = strings.Replace(k, "[#]", "", -1)
vstr = ObjToStr(v)
if tempLen < lens {
queryString += "`" + k + "`,"
valueString += vstr + ","
} else {
queryString += "`" + k + "`) "
valueString += vstr + ");"
}
} else {
values = append(values, v)
if tempLen < lens {
queryString += "`" + k + "`,"
valueString += "?,"
} else {
queryString += "`" + k + "`) "
valueString += "?);"
}
}
}
query := "INSERT INTO `" + that.Prefix + table + "` " + queryString + "VALUES" + valueString
res, err := that.Exec(query, values...)
id := int64(0)
if err.GetError() == nil && res != nil {
id1, err := res.LastInsertId()
that.LastErr.SetError(err)
id = id1
}
// 如果插入成功,删除缓存
if id != 0 {
if that.HoTimeCache != nil && table != "cached" {
_ = that.HoTimeCache.Db(table+"*", nil)
}
}
return id
}
// BatchInsert 批量插入数据
// table: 表名
// dataList: 数据列表,每个元素是一个 Map
// 返回受影响的行数
//
// 示例:
//
// affected := db.BatchInsert("user", []Map{
// {"name": "张三", "age": 25, "email": "zhang@example.com"},
// {"name": "李四", "age": 30, "email": "li@example.com"},
// {"name": "王五", "age": 28, "email": "wang@example.com"},
// })
func (that *HoTimeDB) BatchInsert(table string, dataList []Map) int64 {
if len(dataList) == 0 {
return 0
}
// 从第一条数据提取所有列名(确保顺序一致)
columns := make([]string, 0)
rawValues := make(map[string]string) // 存储 [#] 标记的直接 SQL 值
for k := range dataList[0] {
realKey := k
if Substr(k, len(k)-3, 3) == "[#]" {
realKey = strings.Replace(k, "[#]", "", -1)
rawValues[realKey] = ObjToStr(dataList[0][k])
}
columns = append(columns, realKey)
}
// 排序列名以确保一致性
sort.Strings(columns)
// 构建列名部分
quotedCols := make([]string, len(columns))
for i, col := range columns {
quotedCols[i] = "`" + col + "`"
}
colStr := strings.Join(quotedCols, ", ")
// 构建每行的占位符和值
placeholders := make([]string, len(dataList))
values := make([]interface{}, 0, len(dataList)*len(columns))
for i, data := range dataList {
rowPlaceholders := make([]string, len(columns))
for j, col := range columns {
// 检查是否有 [#] 标记
rawKey := col + "[#]"
if rawVal, ok := data[rawKey]; ok {
// 直接 SQL 表达式
rowPlaceholders[j] = ObjToStr(rawVal)
} else if _, isRaw := rawValues[col]; isRaw && i == 0 {
// 第一条数据中的 [#] 标记
rowPlaceholders[j] = rawValues[col]
} else if val, ok := data[col]; ok {
// 普通值
rowPlaceholders[j] = "?"
values = append(values, val)
} else {
// 字段不存在,使用 NULL
rowPlaceholders[j] = "NULL"
}
}
placeholders[i] = "(" + strings.Join(rowPlaceholders, ", ") + ")"
}
query := "INSERT INTO `" + that.Prefix + table + "` (" + colStr + ") VALUES " + strings.Join(placeholders, ", ")
res, err := that.Exec(query, values...)
rows64 := int64(0)
if err.GetError() == nil && res != nil {
rows64, _ = res.RowsAffected()
}
// 如果插入成功,删除缓存
if rows64 != 0 {
if that.HoTimeCache != nil && table != "cached" {
_ = that.HoTimeCache.Db(table+"*", nil)
}
}
return rows64
}
// Upsert 插入或更新数据
// table: 表名
// data: 要插入的数据
// uniqueKeys: 唯一键字段(用于冲突检测),支持 Slice{"id"} 或 Slice{"col1", "col2"}
// updateColumns: 冲突时要更新的字段(如果为空,则更新所有非唯一键字段)
// 返回受影响的行数
//
// 示例:
//
// affected := db.Upsert("user",
// Map{"id": 1, "name": "张三", "email": "zhang@example.com"},
// Slice{"id"}, // 唯一键
// Slice{"name", "email"}, // 冲突时更新的字段
// )
func (that *HoTimeDB) Upsert(table string, data Map, uniqueKeys Slice, updateColumns ...interface{}) int64 {
if len(data) == 0 || len(uniqueKeys) == 0 {
return 0
}
// 转换 uniqueKeys 为 []string
uniqueKeyStrs := make([]string, len(uniqueKeys))
for i, uk := range uniqueKeys {
uniqueKeyStrs[i] = ObjToStr(uk)
}
// 转换 updateColumns 为 []string
var updateColumnStrs []string
if len(updateColumns) > 0 {
// 支持两种调用方式Upsert(table, data, Slice{"id"}, Slice{"name"}) 或 Upsert(table, data, Slice{"id"}, "name", "email")
if slice, ok := updateColumns[0].(Slice); ok {
updateColumnStrs = make([]string, len(slice))
for i, col := range slice {
updateColumnStrs[i] = ObjToStr(col)
}
} else {
updateColumnStrs = make([]string, len(updateColumns))
for i, col := range updateColumns {
updateColumnStrs[i] = ObjToStr(col)
}
}
}
// 收集列和值
columns := make([]string, 0, len(data))
values := make([]interface{}, 0, len(data))
rawValues := make(map[string]string) // 存储 [#] 标记的直接 SQL 值
for k, v := range data {
if Substr(k, len(k)-3, 3) == "[#]" {
realKey := strings.Replace(k, "[#]", "", -1)
columns = append(columns, realKey)
rawValues[realKey] = ObjToStr(v)
} else {
columns = append(columns, k)
values = append(values, v)
}
}
// 如果没有指定更新字段,则更新所有非唯一键字段
if len(updateColumnStrs) == 0 {
uniqueKeySet := make(map[string]bool)
for _, uk := range uniqueKeyStrs {
uniqueKeySet[uk] = true
}
for _, col := range columns {
if !uniqueKeySet[col] {
updateColumnStrs = append(updateColumnStrs, col)
}
}
}
// 构建 SQL
var query string
dbType := that.Type
if dbType == "" {
dbType = "mysql"
}
switch dbType {
case "postgres", "postgresql":
query = that.buildPostgresUpsert(table, columns, uniqueKeyStrs, updateColumnStrs, rawValues)
case "sqlite3", "sqlite":
query = that.buildSQLiteUpsert(table, columns, uniqueKeyStrs, updateColumnStrs, rawValues)
default: // mysql
query = that.buildMySQLUpsert(table, columns, uniqueKeyStrs, updateColumnStrs, rawValues)
}
res, err := that.Exec(query, values...)
rows := int64(0)
if err.GetError() == nil && res != nil {
rows, _ = res.RowsAffected()
}
// 清除缓存
if rows != 0 {
if that.HoTimeCache != nil && table != "cached" {
_ = that.HoTimeCache.Db(table+"*", nil)
}
}
return rows
}
// buildMySQLUpsert 构建 MySQL 的 Upsert 语句
func (that *HoTimeDB) buildMySQLUpsert(table string, columns []string, uniqueKeys []string, updateColumns []string, rawValues map[string]string) string {
// INSERT INTO table (col1, col2) VALUES (?, ?)
// ON DUPLICATE KEY UPDATE col1 = VALUES(col1), col2 = VALUES(col2)
quotedCols := make([]string, len(columns))
valueParts := make([]string, len(columns))
for i, col := range columns {
quotedCols[i] = "`" + col + "`"
if raw, ok := rawValues[col]; ok {
valueParts[i] = raw
} else {
valueParts[i] = "?"
}
}
updateParts := make([]string, len(updateColumns))
for i, col := range updateColumns {
if raw, ok := rawValues[col]; ok {
updateParts[i] = "`" + col + "` = " + raw
} else {
updateParts[i] = "`" + col + "` = VALUES(`" + col + "`)"
}
}
return "INSERT INTO `" + that.Prefix + table + "` (" + strings.Join(quotedCols, ", ") +
") VALUES (" + strings.Join(valueParts, ", ") +
") ON DUPLICATE KEY UPDATE " + strings.Join(updateParts, ", ")
}
// buildPostgresUpsert 构建 PostgreSQL 的 Upsert 语句
func (that *HoTimeDB) buildPostgresUpsert(table string, columns []string, uniqueKeys []string, updateColumns []string, rawValues map[string]string) string {
// INSERT INTO table (col1, col2) VALUES ($1, $2)
// ON CONFLICT (unique_key) DO UPDATE SET col1 = EXCLUDED.col1
quotedCols := make([]string, len(columns))
valueParts := make([]string, len(columns))
paramIndex := 1
for i, col := range columns {
quotedCols[i] = "\"" + col + "\""
if raw, ok := rawValues[col]; ok {
valueParts[i] = raw
} else {
valueParts[i] = "$" + ObjToStr(paramIndex)
paramIndex++
}
}
quotedUniqueKeys := make([]string, len(uniqueKeys))
for i, key := range uniqueKeys {
quotedUniqueKeys[i] = "\"" + key + "\""
}
updateParts := make([]string, len(updateColumns))
for i, col := range updateColumns {
if raw, ok := rawValues[col]; ok {
updateParts[i] = "\"" + col + "\" = " + raw
} else {
updateParts[i] = "\"" + col + "\" = EXCLUDED.\"" + col + "\""
}
}
return "INSERT INTO \"" + that.Prefix + table + "\" (" + strings.Join(quotedCols, ", ") +
") VALUES (" + strings.Join(valueParts, ", ") +
") ON CONFLICT (" + strings.Join(quotedUniqueKeys, ", ") +
") DO UPDATE SET " + strings.Join(updateParts, ", ")
}
// buildSQLiteUpsert 构建 SQLite 的 Upsert 语句
func (that *HoTimeDB) buildSQLiteUpsert(table string, columns []string, uniqueKeys []string, updateColumns []string, rawValues map[string]string) string {
// INSERT INTO table (col1, col2) VALUES (?, ?)
// ON CONFLICT (unique_key) DO UPDATE SET col1 = excluded.col1
quotedCols := make([]string, len(columns))
valueParts := make([]string, len(columns))
for i, col := range columns {
quotedCols[i] = "\"" + col + "\""
if raw, ok := rawValues[col]; ok {
valueParts[i] = raw
} else {
valueParts[i] = "?"
}
}
quotedUniqueKeys := make([]string, len(uniqueKeys))
for i, key := range uniqueKeys {
quotedUniqueKeys[i] = "\"" + key + "\""
}
updateParts := make([]string, len(updateColumns))
for i, col := range updateColumns {
if raw, ok := rawValues[col]; ok {
updateParts[i] = "\"" + col + "\" = " + raw
} else {
updateParts[i] = "\"" + col + "\" = excluded.\"" + col + "\""
}
}
return "INSERT INTO \"" + that.Prefix + table + "\" (" + strings.Join(quotedCols, ", ") +
") VALUES (" + strings.Join(valueParts, ", ") +
") ON CONFLICT (" + strings.Join(quotedUniqueKeys, ", ") +
") DO UPDATE SET " + strings.Join(updateParts, ", ")
}
// Update 更新数据
func (that *HoTimeDB) Update(table string, data Map, where Map) int64 {
query := "UPDATE `" + that.Prefix + table + "` SET "
qs := make([]interface{}, 0)
tp := len(data)
for k, v := range data {
vstr := "?"
if Substr(k, len(k)-3, 3) == "[#]" {
k = strings.Replace(k, "[#]", "", -1)
vstr = ObjToStr(v)
} else {
qs = append(qs, v)
}
query += "`" + k + "`=" + vstr + " "
if tp--; tp != 0 {
query += ", "
}
}
temp, resWhere := that.where(where)
query += temp + ";"
qs = append(qs, resWhere...)
res, err := that.Exec(query, qs...)
rows := int64(0)
if err.GetError() == nil && res != nil {
rows, _ = res.RowsAffected()
}
// 如果更新成功,则删除缓存
if rows != 0 {
if that.HoTimeCache != nil && table != "cached" {
_ = that.HoTimeCache.Db(table+"*", nil)
}
}
return rows
}
// Delete 删除数据
func (that *HoTimeDB) Delete(table string, data map[string]interface{}) int64 {
query := "DELETE FROM `" + that.Prefix + table + "` "
temp, resWhere := that.where(data)
query += temp + ";"
res, err := that.Exec(query, resWhere...)
rows := int64(0)
if err.GetError() == nil && res != nil {
rows, _ = res.RowsAffected()
}
// 如果删除成功,删除对应缓存
if rows != 0 {
if that.HoTimeCache != nil && table != "cached" {
_ = that.HoTimeCache.Db(table+"*", nil)
}
}
return rows
}

File diff suppressed because it is too large Load Diff

188
db/query.go Normal file
View File

@ -0,0 +1,188 @@
package db
import (
. "code.hoteas.com/golang/hotime/common"
"database/sql"
"encoding/json"
"errors"
"reflect"
"strings"
)
// md5 生成查询的 MD5 哈希(用于缓存)
func (that *HoTimeDB) md5(query string, args ...interface{}) string {
strByte, _ := json.Marshal(args)
str := Md5(query + ":" + string(strByte))
return str
}
// Query 执行查询 SQL
func (that *HoTimeDB) Query(query string, args ...interface{}) []Map {
return that.queryWithRetry(query, false, args...)
}
// queryWithRetry 内部查询方法,支持重试标记
func (that *HoTimeDB) queryWithRetry(query string, retried bool, args ...interface{}) []Map {
// 保存调试信息(加锁保护)
that.mu.Lock()
that.LastQuery = query
that.LastData = args
that.mu.Unlock()
defer func() {
if that.Mode != 0 {
that.mu.RLock()
that.Log.Info("SQL:"+that.LastQuery, " DATA:", that.LastData, " ERROR:", that.LastErr.GetError())
that.mu.RUnlock()
}
}()
var err error
var resl *sql.Rows
// 主从数据库切换只有select语句有从数据库
db := that.DB
if that.SlaveDB != nil {
db = that.SlaveDB
}
if db == nil {
err = errors.New("没有初始化数据库")
that.LastErr.SetError(err)
return nil
}
// 处理参数中的 slice 类型
processedArgs := that.processArgs(args)
if that.Tx != nil {
resl, err = that.Tx.Query(query, processedArgs...)
} else {
resl, err = db.Query(query, processedArgs...)
}
that.LastErr.SetError(err)
if err != nil {
// 如果还没重试过,尝试 Ping 后重试一次
if !retried {
if pingErr := db.Ping(); pingErr == nil {
return that.queryWithRetry(query, true, args...)
}
}
return nil
}
return that.Row(resl)
}
// Exec 执行非查询 SQL
func (that *HoTimeDB) Exec(query string, args ...interface{}) (sql.Result, *Error) {
return that.execWithRetry(query, false, args...)
}
// execWithRetry 内部执行方法,支持重试标记
func (that *HoTimeDB) execWithRetry(query string, retried bool, args ...interface{}) (sql.Result, *Error) {
// 保存调试信息(加锁保护)
that.mu.Lock()
that.LastQuery = query
that.LastData = args
that.mu.Unlock()
defer func() {
if that.Mode != 0 {
that.mu.RLock()
that.Log.Info("SQL: "+that.LastQuery, " DATA: ", that.LastData, " ERROR: ", that.LastErr.GetError())
that.mu.RUnlock()
}
}()
var e error
var resl sql.Result
if that.DB == nil {
err := errors.New("没有初始化数据库")
that.LastErr.SetError(err)
return nil, that.LastErr
}
// 处理参数中的 slice 类型
processedArgs := that.processArgs(args)
if that.Tx != nil {
resl, e = that.Tx.Exec(query, processedArgs...)
} else {
resl, e = that.DB.Exec(query, processedArgs...)
}
that.LastErr.SetError(e)
// 判断是否连接断开了,如果还没重试过,尝试重试一次
if e != nil {
if !retried {
if pingErr := that.DB.Ping(); pingErr == nil {
return that.execWithRetry(query, true, args...)
}
}
return resl, that.LastErr
}
return resl, that.LastErr
}
// processArgs 处理参数中的 slice 类型
func (that *HoTimeDB) processArgs(args []interface{}) []interface{} {
processedArgs := make([]interface{}, len(args))
copy(processedArgs, args)
for key := range processedArgs {
arg := processedArgs[key]
if arg == nil {
continue
}
argType := reflect.ValueOf(arg).Type().String()
if strings.Contains(argType, "[]") || strings.Contains(argType, "Slice") {
argLis := ObjToSlice(arg)
// 将slice转为逗号分割字符串
argStr := ""
for i := 0; i < len(argLis); i++ {
if i == len(argLis)-1 {
argStr += ObjToStr(argLis[i])
} else {
argStr += ObjToStr(argLis[i]) + ","
}
}
processedArgs[key] = argStr
}
}
return processedArgs
}
// Row 数据库数据解析
func (that *HoTimeDB) Row(resl *sql.Rows) []Map {
dest := make([]Map, 0)
strs, _ := resl.Columns()
for i := 0; resl.Next(); i++ {
lis := make(Map, 0)
a := make([]interface{}, len(strs))
b := make([]interface{}, len(a))
for j := 0; j < len(a); j++ {
b[j] = &a[j]
}
err := resl.Scan(b...)
if err != nil {
that.LastErr.SetError(err)
return nil
}
for j := 0; j < len(a); j++ {
if a[j] != nil && reflect.ValueOf(a[j]).Type().String() == "[]uint8" {
lis[strs[j]] = string(a[j].([]byte))
} else {
lis[strs[j]] = a[j] // 取实际类型
}
}
dest = append(dest, lis)
}
return dest
}

57
db/transaction.go Normal file
View File

@ -0,0 +1,57 @@
package db
import (
"sync"
)
// Action 事务操作
// 如果 action 返回 true 则提交事务;返回 false 则回滚
func (that *HoTimeDB) Action(action func(db HoTimeDB) (isSuccess bool)) (isSuccess bool) {
db := HoTimeDB{
DB: that.DB,
ContextBase: that.ContextBase,
DBName: that.DBName,
HoTimeCache: that.HoTimeCache,
Log: that.Log,
Type: that.Type,
Prefix: that.Prefix,
LastQuery: that.LastQuery,
LastData: that.LastData,
ConnectFunc: that.ConnectFunc,
LastErr: that.LastErr,
limit: that.limit,
Tx: that.Tx,
SlaveDB: that.SlaveDB,
Mode: that.Mode,
Dialect: that.Dialect,
mu: sync.RWMutex{},
limitMu: sync.Mutex{},
}
tx, err := db.Begin()
if err != nil {
that.LastErr.SetError(err)
return isSuccess
}
db.Tx = tx
isSuccess = action(db)
if !isSuccess {
err = db.Tx.Rollback()
if err != nil {
that.LastErr.SetError(err)
return isSuccess
}
return isSuccess
}
err = db.Tx.Commit()
if err != nil {
that.LastErr.SetError(err)
return false
}
return true
}

545
db/where.go Normal file
View File

@ -0,0 +1,545 @@
package db
import (
. "code.hoteas.com/golang/hotime/common"
"reflect"
"sort"
"strings"
)
// 条件关键字
var condition = []string{"AND", "OR"}
// 特殊关键字(支持大小写)
var vcond = []string{"GROUP", "ORDER", "LIMIT", "DISTINCT", "HAVING", "OFFSET"}
// normalizeKey 标准化关键字(转大写)
func normalizeKey(k string) string {
upper := strings.ToUpper(k)
for _, v := range vcond {
if upper == v {
return v
}
}
for _, v := range condition {
if upper == v {
return v
}
}
return k
}
// isConditionKey 判断是否是条件关键字
func isConditionKey(k string) bool {
upper := strings.ToUpper(k)
for _, v := range condition {
if upper == v {
return true
}
}
return false
}
// isVcondKey 判断是否是特殊关键字
func isVcondKey(k string) bool {
upper := strings.ToUpper(k)
for _, v := range vcond {
if upper == v {
return true
}
}
return false
}
// where 语句解析
func (that *HoTimeDB) where(data Map) (string, []interface{}) {
where := ""
res := make([]interface{}, 0)
// 标准化 Map 的 key大小写兼容
normalizedData := Map{}
for k, v := range data {
normalizedData[normalizeKey(k)] = v
}
data = normalizedData
// 收集所有 key 并排序
testQu := []string{}
for key := range data {
testQu = append(testQu, key)
}
sort.Strings(testQu)
// 追踪普通条件数量,用于自动添加 AND
normalCondCount := 0
for _, k := range testQu {
v := data[k]
// 检查是否是 AND/OR 条件关键字
if isConditionKey(k) {
tw, ts := that.cond(strings.ToUpper(k), v.(Map))
where += tw
res = append(res, ts...)
continue
}
// 检查是否是特殊关键字GROUP, ORDER, LIMIT 等)
if isVcondKey(k) {
continue // 特殊关键字在后面单独处理
}
// 处理普通条件字段
if v != nil && reflect.ValueOf(v).Type().String() == "common.Slice" && len(v.(Slice)) == 0 {
continue
}
if v != nil && strings.Contains(reflect.ValueOf(v).Type().String(), "[]") && len(ObjToSlice(v)) == 0 {
continue
}
tv, vv := that.varCond(k, v)
if tv != "" {
// 自动添加 AND 连接符
if normalCondCount > 0 {
where += " AND "
}
where += tv
normalCondCount++
res = append(res, vv...)
}
}
// 添加 WHERE 关键字
if len(where) != 0 {
hasWhere := true
for _, v := range vcond {
if strings.Index(where, v) == 0 {
hasWhere = false
}
}
if hasWhere {
where = " WHERE " + where + " "
}
}
// 处理特殊字符按固定顺序GROUP, HAVING, ORDER, LIMIT, OFFSET
specialOrder := []string{"GROUP", "HAVING", "ORDER", "LIMIT", "OFFSET", "DISTINCT"}
for _, vcondKey := range specialOrder {
v, exists := data[vcondKey]
if !exists {
continue
}
switch vcondKey {
case "GROUP":
where += " GROUP BY "
where += that.formatVcondValue(v)
case "HAVING":
// HAVING 条件处理
if havingMap, ok := v.(Map); ok {
havingWhere, havingRes := that.cond("AND", havingMap)
if havingWhere != "" {
where += " HAVING " + strings.TrimSpace(havingWhere) + " "
res = append(res, havingRes...)
}
}
case "ORDER":
where += " ORDER BY "
where += that.formatVcondValue(v)
case "LIMIT":
where += " LIMIT "
where += that.formatVcondValue(v)
case "OFFSET":
where += " OFFSET " + ObjToStr(v) + " "
case "DISTINCT":
// DISTINCT 通常在 SELECT 中处理,这里暂时忽略
}
}
return where, res
}
// formatVcondValue 格式化特殊关键字的值
func (that *HoTimeDB) formatVcondValue(v interface{}) string {
result := ""
if reflect.ValueOf(v).Type().String() == "common.Slice" || strings.Contains(reflect.ValueOf(v).Type().String(), "[]") {
vs := ObjToSlice(v)
for i := 0; i < len(vs); i++ {
result += " " + vs.GetString(i) + " "
if len(vs) != i+1 {
result += ", "
}
}
} else {
result += " " + ObjToStr(v) + " "
}
return result
}
// varCond 变量条件解析
func (that *HoTimeDB) varCond(k string, v interface{}) (string, []interface{}) {
where := ""
res := make([]interface{}, 0)
length := len(k)
if k == "[#]" {
k = strings.Replace(k, "[#]", "", -1)
where += " " + ObjToStr(v) + " "
} else if length > 0 && strings.Contains(k, "[") && k[length-1] == ']' {
def := false
switch Substr(k, length-3, 3) {
case "[>]":
k = strings.Replace(k, "[>]", "", -1)
if !strings.Contains(k, ".") {
k = "`" + k + "` "
}
where += k + ">? "
res = append(res, v)
case "[<]":
k = strings.Replace(k, "[<]", "", -1)
if !strings.Contains(k, ".") {
k = "`" + k + "` "
}
where += k + "<? "
res = append(res, v)
case "[!]":
k = strings.Replace(k, "[!]", "", -1)
if !strings.Contains(k, ".") {
k = "`" + k + "` "
}
where, res = that.notIn(k, v, where, res)
case "[#]":
k = strings.Replace(k, "[#]", "", -1)
if !strings.Contains(k, ".") {
k = "`" + k + "` "
}
where += " " + k + "=" + ObjToStr(v) + " "
case "[##]": // 直接添加value到sql需要考虑防注入
where += " " + ObjToStr(v)
case "[#!]":
k = strings.Replace(k, "[#!]", "", -1)
if !strings.Contains(k, ".") {
k = "`" + k + "` "
}
where += " " + k + "!=" + ObjToStr(v) + " "
case "[!#]":
k = strings.Replace(k, "[!#]", "", -1)
if !strings.Contains(k, ".") {
k = "`" + k + "` "
}
where += " " + k + "!=" + ObjToStr(v) + " "
case "[~]":
k = strings.Replace(k, "[~]", "", -1)
if !strings.Contains(k, ".") {
k = "`" + k + "` "
}
where += k + " LIKE ? "
v = "%" + ObjToStr(v) + "%"
res = append(res, v)
case "[!~]": // 左边任意
k = strings.Replace(k, "[!~]", "", -1)
if !strings.Contains(k, ".") {
k = "`" + k + "` "
}
where += k + " LIKE ? "
v = "%" + ObjToStr(v) + ""
res = append(res, v)
case "[~!]": // 右边任意
k = strings.Replace(k, "[~!]", "", -1)
if !strings.Contains(k, ".") {
k = "`" + k + "` "
}
where += k + " LIKE ? "
v = ObjToStr(v) + "%"
res = append(res, v)
case "[~~]": // 手动任意
k = strings.Replace(k, "[~~]", "", -1)
if !strings.Contains(k, ".") {
k = "`" + k + "` "
}
where += k + " LIKE ? "
res = append(res, v)
default:
def = true
}
if def {
switch Substr(k, length-4, 4) {
case "[>=]":
k = strings.Replace(k, "[>=]", "", -1)
if !strings.Contains(k, ".") {
k = "`" + k + "` "
}
where += k + ">=? "
res = append(res, v)
case "[<=]":
k = strings.Replace(k, "[<=]", "", -1)
if !strings.Contains(k, ".") {
k = "`" + k + "` "
}
where += k + "<=? "
res = append(res, v)
case "[><]":
k = strings.Replace(k, "[><]", "", -1)
if !strings.Contains(k, ".") {
k = "`" + k + "` "
}
where += k + " NOT BETWEEN ? AND ? "
vs := ObjToSlice(v)
res = append(res, vs[0])
res = append(res, vs[1])
case "[<>]":
k = strings.Replace(k, "[<>]", "", -1)
if !strings.Contains(k, ".") {
k = "`" + k + "` "
}
where += k + " BETWEEN ? AND ? "
vs := ObjToSlice(v)
res = append(res, vs[0])
res = append(res, vs[1])
default:
where, res = that.handleDefaultCondition(k, v, where, res)
}
}
} else {
where, res = that.handlePlainField(k, v, where, res)
}
return where, res
}
// handleDefaultCondition 处理默认条件(带方括号但不是特殊操作符)
func (that *HoTimeDB) handleDefaultCondition(k string, v interface{}, where string, res []interface{}) (string, []interface{}) {
if !strings.Contains(k, ".") {
k = "`" + k + "` "
}
if reflect.ValueOf(v).Type().String() == "common.Slice" || strings.Contains(reflect.ValueOf(v).Type().String(), "[]") {
vs := ObjToSlice(v)
if len(vs) == 0 {
return where, res
}
if len(vs) == 1 {
where += k + "=? "
res = append(res, vs[0])
return where, res
}
// IN 优化:连续整数转为 BETWEEN
where, res = that.optimizeInCondition(k, vs, where, res)
} else {
where += k + "=? "
res = append(res, v)
}
return where, res
}
// handlePlainField 处理普通字段(无方括号)
func (that *HoTimeDB) handlePlainField(k string, v interface{}, where string, res []interface{}) (string, []interface{}) {
if !strings.Contains(k, ".") {
k = "`" + k + "` "
}
if v == nil {
where += k + " IS NULL "
} else if reflect.ValueOf(v).Type().String() == "common.Slice" || strings.Contains(reflect.ValueOf(v).Type().String(), "[]") {
vs := ObjToSlice(v)
if len(vs) == 0 {
return where, res
}
if len(vs) == 1 {
where += k + "=? "
res = append(res, vs[0])
return where, res
}
// IN 优化
where, res = that.optimizeInCondition(k, vs, where, res)
} else {
where += k + "=? "
res = append(res, v)
}
return where, res
}
// optimizeInCondition 优化 IN 条件(连续整数转为 BETWEEN
func (that *HoTimeDB) optimizeInCondition(k string, vs Slice, where string, res []interface{}) (string, []interface{}) {
min := int64(0)
isMin := true
IsRange := true
num := int64(0)
isNum := true
where1 := ""
res1 := Slice{}
where2 := k + " IN ("
res2 := Slice{}
for kvs := 0; kvs <= len(vs); kvs++ {
vsv := int64(0)
if kvs < len(vs) {
vsv = vs.GetCeilInt64(kvs)
// 确保是全部是int类型
if ObjToStr(vsv) != vs.GetString(kvs) {
IsRange = false
break
}
}
if isNum {
isNum = false
num = vsv
} else {
num++
}
if isMin {
isMin = false
min = vsv
}
// 不等于则到了分路口
if num != vsv {
// between
if num-min > 1 {
if where1 != "" {
where1 += " OR " + k + " BETWEEN ? AND ? "
} else {
where1 += k + " BETWEEN ? AND ? "
}
res1 = append(res1, min)
res1 = append(res1, num-1)
} else {
where2 += "?,"
res2 = append(res2, min)
}
min = vsv
num = vsv
}
}
if IsRange {
where3 := ""
if where1 != "" {
where3 += where1
res = append(res, res1...)
}
if len(res2) == 1 {
if where3 == "" {
where3 += k + " = ? "
} else {
where3 += " OR " + k + " = ? "
}
res = append(res, res2...)
} else if len(res2) > 1 {
where2 = where2[:len(where2)-1]
if where3 == "" {
where3 += where2 + ")"
} else {
where3 += " OR " + where2 + ")"
}
res = append(res, res2...)
}
if where3 != "" {
where += "(" + where3 + ")"
}
return where, res
}
// 非连续整数,使用普通 IN
where += k + " IN ("
res = append(res, vs...)
for i := 0; i < len(vs); i++ {
if i+1 != len(vs) {
where += "?,"
} else {
where += "?) "
}
}
return where, res
}
// notIn NOT IN 条件处理
func (that *HoTimeDB) notIn(k string, v interface{}, where string, res []interface{}) (string, []interface{}) {
if v == nil {
where += k + " IS NOT NULL "
} else if reflect.ValueOf(v).Type().String() == "common.Slice" || strings.Contains(reflect.ValueOf(v).Type().String(), "[]") {
vs := ObjToSlice(v)
if len(vs) == 0 {
return where, res
}
where += k + " NOT IN ("
res = append(res, vs...)
for i := 0; i < len(vs); i++ {
if i+1 != len(vs) {
where += "?,"
} else {
where += "?) "
}
}
} else {
where += k + " !=? "
res = append(res, v)
}
return where, res
}
// cond 条件组合处理
func (that *HoTimeDB) cond(tag string, data Map) (string, []interface{}) {
where := " "
res := make([]interface{}, 0)
lens := len(data)
testQu := []string{}
for key := range data {
testQu = append(testQu, key)
}
sort.Strings(testQu)
for _, k := range testQu {
v := data[k]
x := 0
for i := 0; i < len(condition); i++ {
if condition[i] == strings.ToUpper(k) {
tw, ts := that.cond(strings.ToUpper(k), v.(Map))
if lens--; lens <= 0 {
where += "(" + tw + ") "
} else {
where += "(" + tw + ") " + tag + " "
}
res = append(res, ts...)
break
}
x++
}
if x == len(condition) {
tv, vv := that.varCond(k, v)
if tv == "" {
lens--
continue
}
res = append(res, vv...)
if lens--; lens <= 0 {
where += tv + ""
} else {
where += tv + " " + tag + " "
}
}
}
return where, res
}

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@ -372,7 +372,7 @@ func Example7_AggregateQuery_Fixed(db *db.HoTimeDB) {
} }
// 示例8: 事务处理(修正版) // 示例8: 事务处理(修正版)
func Example8_Transaction_Fixed(db *db.HoTimeDB) { func Example8_Transaction_Fixed(database *db.HoTimeDB) {
fmt.Println("=== 事务处理示例 ===") fmt.Println("=== 事务处理示例 ===")
// 模拟转账操作 // 模拟转账操作
@ -380,7 +380,7 @@ func Example8_Transaction_Fixed(db *db.HoTimeDB) {
toUserId := int64(2) toUserId := int64(2)
amount := 100.0 amount := 100.0
success := db.Action(func(tx db.HoTimeDB) bool { success := database.Action(func(tx db.HoTimeDB) bool {
// 检查转出账户余额(单条件) // 检查转出账户余额(单条件)
fromUser := tx.Get("user", "balance", common.Map{"id": fromUserId}) fromUser := tx.Get("user", "balance", common.Map{"id": fromUserId})
if fromUser == nil { if fromUser == nil {
@ -445,22 +445,22 @@ func Example8_Transaction_Fixed(db *db.HoTimeDB) {
fmt.Println("事务执行成功") fmt.Println("事务执行成功")
} else { } else {
fmt.Println("事务回滚") fmt.Println("事务回滚")
if db.LastErr.GetError() != nil { if database.LastErr.GetError() != nil {
fmt.Println("错误原因:", db.LastErr.GetError()) fmt.Println("错误原因:", database.LastErr.GetError())
} }
} }
} }
// 示例9: 缓存机制(修正版) // 示例9: 缓存机制(修正版)
func Example9_CacheSystem_Fixed(db *db.HoTimeDB) { func Example9_CacheSystem_Fixed(database *db.HoTimeDB) {
fmt.Println("=== 缓存机制示例 ===") fmt.Println("=== 缓存机制示例 ===")
// 设置缓存(实际项目中需要配置缓存参数) // 设置缓存(实际项目中需要配置缓存参数)
db.HoTimeCache = &cache.HoTimeCache{} database.HoTimeCache = &cache.HoTimeCache{}
// 第一次查询(会缓存结果) // 第一次查询(会缓存结果)
fmt.Println("第一次查询(会缓存)...") fmt.Println("第一次查询(会缓存)...")
users1 := db.Select("user", "*", common.Map{ users1 := database.Select("user", "*", common.Map{
"status": 1, // 单条件 "status": 1, // 单条件
"LIMIT": 10, "LIMIT": 10,
}) })
@ -468,7 +468,7 @@ func Example9_CacheSystem_Fixed(db *db.HoTimeDB) {
// 第二次相同查询(从缓存获取) // 第二次相同查询(从缓存获取)
fmt.Println("第二次相同查询(从缓存获取)...") fmt.Println("第二次相同查询(从缓存获取)...")
users2 := db.Select("user", "*", common.Map{ users2 := database.Select("user", "*", common.Map{
"status": 1, // 单条件 "status": 1, // 单条件
"LIMIT": 10, "LIMIT": 10,
}) })
@ -476,7 +476,7 @@ func Example9_CacheSystem_Fixed(db *db.HoTimeDB) {
// 更新操作会清除缓存 // 更新操作会清除缓存
fmt.Println("执行更新操作(会清除缓存)...") fmt.Println("执行更新操作(会清除缓存)...")
affected := db.Update("user", common.Map{ affected := database.Update("user", common.Map{
"updated_time[#]": "NOW()", "updated_time[#]": "NOW()",
}, common.Map{ }, common.Map{
"id": 1, // 单条件 "id": 1, // 单条件
@ -485,7 +485,7 @@ func Example9_CacheSystem_Fixed(db *db.HoTimeDB) {
// 再次查询(重新从数据库获取并缓存) // 再次查询(重新从数据库获取并缓存)
fmt.Println("更新后再次查询(重新缓存)...") fmt.Println("更新后再次查询(重新缓存)...")
users3 := db.Select("user", "*", common.Map{ users3 := database.Select("user", "*", common.Map{
"status": 1, // 单条件 "status": 1, // 单条件
"LIMIT": 10, "LIMIT": 10,
}) })
@ -493,20 +493,20 @@ func Example9_CacheSystem_Fixed(db *db.HoTimeDB) {
} }
// 示例10: 性能优化技巧(修正版) // 示例10: 性能优化技巧(修正版)
func Example10_PerformanceOptimization_Fixed(db *db.HoTimeDB) { func Example10_PerformanceOptimization_Fixed(database *db.HoTimeDB) {
fmt.Println("=== 性能优化技巧示例 ===") fmt.Println("=== 性能优化技巧示例 ===")
// IN查询优化连续数字自动转为BETWEEN // IN查询优化连续数字自动转为BETWEEN
fmt.Println("IN查询优化示例...") fmt.Println("IN查询优化示例...")
users := db.Select("user", "*", common.Map{ users := database.Select("user", "*", common.Map{
"id": []int{1, 2, 3, 4, 5, 10, 11, 12, 13, 20}, // 单个IN条件会被优化 "id": []int{1, 2, 3, 4, 5, 10, 11, 12, 13, 20}, // 单个IN条件会被优化
}) })
fmt.Printf("查询到 %d 个用户\n", len(users)) fmt.Printf("查询到 %d 个用户\n", len(users))
fmt.Println("执行的SQL:", db.LastQuery) fmt.Println("执行的SQL:", database.LastQuery)
// 批量插入(使用事务) // 批量插入(使用事务)
fmt.Println("\n批量插入示例...") fmt.Println("\n批量插入示例...")
success := db.Action(func(tx db.HoTimeDB) bool { success := database.Action(func(tx db.HoTimeDB) bool {
for i := 1; i <= 100; i++ { for i := 1; i <= 100; i++ {
id := tx.Insert("user_batch", common.Map{ id := tx.Insert("user_batch", common.Map{
"name": fmt.Sprintf("批量用户%d", i), "name": fmt.Sprintf("批量用户%d", i),
@ -534,7 +534,7 @@ func Example10_PerformanceOptimization_Fixed(db *db.HoTimeDB) {
// 索引友好的查询(修正版) // 索引友好的查询(修正版)
fmt.Println("\n索引友好的查询...") fmt.Println("\n索引友好的查询...")
recentUsers := db.Select("user", "*", common.Map{ recentUsers := database.Select("user", "*", common.Map{
"AND": common.Map{ "AND": common.Map{
"created_time[>]": "2023-01-01", // 假设created_time有索引 "created_time[>]": "2023-01-01", // 假设created_time有索引
"status": 1, // 假设status有索引 "status": 1, // 假设status有索引
@ -766,6 +766,66 @@ correctUsers4 := db.Select("user", "*", common.Map{
_ = db _ = db
} }
// BatchInsertExample 批量插入示例
func BatchInsertExample(database *db.HoTimeDB) {
fmt.Println("\n=== 批量插入示例 ===")
// 批量插入用户(使用 []Map 格式)
affected := database.BatchInsert("user", []common.Map{
{"name": "批量用户1", "email": "batch1@example.com", "age": 25, "status": 1},
{"name": "批量用户2", "email": "batch2@example.com", "age": 30, "status": 1},
{"name": "批量用户3", "email": "batch3@example.com", "age": 28, "status": 1},
})
fmt.Printf("批量插入了 %d 条用户记录\n", affected)
// 批量插入日志(使用 [#] 标记直接 SQL
logAffected := database.BatchInsert("log", []common.Map{
{"user_id": 1, "action": "login", "ip": "192.168.1.1", "created_time[#]": "NOW()"},
{"user_id": 2, "action": "logout", "ip": "192.168.1.2", "created_time[#]": "NOW()"},
{"user_id": 3, "action": "view", "ip": "192.168.1.3", "created_time[#]": "NOW()"},
})
fmt.Printf("批量插入了 %d 条日志记录\n", logAffected)
}
// UpsertExample Upsert插入或更新示例
func UpsertExample(database *db.HoTimeDB) {
fmt.Println("\n=== Upsert示例 ===")
// Upsert 用户数据(使用 Slice 格式)
// 如果 id 存在则更新,不存在则插入
affected := database.Upsert("user",
common.Map{
"id": 1,
"name": "张三更新",
"email": "zhang_updated@example.com",
"age": 26,
},
common.Slice{"id"}, // 唯一键
common.Slice{"name", "email", "age"}, // 冲突时更新的字段
)
fmt.Printf("Upsert 影响了 %d 行\n", affected)
// Upsert 使用 [#] 直接 SQL
affected2 := database.Upsert("user_stats",
common.Map{
"user_id": 1,
"login_count[#]": "login_count + 1",
"last_login[#]": "NOW()",
},
common.Slice{"user_id"},
common.Slice{"login_count", "last_login"},
)
fmt.Printf("统计 Upsert 影响了 %d 行\n", affected2)
// 也支持可变参数形式
affected3 := database.Upsert("user",
common.Map{"id": 2, "name": "李四", "status": 1},
common.Slice{"id"},
"name", "status", // 可变参数
)
fmt.Printf("可变参数 Upsert 影响了 %d 行\n", affected3)
}
// 运行所有修正后的示例 // 运行所有修正后的示例
func RunAllFixedExamples() { func RunAllFixedExamples() {
fmt.Println("开始运行HoTimeDB所有修正后的示例...") fmt.Println("开始运行HoTimeDB所有修正后的示例...")
@ -776,6 +836,10 @@ func RunAllFixedExamples() {
fmt.Println("请根据实际环境配置数据库连接后运行相应示例") fmt.Println("请根据实际环境配置数据库连接后运行相应示例")
fmt.Println("所有示例代码已修正完毕,语法正确!") fmt.Println("所有示例代码已修正完毕,语法正确!")
fmt.Println("")
fmt.Println("新增功能示例说明:")
fmt.Println(" - BatchInsertExample(db): 批量插入示例,使用 []Map 格式")
fmt.Println(" - UpsertExample(db): 插入或更新示例")
} }
func main() { func main() {