github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/libraries/doltcore/sqle/json/noms_json_value.go (about)

     1  // Copyright 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  	"context"
    19  	"errors"
    20  	"fmt"
    21  	"sort"
    22  	"strings"
    23  
    24  	"github.com/dolthub/go-mysql-server/sql"
    25  	gmstypes "github.com/dolthub/go-mysql-server/sql/types"
    26  
    27  	"github.com/dolthub/dolt/go/store/types"
    28  )
    29  
    30  var ErrUnexpectedJSONTypeIn = errors.New("unexpected type during JSON marshalling")
    31  var ErrUnexpectedJSONTypeOut = errors.New("unexpected type during JSON unmarshalling")
    32  
    33  const (
    34  	JSONNull = "null"
    35  )
    36  
    37  // NomsJSON is a type alias for types.JSON. The alias allows MySQL-specific
    38  // logic to be kept separate from the storage-layer code in pkg types.
    39  type NomsJSON types.JSON
    40  
    41  var _ sql.JSONWrapper = NomsJSON{}
    42  
    43  // NomsJSONFromJSONValue converts a sql.JSONValue to a NomsJSON value.
    44  func NomsJSONFromJSONValue(ctx context.Context, vrw types.ValueReadWriter, val sql.JSONWrapper) (NomsJSON, error) {
    45  	if noms, ok := val.(NomsJSON); ok {
    46  		return noms, nil
    47  	}
    48  
    49  	sqlVal, err := val.ToInterface()
    50  	if err != nil {
    51  		return NomsJSON{}, err
    52  	}
    53  
    54  	v, err := marshalJSON(ctx, vrw, sqlVal)
    55  	if err != nil {
    56  		return NomsJSON{}, err
    57  	}
    58  
    59  	doc, err := types.NewJSONDoc(vrw.Format(), vrw, v)
    60  	if err != nil {
    61  		return NomsJSON{}, err
    62  	}
    63  
    64  	return NomsJSON(doc), nil
    65  }
    66  
    67  func marshalJSON(ctx context.Context, vrw types.ValueReadWriter, val interface{}) (types.Value, error) {
    68  	if val == nil {
    69  		return types.NullValue, nil
    70  	}
    71  
    72  	switch val := val.(type) {
    73  	case []interface{}:
    74  		return marshalJSONArray(ctx, vrw, val)
    75  	case map[string]interface{}:
    76  		return marshalJSONObject(ctx, vrw, val)
    77  	case bool:
    78  		return types.Bool(val), nil
    79  	case string:
    80  		return types.String(val), nil
    81  	case float64:
    82  		return types.Float(val), nil
    83  
    84  	// TODO(andy): unclear how to handle these
    85  	case float32:
    86  		return types.Float(val), nil
    87  	case int:
    88  		return types.Float(val), nil
    89  	case int8:
    90  		return types.Float(val), nil
    91  	case int16:
    92  		return types.Float(val), nil
    93  	case int32:
    94  		return types.Float(val), nil
    95  	case int64:
    96  		return types.Float(val), nil
    97  	case uint:
    98  		return types.Float(val), nil
    99  	case uint8:
   100  		return types.Float(val), nil
   101  	case uint16:
   102  		return types.Float(val), nil
   103  	case uint32:
   104  		return types.Float(val), nil
   105  	case uint64:
   106  		return types.Float(val), nil
   107  	default:
   108  		return nil, ErrUnexpectedJSONTypeIn
   109  	}
   110  }
   111  
   112  func marshalJSONArray(ctx context.Context, vrw types.ValueReadWriter, arr []interface{}) (types.Value, error) {
   113  	var err error
   114  	vals := make([]types.Value, len(arr))
   115  	for i, elem := range arr {
   116  		vals[i], err = marshalJSON(ctx, vrw, elem)
   117  		if err != nil {
   118  			return nil, err
   119  		}
   120  	}
   121  	return types.NewList(ctx, vrw, vals...)
   122  }
   123  
   124  func marshalJSONObject(ctx context.Context, vrw types.ValueReadWriter, obj map[string]interface{}) (types.Value, error) {
   125  	var err error
   126  	i := 0
   127  	vals := make([]types.Value, len(obj)*2)
   128  	for k, v := range obj {
   129  		vals[i] = types.String(k)
   130  		vals[i+1], err = marshalJSON(ctx, vrw, v)
   131  		if err != nil {
   132  			return nil, err
   133  		}
   134  		i += 2
   135  	}
   136  	return types.NewMap(ctx, vrw, vals...)
   137  }
   138  
   139  func (v NomsJSON) ToInterface() (interface{}, error) {
   140  	nomsVal, err := types.JSON(v).Inner()
   141  	if err != nil {
   142  		return nil, err
   143  	}
   144  
   145  	val, err := unmarshalJSON(context.Background(), nomsVal)
   146  	if err != nil {
   147  		return nil, err
   148  	}
   149  	return val, nil
   150  }
   151  
   152  // Unmarshall implements the sql.JSONValue interface.
   153  func (v NomsJSON) Unmarshall(ctx *sql.Context) (doc gmstypes.JSONDocument, err error) {
   154  	nomsVal, err := types.JSON(v).Inner()
   155  	if err != nil {
   156  		return gmstypes.JSONDocument{}, err
   157  	}
   158  
   159  	val, err := unmarshalJSON(ctx, nomsVal)
   160  	if err != nil {
   161  		return gmstypes.JSONDocument{}, err
   162  	}
   163  
   164  	return gmstypes.JSONDocument{Val: val}, nil
   165  }
   166  
   167  func unmarshalJSON(ctx context.Context, val types.Value) (interface{}, error) {
   168  	switch val := val.(type) {
   169  	case types.Null:
   170  		return nil, nil
   171  	case types.Bool:
   172  		return bool(val), nil
   173  	case types.String:
   174  		return string(val), nil
   175  	case types.Float:
   176  		return float64(val), nil
   177  	case types.List:
   178  		return unmarshalJSONArray(ctx, val)
   179  	case types.Map:
   180  		return unmarshalJSONObject(ctx, val)
   181  	default:
   182  		return nil, ErrUnexpectedJSONTypeIn
   183  	}
   184  }
   185  
   186  func unmarshalJSONArray(ctx context.Context, l types.List) (arr []interface{}, err error) {
   187  	arr = make([]interface{}, l.Len())
   188  	err = l.Iter(ctx, func(v types.Value, index uint64) (stop bool, err error) {
   189  		arr[index], err = unmarshalJSON(ctx, v)
   190  		return
   191  	})
   192  	return
   193  }
   194  
   195  func unmarshalJSONObject(ctx context.Context, m types.Map) (obj map[string]interface{}, err error) {
   196  	obj = make(map[string]interface{}, m.Len())
   197  	err = m.Iter(ctx, func(key, value types.Value) (stop bool, err error) {
   198  		ks, ok := key.(types.String)
   199  		if !ok {
   200  			return false, ErrUnexpectedJSONTypeOut
   201  		}
   202  
   203  		obj[string(ks)], err = unmarshalJSON(ctx, value)
   204  		return
   205  	})
   206  	return
   207  }
   208  
   209  // JSONString implements the sql.JSONWrapper interface.
   210  func (v NomsJSON) JSONString() (string, error) {
   211  	return NomsJSONToString(context.Background(), v)
   212  }
   213  
   214  func NomsJSONToString(ctx context.Context, js NomsJSON) (string, error) {
   215  	jd, err := types.JSON(js).Inner()
   216  	if err != nil {
   217  		return "", err
   218  	}
   219  
   220  	sb := &strings.Builder{}
   221  	if err = marshalToString(ctx, sb, jd); err != nil {
   222  		return "", err
   223  	}
   224  
   225  	return sb.String(), nil
   226  }
   227  
   228  func marshalToString(ctx context.Context, sb *strings.Builder, val types.Value) (err error) {
   229  	switch val := val.(type) {
   230  	case types.Null:
   231  		sb.WriteString(JSONNull)
   232  
   233  	case types.Bool:
   234  		sb.WriteString(val.HumanReadableString())
   235  
   236  	case types.String:
   237  		sb.WriteString(val.HumanReadableString())
   238  
   239  	case types.Float:
   240  		sb.WriteString(val.HumanReadableString())
   241  
   242  	case types.List:
   243  		sb.WriteRune('[')
   244  		seenOne := false
   245  		err = val.Iter(ctx, func(v types.Value, _ uint64) (stop bool, err error) {
   246  			if seenOne {
   247  				sb.WriteString(", ")
   248  			}
   249  			seenOne = true
   250  			err = marshalToString(ctx, sb, v)
   251  			return
   252  		})
   253  		if err != nil {
   254  			return err
   255  		}
   256  		sb.WriteRune(']')
   257  
   258  	case types.Map:
   259  		obj := make(map[string]types.Value, val.Len())
   260  		var keys []string
   261  		err = val.Iter(ctx, func(key, value types.Value) (stop bool, err error) {
   262  			ks, ok := key.(types.String)
   263  			if !ok {
   264  				return false, ErrUnexpectedJSONTypeOut
   265  			}
   266  			obj[string(ks)] = value
   267  			keys = append(keys, string(ks))
   268  			return
   269  		})
   270  		if err != nil {
   271  			return err
   272  		}
   273  		// JSON map keys are sorted k by length then alphabetically
   274  		sort.Slice(keys, func(i, j int) bool {
   275  			if len(keys[i]) != len(keys[j]) {
   276  				return len(keys[i]) < len(keys[j])
   277  			}
   278  			return keys[i] < keys[j]
   279  		})
   280  
   281  		sb.WriteRune('{')
   282  		seenOne := false
   283  		for _, k := range keys {
   284  			if seenOne {
   285  				sb.WriteString(", ")
   286  			}
   287  			seenOne = true
   288  			sb.WriteString(fmt.Sprintf("\"%s\": ", k))
   289  			err = marshalToString(ctx, sb, obj[k])
   290  			if err != nil {
   291  				return err
   292  			}
   293  		}
   294  		sb.WriteRune('}')
   295  	default:
   296  		err = ErrUnexpectedJSONTypeOut
   297  	}
   298  	return
   299  }