1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
//! Shared `prepare → query_map → collect → format!()-wrapped errors`
//! boilerplate. Without this helper every analysis would copy-paste
//! the same 7-line pattern with only the SQL constant, params, and
//! mapper closure varying, and the error-message format would drift
//! across analyses.
//!
//! Usage:
//!
//! ```ignore
//! use crate::analyses::query::query_map_collect;
//! let rows: Vec<MyRow> = query_map_collect(
//! db, &sql, duckdb::params![opts.min_revs, row_limit], "my-analysis",
//! |r| Ok(MyRow { x: r.get(0)?, y: r.get(1)? }),
//! )?;
//! ```
use crateFactsDb;
use crate::;
/// The current UTC instant as a `YYYY-MM-DD HH:MM:SS` string — the exact
/// UTC-naive frame and format commit dates are stored in
/// (`facts::ingest::consumer`'s timestamp formatter), for embedding as a
/// `DuckDB` `TIMESTAMP` literal.
///
/// "Now" is resolved in Rust rather than through SQL `now()` / `timezone()` /
/// `AT TIME ZONE` deliberately: those are `DuckDB`'s ICU-extension functions,
/// they render in the session timezone (so a bare `CAST(now() AS TIMESTAMP)`
/// on a runner behind UTC could clamp a commit made minutes ago), and the
/// value they produce carries an ICU timestamp type whose `- INTERVAL`
/// operator does not bind in every position a subquery embeds it. A plain
/// `TIMESTAMP` literal sidesteps all three, and matches how `code-age` and
/// `knowledge-islands` already resolve their wall-clock anchor.
/// SQL expression for the repository's window anchor — the "now" that every
/// trailing-window and time-decay term is measured against — clamped so a
/// single future-dated commit cannot become "now" for the whole analysis.
///
/// Drops in wherever a data-controlled anchor previously read a bare
/// `MAX(<col>)` over `commits`, emitting `LEAST(MAX(<col>), TIMESTAMP
/// '<utc-now>')`. `col` is the `commits` timestamp column to anchor on:
/// `"date"` (author date — the anchor of nearly every window) or
/// `"committer_date"`. The clamp caps the anchor at the wall clock: a
/// timestamp set to the far future (a bad `GIT_AUTHOR_DATE`, contributor clock
/// skew, or a mis-imported commit) would otherwise collapse active-author
/// windows, underflow the knowledge-decay terms, and shift the new-code
/// born/touched partition — every one of which anchors on `MAX(commits.date)`
/// as "now". The "now" literal comes from [`wall_clock_utc_literal`], so both
/// operands of `LEAST` are plain `TIMESTAMP`s in one UTC frame.
///
/// ## Determinism
///
/// On any repository whose newest commit predates the current instant — every
/// healthy repository — `MAX(<col>)` is the smaller operand, so `LEAST` returns
/// it unchanged: the output is byte-identical to the un-clamped form and does
/// not depend on when the query runs, even though the embedded literal does.
/// Only a repository that actually carries a future-dated commit becomes
/// wall-clock dependent, and there the anchor tracks the real instant of
/// computation; a persisted fact-store cache pins whatever anchor the first
/// run observed until the cache is rebuilt. That wall-clock dependence is the
/// pathological state being defended against, not a regression of the healthy
/// path.
///
/// ## Scope
///
/// This clamps only the *data-controlled* anchor idiom (a bare `MAX(<col>)`
/// standing in for "now"). The `code-age` / `knowledge-islands` family instead
/// anchors on a wall-clock instant (or `--age-time-now`) and filters
/// `<col> <= anchor`, so it never trusts a future date in the first place and
/// is left untouched. Unifying the two idioms is a separate design question,
/// not part of making the data-controlled anchor safe.
///
/// `col` is a fixed internal column name, never user input.
/// CTE body: every `(raw_name, raw_email)` alias row that is NOT
/// bot-classified, alongside the canonical it resolves to.
///
/// `author_aliases` is keyed on the exact `(name, email)` pair a commit
/// shipped with, and `is_bot` rides that same pair (see the schema comment
/// on `author_aliases` in `schema_v1.sql`) — so a human and a bot sharing
/// one canonical identity (a `--team-map` fold, a `bots.rs` name-or-email
/// pattern hit, or the raw-email canonical fallback landing two different
/// names on one email) classify independently. A consumer that needs "was
/// THIS commit authored by a human" joins its `commits` row to this CTE on
/// the exact pair — `commits.author_name = raw_name AND commits.author_email
/// = raw_email` — rather than testing a canonical-level flag.
/// `author_aliases` is `PRIMARY KEY (raw_name, raw_email)`, so that join is
/// always 1:1 and never fans out.
///
/// Replaces the `canonical`-level `SELECT canonical, BOOL_OR(is_bot) ...
/// GROUP BY canonical` lookup and `... HAVING NOT BOOL_OR(is_bot)` filter
/// that used to collapse bot classification to the canonical: either flag
/// (`BOOL_OR`) marks a canonical bot the instant ANY alias sharing it is
/// bot-classified, or `HAVING NOT BOOL_OR` drops the canonical's rows
/// entirely — both erase a mixed canonical's human commits alongside the
/// bot's. Joining on the pair instead excludes bot-classified rows
/// row-wise; a canonical stays eligible through its human rows.
pub const HUMAN_ALIASES_CTE: &str = "human_aliases AS (
SELECT raw_name, raw_email, canonical
FROM author_aliases
WHERE NOT is_bot
)";
/// Prepare + `query_map` + collect, with uniform `CodeLoreError::Analysis`
/// error context at each step. `label` is interpolated into the error
/// messages so debug output identifies which analysis failed.
///
/// # Errors
///
/// Returns [`CodeLoreError::Analysis`] on prepare, query, or row-mapping
/// failure; the error message is `"<step> <label>: <underlying>"`.
/// Emit the `DuckDB` EXPLAIN plan for `sql` + `params` to stderr if
/// `opts.explain` is on. No-op otherwise. Shared so every analysis can
/// add `--explain` support in one line instead of copying the
/// `if opts.explain { db.explain_sql(...)?; eprintln!(...); }` block.
///
/// # Errors
///
/// Returns [`CodeLoreError::Analysis`] only if `--explain` is on AND
/// `db.explain_sql` fails. Off path is infallible.