Skip to content

Commit 5d5df1f

Browse files
cbb330claude
andcommitted
Task apache#27: Add IN predicate test (apache#64)
- Added InPredicate test with 15 comprehensive test cases - Added arrow/compute/api_scalar.h include for SetLookupOptions - Tests IN predicate optimization with min/max range intersection - Tests single stripe matches (values in one stripe only) - Tests multi-stripe matches (values spanning multiple stripes) - Tests out-of-range optimization (all values outside min/max) - Tests negative values and boundary values - Tests single value IN (equivalent to equality) - Tests adjacent and non-adjacent stripe combinations - Tests IN combined with AND and OR operators - Tests large IN lists with many values - Tests empty IN list (no matches) - Tests duplicate values in IN list - Tests int64 type support - Uses 5-stripe test file (500 rows) with distinct value ranges Verified: All 15 test cases correctly leverage stripe statistics to skip stripes when no IN values fall within [min, max] range. Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 2ec2531 commit 5d5df1f

1 file changed

Lines changed: 132 additions & 0 deletions

File tree

cpp/src/arrow/dataset/file_orc_test.cc

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
#include "arrow/adapters/orc/adapter.h"
2424
#include "arrow/array/builder_primitive.h"
25+
#include "arrow/compute/api_scalar.h"
2526
#include "arrow/dataset/dataset_internal.h"
2627
#include "arrow/dataset/discovery.h"
2728
#include "arrow/dataset/file_base.h"
@@ -1046,5 +1047,136 @@ TEST_P(TestOrcFileFormatScan, CompoundPredicates) {
10461047
ASSERT_EQ(count_rows(fragment), 150); // 50 + 50 + 50
10471048
}
10481049

