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

618 lines
20 KiB
Go

/*
* SPDX-FileCopyrightText: © 2017-2026 Istari Digital, Inc.
* SPDX-License-Identifier: Apache-2.0
*/
package posting
import (
"bytes"
"context"
"math"
"testing"
"time"
"github.com/stretchr/testify/require"
badger "github.com/luxfi/zapdb"
"github.com/hanzoai/graph/protos/pb"
"github.com/hanzoai/graph/schema"
"github.com/hanzoai/graph/types"
"github.com/hanzoai/graph/x"
)
func uids(l *List, readTs uint64) []uint64 {
r, err := l.Uids(ListOptions{ReadTs: readTs})
x.Check(err)
return r.Uids
}
// indexTokensForTest is just a wrapper around indexTokens used for convenience.
func indexTokensForTest(attr, lang string, val types.Val) ([]string, error) {
return indexTokens(context.Background(), &indexMutationInfo{
tokenizers: schema.State().Tokenizer(context.Background(), x.AttrInRootNamespace(attr)),
edge: &pb.DirectedEdge{
Attr: x.AttrInRootNamespace(attr),
Lang: lang,
},
val: val,
})
}
func TestIndexingInt(t *testing.T) {
require.NoError(t, schema.ParseBytes([]byte("age:int @index(int) ."), 1))
a, err := indexTokensForTest("age", "", types.Val{Tid: types.StringID, Value: []byte("10")})
require.NoError(t, err)
require.EqualValues(t, []byte{0x6, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa}, []byte(a[0]))
}
func TestIndexingIntNegative(t *testing.T) {
require.NoError(t, schema.ParseBytes([]byte("age:int @index(int) ."), 1))
a, err := indexTokensForTest("age", "", types.Val{Tid: types.StringID, Value: []byte("-10")})
require.NoError(t, err)
require.EqualValues(t, []byte{0x6, 0x0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf6},
[]byte(a[0]))
}
func TestIndexingFloat(t *testing.T) {
require.NoError(t, schema.ParseBytes([]byte("age:float @index(float) ."), 1))
a, err := indexTokensForTest("age", "", types.Val{Tid: types.StringID, Value: []byte("10.43")})
require.NoError(t, err)
require.EqualValues(t, []byte{0x7, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa}, []byte(a[0]))
}
func TestIndexingTime(t *testing.T) {
require.NoError(t, schema.ParseBytes([]byte("age:dateTime @index(year) ."), 1))
a, err := indexTokensForTest("age", "", types.Val{Tid: types.StringID,
Value: []byte("0010-01-01T01:01:01.000000001")})
require.NoError(t, err)
require.EqualValues(t, []byte{0x4, 0x0, 0xa}, []byte(a[0]))
}
func TestIndexing(t *testing.T) {
require.NoError(t, schema.ParseBytes([]byte("name:string @index(term) ."), 1))
a, err := indexTokensForTest("name", "", types.Val{Tid: types.StringID, Value: []byte("abc")})
require.NoError(t, err)
require.EqualValues(t, "\x01abc", a[0])
}
func TestIndexingMultiLang(t *testing.T) {
require.NoError(t, schema.ParseBytes([]byte("name:string @index(fulltext) ."), 1))
// ensure that default tokenizer is suitable for English
a, err := indexTokensForTest("name", "", types.Val{Tid: types.StringID,
Value: []byte("stemming")})
require.NoError(t, err)
require.EqualValues(t, "\x08stem", a[0])
// ensure that Finnish tokenizer is used
a, err = indexTokensForTest("name", "fi", types.Val{Tid: types.StringID,
Value: []byte("edeltäneessä")})
require.NoError(t, err)
require.EqualValues(t, "\x08edeltän", a[0])
// ensure that German tokenizer is used
a, err = indexTokensForTest("name", "de", types.Val{Tid: types.StringID,
Value: []byte("Auffassungsvermögen")})
require.NoError(t, err)
require.EqualValues(t, "\x08auffassungsvermog", a[0])
// ensure that default tokenizer works differently than German
a, err = indexTokensForTest("name", "", types.Val{Tid: types.StringID,
Value: []byte("Auffassungsvermögen")})
require.NoError(t, err)
require.EqualValues(t, "\x08auffassungsvermögen", a[0])
}
func TestIndexingInvalidLang(t *testing.T) {
require.NoError(t, schema.ParseBytes([]byte("name:string @index(fulltext) ."), 1))
// tokenizer for "xx" language won't return an error.
_, err := indexTokensForTest("name", "xx", types.Val{Tid: types.StringID,
Value: []byte("error")})
require.NoError(t, err)
}
func TestIndexingAliasedLang(t *testing.T) {
require.NoError(t, schema.ParseBytes([]byte("name:string @index(fulltext) @lang ."), 1))
_, err := indexTokensForTest("name", "es", types.Val{Tid: types.StringID,
Value: []byte("base")})
require.NoError(t, err)
// es-es and es-419 are aliased to es
_, err = indexTokensForTest("name", "es-es", types.Val{Tid: types.StringID,
Value: []byte("alias")})
require.NoError(t, err)
_, err = indexTokensForTest("name", "es-419", types.Val{Tid: types.StringID,
Value: []byte("alias")})
require.NoError(t, err)
}
func addMutation(t *testing.T, l *List, edge *pb.DirectedEdge, op uint32,
startTs uint64, commitTs uint64, index bool) {
switch op {
case Del:
edge.Op = pb.DirectedEdge_DEL
case Set:
edge.Op = pb.DirectedEdge_SET
default:
x.Fatalf("Unhandled op: %v", op)
}
txn := Oracle().RegisterStartTs(startTs)
txn.cache.SetIfAbsent(string(l.key), l)
if index {
require.NoError(t, l.AddMutationWithIndex(context.Background(), edge, txn))
} else {
require.NoError(t, l.addMutation(context.Background(), txn, edge))
}
txn.Update()
txn.UpdateCachedKeys(commitTs)
writer := NewTxnWriter(pstore)
require.NoError(t, txn.CommitToDisk(writer, commitTs))
require.NoError(t, writer.Flush())
}
const schemaVal = `
name: string @index(term) .
name2: string @index(term) .
dob: dateTime @index(year) .
friend: [uid] @reverse .
`
const mutatedSchemaVal = `
name:string @index(term) .
name2:string .
dob:dateTime @index(year) .
friend:[uid] @reverse .
`
// TODO(Txn): We can't read index key on disk if it was written in same txn.
func TestTokensTable(t *testing.T) {
require.NoError(t, schema.ParseBytes([]byte(schemaVal), 1))
attr := x.AttrInRootNamespace("name")
key := x.DataKey(attr, 1)
l, err := getNew(key, ps, math.MaxUint64, false)
l.mutationMap.readTs = 1
require.NoError(t, err)
edge := &pb.DirectedEdge{
Value: []byte("david"),
Attr: attr,
Entity: 157,
}
addMutation(t, l, edge, Set, 1, 2, true)
key = x.IndexKey(attr, "\x01david")
time.Sleep(10 * time.Millisecond)
txn := ps.NewTransactionAt(3, false)
_, err = txn.Get(key)
require.NoError(t, err)
require.EqualValues(t, []string{"\x01david"}, tokensForTest(attr))
}
// tokensForTest returns keys for a table. This is just for testing / debugging.
func tokensForTest(attr string) []string {
pk := x.ParsedKey{Attr: attr}
prefix := pk.IndexPrefix()
txn := pstore.NewTransactionAt(math.MaxUint64, false)
defer txn.Discard()
it := txn.NewIterator(badger.DefaultIteratorOptions)
defer it.Close()
var out []string
for it.Seek(prefix); it.Valid(); it.Next() {
key := it.Item().Key()
if !bytes.HasPrefix(key, prefix) {
break
}
k, err := x.Parse(key)
x.Check(err)
x.AssertTrue(k.IsIndex())
out = append(out, k.Term)
}
return out
}
// addEdgeToValue adds edge without indexing.
func addEdgeToValue(t *testing.T, attr string, src uint64,
value string, startTs, commitTs uint64) {
edge := &pb.DirectedEdge{
Value: []byte(value),
Attr: attr,
Entity: src,
Op: pb.DirectedEdge_SET,
}
l, err := GetNoStore(x.DataKey(attr, src), startTs)
require.NoError(t, err)
// No index entries added here as we do not call AddMutationWithIndex.
addMutation(t, l, edge, Set, startTs, commitTs, false)
}
func addDelEdgeToUID(t *testing.T, attr string, src uint64,
dst uint64, startTs, commitTs uint64) {
edge := &pb.DirectedEdge{
ValueId: dst,
Attr: attr,
Entity: src,
Op: pb.DirectedEdge_DEL,
}
l, err := GetNoStore(x.DataKey(attr, src), startTs)
require.NoError(t, err)
// No index entries added here as we do not call AddMutationWithIndex.
addMutation(t, l, edge, Del, startTs, commitTs, false)
}
// addEdgeToUID adds uid edge with reverse edge
func addEdgeToUID(t *testing.T, attr string, src uint64,
dst uint64, startTs, commitTs uint64) {
edge := &pb.DirectedEdge{
ValueId: dst,
Attr: attr,
Entity: src,
Op: pb.DirectedEdge_SET,
}
l, err := GetNoStore(x.DataKey(attr, src), startTs)
require.NoError(t, err)
// No index entries added here as we do not call AddMutationWithIndex.
addMutation(t, l, edge, Set, startTs, commitTs, false)
}
func TestCountReverseIndexWithData(t *testing.T) {
require.NoError(t, pstore.DropAll())
MemLayerInstance.clear()
indexNameCountVal := "testcount: [uid] @count @reverse ."
attr := x.AttrInRootNamespace("testcount")
addEdgeToUID(t, attr, 1, 23, uint64(8), uint64(9))
addEdgeToUID(t, attr, 1, 23, uint64(10), uint64(11))
l, err := GetNoStore(x.DataKey(attr, 1), 12)
require.NoError(t, err)
l.RLock()
require.Equal(t, l.GetLength(12), 2)
l.RUnlock()
require.NoError(t, schema.ParseBytes([]byte(indexNameCountVal), 1))
currentSchema, _ := schema.State().Get(context.Background(), attr)
rb := IndexRebuild{
Attr: attr,
StartTs: 12,
OldSchema: nil,
CurrentSchema: &currentSchema,
}
rebuildInfo := rb.needsTokIndexRebuild()
prefixes, err := rebuildInfo.prefixesForTokIndexes()
require.NoError(t, err)
require.NoError(t, pstore.DropPrefix(prefixes...))
require.NoError(t, rebuildCountIndex(context.Background(), &rb))
l, err = GetNoStore(x.DataKey(attr, 1), 14)
require.NoError(t, err)
l.RLock()
require.Equal(t, l.GetLength(14), 1)
l.RUnlock()
}
func TestCountReverseIndexEmptyPosting(t *testing.T) {
require.NoError(t, pstore.DropAll())
MemLayerInstance.clear()
indexNameCountVal := "testcount: [uid] @count @reverse ."
attr := x.AttrInRootNamespace("testcount")
addDelEdgeToUID(t, attr, 1, 23, uint64(10), uint64(11))
l, err := GetNoStore(x.DataKey(attr, 1), 12)
require.NoError(t, err)
l.RLock()
require.Equal(t, l.GetLength(12), -1)
l.RUnlock()
require.NoError(t, schema.ParseBytes([]byte(indexNameCountVal), 1))
currentSchema, _ := schema.State().Get(context.Background(), attr)
rb := IndexRebuild{
Attr: attr,
StartTs: 12,
OldSchema: nil,
CurrentSchema: &currentSchema,
}
rebuildInfo := rb.needsTokIndexRebuild()
prefixes, err := rebuildInfo.prefixesForTokIndexes()
require.NoError(t, err)
require.NoError(t, pstore.DropPrefix(prefixes...))
require.NoError(t, rebuildCountIndex(context.Background(), &rb))
l, err = GetNoStore(x.DataKey(attr, 1), 14)
require.NoError(t, err)
l.RLock()
require.Equal(t, l.GetLength(14), 0)
l.RUnlock()
}
func TestRebuildTokIndex(t *testing.T) {
addEdgeToValue(t, x.AttrInRootNamespace("name2"), 91, "Michonne", uint64(1), uint64(2))
addEdgeToValue(t, x.AttrInRootNamespace("name2"), 92, "David", uint64(3), uint64(4))
require.NoError(t, schema.ParseBytes([]byte(schemaVal), 1))
currentSchema, _ := schema.State().Get(context.Background(), x.AttrInRootNamespace("name2"))
rb := IndexRebuild{
Attr: x.AttrInRootNamespace("name2"),
StartTs: 5,
OldSchema: nil,
CurrentSchema: &currentSchema,
}
rebuildInfo := rb.needsTokIndexRebuild()
prefixes, err := rebuildInfo.prefixesForTokIndexes()
require.NoError(t, err)
require.NoError(t, pstore.DropPrefix(prefixes...))
require.NoError(t, rebuildTokIndex(context.Background(), &rb))
// Check index entries in data store.
txn := ps.NewTransactionAt(6, false)
defer txn.Discard()
it := txn.NewIterator(badger.DefaultIteratorOptions)
defer it.Close()
pk := x.ParsedKey{Attr: x.AttrInRootNamespace("name2")}
prefix := pk.IndexPrefix()
var idxKeys []string
var idxVals []*List
for it.Seek(prefix); it.Valid(); it.Next() {
item := it.Item()
key := item.Key()
if !bytes.HasPrefix(key, prefix) {
break
}
if item.UserMeta()&BitEmptyPosting == BitEmptyPosting {
continue
}
idxKeys = append(idxKeys, string(key))
l, err := GetNoStore(key, 6)
require.NoError(t, err)
idxVals = append(idxVals, l)
}
require.Len(t, idxKeys, 2)
require.Len(t, idxVals, 2)
require.EqualValues(t, idxKeys[0], x.IndexKey(x.AttrInRootNamespace("name2"), "\x01david"))
require.EqualValues(t, idxKeys[1], x.IndexKey(x.AttrInRootNamespace("name2"), "\x01michonne"))
uids1 := uids(idxVals[0], 6)
uids2 := uids(idxVals[1], 6)
require.Len(t, uids1, 1)
require.Len(t, uids2, 1)
require.EqualValues(t, 92, uids1[0])
require.EqualValues(t, 91, uids2[0])
}
func TestRebuildTokIndexWithDeletion(t *testing.T) {
addEdgeToValue(t, x.AttrInRootNamespace("name2"), 91, "Michonne", uint64(1), uint64(2))
addEdgeToValue(t, x.AttrInRootNamespace("name2"), 92, "David", uint64(3), uint64(4))
require.NoError(t, schema.ParseBytes([]byte(schemaVal), 1))
currentSchema, _ := schema.State().Get(context.Background(), x.AttrInRootNamespace("name2"))
rb := IndexRebuild{
Attr: x.AttrInRootNamespace("name2"),
StartTs: 5,
OldSchema: nil,
CurrentSchema: &currentSchema,
}
rebuildInfo := rb.needsTokIndexRebuild()
prefixes, err := rebuildInfo.prefixesForTokIndexes()
require.NoError(t, err)
require.NoError(t, pstore.DropPrefix(prefixes...))
require.NoError(t, rebuildTokIndex(context.Background(), &rb))
// Mutate the schema (the index in name2 is deleted) and rebuild the index.
require.NoError(t, schema.ParseBytes([]byte(mutatedSchemaVal), 1))
newSchema, _ := schema.State().Get(context.Background(), x.AttrInRootNamespace("name2"))
rb = IndexRebuild{
Attr: x.AttrInRootNamespace("name2"),
StartTs: 6,
OldSchema: &currentSchema,
CurrentSchema: &newSchema,
}
rebuildInfo = rb.needsTokIndexRebuild()
prefixes, err = rebuildInfo.prefixesForTokIndexes()
require.NoError(t, err)
require.NoError(t, pstore.DropPrefix(prefixes...))
require.NoError(t, rebuildTokIndex(context.Background(), &rb))
// Check index entries in data store.
txn := ps.NewTransactionAt(7, false)
defer txn.Discard()
it := txn.NewIterator(badger.DefaultIteratorOptions)
defer it.Close()
pk := x.ParsedKey{Attr: x.AttrInRootNamespace("name2")}
prefix := pk.IndexPrefix()
var idxKeys []string
var idxVals []*List
for it.Seek(prefix); it.Valid(); it.Next() {
item := it.Item()
key := item.Key()
if !bytes.HasPrefix(key, prefix) {
break
}
if item.UserMeta()&BitEmptyPosting == BitEmptyPosting {
continue
}
idxKeys = append(idxKeys, string(key))
l, err := GetNoStore(key, 7)
require.NoError(t, err)
idxVals = append(idxVals, l)
}
// The index keys should not be available anymore.
require.Len(t, idxKeys, 0)
require.Len(t, idxVals, 0)
}
func TestRebuildReverseEdges(t *testing.T) {
friendAttr := x.AttrInRootNamespace("friend")
addEdgeToUID(t, friendAttr, 1, 23, uint64(10), uint64(11))
addEdgeToUID(t, friendAttr, 1, 24, uint64(12), uint64(13))
addEdgeToUID(t, friendAttr, 2, 23, uint64(14), uint64(15))
require.NoError(t, schema.ParseBytes([]byte(schemaVal), 1))
currentSchema, _ := schema.State().Get(context.Background(), friendAttr)
rb := IndexRebuild{
Attr: friendAttr,
StartTs: 16,
OldSchema: nil,
CurrentSchema: &currentSchema,
}
// TODO: Remove after fixing sync marks.
require.NoError(t, rebuildReverseEdges(context.Background(), &rb))
// Check index entries in data store.
txn := ps.NewTransactionAt(17, false)
defer txn.Discard()
iterOpts := badger.DefaultIteratorOptions
iterOpts.AllVersions = true
it := txn.NewIterator(iterOpts)
defer it.Close()
pk := x.ParsedKey{Attr: friendAttr}
prefix := pk.ReversePrefix()
var revKeys []string
var revVals []*List
var prevKey []byte
it.Seek(prefix)
for it.ValidForPrefix(prefix) {
item := it.Item()
key := item.Key()
if bytes.Equal(key, prevKey) {
it.Next()
continue
}
prevKey = append(prevKey[:0], key...)
revKeys = append(revKeys, string(key))
l, err := ReadPostingList(key, it)
require.NoError(t, err)
revVals = append(revVals, l)
}
require.Len(t, revKeys, 2)
require.Len(t, revVals, 2)
uids0 := uids(revVals[0], 17)
uids1 := uids(revVals[1], 17)
require.Len(t, uids0, 2)
require.Len(t, uids1, 1)
require.EqualValues(t, 1, uids0[0])
require.EqualValues(t, 2, uids0[1])
require.EqualValues(t, 1, uids1[0])
}
func TestNeedsTokIndexRebuild(t *testing.T) {
rb := IndexRebuild{}
rb.OldSchema = &pb.SchemaUpdate{ValueType: pb.Posting_UID}
rb.CurrentSchema = &pb.SchemaUpdate{ValueType: pb.Posting_UID}
rebuildInfo := rb.needsTokIndexRebuild()
require.Equal(t, indexNoop, rebuildInfo.op)
require.Equal(t, []string(nil), rebuildInfo.tokenizersToDelete)
require.Equal(t, []string(nil), rebuildInfo.tokenizersToRebuild)
rb.OldSchema = nil
rebuildInfo = rb.needsTokIndexRebuild()
require.Equal(t, indexNoop, rebuildInfo.op)
require.Equal(t, []string(nil), rebuildInfo.tokenizersToDelete)
require.Equal(t, []string(nil), rebuildInfo.tokenizersToRebuild)
rb.OldSchema = &pb.SchemaUpdate{ValueType: pb.Posting_STRING, Directive: pb.SchemaUpdate_INDEX,
Tokenizer: []string{"exact"}}
rb.CurrentSchema = &pb.SchemaUpdate{ValueType: pb.Posting_STRING,
Directive: pb.SchemaUpdate_INDEX,
Tokenizer: []string{"exact"}}
rebuildInfo = rb.needsTokIndexRebuild()
require.Equal(t, indexNoop, rebuildInfo.op)
require.Equal(t, []string(nil), rebuildInfo.tokenizersToDelete)
require.Equal(t, []string(nil), rebuildInfo.tokenizersToRebuild)
rb.OldSchema = &pb.SchemaUpdate{ValueType: pb.Posting_STRING, Directive: pb.SchemaUpdate_INDEX,
Tokenizer: []string{"term"}}
rb.CurrentSchema = &pb.SchemaUpdate{ValueType: pb.Posting_STRING,
Directive: pb.SchemaUpdate_INDEX}
rebuildInfo = rb.needsTokIndexRebuild()
require.Equal(t, indexOp(indexRebuild), rebuildInfo.op)
require.Equal(t, []string{"term"}, rebuildInfo.tokenizersToDelete)
require.Equal(t, []string(nil), rebuildInfo.tokenizersToRebuild)
rb.OldSchema = &pb.SchemaUpdate{ValueType: pb.Posting_STRING, Directive: pb.SchemaUpdate_INDEX,
Tokenizer: []string{"exact"}}
rb.CurrentSchema = &pb.SchemaUpdate{ValueType: pb.Posting_FLOAT,
Directive: pb.SchemaUpdate_INDEX,
Tokenizer: []string{"exact"}}
rebuildInfo = rb.needsTokIndexRebuild()
require.Equal(t, indexOp(indexRebuild), rebuildInfo.op)
require.Equal(t, []string{"exact"}, rebuildInfo.tokenizersToDelete)
require.Equal(t, []string{"exact"}, rebuildInfo.tokenizersToRebuild)
rb.OldSchema = &pb.SchemaUpdate{ValueType: pb.Posting_STRING, Directive: pb.SchemaUpdate_INDEX,
Tokenizer: []string{"exact"}}
rb.CurrentSchema = &pb.SchemaUpdate{ValueType: pb.Posting_FLOAT,
Directive: pb.SchemaUpdate_NONE}
rebuildInfo = rb.needsTokIndexRebuild()
require.Equal(t, indexOp(indexDelete), rebuildInfo.op)
require.Equal(t, []string{"exact"}, rebuildInfo.tokenizersToDelete)
require.Equal(t, []string(nil), rebuildInfo.tokenizersToRebuild)
}
func TestNeedsCountIndexRebuild(t *testing.T) {
rb := IndexRebuild{}
rb.OldSchema = &pb.SchemaUpdate{ValueType: pb.Posting_UID}
rb.CurrentSchema = &pb.SchemaUpdate{ValueType: pb.Posting_UID, Count: true}
require.Equal(t, indexOp(indexRebuild), rb.needsCountIndexRebuild())
rb.OldSchema = nil
require.Equal(t, indexOp(indexRebuild), rb.needsCountIndexRebuild())
rb.OldSchema = &pb.SchemaUpdate{ValueType: pb.Posting_UID, Count: false}
rb.CurrentSchema = &pb.SchemaUpdate{ValueType: pb.Posting_UID, Count: false}
require.Equal(t, indexNoop, rb.needsCountIndexRebuild())
rb.OldSchema = &pb.SchemaUpdate{ValueType: pb.Posting_UID, Count: true}
rb.CurrentSchema = &pb.SchemaUpdate{ValueType: pb.Posting_UID, Count: false}
require.Equal(t, indexOp(indexDelete), rb.needsCountIndexRebuild())
}
func TestNeedsReverseEdgesRebuild(t *testing.T) {
rb := IndexRebuild{}
rb.OldSchema = &pb.SchemaUpdate{ValueType: pb.Posting_UID, Directive: pb.SchemaUpdate_INDEX}
rb.CurrentSchema = &pb.SchemaUpdate{ValueType: pb.Posting_UID,
Directive: pb.SchemaUpdate_REVERSE}
require.Equal(t, indexOp(indexRebuild), rb.needsReverseEdgesRebuild())
rb.OldSchema = nil
require.Equal(t, indexOp(indexRebuild), rb.needsReverseEdgesRebuild())
rb.OldSchema = &pb.SchemaUpdate{ValueType: pb.Posting_UID, Directive: pb.SchemaUpdate_REVERSE}
rb.CurrentSchema = &pb.SchemaUpdate{ValueType: pb.Posting_UID,
Directive: pb.SchemaUpdate_REVERSE}
require.Equal(t, indexNoop, rb.needsReverseEdgesRebuild())
rb.CurrentSchema = &pb.SchemaUpdate{ValueType: pb.Posting_UID,
Directive: pb.SchemaUpdate_REVERSE}
rb.CurrentSchema = &pb.SchemaUpdate{ValueType: pb.Posting_UID,
Directive: pb.SchemaUpdate_INDEX}
require.Equal(t, indexOp(indexDelete), rb.needsReverseEdgesRebuild())
}
func TestNeedsListTypeRebuild(t *testing.T) {
rb := IndexRebuild{}
rb.OldSchema = &pb.SchemaUpdate{ValueType: pb.Posting_UID, List: false}
rb.CurrentSchema = &pb.SchemaUpdate{ValueType: pb.Posting_UID, List: true}
rebuild, err := rb.needsListTypeRebuild()
require.True(t, rebuild)
require.NoError(t, err)
rb.OldSchema = nil
rebuild, err = rb.needsListTypeRebuild()
require.False(t, rebuild)
require.NoError(t, err)
rb.OldSchema = &pb.SchemaUpdate{ValueType: pb.Posting_UID, List: true}
rb.CurrentSchema = &pb.SchemaUpdate{ValueType: pb.Posting_UID, List: false,
Predicate: x.AttrInRootNamespace("")} // This is added to prevent a crash in rebuilder.
// We don't expect rebuilder to have predicates without namespace.
rebuild, err = rb.needsListTypeRebuild()
require.False(t, rebuild)
require.Error(t, err)
}