rows: cache the parseTime format index per result column
Background
(*conn).parseTime ran on every TEXT-stored DATE / DATETIME / TIMESTAMP column read in Next(). The function tried (*conn).parseTimeString first and then walked parseTimeFormats[0..6] sequentially until time.Parse matched the row's value. For the canonical SQLite TEXT datetime format ("2006-01-02 15:04:05.999999999", index 2) every row paid two failed time.Parse attempts before the matching one. Each failed Parse allocates a time.ParseError, so the per-row cost from the format search alone was ~5 allocs (warmup + ParseError structs + the matching value).
What this MR does
Add a sticky per-column hint cache:
rows.parseFmtIdx []int8, sized atnewRows()to the column count and initialised to-1(no match recorded yet).(*conn).parseTimenow takeshintIdx intand returns(value, ok, matchedIdx). It trieshintIdxfirst if in range, then walks the list skipping the index it just tried. Old contract is preserved: on failure the returned value is the original string andokisfalse.Next()records the first successful index per column and reuses it on subsequent rows. The cache is sticky: it is set once and not overwritten, so a mixed-format column still pays the original fall-through cost on non-matching rows but a steady column wins on every row after the first.
No API change. The fall-through chain is preserved bit-for-bit so any row the old code would have parsed still parses to the same value.
Benchmark
darwin/arm64 (Apple M3), 1000-row SELECT of a DATETIME TEXT column in the canonical SQLite format, -benchtime=2s, before -> after:
| allocs/op | B/op | ns/op | |
|---|---|---|---|
| Baseline | 10013 | 392417 | 633531 |
| With cache | 5019 | 168672 | 397843 |
| Diff | -50% | -57% | -37% |
The 5 allocs saved per row break down as the two failed time.Parse attempts that the cache skips (each producing a *time.ParseError) plus an internal allocation in the time package's format machinery. Bytes saved (~223 per row) are dominated by those ParseError structures with their captured layout/value strings. ns/op gain is the wall-time cost of running those format-fail paths.
Tests
TestParseTimeFormatCache walks five rows whose TEXT values span three different parseTimeFormats entries (three canonical-format rows that prime the cache at index 2, then one ISO-T row at index 3, then one date-only row at index 6). Each row is scanned as time.Time and asserted against the expected wall-clock value, so any stale-hint regression or fall-through skip bug would surface as a scan failure or a wrong time.
Full go test -count=1 ./... stays green (467s on darwin/arm64).
Backward compatibility
Pure perf fix; no exported API change, no behaviour change visible to callers. The returned driver.Value for every parsed row is identical to before.