diff --git a/Makefile b/Makefile index 18d9ff5..1fab6c0 100644 --- a/Makefile +++ b/Makefile @@ -14,12 +14,18 @@ build-modules-test: test: env RACE=false OUT=internal/modules/admin/testdata make build-modules-test && \ env RACE=false OUT=sugardb/testdata make build-modules-test && \ - CGO_ENABLED=1 go test ./... -coverprofile coverage/coverage.out + CGO_ENABLED=1 go test ./... -coverprofile coverage/coverage.out && \ + rm -rf ./internal/modules/admin/testdata && \ + rm -rf ./sugardb/testdata && \ + rm -rf ./sugardb/aof test-race: env RACE=true OUT=internal/modules/admin/testdata make build-modules-test && \ env RACE=true OUT=sugardb/testdata make build-modules-test && \ - CGO_ENABLED=1 go test ./... --race + CGO_ENABLED=1 go test ./... --race && \ + rm -rf ./internal/modules/admin/testdata && \ + rm -rf ./sugardb/testdata && \ + rm -rf ./sugardb/aof testenv-run: docker-compose -f test_env/run/docker-compose.yaml build diff --git a/coverage/coverage.out b/coverage/coverage.out index 5d1cd5a..8f34c12 100644 --- a/coverage/coverage.out +++ b/coverage/coverage.out @@ -27,13 +27,194 @@ github.com/echovault/sugardb/cmd/main.go:30.16,32.3 1 0 github.com/echovault/sugardb/cmd/main.go:34.2,44.16 5 0 github.com/echovault/sugardb/cmd/main.go:44.16,46.3 1 0 github.com/echovault/sugardb/cmd/main.go:48.2,52.19 3 0 -github.com/echovault/sugardb/internal/clock/clock.go:14.23,16.43 1 0 -github.com/echovault/sugardb/internal/clock/clock.go:16.43,18.3 1 0 -github.com/echovault/sugardb/internal/clock/clock.go:19.2,19.20 1 0 -github.com/echovault/sugardb/internal/clock/clock.go:24.34,26.2 1 0 -github.com/echovault/sugardb/internal/clock/clock.go:28.58,30.2 1 0 -github.com/echovault/sugardb/internal/clock/clock.go:34.34,37.2 2 0 -github.com/echovault/sugardb/internal/clock/clock.go:39.58,41.2 1 0 +github.com/echovault/sugardb/internal/types.go:35.43,40.29 3 0 +github.com/echovault/sugardb/internal/types.go:41.11,42.12 1 0 +github.com/echovault/sugardb/internal/types.go:44.11,45.34 1 0 +github.com/echovault/sugardb/internal/types.go:47.22,48.12 1 0 +github.com/echovault/sugardb/internal/types.go:49.14,52.24 2 0 +github.com/echovault/sugardb/internal/types.go:55.16,56.23 1 0 +github.com/echovault/sugardb/internal/types.go:56.23,59.4 2 0 +github.com/echovault/sugardb/internal/types.go:62.31,63.53 1 0 +github.com/echovault/sugardb/internal/types.go:65.10,66.117 1 0 +github.com/echovault/sugardb/internal/types.go:69.2,69.18 1 0 +github.com/echovault/sugardb/internal/utils.go:41.38,45.16 2 0 +github.com/echovault/sugardb/internal/utils.go:45.16,47.3 1 0 +github.com/echovault/sugardb/internal/utils.go:49.2,49.15 1 0 +github.com/echovault/sugardb/internal/utils.go:49.15,52.3 2 0 +github.com/echovault/sugardb/internal/utils.go:54.2,56.10 2 0 +github.com/echovault/sugardb/internal/utils.go:59.43,63.16 3 0 +github.com/echovault/sugardb/internal/utils.go:63.16,65.3 1 0 +github.com/echovault/sugardb/internal/utils.go:67.2,68.42 2 0 +github.com/echovault/sugardb/internal/utils.go:68.42,70.3 1 0 +github.com/echovault/sugardb/internal/utils.go:72.2,72.17 1 0 +github.com/echovault/sugardb/internal/utils.go:75.47,82.6 4 0 +github.com/echovault/sugardb/internal/utils.go:82.6,84.43 2 0 +github.com/echovault/sugardb/internal/utils.go:84.43,85.9 1 0 +github.com/echovault/sugardb/internal/utils.go:87.3,87.17 1 0 +github.com/echovault/sugardb/internal/utils.go:87.17,89.4 1 0 +github.com/echovault/sugardb/internal/utils.go:90.3,91.21 2 0 +github.com/echovault/sugardb/internal/utils.go:91.21,92.9 1 0 +github.com/echovault/sugardb/internal/utils.go:94.3,94.15 1 0 +github.com/echovault/sugardb/internal/utils.go:97.2,97.37 1 0 +github.com/echovault/sugardb/internal/utils.go:100.120,102.20 2 0 +github.com/echovault/sugardb/internal/utils.go:102.20,104.3 1 0 +github.com/echovault/sugardb/internal/utils.go:105.2,105.16 1 0 +github.com/echovault/sugardb/internal/utils.go:105.16,107.3 1 0 +github.com/echovault/sugardb/internal/utils.go:108.2,108.24 1 0 +github.com/echovault/sugardb/internal/utils.go:108.24,110.3 1 0 +github.com/echovault/sugardb/internal/utils.go:111.2,111.21 1 0 +github.com/echovault/sugardb/internal/utils.go:111.21,113.3 1 0 +github.com/echovault/sugardb/internal/utils.go:114.2,114.16 1 0 +github.com/echovault/sugardb/internal/utils.go:117.37,119.16 2 0 +github.com/echovault/sugardb/internal/utils.go:119.16,121.3 1 0 +github.com/echovault/sugardb/internal/utils.go:122.2,122.15 1 0 +github.com/echovault/sugardb/internal/utils.go:122.15,123.37 1 0 +github.com/echovault/sugardb/internal/utils.go:123.37,125.4 1 0 +github.com/echovault/sugardb/internal/utils.go:128.2,130.23 2 0 +github.com/echovault/sugardb/internal/utils.go:133.72,134.65 1 0 +github.com/echovault/sugardb/internal/utils.go:134.65,137.3 1 0 +github.com/echovault/sugardb/internal/utils.go:138.2,138.18 1 0 +github.com/echovault/sugardb/internal/utils.go:138.18,141.3 1 0 +github.com/echovault/sugardb/internal/utils.go:142.2,142.49 1 0 +github.com/echovault/sugardb/internal/utils.go:142.49,143.52 1 0 +github.com/echovault/sugardb/internal/utils.go:143.52,145.4 1 0 +github.com/echovault/sugardb/internal/utils.go:147.2,147.71 1 0 +github.com/echovault/sugardb/internal/utils.go:150.66,152.2 1 0 +github.com/echovault/sugardb/internal/utils.go:154.24,155.11 1 0 +github.com/echovault/sugardb/internal/utils.go:155.11,157.3 1 0 +github.com/echovault/sugardb/internal/utils.go:158.2,158.10 1 0 +github.com/echovault/sugardb/internal/utils.go:162.49,166.16 3 0 +github.com/echovault/sugardb/internal/utils.go:166.16,168.3 1 0 +github.com/echovault/sugardb/internal/utils.go:170.2,171.17 2 0 +github.com/echovault/sugardb/internal/utils.go:172.12,173.19 1 0 +github.com/echovault/sugardb/internal/utils.go:174.12,175.26 1 0 +github.com/echovault/sugardb/internal/utils.go:176.12,177.33 1 0 +github.com/echovault/sugardb/internal/utils.go:178.12,179.40 1 0 +github.com/echovault/sugardb/internal/utils.go:180.12,181.47 1 0 +github.com/echovault/sugardb/internal/utils.go:182.10,183.91 1 0 +github.com/echovault/sugardb/internal/utils.go:186.2,186.30 1 0 +github.com/echovault/sugardb/internal/utils.go:190.64,191.20 1 0 +github.com/echovault/sugardb/internal/utils.go:191.20,193.3 1 0 +github.com/echovault/sugardb/internal/utils.go:196.2,196.33 1 0 +github.com/echovault/sugardb/internal/utils.go:196.33,198.3 1 0 +github.com/echovault/sugardb/internal/utils.go:203.2,206.37 2 0 +github.com/echovault/sugardb/internal/utils.go:210.100,211.36 1 0 +github.com/echovault/sugardb/internal/utils.go:211.36,213.26 2 0 +github.com/echovault/sugardb/internal/utils.go:213.26,215.35 1 0 +github.com/echovault/sugardb/internal/utils.go:215.35,216.13 1 0 +github.com/echovault/sugardb/internal/utils.go:219.4,219.30 1 0 +github.com/echovault/sugardb/internal/utils.go:219.30,221.5 1 0 +github.com/echovault/sugardb/internal/utils.go:223.3,223.36 1 0 +github.com/echovault/sugardb/internal/utils.go:223.36,225.4 1 0 +github.com/echovault/sugardb/internal/utils.go:227.2,227.14 1 0 +github.com/echovault/sugardb/internal/utils.go:232.43,233.14 1 0 +github.com/echovault/sugardb/internal/utils.go:233.14,235.3 1 0 +github.com/echovault/sugardb/internal/utils.go:236.2,236.30 1 0 +github.com/echovault/sugardb/internal/utils.go:236.30,238.3 1 0 +github.com/echovault/sugardb/internal/utils.go:239.2,239.30 1 0 +github.com/echovault/sugardb/internal/utils.go:239.30,241.3 1 0 +github.com/echovault/sugardb/internal/utils.go:243.2,244.21 2 0 +github.com/echovault/sugardb/internal/utils.go:244.21,246.3 1 0 +github.com/echovault/sugardb/internal/utils.go:248.2,249.29 2 0 +github.com/echovault/sugardb/internal/utils.go:249.29,251.13 2 0 +github.com/echovault/sugardb/internal/utils.go:251.13,252.9 1 0 +github.com/echovault/sugardb/internal/utils.go:256.2,256.10 1 0 +github.com/echovault/sugardb/internal/utils.go:259.41,261.28 2 0 +github.com/echovault/sugardb/internal/utils.go:261.28,263.3 1 0 +github.com/echovault/sugardb/internal/utils.go:264.2,264.20 1 0 +github.com/echovault/sugardb/internal/utils.go:267.47,270.16 3 0 +github.com/echovault/sugardb/internal/utils.go:270.16,272.3 1 0 +github.com/echovault/sugardb/internal/utils.go:273.2,273.24 1 0 +github.com/echovault/sugardb/internal/utils.go:276.52,279.16 3 0 +github.com/echovault/sugardb/internal/utils.go:279.16,281.3 1 0 +github.com/echovault/sugardb/internal/utils.go:282.2,282.24 1 0 +github.com/echovault/sugardb/internal/utils.go:285.50,288.16 3 0 +github.com/echovault/sugardb/internal/utils.go:288.16,290.3 1 0 +github.com/echovault/sugardb/internal/utils.go:291.2,291.25 1 0 +github.com/echovault/sugardb/internal/utils.go:294.52,297.16 3 0 +github.com/echovault/sugardb/internal/utils.go:297.16,299.3 1 0 +github.com/echovault/sugardb/internal/utils.go:300.2,300.23 1 0 +github.com/echovault/sugardb/internal/utils.go:303.51,306.16 3 0 +github.com/echovault/sugardb/internal/utils.go:306.16,308.3 1 0 +github.com/echovault/sugardb/internal/utils.go:309.2,309.22 1 0 +github.com/echovault/sugardb/internal/utils.go:312.59,316.16 3 0 +github.com/echovault/sugardb/internal/utils.go:316.16,318.3 1 0 +github.com/echovault/sugardb/internal/utils.go:320.2,320.16 1 0 +github.com/echovault/sugardb/internal/utils.go:320.16,322.3 1 0 +github.com/echovault/sugardb/internal/utils.go:324.2,324.39 1 0 +github.com/echovault/sugardb/internal/utils.go:324.39,326.3 1 0 +github.com/echovault/sugardb/internal/utils.go:328.2,329.30 2 0 +github.com/echovault/sugardb/internal/utils.go:329.30,330.17 1 0 +github.com/echovault/sugardb/internal/utils.go:330.17,332.12 2 0 +github.com/echovault/sugardb/internal/utils.go:334.3,334.22 1 0 +github.com/echovault/sugardb/internal/utils.go:336.2,336.17 1 0 +github.com/echovault/sugardb/internal/utils.go:339.67,342.16 3 0 +github.com/echovault/sugardb/internal/utils.go:342.16,344.3 1 0 +github.com/echovault/sugardb/internal/utils.go:345.2,345.16 1 0 +github.com/echovault/sugardb/internal/utils.go:345.16,347.3 1 0 +github.com/echovault/sugardb/internal/utils.go:348.2,349.31 2 0 +github.com/echovault/sugardb/internal/utils.go:349.31,350.18 1 0 +github.com/echovault/sugardb/internal/utils.go:350.18,352.12 2 0 +github.com/echovault/sugardb/internal/utils.go:354.3,355.33 2 0 +github.com/echovault/sugardb/internal/utils.go:355.33,357.4 1 0 +github.com/echovault/sugardb/internal/utils.go:358.3,358.17 1 0 +github.com/echovault/sugardb/internal/utils.go:360.2,360.17 1 0 +github.com/echovault/sugardb/internal/utils.go:363.57,366.16 3 0 +github.com/echovault/sugardb/internal/utils.go:366.16,368.3 1 0 +github.com/echovault/sugardb/internal/utils.go:369.2,369.16 1 0 +github.com/echovault/sugardb/internal/utils.go:369.16,371.3 1 0 +github.com/echovault/sugardb/internal/utils.go:372.2,373.30 2 0 +github.com/echovault/sugardb/internal/utils.go:373.30,374.17 1 0 +github.com/echovault/sugardb/internal/utils.go:374.17,376.12 2 0 +github.com/echovault/sugardb/internal/utils.go:378.3,378.23 1 0 +github.com/echovault/sugardb/internal/utils.go:380.2,380.17 1 0 +github.com/echovault/sugardb/internal/utils.go:383.58,386.16 3 0 +github.com/echovault/sugardb/internal/utils.go:386.16,388.3 1 0 +github.com/echovault/sugardb/internal/utils.go:389.2,389.16 1 0 +github.com/echovault/sugardb/internal/utils.go:389.16,391.3 1 0 +github.com/echovault/sugardb/internal/utils.go:392.2,393.30 2 0 +github.com/echovault/sugardb/internal/utils.go:393.30,394.17 1 0 +github.com/echovault/sugardb/internal/utils.go:394.17,396.12 2 0 +github.com/echovault/sugardb/internal/utils.go:398.3,398.20 1 0 +github.com/echovault/sugardb/internal/utils.go:400.2,400.17 1 0 +github.com/echovault/sugardb/internal/utils.go:403.70,404.32 1 0 +github.com/echovault/sugardb/internal/utils.go:404.32,405.60 1 0 +github.com/echovault/sugardb/internal/utils.go:405.60,407.4 1 0 +github.com/echovault/sugardb/internal/utils.go:407.6,409.4 1 0 +github.com/echovault/sugardb/internal/utils.go:411.2,411.30 1 0 +github.com/echovault/sugardb/internal/utils.go:411.30,412.62 1 0 +github.com/echovault/sugardb/internal/utils.go:412.62,414.4 1 0 +github.com/echovault/sugardb/internal/utils.go:414.6,416.4 1 0 +github.com/echovault/sugardb/internal/utils.go:418.2,418.13 1 0 +github.com/echovault/sugardb/internal/utils.go:421.33,423.16 2 0 +github.com/echovault/sugardb/internal/utils.go:423.16,425.3 1 0 +github.com/echovault/sugardb/internal/utils.go:427.2,428.16 2 0 +github.com/echovault/sugardb/internal/utils.go:428.16,430.3 1 0 +github.com/echovault/sugardb/internal/utils.go:431.2,431.15 1 0 +github.com/echovault/sugardb/internal/utils.go:431.15,433.3 1 0 +github.com/echovault/sugardb/internal/utils.go:435.2,435.42 1 0 +github.com/echovault/sugardb/internal/utils.go:438.61,443.12 4 0 +github.com/echovault/sugardb/internal/utils.go:443.12,444.7 1 0 +github.com/echovault/sugardb/internal/utils.go:444.7,446.73 2 0 +github.com/echovault/sugardb/internal/utils.go:446.73,448.13 1 0 +github.com/echovault/sugardb/internal/utils.go:450.4,450.9 1 0 +github.com/echovault/sugardb/internal/utils.go:452.3,452.21 1 0 +github.com/echovault/sugardb/internal/utils.go:455.2,456.15 2 0 +github.com/echovault/sugardb/internal/utils.go:456.15,458.3 1 0 +github.com/echovault/sugardb/internal/utils.go:460.2,460.9 1 0 +github.com/echovault/sugardb/internal/utils.go:461.18,462.47 1 0 +github.com/echovault/sugardb/internal/utils.go:463.14,464.19 1 0 +github.com/echovault/sugardb/internal/utils.go:468.84,473.12 4 0 +github.com/echovault/sugardb/internal/utils.go:473.12,474.7 1 0 +github.com/echovault/sugardb/internal/utils.go:474.7,476.73 2 0 +github.com/echovault/sugardb/internal/utils.go:476.73,478.13 1 0 +github.com/echovault/sugardb/internal/utils.go:480.4,480.9 1 0 +github.com/echovault/sugardb/internal/utils.go:482.3,482.21 1 0 +github.com/echovault/sugardb/internal/utils.go:485.2,486.15 2 0 +github.com/echovault/sugardb/internal/utils.go:486.15,488.3 1 0 +github.com/echovault/sugardb/internal/utils.go:490.2,490.9 1 0 +github.com/echovault/sugardb/internal/utils.go:491.18,492.47 1 0 +github.com/echovault/sugardb/internal/utils.go:493.14,494.19 1 0 github.com/echovault/sugardb/internal/types.go:35.43,40.29 3 0 github.com/echovault/sugardb/internal/types.go:41.11,42.12 1 0 github.com/echovault/sugardb/internal/types.go:44.11,45.34 1 0 @@ -229,239 +410,58 @@ github.com/echovault/sugardb/internal/clock/clock.go:24.34,26.2 1 0 github.com/echovault/sugardb/internal/clock/clock.go:28.58,30.2 1 0 github.com/echovault/sugardb/internal/clock/clock.go:34.34,37.2 2 0 github.com/echovault/sugardb/internal/clock/clock.go:39.58,41.2 1 0 -github.com/echovault/sugardb/internal/config/config.go:64.34,70.24 3 0 -github.com/echovault/sugardb/internal/config/config.go:70.24,72.35 2 0 -github.com/echovault/sugardb/internal/config/config.go:72.35,74.5 1 0 -github.com/echovault/sugardb/internal/config/config.go:75.4,75.22 1 0 -github.com/echovault/sugardb/internal/config/config.go:75.22,77.5 1 0 -github.com/echovault/sugardb/internal/config/config.go:78.4,79.14 2 0 -github.com/echovault/sugardb/internal/config/config.go:82.2,82.115 1 0 -github.com/echovault/sugardb/internal/config/config.go:82.115,85.3 2 0 -github.com/echovault/sugardb/internal/config/config.go:87.2,90.29 2 0 -github.com/echovault/sugardb/internal/config/config.go:90.29,91.86 1 0 -github.com/echovault/sugardb/internal/config/config.go:91.86,93.5 1 0 -github.com/echovault/sugardb/internal/config/config.go:93.7,95.5 1 0 -github.com/echovault/sugardb/internal/config/config.go:96.4,97.14 2 0 -github.com/echovault/sugardb/internal/config/config.go:100.2,103.59 2 0 -github.com/echovault/sugardb/internal/config/config.go:103.59,105.17 2 0 -github.com/echovault/sugardb/internal/config/config.go:105.17,107.4 1 0 -github.com/echovault/sugardb/internal/config/config.go:108.3,109.13 2 0 -github.com/echovault/sugardb/internal/config/config.go:112.2,121.88 2 0 -github.com/echovault/sugardb/internal/config/config.go:121.88,128.23 3 0 -github.com/echovault/sugardb/internal/config/config.go:128.23,130.5 1 0 -github.com/echovault/sugardb/internal/config/config.go:131.4,132.14 2 0 -github.com/echovault/sugardb/internal/config/config.go:135.2,139.24 2 0 -github.com/echovault/sugardb/internal/config/config.go:139.24,140.36 1 0 -github.com/echovault/sugardb/internal/config/config.go:140.36,142.5 1 0 -github.com/echovault/sugardb/internal/config/config.go:143.4,144.14 2 0 -github.com/echovault/sugardb/internal/config/config.go:147.2,188.14 23 0 -github.com/echovault/sugardb/internal/config/config.go:188.14,190.3 1 0 -github.com/echovault/sugardb/internal/config/config.go:191.2,192.14 2 0 -github.com/echovault/sugardb/internal/config/config.go:192.14,194.3 1 0 -github.com/echovault/sugardb/internal/config/config.go:196.2,226.22 2 0 -github.com/echovault/sugardb/internal/config/config.go:226.22,228.45 1 0 -github.com/echovault/sugardb/internal/config/config.go:228.45,229.14 1 0 -github.com/echovault/sugardb/internal/config/config.go:230.9,231.17 1 0 -github.com/echovault/sugardb/internal/config/config.go:231.17,232.36 1 0 -github.com/echovault/sugardb/internal/config/config.go:232.36,234.6 1 0 -github.com/echovault/sugardb/internal/config/config.go:237.4,239.22 2 0 -github.com/echovault/sugardb/internal/config/config.go:239.22,240.59 1 0 -github.com/echovault/sugardb/internal/config/config.go:240.59,242.6 1 0 -github.com/echovault/sugardb/internal/config/config.go:245.4,245.39 1 0 -github.com/echovault/sugardb/internal/config/config.go:245.39,246.59 1 0 -github.com/echovault/sugardb/internal/config/config.go:246.59,248.6 1 0 -github.com/echovault/sugardb/internal/config/config.go:254.2,256.45 2 0 -github.com/echovault/sugardb/internal/config/config.go:256.45,258.3 1 0 -github.com/echovault/sugardb/internal/config/config.go:260.2,260.18 1 0 -github.com/echovault/sugardb/internal/config/default.go:9.29,42.2 3 0 -github.com/echovault/sugardb/internal/types.go:35.43,40.29 3 0 -github.com/echovault/sugardb/internal/types.go:41.11,42.12 1 0 -github.com/echovault/sugardb/internal/types.go:44.11,45.34 1 0 -github.com/echovault/sugardb/internal/types.go:47.22,48.12 1 0 -github.com/echovault/sugardb/internal/types.go:49.14,52.24 2 0 -github.com/echovault/sugardb/internal/types.go:55.16,56.23 1 0 -github.com/echovault/sugardb/internal/types.go:56.23,59.4 2 0 -github.com/echovault/sugardb/internal/types.go:62.31,63.53 1 0 -github.com/echovault/sugardb/internal/types.go:65.10,66.117 1 0 -github.com/echovault/sugardb/internal/types.go:69.2,69.18 1 0 -github.com/echovault/sugardb/internal/utils.go:41.38,45.16 2 0 -github.com/echovault/sugardb/internal/utils.go:45.16,47.3 1 0 -github.com/echovault/sugardb/internal/utils.go:49.2,49.15 1 0 -github.com/echovault/sugardb/internal/utils.go:49.15,52.3 2 0 -github.com/echovault/sugardb/internal/utils.go:54.2,56.10 2 0 -github.com/echovault/sugardb/internal/utils.go:59.43,63.16 3 0 -github.com/echovault/sugardb/internal/utils.go:63.16,65.3 1 0 -github.com/echovault/sugardb/internal/utils.go:67.2,68.42 2 0 -github.com/echovault/sugardb/internal/utils.go:68.42,70.3 1 0 -github.com/echovault/sugardb/internal/utils.go:72.2,72.17 1 0 -github.com/echovault/sugardb/internal/utils.go:75.47,82.6 4 0 -github.com/echovault/sugardb/internal/utils.go:82.6,84.43 2 0 -github.com/echovault/sugardb/internal/utils.go:84.43,85.9 1 0 -github.com/echovault/sugardb/internal/utils.go:87.3,87.17 1 0 -github.com/echovault/sugardb/internal/utils.go:87.17,89.4 1 0 -github.com/echovault/sugardb/internal/utils.go:90.3,91.21 2 0 -github.com/echovault/sugardb/internal/utils.go:91.21,92.9 1 0 -github.com/echovault/sugardb/internal/utils.go:94.3,94.15 1 0 -github.com/echovault/sugardb/internal/utils.go:97.2,97.37 1 0 -github.com/echovault/sugardb/internal/utils.go:100.120,102.20 2 0 -github.com/echovault/sugardb/internal/utils.go:102.20,104.3 1 0 -github.com/echovault/sugardb/internal/utils.go:105.2,105.16 1 0 -github.com/echovault/sugardb/internal/utils.go:105.16,107.3 1 0 -github.com/echovault/sugardb/internal/utils.go:108.2,108.24 1 0 -github.com/echovault/sugardb/internal/utils.go:108.24,110.3 1 0 -github.com/echovault/sugardb/internal/utils.go:111.2,111.21 1 0 -github.com/echovault/sugardb/internal/utils.go:111.21,113.3 1 0 -github.com/echovault/sugardb/internal/utils.go:114.2,114.16 1 0 -github.com/echovault/sugardb/internal/utils.go:117.37,119.16 2 0 -github.com/echovault/sugardb/internal/utils.go:119.16,121.3 1 0 -github.com/echovault/sugardb/internal/utils.go:122.2,122.15 1 0 -github.com/echovault/sugardb/internal/utils.go:122.15,123.37 1 0 -github.com/echovault/sugardb/internal/utils.go:123.37,125.4 1 0 -github.com/echovault/sugardb/internal/utils.go:128.2,130.23 2 0 -github.com/echovault/sugardb/internal/utils.go:133.72,134.65 1 0 -github.com/echovault/sugardb/internal/utils.go:134.65,137.3 1 0 -github.com/echovault/sugardb/internal/utils.go:138.2,138.18 1 0 -github.com/echovault/sugardb/internal/utils.go:138.18,141.3 1 0 -github.com/echovault/sugardb/internal/utils.go:142.2,142.49 1 0 -github.com/echovault/sugardb/internal/utils.go:142.49,143.52 1 0 -github.com/echovault/sugardb/internal/utils.go:143.52,145.4 1 0 -github.com/echovault/sugardb/internal/utils.go:147.2,147.71 1 0 -github.com/echovault/sugardb/internal/utils.go:150.66,152.2 1 0 -github.com/echovault/sugardb/internal/utils.go:154.24,155.11 1 0 -github.com/echovault/sugardb/internal/utils.go:155.11,157.3 1 0 -github.com/echovault/sugardb/internal/utils.go:158.2,158.10 1 0 -github.com/echovault/sugardb/internal/utils.go:162.49,166.16 3 0 -github.com/echovault/sugardb/internal/utils.go:166.16,168.3 1 0 -github.com/echovault/sugardb/internal/utils.go:170.2,171.17 2 0 -github.com/echovault/sugardb/internal/utils.go:172.12,173.19 1 0 -github.com/echovault/sugardb/internal/utils.go:174.12,175.26 1 0 -github.com/echovault/sugardb/internal/utils.go:176.12,177.33 1 0 -github.com/echovault/sugardb/internal/utils.go:178.12,179.40 1 0 -github.com/echovault/sugardb/internal/utils.go:180.12,181.47 1 0 -github.com/echovault/sugardb/internal/utils.go:182.10,183.91 1 0 -github.com/echovault/sugardb/internal/utils.go:186.2,186.30 1 0 -github.com/echovault/sugardb/internal/utils.go:190.64,191.20 1 0 -github.com/echovault/sugardb/internal/utils.go:191.20,193.3 1 0 -github.com/echovault/sugardb/internal/utils.go:196.2,196.33 1 0 -github.com/echovault/sugardb/internal/utils.go:196.33,198.3 1 0 -github.com/echovault/sugardb/internal/utils.go:203.2,206.37 2 0 -github.com/echovault/sugardb/internal/utils.go:210.100,211.36 1 0 -github.com/echovault/sugardb/internal/utils.go:211.36,213.26 2 0 -github.com/echovault/sugardb/internal/utils.go:213.26,215.35 1 0 -github.com/echovault/sugardb/internal/utils.go:215.35,216.13 1 0 -github.com/echovault/sugardb/internal/utils.go:219.4,219.30 1 0 -github.com/echovault/sugardb/internal/utils.go:219.30,221.5 1 0 -github.com/echovault/sugardb/internal/utils.go:223.3,223.36 1 0 -github.com/echovault/sugardb/internal/utils.go:223.36,225.4 1 0 -github.com/echovault/sugardb/internal/utils.go:227.2,227.14 1 0 -github.com/echovault/sugardb/internal/utils.go:232.43,233.14 1 0 -github.com/echovault/sugardb/internal/utils.go:233.14,235.3 1 0 -github.com/echovault/sugardb/internal/utils.go:236.2,236.30 1 0 -github.com/echovault/sugardb/internal/utils.go:236.30,238.3 1 0 -github.com/echovault/sugardb/internal/utils.go:239.2,239.30 1 0 -github.com/echovault/sugardb/internal/utils.go:239.30,241.3 1 0 -github.com/echovault/sugardb/internal/utils.go:243.2,244.21 2 0 -github.com/echovault/sugardb/internal/utils.go:244.21,246.3 1 0 -github.com/echovault/sugardb/internal/utils.go:248.2,249.29 2 0 -github.com/echovault/sugardb/internal/utils.go:249.29,251.13 2 0 -github.com/echovault/sugardb/internal/utils.go:251.13,252.9 1 0 -github.com/echovault/sugardb/internal/utils.go:256.2,256.10 1 0 -github.com/echovault/sugardb/internal/utils.go:259.41,261.28 2 0 -github.com/echovault/sugardb/internal/utils.go:261.28,263.3 1 0 -github.com/echovault/sugardb/internal/utils.go:264.2,264.20 1 0 -github.com/echovault/sugardb/internal/utils.go:267.47,270.16 3 0 -github.com/echovault/sugardb/internal/utils.go:270.16,272.3 1 0 -github.com/echovault/sugardb/internal/utils.go:273.2,273.24 1 0 -github.com/echovault/sugardb/internal/utils.go:276.52,279.16 3 0 -github.com/echovault/sugardb/internal/utils.go:279.16,281.3 1 0 -github.com/echovault/sugardb/internal/utils.go:282.2,282.24 1 0 -github.com/echovault/sugardb/internal/utils.go:285.50,288.16 3 0 -github.com/echovault/sugardb/internal/utils.go:288.16,290.3 1 0 -github.com/echovault/sugardb/internal/utils.go:291.2,291.25 1 0 -github.com/echovault/sugardb/internal/utils.go:294.52,297.16 3 0 -github.com/echovault/sugardb/internal/utils.go:297.16,299.3 1 0 -github.com/echovault/sugardb/internal/utils.go:300.2,300.23 1 0 -github.com/echovault/sugardb/internal/utils.go:303.51,306.16 3 0 -github.com/echovault/sugardb/internal/utils.go:306.16,308.3 1 0 -github.com/echovault/sugardb/internal/utils.go:309.2,309.22 1 0 -github.com/echovault/sugardb/internal/utils.go:312.59,316.16 3 0 -github.com/echovault/sugardb/internal/utils.go:316.16,318.3 1 0 -github.com/echovault/sugardb/internal/utils.go:320.2,320.16 1 0 -github.com/echovault/sugardb/internal/utils.go:320.16,322.3 1 0 -github.com/echovault/sugardb/internal/utils.go:324.2,324.39 1 0 -github.com/echovault/sugardb/internal/utils.go:324.39,326.3 1 0 -github.com/echovault/sugardb/internal/utils.go:328.2,329.30 2 0 -github.com/echovault/sugardb/internal/utils.go:329.30,330.17 1 0 -github.com/echovault/sugardb/internal/utils.go:330.17,332.12 2 0 -github.com/echovault/sugardb/internal/utils.go:334.3,334.22 1 0 -github.com/echovault/sugardb/internal/utils.go:336.2,336.17 1 0 -github.com/echovault/sugardb/internal/utils.go:339.67,342.16 3 0 -github.com/echovault/sugardb/internal/utils.go:342.16,344.3 1 0 -github.com/echovault/sugardb/internal/utils.go:345.2,345.16 1 0 -github.com/echovault/sugardb/internal/utils.go:345.16,347.3 1 0 -github.com/echovault/sugardb/internal/utils.go:348.2,349.31 2 0 -github.com/echovault/sugardb/internal/utils.go:349.31,350.18 1 0 -github.com/echovault/sugardb/internal/utils.go:350.18,352.12 2 0 -github.com/echovault/sugardb/internal/utils.go:354.3,355.33 2 0 -github.com/echovault/sugardb/internal/utils.go:355.33,357.4 1 0 -github.com/echovault/sugardb/internal/utils.go:358.3,358.17 1 0 -github.com/echovault/sugardb/internal/utils.go:360.2,360.17 1 0 -github.com/echovault/sugardb/internal/utils.go:363.57,366.16 3 0 -github.com/echovault/sugardb/internal/utils.go:366.16,368.3 1 0 -github.com/echovault/sugardb/internal/utils.go:369.2,369.16 1 0 -github.com/echovault/sugardb/internal/utils.go:369.16,371.3 1 0 -github.com/echovault/sugardb/internal/utils.go:372.2,373.30 2 0 -github.com/echovault/sugardb/internal/utils.go:373.30,374.17 1 0 -github.com/echovault/sugardb/internal/utils.go:374.17,376.12 2 0 -github.com/echovault/sugardb/internal/utils.go:378.3,378.23 1 0 -github.com/echovault/sugardb/internal/utils.go:380.2,380.17 1 0 -github.com/echovault/sugardb/internal/utils.go:383.58,386.16 3 0 -github.com/echovault/sugardb/internal/utils.go:386.16,388.3 1 0 -github.com/echovault/sugardb/internal/utils.go:389.2,389.16 1 0 -github.com/echovault/sugardb/internal/utils.go:389.16,391.3 1 0 -github.com/echovault/sugardb/internal/utils.go:392.2,393.30 2 0 -github.com/echovault/sugardb/internal/utils.go:393.30,394.17 1 0 -github.com/echovault/sugardb/internal/utils.go:394.17,396.12 2 0 -github.com/echovault/sugardb/internal/utils.go:398.3,398.20 1 0 -github.com/echovault/sugardb/internal/utils.go:400.2,400.17 1 0 -github.com/echovault/sugardb/internal/utils.go:403.70,404.32 1 0 -github.com/echovault/sugardb/internal/utils.go:404.32,405.60 1 0 -github.com/echovault/sugardb/internal/utils.go:405.60,407.4 1 0 -github.com/echovault/sugardb/internal/utils.go:407.6,409.4 1 0 -github.com/echovault/sugardb/internal/utils.go:411.2,411.30 1 0 -github.com/echovault/sugardb/internal/utils.go:411.30,412.62 1 0 -github.com/echovault/sugardb/internal/utils.go:412.62,414.4 1 0 -github.com/echovault/sugardb/internal/utils.go:414.6,416.4 1 0 -github.com/echovault/sugardb/internal/utils.go:418.2,418.13 1 0 -github.com/echovault/sugardb/internal/utils.go:421.33,423.16 2 0 -github.com/echovault/sugardb/internal/utils.go:423.16,425.3 1 0 -github.com/echovault/sugardb/internal/utils.go:427.2,428.16 2 0 -github.com/echovault/sugardb/internal/utils.go:428.16,430.3 1 0 -github.com/echovault/sugardb/internal/utils.go:431.2,431.15 1 0 -github.com/echovault/sugardb/internal/utils.go:431.15,433.3 1 0 -github.com/echovault/sugardb/internal/utils.go:435.2,435.42 1 0 -github.com/echovault/sugardb/internal/utils.go:438.61,443.12 4 0 -github.com/echovault/sugardb/internal/utils.go:443.12,444.7 1 0 -github.com/echovault/sugardb/internal/utils.go:444.7,446.73 2 0 -github.com/echovault/sugardb/internal/utils.go:446.73,448.13 1 0 -github.com/echovault/sugardb/internal/utils.go:450.4,450.9 1 0 -github.com/echovault/sugardb/internal/utils.go:452.3,452.21 1 0 -github.com/echovault/sugardb/internal/utils.go:455.2,456.15 2 0 -github.com/echovault/sugardb/internal/utils.go:456.15,458.3 1 0 -github.com/echovault/sugardb/internal/utils.go:460.2,460.9 1 0 -github.com/echovault/sugardb/internal/utils.go:461.18,462.47 1 0 -github.com/echovault/sugardb/internal/utils.go:463.14,464.19 1 0 -github.com/echovault/sugardb/internal/utils.go:468.84,473.12 4 0 -github.com/echovault/sugardb/internal/utils.go:473.12,474.7 1 0 -github.com/echovault/sugardb/internal/utils.go:474.7,476.73 2 0 -github.com/echovault/sugardb/internal/utils.go:476.73,478.13 1 0 -github.com/echovault/sugardb/internal/utils.go:480.4,480.9 1 0 -github.com/echovault/sugardb/internal/utils.go:482.3,482.21 1 0 -github.com/echovault/sugardb/internal/utils.go:485.2,486.15 2 0 -github.com/echovault/sugardb/internal/utils.go:486.15,488.3 1 0 -github.com/echovault/sugardb/internal/utils.go:490.2,490.9 1 0 -github.com/echovault/sugardb/internal/utils.go:491.18,492.47 1 0 -github.com/echovault/sugardb/internal/utils.go:493.14,494.19 1 0 +github.com/echovault/sugardb/internal/config/config.go:68.34,74.24 3 0 +github.com/echovault/sugardb/internal/config/config.go:74.24,76.35 2 0 +github.com/echovault/sugardb/internal/config/config.go:76.35,78.5 1 0 +github.com/echovault/sugardb/internal/config/config.go:79.4,79.22 1 0 +github.com/echovault/sugardb/internal/config/config.go:79.22,81.5 1 0 +github.com/echovault/sugardb/internal/config/config.go:82.4,83.14 2 0 +github.com/echovault/sugardb/internal/config/config.go:86.2,86.115 1 0 +github.com/echovault/sugardb/internal/config/config.go:86.115,89.3 2 0 +github.com/echovault/sugardb/internal/config/config.go:91.2,94.29 2 0 +github.com/echovault/sugardb/internal/config/config.go:94.29,95.86 1 0 +github.com/echovault/sugardb/internal/config/config.go:95.86,97.5 1 0 +github.com/echovault/sugardb/internal/config/config.go:97.7,99.5 1 0 +github.com/echovault/sugardb/internal/config/config.go:100.4,101.14 2 0 +github.com/echovault/sugardb/internal/config/config.go:104.2,107.59 2 0 +github.com/echovault/sugardb/internal/config/config.go:107.59,109.17 2 0 +github.com/echovault/sugardb/internal/config/config.go:109.17,111.4 1 0 +github.com/echovault/sugardb/internal/config/config.go:112.3,113.13 2 0 +github.com/echovault/sugardb/internal/config/config.go:116.2,125.88 2 0 +github.com/echovault/sugardb/internal/config/config.go:125.88,132.23 3 0 +github.com/echovault/sugardb/internal/config/config.go:132.23,134.5 1 0 +github.com/echovault/sugardb/internal/config/config.go:135.4,136.14 2 0 +github.com/echovault/sugardb/internal/config/config.go:139.2,143.24 2 0 +github.com/echovault/sugardb/internal/config/config.go:143.24,144.36 1 0 +github.com/echovault/sugardb/internal/config/config.go:144.36,146.5 1 0 +github.com/echovault/sugardb/internal/config/config.go:147.4,148.14 2 0 +github.com/echovault/sugardb/internal/config/config.go:151.2,195.14 26 0 +github.com/echovault/sugardb/internal/config/config.go:195.14,197.3 1 0 +github.com/echovault/sugardb/internal/config/config.go:198.2,199.14 2 0 +github.com/echovault/sugardb/internal/config/config.go:199.14,201.3 1 0 +github.com/echovault/sugardb/internal/config/config.go:203.2,236.22 2 0 +github.com/echovault/sugardb/internal/config/config.go:236.22,238.45 1 0 +github.com/echovault/sugardb/internal/config/config.go:238.45,239.14 1 0 +github.com/echovault/sugardb/internal/config/config.go:240.9,241.17 1 0 +github.com/echovault/sugardb/internal/config/config.go:241.17,242.36 1 0 +github.com/echovault/sugardb/internal/config/config.go:242.36,244.6 1 0 +github.com/echovault/sugardb/internal/config/config.go:247.4,249.22 2 0 +github.com/echovault/sugardb/internal/config/config.go:249.22,250.59 1 0 +github.com/echovault/sugardb/internal/config/config.go:250.59,252.6 1 0 +github.com/echovault/sugardb/internal/config/config.go:255.4,255.39 1 0 +github.com/echovault/sugardb/internal/config/config.go:255.39,256.59 1 0 +github.com/echovault/sugardb/internal/config/config.go:256.59,258.6 1 0 +github.com/echovault/sugardb/internal/config/config.go:264.2,266.45 2 0 +github.com/echovault/sugardb/internal/config/config.go:266.45,268.3 1 0 +github.com/echovault/sugardb/internal/config/config.go:270.2,270.18 1 0 +github.com/echovault/sugardb/internal/config/default.go:10.29,46.2 3 0 +github.com/echovault/sugardb/internal/clock/clock.go:14.23,16.43 1 0 +github.com/echovault/sugardb/internal/clock/clock.go:16.43,18.3 1 0 +github.com/echovault/sugardb/internal/clock/clock.go:19.2,19.20 1 0 +github.com/echovault/sugardb/internal/clock/clock.go:24.34,26.2 1 0 +github.com/echovault/sugardb/internal/clock/clock.go:28.58,30.2 1 0 +github.com/echovault/sugardb/internal/clock/clock.go:34.34,37.2 2 0 +github.com/echovault/sugardb/internal/clock/clock.go:39.58,41.2 1 0 github.com/echovault/sugardb/internal/types.go:35.43,40.29 3 0 github.com/echovault/sugardb/internal/types.go:41.11,42.12 1 0 github.com/echovault/sugardb/internal/types.go:44.11,45.34 1 0 @@ -1703,47 +1703,47 @@ github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:67.2,67.40 1 0 github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:67.40,70.3 2 0 github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:72.2,74.12 2 0 github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:78.30,80.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:52.31,56.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:58.46,70.24 9 0 -github.com/echovault/sugardb/internal/raft/raft.go:70.24,75.3 3 0 -github.com/echovault/sugardb/internal/raft/raft.go:75.8,77.17 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:77.17,79.4 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:81.3,82.17 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:82.17,84.4 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:86.3,89.17 3 0 -github.com/echovault/sugardb/internal/raft/raft.go:89.17,91.4 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:94.2,96.16 3 0 -github.com/echovault/sugardb/internal/raft/raft.go:96.16,98.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:100.2,108.16 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:108.16,110.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:113.2,133.16 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:133.16,135.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:137.2,137.27 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:137.27,148.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:150.2,150.21 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:153.74,155.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:157.36,159.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:161.38,163.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:165.40,172.2 4 0 -github.com/echovault/sugardb/internal/raft/raft.go:179.9,180.22 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:180.22,182.44 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:182.44,184.4 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:186.3,186.56 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:186.56,188.42 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:188.42,190.5 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:193.3,194.17 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:194.17,196.4 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:199.2,199.12 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:202.61,203.23 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:203.23,205.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:207.2,207.73 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:207.73,209.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:211.2,211.12 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:214.37,216.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:218.31,220.22 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:220.22,222.17 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:222.17,225.4 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:226.3,226.49 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:53.31,57.2 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:59.46,74.24 12 0 +github.com/echovault/sugardb/internal/raft/raft.go:74.24,79.3 3 0 +github.com/echovault/sugardb/internal/raft/raft.go:79.8,81.17 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:81.17,83.4 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:85.3,86.17 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:86.17,88.4 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:90.3,93.17 3 0 +github.com/echovault/sugardb/internal/raft/raft.go:93.17,95.4 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:98.2,100.16 3 0 +github.com/echovault/sugardb/internal/raft/raft.go:100.16,102.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:104.2,112.16 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:112.16,114.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:117.2,137.16 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:137.16,139.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:141.2,141.27 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:141.27,152.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:154.2,154.21 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:157.74,159.2 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:161.36,163.2 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:165.38,167.2 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:169.40,176.2 4 0 +github.com/echovault/sugardb/internal/raft/raft.go:183.9,184.22 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:184.22,186.44 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:186.44,188.4 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:190.3,190.56 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:190.56,192.42 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:192.42,194.5 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:197.3,198.17 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:198.17,200.4 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:203.2,203.12 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:206.61,207.23 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:207.23,209.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:211.2,211.73 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:211.73,213.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:215.2,215.12 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:218.37,220.2 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:222.31,224.22 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:224.22,226.17 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:226.17,229.4 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:230.3,230.49 1 0 github.com/echovault/sugardb/internal/types.go:35.43,40.29 3 0 github.com/echovault/sugardb/internal/types.go:41.11,42.12 1 0 github.com/echovault/sugardb/internal/types.go:44.11,45.34 1 0 @@ -1913,7 +1913,7 @@ github.com/echovault/sugardb/internal/utils.go:435.2,435.42 1 1 github.com/echovault/sugardb/internal/utils.go:438.61,443.12 4 1 github.com/echovault/sugardb/internal/utils.go:443.12,444.7 1 1 github.com/echovault/sugardb/internal/utils.go:444.7,446.73 2 1 -github.com/echovault/sugardb/internal/utils.go:446.73,448.13 1 1 +github.com/echovault/sugardb/internal/utils.go:446.73,448.13 1 0 github.com/echovault/sugardb/internal/utils.go:450.4,450.9 1 1 github.com/echovault/sugardb/internal/utils.go:452.3,452.21 1 1 github.com/echovault/sugardb/internal/utils.go:455.2,456.15 2 1 @@ -2489,47 +2489,481 @@ github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:67.2,67.40 1 0 github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:67.40,70.3 2 0 github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:72.2,74.12 2 0 github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:78.30,80.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:52.31,56.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:58.46,70.24 9 0 -github.com/echovault/sugardb/internal/raft/raft.go:70.24,75.3 3 0 -github.com/echovault/sugardb/internal/raft/raft.go:75.8,77.17 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:77.17,79.4 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:81.3,82.17 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:82.17,84.4 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:86.3,89.17 3 0 -github.com/echovault/sugardb/internal/raft/raft.go:89.17,91.4 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:94.2,96.16 3 0 -github.com/echovault/sugardb/internal/raft/raft.go:96.16,98.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:100.2,108.16 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:108.16,110.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:113.2,133.16 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:133.16,135.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:137.2,137.27 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:137.27,148.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:150.2,150.21 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:153.74,155.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:157.36,159.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:161.38,163.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:165.40,172.2 4 0 -github.com/echovault/sugardb/internal/raft/raft.go:179.9,180.22 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:180.22,182.44 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:182.44,184.4 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:186.3,186.56 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:186.56,188.42 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:188.42,190.5 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:193.3,194.17 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:194.17,196.4 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:199.2,199.12 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:202.61,203.23 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:203.23,205.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:207.2,207.73 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:207.73,209.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:211.2,211.12 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:214.37,216.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:218.31,220.22 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:220.22,222.17 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:222.17,225.4 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:226.3,226.49 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:53.31,57.2 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:59.46,74.24 12 0 +github.com/echovault/sugardb/internal/raft/raft.go:74.24,79.3 3 0 +github.com/echovault/sugardb/internal/raft/raft.go:79.8,81.17 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:81.17,83.4 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:85.3,86.17 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:86.17,88.4 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:90.3,93.17 3 0 +github.com/echovault/sugardb/internal/raft/raft.go:93.17,95.4 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:98.2,100.16 3 0 +github.com/echovault/sugardb/internal/raft/raft.go:100.16,102.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:104.2,112.16 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:112.16,114.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:117.2,137.16 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:137.16,139.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:141.2,141.27 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:141.27,152.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:154.2,154.21 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:157.74,159.2 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:161.36,163.2 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:165.38,167.2 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:169.40,176.2 4 0 +github.com/echovault/sugardb/internal/raft/raft.go:183.9,184.22 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:184.22,186.44 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:186.44,188.4 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:190.3,190.56 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:190.56,192.42 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:192.42,194.5 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:197.3,198.17 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:198.17,200.4 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:203.2,203.12 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:206.61,207.23 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:207.23,209.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:211.2,211.73 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:211.73,213.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:215.2,215.12 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:218.37,220.2 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:222.31,224.22 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:224.22,226.17 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:226.17,229.4 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:230.3,230.49 1 0 +github.com/echovault/sugardb/internal/types.go:35.43,40.29 3 1 +github.com/echovault/sugardb/internal/types.go:41.11,42.12 1 0 +github.com/echovault/sugardb/internal/types.go:44.11,45.34 1 1 +github.com/echovault/sugardb/internal/types.go:47.22,48.12 1 1 +github.com/echovault/sugardb/internal/types.go:49.14,52.24 2 1 +github.com/echovault/sugardb/internal/types.go:55.16,56.23 1 1 +github.com/echovault/sugardb/internal/types.go:56.23,59.4 2 1 +github.com/echovault/sugardb/internal/types.go:62.31,63.53 1 1 +github.com/echovault/sugardb/internal/types.go:65.10,66.117 1 0 +github.com/echovault/sugardb/internal/types.go:69.2,69.18 1 1 +github.com/echovault/sugardb/internal/utils.go:41.38,45.16 2 1 +github.com/echovault/sugardb/internal/utils.go:45.16,47.3 1 1 +github.com/echovault/sugardb/internal/utils.go:49.2,49.15 1 1 +github.com/echovault/sugardb/internal/utils.go:49.15,52.3 2 1 +github.com/echovault/sugardb/internal/utils.go:54.2,56.10 2 0 +github.com/echovault/sugardb/internal/utils.go:59.43,63.16 3 1 +github.com/echovault/sugardb/internal/utils.go:63.16,65.3 1 1 +github.com/echovault/sugardb/internal/utils.go:67.2,68.42 2 1 +github.com/echovault/sugardb/internal/utils.go:68.42,70.3 1 1 +github.com/echovault/sugardb/internal/utils.go:72.2,72.17 1 1 +github.com/echovault/sugardb/internal/utils.go:75.47,82.6 4 1 +github.com/echovault/sugardb/internal/utils.go:82.6,84.43 2 1 +github.com/echovault/sugardb/internal/utils.go:84.43,85.9 1 1 +github.com/echovault/sugardb/internal/utils.go:87.3,87.17 1 1 +github.com/echovault/sugardb/internal/utils.go:87.17,89.4 1 0 +github.com/echovault/sugardb/internal/utils.go:90.3,91.21 2 1 +github.com/echovault/sugardb/internal/utils.go:91.21,92.9 1 1 +github.com/echovault/sugardb/internal/utils.go:94.3,94.15 1 0 +github.com/echovault/sugardb/internal/utils.go:97.2,97.37 1 1 +github.com/echovault/sugardb/internal/utils.go:100.120,102.20 2 0 +github.com/echovault/sugardb/internal/utils.go:102.20,104.3 1 0 +github.com/echovault/sugardb/internal/utils.go:105.2,105.16 1 0 +github.com/echovault/sugardb/internal/utils.go:105.16,107.3 1 0 +github.com/echovault/sugardb/internal/utils.go:108.2,108.24 1 0 +github.com/echovault/sugardb/internal/utils.go:108.24,110.3 1 0 +github.com/echovault/sugardb/internal/utils.go:111.2,111.21 1 0 +github.com/echovault/sugardb/internal/utils.go:111.21,113.3 1 0 +github.com/echovault/sugardb/internal/utils.go:114.2,114.16 1 0 +github.com/echovault/sugardb/internal/utils.go:117.37,119.16 2 1 +github.com/echovault/sugardb/internal/utils.go:119.16,121.3 1 0 +github.com/echovault/sugardb/internal/utils.go:122.2,122.15 1 1 +github.com/echovault/sugardb/internal/utils.go:122.15,123.37 1 1 +github.com/echovault/sugardb/internal/utils.go:123.37,125.4 1 0 +github.com/echovault/sugardb/internal/utils.go:128.2,130.23 2 1 +github.com/echovault/sugardb/internal/utils.go:133.72,134.65 1 1 +github.com/echovault/sugardb/internal/utils.go:134.65,137.3 1 1 +github.com/echovault/sugardb/internal/utils.go:138.2,138.18 1 1 +github.com/echovault/sugardb/internal/utils.go:138.18,141.3 1 0 +github.com/echovault/sugardb/internal/utils.go:142.2,142.49 1 1 +github.com/echovault/sugardb/internal/utils.go:142.49,143.52 1 1 +github.com/echovault/sugardb/internal/utils.go:143.52,145.4 1 1 +github.com/echovault/sugardb/internal/utils.go:147.2,147.71 1 0 +github.com/echovault/sugardb/internal/utils.go:150.66,152.2 1 1 +github.com/echovault/sugardb/internal/utils.go:154.24,155.11 1 1 +github.com/echovault/sugardb/internal/utils.go:155.11,157.3 1 0 +github.com/echovault/sugardb/internal/utils.go:158.2,158.10 1 1 +github.com/echovault/sugardb/internal/utils.go:162.49,166.16 3 0 +github.com/echovault/sugardb/internal/utils.go:166.16,168.3 1 0 +github.com/echovault/sugardb/internal/utils.go:170.2,171.17 2 0 +github.com/echovault/sugardb/internal/utils.go:172.12,173.19 1 0 +github.com/echovault/sugardb/internal/utils.go:174.12,175.26 1 0 +github.com/echovault/sugardb/internal/utils.go:176.12,177.33 1 0 +github.com/echovault/sugardb/internal/utils.go:178.12,179.40 1 0 +github.com/echovault/sugardb/internal/utils.go:180.12,181.47 1 0 +github.com/echovault/sugardb/internal/utils.go:182.10,183.91 1 0 +github.com/echovault/sugardb/internal/utils.go:186.2,186.30 1 0 +github.com/echovault/sugardb/internal/utils.go:190.64,191.20 1 1 +github.com/echovault/sugardb/internal/utils.go:191.20,193.3 1 1 +github.com/echovault/sugardb/internal/utils.go:196.2,196.33 1 0 +github.com/echovault/sugardb/internal/utils.go:196.33,198.3 1 0 +github.com/echovault/sugardb/internal/utils.go:203.2,206.37 2 0 +github.com/echovault/sugardb/internal/utils.go:210.100,211.36 1 1 +github.com/echovault/sugardb/internal/utils.go:211.36,213.26 2 1 +github.com/echovault/sugardb/internal/utils.go:213.26,215.35 1 1 +github.com/echovault/sugardb/internal/utils.go:215.35,216.13 1 1 +github.com/echovault/sugardb/internal/utils.go:219.4,219.30 1 0 +github.com/echovault/sugardb/internal/utils.go:219.30,221.5 1 0 +github.com/echovault/sugardb/internal/utils.go:223.3,223.36 1 1 +github.com/echovault/sugardb/internal/utils.go:223.36,225.4 1 0 +github.com/echovault/sugardb/internal/utils.go:227.2,227.14 1 1 +github.com/echovault/sugardb/internal/utils.go:232.43,233.14 1 0 +github.com/echovault/sugardb/internal/utils.go:233.14,235.3 1 0 +github.com/echovault/sugardb/internal/utils.go:236.2,236.30 1 0 +github.com/echovault/sugardb/internal/utils.go:236.30,238.3 1 0 +github.com/echovault/sugardb/internal/utils.go:239.2,239.30 1 0 +github.com/echovault/sugardb/internal/utils.go:239.30,241.3 1 0 +github.com/echovault/sugardb/internal/utils.go:243.2,244.21 2 0 +github.com/echovault/sugardb/internal/utils.go:244.21,246.3 1 0 +github.com/echovault/sugardb/internal/utils.go:248.2,249.29 2 0 +github.com/echovault/sugardb/internal/utils.go:249.29,251.13 2 0 +github.com/echovault/sugardb/internal/utils.go:251.13,252.9 1 0 +github.com/echovault/sugardb/internal/utils.go:256.2,256.10 1 0 +github.com/echovault/sugardb/internal/utils.go:259.41,261.28 2 1 +github.com/echovault/sugardb/internal/utils.go:261.28,263.3 1 1 +github.com/echovault/sugardb/internal/utils.go:264.2,264.20 1 1 +github.com/echovault/sugardb/internal/utils.go:267.47,270.16 3 0 +github.com/echovault/sugardb/internal/utils.go:270.16,272.3 1 0 +github.com/echovault/sugardb/internal/utils.go:273.2,273.24 1 0 +github.com/echovault/sugardb/internal/utils.go:276.52,279.16 3 1 +github.com/echovault/sugardb/internal/utils.go:279.16,281.3 1 0 +github.com/echovault/sugardb/internal/utils.go:282.2,282.24 1 1 +github.com/echovault/sugardb/internal/utils.go:285.50,288.16 3 0 +github.com/echovault/sugardb/internal/utils.go:288.16,290.3 1 0 +github.com/echovault/sugardb/internal/utils.go:291.2,291.25 1 0 +github.com/echovault/sugardb/internal/utils.go:294.52,297.16 3 0 +github.com/echovault/sugardb/internal/utils.go:297.16,299.3 1 0 +github.com/echovault/sugardb/internal/utils.go:300.2,300.23 1 0 +github.com/echovault/sugardb/internal/utils.go:303.51,306.16 3 0 +github.com/echovault/sugardb/internal/utils.go:306.16,308.3 1 0 +github.com/echovault/sugardb/internal/utils.go:309.2,309.22 1 0 +github.com/echovault/sugardb/internal/utils.go:312.59,316.16 3 0 +github.com/echovault/sugardb/internal/utils.go:316.16,318.3 1 0 +github.com/echovault/sugardb/internal/utils.go:320.2,320.16 1 0 +github.com/echovault/sugardb/internal/utils.go:320.16,322.3 1 0 +github.com/echovault/sugardb/internal/utils.go:324.2,324.39 1 0 +github.com/echovault/sugardb/internal/utils.go:324.39,326.3 1 0 +github.com/echovault/sugardb/internal/utils.go:328.2,329.30 2 0 +github.com/echovault/sugardb/internal/utils.go:329.30,330.17 1 0 +github.com/echovault/sugardb/internal/utils.go:330.17,332.12 2 0 +github.com/echovault/sugardb/internal/utils.go:334.3,334.22 1 0 +github.com/echovault/sugardb/internal/utils.go:336.2,336.17 1 0 +github.com/echovault/sugardb/internal/utils.go:339.67,342.16 3 0 +github.com/echovault/sugardb/internal/utils.go:342.16,344.3 1 0 +github.com/echovault/sugardb/internal/utils.go:345.2,345.16 1 0 +github.com/echovault/sugardb/internal/utils.go:345.16,347.3 1 0 +github.com/echovault/sugardb/internal/utils.go:348.2,349.31 2 0 +github.com/echovault/sugardb/internal/utils.go:349.31,350.18 1 0 +github.com/echovault/sugardb/internal/utils.go:350.18,352.12 2 0 +github.com/echovault/sugardb/internal/utils.go:354.3,355.33 2 0 +github.com/echovault/sugardb/internal/utils.go:355.33,357.4 1 0 +github.com/echovault/sugardb/internal/utils.go:358.3,358.17 1 0 +github.com/echovault/sugardb/internal/utils.go:360.2,360.17 1 0 +github.com/echovault/sugardb/internal/utils.go:363.57,366.16 3 0 +github.com/echovault/sugardb/internal/utils.go:366.16,368.3 1 0 +github.com/echovault/sugardb/internal/utils.go:369.2,369.16 1 0 +github.com/echovault/sugardb/internal/utils.go:369.16,371.3 1 0 +github.com/echovault/sugardb/internal/utils.go:372.2,373.30 2 0 +github.com/echovault/sugardb/internal/utils.go:373.30,374.17 1 0 +github.com/echovault/sugardb/internal/utils.go:374.17,376.12 2 0 +github.com/echovault/sugardb/internal/utils.go:378.3,378.23 1 0 +github.com/echovault/sugardb/internal/utils.go:380.2,380.17 1 0 +github.com/echovault/sugardb/internal/utils.go:383.58,386.16 3 0 +github.com/echovault/sugardb/internal/utils.go:386.16,388.3 1 0 +github.com/echovault/sugardb/internal/utils.go:389.2,389.16 1 0 +github.com/echovault/sugardb/internal/utils.go:389.16,391.3 1 0 +github.com/echovault/sugardb/internal/utils.go:392.2,393.30 2 0 +github.com/echovault/sugardb/internal/utils.go:393.30,394.17 1 0 +github.com/echovault/sugardb/internal/utils.go:394.17,396.12 2 0 +github.com/echovault/sugardb/internal/utils.go:398.3,398.20 1 0 +github.com/echovault/sugardb/internal/utils.go:400.2,400.17 1 0 +github.com/echovault/sugardb/internal/utils.go:403.70,404.32 1 0 +github.com/echovault/sugardb/internal/utils.go:404.32,405.60 1 0 +github.com/echovault/sugardb/internal/utils.go:405.60,407.4 1 0 +github.com/echovault/sugardb/internal/utils.go:407.6,409.4 1 0 +github.com/echovault/sugardb/internal/utils.go:411.2,411.30 1 0 +github.com/echovault/sugardb/internal/utils.go:411.30,412.62 1 0 +github.com/echovault/sugardb/internal/utils.go:412.62,414.4 1 0 +github.com/echovault/sugardb/internal/utils.go:414.6,416.4 1 0 +github.com/echovault/sugardb/internal/utils.go:418.2,418.13 1 0 +github.com/echovault/sugardb/internal/utils.go:421.33,423.16 2 1 +github.com/echovault/sugardb/internal/utils.go:423.16,425.3 1 0 +github.com/echovault/sugardb/internal/utils.go:427.2,428.16 2 1 +github.com/echovault/sugardb/internal/utils.go:428.16,430.3 1 0 +github.com/echovault/sugardb/internal/utils.go:431.2,431.15 1 1 +github.com/echovault/sugardb/internal/utils.go:431.15,433.3 1 1 +github.com/echovault/sugardb/internal/utils.go:435.2,435.42 1 1 +github.com/echovault/sugardb/internal/utils.go:438.61,443.12 4 1 +github.com/echovault/sugardb/internal/utils.go:443.12,444.7 1 1 +github.com/echovault/sugardb/internal/utils.go:444.7,446.73 2 1 +github.com/echovault/sugardb/internal/utils.go:446.73,448.13 1 1 +github.com/echovault/sugardb/internal/utils.go:450.4,450.9 1 1 +github.com/echovault/sugardb/internal/utils.go:452.3,452.21 1 1 +github.com/echovault/sugardb/internal/utils.go:455.2,456.15 2 1 +github.com/echovault/sugardb/internal/utils.go:456.15,458.3 1 1 +github.com/echovault/sugardb/internal/utils.go:460.2,460.9 1 1 +github.com/echovault/sugardb/internal/utils.go:461.18,462.47 1 0 +github.com/echovault/sugardb/internal/utils.go:463.14,464.19 1 1 +github.com/echovault/sugardb/internal/utils.go:468.84,473.12 4 0 +github.com/echovault/sugardb/internal/utils.go:473.12,474.7 1 0 +github.com/echovault/sugardb/internal/utils.go:474.7,476.73 2 0 +github.com/echovault/sugardb/internal/utils.go:476.73,478.13 1 0 +github.com/echovault/sugardb/internal/utils.go:480.4,480.9 1 0 +github.com/echovault/sugardb/internal/utils.go:482.3,482.21 1 0 +github.com/echovault/sugardb/internal/utils.go:485.2,486.15 2 0 +github.com/echovault/sugardb/internal/utils.go:486.15,488.3 1 0 +github.com/echovault/sugardb/internal/utils.go:490.2,490.9 1 0 +github.com/echovault/sugardb/internal/utils.go:491.18,492.47 1 0 +github.com/echovault/sugardb/internal/utils.go:493.14,494.19 1 0 +github.com/echovault/sugardb/internal/clock/clock.go:14.23,16.43 1 1 +github.com/echovault/sugardb/internal/clock/clock.go:16.43,18.3 1 1 +github.com/echovault/sugardb/internal/clock/clock.go:19.2,19.20 1 0 +github.com/echovault/sugardb/internal/clock/clock.go:24.34,26.2 1 0 +github.com/echovault/sugardb/internal/clock/clock.go:28.58,30.2 1 0 +github.com/echovault/sugardb/internal/clock/clock.go:34.34,37.2 2 1 +github.com/echovault/sugardb/internal/clock/clock.go:39.58,41.2 1 0 +github.com/echovault/sugardb/internal/memberlist/broadcast.go:32.88,35.9 2 0 +github.com/echovault/sugardb/internal/memberlist/broadcast.go:35.9,37.3 1 0 +github.com/echovault/sugardb/internal/memberlist/broadcast.go:39.2,39.33 1 0 +github.com/echovault/sugardb/internal/memberlist/broadcast.go:40.18,42.56 1 0 +github.com/echovault/sugardb/internal/memberlist/broadcast.go:43.20,45.62 1 0 +github.com/echovault/sugardb/internal/memberlist/broadcast.go:46.10,47.15 1 0 +github.com/echovault/sugardb/internal/memberlist/broadcast.go:52.60,55.16 2 0 +github.com/echovault/sugardb/internal/memberlist/broadcast.go:55.16,58.3 2 0 +github.com/echovault/sugardb/internal/memberlist/broadcast.go:60.2,60.12 1 0 +github.com/echovault/sugardb/internal/memberlist/broadcast.go:64.55,66.2 0 0 +github.com/echovault/sugardb/internal/memberlist/delegate.go:42.47,46.2 1 0 +github.com/echovault/sugardb/internal/memberlist/delegate.go:49.54,59.16 3 0 +github.com/echovault/sugardb/internal/memberlist/delegate.go:59.16,61.3 1 0 +github.com/echovault/sugardb/internal/memberlist/delegate.go:63.2,63.10 1 0 +github.com/echovault/sugardb/internal/memberlist/delegate.go:67.54,69.55 2 0 +github.com/echovault/sugardb/internal/memberlist/delegate.go:69.55,72.3 2 0 +github.com/echovault/sugardb/internal/memberlist/delegate.go:74.2,74.20 1 0 +github.com/echovault/sugardb/internal/memberlist/delegate.go:75.18,77.39 1 0 +github.com/echovault/sugardb/internal/memberlist/delegate.go:77.39,80.4 2 0 +github.com/echovault/sugardb/internal/memberlist/delegate.go:81.3,82.17 2 0 +github.com/echovault/sugardb/internal/memberlist/delegate.go:82.17,84.4 1 0 +github.com/echovault/sugardb/internal/memberlist/delegate.go:86.19,88.39 1 0 +github.com/echovault/sugardb/internal/memberlist/delegate.go:88.39,91.4 2 0 +github.com/echovault/sugardb/internal/memberlist/delegate.go:93.3,99.67 3 0 +github.com/echovault/sugardb/internal/memberlist/delegate.go:99.67,101.4 1 0 +github.com/echovault/sugardb/internal/memberlist/delegate.go:103.20,105.39 1 0 +github.com/echovault/sugardb/internal/memberlist/delegate.go:105.39,108.4 2 0 +github.com/echovault/sugardb/internal/memberlist/delegate.go:110.3,115.17 3 0 +github.com/echovault/sugardb/internal/memberlist/delegate.go:115.17,118.4 2 0 +github.com/echovault/sugardb/internal/memberlist/delegate.go:120.3,120.67 1 0 +github.com/echovault/sugardb/internal/memberlist/delegate.go:120.67,122.4 1 0 +github.com/echovault/sugardb/internal/memberlist/delegate.go:127.71,129.2 1 0 +github.com/echovault/sugardb/internal/memberlist/delegate.go:132.56,135.2 1 0 +github.com/echovault/sugardb/internal/memberlist/delegate.go:138.68,140.2 0 0 +github.com/echovault/sugardb/internal/memberlist/event_delegate.go:33.62,37.2 1 0 +github.com/echovault/sugardb/internal/memberlist/event_delegate.go:40.71,42.2 1 0 +github.com/echovault/sugardb/internal/memberlist/event_delegate.go:45.72,52.16 4 0 +github.com/echovault/sugardb/internal/memberlist/event_delegate.go:52.16,55.3 2 0 +github.com/echovault/sugardb/internal/memberlist/event_delegate.go:57.2,59.16 2 0 +github.com/echovault/sugardb/internal/memberlist/event_delegate.go:59.16,61.3 1 0 +github.com/echovault/sugardb/internal/memberlist/event_delegate.go:65.74,67.2 0 0 +github.com/echovault/sugardb/internal/memberlist/memberlist.go:56.43,63.2 1 0 +github.com/echovault/sugardb/internal/memberlist/memberlist.go:65.58,80.26 7 0 +github.com/echovault/sugardb/internal/memberlist/memberlist.go:80.26,84.4 3 0 +github.com/echovault/sugardb/internal/memberlist/memberlist.go:85.26,89.4 3 0 +github.com/echovault/sugardb/internal/memberlist/memberlist.go:93.2,94.41 2 0 +github.com/echovault/sugardb/internal/memberlist/memberlist.go:94.41,99.3 4 0 +github.com/echovault/sugardb/internal/memberlist/memberlist.go:101.2,104.16 3 0 +github.com/echovault/sugardb/internal/memberlist/memberlist.go:104.16,106.3 1 0 +github.com/echovault/sugardb/internal/memberlist/memberlist.go:108.2,108.37 1 0 +github.com/echovault/sugardb/internal/memberlist/memberlist.go:108.37,111.70 2 0 +github.com/echovault/sugardb/internal/memberlist/memberlist.go:111.70,113.18 2 0 +github.com/echovault/sugardb/internal/memberlist/memberlist.go:113.18,115.5 1 0 +github.com/echovault/sugardb/internal/memberlist/memberlist.go:116.4,116.14 1 0 +github.com/echovault/sugardb/internal/memberlist/memberlist.go:119.3,119.17 1 0 +github.com/echovault/sugardb/internal/memberlist/memberlist.go:119.17,121.4 1 0 +github.com/echovault/sugardb/internal/memberlist/memberlist.go:123.3,123.27 1 0 +github.com/echovault/sugardb/internal/memberlist/memberlist.go:127.45,137.2 2 0 +github.com/echovault/sugardb/internal/memberlist/memberlist.go:141.72,154.2 2 0 +github.com/echovault/sugardb/internal/memberlist/memberlist.go:158.75,171.2 2 0 +github.com/echovault/sugardb/internal/memberlist/memberlist.go:173.43,176.16 2 0 +github.com/echovault/sugardb/internal/memberlist/memberlist.go:176.16,179.3 2 0 +github.com/echovault/sugardb/internal/memberlist/memberlist.go:181.2,182.16 2 0 +github.com/echovault/sugardb/internal/memberlist/memberlist.go:182.16,185.3 2 0 +github.com/echovault/sugardb/internal/memberlist/memberlist.go:187.2,187.49 1 0 +github.com/echovault/sugardb/internal/modules/admin/commands.go:27.78,33.29 4 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:33.29,34.54 1 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:34.54,40.42 4 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:40.42,42.5 1 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:44.4,47.12 3 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:50.3,50.36 1 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:50.36,57.43 5 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:57.43,59.5 1 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:61.4,63.21 2 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:67.2,69.25 2 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:72.76,76.35 3 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:76.35,77.65 1 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:77.65,78.41 1 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:78.41,80.5 1 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:81.4,81.12 1 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:83.3,83.13 1 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:86.2,86.51 1 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:89.75,90.29 1 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:91.9,96.36 4 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:96.36,97.66 1 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:97.66,98.52 1 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:98.52,102.6 3 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:103.5,103.13 1 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:105.4,106.14 2 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:108.3,109.26 2 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:111.9,115.56 3 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:115.56,117.4 1 0 +github.com/echovault/sugardb/internal/modules/admin/commands.go:118.3,118.53 1 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:118.53,122.37 3 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:122.37,123.67 1 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:123.67,124.53 1 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:124.53,125.59 1 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:125.59,129.8 3 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:131.6,131.14 1 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:133.5,133.54 1 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:133.54,136.6 2 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:138.9,138.61 1 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:138.61,142.37 3 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:142.37,143.67 1 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:143.67,144.53 1 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:144.53,146.24 2 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:146.24,149.8 2 0 +github.com/echovault/sugardb/internal/modules/admin/commands.go:151.6,151.14 1 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:153.5,153.33 1 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:153.33,156.6 2 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:158.9,158.60 1 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:158.60,162.37 3 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:162.37,163.67 1 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:163.67,164.53 1 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:164.53,165.55 1 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:165.55,169.8 3 0 +github.com/echovault/sugardb/internal/modules/admin/commands.go:171.6,171.14 1 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:173.5,173.50 1 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:173.50,176.6 2 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:178.9,180.4 1 0 +github.com/echovault/sugardb/internal/modules/admin/commands.go:181.3,182.26 2 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:183.10,184.54 1 0 +github.com/echovault/sugardb/internal/modules/admin/commands.go:188.75,190.2 1 0 +github.com/echovault/sugardb/internal/modules/admin/commands.go:192.36,201.84 1 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:201.84,205.5 1 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:215.84,219.5 1 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:227.86,231.7 1 0 +github.com/echovault/sugardb/internal/modules/admin/commands.go:240.86,244.7 1 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:254.86,258.7 1 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:270.84,274.5 1 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:275.73,276.49 1 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:276.49,278.6 1 0 +github.com/echovault/sugardb/internal/modules/admin/commands.go:279.5,279.45 1 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:289.84,293.5 1 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:294.73,296.18 2 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:296.18,298.6 1 0 +github.com/echovault/sugardb/internal/modules/admin/commands.go:299.5,299.53 1 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:309.84,313.5 1 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:314.73,315.47 1 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:315.47,317.6 1 0 +github.com/echovault/sugardb/internal/modules/admin/commands.go:318.5,318.45 1 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:327.84,331.5 1 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:341.86,345.7 1 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:346.75,347.34 1 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:347.34,349.8 1 0 +github.com/echovault/sugardb/internal/modules/admin/commands.go:350.7,351.34 2 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:351.34,353.8 1 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:354.7,354.75 1 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:354.75,356.8 1 0 +github.com/echovault/sugardb/internal/modules/admin/commands.go:357.7,357.47 1 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:367.86,371.7 1 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:372.75,373.35 1 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:373.35,375.8 1 0 +github.com/echovault/sugardb/internal/modules/admin/commands.go:376.7,377.47 2 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:386.86,390.7 1 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:391.75,394.38 3 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:394.38,396.8 1 1 +github.com/echovault/sugardb/internal/modules/admin/commands.go:397.7,397.30 1 1 +github.com/echovault/sugardb/internal/raft/fsm.go:48.36,52.2 1 0 +github.com/echovault/sugardb/internal/raft/fsm.go:55.50,56.18 1 0 +github.com/echovault/sugardb/internal/raft/fsm.go:57.10,57.10 0 0 +github.com/echovault/sugardb/internal/raft/fsm.go:59.23,62.60 2 0 +github.com/echovault/sugardb/internal/raft/fsm.go:62.60,67.4 1 0 +github.com/echovault/sugardb/internal/raft/fsm.go:69.3,74.40 5 0 +github.com/echovault/sugardb/internal/raft/fsm.go:75.11,79.5 1 0 +github.com/echovault/sugardb/internal/raft/fsm.go:81.21,82.66 1 0 +github.com/echovault/sugardb/internal/raft/fsm.go:82.66,87.5 1 0 +github.com/echovault/sugardb/internal/raft/fsm.go:88.4,91.5 1 0 +github.com/echovault/sugardb/internal/raft/fsm.go:93.18,96.18 2 0 +github.com/echovault/sugardb/internal/raft/fsm.go:96.18,101.5 1 0 +github.com/echovault/sugardb/internal/raft/fsm.go:103.4,106.18 3 0 +github.com/echovault/sugardb/internal/raft/fsm.go:106.18,111.5 1 0 +github.com/echovault/sugardb/internal/raft/fsm.go:112.4,113.10 2 0 +github.com/echovault/sugardb/internal/raft/fsm.go:113.10,115.5 1 0 +github.com/echovault/sugardb/internal/raft/fsm.go:117.4,117.96 1 0 +github.com/echovault/sugardb/internal/raft/fsm.go:117.96,122.5 1 0 +github.com/echovault/sugardb/internal/raft/fsm.go:122.10,127.5 1 0 +github.com/echovault/sugardb/internal/raft/fsm.go:131.2,131.12 1 0 +github.com/echovault/sugardb/internal/raft/fsm.go:135.54,143.2 1 0 +github.com/echovault/sugardb/internal/raft/fsm.go:146.55,149.16 2 0 +github.com/echovault/sugardb/internal/raft/fsm.go:149.16,152.3 2 0 +github.com/echovault/sugardb/internal/raft/fsm.go:154.2,159.48 2 0 +github.com/echovault/sugardb/internal/raft/fsm.go:159.48,162.3 2 0 +github.com/echovault/sugardb/internal/raft/fsm.go:165.2,165.81 1 0 +github.com/echovault/sugardb/internal/raft/fsm.go:165.81,167.34 2 0 +github.com/echovault/sugardb/internal/raft/fsm.go:167.34,168.96 1 0 +github.com/echovault/sugardb/internal/raft/fsm.go:168.96,170.5 1 0 +github.com/echovault/sugardb/internal/raft/fsm.go:171.4,171.60 1 0 +github.com/echovault/sugardb/internal/raft/fsm.go:176.2,178.12 2 0 +github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:39.50,43.2 1 0 +github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:46.58,50.16 3 0 +github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:50.16,53.3 2 0 +github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:55.2,62.16 3 0 +github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:62.16,65.3 2 0 +github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:67.2,67.40 1 0 +github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:67.40,70.3 2 0 +github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:72.2,74.12 2 0 +github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:78.30,80.2 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:53.31,57.2 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:59.46,74.24 12 0 +github.com/echovault/sugardb/internal/raft/raft.go:74.24,79.3 3 0 +github.com/echovault/sugardb/internal/raft/raft.go:79.8,81.17 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:81.17,83.4 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:85.3,86.17 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:86.17,88.4 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:90.3,93.17 3 0 +github.com/echovault/sugardb/internal/raft/raft.go:93.17,95.4 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:98.2,100.16 3 0 +github.com/echovault/sugardb/internal/raft/raft.go:100.16,102.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:104.2,112.16 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:112.16,114.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:117.2,137.16 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:137.16,139.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:141.2,141.27 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:141.27,152.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:154.2,154.21 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:157.74,159.2 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:161.36,163.2 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:165.38,167.2 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:169.40,176.2 4 0 +github.com/echovault/sugardb/internal/raft/raft.go:183.9,184.22 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:184.22,186.44 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:186.44,188.4 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:190.3,190.56 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:190.56,192.42 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:192.42,194.5 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:197.3,198.17 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:198.17,200.4 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:203.2,203.12 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:206.61,207.23 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:207.23,209.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:211.2,211.73 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:211.73,213.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:215.2,215.12 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:218.37,220.2 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:222.31,224.22 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:224.22,226.17 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:226.17,229.4 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:230.3,230.49 1 0 github.com/echovault/sugardb/internal/types.go:35.43,40.29 3 1 github.com/echovault/sugardb/internal/types.go:41.11,42.12 1 0 github.com/echovault/sugardb/internal/types.go:44.11,45.34 1 0 @@ -2923,7 +3357,7 @@ github.com/echovault/sugardb/internal/modules/hash/commands.go:364.38,366.17 2 1 github.com/echovault/sugardb/internal/modules/hash/commands.go:366.17,367.47 1 1 github.com/echovault/sugardb/internal/modules/hash/commands.go:367.47,369.13 2 1 github.com/echovault/sugardb/internal/modules/hash/commands.go:371.4,371.48 1 1 -github.com/echovault/sugardb/internal/modules/hash/commands.go:371.48,374.13 3 1 +github.com/echovault/sugardb/internal/modules/hash/commands.go:371.48,374.13 3 0 github.com/echovault/sugardb/internal/modules/hash/commands.go:376.4,376.44 1 1 github.com/echovault/sugardb/internal/modules/hash/commands.go:376.44,378.13 2 1 github.com/echovault/sugardb/internal/modules/hash/commands.go:383.2,383.25 1 1 @@ -3187,481 +3621,47 @@ github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:67.2,67.40 1 0 github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:67.40,70.3 2 0 github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:72.2,74.12 2 0 github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:78.30,80.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:52.31,56.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:58.46,70.24 9 0 -github.com/echovault/sugardb/internal/raft/raft.go:70.24,75.3 3 0 -github.com/echovault/sugardb/internal/raft/raft.go:75.8,77.17 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:77.17,79.4 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:81.3,82.17 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:82.17,84.4 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:86.3,89.17 3 0 -github.com/echovault/sugardb/internal/raft/raft.go:89.17,91.4 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:94.2,96.16 3 0 -github.com/echovault/sugardb/internal/raft/raft.go:96.16,98.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:100.2,108.16 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:108.16,110.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:113.2,133.16 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:133.16,135.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:137.2,137.27 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:137.27,148.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:150.2,150.21 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:153.74,155.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:157.36,159.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:161.38,163.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:165.40,172.2 4 0 -github.com/echovault/sugardb/internal/raft/raft.go:179.9,180.22 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:180.22,182.44 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:182.44,184.4 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:186.3,186.56 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:186.56,188.42 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:188.42,190.5 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:193.3,194.17 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:194.17,196.4 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:199.2,199.12 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:202.61,203.23 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:203.23,205.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:207.2,207.73 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:207.73,209.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:211.2,211.12 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:214.37,216.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:218.31,220.22 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:220.22,222.17 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:222.17,225.4 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:226.3,226.49 1 0 -github.com/echovault/sugardb/internal/types.go:35.43,40.29 3 1 -github.com/echovault/sugardb/internal/types.go:41.11,42.12 1 0 -github.com/echovault/sugardb/internal/types.go:44.11,45.34 1 1 -github.com/echovault/sugardb/internal/types.go:47.22,48.12 1 1 -github.com/echovault/sugardb/internal/types.go:49.14,52.24 2 1 -github.com/echovault/sugardb/internal/types.go:55.16,56.23 1 1 -github.com/echovault/sugardb/internal/types.go:56.23,59.4 2 1 -github.com/echovault/sugardb/internal/types.go:62.31,63.53 1 1 -github.com/echovault/sugardb/internal/types.go:65.10,66.117 1 0 -github.com/echovault/sugardb/internal/types.go:69.2,69.18 1 1 -github.com/echovault/sugardb/internal/utils.go:41.38,45.16 2 1 -github.com/echovault/sugardb/internal/utils.go:45.16,47.3 1 1 -github.com/echovault/sugardb/internal/utils.go:49.2,49.15 1 1 -github.com/echovault/sugardb/internal/utils.go:49.15,52.3 2 1 -github.com/echovault/sugardb/internal/utils.go:54.2,56.10 2 0 -github.com/echovault/sugardb/internal/utils.go:59.43,63.16 3 1 -github.com/echovault/sugardb/internal/utils.go:63.16,65.3 1 1 -github.com/echovault/sugardb/internal/utils.go:67.2,68.42 2 1 -github.com/echovault/sugardb/internal/utils.go:68.42,70.3 1 1 -github.com/echovault/sugardb/internal/utils.go:72.2,72.17 1 1 -github.com/echovault/sugardb/internal/utils.go:75.47,82.6 4 1 -github.com/echovault/sugardb/internal/utils.go:82.6,84.43 2 1 -github.com/echovault/sugardb/internal/utils.go:84.43,85.9 1 1 -github.com/echovault/sugardb/internal/utils.go:87.3,87.17 1 1 -github.com/echovault/sugardb/internal/utils.go:87.17,89.4 1 0 -github.com/echovault/sugardb/internal/utils.go:90.3,91.21 2 1 -github.com/echovault/sugardb/internal/utils.go:91.21,92.9 1 1 -github.com/echovault/sugardb/internal/utils.go:94.3,94.15 1 0 -github.com/echovault/sugardb/internal/utils.go:97.2,97.37 1 1 -github.com/echovault/sugardb/internal/utils.go:100.120,102.20 2 0 -github.com/echovault/sugardb/internal/utils.go:102.20,104.3 1 0 -github.com/echovault/sugardb/internal/utils.go:105.2,105.16 1 0 -github.com/echovault/sugardb/internal/utils.go:105.16,107.3 1 0 -github.com/echovault/sugardb/internal/utils.go:108.2,108.24 1 0 -github.com/echovault/sugardb/internal/utils.go:108.24,110.3 1 0 -github.com/echovault/sugardb/internal/utils.go:111.2,111.21 1 0 -github.com/echovault/sugardb/internal/utils.go:111.21,113.3 1 0 -github.com/echovault/sugardb/internal/utils.go:114.2,114.16 1 0 -github.com/echovault/sugardb/internal/utils.go:117.37,119.16 2 1 -github.com/echovault/sugardb/internal/utils.go:119.16,121.3 1 0 -github.com/echovault/sugardb/internal/utils.go:122.2,122.15 1 1 -github.com/echovault/sugardb/internal/utils.go:122.15,123.37 1 1 -github.com/echovault/sugardb/internal/utils.go:123.37,125.4 1 0 -github.com/echovault/sugardb/internal/utils.go:128.2,130.23 2 1 -github.com/echovault/sugardb/internal/utils.go:133.72,134.65 1 1 -github.com/echovault/sugardb/internal/utils.go:134.65,137.3 1 1 -github.com/echovault/sugardb/internal/utils.go:138.2,138.18 1 1 -github.com/echovault/sugardb/internal/utils.go:138.18,141.3 1 0 -github.com/echovault/sugardb/internal/utils.go:142.2,142.49 1 1 -github.com/echovault/sugardb/internal/utils.go:142.49,143.52 1 1 -github.com/echovault/sugardb/internal/utils.go:143.52,145.4 1 1 -github.com/echovault/sugardb/internal/utils.go:147.2,147.71 1 0 -github.com/echovault/sugardb/internal/utils.go:150.66,152.2 1 1 -github.com/echovault/sugardb/internal/utils.go:154.24,155.11 1 1 -github.com/echovault/sugardb/internal/utils.go:155.11,157.3 1 0 -github.com/echovault/sugardb/internal/utils.go:158.2,158.10 1 1 -github.com/echovault/sugardb/internal/utils.go:162.49,166.16 3 0 -github.com/echovault/sugardb/internal/utils.go:166.16,168.3 1 0 -github.com/echovault/sugardb/internal/utils.go:170.2,171.17 2 0 -github.com/echovault/sugardb/internal/utils.go:172.12,173.19 1 0 -github.com/echovault/sugardb/internal/utils.go:174.12,175.26 1 0 -github.com/echovault/sugardb/internal/utils.go:176.12,177.33 1 0 -github.com/echovault/sugardb/internal/utils.go:178.12,179.40 1 0 -github.com/echovault/sugardb/internal/utils.go:180.12,181.47 1 0 -github.com/echovault/sugardb/internal/utils.go:182.10,183.91 1 0 -github.com/echovault/sugardb/internal/utils.go:186.2,186.30 1 0 -github.com/echovault/sugardb/internal/utils.go:190.64,191.20 1 1 -github.com/echovault/sugardb/internal/utils.go:191.20,193.3 1 1 -github.com/echovault/sugardb/internal/utils.go:196.2,196.33 1 0 -github.com/echovault/sugardb/internal/utils.go:196.33,198.3 1 0 -github.com/echovault/sugardb/internal/utils.go:203.2,206.37 2 0 -github.com/echovault/sugardb/internal/utils.go:210.100,211.36 1 1 -github.com/echovault/sugardb/internal/utils.go:211.36,213.26 2 1 -github.com/echovault/sugardb/internal/utils.go:213.26,215.35 1 1 -github.com/echovault/sugardb/internal/utils.go:215.35,216.13 1 1 -github.com/echovault/sugardb/internal/utils.go:219.4,219.30 1 0 -github.com/echovault/sugardb/internal/utils.go:219.30,221.5 1 0 -github.com/echovault/sugardb/internal/utils.go:223.3,223.36 1 1 -github.com/echovault/sugardb/internal/utils.go:223.36,225.4 1 0 -github.com/echovault/sugardb/internal/utils.go:227.2,227.14 1 1 -github.com/echovault/sugardb/internal/utils.go:232.43,233.14 1 0 -github.com/echovault/sugardb/internal/utils.go:233.14,235.3 1 0 -github.com/echovault/sugardb/internal/utils.go:236.2,236.30 1 0 -github.com/echovault/sugardb/internal/utils.go:236.30,238.3 1 0 -github.com/echovault/sugardb/internal/utils.go:239.2,239.30 1 0 -github.com/echovault/sugardb/internal/utils.go:239.30,241.3 1 0 -github.com/echovault/sugardb/internal/utils.go:243.2,244.21 2 0 -github.com/echovault/sugardb/internal/utils.go:244.21,246.3 1 0 -github.com/echovault/sugardb/internal/utils.go:248.2,249.29 2 0 -github.com/echovault/sugardb/internal/utils.go:249.29,251.13 2 0 -github.com/echovault/sugardb/internal/utils.go:251.13,252.9 1 0 -github.com/echovault/sugardb/internal/utils.go:256.2,256.10 1 0 -github.com/echovault/sugardb/internal/utils.go:259.41,261.28 2 1 -github.com/echovault/sugardb/internal/utils.go:261.28,263.3 1 1 -github.com/echovault/sugardb/internal/utils.go:264.2,264.20 1 1 -github.com/echovault/sugardb/internal/utils.go:267.47,270.16 3 0 -github.com/echovault/sugardb/internal/utils.go:270.16,272.3 1 0 -github.com/echovault/sugardb/internal/utils.go:273.2,273.24 1 0 -github.com/echovault/sugardb/internal/utils.go:276.52,279.16 3 1 -github.com/echovault/sugardb/internal/utils.go:279.16,281.3 1 0 -github.com/echovault/sugardb/internal/utils.go:282.2,282.24 1 1 -github.com/echovault/sugardb/internal/utils.go:285.50,288.16 3 0 -github.com/echovault/sugardb/internal/utils.go:288.16,290.3 1 0 -github.com/echovault/sugardb/internal/utils.go:291.2,291.25 1 0 -github.com/echovault/sugardb/internal/utils.go:294.52,297.16 3 0 -github.com/echovault/sugardb/internal/utils.go:297.16,299.3 1 0 -github.com/echovault/sugardb/internal/utils.go:300.2,300.23 1 0 -github.com/echovault/sugardb/internal/utils.go:303.51,306.16 3 0 -github.com/echovault/sugardb/internal/utils.go:306.16,308.3 1 0 -github.com/echovault/sugardb/internal/utils.go:309.2,309.22 1 0 -github.com/echovault/sugardb/internal/utils.go:312.59,316.16 3 0 -github.com/echovault/sugardb/internal/utils.go:316.16,318.3 1 0 -github.com/echovault/sugardb/internal/utils.go:320.2,320.16 1 0 -github.com/echovault/sugardb/internal/utils.go:320.16,322.3 1 0 -github.com/echovault/sugardb/internal/utils.go:324.2,324.39 1 0 -github.com/echovault/sugardb/internal/utils.go:324.39,326.3 1 0 -github.com/echovault/sugardb/internal/utils.go:328.2,329.30 2 0 -github.com/echovault/sugardb/internal/utils.go:329.30,330.17 1 0 -github.com/echovault/sugardb/internal/utils.go:330.17,332.12 2 0 -github.com/echovault/sugardb/internal/utils.go:334.3,334.22 1 0 -github.com/echovault/sugardb/internal/utils.go:336.2,336.17 1 0 -github.com/echovault/sugardb/internal/utils.go:339.67,342.16 3 0 -github.com/echovault/sugardb/internal/utils.go:342.16,344.3 1 0 -github.com/echovault/sugardb/internal/utils.go:345.2,345.16 1 0 -github.com/echovault/sugardb/internal/utils.go:345.16,347.3 1 0 -github.com/echovault/sugardb/internal/utils.go:348.2,349.31 2 0 -github.com/echovault/sugardb/internal/utils.go:349.31,350.18 1 0 -github.com/echovault/sugardb/internal/utils.go:350.18,352.12 2 0 -github.com/echovault/sugardb/internal/utils.go:354.3,355.33 2 0 -github.com/echovault/sugardb/internal/utils.go:355.33,357.4 1 0 -github.com/echovault/sugardb/internal/utils.go:358.3,358.17 1 0 -github.com/echovault/sugardb/internal/utils.go:360.2,360.17 1 0 -github.com/echovault/sugardb/internal/utils.go:363.57,366.16 3 0 -github.com/echovault/sugardb/internal/utils.go:366.16,368.3 1 0 -github.com/echovault/sugardb/internal/utils.go:369.2,369.16 1 0 -github.com/echovault/sugardb/internal/utils.go:369.16,371.3 1 0 -github.com/echovault/sugardb/internal/utils.go:372.2,373.30 2 0 -github.com/echovault/sugardb/internal/utils.go:373.30,374.17 1 0 -github.com/echovault/sugardb/internal/utils.go:374.17,376.12 2 0 -github.com/echovault/sugardb/internal/utils.go:378.3,378.23 1 0 -github.com/echovault/sugardb/internal/utils.go:380.2,380.17 1 0 -github.com/echovault/sugardb/internal/utils.go:383.58,386.16 3 0 -github.com/echovault/sugardb/internal/utils.go:386.16,388.3 1 0 -github.com/echovault/sugardb/internal/utils.go:389.2,389.16 1 0 -github.com/echovault/sugardb/internal/utils.go:389.16,391.3 1 0 -github.com/echovault/sugardb/internal/utils.go:392.2,393.30 2 0 -github.com/echovault/sugardb/internal/utils.go:393.30,394.17 1 0 -github.com/echovault/sugardb/internal/utils.go:394.17,396.12 2 0 -github.com/echovault/sugardb/internal/utils.go:398.3,398.20 1 0 -github.com/echovault/sugardb/internal/utils.go:400.2,400.17 1 0 -github.com/echovault/sugardb/internal/utils.go:403.70,404.32 1 0 -github.com/echovault/sugardb/internal/utils.go:404.32,405.60 1 0 -github.com/echovault/sugardb/internal/utils.go:405.60,407.4 1 0 -github.com/echovault/sugardb/internal/utils.go:407.6,409.4 1 0 -github.com/echovault/sugardb/internal/utils.go:411.2,411.30 1 0 -github.com/echovault/sugardb/internal/utils.go:411.30,412.62 1 0 -github.com/echovault/sugardb/internal/utils.go:412.62,414.4 1 0 -github.com/echovault/sugardb/internal/utils.go:414.6,416.4 1 0 -github.com/echovault/sugardb/internal/utils.go:418.2,418.13 1 0 -github.com/echovault/sugardb/internal/utils.go:421.33,423.16 2 1 -github.com/echovault/sugardb/internal/utils.go:423.16,425.3 1 0 -github.com/echovault/sugardb/internal/utils.go:427.2,428.16 2 1 -github.com/echovault/sugardb/internal/utils.go:428.16,430.3 1 0 -github.com/echovault/sugardb/internal/utils.go:431.2,431.15 1 1 -github.com/echovault/sugardb/internal/utils.go:431.15,433.3 1 1 -github.com/echovault/sugardb/internal/utils.go:435.2,435.42 1 1 -github.com/echovault/sugardb/internal/utils.go:438.61,443.12 4 1 -github.com/echovault/sugardb/internal/utils.go:443.12,444.7 1 1 -github.com/echovault/sugardb/internal/utils.go:444.7,446.73 2 1 -github.com/echovault/sugardb/internal/utils.go:446.73,448.13 1 0 -github.com/echovault/sugardb/internal/utils.go:450.4,450.9 1 1 -github.com/echovault/sugardb/internal/utils.go:452.3,452.21 1 1 -github.com/echovault/sugardb/internal/utils.go:455.2,456.15 2 1 -github.com/echovault/sugardb/internal/utils.go:456.15,458.3 1 1 -github.com/echovault/sugardb/internal/utils.go:460.2,460.9 1 1 -github.com/echovault/sugardb/internal/utils.go:461.18,462.47 1 0 -github.com/echovault/sugardb/internal/utils.go:463.14,464.19 1 1 -github.com/echovault/sugardb/internal/utils.go:468.84,473.12 4 0 -github.com/echovault/sugardb/internal/utils.go:473.12,474.7 1 0 -github.com/echovault/sugardb/internal/utils.go:474.7,476.73 2 0 -github.com/echovault/sugardb/internal/utils.go:476.73,478.13 1 0 -github.com/echovault/sugardb/internal/utils.go:480.4,480.9 1 0 -github.com/echovault/sugardb/internal/utils.go:482.3,482.21 1 0 -github.com/echovault/sugardb/internal/utils.go:485.2,486.15 2 0 -github.com/echovault/sugardb/internal/utils.go:486.15,488.3 1 0 -github.com/echovault/sugardb/internal/utils.go:490.2,490.9 1 0 -github.com/echovault/sugardb/internal/utils.go:491.18,492.47 1 0 -github.com/echovault/sugardb/internal/utils.go:493.14,494.19 1 0 -github.com/echovault/sugardb/internal/clock/clock.go:14.23,16.43 1 1 -github.com/echovault/sugardb/internal/clock/clock.go:16.43,18.3 1 1 -github.com/echovault/sugardb/internal/clock/clock.go:19.2,19.20 1 0 -github.com/echovault/sugardb/internal/clock/clock.go:24.34,26.2 1 0 -github.com/echovault/sugardb/internal/clock/clock.go:28.58,30.2 1 0 -github.com/echovault/sugardb/internal/clock/clock.go:34.34,37.2 2 1 -github.com/echovault/sugardb/internal/clock/clock.go:39.58,41.2 1 0 -github.com/echovault/sugardb/internal/memberlist/broadcast.go:32.88,35.9 2 0 -github.com/echovault/sugardb/internal/memberlist/broadcast.go:35.9,37.3 1 0 -github.com/echovault/sugardb/internal/memberlist/broadcast.go:39.2,39.33 1 0 -github.com/echovault/sugardb/internal/memberlist/broadcast.go:40.18,42.56 1 0 -github.com/echovault/sugardb/internal/memberlist/broadcast.go:43.20,45.62 1 0 -github.com/echovault/sugardb/internal/memberlist/broadcast.go:46.10,47.15 1 0 -github.com/echovault/sugardb/internal/memberlist/broadcast.go:52.60,55.16 2 0 -github.com/echovault/sugardb/internal/memberlist/broadcast.go:55.16,58.3 2 0 -github.com/echovault/sugardb/internal/memberlist/broadcast.go:60.2,60.12 1 0 -github.com/echovault/sugardb/internal/memberlist/broadcast.go:64.55,66.2 0 0 -github.com/echovault/sugardb/internal/memberlist/delegate.go:42.47,46.2 1 0 -github.com/echovault/sugardb/internal/memberlist/delegate.go:49.54,59.16 3 0 -github.com/echovault/sugardb/internal/memberlist/delegate.go:59.16,61.3 1 0 -github.com/echovault/sugardb/internal/memberlist/delegate.go:63.2,63.10 1 0 -github.com/echovault/sugardb/internal/memberlist/delegate.go:67.54,69.55 2 0 -github.com/echovault/sugardb/internal/memberlist/delegate.go:69.55,72.3 2 0 -github.com/echovault/sugardb/internal/memberlist/delegate.go:74.2,74.20 1 0 -github.com/echovault/sugardb/internal/memberlist/delegate.go:75.18,77.39 1 0 -github.com/echovault/sugardb/internal/memberlist/delegate.go:77.39,80.4 2 0 -github.com/echovault/sugardb/internal/memberlist/delegate.go:81.3,82.17 2 0 -github.com/echovault/sugardb/internal/memberlist/delegate.go:82.17,84.4 1 0 -github.com/echovault/sugardb/internal/memberlist/delegate.go:86.19,88.39 1 0 -github.com/echovault/sugardb/internal/memberlist/delegate.go:88.39,91.4 2 0 -github.com/echovault/sugardb/internal/memberlist/delegate.go:93.3,99.67 3 0 -github.com/echovault/sugardb/internal/memberlist/delegate.go:99.67,101.4 1 0 -github.com/echovault/sugardb/internal/memberlist/delegate.go:103.20,105.39 1 0 -github.com/echovault/sugardb/internal/memberlist/delegate.go:105.39,108.4 2 0 -github.com/echovault/sugardb/internal/memberlist/delegate.go:110.3,115.17 3 0 -github.com/echovault/sugardb/internal/memberlist/delegate.go:115.17,118.4 2 0 -github.com/echovault/sugardb/internal/memberlist/delegate.go:120.3,120.67 1 0 -github.com/echovault/sugardb/internal/memberlist/delegate.go:120.67,122.4 1 0 -github.com/echovault/sugardb/internal/memberlist/delegate.go:127.71,129.2 1 0 -github.com/echovault/sugardb/internal/memberlist/delegate.go:132.56,135.2 1 0 -github.com/echovault/sugardb/internal/memberlist/delegate.go:138.68,140.2 0 0 -github.com/echovault/sugardb/internal/memberlist/event_delegate.go:33.62,37.2 1 0 -github.com/echovault/sugardb/internal/memberlist/event_delegate.go:40.71,42.2 1 0 -github.com/echovault/sugardb/internal/memberlist/event_delegate.go:45.72,52.16 4 0 -github.com/echovault/sugardb/internal/memberlist/event_delegate.go:52.16,55.3 2 0 -github.com/echovault/sugardb/internal/memberlist/event_delegate.go:57.2,59.16 2 0 -github.com/echovault/sugardb/internal/memberlist/event_delegate.go:59.16,61.3 1 0 -github.com/echovault/sugardb/internal/memberlist/event_delegate.go:65.74,67.2 0 0 -github.com/echovault/sugardb/internal/memberlist/memberlist.go:56.43,63.2 1 0 -github.com/echovault/sugardb/internal/memberlist/memberlist.go:65.58,80.26 7 0 -github.com/echovault/sugardb/internal/memberlist/memberlist.go:80.26,84.4 3 0 -github.com/echovault/sugardb/internal/memberlist/memberlist.go:85.26,89.4 3 0 -github.com/echovault/sugardb/internal/memberlist/memberlist.go:93.2,94.41 2 0 -github.com/echovault/sugardb/internal/memberlist/memberlist.go:94.41,99.3 4 0 -github.com/echovault/sugardb/internal/memberlist/memberlist.go:101.2,104.16 3 0 -github.com/echovault/sugardb/internal/memberlist/memberlist.go:104.16,106.3 1 0 -github.com/echovault/sugardb/internal/memberlist/memberlist.go:108.2,108.37 1 0 -github.com/echovault/sugardb/internal/memberlist/memberlist.go:108.37,111.70 2 0 -github.com/echovault/sugardb/internal/memberlist/memberlist.go:111.70,113.18 2 0 -github.com/echovault/sugardb/internal/memberlist/memberlist.go:113.18,115.5 1 0 -github.com/echovault/sugardb/internal/memberlist/memberlist.go:116.4,116.14 1 0 -github.com/echovault/sugardb/internal/memberlist/memberlist.go:119.3,119.17 1 0 -github.com/echovault/sugardb/internal/memberlist/memberlist.go:119.17,121.4 1 0 -github.com/echovault/sugardb/internal/memberlist/memberlist.go:123.3,123.27 1 0 -github.com/echovault/sugardb/internal/memberlist/memberlist.go:127.45,137.2 2 0 -github.com/echovault/sugardb/internal/memberlist/memberlist.go:141.72,154.2 2 0 -github.com/echovault/sugardb/internal/memberlist/memberlist.go:158.75,171.2 2 0 -github.com/echovault/sugardb/internal/memberlist/memberlist.go:173.43,176.16 2 0 -github.com/echovault/sugardb/internal/memberlist/memberlist.go:176.16,179.3 2 0 -github.com/echovault/sugardb/internal/memberlist/memberlist.go:181.2,182.16 2 0 -github.com/echovault/sugardb/internal/memberlist/memberlist.go:182.16,185.3 2 0 -github.com/echovault/sugardb/internal/memberlist/memberlist.go:187.2,187.49 1 0 -github.com/echovault/sugardb/internal/modules/admin/commands.go:27.78,33.29 4 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:33.29,34.54 1 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:34.54,40.42 4 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:40.42,42.5 1 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:44.4,47.12 3 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:50.3,50.36 1 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:50.36,57.43 5 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:57.43,59.5 1 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:61.4,63.21 2 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:67.2,69.25 2 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:72.76,76.35 3 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:76.35,77.65 1 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:77.65,78.41 1 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:78.41,80.5 1 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:81.4,81.12 1 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:83.3,83.13 1 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:86.2,86.51 1 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:89.75,90.29 1 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:91.9,96.36 4 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:96.36,97.66 1 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:97.66,98.52 1 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:98.52,102.6 3 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:103.5,103.13 1 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:105.4,106.14 2 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:108.3,109.26 2 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:111.9,115.56 3 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:115.56,117.4 1 0 -github.com/echovault/sugardb/internal/modules/admin/commands.go:118.3,118.53 1 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:118.53,122.37 3 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:122.37,123.67 1 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:123.67,124.53 1 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:124.53,125.59 1 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:125.59,129.8 3 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:131.6,131.14 1 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:133.5,133.54 1 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:133.54,136.6 2 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:138.9,138.61 1 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:138.61,142.37 3 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:142.37,143.67 1 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:143.67,144.53 1 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:144.53,146.24 2 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:146.24,149.8 2 0 -github.com/echovault/sugardb/internal/modules/admin/commands.go:151.6,151.14 1 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:153.5,153.33 1 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:153.33,156.6 2 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:158.9,158.60 1 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:158.60,162.37 3 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:162.37,163.67 1 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:163.67,164.53 1 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:164.53,165.55 1 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:165.55,169.8 3 0 -github.com/echovault/sugardb/internal/modules/admin/commands.go:171.6,171.14 1 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:173.5,173.50 1 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:173.50,176.6 2 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:178.9,180.4 1 0 -github.com/echovault/sugardb/internal/modules/admin/commands.go:181.3,182.26 2 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:183.10,184.54 1 0 -github.com/echovault/sugardb/internal/modules/admin/commands.go:188.75,190.2 1 0 -github.com/echovault/sugardb/internal/modules/admin/commands.go:192.36,201.84 1 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:201.84,205.5 1 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:215.84,219.5 1 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:227.86,231.7 1 0 -github.com/echovault/sugardb/internal/modules/admin/commands.go:240.86,244.7 1 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:254.86,258.7 1 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:270.84,274.5 1 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:275.73,276.49 1 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:276.49,278.6 1 0 -github.com/echovault/sugardb/internal/modules/admin/commands.go:279.5,279.45 1 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:289.84,293.5 1 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:294.73,296.18 2 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:296.18,298.6 1 0 -github.com/echovault/sugardb/internal/modules/admin/commands.go:299.5,299.53 1 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:309.84,313.5 1 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:314.73,315.47 1 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:315.47,317.6 1 0 -github.com/echovault/sugardb/internal/modules/admin/commands.go:318.5,318.45 1 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:327.84,331.5 1 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:341.86,345.7 1 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:346.75,347.34 1 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:347.34,349.8 1 0 -github.com/echovault/sugardb/internal/modules/admin/commands.go:350.7,351.34 2 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:351.34,353.8 1 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:354.7,354.75 1 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:354.75,356.8 1 0 -github.com/echovault/sugardb/internal/modules/admin/commands.go:357.7,357.47 1 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:367.86,371.7 1 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:372.75,373.35 1 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:373.35,375.8 1 0 -github.com/echovault/sugardb/internal/modules/admin/commands.go:376.7,377.47 2 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:386.86,390.7 1 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:391.75,394.38 3 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:394.38,396.8 1 1 -github.com/echovault/sugardb/internal/modules/admin/commands.go:397.7,397.30 1 1 -github.com/echovault/sugardb/internal/raft/fsm.go:48.36,52.2 1 0 -github.com/echovault/sugardb/internal/raft/fsm.go:55.50,56.18 1 0 -github.com/echovault/sugardb/internal/raft/fsm.go:57.10,57.10 0 0 -github.com/echovault/sugardb/internal/raft/fsm.go:59.23,62.60 2 0 -github.com/echovault/sugardb/internal/raft/fsm.go:62.60,67.4 1 0 -github.com/echovault/sugardb/internal/raft/fsm.go:69.3,74.40 5 0 -github.com/echovault/sugardb/internal/raft/fsm.go:75.11,79.5 1 0 -github.com/echovault/sugardb/internal/raft/fsm.go:81.21,82.66 1 0 -github.com/echovault/sugardb/internal/raft/fsm.go:82.66,87.5 1 0 -github.com/echovault/sugardb/internal/raft/fsm.go:88.4,91.5 1 0 -github.com/echovault/sugardb/internal/raft/fsm.go:93.18,96.18 2 0 -github.com/echovault/sugardb/internal/raft/fsm.go:96.18,101.5 1 0 -github.com/echovault/sugardb/internal/raft/fsm.go:103.4,106.18 3 0 -github.com/echovault/sugardb/internal/raft/fsm.go:106.18,111.5 1 0 -github.com/echovault/sugardb/internal/raft/fsm.go:112.4,113.10 2 0 -github.com/echovault/sugardb/internal/raft/fsm.go:113.10,115.5 1 0 -github.com/echovault/sugardb/internal/raft/fsm.go:117.4,117.96 1 0 -github.com/echovault/sugardb/internal/raft/fsm.go:117.96,122.5 1 0 -github.com/echovault/sugardb/internal/raft/fsm.go:122.10,127.5 1 0 -github.com/echovault/sugardb/internal/raft/fsm.go:131.2,131.12 1 0 -github.com/echovault/sugardb/internal/raft/fsm.go:135.54,143.2 1 0 -github.com/echovault/sugardb/internal/raft/fsm.go:146.55,149.16 2 0 -github.com/echovault/sugardb/internal/raft/fsm.go:149.16,152.3 2 0 -github.com/echovault/sugardb/internal/raft/fsm.go:154.2,159.48 2 0 -github.com/echovault/sugardb/internal/raft/fsm.go:159.48,162.3 2 0 -github.com/echovault/sugardb/internal/raft/fsm.go:165.2,165.81 1 0 -github.com/echovault/sugardb/internal/raft/fsm.go:165.81,167.34 2 0 -github.com/echovault/sugardb/internal/raft/fsm.go:167.34,168.96 1 0 -github.com/echovault/sugardb/internal/raft/fsm.go:168.96,170.5 1 0 -github.com/echovault/sugardb/internal/raft/fsm.go:171.4,171.60 1 0 -github.com/echovault/sugardb/internal/raft/fsm.go:176.2,178.12 2 0 -github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:39.50,43.2 1 0 -github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:46.58,50.16 3 0 -github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:50.16,53.3 2 0 -github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:55.2,62.16 3 0 -github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:62.16,65.3 2 0 -github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:67.2,67.40 1 0 -github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:67.40,70.3 2 0 -github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:72.2,74.12 2 0 -github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:78.30,80.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:52.31,56.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:58.46,70.24 9 0 -github.com/echovault/sugardb/internal/raft/raft.go:70.24,75.3 3 0 -github.com/echovault/sugardb/internal/raft/raft.go:75.8,77.17 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:77.17,79.4 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:81.3,82.17 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:82.17,84.4 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:86.3,89.17 3 0 -github.com/echovault/sugardb/internal/raft/raft.go:89.17,91.4 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:94.2,96.16 3 0 -github.com/echovault/sugardb/internal/raft/raft.go:96.16,98.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:100.2,108.16 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:108.16,110.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:113.2,133.16 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:133.16,135.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:137.2,137.27 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:137.27,148.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:150.2,150.21 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:153.74,155.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:157.36,159.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:161.38,163.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:165.40,172.2 4 0 -github.com/echovault/sugardb/internal/raft/raft.go:179.9,180.22 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:180.22,182.44 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:182.44,184.4 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:186.3,186.56 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:186.56,188.42 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:188.42,190.5 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:193.3,194.17 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:194.17,196.4 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:199.2,199.12 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:202.61,203.23 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:203.23,205.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:207.2,207.73 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:207.73,209.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:211.2,211.12 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:214.37,216.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:218.31,220.22 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:220.22,222.17 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:222.17,225.4 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:226.3,226.49 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:53.31,57.2 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:59.46,74.24 12 0 +github.com/echovault/sugardb/internal/raft/raft.go:74.24,79.3 3 0 +github.com/echovault/sugardb/internal/raft/raft.go:79.8,81.17 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:81.17,83.4 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:85.3,86.17 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:86.17,88.4 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:90.3,93.17 3 0 +github.com/echovault/sugardb/internal/raft/raft.go:93.17,95.4 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:98.2,100.16 3 0 +github.com/echovault/sugardb/internal/raft/raft.go:100.16,102.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:104.2,112.16 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:112.16,114.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:117.2,137.16 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:137.16,139.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:141.2,141.27 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:141.27,152.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:154.2,154.21 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:157.74,159.2 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:161.36,163.2 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:165.38,167.2 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:169.40,176.2 4 0 +github.com/echovault/sugardb/internal/raft/raft.go:183.9,184.22 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:184.22,186.44 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:186.44,188.4 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:190.3,190.56 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:190.56,192.42 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:192.42,194.5 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:197.3,198.17 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:198.17,200.4 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:203.2,203.12 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:206.61,207.23 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:207.23,209.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:211.2,211.73 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:211.73,213.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:215.2,215.12 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:218.37,220.2 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:222.31,224.22 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:224.22,226.17 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:226.17,229.4 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:230.3,230.49 1 0 github.com/echovault/sugardb/internal/types.go:35.43,40.29 3 1 github.com/echovault/sugardb/internal/types.go:41.11,42.12 1 0 github.com/echovault/sugardb/internal/types.go:44.11,45.34 1 0 @@ -4171,47 +4171,47 @@ github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:67.2,67.40 1 0 github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:67.40,70.3 2 0 github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:72.2,74.12 2 0 github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:78.30,80.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:52.31,56.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:58.46,70.24 9 0 -github.com/echovault/sugardb/internal/raft/raft.go:70.24,75.3 3 0 -github.com/echovault/sugardb/internal/raft/raft.go:75.8,77.17 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:77.17,79.4 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:81.3,82.17 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:82.17,84.4 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:86.3,89.17 3 0 -github.com/echovault/sugardb/internal/raft/raft.go:89.17,91.4 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:94.2,96.16 3 0 -github.com/echovault/sugardb/internal/raft/raft.go:96.16,98.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:100.2,108.16 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:108.16,110.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:113.2,133.16 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:133.16,135.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:137.2,137.27 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:137.27,148.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:150.2,150.21 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:153.74,155.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:157.36,159.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:161.38,163.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:165.40,172.2 4 0 -github.com/echovault/sugardb/internal/raft/raft.go:179.9,180.22 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:180.22,182.44 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:182.44,184.4 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:186.3,186.56 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:186.56,188.42 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:188.42,190.5 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:193.3,194.17 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:194.17,196.4 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:199.2,199.12 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:202.61,203.23 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:203.23,205.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:207.2,207.73 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:207.73,209.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:211.2,211.12 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:214.37,216.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:218.31,220.22 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:220.22,222.17 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:222.17,225.4 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:226.3,226.49 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:53.31,57.2 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:59.46,74.24 12 0 +github.com/echovault/sugardb/internal/raft/raft.go:74.24,79.3 3 0 +github.com/echovault/sugardb/internal/raft/raft.go:79.8,81.17 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:81.17,83.4 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:85.3,86.17 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:86.17,88.4 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:90.3,93.17 3 0 +github.com/echovault/sugardb/internal/raft/raft.go:93.17,95.4 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:98.2,100.16 3 0 +github.com/echovault/sugardb/internal/raft/raft.go:100.16,102.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:104.2,112.16 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:112.16,114.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:117.2,137.16 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:137.16,139.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:141.2,141.27 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:141.27,152.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:154.2,154.21 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:157.74,159.2 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:161.36,163.2 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:165.38,167.2 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:169.40,176.2 4 0 +github.com/echovault/sugardb/internal/raft/raft.go:183.9,184.22 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:184.22,186.44 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:186.44,188.4 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:190.3,190.56 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:190.56,192.42 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:192.42,194.5 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:197.3,198.17 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:198.17,200.4 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:203.2,203.12 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:206.61,207.23 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:207.23,209.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:211.2,211.73 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:211.73,213.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:215.2,215.12 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:218.37,220.2 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:222.31,224.22 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:224.22,226.17 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:226.17,229.4 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:230.3,230.49 1 0 github.com/echovault/sugardb/internal/types.go:35.43,40.29 3 0 github.com/echovault/sugardb/internal/types.go:41.11,42.12 1 0 github.com/echovault/sugardb/internal/types.go:44.11,45.34 1 0 @@ -4381,7 +4381,7 @@ github.com/echovault/sugardb/internal/utils.go:435.2,435.42 1 1 github.com/echovault/sugardb/internal/utils.go:438.61,443.12 4 1 github.com/echovault/sugardb/internal/utils.go:443.12,444.7 1 1 github.com/echovault/sugardb/internal/utils.go:444.7,446.73 2 1 -github.com/echovault/sugardb/internal/utils.go:446.73,448.13 1 1 +github.com/echovault/sugardb/internal/utils.go:446.73,448.13 1 0 github.com/echovault/sugardb/internal/utils.go:450.4,450.9 1 1 github.com/echovault/sugardb/internal/utils.go:452.3,452.21 1 1 github.com/echovault/sugardb/internal/utils.go:455.2,456.15 2 1 @@ -4472,157 +4472,178 @@ github.com/echovault/sugardb/internal/memberlist/memberlist.go:176.16,179.3 2 0 github.com/echovault/sugardb/internal/memberlist/memberlist.go:181.2,182.16 2 0 github.com/echovault/sugardb/internal/memberlist/memberlist.go:182.16,185.3 2 0 github.com/echovault/sugardb/internal/memberlist/memberlist.go:187.2,187.49 1 0 -github.com/echovault/sugardb/internal/modules/pubsub/channel.go:34.51,35.32 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/channel.go:35.32,37.3 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/channel.go:41.57,42.32 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/channel.go:42.32,45.3 2 1 -github.com/echovault/sugardb/internal/modules/pubsub/channel.go:48.61,59.33 3 1 -github.com/echovault/sugardb/internal/modules/pubsub/channel.go:59.33,61.3 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/channel.go:63.2,63.16 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/channel.go:66.28,67.12 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/channel.go:67.12,68.7 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/channel.go:68.7,73.40 3 1 -github.com/echovault/sugardb/internal/modules/pubsub/channel.go:73.40,74.30 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/channel.go:74.30,79.21 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/channel.go:79.21,81.7 1 0 -github.com/echovault/sugardb/internal/modules/pubsub/channel.go:85.4,85.33 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/channel.go:90.34,92.2 1 0 -github.com/echovault/sugardb/internal/modules/pubsub/channel.go:94.40,96.2 1 0 -github.com/echovault/sugardb/internal/modules/pubsub/channel.go:98.51,101.40 3 1 -github.com/echovault/sugardb/internal/modules/pubsub/channel.go:101.40,103.3 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/channel.go:104.2,105.11 2 1 -github.com/echovault/sugardb/internal/modules/pubsub/channel.go:108.53,111.40 3 1 -github.com/echovault/sugardb/internal/modules/pubsub/channel.go:111.40,113.3 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/channel.go:114.2,115.13 2 1 -github.com/echovault/sugardb/internal/modules/pubsub/channel.go:118.44,120.2 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/channel.go:122.36,129.2 4 1 -github.com/echovault/sugardb/internal/modules/pubsub/channel.go:131.34,138.2 4 1 -github.com/echovault/sugardb/internal/modules/pubsub/channel.go:140.59,145.35 4 0 -github.com/echovault/sugardb/internal/modules/pubsub/channel.go:145.35,147.3 1 0 -github.com/echovault/sugardb/internal/modules/pubsub/channel.go:149.2,149.20 1 0 +github.com/echovault/sugardb/internal/modules/pubsub/channel.go:45.51,46.32 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/channel.go:46.32,48.3 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/channel.go:52.57,53.32 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/channel.go:53.32,56.3 2 1 +github.com/echovault/sugardb/internal/modules/pubsub/channel.go:59.82,75.33 3 1 +github.com/echovault/sugardb/internal/modules/pubsub/channel.go:75.33,77.3 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/channel.go:79.2,79.12 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/channel.go:79.12,80.7 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/channel.go:80.7,81.11 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/channel.go:82.22,84.11 2 0 +github.com/echovault/sugardb/internal/modules/pubsub/channel.go:85.12,87.36 2 1 +github.com/echovault/sugardb/internal/modules/pubsub/channel.go:87.36,89.6 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/channel.go:91.5,99.15 6 1 +github.com/echovault/sugardb/internal/modules/pubsub/channel.go:99.15,104.45 5 1 +github.com/echovault/sugardb/internal/modules/pubsub/channel.go:104.45,106.31 2 0 +github.com/echovault/sugardb/internal/modules/pubsub/channel.go:106.31,109.8 2 0 +github.com/echovault/sugardb/internal/modules/pubsub/channel.go:111.6,113.15 3 1 +github.com/echovault/sugardb/internal/modules/pubsub/channel.go:117.5,117.15 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/channel.go:117.15,120.43 3 1 +github.com/echovault/sugardb/internal/modules/pubsub/channel.go:120.43,122.32 2 1 +github.com/echovault/sugardb/internal/modules/pubsub/channel.go:122.32,129.8 2 1 +github.com/echovault/sugardb/internal/modules/pubsub/channel.go:131.6,133.15 3 1 +github.com/echovault/sugardb/internal/modules/pubsub/channel.go:136.5,136.14 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/channel.go:141.2,141.16 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/channel.go:144.34,146.2 1 0 +github.com/echovault/sugardb/internal/modules/pubsub/channel.go:148.40,150.2 1 0 +github.com/echovault/sugardb/internal/modules/pubsub/channel.go:152.67,153.20 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/channel.go:154.17,158.37 4 1 +github.com/echovault/sugardb/internal/modules/pubsub/channel.go:158.37,160.4 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/channel.go:161.3,167.5 2 1 +github.com/echovault/sugardb/internal/modules/pubsub/channel.go:169.20,173.75 4 0 +github.com/echovault/sugardb/internal/modules/pubsub/channel.go:173.75,175.4 1 0 +github.com/echovault/sugardb/internal/modules/pubsub/channel.go:175.6,177.4 1 0 +github.com/echovault/sugardb/internal/modules/pubsub/channel.go:179.3,181.22 3 0 +github.com/echovault/sugardb/internal/modules/pubsub/channel.go:185.46,186.20 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/channel.go:187.10,188.15 1 0 +github.com/echovault/sugardb/internal/modules/pubsub/channel.go:190.17,194.37 4 1 +github.com/echovault/sugardb/internal/modules/pubsub/channel.go:194.37,196.4 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/channel.go:197.3,198.14 2 1 +github.com/echovault/sugardb/internal/modules/pubsub/channel.go:200.20,205.87 5 0 +github.com/echovault/sugardb/internal/modules/pubsub/channel.go:205.87,208.4 2 0 +github.com/echovault/sugardb/internal/modules/pubsub/channel.go:209.3,209.17 1 0 +github.com/echovault/sugardb/internal/modules/pubsub/channel.go:213.44,218.2 4 1 +github.com/echovault/sugardb/internal/modules/pubsub/channel.go:220.36,228.2 5 1 +github.com/echovault/sugardb/internal/modules/pubsub/channel.go:230.34,238.2 5 1 github.com/echovault/sugardb/internal/modules/pubsub/commands.go:25.73,27.9 2 1 github.com/echovault/sugardb/internal/modules/pubsub/commands.go:27.9,29.3 1 0 github.com/echovault/sugardb/internal/modules/pubsub/commands.go:31.2,33.24 2 1 github.com/echovault/sugardb/internal/modules/pubsub/commands.go:33.24,35.3 1 0 -github.com/echovault/sugardb/internal/modules/pubsub/commands.go:37.2,40.17 3 1 -github.com/echovault/sugardb/internal/modules/pubsub/commands.go:43.75,45.9 2 1 -github.com/echovault/sugardb/internal/modules/pubsub/commands.go:45.9,47.3 1 0 -github.com/echovault/sugardb/internal/modules/pubsub/commands.go:49.2,53.90 3 1 -github.com/echovault/sugardb/internal/modules/pubsub/commands.go:56.71,58.9 2 1 -github.com/echovault/sugardb/internal/modules/pubsub/commands.go:58.9,60.3 1 0 -github.com/echovault/sugardb/internal/modules/pubsub/commands.go:61.2,61.30 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/commands.go:61.30,63.3 1 0 -github.com/echovault/sugardb/internal/modules/pubsub/commands.go:64.2,65.42 2 1 -github.com/echovault/sugardb/internal/modules/pubsub/commands.go:68.78,69.29 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/commands.go:69.29,71.3 1 0 -github.com/echovault/sugardb/internal/modules/pubsub/commands.go:73.2,74.9 2 1 -github.com/echovault/sugardb/internal/modules/pubsub/commands.go:74.9,76.3 1 0 -github.com/echovault/sugardb/internal/modules/pubsub/commands.go:78.2,79.30 2 1 -github.com/echovault/sugardb/internal/modules/pubsub/commands.go:79.30,81.3 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/commands.go:83.2,83.38 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/commands.go:86.76,88.9 2 1 -github.com/echovault/sugardb/internal/modules/pubsub/commands.go:88.9,90.3 1 0 -github.com/echovault/sugardb/internal/modules/pubsub/commands.go:91.2,92.49 2 1 -github.com/echovault/sugardb/internal/modules/pubsub/commands.go:95.77,97.9 2 1 -github.com/echovault/sugardb/internal/modules/pubsub/commands.go:97.9,99.3 1 0 -github.com/echovault/sugardb/internal/modules/pubsub/commands.go:100.2,100.47 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/commands.go:103.36,112.84 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/commands.go:112.84,114.21 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/commands.go:114.21,116.6 1 0 -github.com/echovault/sugardb/internal/modules/pubsub/commands.go:117.5,121.11 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/commands.go:132.84,134.21 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/commands.go:134.21,136.6 1 0 -github.com/echovault/sugardb/internal/modules/pubsub/commands.go:137.5,141.11 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/commands.go:152.84,154.22 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/commands.go:154.22,156.6 1 0 -github.com/echovault/sugardb/internal/modules/pubsub/commands.go:157.5,161.11 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/commands.go:174.84,181.5 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/commands.go:193.84,199.5 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/commands.go:209.84,215.5 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/commands.go:216.68,218.5 1 0 -github.com/echovault/sugardb/internal/modules/pubsub/commands.go:228.86,234.7 1 0 -github.com/echovault/sugardb/internal/modules/pubsub/commands.go:243.86,249.7 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/commands.go:259.86,265.7 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:34.26,39.2 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:41.101,48.17 5 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:48.17,50.3 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:52.2,52.37 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:52.37,56.75 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:56.75,58.4 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:60.3,60.23 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:60.23,63.19 2 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:63.19,65.5 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:65.10,67.5 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:68.4,69.31 2 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:69.31,74.20 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:74.20,76.6 1 0 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:77.5,77.47 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:79.9,81.47 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:81.47,86.20 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:86.20,88.6 1 0 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:94.110,99.17 4 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:99.17,101.3 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:103.2,106.24 3 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:106.24,107.19 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:107.19,110.40 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:110.40,111.31 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:111.31,112.14 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:114.5,114.34 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:114.34,117.6 2 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:119.9,122.40 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:122.40,123.31 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:123.31,124.14 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:126.5,126.34 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:126.34,129.6 2 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:137.2,137.38 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:137.38,138.30 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:138.30,139.54 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:139.54,142.5 2 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:148.2,148.17 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:148.17,149.36 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:149.36,151.40 2 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:151.40,153.58 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:153.58,154.35 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:154.35,157.7 2 0 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:158.6,158.14 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:161.5,161.30 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:161.30,162.35 1 0 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:162.35,165.7 2 0 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:171.2,172.39 2 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:172.39,174.3 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:176.2,176.20 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:179.82,183.38 3 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:183.38,185.29 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:185.29,186.35 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:186.35,188.5 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:189.4,189.12 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:192.3,192.41 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:192.41,194.4 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:198.51,205.19 5 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:205.19,206.39 1 0 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:206.39,207.26 1 0 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:207.26,210.5 2 0 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:212.3,213.21 2 0 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:216.2,218.38 2 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:218.38,220.78 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:220.78,223.12 3 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:226.3,226.50 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:226.50,229.4 2 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:232.2,232.53 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:235.32,240.38 4 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:240.38,241.51 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:241.51,243.4 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:245.2,245.14 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:248.52,253.35 4 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:253.35,255.66 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:255.66,257.4 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:258.3,258.20 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:258.20,260.12 2 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:262.3,262.106 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:264.2,264.20 1 1 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:267.47,272.38 4 0 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:272.38,274.3 1 0 -github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:276.2,276.17 1 0 +github.com/echovault/sugardb/internal/modules/pubsub/commands.go:37.2,41.17 3 1 +github.com/echovault/sugardb/internal/modules/pubsub/commands.go:44.75,46.9 2 1 +github.com/echovault/sugardb/internal/modules/pubsub/commands.go:46.9,48.3 1 0 +github.com/echovault/sugardb/internal/modules/pubsub/commands.go:50.2,54.74 3 1 +github.com/echovault/sugardb/internal/modules/pubsub/commands.go:57.71,59.9 2 1 +github.com/echovault/sugardb/internal/modules/pubsub/commands.go:59.9,61.3 1 0 +github.com/echovault/sugardb/internal/modules/pubsub/commands.go:62.2,62.30 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/commands.go:62.30,64.3 1 0 +github.com/echovault/sugardb/internal/modules/pubsub/commands.go:65.2,66.42 2 1 +github.com/echovault/sugardb/internal/modules/pubsub/commands.go:69.78,70.29 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/commands.go:70.29,72.3 1 0 +github.com/echovault/sugardb/internal/modules/pubsub/commands.go:74.2,75.9 2 1 +github.com/echovault/sugardb/internal/modules/pubsub/commands.go:75.9,77.3 1 0 +github.com/echovault/sugardb/internal/modules/pubsub/commands.go:79.2,80.30 2 1 +github.com/echovault/sugardb/internal/modules/pubsub/commands.go:80.30,82.3 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/commands.go:84.2,84.38 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/commands.go:87.76,89.9 2 1 +github.com/echovault/sugardb/internal/modules/pubsub/commands.go:89.9,91.3 1 0 +github.com/echovault/sugardb/internal/modules/pubsub/commands.go:92.2,93.49 2 1 +github.com/echovault/sugardb/internal/modules/pubsub/commands.go:96.77,98.9 2 1 +github.com/echovault/sugardb/internal/modules/pubsub/commands.go:98.9,100.3 1 0 +github.com/echovault/sugardb/internal/modules/pubsub/commands.go:101.2,101.47 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/commands.go:104.36,113.84 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/commands.go:113.84,115.21 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/commands.go:115.21,117.6 1 0 +github.com/echovault/sugardb/internal/modules/pubsub/commands.go:118.5,122.11 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/commands.go:133.84,135.21 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/commands.go:135.21,137.6 1 0 +github.com/echovault/sugardb/internal/modules/pubsub/commands.go:138.5,142.11 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/commands.go:153.84,155.22 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/commands.go:155.22,157.6 1 0 +github.com/echovault/sugardb/internal/modules/pubsub/commands.go:158.5,162.11 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/commands.go:175.84,182.5 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/commands.go:194.84,200.5 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/commands.go:210.84,216.5 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/commands.go:217.68,219.5 1 0 +github.com/echovault/sugardb/internal/modules/pubsub/commands.go:229.86,235.7 1 0 +github.com/echovault/sugardb/internal/modules/pubsub/commands.go:244.86,250.7 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/commands.go:260.86,266.7 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:33.45,39.2 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:41.75,46.17 4 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:46.17,48.3 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:50.2,50.37 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:50.37,54.75 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:54.75,56.4 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:58.3,58.23 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:58.23,61.19 2 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:61.19,63.5 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:63.10,65.5 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:67.4,70.22 2 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:71.19,74.39 2 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:76.22,79.36 2 0 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:81.9,83.22 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:84.19,87.55 2 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:89.22,92.52 2 0 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:98.84,103.17 4 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:103.17,105.3 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:107.2,110.24 3 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:110.24,111.19 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:111.19,114.40 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:114.40,115.31 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:115.31,116.14 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:118.5,118.33 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:118.33,121.6 2 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:123.9,126.40 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:126.40,127.31 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:127.31,128.14 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:130.5,130.33 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:130.33,133.6 2 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:141.2,141.38 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:141.38,142.30 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:142.30,143.53 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:143.53,146.5 2 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:152.2,152.17 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:152.17,153.36 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:153.36,155.40 2 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:155.40,157.58 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:157.58,158.34 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:158.34,161.7 2 0 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:162.6,162.14 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:165.5,165.30 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:165.30,166.34 1 0 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:166.34,169.7 2 0 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:175.2,176.39 2 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:176.39,178.3 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:180.2,180.20 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:183.63,187.38 3 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:187.38,189.29 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:189.29,190.35 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:190.35,192.5 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:193.4,193.12 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:196.3,196.41 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:196.41,198.4 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:202.51,209.19 5 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:209.19,210.39 1 0 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:210.39,211.26 1 0 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:211.26,214.5 2 0 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:216.3,217.21 2 0 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:220.2,222.38 2 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:222.38,224.78 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:224.78,227.12 3 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:230.3,230.50 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:230.50,233.4 2 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:236.2,236.53 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:239.32,244.38 4 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:244.38,245.51 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:245.51,247.4 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:249.2,249.14 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:252.52,257.35 4 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:257.35,259.66 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:259.66,261.4 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:262.3,262.20 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:262.20,264.12 2 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:266.3,266.106 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:268.2,268.20 1 1 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:271.47,276.38 4 0 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:276.38,278.3 1 0 +github.com/echovault/sugardb/internal/modules/pubsub/pubsub.go:280.2,280.17 1 0 +github.com/echovault/sugardb/internal/modules/pubsub/sub.go:30.36,38.2 4 0 +github.com/echovault/sugardb/internal/modules/pubsub/sub.go:40.54,44.16 4 0 +github.com/echovault/sugardb/internal/modules/pubsub/sub.go:44.16,46.3 1 0 +github.com/echovault/sugardb/internal/modules/pubsub/sub.go:47.2,48.15 2 0 +github.com/echovault/sugardb/internal/modules/pubsub/sub.go:51.53,59.2 5 0 github.com/echovault/sugardb/internal/raft/fsm.go:48.36,52.2 1 0 github.com/echovault/sugardb/internal/raft/fsm.go:55.50,56.18 1 0 github.com/echovault/sugardb/internal/raft/fsm.go:57.10,57.10 0 0 @@ -4663,47 +4684,47 @@ github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:67.2,67.40 1 0 github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:67.40,70.3 2 0 github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:72.2,74.12 2 0 github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:78.30,80.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:52.31,56.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:58.46,70.24 9 0 -github.com/echovault/sugardb/internal/raft/raft.go:70.24,75.3 3 0 -github.com/echovault/sugardb/internal/raft/raft.go:75.8,77.17 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:77.17,79.4 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:81.3,82.17 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:82.17,84.4 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:86.3,89.17 3 0 -github.com/echovault/sugardb/internal/raft/raft.go:89.17,91.4 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:94.2,96.16 3 0 -github.com/echovault/sugardb/internal/raft/raft.go:96.16,98.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:100.2,108.16 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:108.16,110.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:113.2,133.16 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:133.16,135.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:137.2,137.27 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:137.27,148.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:150.2,150.21 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:153.74,155.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:157.36,159.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:161.38,163.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:165.40,172.2 4 0 -github.com/echovault/sugardb/internal/raft/raft.go:179.9,180.22 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:180.22,182.44 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:182.44,184.4 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:186.3,186.56 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:186.56,188.42 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:188.42,190.5 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:193.3,194.17 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:194.17,196.4 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:199.2,199.12 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:202.61,203.23 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:203.23,205.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:207.2,207.73 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:207.73,209.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:211.2,211.12 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:214.37,216.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:218.31,220.22 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:220.22,222.17 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:222.17,225.4 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:226.3,226.49 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:53.31,57.2 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:59.46,74.24 12 0 +github.com/echovault/sugardb/internal/raft/raft.go:74.24,79.3 3 0 +github.com/echovault/sugardb/internal/raft/raft.go:79.8,81.17 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:81.17,83.4 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:85.3,86.17 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:86.17,88.4 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:90.3,93.17 3 0 +github.com/echovault/sugardb/internal/raft/raft.go:93.17,95.4 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:98.2,100.16 3 0 +github.com/echovault/sugardb/internal/raft/raft.go:100.16,102.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:104.2,112.16 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:112.16,114.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:117.2,137.16 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:137.16,139.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:141.2,141.27 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:141.27,152.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:154.2,154.21 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:157.74,159.2 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:161.36,163.2 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:165.38,167.2 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:169.40,176.2 4 0 +github.com/echovault/sugardb/internal/raft/raft.go:183.9,184.22 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:184.22,186.44 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:186.44,188.4 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:190.3,190.56 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:190.56,192.42 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:192.42,194.5 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:197.3,198.17 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:198.17,200.4 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:203.2,203.12 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:206.61,207.23 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:207.23,209.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:211.2,211.73 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:211.73,213.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:215.2,215.12 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:218.37,220.2 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:222.31,224.22 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:224.22,226.17 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:226.17,229.4 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:230.3,230.49 1 0 github.com/echovault/sugardb/internal/types.go:35.43,40.29 3 1 github.com/echovault/sugardb/internal/types.go:41.11,42.12 1 0 github.com/echovault/sugardb/internal/types.go:44.11,45.34 1 1 @@ -5011,7 +5032,7 @@ github.com/echovault/sugardb/internal/modules/set/commands.go:159.70,161.16 2 1 github.com/echovault/sugardb/internal/modules/set/commands.go:161.16,163.3 1 0 github.com/echovault/sugardb/internal/modules/set/commands.go:165.2,169.37 3 1 github.com/echovault/sugardb/internal/modules/set/commands.go:169.37,170.14 1 1 -github.com/echovault/sugardb/internal/modules/set/commands.go:170.14,172.4 1 1 +github.com/echovault/sugardb/internal/modules/set/commands.go:170.14,172.4 1 0 github.com/echovault/sugardb/internal/modules/set/commands.go:173.3,174.10 2 1 github.com/echovault/sugardb/internal/modules/set/commands.go:174.10,177.4 1 1 github.com/echovault/sugardb/internal/modules/set/commands.go:178.3,178.27 1 1 @@ -5035,7 +5056,7 @@ github.com/echovault/sugardb/internal/modules/set/commands.go:221.71,223.4 1 0 github.com/echovault/sugardb/internal/modules/set/commands.go:223.9,225.4 1 1 github.com/echovault/sugardb/internal/modules/set/commands.go:228.2,230.37 2 1 github.com/echovault/sugardb/internal/modules/set/commands.go:230.37,231.14 1 1 -github.com/echovault/sugardb/internal/modules/set/commands.go:231.14,233.4 1 1 +github.com/echovault/sugardb/internal/modules/set/commands.go:231.14,233.4 1 0 github.com/echovault/sugardb/internal/modules/set/commands.go:234.3,235.10 2 1 github.com/echovault/sugardb/internal/modules/set/commands.go:235.10,238.4 1 1 github.com/echovault/sugardb/internal/modules/set/commands.go:239.3,239.27 1 1 @@ -5299,70 +5320,47 @@ github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:67.2,67.40 1 0 github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:67.40,70.3 2 0 github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:72.2,74.12 2 0 github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:78.30,80.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:52.31,56.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:58.46,70.24 9 0 -github.com/echovault/sugardb/internal/raft/raft.go:70.24,75.3 3 0 -github.com/echovault/sugardb/internal/raft/raft.go:75.8,77.17 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:77.17,79.4 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:81.3,82.17 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:82.17,84.4 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:86.3,89.17 3 0 -github.com/echovault/sugardb/internal/raft/raft.go:89.17,91.4 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:94.2,96.16 3 0 -github.com/echovault/sugardb/internal/raft/raft.go:96.16,98.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:100.2,108.16 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:108.16,110.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:113.2,133.16 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:133.16,135.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:137.2,137.27 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:137.27,148.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:150.2,150.21 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:153.74,155.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:157.36,159.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:161.38,163.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:165.40,172.2 4 0 -github.com/echovault/sugardb/internal/raft/raft.go:179.9,180.22 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:180.22,182.44 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:182.44,184.4 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:186.3,186.56 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:186.56,188.42 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:188.42,190.5 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:193.3,194.17 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:194.17,196.4 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:199.2,199.12 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:202.61,203.23 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:203.23,205.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:207.2,207.73 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:207.73,209.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:211.2,211.12 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:214.37,216.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:218.31,220.22 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:220.22,222.17 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:222.17,225.4 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:226.3,226.49 1 0 -github.com/echovault/sugardb/internal/volumes/modules/go/module_set/module_set.go:33.82,34.19 1 0 -github.com/echovault/sugardb/internal/volumes/modules/go/module_set/module_set.go:34.19,36.3 1 0 -github.com/echovault/sugardb/internal/volumes/modules/go/module_set/module_set.go:37.2,37.34 1 0 -github.com/echovault/sugardb/internal/volumes/modules/go/module_set/module_set.go:46.34,49.16 2 0 -github.com/echovault/sugardb/internal/volumes/modules/go/module_set/module_set.go:49.16,51.3 1 0 -github.com/echovault/sugardb/internal/volumes/modules/go/module_set/module_set.go:52.2,55.16 3 0 -github.com/echovault/sugardb/internal/volumes/modules/go/module_set/module_set.go:55.16,57.3 1 0 -github.com/echovault/sugardb/internal/volumes/modules/go/module_set/module_set.go:59.2,60.16 2 0 -github.com/echovault/sugardb/internal/volumes/modules/go/module_set/module_set.go:60.16,62.3 1 0 -github.com/echovault/sugardb/internal/volumes/modules/go/module_set/module_set.go:64.2,64.31 1 0 -github.com/echovault/sugardb/internal/volumes/modules/go/module_get/module_get.go:33.82,34.19 1 0 -github.com/echovault/sugardb/internal/volumes/modules/go/module_get/module_get.go:34.19,36.3 1 0 -github.com/echovault/sugardb/internal/volumes/modules/go/module_get/module_get.go:37.2,37.33 1 0 -github.com/echovault/sugardb/internal/volumes/modules/go/module_get/module_get.go:46.34,49.16 2 0 -github.com/echovault/sugardb/internal/volumes/modules/go/module_get/module_get.go:49.16,51.3 1 0 -github.com/echovault/sugardb/internal/volumes/modules/go/module_get/module_get.go:52.2,55.13 3 0 -github.com/echovault/sugardb/internal/volumes/modules/go/module_get/module_get.go:55.13,57.3 1 0 -github.com/echovault/sugardb/internal/volumes/modules/go/module_get/module_get.go:59.2,60.9 2 0 -github.com/echovault/sugardb/internal/volumes/modules/go/module_get/module_get.go:60.9,62.3 1 0 -github.com/echovault/sugardb/internal/volumes/modules/go/module_get/module_get.go:64.2,65.20 2 0 -github.com/echovault/sugardb/internal/volumes/modules/go/module_get/module_get.go:65.20,67.17 2 0 -github.com/echovault/sugardb/internal/volumes/modules/go/module_get/module_get.go:67.17,69.4 1 0 -github.com/echovault/sugardb/internal/volumes/modules/go/module_get/module_get.go:72.2,72.56 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:53.31,57.2 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:59.46,74.24 12 0 +github.com/echovault/sugardb/internal/raft/raft.go:74.24,79.3 3 0 +github.com/echovault/sugardb/internal/raft/raft.go:79.8,81.17 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:81.17,83.4 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:85.3,86.17 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:86.17,88.4 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:90.3,93.17 3 0 +github.com/echovault/sugardb/internal/raft/raft.go:93.17,95.4 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:98.2,100.16 3 0 +github.com/echovault/sugardb/internal/raft/raft.go:100.16,102.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:104.2,112.16 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:112.16,114.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:117.2,137.16 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:137.16,139.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:141.2,141.27 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:141.27,152.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:154.2,154.21 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:157.74,159.2 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:161.36,163.2 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:165.38,167.2 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:169.40,176.2 4 0 +github.com/echovault/sugardb/internal/raft/raft.go:183.9,184.22 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:184.22,186.44 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:186.44,188.4 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:190.3,190.56 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:190.56,192.42 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:192.42,194.5 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:197.3,198.17 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:198.17,200.4 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:203.2,203.12 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:206.61,207.23 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:207.23,209.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:211.2,211.73 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:211.73,213.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:215.2,215.12 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:218.37,220.2 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:222.31,224.22 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:224.22,226.17 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:226.17,229.4 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:230.3,230.49 1 0 github.com/echovault/sugardb/internal/raft/fsm.go:48.36,52.2 1 0 github.com/echovault/sugardb/internal/raft/fsm.go:55.50,56.18 1 0 github.com/echovault/sugardb/internal/raft/fsm.go:57.10,57.10 0 0 @@ -5403,47 +5401,70 @@ github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:67.2,67.40 1 0 github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:67.40,70.3 2 0 github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:72.2,74.12 2 0 github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:78.30,80.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:52.31,56.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:58.46,70.24 9 0 -github.com/echovault/sugardb/internal/raft/raft.go:70.24,75.3 3 0 -github.com/echovault/sugardb/internal/raft/raft.go:75.8,77.17 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:77.17,79.4 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:81.3,82.17 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:82.17,84.4 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:86.3,89.17 3 0 -github.com/echovault/sugardb/internal/raft/raft.go:89.17,91.4 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:94.2,96.16 3 0 -github.com/echovault/sugardb/internal/raft/raft.go:96.16,98.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:100.2,108.16 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:108.16,110.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:113.2,133.16 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:133.16,135.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:137.2,137.27 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:137.27,148.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:150.2,150.21 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:153.74,155.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:157.36,159.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:161.38,163.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:165.40,172.2 4 0 -github.com/echovault/sugardb/internal/raft/raft.go:179.9,180.22 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:180.22,182.44 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:182.44,184.4 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:186.3,186.56 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:186.56,188.42 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:188.42,190.5 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:193.3,194.17 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:194.17,196.4 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:199.2,199.12 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:202.61,203.23 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:203.23,205.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:207.2,207.73 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:207.73,209.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:211.2,211.12 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:214.37,216.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:218.31,220.22 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:220.22,222.17 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:222.17,225.4 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:226.3,226.49 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:53.31,57.2 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:59.46,74.24 12 0 +github.com/echovault/sugardb/internal/raft/raft.go:74.24,79.3 3 0 +github.com/echovault/sugardb/internal/raft/raft.go:79.8,81.17 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:81.17,83.4 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:85.3,86.17 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:86.17,88.4 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:90.3,93.17 3 0 +github.com/echovault/sugardb/internal/raft/raft.go:93.17,95.4 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:98.2,100.16 3 0 +github.com/echovault/sugardb/internal/raft/raft.go:100.16,102.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:104.2,112.16 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:112.16,114.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:117.2,137.16 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:137.16,139.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:141.2,141.27 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:141.27,152.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:154.2,154.21 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:157.74,159.2 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:161.36,163.2 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:165.38,167.2 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:169.40,176.2 4 0 +github.com/echovault/sugardb/internal/raft/raft.go:183.9,184.22 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:184.22,186.44 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:186.44,188.4 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:190.3,190.56 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:190.56,192.42 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:192.42,194.5 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:197.3,198.17 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:198.17,200.4 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:203.2,203.12 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:206.61,207.23 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:207.23,209.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:211.2,211.73 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:211.73,213.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:215.2,215.12 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:218.37,220.2 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:222.31,224.22 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:224.22,226.17 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:226.17,229.4 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:230.3,230.49 1 0 +github.com/echovault/sugardb/internal/volumes/modules/go/module_set/module_set.go:33.82,34.19 1 0 +github.com/echovault/sugardb/internal/volumes/modules/go/module_set/module_set.go:34.19,36.3 1 0 +github.com/echovault/sugardb/internal/volumes/modules/go/module_set/module_set.go:37.2,37.34 1 0 +github.com/echovault/sugardb/internal/volumes/modules/go/module_set/module_set.go:46.34,49.16 2 0 +github.com/echovault/sugardb/internal/volumes/modules/go/module_set/module_set.go:49.16,51.3 1 0 +github.com/echovault/sugardb/internal/volumes/modules/go/module_set/module_set.go:52.2,55.16 3 0 +github.com/echovault/sugardb/internal/volumes/modules/go/module_set/module_set.go:55.16,57.3 1 0 +github.com/echovault/sugardb/internal/volumes/modules/go/module_set/module_set.go:59.2,60.16 2 0 +github.com/echovault/sugardb/internal/volumes/modules/go/module_set/module_set.go:60.16,62.3 1 0 +github.com/echovault/sugardb/internal/volumes/modules/go/module_set/module_set.go:64.2,64.31 1 0 +github.com/echovault/sugardb/internal/volumes/modules/go/module_get/module_get.go:33.82,34.19 1 0 +github.com/echovault/sugardb/internal/volumes/modules/go/module_get/module_get.go:34.19,36.3 1 0 +github.com/echovault/sugardb/internal/volumes/modules/go/module_get/module_get.go:37.2,37.33 1 0 +github.com/echovault/sugardb/internal/volumes/modules/go/module_get/module_get.go:46.34,49.16 2 0 +github.com/echovault/sugardb/internal/volumes/modules/go/module_get/module_get.go:49.16,51.3 1 0 +github.com/echovault/sugardb/internal/volumes/modules/go/module_get/module_get.go:52.2,55.13 3 0 +github.com/echovault/sugardb/internal/volumes/modules/go/module_get/module_get.go:55.13,57.3 1 0 +github.com/echovault/sugardb/internal/volumes/modules/go/module_get/module_get.go:59.2,60.9 2 0 +github.com/echovault/sugardb/internal/volumes/modules/go/module_get/module_get.go:60.9,62.3 1 0 +github.com/echovault/sugardb/internal/volumes/modules/go/module_get/module_get.go:64.2,65.20 2 0 +github.com/echovault/sugardb/internal/volumes/modules/go/module_get/module_get.go:65.20,67.17 2 0 +github.com/echovault/sugardb/internal/volumes/modules/go/module_get/module_get.go:67.17,69.4 1 0 +github.com/echovault/sugardb/internal/volumes/modules/go/module_get/module_get.go:72.2,72.56 1 0 github.com/echovault/sugardb/internal/types.go:35.43,40.29 3 1 github.com/echovault/sugardb/internal/types.go:41.11,42.12 1 0 github.com/echovault/sugardb/internal/types.go:44.11,45.34 1 1 @@ -5821,353 +5842,47 @@ github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:67.2,67.40 1 0 github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:67.40,70.3 2 0 github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:72.2,74.12 2 0 github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:78.30,80.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:52.31,56.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:58.46,70.24 9 0 -github.com/echovault/sugardb/internal/raft/raft.go:70.24,75.3 3 0 -github.com/echovault/sugardb/internal/raft/raft.go:75.8,77.17 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:77.17,79.4 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:81.3,82.17 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:82.17,84.4 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:86.3,89.17 3 0 -github.com/echovault/sugardb/internal/raft/raft.go:89.17,91.4 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:94.2,96.16 3 0 -github.com/echovault/sugardb/internal/raft/raft.go:96.16,98.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:100.2,108.16 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:108.16,110.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:113.2,133.16 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:133.16,135.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:137.2,137.27 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:137.27,148.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:150.2,150.21 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:153.74,155.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:157.36,159.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:161.38,163.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:165.40,172.2 4 0 -github.com/echovault/sugardb/internal/raft/raft.go:179.9,180.22 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:180.22,182.44 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:182.44,184.4 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:186.3,186.56 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:186.56,188.42 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:188.42,190.5 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:193.3,194.17 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:194.17,196.4 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:199.2,199.12 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:202.61,203.23 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:203.23,205.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:207.2,207.73 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:207.73,209.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:211.2,211.12 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:214.37,216.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:218.31,220.22 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:220.22,222.17 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:222.17,225.4 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:226.3,226.49 1 0 -github.com/echovault/sugardb/internal/types.go:35.43,40.29 3 0 -github.com/echovault/sugardb/internal/types.go:41.11,42.12 1 0 -github.com/echovault/sugardb/internal/types.go:44.11,45.34 1 0 -github.com/echovault/sugardb/internal/types.go:47.22,48.12 1 0 -github.com/echovault/sugardb/internal/types.go:49.14,52.24 2 0 -github.com/echovault/sugardb/internal/types.go:55.16,56.23 1 0 -github.com/echovault/sugardb/internal/types.go:56.23,59.4 2 0 -github.com/echovault/sugardb/internal/types.go:62.31,63.53 1 0 -github.com/echovault/sugardb/internal/types.go:65.10,66.117 1 0 -github.com/echovault/sugardb/internal/types.go:69.2,69.18 1 0 -github.com/echovault/sugardb/internal/utils.go:41.38,45.16 2 0 -github.com/echovault/sugardb/internal/utils.go:45.16,47.3 1 0 -github.com/echovault/sugardb/internal/utils.go:49.2,49.15 1 0 -github.com/echovault/sugardb/internal/utils.go:49.15,52.3 2 0 -github.com/echovault/sugardb/internal/utils.go:54.2,56.10 2 0 -github.com/echovault/sugardb/internal/utils.go:59.43,63.16 3 0 -github.com/echovault/sugardb/internal/utils.go:63.16,65.3 1 0 -github.com/echovault/sugardb/internal/utils.go:67.2,68.42 2 0 -github.com/echovault/sugardb/internal/utils.go:68.42,70.3 1 0 -github.com/echovault/sugardb/internal/utils.go:72.2,72.17 1 0 -github.com/echovault/sugardb/internal/utils.go:75.47,82.6 4 0 -github.com/echovault/sugardb/internal/utils.go:82.6,84.43 2 0 -github.com/echovault/sugardb/internal/utils.go:84.43,85.9 1 0 -github.com/echovault/sugardb/internal/utils.go:87.3,87.17 1 0 -github.com/echovault/sugardb/internal/utils.go:87.17,89.4 1 0 -github.com/echovault/sugardb/internal/utils.go:90.3,91.21 2 0 -github.com/echovault/sugardb/internal/utils.go:91.21,92.9 1 0 -github.com/echovault/sugardb/internal/utils.go:94.3,94.15 1 0 -github.com/echovault/sugardb/internal/utils.go:97.2,97.37 1 0 -github.com/echovault/sugardb/internal/utils.go:100.120,102.20 2 0 -github.com/echovault/sugardb/internal/utils.go:102.20,104.3 1 0 -github.com/echovault/sugardb/internal/utils.go:105.2,105.16 1 0 -github.com/echovault/sugardb/internal/utils.go:105.16,107.3 1 0 -github.com/echovault/sugardb/internal/utils.go:108.2,108.24 1 0 -github.com/echovault/sugardb/internal/utils.go:108.24,110.3 1 0 -github.com/echovault/sugardb/internal/utils.go:111.2,111.21 1 0 -github.com/echovault/sugardb/internal/utils.go:111.21,113.3 1 0 -github.com/echovault/sugardb/internal/utils.go:114.2,114.16 1 0 -github.com/echovault/sugardb/internal/utils.go:117.37,119.16 2 0 -github.com/echovault/sugardb/internal/utils.go:119.16,121.3 1 0 -github.com/echovault/sugardb/internal/utils.go:122.2,122.15 1 0 -github.com/echovault/sugardb/internal/utils.go:122.15,123.37 1 0 -github.com/echovault/sugardb/internal/utils.go:123.37,125.4 1 0 -github.com/echovault/sugardb/internal/utils.go:128.2,130.23 2 0 -github.com/echovault/sugardb/internal/utils.go:133.72,134.65 1 0 -github.com/echovault/sugardb/internal/utils.go:134.65,137.3 1 0 -github.com/echovault/sugardb/internal/utils.go:138.2,138.18 1 0 -github.com/echovault/sugardb/internal/utils.go:138.18,141.3 1 0 -github.com/echovault/sugardb/internal/utils.go:142.2,142.49 1 0 -github.com/echovault/sugardb/internal/utils.go:142.49,143.52 1 0 -github.com/echovault/sugardb/internal/utils.go:143.52,145.4 1 0 -github.com/echovault/sugardb/internal/utils.go:147.2,147.71 1 0 -github.com/echovault/sugardb/internal/utils.go:150.66,152.2 1 0 -github.com/echovault/sugardb/internal/utils.go:154.24,155.11 1 0 -github.com/echovault/sugardb/internal/utils.go:155.11,157.3 1 0 -github.com/echovault/sugardb/internal/utils.go:158.2,158.10 1 0 -github.com/echovault/sugardb/internal/utils.go:162.49,166.16 3 0 -github.com/echovault/sugardb/internal/utils.go:166.16,168.3 1 0 -github.com/echovault/sugardb/internal/utils.go:170.2,171.17 2 0 -github.com/echovault/sugardb/internal/utils.go:172.12,173.19 1 0 -github.com/echovault/sugardb/internal/utils.go:174.12,175.26 1 0 -github.com/echovault/sugardb/internal/utils.go:176.12,177.33 1 0 -github.com/echovault/sugardb/internal/utils.go:178.12,179.40 1 0 -github.com/echovault/sugardb/internal/utils.go:180.12,181.47 1 0 -github.com/echovault/sugardb/internal/utils.go:182.10,183.91 1 0 -github.com/echovault/sugardb/internal/utils.go:186.2,186.30 1 0 -github.com/echovault/sugardb/internal/utils.go:190.64,191.20 1 0 -github.com/echovault/sugardb/internal/utils.go:191.20,193.3 1 0 -github.com/echovault/sugardb/internal/utils.go:196.2,196.33 1 0 -github.com/echovault/sugardb/internal/utils.go:196.33,198.3 1 0 -github.com/echovault/sugardb/internal/utils.go:203.2,206.37 2 0 -github.com/echovault/sugardb/internal/utils.go:210.100,211.36 1 1 -github.com/echovault/sugardb/internal/utils.go:211.36,213.26 2 1 -github.com/echovault/sugardb/internal/utils.go:213.26,215.35 1 1 -github.com/echovault/sugardb/internal/utils.go:215.35,216.13 1 0 -github.com/echovault/sugardb/internal/utils.go:219.4,219.30 1 1 -github.com/echovault/sugardb/internal/utils.go:219.30,221.5 1 0 -github.com/echovault/sugardb/internal/utils.go:223.3,223.36 1 1 -github.com/echovault/sugardb/internal/utils.go:223.36,225.4 1 0 -github.com/echovault/sugardb/internal/utils.go:227.2,227.14 1 1 -github.com/echovault/sugardb/internal/utils.go:232.43,233.14 1 0 -github.com/echovault/sugardb/internal/utils.go:233.14,235.3 1 0 -github.com/echovault/sugardb/internal/utils.go:236.2,236.30 1 0 -github.com/echovault/sugardb/internal/utils.go:236.30,238.3 1 0 -github.com/echovault/sugardb/internal/utils.go:239.2,239.30 1 0 -github.com/echovault/sugardb/internal/utils.go:239.30,241.3 1 0 -github.com/echovault/sugardb/internal/utils.go:243.2,244.21 2 0 -github.com/echovault/sugardb/internal/utils.go:244.21,246.3 1 0 -github.com/echovault/sugardb/internal/utils.go:248.2,249.29 2 0 -github.com/echovault/sugardb/internal/utils.go:249.29,251.13 2 0 -github.com/echovault/sugardb/internal/utils.go:251.13,252.9 1 0 -github.com/echovault/sugardb/internal/utils.go:256.2,256.10 1 0 -github.com/echovault/sugardb/internal/utils.go:259.41,261.28 2 0 -github.com/echovault/sugardb/internal/utils.go:261.28,263.3 1 0 -github.com/echovault/sugardb/internal/utils.go:264.2,264.20 1 0 -github.com/echovault/sugardb/internal/utils.go:267.47,270.16 3 0 -github.com/echovault/sugardb/internal/utils.go:270.16,272.3 1 0 -github.com/echovault/sugardb/internal/utils.go:273.2,273.24 1 0 -github.com/echovault/sugardb/internal/utils.go:276.52,279.16 3 0 -github.com/echovault/sugardb/internal/utils.go:279.16,281.3 1 0 -github.com/echovault/sugardb/internal/utils.go:282.2,282.24 1 0 -github.com/echovault/sugardb/internal/utils.go:285.50,288.16 3 0 -github.com/echovault/sugardb/internal/utils.go:288.16,290.3 1 0 -github.com/echovault/sugardb/internal/utils.go:291.2,291.25 1 0 -github.com/echovault/sugardb/internal/utils.go:294.52,297.16 3 0 -github.com/echovault/sugardb/internal/utils.go:297.16,299.3 1 0 -github.com/echovault/sugardb/internal/utils.go:300.2,300.23 1 0 -github.com/echovault/sugardb/internal/utils.go:303.51,306.16 3 0 -github.com/echovault/sugardb/internal/utils.go:306.16,308.3 1 0 -github.com/echovault/sugardb/internal/utils.go:309.2,309.22 1 0 -github.com/echovault/sugardb/internal/utils.go:312.59,316.16 3 0 -github.com/echovault/sugardb/internal/utils.go:316.16,318.3 1 0 -github.com/echovault/sugardb/internal/utils.go:320.2,320.16 1 0 -github.com/echovault/sugardb/internal/utils.go:320.16,322.3 1 0 -github.com/echovault/sugardb/internal/utils.go:324.2,324.39 1 0 -github.com/echovault/sugardb/internal/utils.go:324.39,326.3 1 0 -github.com/echovault/sugardb/internal/utils.go:328.2,329.30 2 0 -github.com/echovault/sugardb/internal/utils.go:329.30,330.17 1 0 -github.com/echovault/sugardb/internal/utils.go:330.17,332.12 2 0 -github.com/echovault/sugardb/internal/utils.go:334.3,334.22 1 0 -github.com/echovault/sugardb/internal/utils.go:336.2,336.17 1 0 -github.com/echovault/sugardb/internal/utils.go:339.67,342.16 3 0 -github.com/echovault/sugardb/internal/utils.go:342.16,344.3 1 0 -github.com/echovault/sugardb/internal/utils.go:345.2,345.16 1 0 -github.com/echovault/sugardb/internal/utils.go:345.16,347.3 1 0 -github.com/echovault/sugardb/internal/utils.go:348.2,349.31 2 0 -github.com/echovault/sugardb/internal/utils.go:349.31,350.18 1 0 -github.com/echovault/sugardb/internal/utils.go:350.18,352.12 2 0 -github.com/echovault/sugardb/internal/utils.go:354.3,355.33 2 0 -github.com/echovault/sugardb/internal/utils.go:355.33,357.4 1 0 -github.com/echovault/sugardb/internal/utils.go:358.3,358.17 1 0 -github.com/echovault/sugardb/internal/utils.go:360.2,360.17 1 0 -github.com/echovault/sugardb/internal/utils.go:363.57,366.16 3 0 -github.com/echovault/sugardb/internal/utils.go:366.16,368.3 1 0 -github.com/echovault/sugardb/internal/utils.go:369.2,369.16 1 0 -github.com/echovault/sugardb/internal/utils.go:369.16,371.3 1 0 -github.com/echovault/sugardb/internal/utils.go:372.2,373.30 2 0 -github.com/echovault/sugardb/internal/utils.go:373.30,374.17 1 0 -github.com/echovault/sugardb/internal/utils.go:374.17,376.12 2 0 -github.com/echovault/sugardb/internal/utils.go:378.3,378.23 1 0 -github.com/echovault/sugardb/internal/utils.go:380.2,380.17 1 0 -github.com/echovault/sugardb/internal/utils.go:383.58,386.16 3 0 -github.com/echovault/sugardb/internal/utils.go:386.16,388.3 1 0 -github.com/echovault/sugardb/internal/utils.go:389.2,389.16 1 0 -github.com/echovault/sugardb/internal/utils.go:389.16,391.3 1 0 -github.com/echovault/sugardb/internal/utils.go:392.2,393.30 2 0 -github.com/echovault/sugardb/internal/utils.go:393.30,394.17 1 0 -github.com/echovault/sugardb/internal/utils.go:394.17,396.12 2 0 -github.com/echovault/sugardb/internal/utils.go:398.3,398.20 1 0 -github.com/echovault/sugardb/internal/utils.go:400.2,400.17 1 0 -github.com/echovault/sugardb/internal/utils.go:403.70,404.32 1 0 -github.com/echovault/sugardb/internal/utils.go:404.32,405.60 1 0 -github.com/echovault/sugardb/internal/utils.go:405.60,407.4 1 0 -github.com/echovault/sugardb/internal/utils.go:407.6,409.4 1 0 -github.com/echovault/sugardb/internal/utils.go:411.2,411.30 1 0 -github.com/echovault/sugardb/internal/utils.go:411.30,412.62 1 0 -github.com/echovault/sugardb/internal/utils.go:412.62,414.4 1 0 -github.com/echovault/sugardb/internal/utils.go:414.6,416.4 1 0 -github.com/echovault/sugardb/internal/utils.go:418.2,418.13 1 0 -github.com/echovault/sugardb/internal/utils.go:421.33,423.16 2 0 -github.com/echovault/sugardb/internal/utils.go:423.16,425.3 1 0 -github.com/echovault/sugardb/internal/utils.go:427.2,428.16 2 0 -github.com/echovault/sugardb/internal/utils.go:428.16,430.3 1 0 -github.com/echovault/sugardb/internal/utils.go:431.2,431.15 1 0 -github.com/echovault/sugardb/internal/utils.go:431.15,433.3 1 0 -github.com/echovault/sugardb/internal/utils.go:435.2,435.42 1 0 -github.com/echovault/sugardb/internal/utils.go:438.61,443.12 4 0 -github.com/echovault/sugardb/internal/utils.go:443.12,444.7 1 0 -github.com/echovault/sugardb/internal/utils.go:444.7,446.73 2 0 -github.com/echovault/sugardb/internal/utils.go:446.73,448.13 1 0 -github.com/echovault/sugardb/internal/utils.go:450.4,450.9 1 0 -github.com/echovault/sugardb/internal/utils.go:452.3,452.21 1 0 -github.com/echovault/sugardb/internal/utils.go:455.2,456.15 2 0 -github.com/echovault/sugardb/internal/utils.go:456.15,458.3 1 0 -github.com/echovault/sugardb/internal/utils.go:460.2,460.9 1 0 -github.com/echovault/sugardb/internal/utils.go:461.18,462.47 1 0 -github.com/echovault/sugardb/internal/utils.go:463.14,464.19 1 0 -github.com/echovault/sugardb/internal/utils.go:468.84,473.12 4 0 -github.com/echovault/sugardb/internal/utils.go:473.12,474.7 1 0 -github.com/echovault/sugardb/internal/utils.go:474.7,476.73 2 0 -github.com/echovault/sugardb/internal/utils.go:476.73,478.13 1 0 -github.com/echovault/sugardb/internal/utils.go:480.4,480.9 1 0 -github.com/echovault/sugardb/internal/utils.go:482.3,482.21 1 0 -github.com/echovault/sugardb/internal/utils.go:485.2,486.15 2 0 -github.com/echovault/sugardb/internal/utils.go:486.15,488.3 1 0 -github.com/echovault/sugardb/internal/utils.go:490.2,490.9 1 0 -github.com/echovault/sugardb/internal/utils.go:491.18,492.47 1 0 -github.com/echovault/sugardb/internal/utils.go:493.14,494.19 1 0 -github.com/echovault/sugardb/internal/clock/clock.go:14.23,16.43 1 1 -github.com/echovault/sugardb/internal/clock/clock.go:16.43,18.3 1 1 -github.com/echovault/sugardb/internal/clock/clock.go:19.2,19.20 1 0 -github.com/echovault/sugardb/internal/clock/clock.go:24.34,26.2 1 0 -github.com/echovault/sugardb/internal/clock/clock.go:28.58,30.2 1 0 -github.com/echovault/sugardb/internal/clock/clock.go:34.34,37.2 2 1 -github.com/echovault/sugardb/internal/clock/clock.go:39.58,41.2 1 0 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:55.56,56.30 1 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:56.30,58.3 1 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:61.59,62.30 1 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:62.30,64.3 1 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:67.64,68.30 1 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:68.30,70.3 1 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:73.59,74.30 1 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:74.30,76.3 1 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:79.59,80.30 1 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:80.30,82.3 1 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:85.60,86.30 1 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:86.30,88.3 1 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:91.90,92.30 1 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:92.30,94.3 1 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:97.77,98.30 1 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:98.30,100.3 1 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:103.73,104.30 1 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:104.30,106.3 1 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:109.103,110.30 1 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:110.30,112.3 1 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:115.65,122.30 1 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:122.31,122.32 0 0 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:123.31,123.32 0 0 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:124.60,126.4 1 0 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:127.85,127.86 0 0 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:128.48,128.49 0 0 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:129.43,131.4 1 0 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:134.2,134.33 1 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:134.33,136.3 1 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:138.2,138.34 1 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:138.34,139.13 1 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:139.13,141.17 2 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:141.17,143.5 1 0 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:144.4,144.8 1 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:144.8,146.62 2 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:146.62,147.50 1 0 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:147.50,149.7 1 0 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:155.2,155.15 1 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:158.44,177.58 6 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:177.58,180.3 2 0 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:183.2,185.16 3 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:185.16,186.37 1 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:186.37,189.18 2 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:189.18,192.5 2 0 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:193.4,193.24 1 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:194.9,197.4 2 0 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:200.2,201.16 2 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:201.16,204.3 2 0 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:205.2,205.35 1 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:205.35,208.3 2 0 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:210.2,212.20 2 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:212.20,213.53 1 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:213.53,216.4 2 0 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:220.2,225.16 3 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:225.16,228.3 2 0 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:230.2,231.49 2 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:231.49,233.3 1 0 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:236.2,239.16 3 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:239.16,242.3 2 0 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:245.2,246.16 2 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:246.16,249.3 2 0 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:252.2,257.16 3 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:257.16,260.3 2 0 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:261.2,261.39 1 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:261.39,264.3 2 0 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:265.2,265.33 1 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:265.33,267.3 1 0 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:268.2,268.34 1 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:268.34,271.3 2 0 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:274.2,275.58 2 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:275.58,277.3 1 0 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:280.2,281.16 2 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:281.16,284.3 2 0 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:285.2,285.15 1 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:285.15,286.35 1 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:286.35,288.4 1 0 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:292.2,292.39 1 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:292.39,294.3 1 0 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:295.2,295.32 1 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:295.32,297.3 1 0 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:300.2,305.12 3 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:308.39,310.50 2 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:310.50,312.3 1 0 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:313.2,313.16 1 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:313.16,315.3 1 0 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:316.2,316.15 1 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:316.15,317.36 1 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:317.36,319.4 1 0 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:322.2,325.16 3 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:325.16,327.3 1 0 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:329.2,329.52 1 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:329.52,331.3 1 0 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:333.2,333.46 1 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:333.46,335.3 1 0 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:337.2,342.50 2 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:342.50,344.3 1 0 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:345.2,345.16 1 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:345.16,347.3 1 0 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:348.2,348.15 1 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:348.15,349.36 1 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:349.36,351.4 1 0 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:354.2,355.16 2 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:355.16,357.3 1 0 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:359.2,360.58 2 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:360.58,362.3 1 0 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:364.2,366.99 2 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:366.99,367.34 1 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:367.34,369.4 1 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:372.2,374.12 2 1 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:377.46,379.2 1 0 -github.com/echovault/sugardb/internal/snapshot/snapshot.go:381.42,383.2 1 1 +github.com/echovault/sugardb/internal/raft/raft.go:53.31,57.2 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:59.46,74.24 12 0 +github.com/echovault/sugardb/internal/raft/raft.go:74.24,79.3 3 0 +github.com/echovault/sugardb/internal/raft/raft.go:79.8,81.17 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:81.17,83.4 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:85.3,86.17 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:86.17,88.4 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:90.3,93.17 3 0 +github.com/echovault/sugardb/internal/raft/raft.go:93.17,95.4 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:98.2,100.16 3 0 +github.com/echovault/sugardb/internal/raft/raft.go:100.16,102.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:104.2,112.16 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:112.16,114.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:117.2,137.16 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:137.16,139.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:141.2,141.27 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:141.27,152.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:154.2,154.21 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:157.74,159.2 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:161.36,163.2 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:165.38,167.2 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:169.40,176.2 4 0 +github.com/echovault/sugardb/internal/raft/raft.go:183.9,184.22 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:184.22,186.44 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:186.44,188.4 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:190.3,190.56 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:190.56,192.42 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:192.42,194.5 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:197.3,198.17 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:198.17,200.4 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:203.2,203.12 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:206.61,207.23 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:207.23,209.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:211.2,211.73 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:211.73,213.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:215.2,215.12 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:218.37,220.2 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:222.31,224.22 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:224.22,226.17 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:226.17,229.4 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:230.3,230.49 1 0 github.com/echovault/sugardb/internal/types.go:35.43,40.29 3 1 github.com/echovault/sugardb/internal/types.go:41.11,42.12 1 0 github.com/echovault/sugardb/internal/types.go:44.11,45.34 1 1 @@ -6337,7 +6052,7 @@ github.com/echovault/sugardb/internal/utils.go:435.2,435.42 1 1 github.com/echovault/sugardb/internal/utils.go:438.61,443.12 4 1 github.com/echovault/sugardb/internal/utils.go:443.12,444.7 1 1 github.com/echovault/sugardb/internal/utils.go:444.7,446.73 2 1 -github.com/echovault/sugardb/internal/utils.go:446.73,448.13 1 0 +github.com/echovault/sugardb/internal/utils.go:446.73,448.13 1 1 github.com/echovault/sugardb/internal/utils.go:450.4,450.9 1 1 github.com/echovault/sugardb/internal/utils.go:452.3,452.21 1 1 github.com/echovault/sugardb/internal/utils.go:455.2,456.15 2 1 @@ -7282,47 +6997,353 @@ github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:67.2,67.40 1 0 github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:67.40,70.3 2 0 github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:72.2,74.12 2 0 github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:78.30,80.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:52.31,56.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:58.46,70.24 9 0 -github.com/echovault/sugardb/internal/raft/raft.go:70.24,75.3 3 0 -github.com/echovault/sugardb/internal/raft/raft.go:75.8,77.17 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:77.17,79.4 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:81.3,82.17 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:82.17,84.4 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:86.3,89.17 3 0 -github.com/echovault/sugardb/internal/raft/raft.go:89.17,91.4 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:94.2,96.16 3 0 -github.com/echovault/sugardb/internal/raft/raft.go:96.16,98.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:100.2,108.16 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:108.16,110.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:113.2,133.16 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:133.16,135.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:137.2,137.27 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:137.27,148.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:150.2,150.21 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:153.74,155.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:157.36,159.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:161.38,163.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:165.40,172.2 4 0 -github.com/echovault/sugardb/internal/raft/raft.go:179.9,180.22 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:180.22,182.44 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:182.44,184.4 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:186.3,186.56 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:186.56,188.42 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:188.42,190.5 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:193.3,194.17 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:194.17,196.4 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:199.2,199.12 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:202.61,203.23 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:203.23,205.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:207.2,207.73 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:207.73,209.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:211.2,211.12 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:214.37,216.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:218.31,220.22 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:220.22,222.17 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:222.17,225.4 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:226.3,226.49 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:53.31,57.2 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:59.46,74.24 12 0 +github.com/echovault/sugardb/internal/raft/raft.go:74.24,79.3 3 0 +github.com/echovault/sugardb/internal/raft/raft.go:79.8,81.17 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:81.17,83.4 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:85.3,86.17 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:86.17,88.4 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:90.3,93.17 3 0 +github.com/echovault/sugardb/internal/raft/raft.go:93.17,95.4 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:98.2,100.16 3 0 +github.com/echovault/sugardb/internal/raft/raft.go:100.16,102.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:104.2,112.16 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:112.16,114.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:117.2,137.16 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:137.16,139.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:141.2,141.27 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:141.27,152.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:154.2,154.21 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:157.74,159.2 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:161.36,163.2 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:165.38,167.2 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:169.40,176.2 4 0 +github.com/echovault/sugardb/internal/raft/raft.go:183.9,184.22 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:184.22,186.44 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:186.44,188.4 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:190.3,190.56 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:190.56,192.42 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:192.42,194.5 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:197.3,198.17 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:198.17,200.4 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:203.2,203.12 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:206.61,207.23 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:207.23,209.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:211.2,211.73 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:211.73,213.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:215.2,215.12 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:218.37,220.2 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:222.31,224.22 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:224.22,226.17 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:226.17,229.4 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:230.3,230.49 1 0 +github.com/echovault/sugardb/internal/types.go:35.43,40.29 3 0 +github.com/echovault/sugardb/internal/types.go:41.11,42.12 1 0 +github.com/echovault/sugardb/internal/types.go:44.11,45.34 1 0 +github.com/echovault/sugardb/internal/types.go:47.22,48.12 1 0 +github.com/echovault/sugardb/internal/types.go:49.14,52.24 2 0 +github.com/echovault/sugardb/internal/types.go:55.16,56.23 1 0 +github.com/echovault/sugardb/internal/types.go:56.23,59.4 2 0 +github.com/echovault/sugardb/internal/types.go:62.31,63.53 1 0 +github.com/echovault/sugardb/internal/types.go:65.10,66.117 1 0 +github.com/echovault/sugardb/internal/types.go:69.2,69.18 1 0 +github.com/echovault/sugardb/internal/utils.go:41.38,45.16 2 0 +github.com/echovault/sugardb/internal/utils.go:45.16,47.3 1 0 +github.com/echovault/sugardb/internal/utils.go:49.2,49.15 1 0 +github.com/echovault/sugardb/internal/utils.go:49.15,52.3 2 0 +github.com/echovault/sugardb/internal/utils.go:54.2,56.10 2 0 +github.com/echovault/sugardb/internal/utils.go:59.43,63.16 3 0 +github.com/echovault/sugardb/internal/utils.go:63.16,65.3 1 0 +github.com/echovault/sugardb/internal/utils.go:67.2,68.42 2 0 +github.com/echovault/sugardb/internal/utils.go:68.42,70.3 1 0 +github.com/echovault/sugardb/internal/utils.go:72.2,72.17 1 0 +github.com/echovault/sugardb/internal/utils.go:75.47,82.6 4 0 +github.com/echovault/sugardb/internal/utils.go:82.6,84.43 2 0 +github.com/echovault/sugardb/internal/utils.go:84.43,85.9 1 0 +github.com/echovault/sugardb/internal/utils.go:87.3,87.17 1 0 +github.com/echovault/sugardb/internal/utils.go:87.17,89.4 1 0 +github.com/echovault/sugardb/internal/utils.go:90.3,91.21 2 0 +github.com/echovault/sugardb/internal/utils.go:91.21,92.9 1 0 +github.com/echovault/sugardb/internal/utils.go:94.3,94.15 1 0 +github.com/echovault/sugardb/internal/utils.go:97.2,97.37 1 0 +github.com/echovault/sugardb/internal/utils.go:100.120,102.20 2 0 +github.com/echovault/sugardb/internal/utils.go:102.20,104.3 1 0 +github.com/echovault/sugardb/internal/utils.go:105.2,105.16 1 0 +github.com/echovault/sugardb/internal/utils.go:105.16,107.3 1 0 +github.com/echovault/sugardb/internal/utils.go:108.2,108.24 1 0 +github.com/echovault/sugardb/internal/utils.go:108.24,110.3 1 0 +github.com/echovault/sugardb/internal/utils.go:111.2,111.21 1 0 +github.com/echovault/sugardb/internal/utils.go:111.21,113.3 1 0 +github.com/echovault/sugardb/internal/utils.go:114.2,114.16 1 0 +github.com/echovault/sugardb/internal/utils.go:117.37,119.16 2 0 +github.com/echovault/sugardb/internal/utils.go:119.16,121.3 1 0 +github.com/echovault/sugardb/internal/utils.go:122.2,122.15 1 0 +github.com/echovault/sugardb/internal/utils.go:122.15,123.37 1 0 +github.com/echovault/sugardb/internal/utils.go:123.37,125.4 1 0 +github.com/echovault/sugardb/internal/utils.go:128.2,130.23 2 0 +github.com/echovault/sugardb/internal/utils.go:133.72,134.65 1 0 +github.com/echovault/sugardb/internal/utils.go:134.65,137.3 1 0 +github.com/echovault/sugardb/internal/utils.go:138.2,138.18 1 0 +github.com/echovault/sugardb/internal/utils.go:138.18,141.3 1 0 +github.com/echovault/sugardb/internal/utils.go:142.2,142.49 1 0 +github.com/echovault/sugardb/internal/utils.go:142.49,143.52 1 0 +github.com/echovault/sugardb/internal/utils.go:143.52,145.4 1 0 +github.com/echovault/sugardb/internal/utils.go:147.2,147.71 1 0 +github.com/echovault/sugardb/internal/utils.go:150.66,152.2 1 0 +github.com/echovault/sugardb/internal/utils.go:154.24,155.11 1 0 +github.com/echovault/sugardb/internal/utils.go:155.11,157.3 1 0 +github.com/echovault/sugardb/internal/utils.go:158.2,158.10 1 0 +github.com/echovault/sugardb/internal/utils.go:162.49,166.16 3 0 +github.com/echovault/sugardb/internal/utils.go:166.16,168.3 1 0 +github.com/echovault/sugardb/internal/utils.go:170.2,171.17 2 0 +github.com/echovault/sugardb/internal/utils.go:172.12,173.19 1 0 +github.com/echovault/sugardb/internal/utils.go:174.12,175.26 1 0 +github.com/echovault/sugardb/internal/utils.go:176.12,177.33 1 0 +github.com/echovault/sugardb/internal/utils.go:178.12,179.40 1 0 +github.com/echovault/sugardb/internal/utils.go:180.12,181.47 1 0 +github.com/echovault/sugardb/internal/utils.go:182.10,183.91 1 0 +github.com/echovault/sugardb/internal/utils.go:186.2,186.30 1 0 +github.com/echovault/sugardb/internal/utils.go:190.64,191.20 1 0 +github.com/echovault/sugardb/internal/utils.go:191.20,193.3 1 0 +github.com/echovault/sugardb/internal/utils.go:196.2,196.33 1 0 +github.com/echovault/sugardb/internal/utils.go:196.33,198.3 1 0 +github.com/echovault/sugardb/internal/utils.go:203.2,206.37 2 0 +github.com/echovault/sugardb/internal/utils.go:210.100,211.36 1 1 +github.com/echovault/sugardb/internal/utils.go:211.36,213.26 2 1 +github.com/echovault/sugardb/internal/utils.go:213.26,215.35 1 1 +github.com/echovault/sugardb/internal/utils.go:215.35,216.13 1 0 +github.com/echovault/sugardb/internal/utils.go:219.4,219.30 1 1 +github.com/echovault/sugardb/internal/utils.go:219.30,221.5 1 0 +github.com/echovault/sugardb/internal/utils.go:223.3,223.36 1 1 +github.com/echovault/sugardb/internal/utils.go:223.36,225.4 1 0 +github.com/echovault/sugardb/internal/utils.go:227.2,227.14 1 1 +github.com/echovault/sugardb/internal/utils.go:232.43,233.14 1 0 +github.com/echovault/sugardb/internal/utils.go:233.14,235.3 1 0 +github.com/echovault/sugardb/internal/utils.go:236.2,236.30 1 0 +github.com/echovault/sugardb/internal/utils.go:236.30,238.3 1 0 +github.com/echovault/sugardb/internal/utils.go:239.2,239.30 1 0 +github.com/echovault/sugardb/internal/utils.go:239.30,241.3 1 0 +github.com/echovault/sugardb/internal/utils.go:243.2,244.21 2 0 +github.com/echovault/sugardb/internal/utils.go:244.21,246.3 1 0 +github.com/echovault/sugardb/internal/utils.go:248.2,249.29 2 0 +github.com/echovault/sugardb/internal/utils.go:249.29,251.13 2 0 +github.com/echovault/sugardb/internal/utils.go:251.13,252.9 1 0 +github.com/echovault/sugardb/internal/utils.go:256.2,256.10 1 0 +github.com/echovault/sugardb/internal/utils.go:259.41,261.28 2 0 +github.com/echovault/sugardb/internal/utils.go:261.28,263.3 1 0 +github.com/echovault/sugardb/internal/utils.go:264.2,264.20 1 0 +github.com/echovault/sugardb/internal/utils.go:267.47,270.16 3 0 +github.com/echovault/sugardb/internal/utils.go:270.16,272.3 1 0 +github.com/echovault/sugardb/internal/utils.go:273.2,273.24 1 0 +github.com/echovault/sugardb/internal/utils.go:276.52,279.16 3 0 +github.com/echovault/sugardb/internal/utils.go:279.16,281.3 1 0 +github.com/echovault/sugardb/internal/utils.go:282.2,282.24 1 0 +github.com/echovault/sugardb/internal/utils.go:285.50,288.16 3 0 +github.com/echovault/sugardb/internal/utils.go:288.16,290.3 1 0 +github.com/echovault/sugardb/internal/utils.go:291.2,291.25 1 0 +github.com/echovault/sugardb/internal/utils.go:294.52,297.16 3 0 +github.com/echovault/sugardb/internal/utils.go:297.16,299.3 1 0 +github.com/echovault/sugardb/internal/utils.go:300.2,300.23 1 0 +github.com/echovault/sugardb/internal/utils.go:303.51,306.16 3 0 +github.com/echovault/sugardb/internal/utils.go:306.16,308.3 1 0 +github.com/echovault/sugardb/internal/utils.go:309.2,309.22 1 0 +github.com/echovault/sugardb/internal/utils.go:312.59,316.16 3 0 +github.com/echovault/sugardb/internal/utils.go:316.16,318.3 1 0 +github.com/echovault/sugardb/internal/utils.go:320.2,320.16 1 0 +github.com/echovault/sugardb/internal/utils.go:320.16,322.3 1 0 +github.com/echovault/sugardb/internal/utils.go:324.2,324.39 1 0 +github.com/echovault/sugardb/internal/utils.go:324.39,326.3 1 0 +github.com/echovault/sugardb/internal/utils.go:328.2,329.30 2 0 +github.com/echovault/sugardb/internal/utils.go:329.30,330.17 1 0 +github.com/echovault/sugardb/internal/utils.go:330.17,332.12 2 0 +github.com/echovault/sugardb/internal/utils.go:334.3,334.22 1 0 +github.com/echovault/sugardb/internal/utils.go:336.2,336.17 1 0 +github.com/echovault/sugardb/internal/utils.go:339.67,342.16 3 0 +github.com/echovault/sugardb/internal/utils.go:342.16,344.3 1 0 +github.com/echovault/sugardb/internal/utils.go:345.2,345.16 1 0 +github.com/echovault/sugardb/internal/utils.go:345.16,347.3 1 0 +github.com/echovault/sugardb/internal/utils.go:348.2,349.31 2 0 +github.com/echovault/sugardb/internal/utils.go:349.31,350.18 1 0 +github.com/echovault/sugardb/internal/utils.go:350.18,352.12 2 0 +github.com/echovault/sugardb/internal/utils.go:354.3,355.33 2 0 +github.com/echovault/sugardb/internal/utils.go:355.33,357.4 1 0 +github.com/echovault/sugardb/internal/utils.go:358.3,358.17 1 0 +github.com/echovault/sugardb/internal/utils.go:360.2,360.17 1 0 +github.com/echovault/sugardb/internal/utils.go:363.57,366.16 3 0 +github.com/echovault/sugardb/internal/utils.go:366.16,368.3 1 0 +github.com/echovault/sugardb/internal/utils.go:369.2,369.16 1 0 +github.com/echovault/sugardb/internal/utils.go:369.16,371.3 1 0 +github.com/echovault/sugardb/internal/utils.go:372.2,373.30 2 0 +github.com/echovault/sugardb/internal/utils.go:373.30,374.17 1 0 +github.com/echovault/sugardb/internal/utils.go:374.17,376.12 2 0 +github.com/echovault/sugardb/internal/utils.go:378.3,378.23 1 0 +github.com/echovault/sugardb/internal/utils.go:380.2,380.17 1 0 +github.com/echovault/sugardb/internal/utils.go:383.58,386.16 3 0 +github.com/echovault/sugardb/internal/utils.go:386.16,388.3 1 0 +github.com/echovault/sugardb/internal/utils.go:389.2,389.16 1 0 +github.com/echovault/sugardb/internal/utils.go:389.16,391.3 1 0 +github.com/echovault/sugardb/internal/utils.go:392.2,393.30 2 0 +github.com/echovault/sugardb/internal/utils.go:393.30,394.17 1 0 +github.com/echovault/sugardb/internal/utils.go:394.17,396.12 2 0 +github.com/echovault/sugardb/internal/utils.go:398.3,398.20 1 0 +github.com/echovault/sugardb/internal/utils.go:400.2,400.17 1 0 +github.com/echovault/sugardb/internal/utils.go:403.70,404.32 1 0 +github.com/echovault/sugardb/internal/utils.go:404.32,405.60 1 0 +github.com/echovault/sugardb/internal/utils.go:405.60,407.4 1 0 +github.com/echovault/sugardb/internal/utils.go:407.6,409.4 1 0 +github.com/echovault/sugardb/internal/utils.go:411.2,411.30 1 0 +github.com/echovault/sugardb/internal/utils.go:411.30,412.62 1 0 +github.com/echovault/sugardb/internal/utils.go:412.62,414.4 1 0 +github.com/echovault/sugardb/internal/utils.go:414.6,416.4 1 0 +github.com/echovault/sugardb/internal/utils.go:418.2,418.13 1 0 +github.com/echovault/sugardb/internal/utils.go:421.33,423.16 2 0 +github.com/echovault/sugardb/internal/utils.go:423.16,425.3 1 0 +github.com/echovault/sugardb/internal/utils.go:427.2,428.16 2 0 +github.com/echovault/sugardb/internal/utils.go:428.16,430.3 1 0 +github.com/echovault/sugardb/internal/utils.go:431.2,431.15 1 0 +github.com/echovault/sugardb/internal/utils.go:431.15,433.3 1 0 +github.com/echovault/sugardb/internal/utils.go:435.2,435.42 1 0 +github.com/echovault/sugardb/internal/utils.go:438.61,443.12 4 0 +github.com/echovault/sugardb/internal/utils.go:443.12,444.7 1 0 +github.com/echovault/sugardb/internal/utils.go:444.7,446.73 2 0 +github.com/echovault/sugardb/internal/utils.go:446.73,448.13 1 0 +github.com/echovault/sugardb/internal/utils.go:450.4,450.9 1 0 +github.com/echovault/sugardb/internal/utils.go:452.3,452.21 1 0 +github.com/echovault/sugardb/internal/utils.go:455.2,456.15 2 0 +github.com/echovault/sugardb/internal/utils.go:456.15,458.3 1 0 +github.com/echovault/sugardb/internal/utils.go:460.2,460.9 1 0 +github.com/echovault/sugardb/internal/utils.go:461.18,462.47 1 0 +github.com/echovault/sugardb/internal/utils.go:463.14,464.19 1 0 +github.com/echovault/sugardb/internal/utils.go:468.84,473.12 4 0 +github.com/echovault/sugardb/internal/utils.go:473.12,474.7 1 0 +github.com/echovault/sugardb/internal/utils.go:474.7,476.73 2 0 +github.com/echovault/sugardb/internal/utils.go:476.73,478.13 1 0 +github.com/echovault/sugardb/internal/utils.go:480.4,480.9 1 0 +github.com/echovault/sugardb/internal/utils.go:482.3,482.21 1 0 +github.com/echovault/sugardb/internal/utils.go:485.2,486.15 2 0 +github.com/echovault/sugardb/internal/utils.go:486.15,488.3 1 0 +github.com/echovault/sugardb/internal/utils.go:490.2,490.9 1 0 +github.com/echovault/sugardb/internal/utils.go:491.18,492.47 1 0 +github.com/echovault/sugardb/internal/utils.go:493.14,494.19 1 0 +github.com/echovault/sugardb/internal/clock/clock.go:14.23,16.43 1 1 +github.com/echovault/sugardb/internal/clock/clock.go:16.43,18.3 1 1 +github.com/echovault/sugardb/internal/clock/clock.go:19.2,19.20 1 0 +github.com/echovault/sugardb/internal/clock/clock.go:24.34,26.2 1 0 +github.com/echovault/sugardb/internal/clock/clock.go:28.58,30.2 1 0 +github.com/echovault/sugardb/internal/clock/clock.go:34.34,37.2 2 1 +github.com/echovault/sugardb/internal/clock/clock.go:39.58,41.2 1 0 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:55.56,56.30 1 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:56.30,58.3 1 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:61.59,62.30 1 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:62.30,64.3 1 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:67.64,68.30 1 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:68.30,70.3 1 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:73.59,74.30 1 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:74.30,76.3 1 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:79.59,80.30 1 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:80.30,82.3 1 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:85.60,86.30 1 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:86.30,88.3 1 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:91.90,92.30 1 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:92.30,94.3 1 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:97.77,98.30 1 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:98.30,100.3 1 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:103.73,104.30 1 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:104.30,106.3 1 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:109.103,110.30 1 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:110.30,112.3 1 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:115.65,122.30 1 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:122.31,122.32 0 0 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:123.31,123.32 0 0 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:124.60,126.4 1 0 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:127.85,127.86 0 0 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:128.48,128.49 0 0 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:129.43,131.4 1 0 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:134.2,134.33 1 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:134.33,136.3 1 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:138.2,138.34 1 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:138.34,139.13 1 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:139.13,141.17 2 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:141.17,143.5 1 0 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:144.4,144.8 1 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:144.8,146.62 2 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:146.62,147.50 1 0 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:147.50,149.7 1 0 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:155.2,155.15 1 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:158.44,177.58 6 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:177.58,180.3 2 0 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:183.2,185.16 3 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:185.16,186.37 1 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:186.37,189.18 2 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:189.18,192.5 2 0 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:193.4,193.24 1 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:194.9,197.4 2 0 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:200.2,201.16 2 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:201.16,204.3 2 0 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:205.2,205.35 1 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:205.35,208.3 2 0 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:210.2,212.20 2 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:212.20,213.53 1 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:213.53,216.4 2 0 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:220.2,225.16 3 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:225.16,228.3 2 0 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:230.2,231.49 2 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:231.49,233.3 1 0 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:236.2,239.16 3 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:239.16,242.3 2 0 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:245.2,246.16 2 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:246.16,249.3 2 0 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:252.2,257.16 3 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:257.16,260.3 2 0 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:261.2,261.39 1 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:261.39,264.3 2 0 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:265.2,265.33 1 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:265.33,267.3 1 0 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:268.2,268.34 1 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:268.34,271.3 2 0 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:274.2,275.58 2 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:275.58,277.3 1 0 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:280.2,281.16 2 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:281.16,284.3 2 0 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:285.2,285.15 1 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:285.15,286.35 1 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:286.35,288.4 1 0 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:292.2,292.39 1 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:292.39,294.3 1 0 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:295.2,295.32 1 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:295.32,297.3 1 0 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:300.2,305.12 3 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:308.39,310.50 2 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:310.50,312.3 1 0 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:313.2,313.16 1 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:313.16,315.3 1 0 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:316.2,316.15 1 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:316.15,317.36 1 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:317.36,319.4 1 0 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:322.2,325.16 3 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:325.16,327.3 1 0 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:329.2,329.52 1 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:329.52,331.3 1 0 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:333.2,333.46 1 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:333.46,335.3 1 0 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:337.2,342.50 2 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:342.50,344.3 1 0 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:345.2,345.16 1 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:345.16,347.3 1 0 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:348.2,348.15 1 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:348.15,349.36 1 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:349.36,351.4 1 0 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:354.2,355.16 2 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:355.16,357.3 1 0 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:359.2,360.58 2 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:360.58,362.3 1 0 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:364.2,366.99 2 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:366.99,367.34 1 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:367.34,369.4 1 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:372.2,374.12 2 1 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:377.46,379.2 1 0 +github.com/echovault/sugardb/internal/snapshot/snapshot.go:381.42,383.2 1 1 github.com/echovault/sugardb/internal/types.go:35.43,40.29 3 1 github.com/echovault/sugardb/internal/types.go:41.11,42.12 1 0 github.com/echovault/sugardb/internal/types.go:44.11,45.34 1 1 @@ -7492,7 +7513,7 @@ github.com/echovault/sugardb/internal/utils.go:435.2,435.42 1 1 github.com/echovault/sugardb/internal/utils.go:438.61,443.12 4 1 github.com/echovault/sugardb/internal/utils.go:443.12,444.7 1 1 github.com/echovault/sugardb/internal/utils.go:444.7,446.73 2 1 -github.com/echovault/sugardb/internal/utils.go:446.73,448.13 1 1 +github.com/echovault/sugardb/internal/utils.go:446.73,448.13 1 0 github.com/echovault/sugardb/internal/utils.go:450.4,450.9 1 1 github.com/echovault/sugardb/internal/utils.go:452.3,452.21 1 1 github.com/echovault/sugardb/internal/utils.go:455.2,456.15 2 1 @@ -8097,47 +8118,47 @@ github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:67.2,67.40 1 0 github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:67.40,70.3 2 0 github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:72.2,74.12 2 0 github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:78.30,80.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:52.31,56.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:58.46,70.24 9 0 -github.com/echovault/sugardb/internal/raft/raft.go:70.24,75.3 3 0 -github.com/echovault/sugardb/internal/raft/raft.go:75.8,77.17 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:77.17,79.4 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:81.3,82.17 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:82.17,84.4 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:86.3,89.17 3 0 -github.com/echovault/sugardb/internal/raft/raft.go:89.17,91.4 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:94.2,96.16 3 0 -github.com/echovault/sugardb/internal/raft/raft.go:96.16,98.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:100.2,108.16 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:108.16,110.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:113.2,133.16 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:133.16,135.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:137.2,137.27 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:137.27,148.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:150.2,150.21 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:153.74,155.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:157.36,159.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:161.38,163.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:165.40,172.2 4 0 -github.com/echovault/sugardb/internal/raft/raft.go:179.9,180.22 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:180.22,182.44 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:182.44,184.4 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:186.3,186.56 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:186.56,188.42 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:188.42,190.5 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:193.3,194.17 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:194.17,196.4 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:199.2,199.12 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:202.61,203.23 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:203.23,205.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:207.2,207.73 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:207.73,209.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:211.2,211.12 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:214.37,216.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:218.31,220.22 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:220.22,222.17 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:222.17,225.4 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:226.3,226.49 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:53.31,57.2 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:59.46,74.24 12 0 +github.com/echovault/sugardb/internal/raft/raft.go:74.24,79.3 3 0 +github.com/echovault/sugardb/internal/raft/raft.go:79.8,81.17 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:81.17,83.4 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:85.3,86.17 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:86.17,88.4 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:90.3,93.17 3 0 +github.com/echovault/sugardb/internal/raft/raft.go:93.17,95.4 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:98.2,100.16 3 0 +github.com/echovault/sugardb/internal/raft/raft.go:100.16,102.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:104.2,112.16 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:112.16,114.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:117.2,137.16 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:137.16,139.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:141.2,141.27 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:141.27,152.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:154.2,154.21 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:157.74,159.2 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:161.36,163.2 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:165.38,167.2 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:169.40,176.2 4 0 +github.com/echovault/sugardb/internal/raft/raft.go:183.9,184.22 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:184.22,186.44 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:186.44,188.4 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:190.3,190.56 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:190.56,192.42 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:192.42,194.5 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:197.3,198.17 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:198.17,200.4 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:203.2,203.12 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:206.61,207.23 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:207.23,209.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:211.2,211.73 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:211.73,213.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:215.2,215.12 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:218.37,220.2 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:222.31,224.22 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:224.22,226.17 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:226.17,229.4 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:230.3,230.49 1 0 github.com/echovault/sugardb/internal/types.go:35.43,40.29 3 1 github.com/echovault/sugardb/internal/types.go:41.11,42.12 1 1 github.com/echovault/sugardb/internal/types.go:44.11,45.34 1 1 @@ -8307,7 +8328,7 @@ github.com/echovault/sugardb/internal/utils.go:435.2,435.42 1 1 github.com/echovault/sugardb/internal/utils.go:438.61,443.12 4 1 github.com/echovault/sugardb/internal/utils.go:443.12,444.7 1 1 github.com/echovault/sugardb/internal/utils.go:444.7,446.73 2 1 -github.com/echovault/sugardb/internal/utils.go:446.73,448.13 1 1 +github.com/echovault/sugardb/internal/utils.go:446.73,448.13 1 0 github.com/echovault/sugardb/internal/utils.go:450.4,450.9 1 1 github.com/echovault/sugardb/internal/utils.go:452.3,452.21 1 1 github.com/echovault/sugardb/internal/utils.go:455.2,456.15 2 1 @@ -8438,47 +8459,47 @@ github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:67.2,67.40 1 0 github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:67.40,70.3 2 0 github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:72.2,74.12 2 0 github.com/echovault/sugardb/internal/raft/fsm_snapshot.go:78.30,80.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:52.31,56.2 1 1 -github.com/echovault/sugardb/internal/raft/raft.go:58.46,70.24 9 1 -github.com/echovault/sugardb/internal/raft/raft.go:70.24,75.3 3 1 -github.com/echovault/sugardb/internal/raft/raft.go:75.8,77.17 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:77.17,79.4 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:81.3,82.17 2 0 -github.com/echovault/sugardb/internal/raft/raft.go:82.17,84.4 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:86.3,89.17 3 0 -github.com/echovault/sugardb/internal/raft/raft.go:89.17,91.4 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:94.2,96.16 3 1 -github.com/echovault/sugardb/internal/raft/raft.go:96.16,98.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:100.2,108.16 2 1 -github.com/echovault/sugardb/internal/raft/raft.go:108.16,110.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:113.2,133.16 2 1 -github.com/echovault/sugardb/internal/raft/raft.go:133.16,135.3 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:137.2,137.27 1 1 -github.com/echovault/sugardb/internal/raft/raft.go:137.27,148.3 1 1 -github.com/echovault/sugardb/internal/raft/raft.go:150.2,150.21 1 1 -github.com/echovault/sugardb/internal/raft/raft.go:153.74,155.2 1 1 -github.com/echovault/sugardb/internal/raft/raft.go:157.36,159.2 1 1 -github.com/echovault/sugardb/internal/raft/raft.go:161.38,163.2 1 1 -github.com/echovault/sugardb/internal/raft/raft.go:165.40,172.2 4 1 -github.com/echovault/sugardb/internal/raft/raft.go:179.9,180.22 1 1 -github.com/echovault/sugardb/internal/raft/raft.go:180.22,182.44 2 1 -github.com/echovault/sugardb/internal/raft/raft.go:182.44,184.4 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:186.3,186.56 1 1 -github.com/echovault/sugardb/internal/raft/raft.go:186.56,188.42 1 1 -github.com/echovault/sugardb/internal/raft/raft.go:188.42,190.5 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:193.3,194.17 2 1 -github.com/echovault/sugardb/internal/raft/raft.go:194.17,196.4 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:199.2,199.12 1 1 -github.com/echovault/sugardb/internal/raft/raft.go:202.61,203.23 1 1 -github.com/echovault/sugardb/internal/raft/raft.go:203.23,205.3 1 1 -github.com/echovault/sugardb/internal/raft/raft.go:207.2,207.73 1 1 -github.com/echovault/sugardb/internal/raft/raft.go:207.73,209.3 1 1 -github.com/echovault/sugardb/internal/raft/raft.go:211.2,211.12 1 1 -github.com/echovault/sugardb/internal/raft/raft.go:214.37,216.2 1 0 -github.com/echovault/sugardb/internal/raft/raft.go:218.31,220.22 1 1 -github.com/echovault/sugardb/internal/raft/raft.go:220.22,222.17 2 1 -github.com/echovault/sugardb/internal/raft/raft.go:222.17,225.4 2 1 -github.com/echovault/sugardb/internal/raft/raft.go:226.3,226.49 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:53.31,57.2 1 1 +github.com/echovault/sugardb/internal/raft/raft.go:59.46,74.24 12 1 +github.com/echovault/sugardb/internal/raft/raft.go:74.24,79.3 3 1 +github.com/echovault/sugardb/internal/raft/raft.go:79.8,81.17 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:81.17,83.4 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:85.3,86.17 2 0 +github.com/echovault/sugardb/internal/raft/raft.go:86.17,88.4 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:90.3,93.17 3 0 +github.com/echovault/sugardb/internal/raft/raft.go:93.17,95.4 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:98.2,100.16 3 1 +github.com/echovault/sugardb/internal/raft/raft.go:100.16,102.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:104.2,112.16 2 1 +github.com/echovault/sugardb/internal/raft/raft.go:112.16,114.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:117.2,137.16 2 1 +github.com/echovault/sugardb/internal/raft/raft.go:137.16,139.3 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:141.2,141.27 1 1 +github.com/echovault/sugardb/internal/raft/raft.go:141.27,152.3 1 1 +github.com/echovault/sugardb/internal/raft/raft.go:154.2,154.21 1 1 +github.com/echovault/sugardb/internal/raft/raft.go:157.74,159.2 1 1 +github.com/echovault/sugardb/internal/raft/raft.go:161.36,163.2 1 1 +github.com/echovault/sugardb/internal/raft/raft.go:165.38,167.2 1 1 +github.com/echovault/sugardb/internal/raft/raft.go:169.40,176.2 4 1 +github.com/echovault/sugardb/internal/raft/raft.go:183.9,184.22 1 1 +github.com/echovault/sugardb/internal/raft/raft.go:184.22,186.44 2 1 +github.com/echovault/sugardb/internal/raft/raft.go:186.44,188.4 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:190.3,190.56 1 1 +github.com/echovault/sugardb/internal/raft/raft.go:190.56,192.42 1 1 +github.com/echovault/sugardb/internal/raft/raft.go:192.42,194.5 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:197.3,198.17 2 1 +github.com/echovault/sugardb/internal/raft/raft.go:198.17,200.4 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:203.2,203.12 1 1 +github.com/echovault/sugardb/internal/raft/raft.go:206.61,207.23 1 1 +github.com/echovault/sugardb/internal/raft/raft.go:207.23,209.3 1 1 +github.com/echovault/sugardb/internal/raft/raft.go:211.2,211.73 1 1 +github.com/echovault/sugardb/internal/raft/raft.go:211.73,213.3 1 1 +github.com/echovault/sugardb/internal/raft/raft.go:215.2,215.12 1 1 +github.com/echovault/sugardb/internal/raft/raft.go:218.37,220.2 1 0 +github.com/echovault/sugardb/internal/raft/raft.go:222.31,224.22 1 1 +github.com/echovault/sugardb/internal/raft/raft.go:224.22,226.17 2 1 +github.com/echovault/sugardb/internal/raft/raft.go:226.17,229.4 2 1 +github.com/echovault/sugardb/internal/raft/raft.go:230.3,230.49 1 0 github.com/echovault/sugardb/sugardb/api_acl.go:126.69,128.23 2 1 github.com/echovault/sugardb/sugardb/api_acl.go:128.23,130.3 1 1 github.com/echovault/sugardb/sugardb/api_acl.go:131.2,132.16 2 1 @@ -8859,54 +8880,39 @@ github.com/echovault/sugardb/sugardb/api_list.go:299.2,299.41 1 1 github.com/echovault/sugardb/sugardb/api_list.go:315.74,318.16 3 1 github.com/echovault/sugardb/sugardb/api_list.go:318.16,320.3 1 1 github.com/echovault/sugardb/sugardb/api_list.go:321.2,321.41 1 0 -github.com/echovault/sugardb/sugardb/api_pubsub.go:42.69,46.41 3 1 -github.com/echovault/sugardb/sugardb/api_pubsub.go:46.41,55.3 4 1 -github.com/echovault/sugardb/sugardb/api_pubsub.go:55.8,58.10 2 1 -github.com/echovault/sugardb/sugardb/api_pubsub.go:58.10,60.4 1 0 -github.com/echovault/sugardb/sugardb/api_pubsub.go:61.3,62.33 2 1 -github.com/echovault/sugardb/sugardb/api_pubsub.go:65.2,65.33 1 1 -github.com/echovault/sugardb/sugardb/api_pubsub.go:78.93,80.16 2 1 -github.com/echovault/sugardb/sugardb/api_pubsub.go:80.16,81.26 1 0 -github.com/echovault/sugardb/sugardb/api_pubsub.go:81.26,83.4 1 0 -github.com/echovault/sugardb/sugardb/api_pubsub.go:87.2,88.12 2 1 -github.com/echovault/sugardb/sugardb/api_pubsub.go:88.12,90.3 1 1 -github.com/echovault/sugardb/sugardb/api_pubsub.go:92.2,92.25 1 1 -github.com/echovault/sugardb/sugardb/api_pubsub.go:92.25,97.33 4 1 -github.com/echovault/sugardb/sugardb/api_pubsub.go:97.33,99.4 1 1 -github.com/echovault/sugardb/sugardb/api_pubsub.go:101.3,101.13 1 1 -github.com/echovault/sugardb/sugardb/api_pubsub.go:112.68,114.9 2 1 -github.com/echovault/sugardb/sugardb/api_pubsub.go:114.9,116.3 1 0 -github.com/echovault/sugardb/sugardb/api_pubsub.go:117.2,118.107 2 1 -github.com/echovault/sugardb/sugardb/api_pubsub.go:131.94,133.16 2 1 -github.com/echovault/sugardb/sugardb/api_pubsub.go:133.16,134.26 1 0 -github.com/echovault/sugardb/sugardb/api_pubsub.go:134.26,136.4 1 0 -github.com/echovault/sugardb/sugardb/api_pubsub.go:140.2,141.12 2 1 -github.com/echovault/sugardb/sugardb/api_pubsub.go:141.12,143.3 1 1 -github.com/echovault/sugardb/sugardb/api_pubsub.go:145.2,145.25 1 1 -github.com/echovault/sugardb/sugardb/api_pubsub.go:145.25,150.33 4 1 -github.com/echovault/sugardb/sugardb/api_pubsub.go:150.33,152.4 1 1 -github.com/echovault/sugardb/sugardb/api_pubsub.go:154.3,154.13 1 1 -github.com/echovault/sugardb/sugardb/api_pubsub.go:165.69,167.9 2 1 -github.com/echovault/sugardb/sugardb/api_pubsub.go:167.9,169.3 1 0 -github.com/echovault/sugardb/sugardb/api_pubsub.go:170.2,171.107 2 1 -github.com/echovault/sugardb/sugardb/api_pubsub.go:184.71,186.16 2 1 -github.com/echovault/sugardb/sugardb/api_pubsub.go:186.16,188.3 1 0 -github.com/echovault/sugardb/sugardb/api_pubsub.go:189.2,190.40 2 1 -github.com/echovault/sugardb/sugardb/api_pubsub.go:200.73,202.19 2 1 -github.com/echovault/sugardb/sugardb/api_pubsub.go:202.19,204.3 1 1 -github.com/echovault/sugardb/sugardb/api_pubsub.go:205.2,206.16 2 1 -github.com/echovault/sugardb/sugardb/api_pubsub.go:206.16,208.3 1 0 -github.com/echovault/sugardb/sugardb/api_pubsub.go:209.2,209.45 1 1 -github.com/echovault/sugardb/sugardb/api_pubsub.go:215.52,217.16 2 1 -github.com/echovault/sugardb/sugardb/api_pubsub.go:217.16,219.3 1 0 -github.com/echovault/sugardb/sugardb/api_pubsub.go:220.2,220.41 1 1 -github.com/echovault/sugardb/sugardb/api_pubsub.go:230.81,234.16 3 1 -github.com/echovault/sugardb/sugardb/api_pubsub.go:234.16,236.3 1 0 -github.com/echovault/sugardb/sugardb/api_pubsub.go:238.2,240.16 3 1 -github.com/echovault/sugardb/sugardb/api_pubsub.go:240.16,242.3 1 0 -github.com/echovault/sugardb/sugardb/api_pubsub.go:244.2,247.28 3 1 -github.com/echovault/sugardb/sugardb/api_pubsub.go:247.28,250.3 2 1 -github.com/echovault/sugardb/sugardb/api_pubsub.go:252.2,252.20 1 1 +github.com/echovault/sugardb/sugardb/api_pubsub.go:30.58,32.2 1 1 +github.com/echovault/sugardb/sugardb/api_pubsub.go:46.90,50.9 3 1 +github.com/echovault/sugardb/sugardb/api_pubsub.go:50.9,55.3 1 1 +github.com/echovault/sugardb/sugardb/api_pubsub.go:55.8,57.3 1 0 +github.com/echovault/sugardb/sugardb/api_pubsub.go:59.2,61.23 2 1 +github.com/echovault/sugardb/sugardb/api_pubsub.go:71.68,73.9 2 1 +github.com/echovault/sugardb/sugardb/api_pubsub.go:73.9,75.3 1 1 +github.com/echovault/sugardb/sugardb/api_pubsub.go:76.2,77.55 2 0 +github.com/echovault/sugardb/sugardb/api_pubsub.go:91.91,95.9 3 1 +github.com/echovault/sugardb/sugardb/api_pubsub.go:95.9,100.3 1 1 +github.com/echovault/sugardb/sugardb/api_pubsub.go:100.8,102.3 1 0 +github.com/echovault/sugardb/sugardb/api_pubsub.go:104.2,106.23 2 1 +github.com/echovault/sugardb/sugardb/api_pubsub.go:116.69,118.9 2 1 +github.com/echovault/sugardb/sugardb/api_pubsub.go:118.9,120.3 1 1 +github.com/echovault/sugardb/sugardb/api_pubsub.go:121.2,122.54 2 0 +github.com/echovault/sugardb/sugardb/api_pubsub.go:135.71,139.16 2 1 +github.com/echovault/sugardb/sugardb/api_pubsub.go:139.16,141.3 1 0 +github.com/echovault/sugardb/sugardb/api_pubsub.go:142.2,143.40 2 1 +github.com/echovault/sugardb/sugardb/api_pubsub.go:153.73,155.19 2 1 +github.com/echovault/sugardb/sugardb/api_pubsub.go:155.19,157.3 1 1 +github.com/echovault/sugardb/sugardb/api_pubsub.go:158.2,159.16 2 1 +github.com/echovault/sugardb/sugardb/api_pubsub.go:159.16,161.3 1 0 +github.com/echovault/sugardb/sugardb/api_pubsub.go:162.2,162.45 1 1 +github.com/echovault/sugardb/sugardb/api_pubsub.go:168.52,170.16 2 1 +github.com/echovault/sugardb/sugardb/api_pubsub.go:170.16,172.3 1 0 +github.com/echovault/sugardb/sugardb/api_pubsub.go:173.2,173.41 1 1 +github.com/echovault/sugardb/sugardb/api_pubsub.go:183.81,187.16 3 1 +github.com/echovault/sugardb/sugardb/api_pubsub.go:187.16,189.3 1 0 +github.com/echovault/sugardb/sugardb/api_pubsub.go:191.2,193.16 3 1 +github.com/echovault/sugardb/sugardb/api_pubsub.go:193.16,195.3 1 0 +github.com/echovault/sugardb/sugardb/api_pubsub.go:197.2,200.28 3 1 +github.com/echovault/sugardb/sugardb/api_pubsub.go:200.28,203.3 2 1 +github.com/echovault/sugardb/sugardb/api_pubsub.go:205.2,205.20 1 1 github.com/echovault/sugardb/sugardb/api_set.go:36.73,39.16 3 1 github.com/echovault/sugardb/sugardb/api_set.go:39.16,41.3 1 1 github.com/echovault/sugardb/sugardb/api_set.go:42.2,42.41 1 1 @@ -9190,89 +9196,95 @@ github.com/echovault/sugardb/sugardb/cluster.go:95.9,97.3 1 0 github.com/echovault/sugardb/sugardb/cluster.go:99.2,99.20 1 1 github.com/echovault/sugardb/sugardb/cluster.go:99.20,101.3 1 0 github.com/echovault/sugardb/sugardb/cluster.go:103.2,103.24 1 1 -github.com/echovault/sugardb/sugardb/config.go:27.36,29.2 1 1 -github.com/echovault/sugardb/sugardb/config.go:31.60,36.23 1 1 -github.com/echovault/sugardb/sugardb/config.go:36.23,37.28 1 1 -github.com/echovault/sugardb/sugardb/config.go:37.28,39.5 1 1 -github.com/echovault/sugardb/sugardb/config.go:40.4,40.23 1 1 -github.com/echovault/sugardb/sugardb/config.go:42.23,43.29 1 1 -github.com/echovault/sugardb/sugardb/config.go:43.29,45.5 1 1 -github.com/echovault/sugardb/sugardb/config.go:46.4,46.34 1 1 -github.com/echovault/sugardb/sugardb/config.go:46.34,48.5 1 1 -github.com/echovault/sugardb/sugardb/config.go:49.4,49.20 1 1 -github.com/echovault/sugardb/sugardb/config.go:60.48,61.32 1 0 -github.com/echovault/sugardb/sugardb/config.go:61.32,62.17 1 0 -github.com/echovault/sugardb/sugardb/config.go:62.17,64.4 1 0 -github.com/echovault/sugardb/sugardb/config.go:64.9,66.4 1 0 -github.com/echovault/sugardb/sugardb/config.go:73.62,74.32 1 1 -github.com/echovault/sugardb/sugardb/config.go:74.32,76.3 1 1 -github.com/echovault/sugardb/sugardb/config.go:82.62,83.32 1 1 -github.com/echovault/sugardb/sugardb/config.go:83.32,85.3 1 1 -github.com/echovault/sugardb/sugardb/config.go:91.49,92.32 1 0 -github.com/echovault/sugardb/sugardb/config.go:92.32,93.17 1 0 -github.com/echovault/sugardb/sugardb/config.go:93.17,95.4 1 0 -github.com/echovault/sugardb/sugardb/config.go:95.9,97.4 1 0 -github.com/echovault/sugardb/sugardb/config.go:110.74,111.32 1 0 -github.com/echovault/sugardb/sugardb/config.go:111.32,112.37 1 0 -github.com/echovault/sugardb/sugardb/config.go:112.37,114.4 1 0 -github.com/echovault/sugardb/sugardb/config.go:121.63,122.32 1 0 -github.com/echovault/sugardb/sugardb/config.go:122.32,124.3 1 0 -github.com/echovault/sugardb/sugardb/config.go:130.51,131.32 1 0 -github.com/echovault/sugardb/sugardb/config.go:131.32,133.3 1 0 -github.com/echovault/sugardb/sugardb/config.go:139.59,140.32 1 0 -github.com/echovault/sugardb/sugardb/config.go:140.32,142.3 1 0 -github.com/echovault/sugardb/sugardb/config.go:148.59,149.32 1 0 -github.com/echovault/sugardb/sugardb/config.go:149.32,151.3 1 0 -github.com/echovault/sugardb/sugardb/config.go:157.59,158.32 1 0 -github.com/echovault/sugardb/sugardb/config.go:158.32,160.3 1 0 -github.com/echovault/sugardb/sugardb/config.go:166.57,167.32 1 0 -github.com/echovault/sugardb/sugardb/config.go:167.32,169.3 1 0 -github.com/echovault/sugardb/sugardb/config.go:175.61,176.32 1 0 -github.com/echovault/sugardb/sugardb/config.go:176.32,177.17 1 0 -github.com/echovault/sugardb/sugardb/config.go:177.17,179.4 1 0 -github.com/echovault/sugardb/sugardb/config.go:179.9,181.4 1 0 -github.com/echovault/sugardb/sugardb/config.go:188.61,189.32 1 0 -github.com/echovault/sugardb/sugardb/config.go:189.32,191.3 1 0 -github.com/echovault/sugardb/sugardb/config.go:197.59,198.32 1 0 -github.com/echovault/sugardb/sugardb/config.go:198.32,199.17 1 0 -github.com/echovault/sugardb/sugardb/config.go:199.17,201.4 1 0 -github.com/echovault/sugardb/sugardb/config.go:201.9,203.4 1 0 -github.com/echovault/sugardb/sugardb/config.go:210.56,211.32 1 0 -github.com/echovault/sugardb/sugardb/config.go:211.32,212.17 1 0 -github.com/echovault/sugardb/sugardb/config.go:212.17,214.4 1 0 -github.com/echovault/sugardb/sugardb/config.go:214.9,216.4 1 0 -github.com/echovault/sugardb/sugardb/config.go:223.59,224.32 1 0 -github.com/echovault/sugardb/sugardb/config.go:224.32,226.3 1 0 -github.com/echovault/sugardb/sugardb/config.go:232.77,233.32 1 0 -github.com/echovault/sugardb/sugardb/config.go:233.32,235.3 1 0 -github.com/echovault/sugardb/sugardb/config.go:241.82,242.32 1 0 -github.com/echovault/sugardb/sugardb/config.go:242.32,244.3 1 0 -github.com/echovault/sugardb/sugardb/config.go:250.60,251.32 1 0 -github.com/echovault/sugardb/sugardb/config.go:251.32,252.17 1 0 -github.com/echovault/sugardb/sugardb/config.go:252.17,254.4 1 0 -github.com/echovault/sugardb/sugardb/config.go:254.9,256.4 1 0 -github.com/echovault/sugardb/sugardb/config.go:263.55,264.32 1 0 -github.com/echovault/sugardb/sugardb/config.go:264.32,265.17 1 0 -github.com/echovault/sugardb/sugardb/config.go:265.17,267.4 1 0 -github.com/echovault/sugardb/sugardb/config.go:267.9,269.4 1 0 -github.com/echovault/sugardb/sugardb/config.go:276.73,277.32 1 0 -github.com/echovault/sugardb/sugardb/config.go:277.32,279.3 1 0 -github.com/echovault/sugardb/sugardb/config.go:285.61,286.32 1 0 -github.com/echovault/sugardb/sugardb/config.go:286.32,288.3 1 0 -github.com/echovault/sugardb/sugardb/config.go:294.71,295.32 1 0 -github.com/echovault/sugardb/sugardb/config.go:295.32,297.3 1 0 -github.com/echovault/sugardb/sugardb/config.go:303.69,304.32 1 0 -github.com/echovault/sugardb/sugardb/config.go:304.32,306.3 1 0 -github.com/echovault/sugardb/sugardb/config.go:312.82,313.32 1 0 -github.com/echovault/sugardb/sugardb/config.go:313.32,315.3 1 0 -github.com/echovault/sugardb/sugardb/config.go:321.59,322.32 1 0 -github.com/echovault/sugardb/sugardb/config.go:322.32,324.3 1 0 -github.com/echovault/sugardb/sugardb/config.go:330.69,331.32 1 0 -github.com/echovault/sugardb/sugardb/config.go:331.32,333.3 1 0 -github.com/echovault/sugardb/sugardb/config.go:339.67,340.32 1 0 -github.com/echovault/sugardb/sugardb/config.go:340.32,342.3 1 0 -github.com/echovault/sugardb/sugardb/config.go:348.67,349.32 1 0 -github.com/echovault/sugardb/sugardb/config.go:349.32,351.3 1 0 +github.com/echovault/sugardb/sugardb/config.go:28.36,30.2 1 1 +github.com/echovault/sugardb/sugardb/config.go:32.60,37.23 1 1 +github.com/echovault/sugardb/sugardb/config.go:37.23,38.28 1 1 +github.com/echovault/sugardb/sugardb/config.go:38.28,40.5 1 1 +github.com/echovault/sugardb/sugardb/config.go:41.4,41.23 1 1 +github.com/echovault/sugardb/sugardb/config.go:43.23,44.29 1 1 +github.com/echovault/sugardb/sugardb/config.go:44.29,46.5 1 1 +github.com/echovault/sugardb/sugardb/config.go:47.4,47.34 1 1 +github.com/echovault/sugardb/sugardb/config.go:47.34,49.5 1 1 +github.com/echovault/sugardb/sugardb/config.go:50.4,50.20 1 1 +github.com/echovault/sugardb/sugardb/config.go:61.48,62.32 1 0 +github.com/echovault/sugardb/sugardb/config.go:62.32,63.17 1 0 +github.com/echovault/sugardb/sugardb/config.go:63.17,65.4 1 0 +github.com/echovault/sugardb/sugardb/config.go:65.9,67.4 1 0 +github.com/echovault/sugardb/sugardb/config.go:74.62,75.32 1 1 +github.com/echovault/sugardb/sugardb/config.go:75.32,77.3 1 1 +github.com/echovault/sugardb/sugardb/config.go:83.62,84.32 1 1 +github.com/echovault/sugardb/sugardb/config.go:84.32,86.3 1 1 +github.com/echovault/sugardb/sugardb/config.go:92.49,93.32 1 0 +github.com/echovault/sugardb/sugardb/config.go:93.32,94.17 1 0 +github.com/echovault/sugardb/sugardb/config.go:94.17,96.4 1 0 +github.com/echovault/sugardb/sugardb/config.go:96.9,98.4 1 0 +github.com/echovault/sugardb/sugardb/config.go:111.74,112.32 1 0 +github.com/echovault/sugardb/sugardb/config.go:112.32,113.37 1 0 +github.com/echovault/sugardb/sugardb/config.go:113.37,115.4 1 0 +github.com/echovault/sugardb/sugardb/config.go:122.63,123.32 1 0 +github.com/echovault/sugardb/sugardb/config.go:123.32,125.3 1 0 +github.com/echovault/sugardb/sugardb/config.go:131.51,132.32 1 0 +github.com/echovault/sugardb/sugardb/config.go:132.32,134.3 1 0 +github.com/echovault/sugardb/sugardb/config.go:140.59,141.32 1 0 +github.com/echovault/sugardb/sugardb/config.go:141.32,143.3 1 0 +github.com/echovault/sugardb/sugardb/config.go:149.59,150.32 1 0 +github.com/echovault/sugardb/sugardb/config.go:150.32,152.3 1 0 +github.com/echovault/sugardb/sugardb/config.go:158.59,159.32 1 0 +github.com/echovault/sugardb/sugardb/config.go:159.32,161.3 1 0 +github.com/echovault/sugardb/sugardb/config.go:167.57,168.32 1 0 +github.com/echovault/sugardb/sugardb/config.go:168.32,170.3 1 0 +github.com/echovault/sugardb/sugardb/config.go:176.61,177.32 1 0 +github.com/echovault/sugardb/sugardb/config.go:177.32,178.17 1 0 +github.com/echovault/sugardb/sugardb/config.go:178.17,180.4 1 0 +github.com/echovault/sugardb/sugardb/config.go:180.9,182.4 1 0 +github.com/echovault/sugardb/sugardb/config.go:189.61,190.32 1 0 +github.com/echovault/sugardb/sugardb/config.go:190.32,192.3 1 0 +github.com/echovault/sugardb/sugardb/config.go:198.59,199.32 1 0 +github.com/echovault/sugardb/sugardb/config.go:199.32,200.17 1 0 +github.com/echovault/sugardb/sugardb/config.go:200.17,202.4 1 0 +github.com/echovault/sugardb/sugardb/config.go:202.9,204.4 1 0 +github.com/echovault/sugardb/sugardb/config.go:211.56,212.32 1 0 +github.com/echovault/sugardb/sugardb/config.go:212.32,213.17 1 0 +github.com/echovault/sugardb/sugardb/config.go:213.17,215.4 1 0 +github.com/echovault/sugardb/sugardb/config.go:215.9,217.4 1 0 +github.com/echovault/sugardb/sugardb/config.go:224.59,225.32 1 0 +github.com/echovault/sugardb/sugardb/config.go:225.32,227.3 1 0 +github.com/echovault/sugardb/sugardb/config.go:233.77,234.32 1 0 +github.com/echovault/sugardb/sugardb/config.go:234.32,236.3 1 0 +github.com/echovault/sugardb/sugardb/config.go:242.82,243.32 1 0 +github.com/echovault/sugardb/sugardb/config.go:243.32,245.3 1 0 +github.com/echovault/sugardb/sugardb/config.go:251.60,252.32 1 0 +github.com/echovault/sugardb/sugardb/config.go:252.32,253.17 1 0 +github.com/echovault/sugardb/sugardb/config.go:253.17,255.4 1 0 +github.com/echovault/sugardb/sugardb/config.go:255.9,257.4 1 0 +github.com/echovault/sugardb/sugardb/config.go:264.55,265.32 1 0 +github.com/echovault/sugardb/sugardb/config.go:265.32,266.17 1 0 +github.com/echovault/sugardb/sugardb/config.go:266.17,268.4 1 0 +github.com/echovault/sugardb/sugardb/config.go:268.9,270.4 1 0 +github.com/echovault/sugardb/sugardb/config.go:277.73,278.32 1 0 +github.com/echovault/sugardb/sugardb/config.go:278.32,280.3 1 0 +github.com/echovault/sugardb/sugardb/config.go:286.61,287.32 1 0 +github.com/echovault/sugardb/sugardb/config.go:287.32,289.3 1 0 +github.com/echovault/sugardb/sugardb/config.go:295.71,296.32 1 0 +github.com/echovault/sugardb/sugardb/config.go:296.32,298.3 1 0 +github.com/echovault/sugardb/sugardb/config.go:304.69,305.32 1 0 +github.com/echovault/sugardb/sugardb/config.go:305.32,307.3 1 0 +github.com/echovault/sugardb/sugardb/config.go:313.82,314.32 1 0 +github.com/echovault/sugardb/sugardb/config.go:314.32,316.3 1 0 +github.com/echovault/sugardb/sugardb/config.go:322.80,323.32 1 0 +github.com/echovault/sugardb/sugardb/config.go:323.32,325.3 1 0 +github.com/echovault/sugardb/sugardb/config.go:331.82,332.32 1 0 +github.com/echovault/sugardb/sugardb/config.go:332.32,334.3 1 0 +github.com/echovault/sugardb/sugardb/config.go:340.76,341.32 1 0 +github.com/echovault/sugardb/sugardb/config.go:341.32,343.3 1 0 +github.com/echovault/sugardb/sugardb/config.go:349.59,350.32 1 0 +github.com/echovault/sugardb/sugardb/config.go:350.32,352.3 1 0 +github.com/echovault/sugardb/sugardb/config.go:358.69,359.32 1 0 +github.com/echovault/sugardb/sugardb/config.go:359.32,361.3 1 0 +github.com/echovault/sugardb/sugardb/config.go:367.67,368.32 1 0 +github.com/echovault/sugardb/sugardb/config.go:368.32,370.3 1 0 +github.com/echovault/sugardb/sugardb/config.go:376.67,377.32 1 0 +github.com/echovault/sugardb/sugardb/config.go:377.32,379.3 1 0 github.com/echovault/sugardb/sugardb/keyspace.go:42.58,44.28 1 0 github.com/echovault/sugardb/sugardb/keyspace.go:44.28,46.3 1 0 github.com/echovault/sugardb/sugardb/keyspace.go:49.2,50.55 2 0 diff --git a/sugardb/api_acl_test.go b/sugardb/api_acl_test.go index 4d70a06..b6a0c5e 100644 --- a/sugardb/api_acl_test.go +++ b/sugardb/api_acl_test.go @@ -124,539 +124,562 @@ func generateSHA256Password(plain string) string { return hex.EncodeToString(h.Sum(nil)) } -func TestSugarDB_ACLCat(t *testing.T) { - server := createSugarDB() +func TestSugarDB_ACL(t *testing.T) { + t.Run("TestSugarDB_ACLCat", func(t *testing.T) { + t.Parallel() - getCategoryCommands := func(category string) []string { - var commands []string - for _, command := range server.commands { - if slices.Contains(command.Categories, category) && (command.SubCommands == nil || len(command.SubCommands) == 0) { - commands = append(commands, strings.ToLower(command.Command)) - continue - } - for _, subcommand := range command.SubCommands { - if slices.Contains(subcommand.Categories, category) { - commands = append(commands, strings.ToLower(fmt.Sprintf("%s|%s", command.Command, subcommand.Command))) + server := createSugarDB() + t.Cleanup(func() { + server.ShutDown() + }) + + getCategoryCommands := func(category string) []string { + var commands []string + for _, command := range server.commands { + if slices.Contains(command.Categories, category) && (command.SubCommands == nil || len(command.SubCommands) == 0) { + commands = append(commands, strings.ToLower(command.Command)) + continue } + for _, subcommand := range command.SubCommands { + if slices.Contains(subcommand.Categories, category) { + commands = append(commands, strings.ToLower(fmt.Sprintf("%s|%s", command.Command, subcommand.Command))) + } + } + } + return commands + } + + tests := []struct { + name string + args []string + want []string + wantErr bool + }{ + { + name: "1. Get all ACL categories loaded on the server", + args: make([]string, 0), + want: []string{ + constants.AdminCategory, constants.ConnectionCategory, constants.DangerousCategory, + constants.HashCategory, constants.FastCategory, constants.KeyspaceCategory, constants.ListCategory, + constants.PubSubCategory, constants.ReadCategory, constants.WriteCategory, constants.SetCategory, + constants.SortedSetCategory, constants.SlowCategory, constants.StringCategory, + }, + wantErr: false, + }, + { + name: "2. Get all commands within the admin category", + args: []string{constants.AdminCategory}, + want: getCategoryCommands(constants.AdminCategory), + wantErr: false, + }, + { + name: "3. Get all commands within the connection category", + args: []string{constants.ConnectionCategory}, + want: getCategoryCommands(constants.ConnectionCategory), + wantErr: false, + }, + { + name: "4. Get all the commands within the dangerous category", + args: []string{constants.DangerousCategory}, + want: getCategoryCommands(constants.DangerousCategory), + wantErr: false, + }, + { + name: "5. Get all the commands within the hash category", + args: []string{constants.HashCategory}, + want: getCategoryCommands(constants.HashCategory), + wantErr: false, + }, + { + name: "6. Get all the commands within the fast category", + args: []string{constants.FastCategory}, + want: getCategoryCommands(constants.FastCategory), + wantErr: false, + }, + { + name: "7. Get all the commands within the keyspace category", + args: []string{constants.KeyspaceCategory}, + want: getCategoryCommands(constants.KeyspaceCategory), + wantErr: false, + }, + { + name: "8. Get all the commands within the list category", + args: []string{constants.ListCategory}, + want: getCategoryCommands(constants.ListCategory), + wantErr: false, + }, + { + name: "9. Get all the commands within the pubsub category", + args: []string{constants.PubSubCategory}, + want: getCategoryCommands(constants.PubSubCategory), + wantErr: false, + }, + { + name: "10. Get all the commands within the read category", + args: []string{constants.ReadCategory}, + want: getCategoryCommands(constants.ReadCategory), + wantErr: false, + }, + { + name: "11. Get all the commands within the write category", + args: []string{constants.WriteCategory}, + want: getCategoryCommands(constants.WriteCategory), + wantErr: false, + }, + { + name: "12. Get all the commands within the set category", + args: []string{constants.SetCategory}, + want: getCategoryCommands(constants.SetCategory), + wantErr: false, + }, + { + name: "13. Get all the commands within the sortedset category", + args: []string{constants.SortedSetCategory}, + want: getCategoryCommands(constants.SortedSetCategory), + wantErr: false, + }, + { + name: "14. Get all the commands within the slow category", + args: []string{constants.SlowCategory}, + want: getCategoryCommands(constants.SlowCategory), + wantErr: false, + }, + { + name: "15. Get all the commands within the string category", + args: []string{constants.StringCategory}, + want: getCategoryCommands(constants.StringCategory), + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := server.ACLCat(tt.args...) + if (err != nil) != tt.wantErr { + t.Errorf("ACLCat() error = %v, wantErr %v", err, tt.wantErr) + return + } + if len(got) != len(tt.want) { + t.Errorf("ACLCat() got length = %d, want length %d", len(got), len(tt.want)) + } + for _, item := range got { + if !slices.Contains(tt.want, item) { + t.Errorf("ACLCat() got unexpected element = %s, want %v", item, tt.want) + } + } + }) + } + }) + + t.Run("TestSugarDB_ACLUsers", func(t *testing.T) { + t.Parallel() + + server := createSugarDB() + t.Cleanup(func() { + server.ShutDown() + }) + + // Set Users + users := []User{ + { + Username: "user1", + Enabled: true, + NoPassword: true, + NoKeys: true, + NoCommands: true, + AddPlainPasswords: []string{}, + AddHashPasswords: []string{}, + IncludeCategories: []string{}, + IncludeReadWriteKeys: []string{}, + IncludeReadKeys: []string{}, + IncludeWriteKeys: []string{}, + IncludeChannels: []string{}, + ExcludeChannels: []string{}, + }, + { + Username: "user2", + Enabled: true, + NoPassword: false, + NoKeys: false, + NoCommands: false, + AddPlainPasswords: []string{"password1", "password2"}, + AddHashPasswords: []string{ + func() string { + h := sha256.New() + h.Write([]byte("password1")) + return string(h.Sum(nil)) + }(), + }, + IncludeCategories: []string{constants.FastCategory, constants.SlowCategory, constants.HashCategory}, + ExcludeCategories: []string{constants.AdminCategory, constants.DangerousCategory}, + IncludeCommands: []string{"*"}, + ExcludeCommands: []string{"acl|load", "acl|save"}, + IncludeReadWriteKeys: []string{"user2-profile-*"}, + IncludeReadKeys: []string{"user2-privileges-*"}, + IncludeWriteKeys: []string{"write-key"}, + IncludeChannels: []string{"posts-*"}, + ExcludeChannels: []string{"actions-*"}, + }, + } + + for _, user := range users { + ok, err := server.ACLSetUser(user) + if err != nil { + t.Errorf("ACLSetUser() err = %v", err) + } + if !ok { + t.Errorf("ACLSetUser() ok = %v", ok) } } - return commands - } - tests := []struct { - name string - args []string - want []string - wantErr bool - }{ - { - name: "1. Get all ACL categories loaded on the server", - args: make([]string, 0), - want: []string{ - constants.AdminCategory, constants.ConnectionCategory, constants.DangerousCategory, - constants.HashCategory, constants.FastCategory, constants.KeyspaceCategory, constants.ListCategory, - constants.PubSubCategory, constants.ReadCategory, constants.WriteCategory, constants.SetCategory, - constants.SortedSetCategory, constants.SlowCategory, constants.StringCategory, - }, - wantErr: false, - }, - { - name: "2. Get all commands within the admin category", - args: []string{constants.AdminCategory}, - want: getCategoryCommands(constants.AdminCategory), - wantErr: false, - }, - { - name: "3. Get all commands within the connection category", - args: []string{constants.ConnectionCategory}, - want: getCategoryCommands(constants.ConnectionCategory), - wantErr: false, - }, - { - name: "4. Get all the commands within the dangerous category", - args: []string{constants.DangerousCategory}, - want: getCategoryCommands(constants.DangerousCategory), - wantErr: false, - }, - { - name: "5. Get all the commands within the hash category", - args: []string{constants.HashCategory}, - want: getCategoryCommands(constants.HashCategory), - wantErr: false, - }, - { - name: "6. Get all the commands within the fast category", - args: []string{constants.FastCategory}, - want: getCategoryCommands(constants.FastCategory), - wantErr: false, - }, - { - name: "7. Get all the commands within the keyspace category", - args: []string{constants.KeyspaceCategory}, - want: getCategoryCommands(constants.KeyspaceCategory), - wantErr: false, - }, - { - name: "8. Get all the commands within the list category", - args: []string{constants.ListCategory}, - want: getCategoryCommands(constants.ListCategory), - wantErr: false, - }, - { - name: "9. Get all the commands within the pubsub category", - args: []string{constants.PubSubCategory}, - want: getCategoryCommands(constants.PubSubCategory), - wantErr: false, - }, - { - name: "10. Get all the commands within the read category", - args: []string{constants.ReadCategory}, - want: getCategoryCommands(constants.ReadCategory), - wantErr: false, - }, - { - name: "11. Get all the commands within the write category", - args: []string{constants.WriteCategory}, - want: getCategoryCommands(constants.WriteCategory), - wantErr: false, - }, - { - name: "12. Get all the commands within the set category", - args: []string{constants.SetCategory}, - want: getCategoryCommands(constants.SetCategory), - wantErr: false, - }, - { - name: "13. Get all the commands within the sortedset category", - args: []string{constants.SortedSetCategory}, - want: getCategoryCommands(constants.SortedSetCategory), - wantErr: false, - }, - { - name: "14. Get all the commands within the slow category", - args: []string{constants.SlowCategory}, - want: getCategoryCommands(constants.SlowCategory), - wantErr: false, - }, - { - name: "15. Get all the commands within the string category", - args: []string{constants.StringCategory}, - want: getCategoryCommands(constants.StringCategory), - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := server.ACLCat(tt.args...) - if (err != nil) != tt.wantErr { - t.Errorf("ACLCat() error = %v, wantErr %v", err, tt.wantErr) - return - } - if len(got) != len(tt.want) { - t.Errorf("ACLCat() got length = %d, want length %d", len(got), len(tt.want)) - } - for _, item := range got { - if !slices.Contains(tt.want, item) { - t.Errorf("ACLCat() got unexpected element = %s, want %v", item, tt.want) - } - } - }) - } -} - -func TestSugarDB_ACLUsers(t *testing.T) { - server := createSugarDB() - - // Set Users - users := []User{ - { - Username: "user1", - Enabled: true, - NoPassword: true, - NoKeys: true, - NoCommands: true, - AddPlainPasswords: []string{}, - AddHashPasswords: []string{}, - IncludeCategories: []string{}, - IncludeReadWriteKeys: []string{}, - IncludeReadKeys: []string{}, - IncludeWriteKeys: []string{}, - IncludeChannels: []string{}, - ExcludeChannels: []string{}, - }, - { - Username: "user2", - Enabled: true, - NoPassword: false, - NoKeys: false, - NoCommands: false, - AddPlainPasswords: []string{"password1", "password2"}, - AddHashPasswords: []string{ - func() string { - h := sha256.New() - h.Write([]byte("password1")) - return string(h.Sum(nil)) - }(), - }, - IncludeCategories: []string{constants.FastCategory, constants.SlowCategory, constants.HashCategory}, - ExcludeCategories: []string{constants.AdminCategory, constants.DangerousCategory}, - IncludeCommands: []string{"*"}, - ExcludeCommands: []string{"acl|load", "acl|save"}, - IncludeReadWriteKeys: []string{"user2-profile-*"}, - IncludeReadKeys: []string{"user2-privileges-*"}, - IncludeWriteKeys: []string{"write-key"}, - IncludeChannels: []string{"posts-*"}, - ExcludeChannels: []string{"actions-*"}, - }, - } - - for _, user := range users { - ok, err := server.ACLSetUser(user) + // Get users + aclUsers, err := server.ACLUsers() if err != nil { - t.Errorf("ACLSetUser() err = %v", err) + t.Errorf("ACLUsers() err = %v", err) + } + if len(aclUsers) != len(users)+1 { + t.Errorf("ACLUsers() got length %d, want %d", len(aclUsers), len(users)+1) + } + for _, username := range aclUsers { + if !slices.Contains([]string{"default", "user1", "user2"}, username) { + t.Errorf("ACLUsers() unexpected username = %s", username) + } + } + + // Get specific user. + user, err := server.ACLGetUser("user2") + if err != nil { + t.Errorf("ACLGetUser() err = %v", err) + } + if user == nil { + t.Errorf("ACLGetUser() user is nil") + } + + // Delete user + ok, err := server.ACLDelUser("user1") + if err != nil { + t.Errorf("ACLDelUser() err = %v", err) } if !ok { - t.Errorf("ACLSetUser() ok = %v", ok) + t.Errorf("ACLDelUser() could not delete user user1") } - } - - // Get users - aclUsers, err := server.ACLUsers() - if err != nil { - t.Errorf("ACLUsers() err = %v", err) - } - if len(aclUsers) != len(users)+1 { - t.Errorf("ACLUsers() got length %d, want %d", len(aclUsers), len(users)+1) - } - for _, username := range aclUsers { - if !slices.Contains([]string{"default", "user1", "user2"}, username) { - t.Errorf("ACLUsers() unexpected username = %s", username) + aclUsers, err = server.ACLUsers() + if err != nil { + t.Errorf("ACLDelUser() err = %v", err) } - } - - // Get specific user. - user, err := server.ACLGetUser("user2") - if err != nil { - t.Errorf("ACLGetUser() err = %v", err) - } - if user == nil { - t.Errorf("ACLGetUser() user is nil") - } - - // Delete user - ok, err := server.ACLDelUser("user1") - if err != nil { - t.Errorf("ACLDelUser() err = %v", err) - } - if !ok { - t.Errorf("ACLDelUser() could not delete user user1") - } - aclUsers, err = server.ACLUsers() - if err != nil { - t.Errorf("ACLDelUser() err = %v", err) - } - if slices.Contains(aclUsers, "user1") { - t.Errorf("ACLDelUser() unexpected username user1") - } - - // Get list of currently loaded ACL rules. - list, err := server.ACLList() - if err != nil { - t.Errorf("ACLList() err = %v", err) - } - if len(list) != 2 { - t.Errorf("ACLList() got list length %d, want %d", len(list), 2) - } -} - -func TestSugarDB_ACLConfig(t *testing.T) { - t.Run("Test_HandleSave", func(t *testing.T) { - baseDir := path.Join(".", "testdata", "save") - t.Cleanup(func() { - _ = os.RemoveAll(baseDir) - }) - - tests := []struct { - name string - path string - want []string // Response from ACL List command. - }{ - { - name: "1. Save ACL config to .json file", - path: path.Join(baseDir, "json_test.json"), - want: []string{ - "default on +@all +all %RW~* +&*", - fmt.Sprintf("with_password_user on >password2 #%s +@all +all %s~* +&*", - generateSHA256Password("password3"), "%RW"), - "no_password_user on nopass +@all +all %RW~* +&*", - "disabled_user off >password5 +@all +all %RW~* +&*", - }, - }, - { - name: "2. Save ACL config to .yaml file", - path: path.Join(baseDir, "yaml_test.yaml"), - want: []string{ - "default on +@all +all %RW~* +&*", - fmt.Sprintf("with_password_user on >password2 #%s +@all +all %s~* +&*", - generateSHA256Password("password3"), "%RW"), - "no_password_user on nopass +@all +all %RW~* +&*", - "disabled_user off >password5 +@all +all %RW~* +&*", - }, - }, - { - name: "3. Save ACL config to .yml file", - path: path.Join(baseDir, "yml_test.yml"), - want: []string{ - "default on +@all +all %RW~* +&*", - fmt.Sprintf("with_password_user on >password2 #%s +@all +all %s~* +&*", - generateSHA256Password("password3"), "%RW"), - "no_password_user on nopass +@all +all %RW~* +&*", - "disabled_user off >password5 +@all +all %RW~* +&*", - }, - }, + if slices.Contains(aclUsers, "user1") { + t.Errorf("ACLDelUser() unexpected username user1") } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - // Create new server instance - conf := DefaultConfig() - conf.DataDir = "" - conf.AclConfig = test.path - server := createSugarDBWithConfig(conf) - // Add the initial test users to the ACL module. - for _, user := range generateInitialTestUsers() { - if _, err := server.ACLSetUser(user); err != nil { - t.Error(err) - return - } - } - - ok, err := server.ACLSave() - if err != nil { - t.Error(err) - return - } - if !ok { - t.Errorf("expected ok to be true, got false") - } - - // Shutdown the mock server - server.ShutDown() - - // Restart server - server = createSugarDBWithConfig(conf) - - // Get users rules list. - list, err := server.ACLList() - - // Check if ACL LIST returns the expected list of users. - var resStr []string - for i := 0; i < len(list); i++ { - resStr = strings.Split(list[i], " ") - if !slices.ContainsFunc(test.want, func(s string) bool { - expectedUserSlice := strings.Split(s, " ") - return compareSlices(resStr, expectedUserSlice) == nil - }) { - t.Errorf("could not find the following user in expected slice: %+v", resStr) - return - } - } - }) + // Get list of currently loaded ACL rules. + list, err := server.ACLList() + if err != nil { + t.Errorf("ACLList() err = %v", err) + } + if len(list) != 2 { + t.Errorf("ACLList() got list length %d, want %d", len(list), 2) } }) - t.Run("Test_HandleLoad", func(t *testing.T) { - baseDir := path.Join(".", "testdata", "load") - t.Cleanup(func() { - _ = os.RemoveAll(baseDir) + t.Run("TestSugarDB_ACLConfig", func(t *testing.T) { + t.Parallel() + + t.Run("Test_HandleSave", func(t *testing.T) { + baseDir := path.Join(".", "testdata", "save") + t.Cleanup(func() { + _ = os.RemoveAll(baseDir) + }) + + tests := []struct { + name string + path string + want []string // Response from ACL List command. + }{ + { + name: "1. Save ACL config to .json file", + path: path.Join(baseDir, "json_test.json"), + want: []string{ + "default on +@all +all %RW~* +&*", + fmt.Sprintf("with_password_user on >password2 #%s +@all +all %s~* +&*", + generateSHA256Password("password3"), "%RW"), + "no_password_user on nopass +@all +all %RW~* +&*", + "disabled_user off >password5 +@all +all %RW~* +&*", + }, + }, + { + name: "2. Save ACL config to .yaml file", + path: path.Join(baseDir, "yaml_test.yaml"), + want: []string{ + "default on +@all +all %RW~* +&*", + fmt.Sprintf("with_password_user on >password2 #%s +@all +all %s~* +&*", + generateSHA256Password("password3"), "%RW"), + "no_password_user on nopass +@all +all %RW~* +&*", + "disabled_user off >password5 +@all +all %RW~* +&*", + }, + }, + { + name: "3. Save ACL config to .yml file", + path: path.Join(baseDir, "yml_test.yml"), + want: []string{ + "default on +@all +all %RW~* +&*", + fmt.Sprintf("with_password_user on >password2 #%s +@all +all %s~* +&*", + generateSHA256Password("password3"), "%RW"), + "no_password_user on nopass +@all +all %RW~* +&*", + "disabled_user off >password5 +@all +all %RW~* +&*", + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + // Create new server instance + conf := DefaultConfig() + conf.DataDir = "" + conf.AclConfig = test.path + + server := createSugarDBWithConfig(conf) + + // Add the initial test users to the ACL module. + for _, user := range generateInitialTestUsers() { + if _, err := server.ACLSetUser(user); err != nil { + t.Error(err) + return + } + } + + ok, err := server.ACLSave() + if err != nil { + t.Error(err) + return + } + if !ok { + t.Errorf("expected ok to be true, got false") + } + + // Shutdown the mock server + server.ShutDown() + + // Restart server + server = createSugarDBWithConfig(conf) + + // Get users rules list. + list, err := server.ACLList() + + // Check if ACL LIST returns the expected list of users. + var resStr []string + for i := 0; i < len(list); i++ { + resStr = strings.Split(list[i], " ") + if !slices.ContainsFunc(test.want, func(s string) bool { + expectedUserSlice := strings.Split(s, " ") + return compareSlices(resStr, expectedUserSlice) == nil + }) { + t.Errorf("could not find the following user in expected slice: %+v", resStr) + return + } + } + }) + } }) - tests := []struct { - name string - path string - users []User // Add users after server startup. - loadFunc func(server *SugarDB) (bool, error) // Function to load users from ACL config. - want []string - }{ - { - name: "1. Load config from the .json file", - path: path.Join(baseDir, "json_test.json"), - users: []User{ - {Username: "user1", Enabled: true}, - }, - loadFunc: func(server *SugarDB) (bool, error) { - return server.ACLLoad(ACLLoadOptions{}) - }, - want: []string{ - "default on +@all +all %RW~* +&*", - fmt.Sprintf("with_password_user on >password2 #%s +@all +all %s~* +&*", - generateSHA256Password("password3"), "%RW"), - "no_password_user on nopass +@all +all %RW~* +&*", - "disabled_user off >password5 +@all +all %RW~* +&*", - "user1 on +@all +all %RW~* +&*", - }, - }, - { - name: "2. Load users from the .yaml file", - path: path.Join(baseDir, "yaml_test.yaml"), - users: []User{ - {Username: "user1", Enabled: true}, - }, - loadFunc: func(server *SugarDB) (bool, error) { - return server.ACLLoad(ACLLoadOptions{}) - }, - want: []string{ - "default on +@all +all %RW~* +&*", - fmt.Sprintf("with_password_user on >password2 #%s +@all +all %s~* +&*", - generateSHA256Password("password3"), "%RW"), - "no_password_user on nopass +@all +all %RW~* +&*", - "disabled_user off >password5 +@all +all %RW~* +&*", - "user1 on +@all +all %RW~* +&*", - }, - }, - { - name: "3. Load users from the .yml file", - path: path.Join(baseDir, "yml_test.yml"), - users: []User{ - {Username: "user1", Enabled: true}, - }, - loadFunc: func(server *SugarDB) (bool, error) { - return server.ACLLoad(ACLLoadOptions{}) - }, - want: []string{ - "default on +@all +all %RW~* +&*", - fmt.Sprintf("with_password_user on >password2 #%s +@all +all %s~* +&*", - generateSHA256Password("password3"), "%RW"), - "no_password_user on nopass +@all +all %RW~* +&*", - "disabled_user off >password5 +@all +all %RW~* +&*", - "user1 on +@all +all %RW~* +&*", - }, - }, - { - name: "4. Merge loaded users", - path: path.Join(baseDir, "merge.yml"), - users: []User{ - { // Disable user1. - Username: "user1", - Enabled: false, - }, - { // Update with_password_user. This should be merged with the existing user. - Username: "with_password_user", - AddPlainPasswords: []string{"password3", "password4"}, - IncludeReadWriteKeys: []string{"key1", "key2"}, - IncludeWriteKeys: []string{"key3", "key4"}, - IncludeReadKeys: []string{"key5", "key6"}, - IncludeChannels: []string{"channel[12]"}, - ExcludeChannels: []string{"channel[34]"}, - }, - }, - loadFunc: func(server *SugarDB) (bool, error) { - return server.ACLLoad(ACLLoadOptions{Merge: true, Replace: false}) - }, - want: []string{ - "default on +@all +all %RW~* +&*", - fmt.Sprintf(`with_password_user on >password2 >password3 >password4 #%s +@all +all %s~key1 %s~key2 %s~key5 %s~key6 %s~key3 %s~key4 +&channel[12] -&channel[34]`, - generateSHA256Password("password3"), "%RW", "%RW", "%R", "%R", "%W", "%W"), - "no_password_user on nopass +@all +all %RW~* +&*", - "disabled_user off >password5 +@all +all %RW~* +&*", - "user1 off +@all +all %RW~* +&*", - }, - }, - { - name: "5. Replace loaded users", - path: path.Join(baseDir, "replace.yml"), - users: []User{ - { // Disable user1. - Username: "user1", - Enabled: false, - }, - { // Update with_password_user. This should be merged with the existing user. - Username: "with_password_user", - AddPlainPasswords: []string{"password3", "password4"}, - IncludeReadWriteKeys: []string{"key1", "key2"}, - IncludeWriteKeys: []string{"key3", "key4"}, - IncludeReadKeys: []string{"key5", "key6"}, - IncludeChannels: []string{"channel[12]"}, - ExcludeChannels: []string{"channel[34]"}, - }, - }, - loadFunc: func(server *SugarDB) (bool, error) { - return server.ACLLoad(ACLLoadOptions{Replace: true, Merge: false}) - }, - want: []string{ - "default on +@all +all %RW~* +&*", - fmt.Sprintf("with_password_user on >password2 #%s +@all +all %s~* +&*", - generateSHA256Password("password3"), "%RW"), - "no_password_user on nopass +@all +all %RW~* +&*", - "disabled_user off >password5 +@all +all %RW~* +&*", - "user1 off +@all +all %RW~* +&*", - }, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - // Create server. - conf := DefaultConfig() - conf.DataDir = "" - conf.AclConfig = test.path - server := createSugarDBWithConfig(conf) - // Add the initial test users to the ACL module. - for _, user := range generateInitialTestUsers() { - if _, err := server.ACLSetUser(user); err != nil { - t.Error(err) - return - } - } - - // Save the current users to the ACL config file. - if _, err := server.ACLSave(); err != nil { - t.Error(err) - return - } - - ticker := time.NewTicker(200 * time.Millisecond) - <-ticker.C - - // Add some users to the ACL. - for _, user := range test.users { - if _, err := server.ACLSetUser(user); err != nil { - t.Error(err) - return - } - } - - // Load the users from the ACL config file. - ok, err := test.loadFunc(server) - if err != nil { - t.Error(err) - return - } - if !ok { - t.Errorf("expected ok to be true, got false") - return - } - - // Get ACL List - list, err := server.ACLList() - if err != nil { - t.Error(err) - return - } - - // Check if ACL LIST returns the expected list of users. - var resStr []string - for i := 0; i < len(list); i++ { - resStr = strings.Split(list[i], " ") - if !slices.ContainsFunc(test.want, func(s string) bool { - expectedUserSlice := strings.Split(s, " ") - return compareSlices(resStr, expectedUserSlice) == nil - }) { - t.Errorf("could not find the following user in expected slice: %+v", resStr) - return - } - } + t.Run("Test_HandleLoad", func(t *testing.T) { + baseDir := path.Join(".", "testdata", "load") + t.Cleanup(func() { + _ = os.RemoveAll(baseDir) }) - } + + tests := []struct { + name string + path string + users []User // Add users after server startup. + loadFunc func(server *SugarDB) (bool, error) // Function to load users from ACL config. + want []string + }{ + { + name: "1. Load config from the .json file", + path: path.Join(baseDir, "json_test.json"), + users: []User{ + {Username: "user1", Enabled: true}, + }, + loadFunc: func(server *SugarDB) (bool, error) { + return server.ACLLoad(ACLLoadOptions{}) + }, + want: []string{ + "default on +@all +all %RW~* +&*", + fmt.Sprintf("with_password_user on >password2 #%s +@all +all %s~* +&*", + generateSHA256Password("password3"), "%RW"), + "no_password_user on nopass +@all +all %RW~* +&*", + "disabled_user off >password5 +@all +all %RW~* +&*", + "user1 on +@all +all %RW~* +&*", + }, + }, + { + name: "2. Load users from the .yaml file", + path: path.Join(baseDir, "yaml_test.yaml"), + users: []User{ + {Username: "user1", Enabled: true}, + }, + loadFunc: func(server *SugarDB) (bool, error) { + return server.ACLLoad(ACLLoadOptions{}) + }, + want: []string{ + "default on +@all +all %RW~* +&*", + fmt.Sprintf("with_password_user on >password2 #%s +@all +all %s~* +&*", + generateSHA256Password("password3"), "%RW"), + "no_password_user on nopass +@all +all %RW~* +&*", + "disabled_user off >password5 +@all +all %RW~* +&*", + "user1 on +@all +all %RW~* +&*", + }, + }, + { + name: "3. Load users from the .yml file", + path: path.Join(baseDir, "yml_test.yml"), + users: []User{ + {Username: "user1", Enabled: true}, + }, + loadFunc: func(server *SugarDB) (bool, error) { + return server.ACLLoad(ACLLoadOptions{}) + }, + want: []string{ + "default on +@all +all %RW~* +&*", + fmt.Sprintf("with_password_user on >password2 #%s +@all +all %s~* +&*", + generateSHA256Password("password3"), "%RW"), + "no_password_user on nopass +@all +all %RW~* +&*", + "disabled_user off >password5 +@all +all %RW~* +&*", + "user1 on +@all +all %RW~* +&*", + }, + }, + { + name: "4. Merge loaded users", + path: path.Join(baseDir, "merge.yml"), + users: []User{ + { // Disable user1. + Username: "user1", + Enabled: false, + }, + { // Update with_password_user. This should be merged with the existing user. + Username: "with_password_user", + AddPlainPasswords: []string{"password3", "password4"}, + IncludeReadWriteKeys: []string{"key1", "key2"}, + IncludeWriteKeys: []string{"key3", "key4"}, + IncludeReadKeys: []string{"key5", "key6"}, + IncludeChannels: []string{"channel[12]"}, + ExcludeChannels: []string{"channel[34]"}, + }, + }, + loadFunc: func(server *SugarDB) (bool, error) { + return server.ACLLoad(ACLLoadOptions{Merge: true, Replace: false}) + }, + want: []string{ + "default on +@all +all %RW~* +&*", + fmt.Sprintf(`with_password_user on >password2 >password3 >password4 #%s +@all +all %s~key1 %s~key2 %s~key5 %s~key6 %s~key3 %s~key4 +&channel[12] -&channel[34]`, + generateSHA256Password("password3"), "%RW", "%RW", "%R", "%R", "%W", "%W"), + "no_password_user on nopass +@all +all %RW~* +&*", + "disabled_user off >password5 +@all +all %RW~* +&*", + "user1 off +@all +all %RW~* +&*", + }, + }, + { + name: "5. Replace loaded users", + path: path.Join(baseDir, "replace.yml"), + users: []User{ + { // Disable user1. + Username: "user1", + Enabled: false, + }, + { // Update with_password_user. This should be merged with the existing user. + Username: "with_password_user", + AddPlainPasswords: []string{"password3", "password4"}, + IncludeReadWriteKeys: []string{"key1", "key2"}, + IncludeWriteKeys: []string{"key3", "key4"}, + IncludeReadKeys: []string{"key5", "key6"}, + IncludeChannels: []string{"channel[12]"}, + ExcludeChannels: []string{"channel[34]"}, + }, + }, + loadFunc: func(server *SugarDB) (bool, error) { + return server.ACLLoad(ACLLoadOptions{Replace: true, Merge: false}) + }, + want: []string{ + "default on +@all +all %RW~* +&*", + fmt.Sprintf("with_password_user on >password2 #%s +@all +all %s~* +&*", + generateSHA256Password("password3"), "%RW"), + "no_password_user on nopass +@all +all %RW~* +&*", + "disabled_user off >password5 +@all +all %RW~* +&*", + "user1 off +@all +all %RW~* +&*", + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + // Create server. + conf := DefaultConfig() + conf.DataDir = "" + conf.AclConfig = test.path + + server := createSugarDBWithConfig(conf) + + // Add the initial test users to the ACL module. + for _, user := range generateInitialTestUsers() { + if _, err := server.ACLSetUser(user); err != nil { + t.Error(err) + return + } + } + + // Save the current users to the ACL config file. + if _, err := server.ACLSave(); err != nil { + t.Error(err) + return + } + + ticker := time.NewTicker(100 * time.Millisecond) + <-ticker.C + ticker.Stop() + + // Add some users to the ACL. + for _, user := range test.users { + if _, err := server.ACLSetUser(user); err != nil { + t.Error(err) + return + } + } + + // Load the users from the ACL config file. + ok, err := test.loadFunc(server) + if err != nil { + t.Error(err) + return + } + if !ok { + t.Errorf("expected ok to be true, got false") + return + } + + // Get ACL List + list, err := server.ACLList() + if err != nil { + t.Error(err) + return + } + + // Check if ACL LIST returns the expected list of users. + var resStr []string + for i := 0; i < len(list); i++ { + resStr = strings.Split(list[i], " ") + if !slices.ContainsFunc(test.want, func(s string) bool { + expectedUserSlice := strings.Split(s, " ") + return compareSlices(resStr, expectedUserSlice) == nil + }) { + t.Errorf("could not find the following user in expected slice: %+v", resStr) + return + } + } + }) + } + }) }) } diff --git a/sugardb/api_admin_test.go b/sugardb/api_admin_test.go index 381028f..0588405 100644 --- a/sugardb/api_admin_test.go +++ b/sugardb/api_admin_test.go @@ -31,653 +31,706 @@ import ( "time" ) -func TestSugarDB_AddCommand(t *testing.T) { - type args struct { - command CommandOptions - } - type scenarios struct { - name string - command []string - wantRes int - wantErr error - } - tests := []struct { - name string - args args - scenarios []scenarios - wantErr bool - }{ - { - name: "1 Add command without subcommands", - wantErr: false, - args: args{ - command: CommandOptions{ - Command: "CommandOne", - Module: "test-module", - Description: `(CommandOne write-key read-key ) +func TestSugarDB_Admin(t *testing.T) { + t.Run("TestSugarDB_AddCommand", func(t *testing.T) { + t.Parallel() + + type args struct { + command CommandOptions + } + type scenarios struct { + name string + command []string + wantRes int + wantErr error + } + tests := []struct { + name string + args args + scenarios []scenarios + wantErr bool + }{ + { + name: "1 Add command without subcommands", + wantErr: false, + args: args{ + command: CommandOptions{ + Command: "CommandOne", + Module: "test-module", + Description: `(CommandOne write-key read-key ) Test command to handle successful addition of a single command without subcommands. The value passed must be an integer.`, - Categories: []string{}, - Sync: false, - KeyExtractionFunc: func(cmd []string) (CommandKeyExtractionFuncResult, error) { - if len(cmd) != 4 { - return CommandKeyExtractionFuncResult{}, errors.New(constants.WrongArgsResponse) - } - return CommandKeyExtractionFuncResult{ - WriteKeys: cmd[1:2], - ReadKeys: cmd[2:3], - }, nil + Categories: []string{}, + Sync: false, + KeyExtractionFunc: func(cmd []string) (CommandKeyExtractionFuncResult, error) { + if len(cmd) != 4 { + return CommandKeyExtractionFuncResult{}, errors.New(constants.WrongArgsResponse) + } + return CommandKeyExtractionFuncResult{ + WriteKeys: cmd[1:2], + ReadKeys: cmd[2:3], + }, nil + }, + HandlerFunc: func(params CommandHandlerFuncParams) ([]byte, error) { + if len(params.Command) != 4 { + return nil, errors.New(constants.WrongArgsResponse) + } + value := params.Command[3] + i, err := strconv.ParseInt(value, 10, 64) + if err != nil { + return nil, errors.New("value must be an integer") + } + return []byte(fmt.Sprintf(":%d\r\n", i)), nil + }, }, - HandlerFunc: func(params CommandHandlerFuncParams) ([]byte, error) { - if len(params.Command) != 4 { - return nil, errors.New(constants.WrongArgsResponse) - } - value := params.Command[3] - i, err := strconv.ParseInt(value, 10, 64) - if err != nil { - return nil, errors.New("value must be an integer") - } - return []byte(fmt.Sprintf(":%d\r\n", i)), nil + }, + scenarios: []scenarios{ + { + name: "1 Successfully execute the command and return the expected integer.", + command: []string{"CommandOne", "write-key1", "read-key1", "1111"}, + wantRes: 1111, + wantErr: nil, + }, + { + name: "2 Get error due to command being too long", + command: []string{"CommandOne", "write-key1", "read-key1", "1111", "2222"}, + wantRes: 0, + wantErr: errors.New(constants.WrongArgsResponse), + }, + { + name: "3 Get error due to command being too short", + command: []string{"CommandOne", "write-key1", "read-key1"}, + wantRes: 0, + wantErr: errors.New(constants.WrongArgsResponse), + }, + { + name: "4 Get error due to value not being an integer", + command: []string{"CommandOne", "write-key1", "read-key1", "string"}, + wantRes: 0, + wantErr: errors.New("value must be an integer"), }, }, }, - scenarios: []scenarios{ - { - name: "1 Successfully execute the command and return the expected integer.", - command: []string{"CommandOne", "write-key1", "read-key1", "1111"}, - wantRes: 1111, - wantErr: nil, - }, - { - name: "2 Get error due to command being too long", - command: []string{"CommandOne", "write-key1", "read-key1", "1111", "2222"}, - wantRes: 0, - wantErr: errors.New(constants.WrongArgsResponse), - }, - { - name: "3 Get error due to command being too short", - command: []string{"CommandOne", "write-key1", "read-key1"}, - wantRes: 0, - wantErr: errors.New(constants.WrongArgsResponse), - }, - { - name: "4 Get error due to value not being an integer", - command: []string{"CommandOne", "write-key1", "read-key1", "string"}, - wantRes: 0, - wantErr: errors.New("value must be an integer"), - }, - }, - }, - { - name: "2 Add command with subcommands", - wantErr: false, - args: args{ - command: CommandOptions{ - Command: "CommandTwo", - SubCommand: []SubCommandOptions{ - { - Command: "SubCommandOne", - Module: "test-module", - Description: `(CommandTwo SubCommandOne write-key read-key ) + { + name: "2 Add command with subcommands", + wantErr: false, + args: args{ + command: CommandOptions{ + Command: "CommandTwo", + SubCommand: []SubCommandOptions{ + { + Command: "SubCommandOne", + Module: "test-module", + Description: `(CommandTwo SubCommandOne write-key read-key ) Test command to handle successful addition of a single command with subcommands. The value passed must be an integer.`, - Categories: []string{}, - Sync: false, - KeyExtractionFunc: func(cmd []string) (CommandKeyExtractionFuncResult, error) { - if len(cmd) != 5 { - return CommandKeyExtractionFuncResult{}, errors.New(constants.WrongArgsResponse) - } - return CommandKeyExtractionFuncResult{ - WriteKeys: cmd[2:3], - ReadKeys: cmd[3:4], - }, nil - }, - HandlerFunc: func(params CommandHandlerFuncParams) ([]byte, error) { - if len(params.Command) != 5 { - return nil, errors.New(constants.WrongArgsResponse) - } - value := params.Command[4] - i, err := strconv.ParseInt(value, 10, 64) - if err != nil { - return nil, errors.New("value must be an integer") - } - return []byte(fmt.Sprintf(":%d\r\n", i)), nil + Categories: []string{}, + Sync: false, + KeyExtractionFunc: func(cmd []string) (CommandKeyExtractionFuncResult, error) { + if len(cmd) != 5 { + return CommandKeyExtractionFuncResult{}, errors.New(constants.WrongArgsResponse) + } + return CommandKeyExtractionFuncResult{ + WriteKeys: cmd[2:3], + ReadKeys: cmd[3:4], + }, nil + }, + HandlerFunc: func(params CommandHandlerFuncParams) ([]byte, error) { + if len(params.Command) != 5 { + return nil, errors.New(constants.WrongArgsResponse) + } + value := params.Command[4] + i, err := strconv.ParseInt(value, 10, 64) + if err != nil { + return nil, errors.New("value must be an integer") + } + return []byte(fmt.Sprintf(":%d\r\n", i)), nil + }, }, }, }, }, - }, - scenarios: []scenarios{ - { - name: "1 Successfully execute the command and return the expected integer.", - command: []string{"CommandTwo", "SubCommandOne", "write-key1", "read-key1", "1111"}, - wantRes: 1111, - wantErr: nil, - }, - { - name: "2 Get error due to command being too long", - command: []string{"CommandTwo", "SubCommandOne", "write-key1", "read-key1", "1111", "2222"}, - wantRes: 0, - wantErr: errors.New(constants.WrongArgsResponse), - }, - { - name: "3 Get error due to command being too short", - command: []string{"CommandTwo", "SubCommandOne", "write-key1", "read-key1"}, - wantRes: 0, - wantErr: errors.New(constants.WrongArgsResponse), - }, - { - name: "4 Get error due to value not being an integer", - command: []string{"CommandTwo", "SubCommandOne", "write-key1", "read-key1", "string"}, - wantRes: 0, - wantErr: errors.New("value must be an integer"), + scenarios: []scenarios{ + { + name: "1 Successfully execute the command and return the expected integer.", + command: []string{"CommandTwo", "SubCommandOne", "write-key1", "read-key1", "1111"}, + wantRes: 1111, + wantErr: nil, + }, + { + name: "2 Get error due to command being too long", + command: []string{"CommandTwo", "SubCommandOne", "write-key1", "read-key1", "1111", "2222"}, + wantRes: 0, + wantErr: errors.New(constants.WrongArgsResponse), + }, + { + name: "3 Get error due to command being too short", + command: []string{"CommandTwo", "SubCommandOne", "write-key1", "read-key1"}, + wantRes: 0, + wantErr: errors.New(constants.WrongArgsResponse), + }, + { + name: "4 Get error due to value not being an integer", + command: []string{"CommandTwo", "SubCommandOne", "write-key1", "read-key1", "string"}, + wantRes: 0, + wantErr: errors.New("value must be an integer"), + }, }, }, - }, - } - for _, tt := range tests { - server := createSugarDB() - t.Run(tt.name, func(t *testing.T) { - if err := server.AddCommand(tt.args.command); (err != nil) != tt.wantErr { - t.Errorf("AddCommand() error = %v, wantErr %v", err, tt.wantErr) - } - for _, scenario := range tt.scenarios { - b, err := server.ExecuteCommand(scenario.command...) - if scenario.wantErr != nil { - if scenario.wantErr.Error() != err.Error() { - t.Errorf("AddCommand() error = %v, wantErr %v", err, scenario.wantErr) + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + server := createSugarDB() + t.Cleanup(func() { + server.ShutDown() + }) + + if err := server.AddCommand(tt.args.command); (err != nil) != tt.wantErr { + t.Errorf("AddCommand() error = %v, wantErr %v", err, tt.wantErr) + } + for _, scenario := range tt.scenarios { + b, err := server.ExecuteCommand(scenario.command...) + if scenario.wantErr != nil { + if scenario.wantErr.Error() != err.Error() { + t.Errorf("AddCommand() error = %v, wantErr %v", err, scenario.wantErr) + } + continue + } + r := resp.NewReader(bytes.NewReader(b)) + v, _, _ := r.ReadValue() + if v.Integer() != scenario.wantRes { + t.Errorf("AddCommand() res = %v, wantRes %v", resp.BytesValue(b).Integer(), scenario.wantRes) + } + } + }) + } + }) + + t.Run("TestSugarDB_ExecuteCommand", func(t *testing.T) { + t.Parallel() + + type args struct { + key string + presetValue []string + command []string + } + tests := []struct { + name string + args args + wantRes int + wantErr error + }{ + { + name: "1 Execute LPUSH command and get expected result", + args: args{ + key: "key1", + presetValue: []string{"1", "2", "3"}, + command: []string{"LPUSH", "key1", "4", "5", "6", "7", "8", "9", "10"}, + }, + wantRes: 10, + wantErr: nil, + }, + { + name: "2 Expect error when trying to execute non-existent command", + args: args{ + key: "key2", + presetValue: nil, + command: []string{"NON-EXISTENT", "key1", "key2"}, + }, + wantRes: 0, + wantErr: errors.New("command NON-EXISTENT not supported"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + server := createSugarDB() + t.Cleanup(func() { + server.ShutDown() + }) + if tt.args.presetValue != nil { + _, _ = server.LPush(tt.args.key, tt.args.presetValue...) + } + b, err := server.ExecuteCommand(tt.args.command...) + if tt.wantErr != nil { + if err.Error() != tt.wantErr.Error() { + t.Errorf("ExecuteCommand() error = %v, wantErr %v", err, tt.wantErr) } - continue } r := resp.NewReader(bytes.NewReader(b)) v, _, _ := r.ReadValue() - if v.Integer() != scenario.wantRes { - t.Errorf("AddCommand() res = %v, wantRes %v", resp.BytesValue(b).Integer(), scenario.wantRes) + if v.Integer() != tt.wantRes { + t.Errorf("ExecuteCommand() response = %d, wantRes %d", v.Integer(), tt.wantRes) } - } - }) - } -} - -func TestSugarDB_ExecuteCommand(t *testing.T) { - type args struct { - key string - presetValue []string - command []string - } - tests := []struct { - name string - args args - wantRes int - wantErr error - }{ - { - name: "1 Execute LPUSH command and get expected result", - args: args{ - key: "key1", - presetValue: []string{"1", "2", "3"}, - command: []string{"LPUSH", "key1", "4", "5", "6", "7", "8", "9", "10"}, - }, - wantRes: 10, - wantErr: nil, - }, - { - name: "2 Expect error when trying to execute non-existent command", - args: args{ - key: "key2", - presetValue: nil, - command: []string{"NON-EXISTENT", "key1", "key2"}, - }, - wantRes: 0, - wantErr: errors.New("command NON-EXISTENT not supported"), - }, - } - for _, tt := range tests { - server := createSugarDB() - t.Run(tt.name, func(t *testing.T) { - if tt.args.presetValue != nil { - _, _ = server.LPush(tt.args.key, tt.args.presetValue...) - } - b, err := server.ExecuteCommand(tt.args.command...) - if tt.wantErr != nil { - if err.Error() != tt.wantErr.Error() { - t.Errorf("ExecuteCommand() error = %v, wantErr %v", err, tt.wantErr) - } - } - r := resp.NewReader(bytes.NewReader(b)) - v, _, _ := r.ReadValue() - if v.Integer() != tt.wantRes { - t.Errorf("ExecuteCommand() response = %d, wantRes %d", v.Integer(), tt.wantRes) - } - }) - } -} - -func TestSugarDB_RemoveCommand(t *testing.T) { - type args struct { - removeCommand []string - executeCommand []string - } - tests := []struct { - name string - args args - wantErr error - }{ - { - name: "1 Remove command and expect error when the command is called", - args: args{ - removeCommand: []string{"LPUSH"}, - executeCommand: []string{"LPUSH", "key", "item"}, - }, - wantErr: errors.New("command LPUSH not supported"), - }, - { - name: "2 Remove sub-command and expect error when the subcommand is called", - args: args{ - removeCommand: []string{"ACL", "CAT"}, - executeCommand: []string{"ACL", "CAT"}, - }, - wantErr: errors.New("command ACL CAT not supported"), - }, - { - name: "3 Remove sub-command and expect successful response from calling another subcommand", - args: args{ - removeCommand: []string{"ACL", "WHOAMI"}, - executeCommand: []string{"ACL", "DELUSER", "user-one"}, - }, - wantErr: nil, - }, - } - for _, tt := range tests { - server := createSugarDB() - t.Run(tt.name, func(t *testing.T) { - server.RemoveCommand(tt.args.removeCommand...) - _, err := server.ExecuteCommand(tt.args.executeCommand...) - if tt.wantErr != nil { - if err.Error() != tt.wantErr.Error() { - t.Errorf("RemoveCommand() error = %v, wantErr %v", err, tt.wantErr) - } - } - }) - } -} - -func TestSugarDB_Plugins(t *testing.T) { - t.Cleanup(func() { - _ = os.RemoveAll("./testdata/modules") + }) + } }) - server := createSugarDB() + t.Run("TestSugarDB_RemoveCommand", func(t *testing.T) { + t.Parallel() - tests := []struct { - name string - path string - expect bool - args []string - cmd []string - want string - wantErr error - }{ - { - name: "1. Test shared object plugin MODULE.SET", - path: path.Join(".", "testdata", "modules", "module_set", "module_set.so"), - expect: true, - args: []string{}, - cmd: []string{"MODULE.SET", "key1", "15"}, - want: "OK", - wantErr: nil, - }, - { - name: "2. Test shared object plugin MODULE.GET", - path: path.Join(".", "testdata", "modules", "module_get", "module_get.so"), - expect: true, - args: []string{"10"}, - cmd: []string{"MODULE.GET", "key1"}, - want: "150", - wantErr: nil, - }, - { - name: "3. Test Non existent module.", - path: path.Join(".", "testdata", "modules", "non_existent", "module_non_existent.so"), - expect: false, - args: []string{}, - cmd: []string{"NONEXISTENT", "key", "value"}, - want: "", - wantErr: fmt.Errorf("load module: module %s not found", - path.Join(".", "testdata", "modules", "non_existent", "module_non_existent.so")), - }, - { - name: "4. Test LUA module that handles hash values", - path: path.Join("..", "internal", "volumes", "modules", "lua", "hash.lua"), - expect: true, - args: []string{}, - cmd: []string{"LUA.HASH", "LUA.HASH_KEY_1"}, - want: "OK", - wantErr: nil, - }, - { - name: "5. Test LUA module that handles set values", - path: path.Join("..", "internal", "volumes", "modules", "lua", "set.lua"), - expect: true, - args: []string{}, - cmd: []string{"LUA.SET", "LUA.SET_KEY_1", "LUA.SET_KEY_2", "LUA.SET_KEY_3"}, - want: "OK", - wantErr: nil, - }, - { - name: "6. Test LUA module that handles zset values", - path: path.Join("..", "internal", "volumes", "modules", "lua", "zset.lua"), - expect: true, - args: []string{}, - cmd: []string{"LUA.ZSET", "LUA.ZSET_KEY_1", "LUA.ZSET_KEY_2", "LUA.ZSET_KEY_3"}, - want: "OK", - wantErr: nil, - }, - { - name: "6. Test LUA module that handles list values", - path: path.Join("..", "internal", "volumes", "modules", "lua", "list.lua"), - expect: true, - args: []string{}, - cmd: []string{"LUA.LIST", "LUA.LIST_KEY_1"}, - want: "OK", - wantErr: nil, - }, - { - name: "8. Test LUA module that handles primitive types", - path: path.Join("..", "internal", "volumes", "modules", "lua", "example.lua"), - expect: true, - args: []string{}, - cmd: []string{"LUA.EXAMPLE"}, - want: "OK", - wantErr: nil, - }, - { - name: "9. Test JS module that handles primitive types", - path: path.Join("..", "internal", "volumes", "modules", "js", "example.js"), - expect: true, - args: []string{}, - cmd: []string{"JS.EXAMPLE"}, - want: "OK", - wantErr: nil, - }, - { - name: "10. Test JS module that handles hashes", - path: path.Join("..", "internal", "volumes", "modules", "js", "hash.js"), - expect: true, - args: []string{}, - cmd: []string{"JS.HASH", "JS_HASH_KEY1"}, - want: "OK", - wantErr: nil, - }, - { - name: "11. Test JS module that handles sets", - path: path.Join("..", "internal", "volumes", "modules", "js", "set.js"), - expect: true, - args: []string{}, - cmd: []string{"JS.SET", "JS_SET_KEY1", "member1"}, - want: "OK", - wantErr: nil, - }, - { - name: "12. Test JS module that handles sorted sets", - path: path.Join("..", "internal", "volumes", "modules", "js", "zset.js"), - expect: true, - args: []string{}, - cmd: []string{"JS.ZSET", "JS_ZSET_KEY1", "member1", "2.142"}, - want: "OK", - wantErr: nil, - }, - { - name: "13. Test JS module that handles lists", - path: path.Join("..", "internal", "volumes", "modules", "js", "list.js"), - expect: true, - args: []string{}, - cmd: []string{"JS.LIST", "JS_LIST_KEY1"}, - want: "OK", - wantErr: nil, - }, - } + type args struct { + removeCommand []string + executeCommand []string + } + tests := []struct { + name string + args args + wantErr error + }{ + { + name: "1 Remove command and expect error when the command is called", + args: args{ + removeCommand: []string{"LPUSH"}, + executeCommand: []string{"LPUSH", "key", "item"}, + }, + wantErr: errors.New("command LPUSH not supported"), + }, + { + name: "2 Remove sub-command and expect error when the subcommand is called", + args: args{ + removeCommand: []string{"ACL", "CAT"}, + executeCommand: []string{"ACL", "CAT"}, + }, + wantErr: errors.New("command ACL CAT not supported"), + }, + { + name: "3 Remove sub-command and expect successful response from calling another subcommand", + args: args{ + removeCommand: []string{"ACL", "WHOAMI"}, + executeCommand: []string{"ACL", "DELUSER", "user-one"}, + }, + wantErr: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() - for _, test := range tests { - // Load module - err := server.LoadModule(test.path, test.args...) - if err != nil { - if test.wantErr == nil || err.Error() != test.wantErr.Error() { + server := createSugarDB() + t.Cleanup(func() { + server.ShutDown() + }) + + server.RemoveCommand(tt.args.removeCommand...) + _, err := server.ExecuteCommand(tt.args.executeCommand...) + if tt.wantErr != nil { + if err.Error() != tt.wantErr.Error() { + t.Errorf("RemoveCommand() error = %v, wantErr %v", err, tt.wantErr) + } + } + }) + } + }) + + t.Run("TestSugarDB_Plugins", func(t *testing.T) { + t.Parallel() + + t.Cleanup(func() { + _ = os.RemoveAll("./testdata/modules") + }) + + server := createSugarDB() + t.Cleanup(func() { + server.ShutDown() + }) + + tests := []struct { + name string + path string + expect bool + args []string + cmd []string + want string + wantErr error + }{ + { + name: "1. Test shared object plugin MODULE.SET", + path: path.Join(".", "testdata", "modules", "module_set", "module_set.so"), + expect: true, + args: []string{}, + cmd: []string{"MODULE.SET", "key1", "15"}, + want: "OK", + wantErr: nil, + }, + { + name: "2. Test shared object plugin MODULE.GET", + path: path.Join(".", "testdata", "modules", "module_get", "module_get.so"), + expect: true, + args: []string{"10"}, + cmd: []string{"MODULE.GET", "key1"}, + want: "150", + wantErr: nil, + }, + { + name: "3. Test Non existent module.", + path: path.Join(".", "testdata", "modules", "non_existent", "module_non_existent.so"), + expect: false, + args: []string{}, + cmd: []string{"NONEXISTENT", "key", "value"}, + want: "", + wantErr: fmt.Errorf("load module: module %s not found", + path.Join(".", "testdata", "modules", "non_existent", "module_non_existent.so")), + }, + { + name: "4. Test LUA module that handles hash values", + path: path.Join("..", "internal", "volumes", "modules", "lua", "hash.lua"), + expect: true, + args: []string{}, + cmd: []string{"LUA.HASH", "LUA.HASH_KEY_1"}, + want: "OK", + wantErr: nil, + }, + { + name: "5. Test LUA module that handles set values", + path: path.Join("..", "internal", "volumes", "modules", "lua", "set.lua"), + expect: true, + args: []string{}, + cmd: []string{"LUA.SET", "LUA.SET_KEY_1", "LUA.SET_KEY_2", "LUA.SET_KEY_3"}, + want: "OK", + wantErr: nil, + }, + { + name: "6. Test LUA module that handles zset values", + path: path.Join("..", "internal", "volumes", "modules", "lua", "zset.lua"), + expect: true, + args: []string{}, + cmd: []string{"LUA.ZSET", "LUA.ZSET_KEY_1", "LUA.ZSET_KEY_2", "LUA.ZSET_KEY_3"}, + want: "OK", + wantErr: nil, + }, + { + name: "6. Test LUA module that handles list values", + path: path.Join("..", "internal", "volumes", "modules", "lua", "list.lua"), + expect: true, + args: []string{}, + cmd: []string{"LUA.LIST", "LUA.LIST_KEY_1"}, + want: "OK", + wantErr: nil, + }, + { + name: "8. Test LUA module that handles primitive types", + path: path.Join("..", "internal", "volumes", "modules", "lua", "example.lua"), + expect: true, + args: []string{}, + cmd: []string{"LUA.EXAMPLE"}, + want: "OK", + wantErr: nil, + }, + { + name: "9. Test JS module that handles primitive types", + path: path.Join("..", "internal", "volumes", "modules", "js", "example.js"), + expect: true, + args: []string{}, + cmd: []string{"JS.EXAMPLE"}, + want: "OK", + wantErr: nil, + }, + { + name: "10. Test JS module that handles hashes", + path: path.Join("..", "internal", "volumes", "modules", "js", "hash.js"), + expect: true, + args: []string{}, + cmd: []string{"JS.HASH", "JS_HASH_KEY1"}, + want: "OK", + wantErr: nil, + }, + { + name: "11. Test JS module that handles sets", + path: path.Join("..", "internal", "volumes", "modules", "js", "set.js"), + expect: true, + args: []string{}, + cmd: []string{"JS.SET", "JS_SET_KEY1", "member1"}, + want: "OK", + wantErr: nil, + }, + { + name: "12. Test JS module that handles sorted sets", + path: path.Join("..", "internal", "volumes", "modules", "js", "zset.js"), + expect: true, + args: []string{}, + cmd: []string{"JS.ZSET", "JS_ZSET_KEY1", "member1", "2.142"}, + want: "OK", + wantErr: nil, + }, + { + name: "13. Test JS module that handles lists", + path: path.Join("..", "internal", "volumes", "modules", "js", "list.js"), + expect: true, + args: []string{}, + cmd: []string{"JS.LIST", "JS_LIST_KEY1"}, + want: "OK", + wantErr: nil, + }, + } + + for _, test := range tests { + // Load module + err := server.LoadModule(test.path, test.args...) + if err != nil { + if test.wantErr == nil || err.Error() != test.wantErr.Error() { + t.Error(fmt.Errorf("%s: %v", test.name, err)) + return + } + continue + } + // Execute command and check expected response + res, err := server.ExecuteCommand(test.cmd...) + if err != nil { t.Error(fmt.Errorf("%s: %v", test.name, err)) + } + rv, _, err := resp.NewReader(bytes.NewReader(res)).ReadValue() + if err != nil { + t.Error(err) + } + if test.wantErr != nil { + if test.wantErr.Error() != rv.Error().Error() { + t.Errorf("expected error \"%s\", got \"%s\"", test.wantErr.Error(), rv.Error().Error()) + } return } - continue - } - // Execute command and check expected response - res, err := server.ExecuteCommand(test.cmd...) - if err != nil { - t.Error(fmt.Errorf("%s: %v", test.name, err)) - } - rv, _, err := resp.NewReader(bytes.NewReader(res)).ReadValue() - if err != nil { - t.Error(err) - } - if test.wantErr != nil { - if test.wantErr.Error() != rv.Error().Error() { - t.Errorf("expected error \"%s\", got \"%s\"", test.wantErr.Error(), rv.Error().Error()) + if rv.String() != test.want { + t.Errorf("expected response \"%s\", got \"%s\"", test.want, rv.String()) } - return } - if rv.String() != test.want { - t.Errorf("expected response \"%s\", got \"%s\"", test.want, rv.String()) - } - } - // Module list should contain all the modules above - modules := server.ListModules() - for _, test := range tests { - // Skip the module if it's not expected - if !test.expect { - continue - } - // Check if module is loaded - if !slices.Contains(modules, test.path) { - t.Errorf("expected modules list to contain module \"%s\" but did not find it", test.path) - } - // Unload the module - server.UnloadModule(test.path) - } - - // Make sure the modules are no longer loaded - modules = server.ListModules() - for _, test := range tests { - if slices.Contains(modules, test.path) { - t.Errorf("expected modules list to not contain module \"%s\" but found it", test.path) - } - } -} - -func TestSugarDB_CommandList(t *testing.T) { - server := createSugarDB() - - tests := []struct { - name string - options interface{} - want []string - wantErr bool - }{ - { - name: "1. Get all present commands when no options are passed", - options: nil, - want: func() []string { - var commands []string - for _, command := range server.commands { - if command.SubCommands == nil || len(command.SubCommands) == 0 { - commands = append(commands, strings.ToLower(command.Command)) - continue - } - for _, subcommand := range command.SubCommands { - commands = append(commands, strings.ToLower(fmt.Sprintf("%s %s", command.Command, subcommand.Command))) - } - } - return commands - }(), - wantErr: false, - }, - { - name: "2. Get commands filtered by hash ACL category", - options: CommandListOptions{ACLCAT: constants.HashCategory}, - want: func() []string { - var commands []string - for _, command := range server.commands { - if slices.Contains(command.Categories, constants.HashCategory) { - commands = append(commands, strings.ToLower(command.Command)) - } - } - return commands - }(), - wantErr: false, - }, - { - name: "3. Get commands filtered by pattern", - options: CommandListOptions{PATTERN: "z*"}, - want: func() []string { - var commands []string - for _, command := range server.commands { - if strings.EqualFold(command.Module, constants.SortedSetModule) { - commands = append(commands, strings.ToLower(command.Command)) - } - } - return commands - }(), - wantErr: false, - }, - { - name: "4. Get commands filtered by module", - options: CommandListOptions{MODULE: constants.ListModule}, - want: func() []string { - var commands []string - for _, command := range server.commands { - if strings.EqualFold(command.Module, constants.ListModule) { - commands = append(commands, strings.ToLower(command.Command)) - } - } - return commands - }(), - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var got []string - var err error - if tt.options == nil { - got, err = server.CommandList() - } else { - got, err = server.CommandList(tt.options.(CommandListOptions)) + // Module list should contain all the modules above + modules := server.ListModules() + for _, test := range tests { + // Skip the module if it's not expected + if !test.expect { + continue } - if (err != nil) != tt.wantErr { - t.Errorf("CommandList() error = %v, wantErr %v", err, tt.wantErr) - return + // Check if module is loaded + if !slices.Contains(modules, test.path) { + t.Errorf("expected modules list to contain module \"%s\" but did not find it", test.path) } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("CommandList() got = %v, want %v", got, tt.want) + // Unload the module + server.UnloadModule(test.path) + } + + // Make sure the modules are no longer loaded + modules = server.ListModules() + for _, test := range tests { + if slices.Contains(modules, test.path) { + t.Errorf("expected modules list to not contain module \"%s\" but found it", test.path) } + } + }) + + t.Run("TestSugarDB_CommandList", func(t *testing.T) { + t.Parallel() + + server := createSugarDB() + t.Cleanup(func() { + server.ShutDown() }) - } -} -func TestSugarDB_CommandCount(t *testing.T) { - server := createSugarDB() - - tests := []struct { - name string - want int - wantErr bool - }{ - { - name: "1. Get the count of all commands/subcommands on the server", - want: func() int { - var commands []string - for _, command := range server.commands { - if command.SubCommands == nil || len(command.SubCommands) == 0 { - commands = append(commands, strings.ToLower(command.Command)) - continue + tests := []struct { + name string + options interface{} + want []string + wantErr bool + }{ + { + name: "1. Get all present commands when no options are passed", + options: nil, + want: func() []string { + var commands []string + for _, command := range server.commands { + if command.SubCommands == nil || len(command.SubCommands) == 0 { + commands = append(commands, strings.ToLower(command.Command)) + continue + } + for _, subcommand := range command.SubCommands { + commands = append(commands, strings.ToLower(fmt.Sprintf("%s %s", command.Command, subcommand.Command))) + } } - for _, subcommand := range command.SubCommands { - commands = append(commands, strings.ToLower(fmt.Sprintf("%s %s", command.Command, subcommand.Command))) + return commands + }(), + wantErr: false, + }, + { + name: "2. Get commands filtered by hash ACL category", + options: CommandListOptions{ACLCAT: constants.HashCategory}, + want: func() []string { + var commands []string + for _, command := range server.commands { + if slices.Contains(command.Categories, constants.HashCategory) { + commands = append(commands, strings.ToLower(command.Command)) + } } + return commands + }(), + wantErr: false, + }, + { + name: "3. Get commands filtered by pattern", + options: CommandListOptions{PATTERN: "z*"}, + want: func() []string { + var commands []string + for _, command := range server.commands { + if strings.EqualFold(command.Module, constants.SortedSetModule) { + commands = append(commands, strings.ToLower(command.Command)) + } + } + return commands + }(), + wantErr: false, + }, + { + name: "4. Get commands filtered by module", + options: CommandListOptions{MODULE: constants.ListModule}, + want: func() []string { + var commands []string + for _, command := range server.commands { + if strings.EqualFold(command.Module, constants.ListModule) { + commands = append(commands, strings.ToLower(command.Command)) + } + } + return commands + }(), + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + var got []string + var err error + if tt.options == nil { + got, err = server.CommandList() + } else { + got, err = server.CommandList(tt.options.(CommandListOptions)) } - return len(commands) - }(), - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := server.CommandCount() - if (err != nil) != tt.wantErr { - t.Errorf("CommandCount() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("CommandCount() got = %v, want %v", got, tt.want) - } + if (err != nil) != tt.wantErr { + t.Errorf("CommandList() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("CommandList() got = %v, want %v", got, tt.want) + } + }) + } + }) + + t.Run("TestSugarDB_CommandCount", func(t *testing.T) { + t.Parallel() + + server := createSugarDB() + t.Cleanup(func() { + server.ShutDown() }) - } -} -func TestSugarDB_Save(t *testing.T) { - conf := DefaultConfig() - conf.DataDir = path.Join(".", "testdata", "data") - conf.EvictionPolicy = constants.NoEviction - server := createSugarDBWithConfig(conf) + tests := []struct { + name string + want int + wantErr bool + }{ + { + name: "1. Get the count of all commands/subcommands on the server", + want: func() int { + var commands []string + for _, command := range server.commands { + if command.SubCommands == nil || len(command.SubCommands) == 0 { + commands = append(commands, strings.ToLower(command.Command)) + continue + } + for _, subcommand := range command.SubCommands { + commands = append(commands, strings.ToLower(fmt.Sprintf("%s %s", command.Command, subcommand.Command))) + } + } + return len(commands) + }(), + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + got, err := server.CommandCount() + if (err != nil) != tt.wantErr { + t.Errorf("CommandCount() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("CommandCount() got = %v, want %v", got, tt.want) + } + }) + } + }) - tests := []struct { - name string - want bool - wantErr bool - }{ - { - name: "1. Return true response when save process is started", - want: true, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := server.Save() - if (err != nil) != tt.wantErr { - t.Errorf("Save() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("Save() got = %v, want %v", got, tt.want) - } + t.Run("TestSugarDB_Save", func(t *testing.T) { + t.Parallel() + + conf := DefaultConfig() + conf.DataDir = path.Join(".", "testdata", "data") + conf.EvictionPolicy = constants.NoEviction + + server := createSugarDBWithConfig(conf) + t.Cleanup(func() { + server.ShutDown() }) - } -} -func TestSugarDB_LastSave(t *testing.T) { - server := createSugarDB() - server.setLatestSnapshot(clock.NewClock().Now().Add(5 * time.Minute).UnixMilli()) + tests := []struct { + name string + want bool + wantErr bool + }{ + { + name: "1. Return true response when save process is started", + want: true, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := server.Save() + if (err != nil) != tt.wantErr { + t.Errorf("Save() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("Save() got = %v, want %v", got, tt.want) + } + }) + } + }) - tests := []struct { - name string - want int - wantErr bool - }{ - { - name: "1. Get latest snapshot time milliseconds", - want: int(clock.NewClock().Now().Add(5 * time.Minute).UnixMilli()), - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := server.LastSave() - if (err != nil) != tt.wantErr { - t.Errorf("LastSave() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("LastSave() got = %v, want %v", got, tt.want) - } + t.Run("TestSugarDB_LastSave", func(t *testing.T) { + t.Parallel() + + server := createSugarDB() + server.setLatestSnapshot(clock.NewClock().Now().Add(5 * time.Minute).UnixMilli()) + t.Cleanup(func() { + server.ShutDown() }) - } + + tests := []struct { + name string + want int + wantErr bool + }{ + { + name: "1. Get latest snapshot time milliseconds", + want: int(clock.NewClock().Now().Add(5 * time.Minute).UnixMilli()), + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := server.LastSave() + if (err != nil) != tt.wantErr { + t.Errorf("LastSave() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("LastSave() got = %v, want %v", got, tt.want) + } + }) + } + }) } diff --git a/sugardb/api_connection_test.go b/sugardb/api_connection_test.go index d3331e6..0c8760b 100644 --- a/sugardb/api_connection_test.go +++ b/sugardb/api_connection_test.go @@ -25,261 +25,272 @@ import ( "testing" ) -func TestSugarDB_Hello(t *testing.T) { - t.Parallel() +func TestSugarDB_Connection(t *testing.T) { + t.Run("TestSugarDB_Hello", func(t *testing.T) { + t.Parallel() - port, err := internal.GetFreePort() - if err != nil { - t.Error(err) - return - } + port, err := internal.GetFreePort() + if err != nil { + t.Error(err) + return + } - conf := DefaultConfig() - conf.Port = uint16(port) - conf.RequirePass = false + conf := DefaultConfig() + conf.Port = uint16(port) + conf.RequirePass = false - mockServer := createSugarDBWithConfig(conf) - if err != nil { - t.Error(err) - return - } - go func() { - mockServer.Start() - }() - t.Cleanup(func() { - mockServer.ShutDown() + mockServer := createSugarDBWithConfig(conf) + if err != nil { + t.Error(err) + return + } + go func() { + mockServer.Start() + }() + t.Cleanup(func() { + mockServer.ShutDown() + }) + + tests := []struct { + name string + command []resp.Value + wantRes []byte + }{ + { + name: "1. Hello", + command: []resp.Value{resp.StringValue("HELLO")}, + wantRes: connection.BuildHelloResponse( + internal.ServerInfo{ + Server: "sugardb", + Version: constants.Version, + Id: "", + Mode: "standalone", + Role: "master", + Modules: mockServer.ListModules(), + }, + internal.ConnectionInfo{ + Id: 1, + Name: "", + Protocol: 2, + Database: 0, + }, + ), + }, + { + name: "2. Hello 2", + command: []resp.Value{resp.StringValue("HELLO"), resp.StringValue("2")}, + wantRes: connection.BuildHelloResponse( + internal.ServerInfo{ + Server: "sugardb", + Version: constants.Version, + Id: "", + Mode: "standalone", + Role: "master", + Modules: mockServer.ListModules(), + }, + internal.ConnectionInfo{ + Id: 2, + Name: "", + Protocol: 2, + Database: 0, + }, + ), + }, + { + name: "3. Hello 3", + command: []resp.Value{resp.StringValue("HELLO"), resp.StringValue("3")}, + wantRes: connection.BuildHelloResponse( + internal.ServerInfo{ + Server: "sugardb", + Version: constants.Version, + Id: "", + Mode: "standalone", + Role: "master", + Modules: mockServer.ListModules(), + }, + internal.ConnectionInfo{ + Id: 3, + Name: "", + Protocol: 3, + Database: 0, + }, + ), + }, + } + + for i := 0; i < len(tests); i++ { + conn, err := internal.GetConnection("localhost", port) + if err != nil { + t.Error(err) + return + } + client := resp.NewConn(conn) + + if err = client.WriteArray(tests[i].command); err != nil { + t.Error(err) + return + } + + buf := bufio.NewReader(conn) + res, err := internal.ReadMessage(buf) + if err != nil { + t.Error(err) + return + } + + if !bytes.Equal(tests[i].wantRes, res) { + t.Errorf("expected byte resposne:\n%s, \n\ngot:\n%s", string(tests[i].wantRes), string(res)) + return + } + + // Close connection + _ = conn.Close() + } }) - tests := []struct { - name string - command []resp.Value - wantRes []byte - }{ - { - name: "1. Hello", - command: []resp.Value{resp.StringValue("HELLO")}, - wantRes: connection.BuildHelloResponse( - internal.ServerInfo{ - Server: "sugardb", - Version: constants.Version, - Id: "", - Mode: "standalone", - Role: "master", - Modules: mockServer.ListModules(), + t.Run("TestSugarDB_SelectDB", func(t *testing.T) { + t.Parallel() + tests := []struct { + name string + presetValues map[int]map[string]string + database int + want map[int][]string + wantErr bool + }{ + { + name: "1. Change database and read new values", + presetValues: map[int]map[string]string{ + 0: {"key1": "value-01", "key2": "value-02", "key3": "value-03"}, + 1: {"key1": "value-11", "key2": "value-12", "key3": "value-13"}, }, - internal.ConnectionInfo{ - Id: 1, - Name: "", - Protocol: 2, - Database: 0, + database: 1, + want: map[int][]string{ + 0: {"value-01", "value-02", "value-03"}, + 1: {"value-11", "value-12", "value-13"}, }, - ), - }, - { - name: "2. Hello 2", - command: []resp.Value{resp.StringValue("HELLO"), resp.StringValue("2")}, - wantRes: connection.BuildHelloResponse( - internal.ServerInfo{ - Server: "sugardb", - Version: constants.Version, - Id: "", - Mode: "standalone", - Role: "master", - Modules: mockServer.ListModules(), - }, - internal.ConnectionInfo{ - Id: 2, - Name: "", - Protocol: 2, - Database: 0, - }, - ), - }, - { - name: "3. Hello 3", - command: []resp.Value{resp.StringValue("HELLO"), resp.StringValue("3")}, - wantRes: connection.BuildHelloResponse( - internal.ServerInfo{ - Server: "sugardb", - Version: constants.Version, - Id: "", - Mode: "standalone", - Role: "master", - Modules: mockServer.ListModules(), - }, - internal.ConnectionInfo{ - Id: 3, - Name: "", - Protocol: 3, - Database: 0, - }, - ), - }, - } - - for i := 0; i < len(tests); i++ { - conn, err := internal.GetConnection("localhost", port) - if err != nil { - t.Error(err) - return - } - client := resp.NewConn(conn) - - if err = client.WriteArray(tests[i].command); err != nil { - t.Error(err) - return - } - - buf := bufio.NewReader(conn) - res, err := internal.ReadMessage(buf) - if err != nil { - t.Error(err) - return - } - - if !bytes.Equal(tests[i].wantRes, res) { - t.Errorf("expected byte resposne:\n%s, \n\ngot:\n%s", string(tests[i].wantRes), string(res)) - return - } - - // Close connection - _ = conn.Close() - } -} - -func TestSugarDB_SelectDB(t *testing.T) { - t.Parallel() - tests := []struct { - name string - presetValues map[int]map[string]string - database int - want map[int][]string - wantErr bool - }{ - { - name: "1. Change database and read new values", - presetValues: map[int]map[string]string{ - 0: {"key1": "value-01", "key2": "value-02", "key3": "value-03"}, - 1: {"key1": "value-11", "key2": "value-12", "key3": "value-13"}, + wantErr: false, }, - database: 1, - want: map[int][]string{ - 0: {"value-01", "value-02", "value-03"}, - 1: {"value-11", "value-12", "value-13"}, + { + name: "2. Error when database parameter is < 0", + presetValues: map[int]map[string]string{ + 0: {"key1": "value-01", "key2": "value-02", "key3": "value-03"}, + }, + database: -1, + want: map[int][]string{ + 0: {"value-01", "value-02", "value-03"}, + }, + wantErr: true, }, - wantErr: false, - }, - { - name: "2. Error when database parameter is < 0", - presetValues: map[int]map[string]string{ - 0: {"key1": "value-01", "key2": "value-02", "key3": "value-03"}, - }, - database: -1, - want: map[int][]string{ - 0: {"value-01", "value-02", "value-03"}, - }, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - server := createSugarDB() + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() - if tt.presetValues != nil { - for db, data := range tt.presetValues { - _ = server.SelectDB(db) - if _, err := server.MSet(data); err != nil { - t.Errorf("SelectDB() error = %v", err) + server := createSugarDB() + t.Cleanup(func() { + server.ShutDown() + }) + + if tt.presetValues != nil { + for db, data := range tt.presetValues { + _ = server.SelectDB(db) + if _, err := server.MSet(data); err != nil { + t.Errorf("SelectDB() error = %v", err) + return + } + } + _ = server.SelectDB(0) + } + + // Check the values for DB 0 + values, err := server.MGet("key1", "key2", "key3") + if err != nil { + t.Errorf("SelectDB() error = %v", err) + return + } + + if !reflect.DeepEqual(values, tt.want[0]) { + t.Errorf("SelectDB() result-0 = %v, want-0 %v", values, tt.want[0]) + return + } + + err = server.SelectDB(tt.database) + if tt.wantErr { + if err == nil { + t.Errorf("SelectDB() error = %v, wantErr %v", err, tt.wantErr) return } + return } - _ = server.SelectDB(0) - } - - // Check the values for DB 0 - values, err := server.MGet("key1", "key2", "key3") - if err != nil { - t.Errorf("SelectDB() error = %v", err) - return - } - - if !reflect.DeepEqual(values, tt.want[0]) { - t.Errorf("SelectDB() result-0 = %v, want-0 %v", values, tt.want[0]) - return - } - - err = server.SelectDB(tt.database) - if tt.wantErr { - if err == nil { + if err != nil { t.Errorf("SelectDB() error = %v, wantErr %v", err, tt.wantErr) return } - return - } - if err != nil { - t.Errorf("SelectDB() error = %v, wantErr %v", err, tt.wantErr) - return - } - // Check the values the new DB - values, err = server.MGet("key1", "key2", "key3") - if err != nil { - t.Errorf("SelectDB() error = %v", err) - return - } + // Check the values the new DB + values, err = server.MGet("key1", "key2", "key3") + if err != nil { + t.Errorf("SelectDB() error = %v", err) + return + } - if !reflect.DeepEqual(values, tt.want[1]) { - t.Errorf("SelectDB() result-1 = %v, want-1 %v", values, tt.want[1]) - return - } + if !reflect.DeepEqual(values, tt.want[1]) { + t.Errorf("SelectDB() result-1 = %v, want-1 %v", values, tt.want[1]) + return + } + }) + } + }) + + t.Run("TestSugarDB_SetProtocol", func(t *testing.T) { + t.Parallel() + + server := createSugarDB() + t.Cleanup(func() { + server.ShutDown() }) - } -} -func TestSugarDB_SetProtocol(t *testing.T) { - t.Parallel() - server := createSugarDB() - tests := []struct { - name string - protocol int - wantErr bool - }{ - { - name: "1. Change protocol to 2", - protocol: 2, - wantErr: false, - }, - { - name: "2. Change protocol to 3", - protocol: 3, - wantErr: false, - }, - { - name: "3. Return error when protocol is neither 2 or 3", - protocol: 4, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := server.SetProtocol(tt.protocol) - if tt.wantErr { - if err == nil { + tests := []struct { + name string + protocol int + wantErr bool + }{ + { + name: "1. Change protocol to 2", + protocol: 2, + wantErr: false, + }, + { + name: "2. Change protocol to 3", + protocol: 3, + wantErr: false, + }, + { + name: "3. Return error when protocol is neither 2 or 3", + protocol: 4, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := server.SetProtocol(tt.protocol) + if tt.wantErr { + if err == nil { + t.Errorf("SetProtocol() error = %v, wantErr %v", err, tt.wantErr) + return + } + return + } + if err != nil { t.Errorf("SetProtocol() error = %v, wantErr %v", err, tt.wantErr) return } - return - } - if err != nil { - t.Errorf("SetProtocol() error = %v, wantErr %v", err, tt.wantErr) - return - } - // Check if the protocol has been changed - if server.connInfo.embedded.Protocol != tt.protocol { - t.Errorf("SetProtocol() protocol = %v, wantProtocol %v", - server.connInfo.embedded.Protocol, tt.protocol) - } - }) - } + // Check if the protocol has been changed + if server.connInfo.embedded.Protocol != tt.protocol { + t.Errorf("SetProtocol() protocol = %v, wantProtocol %v", + server.connInfo.embedded.Protocol, tt.protocol) + } + }) + } + }) } diff --git a/sugardb/api_generic_test.go b/sugardb/api_generic_test.go index 521a54b..c42e12c 100644 --- a/sugardb/api_generic_test.go +++ b/sugardb/api_generic_test.go @@ -16,2080 +16,2131 @@ package sugardb import ( "context" + "github.com/echovault/sugardb/internal" + "github.com/echovault/sugardb/internal/clock" + "github.com/echovault/sugardb/internal/config" + "github.com/echovault/sugardb/internal/constants" "reflect" "slices" "strings" "testing" "time" - - "github.com/echovault/sugardb/internal" - "github.com/echovault/sugardb/internal/clock" - "github.com/echovault/sugardb/internal/config" - "github.com/echovault/sugardb/internal/constants" ) -func TestSugarDB_DEL(t *testing.T) { - server := createSugarDB() - - tests := []struct { - name string - presetValues map[string]internal.KeyData - keys []string - want int - wantErr bool - }{ - { - name: "Delete several keys and return deleted count", - keys: []string{"key1", "key2", "key3", "key4", "key5"}, - presetValues: map[string]internal.KeyData{ - "key1": {Value: "value1", ExpireAt: time.Time{}}, - "key2": {Value: "value2", ExpireAt: time.Time{}}, - "key3": {Value: "value3", ExpireAt: time.Time{}}, - "key4": {Value: "value4", ExpireAt: time.Time{}}, - }, - want: 4, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.presetValues != nil { - for k, d := range tt.presetValues { - presetKeyData(server, context.Background(), k, d) - } - } - got, err := server.Del(tt.keys...) - if (err != nil) != tt.wantErr { - t.Errorf("DEL() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("DEL() got = %v, want %v", got, tt.want) - } - }) - } -} - -func TestSugarDB_EXPIRE(t *testing.T) { +func TestSugarDB_Generic(t *testing.T) { mockClock := clock.NewClock() server := createSugarDB() + t.Cleanup(func() { + server.ShutDown() + }) - tests := []struct { - name string - presetValues map[string]internal.KeyData - cmd string - key string - time int - expireOpts ExpireOptions - want bool - wantErr bool - }{ - { - name: "Set new expire by seconds", - cmd: "EXPIRE", - key: "key1", - time: 100, - expireOpts: nil, - presetValues: map[string]internal.KeyData{ - "key1": {Value: "value1", ExpireAt: time.Time{}}, + t.Run("TestSugarDB_DEL", func(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + presetValues map[string]internal.KeyData + keys []string + want int + wantErr bool + }{ + { + name: "Delete several keys and return deleted count", + keys: []string{"del_key1", "del_key2", "del_key3", "del_key4", "del_key5"}, + presetValues: map[string]internal.KeyData{ + "del_key1": {Value: "value1", ExpireAt: time.Time{}}, + "del_key2": {Value: "value2", ExpireAt: time.Time{}}, + "del_key3": {Value: "value3", ExpireAt: time.Time{}}, + "del_key4": {Value: "value4", ExpireAt: time.Time{}}, + }, + want: 4, + wantErr: false, }, - want: true, - wantErr: false, - }, - { - name: "Set new expire by milliseconds", - cmd: "PEXPIRE", - key: "key2", - time: 1000, - expireOpts: nil, - presetValues: map[string]internal.KeyData{ - "key2": {Value: "value2", ExpireAt: time.Time{}}, - }, - want: true, - wantErr: false, - }, - { - name: "Set new expire only when key does not have an expiry time with NX flag", - cmd: "EXPIRE", - key: "key3", - time: 1000, - expireOpts: NX, - presetValues: map[string]internal.KeyData{ - "key3": {Value: "value3", ExpireAt: time.Time{}}, - }, - want: true, - wantErr: false, - }, - { - name: "Return false when NX flag is provided and key already has an expiry time", - cmd: "EXPIRE", - key: "key4", - time: 1000, - expireOpts: NX, - presetValues: map[string]internal.KeyData{ - "key4": {Value: "value4", ExpireAt: mockClock.Now().Add(1000 * time.Second)}, - }, - want: false, - wantErr: false, - }, - { - name: "Set new expire time from now key only when the key already has an expiry time with XX flag", - cmd: "EXPIRE", - key: "key5", - time: 1000, - expireOpts: XX, - presetValues: map[string]internal.KeyData{ - "key5": {Value: "value5", ExpireAt: mockClock.Now().Add(30 * time.Second)}, - }, - want: true, - wantErr: false, - }, - { - name: "Return false when key does not have an expiry and the XX flag is provided", - cmd: "EXPIRE", - time: 1000, - expireOpts: XX, - key: "key6", - presetValues: map[string]internal.KeyData{ - "key6": {Value: "value6", ExpireAt: time.Time{}}, - }, - want: false, - wantErr: false, - }, - { - name: "Set expiry time when the provided time is after the current expiry time when GT flag is provided", - cmd: "EXPIRE", - key: "key7", - time: 100000, - expireOpts: GT, - presetValues: map[string]internal.KeyData{ - "key7": {Value: "value7", ExpireAt: mockClock.Now().Add(30 * time.Second)}, - }, - want: true, - wantErr: false, - }, - { - name: "Return false when GT flag is passed and current expiry time is greater than provided time", - cmd: "EXPIRE", - key: "key8", - time: 1000, - expireOpts: GT, - presetValues: map[string]internal.KeyData{ - "key8": {Value: "value8", ExpireAt: mockClock.Now().Add(3000 * time.Second)}, - }, - want: false, - wantErr: false, - }, - { - name: "Return false when GT flag is passed and key does not have an expiry time", - cmd: "EXPIRE", - key: "key9", - time: 1000, - expireOpts: GT, - presetValues: map[string]internal.KeyData{ - "key9": {Value: "value9", ExpireAt: time.Time{}}, - }, - want: false, - wantErr: false, - }, - { - name: "Set expiry time when the provided time is before the current expiry time when LT flag is provided", - cmd: "EXPIRE", - key: "key10", - time: 1000, - expireOpts: LT, - presetValues: map[string]internal.KeyData{ - "key10": {Value: "value10", ExpireAt: mockClock.Now().Add(3000 * time.Second)}, - }, - want: true, - wantErr: false, - }, - { - name: "Return false when LT flag is passed and current expiry time is less than provided time", - cmd: "EXPIRE", - key: "key11", - time: 50000, - expireOpts: LT, - presetValues: map[string]internal.KeyData{ - "key11": {Value: "value11", ExpireAt: mockClock.Now().Add(30 * time.Second)}, - }, - want: false, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.presetValues != nil { - for k, d := range tt.presetValues { - presetKeyData(server, context.Background(), k, d) + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if tt.presetValues != nil { + for k, d := range tt.presetValues { + presetKeyData(server, context.Background(), k, d) + } } - } - var got bool - var err error - if strings.EqualFold(tt.cmd, "PEXPIRE") { - got, err = server.PExpire(tt.key, tt.time, tt.expireOpts) - } else { - got, err = server.Expire(tt.key, tt.time, tt.expireOpts) - } - if (err != nil) != tt.wantErr { - t.Errorf("(P)EXPIRE() error = %v, wantErr %v, key %s", err, tt.wantErr, tt.key) - return - } - if got != tt.want { - t.Errorf("(P)EXPIRE() got = %v, want %v, key %s", got, tt.want, tt.key) - } - }) - } -} - -func TestSugarDB_EXPIREAT(t *testing.T) { - mockClock := clock.NewClock() - - server := createSugarDB() - - tests := []struct { - name string - presetValues map[string]internal.KeyData - cmd string - key string - time int - expireAtOpts ExpireOptions - want int - wantErr bool - }{ - { - name: "Set new expire by unix seconds", - cmd: "EXPIREAT", - key: "key1", - expireAtOpts: nil, - time: int(mockClock.Now().Add(1000 * time.Second).Unix()), - presetValues: map[string]internal.KeyData{ - "key1": {Value: "value1", ExpireAt: time.Time{}}, - }, - want: 1, - wantErr: false, - }, - { - name: "Set new expire by milliseconds", - cmd: "PEXPIREAT", - key: "key2", - expireAtOpts: nil, - time: int(mockClock.Now().Add(1000 * time.Second).UnixMilli()), - presetValues: map[string]internal.KeyData{ - "key2": {Value: "value2", ExpireAt: time.Time{}}, - }, - want: 1, - wantErr: false, - }, - { // 3. - name: "Set new expire only when key does not have an expiry time with NX flag", - cmd: "EXPIREAT", - key: "key3", - time: int(mockClock.Now().Add(1000 * time.Second).Unix()), - expireAtOpts: NX, - presetValues: map[string]internal.KeyData{ - "key3": {Value: "value3", ExpireAt: time.Time{}}, - }, - want: 1, - wantErr: false, - }, - { - name: "Return 0, when NX flag is provided and key already has an expiry time", - cmd: "EXPIREAT", - time: int(mockClock.Now().Add(1000 * time.Second).Unix()), - expireAtOpts: NX, - key: "key4", - presetValues: map[string]internal.KeyData{ - "key4": {Value: "value4", ExpireAt: mockClock.Now().Add(1000 * time.Second)}, - }, - want: 0, - wantErr: false, - }, - { - name: "Set new expire time from now key only when the key already has an expiry time with XX flag", - cmd: "EXPIREAT", - time: int(mockClock.Now().Add(1000 * time.Second).Unix()), - key: "key5", - expireAtOpts: XX, - presetValues: map[string]internal.KeyData{ - "key5": {Value: "value5", ExpireAt: mockClock.Now().Add(30 * time.Second)}, - }, - want: 1, - wantErr: false, - }, - { - name: "Return 0 when key does not have an expiry and the XX flag is provided", - cmd: "EXPIREAT", - key: "key6", - time: int(mockClock.Now().Add(1000 * time.Second).Unix()), - expireAtOpts: XX, - presetValues: map[string]internal.KeyData{ - "key6": {Value: "value6", ExpireAt: time.Time{}}, - }, - want: 0, - wantErr: false, - }, - { - name: "Set expiry time when the provided time is after the current expiry time when GT flag is provided", - cmd: "EXPIREAT", - key: "key7", - time: int(mockClock.Now().Add(1000 * time.Second).Unix()), - expireAtOpts: GT, - presetValues: map[string]internal.KeyData{ - "key7": {Value: "value7", ExpireAt: mockClock.Now().Add(30 * time.Second)}, - }, - want: 1, - wantErr: false, - }, - { - name: "Return 0 when GT flag is passed and current expiry time is greater than provided time", - cmd: "EXPIREAT", - key: "key8", - time: int(mockClock.Now().Add(1000 * time.Second).Unix()), - expireAtOpts: GT, - presetValues: map[string]internal.KeyData{ - "key8": {Value: "value8", ExpireAt: mockClock.Now().Add(3000 * time.Second)}, - }, - want: 0, - wantErr: false, - }, - { - name: "Return 0 when GT flag is passed and key does not have an expiry time", - cmd: "EXPIREAT", - key: "key9", - time: int(mockClock.Now().Add(1000 * time.Second).Unix()), - expireAtOpts: GT, - presetValues: map[string]internal.KeyData{ - "key9": {Value: "value9", ExpireAt: time.Time{}}, - }, - want: 0, - }, - { - name: "Set expiry time when the provided time is before the current expiry time when LT flag is provided", - cmd: "EXPIREAT", - key: "key10", - time: int(mockClock.Now().Add(1000 * time.Second).Unix()), - expireAtOpts: LT, - presetValues: map[string]internal.KeyData{ - "key10": {Value: "value10", ExpireAt: mockClock.Now().Add(3000 * time.Second)}, - }, - want: 1, - wantErr: false, - }, - { - name: "Return 0 when LT flag is passed and current expiry time is less than provided time", - cmd: "EXPIREAT", - key: "key11", - time: int(mockClock.Now().Add(3000 * time.Second).Unix()), - expireAtOpts: LT, - presetValues: map[string]internal.KeyData{ - "key11": {Value: "value11", ExpireAt: mockClock.Now().Add(1000 * time.Second)}, - }, - want: 0, - wantErr: false, - }, - { - name: "Return 0 when LT flag is passed and key does not have an expiry time", - cmd: "EXPIREAT", - key: "key12", - time: int(mockClock.Now().Add(1000 * time.Second).Unix()), - expireAtOpts: LT, - presetValues: map[string]internal.KeyData{ - "key12": {Value: "value12", ExpireAt: time.Time{}}, - }, - want: 1, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.presetValues != nil { - for k, d := range tt.presetValues { - presetKeyData(server, context.Background(), k, d) - } - } - var got int - var err error - if strings.EqualFold(tt.cmd, "PEXPIREAT") { - got, err = server.PExpireAt(tt.key, tt.time, tt.expireAtOpts) - } else { - got, err = server.ExpireAt(tt.key, tt.time, tt.expireAtOpts) - } - if (err != nil) != tt.wantErr { - t.Errorf("(P)EXPIREAT() error = %v, wantErr %v, KEY %s", err, tt.wantErr, tt.key) - return - } - if got != tt.want { - t.Errorf("(P)EXPIREAT() got = %v, want %v, KEY %s", got, tt.want, tt.key) - } - }) - } -} - -func TestSugarDB_EXPIRETIME(t *testing.T) { - mockClock := clock.NewClock() - - server := createSugarDB() - - tests := []struct { - name string - presetValues map[string]internal.KeyData - key string - expiretimeFunc func(key string) (int, error) - want int - wantErr bool - }{ - { - name: "Return expire time in seconds", - key: "key1", - presetValues: map[string]internal.KeyData{ - "key1": {Value: "value1", ExpireAt: mockClock.Now().Add(100 * time.Second)}, - }, - expiretimeFunc: server.ExpireTime, - want: int(mockClock.Now().Add(100 * time.Second).Unix()), - wantErr: false, - }, - { - name: "Return expire time in milliseconds", - key: "key2", - presetValues: map[string]internal.KeyData{ - "key2": {Value: "value2", ExpireAt: mockClock.Now().Add(4096 * time.Millisecond)}, - }, - expiretimeFunc: server.PExpireTime, - want: int(mockClock.Now().Add(4096 * time.Millisecond).UnixMilli()), - wantErr: false, - }, - { - name: "If the key is non-volatile, return -1", - key: "key3", - presetValues: map[string]internal.KeyData{ - "key3": {Value: "value3", ExpireAt: time.Time{}}, - }, - expiretimeFunc: server.PExpireTime, - want: -1, - wantErr: false, - }, - { - name: "If the key is non-existent return -2", - presetValues: nil, - expiretimeFunc: server.PExpireTime, - want: -2, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.presetValues != nil { - for k, d := range tt.presetValues { - presetKeyData(server, context.Background(), k, d) - } - } - got, err := tt.expiretimeFunc(tt.key) - if (err != nil) != tt.wantErr { - t.Errorf("(P)EXPIRETIME() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("(P)EXPIRETIME() got = %v, want %v", got, tt.want) - } - }) - } -} - -func TestSugarDB_GET(t *testing.T) { - server := createSugarDB() - - tests := []struct { - name string - presetValue interface{} - key string - want string - wantErr bool - }{ - { - name: "Return string from existing key", - presetValue: "value1", - key: "key1", - want: "value1", - wantErr: false, - }, - { - name: "Return empty string if the key does not exist", - presetValue: nil, - key: "key2", - want: "", - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.presetValue != nil { - err := presetValue(server, context.Background(), tt.key, tt.presetValue) - if err != nil { - t.Error(err) + got, err := server.Del(tt.keys...) + if (err != nil) != tt.wantErr { + t.Errorf("DEL() error = %v, wantErr %v", err, tt.wantErr) return } - } - got, err := server.Get(tt.key) - if (err != nil) != tt.wantErr { - t.Errorf("GET() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("GET() got = %v, want %v", got, tt.want) - } - }) - } -} + if got != tt.want { + t.Errorf("DEL() got = %v, want %v", got, tt.want) + } + }) + } + }) -func TestSugarDB_MGET(t *testing.T) { - server := createSugarDB() + t.Run("TestSugarDB_EXPIRE", func(t *testing.T) { + t.Parallel() - tests := []struct { - name string - presetValues map[string]interface{} - keys []string - want []string - wantErr bool - }{ - { - name: "Get all values in the same order the keys were provided in", - presetValues: map[string]interface{}{"key1": "value1", "key2": "value2", "key3": "value3", "key4": "value4"}, - keys: []string{"key1", "key4", "key2", "key3", "key1"}, - want: []string{"value1", "value4", "value2", "value3", "value1"}, - wantErr: false, - }, - { - name: "Return empty strings for non-existent keys", - presetValues: map[string]interface{}{"key5": "value5", "key6": "value6", "key7": "value7"}, - keys: []string{"key5", "key6", "non-existent", "non-existent", "key7", "non-existent"}, - want: []string{"value5", "value6", "", "", "value7", ""}, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.presetValues != nil { - for k, v := range tt.presetValues { - err := presetValue(server, context.Background(), k, v) + tests := []struct { + name string + presetValues map[string]internal.KeyData + cmd string + key string + time int + expireOpts ExpireOptions + want bool + wantErr bool + }{ + { + name: "Set new expire by seconds", + cmd: "EXPIRE", + key: "expire_key1", + time: 100, + expireOpts: nil, + presetValues: map[string]internal.KeyData{ + "expire_key1": {Value: "value1", ExpireAt: time.Time{}}, + }, + want: true, + wantErr: false, + }, + { + name: "Set new expire by milliseconds", + cmd: "PEXPIRE", + key: "expire_key2", + time: 1000, + expireOpts: nil, + presetValues: map[string]internal.KeyData{ + "expire_key2": {Value: "value2", ExpireAt: time.Time{}}, + }, + want: true, + wantErr: false, + }, + { + name: "Set new expire only when key does not have an expiry time with NX flag", + cmd: "EXPIRE", + key: "expire_key3", + time: 1000, + expireOpts: NX, + presetValues: map[string]internal.KeyData{ + "expire_key3": {Value: "value3", ExpireAt: time.Time{}}, + }, + want: true, + wantErr: false, + }, + { + name: "Return false when NX flag is provided and key already has an expiry time", + cmd: "EXPIRE", + key: "expire_key4", + time: 1000, + expireOpts: NX, + presetValues: map[string]internal.KeyData{ + "expire_key4": {Value: "value4", ExpireAt: mockClock.Now().Add(1000 * time.Second)}, + }, + want: false, + wantErr: false, + }, + { + name: "Set new expire time from now key only when the key already has an expiry time with XX flag", + cmd: "EXPIRE", + key: "expire_key5", + time: 1000, + expireOpts: XX, + presetValues: map[string]internal.KeyData{ + "expire_key5": {Value: "value5", ExpireAt: mockClock.Now().Add(30 * time.Second)}, + }, + want: true, + wantErr: false, + }, + { + name: "Return false when key does not have an expiry and the XX flag is provided", + cmd: "EXPIRE", + time: 1000, + expireOpts: XX, + key: "expire_key6", + presetValues: map[string]internal.KeyData{ + "expire_key6": {Value: "value6", ExpireAt: time.Time{}}, + }, + want: false, + wantErr: false, + }, + { + name: "Set expiry time when the provided time is after the current expiry time when GT flag is provided", + cmd: "EXPIRE", + key: "expire_key7", + time: 100000, + expireOpts: GT, + presetValues: map[string]internal.KeyData{ + "expire_key7": {Value: "value7", ExpireAt: mockClock.Now().Add(30 * time.Second)}, + }, + want: true, + wantErr: false, + }, + { + name: "Return false when GT flag is passed and current expiry time is greater than provided time", + cmd: "EXPIRE", + key: "expire_key8", + time: 1000, + expireOpts: GT, + presetValues: map[string]internal.KeyData{ + "expire_key8": {Value: "value8", ExpireAt: mockClock.Now().Add(3000 * time.Second)}, + }, + want: false, + wantErr: false, + }, + { + name: "Return false when GT flag is passed and key does not have an expiry time", + cmd: "EXPIRE", + key: "expire_key9", + time: 1000, + expireOpts: GT, + presetValues: map[string]internal.KeyData{ + "expire_key9": {Value: "value9", ExpireAt: time.Time{}}, + }, + want: false, + wantErr: false, + }, + { + name: "Set expiry time when the provided time is before the current expiry time when LT flag is provided", + cmd: "EXPIRE", + key: "expire_key10", + time: 1000, + expireOpts: LT, + presetValues: map[string]internal.KeyData{ + "expire_key10": {Value: "value10", ExpireAt: mockClock.Now().Add(3000 * time.Second)}, + }, + want: true, + wantErr: false, + }, + { + name: "Return false when LT flag is passed and current expiry time is less than provided time", + cmd: "EXPIRE", + key: "expire_key11", + time: 50000, + expireOpts: LT, + presetValues: map[string]internal.KeyData{ + "expire_key11": {Value: "value11", ExpireAt: mockClock.Now().Add(30 * time.Second)}, + }, + want: false, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if tt.presetValues != nil { + for k, d := range tt.presetValues { + presetKeyData(server, context.Background(), k, d) + } + } + var got bool + var err error + if strings.EqualFold(tt.cmd, "PEXPIRE") { + got, err = server.PExpire(tt.key, tt.time, tt.expireOpts) + } else { + got, err = server.Expire(tt.key, tt.time, tt.expireOpts) + } + if (err != nil) != tt.wantErr { + t.Errorf("(P)EXPIRE() error = %v, wantErr %v, key %s", err, tt.wantErr, tt.key) + return + } + if got != tt.want { + t.Errorf("(P)EXPIRE() got = %v, want %v, key %s", got, tt.want, tt.key) + } + }) + } + }) + + t.Run("TestSugarDB_EXPIREAT", func(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + presetValues map[string]internal.KeyData + cmd string + key string + time int + expireAtOpts ExpireOptions + want int + wantErr bool + }{ + { + name: "Set new expire by unix seconds", + cmd: "EXPIREAT", + key: "expireat_key1", + expireAtOpts: nil, + time: int(mockClock.Now().Add(1000 * time.Second).Unix()), + presetValues: map[string]internal.KeyData{ + "expireat_key1": {Value: "value1", ExpireAt: time.Time{}}, + }, + want: 1, + wantErr: false, + }, + { + name: "Set new expire by milliseconds", + cmd: "PEXPIREAT", + key: "expireat_key2", + expireAtOpts: nil, + time: int(mockClock.Now().Add(1000 * time.Second).UnixMilli()), + presetValues: map[string]internal.KeyData{ + "expireat_key2": {Value: "value2", ExpireAt: time.Time{}}, + }, + want: 1, + wantErr: false, + }, + { // 3. + name: "Set new expire only when key does not have an expiry time with NX flag", + cmd: "EXPIREAT", + key: "expireat_key3", + time: int(mockClock.Now().Add(1000 * time.Second).Unix()), + expireAtOpts: NX, + presetValues: map[string]internal.KeyData{ + "expireat_key3": {Value: "value3", ExpireAt: time.Time{}}, + }, + want: 1, + wantErr: false, + }, + { + name: "Return 0, when NX flag is provided and key already has an expiry time", + cmd: "EXPIREAT", + time: int(mockClock.Now().Add(1000 * time.Second).Unix()), + expireAtOpts: NX, + key: "expireat_key4", + presetValues: map[string]internal.KeyData{ + "expireat_key4": {Value: "value4", ExpireAt: mockClock.Now().Add(1000 * time.Second)}, + }, + want: 0, + wantErr: false, + }, + { + name: "Set new expire time from now key only when the key already has an expiry time with XX flag", + cmd: "EXPIREAT", + time: int(mockClock.Now().Add(1000 * time.Second).Unix()), + key: "expireat_key5", + expireAtOpts: XX, + presetValues: map[string]internal.KeyData{ + "expireat_key5": {Value: "value5", ExpireAt: mockClock.Now().Add(30 * time.Second)}, + }, + want: 1, + wantErr: false, + }, + { + name: "Return 0 when key does not have an expiry and the XX flag is provided", + cmd: "EXPIREAT", + key: "expireat_key6", + time: int(mockClock.Now().Add(1000 * time.Second).Unix()), + expireAtOpts: XX, + presetValues: map[string]internal.KeyData{ + "expireat_key6": {Value: "value6", ExpireAt: time.Time{}}, + }, + want: 0, + wantErr: false, + }, + { + name: "Set expiry time when the provided time is after the current expiry time when GT flag is provided", + cmd: "EXPIREAT", + key: "expireat_key7", + time: int(mockClock.Now().Add(1000 * time.Second).Unix()), + expireAtOpts: GT, + presetValues: map[string]internal.KeyData{ + "expireat_key7": {Value: "value7", ExpireAt: mockClock.Now().Add(30 * time.Second)}, + }, + want: 1, + wantErr: false, + }, + { + name: "Return 0 when GT flag is passed and current expiry time is greater than provided time", + cmd: "EXPIREAT", + key: "expireat_key8", + time: int(mockClock.Now().Add(1000 * time.Second).Unix()), + expireAtOpts: GT, + presetValues: map[string]internal.KeyData{ + "expireat_key8": {Value: "value8", ExpireAt: mockClock.Now().Add(3000 * time.Second)}, + }, + want: 0, + wantErr: false, + }, + { + name: "Return 0 when GT flag is passed and key does not have an expiry time", + cmd: "EXPIREAT", + key: "expireat_key9", + time: int(mockClock.Now().Add(1000 * time.Second).Unix()), + expireAtOpts: GT, + presetValues: map[string]internal.KeyData{ + "expireat_key9": {Value: "value9", ExpireAt: time.Time{}}, + }, + want: 0, + }, + { + name: "Set expiry time when the provided time is before the current expiry time when LT flag is provided", + cmd: "EXPIREAT", + key: "expireat_key10", + time: int(mockClock.Now().Add(1000 * time.Second).Unix()), + expireAtOpts: LT, + presetValues: map[string]internal.KeyData{ + "expireat_key10": {Value: "value10", ExpireAt: mockClock.Now().Add(3000 * time.Second)}, + }, + want: 1, + wantErr: false, + }, + { + name: "Return 0 when LT flag is passed and current expiry time is less than provided time", + cmd: "EXPIREAT", + key: "expireat_key11", + time: int(mockClock.Now().Add(3000 * time.Second).Unix()), + expireAtOpts: LT, + presetValues: map[string]internal.KeyData{ + "expireat_key11": {Value: "value11", ExpireAt: mockClock.Now().Add(1000 * time.Second)}, + }, + want: 0, + wantErr: false, + }, + { + name: "Return 0 when LT flag is passed and key does not have an expiry time", + cmd: "EXPIREAT", + key: "expireat_key12", + time: int(mockClock.Now().Add(1000 * time.Second).Unix()), + expireAtOpts: LT, + presetValues: map[string]internal.KeyData{ + "expireat_key12": {Value: "value12", ExpireAt: time.Time{}}, + }, + want: 1, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if tt.presetValues != nil { + for k, d := range tt.presetValues { + presetKeyData(server, context.Background(), k, d) + } + } + var got int + var err error + if strings.EqualFold(tt.cmd, "PEXPIREAT") { + got, err = server.PExpireAt(tt.key, tt.time, tt.expireAtOpts) + } else { + got, err = server.ExpireAt(tt.key, tt.time, tt.expireAtOpts) + } + if (err != nil) != tt.wantErr { + t.Errorf("(P)EXPIREAT() error = %v, wantErr %v, KEY %s", err, tt.wantErr, tt.key) + return + } + if got != tt.want { + t.Errorf("(P)EXPIREAT() got = %v, want %v, KEY %s", got, tt.want, tt.key) + } + }) + } + }) + + t.Run("TestSugarDB_EXPIRETIME", func(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + presetValues map[string]internal.KeyData + key string + expiretimeFunc func(key string) (int, error) + want int + wantErr bool + }{ + { + name: "Return expire time in seconds", + key: "expiretime_key1", + presetValues: map[string]internal.KeyData{ + "expiretime_key1": {Value: "value1", ExpireAt: mockClock.Now().Add(100 * time.Second)}, + }, + expiretimeFunc: server.ExpireTime, + want: int(mockClock.Now().Add(100 * time.Second).Unix()), + wantErr: false, + }, + { + name: "Return expire time in milliseconds", + key: "expiretime_key2", + presetValues: map[string]internal.KeyData{ + "expiretime_key2": {Value: "value2", ExpireAt: mockClock.Now().Add(4096 * time.Millisecond)}, + }, + expiretimeFunc: server.PExpireTime, + want: int(mockClock.Now().Add(4096 * time.Millisecond).UnixMilli()), + wantErr: false, + }, + { + name: "If the key is non-volatile, return -1", + key: "expiretime_key3", + presetValues: map[string]internal.KeyData{ + "expiretime_key3": {Value: "value3", ExpireAt: time.Time{}}, + }, + expiretimeFunc: server.PExpireTime, + want: -1, + wantErr: false, + }, + { + name: "If the key is non-existent return -2", + presetValues: nil, + expiretimeFunc: server.PExpireTime, + want: -2, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if tt.presetValues != nil { + for k, d := range tt.presetValues { + presetKeyData(server, context.Background(), k, d) + } + } + got, err := tt.expiretimeFunc(tt.key) + if (err != nil) != tt.wantErr { + t.Errorf("(P)EXPIRETIME() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("(P)EXPIRETIME() got = %v, want %v", got, tt.want) + } + }) + } + }) + + t.Run("TestSugarDB_GET", func(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + presetValue interface{} + key string + want string + wantErr bool + }{ + { + name: "Return string from existing key", + presetValue: "value1", + key: "get_key1", + want: "value1", + wantErr: false, + }, + { + name: "Return empty string if the key does not exist", + presetValue: nil, + key: "get_key2", + want: "", + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if tt.presetValue != nil { + err := presetValue(server, context.Background(), tt.key, tt.presetValue) if err != nil { t.Error(err) return } } - } - got, err := server.MGet(tt.keys...) - if (err != nil) != tt.wantErr { - t.Errorf("MGET() error = %v, wantErr %v", err, tt.wantErr) - return - } - if len(got) != len(tt.want) { - t.Errorf("MGET() got = %v, want %v", got, tt.want) - } - for _, g := range got { - if !slices.Contains(tt.want, g) { - t.Errorf("MGET() got = %v, want %v", got, tt.want) - } - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("MGET() got = %v, want %v", got, tt.want) - } - }) - } -} - -func TestSugarDB_SET(t *testing.T) { - mockClock := clock.NewClock() - - server := createSugarDB() - - SetOptions := func(W SetWriteOption, EX SetExOption, EXTIME int, GET bool) SETOptions { - return SETOptions{ - WriteOpt: W, - ExpireOpt: EX, - ExpireTime: EXTIME, - Get: GET, - } - } - - tests := []struct { - name string - presetValues map[string]internal.KeyData - key string - value string - options SETOptions - wantOk bool - wantPrev string - wantErr bool - }{ - { - name: "Set normal value", - presetValues: nil, - key: "key1", - value: "value1", - options: SetOptions(nil, nil, 0, false), - wantOk: true, - wantPrev: "", - wantErr: false, - }, - { - name: "Only set the value if the key does not exist", - presetValues: nil, - key: "key2", - value: "value2", - options: SetOptions(SETNX, nil, 0, false), - wantOk: true, - wantPrev: "", - wantErr: false, - }, - { - name: "Throw error when value already exists with NX flag passed", - presetValues: map[string]internal.KeyData{ - "key3": { - Value: "preset-value3", - ExpireAt: time.Time{}, - }, - }, - key: "key3", - value: "value3", - options: SetOptions(SETNX, nil, 0, false), - wantOk: false, - wantPrev: "", - wantErr: true, - }, - { - name: "Set new key value when key exists with XX flag passed", - presetValues: map[string]internal.KeyData{ - "key4": { - Value: "preset-value4", - ExpireAt: time.Time{}, - }, - }, - key: "key4", - value: "value4", - options: SetOptions(SETXX, nil, 0, false), - wantOk: true, - wantPrev: "", - wantErr: false, - }, - { - name: "Return error when setting non-existent key with XX flag", - presetValues: nil, - key: "key5", - value: "value5", - options: SetOptions(SETXX, nil, 0, false), - wantOk: false, - wantPrev: "", - wantErr: true, - }, - { - name: "Set expiry time on the key to 100 seconds from now", - presetValues: nil, - key: "key6", - value: "value6", - options: SetOptions(nil, SETEX, 100, false), - wantOk: true, - wantPrev: "", - wantErr: false, - }, - { - name: "Set expiry time on the key in unix milliseconds", - presetValues: nil, - key: "key7", - value: "value7", - options: SetOptions(nil, SETPX, 4096, false), - wantOk: true, - wantPrev: "", - wantErr: false, - }, - { - name: "Set exact expiry time in seconds from unix epoch", - presetValues: nil, - key: "key8", - value: "value8", - options: SetOptions(nil, SETEXAT, int(mockClock.Now().Add(200*time.Second).Unix()), false), - wantOk: true, - wantPrev: "", - wantErr: false, - }, - { - name: "Set exact expiry time in milliseconds from unix epoch", - key: "key9", - value: "value9", - options: SetOptions(nil, SETPXAT, int(mockClock.Now().Add(4096*time.Millisecond).UnixMilli()), false), - presetValues: nil, - wantOk: true, - wantPrev: "", - wantErr: false, - }, - { - name: "Get the previous value when GET flag is passed", - presetValues: map[string]internal.KeyData{ - "key10": { - Value: "previous-value", - ExpireAt: time.Time{}, - }, - }, - key: "key10", - value: "value10", - options: SetOptions(nil, SETEX, 1000, true), - wantOk: true, - wantPrev: "previous-value", - wantErr: false, - }, - { - name: "Return nil when GET value is passed and no previous value exists", - presetValues: nil, - key: "key11", - value: "value11", - options: SetOptions(nil, SETEX, 1000, true), - wantOk: true, - wantPrev: "", - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.presetValues != nil { - for k, d := range tt.presetValues { - presetKeyData(server, context.Background(), k, d) - } - } - previousValue, ok, err := server.Set( - tt.key, - tt.value, - tt.options, - ) - if (err != nil) != tt.wantErr { - t.Errorf("SET() error = %v, wantErr %v", err, tt.wantErr) - return - } - if ok != tt.wantOk { - t.Errorf("SET() ok got = %v, want %v", ok, tt.wantOk) - } - if previousValue != tt.wantPrev { - t.Errorf("SET() previous value got = %v, want %v", previousValue, tt.wantPrev) - } - }) - } -} - -func TestSugarDB_MSET(t *testing.T) { - server := createSugarDB() - - tests := []struct { - name string - kvPairs map[string]string - want bool - wantErr bool - }{ - { - name: "Set multiple keys", - kvPairs: map[string]string{"key1": "value1", "key2": "10", "key3": "3.142"}, - want: true, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := server.MSet(tt.kvPairs) - if (err != nil) != tt.wantErr { - t.Errorf("MSET() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("MSET() got = %v, want %v", got, tt.want) - } - }) - } -} - -func TestSugarDB_PERSIST(t *testing.T) { - mockClock := clock.NewClock() - - server := createSugarDB() - - tests := []struct { - name string - presetValues map[string]internal.KeyData - key string - want bool - wantErr bool - }{ - { - name: "Successfully persist a volatile key", - key: "key1", - presetValues: map[string]internal.KeyData{ - "key1": {Value: "value1", ExpireAt: mockClock.Now().Add(1000 * time.Second)}, - }, - want: true, - wantErr: false, - }, - { - name: "Return false when trying to persist a non-existent key", - key: "key2", - presetValues: nil, - want: false, - wantErr: false, - }, - { - name: "Return false when trying to persist a non-volatile key", - key: "key3", - presetValues: map[string]internal.KeyData{ - "key3": {Value: "value3", ExpireAt: time.Time{}}, - }, - want: false, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.presetValues != nil { - for k, d := range tt.presetValues { - presetKeyData(server, context.Background(), k, d) - } - } - got, err := server.Persist(tt.key) - if (err != nil) != tt.wantErr { - t.Errorf("PERSIST() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("PERSIST() got = %v, want %v", got, tt.want) - } - }) - } -} - -func TestSugarDB_TTL(t *testing.T) { - mockClock := clock.NewClock() - - server := createSugarDB() - - tests := []struct { - name string - presetValues map[string]internal.KeyData - key string - ttlFunc func(key string) (int, error) - want int - wantErr bool - }{ - { - name: "Return TTL time in seconds", - key: "key1", - presetValues: map[string]internal.KeyData{ - "key1": {Value: "value1", ExpireAt: mockClock.Now().Add(100 * time.Second)}, - }, - ttlFunc: server.TTL, - want: 100, - wantErr: false, - }, - { - name: "Return TTL time in milliseconds", - key: "key2", - ttlFunc: server.PTTL, - presetValues: map[string]internal.KeyData{ - "key2": {Value: "value2", ExpireAt: mockClock.Now().Add(4096 * time.Millisecond)}, - }, - want: 4096, - wantErr: false, - }, - { - name: "If the key is non-volatile, return -1", - key: "key3", - ttlFunc: server.TTL, - presetValues: map[string]internal.KeyData{ - "key3": {Value: "value3", ExpireAt: time.Time{}}, - }, - want: -1, - wantErr: false, - }, - { - name: "If the key is non-existent return -2", - key: "key4", - ttlFunc: server.TTL, - presetValues: nil, - want: -2, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.presetValues != nil { - for k, d := range tt.presetValues { - presetKeyData(server, context.Background(), k, d) - } - } - got, err := tt.ttlFunc(tt.key) - if (err != nil) != tt.wantErr { - t.Errorf("TTL() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("TTL() got = %v, want %v", got, tt.want) - } - }) - } -} - -func TestSugarDB_INCR(t *testing.T) { - server := createSugarDB() - - tests := []struct { - name string - key string - presetValues map[string]internal.KeyData - want int - wantErr bool - }{ - { - name: "1. Increment non-existent key", - key: "IncrKey1", - presetValues: nil, - want: 1, - wantErr: false, - }, - { - name: "2. Increment existing key with integer value", - key: "IncrKey2", - presetValues: map[string]internal.KeyData{ - "IncrKey2": {Value: "5"}, - }, - want: 6, - wantErr: false, - }, - { - name: "3. Increment existing key with non-integer value", - key: "IncrKey3", - presetValues: map[string]internal.KeyData{ - "IncrKey3": {Value: "not_an_int"}, - }, - want: 0, - wantErr: true, - }, - { - name: "4. Increment existing key with int64 value", - key: "IncrKey4", - presetValues: map[string]internal.KeyData{ - "IncrKey4": {Value: int64(10)}, - }, - want: 11, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.presetValues != nil { - for k, d := range tt.presetValues { - presetKeyData(server, context.Background(), k, d) - } - } - got, err := server.Incr(tt.key) - if (err != nil) != tt.wantErr { - t.Errorf("INCR() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("INCR() got = %v, want %v", got, tt.want) - } - }) - } -} - -func TestSugarDB_DECR(t *testing.T) { - server := createSugarDB() - - tests := []struct { - name string - key string - presetValues map[string]internal.KeyData - want int - wantErr bool - }{ - { - name: "1. Decrement non-existent key", - key: "DecrKey1", - presetValues: nil, - want: -1, - wantErr: false, - }, - { - name: "2. Decrement existing key with integer value", - key: "DecrKey2", - presetValues: map[string]internal.KeyData{ - "DecrKey2": {Value: "5"}, - }, - want: 4, - wantErr: false, - }, - { - name: "3. Decrement existing key with non-integer value", - key: "DecrKey3", - presetValues: map[string]internal.KeyData{ - "DecrKey3": {Value: "not_an_int"}, - }, - want: 0, - wantErr: true, - }, - { - name: "4. Decrement existing key with int64 value", - key: "DecrKey4", - presetValues: map[string]internal.KeyData{ - "DecrKey4": {Value: int64(10)}, - }, - want: 9, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.presetValues != nil { - for k, d := range tt.presetValues { - presetKeyData(server, context.Background(), k, d) - } - } - got, err := server.Decr(tt.key) - if (err != nil) != tt.wantErr { - t.Errorf("DECR() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("DECR() got = %v, want %v", got, tt.want) - } - }) - } -} - -func TestSugarDB_INCRBY(t *testing.T) { - server := createSugarDB() - - tests := []struct { - name string - key string - increment string - presetValues map[string]internal.KeyData - want int - wantErr bool - }{ - { - name: "1. Increment non-existent key by 4", - key: "IncrByKey1", - increment: "4", - presetValues: nil, - want: 4, - wantErr: false, - }, - { - name: "2. Increment existing key with integer value by 3", - key: "IncrByKey2", - increment: "3", - presetValues: map[string]internal.KeyData{ - "IncrByKey2": {Value: "5"}, - }, - want: 8, - wantErr: false, - }, - { - name: "3. Increment existing key with non-integer value by 2", - key: "IncrByKey3", - increment: "2", - presetValues: map[string]internal.KeyData{ - "IncrByKey3": {Value: "not_an_int"}, - }, - want: 0, - wantErr: true, - }, - { - name: "4. Increment existing key with int64 value by 7", - key: "IncrByKey4", - increment: "7", - presetValues: map[string]internal.KeyData{ - "IncrByKey4": {Value: int64(10)}, - }, - want: 17, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.presetValues != nil { - for k, d := range tt.presetValues { - presetKeyData(server, context.Background(), k, d) - } - } - got, err := server.IncrBy(tt.key, tt.increment) - if (err != nil) != tt.wantErr { - t.Errorf("IncrBy() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("IncrBy() got = %v, want %v", got, tt.want) - } - }) - } -} - -func TestSugarDB_INCRBYFLOAT(t *testing.T) { - server := createSugarDB() - - tests := []struct { - name string - key string - increment string - presetValues map[string]internal.KeyData - want float64 - wantErr bool - }{ - { - name: "1. Increment non-existent key by 2.5", - key: "IncrByFloatKey1", - increment: "2.5", - presetValues: nil, - want: 2.5, - wantErr: false, - }, - { - name: "2. Increment existing key with integer value by 1.2", - key: "IncrByFloatKey2", - increment: "1.2", - presetValues: map[string]internal.KeyData{ - "IncrByFloatKey2": {Value: "5"}, - }, - want: 6.2, - wantErr: false, - }, - { - name: "3. Increment existing key with float value by 0.7", - key: "IncrByFloatKey4", - increment: "0.7", - presetValues: map[string]internal.KeyData{ - "IncrByFloatKey4": {Value: "10.0"}, - }, - want: 10.7, - wantErr: false, - }, - { - name: "4. Increment existing key with scientific notation value by 200", - key: "IncrByFloatKey5", - increment: "200", - presetValues: map[string]internal.KeyData{ - "IncrByFloatKey5": {Value: "5.0e3"}, - }, - want: 5200, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.presetValues != nil { - for k, d := range tt.presetValues { - presetKeyData(server, context.Background(), k, d) - } - } - got, err := server.IncrByFloat(tt.key, tt.increment) - if (err != nil) != tt.wantErr { - t.Errorf("IncrByFloat() error = %v, wantErr %v", err, tt.wantErr) - return - } - if err == nil && got != tt.want { - t.Errorf("IncrByFloat() got = %v, want %v", got, tt.want) - } - }) - } -} - -func TestSugarDB_DECRBY(t *testing.T) { - server := createSugarDB() - - tests := []struct { - name string - key string - decrement string - presetValues map[string]internal.KeyData - want int - wantErr bool - }{ - { - name: "1. Decrement non-existent key by 4", - key: "DecrByKey1", - decrement: "4", - presetValues: nil, - want: -4, - wantErr: false, - }, - { - name: "2. Decrement existing key with integer value by 3", - key: "DecrByKey2", - decrement: "3", - presetValues: map[string]internal.KeyData{ - "DecrByKey2": {Value: "-5"}, - }, - want: -8, - wantErr: false, - }, - { - name: "3. Decrement existing key with non-integer value by 2", - key: "DecrByKey3", - decrement: "2", - presetValues: map[string]internal.KeyData{ - "DecrByKey3": {Value: "not_an_int"}, - }, - want: 0, - wantErr: true, - }, - { - name: "4. Decrement existing key with int64 value by 7", - key: "DecrByKey4", - decrement: "7", - presetValues: map[string]internal.KeyData{ - "DecrByKey4": {Value: int64(10)}}, - want: 3, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.presetValues != nil { - for k, d := range tt.presetValues { - presetKeyData(server, context.Background(), k, d) - } - } - got, err := server.DecrBy(tt.key, tt.decrement) - if (err != nil) != tt.wantErr { - t.Errorf("DecrBy() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("DecrBy() got = %v, want %v", got, tt.want) - } - }) - } -} - -func TestSugarDB_Rename(t *testing.T) { - server := createSugarDB() - - tests := []struct { - name string - oldKey string - newKey string - presetValues map[string]internal.KeyData - want string - wantErr bool - }{ - { - name: "1. Rename existing key", - oldKey: "oldKey1", - newKey: "newKey1", - presetValues: map[string]internal.KeyData{"oldKey1": {Value: "value1"}}, - want: "OK", - wantErr: false, - }, - { - name: "2. Rename non-existent key", - oldKey: "oldKey2", - newKey: "newKey2", - presetValues: nil, - want: "", - wantErr: true, - }, - { - name: "3. Rename to existing key", - oldKey: "oldKey3", - newKey: "newKey4", - presetValues: map[string]internal.KeyData{"oldKey3": {Value: "value3"}, "newKey4": {Value: "value4"}}, - want: "OK", - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.presetValues != nil { - for k, d := range tt.presetValues { - presetKeyData(server, context.Background(), k, d) - } - } - got, err := server.Rename(tt.oldKey, tt.newKey) - if (err != nil) != tt.wantErr { - t.Errorf("Rename() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("Rename() got = %v, want %v", got, tt.want) - } - }) - } -} - -func TestSugarDB_Renamenx(t *testing.T) { - server := createSugarDB() - - tests := []struct { - name string - oldKey string - newKey string - presetValues map[string]internal.KeyData - want string - wantErr bool - }{ - { - name: "1. Rename existing key", - oldKey: "oldKey1", - newKey: "newKey1", - presetValues: map[string]internal.KeyData{"oldKey1": {Value: "value1"}}, - want: "OK", - wantErr: false, - }, - { - name: "2. Rename non-existent key", - oldKey: "oldKey2", - newKey: "newKey2", - presetValues: nil, - want: "", - wantErr: true, - }, - { - name: "3. Rename to existing key", - oldKey: "oldKey3", - newKey: "newKey4", - presetValues: map[string]internal.KeyData{"oldKey3": {Value: "value3"}, "newKey4": {Value: "value4"}}, - want: "", - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.presetValues != nil { - for k, d := range tt.presetValues { - presetKeyData(server, context.Background(), k, d) - } - } - got, err := server.RenameNX(tt.oldKey, tt.newKey) - if (err != nil) != tt.wantErr { - t.Errorf("Rename() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("Rename() got = %v, want %v", got, tt.want) - } - }) - } -} -func TestSugarDB_RANDOMKEY(t *testing.T) { - server := createSugarDB() - - // test without keys - got, err := server.RandomKey() - if err != nil { - t.Error(err) - return - } - if got != "" { - t.Errorf("RANDOMKEY error, expected emtpy string (%v), got (%v)", []byte(""), []byte(got)) - } - - // test with keys - testkeys := []string{"key1", "key2", "key3"} - for _, k := range testkeys { - err := presetValue(server, context.Background(), k, "") - if err != nil { - t.Error(err) - return - } - } - - actual, err := server.RandomKey() - if err != nil { - t.Error(err) - return - } - if !strings.Contains(actual, "key") { - t.Errorf("RANDOMKEY error, expected one of %v, got %s", testkeys, got) - } - -} - -func TestSugarDB_Exists(t *testing.T) { - server := createSugarDB() - - // Test with no keys - keys := []string{"key1", "key2", "key3"} - existsCount, err := server.Exists(keys...) - if err != nil { - t.Error(err) - return - } - if existsCount != 0 { - t.Errorf("EXISTS error, expected 0, got %d", existsCount) - } - - // Test with some keys - for _, k := range keys { - err := presetValue(server, context.Background(), k, "") - if err != nil { - t.Error(err) - return - } - } - - existsCount, err = server.Exists(keys...) - if err != nil { - t.Error(err) - return - } - if existsCount != len(keys) { - t.Errorf("EXISTS error, expected %d, got %d", len(keys), existsCount) - } -} - -func TestSugarDB_DBSize(t *testing.T) { - server := createSugarDB() - got, err := server.DBSize() - if err != nil { - t.Error(err) - return - } - if got != 0 { - t.Errorf("DBSIZE error, expected 0, got %d", got) - } - - // test with keys - testkeys := []string{"1", "2", "3"} - for _, k := range testkeys { - err := presetValue(server, context.Background(), k, "") - if err != nil { - t.Error(err) - return - } - } - - got, err = server.DBSize() - if err != nil { - t.Error(err) - return - } - if got != len(testkeys) { - t.Errorf("DBSIZE error, expected %d, got %d", len(testkeys), got) - } -} - -func TestSugarDB_GETDEL(t *testing.T) { - server := createSugarDB() - - tests := []struct { - name string - presetValue interface{} - key string - want string - wantErr bool - }{ - { - name: "Return string from existing key", - presetValue: "value1", - key: "key1", - want: "value1", - wantErr: false, - }, - { - name: "Return empty string if the key does not exist", - presetValue: nil, - key: "key2", - want: "", - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.presetValue != nil { - err := presetValue(server, context.Background(), tt.key, tt.presetValue) - if err != nil { - t.Error(err) + got, err := server.Get(tt.key) + if (err != nil) != tt.wantErr { + t.Errorf("GET() error = %v, wantErr %v", err, tt.wantErr) return } + if got != tt.want { + t.Errorf("GET() got = %v, want %v", got, tt.want) + } + }) + } + }) + + t.Run("TestSugarDB_MGET", func(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + presetValues map[string]interface{} + keys []string + want []string + wantErr bool + }{ + { + name: "1. Get all values in the same order the keys were provided in", + presetValues: map[string]interface{}{ + "mget_key1": "value1", "mget_key2": "value2", "mget_key3": "value3", "mget_key4": "value4", + }, + keys: []string{"mget_key1", "mget_key4", "mget_key2", "mget_key3", "mget_key1"}, + want: []string{"value1", "value4", "value2", "value3", "value1"}, + wantErr: false, + }, + { + name: "2. Return empty strings for non-existent keys", + presetValues: map[string]interface{}{ + "mget_key5": "value5", "mget_key6": "value6", "mget_key7": "value7", + }, + keys: []string{ + "mget_key5", "mget_key6", "mget_non-existent", "mget_non-existent", "mget_key7", "mget_non-existent", + }, + want: []string{"value5", "value6", "", "", "value7", ""}, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if tt.presetValues != nil { + for k, v := range tt.presetValues { + err := presetValue(server, context.Background(), k, v) + if err != nil { + t.Error(err) + return + } + } + } + got, err := server.MGet(tt.keys...) + if (err != nil) != tt.wantErr { + t.Errorf("MGET() error = %v, wantErr %v", err, tt.wantErr) + return + } + if len(got) != len(tt.want) { + t.Errorf("MGET() got = %v, want %v", got, tt.want) + } + for _, g := range got { + if !slices.Contains(tt.want, g) { + t.Errorf("MGET() got = %v, want %v", got, tt.want) + } + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("MGET() got = %v, want %v", got, tt.want) + } + }) + } + }) + + t.Run("TestSugarDB_SET", func(t *testing.T) { + t.Parallel() + + SetOptions := func(W SetWriteOption, EX SetExOption, EXTIME int, GET bool) SETOptions { + return SETOptions{ + WriteOpt: W, + ExpireOpt: EX, + ExpireTime: EXTIME, + Get: GET, } - // Check value received - got, err := server.GetDel(tt.key) - if (err != nil) != tt.wantErr { - t.Errorf("GETDEL() error = %v, wantErr %v", err, tt.wantErr) + } + + tests := []struct { + name string + presetValues map[string]internal.KeyData + key string + value string + options SETOptions + wantOk bool + wantPrev string + wantErr bool + }{ + { + name: "1. Set normal value", + presetValues: nil, + key: "set_key1", + value: "value1", + options: SetOptions(nil, nil, 0, false), + wantOk: true, + wantPrev: "", + wantErr: false, + }, + { + name: "2. Only set the value if the key does not exist", + presetValues: nil, + key: "set_key2", + value: "value2", + options: SetOptions(SETNX, nil, 0, false), + wantOk: true, + wantPrev: "", + wantErr: false, + }, + { + name: "3. Throw error when value already exists with NX flag passed", + presetValues: map[string]internal.KeyData{ + "set_key3": { + Value: "preset-value3", + ExpireAt: time.Time{}, + }, + }, + key: "set_key3", + value: "value3", + options: SetOptions(SETNX, nil, 0, false), + wantOk: false, + wantPrev: "", + wantErr: true, + }, + { + name: "4. Set new key value when key exists with XX flag passed", + presetValues: map[string]internal.KeyData{ + "set_key4": { + Value: "preset-value4", + ExpireAt: time.Time{}, + }, + }, + key: "set_key4", + value: "value4", + options: SetOptions(SETXX, nil, 0, false), + wantOk: true, + wantPrev: "", + wantErr: false, + }, + { + name: "5. Return error when setting non-existent key with XX flag", + presetValues: nil, + key: "set_key5", + value: "value5", + options: SetOptions(SETXX, nil, 0, false), + wantOk: false, + wantPrev: "", + wantErr: true, + }, + { + name: "6. Set expiry time on the key to 100 seconds from now", + presetValues: nil, + key: "set_key6", + value: "value6", + options: SetOptions(nil, SETEX, 100, false), + wantOk: true, + wantPrev: "", + wantErr: false, + }, + { + name: "7. Set expiry time on the key in unix milliseconds", + presetValues: nil, + key: "set_key7", + value: "value7", + options: SetOptions(nil, SETPX, 4096, false), + wantOk: true, + wantPrev: "", + wantErr: false, + }, + { + name: "8. Set exact expiry time in seconds from unix epoch", + presetValues: nil, + key: "set_key8", + value: "value8", + options: SetOptions(nil, SETEXAT, int(mockClock.Now().Add(200*time.Second).Unix()), false), + wantOk: true, + wantPrev: "", + wantErr: false, + }, + { + name: "9. Set exact expiry time in milliseconds from unix epoch", + key: "set_key9", + value: "value9", + options: SetOptions(nil, SETPXAT, int(mockClock.Now().Add(4096*time.Millisecond).UnixMilli()), false), + presetValues: nil, + wantOk: true, + wantPrev: "", + wantErr: false, + }, + { + name: "10. Get the previous value when GET flag is passed", + presetValues: map[string]internal.KeyData{ + "set_key10": { + Value: "previous-value", + ExpireAt: time.Time{}, + }, + }, + key: "set_key10", + value: "value10", + options: SetOptions(nil, SETEX, 1000, true), + wantOk: true, + wantPrev: "previous-value", + wantErr: false, + }, + { + name: "11. Return nil when GET value is passed and no previous value exists", + presetValues: nil, + key: "set_key11", + value: "value11", + options: SetOptions(nil, SETEX, 1000, true), + wantOk: true, + wantPrev: "", + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if tt.presetValues != nil { + for k, d := range tt.presetValues { + presetKeyData(server, context.Background(), k, d) + } + } + previousValue, ok, err := server.Set( + tt.key, + tt.value, + tt.options, + ) + if (err != nil) != tt.wantErr { + t.Errorf("SET() error = %v, wantErr %v", err, tt.wantErr) + return + } + if ok != tt.wantOk { + t.Errorf("SET() ok got = %v, want %v", ok, tt.wantOk) + } + if previousValue != tt.wantPrev { + t.Errorf("SET() previous value got = %v, want %v", previousValue, tt.wantPrev) + } + }) + } + }) + + t.Run("TestSugarDB_MSET", func(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + kvPairs map[string]string + want bool + wantErr bool + }{ + { + name: "1. Set multiple keys", + kvPairs: map[string]string{"mset_key1": "value1", "mset_key2": "10", "mset_key3": "3.142"}, + want: true, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + got, err := server.MSet(tt.kvPairs) + if (err != nil) != tt.wantErr { + t.Errorf("MSET() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("MSET() got = %v, want %v", got, tt.want) + } + }) + } + }) + + t.Run("TestSugarDB_PERSIST", func(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + presetValues map[string]internal.KeyData + key string + want bool + wantErr bool + }{ + { + name: "1. Successfully persist a volatile key", + key: "persist_key1", + presetValues: map[string]internal.KeyData{ + "persist_key1": {Value: "value1", ExpireAt: mockClock.Now().Add(1000 * time.Second)}, + }, + want: true, + wantErr: false, + }, + { + name: "2. Return false when trying to persist a non-existent key", + key: "persist_key2", + presetValues: nil, + want: false, + wantErr: false, + }, + { + name: "3. Return false when trying to persist a non-volatile key", + key: "persist_key3", + presetValues: map[string]internal.KeyData{ + "persist_key3": {Value: "value3", ExpireAt: time.Time{}}, + }, + want: false, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if tt.presetValues != nil { + for k, d := range tt.presetValues { + presetKeyData(server, context.Background(), k, d) + } + } + got, err := server.Persist(tt.key) + if (err != nil) != tt.wantErr { + t.Errorf("PERSIST() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("PERSIST() got = %v, want %v", got, tt.want) + } + }) + } + }) + + t.Run("TestSugarDB_TTL", func(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + presetValues map[string]internal.KeyData + key string + ttlFunc func(key string) (int, error) + want int + wantErr bool + }{ + { + name: "1. Return TTL time in seconds", + key: "ttl_key1", + presetValues: map[string]internal.KeyData{ + "ttl_key1": {Value: "value1", ExpireAt: mockClock.Now().Add(100 * time.Second)}, + }, + ttlFunc: server.TTL, + want: 100, + wantErr: false, + }, + { + name: "2. Return TTL time in milliseconds", + key: "ttl_key2", + ttlFunc: server.PTTL, + presetValues: map[string]internal.KeyData{ + "ttl_key2": {Value: "value2", ExpireAt: mockClock.Now().Add(4096 * time.Millisecond)}, + }, + want: 4096, + wantErr: false, + }, + { + name: "3. If the key is non-volatile, return -1", + key: "ttl_key3", + ttlFunc: server.TTL, + presetValues: map[string]internal.KeyData{ + "ttl_key3": {Value: "value3", ExpireAt: time.Time{}}, + }, + want: -1, + wantErr: false, + }, + { + name: "4. If the key is non-existent return -2", + key: "ttl_key4", + ttlFunc: server.TTL, + presetValues: nil, + want: -2, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if tt.presetValues != nil { + for k, d := range tt.presetValues { + presetKeyData(server, context.Background(), k, d) + } + } + got, err := tt.ttlFunc(tt.key) + if (err != nil) != tt.wantErr { + t.Errorf("TTL() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("TTL() got = %v, want %v", got, tt.want) + } + }) + } + }) + + t.Run("TestSugarDB_INCR", func(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + key string + presetValues map[string]internal.KeyData + want int + wantErr bool + }{ + { + name: "1. Increment non-existent key", + key: "IncrKey1", + presetValues: nil, + want: 1, + wantErr: false, + }, + { + name: "2. Increment existing key with integer value", + key: "IncrKey2", + presetValues: map[string]internal.KeyData{ + "IncrKey2": {Value: "5"}, + }, + want: 6, + wantErr: false, + }, + { + name: "3. Increment existing key with non-integer value", + key: "IncrKey3", + presetValues: map[string]internal.KeyData{ + "IncrKey3": {Value: "not_an_int"}, + }, + want: 0, + wantErr: true, + }, + { + name: "4. Increment existing key with int64 value", + key: "IncrKey4", + presetValues: map[string]internal.KeyData{ + "IncrKey4": {Value: int64(10)}, + }, + want: 11, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if tt.presetValues != nil { + for k, d := range tt.presetValues { + presetKeyData(server, context.Background(), k, d) + } + } + got, err := server.Incr(tt.key) + if (err != nil) != tt.wantErr { + t.Errorf("INCR() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("INCR() got = %v, want %v", got, tt.want) + } + }) + } + }) + + t.Run("TestSugarDB_DECR", func(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + key string + presetValues map[string]internal.KeyData + want int + wantErr bool + }{ + { + name: "1. Decrement non-existent key", + key: "DecrKey1", + presetValues: nil, + want: -1, + wantErr: false, + }, + { + name: "2. Decrement existing key with integer value", + key: "DecrKey2", + presetValues: map[string]internal.KeyData{ + "DecrKey2": {Value: "5"}, + }, + want: 4, + wantErr: false, + }, + { + name: "3. Decrement existing key with non-integer value", + key: "DecrKey3", + presetValues: map[string]internal.KeyData{ + "DecrKey3": {Value: "not_an_int"}, + }, + want: 0, + wantErr: true, + }, + { + name: "4. Decrement existing key with int64 value", + key: "DecrKey4", + presetValues: map[string]internal.KeyData{ + "DecrKey4": {Value: int64(10)}, + }, + want: 9, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if tt.presetValues != nil { + for k, d := range tt.presetValues { + presetKeyData(server, context.Background(), k, d) + } + } + got, err := server.Decr(tt.key) + if (err != nil) != tt.wantErr { + t.Errorf("DECR() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("DECR() got = %v, want %v", got, tt.want) + } + }) + } + }) + + t.Run("TestSugarDB_INCRBY", func(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + key string + increment string + presetValues map[string]internal.KeyData + want int + wantErr bool + }{ + { + name: "1. Increment non-existent key by 4", + key: "IncrByKey1", + increment: "4", + presetValues: nil, + want: 4, + wantErr: false, + }, + { + name: "2. Increment existing key with integer value by 3", + key: "IncrByKey2", + increment: "3", + presetValues: map[string]internal.KeyData{ + "IncrByKey2": {Value: "5"}, + }, + want: 8, + wantErr: false, + }, + { + name: "3. Increment existing key with non-integer value by 2", + key: "IncrByKey3", + increment: "2", + presetValues: map[string]internal.KeyData{ + "IncrByKey3": {Value: "not_an_int"}, + }, + want: 0, + wantErr: true, + }, + { + name: "4. Increment existing key with int64 value by 7", + key: "IncrByKey4", + increment: "7", + presetValues: map[string]internal.KeyData{ + "IncrByKey4": {Value: int64(10)}, + }, + want: 17, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if tt.presetValues != nil { + for k, d := range tt.presetValues { + presetKeyData(server, context.Background(), k, d) + } + } + got, err := server.IncrBy(tt.key, tt.increment) + if (err != nil) != tt.wantErr { + t.Errorf("IncrBy() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("IncrBy() got = %v, want %v", got, tt.want) + } + }) + } + }) + + t.Run("TestSugarDB_INCRBYFLOAT", func(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + key string + increment string + presetValues map[string]internal.KeyData + want float64 + wantErr bool + }{ + { + name: "1. Increment non-existent key by 2.5", + key: "IncrByFloatKey1", + increment: "2.5", + presetValues: nil, + want: 2.5, + wantErr: false, + }, + { + name: "2. Increment existing key with integer value by 1.2", + key: "IncrByFloatKey2", + increment: "1.2", + presetValues: map[string]internal.KeyData{ + "IncrByFloatKey2": {Value: "5"}, + }, + want: 6.2, + wantErr: false, + }, + { + name: "3. Increment existing key with float value by 0.7", + key: "IncrByFloatKey4", + increment: "0.7", + presetValues: map[string]internal.KeyData{ + "IncrByFloatKey4": {Value: "10.0"}, + }, + want: 10.7, + wantErr: false, + }, + { + name: "4. Increment existing key with scientific notation value by 200", + key: "IncrByFloatKey5", + increment: "200", + presetValues: map[string]internal.KeyData{ + "IncrByFloatKey5": {Value: "5.0e3"}, + }, + want: 5200, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if tt.presetValues != nil { + for k, d := range tt.presetValues { + presetKeyData(server, context.Background(), k, d) + } + } + got, err := server.IncrByFloat(tt.key, tt.increment) + if (err != nil) != tt.wantErr { + t.Errorf("IncrByFloat() error = %v, wantErr %v", err, tt.wantErr) + return + } + if err == nil && got != tt.want { + t.Errorf("IncrByFloat() got = %v, want %v", got, tt.want) + } + }) + } + }) + + t.Run("TestSugarDB_DECRBY", func(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + key string + decrement string + presetValues map[string]internal.KeyData + want int + wantErr bool + }{ + { + name: "1. Decrement non-existent key by 4", + key: "DecrByKey1", + decrement: "4", + presetValues: nil, + want: -4, + wantErr: false, + }, + { + name: "2. Decrement existing key with integer value by 3", + key: "DecrByKey2", + decrement: "3", + presetValues: map[string]internal.KeyData{ + "DecrByKey2": {Value: "-5"}, + }, + want: -8, + wantErr: false, + }, + { + name: "3. Decrement existing key with non-integer value by 2", + key: "DecrByKey3", + decrement: "2", + presetValues: map[string]internal.KeyData{ + "DecrByKey3": {Value: "not_an_int"}, + }, + want: 0, + wantErr: true, + }, + { + name: "4. Decrement existing key with int64 value by 7", + key: "DecrByKey4", + decrement: "7", + presetValues: map[string]internal.KeyData{ + "DecrByKey4": {Value: int64(10)}}, + want: 3, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if tt.presetValues != nil { + for k, d := range tt.presetValues { + presetKeyData(server, context.Background(), k, d) + } + } + got, err := server.DecrBy(tt.key, tt.decrement) + if (err != nil) != tt.wantErr { + t.Errorf("DecrBy() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("DecrBy() got = %v, want %v", got, tt.want) + } + }) + } + }) + + t.Run("TestSugarDB_Rename", func(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + oldKey string + newKey string + presetValues map[string]internal.KeyData + want string + wantErr bool + }{ + { + name: "1. Rename existing key", + oldKey: "rename_oldKey1", + newKey: "rename_newKey1", + presetValues: map[string]internal.KeyData{"rename_oldKey1": {Value: "value1"}}, + want: "OK", + wantErr: false, + }, + { + name: "2. Rename non-existent key", + oldKey: "rename_oldKey2", + newKey: "rename_newKey2", + presetValues: nil, + want: "", + wantErr: true, + }, + { + name: "3. Rename to existing key", + oldKey: "rename_oldKey3", + newKey: "rename_newKey4", + presetValues: map[string]internal.KeyData{ + "rename_oldKey3": {Value: "value3"}, + "rename_newKey4": {Value: "value4"}, + }, + want: "OK", + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if tt.presetValues != nil { + for k, d := range tt.presetValues { + presetKeyData(server, context.Background(), k, d) + } + } + got, err := server.Rename(tt.oldKey, tt.newKey) + if (err != nil) != tt.wantErr { + t.Errorf("Rename() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("Rename() got = %v, want %v", got, tt.want) + } + }) + } + }) + + t.Run("TestSugarDB_RENAMENX", func(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + oldKey string + newKey string + presetValues map[string]internal.KeyData + want string + wantErr bool + }{ + { + name: "1. Rename existing key", + oldKey: "renamenx_oldKey1", + newKey: "renamenx_newKey1", + presetValues: map[string]internal.KeyData{"renamenx_oldKey1": {Value: "value1"}}, + want: "OK", + wantErr: false, + }, + { + name: "2. Rename non-existent key", + oldKey: "renamenx_oldKey2", + newKey: "renamenx_newKey2", + presetValues: nil, + want: "", + wantErr: true, + }, + { + name: "3. Rename to existing key", + oldKey: "renamenx_oldKey3", + newKey: "renamenx_newKey4", + presetValues: map[string]internal.KeyData{ + "renamenx_oldKey3": {Value: "value3"}, + "renamenx_newKey4": {Value: "value4"}, + }, + want: "", + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if tt.presetValues != nil { + for k, d := range tt.presetValues { + presetKeyData(server, context.Background(), k, d) + } + } + got, err := server.RenameNX(tt.oldKey, tt.newKey) + if (err != nil) != tt.wantErr { + t.Errorf("Rename() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("Rename() got = %v, want %v", got, tt.want) + } + }) + } + }) + + t.Run("TestSugarDB_RANDOMKEY", func(t *testing.T) { + t.Parallel() + + server := createSugarDB() + t.Cleanup(func() { + server.ShutDown() + }) + + // test without keys + got, err := server.RandomKey() + if err != nil { + t.Error(err) + return + } + if got != "" { + t.Errorf("RANDOMKEY error, expected emtpy string (%v), got (%v)", []byte(""), []byte(got)) + } + + // test with keys + testKeys := []string{"randomkey_key1", "randomkey_key2", "randomkey_key3"} + for _, k := range testKeys { + err := presetValue(server, context.Background(), k, "") + if err != nil { + t.Error(err) return } - if got != tt.want { - t.Errorf("GETDEL() got = %v, want %v", got, tt.want) + } + + actual, err := server.RandomKey() + if err != nil { + t.Error(err) + return + } + if !strings.Contains(actual, "key") { + t.Errorf("RANDOMKEY error, expected one of %v, got %s", testKeys, got) + } + + }) + + t.Run("TestSugarDB_EXISTS", func(t *testing.T) { + t.Parallel() + + // Test with no keys + keys := []string{"exists_key1", "exists_key2", "exists_key3"} + existsCount, err := server.Exists(keys...) + if err != nil { + t.Error(err) + return + } + if existsCount != 0 { + t.Errorf("EXISTS error, expected 0, got %d", existsCount) + } + + // Test with some keys + for _, k := range keys { + err := presetValue(server, context.Background(), k, "") + if err != nil { + t.Error(err) + return } - // Check key was deleted - if tt.presetValue != nil { - got, err := server.Get(tt.key) + } + + existsCount, err = server.Exists(keys...) + if err != nil { + t.Error(err) + return + } + if existsCount != len(keys) { + t.Errorf("EXISTS error, expected %d, got %d", len(keys), existsCount) + } + }) + + t.Run("TestSugarDB_DBSIZE", func(t *testing.T) { + t.Parallel() + + server := createSugarDB() + t.Cleanup(func() { + server.ShutDown() + }) + + got, err := server.DBSize() + if err != nil { + t.Error(err) + return + } + if got != 0 { + t.Errorf("DBSIZE error, expected 0, got %d", got) + } + + // test with keys + testKeys := []string{"1", "2", "3"} + for _, k := range testKeys { + err := presetValue(server, context.Background(), k, "") + if err != nil { + t.Error(err) + return + } + } + + got, err = server.DBSize() + if err != nil { + t.Error(err) + return + } + if got != len(testKeys) { + t.Errorf("DBSIZE error, expected %d, got %d", len(testKeys), got) + } + }) + + t.Run("TestSugarDB_GETDEL", func(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + presetValue interface{} + key string + want string + wantErr bool + }{ + { + name: "1. Return string from existing key", + presetValue: "value1", + key: "getdel_key1", + want: "value1", + wantErr: false, + }, + { + name: "2. Return empty string if the key does not exist", + presetValue: nil, + key: "getdel_key2", + want: "", + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if tt.presetValue != nil { + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } + } + // Check value received + got, err := server.GetDel(tt.key) if (err != nil) != tt.wantErr { t.Errorf("GETDEL() error = %v, wantErr %v", err, tt.wantErr) return } - if got != "" { - t.Errorf("GETDEL() got = %v, want empty string", got) + if got != tt.want { + t.Errorf("GETDEL() got = %v, want %v", got, tt.want) } - } - }) - } -} - -func TestSugarDB_GETEX(t *testing.T) { - mockClock := clock.NewClock() - server := createSugarDB() - - tests := []struct { - name string - presetValue interface{} - getExOpt GetExOption - getExOptTime int - key string - want string - wantEx int - wantErr bool - }{ - { - name: "1. Return string from existing key, no expire options", - presetValue: "value1", - getExOpt: nil, - key: "key1", - want: "value1", - wantEx: -1, - wantErr: false, - }, - { - name: "2. Return empty string if the key does not exist", - presetValue: nil, - getExOpt: EX, - getExOptTime: int(mockClock.Now().Add(100 * time.Second).Unix()), - key: "key2", - want: "", - wantEx: 0, - wantErr: false, - }, - { - name: "3. Return key set expiry with EX", - presetValue: "value3", - getExOpt: EX, - getExOptTime: 100, - key: "key3", - want: "value3", - wantEx: 100, - wantErr: false, - }, - { - name: "4. Return key set expiry with PX", - presetValue: "value4", - getExOpt: PX, - getExOptTime: 100000, - key: "key4", - want: "value4", - wantEx: 100, - wantErr: false, - }, - { - name: "5. Return key set expiry with EXAT", - presetValue: "value5", - getExOpt: EXAT, - getExOptTime: int(mockClock.Now().Add(100 * time.Second).Unix()), - key: "key5", - want: "value5", - wantEx: 100, - wantErr: false, - }, - { - name: "6. Return key set expiry with PXAT", - presetValue: "value6", - getExOpt: PXAT, - getExOptTime: int(mockClock.Now().Add(100 * time.Second).UnixMilli()), - key: "key6", - want: "value6", - wantEx: 100, - wantErr: false, - }, - { - name: "7. Return key passing PERSIST", - presetValue: "value7", - getExOpt: PERSIST, - key: "key7", - want: "value7", - wantEx: -1, - wantErr: false, - }, - { - name: "8. Return key passing PERSIST, and include a UNIXTIME", - presetValue: "value8", - getExOpt: PERSIST, - getExOptTime: int(mockClock.Now().Add(100 * time.Second).Unix()), - key: "key8", - want: "value8", - wantEx: -1, - wantErr: false, - }, - { - name: "9. Return key and attempt to set expiry with EX without providing UNIXTIME", - presetValue: "value9", - getExOpt: EX, - key: "key9", - want: "value9", - wantEx: -1, - wantErr: false, - }, - { - name: "10. Return key and attempt to set expiry with PXAT without providing UNIXTIME", - presetValue: "value10", - getExOpt: PXAT, - key: "key10", - want: "value10", - wantEx: -1, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.presetValue != nil { - err := presetValue(server, context.Background(), tt.key, tt.presetValue) - if err != nil { - t.Error(err) - return - } - } - // Check value received - got, err := server.GetEx(tt.key, tt.getExOpt, tt.getExOptTime) - if (err != nil) != tt.wantErr { - t.Errorf("GETEX() GET error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("GETEX() GET - got = %v, want %v", got, tt.want) - } - // Check expiry was set - if tt.presetValue != nil { - actual, err := server.TTL(tt.key) - if (err != nil) != tt.wantErr { - t.Errorf("GETEX() EXPIRY error = %v, wantErr %v", err, tt.wantErr) - return - } - if actual != tt.wantEx { - t.Errorf("GETEX() EXPIRY - got = %v, want %v", actual, tt.wantEx) - } - } - }) - } -} - -// Tests Touch and OBJECTFREQ commands -func TestSugarDB_LFU_TOUCH(t *testing.T) { - - duration := time.Duration(30) * time.Second - - server := createSugarDBWithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.AllKeysLFU, - EvictionInterval: duration, - MaxMemory: 4000000, - }) - - tests := []struct { - name string - keys []string - setKeys []bool - want int - wantErrs []bool - }{ - { - name: "1. Touch key that exists.", - keys: []string{"Key1"}, - setKeys: []bool{true}, - want: 1, - wantErrs: []bool{false}, - }, - { - name: "2. Touch key that doesn't exist.", - keys: []string{"Key2"}, - setKeys: []bool{false}, - want: 0, - wantErrs: []bool{true}, - }, - { - name: "3. Touch multiple keys that all exist.", - keys: []string{"Key3", "Key3.1"}, - setKeys: []bool{true, true}, - want: 2, - wantErrs: []bool{false, false}, - }, - { - name: "4. Touch multiple keys, some don't exist.", - keys: []string{"Key4", "Key4.9"}, - setKeys: []bool{true, false}, - want: 1, - wantErrs: []bool{false, true}, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Preset values - for i, key := range tt.keys { - if tt.setKeys[i] { - err := presetValue(server, context.Background(), key, "___") - if err != nil { - t.Error(err) + // Check key was deleted + if tt.presetValue != nil { + got, err := server.Get(tt.key) + if (err != nil) != tt.wantErr { + t.Errorf("GETDEL() error = %v, wantErr %v", err, tt.wantErr) return } - } - } - - // Touch keys - got, err := server.Touch(tt.keys...) - if err != nil { - t.Errorf("TOUCH() error - %v", err) - } - - if got != tt.want { - t.Errorf("TOUCH() got %v, want %v, using keys %v setKeys %v", got, tt.want, tt.keys, tt.setKeys) - } - - // Another touch to help testing object freq - got, err = server.Touch(tt.keys...) - if err != nil { - t.Errorf("TOUCH() error - %v", err) - } - - if got != tt.want { - t.Errorf("TOUCH() got %v, want %v, using keys %v setKeys %v", got, tt.want, tt.keys, tt.setKeys) - } - - // Wait to avoid race - ticker := time.NewTicker(200 * time.Millisecond) - <-ticker.C - ticker.Stop() - - // Objectfreq - for i, key := range tt.keys { - actual, err := server.ObjectFreq(key) - if (err != nil) != tt.wantErrs[i] { - t.Errorf("OBJECTFREQ() error: %v, wanted error: %v", err, tt.wantErrs[i]) - } - if !tt.wantErrs[i] && actual != 3 { - t.Errorf("OBJECTFREQ() error - expected 3 got %v for key %v", actual, key) - } - - // Check error for object idletime - _, err = server.ObjectIdleTime(key) - if err == nil { - t.Errorf("OBJECTIDLETIME() error - expected error when used on server with lfu eviction policy but got none.") - } - - } - - }) - } -} - -// Tests Touch and OBJECTIDLETIME commands -func TestSugarDB_LRU_TOUCH(t *testing.T) { - - duration := time.Duration(30) * time.Second - - server := createSugarDBWithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.AllKeysLRU, - EvictionInterval: duration, - MaxMemory: 4000000, - }) - - tests := []struct { - name string - keys []string - setKeys []bool - want int - wantErrs []bool - }{ - { - name: "1. Touch key that exists.", - keys: []string{"Key1"}, - setKeys: []bool{true}, - want: 1, - wantErrs: []bool{false}, - }, - { - name: "2. Touch key that doesn't exist.", - keys: []string{"Key2"}, - setKeys: []bool{false}, - want: 0, - wantErrs: []bool{true}, - }, - { - name: "3. Touch multiple keys that all exist.", - keys: []string{"Key3", "Key3.1"}, - setKeys: []bool{true, true}, - want: 2, - wantErrs: []bool{false, false}, - }, - { - name: "4. Touch multiple keys, some don't exist.", - keys: []string{"Key4", "Key4.9"}, - setKeys: []bool{true, false}, - want: 1, - wantErrs: []bool{false, true}, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Preset values - for i, key := range tt.keys { - if tt.setKeys[i] { - err := presetValue(server, context.Background(), key, "___") - if err != nil { - t.Error(err) - return + if got != "" { + t.Errorf("GETDEL() got = %v, want empty string", got) } } - } - - // Touch keys - got, err := server.Touch(tt.keys...) - if err != nil { - t.Errorf("TOUCH() error - %v", err) - } - - if got != tt.want { - t.Errorf("TOUCH() got %v, want %v, using keys %v setKeys %v", got, tt.want, tt.keys, tt.setKeys) - } - - // Sleep to more easily test Object idle time - ticker := time.NewTicker(200 * time.Millisecond) - <-ticker.C - ticker.Stop() - - // Objectidletime - for i, key := range tt.keys { - actual, err := server.ObjectIdleTime(key) - if (err != nil) != tt.wantErrs[i] { - t.Errorf("OBJECTIDLETIME() error: %v, wanted error: %v", err, tt.wantErrs[i]) - } - if !tt.wantErrs[i] && actual < 0.2 { - t.Errorf("OBJECTIDLETIME() error - expected 0.2 got %v", actual) - } - - // Check error for object freq - _, err = server.ObjectFreq(key) - if err == nil { - t.Errorf("OBJECTFREQ() error - expected error when used on server with lru eviction policy but got none.") - } - - } - - }) - } -} - -func TestSugarDB_TYPE(t *testing.T) { - server := createSugarDB() - - tests := []struct { - name string - presetValue interface{} - key string - want string - wantErr bool - }{ - { - name: "Return string from existing key", - presetValue: "value1", - key: "key1", - want: "string", - wantErr: false, - }, - { - name: "Return empty string if the key does not exist", - presetValue: nil, - key: "key2", - want: "", - wantErr: true, - }, - { - name: "Return string from existing key", - presetValue: 10, - key: "key3", - want: "integer", - wantErr: false, - }, - { - name: "Return string from existing key", - presetValue: 10.1, - key: "key4", - want: "float", - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.presetValue != nil { - err := presetValue(server, context.Background(), tt.key, tt.presetValue) - if err != nil { - t.Error(err) - return - } - } - got, err := server.Type(tt.key) - if (err != nil) != tt.wantErr { - t.Errorf("GET() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("GET() got = %v, want %v", got, tt.want) - } - }) - } -} - -func TestSugarDB_COPY(t *testing.T) { - server := createSugarDB() - - CopyOptions := func(DB string, R bool) COPYOptions { - return COPYOptions{ - Database: DB, - Replace: R, + }) } - } + }) - tests := []struct { - name string - sourceKeyPresetValue interface{} - sourcekey string - destKeyPresetValue interface{} - destinationKey string - options COPYOptions - expectedValue string - want int - wantErr bool - }{ - { - name: "Copy Value into non existing key", - sourceKeyPresetValue: "value1", - sourcekey: "skey1", - destKeyPresetValue: nil, - destinationKey: "dkey1", - options: CopyOptions("0", false), - expectedValue: "value1", - want: 1, - wantErr: false, - }, - { - name: "Copy Value into existing key without replace option", - sourceKeyPresetValue: "value2", - sourcekey: "skey2", - destKeyPresetValue: "dValue2", - destinationKey: "dkey2", - options: CopyOptions("0", false), - expectedValue: "dValue2", - want: 0, - wantErr: false, - }, - { - name: "Copy Value into existing key with replace option", - sourceKeyPresetValue: "value3", - sourcekey: "skey3", - destKeyPresetValue: "dValue3", - destinationKey: "dkey3", - options: CopyOptions("0", true), - expectedValue: "value3", - want: 1, - wantErr: false, - }, - { - name: "Copy Value into different database", - sourceKeyPresetValue: "value4", - sourcekey: "skey4", - destKeyPresetValue: nil, - destinationKey: "dkey4", - options: CopyOptions("1", false), - expectedValue: "value4", - want: 1, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.sourceKeyPresetValue != nil { - err := presetValue(server, context.Background(), tt.sourcekey, tt.sourceKeyPresetValue) - if err != nil { - t.Error(err) + t.Run("TestSugarDB_GETEX", func(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + presetValue interface{} + getExOpt GetExOption + getExOptTime int + key string + want string + wantEx int + wantErr bool + }{ + { + name: "1. Return string from existing key, no expire options", + presetValue: "value1", + getExOpt: nil, + key: "getex_key1", + want: "value1", + wantEx: -1, + wantErr: false, + }, + { + name: "2. Return empty string if the key does not exist", + presetValue: nil, + getExOpt: EX, + getExOptTime: int(mockClock.Now().Add(100 * time.Second).Unix()), + key: "getex_key2", + want: "", + wantEx: 0, + wantErr: false, + }, + { + name: "3. Return key set expiry with EX", + presetValue: "value3", + getExOpt: EX, + getExOptTime: 100, + key: "getex_key3", + want: "value3", + wantEx: 100, + wantErr: false, + }, + { + name: "4. Return key set expiry with PX", + presetValue: "value4", + getExOpt: PX, + getExOptTime: 100000, + key: "getex_key4", + want: "value4", + wantEx: 100, + wantErr: false, + }, + { + name: "5. Return key set expiry with EXAT", + presetValue: "value5", + getExOpt: EXAT, + getExOptTime: int(mockClock.Now().Add(100 * time.Second).Unix()), + key: "getex_key5", + want: "value5", + wantEx: 100, + wantErr: false, + }, + { + name: "6. Return key set expiry with PXAT", + presetValue: "value6", + getExOpt: PXAT, + getExOptTime: int(mockClock.Now().Add(100 * time.Second).UnixMilli()), + key: "getex_key6", + want: "value6", + wantEx: 100, + wantErr: false, + }, + { + name: "7. Return key passing PERSIST", + presetValue: "value7", + getExOpt: PERSIST, + key: "getex_key7", + want: "value7", + wantEx: -1, + wantErr: false, + }, + { + name: "8. Return key passing PERSIST, and include a UNIXTIME", + presetValue: "value8", + getExOpt: PERSIST, + getExOptTime: int(mockClock.Now().Add(100 * time.Second).Unix()), + key: "getex_key8", + want: "value8", + wantEx: -1, + wantErr: false, + }, + { + name: "9. Return key and attempt to set expiry with EX without providing UNIXTIME", + presetValue: "value9", + getExOpt: EX, + key: "getex_key9", + want: "value9", + wantEx: -1, + wantErr: false, + }, + { + name: "10. Return key and attempt to set expiry with PXAT without providing UNIXTIME", + presetValue: "value10", + getExOpt: PXAT, + key: "getex_key10", + want: "value10", + wantEx: -1, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if tt.presetValue != nil { + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } + } + // Check value received + got, err := server.GetEx(tt.key, tt.getExOpt, tt.getExOptTime) + if (err != nil) != tt.wantErr { + t.Errorf("GETEX() GET error = %v, wantErr %v", err, tt.wantErr) return } - } - if tt.destKeyPresetValue != nil { - err := presetValue(server, context.Background(), tt.destinationKey, tt.destKeyPresetValue) - if err != nil { - t.Error(err) - return + if got != tt.want { + t.Errorf("GETEX() GET - got = %v, want %v", got, tt.want) } - } + // Check expiry was set + if tt.presetValue != nil { + actual, err := server.TTL(tt.key) + if (err != nil) != tt.wantErr { + t.Errorf("GETEX() EXPIRY error = %v, wantErr %v", err, tt.wantErr) + return + } + if actual != tt.wantEx { + t.Errorf("GETEX() EXPIRY - got = %v, want %v", actual, tt.wantEx) + } + } + }) + } + }) - got, err := server.Copy(tt.sourcekey, tt.destinationKey, tt.options) - if (err != nil) != tt.wantErr { - t.Errorf("COPY() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("COPY() got = %v, want %v", got, tt.want) - } + // Tests Touch and OBJECTFREQ commands + t.Run("TestSugarDB_LFU_TOUCH", func(t *testing.T) { + t.Parallel() - val, err := getValue(server, context.Background(), tt.destinationKey, tt.options.Database) - if err != nil { - t.Error(err) - return - } + duration := time.Duration(30) * time.Second - if val != tt.expectedValue { - t.Errorf("COPY() value in destionation key: %v, should be: %v", val, tt.expectedValue) - } + server := createSugarDBWithConfig(config.Config{ + DataDir: "", + EvictionPolicy: constants.AllKeysLFU, + EvictionInterval: duration, + MaxMemory: 4000000, + }) + t.Cleanup(func() { + server.ShutDown() }) - } -} -func TestSugarDB_MOVE(t *testing.T) { - server := createSugarDB() + tests := []struct { + name string + keys []string + setKeys []bool + want int + wantErrs []bool + }{ + { + name: "1. Touch key that exists.", + keys: []string{"Key1"}, + setKeys: []bool{true}, + want: 1, + wantErrs: []bool{false}, + }, + { + name: "2. Touch key that doesn't exist.", + keys: []string{"Key2"}, + setKeys: []bool{false}, + want: 0, + wantErrs: []bool{true}, + }, + { + name: "3. Touch multiple keys that all exist.", + keys: []string{"Key3", "Key3.1"}, + setKeys: []bool{true, true}, + want: 2, + wantErrs: []bool{false, false}, + }, + { + name: "4. Touch multiple keys, some don't exist.", + keys: []string{"Key4", "Key4.9"}, + setKeys: []bool{true, false}, + want: 1, + wantErrs: []bool{false, true}, + }, + } - tests := []struct { - name string - presetValue interface{} - key string - want int - }{ - { - name: "1. Move key successfully", - presetValue: "value1", - key: "key1", - want: 1, - }, - { - name: "2. Attempt to move key, unsuccessful", - presetValue: nil, - key: "key2", - want: 0, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Log(tt.name) - if tt.presetValue != nil { - err := presetValue(server, context.Background(), tt.key, tt.presetValue) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Preset values + for i, key := range tt.keys { + if tt.setKeys[i] { + err := presetValue(server, context.Background(), key, "___") + if err != nil { + t.Error(err) + return + } + } + } + + // Touch keys + got, err := server.Touch(tt.keys...) + if err != nil { + t.Errorf("TOUCH() error - %v", err) + } + + if got != tt.want { + t.Errorf("TOUCH() got %v, want %v, using keys %v setKeys %v", got, tt.want, tt.keys, tt.setKeys) + } + + // Another touch to help testing object freq + got, err = server.Touch(tt.keys...) + if err != nil { + t.Errorf("TOUCH() error - %v", err) + } + + if got != tt.want { + t.Errorf("TOUCH() got %v, want %v, using keys %v setKeys %v", got, tt.want, tt.keys, tt.setKeys) + } + + // Wait to avoid race + ticker := time.NewTicker(300 * time.Millisecond) + <-ticker.C + ticker.Stop() + + // Objectfreq + for i, key := range tt.keys { + actual, err := server.ObjectFreq(key) + if (err != nil) != tt.wantErrs[i] { + t.Errorf("OBJECTFREQ() error: %v, wanted error: %v", err, tt.wantErrs[i]) + } + if !tt.wantErrs[i] && actual != 3 { + t.Errorf("OBJECTFREQ() error - expected 3 got %v for key %v", actual, key) + } + + // Check error for object idletime + _, err = server.ObjectIdleTime(key) + if err == nil { + t.Errorf("OBJECTIDLETIME() error - expected error when used on server with lfu eviction policy but got none.") + } + + } + + }) + } + }) + + // Tests Touch and OBJECTIDLETIME commands + t.Run("TestSugarDB_LRU_TOUCH", func(t *testing.T) { + t.Parallel() + + duration := time.Duration(30) * time.Second + + server := createSugarDBWithConfig(config.Config{ + DataDir: "", + EvictionPolicy: constants.AllKeysLRU, + EvictionInterval: duration, + MaxMemory: 4000000, + }) + t.Cleanup(func() { + server.ShutDown() + }) + + tests := []struct { + name string + keys []string + setKeys []bool + want int + wantErrs []bool + }{ + { + name: "1. Touch key that exists.", + keys: []string{"Key1"}, + setKeys: []bool{true}, + want: 1, + wantErrs: []bool{false}, + }, + { + name: "2. Touch key that doesn't exist.", + keys: []string{"Key2"}, + setKeys: []bool{false}, + want: 0, + wantErrs: []bool{true}, + }, + { + name: "3. Touch multiple keys that all exist.", + keys: []string{"Key3", "Key3.1"}, + setKeys: []bool{true, true}, + want: 2, + wantErrs: []bool{false, false}, + }, + { + name: "4. Touch multiple keys, some don't exist.", + keys: []string{"Key4", "Key4.9"}, + setKeys: []bool{true, false}, + want: 1, + wantErrs: []bool{false, true}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Preset values + for i, key := range tt.keys { + if tt.setKeys[i] { + err := presetValue(server, context.Background(), key, "___") + if err != nil { + t.Error(err) + return + } + } + } + + // Touch keys + got, err := server.Touch(tt.keys...) + if err != nil { + t.Errorf("TOUCH() error - %v", err) + } + + if got != tt.want { + t.Errorf("TOUCH() got %v, want %v, using keys %v setKeys %v", got, tt.want, tt.keys, tt.setKeys) + } + + // Sleep to more easily test Object idle time + // TODO: Update this ticker when updateKeysInCache implementation is updated + // Due to the event-based command execution, the actual touch may be done slightly later + // than the invocation time as it waits for earlier events to be handled. + ticker := time.NewTicker(200 * time.Millisecond) + <-ticker.C + ticker.Stop() + + // Objectidletime + for i, key := range tt.keys { + actual, err := server.ObjectIdleTime(key) + if (err != nil) != tt.wantErrs[i] { + t.Errorf("OBJECTIDLETIME() error: %v, wanted error: %v", err, tt.wantErrs[i]) + } + if !tt.wantErrs[i] && (actual <= 0) { // TODO: Fix updated condition to account for touch delay + t.Errorf("OBJECTIDLETIME() error - expected 0.2 got %v", actual) + } + + // Check error for object freq + _, err = server.ObjectFreq(key) + if err == nil { + t.Errorf("OBJECTFREQ() error - expected error when used on server with lru eviction policy but got none.") + } + } + + }) + } + }) + + t.Run("TestSugarDB_TYPE", func(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + presetValue interface{} + key string + want string + wantErr bool + }{ + { + name: "1. Return string from existing key", + presetValue: "value1", + key: "type_key1", + want: "string", + wantErr: false, + }, + { + name: "2. Return empty string if the key does not exist", + presetValue: nil, + key: "type_key2", + want: "", + wantErr: true, + }, + { + name: "3. Return string from existing key", + presetValue: 10, + key: "type_key3", + want: "integer", + wantErr: false, + }, + { + name: "4. Return string from existing key", + presetValue: 10.1, + key: "type_key4", + want: "float", + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if tt.presetValue != nil { + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } + } + got, err := server.Type(tt.key) + if (err != nil) != tt.wantErr { + t.Errorf("GET() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("GET() got = %v, want %v", got, tt.want) + } + }) + } + }) + + t.Run("TestSugarDB_COPY", func(t *testing.T) { + t.Parallel() + + CopyOptions := func(DB string, R bool) COPYOptions { + return COPYOptions{ + Database: DB, + Replace: R, + } + } + + tests := []struct { + name string + sourceKeyPresetValue interface{} + sourceKey string + destKeyPresetValue interface{} + destinationKey string + options COPYOptions + expectedValue string + want int + wantErr bool + }{ + { + name: "1. Copy Value into non existing key", + sourceKeyPresetValue: "value1", + sourceKey: "copy_skey1", + destKeyPresetValue: nil, + destinationKey: "copy_dkey1", + options: CopyOptions("0", false), + expectedValue: "value1", + want: 1, + wantErr: false, + }, + { + name: "2. Copy Value into existing key without replace option", + sourceKeyPresetValue: "value2", + sourceKey: "copy_skey2", + destKeyPresetValue: "dValue2", + destinationKey: "copy_dkey2", + options: CopyOptions("0", false), + expectedValue: "dValue2", + want: 0, + wantErr: false, + }, + { + name: "3. Copy Value into existing key with replace option", + sourceKeyPresetValue: "value3", + sourceKey: "copy_skey3", + destKeyPresetValue: "dValue3", + destinationKey: "copy_dkey3", + options: CopyOptions("0", true), + expectedValue: "value3", + want: 1, + wantErr: false, + }, + { + name: "4. Copy Value into different database", + sourceKeyPresetValue: "value4", + sourceKey: "copy_skey4", + destKeyPresetValue: nil, + destinationKey: "copy_dkey4", + options: CopyOptions("1", false), + expectedValue: "value4", + want: 1, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if tt.sourceKeyPresetValue != nil { + err := presetValue(server, context.Background(), tt.sourceKey, tt.sourceKeyPresetValue) + if err != nil { + t.Error(err) + return + } + } + if tt.destKeyPresetValue != nil { + err := presetValue(server, context.Background(), tt.destinationKey, tt.destKeyPresetValue) + if err != nil { + t.Error(err) + return + } + } + + got, err := server.Copy(tt.sourceKey, tt.destinationKey, tt.options) + if (err != nil) != tt.wantErr { + t.Errorf("COPY() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("COPY() got = %v, want %v", got, tt.want) + } + + val, err := getValue(server, context.Background(), tt.destinationKey, tt.options.Database) if err != nil { t.Error(err) return } - } - got, err := server.Move(tt.key, 1) - if err != nil { - t.Error(err) - } + if val != tt.expectedValue { + t.Errorf("COPY() value in destionation key: %v, should be: %v", val, tt.expectedValue) + } + }) + } + }) - if got != tt.want { - t.Errorf("MOVE() got %v, want %v", got, tt.want) - } - }) - } + t.Run("TestSugarDB_MOVE", func(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + presetValue interface{} + key string + want int + }{ + { + name: "1. Move key successfully", + presetValue: "value1", + key: "move_key1", + want: 1, + }, + { + name: "2. Attempt to move key, unsuccessful", + presetValue: nil, + key: "move_key2", + want: 0, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + t.Log(tt.name) + if tt.presetValue != nil { + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } + } + + got, err := server.Move(tt.key, 1) + if err != nil { + t.Error(err) + } + + if got != tt.want { + t.Errorf("MOVE() got %v, want %v", got, tt.want) + } + }) + } + }) } diff --git a/sugardb/api_hash_test.go b/sugardb/api_hash_test.go index ee32591..028e5e0 100644 --- a/sugardb/api_hash_test.go +++ b/sugardb/api_hash_test.go @@ -16,1127 +16,1151 @@ package sugardb import ( "context" + "github.com/echovault/sugardb/internal/modules/hash" "reflect" "slices" "testing" "time" - - "github.com/echovault/sugardb/internal/modules/hash" ) -func TestSugarDB_HDEL(t *testing.T) { +func TestSugarDB_Hash(t *testing.T) { server := createSugarDB() + t.Cleanup(func() { + server.ShutDown() + }) - tests := []struct { - name string - presetValue interface{} - key string - fields []string - want int - wantErr bool - }{ - { - name: "Return count of deleted fields in the specified hash", - key: "key1", - presetValue: hash.Hash{ - "field1": {Value: "value1"}, - "field2": {Value: 123456789}, - "field3": {Value: 3.142}, - "field7": {Value: "value7"}, + t.Run("TestSugarDB_HDEL", func(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + presetValue interface{} + key string + fields []string + want int + wantErr bool + }{ + { + name: "1. Return count of deleted fields in the specified hash", + key: "hdel_key1", + presetValue: hash.Hash{ + "field1": {Value: "value1"}, + "field2": {Value: 123456789}, + "field3": {Value: 3.142}, + "field7": {Value: "value7"}, + }, + fields: []string{"field1", "field2", "field3", "field4", "field5", "field6"}, + want: 3, + wantErr: false, }, - fields: []string{"field1", "field2", "field3", "field4", "field5", "field6"}, - want: 3, - wantErr: false, - }, - { - name: "0 response when passing delete fields that are non-existent on valid hash", - key: "key2", - presetValue: hash.Hash{ - "field1": {Value: "value1"}, - "field2": {Value: "value2"}, - "field3": {Value: "value3"}, + { + name: "2. 0 response when passing delete fields that are non-existent on valid hash", + key: "hdel_key2", + presetValue: hash.Hash{ + "field1": {Value: "value1"}, + "field2": {Value: "value2"}, + "field3": {Value: "value3"}, + }, + fields: []string{"field4", "field5", "field6"}, + want: 0, + wantErr: false, }, - fields: []string{"field4", "field5", "field6"}, - want: 0, - wantErr: false, - }, - { - name: "0 response when trying to call HDEL on non-existent key", - key: "key3", - presetValue: nil, - fields: []string{"field1"}, - want: 0, - wantErr: false, - }, - { - name: "Trying to get lengths on a non hash map returns error", - presetValue: "Default value", - key: "key5", - fields: []string{"field1"}, - want: 0, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.presetValue != nil { - err := presetValue(server, context.Background(), tt.key, tt.presetValue) - if err != nil { - t.Error(err) + { + name: "3. 0 response when trying to call HDEL on non-existent key", + key: "hdel_key3", + presetValue: nil, + fields: []string{"field1"}, + want: 0, + wantErr: false, + }, + { + name: "4. Trying to get lengths on a non hash map returns error", + presetValue: "Default value", + key: "hdel_key5", + fields: []string{"field1"}, + want: 0, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if tt.presetValue != nil { + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } + } + got, err := server.HDel(tt.key, tt.fields...) + if (err != nil) != tt.wantErr { + t.Errorf("HDEL() error = %v, wantErr %v", err, tt.wantErr) return } - } - got, err := server.HDel(tt.key, tt.fields...) - if (err != nil) != tt.wantErr { - t.Errorf("HDEL() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("HDEL() got = %v, want %v", got, tt.want) - } - }) - } -} + if got != tt.want { + t.Errorf("HDEL() got = %v, want %v", got, tt.want) + } + }) + } + }) -func TestSugarDB_HEXISTS(t *testing.T) { - server := createSugarDB() + t.Run("TestSugarDB_HEXISTS", func(t *testing.T) { + t.Parallel() - tests := []struct { - name string - presetValue interface{} - key string - field string - want bool - wantErr bool - }{ - { - name: "Return 1 if the field exists in the hash", - presetValue: hash.Hash{ - "field1": {Value: "value1"}, - "field2": {Value: 123456789}, - "field3": {Value: 3.142}, + tests := []struct { + name string + presetValue interface{} + key string + field string + want bool + wantErr bool + }{ + { + name: "1. Return 1 if the field exists in the hash", + presetValue: hash.Hash{ + "field1": {Value: "value1"}, + "field2": {Value: 123456789}, + "field3": {Value: 3.142}, + }, + key: "hexists_key1", + field: "field1", + want: true, + wantErr: false, }, - key: "key1", - field: "field1", - want: true, - wantErr: false, - }, - { - name: "False response when trying to call HEXISTS on non-existent key", - presetValue: hash.Hash{}, - key: "key2", - field: "field1", - want: false, - wantErr: false, - }, - { - name: "Trying to get lengths on a non hash map returns error", - presetValue: "Default value", - key: "key5", - field: "field1", - want: false, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.presetValue != nil { - err := presetValue(server, context.Background(), tt.key, tt.presetValue) - if err != nil { - t.Error(err) + { + name: "2. False response when trying to call HEXISTS on non-existent key", + presetValue: hash.Hash{}, + key: "hexists_key2", + field: "field1", + want: false, + wantErr: false, + }, + { + name: "3. Trying to get lengths on a non hash map returns error", + presetValue: "Default value", + key: "hexists_key5", + field: "field1", + want: false, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if tt.presetValue != nil { + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } + } + got, err := server.HExists(tt.key, tt.field) + if (err != nil) != tt.wantErr { + t.Errorf("HEXISTS() error = %v, wantErr %v", err, tt.wantErr) return } - } - got, err := server.HExists(tt.key, tt.field) - if (err != nil) != tt.wantErr { - t.Errorf("HEXISTS() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("HEXISTS() got = %v, want %v", got, tt.want) - } - }) - } -} + if got != tt.want { + t.Errorf("HEXISTS() got = %v, want %v", got, tt.want) + } + }) + } + }) -func TestSugarDB_HGETALL(t *testing.T) { - server := createSugarDB() + t.Run("TestSugarDB_HGETALL", func(t *testing.T) { + t.Parallel() - tests := []struct { - name string - presetValue interface{} - key string - want []string - wantErr bool - }{ - { - name: "Return an array containing all the fields and values of the hash", - key: "key1", - presetValue: hash.Hash{ - "field1": {Value: "value1"}, - "field2": {Value: 123456789}, - "field3": {Value: 3.142}, + tests := []struct { + name string + presetValue interface{} + key string + want []string + wantErr bool + }{ + { + name: "1. Return an array containing all the fields and values of the hash", + key: "hgetall_key1", + presetValue: hash.Hash{ + "field1": {Value: "value1"}, + "field2": {Value: 123456789}, + "field3": {Value: 3.142}, + }, + want: []string{"field1", "value1", "field2", "123456789", "field3", "3.142"}, + wantErr: false, }, - want: []string{"field1", "value1", "field2", "123456789", "field3", "3.142"}, - wantErr: false, - }, - { - name: "Empty array response when trying to call HGETALL on non-existent key", - key: "key2", - presetValue: hash.Hash{}, - want: []string{}, - wantErr: false, - }, - { - name: "Trying to get lengths on a non hash map returns error", - key: "key5", - presetValue: "Default value", - want: nil, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.presetValue != nil { - err := presetValue(server, context.Background(), tt.key, tt.presetValue) - if err != nil { - t.Error(err) + { + name: "2. Empty array response when trying to call HGETALL on non-existent key", + key: "hgetall_key2", + presetValue: hash.Hash{}, + want: []string{}, + wantErr: false, + }, + { + name: "3. Trying to get lengths on a non hash map returns error", + key: "hgetall_key5", + presetValue: "Default value", + want: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if tt.presetValue != nil { + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } + } + got, err := server.HGetAll(tt.key) + if (err != nil) != tt.wantErr { + t.Errorf("HGETALL() error = %v, wantErr %v", err, tt.wantErr) return } - } - got, err := server.HGetAll(tt.key) - if (err != nil) != tt.wantErr { - t.Errorf("HGETALL() error = %v, wantErr %v", err, tt.wantErr) - return - } - if len(got) != len(tt.want) { - t.Errorf("HGETALL() got = %v, want %v", got, tt.want) - return - } - for _, g := range got { - if !slices.Contains(tt.want, g) { + if len(got) != len(tt.want) { t.Errorf("HGETALL() got = %v, want %v", got, tt.want) return } - } - }) - } -} - -func TestSugarDB_HINCRBY(t *testing.T) { - server := createSugarDB() - - const ( - HINCRBY = "HINCRBY" - HINCRBYFLOAT = "HINCRBYFLOAT" - ) - - tests := []struct { - name string - presetValue interface{} - incr_type string - key string - field string - increment_int int - increment_float float64 - want float64 - wantErr bool - }{ - { - name: "Increment by integer on non-existent hash should create a new one", - presetValue: nil, - incr_type: HINCRBY, - key: "key1", - field: "field1", - increment_int: 1, - want: 1, - wantErr: false, - }, - { - name: "Increment by float on non-existent hash should create one", - presetValue: nil, - incr_type: HINCRBYFLOAT, - key: "key2", - field: "field1", - increment_float: 3.142, - want: 3.142, - wantErr: false, - }, - { - name: "Increment by integer on existing hash", - presetValue: hash.Hash{"field1": {Value: 1}}, - incr_type: HINCRBY, - key: "key3", - field: "field1", - increment_int: 10, - want: 11, - wantErr: false, - }, - { - name: "Increment by float on an existing hash", - presetValue: hash.Hash{"field1": {Value: 3.142}}, - incr_type: HINCRBYFLOAT, - key: "key4", - field: "field1", - increment_float: 3.142, - want: 6.284, - wantErr: false, - }, - { - name: "Error when trying to increment on a key that is not a hash", - presetValue: "Default value", - incr_type: HINCRBY, - key: "key9", - field: "field1", - increment_int: 3, - want: 0, - wantErr: true, - }, - { - name: "Error when trying to increment a hash field that is not a number", - presetValue: hash.Hash{"field1": {Value: "value1"}}, - incr_type: HINCRBY, - key: "key10", - field: "field1", - increment_int: 1, - want: 0, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.presetValue != nil { - err := presetValue(server, context.Background(), tt.key, tt.presetValue) - if err != nil { - t.Error(err) - return + for _, g := range got { + if !slices.Contains(tt.want, g) { + t.Errorf("HGETALL() got = %v, want %v", got, tt.want) + return + } } - } - var got float64 - var err error - if tt.incr_type == HINCRBY { - got, err = server.HIncrBy(tt.key, tt.field, tt.increment_int) - if (err != nil) != tt.wantErr { - t.Errorf("HINCRBY() error = %v, wantErr %v", err, tt.wantErr) - return - } - } - if tt.incr_type == HINCRBYFLOAT { - got, err = server.HIncrByFloat(tt.key, tt.field, tt.increment_float) - if (err != nil) != tt.wantErr { - t.Errorf("HINCRBYFLOAT() error = %v, wantErr %v", err, tt.wantErr) - return - } - } - if got != tt.want { - t.Errorf("HINCRBY/HINCRBYFLOAT() got = %v, want %v", got, tt.want) - } - }) - } -} + }) + } + }) -func TestSugarDB_HKEYS(t *testing.T) { - server := createSugarDB() + t.Run("TestSugarDB_HINCRBY", func(t *testing.T) { + t.Parallel() - tests := []struct { - name string - presetValue interface{} - key string - want []string - wantErr bool - }{ - { - name: "Return an array containing all the keys of the hash", - presetValue: hash.Hash{ - "field1": {Value: "value1"}, - "field2": {Value: 123456789}, - "field3": {Value: 3.142}, + const ( + HINCRBY = "HINCRBY" + HINCRBYFLOAT = "HINCRBYFLOAT" + ) + + tests := []struct { + name string + presetValue interface{} + incr_type string + key string + field string + increment_int int + increment_float float64 + want float64 + wantErr bool + }{ + { + name: "1. Increment by integer on non-existent hash should create a new one", + presetValue: nil, + incr_type: HINCRBY, + key: "hincrby_key1", + field: "field1", + increment_int: 1, + want: 1, + wantErr: false, }, - key: "key1", - want: []string{"field1", "field2", "field3"}, - wantErr: false, - }, - { - name: "Empty array response when trying to call HKEYS on non-existent key", - presetValue: hash.Hash{}, - key: "key2", - want: []string{}, - wantErr: false, - }, - { - name: "Trying to get lengths on a non hash map returns error", - presetValue: "Default value", - key: "key3", - want: nil, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.presetValue != nil { - err := presetValue(server, context.Background(), tt.key, tt.presetValue) - if err != nil { - t.Error(err) + { + name: "2. Increment by float on non-existent hash should create one", + presetValue: nil, + incr_type: HINCRBYFLOAT, + key: "hincrby_key2", + field: "field1", + increment_float: 3.142, + want: 3.142, + wantErr: false, + }, + { + name: "3. Increment by integer on existing hash", + presetValue: hash.Hash{"field1": {Value: 1}}, + incr_type: HINCRBY, + key: "hincrby_key3", + field: "field1", + increment_int: 10, + want: 11, + wantErr: false, + }, + { + name: "4. Increment by float on an existing hash", + presetValue: hash.Hash{"field1": {Value: 3.142}}, + incr_type: HINCRBYFLOAT, + key: "hincrby_key4", + field: "field1", + increment_float: 3.142, + want: 6.284, + wantErr: false, + }, + { + name: "5. Error when trying to increment on a key that is not a hash", + presetValue: "Default value", + incr_type: HINCRBY, + key: "hincrby_key9", + field: "field1", + increment_int: 3, + want: 0, + wantErr: true, + }, + { + name: "6. Error when trying to increment a hash field that is not a number", + presetValue: hash.Hash{"field1": {Value: "value1"}}, + incr_type: HINCRBY, + key: "hincrby_key10", + field: "field1", + increment_int: 1, + want: 0, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if tt.presetValue != nil { + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } + } + var got float64 + var err error + if tt.incr_type == HINCRBY { + got, err = server.HIncrBy(tt.key, tt.field, tt.increment_int) + if (err != nil) != tt.wantErr { + t.Errorf("HINCRBY() error = %v, wantErr %v", err, tt.wantErr) + return + } + } + if tt.incr_type == HINCRBYFLOAT { + got, err = server.HIncrByFloat(tt.key, tt.field, tt.increment_float) + if (err != nil) != tt.wantErr { + t.Errorf("HINCRBYFLOAT() error = %v, wantErr %v", err, tt.wantErr) + return + } + } + if got != tt.want { + t.Errorf("HINCRBY/HINCRBYFLOAT() got = %v, want %v", got, tt.want) + } + }) + } + }) + + t.Run("TestSugarDB_HKEYS", func(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + presetValue interface{} + key string + want []string + wantErr bool + }{ + { + name: "1. Return an array containing all the keys of the hash", + presetValue: hash.Hash{ + "field1": {Value: "value1"}, + "field2": {Value: 123456789}, + "field3": {Value: 3.142}, + }, + key: "hkeys_key1", + want: []string{"field1", "field2", "field3"}, + wantErr: false, + }, + { + name: "2. Empty array response when trying to call HKEYS on non-existent key", + presetValue: hash.Hash{}, + key: "hkeys_key2", + want: []string{}, + wantErr: false, + }, + { + name: "3. Trying to get lengths on a non hash map returns error", + presetValue: "Default value", + key: "hkeys_key3", + want: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if tt.presetValue != nil { + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } + } + got, err := server.HKeys(tt.key) + if (err != nil) != tt.wantErr { + t.Errorf("HKEYS() error = %v, wantErr %v", err, tt.wantErr) return } - } - got, err := server.HKeys(tt.key) - if (err != nil) != tt.wantErr { - t.Errorf("HKEYS() error = %v, wantErr %v", err, tt.wantErr) - return - } - if len(got) != len(tt.want) { - t.Errorf("HKEYS() got = %v, want %v", got, tt.want) - } - for _, g := range got { - if !slices.Contains(tt.want, g) { + if len(got) != len(tt.want) { t.Errorf("HKEYS() got = %v, want %v", got, tt.want) } - } - }) - } -} + for _, g := range got { + if !slices.Contains(tt.want, g) { + t.Errorf("HKEYS() got = %v, want %v", got, tt.want) + } + } + }) + } + }) -func TestSugarDB_HLEN(t *testing.T) { - server := createSugarDB() + t.Run("TestSugarDB_HLEN", func(t *testing.T) { + t.Parallel() - tests := []struct { - name string - presetValue interface{} - key string - want int - wantErr bool - }{ - { - name: "Return the correct length of the hash", - presetValue: hash.Hash{ - "field1": {Value: "value1"}, - "field2": {Value: 123456789}, - "field3": {Value: 3.142}, + tests := []struct { + name string + presetValue interface{} + key string + want int + wantErr bool + }{ + { + name: "1. Return the correct length of the hash", + presetValue: hash.Hash{ + "field1": {Value: "value1"}, + "field2": {Value: 123456789}, + "field3": {Value: 3.142}, + }, + key: "hlen_key1", + want: 3, + wantErr: false, }, - key: "key1", - want: 3, - wantErr: false, - }, - { - name: "0 Response when trying to call HLEN on non-existent key", - presetValue: nil, - key: "key2", - want: 0, - wantErr: false, - }, - { - name: "Trying to get lengths on a non hash map returns error", - presetValue: "Default value", - key: "key5", - want: 0, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.presetValue != nil { - err := presetValue(server, context.Background(), tt.key, tt.presetValue) - if err != nil { - t.Error(err) + { + name: "2. 0 Response when trying to call HLEN on non-existent key", + presetValue: nil, + key: "hlen_key2", + want: 0, + wantErr: false, + }, + { + name: "3. Trying to get lengths on a non hash map returns error", + presetValue: "Default value", + key: "hlen_key5", + want: 0, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if tt.presetValue != nil { + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } + } + got, err := server.HLen(tt.key) + if (err != nil) != tt.wantErr { + t.Errorf("HLEN() error = %v, wantErr %v", err, tt.wantErr) return } - } - got, err := server.HLen(tt.key) - if (err != nil) != tt.wantErr { - t.Errorf("HLEN() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("HLEN() got = %v, want %v", got, tt.want) - } - }) - } -} + if got != tt.want { + t.Errorf("HLEN() got = %v, want %v", got, tt.want) + } + }) + } + }) -func TestSugarDB_HRANDFIELD(t *testing.T) { - server := createSugarDB() + t.Run("TestSugarDB_HRANDFIELD", func(t *testing.T) { + t.Parallel() - tests := []struct { - name string - presetValue interface{} - key string - options HRandFieldOptions - wantCount int - want []string - wantErr bool - }{ - { - name: "Get a random field", - presetValue: hash.Hash{ - "field1": {Value: "value1"}, - "field2": {Value: 123456789}, - "field3": {Value: 3.142}, + tests := []struct { + name string + presetValue interface{} + key string + options HRandFieldOptions + wantCount int + want []string + wantErr bool + }{ + { + name: "1. Get a random field", + presetValue: hash.Hash{ + "field1": {Value: "value1"}, + "field2": {Value: 123456789}, + "field3": {Value: 3.142}, + }, + key: "hrandfield_key1", + options: HRandFieldOptions{Count: 1}, + wantCount: 1, + want: []string{"field1", "field2", "field3"}, + wantErr: false, }, - key: "key1", - options: HRandFieldOptions{Count: 1}, - wantCount: 1, - want: []string{"field1", "field2", "field3"}, - wantErr: false, - }, - { - name: "Get a random field with a value", - presetValue: hash.Hash{ - "field1": {Value: "value1"}, - "field2": {Value: 123456789}, - "field3": {Value: 3.142}, + { + name: "2. Get a random field with a value", + presetValue: hash.Hash{ + "field1": {Value: "value1"}, + "field2": {Value: 123456789}, + "field3": {Value: 3.142}, + }, + key: "hrandfield_key2", + options: HRandFieldOptions{WithValues: true, Count: 1}, + wantCount: 2, + want: []string{"field1", "value1", "field2", "123456789", "field3", "3.142"}, + wantErr: false, }, - key: "key2", - options: HRandFieldOptions{WithValues: true, Count: 1}, - wantCount: 2, - want: []string{"field1", "value1", "field2", "123456789", "field3", "3.142"}, - wantErr: false, - }, - { - name: "Get several random fields", - presetValue: hash.Hash{ - "field1": {Value: "value1"}, - "field2": {Value: 123456789}, - "field3": {Value: 3.142}, - "field4": {Value: "value4"}, - "field5": {Value: "value6"}, + { + name: "3. Get several random fields", + presetValue: hash.Hash{ + "field1": {Value: "value1"}, + "field2": {Value: 123456789}, + "field3": {Value: 3.142}, + "field4": {Value: "value4"}, + "field5": {Value: "value6"}, + }, + key: "hrandfield_key3", + options: HRandFieldOptions{Count: 3}, + wantCount: 3, + want: []string{"field1", "field2", "field3", "field4", "field5"}, + wantErr: false, }, - key: "key3", - options: HRandFieldOptions{Count: 3}, - wantCount: 3, - want: []string{"field1", "field2", "field3", "field4", "field5"}, - wantErr: false, - }, - { - name: "Get several random fields with their corresponding values", - presetValue: hash.Hash{ - "field1": {Value: "value1"}, - "field2": {Value: 123456789}, - "field3": {Value: 3.142}, - "field4": {Value: "value4"}, - "field5": {Value: "value5"}, + { + name: "4. Get several random fields with their corresponding values", + presetValue: hash.Hash{ + "field1": {Value: "value1"}, + "field2": {Value: 123456789}, + "field3": {Value: 3.142}, + "field4": {Value: "value4"}, + "field5": {Value: "value5"}, + }, + key: "hrandfield_key4", + options: HRandFieldOptions{WithValues: true, Count: 3}, + wantCount: 6, + want: []string{ + "field1", "value1", "field2", "123456789", "field3", + "3.142", "field4", "value4", "field5", "value5", + }, + wantErr: false, }, - key: "key4", - options: HRandFieldOptions{WithValues: true, Count: 3}, - wantCount: 6, - want: []string{ - "field1", "value1", "field2", "123456789", "field3", - "3.142", "field4", "value4", "field5", "value5", + { + name: "5. Get the entire hash", + presetValue: hash.Hash{ + "field1": {Value: "value1"}, + "field2": {Value: 123456789}, + "field3": {Value: 3.142}, + "field4": {Value: "value4"}, + "field5": {Value: "value5"}, + }, + key: "hrandfield_key5", + options: HRandFieldOptions{Count: 5}, + wantCount: 5, + want: []string{"field1", "field2", "field3", "field4", "field5"}, + wantErr: false, }, - wantErr: false, - }, - { - name: "Get the entire hash", - presetValue: hash.Hash{ - "field1": {Value: "value1"}, - "field2": {Value: 123456789}, - "field3": {Value: 3.142}, - "field4": {Value: "value4"}, - "field5": {Value: "value5"}, + { + name: "6. Get the entire hash with values", + presetValue: hash.Hash{ + "field1": {Value: "value1"}, + "field2": {Value: 123456789}, + "field3": {Value: 3.142}, + "field4": {Value: "value4"}, + "field5": {Value: "value5"}, + }, + key: "hrandfield_key6", + options: HRandFieldOptions{WithValues: true, Count: 5}, + wantCount: 10, + want: []string{ + "field1", "value1", "field2", "123456789", "field3", + "3.142", "field4", "value4", "field5", "value5", + }, + wantErr: false, }, - key: "key5", - options: HRandFieldOptions{Count: 5}, - wantCount: 5, - want: []string{"field1", "field2", "field3", "field4", "field5"}, - wantErr: false, - }, - { - name: "Get the entire hash with values", - presetValue: hash.Hash{ - "field1": {Value: "value1"}, - "field2": {Value: 123456789}, - "field3": {Value: 3.142}, - "field4": {Value: "value4"}, - "field5": {Value: "value5"}, + { + name: "7. Trying to get random field on a non hash map returns error", + presetValue: "Default value", + key: "hrandfield_key7", + options: HRandFieldOptions{}, + wantCount: 0, + want: nil, + wantErr: true, }, - key: "key5", - options: HRandFieldOptions{WithValues: true, Count: 5}, - wantCount: 10, - want: []string{ - "field1", "value1", "field2", "123456789", "field3", - "3.142", "field4", "value4", "field5", "value5", - }, - wantErr: false, - }, - { - name: "Trying to get random field on a non hash map returns error", - presetValue: "Default value", - key: "key12", - options: HRandFieldOptions{}, - wantCount: 0, - want: nil, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.presetValue != nil { - err := presetValue(server, context.Background(), tt.key, tt.presetValue) - if err != nil { - t.Error(err) + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if tt.presetValue != nil { + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } + } + got, err := server.HRandField(tt.key, tt.options) + if (err != nil) != tt.wantErr { + t.Errorf("HRANDFIELD() error = %v, wantErr %v", err, tt.wantErr) return } - } - got, err := server.HRandField(tt.key, tt.options) - if (err != nil) != tt.wantErr { - t.Errorf("HRANDFIELD() error = %v, wantErr %v", err, tt.wantErr) - return - } - if len(got) != tt.wantCount { - t.Errorf("HRANDFIELD() got = %v, want %v", got, tt.want) - } - for _, g := range got { - if !slices.Contains(tt.want, g) { + if len(got) != tt.wantCount { t.Errorf("HRANDFIELD() got = %v, want %v", got, tt.want) } - } - }) - } -} - -func TestSugarDB_HSET(t *testing.T) { - server := createSugarDB() - - tests := []struct { - name string - presetValue interface{} - hsetFunc func(key string, pairs map[string]string) (int, error) - key string - fieldValuePairs map[string]string - want int - wantErr bool - }{ - { - name: "HSETNX set field on non-existent hash map", - key: "key1", - presetValue: nil, - hsetFunc: server.HSetNX, - fieldValuePairs: map[string]string{"field1": "value1"}, - want: 1, - wantErr: false, - }, - { - name: "HSETNX set field on existing hash map", - key: "key2", - presetValue: hash.Hash{"field1": {Value: "value1"}}, - hsetFunc: server.HSetNX, - fieldValuePairs: map[string]string{"field2": "value2"}, - want: 1, - wantErr: false, - }, - { - name: "HSETNX skips operation when setting on existing field", - key: "key3", - presetValue: hash.Hash{"field1": {Value: "value1"}}, - hsetFunc: server.HSetNX, - fieldValuePairs: map[string]string{"field1": "value1"}, - want: 0, - wantErr: false, - }, - { - name: "Regular HSET command on non-existent hash map", - key: "key4", - presetValue: nil, - fieldValuePairs: map[string]string{"field1": "value1", "field2": "value2"}, - hsetFunc: server.HSet, - want: 2, - wantErr: false, - }, - { - name: "Regular HSET update on existing hash map", - key: "key5", - presetValue: hash.Hash{"field1": {Value: "value1"}, "field2": {Value: "value2"}}, - fieldValuePairs: map[string]string{"field1": "value1-new", "field2": "value2-ne2", "field3": "value3"}, - hsetFunc: server.HSet, - want: 3, - wantErr: false, - }, - { - name: "HSET overwrites when the target key is not a map", - key: "key6", - presetValue: "Default preset value", - fieldValuePairs: map[string]string{"field1": "value1"}, - hsetFunc: server.HSet, - want: 1, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.presetValue != nil { - err := presetValue(server, context.Background(), tt.key, tt.presetValue) - if err != nil { - t.Error(err) - return + for _, g := range got { + if !slices.Contains(tt.want, g) { + t.Errorf("HRANDFIELD() got = %v, want %v", got, tt.want) + } } - } - got, err := tt.hsetFunc(tt.key, tt.fieldValuePairs) - if (err != nil) != tt.wantErr { - t.Errorf("HSET() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("HSET() got = %v, want %v", got, tt.want) - } - }) - } -} + }) + } + }) -func TestSugarDB_HSTRLEN(t *testing.T) { - server := createSugarDB() + t.Run("TestSugarDB_HSET", func(t *testing.T) { + t.Parallel() - tests := []struct { - name string - presetValue interface{} - key string - fields []string - want []int - wantErr bool - }{ - { - // Return lengths of field values. - // If the key does not exist, its length should be 0. - name: "1. Return lengths of field values", - presetValue: hash.Hash{ - "field1": {Value: "value1"}, - "field2": {Value: 123456789}, - "field3": {Value: 3.142}, + tests := []struct { + name string + presetValue interface{} + hsetFunc func(key string, pairs map[string]string) (int, error) + key string + fieldValuePairs map[string]string + want int + wantErr bool + }{ + { + name: "1. HSETNX set field on non-existent hash map", + key: "hset_key1", + presetValue: nil, + hsetFunc: server.HSetNX, + fieldValuePairs: map[string]string{"field1": "value1"}, + want: 1, + wantErr: false, }, - key: "key1", - fields: []string{"field1", "field2", "field3", "field4"}, - want: []int{len("value1"), len("123456789"), len("3.142"), 0}, - wantErr: false, - }, - { - name: "2. Response when trying to get HSTRLEN non-existent key", - presetValue: hash.Hash{}, - key: "key2", - fields: []string{"field1"}, - want: []int{0}, - wantErr: false, - }, - { - name: "3. Command too short", - key: "key3", - presetValue: hash.Hash{}, - fields: []string{}, - want: nil, - wantErr: true, - }, - { - name: "4. Trying to get lengths on a non hash map returns error", - key: "key4", - presetValue: "Default value", - fields: []string{"field1"}, - want: nil, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Log(tt.name) - if tt.presetValue != nil { - err := presetValue(server, context.Background(), tt.key, tt.presetValue) - if err != nil { - t.Error(err) - return - } - } - got, err := server.HStrLen(tt.key, tt.fields...) - if (err != nil) != tt.wantErr { - t.Errorf("HSTRLEN() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("HSTRLEN() got = %v, want %v", got, tt.want) - } - }) - } -} - -func TestSugarDB_HVALS(t *testing.T) { - server := createSugarDB() - - tests := []struct { - name string - presetValue interface{} - key string - want []string - wantErr bool - }{ - { - name: "Return all the values from a hash", - key: "key1", - presetValue: hash.Hash{ - "field1": {Value: "value1"}, - "field2": {Value: 123456789}, - "field3": {Value: 3.142}, + { + name: "2. HSETNX set field on existing hash map", + key: "hset_key2", + presetValue: hash.Hash{"field1": {Value: "value1"}}, + hsetFunc: server.HSetNX, + fieldValuePairs: map[string]string{"field2": "value2"}, + want: 1, + wantErr: false, }, - want: []string{"value1", "123456789", "3.142"}, - wantErr: false, - }, - { - name: "Empty array response when trying to get HSTRLEN non-existent key", - key: "key2", - presetValue: nil, - want: []string{}, - wantErr: false, - }, - { - name: "Trying to get lengths on a non hash map returns error", - key: "key5", - presetValue: "Default value", - want: nil, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.presetValue != nil { - err := presetValue(server, context.Background(), tt.key, tt.presetValue) - if err != nil { - t.Error(err) + { + name: "3. HSETNX skips operation when setting on existing field", + key: "hset_key3", + presetValue: hash.Hash{"field1": {Value: "value1"}}, + hsetFunc: server.HSetNX, + fieldValuePairs: map[string]string{"field1": "value1"}, + want: 0, + wantErr: false, + }, + { + name: "4. Regular HSET command on non-existent hash map", + key: "hset_key4", + presetValue: nil, + fieldValuePairs: map[string]string{"field1": "value1", "field2": "value2"}, + hsetFunc: server.HSet, + want: 2, + wantErr: false, + }, + { + name: "5. Regular HSET update on existing hash map", + key: "hset_key5", + presetValue: hash.Hash{"field1": {Value: "value1"}, "field2": {Value: "value2"}}, + fieldValuePairs: map[string]string{"field1": "value1-new", "field2": "value2-ne2", "field3": "value3"}, + hsetFunc: server.HSet, + want: 3, + wantErr: false, + }, + { + name: "6. HSET overwrites when the target key is not a map", + key: "hset_key6", + presetValue: "Default preset value", + fieldValuePairs: map[string]string{"field1": "value1"}, + hsetFunc: server.HSet, + want: 1, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if tt.presetValue != nil { + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } + } + got, err := tt.hsetFunc(tt.key, tt.fieldValuePairs) + if (err != nil) != tt.wantErr { + t.Errorf("HSET() error = %v, wantErr %v", err, tt.wantErr) return } - } - got, err := server.HVals(tt.key) - if (err != nil) != tt.wantErr { - t.Errorf("HVALS() error = %v, wantErr %v", err, tt.wantErr) - return - } - if len(got) != len(tt.want) { - t.Errorf("HVALS() got = %v, want %v", got, tt.want) - } - for _, g := range got { - if !slices.Contains(tt.want, g) { + if got != tt.want { + t.Errorf("HSET() got = %v, want %v", got, tt.want) + } + }) + } + }) + + t.Run("TestSugarDB_HSTRLEN", func(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + presetValue interface{} + key string + fields []string + want []int + wantErr bool + }{ + { + // Return lengths of field values. + // If the key does not exist, its length should be 0. + name: "1. Return lengths of field values", + presetValue: hash.Hash{ + "field1": {Value: "value1"}, + "field2": {Value: 123456789}, + "field3": {Value: 3.142}, + }, + key: "hstrlen_key1", + fields: []string{"field1", "field2", "field3", "field4"}, + want: []int{len("value1"), len("123456789"), len("3.142"), 0}, + wantErr: false, + }, + { + name: "2. Response when trying to get HSTRLEN non-existent key", + presetValue: hash.Hash{}, + key: "hstrlen_key2", + fields: []string{"field1"}, + want: []int{0}, + wantErr: false, + }, + { + name: "3. Command too short", + key: "hstrlen_key3", + presetValue: hash.Hash{}, + fields: []string{}, + want: nil, + wantErr: true, + }, + { + name: "4. Trying to get lengths on a non hash map returns error", + key: "hstrlen_key4", + presetValue: "Default value", + fields: []string{"field1"}, + want: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + t.Log(tt.name) + if tt.presetValue != nil { + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } + } + got, err := server.HStrLen(tt.key, tt.fields...) + if (err != nil) != tt.wantErr { + t.Errorf("HSTRLEN() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("HSTRLEN() got = %v, want %v", got, tt.want) + } + }) + } + }) + + t.Run("TestSugarDB_HVALS", func(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + presetValue interface{} + key string + want []string + wantErr bool + }{ + { + name: "1. Return all the values from a hash", + key: "hvals_key1", + presetValue: hash.Hash{ + "field1": {Value: "value1"}, + "field2": {Value: 123456789}, + "field3": {Value: 3.142}, + }, + want: []string{"value1", "123456789", "3.142"}, + wantErr: false, + }, + { + name: "2. Empty array response when trying to get HSTRLEN non-existent key", + key: "hvals_key2", + presetValue: nil, + want: []string{}, + wantErr: false, + }, + { + name: "3. Trying to get lengths on a non hash map returns error", + key: "hvals_key5", + presetValue: "Default value", + want: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if tt.presetValue != nil { + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } + } + got, err := server.HVals(tt.key) + if (err != nil) != tt.wantErr { + t.Errorf("HVALS() error = %v, wantErr %v", err, tt.wantErr) + return + } + if len(got) != len(tt.want) { t.Errorf("HVALS() got = %v, want %v", got, tt.want) } - } - }) - } -} + for _, g := range got { + if !slices.Contains(tt.want, g) { + t.Errorf("HVALS() got = %v, want %v", got, tt.want) + } + } + }) + } + }) -func TestSugarDB_HGet(t *testing.T) { - server := createSugarDB() - tests := []struct { - name string - presetValue interface{} - key string - fields []string - want []string - wantErr bool - }{ - { - name: "1. Get values from existing hash.", - key: "HgetKey1", - presetValue: hash.Hash{ - "field1": {Value: "value1"}, - "field2": {Value: 365}, - "field3": {Value: 3.142}, + t.Run("TestSugarDB_HGet", func(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + presetValue interface{} + key string + fields []string + want []string + wantErr bool + }{ + { + name: "1. Get values from existing hash.", + key: "HgetKey1", + presetValue: hash.Hash{ + "field1": {Value: "value1"}, + "field2": {Value: 365}, + "field3": {Value: 3.142}, + }, + fields: []string{"field1", "field2", "field3", "field4"}, + want: []string{"value1", "365", "3.142", ""}, + wantErr: false, }, - fields: []string{"field1", "field2", "field3", "field4"}, - want: []string{"value1", "365", "3.142", ""}, - wantErr: false, - }, - { - name: "2. Return empty slice when attempting to get from non-existed key", - presetValue: nil, - key: "HgetKey2", - fields: []string{"field1"}, - want: []string{}, - wantErr: false, - }, - { - name: "3. Error when trying to get from a value that is not a hash map", - presetValue: "Default Value", - key: "HgetKey3", - fields: []string{"field1"}, - want: nil, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.presetValue != nil { - err := presetValue(server, context.Background(), tt.key, tt.presetValue) - if err != nil { - t.Error(err) + { + name: "2. Return empty slice when attempting to get from non-existed key", + presetValue: nil, + key: "HgetKey2", + fields: []string{"field1"}, + want: []string{}, + wantErr: false, + }, + { + name: "3. Error when trying to get from a value that is not a hash map", + presetValue: "Default Value", + key: "HgetKey3", + fields: []string{"field1"}, + want: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if tt.presetValue != nil { + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } + } + got, err := server.HGet(tt.key, tt.fields...) + if (err != nil) != tt.wantErr { + t.Errorf("HGet() error = %v, wantErr %v", err, tt.wantErr) return } - } - got, err := server.HGet(tt.key, tt.fields...) - if (err != nil) != tt.wantErr { - t.Errorf("HGet() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("HGet() got = %v, want %v", got, tt.want) - } - }) - } -} + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("HGet() got = %v, want %v", got, tt.want) + } + }) + } + }) -func TestSugarDB_HMGet(t *testing.T) { - server := createSugarDB() - tests := []struct { - name string - presetValue interface{} - key string - fields []string - want []string - wantErr bool - }{ - { - name: "1. Get values from existing hash.", - key: "HgetKey1", - presetValue: hash.Hash{ - "field1": {Value: "value1"}, - "field2": {Value: 365}, - "field3": {Value: 3.142}, + t.Run("TestSugarDB_HMGet", func(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + presetValue interface{} + key string + fields []string + want []string + wantErr bool + }{ + { + name: "1. Get values from existing hash.", + key: "HMgetKey1", + presetValue: hash.Hash{ + "field1": {Value: "value1"}, + "field2": {Value: 365}, + "field3": {Value: 3.142}, + }, + fields: []string{"field1", "field2", "field3", "field4"}, + want: []string{"value1", "365", "3.142", ""}, + wantErr: false, }, - fields: []string{"field1", "field2", "field3", "field4"}, - want: []string{"value1", "365", "3.142", ""}, - wantErr: false, - }, - { - name: "2. Return empty slice when attempting to get from non-existed key", - presetValue: nil, - key: "HgetKey2", - fields: []string{"field1"}, - want: []string{}, - wantErr: false, - }, - { - name: "3. Error when trying to get from a value that is not a hash map", - presetValue: "Default Value", - key: "HgetKey3", - fields: []string{"field1"}, - want: nil, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.presetValue != nil { - err := presetValue(server, context.Background(), tt.key, tt.presetValue) - if err != nil { - t.Error(err) + { + name: "2. Return empty slice when attempting to get from non-existed key", + presetValue: nil, + key: "HMgetKey2", + fields: []string{"field1"}, + want: []string{}, + wantErr: false, + }, + { + name: "3. Error when trying to get from a value that is not a hash map", + presetValue: "Default Value", + key: "HMgetKey3", + fields: []string{"field1"}, + want: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if tt.presetValue != nil { + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } + } + got, err := server.HGet(tt.key, tt.fields...) + if (err != nil) != tt.wantErr { + t.Errorf("HMGet() error = %v, wantErr %v", err, tt.wantErr) return } - } - got, err := server.HGet(tt.key, tt.fields...) - if (err != nil) != tt.wantErr { - t.Errorf("HMGet() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("HMGet() got = %v, want %v", got, tt.want) - } - }) - } -} + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("HMGet() got = %v, want %v", got, tt.want) + } + }) + } + }) -func TestSugarDB_HExpire(t *testing.T) { - server := createSugarDB() - tests := []struct { - name string - presetValue interface{} - key string - fields []string - expireOption ExpireOptions - want []int - wantErr bool - }{ - { - name: "1. Set Expiration from existing hash.", - key: "HExpireKey1", - presetValue: hash.Hash{ - "field1": {Value: "value1"}, - "field2": {Value: 365}, - "field3": {Value: 3.142}, + t.Run("TestSugarDB_HExpire", func(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + presetValue interface{} + key string + fields []string + expireOption ExpireOptions + want []int + wantErr bool + }{ + { + name: "1. Set Expiration from existing hash.", + key: "HExpireKey1", + presetValue: hash.Hash{ + "field1": {Value: "value1"}, + "field2": {Value: 365}, + "field3": {Value: 3.142}, + }, + fields: []string{"field1", "field2", "field3"}, + want: []int{1, 1, 1}, + wantErr: false, }, - fields: []string{"field1", "field2", "field3"}, - want: []int{1, 1, 1}, - wantErr: false, - }, - { - name: "2. Return -2 when attempting to get from non-existed key", - presetValue: nil, - key: "HExpireKey2", - fields: []string{"field1"}, - want: []int{-2}, - wantErr: false, - }, - { - name: "3. Error when trying to get from a value that is not a hash map", - presetValue: "Default Value", - key: "HExpireKey3", - fields: []string{"field1"}, - want: nil, - wantErr: true, - }, - { - name: "4. Set Expiration with option NX.", - key: "HExpireKey4", - presetValue: hash.Hash{ - "field1": {Value: "value1"}, - "field2": {Value: 365}, - "field3": {Value: 3.142}, + { + name: "2. Return -2 when attempting to get from non-existed key", + presetValue: nil, + key: "HExpireKey2", + fields: []string{"field1"}, + want: []int{-2}, + wantErr: false, }, - fields: []string{"field1", "field2", "field3"}, - expireOption: NX, - want: []int{1, 1, 1}, - wantErr: false, - }, - { - name: "5. Set Expiration with option XX.", - key: "HExpireKey5", - presetValue: hash.Hash{ - "field1": {Value: "value1"}, - "field2": {Value: 365}, - "field3": {Value: 3.142}, + { + name: "3. Error when trying to get from a value that is not a hash map", + presetValue: "Default Value", + key: "HExpireKey3", + fields: []string{"field1"}, + want: nil, + wantErr: true, }, - fields: []string{"field1", "field2", "field3"}, - expireOption: XX, - want: []int{0, 0, 0}, - wantErr: false, - }, - { - name: "6. Set Expiration with option GT.", - key: "HExpireKey6", - presetValue: hash.Hash{ - "field1": {Value: "value1"}, - "field2": {Value: 365}, - "field3": {Value: 3.142}, + { + name: "4. Set Expiration with option NX.", + key: "HExpireKey4", + presetValue: hash.Hash{ + "field1": {Value: "value1"}, + "field2": {Value: 365}, + "field3": {Value: 3.142}, + }, + fields: []string{"field1", "field2", "field3"}, + expireOption: NX, + want: []int{1, 1, 1}, + wantErr: false, }, - fields: []string{"field1", "field2", "field3"}, - expireOption: GT, - want: []int{0, 0, 0}, - wantErr: false, - }, - { - name: "7. Set Expiration with option LT.", - key: "HExpireKey7", - presetValue: hash.Hash{ - "field1": {Value: "value1"}, - "field2": {Value: 365}, - "field3": {Value: 3.142}, + { + name: "5. Set Expiration with option XX.", + key: "HExpireKey5", + presetValue: hash.Hash{ + "field1": {Value: "value1"}, + "field2": {Value: 365}, + "field3": {Value: 3.142}, + }, + fields: []string{"field1", "field2", "field3"}, + expireOption: XX, + want: []int{0, 0, 0}, + wantErr: false, }, - fields: []string{"field1", "field2", "field3"}, - expireOption: LT, - want: []int{1, 1, 1}, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.presetValue != nil { - err := presetValue(server, context.Background(), tt.key, tt.presetValue) - if err != nil { - t.Error(err) + { + name: "6. Set Expiration with option GT.", + key: "HExpireKey6", + presetValue: hash.Hash{ + "field1": {Value: "value1"}, + "field2": {Value: 365}, + "field3": {Value: 3.142}, + }, + fields: []string{"field1", "field2", "field3"}, + expireOption: GT, + want: []int{0, 0, 0}, + wantErr: false, + }, + { + name: "7. Set Expiration with option LT.", + key: "HExpireKey7", + presetValue: hash.Hash{ + "field1": {Value: "value1"}, + "field2": {Value: 365}, + "field3": {Value: 3.142}, + }, + fields: []string{"field1", "field2", "field3"}, + expireOption: LT, + want: []int{1, 1, 1}, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if tt.presetValue != nil { + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } + } + got, err := server.HExpire(tt.key, 5, tt.expireOption, tt.fields...) + if (err != nil) != tt.wantErr { + t.Errorf("HExpire() error = %v, wantErr %v", err, tt.wantErr) return } - } - got, err := server.HExpire(tt.key, 5, tt.expireOption, tt.fields...) - if (err != nil) != tt.wantErr { - t.Errorf("HExpire() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("HExpire() got = %v, want %v", got, tt.want) - } - }) - } -} + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("HExpire() got = %v, want %v", got, tt.want) + } + }) + } + }) -func TestSugarDB_HTTL(t *testing.T) { - server := createSugarDB() - tests := []struct { - name string - presetValue interface{} - key string - fields []string - want []int - wantErr bool - }{ - { - name: "1. Get TTL for one field when expireTime is set.", - key: "HExpireKey1", - presetValue: hash.Hash{ - "field1": {Value: "value1", ExpireAt: server.clock.Now().Add(time.Duration(500) * time.Second)}, + t.Run("TestSugarDB_HTTL", func(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + presetValue interface{} + key string + fields []string + want []int + wantErr bool + }{ + { + name: "1. Get TTL for one field when expireTime is set.", + key: "HTTL_Key1", + presetValue: hash.Hash{ + "field1": {Value: "value1", ExpireAt: server.clock.Now().Add(time.Duration(500) * time.Second)}, + }, + fields: []string{"field1"}, + want: []int{500}, + wantErr: false, }, - fields: []string{"field1"}, - want: []int{500}, - wantErr: false, - }, - { - name: "2. Get TTL for multiple fields when expireTime is set.", - presetValue: hash.Hash{ - "field1": {Value: "value1", ExpireAt: server.clock.Now().Add(time.Duration(500) * time.Second)}, - "field2": {Value: "value2", ExpireAt: server.clock.Now().Add(time.Duration(500) * time.Second)}, - "field3": {Value: "value3", ExpireAt: server.clock.Now().Add(time.Duration(500) * time.Second)}, + { + name: "2. Get TTL for multiple fields when expireTime is set.", + presetValue: hash.Hash{ + "field1": {Value: "value1", ExpireAt: server.clock.Now().Add(time.Duration(500) * time.Second)}, + "field2": {Value: "value2", ExpireAt: server.clock.Now().Add(time.Duration(500) * time.Second)}, + "field3": {Value: "value3", ExpireAt: server.clock.Now().Add(time.Duration(500) * time.Second)}, + }, + key: "HTTL_Key2", + fields: []string{"field1", "field2", "field3"}, + want: []int{500, 500, 500}, + wantErr: false, }, - key: "HExpireKey2", - fields: []string{"field1", "field2", "field3"}, - want: []int{500, 500, 500}, - wantErr: false, - }, - { - name: "3. Get TTL for one field when expireTime is not set.", - presetValue: hash.Hash{ - "field1": {Value: "value1"}, + { + name: "3. Get TTL for one field when expireTime is not set.", + presetValue: hash.Hash{ + "field1": {Value: "value1"}, + }, + key: "HTTL_Key3", + fields: []string{"field1"}, + want: []int{-1}, + wantErr: false, }, - key: "HExpireKey3", - fields: []string{"field1"}, - want: []int{-1}, - wantErr: false, - }, - { - name: "4. Get TTL for multiple fields when expireTime is not set.", - key: "HExpireKey4", - presetValue: hash.Hash{ - "field1": {Value: "value1"}, - "field2": {Value: 365}, - "field3": {Value: 3.142}, + { + name: "4. Get TTL for multiple fields when expireTime is not set.", + key: "HTTL_Key4", + presetValue: hash.Hash{ + "field1": {Value: "value1"}, + "field2": {Value: 365}, + "field3": {Value: 3.142}, + }, + fields: []string{"field1", "field2", "field3"}, + want: []int{-1, -1, -1}, + wantErr: false, }, - fields: []string{"field1", "field2", "field3"}, - want: []int{-1, -1, -1}, - wantErr: false, - }, - { - name: "5. Try to get TTL for key that doesn't exist.", - key: "HExpireKey5", - presetValue: nil, - fields: []string{"field1"}, - want: []int{-2}, - wantErr: false, - }, - { - name: "6. Try to get TTL for key that isn't a hash.", - key: "HExpireKey6", - presetValue: "not a hash", - fields: []string{"field1", "field2", "field3"}, - want: nil, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.presetValue != nil { - err := presetValue(server, context.Background(), tt.key, tt.presetValue) - if err != nil { - t.Error(err) + { + name: "5. Try to get TTL for key that doesn't exist.", + key: "HTTL_Key5", + presetValue: nil, + fields: []string{"field1"}, + want: []int{-2}, + wantErr: false, + }, + { + name: "6. Try to get TTL for key that isn't a hash.", + key: "HTTL_Key6", + presetValue: "not a hash", + fields: []string{"field1", "field2", "field3"}, + want: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if tt.presetValue != nil { + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } + } + got, err := server.HTTL(tt.key, tt.fields...) + if (err != nil) != tt.wantErr { + t.Errorf("HExpire() error = %v, wantErr %v", err, tt.wantErr) return } - } - got, err := server.HTTL(tt.key, tt.fields...) - if (err != nil) != tt.wantErr { - t.Errorf("HExpire() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("HExpire() got = %v, want %v", got, tt.want) - } - }) - } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("HExpire() got = %v, want %v", got, tt.want) + } + }) + } + }) } diff --git a/sugardb/api_list_test.go b/sugardb/api_list_test.go index d75d5ee..768920f 100644 --- a/sugardb/api_list_test.go +++ b/sugardb/api_list_test.go @@ -20,796 +20,138 @@ import ( "testing" ) -func TestSugarDB_LLEN(t *testing.T) { +func TestSugarDB_List(t *testing.T) { server := createSugarDB() + t.Cleanup(func() { + server.ShutDown() + }) - tests := []struct { - preset bool - presetValue interface{} - name string - key string - want int - wantErr bool - }{ - { - name: "1. If key exists and is a list, return the lists length", - preset: true, - key: "key1", - presetValue: []string{"value1", "value2", "value3", "value4"}, - want: 4, - wantErr: false, - }, - { - name: "2. If key does not exist, return 0", - preset: false, - key: "key2", - presetValue: nil, - want: 0, - wantErr: false, - }, - { - preset: true, - key: "key5", - name: "3. Trying to get lengths on a non-list returns error", - presetValue: "Default value", - want: 0, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.preset { - err := presetValue(server, context.Background(), tt.key, tt.presetValue) - if err != nil { - t.Error(err) - return - } - } - got, err := server.LLen(tt.key) - if (err != nil) != tt.wantErr { - t.Errorf("LLEN() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("LLEN() got = %v, want %v", got, tt.want) - } - }) - } -} + t.Run("TestSugarDB_LLEN", func(t *testing.T) { + t.Parallel() -func TestSugarDB_LINDEX(t *testing.T) { - server := createSugarDB() - - tests := []struct { - preset bool - presetValue interface{} - key string - index uint - name string - want string - wantErr bool - }{ - { - name: "1. Return last element within range", - preset: true, - presetValue: []string{"value1", "value2", "value3", "value4"}, - key: "key1", - index: 3, - want: "value4", - wantErr: false, - }, - { - name: "2. Return first element within range", - preset: true, - presetValue: []string{"value1", "value2", "value3", "value4"}, - key: "key2", - index: 0, - want: "value1", - wantErr: false, - }, - { - name: "3. Return middle element within range", - preset: true, - presetValue: []string{"value1", "value2", "value3", "value4"}, - key: "key3", - index: 1, - want: "value2", - wantErr: false, - }, - { - name: "4. If key does not exist, return error", - preset: false, - presetValue: nil, - key: "key4", - index: 0, - want: "", - wantErr: false, - }, - { - name: "5. Trying to get element by index on a non-list returns error", - preset: true, - presetValue: "Default value", - key: "key5", - index: 0, - want: "", - wantErr: true, - }, - { - name: "6. Trying to get index out of range index beyond last index", - preset: true, - presetValue: []string{"value1", "value2", "value3"}, - key: "key6", - index: 3, - want: "", - wantErr: false, - }, - } - for _, tt := range tests { - if tt.preset { - err := presetValue(server, context.Background(), tt.key, tt.presetValue) - if err != nil { - t.Error(err) - return - } + tests := []struct { + preset bool + presetValue interface{} + name string + key string + want int + wantErr bool + }{ + { + name: "1. If key exists and is a list, return the lists length", + preset: true, + key: "llen_key1", + presetValue: []string{"value1", "value2", "value3", "value4"}, + want: 4, + wantErr: false, + }, + { + name: "2. If key does not exist, return 0", + preset: false, + key: "llen_key2", + presetValue: nil, + want: 0, + wantErr: false, + }, + { + preset: true, + key: "llen_key5", + name: "3. Trying to get lengths on a non-list returns error", + presetValue: "Default value", + want: 0, + wantErr: true, + }, } - t.Run(tt.name, func(t *testing.T) { - got, err := server.LIndex(tt.key, tt.index) - if (err != nil) != tt.wantErr { - t.Errorf("LINDEX() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("LINDEX() got = %v, want %v", got, tt.want) - } - }) - } -} - -func TestSugarDB_LMOVE(t *testing.T) { - server := createSugarDB() - - tests := []struct { - name string - preset bool - presetValue map[string]interface{} - source string - destination string - whereFrom string - whereTo string - want bool - wantErr bool - }{ - { - name: "1. Move element from LEFT of left list to LEFT of right list", - preset: true, - presetValue: map[string]interface{}{ - "source1": []string{"one", "two", "three"}, - "destination1": []string{"one", "two", "three"}, - }, - source: "source1", - destination: "destination1", - whereFrom: "LEFT", - whereTo: "LEFT", - want: true, - wantErr: false, - }, - { - name: "2. Move element from LEFT of left list to RIGHT of right list", - preset: true, - presetValue: map[string]interface{}{ - "source2": []string{"one", "two", "three"}, - "destination2": []string{"one", "two", "three"}, - }, - source: "source2", - destination: "destination2", - whereFrom: "LEFT", - whereTo: "RIGHT", - want: true, - wantErr: false, - }, - { - name: "3. Move element from RIGHT of left list to LEFT of right list", - preset: true, - presetValue: map[string]interface{}{ - "source3": []string{"one", "two", "three"}, - "destination3": []string{"one", "two", "three"}, - }, - source: "source3", - destination: "destination3", - whereFrom: "RIGHT", - whereTo: "LEFT", - want: true, - wantErr: false, - }, - { - name: "4. Move element from RIGHT of left list to RIGHT of right list", - preset: true, - presetValue: map[string]interface{}{ - "source4": []string{"one", "two", "three"}, - "destination4": []string{"one", "two", "three"}, - }, - source: "source4", - destination: "destination4", - whereFrom: "RIGHT", - whereTo: "RIGHT", - want: true, - wantErr: false, - }, - { - name: "5. Throw error when the right list is non-existent", - preset: true, - presetValue: map[string]interface{}{ - "source5": []string{"one", "two", "three"}, - }, - source: "source5", - destination: "destination5", - whereFrom: "LEFT", - whereTo: "LEFT", - want: false, - wantErr: true, - }, - { - name: "6. Throw error when right list in not a list", - preset: true, - presetValue: map[string]interface{}{ - "source6": []string{"one", "two", "tree"}, - "destination6": "Default value", - }, - source: "source6", - destination: "destination6", - whereFrom: "LEFT", - whereTo: "LEFT", - want: false, - wantErr: true, - }, - { - name: "7. Throw error when left list is non-existent", - preset: true, - presetValue: map[string]interface{}{ - "destination7": []string{"one", "two", "three"}, - }, - source: "source7", - destination: "destination7", - whereFrom: "LEFT", - whereTo: "LEFT", - want: false, - wantErr: true, - }, - { - name: "8. Throw error when left list is not a list", - preset: true, - presetValue: map[string]interface{}{ - "source8": "Default value", - "destination8": []string{"one", "two", "three"}, - }, - source: "source8", - destination: "destination8", - whereFrom: "LEFT", - whereTo: "LEFT", - want: false, - wantErr: true, - }, - { - name: "9. Throw error when WHEREFROM argument is not LEFT/RIGHT", - preset: false, - presetValue: map[string]interface{}{}, - source: "source9", - destination: "destination9", - whereFrom: "LEFT", - whereTo: "LEFT", - want: false, - wantErr: true, - }, - { - name: "10. Throw error when WHERETO argument is not LEFT/RIGHT", - preset: false, - presetValue: map[string]interface{}{}, - source: "source10", - destination: "destination10", - whereFrom: "LEFT", - whereTo: "LEFT", - want: false, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.preset { - for k, v := range tt.presetValue { - err := presetValue(server, context.Background(), k, v) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if tt.preset { + err := presetValue(server, context.Background(), tt.key, tt.presetValue) if err != nil { t.Error(err) return } } - } - got, err := server.LMove(tt.source, tt.destination, tt.whereFrom, tt.whereTo) - if (err != nil) != tt.wantErr { - t.Errorf("LMOVE() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("LMOVE() got = %v, want %v", got, tt.want) - } - }) - } -} - -func TestSugarDB_POP(t *testing.T) { - server := createSugarDB() - - tests := []struct { - name string - preset bool - presetValue interface{} - key string - count uint - popFunc func(key string, count uint) ([]string, error) - want []string - wantErr bool - }{ - { - name: "1. LPOP returns last element and removed first element from the list", - preset: true, - presetValue: []string{"value1", "value2", "value3", "value4"}, - key: "key1", - count: 1, - popFunc: server.LPop, - want: []string{"value1"}, - wantErr: false, - }, - { - name: "2. RPOP returns last element and removed last element from the list", - preset: true, - presetValue: []string{"value1", "value2", "value3", "value4"}, - key: "key2", - count: 1, - popFunc: server.RPop, - want: []string{"value4"}, - wantErr: false, - }, - { - name: "3. Trying to execute LPOP from a non-list item return an error", - preset: true, - key: "key3", - count: 1, - presetValue: "Default value", - popFunc: server.LPop, - want: []string{}, - wantErr: true, - }, - { - name: "4. Trying to execute RPOP from a non-list item return an error", - preset: true, - presetValue: "Default value", - key: "key6", - count: 1, - popFunc: server.RPop, - want: []string{}, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.preset { - err := presetValue(server, context.Background(), tt.key, tt.presetValue) - if err != nil { - t.Error(err) + got, err := server.LLen(tt.key) + if (err != nil) != tt.wantErr { + t.Errorf("LLEN() error = %v, wantErr %v", err, tt.wantErr) return } - } - got, err := tt.popFunc(tt.key, tt.count) - if (err != nil) != tt.wantErr { - t.Errorf("POP() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("POP() got = %v, want %v", got, tt.want) - } - }) - } -} - -func TestSugarDB_LPUSH(t *testing.T) { - server := createSugarDB() - - tests := []struct { - name string - preset bool - key string - values []string - presetValue interface{} - lpushFunc func(key string, values ...string) (int, error) - want int - wantErr bool - }{ - { - name: "1. LPUSHX to existing list prepends the element to the list", - preset: true, - presetValue: []string{"1", "2", "4", "5"}, - key: "key1", - values: []string{"value1", "value2"}, - lpushFunc: server.LPushX, - want: 6, - wantErr: false, - }, - { - name: "2. LPUSH on existing list prepends the elements to the list", - preset: true, - presetValue: []string{"1", "2", "4", "5"}, - key: "key2", - values: []string{"value1", "value2"}, - lpushFunc: server.LPush, - want: 6, - wantErr: false, - }, - { - name: "3. LPUSH on non-existent list creates the list", - preset: false, - presetValue: nil, - key: "key3", - values: []string{"value1", "value2"}, - lpushFunc: server.LPush, - want: 2, - wantErr: false, - }, - { - name: "4. LPUSHX command returns error on non-existent list", - preset: false, - presetValue: nil, - key: "key4", - values: []string{"value1", "value2"}, - lpushFunc: server.LPushX, - want: 0, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.preset { - err := presetValue(server, context.Background(), tt.key, tt.presetValue) - if err != nil { - t.Error(err) - return + if got != tt.want { + t.Errorf("LLEN() got = %v, want %v", got, tt.want) } - } - got, err := tt.lpushFunc(tt.key, tt.values...) - if (err != nil) != tt.wantErr { - t.Errorf("LPUSH() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("LPUSH() got = %v, want %v", got, tt.want) - } - }) - } -} - -func TestSugarDB_RPUSH(t *testing.T) { - server := createSugarDB() - - tests := []struct { - name string - preset bool - key string - values []string - presetValue interface{} - rpushFunc func(key string, values ...string) (int, error) - want int - wantErr bool - }{ - { - name: "1. RPUSH on non-existent list creates the list", - preset: false, - presetValue: nil, - key: "key1", - values: []string{"value1", "value2"}, - rpushFunc: server.RPush, - want: 2, - wantErr: false, - }, - { - name: "2. RPUSHX command returns error on non-existent list", - preset: false, - presetValue: nil, - key: "key2", - values: []string{"value1", "value2"}, - rpushFunc: server.RPushX, - want: 0, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.preset { - err := presetValue(server, context.Background(), tt.key, tt.presetValue) - if err != nil { - t.Error(err) - return - } - } - got, err := tt.rpushFunc(tt.key, tt.values...) - if (err != nil) != tt.wantErr { - t.Errorf("RPUSH() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("RPUSH() got = %v, want %v", got, tt.want) - } - }) - } -} - -func TestSugarDB_LRANGE(t *testing.T) { - server := createSugarDB() - - tests := []struct { - name string - preset bool - presetValue interface{} - key string - start int - end int - want []string - wantErr bool - }{ - { - // Return sub-list within range. - // Both start and end indices are positive. - // End index is greater than start index. - name: "1. Return sub-list within range.", - preset: true, - presetValue: []string{"value1", "value2", "value3", "value4", "value5", "value6", "value7", "value8"}, - key: "key1", - start: 3, - end: 6, - want: []string{"value4", "value5", "value6", "value7"}, - wantErr: false, - }, - { - name: "2. Return sub-list from start index to the end of the list when end index is -1", - preset: true, - presetValue: []string{"value1", "value2", "value3", "value4", "value5", "value6", "value7", "value8"}, - key: "key2", - start: 3, - end: -1, - want: []string{"value4", "value5", "value6", "value7", "value8"}, - wantErr: false, - }, - { - name: "3. Return empty list when the end index is less than start index", - preset: true, - presetValue: []string{"value1", "value2", "value3", "value4", "value5", "value6", "value7", "value8"}, - key: "key3", - start: 3, - end: 0, - want: []string{}, - wantErr: false, - }, - { - name: "4. If key does not exist, return empty list", - preset: false, - presetValue: nil, - key: "key4", - start: 0, - end: 2, - want: []string{}, - wantErr: false, - }, - - { - name: "5. Error when executing command on non-list command", - preset: true, - presetValue: "Default value", - key: "key5", - start: 0, - end: 3, - want: nil, - wantErr: true, - }, - { - name: "6. Start index calculated from end of list when start index is less than 0", - preset: true, - presetValue: []string{"value1", "value2", "value3", "value4"}, - key: "key6", - start: -3, - end: 3, - want: []string{"value2", "value3", "value4"}, - wantErr: false, - }, - { - name: "7. Empty list when start index is higher than the length of the list", - preset: true, - presetValue: []string{"value1", "value2", "value3"}, - key: "key7", - start: 10, - end: 11, - want: []string{}, - wantErr: false, - }, - { - name: "8. One element when start and end indices are equal", - preset: true, - presetValue: []string{"value1", "value2", "value3"}, - key: "key8", - start: 1, - end: 1, - want: []string{"value2"}, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.preset { - err := presetValue(server, context.Background(), tt.key, tt.presetValue) - if err != nil { - t.Error(err) - return - } - } - got, err := server.LRange(tt.key, tt.start, tt.end) - if (err != nil) != tt.wantErr { - t.Errorf("LRANGE() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("LRANGE() got = %v, want %v", got, tt.want) - } - }) - } -} - -func TestSugarDB_LREM(t *testing.T) { - server := createSugarDB() - - tests := []struct { - name string - preset bool - presetValue interface{} - key string - count int - value string - want int - wantErr bool - }{ - { - name: "1. Remove the first 3 elements that appear in the list", - preset: true, - presetValue: []string{"1", "2", "4", "4", "5", "6", "7", "4", "8", "4", "9", "10", "5", "4"}, - key: "key1", - count: 3, - value: "4", - want: 3, - wantErr: false, - }, - { - name: "2. Remove the last 3 elements that appear in the list", - preset: true, - presetValue: []string{"1", "2", "4", "4", "5", "6", "7", "4", "8", "4", "9", "10", "5", "4"}, - key: "key2", - count: -3, - value: "4", - want: 3, - wantErr: false, - }, - { - name: "3. Throw error on non-list item", - preset: true, - presetValue: "Default value", - key: "LremKey8", - count: 0, - value: "value1", - want: 0, - wantErr: true, - }, - } - for _, tt := range tests { - if tt.preset { - err := presetValue(server, context.Background(), tt.key, tt.presetValue) - if err != nil { - t.Error(err) - return - } + }) } - t.Run(tt.name, func(t *testing.T) { - got, err := server.LRem(tt.key, tt.count, tt.value) - if (err != nil) != tt.wantErr { - t.Errorf("LREM() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("LREM() got = %v, want %v", got, tt.want) - } - }) - } -} + }) -func TestSugarDB_LSET(t *testing.T) { - server := createSugarDB() + t.Run("TestSugarDB_LINDEX", func(t *testing.T) { + t.Parallel() - tests := []struct { - name string - preset bool - presetValue interface{} - key string - index int - value string - want bool - wantErr bool - }{ - { - name: "1. Return last element within range", - preset: true, - presetValue: []string{"value1", "value2", "value3", "value4"}, - key: "key1", - index: 3, - value: "new-value", - want: true, - wantErr: false, - }, - { - name: "2. Return first element within range", - preset: true, - presetValue: []string{"value1", "value2", "value3", "value4"}, - key: "key2", - index: 0, - value: "new-value", - want: true, - wantErr: false, - }, - { - name: "3. Return middle element within range", - preset: true, - presetValue: []string{"value1", "value2", "value3", "value4"}, - key: "key3", - index: 1, - value: "new-value", - want: true, - wantErr: false, - }, - { - name: "4. If key does not exist, return error", - preset: false, - presetValue: nil, - key: "key4", - index: 0, - value: "element", - want: false, - wantErr: true, - }, - { - name: "5. Trying to get element by index on a non-list returns error", - preset: true, - presetValue: "Default value", - key: "key5", - index: 0, - value: "element", - want: false, - wantErr: true, - }, - { - name: "6. Trying to get index out of range index beyond last index", - preset: true, - presetValue: []string{"value1", "value2", "value3"}, - key: "key6", - index: 3, - value: "element", - want: false, - wantErr: true, - }, - { - name: "7. Trying to get index out of range with negative index", - preset: true, - presetValue: []string{"value1", "value2", "value3"}, - key: "key7", - index: -4, - value: "element", - want: false, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { + tests := []struct { + preset bool + presetValue interface{} + key string + index uint + name string + want string + wantErr bool + }{ + { + name: "1. Return last element within range", + preset: true, + presetValue: []string{"value1", "value2", "value3", "value4"}, + key: "lindex_key1", + index: 3, + want: "value4", + wantErr: false, + }, + { + name: "2. Return first element within range", + preset: true, + presetValue: []string{"value1", "value2", "value3", "value4"}, + key: "lindex_key2", + index: 0, + want: "value1", + wantErr: false, + }, + { + name: "3. Return middle element within range", + preset: true, + presetValue: []string{"value1", "value2", "value3", "value4"}, + key: "lindex_key3", + index: 1, + want: "value2", + wantErr: false, + }, + { + name: "4. If key does not exist, return error", + preset: false, + presetValue: nil, + key: "lindex_key4", + index: 0, + want: "", + wantErr: false, + }, + { + name: "5. Trying to get element by index on a non-list returns error", + preset: true, + presetValue: "Default value", + key: "lindex_key5", + index: 0, + want: "", + wantErr: true, + }, + { + name: "6. Trying to get index out of range index beyond last index", + preset: true, + presetValue: []string{"value1", "value2", "value3"}, + key: "lindex_key6", + index: 3, + want: "", + wantErr: false, + }, + } + for _, tt := range tests { if tt.preset { err := presetValue(server, context.Background(), tt.key, tt.presetValue) if err != nil { @@ -817,122 +159,797 @@ func TestSugarDB_LSET(t *testing.T) { return } } - got, err := server.LSet(tt.key, tt.index, tt.value) - if (err != nil) != tt.wantErr { - t.Errorf("LSET() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("LSET() got = %v, want %v", got, tt.want) - } - }) - } -} - -func TestSugarDB_LTRIM(t *testing.T) { - server := createSugarDB() - - tests := []struct { - name string - preset bool - presetValue interface{} - key string - start int - end int - want bool - wantErr bool - }{ - { - // Return trim within range. - // Both start and end indices are positive. - // End index is greater than start index. - name: "1. Return trim within range", - preset: true, - presetValue: []string{"value1", "value2", "value3", "value4", "value5", "value6", "value7", "value8"}, - key: "key1", - start: 3, - end: 6, - want: true, - wantErr: false, - }, - { - name: "2. Return element from start index to end index when end index is greater than length of the list", - preset: true, - presetValue: []string{"value1", "value2", "value3", "value4", "value5", "value6", "value7", "value8"}, - key: "key2", - start: 5, - end: -1, - want: true, - wantErr: false, - }, - { - name: "3. Return false when end index is smaller than start index.", - preset: true, - presetValue: []string{"value1", "value2", "value3", "value4"}, - key: "key3", - start: 3, - end: 1, - want: true, - wantErr: false, - }, - { - name: "4. If key does not exist, return true", - preset: false, - presetValue: nil, - key: "key4", - start: 0, - end: 2, - want: true, - wantErr: false, - }, - { - name: "5. Trying to get element by index on a non-list returns error", - preset: true, - presetValue: "Default value", - key: "key5", - start: 0, - end: 3, - want: false, - wantErr: true, - }, - { - name: "6. Trim from the end when start index is less than 0", - preset: true, - presetValue: []string{"value1", "value2", "value3", "value4"}, - key: "key6", - start: -3, - end: 3, - want: true, - wantErr: false, - }, - { - name: "7. Return true when start index is higher than the length of the list", - preset: true, - presetValue: []string{"value1", "value2", "value3"}, - key: "key7", - start: 10, - end: 11, - want: true, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.preset { - err := presetValue(server, context.Background(), tt.key, tt.presetValue) - if err != nil { - t.Error(err) + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + got, err := server.LIndex(tt.key, tt.index) + if (err != nil) != tt.wantErr { + t.Errorf("LINDEX() error = %v, wantErr %v", err, tt.wantErr) return } - } - got, err := server.LTrim(tt.key, tt.start, tt.end) - if (err != nil) != tt.wantErr { - t.Errorf("LTRIM() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("LTRIM() got = %v, want %v", got, tt.want) - } - }) - } + if got != tt.want { + t.Errorf("LINDEX() got = %v, want %v", got, tt.want) + } + }) + } + }) + + t.Run("TestSugarDB_LMOVE", func(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + preset bool + presetValue map[string]interface{} + source string + destination string + whereFrom string + whereTo string + want bool + wantErr bool + }{ + { + name: "1. Move element from LEFT of left list to LEFT of right list", + preset: true, + presetValue: map[string]interface{}{ + "lmove_source1": []string{"one", "two", "three"}, + "lmove_destination1": []string{"one", "two", "three"}, + }, + source: "lmove_source1", + destination: "lmove_destination1", + whereFrom: "LEFT", + whereTo: "LEFT", + want: true, + wantErr: false, + }, + { + name: "2. Move element from LEFT of left list to RIGHT of right list", + preset: true, + presetValue: map[string]interface{}{ + "lmove_source2": []string{"one", "two", "three"}, + "lmove_destination2": []string{"one", "two", "three"}, + }, + source: "lmove_source2", + destination: "lmove_destination2", + whereFrom: "LEFT", + whereTo: "RIGHT", + want: true, + wantErr: false, + }, + { + name: "3. Move element from RIGHT of left list to LEFT of right list", + preset: true, + presetValue: map[string]interface{}{ + "lmove_source3": []string{"one", "two", "three"}, + "lmove_destination3": []string{"one", "two", "three"}, + }, + source: "lmove_source3", + destination: "lmove_destination3", + whereFrom: "RIGHT", + whereTo: "LEFT", + want: true, + wantErr: false, + }, + { + name: "4. Move element from RIGHT of left list to RIGHT of right list", + preset: true, + presetValue: map[string]interface{}{ + "lmove_source4": []string{"one", "two", "three"}, + "lmove_destination4": []string{"one", "two", "three"}, + }, + source: "lmove_source4", + destination: "lmove_destination4", + whereFrom: "RIGHT", + whereTo: "RIGHT", + want: true, + wantErr: false, + }, + { + name: "5. Throw error when the right list is non-existent", + preset: true, + presetValue: map[string]interface{}{ + "lmove_source5": []string{"one", "two", "three"}, + }, + source: "lmove_source5", + destination: "lmove_destination5", + whereFrom: "LEFT", + whereTo: "LEFT", + want: false, + wantErr: true, + }, + { + name: "6. Throw error when right list in not a list", + preset: true, + presetValue: map[string]interface{}{ + "lmove_source6": []string{"one", "two", "tree"}, + "lmove_destination6": "Default value", + }, + source: "lmove_source6", + destination: "lmove_destination6", + whereFrom: "LEFT", + whereTo: "LEFT", + want: false, + wantErr: true, + }, + { + name: "7. Throw error when left list is non-existent", + preset: true, + presetValue: map[string]interface{}{ + "lmove_destination7": []string{"one", "two", "three"}, + }, + source: "lmove_source7", + destination: "lmove_destination7", + whereFrom: "LEFT", + whereTo: "LEFT", + want: false, + wantErr: true, + }, + { + name: "8. Throw error when left list is not a list", + preset: true, + presetValue: map[string]interface{}{ + "lmove_source8": "Default value", + "lmove_destination8": []string{"one", "two", "three"}, + }, + source: "lmove_source8", + destination: "lmove_destination8", + whereFrom: "LEFT", + whereTo: "LEFT", + want: false, + wantErr: true, + }, + { + name: "9. Throw error when WHEREFROM argument is not LEFT/RIGHT", + preset: false, + presetValue: map[string]interface{}{}, + source: "lmove_source9", + destination: "lmove_destination9", + whereFrom: "LEFT", + whereTo: "LEFT", + want: false, + wantErr: true, + }, + { + name: "10. Throw error when WHERETO argument is not LEFT/RIGHT", + preset: false, + presetValue: map[string]interface{}{}, + source: "lmove_source10", + destination: "lmove_destination10", + whereFrom: "LEFT", + whereTo: "LEFT", + want: false, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if tt.preset { + for k, v := range tt.presetValue { + err := presetValue(server, context.Background(), k, v) + if err != nil { + t.Error(err) + return + } + } + } + got, err := server.LMove(tt.source, tt.destination, tt.whereFrom, tt.whereTo) + if (err != nil) != tt.wantErr { + t.Errorf("LMOVE() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("LMOVE() got = %v, want %v", got, tt.want) + } + }) + } + }) + + t.Run("TestSugarDB_POP", func(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + preset bool + presetValue interface{} + key string + count uint + popFunc func(key string, count uint) ([]string, error) + want []string + wantErr bool + }{ + { + name: "1. LPOP returns last element and removed first element from the list", + preset: true, + presetValue: []string{"value1", "value2", "value3", "value4"}, + key: "pop_key1", + count: 1, + popFunc: server.LPop, + want: []string{"value1"}, + wantErr: false, + }, + { + name: "2. RPOP returns last element and removed last element from the list", + preset: true, + presetValue: []string{"value1", "value2", "value3", "value4"}, + key: "pop_key2", + count: 1, + popFunc: server.RPop, + want: []string{"value4"}, + wantErr: false, + }, + { + name: "3. Trying to execute LPOP from a non-list item return an error", + preset: true, + key: "pop_key3", + count: 1, + presetValue: "Default value", + popFunc: server.LPop, + want: []string{}, + wantErr: true, + }, + { + name: "4. Trying to execute RPOP from a non-list item return an error", + preset: true, + presetValue: "Default value", + key: "pop_key6", + count: 1, + popFunc: server.RPop, + want: []string{}, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if tt.preset { + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } + } + got, err := tt.popFunc(tt.key, tt.count) + if (err != nil) != tt.wantErr { + t.Errorf("POP() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("POP() got = %v, want %v", got, tt.want) + } + }) + } + }) + + t.Run("TestSugarDB_LPUSH", func(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + preset bool + key string + values []string + presetValue interface{} + lpushFunc func(key string, values ...string) (int, error) + want int + wantErr bool + }{ + { + name: "1. LPUSHX to existing list prepends the element to the list", + preset: true, + presetValue: []string{"1", "2", "4", "5"}, + key: "lpush_key1", + values: []string{"value1", "value2"}, + lpushFunc: server.LPushX, + want: 6, + wantErr: false, + }, + { + name: "2. LPUSH on existing list prepends the elements to the list", + preset: true, + presetValue: []string{"1", "2", "4", "5"}, + key: "lpush_key2", + values: []string{"value1", "value2"}, + lpushFunc: server.LPush, + want: 6, + wantErr: false, + }, + { + name: "3. LPUSH on non-existent list creates the list", + preset: false, + presetValue: nil, + key: "lpush_key3", + values: []string{"value1", "value2"}, + lpushFunc: server.LPush, + want: 2, + wantErr: false, + }, + { + name: "4. LPUSHX command returns error on non-existent list", + preset: false, + presetValue: nil, + key: "lpush_key4", + values: []string{"value1", "value2"}, + lpushFunc: server.LPushX, + want: 0, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if tt.preset { + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } + } + got, err := tt.lpushFunc(tt.key, tt.values...) + if (err != nil) != tt.wantErr { + t.Errorf("LPUSH() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("LPUSH() got = %v, want %v", got, tt.want) + } + }) + } + }) + + t.Run("TestSugarDB_RPUSH", func(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + preset bool + key string + values []string + presetValue interface{} + rpushFunc func(key string, values ...string) (int, error) + want int + wantErr bool + }{ + { + name: "1. RPUSH on non-existent list creates the list", + preset: false, + presetValue: nil, + key: "rpush_key1", + values: []string{"value1", "value2"}, + rpushFunc: server.RPush, + want: 2, + wantErr: false, + }, + { + name: "2. RPUSHX command returns error on non-existent list", + preset: false, + presetValue: nil, + key: "rpush_key2", + values: []string{"value1", "value2"}, + rpushFunc: server.RPushX, + want: 0, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if tt.preset { + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } + } + got, err := tt.rpushFunc(tt.key, tt.values...) + if (err != nil) != tt.wantErr { + t.Errorf("RPUSH() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("RPUSH() got = %v, want %v", got, tt.want) + } + }) + } + }) + + t.Run("TestSugarDB_LRANGE", func(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + preset bool + presetValue interface{} + key string + start int + end int + want []string + wantErr bool + }{ + { + // Return sub-list within range. + // Both start and end indices are positive. + // End index is greater than start index. + name: "1. Return sub-list within range.", + preset: true, + presetValue: []string{"value1", "value2", "value3", "value4", "value5", "value6", "value7", "value8"}, + key: "lrange_key1", + start: 3, + end: 6, + want: []string{"value4", "value5", "value6", "value7"}, + wantErr: false, + }, + { + name: "2. Return sub-list from start index to the end of the list when end index is -1", + preset: true, + presetValue: []string{"value1", "value2", "value3", "value4", "value5", "value6", "value7", "value8"}, + key: "lrange_key2", + start: 3, + end: -1, + want: []string{"value4", "value5", "value6", "value7", "value8"}, + wantErr: false, + }, + { + name: "3. Return empty list when the end index is less than start index", + preset: true, + presetValue: []string{"value1", "value2", "value3", "value4", "value5", "value6", "value7", "value8"}, + key: "lrange_key3", + start: 3, + end: 0, + want: []string{}, + wantErr: false, + }, + { + name: "4. If key does not exist, return empty list", + preset: false, + presetValue: nil, + key: "lrange_key4", + start: 0, + end: 2, + want: []string{}, + wantErr: false, + }, + + { + name: "5. Error when executing command on non-list command", + preset: true, + presetValue: "Default value", + key: "lrange_key5", + start: 0, + end: 3, + want: nil, + wantErr: true, + }, + { + name: "6. Start index calculated from end of list when start index is less than 0", + preset: true, + presetValue: []string{"value1", "value2", "value3", "value4"}, + key: "lrange_key6", + start: -3, + end: 3, + want: []string{"value2", "value3", "value4"}, + wantErr: false, + }, + { + name: "7. Empty list when start index is higher than the length of the list", + preset: true, + presetValue: []string{"value1", "value2", "value3"}, + key: "lrange_key7", + start: 10, + end: 11, + want: []string{}, + wantErr: false, + }, + { + name: "8. One element when start and end indices are equal", + preset: true, + presetValue: []string{"value1", "value2", "value3"}, + key: "lrange_key8", + start: 1, + end: 1, + want: []string{"value2"}, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if tt.preset { + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } + } + got, err := server.LRange(tt.key, tt.start, tt.end) + if (err != nil) != tt.wantErr { + t.Errorf("LRANGE() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("LRANGE() got = %v, want %v", got, tt.want) + } + }) + } + }) + + t.Run("TestSugarDB_LREM", func(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + preset bool + presetValue interface{} + key string + count int + value string + want int + wantErr bool + }{ + { + name: "1. Remove the first 3 elements that appear in the list", + preset: true, + presetValue: []string{"1", "2", "4", "4", "5", "6", "7", "4", "8", "4", "9", "10", "5", "4"}, + key: "lrem_key1", + count: 3, + value: "4", + want: 3, + wantErr: false, + }, + { + name: "2. Remove the last 3 elements that appear in the list", + preset: true, + presetValue: []string{"1", "2", "4", "4", "5", "6", "7", "4", "8", "4", "9", "10", "5", "4"}, + key: "lrem_key2", + count: -3, + value: "4", + want: 3, + wantErr: false, + }, + { + name: "3. Throw error on non-list item", + preset: true, + presetValue: "Default value", + key: "lrem_key8", + count: 0, + value: "value1", + want: 0, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if tt.preset { + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } + } + got, err := server.LRem(tt.key, tt.count, tt.value) + if (err != nil) != tt.wantErr { + t.Errorf("LREM() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("LREM() got = %v, want %v", got, tt.want) + } + }) + } + }) + + t.Run("TestSugarDB_LSET", func(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + preset bool + presetValue interface{} + key string + index int + value string + want bool + wantErr bool + }{ + { + name: "1. Return last element within range", + preset: true, + presetValue: []string{"value1", "value2", "value3", "value4"}, + key: "lset_key1", + index: 3, + value: "new-value", + want: true, + wantErr: false, + }, + { + name: "2. Return first element within range", + preset: true, + presetValue: []string{"value1", "value2", "value3", "value4"}, + key: "lset_key2", + index: 0, + value: "new-value", + want: true, + wantErr: false, + }, + { + name: "3. Return middle element within range", + preset: true, + presetValue: []string{"value1", "value2", "value3", "value4"}, + key: "lset_key3", + index: 1, + value: "new-value", + want: true, + wantErr: false, + }, + { + name: "4. If key does not exist, return error", + preset: false, + presetValue: nil, + key: "lset_key4", + index: 0, + value: "element", + want: false, + wantErr: true, + }, + { + name: "5. Trying to get element by index on a non-list returns error", + preset: true, + presetValue: "Default value", + key: "lset_key5", + index: 0, + value: "element", + want: false, + wantErr: true, + }, + { + name: "6. Trying to get index out of range index beyond last index", + preset: true, + presetValue: []string{"value1", "value2", "value3"}, + key: "lset_key6", + index: 3, + value: "element", + want: false, + wantErr: true, + }, + { + name: "7. Trying to get index out of range with negative index", + preset: true, + presetValue: []string{"value1", "value2", "value3"}, + key: "lset_key7", + index: -4, + value: "element", + want: false, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if tt.preset { + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } + } + got, err := server.LSet(tt.key, tt.index, tt.value) + if (err != nil) != tt.wantErr { + t.Errorf("LSET() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("LSET() got = %v, want %v", got, tt.want) + } + }) + } + }) + + t.Run("TestSugarDB_LTRIM", func(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + preset bool + presetValue interface{} + key string + start int + end int + want bool + wantErr bool + }{ + { + // Return trim within range. + // Both start and end indices are positive. + // End index is greater than start index. + name: "1. Return trim within range", + preset: true, + presetValue: []string{"value1", "value2", "value3", "value4", "value5", "value6", "value7", "value8"}, + key: "ltrim_key1", + start: 3, + end: 6, + want: true, + wantErr: false, + }, + { + name: "2. Return element from start index to end index when end index is greater than length of the list", + preset: true, + presetValue: []string{"value1", "value2", "value3", "value4", "value5", "value6", "value7", "value8"}, + key: "ltrim_key2", + start: 5, + end: -1, + want: true, + wantErr: false, + }, + { + name: "3. Return false when end index is smaller than start index.", + preset: true, + presetValue: []string{"value1", "value2", "value3", "value4"}, + key: "ltrim_key3", + start: 3, + end: 1, + want: true, + wantErr: false, + }, + { + name: "4. If key does not exist, return true", + preset: false, + presetValue: nil, + key: "ltrim_key4", + start: 0, + end: 2, + want: true, + wantErr: false, + }, + { + name: "5. Trying to get element by index on a non-list returns error", + preset: true, + presetValue: "Default value", + key: "ltrim_key5", + start: 0, + end: 3, + want: false, + wantErr: true, + }, + { + name: "6. Trim from the end when start index is less than 0", + preset: true, + presetValue: []string{"value1", "value2", "value3", "value4"}, + key: "ltrim_key6", + start: -3, + end: 3, + want: true, + wantErr: false, + }, + { + name: "7. Return true when start index is higher than the length of the list", + preset: true, + presetValue: []string{"value1", "value2", "value3"}, + key: "ltrim_key7", + start: 10, + end: 11, + want: true, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if tt.preset { + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } + } + got, err := server.LTrim(tt.key, tt.start, tt.end) + if (err != nil) != tt.wantErr { + t.Errorf("LTRIM() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("LTRIM() got = %v, want %v", got, tt.want) + } + }) + } + }) } diff --git a/sugardb/api_set_test.go b/sugardb/api_set_test.go index 679907b..031f722 100644 --- a/sugardb/api_set_test.go +++ b/sugardb/api_set_test.go @@ -22,1190 +22,1198 @@ import ( "testing" ) -func TestSugarDB_SADD(t *testing.T) { +func TestSugarDB_Set(t *testing.T) { server := createSugarDB() - tests := []struct { - name string - presetValue interface{} - key string - members []string - want int - wantErr bool - }{ - { - name: "Create new set on a non-existent key, return count of added elements", - presetValue: nil, - key: "key1", - members: []string{"one", "two", "three", "four"}, - want: 4, - wantErr: false, - }, - { - name: "Add members to an exiting set, skip members that already exist in the set, return added count", - presetValue: set.NewSet([]string{"one", "two", "three", "four"}), - key: "key2", - members: []string{"three", "four", "five", "six", "seven"}, - want: 3, - wantErr: false, - }, - { - name: "Throw error when trying to add to a key that does not hold a set", - presetValue: "Default value", - key: "key3", - members: []string{"member"}, - want: 0, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.presetValue != nil { - err := presetValue(server, context.Background(), tt.key, tt.presetValue) - if err != nil { - t.Error(err) - return - } - } - got, err := server.SAdd(tt.key, tt.members...) - if (err != nil) != tt.wantErr { - t.Errorf("SADD() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("SADD() got = %v, want %v", got, tt.want) - } - }) - } -} + t.Cleanup(func() { + server.ShutDown() + }) -func TestSugarDB_SCARD(t *testing.T) { - server := createSugarDB() + t.Run("TestSugarDB_SADD", func(t *testing.T) { + t.Parallel() - tests := []struct { - name string - presetValue interface{} - key string - want int - wantErr bool - }{ - { - name: "Get cardinality of valid set", - presetValue: set.NewSet([]string{"one", "two", "three", "four"}), - key: "key1", - want: 4, - wantErr: false, - }, - { - name: "Return 0 when trying to get cardinality on non-existent key", - presetValue: nil, - key: "key2", - want: 0, - wantErr: false, - }, - { - name: "Throw error when trying to get cardinality of a value that is not a set", - presetValue: "Default value", - key: "key3", - want: 0, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.presetValue != nil { - err := presetValue(server, context.Background(), tt.key, tt.presetValue) - if err != nil { - t.Error(err) - return - } - } - got, err := server.SCard(tt.key) - if (err != nil) != tt.wantErr { - t.Errorf("SCARD() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("SCARD() got = %v, want %v", got, tt.want) - } - }) - } -} - -func TestSugarDB_SDIFF(t *testing.T) { - server := createSugarDB() - - tests := []struct { - name string - presetValues map[string]interface{} - keys []string - want []string - wantErr bool - }{ - { - name: "Get the difference between 2 sets", - presetValues: map[string]interface{}{ - "key1": set.NewSet([]string{"one", "two", "three", "four", "five"}), - "key2": set.NewSet([]string{"three", "four", "five", "six", "seven", "eight"}), + tests := []struct { + name string + presetValue interface{} + key string + members []string + want int + wantErr bool + }{ + { + name: "1. Create new set on a non-existent key, return count of added elements", + presetValue: nil, + key: "sadd_key1", + members: []string{"one", "two", "three", "four"}, + want: 4, + wantErr: false, }, - keys: []string{"key1", "key2"}, - want: []string{"one", "two"}, - wantErr: false, - }, - { - name: "Get the difference between 3 sets", - presetValues: map[string]interface{}{ - "key3": set.NewSet([]string{"one", "two", "three", "four", "five", "six", "seven", "eight"}), - "key4": set.NewSet([]string{"one", "two", "thirty-six", "twelve", "eleven"}), - "key5": set.NewSet([]string{"seven", "eight", "nine", "ten", "twelve"}), + { + name: "2. Add members to an exiting set, skip members that already exist in the set, return added count", + presetValue: set.NewSet([]string{"one", "two", "three", "four"}), + key: "sadd_key2", + members: []string{"three", "four", "five", "six", "seven"}, + want: 3, + wantErr: false, }, - keys: []string{"key3", "key4", "key5"}, - want: []string{"three", "four", "five", "six"}, - wantErr: false, - }, - { - name: "Return base set element if base set is the only valid set", - presetValues: map[string]interface{}{ - "key6": set.NewSet([]string{"one", "two", "three", "four", "five", "six", "seven", "eight"}), - "key7": "Default value", - "key8": 123456789, + { + name: "2. Throw error when trying to add to a key that does not hold a set", + presetValue: "Default value", + key: "sadd_key3", + members: []string{"member"}, + want: 0, + wantErr: true, }, - keys: []string{"key6", "key7", "key8"}, - want: []string{"one", "two", "three", "four", "five", "six", "seven", "eight"}, - wantErr: false, - }, - { - name: "Throw error when base set is not a set", - presetValues: map[string]interface{}{ - "key9": "Default value", - "key10": set.NewSet([]string{"one", "two", "thirty-six", "twelve", "eleven"}), - "key11": set.NewSet([]string{"seven", "eight", "nine", "ten", "twelve"}), - }, - keys: []string{"key9", "key10", "key11"}, - want: nil, - wantErr: true, - }, - { - name: "Throw error when base set is non-existent", - presetValues: map[string]interface{}{ - "key12": set.NewSet([]string{"one", "two", "thirty-six", "twelve", "eleven"}), - "key13": set.NewSet([]string{"seven", "eight", "nine", "ten", "twelve"}), - }, - keys: []string{"non-existent", "key7", "key8"}, - want: nil, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.presetValues != nil { - for k, v := range tt.presetValues { - err := presetValue(server, context.Background(), k, v) + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.presetValue != nil { + err := presetValue(server, context.Background(), tt.key, tt.presetValue) if err != nil { t.Error(err) return } } - } - got, err := server.SDiff(tt.keys...) - if (err != nil) != tt.wantErr { - t.Errorf("SDIFF() error = %v, wantErr %v", err, tt.wantErr) - return - } - if len(got) != len(tt.want) { - t.Errorf("SDIFF() got = %v, want %v", got, tt.want) - } - for _, g := range got { - if !slices.Contains(tt.want, g) { + got, err := server.SAdd(tt.key, tt.members...) + if (err != nil) != tt.wantErr { + t.Errorf("SADD() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("SADD() got = %v, want %v", got, tt.want) + } + }) + } + }) + + t.Run("TestSugarDB_SCARD", func(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + presetValue interface{} + key string + want int + wantErr bool + }{ + { + name: "1. Get cardinality of valid set", + presetValue: set.NewSet([]string{"one", "two", "three", "four"}), + key: "scard_key1", + want: 4, + wantErr: false, + }, + { + name: "2. Return 0 when trying to get cardinality on non-existent key", + presetValue: nil, + key: "scard_key2", + want: 0, + wantErr: false, + }, + { + name: "3. Throw error when trying to get cardinality of a value that is not a set", + presetValue: "Default value", + key: "scard_key3", + want: 0, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.presetValue != nil { + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } + } + got, err := server.SCard(tt.key) + if (err != nil) != tt.wantErr { + t.Errorf("SCARD() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("SCARD() got = %v, want %v", got, tt.want) + } + }) + } + }) + + t.Run("TestSugarDB_SDIFF", func(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + presetValues map[string]interface{} + keys []string + want []string + wantErr bool + }{ + { + name: "1. Get the difference between 2 sets", + presetValues: map[string]interface{}{ + "sdiff_key1": set.NewSet([]string{"one", "two", "three", "four", "five"}), + "sdiff_key2": set.NewSet([]string{"three", "four", "five", "six", "seven", "eight"}), + }, + keys: []string{"sdiff_key1", "sdiff_key2"}, + want: []string{"one", "two"}, + wantErr: false, + }, + { + name: "2. Get the difference between 3 sets", + presetValues: map[string]interface{}{ + "sdiff_key3": set.NewSet([]string{"one", "two", "three", "four", "five", "six", "seven", "eight"}), + "sdiff_key4": set.NewSet([]string{"one", "two", "thirty-six", "twelve", "eleven"}), + "sdiff_key5": set.NewSet([]string{"seven", "eight", "nine", "ten", "twelve"}), + }, + keys: []string{"sdiff_key3", "sdiff_key4", "sdiff_key5"}, + want: []string{"three", "four", "five", "six"}, + wantErr: false, + }, + { + name: "3. Return base set element if base set is the only valid set", + presetValues: map[string]interface{}{ + "sdiff_key6": set.NewSet([]string{"one", "two", "three", "four", "five", "six", "seven", "eight"}), + "sdiff_key7": "Default value", + "sdiff_key8": 123456789, + }, + keys: []string{"sdiff_key6", "sdiff_key7", "sdiff_key8"}, + want: []string{"one", "two", "three", "four", "five", "six", "seven", "eight"}, + wantErr: false, + }, + { + name: "4. Throw error when base set is not a set", + presetValues: map[string]interface{}{ + "sdiff_key9": "Default value", + "sdiff_key10": set.NewSet([]string{"one", "two", "thirty-six", "twelve", "eleven"}), + "sdiff_key11": set.NewSet([]string{"seven", "eight", "nine", "ten", "twelve"}), + }, + keys: []string{"sdiff_key9", "sdiff_key10", "sdiff_key11"}, + want: nil, + wantErr: true, + }, + { + name: "5. Throw error when base set is non-existent", + presetValues: map[string]interface{}{ + "sdiff_key12": set.NewSet([]string{"one", "two", "thirty-six", "twelve", "eleven"}), + "sdiff_key13": set.NewSet([]string{"seven", "eight", "nine", "ten", "twelve"}), + }, + keys: []string{"sdiff_non-existent", "sdiff_key7", "sdiff_key8"}, + want: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.presetValues != nil { + for k, v := range tt.presetValues { + err := presetValue(server, context.Background(), k, v) + if err != nil { + t.Error(err) + return + } + } + } + got, err := server.SDiff(tt.keys...) + if (err != nil) != tt.wantErr { + t.Errorf("SDIFF() error = %v, wantErr %v", err, tt.wantErr) + return + } + if len(got) != len(tt.want) { t.Errorf("SDIFF() got = %v, want %v", got, tt.want) } - } - }) - } -} - -func TestSugarDB_SDIFFSTORE(t *testing.T) { - server := createSugarDB() - - tests := []struct { - name string - presetValues map[string]interface{} - destination string - keys []string - want int - wantErr bool - }{ - { - name: "Get the difference between 2 sets", - presetValues: map[string]interface{}{ - "key1": set.NewSet([]string{"one", "two", "three", "four", "five"}), - "key2": set.NewSet([]string{"three", "four", "five", "six", "seven", "eight"}), - }, - destination: "destination1", - keys: []string{"key1", "key2"}, - want: 2, - wantErr: false, - }, - { - name: "Get the difference between 3 sets", - presetValues: map[string]interface{}{ - "key3": set.NewSet([]string{"one", "two", "three", "four", "five", "six", "seven", "eight"}), - "key4": set.NewSet([]string{"one", "two", "thirty-six", "twelve", "eleven"}), - "key5": set.NewSet([]string{"seven", "eight", "nine", "ten", "twelve"}), - }, - destination: "destination2", - keys: []string{"key3", "key4", "key5"}, - want: 4, - wantErr: false, - }, - { - name: "Return base set element if base set is the only valid set", - presetValues: map[string]interface{}{ - "key6": set.NewSet([]string{"one", "two", "three", "four", "five", "six", "seven", "eight"}), - "key7": "Default value", - "key8": 123456789, - }, - destination: "destination3", - keys: []string{"key6", "key7", "key8"}, - want: 8, - wantErr: false, - }, - { - name: "Throw error when base set is not a set", - presetValues: map[string]interface{}{ - "key9": "Default value", - "key10": set.NewSet([]string{"one", "two", "thirty-six", "twelve", "eleven"}), - "key11": set.NewSet([]string{"seven", "eight", "nine", "ten", "twelve"}), - }, - destination: "destination4", - keys: []string{"key9", "key10", "key11"}, - want: 0, - wantErr: true, - }, - { - name: " Throw error when base set is non-existent", - destination: "destination5", - presetValues: map[string]interface{}{ - "key12": set.NewSet([]string{"one", "two", "thirty-six", "twelve", "eleven"}), - "key13": set.NewSet([]string{"seven", "eight", "nine", "ten", "twelve"}), - }, - keys: []string{"non-existent", "key7", "key8"}, - want: 0, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.presetValues != nil { - for k, v := range tt.presetValues { - err := presetValue(server, context.Background(), k, v) - if err != nil { - t.Error(err) - return + for _, g := range got { + if !slices.Contains(tt.want, g) { + t.Errorf("SDIFF() got = %v, want %v", got, tt.want) } } - } - got, err := server.SDiffStore(tt.destination, tt.keys...) - if (err != nil) != tt.wantErr { - t.Errorf("SDIFFSTORE() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("SDIFFSTORE() got = %v, want %v", got, tt.want) - } - }) - } -} + }) + } + }) -func TestSugarDB_SINTER(t *testing.T) { - server := createSugarDB() + t.Run("TestSugarDB_SDIFFSTORE", func(t *testing.T) { + t.Parallel() - tests := []struct { - name string - presetValues map[string]interface{} - keys []string - want []string - wantErr bool - }{ - { - name: "Get the intersection between 2 sets", - presetValues: map[string]interface{}{ - "key1": set.NewSet([]string{"one", "two", "three", "four", "five"}), - "key2": set.NewSet([]string{"three", "four", "five", "six", "seven", "eight"}), + tests := []struct { + name string + presetValues map[string]interface{} + destination string + keys []string + want int + wantErr bool + }{ + { + name: "1. Get the difference between 2 sets", + presetValues: map[string]interface{}{ + "sdiffstore_key1": set.NewSet([]string{"one", "two", "three", "four", "five"}), + "sdiffstore_key2": set.NewSet([]string{"three", "four", "five", "six", "seven", "eight"}), + }, + destination: "sdiffstore_destination1", + keys: []string{"sdiffstore_key1", "sdiffstore_key2"}, + want: 2, + wantErr: false, }, - keys: []string{"key1", "key2"}, - want: []string{"three", "four", "five"}, - wantErr: false, - }, - { - name: "Get the intersection between 3 sets", - presetValues: map[string]interface{}{ - "key3": set.NewSet([]string{"one", "two", "three", "four", "five", "six", "seven", "eight"}), - "key4": set.NewSet([]string{"one", "two", "thirty-six", "twelve", "eleven", "eight"}), - "key5": set.NewSet([]string{"one", "eight", "nine", "ten", "twelve"}), + { + name: "2. Get the difference between 3 sets", + presetValues: map[string]interface{}{ + "sdiffstore_key3": set.NewSet([]string{"one", "two", "three", "four", "five", "six", "seven", "eight"}), + "sdiffstore_key4": set.NewSet([]string{"one", "two", "thirty-six", "twelve", "eleven"}), + "sdiffstore_key5": set.NewSet([]string{"seven", "eight", "nine", "ten", "twelve"}), + }, + destination: "sdiffstore_destination2", + keys: []string{"sdiffstore_key3", "sdiffstore_key4", "sdiffstore_key5"}, + want: 4, + wantErr: false, }, - keys: []string{"key3", "key4", "key5"}, - want: []string{"one", "eight"}, - wantErr: false, - }, - { - name: "Throw an error if any of the provided keys are not sets", - presetValues: map[string]interface{}{ - "key6": set.NewSet([]string{"one", "two", "three", "four", "five", "six", "seven", "eight"}), - "key7": "Default value", - "key8": set.NewSet([]string{"one"}), + { + name: "3. Return base set element if base set is the only valid set", + presetValues: map[string]interface{}{ + "sdiffstore_key6": set.NewSet([]string{"one", "two", "three", "four", "five", "six", "seven", "eight"}), + "sdiffstore_key7": "Default value", + "sdiffstore_key8": 123456789, + }, + destination: "sdiffstore_destination3", + keys: []string{"sdiffstore_key6", "sdiffstore_key7", "sdiffstore_key8"}, + want: 8, + wantErr: false, }, - keys: []string{"key6", "key7", "key8"}, - want: nil, - wantErr: true, - }, - { - name: "Throw error when base set is not a set", - presetValues: map[string]interface{}{ - "key9": "Default value", - "key10": set.NewSet([]string{"one", "two", "thirty-six", "twelve", "eleven"}), - "key11": set.NewSet([]string{"seven", "eight", "nine", "ten", "twelve"}), + { + name: "4. Throw error when base set is not a set", + presetValues: map[string]interface{}{ + "sdiffstore_key9": "Default value", + "sdiffstore_key10": set.NewSet([]string{"one", "two", "thirty-six", "twelve", "eleven"}), + "sdiffstore_key11": set.NewSet([]string{"seven", "eight", "nine", "ten", "twelve"}), + }, + destination: "sdiffstore_destination4", + keys: []string{"sdiffstore_key9", "sdiffstore_key10", "sdiffstore_key11"}, + want: 0, + wantErr: true, }, - keys: []string{"key9", "key10", "key11"}, - want: nil, - wantErr: true, - }, - { - name: "If any of the keys does not exist, return an empty array", - presetValues: map[string]interface{}{ - "key12": set.NewSet([]string{"one", "two", "thirty-six", "twelve", "eleven"}), - "key13": set.NewSet([]string{"seven", "eight", "nine", "ten", "twelve"}), + { + name: "5. Throw error when base set is non-existent", + presetValues: map[string]interface{}{ + "sdiffstore_key12": set.NewSet([]string{"one", "two", "thirty-six", "twelve", "eleven"}), + "sdiffstore_key13": set.NewSet([]string{"seven", "eight", "nine", "ten", "twelve"}), + }, + destination: "sdiffstore_destination5", + keys: []string{"sdiffstore_non-existent", "sdiffstore_key7", "sdiffstore_key8"}, + want: 0, + wantErr: true, }, - keys: []string{"non-existent", "key12", "key13"}, - want: []string{}, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.presetValues != nil { - for k, v := range tt.presetValues { - err := presetValue(server, context.Background(), k, v) - if err != nil { - t.Error(err) - return + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.presetValues != nil { + for k, v := range tt.presetValues { + err := presetValue(server, context.Background(), k, v) + if err != nil { + t.Error(err) + return + } } } - } - got, err := server.SInter(tt.keys...) - if (err != nil) != tt.wantErr { - t.Errorf("SINTER() error = %v, wantErr %v", err, tt.wantErr) - return - } - if len(got) != len(tt.want) { - t.Errorf("SINTER() got = %v, want %v", got, tt.want) - } - for _, g := range got { - if !slices.Contains(tt.want, g) { + got, err := server.SDiffStore(tt.destination, tt.keys...) + if (err != nil) != tt.wantErr { + t.Errorf("SDIFFSTORE() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("SDIFFSTORE() got = %v, want %v", got, tt.want) + } + }) + } + }) + + t.Run("TestSugarDB_SINTER", func(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + presetValues map[string]interface{} + keys []string + want []string + wantErr bool + }{ + { + name: "1. Get the intersection between 2 sets", + presetValues: map[string]interface{}{ + "sinter_key1": set.NewSet([]string{"one", "two", "three", "four", "five"}), + "sinter_key2": set.NewSet([]string{"three", "four", "five", "six", "seven", "eight"}), + }, + keys: []string{"sinter_key1", "sinter_key2"}, + want: []string{"three", "four", "five"}, + wantErr: false, + }, + { + name: "2. Get the intersection between 3 sets", + presetValues: map[string]interface{}{ + "sinter_key3": set.NewSet([]string{"one", "two", "three", "four", "five", "six", "seven", "eight"}), + "sinter_key4": set.NewSet([]string{"one", "two", "thirty-six", "twelve", "eleven", "eight"}), + "sinter_key5": set.NewSet([]string{"one", "eight", "nine", "ten", "twelve"}), + }, + keys: []string{"sinter_key3", "sinter_key4", "sinter_key5"}, + want: []string{"one", "eight"}, + wantErr: false, + }, + { + name: "3. Throw an error if any of the provided keys are not sets", + presetValues: map[string]interface{}{ + "sinter_key6": set.NewSet([]string{"one", "two", "three", "four", "five", "six", "seven", "eight"}), + "sinter_key7": "Default value", + "sinter_key8": set.NewSet([]string{"one"}), + }, + keys: []string{"sinter_key6", "sinter_key7", "sinter_key8"}, + want: nil, + wantErr: true, + }, + { + name: "4. Throw error when base set is not a set", + presetValues: map[string]interface{}{ + "sinter_key9": "Default value", + "sinter_key10": set.NewSet([]string{"one", "two", "thirty-six", "twelve", "eleven"}), + "sinter_key11": set.NewSet([]string{"seven", "eight", "nine", "ten", "twelve"}), + }, + keys: []string{"sinter_key9", "sinter_key10", "sinter_key11"}, + want: nil, + wantErr: true, + }, + { + name: "5. If any of the keys does not exist, return an empty array", + presetValues: map[string]interface{}{ + "sinter_key12": set.NewSet([]string{"one", "two", "thirty-six", "twelve", "eleven"}), + "sinter_key13": set.NewSet([]string{"seven", "eight", "nine", "ten", "twelve"}), + }, + keys: []string{"sinter_non-existent", "sinter_key12", "sinter_key13"}, + want: []string{}, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.presetValues != nil { + for k, v := range tt.presetValues { + err := presetValue(server, context.Background(), k, v) + if err != nil { + t.Error(err) + return + } + } + } + got, err := server.SInter(tt.keys...) + if (err != nil) != tt.wantErr { + t.Errorf("SINTER() error = %v, wantErr %v", err, tt.wantErr) + return + } + if len(got) != len(tt.want) { t.Errorf("SINTER() got = %v, want %v", got, tt.want) } - } - }) - } -} + for _, g := range got { + if !slices.Contains(tt.want, g) { + t.Errorf("SINTER() got = %v, want %v", got, tt.want) + } + } + }) + } + }) -func TestSugarDB_SINTERCARD(t *testing.T) { - server := createSugarDB() + t.Run("TestSugarDB_SINTERCARD", func(t *testing.T) { + t.Parallel() - tests := []struct { - name string - presetValues map[string]interface{} - keys []string - limit uint - want int - wantErr bool - }{ - { - name: "Get the full intersect cardinality between 2 sets", - presetValues: map[string]interface{}{ - "key1": set.NewSet([]string{"one", "two", "three", "four", "five"}), - "key2": set.NewSet([]string{"three", "four", "five", "six", "seven", "eight"}), + tests := []struct { + name string + presetValues map[string]interface{} + keys []string + limit uint + want int + wantErr bool + }{ + { + name: "1. Get the full intersect cardinality between 2 sets", + presetValues: map[string]interface{}{ + "sintercard_key1": set.NewSet([]string{"one", "two", "three", "four", "five"}), + "sintercard_key2": set.NewSet([]string{"three", "four", "five", "six", "seven", "eight"}), + }, + keys: []string{"sintercard_key1", "sintercard_key2"}, + limit: 0, + want: 3, + wantErr: false, }, - keys: []string{"key1", "key2"}, - limit: 0, - want: 3, - wantErr: false, - }, - { - name: "Get an intersect cardinality between 2 sets with a limit", - presetValues: map[string]interface{}{ - "key3": set.NewSet([]string{"one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"}), - "key4": set.NewSet([]string{"three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve"}), + { + name: "2. Get an intersect cardinality between 2 sets with a limit", + presetValues: map[string]interface{}{ + "sintercard_key3": set.NewSet([]string{"one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"}), + "sintercard_key4": set.NewSet([]string{"three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve"}), + }, + keys: []string{"sintercard_key3", "sintercard_key4"}, + limit: 3, + want: 3, + wantErr: false, }, - keys: []string{"key3", "key4"}, - limit: 3, - want: 3, - wantErr: false, - }, - { - name: "Get the full intersect cardinality between 3 sets", - presetValues: map[string]interface{}{ - "key5": set.NewSet([]string{"one", "two", "three", "four", "five", "six", "seven", "eight"}), - "key6": set.NewSet([]string{"one", "two", "thirty-six", "twelve", "eleven", "eight"}), - "key7": set.NewSet([]string{"one", "seven", "eight", "nine", "ten", "twelve"}), + { + name: "3. Get the full intersect cardinality between 3 sets", + presetValues: map[string]interface{}{ + "sintercard_key5": set.NewSet([]string{"one", "two", "three", "four", "five", "six", "seven", "eight"}), + "sintercard_key6": set.NewSet([]string{"one", "two", "thirty-six", "twelve", "eleven", "eight"}), + "sintercard_key7": set.NewSet([]string{"one", "seven", "eight", "nine", "ten", "twelve"}), + }, + keys: []string{"sintercard_key5", "sintercard_key6", "sintercard_key7"}, + limit: 0, + want: 2, + wantErr: false, }, - keys: []string{"key5", "key6", "key7"}, - limit: 0, - want: 2, - wantErr: false, - }, - { - name: "Get the intersection of 3 sets with a limit", - presetValues: map[string]interface{}{ - "key8": set.NewSet([]string{"one", "two", "three", "four", "five", "six", "seven", "eight"}), - "key9": set.NewSet([]string{"one", "two", "thirty-six", "twelve", "eleven", "eight"}), - "key10": set.NewSet([]string{"one", "two", "seven", "eight", "nine", "ten", "twelve"}), + { + name: "4. Get the intersection of 3 sets with a limit", + presetValues: map[string]interface{}{ + "sintercard_key8": set.NewSet([]string{"one", "two", "three", "four", "five", "six", "seven", "eight"}), + "sintercard_key9": set.NewSet([]string{"one", "two", "thirty-six", "twelve", "eleven", "eight"}), + "sintercard_key10": set.NewSet([]string{"one", "two", "seven", "eight", "nine", "ten", "twelve"}), + }, + keys: []string{"sintercard_key8", "sintercard_key9", "sintercard_key10"}, + limit: 2, + want: 2, + wantErr: false, }, - keys: []string{"key8", "key9", "key10"}, - limit: 2, - want: 2, - wantErr: false, - }, - { - name: "Return error if any of the keys is non-existent", - presetValues: map[string]interface{}{ - "key11": set.NewSet([]string{"one", "two", "three", "four", "five", "six", "seven", "eight"}), - "key13": set.NewSet([]string{"one"}), + { + name: "5. Return error if any of the keys is non-existent", + presetValues: map[string]interface{}{ + "sintercard_key11": set.NewSet([]string{"one", "two", "three", "four", "five", "six", "seven", "eight"}), + "sintercard_key13": set.NewSet([]string{"one"}), + }, + keys: []string{"sintercard_key11", "sintercard_key12", "sintercard_key13"}, + limit: 0, + want: 0, + wantErr: false, }, - keys: []string{"key11", "key12", "key13"}, - limit: 0, - want: 0, - wantErr: false, - }, - { - name: "Throw error when one of the keys is not a valid set", - presetValues: map[string]interface{}{ - "key14": "Default value", - "key15": set.NewSet([]string{"one", "two", "thirty-six", "twelve", "eleven"}), - "key16": set.NewSet([]string{"seven", "eight", "nine", "ten", "twelve"}), + { + name: "6. Throw error when one of the keys is not a valid set", + presetValues: map[string]interface{}{ + "sintercard_key14": "Default value", + "sintercard_key15": set.NewSet([]string{"one", "two", "thirty-six", "twelve", "eleven"}), + "sintercard_key16": set.NewSet([]string{"seven", "eight", "nine", "ten", "twelve"}), + }, + keys: []string{"sintercard_key14", "sintercard_key15", "sintercard_key16"}, + want: 0, + wantErr: true, }, - keys: []string{"key14", "key15", "key16"}, - want: 0, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.presetValues != nil { - for k, v := range tt.presetValues { - err := presetValue(server, context.Background(), k, v) + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.presetValues != nil { + for k, v := range tt.presetValues { + err := presetValue(server, context.Background(), k, v) + if err != nil { + t.Error(err) + return + } + } + } + got, err := server.SInterCard(tt.keys, tt.limit) + if (err != nil) != tt.wantErr { + t.Errorf("SINTERCARD() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("SINTERCARD() got = %v, want %v", got, tt.want) + } + }) + } + }) + + t.Run("TestSugarDB_SINTERSTORE", func(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + presetValues map[string]interface{} + destination string + keys []string + want int + wantErr bool + }{ + { + name: "1. Get the intersection between 2 sets and store it at the destination", + presetValues: map[string]interface{}{ + "sinterstore_key1": set.NewSet([]string{"one", "two", "three", "four", "five"}), + "sinterstore_key2": set.NewSet([]string{"three", "four", "five", "six", "seven", "eight"}), + }, + destination: "sinterstore_destination1", + keys: []string{"sinterstore_key1", "sinterstore_key2"}, + want: 3, + wantErr: false, + }, + { + name: "2. Get the intersection between 3 sets and store it at the destination key", + presetValues: map[string]interface{}{ + "sinterstore_key3": set.NewSet([]string{"one", "two", "three", "four", "five", "six", "seven", "eight"}), + "sinterstore_key4": set.NewSet([]string{"one", "two", "thirty-six", "twelve", "eleven", "eight"}), + "sinterstore_key5": set.NewSet([]string{"one", "seven", "eight", "nine", "ten", "twelve"}), + }, + destination: "sinterstore_destination2", + keys: []string{"sinterstore_key3", "sinterstore_key4", "sinterstore_key5"}, + want: 2, + wantErr: false, + }, + { + name: "3. Throw error when any of the keys is not a set", + presetValues: map[string]interface{}{ + "sinterstore_key6": set.NewSet([]string{"one", "two", "three", "four", "five", "six", "seven", "eight"}), + "sinterstore_key7": "Default value", + "sinterstore_key8": set.NewSet([]string{"one"}), + }, + destination: "sinterstore_destination3", + keys: []string{"sinterstore_key6", "sinterstore_key7", "sinterstore_key8"}, + want: 0, + wantErr: true, + }, + { + name: "4. Throw error when base set is not a set", + presetValues: map[string]interface{}{ + "sinterstore_key9": "Default value", + "sinterstore_key10": set.NewSet([]string{"one", "two", "thirty-six", "twelve", "eleven"}), + "sinterstore_key11": set.NewSet([]string{"seven", "eight", "nine", "ten", "twelve"}), + }, + destination: "sinterstore_destination4", + keys: []string{"sinterstore_key9", "sinterstore_key10", "sinterstore_key11"}, + want: 0, + wantErr: true, + }, + { + name: "5. Return an empty intersection if one of the keys does not exist", + presetValues: map[string]interface{}{ + "sinterstore_key12": set.NewSet([]string{"one", "two", "thirty-six", "twelve", "eleven"}), + "sinterstore_key13": set.NewSet([]string{"seven", "eight", "nine", "ten", "twelve"}), + }, + destination: "sinterstore_destination5", + keys: []string{"sinterstore_non-existent", "sinterstore_key12", "sinterstore_key13"}, + want: 0, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.presetValues != nil { + for k, v := range tt.presetValues { + err := presetValue(server, context.Background(), k, v) + if err != nil { + t.Error(err) + return + } + } + } + got, err := server.SInterStore(tt.destination, tt.keys...) + if (err != nil) != tt.wantErr { + t.Errorf("SINTERSTORE() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("SINTERSTORE() got = %v, want %v", got, tt.want) + } + }) + } + }) + + t.Run("TestSugarDB_SISMEMBER", func(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + presetValue interface{} + key string + member string + want bool + wantErr bool + }{ + { + name: "1. Return true when element is a member of the set", + presetValue: set.NewSet([]string{"one", "two", "three", "four"}), + key: "sismember_key1", + member: "three", + want: true, + wantErr: false, + }, + { + name: "2. Return false when element is not a member of the set", + presetValue: set.NewSet([]string{"one", "two", "three", "four"}), + key: "sismember_key2", + member: "five", + want: false, + wantErr: false, + }, + { + name: "3. Throw error when trying to assert membership when the key does not hold a valid set", + presetValue: "Default value", + key: "sismember_key3", + member: "one", + want: false, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.presetValue != nil { + err := presetValue(server, context.Background(), tt.key, tt.presetValue) if err != nil { t.Error(err) return } } - } - got, err := server.SInterCard(tt.keys, tt.limit) - if (err != nil) != tt.wantErr { - t.Errorf("SINTERCARD() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("SINTERCARD() got = %v, want %v", got, tt.want) - } - }) - } -} + got, err := server.SisMember(tt.key, tt.member) + if (err != nil) != tt.wantErr { + t.Errorf("SISMEMBER() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("SISMEMBER() got = %v, want %v", got, tt.want) + } + }) + } + }) -func TestSugarDB_SINTERSTORE(t *testing.T) { - server := createSugarDB() + t.Run("TestSugarDB_SMEMBERS", func(t *testing.T) { + t.Parallel() - tests := []struct { - name string - presetValues map[string]interface{} - destination string - keys []string - want int - wantErr bool - }{ - { - name: "Get the intersection between 2 sets and store it at the destination", - presetValues: map[string]interface{}{ - "key1": set.NewSet([]string{"one", "two", "three", "four", "five"}), - "key2": set.NewSet([]string{"three", "four", "five", "six", "seven", "eight"}), + tests := []struct { + name string + presetValue interface{} + key string + want []string + wantErr bool + }{ + { + name: "1. Return all the members of the set", + key: "smembers_key1", + presetValue: set.NewSet([]string{"one", "two", "three", "four", "five"}), + want: []string{"one", "two", "three", "four", "five"}, + wantErr: false, }, - destination: "destination1", - keys: []string{"key1", "key2"}, - want: 3, - wantErr: false, - }, - { - name: "Get the intersection between 3 sets and store it at the destination key", - presetValues: map[string]interface{}{ - "key3": set.NewSet([]string{"one", "two", "three", "four", "five", "six", "seven", "eight"}), - "key4": set.NewSet([]string{"one", "two", "thirty-six", "twelve", "eleven", "eight"}), - "key5": set.NewSet([]string{"one", "seven", "eight", "nine", "ten", "twelve"}), + { + name: "2. If the key does not exist, return an empty array", + key: "smembers_key2", + presetValue: nil, + want: []string{}, + wantErr: false, }, - destination: "destination2", - keys: []string{"key3", "key4", "key5"}, - want: 2, - wantErr: false, - }, - { - name: "Throw error when any of the keys is not a set", - presetValues: map[string]interface{}{ - "key6": set.NewSet([]string{"one", "two", "three", "four", "five", "six", "seven", "eight"}), - "key7": "Default value", - "key8": set.NewSet([]string{"one"}), + { + name: "3. Throw error when the provided key is not a set", + key: "smembers_key3", + presetValue: "Default value", + want: nil, + wantErr: true, }, - destination: "destination3", - keys: []string{"key6", "key7", "key8"}, - want: 0, - wantErr: true, - }, - { - name: "Throw error when base set is not a set", - presetValues: map[string]interface{}{ - "key9": "Default value", - "key10": set.NewSet([]string{"one", "two", "thirty-six", "twelve", "eleven"}), - "key11": set.NewSet([]string{"seven", "eight", "nine", "ten", "twelve"}), - }, - destination: "destination4", - keys: []string{"key9", "key10", "key11"}, - want: 0, - wantErr: true, - }, - { - name: "Return an empty intersection if one of the keys does not exist", - destination: "destination5", - presetValues: map[string]interface{}{ - "key12": set.NewSet([]string{"one", "two", "thirty-six", "twelve", "eleven"}), - "key13": set.NewSet([]string{"seven", "eight", "nine", "ten", "twelve"}), - }, - keys: []string{"non-existent", "key12", "key13"}, - want: 0, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.presetValues != nil { - for k, v := range tt.presetValues { - err := presetValue(server, context.Background(), k, v) + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.presetValue != nil { + err := presetValue(server, context.Background(), tt.key, tt.presetValue) if err != nil { t.Error(err) return } } - } - got, err := server.SInterStore(tt.destination, tt.keys...) - if (err != nil) != tt.wantErr { - t.Errorf("SINTERSTORE() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("SINTERSTORE() got = %v, want %v", got, tt.want) - } - }) - } -} - -func TestSugarDB_SISMEMBER(t *testing.T) { - server := createSugarDB() - - tests := []struct { - name string - presetValue interface{} - key string - member string - want bool - wantErr bool - }{ - { - name: "Return true when element is a member of the set", - presetValue: set.NewSet([]string{"one", "two", "three", "four"}), - key: "key1", - member: "three", - want: true, - wantErr: false, - }, - { - name: "Return false when element is not a member of the set", - presetValue: set.NewSet([]string{"one", "two", "three", "four"}), - key: "key2", - member: "five", - want: false, - wantErr: false, - }, - { - name: "Throw error when trying to assert membership when the key does not hold a valid set", - presetValue: "Default value", - key: "key3", - member: "one", - want: false, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.presetValue != nil { - err := presetValue(server, context.Background(), tt.key, tt.presetValue) - if err != nil { - t.Error(err) + got, err := server.SMembers(tt.key) + if (err != nil) != tt.wantErr { + t.Errorf("SMEMBERS() error = %v, wantErr %v", err, tt.wantErr) return } - } - got, err := server.SisMember(tt.key, tt.member) - if (err != nil) != tt.wantErr { - t.Errorf("SISMEMBER() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("SISMEMBER() got = %v, want %v", got, tt.want) - } - }) - } -} - -func TestSugarDB_SMEMBERS(t *testing.T) { - server := createSugarDB() - - tests := []struct { - name string - presetValue interface{} - key string - want []string - wantErr bool - }{ - { - name: "Return all the members of the set", - key: "key1", - presetValue: set.NewSet([]string{"one", "two", "three", "four", "five"}), - want: []string{"one", "two", "three", "four", "five"}, - wantErr: false, - }, - { - name: "If the key does not exist, return an empty array", - key: "key2", - presetValue: nil, - want: []string{}, - wantErr: false, - }, - { - name: "Throw error when the provided key is not a set", - key: "key3", - presetValue: "Default value", - want: nil, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.presetValue != nil { - err := presetValue(server, context.Background(), tt.key, tt.presetValue) - if err != nil { - t.Error(err) - return - } - } - got, err := server.SMembers(tt.key) - if (err != nil) != tt.wantErr { - t.Errorf("SMEMBERS() error = %v, wantErr %v", err, tt.wantErr) - return - } - if len(got) != len(tt.want) { - t.Errorf("SMEMBERS() got = %v, want %v", got, tt.want) - } - for _, g := range got { - if !slices.Contains(tt.want, g) { + if len(got) != len(tt.want) { t.Errorf("SMEMBERS() got = %v, want %v", got, tt.want) } - } - }) - } -} - -func TestSugarDB_SMISMEMBER(t *testing.T) { - server := createSugarDB() - - tests := []struct { - name string - presetValue interface{} - key string - members []string - want []bool - wantErr bool - }{ - { - // Return set membership status for multiple elements (true for present and false for absent). - // The placement of the membership status flag should be consistent with the order the elements - // are in within the original command - name: "Return set membership status for multiple elements", - presetValue: set.NewSet([]string{"one", "two", "three", "four", "five", "six", "seven"}), - key: "key1", - members: []string{"three", "four", "five", "six", "eight", "nine", "seven"}, - want: []bool{true, true, true, true, false, false, true}, - wantErr: false, - }, - { - name: "If the set key does not exist, return an array of zeroes as long as the list of members", - presetValue: nil, - key: "key2", - members: []string{"one", "two", "three", "four"}, - want: []bool{false, false, false, false}, - wantErr: false, - }, - { - name: "Throw error when trying to assert membership when the key does not hold a valid set", - presetValue: "Default value", - key: "key3", - members: []string{"one"}, - want: nil, - wantErr: true, - }, - { - name: "Throw error for empty member slice", - presetValue: nil, - key: "key4", - members: []string{}, - want: nil, - wantErr: true, - }, - { - name: "Throw error for nil member slice", - presetValue: nil, - key: "key4", - members: nil, - want: nil, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.presetValue != nil { - err := presetValue(server, context.Background(), tt.key, tt.presetValue) - if err != nil { - t.Error(err) - return + for _, g := range got { + if !slices.Contains(tt.want, g) { + t.Errorf("SMEMBERS() got = %v, want %v", got, tt.want) + } } - } - got, err := server.SMisMember(tt.key, tt.members...) - if (err != nil) != tt.wantErr { - t.Errorf("SMISMEMBER() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("SMISMEMBER() got = %v, want %v", got, tt.want) - } - }) - } -} + }) + } + }) -func TestSugarDB_SMOVE(t *testing.T) { - server := createSugarDB() + t.Run("TestSugarDB_SMISMEMBER", func(t *testing.T) { + t.Parallel() - tests := []struct { - name string - presetValues map[string]interface{} - source string - destination string - member string - want bool - wantErr bool - }{ - { - name: "Return true after a successful move of a member from source set to destination set", - presetValues: map[string]interface{}{ - "source1": set.NewSet([]string{"one", "two", "three", "four"}), - "destination1": set.NewSet([]string{"five", "six", "seven", "eight"}), + tests := []struct { + name string + presetValue interface{} + key string + members []string + want []bool + wantErr bool + }{ + { + // Return set membership status for multiple elements (true for present and false for absent). + // The placement of the membership status flag should be consistent with the order the elements + // are in within the original command + name: "1. Return set membership status for multiple elements", + presetValue: set.NewSet([]string{"one", "two", "three", "four", "five", "six", "seven"}), + key: "smismember_key1", + members: []string{"three", "four", "five", "six", "eight", "nine", "seven"}, + want: []bool{true, true, true, true, false, false, true}, + wantErr: false, }, - source: "source1", - destination: "destination1", - member: "four", - want: true, - wantErr: false, - }, - { - name: "Return false when trying to move a member from source set to destination set when it doesn't exist in source", - presetValues: map[string]interface{}{ - "source2": set.NewSet([]string{"one", "two", "three", "four", "five"}), - "destination2": set.NewSet([]string{"five", "six", "seven", "eight"}), + { + name: "2. If the set key does not exist, return an array of zeroes as long as the list of members", + presetValue: nil, + key: "smismember_key2", + members: []string{"one", "two", "three", "four"}, + want: []bool{false, false, false, false}, + wantErr: false, }, - source: "source2", - destination: "destination2", - member: "six", - want: false, - wantErr: false, - }, - { - name: "Return error when the source key is not a set", - presetValues: map[string]interface{}{ - "source3": "Default value", - "destination3": set.NewSet([]string{"five", "six", "seven", "eight"}), + { + name: "3. Throw error when trying to assert membership when the key does not hold a valid set", + presetValue: "Default value", + key: "smismember_key3", + members: []string{"one"}, + want: nil, + wantErr: true, }, - source: "source3", - destination: "destination3", - member: "five", - want: false, - wantErr: true, - }, - { - name: "Return error when the destination key is not a set", - presetValues: map[string]interface{}{ - "source4": set.NewSet([]string{"one", "two", "three", "four", "five"}), - "destination4": "Default value", + { + name: "4. Throw error for empty member slice", + presetValue: nil, + key: "smismember_key4", + members: []string{}, + want: nil, + wantErr: true, }, - source: "source4", - destination: "destination4", - member: "five", - want: false, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.presetValues != nil { - for k, v := range tt.presetValues { - err := presetValue(server, context.Background(), k, v) + { + name: "5. Throw error for nil member slice", + presetValue: nil, + key: "smismember_key4", + members: nil, + want: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.presetValue != nil { + err := presetValue(server, context.Background(), tt.key, tt.presetValue) if err != nil { t.Error(err) return } } - } - got, err := server.SMove(tt.source, tt.destination, tt.member) - if (err != nil) != tt.wantErr { - t.Errorf("SMOVE() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("SMOVE() got = %v, want %v", got, tt.want) - } - }) - } -} - -func TestSugarDB_SPOP(t *testing.T) { - server := createSugarDB() - - tests := []struct { - name string - presetValue interface{} - key string - count uint - want []string - wantErr bool - }{ - { - name: "Return multiple popped elements and modify the set", - key: "key1", - presetValue: set.NewSet([]string{"one", "two", "three", "four", "five", "six", "seven", "eight"}), - count: 3, - want: []string{"one", "two", "three", "four", "five", "six", "seven", "eight"}, - wantErr: false, - }, - { - name: "Return error when the source key is not a set", - key: "key2", - presetValue: "Default value", - count: 1, - want: nil, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.presetValue != nil { - err := presetValue(server, context.Background(), tt.key, tt.presetValue) - if err != nil { - t.Error(err) + got, err := server.SMisMember(tt.key, tt.members...) + if (err != nil) != tt.wantErr { + t.Errorf("SMISMEMBER() error = %v, wantErr %v", err, tt.wantErr) return } - } - got, err := server.SPop(tt.key, tt.count) - if (err != nil) != tt.wantErr { - t.Errorf("SPOP() error = %v, wantErr %v", err, tt.wantErr) - return - } - for _, g := range got { - if !slices.Contains(tt.want, g) { - t.Errorf("SPOP() got = %v, want %v", got, tt.want) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("SMISMEMBER() got = %v, want %v", got, tt.want) } - } - }) - } -} + }) + } + }) -func TestSugarDB_SRANDMEMBER(t *testing.T) { - server := createSugarDB() + t.Run("TestSugarDB_SMOVE", func(t *testing.T) { + t.Parallel() - tests := []struct { - name string - presetValue interface{} - key string - count int - wantCount int - wantErr bool - }{ - { - // Return multiple random elements without removing them - // Count is positive, do not allow repeated elements - name: "Return multiple random elements without removing them", - key: "key1", - presetValue: set.NewSet([]string{"one", "two", "three", "four", "five", "six", "seven", "eight"}), - count: 3, - wantCount: 3, - wantErr: false, - }, - { - // Return multiple random elements without removing them - // Count is negative, so allow repeated numbers - name: "Return multiple random elements without removing them", - key: "key2", - presetValue: set.NewSet([]string{"one", "two", "three", "four", "five", "six", "seven", "eight"}), - count: -5, - wantCount: 5, - wantErr: false, - }, - { - name: "Return error when the source key is not a set", - key: "key3", - presetValue: "Default value", - count: 1, - wantCount: 0, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.presetValue != nil { - err := presetValue(server, context.Background(), tt.key, tt.presetValue) - if err != nil { - t.Error(err) + tests := []struct { + name string + presetValues map[string]interface{} + source string + destination string + member string + want bool + wantErr bool + }{ + { + name: "1. Return true after a successful move of a member from source set to destination set", + presetValues: map[string]interface{}{ + "smove_source1": set.NewSet([]string{"one", "two", "three", "four"}), + "smove_destination1": set.NewSet([]string{"five", "six", "seven", "eight"}), + }, + source: "smove_source1", + destination: "smove_destination1", + member: "four", + want: true, + wantErr: false, + }, + { + name: "2. Return false when trying to move a member from source set to destination set when it doesn't exist in source", + presetValues: map[string]interface{}{ + "smove_source2": set.NewSet([]string{"one", "two", "three", "four", "five"}), + "smove_destination2": set.NewSet([]string{"five", "six", "seven", "eight"}), + }, + source: "smove_source2", + destination: "smove_destination2", + member: "six", + want: false, + wantErr: false, + }, + { + name: "3. Return error when the source key is not a set", + presetValues: map[string]interface{}{ + "smove_source3": "Default value", + "smove_destination3": set.NewSet([]string{"five", "six", "seven", "eight"}), + }, + source: "smove_source3", + destination: "smove_destination3", + member: "five", + want: false, + wantErr: true, + }, + { + name: "4. Return error when the destination key is not a set", + presetValues: map[string]interface{}{ + "smove_source4": set.NewSet([]string{"one", "two", "three", "four", "five"}), + "smove_destination4": "Default value", + }, + source: "smove_source4", + destination: "smove_destination4", + member: "five", + want: false, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.presetValues != nil { + for k, v := range tt.presetValues { + err := presetValue(server, context.Background(), k, v) + if err != nil { + t.Error(err) + return + } + } + } + got, err := server.SMove(tt.source, tt.destination, tt.member) + if (err != nil) != tt.wantErr { + t.Errorf("SMOVE() error = %v, wantErr %v", err, tt.wantErr) return } - } - got, err := server.SRandMember(tt.key, tt.count) - if (err != nil) != tt.wantErr { - t.Errorf("SRANDMEMBER() error = %v, wantErr %v", err, tt.wantErr) - return - } - if len(got) != tt.wantCount { - t.Errorf("SRANDMEMBER() got = %v, want %v", len(got), tt.wantCount) - } - if tt.count > 0 { - s := set.NewSet(got) - if s.Cardinality() != len(got) { - t.Errorf("SRANDMEMBER - UNIQUE () got = %v, want %v", len(got), s.Cardinality()) + if got != tt.want { + t.Errorf("SMOVE() got = %v, want %v", got, tt.want) } - } - }) - } -} + }) + } + }) -func TestSugarDB_SREM(t *testing.T) { - server := createSugarDB() + t.Run("TestSugarDB_SPOP", func(t *testing.T) { + t.Parallel() - tests := []struct { - name string - presetValue interface{} - key string - members []string - want int - wantErr bool - }{ - { - name: "Remove multiple elements and return the number of elements removed", - key: "key1", - presetValue: set.NewSet([]string{"one", "two", "three", "four", "five", "six", "seven", "eight"}), - members: []string{"one", "two", "three", "nine"}, - want: 3, - wantErr: false, - }, - { - name: "If key does not exist, return 0", - key: "key2", - presetValue: nil, - members: []string{"one", "two", "three", "nine"}, - want: 0, - wantErr: false, - }, - { - name: "Return error when the source key is not a set", - key: "key3", - presetValue: "Default value", - members: []string{"one"}, - want: 0, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.presetValue != nil { - err := presetValue(server, context.Background(), tt.key, tt.presetValue) - if err != nil { - t.Error(err) - return - } - } - got, err := server.SRem(tt.key, tt.members...) - if (err != nil) != tt.wantErr { - t.Errorf("SREM() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("SREM() got = %v, want %v", got, tt.want) - } - }) - } -} - -func TestSugarDB_SUNION(t *testing.T) { - server := createSugarDB() - - tests := []struct { - name string - presetValues map[string]interface{} - keys []string - want []string - wantErr bool - }{ - { - name: "Get the union between 2 sets", - presetValues: map[string]interface{}{ - "key1": set.NewSet([]string{"one", "two", "three", "four", "five"}), - "key2": set.NewSet([]string{"three", "four", "five", "six", "seven", "eight"}), + tests := []struct { + name string + presetValue interface{} + key string + count uint + want []string + wantErr bool + }{ + { + name: "1. Return multiple popped elements and modify the set", + key: "spop_key1", + presetValue: set.NewSet([]string{"one", "two", "three", "four", "five", "six", "seven", "eight"}), + count: 3, + want: []string{"one", "two", "three", "four", "five", "six", "seven", "eight"}, + wantErr: false, }, - keys: []string{"key1", "key2"}, - want: []string{"one", "two", "three", "four", "five", "six", "seven", "eight"}, - wantErr: false, - }, - { - name: "Get the union between 3 sets", - presetValues: map[string]interface{}{ - "key3": set.NewSet([]string{"one", "two", "three", "four", "five", "six", "seven", "eight"}), - "key4": set.NewSet([]string{"one", "two", "thirty-six", "twelve", "eleven", "eight"}), - "key5": set.NewSet([]string{"one", "eight", "nine", "ten", "twelve"}), + { + name: "2. Return error when the source key is not a set", + key: "spop_key2", + presetValue: "Default value", + count: 1, + want: nil, + wantErr: true, }, - keys: []string{"key3", "key4", "key5"}, - want: []string{ - "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", - "ten", "eleven", "twelve", "thirty-six", - }, - wantErr: false, - }, - { - name: "Throw an error if any of the provided keys are not sets", - presetValues: map[string]interface{}{ - "key6": set.NewSet([]string{"one", "two", "three", "four", "five", "six", "seven", "eight"}), - "key7": "Default value", - "key8": set.NewSet([]string{"one"}), - }, - keys: []string{"key6", "key7", "key8"}, - want: nil, - wantErr: true, - }, - { - name: "Throw error any of the keys does not hold a set", - presetValues: map[string]interface{}{ - "key9": "Default value", - "key10": set.NewSet([]string{"one", "two", "thirty-six", "twelve", "eleven"}), - "key11": set.NewSet([]string{"seven", "eight", "nine", "ten", "twelve"}), - }, - keys: []string{"key9", "key10", "key11"}, - want: nil, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.presetValues != nil { - for k, v := range tt.presetValues { - err := presetValue(server, context.Background(), k, v) + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.presetValue != nil { + err := presetValue(server, context.Background(), tt.key, tt.presetValue) if err != nil { t.Error(err) return } } - } - got, err := server.SUnion(tt.keys...) - if (err != nil) != tt.wantErr { - t.Errorf("SUNION() error = %v, wantErr %v", err, tt.wantErr) - return - } - if len(got) != len(tt.want) { - t.Errorf("SUNION() got = %v, want %v", got, tt.want) - } - for _, g := range got { - if !slices.Contains(tt.want, g) { + got, err := server.SPop(tt.key, tt.count) + if (err != nil) != tt.wantErr { + t.Errorf("SPOP() error = %v, wantErr %v", err, tt.wantErr) + return + } + for _, g := range got { + if !slices.Contains(tt.want, g) { + t.Errorf("SPOP() got = %v, want %v", got, tt.want) + } + } + }) + } + }) + + t.Run("TestSugarDB_SRANDMEMBER", func(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + presetValue interface{} + key string + count int + wantCount int + wantErr bool + }{ + { + // Return multiple random elements without removing them + // Count is positive, do not allow repeated elements + name: "1. Return multiple random elements without removing them", + key: "srandmember_key1", + presetValue: set.NewSet([]string{"one", "two", "three", "four", "five", "six", "seven", "eight"}), + count: 3, + wantCount: 3, + wantErr: false, + }, + { + // Return multiple random elements without removing them + // Count is negative, so allow repeated numbers + name: "2. Return multiple random elements without removing them", + key: "srandmember_key2", + presetValue: set.NewSet([]string{"one", "two", "three", "four", "five", "six", "seven", "eight"}), + count: -5, + wantCount: 5, + wantErr: false, + }, + { + name: "3. Return error when the source key is not a set", + key: "srandmember_key3", + presetValue: "Default value", + count: 1, + wantCount: 0, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.presetValue != nil { + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } + } + got, err := server.SRandMember(tt.key, tt.count) + if (err != nil) != tt.wantErr { + t.Errorf("SRANDMEMBER() error = %v, wantErr %v", err, tt.wantErr) + return + } + if len(got) != tt.wantCount { + t.Errorf("SRANDMEMBER() got = %v, want %v", len(got), tt.wantCount) + } + if tt.count > 0 { + s := set.NewSet(got) + if s.Cardinality() != len(got) { + t.Errorf("SRANDMEMBER - UNIQUE () got = %v, want %v", len(got), s.Cardinality()) + } + } + }) + } + }) + + t.Run("TestSugarDB_SREM", func(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + presetValue interface{} + key string + members []string + want int + wantErr bool + }{ + { + name: "1. Remove multiple elements and return the number of elements removed", + key: "srem_key1", + presetValue: set.NewSet([]string{"one", "two", "three", "four", "five", "six", "seven", "eight"}), + members: []string{"one", "two", "three", "nine"}, + want: 3, + wantErr: false, + }, + { + name: "2. If key does not exist, return 0", + key: "srem_key2", + presetValue: nil, + members: []string{"one", "two", "three", "nine"}, + want: 0, + wantErr: false, + }, + { + name: "3. Return error when the source key is not a set", + key: "srem_key3", + presetValue: "Default value", + members: []string{"one"}, + want: 0, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.presetValue != nil { + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } + } + got, err := server.SRem(tt.key, tt.members...) + if (err != nil) != tt.wantErr { + t.Errorf("SREM() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("SREM() got = %v, want %v", got, tt.want) + } + }) + } + }) + + t.Run("TestSugarDB_SUNION", func(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + presetValues map[string]interface{} + keys []string + want []string + wantErr bool + }{ + { + name: "1. Get the union between 2 sets", + presetValues: map[string]interface{}{ + "sunion_key1": set.NewSet([]string{"one", "two", "three", "four", "five"}), + "sunion_key2": set.NewSet([]string{"three", "four", "five", "six", "seven", "eight"}), + }, + keys: []string{"sunion_key1", "sunion_key2"}, + want: []string{"one", "two", "three", "four", "five", "six", "seven", "eight"}, + wantErr: false, + }, + { + name: "2. Get the union between 3 sets", + presetValues: map[string]interface{}{ + "sunion_key3": set.NewSet([]string{"one", "two", "three", "four", "five", "six", "seven", "eight"}), + "sunion_key4": set.NewSet([]string{"one", "two", "thirty-six", "twelve", "eleven", "eight"}), + "sunion_key5": set.NewSet([]string{"one", "eight", "nine", "ten", "twelve"}), + }, + keys: []string{"sunion_key3", "sunion_key4", "sunion_key5"}, + want: []string{ + "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", + "ten", "eleven", "twelve", "thirty-six", + }, + wantErr: false, + }, + { + name: "3. Throw an error if any of the provided keys are not sets", + presetValues: map[string]interface{}{ + "sunion_key6": set.NewSet([]string{"one", "two", "three", "four", "five", "six", "seven", "eight"}), + "sunion_key7": "Default value", + "sunion_key8": set.NewSet([]string{"one"}), + }, + keys: []string{"sunion_key6", "sunion_key7", "sunion_key8"}, + want: nil, + wantErr: true, + }, + { + name: "4. Throw error any of the keys does not hold a set", + presetValues: map[string]interface{}{ + "sunion_key9": "Default value", + "sunion_key10": set.NewSet([]string{"one", "two", "thirty-six", "twelve", "eleven"}), + "sunion_key11": set.NewSet([]string{"seven", "eight", "nine", "ten", "twelve"}), + }, + keys: []string{"sunion_key9", "sunion_key10", "sunion_key11"}, + want: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.presetValues != nil { + for k, v := range tt.presetValues { + err := presetValue(server, context.Background(), k, v) + if err != nil { + t.Error(err) + return + } + } + } + got, err := server.SUnion(tt.keys...) + if (err != nil) != tt.wantErr { + t.Errorf("SUNION() error = %v, wantErr %v", err, tt.wantErr) + return + } + if len(got) != len(tt.want) { t.Errorf("SUNION() got = %v, want %v", got, tt.want) } - } - }) - } -} - -func TestSugarDB_SUNIONSTORE(t *testing.T) { - server := createSugarDB() - - tests := []struct { - name string - presetValues map[string]interface{} - destination string - keys []string - want int - wantErr bool - }{ - { - name: "Get the intersection between 2 sets and store it at the destination", - presetValues: map[string]interface{}{ - "key1": set.NewSet([]string{"one", "two", "three", "four", "five"}), - "key2": set.NewSet([]string{"three", "four", "five", "six", "seven", "eight"}), - }, - destination: "destination1", - keys: []string{"key1", "key2"}, - want: 8, - wantErr: false, - }, - { - name: "Get the intersection between 3 sets and store it at the destination key", - presetValues: map[string]interface{}{ - "key3": set.NewSet([]string{"one", "two", "three", "four", "five", "six", "seven", "eight"}), - "key4": set.NewSet([]string{"one", "two", "thirty-six", "twelve", "eleven", "eight"}), - "key5": set.NewSet([]string{"one", "seven", "eight", "nine", "ten", "twelve"}), - }, - destination: "destination2", - keys: []string{"key3", "key4", "key5"}, - want: 13, - wantErr: false, - }, - { - name: "Throw error when any of the keys is not a set", - presetValues: map[string]interface{}{ - "key6": set.NewSet([]string{"one", "two", "three", "four", "five", "six", "seven", "eight"}), - "key7": "Default value", - "key8": set.NewSet([]string{"one"}), - }, - destination: "destination3", - keys: []string{"key6", "key7", "key8"}, - want: 0, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.presetValues != nil { - for k, v := range tt.presetValues { - err := presetValue(server, context.Background(), k, v) - if err != nil { - t.Error(err) - return + for _, g := range got { + if !slices.Contains(tt.want, g) { + t.Errorf("SUNION() got = %v, want %v", got, tt.want) } } - } - got, err := server.SUnionStore(tt.destination, tt.keys...) - if (err != nil) != tt.wantErr { - t.Errorf("SUNIONSTORE() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("SUNIONSTORE() got = %v, want %v", got, tt.want) - } - }) - } + }) + } + }) + + t.Run("TestSugarDB_SUNIONSTORE", func(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + presetValues map[string]interface{} + destination string + keys []string + want int + wantErr bool + }{ + { + name: "1. Get the intersection between 2 sets and store it at the destination", + presetValues: map[string]interface{}{ + "sunionstore_key1": set.NewSet([]string{"one", "two", "three", "four", "five"}), + "sunionstore_key2": set.NewSet([]string{"three", "four", "five", "six", "seven", "eight"}), + }, + destination: "sunionstore_destination1", + keys: []string{"sunionstore_key1", "sunionstore_key2"}, + want: 8, + wantErr: false, + }, + { + name: "2. Get the intersection between 3 sets and store it at the destination key", + presetValues: map[string]interface{}{ + "sunionstore_key3": set.NewSet([]string{"one", "two", "three", "four", "five", "six", "seven", "eight"}), + "sunionstore_key4": set.NewSet([]string{"one", "two", "thirty-six", "twelve", "eleven", "eight"}), + "sunionstore_key5": set.NewSet([]string{"one", "seven", "eight", "nine", "ten", "twelve"}), + }, + destination: "sunionstore_destination2", + keys: []string{"sunionstore_key3", "sunionstore_key4", "sunionstore_key5"}, + want: 13, + wantErr: false, + }, + { + name: "3. Throw error when any of the keys is not a set", + presetValues: map[string]interface{}{ + "sunionstore_key6": set.NewSet([]string{"one", "two", "three", "four", "five", "six", "seven", "eight"}), + "sunionstore_key7": "Default value", + "sunionstore_key8": set.NewSet([]string{"one"}), + }, + destination: "sunionstore_destination3", + keys: []string{"sunionstore_key6", "sunionstore_key7", "sunionstore_key8"}, + want: 0, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.presetValues != nil { + for k, v := range tt.presetValues { + err := presetValue(server, context.Background(), k, v) + if err != nil { + t.Error(err) + return + } + } + } + got, err := server.SUnionStore(tt.destination, tt.keys...) + if (err != nil) != tt.wantErr { + t.Errorf("SUNIONSTORE() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("SUNIONSTORE() got = %v, want %v", got, tt.want) + } + }) + } + }) } diff --git a/sugardb/api_sorted_set_test.go b/sugardb/api_sorted_set_test.go index 4eac747..20a36d9 100644 --- a/sugardb/api_sorted_set_test.go +++ b/sugardb/api_sorted_set_test.go @@ -24,3567 +24,3570 @@ import ( "testing" ) -func TestSugarDB_ZADD(t *testing.T) { +func TestSugarDB_SortedSet(t *testing.T) { server := createSugarDB() - tests := []struct { - name string - preset bool - presetValue *ss.SortedSet - key string - entries map[string]float64 - options ZAddOptions - want int - wantErr bool - }{ - { - name: "Create new sorted set and return the cardinality of the new sorted set", - preset: false, - presetValue: nil, - key: "key1", - entries: map[string]float64{ - "member1": 5.5, - "member2": 67.77, - "member3": 10, - "member4": math.Inf(-1), - "member5": math.Inf(1), - }, - options: ZAddOptions{}, - want: 5, - wantErr: false, - }, - { - name: "Only add the elements that do not currently exist in the sorted set when NX flag is provided", - preset: true, - presetValue: ss.NewSortedSet([]ss.MemberParam{ - {Value: "member1", Score: ss.Score(5.5)}, - {Value: "member2", Score: ss.Score(67.77)}, - {Value: "member3", Score: ss.Score(10)}, - }), - key: "key2", - entries: map[string]float64{ - "member1": 5.5, - "member4": 67.77, - "member5": 10, - }, - options: ZAddOptions{NX: true}, - want: 2, - wantErr: false, - }, - { - name: "Do not add any elements when providing existing members with NX flag", - preset: true, - presetValue: ss.NewSortedSet([]ss.MemberParam{ - {Value: "member1", Score: ss.Score(5.5)}, - {Value: "member2", Score: ss.Score(67.77)}, - {Value: "member3", Score: ss.Score(10)}, - }), - key: "key3", - entries: map[string]float64{ - "member1": 5.5, - "member2": 67.77, - "member3": 10, - }, - options: ZAddOptions{NX: true}, - want: 0, - wantErr: false, - }, - { - name: "Successfully add elements to an existing set when XX flag is provided with existing elements", - preset: true, - presetValue: ss.NewSortedSet([]ss.MemberParam{ - {Value: "member1", Score: ss.Score(5.5)}, - {Value: "member2", Score: ss.Score(67.77)}, - {Value: "member3", Score: ss.Score(10)}, - }), - key: "key4", - entries: map[string]float64{ - "member1": 55, - "member2": 1005, - "member3": 15, - "member4": 99.75, - }, - options: ZAddOptions{XX: true, CH: true}, - want: 3, - wantErr: false, - }, - { - name: "Fail to add element when providing XX flag with elements that do not exist in the sorted set", - preset: true, - presetValue: ss.NewSortedSet([]ss.MemberParam{ - {Value: "member1", Score: ss.Score(5.5)}, - {Value: "member2", Score: ss.Score(67.77)}, - {Value: "member3", Score: ss.Score(10)}, - }), - key: "key5", - entries: map[string]float64{ - "member4": 5.5, - "member5": 100.5, - "member6": 15, - }, - options: ZAddOptions{XX: true}, - want: 0, - wantErr: false, - }, - { - // Only update the elements where provided score is greater than current score if GT flag - // Return only the new elements added by default - name: "Only update the elements where provided score is greater than current score if GT flag", - preset: true, - presetValue: ss.NewSortedSet([]ss.MemberParam{ - {Value: "member1", Score: ss.Score(5.5)}, - {Value: "member2", Score: ss.Score(67.77)}, - {Value: "member3", Score: ss.Score(10)}, - }), - key: "key6", - entries: map[string]float64{ - "member1": 7.5, - "member4": 100.5, - "member5": 15, - }, - options: ZAddOptions{XX: true, CH: true, GT: true}, - want: 1, - wantErr: false, - }, - { - // Only update the elements where provided score is less than current score if LT flag is provided - // Return only the new elements added by default. - name: "Only update the elements where provided score is less than current score if LT flag is provided", - preset: true, - presetValue: ss.NewSortedSet([]ss.MemberParam{ - {Value: "member1", Score: ss.Score(5.5)}, - {Value: "member2", Score: ss.Score(67.77)}, - {Value: "member3", Score: ss.Score(10)}, - }), - key: "key7", - entries: map[string]float64{ - "member1": 3.5, - "member4": 100.5, - "member5": 15, - }, - options: ZAddOptions{XX: true, LT: true}, - want: 0, - wantErr: false, - }, - { - name: "Return all the elements that were updated AND added when CH flag is provided", - preset: true, - presetValue: ss.NewSortedSet([]ss.MemberParam{ - {Value: "member1", Score: ss.Score(5.5)}, - {Value: "member2", Score: ss.Score(67.77)}, - {Value: "member3", Score: ss.Score(10)}, - }), - key: "key8", - entries: map[string]float64{ - "member1": 3.5, - "member4": 100.5, - "member5": 15, - }, - options: ZAddOptions{XX: true, LT: true, CH: true}, - want: 1, - wantErr: false, - }, - { - name: "Increment the member by score", - preset: true, - presetValue: ss.NewSortedSet([]ss.MemberParam{ - {Value: "member1", Score: ss.Score(5.5)}, - {Value: "member2", Score: ss.Score(67.77)}, - {Value: "member3", Score: ss.Score(10)}, - }), - key: "key9", - entries: map[string]float64{ - "member3": 5.5, - }, - options: ZAddOptions{INCR: true}, - want: 0, - wantErr: false, - }, - { - name: "Fail when GT/LT flag is provided alongside NX flag", - preset: false, - presetValue: nil, - key: "key10", - entries: map[string]float64{ - "member1": 3.5, - "member5": 15, - }, - options: ZAddOptions{NX: true, LT: true, CH: true}, - want: 0, - wantErr: true, - }, - { - name: "Throw error when INCR flag is passed with more than one score/member pair", - preset: false, - presetValue: nil, - key: "key11", - entries: map[string]float64{ - "member1": 10.5, - "member2": 12.5, - }, - options: ZAddOptions{INCR: true}, - want: 0, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.preset { - err := presetValue(server, context.Background(), tt.key, tt.presetValue) - if err != nil { - t.Error(err) - return - } - } - got, err := server.ZAdd(tt.key, tt.entries, tt.options) - if (err != nil) != tt.wantErr { - t.Errorf("ZADD() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("ZADD() got = %v, want %v", got, tt.want) - } - }) - } -} + t.Cleanup(func() { + server.ShutDown() + }) -func TestSugarDB_ZCARD(t *testing.T) { - server := createSugarDB() + t.Run("TestSugarDB_ZADD", func(t *testing.T) { + t.Parallel() - tests := []struct { - name string - preset bool - presetValue interface{} - key string - want int - wantErr bool - }{ - { - name: "Get cardinality of valid sorted set", - preset: true, - presetValue: ss.NewSortedSet([]ss.MemberParam{ - {Value: "member1", Score: ss.Score(5.5)}, - {Value: "member2", Score: ss.Score(67.77)}, - {Value: "member3", Score: ss.Score(10)}, - }), - key: "key1", - want: 3, - wantErr: false, - }, - { - name: "Return 0 when trying to get cardinality from non-existent key", - preset: false, - presetValue: nil, - key: "key2", - want: 0, - wantErr: false, - }, - { - name: "Return error when not a sorted set", - preset: true, - presetValue: "Default value", - key: "key3", - want: 0, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.preset { - err := presetValue(server, context.Background(), tt.key, tt.presetValue) - if err != nil { - t.Error(err) - return - } - } - got, err := server.ZCard(tt.key) - if (err != nil) != tt.wantErr { - t.Errorf("ZCARD() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("ZCARD() got = %v, want %v", got, tt.want) - } - }) - } -} - -func TestSugarDB_ZCOUNT(t *testing.T) { - server := createSugarDB() - - tests := []struct { - name string - preset bool - presetValue interface{} - key string - min float64 - max float64 - want int - wantErr bool - }{ - { - name: "Get entire count using infinity boundaries", - preset: true, - presetValue: ss.NewSortedSet([]ss.MemberParam{ - {Value: "member1", Score: ss.Score(5.5)}, - {Value: "member2", Score: ss.Score(67.77)}, - {Value: "member3", Score: ss.Score(10)}, - {Value: "member4", Score: ss.Score(1083.13)}, - {Value: "member5", Score: ss.Score(11)}, - {Value: "member6", Score: ss.Score(math.Inf(-1))}, - {Value: "member7", Score: ss.Score(math.Inf(1))}, - }), - key: "key1", - min: math.Inf(-1), - max: math.Inf(1), - want: 7, - wantErr: false, - }, - { - name: "Get count of sub-set from -inf to limit", - preset: true, - presetValue: ss.NewSortedSet([]ss.MemberParam{ - {Value: "member1", Score: ss.Score(5.5)}, - {Value: "member2", Score: ss.Score(67.77)}, - {Value: "member3", Score: ss.Score(10)}, - {Value: "member4", Score: ss.Score(1083.13)}, - {Value: "member5", Score: ss.Score(11)}, - {Value: "member6", Score: ss.Score(math.Inf(-1))}, - {Value: "member7", Score: ss.Score(math.Inf(1))}, - }), - key: "key2", - min: math.Inf(-1), - max: 90, - want: 5, - wantErr: false, - }, - { - name: "Get count of sub-set from bottom boundary to +inf limit", - preset: true, - presetValue: ss.NewSortedSet([]ss.MemberParam{ - {Value: "member1", Score: ss.Score(5.5)}, - {Value: "member2", Score: ss.Score(67.77)}, - {Value: "member3", Score: ss.Score(10)}, - {Value: "member4", Score: ss.Score(1083.13)}, - {Value: "member5", Score: ss.Score(11)}, - {Value: "member6", Score: ss.Score(math.Inf(-1))}, - {Value: "member7", Score: ss.Score(math.Inf(1))}, - }), - key: "key3", - min: 1000, - max: math.Inf(1), - want: 2, - wantErr: false, - }, - { - name: "Throw error when value at the key is not a sorted set", - preset: true, - presetValue: "Default value", - key: "key4", - min: 1, - max: 10, - want: 0, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.preset { - err := presetValue(server, context.Background(), tt.key, tt.presetValue) - if err != nil { - t.Error(err) - return - } - } - got, err := server.ZCount(tt.key, tt.min, tt.max) - if (err != nil) != tt.wantErr { - t.Errorf("ZCOUNT() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("ZCOUNT() got = %v, want %v", got, tt.want) - } - }) - } -} - -func TestSugarDB_ZDIFF(t *testing.T) { - server := createSugarDB() - - tests := []struct { - name string - preset bool - presetValues map[string]interface{} - withscores bool - keys []string - want map[string]float64 - wantErr bool - }{ - { - name: "Get the difference between 2 sorted sets without scores", - preset: true, - presetValues: map[string]interface{}{ - "key1": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, - {Value: "two", Score: 2}, - {Value: "three", Score: 3}, - {Value: "four", Score: 4}, - }), - "key2": ss.NewSortedSet([]ss.MemberParam{ - {Value: "three", Score: 3}, - {Value: "four", Score: 4}, - {Value: "five", Score: 5}, - {Value: "six", Score: 6}, - {Value: "seven", Score: 7}, - {Value: "eight", Score: 8}, - }), + tests := []struct { + name string + preset bool + presetValue *ss.SortedSet + key string + entries map[string]float64 + options ZAddOptions + want int + wantErr bool + }{ + { + name: "1. Create new sorted set and return the cardinality of the new sorted set", + preset: false, + presetValue: nil, + key: "zadd_key1", + entries: map[string]float64{ + "member1": 5.5, + "member2": 67.77, + "member3": 10, + "member4": math.Inf(-1), + "member5": math.Inf(1), + }, + options: ZAddOptions{}, + want: 5, + wantErr: false, }, - withscores: false, - keys: []string{"key1", "key2"}, - want: map[string]float64{"one": 0, "two": 0}, - wantErr: false, - }, - { - name: "Get the difference between 2 sorted sets with scores", - preset: true, - presetValues: map[string]interface{}{ - "key3": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, - {Value: "two", Score: 2}, - {Value: "three", Score: 3}, - {Value: "four", Score: 4}, - }), - "key4": ss.NewSortedSet([]ss.MemberParam{ - {Value: "three", Score: 3}, - {Value: "four", Score: 4}, - {Value: "five", Score: 5}, - {Value: "six", Score: 6}, - {Value: "seven", Score: 7}, - {Value: "eight", Score: 8}, + { + name: "2. Only add the elements that do not currently exist in the sorted set when NX flag is provided", + preset: true, + presetValue: ss.NewSortedSet([]ss.MemberParam{ + {Value: "member1", Score: ss.Score(5.5)}, + {Value: "member2", Score: ss.Score(67.77)}, + {Value: "member3", Score: ss.Score(10)}, }), + key: "zadd_key2", + entries: map[string]float64{ + "member1": 5.5, + "member4": 67.77, + "member5": 10, + }, + options: ZAddOptions{NX: true}, + want: 2, + wantErr: false, }, - withscores: true, - keys: []string{"key3", "key4"}, - want: map[string]float64{"one": 1, "two": 2}, - wantErr: false, - }, - { - name: "Get the difference between 3 sets with scores", - preset: true, - presetValues: map[string]interface{}{ - "key5": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, - }), - "key6": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, - {Value: "eleven", Score: 11}, - }), - "key7": ss.NewSortedSet([]ss.MemberParam{ - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, - {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, - {Value: "twelve", Score: 12}, + { + name: "3. Do not add any elements when providing existing members with NX flag", + preset: true, + presetValue: ss.NewSortedSet([]ss.MemberParam{ + {Value: "member1", Score: ss.Score(5.5)}, + {Value: "member2", Score: ss.Score(67.77)}, + {Value: "member3", Score: ss.Score(10)}, }), + key: "zadd_key3", + entries: map[string]float64{ + "member1": 5.5, + "member2": 67.77, + "member3": 10, + }, + options: ZAddOptions{NX: true}, + want: 0, + wantErr: false, }, - withscores: true, - keys: []string{"key5", "key6", "key7"}, - want: map[string]float64{"three": 3, "four": 4, "five": 5, "six": 6}, - wantErr: false, - }, - { - name: "Return sorted set if only one key exists and is a sorted set", - preset: true, - presetValues: map[string]interface{}{ - "key8": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + { + name: "4. Successfully add elements to an existing set when XX flag is provided with existing elements", + preset: true, + presetValue: ss.NewSortedSet([]ss.MemberParam{ + {Value: "member1", Score: ss.Score(5.5)}, + {Value: "member2", Score: ss.Score(67.77)}, + {Value: "member3", Score: ss.Score(10)}, }), + key: "zadd_key4", + entries: map[string]float64{ + "member1": 55, + "member2": 1005, + "member3": 15, + "member4": 99.75, + }, + options: ZAddOptions{XX: true, CH: true}, + want: 3, + wantErr: false, }, - withscores: true, - keys: []string{"key8", "non-existent-key-1", "non-existent-key-2", "non-existent-key-3"}, - want: map[string]float64{ - "one": 1, "two": 2, "three": 3, "four": 4, - "five": 5, "six": 6, "seven": 7, "eight": 8, - }, - wantErr: false, - }, - { - name: "Throw error when one of the keys is not a sorted set", - preset: true, - presetValues: map[string]interface{}{ - "key9": "Default value", - "key10": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, - {Value: "eleven", Score: 11}, - }), - "key11": ss.NewSortedSet([]ss.MemberParam{ - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, - {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, - {Value: "twelve", Score: 12}, + { + name: "5. Fail to add element when providing XX flag with elements that do not exist in the sorted set", + preset: true, + presetValue: ss.NewSortedSet([]ss.MemberParam{ + {Value: "member1", Score: ss.Score(5.5)}, + {Value: "member2", Score: ss.Score(67.77)}, + {Value: "member3", Score: ss.Score(10)}, }), + key: "zadd_key5", + entries: map[string]float64{ + "member4": 5.5, + "member5": 100.5, + "member6": 15, + }, + options: ZAddOptions{XX: true}, + want: 0, + wantErr: false, }, - withscores: false, - keys: []string{"key9", "key10", "key11"}, - want: nil, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.preset { - for k, v := range tt.presetValues { - err := presetValue(server, context.Background(), k, v) + { + // Only update the elements where provided score is greater than current score if GT flag + // Return only the new elements added by default + name: "6. Only update the elements where provided score is greater than current score if GT flag", + preset: true, + presetValue: ss.NewSortedSet([]ss.MemberParam{ + {Value: "member1", Score: ss.Score(5.5)}, + {Value: "member2", Score: ss.Score(67.77)}, + {Value: "member3", Score: ss.Score(10)}, + }), + key: "zadd_key6", + entries: map[string]float64{ + "member1": 7.5, + "member4": 100.5, + "member5": 15, + }, + options: ZAddOptions{XX: true, CH: true, GT: true}, + want: 1, + wantErr: false, + }, + { + // Only update the elements where provided score is less than current score if LT flag is provided + // Return only the new elements added by default. + name: "7. Only update the elements where provided score is less than current score if LT flag is provided", + preset: true, + presetValue: ss.NewSortedSet([]ss.MemberParam{ + {Value: "member1", Score: ss.Score(5.5)}, + {Value: "member2", Score: ss.Score(67.77)}, + {Value: "member3", Score: ss.Score(10)}, + }), + key: "zadd_key7", + entries: map[string]float64{ + "member1": 3.5, + "member4": 100.5, + "member5": 15, + }, + options: ZAddOptions{XX: true, LT: true}, + want: 0, + wantErr: false, + }, + { + name: "8. Return all the elements that were updated AND added when CH flag is provided", + preset: true, + presetValue: ss.NewSortedSet([]ss.MemberParam{ + {Value: "member1", Score: ss.Score(5.5)}, + {Value: "member2", Score: ss.Score(67.77)}, + {Value: "member3", Score: ss.Score(10)}, + }), + key: "zadd_key8", + entries: map[string]float64{ + "member1": 3.5, + "member4": 100.5, + "member5": 15, + }, + options: ZAddOptions{XX: true, LT: true, CH: true}, + want: 1, + wantErr: false, + }, + { + name: "9. Increment the member by score", + preset: true, + presetValue: ss.NewSortedSet([]ss.MemberParam{ + {Value: "member1", Score: ss.Score(5.5)}, + {Value: "member2", Score: ss.Score(67.77)}, + {Value: "member3", Score: ss.Score(10)}, + }), + key: "zadd_key9", + entries: map[string]float64{ + "member3": 5.5, + }, + options: ZAddOptions{INCR: true}, + want: 0, + wantErr: false, + }, + { + name: "10. Fail when GT/LT flag is provided alongside NX flag", + preset: false, + presetValue: nil, + key: "zadd_key10", + entries: map[string]float64{ + "member1": 3.5, + "member5": 15, + }, + options: ZAddOptions{NX: true, LT: true, CH: true}, + want: 0, + wantErr: true, + }, + { + name: "11. Throw error when INCR flag is passed with more than one score/member pair", + preset: false, + presetValue: nil, + key: "zadd_key11", + entries: map[string]float64{ + "member1": 10.5, + "member2": 12.5, + }, + options: ZAddOptions{INCR: true}, + want: 0, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.preset { + err := presetValue(server, context.Background(), tt.key, tt.presetValue) if err != nil { t.Error(err) return } } - } - got, err := server.ZDiff(tt.withscores, tt.keys...) - if (err != nil) != tt.wantErr { - t.Errorf("ZDIFF() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("ZDIFF() got = %v, want %v", got, tt.want) - } - }) - } -} + got, err := server.ZAdd(tt.key, tt.entries, tt.options) + if (err != nil) != tt.wantErr { + t.Errorf("ZADD() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("ZADD() got = %v, want %v", got, tt.want) + } + }) + } + }) -func TestSugarDB_ZDIFFSTORE(t *testing.T) { - server := createSugarDB() + t.Run("TestSugarDB_ZCARD", func(t *testing.T) { + t.Parallel() - tests := []struct { - name string - preset bool - presetValues map[string]interface{} - destination string - keys []string - want int - wantErr bool - }{ - { - name: "Get the difference between 2 sorted sets", - preset: true, - presetValues: map[string]interface{}{ - "key1": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, - }), - "key2": ss.NewSortedSet([]ss.MemberParam{ - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + tests := []struct { + name string + preset bool + presetValue interface{} + key string + want int + wantErr bool + }{ + { + name: "1. Get cardinality of valid sorted set", + preset: true, + presetValue: ss.NewSortedSet([]ss.MemberParam{ + {Value: "member1", Score: ss.Score(5.5)}, + {Value: "member2", Score: ss.Score(67.77)}, + {Value: "member3", Score: ss.Score(10)}, }), + key: "zcard_key1", + want: 3, + wantErr: false, }, - destination: "destination1", - keys: []string{"key1", "key2"}, - want: 2, - wantErr: false, - }, - { - name: "Get the difference between 3 sorted sets", - preset: true, - presetValues: map[string]interface{}{ - "key3": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, - }), - "key4": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, - {Value: "eleven", Score: 11}, - }), - "key5": ss.NewSortedSet([]ss.MemberParam{ - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, - {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, - {Value: "twelve", Score: 12}, - }), + { + name: "2. Return 0 when trying to get cardinality from non-existent key", + preset: false, + presetValue: nil, + key: "zcard_key2", + want: 0, + wantErr: false, }, - destination: "destination2", - keys: []string{"key3", "key4", "key5"}, - want: 4, - wantErr: false, - }, - { - name: "Return base sorted set element if base set is the only existing key provided and is a valid sorted set", - preset: true, - presetValues: map[string]interface{}{ - "key6": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, - }), + { + name: "3. Return error when not a sorted set", + preset: true, + presetValue: "Default value", + key: "zcard_key3", + want: 0, + wantErr: true, }, - destination: "destination3", - keys: []string{"key6", "non-existent-key-1", "non-existent-key-2"}, - want: 8, - wantErr: false, - }, - { - name: "Throw error when base sorted set is not a set", - preset: true, - presetValues: map[string]interface{}{ - "key7": "Default value", - "key8": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, - {Value: "eleven", Score: 11}, - }), - "key9": ss.NewSortedSet([]ss.MemberParam{ - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, - {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, - {Value: "twelve", Score: 12}, - }), - }, - destination: "destination4", - keys: []string{"key7", "key8", "key9"}, - want: 0, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.preset { - for k, v := range tt.presetValues { - err := presetValue(server, context.Background(), k, v) + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.preset { + err := presetValue(server, context.Background(), tt.key, tt.presetValue) if err != nil { t.Error(err) return } } - } - got, err := server.ZDiffStore(tt.destination, tt.keys...) - if (err != nil) != tt.wantErr { - t.Errorf("ZDIFFSTORE() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("ZDIFFSTORE() got = %v, want %v", got, tt.want) - } - }) - } -} - -func TestSugarDB_ZINCRBY(t *testing.T) { - server := createSugarDB() - - tests := []struct { - name string - preset bool - presetValue interface{} - key string - increment float64 - member string - want float64 - wantErr bool - }{ - { - name: "Successfully increment by int. Return the new score", - preset: true, - presetValue: ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, - }), - key: "key1", - increment: 5, - member: "one", - want: 6, - wantErr: false, - }, - { - name: "Successfully increment by float. Return new score", - preset: true, - presetValue: ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, - }), - key: "key2", - increment: 346.785, - member: "one", - want: 347.785, - }, - { - name: "Increment on non-existent sorted set will create the set with the member and increment as its score", - preset: false, - presetValue: nil, - key: "key3", - increment: 346.785, - member: "one", - want: 346.785, - wantErr: false, - }, - { // 4. - name: "Increment score to +inf", - preset: true, - presetValue: ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, - }), - key: "key4", - increment: math.Inf(1), - member: "one", - want: math.Inf(1), - wantErr: false, - }, - { - name: "Increment score to -inf", - preset: true, - presetValue: ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, - }), - key: "key5", - increment: math.Inf(-1), - member: "one", - want: math.Inf(-1), - wantErr: false, - }, - { - name: "Incrementing score by negative increment should lower the score", - preset: true, - presetValue: ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, - }), - key: "key6", - increment: -2.5, - member: "five", - want: 2.5, - wantErr: false, - }, - { - name: "Return error when attempting to increment on a value that is not a valid sorted set", - preset: true, - presetValue: "Default value", - key: "key7", - increment: -2.5, - member: "five", - want: 0, - wantErr: true, - }, - { - name: "Return error when trying to increment a member that already has score -inf", - preset: true, - presetValue: ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: ss.Score(math.Inf(-1))}, - }), - key: "key8", - increment: 2.5, - member: "one", - want: 0, - wantErr: true, - }, - { - name: "Return error when trying to increment a member that already has score +inf", - preset: true, - presetValue: ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: ss.Score(math.Inf(1))}, - }), - key: "key9", - increment: 2.5, - member: "one", - want: 0, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.preset { - err := presetValue(server, context.Background(), tt.key, tt.presetValue) - if err != nil { - t.Error(err) + got, err := server.ZCard(tt.key) + if (err != nil) != tt.wantErr { + t.Errorf("ZCARD() error = %v, wantErr %v", err, tt.wantErr) return } - } - got, err := server.ZIncrBy(tt.key, tt.increment, tt.member) - if (err != nil) != tt.wantErr { - t.Errorf("ZINCRBY() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("ZINCRBY() got = %v, want %v", got, tt.want) - } - }) - } -} + if got != tt.want { + t.Errorf("ZCARD() got = %v, want %v", got, tt.want) + } + }) + } + }) -func TestSugarDB_ZINTER(t *testing.T) { - server := createSugarDB() + t.Run("TestSugarDB_ZCOUNT", func(t *testing.T) { + t.Parallel() - tests := []struct { - name string - preset bool - presetValues map[string]interface{} - keys []string - options ZInterOptions - want map[string]float64 - wantErr bool - }{ - { - name: "Get the intersection between 2 sorted sets", - preset: true, - presetValues: map[string]interface{}{ - "key1": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, - }), - "key2": ss.NewSortedSet([]ss.MemberParam{ - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + tests := []struct { + name string + preset bool + presetValue interface{} + key string + min float64 + max float64 + want int + wantErr bool + }{ + { + name: "1. Get entire count using infinity boundaries", + preset: true, + presetValue: ss.NewSortedSet([]ss.MemberParam{ + {Value: "member1", Score: ss.Score(5.5)}, + {Value: "member2", Score: ss.Score(67.77)}, + {Value: "member3", Score: ss.Score(10)}, + {Value: "member4", Score: ss.Score(1083.13)}, + {Value: "member5", Score: ss.Score(11)}, + {Value: "member6", Score: ss.Score(math.Inf(-1))}, + {Value: "member7", Score: ss.Score(math.Inf(1))}, }), + key: "zcount_key1", + min: math.Inf(-1), + max: math.Inf(1), + want: 7, + wantErr: false, }, - keys: []string{"key1", "key2"}, - options: ZInterOptions{}, - want: map[string]float64{"three": 0, "four": 0, "five": 0}, - wantErr: false, - }, - { - // Get the intersection between 3 sorted sets with scores. - // By default, the SUM aggregate will be used. - name: "Get the intersection between 3 sorted sets with scores", - preset: true, - presetValues: map[string]interface{}{ - "key3": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, - }), - "key4": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, - {Value: "eleven", Score: 11}, {Value: "eight", Score: 8}, - }), - "key5": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "eight", Score: 8}, - {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, - {Value: "twelve", Score: 12}, + { + name: "2. Get count of sub-set from -inf to limit", + preset: true, + presetValue: ss.NewSortedSet([]ss.MemberParam{ + {Value: "member1", Score: ss.Score(5.5)}, + {Value: "member2", Score: ss.Score(67.77)}, + {Value: "member3", Score: ss.Score(10)}, + {Value: "member4", Score: ss.Score(1083.13)}, + {Value: "member5", Score: ss.Score(11)}, + {Value: "member6", Score: ss.Score(math.Inf(-1))}, + {Value: "member7", Score: ss.Score(math.Inf(1))}, }), + key: "zcount_key2", + min: math.Inf(-1), + max: 90, + want: 5, + wantErr: false, }, - keys: []string{"key3", "key4", "key5"}, - options: ZInterOptions{WithScores: true}, - want: map[string]float64{"one": 3, "eight": 24}, - wantErr: false, - }, - { - // Get the intersection between 3 sorted sets with scores. - // Use MIN aggregate. - name: "Get the intersection between 3 sorted sets with scores", - preset: true, - presetValues: map[string]interface{}{ - "key6": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 100}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, - }), - "key7": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, - {Value: "eleven", Score: 11}, {Value: "eight", Score: 80}, - }), - "key8": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, - {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, - {Value: "twelve", Score: 12}, + { + name: "3. Get count of sub-set from bottom boundary to +inf limit", + preset: true, + presetValue: ss.NewSortedSet([]ss.MemberParam{ + {Value: "member1", Score: ss.Score(5.5)}, + {Value: "member2", Score: ss.Score(67.77)}, + {Value: "member3", Score: ss.Score(10)}, + {Value: "member4", Score: ss.Score(1083.13)}, + {Value: "member5", Score: ss.Score(11)}, + {Value: "member6", Score: ss.Score(math.Inf(-1))}, + {Value: "member7", Score: ss.Score(math.Inf(1))}, }), + key: "zcount_key3", + min: 1000, + max: math.Inf(1), + want: 2, + wantErr: false, }, - keys: []string{"key6", "key7", "key8"}, - options: ZInterOptions{Aggregate: "MIN", WithScores: true}, - want: map[string]float64{"one": 1, "eight": 8}, - wantErr: false, - }, - { - // Get the intersection between 3 sorted sets with scores. - // Use MAX aggregate. - preset: true, - presetValues: map[string]interface{}{ - "key9": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 100}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, - }), - "key10": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, - {Value: "eleven", Score: 11}, {Value: "eight", Score: 80}, - }), - "key11": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, - {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, - {Value: "twelve", Score: 12}, - }), + { + name: "4. Throw error when value at the key is not a sorted set", + preset: true, + presetValue: "Default value", + key: "zcount_key4", + min: 1, + max: 10, + want: 0, + wantErr: true, }, - keys: []string{"key9", "key10", "key11"}, - options: ZInterOptions{WithScores: true, Aggregate: "MAX"}, - want: map[string]float64{"one": 1000, "eight": 800}, - wantErr: false, - }, - { - // Get the intersection between 3 sorted sets with scores. - // Use SUM aggregate with weights modifier. - name: "Get the intersection between 3 sorted sets with scores", - preset: true, - presetValues: map[string]interface{}{ - "key12": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 100}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, - }), - "key13": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, - {Value: "eleven", Score: 11}, {Value: "eight", Score: 80}, - }), - "key14": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, - {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, - {Value: "twelve", Score: 12}, - }), - }, - keys: []string{"key12", "key13", "key14"}, - options: ZInterOptions{WithScores: true, Aggregate: "SUM", Weights: []float64{1, 5, 3}}, - want: map[string]float64{"one": 3105, "eight": 2808}, - wantErr: false, - }, - { - // Get the intersection between 3 sorted sets with scores. - // Use MAX aggregate with added weights. - name: "Get the intersection between 3 sorted sets with scores", - preset: true, - presetValues: map[string]interface{}{ - "key15": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 100}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, - }), - "key16": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, - {Value: "eleven", Score: 11}, {Value: "eight", Score: 80}, - }), - "key17": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, - {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, - {Value: "twelve", Score: 12}, - }), - }, - keys: []string{"key15", "key16", "key17"}, - options: ZInterOptions{WithScores: true, Aggregate: "MAX", Weights: []float64{1, 5, 3}}, - want: map[string]float64{"one": 3000, "eight": 2400}, - wantErr: false, - }, - { - // Get the intersection between 3 sorted sets with scores. - // Use MIN aggregate with added weights. - name: "Get the intersection between 3 sorted sets with scores", - preset: true, - presetValues: map[string]interface{}{ - "key18": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 100}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, - }), - "key19": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, - {Value: "eleven", Score: 11}, {Value: "eight", Score: 80}, - }), - "key20": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, - {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, - {Value: "twelve", Score: 12}, - }), - }, - keys: []string{"key18", "key19", "key20"}, - options: ZInterOptions{WithScores: true, Aggregate: "MIN", Weights: []float64{1, 5, 3}}, - want: map[string]float64{"one": 5, "eight": 8}, - wantErr: false, - }, - { - name: "Throw an error if there are more weights than keys", - preset: true, - presetValues: map[string]interface{}{ - "key21": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, - }), - "key22": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), - }, - keys: []string{"key21", "key22"}, - options: ZInterOptions{Weights: []float64{1, 2, 3}}, - want: nil, - wantErr: true, - }, - { - name: "Throw an error if there are fewer weights than keys", - preset: true, - presetValues: map[string]interface{}{ - "key23": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, - }), - "key24": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - }), - "key25": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), - }, - keys: []string{"key23", "key24", "key25"}, - options: ZInterOptions{Weights: []float64{5, 4}}, - want: nil, - wantErr: true, - }, - { - name: "Throw an error if there are no keys provided", - preset: true, - presetValues: map[string]interface{}{ - "key26": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), - "key27": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), - "key28": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), - }, - keys: []string{}, - options: ZInterOptions{}, - want: nil, - wantErr: true, - }, - { - name: "Throw an error if any of the provided keys are not sorted sets", - preset: true, - presetValues: map[string]interface{}{ - "key29": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, - }), - "key30": "Default value", - "key31": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), - }, - keys: []string{"key29", "key30", "key31"}, - options: ZInterOptions{}, - want: nil, - wantErr: true, - }, - { - name: "If any of the keys does not exist, return an empty array", - preset: true, - presetValues: map[string]interface{}{ - "key32": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, - {Value: "eleven", Score: 11}, - }), - "key33": ss.NewSortedSet([]ss.MemberParam{ - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, - {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, - {Value: "twelve", Score: 12}, - }), - }, - keys: []string{"non-existent", "key32", "key33"}, - options: ZInterOptions{}, - want: map[string]float64{}, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.preset { - for k, v := range tt.presetValues { - err := presetValue(server, context.Background(), k, v) + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.preset { + err := presetValue(server, context.Background(), tt.key, tt.presetValue) if err != nil { t.Error(err) return } } - } - got, err := server.ZInter(tt.keys, tt.options) - if (err != nil) != tt.wantErr { - t.Errorf("ZINTER() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("ZINTER() got = %v, want %v", got, tt.want) - } - }) - } -} + got, err := server.ZCount(tt.key, tt.min, tt.max) + if (err != nil) != tt.wantErr { + t.Errorf("ZCOUNT() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("ZCOUNT() got = %v, want %v", got, tt.want) + } + }) + } + }) -func TestSugarDB_ZINTERSTORE(t *testing.T) { - server := createSugarDB() + t.Run("TestSugarDB_ZDIFF", func(t *testing.T) { + t.Parallel() - tests := []struct { - name string - preset bool - presetValues map[string]interface{} - destination string - keys []string - options ZInterStoreOptions - want int - wantErr bool - }{ - { - name: "Get the intersection between 2 sorted sets", - preset: true, - presetValues: map[string]interface{}{ - "key1": ss.NewSortedSet([]ss.MemberParam{ + tests := []struct { + name string + preset bool + presetValues map[string]interface{} + withscores bool + keys []string + want map[string]float64 + wantErr bool + }{ + { + name: "1. Get the difference between 2 sorted sets without scores", + preset: true, + presetValues: map[string]interface{}{ + "zdiff_key1": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, + {Value: "two", Score: 2}, + {Value: "three", Score: 3}, + {Value: "four", Score: 4}, + }), + "zdiff_key2": ss.NewSortedSet([]ss.MemberParam{ + {Value: "three", Score: 3}, + {Value: "four", Score: 4}, + {Value: "five", Score: 5}, + {Value: "six", Score: 6}, + {Value: "seven", Score: 7}, + {Value: "eight", Score: 8}, + }), + }, + withscores: false, + keys: []string{"zdiff_key1", "zdiff_key2"}, + want: map[string]float64{"one": 0, "two": 0}, + wantErr: false, + }, + { + name: "2. Get the difference between 2 sorted sets with scores", + preset: true, + presetValues: map[string]interface{}{ + "zdiff_key3": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, + {Value: "two", Score: 2}, + {Value: "three", Score: 3}, + {Value: "four", Score: 4}, + }), + "zdiff_key4": ss.NewSortedSet([]ss.MemberParam{ + {Value: "three", Score: 3}, + {Value: "four", Score: 4}, + {Value: "five", Score: 5}, + {Value: "six", Score: 6}, + {Value: "seven", Score: 7}, + {Value: "eight", Score: 8}, + }), + }, + withscores: true, + keys: []string{"zdiff_key3", "zdiff_key4"}, + want: map[string]float64{"one": 1, "two": 2}, + wantErr: false, + }, + { + name: "3. Get the difference between 3 sets with scores", + preset: true, + presetValues: map[string]interface{}{ + "zdiff_key5": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + }), + "zdiff_key6": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, + {Value: "eleven", Score: 11}, + }), + "zdiff_key7": ss.NewSortedSet([]ss.MemberParam{ + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, + {Value: "twelve", Score: 12}, + }), + }, + withscores: true, + keys: []string{"zdiff_key5", "zdiff_key6", "zdiff_key7"}, + want: map[string]float64{"three": 3, "four": 4, "five": 5, "six": 6}, + wantErr: false, + }, + { + name: "4. Return sorted set if only one key exists and is a sorted set", + preset: true, + presetValues: map[string]interface{}{ + "zdiff_key8": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + }), + }, + withscores: true, + keys: []string{"zdiff_key8", "zdiff_non-existent-key-1", "zdiff_non-existent-key-2", "zdiff_non-existent-key-3"}, + want: map[string]float64{ + "one": 1, "two": 2, "three": 3, "four": 4, + "five": 5, "six": 6, "seven": 7, "eight": 8, + }, + wantErr: false, + }, + { + name: "5. Throw error when one of the keys is not a sorted set", + preset: true, + presetValues: map[string]interface{}{ + "zdiff_key9": "Default value", + "zdiff_key10": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, + {Value: "eleven", Score: 11}, + }), + "zdiff_key11": ss.NewSortedSet([]ss.MemberParam{ + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, + {Value: "twelve", Score: 12}, + }), + }, + withscores: false, + keys: []string{"zdiff_key9", "zdiff_key10", "zdiff_key11"}, + want: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.preset { + for k, v := range tt.presetValues { + err := presetValue(server, context.Background(), k, v) + if err != nil { + t.Error(err) + return + } + } + } + got, err := server.ZDiff(tt.withscores, tt.keys...) + if (err != nil) != tt.wantErr { + t.Errorf("ZDIFF() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("ZDIFF() got = %v, want %v", got, tt.want) + } + }) + } + }) + + t.Run("TestSugarDB_ZDIFFSTORE", func(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + preset bool + presetValues map[string]interface{} + destination string + keys []string + want int + wantErr bool + }{ + { + name: "1. Get the difference between 2 sorted sets", + preset: true, + presetValues: map[string]interface{}{ + "zdiffstore_key1": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, + }), + "zdiffstore_key2": ss.NewSortedSet([]ss.MemberParam{ + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + }), + }, + destination: "zdiffstore_destination1", + keys: []string{"zdiffstore_key1", "zdiffstore_key2"}, + want: 2, + wantErr: false, + }, + { + name: "2. Get the difference between 3 sorted sets", + preset: true, + presetValues: map[string]interface{}{ + "zdiffstore_key3": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + }), + "zdiffstore_key4": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, + {Value: "eleven", Score: 11}, + }), + "zdiffstore_key5": ss.NewSortedSet([]ss.MemberParam{ + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, + {Value: "twelve", Score: 12}, + }), + }, + destination: "zdiffstore_destination2", + keys: []string{"zdiffstore_key3", "zdiffstore_key4", "zdiffstore_key5"}, + want: 4, + wantErr: false, + }, + { + name: "3. Return base sorted set element if base set is the only existing key provided and is a valid sorted set", + preset: true, + presetValues: map[string]interface{}{ + "zdiffstore_key6": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + }), + }, + destination: "zdiffstore_destination3", + keys: []string{"zdiffstore_key6", "zdiffstore_non-existent-key-1", "zdiffstore_non-existent-key-2"}, + want: 8, + wantErr: false, + }, + { + name: "4. Throw error when base sorted set is not a set", + preset: true, + presetValues: map[string]interface{}{ + "zdiffstore_key7": "Default value", + "zdiffstore_key8": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, + {Value: "eleven", Score: 11}, + }), + "zdiffstore_key9": ss.NewSortedSet([]ss.MemberParam{ + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, + {Value: "twelve", Score: 12}, + }), + }, + destination: "zdiffstore_destination4", + keys: []string{"zdiffstore_key7", "zdiffstore_key8", "zdiffstore_key9"}, + want: 0, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.preset { + for k, v := range tt.presetValues { + err := presetValue(server, context.Background(), k, v) + if err != nil { + t.Error(err) + return + } + } + } + got, err := server.ZDiffStore(tt.destination, tt.keys...) + if (err != nil) != tt.wantErr { + t.Errorf("ZDIFFSTORE() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("ZDIFFSTORE() got = %v, want %v", got, tt.want) + } + }) + } + }) + + t.Run("TestSugarDB_ZINCRBY", func(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + preset bool + presetValue interface{} + key string + increment float64 + member string + want float64 + wantErr bool + }{ + { + name: "1. Successfully increment by int. Return the new score", + preset: true, + presetValue: ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, }), - "key2": ss.NewSortedSet([]ss.MemberParam{ - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, - }), + key: "zincrby_key1", + increment: 5, + member: "one", + want: 6, + wantErr: false, }, - destination: "destination1", - keys: []string{"key1", "key2"}, - options: ZInterStoreOptions{}, - want: 3, - wantErr: false, - }, - { - // Get the intersection between 3 sorted sets with scores. - // By default, the SUM aggregate will be used. - name: "Get the intersection between 3 sorted sets with scores", - preset: true, - presetValues: map[string]interface{}{ - "key3": ss.NewSortedSet([]ss.MemberParam{ + { + name: "2. Successfully increment by float. Return new score", + preset: true, + presetValue: ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, - }), - "key4": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, - {Value: "eleven", Score: 11}, {Value: "eight", Score: 8}, - }), - "key5": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "eight", Score: 8}, - {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, - {Value: "twelve", Score: 12}, + {Value: "five", Score: 5}, }), + key: "zincrby_key2", + increment: 346.785, + member: "one", + want: 347.785, }, - destination: "destination2", - keys: []string{"key3", "key4", "key5"}, - options: ZInterStoreOptions{WithScores: true}, - want: 2, - wantErr: false, - }, - { - // Get the intersection between 3 sorted sets with scores. - // Use MIN aggregate. - name: "Get the intersection between 3 sorted sets with scores", - preset: true, - presetValues: map[string]interface{}{ - "key6": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 100}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, - }), - "key7": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, - {Value: "eleven", Score: 11}, {Value: "eight", Score: 80}, - }), - "key8": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, - {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, - {Value: "twelve", Score: 12}, - }), + { + name: "3. Increment on non-existent sorted set will create the set with the member and increment as its score", + preset: false, + presetValue: nil, + key: "zincrby_key3", + increment: 346.785, + member: "one", + want: 346.785, + wantErr: false, }, - destination: "destination3", - keys: []string{"key6", "key7", "key8"}, - options: ZInterStoreOptions{WithScores: true, Aggregate: "MIN"}, - want: 2, - wantErr: false, - }, - { - // Get the intersection between 3 sorted sets with scores. - // Use MAX aggregate. - name: "Get the intersection between 3 sorted sets with scores", - preset: true, - presetValues: map[string]interface{}{ - "key9": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 100}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, - }), - "key10": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, - {Value: "eleven", Score: 11}, {Value: "eight", Score: 80}, - }), - "key11": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, - {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, - {Value: "twelve", Score: 12}, - }), - }, - destination: "destination4", - keys: []string{"key9", "key10", "key11"}, - options: ZInterStoreOptions{WithScores: true, Aggregate: "MAX"}, - want: 2, - wantErr: false, - }, - { - // Get the intersection between 3 sorted sets with scores. - // Use SUM aggregate with weights modifier. - name: "Get the intersection between 3 sorted sets with scores", - preset: true, - presetValues: map[string]interface{}{ - "key12": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 100}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, - }), - "key13": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, - {Value: "eleven", Score: 11}, {Value: "eight", Score: 80}, - }), - "key14": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, - {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, - {Value: "twelve", Score: 12}, - }), - }, - destination: "destination5", - keys: []string{"key12", "key13", "key14"}, - options: ZInterStoreOptions{WithScores: true, Aggregate: "SUM", Weights: []float64{1, 5, 3}}, - want: 2, - wantErr: false, - }, - { - // Get the intersection between 3 sorted sets with scores. - // Use MAX aggregate with added weights. - name: "Get the intersection between 3 sorted sets with scores", - preset: true, - presetValues: map[string]interface{}{ - "key15": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 100}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, - }), - "key16": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, - {Value: "eleven", Score: 11}, {Value: "eight", Score: 80}, - }), - "key17": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, - {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, - {Value: "twelve", Score: 12}, - }), - }, - destination: "destination6", - keys: []string{"key15", "key16", "key17"}, - options: ZInterStoreOptions{WithScores: true, Aggregate: "MAX", Weights: []float64{1, 5, 3}}, - want: 2, - wantErr: false, - }, - { - // Get the intersection between 3 sorted sets with scores. - // Use MIN aggregate with added weights. - name: "Get the intersection between 3 sorted sets with scores", - preset: true, - presetValues: map[string]interface{}{ - "key18": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 100}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, - }), - "key19": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, - {Value: "eleven", Score: 11}, {Value: "eight", Score: 80}, - }), - "key20": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, - {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, - {Value: "twelve", Score: 12}, - }), - }, - destination: "destination7", - keys: []string{"key18", "key19", "key20"}, - options: ZInterStoreOptions{WithScores: true, Aggregate: "MIN", Weights: []float64{1, 5, 3}}, - want: 2, - wantErr: false, - }, - { - name: "Throw an error if there are more weights than keys", - preset: true, - presetValues: map[string]interface{}{ - "key21": ss.NewSortedSet([]ss.MemberParam{ + { + name: "4. Increment score to +inf", + preset: true, + presetValue: ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + {Value: "five", Score: 5}, }), - "key22": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), + key: "zincrby_key4", + increment: math.Inf(1), + member: "one", + want: math.Inf(1), + wantErr: false, }, - destination: "destination8", - keys: []string{"key21", "key22"}, - options: ZInterStoreOptions{Weights: []float64{1, 2, 3}}, - want: 0, - wantErr: true, - }, - { - name: "Throw an error if there are fewer weights than keys", - preset: true, - presetValues: map[string]interface{}{ - "key23": ss.NewSortedSet([]ss.MemberParam{ + { + name: "5. Increment score to -inf", + preset: true, + presetValue: ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + {Value: "five", Score: 5}, }), - "key24": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - }), - "key25": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), + key: "zincrby_key5", + increment: math.Inf(-1), + member: "one", + want: math.Inf(-1), + wantErr: false, }, - destination: "destination9", - keys: []string{"key23", "key24"}, - options: ZInterStoreOptions{Weights: []float64{5}}, - want: 0, - wantErr: true, - }, - { - name: "Throw an error if there are no keys provided", - preset: true, - presetValues: map[string]interface{}{ - "key26": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), - "key27": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), - "key28": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), - }, - destination: "destination10", - keys: []string{}, - options: ZInterStoreOptions{Weights: []float64{5, 4}}, - want: 0, - wantErr: true, - }, - { - name: "Throw an error if any of the provided keys are not sorted sets", - preset: true, - presetValues: map[string]interface{}{ - "key29": ss.NewSortedSet([]ss.MemberParam{ + { + name: "6. Incrementing score by negative increment should lower the score", + preset: true, + presetValue: ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + {Value: "five", Score: 5}, }), - "key30": "Default value", - "key31": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), + key: "zincrby_key6", + increment: -2.5, + member: "five", + want: 2.5, + wantErr: false, }, - destination: "destination11", - keys: []string{"key29", "key30", "key31"}, - options: ZInterStoreOptions{}, - want: 0, - wantErr: true, - }, - { - name: "If any of the keys does not exist, return an empty array", - preset: true, - presetValues: map[string]interface{}{ - "key32": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, - {Value: "eleven", Score: 11}, - }), - "key33": ss.NewSortedSet([]ss.MemberParam{ - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, - {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, - {Value: "twelve", Score: 12}, - }), + { + name: "7. Return error when attempting to increment on a value that is not a valid sorted set", + preset: true, + presetValue: "Default value", + key: "zincrby_key7", + increment: -2.5, + member: "five", + want: 0, + wantErr: true, }, - destination: "destination12", - keys: []string{"non-existent", "key32", "key33"}, - options: ZInterStoreOptions{}, - want: 0, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.preset { - for k, v := range tt.presetValues { - err := presetValue(server, context.Background(), k, v) + { + name: "8. Return error when trying to increment a member that already has score -inf", + preset: true, + presetValue: ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: ss.Score(math.Inf(-1))}, + }), + key: "zincrby_key8", + increment: 2.5, + member: "one", + want: 0, + wantErr: true, + }, + { + name: "9. Return error when trying to increment a member that already has score +inf", + preset: true, + presetValue: ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: ss.Score(math.Inf(1))}, + }), + key: "zincrby_key9", + increment: 2.5, + member: "one", + want: 0, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.preset { + err := presetValue(server, context.Background(), tt.key, tt.presetValue) if err != nil { t.Error(err) return } } - } - got, err := server.ZInterStore(tt.destination, tt.keys, tt.options) - if (err != nil) != tt.wantErr { - t.Errorf("ZINTERSTORE() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("ZINTERSTORE() got = %v, want %v", got, tt.want) - } - }) - } -} - -func TestSugarDB_ZLEXCOUNT(t *testing.T) { - server := createSugarDB() - - tests := []struct { - name string - preset bool - presetValue interface{} - key string - min string - max string - want int - wantErr bool - }{ - { - name: "Get entire count using infinity boundaries", - preset: true, - presetValue: ss.NewSortedSet([]ss.MemberParam{ - {Value: "e", Score: ss.Score(1)}, - {Value: "f", Score: ss.Score(1)}, - {Value: "g", Score: ss.Score(1)}, - {Value: "h", Score: ss.Score(1)}, - {Value: "i", Score: ss.Score(1)}, - {Value: "j", Score: ss.Score(1)}, - {Value: "k", Score: ss.Score(1)}, - }), - key: "key1", - min: "f", - max: "j", - want: 5, - wantErr: false, - }, - { - name: "Return 0 when the members do not have the same score", - preset: true, - presetValue: ss.NewSortedSet([]ss.MemberParam{ - {Value: "a", Score: ss.Score(5.5)}, - {Value: "b", Score: ss.Score(67.77)}, - {Value: "c", Score: ss.Score(10)}, - {Value: "d", Score: ss.Score(1083.13)}, - {Value: "e", Score: ss.Score(11)}, - {Value: "f", Score: ss.Score(math.Inf(-1))}, - {Value: "g", Score: ss.Score(math.Inf(1))}, - }), - key: "key2", - min: "a", - max: "b", - want: 0, - wantErr: false, - }, - { - name: "Return 0 when the key does not exist", - preset: false, - presetValue: nil, - key: "key3", - min: "a", - max: "z", - want: 0, - wantErr: false, - }, - { - name: "Return error when the value at the key is not a sorted set", - preset: true, - presetValue: "Default value", - key: "key4", - min: "a", - max: "z", - want: 0, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.preset { - err := presetValue(server, context.Background(), tt.key, tt.presetValue) - if err != nil { - t.Error(err) + got, err := server.ZIncrBy(tt.key, tt.increment, tt.member) + if (err != nil) != tt.wantErr { + t.Errorf("ZINCRBY() error = %v, wantErr %v", err, tt.wantErr) return } - } - got, err := server.ZLexCount(tt.key, tt.min, tt.max) - if (err != nil) != tt.wantErr { - t.Errorf("ZLEXCOUNT() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("ZLEXCOUNT() got = %v, want %v", got, tt.want) - } - }) - } -} + if got != tt.want { + t.Errorf("ZINCRBY() got = %v, want %v", got, tt.want) + } + }) + } + }) -func TestSugarDB_ZMPOP(t *testing.T) { - server := createSugarDB() + t.Run("TestSugarDB_ZINTER", func(t *testing.T) { + t.Parallel() - tests := []struct { - name string - preset bool - presetValues map[string]interface{} - keys []string - options ZMPopOptions - want [][]string - wantErr bool - }{ - { - name: "Successfully pop one min element by default", - preset: true, - presetValues: map[string]interface{}{ - "key1": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, + tests := []struct { + name string + preset bool + presetValues map[string]interface{} + keys []string + options ZInterOptions + want map[string]float64 + wantErr bool + }{ + { + name: "1. Get the intersection between 2 sorted sets", + preset: true, + presetValues: map[string]interface{}{ + "zinter_key1": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, + }), + "zinter_key2": ss.NewSortedSet([]ss.MemberParam{ + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + }), + }, + keys: []string{"zinter_key1", "zinter_key2"}, + options: ZInterOptions{}, + want: map[string]float64{"three": 0, "four": 0, "five": 0}, + wantErr: false, + }, + { + name: "2. Get the intersection between 3 sorted sets with scores, SUM by default", + preset: true, + presetValues: map[string]interface{}{ + "zinter_key3": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + }), + "zinter_key4": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, + {Value: "eleven", Score: 11}, {Value: "eight", Score: 8}, + }), + "zinter_key5": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "eight", Score: 8}, + {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, + {Value: "twelve", Score: 12}, + }), + }, + keys: []string{"zinter_key3", "zinter_key4", "zinter_key5"}, + options: ZInterOptions{WithScores: true}, + want: map[string]float64{"one": 3, "eight": 24}, + wantErr: false, + }, + { + name: "3. Get the intersection between 3 sorted sets with scores (MIN)", + preset: true, + presetValues: map[string]interface{}{ + "zinter_key6": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 100}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + }), + "zinter_key7": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, + {Value: "eleven", Score: 11}, {Value: "eight", Score: 80}, + }), + "zinter_key8": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, + {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, + {Value: "twelve", Score: 12}, + }), + }, + keys: []string{"zinter_key6", "zinter_key7", "zinter_key8"}, + options: ZInterOptions{Aggregate: "MIN", WithScores: true}, + want: map[string]float64{"one": 1, "eight": 8}, + wantErr: false, + }, + { + name: "4. Get the intersection between 3 sorted sets with scores. (MAX)", + preset: true, + presetValues: map[string]interface{}{ + "zinter_key9": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 100}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + }), + "zinter_key10": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, + {Value: "eleven", Score: 11}, {Value: "eight", Score: 80}, + }), + "zinter_key11": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, + {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, + {Value: "twelve", Score: 12}, + }), + }, + keys: []string{"zinter_key9", "zinter_key10", "zinter_key11"}, + options: ZInterOptions{WithScores: true, Aggregate: "MAX"}, + want: map[string]float64{"one": 1000, "eight": 800}, + wantErr: false, + }, + { + name: "5. Get the intersection between 3 sorted sets with scores (SUM w/ weights)", + preset: true, + presetValues: map[string]interface{}{ + "zinter_key12": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 100}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + }), + "zinter_key13": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, + {Value: "eleven", Score: 11}, {Value: "eight", Score: 80}, + }), + "zinter_key14": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, + {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, + {Value: "twelve", Score: 12}, + }), + }, + keys: []string{"zinter_key12", "zinter_key13", "zinter_key14"}, + options: ZInterOptions{WithScores: true, Aggregate: "SUM", Weights: []float64{1, 5, 3}}, + want: map[string]float64{"one": 3105, "eight": 2808}, + wantErr: false, + }, + { + // Get the intersection between 3 sorted sets with scores. + // Use MAX aggregate with added weights. + name: "6. Get the intersection between 3 sorted sets with scores (MAX w/ weights)", + preset: true, + presetValues: map[string]interface{}{ + "zinter_key15": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 100}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + }), + "zinter_key16": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, + {Value: "eleven", Score: 11}, {Value: "eight", Score: 80}, + }), + "zinter_key17": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, + {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, + {Value: "twelve", Score: 12}, + }), + }, + keys: []string{"zinter_key15", "zinter_key16", "zinter_key17"}, + options: ZInterOptions{WithScores: true, Aggregate: "MAX", Weights: []float64{1, 5, 3}}, + want: map[string]float64{"one": 3000, "eight": 2400}, + wantErr: false, + }, + { + // Get the intersection between 3 sorted sets with scores. + // Use MIN aggregate with added weights. + name: "7. Get the intersection between 3 sorted sets with scores (MIN w/ weights)", + preset: true, + presetValues: map[string]interface{}{ + "zinter_key18": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 100}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + }), + "zinter_key19": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, + {Value: "eleven", Score: 11}, {Value: "eight", Score: 80}, + }), + "zinter_key20": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, + {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, + {Value: "twelve", Score: 12}, + }), + }, + keys: []string{"zinter_key18", "zinter_key19", "zinter_key20"}, + options: ZInterOptions{WithScores: true, Aggregate: "MIN", Weights: []float64{1, 5, 3}}, + want: map[string]float64{"one": 5, "eight": 8}, + wantErr: false, + }, + { + name: "8. Throw an error if there are more weights than keys", + preset: true, + presetValues: map[string]interface{}{ + "zinter_key21": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + }), + "zinter_key22": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), + }, + keys: []string{"zinter_key21", "zinter_key22"}, + options: ZInterOptions{Weights: []float64{1, 2, 3}}, + want: nil, + wantErr: true, + }, + { + name: "9. Throw an error if there are fewer weights than keys", + preset: true, + presetValues: map[string]interface{}{ + "zinter_key23": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + }), + "zinter_key24": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + }), + "zinter_key25": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), + }, + keys: []string{"zinter_key23", "zinter_key24", "zinter_key25"}, + options: ZInterOptions{Weights: []float64{5, 4}}, + want: nil, + wantErr: true, + }, + { + name: "10. Throw an error if there are no keys provided", + preset: true, + presetValues: map[string]interface{}{ + "zinter_key26": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), + "zinter_key27": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), + "zinter_key28": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), + }, + keys: []string{}, + options: ZInterOptions{}, + want: nil, + wantErr: true, + }, + { + name: "11. Throw an error if any of the provided keys are not sorted sets", + preset: true, + presetValues: map[string]interface{}{ + "zinter_key29": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + }), + "zinter_key30": "Default value", + "zinter_key31": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), + }, + keys: []string{"zinter_key29", "zinter_key30", "zinter_key31"}, + options: ZInterOptions{}, + want: nil, + wantErr: true, + }, + { + name: "12. If any of the keys does not exist, return an empty array", + preset: true, + presetValues: map[string]interface{}{ + "zinter_key32": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, + {Value: "eleven", Score: 11}, + }), + "zinter_key33": ss.NewSortedSet([]ss.MemberParam{ + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, + {Value: "twelve", Score: 12}, + }), + }, + keys: []string{"zinter_non-existent", "zinter_key32", "zinter_key33"}, + options: ZInterOptions{}, + want: map[string]float64{}, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.preset { + for k, v := range tt.presetValues { + err := presetValue(server, context.Background(), k, v) + if err != nil { + t.Error(err) + return + } + } + } + got, err := server.ZInter(tt.keys, tt.options) + if (err != nil) != tt.wantErr { + t.Errorf("ZINTER() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("ZINTER() got = %v, want %v", got, tt.want) + } + }) + } + }) + + t.Run("TestSugarDB_ZINTERSTORE", func(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + preset bool + presetValues map[string]interface{} + destination string + keys []string + options ZInterStoreOptions + want int + wantErr bool + }{ + { + name: "1. Get the intersection between 2 sorted sets", + preset: true, + presetValues: map[string]interface{}{ + "zinterstore_key1": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, + }), + "zinterstore_key2": ss.NewSortedSet([]ss.MemberParam{ + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + }), + }, + destination: "zinterstore_destination1", + keys: []string{"zinterstore_key1", "zinterstore_key2"}, + options: ZInterStoreOptions{}, + want: 3, + wantErr: false, + }, + { + // Get the intersection between 3 sorted sets with scores. + // By default, the SUM aggregate will be used. + name: "2. Get the intersection between 3 sorted sets with scores", + preset: true, + presetValues: map[string]interface{}{ + "zinterstore_key3": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + }), + "zinterstore_key4": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, + {Value: "eleven", Score: 11}, {Value: "eight", Score: 8}, + }), + "zinterstore_key5": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "eight", Score: 8}, + {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, + {Value: "twelve", Score: 12}, + }), + }, + destination: "zinterstore_destination2", + keys: []string{"zinterstore_key3", "zinterstore_key4", "zinterstore_key5"}, + options: ZInterStoreOptions{WithScores: true}, + want: 2, + wantErr: false, + }, + { + // Get the intersection between 3 sorted sets with scores. + // Use MIN aggregate. + name: "3. Get the intersection between 3 sorted sets with scores", + preset: true, + presetValues: map[string]interface{}{ + "zinterstore_key6": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 100}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + }), + "zinterstore_key7": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, + {Value: "eleven", Score: 11}, {Value: "eight", Score: 80}, + }), + "zinterstore_key8": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, + {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, + {Value: "twelve", Score: 12}, + }), + }, + destination: "zinterstore_destination3", + keys: []string{"zinterstore_key6", "zinterstore_key7", "zinterstore_key8"}, + options: ZInterStoreOptions{WithScores: true, Aggregate: "MIN"}, + want: 2, + wantErr: false, + }, + { + // Get the intersection between 3 sorted sets with scores. + // Use MAX aggregate. + name: "4. Get the intersection between 3 sorted sets with scores", + preset: true, + presetValues: map[string]interface{}{ + "zinterstore_key9": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 100}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + }), + "zinterstore_key10": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, + {Value: "eleven", Score: 11}, {Value: "eight", Score: 80}, + }), + "zinterstore_key11": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, + {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, + {Value: "twelve", Score: 12}, + }), + }, + destination: "zinterstore_destination4", + keys: []string{"zinterstore_key9", "zinterstore_key10", "zinterstore_key11"}, + options: ZInterStoreOptions{WithScores: true, Aggregate: "MAX"}, + want: 2, + wantErr: false, + }, + { + // Get the intersection between 3 sorted sets with scores. + // Use SUM aggregate with weights modifier. + name: "5. Get the intersection between 3 sorted sets with scores", + preset: true, + presetValues: map[string]interface{}{ + "zinterstore_key12": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 100}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + }), + "zinterstore_key13": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, + {Value: "eleven", Score: 11}, {Value: "eight", Score: 80}, + }), + "zinterstore_key14": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, + {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, + {Value: "twelve", Score: 12}, + }), + }, + destination: "zinterstore_destination5", + keys: []string{"zinterstore_key12", "zinterstore_key13", "zinterstore_key14"}, + options: ZInterStoreOptions{WithScores: true, Aggregate: "SUM", Weights: []float64{1, 5, 3}}, + want: 2, + wantErr: false, + }, + { + // Get the intersection between 3 sorted sets with scores. + // Use MAX aggregate with added weights. + name: "6. Get the intersection between 3 sorted sets with scores", + preset: true, + presetValues: map[string]interface{}{ + "zinterstore_key15": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 100}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + }), + "zinterstore_key16": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, + {Value: "eleven", Score: 11}, {Value: "eight", Score: 80}, + }), + "zinterstore_key17": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, + {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, + {Value: "twelve", Score: 12}, + }), + }, + destination: "zinterstore_destination6", + keys: []string{"zinterstore_key15", "zinterstore_key16", "zinterstore_key17"}, + options: ZInterStoreOptions{WithScores: true, Aggregate: "MAX", Weights: []float64{1, 5, 3}}, + want: 2, + wantErr: false, + }, + { + // Get the intersection between 3 sorted sets with scores. + // Use MIN aggregate with added weights. + name: "7. Get the intersection between 3 sorted sets with scores", + preset: true, + presetValues: map[string]interface{}{ + "zinterstore_key18": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 100}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + }), + "zinterstore_key19": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, + {Value: "eleven", Score: 11}, {Value: "eight", Score: 80}, + }), + "zinterstore_key20": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, + {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, + {Value: "twelve", Score: 12}, + }), + }, + destination: "zinterstore_destination7", + keys: []string{"zinterstore_key18", "zinterstore_key19", "zinterstore_key20"}, + options: ZInterStoreOptions{WithScores: true, Aggregate: "MIN", Weights: []float64{1, 5, 3}}, + want: 2, + wantErr: false, + }, + { + name: "8. Throw an error if there are more weights than keys", + preset: true, + presetValues: map[string]interface{}{ + "zinterstore_key21": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + }), + "zinterstore_key22": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), + }, + destination: "zinterstore_destination8", + keys: []string{"zinterstore_key21", "zinterstore_key22"}, + options: ZInterStoreOptions{Weights: []float64{1, 2, 3}}, + want: 0, + wantErr: true, + }, + { + name: "9. Throw an error if there are fewer weights than keys", + preset: true, + presetValues: map[string]interface{}{ + "zinterstore_key23": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + }), + "zinterstore_key24": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + }), + "zinterstore_key25": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), + }, + destination: "zinterstore_destination9", + keys: []string{"zinterstore_key23", "zinterstore_key24"}, + options: ZInterStoreOptions{Weights: []float64{5}}, + want: 0, + wantErr: true, + }, + { + name: "10. Throw an error if there are no keys provided", + preset: true, + presetValues: map[string]interface{}{ + "zinterstore_key26": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), + "zinterstore_key27": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), + "zinterstore_key28": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), + }, + destination: "zinterstore_destination10", + keys: []string{}, + options: ZInterStoreOptions{Weights: []float64{5, 4}}, + want: 0, + wantErr: true, + }, + { + name: "11. Throw an error if any of the provided keys are not sorted sets", + preset: true, + presetValues: map[string]interface{}{ + "zinterstore_key29": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + }), + "zinterstore_key30": "Default value", + "zinterstore_key31": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), + }, + destination: "zinterstore_destination11", + keys: []string{"zinterstore_key29", "zinterstore_key30", "zinterstore_key31"}, + options: ZInterStoreOptions{}, + want: 0, + wantErr: true, + }, + { + name: "12. If any of the keys does not exist, return an empty array", + preset: true, + presetValues: map[string]interface{}{ + "zinterstore_key32": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, + {Value: "eleven", Score: 11}, + }), + "zinterstore_key33": ss.NewSortedSet([]ss.MemberParam{ + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, + {Value: "twelve", Score: 12}, + }), + }, + destination: "zinterstore_destination12", + keys: []string{"zinterstore_non-existent", "zinterstore_key32", "zinterstore_key33"}, + options: ZInterStoreOptions{}, + want: 0, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.preset { + for k, v := range tt.presetValues { + err := presetValue(server, context.Background(), k, v) + if err != nil { + t.Error(err) + return + } + } + } + got, err := server.ZInterStore(tt.destination, tt.keys, tt.options) + if (err != nil) != tt.wantErr { + t.Errorf("ZINTERSTORE() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("ZINTERSTORE() got = %v, want %v", got, tt.want) + } + }) + } + }) + + t.Run("TestSugarDB_ZLEXCOUNT", func(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + preset bool + presetValue interface{} + key string + min string + max string + want int + wantErr bool + }{ + { + name: "1. Get entire count using infinity boundaries", + preset: true, + presetValue: ss.NewSortedSet([]ss.MemberParam{ + {Value: "e", Score: ss.Score(1)}, + {Value: "f", Score: ss.Score(1)}, + {Value: "g", Score: ss.Score(1)}, + {Value: "h", Score: ss.Score(1)}, + {Value: "i", Score: ss.Score(1)}, + {Value: "j", Score: ss.Score(1)}, + {Value: "k", Score: ss.Score(1)}, }), + key: "zlexcount_key1", + min: "f", + max: "j", + want: 5, + wantErr: false, }, - keys: []string{"key1"}, - options: ZMPopOptions{}, - want: [][]string{ - {"one", "1"}, - }, - wantErr: false, - }, - { - name: "Successfully pop one min element by specifying MIN", - preset: true, - presetValues: map[string]interface{}{ - "key2": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, + { + name: "2. Return 0 when the members do not have the same score", + preset: true, + presetValue: ss.NewSortedSet([]ss.MemberParam{ + {Value: "a", Score: ss.Score(5.5)}, + {Value: "b", Score: ss.Score(67.77)}, + {Value: "c", Score: ss.Score(10)}, + {Value: "d", Score: ss.Score(1083.13)}, + {Value: "e", Score: ss.Score(11)}, + {Value: "f", Score: ss.Score(math.Inf(-1))}, + {Value: "g", Score: ss.Score(math.Inf(1))}, }), + key: "zlexcount_key2", + min: "a", + max: "b", + want: 0, + wantErr: false, }, - keys: []string{"key2"}, - options: ZMPopOptions{Min: true}, - want: [][]string{ - {"one", "1"}, + { + name: "3. Return 0 when the key does not exist", + preset: false, + presetValue: nil, + key: "zlexcount_key3", + min: "a", + max: "z", + want: 0, + wantErr: false, }, - wantErr: false, - }, - { - name: "Successfully pop one max element by specifying MAX modifier", - preset: true, - presetValues: map[string]interface{}{ - "key3": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, - }), + { + name: "4. Return error when the value at the key is not a sorted set", + preset: true, + presetValue: "Default value", + key: "zlexcount_key4", + min: "a", + max: "z", + want: 0, + wantErr: true, }, - keys: []string{"key3"}, - options: ZMPopOptions{Max: true}, - want: [][]string{ - {"five", "5"}, - }, - wantErr: false, - }, - { - name: "Successfully pop multiple min elements", - preset: true, - presetValues: map[string]interface{}{ - "key4": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, - }), - }, - keys: []string{"key4"}, - options: ZMPopOptions{Min: true, Count: 5}, - want: [][]string{ - {"one", "1"}, {"two", "2"}, {"three", "3"}, - {"four", "4"}, {"five", "5"}, - }, - wantErr: false, - }, - { - name: "Successfully pop multiple max elements", - preset: true, - presetValues: map[string]interface{}{ - "key5": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, - }), - }, - keys: []string{"key5"}, - options: ZMPopOptions{Max: true, Count: 5}, - want: [][]string{{"two", "2"}, {"three", "3"}, {"four", "4"}, {"five", "5"}, {"six", "6"}}, - wantErr: false, - }, - { - name: "Successfully pop elements from the first set which is non-empty", - preset: true, - presetValues: map[string]interface{}{ - "key6": ss.NewSortedSet([]ss.MemberParam{}), - "key7": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, - }), - }, - keys: []string{"key6", "key7"}, - options: ZMPopOptions{Max: true, Count: 5}, - want: [][]string{{"two", "2"}, {"three", "3"}, {"four", "4"}, {"five", "5"}, {"six", "6"}}, - wantErr: false, - }, - { - name: "Skip the non-set items and pop elements from the first non-empty sorted set found", - preset: true, - presetValues: map[string]interface{}{ - "key8": "Default value", - "key9": 56, - "key10": ss.NewSortedSet([]ss.MemberParam{}), - "key11": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, - }), - }, - keys: []string{"key8", "key9", "key10", "key11"}, - options: ZMPopOptions{Min: true, Count: 5}, - want: [][]string{{"one", "1"}, {"two", "2"}, {"three", "3"}, {"four", "4"}, {"five", "5"}}, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.preset { - for k, v := range tt.presetValues { - err := presetValue(server, context.Background(), k, v) + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.preset { + err := presetValue(server, context.Background(), tt.key, tt.presetValue) if err != nil { t.Error(err) return } } - } - got, err := server.ZMPop(tt.keys, tt.options) - if (err != nil) != tt.wantErr { - t.Errorf("ZMPOP() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !internal.CompareNestedStringArrays(got, tt.want) { - t.Errorf("ZMPOP() got = %v, want %v", got, tt.want) - } - }) - } -} - -func TestSugarDB_ZMSCORE(t *testing.T) { - server := createSugarDB() - - tests := []struct { - name string - preset bool - presetValue interface{} - key string - members []string - want []interface{} - wantErr bool - }{ - { // Return multiple scores from the sorted set. - // Return nil for elements that do not exist in the sorted set. - name: "Return multiple scores from the sorted set", - preset: true, - presetValue: ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1.1}, {Value: "two", Score: 245}, - {Value: "three", Score: 3}, {Value: "four", Score: 4.055}, - {Value: "five", Score: 5}, - }), - key: "key1", - members: []string{"one", "none", "two", "one", "three", "four", "none", "five"}, - want: []interface{}{"1.1", nil, "245", "1.1", "3", "4.055", nil, "5"}, - wantErr: false, - }, - { - name: "If key does not exist, return empty array", - preset: false, - presetValue: nil, - key: "key2", - members: []string{"one", "two", "three", "four"}, - want: []interface{}{}, - wantErr: false, - }, - { - name: "Throw error when trying to find scores from elements that are not sorted sets", - preset: true, - presetValue: "Default value", - key: "key3", - members: []string{"one", "two", "three"}, - want: []interface{}{}, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.preset { - err := presetValue(server, context.Background(), tt.key, tt.presetValue) - if err != nil { - t.Error(err) + got, err := server.ZLexCount(tt.key, tt.min, tt.max) + if (err != nil) != tt.wantErr { + t.Errorf("ZLEXCOUNT() error = %v, wantErr %v", err, tt.wantErr) return } - } - got, err := server.ZMScore(tt.key, tt.members...) - if (err != nil) != tt.wantErr { - t.Errorf("ZMSCORE() error = %v, wantErr %v", err, tt.wantErr) - return - } - if len(got) != len(tt.want) { - t.Errorf("ZMSCORE() got length = %v, want length %v", len(got), len(tt.want)) - return - } - for i := 0; i < len(got); i++ { - if got[i] == nil && tt.want[i] == nil { - continue + if got != tt.want { + t.Errorf("ZLEXCOUNT() got = %v, want %v", got, tt.want) } - if (got[i] == nil) != (tt.want[i] == nil) { - t.Errorf("ZMSCORE() got[%d] = %v, want[%d] %v", i, got, i, tt.want) - } - wantf, _ := strconv.ParseFloat(tt.want[i].(string), 64) - if got[i] != wantf { - t.Errorf("ZMSCORE() got[%d] = %v, want[%d] %v", i, got[i], i, wantf) - } - } - }) - } -} + }) + } + }) -func TestSugarDB_ZPOP(t *testing.T) { - server := createSugarDB() + t.Run("TestSugarDB_ZMPOP", func(t *testing.T) { + t.Parallel() - tests := []struct { - name string - preset bool - presetValue interface{} - key string - count uint - popFunc func(key string, count uint) ([][]string, error) - want [][]string - wantErr bool - }{ - { - name: "Successfully pop one min element", - preset: true, - presetValue: ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, - }), - key: "key1", - count: 1, - popFunc: server.ZPopMin, - want: [][]string{ - {"one", "1"}, + tests := []struct { + name string + preset bool + presetValues map[string]interface{} + keys []string + options ZMPopOptions + want [][]string + wantErr bool + }{ + { + name: "1. Successfully pop one min element by default", + preset: true, + presetValues: map[string]interface{}{ + "zmpop_key1": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, + }), + }, + keys: []string{"zmpop_key1"}, + options: ZMPopOptions{}, + want: [][]string{ + {"one", "1"}, + }, + wantErr: false, }, - wantErr: false, - }, - { - name: "Successfully pop one max element", - preset: true, - presetValue: ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, - }), - key: "key2", - count: 1, - popFunc: server.ZPopMax, - want: [][]string{{"five", "5"}}, - wantErr: false, - }, - { - name: "Successfully pop multiple min elements", - preset: true, - presetValue: ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, - }), - popFunc: server.ZPopMin, - key: "key3", - count: 5, - want: [][]string{ - {"one", "1"}, {"two", "2"}, {"three", "3"}, - {"four", "4"}, {"five", "5"}, + { + name: "2. Successfully pop one min element by specifying MIN", + preset: true, + presetValues: map[string]interface{}{ + "zmpop_key2": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, + }), + }, + keys: []string{"zmpop_key2"}, + options: ZMPopOptions{Min: true}, + want: [][]string{ + {"one", "1"}, + }, + wantErr: false, }, - wantErr: false, - }, - { - name: "Successfully pop multiple max elements", - preset: true, - presetValue: ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, - }), - popFunc: server.ZPopMax, - key: "key4", - count: 5, - want: [][]string{{"two", "2"}, {"three", "3"}, {"four", "4"}, {"five", "5"}, {"six", "6"}}, - wantErr: false, - }, - { - name: "Throw an error when trying to pop from an element that's not a sorted set", - preset: true, - presetValue: "Default value", - popFunc: server.ZPopMin, - key: "key5", - count: 1, - want: [][]string{}, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.preset { - err := presetValue(server, context.Background(), tt.key, tt.presetValue) - if err != nil { - t.Error(err) + { + name: "3. Successfully pop one max element by specifying MAX modifier", + preset: true, + presetValues: map[string]interface{}{ + "zmpop_key3": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, + }), + }, + keys: []string{"zmpop_key3"}, + options: ZMPopOptions{Max: true}, + want: [][]string{ + {"five", "5"}, + }, + wantErr: false, + }, + { + name: "4. Successfully pop multiple min elements", + preset: true, + presetValues: map[string]interface{}{ + "zmpop_key4": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, + }), + }, + keys: []string{"zmpop_key4"}, + options: ZMPopOptions{Min: true, Count: 5}, + want: [][]string{ + {"one", "1"}, {"two", "2"}, {"three", "3"}, + {"four", "4"}, {"five", "5"}, + }, + wantErr: false, + }, + { + name: "5. Successfully pop multiple max elements", + preset: true, + presetValues: map[string]interface{}{ + "zmpop_key5": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, + }), + }, + keys: []string{"zmpop_key5"}, + options: ZMPopOptions{Max: true, Count: 5}, + want: [][]string{{"two", "2"}, {"three", "3"}, {"four", "4"}, {"five", "5"}, {"six", "6"}}, + wantErr: false, + }, + { + name: "6. Successfully pop elements from the first set which is non-empty", + preset: true, + presetValues: map[string]interface{}{ + "zmpop_key6": ss.NewSortedSet([]ss.MemberParam{}), + "zmpop_key7": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, + }), + }, + keys: []string{"zmpop_key6", "zmpop_key7"}, + options: ZMPopOptions{Max: true, Count: 5}, + want: [][]string{{"two", "2"}, {"three", "3"}, {"four", "4"}, {"five", "5"}, {"six", "6"}}, + wantErr: false, + }, + { + name: "7. Skip the non-set items and pop elements from the first non-empty sorted set found", + preset: true, + presetValues: map[string]interface{}{ + "zmpop_key8": "Default value", + "zmpop_key9": 56, + "zmpop_key10": ss.NewSortedSet([]ss.MemberParam{}), + "zmpop_key11": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, + }), + }, + keys: []string{"zmpop_key8", "zmpop_key9", "zmpop_key10", "zmpop_key11"}, + options: ZMPopOptions{Min: true, Count: 5}, + want: [][]string{{"one", "1"}, {"two", "2"}, {"three", "3"}, {"four", "4"}, {"five", "5"}}, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.preset { + for k, v := range tt.presetValues { + err := presetValue(server, context.Background(), k, v) + if err != nil { + t.Error(err) + return + } + } + } + got, err := server.ZMPop(tt.keys, tt.options) + if (err != nil) != tt.wantErr { + t.Errorf("ZMPOP() error = %v, wantErr %v", err, tt.wantErr) return } - } - got, err := tt.popFunc(tt.key, tt.count) - if (err != nil) != tt.wantErr { - t.Errorf("ZPOPMAX() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !internal.CompareNestedStringArrays(got, tt.want) { - t.Errorf("ZPOPMAX() got = %v, want %v", got, tt.want) - } - }) - } -} + if !internal.CompareNestedStringArrays(got, tt.want) { + t.Errorf("ZMPOP() got = %v, want %v", got, tt.want) + } + }) + } + }) -func TestSugarDB_ZRANDMEMBER(t *testing.T) { - server := createSugarDB() + t.Run("TestSugarDB_ZMSCORE", func(t *testing.T) { + t.Parallel() - tests := []struct { - name string - preset bool - presetValue interface{} - key string - count int - withscores bool - want int - wantErr bool - }{ - { // Return multiple random elements without removing them. - // Count is positive, do not allow repeated elements. - name: "Return multiple random elements without removing them", - preset: true, - key: "key1", - presetValue: ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, - }), - count: 3, - withscores: false, - want: 3, - wantErr: false, - }, - { - // Return multiple random elements and their scores without removing them. - // Count is negative, so allow repeated numbers. - name: "Return multiple random elements and their scores without removing them", - preset: true, - key: "key2", - presetValue: ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, - }), - count: -5, - withscores: true, - want: 5, - wantErr: false, - }, - { - name: "Return error when the source key is not a sorted set", - preset: true, - key: "key3", - presetValue: "Default value", - count: 1, - withscores: false, - want: 0, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.preset { - err := presetValue(server, context.Background(), tt.key, tt.presetValue) - if err != nil { - t.Error(err) + tests := []struct { + name string + preset bool + presetValue interface{} + key string + members []string + want []interface{} + wantErr bool + }{ + { // Return multiple scores from the sorted set. + // Return nil for elements that do not exist in the sorted set. + name: "1. Return multiple scores from the sorted set", + preset: true, + presetValue: ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1.1}, {Value: "two", Score: 245}, + {Value: "three", Score: 3}, {Value: "four", Score: 4.055}, + {Value: "five", Score: 5}, + }), + key: "zmscore_key1", + members: []string{"one", "none", "two", "one", "three", "four", "none", "five"}, + want: []interface{}{"1.1", nil, "245", "1.1", "3", "4.055", nil, "5"}, + wantErr: false, + }, + { + name: "2. If key does not exist, return empty array", + preset: false, + presetValue: nil, + key: "zmscore_key2", + members: []string{"one", "two", "three", "four"}, + want: []interface{}{}, + wantErr: false, + }, + { + name: "3. Throw error when trying to find scores from elements that are not sorted sets", + preset: true, + presetValue: "Default value", + key: "zmscore_key3", + members: []string{"one", "two", "three"}, + want: []interface{}{}, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.preset { + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } + } + got, err := server.ZMScore(tt.key, tt.members...) + if (err != nil) != tt.wantErr { + t.Errorf("ZMSCORE() error = %v, wantErr %v", err, tt.wantErr) return } - } - got, err := server.ZRandMember(tt.key, tt.count, tt.withscores) - if (err != nil) != tt.wantErr { - t.Errorf("ZRANDMEMBER() error = %v, wantErr %v", err, tt.wantErr) - return - } - if len(got) != tt.want { - t.Errorf("ZRANDMEMBER() got = %v, want %v", len(got), tt.want) - } - }) - } -} - -func TestSugarDB_ZRANGE(t *testing.T) { - server := createSugarDB() - - tests := []struct { - name string - preset bool - presetValue interface{} - key string - start string - stop string - options ZRangeOptions - want map[string]float64 - wantErr bool - }{ - { - name: "Get elements withing score range without score", - preset: true, - presetValue: ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, - }), - key: "key1", - start: "3", - stop: "7", - options: ZRangeOptions{ByScore: true}, - want: map[string]float64{"three": 0, "four": 0, "five": 0, "six": 0, "seven": 0}, - wantErr: false, - }, - { - name: "Get elements within score range with score", - preset: true, - presetValue: ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, - }), - key: "key2", - start: "3", - stop: "7", - options: ZRangeOptions{ByScore: true, WithScores: true}, - want: map[string]float64{"three": 3, "four": 4, "five": 5, "six": 6, "seven": 7}, - wantErr: false, - }, - { - // Get elements within score range with offset and limit. - // Offset and limit are in where we start and stop counting in the original sorted set (NOT THE RESULT). - name: "Get elements within score range with offset and limit", - preset: true, - presetValue: ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, - }), - key: "key3", - start: "3", - stop: "7", - options: ZRangeOptions{WithScores: true, ByScore: true, Offset: 2, Count: 4}, - want: map[string]float64{"three": 3, "four": 4, "five": 5}, - wantErr: false, - }, - { - name: "Get elements within lex range without score", - preset: true, - presetValue: ss.NewSortedSet([]ss.MemberParam{ - {Value: "a", Score: 1}, {Value: "e", Score: 1}, - {Value: "b", Score: 1}, {Value: "f", Score: 1}, - {Value: "c", Score: 1}, {Value: "g", Score: 1}, - {Value: "d", Score: 1}, {Value: "h", Score: 1}, - }), - key: "key4", - start: "c", - stop: "g", - options: ZRangeOptions{ByLex: true}, - want: map[string]float64{"c": 0, "d": 0, "e": 0, "f": 0, "g": 0}, - wantErr: false, - }, - { - name: "Get elements within lex range with score", - preset: true, - presetValue: ss.NewSortedSet([]ss.MemberParam{ - {Value: "a", Score: 1}, {Value: "e", Score: 1}, - {Value: "b", Score: 1}, {Value: "f", Score: 1}, - {Value: "c", Score: 1}, {Value: "g", Score: 1}, - {Value: "d", Score: 1}, {Value: "h", Score: 1}, - }), - key: "key5", - start: "a", - stop: "f", - options: ZRangeOptions{ByLex: true, WithScores: true}, - want: map[string]float64{"a": 1, "b": 1, "c": 1, "d": 1, "e": 1, "f": 1}, - wantErr: false, - }, - { - // Get elements within lex range with offset and limit. - // Offset and limit are in where we start and stop counting in the original sorted set (NOT THE RESULT). - name: "Get elements within lex range with offset and limit", - preset: true, - presetValue: ss.NewSortedSet([]ss.MemberParam{ - {Value: "a", Score: 1}, {Value: "b", Score: 1}, - {Value: "c", Score: 1}, {Value: "d", Score: 1}, - {Value: "e", Score: 1}, {Value: "f", Score: 1}, - {Value: "g", Score: 1}, {Value: "h", Score: 1}, - }), - key: "key6", - start: "a", - stop: "h", - options: ZRangeOptions{WithScores: true, ByLex: true, Offset: 2, Count: 4}, - want: map[string]float64{"c": 1, "d": 1, "e": 1}, - wantErr: false, - }, - { - name: "Return an empty map when we use BYLEX while elements have different scores", - preset: true, - presetValue: ss.NewSortedSet([]ss.MemberParam{ - {Value: "a", Score: 1}, {Value: "b", Score: 5}, - {Value: "c", Score: 2}, {Value: "d", Score: 6}, - {Value: "e", Score: 3}, {Value: "f", Score: 7}, - {Value: "g", Score: 4}, {Value: "h", Score: 8}, - }), - key: "key7", - start: "a", - stop: "h", - options: ZRangeOptions{WithScores: true, ByLex: true, Offset: 2, Count: 4}, - want: map[string]float64{}, - wantErr: false, - }, - { - name: "Throw error when the key does not hold a sorted set", - preset: true, - presetValue: "Default value", - key: "key10", - start: "a", - stop: "h", - options: ZRangeOptions{WithScores: true, ByLex: true, Offset: 2, Count: 4}, - want: nil, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.preset { - err := presetValue(server, context.Background(), tt.key, tt.presetValue) - if err != nil { - t.Error(err) + if len(got) != len(tt.want) { + t.Errorf("ZMSCORE() got length = %v, want length %v", len(got), len(tt.want)) return } - } - got, err := server.ZRange(tt.key, tt.start, tt.stop, tt.options) - if (err != nil) != tt.wantErr { - t.Errorf("ZRANGE() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("ZRANGE() got = %v, want %v", got, tt.want) - } - }) - } -} + for i := 0; i < len(got); i++ { + if got[i] == nil && tt.want[i] == nil { + continue + } + if (got[i] == nil) != (tt.want[i] == nil) { + t.Errorf("ZMSCORE() got[%d] = %v, want[%d] %v", i, got, i, tt.want) + } + wantf, _ := strconv.ParseFloat(tt.want[i].(string), 64) + if got[i] != wantf { + t.Errorf("ZMSCORE() got[%d] = %v, want[%d] %v", i, got[i], i, wantf) + } + } + }) + } + }) -func TestSugarDB_ZRANGESTORE(t *testing.T) { - server := createSugarDB() + t.Run("TestSugarDB_ZPOP", func(t *testing.T) { + t.Parallel() - tests := []struct { - name string - preset bool - presetValues map[string]interface{} - destination string - source string - start string - stop string - options ZRangeStoreOptions - want int - wantErr bool - }{ - { - name: "Get elements within score range without score", - preset: true, - presetValues: map[string]interface{}{ - "key1": ss.NewSortedSet([]ss.MemberParam{ + tests := []struct { + name string + preset bool + presetValue interface{} + key string + count uint + popFunc func(key string, count uint) ([][]string, error) + want [][]string + wantErr bool + }{ + { + name: "1. Successfully pop one min element", + preset: true, + presetValue: ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, + }), + key: "zpop_key1", + count: 1, + popFunc: server.ZPopMin, + want: [][]string{ + {"one", "1"}, + }, + wantErr: false, + }, + { + name: "2. Successfully pop one max element", + preset: true, + presetValue: ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, + }), + key: "zpop_key2", + count: 1, + popFunc: server.ZPopMax, + want: [][]string{{"five", "5"}}, + wantErr: false, + }, + { + name: "3. Successfully pop multiple min elements", + preset: true, + presetValue: ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, + }), + popFunc: server.ZPopMin, + key: "zpop_key3", + count: 5, + want: [][]string{ + {"one", "1"}, {"two", "2"}, {"three", "3"}, + {"four", "4"}, {"five", "5"}, + }, + wantErr: false, + }, + { + name: "4. Successfully pop multiple max elements", + preset: true, + presetValue: ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, + }), + popFunc: server.ZPopMax, + key: "zpop_key4", + count: 5, + want: [][]string{{"two", "2"}, {"three", "3"}, {"four", "4"}, {"five", "5"}, {"six", "6"}}, + wantErr: false, + }, + { + name: "5. Throw an error when trying to pop from an element that's not a sorted set", + preset: true, + presetValue: "Default value", + popFunc: server.ZPopMin, + key: "zpop_key5", + count: 1, + want: [][]string{}, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.preset { + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } + } + got, err := tt.popFunc(tt.key, tt.count) + if (err != nil) != tt.wantErr { + t.Errorf("ZPOPMAX() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !internal.CompareNestedStringArrays(got, tt.want) { + t.Errorf("ZPOPMAX() got = %v, want %v", got, tt.want) + } + }) + } + }) + + t.Run("TestSugarDB_ZRANDMEMBER", func(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + preset bool + presetValue interface{} + key string + count int + withscores bool + want int + wantErr bool + }{ + { // Return multiple random elements without removing them. + // Count is positive, do not allow repeated elements. + name: "1. Return multiple random elements without removing them", + preset: true, + key: "zrandmember_key1", + presetValue: ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + }), + count: 3, + withscores: false, + want: 3, + wantErr: false, + }, + { + // Return multiple random elements and their scores without removing them. + // Count is negative, so allow repeated numbers. + name: "2. Return multiple random elements and their scores without removing them", + preset: true, + key: "zrandmember_key2", + presetValue: ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + }), + count: -5, + withscores: true, + want: 5, + wantErr: false, + }, + { + name: "3. Return error when the source key is not a sorted set", + preset: true, + key: "zrandmember_key3", + presetValue: "Default value", + count: 1, + withscores: false, + want: 0, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.preset { + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } + } + got, err := server.ZRandMember(tt.key, tt.count, tt.withscores) + if (err != nil) != tt.wantErr { + t.Errorf("ZRANDMEMBER() error = %v, wantErr %v", err, tt.wantErr) + return + } + if len(got) != tt.want { + t.Errorf("ZRANDMEMBER() got = %v, want %v", len(got), tt.want) + } + }) + } + }) + + t.Run("TestSugarDB_ZRANGE", func(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + preset bool + presetValue interface{} + key string + start string + stop string + options ZRangeOptions + want map[string]float64 + wantErr bool + }{ + { + name: "1. Get elements withing score range without score", + preset: true, + presetValue: ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, {Value: "six", Score: 6}, {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, }), + key: "zrange_key1", + start: "3", + stop: "7", + options: ZRangeOptions{ByScore: true}, + want: map[string]float64{"three": 0, "four": 0, "five": 0, "six": 0, "seven": 0}, + wantErr: false, }, - destination: "destination1", - source: "key1", - start: "3", - stop: "7", - options: ZRangeStoreOptions{ByScore: true}, - want: 5, - wantErr: false, - }, - { - name: "Get elements within score range with score", - preset: true, - presetValues: map[string]interface{}{ - "key2": ss.NewSortedSet([]ss.MemberParam{ + { + name: "2. Get elements within score range with score", + preset: true, + presetValue: ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, {Value: "six", Score: 6}, {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, }), + key: "zrange_key2", + start: "3", + stop: "7", + options: ZRangeOptions{ByScore: true, WithScores: true}, + want: map[string]float64{"three": 3, "four": 4, "five": 5, "six": 6, "seven": 7}, + wantErr: false, }, - destination: "destination2", - source: "key2", - start: "3", - stop: "7", - options: ZRangeStoreOptions{WithScores: true, ByScore: true}, - want: 5, - wantErr: false, - }, - { - // Get elements within score range with offset and limit. - // Offset and limit are in where we start and stop counting in the original sorted set (NOT THE RESULT). - name: "Get elements within score range with offset and limit", - preset: true, - presetValues: map[string]interface{}{ - "key3": ss.NewSortedSet([]ss.MemberParam{ + { + // Get elements within score range with offset and limit. + // Offset and limit are in where we start and stop counting in the original sorted set (NOT THE RESULT). + name: "3. Get elements within score range with offset and limit", + preset: true, + presetValue: ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, {Value: "six", Score: 6}, {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, }), + key: "zrange_key3", + start: "3", + stop: "7", + options: ZRangeOptions{WithScores: true, ByScore: true, Offset: 2, Count: 4}, + want: map[string]float64{"three": 3, "four": 4, "five": 5}, + wantErr: false, }, - destination: "destination3", - source: "key3", - start: "3", - stop: "7", - options: ZRangeStoreOptions{ByScore: true, WithScores: true, Offset: 2, Count: 4}, - want: 3, - wantErr: false, - }, - { - name: "Get elements within lex range without score", - preset: true, - presetValues: map[string]interface{}{ - "key4": ss.NewSortedSet([]ss.MemberParam{ + { + name: "4. Get elements within lex range without score", + preset: true, + presetValue: ss.NewSortedSet([]ss.MemberParam{ {Value: "a", Score: 1}, {Value: "e", Score: 1}, {Value: "b", Score: 1}, {Value: "f", Score: 1}, {Value: "c", Score: 1}, {Value: "g", Score: 1}, {Value: "d", Score: 1}, {Value: "h", Score: 1}, }), + key: "zrange_key4", + start: "c", + stop: "g", + options: ZRangeOptions{ByLex: true}, + want: map[string]float64{"c": 0, "d": 0, "e": 0, "f": 0, "g": 0}, + wantErr: false, }, - destination: "destination4", - source: "key4", - start: "c", - stop: "g", - options: ZRangeStoreOptions{ByLex: true}, - want: 5, - wantErr: false, - }, - { - name: "Get elements within lex range with score", - preset: true, - presetValues: map[string]interface{}{ - "key5": ss.NewSortedSet([]ss.MemberParam{ + { + name: "5. Get elements within lex range with score", + preset: true, + presetValue: ss.NewSortedSet([]ss.MemberParam{ {Value: "a", Score: 1}, {Value: "e", Score: 1}, {Value: "b", Score: 1}, {Value: "f", Score: 1}, {Value: "c", Score: 1}, {Value: "g", Score: 1}, {Value: "d", Score: 1}, {Value: "h", Score: 1}, }), + key: "zrange_key5", + start: "a", + stop: "f", + options: ZRangeOptions{ByLex: true, WithScores: true}, + want: map[string]float64{"a": 1, "b": 1, "c": 1, "d": 1, "e": 1, "f": 1}, + wantErr: false, }, - destination: "destination5", - source: "key5", - start: "a", - stop: "f", - options: ZRangeStoreOptions{ByLex: true, WithScores: true}, - want: 6, - wantErr: false, - }, - { - // Get elements within lex range with offset and limit. - // Offset and limit are in where we start and stop counting in the original sorted set (NOT THE RESULT). - name: "Get elements within lex range with offset and limit", - preset: true, - presetValues: map[string]interface{}{ - "key6": ss.NewSortedSet([]ss.MemberParam{ + { + // Get elements within lex range with offset and limit. + // Offset and limit are in where we start and stop counting in the original sorted set (NOT THE RESULT). + name: "6. Get elements within lex range with offset and limit", + preset: true, + presetValue: ss.NewSortedSet([]ss.MemberParam{ {Value: "a", Score: 1}, {Value: "b", Score: 1}, {Value: "c", Score: 1}, {Value: "d", Score: 1}, {Value: "e", Score: 1}, {Value: "f", Score: 1}, {Value: "g", Score: 1}, {Value: "h", Score: 1}, }), + key: "zrange_key6", + start: "a", + stop: "h", + options: ZRangeOptions{WithScores: true, ByLex: true, Offset: 2, Count: 4}, + want: map[string]float64{"c": 1, "d": 1, "e": 1}, + wantErr: false, }, - destination: "destination6", - source: "key6", - start: "a", - stop: "h", - options: ZRangeStoreOptions{WithScores: true, ByLex: true, Offset: 2, Count: 4}, - want: 3, - wantErr: false, - }, - { - // Get elements within lex range with offset and limit + reverse the results. - // Offset and limit are in where we start and stop counting in the original sorted set (NOT THE RESULT). - // REV reverses the original set before getting the range. - name: "Get elements within lex range with offset and limit + reverse the results", - preset: true, - presetValues: map[string]interface{}{ - "key7": ss.NewSortedSet([]ss.MemberParam{ - {Value: "a", Score: 1}, {Value: "b", Score: 1}, - {Value: "c", Score: 1}, {Value: "d", Score: 1}, - {Value: "e", Score: 1}, {Value: "f", Score: 1}, - {Value: "g", Score: 1}, {Value: "h", Score: 1}, - }), - }, - destination: "destination7", - source: "key7", - start: "a", - stop: "h", - options: ZRangeStoreOptions{WithScores: true, ByLex: true, Offset: 2, Count: 4}, - want: 3, - wantErr: false, - }, - { - name: "Return an empty slice when we use BYLEX while elements have different scores", - preset: true, - presetValues: map[string]interface{}{ - "key8": ss.NewSortedSet([]ss.MemberParam{ + { + name: "7. Return an empty map when we use BYLEX while elements have different scores", + preset: true, + presetValue: ss.NewSortedSet([]ss.MemberParam{ {Value: "a", Score: 1}, {Value: "b", Score: 5}, {Value: "c", Score: 2}, {Value: "d", Score: 6}, {Value: "e", Score: 3}, {Value: "f", Score: 7}, {Value: "g", Score: 4}, {Value: "h", Score: 8}, }), + key: "zrange_key7", + start: "a", + stop: "h", + options: ZRangeOptions{WithScores: true, ByLex: true, Offset: 2, Count: 4}, + want: map[string]float64{}, + wantErr: false, }, - destination: "destination8", - source: "key8", - start: "a", - stop: "h", - options: ZRangeStoreOptions{WithScores: true, ByLex: true, Offset: 2, Count: 4}, - want: 0, - wantErr: false, - }, - { - name: "Throw error when the key does not hold a sorted set", - preset: true, - presetValues: map[string]interface{}{ - "key9": "Default value", + { + name: "8. Throw error when the key does not hold a sorted set", + preset: true, + presetValue: "Default value", + key: "zrange_key10", + start: "a", + stop: "h", + options: ZRangeOptions{WithScores: true, ByLex: true, Offset: 2, Count: 4}, + want: nil, + wantErr: true, }, - destination: "destination9", - source: "key9", - start: "a", - stop: "h", - options: ZRangeStoreOptions{WithScores: true, ByLex: true, Offset: 2, Count: 4}, - want: 0, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.preset { - for k, v := range tt.presetValues { - err := presetValue(server, context.Background(), k, v) + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.preset { + err := presetValue(server, context.Background(), tt.key, tt.presetValue) if err != nil { t.Error(err) return } } - } - got, err := server.ZRangeStore(tt.destination, tt.source, tt.start, tt.stop, tt.options) - if (err != nil) != tt.wantErr { - t.Errorf("ZRANGESTORE() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("ZRANGESTORE() got = %v, want %v", got, tt.want) - } - }) - } -} - -func TestSugarDB_ZRANK(t *testing.T) { - server := createSugarDB() - - tests := []struct { - name string - preset bool - presetValue interface{} - key string - member string - withscores bool - want map[int]float64 - wantErr bool - }{ - { - name: "1. Return element's rank from a sorted set", - preset: true, - presetValue: ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, - }), - key: "key1", - member: "four", - withscores: false, - want: map[int]float64{3: 0}, - wantErr: false, - }, - { - name: "2. Return element's rank from a sorted set with its score", - preset: true, - presetValue: ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 100.1}, {Value: "two", Score: 245}, - {Value: "three", Score: 305.43}, {Value: "four", Score: 411.055}, - {Value: "five", Score: 500}, - }), - key: "key2", - member: "four", - withscores: true, - want: map[int]float64{3: 411.055}, - wantErr: false, - }, - { - name: "3. If key does not exist, return nil value", - preset: false, - presetValue: nil, - key: "key3", - member: "one", - withscores: false, - want: map[int]float64{}, - wantErr: false, - }, - { - name: "4. If key exists and is a sorted set, but the member does not exist, return nil", - preset: true, - presetValue: ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1.1}, {Value: "two", Score: 245}, - {Value: "three", Score: 3}, {Value: "four", Score: 4.055}, - {Value: "five", Score: 5}, - }), - key: "key4", - member: "non-existent", - withscores: false, - want: map[int]float64{}, - wantErr: false, - }, - { - name: "5. Throw error when trying to find scores from elements that are not sorted sets", - preset: true, - presetValue: "Default value", - key: "key5", - member: "one", - withscores: false, - want: nil, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.preset { - err := presetValue(server, context.Background(), tt.key, tt.presetValue) - if err != nil { - t.Error(err) + got, err := server.ZRange(tt.key, tt.start, tt.stop, tt.options) + if (err != nil) != tt.wantErr { + t.Errorf("ZRANGE() error = %v, wantErr %v", err, tt.wantErr) return } - } - got, err := server.ZRank(tt.key, tt.member, tt.withscores) - if (err != nil) != tt.wantErr { - t.Errorf("ZRANK() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("ZRANK() got = %v, want %v", got, tt.want) - } - }) - } -} + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("ZRANGE() got = %v, want %v", got, tt.want) + } + }) + } + }) -func TestSugarDB_ZREM(t *testing.T) { - server := createSugarDB() + t.Run("TestSugarDB_ZRANGESTORE", func(t *testing.T) { + t.Parallel() - tests := []struct { - name string - preset bool - presetValue interface{} - key string - members []string - want int - wantErr bool - }{ - { - // Successfully remove multiple elements from sorted set, skipping non-existent members. - // Return deleted count. - name: "Successfully remove multiple elements from sorted set, skipping non-existent members", - preset: true, - presetValue: ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, - {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, - }), - key: "key1", - members: []string{"three", "four", "five", "none", "six", "none", "seven"}, - want: 5, - wantErr: false, - }, - { - name: "If key does not exist, return 0", - preset: false, - presetValue: nil, - key: "key2", - members: []string{"member"}, - want: 0, - wantErr: false, - }, - { - name: "Return error key is not a sorted set", - preset: true, - presetValue: "Default value", - key: "key3", - members: []string{"member"}, - want: 0, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.preset { - err := presetValue(server, context.Background(), tt.key, tt.presetValue) - if err != nil { - t.Error(err) + tests := []struct { + name string + preset bool + presetValues map[string]interface{} + destination string + source string + start string + stop string + options ZRangeStoreOptions + want int + wantErr bool + }{ + { + name: "1. Get elements within score range without score", + preset: true, + presetValues: map[string]interface{}{ + "zrangestore_key1": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + }), + }, + destination: "zrangestore_destination1", + source: "zrangestore_key1", + start: "3", + stop: "7", + options: ZRangeStoreOptions{ByScore: true}, + want: 5, + wantErr: false, + }, + { + name: "2. Get elements within score range with score", + preset: true, + presetValues: map[string]interface{}{ + "zrangestore_key2": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + }), + }, + destination: "zrangestore_destination2", + source: "zrangestore_key2", + start: "3", + stop: "7", + options: ZRangeStoreOptions{WithScores: true, ByScore: true}, + want: 5, + wantErr: false, + }, + { + // Get elements within score range with offset and limit. + // Offset and limit are in where we start and stop counting in the original sorted set (NOT THE RESULT). + name: "3. Get elements within score range with offset and limit", + preset: true, + presetValues: map[string]interface{}{ + "zrangestore_key3": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + }), + }, + destination: "zrangestore_destination3", + source: "zrangestore_key3", + start: "3", + stop: "7", + options: ZRangeStoreOptions{ByScore: true, WithScores: true, Offset: 2, Count: 4}, + want: 3, + wantErr: false, + }, + { + name: "4. Get elements within lex range without score", + preset: true, + presetValues: map[string]interface{}{ + "zrangestore_key4": ss.NewSortedSet([]ss.MemberParam{ + {Value: "a", Score: 1}, {Value: "e", Score: 1}, + {Value: "b", Score: 1}, {Value: "f", Score: 1}, + {Value: "c", Score: 1}, {Value: "g", Score: 1}, + {Value: "d", Score: 1}, {Value: "h", Score: 1}, + }), + }, + destination: "zrangestore_destination4", + source: "zrangestore_key4", + start: "c", + stop: "g", + options: ZRangeStoreOptions{ByLex: true}, + want: 5, + wantErr: false, + }, + { + name: "5. Get elements within lex range with score", + preset: true, + presetValues: map[string]interface{}{ + "zrangestore_key5": ss.NewSortedSet([]ss.MemberParam{ + {Value: "a", Score: 1}, {Value: "e", Score: 1}, + {Value: "b", Score: 1}, {Value: "f", Score: 1}, + {Value: "c", Score: 1}, {Value: "g", Score: 1}, + {Value: "d", Score: 1}, {Value: "h", Score: 1}, + }), + }, + destination: "zrangestore_destination5", + source: "zrangestore_key5", + start: "a", + stop: "f", + options: ZRangeStoreOptions{ByLex: true, WithScores: true}, + want: 6, + wantErr: false, + }, + { + // Get elements within lex range with offset and limit. + // Offset and limit are in where we start and stop counting in the original sorted set (NOT THE RESULT). + name: "6. Get elements within lex range with offset and limit", + preset: true, + presetValues: map[string]interface{}{ + "zrangestore_key6": ss.NewSortedSet([]ss.MemberParam{ + {Value: "a", Score: 1}, {Value: "b", Score: 1}, + {Value: "c", Score: 1}, {Value: "d", Score: 1}, + {Value: "e", Score: 1}, {Value: "f", Score: 1}, + {Value: "g", Score: 1}, {Value: "h", Score: 1}, + }), + }, + destination: "zrangestore_destination6", + source: "zrangestore_key6", + start: "a", + stop: "h", + options: ZRangeStoreOptions{WithScores: true, ByLex: true, Offset: 2, Count: 4}, + want: 3, + wantErr: false, + }, + { + // Get elements within lex range with offset and limit + reverse the results. + // Offset and limit are in where we start and stop counting in the original sorted set (NOT THE RESULT). + // REV reverses the original set before getting the range. + name: "7. Get elements within lex range with offset and limit + reverse the results", + preset: true, + presetValues: map[string]interface{}{ + "zrangestore_key7": ss.NewSortedSet([]ss.MemberParam{ + {Value: "a", Score: 1}, {Value: "b", Score: 1}, + {Value: "c", Score: 1}, {Value: "d", Score: 1}, + {Value: "e", Score: 1}, {Value: "f", Score: 1}, + {Value: "g", Score: 1}, {Value: "h", Score: 1}, + }), + }, + destination: "zrangestore_destination7", + source: "zrangestore_key7", + start: "a", + stop: "h", + options: ZRangeStoreOptions{WithScores: true, ByLex: true, Offset: 2, Count: 4}, + want: 3, + wantErr: false, + }, + { + name: "8. Return an empty slice when we use BYLEX while elements have different scores", + preset: true, + presetValues: map[string]interface{}{ + "zrangestore_key8": ss.NewSortedSet([]ss.MemberParam{ + {Value: "a", Score: 1}, {Value: "b", Score: 5}, + {Value: "c", Score: 2}, {Value: "d", Score: 6}, + {Value: "e", Score: 3}, {Value: "f", Score: 7}, + {Value: "g", Score: 4}, {Value: "h", Score: 8}, + }), + }, + destination: "zrangestore_destination8", + source: "zrangestore_key8", + start: "a", + stop: "h", + options: ZRangeStoreOptions{WithScores: true, ByLex: true, Offset: 2, Count: 4}, + want: 0, + wantErr: false, + }, + { + name: "9. Throw error when the key does not hold a sorted set", + preset: true, + presetValues: map[string]interface{}{ + "zrangestore_key9": "Default value", + }, + destination: "zrangestore_destination9", + source: "zrangestore_key9", + start: "a", + stop: "h", + options: ZRangeStoreOptions{WithScores: true, ByLex: true, Offset: 2, Count: 4}, + want: 0, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.preset { + for k, v := range tt.presetValues { + err := presetValue(server, context.Background(), k, v) + if err != nil { + t.Error(err) + return + } + } + } + got, err := server.ZRangeStore(tt.destination, tt.source, tt.start, tt.stop, tt.options) + if (err != nil) != tt.wantErr { + t.Errorf("ZRANGESTORE() error = %v, wantErr %v", err, tt.wantErr) return } - } - got, err := server.ZRem(tt.key, tt.members...) - if (err != nil) != tt.wantErr { - t.Errorf("ZREM() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("ZREM() got = %v, want %v", got, tt.want) - } - }) - } -} - -func TestSugarDB_ZREMRANGEBYSCORE(t *testing.T) { - server := createSugarDB() - - tests := []struct { - name string - preset bool - presetValue interface{} - key string - min float64 - max float64 - want int - wantErr bool - }{ - { - name: "Successfully remove multiple elements with scores inside the provided range", - preset: true, - presetValue: ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, - {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, - }), - key: "key1", - min: 3, - max: 7, - want: 5, - wantErr: false, - }, - { - name: "If key does not exist, return 0", - preset: false, - key: "key2", - min: 2, - max: 4, - want: 0, - wantErr: false, - }, - { - name: "Return error key is not a sorted set", - preset: true, - presetValue: "Default value", - key: "key3", - min: 2, - max: 4, - want: 0, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.preset { - err := presetValue(server, context.Background(), tt.key, tt.presetValue) - if err != nil { - t.Error(err) - return + if got != tt.want { + t.Errorf("ZRANGESTORE() got = %v, want %v", got, tt.want) } - } - got, err := server.ZRemRangeByScore(tt.key, tt.min, tt.max) - if (err != nil) != tt.wantErr { - t.Errorf("ZREMRANGEBYSCORE() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("ZREMRANGEBYSCORE() got = %v, want %v", got, tt.want) - } - }) - } -} + }) + } + }) -func TestSugarDB_ZSCORE(t *testing.T) { - server := createSugarDB() + t.Run("TestSugarDB_ZRANK", func(t *testing.T) { + t.Parallel() - tests := []struct { - name string - preset bool - presetValue interface{} - key string - member string - want interface{} - wantErr bool - }{ - { - name: "Return score from a sorted set", - preset: true, - presetValue: ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1.1}, {Value: "two", Score: 245}, - {Value: "three", Score: 3}, {Value: "four", Score: 4.055}, - {Value: "five", Score: 5}, - }), - key: "key1", - member: "four", - want: 4.055, - wantErr: false, - }, - { - name: "If key does not exist, return nil value", - preset: false, - presetValue: nil, - key: "key2", - member: "one", - want: nil, - wantErr: false, - }, - { - name: "If key exists and is a sorted set, but the member does not exist, return nil", - preset: true, - presetValue: ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1.1}, {Value: "two", Score: 245}, - {Value: "three", Score: 3}, {Value: "four", Score: 4.055}, - {Value: "five", Score: 5}, - }), - key: "key3", - member: "non-existent", - want: nil, - wantErr: false, - }, - { - name: "Throw error when trying to find scores from elements that are not sorted sets", - preset: true, - presetValue: "Default value", - key: "key4", - member: "one", - want: 0, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.preset { - err := presetValue(server, context.Background(), tt.key, tt.presetValue) - if err != nil { - t.Error(err) - return - } - } - got, err := server.ZScore(tt.key, tt.member) - if (err != nil) != tt.wantErr { - t.Errorf("ZSCORE() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("ZSCORE() got = %v, want %v", got, tt.want) - } - }) - } -} - -func TestSugarDB_ZUNION(t *testing.T) { - server := createSugarDB() - - tests := []struct { - name string - preset bool - presetValues map[string]interface{} - keys []string - options ZUnionOptions - want map[string]float64 - wantErr bool - }{ - { - name: "Get the union between 2 sorted sets", - preset: true, - presetValues: map[string]interface{}{ - "key1": ss.NewSortedSet([]ss.MemberParam{ + tests := []struct { + name string + preset bool + presetValue interface{} + key string + member string + withscores bool + want map[int]float64 + wantErr bool + }{ + { + name: "1. Return element's rank from a sorted set", + preset: true, + presetValue: ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, }), - "key2": ss.NewSortedSet([]ss.MemberParam{ - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, - }), + key: "zrank_key1", + member: "four", + withscores: false, + want: map[int]float64{3: 0}, + wantErr: false, }, - keys: []string{"key1", "key2"}, - options: ZUnionOptions{}, - want: map[string]float64{ - "one": 0, "two": 0, "three": 0, "four": 0, - "five": 0, "six": 0, "seven": 0, "eight": 0, + { + name: "2. Return element's rank from a sorted set with its score", + preset: true, + presetValue: ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 100.1}, {Value: "two", Score: 245}, + {Value: "three", Score: 305.43}, {Value: "four", Score: 411.055}, + {Value: "five", Score: 500}, + }), + key: "zrank_key2", + member: "four", + withscores: true, + want: map[int]float64{3: 411.055}, + wantErr: false, }, - wantErr: false, - }, - { - // Get the union between 3 sorted sets with scores. - // By default, the SUM aggregate will be used. - name: "Get the union between 3 sorted sets with scores", - preset: true, - presetValues: map[string]interface{}{ - "key3": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, - }), - "key4": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, - {Value: "eleven", Score: 11}, {Value: "eight", Score: 8}, - }), - "key5": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "eight", Score: 8}, - {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, - {Value: "twelve", Score: 12}, {Value: "thirty-six", Score: 36}, - }), + { + name: "3. If key does not exist, return nil value", + preset: false, + presetValue: nil, + key: "zrank_key3", + member: "one", + withscores: false, + want: map[int]float64{}, + wantErr: false, }, - keys: []string{"key3", "key4", "key5"}, - options: ZUnionOptions{WithScores: true}, - want: map[string]float64{ - "one": 3, "two": 4, "three": 3, "four": 4, "five": 5, "six": 6, "seven": 7, "eight": 24, "nine": 9, - "ten": 10, "eleven": 11, "twelve": 24, "thirty-six": 72, + { + name: "4. If key exists and is a sorted set, but the member does not exist, return nil", + preset: true, + presetValue: ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1.1}, {Value: "two", Score: 245}, + {Value: "three", Score: 3}, {Value: "four", Score: 4.055}, + {Value: "five", Score: 5}, + }), + key: "zrank_key4", + member: "non-existent", + withscores: false, + want: map[int]float64{}, + wantErr: false, }, - wantErr: false, - }, - { - // Get the union between 3 sorted sets with scores. - // Use MIN aggregate. - name: "Get the union between 3 sorted sets with scores", - preset: true, - presetValues: map[string]interface{}{ - "key6": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 100}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, - }), - "key7": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, - {Value: "eleven", Score: 11}, {Value: "eight", Score: 80}, - }), - "key8": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, - {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, - {Value: "twelve", Score: 12}, {Value: "thirty-six", Score: 72}, - }), + { + name: "5. Throw error when trying to find scores from elements that are not sorted sets", + preset: true, + presetValue: "Default value", + key: "zrank_key5", + member: "one", + withscores: false, + want: nil, + wantErr: true, }, - keys: []string{"key6", "key7", "key8"}, - options: ZUnionOptions{WithScores: true, Aggregate: "MIN"}, - want: map[string]float64{ - "one": 1, "two": 2, "three": 3, "four": 4, "five": 5, "six": 6, "seven": 7, "eight": 8, "nine": 9, - "ten": 10, "eleven": 11, "twelve": 12, "thirty-six": 36, - }, - wantErr: false, - }, - { - // Get the union between 3 sorted sets with scores. - // Use MAX aggregate. - name: "Get the union between 3 sorted sets with scores", - preset: true, - presetValues: map[string]interface{}{ - "key9": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 100}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, - }), - "key10": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, - {Value: "eleven", Score: 11}, {Value: "eight", Score: 80}, - }), - "key11": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, - {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, - {Value: "twelve", Score: 12}, {Value: "thirty-six", Score: 72}, - }), - }, - keys: []string{"key9", "key10", "key11"}, - options: ZUnionOptions{WithScores: true, Aggregate: "MAX"}, - want: map[string]float64{ - "one": 1000, "two": 2, "three": 3, "four": 4, "five": 5, "six": 6, "seven": 7, "eight": 800, "nine": 9, - "ten": 10, "eleven": 11, "twelve": 12, "thirty-six": 72, - }, - wantErr: false, - }, - { - // Get the union between 3 sorted sets with scores. - // Use SUM aggregate with weights modifier. - name: "Get the union between 3 sorted sets with scores", - preset: true, - presetValues: map[string]interface{}{ - "key12": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 100}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, - }), - "key13": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, - {Value: "eleven", Score: 11}, {Value: "eight", Score: 80}, - }), - "key14": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, - {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, - {Value: "twelve", Score: 12}, - }), - }, - keys: []string{"key12", "key13", "key14"}, - options: ZUnionOptions{WithScores: true, Aggregate: "SUM", Weights: []float64{1, 2, 3}}, - want: map[string]float64{ - "one": 3102, "two": 6, "three": 3, "four": 4, "five": 5, "six": 6, "seven": 7, "eight": 2568, - "nine": 27, "ten": 30, "eleven": 22, "twelve": 60, "thirty-six": 72, - }, - wantErr: false, - }, - { - // Get the union between 3 sorted sets with scores. - // Use MAX aggregate with added weights. - name: "Get the union between 3 sorted sets with scores", - preset: true, - presetValues: map[string]interface{}{ - "key15": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 100}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, - }), - "key16": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, - {Value: "eleven", Score: 11}, {Value: "eight", Score: 80}, - }), - "key17": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, - {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, - {Value: "twelve", Score: 12}, - }), - }, - keys: []string{"key15", "key16", "key17"}, - options: ZUnionOptions{WithScores: true, Aggregate: "MAX", Weights: []float64{1, 2, 3}}, - want: map[string]float64{ - "one": 3000, "two": 4, "three": 3, "four": 4, "five": 5, "six": 6, "seven": 7, "eight": 2400, - "nine": 27, "ten": 30, "eleven": 22, "twelve": 36, "thirty-six": 72, - }, - wantErr: false, - }, - { - // Get the union between 3 sorted sets with scores. - // Use MIN aggregate with added weights. - name: "Get the union between 3 sorted sets with scores", - preset: true, - presetValues: map[string]interface{}{ - "key18": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 100}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, - }), - "key19": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, - {Value: "eleven", Score: 11}, {Value: "eight", Score: 80}, - }), - "key20": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, - {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, - {Value: "twelve", Score: 12}, - }), - }, - keys: []string{"key18", "key19", "key20"}, - options: ZUnionOptions{WithScores: true, Aggregate: "MIN", Weights: []float64{1, 2, 3}}, - want: map[string]float64{ - "one": 2, "two": 2, "three": 3, "four": 4, "five": 5, "six": 6, "seven": 7, "eight": 8, "nine": 27, - "ten": 30, "eleven": 22, "twelve": 24, "thirty-six": 72, - }, - wantErr: false, - }, - { - name: "Throw an error if there are more weights than keys", - preset: true, - presetValues: map[string]interface{}{ - "key21": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, - }), - "key22": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), - }, - keys: []string{"key21", "key22"}, - options: ZUnionOptions{Weights: []float64{1, 2, 3}}, - want: nil, - wantErr: true, - }, - { - name: "Throw an error if there are fewer weights than keys", - preset: true, - presetValues: map[string]interface{}{ - "key23": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, - }), - "key24": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - }), - "key25": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), - }, - keys: []string{"key23", "key24", "key25"}, - options: ZUnionOptions{Weights: []float64{5, 4}}, - want: nil, - wantErr: true, - }, - { - name: "Throw an error if there are no keys provided", - preset: true, - presetValues: map[string]interface{}{ - "key26": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), - "key27": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), - "key28": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), - }, - keys: []string{}, - options: ZUnionOptions{Weights: []float64{5, 4}}, - want: nil, - wantErr: true, - }, - { - name: "Throw an error if any of the provided keys are not sorted sets", - preset: true, - presetValues: map[string]interface{}{ - "key29": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, - }), - "key30": "Default value", - "key31": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), - }, - keys: []string{"key29", "key30", "key31"}, - options: ZUnionOptions{}, - want: nil, - wantErr: true, - }, - { - name: "If any of the keys does not exist, skip it", - preset: true, - presetValues: map[string]interface{}{ - "key32": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, - {Value: "eleven", Score: 11}, - }), - "key33": ss.NewSortedSet([]ss.MemberParam{ - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, - {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, - {Value: "twelve", Score: 12}, - }), - }, - keys: []string{"non-existent", "key32", "key33"}, - options: ZUnionOptions{}, - want: map[string]float64{ - "one": 0, "two": 0, "thirty-six": 0, "twelve": 0, "eleven": 0, - "seven": 0, "eight": 0, "nine": 0, "ten": 0, - }, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.preset { - for k, v := range tt.presetValues { - err := presetValue(server, context.Background(), k, v) + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.preset { + err := presetValue(server, context.Background(), tt.key, tt.presetValue) if err != nil { t.Error(err) return } } - } - got, err := server.ZUnion(tt.keys, tt.options) - if (err != nil) != tt.wantErr { - t.Errorf("ZUNION() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("ZUNION() got = %v, want %v", got, tt.want) - } - }) - } -} + got, err := server.ZRank(tt.key, tt.member, tt.withscores) + if (err != nil) != tt.wantErr { + t.Errorf("ZRANK() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("ZRANK() got = %v, want %v", got, tt.want) + } + }) + } + }) -func TestSugarDB_ZUNIONSTORE(t *testing.T) { - server := createSugarDB() + t.Run("TestSugarDB_ZREM", func(t *testing.T) { + t.Parallel() - tests := []struct { - name string - preset bool - presetValues map[string]interface{} - destination string - keys []string - options ZUnionStoreOptions - want int - wantErr bool - }{ - { - name: "Get the union between 2 sorted sets", - preset: true, - presetValues: map[string]interface{}{ - "key1": ss.NewSortedSet([]ss.MemberParam{ + tests := []struct { + name string + preset bool + presetValue interface{} + key string + members []string + want int + wantErr bool + }{ + { + // Successfully remove multiple elements from sorted set, skipping non-existent members. + // Return deleted count. + name: "1. Successfully remove multiple elements from sorted set, skipping non-existent members", + preset: true, + presetValue: ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, + }), + key: "zrem_key1", + members: []string{"three", "four", "five", "none", "six", "none", "seven"}, + want: 5, + wantErr: false, + }, + { + name: "2. If key does not exist, return 0", + preset: false, + presetValue: nil, + key: "zrem_key2", + members: []string{"member"}, + want: 0, + wantErr: false, + }, + { + name: "3. Return error key is not a sorted set", + preset: true, + presetValue: "Default value", + key: "zrem_key3", + members: []string{"member"}, + want: 0, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.preset { + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } + } + got, err := server.ZRem(tt.key, tt.members...) + if (err != nil) != tt.wantErr { + t.Errorf("ZREM() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("ZREM() got = %v, want %v", got, tt.want) + } + }) + } + }) + + t.Run("TestSugarDB_ZREMRANGEBYSCORE", func(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + preset bool + presetValue interface{} + key string + min float64 + max float64 + want int + wantErr bool + }{ + { + name: "1. Successfully remove multiple elements with scores inside the provided range", + preset: true, + presetValue: ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, + }), + key: "zremrangebyscore_key1", + min: 3, + max: 7, + want: 5, + wantErr: false, + }, + { + name: "2. If key does not exist, return 0", + preset: false, + key: "zremrangebyscore_key2", + min: 2, + max: 4, + want: 0, + wantErr: false, + }, + { + name: "3. Return error key is not a sorted set", + preset: true, + presetValue: "Default value", + key: "zremrangebyscore_key3", + min: 2, + max: 4, + want: 0, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.preset { + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } + } + got, err := server.ZRemRangeByScore(tt.key, tt.min, tt.max) + if (err != nil) != tt.wantErr { + t.Errorf("ZREMRANGEBYSCORE() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("ZREMRANGEBYSCORE() got = %v, want %v", got, tt.want) + } + }) + } + }) + + t.Run("TestSugarDB_ZSCORE", func(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + preset bool + presetValue interface{} + key string + member string + want interface{} + wantErr bool + }{ + { + name: "1. Return score from a sorted set", + preset: true, + presetValue: ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1.1}, {Value: "two", Score: 245}, + {Value: "three", Score: 3}, {Value: "four", Score: 4.055}, + {Value: "five", Score: 5}, + }), + key: "zscore_key1", + member: "four", + want: 4.055, + wantErr: false, + }, + { + name: "2. If key does not exist, return nil value", + preset: false, + presetValue: nil, + key: "zscore_key2", + member: "one", + want: nil, + wantErr: false, + }, + { + name: "3. If key exists and is a sorted set, but the member does not exist, return nil", + preset: true, + presetValue: ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1.1}, {Value: "two", Score: 245}, + {Value: "three", Score: 3}, {Value: "four", Score: 4.055}, + {Value: "five", Score: 5}, + }), + key: "zscore_key3", + member: "non-existent", + want: nil, + wantErr: false, + }, + { + name: "4. Throw error when trying to find scores from elements that are not sorted sets", + preset: true, + presetValue: "Default value", + key: "zscore_key4", + member: "one", + want: 0, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.preset { + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } + } + got, err := server.ZScore(tt.key, tt.member) + if (err != nil) != tt.wantErr { + t.Errorf("ZSCORE() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("ZSCORE() got = %v, want %v", got, tt.want) + } + }) + } + }) + + t.Run("TestSugarDB_ZUNION", func(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + preset bool + presetValues map[string]interface{} + keys []string + options ZUnionOptions + want map[string]float64 + wantErr bool + }{ + { + name: "1. Get the union between 2 sorted sets", + preset: true, + presetValues: map[string]interface{}{ + "zunion_key1": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, + }), + "zunion_key2": ss.NewSortedSet([]ss.MemberParam{ + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + }), + }, + keys: []string{"zunion_key1", "zunion_key2"}, + options: ZUnionOptions{}, + want: map[string]float64{ + "one": 0, "two": 0, "three": 0, "four": 0, + "five": 0, "six": 0, "seven": 0, "eight": 0, + }, + wantErr: false, + }, + { + // Get the union between 3 sorted sets with scores. + // By default, the SUM aggregate will be used. + name: "2. Get the union between 3 sorted sets with scores", + preset: true, + presetValues: map[string]interface{}{ + "zunion_key3": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + }), + "zunion_key4": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, + {Value: "eleven", Score: 11}, {Value: "eight", Score: 8}, + }), + "zunion_key5": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "eight", Score: 8}, + {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, + {Value: "twelve", Score: 12}, {Value: "thirty-six", Score: 36}, + }), + }, + keys: []string{"zunion_key3", "zunion_key4", "zunion_key5"}, + options: ZUnionOptions{WithScores: true}, + want: map[string]float64{ + "one": 3, "two": 4, "three": 3, "four": 4, "five": 5, "six": 6, "seven": 7, "eight": 24, "nine": 9, + "ten": 10, "eleven": 11, "twelve": 24, "thirty-six": 72, + }, + wantErr: false, + }, + { + // Get the union between 3 sorted sets with scores. + // Use MIN aggregate. + name: "3. Get the union between 3 sorted sets with scores", + preset: true, + presetValues: map[string]interface{}{ + "zunion_key6": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 100}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + }), + "zunion_key7": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, + {Value: "eleven", Score: 11}, {Value: "eight", Score: 80}, + }), + "zunion_key8": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, + {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, + {Value: "twelve", Score: 12}, {Value: "thirty-six", Score: 72}, + }), + }, + keys: []string{"zunion_key6", "zunion_key7", "zunion_key8"}, + options: ZUnionOptions{WithScores: true, Aggregate: "MIN"}, + want: map[string]float64{ + "one": 1, "two": 2, "three": 3, "four": 4, "five": 5, "six": 6, "seven": 7, "eight": 8, "nine": 9, + "ten": 10, "eleven": 11, "twelve": 12, "thirty-six": 36, + }, + wantErr: false, + }, + { + // Get the union between 3 sorted sets with scores. + // Use MAX aggregate. + name: "4. Get the union between 3 sorted sets with scores", + preset: true, + presetValues: map[string]interface{}{ + "zunion_key9": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 100}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + }), + "zunion_key10": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, + {Value: "eleven", Score: 11}, {Value: "eight", Score: 80}, + }), + "zunion_key11": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, + {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, + {Value: "twelve", Score: 12}, {Value: "thirty-six", Score: 72}, + }), + }, + keys: []string{"zunion_key9", "zunion_key10", "zunion_key11"}, + options: ZUnionOptions{WithScores: true, Aggregate: "MAX"}, + want: map[string]float64{ + "one": 1000, "two": 2, "three": 3, "four": 4, "five": 5, "six": 6, "seven": 7, "eight": 800, "nine": 9, + "ten": 10, "eleven": 11, "twelve": 12, "thirty-six": 72, + }, + wantErr: false, + }, + { + // Get the union between 3 sorted sets with scores. + // Use SUM aggregate with weights modifier. + name: "5. Get the union between 3 sorted sets with scores", + preset: true, + presetValues: map[string]interface{}{ + "zunion_key12": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 100}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + }), + "zunion_key13": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, + {Value: "eleven", Score: 11}, {Value: "eight", Score: 80}, + }), + "zunion_key14": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, + {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, + {Value: "twelve", Score: 12}, + }), + }, + keys: []string{"zunion_key12", "zunion_key13", "zunion_key14"}, + options: ZUnionOptions{WithScores: true, Aggregate: "SUM", Weights: []float64{1, 2, 3}}, + want: map[string]float64{ + "one": 3102, "two": 6, "three": 3, "four": 4, "five": 5, "six": 6, "seven": 7, "eight": 2568, + "nine": 27, "ten": 30, "eleven": 22, "twelve": 60, "thirty-six": 72, + }, + wantErr: false, + }, + { + // Get the union between 3 sorted sets with scores. + // Use MAX aggregate with added weights. + name: "6. Get the union between 3 sorted sets with scores", + preset: true, + presetValues: map[string]interface{}{ + "zunion_key15": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 100}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + }), + "zunion_key16": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, + {Value: "eleven", Score: 11}, {Value: "eight", Score: 80}, + }), + "zunion_key17": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, + {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, + {Value: "twelve", Score: 12}, + }), + }, + keys: []string{"zunion_key15", "zunion_key16", "zunion_key17"}, + options: ZUnionOptions{WithScores: true, Aggregate: "MAX", Weights: []float64{1, 2, 3}}, + want: map[string]float64{ + "one": 3000, "two": 4, "three": 3, "four": 4, "five": 5, "six": 6, "seven": 7, "eight": 2400, + "nine": 27, "ten": 30, "eleven": 22, "twelve": 36, "thirty-six": 72, + }, + wantErr: false, + }, + { + // Get the union between 3 sorted sets with scores. + // Use MIN aggregate with added weights. + name: "7. Get the union between 3 sorted sets with scores", + preset: true, + presetValues: map[string]interface{}{ + "zunion_key18": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 100}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + }), + "zunion_key19": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, + {Value: "eleven", Score: 11}, {Value: "eight", Score: 80}, + }), + "zunion_key20": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, + {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, + {Value: "twelve", Score: 12}, + }), + }, + keys: []string{"zunion_key18", "zunion_key19", "zunion_key20"}, + options: ZUnionOptions{WithScores: true, Aggregate: "MIN", Weights: []float64{1, 2, 3}}, + want: map[string]float64{ + "one": 2, "two": 2, "three": 3, "four": 4, "five": 5, "six": 6, "seven": 7, "eight": 8, "nine": 27, + "ten": 30, "eleven": 22, "twelve": 24, "thirty-six": 72, + }, + wantErr: false, + }, + { + name: "8. Throw an error if there are more weights than keys", + preset: true, + presetValues: map[string]interface{}{ + "zunion_key21": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + }), + "zunion_key22": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), + }, + keys: []string{"zunion_key21", "zunion_key22"}, + options: ZUnionOptions{Weights: []float64{1, 2, 3}}, + want: nil, + wantErr: true, + }, + { + name: "9. Throw an error if there are fewer weights than keys", + preset: true, + presetValues: map[string]interface{}{ + "zunion_key23": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + }), + "zunion_key24": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + }), + "zunion_key25": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), + }, + keys: []string{"zunion_key23", "zunion_key24", "zunion_key25"}, + options: ZUnionOptions{Weights: []float64{5, 4}}, + want: nil, + wantErr: true, + }, + { + name: "10. Throw an error if there are no keys provided", + preset: true, + presetValues: map[string]interface{}{ + "zunion_key26": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), + "zunion_key27": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), + "zunion_key28": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), + }, + keys: []string{}, + options: ZUnionOptions{Weights: []float64{5, 4}}, + want: nil, + wantErr: true, + }, + { + name: "11. Throw an error if any of the provided keys are not sorted sets", + preset: true, + presetValues: map[string]interface{}{ + "zunion_key29": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + }), + "zunion_key30": "Default value", + "zunion_key31": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), + }, + keys: []string{"zunion_key29", "zunion_key30", "zunion_key31"}, + options: ZUnionOptions{}, + want: nil, + wantErr: true, + }, + { + name: "12. If any of the keys does not exist, skip it", + preset: true, + presetValues: map[string]interface{}{ + "zunion_key32": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, + {Value: "eleven", Score: 11}, + }), + "zunion_key33": ss.NewSortedSet([]ss.MemberParam{ + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, + {Value: "twelve", Score: 12}, + }), + }, + keys: []string{"zunion_non-existent", "zunion_key32", "zunion_key33"}, + options: ZUnionOptions{}, + want: map[string]float64{ + "one": 0, "two": 0, "thirty-six": 0, "twelve": 0, "eleven": 0, + "seven": 0, "eight": 0, "nine": 0, "ten": 0, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.preset { + for k, v := range tt.presetValues { + err := presetValue(server, context.Background(), k, v) + if err != nil { + t.Error(err) + return + } + } + } + got, err := server.ZUnion(tt.keys, tt.options) + if (err != nil) != tt.wantErr { + t.Errorf("ZUNION() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("ZUNION() got = %v, want %v", got, tt.want) + } + }) + } + }) + + t.Run("TestSugarDB_ZUNIONSTORE", func(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + preset bool + presetValues map[string]interface{} + destination string + keys []string + options ZUnionStoreOptions + want int + wantErr bool + }{ + { + name: "1. Get the union between 2 sorted sets", + preset: true, + presetValues: map[string]interface{}{ + "zunionstore_key1": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, + }), + "zunionstore_key2": ss.NewSortedSet([]ss.MemberParam{ + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + }), + }, + destination: "zunionstore_destination1", + keys: []string{"zunionstore_key1", "zunionstore_key2"}, + options: ZUnionStoreOptions{}, + want: 8, + wantErr: false, + }, + { + // Get the union between 3 sorted sets with scores. + // By default, the SUM aggregate will be used. + name: "2. Get the union between 3 sorted sets with scores", + preset: true, + presetValues: map[string]interface{}{ + "zunionstore_key3": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + }), + "zunionstore_key4": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, + {Value: "eleven", Score: 11}, {Value: "eight", Score: 8}, + }), + "zunionstore_key5": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "eight", Score: 8}, + {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, + {Value: "twelve", Score: 12}, {Value: "thirty-six", Score: 36}, + }), + }, + destination: "zunionstore_destination2", + keys: []string{"zunionstore_key3", "zunionstore_key4", "zunionstore_key5"}, + options: ZUnionStoreOptions{WithScores: true}, + want: 13, + wantErr: false, + }, + { + // Get the union between 3 sorted sets with scores. + // Use MIN aggregate. + name: "3. Get the union between 3 sorted sets with scores", + preset: true, + presetValues: map[string]interface{}{ + "zunionstore_key6": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 100}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + }), + "zunionstore_key7": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, + {Value: "eleven", Score: 11}, {Value: "eight", Score: 80}, + }), + "zunionstore_key8": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, + {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, + {Value: "twelve", Score: 12}, {Value: "thirty-six", Score: 72}, + }), + }, + destination: "zunionstore_destination3", + keys: []string{"zunionstore_key6", "zunionstore_key7", "zunionstore_key8"}, + options: ZUnionStoreOptions{WithScores: true, Aggregate: "MIN"}, + want: 13, + wantErr: false, + }, + { + // Get the union between 3 sorted sets with scores. + // Use MAX aggregate. + name: "4. Get the union between 3 sorted sets with scores", + preset: true, + presetValues: map[string]interface{}{ + "zunionstore_key9": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 100}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + }), + "zunionstore_key10": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, + {Value: "eleven", Score: 11}, {Value: "eight", Score: 80}, + }), + "zunionstore_key11": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, + {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, + {Value: "twelve", Score: 12}, {Value: "thirty-six", Score: 72}, + }), + }, + destination: "zunionstore_destination4", + keys: []string{"zunionstore_key9", "zunionstore_key10", "zunionstore_key11"}, + options: ZUnionStoreOptions{WithScores: true, Aggregate: "MAX"}, + want: 13, + wantErr: false, + }, + { + // Get the union between 3 sorted sets with scores. + // Use SUM aggregate with weights modifier. + name: "5. Get the union between 3 sorted sets with scores", + preset: true, + presetValues: map[string]interface{}{ + "zunionstore_key12": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 100}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + }), + "zunionstore_key13": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, + {Value: "eleven", Score: 11}, {Value: "eight", Score: 80}, + }), + "zunionstore_key14": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, + {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, + {Value: "twelve", Score: 12}, + }), + }, + destination: "zunionstore_destination5", + keys: []string{"zunionstore_key12", "zunionstore_key13", "zunionstore_key14"}, + options: ZUnionStoreOptions{WithScores: true, Aggregate: "SUM", Weights: []float64{1, 2, 3}}, + want: 13, + wantErr: false, + }, + { + // Get the union between 3 sorted sets with scores. + // Use MAX aggregate with added weights. + name: "6. Get the union between 3 sorted sets with scores", + preset: true, + presetValues: map[string]interface{}{ + "zunionstore_key15": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 100}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + }), + "zunionstore_key16": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, + {Value: "eleven", Score: 11}, {Value: "eight", Score: 80}, + }), + "zunionstore_key17": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, + {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, + {Value: "twelve", Score: 12}, + }), + }, + destination: "zunionstore_destination6", + keys: []string{"zunionstore_key15", "zunionstore_key16", "zunionstore_key17"}, + options: ZUnionStoreOptions{WithScores: true, Aggregate: "MAX", Weights: []float64{1, 2, 3}}, + want: 13, + wantErr: false, + }, + { + // Get the union between 3 sorted sets with scores. + // Use MIN aggregate with added weights. + name: "7. Get the union between 3 sorted sets with scores", + preset: true, + presetValues: map[string]interface{}{ + "zunionstore_key18": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 100}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + }), + "zunionstore_key19": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, + {Value: "eleven", Score: 11}, {Value: "eight", Score: 80}, + }), + "zunionstore_key20": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, + {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, + {Value: "twelve", Score: 12}, + }), + }, + destination: "zunionstore_destination7", + keys: []string{"zunionstore_destination7", "zunionstore_key18", "zunionstore_key19", "zunionstore_key20"}, + options: ZUnionStoreOptions{WithScores: true, Aggregate: "MIN", Weights: []float64{1, 2, 3}}, + want: 13, + wantErr: false, + }, + { + name: "8. Throw an error if there are more weights than keys", + preset: true, + presetValues: map[string]interface{}{ + "zunionstore_key21": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + }), + "zunionstore_key22": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), + }, + destination: "zunionstore_destination8", + keys: []string{"zunionstore_key21", "zunionstore_key22"}, + options: ZUnionStoreOptions{Weights: []float64{1, 2, 3}}, + want: 0, + wantErr: true, + }, + { + name: "9. Throw an error if there are fewer weights than keys", + preset: true, + presetValues: map[string]interface{}{ + "zunionstore_key23": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + }), + "zunionstore_key24": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + }), + "zunionstore_key25": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), + }, + destination: "zunionstore_destination9", + keys: []string{"zunionstore_key23", "zunionstore_key24", "zunionstore_key25"}, + options: ZUnionStoreOptions{Weights: []float64{5, 4}}, + want: 0, + wantErr: true, + }, + { + name: "10. Throw an error if any of the provided keys are not sorted sets", + preset: true, + presetValues: map[string]interface{}{ + "zunionstore_key29": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + }), + "zunionstore_key30": "Default value", + "zunionstore_key31": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), + }, + destination: "zunionstore_destination11", + keys: []string{"zunionstore_key29", "zunionstore_key30", "zunionstore_key31"}, + options: ZUnionStoreOptions{}, + want: 0, + wantErr: true, + }, + { + name: "11. If any of the keys does not exist, skip it", + preset: true, + presetValues: map[string]interface{}{ + "zunionstore_key32": ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, + {Value: "eleven", Score: 11}, + }), + "zunionstore_key33": ss.NewSortedSet([]ss.MemberParam{ + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, + {Value: "twelve", Score: 12}, + }), + }, + destination: "zunionstore_destination12", + keys: []string{"zunionstore_non-existent", "zunionstore_key32", "zunionstore_key33"}, + want: 9, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.preset { + for k, v := range tt.presetValues { + err := presetValue(server, context.Background(), k, v) + if err != nil { + t.Error(err) + return + } + } + } + got, err := server.ZUnionStore(tt.destination, tt.keys, tt.options) + if (err != nil) != tt.wantErr { + t.Errorf("ZUNIONSTORE() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("ZUNIONSTORE() got = %v, want %v", got, tt.want) + } + }) + } + }) + + t.Run("TestSugarDB_ZRevRank", func(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + preset bool + presetValue interface{} + key string + member string + withscores bool + want map[int]float64 + wantErr bool + }{ + { + name: "1. Return element's rank from a sorted set", + preset: true, + presetValue: ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, }), - "key2": ss.NewSortedSet([]ss.MemberParam{ - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, - }), + key: "zrevrank_key1", + member: "four", + withscores: false, + want: map[int]float64{1: 0}, + wantErr: false, }, - destination: "destination1", - keys: []string{"key1", "key2"}, - options: ZUnionStoreOptions{}, - want: 8, - wantErr: false, - }, - { - // Get the union between 3 sorted sets with scores. - // By default, the SUM aggregate will be used. - name: "Get the union between 3 sorted sets with scores", - preset: true, - presetValues: map[string]interface{}{ - "key3": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, - }), - "key4": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, - {Value: "eleven", Score: 11}, {Value: "eight", Score: 8}, - }), - "key5": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "eight", Score: 8}, - {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, - {Value: "twelve", Score: 12}, {Value: "thirty-six", Score: 36}, + { + name: "2. Return element's rank from a sorted set with its score", + preset: true, + presetValue: ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 100.1}, {Value: "two", Score: 245}, + {Value: "three", Score: 305.43}, {Value: "four", Score: 411.055}, + {Value: "five", Score: 500}, }), + key: "zrevrank_key2", + member: "four", + withscores: true, + want: map[int]float64{1: 411.055}, + wantErr: false, }, - destination: "destination2", - keys: []string{"key3", "key4", "key5"}, - options: ZUnionStoreOptions{WithScores: true}, - want: 13, - wantErr: false, - }, - { - // Get the union between 3 sorted sets with scores. - // Use MIN aggregate. - name: "Get the union between 3 sorted sets with scores", - preset: true, - presetValues: map[string]interface{}{ - "key6": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 100}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, - }), - "key7": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, - {Value: "eleven", Score: 11}, {Value: "eight", Score: 80}, - }), - "key8": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, - {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, - {Value: "twelve", Score: 12}, {Value: "thirty-six", Score: 72}, - }), + { + name: "3. If key does not exist, return empty map", + preset: false, + presetValue: nil, + key: "zrevrank_key3", + member: "one", + withscores: false, + want: map[int]float64{}, + wantErr: false, }, - destination: "destination3", - keys: []string{"key6", "key7", "key8"}, - options: ZUnionStoreOptions{WithScores: true, Aggregate: "MIN"}, - want: 13, - wantErr: false, - }, - { - // Get the union between 3 sorted sets with scores. - // Use MAX aggregate. - name: "Get the union between 3 sorted sets with scores", - preset: true, - presetValues: map[string]interface{}{ - "key9": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 100}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, - }), - "key10": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, - {Value: "eleven", Score: 11}, {Value: "eight", Score: 80}, - }), - "key11": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, - {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, - {Value: "twelve", Score: 12}, {Value: "thirty-six", Score: 72}, + { + name: "4. If key exists and is a sorted set, but the member does not exist, return nil", + preset: true, + presetValue: ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1.1}, {Value: "two", Score: 245}, + {Value: "three", Score: 3}, {Value: "four", Score: 4.055}, + {Value: "five", Score: 5}, }), + key: "zrevrank_key4", + member: "non-existent", + withscores: false, + want: map[int]float64{}, + wantErr: false, }, - destination: "destination4", - keys: []string{"key9", "key10", "key11"}, - options: ZUnionStoreOptions{WithScores: true, Aggregate: "MAX"}, - want: 13, - wantErr: false, - }, - { - // Get the union between 3 sorted sets with scores. - // Use SUM aggregate with weights modifier. - name: "Get the union between 3 sorted sets with scores", - preset: true, - presetValues: map[string]interface{}{ - "key12": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 100}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, - }), - "key13": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, - {Value: "eleven", Score: 11}, {Value: "eight", Score: 80}, - }), - "key14": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, - {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, - {Value: "twelve", Score: 12}, - }), + { + name: "5. Throw error when trying to find scores from elements that are not sorted sets", + preset: true, + presetValue: "Default value", + key: "zrevrank_key5", + member: "one", + withscores: false, + want: nil, + wantErr: true, }, - destination: "destination5", - keys: []string{"key12", "key13", "key14"}, - options: ZUnionStoreOptions{WithScores: true, Aggregate: "SUM", Weights: []float64{1, 2, 3}}, - want: 13, - wantErr: false, - }, - { - // Get the union between 3 sorted sets with scores. - // Use MAX aggregate with added weights. - name: "Get the union between 3 sorted sets with scores", - preset: true, - presetValues: map[string]interface{}{ - "key15": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 100}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, - }), - "key16": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, - {Value: "eleven", Score: 11}, {Value: "eight", Score: 80}, - }), - "key17": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, - {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, - {Value: "twelve", Score: 12}, - }), - }, - destination: "destination6", - keys: []string{"key15", "key16", "key17"}, - options: ZUnionStoreOptions{WithScores: true, Aggregate: "MAX", Weights: []float64{1, 2, 3}}, - want: 13, - wantErr: false, - }, - { - // Get the union between 3 sorted sets with scores. - // Use MIN aggregate with added weights. - name: "Get the union between 3 sorted sets with scores", - preset: true, - presetValues: map[string]interface{}{ - "key18": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 100}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, - }), - "key19": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, - {Value: "eleven", Score: 11}, {Value: "eight", Score: 80}, - }), - "key20": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, - {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, - {Value: "twelve", Score: 12}, - }), - }, - destination: "destination7", - keys: []string{"destination7", "key18", "key19", "key20"}, - options: ZUnionStoreOptions{WithScores: true, Aggregate: "MIN", Weights: []float64{1, 2, 3}}, - want: 13, - wantErr: false, - }, - { - name: "Throw an error if there are more weights than keys", - preset: true, - presetValues: map[string]interface{}{ - "key21": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, - }), - "key22": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), - }, - destination: "destination8", - keys: []string{"key21", "key22"}, - options: ZUnionStoreOptions{Weights: []float64{1, 2, 3}}, - want: 0, - wantErr: true, - }, - { - name: "Throw an error if there are fewer weights than keys", - preset: true, - presetValues: map[string]interface{}{ - "key23": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, - }), - "key24": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - }), - "key25": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), - }, - destination: "destination9", - keys: []string{"key23", "key24", "key25"}, - options: ZUnionStoreOptions{Weights: []float64{5, 4}}, - want: 0, - wantErr: true, - }, - { - name: "Throw an error if any of the provided keys are not sorted sets", - preset: true, - presetValues: map[string]interface{}{ - "key29": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, - }), - "key30": "Default value", - "key31": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), - }, - destination: "destination11", - keys: []string{"key29", "key30", "key31"}, - options: ZUnionStoreOptions{}, - want: 0, - wantErr: true, - }, - { - name: "If any of the keys does not exist, skip it", - preset: true, - presetValues: map[string]interface{}{ - "key32": ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, - {Value: "eleven", Score: 11}, - }), - "key33": ss.NewSortedSet([]ss.MemberParam{ - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, - {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, - {Value: "twelve", Score: 12}, - }), - }, - destination: "destination12", - keys: []string{"non-existent", "key32", "key33"}, - want: 9, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.preset { - for k, v := range tt.presetValues { - err := presetValue(server, context.Background(), k, v) + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.preset { + err := presetValue(server, context.Background(), tt.key, tt.presetValue) if err != nil { t.Error(err) return } } - } - got, err := server.ZUnionStore(tt.destination, tt.keys, tt.options) - if (err != nil) != tt.wantErr { - t.Errorf("ZUNIONSTORE() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("ZUNIONSTORE() got = %v, want %v", got, tt.want) - } - }) - } -} - -func TestSugarDB_ZRevRank(t *testing.T) { - server := createSugarDB() - - tests := []struct { - name string - preset bool - presetValue interface{} - key string - member string - withscores bool - want map[int]float64 - wantErr bool - }{ - { - name: "1. Return element's rank from a sorted set", - preset: true, - presetValue: ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, - }), - key: "key1", - member: "four", - withscores: false, - want: map[int]float64{1: 0}, - wantErr: false, - }, - { - name: "2. Return element's rank from a sorted set with its score", - preset: true, - presetValue: ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 100.1}, {Value: "two", Score: 245}, - {Value: "three", Score: 305.43}, {Value: "four", Score: 411.055}, - {Value: "five", Score: 500}, - }), - key: "key2", - member: "four", - withscores: true, - want: map[int]float64{1: 411.055}, - wantErr: false, - }, - { - name: "3. If key does not exist, return empty map", - preset: false, - presetValue: nil, - key: "key3", - member: "one", - withscores: false, - want: map[int]float64{}, - wantErr: false, - }, - { - name: "4. If key exists and is a sorted set, but the member does not exist, return nil", - preset: true, - presetValue: ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1.1}, {Value: "two", Score: 245}, - {Value: "three", Score: 3}, {Value: "four", Score: 4.055}, - {Value: "five", Score: 5}, - }), - key: "key4", - member: "non-existent", - withscores: false, - want: map[int]float64{}, - wantErr: false, - }, - { - name: "5. Throw error when trying to find scores from elements that are not sorted sets", - preset: true, - presetValue: "Default value", - key: "key5", - member: "one", - withscores: false, - want: nil, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.preset { - err := presetValue(server, context.Background(), tt.key, tt.presetValue) - if err != nil { - t.Error(err) + got, err := server.ZRevRank(tt.key, tt.member, tt.withscores) + if (err != nil) != tt.wantErr { + t.Errorf("ZREVRANK() error = %v, wantErr %v", err, tt.wantErr) return } - } - got, err := server.ZRevRank(tt.key, tt.member, tt.withscores) - if (err != nil) != tt.wantErr { - t.Errorf("ZREVRANK() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("ZREVRANK() got = %v, want %v", got, tt.want) - } - }) - } -} + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("ZREVRANK() got = %v, want %v", got, tt.want) + } + }) + } + }) -func TestSugarDB_ZRemRangeByLex(t *testing.T) { - server := createSugarDB() - tests := []struct { - name string - key string - presetValue interface{} - min string - max string - want int - wantErr bool - }{ - { - name: "1. Successfully remove multiple elements with scores inside the provided range", - key: "ZremRangeByLexKey1", - presetValue: ss.NewSortedSet([]ss.MemberParam{ - {Value: "a", Score: 1}, {Value: "b", Score: 1}, - {Value: "c", Score: 1}, {Value: "d", Score: 1}, - {Value: "e", Score: 1}, {Value: "f", Score: 1}, - {Value: "g", Score: 1}, {Value: "h", Score: 1}, - {Value: "i", Score: 1}, {Value: "j", Score: 1}, - }), - min: "a", - max: "d", - want: 4, - wantErr: false, - }, - { - name: "2. Return 0 if the members do not have the same score", - key: "ZremRangeByLexKey2", - presetValue: ss.NewSortedSet([]ss.MemberParam{ - {Value: "a", Score: 1}, {Value: "b", Score: 2}, - {Value: "c", Score: 3}, {Value: "d", Score: 4}, - {Value: "e", Score: 5}, {Value: "f", Score: 6}, - {Value: "g", Score: 7}, {Value: "h", Score: 8}, - {Value: "i", Score: 9}, {Value: "j", Score: 10}, - }), - min: "d", - max: "g", - want: 0, - wantErr: false, - }, - { - name: "3. If key does not exist, return 0", - key: "ZremRangeByLexKey3", - presetValue: nil, - min: "2", - max: "4", - want: 0, - wantErr: false, - }, - { - name: "4. Return error key is not a sorted set", - key: "ZremRangeByLexKey4", - presetValue: "Default value", - min: "a", - max: "d", - want: 0, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.presetValue != nil { - err := presetValue(server, context.Background(), tt.key, tt.presetValue) - if err != nil { - t.Error(err) + t.Run("TestSugarDB_ZRemRangeByLex", func(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + key string + presetValue interface{} + min string + max string + want int + wantErr bool + }{ + { + name: "1. Successfully remove multiple elements with scores inside the provided range", + key: "ZremRangeByLexKey1", + presetValue: ss.NewSortedSet([]ss.MemberParam{ + {Value: "a", Score: 1}, {Value: "b", Score: 1}, + {Value: "c", Score: 1}, {Value: "d", Score: 1}, + {Value: "e", Score: 1}, {Value: "f", Score: 1}, + {Value: "g", Score: 1}, {Value: "h", Score: 1}, + {Value: "i", Score: 1}, {Value: "j", Score: 1}, + }), + min: "a", + max: "d", + want: 4, + wantErr: false, + }, + { + name: "2. Return 0 if the members do not have the same score", + key: "ZremRangeByLexKey2", + presetValue: ss.NewSortedSet([]ss.MemberParam{ + {Value: "a", Score: 1}, {Value: "b", Score: 2}, + {Value: "c", Score: 3}, {Value: "d", Score: 4}, + {Value: "e", Score: 5}, {Value: "f", Score: 6}, + {Value: "g", Score: 7}, {Value: "h", Score: 8}, + {Value: "i", Score: 9}, {Value: "j", Score: 10}, + }), + min: "d", + max: "g", + want: 0, + wantErr: false, + }, + { + name: "3. If key does not exist, return 0", + key: "ZremRangeByLexKey3", + presetValue: nil, + min: "2", + max: "4", + want: 0, + wantErr: false, + }, + { + name: "4. Return error key is not a sorted set", + key: "ZremRangeByLexKey4", + presetValue: "Default value", + min: "a", + max: "d", + want: 0, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.presetValue != nil { + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } + } + got, err := server.ZRemRangeByLex(tt.key, tt.min, tt.max) + if (err != nil) != tt.wantErr { + t.Errorf("ZRemRangeByLex() error = %v, wantErr %v", err, tt.wantErr) return } - } - got, err := server.ZRemRangeByLex(tt.key, tt.min, tt.max) - if (err != nil) != tt.wantErr { - t.Errorf("ZRemRangeByLex() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("ZRemRangeByLex() got = %v, want %v", got, tt.want) - } - }) - } -} + if got != tt.want { + t.Errorf("ZRemRangeByLex() got = %v, want %v", got, tt.want) + } + }) + } + }) -func TestSugarDB_ZRemRangeByRank(t *testing.T) { - server := createSugarDB() - tests := []struct { - name string - key string - presetValue interface{} - min int - max int - want int - wantErr bool - }{ - { - name: "1. Successfully remove multiple elements within range", - key: "ZremRangeByRankKey1", - presetValue: ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, - {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, - }), - min: 0, - max: 5, - want: 6, - wantErr: false, - }, - { - name: "2. Establish boundaries from the end of the set when negative boundaries are provided", - key: "ZremRangeByRankKey2", - presetValue: ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, - {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, - }), - min: -6, - max: -3, - want: 4, - wantErr: false, - }, - { - name: "3. If key does not exist, return 0", - key: "ZremRangeByRankKey3", - presetValue: nil, - min: 2, - max: 4, - want: 0, - wantErr: false, - }, - { - name: "4. Return error key is not a sorted set", - presetValue: "Default value", - key: "ZremRangeByRankKey3", - min: 4, - max: 4, - want: 0, - wantErr: true, - }, - { - name: "5. Return error when start index is out of bounds", - key: "ZremRangeByRankKey5", - presetValue: ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, - {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, - }), - min: -12, - max: 5, - want: 0, - wantErr: true, - }, - { - name: "6. Return error when end index is out of bounds", - key: "ZremRangeByRankKey6", - presetValue: ss.NewSortedSet([]ss.MemberParam{ - {Value: "one", Score: 1}, {Value: "two", Score: 2}, - {Value: "three", Score: 3}, {Value: "four", Score: 4}, - {Value: "five", Score: 5}, {Value: "six", Score: 6}, - {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, - {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, - }), - min: 0, - max: 11, - want: 0, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.presetValue != nil { - err := presetValue(server, context.Background(), tt.key, tt.presetValue) - if err != nil { - t.Error(err) + t.Run("TestSugarDB_ZRemRangeByRank", func(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + key string + presetValue interface{} + min int + max int + want int + wantErr bool + }{ + { + name: "1. Successfully remove multiple elements within range", + key: "ZremRangeByRankKey1", + presetValue: ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, + }), + min: 0, + max: 5, + want: 6, + wantErr: false, + }, + { + name: "2. Establish boundaries from the end of the set when negative boundaries are provided", + key: "ZremRangeByRankKey2", + presetValue: ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, + }), + min: -6, + max: -3, + want: 4, + wantErr: false, + }, + { + name: "3. If key does not exist, return 0", + key: "ZremRangeByRankKey3", + presetValue: nil, + min: 2, + max: 4, + want: 0, + wantErr: false, + }, + { + name: "4. Return error key is not a sorted set", + presetValue: "Default value", + key: "ZremRangeByRankKey3", + min: 4, + max: 4, + want: 0, + wantErr: true, + }, + { + name: "5. Return error when start index is out of bounds", + key: "ZremRangeByRankKey5", + presetValue: ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, + }), + min: -12, + max: 5, + want: 0, + wantErr: true, + }, + { + name: "6. Return error when end index is out of bounds", + key: "ZremRangeByRankKey6", + presetValue: ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: 1}, {Value: "two", Score: 2}, + {Value: "three", Score: 3}, {Value: "four", Score: 4}, + {Value: "five", Score: 5}, {Value: "six", Score: 6}, + {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, + {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, + }), + min: 0, + max: 11, + want: 0, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.presetValue != nil { + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } + } + got, err := server.ZRemRangeByRank(tt.key, tt.min, tt.max) + if (err != nil) != tt.wantErr { + t.Errorf("ZRemRangeByRank() error = %v, wantErr %v", err, tt.wantErr) return } - } - got, err := server.ZRemRangeByRank(tt.key, tt.min, tt.max) - if (err != nil) != tt.wantErr { - t.Errorf("ZRemRangeByRank() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("ZRemRangeByRank() got = %v, want %v", got, tt.want) - } - }) - } + if got != tt.want { + t.Errorf("ZRemRangeByRank() got = %v, want %v", got, tt.want) + } + }) + } + }) } diff --git a/sugardb/api_string_test.go b/sugardb/api_string_test.go index 6888fad..26dd98c 100644 --- a/sugardb/api_string_test.go +++ b/sugardb/api_string_test.go @@ -19,349 +19,359 @@ import ( "testing" ) -func TestSugarDB_SUBSTR(t *testing.T) { +func TestSugarDB_String(t *testing.T) { server := createSugarDB() - tests := []struct { - name string - presetValue interface{} - substrFunc func(key string, start int, end int) (string, error) - key string - start int - end int - want string - wantErr bool - }{ - { - name: "Return substring within the range of the string", - key: "key1", - substrFunc: server.SubStr, - presetValue: "Test String One", - start: 5, - end: 10, - want: "String", - wantErr: false, - }, - { - name: "Return substring at the end of the string with exact end index", - key: "key2", - substrFunc: server.SubStr, - presetValue: "Test String Two", - start: 12, - end: 14, - want: "Two", - wantErr: false, - }, - { - name: "Return substring at the end of the string with end index greater than length", - key: "key3", - substrFunc: server.SubStr, - presetValue: "Test String Three", - start: 12, - end: 75, - want: "Three", - }, - { - name: "Return the substring at the start of the string with 0 start index", - key: "key4", - substrFunc: server.SubStr, - presetValue: "Test String Four", - start: 0, - end: 3, - want: "Test", - wantErr: false, - }, - { - // Return the substring with negative start index. - // Substring should begin abs(start) from the end of the string when start is negative. - name: "Return the substring with negative start index", - key: "key5", - substrFunc: server.SubStr, - presetValue: "Test String Five", - start: -11, - end: 10, - want: "String", - wantErr: false, - }, - { - // Return reverse substring with end index smaller than start index. - // When end index is smaller than start index, the 2 indices are reversed. - name: "Return reverse substring with end index smaller than start index", - key: "key6", - substrFunc: server.SubStr, - presetValue: "Test String Six", - start: 4, - end: 0, - want: "tseT", - }, - { - name: "Return substring within the range of the string", - key: "key7", - substrFunc: server.GetRange, - presetValue: "Test String One", - start: 5, - end: 10, - want: "String", - wantErr: false, - }, - { - name: "Return substring at the end of the string with exact end index", - key: "key8", - substrFunc: server.GetRange, - presetValue: "Test String Two", - start: 12, - end: 14, - want: "Two", - wantErr: false, - }, - { - name: "Return substring at the end of the string with end index greater than length", - key: "key9", - substrFunc: server.GetRange, - presetValue: "Test String Three", - start: 12, - end: 75, - want: "Three", - }, - { - name: "Return the substring at the start of the string with 0 start index", - key: "key10", - substrFunc: server.GetRange, - presetValue: "Test String Four", - start: 0, - end: 3, - want: "Test", - wantErr: false, - }, - { - // Return the substring with negative start index. - // Substring should begin abs(start) from the end of the string when start is negative. - name: "Return the substring with negative start index", - key: "key11", - substrFunc: server.GetRange, - presetValue: "Test String Five", - start: -11, - end: 10, - want: "String", - wantErr: false, - }, - { - // Return reverse substring with end index smaller than start index. - // When end index is smaller than start index, the 2 indices are reversed. - name: "Return reverse substring with end index smaller than start index", - key: "key12", - substrFunc: server.GetRange, - presetValue: "Test String Six", - start: 4, - end: 0, - want: "tseT", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.presetValue != nil { - err := presetValue(server, context.Background(), tt.key, tt.presetValue) - if err != nil { - t.Error(err) + t.Cleanup(func() { + server.ShutDown() + }) + + t.Run("TestSugarDB_SUBSTR", func(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + presetValue interface{} + substrFunc func(key string, start int, end int) (string, error) + key string + start int + end int + want string + wantErr bool + }{ + { + name: "Return substring within the range of the string", + key: "substr_key1", + substrFunc: server.SubStr, + presetValue: "Test String One", + start: 5, + end: 10, + want: "String", + wantErr: false, + }, + { + name: "Return substring at the end of the string with exact end index", + key: "substr_key2", + substrFunc: server.SubStr, + presetValue: "Test String Two", + start: 12, + end: 14, + want: "Two", + wantErr: false, + }, + { + name: "Return substring at the end of the string with end index greater than length", + key: "substr_key3", + substrFunc: server.SubStr, + presetValue: "Test String Three", + start: 12, + end: 75, + want: "Three", + }, + { + name: "Return the substring at the start of the string with 0 start index", + key: "substr_key4", + substrFunc: server.SubStr, + presetValue: "Test String Four", + start: 0, + end: 3, + want: "Test", + wantErr: false, + }, + { + // Return the substring with negative start index. + // Substring should begin abs(start) from the end of the string when start is negative. + name: "Return the substring with negative start index", + key: "substr_key5", + substrFunc: server.SubStr, + presetValue: "Test String Five", + start: -11, + end: 10, + want: "String", + wantErr: false, + }, + { + // Return reverse substring with end index smaller than start index. + // When end index is smaller than start index, the 2 indices are reversed. + name: "Return reverse substring with end index smaller than start index", + key: "substr_key6", + substrFunc: server.SubStr, + presetValue: "Test String Six", + start: 4, + end: 0, + want: "tseT", + }, + { + name: "Return substring within the range of the string", + key: "substr_key7", + substrFunc: server.GetRange, + presetValue: "Test String One", + start: 5, + end: 10, + want: "String", + wantErr: false, + }, + { + name: "Return substring at the end of the string with exact end index", + key: "substr_key8", + substrFunc: server.GetRange, + presetValue: "Test String Two", + start: 12, + end: 14, + want: "Two", + wantErr: false, + }, + { + name: "Return substring at the end of the string with end index greater than length", + key: "substr_key9", + substrFunc: server.GetRange, + presetValue: "Test String Three", + start: 12, + end: 75, + want: "Three", + }, + { + name: "Return the substring at the start of the string with 0 start index", + key: "substr_key10", + substrFunc: server.GetRange, + presetValue: "Test String Four", + start: 0, + end: 3, + want: "Test", + wantErr: false, + }, + { + // Return the substring with negative start index. + // Substring should begin abs(start) from the end of the string when start is negative. + name: "Return the substring with negative start index", + key: "substr_key11", + substrFunc: server.GetRange, + presetValue: "Test String Five", + start: -11, + end: 10, + want: "String", + wantErr: false, + }, + { + // Return reverse substring with end index smaller than start index. + // When end index is smaller than start index, the 2 indices are reversed. + name: "Return reverse substring with end index smaller than start index", + key: "substr_key12", + substrFunc: server.GetRange, + presetValue: "Test String Six", + start: 4, + end: 0, + want: "tseT", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if tt.presetValue != nil { + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } + } + got, err := tt.substrFunc(tt.key, tt.start, tt.end) + if (err != nil) != tt.wantErr { + t.Errorf("GETRANGE() error = %v, wantErr %v", err, tt.wantErr) return } - } - got, err := tt.substrFunc(tt.key, tt.start, tt.end) - if (err != nil) != tt.wantErr { - t.Errorf("GETRANGE() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("GETRANGE() got = %v, want %v", got, tt.want) - } - }) - } -} + if got != tt.want { + t.Errorf("GETRANGE() got = %v, want %v", got, tt.want) + } + }) + } + }) -func TestSugarDB_SETRANGE(t *testing.T) { - server := createSugarDB() - - tests := []struct { - name string - presetValue interface{} - key string - offset int - new string - want int - wantErr bool - }{ - { - name: "Test that SETRANGE on non-existent string creates new string", - key: "key1", - presetValue: "", - offset: 10, - new: "New String Value", - want: len("New String Value"), - wantErr: false, - }, - { - name: "Test SETRANGE with an offset that leads to a longer resulting string", - key: "key2", - presetValue: "Original String Value", - offset: 16, - new: "Portion Replaced With This New String", - want: len("Original String Portion Replaced With This New String"), - wantErr: false, - }, - { - name: "SETRANGE with negative offset prepends the string", - key: "key3", - presetValue: "This is a preset value", - offset: -10, - new: "Prepended ", - want: len("Prepended This is a preset value"), - wantErr: false, - }, - { - name: "SETRANGE with offset that embeds new string inside the old string", - key: "key4", - presetValue: "This is a preset value", - offset: 0, - new: "That", - want: len("That is a preset value"), - wantErr: false, - }, - { - name: "SETRANGE with offset longer than original lengths appends the string", - key: "key5", - presetValue: "This is a preset value", - offset: 100, - new: " Appended", - want: len("This is a preset value Appended"), - wantErr: false, - }, - { - name: "SETRANGE with offset on the last character replaces last character with new string", - key: "key6", - presetValue: "This is a preset value", - offset: len("This is a preset value") - 1, - new: " replaced", - want: len("This is a preset valu replaced"), - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.presetValue != nil { - err := presetValue(server, context.Background(), tt.key, tt.presetValue) - if err != nil { - t.Error(err) + t.Run("TestSugarDB_SETRANGE", func(t *testing.T) { + t.Parallel() + tests := []struct { + name string + presetValue interface{} + key string + offset int + new string + want int + wantErr bool + }{ + { + name: "Test that SETRANGE on non-existent string creates new string", + key: "setrange_key1", + presetValue: "", + offset: 10, + new: "New String Value", + want: len("New String Value"), + wantErr: false, + }, + { + name: "Test SETRANGE with an offset that leads to a longer resulting string", + key: "setrange_key2", + presetValue: "Original String Value", + offset: 16, + new: "Portion Replaced With This New String", + want: len("Original String Portion Replaced With This New String"), + wantErr: false, + }, + { + name: "SETRANGE with negative offset prepends the string", + key: "setrange_key3", + presetValue: "This is a preset value", + offset: -10, + new: "Prepended ", + want: len("Prepended This is a preset value"), + wantErr: false, + }, + { + name: "SETRANGE with offset that embeds new string inside the old string", + key: "setrange_key4", + presetValue: "This is a preset value", + offset: 0, + new: "That", + want: len("That is a preset value"), + wantErr: false, + }, + { + name: "SETRANGE with offset longer than original lengths appends the string", + key: "setrange_key5", + presetValue: "This is a preset value", + offset: 100, + new: " Appended", + want: len("This is a preset value Appended"), + wantErr: false, + }, + { + name: "SETRANGE with offset on the last character replaces last character with new string", + key: "setrange_key6", + presetValue: "This is a preset value", + offset: len("This is a preset value") - 1, + new: " replaced", + want: len("This is a preset valu replaced"), + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if tt.presetValue != nil { + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } + } + got, err := server.SetRange(tt.key, tt.offset, tt.new) + if (err != nil) != tt.wantErr { + t.Errorf("SETRANGE() error = %v, wantErr %v", err, tt.wantErr) return } - } - got, err := server.SetRange(tt.key, tt.offset, tt.new) - if (err != nil) != tt.wantErr { - t.Errorf("SETRANGE() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("SETRANGE() got = %v, want %v", got, tt.want) - } - }) - } -} + if got != tt.want { + t.Errorf("SETRANGE() got = %v, want %v", got, tt.want) + } + }) + } + }) -func TestSugarDB_STRLEN(t *testing.T) { - server := createSugarDB() - - tests := []struct { - name string - presetValue interface{} - key string - want int - wantErr bool - }{ - { - name: "Return the correct string length for an existing string", - key: "key1", - presetValue: "Test String", - want: len("Test String"), - wantErr: false, - }, - { - name: "If the string does not exist, return 0", - key: "key2", - presetValue: "", - want: 0, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.presetValue != nil { - err := presetValue(server, context.Background(), tt.key, tt.presetValue) - if err != nil { - t.Error(err) + t.Run("TestSugarDB_STRLEN", func(t *testing.T) { + t.Parallel() + tests := []struct { + name string + presetValue interface{} + key string + want int + wantErr bool + }{ + { + name: "Return the correct string length for an existing string", + key: "strlen_key1", + presetValue: "Test String", + want: len("Test String"), + wantErr: false, + }, + { + name: "If the string does not exist, return 0", + key: "strlen_key2", + presetValue: "", + want: 0, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if tt.presetValue != nil { + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } + } + got, err := server.StrLen(tt.key) + if (err != nil) != tt.wantErr { + t.Errorf("STRLEN() error = %v, wantErr %v", err, tt.wantErr) return } - } - got, err := server.StrLen(tt.key) - if (err != nil) != tt.wantErr { - t.Errorf("STRLEN() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("STRLEN() got = %v, want %v", got, tt.want) - } - }) - } -} + if got != tt.want { + t.Errorf("STRLEN() got = %v, want %v", got, tt.want) + } + }) + } + }) -func TestSugarDB_APPEND(t *testing.T) { - server := createSugarDB() - tests := []struct { - name string - presetValue interface{} - key string - value string - want int - wantErr bool - }{ - { - name: "Test APPEND with no preset value", - key: "key1", - value: "Hello ", - want: 6, - wantErr: false, - }, - { - name: "Test APPEND with preset value", - presetValue: "Hello ", - key: "key2", - value: "World", - want: 11, - wantErr: false, - }, - { - name: "Test APPEND with integer preset value", - key: "key3", - presetValue: 10, - value: "Hello ", - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.presetValue != nil { - err := presetValue(server, context.Background(), tt.key, tt.presetValue) - if err != nil { - t.Error(err) + t.Run("TestSugarDB_APPEND", func(t *testing.T) { + t.Parallel() + tests := []struct { + name string + presetValue interface{} + key string + value string + want int + wantErr bool + }{ + { + name: "Test APPEND with no preset value", + key: "append_key1", + value: "Hello ", + want: 6, + wantErr: false, + }, + { + name: "Test APPEND with preset value", + presetValue: "Hello ", + key: "append_key2", + value: "World", + want: 11, + wantErr: false, + }, + { + name: "Test APPEND with integer preset value", + key: "append_key3", + presetValue: 10, + value: "Hello ", + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if tt.presetValue != nil { + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } + } + got, err := server.Append(tt.key, tt.value) + if (err != nil) != tt.wantErr { + t.Errorf("APPEND() error = %v, wantErr %v", err, tt.wantErr) return } - } - got, err := server.Append(tt.key, tt.value) - if (err != nil) != tt.wantErr { - t.Errorf("APPEND() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("APPEND() got = %v, want %v", got, tt.want) - } - }) - } + if got != tt.want { + t.Errorf("APPEND() got = %v, want %v", got, tt.want) + } + }) + } + }) } diff --git a/sugardb/sugardb_test.go b/sugardb/sugardb_test.go index bb465f2..5cf81d6 100644 --- a/sugardb/sugardb_test.go +++ b/sugardb/sugardb_test.go @@ -212,6 +212,8 @@ func makeCluster(size int) ([]ClientServerPair, error) { } func Test_Cluster(t *testing.T) { + t.Parallel() + nodes, err := makeCluster(5) if err != nil { t.Error(err) @@ -274,9 +276,7 @@ func Test_Cluster(t *testing.T) { // Yield ticker := time.NewTicker(200 * time.Millisecond) - defer func() { - ticker.Stop() - }() + defer ticker.Stop() <-ticker.C // Check if the data has been replicated on a quorum (majority of the cluster). @@ -322,10 +322,8 @@ func Test_Cluster(t *testing.T) { // Yield ticker := time.NewTicker(200 * time.Millisecond) - defer func() { - ticker.Stop() - }() <-ticker.C + ticker.Stop() // Check if the data has been replicated on a quorum (majority of the cluster). quorum := int(math.Ceil(float64(len(nodes)/2)) + 1) @@ -963,8 +961,18 @@ func Test_Standalone(t *testing.T) { name: "1. Snapshot in embedded instance", dataDir: path.Join(dataDir, "embedded_instance"), values: map[int]map[string]string{ - 0: {"key5": "value-05", "key6": "value-06", "key7": "value-07", "key8": "value-08"}, - 1: {"key5": "value-15", "key6": "value-16", "key7": "value-17", "key8": "value-18"}, + 0: { + "test_snapshot_key5": "value-05", + "test_snapshot_key6": "value-06", + "test_snapshot_key7": "value-07", + "test_snapshot_key8": "value-08", + }, + 1: { + "test_snapshot_key5": "value-15", + "test_snapshot_key6": "value-16", + "test_snapshot_key7": "value-17", + "test_snapshot_key8": "value-18", + }, }, snapshotFunc: func(mockServer *SugarDB) error { if _, err := mockServer.Save(); err != nil { @@ -1022,7 +1030,7 @@ func Test_Standalone(t *testing.T) { } // Yield to allow snapshot to complete sync. - ticker := time.NewTicker(20 * time.Millisecond) + ticker := time.NewTicker(200 * time.Millisecond) <-ticker.C ticker.Stop() @@ -1121,10 +1129,10 @@ func Test_Standalone(t *testing.T) { <-ticker.C // Rewrite AOF - if _, err := mockServer.RewriteAOF(); err != nil { - t.Error(err) - return - } + mockServer.RewriteAOF() + + // Yield + <-ticker.C // Perform write commands from "after-rewrite" for key, value := range data["after-rewrite"] {