mirror of
https://github.com/hanzoai/graph.git
synced 2026-10-30 18:35:37 +00:00
Derived from Dgraph (github.com/dgraph-io/dgraph) at commit 64804bd2, Apache-2.0. See NOTICE. WHAT THIS IS. The graph engine as a Go library: posting lists, the DQL parser, the type and tokenizer system, the index and schema layers, and the wire types. 39,170 lines of the upstream's 166,337. THE STORE is github.com/luxfi/zapdb, a Badger fork that kept the package name, the subpackages (options, pb, y) and the API — Open, OpenManaged, DB, Options — so the 74 import sites are aliased rather than rewritten. THE CODEC is ZAP. zapdb serializes with ZAP, not protobuf: its pb package builds the ZAP types by default and the protobuf ones only under `-tags grpc`, a path that does not compile (table/builder.go calls pb.Marshal, which that tag removes). One call site in posting needed converting; every other type here is Hanzo's own protobuf and is untouched. NO TRANSPORT. An embedded engine dials nothing, so nothing here imports google.golang.org/grpc. Removed: 20 transport functions in x, the generated service stubs for Raft, Zero and Worker (1,657 lines), and the JWT helper — identity belongs to IAM. The core called none of them. NOT KEPT: worker, conn, raftwal, the cluster commands, the GraphQL layer and the test harnesses. `query`, the DQL execution engine, went with them because it depends on worker; parsing DQL is available here, executing it is not. The history of the upstream project lives at github.com/dgraph-io/dgraph. This repository begins at the derivation because three quarters of that history describes code this fork does not carry.
186 lines
4.8 KiB
Go
186 lines
4.8 KiB
Go
/*
|
|
* SPDX-FileCopyrightText: © 2017-2026 Istari Digital, Inc.
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
package facets
|
|
|
|
import (
|
|
"math"
|
|
"sort"
|
|
"strconv"
|
|
"unicode"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/dgraph-io/dgo/v250/protos/api"
|
|
"github.com/hanzoai/graph/protos/pb"
|
|
"github.com/hanzoai/graph/tok"
|
|
"github.com/hanzoai/graph/types"
|
|
)
|
|
|
|
// SortAndValidate sorts And validates the facets.
|
|
func SortAndValidate(fs []*api.Facet) error {
|
|
if len(fs) == 0 {
|
|
return nil
|
|
}
|
|
sort.Slice(fs, func(i, j int) bool {
|
|
return fs[i].Key < fs[j].Key
|
|
})
|
|
for i := 1; i < len(fs); i++ {
|
|
if fs[i-1].Key == fs[i].Key {
|
|
return errors.Errorf("Repeated keys are not allowed in facets. But got %s",
|
|
fs[i].Key)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// CopyFacets makes a copy of facets of the posting which are requested in param.Keys.
|
|
func CopyFacets(fcs []*api.Facet, param *pb.FacetParams) (fs []*api.Facet) {
|
|
if param == nil || fcs == nil {
|
|
return nil
|
|
}
|
|
// facets and param.keys are both sorted,
|
|
// We also need all keys if param.AllKeys is true.
|
|
numKeys := len(param.Param)
|
|
numFacets := len(fcs)
|
|
for kidx, fidx := 0, 0; (param.AllKeys || kidx < numKeys) && fidx < numFacets; {
|
|
f := fcs[fidx]
|
|
switch {
|
|
case param.AllKeys || param.Param[kidx].Key == f.Key:
|
|
fcopy := &api.Facet{
|
|
Key: f.Key,
|
|
Value: nil,
|
|
ValType: f.ValType,
|
|
}
|
|
if !param.AllKeys {
|
|
fcopy.Alias = param.Param[kidx].Alias
|
|
}
|
|
fcopy.Value = make([]byte, len(f.Value))
|
|
copy(fcopy.Value, f.Value)
|
|
fs = append(fs, fcopy)
|
|
kidx++
|
|
fidx++
|
|
case f.Key > param.Param[kidx].Key:
|
|
kidx++
|
|
default:
|
|
fidx++
|
|
}
|
|
}
|
|
return fs
|
|
}
|
|
|
|
// valAndValType returns interface val and valtype for facet.
|
|
func valAndValType(val string) (interface{}, api.Facet_ValType, error) {
|
|
if len(val) == 0 { // empty string case
|
|
return "", api.Facet_STRING, nil
|
|
}
|
|
// strings should be in quotes.
|
|
if len(val) >= 2 && val[0] == '"' && val[len(val)-1] == '"' {
|
|
uq, err := strconv.Unquote(val)
|
|
return uq, api.Facet_STRING, errors.Wrapf(err, "could not unquote %q:", val)
|
|
}
|
|
if intVal, err := strconv.ParseInt(val, 0, 64); err == nil {
|
|
return intVal, api.Facet_INT, nil
|
|
} else if numErr := err.(*strconv.NumError); numErr.Err == strconv.ErrRange {
|
|
// if we have only digits in val, then val is a big integer : return error
|
|
// otherwise try to parse as float.
|
|
allNumChars := true
|
|
for _, v := range val {
|
|
if !unicode.IsDigit(v) {
|
|
allNumChars = false
|
|
break
|
|
}
|
|
}
|
|
if allNumChars {
|
|
return nil, api.Facet_INT, err
|
|
}
|
|
}
|
|
if floatVal, err := strconv.ParseFloat(val, 64); err == nil {
|
|
// We can't store NaN as it is because it serializes into invalid JSON.
|
|
if math.IsNaN(floatVal) {
|
|
return nil, api.Facet_FLOAT, errors.Errorf("Got invalid value: NaN")
|
|
}
|
|
|
|
return floatVal, api.Facet_FLOAT, nil
|
|
} else if numErr := err.(*strconv.NumError); numErr.Err == strconv.ErrRange {
|
|
return nil, api.Facet_FLOAT, err
|
|
}
|
|
if val == "true" || val == "false" {
|
|
return val == "true", api.Facet_BOOL, nil
|
|
}
|
|
if t, err := types.ParseTime(val); err == nil {
|
|
return t, api.Facet_DATETIME, nil
|
|
}
|
|
return nil, api.Facet_STRING, errors.Errorf("Could not parse the facet value : [%s]", val)
|
|
}
|
|
|
|
// FacetFor returns Facet for given key and val.
|
|
func FacetFor(key, val string) (*api.Facet, error) {
|
|
v, vt, err := valAndValType(val)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
facet, err := ToBinary(key, v, vt)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if vt == api.Facet_STRING {
|
|
// tokenize val.
|
|
facet.Tokens, err = tok.GetTermTokens([]string{v.(string)})
|
|
if err == nil {
|
|
sort.Strings(facet.Tokens)
|
|
}
|
|
}
|
|
return facet, err
|
|
}
|
|
|
|
// ToBinary converts the given value into a binary value.
|
|
func ToBinary(key string, value interface{}, sourceType api.Facet_ValType) (
|
|
*api.Facet, error) {
|
|
// convert facet val interface{} to binary
|
|
sourceTid, err := TypeIDFor(&api.Facet{ValType: sourceType})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
targetVal := &types.Val{Tid: types.BinaryID}
|
|
if err = types.Marshal(types.Val{Tid: sourceTid, Value: value}, targetVal); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &api.Facet{Key: key, Value: targetVal.Value.([]byte), ValType: sourceType}, nil
|
|
}
|
|
|
|
// TypeIDFor gives TypeID for facet.
|
|
func TypeIDFor(f *api.Facet) (types.TypeID, error) {
|
|
switch f.ValType {
|
|
case api.Facet_INT:
|
|
return types.IntID, nil
|
|
case api.Facet_FLOAT:
|
|
return types.FloatID, nil
|
|
case api.Facet_BOOL:
|
|
return types.BoolID, nil
|
|
case api.Facet_DATETIME:
|
|
return types.DateTimeID, nil
|
|
case api.Facet_STRING:
|
|
return types.StringID, nil
|
|
default:
|
|
return types.DefaultID, errors.Errorf("Unrecognized facet type: %v", f.ValType)
|
|
}
|
|
}
|
|
|
|
// ValFor converts Facet into types.Val.
|
|
func ValFor(f *api.Facet) (types.Val, error) {
|
|
val := types.Val{Tid: types.BinaryID, Value: f.Value}
|
|
facetTid, err := TypeIDFor(f)
|
|
if err != nil {
|
|
return types.Val{}, err
|
|
}
|
|
|
|
return types.Convert(val, facetTid)
|
|
}
|