{"record":{"id":"32e8d5bb7dcaf695","repo":"influxdata/influxdb","slug":"split-plan-does-not-accept-empty-split-times","errorCode":null,"errorMessage":"Split plan does not accept empty split_times","messagePattern":"Split plan does not accept empty split_times","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/iox_query/src/frontend/reorg.rs","lineNumber":185,"sourceCode":"    ///  X | time\n    /// ---+-----\n    ///  c | 4000\n    /// ```\n    pub fn split_plan<I>(\n        &self,\n        table_id: TableId,\n        table_name: Arc<str>,\n        schema: &Schema,\n        chunks: I,\n        output_sort_key: SortKey,\n        split_times: Vec<i64>,\n    ) -> Result<LogicalPlan>\n    where\n        I: IntoIterator<Item = Arc<dyn QueryChunk>>,\n    {\n        // split_times must have values\n        if split_times.is_empty() {\n            panic!(\"Split plan does not accept empty split_times\");\n        }\n\n        let mut builder = ProviderBuilder::new(Arc::clone(&table_name), schema.clone())\n            .with_enable_deduplication(true);\n\n        for chunk in chunks {\n            builder = builder.add_chunk(chunk);\n        }\n\n        let provider = builder.build().context(CreatingScanSnafu {\n            table_name: table_name.as_ref(),\n            table_id,\n        })?;\n        let plan_builder = Arc::new(provider)\n            .into_logical_plan_builder()\n            .context(BuildingPlanSnafu)?;\n        let sort_expr = logical_sort_key_exprs(&output_sort_key);\n        let plan = plan_builder","sourceCodeStart":167,"sourceCodeEnd":203,"githubUrl":"https://github.com/influxdata/influxdb/blob/06200ef96ba82c5f6727e5038a83af8e722c6875/core/iox_query/src/frontend/reorg.rs#L167-L203","documentation":"In QueryDatabase::split_plan (core/iox_query/src/frontend/reorg.rs), the split_times argument must contain at least one timestamp because each entry defines a split boundary for reorganization. An empty Vec yields zero split expressions, which is meaningless, so the function panics deliberately as an internal invariant check.","triggerScenarios":"Calling split_plan with an empty iterator/Vec for split_times while supplying chunks to reorganize.","commonSituations":"Reorg tooling computing split times from statistics that were empty; users calling split_plan directly with a default/empty Vec; upstream filtering that removed all candidate boundaries.","solutions":["Guard the call site: return early or skip reorganization when split_times is empty.","Compute split_times from actual chunk time ranges so at least one boundary exists.","If empty splits are legitimately expected, use the builder API directly instead of split_plan.","Replace the panic with Result-returning validation if you maintain a fork."],"exampleFix":"// before\nlet plan = db.split_plan(&table, &schema, chunks, &[])?;\n// after\nif split_times.is_empty() {\n    return Ok(None); // nothing to split\n}\nlet plan = db.split_plan(&table, &schema, chunks, &split_times)?;","handlingStrategy":"validation","validationCode":"if split_times.is_empty() {\n    // skip reorg entirely; there are no boundaries to split on\n    return Ok(None);\n}","typeGuard":null,"tryCatchPattern":"// split_plan panics (not Result); guard before calling\nassert!(!split_times.is_empty(), \"split_times must not be empty\");\nlet plan = split_plan(...);","preventionTips":["Never call split_plan with an empty Vec — check len() > 0 first.","Derive split_times from actual chunk time ranges.","Wrap split_plan in a helper that validates inputs and returns Result."],"tags":["query","reorg","panic","invariant"],"backgroundTag":"empty-required-field","analyzedSha":"06200ef96ba82c5f6727e5038a83af8e722c6875","analyzedAt":"2026-09-19T12:55:30.003Z","contentChangedAt":"2026-09-19T12:55:30.003Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}