Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Dynamic Type #1454

Open
wants to merge 1 commit into
base: variant_type
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions chcol.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import "github.com/ClickHouse/clickhouse-go/v2/lib/chcol"
type (
Variant = chcol.Variant
VariantWithType = chcol.VariantWithType
Dynamic = chcol.Dynamic
DynamicWithType = chcol.DynamicWithType
)

// NewVariant creates a new Variant with the given value
Expand All @@ -35,3 +37,13 @@ func NewVariant(v any) Variant {
func NewVariantWithType(v any, chType string) VariantWithType {
return chcol.NewVariantWithType(v, chType)
}

// NewDynamic creates a new Dynamic with the given value
func NewDynamic(v any) Dynamic {
return chcol.NewDynamic(v)
}

// NewDynamicWithType creates a new Dynamic with the given value and ClickHouse type
func NewDynamicWithType(v any, chType string) DynamicWithType {
return chcol.NewDynamicWithType(v, chType)
}
136 changes: 136 additions & 0 deletions examples/clickhouse_api/dynamic.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// Licensed to ClickHouse, Inc. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. ClickHouse, Inc. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

package clickhouse_api

import (
"context"
"fmt"
"github.com/ClickHouse/clickhouse-go/v2"
)

func DynamicExample() error {
ctx := context.Background()

conn, err := GetNativeConnection(clickhouse.Settings{
"allow_experimental_dynamic_type": true,
}, nil, nil)
if err != nil {
return err
}

err = conn.Exec(ctx, "DROP TABLE IF EXISTS go_dynamic_example")
if err != nil {
return err
}

err = conn.Exec(ctx, `
CREATE TABLE go_dynamic_example (
c Dynamic
) ENGINE = Memory
`)
if err != nil {
return err
}

batch, err := conn.PrepareBatch(ctx, "INSERT INTO go_dynamic_example (c)")
if err != nil {
return err
}

if err = batch.Append(true); err != nil {
return err
}

if err = batch.Append(int64(42)); err != nil {
return err
}

if err = batch.Append("example"); err != nil {
return err
}

if err = batch.Append(clickhouse.NewVariant("example dynamic")); err != nil {
return err
}

if err = batch.Append(clickhouse.NewVariantWithType("example dynamic with specific type", "String")); err != nil {
return err
}

if err = batch.Append(nil); err != nil {
return err
}

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

// Switch on Go Type

rows, err := conn.Query(ctx, "SELECT c FROM go_dynamic_example")
if err != nil {
return err
}

for i := 0; rows.Next(); i++ {
var row clickhouse.Variant
err := rows.Scan(&row)
if err != nil {
return fmt.Errorf("failed to scan row index %d: %w", i, err)
}

switch row.Any().(type) {
case bool:
fmt.Printf("row at index %d is Bool: %v\n", i, row.Any())
case int64:
fmt.Printf("row at index %d is Int64: %v\n", i, row.Any())
case string:
fmt.Printf("row at index %d is String: %v\n", i, row.Any())
case nil:
fmt.Printf("row at index %d is NULL\n", i)
}
}

// Switch on ClickHouse Type

rows, err = conn.Query(ctx, "SELECT c FROM go_dynamic_example")
if err != nil {
return err
}

for i := 0; rows.Next(); i++ {
var row clickhouse.VariantWithType
err := rows.Scan(&row)
if err != nil {
return fmt.Errorf("failed to scan row index %d: %w", i, err)
}

switch row.Type() {
case "Bool":
fmt.Printf("row at index %d is bool: %v\n", i, row.Any())
case "Int64":
fmt.Printf("row at index %d is int64: %v\n", i, row.Any())
case "String":
fmt.Printf("row at index %d is string: %v\n", i, row.Any())
case "":
fmt.Printf("row at index %d is nil\n", i)
}
}

return nil
}
4 changes: 4 additions & 0 deletions examples/clickhouse_api/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,3 +213,7 @@ func TestSSLNoVerify(t *testing.T) {
func TestVariantExample(t *testing.T) {
require.NoError(t, VariantExample())
}

func TestDynamicExample(t *testing.T) {
require.NoError(t, DynamicExample())
}
35 changes: 35 additions & 0 deletions lib/chcol/dynamic.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Licensed to ClickHouse, Inc. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. ClickHouse, Inc. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

package chcol

type Dynamic = Variant

// NewDynamic creates a new Dynamic with the given value
func NewDynamic(v any) Dynamic {
return Dynamic{value: v}
}

type DynamicWithType = VariantWithType

// NewDynamicWithType creates a new Dynamic with the given value and ClickHouse type
func NewDynamicWithType(v any, chType string) DynamicWithType {
return DynamicWithType{
Variant: Variant{value: v},
chType: chType,
}
}
5 changes: 5 additions & 0 deletions lib/column/codegen/column.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ func (t Type) Column(name string, tz *time.Location) (Interface, error) {
return &Point{name: name}, nil
case "String":
return &String{name: name, col: colStrProvider()}, nil
case "SharedVariant":
return &SharedVariant{name: name}, nil
case "Object('json')":
return &JSONObject{name: name, root: true, tz: tz}, nil
}
Expand All @@ -133,6 +135,8 @@ func (t Type) Column(name string, tz *time.Location) (Interface, error) {
return (&Tuple{name: name}).parse(t, tz)
case strings.HasPrefix(string(t), "Variant("):
return (&Variant{name: name}).parse(t, tz)
case strings.HasPrefix(string(t), "Dynamic"):
return (&Dynamic{name: name}).parse(t, tz)
case strings.HasPrefix(string(t), "Decimal("):
return (&Decimal{name: name}).parse(t)
case strings.HasPrefix(strType, "Nested("):
Expand Down Expand Up @@ -195,6 +199,7 @@ var (
scanTypeDecimal = reflect.TypeOf(decimal.Decimal{})
scanTypeMultiPolygon = reflect.TypeOf(orb.MultiPolygon{})
scanTypeVariant = reflect.TypeOf(chcol.Variant{})
scanTypeDynamic = reflect.TypeOf(chcol.Dynamic{})
)

{{- range . }}
Expand Down
60 changes: 60 additions & 0 deletions lib/column/codegen/dynamic.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Licensed to ClickHouse, Inc. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. ClickHouse, Inc. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

// Code generated by make codegen DO NOT EDIT.
// source: lib/column/codegen/dynamic.tpl

package column

import (
"database/sql"
"encoding/json"
"github.com/ClickHouse/ch-go/proto"
"github.com/google/uuid"
"github.com/paulmach/orb"
"time"
)

// inferClickHouseTypeFromGoType takes a Go interface{} and converts it to a ClickHouse type.
// Returns empty string if type was not matched.
// This is best effort and does not work for all types.
// Optimally, users should provide a type using DynamicWithType.
func inferClickHouseTypeFromGoType(v any) string {
switch v.(type) {
{{- range . }}
case {{ .GoType }}:
return "{{ .ChType }}"
case *{{ .GoType }}:
return "{{ .ChType }}"
{{- end }}
{{- range . }}
{{- if .SkipArray }}
{{- else}}
case []{{ .GoType }}:
return "Array({{ .ChType }})"
{{- end}}
case []*{{ .GoType }}:
return "Array({{ .ChType }})"
{{- end }}
{{- range . }}
case map[string]{{ .GoType }}:
return "Map(String, {{ .ChType }})"
{{- end }}
default:
return ""
}
}
45 changes: 42 additions & 3 deletions lib/column/codegen/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,22 @@ var (
columnSrc string
//go:embed array.tpl
arraySrc string
//go:embed dynamic.tpl
dynamicSrc string
)
var (
types []_type
supportedGoTypes []string
dynamicTypes []_type
)

type _type struct {
Size int
Size int

ChType string
GoType string

SkipArray bool
}

func init() {
Expand Down Expand Up @@ -72,6 +78,7 @@ func init() {
for _, typ := range types {
supportedGoTypes = append(supportedGoTypes, typ.GoType)
}

supportedGoTypes = append(supportedGoTypes,
"string", "[]byte", "sql.NullString",
"int", "uint", "big.Int", "decimal.Decimal",
Expand All @@ -81,6 +88,37 @@ func init() {
"netip.Addr", "net.IP", "proto.IPv6", "[16]byte",
"orb.MultiPolygon", "orb.Point", "orb.Polygon", "orb.Ring",
)

dynamicTypes = make([]_type, 0, len(types))
for _, typ := range types {

if typ.GoType == "uint8" {
// Prevent conflict with []byte and []uint8
typ.SkipArray = true
dynamicTypes = append(dynamicTypes, typ)
continue
}

dynamicTypes = append(dynamicTypes, typ)
}

// Best-effort type matching for Dynamic inference
dynamicTypes = append(dynamicTypes, []_type{
{ChType: "String", GoType: "string"},
{ChType: "String", GoType: "json.RawMessage"},
{ChType: "String", GoType: "sql.NullString"},
{ChType: "Bool", GoType: "bool"},
{ChType: "Bool", GoType: "sql.NullBool"},
{ChType: "DateTime64(3)", GoType: "time.Time"},
{ChType: "DateTime64(3)", GoType: "sql.NullTime"},
{ChType: "UUID", GoType: "uuid.UUID"},
{ChType: "IPv6", GoType: "proto.IPv6"},
{ChType: "MultiPolygon", GoType: "orb.MultiPolygon"},
{ChType: "Point", GoType: "orb.Point"},
{ChType: "Polygon", GoType: "orb.Polygon"},
{ChType: "Ring", GoType: "orb.Ring"},
}...)

}
func write(name string, v any, t *template.Template) error {
out := new(bytes.Buffer)
Expand All @@ -107,8 +145,9 @@ func main() {
template *template.Template
args any
}{
"column_gen": {template.Must(template.New("column").Parse(columnSrc)), types},
"array_gen": {template.Must(template.New("array").Parse(arraySrc)), supportedGoTypes},
"column_gen": {template.Must(template.New("column").Parse(columnSrc)), types},
"array_gen": {template.Must(template.New("array").Parse(arraySrc)), supportedGoTypes},
"dynamic_gen": {template.Must(template.New("dynamic").Parse(dynamicSrc)), dynamicTypes},
} {
if err := write(name, tpl.args, tpl.template); err != nil {
log.Fatal(err)
Expand Down
5 changes: 5 additions & 0 deletions lib/column/column_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading