github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/internal/s3select/sql/jsonpath.go (about)

     1  // Copyright (c) 2015-2021 MinIO, Inc.
     2  //
     3  // This file is part of MinIO Object Storage stack
     4  //
     5  // This program is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Affero General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // This program is distributed in the hope that it will be useful
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13  // GNU Affero General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Affero General Public License
    16  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17  
    18  package sql
    19  
    20  import (
    21  	"errors"
    22  
    23  	"github.com/bcicen/jstream"
    24  	"github.com/minio/simdjson-go"
    25  )
    26  
    27  var (
    28  	errKeyLookup                  = errors.New("Cannot look up key in non-object value")
    29  	errIndexLookup                = errors.New("Cannot look up array index in non-array value")
    30  	errWildcardObjectLookup       = errors.New("Object wildcard used on non-object value")
    31  	errWildcardArrayLookup        = errors.New("Array wildcard used on non-array value")
    32  	errWildcardObjectUsageInvalid = errors.New("Invalid usage of object wildcard")
    33  )
    34  
    35  // jsonpathEval evaluates a JSON path and returns the value at the path.
    36  // If the value should be considered flat (from wildcards) any array returned should be considered individual values.
    37  func jsonpathEval(p []*JSONPathElement, v interface{}) (r interface{}, flat bool, err error) {
    38  	// fmt.Printf("JPATHexpr: %v jsonobj: %v\n\n", p, v)
    39  	if len(p) == 0 || v == nil {
    40  		return v, false, nil
    41  	}
    42  
    43  	switch {
    44  	case p[0].Key != nil:
    45  		key := p[0].Key.keyString()
    46  
    47  		switch kvs := v.(type) {
    48  		case jstream.KVS:
    49  			for _, kv := range kvs {
    50  				if kv.Key == key {
    51  					return jsonpathEval(p[1:], kv.Value)
    52  				}
    53  			}
    54  			// Key not found - return nil result
    55  			return Missing{}, false, nil
    56  		case simdjson.Object:
    57  			elem := kvs.FindKey(key, nil)
    58  			if elem == nil {
    59  				// Key not found - return nil result
    60  				return Missing{}, false, nil
    61  			}
    62  			val, err := IterToValue(elem.Iter)
    63  			if err != nil {
    64  				return nil, false, err
    65  			}
    66  			return jsonpathEval(p[1:], val)
    67  		default:
    68  			return nil, false, errKeyLookup
    69  		}
    70  
    71  	case p[0].Index != nil:
    72  		idx := *p[0].Index
    73  
    74  		arr, ok := v.([]interface{})
    75  		if !ok {
    76  			return nil, false, errIndexLookup
    77  		}
    78  
    79  		if idx >= len(arr) {
    80  			return nil, false, nil
    81  		}
    82  		return jsonpathEval(p[1:], arr[idx])
    83  
    84  	case p[0].ObjectWildcard:
    85  		switch kvs := v.(type) {
    86  		case jstream.KVS:
    87  			if len(p[1:]) > 0 {
    88  				return nil, false, errWildcardObjectUsageInvalid
    89  			}
    90  
    91  			return kvs, false, nil
    92  		case simdjson.Object:
    93  			if len(p[1:]) > 0 {
    94  				return nil, false, errWildcardObjectUsageInvalid
    95  			}
    96  
    97  			return kvs, false, nil
    98  		default:
    99  			return nil, false, errWildcardObjectLookup
   100  		}
   101  
   102  	case p[0].ArrayWildcard:
   103  		arr, ok := v.([]interface{})
   104  		if !ok {
   105  			return nil, false, errWildcardArrayLookup
   106  		}
   107  
   108  		// Lookup remainder of path in each array element and
   109  		// make result array.
   110  		var result []interface{}
   111  		for _, a := range arr {
   112  			rval, flatten, err := jsonpathEval(p[1:], a)
   113  			if err != nil {
   114  				return nil, false, err
   115  			}
   116  
   117  			if flatten {
   118  				// Flatten if array.
   119  				if arr, ok := rval.([]interface{}); ok {
   120  					result = append(result, arr...)
   121  					continue
   122  				}
   123  			}
   124  			result = append(result, rval)
   125  		}
   126  		return result, true, nil
   127  	}
   128  	panic("cannot reach here")
   129  }