blob: 8584f095ef6e815bec4b4df68dd5cb150b264929 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
#pragma once
extern "C" {
#include "paimon_vindex.h"
}
#include <cstdint>
#include <functional>
#include <memory>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>
namespace paimon::vindex {
class Error : public std::runtime_error {
public:
explicit Error(const std::string& msg) : std::runtime_error(msg) {}
};
inline void check(int result) {
if (result != 0) {
const char* err = paimon_vindex_last_error();
throw Error(err ? err : "unknown vector index error");
}
}
struct OutputFile {
std::function<int(const uint8_t*, size_t)> write_fn;
std::function<int()> flush_fn;
std::function<int64_t()> get_pos_fn;
};
struct InputFile {
std::function<int(uint64_t offset, uint8_t* buf, size_t len)> read_at_fn;
};
namespace detail {
inline int stream_write(void* ctx, const uint8_t* data, size_t len) noexcept {
try {
auto* cbs = static_cast<OutputFile*>(ctx);
return cbs->write_fn(data, len);
} catch (...) {
return -1;
}
}
inline int stream_flush(void* ctx) noexcept {
try {
auto* cbs = static_cast<OutputFile*>(ctx);
if (!cbs->flush_fn) return 0;
return cbs->flush_fn();
} catch (...) {
return -1;
}
}
inline int64_t stream_get_pos(void* ctx) noexcept {
try {
auto* cbs = static_cast<OutputFile*>(ctx);
if (!cbs->get_pos_fn) return -1;
return cbs->get_pos_fn();
} catch (...) {
return -1;
}
}
inline int input_read_at(void* ctx, uint64_t offset, uint8_t* buf, size_t len) noexcept {
try {
auto* cbs = static_cast<InputFile*>(ctx);
return cbs->read_at_fn(offset, buf, len);
} catch (...) {
return -1;
}
}
} // namespace detail
struct Metadata {
uint32_t index_type = 0;
size_t dimension = 0;
size_t nlist = 0;
uint32_t metric = 0;
int64_t total_vectors = 0;
size_t pq_m = 0;
size_t hnsw_m = 0;
size_t hnsw_ef_construction = 0;
size_t hnsw_max_level = 0;
};
struct SearchResult {
std::vector<int64_t> ids;
std::vector<float> distances;
};
struct SearchParams {
size_t top_k = 0;
size_t nprobe = 0;
size_t ef_search = 0;
size_t query_bits = 0;
SearchParams(size_t top_k, size_t nprobe, size_t ef_search = 0, size_t query_bits = 0)
: top_k(top_k), nprobe(nprobe), ef_search(ef_search), query_bits(query_bits) {}
PaimonVindexSearchParams to_ffi() const {
PaimonVindexSearchParams params;
params.top_k = top_k;
params.nprobe = nprobe;
params.ef_search = ef_search;
params.query_bits = query_bits;
return params;
}
};
class Training {
public:
explicit Training(PaimonVindexTrainingHandle* handle = nullptr) : handle_(handle) {}
Training(const Training&) = delete;
Training& operator=(const Training&) = delete;
Training(Training&& other) noexcept : handle_(other.handle_) {
other.handle_ = nullptr;
}
Training& operator=(Training&& other) noexcept {
if (this != &other) {
if (handle_) paimon_vindex_training_free(handle_);
handle_ = other.handle_;
other.handle_ = nullptr;
}
return *this;
}
~Training() {
if (handle_) paimon_vindex_training_free(handle_);
}
private:
friend class Writer;
PaimonVindexTrainingHandle* handle_ = nullptr;
};
class Trainer {
public:
Trainer(const char* const* keys, const char* const* values, size_t num_options) {
handle_ = paimon_vindex_trainer_open(keys, values, num_options);
if (!handle_) throw Error("failed to open vector index trainer");
}
explicit Trainer(const std::vector<std::pair<std::string, std::string>>& options) {
option_keys_.reserve(options.size());
option_values_.reserve(options.size());
key_ptrs_.reserve(options.size());
value_ptrs_.reserve(options.size());
for (const auto& option : options) {
option_keys_.push_back(option.first);
option_values_.push_back(option.second);
}
for (size_t i = 0; i < options.size(); i++) {
key_ptrs_.push_back(option_keys_[i].c_str());
value_ptrs_.push_back(option_values_[i].c_str());
}
handle_ = paimon_vindex_trainer_open(key_ptrs_.data(), value_ptrs_.data(), options.size());
if (!handle_) throw Error("failed to open vector index trainer");
}
Trainer(const Trainer&) = delete;
Trainer& operator=(const Trainer&) = delete;
Trainer(Trainer&& other) noexcept
: handle_(other.handle_),
option_keys_(std::move(other.option_keys_)),
option_values_(std::move(other.option_values_)),
key_ptrs_(std::move(other.key_ptrs_)),
value_ptrs_(std::move(other.value_ptrs_)) {
other.handle_ = nullptr;
}
Trainer& operator=(Trainer&& other) noexcept {
if (this != &other) {
if (handle_) paimon_vindex_trainer_free(handle_);
handle_ = other.handle_;
option_keys_ = std::move(other.option_keys_);
option_values_ = std::move(other.option_values_);
key_ptrs_ = std::move(other.key_ptrs_);
value_ptrs_ = std::move(other.value_ptrs_);
other.handle_ = nullptr;
}
return *this;
}
~Trainer() {
if (handle_) paimon_vindex_trainer_free(handle_);
}
size_t dimension() const {
size_t out = 0;
check(paimon_vindex_trainer_dimension(handle_, &out));
return out;
}
Trainer& add_training_vectors(const float* data, size_t vector_count) {
check(paimon_vindex_trainer_add_training_vectors(handle_, data, vector_count));
return *this;
}
// C ABI note: finish consumes the trainer state but leaves the trainer handle owned by caller.
// This RAII wrapper frees the trainer handle after a successful finish.
Training finish_training() {
PaimonVindexTrainingHandle* training = paimon_vindex_trainer_finish(handle_);
if (!training) {
const char* err = paimon_vindex_last_error();
throw Error(err ? err : "failed to finish vector index training");
}
paimon_vindex_trainer_free(handle_);
handle_ = nullptr;
return Training(training);
}
static Training train(
const std::vector<std::pair<std::string, std::string>>& options,
const float* data,
size_t vector_count) {
Trainer trainer(options);
trainer.add_training_vectors(data, vector_count);
return trainer.finish_training();
}
private:
PaimonVindexTrainerHandle* handle_ = nullptr;
std::vector<std::string> option_keys_;
std::vector<std::string> option_values_;
std::vector<const char*> key_ptrs_;
std::vector<const char*> value_ptrs_;
};
class Writer {
public:
explicit Writer(Training&& training) {
if (!training.handle_) throw Error("training has already been consumed");
PaimonVindexTrainingHandle* training_handle = training.handle_;
training.handle_ = nullptr;
// C ABI note: writer_open consumes the training state but leaves the handle owned by caller.
// This RAII wrapper frees the consumed training handle after opening the writer.
handle_ = paimon_vindex_writer_open(training_handle);
paimon_vindex_training_free(training_handle);
if (!handle_) {
const char* err = paimon_vindex_last_error();
throw Error(err ? err : "failed to open vector index writer");
}
}
Writer(const Writer&) = delete;
Writer& operator=(const Writer&) = delete;
Writer(Writer&& other) noexcept
: handle_(other.handle_),
output_(std::move(other.output_)) {
other.handle_ = nullptr;
}
Writer& operator=(Writer&& other) noexcept {
if (this != &other) {
if (handle_) paimon_vindex_writer_free(handle_);
handle_ = other.handle_;
output_ = std::move(other.output_);
other.handle_ = nullptr;
}
return *this;
}
~Writer() {
if (handle_) paimon_vindex_writer_free(handle_);
}
size_t dimension() const {
size_t out = 0;
check(paimon_vindex_writer_dimension(handle_, &out));
return out;
}
void add_vectors(const int64_t* ids, const float* data, size_t vector_count) {
check(paimon_vindex_writer_add_vectors(handle_, ids, data, vector_count));
}
void write_index(OutputFile output) {
output_ = std::make_shared<OutputFile>(std::move(output));
PaimonVindexOutputFile raw;
raw.ctx = output_.get();
raw.write_fn = detail::stream_write;
raw.flush_fn = detail::stream_flush;
raw.get_pos_fn = detail::stream_get_pos;
check(paimon_vindex_writer_write_index(handle_, raw));
}
private:
PaimonVindexWriterHandle* handle_ = nullptr;
std::shared_ptr<OutputFile> output_;
};
class Reader {
public:
explicit Reader(InputFile input) : input_(std::make_shared<InputFile>(std::move(input))) {
PaimonVindexInputFile raw;
raw.ctx = input_.get();
raw.read_at_fn = detail::input_read_at;
handle_ = paimon_vindex_reader_open(raw);
if (!handle_) throw Error("failed to open vector index reader");
}
Reader(const Reader&) = delete;
Reader& operator=(const Reader&) = delete;
Reader(Reader&& other) noexcept
: handle_(other.handle_), input_(std::move(other.input_)) {
other.handle_ = nullptr;
}
Reader& operator=(Reader&& other) noexcept {
if (this != &other) {
if (handle_) paimon_vindex_reader_free(handle_);
handle_ = other.handle_;
input_ = std::move(other.input_);
other.handle_ = nullptr;
}
return *this;
}
~Reader() {
if (handle_) paimon_vindex_reader_free(handle_);
}
Metadata metadata() const {
PaimonVindexMetadata raw;
check(paimon_vindex_reader_metadata(handle_, &raw));
Metadata result;
result.index_type = raw.index_type;
result.dimension = raw.dimension;
result.nlist = raw.nlist;
result.metric = raw.metric;
result.total_vectors = raw.total_vectors;
result.pq_m = raw.pq_m;
result.hnsw_m = raw.hnsw_m;
result.hnsw_ef_construction = raw.hnsw_ef_construction;
result.hnsw_max_level = raw.hnsw_max_level;
return result;
}
void optimize_for_search() {
check(paimon_vindex_reader_optimize_for_search(handle_));
}
SearchResult search(const float* query, SearchParams params) {
SearchResult result;
result.ids.resize(params.top_k);
result.distances.resize(params.top_k);
check(paimon_vindex_reader_search(
handle_,
query,
params.to_ffi(),
result.ids.data(),
result.distances.data(),
params.top_k));
return result;
}
SearchResult search_with_roaring_filter(
const float* query,
SearchParams params,
const uint8_t* filter,
size_t filter_len) {
SearchResult result;
result.ids.resize(params.top_k);
result.distances.resize(params.top_k);
check(paimon_vindex_reader_search_with_roaring_filter(
handle_,
query,
params.to_ffi(),
filter,
filter_len,
result.ids.data(),
result.distances.data(),
params.top_k));
return result;
}
SearchResult search_batch(
const float* queries,
size_t query_count,
SearchParams params) {
const size_t result_len = query_count * params.top_k;
SearchResult result;
result.ids.resize(result_len);
result.distances.resize(result_len);
check(paimon_vindex_reader_search_batch(
handle_,
queries,
query_count,
params.to_ffi(),
result.ids.data(),
result.distances.data(),
result_len));
return result;
}
SearchResult search_batch_with_roaring_filter(
const float* queries,
size_t query_count,
SearchParams params,
const uint8_t* filter,
size_t filter_len) {
const size_t result_len = query_count * params.top_k;
SearchResult result;
result.ids.resize(result_len);
result.distances.resize(result_len);
check(paimon_vindex_reader_search_batch_with_roaring_filter(
handle_,
queries,
query_count,
params.to_ffi(),
filter,
filter_len,
result.ids.data(),
result.distances.data(),
result_len));
return result;
}
private:
PaimonVindexReaderHandle* handle_ = nullptr;
std::shared_ptr<InputFile> input_;
};
} // namespace paimon::vindex