github.com/dolthub/go-mysql-server@v0.18.0/sql/expression/function/json/json_extract_test.go (about) 1 // Copyright 2020-2021 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 "strings" 19 "testing" 20 21 "github.com/stretchr/testify/require" 22 23 "github.com/dolthub/go-mysql-server/sql" 24 "github.com/dolthub/go-mysql-server/sql/expression" 25 "github.com/dolthub/go-mysql-server/sql/types" 26 ) 27 28 func TestJSONExtract(t *testing.T) { 29 f2, err := NewJSONExtract( 30 expression.NewGetField(0, types.LongText, "arg1", false), 31 expression.NewGetField(1, types.LongText, "arg2", false), 32 ) 33 require.NoError(t, err) 34 35 f3, err := NewJSONExtract( 36 expression.NewGetField(0, types.LongText, "arg1", false), 37 expression.NewGetField(1, types.LongText, "arg2", false), 38 expression.NewGetField(2, types.LongText, "arg3", false), 39 ) 40 require.NoError(t, err) 41 42 f4, err := NewJSONExtract( 43 expression.NewGetField(0, types.LongText, "arg1", false), 44 expression.NewGetField(1, types.LongText, "arg2", false), 45 expression.NewGetField(2, types.LongText, "arg3", false), 46 expression.NewGetField(3, types.LongText, "arg4", false), 47 ) 48 require.NoError(t, err) 49 50 json := map[string]interface{}{ 51 "a": []interface{}{float64(1), float64(2), float64(3), float64(4)}, 52 "b": map[string]interface{}{ 53 "c": "foo", 54 "d": true, 55 }, 56 "e": []interface{}{ 57 []interface{}{float64(1), float64(2)}, 58 []interface{}{float64(3), float64(4)}, 59 }, 60 "f": map[string]interface{}{ 61 `key.with.dots`: 0, 62 `key with spaces`: 1, 63 `key"with"dquotes`: 2, 64 `key'with'squotes`: 3, 65 `key\with\backslashes`: 4, 66 }, 67 } 68 69 testCases := []struct { 70 f sql.Expression 71 row sql.Row 72 expected interface{} 73 err error 74 }{ 75 //{f2, sql.Row{json, "FOO"}, nil, errors.New("should start with '$'")}, 76 {f2, sql.Row{nil, "$.b.c"}, nil, nil}, 77 {f2, sql.Row{json, "$.foo"}, nil, nil}, 78 {f2, sql.Row{json, "$.b.c"}, types.JSONDocument{Val: "foo"}, nil}, 79 {f3, sql.Row{json, "$.b.c", "$.b.d"}, types.JSONDocument{Val: []interface{}{"foo", true}}, nil}, 80 {f4, sql.Row{json, "$.b.c", "$.b.d", "$.e[0][*]"}, types.JSONDocument{Val: []interface{}{ 81 "foo", 82 true, 83 []interface{}{1., 2.}, 84 }}, nil}, 85 86 {f2, sql.Row{json, `$.f."key.with.dots"`}, types.JSONDocument{Val: float64(0)}, nil}, 87 {f2, sql.Row{json, `$.f."key with spaces"`}, types.JSONDocument{Val: float64(1)}, nil}, 88 {f2, sql.Row{json, `$.f.key with spaces`}, types.JSONDocument{Val: float64(1)}, nil}, 89 {f2, sql.Row{json, `$.f.key'with'squotes`}, types.JSONDocument{Val: float64(3)}, nil}, 90 {f2, sql.Row{json, `$.f."key'with'squotes"`}, types.JSONDocument{Val: float64(3)}, nil}, 91 92 // TODO: Fix these. They work in mysql 93 //{f2, sql.Row{json, `$.f.key\\"with\\"dquotes`}, sql.JSONDocument{Val: 2}, nil}, 94 //{f2, sql.Row{json, `$.f.key\'with\'squotes`}, sql.JSONDocument{Val: 3}, nil}, 95 //{f2, sql.Row{json, `$.f.key\\with\\backslashes`}, sql.JSONDocument{Val: 4}, nil}, 96 //{f2, sql.Row{json, `$.f."key\\with\\backslashes"`}, sql.JSONDocument{Val: 4}, nil}, 97 } 98 99 for _, tt := range testCases { 100 var paths []string 101 for _, path := range tt.row[1:] { 102 paths = append(paths, path.(string)) 103 } 104 105 t.Run(tt.f.String()+"."+strings.Join(paths, ","), func(t *testing.T) { 106 require := require.New(t) 107 result, err := tt.f.Eval(sql.NewEmptyContext(), tt.row) 108 if tt.err == nil { 109 require.NoError(err) 110 } else { 111 require.Error(tt.err, err) 112 } 113 114 require.Equal(tt.expected, result) 115 }) 116 } 117 } 118 119 func TestJSONExtractAsterisk(t *testing.T) { 120 jsonStr := ` 121 { 122 "key1": "abc", 123 "key2": 123, 124 "key3": [1,2,3], 125 "key4": { 126 "a": 1, 127 "b": 2, 128 "c": 3 129 } 130 }` 131 f, err := NewJSONExtract( 132 expression.NewLiteral(jsonStr, types.LongText), 133 expression.NewLiteral("$.*", types.LongText)) 134 require.NoError(t, err) 135 136 t.Run("json extract with asterisk", func(t *testing.T) { 137 require := require.New(t) 138 139 result, err := f.Eval(sql.NewEmptyContext(), nil) 140 require.NoError(err) 141 // order of results is not guaranteed 142 for _, v := range result.(types.JSONDocument).Val.([]interface{}) { 143 if vStr, ok := v.(string); ok && vStr == "abc" { 144 continue 145 } 146 if vInt, ok := v.(float64); ok && vInt == 123 { 147 continue 148 } 149 if vArr, ok := v.([]interface{}); ok && len(vArr) == 3 && vArr[0].(float64) == 1 && vArr[1].(float64) == 2 && vArr[2].(float64) == 3 { 150 continue 151 } 152 if vMap, ok := v.(map[string]interface{}); ok && len(vMap) == 3 && vMap["a"].(float64) == 1 && vMap["b"].(float64) == 2 && vMap["c"].(float64) == 3 { 153 continue 154 } 155 t.Errorf("got unexpected value: %v", v) 156 } 157 }) 158 } 159 160 /*func TestUnquoteColumns(t *testing.T) { 161 tests := []struct{ 162 str string 163 expected string 164 } { 165 {"", ""}, 166 {"$", "$"}, 167 {"$.", "$."}, 168 {"$.'", "$.'"}, 169 {"$.''", "$."}, 170 {"$.'col'", "$.col"}, 171 } 172 173 for _, test := range tests { 174 t.Run(test.str, func(t *testing.T) { 175 res := unquoteColumns(test.str) 176 assert.Equal(t, test.expected, res) 177 }) 178 } 179 }*/