|
22 | 22 |
|
23 | 23 | #include "arrow/adapters/orc/adapter.h" |
24 | 24 | #include "arrow/array/builder_primitive.h" |
| 25 | +#include "arrow/compute/api_scalar.h" |
25 | 26 | #include "arrow/dataset/dataset_internal.h" |
26 | 27 | #include "arrow/dataset/discovery.h" |
27 | 28 | #include "arrow/dataset/file_base.h" |
@@ -1046,5 +1047,136 @@ TEST_P(TestOrcFileFormatScan, CompoundPredicates) { |
1046 | 1047 | ASSERT_EQ(count_rows(fragment), 150); // 50 + 50 + 50 |
1047 | 1048 | } |
1048 | 1049 |
|
| 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 | + |
1049 | 1181 | } // namespace dataset |
1050 | 1182 | } // namespace arrow |
0 commit comments