feat(clickhouse): add optional retention TTL#128
Conversation
Add setRetention(?int $days) to the ClickHouse adapter. When set, setup() applies an idempotent `ALTER TABLE ... MODIFY TTL toDateTime(time) + INTERVAL <days> DAY` so rows past the retention window are dropped by background merges. - Retention lives next to the rest of the table's schema management in setup(), consistent with how columns/indexes/settings are handled. - Applied as a separate ALTER (not in CREATE TABLE) so it also covers already-existing tables; MODIFY TTL is a no-op when unchanged, keeping setup() re-runnable. - materialize_ttl_after_modify = 0 defers the purge to background merges instead of an immediate mutation. - Default null preserves existing behaviour (no TTL). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Greptile SummaryThis PR adds optional retention TTL support to the ClickHouse audit adapter via
Confidence Score: 5/5The change is safe to merge; the new TTL logic is isolated to setup() and the setter/getter pair, with no impact on the read or write hot paths. The core feature is correct and idempotent: MODIFY TTL is a no-op when the expression is unchanged, and REMOVE TTL properly strips an existing TTL. The two concerns raised are both non-blocking — the unconditional REMOVE TTL call adds a wasted round-trip for callers using the default null retention, and the error-message substring guard is fragile across ClickHouse versions. Neither causes wrong data or broken behaviour today. The REMOVE TTL else-branch in src/Audit/Adapter/ClickHouse.php around line 837 is worth a second look for the unconditional overhead and the error-message matching strategy. Important Files Changed
Reviews (3): Last reviewed commit: "fix(clickhouse): tolerate REMOVE TTL on ..." | Re-trigger Greptile |
| public function setRetention(?int $days): self | ||
| { | ||
| if ($days !== null && $days < 1) { | ||
| throw new Exception('Retention must be a positive number of days'); | ||
| } | ||
| $this->retention = $days; | ||
| return $this; | ||
| } |
There was a problem hiding this comment.
No test coverage for the new retention feature
setRetention, getRetention, the validation path (negative/zero days → Exception), and the TTL branch inside setup() are not exercised by any test in ClickHouseTest. At minimum, a unit test for the setter/getter contract and the guard clause would protect against regressions without requiring a live ClickHouse instance.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
…n private; add tests Addresses review feedback on the retention TTL: - setup() now issues ALTER TABLE ... REMOVE TTL on the null path so disabling retention actually strips a previously applied TTL, matching the documented "pass null to disable" contract. REMOVE TTL is a no-op on a table without a TTL, so setup() stays idempotent. - $retention is now private, matching the other sensitive fields and preventing subclasses from bypassing the positive-day guard. - Add unit tests for setRetention/getRetention, the null path, and the zero/negative validation guard. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
REMOVE TTL is not a no-op — ClickHouse raises error 36 (BAD_ARGUMENTS, "Table doesn't have any table TTL expression, cannot remove") when the table has no TTL. That broke setup() on every fresh table when retention is disabled. Swallow that specific error to keep setup() idempotent. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
What
Adds
setRetention(?int $days)to the ClickHouse audit adapter. When set,setup()applies an idempotent TTL to the audit table so rows past the retention window are dropped by background merges:Default
nullpreserves current behaviour (no TTL).Why in setup()
Retention is part of the table's schema, and
setup()already owns everything else about it (columns, indexes, engine, partition key). Keeping TTL here means every caller that already runssetup()gets retention with no extra plumbing, and the table-naming logic isn't duplicated downstream.How
ALTER … MODIFY TTL, not baked intoCREATE TABLE, becauseCREATE TABLE IF NOT EXISTSwon't touch an already-existing table.MODIFY TTLis a no-op when the expression is unchanged, sosetup()stays re-runnable/idempotent.SETTINGS materialize_ttl_after_modify = 0defers the purge to normal background merges instead of an immediate, I/O-heavy mutation.timeisDateTime64(3);toDateTime(time)narrows it for the TTL expression.setRetention()rejects non-positive day counts.Pint (
composer lint) and PHPStan level max (composer check) pass on the changed file.🤖 Generated with Claude Code