ClickHouse / clickhouse-go

Golang driver for ClickHouse
Apache License 2.0
2.91k stars 560 forks source link

[Append]: converting time.Time to Date is unsupported #1409

Closed schalekamp closed 1 month ago

schalekamp commented 1 month ago

Observed

batch.Append works for the Date type, but batch.Column(n).Append does NOT work for the Date type

Expected behaviour

I would expect this to work for both

Code example

package main

import (
"context"
"log"
"time"

clickhouse "[github.com/ClickHouse/clickhouse-go/v2](http://github.com/ClickHouse/clickhouse-go/v2)"

)

func main() {
// Set up the connection to ClickHouse
conn, err := clickhouse.Open(&clickhouse.Options{
Addr: []string{"localhost:9000"},
Auth: clickhouse.Auth{
Database: "default",
Username: "default",
Password: "",
},
DialTimeout: 5 * time.Second,
Compression: &clickhouse.Compression{
Method: clickhouse.CompressionLZ4,
},
})

if err != nil {

    log.Fatalf("Failed to connect: %v", err)

}

ctx := context.Background()

// Create a table with a Date column

if err := conn.Exec(ctx, `CREATE TABLE IF NOT EXISTS example_dates (

    id         UInt32,

    name       String,

    birth_date Date,       -- This is a Date type

    is_active  Boolean

) ENGINE = Memory`); err != nil {

    log.Fatalf("Failed to create table: %v", err)

}

// Prepare a batch insert

batch, err := conn.PrepareBatch(ctx, "INSERT INTO example_dates")

if err != nil {

    log.Fatalf("Failed to prepare batch: %v", err)

}

// Insert values into the table, using only Date for the birth_date column

if err := batch.Append(uint32(1), "Alice", time.Date(1990, 6, 15, 0, 0, 0, 0, time.UTC), true); err != nil {

    log.Fatalf("Failed to append data: %v", err)

}

if err := batch.Append(uint32(2), "Bob", time.Date(1985, 12, 30, 0, 0, 0, 0, time.UTC), false); err != nil {

    log.Fatalf("Failed to append data: %v", err)

}

if err := batch.Column(2).Append(time.Date(1985, 12, 30, 0, 0, 0, 0, time.UTC)); err != nil {
        log.Fatalf("Failed to append data: %v", err)
    }

// Send the batch insert

if err := batch.Send(); err != nil {

    log.Fatalf("Failed to send batch: %v", err)

}

log.Println("Data inserted successfully with Date type!")

}

Error log

2024/09/17 14:38:35 Failed to append data: clickhouse [Append]: converting time.Time to Date is unsupported
achmad-dev commented 1 month ago

Hey mate,

I tried to create a test case for your issue here: 1409_test.go. After reviewing the codebase, I noticed something in the Append method for the Date type:


func (col *Date) Append(v any) (nulls []uint8, err error) {
    switch v := v.(type) {
    case []time.Time:
        for _, t := range v {
            col.col.Append(t)
        }
    case []*time.Time:
        nulls = make([]uint8, len(v))
        for i, v := range v {
            switch {
            case v != nil:
                col.col.Append(*v)
            default:
                nulls[i] = 1
                col.col.Append(time.Time{})
            }
        }
    case []sql.NullTime:
        nulls = make([]uint8, len(v))
        for i := range v {
            col.AppendRow(v[i])
        }
    case []*sql.NullTime:
        nulls = make([]uint8, len(v))
        for i := range v {
            if v[i] == nil {
                nulls[i] = 1
            }
            col.AppendRow(v[i])
        }
    case []string:
        nulls = make([]uint8, len(v))
        for i := range v {
            value, err := col.parseDate(v[i])
            if err != nil {
                return nil, err
            }
            col.col.Append(value)
        }
    case []*string:
        nulls = make([]uint8, len(v))
        for i := range v {
            if v[i] == nil || *v[i] == "" {
                nulls[i] = 1
                col.col.Append(time.Time{})
            } else {
                value, err := col.parseDate(*v[i])
                if err != nil {
                    return nil, err
                }
                col.col.Append(value)
            }
        }
    default:
        if valuer, ok := v.(driver.Valuer); ok {
            val, err := valuer.Value()
            if err != nil {
                return nil, &ColumnConverterError{
                    Op:   "Append",
                    To:   "Date",
                    From: fmt.Sprintf("%T", v),
                    Hint: "could not get driver.Valuer value",
                }
            }
            return col.Append(val)
        }
        return nil, &ColumnConverterError{
            Op:   "Append",
            To:   "Date",
            From: fmt.Sprintf("%T", v),
        }
    }
    return
}

It seems that the Append method expects a slice data type to work correctly. When I used a slice in the test case, it functioned as intended.

schalekamp commented 1 month ago

You are correct. In my naive understanding I did not expect this method to only take a slice. User error. :)