ch-go icon indicating copy to clipboard operation
ch-go copied to clipboard

[QUESTION] Decoding a column of type `Array(Tuple(String, String))`

Open shteinitz opened this issue 1 year ago • 3 comments

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)
	}
}

shteinitz avatar May 21 '24 02:05 shteinitz

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)
}

jkaflik avatar May 28 '24 07:05 jkaflik

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?

isaacd9 avatar Mar 11 '25 06:03 isaacd9

+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.

curt-hash avatar Oct 13 '25 16:10 curt-hash