From e2f19311dce2a47cd17b53950edcf43af719fb9d Mon Sep 17 00:00:00 2001 From: Kuba Kaflik Date: Wed, 11 Oct 2023 08:06:46 +0200 Subject: [PATCH 1/2] Fix an empty JSON map append using _dummy column --- lib/column/json.go | 10 +++++++++ tests/issues/1113_test.go | 43 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 tests/issues/1113_test.go diff --git a/lib/column/json.go b/lib/column/json.go index ca9120668e..aa1fa9dd6a 100644 --- a/lib/column/json.go +++ b/lib/column/json.go @@ -501,6 +501,16 @@ func appendStructOrMap(jCol *JSONObject, data any) error { Err: fmt.Errorf("map keys must be string for column %s", jCol.Name()), } } + + if vData.Len() == 0 { + // if map is empty, we need to create a dummy map to make ClickHouse Tuple happy + // this is exactly what ClickHouse does when it receives an empty `{}` object string + // JSON representation will become '{"_dummy":0}' + // JSON object type is experimental and may change in the future + vData = reflect.MakeMap(reflect.MapOf(reflect.TypeOf("_dummy"), reflect.TypeOf(uint8(0)))) + vData.SetMapIndex(reflect.ValueOf("_dummy"), reflect.ValueOf(uint8(0))) + } + return iterateMap(vData, jCol, 0) } return &UnsupportedColumnTypeError{ diff --git a/tests/issues/1113_test.go b/tests/issues/1113_test.go new file mode 100644 index 0000000000..da9e6293f3 --- /dev/null +++ b/tests/issues/1113_test.go @@ -0,0 +1,43 @@ +package issues + +import ( + "context" + "github.com/ClickHouse/clickhouse-go/v2" + clickhouse_tests "github.com/ClickHouse/clickhouse-go/v2/tests" + "github.com/stretchr/testify/require" + "testing" +) + +func Test1113(t *testing.T) { + var ( + conn, err = clickhouse_tests.GetConnection("issues", clickhouse.Settings{ + "max_execution_time": 60, + "allow_experimental_object_type": true, + }, nil, &clickhouse.Compression{ + Method: clickhouse.CompressionLZ4, + }) + ) + ctx := context.Background() + require.NoError(t, err) + const ddl = ` + CREATE TABLE test_1113 ( + col_1 JSON, + col_2 JSON + ) Engine MergeTree() ORDER BY tuple() + ` + require.NoError(t, conn.Exec(ctx, ddl)) + defer func() { + conn.Exec(ctx, "DROP TABLE IF EXISTS test_1113") + }() + + batch, err := conn.PrepareBatch(context.Background(), "INSERT INTO test_1113") + require.NoError(t, err) + + v1 := map[string]struct { + Str string + }{"a": {Str: "value"}} + v2 := map[string]any{} + + require.NoError(t, batch.Append(v1, v2)) + require.NoError(t, batch.Send()) +} From 6bdd7afcf639047d9943660a573e8319959b809c Mon Sep 17 00:00:00 2001 From: Kuba Kaflik Date: Wed, 11 Oct 2023 09:51:44 +0200 Subject: [PATCH 2/2] Fix an empty JSON map append using _dummy column --- lib/column/json.go | 17 +++++++++++------ tests/issues/1113_test.go | 7 +------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/column/json.go b/lib/column/json.go index aa1fa9dd6a..1c11c3b1bd 100644 --- a/lib/column/json.go +++ b/lib/column/json.go @@ -503,12 +503,17 @@ func appendStructOrMap(jCol *JSONObject, data any) error { } if vData.Len() == 0 { - // if map is empty, we need to create a dummy map to make ClickHouse Tuple happy - // this is exactly what ClickHouse does when it receives an empty `{}` object string - // JSON representation will become '{"_dummy":0}' - // JSON object type is experimental and may change in the future - vData = reflect.MakeMap(reflect.MapOf(reflect.TypeOf("_dummy"), reflect.TypeOf(uint8(0)))) - vData.SetMapIndex(reflect.ValueOf("_dummy"), reflect.ValueOf(uint8(0))) + // if map is empty, we need to create an empty Tuple to make sure subcolumns protocol is happy + // _dummy is a ClickHouse internal name for empty Tuple subcolumn + // it has the same effect as `INSERT INTO single_json_type_table VALUES ('{}');` + emptyVal := &JSONValue{Interface: &UInt8{name: "_dummy"}} + if err := emptyVal.appendEmptyValue(); err != nil { + return err + } + jCol.columns = []JSON{ + emptyVal, + } + return nil } return iterateMap(vData, jCol, 0) diff --git a/tests/issues/1113_test.go b/tests/issues/1113_test.go index da9e6293f3..2de26ccfee 100644 --- a/tests/issues/1113_test.go +++ b/tests/issues/1113_test.go @@ -19,12 +19,7 @@ func Test1113(t *testing.T) { ) ctx := context.Background() require.NoError(t, err) - const ddl = ` - CREATE TABLE test_1113 ( - col_1 JSON, - col_2 JSON - ) Engine MergeTree() ORDER BY tuple() - ` + const ddl = "CREATE TABLE test_1113 (col_1 JSON, col_2 JSON) Engine MergeTree() ORDER BY tuple()" require.NoError(t, conn.Exec(ctx, ddl)) defer func() { conn.Exec(ctx, "DROP TABLE IF EXISTS test_1113")