diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 0797aea2..74d3e003 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -12,7 +12,6 @@ on: branches: [ "main" ] jobs: - build: runs-on: ubuntu-latest steps: diff --git a/Makefile b/Makefile index 0dabe089..d5b3c079 100644 --- a/Makefile +++ b/Makefile @@ -27,4 +27,8 @@ test-race: env RACE=true OUT=internal/modules/admin/testdata make build-modules-test && \ env RACE=true OUT=echovault/testdata make build-modules-test && \ go clean -testcache && \ - CGO_ENABLED=1 go test ./... --race \ No newline at end of file + CGO_ENABLED=1 go test ./... --race + +cover: + go tool cover -html=./coverage/coverage.out + diff --git a/coverage/coverage.out b/coverage/coverage.out index 0b038949..29214eb6 100644 --- a/coverage/coverage.out +++ b/coverage/coverage.out @@ -54,54 +54,47 @@ github.com/echovault/echovault/internal/aof/log/store.go:191.2,191.47 1 0 github.com/echovault/echovault/internal/aof/log/store.go:191.47,193.3 1 0 github.com/echovault/echovault/internal/aof/log/store.go:194.2,194.12 1 0 github.com/echovault/echovault/internal/aof/log/store.go:197.41,201.2 3 1 -github.com/echovault/echovault/internal/aof/engine.go:50.56,51.30 1 1 -github.com/echovault/echovault/internal/aof/engine.go:51.30,53.3 1 1 -github.com/echovault/echovault/internal/aof/engine.go:56.57,57.30 1 1 -github.com/echovault/echovault/internal/aof/engine.go:57.30,59.3 1 1 -github.com/echovault/echovault/internal/aof/engine.go:62.59,63.30 1 1 -github.com/echovault/echovault/internal/aof/engine.go:63.30,65.3 1 1 -github.com/echovault/echovault/internal/aof/engine.go:68.58,69.30 1 1 -github.com/echovault/echovault/internal/aof/engine.go:69.30,71.3 1 1 -github.com/echovault/echovault/internal/aof/engine.go:74.59,75.30 1 1 -github.com/echovault/echovault/internal/aof/engine.go:75.30,77.3 1 1 -github.com/echovault/echovault/internal/aof/engine.go:80.82,81.30 1 1 -github.com/echovault/echovault/internal/aof/engine.go:81.30,83.3 1 1 -github.com/echovault/echovault/internal/aof/engine.go:86.89,87.30 1 1 -github.com/echovault/echovault/internal/aof/engine.go:87.30,89.3 1 1 -github.com/echovault/echovault/internal/aof/engine.go:92.73,93.30 1 1 -github.com/echovault/echovault/internal/aof/engine.go:93.30,95.3 1 1 -github.com/echovault/echovault/internal/aof/engine.go:98.82,99.30 1 1 -github.com/echovault/echovault/internal/aof/engine.go:99.30,101.3 1 1 -github.com/echovault/echovault/internal/aof/engine.go:104.78,105.30 1 1 -github.com/echovault/echovault/internal/aof/engine.go:105.30,107.3 1 1 -github.com/echovault/echovault/internal/aof/engine.go:110.69,118.29 1 1 -github.com/echovault/echovault/internal/aof/engine.go:118.30,118.31 0 0 -github.com/echovault/echovault/internal/aof/engine.go:119.30,119.31 0 0 -github.com/echovault/echovault/internal/aof/engine.go:120.57,120.71 1 0 -github.com/echovault/echovault/internal/aof/engine.go:121.63,121.64 0 0 -github.com/echovault/echovault/internal/aof/engine.go:122.44,122.45 0 0 -github.com/echovault/echovault/internal/aof/engine.go:127.2,127.33 1 1 -github.com/echovault/echovault/internal/aof/engine.go:127.33,129.3 1 1 -github.com/echovault/echovault/internal/aof/engine.go:132.2,139.16 2 1 -github.com/echovault/echovault/internal/aof/engine.go:139.16,141.3 1 0 -github.com/echovault/echovault/internal/aof/engine.go:142.2,152.16 3 1 -github.com/echovault/echovault/internal/aof/engine.go:152.16,154.3 1 0 -github.com/echovault/echovault/internal/aof/engine.go:155.2,159.12 2 1 -github.com/echovault/echovault/internal/aof/engine.go:159.12,160.7 1 1 -github.com/echovault/echovault/internal/aof/engine.go:160.7,162.54 2 1 -github.com/echovault/echovault/internal/aof/engine.go:162.54,164.5 1 0 -github.com/echovault/echovault/internal/aof/engine.go:168.2,168.20 1 1 -github.com/echovault/echovault/internal/aof/engine.go:171.52,173.2 1 1 -github.com/echovault/echovault/internal/aof/engine.go:175.42,183.62 5 1 -github.com/echovault/echovault/internal/aof/engine.go:183.62,185.3 1 0 -github.com/echovault/echovault/internal/aof/engine.go:188.2,188.54 1 1 -github.com/echovault/echovault/internal/aof/engine.go:188.54,190.3 1 0 -github.com/echovault/echovault/internal/aof/engine.go:192.2,192.12 1 1 -github.com/echovault/echovault/internal/aof/engine.go:195.39,196.55 1 1 -github.com/echovault/echovault/internal/aof/engine.go:196.55,198.3 1 0 -github.com/echovault/echovault/internal/aof/engine.go:199.2,199.53 1 1 -github.com/echovault/echovault/internal/aof/engine.go:199.53,201.3 1 0 -github.com/echovault/echovault/internal/aof/engine.go:202.2,202.12 1 1 +github.com/echovault/echovault/internal/config/config.go:64.34,70.24 3 0 +github.com/echovault/echovault/internal/config/config.go:70.24,72.35 2 0 +github.com/echovault/echovault/internal/config/config.go:72.35,74.5 1 0 +github.com/echovault/echovault/internal/config/config.go:75.4,75.22 1 0 +github.com/echovault/echovault/internal/config/config.go:75.22,77.5 1 0 +github.com/echovault/echovault/internal/config/config.go:78.4,79.14 2 0 +github.com/echovault/echovault/internal/config/config.go:82.2,82.115 1 0 +github.com/echovault/echovault/internal/config/config.go:82.115,85.3 2 0 +github.com/echovault/echovault/internal/config/config.go:87.2,90.29 2 0 +github.com/echovault/echovault/internal/config/config.go:90.29,91.86 1 0 +github.com/echovault/echovault/internal/config/config.go:91.86,93.5 1 0 +github.com/echovault/echovault/internal/config/config.go:93.7,95.5 1 0 +github.com/echovault/echovault/internal/config/config.go:96.4,97.14 2 0 +github.com/echovault/echovault/internal/config/config.go:100.2,103.59 2 0 +github.com/echovault/echovault/internal/config/config.go:103.59,105.17 2 0 +github.com/echovault/echovault/internal/config/config.go:105.17,107.4 1 0 +github.com/echovault/echovault/internal/config/config.go:108.3,109.13 2 0 +github.com/echovault/echovault/internal/config/config.go:112.2,121.88 2 0 +github.com/echovault/echovault/internal/config/config.go:121.88,128.23 3 0 +github.com/echovault/echovault/internal/config/config.go:128.23,130.5 1 0 +github.com/echovault/echovault/internal/config/config.go:131.4,132.14 2 0 +github.com/echovault/echovault/internal/config/config.go:135.2,139.24 2 0 +github.com/echovault/echovault/internal/config/config.go:139.24,140.36 1 0 +github.com/echovault/echovault/internal/config/config.go:140.36,142.5 1 0 +github.com/echovault/echovault/internal/config/config.go:143.4,144.14 2 0 +github.com/echovault/echovault/internal/config/config.go:147.2,219.22 25 0 +github.com/echovault/echovault/internal/config/config.go:219.22,221.45 1 0 +github.com/echovault/echovault/internal/config/config.go:221.45,222.14 1 0 +github.com/echovault/echovault/internal/config/config.go:223.9,224.17 1 0 +github.com/echovault/echovault/internal/config/config.go:224.17,225.36 1 0 +github.com/echovault/echovault/internal/config/config.go:225.36,227.6 1 0 +github.com/echovault/echovault/internal/config/config.go:230.4,232.22 2 0 +github.com/echovault/echovault/internal/config/config.go:232.22,233.59 1 0 +github.com/echovault/echovault/internal/config/config.go:233.59,235.6 1 0 +github.com/echovault/echovault/internal/config/config.go:238.4,238.39 1 0 +github.com/echovault/echovault/internal/config/config.go:238.39,239.59 1 0 +github.com/echovault/echovault/internal/config/config.go:239.59,241.6 1 0 +github.com/echovault/echovault/internal/config/config.go:247.2,249.45 2 0 +github.com/echovault/echovault/internal/config/config.go:249.45,251.3 1 0 +github.com/echovault/echovault/internal/config/config.go:253.2,253.18 1 0 +github.com/echovault/echovault/internal/config/default.go:8.29,38.2 1 0 github.com/echovault/echovault/internal/aof/preamble/store.go:45.62,46.36 1 1 github.com/echovault/echovault/internal/aof/preamble/store.go:46.36,48.3 1 1 github.com/echovault/echovault/internal/aof/preamble/store.go:51.71,52.36 1 0 @@ -191,6 +184,54 @@ github.com/echovault/echovault/internal/eviction/lru.go:92.73,94.3 1 0 github.com/echovault/echovault/internal/eviction/lru.go:95.2,95.19 1 0 github.com/echovault/echovault/internal/eviction/lru.go:95.19,97.3 1 0 github.com/echovault/echovault/internal/eviction/lru.go:100.50,103.2 2 1 +github.com/echovault/echovault/internal/aof/engine.go:50.56,51.30 1 1 +github.com/echovault/echovault/internal/aof/engine.go:51.30,53.3 1 1 +github.com/echovault/echovault/internal/aof/engine.go:56.57,57.30 1 1 +github.com/echovault/echovault/internal/aof/engine.go:57.30,59.3 1 1 +github.com/echovault/echovault/internal/aof/engine.go:62.59,63.30 1 1 +github.com/echovault/echovault/internal/aof/engine.go:63.30,65.3 1 1 +github.com/echovault/echovault/internal/aof/engine.go:68.58,69.30 1 1 +github.com/echovault/echovault/internal/aof/engine.go:69.30,71.3 1 1 +github.com/echovault/echovault/internal/aof/engine.go:74.59,75.30 1 1 +github.com/echovault/echovault/internal/aof/engine.go:75.30,77.3 1 1 +github.com/echovault/echovault/internal/aof/engine.go:80.82,81.30 1 1 +github.com/echovault/echovault/internal/aof/engine.go:81.30,83.3 1 1 +github.com/echovault/echovault/internal/aof/engine.go:86.89,87.30 1 1 +github.com/echovault/echovault/internal/aof/engine.go:87.30,89.3 1 1 +github.com/echovault/echovault/internal/aof/engine.go:92.73,93.30 1 1 +github.com/echovault/echovault/internal/aof/engine.go:93.30,95.3 1 1 +github.com/echovault/echovault/internal/aof/engine.go:98.82,99.30 1 1 +github.com/echovault/echovault/internal/aof/engine.go:99.30,101.3 1 1 +github.com/echovault/echovault/internal/aof/engine.go:104.78,105.30 1 1 +github.com/echovault/echovault/internal/aof/engine.go:105.30,107.3 1 1 +github.com/echovault/echovault/internal/aof/engine.go:110.69,118.29 1 1 +github.com/echovault/echovault/internal/aof/engine.go:118.30,118.31 0 0 +github.com/echovault/echovault/internal/aof/engine.go:119.30,119.31 0 0 +github.com/echovault/echovault/internal/aof/engine.go:120.57,120.71 1 0 +github.com/echovault/echovault/internal/aof/engine.go:121.63,121.64 0 0 +github.com/echovault/echovault/internal/aof/engine.go:122.44,122.45 0 0 +github.com/echovault/echovault/internal/aof/engine.go:127.2,127.33 1 1 +github.com/echovault/echovault/internal/aof/engine.go:127.33,129.3 1 1 +github.com/echovault/echovault/internal/aof/engine.go:132.2,139.16 2 1 +github.com/echovault/echovault/internal/aof/engine.go:139.16,141.3 1 0 +github.com/echovault/echovault/internal/aof/engine.go:142.2,152.16 3 1 +github.com/echovault/echovault/internal/aof/engine.go:152.16,154.3 1 0 +github.com/echovault/echovault/internal/aof/engine.go:155.2,159.12 2 1 +github.com/echovault/echovault/internal/aof/engine.go:159.12,160.7 1 1 +github.com/echovault/echovault/internal/aof/engine.go:160.7,162.54 2 1 +github.com/echovault/echovault/internal/aof/engine.go:162.54,164.5 1 0 +github.com/echovault/echovault/internal/aof/engine.go:168.2,168.20 1 1 +github.com/echovault/echovault/internal/aof/engine.go:171.52,173.2 1 1 +github.com/echovault/echovault/internal/aof/engine.go:175.42,183.62 5 1 +github.com/echovault/echovault/internal/aof/engine.go:183.62,185.3 1 0 +github.com/echovault/echovault/internal/aof/engine.go:188.2,188.54 1 1 +github.com/echovault/echovault/internal/aof/engine.go:188.54,190.3 1 0 +github.com/echovault/echovault/internal/aof/engine.go:192.2,192.12 1 1 +github.com/echovault/echovault/internal/aof/engine.go:195.39,196.55 1 1 +github.com/echovault/echovault/internal/aof/engine.go:196.55,198.3 1 0 +github.com/echovault/echovault/internal/aof/engine.go:199.2,199.53 1 1 +github.com/echovault/echovault/internal/aof/engine.go:199.53,201.3 1 0 +github.com/echovault/echovault/internal/aof/engine.go:202.2,202.12 1 1 github.com/echovault/echovault/internal/modules/acl/acl.go:52.40,57.24 3 1 github.com/echovault/echovault/internal/modules/acl/acl.go:57.24,65.3 2 1 github.com/echovault/echovault/internal/modules/acl/acl.go:68.2,68.28 1 1 @@ -634,99 +675,6 @@ github.com/echovault/echovault/internal/modules/connection/commands.go:28.9,29.3 github.com/echovault/echovault/internal/modules/connection/commands.go:30.9,31.94 1 1 github.com/echovault/echovault/internal/modules/connection/commands.go:35.36,45.84 1 1 github.com/echovault/echovault/internal/modules/connection/commands.go:45.84,51.5 1 0 -github.com/echovault/echovault/internal/modules/admin/commands.go:27.78,33.29 4 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:33.29,34.54 1 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:34.54,40.42 4 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:40.42,42.5 1 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:44.4,47.12 3 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:50.3,50.36 1 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:50.36,57.43 5 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:57.43,59.5 1 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:61.4,63.21 2 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:67.2,69.25 2 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:72.76,76.35 3 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:76.35,77.65 1 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:77.65,78.41 1 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:78.41,80.5 1 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:81.4,81.12 1 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:83.3,83.13 1 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:86.2,86.51 1 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:89.75,90.29 1 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:91.9,96.36 4 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:96.36,97.66 1 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:97.66,98.52 1 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:98.52,102.6 3 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:103.5,103.13 1 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:105.4,106.14 2 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:108.3,109.26 2 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:111.9,115.56 3 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:115.56,117.4 1 0 -github.com/echovault/echovault/internal/modules/admin/commands.go:118.3,118.53 1 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:118.53,122.37 3 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:122.37,123.67 1 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:123.67,124.53 1 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:124.53,125.59 1 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:125.59,129.8 3 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:131.6,131.14 1 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:133.5,133.54 1 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:133.54,136.6 2 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:138.9,138.61 1 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:138.61,142.37 3 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:142.37,143.67 1 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:143.67,144.53 1 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:144.53,146.24 2 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:146.24,149.8 2 0 -github.com/echovault/echovault/internal/modules/admin/commands.go:151.6,151.14 1 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:153.5,153.33 1 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:153.33,156.6 2 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:158.9,158.60 1 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:158.60,162.37 3 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:162.37,163.67 1 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:163.67,164.53 1 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:164.53,165.55 1 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:165.55,169.8 3 0 -github.com/echovault/echovault/internal/modules/admin/commands.go:171.6,171.14 1 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:173.5,173.50 1 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:173.50,176.6 2 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:178.9,180.4 1 0 -github.com/echovault/echovault/internal/modules/admin/commands.go:181.3,182.26 2 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:183.10,184.54 1 0 -github.com/echovault/echovault/internal/modules/admin/commands.go:188.75,190.2 1 0 -github.com/echovault/echovault/internal/modules/admin/commands.go:192.36,200.84 1 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:200.84,204.5 1 0 -github.com/echovault/echovault/internal/modules/admin/commands.go:213.84,217.5 1 0 -github.com/echovault/echovault/internal/modules/admin/commands.go:225.86,229.7 1 0 -github.com/echovault/echovault/internal/modules/admin/commands.go:238.86,242.7 1 0 -github.com/echovault/echovault/internal/modules/admin/commands.go:252.86,256.7 1 0 -github.com/echovault/echovault/internal/modules/admin/commands.go:267.84,271.5 1 0 -github.com/echovault/echovault/internal/modules/admin/commands.go:272.73,273.49 1 0 -github.com/echovault/echovault/internal/modules/admin/commands.go:273.49,275.6 1 0 -github.com/echovault/echovault/internal/modules/admin/commands.go:276.5,276.45 1 0 -github.com/echovault/echovault/internal/modules/admin/commands.go:285.84,289.5 1 0 -github.com/echovault/echovault/internal/modules/admin/commands.go:290.73,292.18 2 0 -github.com/echovault/echovault/internal/modules/admin/commands.go:292.18,294.6 1 0 -github.com/echovault/echovault/internal/modules/admin/commands.go:295.5,295.53 1 0 -github.com/echovault/echovault/internal/modules/admin/commands.go:304.84,308.5 1 0 -github.com/echovault/echovault/internal/modules/admin/commands.go:309.73,310.47 1 0 -github.com/echovault/echovault/internal/modules/admin/commands.go:310.47,312.6 1 0 -github.com/echovault/echovault/internal/modules/admin/commands.go:313.5,313.45 1 0 -github.com/echovault/echovault/internal/modules/admin/commands.go:321.84,325.5 1 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:335.86,339.7 1 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:340.75,341.34 1 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:341.34,343.8 1 0 -github.com/echovault/echovault/internal/modules/admin/commands.go:344.7,345.34 2 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:345.34,347.8 1 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:348.7,348.75 1 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:348.75,350.8 1 0 -github.com/echovault/echovault/internal/modules/admin/commands.go:351.7,351.47 1 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:361.86,365.7 1 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:366.75,367.35 1 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:367.35,369.8 1 0 -github.com/echovault/echovault/internal/modules/admin/commands.go:370.7,371.47 2 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:380.86,384.7 1 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:385.75,388.38 3 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:388.38,390.8 1 1 -github.com/echovault/echovault/internal/modules/admin/commands.go:391.7,391.30 1 1 github.com/echovault/echovault/internal/modules/generic/commands.go:33.67,35.16 2 1 github.com/echovault/echovault/internal/modules/generic/commands.go:35.16,37.3 1 0 github.com/echovault/echovault/internal/modules/generic/commands.go:39.2,46.16 7 1 @@ -1049,7 +997,7 @@ github.com/echovault/echovault/internal/modules/hash/commands.go:301.38,303.17 2 github.com/echovault/echovault/internal/modules/hash/commands.go:303.17,304.41 1 1 github.com/echovault/echovault/internal/modules/hash/commands.go:304.41,306.13 2 1 github.com/echovault/echovault/internal/modules/hash/commands.go:308.4,308.42 1 1 -github.com/echovault/echovault/internal/modules/hash/commands.go:308.42,311.13 3 0 +github.com/echovault/echovault/internal/modules/hash/commands.go:308.42,311.13 3 1 github.com/echovault/echovault/internal/modules/hash/commands.go:313.4,313.38 1 1 github.com/echovault/echovault/internal/modules/hash/commands.go:313.38,315.13 2 1 github.com/echovault/echovault/internal/modules/hash/commands.go:320.2,320.25 1 1 @@ -1453,7 +1401,7 @@ github.com/echovault/echovault/internal/modules/pubsub/pubsub.go:64.10,66.5 1 1 github.com/echovault/echovault/internal/modules/pubsub/pubsub.go:67.4,68.31 2 1 github.com/echovault/echovault/internal/modules/pubsub/pubsub.go:68.31,73.20 1 1 github.com/echovault/echovault/internal/modules/pubsub/pubsub.go:73.20,75.6 1 0 -github.com/echovault/echovault/internal/modules/pubsub/pubsub.go:77.4,77.46 1 1 +github.com/echovault/echovault/internal/modules/pubsub/pubsub.go:76.5,76.47 1 1 github.com/echovault/echovault/internal/modules/pubsub/pubsub.go:78.9,80.47 1 1 github.com/echovault/echovault/internal/modules/pubsub/pubsub.go:80.47,85.20 1 1 github.com/echovault/echovault/internal/modules/pubsub/pubsub.go:85.20,87.6 1 0 @@ -1520,6 +1468,99 @@ github.com/echovault/echovault/internal/modules/pubsub/pubsub.go:263.2,263.20 1 github.com/echovault/echovault/internal/modules/pubsub/pubsub.go:266.47,271.38 4 1 github.com/echovault/echovault/internal/modules/pubsub/pubsub.go:271.38,273.3 1 1 github.com/echovault/echovault/internal/modules/pubsub/pubsub.go:275.2,275.17 1 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:27.78,33.29 4 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:33.29,34.54 1 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:34.54,40.42 4 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:40.42,42.5 1 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:44.4,47.12 3 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:50.3,50.36 1 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:50.36,57.43 5 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:57.43,59.5 1 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:61.4,63.21 2 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:67.2,69.25 2 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:72.76,76.35 3 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:76.35,77.65 1 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:77.65,78.41 1 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:78.41,80.5 1 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:81.4,81.12 1 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:83.3,83.13 1 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:86.2,86.51 1 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:89.75,90.29 1 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:91.9,96.36 4 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:96.36,97.66 1 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:97.66,98.52 1 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:98.52,102.6 3 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:103.5,103.13 1 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:105.4,106.14 2 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:108.3,109.26 2 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:111.9,115.56 3 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:115.56,117.4 1 0 +github.com/echovault/echovault/internal/modules/admin/commands.go:118.3,118.53 1 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:118.53,122.37 3 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:122.37,123.67 1 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:123.67,124.53 1 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:124.53,125.59 1 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:125.59,129.8 3 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:131.6,131.14 1 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:133.5,133.54 1 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:133.54,136.6 2 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:138.9,138.61 1 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:138.61,142.37 3 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:142.37,143.67 1 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:143.67,144.53 1 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:144.53,146.24 2 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:146.24,149.8 2 0 +github.com/echovault/echovault/internal/modules/admin/commands.go:151.6,151.14 1 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:153.5,153.33 1 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:153.33,156.6 2 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:158.9,158.60 1 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:158.60,162.37 3 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:162.37,163.67 1 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:163.67,164.53 1 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:164.53,165.55 1 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:165.55,169.8 3 0 +github.com/echovault/echovault/internal/modules/admin/commands.go:171.6,171.14 1 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:173.5,173.50 1 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:173.50,176.6 2 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:178.9,180.4 1 0 +github.com/echovault/echovault/internal/modules/admin/commands.go:181.3,182.26 2 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:183.10,184.54 1 0 +github.com/echovault/echovault/internal/modules/admin/commands.go:188.75,190.2 1 0 +github.com/echovault/echovault/internal/modules/admin/commands.go:192.36,200.84 1 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:200.84,204.5 1 0 +github.com/echovault/echovault/internal/modules/admin/commands.go:213.84,217.5 1 0 +github.com/echovault/echovault/internal/modules/admin/commands.go:225.86,229.7 1 0 +github.com/echovault/echovault/internal/modules/admin/commands.go:238.86,242.7 1 0 +github.com/echovault/echovault/internal/modules/admin/commands.go:252.86,256.7 1 0 +github.com/echovault/echovault/internal/modules/admin/commands.go:267.84,271.5 1 0 +github.com/echovault/echovault/internal/modules/admin/commands.go:272.73,273.49 1 0 +github.com/echovault/echovault/internal/modules/admin/commands.go:273.49,275.6 1 0 +github.com/echovault/echovault/internal/modules/admin/commands.go:276.5,276.45 1 0 +github.com/echovault/echovault/internal/modules/admin/commands.go:285.84,289.5 1 0 +github.com/echovault/echovault/internal/modules/admin/commands.go:290.73,292.18 2 0 +github.com/echovault/echovault/internal/modules/admin/commands.go:292.18,294.6 1 0 +github.com/echovault/echovault/internal/modules/admin/commands.go:295.5,295.53 1 0 +github.com/echovault/echovault/internal/modules/admin/commands.go:304.84,308.5 1 0 +github.com/echovault/echovault/internal/modules/admin/commands.go:309.73,310.47 1 0 +github.com/echovault/echovault/internal/modules/admin/commands.go:310.47,312.6 1 0 +github.com/echovault/echovault/internal/modules/admin/commands.go:313.5,313.45 1 0 +github.com/echovault/echovault/internal/modules/admin/commands.go:321.84,325.5 1 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:335.86,339.7 1 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:340.75,341.34 1 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:341.34,343.8 1 0 +github.com/echovault/echovault/internal/modules/admin/commands.go:344.7,345.34 2 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:345.34,347.8 1 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:348.7,348.75 1 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:348.75,350.8 1 0 +github.com/echovault/echovault/internal/modules/admin/commands.go:351.7,351.47 1 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:361.86,365.7 1 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:366.75,367.35 1 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:367.35,369.8 1 0 +github.com/echovault/echovault/internal/modules/admin/commands.go:370.7,371.47 2 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:380.86,384.7 1 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:385.75,388.38 3 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:388.38,390.8 1 1 +github.com/echovault/echovault/internal/modules/admin/commands.go:391.7,391.30 1 1 github.com/echovault/echovault/internal/modules/set/commands.go:26.68,28.16 2 1 github.com/echovault/echovault/internal/modules/set/commands.go:28.16,30.3 1 0 github.com/echovault/echovault/internal/modules/set/commands.go:32.2,37.16 4 1 @@ -2800,143 +2841,144 @@ github.com/echovault/echovault/internal/snapshot/snapshot.go:354.94,356.3 1 1 github.com/echovault/echovault/internal/snapshot/snapshot.go:358.2,360.12 2 1 github.com/echovault/echovault/internal/snapshot/snapshot.go:363.46,365.2 1 0 github.com/echovault/echovault/internal/snapshot/snapshot.go:367.42,369.2 1 1 -github.com/echovault/echovault/echovault/api_acl.go:126.71,128.23 2 0 -github.com/echovault/echovault/echovault/api_acl.go:128.23,130.3 1 0 -github.com/echovault/echovault/echovault/api_acl.go:131.2,132.16 2 0 +github.com/echovault/echovault/echovault/api_acl.go:126.71,128.23 2 1 +github.com/echovault/echovault/echovault/api_acl.go:128.23,130.3 1 1 +github.com/echovault/echovault/echovault/api_acl.go:131.2,132.16 2 1 github.com/echovault/echovault/echovault/api_acl.go:132.16,134.3 1 0 -github.com/echovault/echovault/echovault/api_acl.go:135.2,135.45 1 0 -github.com/echovault/echovault/echovault/api_acl.go:139.55,141.16 2 0 +github.com/echovault/echovault/echovault/api_acl.go:135.2,135.45 1 1 +github.com/echovault/echovault/echovault/api_acl.go:139.55,141.16 2 1 github.com/echovault/echovault/echovault/api_acl.go:141.16,143.3 1 0 -github.com/echovault/echovault/echovault/api_acl.go:144.2,144.45 1 0 -github.com/echovault/echovault/echovault/api_acl.go:155.62,158.18 2 0 -github.com/echovault/echovault/echovault/api_acl.go:158.18,160.3 1 0 +github.com/echovault/echovault/echovault/api_acl.go:144.2,144.45 1 1 +github.com/echovault/echovault/echovault/api_acl.go:155.62,158.18 2 1 +github.com/echovault/echovault/echovault/api_acl.go:158.18,160.3 1 1 github.com/echovault/echovault/echovault/api_acl.go:160.8,162.3 1 0 -github.com/echovault/echovault/echovault/api_acl.go:164.2,164.21 1 0 -github.com/echovault/echovault/echovault/api_acl.go:164.21,166.3 1 0 -github.com/echovault/echovault/echovault/api_acl.go:168.2,168.17 1 0 -github.com/echovault/echovault/echovault/api_acl.go:168.17,170.3 1 0 -github.com/echovault/echovault/echovault/api_acl.go:172.2,172.21 1 0 -github.com/echovault/echovault/echovault/api_acl.go:172.21,174.3 1 0 -github.com/echovault/echovault/echovault/api_acl.go:176.2,176.20 1 0 +github.com/echovault/echovault/echovault/api_acl.go:164.2,164.21 1 1 +github.com/echovault/echovault/echovault/api_acl.go:164.21,166.3 1 1 +github.com/echovault/echovault/echovault/api_acl.go:168.2,168.17 1 1 +github.com/echovault/echovault/echovault/api_acl.go:168.17,170.3 1 1 +github.com/echovault/echovault/echovault/api_acl.go:172.2,172.21 1 1 +github.com/echovault/echovault/echovault/api_acl.go:172.21,174.3 1 1 +github.com/echovault/echovault/echovault/api_acl.go:176.2,176.20 1 1 github.com/echovault/echovault/echovault/api_acl.go:176.20,178.3 1 0 -github.com/echovault/echovault/echovault/api_acl.go:180.2,180.20 1 0 +github.com/echovault/echovault/echovault/api_acl.go:180.2,180.20 1 1 github.com/echovault/echovault/echovault/api_acl.go:180.20,182.3 1 0 -github.com/echovault/echovault/echovault/api_acl.go:184.2,184.24 1 0 +github.com/echovault/echovault/echovault/api_acl.go:184.2,184.24 1 1 github.com/echovault/echovault/echovault/api_acl.go:184.24,186.3 1 0 -github.com/echovault/echovault/echovault/api_acl.go:188.2,188.50 1 0 -github.com/echovault/echovault/echovault/api_acl.go:188.50,190.3 1 0 -github.com/echovault/echovault/echovault/api_acl.go:192.2,192.53 1 0 +github.com/echovault/echovault/echovault/api_acl.go:188.2,188.50 1 1 +github.com/echovault/echovault/echovault/api_acl.go:188.50,190.3 1 1 +github.com/echovault/echovault/echovault/api_acl.go:192.2,192.53 1 1 github.com/echovault/echovault/echovault/api_acl.go:192.53,194.3 1 0 -github.com/echovault/echovault/echovault/api_acl.go:196.2,196.49 1 0 -github.com/echovault/echovault/echovault/api_acl.go:196.49,198.3 1 0 -github.com/echovault/echovault/echovault/api_acl.go:200.2,200.52 1 0 +github.com/echovault/echovault/echovault/api_acl.go:196.2,196.49 1 1 +github.com/echovault/echovault/echovault/api_acl.go:196.49,198.3 1 1 +github.com/echovault/echovault/echovault/api_acl.go:200.2,200.52 1 1 github.com/echovault/echovault/echovault/api_acl.go:200.52,202.3 1 0 -github.com/echovault/echovault/echovault/api_acl.go:204.2,204.50 1 0 -github.com/echovault/echovault/echovault/api_acl.go:204.50,206.3 1 0 -github.com/echovault/echovault/echovault/api_acl.go:208.2,208.50 1 0 -github.com/echovault/echovault/echovault/api_acl.go:208.50,210.3 1 0 -github.com/echovault/echovault/echovault/api_acl.go:212.2,212.47 1 0 -github.com/echovault/echovault/echovault/api_acl.go:212.47,214.3 1 0 -github.com/echovault/echovault/echovault/api_acl.go:216.2,216.47 1 0 -github.com/echovault/echovault/echovault/api_acl.go:216.47,218.3 1 0 -github.com/echovault/echovault/echovault/api_acl.go:220.2,220.48 1 0 -github.com/echovault/echovault/echovault/api_acl.go:220.48,222.3 1 0 -github.com/echovault/echovault/echovault/api_acl.go:224.2,224.43 1 0 -github.com/echovault/echovault/echovault/api_acl.go:224.43,226.3 1 0 -github.com/echovault/echovault/echovault/api_acl.go:228.2,228.44 1 0 -github.com/echovault/echovault/echovault/api_acl.go:228.44,230.3 1 0 -github.com/echovault/echovault/echovault/api_acl.go:232.2,232.47 1 0 -github.com/echovault/echovault/echovault/api_acl.go:232.47,234.3 1 0 -github.com/echovault/echovault/echovault/api_acl.go:236.2,236.47 1 0 -github.com/echovault/echovault/echovault/api_acl.go:236.47,238.3 1 0 -github.com/echovault/echovault/echovault/api_acl.go:240.2,241.16 2 0 +github.com/echovault/echovault/echovault/api_acl.go:204.2,204.50 1 1 +github.com/echovault/echovault/echovault/api_acl.go:204.50,206.3 1 1 +github.com/echovault/echovault/echovault/api_acl.go:208.2,208.50 1 1 +github.com/echovault/echovault/echovault/api_acl.go:208.50,210.3 1 1 +github.com/echovault/echovault/echovault/api_acl.go:212.2,212.47 1 1 +github.com/echovault/echovault/echovault/api_acl.go:212.47,214.3 1 1 +github.com/echovault/echovault/echovault/api_acl.go:216.2,216.47 1 1 +github.com/echovault/echovault/echovault/api_acl.go:216.47,218.3 1 1 +github.com/echovault/echovault/echovault/api_acl.go:220.2,220.48 1 1 +github.com/echovault/echovault/echovault/api_acl.go:220.48,222.3 1 1 +github.com/echovault/echovault/echovault/api_acl.go:224.2,224.43 1 1 +github.com/echovault/echovault/echovault/api_acl.go:224.43,226.3 1 1 +github.com/echovault/echovault/echovault/api_acl.go:228.2,228.44 1 1 +github.com/echovault/echovault/echovault/api_acl.go:228.44,230.3 1 1 +github.com/echovault/echovault/echovault/api_acl.go:232.2,232.47 1 1 +github.com/echovault/echovault/echovault/api_acl.go:232.47,234.3 1 1 +github.com/echovault/echovault/echovault/api_acl.go:236.2,236.47 1 1 +github.com/echovault/echovault/echovault/api_acl.go:236.47,238.3 1 1 +github.com/echovault/echovault/echovault/api_acl.go:240.2,241.16 2 1 github.com/echovault/echovault/echovault/api_acl.go:241.16,243.3 1 0 -github.com/echovault/echovault/echovault/api_acl.go:245.2,246.40 2 0 -github.com/echovault/echovault/echovault/api_acl.go:293.83,295.16 2 0 +github.com/echovault/echovault/echovault/api_acl.go:245.2,246.40 2 1 +github.com/echovault/echovault/echovault/api_acl.go:293.83,295.16 2 1 github.com/echovault/echovault/echovault/api_acl.go:295.16,297.3 1 0 -github.com/echovault/echovault/echovault/api_acl.go:299.2,301.16 3 0 +github.com/echovault/echovault/echovault/api_acl.go:299.2,301.16 3 1 github.com/echovault/echovault/echovault/api_acl.go:301.16,303.3 1 0 -github.com/echovault/echovault/echovault/api_acl.go:305.2,308.35 3 0 -github.com/echovault/echovault/echovault/api_acl.go:308.35,314.35 4 0 -github.com/echovault/echovault/echovault/api_acl.go:314.35,316.4 1 0 -github.com/echovault/echovault/echovault/api_acl.go:319.2,319.20 1 0 -github.com/echovault/echovault/echovault/api_acl.go:329.72,332.16 3 0 -github.com/echovault/echovault/echovault/api_acl.go:332.16,334.3 1 0 -github.com/echovault/echovault/echovault/api_acl.go:335.2,336.40 2 0 -github.com/echovault/echovault/echovault/api_acl.go:340.54,342.16 2 0 -github.com/echovault/echovault/echovault/api_acl.go:342.16,344.3 1 0 -github.com/echovault/echovault/echovault/api_acl.go:345.2,345.45 1 0 -github.com/echovault/echovault/echovault/api_acl.go:356.72,358.9 2 0 -github.com/echovault/echovault/echovault/api_acl.go:359.21,360.29 1 0 -github.com/echovault/echovault/echovault/api_acl.go:361.23,362.31 1 0 -github.com/echovault/echovault/echovault/api_acl.go:363.10,364.31 1 0 -github.com/echovault/echovault/echovault/api_acl.go:367.2,368.16 2 0 -github.com/echovault/echovault/echovault/api_acl.go:368.16,370.3 1 0 -github.com/echovault/echovault/echovault/api_acl.go:372.2,373.40 2 0 -github.com/echovault/echovault/echovault/api_acl.go:379.50,381.16 2 0 -github.com/echovault/echovault/echovault/api_acl.go:381.16,383.3 1 0 -github.com/echovault/echovault/echovault/api_acl.go:384.2,385.40 2 0 -github.com/echovault/echovault/echovault/api_admin.go:142.84,145.9 2 0 -github.com/echovault/echovault/echovault/api_admin.go:146.28,147.71 1 0 -github.com/echovault/echovault/echovault/api_admin.go:148.29,149.73 1 0 -github.com/echovault/echovault/echovault/api_admin.go:150.28,151.71 1 0 -github.com/echovault/echovault/echovault/api_admin.go:154.2,155.16 2 0 -github.com/echovault/echovault/echovault/api_admin.go:155.16,157.3 1 0 -github.com/echovault/echovault/echovault/api_admin.go:159.2,159.45 1 0 -github.com/echovault/echovault/echovault/api_admin.go:165.54,167.16 2 0 -github.com/echovault/echovault/echovault/api_admin.go:167.16,169.3 1 0 -github.com/echovault/echovault/echovault/api_admin.go:170.2,170.41 1 0 -github.com/echovault/echovault/echovault/api_admin.go:174.49,176.16 2 0 -github.com/echovault/echovault/echovault/api_admin.go:176.16,178.3 1 0 -github.com/echovault/echovault/echovault/api_admin.go:179.2,179.40 1 0 -github.com/echovault/echovault/echovault/api_admin.go:183.50,185.16 2 0 -github.com/echovault/echovault/echovault/api_admin.go:185.16,187.3 1 0 -github.com/echovault/echovault/echovault/api_admin.go:188.2,188.41 1 0 -github.com/echovault/echovault/echovault/api_admin.go:192.55,194.16 2 0 -github.com/echovault/echovault/echovault/api_admin.go:194.16,196.3 1 0 -github.com/echovault/echovault/echovault/api_admin.go:197.2,197.40 1 0 -github.com/echovault/echovault/echovault/api_admin.go:209.67,213.36 3 1 -github.com/echovault/echovault/echovault/api_admin.go:213.36,214.52 1 1 -github.com/echovault/echovault/echovault/api_admin.go:214.52,216.4 1 0 -github.com/echovault/echovault/echovault/api_admin.go:219.2,219.63 1 1 -github.com/echovault/echovault/echovault/api_admin.go:219.63,224.32 1 1 -github.com/echovault/echovault/echovault/api_admin.go:224.32,227.44 2 1 -github.com/echovault/echovault/echovault/api_admin.go:227.44,229.6 1 0 -github.com/echovault/echovault/echovault/api_admin.go:230.5,230.16 1 1 -github.com/echovault/echovault/echovault/api_admin.go:234.111,236.19 2 0 -github.com/echovault/echovault/echovault/api_admin.go:236.19,238.6 1 0 -github.com/echovault/echovault/echovault/api_admin.go:239.5,243.11 1 0 -github.com/echovault/echovault/echovault/api_admin.go:245.94,253.5 1 1 -github.com/echovault/echovault/echovault/api_admin.go:255.3,255.13 1 1 -github.com/echovault/echovault/echovault/api_admin.go:259.2,262.31 1 1 -github.com/echovault/echovault/echovault/api_admin.go:262.31,265.43 2 1 -github.com/echovault/echovault/echovault/api_admin.go:265.43,267.5 1 0 -github.com/echovault/echovault/echovault/api_admin.go:268.4,268.15 1 1 -github.com/echovault/echovault/echovault/api_admin.go:272.83,274.4 1 0 -github.com/echovault/echovault/echovault/api_admin.go:275.71,275.90 1 0 -github.com/echovault/echovault/echovault/api_admin.go:279.2,279.40 1 1 -github.com/echovault/echovault/echovault/api_admin.go:279.40,281.92 1 1 -github.com/echovault/echovault/echovault/api_admin.go:281.92,283.4 1 1 -github.com/echovault/echovault/echovault/api_admin.go:283.6,284.12 1 0 -github.com/echovault/echovault/echovault/api_admin.go:286.3,289.32 1 1 -github.com/echovault/echovault/echovault/api_admin.go:289.32,292.39 2 1 -github.com/echovault/echovault/echovault/api_admin.go:292.39,294.6 1 0 -github.com/echovault/echovault/echovault/api_admin.go:295.5,295.16 1 1 -github.com/echovault/echovault/echovault/api_admin.go:299.111,301.19 2 0 -github.com/echovault/echovault/echovault/api_admin.go:301.19,303.6 1 0 -github.com/echovault/echovault/echovault/api_admin.go:304.5,308.11 1 0 -github.com/echovault/echovault/echovault/api_admin.go:310.94,318.5 1 1 -github.com/echovault/echovault/echovault/api_admin.go:322.2,324.12 2 1 -github.com/echovault/echovault/echovault/api_admin.go:350.76,352.2 1 1 -github.com/echovault/echovault/echovault/api_admin.go:368.59,372.22 3 1 -github.com/echovault/echovault/echovault/api_admin.go:373.9,375.86 1 1 -github.com/echovault/echovault/echovault/api_admin.go:375.86,377.4 1 1 -github.com/echovault/echovault/echovault/api_admin.go:378.9,380.45 1 1 -github.com/echovault/echovault/echovault/api_admin.go:380.45,381.66 1 1 -github.com/echovault/echovault/echovault/api_admin.go:381.66,382.13 1 1 -github.com/echovault/echovault/echovault/api_admin.go:384.4,384.88 1 1 -github.com/echovault/echovault/echovault/api_admin.go:384.88,385.122 1 1 -github.com/echovault/echovault/echovault/api_admin.go:385.122,387.6 1 1 +github.com/echovault/echovault/echovault/api_acl.go:305.2,309.35 3 1 +github.com/echovault/echovault/echovault/api_acl.go:309.35,315.35 4 1 +github.com/echovault/echovault/echovault/api_acl.go:315.35,317.4 1 1 +github.com/echovault/echovault/echovault/api_acl.go:320.2,320.20 1 1 +github.com/echovault/echovault/echovault/api_acl.go:330.72,333.16 3 1 +github.com/echovault/echovault/echovault/api_acl.go:333.16,335.3 1 0 +github.com/echovault/echovault/echovault/api_acl.go:336.2,337.40 2 1 +github.com/echovault/echovault/echovault/api_acl.go:341.54,343.16 2 1 +github.com/echovault/echovault/echovault/api_acl.go:343.16,345.3 1 0 +github.com/echovault/echovault/echovault/api_acl.go:346.2,346.45 1 1 +github.com/echovault/echovault/echovault/api_acl.go:357.72,359.9 2 0 +github.com/echovault/echovault/echovault/api_acl.go:360.21,361.29 1 0 +github.com/echovault/echovault/echovault/api_acl.go:362.23,363.31 1 0 +github.com/echovault/echovault/echovault/api_acl.go:364.10,365.31 1 0 +github.com/echovault/echovault/echovault/api_acl.go:368.2,369.16 2 0 +github.com/echovault/echovault/echovault/api_acl.go:369.16,371.3 1 0 +github.com/echovault/echovault/echovault/api_acl.go:373.2,374.40 2 0 +github.com/echovault/echovault/echovault/api_acl.go:380.50,382.16 2 0 +github.com/echovault/echovault/echovault/api_acl.go:382.16,384.3 1 0 +github.com/echovault/echovault/echovault/api_acl.go:385.2,386.40 2 0 +github.com/echovault/echovault/echovault/api_admin.go:142.87,145.22 2 1 +github.com/echovault/echovault/echovault/api_admin.go:145.22,146.10 1 1 +github.com/echovault/echovault/echovault/api_admin.go:147.32,148.75 1 1 +github.com/echovault/echovault/echovault/api_admin.go:149.33,150.77 1 1 +github.com/echovault/echovault/echovault/api_admin.go:151.32,152.75 1 1 +github.com/echovault/echovault/echovault/api_admin.go:156.2,157.16 2 1 +github.com/echovault/echovault/echovault/api_admin.go:157.16,159.3 1 0 +github.com/echovault/echovault/echovault/api_admin.go:161.2,161.45 1 1 +github.com/echovault/echovault/echovault/api_admin.go:167.54,169.16 2 1 +github.com/echovault/echovault/echovault/api_admin.go:169.16,171.3 1 0 +github.com/echovault/echovault/echovault/api_admin.go:172.2,172.41 1 1 +github.com/echovault/echovault/echovault/api_admin.go:179.47,181.16 2 1 +github.com/echovault/echovault/echovault/api_admin.go:181.16,183.3 1 0 +github.com/echovault/echovault/echovault/api_admin.go:184.2,185.42 2 1 +github.com/echovault/echovault/echovault/api_admin.go:189.50,191.16 2 1 +github.com/echovault/echovault/echovault/api_admin.go:191.16,193.3 1 0 +github.com/echovault/echovault/echovault/api_admin.go:194.2,194.41 1 1 +github.com/echovault/echovault/echovault/api_admin.go:198.55,200.16 2 0 +github.com/echovault/echovault/echovault/api_admin.go:200.16,202.3 1 0 +github.com/echovault/echovault/echovault/api_admin.go:203.2,203.40 1 0 +github.com/echovault/echovault/echovault/api_admin.go:215.67,219.36 3 1 +github.com/echovault/echovault/echovault/api_admin.go:219.36,220.52 1 1 +github.com/echovault/echovault/echovault/api_admin.go:220.52,222.4 1 0 +github.com/echovault/echovault/echovault/api_admin.go:225.2,225.63 1 1 +github.com/echovault/echovault/echovault/api_admin.go:225.63,230.32 1 1 +github.com/echovault/echovault/echovault/api_admin.go:230.32,233.44 2 1 +github.com/echovault/echovault/echovault/api_admin.go:233.44,235.6 1 0 +github.com/echovault/echovault/echovault/api_admin.go:236.5,236.16 1 1 +github.com/echovault/echovault/echovault/api_admin.go:240.111,242.19 2 0 +github.com/echovault/echovault/echovault/api_admin.go:242.19,244.6 1 0 +github.com/echovault/echovault/echovault/api_admin.go:245.5,249.11 1 0 +github.com/echovault/echovault/echovault/api_admin.go:251.94,259.5 1 1 +github.com/echovault/echovault/echovault/api_admin.go:261.3,261.13 1 1 +github.com/echovault/echovault/echovault/api_admin.go:265.2,268.31 1 1 +github.com/echovault/echovault/echovault/api_admin.go:268.31,271.43 2 1 +github.com/echovault/echovault/echovault/api_admin.go:271.43,273.5 1 0 +github.com/echovault/echovault/echovault/api_admin.go:274.4,274.15 1 1 +github.com/echovault/echovault/echovault/api_admin.go:278.83,280.4 1 0 +github.com/echovault/echovault/echovault/api_admin.go:281.71,281.90 1 0 +github.com/echovault/echovault/echovault/api_admin.go:285.2,285.40 1 1 +github.com/echovault/echovault/echovault/api_admin.go:285.40,287.92 1 1 +github.com/echovault/echovault/echovault/api_admin.go:287.92,289.4 1 1 +github.com/echovault/echovault/echovault/api_admin.go:289.6,290.12 1 0 +github.com/echovault/echovault/echovault/api_admin.go:292.3,295.32 1 1 +github.com/echovault/echovault/echovault/api_admin.go:295.32,298.39 2 1 +github.com/echovault/echovault/echovault/api_admin.go:298.39,300.6 1 0 +github.com/echovault/echovault/echovault/api_admin.go:301.5,301.16 1 1 +github.com/echovault/echovault/echovault/api_admin.go:305.111,307.19 2 0 +github.com/echovault/echovault/echovault/api_admin.go:307.19,309.6 1 0 +github.com/echovault/echovault/echovault/api_admin.go:310.5,314.11 1 0 +github.com/echovault/echovault/echovault/api_admin.go:316.94,324.5 1 1 +github.com/echovault/echovault/echovault/api_admin.go:328.2,330.12 2 1 +github.com/echovault/echovault/echovault/api_admin.go:356.76,358.2 1 1 +github.com/echovault/echovault/echovault/api_admin.go:374.59,378.22 3 1 +github.com/echovault/echovault/echovault/api_admin.go:379.9,381.86 1 1 +github.com/echovault/echovault/echovault/api_admin.go:381.86,383.4 1 1 +github.com/echovault/echovault/echovault/api_admin.go:384.9,386.45 1 1 +github.com/echovault/echovault/echovault/api_admin.go:386.45,387.66 1 1 +github.com/echovault/echovault/echovault/api_admin.go:387.66,388.13 1 1 +github.com/echovault/echovault/echovault/api_admin.go:390.4,390.88 1 1 +github.com/echovault/echovault/echovault/api_admin.go:390.88,391.122 1 1 +github.com/echovault/echovault/echovault/api_admin.go:391.122,393.6 1 1 github.com/echovault/echovault/echovault/api_generic.go:88.91,91.9 2 1 github.com/echovault/echovault/echovault/api_generic.go:92.18,93.26 1 1 github.com/echovault/echovault/echovault/api_generic.go:94.18,95.26 1 1 @@ -3027,9 +3069,9 @@ github.com/echovault/echovault/echovault/api_hash.go:79.36,81.3 1 1 github.com/echovault/echovault/echovault/api_hash.go:83.2,84.16 2 1 github.com/echovault/echovault/echovault/api_hash.go:84.16,86.3 1 0 github.com/echovault/echovault/echovault/api_hash.go:88.2,88.41 1 1 -github.com/echovault/echovault/echovault/api_hash.go:104.79,112.16 2 0 -github.com/echovault/echovault/echovault/api_hash.go:112.16,114.3 1 0 -github.com/echovault/echovault/echovault/api_hash.go:115.2,115.45 1 0 +github.com/echovault/echovault/echovault/api_hash.go:104.79,112.16 2 1 +github.com/echovault/echovault/echovault/api_hash.go:112.16,114.3 1 1 +github.com/echovault/echovault/echovault/api_hash.go:115.2,115.45 1 1 github.com/echovault/echovault/echovault/api_hash.go:132.79,136.16 3 1 github.com/echovault/echovault/echovault/api_hash.go:136.16,138.3 1 1 github.com/echovault/echovault/echovault/api_hash.go:140.2,140.46 1 1 @@ -3104,54 +3146,56 @@ github.com/echovault/echovault/echovault/api_list.go:294.2,294.41 1 1 github.com/echovault/echovault/echovault/api_list.go:310.76,313.16 3 1 github.com/echovault/echovault/echovault/api_list.go:313.16,315.3 1 1 github.com/echovault/echovault/echovault/api_list.go:316.2,316.41 1 0 -github.com/echovault/echovault/echovault/api_pubsub.go:50.86,52.24 1 0 -github.com/echovault/echovault/echovault/api_pubsub.go:52.24,54.3 1 0 -github.com/echovault/echovault/echovault/api_pubsub.go:57.2,59.36 3 0 -github.com/echovault/echovault/echovault/api_pubsub.go:59.36,65.3 2 0 -github.com/echovault/echovault/echovault/api_pubsub.go:68.2,69.12 2 0 -github.com/echovault/echovault/echovault/api_pubsub.go:69.12,71.3 1 0 -github.com/echovault/echovault/echovault/api_pubsub.go:73.2,73.25 1 0 -github.com/echovault/echovault/echovault/api_pubsub.go:73.25,78.33 4 0 -github.com/echovault/echovault/echovault/api_pubsub.go:78.33,80.4 1 0 -github.com/echovault/echovault/echovault/api_pubsub.go:82.3,82.13 1 0 -github.com/echovault/echovault/echovault/api_pubsub.go:93.70,94.24 1 0 -github.com/echovault/echovault/echovault/api_pubsub.go:94.24,96.3 1 0 -github.com/echovault/echovault/echovault/api_pubsub.go:98.2,98.36 1 0 -github.com/echovault/echovault/echovault/api_pubsub.go:98.36,100.3 1 0 -github.com/echovault/echovault/echovault/api_pubsub.go:102.2,103.115 2 0 -github.com/echovault/echovault/echovault/api_pubsub.go:116.87,118.24 1 0 -github.com/echovault/echovault/echovault/api_pubsub.go:118.24,120.3 1 0 -github.com/echovault/echovault/echovault/api_pubsub.go:123.2,125.36 3 0 -github.com/echovault/echovault/echovault/api_pubsub.go:125.36,131.3 2 0 -github.com/echovault/echovault/echovault/api_pubsub.go:134.2,135.12 2 0 -github.com/echovault/echovault/echovault/api_pubsub.go:135.12,137.3 1 0 -github.com/echovault/echovault/echovault/api_pubsub.go:139.2,139.25 1 0 -github.com/echovault/echovault/echovault/api_pubsub.go:139.25,144.33 4 0 -github.com/echovault/echovault/echovault/api_pubsub.go:144.33,146.4 1 0 -github.com/echovault/echovault/echovault/api_pubsub.go:148.3,148.13 1 0 -github.com/echovault/echovault/echovault/api_pubsub.go:159.71,160.24 1 0 -github.com/echovault/echovault/echovault/api_pubsub.go:160.24,162.3 1 0 -github.com/echovault/echovault/echovault/api_pubsub.go:164.2,164.36 1 0 -github.com/echovault/echovault/echovault/api_pubsub.go:164.36,166.3 1 0 -github.com/echovault/echovault/echovault/api_pubsub.go:168.2,169.115 2 0 -github.com/echovault/echovault/echovault/api_pubsub.go:182.73,184.16 2 0 -github.com/echovault/echovault/echovault/api_pubsub.go:184.16,186.3 1 0 -github.com/echovault/echovault/echovault/api_pubsub.go:187.2,188.40 2 0 -github.com/echovault/echovault/echovault/api_pubsub.go:198.75,200.19 2 0 -github.com/echovault/echovault/echovault/api_pubsub.go:200.19,202.3 1 0 -github.com/echovault/echovault/echovault/api_pubsub.go:203.2,204.16 2 0 -github.com/echovault/echovault/echovault/api_pubsub.go:204.16,206.3 1 0 -github.com/echovault/echovault/echovault/api_pubsub.go:207.2,207.45 1 0 -github.com/echovault/echovault/echovault/api_pubsub.go:213.54,215.16 2 0 -github.com/echovault/echovault/echovault/api_pubsub.go:215.16,217.3 1 0 -github.com/echovault/echovault/echovault/api_pubsub.go:218.2,218.41 1 0 -github.com/echovault/echovault/echovault/api_pubsub.go:228.82,232.16 3 0 -github.com/echovault/echovault/echovault/api_pubsub.go:232.16,234.3 1 0 -github.com/echovault/echovault/echovault/api_pubsub.go:236.2,238.16 3 0 -github.com/echovault/echovault/echovault/api_pubsub.go:238.16,240.3 1 0 -github.com/echovault/echovault/echovault/api_pubsub.go:242.2,245.28 3 0 -github.com/echovault/echovault/echovault/api_pubsub.go:245.28,248.3 2 0 -github.com/echovault/echovault/echovault/api_pubsub.go:250.2,250.20 1 0 +github.com/echovault/echovault/echovault/api_pubsub.go:50.86,52.24 1 1 +github.com/echovault/echovault/echovault/api_pubsub.go:52.24,54.3 1 1 +github.com/echovault/echovault/echovault/api_pubsub.go:56.2,58.36 3 1 +github.com/echovault/echovault/echovault/api_pubsub.go:58.36,65.3 2 1 +github.com/echovault/echovault/echovault/api_pubsub.go:65.8,69.3 2 1 +github.com/echovault/echovault/echovault/api_pubsub.go:72.2,73.12 2 1 +github.com/echovault/echovault/echovault/api_pubsub.go:73.12,75.3 1 1 +github.com/echovault/echovault/echovault/api_pubsub.go:77.2,77.25 1 1 +github.com/echovault/echovault/echovault/api_pubsub.go:77.25,82.33 4 1 +github.com/echovault/echovault/echovault/api_pubsub.go:82.33,84.4 1 1 +github.com/echovault/echovault/echovault/api_pubsub.go:86.3,86.13 1 1 +github.com/echovault/echovault/echovault/api_pubsub.go:97.70,98.24 1 1 +github.com/echovault/echovault/echovault/api_pubsub.go:98.24,100.3 1 0 +github.com/echovault/echovault/echovault/api_pubsub.go:102.2,102.36 1 1 +github.com/echovault/echovault/echovault/api_pubsub.go:102.36,104.3 1 0 +github.com/echovault/echovault/echovault/api_pubsub.go:106.2,107.115 2 1 +github.com/echovault/echovault/echovault/api_pubsub.go:120.87,122.24 1 1 +github.com/echovault/echovault/echovault/api_pubsub.go:122.24,124.3 1 0 +github.com/echovault/echovault/echovault/api_pubsub.go:126.2,128.36 3 1 +github.com/echovault/echovault/echovault/api_pubsub.go:128.36,135.3 2 1 +github.com/echovault/echovault/echovault/api_pubsub.go:135.8,139.3 2 1 +github.com/echovault/echovault/echovault/api_pubsub.go:142.2,143.12 2 1 +github.com/echovault/echovault/echovault/api_pubsub.go:143.12,145.3 1 1 +github.com/echovault/echovault/echovault/api_pubsub.go:147.2,147.25 1 1 +github.com/echovault/echovault/echovault/api_pubsub.go:147.25,152.33 4 1 +github.com/echovault/echovault/echovault/api_pubsub.go:152.33,154.4 1 1 +github.com/echovault/echovault/echovault/api_pubsub.go:156.3,156.13 1 1 +github.com/echovault/echovault/echovault/api_pubsub.go:167.71,168.24 1 1 +github.com/echovault/echovault/echovault/api_pubsub.go:168.24,170.3 1 0 +github.com/echovault/echovault/echovault/api_pubsub.go:172.2,172.36 1 1 +github.com/echovault/echovault/echovault/api_pubsub.go:172.36,174.3 1 0 +github.com/echovault/echovault/echovault/api_pubsub.go:176.2,177.115 2 1 +github.com/echovault/echovault/echovault/api_pubsub.go:190.73,192.16 2 1 +github.com/echovault/echovault/echovault/api_pubsub.go:192.16,194.3 1 0 +github.com/echovault/echovault/echovault/api_pubsub.go:195.2,196.40 2 1 +github.com/echovault/echovault/echovault/api_pubsub.go:206.75,208.19 2 1 +github.com/echovault/echovault/echovault/api_pubsub.go:208.19,210.3 1 1 +github.com/echovault/echovault/echovault/api_pubsub.go:211.2,212.16 2 1 +github.com/echovault/echovault/echovault/api_pubsub.go:212.16,214.3 1 0 +github.com/echovault/echovault/echovault/api_pubsub.go:215.2,215.45 1 1 +github.com/echovault/echovault/echovault/api_pubsub.go:221.54,223.16 2 1 +github.com/echovault/echovault/echovault/api_pubsub.go:223.16,225.3 1 0 +github.com/echovault/echovault/echovault/api_pubsub.go:226.2,226.41 1 1 +github.com/echovault/echovault/echovault/api_pubsub.go:236.83,240.16 3 1 +github.com/echovault/echovault/echovault/api_pubsub.go:240.16,242.3 1 0 +github.com/echovault/echovault/echovault/api_pubsub.go:244.2,246.16 3 1 +github.com/echovault/echovault/echovault/api_pubsub.go:246.16,248.3 1 0 +github.com/echovault/echovault/echovault/api_pubsub.go:250.2,253.28 3 1 +github.com/echovault/echovault/echovault/api_pubsub.go:253.28,256.3 2 1 +github.com/echovault/echovault/echovault/api_pubsub.go:258.2,258.20 1 1 github.com/echovault/echovault/echovault/api_set.go:36.75,39.16 3 1 github.com/echovault/echovault/echovault/api_set.go:39.16,41.3 1 1 github.com/echovault/echovault/echovault/api_set.go:42.2,42.41 1 1 @@ -3209,151 +3253,151 @@ github.com/echovault/echovault/echovault/api_sorted_set.go:110.18,112.5 1 0 github.com/echovault/echovault/echovault/api_sorted_set.go:113.4,114.12 2 1 github.com/echovault/echovault/echovault/api_sorted_set.go:116.3,116.23 1 1 github.com/echovault/echovault/echovault/api_sorted_set.go:118.2,118.20 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:121.85,123.28 2 0 -github.com/echovault/echovault/echovault/api_sorted_set.go:123.28,125.17 2 0 -github.com/echovault/echovault/echovault/api_sorted_set.go:125.17,127.4 1 0 -github.com/echovault/echovault/echovault/api_sorted_set.go:128.3,129.17 2 0 -github.com/echovault/echovault/echovault/api_sorted_set.go:129.17,131.18 2 0 -github.com/echovault/echovault/echovault/api_sorted_set.go:131.18,133.5 1 0 -github.com/echovault/echovault/echovault/api_sorted_set.go:134.4,134.24 1 0 -github.com/echovault/echovault/echovault/api_sorted_set.go:137.2,137.20 1 0 -github.com/echovault/echovault/echovault/api_sorted_set.go:161.105,164.9 2 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:165.18,166.26 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:167.18,168.26 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:171.2,171.9 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:172.18,173.26 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:174.18,175.26 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:178.2,178.16 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:178.16,180.3 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:182.2,182.18 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:182.18,184.3 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:186.2,186.37 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:186.37,188.3 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:190.2,191.16 2 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:191.16,193.3 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:142.105,145.9 2 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:146.18,147.26 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:148.18,149.26 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:152.2,152.9 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:153.18,154.26 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:155.18,156.26 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:159.2,159.16 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:159.16,161.3 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:163.2,163.18 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:163.18,165.3 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:167.2,167.37 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:167.37,169.3 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:171.2,172.16 2 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:172.16,174.3 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:176.2,176.41 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:190.57,192.16 2 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:192.16,194.3 1 1 github.com/echovault/echovault/echovault/api_sorted_set.go:195.2,195.41 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:209.57,211.16 2 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:211.16,213.3 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:214.2,214.41 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:232.76,240.16 3 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:240.16,242.3 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:243.2,243.41 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:261.93,263.16 2 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:263.16,265.3 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:266.2,267.16 2 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:267.16,269.3 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:271.2,272.16 2 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:272.16,274.3 1 0 -github.com/echovault/echovault/echovault/api_sorted_set.go:276.2,276.45 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:293.86,296.16 3 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:296.16,298.3 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:299.2,299.41 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:316.99,319.30 2 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:319.30,321.45 2 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:321.45,323.4 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:326.2,326.29 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:326.29,328.3 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:330.2,330.24 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:330.24,332.3 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:334.2,335.16 2 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:335.16,337.3 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:339.2,340.16 2 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:340.16,342.3 1 0 -github.com/echovault/echovault/echovault/api_sorted_set.go:344.2,344.53 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:363.114,366.30 2 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:366.30,368.42 2 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:368.42,370.4 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:373.2,373.29 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:373.29,375.3 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:377.2,377.24 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:377.24,379.3 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:381.2,382.16 2 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:382.16,384.3 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:386.2,386.41 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:403.99,406.30 2 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:406.30,408.42 2 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:408.42,410.4 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:413.2,413.29 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:413.29,415.3 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:417.2,417.24 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:417.24,419.3 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:421.2,422.16 2 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:422.16,424.3 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:426.2,427.16 2 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:427.16,429.3 1 0 -github.com/echovault/echovault/echovault/api_sorted_set.go:431.2,431.53 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:450.114,453.30 2 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:453.30,455.42 2 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:455.42,457.4 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:460.2,460.29 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:460.29,462.3 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:464.2,464.24 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:464.24,466.3 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:468.2,469.16 2 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:469.16,471.3 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:473.2,473.41 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:492.97,495.16 3 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:495.16,497.3 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:498.2,499.16 2 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:499.16,501.3 1 0 -github.com/echovault/echovault/echovault/api_sorted_set.go:502.2,502.15 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:519.89,522.9 2 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:523.19,524.27 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:525.19,526.27 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:527.10,528.27 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:531.2,531.9 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:532.26,533.76 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:534.10,535.59 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:538.2,539.16 2 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:539.16,541.3 1 0 -github.com/echovault/echovault/echovault/api_sorted_set.go:543.2,543.51 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:561.88,563.33 2 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:563.33,565.3 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:567.2,568.16 2 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:568.16,570.3 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:572.2,573.16 2 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:573.16,575.3 1 0 -github.com/echovault/echovault/echovault/api_sorted_set.go:577.2,578.24 2 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:578.24,579.14 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:579.14,581.12 2 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:583.3,584.17 2 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:584.17,586.4 1 0 -github.com/echovault/echovault/echovault/api_sorted_set.go:587.3,587.20 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:590.2,590.20 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:610.71,613.16 3 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:613.16,615.3 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:616.2,616.41 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:635.78,637.16 2 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:637.16,639.3 1 0 -github.com/echovault/echovault/echovault/api_sorted_set.go:640.2,640.51 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:659.78,661.16 2 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:661.16,663.3 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:664.2,664.51 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:688.98,690.16 2 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:690.16,692.3 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:693.2,693.16 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:693.16,695.3 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:697.2,698.16 2 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:698.16,700.3 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:702.2,702.51 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:723.101,725.16 2 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:725.16,727.3 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:729.2,730.16 2 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:730.16,732.3 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:734.2,736.19 2 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:736.19,738.3 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:740.2,741.16 2 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:741.16,743.3 1 0 -github.com/echovault/echovault/echovault/api_sorted_set.go:745.2,747.16 2 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:747.16,749.17 2 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:749.17,751.4 1 0 -github.com/echovault/echovault/echovault/api_sorted_set.go:752.3,752.13 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:755.2,755.17 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:760.104,762.16 2 0 -github.com/echovault/echovault/echovault/api_sorted_set.go:762.16,764.3 1 0 -github.com/echovault/echovault/echovault/api_sorted_set.go:766.2,767.16 2 0 -github.com/echovault/echovault/echovault/api_sorted_set.go:767.16,769.3 1 0 -github.com/echovault/echovault/echovault/api_sorted_set.go:771.2,773.46 2 0 +github.com/echovault/echovault/echovault/api_sorted_set.go:213.76,221.16 3 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:221.16,223.3 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:224.2,224.41 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:242.93,244.16 2 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:244.16,246.3 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:247.2,248.16 2 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:248.16,250.3 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:252.2,253.16 2 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:253.16,255.3 1 0 +github.com/echovault/echovault/echovault/api_sorted_set.go:257.2,257.45 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:274.86,277.16 3 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:277.16,279.3 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:280.2,280.41 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:297.99,300.30 2 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:300.30,302.45 2 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:302.45,304.4 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:307.2,307.29 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:307.29,309.3 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:311.2,311.24 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:311.24,313.3 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:315.2,316.16 2 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:316.16,318.3 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:320.2,321.16 2 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:321.16,323.3 1 0 +github.com/echovault/echovault/echovault/api_sorted_set.go:325.2,325.53 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:344.114,347.30 2 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:347.30,349.42 2 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:349.42,351.4 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:354.2,354.29 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:354.29,356.3 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:358.2,358.24 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:358.24,360.3 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:362.2,363.16 2 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:363.16,365.3 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:367.2,367.41 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:384.99,387.30 2 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:387.30,389.42 2 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:389.42,391.4 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:394.2,394.29 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:394.29,396.3 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:398.2,398.24 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:398.24,400.3 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:402.2,403.16 2 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:403.16,405.3 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:407.2,408.16 2 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:408.16,410.3 1 0 +github.com/echovault/echovault/echovault/api_sorted_set.go:412.2,412.53 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:431.114,434.30 2 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:434.30,436.42 2 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:436.42,438.4 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:441.2,441.29 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:441.29,443.3 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:445.2,445.24 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:445.24,447.3 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:449.2,450.16 2 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:450.16,452.3 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:454.2,454.41 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:473.97,476.16 3 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:476.16,478.3 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:479.2,480.16 2 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:480.16,482.3 1 0 +github.com/echovault/echovault/echovault/api_sorted_set.go:483.2,483.15 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:500.89,503.9 2 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:504.19,505.27 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:506.19,507.27 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:508.10,509.27 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:512.2,512.9 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:513.26,514.76 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:515.10,516.59 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:519.2,520.16 2 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:520.16,522.3 1 0 +github.com/echovault/echovault/echovault/api_sorted_set.go:524.2,524.51 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:542.88,544.33 2 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:544.33,546.3 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:548.2,549.16 2 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:549.16,551.3 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:553.2,554.16 2 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:554.16,556.3 1 0 +github.com/echovault/echovault/echovault/api_sorted_set.go:558.2,559.24 2 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:559.24,560.14 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:560.14,562.12 2 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:564.3,565.17 2 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:565.17,567.4 1 0 +github.com/echovault/echovault/echovault/api_sorted_set.go:568.3,568.20 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:571.2,571.20 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:591.71,594.16 3 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:594.16,596.3 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:597.2,597.41 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:616.78,618.16 2 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:618.16,620.3 1 0 +github.com/echovault/echovault/echovault/api_sorted_set.go:621.2,621.51 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:640.78,642.16 2 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:642.16,644.3 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:645.2,645.51 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:669.98,671.16 2 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:671.16,673.3 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:674.2,674.16 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:674.16,676.3 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:678.2,679.16 2 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:679.16,681.3 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:683.2,683.51 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:704.101,706.16 2 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:706.16,708.3 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:710.2,711.16 2 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:711.16,713.3 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:715.2,717.19 2 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:717.19,719.3 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:721.2,722.16 2 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:722.16,724.3 1 0 +github.com/echovault/echovault/echovault/api_sorted_set.go:726.2,728.16 2 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:728.16,730.17 2 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:730.17,732.4 1 0 +github.com/echovault/echovault/echovault/api_sorted_set.go:733.3,733.13 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:736.2,736.17 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:741.104,743.16 2 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:743.16,745.3 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:747.2,748.16 2 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:748.16,750.3 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:752.2,754.19 2 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:754.19,756.3 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:758.2,759.16 2 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:759.16,761.3 1 0 +github.com/echovault/echovault/echovault/api_sorted_set.go:763.2,765.16 2 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:765.16,767.17 2 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:767.17,769.4 1 0 +github.com/echovault/echovault/echovault/api_sorted_set.go:770.3,770.13 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:773.2,773.17 1 1 github.com/echovault/echovault/echovault/api_sorted_set.go:790.81,793.16 3 1 github.com/echovault/echovault/echovault/api_sorted_set.go:793.16,795.3 1 1 github.com/echovault/echovault/echovault/api_sorted_set.go:797.2,798.16 2 1 @@ -3371,12 +3415,12 @@ github.com/echovault/echovault/echovault/api_sorted_set.go:836.2,836.41 1 1 github.com/echovault/echovault/echovault/api_sorted_set.go:854.94,863.16 3 1 github.com/echovault/echovault/echovault/api_sorted_set.go:863.16,865.3 1 1 github.com/echovault/echovault/echovault/api_sorted_set.go:867.2,867.41 1 1 -github.com/echovault/echovault/echovault/api_sorted_set.go:885.76,892.16 2 0 -github.com/echovault/echovault/echovault/api_sorted_set.go:892.16,894.3 1 0 -github.com/echovault/echovault/echovault/api_sorted_set.go:895.2,895.41 1 0 -github.com/echovault/echovault/echovault/api_sorted_set.go:913.81,920.16 2 0 -github.com/echovault/echovault/echovault/api_sorted_set.go:920.16,922.3 1 0 -github.com/echovault/echovault/echovault/api_sorted_set.go:923.2,923.41 1 0 +github.com/echovault/echovault/echovault/api_sorted_set.go:885.76,892.16 2 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:892.16,894.3 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:895.2,895.41 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:913.81,920.16 2 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:920.16,922.3 1 1 +github.com/echovault/echovault/echovault/api_sorted_set.go:923.2,923.41 1 1 github.com/echovault/echovault/echovault/api_sorted_set.go:943.109,946.9 2 1 github.com/echovault/echovault/echovault/api_sorted_set.go:947.23,948.31 1 1 github.com/echovault/echovault/echovault/api_sorted_set.go:949.21,950.29 1 1 @@ -3446,13 +3490,13 @@ github.com/echovault/echovault/echovault/echovault.go:162.48,163.52 1 0 github.com/echovault/echovault/echovault/echovault.go:163.52,165.12 2 0 github.com/echovault/echovault/echovault/echovault.go:167.3,167.41 1 0 github.com/echovault/echovault/echovault/echovault.go:171.2,171.52 1 1 -github.com/echovault/echovault/echovault/echovault.go:171.52,173.3 1 0 +github.com/echovault/echovault/echovault/echovault.go:171.52,173.3 1 1 github.com/echovault/echovault/echovault/echovault.go:176.2,176.42 1 1 github.com/echovault/echovault/echovault/echovault.go:176.42,178.3 1 1 github.com/echovault/echovault/echovault/echovault.go:181.2,182.40 2 1 github.com/echovault/echovault/echovault/echovault.go:182.40,184.3 1 1 github.com/echovault/echovault/echovault/echovault.go:187.2,188.43 2 1 -github.com/echovault/echovault/echovault/echovault.go:188.43,190.3 1 0 +github.com/echovault/echovault/echovault/echovault.go:188.43,190.3 1 1 github.com/echovault/echovault/echovault/echovault.go:192.2,192.29 1 1 github.com/echovault/echovault/echovault/echovault.go:192.29,202.38 1 1 github.com/echovault/echovault/echovault/echovault.go:202.38,206.5 3 0 @@ -3462,10 +3506,10 @@ github.com/echovault/echovault/echovault/echovault.go:210.46,212.7 1 0 github.com/echovault/echovault/echovault/echovault.go:214.5,214.17 1 0 github.com/echovault/echovault/echovault/echovault.go:217.3,225.5 1 1 github.com/echovault/echovault/echovault/echovault.go:226.8,237.65 1 1 -github.com/echovault/echovault/echovault/echovault.go:237.65,239.44 2 0 +github.com/echovault/echovault/echovault/echovault.go:237.65,239.44 2 1 github.com/echovault/echovault/echovault/echovault.go:239.44,240.46 1 0 github.com/echovault/echovault/echovault/echovault.go:240.46,242.7 1 0 -github.com/echovault/echovault/echovault/echovault.go:244.5,244.17 1 0 +github.com/echovault/echovault/echovault/echovault.go:244.5,244.17 1 1 github.com/echovault/echovault/echovault/echovault.go:246.72,248.93 2 0 github.com/echovault/echovault/echovault/echovault.go:248.93,250.6 1 0 github.com/echovault/echovault/echovault/echovault.go:251.5,251.56 1 0 @@ -3553,20 +3597,20 @@ github.com/echovault/echovault/echovault/echovault.go:477.4,477.27 1 0 github.com/echovault/echovault/echovault/echovault.go:481.2,481.37 1 1 github.com/echovault/echovault/echovault/echovault.go:481.37,483.3 1 0 github.com/echovault/echovault/echovault/echovault.go:491.34,493.2 1 1 -github.com/echovault/echovault/echovault/echovault.go:496.47,497.38 1 0 +github.com/echovault/echovault/echovault/echovault.go:496.47,497.38 1 1 github.com/echovault/echovault/echovault/echovault.go:497.38,499.3 1 0 -github.com/echovault/echovault/echovault/echovault.go:501.2,501.12 1 0 -github.com/echovault/echovault/echovault/echovault.go:501.12,502.27 1 0 +github.com/echovault/echovault/echovault/echovault.go:501.2,501.12 1 1 +github.com/echovault/echovault/echovault/echovault.go:501.12,502.27 1 1 github.com/echovault/echovault/echovault/echovault.go:502.27,504.53 1 0 github.com/echovault/echovault/echovault/echovault.go:504.53,506.5 1 0 github.com/echovault/echovault/echovault/echovault.go:507.4,507.10 1 0 -github.com/echovault/echovault/echovault/echovault.go:510.3,510.62 1 0 +github.com/echovault/echovault/echovault/echovault.go:510.3,510.62 1 1 github.com/echovault/echovault/echovault/echovault.go:510.62,512.4 1 0 -github.com/echovault/echovault/echovault/echovault.go:515.2,515.12 1 0 -github.com/echovault/echovault/echovault/echovault.go:518.42,520.2 1 0 -github.com/echovault/echovault/echovault/echovault.go:522.43,524.2 1 0 -github.com/echovault/echovault/echovault/echovault.go:526.56,528.2 1 0 -github.com/echovault/echovault/echovault/echovault.go:531.56,533.2 1 0 +github.com/echovault/echovault/echovault/echovault.go:515.2,515.12 1 1 +github.com/echovault/echovault/echovault/echovault.go:518.42,520.2 1 1 +github.com/echovault/echovault/echovault/echovault.go:522.43,524.2 1 1 +github.com/echovault/echovault/echovault/echovault.go:526.56,528.2 1 1 +github.com/echovault/echovault/echovault/echovault.go:531.56,533.2 1 1 github.com/echovault/echovault/echovault/echovault.go:535.44,537.2 1 0 github.com/echovault/echovault/echovault/echovault.go:539.45,541.2 1 0 github.com/echovault/echovault/echovault/echovault.go:544.45,545.40 1 0 @@ -3584,144 +3628,144 @@ github.com/echovault/echovault/echovault/keyspace.go:43.2,43.15 1 1 github.com/echovault/echovault/echovault/keyspace.go:46.58,51.9 4 1 github.com/echovault/echovault/echovault/keyspace.go:51.9,53.3 1 0 github.com/echovault/echovault/echovault/keyspace.go:55.2,55.23 1 1 -github.com/echovault/echovault/echovault/keyspace.go:58.95,64.27 4 1 -github.com/echovault/echovault/echovault/keyspace.go:64.27,66.10 2 1 -github.com/echovault/echovault/echovault/keyspace.go:66.10,68.12 2 1 -github.com/echovault/echovault/echovault/keyspace.go:71.3,71.83 1 1 -github.com/echovault/echovault/echovault/keyspace.go:71.83,72.29 1 0 -github.com/echovault/echovault/echovault/keyspace.go:72.29,75.19 2 0 -github.com/echovault/echovault/echovault/keyspace.go:75.19,77.6 1 0 -github.com/echovault/echovault/echovault/keyspace.go:78.10,78.65 1 0 -github.com/echovault/echovault/echovault/keyspace.go:78.65,81.19 2 0 -github.com/echovault/echovault/echovault/keyspace.go:81.19,83.6 1 0 -github.com/echovault/echovault/echovault/keyspace.go:84.10,84.66 1 0 -github.com/echovault/echovault/echovault/keyspace.go:84.66,89.5 1 0 -github.com/echovault/echovault/echovault/keyspace.go:90.4,91.12 2 0 -github.com/echovault/echovault/echovault/keyspace.go:94.3,94.28 1 1 -github.com/echovault/echovault/echovault/keyspace.go:98.2,98.46 1 1 -github.com/echovault/echovault/echovault/keyspace.go:98.46,99.61 1 1 -github.com/echovault/echovault/echovault/keyspace.go:99.61,101.4 1 0 -github.com/echovault/echovault/echovault/keyspace.go:104.2,104.15 1 1 -github.com/echovault/echovault/echovault/keyspace.go:107.95,111.115 3 1 -github.com/echovault/echovault/echovault/keyspace.go:111.115,113.3 1 0 -github.com/echovault/echovault/echovault/keyspace.go:115.2,115.34 1 1 -github.com/echovault/echovault/echovault/keyspace.go:115.34,117.37 2 1 -github.com/echovault/echovault/echovault/keyspace.go:117.37,119.4 1 1 -github.com/echovault/echovault/echovault/keyspace.go:120.3,124.28 2 1 -github.com/echovault/echovault/echovault/keyspace.go:124.28,126.4 1 1 -github.com/echovault/echovault/echovault/keyspace.go:130.2,130.63 1 1 -github.com/echovault/echovault/echovault/keyspace.go:130.63,131.31 1 1 -github.com/echovault/echovault/echovault/keyspace.go:131.31,133.18 2 1 -github.com/echovault/echovault/echovault/keyspace.go:133.18,135.5 1 0 -github.com/echovault/echovault/echovault/keyspace.go:139.2,139.12 1 1 -github.com/echovault/echovault/echovault/keyspace.go:142.101,153.55 5 1 -github.com/echovault/echovault/echovault/keyspace.go:153.55,155.3 1 1 -github.com/echovault/echovault/echovault/keyspace.go:156.2,159.11 2 1 -github.com/echovault/echovault/echovault/keyspace.go:159.11,160.44 1 1 -github.com/echovault/echovault/echovault/keyspace.go:160.44,162.18 2 1 -github.com/echovault/echovault/echovault/keyspace.go:162.18,164.5 1 0 -github.com/echovault/echovault/echovault/keyspace.go:169.54,176.97 4 1 -github.com/echovault/echovault/echovault/keyspace.go:176.97,178.3 1 1 -github.com/echovault/echovault/echovault/keyspace.go:181.2,181.9 1 1 -github.com/echovault/echovault/echovault/keyspace.go:182.108,183.36 1 0 +github.com/echovault/echovault/echovault/keyspace.go:58.95,66.27 5 1 +github.com/echovault/echovault/echovault/keyspace.go:66.27,68.10 2 1 +github.com/echovault/echovault/echovault/keyspace.go:68.10,70.12 2 1 +github.com/echovault/echovault/echovault/keyspace.go:73.3,73.83 1 1 +github.com/echovault/echovault/echovault/keyspace.go:73.83,74.29 1 0 +github.com/echovault/echovault/echovault/keyspace.go:74.29,77.19 2 0 +github.com/echovault/echovault/echovault/keyspace.go:77.19,79.6 1 0 +github.com/echovault/echovault/echovault/keyspace.go:80.10,80.65 1 0 +github.com/echovault/echovault/echovault/keyspace.go:80.65,83.19 2 0 +github.com/echovault/echovault/echovault/keyspace.go:83.19,85.6 1 0 +github.com/echovault/echovault/echovault/keyspace.go:86.10,86.66 1 0 +github.com/echovault/echovault/echovault/keyspace.go:86.66,91.5 1 0 +github.com/echovault/echovault/echovault/keyspace.go:92.4,93.12 2 0 +github.com/echovault/echovault/echovault/keyspace.go:96.3,96.28 1 1 +github.com/echovault/echovault/echovault/keyspace.go:100.2,100.46 1 1 +github.com/echovault/echovault/echovault/keyspace.go:100.46,101.61 1 1 +github.com/echovault/echovault/echovault/keyspace.go:101.61,103.4 1 0 +github.com/echovault/echovault/echovault/keyspace.go:106.2,106.15 1 1 +github.com/echovault/echovault/echovault/keyspace.go:109.95,113.115 3 1 +github.com/echovault/echovault/echovault/keyspace.go:113.115,115.3 1 0 +github.com/echovault/echovault/echovault/keyspace.go:117.2,117.34 1 1 +github.com/echovault/echovault/echovault/keyspace.go:117.34,119.37 2 1 +github.com/echovault/echovault/echovault/keyspace.go:119.37,121.4 1 1 +github.com/echovault/echovault/echovault/keyspace.go:122.3,126.28 2 1 +github.com/echovault/echovault/echovault/keyspace.go:126.28,128.4 1 1 +github.com/echovault/echovault/echovault/keyspace.go:132.2,132.63 1 1 +github.com/echovault/echovault/echovault/keyspace.go:132.63,133.31 1 1 +github.com/echovault/echovault/echovault/keyspace.go:133.31,135.18 2 1 +github.com/echovault/echovault/echovault/keyspace.go:135.18,137.5 1 0 +github.com/echovault/echovault/echovault/keyspace.go:141.2,141.12 1 1 +github.com/echovault/echovault/echovault/keyspace.go:144.101,155.55 5 1 +github.com/echovault/echovault/echovault/keyspace.go:155.55,157.3 1 1 +github.com/echovault/echovault/echovault/keyspace.go:158.2,161.11 2 1 +github.com/echovault/echovault/echovault/keyspace.go:161.11,162.44 1 1 +github.com/echovault/echovault/echovault/keyspace.go:162.44,164.18 2 1 +github.com/echovault/echovault/echovault/keyspace.go:164.18,166.5 1 0 +github.com/echovault/echovault/echovault/keyspace.go:171.54,178.97 4 1 +github.com/echovault/echovault/echovault/keyspace.go:178.97,180.3 1 1 +github.com/echovault/echovault/echovault/keyspace.go:183.2,183.9 1 1 github.com/echovault/echovault/echovault/keyspace.go:184.108,185.36 1 0 -github.com/echovault/echovault/echovault/keyspace.go:188.2,190.12 2 1 -github.com/echovault/echovault/echovault/keyspace.go:193.60,195.6 1 0 -github.com/echovault/echovault/echovault/keyspace.go:195.6,196.83 1 0 -github.com/echovault/echovault/echovault/keyspace.go:196.83,198.9 2 0 -github.com/echovault/echovault/echovault/keyspace.go:201.2,202.33 2 0 -github.com/echovault/echovault/echovault/keyspace.go:202.33,204.3 1 0 -github.com/echovault/echovault/echovault/keyspace.go:205.2,206.13 2 0 -github.com/echovault/echovault/echovault/keyspace.go:211.86,212.27 1 1 -github.com/echovault/echovault/echovault/keyspace.go:212.27,214.84 1 1 -github.com/echovault/echovault/echovault/keyspace.go:214.84,216.4 1 1 -github.com/echovault/echovault/echovault/keyspace.go:218.3,218.35 1 1 -github.com/echovault/echovault/echovault/keyspace.go:218.35,220.4 1 1 -github.com/echovault/echovault/echovault/keyspace.go:221.3,221.56 1 0 -github.com/echovault/echovault/echovault/keyspace.go:222.29,225.34 3 0 -github.com/echovault/echovault/echovault/keyspace.go:226.29,229.34 3 0 -github.com/echovault/echovault/echovault/keyspace.go:230.30,232.51 2 0 -github.com/echovault/echovault/echovault/keyspace.go:232.51,234.5 1 0 -github.com/echovault/echovault/echovault/keyspace.go:235.4,235.34 1 0 -github.com/echovault/echovault/echovault/keyspace.go:236.30,238.51 2 0 -github.com/echovault/echovault/echovault/keyspace.go:238.51,240.5 1 0 -github.com/echovault/echovault/echovault/keyspace.go:241.4,241.34 1 0 -github.com/echovault/echovault/echovault/keyspace.go:243.3,243.55 1 0 -github.com/echovault/echovault/echovault/keyspace.go:243.55,245.4 1 0 -github.com/echovault/echovault/echovault/keyspace.go:247.2,247.12 1 0 -github.com/echovault/echovault/echovault/keyspace.go:251.71,253.34 1 0 -github.com/echovault/echovault/echovault/keyspace.go:253.34,255.3 1 0 -github.com/echovault/echovault/echovault/keyspace.go:258.2,261.50 3 0 -github.com/echovault/echovault/echovault/keyspace.go:261.50,263.3 1 0 -github.com/echovault/echovault/echovault/keyspace.go:265.2,267.50 3 0 -github.com/echovault/echovault/echovault/keyspace.go:267.50,269.3 1 0 -github.com/echovault/echovault/echovault/keyspace.go:273.2,275.9 3 0 -github.com/echovault/echovault/echovault/keyspace.go:276.125,281.7 3 0 -github.com/echovault/echovault/echovault/keyspace.go:281.7,283.40 1 0 -github.com/echovault/echovault/echovault/keyspace.go:283.40,285.5 1 0 -github.com/echovault/echovault/echovault/keyspace.go:287.4,288.29 2 0 -github.com/echovault/echovault/echovault/keyspace.go:288.29,290.49 1 0 -github.com/echovault/echovault/echovault/keyspace.go:290.49,292.6 1 0 -github.com/echovault/echovault/echovault/keyspace.go:293.10,293.65 1 0 -github.com/echovault/echovault/echovault/keyspace.go:293.65,295.63 1 0 -github.com/echovault/echovault/echovault/keyspace.go:295.63,297.6 1 0 -github.com/echovault/echovault/echovault/keyspace.go:301.4,304.52 3 0 -github.com/echovault/echovault/echovault/keyspace.go:304.52,306.5 1 0 -github.com/echovault/echovault/echovault/keyspace.go:308.125,313.7 3 0 -github.com/echovault/echovault/echovault/keyspace.go:313.7,315.40 1 0 -github.com/echovault/echovault/echovault/keyspace.go:315.40,317.5 1 0 -github.com/echovault/echovault/echovault/keyspace.go:319.4,320.29 2 0 -github.com/echovault/echovault/echovault/keyspace.go:320.29,322.49 1 0 -github.com/echovault/echovault/echovault/keyspace.go:322.49,324.6 1 0 -github.com/echovault/echovault/echovault/keyspace.go:325.10,325.65 1 0 -github.com/echovault/echovault/echovault/keyspace.go:325.65,328.63 1 0 -github.com/echovault/echovault/echovault/keyspace.go:328.63,330.6 1 0 -github.com/echovault/echovault/echovault/keyspace.go:334.4,337.52 3 0 -github.com/echovault/echovault/echovault/keyspace.go:337.52,339.5 1 0 -github.com/echovault/echovault/echovault/keyspace.go:341.105,344.7 1 0 -github.com/echovault/echovault/echovault/keyspace.go:344.7,347.30 2 0 -github.com/echovault/echovault/echovault/keyspace.go:347.30,351.5 3 0 -github.com/echovault/echovault/echovault/keyspace.go:353.4,354.37 2 0 -github.com/echovault/echovault/echovault/keyspace.go:354.37,355.17 1 0 -github.com/echovault/echovault/echovault/keyspace.go:355.17,356.31 1 0 -github.com/echovault/echovault/echovault/keyspace.go:356.31,358.51 1 0 -github.com/echovault/echovault/echovault/keyspace.go:358.51,360.8 1 0 -github.com/echovault/echovault/echovault/keyspace.go:361.12,361.67 1 0 -github.com/echovault/echovault/echovault/keyspace.go:361.67,362.65 1 0 -github.com/echovault/echovault/echovault/keyspace.go:362.65,364.8 1 0 -github.com/echovault/echovault/echovault/keyspace.go:367.6,370.54 3 0 -github.com/echovault/echovault/echovault/keyspace.go:370.54,372.7 1 0 -github.com/echovault/echovault/echovault/keyspace.go:374.5,374.10 1 0 -github.com/echovault/echovault/echovault/keyspace.go:377.106,380.7 1 0 -github.com/echovault/echovault/echovault/keyspace.go:380.7,387.29 5 0 -github.com/echovault/echovault/echovault/keyspace.go:387.29,389.49 1 0 -github.com/echovault/echovault/echovault/keyspace.go:389.49,391.6 1 0 -github.com/echovault/echovault/echovault/keyspace.go:392.10,392.65 1 0 -github.com/echovault/echovault/echovault/keyspace.go:392.65,393.63 1 0 -github.com/echovault/echovault/echovault/keyspace.go:393.63,395.6 1 0 -github.com/echovault/echovault/echovault/keyspace.go:399.4,402.52 3 0 -github.com/echovault/echovault/echovault/keyspace.go:402.52,404.5 1 0 -github.com/echovault/echovault/echovault/keyspace.go:406.10,407.13 1 0 -github.com/echovault/echovault/echovault/keyspace.go:416.77,418.57 1 1 -github.com/echovault/echovault/echovault/keyspace.go:418.57,420.3 1 0 -github.com/echovault/echovault/echovault/keyspace.go:422.2,427.50 3 1 -github.com/echovault/echovault/echovault/keyspace.go:427.50,429.3 1 0 -github.com/echovault/echovault/echovault/keyspace.go:430.2,437.33 6 1 -github.com/echovault/echovault/echovault/keyspace.go:437.33,438.7 1 0 -github.com/echovault/echovault/echovault/keyspace.go:438.7,442.35 3 0 -github.com/echovault/echovault/echovault/keyspace.go:442.35,444.10 2 0 -github.com/echovault/echovault/echovault/keyspace.go:448.2,453.25 4 1 -github.com/echovault/echovault/echovault/keyspace.go:453.25,456.28 2 0 -github.com/echovault/echovault/echovault/keyspace.go:456.28,457.46 1 0 -github.com/echovault/echovault/echovault/keyspace.go:457.46,459.5 1 0 -github.com/echovault/echovault/echovault/keyspace.go:460.9,460.64 1 0 -github.com/echovault/echovault/echovault/keyspace.go:460.64,461.60 1 0 -github.com/echovault/echovault/echovault/keyspace.go:461.60,463.5 1 0 -github.com/echovault/echovault/echovault/keyspace.go:468.2,468.21 1 1 -github.com/echovault/echovault/echovault/keyspace.go:468.21,470.3 1 1 -github.com/echovault/echovault/echovault/keyspace.go:472.2,475.58 2 0 -github.com/echovault/echovault/echovault/keyspace.go:475.58,479.3 2 0 -github.com/echovault/echovault/echovault/keyspace.go:481.2,481.12 1 0 +github.com/echovault/echovault/echovault/keyspace.go:186.108,187.36 1 0 +github.com/echovault/echovault/echovault/keyspace.go:190.2,192.12 2 1 +github.com/echovault/echovault/echovault/keyspace.go:195.60,197.6 1 1 +github.com/echovault/echovault/echovault/keyspace.go:197.6,198.83 1 1 +github.com/echovault/echovault/echovault/keyspace.go:198.83,200.9 2 1 +github.com/echovault/echovault/echovault/keyspace.go:203.2,204.33 2 1 +github.com/echovault/echovault/echovault/keyspace.go:204.33,206.3 1 0 +github.com/echovault/echovault/echovault/keyspace.go:207.2,208.13 2 1 +github.com/echovault/echovault/echovault/keyspace.go:213.86,214.27 1 1 +github.com/echovault/echovault/echovault/keyspace.go:214.27,216.84 1 1 +github.com/echovault/echovault/echovault/keyspace.go:216.84,218.4 1 1 +github.com/echovault/echovault/echovault/keyspace.go:220.3,220.35 1 1 +github.com/echovault/echovault/echovault/keyspace.go:220.35,222.4 1 1 +github.com/echovault/echovault/echovault/keyspace.go:223.3,223.56 1 0 +github.com/echovault/echovault/echovault/keyspace.go:224.29,227.34 3 0 +github.com/echovault/echovault/echovault/keyspace.go:228.29,231.34 3 0 +github.com/echovault/echovault/echovault/keyspace.go:232.30,234.51 2 0 +github.com/echovault/echovault/echovault/keyspace.go:234.51,236.5 1 0 +github.com/echovault/echovault/echovault/keyspace.go:237.4,237.34 1 0 +github.com/echovault/echovault/echovault/keyspace.go:238.30,240.51 2 0 +github.com/echovault/echovault/echovault/keyspace.go:240.51,242.5 1 0 +github.com/echovault/echovault/echovault/keyspace.go:243.4,243.34 1 0 +github.com/echovault/echovault/echovault/keyspace.go:245.3,245.55 1 0 +github.com/echovault/echovault/echovault/keyspace.go:245.55,247.4 1 0 +github.com/echovault/echovault/echovault/keyspace.go:249.2,249.12 1 0 +github.com/echovault/echovault/echovault/keyspace.go:253.71,255.34 1 0 +github.com/echovault/echovault/echovault/keyspace.go:255.34,257.3 1 0 +github.com/echovault/echovault/echovault/keyspace.go:260.2,263.50 3 0 +github.com/echovault/echovault/echovault/keyspace.go:263.50,265.3 1 0 +github.com/echovault/echovault/echovault/keyspace.go:267.2,269.50 3 0 +github.com/echovault/echovault/echovault/keyspace.go:269.50,271.3 1 0 +github.com/echovault/echovault/echovault/keyspace.go:275.2,277.9 3 0 +github.com/echovault/echovault/echovault/keyspace.go:278.125,283.7 3 0 +github.com/echovault/echovault/echovault/keyspace.go:283.7,285.40 1 0 +github.com/echovault/echovault/echovault/keyspace.go:285.40,287.5 1 0 +github.com/echovault/echovault/echovault/keyspace.go:289.4,290.29 2 0 +github.com/echovault/echovault/echovault/keyspace.go:290.29,292.49 1 0 +github.com/echovault/echovault/echovault/keyspace.go:292.49,294.6 1 0 +github.com/echovault/echovault/echovault/keyspace.go:295.10,295.65 1 0 +github.com/echovault/echovault/echovault/keyspace.go:295.65,297.63 1 0 +github.com/echovault/echovault/echovault/keyspace.go:297.63,299.6 1 0 +github.com/echovault/echovault/echovault/keyspace.go:303.4,306.52 3 0 +github.com/echovault/echovault/echovault/keyspace.go:306.52,308.5 1 0 +github.com/echovault/echovault/echovault/keyspace.go:310.125,315.7 3 0 +github.com/echovault/echovault/echovault/keyspace.go:315.7,317.40 1 0 +github.com/echovault/echovault/echovault/keyspace.go:317.40,319.5 1 0 +github.com/echovault/echovault/echovault/keyspace.go:321.4,322.29 2 0 +github.com/echovault/echovault/echovault/keyspace.go:322.29,324.49 1 0 +github.com/echovault/echovault/echovault/keyspace.go:324.49,326.6 1 0 +github.com/echovault/echovault/echovault/keyspace.go:327.10,327.65 1 0 +github.com/echovault/echovault/echovault/keyspace.go:327.65,330.63 1 0 +github.com/echovault/echovault/echovault/keyspace.go:330.63,332.6 1 0 +github.com/echovault/echovault/echovault/keyspace.go:336.4,339.52 3 0 +github.com/echovault/echovault/echovault/keyspace.go:339.52,341.5 1 0 +github.com/echovault/echovault/echovault/keyspace.go:343.105,346.7 1 0 +github.com/echovault/echovault/echovault/keyspace.go:346.7,349.30 2 0 +github.com/echovault/echovault/echovault/keyspace.go:349.30,353.5 3 0 +github.com/echovault/echovault/echovault/keyspace.go:355.4,356.37 2 0 +github.com/echovault/echovault/echovault/keyspace.go:356.37,357.17 1 0 +github.com/echovault/echovault/echovault/keyspace.go:357.17,358.31 1 0 +github.com/echovault/echovault/echovault/keyspace.go:358.31,360.51 1 0 +github.com/echovault/echovault/echovault/keyspace.go:360.51,362.8 1 0 +github.com/echovault/echovault/echovault/keyspace.go:363.12,363.67 1 0 +github.com/echovault/echovault/echovault/keyspace.go:363.67,364.65 1 0 +github.com/echovault/echovault/echovault/keyspace.go:364.65,366.8 1 0 +github.com/echovault/echovault/echovault/keyspace.go:369.6,372.54 3 0 +github.com/echovault/echovault/echovault/keyspace.go:372.54,374.7 1 0 +github.com/echovault/echovault/echovault/keyspace.go:376.5,376.10 1 0 +github.com/echovault/echovault/echovault/keyspace.go:379.106,382.7 1 0 +github.com/echovault/echovault/echovault/keyspace.go:382.7,389.29 5 0 +github.com/echovault/echovault/echovault/keyspace.go:389.29,391.49 1 0 +github.com/echovault/echovault/echovault/keyspace.go:391.49,393.6 1 0 +github.com/echovault/echovault/echovault/keyspace.go:394.10,394.65 1 0 +github.com/echovault/echovault/echovault/keyspace.go:394.65,395.63 1 0 +github.com/echovault/echovault/echovault/keyspace.go:395.63,397.6 1 0 +github.com/echovault/echovault/echovault/keyspace.go:401.4,404.52 3 0 +github.com/echovault/echovault/echovault/keyspace.go:404.52,406.5 1 0 +github.com/echovault/echovault/echovault/keyspace.go:408.10,409.13 1 0 +github.com/echovault/echovault/echovault/keyspace.go:418.77,420.57 1 1 +github.com/echovault/echovault/echovault/keyspace.go:420.57,422.3 1 0 +github.com/echovault/echovault/echovault/keyspace.go:424.2,429.50 3 1 +github.com/echovault/echovault/echovault/keyspace.go:429.50,431.3 1 0 +github.com/echovault/echovault/echovault/keyspace.go:432.2,439.33 6 1 +github.com/echovault/echovault/echovault/keyspace.go:439.33,440.7 1 0 +github.com/echovault/echovault/echovault/keyspace.go:440.7,444.35 3 0 +github.com/echovault/echovault/echovault/keyspace.go:444.35,446.10 2 0 +github.com/echovault/echovault/echovault/keyspace.go:450.2,455.25 4 1 +github.com/echovault/echovault/echovault/keyspace.go:455.25,458.28 2 0 +github.com/echovault/echovault/echovault/keyspace.go:458.28,459.46 1 0 +github.com/echovault/echovault/echovault/keyspace.go:459.46,461.5 1 0 +github.com/echovault/echovault/echovault/keyspace.go:462.9,462.64 1 0 +github.com/echovault/echovault/echovault/keyspace.go:462.64,463.60 1 0 +github.com/echovault/echovault/echovault/keyspace.go:463.60,465.5 1 0 +github.com/echovault/echovault/echovault/keyspace.go:470.2,470.21 1 1 +github.com/echovault/echovault/echovault/keyspace.go:470.21,472.3 1 1 +github.com/echovault/echovault/echovault/keyspace.go:474.2,477.58 2 0 +github.com/echovault/echovault/echovault/keyspace.go:477.58,481.3 2 0 +github.com/echovault/echovault/echovault/keyspace.go:483.2,483.12 1 0 github.com/echovault/echovault/echovault/modules.go:27.75,30.42 3 1 github.com/echovault/echovault/echovault/modules.go:30.42,31.46 1 1 github.com/echovault/echovault/echovault/modules.go:31.46,33.4 1 1 @@ -3807,7 +3851,8 @@ github.com/echovault/echovault/echovault/plugin.go:181.61,183.4 1 1 github.com/echovault/echovault/echovault/plugin.go:183.6,185.4 1 1 github.com/echovault/echovault/echovault/plugin.go:187.2,187.16 1 1 github.com/echovault/echovault/echovault/test_helpers.go:9.35,16.2 2 1 -github.com/echovault/echovault/echovault/test_helpers.go:18.95,19.82 1 1 -github.com/echovault/echovault/echovault/test_helpers.go:19.82,21.3 1 0 -github.com/echovault/echovault/echovault/test_helpers.go:22.2,22.12 1 1 -github.com/echovault/echovault/echovault/test_helpers.go:25.95,28.2 2 1 +github.com/echovault/echovault/echovault/test_helpers.go:18.63,23.2 2 1 +github.com/echovault/echovault/echovault/test_helpers.go:25.95,26.82 1 1 +github.com/echovault/echovault/echovault/test_helpers.go:26.82,28.3 1 0 +github.com/echovault/echovault/echovault/test_helpers.go:29.2,29.12 1 1 +github.com/echovault/echovault/echovault/test_helpers.go:32.95,35.2 2 1 diff --git a/echovault/api_acl.go b/echovault/api_acl.go index 2a88631b..d56e43fb 100644 --- a/echovault/api_acl.go +++ b/echovault/api_acl.go @@ -303,7 +303,8 @@ func (server *EchoVault) ACLGetUser(username string) (map[string][]string, error } arr := v.Array() - result := make(map[string][]string, len(arr)/2) + + result := make(map[string][]string) for i := 0; i < len(arr); i += 2 { key := arr[i].String() @@ -312,7 +313,7 @@ func (server *EchoVault) ACLGetUser(username string) (map[string][]string, error result[key] = make([]string, len(value)) for j := 0; j < len(value); j++ { - result[key][i] = value[i].String() + result[key][j] = value[j].String() } } diff --git a/echovault/api_acl_test.go b/echovault/api_acl_test.go index f6598c81..47bf4275 100644 --- a/echovault/api_acl_test.go +++ b/echovault/api_acl_test.go @@ -13,3 +13,257 @@ // limitations under the License. package echovault + +import ( + "crypto/sha256" + "fmt" + "github.com/echovault/echovault/internal/constants" + "slices" + "strings" + "testing" +) + +func TestEchoVault_ACLCat(t *testing.T) { + server := createEchoVault() + + 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) + } + } + }) + } +} + +func TestEchoVault_ACLUsers(t *testing.T) { + server := createEchoVault() + + // 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) + } + } + + // 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) + } + } + + // 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) + } +} diff --git a/echovault/api_admin.go b/echovault/api_admin.go index 5ae23053..7f070e6c 100644 --- a/echovault/api_admin.go +++ b/echovault/api_admin.go @@ -139,16 +139,18 @@ type SubCommandOptions struct { // `options` - CommandListOptions. // // Returns: a string slice of all the loaded commands. SubCommands are represented as "command|subcommand". -func (server *EchoVault) CommandList(options CommandListOptions) ([]string, error) { +func (server *EchoVault) CommandList(options ...CommandListOptions) ([]string, error) { cmd := []string{"COMMAND", "LIST"} - switch { - case options.ACLCAT != "": - cmd = append(cmd, []string{"FILTERBY", "ACLCAT", options.ACLCAT}...) - case options.PATTERN != "": - cmd = append(cmd, []string{"FILTERBY", "PATTERN", options.PATTERN}...) - case options.MODULE != "": - cmd = append(cmd, []string{"FILTERBY", "MODULE", options.MODULE}...) + if len(options) > 0 { + switch { + case options[0].ACLCAT != "": + cmd = append(cmd, []string{"FILTERBY", "ACLCAT", options[0].ACLCAT}...) + case options[0].PATTERN != "": + cmd = append(cmd, []string{"FILTERBY", "PATTERN", options[0].PATTERN}...) + case options[0].MODULE != "": + cmd = append(cmd, []string{"FILTERBY", "MODULE", options[0].MODULE}...) + } } b, err := server.handleCommand(server.context, internal.EncodeCommand(cmd), nil, false, true) @@ -171,12 +173,16 @@ func (server *EchoVault) CommandCount() (int, error) { } // Save triggers a new snapshot. -func (server *EchoVault) Save() (string, error) { +// +// Returns: true if the save was started. The OK response does not confirm that the save was successfully synced to +// file. Only that the background process has started. +func (server *EchoVault) Save() (bool, error) { b, err := server.handleCommand(server.context, internal.EncodeCommand([]string{"SAVE"}), nil, false, true) if err != nil { - return "", err + return false, err } - return internal.ParseStringResponse(b) + res, err := internal.ParseStringResponse(b) + return strings.EqualFold(res, "ok"), err } // LastSave returns the unix epoch milliseconds timestamp of the last save. diff --git a/echovault/api_admin_test.go b/echovault/api_admin_test.go index 24f38090..749abc3d 100644 --- a/echovault/api_admin_test.go +++ b/echovault/api_admin_test.go @@ -18,13 +18,17 @@ import ( "bytes" "errors" "fmt" + "github.com/echovault/echovault/internal/clock" "github.com/echovault/echovault/internal/constants" "github.com/tidwall/resp" "os" "path" + "reflect" "slices" "strconv" + "strings" "testing" + "time" ) func TestEchoVault_AddCommand(t *testing.T) { @@ -300,7 +304,7 @@ func TestEchoVault_RemoveCommand(t *testing.T) { func TestEchoVault_Plugins(t *testing.T) { t.Cleanup(func() { - _ = os.RemoveAll("./testdata") + _ = os.RemoveAll("./testdata/modules") }) server := createEchoVault() @@ -373,3 +377,192 @@ func TestEchoVault_Plugins(t *testing.T) { } } } + +func TestEchoVault_CommandList(t *testing.T) { + server := createEchoVault() + + 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)) + } + 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) + } + }) + } +} + +func TestEchoVault_CommandCount(t *testing.T) { + server := createEchoVault() + + 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) { + 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) + } + }) + } +} + +func TestEchoVault_Save(t *testing.T) { + conf := DefaultConfig() + conf.DataDir = path.Join(".", "testdata", "data") + server := createEchoVaultWithConfig(conf) + + 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) + } + }) + } +} + +func TestEchoVault_LastSave(t *testing.T) { + server := createEchoVault() + server.setLatestSnapshot(clock.NewClock().Now().Add(5 * time.Minute).UnixMilli()) + + 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/echovault/api_hash_test.go b/echovault/api_hash_test.go index 00f49ad3..7f18e015 100644 --- a/echovault/api_hash_test.go +++ b/echovault/api_hash_test.go @@ -770,3 +770,59 @@ func TestEchoVault_HVALS(t *testing.T) { }) } } + +func TestEchoVault_HGet(t *testing.T) { + server := createEchoVault() + tests := []struct { + name string + presetValue interface{} + key string + fields []string + want []string + wantErr bool + }{ + { + name: "1. Get values from existing hash.", + key: "HgetKey1", + presetValue: map[string]interface{}{"field1": "value1", "field2": 365, "field3": 3.142}, + 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) + 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) + } + }) + } +} diff --git a/echovault/api_pubsub.go b/echovault/api_pubsub.go index 72408358..d8000db4 100644 --- a/echovault/api_pubsub.go +++ b/echovault/api_pubsub.go @@ -16,10 +16,12 @@ package echovault import ( "bytes" + "errors" "github.com/echovault/echovault/internal" "github.com/tidwall/resp" "net" "strings" + "sync" ) type conn struct { @@ -27,7 +29,7 @@ type conn struct { writeConn *net.Conn } -var connections map[string]conn +var connections sync.Map // ReadPubSubMessage is returned by the Subscribe and PSubscribe functions. // @@ -37,6 +39,32 @@ var connections map[string]conn // Index 2 holds the actual message. type ReadPubSubMessage func() []string +func establishConnections(tag string) (*net.Conn, *net.Conn, error) { + var readConn *net.Conn + var writeConn *net.Conn + + if _, ok := connections.Load(tag); !ok { + // If connection with this name does not exist, create new connection. + rc, wc := net.Pipe() + readConn = &rc + writeConn = &wc + connections.Store(tag, conn{ + readConn: &rc, + writeConn: &wc, + }) + } else { + // Reuse existing connection. + c, ok := connections.Load(tag) + if !ok { + return nil, nil, errors.New("could not establish connection") + } + readConn = c.(conn).readConn + writeConn = c.(conn).writeConn + } + + return readConn, writeConn, nil +} + // Subscribe subscribes the caller to the list of provided channels. // // Parameters: @@ -47,31 +75,22 @@ type ReadPubSubMessage func() []string // // Returns: ReadPubSubMessage function which reads the next message sent to the subscription instance. // This function is blocking. -func (server *EchoVault) Subscribe(tag string, channels ...string) ReadPubSubMessage { - // Initialize connection tracker if calling subscribe for the first time - if connections == nil { - connections = make(map[string]conn) - } - - // If connection with this name does not exist, create new connection it - var readConn net.Conn - var writeConn net.Conn - if _, ok := connections[tag]; !ok { - readConn, writeConn = net.Pipe() - connections[tag] = conn{ - readConn: &readConn, - writeConn: &writeConn, - } +func (server *EchoVault) Subscribe(tag string, channels ...string) (ReadPubSubMessage, error) { + readConn, writeConn, err := establishConnections(tag) + if err != nil { + return func() []string { + return []string{} + }, err } - // Subscribe connection to the provided channels + // Subscribe connection to the provided channels. cmd := append([]string{"SUBSCRIBE"}, channels...) go func() { - _, _ = server.handleCommand(server.context, internal.EncodeCommand(cmd), connections[tag].writeConn, false, true) + _, _ = server.handleCommand(server.context, internal.EncodeCommand(cmd), writeConn, false, true) }() return func() []string { - r := resp.NewConn(readConn) + r := resp.NewConn(*readConn) v, _, _ := r.ReadValue() res := make([]string, len(v.Array())) @@ -80,7 +99,7 @@ func (server *EchoVault) Subscribe(tag string, channels ...string) ReadPubSubMes } return res - } + }, nil } // Unsubscribe unsubscribes the caller from the given channels. @@ -91,16 +110,12 @@ func (server *EchoVault) Subscribe(tag string, channels ...string) ReadPubSubMes // // `channels` - ...string - The list of channels to unsubscribe from. func (server *EchoVault) Unsubscribe(tag string, channels ...string) { - if connections == nil { - return - } - - if _, ok := connections[tag]; !ok { + c, ok := connections.Load(tag) + if !ok { return } - cmd := append([]string{"UNSUBSCRIBE"}, channels...) - _, _ = server.handleCommand(server.context, internal.EncodeCommand(cmd), connections[tag].writeConn, false, true) + _, _ = server.handleCommand(server.context, internal.EncodeCommand(cmd), c.(conn).writeConn, false, true) } // PSubscribe subscribes the caller to the list of provided glob patterns. @@ -113,31 +128,22 @@ func (server *EchoVault) Unsubscribe(tag string, channels ...string) { // // Returns: ReadPubSubMessage function which reads the next message sent to the subscription instance. // This function is blocking. -func (server *EchoVault) PSubscribe(tag string, patterns ...string) ReadPubSubMessage { - // Initialize connection tracker if calling subscribe for the first time - if connections == nil { - connections = make(map[string]conn) - } - - // If connection with this name does not exist, create new connection it - var readConn net.Conn - var writeConn net.Conn - if _, ok := connections[tag]; !ok { - readConn, writeConn = net.Pipe() - connections[tag] = conn{ - readConn: &readConn, - writeConn: &writeConn, - } +func (server *EchoVault) PSubscribe(tag string, patterns ...string) (ReadPubSubMessage, error) { + readConn, writeConn, err := establishConnections(tag) + if err != nil { + return func() []string { + return []string{} + }, err } // Subscribe connection to the provided channels cmd := append([]string{"PSUBSCRIBE"}, patterns...) go func() { - _, _ = server.handleCommand(server.context, internal.EncodeCommand(cmd), connections[tag].writeConn, false, true) + _, _ = server.handleCommand(server.context, internal.EncodeCommand(cmd), writeConn, false, true) }() return func() []string { - r := resp.NewConn(readConn) + r := resp.NewConn(*readConn) v, _, _ := r.ReadValue() res := make([]string, len(v.Array())) @@ -146,7 +152,7 @@ func (server *EchoVault) PSubscribe(tag string, patterns ...string) ReadPubSubMe } return res - } + }, nil } // PUnsubscribe unsubscribes the caller from the given glob patterns. @@ -157,16 +163,12 @@ func (server *EchoVault) PSubscribe(tag string, patterns ...string) ReadPubSubMe // // `patterns` - ...string - The list of glob patterns to unsubscribe from. func (server *EchoVault) PUnsubscribe(tag string, patterns ...string) { - if connections == nil { - return - } - - if _, ok := connections[tag]; !ok { + c, ok := connections.Load(tag) + if !ok { return } - cmd := append([]string{"PUNSUBSCRIBE"}, patterns...) - _, _ = server.handleCommand(server.context, internal.EncodeCommand(cmd), connections[tag].writeConn, false, true) + _, _ = server.handleCommand(server.context, internal.EncodeCommand(cmd), c.(conn).writeConn, false, true) } // Publish publishes a message to the given channel. @@ -218,14 +220,14 @@ func (server *EchoVault) PubSubNumPat() (int, error) { return internal.ParseIntegerResponse(b) } -// PubSubNmSub returns the number of subscribers for each of the specified channels. +// PubSubNumSub returns the number of subscribers for each of the specified channels. // // Parameters: // // `channels` - ...string - The list of channels whose number of subscribers is to be checked. // // Returns: A map of map[string]int where the key is the channel name and the value is the number of subscribers. -func (server *EchoVault) PubSubNmSub(channels ...string) (map[string]int, error) { +func (server *EchoVault) PubSubNumSub(channels ...string) (map[string]int, error) { cmd := append([]string{"PUBSUB", "NUMSUB"}, channels...) b, err := server.handleCommand(server.context, internal.EncodeCommand(cmd), nil, false, true) diff --git a/echovault/api_pubsub_test.go b/echovault/api_pubsub_test.go index afd234ac..355f2459 100644 --- a/echovault/api_pubsub_test.go +++ b/echovault/api_pubsub_test.go @@ -13,3 +13,278 @@ // limitations under the License. package echovault + +import ( + "fmt" + "reflect" + "slices" + "testing" +) + +func Test_Subscribe(t *testing.T) { + server := createEchoVault() + + // Subscribe to channels. + tag := "tag" + channels := []string{"channel1", "channel2"} + readMessage, err := server.Subscribe(tag, channels...) + if err != nil { + t.Errorf("SUBSCRIBE() error = %v", err) + } + + for i := 0; i < len(channels); i++ { + message := readMessage() + // Check that we've received the subscribe messages. + if message[0] != "subscribe" { + t.Errorf("SUBSCRIBE() expected index 0 for message at %d to be \"subscribe\", got %s", i, message[0]) + } + if !slices.Contains(channels, message[1]) { + t.Errorf("SUBSCRIBE() unexpected string \"%s\" at index 1 for message %d", message[1], i) + } + } + + // Publish some messages to the channels. + for _, channel := range channels { + ok, err := server.Publish(channel, fmt.Sprintf("message for %s", channel)) + if err != nil { + t.Errorf("PUBLISH() err = %v", err) + } + if !ok { + t.Errorf("PUBLISH() could not publish message to channel %s", channel) + } + } + + // Read messages from the channels + for i := 0; i < len(channels); i++ { + message := readMessage() + // Check that we've received the messages. + if message[0] != "message" { + t.Errorf("SUBSCRIBE() expected index 0 for message at %d to be \"message\", got %s", i, message[0]) + } + if !slices.Contains(channels, message[1]) { + t.Errorf("SUBSCRIBE() unexpected string \"%s\" at index 1 for message %d", message[1], i) + } + if !slices.Contains([]string{"message for channel1", "message for channel2"}, message[2]) { + t.Errorf("SUBSCRIBE() unexpected string \"%s\" at index 1 for message %d", message[1], i) + } + } + + // Unsubscribe from channels + server.Unsubscribe(tag, channels...) +} + +func TestEchoVault_PSubscribe(t *testing.T) { + server := createEchoVault() + + // Subscribe to channels. + tag := "tag" + patterns := []string{"channel[12]", "pattern[12]"} + readMessage, err := server.PSubscribe(tag, patterns...) + if err != nil { + t.Errorf("PSubscribe() error = %v", err) + } + + for i := 0; i < len(patterns); i++ { + message := readMessage() + // Check that we've received the subscribe messages. + if message[0] != "psubscribe" { + t.Errorf("PSUBSCRIBE() expected index 0 for message at %d to be \"psubscribe\", got %s", i, message[0]) + } + if !slices.Contains(patterns, message[1]) { + t.Errorf("PSUBSCRIBE() unexpected string \"%s\" at index 1 for message %d", message[1], i) + } + } + + // Publish some messages to the channels. + for _, channel := range []string{"channel1", "channel2", "pattern1", "pattern2"} { + ok, err := server.Publish(channel, fmt.Sprintf("message for %s", channel)) + if err != nil { + t.Errorf("PUBLISH() err = %v", err) + } + if !ok { + t.Errorf("PUBLISH() could not publish message to channel %s", channel) + } + } + + // Read messages from the channels + for i := 0; i < len(patterns)*2; i++ { + message := readMessage() + // Check that we've received the messages. + if message[0] != "message" { + t.Errorf("SUBSCRIBE() expected index 0 for message at %d to be \"message\", got %s", i, message[0]) + } + if !slices.Contains(patterns, message[1]) { + t.Errorf("SUBSCRIBE() unexpected string \"%s\" at index 1 for message %d", message[1], i) + } + if !slices.Contains([]string{ + "message for channel1", "message for channel2", "message for pattern1", "message for pattern2"}, message[2]) { + t.Errorf("SUBSCRIBE() unexpected string \"%s\" at index 1 for message %d", message[2], i) + } + } + + // Unsubscribe from channels + server.PUnsubscribe(tag, patterns...) +} + +func TestEchoVault_PubSubChannels(t *testing.T) { + server := createEchoVault() + tests := []struct { + name string + tag string + channels []string + patterns []string + pattern string + want []string + wantErr bool + }{ + { + name: "1. Get number of active channels", + tag: "tag", + channels: []string{"channel1", "channel2", "channel3", "channel4"}, + patterns: []string{"channel[56]"}, + pattern: "channel[123456]", + want: []string{"channel1", "channel2", "channel3", "channel4"}, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Subscribe to channels + readChannelMessages, err := server.Subscribe(tt.tag, tt.channels...) + if err != nil { + t.Errorf("PubSubChannels() error = %v", err) + } + + for i := 0; i < len(tt.channels); i++ { + readChannelMessages() + } + // Subscribe to patterns + readPatternMessages, err := server.PSubscribe(tt.tag, tt.patterns...) + if err != nil { + t.Errorf("PubSubChannels() error = %v", err) + } + + for i := 0; i < len(tt.patterns); i++ { + readPatternMessages() + } + got, err := server.PubSubChannels(tt.pattern) + if (err != nil) != tt.wantErr { + t.Errorf("PubSubChannels() error = %v, wantErr %v", err, tt.wantErr) + return + } + if len(got) != len(tt.want) { + t.Errorf("PubSubChannels() got response length %d, want %d", len(got), len(tt.want)) + } + for _, item := range got { + if !slices.Contains(tt.want, item) { + t.Errorf("PubSubChannels() unexpected item \"%s\", in response", item) + } + } + }) + } +} + +func TestEchoVault_PubSubNumPat(t *testing.T) { + server := createEchoVault() + tests := []struct { + name string + tag string + patterns []string + want int + wantErr bool + }{ + { + name: "1. Get number of active patterns on the server", + tag: "tag", + patterns: []string{"channel[56]", "channel[78]"}, + want: 2, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Subscribe to patterns + readPatternMessages, err := server.PSubscribe(tt.tag, tt.patterns...) + if err != nil { + t.Errorf("PubSubNumPat() error = %v", err) + } + for i := 0; i < len(tt.patterns); i++ { + readPatternMessages() + } + got, err := server.PubSubNumPat() + if (err != nil) != tt.wantErr { + t.Errorf("PubSubNumPat() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("PubSubNumPat() got = %v, want %v", got, tt.want) + } + }) + } +} + +func TestEchoVault_PubSubNumSub(t *testing.T) { + server := createEchoVault() + tests := []struct { + name string + subscriptions map[string]struct { + channels []string + patterns []string + } + channels []string + want map[string]int + wantErr bool + }{ + { + name: "Get number of subscriptions for the given channels", + subscriptions: map[string]struct { + channels []string + patterns []string + }{ + "tag1": { + channels: []string{"channel1", "channel2"}, + patterns: []string{"channel[34]"}, + }, + "tag2": { + channels: []string{"channel2", "channel3"}, + patterns: []string{"channel[23]"}, + }, + "tag3": { + channels: []string{"channel2", "channel4"}, + patterns: []string{}, + }, + }, + channels: []string{"channel1", "channel2", "channel3", "channel4", "channel5"}, + want: map[string]int{"channel1": 1, "channel2": 3, "channel3": 1, "channel4": 1, "channel5": 0}, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + for tag, subs := range tt.subscriptions { + readPat, err := server.PSubscribe(tag, subs.patterns...) + if err != nil { + t.Errorf("PubSubNumSub() error = %v", err) + } + for _, _ = range subs.patterns { + readPat() + } + readChan, err := server.Subscribe(tag, subs.channels...) + if err != nil { + t.Errorf("PubSubNumSub() error = %v", err) + } + for _, _ = range subs.channels { + readChan() + } + } + got, err := server.PubSubNumSub(tt.channels...) + if (err != nil) != tt.wantErr { + t.Errorf("PubSubNumSub() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("PubSubNumSub() got = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/echovault/api_sorted_set.go b/echovault/api_sorted_set.go index 4f54a320..03572778 100644 --- a/echovault/api_sorted_set.go +++ b/echovault/api_sorted_set.go @@ -118,25 +118,6 @@ func buildMemberScoreMap(arr [][]string, withscores bool) (map[string]float64, e return result, nil } -func buildIntegerScoreMap(arr [][]string, withscores bool) (map[int]float64, error) { - result := make(map[int]float64, len(arr)) - for _, entry := range arr { - rank, err := strconv.Atoi(entry[0]) - if err != nil { - return nil, err - } - result[rank] = 0 - if withscores { - score, err := strconv.ParseFloat(entry[1], 64) - if err != nil { - return nil, err - } - result[rank] = score - } - } - return result, nil -} - // ZAdd adds member(s) to a sorted set. If the sorted set does not exist, a new sorted set is created with the // member(s). // @@ -734,7 +715,7 @@ func (server *EchoVault) ZRank(key string, member string, withscores bool) (map[ arr, err := internal.ParseStringArrayResponse(b) if len(arr) == 0 { - return nil, nil + return map[int]float64{}, nil } s, err := strconv.Atoi(arr[0]) @@ -768,9 +749,28 @@ func (server *EchoVault) ZRevRank(key string, member string, withscores bool) (m return nil, err } - arr, err := internal.ParseNestedStringArrayResponse(b) + arr, err := internal.ParseStringArrayResponse(b) + + if len(arr) == 0 { + return map[int]float64{}, nil + } + + s, err := strconv.Atoi(arr[0]) + if err != nil { + return nil, err + } + + res := map[int]float64{s: 0} - return buildIntegerScoreMap(arr, withscores) + if withscores { + f, err := strconv.ParseFloat(arr[1], 64) + if err != nil { + return nil, err + } + res[s] = f + } + + return res, nil } // ZScore Returns the score of the member in the sorted set. diff --git a/echovault/api_sorted_set_test.go b/echovault/api_sorted_set_test.go index 0679aaa4..314e9dc5 100644 --- a/echovault/api_sorted_set_test.go +++ b/echovault/api_sorted_set_test.go @@ -2354,7 +2354,7 @@ func TestEchoVault_ZRANK(t *testing.T) { wantErr bool }{ { - name: "Return element's rank from a sorted set", + 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}, @@ -2368,7 +2368,7 @@ func TestEchoVault_ZRANK(t *testing.T) { wantErr: false, }, { - name: "Return element's rank from a sorted set with its score", + 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}, @@ -2382,17 +2382,17 @@ func TestEchoVault_ZRANK(t *testing.T) { wantErr: false, }, { - name: "If key does not exist, return nil value", + name: "3. If key does not exist, return nil value", preset: false, presetValue: nil, key: "key3", member: "one", withscores: false, - want: nil, + want: map[int]float64{}, wantErr: false, }, { - name: "If key exists and is a sorted set, but the member does not exist, return nil", + 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}, @@ -2402,11 +2402,11 @@ func TestEchoVault_ZRANK(t *testing.T) { key: "key4", member: "non-existent", withscores: false, - want: nil, + want: map[int]float64{}, wantErr: false, }, { - name: "Throw error when trying to find scores from elements that are not sorted sets", + name: "5. Throw error when trying to find scores from elements that are not sorted sets", preset: true, presetValue: "Default value", key: "key5", @@ -3299,3 +3299,292 @@ func TestEchoVault_ZUNIONSTORE(t *testing.T) { }) } } + +func TestEchoVault_ZRevRank(t *testing.T) { + server := createEchoVault() + + 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) + 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) + } + }) + } +} + +func TestEchoVault_ZRemRangeByLex(t *testing.T) { + server := createEchoVault() + 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 + } + if got != tt.want { + t.Errorf("ZRemRangeByLex() got = %v, want %v", got, tt.want) + } + }) + } +} + +func TestEchoVault_ZRemRangeByRank(t *testing.T) { + server := createEchoVault() + 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 + } + if got != tt.want { + t.Errorf("ZRemRangeByRank() got = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/echovault/echovault_test.go b/echovault/echovault_test.go index 3c488039..66dcabd9 100644 --- a/echovault/echovault_test.go +++ b/echovault/echovault_test.go @@ -4,11 +4,11 @@ import ( "fmt" "github.com/echovault/echovault/internal" "github.com/tidwall/resp" + "math" "net" "strings" "sync" "testing" - "time" ) type ClientServerPair struct { @@ -69,8 +69,8 @@ func setupServer( return NewEchoVault(WithConfig(config)) } -func Test_ClusterReplication(t *testing.T) { - pairs := make([]ClientServerPair, 3) +func MakeCluster(size int) ([]ClientServerPair, error) { + pairs := make([]ClientServerPair, size) for i := 0; i < len(pairs); i++ { serverId := fmt.Sprintf("SERVER-%d", i) @@ -82,19 +82,19 @@ func Test_ClusterReplication(t *testing.T) { } port, err := internal.GetFreePort() if err != nil { - t.Errorf("could not get free port: %v", err) + return nil, fmt.Errorf("could not get free port: %v", err) } raftPort, err := internal.GetFreePort() if err != nil { - t.Errorf("could not get free raft port: %v", err) + return nil, fmt.Errorf("could not get free raft port: %v", err) } memberlistPort, err := internal.GetFreePort() if err != nil { - t.Errorf("could not get free memberlist port: %v", err) + return nil, fmt.Errorf("could not get free memberlist port: %v", err) } server, err := setupServer(serverId, bootstrapCluster, bindAddr, joinAddr, port, raftPort, memberlistPort) if err != nil { - t.Errorf("could not start server; %v", err) + return nil, fmt.Errorf("could not start server; %v", err) } // Start the server @@ -104,10 +104,16 @@ func Test_ClusterReplication(t *testing.T) { wg.Done() server.Start() }() + wg.Wait() - <-time.After(500 * time.Millisecond) // Yield to allow server start. - - if i > 0 { + if i == 0 { + // If node is a leader, wait until it's established itself as a leader of the raft cluster. + for { + if server.raft.IsRaftLeader() { + break + } + } + } else { // If the node is a follower, wait until it's joined the raft cluster before moving forward. for { if server.raft.HasJoinedCluster() { @@ -119,7 +125,7 @@ func Test_ClusterReplication(t *testing.T) { // Setup client connection. conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", bindAddr, port)) if err != nil { - t.Errorf("could not open tcp connection: %v", err) + return nil, fmt.Errorf("could not open tcp connection: %v", err) } for { // Wait until connection is no longer nil @@ -141,6 +147,16 @@ func Test_ClusterReplication(t *testing.T) { } } + return pairs, nil +} + +func Test_ClusterReplication(t *testing.T) { + nodes, err := MakeCluster(5) + if err != nil { + t.Error(err) + return + } + // Prepare the write data for the cluster tests := []struct { key string @@ -162,7 +178,7 @@ func Test_ClusterReplication(t *testing.T) { // Write all the data to the cluster leader for i, test := range tests { - node := pairs[0] + node := nodes[0] if err := node.client.WriteArray([]resp.Value{ resp.StringValue("SET"), resp.StringValue(test.key), @@ -170,7 +186,7 @@ func Test_ClusterReplication(t *testing.T) { }); err != nil { t.Errorf("could not write data to leader node (test %d): %v", i, err) } - // Read response and make sure we received "ok" response + // Read response and make sure we received "ok" response. rd, _, err := node.client.ReadValue() if err != nil { t.Errorf("could not read response from leader node (test %d): %v", i, err) @@ -180,10 +196,12 @@ func Test_ClusterReplication(t *testing.T) { } } - // On each of the follower nodes, get the values and check if they have been replicated + // Check if the data has been replicated on a quorum (majority of the cluster). + quorum := int(math.Ceil(float64(len(nodes)/2)) + 1) for i, test := range tests { - for j := 1; j < len(pairs); j++ { - node := pairs[i] + count := 0 + for j := 0; j < len(nodes); j++ { + node := nodes[j] if err := node.client.WriteArray([]resp.Value{ resp.StringValue("GET"), resp.StringValue(test.key), @@ -194,9 +212,13 @@ func Test_ClusterReplication(t *testing.T) { if err != nil { t.Errorf("could not read data from follower node %d (test %d): %v", j, i, err) } - if rd.String() != test.value { - t.Errorf("expected value \"%s\" for follower node %d (test %d), got \"%s\"", test.value, j, i, rd.String()) + if rd.String() == test.value { + count += 1 // If the expected value is found, increment the count. } } + // Fail if count is less than quorum. + if count < quorum { + t.Errorf("could not find value %s at key %s in cluster quorum", test.value, test.key) + } } } diff --git a/echovault/test_helpers.go b/echovault/test_helpers.go index 6be0ca4d..2c33a861 100644 --- a/echovault/test_helpers.go +++ b/echovault/test_helpers.go @@ -15,6 +15,13 @@ func createEchoVault() *EchoVault { return ev } +func createEchoVaultWithConfig(conf config.Config) *EchoVault { + ev, _ := NewEchoVault( + WithConfig(conf), + ) + return ev +} + func presetValue(server *EchoVault, ctx context.Context, key string, value interface{}) error { if err := server.setValues(ctx, map[string]interface{}{key: value}); err != nil { return err diff --git a/internal/config/config_test.go b/internal/config/config_test.go new file mode 100644 index 00000000..6f4391aa --- /dev/null +++ b/internal/config/config_test.go @@ -0,0 +1,15 @@ +// Copyright 2024 Kelvin Clement Mwinuka +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package config diff --git a/internal/config/default_test.go b/internal/config/default_test.go new file mode 100644 index 00000000..6f4391aa --- /dev/null +++ b/internal/config/default_test.go @@ -0,0 +1,15 @@ +// Copyright 2024 Kelvin Clement Mwinuka +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package config diff --git a/internal/modules/pubsub/pubsub.go b/internal/modules/pubsub/pubsub.go index 36d09dfc..f7cbcf24 100644 --- a/internal/modules/pubsub/pubsub.go +++ b/internal/modules/pubsub/pubsub.go @@ -73,8 +73,8 @@ func (ps *PubSub) Subscribe(_ context.Context, conn *net.Conn, channels []string }); err != nil { log.Println(err) } + ps.channels = append(ps.channels, newChan) } - ps.channels = append(ps.channels, newChan) } else { // Subscribe to existing channel if ps.channels[channelIdx].Subscribe(conn) { diff --git a/internal/modules/sorted_set/commands_test.go b/internal/modules/sorted_set/commands_test.go index b420be7e..8864c4f6 100644 --- a/internal/modules/sorted_set/commands_test.go +++ b/internal/modules/sorted_set/commands_test.go @@ -91,7 +91,7 @@ func Test_HandleZADD(t *testing.T) { expectedError: nil, }, { - name: "Do not add any elements when providing existing members with NX flag", + name: "3. Do not add any elements when providing existing members with NX flag", presetValue: sorted_set.NewSortedSet([]sorted_set.MemberParam{ {Value: "member1", Score: sorted_set.Score(5.5)}, {Value: "member2", Score: sorted_set.Score(67.77)}, @@ -103,7 +103,7 @@ func Test_HandleZADD(t *testing.T) { expectedError: nil, }, { - name: "Successfully add elements to an existing set when XX flag is provided with existing elements", + name: "4. Successfully add elements to an existing set when XX flag is provided with existing elements", presetValue: sorted_set.NewSortedSet([]sorted_set.MemberParam{ {Value: "member1", Score: sorted_set.Score(5.5)}, {Value: "member2", Score: sorted_set.Score(67.77)}, @@ -3002,7 +3002,7 @@ func Test_HandleZREMRANGEBYRANK(t *testing.T) { expectedError: nil, }, { - name: "2. If key does not exist, return 0", + name: "3. If key does not exist, return 0", presetValues: nil, command: []string{"ZREMRANGEBYRANK", "ZremRangeByRankKey3", "2", "4"}, expectedValues: nil, @@ -3010,18 +3010,13 @@ func Test_HandleZREMRANGEBYRANK(t *testing.T) { expectedError: nil, }, { - name: "3. Return error key is not a sorted set", + name: "4. Return error key is not a sorted set", presetValues: map[string]interface{}{ "ZremRangeByRankKey3": "Default value", }, command: []string{"ZREMRANGEBYRANK", "ZremRangeByRankKey3", "4", "4"}, expectedError: errors.New("value at ZremRangeByRankKey3 is not a sorted set"), }, - { - name: "4. Command too short", - command: []string{"ZREMRANGEBYRANK", "ZremRangeByRankKey4", "3"}, - expectedError: errors.New(constants.WrongArgsResponse), - }, { name: "5. Return error when start index is out of bounds", presetValues: map[string]interface{}{ @@ -3055,7 +3050,12 @@ func Test_HandleZREMRANGEBYRANK(t *testing.T) { expectedError: errors.New("indices out of bounds"), }, { - name: "7. Command too long", + name: "7. Command too short", + command: []string{"ZREMRANGEBYRANK", "ZremRangeByRankKey4", "3"}, + expectedError: errors.New(constants.WrongArgsResponse), + }, + { + name: "8. Command too long", command: []string{"ZREMRANGEBYRANK", "ZremRangeByRankKey7", "4", "5", "8"}, expectedError: errors.New(constants.WrongArgsResponse), }, @@ -3238,7 +3238,7 @@ func Test_HandleZREMRANGEBYLEX(t *testing.T) { expectedError: nil, }, { - name: "3. Return error key is not a sorted set", + name: "4. Return error key is not a sorted set", presetValues: map[string]interface{}{ "ZremRangeByLexKey3": "Default value", }, @@ -3246,12 +3246,12 @@ func Test_HandleZREMRANGEBYLEX(t *testing.T) { expectedError: errors.New("value at ZremRangeByLexKey3 is not a sorted set"), }, { - name: "4. Command too short", + name: "5. Command too short", command: []string{"ZREMRANGEBYLEX", "ZremRangeByLexKey4", "a"}, expectedError: errors.New(constants.WrongArgsResponse), }, { - name: "5. Command too long", + name: "6. Command too long", command: []string{"ZREMRANGEBYLEX", "ZremRangeByLexKey5", "a", "b", "c"}, expectedError: errors.New(constants.WrongArgsResponse), }, diff --git a/internal/raft/fms_snapshot.go b/internal/raft/fsm_snapshot.go similarity index 100% rename from internal/raft/fms_snapshot.go rename to internal/raft/fsm_snapshot.go diff --git a/internal/raft/raft.go b/internal/raft/raft.go index b900fe28..9baa1465 100644 --- a/internal/raft/raft.go +++ b/internal/raft/raft.go @@ -101,7 +101,7 @@ func (r *Raft) RaftInit(ctx context.Context) { addr, advertiseAddr, 10, - 500*time.Millisecond, + 10*time.Second, os.Stdout, ) @@ -131,7 +131,7 @@ func (r *Raft) RaftInit(ctx context.Context) { ) if err != nil { - log.Fatalf("Could not start node with error; %s", err) + log.Fatalf("could not start node with error; %s", err) } if conf.BootstrapCluster { @@ -186,7 +186,7 @@ func (r *Raft) AddVoter( for _, s := range raftConfig.Configuration().Servers { // Check if a echovault already exists with the current attributes if s.ID == id && s.Address == address { - return fmt.Errorf("echovault with id %s and address %s already exists", id, address) + return fmt.Errorf("node with id %s and address %s already exists", id, address) } } @@ -201,7 +201,7 @@ func (r *Raft) AddVoter( func (r *Raft) RemoveServer(meta memberlist.NodeMeta) error { if !r.IsRaftLeader() { - return errors.New("not leader, could not remove echovault") + return errors.New("not leader, could not remove node") } if err := r.raft.RemoveServer(meta.ServerID, 0, 0).Error(); err != nil { diff --git a/volumes/modules/module_get/module_get.so b/volumes/modules/module_get/module_get.so index 6c00d781..368288d3 100644 Binary files a/volumes/modules/module_get/module_get.so and b/volumes/modules/module_get/module_get.so differ diff --git a/volumes/modules/module_set/module_set.so b/volumes/modules/module_set/module_set.so index 445f882c..ccba91bd 100644 Binary files a/volumes/modules/module_set/module_set.so and b/volumes/modules/module_set/module_set.so differ