github.com/dolthub/go-mysql-server@v0.18.0/sql/expression/function/json/json_array_insert_test.go (about)

     1  // Copyright 2023 Dolthub, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package json
    16  
    17  import (
    18  	"fmt"
    19  	"strings"
    20  	"testing"
    21  
    22  	"github.com/stretchr/testify/require"
    23  	"gopkg.in/src-d/go-errors.v1"
    24  
    25  	"github.com/dolthub/go-mysql-server/sql"
    26  	"github.com/dolthub/go-mysql-server/sql/types"
    27  )
    28  
    29  func TestArrayInsert(t *testing.T) {
    30  	_, err := NewJSONArrayInsert()
    31  	require.True(t, errors.Is(err, sql.ErrInvalidArgumentNumber))
    32  
    33  	f1 := buildGetFieldExpressions(t, NewJSONArrayInsert, 3)
    34  	f2 := buildGetFieldExpressions(t, NewJSONArrayInsert, 5)
    35  
    36  	json := `{"a": 1, "b": [2, 3], "c": {"d": "foo"}}`
    37  
    38  	testCases := []struct {
    39  		f        sql.Expression
    40  		row      sql.Row
    41  		expected interface{}
    42  		err      error
    43  	}{
    44  		// Manual testing on MySQL verifies these behaviors are consistent. It seems a little chaotic, but json_array_insert
    45  		// logic is more consistent than other JSON functions. It requires a path ending with an index, and if it doesn't
    46  		// find one, it reports an error about the path expression. If the object lookup does find an array, then it
    47  		// inserts the value at the index. If the index is out of range, it inserts at the appropriate end of the array
    48  		// (similar to other json mutating functions). Finally, if the object lookup finds a non-array, it's a no-op.
    49  		{f1, sql.Row{json, "$.b[0]", 4.1}, `{"a": 1, "b": [4.1, 2, 3], "c": {"d": "foo"}}`, nil},
    50  		{f1, sql.Row{json, "$.a", 2}, nil, fmt.Errorf("A path expression is not a path to a cell in an array at character 3 of $.a")},
    51  		{f1, sql.Row{json, "$.e", "new"}, nil, fmt.Errorf("A path expression is not a path to a cell in an array at character 3 of $.e")},
    52  		{f1, sql.Row{json, "$.c.d", "test"}, nil, fmt.Errorf("A path expression is not a path to a cell in an array at character 5 of $.c.d")},
    53  		{f2, sql.Row{json, "$.b[0]", 4.1, "$.c.d", "test"}, nil, fmt.Errorf("A path expression is not a path to a cell in an array at character 5 of $.c.d")},
    54  		{f1, sql.Row{json, "$.b[5]", 4.1}, `{"a": 1, "b": [2, 3, 4.1], "c": {"d": "foo"}}`, nil},
    55  		{f1, sql.Row{json, "$.b.c", 4}, nil, fmt.Errorf("A path expression is not a path to a cell in an array at character 5 of $.b.c")},
    56  		{f1, sql.Row{json, "$.a[0]", 4.1}, json, nil},
    57  		{f1, sql.Row{json, "$[0]", 4.1}, json, nil},
    58  		{f1, sql.Row{json, "$.[0]", 4.1}, nil, ErrInvalidPath},
    59  		{f1, sql.Row{json, "foo", "test"}, nil, ErrInvalidPath},
    60  		{f1, sql.Row{json, "$.c.*", "test"}, nil, ErrPathWildcard},
    61  		{f1, sql.Row{json, "$.c.**", "test"}, nil, ErrPathWildcard},
    62  		{f1, sql.Row{json, "$", 10.1}, nil, fmt.Errorf("Path expression is not a path to a cell in an array: $")},
    63  		{f1, sql.Row{nil, "$", 42.7}, nil, nil},
    64  		{f1, sql.Row{json, nil, 10}, nil, nil},
    65  
    66  		// mysql> select JSON_ARRAY_INSERT(JSON_ARRAY(1,2,3), "$[1]", 51, "$[1]", 52, "$[1]", 53);
    67  		//+--------------------------------------------------------------------------+
    68  		//| JSON_ARRAY_INSERT(JSON_ARRAY(1,2,3), "$[1]", 51, "$[1]", 52, "$[1]", 53) |
    69  		//+--------------------------------------------------------------------------+
    70  		//| [1, 53, 52, 51, 2, 3]                                                    |
    71  		//+--------------------------------------------------------------------------+
    72  		{buildGetFieldExpressions(t, NewJSONArrayInsert, 7),
    73  			sql.Row{`[1.0,2.0,3.0]`,
    74  				"$[1]", 51.0, // [1, 2, 3] -> [1, 51, 2, 3]
    75  				"$[1]", 52.0, // [1, 51, 2, 3] -> [1, 52, 51, 2, 3]
    76  				"$[1]", 53.0, // [1, 52, 51, 2, 3] -> [1, 53, 52, 51, 2, 3]
    77  			},
    78  			`[1,53,52,51,2,3]`, nil},
    79  	}
    80  
    81  	for _, tstC := range testCases {
    82  		var paths []string
    83  		for _, path := range tstC.row[1:] {
    84  			if _, ok := path.(string); ok {
    85  				paths = append(paths, path.(string))
    86  			}
    87  		}
    88  
    89  		t.Run(tstC.f.String()+"."+strings.Join(paths, ","), func(t *testing.T) {
    90  			req := require.New(t)
    91  			result, err := tstC.f.Eval(sql.NewEmptyContext(), tstC.row)
    92  			if tstC.err == nil {
    93  				req.NoError(err)
    94  
    95  				var expect interface{}
    96  				if tstC.expected != nil {
    97  					expect, _, err = types.JSON.Convert(tstC.expected)
    98  					if err != nil {
    99  						panic("Bad test string. Can't convert string to JSONDocument: " + tstC.expected.(string))
   100  					}
   101  				}
   102  
   103  				req.Equal(expect, result)
   104  			} else {
   105  				req.Nil(result)
   106  				req.Error(tstC.err, err)
   107  			}
   108  		})
   109  	}
   110  
   111  }