iceberg-cpp
Loading...
Searching...
No Matches
projection_util_internal.h
1/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19
20#pragma once
21
22#include <algorithm>
23#include <memory>
24#include <ranges>
25#include <string>
26#include <string_view>
27#include <unordered_set>
28#include <utility>
29
30#include "iceberg/expression/literal.h"
33#include "iceberg/result.h"
34#include "iceberg/transform.h"
38#include "iceberg/util/macros.h"
39#include "iceberg/util/string_util.h"
40
41namespace iceberg {
42
44 private:
45 static Result<Literal> AdjustLiteral(const Literal& literal, int adjustment) {
46 switch (literal.type()->type_id()) {
47 case TypeId::kInt:
48 return Literal::Int(std::get<int32_t>(literal.value()) + adjustment);
49 case TypeId::kLong:
50 return Literal::Long(std::get<int64_t>(literal.value()) + adjustment);
51 case TypeId::kDate:
52 return Literal::Date(std::get<int32_t>(literal.value()) + adjustment);
53 case TypeId::kTimestamp:
54 return Literal::Timestamp(std::get<int64_t>(literal.value()) + adjustment);
55 case TypeId::kTimestampTz:
56 return Literal::TimestampTz(std::get<int64_t>(literal.value()) + adjustment);
57 case TypeId::kTimestampNs:
58 return Literal::TimestampNs(std::get<int64_t>(literal.value()) + adjustment);
59 case TypeId::kTimestampTzNs:
60 return Literal::TimestampTzNs(std::get<int64_t>(literal.value()) + adjustment);
61 case TypeId::kDecimal: {
62 const auto& decimal_type =
63 internal::checked_cast<const DecimalType&>(*literal.type());
64 Decimal adjusted = std::get<Decimal>(literal.value()) + Decimal(adjustment);
65 return Literal::Decimal(adjusted.value(), decimal_type.precision(),
66 decimal_type.scale());
67 }
68 default:
69 return NotSupported("{} is not a valid literal type for value adjustment",
70 literal.type()->ToString());
71 }
72 }
73
74 static Result<Literal> PlusOne(const Literal& literal) {
75 return AdjustLiteral(literal, /*adjustment=*/+1);
76 }
77
78 static Result<Literal> MinusOne(const Literal& literal) {
79 return AdjustLiteral(literal, /*adjustment=*/-1);
80 }
81
82 static Result<std::unique_ptr<UnboundPredicate>> MakePredicate(
83 Expression::Operation op, std::string_view name,
84 const std::shared_ptr<TransformFunction>& func, const Literal& literal) {
85 ICEBERG_ASSIGN_OR_RAISE(auto ref, NamedReference::Make(std::string(name)));
86 ICEBERG_ASSIGN_OR_RAISE(auto lit, func->Transform(literal));
87 return UnboundPredicateImpl<BoundReference>::Make(op, std::move(ref), std::move(lit));
88 }
89
90 static Result<std::unique_ptr<UnboundPredicate>> TransformSet(
91 std::string_view name, const std::shared_ptr<BoundSetPredicate>& pred,
92 const std::shared_ptr<TransformFunction>& func) {
93 std::vector<Literal> transformed;
94 transformed.reserve(pred->literal_set().size());
95 for (const auto& lit : pred->literal_set()) {
96 ICEBERG_ASSIGN_OR_RAISE(auto transformed_lit, func->Transform(lit));
97 transformed.push_back(std::move(transformed_lit));
98 }
99 ICEBERG_ASSIGN_OR_RAISE(auto ref, NamedReference::Make(std::string(name)));
100 return UnboundPredicateImpl<BoundReference>::Make(pred->op(), std::move(ref),
101 std::move(transformed));
102 }
103
104 static Result<std::unique_ptr<UnboundPredicate>> TruncateByteArray(
105 std::string_view name, const std::shared_ptr<BoundLiteralPredicate>& pred,
106 const std::shared_ptr<TransformFunction>& func) {
107 switch (pred->op()) {
108 case Expression::Operation::kLt:
109 case Expression::Operation::kLtEq:
110 return MakePredicate(Expression::Operation::kLtEq, name, func, pred->literal());
111 case Expression::Operation::kGt:
112 case Expression::Operation::kGtEq:
113 return MakePredicate(Expression::Operation::kGtEq, name, func, pred->literal());
114 case Expression::Operation::kEq:
115 case Expression::Operation::kStartsWith:
116 return MakePredicate(pred->op(), name, func, pred->literal());
117 default:
118 return nullptr;
119 }
120 }
121
122 static Result<std::unique_ptr<UnboundPredicate>> TruncateByteArrayStrict(
123 std::string_view name, const std::shared_ptr<BoundLiteralPredicate>& pred,
124 const std::shared_ptr<TransformFunction>& func) {
125 switch (pred->op()) {
126 case Expression::Operation::kLt:
127 case Expression::Operation::kLtEq:
128 return MakePredicate(Expression::Operation::kLt, name, func, pred->literal());
129 case Expression::Operation::kGt:
130 case Expression::Operation::kGtEq:
131 return MakePredicate(Expression::Operation::kGt, name, func, pred->literal());
132 case Expression::Operation::kNotEq:
133 return MakePredicate(Expression::Operation::kNotEq, name, func, pred->literal());
134 default:
135 return nullptr;
136 }
137 }
138
139 // Apply to int32, int64, decimal, and temporal types
140 static Result<std::unique_ptr<UnboundPredicate>> TransformNumeric(
141 std::string_view name, const std::shared_ptr<BoundLiteralPredicate>& pred,
142 const std::shared_ptr<TransformFunction>& func) {
143 switch (func->source_type()->type_id()) {
144 case TypeId::kInt:
145 case TypeId::kLong:
146 case TypeId::kDecimal:
147 case TypeId::kDate:
148 case TypeId::kTimestamp:
149 case TypeId::kTimestampTz:
150 case TypeId::kTimestampNs:
151 case TypeId::kTimestampTzNs:
152 break;
153 default:
154 return NotSupported("{} is not a valid input type for numeric transform",
155 func->source_type()->ToString());
156 }
157
158 switch (pred->op()) {
159 case Expression::Operation::kLt: {
160 // adjust closed and then transform ltEq
161 ICEBERG_ASSIGN_OR_RAISE(auto adjusted, MinusOne(pred->literal()));
162 return MakePredicate(Expression::Operation::kLtEq, name, func, adjusted);
163 }
164 case Expression::Operation::kGt: {
165 // adjust closed and then transform gtEq
166 ICEBERG_ASSIGN_OR_RAISE(auto adjusted, PlusOne(pred->literal()));
167 return MakePredicate(Expression::Operation::kGtEq, name, func, adjusted);
168 }
169 case Expression::Operation::kLtEq:
170 case Expression::Operation::kGtEq:
171 case Expression::Operation::kEq:
172 return MakePredicate(pred->op(), name, func, pred->literal());
173 default:
174 return nullptr;
175 }
176 }
177
178 static Result<std::unique_ptr<UnboundPredicate>> TransformNumericStrict(
179 std::string_view name, const std::shared_ptr<BoundLiteralPredicate>& pred,
180 const std::shared_ptr<TransformFunction>& func) {
181 switch (func->source_type()->type_id()) {
182 case TypeId::kInt:
183 case TypeId::kLong:
184 case TypeId::kDecimal:
185 case TypeId::kDate:
186 case TypeId::kTimestamp:
187 case TypeId::kTimestampTz:
188 case TypeId::kTimestampNs:
189 case TypeId::kTimestampTzNs:
190 break;
191 default:
192 return NotSupported("{} is not a valid input type for numeric transform",
193 func->source_type()->ToString());
194 }
195
196 switch (pred->op()) {
197 case Expression::Operation::kLtEq: {
198 ICEBERG_ASSIGN_OR_RAISE(auto adjusted, PlusOne(pred->literal()));
199 return MakePredicate(Expression::Operation::kLt, name, func, adjusted);
200 }
201 case Expression::Operation::kGtEq: {
202 ICEBERG_ASSIGN_OR_RAISE(auto adjusted, MinusOne(pred->literal()));
203 return MakePredicate(Expression::Operation::kGt, name, func, adjusted);
204 }
205 case Expression::Operation::kLt:
206 case Expression::Operation::kGt:
207 case Expression::Operation::kNotEq:
208 return MakePredicate(pred->op(), name, func, pred->literal());
209 default:
210 return nullptr;
211 }
212 }
213
214 static Result<std::unique_ptr<UnboundPredicate>> TruncateStringLiteral(
215 std::string_view name, const std::shared_ptr<BoundLiteralPredicate>& pred,
216 const std::shared_ptr<TransformFunction>& func) {
217 const auto op = pred->op();
218 if (op != Expression::Operation::kStartsWith &&
219 op != Expression::Operation::kNotStartsWith) {
220 return TruncateByteArray(name, pred, func);
221 }
222
223 const auto& literal = pred->literal();
224 const auto length =
225 StringUtils::CodePointCount(std::get<std::string>(literal.value()));
226 const auto width = static_cast<size_t>(
227 internal::checked_pointer_cast<TruncateTransform>(func)->width());
228
229 if (length < width) {
230 return MakePredicate(op, name, func, literal);
231 }
232
233 if (length == width) {
234 if (op == Expression::Operation::kStartsWith) {
235 return MakePredicate(Expression::Operation::kEq, name, func, literal);
236 } else {
237 return MakePredicate(Expression::Operation::kNotEq, name, func, literal);
238 }
239 }
240
241 if (op == Expression::Operation::kStartsWith) {
242 return TruncateByteArray(name, pred, func);
243 }
244
245 return nullptr;
246 }
247
248 static Result<std::unique_ptr<UnboundPredicate>> TruncateStringLiteralStrict(
249 std::string_view name, const std::shared_ptr<BoundLiteralPredicate>& pred,
250 const std::shared_ptr<TransformFunction>& func) {
251 const auto op = pred->op();
252 if (op != Expression::Operation::kStartsWith &&
253 op != Expression::Operation::kNotStartsWith) {
254 return TruncateByteArrayStrict(name, pred, func);
255 }
256
257 const auto& literal = pred->literal();
258 const auto length =
259 StringUtils::CodePointCount(std::get<std::string>(literal.value()));
260 const auto width = static_cast<size_t>(
261 internal::checked_pointer_cast<TruncateTransform>(func)->width());
262
263 if (length < width) {
264 return MakePredicate(op, name, func, literal);
265 }
266
267 if (length == width) {
268 if (op == Expression::Operation::kStartsWith) {
269 return MakePredicate(Expression::Operation::kEq, name, func, literal);
270 } else {
271 return MakePredicate(Expression::Operation::kNotEq, name, func, literal);
272 }
273 }
274
275 if (op == Expression::Operation::kNotStartsWith) {
276 return MakePredicate(Expression::Operation::kNotStartsWith, name, func, literal);
277 }
278
279 return nullptr;
280 }
281
282 // Fixes an inclusive projection to account for incorrectly transformed values.
283 // align with Java implementation:
284 // https://github.com/apache/iceberg/blob/1.10.x/api/src/main/java/org/apache/iceberg/transforms/ProjectionUtil.java#L275
285 static Result<std::unique_ptr<UnboundPredicate>> FixInclusiveTimeProjection(
286 std::unique_ptr<UnboundPredicateImpl<BoundReference>> projected) {
287 if (projected == nullptr) {
288 return nullptr;
289 }
290
291 // adjust the predicate for values that were 1 larger than the correct transformed
292 // value
293 switch (projected->op()) {
294 case Expression::Operation::kLt: {
295 ICEBERG_DCHECK(!projected->literals().empty(), "Expected at least one literal");
296 const auto& literal = projected->literals().front();
297 ICEBERG_DCHECK(std::holds_alternative<int32_t>(literal.value()),
298 "Expected int32_t");
299 if (auto value = std::get<int32_t>(literal.value()); value < 0) {
300 return UnboundPredicateImpl<BoundReference>::Make(Expression::Operation::kLt,
301 std::move(projected->term()),
302 Literal::Int(value + 1));
303 }
304
305 return projected;
306 }
307
308 case Expression::Operation::kLtEq: {
309 ICEBERG_DCHECK(!projected->literals().empty(), "Expected at least one literal");
310 const auto& literal = projected->literals().front();
311 ICEBERG_DCHECK(std::holds_alternative<int32_t>(literal.value()),
312 "Expected int32_t");
313
314 if (auto value = std::get<int32_t>(literal.value()); value < 0) {
315 return UnboundPredicateImpl<BoundReference>::Make(Expression::Operation::kLtEq,
316 std::move(projected->term()),
317 Literal::Int(value + 1));
318 }
319 return projected;
320 }
321
322 case Expression::Operation::kGt:
323 case Expression::Operation::kGtEq:
324 // incorrect projected values are already greater than the bound for GT, GT_EQ
325 return projected;
326
327 case Expression::Operation::kEq: {
328 ICEBERG_DCHECK(!projected->literals().empty(), "Expected at least one literal");
329 const auto& literal = projected->literals().front();
330 ICEBERG_DCHECK(std::holds_alternative<int32_t>(literal.value()),
331 "Expected int32_t");
332 if (auto value = std::get<int32_t>(literal.value()); value < 0) {
333 // match either the incorrect value (projectedValue + 1) or the correct value
334 // (projectedValue)
336 Expression::Operation::kIn, std::move(projected->term()),
337 {literal, Literal::Int(value + 1)});
338 }
339 return projected;
340 }
341
342 case Expression::Operation::kIn: {
343 ICEBERG_DCHECK(!projected->literals().empty(), "Expected at least one literal");
344 const auto& literals = projected->literals();
345 ICEBERG_DCHECK(
346 std::ranges::all_of(literals,
347 [](const auto& lit) {
348 return std::holds_alternative<int32_t>(lit.value());
349 }),
350 "Expected int32_t");
351 std::unordered_set<int32_t> value_set;
352 bool has_negative_value = false;
353 for (const auto& lit : literals) {
354 auto value = std::get<int32_t>(lit.value());
355 value_set.insert(value);
356 if (value < 0) {
357 value_set.insert(value + 1);
358 has_negative_value = true;
359 }
360 }
361 if (has_negative_value) {
362 auto values =
363 std::views::transform(value_set,
364 [](int32_t value) { return Literal::Int(value); }) |
365 std::ranges::to<std::vector>();
366 return UnboundPredicateImpl<BoundReference>::Make(Expression::Operation::kIn,
367 std::move(projected->term()),
368 std::move(values));
369 }
370 return projected;
371 }
372
373 case Expression::Operation::kNotIn:
374 case Expression::Operation::kNotEq:
375 // there is no inclusive projection for NOT_EQ and NOT_IN
376 return nullptr;
377
378 default:
379 return projected;
380 }
381 }
382
383 // Fixes a strict projection to account for incorrectly transformed values.
384 // align with Java implementation:
385 // https://github.com/apache/iceberg/blob/1.10.x/api/src/main/java/org/apache/iceberg/transforms/ProjectionUtil.java#L347
386 static Result<std::unique_ptr<UnboundPredicate>> FixStrictTimeProjection(
387 std::unique_ptr<UnboundPredicateImpl<BoundReference>> projected) {
388 if (projected == nullptr) {
389 return nullptr;
390 }
391
392 switch (projected->op()) {
393 case Expression::Operation::kLt:
394 case Expression::Operation::kLtEq:
395 // the correct bound is a correct strict projection for the incorrectly
396 // transformed values.
397 return projected;
398
399 case Expression::Operation::kGt: {
400 // GT and GT_EQ need to be adjusted because values that do not match the predicate
401 // may have been transformed into partition values that match the projected
402 // predicate.
403 ICEBERG_DCHECK(!projected->literals().empty(), "Expected at least one literal");
404 const auto& literal = projected->literals().front();
405 ICEBERG_DCHECK(std::holds_alternative<int32_t>(literal.value()),
406 "Expected int32_t");
407 if (auto value = std::get<int32_t>(literal.value()); value <= 0) {
408 return UnboundPredicateImpl<BoundReference>::Make(Expression::Operation::kGt,
409 std::move(projected->term()),
410 Literal::Int(value + 1));
411 }
412 return projected;
413 }
414
415 case Expression::Operation::kGtEq: {
416 ICEBERG_DCHECK(!projected->literals().empty(), "Expected at least one literal");
417 const auto& literal = projected->literals().front();
418 ICEBERG_DCHECK(std::holds_alternative<int32_t>(literal.value()),
419 "Expected int32_t");
420 if (auto value = std::get<int32_t>(literal.value()); value <= 0) {
421 return UnboundPredicateImpl<BoundReference>::Make(Expression::Operation::kGtEq,
422 std::move(projected->term()),
423 Literal::Int(value + 1));
424 }
425 return projected;
426 }
427
428 case Expression::Operation::kEq:
429 case Expression::Operation::kIn:
430 // there is no strict projection for EQ and IN
431 return nullptr;
432
433 case Expression::Operation::kNotEq: {
434 ICEBERG_DCHECK(!projected->literals().empty(), "Expected at least one literal");
435 const auto& literal = projected->literals().front();
436 ICEBERG_DCHECK(std::holds_alternative<int32_t>(literal.value()),
437 "Expected int32_t");
438 if (auto value = std::get<int32_t>(literal.value()); value < 0) {
440 Expression::Operation::kNotIn, std::move(projected->term()),
441 {literal, Literal::Int(value + 1)});
442 }
443 return projected;
444 }
445
446 case Expression::Operation::kNotIn: {
447 ICEBERG_DCHECK(!projected->literals().empty(), "Expected at least one literal");
448 const auto& literals = projected->literals();
449 ICEBERG_DCHECK(
450 std::ranges::all_of(literals,
451 [](const auto& lit) {
452 return std::holds_alternative<int32_t>(lit.value());
453 }),
454 "Expected int32_t");
455 std::unordered_set<int32_t> value_set;
456 bool has_negative_value = false;
457 for (const auto& lit : literals) {
458 auto value = std::get<int32_t>(lit.value());
459 value_set.insert(value);
460 if (value < 0) {
461 value_set.insert(value + 1);
462 has_negative_value = true;
463 }
464 }
465 if (has_negative_value) {
466 auto values =
467 std::views::transform(value_set,
468 [](int32_t value) { return Literal::Int(value); }) |
469 std::ranges::to<std::vector>();
470 return UnboundPredicateImpl<BoundReference>::Make(Expression::Operation::kNotIn,
471 std::move(projected->term()),
472 std::move(values));
473 }
474 return projected;
475 }
476
477 default:
478 return nullptr;
479 }
480 }
481
482 public:
483 static Result<std::unique_ptr<UnboundPredicate>> IdentityProject(
484 std::string_view name, const std::shared_ptr<BoundPredicate>& pred) {
485 ICEBERG_ASSIGN_OR_RAISE(auto ref, NamedReference::Make(std::string(name)));
486 switch (pred->kind()) {
487 case BoundPredicate::Kind::kUnary: {
488 return UnboundPredicateImpl<BoundReference>::Make(pred->op(), std::move(ref));
489 }
490 case BoundPredicate::Kind::kLiteral: {
491 const auto& literalPredicate =
492 internal::checked_pointer_cast<BoundLiteralPredicate>(pred);
493 return UnboundPredicateImpl<BoundReference>::Make(pred->op(), std::move(ref),
494 literalPredicate->literal());
495 }
496 case BoundPredicate::Kind::kSet: {
497 const auto& setPredicate =
498 internal::checked_pointer_cast<BoundSetPredicate>(pred);
500 pred->op(), std::move(ref),
501 std::vector<Literal>(setPredicate->literal_set().begin(),
502 setPredicate->literal_set().end()));
503 }
504 }
505 std::unreachable();
506 }
507
508 static Result<std::unique_ptr<UnboundPredicate>> BucketProject(
509 std::string_view name, const std::shared_ptr<BoundPredicate>& pred,
510 const std::shared_ptr<TransformFunction>& func) {
511 ICEBERG_ASSIGN_OR_RAISE(auto ref, NamedReference::Make(std::string(name)));
512 switch (pred->kind()) {
513 case BoundPredicate::Kind::kUnary: {
514 return UnboundPredicateImpl<BoundReference>::Make(pred->op(), std::move(ref));
515 }
516 case BoundPredicate::Kind::kLiteral: {
517 if (pred->op() == Expression::Operation::kEq) {
518 const auto& literalPredicate =
519 internal::checked_pointer_cast<BoundLiteralPredicate>(pred);
520 ICEBERG_ASSIGN_OR_RAISE(auto transformed,
521 func->Transform(literalPredicate->literal()));
522 return UnboundPredicateImpl<BoundReference>::Make(pred->op(), std::move(ref),
523 std::move(transformed));
524 }
525 break;
526 }
527 case BoundPredicate::Kind::kSet: {
528 // notIn can't be projected
529 if (pred->op() == Expression::Operation::kIn) {
530 const auto& setPredicate =
531 internal::checked_pointer_cast<BoundSetPredicate>(pred);
532 return TransformSet(name, setPredicate, func);
533 }
534 break;
535 }
536 }
537
538 // comparison predicates can't be projected, notEq can't be projected
539 // TODO(anyone): small ranges can be projected.
540 // for example, (x > 0) and (x < 3) can be turned into in({1, 2}) and projected.
541 return nullptr;
542 }
543
544 static Result<std::unique_ptr<UnboundPredicate>> TruncateProject(
545 std::string_view name, const std::shared_ptr<BoundPredicate>& pred,
546 const std::shared_ptr<TransformFunction>& func) {
547 ICEBERG_ASSIGN_OR_RAISE(auto ref, NamedReference::Make(std::string(name)));
548 // Handle unary predicates uniformly for all types
549 if (pred->kind() == BoundPredicate::Kind::kUnary) {
550 return UnboundPredicateImpl<BoundReference>::Make(pred->op(), std::move(ref));
551 }
552
553 // Handle set predicates (kIn) uniformly for all types
554 if (pred->kind() == BoundPredicate::Kind::kSet) {
555 if (pred->op() == Expression::Operation::kIn) {
556 const auto& setPredicate =
557 internal::checked_pointer_cast<BoundSetPredicate>(pred);
558 return TransformSet(name, setPredicate, func);
559 }
560 return nullptr;
561 }
562
563 // Handle literal predicates based on source type
564 const auto& literalPredicate =
565 internal::checked_pointer_cast<BoundLiteralPredicate>(pred);
566
567 switch (func->source_type()->type_id()) {
568 case TypeId::kInt:
569 case TypeId::kLong:
570 case TypeId::kDecimal:
571 return TransformNumeric(name, literalPredicate, func);
572 case TypeId::kString:
573 return TruncateStringLiteral(name, literalPredicate, func);
574 case TypeId::kBinary:
575 return TruncateByteArray(name, literalPredicate, func);
576 default:
577 return NotSupported("{} is not a valid input type for truncate transform",
578 func->source_type()->ToString());
579 }
580 }
581
582 static Result<std::unique_ptr<UnboundPredicate>> TemporalProject(
583 std::string_view name, const std::shared_ptr<BoundPredicate>& pred,
584 const std::shared_ptr<TransformFunction>& func) {
585 ICEBERG_ASSIGN_OR_RAISE(auto ref, NamedReference::Make(std::string(name)));
586 if (pred->kind() == BoundPredicate::Kind::kUnary) {
587 return UnboundPredicateImpl<BoundReference>::Make(pred->op(), std::move(ref));
588 } else if (pred->kind() == BoundPredicate::Kind::kLiteral) {
589 const auto& literalPredicate =
590 internal::checked_pointer_cast<BoundLiteralPredicate>(pred);
591 ICEBERG_ASSIGN_OR_RAISE(auto projected,
592 TransformNumeric(name, literalPredicate, func));
593 if (func->transform_type() != TransformType::kDay ||
594 func->source_type()->type_id() != TypeId::kDate) {
595 return FixInclusiveTimeProjection(
596 internal::checked_pointer_cast<UnboundPredicateImpl<BoundReference>>(
597 std::move(projected)));
598 }
599 return projected;
600 } else if (pred->kind() == BoundPredicate::Kind::kSet &&
601 pred->op() == Expression::Operation::kIn) {
602 const auto& setPredicate = internal::checked_pointer_cast<BoundSetPredicate>(pred);
603 ICEBERG_ASSIGN_OR_RAISE(auto projected, TransformSet(name, setPredicate, func));
604 if (func->transform_type() != TransformType::kDay ||
605 func->source_type()->type_id() != TypeId::kDate) {
606 return FixInclusiveTimeProjection(
607 internal::checked_pointer_cast<UnboundPredicateImpl<BoundReference>>(
608 std::move(projected)));
609 }
610 return projected;
611 }
612
613 return nullptr;
614 }
615
616 static Result<std::unique_ptr<UnboundPredicate>> RemoveTransform(
617 std::string_view name, const std::shared_ptr<BoundPredicate>& pred) {
618 ICEBERG_ASSIGN_OR_RAISE(auto ref, NamedReference::Make(std::string(name)));
619 switch (pred->kind()) {
620 case BoundPredicate::Kind::kUnary: {
621 return UnboundPredicateImpl<BoundReference>::Make(pred->op(), std::move(ref));
622 }
623 case BoundPredicate::Kind::kLiteral: {
624 const auto& literalPredicate =
625 internal::checked_pointer_cast<BoundLiteralPredicate>(pred);
626 return UnboundPredicateImpl<BoundReference>::Make(pred->op(), std::move(ref),
627 literalPredicate->literal());
628 }
629 case BoundPredicate::Kind::kSet: {
630 const auto& setPredicate =
631 internal::checked_pointer_cast<BoundSetPredicate>(pred);
633 pred->op(), std::move(ref),
634 std::vector<Literal>(setPredicate->literal_set().begin(),
635 setPredicate->literal_set().end()));
636 }
637 }
638 std::unreachable();
639 }
640
641 static Result<std::unique_ptr<UnboundPredicate>> BucketProjectStrict(
642 std::string_view name, const std::shared_ptr<BoundPredicate>& pred,
643 const std::shared_ptr<TransformFunction>& func) {
644 ICEBERG_ASSIGN_OR_RAISE(auto ref, NamedReference::Make(std::string(name)));
645 switch (pred->kind()) {
646 case BoundPredicate::Kind::kUnary: {
647 return UnboundPredicateImpl<BoundReference>::Make(pred->op(), std::move(ref));
648 }
649 case BoundPredicate::Kind::kLiteral: {
650 if (pred->op() == Expression::Operation::kNotEq) {
651 const auto& literalPredicate =
652 internal::checked_pointer_cast<BoundLiteralPredicate>(pred);
653 ICEBERG_ASSIGN_OR_RAISE(auto transformed,
654 func->Transform(literalPredicate->literal()));
655 // TODO(anyone): need to translate not(eq(...)) into notEq in expressions
656 return UnboundPredicateImpl<BoundReference>::Make(pred->op(), std::move(ref),
657 std::move(transformed));
658 }
659 break;
660 }
661 case BoundPredicate::Kind::kSet: {
662 if (pred->op() == Expression::Operation::kNotIn) {
663 const auto& setPredicate =
664 internal::checked_pointer_cast<BoundSetPredicate>(pred);
665 return TransformSet(name, setPredicate, func);
666 }
667 break;
668 }
669 }
670
671 // no strict projection for comparison or equality
672 return nullptr;
673 }
674
675 static Result<std::unique_ptr<UnboundPredicate>> TruncateProjectStrict(
676 std::string_view name, const std::shared_ptr<BoundPredicate>& pred,
677 const std::shared_ptr<TransformFunction>& func) {
678 ICEBERG_ASSIGN_OR_RAISE(auto ref, NamedReference::Make(std::string(name)));
679 // Handle unary predicates uniformly for all types
680 if (pred->kind() == BoundPredicate::Kind::kUnary) {
681 return UnboundPredicateImpl<BoundReference>::Make(pred->op(), std::move(ref));
682 }
683
684 // Handle set predicates (kNotIn) uniformly for all types
685 if (pred->kind() == BoundPredicate::Kind::kSet) {
686 if (pred->op() == Expression::Operation::kNotIn) {
687 const auto& setPredicate =
688 internal::checked_pointer_cast<BoundSetPredicate>(pred);
689 return TransformSet(name, setPredicate, func);
690 }
691 return nullptr;
692 }
693
694 // Handle literal predicates based on source type
695 const auto& literalPredicate =
696 internal::checked_pointer_cast<BoundLiteralPredicate>(pred);
697
698 switch (func->source_type()->type_id()) {
699 case TypeId::kInt:
700 case TypeId::kLong:
701 case TypeId::kDecimal:
702 return TransformNumericStrict(name, literalPredicate, func);
703 case TypeId::kString:
704 return TruncateStringLiteralStrict(name, literalPredicate, func);
705 case TypeId::kBinary:
706 return TruncateByteArrayStrict(name, literalPredicate, func);
707 default:
708 return NotSupported("{} is not a valid input type for truncate transform",
709 func->source_type()->ToString());
710 }
711 }
712
713 static Result<std::unique_ptr<UnboundPredicate>> TemporalProjectStrict(
714 std::string_view name, const std::shared_ptr<BoundPredicate>& pred,
715 const std::shared_ptr<TransformFunction>& func) {
716 ICEBERG_ASSIGN_OR_RAISE(auto ref, NamedReference::Make(std::string(name)));
717 if (pred->kind() == BoundPredicate::Kind::kUnary) {
718 return UnboundPredicateImpl<BoundReference>::Make(pred->op(), std::move(ref));
719 } else if (pred->kind() == BoundPredicate::Kind::kLiteral) {
720 const auto& literalPredicate =
721 internal::checked_pointer_cast<BoundLiteralPredicate>(pred);
722 ICEBERG_ASSIGN_OR_RAISE(auto projected,
723 TransformNumericStrict(name, literalPredicate, func));
724 if (func->transform_type() != TransformType::kDay ||
725 func->source_type()->type_id() != TypeId::kDate) {
726 return FixStrictTimeProjection(
727 internal::checked_pointer_cast<UnboundPredicateImpl<BoundReference>>(
728 std::move(projected)));
729 }
730 return projected;
731 } else if (pred->kind() == BoundPredicate::Kind::kSet &&
732 pred->op() == Expression::Operation::kNotIn) {
733 const auto& setPredicate = internal::checked_pointer_cast<BoundSetPredicate>(pred);
734 ICEBERG_ASSIGN_OR_RAISE(auto projected, TransformSet(name, setPredicate, func));
735 if (func->transform_type() != TransformType::kDay ||
736 func->source_type()->type_id() != TypeId::kDate) {
737 return FixStrictTimeProjection(
738 internal::checked_pointer_cast<UnboundPredicateImpl<BoundReference>>(
739 std::move(projected)));
740 }
741 return projected;
742 }
743
744 return nullptr;
745 }
746};
747
748} // namespace iceberg
Checked cast functions for dynamic_cast and static_cast. Adapted from Apache Arrow https://github....
Represents 128-bit fixed-point decimal numbers. The max decimal precision that can be safely represen...
Definition decimal.h:46
Operation
Operation types for expressions.
Definition expression.h:40
Literal is a literal value that is associated with a primitive type.
Definition literal.h:39
static Literal Decimal(int128_t value, int32_t precision, int32_t scale)
Create a decimal literal.
Definition literal.cc:411
static Result< std::unique_ptr< NamedReference > > Make(std::string field_name)
Create a named reference to a field.
Definition term.cc:45
Definition projection_util_internal.h:43
static size_t CodePointCount(std::string_view str)
Count the number of code points in a UTF-8 string.
Definition string_util.h:70
static Result< std::unique_ptr< UnboundPredicateImpl< B > > > Make(Expression::Operation op, std::shared_ptr< UnboundTerm< B > > term)
Create an unbound predicate (unary operation).
Definition predicate.cc:48
128-bit fixed-point decimal numbers. Adapted from Apache Arrow with only Decimal128 support....