fix: reorder type checks after stale validation and fix zset store destination handling - #261
fix: reorder type checks after stale validation and fix zset store destination handling#261guozhihao-224 wants to merge 6 commits into
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughGate Redis Set/ZSet/String operations on metadata staleness (self.is_stale), move type checks after stale validation, update smove member-key encoding, and add tests for wrong-type and stale behaviors. ChangesStaleness gating and set/zset fixes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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 | 🟠 MajorDon’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 earlyreturn Ok(0)paths skip the destination cleanup/write block entirely, so an existing destination can survive even though the command reports0.🤖 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 theis_valid()check.After the stale check at line 732, if
dest_existsis true and the destination was stale,initial_meta_value()was called which resets the metadata (including count to 0 and clearing expiration). Theis_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 + 1The 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
📒 Files selected for processing (6)
src/storage/src/redis_sets.rssrc/storage/src/redis_strings.rssrc/storage/src/redis_zsets.rssrc/storage/tests/redis_set_test.rssrc/storage/tests/redis_string_test.rssrc/storage/tests/redis_zset_test.rs
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
src/storage/src/redis_zsets.rs (1)
82-93:⚠️ Potential issue | 🟠 Major
zaddstill parses ZSET metadata before stale/type gating.Line 82 constructs
ParsedZSetsMetaValuebefore the Line 87 stale check and the Line 91 type check, so a non-ZSET key can still fail meta parsing before this path returnsWRONGTYPEor 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
📒 Files selected for processing (4)
src/storage/src/redis_zsets.rssrc/storage/tests/redis_set_test.rssrc/storage/tests/redis_string_test.rssrc/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
|
@guozhihao-224 辛苦处理CI问题 |
There was a problem hiding this comment.
Pull request overview
This PR adjusts Redis-compatible stale-key handling so expired keys are treated as absent before type validation, while live keys of the wrong type still return the expected behavior.
Changes:
- Reordered stale/type checks across set and sorted-set operations.
- Updated
MGEThandling for wrong-type/missing entries. - Added regression tests for set, sorted-set, and string command behavior.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
src/storage/src/redis_zsets.rs |
Reorders stale/type validation and updates zset store destination handling. |
src/storage/src/redis_sets.rs |
Reorders stale/type validation for set operations. |
src/storage/src/redis_strings.rs |
Adjusts MGET nil handling flow. |
src/storage/tests/redis_zset_test.rs |
Adds ZCARD wrong-type regression coverage. |
src/storage/tests/redis_set_test.rs |
Adds SCARD wrong-type regression coverage. |
src/storage/tests/redis_string_test.rs |
Adds MGET wrong-type and missing-key coverage. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
AlexStocks
left a comment
There was a problem hiding this comment.
审计结论:发现 2 个需要处理的 [P1] 项,均已落到 diff 行评论。
补充说明:
cargo clippy --all-features --workspace -- -D warnings -D clippy::unwrap_used未能产出代码级结果,当前环境缺少dlltool.exe,构建在getrandom依赖处中断。- 本次 review 保持只读,未修改源码。
- 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>
- redis_zsets.rs (zadd): when the existing meta is stale, build a fresh ZSetsMetaValue instead of reparsing the old buffer. The previous code preserved the original data_type byte (Hash/Set/etc.), so persisting it back broke subsequent ZSet ops with WRONGTYPE. - redis_sets.rs (smove): gate the destination meta on is_stale and check_type before parsing, so a non-Set value returns WRONGTYPE rather than InvalidFormat. Stale destinations are rewritten as fresh Set meta. - redis_sets.rs (smove): switch source/destination member-key encoding from a hand-rolled order (version before key) to MemberDataKey, which matches what sadd writes; smove could not previously locate any member written by sadd. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ssion tests - Fix import order in redis_zset_test.rs: std first, then external crates (bytes, kstd), then project-internal storage:: imports - Add test_zinterstore_dest_live_wrong_type_is_overwritten: verifies that ZINTERSTORE silently overwrites a live wrong-type destination key - Add test_zunionstore_dest_stale_wrong_type_is_overwritten: verifies that ZUNIONSTORE treats a stale wrong-type destination as absent and succeeds Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
fc0f8fe to
61cf661
Compare
base_meta_value_format → format_base_meta_value base_value_format → format_base_value Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Summary
Reorder type checks to occur after stale validation across set, string, and sorted-set operations, ensuring correct
WRONGTYPEresponses for live non-matching keys while treating stale keys as non-existent.Key changes
is_valid()with centralizedself.is_stale()gating, and movecheck_type()to after staleness validation. This ensures stale keys are treated as absent before any type check runs.ParsedZSetsMetaValue::new()afteris_stale()/check_type()to prevent wrong error for non-ZSet keys.return Ok(0)with a flag + break so the destination lock and cleanup logic always executes, preventing stale destination data from surviving.check_typeon destination key — per Redis semantics, store commands overwrite the destination regardless of its current type.Nonewithout attempting UTF-8 decode.Tests added
scardreturnsWRONGTYPEfor a live string keyzcardreturnsWRONGTYPEfor a live string keymgetreturnsNonefor wrong-type and missing keysSummary by CodeRabbit
Bug Fixes
Tests