Files
zeekay f5aaeb712e Hanzo Graph — the engine, on zapdb, ZAP-native
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.
2026-08-20 21:26:27 -07:00

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)
}