This is an automated email from the ASF dual-hosted git repository.
Gabriel39 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/master by this push:
new ec716229c84 [fix](be) Fix ORC V2 pushdown correctness and fallback
(#65562)
ec716229c84 is described below
commit ec716229c84bfd9a67c18c2e61913f651e02e1c4
Author: Chenjunwei <[email protected]>
AuthorDate: Wed Jul 15 11:23:23 2026 +0800
[fix](be) Fix ORC V2 pushdown correctness and fallback (#65562)
---
be/src/format_v2/orc/orc_reader.cpp | 78 +++-
be/src/format_v2/orc/orc_search_argument.cpp | 589 +++++++++++++-----------
be/src/format_v2/orc/orc_search_argument.h | 7 +-
be/test/format_v2/orc/orc_reader_test.cpp | 646 ++++++++++++++++++++++++++-
4 files changed, 1047 insertions(+), 273 deletions(-)
diff --git a/be/src/format_v2/orc/orc_reader.cpp
b/be/src/format_v2/orc/orc_reader.cpp
index b728c6fbedb..9a2aeb53897 100644
--- a/be/src/format_v2/orc/orc_reader.cpp
+++ b/be/src/format_v2/orc/orc_reader.cpp
@@ -30,6 +30,7 @@
#include <list>
#include <map>
#include <memory>
+#include <new>
#include <optional>
#include <orc/OrcFile.hh>
#include <orc/Vector.hh>
@@ -47,6 +48,7 @@
#include "common/config.h"
#include "common/consts.h"
#include "common/exception.h"
+#include "common/logging.h"
#include "core/block/block.h"
#include "core/column/column_nullable.h"
#include "core/column/column_string.h"
@@ -78,6 +80,7 @@
#include "storage/index/zone_map/zone_map_index.h"
#include "storage/segment/condition_cache.h"
#include "storage/utils.h"
+#include "util/debug_points.h"
#include "util/slice.h"
#include "util/timezone_utils.h"
@@ -139,6 +142,27 @@ bool is_orc_stop(const io::IOContext* io_ctx, const
std::exception& e) {
return io_ctx != nullptr && io_ctx->should_stop &&
std::string_view(e.what()) == "stop";
}
+Status fall_back_without_orc_sarg(const char* operation, const std::string&
file_path,
+ ::orc::RowReaderOptions* row_reader_options,
+ const std::exception& e) {
+ LOG(WARNING) << "Failed to " << operation << " ORC search argument for
file " << file_path
+ << "; falling back to scan without SARG: " << e.what();
+ row_reader_options->searchArgument(nullptr);
+ return Status::OK();
+}
+
+Status handle_orc_sarg_exception(const char* operation, const std::string&
file_path,
+ ::orc::RowReaderOptions* row_reader_options,
const Exception& e) {
+ if (e.code() == ErrorCode::MEM_ALLOC_FAILED) {
+ return Status::MemoryLimitExceeded("Failed to {} ORC search argument:
{}", operation,
+ e.what());
+ }
+ if (e.code() == ErrorCode::MEM_LIMIT_EXCEEDED) {
+ return e.to_status();
+ }
+ return fall_back_without_orc_sarg(operation, file_path,
row_reader_options, e);
+}
+
bool is_hour_offset_timezone(std::string_view timezone) {
return timezone.size() == 6 && (timezone[0] == '+' || timezone[0] == '-')
&&
std::isdigit(static_cast<unsigned char>(timezone[1])) &&
@@ -605,8 +629,12 @@ bool build_zone_map_from_orc_statistics(const ::orc::Type&
type,
return set_string_zone_map(type, statistics, zone_map);
case ::orc::TypeKind::DATE:
return set_date_zone_map(statistics, zone_map);
- case ::orc::TypeKind::TIMESTAMP:
- return set_timestamp_zone_map(statistics, timezone, false, zone_map);
+ case ::orc::TypeKind::TIMESTAMP: {
+ // ORC stores timestamp statistics as wall-clock values in UTC
coordinates. Restore them
+ // with UTC so the session timezone does not shift the civil time.
+ static const auto utc_time_zone = cctz::utc_time_zone();
+ return set_timestamp_zone_map(statistics, utc_time_zone, false,
zone_map);
+ }
case ::orc::TypeKind::TIMESTAMP_INSTANT:
return set_timestamp_zone_map(statistics, timezone,
enable_mapping_timestamp_tz, zone_map);
case ::orc::TypeKind::DECIMAL:
@@ -720,6 +748,7 @@ struct OrcReaderScanState {
bool enable_filter_by_min_max = true;
bool orc_lazy_read_enabled = false;
bool orc_lazy_selection_valid = false;
+ OrcSargBuildOptions sarg_build_options;
std::vector<StripeRange> selected_stripe_ranges;
size_t current_stripe_range = 0;
@@ -853,6 +882,10 @@ Status OrcReader::init(RuntimeState* state) {
_state->enable_filter_by_min_max =
state->query_options().enable_orc_filter_by_min_max;
_state->timezone = state->timezone();
_state->timezone_obj = state->timezone_obj();
+ if (state->query_options().__isset.max_pushdown_conditions_per_column)
{
+ _state->sarg_build_options.max_pushdown_conditions_per_column =
+ state->query_options().max_pushdown_conditions_per_column;
+ }
}
::orc::ReaderOptions options;
@@ -1350,18 +1383,26 @@ Status
OrcReader::_init_search_argument_from_local_filters() {
if (conjunct == nullptr) {
continue;
}
- has_pushdown =
- build_orc_search_argument(*_request, *_state->root_type,
_state->timezone_obj,
- conjunct->root(), builder) ||
- has_pushdown;
+ has_pushdown = build_orc_search_argument(
+ *_request, *_state->root_type,
_state->timezone_obj,
+ _state->sarg_build_options,
conjunct->root(), builder) ||
+ has_pushdown;
}
if (!has_pushdown) {
return Status::OK();
}
builder->end();
+
DBUG_EXECUTE_IF("OrcReader._init_search_argument_from_local_filters.inject_failure",
+ DBUG_RUN_CALLBACK());
_state->row_reader_options.searchArgument(builder->build());
+ } catch (const Exception& e) {
+ return handle_orc_sarg_exception("build", _file_description->path,
+ &_state->row_reader_options, e);
+ } catch (const std::bad_alloc& e) {
+ return Status::MemoryLimitExceeded("Failed to build ORC search
argument: {}", e.what());
} catch (const std::exception& e) {
- return Status::InternalError("Failed to build ORC search argument:
{}", e.what());
+ return fall_back_without_orc_sarg("build", _file_description->path,
+ &_state->row_reader_options, e);
}
return Status::OK();
}
@@ -1421,6 +1462,8 @@ void OrcReader::_apply_split_range() {
// ORC RowReader ranges are continuous, so non-adjacent surviving stripes are
// compacted into separate ranges.
+// Keep stripe selection and range state transitions atomic.
+// NOLINTNEXTLINE(readability-function-size)
Status OrcReader::_select_stripe_ranges_by_statistics() {
_state->selected_stripe_ranges.clear();
_state->current_stripe_range = 0;
@@ -1439,8 +1482,20 @@ Status OrcReader::_select_stripe_ranges_by_statistics() {
std::vector<int> sarg_needed_stripes;
try {
sarg_needed_stripes =
_state->reader->getNeedReadStripes(_state->row_reader_options);
+ } catch (const Exception& e) {
+ if (is_orc_stop(_io_ctx.get(), e)) {
+ return Status::EndOfFile("stop");
+ }
+ return handle_orc_sarg_exception("evaluate", _file_description->path,
+ &_state->row_reader_options, e);
+ } catch (const std::bad_alloc& e) {
+ return Status::MemoryLimitExceeded("Failed to evaluate ORC search
argument: {}", e.what());
} catch (const std::exception& e) {
- return Status::InternalError("Failed to evaluate ORC search argument:
{}", e.what());
+ if (is_orc_stop(_io_ctx.get(), e)) {
+ return Status::EndOfFile("stop");
+ }
+ return fall_back_without_orc_sarg("evaluate", _file_description->path,
+ &_state->row_reader_options, e);
}
std::vector<uint64_t> selected_stripes;
@@ -1941,6 +1996,8 @@ Status OrcReader::get_block(Block* file_block, size_t*
rows, bool* eof) {
return Status::OK();
}
+// COUNT and MIN/MAX share selected-stripe metadata and the same conservative
fallback contract.
+// NOLINTNEXTLINE(readability-function-size)
Status OrcReader::get_aggregate_result(const format::FileAggregateRequest&
request,
format::FileAggregateResult* result) {
DORIS_CHECK(result != nullptr);
@@ -2049,6 +2106,11 @@ Status OrcReader::get_aggregate_result(const
format::FileAggregateRequest& reque
RETURN_IF_ERROR(find_projected_minmax_leaf(*_state->root_type,
request_column.projection,
&leaf_type));
DORIS_CHECK(leaf_type != nullptr);
+ if (leaf_type->getKind() == ::orc::TypeKind::TIMESTAMP &&
+ _state->reader->getWriterVersion() < ::orc::WriterVersion_ORC_135)
{
+ return Status::NotSupported(
+ "ORC TIMESTAMP min/max statistics are unsafe before writer
version ORC-135");
+ }
auto& aggregate_column = result->columns[column_idx];
aggregate_column.projection = request_column.projection;
diff --git a/be/src/format_v2/orc/orc_search_argument.cpp
b/be/src/format_v2/orc/orc_search_argument.cpp
index 4766fa83045..244109c12ce 100644
--- a/be/src/format_v2/orc/orc_search_argument.cpp
+++ b/be/src/format_v2/orc/orc_search_argument.cpp
@@ -29,6 +29,7 @@
#include <orc/sargs/Literal.hh>
#include <orc/sargs/SearchArgument.hh>
#include <string>
+#include <unordered_map>
#include <utility>
#include <vector>
@@ -46,6 +47,9 @@
#include "exprs/vslot_ref.h"
#include "exprs/vtopn_pred.h"
#include "format_v2/timestamp_statistics.h"
+#ifdef BE_TEST
+#include "util/debug_points.h"
+#endif
namespace doris::format::orc {
namespace {
@@ -95,6 +99,24 @@ struct OrcSargComparison {
TExprOpcode::type normalized_op = TExprOpcode::INVALID_OPCODE;
};
+enum class OrcSargNodeKind {
+ AND,
+ OR,
+ NOT,
+ LESS_THAN,
+ LESS_THAN_EQUALS,
+ EQUALS,
+ IN,
+ IS_NULL,
+};
+
+struct OrcSargNode {
+ OrcSargNodeKind kind;
+ std::optional<OrcSargColumn> column;
+ std::vector<::orc::Literal> literals;
+ std::vector<OrcSargNode> children;
+};
+
std::optional<format::LocalColumnId> file_column_id_for_slot_position(
const format::FileScanRequest& request, int slot_column_id) {
if (slot_column_id < 0) {
@@ -743,12 +765,8 @@ bool is_null_literal(const VExprSPtr& literal_expr) {
literal->get_column_ptr()->is_null_at(0);
}
-// Buildability is checked before emission so the builder path can assume the
-// expression shape was validated and use DORIS_CHECK for impossible branches.
-bool can_build_search_argument(const format::FileScanRequest& request, const
::orc::Type& root_type,
- const cctz::time_zone& timezone, const
VExprSPtr& expr);
-
-std::optional<VExprSPtr> expression_for_search_argument(const VExprSPtr& expr)
{
+std::optional<VExprSPtr> expression_for_search_argument(const VExprSPtr& expr,
+ const
OrcSargBuildOptions& options) {
if (expr == nullptr) {
return std::nullopt;
}
@@ -766,6 +784,10 @@ std::optional<VExprSPtr>
expression_for_search_argument(const VExprSPtr& expr) {
}
if (const auto* direct_in = dynamic_cast<const
VDirectInPredicate*>(impl.get());
direct_in != nullptr) {
+ const auto filter_size = direct_in->get_set_func()->size();
+ if (filter_size == 0 || filter_size >
options.max_pushdown_conditions_per_column) {
+ return std::nullopt;
+ }
VExprSPtr in_expr;
if (!direct_in->get_slot_in_expr(in_expr)) {
return std::nullopt;
@@ -965,6 +987,9 @@ std::optional<OrcSargComparisonLiteral>
make_comparison_literal_for_sarg(
std::optional<std::vector<::orc::Literal>> make_in_literals_for_sarg(
const OrcSargColumn& column, const VExprSPtr& source_expr,
const std::vector<VExprSPtr>& children, const cctz::time_zone&
timezone) {
+#ifdef BE_TEST
+ DBUG_EXECUTE_IF("OrcSearchArgument.make_in_literals_for_sarg",
DBUG_RUN_CALLBACK());
+#endif
if (children.size() < 2) {
return std::nullopt;
}
@@ -1080,35 +1105,6 @@ std::optional<OrcSargComparison>
sarg_comparison_for_expr(const format::FileScan
};
}
-bool can_build_slot_literal_predicate(const format::FileScanRequest& request,
- const ::orc::Type& root_type, const
cctz::time_zone& timezone,
- const VExprSPtr& expr) {
- if (expr == nullptr || expr->children().size() != 2) {
- return false;
- }
- return sarg_comparison_for_expr(request, root_type, timezone,
expr).has_value();
-}
-
-bool can_build_in_predicate(const format::FileScanRequest& request, const
::orc::Type& root_type,
- const cctz::time_zone& timezone, const VExprSPtr&
expr) {
- if (expr == nullptr || expr->children().size() < 2) {
- return false;
- }
- const auto sarg_column =
- sarg_column_for_slot_or_safe_cast(request, root_type,
expr->children()[0]);
- if (!sarg_column.has_value()) {
- return false;
- }
- return make_in_literals_for_sarg(*sarg_column, expr->children()[0],
expr->children(), timezone)
- .has_value();
-}
-
-bool can_build_is_null_predicate(const format::FileScanRequest& request,
- const ::orc::Type& root_type, const
VExprSPtr& expr) {
- return expr != nullptr && expr->children().size() == 1 &&
- sarg_column_for_slot_or_safe_cast(request, root_type,
expr->children()[0]).has_value();
-}
-
std::optional<OrcSargColumn> sarg_column_for_null_safe_equal_null(
const format::FileScanRequest& request, const ::orc::Type& root_type,
const VExprSPtr& expr) {
@@ -1170,263 +1166,356 @@ std::optional<OrcSargComparison>
sarg_comparison_for_null_safe_equal_literal(
return comparison_if_other_child_is_non_null_literal(1, 0);
}
-bool can_build_null_safe_equal_predicate(const format::FileScanRequest&
request,
- const ::orc::Type& root_type,
- const cctz::time_zone& timezone,
const VExprSPtr& expr) {
- return sarg_column_for_null_safe_equal_null(request, root_type,
expr).has_value() ||
- sarg_comparison_for_null_safe_equal_literal(request, root_type,
timezone, expr)
- .has_value();
-}
-
-bool contains_null_safe_equal(const VExprSPtr& expr) {
- const auto sarg_expr = expression_for_search_argument(expr);
- if (!sarg_expr.has_value() || *sarg_expr == nullptr) {
- return false;
- }
- if (sarg_expr->get() != expr.get()) {
- return contains_null_safe_equal(*sarg_expr);
- }
- if ((*sarg_expr)->op() == TExprOpcode::EQ_FOR_NULL) {
- return true;
- }
- return std::ranges::any_of((*sarg_expr)->children(),
contains_null_safe_equal);
+OrcSargNode make_not_node(OrcSargNode child) {
+ std::vector<OrcSargNode> children;
+ children.push_back(std::move(child));
+ return OrcSargNode {
+ .kind = OrcSargNodeKind::NOT,
+ .column = std::nullopt,
+ .literals = {},
+ .children = std::move(children),
+ };
}
-bool can_build_search_argument(const format::FileScanRequest& request, const
::orc::Type& root_type,
- const cctz::time_zone& timezone, const
VExprSPtr& expr) {
- const auto sarg_expr = expression_for_search_argument(expr);
- if (!sarg_expr.has_value()) {
- return false;
- }
- if (*sarg_expr == nullptr) {
- return false;
- }
- if (sarg_expr->get() != expr.get()) {
- return can_build_search_argument(request, root_type, timezone,
*sarg_expr);
- }
-
- switch ((*sarg_expr)->op()) {
- case TExprOpcode::COMPOUND_AND:
- return std::ranges::any_of((*sarg_expr)->children(), [&](const auto&
child) {
- return can_build_search_argument(request, root_type, timezone,
child);
- });
- case TExprOpcode::COMPOUND_OR:
- if (contains_null_safe_equal(*sarg_expr)) {
- return false;
- }
- return !(*sarg_expr)->children().empty() &&
- std::ranges::all_of((*sarg_expr)->children(), [&](const auto&
child) {
- return can_build_search_argument(request, root_type,
timezone, child);
- });
- case TExprOpcode::COMPOUND_NOT:
- if (contains_null_safe_equal(*sarg_expr)) {
- return false;
- }
- return (*sarg_expr)->children().size() == 1 &&
- can_build_search_argument(request, root_type, timezone,
(*sarg_expr)->children()[0]);
+OrcSargNode make_comparison_node(OrcSargComparison comparison) {
+ OrcSargNodeKind kind;
+ bool negate = false;
+ switch (comparison.normalized_op) {
case TExprOpcode::GE:
+ kind = OrcSargNodeKind::LESS_THAN;
+ negate = true;
+ break;
case TExprOpcode::GT:
+ kind = OrcSargNodeKind::LESS_THAN_EQUALS;
+ negate = true;
+ break;
case TExprOpcode::LE:
+ kind = OrcSargNodeKind::LESS_THAN_EQUALS;
+ break;
case TExprOpcode::LT:
+ kind = OrcSargNodeKind::LESS_THAN;
+ break;
case TExprOpcode::EQ:
+ kind = OrcSargNodeKind::EQUALS;
+ break;
case TExprOpcode::NE:
- return (*sarg_expr)->node_type() !=
TExprNodeType::NULL_AWARE_BINARY_PRED &&
- can_build_slot_literal_predicate(request, root_type, timezone,
*sarg_expr);
- case TExprOpcode::EQ_FOR_NULL:
- return can_build_null_safe_equal_predicate(request, root_type,
timezone, *sarg_expr);
- case TExprOpcode::FILTER_IN:
- case TExprOpcode::FILTER_NOT_IN:
- return (*sarg_expr)->node_type() != TExprNodeType::NULL_AWARE_IN_PRED
&&
- can_build_in_predicate(request, root_type, timezone,
*sarg_expr);
- case TExprOpcode::INVALID_OPCODE:
- return (*sarg_expr)->node_type() == TExprNodeType::FUNCTION_CALL &&
- ((*sarg_expr)->fn().name.function_name == "is_null_pred" ||
- (*sarg_expr)->fn().name.function_name == "is_not_null_pred") &&
- can_build_is_null_predicate(request, root_type, *sarg_expr);
+ kind = OrcSargNodeKind::EQUALS;
+ negate = true;
+ break;
default:
- return false;
+ DORIS_CHECK(false) << "Unsupported normalized ORC SARG comparison op "
+ << comparison.normalized_op;
+ __builtin_unreachable();
}
-}
-void build_less_than(const OrcSargColumn& column, const ::orc::Literal&
literal,
- std::unique_ptr<::orc::SearchArgumentBuilder>& builder) {
- builder->lessThan(column.column_id, column.predicate_type, literal);
+ std::vector<::orc::Literal> literals;
+ literals.push_back(std::move(comparison.literal));
+ OrcSargNode node {
+ .kind = kind,
+ .column = comparison.column,
+ .literals = std::move(literals),
+ .children = {},
+ };
+ if (negate) {
+ return make_not_node(std::move(node));
+ }
+ return node;
}
-void build_less_than(const OrcSargComparison& comparison,
- std::unique_ptr<::orc::SearchArgumentBuilder>& builder) {
- build_less_than(comparison.column, comparison.literal, builder);
+OrcSargNode make_in_node(OrcSargColumn column, std::vector<::orc::Literal>
literals) {
+ DORIS_CHECK(!literals.empty());
+ return OrcSargNode {
+ .kind = literals.size() == 1 ? OrcSargNodeKind::EQUALS :
OrcSargNodeKind::IN,
+ .column = column,
+ .literals = std::move(literals),
+ .children = {},
+ };
}
-void build_less_than_equals(const OrcSargColumn& column, const ::orc::Literal&
literal,
- std::unique_ptr<::orc::SearchArgumentBuilder>&
builder) {
- builder->lessThanEquals(column.column_id, column.predicate_type, literal);
+OrcSargNode make_is_null_node(OrcSargColumn column) {
+ return OrcSargNode {
+ .kind = OrcSargNodeKind::IS_NULL,
+ .column = column,
+ .literals = {},
+ .children = {},
+ };
}
-void build_less_than_equals(const OrcSargComparison& comparison,
- std::unique_ptr<::orc::SearchArgumentBuilder>&
builder) {
- build_less_than_equals(comparison.column, comparison.literal, builder);
-}
+class OrcSargCompiler {
+public:
+ OrcSargCompiler(const format::FileScanRequest& request, const ::orc::Type&
root_type,
+ const cctz::time_zone& timezone, const
OrcSargBuildOptions& options)
+ : _request(request), _root_type(root_type), _timezone(timezone),
_options(options) {}
-void build_equals(const OrcSargColumn& column, const ::orc::Literal& literal,
- std::unique_ptr<::orc::SearchArgumentBuilder>& builder) {
- builder->equals(column.column_id, column.predicate_type, literal);
-}
+ std::optional<OrcSargNode> compile(const VExprSPtr& expr) { return
compile_node(expr, false); }
-void build_equals(const OrcSargComparison& comparison,
- std::unique_ptr<::orc::SearchArgumentBuilder>& builder) {
- build_equals(comparison.column, comparison.literal, builder);
-}
+private:
+ std::optional<VExprSPtr> normalized_expression(const VExprSPtr& expr) {
+ if (expr == nullptr) {
+ return std::nullopt;
+ }
+ const auto found = _normalized_expressions.find(expr.get());
+ if (found != _normalized_expressions.end()) {
+ return found->second;
+ }
+ auto normalized = expression_for_search_argument(expr, _options);
+ _normalized_expressions.emplace(expr.get(), normalized);
+ return normalized;
+ }
-void build_comparison_predicate(const format::FileScanRequest& request,
- const ::orc::Type& root_type, const VExprSPtr&
expr,
- const cctz::time_zone& timezone,
- std::unique_ptr<::orc::SearchArgumentBuilder>&
builder) {
- const auto comparison = *sarg_comparison_for_expr(request, root_type,
timezone, expr);
- switch (comparison.normalized_op) {
- case TExprOpcode::GE:
- builder->startNot();
- build_less_than(comparison, builder);
- builder->end();
- return;
- case TExprOpcode::GT:
- builder->startNot();
- build_less_than_equals(comparison, builder);
- builder->end();
- return;
- case TExprOpcode::LE:
- build_less_than_equals(comparison, builder);
- return;
- case TExprOpcode::LT:
- build_less_than(comparison, builder);
- return;
- case TExprOpcode::EQ:
- build_equals(comparison, builder);
- return;
- case TExprOpcode::NE:
- builder->startNot();
- build_equals(comparison, builder);
- builder->end();
- return;
- default:
- DORIS_CHECK(false) << "Unsupported normalized ORC SARG comparison op "
- << comparison.normalized_op;
+ bool contains_null_safe_equal(const VExprSPtr& expr) {
+ const auto normalized = normalized_expression(expr);
+ if (!normalized.has_value() || *normalized == nullptr) {
+ return false;
+ }
+ if (normalized->get() != expr.get()) {
+ return contains_null_safe_equal(*normalized);
+ }
+ if ((*normalized)->op() == TExprOpcode::EQ_FOR_NULL) {
+ return true;
+ }
+ // The probe expression may be complex, but valid IN values are
literals. Avoid walking a
+ // potentially huge literal list only for null-safe-equality metadata.
+ if ((*normalized)->op() == TExprOpcode::FILTER_IN ||
+ (*normalized)->op() == TExprOpcode::FILTER_NOT_IN) {
+ return !(*normalized)->children().empty() &&
+ contains_null_safe_equal((*normalized)->children().front());
+ }
+ return std::ranges::any_of((*normalized)->children(), [this](const
auto& child) {
+ return contains_null_safe_equal(child);
+ });
}
-}
-void build_in_predicate(const format::FileScanRequest& request, const
::orc::Type& root_type,
- const VExprSPtr& expr, const cctz::time_zone& timezone,
- std::unique_ptr<::orc::SearchArgumentBuilder>&
builder) {
- const auto sarg_column =
- *sarg_column_for_slot_or_safe_cast(request, root_type,
expr->children()[0]);
- auto literals = *make_in_literals_for_sarg(sarg_column,
expr->children()[0], expr->children(),
- timezone);
- DORIS_CHECK(!literals.empty());
- if (literals.size() == 1) {
- builder->equals(sarg_column.column_id, sarg_column.predicate_type,
literals.front());
- return;
+ std::optional<OrcSargNode> compile_node(const VExprSPtr& expr, bool
is_negated) {
+ const auto normalized = normalized_expression(expr);
+ if (!normalized.has_value() || *normalized == nullptr) {
+ return std::nullopt;
+ }
+ if (normalized->get() != expr.get()) {
+ return compile_node(*normalized, is_negated);
+ }
+
+ switch ((*normalized)->op()) {
+ case TExprOpcode::COMPOUND_AND:
+ return compile_and(*normalized, is_negated);
+ case TExprOpcode::COMPOUND_OR:
+ return compile_or(*normalized, is_negated);
+ case TExprOpcode::COMPOUND_NOT:
+ return compile_not(*normalized, is_negated);
+ case TExprOpcode::GE:
+ case TExprOpcode::GT:
+ case TExprOpcode::LE:
+ case TExprOpcode::LT:
+ case TExprOpcode::EQ:
+ case TExprOpcode::NE:
+ return compile_comparison(*normalized);
+ case TExprOpcode::EQ_FOR_NULL:
+ return compile_null_safe_equal(*normalized);
+ case TExprOpcode::FILTER_IN:
+ case TExprOpcode::FILTER_NOT_IN:
+ return compile_in(*normalized);
+ case TExprOpcode::INVALID_OPCODE:
+ return compile_is_null(*normalized);
+ default:
+ return std::nullopt;
+ }
}
- builder->in(sarg_column.column_id, sarg_column.predicate_type, literals);
-}
-void build_is_null(const format::FileScanRequest& request, const ::orc::Type&
root_type,
- const VExprSPtr& expr,
std::unique_ptr<::orc::SearchArgumentBuilder>& builder) {
- const auto sarg_column =
- *sarg_column_for_slot_or_safe_cast(request, root_type,
expr->children()[0]);
- builder->isNull(sarg_column.column_id, sarg_column.predicate_type);
-}
+ std::optional<OrcSargNode> compile_and(const VExprSPtr& expr, bool
is_negated) {
+ const auto& source_children = expr->children();
+ if (source_children.empty()) {
+ return std::nullopt;
+ }
+ std::vector<OrcSargNode> children;
+ children.reserve(source_children.size());
+ bool all_children_compiled = true;
+ for (const auto& child : source_children) {
+ auto compiled_child = compile_node(child, is_negated);
+ if (compiled_child.has_value()) {
+ children.push_back(std::move(*compiled_child));
+ } else {
+ all_children_compiled = false;
+ }
+ }
+ // Omitting an AND child weakens the SARG only in positive context.
Under an odd number of
+ // NOTs it would strengthen the SARG and could incorrectly prune
matching stripes.
+ if (children.empty() || (is_negated && !all_children_compiled)) {
+ return std::nullopt;
+ }
+ return OrcSargNode {
+ .kind = OrcSargNodeKind::AND,
+ .column = std::nullopt,
+ .literals = {},
+ .children = std::move(children),
+ };
+ }
-void build_null_safe_equal(const format::FileScanRequest& request, const
::orc::Type& root_type,
- const VExprSPtr& expr, const cctz::time_zone&
timezone,
- std::unique_ptr<::orc::SearchArgumentBuilder>&
builder) {
- const auto sarg_column = sarg_column_for_null_safe_equal_null(request,
root_type, expr);
- if (sarg_column.has_value()) {
- builder->isNull(sarg_column->column_id, sarg_column->predicate_type);
- return;
+ std::optional<OrcSargNode> compile_or(const VExprSPtr& expr, bool
is_negated) {
+ const auto& source_children = expr->children();
+ if (source_children.empty() || contains_null_safe_equal(expr)) {
+ return std::nullopt;
+ }
+ std::vector<OrcSargNode> children;
+ children.reserve(source_children.size());
+ for (const auto& child : source_children) {
+ auto compiled_child = compile_node(child, is_negated);
+ if (!compiled_child.has_value()) {
+ return std::nullopt;
+ }
+ children.push_back(std::move(*compiled_child));
+ }
+ return OrcSargNode {
+ .kind = OrcSargNodeKind::OR,
+ .column = std::nullopt,
+ .literals = {},
+ .children = std::move(children),
+ };
}
- const auto comparison =
- *sarg_comparison_for_null_safe_equal_literal(request, root_type,
timezone, expr);
- build_equals(comparison, builder);
-}
-bool build_search_argument(const format::FileScanRequest& request, const
::orc::Type& root_type,
- const cctz::time_zone& timezone, const VExprSPtr&
expr,
- std::unique_ptr<::orc::SearchArgumentBuilder>&
builder) {
- const auto sarg_expr = expression_for_search_argument(expr);
- if (!sarg_expr.has_value() || *sarg_expr == nullptr) {
- return false;
+ std::optional<OrcSargNode> compile_not(const VExprSPtr& expr, bool
is_negated) {
+ if (expr->children().size() != 1 || contains_null_safe_equal(expr)) {
+ return std::nullopt;
+ }
+ auto child = compile_node(expr->children()[0], !is_negated);
+ if (!child.has_value()) {
+ return std::nullopt;
+ }
+ return make_not_node(std::move(*child));
}
- if (sarg_expr->get() != expr.get()) {
- return build_search_argument(request, root_type, timezone, *sarg_expr,
builder);
+
+ std::optional<OrcSargNode> compile_comparison(const VExprSPtr& expr) const
{
+ if (expr->node_type() == TExprNodeType::NULL_AWARE_BINARY_PRED) {
+ return std::nullopt;
+ }
+ auto comparison = sarg_comparison_for_expr(_request, _root_type,
_timezone, expr);
+ if (!comparison.has_value()) {
+ return std::nullopt;
+ }
+ return make_comparison_node(std::move(*comparison));
}
- if (!can_build_search_argument(request, root_type, timezone, *sarg_expr)) {
- return false;
+
+ std::optional<OrcSargNode> compile_null_safe_equal(const VExprSPtr& expr)
const {
+ const auto column = sarg_column_for_null_safe_equal_null(_request,
_root_type, expr);
+ if (column.has_value()) {
+ return make_is_null_node(*column);
+ }
+ auto comparison =
+ sarg_comparison_for_null_safe_equal_literal(_request,
_root_type, _timezone, expr);
+ if (!comparison.has_value()) {
+ return std::nullopt;
+ }
+ return make_comparison_node(std::move(*comparison));
}
- switch ((*sarg_expr)->op()) {
- case TExprOpcode::COMPOUND_AND:
- builder->startAnd();
- for (const auto& child : (*sarg_expr)->children()) {
- static_cast<void>(build_search_argument(request, root_type,
timezone, child, builder));
+ std::optional<OrcSargNode> compile_in(const VExprSPtr& expr) const {
+ if (expr->node_type() == TExprNodeType::NULL_AWARE_IN_PRED ||
expr->children().size() < 2) {
+ return std::nullopt;
}
- builder->end();
- return true;
- case TExprOpcode::COMPOUND_OR:
- builder->startOr();
- for (const auto& child : (*sarg_expr)->children()) {
- const auto built = build_search_argument(request, root_type,
timezone, child, builder);
- DORIS_CHECK(built);
+ const auto column =
+ sarg_column_for_slot_or_safe_cast(_request, _root_type,
expr->children()[0]);
+ if (!column.has_value()) {
+ return std::nullopt;
}
- builder->end();
- return true;
- case TExprOpcode::COMPOUND_NOT:
- builder->startNot();
- DORIS_CHECK(build_search_argument(request, root_type, timezone,
(*sarg_expr)->children()[0],
- builder));
- builder->end();
- return true;
- case TExprOpcode::GE:
- case TExprOpcode::GT:
- case TExprOpcode::LE:
- case TExprOpcode::LT:
- case TExprOpcode::EQ:
- case TExprOpcode::NE:
- build_comparison_predicate(request, root_type, *sarg_expr, timezone,
builder);
- return true;
- case TExprOpcode::EQ_FOR_NULL:
- build_null_safe_equal(request, root_type, *sarg_expr, timezone,
builder);
- return true;
- case TExprOpcode::FILTER_IN:
- build_in_predicate(request, root_type, *sarg_expr, timezone, builder);
- return true;
- case TExprOpcode::FILTER_NOT_IN:
- builder->startNot();
- build_in_predicate(request, root_type, *sarg_expr, timezone, builder);
- builder->end();
- return true;
- case TExprOpcode::INVALID_OPCODE:
- if ((*sarg_expr)->fn().name.function_name == "is_null_pred") {
- build_is_null(request, root_type, *sarg_expr, builder);
- return true;
+ auto literals = make_in_literals_for_sarg(*column,
expr->children()[0], expr->children(),
+ _timezone);
+ if (!literals.has_value()) {
+ return std::nullopt;
}
- if ((*sarg_expr)->fn().name.function_name == "is_not_null_pred") {
- builder->startNot();
- build_is_null(request, root_type, *sarg_expr, builder);
- builder->end();
- return true;
+ auto node = make_in_node(*column, std::move(*literals));
+ if (expr->op() == TExprOpcode::FILTER_NOT_IN) {
+ node = make_not_node(std::move(node));
}
- return false;
- default:
- return false;
+ return node;
+ }
+
+ std::optional<OrcSargNode> compile_is_null(const VExprSPtr& expr) const {
+ if (expr->node_type() != TExprNodeType::FUNCTION_CALL ||
expr->children().size() != 1) {
+ return std::nullopt;
+ }
+ const auto& function_name = expr->fn().name.function_name;
+ if (function_name != "is_null_pred" && function_name !=
"is_not_null_pred") {
+ return std::nullopt;
+ }
+ const auto column =
+ sarg_column_for_slot_or_safe_cast(_request, _root_type,
expr->children()[0]);
+ if (!column.has_value()) {
+ return std::nullopt;
+ }
+ auto node = make_is_null_node(*column);
+ if (function_name == "is_not_null_pred") {
+ node = make_not_node(std::move(node));
+ }
+ return node;
+ }
+
+ const format::FileScanRequest& _request;
+ const ::orc::Type& _root_type;
+ const cctz::time_zone& _timezone;
+ const OrcSargBuildOptions& _options;
+ std::unordered_map<const VExpr*, std::optional<VExprSPtr>>
_normalized_expressions;
+};
+
+void emit_search_argument(const OrcSargNode& node,
::orc::SearchArgumentBuilder& builder) {
+ switch (node.kind) {
+ case OrcSargNodeKind::AND:
+ DORIS_CHECK(!node.children.empty());
+ builder.startAnd();
+ for (const auto& child : node.children) {
+ emit_search_argument(child, builder);
+ }
+ builder.end();
+ return;
+ case OrcSargNodeKind::OR:
+ DORIS_CHECK(!node.children.empty());
+ builder.startOr();
+ for (const auto& child : node.children) {
+ emit_search_argument(child, builder);
+ }
+ builder.end();
+ return;
+ case OrcSargNodeKind::NOT:
+ DORIS_CHECK(node.children.size() == 1);
+ builder.startNot();
+ emit_search_argument(node.children.front(), builder);
+ builder.end();
+ return;
+ case OrcSargNodeKind::LESS_THAN:
+ DORIS_CHECK(node.column.has_value() && node.literals.size() == 1);
+ builder.lessThan(node.column->column_id, node.column->predicate_type,
+ node.literals.front());
+ return;
+ case OrcSargNodeKind::LESS_THAN_EQUALS:
+ DORIS_CHECK(node.column.has_value() && node.literals.size() == 1);
+ builder.lessThanEquals(node.column->column_id,
node.column->predicate_type,
+ node.literals.front());
+ return;
+ case OrcSargNodeKind::EQUALS:
+ DORIS_CHECK(node.column.has_value() && node.literals.size() == 1);
+ builder.equals(node.column->column_id, node.column->predicate_type,
node.literals.front());
+ return;
+ case OrcSargNodeKind::IN:
+ DORIS_CHECK(node.column.has_value() && node.literals.size() > 1);
+ builder.in(node.column->column_id, node.column->predicate_type,
node.literals);
+ return;
+ case OrcSargNodeKind::IS_NULL:
+ DORIS_CHECK(node.column.has_value() && node.literals.empty());
+ builder.isNull(node.column->column_id, node.column->predicate_type);
+ return;
}
}
} // namespace
bool build_orc_search_argument(const format::FileScanRequest& request, const
::orc::Type& root_type,
- const cctz::time_zone& timezone, const
VExprSPtr& expr,
+ const cctz::time_zone& timezone, const
OrcSargBuildOptions& options,
+ const VExprSPtr& expr,
std::unique_ptr<::orc::SearchArgumentBuilder>&
builder) {
- return build_search_argument(request, root_type, timezone, expr, builder);
+ auto node = OrcSargCompiler(request, root_type, timezone,
options).compile(expr);
+ if (!node.has_value()) {
+ return false;
+ }
+ emit_search_argument(*node, *builder);
+ return true;
}
} // namespace doris::format::orc
diff --git a/be/src/format_v2/orc/orc_search_argument.h
b/be/src/format_v2/orc/orc_search_argument.h
index ad922915e6e..8cf3e23ac2a 100644
--- a/be/src/format_v2/orc/orc_search_argument.h
+++ b/be/src/format_v2/orc/orc_search_argument.h
@@ -31,11 +31,16 @@ class Type;
namespace doris::format::orc {
+struct OrcSargBuildOptions {
+ int max_pushdown_conditions_per_column = 1024;
+};
+
// Lower already-localized Doris file filters to ORC SearchArgument predicates.
// TableColumnMapper owns table-schema -> file-local localization; this module
// owns the ORC-specific type-id/literal lowering needed by the ORC C++
library.
bool build_orc_search_argument(const format::FileScanRequest& request, const
::orc::Type& root_type,
- const cctz::time_zone& timezone, const
VExprSPtr& expr,
+ const cctz::time_zone& timezone, const
OrcSargBuildOptions& options,
+ const VExprSPtr& expr,
std::unique_ptr<::orc::SearchArgumentBuilder>&
builder);
} // namespace doris::format::orc
diff --git a/be/test/format_v2/orc/orc_reader_test.cpp
b/be/test/format_v2/orc/orc_reader_test.cpp
index b98e2bf14e6..6809186f115 100644
--- a/be/test/format_v2/orc/orc_reader_test.cpp
+++ b/be/test/format_v2/orc/orc_reader_test.cpp
@@ -29,17 +29,20 @@
#include <cstring>
#include <filesystem>
#include <fstream>
+#include <functional>
#include <limits>
#include <memory>
#include <optional>
#include <orc/OrcFile.hh>
#include <set>
+#include <stdexcept>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include "common/config.h"
+#include "common/exception.h"
#include "core/assert_cast.h"
#include "core/block/block.h"
#include "core/column/column_array.h"
@@ -77,11 +80,30 @@
#include "runtime/runtime_state.h"
#include "storage/segment/condition_cache.h"
#include "storage/utils.h"
+#include "util/debug_points.h"
#include "util/timezone_utils.h"
namespace doris {
namespace {
+class ScopedDebugPoint {
+public:
+ ScopedDebugPoint(std::string name, std::function<void()> callback)
+ : _name(std::move(name)),
_enable_debug_points(config::enable_debug_points) {
+ config::enable_debug_points = true;
+ DebugPoints::instance()->add_with_callback(_name, std::move(callback));
+ }
+
+ ~ScopedDebugPoint() {
+ DebugPoints::instance()->remove(_name);
+ config::enable_debug_points = _enable_debug_points;
+ }
+
+private:
+ std::string _name;
+ bool _enable_debug_points;
+};
+
std::filesystem::path unique_test_dir(std::string_view prefix) {
static std::atomic<uint64_t> next_dir_id {0};
std::string name(prefix);
@@ -582,9 +604,7 @@ private:
class CompoundPredicateExpr final : public VExpr {
public:
CompoundPredicateExpr(TExprOpcode::type opcode, VExprSPtrs children)
- : VExpr(std::make_shared<DataTypeUInt8>(), false),
- _expr_name(opcode == TExprOpcode::COMPOUND_OR ?
"CompoundOrPredicateExpr"
- :
"CompoundNotPredicateExpr") {
+ : VExpr(std::make_shared<DataTypeUInt8>(), false),
_expr_name("CompoundPredicateExpr") {
_node_type = TExprNodeType::COMPOUND_PRED;
_opcode = opcode;
set_children(std::move(children));
@@ -607,7 +627,7 @@ public:
return Status::OK();
}
- DORIS_CHECK(_opcode == TExprOpcode::COMPOUND_OR);
+ DORIS_CHECK(_opcode == TExprOpcode::COMPOUND_AND || _opcode ==
TExprOpcode::COMPOUND_OR);
DORIS_CHECK(!_children.empty());
std::vector<ColumnPtr> child_columns;
child_columns.reserve(_children.size());
@@ -617,12 +637,17 @@ public:
child_columns.push_back(std::move(child_column));
}
for (size_t row = 0; row < count; ++row) {
- result_data[row] = 0;
+ result_data[row] = _opcode == TExprOpcode::COMPOUND_AND;
for (const auto& child_column : child_columns) {
- if (bool_value(*child_column, row)) {
+ const auto child_value = bool_value(*child_column, row);
+ if (_opcode == TExprOpcode::COMPOUND_OR && child_value) {
result_data[row] = 1;
break;
}
+ if (_opcode == TExprOpcode::COMPOUND_AND && !child_value) {
+ result_data[row] = 0;
+ break;
+ }
}
}
result_column = std::move(result);
@@ -674,6 +699,22 @@ private:
const std::string _expr_name;
};
+class CountingIntHybridSet final : public HybridSet<TYPE_INT> {
+public:
+ using HybridSet<TYPE_INT>::HybridSet;
+
+ HybridSetBase::IteratorBase* begin() override {
+ ++_begin_calls;
+ return HybridSet<TYPE_INT>::begin();
+ }
+
+ void reset_begin_calls() { _begin_calls = 0; }
+ size_t begin_calls() const { return _begin_calls; }
+
+private:
+ size_t _begin_calls = 0;
+};
+
class NullableInt32CastToInt64GreaterThanExpr final : public VExpr {
public:
NullableInt32CastToInt64GreaterThanExpr(int column_id, int64_t value)
@@ -4274,7 +4315,8 @@ void write_two_stripe_constant_date_file(const
std::string& file_path, int64_t f
void write_two_stripe_constant_timestamp_file(const std::string& file_path,
int64_t first_timestamp_second,
- int64_t second_timestamp_second)
{
+ int64_t second_timestamp_second,
+ std::string_view writer_timezone
= "GMT") {
auto type = std::unique_ptr<::orc::Type>(
::orc::Type::buildTypeFromString("struct<timestamp_col:timestamp,payload:string>"));
@@ -4282,6 +4324,7 @@ void write_two_stripe_constant_timestamp_file(const
std::string& file_path,
::orc::WriterOptions options;
options.setCompression(::orc::CompressionKind_NONE);
options.setMemoryPool(::orc::getDefaultPool());
+ options.setTimezoneName(std::string(writer_timezone));
options.setStripeSize(1);
options.setDictionaryKeySizeThreshold(0);
auto writer = ::orc::createWriter(*type, &memory_stream, options);
@@ -4358,6 +4401,40 @@ std::vector<OrcStripeLayout> get_orc_stripe_layout(const
std::string& file_path)
return layout;
}
+bool corrupt_orc_stripe_statistics(const std::string& file_path) {
+ std::ifstream in(file_path, std::ios::binary | std::ios::ate);
+ const auto file_size = in.tellg();
+ if (file_size <= 0) {
+ return false;
+ }
+ in.seekg(0);
+ std::vector<char> data(static_cast<size_t>(file_size));
+ in.read(data.data(), static_cast<std::streamsize>(data.size()));
+ if (!in.good()) {
+ return false;
+ }
+
+ ::orc::ReaderOptions options;
+ options.setMemoryPool(*::orc::getDefaultPool());
+ auto input_stream = std::make_unique<MemoryInputStream>(data.data(),
data.size());
+ auto reader = ::orc::createReader(std::move(input_stream), options);
+ const auto metadata_length = reader->getStripeStatisticsLength();
+ const auto footer_length = reader->getFileFooterLength();
+ const auto postscript_length = reader->getFilePostscriptLength();
+ const auto orc_file_length = reader->getFileLength();
+ const auto trailing_length = metadata_length + footer_length +
postscript_length + 1;
+ if (metadata_length == 0 || orc_file_length != data.size() ||
+ trailing_length > orc_file_length) {
+ return false;
+ }
+
+ const auto metadata_offset = orc_file_length - trailing_length;
+ std::fill_n(data.data() + metadata_offset, metadata_length,
static_cast<char>(0xff));
+ std::ofstream out(file_path, std::ios::binary | std::ios::trunc);
+ out.write(data.data(), static_cast<std::streamsize>(data.size()));
+ return out.good();
+}
+
Block build_file_block(const std::vector<format::ColumnDefinition>& schema) {
Block block;
for (const auto& field : schema) {
@@ -4393,6 +4470,21 @@ format::LocalColumnIndex struct_child_projection(int32_t
root_field_id, int32_t
class NewOrcReaderTest : public testing::Test {
protected:
+ enum class DirectInPredicateShape {
+ ROOT,
+ AND,
+ OR,
+ OR_WITH_NULL_SAFE_EQUAL,
+ };
+
+ struct DirectInScanResult {
+ std::vector<int32_t> ids;
+ size_t materialize_calls = 0;
+ size_t prepare_literals_calls = 0;
+ int64_t filtered_row_groups = 0;
+ int64_t filtered_row_groups_by_min_max = 0;
+ };
+
void SetUp() override {
_test_dir = unique_test_dir("doris_new_orc_reader_test");
std::filesystem::remove_all(_test_dir);
@@ -4447,6 +4539,88 @@ protected:
nullptr, profile,
std::nullopt);
}
+ Status scan_direct_in_filter(const std::string& file_path, int32_t
in_list_size,
+ bool enable_sarg, int
max_pushdown_conditions_per_column,
+ DirectInPredicateShape shape,
DirectInScanResult* result) const {
+ DORIS_CHECK(result != nullptr);
+ *result = {};
+
+ auto reader = create_reader_for_path(file_path);
+ TQueryOptions query_options;
+ query_options.__set_enable_orc_filter_by_min_max(enable_sarg);
+
query_options.__set_max_pushdown_conditions_per_column(max_pushdown_conditions_per_column);
+ RuntimeState state {query_options, TQueryGlobals()};
+ RETURN_IF_ERROR(reader->init(&state));
+
+ std::vector<format::ColumnDefinition> schema;
+ RETURN_IF_ERROR(reader->get_schema(&schema));
+ DORIS_CHECK(schema.size() == 2);
+
+ constexpr int32_t in_list_start = 1000;
+ auto filter = std::make_shared<CountingIntHybridSet>(false);
+ for (int32_t value = in_list_start; value < in_list_start +
in_list_size; ++value) {
+ filter->insert(&value);
+ }
+
+ auto node = make_filter_in_node(TExprNodeType::IN_PRED);
+ auto direct_in = VDirectInPredicate::create_shared(node, filter, true);
+ direct_in->add_child(TableSlotRef::create_shared(0, 0, -1,
schema[0].type, "id"));
+ VExprSPtr predicate = RuntimeFilterExpr::create_shared(node,
direct_in, 0.0, false, 7);
+ if (shape == DirectInPredicateShape::AND) {
+ predicate = std::make_shared<CompoundPredicateExpr>(
+ TExprOpcode::COMPOUND_AND,
+ VExprSPtrs {std::move(predicate),
+
std::make_shared<NullableInt32GreaterThanExpr>(0, 500)});
+ } else if (shape == DirectInPredicateShape::OR) {
+ predicate = std::make_shared<CompoundPredicateExpr>(
+ TExprOpcode::COMPOUND_OR,
+ VExprSPtrs {std::move(predicate),
+ std::make_shared<NullableInt32LessThanExpr>(0,
0)});
+ } else if (shape == DirectInPredicateShape::OR_WITH_NULL_SAFE_EQUAL) {
+ predicate = std::make_shared<CompoundPredicateExpr>(
+ TExprOpcode::COMPOUND_OR,
+ VExprSPtrs {std::move(predicate),
+
std::make_shared<NullableInt32NullSafeEqualsLiteralExpr>(0, -1,
+
false)});
+ } else {
+ DORIS_CHECK(shape == DirectInPredicateShape::ROOT);
+ }
+ auto context = VExprContext::create_shared(std::move(predicate));
+ RETURN_IF_ERROR(context->prepare(&state, RowDescriptor()));
+ RETURN_IF_ERROR(context->open(&state));
+
+ auto request = std::make_shared<format::FileScanRequest>();
+ request->predicate_columns = {field_projection(0)};
+ request->conjuncts.push_back(std::move(context));
+
+ ScopedDebugPoint
debug_point("OrcSearchArgument.make_in_literals_for_sarg",
+ [&]() { ++result->prepare_literals_calls;
});
+ filter->reset_begin_calls();
+ RETURN_IF_ERROR(reader->open(request));
+
+ bool eof = false;
+ while (!eof) {
+ Block block = build_file_block({schema[0]});
+ size_t rows = 0;
+ RETURN_IF_ERROR(reader->get_block(&block, &rows, &eof));
+ if (rows == 0) {
+ continue;
+ }
+ const auto& ids_nullable =
+ assert_cast<const
ColumnNullable&>(*block.get_by_position(0).column);
+ const auto& ids = assert_cast<const
ColumnInt32&>(ids_nullable.get_nested_column());
+ for (size_t row = 0; row < rows; ++row) {
+ result->ids.push_back(ids.get_element(row));
+ }
+ }
+
+ result->materialize_calls = filter->begin_calls();
+ result->filtered_row_groups =
reader->reader_statistics().filtered_row_groups;
+ result->filtered_row_groups_by_min_max =
+ reader->reader_statistics().filtered_row_groups_by_min_max;
+ return Status::OK();
+ }
+
std::filesystem::path _test_dir;
std::string _file_path;
};
@@ -4641,13 +4815,48 @@ TEST_F(NewOrcReaderTest,
AggregatePushdownUsesPrunedStripes) {
EXPECT_EQ(reader->reader_statistics().filtered_row_groups, 1);
}
-TEST_F(NewOrcReaderTest, AggregatePushdownTimestampMinMaxUsesSessionTimezone) {
+TEST_F(NewOrcReaderTest,
AggregatePushdownTimestampMinMaxMatchesScanInSessionTimezone) {
const auto multi_stripe_file_path = (_test_dir /
"aggregate_timestamp_timezone.orc").string();
- write_two_stripe_constant_timestamp_file(multi_stripe_file_path, 0, 3600);
+ write_two_stripe_constant_timestamp_file(multi_stripe_file_path,
1609430400, 1609434000,
+ "Asia/Shanghai");
- auto reader = create_reader_for_path(multi_stripe_file_path);
RuntimeState state {TQueryOptions(), TQueryGlobals()};
state.set_timezone("Asia/Shanghai");
+
+ auto scan_reader = create_reader_for_path(multi_stripe_file_path);
+ ASSERT_TRUE(scan_reader->init(&state).ok());
+ std::vector<format::ColumnDefinition> schema;
+ ASSERT_TRUE(scan_reader->get_schema(&schema).ok());
+ ASSERT_EQ(schema.size(), 2);
+ auto scan_request = std::make_shared<format::FileScanRequest>();
+ scan_request->non_predicate_columns = {field_projection(0)};
+ ASSERT_TRUE(scan_reader->open(scan_request).ok());
+
+ std::optional<DateV2Value<DateTimeV2ValueType>> scan_min;
+ std::optional<DateV2Value<DateTimeV2ValueType>> scan_max;
+ size_t scan_rows = 0;
+ bool eof = false;
+ while (!eof) {
+ Block block = build_file_block({schema[0]});
+ size_t rows = 0;
+ ASSERT_TRUE(scan_reader->get_block(&block, &rows, &eof).ok());
+ scan_rows += rows;
+ const auto& nullable = assert_cast<const
ColumnNullable&>(*block.get_by_position(0).column);
+ const auto& values = assert_cast<const
ColumnDateTimeV2&>(nullable.get_nested_column());
+ for (size_t row = 0; row < rows; ++row) {
+ ASSERT_FALSE(nullable.is_null_at(row));
+ const auto value = values.get_element(row);
+ scan_min = !scan_min.has_value() || value < *scan_min ? value :
*scan_min;
+ scan_max = !scan_max.has_value() || *scan_max < value ? value :
*scan_max;
+ }
+ }
+ ASSERT_EQ(scan_rows, 400);
+ ASSERT_TRUE(scan_min.has_value());
+ ASSERT_TRUE(scan_max.has_value());
+ EXPECT_EQ(*scan_min, make_datetime_v2(2021, 1, 1, 0, 0, 0, 123000));
+ EXPECT_EQ(*scan_max, make_datetime_v2(2021, 1, 1, 1, 0, 0, 123000));
+
+ auto reader = create_reader_for_path(multi_stripe_file_path);
ASSERT_TRUE(reader->init(&state).ok());
auto request = std::make_shared<format::FileScanRequest>();
@@ -4665,10 +4874,87 @@ TEST_F(NewOrcReaderTest,
AggregatePushdownTimestampMinMaxUsesSessionTimezone) {
EXPECT_EQ(aggregate_result.count, 400);
EXPECT_TRUE(aggregate_result.columns[0].has_min);
EXPECT_TRUE(aggregate_result.columns[0].has_max);
- EXPECT_EQ(aggregate_result.columns[0].min_value.get<TYPE_DATETIMEV2>(),
- make_datetime_v2(1970, 1, 1, 8, 0, 0, 123000));
- EXPECT_EQ(aggregate_result.columns[0].max_value.get<TYPE_DATETIMEV2>(),
- make_datetime_v2(1970, 1, 1, 9, 0, 0, 123000));
+ EXPECT_EQ(aggregate_result.columns[0].min_value.get<TYPE_DATETIMEV2>(),
*scan_min);
+ EXPECT_EQ(aggregate_result.columns[0].max_value.get<TYPE_DATETIMEV2>(),
*scan_max);
+}
+
+TEST_F(NewOrcReaderTest,
AggregatePushdownTimestampMinMaxFallsBackForPreOrc135Writer) {
+ const auto file_path = find_repo_file(
+
"contrib/apache-orc/java/core/src/test/resources/orc-file-no-timezone.orc");
+ ASSERT_TRUE(std::filesystem::exists(file_path)) << file_path;
+
+ std::ifstream in(file_path, std::ios::binary | std::ios::ate);
+ ASSERT_TRUE(in.good());
+ const auto file_size = in.tellg();
+ ASSERT_GT(file_size, 0);
+ in.seekg(0);
+ std::vector<char> data(static_cast<size_t>(file_size));
+ in.read(data.data(), static_cast<std::streamsize>(data.size()));
+ ASSERT_TRUE(in.good());
+
+ ::orc::ReaderOptions options;
+ options.setMemoryPool(*::orc::getDefaultPool());
+ auto input_stream = std::make_unique<MemoryInputStream>(data.data(),
data.size());
+ auto metadata_reader = ::orc::createReader(std::move(input_stream),
options);
+ ASSERT_EQ(metadata_reader->getWriterVersion(),
::orc::WriterVersion_HIVE_8732);
+ auto stripe_statistics = metadata_reader->getStripeStatistics(0);
+ ASSERT_NE(stripe_statistics, nullptr);
+ const auto* timestamp_statistics = dynamic_cast<const
::orc::TimestampColumnStatistics*>(
+ stripe_statistics->getColumnStatistics(1));
+ ASSERT_NE(timestamp_statistics, nullptr);
+ ASSERT_TRUE(timestamp_statistics->hasMinimum());
+ ASSERT_TRUE(timestamp_statistics->hasMaximum());
+
+ auto reader = create_reader_for_path(file_path.string());
+ RuntimeState state {TQueryOptions(), TQueryGlobals()};
+ state.set_timezone("UTC");
+ ASSERT_TRUE(reader->init(&state).ok());
+
+ std::vector<format::ColumnDefinition> schema;
+ ASSERT_TRUE(reader->get_schema(&schema).ok());
+ ASSERT_EQ(schema.size(), 1);
+ auto request = std::make_shared<format::FileScanRequest>();
+ request->non_predicate_columns = {field_projection(0)};
+ ASSERT_TRUE(reader->open(request).ok());
+
+ format::FileAggregateRequest aggregate_request;
+ aggregate_request.agg_type = TPushAggOp::type::MINMAX;
+ aggregate_request.columns.push_back(
+ {.projection =
format::LocalColumnIndex::top_level(format::LocalColumnId(0))});
+ format::FileAggregateResult aggregate_result;
+ const auto aggregate_status =
+ reader->get_aggregate_result(aggregate_request, &aggregate_result);
+ ASSERT_TRUE(aggregate_status.is<ErrorCode::NOT_IMPLEMENTED_ERROR>()) <<
aggregate_status;
+
+ const auto read_values = [&](format::orc::OrcReader* scan_reader,
+ std::vector<std::string>* values) -> Status {
+ DORIS_CHECK(scan_reader != nullptr);
+ DORIS_CHECK(values != nullptr);
+ bool eof = false;
+ while (!eof) {
+ Block block = build_file_block(schema);
+ size_t rows = 0;
+ RETURN_IF_ERROR(scan_reader->get_block(&block, &rows, &eof));
+ for (size_t row = 0; row < rows; ++row) {
+
values->push_back(schema[0].type->to_string(*block.get_by_position(0).column,
row));
+ }
+ }
+ return Status::OK();
+ };
+
+ std::vector<std::string> fallback_values;
+ ASSERT_TRUE(read_values(reader.get(), &fallback_values).ok());
+
+ auto baseline_reader = create_reader_for_path(file_path.string());
+ ASSERT_TRUE(baseline_reader->init(&state).ok());
+ auto baseline_request = std::make_shared<format::FileScanRequest>();
+ baseline_request->non_predicate_columns = {field_projection(0)};
+ ASSERT_TRUE(baseline_reader->open(baseline_request).ok());
+
+ std::vector<std::string> baseline_values;
+ ASSERT_TRUE(read_values(baseline_reader.get(), &baseline_values).ok());
+ ASSERT_EQ(baseline_values.size(), 2);
+ EXPECT_EQ(fallback_values, baseline_values);
}
TEST_F(NewOrcReaderTest,
AggregatePushdownTimestampInstantMinMaxUsesTimestampTzWhenMapped) {
@@ -6632,6 +6918,99 @@ TEST_F(NewOrcReaderTest,
SargRuntimeFilterWrapperConjunctPrunesStripes) {
EXPECT_EQ(reader->reader_statistics().filtered_group_rows, 200);
}
+TEST_F(NewOrcReaderTest, SargDirectInCompoundMaterializesLiteralsOnce) {
+ const auto multi_stripe_file_path = (_test_dir /
"sarg_direct_in_prepared_once.orc").string();
+ write_multi_stripe_orc_int_file(multi_stripe_file_path);
+ ASSERT_EQ(get_orc_stripe_count(multi_stripe_file_path), 2);
+
+ constexpr int32_t in_list_size = 1024;
+ for (const auto shape : {DirectInPredicateShape::AND,
DirectInPredicateShape::OR}) {
+ SCOPED_TRACE(shape == DirectInPredicateShape::AND ? "AND" : "OR");
+ DirectInScanResult enabled;
+ DirectInScanResult disabled;
+ ASSERT_TRUE(scan_direct_in_filter(multi_stripe_file_path,
in_list_size, true, in_list_size,
+ shape, &enabled)
+ .ok());
+ ASSERT_TRUE(scan_direct_in_filter(multi_stripe_file_path,
in_list_size, false, in_list_size,
+ shape, &disabled)
+ .ok());
+
+ EXPECT_EQ(enabled.materialize_calls, 1);
+ EXPECT_EQ(enabled.prepare_literals_calls, 1);
+ EXPECT_EQ(enabled.filtered_row_groups, 1);
+ EXPECT_EQ(enabled.filtered_row_groups_by_min_max, 1);
+ EXPECT_EQ(disabled.materialize_calls, 0);
+ EXPECT_EQ(disabled.prepare_literals_calls, 0);
+ EXPECT_EQ(disabled.filtered_row_groups, 0);
+ EXPECT_EQ(disabled.filtered_row_groups_by_min_max, 0);
+ ASSERT_EQ(enabled.ids, disabled.ids);
+ ASSERT_EQ(enabled.ids.size(), 200);
+ EXPECT_EQ(enabled.ids.front(), 1000);
+ EXPECT_EQ(enabled.ids.back(), 1199);
+ }
+}
+
+TEST_F(NewOrcReaderTest,
SargDirectInNullSafeOrFallsBackBeforeLiteralConversion) {
+ const auto multi_stripe_file_path = (_test_dir /
"sarg_direct_in_null_safe_or.orc").string();
+ write_multi_stripe_orc_int_file(multi_stripe_file_path);
+ ASSERT_EQ(get_orc_stripe_count(multi_stripe_file_path), 2);
+
+ constexpr int32_t in_list_size = 1024;
+ DirectInScanResult enabled;
+ DirectInScanResult disabled;
+ ASSERT_TRUE(scan_direct_in_filter(multi_stripe_file_path, in_list_size,
true, in_list_size,
+
DirectInPredicateShape::OR_WITH_NULL_SAFE_EQUAL, &enabled)
+ .ok());
+ ASSERT_TRUE(scan_direct_in_filter(multi_stripe_file_path, in_list_size,
false, in_list_size,
+
DirectInPredicateShape::OR_WITH_NULL_SAFE_EQUAL, &disabled)
+ .ok());
+
+ EXPECT_EQ(enabled.materialize_calls, 1);
+ EXPECT_EQ(enabled.prepare_literals_calls, 0);
+ EXPECT_EQ(enabled.filtered_row_groups, 0);
+ EXPECT_EQ(enabled.filtered_row_groups_by_min_max, 0);
+ EXPECT_EQ(disabled.materialize_calls, 0);
+ EXPECT_EQ(disabled.prepare_literals_calls, 0);
+ EXPECT_EQ(disabled.filtered_row_groups, 0);
+ EXPECT_EQ(disabled.filtered_row_groups_by_min_max, 0);
+ ASSERT_EQ(enabled.ids, disabled.ids);
+ ASSERT_EQ(enabled.ids.size(), 200);
+ EXPECT_EQ(enabled.ids.front(), 1000);
+ EXPECT_EQ(enabled.ids.back(), 1199);
+}
+
+TEST_F(NewOrcReaderTest, SargDirectInOverLimitFallsBackBeforeMaterialization) {
+ const auto multi_stripe_file_path = (_test_dir /
"sarg_direct_in_over_limit.orc").string();
+ write_multi_stripe_orc_int_file(multi_stripe_file_path);
+ ASSERT_EQ(get_orc_stripe_count(multi_stripe_file_path), 2);
+
+ constexpr int32_t max_pushdown_conditions_per_column = 1024;
+ constexpr int32_t in_list_size = max_pushdown_conditions_per_column + 1;
+ DirectInScanResult enabled;
+ DirectInScanResult disabled;
+ ASSERT_TRUE(scan_direct_in_filter(multi_stripe_file_path, in_list_size,
true,
+ max_pushdown_conditions_per_column,
+ DirectInPredicateShape::ROOT, &enabled)
+ .ok());
+ ASSERT_TRUE(scan_direct_in_filter(multi_stripe_file_path, in_list_size,
false,
+ max_pushdown_conditions_per_column,
+ DirectInPredicateShape::ROOT, &disabled)
+ .ok());
+
+ EXPECT_EQ(enabled.materialize_calls, 0);
+ EXPECT_EQ(enabled.prepare_literals_calls, 0);
+ EXPECT_EQ(enabled.filtered_row_groups, 0);
+ EXPECT_EQ(enabled.filtered_row_groups_by_min_max, 0);
+ EXPECT_EQ(disabled.materialize_calls, 0);
+ EXPECT_EQ(disabled.prepare_literals_calls, 0);
+ EXPECT_EQ(disabled.filtered_row_groups, 0);
+ EXPECT_EQ(disabled.filtered_row_groups_by_min_max, 0);
+ ASSERT_EQ(enabled.ids, disabled.ids);
+ ASSERT_EQ(enabled.ids.size(), 200);
+ EXPECT_EQ(enabled.ids.front(), 1000);
+ EXPECT_EQ(enabled.ids.back(), 1199);
+}
+
TEST_F(NewOrcReaderTest, SargNullAwareRuntimeFilterDoesNotPruneNullStripe) {
const auto multi_stripe_file_path = (_test_dir /
"sarg_null_aware_runtime_filter.orc").string();
write_two_stripe_orc_nullable_int_file(multi_stripe_file_path);
@@ -6936,6 +7315,113 @@ TEST_F(NewOrcReaderTest,
SargSlotToSlotNullSafeEqualDoesNotPruneStripes) {
EXPECT_EQ(reader->reader_statistics().filtered_group_rows, 0);
}
+TEST_F(NewOrcReaderTest, SargPartialAndRespectsNegationContext) {
+ const auto multi_stripe_file_path = (_test_dir /
"sarg_partial_and_not.orc").string();
+ write_multi_stripe_orc_int_file(multi_stripe_file_path);
+ ASSERT_EQ(get_orc_stripe_count(multi_stripe_file_path), 2);
+
+ auto partial_and = []() -> VExprSPtr {
+ return std::make_shared<CompoundPredicateExpr>(
+ TExprOpcode::COMPOUND_AND,
+ VExprSPtrs {std::make_shared<NullableInt32GreaterThanExpr>(0,
500),
+
std::make_shared<NullableInt32CastToFloatGreaterThanExpr>(0, 1100.5F)});
+ };
+ auto verify = [&](const auto& conjunct_factory, const
std::vector<int32_t>& expected_ids,
+ int64_t expected_filtered_row_groups) {
+ std::array<std::vector<int32_t>, 2> result_ids;
+ for (size_t run = 0; run < result_ids.size(); ++run) {
+ const bool enable_filter_by_min_max = run == 0;
+ auto reader = create_reader_for_path(multi_stripe_file_path);
+ TQueryOptions query_options;
+
query_options.__set_enable_orc_filter_by_min_max(enable_filter_by_min_max);
+ RuntimeState state {query_options, TQueryGlobals()};
+ ASSERT_TRUE(reader->init(&state).ok());
+
+ std::vector<format::ColumnDefinition> schema;
+ ASSERT_TRUE(reader->get_schema(&schema).ok());
+ ASSERT_EQ(schema.size(), 2);
+
+ auto request = std::make_shared<format::FileScanRequest>();
+ request->predicate_columns = {field_projection(0)};
+
request->conjuncts.push_back(VExprContext::create_shared(conjunct_factory()));
+ ASSERT_TRUE(reader->open(request).ok());
+
+ bool eof = false;
+ while (!eof) {
+ Block block = build_file_block({schema[0]});
+ size_t rows = 0;
+ ASSERT_TRUE(reader->get_block(&block, &rows, &eof).ok());
+ if (eof || rows == 0) {
+ continue;
+ }
+ const auto& ids_nullable =
+ assert_cast<const
ColumnNullable&>(*block.get_by_position(0).column);
+ const auto& ids = assert_cast<const
ColumnInt32&>(ids_nullable.get_nested_column());
+ for (size_t row = 0; row < rows; ++row) {
+ ASSERT_FALSE(ids_nullable.is_null_at(row));
+ result_ids[run].push_back(ids.get_element(row));
+ }
+ }
+
+ const auto filtered_row_groups =
+ enable_filter_by_min_max ? expected_filtered_row_groups :
0;
+ EXPECT_EQ(reader->reader_statistics().filtered_row_groups,
filtered_row_groups);
+
EXPECT_EQ(reader->reader_statistics().filtered_row_groups_by_min_max,
+ filtered_row_groups);
+ EXPECT_EQ(reader->reader_statistics().filtered_group_rows,
filtered_row_groups * 200);
+ }
+
+ EXPECT_EQ(result_ids[0], expected_ids);
+ EXPECT_EQ(result_ids[0], result_ids[1]);
+ };
+
+ std::vector<int32_t> expected_not_ids;
+ for (int32_t id = 1; id <= 200; ++id) {
+ expected_not_ids.push_back(id);
+ }
+ for (int32_t id = 1000; id <= 1100; ++id) {
+ expected_not_ids.push_back(id);
+ }
+ verify(
+ [&]() -> VExprSPtr {
+ return
std::make_shared<CompoundPredicateExpr>(TExprOpcode::COMPOUND_NOT,
+ VExprSPtrs
{partial_and()});
+ },
+ expected_not_ids, 0);
+
+ std::vector<int32_t> expected_and_ids;
+ for (int32_t id = 1101; id <= 1199; ++id) {
+ expected_and_ids.push_back(id);
+ }
+ verify(partial_and, expected_and_ids, 1);
+ verify(
+ [&]() -> VExprSPtr {
+ auto inner_not = std::make_shared<CompoundPredicateExpr>(
+ TExprOpcode::COMPOUND_NOT, VExprSPtrs {partial_and()});
+ return
std::make_shared<CompoundPredicateExpr>(TExprOpcode::COMPOUND_NOT,
+ VExprSPtrs
{std::move(inner_not)});
+ },
+ expected_and_ids, 1);
+
+ std::vector<int32_t> expected_nested_ids;
+ expected_nested_ids.insert(expected_nested_ids.end(),
expected_not_ids.begin(),
+ expected_not_ids.begin() + 200);
+ expected_nested_ids.insert(expected_nested_ids.end(),
expected_and_ids.begin(),
+ expected_and_ids.end());
+ verify(
+ [&]() -> VExprSPtr {
+ auto inner_not = std::make_shared<CompoundPredicateExpr>(
+ TExprOpcode::COMPOUND_NOT, VExprSPtrs {partial_and()});
+ auto outer_and = std::make_shared<CompoundPredicateExpr>(
+ TExprOpcode::COMPOUND_AND,
+ VExprSPtrs {std::move(inner_not),
+
std::make_shared<NullableInt32GreaterThanExpr>(0, 500)});
+ return
std::make_shared<CompoundPredicateExpr>(TExprOpcode::COMPOUND_NOT,
+ VExprSPtrs
{std::move(outer_and)});
+ },
+ expected_nested_ids, 0);
+}
+
TEST_F(NewOrcReaderTest, SargNullSafeEqualInsideOrDoesNotPruneStripes) {
const auto multi_stripe_file_path = (_test_dir /
"sarg_null_safe_equal_inside_or.orc").string();
write_multi_stripe_orc_int_file(multi_stripe_file_path);
@@ -7155,6 +7641,138 @@ TEST_F(NewOrcReaderTest,
SargBinaryVarbinaryLiteralConjunctPrunesRowGroups) {
EXPECT_EQ(reader->reader_statistics().filtered_group_rows, 0);
}
+TEST_F(NewOrcReaderTest, SargExceptionsFallBackToResidualScan) {
+ const auto valid_file_path = (_test_dir /
"sarg_build_exception.orc").string();
+ const auto corrupt_statistics_file_path = (_test_dir /
"sarg_corrupt_statistics.orc").string();
+ write_multi_stripe_orc_binary_file(valid_file_path);
+ write_multi_stripe_orc_binary_file(corrupt_statistics_file_path);
+ const auto valid_stripe_layout = get_orc_stripe_layout(valid_file_path);
+ const auto corrupt_statistics_stripe_layout =
+ get_orc_stripe_layout(corrupt_statistics_file_path);
+ ASSERT_EQ(valid_stripe_layout.size(), 2);
+ ASSERT_EQ(corrupt_statistics_stripe_layout.size(), 2);
+ ASSERT_TRUE(corrupt_orc_stripe_statistics(corrupt_statistics_file_path));
+
+ auto scan_first_stripe = [&](const std::string& file_path, const
OrcStripeLayout& stripe,
+ bool enable_filter_by_min_max, bool
add_real_residual_filter,
+ std::vector<std::string>* result_values,
Status* open_status) {
+ auto reader = create_reader_with_range(file_path,
static_cast<int64_t>(stripe.offset),
+
static_cast<int64_t>(stripe.length));
+ TQueryOptions query_options;
+
query_options.__set_enable_orc_filter_by_min_max(enable_filter_by_min_max);
+ RuntimeState state {query_options, TQueryGlobals()};
+ ASSERT_TRUE(reader->init(&state).ok());
+
+ std::vector<format::ColumnDefinition> schema;
+ ASSERT_TRUE(reader->get_schema(&schema).ok());
+ ASSERT_EQ(schema.size(), 2);
+
+ auto request = std::make_shared<format::FileScanRequest>();
+ request->predicate_columns = {field_projection(0)};
+ request->conjuncts.push_back(VExprContext::create_shared(
+ std::make_shared<SargOnlyStringEqualsVarbinaryLiteralExpr>(
+ 0, std::make_shared<DataTypeVarbinary>(), "zzz_1000",
"value")));
+ if (add_real_residual_filter) {
+ request->conjuncts.push_back(
+
VExprContext::create_shared(std::make_shared<NullableStringEqualsExpr>(
+ 0, std::make_shared<DataTypeString>(), "aaa_1050",
"value")));
+ }
+ *open_status = reader->open(request);
+ if (!open_status->ok()) {
+ return;
+ }
+
+ bool eof = false;
+ while (!eof) {
+ Block block = build_file_block({schema[0]});
+ size_t rows = 0;
+ ASSERT_TRUE(reader->get_block(&block, &rows, &eof).ok());
+ if (eof || rows == 0) {
+ continue;
+ }
+ const auto& values_nullable =
+ assert_cast<const
ColumnNullable&>(*block.get_by_position(0).column);
+ const auto& values =
+ assert_cast<const
ColumnString&>(values_nullable.get_nested_column());
+ for (size_t row = 0; row < rows; ++row) {
+ ASSERT_FALSE(values_nullable.is_null_at(row));
+ result_values->push_back(values.get_data_at(row).to_string());
+ }
+ }
+
+ EXPECT_EQ(reader->reader_statistics().filtered_row_groups, 0);
+ EXPECT_EQ(reader->reader_statistics().filtered_row_groups_by_min_max,
0);
+ EXPECT_EQ(reader->reader_statistics().filtered_group_rows, 0);
+ ASSERT_TRUE(reader->close().ok());
+ };
+
+ std::vector<std::string> without_sarg;
+ Status without_sarg_open_status;
+ scan_first_stripe(corrupt_statistics_file_path,
corrupt_statistics_stripe_layout[0], false,
+ false, &without_sarg, &without_sarg_open_status);
+ ASSERT_TRUE(without_sarg_open_status.ok()) << without_sarg_open_status;
+ ASSERT_EQ(without_sarg.size(), 200);
+ EXPECT_EQ(without_sarg.front(), "aaa_1000");
+ EXPECT_EQ(without_sarg.back(), "aaa_1199");
+
+ int injection_count = 0;
+ std::vector<std::string> after_build_failure;
+ Status build_failure_open_status;
+ {
+ ScopedDebugPoint debug_point(
+
"OrcReader._init_search_argument_from_local_filters.inject_failure", [&]() {
+ ++injection_count;
+ throw std::runtime_error("injected ORC SARG build
failure");
+ });
+ scan_first_stripe(valid_file_path, valid_stripe_layout[0], true, false,
+ &after_build_failure, &build_failure_open_status);
+ }
+ EXPECT_EQ(injection_count, 1);
+ ASSERT_TRUE(build_failure_open_status.ok()) << build_failure_open_status;
+ EXPECT_EQ(after_build_failure, without_sarg);
+
+ std::vector<std::string> after_evaluation_failure;
+ Status evaluation_failure_open_status;
+ scan_first_stripe(corrupt_statistics_file_path,
corrupt_statistics_stripe_layout[0], true,
+ false, &after_evaluation_failure,
&evaluation_failure_open_status);
+ ASSERT_TRUE(evaluation_failure_open_status.ok()) <<
evaluation_failure_open_status;
+ EXPECT_EQ(after_evaluation_failure, without_sarg);
+
+ std::vector<std::string> residual_without_sarg;
+ Status residual_without_sarg_open_status;
+ scan_first_stripe(corrupt_statistics_file_path,
corrupt_statistics_stripe_layout[0], false,
+ true, &residual_without_sarg,
&residual_without_sarg_open_status);
+ ASSERT_TRUE(residual_without_sarg_open_status.ok()) <<
residual_without_sarg_open_status;
+ const std::vector<std::string> expected_residual_values = {"aaa_1050"};
+ EXPECT_EQ(residual_without_sarg, expected_residual_values);
+
+ std::vector<std::string> residual_after_evaluation_failure;
+ Status residual_evaluation_failure_open_status;
+ scan_first_stripe(corrupt_statistics_file_path,
corrupt_statistics_stripe_layout[0], true, true,
+ &residual_after_evaluation_failure,
&residual_evaluation_failure_open_status);
+ ASSERT_TRUE(residual_evaluation_failure_open_status.ok())
+ << residual_evaluation_failure_open_status;
+ EXPECT_EQ(residual_after_evaluation_failure, expected_residual_values);
+
+ int memory_failure_injection_count = 0;
+ std::vector<std::string> after_memory_failure;
+ Status memory_failure_open_status;
+ {
+ ScopedDebugPoint debug_point(
+
"OrcReader._init_search_argument_from_local_filters.inject_failure", [&]() {
+ ++memory_failure_injection_count;
+ throw Exception(ErrorCode::MEM_ALLOC_FAILED,
+ "injected ORC SARG allocation failure");
+ });
+ scan_first_stripe(valid_file_path, valid_stripe_layout[0], true, false,
+ &after_memory_failure, &memory_failure_open_status);
+ }
+ EXPECT_EQ(memory_failure_injection_count, 1);
+ EXPECT_TRUE(memory_failure_open_status.is<ErrorCode::MEM_LIMIT_EXCEEDED>())
+ << memory_failure_open_status;
+ EXPECT_TRUE(after_memory_failure.empty());
+}
+
TEST_F(NewOrcReaderTest,
SargBinaryCastFromVarbinaryToStringConjunctPrunesRowGroups) {
const auto multi_stripe_file_path =
(_test_dir / "binary_varbinary_to_string_cast_sarg.orc").string();
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]