Go查询CH 数据 tuple 转数组
      
        
          Go 查询 ClickHouse
      业务场景:需要使用 Golang 从 CilckHouse 集群中查询数据,查询的 SQL 以模版的形式存放在 Postgresql 数据库中.
使用的 driver 是 sqlx
- 使用CH 在私有集群中,报漏的端口只有 
http 的 8123 所以需要更改拨号协议 
- 另外如果 
ClickHouse 是必要的依赖服务,每次获取查询对象 需要先检查一下 Ping() 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
   |  type ClickHouse struct {     Host         string     Port         int     User         string     Password     string     DBName       string     ReadTimeout  uint     WriteTimeout uint     Parameters   string  }
  func (a ClickHouse) DSN() string {          return fmt.Sprintf("http://%s:%s@%s:%d/%s",  a.User, a.Password, a.Host, a.Port, a.DBName) }
  connect, err := sqlx.Open("clickhouse", "tcp://127.0.0.1:9000?compress=true&debug=true")
 
  checkErr(err) if err := connect.Ping(); err != nil {     if exception, ok := err.(*clickhouse.Exception); ok {         fmt.Printf("[%d] %s \n%s\n", exception.Code, exception.Message, exception.StackTrace)     } else {         fmt.Println(err)     }     return }
 
  | 
 
        
          Row 转 Map
      
- 因为查询的 SQL 模版较多, 不想定义对应的结构体 :smaile:
所以对查询数据做了一个 Row2Map 的处理,这样获取到的数据 直接转换为 JSON 输出到前端
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
   |  type QueryResult map[string]interface{}
  func RowsToMap(rows *sqlx.Rows) []QueryResult {     var list = []QueryResult{} 
      if rows == nil {         return list     }     ts, _ := rows.ColumnTypes()     columns, _ := rows.Columns()     columnLength := len(columns)     cache := make([]interface{}, columnLength)      for index, _ := range cache {                       var a interface{}         cache[index] = &a     }     for rows.Next() {         _ = rows.Scan(cache...)         item := make(QueryResult)         for idex, data := range cache {             val := *data.(*interface{})              item[columns[idex]] = GenarateTuplesToArray(val, ts[idex])         }         list = append(list, item)     }     return list }
 
 
  | 
 
        
          Tuple 转 数组
      
- 在返回的数据中出现 前端 Javascrip 取值异常的情况,因为先前的数据接口已经有一版本 nodejs 完成的.
所以前端的 数据处理部分已经是完成了的.现在出现了出局结构不一致的情况,具体数据不同体现在: 
1 2 3
   | [{"11111","asdasd,aaaa,aaa"}] => nodejs => [["11111","asdasd,aaaa,aaa"]] 
  [{"11111","asdasd,aaaa,aaa"}] => golang => [{field0:"11111",field1:"asdasd,aaaa,aaa"}]
  | 
 
最初以为是 SQL 变更了: 排查后发现并没有修改 SQL.但是发现了查询有使用 Tuple
具体对比数据数据结构后 确认是 由于 node/go 转换元组 Tuple 的操作不同产生的
1 2 3 4 5 6 7 8 9
   | // anwsers 字段的查询Tuple arrayFilter(   (i,type) -> (type IN (1, 28)),   arrayMap(rdata -> (     rdata, concat(dictGet('ipip4', 'country', tuple(IPv4StringToNum(rdata))),      If(province = '','',','),dictGet('ipip4','province',tuple(IPv4StringToNum(rdata))) AS province,     If(city = '','',','),dictGet('ipip4','city',tuple(IPv4StringToNum(rdata))) AS city)),     res.`responseAnswerRrs.rdata`),   res.`responseAnswerRrs.type`) AS answers
   | 
 
打印详细的查询结果 及 元组字段 ‘anwsers’ 格式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
   |  ts, _ := rows.ColumnTypes() 
  columnTypes := ts[idex] 
 
  columnTypes.DatabaseTypeName() = "Array(Tuple(String, String))"  columnTypes.ScanType() = []struct {   Field0 string   Field1 string }
 
 
  type ColumnType struct {   name string 
    hasNullable       bool   hasLength         bool   hasPrecisionScale bool
    nullable     bool   length       int64   databaseType string   precision    int64   scale        int64   scanType     reflect.Type }
 
  [{"11111","asdasd,aaaa,aaa"}]
 
 
  | 
 
最后解决方案是: 修改 row2map 函数,将 Array(Tuple(String, String)) 手动转换为 [][]string,
这样 json.Marshal(d) 处理的时候就会保持原来的数组格式
[{"11111","asdasd,aaaa,aaa"}] => golang => [["11111","asdasd,aaaa,aaa"]]
        
          Code
      1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
   |  func GenarateTuplesToArray(val interface{}, columnTypes *sql.ColumnType) interface{} {   if columnTypes.DatabaseTypeName() == "Array(Tuple(String, String))" {     d := val.([]struct {       Field0 string       Field1 string     })      t := make([][]string, len(d))     for k, v := range d {       t[k] = []string{v.Field0, v.Field1}      }     return t   }   return val }
 
  |