github.com/dolthub/go-mysql-server@v0.18.0/sql/expression/function/json/json_extract.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  	"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_EXTRACT(json_doc, path[, path] ...)
    26  //
    27  // JSONExtract extracts data from a json document using json paths.
    28  // https://dev.mysql.com/doc/refman/8.0/en/json-search-functions.html#function_json-extract
    29  type JSONExtract struct {
    30  	JSON  sql.Expression
    31  	Paths []sql.Expression
    32  }
    33  
    34  var _ sql.FunctionExpression = (*JSONExtract)(nil)
    35  var _ sql.CollationCoercible = (*JSONExtract)(nil)
    36  
    37  // NewJSONExtract creates a new JSONExtract UDF.
    38  func NewJSONExtract(args ...sql.Expression) (sql.Expression, error) {
    39  	if len(args) < 2 {
    40  		return nil, sql.ErrInvalidArgumentNumber.New("JSON_EXTRACT", 2, len(args))
    41  	}
    42  
    43  	return &JSONExtract{args[0], args[1:]}, nil
    44  }
    45  
    46  // FunctionName implements sql.FunctionExpression
    47  func (j *JSONExtract) FunctionName() string {
    48  	return "json_extract"
    49  }
    50  
    51  // Description implements sql.FunctionExpression
    52  func (j *JSONExtract) Description() string {
    53  	return "returns data from JSON document"
    54  }
    55  
    56  // Resolved implements the sql.Expression interface.
    57  func (j *JSONExtract) Resolved() bool {
    58  	for _, p := range j.Paths {
    59  		if !p.Resolved() {
    60  			return false
    61  		}
    62  	}
    63  	return j.JSON.Resolved()
    64  }
    65  
    66  // Type implements the sql.Expression interface.
    67  func (j *JSONExtract) Type() sql.Type { return types.JSON }
    68  
    69  // CollationCoercibility implements the interface sql.CollationCoercible.
    70  func (*JSONExtract) CollationCoercibility(ctx *sql.Context) (collation sql.CollationID, coercibility byte) {
    71  	return ctx.GetCharacterSet().BinaryCollation(), 2
    72  }
    73  
    74  // Eval implements the sql.Expression interface.
    75  func (j *JSONExtract) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {
    76  	span, ctx := ctx.Span("function.JSONExtract")
    77  	defer span.End()
    78  
    79  	js, err := j.JSON.Eval(ctx, row)
    80  	if err != nil {
    81  		return nil, err
    82  	}
    83  	//  sql NULLs, should result in sql NULLs.
    84  	if js == nil {
    85  		return nil, err
    86  	}
    87  
    88  	js, _, err = types.JSON.Convert(js)
    89  	if err != nil {
    90  		return nil, err
    91  	}
    92  
    93  	searchable, ok := js.(sql.JSONWrapper)
    94  	if !ok {
    95  		return fmt.Errorf("expected types.JSONValue, found: %T", js), nil
    96  	}
    97  
    98  	var results = make([]sql.JSONWrapper, len(j.Paths))
    99  	for i, p := range j.Paths {
   100  		path, err := p.Eval(ctx, row)
   101  		if err != nil {
   102  			return nil, err
   103  		}
   104  
   105  		path, _, err = types.LongText.Convert(path)
   106  		if err != nil {
   107  			return nil, err
   108  		}
   109  
   110  		results[i], err = types.LookupJSONValue(searchable, path.(string))
   111  		if err != nil {
   112  			return nil, fmt.Errorf("failed to extract from expression '%s'; %s", j.JSON.String(), err.Error())
   113  		}
   114  	}
   115  
   116  	if len(results) == 1 {
   117  		return results[0], nil
   118  	}
   119  
   120  	return types.ConcatenateJSONValues(ctx, results...)
   121  }
   122  
   123  // IsNullable implements the sql.Expression interface.
   124  func (j *JSONExtract) IsNullable() bool {
   125  	for _, p := range j.Paths {
   126  		if p.IsNullable() {
   127  			return true
   128  		}
   129  	}
   130  	return j.JSON.IsNullable()
   131  }
   132  
   133  // Children implements the sql.Expression interface.
   134  func (j *JSONExtract) Children() []sql.Expression {
   135  	return append([]sql.Expression{j.JSON}, j.Paths...)
   136  }
   137  
   138  // WithChildren implements the Expression interface.
   139  func (j *JSONExtract) WithChildren(children ...sql.Expression) (sql.Expression, error) {
   140  	return NewJSONExtract(children...)
   141  }
   142  
   143  func (j *JSONExtract) String() string {
   144  	children := j.Children()
   145  	var parts = make([]string, len(children))
   146  	for i, c := range children {
   147  		parts[i] = c.String()
   148  	}
   149  	return fmt.Sprintf("json_extract(%s)", strings.Join(parts, ", "))
   150  }
   151  
   152  // IsUnsupported implements sql.UnsupportedFunctionStub
   153  func (j JSONExtract) IsUnsupported() bool {
   154  	return false
   155  }