Skip to content

Commit

Permalink
add variant type
Browse files Browse the repository at this point in the history
  • Loading branch information
SpencerTorres committed Dec 20, 2024
1 parent e734513 commit fde0472
Show file tree
Hide file tree
Showing 10 changed files with 1,107 additions and 1 deletion.
37 changes: 37 additions & 0 deletions chcol.go
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)
}
4 changes: 4 additions & 0 deletions examples/clickhouse_api/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,3 +209,7 @@ func TestSSL(t *testing.T) {
func TestSSLNoVerify(t *testing.T) {
require.NoError(t, SSLNoVerifyVersion())
}

func TestVariantExample(t *testing.T) {
require.NoError(t, VariantExample())
}
136 changes: 136 additions & 0 deletions examples/clickhouse_api/variant.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 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
}
126 changes: 126 additions & 0 deletions lib/chcol/variant.go
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
}

// 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
}
72 changes: 72 additions & 0 deletions lib/chcol/variant_test.go
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")
}
}
Loading

0 comments on commit fde0472

Please sign in to comment.