Skip to content

Commit c732d45

Browse files
authored
refactor: Unify index directives (#5188)
## Relevant issue(s) Resolves #5168 ## Description This unifies the `@vectorIndex` and `@index` directive, while maintaining full backwards compatability, and clean cut outs for adding new index types in the future. ## Tasks - [x] I made sure the code is well commented, particularly hard-to-understand areas. - [ ] I made sure the repository-held documentation is changed accordingly. - [x] I made sure the pull request title adheres to the conventional commit style (the subset used in the project can be found in [tools/configs/chglog/config.yml](tools/configs/chglog/config.yml)). - [x] I made sure to discuss its limitations such as threats to validity, vulnerability to mistake and misuse, robustness to invalidation of assumptions, resource requirements, ... ## How has this been tested? Expanded unit and integration tests. Additional manual testing Specify the platform(s) on which this was tested: - Linux (nixos)
1 parent d08080e commit c732d45

19 files changed

Lines changed: 654 additions & 208 deletions

client/index.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,9 @@ type HNSWParams struct {
8282
EfSearch uint32
8383
}
8484

85-
// Default HNSW parameters, applied when the corresponding @vectorIndex directive argument is
86-
// omitted. These are the single source of truth: both the GraphQL directive definition and the
87-
// directive parser reference them, so the documented defaults cannot drift apart.
85+
// Default HNSW parameters, applied when the corresponding @index vector configuration is omitted.
86+
// These are the single source of truth: both the GraphQL directive definition and parser reference
87+
// them, so the documented defaults cannot drift apart.
8888
const (
8989
// DefaultHNSWM is the default maximum number of connections per node. Higher values improve
9090
// recall at the cost of memory and build time.
@@ -121,8 +121,7 @@ type VectorIndexDescription struct {
121121
Algorithm VectorAlgorithm
122122
// Metric is the distance metric used to compare vectors.
123123
Metric DistanceMetric
124-
// Dimensions is the length of the vectors being indexed. It must be set, except on an @embedding
125-
// field, where the embedding model fixes the length and Dimensions may be left 0.
124+
// Dimensions is the length of the vectors being indexed. It must be greater than zero.
126125
Dimensions uint32
127126
// HNSW holds HNSW-specific parameters. Non-nil when Algorithm == VectorAlgorithmHNSW.
128127
HNSW *HNSWParams

internal/db/collection_index.go

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -559,10 +559,9 @@ func processNewIndexRequest(
559559
}
560560

561561
// validateVectorIndexDescription checks and fills in the vector-specific parts of an index request.
562-
// The field must hold a float32 array, and dimensions must be set unless the field is an @embedding,
563-
// whose model fixes the vector length. It also defaults the algorithm, metric, and any missing
564-
// params (mutating desc.Vector), so a request made through the index API works the same as one from
565-
// the @vectorIndex directive.
562+
// The field must hold a float32 array, and dimensions must be greater than zero. It also defaults
563+
// the algorithm, metric, and any missing params (mutating desc.Vector), so a request made through
564+
// the index API works the same as one from the @index directive's vector configuration.
566565
//
567566
// The field is guaranteed to exist here because validateIndexDescription and
568567
// checkExistingFieldsAndAdjustRelFieldNames run before this and already check that.
@@ -583,10 +582,13 @@ func validateVectorIndexDescription(def client.CollectionVersion, desc client.Ne
583582
if !client.IsVectorEmbeddingCompatible(field.Kind) {
584583
return NewErrUnsupportedVectorIndexFieldType(field.Kind)
585584
}
585+
if desc.Vector.Dimensions == 0 {
586+
return NewErrVectorIndexMissingDimensions(fieldName)
587+
}
586588

587-
// The config object present is what picks the algorithm, so the caller never sets one directly.
588-
// Fill in the algorithm, metric, and any missing params with defaults. A nil config means an empty
589-
// one, so a caller can leave it out and still get a working HNSW index.
589+
// The algorithm config object present is what picks the algorithm, so the caller never sets one
590+
// directly. Fill in the algorithm, metric, and any missing params with defaults. A nil HNSW config
591+
// means an empty one, so a caller can leave it out and still get a working HNSW index.
590592
if desc.Vector.HNSW == nil {
591593
desc.Vector.HNSW = &client.HNSWParams{}
592594
}
@@ -612,19 +614,7 @@ func validateVectorIndexDescription(def client.CollectionVersion, desc client.Ne
612614
return err
613615
}
614616

615-
if desc.Vector.Dimensions > 0 {
616-
return nil
617-
}
618-
619-
// No dimensions were given. That is only allowed when the field is an @embedding, since the
620-
// model then fixes the dimensions. The value itself is filled in later.
621-
for _, embedding := range def.VectorEmbeddings {
622-
if embedding.FieldName == fieldName {
623-
return nil
624-
}
625-
}
626-
627-
return NewErrVectorIndexMissingDimensions(fieldName)
617+
return nil
628618
}
629619

630620
// validateNoConflictingVectorIndexMetric rejects creating a vector index on a field that another

internal/db/errors.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ const (
6262
errInvalidFieldValue string = "invalid field value"
6363
errUnsupportedIndexFieldType string = "unsupported index field type"
6464
errUnsupportedVectorIndexFieldType string = "unsupported field type for vector index"
65-
errVectorIndexMissingDimensions string = "vector index requires dimensions unless field is an embedding"
65+
errVectorIndexMissingDimensions string = "vector index dimensions must be greater than zero"
6666
errCannotIndexAccumulatedCRDTField string = "indexing accumulated CRDT fields is not yet supported"
6767
errIndexDescriptionHasNoFields string = "index description has no fields"
6868
errCreateFile string = "failed to create file"
@@ -615,8 +615,8 @@ func NewErrUnsupportedVectorIndexFieldType(kind client.FieldKind) error {
615615
)
616616
}
617617

618-
// NewErrVectorIndexMissingDimensions returns a new error indicating that a vector index request is
619-
// missing its dimensions, and dimensions could not be inferred from a generated embedding.
618+
// NewErrVectorIndexMissingDimensions returns a new error indicating that a vector index request has
619+
// no dimensions.
620620
func NewErrVectorIndexMissingDimensions(fieldName string) error {
621621
return errors.New(
622622
errVectorIndexMissingDimensions,

internal/db/vector_index_test.go

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import (
2424
)
2525

2626
// newVectorIndexTestDB opens an in-memory badger-backed DB with a collection carrying a
27-
// [Float32!] @vectorIndex field, ready for document writes.
27+
// [Float32!] @index(vector: {...}) field, ready for document writes.
2828
func newVectorIndexTestDB(t *testing.T, dimensions int) (context.Context, *DB, client.Collection) {
2929
t.Helper()
3030
ctx := context.Background()
@@ -35,7 +35,7 @@ func newVectorIndexTestDB(t *testing.T, dimensions int) (context.Context, *DB, c
3535
_, err = db.AddCollection(ctx, `
3636
type Users {
3737
name: String
38-
embedding: [Float32!] @vectorIndex(dimensions: `+strconv.Itoa(dimensions)+`, HNSW: {metric: COSINE})
38+
embedding: [Float32!] @index(vector: {dimensions: `+strconv.Itoa(dimensions)+`, hnsw: {metric: COSINE}})
3939
}
4040
`)
4141
require.NoError(t, err)
@@ -82,6 +82,21 @@ func vectorIndexSearch(
8282
return docIDs
8383
}
8484

85+
func TestValidateVectorIndexDescription_EmbeddingRequiresDimensions(t *testing.T) {
86+
const fieldName = "embedding"
87+
def := client.CollectionVersion{
88+
Fields: []client.CollectionFieldDescription{{Name: fieldName, Kind: client.FieldKind_FLOAT32_ARRAY}},
89+
VectorEmbeddings: []client.VectorEmbeddingDescription{{FieldName: fieldName}},
90+
}
91+
desc := client.NewIndexRequest{
92+
Fields: []client.IndexedFieldDescription{{Name: fieldName}},
93+
Vector: &client.VectorIndexDescription{},
94+
}
95+
96+
err := validateVectorIndexDescription(def, desc)
97+
require.ErrorContains(t, err, "vector index dimensions must be greater than zero")
98+
}
99+
85100
func TestCollectionVectorIndex_Save_InsertsIntoGraphAndIsSearchable(t *testing.T) {
86101
ctx, db, col := newVectorIndexTestDB(t, 3)
87102

0 commit comments

Comments
 (0)