From 0839ff60ee478f0c54a7fe647c629eaa435d70a7 Mon Sep 17 00:00:00 2001 From: HarryLeeIBM Date: Thu, 23 Jun 2022 18:56:15 -0700 Subject: [PATCH 01/42] Initial Checkin of full text search --- src/Common/FST.cpp | 445 ++++++++++ src/Common/FST.h | 126 +++ src/Common/tests/gtest_fst.cpp | 56 ++ src/Interpreters/GinFilter.cpp | 228 +++++ src/Interpreters/GinFilter.h | 85 ++ src/Interpreters/ITokenExtractor.h | 41 +- src/Storages/MergeTree/GinIndexStore.cpp | 450 ++++++++++ src/Storages/MergeTree/GinIndexStore.h | 268 ++++++ src/Storages/MergeTree/IMergeTreeDataPart.cpp | 2 + .../MergeTreeDataPartWriterOnDisk.cpp | 32 +- .../MergeTree/MergeTreeDataPartWriterOnDisk.h | 3 +- .../MergeTree/MergeTreeDataSelectExecutor.cpp | 18 +- src/Storages/MergeTree/MergeTreeIndexGin.cpp | 794 ++++++++++++++++++ src/Storages/MergeTree/MergeTreeIndexGin.h | 186 ++++ src/Storages/MergeTree/MergeTreeIndices.cpp | 4 +- src/Storages/MergeTree/MergeTreeIndices.h | 12 + src/Storages/MergeTree/MergeTreeSettings.h | 1 + 17 files changed, 2743 insertions(+), 8 deletions(-) create mode 100644 src/Common/FST.cpp create mode 100644 src/Common/FST.h create mode 100644 src/Common/tests/gtest_fst.cpp create mode 100644 src/Interpreters/GinFilter.cpp create mode 100644 src/Interpreters/GinFilter.h create mode 100644 src/Storages/MergeTree/GinIndexStore.cpp create mode 100644 src/Storages/MergeTree/GinIndexStore.h create mode 100644 src/Storages/MergeTree/MergeTreeIndexGin.cpp create mode 100644 src/Storages/MergeTree/MergeTreeIndexGin.h diff --git a/src/Common/FST.cpp b/src/Common/FST.cpp new file mode 100644 index 000000000000..0a286521888b --- /dev/null +++ b/src/Common/FST.cpp @@ -0,0 +1,445 @@ +#include +#include +#include +#include +#include +#include <../contrib/consistent-hashing/popcount.h> +#include +#include +#include +#include +#include "FST.h" + +namespace DB +{ +namespace ErrorCodes +{ + extern const int LOGICAL_ERROR; +}; + +UInt16 Arc::serialize(WriteBuffer& write_buffer) +{ + UInt16 written_bytes = 0; + bool has_output = output != 0; + + /// First UInt64 is target_index << 1 + has_output + UInt64 first = ((target->state_index) << 1) + has_output; + writeVarUInt(first, write_buffer); + written_bytes += getLengthOfVarUInt(first); + + /// Second UInt64 is output (optional based on whether has_output is not zero) + if (has_output) + { + writeVarUInt(output, write_buffer); + written_bytes += getLengthOfVarUInt(output); + } + return written_bytes; +} + +void ArcsBitmap::addArc(char label) +{ + uint8_t index = label; + UInt256 bit_label = 1; + bit_label <<= index; + + data |= bit_label; +} + +int ArcsBitmap::getIndex(char label) const +{ + int bitCount = 0; + + uint8_t index = label; + int which_int64 = 0; + while (true) + { + if (index < 64) + { + UInt64 mask = index == 63 ? (-1) : (1ULL << (index+1)) - 1; + + bitCount += PopCountImpl(mask & data.items[which_int64]); + break; + } + index -= 64; + bitCount += PopCountImpl(data.items[which_int64]); + + which_int64++; + } + return bitCount; +} + +int ArcsBitmap::getArcNum() const +{ + int bitCount = 0; + for (size_t i = 0; i < 4; i++) + { + if(data.items[i]) + bitCount += PopCountImpl(data.items[i]); + } + return bitCount; +} + +bool ArcsBitmap::hasArc(char label) const +{ + uint8_t index = label; + UInt256 bit_label = 1; + bit_label <<= index; + + return ((data & bit_label) != 0); +} + +Arc* State::getArc(char label) +{ + auto it(arcs.find(label)); + if (it == arcs.cend()) + return nullptr; + + return &it->second; +} + +void State::addArc(char label, Output output, StatePtr target) +{ + arcs[label] = Arc(output, target); +} + +UInt64 State::hash() const +{ + std::vector values; + + // put 2 magic chars, in case there are no arcs + values.push_back('C'); + values.push_back('H'); + + for (auto& label_arc : arcs) + { + values.push_back(label_arc.first); + auto ptr = reinterpret_cast(&label_arc.second.output); + std::copy(ptr, ptr + sizeof(Output), std::back_inserter(values)); + + ptr = reinterpret_cast(&label_arc.second.target->id); + std::copy(ptr, ptr + sizeof(UInt64), std::back_inserter(values)); + } + + return CityHash_v1_0_2::CityHash64(values.data(), values.size()); +} + +bool operator== (const State& state1, const State& state2) +{ + for (auto& label_arc : state1.arcs) + { + auto it(state2.arcs.find(label_arc.first)); + if (it == state2.arcs.cend()) + return false; + if (it->second.output != label_arc.second.output) + return false; + if (it->second.target->id != label_arc.second.target->id) + return false; + } + return true; +} + +UInt64 State::serialize(WriteBuffer& write_buffer) +{ + UInt64 written_bytes = 0; + + /// Serialize flag + write_buffer.write(flag); + written_bytes += 1; + + if (flag_values.encoding_method == ENCODING_METHOD_SEQUENTIAL) + { + /// Serialize all labels + std::vector labels; + labels.reserve(MAX_ARCS_IN_SEQUENTIAL_METHID); + + for (auto& label_state : arcs) + { + labels.push_back(label_state.first); + } + + UInt8 label_size = labels.size(); + write_buffer.write(label_size); + written_bytes += 1; + + write_buffer.write(labels.data(), labels.size()); + written_bytes += labels.size(); + + /// Serialize all arcs + for (char label : labels) + { + Arc* arc = getArc(label); + assert(arc != nullptr); + written_bytes += arc->serialize(write_buffer); + } + } + else + { + /// Serialize bitmap + ArcsBitmap bmp; + for (auto& label_state : arcs) + { + bmp.addArc(label_state.first); + } + + UInt64 bmp_encoded_size = getLengthOfVarUInt(bmp.data.items[0]) + + getLengthOfVarUInt(bmp.data.items[1]) + + getLengthOfVarUInt(bmp.data.items[2]) + + getLengthOfVarUInt(bmp.data.items[3]); + + writeVarUInt(bmp.data.items[0], write_buffer); + writeVarUInt(bmp.data.items[1], write_buffer); + writeVarUInt(bmp.data.items[2], write_buffer); + writeVarUInt(bmp.data.items[3], write_buffer); + + written_bytes += bmp_encoded_size; + /// Serialize all arcs + for (auto& label_state : arcs) + { + Arc* arc = getArc(label_state.first); + assert(arc != nullptr); + written_bytes += arc->serialize(write_buffer); + } + } + + return written_bytes; +} + +FSTBuilder::FSTBuilder(WriteBuffer& write_buffer_) : write_buffer(write_buffer_) +{ + for (size_t i = 0; i < temp_states.size(); ++i) + { + temp_states[i] = std::make_shared(); + } +} + +StatePtr FSTBuilder::findMinimized(const State& state, bool& found) +{ + found = false; + auto hash = state.hash(); + auto it(minimized_states.find(hash)); + + if (it != minimized_states.cend() && *it->second == state) + { + found = true; + return it->second; + } + StatePtr p(new State(state)); + minimized_states[hash] = p; + return p; +} + +size_t FSTBuilder::getCommonPrefix(const std::string& word1, const std::string& word2) +{ + size_t i = 0; + while (i < word1.size() && i < word2.size() && word1[i] == word2[i]) + i++; + return i; +} + +void FSTBuilder::minimizePreviousWordSuffix(int down_to) +{ + for (int i = static_cast(previous_word.size()); i >= down_to; --i) + { + bool found{ false }; + auto minimized_state = findMinimized(*temp_states[i], found); + + if (i != 0) + { + Output output = 0; + Arc* arc = temp_states[i - 1]->getArc(previous_word[i - 1]); + if (arc) + output = arc->output; + + temp_states[i - 1]->addArc(previous_word[i - 1], output, minimized_state); + } + if (minimized_state->id == 0) + minimized_state->id = next_id++; + + if (i > 0 && temp_states[i - 1]->id == 0) + temp_states[i - 1]->id = next_id++; + + if (!found) + { + minimized_state->state_index = previous_state_index; + + previous_written_bytes = minimized_state->serialize(write_buffer); + state_count++; + previous_state_index += previous_written_bytes; + } + } +} + +void FSTBuilder::add(const std::string& current_word, Output current_output) +{ + /// We assume word size is no greater than MAX_TERM_LENGTH + auto current_word_len = current_word.size(); + + if (current_word_len > MAX_TERM_LENGTH) + throw Exception(DB::ErrorCodes::LOGICAL_ERROR, "Too long term ({}) passed to FST builder.", current_word_len); + + size_t prefix_length_plus1 = getCommonPrefix(current_word, previous_word) + 1; + + minimizePreviousWordSuffix(prefix_length_plus1); + + /// Initialize the tail state + for (size_t i = prefix_length_plus1; i <= current_word.size(); ++i) + { + temp_states[i]->clear(); + temp_states[i - 1]->addArc(current_word[i-1], 0, temp_states[i]); + } + + /// We assume the current word is different with previous word + temp_states[current_word_len]->flag_values.is_final = true; + /// Adjust outputs on the arcs + for (size_t j = 1; j <= prefix_length_plus1 - 1; ++j) + { + auto arc_ptr = temp_states[j - 1]->getArc(current_word[j-1]); + assert(arc_ptr != nullptr); + + auto common_prefix = std::min(arc_ptr->output, current_output); + auto word_suffix = arc_ptr->output - common_prefix; + arc_ptr->output = common_prefix; + + /// For each arc, adjust its output + if (word_suffix != 0) + { + for (auto& label_arc : temp_states[j]->arcs) + { + auto& arc = label_arc.second; + arc.output += word_suffix; + } + } + /// Reduce current_output + current_output -= common_prefix; + } + + /// Set last temp state's output + auto arc = temp_states[prefix_length_plus1 - 1]->getArc(current_word[prefix_length_plus1-1]); + assert(arc != nullptr); + arc->output = current_output; + + previous_word = current_word; +} + +UInt64 FSTBuilder::build() +{ + minimizePreviousWordSuffix(0); + + /// Save initial state index + + previous_state_index -= previous_written_bytes; + UInt8 length = getLengthOfVarUInt(previous_state_index); + writeVarUInt(previous_state_index, write_buffer); + write_buffer.write(length); + + return previous_state_index + previous_written_bytes + length + 1; +} + +FST::FST(std::vector&& data_) : data(data_) +{ +} + +void FST::clear() +{ + data.clear(); +} + +std::pair FST::getOutput(const String& term) +{ + std::pair result_output{ false, 0 }; + /// Read index of initial state + ReadBufferFromMemory read_buffer(data.data(), data.size()); + read_buffer.seek(data.size()-1, SEEK_SET); + + UInt8 length{0}; + read_buffer.read(reinterpret_cast(length)); + + read_buffer.seek(data.size() - 1 - length, SEEK_SET); + UInt64 state_index{ 0 }; + readVarUInt(state_index, read_buffer); + + for (size_t i = 0; i <= term.size(); ++i) + { + UInt64 arc_output{ 0 }; + + /// Read flag + State temp_state; + + read_buffer.seek(state_index, SEEK_SET); + read_buffer.read(reinterpret_cast(temp_state.flag)); + if (i == term.size()) + { + result_output.first = temp_state.flag_values.is_final; + break; + } + + UInt8 label = term[i]; + if (temp_state.flag_values.encoding_method == State::ENCODING_METHOD_SEQUENTIAL) + { + /// Read number of labels + UInt8 label_num{0}; + read_buffer.read(reinterpret_cast(label_num)); + + if(label_num == 0) + return { false, 0 }; + + auto labels_position = read_buffer.getPosition(); + + /// Find the index of the label from "labels" bytes + auto begin_it{ data.begin() + labels_position }; + auto end_it{ data.begin() + labels_position + label_num }; + + auto pos = std::find(begin_it, end_it, label); + + if (pos == end_it) + return { false, 0 }; + + /// Read the arc for the label + UInt64 arc_index = (pos - begin_it); + auto arcs_start_postion = labels_position + label_num; + + read_buffer.seek(arcs_start_postion, SEEK_SET); + for (size_t j = 0; j <= arc_index; j++) + { + state_index = 0; + arc_output = 0; + readVarUInt(state_index, read_buffer); + if (state_index & 0x1) // output is followed + { + readVarUInt(arc_output, read_buffer); + } + state_index >>= 1; + } + } + else + { + ArcsBitmap bmp; + + readVarUInt(bmp.data.items[0], read_buffer); + readVarUInt(bmp.data.items[1], read_buffer); + readVarUInt(bmp.data.items[2], read_buffer); + readVarUInt(bmp.data.items[3], read_buffer); + + if (!bmp.hasArc(label)) + return { false, 0 }; + + /// Read the arc for the label + size_t arc_index = bmp.getIndex(label); + for (size_t j = 0; j < arc_index; j++) + { + state_index = 0; + arc_output = 0; + readVarUInt(state_index, read_buffer); + if (state_index & 0x1) // output is followed + { + readVarUInt(arc_output, read_buffer); + } + state_index >>= 1; + } + } + /// Accumulate the output value + result_output.second += arc_output; + } + return result_output; +} +} diff --git a/src/Common/FST.h b/src/Common/FST.h new file mode 100644 index 000000000000..771742bc48d2 --- /dev/null +++ b/src/Common/FST.h @@ -0,0 +1,126 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace DB +{ +using Output = UInt64; + +struct State; +using StatePtr = std::shared_ptr; + +struct Arc +{ + Arc() = default; + Arc(Output output_, const StatePtr &target_): output{output_}, target{target_}{} + Output output{0}; + StatePtr target; + + UInt16 serialize(WriteBuffer& write_buffer); +}; + +class ArcsBitmap +{ +public: + void addArc(char label); + bool hasArc(char label) const; + int getIndex(char label) const; + int getArcNum() const; +private: + friend struct State; + friend class FST; + UInt256 data{0}; +}; + +struct State +{ + static constexpr size_t MAX_ARCS_IN_SEQUENTIAL_METHID = 32; + enum EncodingMethod + { + ENCODING_METHOD_SEQUENTIAL = 0, + ENCODING_METHOD_BITMAP, + }; + State() = default; + State(const State &state) = default; + + UInt64 hash() const; + + Arc* getArc(char label); + void addArc(char label, Output output, StatePtr target); + + void clear() + { + id = 0; + state_index = 0; + flag = 0; + + arcs.clear(); + } + + UInt64 serialize(WriteBuffer& write_buffer); + + UInt64 id{ 0 }; + UInt64 state_index{ 0 }; + union + { + struct + { + unsigned int is_final : 1; + unsigned int encoding_method : 2; + } flag_values; + uint8_t flag{0}; + }; + std::map arcs; +}; + +bool operator== (const State& state1, const State& state2); + +constexpr size_t MAX_TERM_LENGTH = 256; + +class FSTBuilder +{ +public: + FSTBuilder(WriteBuffer& write_buffer_); + StatePtr findMinimized(const State& s, bool &found); + + void add(const std::string& word, Output output); + UInt64 build(); + + UInt64 state_count{ 0 }; +private: + void minimizePreviousWordSuffix(int down_to); + static size_t getCommonPrefix(const std::string &word1, const std::string& word2); +private: + std::array temp_states; + std::string previous_word; + StatePtr initial_state; + std::unordered_map minimized_states; + + UInt64 next_id{1}; + + WriteBuffer &write_buffer; + UInt64 previous_written_bytes{0}; + UInt64 previous_state_index{0}; + +}; + +class FST +{ +public: + FST() = default; + FST(std::vector&& data_); + std::pair getOutput(const String& term); + void clear(); + std::vector &getData() { return data;} +private: + std::vector data; +}; +} diff --git a/src/Common/tests/gtest_fst.cpp b/src/Common/tests/gtest_fst.cpp new file mode 100644 index 000000000000..113a41e0116b --- /dev/null +++ b/src/Common/tests/gtest_fst.cpp @@ -0,0 +1,56 @@ +#include +#include + +#include +#include +#include + +using namespace std; + +TEST(FST, SimpleTest) +{ + vector> data + { + {"mop", 100}, + {"moth", 91}, + {"pop", 72}, + {"star", 83}, + {"stop", 54}, + {"top", 55}, + }; + + vector> bad_data + { + {"mo", 100}, + {"moth1", 91}, + {"po", 72}, + {"star2", 83}, + {"sto", 54}, + {"top33", 55}, + }; + + std::vector buffer; + DB::WriteBufferFromVector> wbuf(buffer); + DB::FSTBuilder builder(wbuf); + + for (auto& term_output : data) + { + builder.add(term_output.first, term_output.second); + } + builder.build(); + wbuf.finalize(); + + DB::FST fst(std::move(buffer)); + for (auto& item : data) + { + auto output = fst.getOutput(item.first); + ASSERT_EQ(output.first, true); + ASSERT_EQ(output.second, item.second); + } + + for (auto& item : bad_data) + { + auto output = fst.getOutput(item.first); + ASSERT_EQ(output.first, false); + } +} diff --git a/src/Interpreters/GinFilter.cpp b/src/Interpreters/GinFilter.cpp new file mode 100644 index 000000000000..496023d10935 --- /dev/null +++ b/src/Interpreters/GinFilter.cpp @@ -0,0 +1,228 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +namespace DB +{ +namespace ErrorCodes +{ + extern const int BAD_ARGUMENTS; +} +GinFilterParameters::GinFilterParameters(size_t ngrams_) + : ngrams(ngrams_) +{ + if (ngrams > 8) + throw Exception("The size of gin filter cannot be greater than 8", ErrorCodes::BAD_ARGUMENTS); +} + +GinFilter::GinFilter(const GinFilterParameters & params) + : ngrams(params.ngrams) +{ +} + +void GinFilter::add(const char* data, size_t len, UInt32 rowID, GinIndexStorePtr& store) +{ + if(len > MAX_TERM_LENGTH) + return; + + string token(data, len); + auto it(store->getPostings().find(token)); + + if (it != store->getPostings().end()) + { + if (!it->second->contains(rowID)) + it->second->add(rowID); + } + else + { + GinIndexPostingsBuilderPtr builder = std::make_shared(); + builder->add(rowID); + + store->getPostings()[token] = builder; + } +} + +void GinFilter::addRowRangeToGinFilter(UInt32 segmentID, UInt32 rowIDStart, UInt32 rowIDEnd) +{ + if(rowid_range_container.size() > 0) + { + /// Try to merge the rowID range with the last one in the container + if(rowid_range_container.back().segment_id == segmentID && + rowid_range_container.back().range_end+1 == rowIDStart) + { + rowid_range_container.back().range_end = rowIDEnd; + return; + } + } + rowid_range_container.push_back({segmentID, rowIDStart, rowIDEnd}); +} + +void GinFilter::clear() +{ + rowid_range_container.clear(); +} + +#ifndef NDEBUG +void GinFilter::dump() const +{ + printf("filter : '%s', row ID range:\n", getMatchString().c_str()); + for(const auto & rowid_range: rowid_range_container) + { + printf("\t\t%d, %d, %d; ", rowid_range.segment_id, rowid_range.range_start, rowid_range.range_end); + } + printf("\n"); +} + +void dumpPostingsCache(const PostingsCache& postings_cache) +{ + for (const auto& term_postings : postings_cache) + { + printf("--term '%s'---------\n", term_postings.first.c_str()); + + const SegmentedPostingsListContainer& container = term_postings.second; + + for (const auto& [segment_id, postings_list] : container) + { + printf("-----segment id = %d ---------\n", segment_id); + printf("-----postings-list: "); + for (auto it = postings_list->begin(); it != postings_list->end(); ++it) + { + printf("%d ", *it); + } + printf("\n"); + } + } +} + +void dumpPostingsCacheForStore(const PostingsCacheForStore& cache_store) +{ + printf("----cache---store---: %s\n", cache_store.store->getName().c_str()); + for(const auto & query_string_postings_cache: cache_store.cache) + { + printf("----cache_store----filter string:%s---\n", query_string_postings_cache.first.c_str()); + dumpPostingsCache(*query_string_postings_cache.second); + } +} +#endif + +bool GinFilter::hasEmptyPostingsList(const PostingsCachePtr& postings_cache) +{ + if(postings_cache->size() == 0) + return true; + + for (const auto& term_postings : *postings_cache) + { + const SegmentedPostingsListContainer& container = term_postings.second; + if(container.size() == 0) + return true; + } + return false; +} + +bool GinFilter::matchInRange(const PostingsCachePtr& postings_cache, UInt32 segment_id, UInt32 range_start, UInt32 range_end) const +{ + /// Check for each terms + GinIndexPostingsList intersection_result; + bool intersection_result_init = false; + + for (const auto& term_postings : *postings_cache) + { + const SegmentedPostingsListContainer& container = term_postings.second; + auto container_it{ container.find(segment_id) }; + if (container_it == container.cend()) + { + return false; + } + auto min_in_container = container_it->second->minimum(); + auto max_in_container = container_it->second->maximum(); + if(range_start > max_in_container || min_in_container > range_end) + { + return false; + } + + /// Delay initialization as late as possible + if(!intersection_result_init) + { + intersection_result_init = true; + intersection_result.addRange(range_start, range_end+1); + } + intersection_result &= *container_it->second; + if(intersection_result.cardinality() == 0) + { + return false; + } + } + return true; +} + +bool GinFilter::match(const PostingsCachePtr& postings_cache) const +{ + if(hasEmptyPostingsList(postings_cache)) + { + return false; + } + + bool match_result = false; + /// Check for each row ID ranges + for (const auto &rowid_range: rowid_range_container) + { + match_result |= matchInRange(postings_cache, rowid_range.segment_id, rowid_range.range_start, rowid_range.range_end); + } + + return match_result; +} + +bool GinFilter::needsFilter() const +{ + if(getTerms().size() == 0) + return false; + + for(const auto & term: getTerms()) + { + if(term.size() > MAX_TERM_LENGTH) + return false; + } + + return true; +} + +bool GinFilter::contains(const GinFilter & af, PostingsCacheForStore &cache_store) +{ + if(!af.needsFilter()) + return true; + + PostingsCachePtr postings_cache = cache_store.getPostings(af.getMatchString()); + if(postings_cache == nullptr) + { + GinIndexStoreReader reader(cache_store.store); + + postings_cache = reader.loadPostingsIntoCache(af.getTerms()); + cache_store.cache[af.getMatchString()] = postings_cache; + } + + if (!match(postings_cache)) + return false; + + return true; +} + +const String& GinFilter::getName() +{ + static String name("gin"); + return name; +} + +} diff --git a/src/Interpreters/GinFilter.h b/src/Interpreters/GinFilter.h new file mode 100644 index 000000000000..ff279cb59c65 --- /dev/null +++ b/src/Interpreters/GinFilter.h @@ -0,0 +1,85 @@ +#pragma once + +#include +#include +#include +namespace DB +{ +struct GinFilterParameters +{ + GinFilterParameters(size_t ngrams_); + + size_t ngrams; +}; +class DiskLocal; + +using RowIDRange = struct +{ + /// Segement ID of the row ID range + UInt32 segment_id; + + /// First row ID in the range + UInt32 range_start; + + /// Last row ID in the range + UInt32 range_end; +}; + +class GinFilter +{ +public: + using RowIDRangeContainer = std::vector; + + explicit GinFilter(const GinFilterParameters& params); + + void add(const char* data, size_t len, UInt32 rowID, GinIndexStorePtr& store); + + void addRowRangeToGinFilter(UInt32 segmentID, UInt32 rowIDStart, UInt32 rowIDEnd); + + void clear(); + + size_t size() const { return rowid_range_container.size(); } + + bool contains(const GinFilter& af, PostingsCacheForStore &store); + + const RowIDRangeContainer& getFilter() const { return rowid_range_container; } + + RowIDRangeContainer& getFilter() { return rowid_range_container; } + + void setQueryString(const char* data, size_t len) + { + query_string = String(data, len); + } + + const String &getMatchString() const { return query_string; } + +#ifndef NDEBUG + void dump() const; +#endif + + void addTerm(const char* data, size_t len) { terms.push_back(String(data, len));} + + const std::vector& getTerms() const { return terms;} + + bool needsFilter() const; + + bool match(const PostingsCachePtr& postings_cache) const; + + static const String& getName(); +private: + [[maybe_unused]] size_t ngrams; + + String query_string; + + std::vector terms; + + RowIDRangeContainer rowid_range_container; +private: + static bool hasEmptyPostingsList(const PostingsCachePtr& postings_cache); + + bool matchInRange(const PostingsCachePtr& postings_cache, UInt32 segment_id, UInt32 range_start, UInt32 range_end) const; +}; + +using GinFilterPtr = std::shared_ptr; + +} diff --git a/src/Interpreters/ITokenExtractor.h b/src/Interpreters/ITokenExtractor.h index afcc8442d583..77de4233b63c 100644 --- a/src/Interpreters/ITokenExtractor.h +++ b/src/Interpreters/ITokenExtractor.h @@ -3,7 +3,7 @@ #include #include - +#include namespace DB { @@ -37,6 +37,15 @@ struct ITokenExtractor virtual void stringLikeToBloomFilter(const char * data, size_t length, BloomFilter & bloom_filter) const = 0; + virtual void stringToGinFilter(const char * data, size_t length, GinFilter & gin_filter) const = 0; + + virtual void stringPaddedToGinFilter(const char * data, size_t length, GinFilter & gin_filter) const + { + return stringToGinFilter(data, length, gin_filter); + } + + virtual void stringLikeToGinFilter(const char * data, size_t length, GinFilter & gin_filter) const = 0; + }; using TokenExtractorPtr = const ITokenExtractor *; @@ -71,6 +80,36 @@ class ITokenExtractorHelper : public ITokenExtractor while (cur < length && static_cast(this)->nextInStringLike(data, length, &cur, token)) bloom_filter.add(token.c_str(), token.size()); } + void stringToGinFilter(const char * data, size_t length, GinFilter & gin_filter) const override + { + gin_filter.setQueryString(data, length); + size_t cur = 0; + size_t token_start = 0; + size_t token_len = 0; + + while (cur < length && static_cast(this)->nextInString(data, length, &cur, &token_start, &token_len)) + gin_filter.addTerm(data + token_start, token_len); + } + + void stringPaddedToGinFilter(const char * data, size_t length, GinFilter & gin_filter) const override + { + gin_filter.setQueryString(data, length); + size_t cur = 0; + size_t token_start = 0; + size_t token_len = 0; + + while (cur < length && static_cast(this)->nextInStringPadded(data, length, &cur, &token_start, &token_len)) + gin_filter.addTerm(data + token_start, token_len); + } + + void stringLikeToGinFilter(const char * data, size_t length, GinFilter & gin_filter) const override + { + gin_filter.setQueryString(data, length); + size_t cur = 0; + String token; + while (cur < length && static_cast(this)->nextInStringLike(data, length, &cur, token)) + gin_filter.addTerm(token.c_str(), token.size()); + } }; diff --git a/src/Storages/MergeTree/GinIndexStore.cpp b/src/Storages/MergeTree/GinIndexStore.cpp new file mode 100644 index 000000000000..b16a36438f2f --- /dev/null +++ b/src/Storages/MergeTree/GinIndexStore.cpp @@ -0,0 +1,450 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // for high_resolution_clock + +namespace DB +{ + +namespace ErrorCodes +{ + extern const int LOGICAL_ERROR; +}; + +bool GinIndexPostingsBuilder::contains(UInt32 row_id) const +{ + if(useRoaring()) + return bmp.contains(row_id); + + auto it(std::find(lst.begin(), lst.begin()+lst_length, row_id)); + return it != lst.begin()+lst_length; +} + +void GinIndexPostingsBuilder::add(UInt32 row_id) +{ + if(useRoaring()) + { + bmp.add(row_id); + return; + } + assert(lst_length < MIN_SIZE_FOR_ROARING_ENCODING); + lst[lst_length++] = row_id; + + if(lst_length == MIN_SIZE_FOR_ROARING_ENCODING) + { + for(size_t i = 0; i < lst_length; i++) + bmp.add(lst[i]); + + lst_length = 0xFF; + } +} + +bool GinIndexPostingsBuilder::useRoaring() const +{ + return lst_length == 0xFF; +} + +UInt64 GinIndexPostingsBuilder::serialize(WriteBuffer &buffer) const +{ + UInt64 encoding_length = 0; + if(!useRoaring()) + { + /// First byte is number of Row IDS to be encoded + buffer.write(lst_length); + encoding_length += 1; + for (size_t i = 0; i < lst_length; ++i) + { + writeVarUInt(lst[i], buffer); + encoding_length += getLengthOfVarUInt(lst[i]); + } + } + else + { + /// First byte is 0 (for Roaring Bitmap encoding) + buffer.write(0); + encoding_length += 1; + + auto size = bmp.getSizeInBytes(); + + writeVarUInt(size, buffer); + encoding_length += getLengthOfVarUInt(size); + + std::unique_ptr buf(new char[size]); + bmp.write(buf.get()); + buffer.write(buf.get(), size); + encoding_length += size; + } + return encoding_length; +} + +void GinIndexPostingsBuilder::clear() +{ + roaring::api::roaring_bitmap_init_cleared(&bmp.roaring); +} + +GinIndexPostingsListPtr GinIndexPostingsBuilder::deserialize(ReadBuffer &buffer) +{ + char postings_list_size{0}; + buffer.read(postings_list_size); + + if(postings_list_size != 0) + { + assert(postings_list_size < MIN_SIZE_FOR_ROARING_ENCODING); + GinIndexPostingsListPtr postings_list = std::make_shared(); + UInt32 row_ids[MIN_SIZE_FOR_ROARING_ENCODING]; + + for(auto i = 0; i < postings_list_size; ++i) + { + readVarUInt(row_ids[i], buffer); + } + postings_list->addMany(postings_list_size, row_ids); + return postings_list; + } + else + { + size_t size{0}; + readVarUInt(size, buffer); + std::unique_ptr buf(new char[size]); + + buffer.readStrict(reinterpret_cast(buf.get()), size); + + GinIndexPostingsListPtr postings_list = std::shared_ptr + (new GinIndexPostingsList(GinIndexPostingsList::read(buf.get()))); + + return postings_list; + } +} + +bool GinIndexStore::exists() const +{ + String id_file_name = part_path + name + ".gin_sid"; + return disk->exists(id_file_name); +} + +UInt32 GinIndexStore::getNextIDRange(const String& file_name, size_t n) +{ + std::lock_guard guard{gin_index_store_mutex}; + + if (!disk->exists(file_name)) + { + std::unique_ptr ostr = this->disk->writeFile(file_name); + + const auto& int_type = DB::DataTypePtr(std::make_shared()); + auto size_serialization = int_type->getDefaultSerialization(); + size_serialization->serializeBinary(1, *ostr); + ostr->sync(); + } + + /// read id in file + UInt32 result = 0; + { + std::unique_ptr istr = this->disk->readFile(file_name); + + Field field_rows; + const auto& size_type = DB::DataTypePtr(std::make_shared()); + auto size_serialization = size_type->getDefaultSerialization(); + + size_type->getDefaultSerialization()->deserializeBinary(field_rows, *istr); + result = field_rows.get(); + } + //save result+n + { + std::unique_ptr ostr = this->disk->writeFile(file_name); + + const auto& int_type = DB::DataTypePtr(std::make_shared()); + auto size_serialization = int_type->getDefaultSerialization(); + size_serialization->serializeBinary(result + n, *ostr); + ostr->sync(); + } + return result; +} + +UInt32 GinIndexStore::getNextRowIDRange(size_t n) +{ + UInt32 result =current_segment.next_row_id; + current_segment.next_row_id += n; + return result; +} + +UInt32 GinIndexStore::getNextSegmentID() +{ + String sid_file_name = part_path + name + ".gin_sid"; + return getNextIDRange(sid_file_name, 1); +} +UInt32 GinIndexStore::getSegmentNum() +{ + String sid_file_name = part_path + name + ".gin_sid"; + if (!disk->exists(sid_file_name)) + return 0; + Int32 result = 0; + { + std::unique_ptr istr = this->disk->readFile(sid_file_name); + + Field field_rows; + const auto& size_type = DB::DataTypePtr(std::make_shared()); + auto size_serialization = size_type->getDefaultSerialization(); + + size_type->getDefaultSerialization()->deserializeBinary(field_rows, *istr); + result = field_rows.get(); + } + return result - 1; +} + + bool GinIndexStore::needToWrite() const + { + assert(max_digestion_size > 0); + return current_size > max_digestion_size; + } + +void GinIndexStore::finalize() +{ + if(current_postings.size() > 0) + { + writeSegment(); + } +} + +void GinIndexStore::init_file_streams() +{ + String segment_file_name = part_path + name + ".gin_seg"; + String item_dict_file_name = part_path + name + ".gin_dict"; + String postings_file_name = part_path + name + ".gin_post"; + + segment_file_stream = disk->writeFile(segment_file_name, DBMS_DEFAULT_BUFFER_SIZE, WriteMode::Append, {}); + term_dict_file_stream = disk->writeFile(item_dict_file_name, DBMS_DEFAULT_BUFFER_SIZE, WriteMode::Append, {}); + postings_file_stream = disk->writeFile(postings_file_name, DBMS_DEFAULT_BUFFER_SIZE, WriteMode::Append, {}); +} + +void GinIndexStore::writeSegment() +{ + if (segment_file_stream == nullptr) + { + init_file_streams(); + } + + ///write segment + segment_file_stream->write(reinterpret_cast(¤t_segment), sizeof(GinIndexSegment)); + std::vector> token_postings_list_pairs; + token_postings_list_pairs.reserve(current_postings.size()); + + for (const auto& [token, postings_list] : current_postings) { + token_postings_list_pairs.push_back({std::string_view(token), postings_list}); + } + std::sort(token_postings_list_pairs.begin(), token_postings_list_pairs.end(), + [](const std::pair& a, const std::pair& b) { + return a.first < b.first; + }); + + ///write postings + std::vector encoding_lengths(current_postings.size(), 0); + size_t current_index = 0; + + for (const auto& [token, postings_list] : token_postings_list_pairs) + { + auto encoding_length = postings_list->serialize(*postings_file_stream); + postings_list->clear(); + + encoding_lengths[current_index++] = encoding_length; + current_segment.postings_start_offset += encoding_length; + } + ///write item dictionary + std::vector buffer; + WriteBufferFromVector> write_buf(buffer); + FSTBuilder builder(write_buf); + + UInt64 offset{0}; + current_index = 0; + for (const auto& [token, postings_list] : token_postings_list_pairs) + { + String strToken{token}; + builder.add(strToken, offset); + offset += encoding_lengths[current_index++]; + } + + builder.build(); + write_buf.finalize(); + + /// Write FST size + writeVarUInt(buffer.size(), *term_dict_file_stream); + current_segment.item_dict_start_offset += getLengthOfVarUInt(buffer.size()); + + /// Write FST content + term_dict_file_stream->write(reinterpret_cast(buffer.data()), buffer.size()); + current_segment.item_dict_start_offset += buffer.size(); + + current_size = 0; + current_postings.clear(); + current_segment.segment_id = getNextSegmentID(); + + segment_file_stream->sync(); + term_dict_file_stream->sync(); + postings_file_stream->sync(); +} + +void GinIndexStoreReader::init_file_streams() +{ + String segment_file_name = store->part_path + store->name + ".gin_seg"; + String item_dict_file_name = store->part_path + store->name + ".gin_dict"; + String postings_file_name = store->part_path + store->name + ".gin_post"; + + segment_file_stream = store->disk->readFile(segment_file_name); + term_dict_file_stream = store->disk->readFile(item_dict_file_name); + postings_file_stream = store->disk->readFile(postings_file_name); +} +void GinIndexStoreReader::readSegments() +{ + GinIndexSegments segments; + + auto segment_num = store->getSegmentNum(); + if (segment_num == 0) + return; + + segments.assign(segment_num, {}); + + if (segment_file_stream == nullptr) + { + init_file_streams(); + } + segment_file_stream->read(reinterpret_cast(&segments[0]), segment_num * sizeof(GinIndexSegment)); + for (size_t i = 0; i < segment_num; ++i) + { + auto seg_id = segments[i].segment_id; + auto term_dict = std::make_shared(); + term_dict->postings_start_offset = segments[i].postings_start_offset; + term_dict->item_dict_start_offset = segments[i].item_dict_start_offset; + store->term_dicts[seg_id] = term_dict; + } +} + +void GinIndexStoreReader::readTermDictionary(UInt32 segment_id) +{ + /// Check validity of segment_id + auto it{ store->term_dicts.find(segment_id) }; + if (it == store->term_dicts.cend()) + { + throw Exception("Invalid segment id " + std::to_string(segment_id), ErrorCodes::LOGICAL_ERROR); + } + + it->second->offsets.getData().clear(); + + /// Set file pointer of term dictionary file + term_dict_file_stream->seek(it->second->item_dict_start_offset, SEEK_SET); + + /// Read FST size + size_t fst_size{0}; + readVarUInt(fst_size, *term_dict_file_stream); + + /// Read FST content + it->second->offsets.getData().resize(fst_size); + term_dict_file_stream->readStrict(reinterpret_cast(it->second->offsets.getData().data()), fst_size); +} + +SegmentedPostingsListContainer GinIndexStoreReader::readSegmentedPostingsLists(const String& token) +{ + SegmentedPostingsListContainer container; + for (auto const& seg_term_dict : store->term_dicts) + { + auto segment_id = seg_term_dict.first; + + auto [found, offset] = seg_term_dict.second->offsets.getOutput(token); + if (!found) + continue; + + // Set postings file pointer for reading postings list + postings_file_stream->seek(seg_term_dict.second->postings_start_offset + offset, SEEK_SET); + + // Read posting list + auto postings_list = GinIndexPostingsBuilder::deserialize(*postings_file_stream); + container[segment_id] = postings_list; + } + return container; +} + +PostingsCachePtr GinIndexStoreReader::loadPostingsIntoCache(const std::vector& terms) +{ + auto postings_cache = std::make_shared(); + for (const auto& term : terms) + { + // Make sure don't read for duplicated terms + if (postings_cache->find(term) != postings_cache->cend()) + continue; + + auto container = readSegmentedPostingsLists(term); + (*postings_cache)[term] = container; + } + return postings_cache; +} + +GinIndexStoreFactory& GinIndexStoreFactory::instance() +{ + static GinIndexStoreFactory instance; + return instance; +} + +GinIndexStorePtr GinIndexStoreFactory::get(const String& name, DiskPtr disk_, const String& part_path_) +{ + std::lock_guard lock(stores_mutex); + String key = name + String(":")+part_path_; + + GinIndexStores::const_iterator it = stores.find(key); + + if (it == stores.cend()) + { + GinIndexStorePtr store = std::make_shared(name); + store->SetDiskAndPath(disk_, part_path_); + if (!store->exists()) + throw Exception("Index '" + name + "' does not exist", ErrorCodes::LOGICAL_ERROR); + + GinIndexStoreReader reader(store); + reader.readSegments(); + + for (size_t seg_index = 0; seg_index < store->getSegmentNum(); ++seg_index) + { + reader.readTermDictionary(seg_index); + } + + stores[key] = store; + + return store; + } + return it->second; +} + +void GinIndexStoreFactory::remove(const String& part_path) +{ + std::lock_guard lock(stores_mutex); + for (auto it = stores.begin(); it != stores.end();) + { + if(it->first.find(part_path) != String::npos) + it = stores.erase(it); + else + ++it; + } +} + +#ifndef NDEBUG +void GinIndexStoreFactory::dump() +{ + printf("GinIndexStoreFactory----------dump start-------->>\n"); + for(const auto & [key, store]: stores) + { + printf("%s\n", key.c_str()); + } + printf("GinIndexStoreFactory----------dump end---------<<\n"); +} +#endif + +} diff --git a/src/Storages/MergeTree/GinIndexStore.h b/src/Storages/MergeTree/GinIndexStore.h new file mode 100644 index 000000000000..02406d3bc7c6 --- /dev/null +++ b/src/Storages/MergeTree/GinIndexStore.h @@ -0,0 +1,268 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace DB +{ + +constexpr int MIN_SIZE_FOR_ROARING_ENCODING = 16; + +/// Gin Index Segment information, which contains: +struct GinIndexSegment +{ + /// Segment ID retrieved from next available ID from file .gin_sid + UInt32 segment_id {0}; + + /// .gin_post file offset of this segment's postings lists + UInt64 postings_start_offset{0}; + + /// .gin_dict file offset of this segment's term dictionaries + UInt64 item_dict_start_offset{0}; + + /// Next row ID for this segment + UInt32 next_row_id{1}; +}; + +using GinIndexSegments = std::vector; + +/// GinIndexPostingsList which uses 32-bit Roaring +using GinIndexPostingsList = roaring::Roaring; + +using GinIndexPostingsListPtr = std::shared_ptr; + +/// Gin Index Postings List Builder. +struct GinIndexPostingsBuilder +{ + /// When the list length is no greater than MIN_SIZE_FOR_ROARING_ENCODING, array 'lst' is used + std::array lst; + + /// When the list length is greater than MIN_SIZE_FOR_ROARING_ENCODING, Roaring bitmap 'bmp' is used + roaring::Roaring bmp; + + /// lst_length stores the number of row IDs in 'lst' array, can also be a flag(0xFF) indicating that roaring bitmap is used + UInt8 lst_length{0}; + + /// Check whether a row_id is already added + bool contains(UInt32 row_id) const; + + /// Add a row_id into the builder + void add(UInt32 row_id); + + /// Check whether the builder is using roaring bitmap + bool useRoaring() const; + + /// Serialize the content of builder to given WriteBuffer, returns the bytes of serialized data + UInt64 serialize(WriteBuffer &buffer) const; + + /// Deserialize the postings list data from given ReadBuffer, return a pointer to the GinIndexPostingsList created by deserialization + static GinIndexPostingsListPtr deserialize(ReadBuffer &buffer); + + /// Clear Roaring bmp + void clear(); +}; + +using GinIndexPostingsBuilderPtr = std::shared_ptr; + +/// Container for all term's Gin Index Postings List Builder +using GinIndexPostingsBuilderContainer = std::unordered_map; + +/// Container for postings lists for each segment +using SegmentedPostingsListContainer = std::unordered_map; + +/// Postings lists and terms built from query string +using PostingsCache = std::unordered_map; +using PostingsCachePtr = std::shared_ptr; + +/// Term dictionary information, which contains: +struct TermDictionary +{ + /// .gin_post file offset of this segment's postings lists + UInt64 postings_start_offset; + + /// .gin_dict file offset of this segment's term dictionaries + UInt64 item_dict_start_offset; + + /// Finite State Transducer, which can be viewed as a map of + FST offsets; +}; + +using TermDictionaryPtr = std::shared_ptr; +using TermDictionaries = std::unordered_map; + +/// Gin Index Store which has Gin Index meta data for the corresponding Data Part +class GinIndexStore +{ +public: + GinIndexStore(const String& name_) + : name(name_) + { + } + GinIndexStore(const String& name_, DiskPtr disk_, const String& part_path_, UInt64 max_digestion_size_) + : name(name_), + disk(disk_), + part_path(part_path_), + max_digestion_size(max_digestion_size_) + { + } + + bool load(); + + /// Check existance by checking the existence of file .gin_seg + bool exists() const; + + UInt32 getNextIDRange(const String &file_name, size_t n); + + UInt32 getNextRowIDRange(size_t n); + + UInt32 getNextSegmentID(); + + UInt32 getSegmentNum(); + + using GinIndexStorePtr = std::shared_ptr; + + void SetDiskAndPath(DiskPtr disk_, const String& part_path_) + { + disk = disk_; + part_path = part_path_; + } + + GinIndexPostingsBuilderContainer& getPostings() { return current_postings; } + + /// Check if we need to write segment to Gin index files + bool needToWrite() const; + + /// Accumulate the size of text data which has been digested + void addSize(UInt64 sz) { current_size += sz; } + + UInt64 getCurrentSegmentID() { return current_segment.segment_id;} + + /// Do last segment writing + void finalize(); + + /// method for wrting segment data to Gin index files + void writeSegment(); + + String getName() const {return name;} +private: + void init_file_streams(); +private: + friend class GinIndexStoreReader; + + String name; + DiskPtr disk; + String part_path; + + std::mutex gin_index_store_mutex; + + /// Terms dictionaries which are loaded from .gin_dict files + TermDictionaries term_dicts; + + /// container for building postings lists during index construction + GinIndexPostingsBuilderContainer current_postings; + + /// The following is for segmentation of Gin index + GinIndexSegment current_segment; + UInt64 current_size{0}; + UInt64 max_digestion_size{0}; + + /// File streams for segment, term dictionaries and postings lists + std::unique_ptr segment_file_stream; + std::unique_ptr term_dict_file_stream; + std::unique_ptr postings_file_stream; +}; + +using GinIndexStorePtr = std::shared_ptr; +using GinIndexStores = std::unordered_map; + +/// Postings lists from 'store' which are retrieved from Gin index files for the terms in query strings +struct PostingsCacheForStore +{ + /// Which store to retrieve postings lists + GinIndexStorePtr store; + + /// map of + std::unordered_map cache; + + /// Get postings lists for query string, return nullptr if not found + PostingsCachePtr getPostings(const String &query_string) const + { + auto it {cache.find(query_string)}; + + if(it == cache.cend()) + return nullptr; + return it->second; + } +}; + +/// GinIndexStore Factory, which is a singleton for storing GinIndexStores +class GinIndexStoreFactory : private boost::noncopyable +{ +public: + /// Get singleton of GinIndexStoreFactory + static GinIndexStoreFactory& instance(); + + /// Get GinIndexStore by using index name, disk and part_path (which are combined to create key in stores) + GinIndexStorePtr get(const String& name, DiskPtr disk_, const String& part_path_); + + /// Remove all GinIndexStores which are under the same part_path + void remove(const String& part_path); +#ifndef NDEBUG + void dump(); +#endif + +private: + GinIndexStores stores; + std::mutex stores_mutex; +}; + +/// Gin Index Store Reader which helps to read segments, term dictionaries and postings list +class GinIndexStoreReader : private boost::noncopyable +{ +public: + GinIndexStoreReader(const GinIndexStorePtr & store_) + : store(store_) + { + init_file_streams(); + } + + /// Read all segment information from .gin_seg files + void readSegments(); + + /// Read term dictionary for given segment id + void readTermDictionary(UInt32 segment_id); + + /// Read postings lists for the term + SegmentedPostingsListContainer readSegmentedPostingsLists(const String& token); + + /// Read postings lists for terms(which are created by tokenzing query string) + PostingsCachePtr loadPostingsIntoCache(const std::vector& terms); + +private: + /// Initialize Gin index files + void init_file_streams(); + +private: + + /// The store for the reader + GinIndexStorePtr store; + + /// File streams for reading Gin Index + std::unique_ptr segment_file_stream; + std::unique_ptr term_dict_file_stream; + std::unique_ptr postings_file_stream; + + /// Current segment, used in building index + GinIndexSegment current_segment; +}; +using GinIndexStoreReaderPtr = std::unique_ptr; + +} diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.cpp b/src/Storages/MergeTree/IMergeTreeDataPart.cpp index afebb8992e0b..d6f58dda7330 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.cpp +++ b/src/Storages/MergeTree/IMergeTreeDataPart.cpp @@ -1672,6 +1672,8 @@ void IMergeTreeDataPart::remove() metadata_manager->deleteAll(false); metadata_manager->assertAllDeleted(false); + + GinIndexStoreFactory::instance().remove(getFullRelativePath()); std::list projection_checksums; diff --git a/src/Storages/MergeTree/MergeTreeDataPartWriterOnDisk.cpp b/src/Storages/MergeTree/MergeTreeDataPartWriterOnDisk.cpp index fbcf8cb241c1..199b36909002 100644 --- a/src/Storages/MergeTree/MergeTreeDataPartWriterOnDisk.cpp +++ b/src/Storages/MergeTree/MergeTreeDataPartWriterOnDisk.cpp @@ -1,6 +1,6 @@ #include +#include #include - #include #include "IO/WriteBufferFromFileDecorator.h" @@ -212,7 +212,15 @@ void MergeTreeDataPartWriterOnDisk::initSkipIndices() default_codec, settings.max_compress_block_size, marks_compression_codec, settings.marks_compress_block_size, settings.query_write_settings)); - skip_indices_aggregators.push_back(index_helper->createIndexAggregator()); + + GinIndexStorePtr store = nullptr; + if(dynamic_cast(&*index_helper) != nullptr) + { + store = std::make_shared(stream_name, data_part->volume->getDisk(), part_path, storage.getSettings()->max_digestion_size_per_segment); + gin_index_stores[stream_name] = store; + } + skip_indices_aggregators.push_back(index_helper->createIndexAggregatorForPart(store)); + skip_index_accumulated_marks.push_back(0); } } @@ -268,6 +276,18 @@ void MergeTreeDataPartWriterOnDisk::calculateAndSerializeSkipIndices(const Block auto & stream = *skip_indices_streams[i]; WriteBuffer & marks_out = stream.compress_marks ? stream.marks_compressed_hashing : stream.marks_hashing; + GinIndexStorePtr store = nullptr; + if(dynamic_cast(&*index_helper) != nullptr) + { + String stream_name = index_helper->getFileName(); + auto it = gin_index_stores.find(stream_name); + if(it == gin_index_stores.cend()) + { + throw Exception("Index '" + stream_name + "' does not exist", ErrorCodes::LOGICAL_ERROR); + } + store = it->second; + } + for (const auto & granule : granules_to_write) { if (skip_index_accumulated_marks[i] == index_helper->index.granularity) @@ -278,7 +298,7 @@ void MergeTreeDataPartWriterOnDisk::calculateAndSerializeSkipIndices(const Block if (skip_indices_aggregators[i]->empty() && granule.mark_on_start) { - skip_indices_aggregators[i] = index_helper->createIndexAggregator(); + skip_indices_aggregators[i] = index_helper->createIndexAggregatorForPart(store); if (stream.compressed_hashing.offset() >= settings.min_compress_block_size) stream.compressed_hashing.next(); @@ -380,7 +400,11 @@ void MergeTreeDataPartWriterOnDisk::finishSkipIndicesSerialization(bool sync) if (sync) stream->sync(); } - + for (auto & store: gin_index_stores) + { + store.second->finalize(); + } + gin_index_stores.clear(); skip_indices_streams.clear(); skip_indices_aggregators.clear(); skip_index_accumulated_marks.clear(); diff --git a/src/Storages/MergeTree/MergeTreeDataPartWriterOnDisk.h b/src/Storages/MergeTree/MergeTreeDataPartWriterOnDisk.h index ab1adfe7f590..ed9a56419cb8 100644 --- a/src/Storages/MergeTree/MergeTreeDataPartWriterOnDisk.h +++ b/src/Storages/MergeTree/MergeTreeDataPartWriterOnDisk.h @@ -161,7 +161,8 @@ class MergeTreeDataPartWriterOnDisk : public IMergeTreeDataPartWriter /// Data is already written up to this mark. size_t current_mark = 0; - + + GinIndexStores gin_index_stores; private: void initSkipIndices(); void initPrimaryIndex(); diff --git a/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp b/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp index 65f54495b3c8..f31716dfcfa0 100644 --- a/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp +++ b/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -1686,6 +1687,14 @@ MarkRanges MergeTreeDataSelectExecutor::filterMarksUsingIndex( /// this variable is stored to avoid reading the same granule twice. MergeTreeIndexGranulePtr granule = nullptr; size_t last_index_mark = 0; + + PostingsCacheForStore cache_in_store; + + if (dynamic_cast(&*index_helper) != nullptr) + { + cache_in_store.store = GinIndexStoreFactory::instance().get(index_helper->getFileName(), part->volume->getDisk(), part->getFullRelativePath()); + } + for (size_t i = 0; i < ranges.size(); ++i) { const MarkRange & index_range = index_ranges[i]; @@ -1724,8 +1733,15 @@ MarkRanges MergeTreeDataSelectExecutor::filterMarksUsingIndex( } continue; } + auto gin_filter_condition = dynamic_cast(&*condition); + + bool result{false}; + if(!gin_filter_condition) + result = condition->mayBeTrueOnGranule(granule); + else + result = gin_filter_condition->mayBeTrueOnGranuleInPart(granule, cache_in_store); - if (!condition->mayBeTrueOnGranule(granule)) + if (!result) { ++granules_dropped; continue; diff --git a/src/Storages/MergeTree/MergeTreeIndexGin.cpp b/src/Storages/MergeTree/MergeTreeIndexGin.cpp new file mode 100644 index 000000000000..a0efaa78a5c3 --- /dev/null +++ b/src/Storages/MergeTree/MergeTreeIndexGin.cpp @@ -0,0 +1,794 @@ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + + +namespace DB +{ +namespace ErrorCodes +{ + extern const int LOGICAL_ERROR; + extern const int INCORRECT_QUERY; +} + +MergeTreeIndexGranuleGinFilter::MergeTreeIndexGranuleGinFilter( + const String & index_name_, + size_t columns_number, + const GinFilterParameters & params_) + : index_name(index_name_) + , params(params_) + , gin_filters( + columns_number, GinFilter(params)) + , has_elems(false) +{ +} + +void MergeTreeIndexGranuleGinFilter::serializeBinary(WriteBuffer & ostr) const +{ + if (empty()) + throw Exception(ErrorCodes::LOGICAL_ERROR, "Attempt to write empty fulltext index {}.", backQuote(index_name)); + + const auto & size_type = DataTypePtr(std::make_shared()); + auto size_serialization = size_type->getDefaultSerialization(); + + for (const auto & gin_filter : gin_filters) + { + size_t filterSize = gin_filter.getFilter().size(); + size_serialization->serializeBinary(filterSize, ostr); + ostr.write(reinterpret_cast(&gin_filter.getFilter()[0]), filterSize * sizeof(RowIDRange)); + } +} + +void MergeTreeIndexGranuleGinFilter::deserializeBinary(ReadBuffer & istr, MergeTreeIndexVersion version) +{ + if (version != 1) + throw Exception(ErrorCodes::LOGICAL_ERROR, "Unknown index version {}.", version); + + Field field_rows; + const auto & size_type = DataTypePtr(std::make_shared()); + + for (auto & gin_filter : gin_filters) + { + size_type->getDefaultSerialization()->deserializeBinary(field_rows, istr); + size_t filterSize = field_rows.get(); + + if (filterSize == 0) + continue; + + gin_filter.getFilter().assign(filterSize, {}); + istr.read(reinterpret_cast(&gin_filter.getFilter()[0]), filterSize * sizeof(RowIDRange)); + } + has_elems = true; +} + + +MergeTreeIndexAggregatorGinFilter::MergeTreeIndexAggregatorGinFilter( + GinIndexStorePtr store_, + const Names & index_columns_, + const String & index_name_, + const GinFilterParameters & params_, + TokenExtractorPtr token_extractor_) + : store(store_) + , index_columns(index_columns_) + , index_name (index_name_) + , params(params_) + , token_extractor(token_extractor_) + , granule( + std::make_shared( + index_name, index_columns.size(), params)) +{ +} + +MergeTreeIndexGranulePtr MergeTreeIndexAggregatorGinFilter::getGranuleAndReset() +{ + auto new_granule = std::make_shared( + index_name, index_columns.size(), params); + new_granule.swap(granule); + return new_granule; +} + +void MergeTreeIndexAggregatorGinFilter::addToGinFilter(UInt32 rowID, const char* data, size_t length, GinFilter& gin_filter) +{ + size_t cur = 0; + size_t token_start = 0; + size_t token_len = 0; + + int offset = 0; + while (cur < length && token_extractor->nextInStringPadded(data, length, &cur, &token_start, &token_len)) + { + gin_filter.add(data + token_start, token_len, rowID, store); + offset++; + } +} + +void MergeTreeIndexAggregatorGinFilter::update(const Block & block, size_t * pos, size_t limit) +{ + if (*pos >= block.rows()) + throw Exception( + "The provided position is not less than the number of block rows. Position: " + + toString(*pos) + ", Block rows: " + toString(block.rows()) + ".", ErrorCodes::LOGICAL_ERROR); + + size_t rows_read = std::min(limit, block.rows() - *pos); + auto rowID = store->getNextRowIDRange(rows_read); + auto startRowID = rowID; + + for (size_t col = 0; col < index_columns.size(); ++col) + { + const auto & column_with_type = block.getByName(index_columns[col]); + const auto & column = column_with_type.column; + size_t current_position = *pos; + + bool need_to_write = false; + if (isArray(column_with_type.type)) + { + const auto & column_array = assert_cast(*column); + const auto & column_offsets = column_array.getOffsets(); + const auto & column_key = column_array.getData(); + + for (size_t i = 0; i < rows_read; ++i) + { + size_t element_start_row = column_offsets[current_position - 1]; + size_t elements_size = column_offsets[current_position] - element_start_row; + + for (size_t row_num = 0; row_num < elements_size; ++row_num) + { + auto ref = column_key.getDataAt(element_start_row + row_num); + addToGinFilter(rowID, ref.data, ref.size, granule->gin_filters[col]); + store->addSize(ref.size); + } + current_position += 1; + rowID++; + + if (store->needToWrite()) + need_to_write = true; + } + } + else + { + for (size_t i = 0; i < rows_read; ++i) + { + auto ref = column->getDataAt(current_position + i); + addToGinFilter(rowID, ref.data, ref.size, granule->gin_filters[col]); + store->addSize(ref.size); + rowID++; + if (store->needToWrite()) + need_to_write = true; + } + } + granule->gin_filters[col].addRowRangeToGinFilter(store->getCurrentSegmentID(), startRowID, startRowID + rows_read - 1); + if (need_to_write) + { + store->writeSegment(); + } + } + + granule->has_elems = true; + *pos += rows_read; +} + +MergeTreeConditionGinFilter::MergeTreeConditionGinFilter( + const SelectQueryInfo & query_info, + ContextPtr context, + const Block & index_sample_block, + const GinFilterParameters & params_, + TokenExtractorPtr token_extactor_) + : index_columns(index_sample_block.getNames()) + , index_data_types(index_sample_block.getNamesAndTypesList().getTypes()) + , params(params_) + , token_extractor(token_extactor_) + , prepared_sets(query_info.sets) +{ + rpn = std::move( + RPNBuilder( + query_info, context, + [this] (const ASTPtr & node, ContextPtr /* context */, Block & block_with_constants, RPNElement & out) -> bool + { + return this->traverseAtomAST(node, block_with_constants, out); + }).extractRPN()); +} + +bool MergeTreeConditionGinFilter::alwaysUnknownOrTrue() const +{ + /// Check like in KeyCondition. + std::vector rpn_stack; + + for (const auto & element : rpn) + { + if (element.function == RPNElement::FUNCTION_UNKNOWN + || element.function == RPNElement::ALWAYS_TRUE) + { + rpn_stack.push_back(true); + } + else if (element.function == RPNElement::FUNCTION_EQUALS + || element.function == RPNElement::FUNCTION_NOT_EQUALS + || element.function == RPNElement::FUNCTION_HAS + || element.function == RPNElement::FUNCTION_IN + || element.function == RPNElement::FUNCTION_NOT_IN + || element.function == RPNElement::FUNCTION_MULTI_SEARCH + || element.function == RPNElement::ALWAYS_FALSE + || element.function == RPNElement::FUNCTION_LIKE) + { + rpn_stack.push_back(false); + } + else if (element.function == RPNElement::FUNCTION_NOT) + { + // do nothing + } + else if (element.function == RPNElement::FUNCTION_AND) + { + auto arg1 = rpn_stack.back(); + rpn_stack.pop_back(); + auto arg2 = rpn_stack.back(); + rpn_stack.back() = arg1 && arg2; + } + else if (element.function == RPNElement::FUNCTION_OR) + { + auto arg1 = rpn_stack.back(); + rpn_stack.pop_back(); + auto arg2 = rpn_stack.back(); + rpn_stack.back() = arg1 || arg2; + } + else + throw Exception("Unexpected function type in KeyCondition::RPNElement", ErrorCodes::LOGICAL_ERROR); + } + + return rpn_stack[0]; +} + +bool MergeTreeConditionGinFilter::mayBeTrueOnGranuleInPart(MergeTreeIndexGranulePtr idx_granule,[[maybe_unused]] PostingsCacheForStore &cache_in_store) const +{ + std::shared_ptr granule + = std::dynamic_pointer_cast(idx_granule); + if (!granule) + throw Exception( + "GinFilter index condition got a granule with the wrong type.", ErrorCodes::LOGICAL_ERROR); + + /// Check like in KeyCondition. + std::vector rpn_stack; + for (const auto & element : rpn) + { + if (element.function == RPNElement::FUNCTION_UNKNOWN) + { + rpn_stack.emplace_back(true, true); + } + else if (element.function == RPNElement::FUNCTION_EQUALS + || element.function == RPNElement::FUNCTION_NOT_EQUALS + || element.function == RPNElement::FUNCTION_HAS + || element.function == RPNElement::FUNCTION_LIKE) + { + rpn_stack.emplace_back(granule->gin_filters[element.key_column].contains(*element.gin_filter, cache_in_store), true); + + if (element.function == RPNElement::FUNCTION_NOT_EQUALS) + rpn_stack.back() = !rpn_stack.back(); + } + else if (element.function == RPNElement::FUNCTION_IN + || element.function == RPNElement::FUNCTION_NOT_IN) + { + std::vector result(element.set_gin_filters.back().size(), true); + + for (size_t column = 0; column < element.set_key_position.size(); ++column) + { + const size_t key_idx = element.set_key_position[column]; + + const auto & gin_filters = element.set_gin_filters[column]; + for (size_t row = 0; row < gin_filters.size(); ++row) + result[row] = result[row] && granule->gin_filters[key_idx].contains(gin_filters[row], cache_in_store); + } + + rpn_stack.emplace_back( + std::find(std::cbegin(result), std::cend(result), true) != std::end(result), true); + if (element.function == RPNElement::FUNCTION_NOT_IN) + rpn_stack.back() = !rpn_stack.back(); + } + else if (element.function == RPNElement::FUNCTION_MULTI_SEARCH) + { + std::vector result(element.set_gin_filters.back().size(), true); + + const auto & gin_filters = element.set_gin_filters[0]; + + for (size_t row = 0; row < gin_filters.size(); ++row) + result[row] = result[row] && granule->gin_filters[element.key_column].contains(gin_filters[row], cache_in_store); + + rpn_stack.emplace_back( + std::find(std::cbegin(result), std::cend(result), true) != std::end(result), true); + } + else if (element.function == RPNElement::FUNCTION_NOT) + { + rpn_stack.back() = !rpn_stack.back(); + } + else if (element.function == RPNElement::FUNCTION_AND) + { + auto arg1 = rpn_stack.back(); + rpn_stack.pop_back(); + auto arg2 = rpn_stack.back(); + rpn_stack.back() = arg1 & arg2; + } + else if (element.function == RPNElement::FUNCTION_OR) + { + auto arg1 = rpn_stack.back(); + rpn_stack.pop_back(); + auto arg2 = rpn_stack.back(); + rpn_stack.back() = arg1 | arg2; + } + else if (element.function == RPNElement::ALWAYS_FALSE) + { + rpn_stack.emplace_back(false, true); + } + else if (element.function == RPNElement::ALWAYS_TRUE) + { + rpn_stack.emplace_back(true, false); + } + else + throw Exception("Unexpected function type in GinFilterCondition::RPNElement", ErrorCodes::LOGICAL_ERROR); + } + + if (rpn_stack.size() != 1) + throw Exception("Unexpected stack size in GinFilterCondition::mayBeTrueOnGranule", ErrorCodes::LOGICAL_ERROR); + + return rpn_stack[0].can_be_true; +} + +bool MergeTreeConditionGinFilter::getKey(const std::string & key_column_name, size_t & key_column_num) +{ + auto it = std::find(index_columns.begin(), index_columns.end(), key_column_name); + if (it == index_columns.end()) + return false; + + key_column_num = static_cast(it - index_columns.begin()); + return true; +} + +bool MergeTreeConditionGinFilter::traverseAtomAST(const ASTPtr & node, Block & block_with_constants, RPNElement & out) +{ + { + Field const_value; + DataTypePtr const_type; + + if (KeyCondition::getConstant(node, block_with_constants, const_value, const_type)) + { + /// Check constant like in KeyCondition + if (const_value.getType() == Field::Types::UInt64 + || const_value.getType() == Field::Types::Int64 + || const_value.getType() == Field::Types::Float64) + { + /// Zero in all types is represented in memory the same way as in UInt64. + out.function = const_value.get() + ? RPNElement::ALWAYS_TRUE + : RPNElement::ALWAYS_FALSE; + + return true; + } + } + } + + if (const auto * function = node->as()) + { + if (!function->arguments) + return false; + + const ASTs & arguments = function->arguments->children; + + if (arguments.size() != 2) + return false; + + if (functionIsInOrGlobalInOperator(function->name)) + { + if (tryPrepareSetGinFilter(arguments, out)) + { + if (function->name == "notIn") + { + out.function = RPNElement::FUNCTION_NOT_IN; + return true; + } + else if (function->name == "in") + { + out.function = RPNElement::FUNCTION_IN; + return true; + } + } + } + else if (function->name == "equals" || + function->name == "notEquals" || + function->name == "has" || + function->name == "mapContains" || + function->name == "like" || + function->name == "notLike" || + function->name == "hasToken" || + function->name == "startsWith" || + function->name == "endsWith" || + function->name == "multiSearchAny") + { + Field const_value; + DataTypePtr const_type; + if (KeyCondition::getConstant(arguments[1], block_with_constants, const_value, const_type)) + { + if (traverseASTEquals(function->name, arguments[0], const_type, const_value, out)) + return true; + } + else if (KeyCondition::getConstant(arguments[0], block_with_constants, const_value, const_type) && (function->name == "equals" || function->name == "notEquals")) + { + if (traverseASTEquals(function->name, arguments[1], const_type, const_value, out)) + return true; + } + } + } + + return false; +} + +bool MergeTreeConditionGinFilter::traverseASTEquals( + const String & function_name, + const ASTPtr & key_ast, + const DataTypePtr & value_type, + const Field & value_field, + RPNElement & out) +{ + auto value_data_type = WhichDataType(value_type); + if (!value_data_type.isStringOrFixedString() && !value_data_type.isArray()) + return false; + + Field const_value = value_field; + + size_t key_column_num = 0; + bool key_exists = getKey(key_ast->getColumnName(), key_column_num); + bool map_key_exists = getKey(fmt::format("mapKeys({})", key_ast->getColumnName()), key_column_num); + + if (const auto * function = key_ast->as()) + { + if (function->name == "arrayElement") + { + /** Try to parse arrayElement for mapKeys index. + * It is important to ignore keys like column_map['Key'] = '' because if key does not exists in map + * we return default value for arrayElement. + * + * We cannot skip keys that does not exist in map if comparison is with default type value because + * that way we skip necessary granules where map key does not exists. + */ + if (value_field == value_type->getDefault()) + return false; + + const auto * column_ast_identifier = function->arguments.get()->children[0].get()->as(); + if (!column_ast_identifier) + return false; + + const auto & map_column_name = column_ast_identifier->name(); + + size_t map_keys_key_column_num = 0; + auto map_keys_index_column_name = fmt::format("mapKeys({})", map_column_name); + bool map_keys_exists = getKey(map_keys_index_column_name, map_keys_key_column_num); + + size_t map_values_key_column_num = 0; + auto map_values_index_column_name = fmt::format("mapValues({})", map_column_name); + bool map_values_exists = getKey(map_values_index_column_name, map_values_key_column_num); + + if (map_keys_exists) + { + auto & argument = function->arguments.get()->children[1]; + + if (const auto * literal = argument->as()) + { + auto element_key = literal->value; + const_value = element_key; + key_column_num = map_keys_key_column_num; + key_exists = true; + } + else + { + return false; + } + } + else if (map_values_exists) + { + key_column_num = map_values_key_column_num; + key_exists = true; + } + else + { + return false; + } + } + } + + if (!key_exists && !map_key_exists) + return false; + + if (map_key_exists && (function_name == "has" || function_name == "mapContains")) + { + out.key_column = key_column_num; + out.function = RPNElement::FUNCTION_HAS; + out.gin_filter = std::make_unique(params); + auto & value = const_value.get(); + token_extractor->stringToGinFilter(value.data(), value.size(), *out.gin_filter); + return true; + } + else if (function_name == "has") + { + out.key_column = key_column_num; + out.function = RPNElement::FUNCTION_HAS; + out.gin_filter = std::make_unique(params); + auto & value = const_value.get(); + token_extractor->stringToGinFilter(value.data(), value.size(), *out.gin_filter); + return true; + } + + if (function_name == "notEquals") + { + out.key_column = key_column_num; + out.function = RPNElement::FUNCTION_NOT_EQUALS; + out.gin_filter = std::make_unique(params); + const auto & value = const_value.get(); + token_extractor->stringToGinFilter(value.data(), value.size(), *out.gin_filter); + return true; + } + else if (function_name == "equals") + { + out.key_column = key_column_num; + out.function = RPNElement::FUNCTION_EQUALS; + out.gin_filter = std::make_unique(params); + const auto & value = const_value.get(); + token_extractor->stringToGinFilter(value.data(), value.size(), *out.gin_filter); + return true; + } + else if (function_name == "like") + { + out.key_column = key_column_num; + out.function = RPNElement::FUNCTION_LIKE; + out.gin_filter = std::make_unique(params); + const auto & value = const_value.get(); + token_extractor->stringLikeToGinFilter(value.data(), value.size(), *out.gin_filter); + return true; + } + else if (function_name == "notLike") + { + out.key_column = key_column_num; + out.function = RPNElement::FUNCTION_NOT_EQUALS; + out.gin_filter = std::make_unique(params); + const auto & value = const_value.get(); + token_extractor->stringLikeToGinFilter(value.data(), value.size(), *out.gin_filter); + return true; + } + else if (function_name == "hasToken") + { + out.key_column = key_column_num; + out.function = RPNElement::FUNCTION_EQUALS; + out.gin_filter = std::make_unique(params); + const auto & value = const_value.get(); + token_extractor->stringToGinFilter(value.data(), value.size(), *out.gin_filter); + return true; + } + else if (function_name == "startsWith") + { + out.key_column = key_column_num; + out.function = RPNElement::FUNCTION_EQUALS; + out.gin_filter = std::make_unique(params); + const auto & value = const_value.get(); + token_extractor->stringToGinFilter(value.data(), value.size(), *out.gin_filter); + return true; + } + else if (function_name == "endsWith") + { + out.key_column = key_column_num; + out.function = RPNElement::FUNCTION_EQUALS; + out.gin_filter = std::make_unique(params); + const auto & value = const_value.get(); + token_extractor->stringToGinFilter(value.data(), value.size(), *out.gin_filter); + return true; + } + else if (function_name == "multiSearchAny") + { + out.key_column = key_column_num; + out.function = RPNElement::FUNCTION_MULTI_SEARCH; + + /// 2d vector is not needed here but is used because already exists for FUNCTION_IN + std::vector> gin_filters; + gin_filters.emplace_back(); + for (const auto & element : const_value.get()) + { + if (element.getType() != Field::Types::String) + return false; + + gin_filters.back().emplace_back(params); + const auto & value = element.get(); + token_extractor->stringToGinFilter(value.data(), value.size(), gin_filters.back().back()); + } + out.set_gin_filters = std::move(gin_filters); + return true; + } + + return false; +} + +bool MergeTreeConditionGinFilter::tryPrepareSetGinFilter( + const ASTs & args, + RPNElement & out) +{ + const ASTPtr & left_arg = args[0]; + const ASTPtr & right_arg = args[1]; + + std::vector key_tuple_mapping; + DataTypes data_types; + + const auto * left_arg_tuple = typeid_cast(left_arg.get()); + if (left_arg_tuple && left_arg_tuple->name == "tuple") + { + const auto & tuple_elements = left_arg_tuple->arguments->children; + for (size_t i = 0; i < tuple_elements.size(); ++i) + { + size_t key = 0; + if (getKey(tuple_elements[i]->getColumnName(), key)) + { + key_tuple_mapping.emplace_back(i, key); + data_types.push_back(index_data_types[key]); + } + } + } + else + { + size_t key = 0; + if (getKey(left_arg->getColumnName(), key)) + { + key_tuple_mapping.emplace_back(0, key); + data_types.push_back(index_data_types[key]); + } + } + + if (key_tuple_mapping.empty()) + return false; + + PreparedSetKey set_key; + if (typeid_cast(right_arg.get()) || typeid_cast(right_arg.get())) + set_key = PreparedSetKey::forSubquery(*right_arg); + else + set_key = PreparedSetKey::forLiteral(*right_arg, data_types); + + auto set_it = prepared_sets.find(set_key); + if (set_it == prepared_sets.end()) + return false; + + const SetPtr & prepared_set = set_it->second; + if (!prepared_set->hasExplicitSetElements()) + return false; + + for (const auto & data_type : prepared_set->getDataTypes()) + if (data_type->getTypeId() != TypeIndex::String && data_type->getTypeId() != TypeIndex::FixedString) + return false; + + std::vector> gin_filters; + std::vector key_position; + + Columns columns = prepared_set->getSetElements(); + for (const auto & elem : key_tuple_mapping) + { + gin_filters.emplace_back(); + key_position.push_back(elem.key_index); + + size_t tuple_idx = elem.tuple_index; + const auto & column = columns[tuple_idx]; + for (size_t row = 0; row < prepared_set->getTotalRowCount(); ++row) + { + gin_filters.back().emplace_back(params); + auto ref = column->getDataAt(row); + gin_filters.back().back().setQueryString(ref.data, ref.size); + } + } + + out.set_key_position = std::move(key_position); + out.set_gin_filters = std::move(gin_filters); + + return true; +} + +MergeTreeIndexGranulePtr MergeTreeIndexGinFilter::createIndexGranule() const +{ + return std::make_shared(index.name, index.column_names.size(), params); +} + +MergeTreeIndexAggregatorPtr MergeTreeIndexGinFilter::createIndexAggregator() const +{ + /// should not be called: createIndexAggregatorForPart should be used + assert(false); + return nullptr; +} + +MergeTreeIndexAggregatorPtr MergeTreeIndexGinFilter::createIndexAggregatorForPart(const GinIndexStorePtr &store) const +{ + return std::make_shared(store, index.column_names, index.name, params, token_extractor.get()); +} + +MergeTreeIndexConditionPtr MergeTreeIndexGinFilter::createIndexCondition( + const SelectQueryInfo & query, ContextPtr context) const +{ + return std::make_shared(query, context, index.sample_block, params, token_extractor.get()); +}; + +bool MergeTreeIndexGinFilter::mayBenefitFromIndexForIn(const ASTPtr & node) const +{ + return std::find(std::cbegin(index.column_names), std::cend(index.column_names), node->getColumnName()) != std::cend(index.column_names); +} + +MergeTreeIndexPtr ginIndexCreator( + const IndexDescription & index) +{ + if (index.type == GinFilter::getName()) + { + size_t n = index.arguments.size() == 0 ? 0 : index.arguments[0].get(); + GinFilterParameters params(n); + + /// Use SplitTokenExtractor when n is 0, otherwise use NgramTokenExtractor + if(n > 0) + { + auto tokenizer = std::make_unique(n); + return std::make_shared(index, params, std::move(tokenizer)); + } + else + { + auto tokenizer = std::make_unique(); + return std::make_shared(index, params, std::move(tokenizer)); + } + } + else + { + throw Exception("Unknown index type: " + backQuote(index.name), ErrorCodes::LOGICAL_ERROR); + } +} + +void ginIndexValidator(const IndexDescription & index, bool /*attach*/) +{ + for (const auto & index_data_type : index.data_types) + { + WhichDataType data_type(index_data_type); + + if (data_type.isArray()) + { + const auto & gin_type = assert_cast(*index_data_type); + data_type = WhichDataType(gin_type.getNestedType()); + } + else if (data_type.isLowCarnality()) + { + const auto & low_cardinality = assert_cast(*index_data_type); + data_type = WhichDataType(low_cardinality.getDictionaryType()); + } + + if (!data_type.isString() && !data_type.isFixedString()) + throw Exception("Gin filter index can be used only with `String`, `FixedString`, `LowCardinality(String)`, `LowCardinality(FixedString)` column or Array with `String` or `FixedString` values column.", ErrorCodes::INCORRECT_QUERY); + } + + if(index.type != GinFilter::getName()) + throw Exception("Unknown index type: " + backQuote(index.name), ErrorCodes::LOGICAL_ERROR); + + if (index.arguments.size() > 1) + throw Exception("Gin index must have zero or one argument.", ErrorCodes::INCORRECT_QUERY); + + if (index.arguments.size() == 1 and index.arguments[0].getType() != Field::Types::UInt64) + throw Exception("Gin index argument must be positive integer.", ErrorCodes::INCORRECT_QUERY); + + size_t ngrams = index.arguments.size() == 0 ? 0 : index.arguments[0].get(); + + /// Just validate + GinFilterParameters params(ngrams); +} + +} diff --git a/src/Storages/MergeTree/MergeTreeIndexGin.h b/src/Storages/MergeTree/MergeTreeIndexGin.h new file mode 100644 index 000000000000..5852eea347a8 --- /dev/null +++ b/src/Storages/MergeTree/MergeTreeIndexGin.h @@ -0,0 +1,186 @@ +#pragma once +#include +#include + +#include + +#include +#include +#include +#include + +namespace DB +{ +struct MergeTreeIndexGranuleGinFilter final : public IMergeTreeIndexGranule +{ + explicit MergeTreeIndexGranuleGinFilter( + const String & index_name_, + size_t columns_number, + const GinFilterParameters & params_); + + ~MergeTreeIndexGranuleGinFilter() override = default; + + void serializeBinary(WriteBuffer & ostr) const override; + void deserializeBinary(ReadBuffer & istr, MergeTreeIndexVersion version) override; + + bool empty() const override { return !has_elems; } + + String index_name; + GinFilterParameters params; + + std::vector gin_filters; + bool has_elems; +}; + +using MergeTreeIndexGranuleGinFilterPtr = std::shared_ptr; + +struct MergeTreeIndexAggregatorGinFilter final : IMergeTreeIndexAggregator +{ + explicit MergeTreeIndexAggregatorGinFilter( + GinIndexStorePtr store_, + const Names & index_columns_, + const String & index_name_, + const GinFilterParameters & params_, + TokenExtractorPtr token_extractor_); + + ~MergeTreeIndexAggregatorGinFilter() override = default; + + bool empty() const override { return !granule || granule->empty(); } + MergeTreeIndexGranulePtr getGranuleAndReset() override; + + void update(const Block & block, size_t * pos, size_t limit) override; + + void addToGinFilter(UInt32 rowID, const char* data, size_t length, GinFilter& gin_filter); + + GinIndexStorePtr store; + Names index_columns; + String index_name; + GinFilterParameters params; + TokenExtractorPtr token_extractor; + + MergeTreeIndexGranuleGinFilterPtr granule; +}; + + +class MergeTreeConditionGinFilter final : public IMergeTreeIndexCondition +{ +public: + MergeTreeConditionGinFilter( + const SelectQueryInfo & query_info, + ContextPtr context, + const Block & index_sample_block, + const GinFilterParameters & params_, + TokenExtractorPtr token_extactor_); + + ~MergeTreeConditionGinFilter() override = default; + + bool alwaysUnknownOrTrue() const override; + bool mayBeTrueOnGranule([[maybe_unused]]MergeTreeIndexGranulePtr idx_granule) const override + { + /// should call mayBeTrueOnGranuleInPart instead + assert(false); + return false; + } + bool mayBeTrueOnGranuleInPart(MergeTreeIndexGranulePtr idx_granule, [[maybe_unused]] PostingsCacheForStore& cache_store) const; +private: + struct KeyTuplePositionMapping + { + KeyTuplePositionMapping(size_t tuple_index_, size_t key_index_) : tuple_index(tuple_index_), key_index(key_index_) {} + + size_t tuple_index; + size_t key_index; + }; + /// Uses RPN like KeyCondition + struct RPNElement + { + enum Function + { + /// Atoms of a Boolean expression. + FUNCTION_LIKE, + FUNCTION_EQUALS, + FUNCTION_NOT_EQUALS, + FUNCTION_HAS, + FUNCTION_IN, + FUNCTION_NOT_IN, + FUNCTION_MULTI_SEARCH, + FUNCTION_UNKNOWN, /// Can take any value. + /// Operators of the logical expression. + FUNCTION_NOT, + FUNCTION_AND, + FUNCTION_OR, + /// Constants + ALWAYS_FALSE, + ALWAYS_TRUE, + }; + + RPNElement( /// NOLINT + Function function_ = FUNCTION_UNKNOWN, size_t key_column_ = 0, std::unique_ptr && const_gin_filter_ = nullptr) + : function(function_), key_column(key_column_), gin_filter(std::move(const_gin_filter_)) {} + + Function function = FUNCTION_UNKNOWN; + /// For FUNCTION_EQUALS, FUNCTION_NOT_EQUALS and FUNCTION_MULTI_SEARCH + size_t key_column; + + /// For FUNCTION_EQUALS, FUNCTION_NOT_EQUALS + std::unique_ptr gin_filter; + + /// For FUNCTION_IN, FUNCTION_NOT_IN and FUNCTION_MULTI_SEARCH + std::vector> set_gin_filters; + + /// For FUNCTION_IN and FUNCTION_NOT_IN + std::vector set_key_position; + }; + + using RPN = std::vector; + + bool traverseAtomAST(const ASTPtr & node, Block & block_with_constants, RPNElement & out); + + bool traverseASTEquals( + const String & function_name, + const ASTPtr & key_ast, + const DataTypePtr & value_type, + const Field & value_field, + RPNElement & out); + + bool getKey(const std::string & key_column_name, size_t & key_column_num); + bool tryPrepareSetGinFilter(const ASTs & args, RPNElement & out); + + static bool createFunctionEqualsCondition( + RPNElement & out, const Field & value, const GinFilterParameters & params, TokenExtractorPtr token_extractor); + + Names index_columns; + DataTypes index_data_types; + GinFilterParameters params; + TokenExtractorPtr token_extractor; + RPN rpn; + /// Sets from syntax analyzer. + PreparedSets prepared_sets; +}; + +class MergeTreeIndexGinFilter final : public IMergeTreeIndex +{ +public: + MergeTreeIndexGinFilter( + const IndexDescription & index_, + const GinFilterParameters & params_, + std::unique_ptr && token_extractor_) + : IMergeTreeIndex(index_) + , params(params_) + , token_extractor(std::move(token_extractor_)) {} + + ~MergeTreeIndexGinFilter() override = default; + + MergeTreeIndexGranulePtr createIndexGranule() const override; + MergeTreeIndexAggregatorPtr createIndexAggregator() const override; + MergeTreeIndexAggregatorPtr createIndexAggregatorForPart(const GinIndexStorePtr &partIndexInfo) const override; + MergeTreeIndexConditionPtr createIndexCondition( + const SelectQueryInfo & query, ContextPtr context) const override; + + bool mayBenefitFromIndexForIn(const ASTPtr & node) const override; + + GinFilterParameters params; + /// Function for selecting next token. + std::unique_ptr token_extractor; +}; + +} diff --git a/src/Storages/MergeTree/MergeTreeIndices.cpp b/src/Storages/MergeTree/MergeTreeIndices.cpp index eeeef27699f7..ef5e31bbd918 100644 --- a/src/Storages/MergeTree/MergeTreeIndices.cpp +++ b/src/Storages/MergeTree/MergeTreeIndices.cpp @@ -1,9 +1,9 @@ #include +#include #include #include #include #include - #include #include @@ -106,6 +106,8 @@ MergeTreeIndexFactory::MergeTreeIndexFactory() registerCreator("annoy", annoyIndexCreator); registerValidator("annoy", annoyIndexValidator); #endif + registerCreator("gin", ginIndexCreator); + registerValidator("gin", ginIndexValidator); } MergeTreeIndexFactory & MergeTreeIndexFactory::instance() diff --git a/src/Storages/MergeTree/MergeTreeIndices.h b/src/Storages/MergeTree/MergeTreeIndices.h index 6a671c31944e..4ed064793642 100644 --- a/src/Storages/MergeTree/MergeTreeIndices.h +++ b/src/Storages/MergeTree/MergeTreeIndices.h @@ -1,11 +1,14 @@ #pragma once #include +#include #include #include #include #include +#include #include +#include #include #include #include @@ -14,6 +17,8 @@ #include #include +#include + constexpr auto INDEX_FILE_PREFIX = "skp_idx_"; namespace DB @@ -162,6 +167,11 @@ struct IMergeTreeIndex virtual MergeTreeIndexAggregatorPtr createIndexAggregator() const = 0; + virtual MergeTreeIndexAggregatorPtr createIndexAggregatorForPart([[maybe_unused]]const GinIndexStorePtr &store) const + { + return createIndexAggregator(); + } + virtual MergeTreeIndexConditionPtr createIndexCondition( const SelectQueryInfo & query_info, ContextPtr context) const = 0; @@ -228,5 +238,7 @@ void hypothesisIndexValidator(const IndexDescription & index, bool attach); MergeTreeIndexPtr annoyIndexCreator(const IndexDescription & index); void annoyIndexValidator(const IndexDescription & index, bool attach); #endif +MergeTreeIndexPtr ginIndexCreator(const IndexDescription& index); +void ginIndexValidator(const IndexDescription& index, bool attach); } diff --git a/src/Storages/MergeTree/MergeTreeSettings.h b/src/Storages/MergeTree/MergeTreeSettings.h index 82a0a04257be..651e7fb1e361 100644 --- a/src/Storages/MergeTree/MergeTreeSettings.h +++ b/src/Storages/MergeTree/MergeTreeSettings.h @@ -25,6 +25,7 @@ struct Settings; M(UInt64, min_compress_block_size, 0, "When granule is written, compress the data in buffer if the size of pending uncompressed data is larger or equal than the specified threshold. If this setting is not set, the corresponding global setting is used.", 0) \ M(UInt64, max_compress_block_size, 0, "Compress the pending uncompressed data in buffer if its size is larger or equal than the specified threshold. Block of data will be compressed even if the current granule is not finished. If this setting is not set, the corresponding global setting is used.", 0) \ M(UInt64, index_granularity, 8192, "How many rows correspond to one primary key value.", 0) \ + M(UInt64, max_digestion_size_per_segment, 1024 * 1024 * 256, "Max number of bytes to digest per segment to build GIN index.", 0) \ \ /** Data storing format settings. */ \ M(UInt64, min_bytes_for_wide_part, 10485760, "Minimal uncompressed size in bytes to create part in wide format instead of compact", 0) \ From e58f0ef3d2f553e5b99dece698f3a3b6c79c507b Mon Sep 17 00:00:00 2001 From: HarryLeeIBM Date: Fri, 24 Jun 2022 15:15:09 -0700 Subject: [PATCH 02/42] Remove roaring bmp cleanup code which can cause memory leak --- src/Storages/MergeTree/GinIndexStore.cpp | 6 ------ src/Storages/MergeTree/GinIndexStore.h | 3 --- 2 files changed, 9 deletions(-) diff --git a/src/Storages/MergeTree/GinIndexStore.cpp b/src/Storages/MergeTree/GinIndexStore.cpp index b16a36438f2f..dc8865daaf47 100644 --- a/src/Storages/MergeTree/GinIndexStore.cpp +++ b/src/Storages/MergeTree/GinIndexStore.cpp @@ -90,11 +90,6 @@ UInt64 GinIndexPostingsBuilder::serialize(WriteBuffer &buffer) const return encoding_length; } -void GinIndexPostingsBuilder::clear() -{ - roaring::api::roaring_bitmap_init_cleared(&bmp.roaring); -} - GinIndexPostingsListPtr GinIndexPostingsBuilder::deserialize(ReadBuffer &buffer) { char postings_list_size{0}; @@ -255,7 +250,6 @@ void GinIndexStore::writeSegment() for (const auto& [token, postings_list] : token_postings_list_pairs) { auto encoding_length = postings_list->serialize(*postings_file_stream); - postings_list->clear(); encoding_lengths[current_index++] = encoding_length; current_segment.postings_start_offset += encoding_length; diff --git a/src/Storages/MergeTree/GinIndexStore.h b/src/Storages/MergeTree/GinIndexStore.h index 02406d3bc7c6..5f26baa0d24b 100644 --- a/src/Storages/MergeTree/GinIndexStore.h +++ b/src/Storages/MergeTree/GinIndexStore.h @@ -65,9 +65,6 @@ struct GinIndexPostingsBuilder /// Deserialize the postings list data from given ReadBuffer, return a pointer to the GinIndexPostingsList created by deserialization static GinIndexPostingsListPtr deserialize(ReadBuffer &buffer); - - /// Clear Roaring bmp - void clear(); }; using GinIndexPostingsBuilderPtr = std::shared_ptr; From 4b29c02f3557de505430fe160d6dc54929bd4493 Mon Sep 17 00:00:00 2001 From: Harry-Lee Date: Sat, 25 Jun 2022 14:20:07 -0400 Subject: [PATCH 03/42] Remove useless FUNCTION_LIKE --- src/Storages/MergeTree/MergeTreeIndexGin.cpp | 8 +++----- src/Storages/MergeTree/MergeTreeIndexGin.h | 1 - 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/Storages/MergeTree/MergeTreeIndexGin.cpp b/src/Storages/MergeTree/MergeTreeIndexGin.cpp index a0efaa78a5c3..bbe1c59df6d6 100644 --- a/src/Storages/MergeTree/MergeTreeIndexGin.cpp +++ b/src/Storages/MergeTree/MergeTreeIndexGin.cpp @@ -228,8 +228,7 @@ bool MergeTreeConditionGinFilter::alwaysUnknownOrTrue() const || element.function == RPNElement::FUNCTION_IN || element.function == RPNElement::FUNCTION_NOT_IN || element.function == RPNElement::FUNCTION_MULTI_SEARCH - || element.function == RPNElement::ALWAYS_FALSE - || element.function == RPNElement::FUNCTION_LIKE) + || element.function == RPNElement::ALWAYS_FALSE) { rpn_stack.push_back(false); } @@ -276,8 +275,7 @@ bool MergeTreeConditionGinFilter::mayBeTrueOnGranuleInPart(MergeTreeIndexGranule } else if (element.function == RPNElement::FUNCTION_EQUALS || element.function == RPNElement::FUNCTION_NOT_EQUALS - || element.function == RPNElement::FUNCTION_HAS - || element.function == RPNElement::FUNCTION_LIKE) + || element.function == RPNElement::FUNCTION_HAS) { rpn_stack.emplace_back(granule->gin_filters[element.key_column].contains(*element.gin_filter, cache_in_store), true); @@ -555,7 +553,7 @@ bool MergeTreeConditionGinFilter::traverseASTEquals( else if (function_name == "like") { out.key_column = key_column_num; - out.function = RPNElement::FUNCTION_LIKE; + out.function = RPNElement::FUNCTION_EQUALS; out.gin_filter = std::make_unique(params); const auto & value = const_value.get(); token_extractor->stringLikeToGinFilter(value.data(), value.size(), *out.gin_filter); diff --git a/src/Storages/MergeTree/MergeTreeIndexGin.h b/src/Storages/MergeTree/MergeTreeIndexGin.h index 5852eea347a8..acb79e2bd81b 100644 --- a/src/Storages/MergeTree/MergeTreeIndexGin.h +++ b/src/Storages/MergeTree/MergeTreeIndexGin.h @@ -96,7 +96,6 @@ class MergeTreeConditionGinFilter final : public IMergeTreeIndexCondition enum Function { /// Atoms of a Boolean expression. - FUNCTION_LIKE, FUNCTION_EQUALS, FUNCTION_NOT_EQUALS, FUNCTION_HAS, From 91b79b26e38eae70f961b9367afecb9541e21f86 Mon Sep 17 00:00:00 2001 From: Larry Luo Date: Tue, 28 Jun 2022 20:23:36 -0700 Subject: [PATCH 04/42] Use DataPartStorage in GinIndexStore --- .../MergeTree/DataPartStorageOnDisk.cpp | 9 ++++ .../MergeTree/DataPartStorageOnDisk.h | 5 ++ src/Storages/MergeTree/GinIndexStore.cpp | 50 ++++++++++--------- src/Storages/MergeTree/GinIndexStore.h | 20 ++++---- src/Storages/MergeTree/IDataPartStorage.h | 3 ++ src/Storages/MergeTree/IMergeTreeDataPart.cpp | 2 +- .../MergeTreeDataPartWriterOnDisk.cpp | 2 +- .../MergeTree/MergeTreeDataSelectExecutor.cpp | 2 +- 8 files changed, 56 insertions(+), 37 deletions(-) diff --git a/src/Storages/MergeTree/DataPartStorageOnDisk.cpp b/src/Storages/MergeTree/DataPartStorageOnDisk.cpp index 215d6034a531..286a17ee056a 100644 --- a/src/Storages/MergeTree/DataPartStorageOnDisk.cpp +++ b/src/Storages/MergeTree/DataPartStorageOnDisk.cpp @@ -741,6 +741,15 @@ std::unique_ptr DataPartStorageOnDisk::writeFile( return volume->getDisk()->writeFile(fs::path(root_path) / part_dir / name, buf_size, WriteMode::Rewrite, settings); } +std::unique_ptr DataPartStorageBuilderOnDisk::writeFile( + const String & name, + size_t buf_size, + WriteMode mode, + const WriteSettings & settings) +{ + return volume->getDisk()->writeFile(fs::path(root_path) / part_dir / name, buf_size, mode, settings); +} + std::unique_ptr DataPartStorageOnDisk::writeTransactionFile(WriteMode mode) const { return volume->getDisk()->writeFile(fs::path(root_path) / part_dir / "txn_version.txt", 256, mode); diff --git a/src/Storages/MergeTree/DataPartStorageOnDisk.h b/src/Storages/MergeTree/DataPartStorageOnDisk.h index fd408af9cf1b..6080102b1390 100644 --- a/src/Storages/MergeTree/DataPartStorageOnDisk.h +++ b/src/Storages/MergeTree/DataPartStorageOnDisk.h @@ -112,6 +112,11 @@ class DataPartStorageOnDisk final : public IDataPartStorage void createFile(const String & name) override; void moveFile(const String & from_name, const String & to_name) override; void replaceFile(const String & from_name, const String & to_name) override; + std::unique_ptr writeFile( + const String & path, + size_t buf_size, + DB::WriteMode mode, + const WriteSettings & settings) override; void removeFile(const String & name) override; void removeFileIfExists(const String & name) override; diff --git a/src/Storages/MergeTree/GinIndexStore.cpp b/src/Storages/MergeTree/GinIndexStore.cpp index dc8865daaf47..bb03cbc9c412 100644 --- a/src/Storages/MergeTree/GinIndexStore.cpp +++ b/src/Storages/MergeTree/GinIndexStore.cpp @@ -125,17 +125,17 @@ GinIndexPostingsListPtr GinIndexPostingsBuilder::deserialize(ReadBuffer &buffer) bool GinIndexStore::exists() const { - String id_file_name = part_path + name + ".gin_sid"; - return disk->exists(id_file_name); + String id_file_name = getName() + ".gin_sid"; + return storage->exists(id_file_name); } UInt32 GinIndexStore::getNextIDRange(const String& file_name, size_t n) { std::lock_guard guard{gin_index_store_mutex}; - if (!disk->exists(file_name)) + if (!storage->exists(file_name)) { - std::unique_ptr ostr = this->disk->writeFile(file_name); + std::unique_ptr ostr = this->data_part_storage_builder->writeFile(file_name, DBMS_DEFAULT_BUFFER_SIZE, {}); const auto& int_type = DB::DataTypePtr(std::make_shared()); auto size_serialization = int_type->getDefaultSerialization(); @@ -146,7 +146,7 @@ UInt32 GinIndexStore::getNextIDRange(const String& file_name, size_t n) /// read id in file UInt32 result = 0; { - std::unique_ptr istr = this->disk->readFile(file_name); + std::unique_ptr istr = this->storage->readFile(file_name, {}, std::nullopt, std::nullopt); Field field_rows; const auto& size_type = DB::DataTypePtr(std::make_shared()); @@ -157,7 +157,7 @@ UInt32 GinIndexStore::getNextIDRange(const String& file_name, size_t n) } //save result+n { - std::unique_ptr ostr = this->disk->writeFile(file_name); + std::unique_ptr ostr = this->data_part_storage_builder->writeFile(file_name, DBMS_DEFAULT_BUFFER_SIZE, {}); const auto& int_type = DB::DataTypePtr(std::make_shared()); auto size_serialization = int_type->getDefaultSerialization(); @@ -176,17 +176,18 @@ UInt32 GinIndexStore::getNextRowIDRange(size_t n) UInt32 GinIndexStore::getNextSegmentID() { - String sid_file_name = part_path + name + ".gin_sid"; + String sid_file_name = getName() + ".gin_sid"; return getNextIDRange(sid_file_name, 1); } + UInt32 GinIndexStore::getSegmentNum() { - String sid_file_name = part_path + name + ".gin_sid"; - if (!disk->exists(sid_file_name)) + String sid_file_name = getName() + ".gin_sid"; + if (!storage->exists(sid_file_name)) return 0; Int32 result = 0; { - std::unique_ptr istr = this->disk->readFile(sid_file_name); + std::unique_ptr istr = this->storage->readFile(sid_file_name, {}, std::nullopt, std::nullopt); Field field_rows; const auto& size_type = DB::DataTypePtr(std::make_shared()); @@ -214,13 +215,13 @@ void GinIndexStore::finalize() void GinIndexStore::init_file_streams() { - String segment_file_name = part_path + name + ".gin_seg"; - String item_dict_file_name = part_path + name + ".gin_dict"; - String postings_file_name = part_path + name + ".gin_post"; + String segment_file_name = getName() + ".gin_seg"; + String item_dict_file_name = getName() + ".gin_dict"; + String postings_file_name = getName() + ".gin_post"; - segment_file_stream = disk->writeFile(segment_file_name, DBMS_DEFAULT_BUFFER_SIZE, WriteMode::Append, {}); - term_dict_file_stream = disk->writeFile(item_dict_file_name, DBMS_DEFAULT_BUFFER_SIZE, WriteMode::Append, {}); - postings_file_stream = disk->writeFile(postings_file_name, DBMS_DEFAULT_BUFFER_SIZE, WriteMode::Append, {}); + segment_file_stream = data_part_storage_builder->writeFile(segment_file_name, DBMS_DEFAULT_BUFFER_SIZE, WriteMode::Append, {}); + term_dict_file_stream = data_part_storage_builder->writeFile(item_dict_file_name, DBMS_DEFAULT_BUFFER_SIZE, WriteMode::Append, {}); + postings_file_stream = data_part_storage_builder->writeFile(postings_file_name, DBMS_DEFAULT_BUFFER_SIZE, WriteMode::Append, {}); } void GinIndexStore::writeSegment() @@ -290,13 +291,13 @@ void GinIndexStore::writeSegment() void GinIndexStoreReader::init_file_streams() { - String segment_file_name = store->part_path + store->name + ".gin_seg"; - String item_dict_file_name = store->part_path + store->name + ".gin_dict"; - String postings_file_name = store->part_path + store->name + ".gin_post"; + String segment_file_name = store->getName() + ".gin_seg"; + String item_dict_file_name = store->getName() + ".gin_dict"; + String postings_file_name = store->getName() + ".gin_post"; - segment_file_stream = store->disk->readFile(segment_file_name); - term_dict_file_stream = store->disk->readFile(item_dict_file_name); - postings_file_stream = store->disk->readFile(postings_file_name); + segment_file_stream = store->storage->readFile(segment_file_name, {}, std::nullopt, std::nullopt); + term_dict_file_stream = store->storage->readFile(item_dict_file_name, {}, std::nullopt, std::nullopt); + postings_file_stream = store->storage->readFile(postings_file_name, {}, std::nullopt, std::nullopt); } void GinIndexStoreReader::readSegments() { @@ -388,8 +389,9 @@ GinIndexStoreFactory& GinIndexStoreFactory::instance() return instance; } -GinIndexStorePtr GinIndexStoreFactory::get(const String& name, DiskPtr disk_, const String& part_path_) +GinIndexStorePtr GinIndexStoreFactory::get(const String& name, DataPartStoragePtr storage_) { + const String& part_path_ = storage_->getRelativePath(); std::lock_guard lock(stores_mutex); String key = name + String(":")+part_path_; @@ -398,7 +400,7 @@ GinIndexStorePtr GinIndexStoreFactory::get(const String& name, DiskPtr disk_, co if (it == stores.cend()) { GinIndexStorePtr store = std::make_shared(name); - store->SetDiskAndPath(disk_, part_path_); + store->SetStorage(storage_); if (!store->exists()) throw Exception("Index '" + name + "' does not exist", ErrorCodes::LOGICAL_ERROR); diff --git a/src/Storages/MergeTree/GinIndexStore.h b/src/Storages/MergeTree/GinIndexStore.h index 5f26baa0d24b..7d580b4c946e 100644 --- a/src/Storages/MergeTree/GinIndexStore.h +++ b/src/Storages/MergeTree/GinIndexStore.h @@ -10,7 +10,7 @@ #include #include #include - +#include namespace DB { @@ -103,10 +103,10 @@ class GinIndexStore : name(name_) { } - GinIndexStore(const String& name_, DiskPtr disk_, const String& part_path_, UInt64 max_digestion_size_) + GinIndexStore(const String& name_, DataPartStoragePtr storage_, DataPartStorageBuilderPtr data_part_storage_builder_, UInt64 max_digestion_size_) : name(name_), - disk(disk_), - part_path(part_path_), + storage(storage_), + data_part_storage_builder(data_part_storage_builder_), max_digestion_size(max_digestion_size_) { } @@ -126,10 +126,9 @@ class GinIndexStore using GinIndexStorePtr = std::shared_ptr; - void SetDiskAndPath(DiskPtr disk_, const String& part_path_) + void SetStorage(DataPartStoragePtr storage_) { - disk = disk_; - part_path = part_path_; + storage = storage_; } GinIndexPostingsBuilderContainer& getPostings() { return current_postings; } @@ -149,14 +148,15 @@ class GinIndexStore void writeSegment(); String getName() const {return name;} + private: void init_file_streams(); private: friend class GinIndexStoreReader; String name; - DiskPtr disk; - String part_path; + DataPartStoragePtr storage; + DataPartStorageBuilderPtr data_part_storage_builder; std::mutex gin_index_store_mutex; @@ -208,7 +208,7 @@ class GinIndexStoreFactory : private boost::noncopyable static GinIndexStoreFactory& instance(); /// Get GinIndexStore by using index name, disk and part_path (which are combined to create key in stores) - GinIndexStorePtr get(const String& name, DiskPtr disk_, const String& part_path_); + GinIndexStorePtr get(const String& name, DataPartStoragePtr storage_); /// Remove all GinIndexStores which are under the same part_path void remove(const String& part_path); diff --git a/src/Storages/MergeTree/IDataPartStorage.h b/src/Storages/MergeTree/IDataPartStorage.h index d7c0c9c76e3f..eea1e120e934 100644 --- a/src/Storages/MergeTree/IDataPartStorage.h +++ b/src/Storages/MergeTree/IDataPartStorage.h @@ -1,5 +1,6 @@ #pragma once #include +#include #include #include #include @@ -224,6 +225,8 @@ class IDataPartStorage : public boost::noncopyable virtual void createFile(const String & name) = 0; virtual void moveFile(const String & from_name, const String & to_name) = 0; virtual void replaceFile(const String & from_name, const String & to_name) = 0; + virtual std::unique_ptr writeFile(const String & name, size_t buf_size, const WriteSettings & settings) = 0; + virtual std::unique_ptr writeFile(const String & name, size_t buf_size, WriteMode mode, const WriteSettings & settings) = 0; virtual void removeFile(const String & name) = 0; virtual void removeFileIfExists(const String & name) = 0; diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.cpp b/src/Storages/MergeTree/IMergeTreeDataPart.cpp index d6f58dda7330..4a78d7555ce7 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.cpp +++ b/src/Storages/MergeTree/IMergeTreeDataPart.cpp @@ -1673,7 +1673,7 @@ void IMergeTreeDataPart::remove() metadata_manager->deleteAll(false); metadata_manager->assertAllDeleted(false); - GinIndexStoreFactory::instance().remove(getFullRelativePath()); + GinIndexStoreFactory::instance().remove(data_part_storage->getRelativePath()); std::list projection_checksums; diff --git a/src/Storages/MergeTree/MergeTreeDataPartWriterOnDisk.cpp b/src/Storages/MergeTree/MergeTreeDataPartWriterOnDisk.cpp index 199b36909002..6cc4e22844c9 100644 --- a/src/Storages/MergeTree/MergeTreeDataPartWriterOnDisk.cpp +++ b/src/Storages/MergeTree/MergeTreeDataPartWriterOnDisk.cpp @@ -216,7 +216,7 @@ void MergeTreeDataPartWriterOnDisk::initSkipIndices() GinIndexStorePtr store = nullptr; if(dynamic_cast(&*index_helper) != nullptr) { - store = std::make_shared(stream_name, data_part->volume->getDisk(), part_path, storage.getSettings()->max_digestion_size_per_segment); + store = std::make_shared(stream_name, data_part->data_part_storage, data_part_storage_builder, storage.getSettings()->max_digestion_size_per_segment); gin_index_stores[stream_name] = store; } skip_indices_aggregators.push_back(index_helper->createIndexAggregatorForPart(store)); diff --git a/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp b/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp index f31716dfcfa0..b7aad2d9a026 100644 --- a/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp +++ b/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp @@ -1692,7 +1692,7 @@ MarkRanges MergeTreeDataSelectExecutor::filterMarksUsingIndex( if (dynamic_cast(&*index_helper) != nullptr) { - cache_in_store.store = GinIndexStoreFactory::instance().get(index_helper->getFileName(), part->volume->getDisk(), part->getFullRelativePath()); + cache_in_store.store = GinIndexStoreFactory::instance().get(index_helper->getFileName(), part->data_part_storage); } for (size_t i = 0; i < ranges.size(); ++i) From 2055c8f3115def0f533bdc59d0a0e4777c5a93d3 Mon Sep 17 00:00:00 2001 From: Larry Luo Date: Wed, 29 Jun 2022 12:35:55 -0700 Subject: [PATCH 05/42] Add basic full text search functional test --- .../0_stateless/02346_full_text_search.reference | 3 +++ tests/queries/0_stateless/02346_full_text_search.sql | 10 ++++++++++ 2 files changed, 13 insertions(+) create mode 100644 tests/queries/0_stateless/02346_full_text_search.reference create mode 100644 tests/queries/0_stateless/02346_full_text_search.sql diff --git a/tests/queries/0_stateless/02346_full_text_search.reference b/tests/queries/0_stateless/02346_full_text_search.reference new file mode 100644 index 000000000000..f876788a4972 --- /dev/null +++ b/tests/queries/0_stateless/02346_full_text_search.reference @@ -0,0 +1,3 @@ +af gin +101 Alick a01 +111 Alick b01 diff --git a/tests/queries/0_stateless/02346_full_text_search.sql b/tests/queries/0_stateless/02346_full_text_search.sql new file mode 100644 index 000000000000..103568ce3cb7 --- /dev/null +++ b/tests/queries/0_stateless/02346_full_text_search.sql @@ -0,0 +1,10 @@ +-- create table +DROP TABLE IF EXISTS simple; +CREATE TABLE simple(k UInt64,s String,INDEX af (s) TYPE gin(2) GRANULARITY 1) ENGINE = MergeTree() ORDER BY k +SETTINGS index_granularity = 2; +-- insert test data into table +INSERT INTO simple VALUES (101, 'Alick a01'), (102, 'Blick a02'), (103, 'Click a03'),(104, 'Dlick a04'),(105, 'Elick a05'),(106, 'Alick a06'),(107, 'Blick a07'),(108, 'Click a08'),(109, 'Dlick a09'),(110, 'Elick b10'),(111, 'Alick b01'),(112, 'Blick b02'),(113, 'Click b03'),(114, 'Dlick b04'),(115, 'Elick b05'),(116, 'Alick b06'),(117, 'Blick b07'),(118, 'Click b08'),(119, 'Dlick b09'),(120, 'Elick b10'); +-- check gin index was created +SELECT name, type FROM system.data_skipping_indices where (table =='simple') limit 1; +-- search gin index +SELECT * FROM simple WHERE s LIKE '%01%'; \ No newline at end of file From 1311e04d754077fe426366fda81da966cd7a0e10 Mon Sep 17 00:00:00 2001 From: Larry Luo Date: Wed, 29 Jun 2022 14:00:03 -0700 Subject: [PATCH 06/42] capture skipping granules in functional test. --- tests/queries/0_stateless/02346_full_text_search.reference | 1 + tests/queries/0_stateless/02346_full_text_search.sql | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/queries/0_stateless/02346_full_text_search.reference b/tests/queries/0_stateless/02346_full_text_search.reference index f876788a4972..9672047d06bc 100644 --- a/tests/queries/0_stateless/02346_full_text_search.reference +++ b/tests/queries/0_stateless/02346_full_text_search.reference @@ -1,3 +1,4 @@ af gin 101 Alick a01 111 Alick b01 +4 2 diff --git a/tests/queries/0_stateless/02346_full_text_search.sql b/tests/queries/0_stateless/02346_full_text_search.sql index 103568ce3cb7..6ac6a94ffead 100644 --- a/tests/queries/0_stateless/02346_full_text_search.sql +++ b/tests/queries/0_stateless/02346_full_text_search.sql @@ -7,4 +7,6 @@ INSERT INTO simple VALUES (101, 'Alick a01'), (102, 'Blick a02'), (103, 'Click a -- check gin index was created SELECT name, type FROM system.data_skipping_indices where (table =='simple') limit 1; -- search gin index -SELECT * FROM simple WHERE s LIKE '%01%'; \ No newline at end of file +SELECT * FROM simple WHERE s LIKE '%01%'; +-- check the query only read 2 granules (4 rows total; each granule has 2 rows) +SELECT read_rows, result_rows from system.query_log where query_kind ='Select' and startsWith(query, 'SELECT * FROM simple WHERE s LIKE \'%01%\'') and result_rows==2 limit 1; \ No newline at end of file From 0dcfc5ec1eef5f7616c4cf93557feb3ff4e9b1d5 Mon Sep 17 00:00:00 2001 From: HarryLeeIBM Date: Thu, 30 Jun 2022 10:35:12 -0700 Subject: [PATCH 07/42] Add more functional tests --- .../02346_full_text_search.reference | 17 ++++ .../0_stateless/02346_full_text_search.sql | 98 +++++++++++++++++-- 2 files changed, 107 insertions(+), 8 deletions(-) diff --git a/tests/queries/0_stateless/02346_full_text_search.reference b/tests/queries/0_stateless/02346_full_text_search.reference index 9672047d06bc..440bb515ef9a 100644 --- a/tests/queries/0_stateless/02346_full_text_search.reference +++ b/tests/queries/0_stateless/02346_full_text_search.reference @@ -2,3 +2,20 @@ af gin 101 Alick a01 111 Alick b01 4 2 +af gin +101 Alick a01 +106 Alick a06 +111 Alick b01 +116 Alick b06 +8 4 +af gin +101 Alick a01 +111 Alick b01 +201 rick c01 +6 3 +af gin +102 clickhouse你好 +2 1 +af gin +5 BC614E,05397FB1,2D2D2D2D2D2D1540,CF3304 +1024 1 diff --git a/tests/queries/0_stateless/02346_full_text_search.sql b/tests/queries/0_stateless/02346_full_text_search.sql index 6ac6a94ffead..2169174b7e4d 100644 --- a/tests/queries/0_stateless/02346_full_text_search.sql +++ b/tests/queries/0_stateless/02346_full_text_search.sql @@ -1,12 +1,94 @@ --- create table -DROP TABLE IF EXISTS simple; -CREATE TABLE simple(k UInt64,s String,INDEX af (s) TYPE gin(2) GRANULARITY 1) ENGINE = MergeTree() ORDER BY k -SETTINGS index_granularity = 2; +SET log_queries = 1; +SET max_threads = 1; +-- create table for gin(2) +DROP TABLE IF EXISTS simple1; +CREATE TABLE simple1(k UInt64,s String,INDEX af (s) TYPE gin(2) GRANULARITY 1) + ENGINE = MergeTree() ORDER BY k + SETTINGS index_granularity = 2; -- insert test data into table -INSERT INTO simple VALUES (101, 'Alick a01'), (102, 'Blick a02'), (103, 'Click a03'),(104, 'Dlick a04'),(105, 'Elick a05'),(106, 'Alick a06'),(107, 'Blick a07'),(108, 'Click a08'),(109, 'Dlick a09'),(110, 'Elick b10'),(111, 'Alick b01'),(112, 'Blick b02'),(113, 'Click b03'),(114, 'Dlick b04'),(115, 'Elick b05'),(116, 'Alick b06'),(117, 'Blick b07'),(118, 'Click b08'),(119, 'Dlick b09'),(120, 'Elick b10'); +INSERT INTO simple1 VALUES (101, 'Alick a01'), (102, 'Blick a02'), (103, 'Click a03'),(104, 'Dlick a04'),(105, 'Elick a05'),(106, 'Alick a06'),(107, 'Blick a07'),(108, 'Click a08'),(109, 'Dlick a09'),(110, 'Elick b10'),(111, 'Alick b01'),(112, 'Blick b02'),(113, 'Click b03'),(114, 'Dlick b04'),(115, 'Elick b05'),(116, 'Alick b06'),(117, 'Blick b07'),(118, 'Click b08'),(119, 'Dlick b09'),(120, 'Elick b10'); -- check gin index was created -SELECT name, type FROM system.data_skipping_indices where (table =='simple') limit 1; +SELECT name, type FROM system.data_skipping_indices where (table =='simple1') limit 1; -- search gin index -SELECT * FROM simple WHERE s LIKE '%01%'; + +SELECT * FROM simple1 WHERE s LIKE '%01%'; + -- check the query only read 2 granules (4 rows total; each granule has 2 rows) -SELECT read_rows, result_rows from system.query_log where query_kind ='Select' and startsWith(query, 'SELECT * FROM simple WHERE s LIKE \'%01%\'') and result_rows==2 limit 1; \ No newline at end of file +SELECT read_rows, result_rows from system.query_log + where query_kind ='Select' and + startsWith(query, 'SELECT * FROM simple1 WHERE s LIKE \'%01%\'') + and result_rows==2 limit 1; + +-- create table for gin() +DROP TABLE IF EXISTS simple2; +CREATE TABLE simple2(k UInt64,s String,INDEX af (s) TYPE gin() GRANULARITY 1) + ENGINE = MergeTree() ORDER BY k + SETTINGS index_granularity = 2; + +-- insert test data into table +INSERT INTO simple2 VALUES (101, 'Alick a01'), (102, 'Blick a02'), (103, 'Click a03'),(104, 'Dlick a04'),(105, 'Elick a05'),(106, 'Alick a06'),(107, 'Blick a07'),(108, 'Click a08'),(109, 'Dlick a09'),(110, 'Elick b10'),(111, 'Alick b01'),(112, 'Blick b02'),(113, 'Click b03'),(114, 'Dlick b04'),(115, 'Elick b05'),(116, 'Alick b06'),(117, 'Blick b07'),(118, 'Click b08'),(119, 'Dlick b09'),(120, 'Elick b10'); + +-- check gin index was created +SELECT name, type FROM system.data_skipping_indices where (table =='simple2') limit 1; +-- search gin index +SELECT * FROM simple2 WHERE hasToken(s, 'Alick'); + +-- check the query only read 4 granules (8 rows total; each granule has 2 rows) +SELECT read_rows, result_rows from system.query_log + where query_kind ='Select' + and startsWith(query, 'SELECT * FROM simple2 WHERE hasToken(s, \'Alick\')') + and result_rows==4 limit 1; + +-- create table for gin(2) with two parts +DROP TABLE IF EXISTS simple3; +CREATE TABLE simple3(k UInt64,s String,INDEX af (s) TYPE gin(2) GRANULARITY 1) + ENGINE = MergeTree() ORDER BY k + SETTINGS index_granularity = 2; + +-- insert test data into table +INSERT INTO simple3 VALUES (101, 'Alick a01'), (102, 'Blick a02'), (103, 'Click a03'),(104, 'Dlick a04'),(105, 'Elick a05'),(106, 'Alick a06'),(107, 'Blick a07'),(108, 'Click a08'),(109, 'Dlick a09'),(110, 'Elick b10'),(111, 'Alick b01'),(112, 'Blick b02'),(113, 'Click b03'),(114, 'Dlick b04'),(115, 'Elick b05'),(116, 'Alick b06'),(117, 'Blick b07'),(118, 'Click b08'),(119, 'Dlick b09'),(120, 'Elick b10'); +INSERT INTO simple3 VALUES (201, 'rick c01'), (202, 'mick c02'),(203, 'nick c03'); +-- check gin index was created +SELECT name, type FROM system.data_skipping_indices where (table =='simple3') limit 1; +-- search gin index + +SELECT * FROM simple3 WHERE s LIKE '%01%'; + +-- check the query only read 3 granules (6 rows total; each granule has 2 rows) +SELECT read_rows, result_rows from system.query_log + where query_kind ='Select' + and startsWith(query, 'SELECT * FROM simple3 WHERE s LIKE \'%01%\'') + and result_rows==3 limit 1; + +-- create table for gin(2) for utf8 string test +DROP TABLE IF EXISTS simple4; +CREATE TABLE simple4(k UInt64,s String,INDEX af (s) TYPE gin(2) GRANULARITY 1) ENGINE = MergeTree() ORDER BY k +SETTINGS index_granularity = 2; +-- insert test data into table +INSERT INTO simple4 VALUES (101, 'Alick 好'),(102, 'clickhouse你好'), (103, 'Click ä½ '),(104, 'Dlick ä½ a好'),(105, 'Elick 好好你你'),(106, 'Alick 好a好aä½ aä½ '); +-- check gin index was created +SELECT name, type FROM system.data_skipping_indices where (table =='simple4') limit 1; +-- search gin index +SELECT * FROM simple4 WHERE s LIKE '%你好%'; +-- check the query only read 1 granule (2 rows total; each granule has 2 rows) +SELECT read_rows, result_rows from system.query_log where query_kind ='Select' and startsWith(query, 'SELECT * FROM simple4 WHERE s LIKE \'%你好%\'') and result_rows==1 limit 1; + +-- create table with 1000000 rows +DROP TABLE IF EXISTS hextable; +CREATE TABLE hextable(k UInt64,s String,INDEX af(s) TYPE gin(0) GRANULARITY 1) + Engine=MergeTree + ORDER BY (k) + SETTINGS index_granularity = 1024 + AS + SELECT + number, + format('{},{},{},{}', hex(12345678), hex(87654321), hex(number/17 + 5), hex(13579012)) as s + FROM numbers(1000000); +-- check gin index was created +SELECT name, type FROM system.data_skipping_indices where (table =='hextable') limit 1; +-- search gin index +select * from hextable where hasToken(s, '2D2D2D2D2D2D1540'); +-- check the query only read 3 granules (6 rows total; each granule has 2 rows) +SELECT read_rows, result_rows from system.query_log + where query_kind ='Select' + and hasToken(query, 'hextable') and result_rows==1 limit 1; From bc8be368bb5ae8a9f2d905464b9206ef2717c8dd Mon Sep 17 00:00:00 2001 From: HarryLeeIBM Date: Thu, 30 Jun 2022 10:48:07 -0700 Subject: [PATCH 08/42] Fix coding styles --- src/Storages/MergeTree/GinIndexStore.h | 2 +- src/Storages/MergeTree/IMergeTreeDataPart.cpp | 2 +- src/Storages/MergeTree/MergeTreeDataPartWriterOnDisk.cpp | 2 +- src/Storages/MergeTree/MergeTreeDataPartWriterOnDisk.h | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Storages/MergeTree/GinIndexStore.h b/src/Storages/MergeTree/GinIndexStore.h index 7d580b4c946e..1a2cafc56523 100644 --- a/src/Storages/MergeTree/GinIndexStore.h +++ b/src/Storages/MergeTree/GinIndexStore.h @@ -155,7 +155,7 @@ class GinIndexStore friend class GinIndexStoreReader; String name; - DataPartStoragePtr storage; + DataPartStoragePtr storage; DataPartStorageBuilderPtr data_part_storage_builder; std::mutex gin_index_store_mutex; diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.cpp b/src/Storages/MergeTree/IMergeTreeDataPart.cpp index 4a78d7555ce7..9863b254e12f 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.cpp +++ b/src/Storages/MergeTree/IMergeTreeDataPart.cpp @@ -1672,7 +1672,7 @@ void IMergeTreeDataPart::remove() metadata_manager->deleteAll(false); metadata_manager->assertAllDeleted(false); - + GinIndexStoreFactory::instance().remove(data_part_storage->getRelativePath()); std::list projection_checksums; diff --git a/src/Storages/MergeTree/MergeTreeDataPartWriterOnDisk.cpp b/src/Storages/MergeTree/MergeTreeDataPartWriterOnDisk.cpp index 6cc4e22844c9..c62d4a391841 100644 --- a/src/Storages/MergeTree/MergeTreeDataPartWriterOnDisk.cpp +++ b/src/Storages/MergeTree/MergeTreeDataPartWriterOnDisk.cpp @@ -287,7 +287,7 @@ void MergeTreeDataPartWriterOnDisk::calculateAndSerializeSkipIndices(const Block } store = it->second; } - + for (const auto & granule : granules_to_write) { if (skip_index_accumulated_marks[i] == index_helper->index.granularity) diff --git a/src/Storages/MergeTree/MergeTreeDataPartWriterOnDisk.h b/src/Storages/MergeTree/MergeTreeDataPartWriterOnDisk.h index ed9a56419cb8..2377a129ac04 100644 --- a/src/Storages/MergeTree/MergeTreeDataPartWriterOnDisk.h +++ b/src/Storages/MergeTree/MergeTreeDataPartWriterOnDisk.h @@ -161,7 +161,7 @@ class MergeTreeDataPartWriterOnDisk : public IMergeTreeDataPartWriter /// Data is already written up to this mark. size_t current_mark = 0; - + GinIndexStores gin_index_stores; private: void initSkipIndices(); From 473279d2574bd9f91580cbc01d4a54722abc2421 Mon Sep 17 00:00:00 2001 From: Larry Luo Date: Thu, 30 Jun 2022 16:29:29 -0700 Subject: [PATCH 09/42] Make full text search tests reliable --- .../02346_full_text_search.reference | 2 +- .../0_stateless/02346_full_text_search.sql | 21 +++++++++++-------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/tests/queries/0_stateless/02346_full_text_search.reference b/tests/queries/0_stateless/02346_full_text_search.reference index 440bb515ef9a..6b1fcb818e15 100644 --- a/tests/queries/0_stateless/02346_full_text_search.reference +++ b/tests/queries/0_stateless/02346_full_text_search.reference @@ -18,4 +18,4 @@ af gin 2 1 af gin 5 BC614E,05397FB1,2D2D2D2D2D2D1540,CF3304 -1024 1 +6 1 diff --git a/tests/queries/0_stateless/02346_full_text_search.sql b/tests/queries/0_stateless/02346_full_text_search.sql index 2169174b7e4d..963704cbd77c 100644 --- a/tests/queries/0_stateless/02346_full_text_search.sql +++ b/tests/queries/0_stateless/02346_full_text_search.sql @@ -1,5 +1,7 @@ SET log_queries = 1; SET max_threads = 1; +TRUNCATE system.query_log; + -- create table for gin(2) DROP TABLE IF EXISTS simple1; CREATE TABLE simple1(k UInt64,s String,INDEX af (s) TYPE gin(2) GRANULARITY 1) @@ -9,14 +11,14 @@ CREATE TABLE simple1(k UInt64,s String,INDEX af (s) TYPE gin(2) GRANULARITY 1) INSERT INTO simple1 VALUES (101, 'Alick a01'), (102, 'Blick a02'), (103, 'Click a03'),(104, 'Dlick a04'),(105, 'Elick a05'),(106, 'Alick a06'),(107, 'Blick a07'),(108, 'Click a08'),(109, 'Dlick a09'),(110, 'Elick b10'),(111, 'Alick b01'),(112, 'Blick b02'),(113, 'Click b03'),(114, 'Dlick b04'),(115, 'Elick b05'),(116, 'Alick b06'),(117, 'Blick b07'),(118, 'Click b08'),(119, 'Dlick b09'),(120, 'Elick b10'); -- check gin index was created SELECT name, type FROM system.data_skipping_indices where (table =='simple1') limit 1; --- search gin index +-- search gin index SELECT * FROM simple1 WHERE s LIKE '%01%'; - +SYSTEM FLUSH LOGS; -- check the query only read 2 granules (4 rows total; each granule has 2 rows) SELECT read_rows, result_rows from system.query_log where query_kind ='Select' and - startsWith(query, 'SELECT * FROM simple1 WHERE s LIKE \'%01%\'') + endsWith(trimRight(query), 'SELECT * FROM simple1 WHERE s LIKE \'%01%\';') and result_rows==2 limit 1; -- create table for gin() @@ -32,11 +34,11 @@ INSERT INTO simple2 VALUES (101, 'Alick a01'), (102, 'Blick a02'), (103, 'Click SELECT name, type FROM system.data_skipping_indices where (table =='simple2') limit 1; -- search gin index SELECT * FROM simple2 WHERE hasToken(s, 'Alick'); - +SYSTEM FLUSH LOGS; -- check the query only read 4 granules (8 rows total; each granule has 2 rows) SELECT read_rows, result_rows from system.query_log where query_kind ='Select' - and startsWith(query, 'SELECT * FROM simple2 WHERE hasToken(s, \'Alick\')') + and endsWith(trimRight(query), 'SELECT * FROM simple2 WHERE hasToken(s, \'Alick\');') and result_rows==4 limit 1; -- create table for gin(2) with two parts @@ -51,13 +53,12 @@ INSERT INTO simple3 VALUES (201, 'rick c01'), (202, 'mick c02'),(203, 'nick c03' -- check gin index was created SELECT name, type FROM system.data_skipping_indices where (table =='simple3') limit 1; -- search gin index - SELECT * FROM simple3 WHERE s LIKE '%01%'; - +SYSTEM FLUSH LOGS; -- check the query only read 3 granules (6 rows total; each granule has 2 rows) SELECT read_rows, result_rows from system.query_log where query_kind ='Select' - and startsWith(query, 'SELECT * FROM simple3 WHERE s LIKE \'%01%\'') + and endsWith(trimRight(query), 'SELECT * FROM simple3 WHERE s LIKE \'%01%\';') and result_rows==3 limit 1; -- create table for gin(2) for utf8 string test @@ -70,8 +71,9 @@ INSERT INTO simple4 VALUES (101, 'Alick 好'),(102, 'clickhouse你好'), (103, ' SELECT name, type FROM system.data_skipping_indices where (table =='simple4') limit 1; -- search gin index SELECT * FROM simple4 WHERE s LIKE '%你好%'; +SYSTEM FLUSH LOGS; -- check the query only read 1 granule (2 rows total; each granule has 2 rows) -SELECT read_rows, result_rows from system.query_log where query_kind ='Select' and startsWith(query, 'SELECT * FROM simple4 WHERE s LIKE \'%你好%\'') and result_rows==1 limit 1; +SELECT read_rows, result_rows from system.query_log where query_kind ='Select' and endsWith(trimRight(query), 'SELECT * FROM simple4 WHERE s LIKE \'%你好%\';') and result_rows==1 limit 1; -- create table with 1000000 rows DROP TABLE IF EXISTS hextable; @@ -88,6 +90,7 @@ CREATE TABLE hextable(k UInt64,s String,INDEX af(s) TYPE gin(0) GRANULARITY 1) SELECT name, type FROM system.data_skipping_indices where (table =='hextable') limit 1; -- search gin index select * from hextable where hasToken(s, '2D2D2D2D2D2D1540'); +SYSTEM FLUSH LOGS; -- check the query only read 3 granules (6 rows total; each granule has 2 rows) SELECT read_rows, result_rows from system.query_log where query_kind ='Select' From 0b63d6b5beb1ab89293dc8afc5562b1f53966586 Mon Sep 17 00:00:00 2001 From: Larry Luo Date: Sun, 3 Jul 2022 05:18:51 -0700 Subject: [PATCH 10/42] Resolve conflicts and style errors. --- src/Common/FST.cpp | 8 +- src/Common/FST.h | 4 +- src/Common/tests/gtest_fst.cpp | 70 ++++++++-------- src/Interpreters/GinFilter.cpp | 84 +++++++++---------- src/Interpreters/GinFilter.h | 2 +- .../MergeTree/DataPartStorageOnDisk.cpp | 3 + src/Storages/MergeTree/GinIndexStore.cpp | 32 +++---- src/Storages/MergeTree/GinIndexStore.h | 4 +- .../MergeTreeDataPartWriterOnDisk.cpp | 6 +- .../MergeTree/MergeTreeDataSelectExecutor.cpp | 2 +- src/Storages/MergeTree/MergeTreeIndexGin.cpp | 24 +++--- .../02346_full_text_search.reference | 2 +- .../0_stateless/02346_full_text_search.sql | 22 +++-- 13 files changed, 139 insertions(+), 124 deletions(-) diff --git a/src/Common/FST.cpp b/src/Common/FST.cpp index 0a286521888b..220be489f9d3 100644 --- a/src/Common/FST.cpp +++ b/src/Common/FST.cpp @@ -46,7 +46,7 @@ void ArcsBitmap::addArc(char label) } int ArcsBitmap::getIndex(char label) const -{ +{ int bitCount = 0; uint8_t index = label; @@ -73,7 +73,7 @@ int ArcsBitmap::getArcNum() const int bitCount = 0; for (size_t i = 0; i < 4; i++) { - if(data.items[i]) + if (data.items[i]) bitCount += PopCountImpl(data.items[i]); } return bitCount; @@ -259,7 +259,7 @@ void FSTBuilder::minimizePreviousWordSuffix(int down_to) temp_states[i - 1]->id = next_id++; if (!found) - { + { minimized_state->state_index = previous_state_index; previous_written_bytes = minimized_state->serialize(write_buffer); @@ -380,7 +380,7 @@ std::pair FST::getOutput(const String& term) UInt8 label_num{0}; read_buffer.read(reinterpret_cast(label_num)); - if(label_num == 0) + if (label_num == 0) return { false, 0 }; auto labels_position = read_buffer.getPosition(); diff --git a/src/Common/FST.h b/src/Common/FST.h index 771742bc48d2..0d9e2022ef05 100644 --- a/src/Common/FST.h +++ b/src/Common/FST.h @@ -21,8 +21,8 @@ struct Arc { Arc() = default; Arc(Output output_, const StatePtr &target_): output{output_}, target{target_}{} - Output output{0}; - StatePtr target; + Output output{0}; + StatePtr target; UInt16 serialize(WriteBuffer& write_buffer); }; diff --git a/src/Common/tests/gtest_fst.cpp b/src/Common/tests/gtest_fst.cpp index 113a41e0116b..b03a49c4c915 100644 --- a/src/Common/tests/gtest_fst.cpp +++ b/src/Common/tests/gtest_fst.cpp @@ -9,48 +9,48 @@ using namespace std; TEST(FST, SimpleTest) { - vector> data - { - {"mop", 100}, - {"moth", 91}, - {"pop", 72}, - {"star", 83}, - {"stop", 54}, - {"top", 55}, - }; - - vector> bad_data - { - {"mo", 100}, - {"moth1", 91}, - {"po", 72}, - {"star2", 83}, - {"sto", 54}, - {"top33", 55}, - }; + vector> data + { + {"mop", 100}, + {"moth", 91}, + {"pop", 72}, + {"star", 83}, + {"stop", 54}, + {"top", 55}, + }; + + vector> bad_data + { + {"mo", 100}, + {"moth1", 91}, + {"po", 72}, + {"star2", 83}, + {"sto", 54}, + {"top33", 55}, + }; std::vector buffer; DB::WriteBufferFromVector> wbuf(buffer); DB::FSTBuilder builder(wbuf); - for (auto& term_output : data) - { - builder.add(term_output.first, term_output.second); - } + for (auto& term_output : data) + { + builder.add(term_output.first, term_output.second); + } builder.build(); wbuf.finalize(); DB::FST fst(std::move(buffer)); - for (auto& item : data) - { - auto output = fst.getOutput(item.first); - ASSERT_EQ(output.first, true); - ASSERT_EQ(output.second, item.second); - } - - for (auto& item : bad_data) - { - auto output = fst.getOutput(item.first); - ASSERT_EQ(output.first, false); - } + for (auto& item : data) + { + auto output = fst.getOutput(item.first); + ASSERT_EQ(output.first, true); + ASSERT_EQ(output.second, item.second); + } + + for (auto& item : bad_data) + { + auto output = fst.getOutput(item.first); + ASSERT_EQ(output.first, false); + } } diff --git a/src/Interpreters/GinFilter.cpp b/src/Interpreters/GinFilter.cpp index 496023d10935..e99d5016d794 100644 --- a/src/Interpreters/GinFilter.cpp +++ b/src/Interpreters/GinFilter.cpp @@ -35,7 +35,7 @@ GinFilter::GinFilter(const GinFilterParameters & params) void GinFilter::add(const char* data, size_t len, UInt32 rowID, GinIndexStorePtr& store) { - if(len > MAX_TERM_LENGTH) + if (len > MAX_TERM_LENGTH) return; string token(data, len); @@ -57,10 +57,10 @@ void GinFilter::add(const char* data, size_t len, UInt32 rowID, GinIndexStorePtr void GinFilter::addRowRangeToGinFilter(UInt32 segmentID, UInt32 rowIDStart, UInt32 rowIDEnd) { - if(rowid_range_container.size() > 0) + if (rowid_range_container.size() > 0) { /// Try to merge the rowID range with the last one in the container - if(rowid_range_container.back().segment_id == segmentID && + if (rowid_range_container.back().segment_id == segmentID && rowid_range_container.back().range_end+1 == rowIDStart) { rowid_range_container.back().range_end = rowIDEnd; @@ -79,7 +79,7 @@ void GinFilter::clear() void GinFilter::dump() const { printf("filter : '%s', row ID range:\n", getMatchString().c_str()); - for(const auto & rowid_range: rowid_range_container) + for (const auto & rowid_range: rowid_range_container) { printf("\t\t%d, %d, %d; ", rowid_range.segment_id, rowid_range.range_start, rowid_range.range_end); } @@ -110,7 +110,7 @@ void dumpPostingsCache(const PostingsCache& postings_cache) void dumpPostingsCacheForStore(const PostingsCacheForStore& cache_store) { printf("----cache---store---: %s\n", cache_store.store->getName().c_str()); - for(const auto & query_string_postings_cache: cache_store.cache) + for (const auto & query_string_postings_cache: cache_store.cache) { printf("----cache_store----filter string:%s---\n", query_string_postings_cache.first.c_str()); dumpPostingsCache(*query_string_postings_cache.second); @@ -120,13 +120,13 @@ void dumpPostingsCacheForStore(const PostingsCacheForStore& cache_store) bool GinFilter::hasEmptyPostingsList(const PostingsCachePtr& postings_cache) { - if(postings_cache->size() == 0) + if (postings_cache->size() == 0) return true; for (const auto& term_postings : *postings_cache) { const SegmentedPostingsListContainer& container = term_postings.second; - if(container.size() == 0) + if (container.size() == 0) return true; } return false; @@ -140,29 +140,29 @@ bool GinFilter::matchInRange(const PostingsCachePtr& postings_cache, UInt32 segm for (const auto& term_postings : *postings_cache) { - const SegmentedPostingsListContainer& container = term_postings.second; - auto container_it{ container.find(segment_id) }; - if (container_it == container.cend()) - { - return false; - } - auto min_in_container = container_it->second->minimum(); - auto max_in_container = container_it->second->maximum(); - if(range_start > max_in_container || min_in_container > range_end) - { - return false; - } - - /// Delay initialization as late as possible - if(!intersection_result_init) - { - intersection_result_init = true; - intersection_result.addRange(range_start, range_end+1); - } - intersection_result &= *container_it->second; - if(intersection_result.cardinality() == 0) - { - return false; + const SegmentedPostingsListContainer& container = term_postings.second; + auto container_it{ container.find(segment_id) }; + if (container_it == container.cend()) + { + return false; + } + auto min_in_container = container_it->second->minimum(); + auto max_in_container = container_it->second->maximum(); + if (range_start > max_in_container || min_in_container > range_end) + { + return false; + } + + /// Delay initialization as late as possible + if (!intersection_result_init) + { + intersection_result_init = true; + intersection_result.addRange(range_start, range_end+1); + } + intersection_result &= *container_it->second; + if (intersection_result.cardinality() == 0) + { + return false; } } return true; @@ -170,29 +170,29 @@ bool GinFilter::matchInRange(const PostingsCachePtr& postings_cache, UInt32 segm bool GinFilter::match(const PostingsCachePtr& postings_cache) const { - if(hasEmptyPostingsList(postings_cache)) + if (hasEmptyPostingsList(postings_cache)) { return false; } - bool match_result = false; - /// Check for each row ID ranges - for (const auto &rowid_range: rowid_range_container) - { - match_result |= matchInRange(postings_cache, rowid_range.segment_id, rowid_range.range_start, rowid_range.range_end); - } + bool match_result = false; + /// Check for each row ID ranges + for (const auto &rowid_range: rowid_range_container) + { + match_result |= matchInRange(postings_cache, rowid_range.segment_id, rowid_range.range_start, rowid_range.range_end); + } return match_result; } bool GinFilter::needsFilter() const { - if(getTerms().size() == 0) + if (getTerms().size() == 0) return false; - for(const auto & term: getTerms()) + for (const auto & term: getTerms()) { - if(term.size() > MAX_TERM_LENGTH) + if (term.size() > MAX_TERM_LENGTH) return false; } @@ -201,11 +201,11 @@ bool GinFilter::needsFilter() const bool GinFilter::contains(const GinFilter & af, PostingsCacheForStore &cache_store) { - if(!af.needsFilter()) + if (!af.needsFilter()) return true; PostingsCachePtr postings_cache = cache_store.getPostings(af.getMatchString()); - if(postings_cache == nullptr) + if (postings_cache == nullptr) { GinIndexStoreReader reader(cache_store.store); diff --git a/src/Interpreters/GinFilter.h b/src/Interpreters/GinFilter.h index ff279cb59c65..6d5dad1869f8 100644 --- a/src/Interpreters/GinFilter.h +++ b/src/Interpreters/GinFilter.h @@ -15,7 +15,7 @@ class DiskLocal; using RowIDRange = struct { - /// Segement ID of the row ID range + /// Segment ID of the row ID range UInt32 segment_id; /// First row ID in the range diff --git a/src/Storages/MergeTree/DataPartStorageOnDisk.cpp b/src/Storages/MergeTree/DataPartStorageOnDisk.cpp index 286a17ee056a..9b21cf7631d7 100644 --- a/src/Storages/MergeTree/DataPartStorageOnDisk.cpp +++ b/src/Storages/MergeTree/DataPartStorageOnDisk.cpp @@ -747,6 +747,9 @@ std::unique_ptr DataPartStorageBuilderOnDisk::writeFile WriteMode mode, const WriteSettings & settings) { + if (transaction) + return transaction->writeFile(fs::path(root_path) / part_dir / name, buf_size, mode, settings, /* autocommit = */ false); + return volume->getDisk()->writeFile(fs::path(root_path) / part_dir / name, buf_size, mode, settings); } diff --git a/src/Storages/MergeTree/GinIndexStore.cpp b/src/Storages/MergeTree/GinIndexStore.cpp index bb03cbc9c412..591b18fe070d 100644 --- a/src/Storages/MergeTree/GinIndexStore.cpp +++ b/src/Storages/MergeTree/GinIndexStore.cpp @@ -26,7 +26,7 @@ namespace ErrorCodes bool GinIndexPostingsBuilder::contains(UInt32 row_id) const { - if(useRoaring()) + if (useRoaring()) return bmp.contains(row_id); auto it(std::find(lst.begin(), lst.begin()+lst_length, row_id)); @@ -35,7 +35,7 @@ bool GinIndexPostingsBuilder::contains(UInt32 row_id) const void GinIndexPostingsBuilder::add(UInt32 row_id) { - if(useRoaring()) + if (useRoaring()) { bmp.add(row_id); return; @@ -43,9 +43,9 @@ void GinIndexPostingsBuilder::add(UInt32 row_id) assert(lst_length < MIN_SIZE_FOR_ROARING_ENCODING); lst[lst_length++] = row_id; - if(lst_length == MIN_SIZE_FOR_ROARING_ENCODING) + if (lst_length == MIN_SIZE_FOR_ROARING_ENCODING) { - for(size_t i = 0; i < lst_length; i++) + for (size_t i = 0; i < lst_length; i++) bmp.add(lst[i]); lst_length = 0xFF; @@ -60,7 +60,7 @@ bool GinIndexPostingsBuilder::useRoaring() const UInt64 GinIndexPostingsBuilder::serialize(WriteBuffer &buffer) const { UInt64 encoding_length = 0; - if(!useRoaring()) + if (!useRoaring()) { /// First byte is number of Row IDS to be encoded buffer.write(lst_length); @@ -95,13 +95,13 @@ GinIndexPostingsListPtr GinIndexPostingsBuilder::deserialize(ReadBuffer &buffer) char postings_list_size{0}; buffer.read(postings_list_size); - if(postings_list_size != 0) + if (postings_list_size != 0) { assert(postings_list_size < MIN_SIZE_FOR_ROARING_ENCODING); GinIndexPostingsListPtr postings_list = std::make_shared(); UInt32 row_ids[MIN_SIZE_FOR_ROARING_ENCODING]; - for(auto i = 0; i < postings_list_size; ++i) + for (auto i = 0; i < postings_list_size; ++i) { readVarUInt(row_ids[i], buffer); } @@ -199,15 +199,15 @@ UInt32 GinIndexStore::getSegmentNum() return result - 1; } - bool GinIndexStore::needToWrite() const - { +bool GinIndexStore::needToWrite() const +{ assert(max_digestion_size > 0); return current_size > max_digestion_size; - } +} void GinIndexStore::finalize() { - if(current_postings.size() > 0) + if (current_postings.size() > 0) { writeSegment(); } @@ -236,11 +236,13 @@ void GinIndexStore::writeSegment() std::vector> token_postings_list_pairs; token_postings_list_pairs.reserve(current_postings.size()); - for (const auto& [token, postings_list] : current_postings) { + for (const auto& [token, postings_list] : current_postings) + { token_postings_list_pairs.push_back({std::string_view(token), postings_list}); } std::sort(token_postings_list_pairs.begin(), token_postings_list_pairs.end(), - [](const std::pair& a, const std::pair& b) { + [](const std::pair& a, const std::pair& b) + { return a.first < b.first; }); @@ -424,7 +426,7 @@ void GinIndexStoreFactory::remove(const String& part_path) std::lock_guard lock(stores_mutex); for (auto it = stores.begin(); it != stores.end();) { - if(it->first.find(part_path) != String::npos) + if (it->first.find(part_path) != String::npos) it = stores.erase(it); else ++it; @@ -435,7 +437,7 @@ void GinIndexStoreFactory::remove(const String& part_path) void GinIndexStoreFactory::dump() { printf("GinIndexStoreFactory----------dump start-------->>\n"); - for(const auto & [key, store]: stores) + for (const auto & [key, store]: stores) { printf("%s\n", key.c_str()); } diff --git a/src/Storages/MergeTree/GinIndexStore.h b/src/Storages/MergeTree/GinIndexStore.h index 1a2cafc56523..8790100728c9 100644 --- a/src/Storages/MergeTree/GinIndexStore.h +++ b/src/Storages/MergeTree/GinIndexStore.h @@ -144,7 +144,7 @@ class GinIndexStore /// Do last segment writing void finalize(); - /// method for wrting segment data to Gin index files + /// method for writing segment data to Gin index files void writeSegment(); String getName() const {return name;} @@ -195,7 +195,9 @@ struct PostingsCacheForStore auto it {cache.find(query_string)}; if(it == cache.cend()) + { return nullptr; + } return it->second; } }; diff --git a/src/Storages/MergeTree/MergeTreeDataPartWriterOnDisk.cpp b/src/Storages/MergeTree/MergeTreeDataPartWriterOnDisk.cpp index c62d4a391841..6ac04246bcfc 100644 --- a/src/Storages/MergeTree/MergeTreeDataPartWriterOnDisk.cpp +++ b/src/Storages/MergeTree/MergeTreeDataPartWriterOnDisk.cpp @@ -214,7 +214,7 @@ void MergeTreeDataPartWriterOnDisk::initSkipIndices() settings.query_write_settings)); GinIndexStorePtr store = nullptr; - if(dynamic_cast(&*index_helper) != nullptr) + if (dynamic_cast(&*index_helper) != nullptr) { store = std::make_shared(stream_name, data_part->data_part_storage, data_part_storage_builder, storage.getSettings()->max_digestion_size_per_segment); gin_index_stores[stream_name] = store; @@ -277,11 +277,11 @@ void MergeTreeDataPartWriterOnDisk::calculateAndSerializeSkipIndices(const Block WriteBuffer & marks_out = stream.compress_marks ? stream.marks_compressed_hashing : stream.marks_hashing; GinIndexStorePtr store = nullptr; - if(dynamic_cast(&*index_helper) != nullptr) + if (dynamic_cast(&*index_helper) != nullptr) { String stream_name = index_helper->getFileName(); auto it = gin_index_stores.find(stream_name); - if(it == gin_index_stores.cend()) + if (it == gin_index_stores.cend()) { throw Exception("Index '" + stream_name + "' does not exist", ErrorCodes::LOGICAL_ERROR); } diff --git a/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp b/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp index b7aad2d9a026..803a929430e9 100644 --- a/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp +++ b/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp @@ -1736,7 +1736,7 @@ MarkRanges MergeTreeDataSelectExecutor::filterMarksUsingIndex( auto gin_filter_condition = dynamic_cast(&*condition); bool result{false}; - if(!gin_filter_condition) + if (!gin_filter_condition) result = condition->mayBeTrueOnGranule(granule); else result = gin_filter_condition->mayBeTrueOnGranuleInPart(granule, cache_in_store); diff --git a/src/Storages/MergeTree/MergeTreeIndexGin.cpp b/src/Storages/MergeTree/MergeTreeIndexGin.cpp index bbe1c59df6d6..193570020970 100644 --- a/src/Storages/MergeTree/MergeTreeIndexGin.cpp +++ b/src/Storages/MergeTree/MergeTreeIndexGin.cpp @@ -112,16 +112,16 @@ MergeTreeIndexGranulePtr MergeTreeIndexAggregatorGinFilter::getGranuleAndReset() void MergeTreeIndexAggregatorGinFilter::addToGinFilter(UInt32 rowID, const char* data, size_t length, GinFilter& gin_filter) { - size_t cur = 0; - size_t token_start = 0; - size_t token_len = 0; - - int offset = 0; - while (cur < length && token_extractor->nextInStringPadded(data, length, &cur, &token_start, &token_len)) - { - gin_filter.add(data + token_start, token_len, rowID, store); - offset++; - } + size_t cur = 0; + size_t token_start = 0; + size_t token_len = 0; + + int offset = 0; + while (cur < length && token_extractor->nextInStringPadded(data, length, &cur, &token_start, &token_len)) + { + gin_filter.add(data + token_start, token_len, rowID, store); + offset++; + } } void MergeTreeIndexAggregatorGinFilter::update(const Block & block, size_t * pos, size_t limit) @@ -736,7 +736,7 @@ MergeTreeIndexPtr ginIndexCreator( GinFilterParameters params(n); /// Use SplitTokenExtractor when n is 0, otherwise use NgramTokenExtractor - if(n > 0) + if (n > 0) { auto tokenizer = std::make_unique(n); return std::make_shared(index, params, std::move(tokenizer)); @@ -774,7 +774,7 @@ void ginIndexValidator(const IndexDescription & index, bool /*attach*/) throw Exception("Gin filter index can be used only with `String`, `FixedString`, `LowCardinality(String)`, `LowCardinality(FixedString)` column or Array with `String` or `FixedString` values column.", ErrorCodes::INCORRECT_QUERY); } - if(index.type != GinFilter::getName()) + if (index.type != GinFilter::getName()) throw Exception("Unknown index type: " + backQuote(index.name), ErrorCodes::LOGICAL_ERROR); if (index.arguments.size() > 1) diff --git a/tests/queries/0_stateless/02346_full_text_search.reference b/tests/queries/0_stateless/02346_full_text_search.reference index 6b1fcb818e15..1038c5c1ca5b 100644 --- a/tests/queries/0_stateless/02346_full_text_search.reference +++ b/tests/queries/0_stateless/02346_full_text_search.reference @@ -18,4 +18,4 @@ af gin 2 1 af gin 5 BC614E,05397FB1,2D2D2D2D2D2D1540,CF3304 -6 1 +7 1 diff --git a/tests/queries/0_stateless/02346_full_text_search.sql b/tests/queries/0_stateless/02346_full_text_search.sql index 963704cbd77c..8910fd4e3709 100644 --- a/tests/queries/0_stateless/02346_full_text_search.sql +++ b/tests/queries/0_stateless/02346_full_text_search.sql @@ -17,9 +17,10 @@ SELECT * FROM simple1 WHERE s LIKE '%01%'; SYSTEM FLUSH LOGS; -- check the query only read 2 granules (4 rows total; each granule has 2 rows) SELECT read_rows, result_rows from system.query_log - where query_kind ='Select' and - endsWith(trimRight(query), 'SELECT * FROM simple1 WHERE s LIKE \'%01%\';') - and result_rows==2 limit 1; + where query_kind ='Select' + and current_database = currentDatabase() + and endsWith(trimRight(query), 'SELECT * FROM simple1 WHERE s LIKE \'%01%\';') + and result_rows==2 limit 1; -- create table for gin() DROP TABLE IF EXISTS simple2; @@ -37,7 +38,8 @@ SELECT * FROM simple2 WHERE hasToken(s, 'Alick'); SYSTEM FLUSH LOGS; -- check the query only read 4 granules (8 rows total; each granule has 2 rows) SELECT read_rows, result_rows from system.query_log - where query_kind ='Select' + where query_kind ='Select' + and current_database = currentDatabase() and endsWith(trimRight(query), 'SELECT * FROM simple2 WHERE hasToken(s, \'Alick\');') and result_rows==4 limit 1; @@ -58,6 +60,7 @@ SYSTEM FLUSH LOGS; -- check the query only read 3 granules (6 rows total; each granule has 2 rows) SELECT read_rows, result_rows from system.query_log where query_kind ='Select' + and current_database = currentDatabase() and endsWith(trimRight(query), 'SELECT * FROM simple3 WHERE s LIKE \'%01%\';') and result_rows==3 limit 1; @@ -73,7 +76,11 @@ SELECT name, type FROM system.data_skipping_indices where (table =='simple4') li SELECT * FROM simple4 WHERE s LIKE '%你好%'; SYSTEM FLUSH LOGS; -- check the query only read 1 granule (2 rows total; each granule has 2 rows) -SELECT read_rows, result_rows from system.query_log where query_kind ='Select' and endsWith(trimRight(query), 'SELECT * FROM simple4 WHERE s LIKE \'%你好%\';') and result_rows==1 limit 1; +SELECT read_rows, result_rows from system.query_log + where query_kind ='Select' + and current_database = currentDatabase() + and endsWith(trimRight(query), 'SELECT * FROM simple4 WHERE s LIKE \'%你好%\';') + and result_rows==1 limit 1; -- create table with 1000000 rows DROP TABLE IF EXISTS hextable; @@ -92,6 +99,7 @@ SELECT name, type FROM system.data_skipping_indices where (table =='hextable') l select * from hextable where hasToken(s, '2D2D2D2D2D2D1540'); SYSTEM FLUSH LOGS; -- check the query only read 3 granules (6 rows total; each granule has 2 rows) -SELECT read_rows, result_rows from system.query_log - where query_kind ='Select' +SELECT read_rows, result_rows from system.query_log + where query_kind ='Select' + and current_database = currentDatabase() and hasToken(query, 'hextable') and result_rows==1 limit 1; From 3652740535fa3dfa7e738b249d4bf06061ea51d9 Mon Sep 17 00:00:00 2001 From: Larry Luo Date: Sun, 3 Jul 2022 05:44:34 -0700 Subject: [PATCH 11/42] Code clean up --- src/Common/tests/gtest_fst.cpp | 4 ++-- src/Interpreters/GinFilter.cpp | 4 ++-- src/Storages/MergeTree/GinIndexStore.h | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Common/tests/gtest_fst.cpp b/src/Common/tests/gtest_fst.cpp index b03a49c4c915..5fc63d386c3b 100644 --- a/src/Common/tests/gtest_fst.cpp +++ b/src/Common/tests/gtest_fst.cpp @@ -11,9 +11,9 @@ TEST(FST, SimpleTest) { vector> data { - {"mop", 100}, + {"mop", 100}, {"moth", 91}, - {"pop", 72}, + {"pop", 72}, {"star", 83}, {"stop", 54}, {"top", 55}, diff --git a/src/Interpreters/GinFilter.cpp b/src/Interpreters/GinFilter.cpp index e99d5016d794..d6b2358d85fe 100644 --- a/src/Interpreters/GinFilter.cpp +++ b/src/Interpreters/GinFilter.cpp @@ -147,7 +147,7 @@ bool GinFilter::matchInRange(const PostingsCachePtr& postings_cache, UInt32 segm return false; } auto min_in_container = container_it->second->minimum(); - auto max_in_container = container_it->second->maximum(); + auto max_in_container = container_it->second->maximum(); if (range_start > max_in_container || min_in_container > range_end) { return false; @@ -180,7 +180,7 @@ bool GinFilter::match(const PostingsCachePtr& postings_cache) const for (const auto &rowid_range: rowid_range_container) { match_result |= matchInRange(postings_cache, rowid_range.segment_id, rowid_range.range_start, rowid_range.range_end); - } + } return match_result; } diff --git a/src/Storages/MergeTree/GinIndexStore.h b/src/Storages/MergeTree/GinIndexStore.h index 8790100728c9..8b347b7de130 100644 --- a/src/Storages/MergeTree/GinIndexStore.h +++ b/src/Storages/MergeTree/GinIndexStore.h @@ -113,7 +113,7 @@ class GinIndexStore bool load(); - /// Check existance by checking the existence of file .gin_seg + /// Check existence by checking the existence of file .gin_seg bool exists() const; UInt32 getNextIDRange(const String &file_name, size_t n); @@ -194,7 +194,7 @@ struct PostingsCacheForStore { auto it {cache.find(query_string)}; - if(it == cache.cend()) + if (it == cache.cend()) { return nullptr; } From 6429013b277787764b9271c85722237e90200673 Mon Sep 17 00:00:00 2001 From: Larry Luo Date: Thu, 7 Jul 2022 22:43:53 -0400 Subject: [PATCH 12/42] Fix integration tests --- tests/queries/0_stateless/02346_full_text_search.reference | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/queries/0_stateless/02346_full_text_search.reference b/tests/queries/0_stateless/02346_full_text_search.reference index 1038c5c1ca5b..24028b89727a 100644 --- a/tests/queries/0_stateless/02346_full_text_search.reference +++ b/tests/queries/0_stateless/02346_full_text_search.reference @@ -18,4 +18,4 @@ af gin 2 1 af gin 5 BC614E,05397FB1,2D2D2D2D2D2D1540,CF3304 -7 1 +5 1 From 657ce7c61465a4cf0843fbf88487742a442d8a7c Mon Sep 17 00:00:00 2001 From: Larry Luo Date: Mon, 18 Jul 2022 20:07:57 -0700 Subject: [PATCH 13/42] Cleanup code and tests --- .../MergeTree/DataPartStorageOnDisk.h | 2 +- src/Storages/MergeTree/GinIndexStore.cpp | 17 +++++----- .../MergeTree/MergeTreeDataSelectExecutor.cpp | 2 +- src/Storages/MergeTree/MergeTreeIndexGin.cpp | 34 +++++++++---------- src/Storages/MergeTree/MergeTreeIndexGin.h | 2 +- .../0_stateless/02346_full_text_search.sql | 1 - 6 files changed, 28 insertions(+), 30 deletions(-) diff --git a/src/Storages/MergeTree/DataPartStorageOnDisk.h b/src/Storages/MergeTree/DataPartStorageOnDisk.h index 6080102b1390..3e82d44d71e5 100644 --- a/src/Storages/MergeTree/DataPartStorageOnDisk.h +++ b/src/Storages/MergeTree/DataPartStorageOnDisk.h @@ -113,7 +113,7 @@ class DataPartStorageOnDisk final : public IDataPartStorage void moveFile(const String & from_name, const String & to_name) override; void replaceFile(const String & from_name, const String & to_name) override; std::unique_ptr writeFile( - const String & path, + const String & name, size_t buf_size, DB::WriteMode mode, const WriteSettings & settings) override; diff --git a/src/Storages/MergeTree/GinIndexStore.cpp b/src/Storages/MergeTree/GinIndexStore.cpp index 591b18fe070d..5bbe68ca6121 100644 --- a/src/Storages/MergeTree/GinIndexStore.cpp +++ b/src/Storages/MergeTree/GinIndexStore.cpp @@ -29,7 +29,7 @@ bool GinIndexPostingsBuilder::contains(UInt32 row_id) const if (useRoaring()) return bmp.contains(row_id); - auto it(std::find(lst.begin(), lst.begin()+lst_length, row_id)); + const auto it(std::find(lst.begin(), lst.begin()+lst_length, row_id)); return it != lst.begin()+lst_length; } @@ -116,8 +116,7 @@ GinIndexPostingsListPtr GinIndexPostingsBuilder::deserialize(ReadBuffer &buffer) buffer.readStrict(reinterpret_cast(buf.get()), size); - GinIndexPostingsListPtr postings_list = std::shared_ptr - (new GinIndexPostingsList(GinIndexPostingsList::read(buf.get()))); + GinIndexPostingsListPtr postings_list = std::make_shared(GinIndexPostingsList::read(buf.get())); return postings_list; } @@ -207,7 +206,7 @@ bool GinIndexStore::needToWrite() const void GinIndexStore::finalize() { - if (current_postings.size() > 0) + if (!current_postings.empty()) { writeSegment(); } @@ -266,8 +265,8 @@ void GinIndexStore::writeSegment() current_index = 0; for (const auto& [token, postings_list] : token_postings_list_pairs) { - String strToken{token}; - builder.add(strToken, offset); + String str_token{token}; + builder.add(str_token, offset); offset += encoding_lengths[current_index++]; } @@ -315,7 +314,7 @@ void GinIndexStoreReader::readSegments() { init_file_streams(); } - segment_file_stream->read(reinterpret_cast(&segments[0]), segment_num * sizeof(GinIndexSegment)); + segment_file_stream->read(reinterpret_cast(segments.data()), segment_num * sizeof(GinIndexSegment)); for (size_t i = 0; i < segment_num; ++i) { auto seg_id = segments[i].segment_id; @@ -393,9 +392,9 @@ GinIndexStoreFactory& GinIndexStoreFactory::instance() GinIndexStorePtr GinIndexStoreFactory::get(const String& name, DataPartStoragePtr storage_) { - const String& part_path_ = storage_->getRelativePath(); + const String& part_path = storage_->getRelativePath(); std::lock_guard lock(stores_mutex); - String key = name + String(":")+part_path_; + String key = name + String(":")+part_path; GinIndexStores::const_iterator it = stores.find(key); diff --git a/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp b/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp index 803a929430e9..2b3dbba8a2e5 100644 --- a/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp +++ b/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp @@ -1708,6 +1708,7 @@ MarkRanges MergeTreeDataSelectExecutor::filterMarksUsingIndex( { if (index_mark != index_range.begin || !granule || last_index_mark != index_range.begin) granule = reader.read(); + const auto * gin_filter_condition = dynamic_cast(&*condition); // Cast to Ann condition auto ann_condition = std::dynamic_pointer_cast(condition); if (ann_condition != nullptr) @@ -1733,7 +1734,6 @@ MarkRanges MergeTreeDataSelectExecutor::filterMarksUsingIndex( } continue; } - auto gin_filter_condition = dynamic_cast(&*condition); bool result{false}; if (!gin_filter_condition) diff --git a/src/Storages/MergeTree/MergeTreeIndexGin.cpp b/src/Storages/MergeTree/MergeTreeIndexGin.cpp index 193570020970..85a96e31a358 100644 --- a/src/Storages/MergeTree/MergeTreeIndexGin.cpp +++ b/src/Storages/MergeTree/MergeTreeIndexGin.cpp @@ -56,9 +56,9 @@ void MergeTreeIndexGranuleGinFilter::serializeBinary(WriteBuffer & ostr) const for (const auto & gin_filter : gin_filters) { - size_t filterSize = gin_filter.getFilter().size(); - size_serialization->serializeBinary(filterSize, ostr); - ostr.write(reinterpret_cast(&gin_filter.getFilter()[0]), filterSize * sizeof(RowIDRange)); + size_t filter_size = gin_filter.getFilter().size(); + size_serialization->serializeBinary(filter_size, ostr); + ostr.write(reinterpret_cast(gin_filter.getFilter().data()), filter_size * sizeof(RowIDRange)); } } @@ -79,7 +79,7 @@ void MergeTreeIndexGranuleGinFilter::deserializeBinary(ReadBuffer & istr, MergeT continue; gin_filter.getFilter().assign(filterSize, {}); - istr.read(reinterpret_cast(&gin_filter.getFilter()[0]), filterSize * sizeof(RowIDRange)); + istr.read(reinterpret_cast(gin_filter.getFilter().data()), filterSize * sizeof(RowIDRange)); } has_elems = true; } @@ -132,8 +132,8 @@ void MergeTreeIndexAggregatorGinFilter::update(const Block & block, size_t * pos + toString(*pos) + ", Block rows: " + toString(block.rows()) + ".", ErrorCodes::LOGICAL_ERROR); size_t rows_read = std::min(limit, block.rows() - *pos); - auto rowID = store->getNextRowIDRange(rows_read); - auto startRowID = rowID; + auto row_id = store->getNextRowIDRange(rows_read); + auto start_row_id = row_id; for (size_t col = 0; col < index_columns.size(); ++col) { @@ -156,11 +156,11 @@ void MergeTreeIndexAggregatorGinFilter::update(const Block & block, size_t * pos for (size_t row_num = 0; row_num < elements_size; ++row_num) { auto ref = column_key.getDataAt(element_start_row + row_num); - addToGinFilter(rowID, ref.data, ref.size, granule->gin_filters[col]); + addToGinFilter(row_id, ref.data, ref.size, granule->gin_filters[col]); store->addSize(ref.size); } current_position += 1; - rowID++; + row_id++; if (store->needToWrite()) need_to_write = true; @@ -171,14 +171,14 @@ void MergeTreeIndexAggregatorGinFilter::update(const Block & block, size_t * pos for (size_t i = 0; i < rows_read; ++i) { auto ref = column->getDataAt(current_position + i); - addToGinFilter(rowID, ref.data, ref.size, granule->gin_filters[col]); + addToGinFilter(row_id, ref.data, ref.size, granule->gin_filters[col]); store->addSize(ref.size); - rowID++; + row_id++; if (store->needToWrite()) need_to_write = true; } } - granule->gin_filters[col].addRowRangeToGinFilter(store->getCurrentSegmentID(), startRowID, startRowID + rows_read - 1); + granule->gin_filters[col].addRowRangeToGinFilter(store->getCurrentSegmentID(), start_row_id, start_row_id + rows_read - 1); if (need_to_write) { store->writeSegment(); @@ -257,7 +257,7 @@ bool MergeTreeConditionGinFilter::alwaysUnknownOrTrue() const return rpn_stack[0]; } -bool MergeTreeConditionGinFilter::mayBeTrueOnGranuleInPart(MergeTreeIndexGranulePtr idx_granule,[[maybe_unused]] PostingsCacheForStore &cache_in_store) const +bool MergeTreeConditionGinFilter::mayBeTrueOnGranuleInPart(MergeTreeIndexGranulePtr idx_granule,[[maybe_unused]] PostingsCacheForStore &cache_store) const { std::shared_ptr granule = std::dynamic_pointer_cast(idx_granule); @@ -277,7 +277,7 @@ bool MergeTreeConditionGinFilter::mayBeTrueOnGranuleInPart(MergeTreeIndexGranule || element.function == RPNElement::FUNCTION_NOT_EQUALS || element.function == RPNElement::FUNCTION_HAS) { - rpn_stack.emplace_back(granule->gin_filters[element.key_column].contains(*element.gin_filter, cache_in_store), true); + rpn_stack.emplace_back(granule->gin_filters[element.key_column].contains(*element.gin_filter, cache_store), true); if (element.function == RPNElement::FUNCTION_NOT_EQUALS) rpn_stack.back() = !rpn_stack.back(); @@ -293,7 +293,7 @@ bool MergeTreeConditionGinFilter::mayBeTrueOnGranuleInPart(MergeTreeIndexGranule const auto & gin_filters = element.set_gin_filters[column]; for (size_t row = 0; row < gin_filters.size(); ++row) - result[row] = result[row] && granule->gin_filters[key_idx].contains(gin_filters[row], cache_in_store); + result[row] = result[row] && granule->gin_filters[key_idx].contains(gin_filters[row], cache_store); } rpn_stack.emplace_back( @@ -308,7 +308,7 @@ bool MergeTreeConditionGinFilter::mayBeTrueOnGranuleInPart(MergeTreeIndexGranule const auto & gin_filters = element.set_gin_filters[0]; for (size_t row = 0; row < gin_filters.size(); ++row) - result[row] = result[row] && granule->gin_filters[element.key_column].contains(gin_filters[row], cache_in_store); + result[row] = result[row] && granule->gin_filters[element.key_column].contains(gin_filters[row], cache_store); rpn_stack.emplace_back( std::find(std::cbegin(result), std::cend(result), true) != std::end(result), true); @@ -732,7 +732,7 @@ MergeTreeIndexPtr ginIndexCreator( { if (index.type == GinFilter::getName()) { - size_t n = index.arguments.size() == 0 ? 0 : index.arguments[0].get(); + size_t n = index.arguments.empty() ? 0 : index.arguments[0].get(); GinFilterParameters params(n); /// Use SplitTokenExtractor when n is 0, otherwise use NgramTokenExtractor @@ -783,7 +783,7 @@ void ginIndexValidator(const IndexDescription & index, bool /*attach*/) if (index.arguments.size() == 1 and index.arguments[0].getType() != Field::Types::UInt64) throw Exception("Gin index argument must be positive integer.", ErrorCodes::INCORRECT_QUERY); - size_t ngrams = index.arguments.size() == 0 ? 0 : index.arguments[0].get(); + size_t ngrams = index.arguments.empty() ? 0 : index.arguments[0].get(); /// Just validate GinFilterParameters params(ngrams); diff --git a/src/Storages/MergeTree/MergeTreeIndexGin.h b/src/Storages/MergeTree/MergeTreeIndexGin.h index acb79e2bd81b..68c36fdfec35 100644 --- a/src/Storages/MergeTree/MergeTreeIndexGin.h +++ b/src/Storages/MergeTree/MergeTreeIndexGin.h @@ -171,7 +171,7 @@ class MergeTreeIndexGinFilter final : public IMergeTreeIndex MergeTreeIndexGranulePtr createIndexGranule() const override; MergeTreeIndexAggregatorPtr createIndexAggregator() const override; - MergeTreeIndexAggregatorPtr createIndexAggregatorForPart(const GinIndexStorePtr &partIndexInfo) const override; + MergeTreeIndexAggregatorPtr createIndexAggregatorForPart(const GinIndexStorePtr &store) const override; MergeTreeIndexConditionPtr createIndexCondition( const SelectQueryInfo & query, ContextPtr context) const override; diff --git a/tests/queries/0_stateless/02346_full_text_search.sql b/tests/queries/0_stateless/02346_full_text_search.sql index 8910fd4e3709..4ec7e49e489e 100644 --- a/tests/queries/0_stateless/02346_full_text_search.sql +++ b/tests/queries/0_stateless/02346_full_text_search.sql @@ -1,6 +1,5 @@ SET log_queries = 1; SET max_threads = 1; -TRUNCATE system.query_log; -- create table for gin(2) DROP TABLE IF EXISTS simple1; From 7b850b6d93fef8166aec1f834fe24c4f87135b5a Mon Sep 17 00:00:00 2001 From: Larry Luo Date: Tue, 19 Jul 2022 04:01:09 -0700 Subject: [PATCH 14/42] Refactoring --- src/Common/FST.h | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Common/FST.h b/src/Common/FST.h index 0d9e2022ef05..8d626b51a16c 100644 --- a/src/Common/FST.h +++ b/src/Common/FST.h @@ -69,13 +69,15 @@ struct State UInt64 id{ 0 }; UInt64 state_index{ 0 }; + struct Flag_Values + { + unsigned int is_final : 1; + unsigned int encoding_method : 2; + }; + union { - struct - { - unsigned int is_final : 1; - unsigned int encoding_method : 2; - } flag_values; + Flag_Values flag_values; uint8_t flag{0}; }; std::map arcs; From 1d9bf9962cd0f5728f67acb9b33b3ca72bc627dd Mon Sep 17 00:00:00 2001 From: Larry Luo Date: Tue, 19 Jul 2022 13:15:59 -0700 Subject: [PATCH 15/42] Code clean up --- src/Common/FST.cpp | 31 ++++++++++---------- src/Interpreters/GinFilter.cpp | 22 +++++++------- src/Interpreters/GinFilter.h | 4 +-- src/Storages/MergeTree/MergeTreeIndexGin.cpp | 8 ++--- 4 files changed, 33 insertions(+), 32 deletions(-) diff --git a/src/Common/FST.cpp b/src/Common/FST.cpp index 220be489f9d3..98fff7f39558 100644 --- a/src/Common/FST.cpp +++ b/src/Common/FST.cpp @@ -47,7 +47,7 @@ void ArcsBitmap::addArc(char label) int ArcsBitmap::getIndex(char label) const { - int bitCount = 0; + int bit_count = 0; uint8_t index = label; int which_int64 = 0; @@ -57,26 +57,27 @@ int ArcsBitmap::getIndex(char label) const { UInt64 mask = index == 63 ? (-1) : (1ULL << (index+1)) - 1; - bitCount += PopCountImpl(mask & data.items[which_int64]); + bit_count += PopCountImpl(mask & data.items[which_int64]); break; } index -= 64; - bitCount += PopCountImpl(data.items[which_int64]); + bit_count += PopCountImpl(data.items[which_int64]); which_int64++; } - return bitCount; + return bit_count; } int ArcsBitmap::getArcNum() const { - int bitCount = 0; + int bit_count = 0; for (size_t i = 0; i < 4; i++) + for (unsigned long item : data.items) { - if (data.items[i]) - bitCount += PopCountImpl(data.items[i]); + if (item) + bit_count += PopCountImpl(item); } - return bitCount; + return bit_count; } bool ArcsBitmap::hasArc(char label) const @@ -110,10 +111,10 @@ UInt64 State::hash() const values.push_back('C'); values.push_back('H'); - for (auto& label_arc : arcs) + for (const auto& label_arc : arcs) { values.push_back(label_arc.first); - auto ptr = reinterpret_cast(&label_arc.second.output); + const auto * ptr = reinterpret_cast(&label_arc.second.output); std::copy(ptr, ptr + sizeof(Output), std::back_inserter(values)); ptr = reinterpret_cast(&label_arc.second.target->id); @@ -125,7 +126,7 @@ UInt64 State::hash() const bool operator== (const State& state1, const State& state2) { - for (auto& label_arc : state1.arcs) + for (const auto& label_arc : state1.arcs) { auto it(state2.arcs.find(label_arc.first)); if (it == state2.arcs.cend()) @@ -206,9 +207,9 @@ UInt64 State::serialize(WriteBuffer& write_buffer) FSTBuilder::FSTBuilder(WriteBuffer& write_buffer_) : write_buffer(write_buffer_) { - for (size_t i = 0; i < temp_states.size(); ++i) + for (auto & temp_state : temp_states) { - temp_states[i] = std::make_shared(); + temp_state = std::make_shared(); } } @@ -293,7 +294,7 @@ void FSTBuilder::add(const std::string& current_word, Output current_output) /// Adjust outputs on the arcs for (size_t j = 1; j <= prefix_length_plus1 - 1; ++j) { - auto arc_ptr = temp_states[j - 1]->getArc(current_word[j-1]); + auto * arc_ptr = temp_states[j - 1]->getArc(current_word[j-1]); assert(arc_ptr != nullptr); auto common_prefix = std::min(arc_ptr->output, current_output); @@ -314,7 +315,7 @@ void FSTBuilder::add(const std::string& current_word, Output current_output) } /// Set last temp state's output - auto arc = temp_states[prefix_length_plus1 - 1]->getArc(current_word[prefix_length_plus1-1]); + auto * arc = temp_states[prefix_length_plus1 - 1]->getArc(current_word[prefix_length_plus1-1]); assert(arc != nullptr); arc->output = current_output; diff --git a/src/Interpreters/GinFilter.cpp b/src/Interpreters/GinFilter.cpp index d6b2358d85fe..a99b19fe08ab 100644 --- a/src/Interpreters/GinFilter.cpp +++ b/src/Interpreters/GinFilter.cpp @@ -57,7 +57,7 @@ void GinFilter::add(const char* data, size_t len, UInt32 rowID, GinIndexStorePtr void GinFilter::addRowRangeToGinFilter(UInt32 segmentID, UInt32 rowIDStart, UInt32 rowIDEnd) { - if (rowid_range_container.size() > 0) + if (!rowid_range_container.empty()) { /// Try to merge the rowID range with the last one in the container if (rowid_range_container.back().segment_id == segmentID && @@ -98,9 +98,9 @@ void dumpPostingsCache(const PostingsCache& postings_cache) { printf("-----segment id = %d ---------\n", segment_id); printf("-----postings-list: "); - for (auto it = postings_list->begin(); it != postings_list->end(); ++it) + for (unsigned int it : *postings_list) { - printf("%d ", *it); + printf("%d ", it); } printf("\n"); } @@ -120,19 +120,19 @@ void dumpPostingsCacheForStore(const PostingsCacheForStore& cache_store) bool GinFilter::hasEmptyPostingsList(const PostingsCachePtr& postings_cache) { - if (postings_cache->size() == 0) + if (postings_cache->empty()) return true; for (const auto& term_postings : *postings_cache) { const SegmentedPostingsListContainer& container = term_postings.second; - if (container.size() == 0) + if (container.empty()) return true; } return false; } -bool GinFilter::matchInRange(const PostingsCachePtr& postings_cache, UInt32 segment_id, UInt32 range_start, UInt32 range_end) const +bool GinFilter::matchInRange(const PostingsCachePtr& postings_cache, UInt32 segment_id, UInt32 range_start, UInt32 range_end) { /// Check for each terms GinIndexPostingsList intersection_result; @@ -187,7 +187,7 @@ bool GinFilter::match(const PostingsCachePtr& postings_cache) const bool GinFilter::needsFilter() const { - if (getTerms().size() == 0) + if (getTerms().empty()) return false; for (const auto & term: getTerms()) @@ -199,7 +199,7 @@ bool GinFilter::needsFilter() const return true; } -bool GinFilter::contains(const GinFilter & af, PostingsCacheForStore &cache_store) +bool GinFilter::contains(const GinFilter & af, PostingsCacheForStore &cache_store) const { if (!af.needsFilter()) return true; @@ -213,10 +213,10 @@ bool GinFilter::contains(const GinFilter & af, PostingsCacheForStore &cache_stor cache_store.cache[af.getMatchString()] = postings_cache; } - if (!match(postings_cache)) - return false; + if (match(postings_cache)) + return true; - return true; + return false; } const String& GinFilter::getName() diff --git a/src/Interpreters/GinFilter.h b/src/Interpreters/GinFilter.h index 6d5dad1869f8..f3189003c29e 100644 --- a/src/Interpreters/GinFilter.h +++ b/src/Interpreters/GinFilter.h @@ -40,7 +40,7 @@ class GinFilter size_t size() const { return rowid_range_container.size(); } - bool contains(const GinFilter& af, PostingsCacheForStore &store); + bool contains(const GinFilter& af, PostingsCacheForStore &store) const; const RowIDRangeContainer& getFilter() const { return rowid_range_container; } @@ -77,7 +77,7 @@ class GinFilter private: static bool hasEmptyPostingsList(const PostingsCachePtr& postings_cache); - bool matchInRange(const PostingsCachePtr& postings_cache, UInt32 segment_id, UInt32 range_start, UInt32 range_end) const; + static bool matchInRange(const PostingsCachePtr& postings_cache, UInt32 segment_id, UInt32 range_start, UInt32 range_end); }; using GinFilterPtr = std::shared_ptr; diff --git a/src/Storages/MergeTree/MergeTreeIndexGin.cpp b/src/Storages/MergeTree/MergeTreeIndexGin.cpp index 85a96e31a358..4ec1373fd572 100644 --- a/src/Storages/MergeTree/MergeTreeIndexGin.cpp +++ b/src/Storages/MergeTree/MergeTreeIndexGin.cpp @@ -73,13 +73,13 @@ void MergeTreeIndexGranuleGinFilter::deserializeBinary(ReadBuffer & istr, MergeT for (auto & gin_filter : gin_filters) { size_type->getDefaultSerialization()->deserializeBinary(field_rows, istr); - size_t filterSize = field_rows.get(); + size_t filter_size = field_rows.get(); - if (filterSize == 0) + if (filter_size == 0) continue; - gin_filter.getFilter().assign(filterSize, {}); - istr.read(reinterpret_cast(gin_filter.getFilter().data()), filterSize * sizeof(RowIDRange)); + gin_filter.getFilter().assign(filter_size, {}); + istr.read(reinterpret_cast(gin_filter.getFilter().data()), filter_size * sizeof(RowIDRange)); } has_elems = true; } From 514e17d0372d0a336ca79d1191cfc3df85a621a4 Mon Sep 17 00:00:00 2001 From: Larry Luo Date: Tue, 19 Jul 2022 16:57:33 -0700 Subject: [PATCH 16/42] Code clean up --- src/Common/FST.cpp | 5 ++--- src/Common/FST.h | 2 +- src/Interpreters/GinFilter.cpp | 3 ++- src/Interpreters/GinFilter.h | 2 +- src/Storages/MergeTree/GinIndexStore.cpp | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Common/FST.cpp b/src/Common/FST.cpp index 98fff7f39558..47ecda68112a 100644 --- a/src/Common/FST.cpp +++ b/src/Common/FST.cpp @@ -17,7 +17,7 @@ namespace ErrorCodes extern const int LOGICAL_ERROR; }; -UInt16 Arc::serialize(WriteBuffer& write_buffer) +UInt16 Arc::serialize(WriteBuffer& write_buffer) const { UInt16 written_bytes = 0; bool has_output = output != 0; @@ -71,8 +71,7 @@ int ArcsBitmap::getIndex(char label) const int ArcsBitmap::getArcNum() const { int bit_count = 0; - for (size_t i = 0; i < 4; i++) - for (unsigned long item : data.items) + for (uint64_t item : data.items) { if (item) bit_count += PopCountImpl(item); diff --git a/src/Common/FST.h b/src/Common/FST.h index 8d626b51a16c..cb29b95b7bfa 100644 --- a/src/Common/FST.h +++ b/src/Common/FST.h @@ -24,7 +24,7 @@ struct Arc Output output{0}; StatePtr target; - UInt16 serialize(WriteBuffer& write_buffer); + UInt16 serialize(WriteBuffer& write_buffer) const; }; class ArcsBitmap diff --git a/src/Interpreters/GinFilter.cpp b/src/Interpreters/GinFilter.cpp index a99b19fe08ab..7a70ec325700 100644 --- a/src/Interpreters/GinFilter.cpp +++ b/src/Interpreters/GinFilter.cpp @@ -208,13 +208,14 @@ bool GinFilter::contains(const GinFilter & af, PostingsCacheForStore &cache_stor if (postings_cache == nullptr) { GinIndexStoreReader reader(cache_store.store); - postings_cache = reader.loadPostingsIntoCache(af.getTerms()); cache_store.cache[af.getMatchString()] = postings_cache; } if (match(postings_cache)) + { return true; + } return false; } diff --git a/src/Interpreters/GinFilter.h b/src/Interpreters/GinFilter.h index f3189003c29e..eceeffaaacab 100644 --- a/src/Interpreters/GinFilter.h +++ b/src/Interpreters/GinFilter.h @@ -32,7 +32,7 @@ class GinFilter explicit GinFilter(const GinFilterParameters& params); - void add(const char* data, size_t len, UInt32 rowID, GinIndexStorePtr& store); + static void add(const char* data, size_t len, UInt32 rowID, GinIndexStorePtr& store); void addRowRangeToGinFilter(UInt32 segmentID, UInt32 rowIDStart, UInt32 rowIDEnd); diff --git a/src/Storages/MergeTree/GinIndexStore.cpp b/src/Storages/MergeTree/GinIndexStore.cpp index 5bbe68ca6121..ac9cc10c39be 100644 --- a/src/Storages/MergeTree/GinIndexStore.cpp +++ b/src/Storages/MergeTree/GinIndexStore.cpp @@ -29,7 +29,7 @@ bool GinIndexPostingsBuilder::contains(UInt32 row_id) const if (useRoaring()) return bmp.contains(row_id); - const auto it(std::find(lst.begin(), lst.begin()+lst_length, row_id)); + const auto *const it(std::find(lst.begin(), lst.begin()+lst_length, row_id)); return it != lst.begin()+lst_length; } From 62cd376efabf7aee79e9064d5a9de4fe52ed6a01 Mon Sep 17 00:00:00 2001 From: Larry Luo Date: Tue, 19 Jul 2022 19:31:32 -0700 Subject: [PATCH 17/42] Code clean up --- src/Interpreters/GinFilter.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/Interpreters/GinFilter.cpp b/src/Interpreters/GinFilter.cpp index 7a70ec325700..0ab1455c3ec7 100644 --- a/src/Interpreters/GinFilter.cpp +++ b/src/Interpreters/GinFilter.cpp @@ -212,12 +212,7 @@ bool GinFilter::contains(const GinFilter & af, PostingsCacheForStore &cache_stor cache_store.cache[af.getMatchString()] = postings_cache; } - if (match(postings_cache)) - { - return true; - } - - return false; + return match(postings_cache); } const String& GinFilter::getName() From cbe1b156b2d73d46d1254632471395ea6df307a8 Mon Sep 17 00:00:00 2001 From: Larry Luo Date: Wed, 20 Jul 2022 17:49:54 -0700 Subject: [PATCH 18/42] Improved test reliability --- tests/queries/0_stateless/02346_full_text_search.sql | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/queries/0_stateless/02346_full_text_search.sql b/tests/queries/0_stateless/02346_full_text_search.sql index 4ec7e49e489e..eeb2266e3934 100644 --- a/tests/queries/0_stateless/02346_full_text_search.sql +++ b/tests/queries/0_stateless/02346_full_text_search.sql @@ -1,5 +1,4 @@ SET log_queries = 1; -SET max_threads = 1; -- create table for gin(2) DROP TABLE IF EXISTS simple1; From 7df39d931865169cef9bc10cd12ef7030bff786d Mon Sep 17 00:00:00 2001 From: Larry Luo Date: Thu, 21 Jul 2022 12:29:40 -0700 Subject: [PATCH 19/42] Generated consistent results in test --- .../02346_full_text_search.reference | 7 ---- .../0_stateless/02346_full_text_search.sql | 32 +++---------------- 2 files changed, 5 insertions(+), 34 deletions(-) diff --git a/tests/queries/0_stateless/02346_full_text_search.reference b/tests/queries/0_stateless/02346_full_text_search.reference index 24028b89727a..690c30feecb9 100644 --- a/tests/queries/0_stateless/02346_full_text_search.reference +++ b/tests/queries/0_stateless/02346_full_text_search.reference @@ -1,21 +1,14 @@ af gin 101 Alick a01 111 Alick b01 -4 2 af gin 101 Alick a01 106 Alick a06 111 Alick b01 116 Alick b06 -8 4 af gin 101 Alick a01 111 Alick b01 201 rick c01 -6 3 af gin 102 clickhouse你好 -2 1 -af gin -5 BC614E,05397FB1,2D2D2D2D2D2D1540,CF3304 -5 1 diff --git a/tests/queries/0_stateless/02346_full_text_search.sql b/tests/queries/0_stateless/02346_full_text_search.sql index eeb2266e3934..84103a95acb6 100644 --- a/tests/queries/0_stateless/02346_full_text_search.sql +++ b/tests/queries/0_stateless/02346_full_text_search.sql @@ -11,7 +11,7 @@ INSERT INTO simple1 VALUES (101, 'Alick a01'), (102, 'Blick a02'), (103, 'Click SELECT name, type FROM system.data_skipping_indices where (table =='simple1') limit 1; -- search gin index -SELECT * FROM simple1 WHERE s LIKE '%01%'; +SELECT * FROM simple1 WHERE s LIKE '%01%' order by k; SYSTEM FLUSH LOGS; -- check the query only read 2 granules (4 rows total; each granule has 2 rows) SELECT read_rows, result_rows from system.query_log @@ -32,7 +32,7 @@ INSERT INTO simple2 VALUES (101, 'Alick a01'), (102, 'Blick a02'), (103, 'Click -- check gin index was created SELECT name, type FROM system.data_skipping_indices where (table =='simple2') limit 1; -- search gin index -SELECT * FROM simple2 WHERE hasToken(s, 'Alick'); +SELECT * FROM simple2 WHERE hasToken(s, 'Alick') order by k; SYSTEM FLUSH LOGS; -- check the query only read 4 granules (8 rows total; each granule has 2 rows) SELECT read_rows, result_rows from system.query_log @@ -53,7 +53,7 @@ INSERT INTO simple3 VALUES (201, 'rick c01'), (202, 'mick c02'),(203, 'nick c03' -- check gin index was created SELECT name, type FROM system.data_skipping_indices where (table =='simple3') limit 1; -- search gin index -SELECT * FROM simple3 WHERE s LIKE '%01%'; +SELECT * FROM simple3 WHERE s LIKE '%01%' order by k; SYSTEM FLUSH LOGS; -- check the query only read 3 granules (6 rows total; each granule has 2 rows) SELECT read_rows, result_rows from system.query_log @@ -71,33 +71,11 @@ INSERT INTO simple4 VALUES (101, 'Alick 好'),(102, 'clickhouse你好'), (103, ' -- check gin index was created SELECT name, type FROM system.data_skipping_indices where (table =='simple4') limit 1; -- search gin index -SELECT * FROM simple4 WHERE s LIKE '%你好%'; +SELECT * FROM simple4 WHERE s LIKE '%你好%' order by k; SYSTEM FLUSH LOGS; -- check the query only read 1 granule (2 rows total; each granule has 2 rows) SELECT read_rows, result_rows from system.query_log where query_kind ='Select' and current_database = currentDatabase() and endsWith(trimRight(query), 'SELECT * FROM simple4 WHERE s LIKE \'%你好%\';') - and result_rows==1 limit 1; - --- create table with 1000000 rows -DROP TABLE IF EXISTS hextable; -CREATE TABLE hextable(k UInt64,s String,INDEX af(s) TYPE gin(0) GRANULARITY 1) - Engine=MergeTree - ORDER BY (k) - SETTINGS index_granularity = 1024 - AS - SELECT - number, - format('{},{},{},{}', hex(12345678), hex(87654321), hex(number/17 + 5), hex(13579012)) as s - FROM numbers(1000000); --- check gin index was created -SELECT name, type FROM system.data_skipping_indices where (table =='hextable') limit 1; --- search gin index -select * from hextable where hasToken(s, '2D2D2D2D2D2D1540'); -SYSTEM FLUSH LOGS; --- check the query only read 3 granules (6 rows total; each granule has 2 rows) -SELECT read_rows, result_rows from system.query_log - where query_kind ='Select' - and current_database = currentDatabase() - and hasToken(query, 'hextable') and result_rows==1 limit 1; + and result_rows==1 limit 1; \ No newline at end of file From 0a47378e5f66920bfc50dabf925ba4fae6560f1e Mon Sep 17 00:00:00 2001 From: Larry Luo Date: Sun, 31 Jul 2022 19:25:58 -0700 Subject: [PATCH 20/42] Added initializer for current segment --- src/Storages/MergeTree/GinIndexStore.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Storages/MergeTree/GinIndexStore.h b/src/Storages/MergeTree/GinIndexStore.h index 8b347b7de130..11dccc247880 100644 --- a/src/Storages/MergeTree/GinIndexStore.h +++ b/src/Storages/MergeTree/GinIndexStore.h @@ -167,7 +167,7 @@ class GinIndexStore GinIndexPostingsBuilderContainer current_postings; /// The following is for segmentation of Gin index - GinIndexSegment current_segment; + GinIndexSegment current_segment{}; UInt64 current_size{0}; UInt64 max_digestion_size{0}; From 7e5f311da9f458f7abdfc6f748b6a6e46a7ef8bb Mon Sep 17 00:00:00 2001 From: Larry Luo Date: Wed, 3 Aug 2022 13:42:50 -0700 Subject: [PATCH 21/42] Fix MemorySanitizer error --- src/Storages/MergeTree/GinIndexStore.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Storages/MergeTree/GinIndexStore.h b/src/Storages/MergeTree/GinIndexStore.h index 11dccc247880..e2c5a6f31206 100644 --- a/src/Storages/MergeTree/GinIndexStore.h +++ b/src/Storages/MergeTree/GinIndexStore.h @@ -19,9 +19,6 @@ constexpr int MIN_SIZE_FOR_ROARING_ENCODING = 16; /// Gin Index Segment information, which contains: struct GinIndexSegment { - /// Segment ID retrieved from next available ID from file .gin_sid - UInt32 segment_id {0}; - /// .gin_post file offset of this segment's postings lists UInt64 postings_start_offset{0}; @@ -30,6 +27,9 @@ struct GinIndexSegment /// Next row ID for this segment UInt32 next_row_id{1}; + + /// Segment ID retrieved from next available ID from file .gin_sid + UInt32 segment_id {0}; }; using GinIndexSegments = std::vector; From a33232cb0a2b948c0a6c60aae8ceb193037e7a4d Mon Sep 17 00:00:00 2001 From: Larry Luo Date: Thu, 4 Aug 2022 14:40:30 -0700 Subject: [PATCH 22/42] Clean up code --- src/Storages/MergeTree/MergeTreeIndexGin.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Storages/MergeTree/MergeTreeIndexGin.cpp b/src/Storages/MergeTree/MergeTreeIndexGin.cpp index 4ec1373fd572..2bf4528546ae 100644 --- a/src/Storages/MergeTree/MergeTreeIndexGin.cpp +++ b/src/Storages/MergeTree/MergeTreeIndexGin.cpp @@ -116,11 +116,9 @@ void MergeTreeIndexAggregatorGinFilter::addToGinFilter(UInt32 rowID, const char* size_t token_start = 0; size_t token_len = 0; - int offset = 0; while (cur < length && token_extractor->nextInStringPadded(data, length, &cur, &token_start, &token_len)) { gin_filter.add(data + token_start, token_len, rowID, store); - offset++; } } From da93210cc64a406c19bada149bb75d6d3504d473 Mon Sep 17 00:00:00 2001 From: Larry Luo Date: Thu, 25 Aug 2022 05:54:06 -0700 Subject: [PATCH 23/42] Updated MergeTreeConditionGinFilter due to SelectQueryInfo changes. --- src/Storages/MergeTree/MergeTreeIndexGin.cpp | 10 +++------- src/Storages/MergeTree/MergeTreeIndexGin.h | 2 +- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/Storages/MergeTree/MergeTreeIndexGin.cpp b/src/Storages/MergeTree/MergeTreeIndexGin.cpp index 2bf4528546ae..840dfd108959 100644 --- a/src/Storages/MergeTree/MergeTreeIndexGin.cpp +++ b/src/Storages/MergeTree/MergeTreeIndexGin.cpp @@ -197,7 +197,7 @@ MergeTreeConditionGinFilter::MergeTreeConditionGinFilter( , index_data_types(index_sample_block.getNamesAndTypesList().getTypes()) , params(params_) , token_extractor(token_extactor_) - , prepared_sets(query_info.sets) + , prepared_sets(query_info.prepared_sets) { rpn = std::move( RPNBuilder( @@ -660,12 +660,8 @@ bool MergeTreeConditionGinFilter::tryPrepareSetGinFilter( else set_key = PreparedSetKey::forLiteral(*right_arg, data_types); - auto set_it = prepared_sets.find(set_key); - if (set_it == prepared_sets.end()) - return false; - - const SetPtr & prepared_set = set_it->second; - if (!prepared_set->hasExplicitSetElements()) + const SetPtr & prepared_set = prepared_sets->get(set_key); + if (!prepared_set || !prepared_set->hasExplicitSetElements()) return false; for (const auto & data_type : prepared_set->getDataTypes()) diff --git a/src/Storages/MergeTree/MergeTreeIndexGin.h b/src/Storages/MergeTree/MergeTreeIndexGin.h index 68c36fdfec35..f344210f959a 100644 --- a/src/Storages/MergeTree/MergeTreeIndexGin.h +++ b/src/Storages/MergeTree/MergeTreeIndexGin.h @@ -153,7 +153,7 @@ class MergeTreeConditionGinFilter final : public IMergeTreeIndexCondition TokenExtractorPtr token_extractor; RPN rpn; /// Sets from syntax analyzer. - PreparedSets prepared_sets; + PreparedSetsPtr prepared_sets; }; class MergeTreeIndexGinFilter final : public IMergeTreeIndex From 64f807f31aa81fa16fa41404dc83301169f67a38 Mon Sep 17 00:00:00 2001 From: Larry Luo Date: Mon, 29 Aug 2022 19:58:42 -0700 Subject: [PATCH 24/42] fixed an edge case that index column has no data --- src/Storages/MergeTree/GinIndexStore.cpp | 2 +- src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Storages/MergeTree/GinIndexStore.cpp b/src/Storages/MergeTree/GinIndexStore.cpp index ac9cc10c39be..ccad54ab1d37 100644 --- a/src/Storages/MergeTree/GinIndexStore.cpp +++ b/src/Storages/MergeTree/GinIndexStore.cpp @@ -403,7 +403,7 @@ GinIndexStorePtr GinIndexStoreFactory::get(const String& name, DataPartStoragePt GinIndexStorePtr store = std::make_shared(name); store->SetStorage(storage_); if (!store->exists()) - throw Exception("Index '" + name + "' does not exist", ErrorCodes::LOGICAL_ERROR); + return nullptr; //gin index was not generated for the part due to no data or the length of column data is less than ngramsize GinIndexStoreReader reader(store); reader.readSegments(); diff --git a/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp b/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp index 2b3dbba8a2e5..9b49e996aa99 100644 --- a/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp +++ b/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp @@ -1739,7 +1739,7 @@ MarkRanges MergeTreeDataSelectExecutor::filterMarksUsingIndex( if (!gin_filter_condition) result = condition->mayBeTrueOnGranule(granule); else - result = gin_filter_condition->mayBeTrueOnGranuleInPart(granule, cache_in_store); + result = cache_in_store.store ? gin_filter_condition->mayBeTrueOnGranuleInPart(granule, cache_in_store) : true; if (!result) { From 00e4411740fd9069ce3c61200cb1588c71efd7e8 Mon Sep 17 00:00:00 2001 From: Larry Luo Date: Wed, 7 Sep 2022 11:22:09 -0700 Subject: [PATCH 25/42] Created FST namespace to hide FST details --- src/Common/FST.cpp | 20 ++- src/Common/FST.h | 207 ++++++++++++----------- src/Common/tests/gtest_fst.cpp | 4 +- src/Interpreters/GinFilter.cpp | 4 +- src/Storages/MergeTree/GinIndexStore.cpp | 2 +- src/Storages/MergeTree/GinIndexStore.h | 2 +- 6 files changed, 126 insertions(+), 113 deletions(-) diff --git a/src/Common/FST.cpp b/src/Common/FST.cpp index 47ecda68112a..4b4b99804624 100644 --- a/src/Common/FST.cpp +++ b/src/Common/FST.cpp @@ -1,14 +1,14 @@ +#include "FST.h" +#include #include #include #include #include -#include #include <../contrib/consistent-hashing/popcount.h> -#include #include -#include +#include #include -#include "FST.h" +#include namespace DB { @@ -17,6 +17,9 @@ namespace ErrorCodes extern const int LOGICAL_ERROR; }; +namespace FST +{ + UInt16 Arc::serialize(WriteBuffer& write_buffer) const { UInt16 written_bytes = 0; @@ -150,7 +153,7 @@ UInt64 State::serialize(WriteBuffer& write_buffer) { /// Serialize all labels std::vector labels; - labels.reserve(MAX_ARCS_IN_SEQUENTIAL_METHID); + labels.reserve(MAX_ARCS_IN_SEQUENTIAL_METHOD); for (auto& label_state : arcs) { @@ -335,16 +338,16 @@ UInt64 FSTBuilder::build() return previous_state_index + previous_written_bytes + length + 1; } -FST::FST(std::vector&& data_) : data(data_) +FiniteStateTransducer::FiniteStateTransducer(std::vector&& data_) : data(data_) { } -void FST::clear() +void FiniteStateTransducer::clear() { data.clear(); } -std::pair FST::getOutput(const String& term) +std::pair FiniteStateTransducer::getOutput(const String& term) { std::pair result_output{ false, 0 }; /// Read index of initial state @@ -443,3 +446,4 @@ std::pair FST::getOutput(const String& term) return result_output; } } +} diff --git a/src/Common/FST.h b/src/Common/FST.h index cb29b95b7bfa..1fbba4505e44 100644 --- a/src/Common/FST.h +++ b/src/Common/FST.h @@ -1,128 +1,137 @@ #pragma once -#include -#include -#include -#include +#include #include #include -#include +#include +#include +#include +#include #include -#include #include +#include namespace DB { -using Output = UInt64; - -struct State; -using StatePtr = std::shared_ptr; - -struct Arc +/// Finite State Transducer is an efficient way to represent term dictionary. +/// It can be viewed as a map of . +/// Detailed explanation can be found in https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.24.3698&rep=rep1&type=pdf +namespace FST { - Arc() = default; - Arc(Output output_, const StatePtr &target_): output{output_}, target{target_}{} - Output output{0}; - StatePtr target; + using Output = UInt64; - UInt16 serialize(WriteBuffer& write_buffer) const; -}; + struct State; + using StatePtr = std::shared_ptr; -class ArcsBitmap -{ -public: - void addArc(char label); - bool hasArc(char label) const; - int getIndex(char label) const; - int getArcNum() const; -private: - friend struct State; - friend class FST; - UInt256 data{0}; -}; - -struct State -{ - static constexpr size_t MAX_ARCS_IN_SEQUENTIAL_METHID = 32; - enum EncodingMethod + struct Arc { - ENCODING_METHOD_SEQUENTIAL = 0, - ENCODING_METHOD_BITMAP, - }; - State() = default; - State(const State &state) = default; - - UInt64 hash() const; - - Arc* getArc(char label); - void addArc(char label, Output output, StatePtr target); - - void clear() - { - id = 0; - state_index = 0; - flag = 0; - - arcs.clear(); - } + Arc() = default; + Arc(Output output_, const StatePtr & target_) : output{output_}, target{target_} { } + Output output{0}; + StatePtr target; - UInt64 serialize(WriteBuffer& write_buffer); + UInt16 serialize(WriteBuffer & write_buffer) const; + }; - UInt64 id{ 0 }; - UInt64 state_index{ 0 }; - struct Flag_Values + class ArcsBitmap { - unsigned int is_final : 1; - unsigned int encoding_method : 2; + public: + void addArc(char label); + bool hasArc(char label) const; + int getIndex(char label) const; + int getArcNum() const; + + private: + friend struct State; + friend class FiniteStateTransducer; + UInt256 data{0}; }; - union + struct State { - Flag_Values flag_values; - uint8_t flag{0}; + static constexpr size_t MAX_ARCS_IN_SEQUENTIAL_METHOD = 32; + enum EncodingMethod + { + ENCODING_METHOD_SEQUENTIAL = 0, + ENCODING_METHOD_BITMAP, + }; + State() = default; + State(const State & state) = default; + + UInt64 hash() const; + + Arc * getArc(char label); + void addArc(char label, Output output, StatePtr target); + + void clear() + { + id = 0; + state_index = 0; + flag = 0; + + arcs.clear(); + } + + UInt64 serialize(WriteBuffer & write_buffer); + + UInt64 id{0}; + UInt64 state_index{0}; + struct Flag_Values + { + unsigned int is_final : 1; + unsigned int encoding_method : 2; + }; + + union + { + Flag_Values flag_values; + uint8_t flag{0}; + }; + std::map arcs; }; - std::map arcs; -}; -bool operator== (const State& state1, const State& state2); + bool operator==(const State & state1, const State & state2); -constexpr size_t MAX_TERM_LENGTH = 256; + constexpr size_t MAX_TERM_LENGTH = 256; -class FSTBuilder -{ -public: - FSTBuilder(WriteBuffer& write_buffer_); - StatePtr findMinimized(const State& s, bool &found); + class FSTBuilder + { + public: + FSTBuilder(WriteBuffer & write_buffer_); + StatePtr findMinimized(const State & s, bool & found); - void add(const std::string& word, Output output); - UInt64 build(); + void add(const std::string & word, Output output); + UInt64 build(); - UInt64 state_count{ 0 }; -private: - void minimizePreviousWordSuffix(int down_to); - static size_t getCommonPrefix(const std::string &word1, const std::string& word2); -private: - std::array temp_states; - std::string previous_word; - StatePtr initial_state; - std::unordered_map minimized_states; + UInt64 state_count{0}; - UInt64 next_id{1}; + private: + void minimizePreviousWordSuffix(int down_to); + static size_t getCommonPrefix(const std::string & word1, const std::string & word2); - WriteBuffer &write_buffer; - UInt64 previous_written_bytes{0}; - UInt64 previous_state_index{0}; + private: + std::array temp_states; + std::string previous_word; + StatePtr initial_state; + std::unordered_map minimized_states; -}; + UInt64 next_id{1}; -class FST -{ -public: - FST() = default; - FST(std::vector&& data_); - std::pair getOutput(const String& term); - void clear(); - std::vector &getData() { return data;} -private: - std::vector data; -}; + WriteBuffer & write_buffer; + UInt64 previous_written_bytes{0}; + UInt64 previous_state_index{0}; + }; + + class FiniteStateTransducer + { + public: + FiniteStateTransducer() = default; + FiniteStateTransducer(std::vector && data_); + std::pair getOutput(const String & term); + void clear(); + std::vector & getData() { return data; } + + private: + std::vector data; + }; +} } diff --git a/src/Common/tests/gtest_fst.cpp b/src/Common/tests/gtest_fst.cpp index 5fc63d386c3b..9cb1e6b3cb5e 100644 --- a/src/Common/tests/gtest_fst.cpp +++ b/src/Common/tests/gtest_fst.cpp @@ -31,7 +31,7 @@ TEST(FST, SimpleTest) std::vector buffer; DB::WriteBufferFromVector> wbuf(buffer); - DB::FSTBuilder builder(wbuf); + DB::FST::FSTBuilder builder(wbuf); for (auto& term_output : data) { @@ -40,7 +40,7 @@ TEST(FST, SimpleTest) builder.build(); wbuf.finalize(); - DB::FST fst(std::move(buffer)); + DB::FST::FiniteStateTransducer fst(std::move(buffer)); for (auto& item : data) { auto output = fst.getOutput(item.first); diff --git a/src/Interpreters/GinFilter.cpp b/src/Interpreters/GinFilter.cpp index 0ab1455c3ec7..467c1c4598d8 100644 --- a/src/Interpreters/GinFilter.cpp +++ b/src/Interpreters/GinFilter.cpp @@ -35,7 +35,7 @@ GinFilter::GinFilter(const GinFilterParameters & params) void GinFilter::add(const char* data, size_t len, UInt32 rowID, GinIndexStorePtr& store) { - if (len > MAX_TERM_LENGTH) + if (len > FST::MAX_TERM_LENGTH) return; string token(data, len); @@ -192,7 +192,7 @@ bool GinFilter::needsFilter() const for (const auto & term: getTerms()) { - if (term.size() > MAX_TERM_LENGTH) + if (term.size() > FST::MAX_TERM_LENGTH) return false; } diff --git a/src/Storages/MergeTree/GinIndexStore.cpp b/src/Storages/MergeTree/GinIndexStore.cpp index ccad54ab1d37..f772127a00e1 100644 --- a/src/Storages/MergeTree/GinIndexStore.cpp +++ b/src/Storages/MergeTree/GinIndexStore.cpp @@ -259,7 +259,7 @@ void GinIndexStore::writeSegment() ///write item dictionary std::vector buffer; WriteBufferFromVector> write_buf(buffer); - FSTBuilder builder(write_buf); + FST::FSTBuilder builder(write_buf); UInt64 offset{0}; current_index = 0; diff --git a/src/Storages/MergeTree/GinIndexStore.h b/src/Storages/MergeTree/GinIndexStore.h index e2c5a6f31206..346bf565571c 100644 --- a/src/Storages/MergeTree/GinIndexStore.h +++ b/src/Storages/MergeTree/GinIndexStore.h @@ -89,7 +89,7 @@ struct TermDictionary UInt64 item_dict_start_offset; /// Finite State Transducer, which can be viewed as a map of - FST offsets; + FST::FiniteStateTransducer offsets; }; using TermDictionaryPtr = std::shared_ptr; From ca4893307bf547732bfdfafc3af2d234ff2a32b4 Mon Sep 17 00:00:00 2001 From: Larry Luo Date: Thu, 8 Sep 2022 05:00:00 -0700 Subject: [PATCH 26/42] Reference FST paper title directly --- src/Common/FST.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Common/FST.h b/src/Common/FST.h index 1fbba4505e44..821df1b6d3d3 100644 --- a/src/Common/FST.h +++ b/src/Common/FST.h @@ -14,7 +14,8 @@ namespace DB { /// Finite State Transducer is an efficient way to represent term dictionary. /// It can be viewed as a map of . -/// Detailed explanation can be found in https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.24.3698&rep=rep1&type=pdf +/// Detailed explanation can be found in the following paper +/// [Direct Constrution of Minimal Acyclic Subsequential Transduers] by Stoyan Mihov and Denis Maurel, University of Tours, France namespace FST { using Output = UInt64; From 7d1c34316abf0570be2000064322fd1254217ba1 Mon Sep 17 00:00:00 2001 From: Larry Luo Date: Thu, 8 Sep 2022 05:47:05 -0700 Subject: [PATCH 27/42] Fixed a type in comments --- src/Common/FST.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Common/FST.h b/src/Common/FST.h index 821df1b6d3d3..37a288b14018 100644 --- a/src/Common/FST.h +++ b/src/Common/FST.h @@ -15,7 +15,7 @@ namespace DB /// Finite State Transducer is an efficient way to represent term dictionary. /// It can be viewed as a map of . /// Detailed explanation can be found in the following paper -/// [Direct Constrution of Minimal Acyclic Subsequential Transduers] by Stoyan Mihov and Denis Maurel, University of Tours, France +/// [Direct Construction of Minimal Acyclic Subsequential Transduers] by Stoyan Mihov and Denis Maurel, University of Tours, France namespace FST { using Output = UInt64; From b43ddde50a0d2caca8d3f4ae88202b3ae3a324ef Mon Sep 17 00:00:00 2001 From: Harry-Lee Date: Sun, 25 Sep 2022 16:29:30 -0700 Subject: [PATCH 28/42] Refactor full text search according to reviews --- src/Common/FST.cpp | 150 +++++++++---------- src/Common/FST.h | 76 ++++++---- src/Common/tests/gtest_fst.cpp | 26 ++-- src/Interpreters/GinFilter.cpp | 78 +++------- src/Interpreters/GinFilter.h | 11 +- src/Storages/MergeTree/GinIndexStore.cpp | 114 ++++++-------- src/Storages/MergeTree/GinIndexStore.h | 47 +++--- src/Storages/MergeTree/MergeTreeIndexGin.cpp | 14 +- src/Storages/MergeTree/MergeTreeIndexGin.h | 4 +- src/Storages/MergeTree/MergeTreeIndices.cpp | 1 - 10 files changed, 238 insertions(+), 283 deletions(-) diff --git a/src/Common/FST.cpp b/src/Common/FST.cpp index 4b4b99804624..9cc3fb35f35f 100644 --- a/src/Common/FST.cpp +++ b/src/Common/FST.cpp @@ -4,9 +4,6 @@ #include #include #include -#include <../contrib/consistent-hashing/popcount.h> -#include -#include #include #include @@ -20,12 +17,13 @@ namespace ErrorCodes namespace FST { -UInt16 Arc::serialize(WriteBuffer& write_buffer) const +UInt64 Arc::serialize(WriteBuffer& write_buffer) const { - UInt16 written_bytes = 0; + UInt64 written_bytes = 0; bool has_output = output != 0; /// First UInt64 is target_index << 1 + has_output + assert(target != nullptr); UInt64 first = ((target->state_index) << 1) + has_output; writeVarUInt(first, write_buffer); written_bytes += getLengthOfVarUInt(first); @@ -39,52 +37,46 @@ UInt16 Arc::serialize(WriteBuffer& write_buffer) const return written_bytes; } -void ArcsBitmap::addArc(char label) +bool operator==(const Arc & arc1, const Arc & arc2) { - uint8_t index = label; + return (arc1.output == arc2.output && arc1.target->id == arc2.target->id); +} + +void ArcsAsBitmap::addArc(char label) +{ + UInt8 index = label; UInt256 bit_label = 1; bit_label <<= index; data |= bit_label; } -int ArcsBitmap::getIndex(char label) const +int ArcsAsBitmap::getIndex(char label) const { int bit_count = 0; - uint8_t index = label; + UInt8 index = label; int which_int64 = 0; while (true) { if (index < 64) { - UInt64 mask = index == 63 ? (-1) : (1ULL << (index+1)) - 1; + UInt64 mask = index == 63 ? (-1) : (1ULL << (index + 1)) - 1; - bit_count += PopCountImpl(mask & data.items[which_int64]); + bit_count += std::popcount(mask & data.items[which_int64]); break; } index -= 64; - bit_count += PopCountImpl(data.items[which_int64]); + bit_count += std::popcount(data.items[which_int64]); which_int64++; } return bit_count; } -int ArcsBitmap::getArcNum() const +bool ArcsAsBitmap::hasArc(char label) const { - int bit_count = 0; - for (uint64_t item : data.items) - { - if (item) - bit_count += PopCountImpl(item); - } - return bit_count; -} - -bool ArcsBitmap::hasArc(char label) const -{ - uint8_t index = label; + UInt8 index = label; UInt256 bit_label = 1; bit_label <<= index; @@ -93,7 +85,7 @@ bool ArcsBitmap::hasArc(char label) const Arc* State::getArc(char label) { - auto it(arcs.find(label)); + auto it = arcs.find(label); if (it == arcs.cend()) return nullptr; @@ -108,18 +100,14 @@ void State::addArc(char label, Output output, StatePtr target) UInt64 State::hash() const { std::vector values; - - // put 2 magic chars, in case there are no arcs - values.push_back('C'); - values.push_back('H'); - - for (const auto& label_arc : arcs) + values.reserve(arcs.size() * (sizeof(Output) + sizeof(UInt64) + 1)); + for (const auto & [label, arc] : arcs) { - values.push_back(label_arc.first); - const auto * ptr = reinterpret_cast(&label_arc.second.output); + values.push_back(label); + const auto * ptr = reinterpret_cast(&arc.output); std::copy(ptr, ptr + sizeof(Output), std::back_inserter(values)); - ptr = reinterpret_cast(&label_arc.second.target->id); + ptr = reinterpret_cast(&arc.target->id); std::copy(ptr, ptr + sizeof(UInt64), std::back_inserter(values)); } @@ -128,14 +116,16 @@ UInt64 State::hash() const bool operator== (const State& state1, const State& state2) { - for (const auto& label_arc : state1.arcs) + if (state1.arcs.size() != state2.arcs.size()) + return false; + + for (const auto & [label, arc] : state1.arcs) { - auto it(state2.arcs.find(label_arc.first)); + const auto it = state2.arcs.find(label); if (it == state2.arcs.cend()) return false; - if (it->second.output != label_arc.second.output) - return false; - if (it->second.target->id != label_arc.second.target->id) + + if (it->second != arc) return false; } return true; @@ -149,15 +139,15 @@ UInt64 State::serialize(WriteBuffer& write_buffer) write_buffer.write(flag); written_bytes += 1; - if (flag_values.encoding_method == ENCODING_METHOD_SEQUENTIAL) + if (flag_values.encoding_method == EncodingMethod::ENCODING_METHOD_SEQUENTIAL) { /// Serialize all labels std::vector labels; labels.reserve(MAX_ARCS_IN_SEQUENTIAL_METHOD); - for (auto& label_state : arcs) + for (auto& [label, state] : arcs) { - labels.push_back(label_state.first); + labels.push_back(label); } UInt8 label_size = labels.size(); @@ -178,10 +168,10 @@ UInt64 State::serialize(WriteBuffer& write_buffer) else { /// Serialize bitmap - ArcsBitmap bmp; - for (auto& label_state : arcs) + ArcsAsBitmap bmp; + for (auto & [label, state] : arcs) { - bmp.addArc(label_state.first); + bmp.addArc(label); } UInt64 bmp_encoded_size = getLengthOfVarUInt(bmp.data.items[0]) @@ -196,12 +186,12 @@ UInt64 State::serialize(WriteBuffer& write_buffer) written_bytes += bmp_encoded_size; /// Serialize all arcs - for (auto& label_state : arcs) + for (auto & [label, state] : arcs) { - Arc* arc = getArc(label_state.first); + Arc* arc = getArc(label); assert(arc != nullptr); written_bytes += arc->serialize(write_buffer); - } + } } return written_bytes; @@ -215,18 +205,18 @@ FSTBuilder::FSTBuilder(WriteBuffer& write_buffer_) : write_buffer(write_buffer_) } } -StatePtr FSTBuilder::findMinimized(const State& state, bool& found) +StatePtr FSTBuilder::getMinimized(const State& state, bool& found) { found = false; auto hash = state.hash(); - auto it(minimized_states.find(hash)); + auto it = minimized_states.find(hash); if (it != minimized_states.cend() && *it->second == state) { found = true; return it->second; } - StatePtr p(new State(state)); + StatePtr p = std::make_shared(state); minimized_states[hash] = p; return p; } @@ -239,12 +229,12 @@ size_t FSTBuilder::getCommonPrefix(const std::string& word1, const std::string& return i; } -void FSTBuilder::minimizePreviousWordSuffix(int down_to) +void FSTBuilder::minimizePreviousWordSuffix(Int64 down_to) { - for (int i = static_cast(previous_word.size()); i >= down_to; --i) + for (Int64 i = static_cast(previous_word.size()); i >= down_to; --i) { bool found{ false }; - auto minimized_state = findMinimized(*temp_states[i], found); + auto minimized_state = getMinimized(*temp_states[i], found); if (i != 0) { @@ -274,7 +264,12 @@ void FSTBuilder::minimizePreviousWordSuffix(int down_to) void FSTBuilder::add(const std::string& current_word, Output current_output) { - /// We assume word size is no greater than MAX_TERM_LENGTH + /// We assume word size is no greater than MAX_TERM_LENGTH(256). + /// FSTs without word size limitation would be inefficient and easy to cause memory bloat + /// Note that when using "split" tokenizer, if a granule has tokens which are longer than + /// MAX_TERM_LENGTH, the granule cannot be dropped and will be fully-scanned. It doesn't affect "ngram" tokenizers. + /// Another limitation is that if the query string has tokens which exceed this length + /// it will fallback to default searching when using "split" tokenizers. auto current_word_len = current_word.size(); if (current_word_len > MAX_TERM_LENGTH) @@ -288,27 +283,26 @@ void FSTBuilder::add(const std::string& current_word, Output current_output) for (size_t i = prefix_length_plus1; i <= current_word.size(); ++i) { temp_states[i]->clear(); - temp_states[i - 1]->addArc(current_word[i-1], 0, temp_states[i]); + temp_states[i - 1]->addArc(current_word[i - 1], 0, temp_states[i]); } /// We assume the current word is different with previous word - temp_states[current_word_len]->flag_values.is_final = true; + temp_states[current_word_len]->setFinal(true); /// Adjust outputs on the arcs for (size_t j = 1; j <= prefix_length_plus1 - 1; ++j) { - auto * arc_ptr = temp_states[j - 1]->getArc(current_word[j-1]); + Arc * arc_ptr = temp_states[j - 1]->getArc(current_word[j - 1]); assert(arc_ptr != nullptr); - auto common_prefix = std::min(arc_ptr->output, current_output); - auto word_suffix = arc_ptr->output - common_prefix; + Output common_prefix = std::min(arc_ptr->output, current_output); + Output word_suffix = arc_ptr->output - common_prefix; arc_ptr->output = common_prefix; /// For each arc, adjust its output if (word_suffix != 0) { - for (auto& label_arc : temp_states[j]->arcs) + for (auto & [label, arc] : temp_states[j]->arcs) { - auto& arc = label_arc.second; arc.output += word_suffix; } } @@ -317,7 +311,7 @@ void FSTBuilder::add(const std::string& current_word, Output current_output) } /// Set last temp state's output - auto * arc = temp_states[prefix_length_plus1 - 1]->getArc(current_word[prefix_length_plus1-1]); + Arc * arc = temp_states[prefix_length_plus1 - 1]->getArc(current_word[prefix_length_plus1 - 1]); assert(arc != nullptr); arc->output = current_output; @@ -338,7 +332,7 @@ UInt64 FSTBuilder::build() return previous_state_index + previous_written_bytes + length + 1; } -FiniteStateTransducer::FiniteStateTransducer(std::vector&& data_) : data(data_) +FiniteStateTransducer::FiniteStateTransducer(std::vector data_) : data(std::move(data_)) { } @@ -347,15 +341,17 @@ void FiniteStateTransducer::clear() data.clear(); } -std::pair FiniteStateTransducer::getOutput(const String& term) +std::pair FiniteStateTransducer::getOutput(const String& term) { - std::pair result_output{ false, 0 }; + std::pair result_output{ 0, false }; /// Read index of initial state ReadBufferFromMemory read_buffer(data.data(), data.size()); read_buffer.seek(data.size()-1, SEEK_SET); - UInt8 length{0}; + UInt8 length{ 0 }; read_buffer.read(reinterpret_cast(length)); + if (length == 0) + return { 0, false }; read_buffer.seek(data.size() - 1 - length, SEEK_SET); UInt64 state_index{ 0 }; @@ -369,22 +365,22 @@ std::pair FiniteStateTransducer::getOutput(const String& term) State temp_state; read_buffer.seek(state_index, SEEK_SET); - read_buffer.read(reinterpret_cast(temp_state.flag)); + temp_state.readFlag(read_buffer); if (i == term.size()) { - result_output.first = temp_state.flag_values.is_final; + result_output.second = temp_state.isFinal(); break; } UInt8 label = term[i]; - if (temp_state.flag_values.encoding_method == State::ENCODING_METHOD_SEQUENTIAL) + if (temp_state.getEncodingMethod() == State::EncodingMethod::ENCODING_METHOD_SEQUENTIAL) { /// Read number of labels - UInt8 label_num{0}; + UInt8 label_num{ 0 }; read_buffer.read(reinterpret_cast(label_num)); if (label_num == 0) - return { false, 0 }; + return { 0, false }; auto labels_position = read_buffer.getPosition(); @@ -395,7 +391,7 @@ std::pair FiniteStateTransducer::getOutput(const String& term) auto pos = std::find(begin_it, end_it, label); if (pos == end_it) - return { false, 0 }; + return { 0, false }; /// Read the arc for the label UInt64 arc_index = (pos - begin_it); @@ -416,7 +412,7 @@ std::pair FiniteStateTransducer::getOutput(const String& term) } else { - ArcsBitmap bmp; + ArcsAsBitmap bmp; readVarUInt(bmp.data.items[0], read_buffer); readVarUInt(bmp.data.items[1], read_buffer); @@ -424,7 +420,7 @@ std::pair FiniteStateTransducer::getOutput(const String& term) readVarUInt(bmp.data.items[3], read_buffer); if (!bmp.hasArc(label)) - return { false, 0 }; + return { 0, false }; /// Read the arc for the label size_t arc_index = bmp.getIndex(label); @@ -441,7 +437,7 @@ std::pair FiniteStateTransducer::getOutput(const String& term) } } /// Accumulate the output value - result_output.second += arc_output; + result_output.first += arc_output; } return result_output; } diff --git a/src/Common/FST.h b/src/Common/FST.h index 37a288b14018..06851a72b400 100644 --- a/src/Common/FST.h +++ b/src/Common/FST.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -20,37 +21,41 @@ namespace FST { using Output = UInt64; - struct State; + class State; using StatePtr = std::shared_ptr; struct Arc { Arc() = default; - Arc(Output output_, const StatePtr & target_) : output{output_}, target{target_} { } - Output output{0}; + explicit Arc(Output output_, const StatePtr & target_) : output{output_}, target{target_} { } + Output output = 0; StatePtr target; - UInt16 serialize(WriteBuffer & write_buffer) const; + UInt64 serialize(WriteBuffer & write_buffer) const; }; - class ArcsBitmap + bool operator==(const Arc & arc1, const Arc & arc2); + + class ArcsAsBitmap { public: void addArc(char label); bool hasArc(char label) const; int getIndex(char label) const; - int getArcNum() const; private: - friend struct State; + friend class State; friend class FiniteStateTransducer; - UInt256 data{0}; + /// data holds a 256-bit bitmap for all arcs of a node. Ita 256 bits correspond to 256 + /// possible label values. + UInt256 data{ 0 }; }; - struct State + class State { + public: static constexpr size_t MAX_ARCS_IN_SEQUENTIAL_METHOD = 32; - enum EncodingMethod + enum class EncodingMethod { ENCODING_METHOD_SEQUENTIAL = 0, ENCODING_METHOD_BITMAP, @@ -74,60 +79,77 @@ namespace FST UInt64 serialize(WriteBuffer & write_buffer); - UInt64 id{0}; - UInt64 state_index{0}; - struct Flag_Values + inline bool isFinal() const + { + return flag_values.is_final == 1; + } + inline void setFinal(bool value) + { + flag_values.is_final = value; + } + inline EncodingMethod getEncodingMethod() const + { + return flag_values.encoding_method; + } + inline void readFlag(ReadBuffer & read_buffer) + { + read_buffer.read(reinterpret_cast(flag)); + } + + UInt64 id = 0; + UInt64 state_index = 0; + std::unordered_map arcs; + private: + struct FlagValues { unsigned int is_final : 1; - unsigned int encoding_method : 2; + EncodingMethod encoding_method : 3; }; union { - Flag_Values flag_values; - uint8_t flag{0}; + FlagValues flag_values; + uint8_t flag = 0; }; - std::map arcs; }; bool operator==(const State & state1, const State & state2); - constexpr size_t MAX_TERM_LENGTH = 256; + inline constexpr size_t MAX_TERM_LENGTH = 256; class FSTBuilder { public: FSTBuilder(WriteBuffer & write_buffer_); - StatePtr findMinimized(const State & s, bool & found); + StatePtr getMinimized(const State & s, bool & found); void add(const std::string & word, Output output); UInt64 build(); - UInt64 state_count{0}; + UInt64 state_count = 0; private: - void minimizePreviousWordSuffix(int down_to); + void minimizePreviousWordSuffix(Int64 down_to); static size_t getCommonPrefix(const std::string & word1, const std::string & word2); - private: std::array temp_states; std::string previous_word; StatePtr initial_state; std::unordered_map minimized_states; - UInt64 next_id{1}; + UInt64 next_id = 1; WriteBuffer & write_buffer; - UInt64 previous_written_bytes{0}; - UInt64 previous_state_index{0}; + UInt64 previous_written_bytes = 0; + UInt64 previous_state_index = 0; }; class FiniteStateTransducer { public: FiniteStateTransducer() = default; - FiniteStateTransducer(std::vector && data_); - std::pair getOutput(const String & term); + FiniteStateTransducer(std::vector data_); + std::pair getOutput(const String & term); void clear(); std::vector & getData() { return data; } diff --git a/src/Common/tests/gtest_fst.cpp b/src/Common/tests/gtest_fst.cpp index 9cb1e6b3cb5e..ca56fa1fb2bb 100644 --- a/src/Common/tests/gtest_fst.cpp +++ b/src/Common/tests/gtest_fst.cpp @@ -5,11 +5,9 @@ #include #include -using namespace std; - TEST(FST, SimpleTest) { - vector> data + std::vector> indexed_data { {"mop", 100}, {"moth", 91}, @@ -19,7 +17,7 @@ TEST(FST, SimpleTest) {"top", 55}, }; - vector> bad_data + std::vector> not_indexed_dta { {"mo", 100}, {"moth1", 91}, @@ -33,24 +31,24 @@ TEST(FST, SimpleTest) DB::WriteBufferFromVector> wbuf(buffer); DB::FST::FSTBuilder builder(wbuf); - for (auto& term_output : data) + for (auto& [term, output] : indexed_data) { - builder.add(term_output.first, term_output.second); + builder.add(term, output); } builder.build(); wbuf.finalize(); - DB::FST::FiniteStateTransducer fst(std::move(buffer)); - for (auto& item : data) + DB::FST::FiniteStateTransducer fst(buffer); + for (auto& [term, output] : indexed_data) { - auto output = fst.getOutput(item.first); - ASSERT_EQ(output.first, true); - ASSERT_EQ(output.second, item.second); + auto [result, found] = fst.getOutput(term); + ASSERT_EQ(found, true); + ASSERT_EQ(result, output); } - for (auto& item : bad_data) + for (auto& [term, output] : not_indexed_dta) { - auto output = fst.getOutput(item.first); - ASSERT_EQ(output.first, false); + auto [result, found] = fst.getOutput(term); + ASSERT_EQ(found, false); } } diff --git a/src/Interpreters/GinFilter.cpp b/src/Interpreters/GinFilter.cpp index 467c1c4598d8..a58d315ddbe9 100644 --- a/src/Interpreters/GinFilter.cpp +++ b/src/Interpreters/GinFilter.cpp @@ -13,8 +13,6 @@ #include #include -using namespace std; - namespace DB { namespace ErrorCodes @@ -38,8 +36,8 @@ void GinFilter::add(const char* data, size_t len, UInt32 rowID, GinIndexStorePtr if (len > FST::MAX_TERM_LENGTH) return; - string token(data, len); - auto it(store->getPostings().find(token)); + std::string token(data, len); + auto it = store->getPostings().find(token); if (it != store->getPostings().end()) { @@ -55,6 +53,8 @@ void GinFilter::add(const char* data, size_t len, UInt32 rowID, GinIndexStorePtr } } +/// This method assumes segmentIDs are in increasing order, which is true since rows are +/// digested sequentially and segments are created sequentially too. void GinFilter::addRowRangeToGinFilter(UInt32 segmentID, UInt32 rowIDStart, UInt32 rowIDEnd) { if (!rowid_range_container.empty()) @@ -72,52 +72,10 @@ void GinFilter::addRowRangeToGinFilter(UInt32 segmentID, UInt32 rowIDStart, UInt void GinFilter::clear() { + terms.clear(); rowid_range_container.clear(); } -#ifndef NDEBUG -void GinFilter::dump() const -{ - printf("filter : '%s', row ID range:\n", getMatchString().c_str()); - for (const auto & rowid_range: rowid_range_container) - { - printf("\t\t%d, %d, %d; ", rowid_range.segment_id, rowid_range.range_start, rowid_range.range_end); - } - printf("\n"); -} - -void dumpPostingsCache(const PostingsCache& postings_cache) -{ - for (const auto& term_postings : postings_cache) - { - printf("--term '%s'---------\n", term_postings.first.c_str()); - - const SegmentedPostingsListContainer& container = term_postings.second; - - for (const auto& [segment_id, postings_list] : container) - { - printf("-----segment id = %d ---------\n", segment_id); - printf("-----postings-list: "); - for (unsigned int it : *postings_list) - { - printf("%d ", it); - } - printf("\n"); - } - } -} - -void dumpPostingsCacheForStore(const PostingsCacheForStore& cache_store) -{ - printf("----cache---store---: %s\n", cache_store.store->getName().c_str()); - for (const auto & query_string_postings_cache: cache_store.cache) - { - printf("----cache_store----filter string:%s---\n", query_string_postings_cache.first.c_str()); - dumpPostingsCache(*query_string_postings_cache.second); - } -} -#endif - bool GinFilter::hasEmptyPostingsList(const PostingsCachePtr& postings_cache) { if (postings_cache->empty()) @@ -175,14 +133,15 @@ bool GinFilter::match(const PostingsCachePtr& postings_cache) const return false; } - bool match_result = false; /// Check for each row ID ranges for (const auto &rowid_range: rowid_range_container) { - match_result |= matchInRange(postings_cache, rowid_range.segment_id, rowid_range.range_start, rowid_range.range_end); + if (matchInRange(postings_cache, rowid_range.segment_id, rowid_range.range_start, rowid_range.range_end)) + { + return true; + } } - - return match_result; + return false; } bool GinFilter::needsFilter() const @@ -199,26 +158,25 @@ bool GinFilter::needsFilter() const return true; } -bool GinFilter::contains(const GinFilter & af, PostingsCacheForStore &cache_store) const +bool GinFilter::contains(const GinFilter & filter, PostingsCacheForStore &cache_store) const { - if (!af.needsFilter()) + if (!filter.needsFilter()) return true; - PostingsCachePtr postings_cache = cache_store.getPostings(af.getMatchString()); + PostingsCachePtr postings_cache = cache_store.getPostings(filter.getQueryString()); if (postings_cache == nullptr) { - GinIndexStoreReader reader(cache_store.store); - postings_cache = reader.loadPostingsIntoCache(af.getTerms()); - cache_store.cache[af.getMatchString()] = postings_cache; + GinIndexStoreDeserializer reader(cache_store.store); + postings_cache = reader.loadPostingsIntoCache(filter.getTerms()); + cache_store.cache[filter.getQueryString()] = postings_cache; } return match(postings_cache); } -const String& GinFilter::getName() +String GinFilter::getName() { - static String name("gin"); - return name; + return "gin"; } } diff --git a/src/Interpreters/GinFilter.h b/src/Interpreters/GinFilter.h index eceeffaaacab..dca1d95a302a 100644 --- a/src/Interpreters/GinFilter.h +++ b/src/Interpreters/GinFilter.h @@ -7,13 +7,12 @@ namespace DB { struct GinFilterParameters { - GinFilterParameters(size_t ngrams_); + explicit GinFilterParameters(size_t ngrams_); size_t ngrams; }; -class DiskLocal; -using RowIDRange = struct +struct RowIDRange { /// Segment ID of the row ID range UInt32 segment_id; @@ -51,7 +50,7 @@ class GinFilter query_string = String(data, len); } - const String &getMatchString() const { return query_string; } + const String &getQueryString() const { return query_string; } #ifndef NDEBUG void dump() const; @@ -65,7 +64,7 @@ class GinFilter bool match(const PostingsCachePtr& postings_cache) const; - static const String& getName(); + static String getName(); private: [[maybe_unused]] size_t ngrams; @@ -74,7 +73,7 @@ class GinFilter std::vector terms; RowIDRangeContainer rowid_range_container; -private: + static bool hasEmptyPostingsList(const PostingsCachePtr& postings_cache); static bool matchInRange(const PostingsCachePtr& postings_cache, UInt32 segment_id, UInt32 range_start, UInt32 range_end); diff --git a/src/Storages/MergeTree/GinIndexStore.cpp b/src/Storages/MergeTree/GinIndexStore.cpp index f772127a00e1..7f109fb7c63e 100644 --- a/src/Storages/MergeTree/GinIndexStore.cpp +++ b/src/Storages/MergeTree/GinIndexStore.cpp @@ -27,7 +27,7 @@ namespace ErrorCodes bool GinIndexPostingsBuilder::contains(UInt32 row_id) const { if (useRoaring()) - return bmp.contains(row_id); + return rowid_bitmap.contains(row_id); const auto *const it(std::find(lst.begin(), lst.begin()+lst_length, row_id)); return it != lst.begin()+lst_length; @@ -37,7 +37,7 @@ void GinIndexPostingsBuilder::add(UInt32 row_id) { if (useRoaring()) { - bmp.add(row_id); + rowid_bitmap.add(row_id); return; } assert(lst_length < MIN_SIZE_FOR_ROARING_ENCODING); @@ -46,56 +46,52 @@ void GinIndexPostingsBuilder::add(UInt32 row_id) if (lst_length == MIN_SIZE_FOR_ROARING_ENCODING) { for (size_t i = 0; i < lst_length; i++) - bmp.add(lst[i]); + rowid_bitmap.add(lst[i]); - lst_length = 0xFF; + lst_length = UsesBitMap; } } bool GinIndexPostingsBuilder::useRoaring() const { - return lst_length == 0xFF; + return lst_length == UsesBitMap; } UInt64 GinIndexPostingsBuilder::serialize(WriteBuffer &buffer) const { - UInt64 encoding_length = 0; + UInt64 written_bytes = 0; + buffer.write(lst_length); + written_bytes += 1; + if (!useRoaring()) { - /// First byte is number of Row IDS to be encoded - buffer.write(lst_length); - encoding_length += 1; for (size_t i = 0; i < lst_length; ++i) { writeVarUInt(lst[i], buffer); - encoding_length += getLengthOfVarUInt(lst[i]); + written_bytes += getLengthOfVarUInt(lst[i]); } } else { - /// First byte is 0 (for Roaring Bitmap encoding) - buffer.write(0); - encoding_length += 1; - - auto size = bmp.getSizeInBytes(); + auto size = rowid_bitmap.getSizeInBytes(); writeVarUInt(size, buffer); - encoding_length += getLengthOfVarUInt(size); + written_bytes += getLengthOfVarUInt(size); - std::unique_ptr buf(new char[size]); - bmp.write(buf.get()); + auto buf = std::make_unique(size); + rowid_bitmap.write(buf.get()); buffer.write(buf.get(), size); - encoding_length += size; + written_bytes += size; } - return encoding_length; + return written_bytes; } GinIndexPostingsListPtr GinIndexPostingsBuilder::deserialize(ReadBuffer &buffer) { - char postings_list_size{0}; - buffer.read(postings_list_size); + UInt8 postings_list_size{0}; + buffer.read(reinterpret_cast(postings_list_size)); - if (postings_list_size != 0) + if (postings_list_size != UsesBitMap) { assert(postings_list_size < MIN_SIZE_FOR_ROARING_ENCODING); GinIndexPostingsListPtr postings_list = std::make_shared(); @@ -112,8 +108,7 @@ GinIndexPostingsListPtr GinIndexPostingsBuilder::deserialize(ReadBuffer &buffer) { size_t size{0}; readVarUInt(size, buffer); - std::unique_ptr buf(new char[size]); - + auto buf = std::make_unique(size); buffer.readStrict(reinterpret_cast(buf.get()), size); GinIndexPostingsListPtr postings_list = std::make_shared(GinIndexPostingsList::read(buf.get())); @@ -124,7 +119,7 @@ GinIndexPostingsListPtr GinIndexPostingsBuilder::deserialize(ReadBuffer &buffer) bool GinIndexStore::exists() const { - String id_file_name = getName() + ".gin_sid"; + String id_file_name = getName() + GIN_SEGMENT_ID_FILE_TYPE; return storage->exists(id_file_name); } @@ -175,13 +170,13 @@ UInt32 GinIndexStore::getNextRowIDRange(size_t n) UInt32 GinIndexStore::getNextSegmentID() { - String sid_file_name = getName() + ".gin_sid"; + String sid_file_name = getName() + GIN_SEGMENT_ID_FILE_TYPE; return getNextIDRange(sid_file_name, 1); } UInt32 GinIndexStore::getSegmentNum() { - String sid_file_name = getName() + ".gin_sid"; + String sid_file_name = getName() + GIN_SEGMENT_ID_FILE_TYPE; if (!storage->exists(sid_file_name)) return 0; Int32 result = 0; @@ -212,11 +207,11 @@ void GinIndexStore::finalize() } } -void GinIndexStore::init_file_streams() +void GinIndexStore::initFileStreams() { - String segment_file_name = getName() + ".gin_seg"; - String item_dict_file_name = getName() + ".gin_dict"; - String postings_file_name = getName() + ".gin_post"; + String segment_file_name = getName() + GIN_SEGMENT_FILE_TYPE; + String item_dict_file_name = getName() + GIN_DICTIONARY_FILE_TYPE; + String postings_file_name = getName() + GIN_POSTINGS_FILE_TYPE; segment_file_stream = data_part_storage_builder->writeFile(segment_file_name, DBMS_DEFAULT_BUFFER_SIZE, WriteMode::Append, {}); term_dict_file_stream = data_part_storage_builder->writeFile(item_dict_file_name, DBMS_DEFAULT_BUFFER_SIZE, WriteMode::Append, {}); @@ -227,7 +222,7 @@ void GinIndexStore::writeSegment() { if (segment_file_stream == nullptr) { - init_file_streams(); + initFileStreams(); } ///write segment @@ -290,29 +285,32 @@ void GinIndexStore::writeSegment() postings_file_stream->sync(); } -void GinIndexStoreReader::init_file_streams() +GinIndexStoreDeserializer::GinIndexStoreDeserializer(const GinIndexStorePtr & store_) + : store(store_) +{ + initFileStreams(); +} + +void GinIndexStoreDeserializer::initFileStreams() { - String segment_file_name = store->getName() + ".gin_seg"; - String item_dict_file_name = store->getName() + ".gin_dict"; - String postings_file_name = store->getName() + ".gin_post"; + String segment_file_name = store->getName() + GinIndexStore::GIN_SEGMENT_FILE_TYPE; + String item_dict_file_name = store->getName() + GinIndexStore::GIN_DICTIONARY_FILE_TYPE; + String postings_file_name = store->getName() + GinIndexStore::GIN_POSTINGS_FILE_TYPE; segment_file_stream = store->storage->readFile(segment_file_name, {}, std::nullopt, std::nullopt); term_dict_file_stream = store->storage->readFile(item_dict_file_name, {}, std::nullopt, std::nullopt); postings_file_stream = store->storage->readFile(postings_file_name, {}, std::nullopt, std::nullopt); } -void GinIndexStoreReader::readSegments() +void GinIndexStoreDeserializer::readSegments() { - GinIndexSegments segments; - auto segment_num = store->getSegmentNum(); if (segment_num == 0) return; - segments.assign(segment_num, {}); - + GinIndexSegments segments (segment_num); if (segment_file_stream == nullptr) { - init_file_streams(); + initFileStreams(); } segment_file_stream->read(reinterpret_cast(segments.data()), segment_num * sizeof(GinIndexSegment)); for (size_t i = 0; i < segment_num; ++i) @@ -325,7 +323,7 @@ void GinIndexStoreReader::readSegments() } } -void GinIndexStoreReader::readTermDictionary(UInt32 segment_id) +void GinIndexStoreDeserializer::readTermDictionary(UInt32 segment_id) { /// Check validity of segment_id auto it{ store->term_dicts.find(segment_id) }; @@ -348,14 +346,14 @@ void GinIndexStoreReader::readTermDictionary(UInt32 segment_id) term_dict_file_stream->readStrict(reinterpret_cast(it->second->offsets.getData().data()), fst_size); } -SegmentedPostingsListContainer GinIndexStoreReader::readSegmentedPostingsLists(const String& token) +SegmentedPostingsListContainer GinIndexStoreDeserializer::readSegmentedPostingsLists(const String& token) { SegmentedPostingsListContainer container; for (auto const& seg_term_dict : store->term_dicts) { auto segment_id = seg_term_dict.first; - auto [found, offset] = seg_term_dict.second->offsets.getOutput(token); + auto [offset, found] = seg_term_dict.second->offsets.getOutput(token); if (!found) continue; @@ -369,7 +367,7 @@ SegmentedPostingsListContainer GinIndexStoreReader::readSegmentedPostingsLists(c return container; } -PostingsCachePtr GinIndexStoreReader::loadPostingsIntoCache(const std::vector& terms) +PostingsCachePtr GinIndexStoreDeserializer::loadPostingsIntoCache(const std::vector& terms) { auto postings_cache = std::make_shared(); for (const auto& term : terms) @@ -393,19 +391,18 @@ GinIndexStoreFactory& GinIndexStoreFactory::instance() GinIndexStorePtr GinIndexStoreFactory::get(const String& name, DataPartStoragePtr storage_) { const String& part_path = storage_->getRelativePath(); - std::lock_guard lock(stores_mutex); String key = name + String(":")+part_path; + std::lock_guard lock(stores_mutex); GinIndexStores::const_iterator it = stores.find(key); if (it == stores.cend()) { - GinIndexStorePtr store = std::make_shared(name); - store->SetStorage(storage_); + GinIndexStorePtr store = std::make_shared(name, storage_); if (!store->exists()) - return nullptr; //gin index was not generated for the part due to no data or the length of column data is less than ngramsize + return nullptr; - GinIndexStoreReader reader(store); + GinIndexStoreDeserializer reader(store); reader.readSegments(); for (size_t seg_index = 0; seg_index < store->getSegmentNum(); ++seg_index) @@ -431,17 +428,4 @@ void GinIndexStoreFactory::remove(const String& part_path) ++it; } } - -#ifndef NDEBUG -void GinIndexStoreFactory::dump() -{ - printf("GinIndexStoreFactory----------dump start-------->>\n"); - for (const auto & [key, store]: stores) - { - printf("%s\n", key.c_str()); - } - printf("GinIndexStoreFactory----------dump end---------<<\n"); -} -#endif - } diff --git a/src/Storages/MergeTree/GinIndexStore.h b/src/Storages/MergeTree/GinIndexStore.h index 346bf565571c..d27801359487 100644 --- a/src/Storages/MergeTree/GinIndexStore.h +++ b/src/Storages/MergeTree/GinIndexStore.h @@ -40,13 +40,14 @@ using GinIndexPostingsList = roaring::Roaring; using GinIndexPostingsListPtr = std::shared_ptr; /// Gin Index Postings List Builder. -struct GinIndexPostingsBuilder +class GinIndexPostingsBuilder { +public: /// When the list length is no greater than MIN_SIZE_FOR_ROARING_ENCODING, array 'lst' is used std::array lst; - /// When the list length is greater than MIN_SIZE_FOR_ROARING_ENCODING, Roaring bitmap 'bmp' is used - roaring::Roaring bmp; + /// When the list length is greater than MIN_SIZE_FOR_ROARING_ENCODING, Roaring bitmap 'rowid_bitmap' is used + roaring::Roaring rowid_bitmap; /// lst_length stores the number of row IDs in 'lst' array, can also be a flag(0xFF) indicating that roaring bitmap is used UInt8 lst_length{0}; @@ -65,6 +66,8 @@ struct GinIndexPostingsBuilder /// Deserialize the postings list data from given ReadBuffer, return a pointer to the GinIndexPostingsList created by deserialization static GinIndexPostingsListPtr deserialize(ReadBuffer &buffer); +private: + static constexpr UInt8 UsesBitMap = 0xFF; }; using GinIndexPostingsBuilderPtr = std::shared_ptr; @@ -99,8 +102,9 @@ using TermDictionaries = std::unordered_map; class GinIndexStore { public: - GinIndexStore(const String& name_) - : name(name_) + explicit GinIndexStore(const String& name_, DataPartStoragePtr storage_) + : name(name_), + storage(storage_) { } GinIndexStore(const String& name_, DataPartStoragePtr storage_, DataPartStorageBuilderPtr data_part_storage_builder_, UInt64 max_digestion_size_) @@ -126,11 +130,6 @@ class GinIndexStore using GinIndexStorePtr = std::shared_ptr; - void SetStorage(DataPartStoragePtr storage_) - { - storage = storage_; - } - GinIndexPostingsBuilderContainer& getPostings() { return current_postings; } /// Check if we need to write segment to Gin index files @@ -147,12 +146,12 @@ class GinIndexStore /// method for writing segment data to Gin index files void writeSegment(); - String getName() const {return name;} + const String & getName() const {return name;} private: - void init_file_streams(); -private: - friend class GinIndexStoreReader; + friend class GinIndexStoreDeserializer; + + void initFileStreams(); String name; DataPartStoragePtr storage; @@ -175,6 +174,11 @@ class GinIndexStore std::unique_ptr segment_file_stream; std::unique_ptr term_dict_file_stream; std::unique_ptr postings_file_stream; + + static constexpr auto GIN_SEGMENT_ID_FILE_TYPE = ".gin_sid"; + static constexpr auto GIN_SEGMENT_FILE_TYPE = ".gin_seg"; + static constexpr auto GIN_DICTIONARY_FILE_TYPE = ".gin_dict"; + static constexpr auto GIN_POSTINGS_FILE_TYPE = ".gin_post"; }; using GinIndexStorePtr = std::shared_ptr; @@ -214,9 +218,6 @@ class GinIndexStoreFactory : private boost::noncopyable /// Remove all GinIndexStores which are under the same part_path void remove(const String& part_path); -#ifndef NDEBUG - void dump(); -#endif private: GinIndexStores stores; @@ -224,14 +225,10 @@ class GinIndexStoreFactory : private boost::noncopyable }; /// Gin Index Store Reader which helps to read segments, term dictionaries and postings list -class GinIndexStoreReader : private boost::noncopyable +class GinIndexStoreDeserializer : private boost::noncopyable { public: - GinIndexStoreReader(const GinIndexStorePtr & store_) - : store(store_) - { - init_file_streams(); - } + GinIndexStoreDeserializer(const GinIndexStorePtr & store_); /// Read all segment information from .gin_seg files void readSegments(); @@ -247,7 +244,7 @@ class GinIndexStoreReader : private boost::noncopyable private: /// Initialize Gin index files - void init_file_streams(); + void initFileStreams(); private: @@ -262,6 +259,6 @@ class GinIndexStoreReader : private boost::noncopyable /// Current segment, used in building index GinIndexSegment current_segment; }; -using GinIndexStoreReaderPtr = std::unique_ptr; +using GinIndexStoreReaderPtr = std::unique_ptr; } diff --git a/src/Storages/MergeTree/MergeTreeIndexGin.cpp b/src/Storages/MergeTree/MergeTreeIndexGin.cpp index 840dfd108959..6b4a1eebda39 100644 --- a/src/Storages/MergeTree/MergeTreeIndexGin.cpp +++ b/src/Storages/MergeTree/MergeTreeIndexGin.cpp @@ -51,14 +51,14 @@ void MergeTreeIndexGranuleGinFilter::serializeBinary(WriteBuffer & ostr) const if (empty()) throw Exception(ErrorCodes::LOGICAL_ERROR, "Attempt to write empty fulltext index {}.", backQuote(index_name)); - const auto & size_type = DataTypePtr(std::make_shared()); + const auto & size_type = std::make_shared(); auto size_serialization = size_type->getDefaultSerialization(); for (const auto & gin_filter : gin_filters) { size_t filter_size = gin_filter.getFilter().size(); size_serialization->serializeBinary(filter_size, ostr); - ostr.write(reinterpret_cast(gin_filter.getFilter().data()), filter_size * sizeof(RowIDRange)); + ostr.write(reinterpret_cast(gin_filter.getFilter().data()), filter_size * sizeof(GinFilter::RowIDRangeContainer::value_type)); } } @@ -68,18 +68,19 @@ void MergeTreeIndexGranuleGinFilter::deserializeBinary(ReadBuffer & istr, MergeT throw Exception(ErrorCodes::LOGICAL_ERROR, "Unknown index version {}.", version); Field field_rows; - const auto & size_type = DataTypePtr(std::make_shared()); + const auto & size_type = std::make_shared(); + auto size_serialization = size_type->getDefaultSerialization(); for (auto & gin_filter : gin_filters) { - size_type->getDefaultSerialization()->deserializeBinary(field_rows, istr); + size_serialization->deserializeBinary(field_rows, istr); size_t filter_size = field_rows.get(); if (filter_size == 0) continue; gin_filter.getFilter().assign(filter_size, {}); - istr.read(reinterpret_cast(gin_filter.getFilter().data()), filter_size * sizeof(RowIDRange)); + istr.read(reinterpret_cast(gin_filter.getFilter().data()), filter_size * sizeof(GinFilter::RowIDRangeContainer::value_type)); } has_elems = true; } @@ -675,6 +676,7 @@ bool MergeTreeConditionGinFilter::tryPrepareSetGinFilter( for (const auto & elem : key_tuple_mapping) { gin_filters.emplace_back(); + gin_filters.back().reserve(prepared_set->getTotalRowCount()); key_position.push_back(elem.key_index); size_t tuple_idx = elem.tuple_index; @@ -774,7 +776,7 @@ void ginIndexValidator(const IndexDescription & index, bool /*attach*/) if (index.arguments.size() > 1) throw Exception("Gin index must have zero or one argument.", ErrorCodes::INCORRECT_QUERY); - if (index.arguments.size() == 1 and index.arguments[0].getType() != Field::Types::UInt64) + if (index.arguments.size() == 1 && index.arguments[0].getType() != Field::Types::UInt64) throw Exception("Gin index argument must be positive integer.", ErrorCodes::INCORRECT_QUERY); size_t ngrams = index.arguments.empty() ? 0 : index.arguments[0].get(); diff --git a/src/Storages/MergeTree/MergeTreeIndexGin.h b/src/Storages/MergeTree/MergeTreeIndexGin.h index f344210f959a..5e2fabdd0cd5 100644 --- a/src/Storages/MergeTree/MergeTreeIndexGin.h +++ b/src/Storages/MergeTree/MergeTreeIndexGin.h @@ -54,8 +54,8 @@ struct MergeTreeIndexAggregatorGinFilter final : IMergeTreeIndexAggregator GinIndexStorePtr store; Names index_columns; - String index_name; - GinFilterParameters params; + const String index_name; + const GinFilterParameters params; TokenExtractorPtr token_extractor; MergeTreeIndexGranuleGinFilterPtr granule; diff --git a/src/Storages/MergeTree/MergeTreeIndices.cpp b/src/Storages/MergeTree/MergeTreeIndices.cpp index ef5e31bbd918..2e4004ed4ae3 100644 --- a/src/Storages/MergeTree/MergeTreeIndices.cpp +++ b/src/Storages/MergeTree/MergeTreeIndices.cpp @@ -1,5 +1,4 @@ #include -#include #include #include #include From 6a5fef632d5308f37602e3c47263b9e2bfb169f9 Mon Sep 17 00:00:00 2001 From: HarryLeeIBM Date: Wed, 28 Sep 2022 07:28:28 -0700 Subject: [PATCH 29/42] Added more tests and experimental flag, etc. --- src/Common/FST.cpp | 4 +- src/Common/FST.h | 13 +++- src/Common/tests/gtest_fst.cpp | 44 +++++++++++++- src/Core/Settings.h | 1 + src/Interpreters/GinFilter.cpp | 18 +----- src/Interpreters/GinFilter.h | 17 +++--- src/Interpreters/InterpreterCreateQuery.cpp | 18 ++++-- src/Storages/AlterCommands.cpp | 22 ++++++- src/Storages/MergeTree/MergeTreeData.cpp | 7 +++ src/Storages/MergeTree/MergeTreeIndices.cpp | 5 +- .../02346_full_text_search.reference | 10 ++-- .../0_stateless/02346_full_text_search.sql | 59 +++++++++++++------ 12 files changed, 160 insertions(+), 58 deletions(-) diff --git a/src/Common/FST.cpp b/src/Common/FST.cpp index 9cc3fb35f35f..94f033e3fd46 100644 --- a/src/Common/FST.cpp +++ b/src/Common/FST.cpp @@ -11,7 +11,7 @@ namespace DB { namespace ErrorCodes { - extern const int LOGICAL_ERROR; + extern const int BAD_ARGUMENTS; }; namespace FST @@ -273,7 +273,7 @@ void FSTBuilder::add(const std::string& current_word, Output current_output) auto current_word_len = current_word.size(); if (current_word_len > MAX_TERM_LENGTH) - throw Exception(DB::ErrorCodes::LOGICAL_ERROR, "Too long term ({}) passed to FST builder.", current_word_len); + throw DB::Exception(DB::ErrorCodes::BAD_ARGUMENTS, "Too long term ({}) passed to FST builder.", current_word_len); size_t prefix_length_plus1 = getCommonPrefix(current_word, previous_word) + 1; diff --git a/src/Common/FST.h b/src/Common/FST.h index 06851a72b400..a201df3a8f0d 100644 --- a/src/Common/FST.h +++ b/src/Common/FST.h @@ -24,6 +24,8 @@ namespace FST class State; using StatePtr = std::shared_ptr; + /// Arc represents a transition from one state to another + /// It includes the target state to which the arc points and its output. struct Arc { Arc() = default; @@ -36,6 +38,8 @@ namespace FST bool operator==(const Arc & arc1, const Arc & arc2); + /// ArcsAsBitmap implements a 256-bit bitmap for all arcs of a state. Each bit represents + /// an arc's presence and the index value of the bit represents the corresponding label class ArcsAsBitmap { public: @@ -46,11 +50,13 @@ namespace FST private: friend class State; friend class FiniteStateTransducer; - /// data holds a 256-bit bitmap for all arcs of a node. Ita 256 bits correspond to 256 + /// data holds a 256-bit bitmap for all arcs of a state. Ita 256 bits correspond to 256 /// possible label values. UInt256 data{ 0 }; }; + /// State implements the State in Finite State Transducer + /// Each state contains all its arcs and a flag indicating if it is final state class State { public: @@ -117,6 +123,9 @@ namespace FST inline constexpr size_t MAX_TERM_LENGTH = 256; + /// FSTBuilder is used to build Finite State Transducer by adding words incrementally. + /// Note that all the words have to be added in sorted order in order to achieve minimized result. + /// In the end, the caller should call build() to serialize minimized FST to WriteBuffer class FSTBuilder { public: @@ -144,6 +153,8 @@ namespace FST UInt64 previous_state_index = 0; }; + //FiniteStateTransducer is constructed by using minimized FST blob(which is loaded from index storage) + // It is used to retrieve output by given term class FiniteStateTransducer { public: diff --git a/src/Common/tests/gtest_fst.cpp b/src/Common/tests/gtest_fst.cpp index ca56fa1fb2bb..211f98cab74d 100644 --- a/src/Common/tests/gtest_fst.cpp +++ b/src/Common/tests/gtest_fst.cpp @@ -17,7 +17,7 @@ TEST(FST, SimpleTest) {"top", 55}, }; - std::vector> not_indexed_dta + std::vector> not_indexed_data { {"mo", 100}, {"moth1", 91}, @@ -46,9 +46,49 @@ TEST(FST, SimpleTest) ASSERT_EQ(result, output); } - for (auto& [term, output] : not_indexed_dta) + for (auto& [term, output] : not_indexed_data) { auto [result, found] = fst.getOutput(term); ASSERT_EQ(found, false); } } + +TEST(FST, TestForLongTerms) +{ + /// Test long terms within limitation + std::string term1(DB::FST::MAX_TERM_LENGTH - 1, 'A'); + std::string term2(DB::FST::MAX_TERM_LENGTH, 'B'); + + DB::FST::Output output1 = 100; + DB::FST::Output output2 = 200; + + std::vector buffer; + DB::WriteBufferFromVector> wbuf(buffer); + DB::FST::FSTBuilder builder(wbuf); + + builder.add(term1, output1); + builder.add(term2, output2); + + builder.build(); + wbuf.finalize(); + + DB::FST::FiniteStateTransducer fst(buffer); + + auto [result1, found1] = fst.getOutput(term1); + ASSERT_EQ(found1, true); + ASSERT_EQ(result1, output1); + + auto [result2, found2] = fst.getOutput(term2); + ASSERT_EQ(found2, true); + ASSERT_EQ(result2, output2); + + /// Test exception case when term length exceeds limitation + std::string term3(DB::FST::MAX_TERM_LENGTH + 1, 'C'); + DB::FST::Output output3 = 300; + + std::vector buffer3; + DB::WriteBufferFromVector> wbuf3(buffer3); + DB::FST::FSTBuilder builder3(wbuf3); + + EXPECT_THROW(builder3.add(term3, output3), DB::Exception); +} diff --git a/src/Core/Settings.h b/src/Core/Settings.h index 1278a9de6c77..a33522d03c8b 100644 --- a/src/Core/Settings.h +++ b/src/Core/Settings.h @@ -182,6 +182,7 @@ static constexpr UInt64 operator""_GiB(unsigned long long value) M(UInt64, merge_tree_max_rows_to_use_cache, (128 * 8192), "The maximum number of rows per request, to use the cache of uncompressed data. If the request is large, the cache is not used. (For large queries not to flush out the cache.)", 0) \ M(UInt64, merge_tree_max_bytes_to_use_cache, (192 * 10 * 1024 * 1024), "The maximum number of bytes per request, to use the cache of uncompressed data. If the request is large, the cache is not used. (For large queries not to flush out the cache.)", 0) \ M(Bool, do_not_merge_across_partitions_select_final, false, "Merge parts only in one partition in select final", 0) \ + M(Bool, allow_experimental_inverted_index, false, "If it is set to true, allow to use experimental inverted index.", 0) \ \ M(UInt64, mysql_max_rows_to_insert, 65536, "The maximum number of rows in MySQL batch insertion of the MySQL storage engine", 0) \ \ diff --git a/src/Interpreters/GinFilter.cpp b/src/Interpreters/GinFilter.cpp index a58d315ddbe9..abeead9faaa8 100644 --- a/src/Interpreters/GinFilter.cpp +++ b/src/Interpreters/GinFilter.cpp @@ -144,23 +144,9 @@ bool GinFilter::match(const PostingsCachePtr& postings_cache) const return false; } -bool GinFilter::needsFilter() const -{ - if (getTerms().empty()) - return false; - - for (const auto & term: getTerms()) - { - if (term.size() > FST::MAX_TERM_LENGTH) - return false; - } - - return true; -} - bool GinFilter::contains(const GinFilter & filter, PostingsCacheForStore &cache_store) const { - if (!filter.needsFilter()) + if (filter.getTerms().empty()) return true; PostingsCachePtr postings_cache = cache_store.getPostings(filter.getQueryString()); @@ -176,7 +162,7 @@ bool GinFilter::contains(const GinFilter & filter, PostingsCacheForStore &cache_ String GinFilter::getName() { - return "gin"; + return FilterName; } } diff --git a/src/Interpreters/GinFilter.h b/src/Interpreters/GinFilter.h index dca1d95a302a..ceaf98dcc03a 100644 --- a/src/Interpreters/GinFilter.h +++ b/src/Interpreters/GinFilter.h @@ -31,7 +31,7 @@ class GinFilter explicit GinFilter(const GinFilterParameters& params); - static void add(const char* data, size_t len, UInt32 rowID, GinIndexStorePtr& store); + void add(const char* data, size_t len, UInt32 rowID, GinIndexStorePtr& store); void addRowRangeToGinFilter(UInt32 segmentID, UInt32 rowIDStart, UInt32 rowIDEnd); @@ -52,19 +52,20 @@ class GinFilter const String &getQueryString() const { return query_string; } -#ifndef NDEBUG - void dump() const; -#endif - - void addTerm(const char* data, size_t len) { terms.push_back(String(data, len));} + void addTerm(const char* data, size_t len) + { + if (len > FST::MAX_TERM_LENGTH) + return; + terms.push_back(String(data, len)); + } const std::vector& getTerms() const { return terms;} - bool needsFilter() const; - bool match(const PostingsCachePtr& postings_cache) const; static String getName(); + + static constexpr auto FilterName = "inverted"; private: [[maybe_unused]] size_t ngrams; diff --git a/src/Interpreters/InterpreterCreateQuery.cpp b/src/Interpreters/InterpreterCreateQuery.cpp index c359b24df35b..557e945bb514 100644 --- a/src/Interpreters/InterpreterCreateQuery.cpp +++ b/src/Interpreters/InterpreterCreateQuery.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include @@ -102,6 +103,7 @@ namespace ErrorCodes extern const int ENGINE_REQUIRED; extern const int UNKNOWN_STORAGE; extern const int SYNTAX_ERROR; + extern const int SUPPORT_IS_DISABLED; } namespace fs = std::filesystem; @@ -679,12 +681,18 @@ InterpreterCreateQuery::TableProperties InterpreterCreateQuery::getTableProperti if (create.columns_list->indices) for (const auto & index : create.columns_list->indices->children) { - properties.indices.push_back( - IndexDescription::getIndexFromAST(index->clone(), properties.columns, getContext())); - if (properties.indices.back().type == "annoy" && !getContext()->getSettingsRef().allow_experimental_annoy_index) - throw Exception("Annoy index is disabled. Turn on allow_experimental_annoy_index", ErrorCodes::INCORRECT_QUERY); - } + IndexDescription index_desc = IndexDescription::getIndexFromAST(index->clone(), properties.columns, getContext()); + if (index_desc.type == GinFilter::FilterName && getContext()->getSettingsRef().allow_experimental_inverted_index == false) + { + throw Exception( + "Experimental Inverted Index feature is not enabled (the setting 'allow_experimental_inverted_index')", + ErrorCodes::SUPPORT_IS_DISABLED); + } + if (index_desc.type == "annoy" && !getContext()->getSettingsRef().allow_experimental_annoy_index) + throw Exception("Annoy index is disabled. Turn on allow_experimental_annoy_index", ErrorCodes::INCORRECT_QUERY); + properties.indices.push_back(index_desc); + } if (create.columns_list->projections) for (const auto & projection_ast : create.columns_list->projections->children) { diff --git a/src/Storages/AlterCommands.cpp b/src/Storages/AlterCommands.cpp index f39f830bcc0e..e6c5913b150c 100644 --- a/src/Storages/AlterCommands.cpp +++ b/src/Storages/AlterCommands.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -904,7 +905,26 @@ std::optional AlterCommand::tryConvertToMutationCommand(Storage return result; } - +bool AlterCommands::hasInvertedIndex(const StorageInMemoryMetadata & metadata, ContextPtr context) const +{ + for (const auto & index : metadata.secondary_indices) + { + IndexDescription index_desc; + try + { + index_desc = IndexDescription::getIndexFromAST(index.definition_ast, metadata.columns, context); + } + catch (...) + { + continue; + } + if (index.type == GinFilter::FilterName) + { + return true; + } + } + return false; +} void AlterCommands::apply(StorageInMemoryMetadata & metadata, ContextPtr context) const { if (!prepared) diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index 263c95bd68c7..83470175277f 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -2461,6 +2461,13 @@ void MergeTreeData::checkAlterIsPossible(const AlterCommands & commands, Context if (!mutation_commands.empty()) throw Exception(ErrorCodes::ALTER_OF_COLUMN_IS_FORBIDDEN, "The following alter commands: '{}' will modify data on disk, but setting `allow_non_metadata_alters` is disabled", queryToString(mutation_commands.ast())); } + + if (commands.hasInvertedIndex(new_metadata, getContext()) && !settings.allow_experimental_inverted_index) + { + throw Exception( + "Experimental Inverted Index feature is not enabled (the setting 'allow_experimental_inverted_index')", + ErrorCodes::SUPPORT_IS_DISABLED); + } commands.apply(new_metadata, getContext()); /// Set of columns that shouldn't be altered. diff --git a/src/Storages/MergeTree/MergeTreeIndices.cpp b/src/Storages/MergeTree/MergeTreeIndices.cpp index 2e4004ed4ae3..9711c7cdacac 100644 --- a/src/Storages/MergeTree/MergeTreeIndices.cpp +++ b/src/Storages/MergeTree/MergeTreeIndices.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -105,8 +106,8 @@ MergeTreeIndexFactory::MergeTreeIndexFactory() registerCreator("annoy", annoyIndexCreator); registerValidator("annoy", annoyIndexValidator); #endif - registerCreator("gin", ginIndexCreator); - registerValidator("gin", ginIndexValidator); + registerCreator(GinFilter::FilterName, ginIndexCreator); + registerValidator(GinFilter::FilterName, ginIndexValidator); } MergeTreeIndexFactory & MergeTreeIndexFactory::instance() diff --git a/tests/queries/0_stateless/02346_full_text_search.reference b/tests/queries/0_stateless/02346_full_text_search.reference index 690c30feecb9..8f59a02d7a04 100644 --- a/tests/queries/0_stateless/02346_full_text_search.reference +++ b/tests/queries/0_stateless/02346_full_text_search.reference @@ -1,14 +1,16 @@ -af gin +af inverted 101 Alick a01 111 Alick b01 -af gin +af inverted 101 Alick a01 106 Alick a06 111 Alick b01 116 Alick b06 -af gin +af inverted 101 Alick a01 111 Alick b01 201 rick c01 -af gin +af inverted 102 clickhouse你好 +af inverted +BC614E,05397FB1,6969696969898240,CF3304 diff --git a/tests/queries/0_stateless/02346_full_text_search.sql b/tests/queries/0_stateless/02346_full_text_search.sql index 84103a95acb6..f8ebcbf74382 100644 --- a/tests/queries/0_stateless/02346_full_text_search.sql +++ b/tests/queries/0_stateless/02346_full_text_search.sql @@ -1,16 +1,17 @@ SET log_queries = 1; +SET allow_experimental_inverted_index = 1; --- create table for gin(2) +-- create table for inverted(2) DROP TABLE IF EXISTS simple1; -CREATE TABLE simple1(k UInt64,s String,INDEX af (s) TYPE gin(2) GRANULARITY 1) +CREATE TABLE simple1(k UInt64,s String,INDEX af (s) TYPE inverted(2) GRANULARITY 1) ENGINE = MergeTree() ORDER BY k SETTINGS index_granularity = 2; -- insert test data into table INSERT INTO simple1 VALUES (101, 'Alick a01'), (102, 'Blick a02'), (103, 'Click a03'),(104, 'Dlick a04'),(105, 'Elick a05'),(106, 'Alick a06'),(107, 'Blick a07'),(108, 'Click a08'),(109, 'Dlick a09'),(110, 'Elick b10'),(111, 'Alick b01'),(112, 'Blick b02'),(113, 'Click b03'),(114, 'Dlick b04'),(115, 'Elick b05'),(116, 'Alick b06'),(117, 'Blick b07'),(118, 'Click b08'),(119, 'Dlick b09'),(120, 'Elick b10'); --- check gin index was created +-- check inverted index was created SELECT name, type FROM system.data_skipping_indices where (table =='simple1') limit 1; --- search gin index +-- search inverted index SELECT * FROM simple1 WHERE s LIKE '%01%' order by k; SYSTEM FLUSH LOGS; -- check the query only read 2 granules (4 rows total; each granule has 2 rows) @@ -20,18 +21,18 @@ SELECT read_rows, result_rows from system.query_log and endsWith(trimRight(query), 'SELECT * FROM simple1 WHERE s LIKE \'%01%\';') and result_rows==2 limit 1; --- create table for gin() +-- create table for inverted() DROP TABLE IF EXISTS simple2; -CREATE TABLE simple2(k UInt64,s String,INDEX af (s) TYPE gin() GRANULARITY 1) +CREATE TABLE simple2(k UInt64,s String,INDEX af (s) TYPE inverted() GRANULARITY 1) ENGINE = MergeTree() ORDER BY k SETTINGS index_granularity = 2; -- insert test data into table INSERT INTO simple2 VALUES (101, 'Alick a01'), (102, 'Blick a02'), (103, 'Click a03'),(104, 'Dlick a04'),(105, 'Elick a05'),(106, 'Alick a06'),(107, 'Blick a07'),(108, 'Click a08'),(109, 'Dlick a09'),(110, 'Elick b10'),(111, 'Alick b01'),(112, 'Blick b02'),(113, 'Click b03'),(114, 'Dlick b04'),(115, 'Elick b05'),(116, 'Alick b06'),(117, 'Blick b07'),(118, 'Click b08'),(119, 'Dlick b09'),(120, 'Elick b10'); --- check gin index was created +-- check inverted index was created SELECT name, type FROM system.data_skipping_indices where (table =='simple2') limit 1; --- search gin index +-- search inverted index SELECT * FROM simple2 WHERE hasToken(s, 'Alick') order by k; SYSTEM FLUSH LOGS; -- check the query only read 4 granules (8 rows total; each granule has 2 rows) @@ -41,18 +42,18 @@ SELECT read_rows, result_rows from system.query_log and endsWith(trimRight(query), 'SELECT * FROM simple2 WHERE hasToken(s, \'Alick\');') and result_rows==4 limit 1; --- create table for gin(2) with two parts +-- create table for inverted(2) with two parts DROP TABLE IF EXISTS simple3; -CREATE TABLE simple3(k UInt64,s String,INDEX af (s) TYPE gin(2) GRANULARITY 1) +CREATE TABLE simple3(k UInt64,s String,INDEX af (s) TYPE inverted(2) GRANULARITY 1) ENGINE = MergeTree() ORDER BY k SETTINGS index_granularity = 2; -- insert test data into table INSERT INTO simple3 VALUES (101, 'Alick a01'), (102, 'Blick a02'), (103, 'Click a03'),(104, 'Dlick a04'),(105, 'Elick a05'),(106, 'Alick a06'),(107, 'Blick a07'),(108, 'Click a08'),(109, 'Dlick a09'),(110, 'Elick b10'),(111, 'Alick b01'),(112, 'Blick b02'),(113, 'Click b03'),(114, 'Dlick b04'),(115, 'Elick b05'),(116, 'Alick b06'),(117, 'Blick b07'),(118, 'Click b08'),(119, 'Dlick b09'),(120, 'Elick b10'); INSERT INTO simple3 VALUES (201, 'rick c01'), (202, 'mick c02'),(203, 'nick c03'); --- check gin index was created +-- check inverted index was created SELECT name, type FROM system.data_skipping_indices where (table =='simple3') limit 1; --- search gin index +-- search inverted index SELECT * FROM simple3 WHERE s LIKE '%01%' order by k; SYSTEM FLUSH LOGS; -- check the query only read 3 granules (6 rows total; each granule has 2 rows) @@ -62,15 +63,15 @@ SELECT read_rows, result_rows from system.query_log and endsWith(trimRight(query), 'SELECT * FROM simple3 WHERE s LIKE \'%01%\';') and result_rows==3 limit 1; --- create table for gin(2) for utf8 string test +-- create table for inverted(2) for utf8 string test DROP TABLE IF EXISTS simple4; -CREATE TABLE simple4(k UInt64,s String,INDEX af (s) TYPE gin(2) GRANULARITY 1) ENGINE = MergeTree() ORDER BY k +CREATE TABLE simple4(k UInt64,s String,INDEX af (s) TYPE inverted(2) GRANULARITY 1) ENGINE = MergeTree() ORDER BY k SETTINGS index_granularity = 2; -- insert test data into table INSERT INTO simple4 VALUES (101, 'Alick 好'),(102, 'clickhouse你好'), (103, 'Click ä½ '),(104, 'Dlick ä½ a好'),(105, 'Elick 好好你你'),(106, 'Alick 好a好aä½ aä½ '); --- check gin index was created +-- check inverted index was created SELECT name, type FROM system.data_skipping_indices where (table =='simple4') limit 1; --- search gin index +-- search inverted index SELECT * FROM simple4 WHERE s LIKE '%你好%' order by k; SYSTEM FLUSH LOGS; -- check the query only read 1 granule (2 rows total; each granule has 2 rows) @@ -78,4 +79,28 @@ SELECT read_rows, result_rows from system.query_log where query_kind ='Select' and current_database = currentDatabase() and endsWith(trimRight(query), 'SELECT * FROM simple4 WHERE s LIKE \'%你好%\';') - and result_rows==1 limit 1; \ No newline at end of file + and result_rows==1 limit 1; + +-- create table for max_digestion_size_per_segment test +DROP TABLE IF EXISTS simple5; +CREATE TABLE simple5(k UInt64,s String,INDEX af(s) TYPE inverted(0) GRANULARITY 1) + Engine=MergeTree + ORDER BY (k) + SETTINGS max_digestion_size_per_segment = 1024, index_granularity = 256 + AS + SELECT + number, + format('{},{},{},{}', hex(12345678), hex(87654321), hex(number/17 + 5), hex(13579012)) as s + FROM numbers(10240); + +-- check inverted index was created +SELECT name, type FROM system.data_skipping_indices where (table =='simple5') limit 1; +-- search inverted index +SELECT s FROM simple5 WHERE hasToken(s, '6969696969898240'); +SYSTEM FLUSH LOGS; +-- check the query only read 1 granule (1 row total; each granule has 256 rows) +SELECT read_rows, result_rows from system.query_log + where query_kind ='Select' + and current_database = currentDatabase() + and startsWith(query, 'SELECT s FROM simple5 WHERE hasToken') + and result_rows==1 limit 1; From d7a7de9601e8f236dabf0093bc83741d0e09ec68 Mon Sep 17 00:00:00 2001 From: HarryLeeIBM Date: Thu, 29 Sep 2022 12:30:02 -0700 Subject: [PATCH 30/42] Minor fixes for addressing reviews --- src/Storages/MergeTree/GinIndexStore.cpp | 14 +++++++++----- src/Storages/MergeTree/GinIndexStore.h | 2 ++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/Storages/MergeTree/GinIndexStore.cpp b/src/Storages/MergeTree/GinIndexStore.cpp index 7f109fb7c63e..aeab2e0d2edb 100644 --- a/src/Storages/MergeTree/GinIndexStore.cpp +++ b/src/Storages/MergeTree/GinIndexStore.cpp @@ -176,6 +176,9 @@ UInt32 GinIndexStore::getNextSegmentID() UInt32 GinIndexStore::getSegmentNum() { + if (cached_segment_num) + return cached_segment_num; + String sid_file_name = getName() + GIN_SEGMENT_ID_FILE_TYPE; if (!storage->exists(sid_file_name)) return 0; @@ -190,7 +193,9 @@ UInt32 GinIndexStore::getSegmentNum() size_type->getDefaultSerialization()->deserializeBinary(field_rows, *istr); result = field_rows.get(); } - return result - 1; + + cached_segment_num = result - 1; + return cached_segment_num; } bool GinIndexStore::needToWrite() const @@ -308,10 +313,9 @@ void GinIndexStoreDeserializer::readSegments() return; GinIndexSegments segments (segment_num); - if (segment_file_stream == nullptr) - { - initFileStreams(); - } + + assert(segment_file_stream != nullptr); + segment_file_stream->read(reinterpret_cast(segments.data()), segment_num * sizeof(GinIndexSegment)); for (size_t i = 0; i < segment_num; ++i) { diff --git a/src/Storages/MergeTree/GinIndexStore.h b/src/Storages/MergeTree/GinIndexStore.h index d27801359487..3e7bf7094526 100644 --- a/src/Storages/MergeTree/GinIndexStore.h +++ b/src/Storages/MergeTree/GinIndexStore.h @@ -157,6 +157,8 @@ class GinIndexStore DataPartStoragePtr storage; DataPartStorageBuilderPtr data_part_storage_builder; + UInt32 cached_segment_num = 0; + std::mutex gin_index_store_mutex; /// Terms dictionaries which are loaded from .gin_dict files From f0a2c06261549ec488442c8ef5c9c33b0eed89f9 Mon Sep 17 00:00:00 2001 From: Larry Luo Date: Fri, 30 Sep 2022 10:30:15 -0700 Subject: [PATCH 31/42] Fixed errors caused by merging. --- src/Storages/MergeTree/MergeTreeDataPartWriterOnDisk.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Storages/MergeTree/MergeTreeDataPartWriterOnDisk.cpp b/src/Storages/MergeTree/MergeTreeDataPartWriterOnDisk.cpp index 6ac04246bcfc..c1f472de676a 100644 --- a/src/Storages/MergeTree/MergeTreeDataPartWriterOnDisk.cpp +++ b/src/Storages/MergeTree/MergeTreeDataPartWriterOnDisk.cpp @@ -220,7 +220,6 @@ void MergeTreeDataPartWriterOnDisk::initSkipIndices() gin_index_stores[stream_name] = store; } skip_indices_aggregators.push_back(index_helper->createIndexAggregatorForPart(store)); - skip_index_accumulated_marks.push_back(0); } } From 71da1b1379f085ffb973d1ede7f6b4e36c882311 Mon Sep 17 00:00:00 2001 From: Larry Luo Date: Thu, 29 Dec 2022 08:00:17 -0800 Subject: [PATCH 32/42] Updated inverted index to use ActionsDAG --- src/Common/FST.cpp | 4 +- src/Common/FST.h | 2 +- .../MergeTree/DataPartStorageOnDisk.cpp | 2 +- src/Storages/MergeTree/GinIndexStore.cpp | 18 +- src/Storages/MergeTree/GinIndexStore.h | 8 +- src/Storages/MergeTree/IDataPartStorage.h | 5 +- src/Storages/MergeTree/IMergeTreeDataPart.cpp | 2 +- .../MergeTreeDataPartWriterOnDisk.cpp | 2 +- .../MergeTree/MergeTreeDataSelectExecutor.cpp | 2 +- src/Storages/MergeTree/MergeTreeIndexGin.cpp | 164 ++++++++++-------- src/Storages/MergeTree/MergeTreeIndexGin.h | 8 +- .../0_stateless/02346_full_text_search.sql | 6 +- 12 files changed, 122 insertions(+), 101 deletions(-) diff --git a/src/Common/FST.cpp b/src/Common/FST.cpp index 94f033e3fd46..16d20c76b729 100644 --- a/src/Common/FST.cpp +++ b/src/Common/FST.cpp @@ -349,7 +349,7 @@ std::pair FiniteStateTransducer::getOutput(const String& term) read_buffer.seek(data.size()-1, SEEK_SET); UInt8 length{ 0 }; - read_buffer.read(reinterpret_cast(length)); + read_buffer.readStrict(reinterpret_cast(length)); if (length == 0) return { 0, false }; @@ -377,7 +377,7 @@ std::pair FiniteStateTransducer::getOutput(const String& term) { /// Read number of labels UInt8 label_num{ 0 }; - read_buffer.read(reinterpret_cast(label_num)); + read_buffer.readStrict(reinterpret_cast(label_num)); if (label_num == 0) return { 0, false }; diff --git a/src/Common/FST.h b/src/Common/FST.h index a201df3a8f0d..0f17e54d3536 100644 --- a/src/Common/FST.h +++ b/src/Common/FST.h @@ -99,7 +99,7 @@ namespace FST } inline void readFlag(ReadBuffer & read_buffer) { - read_buffer.read(reinterpret_cast(flag)); + read_buffer.readStrict(reinterpret_cast(flag)); } UInt64 id = 0; diff --git a/src/Storages/MergeTree/DataPartStorageOnDisk.cpp b/src/Storages/MergeTree/DataPartStorageOnDisk.cpp index 9b21cf7631d7..b60634544d9a 100644 --- a/src/Storages/MergeTree/DataPartStorageOnDisk.cpp +++ b/src/Storages/MergeTree/DataPartStorageOnDisk.cpp @@ -741,7 +741,7 @@ std::unique_ptr DataPartStorageOnDisk::writeFile( return volume->getDisk()->writeFile(fs::path(root_path) / part_dir / name, buf_size, WriteMode::Rewrite, settings); } -std::unique_ptr DataPartStorageBuilderOnDisk::writeFile( +std::unique_ptr DataPartStorageOnDisk::writeFile( const String & name, size_t buf_size, WriteMode mode, diff --git a/src/Storages/MergeTree/GinIndexStore.cpp b/src/Storages/MergeTree/GinIndexStore.cpp index aeab2e0d2edb..39f2ec671cb3 100644 --- a/src/Storages/MergeTree/GinIndexStore.cpp +++ b/src/Storages/MergeTree/GinIndexStore.cpp @@ -89,7 +89,7 @@ UInt64 GinIndexPostingsBuilder::serialize(WriteBuffer &buffer) const GinIndexPostingsListPtr GinIndexPostingsBuilder::deserialize(ReadBuffer &buffer) { UInt8 postings_list_size{0}; - buffer.read(reinterpret_cast(postings_list_size)); + buffer.readStrict(reinterpret_cast(postings_list_size)); if (postings_list_size != UsesBitMap) { @@ -133,7 +133,7 @@ UInt32 GinIndexStore::getNextIDRange(const String& file_name, size_t n) const auto& int_type = DB::DataTypePtr(std::make_shared()); auto size_serialization = int_type->getDefaultSerialization(); - size_serialization->serializeBinary(1, *ostr); + size_serialization->serializeBinary(1, *ostr, {}); ostr->sync(); } @@ -146,8 +146,8 @@ UInt32 GinIndexStore::getNextIDRange(const String& file_name, size_t n) const auto& size_type = DB::DataTypePtr(std::make_shared()); auto size_serialization = size_type->getDefaultSerialization(); - size_type->getDefaultSerialization()->deserializeBinary(field_rows, *istr); - result = field_rows.get(); + size_type->getDefaultSerialization()->deserializeBinary(field_rows, *istr, {}); + result = static_cast(field_rows.get()); } //save result+n { @@ -155,7 +155,7 @@ UInt32 GinIndexStore::getNextIDRange(const String& file_name, size_t n) const auto& int_type = DB::DataTypePtr(std::make_shared()); auto size_serialization = int_type->getDefaultSerialization(); - size_serialization->serializeBinary(result + n, *ostr); + size_serialization->serializeBinary(result + n, *ostr, {}); ostr->sync(); } return result; @@ -190,8 +190,8 @@ UInt32 GinIndexStore::getSegmentNum() const auto& size_type = DB::DataTypePtr(std::make_shared()); auto size_serialization = size_type->getDefaultSerialization(); - size_type->getDefaultSerialization()->deserializeBinary(field_rows, *istr); - result = field_rows.get(); + size_type->getDefaultSerialization()->deserializeBinary(field_rows, *istr, {}); + result = static_cast(field_rows.get()); } cached_segment_num = result - 1; @@ -316,7 +316,7 @@ void GinIndexStoreDeserializer::readSegments() assert(segment_file_stream != nullptr); - segment_file_stream->read(reinterpret_cast(segments.data()), segment_num * sizeof(GinIndexSegment)); + segment_file_stream->readStrict(reinterpret_cast(segments.data()), segment_num * sizeof(GinIndexSegment)); for (size_t i = 0; i < segment_num; ++i) { auto seg_id = segments[i].segment_id; @@ -409,7 +409,7 @@ GinIndexStorePtr GinIndexStoreFactory::get(const String& name, DataPartStoragePt GinIndexStoreDeserializer reader(store); reader.readSegments(); - for (size_t seg_index = 0; seg_index < store->getSegmentNum(); ++seg_index) + for (UInt32 seg_index = 0; seg_index < store->getSegmentNum(); ++seg_index) { reader.readTermDictionary(seg_index); } diff --git a/src/Storages/MergeTree/GinIndexStore.h b/src/Storages/MergeTree/GinIndexStore.h index 3e7bf7094526..d0981df7293e 100644 --- a/src/Storages/MergeTree/GinIndexStore.h +++ b/src/Storages/MergeTree/GinIndexStore.h @@ -107,7 +107,7 @@ class GinIndexStore storage(storage_) { } - GinIndexStore(const String& name_, DataPartStoragePtr storage_, DataPartStorageBuilderPtr data_part_storage_builder_, UInt64 max_digestion_size_) + GinIndexStore(const String& name_, DataPartStoragePtr storage_, MutableDataPartStoragePtr data_part_storage_builder_, UInt64 max_digestion_size_) : name(name_), storage(storage_), data_part_storage_builder(data_part_storage_builder_), @@ -138,7 +138,7 @@ class GinIndexStore /// Accumulate the size of text data which has been digested void addSize(UInt64 sz) { current_size += sz; } - UInt64 getCurrentSegmentID() { return current_segment.segment_id;} + UInt32 getCurrentSegmentID() { return current_segment.segment_id;} /// Do last segment writing void finalize(); @@ -154,8 +154,8 @@ class GinIndexStore void initFileStreams(); String name; - DataPartStoragePtr storage; - DataPartStorageBuilderPtr data_part_storage_builder; + DataPartStoragePtr storage; + MutableDataPartStoragePtr data_part_storage_builder; UInt32 cached_segment_num = 0; diff --git a/src/Storages/MergeTree/IDataPartStorage.h b/src/Storages/MergeTree/IDataPartStorage.h index eea1e120e934..8197983ea932 100644 --- a/src/Storages/MergeTree/IDataPartStorage.h +++ b/src/Storages/MergeTree/IDataPartStorage.h @@ -216,7 +216,8 @@ class IDataPartStorage : public boost::noncopyable const String & name, size_t buf_size, const WriteSettings & settings) = 0; - + virtual std::unique_ptr writeFile(const String & name, size_t buf_size, WriteMode mode, const WriteSettings & settings) = 0; + /// A special const method to write transaction file. /// It's const, because file with transaction metadata /// can be modified after part creation. @@ -225,8 +226,6 @@ class IDataPartStorage : public boost::noncopyable virtual void createFile(const String & name) = 0; virtual void moveFile(const String & from_name, const String & to_name) = 0; virtual void replaceFile(const String & from_name, const String & to_name) = 0; - virtual std::unique_ptr writeFile(const String & name, size_t buf_size, const WriteSettings & settings) = 0; - virtual std::unique_ptr writeFile(const String & name, size_t buf_size, WriteMode mode, const WriteSettings & settings) = 0; virtual void removeFile(const String & name) = 0; virtual void removeFileIfExists(const String & name) = 0; diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.cpp b/src/Storages/MergeTree/IMergeTreeDataPart.cpp index 9863b254e12f..d88d89ce970e 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.cpp +++ b/src/Storages/MergeTree/IMergeTreeDataPart.cpp @@ -1673,7 +1673,7 @@ void IMergeTreeDataPart::remove() metadata_manager->deleteAll(false); metadata_manager->assertAllDeleted(false); - GinIndexStoreFactory::instance().remove(data_part_storage->getRelativePath()); + GinIndexStoreFactory::instance().remove(getDataPartStoragePtr()->getRelativePath()); std::list projection_checksums; diff --git a/src/Storages/MergeTree/MergeTreeDataPartWriterOnDisk.cpp b/src/Storages/MergeTree/MergeTreeDataPartWriterOnDisk.cpp index c1f472de676a..e9629f83d096 100644 --- a/src/Storages/MergeTree/MergeTreeDataPartWriterOnDisk.cpp +++ b/src/Storages/MergeTree/MergeTreeDataPartWriterOnDisk.cpp @@ -216,7 +216,7 @@ void MergeTreeDataPartWriterOnDisk::initSkipIndices() GinIndexStorePtr store = nullptr; if (dynamic_cast(&*index_helper) != nullptr) { - store = std::make_shared(stream_name, data_part->data_part_storage, data_part_storage_builder, storage.getSettings()->max_digestion_size_per_segment); + store = std::make_shared(stream_name, data_part->getDataPartStoragePtr(), data_part->getDataPartStoragePtr(), storage.getSettings()->max_digestion_size_per_segment); gin_index_stores[stream_name] = store; } skip_indices_aggregators.push_back(index_helper->createIndexAggregatorForPart(store)); diff --git a/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp b/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp index 9b49e996aa99..fa7626603796 100644 --- a/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp +++ b/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp @@ -1692,7 +1692,7 @@ MarkRanges MergeTreeDataSelectExecutor::filterMarksUsingIndex( if (dynamic_cast(&*index_helper) != nullptr) { - cache_in_store.store = GinIndexStoreFactory::instance().get(index_helper->getFileName(), part->data_part_storage); + cache_in_store.store = GinIndexStoreFactory::instance().get(index_helper->getFileName(), part->getDataPartStoragePtr()); } for (size_t i = 0; i < ranges.size(); ++i) diff --git a/src/Storages/MergeTree/MergeTreeIndexGin.cpp b/src/Storages/MergeTree/MergeTreeIndexGin.cpp index 6b4a1eebda39..29a48e9f471f 100644 --- a/src/Storages/MergeTree/MergeTreeIndexGin.cpp +++ b/src/Storages/MergeTree/MergeTreeIndexGin.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -57,7 +58,7 @@ void MergeTreeIndexGranuleGinFilter::serializeBinary(WriteBuffer & ostr) const for (const auto & gin_filter : gin_filters) { size_t filter_size = gin_filter.getFilter().size(); - size_serialization->serializeBinary(filter_size, ostr); + size_serialization->serializeBinary(filter_size, ostr, {}); ostr.write(reinterpret_cast(gin_filter.getFilter().data()), filter_size * sizeof(GinFilter::RowIDRangeContainer::value_type)); } } @@ -73,14 +74,14 @@ void MergeTreeIndexGranuleGinFilter::deserializeBinary(ReadBuffer & istr, MergeT auto size_serialization = size_type->getDefaultSerialization(); for (auto & gin_filter : gin_filters) { - size_serialization->deserializeBinary(field_rows, istr); + size_serialization->deserializeBinary(field_rows, istr, {}); size_t filter_size = field_rows.get(); if (filter_size == 0) continue; gin_filter.getFilter().assign(filter_size, {}); - istr.read(reinterpret_cast(gin_filter.getFilter().data()), filter_size * sizeof(GinFilter::RowIDRangeContainer::value_type)); + istr.readStrict(reinterpret_cast(gin_filter.getFilter().data()), filter_size * sizeof(GinFilter::RowIDRangeContainer::value_type)); } has_elems = true; } @@ -177,7 +178,7 @@ void MergeTreeIndexAggregatorGinFilter::update(const Block & block, size_t * pos need_to_write = true; } } - granule->gin_filters[col].addRowRangeToGinFilter(store->getCurrentSegmentID(), start_row_id, start_row_id + rows_read - 1); + granule->gin_filters[col].addRowRangeToGinFilter(store->getCurrentSegmentID(), start_row_id, static_cast(start_row_id + rows_read - 1)); if (need_to_write) { store->writeSegment(); @@ -190,23 +191,51 @@ void MergeTreeIndexAggregatorGinFilter::update(const Block & block, size_t * pos MergeTreeConditionGinFilter::MergeTreeConditionGinFilter( const SelectQueryInfo & query_info, - ContextPtr context, + ContextPtr context_, const Block & index_sample_block, const GinFilterParameters & params_, TokenExtractorPtr token_extactor_) - : index_columns(index_sample_block.getNames()) + : WithContext(context_) + , index_columns(index_sample_block.getNames()) , index_data_types(index_sample_block.getNamesAndTypesList().getTypes()) , params(params_) , token_extractor(token_extactor_) , prepared_sets(query_info.prepared_sets) { - rpn = std::move( - RPNBuilder( - query_info, context, - [this] (const ASTPtr & node, ContextPtr /* context */, Block & block_with_constants, RPNElement & out) -> bool - { - return this->traverseAtomAST(node, block_with_constants, out); - }).extractRPN()); + if (context_->getSettingsRef().allow_experimental_analyzer) + { + if (!query_info.filter_actions_dag) + { + rpn.push_back(RPNElement::FUNCTION_UNKNOWN); + return; + } + rpn = std::move( + RPNBuilder( + query_info.filter_actions_dag->getOutputs().at(0), context_, + // [this] (const ASTPtr & node, ContextPtr /* context */, Block & block_with_constants, RPNElement & out) -> bool + [&](const RPNBuilderTreeNode & node, RPNElement & out) + { + return this->traverseAtomAST(node, out); + }).extractRPN()); + } + + ASTPtr filter_node = buildFilterNode(query_info.query); + + if (!filter_node) + { + rpn.push_back(RPNElement::FUNCTION_UNKNOWN); + return; + } + + auto block_with_constants = KeyCondition::getBlockWithConstants(query_info.query, query_info.syntax_analyzer_result, context_); + RPNBuilder builder( + filter_node, + context_, + std::move(block_with_constants), + query_info.prepared_sets, + [&](const RPNBuilderTreeNode & node, RPNElement & out) { return traverseAtomAST(node, out); }); + rpn = std::move(builder).extractRPN(); + } bool MergeTreeConditionGinFilter::alwaysUnknownOrTrue() const @@ -358,13 +387,13 @@ bool MergeTreeConditionGinFilter::getKey(const std::string & key_column_name, si return true; } -bool MergeTreeConditionGinFilter::traverseAtomAST(const ASTPtr & node, Block & block_with_constants, RPNElement & out) +bool MergeTreeConditionGinFilter::traverseAtomAST(const RPNBuilderTreeNode & node, RPNElement & out) { { Field const_value; DataTypePtr const_type; - if (KeyCondition::getConstant(node, block_with_constants, const_value, const_type)) + if (node.tryGetConstant(const_value, const_type)) { /// Check constant like in KeyCondition if (const_value.getType() == Field::Types::UInt64 @@ -381,53 +410,55 @@ bool MergeTreeConditionGinFilter::traverseAtomAST(const ASTPtr & node, Block & b } } - if (const auto * function = node->as()) + if (node.isFunction()) { - if (!function->arguments) - return false; - - const ASTs & arguments = function->arguments->children; + const auto function = node.toFunctionNode(); + // auto arguments_size = function.getArgumentsSize(); + auto function_name = function.getFunctionName(); - if (arguments.size() != 2) + size_t function_arguments_size = function.getArgumentsSize(); + if (function_arguments_size != 2) return false; + auto lhs_argument = function.getArgumentAt(0); + auto rhs_argument = function.getArgumentAt(1); - if (functionIsInOrGlobalInOperator(function->name)) + if (functionIsInOrGlobalInOperator(function_name)) { - if (tryPrepareSetGinFilter(arguments, out)) + if (tryPrepareSetGinFilter(lhs_argument, rhs_argument, out)) { - if (function->name == "notIn") + if (function_name == "notIn") { out.function = RPNElement::FUNCTION_NOT_IN; return true; } - else if (function->name == "in") + else if (function_name == "in") { out.function = RPNElement::FUNCTION_IN; return true; } } } - else if (function->name == "equals" || - function->name == "notEquals" || - function->name == "has" || - function->name == "mapContains" || - function->name == "like" || - function->name == "notLike" || - function->name == "hasToken" || - function->name == "startsWith" || - function->name == "endsWith" || - function->name == "multiSearchAny") + else if (function_name == "equals" || + function_name == "notEquals" || + function_name == "has" || + function_name == "mapContains" || + function_name == "like" || + function_name == "notLike" || + function_name == "hasToken" || + function_name == "startsWith" || + function_name == "endsWith" || + function_name == "multiSearchAny") { Field const_value; DataTypePtr const_type; - if (KeyCondition::getConstant(arguments[1], block_with_constants, const_value, const_type)) + if (rhs_argument.tryGetConstant(const_value, const_type)) { - if (traverseASTEquals(function->name, arguments[0], const_type, const_value, out)) + if (traverseASTEquals(function_name, lhs_argument, const_type, const_value, out)) return true; } - else if (KeyCondition::getConstant(arguments[0], block_with_constants, const_value, const_type) && (function->name == "equals" || function->name == "notEquals")) + else if (lhs_argument.tryGetConstant(const_value, const_type) && (function_name == "equals" || function_name == "notEquals")) { - if (traverseASTEquals(function->name, arguments[1], const_type, const_value, out)) + if (traverseASTEquals(function_name, rhs_argument, const_type, const_value, out)) return true; } } @@ -438,7 +469,7 @@ bool MergeTreeConditionGinFilter::traverseAtomAST(const ASTPtr & node, Block & b bool MergeTreeConditionGinFilter::traverseASTEquals( const String & function_name, - const ASTPtr & key_ast, + const RPNBuilderTreeNode & key_ast, const DataTypePtr & value_type, const Field & value_field, RPNElement & out) @@ -450,12 +481,13 @@ bool MergeTreeConditionGinFilter::traverseASTEquals( Field const_value = value_field; size_t key_column_num = 0; - bool key_exists = getKey(key_ast->getColumnName(), key_column_num); - bool map_key_exists = getKey(fmt::format("mapKeys({})", key_ast->getColumnName()), key_column_num); + bool key_exists = getKey(key_ast.getColumnName(), key_column_num); + bool map_key_exists = getKey(fmt::format("mapKeys({})", key_ast.getColumnName()), key_column_num); - if (const auto * function = key_ast->as()) + if (key_ast.isFunction()) { - if (function->name == "arrayElement") + const auto function = key_ast.toFunctionNode(); + if (function.getFunctionName() == "arrayElement") { /** Try to parse arrayElement for mapKeys index. * It is important to ignore keys like column_map['Key'] = '' because if key does not exists in map @@ -467,11 +499,8 @@ bool MergeTreeConditionGinFilter::traverseASTEquals( if (value_field == value_type->getDefault()) return false; - const auto * column_ast_identifier = function->arguments.get()->children[0].get()->as(); - if (!column_ast_identifier) - return false; - - const auto & map_column_name = column_ast_identifier->name(); + auto first_argument = function.getArgumentAt(0); + const auto map_column_name = first_argument.getColumnName(); size_t map_keys_key_column_num = 0; auto map_keys_index_column_name = fmt::format("mapKeys({})", map_column_name); @@ -483,12 +512,11 @@ bool MergeTreeConditionGinFilter::traverseASTEquals( if (map_keys_exists) { - auto & argument = function->arguments.get()->children[1]; - - if (const auto * literal = argument->as()) + // auto & argument = function->arguments.get()->children[1]; + auto argument = function.getArgumentAt(1); + DataTypePtr const_type; + if (argument.tryGetConstant(const_value, const_type)) { - auto element_key = literal->value; - const_value = element_key; key_column_num = map_keys_key_column_num; key_exists = true; } @@ -619,23 +647,21 @@ bool MergeTreeConditionGinFilter::traverseASTEquals( } bool MergeTreeConditionGinFilter::tryPrepareSetGinFilter( - const ASTs & args, + const RPNBuilderTreeNode & lhs, + const RPNBuilderTreeNode & rhs, RPNElement & out) { - const ASTPtr & left_arg = args[0]; - const ASTPtr & right_arg = args[1]; - std::vector key_tuple_mapping; DataTypes data_types; - const auto * left_arg_tuple = typeid_cast(left_arg.get()); - if (left_arg_tuple && left_arg_tuple->name == "tuple") + if (lhs.isFunction() && lhs.toFunctionNode().getFunctionName() == "tuple") { - const auto & tuple_elements = left_arg_tuple->arguments->children; - for (size_t i = 0; i < tuple_elements.size(); ++i) + const auto function = lhs.toFunctionNode(); + auto arguments_size = function.getArgumentsSize(); + for (size_t i = 0; i < arguments_size; ++i) { size_t key = 0; - if (getKey(tuple_elements[i]->getColumnName(), key)) + if (getKey(function.getArgumentAt(i).getColumnName(), key)) { key_tuple_mapping.emplace_back(i, key); data_types.push_back(index_data_types[key]); @@ -645,7 +671,7 @@ bool MergeTreeConditionGinFilter::tryPrepareSetGinFilter( else { size_t key = 0; - if (getKey(left_arg->getColumnName(), key)) + if (getKey(lhs.getColumnName(), key)) { key_tuple_mapping.emplace_back(0, key); data_types.push_back(index_data_types[key]); @@ -655,14 +681,8 @@ bool MergeTreeConditionGinFilter::tryPrepareSetGinFilter( if (key_tuple_mapping.empty()) return false; - PreparedSetKey set_key; - if (typeid_cast(right_arg.get()) || typeid_cast(right_arg.get())) - set_key = PreparedSetKey::forSubquery(*right_arg); - else - set_key = PreparedSetKey::forLiteral(*right_arg, data_types); - - const SetPtr & prepared_set = prepared_sets->get(set_key); - if (!prepared_set || !prepared_set->hasExplicitSetElements()) + ConstSetPtr prepared_set = rhs.tryGetPreparedSet(); + if (!prepared_set && !prepared_set->hasExplicitSetElements()) return false; for (const auto & data_type : prepared_set->getDataTypes()) diff --git a/src/Storages/MergeTree/MergeTreeIndexGin.h b/src/Storages/MergeTree/MergeTreeIndexGin.h index 5e2fabdd0cd5..f03ac1a5de67 100644 --- a/src/Storages/MergeTree/MergeTreeIndexGin.h +++ b/src/Storages/MergeTree/MergeTreeIndexGin.h @@ -62,7 +62,7 @@ struct MergeTreeIndexAggregatorGinFilter final : IMergeTreeIndexAggregator }; -class MergeTreeConditionGinFilter final : public IMergeTreeIndexCondition +class MergeTreeConditionGinFilter final : public IMergeTreeIndexCondition, WithContext { public: MergeTreeConditionGinFilter( @@ -132,17 +132,17 @@ class MergeTreeConditionGinFilter final : public IMergeTreeIndexCondition using RPN = std::vector; - bool traverseAtomAST(const ASTPtr & node, Block & block_with_constants, RPNElement & out); + bool traverseAtomAST(const RPNBuilderTreeNode & node, RPNElement & out); bool traverseASTEquals( const String & function_name, - const ASTPtr & key_ast, + const RPNBuilderTreeNode & key_ast, const DataTypePtr & value_type, const Field & value_field, RPNElement & out); bool getKey(const std::string & key_column_name, size_t & key_column_num); - bool tryPrepareSetGinFilter(const ASTs & args, RPNElement & out); + bool tryPrepareSetGinFilter(const RPNBuilderTreeNode & lhs, const RPNBuilderTreeNode & rhs, RPNElement & out); static bool createFunctionEqualsCondition( RPNElement & out, const Field & value, const GinFilterParameters & params, TokenExtractorPtr token_extractor); diff --git a/tests/queries/0_stateless/02346_full_text_search.sql b/tests/queries/0_stateless/02346_full_text_search.sql index f8ebcbf74382..cd8844402bc2 100644 --- a/tests/queries/0_stateless/02346_full_text_search.sql +++ b/tests/queries/0_stateless/02346_full_text_search.sql @@ -18,8 +18,9 @@ SYSTEM FLUSH LOGS; SELECT read_rows, result_rows from system.query_log where query_kind ='Select' and current_database = currentDatabase() - and endsWith(trimRight(query), 'SELECT * FROM simple1 WHERE s LIKE \'%01%\';') - and result_rows==2 limit 1; + and endsWith(trimRight(query), 'SELECT * FROM simple1 WHERE s LIKE \'%01%\';') + and type='QueryFinish' + limit 1; -- create table for inverted() DROP TABLE IF EXISTS simple2; @@ -40,6 +41,7 @@ SELECT read_rows, result_rows from system.query_log where query_kind ='Select' and current_database = currentDatabase() and endsWith(trimRight(query), 'SELECT * FROM simple2 WHERE hasToken(s, \'Alick\');') + and type='QueryFinish' and result_rows==4 limit 1; -- create table for inverted(2) with two parts From 7cc84c674332331d13cc479dd18f963ba6d3e77d Mon Sep 17 00:00:00 2001 From: Larry Luo Date: Sat, 31 Dec 2022 09:06:56 -0800 Subject: [PATCH 33/42] refactor filter functions support --- src/Storages/MergeTree/MergeTreeIndexGin.cpp | 49 ++----- src/Storages/MergeTree/MergeTreeIndexGin.h | 4 +- .../02346_full_text_search.reference | 28 ++++ .../0_stateless/02346_full_text_search.sql | 130 +++++++++++++++--- 4 files changed, 157 insertions(+), 54 deletions(-) diff --git a/src/Storages/MergeTree/MergeTreeIndexGin.cpp b/src/Storages/MergeTree/MergeTreeIndexGin.cpp index 29a48e9f471f..5581365bd135 100644 --- a/src/Storages/MergeTree/MergeTreeIndexGin.cpp +++ b/src/Storages/MergeTree/MergeTreeIndexGin.cpp @@ -195,9 +195,7 @@ MergeTreeConditionGinFilter::MergeTreeConditionGinFilter( const Block & index_sample_block, const GinFilterParameters & params_, TokenExtractorPtr token_extactor_) - : WithContext(context_) - , index_columns(index_sample_block.getNames()) - , index_data_types(index_sample_block.getNamesAndTypesList().getTypes()) + : WithContext(context_), header(index_sample_block) , params(params_) , token_extractor(token_extactor_) , prepared_sets(query_info.prepared_sets) @@ -212,7 +210,6 @@ MergeTreeConditionGinFilter::MergeTreeConditionGinFilter( rpn = std::move( RPNBuilder( query_info.filter_actions_dag->getOutputs().at(0), context_, - // [this] (const ASTPtr & node, ContextPtr /* context */, Block & block_with_constants, RPNElement & out) -> bool [&](const RPNBuilderTreeNode & node, RPNElement & out) { return this->traverseAtomAST(node, out); @@ -377,16 +374,6 @@ bool MergeTreeConditionGinFilter::mayBeTrueOnGranuleInPart(MergeTreeIndexGranule return rpn_stack[0].can_be_true; } -bool MergeTreeConditionGinFilter::getKey(const std::string & key_column_name, size_t & key_column_num) -{ - auto it = std::find(index_columns.begin(), index_columns.end(), key_column_name); - if (it == index_columns.end()) - return false; - - key_column_num = static_cast(it - index_columns.begin()); - return true; -} - bool MergeTreeConditionGinFilter::traverseAtomAST(const RPNBuilderTreeNode & node, RPNElement & out) { { @@ -479,10 +466,9 @@ bool MergeTreeConditionGinFilter::traverseASTEquals( return false; Field const_value = value_field; - size_t key_column_num = 0; - bool key_exists = getKey(key_ast.getColumnName(), key_column_num); - bool map_key_exists = getKey(fmt::format("mapKeys({})", key_ast.getColumnName()), key_column_num); + bool key_exists = header.has(key_ast.getColumnName()); + bool map_key_exists = header.has(fmt::format("mapKeys({})", key_ast.getColumnName())); if (key_ast.isFunction()) { @@ -501,23 +487,16 @@ bool MergeTreeConditionGinFilter::traverseASTEquals( auto first_argument = function.getArgumentAt(0); const auto map_column_name = first_argument.getColumnName(); - - size_t map_keys_key_column_num = 0; auto map_keys_index_column_name = fmt::format("mapKeys({})", map_column_name); - bool map_keys_exists = getKey(map_keys_index_column_name, map_keys_key_column_num); - - size_t map_values_key_column_num = 0; auto map_values_index_column_name = fmt::format("mapValues({})", map_column_name); - bool map_values_exists = getKey(map_values_index_column_name, map_values_key_column_num); - if (map_keys_exists) + if (header.has(map_keys_index_column_name)) { - // auto & argument = function->arguments.get()->children[1]; auto argument = function.getArgumentAt(1); DataTypePtr const_type; if (argument.tryGetConstant(const_value, const_type)) { - key_column_num = map_keys_key_column_num; + key_column_num = header.getPositionByName(map_keys_index_column_name); key_exists = true; } else @@ -525,9 +504,9 @@ bool MergeTreeConditionGinFilter::traverseASTEquals( return false; } } - else if (map_values_exists) + else if (header.has(map_values_index_column_name)) { - key_column_num = map_values_key_column_num; + key_column_num = header.getPositionByName(map_values_index_column_name); key_exists = true; } else @@ -660,21 +639,21 @@ bool MergeTreeConditionGinFilter::tryPrepareSetGinFilter( auto arguments_size = function.getArgumentsSize(); for (size_t i = 0; i < arguments_size; ++i) { - size_t key = 0; - if (getKey(function.getArgumentAt(i).getColumnName(), key)) + if (header.has(function.getArgumentAt(i).getColumnName())) { + auto key = header.getPositionByName(function.getArgumentAt(i).getColumnName()); key_tuple_mapping.emplace_back(i, key); - data_types.push_back(index_data_types[key]); + data_types.push_back(header.getByPosition(key).type); } } } else { - size_t key = 0; - if (getKey(lhs.getColumnName(), key)) + if (header.has(lhs.getColumnName())) { + auto key = header.getPositionByName(lhs.getColumnName()); key_tuple_mapping.emplace_back(0, key); - data_types.push_back(index_data_types[key]); + data_types.push_back(header.getByPosition(key).type); } } @@ -705,7 +684,7 @@ bool MergeTreeConditionGinFilter::tryPrepareSetGinFilter( { gin_filters.back().emplace_back(params); auto ref = column->getDataAt(row); - gin_filters.back().back().setQueryString(ref.data, ref.size); + token_extractor->stringToGinFilter(ref.data, ref.size, gin_filters.back().back()); } } diff --git a/src/Storages/MergeTree/MergeTreeIndexGin.h b/src/Storages/MergeTree/MergeTreeIndexGin.h index f03ac1a5de67..048fab349771 100644 --- a/src/Storages/MergeTree/MergeTreeIndexGin.h +++ b/src/Storages/MergeTree/MergeTreeIndexGin.h @@ -141,14 +141,12 @@ class MergeTreeConditionGinFilter final : public IMergeTreeIndexCondition, WithC const Field & value_field, RPNElement & out); - bool getKey(const std::string & key_column_name, size_t & key_column_num); bool tryPrepareSetGinFilter(const RPNBuilderTreeNode & lhs, const RPNBuilderTreeNode & rhs, RPNElement & out); static bool createFunctionEqualsCondition( RPNElement & out, const Field & value, const GinFilterParameters & params, TokenExtractorPtr token_extractor); - Names index_columns; - DataTypes index_data_types; + const Block & header; GinFilterParameters params; TokenExtractorPtr token_extractor; RPN rpn; diff --git a/tests/queries/0_stateless/02346_full_text_search.reference b/tests/queries/0_stateless/02346_full_text_search.reference index 8f59a02d7a04..f5d8a2950813 100644 --- a/tests/queries/0_stateless/02346_full_text_search.reference +++ b/tests/queries/0_stateless/02346_full_text_search.reference @@ -1,16 +1,44 @@ af inverted 101 Alick a01 +1 +101 Alick a01 111 Alick b01 +1 +103 Click a03 +108 Click a08 +113 Click b03 +118 Click b08 +1 af inverted 101 Alick a01 106 Alick a06 111 Alick b01 116 Alick b06 +101 Alick a01 +106 Alick a06 +1 +101 Alick a01 +111 Alick b01 +1 +af inverted +3 ['Click a03','Click b03'] +1 +af inverted +103 {'Click':'Click a03'} +108 {'Click':'Click a08'} +113 {'Click':'Click b03'} +118 {'Click':'Click b08'} +1 +103 {'Click':'Click a03'} +1 af inverted 101 Alick a01 111 Alick b01 201 rick c01 +1 af inverted 102 clickhouse你好 +1 af inverted BC614E,05397FB1,6969696969898240,CF3304 +1 diff --git a/tests/queries/0_stateless/02346_full_text_search.sql b/tests/queries/0_stateless/02346_full_text_search.sql index cd8844402bc2..116987ceaf94 100644 --- a/tests/queries/0_stateless/02346_full_text_search.sql +++ b/tests/queries/0_stateless/02346_full_text_search.sql @@ -7,21 +7,45 @@ CREATE TABLE simple1(k UInt64,s String,INDEX af (s) TYPE inverted(2) GRANULARITY ENGINE = MergeTree() ORDER BY k SETTINGS index_granularity = 2; -- insert test data into table -INSERT INTO simple1 VALUES (101, 'Alick a01'), (102, 'Blick a02'), (103, 'Click a03'),(104, 'Dlick a04'),(105, 'Elick a05'),(106, 'Alick a06'),(107, 'Blick a07'),(108, 'Click a08'),(109, 'Dlick a09'),(110, 'Elick b10'),(111, 'Alick b01'),(112, 'Blick b02'),(113, 'Click b03'),(114, 'Dlick b04'),(115, 'Elick b05'),(116, 'Alick b06'),(117, 'Blick b07'),(118, 'Click b08'),(119, 'Dlick b09'),(120, 'Elick b10'); +INSERT INTO simple1 VALUES (101, 'Alick a01'), (102, 'Blick a02'), (103, 'Click a03'),(104, 'Dlick a04'),(105, 'Elick a05'),(106, 'Alick a06'),(107, 'Blick a07'),(108, 'Click a08'),(109, 'Dlick a09'),(110, 'Elick a10'),(111, 'Alick b01'),(112, 'Blick b02'),(113, 'Click b03'),(114, 'Dlick b04'),(115, 'Elick b05'),(116, 'Alick b06'),(117, 'Blick b07'),(118, 'Click b08'),(119, 'Dlick b09'),(120, 'Elick b10'); -- check inverted index was created SELECT name, type FROM system.data_skipping_indices where (table =='simple1') limit 1; --- search inverted index -SELECT * FROM simple1 WHERE s LIKE '%01%' order by k; +-- search inverted index with == +SELECT * FROM simple1 WHERE s == 'Alick a01'; +SYSTEM FLUSH LOGS; +-- check the query only read 1 granules (2 rows total; each granule has 2 rows) +SELECT read_rows==2 from system.query_log + where query_kind ='Select' + and current_database = currentDatabase() + and endsWith(trimRight(query), 'SELECT * FROM simple1 WHERE s == \'Alick a01\';') + and type='QueryFinish' + and result_rows==1 + limit 1; + +-- search inverted index with LIKE +SELECT * FROM simple1 WHERE s LIKE '%01%' ORDER BY k; SYSTEM FLUSH LOGS; -- check the query only read 2 granules (4 rows total; each granule has 2 rows) -SELECT read_rows, result_rows from system.query_log +SELECT read_rows==4 from system.query_log where query_kind ='Select' and current_database = currentDatabase() - and endsWith(trimRight(query), 'SELECT * FROM simple1 WHERE s LIKE \'%01%\';') - and type='QueryFinish' + and endsWith(trimRight(query), 'SELECT * FROM simple1 WHERE s LIKE \'%01%\' ORDER BY k;') + and type='QueryFinish' + and result_rows==2 limit 1; +-- search inverted index with hasToken +SELECT * FROM simple1 WHERE hasToken(s, 'Click') ORDER BY k; +SYSTEM FLUSH LOGS; +-- check the query only read 4 granules (8 rows total; each granule has 2 rows) +SELECT read_rows==8 from system.query_log + where query_kind ='Select' + and current_database = currentDatabase() + and endsWith(trimRight(query), 'SELECT * FROM simple1 WHERE hasToken(s, \'Click\') ORDER BY k;') + and type='QueryFinish' + and result_rows==4 limit 1; + -- create table for inverted() DROP TABLE IF EXISTS simple2; CREATE TABLE simple2(k UInt64,s String,INDEX af (s) TYPE inverted() GRANULARITY 1) @@ -29,27 +53,98 @@ CREATE TABLE simple2(k UInt64,s String,INDEX af (s) TYPE inverted() GRANULARITY SETTINGS index_granularity = 2; -- insert test data into table -INSERT INTO simple2 VALUES (101, 'Alick a01'), (102, 'Blick a02'), (103, 'Click a03'),(104, 'Dlick a04'),(105, 'Elick a05'),(106, 'Alick a06'),(107, 'Blick a07'),(108, 'Click a08'),(109, 'Dlick a09'),(110, 'Elick b10'),(111, 'Alick b01'),(112, 'Blick b02'),(113, 'Click b03'),(114, 'Dlick b04'),(115, 'Elick b05'),(116, 'Alick b06'),(117, 'Blick b07'),(118, 'Click b08'),(119, 'Dlick b09'),(120, 'Elick b10'); +INSERT INTO simple2 VALUES (101, 'Alick a01'), (102, 'Blick a02'), (103, 'Click a03'),(104, 'Dlick a04'),(105, 'Elick a05'),(106, 'Alick a06'),(107, 'Blick a07'),(108, 'Click a08'),(109, 'Dlick a09'),(110, 'Elick a10'),(111, 'Alick b01'),(112, 'Blick b02'),(113, 'Click b03'),(114, 'Dlick b04'),(115, 'Elick b05'),(116, 'Alick b06'),(117, 'Blick b07'),(118, 'Click b08'),(119, 'Dlick b09'),(120, 'Elick b10'); -- check inverted index was created SELECT name, type FROM system.data_skipping_indices where (table =='simple2') limit 1; --- search inverted index + +-- search inverted index with hasToken SELECT * FROM simple2 WHERE hasToken(s, 'Alick') order by k; SYSTEM FLUSH LOGS; -- check the query only read 4 granules (8 rows total; each granule has 2 rows) -SELECT read_rows, result_rows from system.query_log +SELECT read_rows==8 from system.query_log where query_kind ='Select' and current_database = currentDatabase() and endsWith(trimRight(query), 'SELECT * FROM simple2 WHERE hasToken(s, \'Alick\');') and type='QueryFinish' and result_rows==4 limit 1; +-- search inverted index with IN operator +SELECT * FROM simple2 WHERE s IN ('Alick a01', 'Alick a06') ORDER BY k; +SYSTEM FLUSH LOGS; +-- check the query only read 2 granules (4 rows total; each granule has 2 rows) +SELECT read_rows==4 from system.query_log + where query_kind ='Select' + and current_database = currentDatabase() + and endsWith(trimRight(query), 'SELECT * FROM simple2 WHERE s IN (\'Alick a01\', \'Alick a06\') ORDER BY k;') + and type='QueryFinish' + and result_rows==2 limit 1; + +-- search inverted index with multiSearch +SELECT * FROM simple2 WHERE multiSearchAny(s, ['a01', 'b01']) ORDER BY k; +SYSTEM FLUSH LOGS; +-- check the query only read 2 granules (4 rows total; each granule has 2 rows) +SELECT read_rows==4 from system.query_log + where query_kind ='Select' + and current_database = currentDatabase() + and endsWith(trimRight(query), 'SELECT * FROM simple2 WHERE multiSearchAny(s, [\'a01\', \'b01\']) ORDER BY k;') + and type='QueryFinish' + and result_rows==2 limit 1; + +-- create table with an array column +DROP TABLE IF EXISTS simple_array; +create table simple_array (k UInt64, s Array(String), INDEX af (s) TYPE inverted(2) GRANULARITY 1) + ENGINE = MergeTree() ORDER BY k + SETTINGS index_granularity = 2; +INSERT INTO simple_array SELECT rowNumberInBlock(), groupArray(s) FROM simple2 GROUP BY k%10; +-- check inverted index was created +SELECT name, type FROM system.data_skipping_indices where (table =='simple_array') limit 1; +-- search inverted index with has +SELECT * FROM simple_array WHERE has(s, 'Click a03') ORDER BY k; +SYSTEM FLUSH LOGS; +-- check the query must read all 10 granules (20 rows total; each granule has 2 rows) +SELECT read_rows==2 from system.query_log + where query_kind ='Select' + and current_database = currentDatabase() + and endsWith(trimRight(query), 'SELECT * FROM simple_array WHERE has(s, \'Click a03\') ORDER BY k;') + and type='QueryFinish' + and result_rows==1 limit 1; + +-- create table with a map column +DROP TABLE IF EXISTS simple_map; +CREATE TABLE simple_map (k UInt64, s Map(String,String), INDEX af (mapKeys(s)) TYPE inverted(2) GRANULARITY 1) + ENGINE = MergeTree() ORDER BY k + SETTINGS index_granularity = 2; +INSERT INTO simple_map VALUES (101, {'Alick':'Alick a01'}), (102, {'Blick':'Blick a02'}), (103, {'Click':'Click a03'}),(104, {'Dlick':'Dlick a04'}),(105, {'Elick':'Elick a05'}),(106, {'Alick':'Alick a06'}),(107, {'Blick':'Blick a07'}),(108, {'Click':'Click a08'}),(109, {'Dlick':'Dlick a09'}),(110, {'Elick':'Elick a10'}),(111, {'Alick':'Alick b01'}),(112, {'Blick':'Blick b02'}),(113, {'Click':'Click b03'}),(114, {'Dlick':'Dlick b04'}),(115, {'Elick':'Elick b05'}),(116, {'Alick':'Alick b06'}),(117, {'Blick':'Blick b07'}),(118, {'Click':'Click b08'}),(119, {'Dlick':'Dlick b09'}),(120, {'Elick':'Elick b10'}); +-- check inverted index was created +SELECT name, type FROM system.data_skipping_indices where (table =='simple_map') limit 1; +-- search inverted index with mapContains +SELECT * FROM simple_map WHERE mapContains(s, 'Click') ORDER BY k; +SYSTEM FLUSH LOGS; +-- check the query must read all 4 granules (8 rows total; each granule has 2 rows) +SELECT read_rows==8 from system.query_log + where query_kind ='Select' + and current_database = currentDatabase() + and endsWith(trimRight(query), 'SELECT * FROM simple_map WHERE mapContains(s, \'Click\') ORDER BY k;') + and type='QueryFinish' + and result_rows==4 limit 1; + +-- search inverted index with map key +SELECT * FROM simple_map WHERE s['Click'] = 'Click a03'; +SYSTEM FLUSH LOGS; +-- check the query must read all 4 granules (8 rows total; each granule has 2 rows) +SELECT read_rows==8 from system.query_log + where query_kind ='Select' + and current_database = currentDatabase() + and endsWith(trimRight(query), 'SELECT * FROM simple_map WHERE s[\'Click\'] = \'Click a03\';') + and type='QueryFinish' + and result_rows==1 limit 1; + -- create table for inverted(2) with two parts DROP TABLE IF EXISTS simple3; CREATE TABLE simple3(k UInt64,s String,INDEX af (s) TYPE inverted(2) GRANULARITY 1) ENGINE = MergeTree() ORDER BY k SETTINGS index_granularity = 2; - -- insert test data into table INSERT INTO simple3 VALUES (101, 'Alick a01'), (102, 'Blick a02'), (103, 'Click a03'),(104, 'Dlick a04'),(105, 'Elick a05'),(106, 'Alick a06'),(107, 'Blick a07'),(108, 'Click a08'),(109, 'Dlick a09'),(110, 'Elick b10'),(111, 'Alick b01'),(112, 'Blick b02'),(113, 'Click b03'),(114, 'Dlick b04'),(115, 'Elick b05'),(116, 'Alick b06'),(117, 'Blick b07'),(118, 'Click b08'),(119, 'Dlick b09'),(120, 'Elick b10'); INSERT INTO simple3 VALUES (201, 'rick c01'), (202, 'mick c02'),(203, 'nick c03'); @@ -59,10 +154,11 @@ SELECT name, type FROM system.data_skipping_indices where (table =='simple3') li SELECT * FROM simple3 WHERE s LIKE '%01%' order by k; SYSTEM FLUSH LOGS; -- check the query only read 3 granules (6 rows total; each granule has 2 rows) -SELECT read_rows, result_rows from system.query_log +SELECT read_rows==6 from system.query_log where query_kind ='Select' and current_database = currentDatabase() - and endsWith(trimRight(query), 'SELECT * FROM simple3 WHERE s LIKE \'%01%\';') + and endsWith(trimRight(query), 'SELECT * FROM simple3 WHERE s LIKE \'%01%\' order by k;') + and type='QueryFinish' and result_rows==3 limit 1; -- create table for inverted(2) for utf8 string test @@ -77,10 +173,11 @@ SELECT name, type FROM system.data_skipping_indices where (table =='simple4') li SELECT * FROM simple4 WHERE s LIKE '%你好%' order by k; SYSTEM FLUSH LOGS; -- check the query only read 1 granule (2 rows total; each granule has 2 rows) -SELECT read_rows, result_rows from system.query_log +SELECT read_rows==2 from system.query_log where query_kind ='Select' and current_database = currentDatabase() - and endsWith(trimRight(query), 'SELECT * FROM simple4 WHERE s LIKE \'%你好%\';') + and endsWith(trimRight(query), 'SELECT * FROM simple4 WHERE s LIKE \'%你好%\' order by k;') + and type='QueryFinish' and result_rows==1 limit 1; -- create table for max_digestion_size_per_segment test @@ -101,8 +198,9 @@ SELECT name, type FROM system.data_skipping_indices where (table =='simple5') li SELECT s FROM simple5 WHERE hasToken(s, '6969696969898240'); SYSTEM FLUSH LOGS; -- check the query only read 1 granule (1 row total; each granule has 256 rows) -SELECT read_rows, result_rows from system.query_log +SELECT read_rows==256 from system.query_log where query_kind ='Select' and current_database = currentDatabase() - and startsWith(query, 'SELECT s FROM simple5 WHERE hasToken') + and endsWith(trimRight(query), 'SELECT s FROM simple5 WHERE hasToken(s, \'6969696969898240\');') + and type='QueryFinish' and result_rows==1 limit 1; From 3b6c46dc6849272c7a20cc5edc2dbfe66d999102 Mon Sep 17 00:00:00 2001 From: Larry Luo Date: Sun, 1 Jan 2023 06:11:31 -0800 Subject: [PATCH 34/42] Fix build error --- src/Storages/AlterCommands.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Storages/AlterCommands.h b/src/Storages/AlterCommands.h index c91c82e9c7a6..fd91cc114875 100644 --- a/src/Storages/AlterCommands.h +++ b/src/Storages/AlterCommands.h @@ -210,6 +210,9 @@ class AlterCommands : public std::vector /// empty. If some TTL changes happened than, depending on materialize_ttl /// additional mutation command (MATERIALIZE_TTL) will be returned. MutationCommands getMutationCommands(StorageInMemoryMetadata metadata, bool materialize_ttl, ContextPtr context, bool with_alters=false) const; + + /// Check if commands have any inverted index + bool hasInvertedIndex(const StorageInMemoryMetadata & metadata, ContextPtr context) const; }; } From 7a4d499cb18c0d41b64869700e8bdfd05bd9e1b5 Mon Sep 17 00:00:00 2001 From: Larry Luo Date: Wed, 4 Jan 2023 19:42:45 -0800 Subject: [PATCH 35/42] Added density to control Inverted index size --- src/Interpreters/GinFilter.cpp | 22 ++++++-- src/Interpreters/GinFilter.h | 9 +-- src/Storages/MergeTree/GinIndexStore.cpp | 23 +++++++- src/Storages/MergeTree/GinIndexStore.h | 7 +++ src/Storages/MergeTree/MergeTreeIndexGin.cpp | 27 +++++---- src/Storages/MergeTree/MergeTreeIndexGin.h | 2 +- .../02346_full_text_search.reference | 8 +++ .../0_stateless/02346_full_text_search.sql | 55 +++++++++++++++++++ 8 files changed, 130 insertions(+), 23 deletions(-) diff --git a/src/Interpreters/GinFilter.cpp b/src/Interpreters/GinFilter.cpp index abeead9faaa8..c9df7aef9629 100644 --- a/src/Interpreters/GinFilter.cpp +++ b/src/Interpreters/GinFilter.cpp @@ -19,19 +19,21 @@ namespace ErrorCodes { extern const int BAD_ARGUMENTS; } -GinFilterParameters::GinFilterParameters(size_t ngrams_) - : ngrams(ngrams_) +GinFilterParameters::GinFilterParameters(size_t ngrams_, Float64 density_) + : ngrams(ngrams_), density(density_) { if (ngrams > 8) throw Exception("The size of gin filter cannot be greater than 8", ErrorCodes::BAD_ARGUMENTS); + if (density <= 0 || density > 1 ) + throw Exception("The density of gin filter must be between 0 and 1", ErrorCodes::BAD_ARGUMENTS); } -GinFilter::GinFilter(const GinFilterParameters & params) - : ngrams(params.ngrams) +GinFilter::GinFilter(const GinFilterParameters & params_) + : params(params_) { } -void GinFilter::add(const char* data, size_t len, UInt32 rowID, GinIndexStorePtr& store) +void GinFilter::add(const char* data, size_t len, UInt32 rowID, GinIndexStorePtr& store, UInt64 limit) { if (len > FST::MAX_TERM_LENGTH) return; @@ -46,7 +48,8 @@ void GinFilter::add(const char* data, size_t len, UInt32 rowID, GinIndexStorePtr } else { - GinIndexPostingsBuilderPtr builder = std::make_shared(); + UInt64 threshold = std::lround(limit * params.density); + GinIndexPostingsBuilderPtr builder = std::make_shared(threshold); builder->add(rowID); store->getPostings()[token] = builder; @@ -106,6 +109,13 @@ bool GinFilter::matchInRange(const PostingsCachePtr& postings_cache, UInt32 segm } auto min_in_container = container_it->second->minimum(); auto max_in_container = container_it->second->maximum(); + + //check if the postings list has always match flag + if (container_it->second->cardinality() == 1 && UINT32_MAX == min_in_container) + { + continue; //always match + } + if (range_start > max_in_container || min_in_container > range_end) { return false; diff --git a/src/Interpreters/GinFilter.h b/src/Interpreters/GinFilter.h index ceaf98dcc03a..0f30a932e61c 100644 --- a/src/Interpreters/GinFilter.h +++ b/src/Interpreters/GinFilter.h @@ -7,9 +7,10 @@ namespace DB { struct GinFilterParameters { - explicit GinFilterParameters(size_t ngrams_); + explicit GinFilterParameters(size_t ngrams_, Float64 density_); size_t ngrams; + Float64 density; }; struct RowIDRange @@ -29,9 +30,9 @@ class GinFilter public: using RowIDRangeContainer = std::vector; - explicit GinFilter(const GinFilterParameters& params); + explicit GinFilter(const GinFilterParameters& params_); - void add(const char* data, size_t len, UInt32 rowID, GinIndexStorePtr& store); + void add(const char* data, size_t len, UInt32 rowID, GinIndexStorePtr& store, UInt64 limit); void addRowRangeToGinFilter(UInt32 segmentID, UInt32 rowIDStart, UInt32 rowIDEnd); @@ -67,7 +68,7 @@ class GinFilter static constexpr auto FilterName = "inverted"; private: - [[maybe_unused]] size_t ngrams; + const GinFilterParameters& params; String query_string; diff --git a/src/Storages/MergeTree/GinIndexStore.cpp b/src/Storages/MergeTree/GinIndexStore.cpp index 39f2ec671cb3..97bb0d6f4e76 100644 --- a/src/Storages/MergeTree/GinIndexStore.cpp +++ b/src/Storages/MergeTree/GinIndexStore.cpp @@ -24,6 +24,9 @@ namespace ErrorCodes extern const int LOGICAL_ERROR; }; +GinIndexPostingsBuilder::GinIndexPostingsBuilder(UInt64 limit) : size_limit(limit) +{} + bool GinIndexPostingsBuilder::contains(UInt32 row_id) const { if (useRoaring()) @@ -35,9 +38,22 @@ bool GinIndexPostingsBuilder::contains(UInt32 row_id) const void GinIndexPostingsBuilder::add(UInt32 row_id) { + if (containsAllRows()) + { + return; + } if (useRoaring()) { - rowid_bitmap.add(row_id); + if (rowid_bitmap.cardinality() == size_limit ) + { + //reset the postings list with MATCH ALWAYS; + lst_length = 1; //makes sure useRoaring() returns false; + lst[0] = UINT32_MAX; //set CONTAINS ALL flag; + } + else + { + rowid_bitmap.add(row_id); + } return; } assert(lst_length < MIN_SIZE_FOR_ROARING_ENCODING); @@ -57,6 +73,11 @@ bool GinIndexPostingsBuilder::useRoaring() const return lst_length == UsesBitMap; } +bool GinIndexPostingsBuilder::containsAllRows() const +{ + return lst[0] == UINT32_MAX; +} + UInt64 GinIndexPostingsBuilder::serialize(WriteBuffer &buffer) const { UInt64 written_bytes = 0; diff --git a/src/Storages/MergeTree/GinIndexStore.h b/src/Storages/MergeTree/GinIndexStore.h index d0981df7293e..f6df0856d5fa 100644 --- a/src/Storages/MergeTree/GinIndexStore.h +++ b/src/Storages/MergeTree/GinIndexStore.h @@ -43,6 +43,8 @@ using GinIndexPostingsListPtr = std::shared_ptr; class GinIndexPostingsBuilder { public: + GinIndexPostingsBuilder(UInt64 limit); + /// When the list length is no greater than MIN_SIZE_FOR_ROARING_ENCODING, array 'lst' is used std::array lst; @@ -61,6 +63,9 @@ class GinIndexPostingsBuilder /// Check whether the builder is using roaring bitmap bool useRoaring() const; + /// Check whether the postings list has been flagged to contain all row ids + bool containsAllRows() const; + /// Serialize the content of builder to given WriteBuffer, returns the bytes of serialized data UInt64 serialize(WriteBuffer &buffer) const; @@ -68,6 +73,8 @@ class GinIndexPostingsBuilder static GinIndexPostingsListPtr deserialize(ReadBuffer &buffer); private: static constexpr UInt8 UsesBitMap = 0xFF; + /// Clear the postings list and reset it with MATCHALL flags when the size of the postings list is beyond the limit + UInt64 size_limit; }; using GinIndexPostingsBuilderPtr = std::shared_ptr; diff --git a/src/Storages/MergeTree/MergeTreeIndexGin.cpp b/src/Storages/MergeTree/MergeTreeIndexGin.cpp index 5581365bd135..67ab8398640f 100644 --- a/src/Storages/MergeTree/MergeTreeIndexGin.cpp +++ b/src/Storages/MergeTree/MergeTreeIndexGin.cpp @@ -112,7 +112,7 @@ MergeTreeIndexGranulePtr MergeTreeIndexAggregatorGinFilter::getGranuleAndReset() return new_granule; } -void MergeTreeIndexAggregatorGinFilter::addToGinFilter(UInt32 rowID, const char* data, size_t length, GinFilter& gin_filter) +void MergeTreeIndexAggregatorGinFilter::addToGinFilter(UInt32 rowID, const char* data, size_t length, GinFilter& gin_filter, UInt64 limit) { size_t cur = 0; size_t token_start = 0; @@ -120,7 +120,7 @@ void MergeTreeIndexAggregatorGinFilter::addToGinFilter(UInt32 rowID, const char* while (cur < length && token_extractor->nextInStringPadded(data, length, &cur, &token_start, &token_len)) { - gin_filter.add(data + token_start, token_len, rowID, store); + gin_filter.add(data + token_start, token_len, rowID, store, limit); } } @@ -156,7 +156,7 @@ void MergeTreeIndexAggregatorGinFilter::update(const Block & block, size_t * pos for (size_t row_num = 0; row_num < elements_size; ++row_num) { auto ref = column_key.getDataAt(element_start_row + row_num); - addToGinFilter(row_id, ref.data, ref.size, granule->gin_filters[col]); + addToGinFilter(row_id, ref.data, ref.size, granule->gin_filters[col], rows_read); store->addSize(ref.size); } current_position += 1; @@ -171,7 +171,7 @@ void MergeTreeIndexAggregatorGinFilter::update(const Block & block, size_t * pos for (size_t i = 0; i < rows_read; ++i) { auto ref = column->getDataAt(current_position + i); - addToGinFilter(row_id, ref.data, ref.size, granule->gin_filters[col]); + addToGinFilter(row_id, ref.data, ref.size, granule->gin_filters[col], rows_read); store->addSize(ref.size); row_id++; if (store->needToWrite()) @@ -728,7 +728,8 @@ MergeTreeIndexPtr ginIndexCreator( if (index.type == GinFilter::getName()) { size_t n = index.arguments.empty() ? 0 : index.arguments[0].get(); - GinFilterParameters params(n); + Float64 density = index.arguments.size() < 2 ? 1.0f : index.arguments[1].get(); + GinFilterParameters params(n, density); /// Use SplitTokenExtractor when n is 0, otherwise use NgramTokenExtractor if (n > 0) @@ -766,22 +767,26 @@ void ginIndexValidator(const IndexDescription & index, bool /*attach*/) } if (!data_type.isString() && !data_type.isFixedString()) - throw Exception("Gin filter index can be used only with `String`, `FixedString`, `LowCardinality(String)`, `LowCardinality(FixedString)` column or Array with `String` or `FixedString` values column.", ErrorCodes::INCORRECT_QUERY); + throw Exception("Inverted index can be used only with `String`, `FixedString`, `LowCardinality(String)`, `LowCardinality(FixedString)` column or Array with `String` or `FixedString` values column.", ErrorCodes::INCORRECT_QUERY); } if (index.type != GinFilter::getName()) throw Exception("Unknown index type: " + backQuote(index.name), ErrorCodes::LOGICAL_ERROR); - if (index.arguments.size() > 1) - throw Exception("Gin index must have zero or one argument.", ErrorCodes::INCORRECT_QUERY); + if (index.arguments.size() > 2) + throw Exception("Inverted index must have less than two arguments.", ErrorCodes::INCORRECT_QUERY); - if (index.arguments.size() == 1 && index.arguments[0].getType() != Field::Types::UInt64) - throw Exception("Gin index argument must be positive integer.", ErrorCodes::INCORRECT_QUERY); + if (index.arguments.size() >= 1 && index.arguments[0].getType() != Field::Types::UInt64) + throw Exception("The first Inverted index argument must be positive integer.", ErrorCodes::INCORRECT_QUERY); + + if (index.arguments.size() == 2 && (index.arguments[1].getType() != Field::Types::Float64 || index.arguments[1].get() <= 0 || index.arguments[1].get() > 1)) + throw Exception("The second Inverted index argument must be a float between 0 and 1.", ErrorCodes::INCORRECT_QUERY); size_t ngrams = index.arguments.empty() ? 0 : index.arguments[0].get(); + Float64 density = index.arguments.size() < 2 ? 1.0f : index.arguments[1].get(); /// Just validate - GinFilterParameters params(ngrams); + GinFilterParameters params(ngrams, density); } } diff --git a/src/Storages/MergeTree/MergeTreeIndexGin.h b/src/Storages/MergeTree/MergeTreeIndexGin.h index 048fab349771..d915d4938103 100644 --- a/src/Storages/MergeTree/MergeTreeIndexGin.h +++ b/src/Storages/MergeTree/MergeTreeIndexGin.h @@ -50,7 +50,7 @@ struct MergeTreeIndexAggregatorGinFilter final : IMergeTreeIndexAggregator void update(const Block & block, size_t * pos, size_t limit) override; - void addToGinFilter(UInt32 rowID, const char* data, size_t length, GinFilter& gin_filter); + void addToGinFilter(UInt32 rowID, const char* data, size_t length, GinFilter& gin_filter, UInt64 limit); GinIndexStorePtr store; Names index_columns; diff --git a/tests/queries/0_stateless/02346_full_text_search.reference b/tests/queries/0_stateless/02346_full_text_search.reference index f5d8a2950813..e035e93867b9 100644 --- a/tests/queries/0_stateless/02346_full_text_search.reference +++ b/tests/queries/0_stateless/02346_full_text_search.reference @@ -42,3 +42,11 @@ af inverted af inverted BC614E,05397FB1,6969696969898240,CF3304 1 +af inverted +1 +1 +af inverted +1 +1 +1 +1 diff --git a/tests/queries/0_stateless/02346_full_text_search.sql b/tests/queries/0_stateless/02346_full_text_search.sql index 116987ceaf94..a1439f031e21 100644 --- a/tests/queries/0_stateless/02346_full_text_search.sql +++ b/tests/queries/0_stateless/02346_full_text_search.sql @@ -204,3 +204,58 @@ SELECT read_rows==256 from system.query_log and endsWith(trimRight(query), 'SELECT s FROM simple5 WHERE hasToken(s, \'6969696969898240\');') and type='QueryFinish' and result_rows==1 limit 1; + +DROP TABLE IF EXISTS simple6; +-- create inverted index with density==1 +CREATE TABLE simple6(k UInt64,s String,INDEX af(s) TYPE inverted(0, 1.0) GRANULARITY 1) + Engine=MergeTree + ORDER BY (k) + SETTINGS max_digestion_size_per_segment = 1, index_granularity = 512 + AS + SELECT number, if(number%2, format('happy {}', hex(number)), format('birthday {}', hex(number))) + FROM numbers(1024); +-- check inverted index was created +SELECT name, type FROM system.data_skipping_indices where (table =='simple6') limit 1; +-- search inverted index, no row has 'happy birthday' +SELECT count()==0 FROM simple6 WHERE s=='happy birthday'; +SYSTEM FLUSH LOGS; +-- check the query only skip all granules (0 row total; each granule has 512 rows) +SELECT read_rows==0 from system.query_log + where query_kind ='Select' + and current_database = currentDatabase() + and endsWith(trimRight(query), 'SELECT count()==0 FROM simple6 WHERE s==\'happy birthday\';') + and type='QueryFinish' + and result_rows==1 limit 1; + +DROP TABLE IF EXISTS simple7; +-- create inverted index with density==0.1 +CREATE TABLE simple7(k UInt64,s String,INDEX af(s) TYPE inverted(0, 0.1) GRANULARITY 1) + Engine=MergeTree + ORDER BY (k) + SETTINGS max_digestion_size_per_segment = 1, index_granularity = 512 + AS + SELECT number, if(number==1023, 'happy new year', if(number%2, format('happy {}', hex(number)), format('birthday {}', hex(number)))) + FROM numbers(1024); +-- check inverted index was created +SELECT name, type FROM system.data_skipping_indices where (table =='simple7') limit 1; +-- search inverted index, no row has 'happy birthday' +SELECT count()==0 FROM simple7 WHERE s=='happy birthday'; +SYSTEM FLUSH LOGS; +-- check the query does not skip any of the 2 granules(1024 rows total; each granule has 512 rows) +SELECT read_rows==1024 from system.query_log + where query_kind ='Select' + and current_database = currentDatabase() + and endsWith(trimRight(query), 'SELECT count()==0 FROM simple7 WHERE s==\'happy birthday\';') + and type='QueryFinish' + and result_rows==1 limit 1; +-- search inverted index, no row has 'happy new year' +SELECT count()==1 FROM simple7 WHERE s=='happy new year'; +SYSTEM FLUSH LOGS; +-- check the query only read 1 granule because of density (1024 rows total; each granule has 512 rows) +SELECT read_rows==512 from system.query_log + where query_kind ='Select' + and current_database = currentDatabase() + and endsWith(trimRight(query), 'SELECT count()==1 FROM simple7 WHERE s==\'happy new year\';') + and type='QueryFinish' + and result_rows==1 limit 1; + From fd0c6cccc8a4782a5266ab7ca9087fd3546189a7 Mon Sep 17 00:00:00 2001 From: Larry Luo Date: Wed, 4 Jan 2023 20:08:28 -0800 Subject: [PATCH 36/42] Fixed style error --- src/Interpreters/GinFilter.cpp | 4 ++-- src/Storages/MergeTree/DataPartStorageOnDisk.cpp | 1 - src/Storages/MergeTree/GinIndexStore.cpp | 2 +- src/Storages/MergeTree/GinIndexStore.h | 2 +- src/Storages/MergeTree/IDataPartStorage.h | 2 +- src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp | 2 +- src/Storages/MergeTree/MergeTreeIndexGin.cpp | 2 +- 7 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/Interpreters/GinFilter.cpp b/src/Interpreters/GinFilter.cpp index c9df7aef9629..c6c1a8ab1d4d 100644 --- a/src/Interpreters/GinFilter.cpp +++ b/src/Interpreters/GinFilter.cpp @@ -24,7 +24,7 @@ GinFilterParameters::GinFilterParameters(size_t ngrams_, Float64 density_) { if (ngrams > 8) throw Exception("The size of gin filter cannot be greater than 8", ErrorCodes::BAD_ARGUMENTS); - if (density <= 0 || density > 1 ) + if (density <= 0 || density > 1) throw Exception("The density of gin filter must be between 0 and 1", ErrorCodes::BAD_ARGUMENTS); } @@ -110,7 +110,7 @@ bool GinFilter::matchInRange(const PostingsCachePtr& postings_cache, UInt32 segm auto min_in_container = container_it->second->minimum(); auto max_in_container = container_it->second->maximum(); - //check if the postings list has always match flag + //check if the postings list has always match flag if (container_it->second->cardinality() == 1 && UINT32_MAX == min_in_container) { continue; //always match diff --git a/src/Storages/MergeTree/DataPartStorageOnDisk.cpp b/src/Storages/MergeTree/DataPartStorageOnDisk.cpp index b60634544d9a..73e7ae547954 100644 --- a/src/Storages/MergeTree/DataPartStorageOnDisk.cpp +++ b/src/Storages/MergeTree/DataPartStorageOnDisk.cpp @@ -749,7 +749,6 @@ std::unique_ptr DataPartStorageOnDisk::writeFile( { if (transaction) return transaction->writeFile(fs::path(root_path) / part_dir / name, buf_size, mode, settings, /* autocommit = */ false); - return volume->getDisk()->writeFile(fs::path(root_path) / part_dir / name, buf_size, mode, settings); } diff --git a/src/Storages/MergeTree/GinIndexStore.cpp b/src/Storages/MergeTree/GinIndexStore.cpp index 97bb0d6f4e76..0a3cb5ece42b 100644 --- a/src/Storages/MergeTree/GinIndexStore.cpp +++ b/src/Storages/MergeTree/GinIndexStore.cpp @@ -44,7 +44,7 @@ void GinIndexPostingsBuilder::add(UInt32 row_id) } if (useRoaring()) { - if (rowid_bitmap.cardinality() == size_limit ) + if (rowid_bitmap.cardinality() == size_limit) { //reset the postings list with MATCH ALWAYS; lst_length = 1; //makes sure useRoaring() returns false; diff --git a/src/Storages/MergeTree/GinIndexStore.h b/src/Storages/MergeTree/GinIndexStore.h index f6df0856d5fa..3c72b634a735 100644 --- a/src/Storages/MergeTree/GinIndexStore.h +++ b/src/Storages/MergeTree/GinIndexStore.h @@ -161,7 +161,7 @@ class GinIndexStore void initFileStreams(); String name; - DataPartStoragePtr storage; + DataPartStoragePtr storage; MutableDataPartStoragePtr data_part_storage_builder; UInt32 cached_segment_num = 0; diff --git a/src/Storages/MergeTree/IDataPartStorage.h b/src/Storages/MergeTree/IDataPartStorage.h index 8197983ea932..70cc4d3fe702 100644 --- a/src/Storages/MergeTree/IDataPartStorage.h +++ b/src/Storages/MergeTree/IDataPartStorage.h @@ -217,7 +217,7 @@ class IDataPartStorage : public boost::noncopyable size_t buf_size, const WriteSettings & settings) = 0; virtual std::unique_ptr writeFile(const String & name, size_t buf_size, WriteMode mode, const WriteSettings & settings) = 0; - + /// A special const method to write transaction file. /// It's const, because file with transaction metadata /// can be modified after part creation. diff --git a/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp b/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp index fa7626603796..d8a061e18017 100644 --- a/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp +++ b/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp @@ -1708,7 +1708,7 @@ MarkRanges MergeTreeDataSelectExecutor::filterMarksUsingIndex( { if (index_mark != index_range.begin || !granule || last_index_mark != index_range.begin) granule = reader.read(); - const auto * gin_filter_condition = dynamic_cast(&*condition); + const auto * gin_filter_condition = dynamic_cast(&*condition); // Cast to Ann condition auto ann_condition = std::dynamic_pointer_cast(condition); if (ann_condition != nullptr) diff --git a/src/Storages/MergeTree/MergeTreeIndexGin.cpp b/src/Storages/MergeTree/MergeTreeIndexGin.cpp index 67ab8398640f..36648f856068 100644 --- a/src/Storages/MergeTree/MergeTreeIndexGin.cpp +++ b/src/Storages/MergeTree/MergeTreeIndexGin.cpp @@ -653,7 +653,7 @@ bool MergeTreeConditionGinFilter::tryPrepareSetGinFilter( { auto key = header.getPositionByName(lhs.getColumnName()); key_tuple_mapping.emplace_back(0, key); - data_types.push_back(header.getByPosition(key).type); + data_types.push_back(header.getByPosition(key).type); } } From f2bc5bb3354f3920c87910393d903ed5f3c83c2c Mon Sep 17 00:00:00 2001 From: Larry Luo Date: Thu, 5 Jan 2023 05:41:29 -0800 Subject: [PATCH 37/42] Added initializer --- src/Storages/MergeTree/GinIndexStore.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Storages/MergeTree/GinIndexStore.cpp b/src/Storages/MergeTree/GinIndexStore.cpp index 0a3cb5ece42b..05dbe31b21b2 100644 --- a/src/Storages/MergeTree/GinIndexStore.cpp +++ b/src/Storages/MergeTree/GinIndexStore.cpp @@ -24,7 +24,7 @@ namespace ErrorCodes extern const int LOGICAL_ERROR; }; -GinIndexPostingsBuilder::GinIndexPostingsBuilder(UInt64 limit) : size_limit(limit) +GinIndexPostingsBuilder::GinIndexPostingsBuilder(UInt64 limit) : lst{}, size_limit(limit) {} bool GinIndexPostingsBuilder::contains(UInt32 row_id) const From bc48b17817210e68bb3af6b402ed61b7d5cc41f7 Mon Sep 17 00:00:00 2001 From: HarryLeeIBM Date: Tue, 10 Jan 2023 08:26:27 -0800 Subject: [PATCH 38/42] Addressed more review comments. --- src/Common/FST.cpp | 95 ++++--- src/Common/FST.h | 265 +++++++++--------- src/Interpreters/GinFilter.cpp | 45 +-- src/Interpreters/GinFilter.h | 44 ++- src/Storages/MergeTree/GinIndexStore.cpp | 212 +++++++------- src/Storages/MergeTree/GinIndexStore.h | 158 ++++++----- .../MergeTree/MergeTreeIndexFullText.cpp | 1 + src/Storages/MergeTree/MergeTreeIndexGin.cpp | 36 +-- src/Storages/MergeTree/MergeTreeIndices.cpp | 6 +- src/Storages/MergeTree/MergeTreeIndices.h | 1 - 10 files changed, 472 insertions(+), 391 deletions(-) diff --git a/src/Common/FST.cpp b/src/Common/FST.cpp index 16d20c76b729..4f6c587304f8 100644 --- a/src/Common/FST.cpp +++ b/src/Common/FST.cpp @@ -5,7 +5,7 @@ #include #include #include -#include +#include namespace DB { @@ -39,10 +39,11 @@ UInt64 Arc::serialize(WriteBuffer& write_buffer) const bool operator==(const Arc & arc1, const Arc & arc2) { + assert(arc1.target != nullptr && arc2.target != nullptr); return (arc1.output == arc2.output && arc1.target->id == arc2.target->id); } -void ArcsAsBitmap::addArc(char label) +void LabelsAsBitmap::addLabel(char label) { UInt8 index = label; UInt256 bit_label = 1; @@ -51,9 +52,9 @@ void ArcsAsBitmap::addArc(char label) data |= bit_label; } -int ArcsAsBitmap::getIndex(char label) const +UInt64 LabelsAsBitmap::getIndex(char label) const { - int bit_count = 0; + UInt64 bit_count = 0; UInt8 index = label; int which_int64 = 0; @@ -74,7 +75,20 @@ int ArcsAsBitmap::getIndex(char label) const return bit_count; } -bool ArcsAsBitmap::hasArc(char label) const +UInt64 LabelsAsBitmap::serialize(WriteBuffer& write_buffer) +{ + writeVarUInt(data.items[0], write_buffer); + writeVarUInt(data.items[1], write_buffer); + writeVarUInt(data.items[2], write_buffer); + writeVarUInt(data.items[3], write_buffer); + + return getLengthOfVarUInt(data.items[0]) + + getLengthOfVarUInt(data.items[1]) + + getLengthOfVarUInt(data.items[2]) + + getLengthOfVarUInt(data.items[3]); +} + +bool LabelsAsBitmap::hasLabel(char label) const { UInt8 index = label; UInt256 bit_label = 1; @@ -97,6 +111,15 @@ void State::addArc(char label, Output output, StatePtr target) arcs[label] = Arc(output, target); } +void State::clear() +{ + id = 0; + state_index = 0; + flag = 0; + + arcs.clear(); +} + UInt64 State::hash() const { std::vector values; @@ -114,7 +137,7 @@ UInt64 State::hash() const return CityHash_v1_0_2::CityHash64(values.data(), values.size()); } -bool operator== (const State& state1, const State& state2) +bool operator== (const State & state1, const State & state2) { if (state1.arcs.size() != state2.arcs.size()) return false; @@ -139,11 +162,11 @@ UInt64 State::serialize(WriteBuffer& write_buffer) write_buffer.write(flag); written_bytes += 1; - if (flag_values.encoding_method == EncodingMethod::ENCODING_METHOD_SEQUENTIAL) + if (getEncodingMethod() == EncodingMethod::Sequential) { /// Serialize all labels std::vector labels; - labels.reserve(MAX_ARCS_IN_SEQUENTIAL_METHOD); + labels.reserve(arcs.size()); for (auto& [label, state] : arcs) { @@ -168,23 +191,13 @@ UInt64 State::serialize(WriteBuffer& write_buffer) else { /// Serialize bitmap - ArcsAsBitmap bmp; + LabelsAsBitmap bmp; for (auto & [label, state] : arcs) { - bmp.addArc(label); + bmp.addLabel(label); } + written_bytes += bmp.serialize(write_buffer); - UInt64 bmp_encoded_size = getLengthOfVarUInt(bmp.data.items[0]) - + getLengthOfVarUInt(bmp.data.items[1]) - + getLengthOfVarUInt(bmp.data.items[2]) - + getLengthOfVarUInt(bmp.data.items[3]); - - writeVarUInt(bmp.data.items[0], write_buffer); - writeVarUInt(bmp.data.items[1], write_buffer); - writeVarUInt(bmp.data.items[2], write_buffer); - writeVarUInt(bmp.data.items[3], write_buffer); - - written_bytes += bmp_encoded_size; /// Serialize all arcs for (auto & [label, state] : arcs) { @@ -205,7 +218,7 @@ FSTBuilder::FSTBuilder(WriteBuffer& write_buffer_) : write_buffer(write_buffer_) } } -StatePtr FSTBuilder::getMinimized(const State& state, bool& found) +StatePtr FSTBuilder::findMinimized(const State & state, bool & found) { found = false; auto hash = state.hash(); @@ -221,7 +234,7 @@ StatePtr FSTBuilder::getMinimized(const State& state, bool& found) return p; } -size_t FSTBuilder::getCommonPrefix(const std::string& word1, const std::string& word2) +size_t FSTBuilder::getCommonPrefixLength(const String & word1, const String & word2) { size_t i = 0; while (i < word1.size() && i < word2.size() && word1[i] == word2[i]) @@ -233,8 +246,8 @@ void FSTBuilder::minimizePreviousWordSuffix(Int64 down_to) { for (Int64 i = static_cast(previous_word.size()); i >= down_to; --i) { - bool found{ false }; - auto minimized_state = getMinimized(*temp_states[i], found); + bool found = false; + auto minimized_state = findMinimized(*temp_states[i], found); if (i != 0) { @@ -256,13 +269,12 @@ void FSTBuilder::minimizePreviousWordSuffix(Int64 down_to) minimized_state->state_index = previous_state_index; previous_written_bytes = minimized_state->serialize(write_buffer); - state_count++; previous_state_index += previous_written_bytes; } } } -void FSTBuilder::add(const std::string& current_word, Output current_output) +void FSTBuilder::add(const std::string & current_word, Output current_output) { /// We assume word size is no greater than MAX_TERM_LENGTH(256). /// FSTs without word size limitation would be inefficient and easy to cause memory bloat @@ -275,12 +287,12 @@ void FSTBuilder::add(const std::string& current_word, Output current_output) if (current_word_len > MAX_TERM_LENGTH) throw DB::Exception(DB::ErrorCodes::BAD_ARGUMENTS, "Too long term ({}) passed to FST builder.", current_word_len); - size_t prefix_length_plus1 = getCommonPrefix(current_word, previous_word) + 1; + size_t prefix_length_plus1 = getCommonPrefixLength(current_word, previous_word) + 1; minimizePreviousWordSuffix(prefix_length_plus1); /// Initialize the tail state - for (size_t i = prefix_length_plus1; i <= current_word.size(); ++i) + for (size_t i = prefix_length_plus1; i <= current_word.size(); ++i) { temp_states[i]->clear(); temp_states[i - 1]->addArc(current_word[i - 1], 0, temp_states[i]); @@ -289,9 +301,9 @@ void FSTBuilder::add(const std::string& current_word, Output current_output) /// We assume the current word is different with previous word temp_states[current_word_len]->setFinal(true); /// Adjust outputs on the arcs - for (size_t j = 1; j <= prefix_length_plus1 - 1; ++j) + for (size_t i = 1; i <= prefix_length_plus1 - 1; ++i) { - Arc * arc_ptr = temp_states[j - 1]->getArc(current_word[j - 1]); + Arc * arc_ptr = temp_states[i - 1]->getArc(current_word[i - 1]); assert(arc_ptr != nullptr); Output common_prefix = std::min(arc_ptr->output, current_output); @@ -301,7 +313,7 @@ void FSTBuilder::add(const std::string& current_word, Output current_output) /// For each arc, adjust its output if (word_suffix != 0) { - for (auto & [label, arc] : temp_states[j]->arcs) + for (auto & [label, arc] : temp_states[i]->arcs) { arc.output += word_suffix; } @@ -341,15 +353,18 @@ void FiniteStateTransducer::clear() data.clear(); } -std::pair FiniteStateTransducer::getOutput(const String& term) +std::pair FiniteStateTransducer::getOutput(const String & term) { - std::pair result_output{ 0, false }; + std::pair result{ 0, false }; + /// Read index of initial state ReadBufferFromMemory read_buffer(data.data(), data.size()); read_buffer.seek(data.size()-1, SEEK_SET); UInt8 length{ 0 }; read_buffer.readStrict(reinterpret_cast(length)); + + /// FST contains no terms if (length == 0) return { 0, false }; @@ -368,12 +383,12 @@ std::pair FiniteStateTransducer::getOutput(const String& term) temp_state.readFlag(read_buffer); if (i == term.size()) { - result_output.second = temp_state.isFinal(); + result.second = temp_state.isFinal(); break; } UInt8 label = term[i]; - if (temp_state.getEncodingMethod() == State::EncodingMethod::ENCODING_METHOD_SEQUENTIAL) + if (temp_state.getEncodingMethod() == State::EncodingMethod::Sequential) { /// Read number of labels UInt8 label_num{ 0 }; @@ -412,14 +427,14 @@ std::pair FiniteStateTransducer::getOutput(const String& term) } else { - ArcsAsBitmap bmp; + LabelsAsBitmap bmp; readVarUInt(bmp.data.items[0], read_buffer); readVarUInt(bmp.data.items[1], read_buffer); readVarUInt(bmp.data.items[2], read_buffer); readVarUInt(bmp.data.items[3], read_buffer); - if (!bmp.hasArc(label)) + if (!bmp.hasLabel(label)) return { 0, false }; /// Read the arc for the label @@ -437,9 +452,9 @@ std::pair FiniteStateTransducer::getOutput(const String& term) } } /// Accumulate the output value - result_output.first += arc_output; + result.first += arc_output; } - return result_output; + return result; } } } diff --git a/src/Common/FST.h b/src/Common/FST.h index 0f17e54d3536..f6e0629ac04a 100644 --- a/src/Common/FST.h +++ b/src/Common/FST.h @@ -14,158 +14,167 @@ namespace DB { /// Finite State Transducer is an efficient way to represent term dictionary. -/// It can be viewed as a map of . +/// It can be viewed as a map of where output is an integer. /// Detailed explanation can be found in the following paper /// [Direct Construction of Minimal Acyclic Subsequential Transduers] by Stoyan Mihov and Denis Maurel, University of Tours, France namespace FST { - using Output = UInt64; +using Output = UInt64; - class State; - using StatePtr = std::shared_ptr; +class State; +using StatePtr = std::shared_ptr; - /// Arc represents a transition from one state to another - /// It includes the target state to which the arc points and its output. - struct Arc - { - Arc() = default; - explicit Arc(Output output_, const StatePtr & target_) : output{output_}, target{target_} { } - Output output = 0; - StatePtr target; +/// Arc represents a transition from one state to another +/// It includes the target state to which the arc points and the arc's output. +struct Arc +{ + Arc() = default; - UInt64 serialize(WriteBuffer & write_buffer) const; - }; + explicit Arc(Output output_, const StatePtr & target_) : output{output_}, target{target_} { } - bool operator==(const Arc & arc1, const Arc & arc2); + /// 0 means the arc has no output + Output output = 0; - /// ArcsAsBitmap implements a 256-bit bitmap for all arcs of a state. Each bit represents - /// an arc's presence and the index value of the bit represents the corresponding label - class ArcsAsBitmap - { - public: - void addArc(char label); - bool hasArc(char label) const; - int getIndex(char label) const; - - private: - friend class State; - friend class FiniteStateTransducer; - /// data holds a 256-bit bitmap for all arcs of a state. Ita 256 bits correspond to 256 - /// possible label values. - UInt256 data{ 0 }; - }; + StatePtr target; + + UInt64 serialize(WriteBuffer & write_buffer) const; +}; - /// State implements the State in Finite State Transducer - /// Each state contains all its arcs and a flag indicating if it is final state - class State +bool operator==(const Arc & arc1, const Arc & arc2); + +/// LabelsAsBitmap implements a 256-bit bitmap for all labels of a state. Each bit represents +/// a label's presence and the index value of the bit represents the corresponding label +class LabelsAsBitmap +{ +public: + void addLabel(char label); + bool hasLabel(char label) const; + + /// computes the rank + UInt64 getIndex(char label) const; + + UInt64 serialize(WriteBuffer& write_buffer); +private: + friend class State; + friend class FiniteStateTransducer; + /// data holds a 256-bit bitmap for all labels of a state. Its 256 bits correspond to 256 + /// possible label values. + UInt256 data{ 0 }; +}; + +/// State implements the State in Finite State Transducer +/// Each state contains all its arcs and a flag indicating if it is final state +class State +{ +public: + static constexpr size_t MAX_ARCS_IN_SEQUENTIAL_METHOD = 32; + enum class EncodingMethod { - public: - static constexpr size_t MAX_ARCS_IN_SEQUENTIAL_METHOD = 32; - enum class EncodingMethod - { - ENCODING_METHOD_SEQUENTIAL = 0, - ENCODING_METHOD_BITMAP, - }; - State() = default; - State(const State & state) = default; - - UInt64 hash() const; - - Arc * getArc(char label); - void addArc(char label, Output output, StatePtr target); - - void clear() - { - id = 0; - state_index = 0; - flag = 0; - - arcs.clear(); - } - - UInt64 serialize(WriteBuffer & write_buffer); - - inline bool isFinal() const - { - return flag_values.is_final == 1; - } - inline void setFinal(bool value) - { - flag_values.is_final = value; - } - inline EncodingMethod getEncodingMethod() const - { - return flag_values.encoding_method; - } - inline void readFlag(ReadBuffer & read_buffer) - { - read_buffer.readStrict(reinterpret_cast(flag)); - } - - UInt64 id = 0; - UInt64 state_index = 0; - std::unordered_map arcs; - private: - struct FlagValues - { - unsigned int is_final : 1; - EncodingMethod encoding_method : 3; - }; - - union - { - FlagValues flag_values; - uint8_t flag = 0; - }; + /// Serialize arcs sequentially + Sequential = 0, + + /// Serialize arcs by using bitmap + /// Note this is NOT enabled for now since it is experimental + Bitmap, }; + State() = default; - bool operator==(const State & state1, const State & state2); + State(const State & state) = default; - inline constexpr size_t MAX_TERM_LENGTH = 256; + UInt64 hash() const; - /// FSTBuilder is used to build Finite State Transducer by adding words incrementally. - /// Note that all the words have to be added in sorted order in order to achieve minimized result. - /// In the end, the caller should call build() to serialize minimized FST to WriteBuffer - class FSTBuilder - { - public: - FSTBuilder(WriteBuffer & write_buffer_); - StatePtr getMinimized(const State & s, bool & found); + Arc * getArc(char label); - void add(const std::string & word, Output output); - UInt64 build(); + void addArc(char label, Output output, StatePtr target); - UInt64 state_count = 0; + void clear(); - private: - void minimizePreviousWordSuffix(Int64 down_to); - static size_t getCommonPrefix(const std::string & word1, const std::string & word2); + UInt64 serialize(WriteBuffer & write_buffer); - std::array temp_states; - std::string previous_word; - StatePtr initial_state; - std::unordered_map minimized_states; + bool isFinal() const + { + return flag_values.is_final == 1; + } + void setFinal(bool value) + { + flag_values.is_final = value; + } + EncodingMethod getEncodingMethod() const + { + return flag_values.encoding_method; + } + void readFlag(ReadBuffer & read_buffer) + { + read_buffer.readStrict(reinterpret_cast(flag)); + } + + /// Transient ID of the state which is used for building FST. It won't be serialized + UInt64 id = 0; - UInt64 next_id = 1; + /// State index which indicates location of state in FST + UInt64 state_index = 0; - WriteBuffer & write_buffer; - UInt64 previous_written_bytes = 0; - UInt64 previous_state_index = 0; + /// Arcs which are started from state, the 'char' is the label on the arc + std::unordered_map arcs; +private: + struct FlagValues + { + unsigned int is_final : 1; + EncodingMethod encoding_method : 3; }; - //FiniteStateTransducer is constructed by using minimized FST blob(which is loaded from index storage) - // It is used to retrieve output by given term - class FiniteStateTransducer + union { - public: - FiniteStateTransducer() = default; - FiniteStateTransducer(std::vector data_); - std::pair getOutput(const String & term); - void clear(); - std::vector & getData() { return data; } - - private: - std::vector data; + FlagValues flag_values; + uint8_t flag = 0; }; +}; + +bool operator==(const State & state1, const State & state2); + +inline constexpr size_t MAX_TERM_LENGTH = 256; + +/// FSTBuilder is used to build Finite State Transducer by adding words incrementally. +/// Note that all the words have to be added in sorted order in order to achieve minimized result. +/// In the end, the caller should call build() to serialize minimized FST to WriteBuffer +class FSTBuilder +{ +public: + explicit FSTBuilder(WriteBuffer & write_buffer_); + + void add(const std::string & word, Output output); + UInt64 build(); +private: + StatePtr findMinimized(const State & s, bool & found); + void minimizePreviousWordSuffix(Int64 down_to); + static size_t getCommonPrefixLength(const String & word1, const String & word2); + + std::array temp_states; + String previous_word; + StatePtr initial_state; + std::unordered_map minimized_states; + + /// Next available ID of state + UInt64 next_id = 1; + + WriteBuffer & write_buffer; + UInt64 previous_written_bytes = 0; + UInt64 previous_state_index = 0; +}; + +//FiniteStateTransducer is constructed by using minimized FST blob(which is loaded from index storage) +// It is used to retrieve output by given term +class FiniteStateTransducer +{ +public: + FiniteStateTransducer() = default; + FiniteStateTransducer(std::vector data_); + std::pair getOutput(const String & term); + void clear(); + std::vector & getData() { return data; } + +private: + std::vector data; +}; } } diff --git a/src/Interpreters/GinFilter.cpp b/src/Interpreters/GinFilter.cpp index c6c1a8ab1d4d..a2fa47a46310 100644 --- a/src/Interpreters/GinFilter.cpp +++ b/src/Interpreters/GinFilter.cpp @@ -38,8 +38,8 @@ void GinFilter::add(const char* data, size_t len, UInt32 rowID, GinIndexStorePtr if (len > FST::MAX_TERM_LENGTH) return; - std::string token(data, len); - auto it = store->getPostings().find(token); + String term(data, len); + auto it = store->getPostings().find(term); if (it != store->getPostings().end()) { @@ -52,7 +52,7 @@ void GinFilter::add(const char* data, size_t len, UInt32 rowID, GinIndexStorePtr GinIndexPostingsBuilderPtr builder = std::make_shared(threshold); builder->add(rowID); - store->getPostings()[token] = builder; + store->setPostingsBuilder(term, builder); } } @@ -60,31 +60,37 @@ void GinFilter::add(const char* data, size_t len, UInt32 rowID, GinIndexStorePtr /// digested sequentially and segments are created sequentially too. void GinFilter::addRowRangeToGinFilter(UInt32 segmentID, UInt32 rowIDStart, UInt32 rowIDEnd) { - if (!rowid_range_container.empty()) + /// check segment ids are monotonic increasing + assert(rowid_ranges.empty() || rowid_ranges.back().segment_id <= segmentID); + + if (!rowid_ranges.empty()) { /// Try to merge the rowID range with the last one in the container - if (rowid_range_container.back().segment_id == segmentID && - rowid_range_container.back().range_end+1 == rowIDStart) + GinSegmentWithRowIDRange & last_rowid_range = rowid_ranges.back(); + + if (last_rowid_range.segment_id == segmentID && + last_rowid_range.range_end+1 == rowIDStart) { - rowid_range_container.back().range_end = rowIDEnd; + last_rowid_range.range_end = rowIDEnd; return; } } - rowid_range_container.push_back({segmentID, rowIDStart, rowIDEnd}); + rowid_ranges.push_back({segmentID, rowIDStart, rowIDEnd}); } void GinFilter::clear() { terms.clear(); - rowid_range_container.clear(); + rowid_ranges.clear(); + query_string.clear(); } -bool GinFilter::hasEmptyPostingsList(const PostingsCachePtr& postings_cache) +bool GinFilter::hasEmptyPostingsList(const PostingsCache& postings_cache) { - if (postings_cache->empty()) + if (postings_cache.empty()) return true; - for (const auto& term_postings : *postings_cache) + for (const auto& term_postings : postings_cache) { const SegmentedPostingsListContainer& container = term_postings.second; if (container.empty()) @@ -93,16 +99,17 @@ bool GinFilter::hasEmptyPostingsList(const PostingsCachePtr& postings_cache) return false; } -bool GinFilter::matchInRange(const PostingsCachePtr& postings_cache, UInt32 segment_id, UInt32 range_start, UInt32 range_end) +bool GinFilter::matchInRange(const PostingsCache& postings_cache, UInt32 segment_id, UInt32 range_start, UInt32 range_end) { /// Check for each terms GinIndexPostingsList intersection_result; bool intersection_result_init = false; - for (const auto& term_postings : *postings_cache) + for (const auto& term_postings : postings_cache) { + /// Check if it is in the same segement by searching for segment_id const SegmentedPostingsListContainer& container = term_postings.second; - auto container_it{ container.find(segment_id) }; + auto container_it = container.find(segment_id); if (container_it == container.cend()) { return false; @@ -136,7 +143,7 @@ bool GinFilter::matchInRange(const PostingsCachePtr& postings_cache, UInt32 segm return true; } -bool GinFilter::match(const PostingsCachePtr& postings_cache) const +bool GinFilter::match(const PostingsCache& postings_cache) const { if (hasEmptyPostingsList(postings_cache)) { @@ -144,7 +151,7 @@ bool GinFilter::match(const PostingsCachePtr& postings_cache) const } /// Check for each row ID ranges - for (const auto &rowid_range: rowid_range_container) + for (const auto &rowid_range: rowid_ranges) { if (matchInRange(postings_cache, rowid_range.segment_id, rowid_range.range_start, rowid_range.range_end)) { @@ -163,11 +170,11 @@ bool GinFilter::contains(const GinFilter & filter, PostingsCacheForStore &cache_ if (postings_cache == nullptr) { GinIndexStoreDeserializer reader(cache_store.store); - postings_cache = reader.loadPostingsIntoCache(filter.getTerms()); + postings_cache = reader.createPostingsCacheFromTerms(filter.getTerms()); cache_store.cache[filter.getQueryString()] = postings_cache; } - return match(postings_cache); + return match(*postings_cache); } String GinFilter::getName() diff --git a/src/Interpreters/GinFilter.h b/src/Interpreters/GinFilter.h index 0f30a932e61c..d531511472ac 100644 --- a/src/Interpreters/GinFilter.h +++ b/src/Interpreters/GinFilter.h @@ -13,7 +13,7 @@ struct GinFilterParameters Float64 density; }; -struct RowIDRange +struct GinSegmentWithRowIDRange { /// Segment ID of the row ID range UInt32 segment_id; @@ -21,38 +21,50 @@ struct RowIDRange /// First row ID in the range UInt32 range_start; - /// Last row ID in the range + /// Last row ID in the range (inclusive) UInt32 range_end; }; +/// GinFilter provides underlying funtionalities for building inverted index and also +/// it does filtering the unmatched rows according to its query string. +/// It also builds and uses skipping index which stores (segmentID, RowIDStart, RowIDEnd) triples. class GinFilter { public: - using RowIDRangeContainer = std::vector; + using GinSegmentWithRowIDRanges = std::vector; explicit GinFilter(const GinFilterParameters& params_); + /// Add term(which length is 'len' and located at 'data') and its row ID to + /// the postings list builder for building inverted index for the given store. void add(const char* data, size_t len, UInt32 rowID, GinIndexStorePtr& store, UInt64 limit); + /// Accumulate (segmentID, RowIDStart, RowIDEnd) for building skipping index void addRowRangeToGinFilter(UInt32 segmentID, UInt32 rowIDStart, UInt32 rowIDEnd); + /// Clear the content void clear(); - size_t size() const { return rowid_range_container.size(); } - + /// Check if the filter(built from query string) contains any rows in given filter 'af' by using + /// given postings list cache bool contains(const GinFilter& af, PostingsCacheForStore &store) const; - const RowIDRangeContainer& getFilter() const { return rowid_range_container; } + /// Const getter for the row ID ranges + const GinSegmentWithRowIDRanges& getFilter() const { return rowid_ranges; } - RowIDRangeContainer& getFilter() { return rowid_range_container; } + /// Mutable getter for the row ID ranges + GinSegmentWithRowIDRanges& getFilter() { return rowid_ranges; } + /// Set the query string of the filter void setQueryString(const char* data, size_t len) { query_string = String(data, len); } + /// Const getter of the query string const String &getQueryString() const { return query_string; } + /// Add term which are tokens generated from the query string void addTerm(const char* data, size_t len) { if (len > FST::MAX_TERM_LENGTH) @@ -60,25 +72,35 @@ class GinFilter terms.push_back(String(data, len)); } + /// Const getter of terms(generated from the query string) const std::vector& getTerms() const { return terms;} - bool match(const PostingsCachePtr& postings_cache) const; + /// Check if the given postings list cache has matched rows by using the filter + bool match(const PostingsCache& postings_cache) const; + /// Get filter name ("inverted") static String getName(); + /// Constant of filter name static constexpr auto FilterName = "inverted"; private: + /// Filter parameters const GinFilterParameters& params; + /// Query string of the filter String query_string; + /// Tokenized terms from query string std::vector terms; - RowIDRangeContainer rowid_range_container; + /// Row ID ranges which are (segmentID, RowIDStart, RowIDEnd) + GinSegmentWithRowIDRanges rowid_ranges; - static bool hasEmptyPostingsList(const PostingsCachePtr& postings_cache); + /// Helper method for checking if postings list cache is empty + static bool hasEmptyPostingsList(const PostingsCache& postings_cache); - static bool matchInRange(const PostingsCachePtr& postings_cache, UInt32 segment_id, UInt32 range_start, UInt32 range_end); + /// Helper method to check if the postings list cache has intersection with given row ID range + static bool matchInRange(const PostingsCache& postings_cache, UInt32 segment_id, UInt32 range_start, UInt32 range_end); }; using GinFilterPtr = std::shared_ptr; diff --git a/src/Storages/MergeTree/GinIndexStore.cpp b/src/Storages/MergeTree/GinIndexStore.cpp index 05dbe31b21b2..bbe65e23a04e 100644 --- a/src/Storages/MergeTree/GinIndexStore.cpp +++ b/src/Storages/MergeTree/GinIndexStore.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -12,19 +13,34 @@ #include #include #include -#include #include -#include // for high_resolution_clock namespace DB { +using TokenPostingsBuilderPair = std::pair; +using TokenPostingsBuilderPairs = std::vector; + namespace ErrorCodes { extern const int LOGICAL_ERROR; + extern const int UNKNOWN_FORMAT_VERSION; }; -GinIndexPostingsBuilder::GinIndexPostingsBuilder(UInt64 limit) : lst{}, size_limit(limit) +GinIndexStore::GinIndexStore(const String & name_, DataPartStoragePtr storage_) + : name(name_) + , storage(storage_) +{ +} +GinIndexStore::GinIndexStore(const String& name_, DataPartStoragePtr storage_, MutableDataPartStoragePtr data_part_storage_builder_, UInt64 max_digestion_size_) + : name(name_) + , storage(storage_) + , data_part_storage_builder(data_part_storage_builder_) + , max_digestion_size(max_digestion_size_) +{ +} + +GinIndexPostingsBuilder::GinIndexPostingsBuilder(UInt64 limit) : rowid_lst{}, size_limit(limit) {} bool GinIndexPostingsBuilder::contains(UInt32 row_id) const @@ -32,8 +48,8 @@ bool GinIndexPostingsBuilder::contains(UInt32 row_id) const if (useRoaring()) return rowid_bitmap.contains(row_id); - const auto *const it(std::find(lst.begin(), lst.begin()+lst_length, row_id)); - return it != lst.begin()+lst_length; + const auto * const it = std::find(rowid_lst.begin(), rowid_lst.begin()+rowid_lst_length, row_id); + return it != rowid_lst.begin() + rowid_lst_length; } void GinIndexPostingsBuilder::add(UInt32 row_id) @@ -47,8 +63,8 @@ void GinIndexPostingsBuilder::add(UInt32 row_id) if (rowid_bitmap.cardinality() == size_limit) { //reset the postings list with MATCH ALWAYS; - lst_length = 1; //makes sure useRoaring() returns false; - lst[0] = UINT32_MAX; //set CONTAINS ALL flag; + rowid_lst_length = 1; //makes sure useRoaring() returns false; + rowid_lst[0] = UINT32_MAX; //set CONTAINS ALL flag; } else { @@ -56,40 +72,41 @@ void GinIndexPostingsBuilder::add(UInt32 row_id) } return; } - assert(lst_length < MIN_SIZE_FOR_ROARING_ENCODING); - lst[lst_length++] = row_id; + assert(rowid_lst_length < MIN_SIZE_FOR_ROARING_ENCODING); + rowid_lst[rowid_lst_length] = row_id; + rowid_lst_length++; - if (lst_length == MIN_SIZE_FOR_ROARING_ENCODING) + if (rowid_lst_length == MIN_SIZE_FOR_ROARING_ENCODING) { - for (size_t i = 0; i < lst_length; i++) - rowid_bitmap.add(lst[i]); + for (size_t i = 0; i < rowid_lst_length; i++) + rowid_bitmap.add(rowid_lst[i]); - lst_length = UsesBitMap; + rowid_lst_length = UsesBitMap; } } bool GinIndexPostingsBuilder::useRoaring() const { - return lst_length == UsesBitMap; + return rowid_lst_length == UsesBitMap; } bool GinIndexPostingsBuilder::containsAllRows() const { - return lst[0] == UINT32_MAX; + return rowid_lst[0] == UINT32_MAX; } UInt64 GinIndexPostingsBuilder::serialize(WriteBuffer &buffer) const { UInt64 written_bytes = 0; - buffer.write(lst_length); + buffer.write(rowid_lst_length); written_bytes += 1; if (!useRoaring()) { - for (size_t i = 0; i < lst_length; ++i) + for (size_t i = 0; i < rowid_lst_length; ++i) { - writeVarUInt(lst[i], buffer); - written_bytes += getLengthOfVarUInt(lst[i]); + writeVarUInt(rowid_lst[i], buffer); + written_bytes += getLengthOfVarUInt(rowid_lst[i]); } } else @@ -109,7 +126,7 @@ UInt64 GinIndexPostingsBuilder::serialize(WriteBuffer &buffer) const GinIndexPostingsListPtr GinIndexPostingsBuilder::deserialize(ReadBuffer &buffer) { - UInt8 postings_list_size{0}; + UInt8 postings_list_size = 0; buffer.readStrict(reinterpret_cast(postings_list_size)); if (postings_list_size != UsesBitMap) @@ -144,17 +161,18 @@ bool GinIndexStore::exists() const return storage->exists(id_file_name); } -UInt32 GinIndexStore::getNextIDRange(const String& file_name, size_t n) +UInt32 GinIndexStore::getNextSegmentIDRange(const String& file_name, size_t n) { - std::lock_guard guard{gin_index_store_mutex}; + std::lock_guard guard(gin_index_store_mutex); + /// When the method is called for the first time, the file doesn't exist yet, need to create it + /// and write segment ID 1. if (!storage->exists(file_name)) { + /// Create file and write initial segment id = 1 std::unique_ptr ostr = this->data_part_storage_builder->writeFile(file_name, DBMS_DEFAULT_BUFFER_SIZE, {}); - const auto& int_type = DB::DataTypePtr(std::make_shared()); - auto size_serialization = int_type->getDefaultSerialization(); - size_serialization->serializeBinary(1, *ostr, {}); + writeVarUInt(1, *ostr); ostr->sync(); } @@ -163,39 +181,32 @@ UInt32 GinIndexStore::getNextIDRange(const String& file_name, size_t n) { std::unique_ptr istr = this->storage->readFile(file_name, {}, std::nullopt, std::nullopt); - Field field_rows; - const auto& size_type = DB::DataTypePtr(std::make_shared()); - auto size_serialization = size_type->getDefaultSerialization(); - - size_type->getDefaultSerialization()->deserializeBinary(field_rows, *istr, {}); - result = static_cast(field_rows.get()); + readVarUInt(result, *istr); } //save result+n { std::unique_ptr ostr = this->data_part_storage_builder->writeFile(file_name, DBMS_DEFAULT_BUFFER_SIZE, {}); - const auto& int_type = DB::DataTypePtr(std::make_shared()); - auto size_serialization = int_type->getDefaultSerialization(); - size_serialization->serializeBinary(result + n, *ostr, {}); + writeVarUInt(result + n, *ostr); ostr->sync(); } return result; } -UInt32 GinIndexStore::getNextRowIDRange(size_t n) +UInt32 GinIndexStore::getNextRowIDRange(size_t numIDs) { UInt32 result =current_segment.next_row_id; - current_segment.next_row_id += n; + current_segment.next_row_id += numIDs; return result; } UInt32 GinIndexStore::getNextSegmentID() { String sid_file_name = getName() + GIN_SEGMENT_ID_FILE_TYPE; - return getNextIDRange(sid_file_name, 1); + return getNextSegmentIDRange(sid_file_name, 1); } -UInt32 GinIndexStore::getSegmentNum() +UInt32 GinIndexStore::getNumOfSegments() { if (cached_segment_num) return cached_segment_num; @@ -203,16 +214,11 @@ UInt32 GinIndexStore::getSegmentNum() String sid_file_name = getName() + GIN_SEGMENT_ID_FILE_TYPE; if (!storage->exists(sid_file_name)) return 0; - Int32 result = 0; + + UInt32 result = 0; { std::unique_ptr istr = this->storage->readFile(sid_file_name, {}, std::nullopt, std::nullopt); - - Field field_rows; - const auto& size_type = DB::DataTypePtr(std::make_shared()); - auto size_serialization = size_type->getDefaultSerialization(); - - size_type->getDefaultSerialization()->deserializeBinary(field_rows, *istr, {}); - result = static_cast(field_rows.get()); + readVarUInt(result, *istr); } cached_segment_num = result - 1; @@ -236,11 +242,11 @@ void GinIndexStore::finalize() void GinIndexStore::initFileStreams() { String segment_file_name = getName() + GIN_SEGMENT_FILE_TYPE; - String item_dict_file_name = getName() + GIN_DICTIONARY_FILE_TYPE; + String term_dict_file_name = getName() + GIN_DICTIONARY_FILE_TYPE; String postings_file_name = getName() + GIN_POSTINGS_FILE_TYPE; segment_file_stream = data_part_storage_builder->writeFile(segment_file_name, DBMS_DEFAULT_BUFFER_SIZE, WriteMode::Append, {}); - term_dict_file_stream = data_part_storage_builder->writeFile(item_dict_file_name, DBMS_DEFAULT_BUFFER_SIZE, WriteMode::Append, {}); + term_dict_file_stream = data_part_storage_builder->writeFile(term_dict_file_name, DBMS_DEFAULT_BUFFER_SIZE, WriteMode::Append, {}); postings_file_stream = data_part_storage_builder->writeFile(postings_file_name, DBMS_DEFAULT_BUFFER_SIZE, WriteMode::Append, {}); } @@ -251,44 +257,49 @@ void GinIndexStore::writeSegment() initFileStreams(); } - ///write segment + /// Write version + writeChar(static_cast(CURRENT_GIN_FILE_FORMAT_VERSION), *segment_file_stream); + + /// Write segment segment_file_stream->write(reinterpret_cast(¤t_segment), sizeof(GinIndexSegment)); - std::vector> token_postings_list_pairs; + TokenPostingsBuilderPairs token_postings_list_pairs; token_postings_list_pairs.reserve(current_postings.size()); for (const auto& [token, postings_list] : current_postings) { token_postings_list_pairs.push_back({std::string_view(token), postings_list}); } + + /// Sort token-postings list pairs since all tokens have to be added in FST in sorted order std::sort(token_postings_list_pairs.begin(), token_postings_list_pairs.end(), - [](const std::pair& a, const std::pair& b) + [](const TokenPostingsBuilderPair& a, const TokenPostingsBuilderPair & b) { return a.first < b.first; }); ///write postings - std::vector encoding_lengths(current_postings.size(), 0); - size_t current_index = 0; + std::vector posting_list_byte_sizes(current_postings.size(), 0); - for (const auto& [token, postings_list] : token_postings_list_pairs) + for (size_t current_index = 0; const auto& [token, postings_list] : token_postings_list_pairs) { - auto encoding_length = postings_list->serialize(*postings_file_stream); + auto posting_list_byte_size = postings_list->serialize(*postings_file_stream); - encoding_lengths[current_index++] = encoding_length; - current_segment.postings_start_offset += encoding_length; + posting_list_byte_sizes[current_index] = posting_list_byte_size; + current_index++; + current_segment.postings_start_offset += posting_list_byte_size; } ///write item dictionary std::vector buffer; WriteBufferFromVector> write_buf(buffer); FST::FSTBuilder builder(write_buf); - UInt64 offset{0}; - current_index = 0; - for (const auto& [token, postings_list] : token_postings_list_pairs) + UInt64 offset = 0; + for (size_t current_index = 0; const auto& [token, postings_list] : token_postings_list_pairs) { String str_token{token}; builder.add(str_token, offset); - offset += encoding_lengths[current_index++]; + offset += posting_list_byte_sizes[current_index]; + current_index++; } builder.build(); @@ -296,11 +307,11 @@ void GinIndexStore::writeSegment() /// Write FST size writeVarUInt(buffer.size(), *term_dict_file_stream); - current_segment.item_dict_start_offset += getLengthOfVarUInt(buffer.size()); + current_segment.term_dict_start_offset += getLengthOfVarUInt(buffer.size()); /// Write FST content term_dict_file_stream->write(reinterpret_cast(buffer.data()), buffer.size()); - current_segment.item_dict_start_offset += buffer.size(); + current_segment.term_dict_start_offset += buffer.size(); current_size = 0; current_postings.clear(); @@ -320,48 +331,63 @@ GinIndexStoreDeserializer::GinIndexStoreDeserializer(const GinIndexStorePtr & st void GinIndexStoreDeserializer::initFileStreams() { String segment_file_name = store->getName() + GinIndexStore::GIN_SEGMENT_FILE_TYPE; - String item_dict_file_name = store->getName() + GinIndexStore::GIN_DICTIONARY_FILE_TYPE; + String term_dict_file_name = store->getName() + GinIndexStore::GIN_DICTIONARY_FILE_TYPE; String postings_file_name = store->getName() + GinIndexStore::GIN_POSTINGS_FILE_TYPE; segment_file_stream = store->storage->readFile(segment_file_name, {}, std::nullopt, std::nullopt); - term_dict_file_stream = store->storage->readFile(item_dict_file_name, {}, std::nullopt, std::nullopt); + term_dict_file_stream = store->storage->readFile(term_dict_file_name, {}, std::nullopt, std::nullopt); postings_file_stream = store->storage->readFile(postings_file_name, {}, std::nullopt, std::nullopt); } void GinIndexStoreDeserializer::readSegments() { - auto segment_num = store->getSegmentNum(); - if (segment_num == 0) + auto num_segments = store->getNumOfSegments(); + if (num_segments == 0) return; - GinIndexSegments segments (segment_num); + GinIndexSegments segments (num_segments); assert(segment_file_stream != nullptr); - segment_file_stream->readStrict(reinterpret_cast(segments.data()), segment_num * sizeof(GinIndexSegment)); - for (size_t i = 0; i < segment_num; ++i) + uint8_t version = 0; + readBinary(version, *segment_file_stream); + + if (version > CURRENT_GIN_FILE_FORMAT_VERSION) + throw Exception(ErrorCodes::UNKNOWN_FORMAT_VERSION, "Unsupported inverted index version {}", version); + + segment_file_stream->readStrict(reinterpret_cast(segments.data()), num_segments * sizeof(GinIndexSegment)); + for (size_t i = 0; i < num_segments; ++i) { auto seg_id = segments[i].segment_id; - auto term_dict = std::make_shared(); + auto term_dict = std::make_shared(); term_dict->postings_start_offset = segments[i].postings_start_offset; - term_dict->item_dict_start_offset = segments[i].item_dict_start_offset; + term_dict->term_dict_start_offset = segments[i].term_dict_start_offset; store->term_dicts[seg_id] = term_dict; } } -void GinIndexStoreDeserializer::readTermDictionary(UInt32 segment_id) +void GinIndexStoreDeserializer::readSegmentTermDictionaries() +{ + for (UInt32 seg_index = 0; seg_index < store->getNumOfSegments(); ++seg_index) + { + readSegmentTermDictionary(seg_index); + } +} + +void GinIndexStoreDeserializer::readSegmentTermDictionary(UInt32 segment_id) { /// Check validity of segment_id - auto it{ store->term_dicts.find(segment_id) }; - if (it == store->term_dicts.cend()) + auto it = store->term_dicts.find(segment_id); + if (it == store->term_dicts.end()) { - throw Exception("Invalid segment id " + std::to_string(segment_id), ErrorCodes::LOGICAL_ERROR); + throw Exception(ErrorCodes::LOGICAL_ERROR, "Invalid segment id {}", segment_id); } - it->second->offsets.getData().clear(); + assert(term_dict_file_stream != nullptr); /// Set file pointer of term dictionary file - term_dict_file_stream->seek(it->second->item_dict_start_offset, SEEK_SET); + term_dict_file_stream->seek(it->second->term_dict_start_offset, SEEK_SET); + it->second->offsets.getData().clear(); /// Read FST size size_t fst_size{0}; readVarUInt(fst_size, *term_dict_file_stream); @@ -371,14 +397,16 @@ void GinIndexStoreDeserializer::readTermDictionary(UInt32 segment_id) term_dict_file_stream->readStrict(reinterpret_cast(it->second->offsets.getData().data()), fst_size); } -SegmentedPostingsListContainer GinIndexStoreDeserializer::readSegmentedPostingsLists(const String& token) +SegmentedPostingsListContainer GinIndexStoreDeserializer::readSegmentedPostingsLists(const String& term) { + assert(postings_file_stream != nullptr); + SegmentedPostingsListContainer container; for (auto const& seg_term_dict : store->term_dicts) { auto segment_id = seg_term_dict.first; - auto [offset, found] = seg_term_dict.second->offsets.getOutput(token); + auto [offset, found] = seg_term_dict.second->offsets.getOutput(term); if (!found) continue; @@ -392,13 +420,13 @@ SegmentedPostingsListContainer GinIndexStoreDeserializer::readSegmentedPostingsL return container; } -PostingsCachePtr GinIndexStoreDeserializer::loadPostingsIntoCache(const std::vector& terms) +PostingsCachePtr GinIndexStoreDeserializer::createPostingsCacheFromTerms(const std::vector& terms) { auto postings_cache = std::make_shared(); for (const auto& term : terms) { // Make sure don't read for duplicated terms - if (postings_cache->find(term) != postings_cache->cend()) + if (postings_cache->find(term) != postings_cache->end()) continue; auto container = readSegmentedPostingsLists(term); @@ -413,27 +441,23 @@ GinIndexStoreFactory& GinIndexStoreFactory::instance() return instance; } -GinIndexStorePtr GinIndexStoreFactory::get(const String& name, DataPartStoragePtr storage_) +GinIndexStorePtr GinIndexStoreFactory::get(const String& name, DataPartStoragePtr storage) { - const String& part_path = storage_->getRelativePath(); - String key = name + String(":")+part_path; + const String& part_path = storage->getRelativePath(); + String key = name + ":" + part_path; std::lock_guard lock(stores_mutex); GinIndexStores::const_iterator it = stores.find(key); - if (it == stores.cend()) + if (it == stores.end()) { - GinIndexStorePtr store = std::make_shared(name, storage_); + GinIndexStorePtr store = std::make_shared(name, storage); if (!store->exists()) return nullptr; - GinIndexStoreDeserializer reader(store); - reader.readSegments(); - - for (UInt32 seg_index = 0; seg_index < store->getSegmentNum(); ++seg_index) - { - reader.readTermDictionary(seg_index); - } + GinIndexStoreDeserializer deserializer(store); + deserializer.readSegments(); + deserializer.readSegmentTermDictionaries(); stores[key] = store; diff --git a/src/Storages/MergeTree/GinIndexStore.h b/src/Storages/MergeTree/GinIndexStore.h index 3c72b634a735..9bdc8dd4f3d5 100644 --- a/src/Storages/MergeTree/GinIndexStore.h +++ b/src/Storages/MergeTree/GinIndexStore.h @@ -13,46 +13,26 @@ #include namespace DB { - -constexpr int MIN_SIZE_FOR_ROARING_ENCODING = 16; - -/// Gin Index Segment information, which contains: -struct GinIndexSegment +enum : uint8_t { - /// .gin_post file offset of this segment's postings lists - UInt64 postings_start_offset{0}; - - /// .gin_dict file offset of this segment's term dictionaries - UInt64 item_dict_start_offset{0}; - - /// Next row ID for this segment - UInt32 next_row_id{1}; - - /// Segment ID retrieved from next available ID from file .gin_sid - UInt32 segment_id {0}; + GIN_VERSION_0 = 0, + GIN_VERSION_1 = 1, /// Initial version }; -using GinIndexSegments = std::vector; +static constexpr auto CURRENT_GIN_FILE_FORMAT_VERSION = GIN_VERSION_1; /// GinIndexPostingsList which uses 32-bit Roaring using GinIndexPostingsList = roaring::Roaring; -using GinIndexPostingsListPtr = std::shared_ptr; +using GinIndexPostingsListPtr = std::shared_ptr; /// Gin Index Postings List Builder. class GinIndexPostingsBuilder { public: - GinIndexPostingsBuilder(UInt64 limit); - - /// When the list length is no greater than MIN_SIZE_FOR_ROARING_ENCODING, array 'lst' is used - std::array lst; + constexpr static int MIN_SIZE_FOR_ROARING_ENCODING = 16; - /// When the list length is greater than MIN_SIZE_FOR_ROARING_ENCODING, Roaring bitmap 'rowid_bitmap' is used - roaring::Roaring rowid_bitmap; - - /// lst_length stores the number of row IDs in 'lst' array, can also be a flag(0xFF) indicating that roaring bitmap is used - UInt8 lst_length{0}; + GinIndexPostingsBuilder(UInt64 limit); /// Check whether a row_id is already added bool contains(UInt32 row_id) const; @@ -72,16 +52,20 @@ class GinIndexPostingsBuilder /// Deserialize the postings list data from given ReadBuffer, return a pointer to the GinIndexPostingsList created by deserialization static GinIndexPostingsListPtr deserialize(ReadBuffer &buffer); private: + /// When the list length is no greater than MIN_SIZE_FOR_ROARING_ENCODING, array 'rowid_lst' is used + std::array rowid_lst; + + /// When the list length is greater than MIN_SIZE_FOR_ROARING_ENCODING, Roaring bitmap 'rowid_bitmap' is used + roaring::Roaring rowid_bitmap; + + /// rowid_lst_length stores the number of row IDs in 'rowid_lst' array, can also be a flag(0xFF) indicating that roaring bitmap is used + UInt8 rowid_lst_length{0}; + static constexpr UInt8 UsesBitMap = 0xFF; /// Clear the postings list and reset it with MATCHALL flags when the size of the postings list is beyond the limit UInt64 size_limit; }; -using GinIndexPostingsBuilderPtr = std::shared_ptr; - -/// Container for all term's Gin Index Postings List Builder -using GinIndexPostingsBuilderContainer = std::unordered_map; - /// Container for postings lists for each segment using SegmentedPostingsListContainer = std::unordered_map; @@ -89,63 +73,78 @@ using SegmentedPostingsListContainer = std::unordered_map; using PostingsCachePtr = std::shared_ptr; -/// Term dictionary information, which contains: -struct TermDictionary +/// Gin Index Segment information, which contains: +struct GinIndexSegment +{ + /// Segment ID retrieved from next available ID from file .gin_sid + UInt32 segment_id = 0; + + /// Next row ID for this segment + UInt32 next_row_id = 1; + + /// .gin_post file offset of this segment's postings lists + UInt64 postings_start_offset = 0; + + /// .gin_dict file offset of this segment's term dictionaries + UInt64 term_dict_start_offset = 0; +}; + +using GinIndexSegments = std::vector; + +using GinIndexPostingsBuilderPtr = std::shared_ptr; + +/// Container for all term's Gin Index Postings List Builder +using GinIndexPostingsBuilderContainer = std::unordered_map; +struct SegmentTermDictionary { /// .gin_post file offset of this segment's postings lists UInt64 postings_start_offset; /// .gin_dict file offset of this segment's term dictionaries - UInt64 item_dict_start_offset; + UInt64 term_dict_start_offset; - /// Finite State Transducer, which can be viewed as a map of + /// Finite State Transducer, which can be viewed as a map of , where offset is the + /// offset to the term's posting list in postings list file FST::FiniteStateTransducer offsets; }; -using TermDictionaryPtr = std::shared_ptr; -using TermDictionaries = std::unordered_map; +using SegmentTermDictionaryPtr = std::shared_ptr; + +/// Term dictionaries indexed by segment ID +using SegmentTermDictionaries = std::unordered_map; /// Gin Index Store which has Gin Index meta data for the corresponding Data Part class GinIndexStore { public: - explicit GinIndexStore(const String& name_, DataPartStoragePtr storage_) - : name(name_), - storage(storage_) - { - } - GinIndexStore(const String& name_, DataPartStoragePtr storage_, MutableDataPartStoragePtr data_part_storage_builder_, UInt64 max_digestion_size_) - : name(name_), - storage(storage_), - data_part_storage_builder(data_part_storage_builder_), - max_digestion_size(max_digestion_size_) - { - } + explicit GinIndexStore(const String & name_, DataPartStoragePtr storage_); - bool load(); + GinIndexStore(const String& name_, DataPartStoragePtr storage_, MutableDataPartStoragePtr data_part_storage_builder_, UInt64 max_digestion_size_); - /// Check existence by checking the existence of file .gin_seg + /// Check existence by checking the existence of file .gin_sid bool exists() const; - UInt32 getNextIDRange(const String &file_name, size_t n); - - UInt32 getNextRowIDRange(size_t n); + /// Get a range of next 'numIDs' available row IDs + UInt32 getNextRowIDRange(size_t numIDs); + /// Get next available segment ID by updating file .gin_sid UInt32 getNextSegmentID(); - UInt32 getSegmentNum(); - - using GinIndexStorePtr = std::shared_ptr; + /// Get total number of segments in the store + UInt32 getNumOfSegments(); - GinIndexPostingsBuilderContainer& getPostings() { return current_postings; } + /// Get current postings list builder + const GinIndexPostingsBuilderContainer& getPostings() const { return current_postings; } + /// Set postings list builder for given term + void setPostingsBuilder(const String & term, GinIndexPostingsBuilderPtr builder) { current_postings[term] = builder; } /// Check if we need to write segment to Gin index files bool needToWrite() const; /// Accumulate the size of text data which has been digested - void addSize(UInt64 sz) { current_size += sz; } + void incrementCurrentSizeBy(UInt64 sz) { current_size += sz; } - UInt32 getCurrentSegmentID() { return current_segment.segment_id;} + UInt32 getCurrentSegmentID() const { return current_segment.segment_id;} /// Do last segment writing void finalize(); @@ -158,8 +157,12 @@ class GinIndexStore private: friend class GinIndexStoreDeserializer; + /// Initialize all indexing files for this store void initFileStreams(); + /// Get a range of next available segment IDs by updating file .gin_sid + UInt32 getNextSegmentIDRange(const String &file_name, size_t n); + String name; DataPartStoragePtr storage; MutableDataPartStoragePtr data_part_storage_builder; @@ -169,15 +172,15 @@ class GinIndexStore std::mutex gin_index_store_mutex; /// Terms dictionaries which are loaded from .gin_dict files - TermDictionaries term_dicts; + SegmentTermDictionaries term_dicts; /// container for building postings lists during index construction GinIndexPostingsBuilderContainer current_postings; /// The following is for segmentation of Gin index GinIndexSegment current_segment{}; - UInt64 current_size{0}; - UInt64 max_digestion_size{0}; + UInt64 current_size = 0; + const UInt64 max_digestion_size = 0; /// File streams for segment, term dictionaries and postings lists std::unique_ptr segment_file_stream; @@ -191,9 +194,14 @@ class GinIndexStore }; using GinIndexStorePtr = std::shared_ptr; + +/// GinIndexStores indexed by part file path using GinIndexStores = std::unordered_map; -/// Postings lists from 'store' which are retrieved from Gin index files for the terms in query strings +/// PostingsCacheForStore contains postings lists from 'store' which are retrieved from Gin index files for the terms in query strings +/// PostingsCache is per query string(one query can have multiple query strings): when skipping index(row ID ranges) is used for the part during the +/// query, the postings cache is created and associated with the store where postings lists are read +/// for the tokenized query string. The postings caches are released automatically when the query is done. struct PostingsCacheForStore { /// Which store to retrieve postings lists @@ -223,9 +231,9 @@ class GinIndexStoreFactory : private boost::noncopyable static GinIndexStoreFactory& instance(); /// Get GinIndexStore by using index name, disk and part_path (which are combined to create key in stores) - GinIndexStorePtr get(const String& name, DataPartStoragePtr storage_); + GinIndexStorePtr get(const String& name, DataPartStoragePtr storage); - /// Remove all GinIndexStores which are under the same part_path + /// Remove all Gin index files which are under the same part_path void remove(const String& part_path); private: @@ -233,30 +241,33 @@ class GinIndexStoreFactory : private boost::noncopyable std::mutex stores_mutex; }; +/// Term dictionary information, which contains: + /// Gin Index Store Reader which helps to read segments, term dictionaries and postings list class GinIndexStoreDeserializer : private boost::noncopyable { public: - GinIndexStoreDeserializer(const GinIndexStorePtr & store_); + explicit GinIndexStoreDeserializer(const GinIndexStorePtr & store_); /// Read all segment information from .gin_seg files void readSegments(); + /// Read all term dictionaries from .gin_dict files + void readSegmentTermDictionaries(); + /// Read term dictionary for given segment id - void readTermDictionary(UInt32 segment_id); + void readSegmentTermDictionary(UInt32 segment_id); /// Read postings lists for the term - SegmentedPostingsListContainer readSegmentedPostingsLists(const String& token); + SegmentedPostingsListContainer readSegmentedPostingsLists(const String& term); /// Read postings lists for terms(which are created by tokenzing query string) - PostingsCachePtr loadPostingsIntoCache(const std::vector& terms); + PostingsCachePtr createPostingsCacheFromTerms(const std::vector& terms); private: /// Initialize Gin index files void initFileStreams(); -private: - /// The store for the reader GinIndexStorePtr store; @@ -268,6 +279,5 @@ class GinIndexStoreDeserializer : private boost::noncopyable /// Current segment, used in building index GinIndexSegment current_segment; }; -using GinIndexStoreReaderPtr = std::unique_ptr; } diff --git a/src/Storages/MergeTree/MergeTreeIndexFullText.cpp b/src/Storages/MergeTree/MergeTreeIndexFullText.cpp index fd98b0597d49..411141f028be 100644 --- a/src/Storages/MergeTree/MergeTreeIndexFullText.cpp +++ b/src/Storages/MergeTree/MergeTreeIndexFullText.cpp @@ -183,6 +183,7 @@ MergeTreeConditionFullText::MergeTreeConditionFullText( rpn = std::move(builder).extractRPN(); } +/// Keep in-sync with MergeTreeConditionGinFilter::alwaysUnknownOrTrue bool MergeTreeConditionFullText::alwaysUnknownOrTrue() const { /// Check like in KeyCondition. diff --git a/src/Storages/MergeTree/MergeTreeIndexGin.cpp b/src/Storages/MergeTree/MergeTreeIndexGin.cpp index 36648f856068..12465105b7e0 100644 --- a/src/Storages/MergeTree/MergeTreeIndexGin.cpp +++ b/src/Storages/MergeTree/MergeTreeIndexGin.cpp @@ -59,7 +59,7 @@ void MergeTreeIndexGranuleGinFilter::serializeBinary(WriteBuffer & ostr) const { size_t filter_size = gin_filter.getFilter().size(); size_serialization->serializeBinary(filter_size, ostr, {}); - ostr.write(reinterpret_cast(gin_filter.getFilter().data()), filter_size * sizeof(GinFilter::RowIDRangeContainer::value_type)); + ostr.write(reinterpret_cast(gin_filter.getFilter().data()), filter_size * sizeof(GinFilter::GinSegmentWithRowIDRanges::value_type)); } } @@ -81,7 +81,7 @@ void MergeTreeIndexGranuleGinFilter::deserializeBinary(ReadBuffer & istr, MergeT continue; gin_filter.getFilter().assign(filter_size, {}); - istr.readStrict(reinterpret_cast(gin_filter.getFilter().data()), filter_size * sizeof(GinFilter::RowIDRangeContainer::value_type)); + istr.readStrict(reinterpret_cast(gin_filter.getFilter().data()), filter_size * sizeof(GinFilter::GinSegmentWithRowIDRanges::value_type)); } has_elems = true; } @@ -157,7 +157,7 @@ void MergeTreeIndexAggregatorGinFilter::update(const Block & block, size_t * pos { auto ref = column_key.getDataAt(element_start_row + row_num); addToGinFilter(row_id, ref.data, ref.size, granule->gin_filters[col], rows_read); - store->addSize(ref.size); + store->incrementCurrentSizeBy(ref.size); } current_position += 1; row_id++; @@ -172,7 +172,7 @@ void MergeTreeIndexAggregatorGinFilter::update(const Block & block, size_t * pos { auto ref = column->getDataAt(current_position + i); addToGinFilter(row_id, ref.data, ref.size, granule->gin_filters[col], rows_read); - store->addSize(ref.size); + store->incrementCurrentSizeBy(ref.size); row_id++; if (store->needToWrite()) need_to_write = true; @@ -235,6 +235,7 @@ MergeTreeConditionGinFilter::MergeTreeConditionGinFilter( } +/// Keep in-sync with MergeTreeConditionFullText::alwaysUnknownOrTrue bool MergeTreeConditionGinFilter::alwaysUnknownOrTrue() const { /// Check like in KeyCondition. @@ -725,27 +726,20 @@ bool MergeTreeIndexGinFilter::mayBenefitFromIndexForIn(const ASTPtr & node) cons MergeTreeIndexPtr ginIndexCreator( const IndexDescription & index) { - if (index.type == GinFilter::getName()) - { - size_t n = index.arguments.empty() ? 0 : index.arguments[0].get(); - Float64 density = index.arguments.size() < 2 ? 1.0f : index.arguments[1].get(); - GinFilterParameters params(n, density); + size_t n = index.arguments.empty() ? 0 : index.arguments[0].get(); + Float64 density = index.arguments.size() < 2 ? 1.0f : index.arguments[1].get(); + GinFilterParameters params(n, density); - /// Use SplitTokenExtractor when n is 0, otherwise use NgramTokenExtractor - if (n > 0) - { - auto tokenizer = std::make_unique(n); - return std::make_shared(index, params, std::move(tokenizer)); - } - else - { - auto tokenizer = std::make_unique(); - return std::make_shared(index, params, std::move(tokenizer)); - } + /// Use SplitTokenExtractor when n is 0, otherwise use NgramTokenExtractor + if (n > 0) + { + auto tokenizer = std::make_unique(n); + return std::make_shared(index, params, std::move(tokenizer)); } else { - throw Exception("Unknown index type: " + backQuote(index.name), ErrorCodes::LOGICAL_ERROR); + auto tokenizer = std::make_unique(); + return std::make_shared(index, params, std::move(tokenizer)); } } diff --git a/src/Storages/MergeTree/MergeTreeIndices.cpp b/src/Storages/MergeTree/MergeTreeIndices.cpp index 9711c7cdacac..e5e376e7f69d 100644 --- a/src/Storages/MergeTree/MergeTreeIndices.cpp +++ b/src/Storages/MergeTree/MergeTreeIndices.cpp @@ -3,7 +3,6 @@ #include #include #include -#include #include #include @@ -106,8 +105,9 @@ MergeTreeIndexFactory::MergeTreeIndexFactory() registerCreator("annoy", annoyIndexCreator); registerValidator("annoy", annoyIndexValidator); #endif - registerCreator(GinFilter::FilterName, ginIndexCreator); - registerValidator(GinFilter::FilterName, ginIndexValidator); + registerCreator("inverted", ginIndexCreator); + registerValidator("inverted", ginIndexValidator); + } MergeTreeIndexFactory & MergeTreeIndexFactory::instance() diff --git a/src/Storages/MergeTree/MergeTreeIndices.h b/src/Storages/MergeTree/MergeTreeIndices.h index 4ed064793642..52cf8c850b32 100644 --- a/src/Storages/MergeTree/MergeTreeIndices.h +++ b/src/Storages/MergeTree/MergeTreeIndices.h @@ -8,7 +8,6 @@ #include #include #include -#include #include #include #include From fcf17fdc72bf71ee7fb6211a07bcd28fd35596b3 Mon Sep 17 00:00:00 2001 From: HarryLeeIBM Date: Tue, 10 Jan 2023 09:09:11 -0800 Subject: [PATCH 39/42] Fix styles. --- src/Interpreters/GinFilter.cpp | 2 +- src/Interpreters/GinFilter.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Interpreters/GinFilter.cpp b/src/Interpreters/GinFilter.cpp index a2fa47a46310..96ca0c25a257 100644 --- a/src/Interpreters/GinFilter.cpp +++ b/src/Interpreters/GinFilter.cpp @@ -107,7 +107,7 @@ bool GinFilter::matchInRange(const PostingsCache& postings_cache, UInt32 segment for (const auto& term_postings : postings_cache) { - /// Check if it is in the same segement by searching for segment_id + /// Check if it is in the same segment by searching for segment_id const SegmentedPostingsListContainer& container = term_postings.second; auto container_it = container.find(segment_id); if (container_it == container.cend()) diff --git a/src/Interpreters/GinFilter.h b/src/Interpreters/GinFilter.h index d531511472ac..e710af3a6ed3 100644 --- a/src/Interpreters/GinFilter.h +++ b/src/Interpreters/GinFilter.h @@ -25,7 +25,7 @@ struct GinSegmentWithRowIDRange UInt32 range_end; }; -/// GinFilter provides underlying funtionalities for building inverted index and also +/// GinFilter provides underlying functionalities for building inverted index and also /// it does filtering the unmatched rows according to its query string. /// It also builds and uses skipping index which stores (segmentID, RowIDStart, RowIDEnd) triples. class GinFilter From 150f9b48a4acaeafe1512e2cb63aafec082766b0 Mon Sep 17 00:00:00 2001 From: HarryLeeIBM Date: Tue, 10 Jan 2023 14:35:06 -0800 Subject: [PATCH 40/42] Fix functional test for full text search --- src/Storages/MergeTree/GinIndexStore.cpp | 25 +++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/Storages/MergeTree/GinIndexStore.cpp b/src/Storages/MergeTree/GinIndexStore.cpp index bbe65e23a04e..a4b09e00969c 100644 --- a/src/Storages/MergeTree/GinIndexStore.cpp +++ b/src/Storages/MergeTree/GinIndexStore.cpp @@ -172,6 +172,9 @@ UInt32 GinIndexStore::getNextSegmentIDRange(const String& file_name, size_t n) /// Create file and write initial segment id = 1 std::unique_ptr ostr = this->data_part_storage_builder->writeFile(file_name, DBMS_DEFAULT_BUFFER_SIZE, {}); + /// Write version + writeChar(static_cast(CURRENT_GIN_FILE_FORMAT_VERSION), *ostr); + writeVarUInt(1, *ostr); ostr->sync(); } @@ -181,12 +184,18 @@ UInt32 GinIndexStore::getNextSegmentIDRange(const String& file_name, size_t n) { std::unique_ptr istr = this->storage->readFile(file_name, {}, std::nullopt, std::nullopt); + /// Skip version + istr->seek(1, SEEK_SET); + readVarUInt(result, *istr); } //save result+n { std::unique_ptr ostr = this->data_part_storage_builder->writeFile(file_name, DBMS_DEFAULT_BUFFER_SIZE, {}); + /// Write version + writeChar(static_cast(CURRENT_GIN_FILE_FORMAT_VERSION), *ostr); + writeVarUInt(result + n, *ostr); ostr->sync(); } @@ -218,6 +227,13 @@ UInt32 GinIndexStore::getNumOfSegments() UInt32 result = 0; { std::unique_ptr istr = this->storage->readFile(sid_file_name, {}, std::nullopt, std::nullopt); + + uint8_t version = 0; + readBinary(version, *istr); + + if (version > CURRENT_GIN_FILE_FORMAT_VERSION) + throw Exception(ErrorCodes::UNKNOWN_FORMAT_VERSION, "Unsupported inverted index version {}", version); + readVarUInt(result, *istr); } @@ -257,9 +273,6 @@ void GinIndexStore::writeSegment() initFileStreams(); } - /// Write version - writeChar(static_cast(CURRENT_GIN_FILE_FORMAT_VERSION), *segment_file_stream); - /// Write segment segment_file_stream->write(reinterpret_cast(¤t_segment), sizeof(GinIndexSegment)); TokenPostingsBuilderPairs token_postings_list_pairs; @@ -348,12 +361,6 @@ void GinIndexStoreDeserializer::readSegments() assert(segment_file_stream != nullptr); - uint8_t version = 0; - readBinary(version, *segment_file_stream); - - if (version > CURRENT_GIN_FILE_FORMAT_VERSION) - throw Exception(ErrorCodes::UNKNOWN_FORMAT_VERSION, "Unsupported inverted index version {}", version); - segment_file_stream->readStrict(reinterpret_cast(segments.data()), num_segments * sizeof(GinIndexSegment)); for (size_t i = 0; i < num_segments; ++i) { From 6d576cf157e918c3d0fadb098c8bd2a0e2969f89 Mon Sep 17 00:00:00 2001 From: HarryLeeIBM Date: Wed, 11 Jan 2023 13:40:20 -0800 Subject: [PATCH 41/42] Added more documentation --- src/Common/FST.cpp | 22 +++++++++++++++++++++- src/Common/FST.h | 2 ++ src/Storages/MergeTree/GinIndexStore.h | 23 ++++++++++++++++++++++- 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/src/Common/FST.cpp b/src/Common/FST.cpp index 4f6c587304f8..93d996351ec4 100644 --- a/src/Common/FST.cpp +++ b/src/Common/FST.cpp @@ -7,6 +7,9 @@ #include #include +/// "paper" in the comments in this file refers to: +/// [Direct Construction of Minimal Acyclic Subsequential Transduers] by Stoyan Mihov and Denis Maurel, University of Tours, France + namespace DB { namespace ErrorCodes @@ -218,10 +221,13 @@ FSTBuilder::FSTBuilder(WriteBuffer& write_buffer_) : write_buffer(write_buffer_) } } +/// See FindMinimized in the paper pseudo code l11-l21. StatePtr FSTBuilder::findMinimized(const State & state, bool & found) { found = false; auto hash = state.hash(); + + /// MEMBER: in the paper pseudo code l15 auto it = minimized_states.find(hash); if (it != minimized_states.cend() && *it->second == state) @@ -229,11 +235,16 @@ StatePtr FSTBuilder::findMinimized(const State & state, bool & found) found = true; return it->second; } + + /// COPY_STATE: in the paper pseudo code l17 StatePtr p = std::make_shared(state); + + /// INSERT: in the paper pseudo code l18 minimized_states[hash] = p; return p; } +/// See the paper pseudo code l33-34. size_t FSTBuilder::getCommonPrefixLength(const String & word1, const String & word2) { size_t i = 0; @@ -242,6 +253,7 @@ size_t FSTBuilder::getCommonPrefixLength(const String & word1, const String & wo return i; } +/// See the paper pseudo code l33-39 and l70-72(when down_to is 0). void FSTBuilder::minimizePreviousWordSuffix(Int64 down_to) { for (Int64 i = static_cast(previous_word.size()); i >= down_to; --i) @@ -256,6 +268,7 @@ void FSTBuilder::minimizePreviousWordSuffix(Int64 down_to) if (arc) output = arc->output; + /// SET_TRANSITION temp_states[i - 1]->addArc(previous_word[i - 1], output, minimized_state); } if (minimized_state->id == 0) @@ -291,16 +304,22 @@ void FSTBuilder::add(const std::string & current_word, Output current_output) minimizePreviousWordSuffix(prefix_length_plus1); - /// Initialize the tail state + /// Initialize the tail state, see paper pseudo code l39-43 for (size_t i = prefix_length_plus1; i <= current_word.size(); ++i) { + /// CLEAR_STATE: l41 temp_states[i]->clear(); + + /// SET_TRANSITION: l42 temp_states[i - 1]->addArc(current_word[i - 1], 0, temp_states[i]); } /// We assume the current word is different with previous word + /// See paper pseudo code l44-47 temp_states[current_word_len]->setFinal(true); + /// Adjust outputs on the arcs + /// See paper pseudo code l48-63 for (size_t i = 1; i <= prefix_length_plus1 - 1; ++i) { Arc * arc_ptr = temp_states[i - 1]->getArc(current_word[i - 1]); @@ -323,6 +342,7 @@ void FSTBuilder::add(const std::string & current_word, Output current_output) } /// Set last temp state's output + /// paper pseudo code l66-67 (assuming CurrentWord != PreviousWorld) Arc * arc = temp_states[prefix_length_plus1 - 1]->getArc(current_word[prefix_length_plus1 - 1]); assert(arc != nullptr); arc->output = current_output; diff --git a/src/Common/FST.h b/src/Common/FST.h index f6e0629ac04a..64ff2776b70d 100644 --- a/src/Common/FST.h +++ b/src/Common/FST.h @@ -152,6 +152,8 @@ class FSTBuilder std::array temp_states; String previous_word; StatePtr initial_state; + + /// map of (state_hash, StatePtr) std::unordered_map minimized_states; /// Next available ID of state diff --git a/src/Storages/MergeTree/GinIndexStore.h b/src/Storages/MergeTree/GinIndexStore.h index 9bdc8dd4f3d5..6b291064446e 100644 --- a/src/Storages/MergeTree/GinIndexStore.h +++ b/src/Storages/MergeTree/GinIndexStore.h @@ -11,6 +11,27 @@ #include #include #include + +/// GinIndexStore manages the inverted index for a data part, and it is made up of one or more immutable +/// index segments. +/// +/// There are 4 types of index files in a store: +/// 1. Segment ID file(.gin_sid): it contains one byte for version followed by the next available segment ID. +/// 2. Segment Metadata file(.gin_seg): it contains index segment metadata. +/// - Its file format is an array of GinIndexSegment as defined in this file. +/// - postings_start_offset points to the file(.gin_post) starting position for the segment's postings list. +/// - term_dict_start_offset points to the file(.gin_dict) starting position for the segment's term dictionaries. +/// 3. Term Dictionary file(.gin_dict): it contains term dictionaries. +/// - It contains an array of (FST_size, FST_blob) which has size and actual data of FST. +/// 4. Postings Lists(.gin_post): it contains postings lists data. +/// - It contains an array of serialized postings lists. +/// +/// During the searching in the segment, the segment's meta data can be found in .gin_seg file. From the meta data, +/// the starting position of its term dictionary is used to locate its FST. Then FST is read into memory. +/// By using the term and FST, the offset("output" in FST) of the postings list for the term +/// in FST is found. The offset plus the postings_start_offset is the file location in .gin_post file +/// for its postings list. + namespace DB { enum : uint8_t @@ -85,7 +106,7 @@ struct GinIndexSegment /// .gin_post file offset of this segment's postings lists UInt64 postings_start_offset = 0; - /// .gin_dict file offset of this segment's term dictionaries + /// .term_dict file offset of this segment's term dictionaries UInt64 term_dict_start_offset = 0; }; From e7add8218fe121cd4df73301a2b526bb7f9a5e6b Mon Sep 17 00:00:00 2001 From: HarryLeeIBM Date: Tue, 17 Jan 2023 06:29:13 -0800 Subject: [PATCH 42/42] Addressed more review comments and ClangTidy errors --- src/Common/FST.cpp | 4 ++-- src/Common/FST.h | 4 ++-- src/Interpreters/GinFilter.cpp | 4 ++-- src/Interpreters/GinFilter.h | 4 ++-- src/Storages/AlterCommands.cpp | 2 +- src/Storages/AlterCommands.h | 2 +- src/Storages/MergeTree/GinIndexStore.cpp | 4 ++-- src/Storages/MergeTree/GinIndexStore.h | 8 ++++---- src/Storages/MergeTree/MergeTreeIndexGin.cpp | 2 +- 9 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/Common/FST.cpp b/src/Common/FST.cpp index 93d996351ec4..b409d37919ff 100644 --- a/src/Common/FST.cpp +++ b/src/Common/FST.cpp @@ -100,13 +100,13 @@ bool LabelsAsBitmap::hasLabel(char label) const return ((data & bit_label) != 0); } -Arc* State::getArc(char label) +Arc* State::getArc(char label) const { auto it = arcs.find(label); if (it == arcs.cend()) return nullptr; - return &it->second; + return const_cast(&it->second); } void State::addArc(char label, Output output, StatePtr target) diff --git a/src/Common/FST.h b/src/Common/FST.h index 64ff2776b70d..6bb4fdba7e23 100644 --- a/src/Common/FST.h +++ b/src/Common/FST.h @@ -83,7 +83,7 @@ class State UInt64 hash() const; - Arc * getArc(char label); + Arc * getArc(char label) const; void addArc(char label, Output output, StatePtr target); @@ -170,7 +170,7 @@ class FiniteStateTransducer { public: FiniteStateTransducer() = default; - FiniteStateTransducer(std::vector data_); + explicit FiniteStateTransducer(std::vector data_); std::pair getOutput(const String & term); void clear(); std::vector & getData() { return data; } diff --git a/src/Interpreters/GinFilter.cpp b/src/Interpreters/GinFilter.cpp index 96ca0c25a257..8965d3721d22 100644 --- a/src/Interpreters/GinFilter.cpp +++ b/src/Interpreters/GinFilter.cpp @@ -33,7 +33,7 @@ GinFilter::GinFilter(const GinFilterParameters & params_) { } -void GinFilter::add(const char* data, size_t len, UInt32 rowID, GinIndexStorePtr& store, UInt64 limit) +void GinFilter::add(const char* data, size_t len, UInt32 rowID, GinIndexStorePtr& store, UInt64 limit) const { if (len > FST::MAX_TERM_LENGTH) return; @@ -49,7 +49,7 @@ void GinFilter::add(const char* data, size_t len, UInt32 rowID, GinIndexStorePtr else { UInt64 threshold = std::lround(limit * params.density); - GinIndexPostingsBuilderPtr builder = std::make_shared(threshold); + GinIndexStore::GinIndexPostingsBuilderPtr builder = std::make_shared(threshold); builder->add(rowID); store->setPostingsBuilder(term, builder); diff --git a/src/Interpreters/GinFilter.h b/src/Interpreters/GinFilter.h index e710af3a6ed3..0bcd4156f945 100644 --- a/src/Interpreters/GinFilter.h +++ b/src/Interpreters/GinFilter.h @@ -37,7 +37,7 @@ class GinFilter /// Add term(which length is 'len' and located at 'data') and its row ID to /// the postings list builder for building inverted index for the given store. - void add(const char* data, size_t len, UInt32 rowID, GinIndexStorePtr& store, UInt64 limit); + void add(const char* data, size_t len, UInt32 rowID, GinIndexStorePtr& store, UInt64 limit) const; /// Accumulate (segmentID, RowIDStart, RowIDEnd) for building skipping index void addRowRangeToGinFilter(UInt32 segmentID, UInt32 rowIDStart, UInt32 rowIDEnd); @@ -47,7 +47,7 @@ class GinFilter /// Check if the filter(built from query string) contains any rows in given filter 'af' by using /// given postings list cache - bool contains(const GinFilter& af, PostingsCacheForStore &store) const; + bool contains(const GinFilter & filter, PostingsCacheForStore &cache_store) const; /// Const getter for the row ID ranges const GinSegmentWithRowIDRanges& getFilter() const { return rowid_ranges; } diff --git a/src/Storages/AlterCommands.cpp b/src/Storages/AlterCommands.cpp index e6c5913b150c..1d4df05c723b 100644 --- a/src/Storages/AlterCommands.cpp +++ b/src/Storages/AlterCommands.cpp @@ -905,7 +905,7 @@ std::optional AlterCommand::tryConvertToMutationCommand(Storage return result; } -bool AlterCommands::hasInvertedIndex(const StorageInMemoryMetadata & metadata, ContextPtr context) const +bool AlterCommands::hasInvertedIndex(const StorageInMemoryMetadata & metadata, ContextPtr context) { for (const auto & index : metadata.secondary_indices) { diff --git a/src/Storages/AlterCommands.h b/src/Storages/AlterCommands.h index fd91cc114875..a79827b355d0 100644 --- a/src/Storages/AlterCommands.h +++ b/src/Storages/AlterCommands.h @@ -212,7 +212,7 @@ class AlterCommands : public std::vector MutationCommands getMutationCommands(StorageInMemoryMetadata metadata, bool materialize_ttl, ContextPtr context, bool with_alters=false) const; /// Check if commands have any inverted index - bool hasInvertedIndex(const StorageInMemoryMetadata & metadata, ContextPtr context) const; + static bool hasInvertedIndex(const StorageInMemoryMetadata & metadata, ContextPtr context); }; } diff --git a/src/Storages/MergeTree/GinIndexStore.cpp b/src/Storages/MergeTree/GinIndexStore.cpp index a4b09e00969c..995f4f9f88c3 100644 --- a/src/Storages/MergeTree/GinIndexStore.cpp +++ b/src/Storages/MergeTree/GinIndexStore.cpp @@ -18,7 +18,7 @@ namespace DB { -using TokenPostingsBuilderPair = std::pair; +using TokenPostingsBuilderPair = std::pair; using TokenPostingsBuilderPairs = std::vector; namespace ErrorCodes @@ -280,7 +280,7 @@ void GinIndexStore::writeSegment() for (const auto& [token, postings_list] : current_postings) { - token_postings_list_pairs.push_back({std::string_view(token), postings_list}); + token_postings_list_pairs.push_back({token, postings_list}); } /// Sort token-postings list pairs since all tokens have to be added in FST in sorted order diff --git a/src/Storages/MergeTree/GinIndexStore.h b/src/Storages/MergeTree/GinIndexStore.h index 6b291064446e..c326322191f8 100644 --- a/src/Storages/MergeTree/GinIndexStore.h +++ b/src/Storages/MergeTree/GinIndexStore.h @@ -112,10 +112,6 @@ struct GinIndexSegment using GinIndexSegments = std::vector; -using GinIndexPostingsBuilderPtr = std::shared_ptr; - -/// Container for all term's Gin Index Postings List Builder -using GinIndexPostingsBuilderContainer = std::unordered_map; struct SegmentTermDictionary { /// .gin_post file offset of this segment's postings lists @@ -138,6 +134,10 @@ using SegmentTermDictionaries = std::unordered_map; + /// Container for all term's Gin Index Postings List Builder + using GinIndexPostingsBuilderContainer = std::unordered_map; + explicit GinIndexStore(const String & name_, DataPartStoragePtr storage_); GinIndexStore(const String& name_, DataPartStoragePtr storage_, MutableDataPartStoragePtr data_part_storage_builder_, UInt64 max_digestion_size_); diff --git a/src/Storages/MergeTree/MergeTreeIndexGin.cpp b/src/Storages/MergeTree/MergeTreeIndexGin.cpp index 12465105b7e0..26f3fcb4fb6f 100644 --- a/src/Storages/MergeTree/MergeTreeIndexGin.cpp +++ b/src/Storages/MergeTree/MergeTreeIndexGin.cpp @@ -770,7 +770,7 @@ void ginIndexValidator(const IndexDescription & index, bool /*attach*/) if (index.arguments.size() > 2) throw Exception("Inverted index must have less than two arguments.", ErrorCodes::INCORRECT_QUERY); - if (index.arguments.size() >= 1 && index.arguments[0].getType() != Field::Types::UInt64) + if (!index.arguments.empty() && index.arguments[0].getType() != Field::Types::UInt64) throw Exception("The first Inverted index argument must be positive integer.", ErrorCodes::INCORRECT_QUERY); if (index.arguments.size() == 2 && (index.arguments[1].getType() != Field::Types::Float64 || index.arguments[1].get() <= 0 || index.arguments[1].get() > 1))