blob: 54a1d9a6e2e1dc206b73d8f24435554a7fb40ee5 [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 <cstdint>
#include "protos/perfetto/trace_processor/serialization.pbzero.h"
#include "src/trace_processor/containers/bit_vector.h"
#include "src/trace_processor/containers/row_map.h"
#include "src/trace_processor/db/column/column.h"
#include "src/trace_processor/db/column/types.h"
#include "src/trace_processor/tp_metatrace.h"
namespace perfetto {
namespace trace_processor {
namespace column {
namespace {
using Range = RowMap::Range;
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.size() > 0) {
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
SearchValidationResult NullOverlay::ValidateSearchConstraints(
SqlValue sql_val,
FilterOp op) const {
if (op == FilterOp::kIsNull) {
return SearchValidationResult::kOk;
}
return storage_->ValidateSearchConstraints(sql_val, op);
}
NullOverlay::NullOverlay(std::unique_ptr<Column> storage,
const BitVector* non_null)
: storage_(std::move(storage)), non_null_(non_null) {
PERFETTO_DCHECK(non_null_->CountSetBits() <= storage_->size());
}
RangeOrBitVector NullOverlay::Search(FilterOp op,
SqlValue sql_val,
RowMap::Range in) const {
PERFETTO_TP_TRACE(metatrace::Category::DB, "NullOverlay::Search");
if (op == FilterOp::kIsNull) {
switch (storage_->ValidateSearchConstraints(sql_val, op)) {
case SearchValidationResult::kNoData: {
// There is no need to search in underlying storage. It's enough to
// intersect the |non_null_|.
BitVector res = non_null_->IntersectRange(in.start, in.end);
res.Not();
res.Resize(in.end, false);
return RangeOrBitVector(std::move(res));
}
case SearchValidationResult::kAllData:
return RangeOrBitVector(in);
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_, storage_->Search(op, sql_val, RowMap::Range(start, end)),
in);
PERFETTO_DCHECK(res.size() == in.end);
return RangeOrBitVector(std::move(res));
}
RangeOrBitVector NullOverlay::IndexSearch(FilterOp op,
SqlValue sql_val,
uint32_t* indices,
uint32_t indices_size,
bool sorted) const {
PERFETTO_TP_TRACE(metatrace::Category::DB, "NullOverlay::IndexSearch");
if (op == FilterOp::kIsNull) {
switch (storage_->ValidateSearchConstraints(sql_val, op)) {
case SearchValidationResult::kNoData: {
BitVector::Builder null_indices(indices_size);
for (uint32_t* it = indices; it != indices + indices_size; it++) {
null_indices.Append(!non_null_->IsSet(*it));
}
// There is no need to search in underlying storage. We should just
// check if the index is set in |non_null_|.
return RangeOrBitVector(std::move(null_indices).Build());
}
case SearchValidationResult::kAllData:
return RangeOrBitVector(Range(0, indices_size));
case SearchValidationResult::kOk:
break;
}
}
BitVector::Builder storage_non_null(indices_size);
std::vector<uint32_t> storage_iv;
storage_iv.reserve(indices_size);
for (uint32_t* it = indices; it != indices + indices_size; it++) {
bool is_non_null = non_null_->IsSet(*it);
if (is_non_null) {
storage_iv.push_back(non_null_->CountSetBits(*it));
}
storage_non_null.Append(is_non_null);
}
RangeOrBitVector range_or_bv =
storage_->IndexSearch(op, sql_val, storage_iv.data(),
static_cast<uint32_t>(storage_iv.size()), sorted);
BitVector res =
ReconcileStorageResult(op, std::move(storage_non_null).Build(),
std::move(range_or_bv), Range(0, indices_size));
PERFETTO_DCHECK(res.size() == indices_size);
return RangeOrBitVector(std::move(res));
}
void NullOverlay::StableSort(uint32_t*, uint32_t) const {
// TODO(b/307482437): Implement.
PERFETTO_FATAL("Not implemented");
}
void NullOverlay::Sort(uint32_t*, uint32_t) const {
// TODO(b/307482437): Implement.
PERFETTO_FATAL("Not implemented");
}
void NullOverlay::Serialize(StorageProto* storage) const {
auto* null_overlay = storage->set_null_overlay();
non_null_->Serialize(null_overlay->set_bit_vector());
storage_->Serialize(null_overlay->set_storage());
}
} // namespace column
} // namespace trace_processor
} // namespace perfetto