github.com/unigraph-dev/dgraph@v1.1.1-0.20200923154953-8b52b426f765/types/conversion.go (about)

     1  /*
     2   * Copyright 2016-2018 Dgraph Labs, Inc. and Contributors
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package types
    18  
    19  import (
    20  	"bytes"
    21  	"encoding/binary"
    22  	"encoding/json"
    23  	"math"
    24  	"strconv"
    25  	"time"
    26  
    27  	"github.com/pkg/errors"
    28  	geom "github.com/twpayne/go-geom"
    29  	"github.com/twpayne/go-geom/encoding/geojson"
    30  	"github.com/twpayne/go-geom/encoding/wkb"
    31  
    32  	"github.com/dgraph-io/dgo/protos/api"
    33  )
    34  
    35  // Convert converts the value to given scalar type.
    36  func Convert(from Val, toID TypeID) (Val, error) {
    37  	var to Val
    38  
    39  	// sanity: we expect a value
    40  	data, ok := from.Value.([]byte)
    41  	if !ok {
    42  		return to, errors.Errorf("Invalid data to convert to %s", toID.Name())
    43  	}
    44  	to = ValueForType(toID)
    45  	fromID := from.Tid
    46  	res := &to.Value
    47  
    48  	// Convert from-type to to-type and store in the result interface.
    49  	switch fromID {
    50  	case BinaryID:
    51  		{
    52  			// Unmarshal from Binary to type interfaces.
    53  			switch toID {
    54  			case BinaryID:
    55  				*res = data
    56  			case StringID, DefaultID:
    57  				*res = string(data)
    58  			case IntID:
    59  				if len(data) < 8 {
    60  					return to, errors.Errorf("Invalid data for int64 %v", data)
    61  				}
    62  				*res = int64(binary.LittleEndian.Uint64(data))
    63  			case FloatID:
    64  				if len(data) < 8 {
    65  					return to, errors.Errorf("Invalid data for float %v", data)
    66  				}
    67  				i := binary.LittleEndian.Uint64(data)
    68  				*res = math.Float64frombits(i)
    69  			case BoolID:
    70  				if len(data) == 0 || data[0] == 0 {
    71  					*res = false
    72  					return to, nil
    73  				} else if data[0] == 1 {
    74  					*res = true
    75  					return to, nil
    76  				}
    77  				return to, errors.Errorf("Invalid value for bool %v", data[0])
    78  			case DateTimeID:
    79  				var t time.Time
    80  				if err := t.UnmarshalBinary(data); err != nil {
    81  					return to, err
    82  				}
    83  				*res = t
    84  			case GeoID:
    85  				w, err := wkb.Unmarshal(data)
    86  				if err != nil {
    87  					return to, err
    88  				}
    89  				*res = w
    90  			case PasswordID:
    91  				*res = string(data)
    92  			default:
    93  				return to, cantConvert(fromID, toID)
    94  			}
    95  		}
    96  	case StringID, DefaultID:
    97  		{
    98  			vc := string(data)
    99  			switch toID {
   100  			case BinaryID:
   101  				*res = []byte(vc)
   102  			case IntID:
   103  				val, err := strconv.ParseInt(vc, 10, 64)
   104  				if err != nil {
   105  					return to, err
   106  				}
   107  				*res = val
   108  			case FloatID:
   109  				val, err := strconv.ParseFloat(vc, 64)
   110  				if err != nil {
   111  					return to, err
   112  				}
   113  				if math.IsNaN(val) {
   114  					return to, errors.Errorf("Got invalid value: NaN")
   115  				}
   116  				*res = val
   117  			case StringID, DefaultID:
   118  				*res = vc
   119  			case BoolID:
   120  				val, err := strconv.ParseBool(vc)
   121  				if err != nil {
   122  					return to, err
   123  				}
   124  				*res = val
   125  			case DateTimeID:
   126  				t, err := ParseTime(vc)
   127  				if err != nil {
   128  					return to, err
   129  				}
   130  				*res = t
   131  			case GeoID:
   132  				var g geom.T
   133  				text := bytes.Replace([]byte(vc), []byte("'"), []byte("\""), -1)
   134  				if err := geojson.Unmarshal(text, &g); err != nil {
   135  					return to,
   136  						errors.Wrapf(err, "Error while unmarshalling: [%s] as geojson", vc)
   137  				}
   138  				*res = g
   139  			case PasswordID:
   140  				p, err := Encrypt(vc)
   141  				if err != nil {
   142  					return to, err
   143  				}
   144  				*res = p
   145  			default:
   146  				return to, cantConvert(fromID, toID)
   147  			}
   148  		}
   149  	case IntID:
   150  		{
   151  			if len(data) < 8 {
   152  				return to, errors.Errorf("Invalid data for int64 %v", data)
   153  			}
   154  			vc := int64(binary.LittleEndian.Uint64(data))
   155  			switch toID {
   156  			case IntID:
   157  				*res = vc
   158  			case BinaryID:
   159  				var bs [8]byte
   160  				binary.LittleEndian.PutUint64(bs[:], uint64(vc))
   161  				*res = bs[:]
   162  			case FloatID:
   163  				*res = float64(vc)
   164  			case BoolID:
   165  				*res = vc != 0
   166  			case StringID, DefaultID:
   167  				*res = strconv.FormatInt(vc, 10)
   168  			case DateTimeID:
   169  				*res = time.Unix(vc, 0).UTC()
   170  			default:
   171  				return to, cantConvert(fromID, toID)
   172  			}
   173  		}
   174  	case FloatID:
   175  		{
   176  			if len(data) < 8 {
   177  				return to, errors.Errorf("Invalid data for float %v", data)
   178  			}
   179  			i := binary.LittleEndian.Uint64(data)
   180  			vc := math.Float64frombits(i)
   181  			switch toID {
   182  			case FloatID:
   183  				*res = vc
   184  			case BinaryID:
   185  				var bs [8]byte
   186  				u := math.Float64bits(vc)
   187  				binary.LittleEndian.PutUint64(bs[:], u)
   188  				*res = bs[:]
   189  			case IntID:
   190  				if vc > math.MaxInt64 || vc < math.MinInt64 || math.IsNaN(vc) {
   191  					return to, errors.Errorf("Float out of int64 range")
   192  				}
   193  				*res = int64(vc)
   194  			case BoolID:
   195  				*res = vc != 0
   196  			case StringID, DefaultID:
   197  				*res = strconv.FormatFloat(vc, 'G', -1, 64)
   198  			case DateTimeID:
   199  				secs := int64(vc)
   200  				fracSecs := vc - float64(secs)
   201  				nsecs := int64(fracSecs * nanoSecondsInSec)
   202  				*res = time.Unix(secs, nsecs).UTC()
   203  			default:
   204  				return to, cantConvert(fromID, toID)
   205  			}
   206  		}
   207  	case BoolID:
   208  		{
   209  			var vc bool
   210  			if len(data) == 0 || data[0] > 1 {
   211  				return to, errors.Errorf("Invalid value for bool %v", data)
   212  			}
   213  			vc = data[0] == 1
   214  
   215  			switch toID {
   216  			case BoolID:
   217  				*res = vc
   218  			case BinaryID:
   219  				*res = []byte{0}
   220  				if vc {
   221  					*res = []byte{1}
   222  				}
   223  			case IntID:
   224  				*res = int64(0)
   225  				if vc {
   226  					*res = int64(1)
   227  				}
   228  			case FloatID:
   229  				*res = float64(0)
   230  				if vc {
   231  					*res = float64(1)
   232  				}
   233  			case StringID, DefaultID:
   234  				*res = strconv.FormatBool(vc)
   235  			default:
   236  				return to, cantConvert(fromID, toID)
   237  			}
   238  		}
   239  	case DateTimeID:
   240  		{
   241  			var t time.Time
   242  			if err := t.UnmarshalBinary(data); err != nil {
   243  				return to, err
   244  			}
   245  			switch toID {
   246  			case DateTimeID:
   247  				*res = t
   248  			case BinaryID:
   249  				r, err := t.MarshalBinary()
   250  				if err != nil {
   251  					return to, err
   252  				}
   253  				*res = r
   254  			case StringID, DefaultID:
   255  				val, err := t.MarshalText()
   256  				if err != nil {
   257  					return to, err
   258  				}
   259  				*res = string(val)
   260  			case IntID:
   261  				*res = t.Unix()
   262  			case FloatID:
   263  				*res = float64(t.UnixNano()) / float64(nanoSecondsInSec)
   264  			default:
   265  				return to, cantConvert(fromID, toID)
   266  			}
   267  		}
   268  	case GeoID:
   269  		{
   270  			vc, err := wkb.Unmarshal(data)
   271  			if err != nil {
   272  				return to, err
   273  			}
   274  			switch toID {
   275  			case GeoID:
   276  				*res = vc
   277  			case BinaryID:
   278  				r, err := wkb.Marshal(vc, binary.LittleEndian)
   279  				if err != nil {
   280  					return to, err
   281  				}
   282  				*res = r
   283  			case StringID, DefaultID:
   284  				val, err := geojson.Marshal(vc)
   285  				if err != nil {
   286  					return to, nil
   287  				}
   288  				*res = string(bytes.Replace(val, []byte("\""), []byte("'"), -1))
   289  			default:
   290  				return to, cantConvert(fromID, toID)
   291  			}
   292  		}
   293  	case PasswordID:
   294  		{
   295  			vc := string(data)
   296  			switch toID {
   297  			case BinaryID:
   298  				*res = []byte(vc)
   299  			case StringID, PasswordID:
   300  				*res = vc
   301  			default:
   302  				return to, cantConvert(fromID, toID)
   303  			}
   304  		}
   305  	default:
   306  		return to, cantConvert(fromID, toID)
   307  	}
   308  	return to, nil
   309  }
   310  
   311  func Marshal(from Val, to *Val) error {
   312  	if to == nil {
   313  		return errors.Errorf("Invalid conversion %s to nil", from.Tid.Name())
   314  	}
   315  
   316  	fromID := from.Tid
   317  	toID := to.Tid
   318  	val := from.Value
   319  	res := &to.Value
   320  
   321  	// This is a default value from sg.fillVars, don't convert it's empty.
   322  	// Fixes issue #2980.
   323  	if val == nil {
   324  		*to = ValueForType(toID)
   325  		return nil
   326  	}
   327  
   328  	switch fromID {
   329  	case BinaryID:
   330  		vc := val.([]byte)
   331  		switch toID {
   332  		case StringID, DefaultID:
   333  			*res = string(vc)
   334  		case BinaryID:
   335  			*res = vc
   336  		default:
   337  			return cantConvert(fromID, toID)
   338  		}
   339  	case StringID, DefaultID:
   340  		vc := val.(string)
   341  		switch toID {
   342  		case StringID, DefaultID:
   343  			*res = vc
   344  		case BinaryID:
   345  			*res = []byte(vc)
   346  		default:
   347  			return cantConvert(fromID, toID)
   348  		}
   349  	case IntID:
   350  		vc := val.(int64)
   351  		switch toID {
   352  		case StringID, DefaultID:
   353  			*res = strconv.FormatInt(vc, 10)
   354  		case BinaryID:
   355  			var bs [8]byte
   356  			binary.LittleEndian.PutUint64(bs[:], uint64(vc))
   357  			*res = bs[:]
   358  		default:
   359  			return cantConvert(fromID, toID)
   360  		}
   361  	case FloatID:
   362  		vc := val.(float64)
   363  		switch toID {
   364  		case StringID, DefaultID:
   365  			*res = strconv.FormatFloat(vc, 'G', -1, 64)
   366  		case BinaryID:
   367  			var bs [8]byte
   368  			u := math.Float64bits(vc)
   369  			binary.LittleEndian.PutUint64(bs[:], u)
   370  			*res = bs[:]
   371  		default:
   372  			return cantConvert(fromID, toID)
   373  		}
   374  	case BoolID:
   375  		vc := val.(bool)
   376  		switch toID {
   377  		case StringID, DefaultID:
   378  			*res = strconv.FormatBool(vc)
   379  		case BinaryID:
   380  			*res = []byte{0}
   381  			if vc {
   382  				*res = []byte{1}
   383  			}
   384  		default:
   385  			return cantConvert(fromID, toID)
   386  		}
   387  	case DateTimeID:
   388  		vc := val.(time.Time)
   389  		switch toID {
   390  		case StringID, DefaultID:
   391  			val, err := vc.MarshalText()
   392  			if err != nil {
   393  				return err
   394  			}
   395  			*res = string(val)
   396  		case BinaryID:
   397  			r, err := vc.MarshalBinary()
   398  			if err != nil {
   399  				return err
   400  			}
   401  			*res = r
   402  		default:
   403  			return cantConvert(fromID, toID)
   404  		}
   405  	case GeoID:
   406  		vc, ok := val.(geom.T)
   407  		if !ok {
   408  			return errors.Errorf("Expected a Geo type")
   409  		}
   410  		switch toID {
   411  		case BinaryID:
   412  			r, err := wkb.Marshal(vc, binary.LittleEndian)
   413  			if err != nil {
   414  				return err
   415  			}
   416  			*res = r
   417  		case StringID, DefaultID:
   418  			val, err := geojson.Marshal(vc)
   419  			if err != nil {
   420  				return nil
   421  			}
   422  			*res = string(bytes.Replace(val, []byte("\""), []byte("'"), -1))
   423  		default:
   424  			return cantConvert(fromID, toID)
   425  		}
   426  	case PasswordID:
   427  		vc := val.(string)
   428  		switch toID {
   429  		case StringID:
   430  			*res = vc
   431  		case BinaryID:
   432  			*res = []byte(vc)
   433  		default:
   434  			return cantConvert(fromID, toID)
   435  		}
   436  	default:
   437  		return cantConvert(fromID, toID)
   438  	}
   439  	return nil
   440  }
   441  
   442  // ObjectValue converts into api.Value.
   443  func ObjectValue(id TypeID, value interface{}) (*api.Value, error) {
   444  	def := &api.Value{Val: &api.Value_StrVal{StrVal: ""}}
   445  	var ok bool
   446  	// Lets set the object value according to the storage type.
   447  	switch id {
   448  	case StringID:
   449  		var v string
   450  		if v, ok = value.(string); !ok {
   451  			return def, errors.Errorf("Expected value of type string. Got : %v", value)
   452  		}
   453  		return &api.Value{Val: &api.Value_StrVal{StrVal: v}}, nil
   454  	case DefaultID:
   455  		var v string
   456  		if v, ok = value.(string); !ok {
   457  			return def, errors.Errorf("Expected value of type string. Got : %v", value)
   458  		}
   459  		return &api.Value{Val: &api.Value_DefaultVal{DefaultVal: v}}, nil
   460  	case IntID:
   461  		var v int64
   462  		if v, ok = value.(int64); !ok {
   463  			return def, errors.Errorf("Expected value of type int64. Got : %v", value)
   464  		}
   465  		return &api.Value{Val: &api.Value_IntVal{IntVal: v}}, nil
   466  	case FloatID:
   467  		var v float64
   468  		if v, ok = value.(float64); !ok {
   469  			return def, errors.Errorf("Expected value of type float64. Got : %v", value)
   470  		}
   471  		return &api.Value{Val: &api.Value_DoubleVal{DoubleVal: v}}, nil
   472  	case BoolID:
   473  		var v bool
   474  		if v, ok = value.(bool); !ok {
   475  			return def, errors.Errorf("Expected value of type bool. Got : %v", value)
   476  		}
   477  		return &api.Value{Val: &api.Value_BoolVal{BoolVal: v}}, nil
   478  	case BinaryID:
   479  		var v []byte
   480  		if v, ok = value.([]byte); !ok {
   481  			return def, errors.Errorf("Expected value of type []byte. Got : %v", value)
   482  		}
   483  		return &api.Value{Val: &api.Value_BytesVal{BytesVal: v}}, nil
   484  	// Geo and datetime are stored in binary format in the N-Quad, so lets
   485  	// convert them here.
   486  	case GeoID:
   487  		b, err := toBinary(id, value)
   488  		if err != nil {
   489  			return def, err
   490  		}
   491  		return &api.Value{Val: &api.Value_GeoVal{GeoVal: b}}, nil
   492  	case DateTimeID:
   493  		b, err := toBinary(id, value)
   494  		if err != nil {
   495  			return def, err
   496  		}
   497  		return &api.Value{Val: &api.Value_DatetimeVal{DatetimeVal: b}}, nil
   498  	case PasswordID:
   499  		var v string
   500  		if v, ok = value.(string); !ok {
   501  			return def, errors.Errorf("Expected value of type password. Got : %v", value)
   502  		}
   503  		return &api.Value{Val: &api.Value_PasswordVal{PasswordVal: v}}, nil
   504  	default:
   505  		return def, errors.Errorf("ObjectValue not available for: %v", id)
   506  	}
   507  }
   508  
   509  func toBinary(id TypeID, b interface{}) ([]byte, error) {
   510  	p1 := ValueForType(BinaryID)
   511  	if err := Marshal(Val{id, b}, &p1); err != nil {
   512  		return nil, err
   513  	}
   514  	return p1.Value.([]byte), nil
   515  }
   516  
   517  func cantConvert(from TypeID, to TypeID) error {
   518  	return errors.Errorf("Cannot convert %s to type %s", from.Name(), to.Name())
   519  }
   520  
   521  // MarshalJSON makes Val satisfy the json.Marshaler interface.
   522  func (v Val) MarshalJSON() ([]byte, error) {
   523  	switch v.Tid {
   524  	case IntID:
   525  		return json.Marshal(v.Value.(int64))
   526  	case BoolID:
   527  		return json.Marshal(v.Value.(bool))
   528  	case FloatID:
   529  		return json.Marshal(v.Value.(float64))
   530  	case DateTimeID:
   531  		return json.Marshal(v.Value.(time.Time))
   532  	case GeoID:
   533  		return geojson.Marshal(v.Value.(geom.T))
   534  	case StringID, DefaultID:
   535  		return json.Marshal(v.Safe().(string))
   536  	case PasswordID:
   537  		return json.Marshal(v.Value.(string))
   538  	}
   539  	return nil, errors.Errorf("Invalid type for MarshalJSON: %v", v.Tid)
   540  }