github.com/dolthub/go-mysql-server@v0.18.0/sql/types/json_test.go (about)

     1  // Copyright 2022 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 types
    16  
    17  import (
    18  	"fmt"
    19  	"reflect"
    20  	"testing"
    21  
    22  	"github.com/dolthub/go-mysql-server/sql"
    23  	// _ "github.com/dolthub/go-mysql-server/sql/variables"
    24  	"github.com/dolthub/vitess/go/vt/proto/query"
    25  	"github.com/stretchr/testify/assert"
    26  	"github.com/stretchr/testify/require"
    27  )
    28  
    29  func TestJsonCompare(t *testing.T) {
    30  	tests := []struct {
    31  		left  string
    32  		right string
    33  		cmp   int
    34  	}{
    35  		// type precedence hierarchy: BOOLEAN, ARRAY, OBJECT, STRING, DOUBLE, NULL
    36  		{`true`, `[0]`, 1},
    37  		{`[0]`, `{"a": 0}`, 1},
    38  		{`{"a": 0}`, `"a"`, 1},
    39  		{`"a"`, `0`, 1},
    40  		{`0`, `null`, 1},
    41  
    42  		// json null
    43  		{`null`, `0`, -1},
    44  		{`0`, `null`, 1},
    45  		{`null`, `null`, 0},
    46  
    47  		// boolean
    48  		{`true`, `false`, 1},
    49  		{`true`, `true`, 0},
    50  		{`false`, `false`, 0},
    51  
    52  		// strings
    53  		{`"A"`, `"B"`, -1},
    54  		{`"A"`, `"A"`, 0},
    55  		{`"C"`, `"B"`, 1},
    56  
    57  		// numbers
    58  		{`0`, `0.0`, 0},
    59  		{`0`, `-1`, 1},
    60  		{`0`, `3.14`, -1},
    61  
    62  		// arrays
    63  		{`[1,2]`, `[1,2]`, 0},
    64  		{`[1,9]`, `[1,2]`, 1},
    65  		{`[1,2]`, `[1,2,3]`, -1},
    66  
    67  		// objects
    68  		{`{"a": 0}`, `{"a": 0}`, 0},
    69  		// deterministic object ordering with arbitrary rules
    70  		{`{"a": 1}`, `{"a": 0}`, 1},                 // 1 > 0
    71  		{`{"a": 0}`, `{"a": 0, "b": 1}`, -1},        // longer
    72  		{`{"a": 0, "c": 2}`, `{"a": 0, "b": 1}`, 1}, // "c" > "b"
    73  
    74  		// nested
    75  		{
    76  			left:  `{"one": ["x", "y", "z"], "two": { "a": 0, "b": 1}, "three": false, "four": null, "five": " "}`,
    77  			right: `{"one": ["x", "y", "z"], "two": { "a": 0, "b": 1}, "three": false, "four": null, "five": " "}`,
    78  			cmp:   0,
    79  		},
    80  		{
    81  			left:  `{"one": ["x", "y"],      "two": { "a": 0, "b": 1}, "three": false, "four": null, "five": " "}`,
    82  			right: `{"one": ["x", "y", "z"], "two": { "a": 0, "b": 1}, "three": false, "four": null, "five": " "}`,
    83  			cmp:   -1,
    84  		},
    85  	}
    86  
    87  	for _, test := range tests {
    88  		name := fmt.Sprintf("%v_%v__%d", test.left, test.right, test.cmp)
    89  		t.Run(name, func(t *testing.T) {
    90  			cmp, err := JSON.Compare(
    91  				MustJSON(test.left),
    92  				MustJSON(test.right),
    93  			)
    94  			require.NoError(t, err)
    95  			assert.Equal(t, test.cmp, cmp)
    96  		})
    97  	}
    98  }
    99  
   100  func TestJsonCompareNulls(t *testing.T) {
   101  	tests := []struct {
   102  		left  interface{}
   103  		right interface{}
   104  		cmp   int
   105  	}{
   106  		{nil, MustJSON(`{"key": "value"}`), 1},
   107  		{MustJSON(`{"key": "value"}`), nil, -1},
   108  		{nil, nil, 0},
   109  		{nil, MustJSON(`null`), 1},
   110  		{MustJSON(`null`), nil, -1},
   111  	}
   112  
   113  	for _, test := range tests {
   114  		name := fmt.Sprintf("%v_%v__%d", test.left, test.right, test.cmp)
   115  		t.Run(name, func(t *testing.T) {
   116  			cmp, err := JSON.Compare(test.left, test.right)
   117  			require.NoError(t, err)
   118  			assert.Equal(t, test.cmp, cmp)
   119  		})
   120  	}
   121  }
   122  
   123  func TestJsonConvert(t *testing.T) {
   124  	type testStruct struct {
   125  		Field string `json:"field"`
   126  	}
   127  	tests := []struct {
   128  		val         interface{}
   129  		expectedVal interface{}
   130  		expectedErr bool
   131  	}{
   132  		{`""`, MustJSON(`""`), false},
   133  		{[]int{1, 2}, MustJSON(`[1, 2]`), false},
   134  		{`{"a": true, "b": 3}`, MustJSON(`{"a":true,"b":3}`), false},
   135  		{[]byte(`{"a": true, "b": 3}`), MustJSON(`{"a":true,"b":3}`), false},
   136  		{testStruct{Field: "test"}, MustJSON(`{"field":"test"}`), false},
   137  		{MustJSON(`{"field":"test"}`), MustJSON(`{"field":"test"}`), false},
   138  		{[]string{}, MustJSON(`[]`), false},
   139  		{[]string{`555-555-5555`}, MustJSON(`["555-555-5555"]`), false},
   140  	}
   141  
   142  	for _, test := range tests {
   143  		t.Run(fmt.Sprintf("%v %v", test.val, test.expectedVal), func(t *testing.T) {
   144  			val, _, err := JSON.Convert(test.val)
   145  			if test.expectedErr {
   146  				assert.Error(t, err)
   147  			} else {
   148  				require.NoError(t, err)
   149  				assert.Equal(t, test.expectedVal, val)
   150  				if val != nil {
   151  					assert.True(t, reflect.TypeOf(val).Implements(JSON.ValueType()))
   152  				}
   153  			}
   154  		})
   155  	}
   156  }
   157  
   158  func TestJsonString(t *testing.T) {
   159  	require.Equal(t, "json", JSON.String())
   160  }
   161  
   162  func TestJsonSQL(t *testing.T) {
   163  	tests := []struct {
   164  		val         interface{}
   165  		expectedErr bool
   166  	}{
   167  		{`""`, false},
   168  		{`"555-555-555"`, false},
   169  		{`{}`, false},
   170  		{`{"field":"test"}`, false},
   171  		{MustJSON(`{"field":"test"}`), false},
   172  		{"1", false},
   173  		{`[1,2,3]`, false},
   174  		{[]int{1, 2, 3}, false},
   175  		{[]string{"1", "2", "3"}, false},
   176  		{"thisisbad", true},
   177  	}
   178  
   179  	for _, test := range tests {
   180  		t.Run(fmt.Sprintf("%v", test.val), func(t *testing.T) {
   181  			val, err := JSON.SQL(sql.NewEmptyContext(), nil, test.val)
   182  			if test.expectedErr {
   183  				require.Error(t, err)
   184  			} else {
   185  				require.NoError(t, err)
   186  				assert.Equal(t, query.Type_JSON, val.Type())
   187  			}
   188  		})
   189  	}
   190  
   191  	// test that nulls are null
   192  	t.Run(fmt.Sprintf("%v", nil), func(t *testing.T) {
   193  		val, err := JSON.SQL(sql.NewEmptyContext(), nil, nil)
   194  		require.NoError(t, err)
   195  		assert.Equal(t, query.Type_NULL_TYPE, val.Type())
   196  	})
   197  }
   198  
   199  func TestValuer(t *testing.T) {
   200  	var empty JSONDocument
   201  	res, err := empty.Value()
   202  	require.NoError(t, err)
   203  	require.Equal(t, nil, res)
   204  
   205  	withVal := JSONDocument{
   206  		Val: map[string]string{
   207  			"a": "one",
   208  		},
   209  	}
   210  	res, err = withVal.Value()
   211  	require.NoError(t, err)
   212  	require.Equal(t, `{"a": "one"}`, res)
   213  }
   214  
   215  type JsonRoundtripTest struct {
   216  	desc     string
   217  	input    string
   218  	expected string
   219  }
   220  
   221  var JsonRoundtripTests = []JsonRoundtripTest{
   222  	{
   223  		desc:     "formatted json",
   224  		input:    `{"a": 1, "b": 2}`,
   225  		expected: `{"a": 1, "b": 2}`,
   226  	},
   227  	{
   228  		desc:     "unordered keys with correct spacing",
   229  		input:    `{"b": 2, "a": 1}`,
   230  		expected: `{"a": 1, "b": 2}`,
   231  	},
   232  	{
   233  		desc:     "missing spaces after comma and colon",
   234  		input:    `{"a":1,"b":2}`,
   235  		expected: `{"a": 1, "b": 2}`,
   236  	},
   237  	{
   238  		desc:     "unordered keys with no spacing",
   239  		input:    `{"b":2,"a":1}`,
   240  		expected: `{"a": 1, "b": 2}`,
   241  	},
   242  	{
   243  		desc:     "unordered keys with extra spaces",
   244  		input:    `{"b" : 2, "a" : 1}`,
   245  		expected: `{"a": 1, "b": 2}`,
   246  	},
   247  	{
   248  		desc:     "unordered keys with extra spaces and missing spaces after comma and colon",
   249  		input:    `{"b" :2,"a" :1}`,
   250  		expected: `{"a": 1, "b": 2}`,
   251  	},
   252  	{
   253  		desc:     "arrays of primitives without spaces",
   254  		input:    `{"a":[1,2,3],"b":[4,5,6]}`,
   255  		expected: `{"a": [1, 2, 3], "b": [4, 5, 6]}`,
   256  	},
   257  	{
   258  		desc:     "unordered keys with arrays of primitives",
   259  		input:    `{"b":[4,5,6],"a":[1,2,3]}`,
   260  		expected: `{"a": [1, 2, 3], "b": [4, 5, 6]}`,
   261  	},
   262  	{
   263  		desc:     "arrays of objects without spaces",
   264  		input:    `{"a":[{"a":1},{"b":2}],"b":[{"c":3},{"d":4}]}`,
   265  		expected: `{"a": [{"a": 1}, {"b": 2}], "b": [{"c": 3}, {"d": 4}]}`,
   266  	},
   267  	{
   268  		desc:     "unordered keys with arrays of objects",
   269  		input:    `{"b":[{"c":3},{"d":4}],"a":[{"a":1},{"b":2}]}`,
   270  		expected: `{"a": [{"a": 1}, {"b": 2}], "b": [{"c": 3}, {"d": 4}]}`,
   271  	},
   272  	{
   273  		desc:     "unordered keys with arrays of objects with extra spaces",
   274  		input:    `{"b" : [ { "c" : 3 }, { "d" : 4 } ], "a" : [ { "a" : 1 }, { "b" : 2 } ] }`,
   275  		expected: `{"a": [{"a": 1}, {"b": 2}], "b": [{"c": 3}, {"d": 4}]}`,
   276  	},
   277  	{
   278  		desc:     "arrays of objects with extra spaces",
   279  		input:    `{"a": [ { "a" : 1 }, { "b" : 2 } ], "b": [ { "c" : 3 }, { "d" : 4 } ] }`,
   280  		expected: `{"a": [{"a": 1}, {"b": 2}], "b": [{"c": 3}, {"d": 4}]}`,
   281  	},
   282  }
   283  
   284  func TestJsonRoundTripping(t *testing.T) {
   285  	for _, test := range JsonRoundtripTests {
   286  		t.Run("JSON roundtripping: "+test.desc, func(t *testing.T) {
   287  			val, err := JSON.SQL(sql.NewEmptyContext(), nil, test.input)
   288  			require.NoError(t, err)
   289  			assert.Equal(t, test.expected, val.ToString())
   290  		})
   291  	}
   292  }
   293  
   294  type JsonMutationTest struct {
   295  	desc      string
   296  	doc       string
   297  	path      string
   298  	value     string
   299  	resultVal string
   300  	changed   bool
   301  	//	expectErrStr string
   302  }
   303  
   304  var JsonSetTests = []JsonMutationTest{
   305  	{
   306  		desc:      "set root",
   307  		doc:       `{"a": 1, "b": 2}`,
   308  		path:      "$",
   309  		value:     `{"c": 3}`,
   310  		resultVal: `{"c": 3}`,
   311  		changed:   true,
   312  	},
   313  	{
   314  		desc:      "set root ignore white space",
   315  		doc:       `{"a": 1, "b": 2}`,
   316  		path:      "   $   ",
   317  		value:     `{"c": 3}`,
   318  		resultVal: `{"c": 3}`,
   319  		changed:   true,
   320  	},
   321  	{
   322  		desc:      "set middle of an array",
   323  		doc:       `[1, 2, 3]`,
   324  		path:      "$[1]",
   325  		value:     `42`,
   326  		resultVal: `[1, 42, 3]`,
   327  		changed:   true,
   328  	},
   329  	{
   330  		desc:      "set last item of an array",
   331  		doc:       `[1, 2, 3]`,
   332  		path:      "$[2]",
   333  		value:     `42`,
   334  		resultVal: `[1, 2, 42]`,
   335  		changed:   true,
   336  	},
   337  	{
   338  		desc:      "append to an array when overflown",
   339  		doc:       `[1, 2, 3]`,
   340  		path:      "$[23]",
   341  		value:     `42`,
   342  		resultVal: `[1, 2, 3, 42]`,
   343  		changed:   true,
   344  	},
   345  	{
   346  		desc:      "set 'last' element of an array",
   347  		doc:       `[1, 2, 3]`,
   348  		path:      "$[last]",
   349  		value:     `42`,
   350  		resultVal: `[1, 2, 42]`,
   351  		changed:   true,
   352  	},
   353  	{
   354  		desc:      "set 'last-0' element of an array",
   355  		doc:       `[1, 2, 3]`,
   356  		path:      "$[last-0]",
   357  		value:     `42`,
   358  		resultVal: `[1, 2, 42]`,
   359  		changed:   true,
   360  	},
   361  	{
   362  		desc:      "set 'last-23' element of an array",
   363  		doc:       `[1, 2, 3]`,
   364  		path:      "$[last-23]",
   365  		value:     `42`,
   366  		resultVal: `[42, 2, 3]`,
   367  		changed:   true,
   368  	},
   369  	{
   370  		desc:      "array index ignores white space",
   371  		doc:       `[1, 2, 3]`,
   372  		path:      "$[   last -    1]",
   373  		value:     `42`,
   374  		resultVal: `[1, 42, 3]`,
   375  		changed:   true,
   376  	},
   377  	{
   378  		desc:      "empty array last index is 0",
   379  		doc:       `[]`,
   380  		path:      "$[last]",
   381  		value:     `42`,
   382  		resultVal: `[42]`,
   383  		changed:   true,
   384  	},
   385  	{
   386  		desc:      "treating object as an array replaces for index 0",
   387  		doc:       `{"a":1}`,
   388  		path:      "$[0]",
   389  		value:     `42`,
   390  		resultVal: `42`,
   391  		changed:   true,
   392  	},
   393  	{
   394  		desc:      "treating object as an array replaces for index last",
   395  		doc:       `{"a":1}`,
   396  		path:      "$[last]",
   397  		value:     `42`,
   398  		resultVal: `42`,
   399  		changed:   true,
   400  	},
   401  	{
   402  		desc:      "treating object will prefix as an array",
   403  		doc:       `{"a":1}`,
   404  		path:      "$[last-23]",
   405  		value:     `42`,
   406  		resultVal: `[42, {"a": 1}]`,
   407  		changed:   true,
   408  	},
   409  	{
   410  		desc:      "treating object will append as an array for out of bounds",
   411  		doc:       `{"a":1}`,
   412  		path:      "$[51]",
   413  		value:     `42`,
   414  		resultVal: `[{"a": 1}, 42]`,
   415  		changed:   true,
   416  	},
   417  	{
   418  		desc:      "scalar will append as an array for out of bounds",
   419  		doc:       `17`,
   420  		path:      "$[51]",
   421  		value:     `42`,
   422  		resultVal: `[17, 42]`,
   423  		changed:   true,
   424  	},
   425  	{
   426  		desc:      "scalar will be overwritten for index 0",
   427  		doc:       `17`,
   428  		path:      "$[0]",
   429  		value:     `42`,
   430  		resultVal: `42`,
   431  		changed:   true,
   432  	},
   433  	{
   434  		desc:      "scalar will be prefixed when underflow happens",
   435  		doc:       `17`,
   436  		path:      "$[last-23]",
   437  		value:     `42`,
   438  		resultVal: `[42, 17]`,
   439  		changed:   true,
   440  	},
   441  	{
   442  		desc:      "Object field updated",
   443  		doc:       `{"a": 1}`,
   444  		path:      "$.a",
   445  		value:     `42`,
   446  		resultVal: `{"a": 42}`,
   447  		changed:   true,
   448  	},
   449  	{
   450  		desc:      "Object field set",
   451  		doc:       `{"a": 1}`,
   452  		path:      "$.b",
   453  		value:     `42`,
   454  		resultVal: `{"a": 1, "b": 42}`,
   455  		changed:   true,
   456  	},
   457  	{
   458  		desc:      "Object field set Unicode",
   459  		doc:       `{"โค๏ธ๐Ÿงก๐Ÿ’›๐Ÿ’š๐Ÿ’™๐Ÿ’œ": {}}`,
   460  		path:      `$."โค๏ธ๐Ÿงก๐Ÿ’›๐Ÿ’š๐Ÿ’™๐Ÿ’œ"`,
   461  		value:     `42`,
   462  		resultVal: `{"โค๏ธ๐Ÿงก๐Ÿ’›๐Ÿ’š๐Ÿ’™๐Ÿ’œ": 42 }`,
   463  		changed:   true,
   464  	},
   465  	{
   466  		desc:      "Object field name can optionally have quotes",
   467  		doc:       `{"a": {}}`,
   468  		path:      `$."a"`,
   469  		value:     `42`,
   470  		resultVal: `{"a": 42 }`,
   471  		changed:   true,
   472  	},
   473  	{
   474  		desc:      "object field name can contain escaped quotes",
   475  		doc:       `{"\"a\"": {}}`,
   476  		path:      `$."\"a\""`,
   477  		value:     `42`,
   478  		resultVal: `{"\"a\"": 42 }`,
   479  		changed:   true,
   480  	},
   481  	{
   482  		desc:      "Object field can be set to null",
   483  		doc:       `{"a": {}}`,
   484  		path:      `$."a"`,
   485  		value:     `null`,
   486  		resultVal: `{"a": null }`,
   487  		changed:   true,
   488  	},
   489  	{
   490  		desc:      "Array treated as an object is a no op",
   491  		doc:       `[1, 2, 3]`,
   492  		path:      `$.a`,
   493  		value:     `42`,
   494  		resultVal: `[1, 2, 3]`,
   495  		changed:   false,
   496  	},
   497  	{
   498  		desc:      "Setting in a nested array works",
   499  		doc:       `[1, [2]]`,
   500  		path:      "$[1][0]",
   501  		changed:   true,
   502  		value:     `42`,
   503  		resultVal: `[1, [42]]`,
   504  	},
   505  	{
   506  		desc:      "Setting in a nested objects works",
   507  		doc:       `{"a": {"b": 1}}`,
   508  		path:      "$.a.b",
   509  		changed:   true,
   510  		value:     `42`,
   511  		resultVal: `{"a": {"b": 42}}`,
   512  	},
   513  	{
   514  		desc:      "Setting in a nested several levels deep works",
   515  		doc:       `{"a": {"b": [1,2,3,4,[5,6,7]]}}`,
   516  		path:      "$.a.b[4][1]",
   517  		changed:   true,
   518  		value:     `96`,
   519  		resultVal: `{"a": {"b": [1,2,3,4,[5,96,7]]}}`,
   520  	},
   521  	{
   522  		desc:      "setting in a nested several levels deep works",
   523  		doc:       `[9,8, {"a": [3,4,5] } ]`,
   524  		path:      "$[2].a[0]",
   525  		changed:   true,
   526  		value:     `96`,
   527  		resultVal: `[9,8, {"a": [96,4,5]}]`,
   528  	},
   529  	{
   530  		desc:      "setting a deep path has no effect",
   531  		doc:       `{}`,
   532  		path:      "$.a.b.c",
   533  		changed:   false,
   534  		value:     `42`,
   535  		resultVal: `{}`,
   536  	},
   537  	{
   538  		desc:      "setting a deep path has no effect",
   539  		doc:       `{}`,
   540  		path:      "$.a.b[last]",
   541  		changed:   false,
   542  		value:     `42`,
   543  		resultVal: `{}`,
   544  	},
   545  	{
   546  		// I've verified that null in MySQL is treated as a scalar in all ways that I can tell, which makes sense.
   547  		// Therefore, testing beyond these two tests doesn't necessary.
   548  		desc:      "setting a null doc with a value results in the value",
   549  		doc:       `null`,
   550  		path:      "$",
   551  		changed:   true,
   552  		value:     `{"a": 42}`,
   553  		resultVal: `{"a": 42}`,
   554  	},
   555  	{
   556  		desc:      "setting a null doc with a value results in the value",
   557  		doc:       `{"a" : 1}`,
   558  		path:      "$",
   559  		changed:   true,
   560  		value:     `null`,
   561  		resultVal: `null`,
   562  	},
   563  
   564  	/* Known ways we don't behave like MySQL. Frankly if anyone is depending on these behaviors they are doing it wrong.
   565  		   mysql> select JSON_SET('{"a": 1}', "$[0][0]", 42);
   566  		+-------------------------------------+
   567  		| JSON_SET('{"a": 1}', "$[0][0]", 42) |
   568  		+-------------------------------------+
   569  		| 42                                  |
   570  		+-------------------------------------+
   571  
   572  		mysql> select JSON_SET('{"a": 1}', "$[0][1]", 42);
   573  		+-------------------------------------+
   574  		| JSON_SET('{"a": 1}', "$[0][1]", 42) |
   575  		+-------------------------------------+
   576  		| [{"a": 1}, 42]                      |
   577  		+-------------------------------------+
   578  
   579  		mysql> select JSON_SET('{"a": 1}', "$[0][last-3]", 42);
   580  		+------------------------------------------+
   581  		| JSON_SET('{"a": 1}', "$[0][last-3]", 42) |
   582  		+------------------------------------------+
   583  		| [42, {"a": 1}]                           |
   584  		+------------------------------------------+
   585  	    The three examples above seems to indicate that MySQL coerces objects to arrays earlier in the search process than we do.
   586  		Reason for thinking this is that  JSON_SET('{"a": 1}', "$[0][0][ANYTHING]", 42); is a no op.
   587  	*/
   588  
   589  }
   590  
   591  func TestJsonSet(t *testing.T) {
   592  	for _, test := range JsonSetTests {
   593  		t.Run("JSON set: "+test.desc, func(t *testing.T) {
   594  			doc := MustJSON(test.doc)
   595  			val := MustJSON(test.value)
   596  			res, changed, err := doc.Set(test.path, val)
   597  			require.NoError(t, err)
   598  			assert.Equal(t, MustJSON(test.resultVal), res)
   599  			assert.Equal(t, test.changed, changed)
   600  		})
   601  	}
   602  }
   603  
   604  var JsonInsertTests = []JsonMutationTest{
   605  	{
   606  		desc:      "insert root",
   607  		doc:       `{"a": 1, "b": 2}`,
   608  		path:      "$",
   609  		value:     `{"c": 3}`,
   610  		resultVal: `{"a": 1, "b": 2}`,
   611  		changed:   false,
   612  	},
   613  
   614  	{
   615  		desc:      "insert root ignore white space",
   616  		doc:       `{"a": 1, "b": 2}`,
   617  		path:      "   $   ",
   618  		value:     `{"c": 3}`,
   619  		resultVal: `{"a": 1, "b": 2}`,
   620  		changed:   false,
   621  	},
   622  	{
   623  		desc:      "insert middle of an array",
   624  		doc:       `[1, 2, 3]`,
   625  		path:      "$[1]",
   626  		value:     `42`,
   627  		resultVal: `[1, 2, 3]`,
   628  		changed:   false,
   629  	},
   630  	{
   631  		desc:      "insert last item of an array does nothing",
   632  		doc:       `[1, 2, 3]`,
   633  		path:      "$[2]",
   634  		value:     `42`,
   635  		resultVal: `[1, 2, 3]`,
   636  		changed:   false,
   637  	},
   638  	{
   639  		desc:      "append to an array when overflown",
   640  		doc:       `[1, 2, 3]`,
   641  		path:      "$[23]",
   642  		value:     `42`,
   643  		resultVal: `[1, 2, 3, 42]`,
   644  		changed:   true,
   645  	},
   646  	{
   647  		desc:      "insert 'last' element of an array does nothing",
   648  		doc:       `[1, 2, 3]`,
   649  		path:      "$[last]",
   650  		value:     `42`,
   651  		resultVal: `[1, 2, 3]`,
   652  		changed:   false,
   653  	},
   654  	{
   655  		desc:      "insert into empty array mutates",
   656  		doc:       `[]`,
   657  		path:      "$[last]",
   658  		value:     `42`,
   659  		resultVal: `[42]`,
   660  		changed:   true,
   661  	},
   662  	{
   663  		desc:      "treating object as an array replaces for index 0",
   664  		doc:       `{"a":1}`,
   665  		path:      "$[0]",
   666  		value:     `42`,
   667  		resultVal: `{"a":1}`,
   668  		changed:   false,
   669  	},
   670  	{
   671  		// Can't make this stuff up.
   672  		//	mysql> select JSON_INSERT(JSON_OBJECT("a",1),'$[last-21]', 42);
   673  		// +--------------------------------------------------+
   674  		// | JSON_INSERT(JSON_OBJECT("a",1),'$[last-21]', 42) |
   675  		// +--------------------------------------------------+
   676  		// | [42, {"a": 1}]                                   |
   677  		// +--------------------------------------------------+
   678  		desc:      "treating object will prefix as an array",
   679  		doc:       `{"a":1}`,
   680  		path:      "$[last-23]",
   681  		value:     `42`,
   682  		resultVal: `[42, {"a": 1}]`,
   683  		changed:   true,
   684  	},
   685  	{
   686  		// mysql> select JSON_INSERT(JSON_OBJECT("a",1),'$[51]', 42);
   687  		// +---------------------------------------------+
   688  		// | JSON_INSERT(JSON_OBJECT("a",1),'$[51]', 42) |
   689  		// +---------------------------------------------+
   690  		// | [{"a": 1}, 42]                              |
   691  		// +---------------------------------------------+
   692  		desc:      "treating object will append as an array for out of bounds",
   693  		doc:       `{"a":1}`,
   694  		path:      "$[51]",
   695  		value:     `42`,
   696  		resultVal: `[{"a": 1}, 42]`,
   697  		changed:   true,
   698  	},
   699  	{
   700  		desc:      "scalar will append as an array for out of bounds",
   701  		doc:       `17`,
   702  		path:      "$[51]",
   703  		value:     `42`,
   704  		resultVal: `[17, 42]`,
   705  		changed:   true,
   706  	},
   707  	{
   708  		desc:      "scalar will not be overwritten for index 0",
   709  		doc:       `17`,
   710  		path:      "$[0]",
   711  		value:     `42`,
   712  		resultVal: `17`,
   713  		changed:   false,
   714  	},
   715  	{
   716  		desc:      "scalar will be prefixed when underflow happens",
   717  		doc:       `17`,
   718  		path:      "$[last-23]",
   719  		value:     `42`,
   720  		resultVal: `[42, 17]`,
   721  		changed:   true,
   722  	},
   723  	{
   724  		desc:      "existing object field not updated",
   725  		doc:       `{"a": 1}`,
   726  		path:      "$.a",
   727  		value:     `42`,
   728  		resultVal: `{"a": 1}`,
   729  		changed:   false,
   730  	},
   731  	{
   732  		desc:      "new object field inserted",
   733  		doc:       `{"a": 1}`,
   734  		path:      "$.b",
   735  		value:     `42`,
   736  		resultVal: `{"a": 1, "b": 42}`,
   737  		changed:   true,
   738  	},
   739  	{
   740  		desc:      "object field name can optionally have quotes",
   741  		doc:       `{"a": {}}`,
   742  		path:      `$."a"`,
   743  		value:     `42`,
   744  		resultVal: `{"a": {} }`,
   745  		changed:   false,
   746  	},
   747  	{
   748  		desc:      "object field name can contain escaped quotes",
   749  		doc:       `{"\"a\"": {}}`,
   750  		path:      `$."\"a\""`,
   751  		value:     `42`,
   752  		resultVal: `{"\"a\"": {} }`,
   753  		changed:   false,
   754  	},
   755  	{
   756  		desc:      "array treated as an object is a no op",
   757  		doc:       `[1, 2, 3]`,
   758  		path:      `$.a`,
   759  		value:     `42`,
   760  		resultVal: `[1, 2, 3]`,
   761  		changed:   false,
   762  	},
   763  
   764  	{
   765  		desc:      "inserting a deep path has no effect",
   766  		doc:       `{}`,
   767  		path:      "$.a.b.c",
   768  		changed:   false,
   769  		value:     `42`,
   770  		resultVal: `{}`,
   771  	},
   772  	{
   773  		desc:      "inserting a deep path has no effect",
   774  		doc:       `{}`,
   775  		path:      "$.a.b[last]",
   776  		changed:   false,
   777  		value:     `42`,
   778  		resultVal: `{}`,
   779  	},
   780  }
   781  
   782  func TestJsonInsert(t *testing.T) {
   783  	for _, test := range JsonInsertTests {
   784  		t.Run("JSON insert: "+test.desc, func(t *testing.T) {
   785  			doc := MustJSON(test.doc)
   786  			val := MustJSON(test.value)
   787  			res, changed, err := doc.Insert(test.path, val)
   788  			require.NoError(t, err)
   789  			assert.Equal(t, MustJSON(test.resultVal), res)
   790  			assert.Equal(t, test.changed, changed)
   791  		})
   792  	}
   793  }
   794  
   795  var JsonRemoveTests = []JsonMutationTest{
   796  	{
   797  		desc:      "remove middle of an array",
   798  		doc:       `[1, 2, 3]`,
   799  		path:      "$[1]",
   800  		resultVal: `[1, 3]`,
   801  		changed:   true,
   802  	},
   803  	{
   804  		desc:      "remove last item of an array",
   805  		doc:       `[1, 2, 3]`,
   806  		path:      "$[2]",
   807  		resultVal: `[1, 2]`,
   808  		changed:   true,
   809  	},
   810  	{
   811  		desc:      "no op remove on array when overflown",
   812  		doc:       `[1, 2, 3]`,
   813  		path:      "$[23]",
   814  		resultVal: `[1, 2, 3]`,
   815  		changed:   false,
   816  	},
   817  	{
   818  		desc:      "remove 'last' element of an array",
   819  		doc:       `[1, 2, 3]`,
   820  		path:      "$[last]",
   821  		resultVal: `[1, 2]`,
   822  		changed:   true,
   823  	},
   824  	{
   825  		desc:      "remove 'last-0' element of an array",
   826  		doc:       `[1, 2, 3]`,
   827  		path:      "$[last-0]",
   828  		resultVal: `[1, 2]`,
   829  		changed:   true,
   830  	},
   831  	{
   832  		desc:      "no op remove on underflow 'last-23' element of an array",
   833  		doc:       `[1, 2, 3]`,
   834  		path:      "$[last-23]",
   835  		resultVal: `[1, 2, 3]`,
   836  		changed:   false,
   837  	},
   838  	{
   839  		desc:      "no op remove with empty array",
   840  		doc:       `[]`,
   841  		path:      "$[last]",
   842  		resultVal: `[]`,
   843  		changed:   false,
   844  	},
   845  	{
   846  		// Remove behaves much more reasonably than other operations when the path is invalid. When you treat an
   847  		// object or scalar as an array, it is a no-op. period. For this reason, there are far fewer remove tests than
   848  		// there are for insert/set/replace.
   849  		desc:      "treating object as an array is no op",
   850  		doc:       `{"a":1}`,
   851  		path:      "$[0]",
   852  		resultVal: `{"a" : 1}`,
   853  		changed:   false,
   854  	},
   855  	{
   856  		desc:      "scalar will append as an array for out of bounds",
   857  		doc:       `17`,
   858  		path:      "$[0]",
   859  		resultVal: `17`,
   860  		changed:   false,
   861  	},
   862  	{
   863  		desc:      "Object field updated",
   864  		doc:       `{"a": 1, "b": 2}`,
   865  		path:      "$.a",
   866  		resultVal: `{"b": 2}`,
   867  		changed:   true,
   868  	},
   869  	{
   870  		desc:      "No op object change when field not found",
   871  		doc:       `{"a": 1}`,
   872  		path:      "$.b",
   873  		resultVal: `{"a": 1}`,
   874  		changed:   false,
   875  	},
   876  }
   877  
   878  func TestJsonRemove(t *testing.T) {
   879  	for _, test := range JsonRemoveTests {
   880  		t.Run("JSON remove: "+test.desc, func(t *testing.T) {
   881  			doc := MustJSON(test.doc)
   882  			res, changed, err := doc.Remove(test.path)
   883  			require.NoError(t, err)
   884  			assert.Equal(t, MustJSON(test.resultVal), res)
   885  			assert.Equal(t, test.changed, changed)
   886  		})
   887  	}
   888  }
   889  
   890  var JsonReplaceTests = []JsonMutationTest{
   891  	{
   892  		desc:      "replace root",
   893  		doc:       `{"a": 1, "b": 2}`,
   894  		path:      "$",
   895  		value:     `{"c": 3}`,
   896  		resultVal: `{"c": 3}`,
   897  		changed:   true,
   898  	},
   899  
   900  	{
   901  		desc:      "replace root ignore white space",
   902  		doc:       `{"a": 1, "b": 2}`,
   903  		path:      "   $   ",
   904  		value:     `{"c": 3}`,
   905  		resultVal: `{"c": 3}`,
   906  		changed:   true,
   907  	},
   908  	{
   909  		desc:      "replace middle of an array",
   910  		doc:       `[1, 2, 3]`,
   911  		path:      "$[1]",
   912  		value:     `42`,
   913  		resultVal: `[1, 42, 3]`,
   914  		changed:   true,
   915  	},
   916  	{
   917  		desc:      "set last item of an array",
   918  		doc:       `[1, 2, 3]`,
   919  		path:      "$[2]",
   920  		value:     `42`,
   921  		resultVal: `[1, 2, 42]`,
   922  		changed:   true,
   923  	},
   924  	{
   925  		desc:      "noupdate to an array when overflown",
   926  		doc:       `[1, 2, 3]`,
   927  		path:      "$[23]",
   928  		value:     `42`,
   929  		resultVal: `[1, 2, 3]`,
   930  		changed:   false,
   931  	},
   932  	{
   933  		desc:      "replace 'last' element of an array",
   934  		doc:       `[1, 2, 3]`,
   935  		path:      "$[last]",
   936  		value:     `42`,
   937  		resultVal: `[1, 2, 42]`,
   938  		changed:   true,
   939  	},
   940  	{
   941  		// mysql> select JSON_REPLACE(JSON_ARRAY(1,2,3),'$[last-23]', 42);
   942  		// +--------------------------------------------------+
   943  		// | JSON_REPLACE(JSON_ARRAY(1,2,3),'$[last-23]', 42) |
   944  		// +--------------------------------------------------+
   945  		// | [1, 2, 3]                                        |
   946  		// +--------------------------------------------------+
   947  		// 1 row in set (0.00 sec)
   948  		desc:      "no update for element underflow",
   949  		doc:       `[1, 2, 3]`,
   950  		path:      "$[last-23]",
   951  		value:     `42`,
   952  		resultVal: `[1, 2, 3]`,
   953  		changed:   false,
   954  	},
   955  	{
   956  		desc:      "no update for empty array",
   957  		doc:       `[]`,
   958  		path:      "$[0]",
   959  		value:     `42`,
   960  		resultVal: `[]`,
   961  		changed:   false,
   962  	},
   963  	{
   964  		desc:      "treating object as an array replaces for index 0",
   965  		doc:       `{"a":1}`,
   966  		path:      "$[0]",
   967  		value:     `42`,
   968  		resultVal: `42`,
   969  		changed:   true,
   970  	},
   971  	{
   972  		// mysql> select JSON_REPLACE(JSON_OBJECT("a",1),'$[last]', 42);
   973  		// +------------------------------------------------+
   974  		// | JSON_REPLACE(JSON_OBJECT("a",1),'$[last]', 42) |
   975  		// +------------------------------------------------+
   976  		// | 42                                             |
   977  		// +------------------------------------------------+
   978  		desc:      "treating object as an array replaces for index last",
   979  		doc:       `{"a":1}`,
   980  		path:      "$[last]",
   981  		value:     `42`,
   982  		resultVal: `42`,
   983  		changed:   true,
   984  	},
   985  	{
   986  		desc:      "no op when treating object as an array with underflow",
   987  		doc:       `{"a":1}`,
   988  		path:      "$[last-23]",
   989  		value:     `42`,
   990  		resultVal: `{"a": 1}`,
   991  		changed:   false,
   992  	},
   993  	{
   994  		desc:      "no op when treating object as an array with overflow",
   995  		doc:       `{"a":1}`,
   996  		path:      "$[51]",
   997  		value:     `42`,
   998  		resultVal: `{"a": 1}`,
   999  		changed:   false,
  1000  	},
  1001  	{
  1002  		desc:      "no update for scalar will treated as an array for out of bounds",
  1003  		doc:       `17`,
  1004  		path:      "$[51]",
  1005  		value:     `42`,
  1006  		resultVal: `17`,
  1007  		changed:   false,
  1008  	},
  1009  	{
  1010  		desc:      "scalar will be overwritten for index 0",
  1011  		doc:       `17`,
  1012  		path:      "$[0]",
  1013  		value:     `42`,
  1014  		resultVal: `42`,
  1015  		changed:   true,
  1016  	},
  1017  	{
  1018  		desc:      "no update for scalar when used as an array with underflow",
  1019  		doc:       `17`,
  1020  		path:      "$[last-23]",
  1021  		value:     `42`,
  1022  		resultVal: `17`,
  1023  		changed:   false,
  1024  	},
  1025  	{
  1026  		desc:      "Object field updated",
  1027  		doc:       `{"a": 1}`,
  1028  		path:      "$.a",
  1029  		value:     `42`,
  1030  		resultVal: `{"a": 42}`,
  1031  		changed:   true,
  1032  	},
  1033  	{
  1034  		desc:      "Object field not inserted",
  1035  		doc:       `{"a": 1}`,
  1036  		path:      "$.b",
  1037  		value:     `42`,
  1038  		resultVal: `{"a": 1}`,
  1039  		changed:   false,
  1040  	},
  1041  }
  1042  
  1043  func TestJsonReplace(t *testing.T) {
  1044  	for _, test := range JsonReplaceTests {
  1045  		t.Run("JSON replace: "+test.desc, func(t *testing.T) {
  1046  			doc := MustJSON(test.doc)
  1047  			val := MustJSON(test.value)
  1048  			res, changed, err := doc.Replace(test.path, val)
  1049  			require.NoError(t, err)
  1050  			assert.Equal(t, MustJSON(test.resultVal), res)
  1051  			assert.Equal(t, test.changed, changed)
  1052  		})
  1053  	}
  1054  }
  1055  
  1056  var JsonArrayAppendTests = []JsonMutationTest{
  1057  	{
  1058  		desc:      "append to empty object",
  1059  		doc:       `{}`,
  1060  		path:      "$",
  1061  		value:     `42`,
  1062  		changed:   true,
  1063  		resultVal: `[{}, 42]`,
  1064  	},
  1065  	{
  1066  		desc:      "append to empty array",
  1067  		doc:       `[]`,
  1068  		path:      "$",
  1069  		value:     `42`,
  1070  		changed:   true,
  1071  		resultVal: `[42]`,
  1072  	},
  1073  	{
  1074  		desc:      "append to a nested array",
  1075  		doc:       `[{"a": [1, 2, 3, 4]}]`,
  1076  		path:      "$[0].a",
  1077  		value:     `42`,
  1078  		changed:   true,
  1079  		resultVal: `[{"a": [1, 2, 3, 4, 42]}]`,
  1080  	},
  1081  	{
  1082  		desc:      "append a scalar to a scalar leads to an array",
  1083  		doc:       `{"a": "eh"}`,
  1084  		path:      "$.a",
  1085  		changed:   true,
  1086  		value:     `42`,
  1087  		resultVal: `{"a": ["eh", 42]}`,
  1088  	},
  1089  	{
  1090  		desc:      "append to an array index",
  1091  		doc:       `[[1, 2, 3], {"a": "eh"}]`,
  1092  		path:      "$[0]",
  1093  		changed:   true,
  1094  		value:     `42`,
  1095  		resultVal: `[[1, 2, 3, 42], {"a": "eh"}]`,
  1096  	},
  1097  	{
  1098  		desc:      "append to an array index",
  1099  		doc:       `[{"b" : "be"}, {"a": "eh"}]`,
  1100  		path:      "$[0]",
  1101  		changed:   true,
  1102  		value:     `42`,
  1103  		resultVal: `[[{"b" : "be"}, 42], {"a": "eh"}]`,
  1104  	},
  1105  	{
  1106  		desc:      "no op when out of bounds",
  1107  		doc:       `[1, 2, 3]`,
  1108  		path:      "$[51]",
  1109  		changed:   false,
  1110  		value:     `42`,
  1111  		resultVal: `[1, 2, 3]`,
  1112  	},
  1113  	{
  1114  		desc:      "last index works for lookup",
  1115  		doc:       `[1, 2, [3,4]]`,
  1116  		path:      "$[last]",
  1117  		changed:   true,
  1118  		value:     `42`,
  1119  		resultVal: `[1, 2, [3, 4, 42]]`,
  1120  	},
  1121  	{
  1122  		desc:      "no op when there is an underflow",
  1123  		doc:       `[1, 2, 3]`,
  1124  		path:      "$[last-23]",
  1125  		changed:   false,
  1126  		value:     `42`,
  1127  		resultVal: `[1, 2, 3]`,
  1128  	},
  1129  }
  1130  
  1131  func TestJsonArrayAppend(t *testing.T) {
  1132  	for _, test := range JsonArrayAppendTests {
  1133  		t.Run("JSON array append: "+test.desc, func(t *testing.T) {
  1134  			doc := MustJSON(test.doc)
  1135  			val := MustJSON(test.value)
  1136  			res, changed, err := doc.ArrayAppend(test.path, val)
  1137  			require.NoError(t, err)
  1138  			assert.Equal(t, MustJSON(test.resultVal), res)
  1139  			assert.Equal(t, test.changed, changed)
  1140  		})
  1141  	}
  1142  }
  1143  
  1144  var JsonArrayInsertTests = []JsonMutationTest{
  1145  	{
  1146  		desc:      "array insert overflow appends",
  1147  		doc:       `[1,2,3]`,
  1148  		path:      "$[51]",
  1149  		value:     `42`,
  1150  		resultVal: `[1,2,3,42]`,
  1151  		changed:   true,
  1152  	},
  1153  	{
  1154  		desc:      "array insert at first element",
  1155  		doc:       `[1,2,3]`,
  1156  		path:      "$[0]",
  1157  		value:     `42`,
  1158  		resultVal: `[42,1,2,3]`,
  1159  		changed:   true,
  1160  	},
  1161  	{
  1162  		desc:      "array insert at second element",
  1163  		doc:       `[1,2,3]`,
  1164  		path:      "$[1]",
  1165  		value:     `42`,
  1166  		resultVal: `[1,42,2,3]`,
  1167  		changed:   true,
  1168  	},
  1169  	{
  1170  		desc:      "insert to empty array",
  1171  		doc:       `{"a" :[]}`,
  1172  		path:      "$.a[0]",
  1173  		value:     `42`,
  1174  		resultVal: `{"a" : [42]}`,
  1175  		changed:   true,
  1176  	},
  1177  	{
  1178  		desc:      "insert to an object no op",
  1179  		doc:       `{"a" :{}}`,
  1180  		path:      "$.a[0]",
  1181  		value:     `42`,
  1182  		resultVal: `{"a" : {}}`,
  1183  		changed:   false,
  1184  	},
  1185  	// mysql> select json_array_insert(json_array(1,2,3), "$[last]", 42);
  1186  	// +-----------------------------------------------------+
  1187  	// | json_array_insert(json_array(1,2,3), "$[last]", 42) |
  1188  	// +-----------------------------------------------------+
  1189  	// | [1, 2, 42, 3]                                       |
  1190  	// +-----------------------------------------------------+
  1191  	{
  1192  		desc:      "insert to [last] does crazy things",
  1193  		doc:       `[1,2,3]`,
  1194  		path:      "$[last]",
  1195  		value:     `42`,
  1196  		resultVal: `[1,2,42,3]`, // It's true. Try it yourself.
  1197  		changed:   true,
  1198  	},
  1199  	{
  1200  		desc:      "insert into non-array results in noop",
  1201  		doc:       `{}`,
  1202  		path:      "$[0]",
  1203  		value:     `42`,
  1204  		changed:   false,
  1205  		resultVal: `{}`,
  1206  	},
  1207  }
  1208  
  1209  func TestJsonArrayInsert(t *testing.T) {
  1210  	for _, test := range JsonArrayInsertTests {
  1211  		t.Run("JSON array insert: "+test.desc, func(t *testing.T) {
  1212  			doc := MustJSON(test.doc)
  1213  			val := MustJSON(test.value)
  1214  			res, changed, err := doc.ArrayInsert(test.path, val)
  1215  			require.NoError(t, err)
  1216  			assert.Equal(t, MustJSON(test.resultVal), res)
  1217  			assert.Equal(t, test.changed, changed)
  1218  		})
  1219  	}
  1220  }
  1221  
  1222  type parseErrTest struct {
  1223  	desc         string
  1224  	doc          string
  1225  	path         string
  1226  	expectErrStr string
  1227  }
  1228  
  1229  var JsonPathParseErrTests = []parseErrTest{
  1230  	{
  1231  		desc:         "empty path",
  1232  		path:         "",
  1233  		expectErrStr: "Invalid JSON path expression. Empty path",
  1234  	},
  1235  	{
  1236  		desc:         "non $ prefix",
  1237  		path:         "bogus",
  1238  		expectErrStr: "Invalid JSON path expression. Path must start with '$'",
  1239  	},
  1240  	{
  1241  		desc:         "dot to nowhere",
  1242  		path:         "$.",
  1243  		expectErrStr: "Invalid JSON path expression. Expected field name after '.' at character 2 of $.",
  1244  	},
  1245  	{
  1246  		desc:         "no . or [",
  1247  		path:         "$fu.bar",
  1248  		expectErrStr: "Invalid JSON path expression. Expected '.' or '[' at character 1 of $fu.bar",
  1249  	},
  1250  	{
  1251  		desc:         "incomplete quoted field",
  1252  		path:         `$."a"."b`,
  1253  		expectErrStr: `Invalid JSON path expression. '"' expected at character 6 of $."a"."b`,
  1254  	},
  1255  	{
  1256  		desc:         "invalid bare string",
  1257  		path:         "$.a@<>bc",
  1258  		expectErrStr: `Invalid JSON path expression. Expected '.' or '[' at character 3 of $.a@<>bc`,
  1259  	},
  1260  	{
  1261  		desc:         "non-integer array index",
  1262  		path:         "$[abcd]",
  1263  		expectErrStr: `Invalid JSON path expression. Unable to convert abcd to an int at character 2 of $[abcd]`,
  1264  	},
  1265  	{
  1266  		desc:         "non-integer array index",
  1267  		path:         "$[last-abcd]",
  1268  		expectErrStr: `Invalid JSON path expression. Expected a positive integer after 'last-' at character 6 of $[last-abcd]`,
  1269  	},
  1270  	{
  1271  		desc:         "too many dashes in last-",
  1272  		path:         "$[last-abcd-xyz]",
  1273  		expectErrStr: `Invalid JSON path expression. Unable to convert last-abcd-xyz to an int at character 2 of $[last-abcd-xyz]`,
  1274  	},
  1275  }
  1276  
  1277  func TestJsonPathErrors(t *testing.T) {
  1278  	doc := MustJSON(`{"a": {"b": 2} , "c": [1, 2, 3]}`)
  1279  
  1280  	for _, test := range JsonPathParseErrTests {
  1281  		t.Run("JSON Path: "+test.desc, func(t *testing.T) {
  1282  			_, changed, err := doc.Set(test.path, MustJSON(`{"a": 42}`))
  1283  			assert.Equal(t, false, changed)
  1284  			require.Error(t, err)
  1285  			assert.Equal(t, test.expectErrStr, err.Error())
  1286  		})
  1287  	}
  1288  }
  1289  
  1290  var JsonArrayInsertErrors = []parseErrTest{
  1291  	{
  1292  		desc:         "empty path",
  1293  		path:         "",
  1294  		expectErrStr: "Invalid JSON path expression. Empty path",
  1295  	},
  1296  	{
  1297  		desc:         "insert into root path results in an error",
  1298  		doc:          `[]`,
  1299  		path:         "$",
  1300  		expectErrStr: "Path expression is not a path to a cell in an array: $",
  1301  	},
  1302  	{
  1303  		desc:         "no op insert into non-array",
  1304  		doc:          `{"a": "eh"}`,
  1305  		path:         "$.a",
  1306  		expectErrStr: "A path expression is not a path to a cell in an array at character 3 of $.a",
  1307  	},
  1308  }
  1309  
  1310  func TestJsonInsertErrors(t *testing.T) {
  1311  	doc := MustJSON(`{"a": {"b": 2} , "c": [1, 2, 3]}`)
  1312  
  1313  	for _, test := range JsonArrayInsertErrors {
  1314  		t.Run("JSON Path: "+test.desc, func(t *testing.T) {
  1315  			_, changed, err := doc.ArrayInsert(test.path, MustJSON(`{"a": 42}`))
  1316  			assert.Equal(t, false, changed)
  1317  			require.Error(t, err)
  1318  			assert.Equal(t, test.expectErrStr, err.Error())
  1319  		})
  1320  	}
  1321  }
  1322  
  1323  func TestRemoveRoot(t *testing.T) {
  1324  	// Fairly special case situation which doesn't mesh with our other tests. MySQL returns a specfic message when you
  1325  	// attempt to remove the root document.
  1326  	doc := MustJSON(`{"a": 1, "b": 2}`)
  1327  	_, changed, err := doc.Remove("$")
  1328  
  1329  	require.Error(t, err)
  1330  	assert.Equal(t, "The path expression '$' is not allowed in this context.", err.Error())
  1331  	assert.Equal(t, false, changed)
  1332  }
  1333  
  1334  type jsonIterKV struct {
  1335  	key   string
  1336  	value interface{}
  1337  }
  1338  
  1339  type jsonIterTest struct {
  1340  	name          string
  1341  	doc           JsonObject
  1342  	expectedPairs []jsonIterKV
  1343  }
  1344  
  1345  var jsonIterTests = []jsonIterTest{
  1346  	{
  1347  		name:          "empty object",
  1348  		doc:           JsonObject{},
  1349  		expectedPairs: []jsonIterKV{},
  1350  	},
  1351  	{
  1352  		name: "iterate over keys in sorted order",
  1353  		doc:  JsonObject{"b": 1, "a": 2},
  1354  		expectedPairs: []jsonIterKV{
  1355  			{key: "a", value: 2},
  1356  			{key: "b", value: 1},
  1357  		},
  1358  	},
  1359  	{
  1360  		name: "keys use lexicographic order, not key-length order",
  1361  		doc:  JsonObject{"b": 1, "aa": 2},
  1362  		expectedPairs: []jsonIterKV{
  1363  			{key: "aa", value: 2},
  1364  			{key: "b", value: 1},
  1365  		},
  1366  	},
  1367  }
  1368  
  1369  func TestJsonIter(t *testing.T) {
  1370  	for _, test := range jsonIterTests {
  1371  		t.Run(test.name, func(t *testing.T) {
  1372  			iter := NewJSONIter(test.doc)
  1373  			pairs := make([]jsonIterKV, 0)
  1374  			for iter.HasNext() {
  1375  				var pair jsonIterKV
  1376  				var err error
  1377  				pair.key, pair.value, err = iter.Next()
  1378  				require.NoError(t, err)
  1379  				pairs = append(pairs, pair)
  1380  			}
  1381  			require.Equal(t, test.expectedPairs, pairs)
  1382  		})
  1383  	}
  1384  }