diff --git a/.cursor/plans/json_path_查询支持_58564923.plan.md b/.cursor/plans/json_path_查询支持_58564923.plan.md new file mode 100644 index 0000000..907a7c0 --- /dev/null +++ b/.cursor/plans/json_path_查询支持_58564923.plan.md @@ -0,0 +1,331 @@ +--- +name: JSON Path 查询支持 +overview: 在现有 Dialect 接口和 WHERE 条件解析引擎上扩展,使 MySQL 和 SQLite 支持类似 PgSQL 的 `data["a"]["b"]["c"]` JSON 路径查询与操作语法,所有三种数据库共享统一的上层 API。 +todos: + - id: dialect-json-interface + content: 在 db/dialect.go 的 Dialect 接口新增 JSONExtract / JSONSet / JSONContains / JSONArrayLength 四个方法 + status: pending + - id: dialect-json-mysql + content: 实现 MySQLDialect 的三个 JSON 方法,处理 JSON_EXTRACT / JSON_SET / JSON_UNQUOTE 等 + status: pending + - id: dialect-json-sqlite + content: 实现 SQLiteDialect 的三个 JSON 方法,使用小写 json_extract / json_set + status: pending + - id: dialect-json-pgsql + content: "实现 PostgreSQLDialect 的三个 JSON 方法,使用 -> / #>> / jsonb_set 语法" + status: pending + - id: where-json-parse + content: 新增 parseJSONPathKey()/parseJSONPathFromName() 工具函数,同时支持 col["key"] 和 col['key'] 两种引号格式,以及数字索引 [0],解析出列名、[]string 路径键、操作符后缀 + status: pending + - id: where-json-cond + content: 在 db/where.go 的 varCond() 开头最优先调用 parseJSONPathKey,分发到 jsonPathCond();jsonPathCond 对现有操作符复用比较逻辑,新增 [?]/[!?]/[@]/[!@]/[len]/[len>] 等分支 + status: pending + - id: crud-json-update + content: 在 db/crud.go 的 Update() SET 构建循环中检测 JSON 路径列名生成 JSONSet 表达式,在 Select() 的 Slice 字段循环中检测 JSON 路径生成 JSONExtract 表达式(修复两处 ProcessColumnNoPrefix 误处理) + status: pending +isProject: false +--- + +# MySQL/SQLite JSON 路径查询支持方案 + +## 背景与现状 + +现有 ORM 已有 `Dialect` 接口抽象三种数据库差异,`where.go` 用 `[operator]` 后缀语法解析条件。扩展的核心思路:**在解析 key 时识别 JSON 路径记法,委托给 Dialect 生成对应函数调用**。 + +## 三种数据库 JSON 能力深度对比 + +### 1. JSON 值提取(WHERE 比较场景) + + +| | MySQL 5.7+ | SQLite 3.9+ | PostgreSQL | +| ---------- | ------------------------------------------------------- | ------------------------------ | ----------------- | +| 单层提取(返回文本) | `JSON_UNQUOTE(JSON_EXTRACT(col,'$.a'))` 或 `col->>'$.a'` | `json_extract(col,'$.a')` | `col->>'a'` | +| 多层嵌套(返回文本) | `JSON_UNQUOTE(JSON_EXTRACT(col,'$.a.b'))` | `json_extract(col,'$.a.b')` | `col#>>'{a,b}'` | +| 数组索引 | `JSON_EXTRACT(col,'$.arr[0]')` | `json_extract(col,'$.arr[0]')` | `col#>>'{arr,0}'` | +| 路径格式 | `$.a.b` | `$.a.b`(与MySQL相同) | `{a,b}`(#>> 方式) | + + +**关键差异**:MySQL 的 `JSON_EXTRACT` 对字符串返回值带外层双引号(如 `"北京"` 而非 `北京`),与普通字符串 `= ?` 比较会失败,**必须包一层 `JSON_UNQUOTE()`**。SQLite 和 PostgreSQL(`->>`/`#>>`)则直接返回文本,无此问题。 + +### 2. JSON 局部更新(UPDATE SET 场景) + + +| | MySQL 5.7+ | SQLite 3.9+ | PostgreSQL | +| ----- | ------------------------- | ------------------------- | ------------------------------------------ | +| 语法 | `JSON_SET(col,'$.a.b',?)` | `json_set(col,'$.a.b',?)` | `jsonb_set(col,'{a,b}',to_jsonb(?::text))` | +| 路径格式 | `$.a.b` | `$.a.b`(与MySQL相同) | `'{a,b}'` 数组字面量 | +| 值的类型 | 直接绑定参数 | 直接绑定参数 | 字符串需 `to_jsonb(?::text)`,数字可 `?::jsonb` | +| 列类型限制 | 无(TEXT 也行) | 无(TEXT 列) | **必须是 `jsonb` 列**,`json` 列无 `jsonb_set` | + + +**关键差异**:MySQL 与 SQLite 的语法几乎完全一致(函数名大小写不同而已)。PostgreSQL 只有 `jsonb_set`,要求列类型为 `jsonb`,且字符串值必须转为 JSON 格式(`to_jsonb(?::text)` 或 `'"value"'::jsonb`)。 + +### 3. JSON 数组包含(`[@]` 操作符) + + +| | MySQL 5.7+ | SQLite 3.9+ | PostgreSQL | +| ------ | --------------------------------------------------------- | ------------------------------------------------------------- | ------------------------------------ | +| SQL 形式 | `JSON_CONTAINS(JSON_EXTRACT(col,'$.path'),JSON_QUOTE(?))` | `EXISTS(SELECT 1 FROM json_each(col,'$.path') WHERE value=?)` | `col#>'{path}' @> to_jsonb(?::text)` | +| 结构类型 | 函数调用(内联) | **EXISTS 子查询**(结构不同) | 中缀操作符(内联) | + + +**最大兼容难点**:SQLite 没有内联的包含函数,必须生成 EXISTS 子查询,SQL 结构与另外两种完全不同。`JSONContains()` 方法在 SQLite 下返回的是一段 `EXISTS(...)` 表达式。 + +### 4. 路径格式统一转换规则 + +ORM 内部统一使用 `[]string{"a","b","c"}` 表示路径键(数字索引用字符串 `"0"` 表示),各 Dialect 自行转换: + +``` +内部路径: ["address","city"] + → MySQL/SQLite: $.address.city + → PostgreSQL: {address,city} + +内部路径: ["tags","0"](数组索引) + → MySQL/SQLite: $.tags[0] + → PostgreSQL: {tags,0} +``` + +## 统一 API 语法设计 + +### Key 写法(单引号/双引号两种都支持) + +```go +// 双引号风格(JSON 标准,推荐,需用反引号字符串) +common.Map{ `profile["addr"]["city"]`: "北京" } + +// 单引号风格(普通字符串也能写) +common.Map{ "profile['addr']['city']": "北京" } +``` + +两种写法解析结果完全一致,内部统一转为 `$.addr.city`。 + +### 操作符设计:复用现有 + 新增 JSON 专属 + +**所有现有操作符均可接在 JSON 路径后直接使用**(`=` / `>` / `<` / `>=` / `<=` / `!=` / `LIKE` / `IN` / `NOT IN` / `BETWEEN` / `IS NULL`): + +```go +db.Select("user", common.Map{ + `profile["age"]`: 18, // = 18 + `profile["age"][>]`: 18, // > 18 + `profile["age"][<>]`: []int{18, 30}, // BETWEEN + `profile["name"][~]`: "张", // LIKE %张% + `profile["tags"][0]`: "vip", // 数组第一个元素 = vip + `profile["addr"]["city"]`: "北京", // 多级嵌套 + `profile["score"]`: nil, // IS NULL +}) +``` + +**新增 JSON 专属操作符**(现有体系无对应): + + +| 操作符 | 含义 | MySQL | SQLite | PgSQL | +| --------- | ----------- | -------------------------------- | ------------------------ | ------------------------- | +| `[?]` | 路径存在(非NULL) | `IS NOT NULL` | `IS NOT NULL` | `? ?` | +| `[!?]` | 路径不存在 | `IS NULL` | `IS NULL` | `NOT (? ?)` | +| `[@]` | 数组/对象包含某值 | `JSON_CONTAINS(col,?,'$.path')` | `json_each` 子查询 | `col @> ?::jsonb` | +| `[!@]` | 不包含 | `NOT JSON_CONTAINS(...)` | 子查询 NOT EXISTS | `NOT (col @> ?)` | +| `[len]` | 数组长度 = ? | `JSON_LENGTH(JSON_EXTRACT(...))` | `json_array_length(...)` | `jsonb_array_length(...)` | +| `[len>]` | 数组长度 > ? | 同上 | 同上 | 同上 | +| `[len<]` | 数组长度 < ? | 同上 | 同上 | 同上 | +| `[len>=]` | 数组长度 >= ? | 同上 | 同上 | 同上 | +| `[len<=]` | 数组长度 <= ? | 同上 | 同上 | 同上 | + + +```go +// 新操作符使用示例 +db.Select("user", common.Map{ + `profile["vip"][?]`: nil, // vip 字段存在 + `profile["tags"][@]`: "admin", // tags 数组包含 "admin" + `profile["friends"][len>]`: 5, // friends 数组长度 > 5 + `profile["items"][len]`: 3, // items 数组长度 = 3 +}) + +// UPDATE 局部更新 JSON 字段 +db.Update("user", common.Map{ + `profile["age"]`: 20, + `profile["addr"]["city"]`: "上海", +}, common.Map{"id": 1}) +``` + +## 与现有代码的兼容性分析 + +### 三处具体冲突点 + +**冲突 1 — WHERE `varCond` 的 `[...]` 分支**(`[db/where.go:225](db/where.go)`) + +现有逻辑:只要 key 含 `[` 且末尾是 `]` 就进分支,取末尾 3/4 字符匹配已知操作符。 + +``` +profile["age"] → 末尾3字符 e"] → 无匹配 → handleDefaultCondition + → ProcessColumn("profile[\"age\"]") → 被加引号 → WRONG SQL + +profile["age"][>] → 末尾3字符 [>] → MATCH,strips → ProcessColumn("profile[\"age\"]") + → 被加引号 → WRONG SQL + +profile["age"][@] → 末尾3字符 [@] → 无匹配(新操作符) → handleDefaultCondition + → ProcessColumn("profile[\"age\"][@]") → WRONG SQL +``` + +**冲突 2 — UPDATE `ProcessColumnNoPrefix` 调用**(`[db/crud.go:629](db/crud.go)`) + +```go +query += processor.ProcessColumnNoPrefix(k) + "=" + vstr // 第629行 +``` + +`k = profile["age"]` → `QuoteIdentifier` 加引号 → ``profile["age"]`=?` → 无效 SQL。 + +**冲突 3 — SELECT slice 字段**(`[db/crud.go:109](db/crud.go)`) + +无 `.` 且无 `AS` 的字段走 `ProcessColumnNoPrefix`,`profile["age"]` 被当成普通列名加引号。 + +### 已有格式不受影响的验证 + +- `profile[>]` — 末尾 `[>]` 匹配现有 switch → 先 strips 再调 `ProcessColumn("profile")` → **正常** +- `user.name` — `ProcessColumn` 有 `.` 分支 → **正常** +- ``user`.name` — `stripQuotes` 后同上 → **正常** +- `"name,age"` 字符串字段 — `ProcessFieldList` 的正则不会错误匹配无 `.` 的字段 → **正常** +- `Slice{"name","age"}` — 无 JSON 路径字符 → **正常** + +--- + +## 实现方案 + +### 核心设计:`parseJSONPathKey` 最先拦截 + +区分 JSON 路径括号和操作符括号的关键:**括号内容** + +- JSON 路径段:`["xxx"]`、`['xxx']`、`[0]`(引号字符串或纯数字) +- 操作符段:`[>]`、`[@]`、`[len>]`(无引号,含符号字符) + +**第一步:新增路径工具函数(`[db/where.go](db/where.go)` 顶部或独立 `jsonpath.go`)** + +```go +// parseJSONPathKey 解析 JSON 路径 key,识别规则: +// ^(\w[\w.]*) 列名 +// ((?:\["[^"]*"\]|\['[^']*'\]|\[\d+\])+) JSON 路径段(双引号/单引号/数字索引) +// (\[.*\])?$ 可选尾部操作符 [>] [@] [len>] 等 +// +// 示例: +// profile["age"][>] → col=profile, keys=["age"], op="[>]", ok=true +// profile['addr']['city'] → col=profile, keys=["addr","city"], op="", ok=true +// profile["tags"][0][@] → col=profile, keys=["tags","0"], op="[@]", ok=true +// profile[>] → ok=false(操作符括号,非 JSON 路径) +func parseJSONPathKey(k string) (col string, pathKeys []string, opSuffix string, ok bool) +``` + +**第二步:`varCond()` 开头最先调用**(`[db/where.go](db/where.go)`) + +```go +func (that *HoTimeDB) varCond(k string, v interface{}) (string, []interface{}) { + // ★ 最优先:JSON 路径检测,完全绕过现有 [...] 分支 + if col, pathKeys, opSuffix, ok := parseJSONPathKey(k); ok { + return that.jsonPathCond(col, pathKeys, opSuffix, v) + } + // 以下全部保持不变 ↓ + ... +} +``` + +`jsonPathCond` 根据 opSuffix 分发: + + +| opSuffix | 动作 | +| --------------------------------------------------------------------------------------- | ---------------------------------------------------------------- | +| `""` / 现有操作符(`[>]`/`[<]`/`[>=]`/`[<=]`/`[!]`/`[~]`/`[!~]`/`[~!]`/`[<>]`/`[><]`/`[#]` 等) | 调用 `dialect.JSONExtract(col, pathKeys)` 得到提取表达式,替换原来的列名,复用现有比较逻辑 | +| `[?]` | `JSONExtract(...) IS NOT NULL` | +| `[!?]` | `JSONExtract(...) IS NULL` | +| `[@]` | `dialect.JSONContains(col, pathKeys, "?")` | +| `[!@]` | `NOT` + JSONContains | +| `[len]` | `dialect.JSONArrayLength(col, pathKeys)` + `=?` | +| `[len>]` / `[len<]` / `[len>=]` / `[len<=]` | JSONArrayLength + 对应比较符 | + + +**第三步:扩展 `Dialect` 接口(`[db/dialect.go](db/dialect.go)`)** + +新增 4 个方法,内部路径统一用 `[]string`(数字索引用字符串 `"0"`): + +```go +// MySQL: JSON_UNQUOTE(JSON_EXTRACT(`col`, '$.a.b')) +// SQLite: json_extract("col", '$.a.b') +// PgSQL: "col"#>>'{a,b}' +JSONExtract(quotedColumn string, pathKeys []string) string + +// MySQL: JSON_SET(`col`, '$.a.b', ?) +// SQLite: json_set("col", '$.a.b', ?) +// PgSQL: jsonb_set("col", '{a,b}', to_jsonb($N::text)) -- isStr=true +// jsonb_set("col", '{a,b}', $N::jsonb) -- isStr=false +JSONSet(quotedColumn string, pathKeys []string, placeholder string, isStr bool) string + +// MySQL: JSON_CONTAINS(JSON_EXTRACT(`col`,'$.path'), JSON_QUOTE(?)) +// SQLite: EXISTS (SELECT 1 FROM json_each("col", '$.path') WHERE value=?) +// PgSQL: "col"#>'{path}' @> to_jsonb($N::text) +JSONContains(quotedColumn string, pathKeys []string, placeholder string) string + +// MySQL: JSON_LENGTH(JSON_EXTRACT(`col`, '$.path')) +// SQLite: json_array_length("col", '$.path') +// PgSQL: jsonb_array_length("col"#>'{path}') +JSONArrayLength(quotedColumn string, pathKeys []string) string +``` + +**第四步:修复 UPDATE SET(`[db/crud.go:621](db/crud.go)`)** + +在 `[#]` 检查之后、`ProcessColumnNoPrefix` 之前增加 JSON 路径检测: + +```go +} else if col, pathKeys, ok := parseJSONPathFromName(k); ok { + quotedCol := processor.ProcessColumnNoPrefix(col) + isStr := v != nil && reflect.TypeOf(v).Kind() == reflect.String + ph := that.Dialect.Placeholder(len(qs) + 1) + query += quotedCol + "=" + that.Dialect.JSONSet(quotedCol, pathKeys, ph, isStr) + qs = append(qs, v) +``` + +**第五步:修复 SELECT slice 字段(`[db/crud.go:101](db/crud.go)`)** + +在 `Slice` 字段循环中,对每个字段先做 JSON 路径检测: + +```go +if col, pathKeys, ok := parseJSONPathFromName(stripAlias(k)); ok { + alias := extractAlias(k) + quotedCol := processor.ProcessColumnNoPrefix(col) + query += " " + that.Dialect.JSONExtract(quotedCol, pathKeys) + alias + " " +} else if strings.Contains(k, " AS ") || strings.Contains(k, ".") { + query += " " + processor.ProcessFieldList(k) + " " +} else { + query += " " + processor.ProcessColumnNoPrefix(k) + " " +} +``` + +字符串字段(如 `"profile[\"age\"] AS age, name"`)暂不处理 JSON 路径,用户可用原有原始 SQL 写法。 + +## 支持的能力范围 + +- **WHERE 条件**:复用全部现有 14 种操作符(`=` / `!=` / `>` / `<` / `>=` / `<=` / `LIKE` / `IN` / `NOT IN` / `BETWEEN` / `IS NULL` 等)+ 新增 `[?]` / `[!?]` / `[@]` / `[!@]` / `[len]` / `[len>]` / `[len<]` / `[len>=]` / `[len<=]` +- **UPDATE SET**:JSON 路径局部更新,原列其他字段保持不变 +- **SELECT slice 字段**:`Slice{profile["age"] AS score}` 支持 JSON 路径 +- **SELECT 字符串字段**:暂不处理,用户写原始 SQL 或 Slice 形式 +- **数组索引**:`["arr"][0]` → `$.arr[0]` +- **多级嵌套**:任意深度 +- **引号兼容**:`["key"]` 和 `['key']` 两种写法完全等效 +- **原有格式零影响**:`user.name` / ``user`.name` / `"name,age"` / `Slice{"name","age"}` 全部不变 + +## 兼容性注意事项 + + +| 级别 | 问题 | 处理方式 | +| --- | --------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | +| 高 | **MySQL 必须 JSON_UNQUOTE**:`JSON_EXTRACT` 返回字符串含外层引号,直接 `= ?` 失败 | `MySQLDialect.JSONExtract()` 统一包 `JSON_UNQUOTE(JSON_EXTRACT(...))` | +| 高 | **PostgreSQL 只支持 `jsonb` 列**:`jsonb_set` 不支持 `json` 类型列 | 文档约定:使用 JSON 路径操作的列需建为 `jsonb` 类型;或调用方在列名后加 `::jsonb` 强转 | +| 高 | **SQLite `[@]` 生成 EXISTS 子查询**:结构与 MySQL/PgSQL 完全不同 | `SQLiteDialect.JSONContains()` 返回 `EXISTS(SELECT 1 FROM json_each(...) WHERE value=?)` 片段,在 `jsonPathCond` 里直接拼入 WHERE | +| 中 | **PgSQL 更新时值的类型转换**:字符串需 `to_jsonb(?::text)`,数值/布尔用 `?::jsonb` | `JSONSet()` 接受 `isStr bool` 参数,由 `jsonPathCond` 根据 Go 值类型传入 | +| 中 | **MySQL 版本**:JSON 函数需 5.7+,`JSON_OVERLAPS` 需 8.0+ | 先实现 5.7 兼容的基础操作符,`[!@]` 用 `NOT JSON_CONTAINS` 实现 | +| 低 | **SQLite 版本**:需 3.9+,`go-sqlite3` 默认已启用 json1 扩展 | 无需特殊处理 | +| 低 | **路径不存在返回 NULL**:三种数据库行为一致 | 调用方自行处理 NULL 判断,或用 `[?]` 操作符先做存在性检查 | + + +## 涉及文件 + +- `[db/dialect.go](db/dialect.go)` — 接口扩展 + 三种方言实现(主要改动) +- `[db/where.go](db/where.go)` — 新增 JSON 路径 key 检测与条件生成 +- `[db/crud.go](db/crud.go)` — UPDATE SET 子句 JSON 路径支持 + diff --git a/.cursor/plans/objtoobj_问题修正_a5e6aae7.plan.md b/.cursor/plans/objtoobj_问题修正_a5e6aae7.plan.md new file mode 100644 index 0000000..9f88e38 --- /dev/null +++ b/.cursor/plans/objtoobj_问题修正_a5e6aae7.plan.md @@ -0,0 +1,305 @@ +--- +name: objtoobj 问题修正 +overview: 对 objtoobj.go 中发现的 3 个运行时 panic bug、2 个错误处理 bug、多处性能问题进行修正,同时新增销量方案所需的 ObjToRoundFloat64 函数。 +todos: + - id: fix-uint8-panics + content: 修复 ObjToFloat64、ObjToInt64、ObjToStr 三处 uint8 分支的 panic bug + status: completed + - id: fix-objtomap-shadow + content: 修复 ObjToMap default 分支变量遮蔽和赋值逻辑错误 + status: completed + - id: fix-objtoslice-default + content: 修复 ObjToSlice default 分支 Marshal 错误被覆盖的问题 + status: completed + - id: fix-objtobool-error + content: 修复 ObjToBool 成功转换时仍报错的问题 + status: completed + - id: fix-objtotime-padding + content: 修复 ObjToTime 单位数日+时间组合时补零失败的 bug + status: completed + - id: fix-objtoceilint64-double + content: 修复 ObjToCeilInt64 双重 Ceil 和多余 ObjToInt64 调用 + status: completed + - id: perf-type-switch-idiom + content: 所有函数改用 switch v := obj.(type) 惯用写法,消除重复类型断言 + status: completed + - id: perf-objtotime + content: ObjToTime 中 ObjToStr(tInt) 只调用一次并缓存 + status: completed + - id: perf-objtoslice-string + content: ObjToSlice []string 分支类型断言只做一次 + status: completed + - id: perf-objtostr-types + content: ObjToStr 补充 float32、bool 等常见类型的直接转换 + status: completed + - id: perf-objtomaparray-cap + content: ObjToMapArray 预分配切片容量 + status: completed + - id: add-roundfloat64 + content: 新增 ObjToRoundFloat64,并在 map.go、obj.go 中新增对应 Get/To 方法 + status: completed +isProject: false +--- + +# objtoobj.go 问题审查与修正计划 + +修改文件:[common/objtoobj.go](d:\work\hotimev1.5\common\objtoobj.go) + +--- + +## 一、严重 Bug(uint8 vs []byte 类型混淆,可能 panic) + +开发者原意是处理数据库 driver 返回的 `[]uint8`(即 `[]byte`),但 Go 中 `uint8`(单字节)和 `[]uint8`/`[]byte`(字节数组)是完全不同的类型,type switch 不会互相匹配。 + +当前 `Row()` 方法(query.go:380)已经用 reflect 将 `[]uint8` 转为 `string`,所以正常 DB 流程不会触发这些分支。但其他非 DB 数据源可能传入 `[]byte`,此时会走 `default` 分支而非预期的 `uint8` 分支。 + +### Bug 1: `ObjToFloat64` — `case uint8` 应为 `case []byte` + +```208:215:d:\work\hotimev1.5\common\objtoobj.go + case uint8: + value, e := strconv.ParseFloat(obj.(string), 64) +``` + +**修正**:改为 `case []byte`,用 `string(v)` 转换后再 ParseFloat: + +```go +case []byte: + value, e := strconv.ParseFloat(string(obj.([]byte)), 64) + if e != nil { + v = float64(0) + err = e + } else { + v = value + } +``` + +### Bug 2: `ObjToInt64` — `case uint8` 应为 `case []byte` + +```279:286:d:\work\hotimev1.5\common\objtoobj.go + case uint8: + value, e := StrToInt(obj.(string)) +``` + +**修正**:改为 `case []byte`,用 `string(v)` 转换后再 StrToInt: + +```go +case []byte: + value, e := StrToInt(string(obj.([]byte))) + if e != nil { + v = int64(0) + err = e + } else { + v = int64(value) + } +``` + +### Bug 3: `ObjToStr` — `case uint8` 应改为正确处理单字节 + +```341:342:d:\work\hotimev1.5\common\objtoobj.go + case uint8: + str = obj.(string) +``` + +`ObjToStr` 已有 `case []byte` 分支正确处理字节数组。这里的 `case uint8` 应正确处理单字节值: + +**修正**:改为 `str = strconv.Itoa(int(obj.(uint8)))` + +> 这三处 bug 目前未爆发,因为 `Row()` 已将 `[]uint8` 转为 `string`。但修正后能覆盖非 DB 数据源直接传入 `[]byte` 的场景,更加健壮。 + +--- + +## 二、逻辑 Bug + +### Bug 4: `ObjToMap` default 分支 — 变量遮蔽导致错误丢失 + +```33:44:d:\work\hotimev1.5\common\objtoobj.go + default: + data, err := json.Marshal(obj) + if err != nil { + err = errors.New("没有合适的转换对象!" + err.Error()) + v = nil + } + v = Map{} + e := json.Unmarshal(data, &v) +``` + +第 34 行的 `data, err := json.Marshal(obj)` 使用了 `:=`,创建了一个**新的局部变量 `err`**,遮蔽了第 14 行声明的外部 `err`。这意味着: + +- Marshal 失败时,外部 `err` 仍然是 nil,错误信息丢失 +- 第 38 行设 `v = nil`,但第 39 行又立刻把 `v` 重新赋值为 `Map{}`,然后用坏数据尝试 Unmarshal + +**修正**:用 `=` 代替 `:=`,并在 Marshal 失败时 break。 + +### Bug 5: `ObjToSlice` default 分支 — Marshal 错误被覆盖 + +```86:91:d:\work\hotimev1.5\common\objtoobj.go + default: + v = Slice{} + var data []byte + data, err = json.Marshal(obj) + err = json.Unmarshal(data, &v) +``` + +第 89 行 `err = json.Marshal(obj)` 即使失败,第 90 行也立刻用 `err = json.Unmarshal(data, &v)` 覆盖了前一个错误。当 Marshal 失败时 `data` 为空,Unmarshal 也会失败,但丢失了原始的错误信息。 + +**修正**:Marshal 失败时直接 break,不再尝试 Unmarshal。 + +### Bug 6: `ObjToBool` — 成功转换时也报错 + +```307:330:d:\work\hotimev1.5\common\objtoobj.go + default: + toInt := ObjToInt(obj) + if toInt != 0 { + v = true + } + err = errors.New("没有合适的转换对象!") +``` + +当传入 `1`(int 类型)时,`ObjToInt` 成功返回 1,`v = true`,但仍然设置了 `err`。逻辑上这不应该报错。 + +**修正**:只在 `ObjToInt` 也无法转换时才报错。 + +--- + +### Bug 7: `ObjToTime` 日期补零逻辑缺陷 + +```108:121:d:\work\hotimev1.5\common\objtoobj.go + timeNewStrs := strings.Split(tStr, "-") + for _, v := range timeNewStrs { + if len(v) == 1 { + v = "0" + v + } + } +``` + +按 `"-"` 分割 `"2006-1-2 15:04:05"` 得到 `["2006", "1", "2 15:04:05"]`。第三段 `"2 15:04:05"` 长度 > 1 不会补零,最终得到 `"2006-01-2 15:04:05"`,`time.Parse` 会失败。 + +**修正**:分割前先按空格分离日期和时间部分,分别处理后再拼接。或者改用 `strings.TrimLeft` 对每段的纯日期部分补零。 + +### Bug 8: `ObjToCeilInt64` 双重 Ceil + 多余 ObjToInt64 + +```238:241:d:\work\hotimev1.5\common\objtoobj.go +func ObjToCeilInt64(obj interface{}, e ...*Error) int64 { + f := ObjToCeilFloat64(obj, e...) // 内部已 math.Ceil + return ObjToInt64(math.Ceil(f)) // 又 Ceil + 走完整 type switch +} +``` + +**修正**:改为 `return int64(f)`,因为 `ObjToCeilFloat64` 已经返回了 Ceil 后的值。 + +--- + +## 三、性能问题 + +### P1: `ObjToTime` 重复调用 `ObjToStr(tInt)` 多达 5 次 + +```152:177:d:\work\hotimev1.5\common\objtoobj.go + if len(ObjToStr(tInt)) > 16 { + // ... + } else if len(ObjToStr(tInt)) > 13 { + // ... + } else if len(ObjToStr(tInt)) > 10 { + // ... + } else if len(ObjToStr(tInt)) > 9 { + // ... + } else if len(ObjToStr(tInt)) > 3 { + t, e := time.Parse("2006", ObjToStr(tInt)) +``` + +每次 `ObjToStr(tInt)` 都会执行 `strconv.FormatInt` + 分配新字符串。应在循环外只调用一次,存入变量复用。 + +### P2: `ObjToSlice` 的 `[]string` 分支重复类型断言 + +```78:81:d:\work\hotimev1.5\common\objtoobj.go + case []string: + v = Slice{} + for i := 0; i < len(obj.([]string)); i++ { + v = append(v, obj.([]string)[i]) + } +``` + +循环中每次 `obj.([]string)` 都做了类型断言。应先断言一次存入变量:`ss := obj.([]string)`。 + +### P3: `ObjToStr` 缺少 `float32`、`bool` 等常见类型直接转换 + +`float32`、`bool`、`int32` 等类型会走 `default` 分支用 `json.MarshalIndent`,比直接转换慢 100 倍以上。 + +**修正**:补充常见类型的 case: + +```go +case float32: + str = strconv.FormatFloat(float64(obj.(float32)), 'f', -1, 32) +case bool: + str = strconv.FormatBool(obj.(bool)) +``` + +### P4: `ObjToStr` default 分支使用 `json.MarshalIndent` + +`MarshalIndent` 比 `Marshal` 慢 20-30%,且产生更大的输出。但由于此改动可能影响现有依赖格式化输出的调用方,标记为**可选优化**。 + +### P5: 所有函数应改用 `switch v := obj.(type)` 惯用写法 + +当前每个 case 都重复做类型断言(如 `obj.(int)`),改为 Go 惯用写法可消除所有冗余断言: + +```go +// 改前 +switch obj.(type) { +case int: + v = int64(obj.(int)) +// 改后 +switch val := obj.(type) { +case int: + v = int64(val) +``` + +### P6: `ObjToMapArray` 未预分配切片容量 + +`res := []Map{}` 应改为 `res := make([]Map, 0, len(s))`。 + +### P7: `StrToMap` / `StrToSlice` 参数名 `string` 遮蔽内置类型 + +参数名应改为 `s` 或 `jsonStr`,避免遮蔽 Go 内置 `string` 类型。 + +--- + +## 四、结合销量方案需新增的函数 + +根据[销量支持小数方案](d:\work\xbc.cursor\plans\销量支持小数方案_92094ec9.plan.md)第二步要求,需在本文件新增 `ObjToRoundFloat64`: + +```go +func ObjToRoundFloat64(obj interface{}, precision int, e ...*Error) float64 { + f := ObjToFloat64(obj, e...) + if precision < 0 { + return f + } + pow := math.Pow10(precision) + return math.Round(f*pow) / pow +} +``` + +同时需在 [common/map.go](d:\work\hotimev1.5\common\map.go) 新增 `GetRoundFloat64`,在 [common/obj.go](d:\work\hotimev1.5\common\obj.go) 新增 `ToRoundFloat64`。 + +--- + +## 五、修正摘要 + + +| # | 类型 | 位置 | 问题 | 修正 | +| --- | ---- | ------------------- | ------------------------- | ---------------------------- | +| 1 | 类型混淆 | ObjToFloat64 uint8 | case uint8 应为 case []byte | 改为 case []byte + string() 解析 | +| 2 | 类型混淆 | ObjToInt64 uint8 | case uint8 应为 case []byte | 改为 case []byte + string() 解析 | +| 3 | 类型混淆 | ObjToStr uint8 | uint8 断言为 string 会 panic | 改为 strconv.Itoa(int(...)) | +| 4 | 逻辑 | ObjToMap default | := 遮蔽外部 err,v 赋值逻辑错乱 | 用 = 并加 break | +| 5 | 逻辑 | ObjToSlice default | Marshal 错误被 Unmarshal 覆盖 | 失败时 break | +| 6 | 逻辑 | ObjToBool default | 成功转换也报错 | 转换成功不报错 | +| 7 | 逻辑 | ObjToTime 日期补零 | 单位数日+时间时补不到零 | 分离日期时间部分再补零 | +| 8 | 逻辑 | ObjToCeilInt64 | 双重 Ceil + 多余 type switch | 改为 int64(f) | +| 9 | 性能 | ObjToTime | ObjToStr 重复调用 5 次 | 缓存结果 | +| 10 | 性能 | ObjToSlice []string | 循环内重复类型断言 | 断言一次复用 | +| 11 | 性能 | 所有函数 | 未用 switch v := obj.(type) | 改用惯用写法消除冗余断言 | +| 12 | 性能 | ObjToStr | 缺少 float32/bool 等常见类型 | 补充直接转换 case | +| 13 | 性能 | ObjToMapArray | 切片未预分配容量 | make([]Map, 0, len(s)) | +| 14 | 规范 | StrToMap/StrToSlice | 参数名 string 遮蔽内置类型 | 改为 s 或 jsonStr | +| 15 | 新增 | ObjToRoundFloat64 | 销量方案需要 | 新增函数 | + + diff --git a/.cursor/plans/row方法方案审查与修正_66435b7a.plan.md b/.cursor/plans/row方法方案审查与修正_66435b7a.plan.md new file mode 100644 index 0000000..ab8b659 --- /dev/null +++ b/.cursor/plans/row方法方案审查与修正_66435b7a.plan.md @@ -0,0 +1,311 @@ +--- +name: Row方法方案审查与修正 +overview: 修改 4 个文件:common/objtoobj.go(新增 JsonToObj + 增强 ObjToMap/ObjToSlice)、common/map.go(增强 JsonToMap)、db/query.go(Row 重写)、cache/cache_db.go(用 JsonToObj 替换 json.Unmarshal)。 +todos: + - id: add-json-safe + content: 在 objtoobj.go 新增 JsonToObj(公共) + fixJsonNumbers(私有),增强 ObjToMap/ObjToSlice 的 4 处 json.Unmarshal + status: completed + - id: fix-json-to-map + content: 增强 map.go 的 JsonToMap,使用 UseNumber + fixJsonNumbers + status: completed + - id: fix-row-method + content: 重写 db/query.go 的 Row() + 新增辅助函数 + 更新 import + status: completed + - id: fix-cache-db + content: cache/cache_db.go 的 3 处 json.Unmarshal 改用 JsonToObj + status: completed +isProject: false +--- + +# Row() + JSON 反序列化类型保留修正方案 + +## 修改文件一览 + + +| 文件 | 改动 | +| ------------------ | --------------------------------------------------------------------------------- | +| common/objtoobj.go | 新增 `JsonToObj` + `fixJsonNumbers`;增强 `ObjToMap`、`ObjToSlice` 的 4 处 json.Unmarshal | +| common/map.go | 增强 `Map.JsonToMap` | +| db/query.go | 重写 Row() + 新增辅助函数 + 更新 import | +| cache/cache_db.go | 3 处 json.Unmarshal 改用 `JsonToObj` | + + +--- + +## 文件 1:common/objtoobj.go + +### 新增 JsonToObj 和 fixJsonNumbers + +在文件 `StrToMap` 之前(约第 378 行前)新增,需要在 import 中添加 `"bytes"`: + +```go +// JsonToObj 将 JSON 字符串反序列化为 interface{},保留数字原始类型。 +// 标准 json.Unmarshal 会把所有 JSON 数字变成 float64,本函数保留整数为 int64、小数为 float64。 +// JsonToObj(`{"id":2,"price":1.20}`) → Map{"id": int64(2), "price": float64(1.2)} +// JsonToObj(`[1, 2.5, "a"]`) → Slice{int64(1), float64(2.5), "a"} +func JsonToObj(jsonStr string) (interface{}, error) { + dec := json.NewDecoder(bytes.NewReader([]byte(jsonStr))) + dec.UseNumber() + var result interface{} + if err := dec.Decode(&result); err != nil { + return nil, err + } + return fixJsonNumbers(result), nil +} + +// fixJsonNumbers 递归将 json.Number 转为 int64 或 float64 +func fixJsonNumbers(v interface{}) interface{} { + switch val := v.(type) { + case json.Number: + if n, err := val.Int64(); err == nil { + return n + } + if f, err := val.Float64(); err == nil { + return f + } + return string(val) + case map[string]interface{}: + for k, item := range val { + val[k] = fixJsonNumbers(item) + } + return val + case []interface{}: + for i, item := range val { + val[i] = fixJsonNumbers(item) + } + return val + default: + return val + } +} +``` + +### 增强 ObjToMap(2 处 json.Unmarshal) + +**位置 1** -- string case(约第 27-31 行): + +```go +// 修改前: +case string: + v = Map{} + e := json.Unmarshal([]byte(obj.(string)), &v) + if e != nil { + err = errors.New("没有合适的转换对象!" + e.Error()) + v = nil + } + +// 修改后: +case string: + result, e := JsonToObj(val) + if e != nil { + err = errors.New("没有合适的转换对象!" + e.Error()) + v = nil + } else if m, ok := result.(map[string]interface{}); ok { + v = m + } else { + err = errors.New("没有合适的转换对象!") + v = nil + } +``` + +**位置 2** -- default case(约第 34-44 行): + +```go +// 修改前: +default: + data, err := json.Marshal(obj) + if err != nil { ... } + v = Map{} + e := json.Unmarshal(data, &v) + if e != nil { ... } + +// 修改后: +default: + data, e2 := json.Marshal(obj) + if e2 != nil { + err = errors.New("没有合适的转换对象!" + e2.Error()) + v = nil + } else { + result, e3 := JsonToObj(string(data)) + if e3 != nil { + err = errors.New("没有合适的转换对象!" + e3.Error()) + v = nil + } else if m, ok := result.(map[string]interface{}); ok { + v = m + } else { + err = errors.New("没有合适的转换对象!") + v = nil + } + } +``` + +### 增强 ObjToSlice(2 处 json.Unmarshal) + +**位置 1** -- string case(约第 82-83 行): + +```go +// 修改前: +case string: + v = Slice{} + err = json.Unmarshal([]byte(obj.(string)), &v) + +// 修改后: +case string: + result, e := JsonToObj(val) + if e != nil { + err = e + } else if s, ok := result.([]interface{}); ok { + v = s + } else { + err = errors.New("没有合适的转换对象!") + } +``` + +**位置 2** -- default case(约第 86-91 行): + +```go +// 修改前: +default: + v = Slice{} + var data []byte + data, err = json.Marshal(obj) + err = json.Unmarshal(data, &v) + +// 修改后: +default: + data, e2 := json.Marshal(obj) + if e2 != nil { + err = e2 + } else { + result, e3 := JsonToObj(string(data)) + if e3 != nil { + err = e3 + } else if s, ok := result.([]interface{}); ok { + v = s + } else { + err = errors.New("没有合适的转换对象!") + } + } +``` + +--- + +## 文件 2:common/map.go + +### 增强 JsonToMap(第 183-189 行) + +需要在 import 中添加 `"bytes"`: + +```go +// 修改前: +func (that Map) JsonToMap(jsonStr string, err ...*Error) { + e := json.Unmarshal([]byte(jsonStr), &that) + if e != nil && len(err) != 0 { + err[0].SetError(e) + } +} + +// 修改后: +func (that Map) JsonToMap(jsonStr string, err ...*Error) { + dec := json.NewDecoder(bytes.NewReader([]byte(jsonStr))) + dec.UseNumber() + e := dec.Decode(&that) + if e != nil { + if len(err) != 0 { + err[0].SetError(e) + } + return + } + for k, v := range that { + that[k] = fixJsonNumbers(v) + } +} +``` + +这里 `fixJsonNumbers` 定义在 `objtoobj.go` 中,同属 `common` 包,可以直接调用。 + +影响链路: + +- `StrToMap(s)` → 调用 `JsonToMap(s)` → **自动修复** +- `Map.JsonToMap(s)` → **直接修复** + +--- + +## 文件 3:db/query.go -- Row() 重写 + +(内容与上一版相同,此处只列关键点) + +### import 变更 + +移除 `"reflect"`,新增 `"math"`、`"strconv"`。 + +### 新增辅助函数 + +`dbTypeUnknown/dbTypeInteger/dbTypeFloat/dbTypeDecimal` 常量 + `classifyDBType` + `getDecimalScale` + `getFloatScale` + `fixFloatValue` + `roundFloat` + `convertBytes`。 + +覆盖 MySQL/SQLite/PostgreSQL 三种数据库的类型名。 + +### Row() 方法替换(第 362-391 行) + +- `defer resl.Close()` + `resl.Err()` 检查 +- `ColumnTypes()` 预分类列类型 +- type switch 替代 reflect:`[]byte` → `convertBytes`,`float64` → `fixFloatValue`,`float32` → `fixFloatValue`,其余不变 + +--- + +## 文件 4:cache/cache_db.go -- DB 缓存 + +3 处 `json.Unmarshal` 改用 `JsonToObj`(来自 common 包,已通过 `. "code.hoteas.com/golang/hotime/common"` 导入): + +**位置 1** -- `getLegacy()` 约第 359-363 行: + +```go +// 修改前: +var data interface{} +err := json.Unmarshal([]byte(valueStr), &data) + +// 修改后: +data, err := JsonToObj(valueStr) +``` + +**位置 2** -- `get()` 约第 411-416 行: + +```go +// 修改前: +var data interface{} +err := json.Unmarshal([]byte(valueStr), &data) + +// 修改后: +data, err := JsonToObj(valueStr) +``` + +**位置 3** -- `CachesGet()` 约第 594-598 行: + +```go +// 修改前: +var data interface{} +err := json.Unmarshal([]byte(valueStr), &data) + +// 修改后: +data, err := JsonToObj(valueStr) +``` + +不需要新增 import(`JsonToObj` 在 common 包,cache_db.go 已 import)。 + +--- + +## 修正效果总结 + + +| 数据路径 | 修正前 | 修正后 | +| ---------------------- | ------------------------------------- | ------------------------------------------------------ | +| DB → Row() → Map | float32/float64 原样存入,[]byte 全转 string | 按列类型精确转换:INT→int64, DECIMAL→roundFloat, FLOAT→fixFloat | +| DB 缓存 → json.Unmarshal | 所有数字→float64 | JsonToObj:整数→int64, 小数→float64 | +| ObjToMap(string) | json.Unmarshal 数字全→float64 | JsonToObj:保留 int64/float64 | +| ObjToSlice(string) | json.Unmarshal 数字全→float64 | JsonToObj:保留 int64/float64 | +| StrToMap(string) | JsonToMap → json.Unmarshal | JsonToMap → UseNumber + fixJsonNumbers | +| Map.JsonToMap(string) | json.Unmarshal | UseNumber + fixJsonNumbers | +| Redis 缓存 | ObjToStr → string → ObjToMap | ObjToMap 已增强,**自动修复** | + + +Redis 缓存路径:`set` 用 `ObjToStr` → `get` 返回 string → 调用方用 `ObjToMap` 转回 Map。由于 `ObjToMap` 的 string 分支现在使用 `JsonToObj`,Redis 缓存也**自动获得类型保留**,无需单独改 cache_redis.go。 \ No newline at end of file diff --git a/application.go b/application.go index 9c9cd2f..7d01224 100644 --- a/application.go +++ b/application.go @@ -411,8 +411,8 @@ func (that *Application) handler(w http.ResponseWriter, req *http.Request) { return } - //url赋值 - path := that.Config.GetString("tpt") + tempHandlerStr + //url赋值,静态文件必须使用原始路径(Linux文件系统大小写敏感) + path := that.Config.GetString("tpt") + context.HandlerStr //判断是否为默认 if path[len(path)-1] == '/' { @@ -448,7 +448,7 @@ func (that *Application) handler(w http.ResponseWriter, req *http.Request) { t := strings.LastIndex(path, ".") if t != -1 { - tt := path[t:] + tt := strings.ToLower(path[t:]) if MimeMaps[tt] != "" { diff --git a/cache/cache_db.go b/cache/cache_db.go index 7cd2f14..8fad537 100644 --- a/cache/cache_db.go +++ b/cache/cache_db.go @@ -357,8 +357,7 @@ func (that *CacheDb) getLegacy(key string) interface{} { return nil } - var data interface{} - err := json.Unmarshal([]byte(valueStr), &data) + data, err := JsonToObj(valueStr) if err != nil { return nil } @@ -409,8 +408,7 @@ func (that *CacheDb) get(key string) interface{} { // 直接解析 value,不再需要 {"data": value} 包装 valueStr := cached.GetString("value") if valueStr != "" { - var data interface{} - err := json.Unmarshal([]byte(valueStr), &data) + data, err := JsonToObj(valueStr) if err == nil { return data } @@ -592,8 +590,7 @@ func (that *CacheDb) CachesGet(keys []string) Map { for _, cached := range cachedList { valueStr := cached.GetString("value") if valueStr != "" { - var data interface{} - err := json.Unmarshal([]byte(valueStr), &data) + data, err := JsonToObj(valueStr) if err == nil { result[cached.GetString("key")] = data } else { diff --git a/common/map.go b/common/map.go index 4992c52..73aa38b 100644 --- a/common/map.go +++ b/common/map.go @@ -1,6 +1,7 @@ package common import ( + "bytes" "encoding/json" "errors" "reflect" @@ -181,9 +182,21 @@ func (that Map) ToJsonString() string { } func (that Map) JsonToMap(jsonStr string, err ...*Error) { - e := json.Unmarshal([]byte(jsonStr), &that) - if e != nil && len(err) != 0 { - err[0].SetError(e) + dec := json.NewDecoder(bytes.NewReader([]byte(jsonStr))) + dec.UseNumber() + e := dec.Decode(&that) + if e != nil { + if len(err) != 0 { + err[0].SetError(e) + } + return + } + for k, v := range that { + that[k] = fixJsonNumbers(v) } - +} + +// GetRoundFloat64 获取四舍五入到 precision 位小数的 float64 +func (that Map) GetRoundFloat64(key string, precision int, err ...*Error) float64 { + return ObjToRoundFloat64((that)[key], precision, err...) } diff --git a/common/obj.go b/common/obj.go index 224f1d3..86ed1aa 100644 --- a/common/obj.go +++ b/common/obj.go @@ -101,3 +101,8 @@ func (that *Obj) ToCeilInt(err ...*Error) int { return v } + +// ToRoundFloat64 获取四舍五入到 precision 位小数的 float64 +func (that *Obj) ToRoundFloat64(precision int, err ...*Error) float64 { + return ObjToRoundFloat64(that.Data, precision, err...) +} diff --git a/common/objtoobj.go b/common/objtoobj.go index 40b1620..87ea0b9 100644 --- a/common/objtoobj.go +++ b/common/objtoobj.go @@ -1,6 +1,7 @@ package common import ( + "bytes" "encoding/json" "errors" "math" @@ -18,31 +19,40 @@ func ObjToMap(obj interface{}, e ...*Error) Map { v = nil err = errors.New("没有合适的转换对象!") } else { - switch obj.(type) { + switch val := obj.(type) { case Map: - v = obj.(Map) + v = val case map[string]interface{}: - v = obj.(map[string]interface{}) + v = Map(val) case string: - v = Map{} - e := json.Unmarshal([]byte(obj.(string)), &v) - if e != nil { - err = errors.New("没有合适的转换对象!" + e.Error()) + result, e2 := JsonToObj(val) + if e2 != nil { + err = errors.New("没有合适的转换对象!" + e2.Error()) + v = nil + } else if m, ok := result.(map[string]interface{}); ok { + v = m + } else { + err = errors.New("没有合适的转换对象!") v = nil } default: - data, err := json.Marshal(obj) + var data []byte + data, err = json.Marshal(obj) if err != nil { err = errors.New("没有合适的转换对象!" + err.Error()) v = nil + break } - v = Map{} - e := json.Unmarshal(data, &v) - if e != nil { - err = errors.New("没有合适的转换对象!" + e.Error()) + result, e2 := JsonToObj(string(data)) + if e2 != nil { + err = errors.New("没有合适的转换对象!" + e2.Error()) + v = nil + } else if m, ok := result.(map[string]interface{}); ok { + v = m + } else { + err = errors.New("没有合适的转换对象!") v = nil } - } } if len(e) != 0 { @@ -53,7 +63,7 @@ func ObjToMap(obj interface{}, e ...*Error) Map { func ObjToMapArray(obj interface{}, e ...*Error) []Map { s := ObjToSlice(obj, e...) - res := []Map{} + res := make([]Map, 0, len(s)) for i := 0; i < len(s); i++ { res = append(res, s.GetMap(i)) } @@ -69,25 +79,39 @@ func ObjToSlice(obj interface{}, e ...*Error) Slice { v = nil err = errors.New("没有合适的转换对象!") } else { - switch obj.(type) { + switch val := obj.(type) { case Slice: - v = obj.(Slice) + v = val case []interface{}: - v = obj.([]interface{}) + v = Slice(val) case []string: - v = Slice{} - for i := 0; i < len(obj.([]string)); i++ { - v = append(v, obj.([]string)[i]) + v = make(Slice, len(val)) + for i, s := range val { + v[i] = s } case string: - v = Slice{} - err = json.Unmarshal([]byte(obj.(string)), &v) - + result, e2 := JsonToObj(val) + if e2 != nil { + err = e2 + } else if s, ok := result.([]interface{}); ok { + v = s + } else { + err = errors.New("没有合适的转换对象!") + } default: - v = Slice{} var data []byte data, err = json.Marshal(obj) - err = json.Unmarshal(data, &v) + if err != nil { + break + } + result, e2 := JsonToObj(string(data)) + if e2 != nil { + err = e2 + } else if s, ok := result.([]interface{}); ok { + v = s + } else { + err = errors.New("没有合适的转换对象!") + } } } @@ -104,22 +128,24 @@ func ObjToTime(obj interface{}, e ...*Error) *time.Time { //字符串类型,只支持标准mysql datetime格式 if tInt == 0 { tStr := ObjToStr(obj) - timeNewStr := "" - timeNewStrs := strings.Split(tStr, "-") - for _, v := range timeNewStrs { - if v == "" { - continue - } - if len(v) == 1 { - v = "0" + v - } - if timeNewStr == "" { - timeNewStr = v - continue - } - timeNewStr = timeNewStr + "-" + v + // 先分离日期和时间部分,再对日期各段补零,避免时间中的数字被误处理 + datePart := tStr + timePart := "" + if idx := strings.Index(tStr, " "); idx > 0 { + datePart = tStr[:idx] + timePart = tStr[idx:] // 含前导空格 } - tStr = timeNewStr + dateSegs := strings.Split(datePart, "-") + for i, seg := range dateSegs { + if seg == "" { + continue + } + if len(seg) == 1 { + dateSegs[i] = "0" + seg + } + } + tStr = strings.Join(dateSegs, "-") + timePart + if len(tStr) > 18 { t, e := time.Parse("2006-01-02 15:04:05", tStr) if e == nil { @@ -146,31 +172,30 @@ func ObjToTime(obj interface{}, e ...*Error) *time.Time { return &t } } - } + // 缓存字符串,避免重复调用 + tIntStr := ObjToStr(tInt) + tIntLen := len(tIntStr) + //纳秒级别 - if len(ObjToStr(tInt)) > 16 { - //t := time.Time{}.Add(time.Nanosecond * time.Duration(tInt)) + if tIntLen > 16 { t := time.UnixMicro(tInt / 1000) return &t //微秒级别 - } else if len(ObjToStr(tInt)) > 13 { - //t := time.Time{}.Add(time.Microsecond * time.Duration(tInt)) + } else if tIntLen > 13 { t := time.UnixMicro(tInt) return &t //毫秒级别 - } else if len(ObjToStr(tInt)) > 10 { - //t := time.Time{}.Add(time.Millisecond * time.Duration(tInt)) + } else if tIntLen > 10 { t := time.UnixMilli(tInt) return &t //秒级别 - } else if len(ObjToStr(tInt)) > 9 { - //t := time.Time{}.Add(time.Second * time.Duration(tInt)) + } else if tIntLen > 9 { t := time.Unix(tInt, 0) return &t - } else if len(ObjToStr(tInt)) > 3 { - t, e := time.Parse("2006", ObjToStr(tInt)) + } else if tIntLen > 3 { + t, e := time.Parse("2006", tIntStr) if e == nil { return &t } @@ -184,37 +209,34 @@ func ObjToFloat64(obj interface{}, e ...*Error) float64 { v := float64(0) if obj == nil { - err = errors.New("没有合适的转换对象!") } else { - - switch obj.(type) { + switch val := obj.(type) { case int: - v = float64(obj.(int)) + v = float64(val) case int64: - v = float64(obj.(int64)) + v = float64(val) case string: - value, e := strconv.ParseFloat(obj.(string), 64) - if e != nil { - v = float64(0) - err = e + value, e2 := strconv.ParseFloat(val, 64) + if e2 != nil { + err = e2 } else { v = value } case float64: - v = obj.(float64) + v = val case float32: - v = float64(obj.(float32)) - case uint8: - value, e := strconv.ParseFloat(obj.(string), 64) - if e != nil { - v = float64(0) - err = e + // 通过 string 中转避免 float32→float64 精度膨胀(如 float32(1.2) 直接转得 1.200000047...) + v, _ = strconv.ParseFloat(strconv.FormatFloat(float64(val), 'f', -1, 32), 64) + case []byte: + // 数据库 driver 有时以 []byte 返回数值字符串 + value, e2 := strconv.ParseFloat(string(val), 64) + if e2 != nil { + err = e2 } else { v = value } default: - v = float64(0) err = errors.New("没有合适的转换对象!") } } @@ -234,25 +256,33 @@ func ObjToFloat64(obj interface{}, e ...*Error) float64 { return v } +// ObjToRoundFloat64 将 obj 转为 float64 后四舍五入到 precision 位小数。 +// precision < 0 时不做舍入,直接返回原始 float64。 +func ObjToRoundFloat64(obj interface{}, precision int, e ...*Error) float64 { + f := ObjToFloat64(obj, e...) + if precision < 0 { + return f + } + pow := math.Pow10(precision) + return math.Round(f*pow) / pow +} + // 向上取整 func ObjToCeilInt64(obj interface{}, e ...*Error) int64 { f := ObjToCeilFloat64(obj, e...) - return ObjToInt64(math.Ceil(f)) - + return int64(f) } // 向上取整 func ObjToCeilFloat64(obj interface{}, e ...*Error) float64 { f := ObjToFloat64(obj, e...) return math.Ceil(f) - } // 向上取整 func ObjToCeilInt(obj interface{}, e ...*Error) int { f := ObjToCeilFloat64(obj, e...) return ObjToInt(f) - } func ObjToInt64(obj interface{}, e ...*Error) int64 { @@ -260,36 +290,43 @@ func ObjToInt64(obj interface{}, e ...*Error) int64 { v := int64(0) if obj == nil { - err = errors.New("没有合适的转换对象!") } else { - switch obj.(type) { + switch val := obj.(type) { case int: - v = int64(obj.(int)) + v = int64(val) case int64: - v = obj.(int64) + v = val case string: - value, e := StrToInt(obj.(string)) - if e != nil { - v = int64(0) - err = e + value, e2 := StrToInt(val) + if e2 != nil { + // fallback:处理 "1.20" 这类带小数点的整数字符串 + if fv, fe := strconv.ParseFloat(val, 64); fe == nil { + v = int64(fv) + } else { + err = e2 + } } else { v = int64(value) } - case uint8: - value, e := StrToInt(obj.(string)) - if e != nil { - v = int64(0) - err = e + case []byte: + // 数据库 driver 有时以 []byte 返回整数字符串 + value, e2 := StrToInt(string(val)) + if e2 != nil { + // fallback:处理 "1.20" 这类带小数点的整数字符串 + if fv, fe := strconv.ParseFloat(string(val), 64); fe == nil { + v = int64(fv) + } else { + err = e2 + } } else { v = int64(value) } case float64: - v = int64(obj.(float64)) + v = int64(val) case float32: - v = int64(obj.(float32)) + v = int64(val) default: - v = int64(0) err = errors.New("没有合适的转换对象!") } } @@ -309,18 +346,19 @@ func ObjToBool(obj interface{}, e ...*Error) bool { v := false if obj == nil { - err = errors.New("没有合适的转换对象!") } else { - switch obj.(type) { + switch val := obj.(type) { case bool: - v = obj.(bool) + v = val default: - toInt := ObjToInt(obj) - if toInt != 0 { + var intErr Error + toInt := ObjToInt(obj, &intErr) + if intErr.GetError() != nil { + err = errors.New("没有合适的转换对象!") + } else if toInt != 0 { v = true } - err = errors.New("没有合适的转换对象!") } } if len(e) != 0 { @@ -330,55 +368,92 @@ func ObjToBool(obj interface{}, e ...*Error) bool { } func ObjToStr(obj interface{}) string { - // fmt.Println(reflect.ValueOf(obj).Type().String() ) str := "" if obj == nil { return str } - switch obj.(type) { + switch val := obj.(type) { case int: - str = strconv.Itoa(obj.(int)) + str = strconv.Itoa(val) case uint8: - str = obj.(string) + str = strconv.Itoa(int(val)) case int64: - str = strconv.FormatInt(obj.(int64), 10) + str = strconv.FormatInt(val, 10) case []byte: - str = string(obj.([]byte)) + str = string(val) case string: - str = obj.(string) + str = val case float64: - str = strconv.FormatFloat(obj.(float64), 'f', -1, 64) + str = strconv.FormatFloat(val, 'f', -1, 64) + case float32: + str = strconv.FormatFloat(float64(val), 'f', -1, 32) + case bool: + str = strconv.FormatBool(val) default: strbte, err := json.MarshalIndent(obj, "", "\t") - if err == nil { str = string(strbte) } - } return str } -// 转换为Map -func StrToMap(string string) Map { - data := Map{} - data.JsonToMap(string) +// JsonToObj 将 JSON 字符串反序列化为 interface{},保留数字原始类型。 +// 标准 json.Unmarshal 会把所有 JSON 数字变成 float64,本函数保留整数为 int64、小数为 float64。 +// +// JsonToObj(`{"id":2,"price":1.20}`) → map{"id": int64(2), "price": float64(1.2)} +// JsonToObj(`[1, 2.5, "a"]`) → []interface{}{int64(1), float64(2.5), "a"} +func JsonToObj(jsonStr string) (interface{}, error) { + dec := json.NewDecoder(bytes.NewReader([]byte(jsonStr))) + dec.UseNumber() + var result interface{} + if err := dec.Decode(&result); err != nil { + return nil, err + } + return fixJsonNumbers(result), nil +} +// fixJsonNumbers 递归将 json.Number 转为 int64 或 float64 +func fixJsonNumbers(v interface{}) interface{} { + switch val := v.(type) { + case json.Number: + if n, err := val.Int64(); err == nil { + return n + } + if f, err := val.Float64(); err == nil { + return f + } + return string(val) + case map[string]interface{}: + for k, item := range val { + val[k] = fixJsonNumbers(item) + } + return val + case []interface{}: + for i, item := range val { + val[i] = fixJsonNumbers(item) + } + return val + default: + return val + } +} + +// 转换为Map +func StrToMap(s string) Map { + data := Map{} + data.JsonToMap(s) return data } // 转换为Slice -func StrToSlice(string string) Slice { - - data := ObjToSlice(string) - - return data +func StrToSlice(s string) Slice { + return ObjToSlice(s) } // 字符串数组: a1,a2,a3转["a1","a2","a3"] func StrArrayToJsonStr(a string) string { - if len(a) > 2 { if a[0] == ',' { a = Substr(a, 1, len(a)-1) @@ -386,7 +461,6 @@ func StrArrayToJsonStr(a string) string { if a[len(a)-1] == ',' { a = Substr(a, 0, len(a)-1) } - //a = strings.Replace(a, ",", `,`, -1) a = `[` + a + `]` } else { a = "[]" @@ -394,13 +468,11 @@ func StrArrayToJsonStr(a string) string { return a } -// 字符串数组: a1,a2,a3转["a1","a2","a3"] +// 字符串数组: ["a1","a2","a3"]转,a1,a2,a3, func JsonStrToStrArray(a string) string { - //a = strings.Replace(a, `"`, "", -1) if len(a) != 0 { a = Substr(a, 1, len(a)-2) } - return "," + a + "," } @@ -408,5 +480,4 @@ func JsonStrToStrArray(a string) string { func StrToInt(s string) (int, error) { i, err := strconv.Atoi(s) return i, err - } diff --git a/common/objtoobj_test.go b/common/objtoobj_test.go new file mode 100644 index 0000000..5c26c64 --- /dev/null +++ b/common/objtoobj_test.go @@ -0,0 +1,917 @@ +package common + +import ( + "math" + "testing" + "time" +) + +// ───────────────────────────────────────────── +// 辅助:检查 Error 是否携带错误 +// ───────────────────────────────────────────── +func hasErr(e *Error) bool { + return e.GetError() != nil +} + +func newErr() *Error { + return &Error{} +} + +// ───────────────────────────────────────────── +// ObjToMap +// ───────────────────────────────────────────── +func TestObjToMap(t *testing.T) { + t.Run("nil returns nil with error", func(t *testing.T) { + e := newErr() + v := ObjToMap(nil, e) + if v != nil || !hasErr(e) { + t.Errorf("nil: got %v err=%v", v, e.GetError()) + } + }) + + t.Run("Map passthrough", func(t *testing.T) { + input := Map{"a": 1} + v := ObjToMap(input) + if v == nil || v["a"] != 1 { + t.Errorf("Map passthrough failed: %v", v) + } + }) + + t.Run("map[string]interface{} cast", func(t *testing.T) { + input := map[string]interface{}{"b": 2} + v := ObjToMap(input) + if v == nil || v["b"] != 2 { + t.Errorf("map[string]interface{} cast failed: %v", v) + } + }) + + t.Run("valid JSON string - integer preserved as int64", func(t *testing.T) { + v := ObjToMap(`{"x":42}`) + if v == nil || v["x"] != int64(42) { + t.Errorf("JSON string: got %v (type %T), want int64(42)", v["x"], v["x"]) + } + }) + + t.Run("invalid JSON string returns nil with error", func(t *testing.T) { + e := newErr() + v := ObjToMap(`not json`, e) + if v != nil || !hasErr(e) { + t.Errorf("invalid JSON: got %v err=%v", v, e.GetError()) + } + }) + + t.Run("struct default branch marshal+unmarshal", func(t *testing.T) { + type S struct{ Name string } + v := ObjToMap(S{Name: "hello"}) + if v == nil || v["Name"] != "hello" { + t.Errorf("struct default: %v", v) + } + }) + + t.Run("marshal fail (channel) returns nil with error", func(t *testing.T) { + e := newErr() + ch := make(chan int) + v := ObjToMap(ch, e) + if v != nil || !hasErr(e) { + t.Errorf("channel marshal: got %v err=%v", v, e.GetError()) + } + }) + + t.Run("default marshal OK but unmarshal to map fails (int)", func(t *testing.T) { + // json.Marshal(123) = "123", which can't unmarshal to Map + e := newErr() + v := ObjToMap(123, e) + if v != nil || !hasErr(e) { + t.Errorf("int default: got %v err=%v", v, e.GetError()) + } + }) +} + +// ───────────────────────────────────────────── +// ObjToMapArray +// ───────────────────────────────────────────── +func TestObjToMapArray(t *testing.T) { + t.Run("nil returns empty", func(t *testing.T) { + e := newErr() + v := ObjToMapArray(nil, e) + if len(v) != 0 || !hasErr(e) { + t.Errorf("nil: got %v err=%v", v, e.GetError()) + } + }) + + t.Run("JSON array of maps", func(t *testing.T) { + v := ObjToMapArray(`[{"a":1},{"b":2}]`) + if len(v) != 2 { + t.Errorf("expected 2 elements, got %d", len(v)) + } + }) +} + +// ───────────────────────────────────────────── +// ObjToSlice +// ───────────────────────────────────────────── +func TestObjToSlice(t *testing.T) { + t.Run("nil returns nil with error", func(t *testing.T) { + e := newErr() + v := ObjToSlice(nil, e) + if v != nil || !hasErr(e) { + t.Errorf("nil: got %v err=%v", v, e.GetError()) + } + }) + + t.Run("Slice passthrough", func(t *testing.T) { + input := Slice{1, 2, 3} + v := ObjToSlice(input) + if len(v) != 3 { + t.Errorf("Slice passthrough: %v", v) + } + }) + + t.Run("[]interface{} cast", func(t *testing.T) { + input := []interface{}{4, 5} + v := ObjToSlice(input) + if len(v) != 2 || v[0] != 4 { + t.Errorf("[]interface{} cast: %v", v) + } + }) + + t.Run("[]string conversion", func(t *testing.T) { + input := []string{"a", "b", "c"} + v := ObjToSlice(input) + if len(v) != 3 || v[0] != "a" { + t.Errorf("[]string: %v", v) + } + }) + + t.Run("valid JSON array string", func(t *testing.T) { + v := ObjToSlice(`[1,2,3]`) + if len(v) != 3 { + t.Errorf("JSON array: %v", v) + } + }) + + t.Run("invalid JSON string returns error", func(t *testing.T) { + e := newErr() + v := ObjToSlice(`not json`, e) + if !hasErr(e) { + t.Errorf("invalid JSON: got %v err=%v", v, e.GetError()) + } + }) + + t.Run("default: []int marshals to JSON array", func(t *testing.T) { + v := ObjToSlice([]int{10, 20, 30}) + if len(v) != 3 { + t.Errorf("[]int default: %v", v) + } + }) + + t.Run("default: marshal fail (channel) returns error", func(t *testing.T) { + e := newErr() + ch := make(chan int) + ObjToSlice(ch, e) + if !hasErr(e) { + t.Errorf("channel marshal: expected error") + } + }) +} + +// ───────────────────────────────────────────── +// ObjToTime +// ───────────────────────────────────────────── +func TestObjToTime(t *testing.T) { + mustParse := func(layout, s string) time.Time { + tt, err := time.Parse(layout, s) + if err != nil { + panic(err) + } + return tt + } + + t.Run("full datetime string", func(t *testing.T) { + ts := ObjToTime("2006-01-02 15:04:05") + want := mustParse("2006-01-02 15:04:05", "2006-01-02 15:04:05") + if ts == nil || !ts.Equal(want) { + t.Errorf("full datetime: %v", ts) + } + }) + + t.Run("single digit day+time (bug fix)", func(t *testing.T) { + ts := ObjToTime("2006-1-2 15:04:05") + want := mustParse("2006-01-02 15:04:05", "2006-01-02 15:04:05") + if ts == nil || !ts.Equal(want) { + t.Errorf("single digit day+time: got %v, want %v", ts, want) + } + }) + + t.Run("datetime without seconds", func(t *testing.T) { + ts := ObjToTime("2006-01-02 15:04") + want := mustParse("2006-01-02 15:04", "2006-01-02 15:04") + if ts == nil || !ts.Equal(want) { + t.Errorf("datetime without seconds: %v", ts) + } + }) + + t.Run("date+hour only", func(t *testing.T) { + ts := ObjToTime("2006-01-02 15") + want := mustParse("2006-01-02 15", "2006-01-02 15") + if ts == nil || !ts.Equal(want) { + t.Errorf("date+hour: %v", ts) + } + }) + + t.Run("date only", func(t *testing.T) { + ts := ObjToTime("2006-01-02") + want := mustParse("2006-01-02", "2006-01-02") + if ts == nil || !ts.Equal(want) { + t.Errorf("date only: %v", ts) + } + }) + + t.Run("month only", func(t *testing.T) { + ts := ObjToTime("2006-01") + want := mustParse("2006-01", "2006-01") + if ts == nil || !ts.Equal(want) { + t.Errorf("month only: %v", ts) + } + }) + + t.Run("invalid string returns nil", func(t *testing.T) { + ts := ObjToTime("not-a-date") + if ts != nil { + t.Errorf("invalid string: expected nil, got %v", ts) + } + }) + + t.Run("unix second timestamp (10 digits)", func(t *testing.T) { + ts := ObjToTime(int64(1700000000)) // 10 digits + if ts == nil { + t.Error("second timestamp: got nil") + } + want := time.Unix(1700000000, 0) + if !ts.Equal(want) { + t.Errorf("second: %v vs %v", ts, want) + } + }) + + t.Run("unix millisecond timestamp (13 digits)", func(t *testing.T) { + ts := ObjToTime(int64(1700000000000)) // 13 digits + if ts == nil { + t.Error("millisecond timestamp: got nil") + } + want := time.UnixMilli(1700000000000) + if !ts.Equal(want) { + t.Errorf("millisecond: %v vs %v", ts, want) + } + }) + + t.Run("unix microsecond timestamp (16 digits)", func(t *testing.T) { + ts := ObjToTime(int64(1700000000000000)) // 16 digits + if ts == nil { + t.Error("microsecond timestamp: got nil") + } + want := time.UnixMicro(1700000000000000) + if !ts.Equal(want) { + t.Errorf("microsecond: %v vs %v", ts, want) + } + }) + + t.Run("nanosecond timestamp (19 digits)", func(t *testing.T) { + ts := ObjToTime(int64(1700000000000000000)) // 19 digits -> nano branch + if ts == nil { + t.Error("nanosecond timestamp: got nil") + } + want := time.UnixMicro(1700000000000000000 / 1000) + if !ts.Equal(want) { + t.Errorf("nanosecond: %v vs %v", ts, want) + } + }) + + t.Run("year int (4 digits)", func(t *testing.T) { + ts := ObjToTime(int64(2024)) + if ts == nil { + t.Error("year int: got nil") + } + want := mustParse("2006", "2024") + if !ts.Equal(want) { + t.Errorf("year: %v vs %v", ts, want) + } + }) + + t.Run("small int returns nil", func(t *testing.T) { + ts := ObjToTime(int64(1)) + if ts != nil { + t.Errorf("small int: expected nil, got %v", ts) + } + }) + + t.Run("nil returns nil", func(t *testing.T) { + ts := ObjToTime(nil) + if ts != nil { + t.Errorf("nil: expected nil, got %v", ts) + } + }) +} + +// ───────────────────────────────────────────── +// ObjToFloat64 +// ───────────────────────────────────────────── +func TestObjToFloat64(t *testing.T) { + cases := []struct { + name string + input interface{} + want float64 + wantErr bool + }{ + {"nil", nil, 0, true}, + {"int", 42, 42.0, false}, + {"int64", int64(100), 100.0, false}, + {"string valid", "3.14", 3.14, false}, + {"string invalid", "abc", 0, true}, + {"float64", float64(1.5), 1.5, false}, + // Fix4: float32 通过 strconv 转换,消除精度膨胀噪声 + {"float32 no precision inflation", float32(1.2), 1.2, false}, + {"[]byte valid", []byte("2.71"), 2.71, false}, + {"[]byte invalid", []byte("xyz"), 0, true}, + {"unknown type", struct{}{}, 0, true}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + e := newErr() + got := ObjToFloat64(c.input, e) + if math.Abs(got-c.want) > 1e-9 { + t.Errorf("value: got %v, want %v", got, c.want) + } + if hasErr(e) != c.wantErr { + t.Errorf("error: got hasErr=%v, wantErr=%v (err=%v)", hasErr(e), c.wantErr, e.GetError()) + } + }) + } + + t.Run("NaN is caught and zeroed", func(t *testing.T) { + e := newErr() + v := ObjToFloat64(math.NaN(), e) + if v != 0 || !hasErr(e) { + t.Errorf("NaN: got %v err=%v", v, e.GetError()) + } + }) + + t.Run("Inf is caught and zeroed", func(t *testing.T) { + e := newErr() + v := ObjToFloat64(math.Inf(1), e) + if v != 0 || !hasErr(e) { + t.Errorf("Inf: got %v err=%v", v, e.GetError()) + } + }) + + t.Run("-Inf is caught and zeroed", func(t *testing.T) { + e := newErr() + v := ObjToFloat64(math.Inf(-1), e) + if v != 0 || !hasErr(e) { + t.Errorf("-Inf: got %v err=%v", v, e.GetError()) + } + }) +} + +// ───────────────────────────────────────────── +// ObjToRoundFloat64 +// ───────────────────────────────────────────── +func TestObjToRoundFloat64(t *testing.T) { + t.Run("precision 2 rounds correctly", func(t *testing.T) { + v := ObjToRoundFloat64(3.5000001, 2) + if v != 3.5 { + t.Errorf("got %v, want 3.5", v) + } + }) + + t.Run("precision 0 rounds to integer", func(t *testing.T) { + v := ObjToRoundFloat64(2.7, 0) + if v != 3.0 { + t.Errorf("got %v, want 3.0", v) + } + }) + + t.Run("precision -1 returns raw float", func(t *testing.T) { + v := ObjToRoundFloat64(1.23456789, -1) + if math.Abs(v-1.23456789) > 1e-9 { + t.Errorf("got %v, want ~1.23456789", v) + } + }) + + t.Run("string input with float noise", func(t *testing.T) { + v := ObjToRoundFloat64("3.15000001", 2) + if v != 3.15 { + t.Errorf("got %v, want 3.15", v) + } + }) +} + +// ───────────────────────────────────────────── +// ObjToCeil* +// ───────────────────────────────────────────── +func TestObjToCeil(t *testing.T) { + t.Run("ObjToCeilInt64 rounds up", func(t *testing.T) { + v := ObjToCeilInt64(1.1) + if v != 2 { + t.Errorf("got %d, want 2", v) + } + }) + + t.Run("ObjToCeilInt64 already integer", func(t *testing.T) { + v := ObjToCeilInt64(float64(3)) + if v != 3 { + t.Errorf("got %d, want 3", v) + } + }) + + t.Run("ObjToCeilInt64 no double ceil (bug fix)", func(t *testing.T) { + // 3.0 -> Ceil(3.0)=3.0 -> int64(3), NOT Ceil(3.0) again + v := ObjToCeilInt64(float64(3)) + if v != 3 { + t.Errorf("double ceil: got %d, want 3", v) + } + }) + + t.Run("ObjToCeilFloat64", func(t *testing.T) { + v := ObjToCeilFloat64(1.1) + if v != 2.0 { + t.Errorf("got %v, want 2.0", v) + } + }) + + t.Run("ObjToCeilInt", func(t *testing.T) { + v := ObjToCeilInt(2.3) + if v != 3 { + t.Errorf("got %d, want 3", v) + } + }) +} + +// ───────────────────────────────────────────── +// ObjToInt64 +// ───────────────────────────────────────────── +func TestObjToInt64(t *testing.T) { + cases := []struct { + name string + input interface{} + want int64 + wantErr bool + }{ + {"nil", nil, 0, true}, + {"int", 7, 7, false}, + {"int64", int64(99), 99, false}, + {"string valid int", "42", 42, false}, + // Fix3 fallback: "1.20" 原来报错,现在截断得 1 + {"string float-like", "1.20", 1, false}, + {"string invalid", "abc", 0, true}, + {"[]byte valid int", []byte("123"), 123, false}, + // Fix3 fallback: []byte "2.70" 截断得 2 + {"[]byte float-like", []byte("2.70"), 2, false}, + {"[]byte invalid", []byte("xyz"), 0, true}, + {"float64 truncate", float64(5.9), 5, false}, + {"float32 truncate", float32(3.1), 3, false}, + {"unknown type", struct{}{}, 0, true}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + e := newErr() + got := ObjToInt64(c.input, e) + if got != c.want { + t.Errorf("value: got %d, want %d", got, c.want) + } + if hasErr(e) != c.wantErr { + t.Errorf("error: got hasErr=%v, wantErr=%v (err=%v)", hasErr(e), c.wantErr, e.GetError()) + } + }) + } +} + +// ───────────────────────────────────────────── +// ObjToInt +// ───────────────────────────────────────────── +func TestObjToInt(t *testing.T) { + v := ObjToInt(10) + if v != 10 { + t.Errorf("got %d, want 10", v) + } +} + +// ───────────────────────────────────────────── +// ObjToBool +// ───────────────────────────────────────────── +func TestObjToBool(t *testing.T) { + t.Run("nil returns false with error", func(t *testing.T) { + e := newErr() + v := ObjToBool(nil, e) + if v || !hasErr(e) { + t.Errorf("nil: got %v err=%v", v, e.GetError()) + } + }) + + t.Run("bool true", func(t *testing.T) { + e := newErr() + v := ObjToBool(true, e) + if !v || hasErr(e) { + t.Errorf("bool true: got %v err=%v", v, e.GetError()) + } + }) + + t.Run("bool false", func(t *testing.T) { + e := newErr() + v := ObjToBool(false, e) + if v || hasErr(e) { + t.Errorf("bool false: got %v err=%v", v, e.GetError()) + } + }) + + t.Run("int 1 -> true, no error (bug fix)", func(t *testing.T) { + e := newErr() + v := ObjToBool(1, e) + if !v || hasErr(e) { + t.Errorf("int 1: got v=%v err=%v", v, e.GetError()) + } + }) + + t.Run("int 0 -> false, no error", func(t *testing.T) { + e := newErr() + v := ObjToBool(0, e) + if v || hasErr(e) { + t.Errorf("int 0: got v=%v err=%v", v, e.GetError()) + } + }) + + t.Run("unknown type returns false with error", func(t *testing.T) { + e := newErr() + v := ObjToBool(struct{}{}, e) + if v || !hasErr(e) { + t.Errorf("struct: got v=%v err=%v", v, e.GetError()) + } + }) +} + +// ───────────────────────────────────────────── +// ObjToStr +// ───────────────────────────────────────────── +func TestObjToStr(t *testing.T) { + cases := []struct { + name string + input interface{} + want string + }{ + {"nil", nil, ""}, + {"int", 42, "42"}, + {"uint8", uint8(65), "65"}, + {"int64", int64(1234567890), "1234567890"}, + {"[]byte", []byte("hello"), "hello"}, + {"string", "world", "world"}, + {"float64", float64(3.14), "3.14"}, + {"float32", float32(2.5), "2.5"}, + {"bool true", true, "true"}, + {"bool false", false, "false"}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := ObjToStr(c.input) + if got != c.want { + t.Errorf("got %q, want %q", got, c.want) + } + }) + } + + t.Run("default (struct) marshals to JSON", func(t *testing.T) { + type S struct{ X int } + got := ObjToStr(S{X: 1}) + if got == "" { + t.Error("struct: expected non-empty JSON string") + } + }) +} + +// ───────────────────────────────────────────── +// StrToMap +// ───────────────────────────────────────────── +func TestStrToMap(t *testing.T) { + t.Run("valid JSON", func(t *testing.T) { + v := StrToMap(`{"k":"v"}`) + if v["k"] != "v" { + t.Errorf("StrToMap: %v", v) + } + }) + + t.Run("invalid JSON returns empty map", func(t *testing.T) { + v := StrToMap(`not json`) + if v == nil { + t.Error("StrToMap invalid: expected empty map, got nil") + } + }) +} + +// ───────────────────────────────────────────── +// StrToSlice +// ───────────────────────────────────────────── +func TestStrToSlice(t *testing.T) { + v := StrToSlice(`[1,2,3]`) + if len(v) != 3 { + t.Errorf("StrToSlice: expected 3 elements, got %d", len(v)) + } +} + +// ───────────────────────────────────────────── +// StrArrayToJsonStr +// ───────────────────────────────────────────── +func TestStrArrayToJsonStr(t *testing.T) { + cases := []struct { + name string + input string + want string + }{ + {"empty returns []", "", "[]"}, + {"short (<=2) returns []", "a", "[]"}, + {"no commas", `"a","b"`, `["a","b"]`}, + {"leading comma stripped", `,"a","b"`, `["a","b"]`}, + {"trailing comma stripped", `"a","b",`, `["a","b"]`}, + {"both commas stripped", `,"a","b",`, `["a","b"]`}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := StrArrayToJsonStr(c.input) + if got != c.want { + t.Errorf("got %q, want %q", got, c.want) + } + }) + } +} + +// ───────────────────────────────────────────── +// JsonStrToStrArray +// ───────────────────────────────────────────── +func TestJsonStrToStrArray(t *testing.T) { + t.Run("non-empty strips brackets", func(t *testing.T) { + got := JsonStrToStrArray(`["a","b"]`) + if got != `,"a","b",` { + t.Errorf("got %q", got) + } + }) + + t.Run("empty string", func(t *testing.T) { + got := JsonStrToStrArray("") + if got != ",," { + t.Errorf("got %q", got) + } + }) +} + +// ───────────────────────────────────────────── +// StrToInt +// ───────────────────────────────────────────── +func TestStrToInt(t *testing.T) { + t.Run("valid", func(t *testing.T) { + v, err := StrToInt("123") + if v != 123 || err != nil { + t.Errorf("got %d %v", v, err) + } + }) + + t.Run("invalid", func(t *testing.T) { + _, err := StrToInt("abc") + if err == nil { + t.Error("expected error") + } + }) +} + +// ───────────────────────────────────────────── +// JsonToObj +// ───────────────────────────────────────────── +func TestJsonToObj(t *testing.T) { + t.Run("integer preserved as int64", func(t *testing.T) { + v, err := JsonToObj(`{"id":2}`) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + m, ok := v.(map[string]interface{}) + if !ok { + t.Fatalf("expected map, got %T", v) + } + if m["id"] != int64(2) { + t.Errorf("got %v (type %T), want int64(2)", m["id"], m["id"]) + } + }) + + t.Run("decimal preserved as float64 with correct precision", func(t *testing.T) { + v, err := JsonToObj(`{"price":1.20}`) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + m := v.(map[string]interface{}) + price, ok := m["price"].(float64) + if !ok { + t.Fatalf("expected float64, got %T", m["price"]) + } + if price != 1.2 { + t.Errorf("got %v, want 1.2", price) + } + }) + + t.Run("large integer preserved as int64", func(t *testing.T) { + v, err := JsonToObj(`{"num":9999999}`) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + m := v.(map[string]interface{}) + if m["num"] != int64(9999999) { + t.Errorf("got %v (type %T), want int64(9999999)", m["num"], m["num"]) + } + }) + + t.Run("array with mixed types", func(t *testing.T) { + v, err := JsonToObj(`[1, 2.5, "hello", true]`) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + arr := v.([]interface{}) + if len(arr) != 4 { + t.Fatalf("expected 4 elements, got %d", len(arr)) + } + if arr[0] != int64(1) { + t.Errorf("arr[0]: got %v (%T), want int64(1)", arr[0], arr[0]) + } + if arr[1] != float64(2.5) { + t.Errorf("arr[1]: got %v (%T), want float64(2.5)", arr[1], arr[1]) + } + if arr[2] != "hello" { + t.Errorf("arr[2]: got %v, want hello", arr[2]) + } + if arr[3] != true { + t.Errorf("arr[3]: got %v, want true", arr[3]) + } + }) + + t.Run("nested object integers preserved", func(t *testing.T) { + v, err := JsonToObj(`{"order":{"id":5,"num":3,"price":1.20}}`) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + m := v.(map[string]interface{}) + order := m["order"].(map[string]interface{}) + if order["id"] != int64(5) { + t.Errorf("nested id: got %v (%T), want int64(5)", order["id"], order["id"]) + } + if order["num"] != int64(3) { + t.Errorf("nested num: got %v (%T), want int64(3)", order["num"], order["num"]) + } + if order["price"] != float64(1.2) { + t.Errorf("nested price: got %v, want 1.2", order["price"]) + } + }) + + t.Run("invalid JSON returns error", func(t *testing.T) { + _, err := JsonToObj(`not json`) + if err == nil { + t.Error("expected error for invalid JSON") + } + }) + + t.Run("empty object", func(t *testing.T) { + v, err := JsonToObj(`{}`) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + m, ok := v.(map[string]interface{}) + if !ok || len(m) != 0 { + t.Errorf("expected empty map, got %v", v) + } + }) + + t.Run("null value", func(t *testing.T) { + v, err := JsonToObj(`{"x":null}`) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + m := v.(map[string]interface{}) + if m["x"] != nil { + t.Errorf("expected nil, got %v", m["x"]) + } + }) + + t.Run("zero integer", func(t *testing.T) { + v, err := JsonToObj(`{"count":0}`) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + m := v.(map[string]interface{}) + if m["count"] != int64(0) { + t.Errorf("got %v (%T), want int64(0)", m["count"], m["count"]) + } + }) +} + +// ───────────────────────────────────────────── +// ObjToMap 数字类型保留(新行为) +// ───────────────────────────────────────────── +func TestObjToMapNumberPreservation(t *testing.T) { + t.Run("JSON integer -> int64", func(t *testing.T) { + v := ObjToMap(`{"id":2,"num":0}`) + if v["id"] != int64(2) { + t.Errorf("id: got %v (%T), want int64(2)", v["id"], v["id"]) + } + if v["num"] != int64(0) { + t.Errorf("num: got %v (%T), want int64(0)", v["num"], v["num"]) + } + }) + + t.Run("JSON decimal -> float64", func(t *testing.T) { + v := ObjToMap(`{"price":1.20,"rate":0.5}`) + if v["price"] != float64(1.2) { + t.Errorf("price: got %v (%T), want float64(1.2)", v["price"], v["price"]) + } + if v["rate"] != float64(0.5) { + t.Errorf("rate: got %v (%T), want float64(0.5)", v["rate"], v["rate"]) + } + }) + + t.Run("struct with int field -> int64", func(t *testing.T) { + type S struct { + Count int `json:"count"` + Name string `json:"name"` + } + v := ObjToMap(S{Count: 3, Name: "test"}) + if v == nil { + t.Fatal("got nil") + } + if v["count"] != int64(3) { + t.Errorf("count: got %v (%T), want int64(3)", v["count"], v["count"]) + } + }) +} + +// ───────────────────────────────────────────── +// ObjToSlice 数字类型保留(新行为) +// ───────────────────────────────────────────── +func TestObjToSliceNumberPreservation(t *testing.T) { + t.Run("JSON integer array -> int64 elements", func(t *testing.T) { + v := ObjToSlice(`[1, 2, 3]`) + if len(v) != 3 { + t.Fatalf("expected 3, got %d", len(v)) + } + if v[0] != int64(1) { + t.Errorf("v[0]: got %v (%T), want int64(1)", v[0], v[0]) + } + }) + + t.Run("JSON mixed array - integer and float", func(t *testing.T) { + v := ObjToSlice(`[2, 1.5, "hello"]`) + if v[0] != int64(2) { + t.Errorf("v[0]: got %v (%T), want int64(2)", v[0], v[0]) + } + if v[1] != float64(1.5) { + t.Errorf("v[1]: got %v (%T), want float64(1.5)", v[1], v[1]) + } + }) +} + +// ───────────────────────────────────────────── +// Map.JsonToMap 数字类型保留(新行为) +// ───────────────────────────────────────────── +func TestMapJsonToMapNumberPreservation(t *testing.T) { + t.Run("integer -> int64", func(t *testing.T) { + m := Map{} + m.JsonToMap(`{"id":5,"count":0}`) + if m["id"] != int64(5) { + t.Errorf("id: got %v (%T), want int64(5)", m["id"], m["id"]) + } + if m["count"] != int64(0) { + t.Errorf("count: got %v (%T), want int64(0)", m["count"], m["count"]) + } + }) + + t.Run("decimal -> float64", func(t *testing.T) { + m := Map{} + m.JsonToMap(`{"price":1.20}`) + if m["price"] != float64(1.2) { + t.Errorf("price: got %v (%T), want float64(1.2)", m["price"], m["price"]) + } + }) + + t.Run("StrToMap integer -> int64", func(t *testing.T) { + m := StrToMap(`{"total":100,"amount":1.50}`) + if m["total"] != int64(100) { + t.Errorf("total: got %v (%T), want int64(100)", m["total"], m["total"]) + } + if m["amount"] != float64(1.5) { + t.Errorf("amount: got %v (%T), want float64(1.5)", m["amount"], m["amount"]) + } + }) + + t.Run("invalid JSON sets error", func(t *testing.T) { + e := newErr() + m := Map{} + m.JsonToMap(`not json`, e) + if !hasErr(e) { + t.Error("expected error for invalid JSON") + } + }) +} diff --git a/common/slice.go b/common/slice.go index b554a5c..16d14cb 100644 --- a/common/slice.go +++ b/common/slice.go @@ -63,6 +63,11 @@ func (that Slice) GetFloat64(key int, err ...*Error) float64 { return v } +// GetRoundFloat64 获取四舍五入到 precision 位小数的 float64 +func (that Slice) GetRoundFloat64(key int, precision int, err ...*Error) float64 { + return ObjToRoundFloat64((that)[key], precision, err...) +} + func (that Slice) GetSlice(key int, err ...*Error) Slice { v := ObjToSlice((that)[key], err...) return v diff --git a/cover b/cover new file mode 100644 index 0000000..d8d0c28 --- /dev/null +++ b/cover @@ -0,0 +1,344 @@ +mode: set +code.hoteas.com/golang/hotime/common/context_base.go:12.42,14.20 1 0 +code.hoteas.com/golang/hotime/common/context_base.go:14.20,16.3 1 0 +code.hoteas.com/golang/hotime/common/context_base.go:17.2,17.17 1 0 +code.hoteas.com/golang/hotime/common/error.go:15.37,19.2 3 1 +code.hoteas.com/golang/hotime/common/error.go:21.40,26.38 4 1 +code.hoteas.com/golang/hotime/common/error.go:26.38,28.3 1 0 +code.hoteas.com/golang/hotime/common/func.go:29.41,30.19 1 0 +code.hoteas.com/golang/hotime/common/func.go:30.19,32.3 1 0 +code.hoteas.com/golang/hotime/common/func.go:34.2,37.39 3 0 +code.hoteas.com/golang/hotime/common/func.go:41.54,42.18 1 0 +code.hoteas.com/golang/hotime/common/func.go:42.18,44.3 1 0 +code.hoteas.com/golang/hotime/common/func.go:45.2,46.18 2 0 +code.hoteas.com/golang/hotime/common/func.go:46.18,48.3 1 0 +code.hoteas.com/golang/hotime/common/func.go:50.2,50.12 1 0 +code.hoteas.com/golang/hotime/common/func.go:51.9,52.29 1 0 +code.hoteas.com/golang/hotime/common/func.go:53.9,54.32 1 0 +code.hoteas.com/golang/hotime/common/func.go:55.9,56.35 1 0 +code.hoteas.com/golang/hotime/common/func.go:57.9,58.38 1 0 +code.hoteas.com/golang/hotime/common/func.go:59.9,60.41 1 0 +code.hoteas.com/golang/hotime/common/func.go:61.10,62.27 1 0 +code.hoteas.com/golang/hotime/common/func.go:63.10,64.33 1 0 +code.hoteas.com/golang/hotime/common/func.go:65.10,66.36 1 0 +code.hoteas.com/golang/hotime/common/func.go:67.10,68.27 1 0 +code.hoteas.com/golang/hotime/common/func.go:69.10,70.30 1 0 +code.hoteas.com/golang/hotime/common/func.go:72.2,72.40 1 0 +code.hoteas.com/golang/hotime/common/func.go:77.46,78.16 1 0 +code.hoteas.com/golang/hotime/common/func.go:78.16,81.3 2 0 +code.hoteas.com/golang/hotime/common/func.go:82.2,83.19 2 0 +code.hoteas.com/golang/hotime/common/func.go:83.19,85.3 1 0 +code.hoteas.com/golang/hotime/common/func.go:86.2,86.19 1 0 +code.hoteas.com/golang/hotime/common/func.go:86.19,88.3 1 0 +code.hoteas.com/golang/hotime/common/func.go:89.2,89.22 1 0 +code.hoteas.com/golang/hotime/common/func.go:89.22,91.3 1 0 +code.hoteas.com/golang/hotime/common/func.go:92.2,92.31 1 0 +code.hoteas.com/golang/hotime/common/func.go:92.31,93.32 1 0 +code.hoteas.com/golang/hotime/common/func.go:93.32,94.24 1 0 +code.hoteas.com/golang/hotime/common/func.go:94.24,96.5 1 0 +code.hoteas.com/golang/hotime/common/func.go:96.10,98.24 2 0 +code.hoteas.com/golang/hotime/common/func.go:98.24,100.6 1 0 +code.hoteas.com/golang/hotime/common/func.go:101.5,101.26 1 0 +code.hoteas.com/golang/hotime/common/func.go:101.26,103.6 1 0 +code.hoteas.com/golang/hotime/common/func.go:104.5,104.22 1 0 +code.hoteas.com/golang/hotime/common/func.go:109.2,109.26 1 0 +code.hoteas.com/golang/hotime/common/func.go:113.55,118.15 4 1 +code.hoteas.com/golang/hotime/common/func.go:118.15,120.3 1 0 +code.hoteas.com/golang/hotime/common/func.go:121.2,123.17 2 1 +code.hoteas.com/golang/hotime/common/func.go:123.17,125.3 1 0 +code.hoteas.com/golang/hotime/common/func.go:127.2,127.15 1 1 +code.hoteas.com/golang/hotime/common/func.go:127.15,129.3 1 0 +code.hoteas.com/golang/hotime/common/func.go:130.2,130.16 1 1 +code.hoteas.com/golang/hotime/common/func.go:130.16,132.3 1 0 +code.hoteas.com/golang/hotime/common/func.go:133.2,133.13 1 1 +code.hoteas.com/golang/hotime/common/func.go:133.13,135.3 1 0 +code.hoteas.com/golang/hotime/common/func.go:136.2,136.14 1 1 +code.hoteas.com/golang/hotime/common/func.go:136.14,138.3 1 0 +code.hoteas.com/golang/hotime/common/func.go:140.2,140.30 1 1 +code.hoteas.com/golang/hotime/common/func.go:145.40,148.35 3 0 +code.hoteas.com/golang/hotime/common/func.go:148.35,150.3 1 0 +code.hoteas.com/golang/hotime/common/func.go:152.2,154.42 2 0 +code.hoteas.com/golang/hotime/common/func.go:154.42,156.14 2 0 +code.hoteas.com/golang/hotime/common/func.go:156.14,158.22 2 0 +code.hoteas.com/golang/hotime/common/func.go:158.22,161.18 3 0 +code.hoteas.com/golang/hotime/common/func.go:161.18,162.11 1 0 +code.hoteas.com/golang/hotime/common/func.go:165.4,165.13 1 0 +code.hoteas.com/golang/hotime/common/func.go:165.13,167.5 1 0 +code.hoteas.com/golang/hotime/common/func.go:171.2,171.11 1 0 +code.hoteas.com/golang/hotime/common/func.go:175.29,180.2 4 0 +code.hoteas.com/golang/hotime/common/func.go:183.26,185.29 2 0 +code.hoteas.com/golang/hotime/common/func.go:185.29,187.3 1 0 +code.hoteas.com/golang/hotime/common/func.go:188.2,188.22 1 0 +code.hoteas.com/golang/hotime/common/func.go:190.23,193.26 3 0 +code.hoteas.com/golang/hotime/common/func.go:193.26,195.28 2 0 +code.hoteas.com/golang/hotime/common/func.go:195.28,198.9 2 0 +code.hoteas.com/golang/hotime/common/func.go:200.3,200.14 1 0 +code.hoteas.com/golang/hotime/common/func.go:203.2,203.10 1 0 +code.hoteas.com/golang/hotime/common/func.go:208.36,211.18 2 0 +code.hoteas.com/golang/hotime/common/func.go:211.18,213.3 1 0 +code.hoteas.com/golang/hotime/common/func.go:215.2,215.6 1 0 +code.hoteas.com/golang/hotime/common/func.go:215.6,217.19 2 0 +code.hoteas.com/golang/hotime/common/func.go:217.19,218.9 1 0 +code.hoteas.com/golang/hotime/common/func.go:221.2,221.12 1 0 +code.hoteas.com/golang/hotime/common/func.go:260.49,261.37 1 0 +code.hoteas.com/golang/hotime/common/func.go:261.37,263.30 2 0 +code.hoteas.com/golang/hotime/common/func.go:263.30,265.4 1 0 +code.hoteas.com/golang/hotime/common/func.go:267.3,267.16 1 0 +code.hoteas.com/golang/hotime/common/func.go:268.8,268.56 1 0 +code.hoteas.com/golang/hotime/common/func.go:268.56,270.32 2 0 +code.hoteas.com/golang/hotime/common/func.go:270.32,272.4 1 0 +code.hoteas.com/golang/hotime/common/func.go:274.3,274.18 1 0 +code.hoteas.com/golang/hotime/common/func.go:275.8,275.63 1 0 +code.hoteas.com/golang/hotime/common/func.go:275.63,277.30 2 0 +code.hoteas.com/golang/hotime/common/func.go:277.30,279.4 1 0 +code.hoteas.com/golang/hotime/common/func.go:281.3,281.16 1 0 +code.hoteas.com/golang/hotime/common/func.go:283.8,283.48 1 0 +code.hoteas.com/golang/hotime/common/func.go:283.48,285.32 2 0 +code.hoteas.com/golang/hotime/common/func.go:285.32,287.4 1 0 +code.hoteas.com/golang/hotime/common/func.go:289.3,289.18 1 0 +code.hoteas.com/golang/hotime/common/func.go:292.2,292.14 1 0 +code.hoteas.com/golang/hotime/common/func.go:319.38,322.2 2 0 +code.hoteas.com/golang/hotime/common/map.go:15.61,17.19 1 0 +code.hoteas.com/golang/hotime/common/map.go:17.19,19.3 1 0 +code.hoteas.com/golang/hotime/common/map.go:20.2,20.30 1 0 +code.hoteas.com/golang/hotime/common/map.go:24.33,27.2 1 0 +code.hoteas.com/golang/hotime/common/map.go:30.52,35.2 1 0 +code.hoteas.com/golang/hotime/common/map.go:38.36,41.2 1 0 +code.hoteas.com/golang/hotime/common/map.go:44.55,49.2 2 0 +code.hoteas.com/golang/hotime/common/map.go:52.59,56.2 2 0 +code.hoteas.com/golang/hotime/common/map.go:59.63,63.2 2 0 +code.hoteas.com/golang/hotime/common/map.go:66.59,70.2 2 0 +code.hoteas.com/golang/hotime/common/map.go:73.67,77.2 2 0 +code.hoteas.com/golang/hotime/common/map.go:80.63,86.2 2 0 +code.hoteas.com/golang/hotime/common/map.go:88.59,95.2 2 0 +code.hoteas.com/golang/hotime/common/map.go:96.57,103.2 2 0 +code.hoteas.com/golang/hotime/common/map.go:105.63,110.2 2 0 +code.hoteas.com/golang/hotime/common/map.go:112.80,115.27 2 0 +code.hoteas.com/golang/hotime/common/map.go:115.27,118.3 1 0 +code.hoteas.com/golang/hotime/common/map.go:119.2,120.27 2 0 +code.hoteas.com/golang/hotime/common/map.go:120.27,122.9 2 0 +code.hoteas.com/golang/hotime/common/map.go:122.9,124.4 1 0 +code.hoteas.com/golang/hotime/common/map.go:129.55,136.2 2 0 +code.hoteas.com/golang/hotime/common/map.go:138.60,140.30 1 0 +code.hoteas.com/golang/hotime/common/map.go:140.30,142.3 1 0 +code.hoteas.com/golang/hotime/common/map.go:143.2,145.19 2 0 +code.hoteas.com/golang/hotime/common/map.go:145.19,148.3 1 0 +code.hoteas.com/golang/hotime/common/map.go:149.2,149.12 1 0 +code.hoteas.com/golang/hotime/common/map.go:153.44,156.25 2 0 +code.hoteas.com/golang/hotime/common/map.go:156.25,159.22 3 0 +code.hoteas.com/golang/hotime/common/map.go:159.22,160.12 1 0 +code.hoteas.com/golang/hotime/common/map.go:162.3,162.31 1 0 +code.hoteas.com/golang/hotime/common/map.go:163.14,164.33 1 0 +code.hoteas.com/golang/hotime/common/map.go:165.16,166.47 1 0 +code.hoteas.com/golang/hotime/common/map.go:167.18,168.49 1 0 +code.hoteas.com/golang/hotime/common/map.go:169.17,170.48 1 0 +code.hoteas.com/golang/hotime/common/map.go:171.22,172.32 1 0 +code.hoteas.com/golang/hotime/common/map.go:178.39,181.2 1 0 +code.hoteas.com/golang/hotime/common/map.go:183.58,185.31 2 1 +code.hoteas.com/golang/hotime/common/map.go:185.31,187.3 1 0 +code.hoteas.com/golang/hotime/common/map.go:192.83,194.2 1 0 +code.hoteas.com/golang/hotime/common/obj.go:12.40,14.2 1 0 +code.hoteas.com/golang/hotime/common/obj.go:16.42,17.19 1 0 +code.hoteas.com/golang/hotime/common/obj.go:17.19,19.3 1 0 +code.hoteas.com/golang/hotime/common/obj.go:20.2,20.41 1 0 +code.hoteas.com/golang/hotime/common/obj.go:23.50,24.19 1 0 +code.hoteas.com/golang/hotime/common/obj.go:24.19,26.3 1 0 +code.hoteas.com/golang/hotime/common/obj.go:27.2,27.42 1 0 +code.hoteas.com/golang/hotime/common/obj.go:30.46,31.19 1 0 +code.hoteas.com/golang/hotime/common/obj.go:31.19,33.3 1 0 +code.hoteas.com/golang/hotime/common/obj.go:34.2,34.43 1 0 +code.hoteas.com/golang/hotime/common/obj.go:37.50,38.19 1 0 +code.hoteas.com/golang/hotime/common/obj.go:38.19,40.3 1 0 +code.hoteas.com/golang/hotime/common/obj.go:41.2,41.45 1 0 +code.hoteas.com/golang/hotime/common/obj.go:45.55,46.19 1 0 +code.hoteas.com/golang/hotime/common/obj.go:46.19,48.3 1 0 +code.hoteas.com/golang/hotime/common/obj.go:49.2,50.10 2 0 +code.hoteas.com/golang/hotime/common/obj.go:54.33,57.2 1 0 +code.hoteas.com/golang/hotime/common/obj.go:59.42,60.19 1 0 +code.hoteas.com/golang/hotime/common/obj.go:60.19,62.3 1 0 +code.hoteas.com/golang/hotime/common/obj.go:63.2,63.41 1 0 +code.hoteas.com/golang/hotime/common/obj.go:66.46,67.19 1 0 +code.hoteas.com/golang/hotime/common/obj.go:67.19,69.3 1 0 +code.hoteas.com/golang/hotime/common/obj.go:70.2,70.43 1 0 +code.hoteas.com/golang/hotime/common/obj.go:73.49,74.19 1 0 +code.hoteas.com/golang/hotime/common/obj.go:74.19,76.3 1 0 +code.hoteas.com/golang/hotime/common/obj.go:77.2,77.46 1 0 +code.hoteas.com/golang/hotime/common/obj.go:80.38,83.2 1 0 +code.hoteas.com/golang/hotime/common/obj.go:86.51,87.19 1 0 +code.hoteas.com/golang/hotime/common/obj.go:87.19,89.3 1 0 +code.hoteas.com/golang/hotime/common/obj.go:90.2,91.10 2 0 +code.hoteas.com/golang/hotime/common/obj.go:96.47,97.19 1 0 +code.hoteas.com/golang/hotime/common/obj.go:97.19,99.3 1 0 +code.hoteas.com/golang/hotime/common/obj.go:100.2,101.10 2 0 +code.hoteas.com/golang/hotime/common/obj.go:106.71,108.2 1 0 +code.hoteas.com/golang/hotime/common/objtoobj.go:13.49,17.16 3 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:17.16,20.3 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:20.8,21.28 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:22.12,23.11 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:24.31,25.16 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:26.15,28.56 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:28.56,31.5 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:32.11,35.18 3 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:35.18,38.10 3 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:40.4,41.49 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:41.49,44.5 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:47.2,47.17 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:47.17,49.3 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:50.2,50.10 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:53.56,56.30 3 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:56.30,58.3 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:59.2,59.12 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:63.53,67.16 3 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:67.16,70.3 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:70.8,71.28 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:72.14,73.11 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:74.22,75.18 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:76.17,78.26 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:78.26,80.5 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:81.15,83.41 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:84.11,88.18 4 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:88.18,89.10 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:91.4,91.34 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:95.2,95.17 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:95.17,97.3 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:99.2,99.10 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:102.57,106.15 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:106.15,111.47 4 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:111.47,114.4 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:115.3,116.32 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:116.32,117.17 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:117.17,118.13 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:120.4,120.21 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:120.21,122.5 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:124.3,126.21 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:126.21,128.16 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:128.16,130.5 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:131.9,131.28 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:131.28,133.16 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:133.16,135.5 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:136.9,136.28 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:136.28,138.16 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:138.16,140.5 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:141.9,141.27 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:141.27,143.16 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:143.16,145.5 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:146.9,146.27 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:146.27,148.16 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:148.16,150.5 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:155.2,159.18 3 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:159.18,163.3 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:163.8,163.25 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:163.25,167.3 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:167.8,167.25 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:167.25,171.3 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:171.8,171.24 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:171.24,174.3 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:174.8,174.24 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:174.24,176.15 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:176.15,178.4 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:181.2,181.12 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:184.57,188.16 3 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:188.16,190.3 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:190.8,191.28 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:192.12,193.20 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:194.14,195.20 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:196.15,198.17 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:198.17,200.5 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:200.10,202.5 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:203.16,204.11 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:205.16,206.20 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:207.15,210.17 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:210.17,212.5 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:212.10,214.5 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:215.11,216.54 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:220.2,220.19 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:220.19,223.3 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:224.2,224.22 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:224.22,227.3 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:228.2,228.17 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:228.17,230.3 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:232.2,232.10 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:237.77,239.19 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:239.19,241.3 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:242.2,243.32 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:247.57,250.2 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:253.61,256.2 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:259.53,262.2 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:264.53,268.16 3 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:268.16,270.3 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:270.8,271.28 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:272.12,273.18 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:274.14,275.11 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:276.15,278.17 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:278.17,280.5 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:280.10,282.5 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:283.15,286.17 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:286.17,288.5 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:288.10,290.5 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:291.16,292.18 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:293.16,294.18 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:295.11,296.54 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:299.2,299.17 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:299.17,301.3 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:302.2,302.10 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:305.49,308.2 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:310.51,314.16 3 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:314.16,316.3 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:316.8,317.28 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:318.13,319.11 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:320.11,323.32 3 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:323.32,325.5 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:325.10,325.25 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:325.25,327.5 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:330.2,330.17 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:330.17,332.3 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:333.2,333.10 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:336.39,338.16 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:338.16,340.3 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:341.2,341.27 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:342.11,343.26 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:344.13,345.31 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:346.13,347.35 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:348.14,349.20 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:350.14,351.12 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:352.15,353.46 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:354.15,355.55 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:356.12,357.32 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:358.10,360.17 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:360.17,362.4 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:365.2,365.12 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:369.29,373.2 3 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:376.33,378.2 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:381.41,382.16 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:382.16,383.18 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:383.18,385.4 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:386.3,386.25 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:386.25,388.4 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:389.3,389.20 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:390.8,392.3 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:393.2,393.10 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:397.41,398.17 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:398.17,400.3 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:401.2,401.22 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:405.38,408.2 2 1 +code.hoteas.com/golang/hotime/common/slice.go:11.60,12.19 1 0 +code.hoteas.com/golang/hotime/common/slice.go:12.19,14.3 1 0 +code.hoteas.com/golang/hotime/common/slice.go:15.2,15.30 1 0 +code.hoteas.com/golang/hotime/common/slice.go:18.62,23.2 2 0 +code.hoteas.com/golang/hotime/common/slice.go:26.54,29.2 2 0 +code.hoteas.com/golang/hotime/common/slice.go:32.58,36.2 2 0 +code.hoteas.com/golang/hotime/common/slice.go:39.62,43.2 2 0 +code.hoteas.com/golang/hotime/common/slice.go:46.58,50.2 2 0 +code.hoteas.com/golang/hotime/common/slice.go:53.66,57.2 2 0 +code.hoteas.com/golang/hotime/common/slice.go:60.62,64.2 2 0 +code.hoteas.com/golang/hotime/common/slice.go:67.82,69.2 1 0 +code.hoteas.com/golang/hotime/common/slice.go:71.58,74.2 2 0 +code.hoteas.com/golang/hotime/common/slice.go:76.56,83.2 2 0 +code.hoteas.com/golang/hotime/common/slice.go:85.54,89.2 2 1 +code.hoteas.com/golang/hotime/common/slice.go:91.59,93.21 1 0 +code.hoteas.com/golang/hotime/common/slice.go:93.21,95.3 1 0 +code.hoteas.com/golang/hotime/common/slice.go:96.2,97.19 2 0 +code.hoteas.com/golang/hotime/common/slice.go:97.19,99.3 1 0 +code.hoteas.com/golang/hotime/common/slice.go:100.2,100.12 1 0 +code.hoteas.com/golang/hotime/common/slice.go:103.51,105.2 1 0 +code.hoteas.com/golang/hotime/common/slice.go:107.41,109.2 1 0 diff --git a/cover.out b/cover.out new file mode 100644 index 0000000..aa4155b --- /dev/null +++ b/cover.out @@ -0,0 +1,1257 @@ +mode: set +code.hoteas.com/golang/hotime/common/context_base.go:12.42,14.20 1 0 +code.hoteas.com/golang/hotime/common/context_base.go:14.20,16.3 1 0 +code.hoteas.com/golang/hotime/common/context_base.go:17.2,17.17 1 0 +code.hoteas.com/golang/hotime/common/error.go:15.37,19.2 3 1 +code.hoteas.com/golang/hotime/common/error.go:21.40,26.38 4 1 +code.hoteas.com/golang/hotime/common/error.go:26.38,28.3 1 0 +code.hoteas.com/golang/hotime/common/func.go:29.41,30.19 1 0 +code.hoteas.com/golang/hotime/common/func.go:30.19,32.3 1 0 +code.hoteas.com/golang/hotime/common/func.go:34.2,37.39 3 0 +code.hoteas.com/golang/hotime/common/func.go:41.54,42.18 1 0 +code.hoteas.com/golang/hotime/common/func.go:42.18,44.3 1 0 +code.hoteas.com/golang/hotime/common/func.go:45.2,46.18 2 0 +code.hoteas.com/golang/hotime/common/func.go:46.18,48.3 1 0 +code.hoteas.com/golang/hotime/common/func.go:50.2,50.12 1 0 +code.hoteas.com/golang/hotime/common/func.go:51.9,52.29 1 0 +code.hoteas.com/golang/hotime/common/func.go:53.9,54.32 1 0 +code.hoteas.com/golang/hotime/common/func.go:55.9,56.35 1 0 +code.hoteas.com/golang/hotime/common/func.go:57.9,58.38 1 0 +code.hoteas.com/golang/hotime/common/func.go:59.9,60.41 1 0 +code.hoteas.com/golang/hotime/common/func.go:61.10,62.27 1 0 +code.hoteas.com/golang/hotime/common/func.go:63.10,64.33 1 0 +code.hoteas.com/golang/hotime/common/func.go:65.10,66.36 1 0 +code.hoteas.com/golang/hotime/common/func.go:67.10,68.27 1 0 +code.hoteas.com/golang/hotime/common/func.go:69.10,70.30 1 0 +code.hoteas.com/golang/hotime/common/func.go:72.2,72.40 1 0 +code.hoteas.com/golang/hotime/common/func.go:77.46,78.16 1 0 +code.hoteas.com/golang/hotime/common/func.go:78.16,81.3 2 0 +code.hoteas.com/golang/hotime/common/func.go:82.2,83.19 2 0 +code.hoteas.com/golang/hotime/common/func.go:83.19,85.3 1 0 +code.hoteas.com/golang/hotime/common/func.go:86.2,86.19 1 0 +code.hoteas.com/golang/hotime/common/func.go:86.19,88.3 1 0 +code.hoteas.com/golang/hotime/common/func.go:89.2,89.22 1 0 +code.hoteas.com/golang/hotime/common/func.go:89.22,91.3 1 0 +code.hoteas.com/golang/hotime/common/func.go:92.2,92.31 1 0 +code.hoteas.com/golang/hotime/common/func.go:92.31,93.32 1 0 +code.hoteas.com/golang/hotime/common/func.go:93.32,94.24 1 0 +code.hoteas.com/golang/hotime/common/func.go:94.24,96.5 1 0 +code.hoteas.com/golang/hotime/common/func.go:96.10,98.24 2 0 +code.hoteas.com/golang/hotime/common/func.go:98.24,100.6 1 0 +code.hoteas.com/golang/hotime/common/func.go:101.5,101.26 1 0 +code.hoteas.com/golang/hotime/common/func.go:101.26,103.6 1 0 +code.hoteas.com/golang/hotime/common/func.go:104.5,104.22 1 0 +code.hoteas.com/golang/hotime/common/func.go:109.2,109.26 1 0 +code.hoteas.com/golang/hotime/common/func.go:113.55,118.15 4 1 +code.hoteas.com/golang/hotime/common/func.go:118.15,120.3 1 0 +code.hoteas.com/golang/hotime/common/func.go:121.2,123.17 2 1 +code.hoteas.com/golang/hotime/common/func.go:123.17,125.3 1 0 +code.hoteas.com/golang/hotime/common/func.go:127.2,127.15 1 1 +code.hoteas.com/golang/hotime/common/func.go:127.15,129.3 1 0 +code.hoteas.com/golang/hotime/common/func.go:130.2,130.16 1 1 +code.hoteas.com/golang/hotime/common/func.go:130.16,132.3 1 0 +code.hoteas.com/golang/hotime/common/func.go:133.2,133.13 1 1 +code.hoteas.com/golang/hotime/common/func.go:133.13,135.3 1 0 +code.hoteas.com/golang/hotime/common/func.go:136.2,136.14 1 1 +code.hoteas.com/golang/hotime/common/func.go:136.14,138.3 1 0 +code.hoteas.com/golang/hotime/common/func.go:140.2,140.30 1 1 +code.hoteas.com/golang/hotime/common/func.go:145.40,148.35 3 0 +code.hoteas.com/golang/hotime/common/func.go:148.35,150.3 1 0 +code.hoteas.com/golang/hotime/common/func.go:152.2,154.42 2 0 +code.hoteas.com/golang/hotime/common/func.go:154.42,156.14 2 0 +code.hoteas.com/golang/hotime/common/func.go:156.14,158.22 2 0 +code.hoteas.com/golang/hotime/common/func.go:158.22,161.18 3 0 +code.hoteas.com/golang/hotime/common/func.go:161.18,162.11 1 0 +code.hoteas.com/golang/hotime/common/func.go:165.4,165.13 1 0 +code.hoteas.com/golang/hotime/common/func.go:165.13,167.5 1 0 +code.hoteas.com/golang/hotime/common/func.go:171.2,171.11 1 0 +code.hoteas.com/golang/hotime/common/func.go:175.29,180.2 4 0 +code.hoteas.com/golang/hotime/common/func.go:183.26,185.29 2 0 +code.hoteas.com/golang/hotime/common/func.go:185.29,187.3 1 0 +code.hoteas.com/golang/hotime/common/func.go:188.2,188.22 1 0 +code.hoteas.com/golang/hotime/common/func.go:190.23,193.26 3 0 +code.hoteas.com/golang/hotime/common/func.go:193.26,195.28 2 0 +code.hoteas.com/golang/hotime/common/func.go:195.28,198.9 2 0 +code.hoteas.com/golang/hotime/common/func.go:200.3,200.14 1 0 +code.hoteas.com/golang/hotime/common/func.go:203.2,203.10 1 0 +code.hoteas.com/golang/hotime/common/func.go:208.36,211.18 2 0 +code.hoteas.com/golang/hotime/common/func.go:211.18,213.3 1 0 +code.hoteas.com/golang/hotime/common/func.go:215.2,215.6 1 0 +code.hoteas.com/golang/hotime/common/func.go:215.6,217.19 2 0 +code.hoteas.com/golang/hotime/common/func.go:217.19,218.9 1 0 +code.hoteas.com/golang/hotime/common/func.go:221.2,221.12 1 0 +code.hoteas.com/golang/hotime/common/func.go:260.49,261.37 1 0 +code.hoteas.com/golang/hotime/common/func.go:261.37,263.30 2 0 +code.hoteas.com/golang/hotime/common/func.go:263.30,265.4 1 0 +code.hoteas.com/golang/hotime/common/func.go:267.3,267.16 1 0 +code.hoteas.com/golang/hotime/common/func.go:268.8,268.56 1 0 +code.hoteas.com/golang/hotime/common/func.go:268.56,270.32 2 0 +code.hoteas.com/golang/hotime/common/func.go:270.32,272.4 1 0 +code.hoteas.com/golang/hotime/common/func.go:274.3,274.18 1 0 +code.hoteas.com/golang/hotime/common/func.go:275.8,275.63 1 0 +code.hoteas.com/golang/hotime/common/func.go:275.63,277.30 2 0 +code.hoteas.com/golang/hotime/common/func.go:277.30,279.4 1 0 +code.hoteas.com/golang/hotime/common/func.go:281.3,281.16 1 0 +code.hoteas.com/golang/hotime/common/func.go:283.8,283.48 1 0 +code.hoteas.com/golang/hotime/common/func.go:283.48,285.32 2 0 +code.hoteas.com/golang/hotime/common/func.go:285.32,287.4 1 0 +code.hoteas.com/golang/hotime/common/func.go:289.3,289.18 1 0 +code.hoteas.com/golang/hotime/common/func.go:292.2,292.14 1 0 +code.hoteas.com/golang/hotime/common/func.go:319.38,322.2 2 0 +code.hoteas.com/golang/hotime/common/map.go:16.61,18.19 1 0 +code.hoteas.com/golang/hotime/common/map.go:18.19,20.3 1 0 +code.hoteas.com/golang/hotime/common/map.go:21.2,21.30 1 0 +code.hoteas.com/golang/hotime/common/map.go:25.33,28.2 1 0 +code.hoteas.com/golang/hotime/common/map.go:31.52,36.2 1 0 +code.hoteas.com/golang/hotime/common/map.go:39.36,42.2 1 0 +code.hoteas.com/golang/hotime/common/map.go:45.55,50.2 2 0 +code.hoteas.com/golang/hotime/common/map.go:53.59,57.2 2 0 +code.hoteas.com/golang/hotime/common/map.go:60.63,64.2 2 0 +code.hoteas.com/golang/hotime/common/map.go:67.59,71.2 2 0 +code.hoteas.com/golang/hotime/common/map.go:74.67,78.2 2 0 +code.hoteas.com/golang/hotime/common/map.go:81.63,87.2 2 0 +code.hoteas.com/golang/hotime/common/map.go:89.59,96.2 2 0 +code.hoteas.com/golang/hotime/common/map.go:97.57,104.2 2 0 +code.hoteas.com/golang/hotime/common/map.go:106.63,111.2 2 0 +code.hoteas.com/golang/hotime/common/map.go:113.80,116.27 2 0 +code.hoteas.com/golang/hotime/common/map.go:116.27,119.3 1 0 +code.hoteas.com/golang/hotime/common/map.go:120.2,121.27 2 0 +code.hoteas.com/golang/hotime/common/map.go:121.27,123.9 2 0 +code.hoteas.com/golang/hotime/common/map.go:123.9,125.4 1 0 +code.hoteas.com/golang/hotime/common/map.go:130.55,137.2 2 0 +code.hoteas.com/golang/hotime/common/map.go:139.60,141.30 1 0 +code.hoteas.com/golang/hotime/common/map.go:141.30,143.3 1 0 +code.hoteas.com/golang/hotime/common/map.go:144.2,146.19 2 0 +code.hoteas.com/golang/hotime/common/map.go:146.19,149.3 1 0 +code.hoteas.com/golang/hotime/common/map.go:150.2,150.12 1 0 +code.hoteas.com/golang/hotime/common/map.go:154.44,157.25 2 0 +code.hoteas.com/golang/hotime/common/map.go:157.25,160.22 3 0 +code.hoteas.com/golang/hotime/common/map.go:160.22,161.12 1 0 +code.hoteas.com/golang/hotime/common/map.go:163.3,163.31 1 0 +code.hoteas.com/golang/hotime/common/map.go:164.14,165.33 1 0 +code.hoteas.com/golang/hotime/common/map.go:166.16,167.47 1 0 +code.hoteas.com/golang/hotime/common/map.go:168.18,169.49 1 0 +code.hoteas.com/golang/hotime/common/map.go:170.17,171.48 1 0 +code.hoteas.com/golang/hotime/common/map.go:172.22,173.32 1 0 +code.hoteas.com/golang/hotime/common/map.go:179.39,182.2 1 0 +code.hoteas.com/golang/hotime/common/map.go:184.58,188.14 4 1 +code.hoteas.com/golang/hotime/common/map.go:188.14,189.20 1 1 +code.hoteas.com/golang/hotime/common/map.go:189.20,191.4 1 1 +code.hoteas.com/golang/hotime/common/map.go:192.3,192.9 1 1 +code.hoteas.com/golang/hotime/common/map.go:194.2,194.25 1 1 +code.hoteas.com/golang/hotime/common/map.go:194.25,196.3 1 1 +code.hoteas.com/golang/hotime/common/map.go:200.83,202.2 1 0 +code.hoteas.com/golang/hotime/common/obj.go:12.40,14.2 1 0 +code.hoteas.com/golang/hotime/common/obj.go:16.42,17.19 1 0 +code.hoteas.com/golang/hotime/common/obj.go:17.19,19.3 1 0 +code.hoteas.com/golang/hotime/common/obj.go:20.2,20.41 1 0 +code.hoteas.com/golang/hotime/common/obj.go:23.50,24.19 1 0 +code.hoteas.com/golang/hotime/common/obj.go:24.19,26.3 1 0 +code.hoteas.com/golang/hotime/common/obj.go:27.2,27.42 1 0 +code.hoteas.com/golang/hotime/common/obj.go:30.46,31.19 1 0 +code.hoteas.com/golang/hotime/common/obj.go:31.19,33.3 1 0 +code.hoteas.com/golang/hotime/common/obj.go:34.2,34.43 1 0 +code.hoteas.com/golang/hotime/common/obj.go:37.50,38.19 1 0 +code.hoteas.com/golang/hotime/common/obj.go:38.19,40.3 1 0 +code.hoteas.com/golang/hotime/common/obj.go:41.2,41.45 1 0 +code.hoteas.com/golang/hotime/common/obj.go:45.55,46.19 1 0 +code.hoteas.com/golang/hotime/common/obj.go:46.19,48.3 1 0 +code.hoteas.com/golang/hotime/common/obj.go:49.2,50.10 2 0 +code.hoteas.com/golang/hotime/common/obj.go:54.33,57.2 1 0 +code.hoteas.com/golang/hotime/common/obj.go:59.42,60.19 1 0 +code.hoteas.com/golang/hotime/common/obj.go:60.19,62.3 1 0 +code.hoteas.com/golang/hotime/common/obj.go:63.2,63.41 1 0 +code.hoteas.com/golang/hotime/common/obj.go:66.46,67.19 1 0 +code.hoteas.com/golang/hotime/common/obj.go:67.19,69.3 1 0 +code.hoteas.com/golang/hotime/common/obj.go:70.2,70.43 1 0 +code.hoteas.com/golang/hotime/common/obj.go:73.49,74.19 1 0 +code.hoteas.com/golang/hotime/common/obj.go:74.19,76.3 1 0 +code.hoteas.com/golang/hotime/common/obj.go:77.2,77.46 1 0 +code.hoteas.com/golang/hotime/common/obj.go:80.38,83.2 1 0 +code.hoteas.com/golang/hotime/common/obj.go:86.51,87.19 1 0 +code.hoteas.com/golang/hotime/common/obj.go:87.19,89.3 1 0 +code.hoteas.com/golang/hotime/common/obj.go:90.2,91.10 2 0 +code.hoteas.com/golang/hotime/common/obj.go:96.47,97.19 1 0 +code.hoteas.com/golang/hotime/common/obj.go:97.19,99.3 1 0 +code.hoteas.com/golang/hotime/common/obj.go:100.2,101.10 2 0 +code.hoteas.com/golang/hotime/common/obj.go:106.71,108.2 1 0 +code.hoteas.com/golang/hotime/common/objtoobj.go:14.49,18.16 3 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:18.16,21.3 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:21.8,22.28 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:23.12,24.11 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:25.31,26.16 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:27.15,29.17 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:29.17,32.5 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:32.10,32.59 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:32.59,34.5 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:34.10,37.5 2 0 +code.hoteas.com/golang/hotime/common/objtoobj.go:38.11,41.18 3 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:41.18,44.10 3 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:46.4,47.17 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:47.17,50.5 2 0 +code.hoteas.com/golang/hotime/common/objtoobj.go:50.10,50.59 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:50.59,52.5 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:52.10,55.5 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:58.2,58.17 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:58.17,60.3 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:61.2,61.10 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:64.56,67.30 3 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:67.30,69.3 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:70.2,70.12 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:74.53,78.16 3 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:78.16,81.3 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:81.8,82.28 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:83.14,84.11 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:85.22,86.18 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:87.17,89.26 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:89.26,91.5 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:92.15,94.17 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:94.17,96.5 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:96.10,96.50 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:96.50,98.5 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:98.10,100.5 1 0 +code.hoteas.com/golang/hotime/common/objtoobj.go:101.11,104.18 3 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:104.18,105.10 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:107.4,108.17 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:108.17,110.5 1 0 +code.hoteas.com/golang/hotime/common/objtoobj.go:110.10,110.50 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:110.50,112.5 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:112.10,114.5 1 0 +code.hoteas.com/golang/hotime/common/objtoobj.go:118.2,118.17 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:118.17,120.3 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:122.2,122.10 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:125.57,129.15 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:129.15,134.47 4 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:134.47,137.4 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:138.3,139.32 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:139.32,140.17 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:140.17,141.13 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:143.4,143.21 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:143.21,145.5 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:147.3,149.21 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:149.21,151.16 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:151.16,153.5 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:154.9,154.28 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:154.28,156.16 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:156.16,158.5 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:159.9,159.28 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:159.28,161.16 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:161.16,163.5 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:164.9,164.27 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:164.27,166.16 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:166.16,168.5 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:169.9,169.27 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:169.27,171.16 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:171.16,173.5 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:178.2,182.18 3 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:182.18,186.3 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:186.8,186.25 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:186.25,190.3 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:190.8,190.25 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:190.25,194.3 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:194.8,194.24 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:194.24,197.3 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:197.8,197.24 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:197.24,199.15 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:199.15,201.4 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:204.2,204.12 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:207.57,211.16 3 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:211.16,213.3 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:213.8,214.28 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:215.12,216.20 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:217.14,218.20 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:219.15,221.17 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:221.17,223.5 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:223.10,225.5 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:226.16,227.11 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:228.16,230.81 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:231.15,234.17 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:234.17,236.5 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:236.10,238.5 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:239.11,240.54 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:244.2,244.19 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:244.19,247.3 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:248.2,248.22 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:248.22,251.3 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:252.2,252.17 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:252.17,254.3 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:256.2,256.10 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:261.77,263.19 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:263.19,265.3 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:266.2,267.32 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:271.57,274.2 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:277.61,280.2 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:283.53,286.2 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:288.53,292.16 3 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:292.16,294.3 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:294.8,295.28 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:296.12,297.18 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:298.14,299.11 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:300.15,302.17 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:302.17,304.57 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:304.57,306.6 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:306.11,308.6 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:309.10,311.5 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:312.15,315.17 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:315.17,317.65 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:317.65,319.6 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:319.11,321.6 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:322.10,324.5 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:325.16,326.18 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:327.16,328.18 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:329.11,330.54 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:333.2,333.17 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:333.17,335.3 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:336.2,336.10 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:339.49,342.2 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:344.51,348.16 3 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:348.16,350.3 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:350.8,351.28 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:352.13,353.11 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:354.11,357.32 3 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:357.32,359.5 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:359.10,359.25 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:359.25,361.5 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:364.2,364.17 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:364.17,366.3 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:367.2,367.10 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:370.39,372.16 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:372.16,374.3 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:375.2,375.27 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:376.11,377.26 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:378.13,379.31 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:380.13,381.35 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:382.14,383.20 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:384.14,385.12 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:386.15,387.46 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:388.15,389.55 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:390.12,391.32 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:392.10,394.17 2 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:394.17,396.4 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:399.2,399.12 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:407.53,411.44 4 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:411.44,413.3 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:414.2,414.36 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:418.48,419.25 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:420.19,421.40 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:421.40,423.4 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:424.3,424.42 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:424.42,426.4 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:427.3,427.21 1 0 +code.hoteas.com/golang/hotime/common/objtoobj.go:428.30,429.28 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:429.28,431.4 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:432.3,432.13 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:433.21,434.28 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:434.28,436.4 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:437.3,437.13 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:438.10,439.13 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:444.29,448.2 3 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:451.33,453.2 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:456.41,457.16 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:457.16,458.18 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:458.18,460.4 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:461.3,461.25 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:461.25,463.4 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:464.3,464.20 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:465.8,467.3 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:468.2,468.10 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:472.41,473.17 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:473.17,475.3 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:476.2,476.22 1 1 +code.hoteas.com/golang/hotime/common/objtoobj.go:480.38,483.2 2 1 +code.hoteas.com/golang/hotime/common/slice.go:11.60,12.19 1 0 +code.hoteas.com/golang/hotime/common/slice.go:12.19,14.3 1 0 +code.hoteas.com/golang/hotime/common/slice.go:15.2,15.30 1 0 +code.hoteas.com/golang/hotime/common/slice.go:18.62,23.2 2 0 +code.hoteas.com/golang/hotime/common/slice.go:26.54,29.2 2 0 +code.hoteas.com/golang/hotime/common/slice.go:32.58,36.2 2 0 +code.hoteas.com/golang/hotime/common/slice.go:39.62,43.2 2 0 +code.hoteas.com/golang/hotime/common/slice.go:46.58,50.2 2 0 +code.hoteas.com/golang/hotime/common/slice.go:53.66,57.2 2 0 +code.hoteas.com/golang/hotime/common/slice.go:60.62,64.2 2 0 +code.hoteas.com/golang/hotime/common/slice.go:67.82,69.2 1 0 +code.hoteas.com/golang/hotime/common/slice.go:71.58,74.2 2 0 +code.hoteas.com/golang/hotime/common/slice.go:76.56,83.2 2 0 +code.hoteas.com/golang/hotime/common/slice.go:85.54,89.2 2 1 +code.hoteas.com/golang/hotime/common/slice.go:91.59,93.21 1 0 +code.hoteas.com/golang/hotime/common/slice.go:93.21,95.3 1 0 +code.hoteas.com/golang/hotime/common/slice.go:96.2,97.19 2 0 +code.hoteas.com/golang/hotime/common/slice.go:97.19,99.3 1 0 +code.hoteas.com/golang/hotime/common/slice.go:100.2,100.12 1 0 +code.hoteas.com/golang/hotime/common/slice.go:103.51,105.2 1 0 +code.hoteas.com/golang/hotime/common/slice.go:107.41,109.2 1 0 +code.hoteas.com/golang/hotime/db/aggregate.go:8.66,10.18 2 0 +code.hoteas.com/golang/hotime/db/aggregate.go:10.18,14.3 3 0 +code.hoteas.com/golang/hotime/db/aggregate.go:14.8,17.3 2 0 +code.hoteas.com/golang/hotime/db/aggregate.go:19.2,20.20 2 0 +code.hoteas.com/golang/hotime/db/aggregate.go:20.20,22.3 1 0 +code.hoteas.com/golang/hotime/db/aggregate.go:24.2,26.14 3 0 +code.hoteas.com/golang/hotime/db/aggregate.go:30.83,32.18 2 0 +code.hoteas.com/golang/hotime/db/aggregate.go:32.18,36.3 3 0 +code.hoteas.com/golang/hotime/db/aggregate.go:36.8,39.3 2 0 +code.hoteas.com/golang/hotime/db/aggregate.go:41.2,42.20 2 0 +code.hoteas.com/golang/hotime/db/aggregate.go:42.20,44.3 1 0 +code.hoteas.com/golang/hotime/db/aggregate.go:46.2,48.12 3 0 +code.hoteas.com/golang/hotime/db/aggregate.go:52.83,54.18 2 0 +code.hoteas.com/golang/hotime/db/aggregate.go:54.18,58.3 3 0 +code.hoteas.com/golang/hotime/db/aggregate.go:58.8,61.3 2 0 +code.hoteas.com/golang/hotime/db/aggregate.go:63.2,64.20 2 0 +code.hoteas.com/golang/hotime/db/aggregate.go:64.20,66.3 1 0 +code.hoteas.com/golang/hotime/db/aggregate.go:68.2,70.12 3 0 +code.hoteas.com/golang/hotime/db/aggregate.go:74.83,76.18 2 0 +code.hoteas.com/golang/hotime/db/aggregate.go:76.18,80.3 3 0 +code.hoteas.com/golang/hotime/db/aggregate.go:80.8,83.3 2 0 +code.hoteas.com/golang/hotime/db/aggregate.go:85.2,86.20 2 0 +code.hoteas.com/golang/hotime/db/aggregate.go:86.20,88.3 1 0 +code.hoteas.com/golang/hotime/db/aggregate.go:90.2,92.12 3 0 +code.hoteas.com/golang/hotime/db/aggregate.go:96.83,98.18 2 0 +code.hoteas.com/golang/hotime/db/aggregate.go:98.18,102.3 3 0 +code.hoteas.com/golang/hotime/db/aggregate.go:102.8,105.3 2 0 +code.hoteas.com/golang/hotime/db/aggregate.go:107.2,108.20 2 0 +code.hoteas.com/golang/hotime/db/aggregate.go:108.20,110.3 1 0 +code.hoteas.com/golang/hotime/db/aggregate.go:112.2,114.12 3 0 +code.hoteas.com/golang/hotime/db/backup.go:13.68,18.28 4 0 +code.hoteas.com/golang/hotime/db/backup.go:18.28,20.3 1 0 +code.hoteas.com/golang/hotime/db/backup.go:22.2,22.28 1 0 +code.hoteas.com/golang/hotime/db/backup.go:22.28,25.3 2 0 +code.hoteas.com/golang/hotime/db/backup.go:27.2,27.30 1 0 +code.hoteas.com/golang/hotime/db/backup.go:31.51,33.20 2 0 +code.hoteas.com/golang/hotime/db/backup.go:33.20,35.3 1 0 +code.hoteas.com/golang/hotime/db/backup.go:37.2,37.56 1 0 +code.hoteas.com/golang/hotime/db/backup.go:41.51,47.18 4 0 +code.hoteas.com/golang/hotime/db/backup.go:47.18,49.3 1 0 +code.hoteas.com/golang/hotime/db/backup.go:51.2,55.25 4 0 +code.hoteas.com/golang/hotime/db/backup.go:55.25,56.30 1 0 +code.hoteas.com/golang/hotime/db/backup.go:56.30,58.4 1 0 +code.hoteas.com/golang/hotime/db/backup.go:58.9,60.4 1 0 +code.hoteas.com/golang/hotime/db/backup.go:61.3,62.16 2 0 +code.hoteas.com/golang/hotime/db/backup.go:65.2,67.31 2 0 +code.hoteas.com/golang/hotime/db/backup.go:67.31,68.31 1 0 +code.hoteas.com/golang/hotime/db/backup.go:68.31,69.14 1 0 +code.hoteas.com/golang/hotime/db/backup.go:69.14,71.5 1 0 +code.hoteas.com/golang/hotime/db/backup.go:73.4,74.30 2 0 +code.hoteas.com/golang/hotime/db/backup.go:74.30,76.5 1 0 +code.hoteas.com/golang/hotime/db/backup.go:78.4,78.21 1 0 +code.hoteas.com/golang/hotime/db/backup.go:78.21,80.5 1 0 +code.hoteas.com/golang/hotime/db/backup.go:80.10,82.5 1 0 +code.hoteas.com/golang/hotime/db/backup.go:85.3,85.21 1 0 +code.hoteas.com/golang/hotime/db/backup.go:85.21,87.4 1 0 +code.hoteas.com/golang/hotime/db/backup.go:87.9,89.4 1 0 +code.hoteas.com/golang/hotime/db/backup.go:92.2,92.12 1 0 +code.hoteas.com/golang/hotime/db/builder.go:20.60,22.2 1 0 +code.hoteas.com/golang/hotime/db/builder.go:25.57,28.24 2 0 +code.hoteas.com/golang/hotime/db/builder.go:28.24,30.18 1 0 +code.hoteas.com/golang/hotime/db/builder.go:30.18,32.4 1 0 +code.hoteas.com/golang/hotime/db/builder.go:32.9,34.4 1 0 +code.hoteas.com/golang/hotime/db/builder.go:35.8,37.18 1 0 +code.hoteas.com/golang/hotime/db/builder.go:37.18,39.4 1 0 +code.hoteas.com/golang/hotime/db/builder.go:39.9,41.4 1 0 +code.hoteas.com/golang/hotime/db/builder.go:43.2,43.47 1 0 +code.hoteas.com/golang/hotime/db/builder.go:47.42,49.24 1 0 +code.hoteas.com/golang/hotime/db/builder.go:49.24,51.3 1 0 +code.hoteas.com/golang/hotime/db/builder.go:52.2,52.52 1 0 +code.hoteas.com/golang/hotime/db/builder.go:56.71,60.2 3 0 +code.hoteas.com/golang/hotime/db/builder.go:63.62,66.24 2 0 +code.hoteas.com/golang/hotime/db/builder.go:66.24,68.18 1 0 +code.hoteas.com/golang/hotime/db/builder.go:68.18,70.4 1 0 +code.hoteas.com/golang/hotime/db/builder.go:70.9,72.4 1 0 +code.hoteas.com/golang/hotime/db/builder.go:73.8,75.18 1 0 +code.hoteas.com/golang/hotime/db/builder.go:75.18,77.4 1 0 +code.hoteas.com/golang/hotime/db/builder.go:77.9,79.4 1 0 +code.hoteas.com/golang/hotime/db/builder.go:82.2,82.20 1 0 +code.hoteas.com/golang/hotime/db/builder.go:82.20,84.3 1 0 +code.hoteas.com/golang/hotime/db/builder.go:85.2,85.50 1 0 +code.hoteas.com/golang/hotime/db/builder.go:89.62,91.14 2 0 +code.hoteas.com/golang/hotime/db/builder.go:91.14,93.3 1 0 +code.hoteas.com/golang/hotime/db/builder.go:94.2,95.14 2 0 +code.hoteas.com/golang/hotime/db/builder.go:95.14,97.3 1 0 +code.hoteas.com/golang/hotime/db/builder.go:98.2,98.13 1 0 +code.hoteas.com/golang/hotime/db/builder.go:98.13,99.28 1 0 +code.hoteas.com/golang/hotime/db/builder.go:99.28,102.4 2 0 +code.hoteas.com/golang/hotime/db/builder.go:105.2,105.59 1 0 +code.hoteas.com/golang/hotime/db/builder.go:109.45,111.2 1 0 +code.hoteas.com/golang/hotime/db/builder.go:114.79,117.2 2 0 +code.hoteas.com/golang/hotime/db/builder.go:120.80,123.2 2 0 +code.hoteas.com/golang/hotime/db/builder.go:126.80,129.2 2 0 +code.hoteas.com/golang/hotime/db/builder.go:132.79,135.2 2 0 +code.hoteas.com/golang/hotime/db/builder.go:138.71,140.14 2 0 +code.hoteas.com/golang/hotime/db/builder.go:140.14,142.3 1 0 +code.hoteas.com/golang/hotime/db/builder.go:143.2,144.14 2 0 +code.hoteas.com/golang/hotime/db/builder.go:144.14,146.3 1 0 +code.hoteas.com/golang/hotime/db/builder.go:147.2,147.13 1 0 +code.hoteas.com/golang/hotime/db/builder.go:147.13,148.28 1 0 +code.hoteas.com/golang/hotime/db/builder.go:148.28,151.4 2 0 +code.hoteas.com/golang/hotime/db/builder.go:154.2,154.22 1 0 +code.hoteas.com/golang/hotime/db/builder.go:154.22,156.3 1 0 +code.hoteas.com/golang/hotime/db/builder.go:157.2,157.17 1 0 +code.hoteas.com/golang/hotime/db/builder.go:157.17,159.3 1 0 +code.hoteas.com/golang/hotime/db/builder.go:161.2,162.13 2 0 +code.hoteas.com/golang/hotime/db/builder.go:166.70,168.14 2 0 +code.hoteas.com/golang/hotime/db/builder.go:168.14,170.3 1 0 +code.hoteas.com/golang/hotime/db/builder.go:171.2,172.14 2 0 +code.hoteas.com/golang/hotime/db/builder.go:172.14,174.3 1 0 +code.hoteas.com/golang/hotime/db/builder.go:175.2,175.13 1 0 +code.hoteas.com/golang/hotime/db/builder.go:175.13,177.28 2 0 +code.hoteas.com/golang/hotime/db/builder.go:177.28,180.4 2 0 +code.hoteas.com/golang/hotime/db/builder.go:183.2,183.18 1 0 +code.hoteas.com/golang/hotime/db/builder.go:183.18,185.3 1 0 +code.hoteas.com/golang/hotime/db/builder.go:187.2,187.27 1 0 +code.hoteas.com/golang/hotime/db/builder.go:187.27,191.3 3 0 +code.hoteas.com/golang/hotime/db/builder.go:192.2,195.13 3 0 +code.hoteas.com/golang/hotime/db/builder.go:199.69,201.14 2 0 +code.hoteas.com/golang/hotime/db/builder.go:201.14,203.3 1 0 +code.hoteas.com/golang/hotime/db/builder.go:204.2,205.14 2 0 +code.hoteas.com/golang/hotime/db/builder.go:205.14,207.3 1 0 +code.hoteas.com/golang/hotime/db/builder.go:208.2,208.13 1 0 +code.hoteas.com/golang/hotime/db/builder.go:208.13,210.28 2 0 +code.hoteas.com/golang/hotime/db/builder.go:210.28,213.4 2 0 +code.hoteas.com/golang/hotime/db/builder.go:215.2,215.18 1 0 +code.hoteas.com/golang/hotime/db/builder.go:215.18,217.3 1 0 +code.hoteas.com/golang/hotime/db/builder.go:219.2,219.27 1 0 +code.hoteas.com/golang/hotime/db/builder.go:219.27,223.3 3 0 +code.hoteas.com/golang/hotime/db/builder.go:224.2,227.13 3 0 +code.hoteas.com/golang/hotime/db/builder.go:231.72,233.14 2 0 +code.hoteas.com/golang/hotime/db/builder.go:233.14,235.3 1 0 +code.hoteas.com/golang/hotime/db/builder.go:236.2,237.14 2 0 +code.hoteas.com/golang/hotime/db/builder.go:237.14,239.3 1 0 +code.hoteas.com/golang/hotime/db/builder.go:240.2,240.13 1 0 +code.hoteas.com/golang/hotime/db/builder.go:240.13,242.28 2 0 +code.hoteas.com/golang/hotime/db/builder.go:242.28,245.4 2 0 +code.hoteas.com/golang/hotime/db/builder.go:248.2,248.18 1 0 +code.hoteas.com/golang/hotime/db/builder.go:248.18,250.3 1 0 +code.hoteas.com/golang/hotime/db/builder.go:252.2,252.27 1 0 +code.hoteas.com/golang/hotime/db/builder.go:252.27,256.3 3 0 +code.hoteas.com/golang/hotime/db/builder.go:257.2,260.13 3 0 +code.hoteas.com/golang/hotime/db/builder.go:264.66,267.2 2 0 +code.hoteas.com/golang/hotime/db/builder.go:270.72,273.2 2 0 +code.hoteas.com/golang/hotime/db/builder.go:276.72,279.2 2 0 +code.hoteas.com/golang/hotime/db/builder.go:282.72,285.2 2 0 +code.hoteas.com/golang/hotime/db/builder.go:288.73,290.14 2 0 +code.hoteas.com/golang/hotime/db/builder.go:290.14,292.3 1 0 +code.hoteas.com/golang/hotime/db/builder.go:293.2,294.14 2 0 +code.hoteas.com/golang/hotime/db/builder.go:294.14,296.3 1 0 +code.hoteas.com/golang/hotime/db/builder.go:297.2,297.13 1 0 +code.hoteas.com/golang/hotime/db/builder.go:297.13,299.28 2 0 +code.hoteas.com/golang/hotime/db/builder.go:299.28,302.4 2 0 +code.hoteas.com/golang/hotime/db/builder.go:304.2,305.13 2 0 +code.hoteas.com/golang/hotime/db/builder.go:309.66,312.2 2 0 +code.hoteas.com/golang/hotime/db/crud.go:14.57,15.14 1 0 +code.hoteas.com/golang/hotime/db/crud.go:15.14,17.3 1 0 +code.hoteas.com/golang/hotime/db/crud.go:18.2,18.17 1 0 +code.hoteas.com/golang/hotime/db/crud.go:18.17,20.3 1 0 +code.hoteas.com/golang/hotime/db/crud.go:21.2,26.13 5 0 +code.hoteas.com/golang/hotime/db/crud.go:30.73,36.18 5 0 +code.hoteas.com/golang/hotime/db/crud.go:36.18,38.3 1 0 +code.hoteas.com/golang/hotime/db/crud.go:41.2,41.17 1 0 +code.hoteas.com/golang/hotime/db/crud.go:42.9,44.44 1 0 +code.hoteas.com/golang/hotime/db/crud.go:45.9,47.51 1 0 +code.hoteas.com/golang/hotime/db/crud.go:47.51,52.4 3 0 +code.hoteas.com/golang/hotime/db/crud.go:52.9,55.4 1 0 +code.hoteas.com/golang/hotime/db/crud.go:56.9,58.51 1 0 +code.hoteas.com/golang/hotime/db/crud.go:58.51,62.4 3 0 +code.hoteas.com/golang/hotime/db/crud.go:62.9,65.4 1 0 +code.hoteas.com/golang/hotime/db/crud.go:66.9,70.15 3 0 +code.hoteas.com/golang/hotime/db/crud.go:73.2,73.34 1 0 +code.hoteas.com/golang/hotime/db/crud.go:77.69,84.18 6 0 +code.hoteas.com/golang/hotime/db/crud.go:84.18,88.3 3 0 +code.hoteas.com/golang/hotime/db/crud.go:90.2,92.17 2 0 +code.hoteas.com/golang/hotime/db/crud.go:92.17,93.61 1 0 +code.hoteas.com/golang/hotime/db/crud.go:93.61,96.23 2 0 +code.hoteas.com/golang/hotime/db/crud.go:96.23,98.5 1 0 +code.hoteas.com/golang/hotime/db/crud.go:99.4,99.27 1 0 +code.hoteas.com/golang/hotime/db/crud.go:100.9,102.35 2 0 +code.hoteas.com/golang/hotime/db/crud.go:102.35,104.64 2 0 +code.hoteas.com/golang/hotime/db/crud.go:104.64,107.6 1 0 +code.hoteas.com/golang/hotime/db/crud.go:107.11,110.6 1 0 +code.hoteas.com/golang/hotime/db/crud.go:112.5,112.25 1 0 +code.hoteas.com/golang/hotime/db/crud.go:112.25,114.6 1 0 +code.hoteas.com/golang/hotime/db/crud.go:117.8,119.3 1 0 +code.hoteas.com/golang/hotime/db/crud.go:122.2,124.10 2 0 +code.hoteas.com/golang/hotime/db/crud.go:124.10,126.3 1 0 +code.hoteas.com/golang/hotime/db/crud.go:128.2,128.17 1 0 +code.hoteas.com/golang/hotime/db/crud.go:128.17,130.3 1 0 +code.hoteas.com/golang/hotime/db/crud.go:132.2,138.50 5 0 +code.hoteas.com/golang/hotime/db/crud.go:138.50,141.48 2 0 +code.hoteas.com/golang/hotime/db/crud.go:141.48,143.4 1 0 +code.hoteas.com/golang/hotime/db/crud.go:147.2,149.16 2 0 +code.hoteas.com/golang/hotime/db/crud.go:149.16,151.3 1 0 +code.hoteas.com/golang/hotime/db/crud.go:154.2,154.50 1 0 +code.hoteas.com/golang/hotime/db/crud.go:154.50,156.3 1 0 +code.hoteas.com/golang/hotime/db/crud.go:158.2,158.12 1 0 +code.hoteas.com/golang/hotime/db/crud.go:162.62,168.63 5 0 +code.hoteas.com/golang/hotime/db/crud.go:168.63,170.31 2 0 +code.hoteas.com/golang/hotime/db/crud.go:170.31,172.4 1 0 +code.hoteas.com/golang/hotime/db/crud.go:175.2,175.134 1 0 +code.hoteas.com/golang/hotime/db/crud.go:175.134,177.24 2 0 +code.hoteas.com/golang/hotime/db/crud.go:177.24,179.26 2 0 +code.hoteas.com/golang/hotime/db/crud.go:179.26,182.5 2 0 +code.hoteas.com/golang/hotime/db/crud.go:186.2,188.27 2 0 +code.hoteas.com/golang/hotime/db/crud.go:188.27,190.26 2 0 +code.hoteas.com/golang/hotime/db/crud.go:191.14,192.11 1 0 +code.hoteas.com/golang/hotime/db/crud.go:192.11,199.5 4 0 +code.hoteas.com/golang/hotime/db/crud.go:200.14,201.11 1 0 +code.hoteas.com/golang/hotime/db/crud.go:201.11,206.5 4 0 +code.hoteas.com/golang/hotime/db/crud.go:208.3,208.26 1 0 +code.hoteas.com/golang/hotime/db/crud.go:209.15,210.11 1 0 +code.hoteas.com/golang/hotime/db/crud.go:210.11,215.5 4 0 +code.hoteas.com/golang/hotime/db/crud.go:216.15,217.11 1 0 +code.hoteas.com/golang/hotime/db/crud.go:217.11,222.5 4 0 +code.hoteas.com/golang/hotime/db/crud.go:226.2,226.14 1 0 +code.hoteas.com/golang/hotime/db/crud.go:230.64,231.18 1 0 +code.hoteas.com/golang/hotime/db/crud.go:231.18,234.3 1 0 +code.hoteas.com/golang/hotime/db/crud.go:234.8,234.25 1 0 +code.hoteas.com/golang/hotime/db/crud.go:234.25,236.3 1 0 +code.hoteas.com/golang/hotime/db/crud.go:236.8,236.25 1 0 +code.hoteas.com/golang/hotime/db/crud.go:236.25,240.3 3 0 +code.hoteas.com/golang/hotime/db/crud.go:240.8,240.25 1 0 +code.hoteas.com/golang/hotime/db/crud.go:240.25,244.3 3 0 +code.hoteas.com/golang/hotime/db/crud.go:246.2,247.20 2 0 +code.hoteas.com/golang/hotime/db/crud.go:247.20,249.3 1 0 +code.hoteas.com/golang/hotime/db/crud.go:250.2,250.16 1 0 +code.hoteas.com/golang/hotime/db/crud.go:254.79,263.25 7 0 +code.hoteas.com/golang/hotime/db/crud.go:263.25,267.38 3 0 +code.hoteas.com/golang/hotime/db/crud.go:267.38,270.22 3 0 +code.hoteas.com/golang/hotime/db/crud.go:270.22,273.5 2 0 +code.hoteas.com/golang/hotime/db/crud.go:273.10,276.5 2 0 +code.hoteas.com/golang/hotime/db/crud.go:277.9,279.22 2 0 +code.hoteas.com/golang/hotime/db/crud.go:279.22,282.5 2 0 +code.hoteas.com/golang/hotime/db/crud.go:282.10,285.5 2 0 +code.hoteas.com/golang/hotime/db/crud.go:289.2,294.41 4 0 +code.hoteas.com/golang/hotime/db/crud.go:294.41,298.3 3 0 +code.hoteas.com/golang/hotime/db/crud.go:301.2,301.13 1 0 +code.hoteas.com/golang/hotime/db/crud.go:301.13,302.51 1 0 +code.hoteas.com/golang/hotime/db/crud.go:302.51,304.4 1 0 +code.hoteas.com/golang/hotime/db/crud.go:307.2,307.11 1 0 +code.hoteas.com/golang/hotime/db/crud.go:322.67,323.24 1 0 +code.hoteas.com/golang/hotime/db/crud.go:323.24,325.3 1 0 +code.hoteas.com/golang/hotime/db/crud.go:328.2,331.29 3 0 +code.hoteas.com/golang/hotime/db/crud.go:331.29,333.38 2 0 +code.hoteas.com/golang/hotime/db/crud.go:333.38,336.4 2 0 +code.hoteas.com/golang/hotime/db/crud.go:337.3,337.37 1 0 +code.hoteas.com/golang/hotime/db/crud.go:341.2,347.30 4 0 +code.hoteas.com/golang/hotime/db/crud.go:347.30,349.3 1 0 +code.hoteas.com/golang/hotime/db/crud.go:350.2,356.32 4 0 +code.hoteas.com/golang/hotime/db/crud.go:356.32,358.31 2 0 +code.hoteas.com/golang/hotime/db/crud.go:358.31,361.38 2 0 +code.hoteas.com/golang/hotime/db/crud.go:361.38,364.5 1 0 +code.hoteas.com/golang/hotime/db/crud.go:364.10,364.58 1 0 +code.hoteas.com/golang/hotime/db/crud.go:364.58,367.5 1 0 +code.hoteas.com/golang/hotime/db/crud.go:367.10,367.39 1 0 +code.hoteas.com/golang/hotime/db/crud.go:367.39,371.5 2 0 +code.hoteas.com/golang/hotime/db/crud.go:371.10,374.5 1 0 +code.hoteas.com/golang/hotime/db/crud.go:376.3,376.68 1 0 +code.hoteas.com/golang/hotime/db/crud.go:379.2,384.41 4 0 +code.hoteas.com/golang/hotime/db/crud.go:384.41,386.3 1 0 +code.hoteas.com/golang/hotime/db/crud.go:389.2,389.17 1 0 +code.hoteas.com/golang/hotime/db/crud.go:389.17,390.51 1 0 +code.hoteas.com/golang/hotime/db/crud.go:390.51,392.4 1 0 +code.hoteas.com/golang/hotime/db/crud.go:395.2,395.15 1 0 +code.hoteas.com/golang/hotime/db/crud.go:412.108,413.44 1 0 +code.hoteas.com/golang/hotime/db/crud.go:413.44,415.3 1 0 +code.hoteas.com/golang/hotime/db/crud.go:418.2,419.32 2 0 +code.hoteas.com/golang/hotime/db/crud.go:419.32,421.3 1 0 +code.hoteas.com/golang/hotime/db/crud.go:424.2,425.28 2 0 +code.hoteas.com/golang/hotime/db/crud.go:425.28,427.48 1 0 +code.hoteas.com/golang/hotime/db/crud.go:427.48,429.30 2 0 +code.hoteas.com/golang/hotime/db/crud.go:429.30,431.5 1 0 +code.hoteas.com/golang/hotime/db/crud.go:432.9,434.38 2 0 +code.hoteas.com/golang/hotime/db/crud.go:434.38,436.5 1 0 +code.hoteas.com/golang/hotime/db/crud.go:441.2,445.25 4 0 +code.hoteas.com/golang/hotime/db/crud.go:445.25,446.38 1 0 +code.hoteas.com/golang/hotime/db/crud.go:446.38,450.4 3 0 +code.hoteas.com/golang/hotime/db/crud.go:450.9,453.4 2 0 +code.hoteas.com/golang/hotime/db/crud.go:457.2,457.32 1 0 +code.hoteas.com/golang/hotime/db/crud.go:457.32,459.36 2 0 +code.hoteas.com/golang/hotime/db/crud.go:459.36,461.4 1 0 +code.hoteas.com/golang/hotime/db/crud.go:462.3,462.31 1 0 +code.hoteas.com/golang/hotime/db/crud.go:462.31,463.26 1 0 +code.hoteas.com/golang/hotime/db/crud.go:463.26,465.5 1 0 +code.hoteas.com/golang/hotime/db/crud.go:470.2,472.18 3 0 +code.hoteas.com/golang/hotime/db/crud.go:472.18,474.3 1 0 +code.hoteas.com/golang/hotime/db/crud.go:476.2,476.16 1 0 +code.hoteas.com/golang/hotime/db/crud.go:477.32,478.95 1 0 +code.hoteas.com/golang/hotime/db/crud.go:479.27,480.93 1 0 +code.hoteas.com/golang/hotime/db/crud.go:481.10,482.92 1 0 +code.hoteas.com/golang/hotime/db/crud.go:485.2,488.41 3 0 +code.hoteas.com/golang/hotime/db/crud.go:488.41,490.3 1 0 +code.hoteas.com/golang/hotime/db/crud.go:493.2,493.15 1 0 +code.hoteas.com/golang/hotime/db/crud.go:493.15,494.51 1 0 +code.hoteas.com/golang/hotime/db/crud.go:494.51,496.4 1 0 +code.hoteas.com/golang/hotime/db/crud.go:499.2,499.13 1 0 +code.hoteas.com/golang/hotime/db/crud.go:503.153,510.30 4 0 +code.hoteas.com/golang/hotime/db/crud.go:510.30,512.36 2 0 +code.hoteas.com/golang/hotime/db/crud.go:512.36,514.4 1 0 +code.hoteas.com/golang/hotime/db/crud.go:514.9,516.4 1 0 +code.hoteas.com/golang/hotime/db/crud.go:519.2,520.36 2 0 +code.hoteas.com/golang/hotime/db/crud.go:520.36,522.36 2 0 +code.hoteas.com/golang/hotime/db/crud.go:522.36,524.4 1 0 +code.hoteas.com/golang/hotime/db/crud.go:524.9,526.4 1 0 +code.hoteas.com/golang/hotime/db/crud.go:529.2,531.65 1 0 +code.hoteas.com/golang/hotime/db/crud.go:535.156,544.30 6 0 +code.hoteas.com/golang/hotime/db/crud.go:544.30,546.36 2 0 +code.hoteas.com/golang/hotime/db/crud.go:546.36,548.4 1 0 +code.hoteas.com/golang/hotime/db/crud.go:548.9,551.4 2 0 +code.hoteas.com/golang/hotime/db/crud.go:554.2,555.33 2 0 +code.hoteas.com/golang/hotime/db/crud.go:555.33,557.3 1 0 +code.hoteas.com/golang/hotime/db/crud.go:559.2,560.36 2 0 +code.hoteas.com/golang/hotime/db/crud.go:560.36,562.36 2 0 +code.hoteas.com/golang/hotime/db/crud.go:562.36,564.4 1 0 +code.hoteas.com/golang/hotime/db/crud.go:564.9,566.4 1 0 +code.hoteas.com/golang/hotime/db/crud.go:569.2,572.55 1 0 +code.hoteas.com/golang/hotime/db/crud.go:576.154,584.30 5 0 +code.hoteas.com/golang/hotime/db/crud.go:584.30,586.36 2 0 +code.hoteas.com/golang/hotime/db/crud.go:586.36,588.4 1 0 +code.hoteas.com/golang/hotime/db/crud.go:588.9,590.4 1 0 +code.hoteas.com/golang/hotime/db/crud.go:593.2,594.33 2 0 +code.hoteas.com/golang/hotime/db/crud.go:594.33,596.3 1 0 +code.hoteas.com/golang/hotime/db/crud.go:598.2,599.36 2 0 +code.hoteas.com/golang/hotime/db/crud.go:599.36,601.36 2 0 +code.hoteas.com/golang/hotime/db/crud.go:601.36,603.4 1 0 +code.hoteas.com/golang/hotime/db/crud.go:603.9,605.4 1 0 +code.hoteas.com/golang/hotime/db/crud.go:608.2,611.55 1 0 +code.hoteas.com/golang/hotime/db/crud.go:615.71,621.25 5 0 +code.hoteas.com/golang/hotime/db/crud.go:621.25,623.38 2 0 +code.hoteas.com/golang/hotime/db/crud.go:623.38,626.4 2 0 +code.hoteas.com/golang/hotime/db/crud.go:626.9,628.4 1 0 +code.hoteas.com/golang/hotime/db/crud.go:629.3,630.20 2 0 +code.hoteas.com/golang/hotime/db/crud.go:630.20,632.4 1 0 +code.hoteas.com/golang/hotime/db/crud.go:635.2,643.41 6 0 +code.hoteas.com/golang/hotime/db/crud.go:643.41,645.3 1 0 +code.hoteas.com/golang/hotime/db/crud.go:648.2,648.15 1 0 +code.hoteas.com/golang/hotime/db/crud.go:648.15,649.51 1 0 +code.hoteas.com/golang/hotime/db/crud.go:649.51,651.4 1 0 +code.hoteas.com/golang/hotime/db/crud.go:654.2,654.13 1 0 +code.hoteas.com/golang/hotime/db/crud.go:658.79,667.41 7 0 +code.hoteas.com/golang/hotime/db/crud.go:667.41,669.3 1 0 +code.hoteas.com/golang/hotime/db/crud.go:672.2,672.15 1 0 +code.hoteas.com/golang/hotime/db/crud.go:672.15,673.51 1 0 +code.hoteas.com/golang/hotime/db/crud.go:673.51,675.4 1 0 +code.hoteas.com/golang/hotime/db/crud.go:678.2,678.13 1 0 +code.hoteas.com/golang/hotime/db/db.go:38.102,41.2 2 0 +code.hoteas.com/golang/hotime/db/db.go:44.52,45.19 1 0 +code.hoteas.com/golang/hotime/db/db.go:45.19,47.3 1 0 +code.hoteas.com/golang/hotime/db/db.go:48.2,49.20 2 0 +code.hoteas.com/golang/hotime/db/db.go:49.20,51.3 1 0 +code.hoteas.com/golang/hotime/db/db.go:52.2,56.25 3 0 +code.hoteas.com/golang/hotime/db/db.go:56.25,59.3 2 0 +code.hoteas.com/golang/hotime/db/db.go:62.2,62.25 1 0 +code.hoteas.com/golang/hotime/db/db.go:62.25,64.3 1 0 +code.hoteas.com/golang/hotime/db/db.go:66.2,66.21 1 0 +code.hoteas.com/golang/hotime/db/db.go:70.37,71.19 1 1 +code.hoteas.com/golang/hotime/db/db.go:72.32,73.38 1 1 +code.hoteas.com/golang/hotime/db/db.go:74.27,75.34 1 0 +code.hoteas.com/golang/hotime/db/db.go:76.10,77.33 1 1 +code.hoteas.com/golang/hotime/db/db.go:82.44,83.25 1 1 +code.hoteas.com/golang/hotime/db/db.go:83.25,85.3 1 0 +code.hoteas.com/golang/hotime/db/db.go:86.2,86.21 1 1 +code.hoteas.com/golang/hotime/db/db.go:90.51,92.2 1 0 +code.hoteas.com/golang/hotime/db/db.go:95.40,97.2 1 0 +code.hoteas.com/golang/hotime/db/db.go:100.42,102.2 1 0 +code.hoteas.com/golang/hotime/db/db.go:106.59,108.2 1 1 +code.hoteas.com/golang/hotime/db/db.go:113.46,115.2 1 1 +code.hoteas.com/golang/hotime/db/db.go:121.48,122.20 1 1 +code.hoteas.com/golang/hotime/db/db.go:122.20,124.3 1 0 +code.hoteas.com/golang/hotime/db/db.go:125.2,125.20 1 1 +code.hoteas.com/golang/hotime/db/db.go:125.20,127.3 1 1 +code.hoteas.com/golang/hotime/db/db.go:129.2,135.91 6 1 +code.hoteas.com/golang/hotime/db/db.go:139.34,141.17 2 1 +code.hoteas.com/golang/hotime/db/db.go:141.17,142.81 1 1 +code.hoteas.com/golang/hotime/db/db.go:142.81,144.4 1 0 +code.hoteas.com/golang/hotime/db/db.go:146.2,146.10 1 1 +code.hoteas.com/golang/hotime/db/dialect.go:54.41,56.2 1 0 +code.hoteas.com/golang/hotime/db/dialect.go:58.50,60.64 1 0 +code.hoteas.com/golang/hotime/db/dialect.go:60.64,62.3 1 0 +code.hoteas.com/golang/hotime/db/dialect.go:63.2,63.25 1 0 +code.hoteas.com/golang/hotime/db/dialect.go:66.60,70.2 2 1 +code.hoteas.com/golang/hotime/db/dialect.go:72.43,74.2 1 1 +code.hoteas.com/golang/hotime/db/dialect.go:76.54,78.2 1 0 +code.hoteas.com/golang/hotime/db/dialect.go:80.71,81.16 1 0 +code.hoteas.com/golang/hotime/db/dialect.go:81.16,83.3 1 0 +code.hoteas.com/golang/hotime/db/dialect.go:84.2,85.29 2 0 +code.hoteas.com/golang/hotime/db/dialect.go:85.29,87.3 1 0 +code.hoteas.com/golang/hotime/db/dialect.go:88.2,88.40 1 0 +code.hoteas.com/golang/hotime/db/dialect.go:91.52,93.2 1 0 +code.hoteas.com/golang/hotime/db/dialect.go:95.62,97.2 1 0 +code.hoteas.com/golang/hotime/db/dialect.go:99.118,103.30 2 0 +code.hoteas.com/golang/hotime/db/dialect.go:103.30,105.3 1 0 +code.hoteas.com/golang/hotime/db/dialect.go:107.2,110.36 3 0 +code.hoteas.com/golang/hotime/db/dialect.go:110.36,112.36 1 0 +code.hoteas.com/golang/hotime/db/dialect.go:112.36,115.4 1 0 +code.hoteas.com/golang/hotime/db/dialect.go:115.9,118.4 2 0 +code.hoteas.com/golang/hotime/db/dialect.go:121.2,125.35 1 0 +code.hoteas.com/golang/hotime/db/dialect.go:131.46,133.2 1 0 +code.hoteas.com/golang/hotime/db/dialect.go:135.55,137.64 1 0 +code.hoteas.com/golang/hotime/db/dialect.go:137.64,139.3 1 0 +code.hoteas.com/golang/hotime/db/dialect.go:140.2,140.27 1 0 +code.hoteas.com/golang/hotime/db/dialect.go:143.65,147.2 2 1 +code.hoteas.com/golang/hotime/db/dialect.go:149.48,151.2 1 1 +code.hoteas.com/golang/hotime/db/dialect.go:153.59,155.2 1 0 +code.hoteas.com/golang/hotime/db/dialect.go:157.76,158.16 1 0 +code.hoteas.com/golang/hotime/db/dialect.go:158.16,160.3 1 0 +code.hoteas.com/golang/hotime/db/dialect.go:161.2,162.29 2 0 +code.hoteas.com/golang/hotime/db/dialect.go:162.29,164.3 1 0 +code.hoteas.com/golang/hotime/db/dialect.go:165.2,165.40 1 0 +code.hoteas.com/golang/hotime/db/dialect.go:168.57,170.2 1 0 +code.hoteas.com/golang/hotime/db/dialect.go:172.67,174.2 1 0 +code.hoteas.com/golang/hotime/db/dialect.go:176.123,180.30 2 0 +code.hoteas.com/golang/hotime/db/dialect.go:180.30,182.3 1 0 +code.hoteas.com/golang/hotime/db/dialect.go:184.2,187.33 3 0 +code.hoteas.com/golang/hotime/db/dialect.go:187.33,189.3 1 0 +code.hoteas.com/golang/hotime/db/dialect.go:191.2,192.36 2 0 +code.hoteas.com/golang/hotime/db/dialect.go:192.36,193.36 1 0 +code.hoteas.com/golang/hotime/db/dialect.go:193.36,195.4 1 0 +code.hoteas.com/golang/hotime/db/dialect.go:195.9,198.4 2 0 +code.hoteas.com/golang/hotime/db/dialect.go:201.2,206.35 1 0 +code.hoteas.com/golang/hotime/db/dialect.go:212.42,214.2 1 0 +code.hoteas.com/golang/hotime/db/dialect.go:216.51,218.64 1 0 +code.hoteas.com/golang/hotime/db/dialect.go:218.64,220.3 1 0 +code.hoteas.com/golang/hotime/db/dialect.go:221.2,221.27 1 0 +code.hoteas.com/golang/hotime/db/dialect.go:224.61,228.2 2 1 +code.hoteas.com/golang/hotime/db/dialect.go:230.44,232.2 1 1 +code.hoteas.com/golang/hotime/db/dialect.go:234.55,236.2 1 0 +code.hoteas.com/golang/hotime/db/dialect.go:238.72,239.16 1 0 +code.hoteas.com/golang/hotime/db/dialect.go:239.16,241.3 1 0 +code.hoteas.com/golang/hotime/db/dialect.go:242.2,243.29 2 0 +code.hoteas.com/golang/hotime/db/dialect.go:243.29,245.3 1 0 +code.hoteas.com/golang/hotime/db/dialect.go:246.2,246.40 1 0 +code.hoteas.com/golang/hotime/db/dialect.go:249.53,251.2 1 0 +code.hoteas.com/golang/hotime/db/dialect.go:253.63,255.2 1 0 +code.hoteas.com/golang/hotime/db/dialect.go:257.119,261.30 2 0 +code.hoteas.com/golang/hotime/db/dialect.go:261.30,263.3 1 0 +code.hoteas.com/golang/hotime/db/dialect.go:265.2,268.33 3 0 +code.hoteas.com/golang/hotime/db/dialect.go:268.33,270.3 1 0 +code.hoteas.com/golang/hotime/db/dialect.go:272.2,273.36 2 0 +code.hoteas.com/golang/hotime/db/dialect.go:273.36,274.36 1 0 +code.hoteas.com/golang/hotime/db/dialect.go:274.36,276.4 1 0 +code.hoteas.com/golang/hotime/db/dialect.go:276.9,279.4 2 0 +code.hoteas.com/golang/hotime/db/dialect.go:282.2,287.35 1 0 +code.hoteas.com/golang/hotime/db/identifier.go:16.82,21.2 1 1 +code.hoteas.com/golang/hotime/db/identifier.go:38.68,43.33 2 1 +code.hoteas.com/golang/hotime/db/identifier.go:43.33,50.3 4 0 +code.hoteas.com/golang/hotime/db/identifier.go:53.2,53.33 1 1 +code.hoteas.com/golang/hotime/db/identifier.go:53.33,55.22 2 0 +code.hoteas.com/golang/hotime/db/identifier.go:55.22,59.31 3 0 +code.hoteas.com/golang/hotime/db/identifier.go:59.31,61.5 1 0 +code.hoteas.com/golang/hotime/db/identifier.go:63.4,63.98 1 0 +code.hoteas.com/golang/hotime/db/identifier.go:68.2,68.51 1 1 +code.hoteas.com/golang/hotime/db/identifier.go:73.76,75.33 2 0 +code.hoteas.com/golang/hotime/db/identifier.go:75.33,80.3 4 0 +code.hoteas.com/golang/hotime/db/identifier.go:81.2,81.40 1 0 +code.hoteas.com/golang/hotime/db/identifier.go:88.65,90.34 1 1 +code.hoteas.com/golang/hotime/db/identifier.go:90.34,93.3 1 1 +code.hoteas.com/golang/hotime/db/identifier.go:96.2,97.21 2 1 +code.hoteas.com/golang/hotime/db/identifier.go:97.21,102.3 3 1 +code.hoteas.com/golang/hotime/db/identifier.go:105.2,105.30 1 0 +code.hoteas.com/golang/hotime/db/identifier.go:109.73,110.34 1 0 +code.hoteas.com/golang/hotime/db/identifier.go:110.34,113.3 1 0 +code.hoteas.com/golang/hotime/db/identifier.go:116.2,117.21 2 0 +code.hoteas.com/golang/hotime/db/identifier.go:117.21,121.3 3 0 +code.hoteas.com/golang/hotime/db/identifier.go:123.2,123.30 1 0 +code.hoteas.com/golang/hotime/db/identifier.go:131.79,132.21 1 1 +code.hoteas.com/golang/hotime/db/identifier.go:132.21,134.3 1 0 +code.hoteas.com/golang/hotime/db/identifier.go:136.2,141.85 3 1 +code.hoteas.com/golang/hotime/db/identifier.go:141.85,143.22 2 0 +code.hoteas.com/golang/hotime/db/identifier.go:143.22,148.4 3 0 +code.hoteas.com/golang/hotime/db/identifier.go:149.3,149.15 1 0 +code.hoteas.com/golang/hotime/db/identifier.go:154.2,155.85 2 1 +code.hoteas.com/golang/hotime/db/identifier.go:155.85,157.22 2 0 +code.hoteas.com/golang/hotime/db/identifier.go:157.22,162.36 4 0 +code.hoteas.com/golang/hotime/db/identifier.go:162.36,164.69 2 0 +code.hoteas.com/golang/hotime/db/identifier.go:164.69,166.6 1 0 +code.hoteas.com/golang/hotime/db/identifier.go:169.4,169.56 1 0 +code.hoteas.com/golang/hotime/db/identifier.go:171.3,171.15 1 0 +code.hoteas.com/golang/hotime/db/identifier.go:176.2,177.82 2 1 +code.hoteas.com/golang/hotime/db/identifier.go:177.82,179.22 2 1 +code.hoteas.com/golang/hotime/db/identifier.go:179.22,186.4 5 1 +code.hoteas.com/golang/hotime/db/identifier.go:187.3,187.15 1 0 +code.hoteas.com/golang/hotime/db/identifier.go:190.2,190.15 1 1 +code.hoteas.com/golang/hotime/db/identifier.go:194.31,196.2 1 0 +code.hoteas.com/golang/hotime/db/identifier.go:201.70,202.35 1 0 +code.hoteas.com/golang/hotime/db/identifier.go:202.35,204.3 1 0 +code.hoteas.com/golang/hotime/db/identifier.go:207.2,207.41 1 0 +code.hoteas.com/golang/hotime/db/identifier.go:211.63,214.66 2 1 +code.hoteas.com/golang/hotime/db/identifier.go:214.66,216.3 1 1 +code.hoteas.com/golang/hotime/db/identifier.go:218.2,218.68 1 1 +code.hoteas.com/golang/hotime/db/identifier.go:218.68,220.3 1 1 +code.hoteas.com/golang/hotime/db/identifier.go:221.2,221.13 1 1 +code.hoteas.com/golang/hotime/db/identifier.go:226.70,233.33 4 1 +code.hoteas.com/golang/hotime/db/identifier.go:233.33,235.27 2 1 +code.hoteas.com/golang/hotime/db/identifier.go:235.27,236.16 1 1 +code.hoteas.com/golang/hotime/db/identifier.go:236.16,239.5 2 1 +code.hoteas.com/golang/hotime/db/identifier.go:239.10,239.29 1 1 +code.hoteas.com/golang/hotime/db/identifier.go:239.29,241.5 1 1 +code.hoteas.com/golang/hotime/db/identifier.go:242.9,242.34 1 1 +code.hoteas.com/golang/hotime/db/identifier.go:242.34,244.9 2 1 +code.hoteas.com/golang/hotime/db/identifier.go:248.2,248.20 1 1 +code.hoteas.com/golang/hotime/db/identifier.go:248.20,250.3 1 0 +code.hoteas.com/golang/hotime/db/identifier.go:252.2,252.53 1 1 +code.hoteas.com/golang/hotime/db/identifier.go:256.65,261.22 3 0 +code.hoteas.com/golang/hotime/db/identifier.go:261.22,263.3 1 0 +code.hoteas.com/golang/hotime/db/identifier.go:264.2,264.13 1 0 +code.hoteas.com/golang/hotime/db/identifier.go:268.52,270.2 1 0 +code.hoteas.com/golang/hotime/db/identifier.go:273.50,275.2 1 0 +code.hoteas.com/golang/hotime/db/query.go:16.69,20.2 3 0 +code.hoteas.com/golang/hotime/db/query.go:23.70,25.2 1 0 +code.hoteas.com/golang/hotime/db/query.go:28.93,38.15 6 0 +code.hoteas.com/golang/hotime/db/query.go:38.15,39.21 1 0 +code.hoteas.com/golang/hotime/db/query.go:39.21,43.4 3 0 +code.hoteas.com/golang/hotime/db/query.go:46.2,51.25 4 0 +code.hoteas.com/golang/hotime/db/query.go:51.25,53.3 1 0 +code.hoteas.com/golang/hotime/db/query.go:55.2,55.15 1 0 +code.hoteas.com/golang/hotime/db/query.go:55.15,59.3 3 0 +code.hoteas.com/golang/hotime/db/query.go:62.2,64.20 2 0 +code.hoteas.com/golang/hotime/db/query.go:64.20,66.3 1 0 +code.hoteas.com/golang/hotime/db/query.go:66.8,68.3 1 0 +code.hoteas.com/golang/hotime/db/query.go:70.2,71.16 2 0 +code.hoteas.com/golang/hotime/db/query.go:71.16,73.15 1 0 +code.hoteas.com/golang/hotime/db/query.go:73.15,74.44 1 0 +code.hoteas.com/golang/hotime/db/query.go:74.44,76.5 1 0 +code.hoteas.com/golang/hotime/db/query.go:78.3,78.13 1 0 +code.hoteas.com/golang/hotime/db/query.go:81.2,81.23 1 0 +code.hoteas.com/golang/hotime/db/query.go:85.84,87.2 1 0 +code.hoteas.com/golang/hotime/db/query.go:90.107,100.15 6 0 +code.hoteas.com/golang/hotime/db/query.go:100.15,101.21 1 0 +code.hoteas.com/golang/hotime/db/query.go:101.21,105.4 3 0 +code.hoteas.com/golang/hotime/db/query.go:108.2,111.20 3 0 +code.hoteas.com/golang/hotime/db/query.go:111.20,115.3 3 0 +code.hoteas.com/golang/hotime/db/query.go:118.2,120.20 2 0 +code.hoteas.com/golang/hotime/db/query.go:120.20,122.3 1 0 +code.hoteas.com/golang/hotime/db/query.go:122.8,124.3 1 0 +code.hoteas.com/golang/hotime/db/query.go:126.2,128.14 2 0 +code.hoteas.com/golang/hotime/db/query.go:128.14,129.15 1 0 +code.hoteas.com/golang/hotime/db/query.go:129.15,130.49 1 0 +code.hoteas.com/golang/hotime/db/query.go:130.49,132.5 1 0 +code.hoteas.com/golang/hotime/db/query.go:134.3,134.28 1 0 +code.hoteas.com/golang/hotime/db/query.go:137.2,137.27 1 0 +code.hoteas.com/golang/hotime/db/query.go:141.69,144.33 3 0 +code.hoteas.com/golang/hotime/db/query.go:144.33,146.17 2 0 +code.hoteas.com/golang/hotime/db/query.go:146.17,147.12 1 0 +code.hoteas.com/golang/hotime/db/query.go:149.3,150.76 2 0 +code.hoteas.com/golang/hotime/db/query.go:150.76,154.37 3 0 +code.hoteas.com/golang/hotime/db/query.go:154.37,155.27 1 0 +code.hoteas.com/golang/hotime/db/query.go:155.27,157.6 1 0 +code.hoteas.com/golang/hotime/db/query.go:157.11,159.6 1 0 +code.hoteas.com/golang/hotime/db/query.go:161.4,161.31 1 0 +code.hoteas.com/golang/hotime/db/query.go:164.2,164.22 1 0 +code.hoteas.com/golang/hotime/db/query.go:183.104,184.53 1 0 +code.hoteas.com/golang/hotime/db/query.go:184.53,186.3 1 0 +code.hoteas.com/golang/hotime/db/query.go:189.2,190.27 2 0 +code.hoteas.com/golang/hotime/db/query.go:190.27,191.17 1 0 +code.hoteas.com/golang/hotime/db/query.go:191.17,192.12 1 0 +code.hoteas.com/golang/hotime/db/query.go:194.3,195.76 2 0 +code.hoteas.com/golang/hotime/db/query.go:195.76,197.9 2 0 +code.hoteas.com/golang/hotime/db/query.go:200.2,200.15 1 0 +code.hoteas.com/golang/hotime/db/query.go:200.15,202.3 1 0 +code.hoteas.com/golang/hotime/db/query.go:204.2,208.34 4 0 +code.hoteas.com/golang/hotime/db/query.go:208.34,209.46 1 0 +code.hoteas.com/golang/hotime/db/query.go:209.46,213.18 3 0 +code.hoteas.com/golang/hotime/db/query.go:213.18,216.13 3 0 +code.hoteas.com/golang/hotime/db/query.go:219.4,220.77 2 0 +code.hoteas.com/golang/hotime/db/query.go:220.77,228.33 5 0 +code.hoteas.com/golang/hotime/db/query.go:228.33,230.6 1 0 +code.hoteas.com/golang/hotime/db/query.go:233.5,235.27 3 0 +code.hoteas.com/golang/hotime/db/query.go:235.27,237.6 1 0 +code.hoteas.com/golang/hotime/db/query.go:241.5,243.25 3 0 +code.hoteas.com/golang/hotime/db/query.go:243.25,246.75 1 0 +code.hoteas.com/golang/hotime/db/query.go:246.75,250.7 2 0 +code.hoteas.com/golang/hotime/db/query.go:250.12,250.54 1 0 +code.hoteas.com/golang/hotime/db/query.go:250.54,254.7 2 0 +code.hoteas.com/golang/hotime/db/query.go:254.12,257.7 1 0 +code.hoteas.com/golang/hotime/db/query.go:258.11,258.29 1 0 +code.hoteas.com/golang/hotime/db/query.go:258.29,260.6 1 0 +code.hoteas.com/golang/hotime/db/query.go:263.5,264.25 2 0 +code.hoteas.com/golang/hotime/db/query.go:264.25,268.24 3 0 +code.hoteas.com/golang/hotime/db/query.go:268.24,270.27 2 0 +code.hoteas.com/golang/hotime/db/query.go:270.27,272.8 1 0 +code.hoteas.com/golang/hotime/db/query.go:276.5,276.20 1 0 +code.hoteas.com/golang/hotime/db/query.go:276.20,279.27 2 0 +code.hoteas.com/golang/hotime/db/query.go:279.27,294.28 9 0 +code.hoteas.com/golang/hotime/db/query.go:294.28,297.8 2 0 +code.hoteas.com/golang/hotime/db/query.go:298.7,298.27 1 0 +code.hoteas.com/golang/hotime/db/query.go:298.27,301.8 2 0 +code.hoteas.com/golang/hotime/db/query.go:302.7,302.30 1 0 +code.hoteas.com/golang/hotime/db/query.go:302.30,305.8 2 0 +code.hoteas.com/golang/hotime/db/query.go:306.7,306.30 1 0 +code.hoteas.com/golang/hotime/db/query.go:306.30,309.8 2 0 +code.hoteas.com/golang/hotime/db/query.go:311.7,311.25 1 0 +code.hoteas.com/golang/hotime/db/query.go:311.25,313.8 1 0 +code.hoteas.com/golang/hotime/db/query.go:315.7,317.18 3 0 +code.hoteas.com/golang/hotime/db/query.go:317.18,320.8 1 0 +code.hoteas.com/golang/hotime/db/query.go:320.13,323.8 1 0 +code.hoteas.com/golang/hotime/db/query.go:325.7,325.43 1 0 +code.hoteas.com/golang/hotime/db/query.go:325.43,326.27 1 0 +code.hoteas.com/golang/hotime/db/query.go:326.27,328.14 2 0 +code.hoteas.com/golang/hotime/db/query.go:331.12,331.34 1 0 +code.hoteas.com/golang/hotime/db/query.go:331.34,335.7 2 0 +code.hoteas.com/golang/hotime/db/query.go:335.12,337.41 1 0 +code.hoteas.com/golang/hotime/db/query.go:337.41,338.17 1 0 +code.hoteas.com/golang/hotime/db/query.go:338.17,340.9 1 0 +code.hoteas.com/golang/hotime/db/query.go:341.8,342.45 2 0 +code.hoteas.com/golang/hotime/db/query.go:345.11,349.6 2 0 +code.hoteas.com/golang/hotime/db/query.go:350.10,354.5 2 0 +code.hoteas.com/golang/hotime/db/query.go:355.9,357.4 1 0 +code.hoteas.com/golang/hotime/db/query.go:360.2,360.33 1 0 +code.hoteas.com/golang/hotime/db/query.go:371.42,376.47 5 1 +code.hoteas.com/golang/hotime/db/query.go:376.47,378.3 1 1 +code.hoteas.com/golang/hotime/db/query.go:379.2,379.14 1 1 +code.hoteas.com/golang/hotime/db/query.go:383.36,384.23 1 1 +code.hoteas.com/golang/hotime/db/query.go:385.42,386.23 1 1 +code.hoteas.com/golang/hotime/db/query.go:387.73,388.21 1 1 +code.hoteas.com/golang/hotime/db/query.go:390.2,390.35 1 1 +code.hoteas.com/golang/hotime/db/query.go:390.35,392.3 1 0 +code.hoteas.com/golang/hotime/db/query.go:393.2,393.76 1 1 +code.hoteas.com/golang/hotime/db/query.go:393.76,395.3 1 0 +code.hoteas.com/golang/hotime/db/query.go:396.2,396.73 1 1 +code.hoteas.com/golang/hotime/db/query.go:396.73,398.3 1 0 +code.hoteas.com/golang/hotime/db/query.go:399.2,399.22 1 1 +code.hoteas.com/golang/hotime/db/query.go:403.68,405.22 2 0 +code.hoteas.com/golang/hotime/db/query.go:405.22,407.3 1 0 +code.hoteas.com/golang/hotime/db/query.go:409.2,410.47 2 0 +code.hoteas.com/golang/hotime/db/query.go:410.47,412.51 2 0 +code.hoteas.com/golang/hotime/db/query.go:412.51,413.73 1 0 +code.hoteas.com/golang/hotime/db/query.go:413.73,415.5 1 0 +code.hoteas.com/golang/hotime/db/query.go:418.2,418.11 1 0 +code.hoteas.com/golang/hotime/db/query.go:422.66,424.35 2 0 +code.hoteas.com/golang/hotime/db/query.go:424.35,426.3 1 0 +code.hoteas.com/golang/hotime/db/query.go:427.2,427.11 1 0 +code.hoteas.com/golang/hotime/db/query.go:431.68,432.39 1 1 +code.hoteas.com/golang/hotime/db/query.go:432.39,434.3 1 1 +code.hoteas.com/golang/hotime/db/query.go:435.2,435.18 1 1 +code.hoteas.com/golang/hotime/db/query.go:436.21,437.30 1 1 +code.hoteas.com/golang/hotime/db/query.go:438.21,439.30 1 1 +code.hoteas.com/golang/hotime/db/query.go:440.19,441.30 1 1 +code.hoteas.com/golang/hotime/db/query.go:442.10,443.11 1 1 +code.hoteas.com/golang/hotime/db/query.go:448.51,449.39 1 1 +code.hoteas.com/golang/hotime/db/query.go:449.39,451.3 1 1 +code.hoteas.com/golang/hotime/db/query.go:452.2,452.16 1 1 +code.hoteas.com/golang/hotime/db/query.go:452.16,455.3 1 1 +code.hoteas.com/golang/hotime/db/query.go:456.2,456.15 1 1 +code.hoteas.com/golang/hotime/db/query.go:456.15,459.3 1 1 +code.hoteas.com/golang/hotime/db/query.go:460.2,462.16 3 1 +code.hoteas.com/golang/hotime/db/query.go:462.16,464.3 1 0 +code.hoteas.com/golang/hotime/db/query.go:465.2,465.10 1 1 +code.hoteas.com/golang/hotime/db/query.go:470.68,472.18 2 1 +code.hoteas.com/golang/hotime/db/query.go:473.21,474.56 1 1 +code.hoteas.com/golang/hotime/db/query.go:474.56,476.4 1 1 +code.hoteas.com/golang/hotime/db/query.go:477.3,477.54 1 1 +code.hoteas.com/golang/hotime/db/query.go:477.54,479.4 1 1 +code.hoteas.com/golang/hotime/db/query.go:480.3,480.11 1 1 +code.hoteas.com/golang/hotime/db/query.go:481.21,482.54 1 1 +code.hoteas.com/golang/hotime/db/query.go:482.54,484.4 1 1 +code.hoteas.com/golang/hotime/db/query.go:485.3,485.11 1 0 +code.hoteas.com/golang/hotime/db/query.go:486.19,487.54 1 1 +code.hoteas.com/golang/hotime/db/query.go:487.54,489.4 1 1 +code.hoteas.com/golang/hotime/db/query.go:490.3,490.11 1 0 +code.hoteas.com/golang/hotime/db/query.go:491.10,492.11 1 1 +code.hoteas.com/golang/hotime/db/query.go:497.49,507.21 8 0 +code.hoteas.com/golang/hotime/db/query.go:507.21,508.54 1 0 +code.hoteas.com/golang/hotime/db/query.go:508.54,511.25 3 0 +code.hoteas.com/golang/hotime/db/query.go:512.23,513.55 1 0 +code.hoteas.com/golang/hotime/db/query.go:514.21,515.53 1 0 +code.hoteas.com/golang/hotime/db/query.go:520.2,520.18 1 0 +code.hoteas.com/golang/hotime/db/query.go:520.18,524.33 4 0 +code.hoteas.com/golang/hotime/db/query.go:524.33,526.4 1 0 +code.hoteas.com/golang/hotime/db/query.go:527.3,527.41 1 0 +code.hoteas.com/golang/hotime/db/query.go:527.41,530.4 2 0 +code.hoteas.com/golang/hotime/db/query.go:531.3,531.33 1 0 +code.hoteas.com/golang/hotime/db/query.go:531.33,533.18 2 0 +code.hoteas.com/golang/hotime/db/query.go:533.18,535.13 2 0 +code.hoteas.com/golang/hotime/db/query.go:537.4,537.27 1 0 +code.hoteas.com/golang/hotime/db/query.go:538.16,539.61 1 0 +code.hoteas.com/golang/hotime/db/query.go:540.17,541.62 1 0 +code.hoteas.com/golang/hotime/db/query.go:542.17,543.71 1 0 +code.hoteas.com/golang/hotime/db/query.go:544.12,545.23 1 0 +code.hoteas.com/golang/hotime/db/query.go:548.3,548.27 1 0 +code.hoteas.com/golang/hotime/db/query.go:550.2,550.35 1 0 +code.hoteas.com/golang/hotime/db/query.go:550.35,552.3 1 0 +code.hoteas.com/golang/hotime/db/query.go:553.2,553.13 1 0 +code.hoteas.com/golang/hotime/db/transaction.go:9.90,33.16 3 0 +code.hoteas.com/golang/hotime/db/transaction.go:33.16,36.3 2 0 +code.hoteas.com/golang/hotime/db/transaction.go:38.2,42.16 3 0 +code.hoteas.com/golang/hotime/db/transaction.go:42.16,44.17 2 0 +code.hoteas.com/golang/hotime/db/transaction.go:44.17,47.4 2 0 +code.hoteas.com/golang/hotime/db/transaction.go:48.3,48.19 1 0 +code.hoteas.com/golang/hotime/db/transaction.go:51.2,52.16 2 0 +code.hoteas.com/golang/hotime/db/transaction.go:52.16,55.3 2 0 +code.hoteas.com/golang/hotime/db/transaction.go:56.2,56.13 1 0 +code.hoteas.com/golang/hotime/db/where.go:17.36,19.26 2 1 +code.hoteas.com/golang/hotime/db/where.go:19.26,20.17 1 1 +code.hoteas.com/golang/hotime/db/where.go:20.17,22.4 1 1 +code.hoteas.com/golang/hotime/db/where.go:24.2,24.30 1 1 +code.hoteas.com/golang/hotime/db/where.go:24.30,25.17 1 1 +code.hoteas.com/golang/hotime/db/where.go:25.17,27.4 1 1 +code.hoteas.com/golang/hotime/db/where.go:29.2,29.10 1 1 +code.hoteas.com/golang/hotime/db/where.go:33.36,35.30 2 1 +code.hoteas.com/golang/hotime/db/where.go:35.30,36.17 1 1 +code.hoteas.com/golang/hotime/db/where.go:36.17,38.4 1 1 +code.hoteas.com/golang/hotime/db/where.go:40.2,40.14 1 1 +code.hoteas.com/golang/hotime/db/where.go:44.32,46.26 2 1 +code.hoteas.com/golang/hotime/db/where.go:46.26,47.17 1 1 +code.hoteas.com/golang/hotime/db/where.go:47.17,49.4 1 1 +code.hoteas.com/golang/hotime/db/where.go:51.2,51.14 1 1 +code.hoteas.com/golang/hotime/db/where.go:55.63,61.25 4 1 +code.hoteas.com/golang/hotime/db/where.go:61.25,63.3 1 1 +code.hoteas.com/golang/hotime/db/where.go:64.2,68.24 3 1 +code.hoteas.com/golang/hotime/db/where.go:68.24,70.3 1 1 +code.hoteas.com/golang/hotime/db/where.go:71.2,76.27 3 1 +code.hoteas.com/golang/hotime/db/where.go:76.27,80.24 2 1 +code.hoteas.com/golang/hotime/db/where.go:80.24,82.47 2 1 +code.hoteas.com/golang/hotime/db/where.go:82.47,84.22 1 1 +code.hoteas.com/golang/hotime/db/where.go:84.22,86.6 1 1 +code.hoteas.com/golang/hotime/db/where.go:88.5,90.29 3 1 +code.hoteas.com/golang/hotime/db/where.go:92.4,92.12 1 1 +code.hoteas.com/golang/hotime/db/where.go:96.3,96.20 1 1 +code.hoteas.com/golang/hotime/db/where.go:96.20,97.12 1 1 +code.hoteas.com/golang/hotime/db/where.go:102.3,102.94 1 1 +code.hoteas.com/golang/hotime/db/where.go:102.94,104.36 1 0 +code.hoteas.com/golang/hotime/db/where.go:104.36,106.22 1 0 +code.hoteas.com/golang/hotime/db/where.go:106.22,108.6 1 0 +code.hoteas.com/golang/hotime/db/where.go:109.5,110.16 2 0 +code.hoteas.com/golang/hotime/db/where.go:112.4,112.12 1 0 +code.hoteas.com/golang/hotime/db/where.go:114.3,114.104 1 1 +code.hoteas.com/golang/hotime/db/where.go:114.104,116.36 1 0 +code.hoteas.com/golang/hotime/db/where.go:116.36,118.22 1 0 +code.hoteas.com/golang/hotime/db/where.go:118.22,120.6 1 0 +code.hoteas.com/golang/hotime/db/where.go:121.5,122.16 2 0 +code.hoteas.com/golang/hotime/db/where.go:124.4,124.12 1 0 +code.hoteas.com/golang/hotime/db/where.go:127.3,128.15 2 1 +code.hoteas.com/golang/hotime/db/where.go:128.15,130.21 1 1 +code.hoteas.com/golang/hotime/db/where.go:130.21,132.5 1 1 +code.hoteas.com/golang/hotime/db/where.go:133.4,135.28 3 1 +code.hoteas.com/golang/hotime/db/where.go:141.2,142.28 2 1 +code.hoteas.com/golang/hotime/db/where.go:142.28,144.27 2 1 +code.hoteas.com/golang/hotime/db/where.go:144.27,145.43 1 1 +code.hoteas.com/golang/hotime/db/where.go:145.43,147.5 1 0 +code.hoteas.com/golang/hotime/db/where.go:150.3,150.15 1 1 +code.hoteas.com/golang/hotime/db/where.go:150.15,152.4 1 1 +code.hoteas.com/golang/hotime/db/where.go:153.8,156.3 1 0 +code.hoteas.com/golang/hotime/db/where.go:159.2,160.40 2 1 +code.hoteas.com/golang/hotime/db/where.go:160.40,162.14 2 1 +code.hoteas.com/golang/hotime/db/where.go:162.14,163.12 1 1 +code.hoteas.com/golang/hotime/db/where.go:166.3,166.19 1 1 +code.hoteas.com/golang/hotime/db/where.go:167.16,169.37 2 0 +code.hoteas.com/golang/hotime/db/where.go:170.17,172.36 1 0 +code.hoteas.com/golang/hotime/db/where.go:172.36,174.26 2 0 +code.hoteas.com/golang/hotime/db/where.go:174.26,177.6 2 0 +code.hoteas.com/golang/hotime/db/where.go:179.16,181.37 2 1 +code.hoteas.com/golang/hotime/db/where.go:182.16,184.37 2 1 +code.hoteas.com/golang/hotime/db/where.go:185.17,186.43 1 0 +code.hoteas.com/golang/hotime/db/where.go:187.19,187.19 0 0 +code.hoteas.com/golang/hotime/db/where.go:192.2,192.19 1 1 +code.hoteas.com/golang/hotime/db/where.go:196.62,198.120 2 1 +code.hoteas.com/golang/hotime/db/where.go:198.120,200.32 2 0 +code.hoteas.com/golang/hotime/db/where.go:200.32,202.22 2 0 +code.hoteas.com/golang/hotime/db/where.go:202.22,204.5 1 0 +code.hoteas.com/golang/hotime/db/where.go:206.8,208.3 1 1 +code.hoteas.com/golang/hotime/db/where.go:209.2,209.15 1 1 +code.hoteas.com/golang/hotime/db/where.go:213.80,219.16 5 1 +code.hoteas.com/golang/hotime/db/where.go:219.16,222.3 2 0 +code.hoteas.com/golang/hotime/db/where.go:222.8,222.24 1 1 +code.hoteas.com/golang/hotime/db/where.go:222.24,225.3 1 0 +code.hoteas.com/golang/hotime/db/where.go:225.8,225.73 1 1 +code.hoteas.com/golang/hotime/db/where.go:225.73,228.33 2 0 +code.hoteas.com/golang/hotime/db/where.go:229.14,233.24 4 0 +code.hoteas.com/golang/hotime/db/where.go:234.14,238.24 4 0 +code.hoteas.com/golang/hotime/db/where.go:239.14,242.45 3 0 +code.hoteas.com/golang/hotime/db/where.go:243.14,246.46 3 0 +code.hoteas.com/golang/hotime/db/where.go:247.15,248.30 1 0 +code.hoteas.com/golang/hotime/db/where.go:249.15,252.47 3 0 +code.hoteas.com/golang/hotime/db/where.go:253.15,256.47 3 0 +code.hoteas.com/golang/hotime/db/where.go:257.14,262.24 5 0 +code.hoteas.com/golang/hotime/db/where.go:263.15,268.24 5 0 +code.hoteas.com/golang/hotime/db/where.go:269.15,274.24 5 0 +code.hoteas.com/golang/hotime/db/where.go:275.15,279.24 4 0 +code.hoteas.com/golang/hotime/db/where.go:280.11,281.14 1 0 +code.hoteas.com/golang/hotime/db/where.go:284.3,284.10 1 0 +code.hoteas.com/golang/hotime/db/where.go:284.10,285.34 1 0 +code.hoteas.com/golang/hotime/db/where.go:286.16,290.25 4 0 +code.hoteas.com/golang/hotime/db/where.go:291.16,295.25 4 0 +code.hoteas.com/golang/hotime/db/where.go:296.16,302.29 6 0 +code.hoteas.com/golang/hotime/db/where.go:303.16,309.29 6 0 +code.hoteas.com/golang/hotime/db/where.go:310.12,311.63 1 0 +code.hoteas.com/golang/hotime/db/where.go:314.8,316.3 1 1 +code.hoteas.com/golang/hotime/db/where.go:318.2,318.19 1 1 +code.hoteas.com/golang/hotime/db/where.go:322.128,326.120 3 0 +code.hoteas.com/golang/hotime/db/where.go:326.120,328.19 2 0 +code.hoteas.com/golang/hotime/db/where.go:328.19,332.4 2 0 +code.hoteas.com/golang/hotime/db/where.go:334.3,334.19 1 0 +code.hoteas.com/golang/hotime/db/where.go:334.19,338.4 3 0 +code.hoteas.com/golang/hotime/db/where.go:341.3,341.59 1 0 +code.hoteas.com/golang/hotime/db/where.go:342.8,345.3 2 0 +code.hoteas.com/golang/hotime/db/where.go:347.2,347.19 1 0 +code.hoteas.com/golang/hotime/db/where.go:351.122,355.14 3 1 +code.hoteas.com/golang/hotime/db/where.go:355.14,357.3 1 0 +code.hoteas.com/golang/hotime/db/where.go:357.8,357.127 1 1 +code.hoteas.com/golang/hotime/db/where.go:357.127,359.19 2 0 +code.hoteas.com/golang/hotime/db/where.go:359.19,363.4 2 0 +code.hoteas.com/golang/hotime/db/where.go:365.3,365.19 1 0 +code.hoteas.com/golang/hotime/db/where.go:365.19,369.4 3 0 +code.hoteas.com/golang/hotime/db/where.go:372.3,372.59 1 0 +code.hoteas.com/golang/hotime/db/where.go:373.8,376.3 2 1 +code.hoteas.com/golang/hotime/db/where.go:378.2,378.19 1 1 +code.hoteas.com/golang/hotime/db/where.go:382.120,394.38 10 0 +code.hoteas.com/golang/hotime/db/where.go:394.38,396.20 2 0 +code.hoteas.com/golang/hotime/db/where.go:396.20,399.42 2 0 +code.hoteas.com/golang/hotime/db/where.go:399.42,401.10 2 0 +code.hoteas.com/golang/hotime/db/where.go:405.3,405.12 1 0 +code.hoteas.com/golang/hotime/db/where.go:405.12,408.4 2 0 +code.hoteas.com/golang/hotime/db/where.go:408.9,410.4 1 0 +code.hoteas.com/golang/hotime/db/where.go:411.3,411.12 1 0 +code.hoteas.com/golang/hotime/db/where.go:411.12,414.4 2 0 +code.hoteas.com/golang/hotime/db/where.go:416.3,416.17 1 0 +code.hoteas.com/golang/hotime/db/where.go:416.17,418.19 1 0 +code.hoteas.com/golang/hotime/db/where.go:418.19,419.21 1 0 +code.hoteas.com/golang/hotime/db/where.go:419.21,421.6 1 0 +code.hoteas.com/golang/hotime/db/where.go:421.11,423.6 1 0 +code.hoteas.com/golang/hotime/db/where.go:424.5,425.31 2 0 +code.hoteas.com/golang/hotime/db/where.go:426.10,429.5 2 0 +code.hoteas.com/golang/hotime/db/where.go:430.4,431.13 2 0 +code.hoteas.com/golang/hotime/db/where.go:435.2,435.13 1 0 +code.hoteas.com/golang/hotime/db/where.go:435.13,438.19 2 0 +code.hoteas.com/golang/hotime/db/where.go:438.19,441.4 2 0 +code.hoteas.com/golang/hotime/db/where.go:443.3,443.21 1 0 +code.hoteas.com/golang/hotime/db/where.go:443.21,444.20 1 0 +code.hoteas.com/golang/hotime/db/where.go:444.20,446.5 1 0 +code.hoteas.com/golang/hotime/db/where.go:446.10,448.5 1 0 +code.hoteas.com/golang/hotime/db/where.go:449.4,449.30 1 0 +code.hoteas.com/golang/hotime/db/where.go:450.9,450.27 1 0 +code.hoteas.com/golang/hotime/db/where.go:450.27,452.20 2 0 +code.hoteas.com/golang/hotime/db/where.go:452.20,454.5 1 0 +code.hoteas.com/golang/hotime/db/where.go:454.10,456.5 1 0 +code.hoteas.com/golang/hotime/db/where.go:457.4,457.30 1 0 +code.hoteas.com/golang/hotime/db/where.go:460.3,460.19 1 0 +code.hoteas.com/golang/hotime/db/where.go:460.19,462.4 1 0 +code.hoteas.com/golang/hotime/db/where.go:464.3,464.20 1 0 +code.hoteas.com/golang/hotime/db/where.go:468.2,471.31 3 0 +code.hoteas.com/golang/hotime/db/where.go:471.31,472.21 1 0 +code.hoteas.com/golang/hotime/db/where.go:472.21,474.4 1 0 +code.hoteas.com/golang/hotime/db/where.go:474.9,476.4 1 0 +code.hoteas.com/golang/hotime/db/where.go:479.2,479.19 1 0 +code.hoteas.com/golang/hotime/db/where.go:483.111,484.14 1 0 +code.hoteas.com/golang/hotime/db/where.go:484.14,486.3 1 0 +code.hoteas.com/golang/hotime/db/where.go:486.8,486.127 1 0 +code.hoteas.com/golang/hotime/db/where.go:486.127,488.19 2 0 +code.hoteas.com/golang/hotime/db/where.go:488.19,490.4 1 0 +code.hoteas.com/golang/hotime/db/where.go:491.3,494.32 3 0 +code.hoteas.com/golang/hotime/db/where.go:494.32,495.22 1 0 +code.hoteas.com/golang/hotime/db/where.go:495.22,497.5 1 0 +code.hoteas.com/golang/hotime/db/where.go:497.10,499.5 1 0 +code.hoteas.com/golang/hotime/db/where.go:501.8,504.3 2 0 +code.hoteas.com/golang/hotime/db/where.go:506.2,506.19 1 0 +code.hoteas.com/golang/hotime/db/where.go:510.74,516.24 5 1 +code.hoteas.com/golang/hotime/db/where.go:516.24,518.3 1 1 +code.hoteas.com/golang/hotime/db/where.go:519.2,521.27 2 1 +code.hoteas.com/golang/hotime/db/where.go:521.27,524.39 3 1 +code.hoteas.com/golang/hotime/db/where.go:524.39,525.42 1 1 +code.hoteas.com/golang/hotime/db/where.go:525.42,527.26 2 1 +code.hoteas.com/golang/hotime/db/where.go:527.26,529.6 1 0 +code.hoteas.com/golang/hotime/db/where.go:529.11,531.6 1 1 +code.hoteas.com/golang/hotime/db/where.go:533.5,534.10 2 1 +code.hoteas.com/golang/hotime/db/where.go:536.4,536.7 1 1 +code.hoteas.com/golang/hotime/db/where.go:539.3,539.26 1 1 +code.hoteas.com/golang/hotime/db/where.go:539.26,541.16 2 1 +code.hoteas.com/golang/hotime/db/where.go:541.16,543.13 2 0 +code.hoteas.com/golang/hotime/db/where.go:545.4,546.25 2 1 +code.hoteas.com/golang/hotime/db/where.go:546.25,548.5 1 1 +code.hoteas.com/golang/hotime/db/where.go:548.10,550.5 1 1 +code.hoteas.com/golang/hotime/db/where.go:554.2,554.19 1 1 diff --git a/db/query.go b/db/query.go index 22d371b..f3df0dd 100644 --- a/db/query.go +++ b/db/query.go @@ -4,7 +4,9 @@ import ( "database/sql" "encoding/json" "errors" + "math" "reflect" + "strconv" "strings" . "code.hoteas.com/golang/hotime/common" @@ -358,34 +360,195 @@ func (that *HoTimeDB) expandArrayPlaceholder(query string, args []interface{}) ( return result.String(), newArgs } +const ( + dbTypeUnknown = iota + dbTypeInteger + dbTypeFloat + dbTypeDecimal +) + +// classifyDBType 将数据库类型名分类,覆盖 MySQL/SQLite/PostgreSQL +func classifyDBType(typeName string) int { + t := strings.ToUpper(strings.TrimSpace(typeName)) + t = strings.TrimSuffix(strings.TrimSuffix(t, " UNSIGNED"), " SIGNED") + t = strings.TrimSpace(t) + base := t + if idx := strings.IndexByte(t, '('); idx > 0 { + base = t[:idx] + } + switch base { + case "INT", "INTEGER", "BIGINT", "TINYINT", "SMALLINT", "MEDIUMINT", + "INT2", "INT4", "INT8", + "SERIAL", "BIGSERIAL", "SMALLSERIAL", + "YEAR", "BOOL", "BOOLEAN", "OID": + return dbTypeInteger + case "DECIMAL", "NUMERIC", "NEWDECIMAL": + return dbTypeDecimal + case "FLOAT", "DOUBLE", "REAL", "FLOAT4", "FLOAT8", "DOUBLE PRECISION": + return dbTypeFloat + } + if strings.Contains(base, "INT") { + return dbTypeInteger + } + if strings.Contains(base, "DECIMAL") || strings.Contains(base, "NUMERIC") { + return dbTypeDecimal + } + if strings.Contains(base, "DOUBLE") || strings.Contains(base, "FLOAT") { + return dbTypeFloat + } + return dbTypeUnknown +} + +// getDecimalScale 提取 DECIMAL/NUMERIC 精度,失败返回 -1 +func getDecimalScale(colType *sql.ColumnType, typeName string) int { + _, scale, ok := colType.DecimalSize() + if ok && scale >= 0 { + return int(scale) + } + // SQLite 不支持 DecimalSize,从类型名解析 "DECIMAL(10,2)" → 2 + upper := strings.ToUpper(typeName) + if idx := strings.Index(upper, ","); idx > 0 { + rest := upper[idx+1:] + if end := strings.IndexByte(rest, ')'); end > 0 { + if s, err := strconv.Atoi(strings.TrimSpace(rest[:end])); err == nil { + return s + } + } + } + return -1 +} + +// getFloatScale 提取 FLOAT/DOUBLE 显式精度,无显式精度返回 -1 表示不截断 +func getFloatScale(colType *sql.ColumnType, typeName string) int { + _, scale, ok := colType.DecimalSize() + if ok && scale > 0 && scale < 20 { + return int(scale) + } + return -1 +} + +// fixFloatValue 对 float64 值做类型感知精度修正 +func fixFloatValue(f float64, category int, scale int) interface{} { + if math.IsNaN(f) || math.IsInf(f, 0) { + return float64(0) + } + switch category { + case dbTypeInteger: + return int64(math.Round(f)) + case dbTypeDecimal: + return roundFloat(f, scale) + case dbTypeFloat: + return roundFloat(f, scale) + default: + return f + } +} + +// roundFloat 使用 strconv 精确舍入,避免 f*pow 中间值产生精度误差 +func roundFloat(f float64, scale int) interface{} { + if math.IsNaN(f) || math.IsInf(f, 0) { + return float64(0) + } + if scale == 0 { + // DECIMAL(10,0) 明确是整数 + return math.Round(f) + } + if scale < 0 { + // 精度未知,不修正,原样返回 + return f + } + s := strconv.FormatFloat(f, 'f', scale, 64) + v, err := strconv.ParseFloat(s, 64) + if err != nil { + return f + } + return v +} + +// convertBytes 对 []byte 类型的列做类型感知解析 +// MySQL 文本协议和 PG 的 DECIMAL/NUMERIC 以 []byte 返回,需要按列类型解析为正确 Go 类型 +func convertBytes(raw []byte, category int, scale int) interface{} { + s := string(raw) + switch category { + case dbTypeInteger: + if n, err := strconv.ParseInt(s, 10, 64); err == nil { + return n + } + if f, err := strconv.ParseFloat(s, 64); err == nil { + return int64(math.Round(f)) + } + return s + case dbTypeDecimal: + if f, err := strconv.ParseFloat(s, 64); err == nil { + return roundFloat(f, scale) + } + return s + case dbTypeFloat: + if f, err := strconv.ParseFloat(s, 64); err == nil { + return roundFloat(f, scale) + } + return s + default: + return s + } +} + // Row 数据库数据解析 func (that *HoTimeDB) Row(resl *sql.Rows) []Map { + defer resl.Close() + dest := make([]Map, 0) strs, _ := resl.Columns() + colTypes, _ := resl.ColumnTypes() - for i := 0; resl.Next(); i++ { - lis := make(Map, 0) - a := make([]interface{}, len(strs)) + colCount := len(strs) + categories := make([]int, colCount) + scales := make([]int, colCount) + if colTypes != nil { + for j := 0; j < len(colTypes) && j < colCount; j++ { + typeName := colTypes[j].DatabaseTypeName() + categories[j] = classifyDBType(typeName) + switch categories[j] { + case dbTypeDecimal: + scales[j] = getDecimalScale(colTypes[j], typeName) + case dbTypeFloat: + scales[j] = getFloatScale(colTypes[j], typeName) + } + } + } - b := make([]interface{}, len(a)) - for j := 0; j < len(a); j++ { + for resl.Next() { + lis := make(Map, colCount) + a := make([]interface{}, colCount) + b := make([]interface{}, colCount) + for j := 0; j < colCount; j++ { b[j] = &a[j] } - err := resl.Scan(b...) - if err != nil { + if err := resl.Scan(b...); 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] // 取实际类型 + for j := 0; j < colCount; j++ { + val := a[j] + if val == nil { + lis[strs[j]] = nil + continue + } + switch v := val.(type) { + case []byte: + lis[strs[j]] = convertBytes(v, categories[j], scales[j]) + case float64: + lis[strs[j]] = fixFloatValue(v, categories[j], scales[j]) + case float32: + lis[strs[j]] = fixFloatValue(float64(v), categories[j], scales[j]) + default: + lis[strs[j]] = val } } - dest = append(dest, lis) } - + if err := resl.Err(); err != nil { + that.LastErr.SetError(err) + } return dest } diff --git a/db/row_test.go b/db/row_test.go new file mode 100644 index 0000000..e2d4798 --- /dev/null +++ b/db/row_test.go @@ -0,0 +1,245 @@ +package db + +import ( + "math" + "testing" +) + +// ───────────────────────────────────────────── +// classifyDBType +// ───────────────────────────────────────────── +func TestClassifyDBType(t *testing.T) { + cases := []struct { + typeName string + want int + }{ + // MySQL 整数类型 + {"INT", dbTypeInteger}, + {"TINYINT", dbTypeInteger}, + {"SMALLINT", dbTypeInteger}, + {"MEDIUMINT", dbTypeInteger}, + {"BIGINT", dbTypeInteger}, + {"INT UNSIGNED", dbTypeInteger}, + {"BIGINT UNSIGNED", dbTypeInteger}, + {"TINYINT(1)", dbTypeInteger}, + {"YEAR", dbTypeInteger}, + {"BOOL", dbTypeInteger}, + {"BOOLEAN", dbTypeInteger}, + + // PostgreSQL 整数类型 + {"INT2", dbTypeInteger}, + {"INT4", dbTypeInteger}, + {"INT8", dbTypeInteger}, + {"SERIAL", dbTypeInteger}, + {"BIGSERIAL", dbTypeInteger}, + {"SMALLSERIAL", dbTypeInteger}, + {"OID", dbTypeInteger}, + + // DECIMAL / NUMERIC + {"DECIMAL", dbTypeDecimal}, + {"DECIMAL(10,2)", dbTypeDecimal}, + {"NUMERIC", dbTypeDecimal}, + {"NUMERIC(8,4)", dbTypeDecimal}, + {"NEWDECIMAL", dbTypeDecimal}, + + // FLOAT / DOUBLE + {"FLOAT", dbTypeFloat}, + {"DOUBLE", dbTypeFloat}, + {"REAL", dbTypeFloat}, + {"FLOAT4", dbTypeFloat}, + {"FLOAT8", dbTypeFloat}, + {"DOUBLE PRECISION", dbTypeFloat}, + + // 未知类型 + {"VARCHAR", dbTypeUnknown}, + {"TEXT", dbTypeUnknown}, + {"DATETIME", dbTypeUnknown}, + {"TIMESTAMP", dbTypeUnknown}, + {"BLOB", dbTypeUnknown}, + {"", dbTypeUnknown}, + } + + for _, c := range cases { + t.Run(c.typeName, func(t *testing.T) { + got := classifyDBType(c.typeName) + if got != c.want { + t.Errorf("classifyDBType(%q) = %d, want %d", c.typeName, got, c.want) + } + }) + } +} + +// ───────────────────────────────────────────── +// fixFloatValue +// ───────────────────────────────────────────── +func TestFixFloatValue(t *testing.T) { + t.Run("integer category: round to int64", func(t *testing.T) { + // 这是核心场景:float64 精度噪声导致整数变为 1.9999... + got := fixFloatValue(1.9999999999999998, dbTypeInteger, 0) + if got != int64(2) { + t.Errorf("got %v (%T), want int64(2)", got, got) + } + }) + + t.Run("integer category: 2.0000000000001 rounds to 2", func(t *testing.T) { + got := fixFloatValue(2.0000000000001, dbTypeInteger, 0) + if got != int64(2) { + t.Errorf("got %v (%T), want int64(2)", got, got) + } + }) + + t.Run("decimal category: scale=2 roundtrip precision", func(t *testing.T) { + got := fixFloatValue(1.1999999999999999, dbTypeDecimal, 2) + f, ok := got.(float64) + if !ok { + t.Fatalf("expected float64, got %T", got) + } + if f != 1.2 { + t.Errorf("got %v, want 1.2", f) + } + }) + + t.Run("decimal category: scale=2, 1.20000000001 -> 1.2", func(t *testing.T) { + got := fixFloatValue(1.20000000001, dbTypeDecimal, 2) + f := got.(float64) + if f != 1.2 { + t.Errorf("got %v, want 1.2", f) + } + }) + + t.Run("decimal category: scale=-1 (unknown), no modification", func(t *testing.T) { + got := fixFloatValue(1.23456789, dbTypeDecimal, -1) + if got != 1.23456789 { + t.Errorf("got %v, want 1.23456789", got) + } + }) + + t.Run("decimal category: scale=0, round to whole number", func(t *testing.T) { + got := fixFloatValue(2.7, dbTypeDecimal, 0) + if got != float64(3) { + t.Errorf("got %v (%T), want float64(3)", got, got) + } + }) + + t.Run("float category: scale=-1 no modification", func(t *testing.T) { + got := fixFloatValue(3.14159265, dbTypeFloat, -1) + if got != 3.14159265 { + t.Errorf("got %v, want 3.14159265", got) + } + }) + + t.Run("unknown category: passthrough", func(t *testing.T) { + got := fixFloatValue(99.9, dbTypeUnknown, -1) + if got != 99.9 { + t.Errorf("got %v, want 99.9", got) + } + }) + + t.Run("NaN -> 0", func(t *testing.T) { + got := fixFloatValue(math.NaN(), dbTypeInteger, 0) + if got != float64(0) { + t.Errorf("got %v, want 0", got) + } + }) + + t.Run("+Inf -> 0", func(t *testing.T) { + got := fixFloatValue(math.Inf(1), dbTypeDecimal, 2) + if got != float64(0) { + t.Errorf("got %v, want 0", got) + } + }) +} + +// ───────────────────────────────────────────── +// roundFloat +// ───────────────────────────────────────────── +func TestRoundFloat(t *testing.T) { + t.Run("scale=2: 1.19999... -> 1.2", func(t *testing.T) { + got := roundFloat(1.1999999999999999, 2) + if got != float64(1.2) { + t.Errorf("got %v, want 1.2", got) + } + }) + + t.Run("scale=2: 1.20000...1 -> 1.2", func(t *testing.T) { + got := roundFloat(1.20000000001, 2) + if got != float64(1.2) { + t.Errorf("got %v, want 1.2", got) + } + }) + + t.Run("scale=0: round to integer float", func(t *testing.T) { + got := roundFloat(2.5, 0) + if got != float64(3) { + t.Errorf("got %v, want 3.0", got) + } + }) + + t.Run("scale=-1: passthrough unchanged", func(t *testing.T) { + got := roundFloat(1.23456789, -1) + if got != 1.23456789 { + t.Errorf("got %v, want 1.23456789", got) + } + }) + + t.Run("NaN -> 0", func(t *testing.T) { + got := roundFloat(math.NaN(), 2) + if got != float64(0) { + t.Errorf("got %v, want 0", got) + } + }) +} + +// ───────────────────────────────────────────── +// convertBytes +// ───────────────────────────────────────────── +func TestConvertBytes(t *testing.T) { + t.Run("integer category: '2' -> int64(2)", func(t *testing.T) { + got := convertBytes([]byte("2"), dbTypeInteger, 0) + if got != int64(2) { + t.Errorf("got %v (%T), want int64(2)", got, got) + } + }) + + t.Run("integer category: '2.00' -> int64(2)", func(t *testing.T) { + got := convertBytes([]byte("2.00"), dbTypeInteger, 0) + if got != int64(2) { + t.Errorf("got %v (%T), want int64(2)", got, got) + } + }) + + t.Run("decimal category: '1.20' scale=2 -> float64(1.2)", func(t *testing.T) { + got := convertBytes([]byte("1.20"), dbTypeDecimal, 2) + if got != float64(1.2) { + t.Errorf("got %v (%T), want float64(1.2)", got, got) + } + }) + + t.Run("decimal category: '0.00' scale=2 -> float64(0)", func(t *testing.T) { + got := convertBytes([]byte("0.00"), dbTypeDecimal, 2) + if got != float64(0) { + t.Errorf("got %v (%T), want float64(0)", got, got) + } + }) + + t.Run("float category: '3.14' scale=-1 -> float64(3.14)", func(t *testing.T) { + got := convertBytes([]byte("3.14"), dbTypeFloat, -1) + if got != float64(3.14) { + t.Errorf("got %v (%T), want float64(3.14)", got, got) + } + }) + + t.Run("unknown category: passthrough as string", func(t *testing.T) { + got := convertBytes([]byte("hello"), dbTypeUnknown, 0) + if got != "hello" { + t.Errorf("got %v (%T), want 'hello'", got, got) + } + }) + + t.Run("integer category: invalid bytes -> string fallback", func(t *testing.T) { + got := convertBytes([]byte("abc"), dbTypeInteger, 0) + if got != "abc" { + t.Errorf("got %v (%T), want 'abc'", got, got) + } + }) +} diff --git a/log/logrus.go b/log/logrus.go index 1a31eea..6ffb53a 100644 --- a/log/logrus.go +++ b/log/logrus.go @@ -20,7 +20,12 @@ func GetLog(path string, showCodeLine bool) *log.Logger { } loger := log.New() - loger.SetFormatter(&log.TextFormatter{}) + loger.SetFormatter(&log.TextFormatter{ + ForceColors: true, + FullTimestamp: true, + TimestampFormat: "2006-01-02 15:04:05", + DisableLevelTruncation: true, + }) loger.AddHook(&hook) return loger }