Skip to content

Commit

Permalink
Merge pull request #1455 from ClickHouse/json_type
Browse files Browse the repository at this point in the history
Add JSON Type
  • Loading branch information
SpencerTorres authored Jan 17, 2025
2 parents 75a2e2a + b123e14 commit 577ea49
Show file tree
Hide file tree
Showing 18 changed files with 3,364 additions and 785 deletions.
6 changes: 6 additions & 0 deletions chcol.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import "github.com/ClickHouse/clickhouse-go/v2/lib/chcol"
type (
Variant = chcol.Variant
Dynamic = chcol.Dynamic
JSON = chcol.JSON
)

// NewVariant creates a new Variant with the given value
Expand All @@ -45,3 +46,8 @@ func NewDynamic(v any) Dynamic {
func NewDynamicWithType(v any, chType string) Dynamic {
return chcol.NewDynamicWithType(v, chType)
}

// NewJSON creates a new empty JSON value
func NewJSON() *JSON {
return chcol.NewJSON()
}
86 changes: 86 additions & 0 deletions examples/clickhouse_api/json_paths.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// 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"
"time"
)

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

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

if !CheckMinServerVersion(conn, 24, 9, 0) {
fmt.Print("unsupported clickhouse version for JSON type")
return nil
}

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

err = conn.Exec(ctx, `
CREATE TABLE go_json_example (product JSON) ENGINE=Memory
`)
if err != nil {
return err
}

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

insertProduct := clickhouse.NewJSON()
insertProduct.SetValueAtPath("id", clickhouse.NewDynamicWithType(uint64(1234), "UInt64"))
insertProduct.SetValueAtPath("name", "Book")
insertProduct.SetValueAtPath("tags", []string{"library", "fiction"})
insertProduct.SetValueAtPath("pricing.price", int64(750))
insertProduct.SetValueAtPath("pricing.currency", "usd")
insertProduct.SetValueAtPath("metadata.region", "us")
insertProduct.SetValueAtPath("metadata.page_count", int64(852))
insertProduct.SetValueAtPath("created_at", clickhouse.NewDynamicWithType(time.Now().UTC().Truncate(time.Millisecond), "DateTime64(3)"))

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

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

var selectedProduct clickhouse.JSON

if err = conn.QueryRow(ctx, "SELECT product FROM go_json_example").Scan(&selectedProduct); err != nil {
return err
}

fmt.Printf("inserted product: %+v\n", insertProduct)
fmt.Printf("selected product: %+v\n", selectedProduct)
return nil
}
81 changes: 81 additions & 0 deletions examples/clickhouse_api/json_strings.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// 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 JSONStringExample() error {
ctx := context.Background()

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

if !CheckMinServerVersion(conn, 24, 9, 0) {
fmt.Print("unsupported clickhouse version for JSON type")
return nil
}

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

err = conn.Exec(ctx, `
CREATE TABLE go_json_example (product JSON) ENGINE=Memory
`)
if err != nil {
return err
}

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

insertProductString := "{\"id\":1234,\"name\":\"Book\",\"tags\":[\"library\",\"fiction\"]," +
"\"pricing\":{\"price\":750,\"currency\":\"usd\"},\"metadata\":{\"page_count\":852,\"region\":\"us\"}," +
"\"created_at\":\"2024-12-19T11:20:04.146Z\"}"

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

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

var selectedProductString string

if err = conn.QueryRow(ctx, "SELECT product FROM go_json_example").Scan(&selectedProductString); err != nil {
return err
}

fmt.Printf("inserted product string: %s\n", insertProductString)
fmt.Printf("selected product string: %s\n", selectedProductString)
fmt.Printf("inserted product string matches selected product string: %t\n", insertProductString == selectedProductString)
return nil
}
121 changes: 121 additions & 0 deletions examples/clickhouse_api/json_structs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// 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"
"encoding/json"
"fmt"
"github.com/ClickHouse/clickhouse-go/v2"
"time"
)

type ProductPricing struct {
Price int64 `json:"price"`
Currency string `json:"currency"`
}

type Product struct {
ID clickhouse.Dynamic `json:"id"`
Name string `json:"name"`
Tags []string `json:"tags"`
Pricing ProductPricing `json:"pricing"`
Metadata map[string]interface{} `json:"metadata"`
CreatedAt time.Time `json:"created_at" chType:"DateTime64(3)"`
}

func NewExampleProduct() *Product {
return &Product{
ID: clickhouse.NewDynamicWithType(uint64(1234), "UInt64"),
Name: "Book",
Tags: []string{"library", "fiction"},
Pricing: ProductPricing{
Price: 750,
Currency: "usd",
},
Metadata: map[string]interface{}{
"region": "us",
"page_count": int64(852),
},
CreatedAt: time.Now().UTC().Truncate(time.Millisecond),
}
}

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

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

if !CheckMinServerVersion(conn, 24, 9, 0) {
fmt.Print("unsupported clickhouse version for JSON type")
return nil
}

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

err = conn.Exec(ctx, `
CREATE TABLE go_json_example (product JSON) ENGINE=Memory
`)
if err != nil {
return err
}

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

insertProduct := NewExampleProduct()

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

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

var selectedProduct Product

if err = conn.QueryRow(ctx, "SELECT product FROM go_json_example").Scan(&selectedProduct); err != nil {
return err
}

insertProductBytes, err := json.Marshal(insertProduct)
if err != nil {
return err
}

selectedProductBytes, err := json.Marshal(&selectedProduct)
if err != nil {
return err
}

fmt.Printf("inserted product: %s\n", string(insertProductBytes))
fmt.Printf("selected product: %s\n", string(selectedProductBytes))
fmt.Printf("inserted product matches selected product: %t\n", string(insertProductBytes) == string(selectedProductBytes))
return nil
}
13 changes: 13 additions & 0 deletions examples/clickhouse_api/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,3 +217,16 @@ func TestVariantExample(t *testing.T) {
func TestDynamicExample(t *testing.T) {
require.NoError(t, DynamicExample())
}

func TestJSONPathsExample(t *testing.T) {
require.NoError(t, JSONPathsExample())
}

func TestJSONStructExample(t *testing.T) {
require.NoError(t, JSONStructExample())
}

func TestJSONStringExample(t *testing.T) {
t.Skip("client cannot receive JSON strings")
require.NoError(t, JSONStringExample())
}
Loading

0 comments on commit 577ea49

Please sign in to comment.