fix(array): check the audit_retain_ms conversion instead of casting - #234
Merged
Conversation
Replace the `as i64` cast on CREATE ARRAY's audit_retain_ms with a checked try_from that errors out on overflow, since a silently wrapped negative value would place the purge horizon in the future and purge every tile version.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
CREATE ARRAYcarriedaudit_retain_msfrom the parser asu64and stored it signed with a raw cast:It is stored signed because downstream it is subtracted from a wall-clock millisecond to place the purge horizon (
now_ms - audit_retain_ms, inengine/array/retention.rs). A value abovei64::MAXwraps negative there, which puts the horizon in the future — and a retention pass with a future horizon purges every tile version the array has. That is the exact inverse of what a retention setting asks for, and it would happen silently.This is not reachable today. Both parsers that produce the value reject a negative (
WITH (audit_retain_ms = ...): must be >= 0, and the same onALTER ... SET) andexpect_intyieldsi64, so every value arrives inside0..=i64::MAX. The cast cannot currently wrap.What it removes is the dependence on that. The invariant is established in two parsers and then re-laundered by
asat each hop; an entry point added later — a sync path, a catalog restore, a programmatic DDL API — that does not repeat the range check would produce a negative retention with nothing between it and a full purge. The conversion is now checked where the value is stored, so such a path fails with a plan error instead.The companion change in
nodedb-lite(789122b, onmain) does the same thing on the embedded side, where the DDL value was being dropped entirely before it reached the engine.Validation
cargo nextest run -p nodedb --all-features -E 'test(array)'— 243/243cargo nextest run -p nodedb --all-features— 7940/7942cargo clippy --all-targets -p nodedb -- -D warnings— cleancargo fmt --all— cleanThe two failures are
crash_wal_truncation::kv_row_survives_wal_segment_truncationandshutdown_abort_offender::offender_task_aborted_at_500ms_budget. Both are timing-bounded and both pass standalone — 20 s → 6.1 s and 60 s → 1.6 s — so they are timeouts under full-suite parallel load. Neither test creates an array, so neither can reach this code path. The same suite ran 7942/7942 on the immediately preceding commit.