Skip to content

Commit 404e5d9

Browse files
authored
perf(predicate): evaluate IN and NOT IN with arrow::compute::is_in (#265)
1 parent eebc096 commit 404e5d9

19 files changed

Lines changed: 1323 additions & 71 deletions

‎src/paimon/CMakeLists.txt‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ set(PAIMON_COMMON_SRCS
125125
common/predicate/like.cpp
126126
common/predicate/literal_converter.cpp
127127
common/predicate/literal.cpp
128+
common/predicate/multi_literals_leaf_function.cpp
128129
common/predicate/not_equal.cpp
129130
common/predicate/not_in.cpp
130131
common/predicate/or.cpp
@@ -609,6 +610,7 @@ if(PAIMON_BUILD_TESTS)
609610
common/options/time_duration_test.cpp
610611
common/predicate/literal_converter_test.cpp
611612
common/predicate/literal_test.cpp
613+
common/predicate/multi_literals_leaf_function_test.cpp
612614
common/predicate/predicate_test.cpp
613615
common/predicate/predicate_utils_test.cpp
614616
common/predicate/predicate_validator_test.cpp

‎src/paimon/common/predicate/and.h‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,17 +49,17 @@ class And : public CompoundFunction {
4949
return instance;
5050
}
5151

52-
Result<std::vector<char>> Test(
53-
const arrow::Array& array,
54-
const std::vector<std::shared_ptr<Predicate>>& children) const override {
52+
Result<std::vector<char>> Test(const arrow::Array& array,
53+
const std::vector<std::shared_ptr<Predicate>>& children,
54+
arrow::MemoryPool* pool) const override {
5555
std::vector<char> is_valid(array.length(), true);
5656
for (const auto& child : children) {
5757
auto child_filter = std::dynamic_pointer_cast<PredicateFilter>(child);
5858
if (!child_filter) {
5959
return Status::Invalid(
6060
fmt::format("child filter {} does not support Test", child->ToString()));
6161
}
62-
PAIMON_ASSIGN_OR_RAISE(std::vector<char> child_valid, child_filter->Test(array));
62+
PAIMON_ASSIGN_OR_RAISE(std::vector<char> child_valid, child_filter->Test(array, pool));
6363
for (size_t i = 0; i < is_valid.size(); i++) {
6464
is_valid[i] = (is_valid[i] & child_valid[i]);
6565
}

‎src/paimon/common/predicate/compound_function.h‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include <vector>
2323

2424
#include "arrow/array/array_base.h"
25+
#include "arrow/type_fwd.h"
2526
#include "paimon/common/data/internal_array.h"
2627
#include "paimon/common/data/internal_row.h"
2728
#include "paimon/predicate/function.h"
@@ -32,9 +33,10 @@ namespace paimon {
3233
class CompoundFunction : public Function {
3334
public:
3435
// input array is the struct array of all fields
35-
virtual Result<std::vector<char>> Test(
36-
const arrow::Array& array,
37-
const std::vector<std::shared_ptr<Predicate>>& children) const = 0;
36+
// `pool` is where any arrow buffer the evaluation allocates comes from, it must not be null.
37+
virtual Result<std::vector<char>> Test(const arrow::Array& array,
38+
const std::vector<std::shared_ptr<Predicate>>& children,
39+
arrow::MemoryPool* pool) const = 0;
3840

3941
virtual Result<bool> Test(const std::shared_ptr<arrow::Schema>& schema, const InternalRow& row,
4042
const std::vector<std::shared_ptr<Predicate>>& children) const = 0;

‎src/paimon/common/predicate/compound_predicate_impl.h‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,9 @@ class CompoundPredicateImpl : public CompoundPredicate, public PredicateFilter {
3232
const std::vector<std::shared_ptr<Predicate>>& children)
3333
: CompoundPredicate(compound_function, children) {}
3434

35-
Result<std::vector<char>> Test(const arrow::Array& array) const override {
36-
return compound_function_.Test(array, children_);
35+
Result<std::vector<char>> Test(const arrow::Array& array,
36+
arrow::MemoryPool* pool) const override {
37+
return compound_function_.Test(array, children_, pool);
3738
}
3839

3940
Result<bool> Test(const std::shared_ptr<arrow::Schema>& schema,

‎src/paimon/common/predicate/leaf_function.h‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,18 @@
2121
#include <vector>
2222

2323
#include "arrow/array/array_base.h"
24+
#include "arrow/type_fwd.h"
2425
#include "paimon/predicate/function.h"
2526
#include "paimon/predicate/literal.h"
2627
#include "paimon/status.h"
2728
namespace paimon {
2829
class LeafFunction : public Function {
2930
public:
3031
// input array is the exact single field array
32+
// `pool` is where any arrow buffer the evaluation allocates comes from, it must not be null.
3133
virtual Result<std::vector<char>> Test(const arrow::Array& array,
32-
const std::vector<Literal>& literals) const = 0;
34+
const std::vector<Literal>& literals,
35+
arrow::MemoryPool* pool) const = 0;
3336

3437
virtual Result<bool> Test(const Literal& value, const std::vector<Literal>& literals) const = 0;
3538

‎src/paimon/common/predicate/leaf_predicate_impl.h‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,16 @@ class LeafPredicateImpl : public LeafPredicate, public PredicateFilter {
4141
return leaf_function_;
4242
}
4343

44-
Result<std::vector<char>> Test(const arrow::Array& array) const override {
44+
Result<std::vector<char>> Test(const arrow::Array& array,
45+
arrow::MemoryPool* pool) const override {
4546
const auto& struct_array = checked_cast<const arrow::StructArray&>(array);
4647
if (field_index_ >= static_cast<int32_t>(struct_array.fields().size())) {
4748
return Status::Invalid(
4849
fmt::format("field index {} exceed field count {} in struct array", field_index_,
4950
struct_array.fields().size()));
5051
}
5152
const auto& field_array = struct_array.field(field_index_);
52-
return leaf_function_.Test(*field_array, literals_);
53+
return leaf_function_.Test(*field_array, literals_, pool);
5354
}
5455

5556
Result<bool> Test(const std::shared_ptr<arrow::Schema>& schema,

‎src/paimon/common/predicate/leaf_unary_function.h‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@
3030
namespace paimon {
3131
class LeafUnaryFunction : public LeafFunction {
3232
public:
33-
Result<std::vector<char>> Test(const arrow::Array& array,
34-
const std::vector<Literal>& literals) const override {
33+
Result<std::vector<char>> Test(const arrow::Array& array, const std::vector<Literal>& literals,
34+
arrow::MemoryPool* pool) const override {
3535
std::vector<char> is_valid(array.length(), false);
3636
PAIMON_ASSIGN_OR_RAISE(
3737
std::vector<Literal> array_values,

‎src/paimon/common/predicate/literal_converter.cpp‎

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,18 @@
2525
#include "arrow/array/array_decimal.h"
2626
#include "arrow/array/array_dict.h"
2727
#include "arrow/array/array_primitive.h"
28+
#include "arrow/array/builder_binary.h"
29+
#include "arrow/array/builder_decimal.h"
30+
#include "arrow/array/builder_primitive.h"
31+
#include "arrow/array/builder_time.h"
32+
#include "arrow/memory_pool.h"
2833
#include "arrow/type.h"
2934
#include "arrow/type_traits.h"
3035
#include "arrow/util/decimal.h"
3136
#include "fmt/format.h"
3237
#include "paimon/common/data/binary_string.h"
3338
#include "paimon/common/data/internal_row.h"
39+
#include "paimon/common/utils/arrow/status_utils.h"
3440
#include "paimon/common/utils/checked_cast.h"
3541
#include "paimon/common/utils/date_time_utils.h"
3642
#include "paimon/common/utils/field_type_utils.h"
@@ -284,4 +290,165 @@ std::vector<Literal> LiteralConverter::GetLiteralFromTimestampArray(const arrow:
284290
}
285291
return literals;
286292
}
293+
294+
arrow::TimeUnit::type LiteralConverter::MinRequiredTimeUnit(const std::vector<Literal>& literals) {
295+
bool needs_micro = false;
296+
for (const auto& literal : literals) {
297+
if (literal.IsNull()) {
298+
continue;
299+
}
300+
const int32_t nano = literal.GetValue<Timestamp>().GetNanoOfMillisecond();
301+
if (nano % 1000 != 0) {
302+
return arrow::TimeUnit::NANO;
303+
}
304+
needs_micro = needs_micro || nano != 0;
305+
}
306+
return needs_micro ? arrow::TimeUnit::MICRO : arrow::TimeUnit::MILLI;
307+
}
308+
309+
namespace {
310+
// Appends every literal, read by `extract`, to a `BuilderType`, keeping a null literal a null.
311+
template <typename BuilderType, typename Extract>
312+
Result<std::shared_ptr<arrow::Array>> BuildArray(const std::vector<Literal>& literals,
313+
Extract extract, arrow::MemoryPool* pool) {
314+
BuilderType builder(pool);
315+
PAIMON_RETURN_NOT_OK_FROM_ARROW(builder.Reserve(static_cast<int64_t>(literals.size())));
316+
for (const auto& literal : literals) {
317+
if (literal.IsNull()) {
318+
PAIMON_RETURN_NOT_OK_FROM_ARROW(builder.AppendNull());
319+
continue;
320+
}
321+
PAIMON_RETURN_NOT_OK_FROM_ARROW(builder.Append(extract(literal)));
322+
}
323+
std::shared_ptr<arrow::Array> array;
324+
PAIMON_RETURN_NOT_OK_FROM_ARROW(builder.Finish(&array));
325+
return array;
326+
}
327+
328+
// Writes the literals to a decimal array of the precision and the scale they carry themselves. One
329+
// array holds one arrow type, so a non null literal is needed to settle it, and every non null
330+
// literal has to carry the same pair: rescaling a value to another scale loses digits or overflows,
331+
// which is not something a conversion decides on its own.
332+
Result<std::shared_ptr<arrow::Array>> BuildDecimalArray(const std::vector<Literal>& literals,
333+
arrow::MemoryPool* pool) {
334+
std::optional<Decimal> typed_value;
335+
for (const auto& literal : literals) {
336+
if (!literal.IsNull()) {
337+
typed_value = literal.GetValue<Decimal>();
338+
break;
339+
}
340+
}
341+
if (typed_value == std::nullopt) {
342+
return Status::Invalid(
343+
"Not support converting literals of DECIMAL type to an arrow array without a non null "
344+
"literal to take the precision and the scale from");
345+
}
346+
int32_t precision = typed_value->Precision();
347+
int32_t scale = typed_value->Scale();
348+
// `arrow::decimal128` checks the precision fatally, `Make` reports it instead.
349+
PAIMON_ASSIGN_OR_RAISE_FROM_ARROW(std::shared_ptr<arrow::DataType> type,
350+
arrow::Decimal128Type::Make(precision, scale));
351+
arrow::Decimal128Builder builder(type, pool);
352+
PAIMON_RETURN_NOT_OK_FROM_ARROW(builder.Reserve(static_cast<int64_t>(literals.size())));
353+
for (const auto& literal : literals) {
354+
if (literal.IsNull()) {
355+
PAIMON_RETURN_NOT_OK_FROM_ARROW(builder.AppendNull());
356+
continue;
357+
}
358+
auto value = literal.GetValue<Decimal>();
359+
if (value.Precision() != precision || value.Scale() != scale) {
360+
return Status::Invalid(fmt::format(
361+
"Not support converting literals of DECIMAL type to an arrow array, {} and {} do "
362+
"not share one precision and scale",
363+
typed_value->ToString(), value.ToString()));
364+
}
365+
PAIMON_RETURN_NOT_OK_FROM_ARROW(
366+
builder.Append(arrow::Decimal128(value.HighBits(), value.LowBits())));
367+
}
368+
std::shared_ptr<arrow::Array> array;
369+
PAIMON_RETURN_NOT_OK_FROM_ARROW(builder.Finish(&array));
370+
return array;
371+
}
372+
373+
// Writes the literals to a timestamp array of the finest time unit that keeps every value, one
374+
// `is_in` can only compare against a column of that very unit. One array holds one arrow type, so
375+
// a non null literal is needed to settle the unit.
376+
Result<std::shared_ptr<arrow::Array>> BuildTimestampArray(const std::vector<Literal>& literals,
377+
arrow::MemoryPool* pool) {
378+
bool has_value = false;
379+
for (const auto& literal : literals) {
380+
if (!literal.IsNull()) {
381+
has_value = true;
382+
break;
383+
}
384+
}
385+
if (!has_value) {
386+
return Status::Invalid(
387+
"Not support converting literals of TIMESTAMP type to an arrow array without a non "
388+
"null literal to take the time unit from");
389+
}
390+
arrow::TimestampBuilder builder(
391+
arrow::timestamp(LiteralConverter::MinRequiredTimeUnit(literals)), pool);
392+
const DateTimeUtils::TimeType time_type = DateTimeUtils::GetTimeTypeFromArrowType(
393+
checked_pointer_cast<arrow::TimestampType>(builder.type()));
394+
PAIMON_RETURN_NOT_OK_FROM_ARROW(builder.Reserve(static_cast<int64_t>(literals.size())));
395+
for (const auto& literal : literals) {
396+
if (literal.IsNull()) {
397+
PAIMON_RETURN_NOT_OK_FROM_ARROW(builder.AppendNull());
398+
continue;
399+
}
400+
PAIMON_RETURN_NOT_OK_FROM_ARROW(builder.Append(
401+
DateTimeUtils::TimestampToInteger(literal.GetValue<Timestamp>(), time_type)));
402+
}
403+
std::shared_ptr<arrow::Array> array;
404+
PAIMON_RETURN_NOT_OK_FROM_ARROW(builder.Finish(&array));
405+
return array;
406+
}
407+
} // namespace
408+
409+
Result<std::shared_ptr<arrow::Array>> LiteralConverter::ConvertLiteralsToArray(
410+
const FieldType& field_type, const std::vector<Literal>& literals, arrow::MemoryPool* pool) {
411+
switch (field_type) {
412+
case FieldType::BOOLEAN:
413+
return BuildArray<arrow::BooleanBuilder>(
414+
literals, [](const Literal& literal) { return literal.GetValue<bool>(); }, pool);
415+
case FieldType::TINYINT:
416+
return BuildArray<arrow::Int8Builder>(
417+
literals, [](const Literal& literal) { return literal.GetValue<int8_t>(); }, pool);
418+
case FieldType::SMALLINT:
419+
return BuildArray<arrow::Int16Builder>(
420+
literals, [](const Literal& literal) { return literal.GetValue<int16_t>(); }, pool);
421+
case FieldType::INT:
422+
return BuildArray<arrow::Int32Builder>(
423+
literals, [](const Literal& literal) { return literal.GetValue<int32_t>(); }, pool);
424+
case FieldType::BIGINT:
425+
return BuildArray<arrow::Int64Builder>(
426+
literals, [](const Literal& literal) { return literal.GetValue<int64_t>(); }, pool);
427+
case FieldType::FLOAT:
428+
return BuildArray<arrow::FloatBuilder>(
429+
literals, [](const Literal& literal) { return literal.GetValue<float>(); }, pool);
430+
case FieldType::DOUBLE:
431+
return BuildArray<arrow::DoubleBuilder>(
432+
literals, [](const Literal& literal) { return literal.GetValue<double>(); }, pool);
433+
case FieldType::DATE:
434+
return BuildArray<arrow::Date32Builder>(
435+
literals, [](const Literal& literal) { return literal.GetValue<int32_t>(); }, pool);
436+
case FieldType::STRING:
437+
return BuildArray<arrow::StringBuilder>(
438+
literals, [](const Literal& literal) { return literal.GetValue<std::string>(); },
439+
pool);
440+
case FieldType::BINARY:
441+
return BuildArray<arrow::BinaryBuilder>(
442+
literals, [](const Literal& literal) { return literal.GetValue<std::string>(); },
443+
pool);
444+
case FieldType::DECIMAL:
445+
return BuildDecimalArray(literals, pool);
446+
case FieldType::TIMESTAMP:
447+
return BuildTimestampArray(literals, pool);
448+
default:
449+
return Status::Invalid(
450+
fmt::format("Not support converting literals of {} type to an arrow array",
451+
FieldTypeUtils::FieldTypeToString(field_type)));
452+
}
453+
}
287454
} // namespace paimon

‎src/paimon/common/predicate/literal_converter.h‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include <vector>
2828

2929
#include "arrow/array/array_dict.h"
30+
#include "arrow/type_fwd.h"
3031
#include "arrow/type_traits.h"
3132
#include "paimon/common/utils/checked_cast.h"
3233
#include "paimon/predicate/literal.h"
@@ -50,6 +51,29 @@ class PAIMON_EXPORT LiteralConverter {
5051
static Result<std::vector<Literal>> ConvertLiteralsFromArray(const arrow::Array& array,
5152
bool own_data);
5253

54+
/// Collects the literals into an arrow array, the reverse of `ConvertLiteralsFromArray`.
55+
///
56+
/// @param field_type The field type shared by every literal, it picks the arrow type.
57+
/// @param literals The literals to convert, a null literal is written as a null, so the result
58+
/// has one entry per literal.
59+
/// @param pool The pool every buffer of the result is allocated from, it must not be null.
60+
/// @return `Status::Invalid` for a field type this does not write, which is every one outside
61+
/// `BOOLEAN`, `TINYINT`, `SMALLINT`, `INT`, `BIGINT`, `FLOAT`, `DOUBLE`, `DATE`,
62+
/// `STRING`, `BINARY`, `DECIMAL` and `TIMESTAMP`. `DECIMAL` is written with the
63+
/// precision and the scale its literals carry, so at least one literal has to be non
64+
/// null and every non null one has to carry the same pair. `TIMESTAMP` is written with
65+
/// the finest time unit that keeps every value, so at least one literal has to be non
66+
/// null to settle the unit.
67+
static Result<std::shared_ptr<arrow::Array>> ConvertLiteralsToArray(
68+
const FieldType& field_type, const std::vector<Literal>& literals, arrow::MemoryPool* pool);
69+
70+
/// The finest time unit any of the non null timestamp literals needs to keep its value, which
71+
/// is the unit `ConvertLiteralsToArray` writes them with. A null literal does not constrain the
72+
/// unit, and literals that are all null leave it at the coarsest one.
73+
///
74+
/// Every literal has to be of `TIMESTAMP` type.
75+
static arrow::TimeUnit::type MinRequiredTimeUnit(const std::vector<Literal>& literals);
76+
5377
static Result<Literal> ConvertLiteralsFromString(const FieldType& type,
5478
const std::string& value_str);
5579

0 commit comments

Comments
 (0)