blob: 27576e4b7afd0c559dd06c1db54773e377f92c4c [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.
*/
#include "paimon/testing/mock/mock_search_file_index.h"
#include <map>
#include "arrow/c/bridge.h"
#include "paimon/common/io/byte_array_output_stream.h"
#include "paimon/common/io/data_output_stream.h"
#include "paimon/common/io/memory_segment_output_stream.h"
#include "paimon/common/io/offset_input_stream.h"
#include "paimon/common/utils/arrow/status_utils.h"
#include "paimon/factories/factory.h"
#include "paimon/file_index/bitmap_index_result.h"
#include "paimon/file_index/scored_file_index_result.h"
#include "paimon/io/data_input_stream.h"
#include "paimon/memory/bytes.h"
#include "paimon/predicate/full_text_search.h"
#include "paimon/predicate/vector_search.h"
namespace paimon::test {
MockSearchFileIndexReader::MockSearchFileIndexReader(std::string field_name,
SearchIndexType index_type,
MockSearchFileIndex::ScoredRows rows)
: field_name_(std::move(field_name)), index_type_(index_type), rows_(std::move(rows)) {}
Result<std::shared_ptr<ScoredFileIndexResult>> MockSearchFileIndexReader::VisitVectorSearch(
const std::shared_ptr<VectorSearch>& search) {
if (index_type_ != SearchIndexType::VECTOR) {
return FileIndexReader::VisitVectorSearch(search);
}
if (search->field_name != field_name_) {
return Status::Invalid("Mock vector search field does not match the indexed field");
}
std::map<int32_t, float> selected;
for (const auto& [row_id, score] : rows_) {
if (selected.size() >= static_cast<size_t>(search->limit)) {
break;
}
if (!search->pre_filter || search->pre_filter(row_id)) {
selected.emplace(row_id, score);
}
}
RoaringBitmap32 positions;
std::vector<float> scores;
for (const auto& [row_id, score] : selected) {
positions.Add(row_id);
scores.push_back(score);
}
return ScoredFileIndexResult::Create(std::move(positions), std::move(scores));
}
Result<std::shared_ptr<FileIndexResult>> MockSearchFileIndexReader::VisitFullTextSearch(
const std::shared_ptr<FullTextSearch>& search) {
if (index_type_ != SearchIndexType::FULL_TEXT) {
return FileIndexReader::VisitFullTextSearch(search);
}
if (search->field_name != field_name_) {
return Status::Invalid("Mock full-text search field does not match the indexed field");
}
RoaringBitmap32 positions;
for (const auto& [row_id, score] : rows_) {
if (positions.Cardinality() >= search->limit) {
break;
}
if (!search->include_row_ids || search->include_row_ids->Contains(row_id)) {
positions.Add(row_id);
}
}
return std::make_shared<BitmapIndexResult>(
[bitmap = std::move(positions)]() -> Result<RoaringBitmap32> { return bitmap; });
}
MockSearchFileIndexer::MockSearchFileIndexer(SearchIndexType index_type)
: index_type_(index_type) {}
Result<std::shared_ptr<FileIndexReader>> MockSearchFileIndexer::CreateReader(
::ArrowSchema* c_schema, int64_t start, int64_t length,
const std::shared_ptr<InputStream>& input, const std::shared_ptr<MemoryPool>&) const {
PAIMON_ASSIGN_OR_RAISE_FROM_ARROW(std::shared_ptr<arrow::Schema> schema,
arrow::ImportSchema(c_schema));
PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<OffsetInputStream> payload,
OffsetInputStream::Create(input, length, start));
DataInputStream data_input(payload);
PAIMON_ASSIGN_OR_RAISE(int32_t count, data_input.ReadValue<int32_t>());
MockSearchFileIndex::ScoredRows rows;
for (int32_t i = 0; i < count; ++i) {
PAIMON_ASSIGN_OR_RAISE(int32_t row_id, data_input.ReadValue<int32_t>());
PAIMON_ASSIGN_OR_RAISE(float score, data_input.ReadValue<float>());
rows.emplace_back(row_id, score);
}
return std::make_shared<MockSearchFileIndexReader>(schema->field(0)->name(), index_type_,
std::move(rows));
}
Result<std::shared_ptr<FileIndexWriter>> MockSearchFileIndexer::CreateWriter(
::ArrowSchema*, const std::shared_ptr<MemoryPool>&) const {
return Status::NotImplemented("Mock search file index is read-only");
}
const char* MockVectorSearchFileIndexFactory::Identifier() const {
return MockSearchFileIndex::VECTOR_IDENTIFIER;
}
Result<std::unique_ptr<FileIndexer>> MockVectorSearchFileIndexFactory::Create(
const std::map<std::string, std::string>&) const {
return std::make_unique<MockSearchFileIndexer>(SearchIndexType::VECTOR);
}
const char* MockFullTextSearchFileIndexFactory::Identifier() const {
return MockSearchFileIndex::FULL_TEXT_IDENTIFIER;
}
Result<std::unique_ptr<FileIndexer>> MockFullTextSearchFileIndexFactory::Create(
const std::map<std::string, std::string>&) const {
return std::make_unique<MockSearchFileIndexer>(SearchIndexType::FULL_TEXT);
}
REGISTER_PAIMON_FACTORY(MockVectorSearchFileIndexFactory);
REGISTER_PAIMON_FACTORY(MockFullTextSearchFileIndexFactory);
Result<std::unique_ptr<FileIndexer>> OptionsCheckingMockSearchFileIndexFactory::Create(
const std::map<std::string, std::string>& options) const {
if (options != expected_options_) {
return Status::Invalid("Mock search backend received unexpected indexer options");
}
return std::make_unique<MockSearchFileIndexer>(index_type_);
}
namespace {
std::shared_ptr<ByteArrayOutputStream> MakeOutput(const std::shared_ptr<MemoryPool>& pool) {
auto segments = std::make_unique<MemorySegmentOutputStream>(
MemorySegmentOutputStream::DEFAULT_SEGMENT_SIZE, pool);
return std::make_shared<ByteArrayOutputStream>(std::move(segments));
}
} // namespace
const char MockSearchFileIndex::VECTOR_IDENTIFIER[] = "mock-vector-search";
const char MockSearchFileIndex::FULL_TEXT_IDENTIFIER[] = "mock-full-text-search";
Result<std::shared_ptr<Bytes>> MockSearchFileIndex::MakePayload(
const ScoredRows& rows, const std::shared_ptr<MemoryPool>& pool) {
auto output = MakeOutput(pool);
DataOutputStream data_output(output);
PAIMON_RETURN_NOT_OK(data_output.WriteValue<int32_t>(static_cast<int32_t>(rows.size())));
for (const auto& [row_id, score] : rows) {
PAIMON_RETURN_NOT_OK(data_output.WriteValue<int32_t>(row_id));
PAIMON_RETURN_NOT_OK(data_output.WriteValue<float>(score));
}
PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<Bytes> payload, output->Finish(pool.get()));
return payload;
}
Result<std::shared_ptr<Bytes>> MockSearchFileIndex::Serialize(
const FileIndexFormat::ColumnIndexes& indexes, const std::shared_ptr<MemoryPool>& pool) {
auto output = MakeOutput(pool);
PAIMON_ASSIGN_OR_RAISE(std::unique_ptr<FileIndexFormat::Writer> writer,
FileIndexFormat::CreateWriter(output, FileIndexFormat::V_1, pool));
for (const auto& [column, entries] : indexes) {
for (const auto& [type, bytes] : entries) {
FileIndexFormat::Writer::PayloadWriter payload;
if (bytes && bytes->size() > 0) {
payload = [bytes = bytes](OutputStream* destination) -> Result<bool> {
PAIMON_RETURN_NOT_OK(DataOutputStream(destination).WriteBytes(bytes));
return true;
};
}
PAIMON_RETURN_NOT_OK(writer->WriteIndex(column, type, payload));
}
}
PAIMON_RETURN_NOT_OK(writer->Finish());
PAIMON_RETURN_NOT_OK(writer->Close());
PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<Bytes> bytes, output->Finish(pool.get()));
return bytes;
}
Result<std::shared_ptr<Bytes>> MockSearchFileIndex::MakeIndex(
const std::string& field_name, const std::string& index_type, const ScoredRows& rows,
const std::shared_ptr<MemoryPool>& pool) {
FileIndexFormat::ColumnIndexes indexes;
PAIMON_ASSIGN_OR_RAISE(indexes[field_name][index_type], MakePayload(rows, pool));
return Serialize(indexes, pool);
}
} // namespace paimon::test