Files
MyGoNavi/internal/db/query_value_stringer_test.go
Syngnat 0c10be0aa2 🐛 fix(db): 修复大整数与 IP 列显示丢值、会话路径方言退化、MariaDB 漏影响行数校验
- normalizeQueryValue 的 Struct 分支补「指针接收者 Stringer」探测:big.Int 的 String() 定义在
  *big.Int 上,clickhouse-go 对 Int128/UInt128/Int256/UInt256 列返回 big.Int 值,原先落到
  fmt.Sprintf("%v") 变成内部结构体转储,前端与 CSV/JSON/XLSX 导出的原始数值完全不可恢复
- Slice 分支对具名字节切片先试 Stringer、再回退 bytesToDisplayValue:net.IP 是具名 []byte,
  不匹配上层的 case []byte,原先被展开成 [192,168,0,1] 而非 "192.168.0.1"
- MySQL/MariaDB 的 OpenSessionExecer 补回扫描方言:原先传空串使 DATE 列在会话路径
  (流式导出、SQL 编辑器托管事务)被格式化成 RFC3339,与网格直查的 2006-01-02 不一致
- MariaDB.ApplyChanges 的 DELETE/UPDATE 补 requireSingleRowAffected(与 mysql_impl 对齐):
  原先丢弃 sql.Result,无主键表上一次单元格编辑可能静默改写多行,或 0 行命中仍提示提交成功
- 补 5 项回归测试,并断言既有的 MySQL BIT 位掩码语义未被破坏

未修:query_value.go 的 PG bit/varbit 文本位串被当作二进制位掩码解释。该缺陷需要给 6 个
PG 系驱动都补 scanDialect 才能可靠区分方言;仅按字节内容启发式会把 MySQL BIT(8) 存值 48
(字节 0x30 即 ASCII '0')误显示为 "0",属于用一个静默错误换另一个,故留待单独处理。
2026-07-26 20:52:58 +08:00

90 lines
3.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package db
import (
"math/big"
"net"
"testing"
)
// TestNormalizeQueryValuePreservesBigIntDecimalValue 覆盖指针接收者 Stringer 的探测。
//
// 回归背景reflect.Struct 分支原先只断言值接收者的 fmt.Stringer而 big.Int 的 String()
// 定义在 *big.Int 上。clickhouse-go 对 Int128/UInt128/Int256/UInt256 列返回 big.Int **值**
// 因此会落到 fmt.Sprintf("%v"),前端与 CSV/JSON/XLSX 导出得到的都是
// `{false [18446744073709551615 9223372036854775807]}` 这类内部结构体转储,原始数值不可恢复。
func TestNormalizeQueryValuePreservesBigIntDecimalValue(t *testing.T) {
// Int128 上界170141183460469231731687303715884105727
huge, ok := new(big.Int).SetString("170141183460469231731687303715884105727", 10)
if !ok {
t.Fatal("构造 big.Int 失败")
}
got := normalizeQueryValue(*huge)
want := "170141183460469231731687303715884105727"
if got != want {
t.Fatalf("big.Int 值 = %v (%T),期望 %q", got, got, want)
}
// 负数与小数值同样应得到精确十进制串。
if got := normalizeQueryValue(*big.NewInt(-42)); got != "-42" {
t.Errorf("big.NewInt(-42) = %v (%T),期望 \"-42\"", got, got)
}
// 指针形式本就满足值接收者断言路径,结果必须一致。
if got := normalizeQueryValue(huge); got != want {
t.Errorf("*big.Int = %v期望 %q", got, want)
}
}
// TestNormalizeQueryValueRendersNamedByteSlicesReadably 覆盖具名字节切片。
//
// 回归背景reflect.Slice 分支原先在任何 Stringer 判定之前就无条件展开为 []interface{}
// 而 net.IP 是 `type IP []byte`(具名类型,不匹配上层的 case []byte
// clickhouse-go 对 IPv4/IPv6 列返回 net.IP于是前端显示 [192,168,0,1] 而非 "192.168.0.1"
// CSV/JSON 导出落盘的也是数组,无法用于回灌或比对。
func TestNormalizeQueryValueRendersNamedByteSlicesReadably(t *testing.T) {
if got := normalizeQueryValue(net.ParseIP("192.168.0.1")); got != "192.168.0.1" {
t.Errorf("IPv4 = %v (%T),期望 \"192.168.0.1\"", got, got)
}
if got := normalizeQueryValue(net.ParseIP("::1")); got != "::1" {
t.Errorf("IPv6 = %v (%T),期望 \"::1\"", got, got)
}
}
// namedByteSliceWithoutStringer 是没有 String() 的具名字节切片,
// 应回退到与普通 []byte 相同的展示逻辑,而不是被展开成数字数组。
type namedByteSliceWithoutStringer []byte
func TestNormalizeQueryValueFallsBackToBytesDisplayForNamedByteSlices(t *testing.T) {
got := normalizeQueryValue(namedByteSliceWithoutStringer("hello"))
if got != "hello" {
t.Fatalf("具名字节切片 = %v (%T),期望 \"hello\"", got, got)
}
}
// TestNormalizeQueryValueKeepsNonByteSliceExpansion 非字节切片必须仍然递归展开。
func TestNormalizeQueryValueKeepsNonByteSliceExpansion(t *testing.T) {
got := normalizeQueryValue([]int32{1, 2, 3})
items, ok := got.([]interface{})
if !ok {
t.Fatalf("[]int32 未被展开为 []interface{},实际 %T", got)
}
if len(items) != 3 {
t.Fatalf("展开长度 = %d期望 3", len(items))
}
}
// TestBitLikeBytesKeepMySQLBitmaskSemantics 守住既有的 MySQL BIT 位掩码语义,
// 确认本次改动没有把 BIT 列的字节当成具名切片或文本处理。
func TestBitLikeBytesKeepMySQLBitmaskSemantics(t *testing.T) {
if got := normalizeQueryValueWithDBType([]byte{0x00}, "BIT"); got != int64(0) {
t.Errorf("BIT 0x00 = %v (%T),期望 int64(0)", got, got)
}
if got := normalizeQueryValueWithDBType([]byte{0x01}, "BIT"); got != int64(1) {
t.Errorf("BIT 0x01 = %v (%T),期望 int64(1)", got, got)
}
if got := normalizeQueryValueWithDBType([]byte{0x01, 0x02}, "BIT VARYING"); got != int64(258) {
t.Errorf("BIT 0x0102 = %v (%T),期望 int64(258)", got, got)
}
}