github.com/dolthub/go-mysql-server@v0.18.0/sql/expression/function/json/json_keys.go (about)

     1  // Copyright 2024 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  	"sort"
    20  
    21  	"github.com/dolthub/jsonpath"
    22  	"gopkg.in/src-d/go-errors.v1"
    23  
    24  	"github.com/dolthub/go-mysql-server/sql"
    25  	"github.com/dolthub/go-mysql-server/sql/expression"
    26  	"github.com/dolthub/go-mysql-server/sql/types"
    27  )
    28  
    29  // JSONKeys (json_doc[, path])
    30  //
    31  // JSONKeys Returns the keys from the top-level value of a JSON object as a JSON array, or, if a path argument is given,
    32  // the top-level keys from the selected path. Returns NULL if any argument is NULL, the json_doc argument is not an
    33  // object, or path, if given, does not locate an object. An error occurs if the json_doc argument is not a valid JSON
    34  // document or the path argument is not a valid path expression or contains a * or ** wildcard. The result array is
    35  // empty if the selected object is empty. If the top-level value has nested subobjects, the return value does not
    36  // include keys from those subobjects.
    37  //
    38  // https://dev.mysql.com/doc/refman/8.0/en/json-search-functions.html#function_json-keys
    39  type JSONKeys struct {
    40  	JSON sql.Expression
    41  	Path sql.Expression
    42  }
    43  
    44  var _ sql.FunctionExpression = &JSONKeys{}
    45  
    46  // NewJSONKeys creates a new JSONKeys function.
    47  func NewJSONKeys(args ...sql.Expression) (sql.Expression, error) {
    48  	if len(args) == 1 {
    49  		return &JSONKeys{args[0], expression.NewLiteral("$", types.Text)}, nil
    50  	}
    51  	if len(args) == 2 {
    52  		return &JSONKeys{args[0], args[1]}, nil
    53  	}
    54  	return nil, sql.ErrInvalidArgumentNumber.New("JSON_KEYS", "1 or 2", len(args))
    55  }
    56  
    57  // FunctionName implements sql.FunctionExpression
    58  func (j *JSONKeys) FunctionName() string {
    59  	return "json_keys"
    60  }
    61  
    62  // Description implements sql.FunctionExpression
    63  func (j *JSONKeys) Description() string {
    64  	return "returns the keys from the top-level value of a JSON object as a JSON array."
    65  }
    66  
    67  // Resolved implements the sql.Expression interface.
    68  func (j *JSONKeys) Resolved() bool {
    69  	return j.JSON.Resolved() && j.Path.Resolved()
    70  }
    71  
    72  // String implements the sql.Expression interface.
    73  func (j *JSONKeys) String() string {
    74  	return fmt.Sprintf("%s(%s, %s)", j.FunctionName(), j.JSON.String(), j.Path.String())
    75  }
    76  
    77  // Type implements the sql.Expression interface.
    78  func (j *JSONKeys) Type() sql.Type {
    79  	return types.JSON
    80  }
    81  
    82  // IsNullable implements the sql.Expression interface.
    83  func (j *JSONKeys) IsNullable() bool {
    84  	return j.JSON.IsNullable()
    85  }
    86  
    87  // Eval implements the sql.Expression interface.
    88  func (j *JSONKeys) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {
    89  	span, ctx := ctx.Span(fmt.Sprintf("function.%s", j.FunctionName()))
    90  	defer span.End()
    91  
    92  	doc, err := getJSONDocumentFromRow(ctx, row, j.JSON)
    93  	if err != nil {
    94  		return nil, err
    95  	}
    96  	if doc == nil {
    97  		return nil, nil
    98  	}
    99  
   100  	path, err := buildPath(ctx, j.Path, row)
   101  	if err != nil {
   102  		return nil, err
   103  	}
   104  	if path == nil {
   105  		return nil, nil
   106  	}
   107  
   108  	js, err := jsonpath.JsonPathLookup(doc.Val, path.(string))
   109  	if err != nil {
   110  		if errors.Is(err, jsonpath.ErrKeyError) {
   111  			return nil, nil
   112  		}
   113  		return nil, err
   114  	}
   115  
   116  	switch v := js.(type) {
   117  	case map[string]any:
   118  		res := make([]string, 0)
   119  		for k := range v {
   120  			res = append(res, k)
   121  		}
   122  		sort.Slice(res, func(i, j int) bool {
   123  			if len(res[i]) != len(res[j]) {
   124  				return len(res[i]) < len(res[j])
   125  			}
   126  			return res[i] < res[j]
   127  		})
   128  		result, _, err := types.JSON.Convert(res)
   129  		if err != nil {
   130  			return nil, err
   131  		}
   132  		return result, nil
   133  	default:
   134  		return nil, nil
   135  	}
   136  }
   137  
   138  // Children implements the Expression interface.
   139  func (j *JSONKeys) Children() []sql.Expression {
   140  	return []sql.Expression{j.JSON, j.Path}
   141  }
   142  
   143  // WithChildren implements the Expression interface.
   144  func (j *JSONKeys) WithChildren(children ...sql.Expression) (sql.Expression, error) {
   145  	return NewJSONKeys(children...)
   146  }