diff options
Diffstat (limited to 'examples/redis-unstable/tests/unit/moduleapi/keymeta.tcl')
| -rw-r--r-- | examples/redis-unstable/tests/unit/moduleapi/keymeta.tcl | 910 |
1 files changed, 0 insertions, 910 deletions
diff --git a/examples/redis-unstable/tests/unit/moduleapi/keymeta.tcl b/examples/redis-unstable/tests/unit/moduleapi/keymeta.tcl deleted file mode 100644 index ebb7784..0000000 --- a/examples/redis-unstable/tests/unit/moduleapi/keymeta.tcl +++ /dev/null @@ -1,910 +0,0 @@ -# ============================================================================ -# Key Metadata (keymeta) Test Suite -# ============================================================================ -# -# Tests the Redis module key metadata framework: up to 7 independent metadata -# classes (IDs 1-7) can be attached to keys. Class ID 0 is reserved for key -# expiration. -# -# The following features are sensitive to Key Metadata and are tested here: -# -# - KEY EXPIRATION (class ID 0) -# - Stored at ((uint64_t *)kv) - 1 (first metadata slot) -# - Managed via db->expires dictionary -# - Must be preserved/updated when kvobj is reallocated -# - HASH FIELD EXPIRATION (HFE) -# - NOT in kvobj metadata slots (Maybe in the future...) -# - Managed via db->hexpires ebuckets (holds direct kvobj pointer) -# - Must be removed before kvobj reallocation (hashTypeRemoveFromExpires) -# and restored after (hashTypeAddToExpires) -# - MODULE METADATA (class IDs 1-7) -# - Defines metadata lifecycle via callbacks -# - EMBEDDED STRINGS vs. REGULAR OBJECTS -# - Short strings and numbers are embedded into kvobj -# - The rest are kept as distinct objects -# - LAZYFREE -# ============================================================================ - -set testmodule [file normalize tests/modules/test_keymeta.so] - -# Helper procedure to convert class ID to 4-char-id name -proc cname {cid} { - return "KMT$cid" -} - -# Helper procedure to check if a class should keep metadata for a given operation -proc shouldKeep {cid operation classesSpec} { - upvar $classesSpec specs - set spec $specs($cid) - switch $operation { - "copy" { return [string match "*KEEPONCOPY*" $spec] } - "rename" { return [string match "*KEEPONRENAME*" $spec] } - "move" { return [string match "*KEEPONMOVE*" $spec] } - default { return 0 } - } -} - -# Helper procedure to setup a key with metadata -proc setupKeyMeta {keyname numClasses expiryBefore expiryAfter} { - # Set expiry if requested - if {$expiryBefore} { - r expire $keyname 10000 - assert_range [r ttl $keyname] 9990 10000 - } - - # Set metadata for all classes - for {set i 1} {$i <= $numClasses} {incr i} { - # Set twice to verify overwrite behavior - r keymeta.set [cname $i] $keyname "blabla$i" - assert_equal [r keymeta.get [cname $i] $keyname] "blabla$i" - r keymeta.set [cname $i] $keyname "meta$i" - } - - # Verify metadata was set correctly - for {set i 1} {$i <= $numClasses} {incr i} { - assert_equal [r keymeta.get [cname $i] $keyname] "meta$i" - } - - if {$expiryAfter} { - r expire $keyname 10000 - assert_range [r ttl $keyname] 9990 10000 - } - - if {$expiryBefore} { - assert_range [r ttl $keyname] 9990 10000 - } -} - -# Helper procedure to verify metadata after an operation -proc verifyKeyMeta {keyname operation numClasses hasExpiry classesSpec} { - upvar $classesSpec specs - - # Verify expiry - if {$hasExpiry} { - assert_range [r ttl $keyname] 9990 10000 - } - - # Verify metadata based on class spec - for {set i 1} {$i <= $numClasses} {incr i} { - set expected [expr {[shouldKeep $i $operation specs] ? "meta$i" : ""}] - assert_equal [r keymeta.get [cname $i] $keyname] $expected - } -} - -proc flushallAndVerifyCleanup {} { - r flushall - # Verify all metadata is cleaned up properly - assert_equal [r keymeta.active] 0 -} - -start_server {tags {"modules" "external:skip" "cluster:skip"} overrides {enable-debug-command yes}} { - r module load $testmodule - - array set classesSpec {} - set classesSpec(1) "KEEPONCOPY:KEEPONRENAME:KEEPONMOVE:ALLOWIGNORE:RDBLOAD:RDBSAVE" - set classesSpec(2) "KEEPONCOPY:KEEPONRENAME:UNLINKFREE:ALLOWIGNORE:RDBLOAD:RDBSAVE" - set classesSpec(3) "KEEPONCOPY:ALLOWIGNORE:RDBLOAD:RDBSAVE" - set classesSpec(4) "ALLOWIGNORE:RDBLOAD:RDBSAVE" - set classesSpec(5) "KEEPONRENAME:KEEPONMOVE:ALLOWIGNORE:RDBLOAD:RDBSAVE" - set classesSpec(6) "KEEPONRENAME:ALLOWIGNORE:RDBLOAD:RDBSAVE" - set classesSpec(7) "KEEPONMOVE:UNLINKFREE:ALLOWIGNORE:RDBLOAD:RDBSAVE" - - array set classes {} - for {set cid 1} {$cid <= 7} {incr cid} { - set spec $classesSpec($cid) - set classes($cid) [r keymeta.register [cname $cid] 1 $spec] - puts "Registered class $cid with spec $spec" - assert_equal $classes($cid) $cid - } - - # Validates metadata behavior across COPY/RENAME/MOVE operations - # with varying numbers of metadata classes (1-7), key expiration states, - # key types (string/hash), hash field expiration, and metadata class flags - # (KEEPONCOPY, KEEPONRENAME, KEEPONMOVE). - for {set numClasses 1} {$numClasses < 8} {incr numClasses} { - foreach expiryBefore {0 1} { - foreach expiryAfter {0 1} { - set hasExpiry [expr {$expiryBefore || $expiryAfter}] - set expiryStr "expiryBefore=$expiryBefore, expiryAfter=$expiryAfter)" - # Test COPY operation - test "KEYMETA - copy key-string with $numClasses classes, $expiryStr" { - foreach value { 3 "value1" [string repeat "ABCD" 1000]} { - r select 0 - r del k1 k2 - r set k1 $value - setupKeyMeta k1 $numClasses $expiryBefore $expiryAfter - # Copy: - r copy k1 k2 - # Verify: - assert_equal [r get k1] $value - assert_equal [r get k2] $value - # Verify expiry and metadata - verifyKeyMeta k2 "copy" $numClasses $hasExpiry classesSpec - flushallAndVerifyCleanup - } - } - - test "KEYMETA - copy key-hash with $numClasses classes, $expiryStr" { - r select 0 - r del h1 h2 - r HSET h1 field1 "value1" field2 "value2" - r hexpire h1 10000 FIELDS 1 field1 - setupKeyMeta h1 $numClasses $expiryBefore $expiryAfter - # Copy: - r copy h1 h2 - # Verify: - verifyKeyMeta h2 "copy" $numClasses $hasExpiry classesSpec - assert_range [r httl h1 FIELDS 1 field1] 9999 10000 - assert_range [r httl h2 FIELDS 1 field1] 9999 10000 - flushallAndVerifyCleanup - } - - # Test RENAME operation - test "KEYMETA - rename key-string with $numClasses classes, $expiryStr" { - foreach value { 3 "value1" [string repeat "ABCD" 1000]} { - r select 0 - r del k1 k2 - r set k1 $value - setupKeyMeta k1 $numClasses $expiryBefore $expiryAfter - # Rename: - r rename k1 k2 - # Verify: - assert_equal [r exists k1] 0 - assert_equal [r get k2] $value - # Verify expiry and metadata - verifyKeyMeta k2 "rename" $numClasses $hasExpiry classesSpec - flushallAndVerifyCleanup - } - } - - test "KEYMETA - rename key-hash with $numClasses classes, $expiryStr" { - r select 0 - r del h1 h2 - r HSET h1 field1 "value1" field2 "value2" - r hexpire h1 10000 FIELDS 1 field1 - setupKeyMeta h1 $numClasses $expiryBefore $expiryAfter - # Rename: - r rename h1 h2 - # Verify: - assert_equal [r exists h1] 0 - assert_range [r httl h2 FIELDS 1 field1] 9999 10000 - verifyKeyMeta h2 "rename" $numClasses $hasExpiry classesSpec - flushallAndVerifyCleanup - } - - - - # Test MOVE operation - test "KEYMETA - move key-string with $numClasses classes, $expiryStr" { - foreach value { 3 "value1" [string repeat "ABCD" 1000]} { - r select 9 - r del k1 - r select 0 - r del k1 - r set k1 $value - setupKeyMeta k1 $numClasses $expiryBefore $expiryAfter - # Perform move - assert_equal [r move k1 9] 1 - # Verify key moved - assert_equal [r exists k1] 0 - r select 9 - assert_equal [r get k1] $value - # Verify expiry and metadata - verifyKeyMeta k1 "move" $numClasses $hasExpiry classesSpec - r select 0 - flushallAndVerifyCleanup - } - } - - test "KEYMETA - move key-hash with $numClasses classes, $expiryStr" { - r select 9 - r del h1 - r select 0 - r del h1 - r HSET h1 field1 "value1" field2 "value2" - r hexpire h1 10000 FIELDS 1 field1 - setupKeyMeta h1 $numClasses $expiryBefore $expiryAfter - assert_range [r httl h1 FIELDS 1 field1] 9999 10000 - assert_equal [r move h1 9] 1 - assert_equal [r exists h1] 0 - r select 9 - assert_range [r httl h1 FIELDS 1 field1] 9999 10000 - verifyKeyMeta h1 "move" $numClasses $hasExpiry classesSpec - r select 0 - flushallAndVerifyCleanup - } - } - } - } - - test "KEYMETA - Verify active metadata count on copy" { - for {set cid 1} {$cid < 7} {incr cid} { - set numAlloc 0 - flushallAndVerifyCleanup - set dupOnCopy [shouldKeep $cid "copy" classesSpec] - r set k1 "v1" - r keymeta.set [cname $cid] k1 "meta1" - assert_equal [r keymeta.active] [incr numAlloc] - r keymeta.set [cname $cid] k1 "meta1b" - assert_equal [r keymeta.active] $numAlloc - r copy k1 k1copy - assert_equal [r keymeta.active] [incr numAlloc $dupOnCopy] - r del k1 - assert_equal [r keymeta.active] [incr numAlloc -1] - r del k1copy - assert_equal [r keymeta.active] 0 - } - } - - test "KEYMETA - Verify active metadata count on rename" { - for {set cid 1} {$cid <= 7} {incr cid} { - set numAlloc 0 - flushallAndVerifyCleanup - set keepOnRename [shouldKeep $cid "rename" classesSpec] - set discOnRename [expr {!$keepOnRename}] - r set k1 "v1" - r keymeta.set [cname $cid] k1 "meta1" - assert_equal [r keymeta.active] [incr numAlloc] - r rename k1 k1_renamed - assert_equal [r keymeta.active] [incr numAlloc -$discOnRename] - r del k1_renamed - assert_equal [r keymeta.active] 0 - } - } - - test "KEYMETA - Verify active metadata count on move" { - for {set cid 1} {$cid <= 7} {incr cid} { - set numAlloc 0 - r select 0 - flushallAndVerifyCleanup - - set keepOnMove [shouldKeep $cid "move" classesSpec] - set discOnMove [expr {!$keepOnMove}] - - # Create keys with metadata in DB 0 - r set k1 "v1" - r keymeta.set [cname $cid] k1 "meta1" - assert_equal [r keymeta.active] [incr numAlloc] - # Move: metadata discarded if !keepOnMove - r move k1 9 - set active [r keymeta.active] - assert_equal [r keymeta.active] [incr numAlloc -$discOnMove] - # Cleanup - r select 9 - r del k1 - r select 0 - assert_equal [r keymeta.active] 0 - } - } - - test "KEYMETA - Verify metadta cleanup on lazyfree" { - r config set lazyfree-lazy-user-del yes - # Class 2 has UNLINKFREE flag, so it should call unlink callback when lazyfree is enabled - # Class 1 does not have UNLINKFREE flag, so it should only call free callback - foreach {cid} { 1 2 } { - r config resetstat - # Create a large unsorted set collection to ensure it exceeds LAZYFREE_THRESHOLD - for {set i 0} {$i < 1024} {incr i} { r sadd myset $i } - r keymeta.set [cname $cid] myset "meta" - assert_equal [r keymeta.active] 1 - r del myset - - # Wait for lazyfree to complete and verify lazyfreed_objects incremented - wait_for_condition 50 100 { - [s lazyfree_pending_objects] == 0 - } else { - fail "lazyfree isn't done" - } - assert_equal [r keymeta.active] 0 - assert_equal [s lazyfreed_objects] 1 - } - r config set lazyfree-lazy-user-del no - } {OK} {needs:config-resetstat} - - test "KEYMETA - Verify metadata cleanup on expire" { - # Class 2 has UNLINKFREE flag, so it should call unlink callback when lazyfree is enabled - # Class 1 does not have UNLINKFREE flag, so it should only call free callback - foreach {cid} { 1 2 } { - r set mykey "mykey$cid" - r keymeta.set [cname $cid] mykey "meta" - assert_equal [r keymeta.active] 1 - r pexpire mykey 1 - wait_for_condition 50 100 { - [r exists mykey] == 0 - } else { - fail "key not expired" - } - assert_equal [r keymeta.active] 0 - } - } - - # ============================================================================ - # AOF Rewrite Tests - # ============================================================================ - # Note: Full AOF round-trip tests (write → restart → load) are not included - # because the test module registers classes dynamically via commands, which - # creates a chicken-and-egg problem: - # - Classes must be registered BEFORE AOF loading (in RedisModule_OnLoad) - # - But the KEYMETA.REGISTER commands are in the AOF itself - # - When server restarts and loads AOF, classes aren't registered yet - # - KEYMETA.SET commands fail with "metadata class not found" - # - # For production modules, classes MUST be registered in RedisModule_OnLoad() - # to ensure they're available when AOF/RDB files are loaded on server startup. - # See src/module.c documentation for RM_CreateKeyMetaClass() for details. - # - # The test below verifies that AOF callbacks correctly emit KEYMETA.SET commands - # to the AOF file during rewrite, which is the module's responsibility. - test "KEYMETA - AOF rewrite emits correct KEYMETA.SET commands to file" { - # This test verifies that the AOF callback implementation correctly writes - # KEYMETA.SET commands to the AOF file during rewrite. We don't test the - # full round-trip (restart + load) due to the dynamic registration limitation - # explained above. - - r config set appendonly yes - r config set auto-aof-rewrite-percentage 0 - r config set aof-use-rdb-preamble no - # Wait for the initial AOF rewrite that Redis triggers when enabling AOF - waitForBgrewriteaof r - - # Create keys with metadata from multiple classes - r set key1 "value1" - r keymeta.set [cname 1] key1 "metadata_c1" - - r set key2 "value2" - r keymeta.set [cname 2] key2 "metadata_c2" - r keymeta.set [cname 3] key2 "metadata_c3" - - r hset hashkey field1 val1 - r keymeta.set [cname 4] hashkey "hash_meta" - - # Trigger AOF rewrite - r bgrewriteaof - waitForBgrewriteaof r - - # Get the AOF directory and read the AOF file - set aof_dir [lindex [r config get dir] 1] - set aof_base_filename [lindex [r config get appendfilename] 1] - - # Find the base AOF file (after rewrite) - set aof_files [glob -nocomplain -directory $aof_dir appendonlydir/${aof_base_filename}.*.base.aof] - assert {[llength $aof_files] > 0} - - # Read the most recent base AOF file - set aof_file [lindex [lsort $aof_files] end] - set fp [open $aof_file r] - set aof_content [read $fp] - close $fp - - # Verify the AOF contains KEYMETA.SET commands with correct format - assert_match "*KEYMETA.SET*[cname 1]*key1*metadata_c1*" $aof_content - assert_match "*KEYMETA.SET*[cname 2]*key2*metadata_c2*" $aof_content - assert_match "*KEYMETA.SET*[cname 3]*key2*metadata_c3*" $aof_content - assert_match "*KEYMETA.SET*[cname 4]*hashkey*hash_meta*" $aof_content - - # Verify the RESP format is correct by checking for the command structure - # The AOF should contain: *4 (array of 4 elements) - assert_match "*\$11*KEYMETA.SET*" $aof_content - # Count how many KEYMETA.SET commands are in the AOF - set keymeta_count [regexp -all {KEYMETA\.SET} $aof_content] - assert_equal $keymeta_count 4 - } {} {external:skip} - - # ======================================================================== - # RDB Save/Load Tests - # ======================================================================== - - test {RDB: SAVE and reload preserves metadata} { - # Create key with metadata - r set key1 "value1" - r keymeta.set [cname 1] key1 "key1_meta1" - assert_equal [r keymeta.get [cname 1] key1] "key1_meta1" - - r save - r debug reload - - # Verify metadata persisted after reload - assert_equal [r keymeta.get [cname 1] key1] "key1_meta1" - - flushallAndVerifyCleanup - } {} {external:skip needs:save} - - test {RDB: BGSAVE writes metadata to RDB file} { - # Create keys with different metadata combinations - r set key1 "value1" - r keymeta.set [cname 1] key1 "key1_meta1" - - r set key2 "value2" - r keymeta.set [cname 1] key2 "key2_meta1" - r keymeta.set [cname 2] key2 "key2_meta2" - - # Trigger BGSAVE and reload (debug reload preserves modules) - r bgsave - waitForBgsave r - r debug reload - - # Verify metadata persisted after reload - assert_equal [r keymeta.get [cname 1] key1] "key1_meta1" - assert_equal [r keymeta.get [cname 1] key2] "key2_meta1" - assert_equal [r keymeta.get [cname 2] key2] "key2_meta2" - - flushallAndVerifyCleanup - } {} {external:skip needs:save} - - test {RDB: Metadata persists with expiretime} { - # Create key with both expiry and metadata - r set key1 "value1" - set expire_time [expr {[clock seconds] + 10000}] - r expireat key1 $expire_time - r keymeta.set [cname 1] key1 "meta_with_expire" - - assert_equal [r expiretime key1] $expire_time - assert_equal [r keymeta.get [cname 1] key1] "meta_with_expire" - - # Reload from RDB - r debug reload - - # Verify metadata and expiry persist after reload - assert_equal [r expiretime key1] $expire_time - assert_equal [r keymeta.get [cname 1] key1] "meta_with_expire" - - flushallAndVerifyCleanup - } {} {external:skip needs:debug} - - test {RDB: Create keys with upto 7 meta classes, with or without expiry} { - # Test all combinations of 1-7 metadata classes, with or without expiry - for {set n 1} {$n <= 7} {incr n} { - foreach hasExpiry {0 1} { - set keyname "key_${n}_exp${hasExpiry}" - r set $keyname "value$n" - - # Set expiry if hasExpiry is 1 - if {$hasExpiry} { - set ttl [expr {3600 + $n}] - r expire $keyname $ttl - # Get the actual expiretime set by Redis to use as expected value - set expExpiry [r expiretime $keyname] - } - - # Create list of class IDs to attach (1 through n) - set class_ids {} - for {set i 1} {$i <= $n} {incr i} { - lappend class_ids $i - } - - # Randomize the order of metadata attachment - set class_ids [lshuffle $class_ids] - - # Attach metadata in randomized order - foreach cid $class_ids { - r keymeta.set [cname $cid] $keyname "meta$cid" - } - - # Verify metadata before RDB save - # Verify exactly n metadata classes are attached - for {set i 1} {$i <= 7} {incr i} { - if {$i <= $n} { - assert_equal [r keymeta.get [cname $i] $keyname] "meta$i" - } else { - assert_equal [r keymeta.get [cname $i] $keyname] "" - } - } - - # Verify expiry before RDB save - if {$hasExpiry} { - set actual_expiretime [r expiretime $keyname] - assert_equal $actual_expiretime $expExpiry - } - - # Save and reload from RDB (debug reload preserves modules) - r save - r debug reload - - # Verify metadata after RDB reload - # Verify exactly n metadata classes are still attached - for {set i 1} {$i <= 7} {incr i} { - if {$i <= $n} { - assert_equal [r keymeta.get [cname $i] $keyname] "meta$i" - } else { - assert_equal [r keymeta.get [cname $i] $keyname] "" - } - } - - # Verify expiry after RDB reload - if {$hasExpiry} { - set actual_expiretime [r expiretime $keyname] - assert_equal $actual_expiretime $expExpiry - } else { - # Verify no expiry set - assert_equal [r expiretime $keyname] -1 - } - flushallAndVerifyCleanup - } - } - } {} {external:skip needs:save} - - # ======================================================================== - # RDB Flag Tests: ALLOW_IGNORE, RDBLOAD, RDBSAVE - # ======================================================================== - - # Test all combinations except the error case (ALLOW_IGNORE=0, RDBLOAD=0, RDBSAVE=1) - foreach RDBLOAD {0 1} { - foreach RDBSAVE {0 1} { - foreach ALLOW_IGNORE {0 1} { - # Skip the error case - we'll test it last since it causes RDB load to fail - if {!$RDBLOAD && $RDBSAVE && !$ALLOW_IGNORE} { continue } - - test "RDB: SAVE and LOAD (ALLOW_IGNORE=$ALLOW_IGNORE, RDBLOAD=$RDBLOAD, RDBSAVE=$RDBSAVE)" { - # Flush all data and save empty RDB to start with a clean slate - r flushall - r save - - # re-register class 1 with new flags. Expected re-registered same class ID - r keymeta.unregister [cname 1] - # dummy default spec - set newSpec "KEEPONCOPY" - if {$ALLOW_IGNORE} { append newSpec ":ALLOWIGNORE" } - if {$RDBLOAD} { append newSpec ":RDBLOAD" } - if {$RDBSAVE} { append newSpec ":RDBSAVE" } - - # Must reuse same class-id that it had before - assert_equal $classes(1) [r keymeta.register [cname 1] 1 $newSpec] - - r set key1 "value1" - r keymeta.set [cname 1] key1 "key1_meta1" - assert_equal [r keymeta.get [cname 1] key1] "key1_meta1" - - r save - r debug reload - - # Metadata is preserved only when BOTH rdb_save AND rdb_load are enabled - # Otherwise metadata is lost (either not saved, or saved but not loaded) - set metaPreserved [expr {$RDBSAVE && $RDBLOAD}] - set expectedMeta [expr {$metaPreserved ? "key1_meta1" : ""}] - - assert_equal [r keymeta.get [cname 1] key1] $expectedMeta - - flushallAndVerifyCleanup - } {} {external:skip needs:save} - } - } - } - - # Test the error case last (ALLOW_IGNORE=0, RDBLOAD=0, RDBSAVE=1) - # This test causes RDB load to fail, so we test it last to avoid polluting subsequent tests - test "RDB: SAVE and LOAD Invalid combination: (ALLOW_IGNORE=0, RDBLOAD=0, RDBSAVE=1)" { - # re-register class 1 with RDBSAVE flag but no RDBLOAD or ALLOW_IGNORE - r keymeta.unregister [cname 1] - set newSpec "KEEPONCOPY:RDBSAVE" - assert_equal $classes(1) [r keymeta.register [cname 1] 1 $newSpec] - - r set key1 "value1" - r keymeta.set [cname 1] key1 "key1_meta1" - assert_equal [r keymeta.get [cname 1] key1] "key1_meta1" - - r save - - # This combination causes RDB load to fail because: - # - Metadata was saved (RDBSAVE=1) - # - Class has no rdb_load callback (RDBLOAD=0) - # - Errors are not ignored (ALLOW_IGNORE=0) - catch {r debug reload} err - assert_match "*Error trying to load the RDB dump*" $err - } {} {external:skip needs:save} - - # ======================================================================== - # DUMP/RESTORE Tests - # ======================================================================== - - test {DUMP/RESTORE: 1 to 7 metadata classes, optional TTL} { - foreach withTTL {0 1} { - for {set numClasses 1} {$numClasses < 8} {incr numClasses} { - # Re-register classes with RDBLOAD and RDBSAVE flags - for {set cid 1} {$cid <= $numClasses} {incr cid} { - r keymeta.unregister [cname $cid] - assert_equal $classes($cid) [r keymeta.register [cname $cid] 1 $classesSpec($cid)] - } - - # Create key with metadata classes - r set key1 "value1" - for {set i 1} {$i <= $numClasses} {incr i} { - r keymeta.set [cname $i] key1 "meta${i}_value" - } - - if {$withTTL} { r expire key1 10000 } - - # Verify all metadata before DUMP - for {set i 1} {$i <= $numClasses} {incr i} { - assert_equal [r keymeta.get [cname $i] key1] "meta${i}_value" - } - - # DUMP the key - set encoded [r dump key1] - - # Delete and RESTORE - r del key1 - r restore key1 [expr {$withTTL ? 10000 : 0}] $encoded - - # Verify all metadata was restored - assert_equal [r get key1] "value1" - for {set i 1} {$i <= $numClasses} {incr i} { - assert_equal [r keymeta.get [cname $i] key1] "meta${i}_value" - } - if {$withTTL} { assert_range [r pttl key1] 9000 10000 } - - flushallAndVerifyCleanup - } - } - } - - test {DUMP/RESTORE: REPLACE with metadata} { - # Create key with metadata - r set key1 value1 - r keymeta.set [cname 1] key1 "meta1_original" - - # DUMP the key - set encoded1 [r dump key1] - - # Create different key with different metadata - r set key1 value2 - r keymeta.set [cname 1] key1 "meta1_new" - - # DUMP the second version - set encoded2 [r dump key1] - - # Delete and restore first version - r del key1 - r restore key1 0 $encoded1 - assert_equal [r get key1] "value1" - assert_equal [r keymeta.get [cname 1] key1] "meta1_original" - - # RESTORE second version with REPLACE - r restore key1 0 $encoded2 replace - assert_equal [r get key1] "value2" - assert_equal [r keymeta.get [cname 1] key1] "meta1_new" - - flushallAndVerifyCleanup - } - - - # Test all combinations except the error case (ALLOW_IGNORE=0, RDBLOAD=0, RDBSAVE=1) - foreach RDBLOAD {0 1} { - foreach RDBSAVE {0 1} { - foreach ALLOW_IGNORE {0 1} { - # Skip the error case - we'll test it last since it causes RESTORE to fail - if {!$RDBLOAD && $RDBSAVE && !$ALLOW_IGNORE} { continue } - - test "DUMP/RESTORE: (ALLOW_IGNORE=$ALLOW_IGNORE, RDBLOAD=$RDBLOAD, RDBSAVE=$RDBSAVE)" { - # re-register class 1 with new flags. Expected re-registered same class ID - r keymeta.unregister [cname 1] - # dummy default spec - set newSpec "KEEPONCOPY" - if {$ALLOW_IGNORE} { append newSpec ":ALLOWIGNORE" } - if {$RDBLOAD} { append newSpec ":RDBLOAD" } - if {$RDBSAVE} { append newSpec ":RDBSAVE" } - - # Must reuse same class-id that it had before - assert_equal $classes(1) [r keymeta.register [cname 1] 1 $newSpec] - - r set key1 "value1" - r keymeta.set [cname 1] key1 "key1_meta1" - assert_equal [r keymeta.get [cname 1] key1] "key1_meta1" - - # DUMP & RESTORE - set encoded [r dump key1] - r del key1 - r restore key1 0 $encoded - - # Metadata is preserved only when BOTH rdb_save AND rdb_load are enabled - # Otherwise metadata is lost (either not saved, or saved but not loaded) - set metaPreserved [expr {$RDBSAVE && $RDBLOAD}] - set expectedMeta [expr {$metaPreserved ? "key1_meta1" : ""}] - - assert_equal [r keymeta.get [cname 1] key1] $expectedMeta - - flushallAndVerifyCleanup - } - } - } - } - - # Test the error case last (ALLOW_IGNORE=0, RDBLOAD=0, RDBSAVE=1) - # This test causes RESTORE to fail, so we test it last to avoid polluting subsequent tests - test "DUMP/RESTORE: Invalid combination: (ALLOW_IGNORE=0, RDBLOAD=0, RDBSAVE=1)" { - # re-register class 1 with RDBSAVE flag but no RDBLOAD or ALLOW_IGNORE - r keymeta.unregister [cname 1] - set newSpec "KEEPONCOPY:RDBSAVE" - assert_equal $classes(1) [r keymeta.register [cname 1] 1 $newSpec] - - r set key1 "value1" - r keymeta.set [cname 1] key1 "key1_meta1" - assert_equal [r keymeta.get [cname 1] key1] "key1_meta1" - - # DUMP the key - set encoded [r dump key1] - - # Delete and try to RESTORE - r del key1 - - # This combination causes RESTORE to fail because: - # - Metadata was saved (RDBSAVE=1) - # - Class has no rdb_load callback (RDBLOAD=0) - # - Errors are not ignored (ALLOW_IGNORE=0) - catch {r restore key1 0 $encoded} err - assert_match "*Bad data format*" $err - - flushallAndVerifyCleanup - } -} - -test "RDB: Load with different module registration order preserves metadata correctly" { - # This test verifies out-of-order metadata attachment during RDB load. - # When modules register in different order at load time vs save time, - # metadata values should still be correctly associated with their classes. - start_server {tags {"modules" "external:skip" "cluster:skip"} overrides {enable-debug-command yes}} { - r module load $testmodule - - # Helper function to generate class names (needed in inner scope) - proc cname {id} { return "CLS$id" } - - # Register classes in order: 1, 2, 3 - set spec1 "KEEPONCOPY:ALLOWIGNORE:RDBLOAD:RDBSAVE" - set spec2 "KEEPONRENAME:ALLOWIGNORE:RDBLOAD:RDBSAVE" - set spec3 "KEEPONMOVE:ALLOWIGNORE:RDBLOAD:RDBSAVE" - - set class1 [r keymeta.register [cname 1] 1 $spec1] - set class2 [r keymeta.register [cname 2] 1 $spec2] - set class3 [r keymeta.register [cname 3] 1 $spec3] - - # Verify class IDs match registration order - assert_equal $class1 1 "Class 1 registered first, gets ID 1" - assert_equal $class2 2 "Class 2 registered second, gets ID 2" - assert_equal $class3 3 "Class 3 registered third, gets ID 3" - - # OUTER SERVER: Create RDB with classes registered in order 1,2,3 - r flushall - r set mykey "myvalue" - r keymeta.set [cname 1] mykey "metadata_for_class1" - r keymeta.set [cname 2] mykey "metadata_for_class2" - r keymeta.set [cname 3] mykey "metadata_for_class3" - - # Verify metadata before save - assert_equal [r keymeta.get [cname 1] mykey] "metadata_for_class1" - assert_equal [r keymeta.get [cname 2] mykey] "metadata_for_class2" - assert_equal [r keymeta.get [cname 3] mykey] "metadata_for_class3" - - r save - - # Get RDB file path & Copy RDB to a temp location with unique name - set rdb_dir [lindex [r config get dir] 1] - set rdb_file [lindex [r config get dbfilename] 1] - set rdb_path [file join $rdb_dir $rdb_file] - set temp_rdb [file join $rdb_dir "temp_metadata_outoforder_[pid].rdb"] - file copy -force $rdb_path $temp_rdb - - # INNER SERVER: Start new server, register classes in DIFFERENT order, then load RDB - start_server [list overrides [list dir $rdb_dir enable-debug-command yes]] { - r module load $testmodule - - # Helper function to generate class names (needed in inner scope) - proc cname {id} { return "CLS$id" } - - # Register classes in DIFFERENT order: 3, 1, 2 - # This simulates a server where modules load in different order - set spec1 "KEEPONCOPY:ALLOWIGNORE:RDBLOAD:RDBSAVE" - set spec2 "KEEPONRENAME:ALLOWIGNORE:RDBLOAD:RDBSAVE" - set spec3 "KEEPONMOVE:ALLOWIGNORE:RDBLOAD:RDBSAVE" - - set class3 [r keymeta.register [cname 3] 1 $spec3] - set class1 [r keymeta.register [cname 1] 1 $spec1] - set class2 [r keymeta.register [cname 2] 1 $spec2] - - # Verify class IDs are assigned by REGISTRATION ORDER, not name - # We registered in order 3,1,2, so the runtime IDs are: - # - class3 (name "CLS3") gets ID 1 (first registered) - # - class1 (name "CLS1") gets ID 2 (second registered) - # - class2 (name "CLS2") gets ID 3 (third registered) - # This is DIFFERENT from outer server which registered in order 1,2,3 - assert_equal $class3 1 "Class 3 registered first, gets ID 1" - assert_equal $class1 2 "Class 1 registered second, gets ID 2" - assert_equal $class2 3 "Class 2 registered third, gets ID 3" - - # Copy the saved RDB to this server's dbfilename - set inner_rdb_file [lindex [r config get dbfilename] 1] - set inner_rdb_path [file join $rdb_dir $inner_rdb_file] - file copy -force $temp_rdb $inner_rdb_path - - # NOW load the RDB (AFTER registration in different order) - # Use 'nosave' to reload from the copied RDB without saving current state first - r debug reload nosave - - # Verify the key exists - assert_equal [r exists mykey] 1 "Key should exist after RDB load" - assert_equal [r get mykey] "myvalue" "Key value should be preserved" - - # Verify metadata values are correctly associated with their classes - # WITHOUT metadata would be swapped because: - # - At SAVE time (outer): classes registered in order 1,2,3 - # - At LOAD time (inner): classes registered in order 3,1,2 - # - RDB contains metadata in saved order, but keyMetaClassLookupByName - # maps them back to correct classes by NAME, not by registration order - assert_equal [r keymeta.get [cname 1] mykey] "metadata_for_class1" - assert_equal [r keymeta.get [cname 2] mykey] "metadata_for_class2" - assert_equal [r keymeta.get [cname 3] mykey] "metadata_for_class3" - } - - # Cleanup temp file - file delete $temp_rdb - - } -} {} {external:skip needs:save} - -test "RDB: File size same with/without metadata when no rdb_save callback" { - # This test verifies that when a metadata class has no rdb_save callback, - # the metadata is not serialized to RDB, so the RDB file size should be - # approximately the same (within a small tolerance for header differences). - - start_server {tags {"modules" "external:skip" "cluster:skip"} overrides {enable-debug-command yes}} { - r module load $testmodule - - # Get RDB directory - set rdb_dir [lindex [r config get dir] 1] - set rdb_file [lindex [r config get dbfilename] 1] - set rdb_path [file join $rdb_dir $rdb_file] - - # Test 1: Create key WITHOUT metadata and save - r flushall - r set key1 "test_value_12345" - r save - set size_without_meta [file size $rdb_path] - - # Test 2: Create identical key WITH metadata (but no rdb_save) and save - # Register a class WITHOUT rdb_save callback (RDBSAVE=0) - # Use ALLOWIGNORE so loading doesn't fail when metadata is missing - set spec "ALLOWIGNORE" - r keymeta.register [cname 1] 1 $spec - - r flushall - r set key1 "test_value_12345" - r keymeta.set [cname 1] key1 "some_metadata_value" - - # Verify metadata is attached - assert_equal [r keymeta.get [cname 1] key1] "some_metadata_value" - - r save - set size_with_meta [file size $rdb_path] - - # The file sizes should be the same (metadata not serialized) - assert_equal $size_without_meta $size_with_meta - } -} {} {external:skip needs:save} - -test "Creating key metadata not during OnLoad should fail" { - # This time start_server without "enable-debug-command yes" - start_server {tags {"modules" "external:skip" "cluster:skip"} overrides {enable-debug-command no}} { - r module load $testmodule - # Creating a class not during OnLoad should fail - catch {r keymeta.register [cname 1] 1 "ALLOWIGNORE"} err - assert_match {*failed to create metadata class*} $err - } -} {} {external:skip needs:save} |
