blob: 66955a19ab0792b0e807b4910789d020379b761b [file] [log] [blame]
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "src/trace_processor/db/column/null_overlay.h"
#include <algorithm>
#include <cstdint>
#include <limits>
#include <memory>
#include <optional>
#include <utility>
#include <vector>
#include "perfetto/base/logging.h"
#include "perfetto/public/compiler.h"
#include "perfetto/trace_processor/basic_types.h"
#include "src/trace_processor/containers/bit_vector.h"
#include "src/trace_processor/db/column/data_layer.h"
#include "src/trace_processor/db/column/types.h"
#include "src/trace_processor/tp_metatrace.h"
#include "protos/perfetto/trace_processor/metatrace_categories.pbzero.h"
namespace perfetto::trace_processor::column {
namespace {
namespace {
using Indices = DataLayerChain::Indices;
std::optional<Token> UpdateIndicesForInner(Indices& indices,
const BitVector& non_null) {
// Find first NULL.
auto first_null_it = std::find_if(
indices.tokens.begin(), indices.tokens.end(),
[&non_null](const Token& t) { return !non_null.IsSet(t.index); });
// Save first NULL.
std::optional<Token> null_tok;
if (first_null_it != indices.tokens.end()) {
null_tok = *first_null_it;
}
// Erase all NULLs.
indices.tokens.erase(std::remove_if(first_null_it, indices.tokens.end(),
[&non_null](const Token& idx) {
return !non_null.IsSet(idx.index);
}),
indices.tokens.end());
// Update index of each token so they all point to inner.
for (auto& token : indices.tokens) {
token.index = non_null.CountSetBits(token.index);
}
return null_tok;
}
} // namespace
BitVector ReconcileStorageResult(FilterOp op,
const BitVector& non_null,
RangeOrBitVector storage_result,
Range in_range) {
PERFETTO_CHECK(in_range.end <= non_null.size());
// Reconcile the results of the Search operation with the non-null indices
// to ensure only those positions are set.
BitVector res;
if (storage_result.IsRange()) {
Range range = std::move(storage_result).TakeIfRange();
if (!range.empty()) {
res = non_null.IntersectRange(non_null.IndexOfNthSet(range.start),
non_null.IndexOfNthSet(range.end - 1) + 1);
// We should always have at least as many elements as the input range
// itself.
PERFETTO_CHECK(res.size() <= in_range.end);
}
} else {
res = non_null.Copy();
res.UpdateSetBits(std::move(storage_result).TakeIfBitVector());
}
// Ensure that |res| exactly matches the size which we need to return,
// padding with zeros or truncating if necessary.
res.Resize(in_range.end, false);
// For the IS NULL constraint, we also need to include all the null indices
// themselves.
if (PERFETTO_UNLIKELY(op == FilterOp::kIsNull)) {
BitVector null = non_null.IntersectRange(in_range.start, in_range.end);
null.Resize(in_range.end, false);
null.Not();
res.Or(null);
}
return res;
}
} // namespace
void NullOverlay::Flatten(uint32_t* start,
const uint32_t* end,
uint32_t stride) {
for (uint32_t* it = start; it < end; it += stride) {
if (non_null_->IsSet(*it)) {
*it = non_null_->CountSetBits(*it);
} else {
*it = std::numeric_limits<uint32_t>::max();
}
}
}
SingleSearchResult NullOverlay::ChainImpl::SingleSearch(FilterOp op,
SqlValue sql_val,
uint32_t index) const {
switch (op) {
case FilterOp::kIsNull:
return non_null_->IsSet(index)
? inner_->SingleSearch(op, sql_val,
non_null_->CountSetBits(index))
: SingleSearchResult::kMatch;
case FilterOp::kIsNotNull:
case FilterOp::kEq:
case FilterOp::kGe:
case FilterOp::kGt:
case FilterOp::kLt:
case FilterOp::kLe:
case FilterOp::kNe:
case FilterOp::kGlob:
case FilterOp::kRegex:
return non_null_->IsSet(index)
? inner_->SingleSearch(op, sql_val,
non_null_->CountSetBits(index))
: SingleSearchResult::kNoMatch;
}
PERFETTO_FATAL("For GCC");
}
NullOverlay::ChainImpl::ChainImpl(std::unique_ptr<DataLayerChain> inner,
const BitVector* non_null)
: inner_(std::move(inner)), non_null_(non_null) {
PERFETTO_DCHECK(non_null_->CountSetBits() <= inner_->size());
}
SearchValidationResult NullOverlay::ChainImpl::ValidateSearchConstraints(
FilterOp op,
SqlValue sql_val) const {
if (op == FilterOp::kIsNull || op == FilterOp::kIsNotNull) {
return SearchValidationResult::kOk;
}
if (sql_val.is_null()) {
return SearchValidationResult::kNoData;
}
return inner_->ValidateSearchConstraints(op, sql_val);
}
RangeOrBitVector NullOverlay::ChainImpl::SearchValidated(FilterOp op,
SqlValue sql_val,
Range in) const {
PERFETTO_TP_TRACE(metatrace::Category::DB, "NullOverlay::ChainImpl::Search");
if (op == FilterOp::kIsNull) {
switch (inner_->ValidateSearchConstraints(op, sql_val)) {
case SearchValidationResult::kNoData: {
// There is no need to search in underlying storage. It's enough to
// intersect the |non_null_|.
BitVector res = non_null_->Copy();
res.Resize(in.end, false);
res.Not();
return RangeOrBitVector(res.IntersectRange(in.start, in.end));
}
case SearchValidationResult::kAllData:
return RangeOrBitVector(in);
case SearchValidationResult::kOk:
break;
}
} else if (op == FilterOp::kIsNotNull) {
switch (inner_->ValidateSearchConstraints(op, sql_val)) {
case SearchValidationResult::kNoData:
return RangeOrBitVector(Range());
case SearchValidationResult::kAllData:
return RangeOrBitVector(non_null_->IntersectRange(in.start, in.end));
case SearchValidationResult::kOk:
break;
}
}
// Figure out the bounds of the indices in the underlying storage and search
// it.
uint32_t start = non_null_->CountSetBits(in.start);
uint32_t end = non_null_->CountSetBits(in.end);
BitVector res = ReconcileStorageResult(
op, *non_null_, inner_->SearchValidated(op, sql_val, Range(start, end)),
in);
PERFETTO_DCHECK(res.size() == in.end);
return RangeOrBitVector(std::move(res));
}
void NullOverlay::ChainImpl::IndexSearchValidated(FilterOp op,
SqlValue sql_val,
Indices& indices) const {
PERFETTO_TP_TRACE(metatrace::Category::DB,
"NullOverlay::ChainImpl::IndexSearch");
if (op == FilterOp::kIsNull) {
// Partition the vector into all the null indices followed by all the
// non-null indices.
auto non_null_it = std::stable_partition(
indices.tokens.begin(), indices.tokens.end(),
[this](const Token& t) { return !non_null_->IsSet(t.index); });
// IndexSearch |inner_| with a vector containing a copy of the (translated)
// non-null indices.
Indices non_null{{non_null_it, indices.tokens.end()}, indices.state};
for (auto& token : non_null.tokens) {
token.index = non_null_->CountSetBits(token.index);
}
inner_->IndexSearch(op, sql_val, non_null);
// Replace all the original non-null positions with the result from calling
// IndexSearch.
auto new_non_null_it =
indices.tokens.erase(non_null_it, indices.tokens.end());
indices.tokens.insert(new_non_null_it, non_null.tokens.begin(),
non_null.tokens.end());
// Merge the two sorted index ranges together using the payload as the
// comparator. This is a required post-condition of IndexSearch.
std::inplace_merge(indices.tokens.begin(), new_non_null_it,
indices.tokens.end(), Token::PayloadComparator());
return;
}
auto keep_only_non_null = [this, &indices]() {
indices.tokens.erase(
std::remove_if(
indices.tokens.begin(), indices.tokens.end(),
[this](const Token& idx) { return !non_null_->IsSet(idx.index); }),
indices.tokens.end());
return;
};
if (op == FilterOp::kIsNotNull) {
switch (inner_->ValidateSearchConstraints(op, sql_val)) {
case SearchValidationResult::kNoData:
indices.tokens.clear();
return;
case SearchValidationResult::kAllData:
keep_only_non_null();
return;
case SearchValidationResult::kOk:
break;
}
}
keep_only_non_null();
for (auto& token : indices.tokens) {
token.index = non_null_->CountSetBits(token.index);
}
inner_->IndexSearchValidated(op, sql_val, indices);
}
void NullOverlay::ChainImpl::StableSort(Token* start,
Token* end,
SortDirection direction) const {
PERFETTO_TP_TRACE(metatrace::Category::DB,
"NullOverlay::ChainImpl::StableSort");
Token* middle = std::stable_partition(start, end, [this](const Token& idx) {
return !non_null_->IsSet(idx.index);
});
for (Token* it = middle; it != end; ++it) {
it->index = non_null_->CountSetBits(it->index);
}
inner_->StableSort(middle, end, direction);
if (direction == SortDirection::kDescending) {
std::rotate(start, middle, end);
}
}
void NullOverlay::ChainImpl::Distinct(Indices& indices) const {
PERFETTO_TP_TRACE(metatrace::Category::DB,
"NullOverlay::ChainImpl::Distinct");
auto null_tok = UpdateIndicesForInner(indices, *non_null_);
inner_->Distinct(indices);
// Add the only null as it is distinct value.
if (null_tok.has_value()) {
indices.tokens.push_back(*null_tok);
}
}
std::optional<Token> NullOverlay::ChainImpl::MaxElement(
Indices& indices) const {
PERFETTO_TP_TRACE(metatrace::Category::DB,
"NullOverlay::ChainImpl::MaxElement");
auto null_tok = UpdateIndicesForInner(indices, *non_null_);
std::optional<Token> max_tok = inner_->MaxElement(indices);
return max_tok ? max_tok : null_tok;
}
std::optional<Token> NullOverlay::ChainImpl::MinElement(
Indices& indices) const {
PERFETTO_TP_TRACE(metatrace::Category::DB,
"NullOverlay::ChainImpl::MinDistinct");
// The smallest value would be a NULL, so we should just return NULL here.
auto first_null_it = std::find_if(
indices.tokens.begin(), indices.tokens.end(),
[this](const Token& t) { return !non_null_->IsSet(t.index); });
if (first_null_it != indices.tokens.end()) {
return *first_null_it;
}
// If we didn't find a null in indices we need to update index of each token
// so they all point to inner and look for the smallest value in the storage.
for (auto& token : indices.tokens) {
token.index = non_null_->CountSetBits(token.index);
}
return inner_->MinElement(indices);
}
SqlValue NullOverlay::ChainImpl::Get_AvoidUsingBecauseSlow(
uint32_t index) const {
return non_null_->IsSet(index)
? inner_->Get_AvoidUsingBecauseSlow(non_null_->CountSetBits(index))
: SqlValue();
}
} // namespace perfetto::trace_processor::column