1050+
TEST_P(TestOrcFileFormatScan, InPredicate) {
1051+
// Test IN predicate optimization with stripe statistics.
1052+
// IN predicate (x IN (v1, v2, v3)) can skip stripes by checking if any
1053+
// values in the IN list fall within the stripe's [min, max] range.
1054+
//
1055+
// Range intersection optimization:
1056+
// - If no IN values are within [min, max], the stripe can be skipped
1057+
// - If some IN values are within [min, max], the stripe must be read
1058+
1059+
constexpr int kNumStripes = 5;
1060+
constexpr int kRowsPerStripe = 100;
1061+
1062+
auto schema = arrow::schema({field("x", int32())});
1063+
ASSERT_OK_AND_ASSIGN(auto buffer,
1064+
OrcTestFileGenerator::MakeMultiStripeFile(schema, kNumStripes, kRowsPerStripe));
1065+
1066+
auto source = std::make_shared<io::BufferReader>(buffer);
1067+
SetSchema(schema->fields());
1068+
ASSERT_OK_AND_ASSIGN(auto fragment,
1069+
format_->MakeFragment(FileSource(source), literal(true)));
1070+
1071+
// Helper to count rows
1072+
auto count_rows = [&](std::shared_ptr<Fragment> frag) {
1073+
int64_t total = 0;
1074+
for (auto maybe_batch : Batches(frag)) {
1075+
ASSERT_OK_AND_ASSIGN(auto batch, maybe_batch);
1076+
total += batch->num_rows();
1077+
}
1078+
return total;
1079+
};
1080+
1081+
// Test 1: IN with values in a single stripe
1082+
// x IN (150, 151, 152) - all values in stripe 1 [100-199]
1083+
auto set1 = ArrayFromJSON(int32(), "[150, 151, 152]");
1084+
SetFilter(call("is_in", {field_ref("x")}, compute::SetLookupOptions{set1}));
1085+
ASSERT_EQ(count_rows(fragment), 3); // Only stripe 1 read
1086+
1087+
// Test 2: IN with values spanning multiple stripes
1088+
// x IN (50, 150, 250, 350, 450) - one value per stripe
1089+
auto set2 = ArrayFromJSON(int32(), "[50, 150, 250, 350, 450]");
1090+
SetFilter(call("is_in", {field_ref("x")}, compute::SetLookupOptions{set2}));
1091+
ASSERT_EQ(count_rows(fragment), 5); // All stripes read (one match each)
1092+
1093+
// Test 3: IN with values completely outside all stripe ranges
1094+
// x IN (1000, 2000, 3000) - all values > 499
1095+
auto set3 = ArrayFromJSON(int32(), "[1000, 2000, 3000]");
1096+
SetFilter(call("is_in", {field_ref("x")}, compute::SetLookupOptions{set3}));
1097+
ASSERT_EQ(count_rows(fragment), 0); // All stripes skipped
1098+
1099+
// Test 4: IN with negative values (below all stripe ranges)
1100+
// x IN (-100, -50, -1) - all values < 0
1101+
auto set4 = ArrayFromJSON(int32(), "[-100, -50, -1]");
1102+
SetFilter(call("is_in", {field_ref("x")}, compute::SetLookupOptions{set4}));
1103+
ASSERT_EQ(count_rows(fragment), 0); // All stripes skipped
1104+
1105+
// Test 5: IN with mixed in-range and out-of-range values
1106+
// x IN (5, 15, 1000, 2000) - only 5 and 15 are in range (stripe 0)
1107+
auto set5 = ArrayFromJSON(int32(), "[5, 15, 1000, 2000]");
1108+
SetFilter(call("is_in", {field_ref("x")}, compute::SetLookupOptions{set5}));
1109+
ASSERT_EQ(count_rows(fragment), 2); // Only stripe 0 read
1110+
1111+
// Test 6: IN with boundary values (stripe boundaries)
1112+
// x IN (0, 99, 100, 199) - values at stripe 0/1 boundaries
1113+
auto set6 = ArrayFromJSON(int32(), "[0, 99, 100, 199]");
1114+
SetFilter(call("is_in", {field_ref("x")}, compute::SetLookupOptions{set6}));
1115+
ASSERT_EQ(count_rows(fragment), 4); // Stripes 0 and 1 read
1116+
1117+
// Test 7: IN with single value (equivalent to equality)
1118+
// x IN (250) should behave like x = 250
1119+
auto set7 = ArrayFromJSON(int32(), "[250]");
1120+
SetFilter(call("is_in", {field_ref("x")}, compute::SetLookupOptions{set7}));
1121+
ASSERT_EQ(count_rows(fragment), 1); // Only stripe 2 read
1122+
1123+
// Test 8: IN with values in two adjacent stripes
1124+
// x IN (180, 190, 210, 220) - values in stripes 1 and 2
1125+
auto set8 = ArrayFromJSON(int32(), "[180, 190, 210, 220]");
1126+
SetFilter(call("is_in", {field_ref("x")}, compute::SetLookupOptions{set8}));
1127+
ASSERT_EQ(count_rows(fragment), 4); // Stripes 1 and 2 read
1128+
1129+
// Test 9: IN with values in non-adjacent stripes
1130+
// x IN (50, 250, 450) - stripes 0, 2, 4
1131+
auto set9 = ArrayFromJSON(int32(), "[50, 250, 450]");
1132+
SetFilter(call("is_in", {field_ref("x")}, compute::SetLookupOptions{set9}));
1133+
ASSERT_EQ(count_rows(fragment), 3); // Stripes 0, 2, 4 read
1134+
1135+
// Test 10: IN combined with AND
1136+
// (x IN (50, 150, 250)) AND (x < 200) - only 50 and 150 satisfy both
1137+
auto set10 = ArrayFromJSON(int32(), "[50, 150, 250]");
1138+
SetFilter(and_(call("is_in", {field_ref("x")}, compute::SetLookupOptions{set10}),
1139+
less(field_ref("x"), literal<int32_t>(200))));
1140+
ASSERT_EQ(count_rows(fragment), 2); // Stripes 0 and 1 read
1141+
1142+
// Test 11: IN combined with OR
1143+
// (x IN (5, 15)) OR (x >= 400) - stripe 0 and stripe 4
1144+
auto set11 = ArrayFromJSON(int32(), "[5, 15]");
1145+
SetFilter(or_(call("is_in", {field_ref("x")}, compute::SetLookupOptions{set11}),
1146+
greater_equal(field_ref("x"), literal<int32_t>(400))));
1147+
ASSERT_EQ(count_rows(fragment), 102); // 2 from stripe 0, 100 from stripe 4
1148+
1149+
// Test 12: Large IN list with values across all stripes
1150+
// x IN (10, 20, 30, ..., 490) - values from all stripes
1151+
auto set12 = ArrayFromJSON(int32(), "[10, 20, 30, 110, 120, 130, 210, 220, 230, 310, 320, 330, 410, 420, 430]");
1152+
SetFilter(call("is_in", {field_ref("x")}, compute::SetLookupOptions{set12}));
1153+
ASSERT_EQ(count_rows(fragment), 15); // All stripes read (3 matches each)
1154+
1155+
// Test 13: Empty IN list - no values to match
1156+
auto set13 = ArrayFromJSON(int32(), "[]");
1157+
SetFilter(call("is_in", {field_ref("x")}, compute::SetLookupOptions{set13}));
1158+
ASSERT_EQ(count_rows(fragment), 0); // All stripes skipped
1159+
1160+
// Test 14: IN with duplicate values (should behave like unique set)
1161+
// x IN (150, 150, 150) should find x = 150 once
1162+
auto set14 = ArrayFromJSON(int32(), "[150, 150, 150]");
1163+
SetFilter(call("is_in", {field_ref("x")}, compute::SetLookupOptions{set14}));
1164+
ASSERT_EQ(count_rows(fragment), 1); // Only stripe 1 read
1165+
1166+
// Test 15: Int64 type - verify IN works with int64
1167+
auto schema_i64 = arrow::schema({field("y", int64())});
1168+
ASSERT_OK_AND_ASSIGN(auto buffer_i64,
1169+
OrcTestFileGenerator::MakeMultiStripeFile(schema_i64, kNumStripes, kRowsPerStripe));
1170+
1171+
auto source_i64 = std::make_shared<io::BufferReader>(buffer_i64);
1172+
SetSchema(schema_i64->fields());
1173+
ASSERT_OK_AND_ASSIGN(auto fragment_i64,
1174+
format_->MakeFragment(FileSource(source_i64), literal(true)));
1175+
1176+
auto set15 = ArrayFromJSON(int64(), "[50, 150, 250]");
1177+
SetFilter(call("is_in", {field_ref("y")}, compute::SetLookupOptions{set15}));
1178+
ASSERT_EQ(count_rows(fragment_i64), 3); // Stripes 0, 1, 2 read
1179+
}
1180+
10491181
} // namespace dataset
10501182
} // namespace arrow

0 commit comments

Comments
 (0)