github.com/dolthub/go-mysql-server@v0.18.0/sql/expression/function/json/json_value.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  	"encoding/json"
    19  	"fmt"
    20  	"strings"
    21  
    22  	"github.com/dolthub/jsonpath"
    23  	"github.com/dolthub/vitess/go/sqltypes"
    24  
    25  	"github.com/dolthub/go-mysql-server/sql"
    26  	"github.com/dolthub/go-mysql-server/sql/expression"
    27  	"github.com/dolthub/go-mysql-server/sql/types"
    28  )
    29  
    30  // JsonValue selects data from a json document using a json path and
    31  // optional type coercion.
    32  // https://dev.mysql.com/doc/refman/8.0/en/json-search-functions.html#function_json-value
    33  // usage: JSON_VALUE(json_doc, path, [returning type])
    34  // TODO: [RETURNING TYPE] should be appended to path option in parser
    35  // TODO: missing [on empty] and [on error] support
    36  type JsonValue struct {
    37  	JSON sql.Expression
    38  	Path sql.Expression
    39  	Typ  sql.Type
    40  }
    41  
    42  var _ sql.FunctionExpression = (*JsonValue)(nil)
    43  var _ sql.CollationCoercible = (*JsonValue)(nil)
    44  
    45  var jsonValueDefaultType = types.MustCreateString(sqltypes.VarChar, 512, sql.Collation_Default)
    46  
    47  // NewJsonValue creates a new JsonValue UDF.
    48  func NewJsonValue(args ...sql.Expression) (sql.Expression, error) {
    49  	if len(args) < 1 || len(args) > 3 {
    50  		return nil, sql.ErrInvalidArgumentNumber.New("JSON_VALUE", 2, len(args))
    51  	} else if len(args) == 1 {
    52  		return &JsonValue{JSON: args[0], Path: expression.NewLiteral("$", types.Text), Typ: jsonValueDefaultType}, nil
    53  	} else if len(args) == 2 {
    54  		return &JsonValue{JSON: args[0], Path: args[1], Typ: jsonValueDefaultType}, nil
    55  	} else {
    56  		// third argument is literal zero of the coercion type
    57  		return &JsonValue{JSON: args[0], Path: args[1], Typ: args[2].Type()}, nil
    58  	}
    59  }
    60  
    61  // FunctionName implements sql.FunctionExpression
    62  func (j *JsonValue) FunctionName() string {
    63  	return "json_value"
    64  }
    65  
    66  // Description implements sql.FunctionExpression
    67  func (j *JsonValue) Description() string {
    68  	return "returns value from JSON document"
    69  }
    70  
    71  // Resolved implements the sql.Expression interface.
    72  func (j *JsonValue) Resolved() bool {
    73  	return j.JSON.Resolved() && j.Path.Resolved()
    74  }
    75  
    76  // Type implements the sql.Expression interface.
    77  func (j *JsonValue) Type() sql.Type { return j.Typ }
    78  
    79  // CollationCoercibility implements the interface sql.CollationCoercible.
    80  func (*JsonValue) CollationCoercibility(ctx *sql.Context) (collation sql.CollationID, coercibility byte) {
    81  	return ctx.GetCharacterSet().BinaryCollation(), 2
    82  }
    83  
    84  // Eval implements the sql.Expression interface.
    85  func (j *JsonValue) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {
    86  	span, ctx := ctx.Span("function.JsonValue")
    87  	defer span.End()
    88  
    89  	js, err := j.JSON.Eval(ctx, row)
    90  	if err != nil {
    91  		return nil, err
    92  	}
    93  	//  sql NULLs, should result in sql NULLs.
    94  	if js == nil {
    95  		return nil, err
    96  	}
    97  
    98  	strData, _, err := types.LongBlob.Convert(js)
    99  	if err != nil {
   100  		return nil, fmt.Errorf("invalid data type for JSON data in argument 1 to function json_value; a JSON string or JSON type is required")
   101  	}
   102  	if strData == nil {
   103  		return nil, nil
   104  	}
   105  
   106  	var jsonData interface{}
   107  	if err = json.Unmarshal(strData.([]byte), &jsonData); err != nil {
   108  		return nil, err
   109  	}
   110  
   111  	path, err := j.Path.Eval(ctx, row)
   112  	if err != nil {
   113  		return nil, err
   114  	}
   115  
   116  	res, err := jsonpath.JsonPathLookup(jsonData, path.(string))
   117  	if err != nil {
   118  		return nil, err
   119  	}
   120  
   121  	switch r := res.(type) {
   122  	case nil:
   123  		return nil, nil
   124  	case []interface{}:
   125  		if len(r) == 0 {
   126  			return nil, nil
   127  		}
   128  		res = types.JSONDocument{Val: res}
   129  	case map[string]interface{}:
   130  		res = types.JSONDocument{Val: res}
   131  	}
   132  
   133  	if j.Typ != nil {
   134  		res, _, err = j.Typ.Convert(res)
   135  		if err != nil {
   136  			return nil, err
   137  		}
   138  	}
   139  
   140  	return res, nil
   141  }
   142  
   143  // IsNullable implements the sql.Expression interface.
   144  func (j *JsonValue) IsNullable() bool {
   145  	return j.JSON.IsNullable() || j.Path.IsNullable()
   146  }
   147  
   148  // Children implements the sql.Expression interface.
   149  func (j *JsonValue) Children() []sql.Expression {
   150  	return []sql.Expression{j.JSON, j.Path}
   151  }
   152  
   153  // WithChildren implements the Expression interface.
   154  func (j *JsonValue) WithChildren(children ...sql.Expression) (sql.Expression, error) {
   155  	if len(children) != 2 {
   156  		return nil, sql.ErrInvalidChildrenNumber.New(j, len(children), 2)
   157  	}
   158  	ret := *j
   159  	ret.JSON = children[0]
   160  	ret.Path = children[1]
   161  	return &ret, nil
   162  }
   163  
   164  func (j *JsonValue) String() string {
   165  	children := j.Children()
   166  	var parts = make([]string, len(children))
   167  	for i, c := range children {
   168  		parts[i] = c.String()
   169  	}
   170  	return fmt.Sprintf("json_value(%s)", strings.Join(parts, ", "))
   171  }