github.com/dolthub/go-mysql-server@v0.18.0/sql/expression/function/json/json_merge_preserve.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 json 16 17 import ( 18 "fmt" 19 "strings" 20 21 "github.com/dolthub/go-mysql-server/sql" 22 "github.com/dolthub/go-mysql-server/sql/types" 23 ) 24 25 // JSON_MERGE_PRESERVE(json_doc, json_doc[, json_doc] ...) 26 // 27 // JSONMergePreserve Merges two or more JSON documents and returns the merged result. Returns NULL if any argument is 28 // NULL. An error occurs if any argument is not a valid JSON document. Merging takes place according to the following 29 // rules: 30 // - Adjacent arrays are merged to a single array. 31 // - Adjacent objects are merged to a single object. 32 // - A scalar value is autowrapped as an array and merged as an array. 33 // - An adjacent array and object are merged by autowrapping the object as an array and merging the two arrays. 34 // 35 // This function was added in MySQL 8.0.3 as a synonym for JSONMerge. The JSONMerge function is now deprecated, 36 // and is subject to removal in a future release of MySQL. 37 // 38 // The behavior of JSONMergePatch is the same as that of JSONMergePreserve, with the following two exceptions: 39 // - JSONMergePatch removes any member in the first object with a matching key in the second object, provided that 40 // the value associated with the key in the second object is not JSON null. 41 // - If the second object has a member with a key matching a member in the first object, JSONMergePatch replaces 42 // the value in the first object with the value in the second object, whereas JSONMergePreserve appends the 43 // second value to the first value. 44 // 45 // https://dev.mysql.com/doc/refman/8.0/en/json-modification-functions.html#function_json-merge-preserve 46 47 type JSONMergePreserve struct { 48 JSONDocs []sql.Expression 49 } 50 51 var _ sql.FunctionExpression = (*JSONMergePreserve)(nil) 52 var _ sql.CollationCoercible = (*JSONMergePreserve)(nil) 53 54 // NewJSONMergePreserve creates a new JSONMergePreserve function. 55 func NewJSONMergePreserve(args ...sql.Expression) (sql.Expression, error) { 56 if len(args) < 2 { 57 return nil, sql.ErrInvalidArgumentNumber.New("JSON_MERGE_PRESERVE", 2, len(args)) 58 } 59 60 return &JSONMergePreserve{JSONDocs: args}, nil 61 } 62 63 // FunctionName implements sql.FunctionExpression 64 func (j *JSONMergePreserve) FunctionName() string { 65 return "json_merge_preserve" 66 } 67 68 // Description implements sql.FunctionExpression 69 func (j *JSONMergePreserve) Description() string { 70 return "merges JSON documents, preserving duplicate keys." 71 } 72 73 // IsUnsupported implements sql.UnsupportedFunctionStub 74 func (j JSONMergePreserve) IsUnsupported() bool { 75 return false 76 } 77 78 // Resolved implements the Expression interface. 79 func (j *JSONMergePreserve) Resolved() bool { 80 for _, d := range j.JSONDocs { 81 if !d.Resolved() { 82 return false 83 } 84 } 85 return true 86 } 87 88 // String implements the Expression interface. 89 func (j *JSONMergePreserve) String() string { 90 children := j.Children() 91 var parts = make([]string, len(children)) 92 93 for i, c := range children { 94 parts[i] = c.String() 95 } 96 97 return fmt.Sprintf("%s(%s)", j.FunctionName(), strings.Join(parts, ",")) 98 } 99 100 // Type implements the Expression interface. 101 func (j *JSONMergePreserve) Type() sql.Type { 102 return types.JSON 103 } 104 105 // CollationCoercibility implements the interface sql.CollationCoercible. 106 func (*JSONMergePreserve) CollationCoercibility(ctx *sql.Context) (collation sql.CollationID, coercibility byte) { 107 return ctx.GetCharacterSet().BinaryCollation(), 2 108 } 109 110 // IsNullable implements the Expression interface. 111 func (j *JSONMergePreserve) IsNullable() bool { 112 for _, d := range j.JSONDocs { 113 if d.IsNullable() { 114 return true 115 } 116 } 117 return false 118 } 119 120 // Eval implements the Expression interface. 121 func (j *JSONMergePreserve) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { 122 initialJSON, err := j.JSONDocs[0].Eval(ctx, row) 123 if err != nil { 124 return nil, err 125 } 126 127 initialJSON, _, err = j.Type().Convert(initialJSON) 128 if err != nil { 129 return nil, err 130 } 131 132 mergedMap := types.DeepCopyJson(initialJSON.(types.JSONDocument).Val) 133 134 for _, json := range j.JSONDocs[1:] { 135 js, jErr := json.Eval(ctx, row) 136 if jErr != nil { 137 return nil, err 138 } 139 140 js, _, jErr = j.Type().Convert(js) 141 if jErr != nil { 142 return nil, err 143 } 144 145 jsMap := js.(types.JSONDocument).Val 146 147 mergedMap = merge(mergedMap, jsMap) 148 149 } 150 151 return types.JSONDocument{Val: mergedMap}, nil 152 } 153 154 // Children implements the Expression interface. 155 func (j *JSONMergePreserve) Children() []sql.Expression { 156 return j.JSONDocs 157 } 158 159 // WithChildren implements the Expression interface. 160 func (j *JSONMergePreserve) WithChildren(children ...sql.Expression) (sql.Expression, error) { 161 if len(j.Children()) != len(children) { 162 return nil, fmt.Errorf("json_merge_preserve did not receive the correct amount of args") 163 } 164 165 return NewJSONMergePreserve(children...) 166 } 167 168 // merge returns merged json document as interface{} type 169 func merge(base, add interface{}) interface{} { 170 // "base" is JSON object 171 if baseObj, baseOk := base.(map[string]interface{}); baseOk { 172 // "add" is JSON object 173 if addObj, addOk := add.(map[string]interface{}); addOk { 174 for key, val := range addObj { 175 if exists, found := baseObj[key]; found { 176 baseObj[key] = merge(exists, addObj[key]) 177 } else { 178 baseObj[key] = val 179 } 180 } 181 return baseObj 182 } 183 } 184 return mergeIntoArrays(base, add) 185 } 186 187 // mergeIntoArrays returns array of interface{} that takes JSON object OR JSON array OR JSON value 188 func mergeIntoArrays(base, add interface{}) interface{} { 189 var baseArray []interface{} 190 191 if baseArr, ok := base.([]interface{}); ok { 192 baseArray = baseArr 193 } else { 194 baseArray = append(baseArray, base) 195 } 196 197 if addArr, ok := add.([]interface{}); ok { 198 return append(baseArray, addArr...) 199 } 200 201 return append(baseArray, add) 202 }