{"record":{"id":"bc9b6d7fe0198ecf","repo":"atuinsh/atuin","slug":"issue-in-stats-duration-over-time-query","errorCode":null,"errorMessage":"issue in stats duration over time query","messagePattern":"issue in stats duration over time query","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/atuin-client/src/database.rs","lineNumber":892,"sourceCode":"        // string sort, which won't be correct.\n        let mut duration_over_time = SqlBuilder::select_from(\"history\");\n        duration_over_time\n            .fields(&[\n                \"strftime('01-%m-%Y', ROUND(timestamp / 1000000000), 'unixepoch') AS month_year\",\n                \"avg(duration) as duration\",\n            ])\n            .and_where(\"command = ?1\")\n            .group_by(\"month_year\")\n            .having(\"duration > 0\");\n\n        let prev = prev.sql().expect(\"issue in stats previous query\");\n        let next = next.sql().expect(\"issue in stats next query\");\n        let total = total.sql().expect(\"issue in stats average query\");\n        let average = average.sql().expect(\"issue in stats previous query\");\n        let exits = exits.sql().expect(\"issue in stats exits query\");\n        let day_of_week = day_of_week.sql().expect(\"issue in stats day of week query\");\n        let duration_over_time =\n            duration_over_time.sql().expect(\"issue in stats duration over time query\");\n\n        // The queries are all independent, so run them concurrently on the pool.\n        #[allow(clippy::type_complexity)]\n        let (prev, next, total, average, exits, day_of_week, duration_over_time): (\n            _,\n            _,\n            (i64,),\n            (f64,),\n            Vec<(i64, i64)>,\n            Vec<(String, i64)>,\n            Vec<(String, f64)>,\n        ) = tokio::try_join!(\n            sqlx::query(sqlx::AssertSqlSafe(prev))\n                .bind(h.timestamp.unix_timestamp_nanos() as i64)\n                .bind(&h.session)\n                .map(Self::row_to_history)\n                .fetch_optional(&self.pool),\n            sqlx::query(sqlx::AssertSqlSafe(next))","sourceCodeStart":874,"sourceCodeEnd":910,"githubUrl":"https://github.com/atuinsh/atuin/blob/15fe1318f1df51de604262eb50734c9883d48e7b/crates/atuin-client/src/database.rs#L874-L910","documentation":"Panic while compiling the duration-over-time query. The guarded builder (database.rs:875-883) selects `strftime('01-%m-%Y', ...) AS month_year` and `avg(duration)`, filtered by `command = ?1`, grouped by month_year with `having(\"duration > 0\")` — the only stats query using .having(). All parts are hardcoded, so sql() Err is an unreachable invariant.","triggerScenarios":"Database::stats() with a sqlbuilder version that mishandles the .having() call or the aliased group_by/having combination. Runtime data never reaches the builder; only the library version matters.","commonSituations":"Dependency upgrade that changes having() validation in sqlbuilder; local forks of sqlbuilder. The '01-' hardcoded month format comment shows the string is intentional, so a naive 'fix' of the strftime literal is not the issue.","solutions":["Pin sqlbuilder back to the last working version from Cargo.lock history","Include this builder (with its .having()) in the compile-check unit test for all stats queries","If reproducible on the pinned version, report the .group_by(alias) + .having() combination to sqlbuilder upstream"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"let stats = std::panic::catch_unwind(|| {\n    tokio::task::block_on(database.stats(&history))\n});\nlet _ = stats.map_err(|_| tracing::error!(\"stats duration-over-time builder panicked\"));","preventionTips":["Keep Cargo.lock under review so sqlbuilder cannot drift silently","Unit-test the .having() builder variant specifically on upgrades","Remember the '01-' month format is intentional — do not 'fix' it when diagnosing"],"tags":["rust","sqlite","sqlbuilder","panic","invariant","stats","having-clause","unreachable"],"backgroundTag":"query-builder-invalid-state","analyzedSha":"15fe1318f1df51de604262eb50734c9883d48e7b","analyzedAt":"2026-08-19T08:56:57.719Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}