From f9bf8174d4bda9e362e7cce266f558387a0db473 Mon Sep 17 00:00:00 2001 From: James Cor Date: Tue, 5 Mar 2024 10:53:43 -0800 Subject: [PATCH 1/7] add synonym --- enginetest/queries/queries.go | 22 ++++++++++++++++++++++ sql/expression/function/registry.go | 1 + 2 files changed, 23 insertions(+) diff --git a/enginetest/queries/queries.go b/enginetest/queries/queries.go index dcbdddddfe..3dd4db6624 100644 --- a/enginetest/queries/queries.go +++ b/enginetest/queries/queries.go @@ -1718,6 +1718,24 @@ Select * from ( {types.MustJSON(`["d"]`)}, }, }, + { + Query: `SELECT JSON_MERGE(c3, '{"a": 1}') FROM jsontable`, + Expected: []sql.Row{ + {types.MustJSON(`{"a": [2, 1]}`)}, + {types.MustJSON(`{"a": 1, "b": 2}`)}, + {types.MustJSON(`{"a": 1, "c": 2}`)}, + {types.MustJSON(`{"a": 1, "d": 2}`)}, + }, + }, + { + Query: `SELECT JSON_MERGE_PRESERVE(c3, '{"a": 1}') FROM jsontable`, + Expected: []sql.Row{ + {types.MustJSON(`{"a": [2, 1]}`)}, + {types.MustJSON(`{"a": 1, "b": 2}`)}, + {types.MustJSON(`{"a": 1, "c": 2}`)}, + {types.MustJSON(`{"a": 1, "d": 2}`)}, + }, + }, { Query: `SELECT CONCAT(JSON_OBJECT('aa', JSON_OBJECT('bb', 123, 'y', 456), 'z', JSON_OBJECT('cc', 321, 'x', 654)), "")`, Expected: []sql.Row{ @@ -10419,6 +10437,10 @@ var ErrorQueries = []QueryErrorTest{ Query: "SELECT json_insert() FROM dual;", ExpectedErr: sql.ErrInvalidArgumentNumber, }, + { + Query: "SELECT json_merge() FROM dual;", + ExpectedErr: sql.ErrInvalidArgumentNumber, + }, { Query: "SELECT json_merge_preserve() FROM dual;", ExpectedErr: sql.ErrInvalidArgumentNumber, diff --git a/sql/expression/function/registry.go b/sql/expression/function/registry.go index df77a78a5b..d7c8bc463f 100644 --- a/sql/expression/function/registry.go +++ b/sql/expression/function/registry.go @@ -126,6 +126,7 @@ var BuiltIns = []sql.Function{ sql.FunctionN{Name: "json_insert", Fn: json.NewJSONInsert}, sql.FunctionN{Name: "json_keys", Fn: json.NewJSONKeys}, sql.FunctionN{Name: "json_length", Fn: json.NewJsonLength}, + sql.FunctionN{Name: "json_merge", Fn: json.NewJSONMergePreserve}, sql.FunctionN{Name: "json_merge_patch", Fn: json.NewJSONMergePatch}, sql.FunctionN{Name: "json_merge_preserve", Fn: json.NewJSONMergePreserve}, sql.FunctionN{Name: "json_object", Fn: json.NewJSONObject}, From 6bf148615b88cdecbfa96d196fd3026a0105b6a4 Mon Sep 17 00:00:00 2001 From: James Cor Date: Tue, 5 Mar 2024 14:30:03 -0800 Subject: [PATCH 2/7] implement merge patch and simplify code --- .../function/json/json_depth_test.go | 4 +- .../function/json/json_merge_patch.go | 142 ++++++++++++++++++ .../function/json/json_merge_patch_test.go | 102 +++++++++++++ .../function/json/json_merge_preserve.go | 88 ++++++----- .../function/json/json_merge_preserve_test.go | 2 +- .../function/json/json_unsupported.go | 65 -------- 6 files changed, 290 insertions(+), 113 deletions(-) create mode 100644 sql/expression/function/json/json_merge_patch.go create mode 100644 sql/expression/function/json/json_merge_patch_test.go diff --git a/sql/expression/function/json/json_depth_test.go b/sql/expression/function/json/json_depth_test.go index aaa0bb2a4a..a56998e5b7 100644 --- a/sql/expression/function/json/json_depth_test.go +++ b/sql/expression/function/json/json_depth_test.go @@ -159,9 +159,9 @@ func TestJSONDepth(t *testing.T) { result, err := tt.f.Eval(sql.NewEmptyContext(), tt.row) if tt.err { require.Error(err) - } else { - require.NoError(err) + return } + require.NoError(err) require.Equal(tt.exp, result) }) } diff --git a/sql/expression/function/json/json_merge_patch.go b/sql/expression/function/json/json_merge_patch.go new file mode 100644 index 0000000000..f09e4c3e89 --- /dev/null +++ b/sql/expression/function/json/json_merge_patch.go @@ -0,0 +1,142 @@ +// Copyright 2022 Dolthub, Inc. +// +// Licensed 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 json + +import ( + "fmt" + "strings" + + "github.com/dolthub/go-mysql-server/sql" + "github.com/dolthub/go-mysql-server/sql/types" +) + +// JSONMergePatch (json_doc, json_doc[, json_doc] ...) +// +// JSONMergePatch Performs an RFC 7396 compliant merge of two or more JSON documents and returns the merged result, +// without preserving members having duplicate keys. Raises an error if at least one of the documents passed as arguments +// to this function is not valid. JSONMergePatch performs a merge as follows: +// - If the first argument is not an object, the result of the merge is the same as if an empty object had been merged +// with the second argument. +// - If the second argument is not an object, the result of the merge is the second argument. +// - If both arguments are objects, the result of the merge is an object with the following members: +// - All members of the first object which do not have a corresponding member with the same key in the second +// object. +// - All members of the second object which do not have a corresponding key in the first object, and whose value is +// not the JSON null literal. +// - All members with a key that exists in both the first and the second object, and whose value in the second +// object is not the JSON null literal. The values of these members are the results of recursively merging the +// value in the first object with the value in the second object. +// +// The behavior of JSONMergePatch is the same as that of JSONMergePreserve, with the following two exceptions: +// - JSONMergePatch removes any member in the first object with a matching key in the second object, provided that +// the value associated with the key in the second object is not JSON null. +// - If the second object has a member with a key matching a member in the first object, JSONMergePatch replaces +// the value in the first object with the value in the second object, whereas JSONMergePreserve appends the +// second value to the first value. +// +// https://dev.mysql.com/doc/refman/8.0/en/json-modification-functions.html#function_json-merge-patch +type JSONMergePatch struct { + JSONs []sql.Expression +} + +var _ sql.FunctionExpression = &JSONMergePatch{} + +// NewJSONMergePatch creates a new JSONMergePatch function. +func NewJSONMergePatch(args ...sql.Expression) (sql.Expression, error) { + if len(args) < 2 { + return nil, sql.ErrInvalidArgumentNumber.New("JSON_MERGE_PATCH", 2, len(args)) + } + return &JSONMergePatch{JSONs: args}, nil +} + +// FunctionName implements sql.FunctionExpression +func (j *JSONMergePatch) FunctionName() string { + return "json_merge_patch" +} + +// Description implements sql.FunctionExpression +func (j *JSONMergePatch) Description() string { + return "merges JSON documents, replacing values of duplicate keys" +} + +// Resolved implements sql.Expression +func (j *JSONMergePatch) Resolved() bool { + for _, arg := range j.JSONs { + if !arg.Resolved() { + return false + } + } + return true +} + +// String implements sql.Expression +func (j *JSONMergePatch) String() string { + children := j.Children() + var parts = make([]string, len(children)) + for i, c := range children { + parts[i] = c.String() + } + return fmt.Sprintf("%s(%s)", j.FunctionName(), strings.Join(parts, ",")) +} + +// Type implements the Expression interface. +func (j *JSONMergePatch) Type() sql.Type { + return types.JSON +} + +// IsNullable implements the Expression interface. +func (j *JSONMergePatch) IsNullable() bool { + for _, arg := range j.JSONs { + if arg.IsNullable() { + return true + } + } + return false +} + +// Eval implements the Expression interface. +func (j *JSONMergePatch) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { + initDoc, err := getJSONDocumentFromRow(ctx, row, j.JSONs[0]) + if err != nil { + return nil, err + } + if initDoc == nil { + return nil, nil + } + + for _, json := range j.JSONs[1:] { + var doc *types.JSONDocument + doc, err = getJSONDocumentFromRow(ctx, row, json) + if err != nil { + return nil, err + } + if doc == nil { + return nil, nil + } + initDoc.Val = merge(initDoc.Val, doc.Val, true) + } + return *initDoc, nil +} + + +// Children implements the Expression interface. +func (j *JSONMergePatch) Children() []sql.Expression { + return j.JSONs +} + +// WithChildren implements the Expression interface. +func (j *JSONMergePatch) WithChildren(children ...sql.Expression) (sql.Expression, error) { + return NewJSONMergePatch(children...) +} \ No newline at end of file diff --git a/sql/expression/function/json/json_merge_patch_test.go b/sql/expression/function/json/json_merge_patch_test.go new file mode 100644 index 0000000000..cdee6eb3a2 --- /dev/null +++ b/sql/expression/function/json/json_merge_patch_test.go @@ -0,0 +1,102 @@ +// Copyright 2024 Dolthub, Inc. +// +// Licensed 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 json + +import ( + "fmt" + "strings" +"testing" + + "github.com/stretchr/testify/require" + + "github.com/dolthub/go-mysql-server/sql" + "github.com/dolthub/go-mysql-server/sql/types" +) + +func TestJSONMergePatch(t *testing.T) { + f2 := buildGetFieldExpressions(t, NewJSONMergePatch, 2) + f3 := buildGetFieldExpressions(t, NewJSONMergePatch, 3) + //f4 := buildGetFieldExpressions(t, NewJSONMergePatch, 4) + testCases := []struct { + f sql.Expression + row sql.Row + exp interface{} + err bool + }{ + { + f: f2, + row: sql.Row{nil, nil}, + exp: nil, + }, + { + f: f2, + row: sql.Row{`null`, `null`}, + exp: types.MustJSON(`null`), + }, + { + f: f2, + row: sql.Row{`1`, `true`}, + exp: types.MustJSON(`true`), + }, + { + f: f2, + row: sql.Row{`"abc"`, `"def"`}, + exp: types.MustJSON(`"def"`), + }, + { + f: f2, + row: sql.Row{`[1, 2]`, `null`}, + exp: types.MustJSON(`null`), + }, + { + f: f2, + row: sql.Row{`[1, 2]`, `{"id": 47}`}, + exp: types.MustJSON(`{"id": 47}`), + }, + { + f: f2, + row: sql.Row{`[1, 2]`, `[true, false]`}, + exp: types.MustJSON(`[true, false]`), + }, + { + f: f2, + row: sql.Row{`{"name": "x"}`, `{"id": 47}`}, + exp: types.MustJSON(`{"id": 47, "name": "x"}`), + }, + + { + f: f3, + row: sql.Row{`{"a": 1, "b": 2}`, `{"a": 3, "c": 4}`, `{"a": 5, "d": 6}`}, + exp: types.MustJSON(`{"a": 5, "b": 2, "c": 4, "d": 6}`), + }, + } + + for _, tt := range testCases { + var args []string + for _, a := range tt.row { + args = append(args, fmt.Sprintf("%v", a)) + } + t.Run(strings.Join(args, ", "), func(t *testing.T) { + require := require.New(t) + result, err := tt.f.Eval(sql.NewEmptyContext(), tt.row) + if tt.err { + require.Error(err) + return + } + require.NoError(err) + require.Equal(tt.exp, result) + }) + } +} diff --git a/sql/expression/function/json/json_merge_preserve.go b/sql/expression/function/json/json_merge_preserve.go index 46f2f02a21..e40e520e90 100644 --- a/sql/expression/function/json/json_merge_preserve.go +++ b/sql/expression/function/json/json_merge_preserve.go @@ -1,4 +1,4 @@ -// Copyright 2022 Dolthub, Inc. +// Copyright 2022=2024 Dolthub, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -22,7 +22,7 @@ import ( "github.com/dolthub/go-mysql-server/sql/types" ) -// JSON_MERGE_PRESERVE(json_doc, json_doc[, json_doc] ...) +// JSONMergePreserve (json_doc, json_doc[, json_doc] ...) // // JSONMergePreserve Merges two or more JSON documents and returns the merged result. Returns NULL if any argument is // NULL. An error occurs if any argument is not a valid JSON document. Merging takes place according to the following @@ -43,9 +43,8 @@ import ( // second value to the first value. // // https://dev.mysql.com/doc/refman/8.0/en/json-modification-functions.html#function_json-merge-preserve - type JSONMergePreserve struct { - JSONDocs []sql.Expression + JSONs []sql.Expression } var _ sql.FunctionExpression = (*JSONMergePreserve)(nil) @@ -57,7 +56,7 @@ func NewJSONMergePreserve(args ...sql.Expression) (sql.Expression, error) { return nil, sql.ErrInvalidArgumentNumber.New("JSON_MERGE_PRESERVE", 2, len(args)) } - return &JSONMergePreserve{JSONDocs: args}, nil + return &JSONMergePreserve{JSONs: args}, nil } // FunctionName implements sql.FunctionExpression @@ -77,7 +76,7 @@ func (j JSONMergePreserve) IsUnsupported() bool { // Resolved implements the Expression interface. func (j *JSONMergePreserve) Resolved() bool { - for _, d := range j.JSONDocs { + for _, d := range j.JSONs { if !d.Resolved() { return false } @@ -89,11 +88,9 @@ func (j *JSONMergePreserve) Resolved() bool { func (j *JSONMergePreserve) String() string { children := j.Children() var parts = make([]string, len(children)) - for i, c := range children { parts[i] = c.String() } - return fmt.Sprintf("%s(%s)", j.FunctionName(), strings.Join(parts, ",")) } @@ -109,7 +106,7 @@ func (*JSONMergePreserve) CollationCoercibility(ctx *sql.Context) (collation sql // IsNullable implements the Expression interface. func (j *JSONMergePreserve) IsNullable() bool { - for _, d := range j.JSONDocs { + for _, d := range j.JSONs { if d.IsNullable() { return true } @@ -119,41 +116,31 @@ func (j *JSONMergePreserve) IsNullable() bool { // Eval implements the Expression interface. func (j *JSONMergePreserve) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { - initialJSON, err := j.JSONDocs[0].Eval(ctx, row) + initDoc, err := getJSONDocumentFromRow(ctx, row, j.JSONs[0]) if err != nil { return nil, err } - - initialJSON, _, err = j.Type().Convert(initialJSON) - if err != nil { - return nil, err + if initDoc == nil { + return nil, nil } - mergedMap := types.DeepCopyJson(initialJSON.(types.JSONDocument).Val) - - for _, json := range j.JSONDocs[1:] { - js, jErr := json.Eval(ctx, row) - if jErr != nil { + for _, json := range j.JSONs[1:] { + var doc *types.JSONDocument + doc, err = getJSONDocumentFromRow(ctx, row, json) + if err != nil { return nil, err } - - js, _, jErr = j.Type().Convert(js) - if jErr != nil { - return nil, err + if doc == nil { + return nil, nil } - - jsMap := js.(types.JSONDocument).Val - - mergedMap = merge(mergedMap, jsMap) - + initDoc.Val = merge(initDoc.Val, doc.Val, false) } - - return types.JSONDocument{Val: mergedMap}, nil + return initDoc, nil } // Children implements the Expression interface. func (j *JSONMergePreserve) Children() []sql.Expression { - return j.JSONDocs + return j.JSONs } // WithChildren implements the Expression interface. @@ -166,22 +153,33 @@ func (j *JSONMergePreserve) WithChildren(children ...sql.Expression) (sql.Expres } // merge returns merged json document as interface{} type -func merge(base, add interface{}) interface{} { - // "base" is JSON object - if baseObj, baseOk := base.(map[string]interface{}); baseOk { - // "add" is JSON object - if addObj, addOk := add.(map[string]interface{}); addOk { - for key, val := range addObj { - if exists, found := baseObj[key]; found { - baseObj[key] = merge(exists, addObj[key]) - } else { - baseObj[key] = val - } - } - return baseObj +// if patch is true, it will replace the value of the first object with the value of the second object +// otherwise, it will append the second value to the first value, creating an array if necessary +func merge(base, add interface{}, patch bool) interface{} { + baseObj, baseOk := base.(map[string]interface{}) + addObj, addOk := add.(map[string]interface{}) + if !baseOk || !addOk { + if patch { + return add } + return mergeIntoArrays(base, add) } - return mergeIntoArrays(base, add) + + // "base" and "add" are JSON objects + for key, val := range addObj { + if val == nil && patch { + delete(baseObj, key) + continue + } + baseVal, found := baseObj[key] + if !found { + baseObj[key] = val + continue + } + baseObj[key] = merge(baseVal, val, patch) + } + + return baseObj } // mergeIntoArrays returns array of interface{} that takes JSON object OR JSON array OR JSON value diff --git a/sql/expression/function/json/json_merge_preserve_test.go b/sql/expression/function/json/json_merge_preserve_test.go index 41a55960c3..9c72a343e0 100644 --- a/sql/expression/function/json/json_merge_preserve_test.go +++ b/sql/expression/function/json/json_merge_preserve_test.go @@ -1,4 +1,4 @@ -// Copyright 2022 Dolthub, Inc. +// Copyright 2022-2024 Dolthub, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/sql/expression/function/json/json_unsupported.go b/sql/expression/function/json/json_unsupported.go index deeb140776..17c0412e04 100644 --- a/sql/expression/function/json/json_unsupported.go +++ b/sql/expression/function/json/json_unsupported.go @@ -167,71 +167,6 @@ func (j JSONQuote) IsUnsupported() bool { return true } -///////////////////////////////// -// JSON modification functions // -///////////////////////////////// - -// JSON_MERGE_PATCH(json_doc, json_doc[, json_doc] ...) -// -// JSONMergePatch Performs an RFC 7396 compliant merge of two or more JSON documents and returns the merged result, -// without preserving members having duplicate keys. Raises an error if at least one of the documents passed as arguments -// to this function is not valid. JSONMergePatch performs a merge as follows: -// - If the first argument is not an object, the result of the merge is the same as if an empty object had been merged -// with the second argument. -// - If the second argument is not an object, the result of the merge is the second argument. -// - If both arguments are objects, the result of the merge is an object with the following members: -// - All members of the first object which do not have a corresponding member with the same key in the second -// object. -// - All members of the second object which do not have a corresponding key in the first object, and whose value is -// not the JSON null literal. -// - All members with a key that exists in both the first and the second object, and whose value in the second -// object is not the JSON null literal. The values of these members are the results of recursively merging the -// value in the first object with the value in the second object. -// -// The behavior of JSONMergePatch is the same as that of JSONMergePreserve, with the following two exceptions: -// - JSONMergePatch removes any member in the first object with a matching key in the second object, provided that -// the value associated with the key in the second object is not JSON null. -// - If the second object has a member with a key matching a member in the first object, JSONMergePatch replaces -// the value in the first object with the value in the second object, whereas JSONMergePreserve appends the -// second value to the first value. -// -// https://dev.mysql.com/doc/refman/8.0/en/json-modification-functions.html#function_json-merge-patch -type JSONMergePatch struct { - sql.Expression -} - -var _ sql.FunctionExpression = JSONMergePatch{} - -// NewJSONMergePatch creates a new JSONMergePatch function. -func NewJSONMergePatch(args ...sql.Expression) (sql.Expression, error) { - return nil, ErrUnsupportedJSONFunction.New(JSONMergePatch{}.FunctionName()) -} - -// FunctionName implements sql.FunctionExpression -func (j JSONMergePatch) FunctionName() string { - return "json_merge_patch" -} - -// Description implements sql.FunctionExpression -func (j JSONMergePatch) Description() string { - return "merges JSON documents, replacing values of duplicate keys" -} - -// IsUnsupported implements sql.UnsupportedFunctionStub -func (j JSONMergePatch) IsUnsupported() bool { - return true -} - -// JSON_MERGE(json_doc, json_doc[, json_doc] ...) -// -// JSONMerge Merges two or more JSON documents. Synonym for JSONMergePreserve(); deprecated in MySQL 8.0.3 and subject -// to removal in a future release. -// -// https://dev.mysql.com/doc/refman/8.0/en/json-modification-functions.html#function_json-merge -type JSONMerge struct { - sql.Expression -} - ////////////////////////// // JSON table functions // ////////////////////////// From be8b8a38f561affefad66227d228ee945054cc3c Mon Sep 17 00:00:00 2001 From: James Cor Date: Tue, 5 Mar 2024 15:54:11 -0800 Subject: [PATCH 3/7] fixing bugs and adding tests --- .../function/json/json_merge_patch_test.go | 52 +++- .../function/json/json_merge_preserve.go | 2 +- .../function/json/json_merge_preserve_test.go | 222 ++++++++++-------- 3 files changed, 170 insertions(+), 106 deletions(-) diff --git a/sql/expression/function/json/json_merge_patch_test.go b/sql/expression/function/json/json_merge_patch_test.go index cdee6eb3a2..d44d55eeac 100644 --- a/sql/expression/function/json/json_merge_patch_test.go +++ b/sql/expression/function/json/json_merge_patch_test.go @@ -28,7 +28,6 @@ import ( func TestJSONMergePatch(t *testing.T) { f2 := buildGetFieldExpressions(t, NewJSONMergePatch, 2) f3 := buildGetFieldExpressions(t, NewJSONMergePatch, 3) - //f4 := buildGetFieldExpressions(t, NewJSONMergePatch, 4) testCases := []struct { f sql.Expression row sql.Row @@ -75,12 +74,57 @@ func TestJSONMergePatch(t *testing.T) { row: sql.Row{`{"name": "x"}`, `{"id": 47}`}, exp: types.MustJSON(`{"id": 47, "name": "x"}`), }, - + { + f: f2, + row: sql.Row{ + `{ + "Suspect": { + "Name": "Bart", + "Hobbies": ["Skateboarding", "Mischief"] + }, + "Victim": "Lisa", + "Case": { + "Id": 33845, + "Date": "2006-01-02T15:04:05-07:00", + "Closed": true + } + }`, + `{ + "Suspect": { + "Age": 10, + "Parents": ["Marge", "Homer"], + "Hobbies": ["Trouble"] + }, + "Witnesses": ["Maggie", "Ned"] + }`, + }, + exp: types.MustJSON( + `{ + "Case": { + "Id": 33845, + "Date": "2006-01-02T15:04:05-07:00", + "Closed": true + }, + "Victim": "Lisa", + "Suspect": { + "Age": 10, + "Name": "Bart", + "Hobbies": ["Trouble"], + "Parents": ["Marge", "Homer"] + }, + "Witnesses": ["Maggie", "Ned"] + }`), + }, { f: f3, row: sql.Row{`{"a": 1, "b": 2}`, `{"a": 3, "c": 4}`, `{"a": 5, "d": 6}`}, exp: types.MustJSON(`{"a": 5, "b": 2, "c": 4, "d": 6}`), }, + { + f: f3, + row: sql.Row{`{"a": 1, "b": 2}`, `{"a": {"one": false, "two": 2.55, "e": 8}}`, `"single value"`}, + exp: types.MustJSON(`"single value"`), + }, } for _, tt := range testCases { @@ -90,13 +134,13 @@ func TestJSONMergePatch(t *testing.T) { } t.Run(strings.Join(args, ", "), func(t *testing.T) { require := require.New(t) - result, err := tt.f.Eval(sql.NewEmptyContext(), tt.row) + res, err := tt.f.Eval(sql.NewEmptyContext(), tt.row) if tt.err { require.Error(err) return } require.NoError(err) - require.Equal(tt.exp, result) + require.Equal(tt.exp, res) }) } } diff --git a/sql/expression/function/json/json_merge_preserve.go b/sql/expression/function/json/json_merge_preserve.go index e40e520e90..6dff55201e 100644 --- a/sql/expression/function/json/json_merge_preserve.go +++ b/sql/expression/function/json/json_merge_preserve.go @@ -135,7 +135,7 @@ func (j *JSONMergePreserve) Eval(ctx *sql.Context, row sql.Row) (interface{}, er } initDoc.Val = merge(initDoc.Val, doc.Val, false) } - return initDoc, nil + return *initDoc, nil } // Children implements the Expression interface. diff --git a/sql/expression/function/json/json_merge_preserve_test.go b/sql/expression/function/json/json_merge_preserve_test.go index 9c72a343e0..0c0e3130f6 100644 --- a/sql/expression/function/json/json_merge_preserve_test.go +++ b/sql/expression/function/json/json_merge_preserve_test.go @@ -15,128 +15,148 @@ package json import ( - "testing" + "fmt" +"strings" +"testing" "github.com/stretchr/testify/require" "github.com/dolthub/go-mysql-server/sql" - "github.com/dolthub/go-mysql-server/sql/expression" "github.com/dolthub/go-mysql-server/sql/types" ) func TestJSONMergePreserve(t *testing.T) { - f2, err := NewJSONMergePreserve( - expression.NewGetField(0, types.LongText, "arg1", false), - expression.NewGetField(1, types.LongText, "arg2", false), - ) - require.NoError(t, err) + f2 := buildGetFieldExpressions(t, NewJSONMergePreserve, 2) + f3 := buildGetFieldExpressions(t, NewJSONMergePreserve, 3) + f4 := buildGetFieldExpressions(t, NewJSONMergePreserve, 4) - f3, err := NewJSONMergePreserve( - expression.NewGetField(0, types.LongText, "arg1", false), - expression.NewGetField(1, types.LongText, "arg2", false), - expression.NewGetField(2, types.LongText, "arg3", false), - ) - require.NoError(t, err) - - f4, err := NewJSONMergePreserve( - expression.NewGetField(0, types.LongText, "arg1", false), - expression.NewGetField(1, types.LongText, "arg2", false), - expression.NewGetField(2, types.LongText, "arg3", false), - expression.NewGetField(3, types.LongText, "arg4", false), - ) - require.NoError(t, err) - - jsonArray1 := []interface{}{1, 2} - jsonArray2 := []interface{}{true, false} - jsonValue1 := `"single Value"` - jsonObj1 := map[string]interface{}{"name": "x"} - jsonObj2 := map[string]interface{}{"id": 47} - jsonObj3 := map[string]interface{}{"a": 1, "b": 2} - jsonObj4 := map[string]interface{}{"a": 3, "c": 4} - jsonObj5 := map[string]interface{}{"a": 5, "d": 6} - jsonObj6 := map[string]interface{}{"a": 3, "e": 8} - jsonObj7 := map[string]interface{}{"a": map[string]interface{}{"one": false, "two": 2.55}, "e": 8} - json3ObjResult := map[string]interface{}{"a": []interface{}{float64(1), float64(3), float64(5)}, "b": float64(2), "c": float64(4), "d": float64(6)} - json4ObjResult := map[string]interface{}{"a": []interface{}{float64(1), float64(3), float64(5), float64(3)}, "b": float64(2), "c": float64(4), "d": float64(6), "e": float64(8)} - sData1 := map[string]interface{}{ - "Suspect": map[string]interface{}{ - "Name": "Bart", - "Hobbies": []interface{}{"Skateboarding", "Mischief"}, + testCases := []struct { + f sql.Expression + row sql.Row + exp interface{} + err bool + }{ + { + f: f2, + row: sql.Row{nil, nil}, + exp: nil, }, - "Victim": "Lisa", - "Case": map[string]interface{}{ - "Id": 33845, - "Date": "2006-01-02T15:04:05-07:00", - "Closed": true, + { + f: f2, + row: sql.Row{`null`, `null`}, + exp: types.MustJSON(`[null, null]`), }, - } - sData2 := map[string]interface{}{ - "Suspect": map[string]interface{}{ - "Age": 10, - "Parents": []interface{}{"Marge", "Homer"}, - "Hobbies": []interface{}{"Trouble"}, + { + f: f2, + row: sql.Row{`1`, `true`}, + exp: types.MustJSON(`[1, true]`), }, - "Witnesses": []interface{}{"Maggie", "Ned"}, - } - resultData := map[string]interface{}{ - "Suspect": map[string]interface{}{ - "Age": float64(10), - "Name": "Bart", - "Hobbies": []interface{}{"Skateboarding", "Mischief", "Trouble"}, - "Parents": []interface{}{"Marge", "Homer"}, + { + f: f2, + row: sql.Row{`"abc"`, `"def"`}, + exp: types.MustJSON(`["abc", "def"]`), }, - "Victim": "Lisa", - "Case": map[string]interface{}{ - "Id": float64(33845), - "Date": "2006-01-02T15:04:05-07:00", - "Closed": true, + { + f: f2, + row: sql.Row{`[1, 2]`, `null`}, + exp: types.MustJSON(`[1, 2, null]`), }, - "Witnesses": []interface{}{"Maggie", "Ned"}, - } - mixedData := []interface{}{ - map[string]interface{}{ - "a": []interface{}{ - float64(1), - map[string]interface{}{ - "one": false, - "two": 2.55, - }, + { + f: f2, + row: sql.Row{`[1, 2]`, `{"id": 47}`}, + exp: types.MustJSON(`[1, 2, {"id": 47}]`), + }, + { + f: f2, + row: sql.Row{`[1, 2]`, `[true, false]`}, + exp: types.MustJSON(`[1, 2, true, false]`), + }, + { + f: f2, + row: sql.Row{`{"name": "x"}`, `{"id": 47}`}, + exp: types.MustJSON(`{"id": 47, "name": "x"}`), + }, + { + f: f2, + row: sql.Row{ + `{ + "Suspect": { + "Name": "Bart", + "Hobbies": ["Skateboarding", "Mischief"] + }, + "Victim": "Lisa", + "Case": { + "Id": 33845, + "Date": "2006-01-02T15:04:05-07:00", + "Closed": true + } + }`, + `{ + "Suspect": { + "Age": 10, + "Parents": ["Marge", "Homer"], + "Hobbies": ["Trouble"] + }, + "Witnesses": ["Maggie", "Ned"] + }`, }, - "b": float64(2), - "e": float64(8), + exp: types.MustJSON( + `{ + "Case": { + "Id": 33845, + "Date": "2006-01-02T15:04:05-07:00", + "Closed": true + }, + "Victim": "Lisa", + "Suspect": { + "Name": "Bart", + "Age": 10, + "Hobbies": ["Skateboarding", "Mischief", "Trouble"], + "Parents": ["Marge", "Homer"] + }, + "Witnesses": ["Maggie", "Ned"] + }`), + }, + { + f: f3, + row: sql.Row{ + `{"a": 1, "b": 2}`, + `{"a": 3, "c": 4}`, + `{"a": 5, "d": 6}`, + }, + exp: types.MustJSON(`{"a": [1, 3, 5], "b": 2, "c": 4, "d": 6}`), + }, + { + f: f4, + row: sql.Row{ + `{"a": 1, "b": 2}`, + `{"a": 3, "c": 4}`, + `{"a": 5, "d": 6}`, + `{"a": 3, "e": 8}`, + }, + exp: types.MustJSON(`{"a": [1, 3, 5, 3], "b": 2, "c": 4, "d": 6, "e": 8}`), + }, + { + f: f3, + row: sql.Row{`{"a": 1, "b": 2}`, `{"a": {"one": false, "two": 2.55, "e": 8}}`, `"single value"`}, + exp: types.MustJSON(`[{"a": [1, {"e": 8, "one": false, "two": 2.55}], "b": 2}, "single value"]`), }, - "single Value", - } - - testCases := []struct { - f sql.Expression - row sql.Row - expected interface{} - err error - }{ - {f2, sql.Row{nil, nil}, types.JSONDocument{Val: []interface{}{nil, nil}}, nil}, - {f2, sql.Row{jsonArray1, nil}, types.JSONDocument{Val: []interface{}{float64(1), float64(2), nil}}, nil}, - {f2, sql.Row{jsonArray1, jsonArray2}, types.JSONDocument{Val: []interface{}{float64(1), float64(2), true, false}}, nil}, - {f2, sql.Row{jsonObj1, jsonObj2}, types.JSONDocument{Val: map[string]interface{}{"name": "x", "id": float64(47)}}, nil}, - {f2, sql.Row{1, true}, types.JSONDocument{Val: []interface{}{float64(1), true}}, nil}, - {f2, sql.Row{jsonArray1, jsonObj2}, types.JSONDocument{Val: []interface{}{float64(1), float64(2), map[string]interface{}{"id": float64(47)}}}, nil}, - {f3, sql.Row{jsonObj3, jsonObj4, jsonObj5}, types.JSONDocument{Val: json3ObjResult}, nil}, - {f2, sql.Row{sData1, sData2}, types.JSONDocument{Val: resultData}, nil}, - {f4, sql.Row{jsonObj3, jsonObj4, jsonObj5, jsonObj6}, types.JSONDocument{Val: json4ObjResult}, nil}, - {f3, sql.Row{jsonObj3, jsonObj7, jsonValue1}, types.JSONDocument{Val: mixedData}, nil}, } for _, tt := range testCases { - t.Run(tt.f.String(), func(t *testing.T) { + var args []string + for _, a := range tt.row { + args = append(args, fmt.Sprintf("%v", a)) + } + t.Run(strings.Join(args, ", "), func(t *testing.T) { require := require.New(t) - result, err := tt.f.Eval(sql.NewEmptyContext(), tt.row) - if tt.err == nil { - require.NoError(err) - } else { - require.Equal(err.Error(), tt.err.Error()) + res, err := tt.f.Eval(sql.NewEmptyContext(), tt.row) + if tt.err { + require.Error(err) + return } - - require.Equal(tt.expected, result) + require.NoError(err) + require.Equal(tt.exp, res) }) } } From bb657e42459a7684c49cee0c25dcaf16ca40cf9d Mon Sep 17 00:00:00 2001 From: jycor Date: Tue, 5 Mar 2024 23:58:41 +0000 Subject: [PATCH 4/7] [ga-format-pr] Run ./format_repo.sh to fix formatting --- sql/expression/function/json/json_merge_patch.go | 3 +-- sql/expression/function/json/json_merge_patch_test.go | 4 ++-- .../function/json/json_merge_preserve_test.go | 10 +++++----- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/sql/expression/function/json/json_merge_patch.go b/sql/expression/function/json/json_merge_patch.go index f09e4c3e89..b49e3f860a 100644 --- a/sql/expression/function/json/json_merge_patch.go +++ b/sql/expression/function/json/json_merge_patch.go @@ -130,7 +130,6 @@ func (j *JSONMergePatch) Eval(ctx *sql.Context, row sql.Row) (interface{}, error return *initDoc, nil } - // Children implements the Expression interface. func (j *JSONMergePatch) Children() []sql.Expression { return j.JSONs @@ -139,4 +138,4 @@ func (j *JSONMergePatch) Children() []sql.Expression { // WithChildren implements the Expression interface. func (j *JSONMergePatch) WithChildren(children ...sql.Expression) (sql.Expression, error) { return NewJSONMergePatch(children...) -} \ No newline at end of file +} diff --git a/sql/expression/function/json/json_merge_patch_test.go b/sql/expression/function/json/json_merge_patch_test.go index d44d55eeac..c4fe4bbbd1 100644 --- a/sql/expression/function/json/json_merge_patch_test.go +++ b/sql/expression/function/json/json_merge_patch_test.go @@ -17,7 +17,7 @@ package json import ( "fmt" "strings" -"testing" + "testing" "github.com/stretchr/testify/require" @@ -75,7 +75,7 @@ func TestJSONMergePatch(t *testing.T) { exp: types.MustJSON(`{"id": 47, "name": "x"}`), }, { - f: f2, + f: f2, row: sql.Row{ `{ "Suspect": { diff --git a/sql/expression/function/json/json_merge_preserve_test.go b/sql/expression/function/json/json_merge_preserve_test.go index 0c0e3130f6..dedfc1f211 100644 --- a/sql/expression/function/json/json_merge_preserve_test.go +++ b/sql/expression/function/json/json_merge_preserve_test.go @@ -16,8 +16,8 @@ package json import ( "fmt" -"strings" -"testing" + "strings" + "testing" "github.com/stretchr/testify/require" @@ -77,7 +77,7 @@ func TestJSONMergePreserve(t *testing.T) { exp: types.MustJSON(`{"id": 47, "name": "x"}`), }, { - f: f2, + f: f2, row: sql.Row{ `{ "Suspect": { @@ -118,7 +118,7 @@ func TestJSONMergePreserve(t *testing.T) { }`), }, { - f: f3, + f: f3, row: sql.Row{ `{"a": 1, "b": 2}`, `{"a": 3, "c": 4}`, @@ -127,7 +127,7 @@ func TestJSONMergePreserve(t *testing.T) { exp: types.MustJSON(`{"a": [1, 3, 5], "b": 2, "c": 4, "d": 6}`), }, { - f: f4, + f: f4, row: sql.Row{ `{"a": 1, "b": 2}`, `{"a": 3, "c": 4}`, From 3e4a3fbf8eff26962ac916e8c3c7dd8298cbc7f0 Mon Sep 17 00:00:00 2001 From: James Cor Date: Tue, 5 Mar 2024 16:03:28 -0800 Subject: [PATCH 5/7] fixing deep copy issue --- enginetest/memory_engine_test.go | 49 ++++++++----------- enginetest/queries/queries.go | 18 +++++++ .../function/json/json_merge_patch.go | 5 +- .../function/json/json_merge_preserve.go | 5 +- 4 files changed, 44 insertions(+), 33 deletions(-) diff --git a/enginetest/memory_engine_test.go b/enginetest/memory_engine_test.go index c62c2e2ec0..37c720b40a 100644 --- a/enginetest/memory_engine_test.go +++ b/enginetest/memory_engine_test.go @@ -202,44 +202,35 @@ func newUpdateResult(matched, updated int) types.OkResult { // Convenience test for debugging a single query. Unskip and set to the desired query. func TestSingleScript(t *testing.T) { - t.Skip() + //t.Skip() var scripts = []queries.ScriptTest{ { - Name: "physical columns added after virtual one", + Name: "delete me", SetUpScript: []string{ - "create table t (pk int primary key, col1 int as (pk + 1));", - "insert into t (pk) values (1), (3)", - "alter table t add index idx1 (col1, pk);", - "alter table t add index idx2 (col1);", - "alter table t add column col2 int;", - "alter table t add column col3 int;", - "insert into t (pk, col2, col3) values (2, 4, 5);", + `create table jsontable (pk smallint primary key, c1 varchar(20), c2 JSON, c3 JSON)`, + `insert into jsontable values + (1, 'row one', '[1,2]', '{"a": 2}'), + (2, 'row two', '[3,4]', '{"b": 2}'), + (3, 'row three', '[5,6]', '{"c": 2}'), + (4, 'row four', '[7,8]', '{"d": 2}')`, }, Assertions: []queries.ScriptTestAssertion{ { - Query: "select * from t order by pk", - Expected: []sql.Row{ - {1, 2, nil, nil}, - {2, 3, 4, 5}, - {3, 4, nil, nil}, - }, - }, - { - Query: "select * from t where col1 = 2", - Expected: []sql.Row{ - {1, 2, nil, nil}, - }, - }, - { - Query: "select * from t where col1 = 3 and pk = 2", + Query: `SELECT JSON_MERGE(c3, '{"a": 1}') FROM jsontable`, Expected: []sql.Row{ - {2, 3, 4, 5}, + {types.MustJSON(`{"a": [2, 1]}`)}, + {types.MustJSON(`{"a": 1, "b": 2}`)}, + {types.MustJSON(`{"a": 1, "c": 2}`)}, + {types.MustJSON(`{"a": 1, "d": 2}`)}, }, }, { - Query: "select * from t where pk = 2", + Query: `SELECT JSON_MERGE(c3, '{"a": 1}') FROM jsontable`, Expected: []sql.Row{ - {2, 3, 4, 5}, + {types.MustJSON(`{"a": [2, 1]}`)}, + {types.MustJSON(`{"a": 1, "b": 2}`)}, + {types.MustJSON(`{"a": 1, "c": 2}`)}, + {types.MustJSON(`{"a": 1, "d": 2}`)}, }, }, }, @@ -253,8 +244,8 @@ func TestSingleScript(t *testing.T) { if err != nil { panic(err) } - engine.EngineAnalyzer().Debug = true - engine.EngineAnalyzer().Verbose = true + //engine.EngineAnalyzer().Debug = true + //engine.EngineAnalyzer().Verbose = true enginetest.TestScriptWithEngine(t, engine, harness, test) } diff --git a/enginetest/queries/queries.go b/enginetest/queries/queries.go index 66db4d1d92..72fd35070d 100644 --- a/enginetest/queries/queries.go +++ b/enginetest/queries/queries.go @@ -1736,6 +1736,15 @@ Select * from ( {types.MustJSON(`{"a": 1, "d": 2}`)}, }, }, + { + Query: `SELECT JSON_MERGE(c3, '{"a": 1}') FROM jsontable`, + Expected: []sql.Row{ + {types.MustJSON(`{"a": [2, 1]}`)}, + {types.MustJSON(`{"a": 1, "b": 2}`)}, + {types.MustJSON(`{"a": 1, "c": 2}`)}, + {types.MustJSON(`{"a": 1, "d": 2}`)}, + }, + }, { Query: `SELECT JSON_MERGE_PRESERVE(c3, '{"a": 1}') FROM jsontable`, Expected: []sql.Row{ @@ -1745,6 +1754,15 @@ Select * from ( {types.MustJSON(`{"a": 1, "d": 2}`)}, }, }, + { + Query: `SELECT JSON_MERGE_PATCH(c3, '{"a": 1}') FROM jsontable`, + Expected: []sql.Row{ + {types.MustJSON(`{"a": [2, 1]}`)}, + {types.MustJSON(`{"a": 1, "b": 2}`)}, + {types.MustJSON(`{"a": 1, "c": 2}`)}, + {types.MustJSON(`{"a": 1, "d": 2}`)}, + }, + }, { Query: `SELECT CONCAT(JSON_OBJECT('aa', JSON_OBJECT('bb', 123, 'y', 456), 'z', JSON_OBJECT('cc', 321, 'x', 654)), "")`, Expected: []sql.Row{ diff --git a/sql/expression/function/json/json_merge_patch.go b/sql/expression/function/json/json_merge_patch.go index f09e4c3e89..9b721ac17d 100644 --- a/sql/expression/function/json/json_merge_patch.go +++ b/sql/expression/function/json/json_merge_patch.go @@ -116,6 +116,7 @@ func (j *JSONMergePatch) Eval(ctx *sql.Context, row sql.Row) (interface{}, error return nil, nil } + result := types.DeepCopyJson(initDoc.Val) for _, json := range j.JSONs[1:] { var doc *types.JSONDocument doc, err = getJSONDocumentFromRow(ctx, row, json) @@ -125,9 +126,9 @@ func (j *JSONMergePatch) Eval(ctx *sql.Context, row sql.Row) (interface{}, error if doc == nil { return nil, nil } - initDoc.Val = merge(initDoc.Val, doc.Val, true) + result = merge(result, doc.Val, true) } - return *initDoc, nil + return types.JSONDocument{Val: result}, nil } diff --git a/sql/expression/function/json/json_merge_preserve.go b/sql/expression/function/json/json_merge_preserve.go index 6dff55201e..a5c5abb565 100644 --- a/sql/expression/function/json/json_merge_preserve.go +++ b/sql/expression/function/json/json_merge_preserve.go @@ -124,6 +124,7 @@ func (j *JSONMergePreserve) Eval(ctx *sql.Context, row sql.Row) (interface{}, er return nil, nil } + result := types.DeepCopyJson(initDoc.Val) for _, json := range j.JSONs[1:] { var doc *types.JSONDocument doc, err = getJSONDocumentFromRow(ctx, row, json) @@ -133,9 +134,9 @@ func (j *JSONMergePreserve) Eval(ctx *sql.Context, row sql.Row) (interface{}, er if doc == nil { return nil, nil } - initDoc.Val = merge(initDoc.Val, doc.Val, false) + result = merge(result, doc.Val, false) } - return *initDoc, nil + return types.JSONDocument{Val: result}, nil } // Children implements the Expression interface. From 6062738dc5b74388aae6deb4a79a61def1b348ff Mon Sep 17 00:00:00 2001 From: James Cor Date: Tue, 5 Mar 2024 16:11:35 -0800 Subject: [PATCH 6/7] fix test --- enginetest/memory_engine_test.go | 49 +++++++++++++++++++------------- enginetest/queries/queries.go | 11 +------ 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/enginetest/memory_engine_test.go b/enginetest/memory_engine_test.go index 37c720b40a..c62c2e2ec0 100644 --- a/enginetest/memory_engine_test.go +++ b/enginetest/memory_engine_test.go @@ -202,35 +202,44 @@ func newUpdateResult(matched, updated int) types.OkResult { // Convenience test for debugging a single query. Unskip and set to the desired query. func TestSingleScript(t *testing.T) { - //t.Skip() + t.Skip() var scripts = []queries.ScriptTest{ { - Name: "delete me", + Name: "physical columns added after virtual one", SetUpScript: []string{ - `create table jsontable (pk smallint primary key, c1 varchar(20), c2 JSON, c3 JSON)`, - `insert into jsontable values - (1, 'row one', '[1,2]', '{"a": 2}'), - (2, 'row two', '[3,4]', '{"b": 2}'), - (3, 'row three', '[5,6]', '{"c": 2}'), - (4, 'row four', '[7,8]', '{"d": 2}')`, + "create table t (pk int primary key, col1 int as (pk + 1));", + "insert into t (pk) values (1), (3)", + "alter table t add index idx1 (col1, pk);", + "alter table t add index idx2 (col1);", + "alter table t add column col2 int;", + "alter table t add column col3 int;", + "insert into t (pk, col2, col3) values (2, 4, 5);", }, Assertions: []queries.ScriptTestAssertion{ { - Query: `SELECT JSON_MERGE(c3, '{"a": 1}') FROM jsontable`, + Query: "select * from t order by pk", + Expected: []sql.Row{ + {1, 2, nil, nil}, + {2, 3, 4, 5}, + {3, 4, nil, nil}, + }, + }, + { + Query: "select * from t where col1 = 2", + Expected: []sql.Row{ + {1, 2, nil, nil}, + }, + }, + { + Query: "select * from t where col1 = 3 and pk = 2", Expected: []sql.Row{ - {types.MustJSON(`{"a": [2, 1]}`)}, - {types.MustJSON(`{"a": 1, "b": 2}`)}, - {types.MustJSON(`{"a": 1, "c": 2}`)}, - {types.MustJSON(`{"a": 1, "d": 2}`)}, + {2, 3, 4, 5}, }, }, { - Query: `SELECT JSON_MERGE(c3, '{"a": 1}') FROM jsontable`, + Query: "select * from t where pk = 2", Expected: []sql.Row{ - {types.MustJSON(`{"a": [2, 1]}`)}, - {types.MustJSON(`{"a": 1, "b": 2}`)}, - {types.MustJSON(`{"a": 1, "c": 2}`)}, - {types.MustJSON(`{"a": 1, "d": 2}`)}, + {2, 3, 4, 5}, }, }, }, @@ -244,8 +253,8 @@ func TestSingleScript(t *testing.T) { if err != nil { panic(err) } - //engine.EngineAnalyzer().Debug = true - //engine.EngineAnalyzer().Verbose = true + engine.EngineAnalyzer().Debug = true + engine.EngineAnalyzer().Verbose = true enginetest.TestScriptWithEngine(t, engine, harness, test) } diff --git a/enginetest/queries/queries.go b/enginetest/queries/queries.go index 72fd35070d..2db08a47fe 100644 --- a/enginetest/queries/queries.go +++ b/enginetest/queries/queries.go @@ -1736,15 +1736,6 @@ Select * from ( {types.MustJSON(`{"a": 1, "d": 2}`)}, }, }, - { - Query: `SELECT JSON_MERGE(c3, '{"a": 1}') FROM jsontable`, - Expected: []sql.Row{ - {types.MustJSON(`{"a": [2, 1]}`)}, - {types.MustJSON(`{"a": 1, "b": 2}`)}, - {types.MustJSON(`{"a": 1, "c": 2}`)}, - {types.MustJSON(`{"a": 1, "d": 2}`)}, - }, - }, { Query: `SELECT JSON_MERGE_PRESERVE(c3, '{"a": 1}') FROM jsontable`, Expected: []sql.Row{ @@ -1757,7 +1748,7 @@ Select * from ( { Query: `SELECT JSON_MERGE_PATCH(c3, '{"a": 1}') FROM jsontable`, Expected: []sql.Row{ - {types.MustJSON(`{"a": [2, 1]}`)}, + {types.MustJSON(`{"a": 1}`)}, {types.MustJSON(`{"a": 1, "b": 2}`)}, {types.MustJSON(`{"a": 1, "c": 2}`)}, {types.MustJSON(`{"a": 1, "d": 2}`)}, From 0e0e0a167530a2cfc3391ad08f24f9becd3899fb Mon Sep 17 00:00:00 2001 From: James Cor Date: Wed, 6 Mar 2024 11:26:54 -0800 Subject: [PATCH 7/7] feedback --- sql/expression/function/json/json_merge_patch.go | 2 +- sql/expression/function/json/json_merge_patch_test.go | 5 +++++ sql/expression/function/json/json_merge_preserve.go | 2 +- sql/expression/function/json/json_merge_preserve_test.go | 5 +++++ 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/sql/expression/function/json/json_merge_patch.go b/sql/expression/function/json/json_merge_patch.go index ad1cf25427..418d3316d8 100644 --- a/sql/expression/function/json/json_merge_patch.go +++ b/sql/expression/function/json/json_merge_patch.go @@ -1,4 +1,4 @@ -// Copyright 2022 Dolthub, Inc. +// Copyright 2024 Dolthub, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/sql/expression/function/json/json_merge_patch_test.go b/sql/expression/function/json/json_merge_patch_test.go index c4fe4bbbd1..353fb0a48d 100644 --- a/sql/expression/function/json/json_merge_patch_test.go +++ b/sql/expression/function/json/json_merge_patch_test.go @@ -74,6 +74,11 @@ func TestJSONMergePatch(t *testing.T) { row: sql.Row{`{"name": "x"}`, `{"id": 47}`}, exp: types.MustJSON(`{"id": 47, "name": "x"}`), }, + { + f: f2, + row: sql.Row{`{"id": 123}`, `{"id": null}`}, + exp: types.MustJSON(`{}`), + }, { f: f2, row: sql.Row{ diff --git a/sql/expression/function/json/json_merge_preserve.go b/sql/expression/function/json/json_merge_preserve.go index a5c5abb565..b96bd80db9 100644 --- a/sql/expression/function/json/json_merge_preserve.go +++ b/sql/expression/function/json/json_merge_preserve.go @@ -1,4 +1,4 @@ -// Copyright 2022=2024 Dolthub, Inc. +// Copyright 2022-2024 Dolthub, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/sql/expression/function/json/json_merge_preserve_test.go b/sql/expression/function/json/json_merge_preserve_test.go index dedfc1f211..1e232af9d1 100644 --- a/sql/expression/function/json/json_merge_preserve_test.go +++ b/sql/expression/function/json/json_merge_preserve_test.go @@ -76,6 +76,11 @@ func TestJSONMergePreserve(t *testing.T) { row: sql.Row{`{"name": "x"}`, `{"id": 47}`}, exp: types.MustJSON(`{"id": 47, "name": "x"}`), }, + { + f: f2, + row: sql.Row{`{"id": 123}`, `{"id": null}`}, + exp: types.MustJSON(`{"id": [123, null]}`), + }, { f: f2, row: sql.Row{