@@ -337,6 +337,100 @@ std::vector<PredicateField> ResolvePredicateFields(
337337 return resolved_fields;
338338}
339339
340+ // / \brief Derive guarantee expression from stripe column statistics
341+ // /
342+ // / Converts ORC column statistics into an Arrow expression that represents
343+ // / the guarantee about values in this stripe. This guarantee can be used
344+ // / to simplify predicates and potentially eliminate the stripe entirely.
345+ // /
346+ // / Returns std::nullopt if:
347+ // / - Statistics are deprecated (ORC writer indicated they're unreliable)
348+ // / - Statistics are missing or incomplete
349+ // / - Statistics are corrupted (min > max)
350+ // /
351+ // / Special cases:
352+ // / - All null (num_values == 0): returns "is_null(field_ref)"
353+ // / - Single value (min == max): returns "field_ref == value OR is_null(field_ref)"
354+ // / (if has_null) or "field_ref == value" (if !has_null)
355+ // / - Range (min != max): returns "field_ref >= min AND field_ref <= max"
356+ // / plus "OR is_null(field_ref)" if has_null
357+ // /
358+ // / \param field_ref The field reference for the expression
359+ // / \param field_type The Arrow data type of the field
360+ // / \param stats The ORC column statistics for this stripe
361+ // / \return Guarantee expression, or nullopt if statistics can't be used
362+ std::optional<compute::Expression> DeriveFieldGuarantee (
363+ const FieldRef& field_ref,
364+ const std::shared_ptr<DataType>& field_type,
365+ const adapters::orc::ColumnStatistics& stats) {
366+ auto field_expr = compute::field_ref (field_ref);
367+
368+ // Check if statistics are deprecated (writer indicates they're unreliable)
369+ if (stats.is_statistics_deprecated ) {
370+ return std::nullopt ;
371+ }
372+
373+ // Determine if the stripe may contain null values
374+ bool may_have_null = stats.has_null ;
375+
376+ // Special case: All values are null (no non-null values)
377+ if (stats.num_values == 0 ) {
378+ // The entire stripe is either empty or all nulls
379+ // Return "is_null(field_ref)" as the guarantee
380+ return compute::is_null (std::move (field_expr));
381+ }
382+
383+ // Check if min/max statistics are available
384+ if (!stats.has_minimum || !stats.has_maximum ) {
385+ // Incomplete statistics - can't derive useful guarantees
386+ return std::nullopt ;
387+ }
388+
389+ // Get min and max scalars
390+ auto min = stats.minimum ;
391+ auto max = stats.maximum ;
392+
393+ if (!min || !max) {
394+ // Missing scalar values
395+ return std::nullopt ;
396+ }
397+
398+ // Validate statistics: min should not be greater than max
399+ // If this occurs, statistics are corrupted and should not be trusted
400+ if (min->type ()->id () == max->type ()->id ()) {
401+ auto cmp_result = Scalar::Compare (*min, *max);
402+ if (!cmp_result.ok () || *cmp_result == CompareOperator::GREATER ) {
403+ // Corrupted statistics (min > max) - don't use them
404+ return std::nullopt ;
405+ }
406+ }
407+
408+ // Case 1: Single value (min == max)
409+ if (min->Equals (*max)) {
410+ auto single_value = compute::equal (field_expr, compute::literal (min));
411+
412+ if (!may_have_null) {
413+ // All non-null values equal this value, no nulls present
414+ return single_value;
415+ }
416+ // Values are either this single value OR null
417+ return compute::or_ (std::move (single_value), compute::is_null (std::move (field_expr)));
418+ }
419+
420+ // Case 2: Range of values (min < max)
421+ auto lower_bound = compute::greater_equal (field_expr, compute::literal (min));
422+ auto upper_bound = compute::less_equal (field_expr, compute::literal (max));
423+ auto in_range = compute::and_ (std::move (lower_bound), std::move (upper_bound));
424+
425+ if (!may_have_null) {
426+ // All values are within [min, max], no nulls
427+ return in_range;
428+ }
429+
430+ // Values are within [min, max] OR null
431+ return compute::or_ (std::move (in_range), compute::is_null (std::move (field_expr)));
432+ }
433+
340434// / \brief A ScanTask backed by an ORC file.
341435class OrcScanTask {
342436 public:
0 commit comments