Skip to content

fix: reorder type checks after stale validation and fix zset store destination handling#261

Open
guozhihao-224 wants to merge 3 commits intomainfrom
fix/type_check
Open

fix: reorder type checks after stale validation and fix zset store destination handling#261
guozhihao-224 wants to merge 3 commits intomainfrom
fix/type_check

Conversation

@guozhihao-224
Copy link
Copy Markdown
Collaborator

@guozhihao-224 guozhihao-224 commented Apr 11, 2026

Summary

Reorder type checks to occur after stale validation across set, string, and sorted-set operations, ensuring correct WRONGTYPE responses for live non-matching keys while treating stale keys as non-existent.

Key changes

  • Reorder stale/type check flow (set, zset): Replace is_valid() with centralized self.is_stale() gating, and move check_type() to after staleness validation. This ensures stale keys are treated as absent before any type check runs.
  • Fix zadd meta parsing order: Move ParsedZSetsMetaValue::new() after is_stale()/check_type() to prevent wrong error for non-ZSet keys.
  • Fix ZINTERSTORE/ZUNIONSTORE destination cleanup: Replace early return Ok(0) with a flag + break so the destination lock and cleanup logic always executes, preventing stale destination data from surviving.
  • Fix ZINTERSTORE/ZUNIONSTORE destination type check: Remove check_type on destination key — per Redis semantics, store commands overwrite the destination regardless of its current type.
  • Fix mget for expired entries: Short-circuit on stale values, returning None without attempting UTF-8 decode.

Tests added

  • scard returns WRONGTYPE for a live string key
  • zcard returns WRONGTYPE for a live string key
  • mget returns None for wrong-type and missing keys

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 11, 2026

📝 Walkthrough

Walkthrough

Reworks staleness gating across Redis set, string, and zset operations: replaces many is_valid() checks with self.is_stale(...) calls, short-circuits on stale meta, moves type checks after stale validation, and adds tests for wrong-type behaviors.

Changes

Cohort / File(s) Summary
Set ops
src/storage/src/redis_sets.rs
Replaced many ParsedSetsMetaValue::is_valid() usages with self.is_stale(&val)? gating; early-return for stale metas; moved type checks and meta parsing to after stale check; updated smove destination-version logic to depend on staleness.
Sorted-set (ZSet) ops
src/storage/src/redis_zsets.rs
Replaced is_valid() guards with self.is_stale(&base_meta_val)? across read/write paths; early-return or reroute when meta is stale (e.g., zincrbyzadd path); refined zset_store_operation to use is_stale and introduced result_empty handling; adjusted destination-deletion conditions.
String ops
src/storage/src/redis_strings.rs
mget now short-circuits per-key on stale values (push None then continue), performing UTF-8 extraction only for non-stale entries.
Tests — Sets
src/storage/tests/redis_set_test.rs
Added test asserting scard on a live non-set (string) key returns a WRONGTYPE error.
Tests — Strings
src/storage/tests/redis_string_test.rs
Added test verifying mget returns None for wrong-type and missing keys while returning values for valid string keys.
Tests — ZSets
src/storage/tests/redis_zset_test.rs
Added test asserting zcard on a live non-zset (string) key returns a WRONGTYPE error.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

🧹 Updates

Suggested reviewers

  • AlexStocks
  • marsevilspirit

Poem

🐰 I hopped through meta, checked for stale time,
Pushed nils, skipped work—kept the code trim and prime.
Wrong-type alarms I help to rhyme,
A little clean hop, a small happy chime. ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Title check ✅ Passed The title accurately describes the main changes: reordering type checks after stale validation across multiple Redis data structure files and fixing zset store destination handling.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/type_check

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/storage/src/redis_zsets.rs (1)

1143-1160: ⚠️ Potential issue | 🟠 Major

Don’t return before clearing the destination for empty intersections.

