|
25 | 25 | #include "paimon/core/schema/schema_manager.h" |
26 | 26 | #include "paimon/defs.h" |
27 | 27 | #include "paimon/fs/local/local_file_system.h" |
| 28 | +#include "paimon/predicate/compound_predicate.h" |
| 29 | +#include "paimon/predicate/leaf_predicate.h" |
| 30 | +#include "paimon/predicate/predicate_builder.h" |
28 | 31 | #include "paimon/status.h" |
29 | 32 | #include "paimon/testing/utils/testharness.h" |
30 | 33 |
|
@@ -191,4 +194,99 @@ TEST(InternalReadContext, TestReadWithFieldIdsAndSpecialFields) { |
191 | 194 | } |
192 | 195 | } |
193 | 196 |
|
| 197 | +// Upstream predicates are constructed against the latest table schema. When the query |
| 198 | +// projects a subset of columns, the read schema built inside InternalReadContext::Create |
| 199 | +// lays those columns out at different positions and the original field_index no longer |
| 200 | +// matches. The remapping path in Create() must rewrite each leaf predicate so its |
| 201 | +// field_index points to the column's position in the projected read schema. |
| 202 | +TEST(InternalReadContext, TestPredicateFieldIdxRemappedWhenProjected) { |
| 203 | + std::string path = paimon::test::GetDataDir() + "/orc/append_09.db/append_09"; |
| 204 | + ReadContextBuilder context_builder(path); |
| 205 | + // read_schema lays out (f3, f0) — f3 ends up at position 0, f0 at position 1, while |
| 206 | + // the latest table schema has them at 3 and 0 respectively. |
| 207 | + context_builder.SetReadSchema({"f3", "f0"}); |
| 208 | + // Predicate was constructed against the latest schema, so f3 carries field_index 3. |
| 209 | + auto predicate = |
| 210 | + PredicateBuilder::Equal(/*field_index=*/3, "f3", FieldType::DOUBLE, Literal(1.5)); |
| 211 | + context_builder.SetPredicate(predicate); |
| 212 | + ASSERT_OK_AND_ASSIGN(auto read_context, context_builder.Finish()); |
| 213 | + SchemaManager schema_manager(std::make_shared<LocalFileSystem>(), read_context->GetPath()); |
| 214 | + ASSERT_OK_AND_ASSIGN(auto table_schema, schema_manager.ReadSchema(0)); |
| 215 | + ASSERT_OK_AND_ASSIGN(auto internal_context, |
| 216 | + InternalReadContext::Create(std::move(read_context), table_schema, |
| 217 | + table_schema->Options())); |
| 218 | + auto leaf = std::dynamic_pointer_cast<LeafPredicate>(internal_context->GetPredicate()); |
| 219 | + ASSERT_NE(leaf, nullptr); |
| 220 | + EXPECT_EQ(leaf->FieldName(), "f3"); |
| 221 | + // f3 is the first column in the projected read schema. |
| 222 | + EXPECT_EQ(leaf->FieldIndex(), 0); |
| 223 | +} |
| 224 | + |
| 225 | +// When the predicate already aligns with the read schema, remapping should be a no-op |
| 226 | +// and return the original shared_ptr without reconstructing the leaf. |
| 227 | +TEST(InternalReadContext, TestPredicateUnchangedWhenAligned) { |
| 228 | + std::string path = paimon::test::GetDataDir() + "/orc/append_09.db/append_09"; |
| 229 | + ReadContextBuilder context_builder(path); |
| 230 | + // No projection -> read schema matches the latest schema: f0(0), f1(1), f2(2), f3(3). |
| 231 | + auto predicate = |
| 232 | + PredicateBuilder::Equal(/*field_index=*/3, "f3", FieldType::DOUBLE, Literal(1.5)); |
| 233 | + context_builder.SetPredicate(predicate); |
| 234 | + ASSERT_OK_AND_ASSIGN(auto read_context, context_builder.Finish()); |
| 235 | + SchemaManager schema_manager(std::make_shared<LocalFileSystem>(), read_context->GetPath()); |
| 236 | + ASSERT_OK_AND_ASSIGN(auto table_schema, schema_manager.ReadSchema(0)); |
| 237 | + ASSERT_OK_AND_ASSIGN(auto internal_context, |
| 238 | + InternalReadContext::Create(std::move(read_context), table_schema, |
| 239 | + table_schema->Options())); |
| 240 | + // shared_ptr equality: remap returned the input unchanged. |
| 241 | + EXPECT_EQ(predicate.get(), internal_context->GetPredicate().get()); |
| 242 | +} |
| 243 | + |
| 244 | +// CompoundPredicate is recursively remapped: every nested leaf must point to the |
| 245 | +// column's position in the projected read schema. |
| 246 | +TEST(InternalReadContext, TestCompoundPredicateRemap) { |
| 247 | + std::string path = paimon::test::GetDataDir() + "/orc/append_09.db/append_09"; |
| 248 | + ReadContextBuilder context_builder(path); |
| 249 | + context_builder.SetReadSchema({"f3", "f0"}); |
| 250 | + // AND(f3 == 1.5, f0 == "x") with field indices from the latest schema. |
| 251 | + auto left = PredicateBuilder::Equal(/*field_index=*/3, "f3", FieldType::DOUBLE, Literal(1.5)); |
| 252 | + auto right = PredicateBuilder::Equal(/*field_index=*/0, "f0", FieldType::STRING, |
| 253 | + Literal(FieldType::STRING, "x", 1)); |
| 254 | + ASSERT_OK_AND_ASSIGN(auto compound, PredicateBuilder::And({left, right})); |
| 255 | + context_builder.SetPredicate(compound); |
| 256 | + ASSERT_OK_AND_ASSIGN(auto read_context, context_builder.Finish()); |
| 257 | + SchemaManager schema_manager(std::make_shared<LocalFileSystem>(), read_context->GetPath()); |
| 258 | + ASSERT_OK_AND_ASSIGN(auto table_schema, schema_manager.ReadSchema(0)); |
| 259 | + ASSERT_OK_AND_ASSIGN(auto internal_context, |
| 260 | + InternalReadContext::Create(std::move(read_context), table_schema, |
| 261 | + table_schema->Options())); |
| 262 | + auto remapped_compound = |
| 263 | + std::dynamic_pointer_cast<CompoundPredicate>(internal_context->GetPredicate()); |
| 264 | + ASSERT_NE(remapped_compound, nullptr); |
| 265 | + ASSERT_EQ(remapped_compound->Children().size(), 2); |
| 266 | + auto remapped_left = std::dynamic_pointer_cast<LeafPredicate>(remapped_compound->Children()[0]); |
| 267 | + auto remapped_right = |
| 268 | + std::dynamic_pointer_cast<LeafPredicate>(remapped_compound->Children()[1]); |
| 269 | + ASSERT_NE(remapped_left, nullptr); |
| 270 | + ASSERT_NE(remapped_right, nullptr); |
| 271 | + EXPECT_EQ(remapped_left->FieldName(), "f3"); |
| 272 | + EXPECT_EQ(remapped_left->FieldIndex(), 0); |
| 273 | + EXPECT_EQ(remapped_right->FieldName(), "f0"); |
| 274 | + EXPECT_EQ(remapped_right->FieldIndex(), 1); |
| 275 | +} |
| 276 | + |
| 277 | +// Field not present in the projected read schema must surface as an Invalid status. |
| 278 | +TEST(InternalReadContext, TestPredicateOnFieldMissingFromReadSchema) { |
| 279 | + std::string path = paimon::test::GetDataDir() + "/orc/append_09.db/append_09"; |
| 280 | + ReadContextBuilder context_builder(path); |
| 281 | + context_builder.SetReadSchema({"f3", "f0"}); |
| 282 | + auto predicate = PredicateBuilder::Equal(/*field_index=*/1, "f1", FieldType::INT, Literal(7)); |
| 283 | + context_builder.SetPredicate(predicate); |
| 284 | + ASSERT_OK_AND_ASSIGN(auto read_context, context_builder.Finish()); |
| 285 | + SchemaManager schema_manager(std::make_shared<LocalFileSystem>(), read_context->GetPath()); |
| 286 | + ASSERT_OK_AND_ASSIGN(auto table_schema, schema_manager.ReadSchema(0)); |
| 287 | + ASSERT_NOK_WITH_MSG( |
| 288 | + InternalReadContext::Create(std::move(read_context), table_schema, table_schema->Options()), |
| 289 | + "field f1 does not exist in schema"); |
| 290 | +} |
| 291 | + |
194 | 292 | } // namespace paimon::test |
0 commit comments