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  }