{"record":{"id":"3e8b788e6a9a0af1","repo":"influxdata/influxdb","slug":"no-min-time-statistic","errorCode":null,"errorMessage":"no min time statistic","messagePattern":"no min time statistic","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/parquet_file/src/metadata.rs","lineNumber":577,"sourceCode":"/// A [`RecordBatch`] serialized without the embedded metadata found in the\n/// IOx [`Schema`] type will cause a statistic resolution failure due to\n/// lack of the IOx field type metadata for the time column. Batches\n/// produced from the through the IOx write path always include this\n/// metadata.\n///\n/// [`RecordBatch`]: arrow::record_batch::RecordBatch\npub fn derive_min_max_time(stats: Vec<ColumnSummary>) -> TimestampRange {\n    let time_summary = stats\n        .into_iter()\n        .find(|v| v.name == TIME_COLUMN_NAME)\n        .expect(\"no time column in metadata statistics\");\n\n    assert_eq!(time_summary.influxdb_type, InfluxDbType::Timestamp);\n\n    // Extract the min/max timestamps.\n    match time_summary.stats {\n        Statistics::I64(stats) => {\n            let min = Timestamp::new(stats.min.expect(\"no min time statistic\"));\n            let max = Timestamp::new(stats.max.expect(\"no max time statistic\"));\n            TimestampRange { min, max }\n        }\n        _ => panic!(\"unexpected physical type for timestamp column\"),\n    }\n}\n/// Parquet metadata with IOx-specific wrapper.\n#[derive(Clone, PartialEq, Eq)]\npub struct IoxParquetMetaData {\n    /// [Apache Parquet] metadata as freestanding [Apache Thrift]-encoded, and [Zstandard]-compressed bytes.\n    ///\n    /// This can be used to store metadata separate from the related payload data. The usage of [Apache Thrift] allows the\n    /// same stability guarantees as the usage of an ordinary [Apache Parquet] file. To encode a thrift message into bytes\n    /// the [Thrift Compact Protocol] is used.\n    ///\n    /// [Apache Parquet]: https://parquet.apache.org/\n    /// [Apache Thrift]: https://thrift.apache.org/\n    /// [Thrift Compact Protocol]: https://github.com/apache/thrift/blob/master/doc/specs/thrift-compact-protocol.md","sourceCodeStart":559,"sourceCodeEnd":595,"githubUrl":"https://github.com/influxdata/influxdb/blob/d28e26e048401c53cbb98cf2d6ab0cf1e98048ca/core/parquet_file/src/metadata.rs#L559-L595","documentation":"After finding the time column, derive_min_max_time matches Statistics::I64(stats) and unwraps stats.min.expect(\"no min time statistic\") (and max on the next line). Parquet column statistics are optional: writers can disable them, and an all-null column has no min/max. When the time column exists but its statistics carry no min, this expect panics.","triggerScenarios":"to_parquet_file / derive_min_max_time on a file whose time-column chunk statistics were not written — writer configured with statistics disabled, a null-only time column, or a writer version that omits min/max for some encodings.","commonSituations":"Externally generated parquet written with parquet.statistics_enabled=false; empty or all-null test batches; files written by old/limited parquet writers that skip min/max for int64 columns.","solutions":["Re-write the file with column statistics enabled (arrow-rs/parquet writer default enables them).","If calling derive_min_max_time directly, pre-check the time summary's stats have min/max Some before calling, and skip or compute from data otherwise.","Validate files coming from external sources with a parquet-inspection step (footer statistics present for the time column) before cataloging.","For genuinely empty time columns, write at least one row or handle the empty-range case in your tooling before IOx paths see the file."],"exampleFix":"// before\nlet range = derive_min_max_time(stats);\n\n// after\nlet time = stats.iter().find(|v| v.name == TIME_COLUMN_NAME);\nmatch time.map(|t| &t.stats) {\n    Some(Statistics::I64(s)) if s.min.is_some() && s.max.is_some() => {\n        let range = derive_min_max_time(stats);\n        // ...\n    }\n    _ => return Err(\"time column lacks min/max statistics\".into()),\n}","handlingStrategy":"validation","validationCode":"// verify min/max statistics exist for the time column before deriving the range\nfn time_stats_complete(stats: &[ColumnSummary]) -> bool {\n    stats.iter().any(|v| {\n        v.name == TIME_COLUMN_NAME\n            && matches!(&v.stats, Statistics::I64(s) if s.min.is_some() && s.max.is_some())\n    })\n}\n\nif !time_stats_complete(&stats) {\n    return Err(\"time column lacks min/max statistics (writer omitted them?)\".into());\n}","typeGuard":null,"tryCatchPattern":"let range = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {\n    derive_min_max_time(stats.clone())\n}));\nif let Err(_) = range {\n    return Err(CatalogError::IncompleteTimeStatistics.into());\n}","preventionTips":["Write parquet with statistics enabled (avoid parquet statistics_enabled=false).","Screen external files for footer statistics on the time column before cataloging.","Handle all-null/empty time columns upstream (reject or populate) rather than letting derive_min_max_time see them.","Add unit fixtures covering the no-min-statistics shape so tooling fails gracefully in tests, not prod."],"tags":["rust","influxdb-iox","parquet","statistics","time-column","panic"],"backgroundTag":"missing-column-statistics","analyzedSha":"d28e26e048401c53cbb98cf2d6ab0cf1e98048ca","analyzedAt":"2026-08-16T19:53:34.623Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}