[QUESTION] Decoding a column of type `Array(Tuple(String, String))`
What is the correct way to decode a column whose ClickHouse type is Array(Tuple(String, String)) and map it to a Go [][]string?
For example, given the following SQL query:
SELECT [('one', 'two'), ('three', 'four')] AS arr;
...how do I replace the TODO comment with something that will map each tuple to a [][]string?
func arrayOfTuples() {
ctx := context.Background()
c, err := ch.Dial(ctx, ch.Options{Address: "localhost:9000"})
if err != nil {
panic(err)
}
arr := new(proto.ColArr[proto.ColTuple])
if err := c.Do(ctx, ch.Query{
Body: "SELECT [('one', 'two'), ('three', 'four')] AS arr",
Result: proto.Results{
{Name: "arr", Data: arr},
},
OnResult: func(ctx context.Context, b proto.Block) error {
// TODO: Decode b and map its values to `[][]string`
return nil
},
}); err != nil {
panic(err)
}
}
The difficulty is in figuring out how to allocate a proto.ColumnOf[proto.ColTuple], where ColTuple is of type tuple(string, string), and how to properly decode a block into it.
The equivalent clickhouse-go code works smoothly:
func clickhouseGoArrayOfTuples() {
conn, err := clickhousego.Open(&clickhousego.Options{
Addr: []string{"127.0.0.1:9000"},
Auth: clickhousego.Auth{
Database: "default",
Username: "default",
Password: "",
},
})
if err != nil {
panic(err)
}
rows, err := conn.Query(context.Background(), "SELECT [('one', 'two'), ('three', 'four')] AS arr")
if err != nil {
panic(err)
}
for rows.Next() {
var arr [][]string
if err := rows.Scan(&arr); err != nil {
panic(err)
}
fmt.Println(arr)
}
}
Hi @shteinitz
We got the same ask at https://github.com/ClickHouse/ch-go/issues/227
ch-go doesn't provide an interface to interact with Array( Tuple() ). Tuple is implemented a bit differently compared to other column types.
When supported, interacting with the result would look like this:
package main
import (
"context"
"fmt"
"github.com/ClickHouse/ch-go"
"github.com/ClickHouse/ch-go/proto"
"github.com/go-faster/errors"
)
func arrayOfTuples() ([][]string, error) {
ctx := context.Background()
c, err := ch.Dial(ctx, ch.Options{Address: "localhost:9000"})
if err != nil {
panic(err)
}
arr := &proto.ColArr[[]string]{
Data: &proto.ColTuple{&proto.ColStr{}, &proto.ColStr{}},
}
if err := c.Do(ctx, ch.Query{
Body: "SELECT [('one', 'two'), ('three', 'four')] AS arr",
Result: proto.Results{
{Name: "arr", Data: &arr},
},
}); err != nil {
return nil, err
}
if arr.Rows() != 1 {
return nil, errors.New("expected one row")
}
var result [][]string
for _, row := range arr.Data.Row(0) {
var tuple []string
for _, col := range row.(proto.ColTuple) {
switch col := col.(type) {
case *proto.ColStr:
tuple = append(tuple, col.First())
default:
return nil, errors.Errorf("unexpected column type in tuple: %T, expected proto.ColStr", col)
}
}
result = append(result, tuple)
}
return result, nil
}
func main() {
res, err := arrayOfTuples()
if err != nil {
panic(err)
}
fmt.Println(res)
}
The difficulty today I think comes from the fact that generic types cannot be variadic, but the Tuple type needs to vary in length to satisfy Append for ColumnOf. One fix here would be to add an AppendAny method to any of the ColumnOf types, define an Appendable interface which resembles the ColumnOf interface, but is not generic:
type AppendableColumn interface {
Column
AppendAny(v any)
AppendArrAny(v []any)
Row(i int) any
}
We could define another Array type (ColArrRaw) which contains data within Appendable. Then, it would be relatively simple to implement AppendAny on the Tuple type?
+1. Are there any plans to support inserting arrays of tuples in the near future? ch-go is one of the few (maybe only) actively maintained low-level Go client libraries, so better type support would be amazing. I'm looking at migrating away from chconn and this is the only blocker.