Skip to content

Unexpected behavior when scanning Nullable(Decimal64(2)) column #1730

@Nicholas-Horton

Description

@Nicholas-Horton

Observed

  1. Create a table with a single Nullable(Decimal64(2))
  2. Insert (NULL), (NULL), (NULL), (123.1), (NULL), (124.1), (NULL), (NULL), (NULL), (NULL)
  3. Read back the rows and observe the pointer not being reset when encountering a null value. The previous row's value is returned instead.
  4. NULL, NULL, NULL, 123.1, 123.1, 124.1, 124.1, 124.1, 124.1, 124.1 returned

Expected behaviour

The pointer should be reset when encountering NULL and not return the previous row's value.

Code example

Reproduction example from the original issue modified for decimal.Decimal.

examples/clickhouse_api/dynamic_scan_types.go

package clickhouse_api

import (
	"context"
	"fmt"
	"reflect"

	"github.com/shopspring/decimal"
)

func DynamicScan() error {
	conn, err := GetNativeConnection(nil, nil, nil)
	if err != nil {
		return err
	}

	if err := conn.Exec(context.Background(), "CREATE TABLE IF NOT EXISTS dynamic_scan_table (Col1 Nullable(Decimal64(2))) ENGINE = Memory"); err != nil {
		return err
	}

	if err := conn.Exec(context.Background(), "INSERT INTO dynamic_scan_table VALUES (NULL), (NULL), (NULL), (123.1), (NULL), (124.1), (NULL), (NULL), (NULL), (NULL)"); err != nil {
		return err
	}

	rows, err := conn.Query(context.Background(), "SELECT Col1 FROM dynamic_scan_table")
	if err != nil {
		return err
	}

	var (
		columnTypes = rows.ColumnTypes()
		vars        = make([]interface{}, len(columnTypes))
	)
	for i := range columnTypes {
		vars[i] = reflect.New(columnTypes[i].ScanType()).Interface()
	}
	for rows.Next() {
		if err := rows.Scan(vars...); err != nil {
			return err
		}
		for _, v := range vars {
			switch v := v.(type) {
			case **decimal.Decimal:
				if *v == nil {
					fmt.Println("NULL")
					continue
				}
				fmt.Println(**v)
			}
		}
	}
	return nil
}
$ go test ./... -v -run DynamicScan
...
=== RUN   TestDynamicScan
NULL
NULL
NULL
123.1
123.1
124.1
124.1
124.1
124.1
124.1
--- PASS: TestDynamicScan (0.06s)

Details

Note: seems to be working as expected if I implement a similar test for TestStdDynamicScan, in examples/std/dynamic_scan_types.go

=== RUN   TestStdDynamicScan
NULL
NULL
NULL
123.1
NULL
124.1
NULL
NULL
NULL
NULL
--- PASS: TestStdDynamicScan (0.02s)

Environment

  • clickhouse-go version: 2.40.3
  • Interface: ClickHouse API
  • Go version: 1.25.4
  • Operating system: macOS
  • ClickHouse version: 25.11 (latest)
  • Is it a ClickHouse Cloud? No
  • ClickHouse Server non-default settings, if any:
  • CREATE TABLE statements for tables involved:
  • Sample data for all these tables, use clickhouse-obfuscator if necessary

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions