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.
170 lines
5.7 KiB
Go
170 lines
5.7 KiB
Go
/*
|
|
* SPDX-FileCopyrightText: © 2017-2026 Istari Digital, Inc.
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
package hnsw
|
|
|
|
import (
|
|
"fmt"
|
|
"sync"
|
|
|
|
c "github.com/hanzoai/graph/tok/constraints"
|
|
"github.com/hanzoai/graph/tok/index"
|
|
opt "github.com/hanzoai/graph/tok/options"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
const (
|
|
ExponentOpt string = "exponent"
|
|
MaxLevelsOpt string = "maxLevels"
|
|
EfConstructionOpt string = "efConstruction"
|
|
EfSearchOpt string = "efSearch"
|
|
MetricOpt string = "metric"
|
|
Hnsw string = "hnsw"
|
|
)
|
|
|
|
// persistentIndexFactory is an in memory implementation of the IndexFactory interface.
|
|
// indexMap is an in memory map that corresponds an index name with a corresponding VectorIndex.
|
|
// In persistentIndexFactory, the VectorIndex will be of type HNSW.
|
|
type persistentIndexFactory[T c.Float] struct {
|
|
// TODO: Can we kill the map?? This should disappear once server
|
|
// restarts, correct? So at the very least, this is not dependable.
|
|
indexMap map[string]index.VectorIndex[T]
|
|
floatBits int
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
// CreateFactory creates an instance of the private struct persistentIndexFactory.
|
|
// NOTE: if T and floatBits do not match in # of bits, there will be consequences.
|
|
func CreateFactory[T c.Float](floatBits int) index.IndexFactory[T] {
|
|
f := &persistentIndexFactory[T]{
|
|
indexMap: map[string]index.VectorIndex[T]{},
|
|
floatBits: floatBits,
|
|
}
|
|
return f
|
|
}
|
|
|
|
// Implements NamedFactory interface for use as a plugin.
|
|
func (hf *persistentIndexFactory[T]) Name() string { return Hnsw }
|
|
|
|
func (hf *persistentIndexFactory[T]) GetOptions(o opt.Options) string {
|
|
return GetPersistantOptions[T](o)
|
|
}
|
|
|
|
func (hf *persistentIndexFactory[T]) isNameAvailableWithLock(name string) bool {
|
|
_, nameUsed := hf.indexMap[name]
|
|
return !nameUsed
|
|
}
|
|
|
|
// hf.AllowedOptions() allows persistentIndexFactory to implement the
|
|
// IndexFactory interface (see vector-indexer/index/index.go for details).
|
|
// We define here options for exponent, maxLevels, efSearch, efConstruction,
|
|
// and metric.
|
|
func (hf *persistentIndexFactory[T]) AllowedOptions() opt.AllowedOptions {
|
|
retVal := opt.NewAllowedOptions()
|
|
retVal.AddIntOption(ExponentOpt).
|
|
AddIntOption(MaxLevelsOpt).
|
|
AddIntOption(EfConstructionOpt).
|
|
AddIntOption(EfSearchOpt)
|
|
getSimFunc := func(optValue string) (any, error) {
|
|
if optValue != Euclidean && optValue != Cosine && optValue != DotProd {
|
|
return nil, errors.New(fmt.Sprintf("Can't create a vector index for %s", optValue))
|
|
}
|
|
return GetSimType[T](optValue, hf.floatBits), nil
|
|
}
|
|
|
|
retVal.AddCustomOption(MetricOpt, getSimFunc)
|
|
return retVal
|
|
}
|
|
|
|
// Create is an implementation of the IndexFactory interface function, invoked by an HNSWIndexFactory
|
|
// instance. It takes in a string name and a VectorSource implementation, and returns a VectorIndex and error
|
|
// flag. It creates an HNSW instance using the index name and populates other parts of the HNSW struct such as
|
|
// multFactor, maxLevels, efConstruction, maxNeighbors, and efSearch using struct parameters.
|
|
// It then populates the HNSW graphs using the InsertChunk function until there are no more items to populate.
|
|
// Finally, the function adds the name and hnsw object to the in memory map and returns the object.
|
|
func (hf *persistentIndexFactory[T]) Create(
|
|
name string,
|
|
o opt.Options,
|
|
floatBits int) (index.VectorIndex[T], error) {
|
|
hf.mu.Lock()
|
|
defer hf.mu.Unlock()
|
|
return hf.createWithLock(name, o, floatBits)
|
|
}
|
|
|
|
func (hf *persistentIndexFactory[T]) createWithLock(
|
|
name string,
|
|
o opt.Options,
|
|
floatBits int) (index.VectorIndex[T], error) {
|
|
if !hf.isNameAvailableWithLock(name) {
|
|
err := errors.New("index with name " + name + " already exists")
|
|
return nil, err
|
|
}
|
|
retVal := &persistentHNSW[T]{
|
|
pred: name,
|
|
vecEntryKey: ConcatStrings(name, VecEntry),
|
|
vecKey: ConcatStrings(name, VecKeyword),
|
|
vecDead: ConcatStrings(name, VecDead),
|
|
floatBits: floatBits,
|
|
nodeAllEdges: map[uint64][][]uint64{},
|
|
}
|
|
err := retVal.applyOptions(o)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
hf.indexMap[name] = retVal
|
|
return retVal, nil
|
|
}
|
|
|
|
// Find is an implementation of the IndexFactory interface function, invoked by an persistentIndexFactory
|
|
// instance. It returns the VectorIndex corresponding with a string name using the in memory map.
|
|
func (hf *persistentIndexFactory[T]) Find(name string) (index.VectorIndex[T], error) {
|
|
hf.mu.RLock()
|
|
defer hf.mu.RUnlock()
|
|
return hf.findWithLock(name)
|
|
}
|
|
|
|
func (hf *persistentIndexFactory[T]) findWithLock(name string) (index.VectorIndex[T], error) {
|
|
vecInd := hf.indexMap[name]
|
|
return vecInd, nil
|
|
}
|
|
|
|
// Remove is an implementation of the IndexFactory interface function, invoked by an persistentIndexFactory
|
|
// instance. It removes the VectorIndex corresponding with a string name using the in memory map.
|
|
func (hf *persistentIndexFactory[T]) Remove(name string) error {
|
|
hf.mu.Lock()
|
|
defer hf.mu.Unlock()
|
|
return hf.removeWithLock(name)
|
|
}
|
|
|
|
func (hf *persistentIndexFactory[T]) removeWithLock(name string) error {
|
|
delete(hf.indexMap, name)
|
|
return nil
|
|
}
|
|
|
|
// CreateOrReplace is an implementation of the IndexFactory interface funciton,
|
|
// invoked by an persistentIndexFactory. It checks if a VectorIndex
|
|
// correpsonding with name exists. If it does, it removes it, and replaces it
|
|
// via the Create function using the passed VectorSource. If the VectorIndex
|
|
// does not exist, it creates that VectorIndex corresponding with the name using
|
|
// the VectorSource.
|
|
func (hf *persistentIndexFactory[T]) CreateOrReplace(
|
|
name string,
|
|
o opt.Options,
|
|
floatBits int) (index.VectorIndex[T], error) {
|
|
hf.mu.Lock()
|
|
defer hf.mu.Unlock()
|
|
vi, err := hf.findWithLock(name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if vi != nil {
|
|
err = hf.removeWithLock(name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return hf.createWithLock(name, o, floatBits)
|
|
}
|