mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-05-12 01:49:42 +08:00
* feat(http-tunnel): 支持独立 HTTP 隧道连接并覆盖多数据源 refs #168 * fix(kingbase-data-grid): 修复金仓打开表卡顿并降低对象渲染开销 refs #178 * fix(kingbase-transaction): 修复金仓事务提交重复引号导致语法错误 refs #176 * fix(driver-agent): 修复老版本 Win10 升级后金仓驱动代理启动失败 refs #177 * chore(ci): 新增手动触发的 macOS 测试构建工作流 * chore(ci): 允许测试工作流在当前分支自动触发 * fix(query-editor): 修复 SQL 编辑中光标随机跳到末尾 refs #185 * feat(data-sync): 增加差异 SQL 预览能力便于审核 refs #174 * fix(clickhouse-connect): 自动识别并回退 HTTP/Native 协议连接 refs #181 * fix(oracle-metadata): 修复视图与函数加载按 schema 过滤异常 refs #155 * fix(dameng-databases): 修复显示全部库时数据库列表不完整 refs #154 * fix(connection,db-list): 统一处理空列表返回并修复达梦连接测试报错 refs #157
266 lines
5.7 KiB
Go
266 lines
5.7 KiB
Go
package db
|
||
|
||
import (
|
||
"encoding/hex"
|
||
"encoding/json"
|
||
"fmt"
|
||
"math/big"
|
||
"reflect"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
"unicode"
|
||
"unicode/utf8"
|
||
)
|
||
|
||
const (
|
||
jsMaxSafeInteger int64 = 9007199254740991
|
||
jsMinSafeInteger int64 = -9007199254740991
|
||
jsMaxSafeUint uint64 = 9007199254740991
|
||
)
|
||
|
||
var (
|
||
jsMaxSafeBigInt = big.NewInt(jsMaxSafeInteger)
|
||
jsMinSafeBigInt = big.NewInt(jsMinSafeInteger)
|
||
)
|
||
|
||
// normalizeQueryValue normalizes driver-returned values for UI/JSON transport.
|
||
// 当前主要处理 []byte:如果是可读文本则转为 string,否则转为十六进制字符串,避免前端出现“空白值”。
|
||
func normalizeQueryValue(v interface{}) interface{} {
|
||
return normalizeQueryValueWithDBType(v, "")
|
||
}
|
||
|
||
func normalizeQueryValueWithDBType(v interface{}, databaseTypeName string) interface{} {
|
||
if b, ok := v.([]byte); ok {
|
||
return bytesToDisplayValue(b, databaseTypeName)
|
||
}
|
||
return normalizeCompositeQueryValue(v)
|
||
}
|
||
|
||
func normalizeCompositeQueryValue(v interface{}) interface{} {
|
||
if v == nil {
|
||
return nil
|
||
}
|
||
|
||
switch typed := v.(type) {
|
||
case []interface{}:
|
||
items := make([]interface{}, len(typed))
|
||
for i, item := range typed {
|
||
items[i] = normalizeQueryValue(item)
|
||
}
|
||
return items
|
||
case map[string]interface{}:
|
||
out := make(map[string]interface{}, len(typed))
|
||
for key, value := range typed {
|
||
out[key] = normalizeQueryValue(value)
|
||
}
|
||
return out
|
||
case json.Number:
|
||
return normalizeJSONNumberForJS(typed)
|
||
}
|
||
|
||
rv := reflect.ValueOf(v)
|
||
switch rv.Kind() {
|
||
case reflect.Pointer:
|
||
if rv.IsNil() {
|
||
return nil
|
||
}
|
||
return normalizeQueryValue(rv.Elem().Interface())
|
||
case reflect.Map:
|
||
if rv.IsNil() {
|
||
return nil
|
||
}
|
||
out := make(map[string]interface{}, rv.Len())
|
||
iter := rv.MapRange()
|
||
for iter.Next() {
|
||
out[mapKeyToString(iter.Key().Interface())] = normalizeQueryValue(iter.Value().Interface())
|
||
}
|
||
return out
|
||
case reflect.Slice, reflect.Array:
|
||
// []byte 在上层已单独处理,这里保留对其它切片/数组的递归规整。
|
||
if rv.Kind() == reflect.Slice && rv.IsNil() {
|
||
return nil
|
||
}
|
||
size := rv.Len()
|
||
items := make([]interface{}, size)
|
||
for i := 0; i < size; i++ {
|
||
items[i] = normalizeQueryValue(rv.Index(i).Interface())
|
||
}
|
||
return items
|
||
case reflect.Struct:
|
||
// 部分驱动(如 Kingbase)会返回复杂结构体值,直接透传会导致前端渲染和比较开销激增。
|
||
// 统一降级为可读字符串,避免对象深层序列化触发 UI 卡顿。
|
||
if tm, ok := v.(time.Time); ok {
|
||
return tm.Format(time.RFC3339Nano)
|
||
}
|
||
if stringer, ok := v.(fmt.Stringer); ok {
|
||
return stringer.String()
|
||
}
|
||
return fmt.Sprintf("%v", v)
|
||
default:
|
||
return normalizeUnsafeIntegerForJS(rv, v)
|
||
}
|
||
}
|
||
|
||
func normalizeJSONNumberForJS(n json.Number) interface{} {
|
||
text := strings.TrimSpace(n.String())
|
||
if text == "" {
|
||
return ""
|
||
}
|
||
|
||
if integer, ok := parseJSONInteger(text); ok {
|
||
if integer.Cmp(jsMaxSafeBigInt) > 0 || integer.Cmp(jsMinSafeBigInt) < 0 {
|
||
return text
|
||
}
|
||
return integer.Int64()
|
||
}
|
||
|
||
if f, err := n.Float64(); err == nil {
|
||
return f
|
||
}
|
||
return text
|
||
}
|
||
|
||
func parseJSONInteger(text string) (*big.Int, bool) {
|
||
if text == "" {
|
||
return nil, false
|
||
}
|
||
start := 0
|
||
if text[0] == '+' || text[0] == '-' {
|
||
if len(text) == 1 {
|
||
return nil, false
|
||
}
|
||
start = 1
|
||
}
|
||
for i := start; i < len(text); i++ {
|
||
if text[i] < '0' || text[i] > '9' {
|
||
return nil, false
|
||
}
|
||
}
|
||
value, ok := new(big.Int).SetString(text, 10)
|
||
if !ok {
|
||
return nil, false
|
||
}
|
||
return value, true
|
||
}
|
||
|
||
func mapKeyToString(key interface{}) string {
|
||
if key == nil {
|
||
return "null"
|
||
}
|
||
if s, ok := key.(string); ok {
|
||
return s
|
||
}
|
||
return fmt.Sprintf("%v", key)
|
||
}
|
||
|
||
func bytesToDisplayValue(b []byte, databaseTypeName string) interface{} {
|
||
if b == nil {
|
||
return nil
|
||
}
|
||
if len(b) == 0 {
|
||
return ""
|
||
}
|
||
|
||
dbType := strings.ToUpper(strings.TrimSpace(databaseTypeName))
|
||
if isBitLikeDBType(dbType) {
|
||
if u, ok := bytesToUint64(b); ok {
|
||
// JS number precision is limited; keep large bitmasks as string.
|
||
if u <= jsMaxSafeUint {
|
||
return int64(u)
|
||
}
|
||
return fmt.Sprintf("%d", u)
|
||
}
|
||
}
|
||
|
||
if utf8.Valid(b) {
|
||
s := string(b)
|
||
if isMostlyPrintable(s) {
|
||
return s
|
||
}
|
||
}
|
||
|
||
// Fallback: some drivers return BIT(1) as []byte{0} / []byte{1} without type info.
|
||
if dbType == "" && len(b) == 1 && (b[0] == 0 || b[0] == 1) {
|
||
return int64(b[0])
|
||
}
|
||
|
||
return bytesToReadableString(b)
|
||
}
|
||
|
||
func bytesToReadableString(b []byte) interface{} {
|
||
if b == nil {
|
||
return nil
|
||
}
|
||
if len(b) == 0 {
|
||
return ""
|
||
}
|
||
return "0x" + hex.EncodeToString(b)
|
||
}
|
||
|
||
func isBitLikeDBType(typeName string) bool {
|
||
if typeName == "" {
|
||
return false
|
||
}
|
||
switch typeName {
|
||
case "BIT", "VARBIT":
|
||
return true
|
||
default:
|
||
}
|
||
return strings.HasPrefix(typeName, "BIT")
|
||
}
|
||
|
||
func bytesToUint64(b []byte) (uint64, bool) {
|
||
if len(b) == 0 || len(b) > 8 {
|
||
return 0, false
|
||
}
|
||
var u uint64
|
||
for _, v := range b {
|
||
u = (u << 8) | uint64(v)
|
||
}
|
||
return u, true
|
||
}
|
||
|
||
func normalizeUnsafeIntegerForJS(rv reflect.Value, original interface{}) interface{} {
|
||
switch rv.Kind() {
|
||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||
n := rv.Int()
|
||
if n > jsMaxSafeInteger || n < jsMinSafeInteger {
|
||
return strconv.FormatInt(n, 10)
|
||
}
|
||
return original
|
||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||
u := rv.Uint()
|
||
if u > jsMaxSafeUint {
|
||
return strconv.FormatUint(u, 10)
|
||
}
|
||
return original
|
||
default:
|
||
return original
|
||
}
|
||
}
|
||
|
||
func isMostlyPrintable(s string) bool {
|
||
if s == "" {
|
||
return true
|
||
}
|
||
|
||
total := 0
|
||
printable := 0
|
||
for _, r := range s {
|
||
total++
|
||
switch r {
|
||
case '\n', '\r', '\t':
|
||
printable++
|
||
continue
|
||
default:
|
||
}
|
||
if unicode.IsPrint(r) {
|
||
printable++
|
||
}
|
||
}
|
||
|
||
// 允许少量不可见字符,避免把正常文本误判为二进制。
|
||
return printable*100 >= total*90
|
||
}
|