chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:47:42 +08:00
commit be3ef883e1
1214 changed files with 431743 additions and 0 deletions
+128
View File
@@ -0,0 +1,128 @@
cmake_minimum_required(VERSION 3.13)
cmake_policy(SET CMP0077 NEW)
project(zvec-example-c++)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Enable compile_commands.json
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# --- Paths to Zvec ---
# Allow custom host build directory, default to "build"
if(NOT DEFINED HOST_BUILD_DIR)
set(HOST_BUILD_DIR "build")
endif()
get_filename_component(ZVEC_ROOT_DIR "${CMAKE_CURRENT_LIST_DIR}/../.." ABSOLUTE)
set(ZVEC_INCLUDE_DIR ${ZVEC_ROOT_DIR}/src/include)
set(ZVEC_LIB_DIR ${ZVEC_ROOT_DIR}/${HOST_BUILD_DIR}/lib)
# Add include and library search paths
include_directories(${ZVEC_INCLUDE_DIR})
set(ZVEC_LIB_SEARCH_DIRS ${ZVEC_LIB_DIR})
# Support multi-config builds (MSVC puts libs in Debug/Release subdirectories)
if(CMAKE_BUILD_TYPE)
set(ZVEC_CONFIG_LIB_DIR ${ZVEC_LIB_DIR}/${CMAKE_BUILD_TYPE})
if(EXISTS "${ZVEC_CONFIG_LIB_DIR}")
list(APPEND ZVEC_LIB_SEARCH_DIRS ${ZVEC_CONFIG_LIB_DIR})
endif()
endif()
if(WIN32)
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
endif()
function(zvec_find_shared_library OUT_VAR LIB_NAME)
unset(${OUT_VAR} CACHE)
if(WIN32)
find_library(${OUT_VAR}
NAMES ${LIB_NAME}_shared ${LIB_NAME}
PATHS ${ZVEC_LIB_SEARCH_DIRS}
NO_DEFAULT_PATH
NO_CMAKE_FIND_ROOT_PATH
)
else()
set(ZVEC_ORIGINAL_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES})
if(APPLE)
set(CMAKE_FIND_LIBRARY_SUFFIXES ".dylib")
else()
set(CMAKE_FIND_LIBRARY_SUFFIXES ".so")
endif()
find_library(${OUT_VAR}
NAMES ${LIB_NAME}
PATHS ${ZVEC_LIB_SEARCH_DIRS}
NO_DEFAULT_PATH
NO_CMAKE_FIND_ROOT_PATH
)
set(CMAKE_FIND_LIBRARY_SUFFIXES "${ZVEC_ORIGINAL_LIBRARY_SUFFIXES}")
endif()
set(${OUT_VAR} "${${OUT_VAR}}" PARENT_SCOPE)
endfunction()
function(zvec_require_shared_library OUT_VAR LIB_NAME)
zvec_find_shared_library(${OUT_VAR} ${LIB_NAME})
if(NOT ${OUT_VAR})
message(FATAL_ERROR
"lib${LIB_NAME} shared library was not found in ${ZVEC_LIB_SEARCH_DIRS}. "
"Build zvec first, or pass -DHOST_BUILD_DIR=<build-dir>.")
endif()
set(${OUT_VAR} "${${OUT_VAR}}" PARENT_SCOPE)
endfunction()
zvec_require_shared_library(ZVEC_SHARED_LIBRARY zvec)
zvec_require_shared_library(ZVEC_AILEGO_SHARED_LIBRARY zvec_ailego)
zvec_require_shared_library(ZVEC_CORE_SHARED_LIBRARY zvec_core)
# --- Create INTERFACE target for libzvec (all-in-one C++ shared library) ---
# libzvec.so/.dylib/.dll already bundles all zvec internal components
# (zvec, zvec_core, zvec_ailego, zvec_turbo), so no individual dependency
# libraries need to be specified by the consumer.
add_library(zvec-lib INTERFACE)
target_link_libraries(zvec-lib INTERFACE "${ZVEC_SHARED_LIBRARY}")
# --- Create INTERFACE target for libzvec_ailego (ailego-only all-in-one library) ---
# The ailego example intentionally depends only on libzvec_ailego.
add_library(zvec-ailego-lib INTERFACE)
target_link_libraries(zvec-ailego-lib INTERFACE "${ZVEC_AILEGO_SHARED_LIBRARY}")
# --- Create INTERFACE target for libzvec_core (core-only all-in-one library) ---
# The core example intentionally depends only on libzvec_core.
add_library(zvec-core-lib INTERFACE)
target_link_libraries(zvec-core-lib INTERFACE "${ZVEC_CORE_SHARED_LIBRARY}")
# --- Executables ---
set(ZVEC_EXAMPLE_TARGETS)
add_executable(db-example db/main.cc)
target_link_libraries(db-example PRIVATE zvec-lib)
if(ANDROID)
target_link_libraries(db-example PRIVATE log)
endif()
list(APPEND ZVEC_EXAMPLE_TARGETS db-example)
add_executable(ailego-example ailego/main.cc)
target_link_libraries(ailego-example PRIVATE zvec-ailego-lib)
list(APPEND ZVEC_EXAMPLE_TARGETS ailego-example)
add_executable(core-example core/main.cc)
target_link_libraries(core-example PRIVATE zvec-core-lib)
list(APPEND ZVEC_EXAMPLE_TARGETS core-example)
add_executable(external-vector-example core/external_vector_example.cc)
target_link_libraries(external-vector-example PRIVATE zvec-core-lib)
list(APPEND ZVEC_EXAMPLE_TARGETS external-vector-example)
# Strip symbols to reduce executable size
if(CMAKE_BUILD_TYPE STREQUAL "Release" AND ANDROID)
foreach(ZVEC_EXAMPLE_TARGET ${ZVEC_EXAMPLE_TARGETS})
add_custom_command(TARGET ${ZVEC_EXAMPLE_TARGET} POST_BUILD
COMMAND ${CMAKE_STRIP} "$<TARGET_FILE:${ZVEC_EXAMPLE_TARGET}>"
COMMENT "Stripping symbols from ${ZVEC_EXAMPLE_TARGET}")
endforeach()
endif()
# Optimize for size
if(CMAKE_BUILD_TYPE STREQUAL "Release" AND ANDROID AND ZVEC_EXAMPLE_TARGETS)
set_property(TARGET ${ZVEC_EXAMPLE_TARGETS} PROPERTY COMPILE_FLAGS "-Os")
set_property(TARGET ${ZVEC_EXAMPLE_TARGETS} PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE)
endif()
+11
View File
@@ -0,0 +1,11 @@
#include <iostream>
#include <string>
#include <zvec/ailego/utility/string_helper.h>
using namespace zvec;
int main() {
std::string a{"hello world"};
std::cout << ailego::StringHelper::StartsWith(a, "hello") << std::endl;
}
@@ -0,0 +1,219 @@
// Copyright 2025-present the zvec project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/// @file external_vector_example.cc
/// @brief Demonstrates using HNSW index in external-vector mode.
///
/// In external-vector mode the index does NOT store raw vectors internally.
/// Instead, a user-provided VectorSource is passed on every Add/Search call
/// so the index can fetch vectors on demand. This is useful when vectors are
/// already stored elsewhere (e.g. a columnar store, mmap file, remote storage)
/// and you want to avoid duplicating them inside the index.
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <filesystem>
#include <iostream>
#include <numeric>
#include <vector>
#include <zvec/core/interface/index.h>
#include <zvec/core/interface/index_factory.h>
#include <zvec/core/interface/index_param.h>
#include <zvec/core/interface/index_param_builders.h>
#include <zvec/core/interface/vector_source.h>
using namespace zvec::core_interface;
// ---------------------------------------------------------------------------
// A simple VectorSource backed by an in-memory float matrix.
// In production this could be backed by mmap, a database, or remote storage.
// ---------------------------------------------------------------------------
class InMemoryVectorSource : public zvec::core::VectorSource {
public:
/// @param base Pointer to a contiguous float array of shape [n, dim].
/// @param dim Dimensionality of each vector.
InMemoryVectorSource(const float *base, uint32_t dim)
: base_(base), dim_(dim) {}
const void *get_vector(uint32_t node_id) const override {
return base_ + static_cast<size_t>(node_id) * dim_;
}
private:
const float *base_;
uint32_t dim_;
};
// ---------------------------------------------------------------------------
// Helper: generate random float vectors in [0, 1)
// ---------------------------------------------------------------------------
static std::vector<float> generate_random_vectors(uint32_t count,
uint32_t dim) {
std::vector<float> data(static_cast<size_t>(count) * dim);
for (auto &v : data) {
v = static_cast<float>(std::rand()) / static_cast<float>(RAND_MAX);
}
return data;
}
// ---------------------------------------------------------------------------
// Helper: brute-force kNN (L2) for recall verification
// ---------------------------------------------------------------------------
static std::vector<uint32_t> brute_force_knn(const float *base, uint32_t n,
uint32_t dim, const float *query,
uint32_t topk) {
std::vector<std::pair<float, uint32_t>> dists(n);
for (uint32_t i = 0; i < n; ++i) {
float dist = 0.0f;
for (uint32_t d = 0; d < dim; ++d) {
float diff = base[static_cast<size_t>(i) * dim + d] - query[d];
dist += diff * diff;
}
dists[i] = {dist, i};
}
std::partial_sort(dists.begin(), dists.begin() + topk, dists.end());
std::vector<uint32_t> result(topk);
for (uint32_t i = 0; i < topk; ++i) {
result[i] = dists[i].second;
}
return result;
}
int main() {
constexpr uint32_t kDimension = 32;
constexpr uint32_t kDocCount = 200;
constexpr uint32_t kTopK = 5;
const std::string index_path = "external_vector_example.index";
// Clean up any previous run
std::filesystem::remove_all(index_path);
// ------ Step 1: Generate random vector data (simulating external storage)
std::srand(42);
auto vectors = generate_random_vectors(kDocCount, kDimension);
// Wrap data in our VectorSource
InMemoryVectorSource source(vectors.data(), kDimension);
// ------ Step 2: Build HNSW index with external-vector mode enabled
auto param = HNSWIndexParamBuilder()
.WithMetricType(MetricType::kL2sq)
.WithDataType(DataType::DT_FP32)
.WithDimension(kDimension)
.WithIsSparse(false)
.WithUseExternalVector(true) // <-- key setting
.Build();
auto index = IndexFactory::CreateAndInitIndex(*param);
if (!index) {
std::cerr << "Failed to create index." << std::endl;
return 1;
}
int ret = index->Open(
index_path, StorageOptions{StorageOptions::StorageType::kMMAP, true});
if (ret != 0) {
std::cerr << "Failed to open index." << std::endl;
return 1;
}
// ------ Step 3: Add vectors using AddWithSource
for (uint32_t i = 0; i < kDocCount; ++i) {
VectorData vd;
vd.vector =
DenseVector{vectors.data() + static_cast<size_t>(i) * kDimension};
ret = index->AddWithSource(vd, i, source);
if (ret != 0) {
std::cerr << "Failed to add doc " << i << std::endl;
return 1;
}
}
std::cout << "[OK] Added " << kDocCount << " vectors in external mode."
<< std::endl;
// ------ Step 4: Search using SearchWithSource
auto query_param =
HNSWQueryParamBuilder().with_topk(kTopK).with_ef_search(64).build();
// Use the first vector as query
const float *query_vec = vectors.data();
VectorData query;
query.vector = DenseVector{query_vec};
SearchResult result;
ret = index->SearchWithSource(query, query_param, source, &result);
if (ret != 0) {
std::cerr << "Search failed." << std::endl;
return 1;
}
std::cout << "[OK] Search returned " << result.doc_list_.size() << " results."
<< std::endl;
// The closest vector to vectors[0] should be itself (doc_id=0)
if (!result.doc_list_.empty() && result.doc_list_[0].key() == 0) {
std::cout << "[OK] Nearest neighbor is doc_id=0 (self), score="
<< result.doc_list_[0].score() << std::endl;
}
// ------ Step 5: Verify recall against brute-force
auto gt =
brute_force_knn(vectors.data(), kDocCount, kDimension, query_vec, kTopK);
uint32_t hits = 0;
for (const auto &doc : result.doc_list_) {
if (std::find(gt.begin(), gt.end(), static_cast<uint32_t>(doc.key())) !=
gt.end()) {
++hits;
}
}
float recall = static_cast<float>(hits) / static_cast<float>(kTopK);
std::cout << "[OK] Recall@" << kTopK << " = " << recall * 100.0f << "%"
<< std::endl;
// ------ Step 6: Reopen index and search again (persistence verification)
index->Close();
std::cout << "[OK] Index closed." << std::endl;
// Must re-create index instance with same params before reopening
index = IndexFactory::CreateAndInitIndex(*param);
if (!index) {
std::cerr << "Failed to re-create index for reopen." << std::endl;
return 1;
}
ret = index->Open(index_path,
StorageOptions{StorageOptions::StorageType::kMMAP, false});
if (ret != 0) {
std::cerr << "Failed to reopen index." << std::endl;
return 1;
}
SearchResult result2;
ret = index->SearchWithSource(query, query_param, source, &result2);
if (ret != 0) {
std::cerr << "Search after reopen failed." << std::endl;
return 1;
}
std::cout << "[OK] After reopen: search returned " << result2.doc_list_.size()
<< " results, top1 doc_id=" << result2.doc_list_[0].key()
<< std::endl;
// Cleanup
index->Close();
std::filesystem::remove_all(index_path);
std::cout << "\n=== External Vector Example Complete ===" << std::endl;
return 0;
}
+88
View File
@@ -0,0 +1,88 @@
#include <cstdlib>
#include <filesystem>
#include <iostream>
#include <zvec/core/interface/index.h>
#include <zvec/core/interface/index_factory.h>
#include <zvec/core/interface/index_param.h>
#include <zvec/core/interface/index_param_builders.h>
using namespace zvec::core_interface;
constexpr uint32_t kDimension = 64;
const std::string index_name{"test.index"};
Index::Pointer create_index(const BaseIndexParam::Pointer &param,
int doc_num = 10) {
auto index = IndexFactory::CreateAndInitIndex(*param);
if (!index) {
std::cout << "Failed to create index." << std::endl;
return nullptr;
}
int ret = index->Open(
index_name, StorageOptions{StorageOptions::StorageType::kMMAP, true});
if (ret != 0) {
std::cout << "Failed to open index." << std::endl;
return nullptr;
}
for (int i = 0; i < doc_num; ++i) {
std::vector<float> vector(kDimension, i / 10.0f + 0.1f);
VectorData vector_data;
vector_data.vector = DenseVector{vector.data()};
ret = index->Add(vector_data, i);
if (ret != 0) {
std::cout << "Failed to add to index." << std::endl;
return nullptr;
}
}
ret = index->Train();
if (ret != 0) {
std::cout << "Failed to train index." << std::endl;
return nullptr;
}
return index;
}
int main() {
std::filesystem::remove(index_name);
auto param = HNSWIndexParamBuilder()
.WithMetricType(MetricType::kInnerProduct)
.WithDataType(DataType::DT_FP32)
.WithDimension(kDimension)
.WithIsSparse(false)
.Build();
auto index = create_index(param, 1);
std::cout << "index stats: " << index->GetDocCount() << std::endl;
// query
auto query_param = HNSWQueryParamBuilder()
.with_topk(10)
.with_fetch_vector(true)
.with_ef_search(20)
.build();
SearchResult result;
VectorData query;
std::vector<float> vector(kDimension, 0.1f);
query.vector = DenseVector{vector.data()};
int ret = index->Search(query, query_param, &result);
if (ret != 0) {
std::cout << "Failed to search index." << std::endl;
return -1;
}
std::cout << "query results: " << result.doc_list_.size() << std::endl;
if (result.doc_list_.size() == 0) {
std::cout << "No results found." << std::endl;
return -1;
}
std::cout << "key: " << result.doc_list_[0].key()
<< ", score: " << result.doc_list_[0].score() << std::endl;
return 0;
}
+264
View File
@@ -0,0 +1,264 @@
#include <cstdlib>
#include <filesystem>
#include <string>
#include <vector>
#include <zvec/db/collection.h>
#include <zvec/db/doc.h>
#include <zvec/db/schema.h>
#include <zvec/db/status.h>
using namespace zvec;
Doc create_doc(const uint64_t doc_id, const CollectionSchema &schema,
std::string pk = "") {
Doc new_doc;
if (pk.empty()) {
pk = "pk_" + std::to_string(doc_id);
}
new_doc.set_pk(pk);
for (auto &field : schema.fields()) {
switch (field->data_type()) {
case DataType::BINARY: {
std::string binary_str("binary_" + std::to_string(doc_id));
new_doc.set<std::string>(field->name(), binary_str);
break;
}
case DataType::BOOL:
new_doc.set<bool>(field->name(), doc_id % 10 == 0);
break;
case DataType::INT32:
new_doc.set<int32_t>(field->name(), (int32_t)doc_id);
break;
case DataType::INT64:
new_doc.set<int64_t>(field->name(), (int64_t)doc_id);
break;
case DataType::UINT32:
new_doc.set<uint32_t>(field->name(), (uint32_t)doc_id);
break;
case DataType::UINT64:
new_doc.set<uint64_t>(field->name(), (uint64_t)doc_id);
break;
case DataType::FLOAT:
new_doc.set<float>(field->name(), (float)doc_id);
break;
case DataType::DOUBLE:
new_doc.set<double>(field->name(), (double)doc_id);
break;
case DataType::STRING:
new_doc.set<std::string>(field->name(),
"value_" + std::to_string(doc_id));
break;
case DataType::ARRAY_BINARY: {
std::vector<std::string> bin_vec;
for (size_t i = 0; i < (doc_id % 10); i++) {
bin_vec.push_back("bin_" + std::to_string(i));
}
new_doc.set<std::vector<std::string>>(field->name(), bin_vec);
break;
}
case DataType::ARRAY_BOOL:
new_doc.set<std::vector<bool>>(field->name(),
std::vector<bool>(10, doc_id % 10 == 0));
break;
case DataType::ARRAY_INT32:
new_doc.set<std::vector<int32_t>>(
field->name(), std::vector<int32_t>(10, (int32_t)doc_id));
break;
case DataType::ARRAY_INT64:
new_doc.set<std::vector<int64_t>>(
field->name(), std::vector<int64_t>(10, (int64_t)doc_id));
break;
case DataType::ARRAY_UINT32:
new_doc.set<std::vector<uint32_t>>(
field->name(), std::vector<uint32_t>(10, (uint32_t)doc_id));
break;
case DataType::ARRAY_UINT64:
new_doc.set<std::vector<uint64_t>>(
field->name(), std::vector<uint64_t>(10, (uint64_t)doc_id));
break;
case DataType::ARRAY_FLOAT:
new_doc.set<std::vector<float>>(field->name(),
std::vector<float>(10, (float)doc_id));
break;
case DataType::ARRAY_DOUBLE:
new_doc.set<std::vector<double>>(
field->name(), std::vector<double>(10, (double)doc_id));
break;
case DataType::ARRAY_STRING:
new_doc.set<std::vector<std::string>>(
field->name(),
std::vector<std::string>(10, "value_" + std::to_string(doc_id)));
break;
case DataType::VECTOR_BINARY32:
new_doc.set<std::vector<uint32_t>>(
field->name(),
std::vector<uint32_t>(field->dimension(), uint32_t(doc_id + 0.1)));
break;
case DataType::VECTOR_BINARY64:
new_doc.set<std::vector<uint64_t>>(
field->name(),
std::vector<uint64_t>(field->dimension(), uint64_t(doc_id + 0.1)));
break;
case DataType::VECTOR_FP32:
new_doc.set<std::vector<float>>(
field->name(),
std::vector<float>(field->dimension(), float(doc_id + 0.1)));
break;
case DataType::VECTOR_FP64:
new_doc.set<std::vector<double>>(
field->name(),
std::vector<double>(field->dimension(), double(doc_id + 0.1)));
break;
case DataType::VECTOR_FP16:
new_doc.set<std::vector<zvec::float16_t>>(
field->name(), std::vector<zvec::float16_t>(
field->dimension(), static_cast<zvec::float16_t>(
float(doc_id + 0.1))));
break;
case DataType::VECTOR_INT8:
new_doc.set<std::vector<int8_t>>(
field->name(),
std::vector<int8_t>(field->dimension(), (int8_t)doc_id));
break;
case DataType::VECTOR_INT16:
new_doc.set<std::vector<int16_t>>(
field->name(),
std::vector<int16_t>(field->dimension(), (int16_t)doc_id));
break;
case DataType::SPARSE_VECTOR_FP16: {
std::vector<uint32_t> indices;
std::vector<zvec::float16_t> values;
for (uint32_t i = 0; i < 100; i++) {
indices.push_back(i);
values.push_back(zvec::float16_t(float(doc_id + 0.1)));
}
std::pair<std::vector<uint32_t>, std::vector<zvec::float16_t>>
sparse_float_vec;
sparse_float_vec.first = indices;
sparse_float_vec.second = values;
new_doc.set<
std::pair<std::vector<uint32_t>, std::vector<zvec::float16_t>>>(
field->name(), sparse_float_vec);
break;
}
case DataType::SPARSE_VECTOR_FP32: {
std::vector<uint32_t> indices;
std::vector<float> values;
for (uint32_t i = 0; i < 100; i++) {
indices.push_back(i);
values.push_back(float(doc_id + 0.1));
}
std::pair<std::vector<uint32_t>, std::vector<float>> sparse_float_vec;
sparse_float_vec.first = indices;
sparse_float_vec.second = values;
new_doc.set<std::pair<std::vector<uint32_t>, std::vector<float>>>(
field->name(), sparse_float_vec);
break;
}
default:
std::cout << "Unsupported data type: " << field->name() << std::endl;
throw std::runtime_error("Unsupported vector data type");
}
}
return new_doc;
}
CollectionSchema::Ptr create_schema() {
auto schema = std::make_shared<CollectionSchema>("demo");
schema->set_max_doc_count_per_segment(1000);
schema->add_field(std::make_shared<FieldSchema>(
"id", DataType::INT64, false, std::make_shared<InvertIndexParams>(true)));
schema->add_field(std::make_shared<FieldSchema>(
"name", DataType::STRING, false,
std::make_shared<InvertIndexParams>(false)));
schema->add_field(
std::make_shared<FieldSchema>("weight", DataType::FLOAT, true));
schema->add_field(std::make_shared<FieldSchema>(
"dense", DataType::VECTOR_FP32, 128, false,
std::make_shared<HnswIndexParams>(MetricType::IP)));
schema->add_field(std::make_shared<FieldSchema>(
"sparse", DataType::SPARSE_VECTOR_FP32, 0, false,
std::make_shared<HnswIndexParams>(MetricType::IP)));
return schema;
}
int main() {
std::string path = "./demo";
std::filesystem::remove_all(path);
auto schema = create_schema();
CollectionOptions options{false, true};
auto result = Collection::CreateAndOpen(path, *schema, options);
if (!result.has_value()) {
std::cout << result.error().message() << std::endl;
return -1;
}
std::cout << "init stats: " << result.value()->Stats().value().to_string()
<< std::endl;
auto coll = std::move(result).value();
// insert docs
{
auto doc1 = create_doc(0, *schema);
std::vector<Doc> docs{doc1};
auto res = coll->Insert(docs);
if (!res.has_value()) {
std::cout << res.error().message() << std::endl;
return -1;
}
std::cout << "after insert stats " << coll->Stats().value().to_string()
<< std::endl;
}
// optimize
{
auto res = coll->Optimize();
if (!res.ok()) {
std::cout << res.message() << std::endl;
return -1;
}
std::cout << "after optimize stats " << coll->Stats().value().to_string()
<< std::endl;
}
// query
{
SearchQuery query;
query.topk_ = 10;
query.target_.field_name_ = "dense";
query.include_vector_ = true;
std::vector<float> query_vector = std::vector<float>(128, 0.1);
query.target_.set_vector(std::string((char *)query_vector.data(),
query_vector.size() * sizeof(float)));
auto res = coll->Query(query);
if (!res.has_value()) {
std::cout << res.error().message() << std::endl;
return -1;
}
std::cout << "query result: doc_count[" << res.value().size() << "]"
<< std::endl;
std::cout << "first doc: " << res.value()[0]->to_detail_string()
<< std::endl;
}
// close and reopen
coll.reset();
options.read_only_ = true;
result = Collection::Open(path, options);
if (!result.has_value()) {
std::cout << result.error().message() << std::endl;
return -1;
}
std::cout << "reopen stats: " << result.value()->Stats().value().to_string()
<< std::endl;
return 0;
}
+137
View File
@@ -0,0 +1,137 @@
# Copyright 2025-present the zvec project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
cmake_minimum_required(VERSION 3.13)
cmake_policy(SET CMP0077 NEW)
project(zvec-example-c)
set(CMAKE_C_STANDARD 99)
set(CMAKE_C_STANDARD_REQUIRED ON)
# Enable compile_commands.json
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# --- Paths to Zvec and dependencies ---
# Allow custom host build directory, default to "build"
if(NOT DEFINED HOST_BUILD_DIR)
set(HOST_BUILD_DIR "build")
endif()
set(ZVEC_INCLUDE_DIR ${CMAKE_BINARY_DIR}/../../../src/include)
set(ZVEC_GENERATED_INCLUDE_DIR ${CMAKE_BINARY_DIR}/../../../${HOST_BUILD_DIR}/src/generated)
set(ZVEC_LIB_DIR ${CMAKE_BINARY_DIR}/../../../${HOST_BUILD_DIR}/lib)
# Add include and library search paths
include_directories(${ZVEC_INCLUDE_DIR} ${ZVEC_GENERATED_INCLUDE_DIR})
link_directories(${ZVEC_LIB_DIR})
# Support multi-config builds (MSVC puts libs in Debug/Release subdirectories)
if(CMAKE_BUILD_TYPE)
link_directories(${ZVEC_LIB_DIR}/${CMAKE_BUILD_TYPE})
endif()
# Find required packages
find_package(Threads REQUIRED)
# Create INTERFACE target for zvec_c_api (fat shared library)
# No whole-archive flags needed — all symbols are already resolved in the .so/.dylib
add_library(zvec-c-api INTERFACE)
target_link_libraries(zvec-c-api INTERFACE
zvec_c_api
Threads::Threads
)
if(NOT WIN32)
target_link_libraries(zvec-c-api INTERFACE ${CMAKE_DL_LIBS})
endif()
if(APPLE)
target_link_options(zvec-c-api INTERFACE -Wl,-rpath,${ZVEC_LIB_DIR})
endif()
# Basic example
add_executable(c_api_basic_example basic_example.c)
target_link_libraries(c_api_basic_example PRIVATE
zvec-c-api
)
# Schema example
add_executable(c_api_collection_schema_example collection_schema_example.c)
target_link_libraries(c_api_collection_schema_example PRIVATE
zvec-c-api
)
# Struct document example
add_executable(c_api_doc_example doc_example.c)
target_link_libraries(c_api_doc_example PRIVATE
zvec-c-api
)
# Index example
add_executable(c_api_index_example index_example.c)
target_link_libraries(c_api_index_example PRIVATE
zvec-c-api
)
# Field schema example
add_executable(c_api_field_schema_example field_schema_example.c)
target_link_libraries(c_api_field_schema_example PRIVATE
zvec-c-api
)
# Optimized example
add_executable(c_api_optimized_example optimized_example.c)
target_link_libraries(c_api_optimized_example PRIVATE
zvec-c-api
)
# DiskANN example
add_executable(c_api_diskann_example diskann_example.c)
target_link_libraries(c_api_diskann_example PRIVATE
zvec-c-api
)
# Strip symbols to reduce executable size
if(CMAKE_BUILD_TYPE STREQUAL "Release" AND (ANDROID OR (CMAKE_SYSTEM_NAME STREQUAL "Linux")))
add_custom_command(TARGET c_api_basic_example POST_BUILD
COMMAND ${CMAKE_STRIP} "$<TARGET_FILE:c_api_basic_example>"
COMMENT "Stripping symbols from c_api_basic_example")
add_custom_command(TARGET c_api_collection_schema_example POST_BUILD
COMMAND ${CMAKE_STRIP} "$<TARGET_FILE:c_api_collection_schema_example>"
COMMENT "Stripping symbols from c_api_collection_schema_example")
add_custom_command(TARGET c_api_doc_example POST_BUILD
COMMAND ${CMAKE_STRIP} "$<TARGET_FILE:c_api_doc_example>"
COMMENT "Stripping symbols from c_api_doc_example")
add_custom_command(TARGET c_api_index_example POST_BUILD
COMMAND ${CMAKE_STRIP} "$<TARGET_FILE:c_api_index_example>"
COMMENT "Stripping symbols from c_api_index_example")
add_custom_command(TARGET c_api_field_schema_example POST_BUILD
COMMAND ${CMAKE_STRIP} "$<TARGET_FILE:c_api_field_schema_example>"
COMMENT "Stripping symbols from c_api_field_schema_example")
add_custom_command(TARGET c_api_optimized_example POST_BUILD
COMMAND ${CMAKE_STRIP} "$<TARGET_FILE:c_api_optimized_example>"
COMMENT "Stripping symbols from c_api_optimized_example")
add_custom_command(TARGET c_api_diskann_example POST_BUILD
COMMAND ${CMAKE_STRIP} "$<TARGET_FILE:c_api_diskann_example>"
COMMENT "Stripping symbols from c_api_diskann_example")
endif()
# Optimize for size
if(CMAKE_BUILD_TYPE STREQUAL "Release" AND ANDROID)
set_property(TARGET c_api_basic_example c_api_collection_schema_example c_api_doc_example
c_api_index_example c_api_field_schema_example c_api_optimized_example
c_api_diskann_example
PROPERTY COMPILE_FLAGS "-Os")
set_property(TARGET c_api_basic_example c_api_collection_schema_example c_api_doc_example
c_api_index_example c_api_field_schema_example c_api_optimized_example
c_api_diskann_example
PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE)
endif()
+267
View File
@@ -0,0 +1,267 @@
// Copyright 2025-present the zvec project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "zvec/c_api.h"
/**
* @brief Print error message and return error code
*/
static zvec_error_code_t handle_error(zvec_error_code_t error,
const char *context) {
if (error != ZVEC_OK) {
char *error_msg = NULL;
zvec_get_last_error(&error_msg);
fprintf(stderr, "Error in %s: %d - %s\n", context, error,
error_msg ? error_msg : "Unknown error");
zvec_free(error_msg);
}
return error;
}
/**
* @brief Create a simple test collection using CollectionSchema
*/
static zvec_error_code_t create_simple_test_collection(
zvec_collection_t **collection) {
// Create collection schema using C API
zvec_collection_schema_t *schema =
zvec_collection_schema_create("test_collection");
if (!schema) {
return ZVEC_ERROR_INTERNAL_ERROR;
}
zvec_error_code_t error = ZVEC_OK;
// Create index parameters using new API
zvec_index_params_t *invert_params =
zvec_index_params_create(ZVEC_INDEX_TYPE_INVERT);
if (!invert_params) {
zvec_collection_schema_destroy(schema);
return ZVEC_ERROR_RESOURCE_EXHAUSTED;
}
zvec_index_params_set_invert_params(invert_params, true, false);
zvec_index_params_t *hnsw_params =
zvec_index_params_create(ZVEC_INDEX_TYPE_HNSW);
if (!hnsw_params) {
zvec_index_params_destroy(invert_params);
zvec_collection_schema_destroy(schema);
return ZVEC_ERROR_RESOURCE_EXHAUSTED;
}
zvec_index_params_set_metric_type(hnsw_params, ZVEC_METRIC_TYPE_COSINE);
zvec_index_params_set_hnsw_params(hnsw_params, 16, 200);
// Create and add ID field (primary key)
zvec_field_schema_t *id_field =
zvec_field_schema_create("id", ZVEC_DATA_TYPE_STRING, false, 0);
zvec_field_schema_set_index_params(id_field, invert_params);
error = zvec_collection_schema_add_field(schema, id_field);
if (error != ZVEC_OK) {
zvec_index_params_destroy(invert_params);
zvec_index_params_destroy(hnsw_params);
zvec_collection_schema_destroy(schema);
return error;
}
// Create text field (inverted index)
zvec_field_schema_t *text_field =
zvec_field_schema_create("text", ZVEC_DATA_TYPE_STRING, true, 0);
zvec_field_schema_set_index_params(text_field, invert_params);
error = zvec_collection_schema_add_field(schema, text_field);
if (error != ZVEC_OK) {
zvec_index_params_destroy(invert_params);
zvec_index_params_destroy(hnsw_params);
zvec_collection_schema_destroy(schema);
return error;
}
// Create embedding field (HNSW index)
zvec_field_schema_t *embedding_field = zvec_field_schema_create(
"embedding", ZVEC_DATA_TYPE_VECTOR_FP32, false, 3);
zvec_field_schema_set_index_params(embedding_field, hnsw_params);
error = zvec_collection_schema_add_field(schema, embedding_field);
if (error != ZVEC_OK) {
zvec_index_params_destroy(invert_params);
zvec_index_params_destroy(hnsw_params);
zvec_collection_schema_destroy(schema);
return error;
}
// Cleanup index parameters (they have been copied to the field schemas)
zvec_index_params_destroy(invert_params);
zvec_index_params_destroy(hnsw_params);
// Use default options
zvec_collection_options_t *options = zvec_collection_options_create();
if (!options) {
zvec_collection_schema_destroy(schema);
return ZVEC_ERROR_RESOURCE_EXHAUSTED;
}
// Create collection using the new API
error = zvec_collection_create_and_open("./test_collection", schema, options,
collection);
// Cleanup resources
zvec_collection_options_destroy(options);
zvec_collection_schema_destroy(schema);
return error;
}
/**
* @brief Basic C API usage example
*/
int main() {
printf("=== ZVec C API Basic Example ===\n\n");
zvec_error_code_t error;
// Create collection using simplified function
zvec_collection_t *collection = NULL;
error = create_simple_test_collection(&collection);
if (handle_error(error, "creating collection") != ZVEC_OK) {
return 1;
}
printf("✓ Collection created successfully\n");
// Prepare test data
float vector1[] = {0.1f, 0.2f, 0.3f};
float vector2[] = {0.4f, 0.5f, 0.6f};
zvec_doc_t *docs[2];
for (int i = 0; i < 2; ++i) {
docs[i] = zvec_doc_create();
if (!docs[i]) {
fprintf(stderr, "Failed to create document %d\n", i);
// Cleanup allocated resources
for (int j = 0; j < i; ++j) {
zvec_doc_destroy(docs[j]);
}
return ZVEC_ERROR_INTERNAL_ERROR;
}
}
// Manually add fields to document 1
zvec_doc_set_pk(docs[0], "doc1");
zvec_doc_add_field_by_value(docs[0], "id", ZVEC_DATA_TYPE_STRING, "doc1",
strlen("doc1"));
zvec_doc_add_field_by_value(docs[0], "text", ZVEC_DATA_TYPE_STRING,
"First document", strlen("First document"));
zvec_doc_add_field_by_value(docs[0], "embedding", ZVEC_DATA_TYPE_VECTOR_FP32,
vector1, 3 * sizeof(float));
// Manually add fields to document 2
zvec_doc_set_pk(docs[1], "doc2");
zvec_doc_add_field_by_value(docs[1], "id", ZVEC_DATA_TYPE_STRING, "doc2",
strlen("doc2"));
zvec_doc_add_field_by_value(docs[1], "text", ZVEC_DATA_TYPE_STRING,
"Second document", strlen("Second document"));
zvec_doc_add_field_by_value(docs[1], "embedding", ZVEC_DATA_TYPE_VECTOR_FP32,
vector2, 3 * sizeof(float));
// Insert documents
size_t success_count = 0;
size_t error_count = 0;
error = zvec_collection_insert(collection, (const zvec_doc_t **)docs, 2,
&success_count, &error_count);
if (handle_error(error, "inserting documents") != ZVEC_OK) {
zvec_collection_destroy(collection);
return 1;
}
printf("✓ Documents inserted - Success: %zu, Failed: %zu\n", success_count,
error_count);
for (int i = 0; i < 2; ++i) {
zvec_doc_destroy(docs[i]);
}
// Flush collection
error = zvec_collection_flush(collection);
if (handle_error(error, "flushing collection") != ZVEC_OK) {
printf("Collection flush failed\n");
} else {
printf("✓ Collection flushed successfully\n");
}
// Get collection statistics
zvec_collection_stats_t *stats = NULL;
error = zvec_collection_get_stats(collection, &stats);
if (handle_error(error, "getting collection stats") == ZVEC_OK) {
printf("✓ Collection stats - Document count: %llu\n",
(unsigned long long)zvec_collection_stats_get_doc_count(stats));
// Free statistics memory
zvec_collection_stats_destroy(stats);
}
printf("Testing vector query...\n");
// Query documents
zvec_vector_query_t *query = zvec_vector_query_create();
if (!query) {
fprintf(stderr, "Failed to create vector query\n");
zvec_collection_destroy(collection);
return 1;
}
zvec_vector_query_set_field_name(query, "embedding");
zvec_vector_query_set_query_vector(query, vector1, 3 * sizeof(float));
zvec_vector_query_set_topk(query, 10);
zvec_vector_query_set_filter(query, "");
zvec_vector_query_set_include_vector(query, true);
zvec_vector_query_set_include_doc_id(query, true);
zvec_doc_t **results = NULL;
size_t result_count = 0;
error = zvec_collection_query(collection, (const zvec_vector_query_t *)query,
&results, &result_count);
if (error != ZVEC_OK) {
char *error_msg = NULL;
zvec_get_last_error(&error_msg);
printf("[ERROR] Query failed: %s\n",
error_msg ? error_msg : "Unknown error");
zvec_free(error_msg);
zvec_vector_query_destroy(query);
goto cleanup;
}
zvec_vector_query_destroy(query);
printf("✓ Query successful - Returned %zu results\n", result_count);
// Process query results
for (size_t i = 0; i < result_count && i < 5; ++i) {
const zvec_doc_t *doc = results[i];
const char *pk = zvec_doc_get_pk_copy(doc);
printf(" Result %zu: PK=%s, DocID=%llu, Score=%.4f\n", i + 1,
pk ? pk : "NULL", (unsigned long long)zvec_doc_get_doc_id(doc),
zvec_doc_get_score(doc));
if (pk) {
zvec_free((void *)pk);
}
}
// Free query results memory
zvec_docs_free(results, result_count);
cleanup:
// Cleanup resources
zvec_collection_destroy(collection);
printf("✓ Example completed\n");
return 0;
}
+255
View File
@@ -0,0 +1,255 @@
// Copyright 2025-present the zvec project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "zvec/c_api.h"
/**
* @brief Print error message and return error code
*/
static zvec_error_code_t handle_error(zvec_error_code_t error,
const char *context) {
if (error != ZVEC_OK) {
char *error_msg = NULL;
zvec_get_last_error(&error_msg);
fprintf(stderr, "Error in %s: %d - %s\n", context, error,
error_msg ? error_msg : "Unknown error");
zvec_free(error_msg);
}
return error;
}
/**
* @brief Collection schema creation and management example
*/
int main() {
printf("=== ZVec Collection Schema Example ===\n\n");
zvec_error_code_t error;
// 1. Create collection schema
zvec_collection_schema_t *schema =
zvec_collection_schema_create("schema_example_collection");
if (!schema) {
fprintf(stderr, "Failed to create collection schema\n");
return 1;
}
printf("✓ Collection schema created successfully\n");
// 2. Set schema properties
zvec_collection_schema_set_max_doc_count_per_segment(schema, 1000000);
printf("✓ Set max documents per segment: %llu\n",
(unsigned long long)
zvec_collection_schema_get_max_doc_count_per_segment(schema));
// 3. Create index parameters
zvec_index_params_t *invert_params =
zvec_index_params_create(ZVEC_INDEX_TYPE_INVERT);
if (!invert_params) {
fprintf(stderr, "Failed to create invert index parameters\n");
zvec_collection_schema_destroy(schema);
return 1;
}
zvec_index_params_set_invert_params(invert_params, true, false);
zvec_index_params_t *hnsw_params =
zvec_index_params_create(ZVEC_INDEX_TYPE_HNSW);
if (!hnsw_params) {
fprintf(stderr, "Failed to create HNSW index parameters\n");
zvec_index_params_destroy(invert_params);
zvec_collection_schema_destroy(schema);
return 1;
}
zvec_index_params_set_metric_type(hnsw_params, ZVEC_METRIC_TYPE_L2);
zvec_index_params_set_hnsw_params(hnsw_params, 16, 200);
// 4. Create and add ID field (primary key)
zvec_field_schema_t *id_field =
zvec_field_schema_create("id", ZVEC_DATA_TYPE_STRING, false, 0);
if (!id_field) {
fprintf(stderr, "Failed to create ID field\n");
zvec_collection_schema_destroy(schema);
return 1;
}
error = zvec_collection_schema_add_field(schema, id_field);
if (handle_error(error, "adding ID field") != ZVEC_OK) {
zvec_collection_schema_destroy(schema);
return 1;
}
printf("✓ ID field added successfully\n");
// 5. Create and add text field with inverted index
zvec_field_schema_t *text_field =
zvec_field_schema_create("content", ZVEC_DATA_TYPE_STRING, true, 0);
if (!text_field) {
fprintf(stderr, "Failed to create text field\n");
zvec_collection_schema_destroy(schema);
return 1;
}
zvec_field_schema_set_index_params(text_field, invert_params);
error = zvec_collection_schema_add_field(schema, text_field);
if (handle_error(error, "adding text field") != ZVEC_OK) {
zvec_collection_schema_destroy(schema);
return 1;
}
printf("✓ Text field with inverted index added successfully\n");
// 6. Create and add vector field with HNSW index
zvec_field_schema_t *vector_field = zvec_field_schema_create(
"embedding", ZVEC_DATA_TYPE_VECTOR_FP32, false, 128);
if (!vector_field) {
fprintf(stderr, "Failed to create vector field\n");
zvec_collection_schema_destroy(schema);
return 1;
}
zvec_field_schema_set_index_params(vector_field, hnsw_params);
error = zvec_collection_schema_add_field(schema, vector_field);
if (handle_error(error, "adding vector field") != ZVEC_OK) {
zvec_collection_schema_destroy(schema);
return 1;
}
printf("✓ Vector field with HNSW index added successfully\n");
// 7. Check field count
// Note: This function may not exist in current API, commenting out for now
// size_t field_count = zvec_collection_schema_get_field_count(schema);
// printf("✓ Total field count: %zu\n", field_count);
// 8. Create collection with schema
zvec_collection_options_t *options = zvec_collection_options_create();
if (!options) {
fprintf(stderr, "Failed to create collection options\n");
zvec_collection_schema_destroy(schema);
return 1;
}
zvec_collection_t *collection = NULL;
error = zvec_collection_create_and_open("./schema_example_collection", schema,
options, &collection);
if (handle_error(error, "creating collection with schema") != ZVEC_OK) {
zvec_collection_options_destroy(options);
zvec_collection_schema_destroy(schema);
return 1;
}
zvec_collection_options_destroy(options);
printf("✓ Collection created successfully with schema\n");
// 9. Prepare test data
float vector1[128];
float vector2[128];
for (int i = 0; i < 128; i++) {
vector1[i] = (float)(i + 1) / 128.0f;
vector2[i] = (float)(i + 2) / 128.0f;
}
// 10. Create documents
zvec_doc_t *docs[2];
for (int i = 0; i < 2; i++) {
docs[i] = zvec_doc_create();
if (!docs[i]) {
fprintf(stderr, "Failed to create document %d\n", i);
// Cleanup
for (int j = 0; j < i; j++) {
zvec_doc_destroy(docs[j]);
}
zvec_collection_destroy(collection);
zvec_collection_schema_destroy(schema);
return 1;
}
}
// Add fields to document 1
zvec_doc_set_pk(docs[0], "doc1");
zvec_doc_add_field_by_value(docs[0], "id", ZVEC_DATA_TYPE_STRING, "doc1",
strlen("doc1"));
zvec_doc_add_field_by_value(docs[0], "content", ZVEC_DATA_TYPE_STRING,
"First test document",
strlen("First test document"));
zvec_doc_add_field_by_value(docs[0], "embedding", ZVEC_DATA_TYPE_VECTOR_FP32,
vector1, 128 * sizeof(float));
// Add fields to document 2
zvec_doc_set_pk(docs[1], "doc2");
zvec_doc_add_field_by_value(docs[1], "id", ZVEC_DATA_TYPE_STRING, "doc2",
strlen("doc2"));
zvec_doc_add_field_by_value(docs[1], "content", ZVEC_DATA_TYPE_STRING,
"Second test document",
strlen("Second test document"));
zvec_doc_add_field_by_value(docs[1], "embedding", ZVEC_DATA_TYPE_VECTOR_FP32,
vector2, 128 * sizeof(float));
// 11. Insert documents
size_t success_count = 0, error_count = 0;
error = zvec_collection_insert(collection, (const zvec_doc_t **)docs, 2,
&success_count, &error_count);
if (handle_error(error, "inserting documents") != ZVEC_OK) {
// Cleanup
for (int i = 0; i < 2; i++) {
zvec_doc_destroy(docs[i]);
}
zvec_collection_destroy(collection);
zvec_collection_schema_destroy(schema);
return 1;
}
printf("✓ Documents inserted - Success: %zu, Failed: %zu\n", success_count,
error_count);
// Cleanup documents
for (int i = 0; i < 2; i++) {
zvec_doc_destroy(docs[i]);
}
// 12. Flush collection
error = zvec_collection_flush(collection);
if (handle_error(error, "flushing collection") == ZVEC_OK) {
printf("✓ Collection flushed successfully\n");
}
// 13. Query test
zvec_vector_query_t *query = zvec_vector_query_create();
if (!query) {
fprintf(stderr, "Failed to create vector query\n");
zvec_collection_destroy(collection);
zvec_collection_schema_destroy(schema);
return 1;
}
zvec_vector_query_set_field_name(query, "embedding");
zvec_vector_query_set_query_vector(query, vector1, 128 * sizeof(float));
zvec_vector_query_set_topk(query, 5);
zvec_vector_query_set_filter(query, "");
zvec_vector_query_set_include_vector(query, true);
zvec_vector_query_set_include_doc_id(query, true);
zvec_doc_t **results = NULL;
size_t result_count = 0;
error = zvec_collection_query(collection, (const zvec_vector_query_t *)query,
&results, &result_count);
if (error == ZVEC_OK) {
printf("✓ Vector query successful - Returned %zu results\n", result_count);
zvec_docs_free(results, result_count);
}
zvec_vector_query_destroy(query);
// 14. Cleanup resources
zvec_collection_destroy(collection);
zvec_collection_schema_destroy(schema);
printf("✓ Schema example completed\n");
return 0;
}
+346
View File
@@ -0,0 +1,346 @@
// Copyright 2025-present the zvec project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @file diskann_example.c
* @brief End-to-end example demonstrating DiskANN index usage via the C API.
*
* DiskANN is a disk-based approximate nearest neighbor search algorithm
* optimized for large-scale datasets that exceed available memory. It uses
* a Vamana graph structure combined with product quantization (PQ) to
* achieve high recall with efficient disk I/O.
*
* NOTE: DiskANN requires Linux x86_64 with libaio. On other platforms the
* example will compile but the runtime plugin will fail to load.
*
* Workflow demonstrated:
* 1. Create collection schema with DiskANN-indexed vector field
* 2. Insert documents with high-dimensional vectors
* 3. Flush collection (triggers PQ training + graph build)
* 4. Search using DiskANN query parameters (list_size controls recall)
* 5. Clean up all resources
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "zvec/c_api.h"
/* --------------------------------------------------------------------------
* Helpers
* -------------------------------------------------------------------------- */
static zvec_error_code_t handle_error(zvec_error_code_t error,
const char *context) {
if (error != ZVEC_OK) {
char *error_msg = NULL;
zvec_get_last_error(&error_msg);
fprintf(stderr, "Error in %s: %d - %s\n", context, error,
error_msg ? error_msg : "Unknown error");
zvec_free(error_msg);
}
return error;
}
#define VECTOR_DIM 64
#define NUM_DOCS 100
#define COLLECTION_DIR "./diskann_example_collection"
/* --------------------------------------------------------------------------
* Main
* -------------------------------------------------------------------------- */
int main(void) {
printf("=== ZVec DiskANN Index Example ===\n\n");
zvec_error_code_t error;
int i;
/* ------------------------------------------------------------------
* Step 1: Create collection schema
* ------------------------------------------------------------------ */
printf("[Step 1] Creating collection schema...\n");
zvec_collection_schema_t *schema =
zvec_collection_schema_create("diskann_example");
if (!schema) {
fprintf(stderr, "Failed to create schema\n");
return 1;
}
/* Index params — declared up-front and NULL-initialized so the
* cleanup_schema path never touches an uninitialized pointer even if an
* early field addition fails. */
zvec_index_params_t *invert_params = NULL;
zvec_index_params_t *diskann_params = NULL;
/* Scalar field with inverted index (for primary key / filtering) */
invert_params = zvec_index_params_create(ZVEC_INDEX_TYPE_INVERT);
zvec_index_params_set_invert_params(invert_params, true, false);
zvec_field_schema_t *id_field =
zvec_field_schema_create("id", ZVEC_DATA_TYPE_STRING, false, 0);
zvec_field_schema_set_index_params(id_field, invert_params);
error = zvec_collection_schema_add_field(schema, id_field);
if (handle_error(error, "adding id field") != ZVEC_OK) {
goto cleanup_schema;
}
printf(" + id field (STRING, inverted index)\n");
/* Vector field with DiskANN index */
diskann_params = zvec_index_params_create(ZVEC_INDEX_TYPE_DISKANN);
if (!diskann_params) {
fprintf(stderr, "Failed to create DiskANN index parameters\n");
goto cleanup_schema;
}
zvec_index_params_set_metric_type(diskann_params, ZVEC_METRIC_TYPE_L2);
zvec_index_params_set_diskann_params(
diskann_params, 64, /* max_degree: graph connectivity */
100, /* list_size: build-time candidates */
8); /* pq_chunk_num: PQ chunks (0=auto) */
printf(
" DiskANN index params: max_degree=%d, list_size=%d, pq_chunk_num=%d\n",
zvec_index_params_get_diskann_max_degree(diskann_params),
zvec_index_params_get_diskann_list_size(diskann_params),
zvec_index_params_get_diskann_pq_chunk_num(diskann_params));
zvec_field_schema_t *embedding_field = zvec_field_schema_create(
"embedding", ZVEC_DATA_TYPE_VECTOR_FP32, false, VECTOR_DIM);
zvec_field_schema_set_index_params(embedding_field, diskann_params);
error = zvec_collection_schema_add_field(schema, embedding_field);
if (handle_error(error, "adding embedding field") != ZVEC_OK) {
goto cleanup_schema;
}
printf(" + embedding field (VECTOR_FP32, %dD, DiskANN index)\n", VECTOR_DIM);
/* Index params are copied into field schemas; safe to destroy now */
zvec_index_params_destroy(invert_params);
zvec_index_params_destroy(diskann_params);
invert_params = NULL;
diskann_params = NULL;
/* ------------------------------------------------------------------
* Step 2: Create and open collection
* ------------------------------------------------------------------ */
printf("\n[Step 2] Creating collection...\n");
zvec_collection_options_t *options = zvec_collection_options_create();
zvec_collection_t *collection = NULL;
error = zvec_collection_create_and_open(COLLECTION_DIR, schema, options,
&collection);
zvec_collection_options_destroy(options);
if (handle_error(error, "creating collection") != ZVEC_OK) {
goto cleanup_schema;
}
printf(" Collection created at %s\n", COLLECTION_DIR);
/* ------------------------------------------------------------------
* Step 3: Generate and insert documents
* ------------------------------------------------------------------ */
printf("\n[Step 3] Inserting %d documents with %dD vectors...\n", NUM_DOCS,
VECTOR_DIM);
/* Allocate vector storage */
float(*vectors)[VECTOR_DIM] =
(float(*)[VECTOR_DIM])malloc(NUM_DOCS * VECTOR_DIM * sizeof(float));
if (!vectors) {
fprintf(stderr, "Failed to allocate vector storage\n");
goto cleanup_collection;
}
/* Generate deterministic vector data */
for (i = 0; i < NUM_DOCS; i++) {
for (int d = 0; d < VECTOR_DIM; d++) {
vectors[i][d] = (float)((i * VECTOR_DIM + d) % 1000) / 1000.0f;
}
}
/* Insert in batches */
int batch_size = 20;
size_t total_success = 0, total_error = 0;
for (int batch_start = 0; batch_start < NUM_DOCS; batch_start += batch_size) {
int count = batch_start + batch_size > NUM_DOCS ? NUM_DOCS - batch_start
: batch_size;
zvec_doc_t **docs =
(zvec_doc_t **)malloc((size_t)count * sizeof(zvec_doc_t *));
for (i = 0; i < count; i++) {
int idx = batch_start + i;
docs[i] = zvec_doc_create();
char pk[32];
snprintf(pk, sizeof(pk), "doc_%04d", idx);
zvec_doc_set_pk(docs[i], pk);
zvec_doc_add_field_by_value(docs[i], "id", ZVEC_DATA_TYPE_STRING, pk,
strlen(pk));
zvec_doc_add_field_by_value(docs[i], "embedding",
ZVEC_DATA_TYPE_VECTOR_FP32, vectors[idx],
VECTOR_DIM * sizeof(float));
}
size_t success_count = 0, error_count = 0;
error = zvec_collection_insert(collection, (const zvec_doc_t **)docs,
(size_t)count, &success_count, &error_count);
if (error != ZVEC_OK) {
handle_error(error, "inserting batch");
}
total_success += success_count;
total_error += error_count;
for (i = 0; i < count; i++) {
zvec_doc_destroy(docs[i]);
}
free(docs);
}
printf(" Inserted: %zu succeeded, %zu failed\n", total_success, total_error);
/* ------------------------------------------------------------------
* Step 4: Flush to trigger index build (PQ training + graph construction)
* ------------------------------------------------------------------ */
printf("\n[Step 4] Flushing collection (triggers DiskANN index build)...\n");
error = zvec_collection_flush(collection);
if (handle_error(error, "flushing collection") != ZVEC_OK) {
goto cleanup_vectors;
}
zvec_collection_stats_t *stats = NULL;
error = zvec_collection_get_stats(collection, &stats);
if (error == ZVEC_OK && stats) {
printf(" Document count after flush: %llu\n",
(unsigned long long)zvec_collection_stats_get_doc_count(stats));
zvec_collection_stats_destroy(stats);
}
/* ------------------------------------------------------------------
* Step 5: Search with DiskANN query parameters
* ------------------------------------------------------------------ */
printf("\n[Step 5] Searching with DiskANN query parameters...\n");
/* Create DiskANN query params — list_size controls the search frontier
* (beam width). Larger values improve recall at the cost of latency. */
zvec_diskann_query_params_t *da_qp = zvec_query_params_diskann_create(200);
if (!da_qp) {
fprintf(stderr, "Failed to create DiskANN query params\n");
goto cleanup_vectors;
}
printf(" DiskANN query params: list_size=%d\n",
zvec_query_params_diskann_get_list_size(da_qp));
/* Build the vector query */
zvec_vector_query_t *query = zvec_vector_query_create();
zvec_vector_query_set_field_name(query, "embedding");
zvec_vector_query_set_query_vector(query, vectors[0],
VECTOR_DIM * sizeof(float));
zvec_vector_query_set_topk(query, 10);
zvec_vector_query_set_include_vector(query, false);
zvec_vector_query_set_include_doc_id(query, true);
/* Attach DiskANN query params (ownership transfers to query) */
error = zvec_vector_query_set_diskann_params(query, da_qp);
if (handle_error(error, "setting DiskANN query params") != ZVEC_OK) {
zvec_vector_query_destroy(query);
goto cleanup_vectors;
}
/* da_qp is now owned by query — do NOT call diskann_destroy on it */
/* Execute the query */
zvec_doc_t **results = NULL;
size_t result_count = 0;
error = zvec_collection_query(collection, (const zvec_vector_query_t *)query,
&results, &result_count);
if (error != ZVEC_OK) {
handle_error(error, "executing DiskANN query");
printf(
" (This is expected on non-Linux platforms — DiskANN requires "
"libaio)\n");
} else {
printf(" Query returned %zu results:\n", result_count);
for (size_t r = 0; r < result_count && r < 5; r++) {
const char *pk = zvec_doc_get_pk_copy(results[r]);
printf(" [%zu] pk=%s doc_id=%llu score=%.6f\n", r + 1,
pk ? pk : "NULL",
(unsigned long long)zvec_doc_get_doc_id(results[r]),
zvec_doc_get_score(results[r]));
if (pk) {
zvec_free((void *)pk);
}
}
if (result_count > 5) {
printf(" ... and %zu more\n", result_count - 5);
}
zvec_docs_free(results, result_count);
}
zvec_vector_query_destroy(query);
/* ------------------------------------------------------------------
* Step 6: Demonstrate list_size tuning (higher recall vs. lower latency)
* ------------------------------------------------------------------ */
printf("\n[Step 6] Tuning list_size for recall/latency trade-off...\n");
int list_sizes[] = {50, 100, 300};
for (int li = 0; li < 3; li++) {
zvec_diskann_query_params_t *tune_qp =
zvec_query_params_diskann_create(list_sizes[li]);
zvec_vector_query_t *tune_query = zvec_vector_query_create();
zvec_vector_query_set_field_name(tune_query, "embedding");
zvec_vector_query_set_query_vector(tune_query, vectors[0],
VECTOR_DIM * sizeof(float));
zvec_vector_query_set_topk(tune_query, 10);
zvec_vector_query_set_include_doc_id(tune_query, true);
zvec_vector_query_set_diskann_params(tune_query, tune_qp);
zvec_doc_t **tune_results = NULL;
size_t tune_count = 0;
error = zvec_collection_query(collection,
(const zvec_vector_query_t *)tune_query,
&tune_results, &tune_count);
if (error == ZVEC_OK) {
printf(" list_size=%3d -> %zu results returned\n", list_sizes[li],
tune_count);
zvec_docs_free(tune_results, tune_count);
} else {
printf(" list_size=%3d -> query failed (expected on non-Linux)\n",
list_sizes[li]);
}
zvec_vector_query_destroy(tune_query);
}
/* ------------------------------------------------------------------
* Cleanup
* ------------------------------------------------------------------ */
cleanup_vectors:
free(vectors);
cleanup_collection:
zvec_collection_destroy(collection);
cleanup_schema:
zvec_collection_schema_destroy(schema);
if (invert_params) {
zvec_index_params_destroy(invert_params);
}
if (diskann_params) {
zvec_index_params_destroy(diskann_params);
}
printf("\n DiskANN index type string: %s\n",
zvec_index_type_to_string(ZVEC_INDEX_TYPE_DISKANN));
printf("=== Example completed ===\n");
return 0;
}
+539
View File
@@ -0,0 +1,539 @@
// Copyright 2025-present the zvec project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <math.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "zvec/c_api.h"
/**
* @brief Print error message and return error code
*/
static zvec_error_code_t handle_error(zvec_error_code_t error,
const char *context) {
if (error != ZVEC_OK) {
char *error_msg = NULL;
zvec_get_last_error(&error_msg);
fprintf(stderr, "Error in %s: %d - %s\n", context, error,
error_msg ? error_msg : "Unknown error");
zvec_free(error_msg);
}
return error;
}
/**
* @brief Create a test document with all data types
* @param doc_index Document index for generating unique data
* @return zvec_doc_t* Created document pointer
*/
static zvec_doc_t *create_full_type_test_doc(int doc_index) {
zvec_doc_t *doc = zvec_doc_create();
if (!doc) {
fprintf(stderr, "Failed to create document\n");
return NULL;
}
// Set primary key
char pk_buffer[32];
snprintf(pk_buffer, sizeof(pk_buffer), "doc_%d", doc_index);
zvec_doc_set_pk(doc, pk_buffer);
// Add Id field with inverted index
char id_buffer[32];
snprintf(id_buffer, sizeof(id_buffer), "id_%d", doc_index);
zvec_doc_add_field_by_value(doc, "id", ZVEC_DATA_TYPE_STRING, id_buffer,
strlen(id_buffer));
// Add scalar fields with different data types
// String field
char string_value[64];
snprintf(string_value, sizeof(string_value), "test_string_%d", doc_index);
zvec_doc_add_field_by_value(doc, "string_field", ZVEC_DATA_TYPE_STRING,
string_value, strlen(string_value));
// Boolean field
bool bool_value = (doc_index % 2 == 0);
zvec_doc_add_field_by_value(doc, "bool_field", ZVEC_DATA_TYPE_BOOL,
&bool_value, sizeof(bool_value));
// Integer fields
int32_t int32_value = doc_index * 1000;
zvec_doc_add_field_by_value(doc, "int32_field", ZVEC_DATA_TYPE_INT32,
&int32_value, sizeof(int32_value));
int64_t int64_value = (int64_t)doc_index * 1000000LL;
zvec_doc_add_field_by_value(doc, "int64_field", ZVEC_DATA_TYPE_INT64,
&int64_value, sizeof(int64_value));
// Floating point fields
float float_value = (float)doc_index * 1.5f;
zvec_doc_add_field_by_value(doc, "float_field", ZVEC_DATA_TYPE_FLOAT,
&float_value, sizeof(float_value));
double double_value = (double)doc_index * 2.718281828;
zvec_doc_add_field_by_value(doc, "double_field", ZVEC_DATA_TYPE_DOUBLE,
&double_value, sizeof(double_value));
// Vector fields with different dimensions
// FP32 vector (3D)
float fp32_vector[3] = {(float)doc_index, (float)doc_index * 2.0f,
(float)doc_index * 3.0f};
zvec_doc_add_field_by_value(doc, "vector_fp32", ZVEC_DATA_TYPE_VECTOR_FP32,
fp32_vector, 3 * sizeof(float));
// Larger FP32 vector (16D)
float large_vector[16];
for (int i = 0; i < 16; i++) {
large_vector[i] = (float)(doc_index * 16 + i) / 256.0f;
}
zvec_doc_add_field_by_value(doc, "large_vector", ZVEC_DATA_TYPE_VECTOR_FP32,
large_vector, 16 * sizeof(float));
return doc;
}
/**
* @brief Compare two documents for equality
*/
static bool compare_documents(const zvec_doc_t *doc1, const zvec_doc_t *doc2) {
if (!doc1 || !doc2) return false;
// Compare primary keys
const char *pk1 = zvec_doc_get_pk_pointer(doc1);
const char *pk2 = zvec_doc_get_pk_pointer(doc2);
if (!pk1 || !pk2 || strcmp(pk1, pk2) != 0) {
return false;
}
// TODO: Compare other fields and values
return true;
}
/**
* @brief Print document fields and their values
* @param doc The document to print
* @param doc_index Document index for identification
*/
static void print_doc(const zvec_doc_t *doc, int doc_index) {
if (!doc) {
printf("Document %d: NULL document\n", doc_index);
return;
}
printf("\n=== Document %d ===\n", doc_index);
// Print primary key
const char *pk = zvec_doc_get_pk_pointer(doc);
printf("Primary Key: %s\n", pk ? pk : "NULL");
// Print document ID
uint64_t doc_id = zvec_doc_get_doc_id(doc);
printf("Document ID: %llu\n", (unsigned long long)doc_id);
// Print score
float score = zvec_doc_get_score(doc);
printf("Score: %.6f\n", score);
// Print scalar fields
printf("\nScalar Fields:\n");
// ID field (using pointer function for strings)
const void *id_value = NULL;
size_t id_size = 0;
zvec_error_code_t error = zvec_doc_get_field_value_pointer(
doc, "id", ZVEC_DATA_TYPE_STRING, &id_value, &id_size);
if (error == ZVEC_OK && id_value) {
printf(" id: %.*s\n", (int)id_size, (const char *)id_value);
}
// String field (using pointer function for strings)
const void *string_value = NULL;
size_t string_size = 0;
error = zvec_doc_get_field_value_pointer(
doc, "string_field", ZVEC_DATA_TYPE_STRING, &string_value, &string_size);
if (error == ZVEC_OK && string_value) {
printf(" string_field: %.*s\n", (int)string_size,
(const char *)string_value);
}
// Boolean field
bool bool_value;
error = zvec_doc_get_field_value_basic(doc, "bool_field", ZVEC_DATA_TYPE_BOOL,
&bool_value, sizeof(bool_value));
if (error == ZVEC_OK) {
printf(" bool_field: %s\n", bool_value ? "true" : "false");
}
// Int32 field
int32_t int32_value;
error =
zvec_doc_get_field_value_basic(doc, "int32_field", ZVEC_DATA_TYPE_INT32,
&int32_value, sizeof(int32_value));
if (error == ZVEC_OK) {
printf(" int32_field: %d\n", int32_value);
}
// Int64 field
int64_t int64_value;
error =
zvec_doc_get_field_value_basic(doc, "int64_field", ZVEC_DATA_TYPE_INT64,
&int64_value, sizeof(int64_value));
if (error == ZVEC_OK) {
printf(" int64_field: %lld\n", (long long)int64_value);
}
// Float field
float float_value;
error =
zvec_doc_get_field_value_basic(doc, "float_field", ZVEC_DATA_TYPE_FLOAT,
&float_value, sizeof(float_value));
if (error == ZVEC_OK) {
printf(" float_field: %.6f\n", float_value);
}
// Double field
double double_value;
error =
zvec_doc_get_field_value_basic(doc, "double_field", ZVEC_DATA_TYPE_DOUBLE,
&double_value, sizeof(double_value));
if (error == ZVEC_OK) {
printf(" double_field: %.6f\n", double_value);
}
// Print vector fields (using copy function for complex types)
printf("\nVector Fields:\n");
// FP32 vector (3D)
void *fp32_vector = NULL;
size_t fp32_size = 0;
error = zvec_doc_get_field_value_copy(
doc, "vector_fp32", ZVEC_DATA_TYPE_VECTOR_FP32, &fp32_vector, &fp32_size);
if (error == ZVEC_OK && fp32_vector) {
const float *vec = (const float *)fp32_vector;
size_t dim = fp32_size / sizeof(float);
printf(" vector_fp32 (%zuD): [", dim);
for (size_t i = 0; i < dim && i < 10; i++) { // Limit to first 10 elements
printf("%.3f", vec[i]);
if (i < dim - 1 && i < 9) printf(", ");
}
if (dim > 10) printf(", ...");
printf("]\n");
zvec_free(fp32_vector); // Free the allocated memory
}
// Large vector (16D)
void *large_vector = NULL;
size_t large_size = 0;
error = zvec_doc_get_field_value_copy(doc, "large_vector",
ZVEC_DATA_TYPE_VECTOR_FP32,
&large_vector, &large_size);
if (error == ZVEC_OK && large_vector) {
const float *vec = (const float *)large_vector;
size_t dim = large_size / sizeof(float);
printf(" large_vector (%zuD): [", dim);
for (size_t i = 0; i < dim && i < 10; i++) { // Limit to first 10 elements
printf("%.3f", vec[i]);
if (i < dim - 1 && i < 9) printf(", ");
}
if (dim > 10) printf(", ...");
printf("]\n");
zvec_free(large_vector); // Free the allocated memory
}
printf("==================\n\n");
}
/**
* @brief Document creation, manipulation, and query example
*/
int main() {
printf("=== ZVec Document Example ===\n\n");
zvec_error_code_t error;
// 1. Create collection schema for document testing
zvec_collection_schema_t *schema =
zvec_collection_schema_create("doc_example_collection");
if (!schema) {
fprintf(stderr, "Failed to create collection schema\n");
return -1;
}
printf("✓ Collection schema created\n");
// 2. Create index parameters
zvec_index_params_t *invert_params =
zvec_index_params_create(ZVEC_INDEX_TYPE_INVERT);
if (!invert_params) {
fprintf(stderr, "Failed to create invert index parameters\n");
zvec_collection_schema_destroy(schema);
return -1;
}
zvec_index_params_set_invert_params(invert_params, true, false);
zvec_index_params_t *hnsw_params =
zvec_index_params_create(ZVEC_INDEX_TYPE_HNSW);
if (!hnsw_params) {
fprintf(stderr, "Failed to create HNSW index parameters\n");
zvec_index_params_destroy(invert_params);
zvec_collection_schema_destroy(schema);
return -1;
}
zvec_index_params_set_metric_type(hnsw_params, ZVEC_METRIC_TYPE_L2);
zvec_index_params_set_hnsw_params(hnsw_params, 16, 200);
// 3. Create fields for all data types
printf("Creating fields for all data types...\n");
// Id field with inverted index
zvec_field_schema_t *id_field =
zvec_field_schema_create("id", ZVEC_DATA_TYPE_STRING, false, 0);
if (id_field) {
zvec_field_schema_set_index_params(id_field, invert_params);
error = zvec_collection_schema_add_field(schema, id_field);
if (handle_error(error, "adding ID field") == ZVEC_OK) {
printf("✓ ID field with inverted index added\n");
}
}
// Scalar fields
zvec_field_schema_t *string_field =
zvec_field_schema_create("string_field", ZVEC_DATA_TYPE_STRING, true, 0);
zvec_field_schema_t *bool_field =
zvec_field_schema_create("bool_field", ZVEC_DATA_TYPE_BOOL, true, 0);
zvec_field_schema_t *int32_field =
zvec_field_schema_create("int32_field", ZVEC_DATA_TYPE_INT32, true, 0);
zvec_field_schema_t *int64_field =
zvec_field_schema_create("int64_field", ZVEC_DATA_TYPE_INT64, true, 0);
zvec_field_schema_t *float_field =
zvec_field_schema_create("float_field", ZVEC_DATA_TYPE_FLOAT, true, 0);
zvec_field_schema_t *double_field =
zvec_field_schema_create("double_field", ZVEC_DATA_TYPE_DOUBLE, true, 0);
if (string_field) zvec_collection_schema_add_field(schema, string_field);
if (bool_field) zvec_collection_schema_add_field(schema, bool_field);
if (int32_field) zvec_collection_schema_add_field(schema, int32_field);
if (int64_field) zvec_collection_schema_add_field(schema, int64_field);
if (float_field) zvec_collection_schema_add_field(schema, float_field);
if (double_field) zvec_collection_schema_add_field(schema, double_field);
// Vector fields
zvec_field_schema_t *vector_fp32_field = zvec_field_schema_create(
"vector_fp32", ZVEC_DATA_TYPE_VECTOR_FP32, false, 3);
zvec_field_schema_t *large_vector_field = zvec_field_schema_create(
"large_vector", ZVEC_DATA_TYPE_VECTOR_FP32, false, 16);
if (vector_fp32_field) {
zvec_field_schema_set_index_params(vector_fp32_field, hnsw_params);
error = zvec_collection_schema_add_field(schema, vector_fp32_field);
if (handle_error(error, "adding vector FP32 field") == ZVEC_OK) {
printf("✓ Vector FP32 field with HNSW index added\n");
}
}
if (large_vector_field) {
zvec_field_schema_set_index_params(large_vector_field, hnsw_params);
error = zvec_collection_schema_add_field(schema, large_vector_field);
if (handle_error(error, "adding large vector field") == ZVEC_OK) {
printf("✓ Large vector field with HNSW index added\n");
}
}
// 4. Create collection
zvec_collection_options_t *options = zvec_collection_options_create();
if (!options) {
fprintf(stderr, "Failed to create collection options\n");
zvec_collection_schema_destroy(schema);
return -1;
}
zvec_collection_t *collection = NULL;
error = zvec_collection_create_and_open("./doc_example_collection", schema,
options, &collection);
zvec_collection_options_destroy(options);
if (handle_error(error, "creating collection") != ZVEC_OK) {
zvec_collection_schema_destroy(schema);
return -1;
}
printf("✓ Collection created successfully\n");
// 5. Create and insert multiple test documents
printf("Creating and inserting test documents...\n");
#define DOC_COUNT 5
// Use dynamic allocation for MSVC compatibility (no VLA support)
zvec_doc_t **test_docs =
(zvec_doc_t **)malloc(DOC_COUNT * sizeof(zvec_doc_t *));
if (!test_docs) {
fprintf(stderr, "Failed to allocate test documents\n");
goto cleanup;
}
for (int i = 0; i < DOC_COUNT; i++) {
test_docs[i] = create_full_type_test_doc(i);
if (!test_docs[i]) {
fprintf(stderr, "Failed to create document %d\n", i);
// Cleanup
for (int j = 0; j < i; j++) {
zvec_doc_destroy(test_docs[j]);
}
goto cleanup;
}
printf("✓ Created document %d with PK: %s\n", i,
zvec_doc_get_pk_pointer(test_docs[i]));
}
// Print all documents before insertion
printf("\nDocuments before insertion:\n");
for (int i = 0; i < DOC_COUNT; i++) {
print_doc(test_docs[i], i);
}
// Insert documents
size_t success_count = 0, error_count = 0;
error = zvec_collection_insert(collection, (const zvec_doc_t **)test_docs,
DOC_COUNT, &success_count, &error_count);
if (handle_error(error, "inserting documents") == ZVEC_OK) {
printf("✓ Documents inserted - Success: %zu, Failed: %zu\n", success_count,
error_count);
}
// 6. Flush collection
error = zvec_collection_flush(collection);
if (handle_error(error, "flushing collection") != ZVEC_OK) {
printf("Warning: Collection flush failed\n");
} else {
printf("✓ Collection flushed successfully\n");
}
// Use the first document's vector for querying
float query_vector[] = {0.0f, 0.0f, 0.0f};
zvec_vector_query_t *query = zvec_vector_query_create();
if (!query) {
fprintf(stderr, "Failed to create vector query\n");
zvec_collection_destroy(collection);
zvec_collection_schema_destroy(schema);
return -1;
}
zvec_vector_query_set_field_name(query, "vector_fp32");
zvec_vector_query_set_query_vector(query, query_vector, 3 * sizeof(float));
zvec_vector_query_set_topk(query, 5);
zvec_vector_query_set_filter(query, "");
zvec_vector_query_set_include_vector(query, true);
zvec_vector_query_set_include_doc_id(query, true);
zvec_doc_t **query_results = NULL;
size_t result_count = 0;
error = zvec_collection_query(collection, (const zvec_vector_query_t *)query,
&query_results, &result_count);
if (handle_error(error, "querying documents") != ZVEC_OK) {
query_results = NULL;
result_count = 0;
}
printf("Query returned %zu results\n", result_count);
// Print query results
printf("\nQuery Results:\n");
for (size_t i = 0; i < result_count; i++) {
print_doc(query_results[i], i);
}
// Compare query results
for (size_t i = 0; i < result_count && i < DOC_COUNT; i++) {
const char *result_pk = zvec_doc_get_pk_pointer(query_results[i]);
printf("Comparing query result[%zu]: %s\n", i, result_pk);
// Find matching original document
bool found = false;
for (int j = 0; j < DOC_COUNT; j++) {
const char *original_pk = zvec_doc_get_pk_pointer(test_docs[j]);
if (strcmp(result_pk, original_pk) == 0) {
if (compare_documents(test_docs[j], query_results[i])) {
printf("✓ Query result %s matches original document\n", result_pk);
} else {
printf("✗ Query result %s does not match original document\n",
result_pk);
}
found = true;
break;
}
}
if (!found) {
printf("⚠ Original document not found for: %s\n", result_pk);
}
}
// 7. Filter query test
printf("\n=== Filter Query Test ===\n");
// Create filtered query
zvec_vector_query_set_filter(query, "string_field = 'string_field_0'");
zvec_doc_t **filtered_results = NULL;
size_t filtered_count = 0;
error = zvec_collection_query(collection, (const zvec_vector_query_t *)query,
&filtered_results, &filtered_count);
if (handle_error(error, "filtered querying") == ZVEC_OK) {
printf("Filtered query returned %zu results\n", filtered_count);
// Verify filter results
bool filter_correct = true;
for (size_t i = 0; i < filtered_count; i++) {
// Note: Field value access may require different API
// For now, we'll just check that we got results
const char *pk = zvec_doc_get_pk_pointer(filtered_results[i]);
if (strstr(pk, "doc_") == NULL) {
filter_correct = false;
break;
}
}
if (filter_correct) {
printf("✓ Filter query results are correct\n");
} else {
printf("✗ Filter query results are incorrect\n");
}
if (filtered_results) {
zvec_docs_free(filtered_results, filtered_count);
}
}
// 8. Cleanup query results
if (query_results) {
zvec_docs_free(query_results, result_count);
}
// 9. Cleanup documents
for (int i = 0; i < DOC_COUNT; i++) {
zvec_doc_destroy(test_docs[i]);
}
free(test_docs); // Free the array itself
// 10. Final cleanup
cleanup:
zvec_collection_destroy(collection);
zvec_collection_schema_destroy(schema);
printf("✓ Document example completed\n");
return 0;
}
+312
View File
@@ -0,0 +1,312 @@
// Copyright 2025-present the zvec project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "zvec/c_api.h"
/**
* @brief Print error message and return error code
*/
static zvec_error_code_t handle_error(zvec_error_code_t error,
const char *context) {
if (error != ZVEC_OK) {
char *error_msg = NULL;
zvec_get_last_error(&error_msg);
fprintf(stderr, "Error in %s: %d - %s\n", context, error,
error_msg ? error_msg : "Unknown error");
zvec_free(error_msg);
}
return error;
}
/**
* @brief Field schema creation and management example
*/
int main() {
printf("=== ZVec Field Schema Example ===\n\n");
zvec_error_code_t error;
// 1. Create collection schema
zvec_collection_schema_t *schema =
zvec_collection_schema_create("field_example_collection");
if (!schema) {
fprintf(stderr, "Failed to create collection schema\n");
return -1;
}
printf("✓ Collection schema created successfully\n");
// 2. Create different types of index parameters
zvec_index_params_t *invert_params =
zvec_index_params_create(ZVEC_INDEX_TYPE_INVERT);
if (!invert_params) {
fprintf(stderr, "Failed to create invert index parameters\n");
zvec_collection_schema_destroy(schema);
return -1;
}
zvec_index_params_set_invert_params(invert_params, true, false);
zvec_index_params_t *hnsw_params =
zvec_index_params_create(ZVEC_INDEX_TYPE_HNSW);
if (!hnsw_params) {
fprintf(stderr, "Failed to create HNSW index parameters\n");
zvec_index_params_destroy(invert_params);
zvec_collection_schema_destroy(schema);
return -1;
}
zvec_index_params_set_metric_type(hnsw_params, ZVEC_METRIC_TYPE_COSINE);
zvec_index_params_set_hnsw_params(hnsw_params, 16, 200);
zvec_index_params_t *flat_params =
zvec_index_params_create(ZVEC_INDEX_TYPE_FLAT);
if (!flat_params) {
fprintf(stderr, "Failed to create Flat index parameters\n");
zvec_index_params_destroy(invert_params);
zvec_index_params_destroy(hnsw_params);
zvec_collection_schema_destroy(schema);
return -1;
}
zvec_index_params_set_metric_type(flat_params, ZVEC_METRIC_TYPE_L2);
if (!invert_params || !hnsw_params || !flat_params) {
fprintf(stderr, "Failed to create index parameters\n");
zvec_index_params_destroy(invert_params);
zvec_index_params_destroy(hnsw_params);
zvec_index_params_destroy(flat_params);
zvec_collection_schema_destroy(schema);
return -1;
}
// 3. Create scalar fields with different data types
printf("Creating scalar fields...\n");
// String field with inverted index
zvec_field_schema_t *name_field =
zvec_field_schema_create("name", ZVEC_DATA_TYPE_STRING, false, 0);
if (name_field) {
zvec_field_schema_set_index_params(name_field, invert_params);
error = zvec_collection_schema_add_field(schema, name_field);
if (handle_error(error, "adding name field") == ZVEC_OK) {
printf("✓ String field 'name' with inverted index added\n");
}
}
// Integer field
zvec_field_schema_t *age_field =
zvec_field_schema_create("age", ZVEC_DATA_TYPE_INT32, true, 0);
if (age_field) {
error = zvec_collection_schema_add_field(schema, age_field);
if (handle_error(error, "adding age field") == ZVEC_OK) {
printf("✓ Integer field 'age' added\n");
}
}
// Float field
zvec_field_schema_t *score_field =
zvec_field_schema_create("score", ZVEC_DATA_TYPE_FLOAT, true, 0);
if (score_field) {
error = zvec_collection_schema_add_field(schema, score_field);
if (handle_error(error, "adding score field") == ZVEC_OK) {
printf("✓ Float field 'score' added\n");
}
}
// Boolean field
zvec_field_schema_t *active_field =
zvec_field_schema_create("active", ZVEC_DATA_TYPE_BOOL, false, 0);
if (active_field) {
error = zvec_collection_schema_add_field(schema, active_field);
if (handle_error(error, "adding active field") == ZVEC_OK) {
printf("✓ Boolean field 'active' added\n");
}
}
// 4. Create vector fields with different dimensions and indexes
printf("Creating vector fields...\n");
// Small dimension vector with HNSW index
zvec_field_schema_t *small_vector_field = zvec_field_schema_create(
"small_vector", ZVEC_DATA_TYPE_VECTOR_FP32, false, 32);
if (small_vector_field) {
zvec_field_schema_set_index_params(small_vector_field, hnsw_params);
error = zvec_collection_schema_add_field(schema, small_vector_field);
if (handle_error(error, "adding small vector field") == ZVEC_OK) {
printf(
"✓ Small vector field 'small_vector' (32D) with HNSW index added\n");
}
}
// Medium dimension vector with Flat index
zvec_field_schema_t *medium_vector_field = zvec_field_schema_create(
"medium_vector", ZVEC_DATA_TYPE_VECTOR_FP32, false, 128);
if (medium_vector_field) {
zvec_field_schema_set_index_params(medium_vector_field, flat_params);
error = zvec_collection_schema_add_field(schema, medium_vector_field);
if (handle_error(error, "adding medium vector field") == ZVEC_OK) {
printf(
"✓ Medium vector field 'medium_vector' (128D) with Flat index "
"added\n");
}
}
// Large dimension vector with HNSW index
zvec_field_schema_t *large_vector_field = zvec_field_schema_create(
"large_vector", ZVEC_DATA_TYPE_VECTOR_FP32, false, 512);
if (large_vector_field) {
zvec_field_schema_set_index_params(large_vector_field, hnsw_params);
error = zvec_collection_schema_add_field(schema, large_vector_field);
if (handle_error(error, "adding large vector field") == ZVEC_OK) {
printf(
"✓ Large vector field 'large_vector' (512D) with HNSW index added\n");
}
}
// 5. Create collection with the schema
zvec_collection_options_t *options = zvec_collection_options_create();
if (!options) {
fprintf(stderr, "Failed to create collection options\n");
zvec_collection_schema_destroy(schema);
return -1;
}
zvec_collection_t *collection = NULL;
error = zvec_collection_create_and_open("./field_example_collection", schema,
options, &collection);
zvec_collection_options_destroy(options);
if (handle_error(error, "creating collection") != ZVEC_OK) {
zvec_collection_schema_destroy(schema);
return -1;
}
printf("✓ Collection created successfully\n");
// 6. Create test documents with various field types
printf("Creating test documents...\n");
zvec_doc_t *doc1 = zvec_doc_create();
zvec_doc_t *doc2 = zvec_doc_create();
if (!doc1 || !doc2) {
fprintf(stderr, "Failed to create documents\n");
goto cleanup;
}
// Document 1
zvec_doc_set_pk(doc1, "user1");
zvec_doc_add_field_by_value(doc1, "name", ZVEC_DATA_TYPE_STRING,
"Alice Johnson", strlen("Alice Johnson"));
int32_t age1 = 28;
zvec_doc_add_field_by_value(doc1, "age", ZVEC_DATA_TYPE_INT32, &age1,
sizeof(age1));
float score1 = 87.5f;
zvec_doc_add_field_by_value(doc1, "score", ZVEC_DATA_TYPE_FLOAT, &score1,
sizeof(score1));
bool active1 = true;
zvec_doc_add_field_by_value(doc1, "active", ZVEC_DATA_TYPE_BOOL, &active1,
sizeof(active1));
// Add vector data
float small_vec1[32];
float medium_vec1[128];
float large_vec1[512];
for (int i = 0; i < 32; i++) small_vec1[i] = (float)i / 32.0f;
for (int i = 0; i < 128; i++) medium_vec1[i] = (float)i / 128.0f;
for (int i = 0; i < 512; i++) large_vec1[i] = (float)i / 512.0f;
zvec_doc_add_field_by_value(doc1, "small_vector", ZVEC_DATA_TYPE_VECTOR_FP32,
small_vec1, 32 * sizeof(float));
zvec_doc_add_field_by_value(doc1, "medium_vector", ZVEC_DATA_TYPE_VECTOR_FP32,
medium_vec1, 128 * sizeof(float));
zvec_doc_add_field_by_value(doc1, "large_vector", ZVEC_DATA_TYPE_VECTOR_FP32,
large_vec1, 512 * sizeof(float));
// Document 2
zvec_doc_set_pk(doc2, "user2");
zvec_doc_add_field_by_value(doc2, "name", ZVEC_DATA_TYPE_STRING, "Bob Smith",
strlen("Bob Smith"));
int32_t age2 = 35;
zvec_doc_add_field_by_value(doc2, "age", ZVEC_DATA_TYPE_INT32, &age2,
sizeof(age2));
float score2 = 92.0f;
zvec_doc_add_field_by_value(doc2, "score", ZVEC_DATA_TYPE_FLOAT, &score2,
sizeof(score2));
bool active2 = false;
zvec_doc_add_field_by_value(doc2, "active", ZVEC_DATA_TYPE_BOOL, &active2,
sizeof(active2));
// Add vector data
float small_vec2[32];
float medium_vec2[128];
float large_vec2[512];
for (int i = 0; i < 32; i++) small_vec2[i] = (float)(32 - i) / 32.0f;
for (int i = 0; i < 128; i++) medium_vec2[i] = (float)(128 - i) / 128.0f;
for (int i = 0; i < 512; i++) large_vec2[i] = (float)(512 - i) / 512.0f;
zvec_doc_add_field_by_value(doc2, "small_vector", ZVEC_DATA_TYPE_VECTOR_FP32,
small_vec2, 32 * sizeof(float));
zvec_doc_add_field_by_value(doc2, "medium_vector", ZVEC_DATA_TYPE_VECTOR_FP32,
medium_vec2, 128 * sizeof(float));
zvec_doc_add_field_by_value(doc2, "large_vector", ZVEC_DATA_TYPE_VECTOR_FP32,
large_vec2, 512 * sizeof(float));
// 7. Insert documents
zvec_doc_t *docs[] = {doc1, doc2};
size_t success_count = 0, error_count = 0;
error = zvec_collection_insert(collection, (const zvec_doc_t **)docs, 2,
&success_count, &error_count);
if (handle_error(error, "inserting documents") == ZVEC_OK) {
printf("✓ Documents inserted - Success: %zu, Failed: %zu\n", success_count,
error_count);
}
// 8. Flush and test queries
zvec_collection_flush(collection);
printf("✓ Collection flushed\n");
// Test vector query on medium vector field
zvec_vector_query_t *query = zvec_vector_query_create();
if (!query) {
fprintf(stderr, "Failed to create vector query\n");
goto cleanup;
}
zvec_vector_query_set_field_name(query, "medium_vector");
zvec_vector_query_set_query_vector(query, medium_vec1, 128 * sizeof(float));
zvec_vector_query_set_topk(query, 2);
zvec_vector_query_set_filter(query, "");
zvec_vector_query_set_include_vector(query, false);
zvec_vector_query_set_include_doc_id(query, true);
zvec_doc_t **results = NULL;
size_t result_count = 0;
error = zvec_collection_query(collection, (const zvec_vector_query_t *)query,
&results, &result_count);
if (error == ZVEC_OK) {
printf("✓ Vector query successful - Found %zu results\n", result_count);
zvec_docs_free(results, result_count);
}
zvec_vector_query_destroy(query);
// 9. Cleanup
cleanup:
if (doc1) zvec_doc_destroy(doc1);
if (doc2) zvec_doc_destroy(doc2);
zvec_collection_destroy(collection);
zvec_collection_schema_destroy(schema);
printf("✓ Field schema example completed\n");
return 0;
}
+398
View File
@@ -0,0 +1,398 @@
// Copyright 2025-present the zvec project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "zvec/c_api.h"
/**
* @brief Print error message and return error code
*/
static zvec_error_code_t handle_error(zvec_error_code_t error,
const char *context) {
if (error != ZVEC_OK) {
char *error_msg = NULL;
zvec_get_last_error(&error_msg);
fprintf(stderr, "Error in %s: %d - %s\n", context, error,
error_msg ? error_msg : "Unknown error");
zvec_free(error_msg);
}
return error;
}
/**
* @brief Index creation and management example
*/
int main() {
printf("=== ZVec Index Example ===\n\n");
zvec_error_code_t error;
// 1. Create collection schema
zvec_collection_schema_t *schema =
zvec_collection_schema_create("index_example_collection");
if (!schema) {
fprintf(stderr, "Failed to create collection schema\n");
return -1;
}
printf("✓ Collection schema created successfully\n");
// 2. Create different index parameter configurations
printf("Creating index parameters...\n");
// Inverted index parameters
zvec_index_params_t *invert_params_standard =
zvec_index_params_create(ZVEC_INDEX_TYPE_INVERT);
if (!invert_params_standard) {
fprintf(stderr, "Failed to create invert index parameters (standard)\n");
zvec_collection_schema_destroy(schema);
return -1;
}
zvec_index_params_set_invert_params(invert_params_standard, true, false);
zvec_index_params_t *invert_params_extended =
zvec_index_params_create(ZVEC_INDEX_TYPE_INVERT);
if (!invert_params_extended) {
fprintf(stderr, "Failed to create invert index parameters (extended)\n");
zvec_index_params_destroy(invert_params_standard);
zvec_collection_schema_destroy(schema);
return -1;
}
zvec_index_params_set_invert_params(invert_params_extended, true, true);
// HNSW index parameters with different configurations
zvec_index_params_t *hnsw_params_fast =
zvec_index_params_create(ZVEC_INDEX_TYPE_HNSW);
if (!hnsw_params_fast) {
fprintf(stderr, "Failed to create HNSW index parameters (fast)\n");
zvec_index_params_destroy(invert_params_standard);
zvec_index_params_destroy(invert_params_extended);
zvec_collection_schema_destroy(schema);
return -1;
}
zvec_index_params_set_metric_type(hnsw_params_fast, ZVEC_METRIC_TYPE_L2);
zvec_index_params_set_hnsw_params(hnsw_params_fast, 16, 100);
// Demonstrate INT8 quantization with random rotation preprocessing
// (enable_rotate rotates vectors before INT8 quantization to reduce error)
zvec_index_params_set_quantize_type(hnsw_params_fast,
ZVEC_QUANTIZE_TYPE_INT8);
zvec_index_params_set_quantizer_enable_rotate(hnsw_params_fast, true);
zvec_index_params_t *hnsw_params_balanced =
zvec_index_params_create(ZVEC_INDEX_TYPE_HNSW);
if (!hnsw_params_balanced) {
fprintf(stderr, "Failed to create HNSW index parameters (balanced)\n");
zvec_index_params_destroy(invert_params_standard);
zvec_index_params_destroy(invert_params_extended);
zvec_index_params_destroy(hnsw_params_fast);
zvec_collection_schema_destroy(schema);
return -1;
}
zvec_index_params_set_metric_type(hnsw_params_balanced,
ZVEC_METRIC_TYPE_COSINE);
zvec_index_params_set_hnsw_params(hnsw_params_balanced, 32, 200);
zvec_index_params_t *hnsw_params_accurate =
zvec_index_params_create(ZVEC_INDEX_TYPE_HNSW);
if (!hnsw_params_accurate) {
fprintf(stderr, "Failed to create HNSW index parameters (accurate)\n");
zvec_index_params_destroy(invert_params_standard);
zvec_index_params_destroy(invert_params_extended);
zvec_index_params_destroy(hnsw_params_fast);
zvec_index_params_destroy(hnsw_params_balanced);
zvec_collection_schema_destroy(schema);
return -1;
}
zvec_index_params_set_metric_type(hnsw_params_accurate, ZVEC_METRIC_TYPE_IP);
zvec_index_params_set_hnsw_params(hnsw_params_accurate, 64, 400);
// Flat index parameters
zvec_index_params_t *flat_params_l2 =
zvec_index_params_create(ZVEC_INDEX_TYPE_FLAT);
if (!flat_params_l2) {
fprintf(stderr, "Failed to create Flat index parameters (L2)\n");
zvec_index_params_destroy(invert_params_standard);
zvec_index_params_destroy(invert_params_extended);
zvec_index_params_destroy(hnsw_params_fast);
zvec_index_params_destroy(hnsw_params_balanced);
zvec_index_params_destroy(hnsw_params_accurate);
zvec_collection_schema_destroy(schema);
return -1;
}
zvec_index_params_set_metric_type(flat_params_l2, ZVEC_METRIC_TYPE_L2);
zvec_index_params_t *flat_params_cosine =
zvec_index_params_create(ZVEC_INDEX_TYPE_FLAT);
if (!flat_params_cosine) {
fprintf(stderr, "Failed to create Flat index parameters (cosine)\n");
zvec_index_params_destroy(invert_params_standard);
zvec_index_params_destroy(invert_params_extended);
zvec_index_params_destroy(hnsw_params_fast);
zvec_index_params_destroy(hnsw_params_balanced);
zvec_index_params_destroy(hnsw_params_accurate);
zvec_index_params_destroy(flat_params_l2);
zvec_collection_schema_destroy(schema);
return -1;
}
zvec_index_params_set_metric_type(flat_params_cosine,
ZVEC_METRIC_TYPE_COSINE);
// 3. Create fields with different index types
printf("Creating fields with various index types...\n");
// Fields with inverted indexes
zvec_field_schema_t *id_field =
zvec_field_schema_create("id", ZVEC_DATA_TYPE_STRING, false, 0);
if (id_field) {
zvec_field_schema_set_index_params(id_field, invert_params_standard);
error = zvec_collection_schema_add_field(schema, id_field);
if (handle_error(error, "adding ID field") == ZVEC_OK) {
printf("✓ ID field with standard inverted index added\n");
}
}
zvec_field_schema_t *category_field =
zvec_field_schema_create("category", ZVEC_DATA_TYPE_STRING, true, 0);
if (category_field) {
zvec_field_schema_set_index_params(category_field, invert_params_extended);
error = zvec_collection_schema_add_field(schema, category_field);
if (handle_error(error, "adding category field") == ZVEC_OK) {
printf("✓ Category field with extended inverted index added\n");
}
}
// Vector fields with HNSW indexes (different configurations)
zvec_field_schema_t *fast_search_field = zvec_field_schema_create(
"fast_vector", ZVEC_DATA_TYPE_VECTOR_FP32, false, 64);
if (fast_search_field) {
zvec_field_schema_set_index_params(fast_search_field, hnsw_params_fast);
error = zvec_collection_schema_add_field(schema, fast_search_field);
if (handle_error(error, "adding fast search field") == ZVEC_OK) {
printf("✓ Fast search vector field (64D) with HNSW index added\n");
}
}
zvec_field_schema_t *balanced_field = zvec_field_schema_create(
"balanced_vector", ZVEC_DATA_TYPE_VECTOR_FP32, false, 128);
if (balanced_field) {
zvec_field_schema_set_index_params(balanced_field, hnsw_params_balanced);
error = zvec_collection_schema_add_field(schema, balanced_field);
if (handle_error(error, "adding balanced field") == ZVEC_OK) {
printf("✓ Balanced vector field (128D) with HNSW index added\n");
}
}
zvec_field_schema_t *accurate_field = zvec_field_schema_create(
"accurate_vector", ZVEC_DATA_TYPE_VECTOR_FP32, false, 256);
if (accurate_field) {
zvec_field_schema_set_index_params(accurate_field, hnsw_params_accurate);
error = zvec_collection_schema_add_field(schema, accurate_field);
if (handle_error(error, "adding accurate field") == ZVEC_OK) {
printf("✓ Accurate vector field (256D) with HNSW index added\n");
}
}
// Vector field with Flat index
zvec_field_schema_t *exact_field = zvec_field_schema_create(
"exact_vector", ZVEC_DATA_TYPE_VECTOR_FP32, false, 32);
if (exact_field) {
zvec_field_schema_set_index_params(exact_field, flat_params_l2);
error = zvec_collection_schema_add_field(schema, exact_field);
if (handle_error(error, "adding exact field") == ZVEC_OK) {
printf("✓ Exact search vector field (32D) with Flat index added\n");
}
}
// 4. Create collection
zvec_collection_options_t *options = zvec_collection_options_create();
if (!options) {
fprintf(stderr, "Failed to create collection options\n");
zvec_collection_schema_destroy(schema);
return -1;
}
zvec_collection_t *collection = NULL;
error = zvec_collection_create_and_open("./index_example_collection", schema,
options, &collection);
zvec_collection_options_destroy(options);
if (handle_error(error, "creating collection") != ZVEC_OK) {
zvec_collection_schema_destroy(schema);
return -1;
}
printf("✓ Collection created successfully\n");
// 5. Create test data
printf("Creating test documents...\n");
zvec_doc_t *docs[3];
for (int i = 0; i < 3; i++) {
docs[i] = zvec_doc_create();
if (!docs[i]) {
fprintf(stderr, "Failed to create document %d\n", i);
// Cleanup
for (int j = 0; j < i; j++) {
zvec_doc_destroy(docs[j]);
}
goto cleanup;
}
}
// Prepare vector data
float fast_vec[3][64];
float balanced_vec[3][128];
float accurate_vec[3][256];
float exact_vec[3][32];
// Generate different vector patterns for testing
for (int doc_idx = 0; doc_idx < 3; doc_idx++) {
for (int i = 0; i < 64; i++) {
fast_vec[doc_idx][i] = (float)(doc_idx * 64 + i) / (64.0f * 3.0f);
}
for (int i = 0; i < 128; i++) {
balanced_vec[doc_idx][i] = (float)(doc_idx * 128 + i) / (128.0f * 3.0f);
}
for (int i = 0; i < 256; i++) {
accurate_vec[doc_idx][i] = (float)(doc_idx * 256 + i) / (256.0f * 3.0f);
}
for (int i = 0; i < 32; i++) {
exact_vec[doc_idx][i] = (float)(doc_idx * 32 + i) / (32.0f * 3.0f);
}
}
// Populate documents
for (int i = 0; i < 3; i++) {
char pk[16];
snprintf(pk, sizeof(pk), "doc%d", i + 1);
zvec_doc_set_pk(docs[i], pk);
char id_val[16];
snprintf(id_val, sizeof(id_val), "ID_%d", i + 1);
zvec_doc_add_field_by_value(docs[i], "id", ZVEC_DATA_TYPE_STRING, id_val,
strlen(id_val));
char category_val[16];
snprintf(category_val, sizeof(category_val), "cat_%d", (i % 2) + 1);
zvec_doc_add_field_by_value(docs[i], "category", ZVEC_DATA_TYPE_STRING,
category_val, strlen(category_val));
zvec_doc_add_field_by_value(docs[i], "fast_vector",
ZVEC_DATA_TYPE_VECTOR_FP32, fast_vec[i],
64 * sizeof(float));
zvec_doc_add_field_by_value(docs[i], "balanced_vector",
ZVEC_DATA_TYPE_VECTOR_FP32, balanced_vec[i],
128 * sizeof(float));
zvec_doc_add_field_by_value(docs[i], "accurate_vector",
ZVEC_DATA_TYPE_VECTOR_FP32, accurate_vec[i],
256 * sizeof(float));
zvec_doc_add_field_by_value(docs[i], "exact_vector",
ZVEC_DATA_TYPE_VECTOR_FP32, exact_vec[i],
32 * sizeof(float));
}
// 6. Insert documents
size_t success_count = 0, error_count = 0;
error = zvec_collection_insert(collection, (const zvec_doc_t **)docs, 3,
&success_count, &error_count);
if (handle_error(error, "inserting documents") == ZVEC_OK) {
printf("✓ Documents inserted - Success: %zu, Failed: %zu\n", success_count,
error_count);
}
// Cleanup documents
for (int i = 0; i < 3; i++) {
zvec_doc_destroy(docs[i]);
}
// 7. Flush collection to build indexes
error = zvec_collection_flush(collection);
if (handle_error(error, "flushing collection") == ZVEC_OK) {
printf("✓ Collection flushed - indexes built\n");
}
// 8. Test different query types
printf("Testing various index queries...\n");
// Test HNSW query (balanced)
zvec_vector_query_t *hnsw_query = zvec_vector_query_create();
if (!hnsw_query) {
fprintf(stderr, "Failed to create HNSW query\n");
goto cleanup;
}
zvec_vector_query_set_field_name(hnsw_query, "balanced_vector");
zvec_vector_query_set_query_vector(hnsw_query, balanced_vec[0],
128 * sizeof(float));
zvec_vector_query_set_topk(hnsw_query, 2);
zvec_vector_query_set_filter(hnsw_query, "");
zvec_vector_query_set_include_vector(hnsw_query, false);
zvec_vector_query_set_include_doc_id(hnsw_query, true);
zvec_doc_t **hnsw_results = NULL;
size_t hnsw_result_count = 0;
error =
zvec_collection_query(collection, (const zvec_vector_query_t *)hnsw_query,
&hnsw_results, &hnsw_result_count);
if (error == ZVEC_OK) {
printf("✓ HNSW query successful - Found %zu results\n", hnsw_result_count);
zvec_docs_free(hnsw_results, hnsw_result_count);
}
zvec_vector_query_destroy(hnsw_query);
// Test Flat query (exact)
zvec_vector_query_t *flat_query = zvec_vector_query_create();
if (!flat_query) {
fprintf(stderr, "Failed to create Flat query\n");
goto cleanup;
}
zvec_vector_query_set_field_name(flat_query, "exact_vector");
zvec_vector_query_set_query_vector(flat_query, exact_vec[0],
32 * sizeof(float));
zvec_vector_query_set_topk(flat_query, 2);
zvec_vector_query_set_filter(flat_query, "");
zvec_vector_query_set_include_vector(flat_query, false);
zvec_vector_query_set_include_doc_id(flat_query, true);
zvec_doc_t **flat_results = NULL;
size_t flat_result_count = 0;
error =
zvec_collection_query(collection, (const zvec_vector_query_t *)flat_query,
&flat_results, &flat_result_count);
if (error == ZVEC_OK) {
printf("✓ Flat (exact) query successful - Found %zu results\n",
flat_result_count);
zvec_docs_free(flat_results, flat_result_count);
}
zvec_vector_query_destroy(flat_query);
// 9. Performance comparison information
printf("\nIndex Performance Characteristics:\n");
printf("- Inverted Index: Fast text search, supports filtering\n");
printf(
"- HNSW Index: Approximate nearest neighbor search, good balance of "
"speed/accuracy\n");
printf("- Flat Index: Exact search, slower but 100%% accurate\n");
printf(
"- Trade-off: Speed vs Accuracy - choose based on your requirements\n");
// 10. Cleanup
cleanup:
zvec_collection_destroy(collection);
zvec_collection_schema_destroy(schema);
// Cleanup index parameters
printf("✓ Index example completed\n");
return 0;
}
+310
View File
@@ -0,0 +1,310 @@
// Copyright 2025-present the zvec project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "zvec/c_api.h"
/**
* @brief Print error message and return error code
*/
static zvec_error_code_t handle_error(zvec_error_code_t error,
const char *context) {
if (error != ZVEC_OK) {
char *error_msg = NULL;
zvec_get_last_error(&error_msg);
fprintf(stderr, "Error in %s: %d - %s\n", context, error,
error_msg ? error_msg : "Unknown error");
zvec_free(error_msg);
}
return error;
}
/**
* @brief Create test vector data
*/
static float *create_test_vector(size_t dimension) {
float *vector = malloc(dimension * sizeof(float));
if (!vector) {
return NULL;
}
for (size_t i = 0; i < dimension; i++) {
vector[i] = (float)rand() / RAND_MAX;
}
return vector;
}
/**
* @brief Optimized C API usage example with performance considerations
*/
int main() {
printf("=== ZVec Optimized C API Example ===\n\n");
// Get version information
const char *version = zvec_get_version();
printf("ZVec Version: %s\n\n", version ? version : "Unknown");
zvec_error_code_t error;
// 1. Create optimized collection schema
zvec_collection_schema_t *schema =
zvec_collection_schema_create("optimized_example_collection");
if (!schema) {
fprintf(stderr, "Failed to create collection schema\n");
return -1;
}
printf("✓ Collection schema created\n");
// 2. Create optimized index parameters
zvec_index_params_t *hnsw_params =
zvec_index_params_create(ZVEC_INDEX_TYPE_HNSW);
if (!hnsw_params) {
fprintf(stderr, "Failed to create HNSW index parameters\n");
zvec_collection_schema_destroy(schema);
return -1;
}
zvec_index_params_set_metric_type(hnsw_params, ZVEC_METRIC_TYPE_L2);
zvec_index_params_set_hnsw_params(hnsw_params, 32, 200);
// 3. Create fields with optimized configuration
zvec_field_schema_t *id_field =
zvec_field_schema_create("id", ZVEC_DATA_TYPE_STRING, false, 0);
zvec_field_schema_t *text_field =
zvec_field_schema_create("text", ZVEC_DATA_TYPE_STRING, true, 0);
zvec_field_schema_t *embedding_field = zvec_field_schema_create(
"embedding", ZVEC_DATA_TYPE_VECTOR_FP32, false, 128);
if (!id_field || !text_field || !embedding_field) {
fprintf(stderr, "Failed to create field schemas\n");
goto cleanup_params;
}
// Set indexes
zvec_field_schema_set_index_params(embedding_field, hnsw_params);
// Add fields to schema
error = zvec_collection_schema_add_field(schema, id_field);
if (handle_error(error, "adding ID field") != ZVEC_OK) goto cleanup_fields;
error = zvec_collection_schema_add_field(schema, text_field);
if (handle_error(error, "adding text field") != ZVEC_OK) goto cleanup_fields;
error = zvec_collection_schema_add_field(schema, embedding_field);
if (handle_error(error, "adding embedding field") != ZVEC_OK)
goto cleanup_fields;
printf("✓ Fields configured with indexes\n");
// 4. Create collection with optimized options
zvec_collection_options_t *options = zvec_collection_options_create();
if (!options) {
fprintf(stderr, "Failed to create collection options\n");
goto cleanup_fields;
}
zvec_collection_options_set_enable_mmap(
options, true); // Enable memory mapping for better performance
zvec_collection_t *collection = NULL;
error = zvec_collection_create_and_open("./optimized_example_collection",
schema, options, &collection);
zvec_collection_options_destroy(options);
if (handle_error(error, "creating collection") != ZVEC_OK) {
goto cleanup_fields;
}
printf("✓ Collection created with optimized settings\n");
// 5. Bulk insert test data
const size_t DOC_COUNT = 1000;
const size_t BATCH_SIZE = 100;
printf("Inserting %zu documents in batches of %zu...\n", DOC_COUNT,
BATCH_SIZE);
clock_t start_time = clock();
for (size_t batch_start = 0; batch_start < DOC_COUNT;
batch_start += BATCH_SIZE) {
size_t current_batch_size = (batch_start + BATCH_SIZE > DOC_COUNT)
? DOC_COUNT - batch_start
: BATCH_SIZE;
zvec_doc_t **batch_docs = malloc(current_batch_size * sizeof(zvec_doc_t *));
if (!batch_docs) {
fprintf(stderr, "Failed to allocate batch documents\n");
break;
}
// Create batch documents
for (size_t i = 0; i < current_batch_size; i++) {
batch_docs[i] = zvec_doc_create();
if (!batch_docs[i]) {
fprintf(stderr, "Failed to create document\n");
// Cleanup previous documents in batch
for (size_t j = 0; j < i; j++) {
zvec_doc_destroy(batch_docs[j]);
}
free(batch_docs);
goto cleanup_collection;
}
size_t doc_id = batch_start + i;
char pk[32];
snprintf(pk, sizeof(pk), "doc_%zu", doc_id);
zvec_doc_set_pk(batch_docs[i], pk);
// Add ID field
char id_str[32];
snprintf(id_str, sizeof(id_str), "ID_%zu", doc_id);
zvec_doc_add_field_by_value(batch_docs[i], "id", ZVEC_DATA_TYPE_STRING,
id_str, strlen(id_str));
// Add text field
char text_str[64];
snprintf(text_str, sizeof(text_str),
"Document number %zu with sample text", doc_id);
zvec_doc_add_field_by_value(batch_docs[i], "text", ZVEC_DATA_TYPE_STRING,
text_str, strlen(text_str));
// Add vector field
float *vector = create_test_vector(128);
if (vector) {
zvec_doc_add_field_by_value(batch_docs[i], "embedding",
ZVEC_DATA_TYPE_VECTOR_FP32, vector,
128 * sizeof(float));
free(vector);
}
}
// Insert batch
size_t success_count, error_count;
error = zvec_collection_insert(collection, (const zvec_doc_t **)batch_docs,
current_batch_size, &success_count,
&error_count);
if (handle_error(error, "inserting batch") != ZVEC_OK) {
// Cleanup batch documents
for (size_t i = 0; i < current_batch_size; i++) {
zvec_doc_destroy(batch_docs[i]);
}
free(batch_docs);
goto cleanup_collection;
}
printf(" Batch %zu-%zu: %zu successful, %zu failed\n", batch_start,
batch_start + current_batch_size - 1, success_count, error_count);
// Cleanup batch documents
for (size_t i = 0; i < current_batch_size; i++) {
zvec_doc_destroy(batch_docs[i]);
}
free(batch_docs);
}
clock_t insert_end_time = clock();
double insert_time =
((double)(insert_end_time - start_time)) / CLOCKS_PER_SEC;
printf("✓ Bulk insertion completed in %.3f seconds (%.0f docs/sec)\n",
insert_time, DOC_COUNT / insert_time);
// 6. Flush and optimize collection
printf("Flushing and optimizing collection...\n");
zvec_collection_flush(collection);
zvec_collection_optimize(collection);
printf("✓ Collection optimized\n");
// 7. Performance query test
printf("Testing query performance...\n");
float *query_vector = create_test_vector(128);
if (!query_vector) {
fprintf(stderr, "Failed to create query vector\n");
goto cleanup_collection;
}
zvec_vector_query_t *query = zvec_vector_query_create();
if (!query) {
fprintf(stderr, "Failed to create vector query\n");
free(query_vector);
goto cleanup_collection;
}
zvec_vector_query_set_field_name(query, "embedding");
zvec_vector_query_set_query_vector(query, query_vector, 128 * sizeof(float));
zvec_vector_query_set_topk(query, 10);
zvec_vector_query_set_filter(query, "");
zvec_vector_query_set_include_vector(query, false);
zvec_vector_query_set_include_doc_id(query, true);
const int QUERY_COUNT = 100;
start_time = clock();
for (int q = 0; q < QUERY_COUNT; q++) {
zvec_doc_t **results = NULL;
size_t result_count = 0;
error =
zvec_collection_query(collection, (const zvec_vector_query_t *)query,
&results, &result_count);
if (error != ZVEC_OK) {
char *error_msg = NULL;
zvec_get_last_error(&error_msg);
printf("Query %d failed: %s\n", q,
error_msg ? error_msg : "Unknown error");
zvec_free(error_msg);
continue;
}
if (results) {
zvec_docs_free(results, result_count);
}
}
clock_t query_end_time = clock();
double query_time = ((double)(query_end_time - start_time)) / CLOCKS_PER_SEC;
double avg_query_time = (query_time * 1000) / QUERY_COUNT;
printf("✓ Performance test completed\n");
printf(" Average query time: %.2f ms\n", avg_query_time);
printf(" Queries per second: %.0f\n", 1000.0 / avg_query_time);
free(query_vector);
zvec_vector_query_destroy(query);
// 8. Memory usage information
zvec_collection_stats_t *stats = NULL;
error = zvec_collection_get_stats(collection, &stats);
if (error == ZVEC_OK && stats) {
printf("Collection Statistics:\n");
printf(" Document count: %llu\n",
(unsigned long long)zvec_collection_stats_get_doc_count(stats));
zvec_collection_stats_destroy(stats);
}
// 9. Cleanup
cleanup_collection:
zvec_collection_destroy(collection);
cleanup_fields:
// Field schemas are managed by the collection schema, no need to destroy
// individually
cleanup_params:
zvec_collection_schema_destroy(schema);
printf("✓ Optimized example completed\n");
return 0;
}