-
Notifications
You must be signed in to change notification settings - Fork 568
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 Variant Type #1453
base: main
Are you sure you want to change the base?
Add Variant Type #1453
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
// 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 | ||
|
||
import "github.com/ClickHouse/clickhouse-go/v2/lib/chcol" | ||
|
||
// Re-export chcol types/funcs to top level clickhouse package | ||
|
||
type ( | ||
Variant = chcol.Variant | ||
VariantWithType = chcol.VariantWithType | ||
) | ||
|
||
// NewVariant creates a new Variant with the given value | ||
func NewVariant(v any) Variant { | ||
return chcol.NewVariant(v) | ||
} | ||
|
||
// NewVariantWithType creates a new Variant with the given value and ClickHouse type | ||
func NewVariantWithType(v any, chType string) VariantWithType { | ||
return chcol.NewVariantWithType(v, chType) | ||
} |
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 VariantExample() error { | ||
ctx := context.Background() | ||
|
||
conn, err := GetNativeConnection(clickhouse.Settings{ | ||
"allow_experimental_variant_type": true, | ||
}, nil, nil) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
err = conn.Exec(ctx, "DROP TABLE IF EXISTS go_variant_example") | ||
if err != nil { | ||
return err | ||
} | ||
|
||
err = conn.Exec(ctx, ` | ||
CREATE TABLE go_variant_example ( | ||
c Variant(Bool, Int64, String) | ||
) ENGINE = Memory | ||
`) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
batch, err := conn.PrepareBatch(ctx, "INSERT INTO go_variant_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 variant")); err != nil { | ||
return err | ||
} | ||
|
||
if err = batch.Append(clickhouse.NewVariantWithType("example variant 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_variant_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_variant_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 | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
// 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 | ||
|
||
import ( | ||
"database/sql/driver" | ||
"encoding/json" | ||
) | ||
|
||
// Variant represents a ClickHouse Variant type that can hold multiple possible types | ||
type Variant struct { | ||
value any | ||
} | ||
|
||
// NewVariant creates a new Variant with the given value | ||
func NewVariant(v any) Variant { | ||
return Variant{value: v} | ||
} | ||
|
||
// Nil returns true if the underlying value is nil. | ||
func (v Variant) Nil() bool { | ||
return v.value == nil | ||
} | ||
|
||
// Any returns the underlying value as any. Same as Interface. | ||
func (v Variant) Any() any { | ||
return v.value | ||
} | ||
|
||
// Interface returns the underlying value as interface{}. Same as Any. | ||
func (v Variant) Interface() interface{} { | ||
return v.value | ||
} | ||
|
||
// Int returns the value as an int if possible | ||
func (v Variant) Int() (int, bool) { | ||
if i, ok := v.value.(int); ok { | ||
return i, true | ||
} | ||
|
||
return 0, false | ||
} | ||
|
||
// Int64 returns the value as an int64 if possible | ||
func (v Variant) Int64() (int64, bool) { | ||
if i, ok := v.value.(int64); ok { | ||
return i, true | ||
} | ||
|
||
return 0, false | ||
} | ||
|
||
// String returns the value as a string if possible | ||
func (v Variant) String() (string, bool) { | ||
if s, ok := v.value.(string); ok { | ||
return s, true | ||
} | ||
|
||
return "", false | ||
} | ||
|
||
// Bool returns the value as an bool if possible | ||
func (v Variant) Bool() (bool, bool) { | ||
if b, ok := v.value.(bool); ok { | ||
return b, true | ||
} | ||
|
||
return false, false | ||
} | ||
Comment on lines
+50
to
+84
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I just wonder if we need these functions at all. See: package main
import "fmt"
type Variant struct {
value any
}
func (v *Variant) Value() any {
return v.value
}
func main() {
v := Variant{}
i, ok := v.Value().(int)
fmt.Println(i, ok)
} We can just expose value and let the user freely type assert. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. More: If we make There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm considering removing For now I agree it may be best to simply make |
||
|
||
// MarshalJSON implements the json.Marshaler interface | ||
func (v *Variant) MarshalJSON() ([]byte, error) { | ||
return json.Marshal(v.value) | ||
} | ||
|
||
// Scan implements the sql.Scanner interface | ||
func (v *Variant) Scan(value interface{}) error { | ||
v.value = value | ||
return nil | ||
} | ||
|
||
// Value implements the driver.Valuer interface | ||
func (v Variant) Value() (driver.Value, error) { | ||
return v.value, nil | ||
} | ||
|
||
func (v Variant) WithType(chType string) VariantWithType { | ||
return VariantWithType{ | ||
Variant: v, | ||
chType: chType, | ||
} | ||
} | ||
|
||
// VariantWithType is Variant with an extra value for specifying the preferred ClickHouse type for column encoding | ||
type VariantWithType struct { | ||
Variant | ||
chType string | ||
} | ||
|
||
// NewVariantWithType creates a new Variant with the given value and ClickHouse type | ||
func NewVariantWithType(v any, chType string) VariantWithType { | ||
return VariantWithType{ | ||
Variant: Variant{value: v}, | ||
chType: chType, | ||
} | ||
} | ||
|
||
// Type returns the ClickHouse type as a string. | ||
func (v VariantWithType) Type() string { | ||
return v.chType | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
// 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 | ||
|
||
import ( | ||
"testing" | ||
) | ||
|
||
func TestVariant_Nil(t *testing.T) { | ||
v := NewVariant(nil) | ||
|
||
if !v.Nil() { | ||
t.Fatalf("expected variant to be nil") | ||
} | ||
} | ||
|
||
func TestVariant_Int64(t *testing.T) { | ||
var in int64 = 42 | ||
|
||
v := NewVariant(in) | ||
|
||
out, ok := v.Int64() | ||
if !ok { | ||
t.Fatalf("failed to get int64 from variant") | ||
} else if out != in { | ||
t.Fatalf("incorrect value from variant. expected: %d got: %d", in, out) | ||
} | ||
} | ||
|
||
func TestVariant_String(t *testing.T) { | ||
in := "test" | ||
|
||
v := NewVariant(in) | ||
|
||
out, ok := v.String() | ||
if !ok { | ||
t.Fatalf("failed to get string from variant") | ||
} else if out != in { | ||
t.Fatalf("incorrect value from variant. expected: %s got: %s", in, out) | ||
} | ||
} | ||
|
||
func TestVariant_TypeSwitch(t *testing.T) { | ||
var in any | ||
|
||
v := NewVariant(in) | ||
|
||
switch v.Any().(type) { | ||
case int64: | ||
t.Fatalf("unexpected int64 value from variant") | ||
case string: | ||
t.Fatalf("unexpected string value from variant") | ||
case nil: | ||
default: | ||
t.Fatalf("expected nil value from variant") | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the point?
interface{}
is an alias toany
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I put both so users could choose whichever name they prefer within their application. Some apps prefer using
any
and othersinterface{}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's only a name. Why have both functions?