For ZINTERSTORE, a missing or stale source means the result is empty, but the destination still needs to be overwritten. These early return Ok(0) paths skip the destination cleanup/write block entirely, so an existing destination can survive even though the command reports 0.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/storage/src/redis_zsets.rs` around lines 1143 - 1160, The early returns
in the ZINTERSTORE handling (the branches checking base_meta_val.is_empty() and
self.is_stale(&base_meta_val) that currently do `return Ok(0)` when is_inter is
true) skip the destination cleanup/write and leave an old destination intact;
change these returns to instead set a flag (e.g., `empty_result = true`) or
update the loop state and break out so processing continues to the common
destination-write/clear logic, and then ensure the destination is
overwritten/cleared and returns 0 at the end; update the branches in the
function handling ZINTERSTORE (use the `is_inter` check, the `base_meta_val`
empty/stale branches, and the existing destination write/cleanup code) to follow
this flow so destination is always cleared when intersection yields empty.
🧹 Nitpick comments (1)
src/storage/src/redis_sets.rs (1)

802-806: Consider simplifying the is_valid() check.

After the stale check at line 732, if dest_exists is true and the destination was stale, initial_meta_value() was called which resets the metadata (including count to 0 and clearing expiration). The is_valid() check here may be redundant since:

  • If dest was stale → initial_meta_value() resets etime → is_valid() returns true, count is 0 → 0 + 1 = 1
  • If dest was not stale → is_valid() returns true, count is existing value → count + 1

The current code works correctly, but the condition could potentially be simplified to track whether the destination was stale explicitly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/storage/src/redis_sets.rs` around lines 802 - 806, The is_valid() check
after handling a stale destination is redundant; instead, track whether you
reset the destination metadata (e.g., via a boolean like dest_was_stale) when
calling initial_meta_value() and then unconditionally set the new count: if
dest_exists { if dest_was_stale { dest_meta.set_count(1) } else {
dest_meta.set_count(dest_meta.count() + 1) } } else { dest_meta.set_count(1) } —
update the logic around dest_meta, dest_exists, initial_meta_value(),
is_valid(), set_count(), and count() so the code no longer relies on is_valid()
to distinguish stale vs. fresh state.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/storage/src/redis_zsets.rs`:
- Around line 82-93: Parsed zset-meta is being constructed before we verify the
live key type, which causes non-zset live keys to error during meta parsing;
move the call to ParsedZSetsMetaValue::new(...) so it occurs after
check_type(&base_meta_val, DataType::ZSet) and the is_stale check, i.e., first
call self.is_stale(&base_meta_val) and self.check_type(...) to ensure WRONGTYPE
is returned for live non-zset keys, then parse with
ParsedZSetsMetaValue::new(...); apply the same reorder to the zadd code path so
zadd also performs type and staleness checks before invoking
ParsedZSetsMetaValue::new.

In `@src/storage/tests/redis_set_test.rs`:
- Line 170: Formatting mismatch on the call to redis.set(key, b"x").expect("set
failed"); — run rustfmt to fix the whitespace/formatting by running `cargo fmt
--package storage` (or apply rustfmt to the test file) so the line conforms to
project formatting; target the test containing the redis.set call to reformat
and then re-run tests.

In `@src/storage/tests/redis_string_test.rs`:
- Line 350: Formatting mismatch on the `.hset(hash_key, b"field", b"value")`
call in the Redis string tests: run rustfmt to fix it (e.g., run `cargo fmt
--package storage`) or apply rustfmt formatting to the test containing the
`.hset` invocation so the line matches project formatting; ensure the `hset`
call and surrounding test are formatted by rustfmt and commit the updated file.

---

Outside diff comments:
In `@src/storage/src/redis_zsets.rs`:
- Around line 1143-1160: The early returns in the ZINTERSTORE handling (the
branches checking base_meta_val.is_empty() and self.is_stale(&base_meta_val)
that currently do `return Ok(0)` when is_inter is true) skip the destination
cleanup/write and leave an old destination intact; change these returns to
instead set a flag (e.g., `empty_result = true`) or update the loop state and
break out so processing continues to the common destination-write/clear logic,
and then ensure the destination is overwritten/cleared and returns 0 at the end;
update the branches in the function handling ZINTERSTORE (use the `is_inter`
check, the `base_meta_val` empty/stale branches, and the existing destination
write/cleanup code) to follow this flow so destination is always cleared when
intersection yields empty.

---

Nitpick comments:
In `@src/storage/src/redis_sets.rs`:
- Around line 802-806: The is_valid() check after handling a stale destination
is redundant; instead, track whether you reset the destination metadata (e.g.,
via a boolean like dest_was_stale) when calling initial_meta_value() and then
unconditionally set the new count: if dest_exists { if dest_was_stale {
dest_meta.set_count(1) } else { dest_meta.set_count(dest_meta.count() + 1) } }
else { dest_meta.set_count(1) } — update the logic around dest_meta,
dest_exists, initial_meta_value(), is_valid(), set_count(), and count() so the
code no longer relies on is_valid() to distinguish stale vs. fresh state.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a00e8307-6d0e-4a7b-88c0-5c1aeda43102

📥 Commits

Reviewing files that changed from the base of the PR and between 60cd512 and 73b4f0f.

📒 Files selected for processing (6)
  • src/storage/src/redis_sets.rs
  • src/storage/src/redis_strings.rs
  • src/storage/src/redis_zsets.rs
  • src/storage/tests/redis_set_test.rs
  • src/storage/tests/redis_string_test.rs
  • src/storage/tests/redis_zset_test.rs

Comment thread src/storage/src/redis_zsets.rs Outdated
Comment thread src/storage/tests/redis_set_test.rs
Comment thread src/storage/tests/redis_string_test.rs
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
src/storage/src/redis_zsets.rs (1)

82-93: ⚠️ Potential issue | 🟠 Major

zadd still parses ZSET metadata before stale/type gating.

Line 82 constructs ParsedZSetsMetaValue before the Line 87 stale check and the Line 91 type check, so a non-ZSET key can still fail meta parsing before this path returns WRONGTYPE or treats an expired key as absent.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/storage/src/redis_zsets.rs` around lines 82 - 93, ParsedZSetsMetaValue is
being constructed before gating by is_stale and check_type in the zadd path,
which can cause meta parsing to error for non-ZSET or expired keys; change the
order in the zadd implementation so you first call
self.is_stale(&base_meta_val)? and if not stale call
self.check_type(&base_meta_val, DataType::ZSet)? before constructing
ParsedZSetsMetaValue, and for the stale branch use ParsedZSetsMetaValue::new
only if you need parsed fields (or use parsed_zset_meta.initial_meta_value()
only after safe parsing), ensuring any use of parsed_zset_meta happens after the
type/stale checks to avoid premature parsing errors (look for
ParsedZSetsMetaValue, is_stale, check_type, and the zadd function to update).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/storage/src/redis_zsets.rs`:
- Around line 1154-1159: In the zinterstore/zunionstore scan logic, remove the
early return Ok(0) when encountering a stale or missing source (the branch that
currently returns for is_inter) and instead set a local flag (e.g., result_empty
= true) and fall through to the existing destination-delete/write path so the
destination lock acquisition and cleanup code (the destination lock and final
cleanup block) still run; apply the same change to the "missing-key" branch so
both cases mark the result as empty and continue rather than returning
prematurely, then have the final write/delete logic check result_empty and
perform the delete/empty-write behavior before returning 0.
- Around line 1225-1227: The code currently calls
self.check_type(&dest_meta_val, DataType::ZSet) and rejects non-ZSET destination
keys, but Redis semantics overwrite any existing type; remove the unconditional
check_type call and instead only perform ZSET-specific physical cleanup when the
existing destination is actually a ZSET: check if dest_meta_val is non-empty and
not stale (using self.is_stale), then attempt to parse
ParsedZSetsMetaValue::new(&dest_meta_val[..]) and if parsing succeeds treat it
as an existing ZSET and run the cleanup logic, otherwise skip cleanup and
proceed with the overwrite unconditionally (do not return WRONGTYPE).

---

Duplicate comments:
In `@src/storage/src/redis_zsets.rs`:
- Around line 82-93: ParsedZSetsMetaValue is being constructed before gating by
is_stale and check_type in the zadd path, which can cause meta parsing to error
for non-ZSET or expired keys; change the order in the zadd implementation so you
first call self.is_stale(&base_meta_val)? and if not stale call
self.check_type(&base_meta_val, DataType::ZSet)? before constructing
ParsedZSetsMetaValue, and for the stale branch use ParsedZSetsMetaValue::new
only if you need parsed fields (or use parsed_zset_meta.initial_meta_value()
only after safe parsing), ensuring any use of parsed_zset_meta happens after the
type/stale checks to avoid premature parsing errors (look for
ParsedZSetsMetaValue, is_stale, check_type, and the zadd function to update).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e3a6d039-60e7-49d2-9c95-3e2b7343a685

📥 Commits

Reviewing files that changed from the base of the PR and between 73b4f0f and a55f171.

📒 Files selected for processing (4)
  • src/storage/src/redis_zsets.rs
  • src/storage/tests/redis_set_test.rs
  • src/storage/tests/redis_string_test.rs
  • src/storage/tests/redis_zset_test.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/storage/tests/redis_zset_test.rs
  • src/storage/tests/redis_string_test.rs

Comment thread src/storage/src/redis_zsets.rs
Comment thread src/storage/src/redis_zsets.rs Outdated
- Move ParsedZSetsMetaValue::new() after is_stale/check_type in zadd
  to prevent wrong error for non-ZSet keys
- Replace early return Ok(0) in zinterstore/zunionstore with break +
  flag so destination cleanup always runs
- Remove check_type on destination key in zinterstore/zunionstore
  since Redis overwrites destination regardless of its current type

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@guozhihao-224 guozhihao-224 changed the title refactor: Correct the order of type_check fix: reorder type checks after stale validation and fix zset store destination handling Apr 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant