Skip to content

Commit 3590961

Browse files
committed
Add: Missing GoLang APIs
1 parent 42aef88 commit 3590961

File tree

3 files changed

+289
-45
lines changed

3 files changed

+289
-45
lines changed

golang/README.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,3 +120,67 @@ if err != nil {
120120
panic("Failed to view index")
121121
}
122122
```
123+
124+
## Index Introspection
125+
126+
Inspect and interact with the index:
127+
128+
```go
129+
dimensions, _ := index.Dimensions() // Get the number of dimensions
130+
size, _ := index.Len() // Get the number of vectors
131+
capacity, _ := index.Capacity() // Get the capacity
132+
containsKey, _ := index.Contains(42) // Check if a key is in the index
133+
count, _ := index.Count(42) // Get the count of vectors for a key (multi-vector indexes)
134+
version := usearch.Version() // Get the library version string
135+
```
136+
137+
## Modifying the Index
138+
139+
```go
140+
// Remove a vector by key
141+
err := index.Remove(42)
142+
143+
// Clear all vectors while preserving the index structure
144+
err = index.Clear()
145+
146+
// Rename a key
147+
err = index.Rename(oldKey, newKey)
148+
```
149+
150+
## Filtered Search
151+
152+
Perform searches with custom filtering:
153+
154+
```go
155+
// Define a filter callback
156+
handler := &usearch.FilteredSearchHandler{
157+
Callback: func(key usearch.Key, handler *usearch.FilteredSearchHandler) int {
158+
// Return non-zero to accept, zero to reject
159+
if key % 2 == 0 {
160+
return 1 // Accept even keys
161+
}
162+
return 0 // Reject odd keys
163+
},
164+
Data: nil, // Optional user data
165+
}
166+
167+
// Perform filtered search
168+
keys, distances, err := index.FilteredSearch(queryVector, 10, handler)
169+
```
170+
171+
## Exact Search
172+
173+
For smaller datasets, perform brute-force exact search without building an index:
174+
175+
```go
176+
dataset := []float32{...} // Flattened vectors
177+
queries := []float32{...} // Flattened query vectors
178+
179+
keys, distances, err := usearch.ExactSearch(
180+
dataset, queries,
181+
datasetSize, queryCount,
182+
vectorDims*4, vectorDims*4, // Strides in bytes
183+
vectorDims, usearch.Cosine,
184+
maxResults, 0, // 0 threads = auto-detect
185+
)
186+
```

golang/lib.go

Lines changed: 84 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ func DefaultConfig(dimensions uint) IndexConfig {
208208
return c
209209
}
210210

211-
// FilteredSearchHandler include the callback functiona and user data
211+
// FilteredSearchHandler includes the callback function and user data.
212212
type FilteredSearchHandler struct {
213213
Callback func(key Key, handler *FilteredSearchHandler) int
214214
Data any
@@ -246,15 +246,15 @@ func NewIndex(conf IndexConfig) (index *Index, err error) {
246246
conf = index.config
247247
dimensions := C.size_t(conf.Dimensions)
248248
connectivity := C.size_t(conf.Connectivity)
249-
expansion_add := C.size_t(conf.ExpansionAdd)
250-
expansion_search := C.size_t(conf.ExpansionSearch)
249+
expansionAdd := C.size_t(conf.ExpansionAdd)
250+
expansionSearch := C.size_t(conf.ExpansionSearch)
251251
multi := C.bool(conf.Multi)
252252

253253
options := C.struct_usearch_init_options_t{}
254254
options.dimensions = dimensions
255255
options.connectivity = connectivity
256-
options.expansion_add = expansion_add
257-
options.expansion_search = expansion_search
256+
options.expansion_add = expansionAdd
257+
options.expansion_search = expansionSearch
258258
options.multi = multi
259259
options.metric_kind = conf.Metric.CValue()
260260

@@ -271,6 +271,11 @@ func NewIndex(conf IndexConfig) (index *Index, err error) {
271271
return index, nil
272272
}
273273

274+
// Version returns the USearch library version string.
275+
func Version() string {
276+
return C.GoString(C.usearch_version())
277+
}
278+
274279
// Len returns the number of vectors in the index.
275280
func (index *Index) Len() (len uint, err error) {
276281
var errorMessage *C.char
@@ -393,13 +398,13 @@ func (index *Index) Capacity() (cap uint, err error) {
393398
return cap, err
394399
}
395400

396-
// HardwareAcceleration returns a string showing the SIMD capability for the index
401+
// HardwareAcceleration returns a string showing the SIMD capability for the index.
397402
func (index *Index) HardwareAcceleration() (string, error) {
398403
var str *C.char
399404
var errorMessage *C.char
400405
str = C.usearch_hardware_acceleration(index.handle, (*C.usearch_error_t)(&errorMessage))
401406
if errorMessage != nil {
402-
return C.GoString(nil), errors.New(C.GoString(errorMessage))
407+
return "", errors.New(C.GoString(errorMessage))
403408
}
404409
return C.GoString(str), nil
405410
}
@@ -512,6 +517,20 @@ func (index *Index) Remove(key Key) error {
512517
return nil
513518
}
514519

520+
// Clear removes all vectors from the index while preserving its structure.
521+
func (index *Index) Clear() error {
522+
if index.handle == nil {
523+
panic("index is uninitialized")
524+
}
525+
526+
var errorMessage *C.char
527+
C.usearch_clear(index.handle, (*C.usearch_error_t)(&errorMessage))
528+
if errorMessage != nil {
529+
return errors.New(C.GoString(errorMessage))
530+
}
531+
return nil
532+
}
533+
515534
// Contains checks if the index contains a vector with a specific key.
516535
func (index *Index) Contains(key Key) (found bool, err error) {
517536
if index.handle == nil {
@@ -526,6 +545,21 @@ func (index *Index) Contains(key Key) (found bool, err error) {
526545
return found, nil
527546
}
528547

548+
// Count returns the number of vectors stored under the given key.
549+
// Useful for multi-vector indexes where multiple vectors can share a key.
550+
func (index *Index) Count(key Key) (count uint, err error) {
551+
if index.handle == nil {
552+
panic("index is uninitialized")
553+
}
554+
555+
var errorMessage *C.char
556+
count = uint(C.usearch_count(index.handle, (C.usearch_key_t)(key), (*C.usearch_error_t)(&errorMessage)))
557+
if errorMessage != nil {
558+
return 0, errors.New(C.GoString(errorMessage))
559+
}
560+
return count, nil
561+
}
562+
529563
// Get retrieves the vectors associated with the given key from the index.
530564
// Returns nil if the key is not found.
531565
func (index *Index) Get(key Key, maxCount uint) (vectors []float32, err error) {
@@ -550,7 +584,7 @@ func (index *Index) Get(key Key, maxCount uint) (vectors []float32, err error) {
550584
return vectors, nil
551585
}
552586

553-
// Rename the vector at key from to key to
587+
// Rename changes the key of a vector from one value to another.
554588
func (index *Index) Rename(from Key, to Key) error {
555589
var errorMessage *C.char
556590
C.usearch_rename(index.handle, C.usearch_key_t(from), C.usearch_key_t(to), (*C.usearch_error_t)(&errorMessage))
@@ -649,19 +683,19 @@ func (index *Index) Search(query []float32, limit uint) (keys []Key, distances [
649683
return keys, distances, nil
650684
}
651685

652-
// Search finds the k nearest neighbors to the query vector.
686+
// FilteredSearch finds the k nearest neighbors to the query vector, applying a filter.
653687
//
654688
// Parameters:
655689
// - query: Must have exactly Dimensions() elements
656690
// - limit: Maximum number of results to return
691+
// - handler: Filter callback that returns non-zero to accept a candidate, zero to reject
657692
//
658693
// Returns:
659-
// - keys: IDs of the nearest vectors (up to limit)
694+
// - keys: IDs of the nearest vectors that passed the filter (up to limit)
660695
// - distances: Distance to each result (same length as keys)
661-
// - err: Error if query is invalid or search fails
696+
// - err: Error if query is invalid, handler is nil, or search fails
662697
//
663-
// The actual number of results may be less than limit if the index
664-
// contains fewer vectors.
698+
// The actual number of results may be less than limit if fewer vectors pass the filter.
665699
func (index *Index) FilteredSearch(query []float32, limit uint, handler *FilteredSearchHandler) (keys []Key, distances []float32, err error) {
666700
if index.handle == nil {
667701
panic("index is uninitialized")
@@ -743,7 +777,13 @@ func goFilteredSearchCallback(key C.usearch_key_t, ptr unsafe.Pointer) C.int {
743777
return C.int(handler.Callback(Key(key), handler))
744778
}
745779

746-
// Filtred Search performs k-Approximate Nearest Neighbors Search for the closest vectors to the query vector with filtering.
780+
// FilteredSearchUnsafe performs filtered k-ANN search using an unsafe pointer.
781+
//
782+
// SAFETY REQUIREMENTS:
783+
// - query must not be nil
784+
// - Memory at query must contain exactly Dimensions() scalars
785+
// - Scalar type must match index.config.Quantization
786+
// - Memory must remain valid for the duration of the call
747787
func (index *Index) FilteredSearchUnsafe(query unsafe.Pointer, limit uint, handler *FilteredSearchHandler) (keys []Key, distances []float32, err error) {
748788
if index.handle == nil {
749789
panic("index is uninitialized")
@@ -1028,20 +1068,20 @@ func ExactSearchI8(dataset []int8, queries []int8, datasetSize uint, queryCount
10281068
// SaveBuffer serializes the index into a byte buffer.
10291069
// The buffer must be large enough to hold the serialized index.
10301070
// Use SerializedLength() to determine the required buffer size.
1031-
func (index *Index) SaveBuffer(buf []byte, buffer_size uint) error {
1071+
func (index *Index) SaveBuffer(buf []byte, bufferSize uint) error {
10321072
if index.handle == nil {
10331073
panic("index is uninitialized")
10341074
}
10351075

10361076
if len(buf) == 0 {
10371077
return errors.New("buffer cannot be empty")
10381078
}
1039-
if uint(len(buf)) < buffer_size {
1040-
return fmt.Errorf("buffer too small: has %d bytes, need %d", len(buf), buffer_size)
1079+
if uint(len(buf)) < bufferSize {
1080+
return fmt.Errorf("buffer too small: has %d bytes, need %d", len(buf), bufferSize)
10411081
}
10421082

10431083
var errorMessage *C.char
1044-
C.usearch_save_buffer(index.handle, unsafe.Pointer(&buf[0]), C.size_t(buffer_size), (*C.usearch_error_t)(&errorMessage))
1084+
C.usearch_save_buffer(index.handle, unsafe.Pointer(&buf[0]), C.size_t(bufferSize), (*C.usearch_error_t)(&errorMessage))
10451085
runtime.KeepAlive(buf)
10461086
if errorMessage != nil {
10471087
return errors.New(C.GoString(errorMessage))
@@ -1051,20 +1091,20 @@ func (index *Index) SaveBuffer(buf []byte, buffer_size uint) error {
10511091

10521092
// LoadBuffer loads a serialized index from a byte buffer.
10531093
// The buffer must contain a valid serialized index.
1054-
func (index *Index) LoadBuffer(buf []byte, buffer_size uint) error {
1094+
func (index *Index) LoadBuffer(buf []byte, bufferSize uint) error {
10551095
if index.handle == nil {
10561096
panic("index is uninitialized")
10571097
}
10581098

10591099
if len(buf) == 0 {
10601100
return errors.New("buffer cannot be empty")
10611101
}
1062-
if uint(len(buf)) < buffer_size {
1063-
return fmt.Errorf("buffer too small: has %d bytes, need %d", len(buf), buffer_size)
1102+
if uint(len(buf)) < bufferSize {
1103+
return fmt.Errorf("buffer too small: has %d bytes, need %d", len(buf), bufferSize)
10641104
}
10651105

10661106
var errorMessage *C.char
1067-
C.usearch_load_buffer(index.handle, unsafe.Pointer(&buf[0]), C.size_t(buffer_size), (*C.usearch_error_t)(&errorMessage))
1107+
C.usearch_load_buffer(index.handle, unsafe.Pointer(&buf[0]), C.size_t(bufferSize), (*C.usearch_error_t)(&errorMessage))
10681108
runtime.KeepAlive(buf)
10691109
if errorMessage != nil {
10701110
return errors.New(C.GoString(errorMessage))
@@ -1075,20 +1115,20 @@ func (index *Index) LoadBuffer(buf []byte, buffer_size uint) error {
10751115
// ViewBuffer creates a view of a serialized index without copying the data.
10761116
// The buffer must remain valid for the lifetime of the index.
10771117
// Changes to the buffer will affect the index.
1078-
func (index *Index) ViewBuffer(buf []byte, buffer_size uint) error {
1118+
func (index *Index) ViewBuffer(buf []byte, bufferSize uint) error {
10791119
if index.handle == nil {
10801120
panic("index is uninitialized")
10811121
}
10821122

10831123
if len(buf) == 0 {
10841124
return errors.New("buffer cannot be empty")
10851125
}
1086-
if uint(len(buf)) < buffer_size {
1087-
return fmt.Errorf("buffer too small: has %d bytes, need %d", len(buf), buffer_size)
1126+
if uint(len(buf)) < bufferSize {
1127+
return fmt.Errorf("buffer too small: has %d bytes, need %d", len(buf), bufferSize)
10881128
}
10891129

10901130
var errorMessage *C.char
1091-
C.usearch_view_buffer(index.handle, unsafe.Pointer(&buf[0]), C.size_t(buffer_size), (*C.usearch_error_t)(&errorMessage))
1131+
C.usearch_view_buffer(index.handle, unsafe.Pointer(&buf[0]), C.size_t(bufferSize), (*C.usearch_error_t)(&errorMessage))
10921132
runtime.KeepAlive(buf)
10931133
if errorMessage != nil {
10941134
return errors.New(C.GoString(errorMessage))
@@ -1098,19 +1138,19 @@ func (index *Index) ViewBuffer(buf []byte, buffer_size uint) error {
10981138

10991139
// MetadataBuffer extracts index configuration metadata from a serialized buffer.
11001140
// This can be used to inspect an index before loading it.
1101-
func MetadataBuffer(buf []byte, buffer_size uint) (c IndexConfig, err error) {
1141+
func MetadataBuffer(buf []byte, bufferSize uint) (c IndexConfig, err error) {
11021142
if len(buf) == 0 {
11031143
return c, errors.New("buffer cannot be empty")
11041144
}
1105-
if uint(len(buf)) < buffer_size {
1106-
return c, fmt.Errorf("buffer too small: has %d bytes, need %d", len(buf), buffer_size)
1145+
if uint(len(buf)) < bufferSize {
1146+
return c, fmt.Errorf("buffer too small: has %d bytes, need %d", len(buf), bufferSize)
11071147
}
11081148
c = IndexConfig{}
11091149

11101150
options := C.struct_usearch_init_options_t{}
11111151

11121152
var errorMessage *C.char
1113-
C.usearch_metadata_buffer(unsafe.Pointer(&buf[0]), C.size_t(buffer_size), &options, (*C.usearch_error_t)(&errorMessage))
1153+
C.usearch_metadata_buffer(unsafe.Pointer(&buf[0]), C.size_t(bufferSize), &options, (*C.usearch_error_t)(&errorMessage))
11141154
runtime.KeepAlive(buf)
11151155
if errorMessage != nil {
11161156
return c, errors.New(C.GoString(errorMessage))
@@ -1154,6 +1194,8 @@ func MetadataBuffer(buf []byte, buffer_size uint) (c IndexConfig, err error) {
11541194
c.Quantization = I8
11551195
case C.usearch_scalar_b1_k:
11561196
c.Quantization = B1
1197+
case C.usearch_scalar_bf16_k:
1198+
c.Quantization = BF16
11571199
}
11581200

11591201
return c, nil
@@ -1166,13 +1208,13 @@ func Metadata(path string) (c IndexConfig, err error) {
11661208
return c, errors.New("path cannot be empty")
11671209
}
11681210

1169-
c_path := C.CString(path)
1170-
defer C.free(unsafe.Pointer(c_path))
1211+
cPath := C.CString(path)
1212+
defer C.free(unsafe.Pointer(cPath))
11711213

11721214
options := C.struct_usearch_init_options_t{}
11731215

11741216
var errorMessage *C.char
1175-
C.usearch_metadata(c_path, &options, (*C.usearch_error_t)(&errorMessage))
1217+
C.usearch_metadata(cPath, &options, (*C.usearch_error_t)(&errorMessage))
11761218
if errorMessage != nil {
11771219
return c, errors.New(C.GoString(errorMessage))
11781220
}
@@ -1228,11 +1270,11 @@ func (index *Index) Save(path string) error {
12281270
panic("index is uninitialized")
12291271
}
12301272

1231-
c_path := C.CString(path)
1232-
defer C.free(unsafe.Pointer(c_path))
1273+
cPath := C.CString(path)
1274+
defer C.free(unsafe.Pointer(cPath))
12331275

12341276
var errorMessage *C.char
1235-
C.usearch_save((C.usearch_index_t)(unsafe.Pointer(index.handle)), c_path, (*C.usearch_error_t)(&errorMessage))
1277+
C.usearch_save(index.handle, cPath, (*C.usearch_error_t)(&errorMessage))
12361278
if errorMessage != nil {
12371279
return errors.New(C.GoString(errorMessage))
12381280
}
@@ -1245,11 +1287,11 @@ func (index *Index) Load(path string) error {
12451287
panic("index is uninitialized")
12461288
}
12471289

1248-
c_path := C.CString(path)
1249-
defer C.free(unsafe.Pointer(c_path))
1290+
cPath := C.CString(path)
1291+
defer C.free(unsafe.Pointer(cPath))
12501292

12511293
var errorMessage *C.char
1252-
C.usearch_load((C.usearch_index_t)(unsafe.Pointer(index.handle)), c_path, (*C.usearch_error_t)(&errorMessage))
1294+
C.usearch_load(index.handle, cPath, (*C.usearch_error_t)(&errorMessage))
12531295
if errorMessage != nil {
12541296
return errors.New(C.GoString(errorMessage))
12551297
}
@@ -1262,11 +1304,11 @@ func (index *Index) View(path string) error {
12621304
panic("index is uninitialized")
12631305
}
12641306

1265-
c_path := C.CString(path)
1266-
defer C.free(unsafe.Pointer(c_path))
1307+
cPath := C.CString(path)
1308+
defer C.free(unsafe.Pointer(cPath))
12671309

12681310
var errorMessage *C.char
1269-
C.usearch_view((C.usearch_index_t)(unsafe.Pointer(index.handle)), c_path, (*C.usearch_error_t)(&errorMessage))
1311+
C.usearch_view(index.handle, cPath, (*C.usearch_error_t)(&errorMessage))
12701312
if errorMessage != nil {
12711313
return errors.New(C.GoString(errorMessage))
12721314
}

0 commit comments

Comments
 (0)