Skip to content

Commit 77508bd

Browse files
authored
Task #10: Implement DeriveFieldGuarantee function (#25)
- Converts ORC column statistics into guarantee expressions - Handles all-null case (num_values=0): returns is_null(field) - Handles single-value case (min==max): returns field==value with null handling - Handles range case (min<max): returns field in [min, max] with null handling - Validates statistics: ignores deprecated or corrupted stats (min>max) - Returns nullopt for incomplete or missing statistics (conservative fallback) Verified: - Checks is_statistics_deprecated flag before using stats - Properly handles has_null flag for null value inclusion - Validates min <= max to detect corrupted statistics - Uses std::optional for safe failure handling
1 parent 8e7bc09 commit 77508bd

1 file changed

Lines changed: 94 additions & 0 deletions

File tree

cpp/src/arrow/dataset/file_orc.cc

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
341435
class OrcScanTask {
342436
public:

0 commit comments

Comments
 (0)