diff --git a/Makefile b/Makefile index 61b39bc0..5bd7f5d8 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ build: run: make build && docker-compose up --build -test: +test-unit: go clean -testcache && go test ./... -coverprofile coverage/coverage.out test-race: diff --git a/cmd/main.go b/cmd/main.go index 7719d87a..d0ce2c0b 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -18,7 +18,6 @@ import ( "context" "github.com/echovault/echovault/internal" "github.com/echovault/echovault/internal/config" - "github.com/echovault/echovault/pkg/commands" "github.com/echovault/echovault/pkg/echovault" "log" "os" @@ -49,7 +48,6 @@ func main() { server, err := echovault.NewEchoVault( echovault.WithContext(ctx), echovault.WithConfig(conf), - echovault.WithCommands(commands.All()), ) if err != nil { diff --git a/coverage/coverage.out b/coverage/coverage.out index ec87e687..5f02b111 100644 --- a/coverage/coverage.out +++ b/coverage/coverage.out @@ -1,3139 +1 @@ mode: set -github.com/echovault/echovault/pkg/modules/acl/commands.go:34.108,35.34 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:35.34,37.3 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:38.2,39.9 2 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:39.9,41.3 1 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:42.2,42.67 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:42.67,44.3 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:45.2,45.42 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:48.106,49.19 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:49.19,51.3 1 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:53.2,54.9 2 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:54.9,56.3 1 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:58.2,60.30 3 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:60.30,61.27 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:61.27,64.9 3 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:68.2,68.16 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:68.16,70.3 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:73.2,77.18 3 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:77.18,79.3 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:79.8,81.3 1 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:82.2,82.21 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:82.21,84.3 1 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:85.2,85.17 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:85.17,87.3 1 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:89.2,90.29 2 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:90.29,92.3 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:95.2,96.51 2 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:96.51,97.22 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:97.22,99.12 2 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:101.3,101.49 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:103.2,103.51 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:103.51,104.22 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:104.22,106.12 2 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:108.3,108.49 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:112.2,113.48 2 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:113.48,114.21 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:114.21,116.12 2 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:118.3,118.47 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:120.2,120.48 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:120.48,121.21 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:121.21,123.12 2 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:125.3,125.47 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:129.2,130.79 2 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:130.79,131.37 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:131.37,133.4 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:135.2,136.30 2 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:136.30,137.10 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:138.100,140.53 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:141.53,143.52 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:144.52,146.52 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:151.2,153.54 2 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:153.54,155.3 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:156.2,156.54 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:156.54,158.3 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:160.2,162.25 2 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:165.102,166.18 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:166.18,168.3 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:170.2,174.35 3 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:174.35,175.36 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:175.36,176.48 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:176.48,178.5 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:179.4,179.12 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:181.3,181.50 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:181.50,182.51 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:182.51,185.5 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:189.2,189.19 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:189.19,192.34 3 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:192.34,195.4 2 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:196.3,197.28 2 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:197.28,199.24 2 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:199.24,201.5 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:203.3,203.26 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:206.2,206.19 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:206.19,208.46 2 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:208.46,209.43 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:209.43,211.38 2 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:211.38,213.30 2 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:213.30,215.7 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:217.5,217.28 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:222.2,222.74 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:225.102,227.9 2 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:227.9,229.3 1 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:230.2,231.33 2 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:231.33,233.3 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:234.2,235.25 2 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:238.106,240.9 2 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:240.9,242.3 1 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:243.2,243.45 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:243.45,245.3 1 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:246.2,246.42 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:249.108,250.18 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:250.18,252.3 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:253.2,254.9 2 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:254.9,256.3 1 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:257.2,257.53 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:257.53,259.3 1 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:260.2,260.42 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:263.106,265.9 2 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:265.9,267.3 1 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:268.2,269.74 2 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:272.103,273.18 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:273.18,275.3 1 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:276.2,277.9 2 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:277.9,279.3 1 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:280.2,282.33 3 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:282.33,285.19 2 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:285.19,287.4 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:287.9,289.4 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:291.3,291.22 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:291.22,293.4 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:295.3,295.18 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:295.18,297.4 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:299.3,299.43 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:299.43,300.61 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:300.61,302.5 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:303.4,303.58 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:303.58,305.5 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:308.3,308.52 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:308.52,309.23 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:309.23,311.13 2 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:313.4,313.39 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:316.3,316.52 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:316.52,317.23 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:317.23,319.13 2 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:321.4,321.39 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:324.3,324.49 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:324.49,325.22 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:325.22,327.13 2 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:329.4,329.37 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:332.3,332.49 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:332.49,333.22 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:333.22,335.13 2 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:337.4,337.37 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:340.3,340.45 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:340.45,341.52 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:341.52,343.13 2 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:345.4,345.41 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:348.3,348.45 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:348.45,349.52 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:349.52,351.5 1 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:354.3,354.55 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:354.55,356.4 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:358.3,358.55 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:358.55,360.4 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:361.3,361.54 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:364.2,365.25 2 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:368.103,369.19 1 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:369.19,371.3 1 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:373.2,374.9 2 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:374.9,376.3 1 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:378.2,382.16 4 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:382.16,384.3 1 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:386.2,386.15 1 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:386.15,387.35 1 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:387.35,389.4 1 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:392.2,396.20 3 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:396.20,397.59 1 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:397.59,399.4 1 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:402.2,402.37 1 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:402.37,403.59 1 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:403.59,405.4 1 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:409.2,409.29 1 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:409.29,413.31 3 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:413.31,414.35 1 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:414.35,417.43 2 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:417.43,419.6 1 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:419.11,422.6 1 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:423.5,423.10 1 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:427.3,427.17 1 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:427.17,429.4 1 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:432.2,432.42 1 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:435.103,436.18 1 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:436.18,438.3 1 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:440.2,441.9 2 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:441.9,443.3 1 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:445.2,449.16 4 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:449.16,451.3 1 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:453.2,453.15 1 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:453.15,454.35 1 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:454.35,456.4 1 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:459.2,461.20 2 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:461.20,464.17 2 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:464.17,466.4 1 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:467.3,468.17 2 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:468.17,470.4 1 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:473.2,473.37 1 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:473.37,476.17 2 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:476.17,478.4 1 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:479.3,480.17 2 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:480.17,482.4 1 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:485.2,486.16 2 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:486.16,488.3 1 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:490.2,490.42 1 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:493.33,501.68 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:501.68,507.5 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:516.68,522.5 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:531.70,537.7 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:546.70,552.7 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:561.70,567.7 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:576.70,582.7 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:591.70,597.7 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:606.70,612.7 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:621.70,627.7 1 1 -github.com/echovault/echovault/pkg/modules/acl/commands.go:639.70,645.7 1 0 -github.com/echovault/echovault/pkg/modules/acl/commands.go:654.70,660.7 1 0 -github.com/echovault/echovault/pkg/modules/list/commands.go:30.105,32.16 2 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:32.16,34.3 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:36.2,38.33 2 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:38.33,41.3 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:43.2,43.52 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:43.52,45.3 1 0 -github.com/echovault/echovault/pkg/modules/list/commands.go:46.2,48.63 2 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:48.63,50.3 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:52.2,52.57 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:55.107,57.16 2 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:57.16,59.3 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:61.2,64.9 3 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:64.9,66.3 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:68.2,68.33 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:68.33,70.3 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:72.2,72.52 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:72.52,74.3 1 0 -github.com/echovault/echovault/pkg/modules/list/commands.go:75.2,78.9 3 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:78.9,80.3 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:82.2,82.40 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:82.40,84.3 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:86.2,86.57 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:89.107,91.16 2 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:91.16,93.3 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:95.2,99.24 4 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:99.24,101.3 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:103.2,103.33 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:103.33,105.3 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:107.2,107.52 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:107.52,109.3 1 0 -github.com/echovault/echovault/pkg/modules/list/commands.go:110.2,113.9 3 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:113.9,115.3 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:118.2,118.40 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:118.40,120.3 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:123.2,123.51 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:123.51,125.3 1 0 -github.com/echovault/echovault/pkg/modules/list/commands.go:127.2,130.15 2 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:130.15,132.43 2 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:132.43,135.4 2 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:136.3,136.20 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:140.2,140.18 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:140.18,142.3 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:147.2,151.17 4 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:151.17,153.3 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:155.2,155.13 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:155.13,158.18 3 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:158.18,160.4 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:160.9,162.4 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:165.2,165.19 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:168.105,170.16 2 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:170.16,172.3 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:174.2,177.9 3 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:177.9,179.3 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:181.2,181.33 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:181.33,183.3 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:185.2,185.51 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:185.51,187.3 1 0 -github.com/echovault/echovault/pkg/modules/list/commands.go:188.2,191.9 3 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:191.9,193.3 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:195.2,195.40 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:195.40,197.3 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:199.2,200.55 2 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:200.55,202.3 1 0 -github.com/echovault/echovault/pkg/modules/list/commands.go:204.2,204.42 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:207.106,209.16 2 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:209.16,211.3 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:213.2,217.24 4 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:217.24,219.3 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:221.2,221.30 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:221.30,223.3 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:225.2,225.33 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:225.33,227.3 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:229.2,229.51 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:229.51,231.3 1 0 -github.com/echovault/echovault/pkg/modules/list/commands.go:232.2,235.9 3 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:235.9,237.3 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:239.2,239.40 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:239.40,241.3 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:243.2,243.34 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:243.34,244.64 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:244.64,246.4 1 0 -github.com/echovault/echovault/pkg/modules/list/commands.go:247.3,247.43 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:250.2,250.66 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:250.66,252.3 1 0 -github.com/echovault/echovault/pkg/modules/list/commands.go:253.2,253.42 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:256.105,258.16 2 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:258.16,260.3 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:262.2,266.9 4 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:266.9,268.3 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:270.2,272.33 2 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:272.33,274.3 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:276.2,276.51 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:276.51,278.3 1 0 -github.com/echovault/echovault/pkg/modules/list/commands.go:279.2,282.9 3 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:282.9,284.3 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:286.2,286.9 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:287.10,287.10 0 0 -github.com/echovault/echovault/pkg/modules/list/commands.go:289.17,291.34 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:291.34,292.26 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:292.26,293.10 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:295.4,295.43 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:295.43,298.5 2 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:300.17,302.39 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:302.39,303.26 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:303.26,304.10 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:306.4,306.43 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:306.43,309.5 2 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:313.2,313.61 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:313.61,315.3 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:317.2,317.55 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:317.55,319.3 1 0 -github.com/echovault/echovault/pkg/modules/list/commands.go:321.2,321.42 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:324.106,326.16 2 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:326.16,328.3 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:330.2,334.116 4 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:334.116,336.3 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:338.2,338.75 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:338.75,340.3 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:342.2,342.54 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:342.54,344.3 1 0 -github.com/echovault/echovault/pkg/modules/list/commands.go:345.2,348.16 3 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:348.16,350.3 1 0 -github.com/echovault/echovault/pkg/modules/list/commands.go:351.2,356.33 4 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:356.33,358.3 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:360.2,360.19 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:361.14,363.24 2 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:363.24,365.4 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:365.9,365.32 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:365.32,367.4 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:368.15,370.24 2 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:370.24,372.4 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:372.9,372.32 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:372.32,374.4 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:377.2,377.16 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:377.16,379.3 1 0 -github.com/echovault/echovault/pkg/modules/list/commands.go:381.2,381.42 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:384.106,386.16 2 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:386.16,388.3 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:390.2,392.31 2 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:392.31,394.3 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:396.2,398.33 2 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:398.33,399.34 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:400.17,401.61 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:402.11,403.62 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:403.62,405.5 1 0 -github.com/echovault/echovault/pkg/modules/list/commands.go:406.4,406.68 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:406.68,408.5 1 0 -github.com/echovault/echovault/pkg/modules/list/commands.go:410.8,411.52 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:411.52,413.4 1 0 -github.com/echovault/echovault/pkg/modules/list/commands.go:415.2,420.9 4 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:420.9,422.3 1 0 -github.com/echovault/echovault/pkg/modules/list/commands.go:424.2,424.73 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:424.73,426.3 1 0 -github.com/echovault/echovault/pkg/modules/list/commands.go:427.2,427.42 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:430.106,432.16 2 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:432.16,434.3 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:436.2,440.31 3 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:440.31,442.3 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:444.2,444.33 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:444.33,445.34 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:446.17,447.61 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:448.11,449.62 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:449.62,451.5 1 0 -github.com/echovault/echovault/pkg/modules/list/commands.go:452.4,453.68 2 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:453.68,455.5 1 0 -github.com/echovault/echovault/pkg/modules/list/commands.go:457.8,458.52 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:458.52,460.4 1 0 -github.com/echovault/echovault/pkg/modules/list/commands.go:461.3,461.35 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:464.2,468.9 3 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:468.9,470.3 1 0 -github.com/echovault/echovault/pkg/modules/list/commands.go:472.2,472.73 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:472.73,474.3 1 0 -github.com/echovault/echovault/pkg/modules/list/commands.go:475.2,475.42 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:478.104,480.16 2 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:480.16,482.3 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:484.2,486.33 2 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:486.33,488.3 1 0 -github.com/echovault/echovault/pkg/modules/list/commands.go:490.2,490.51 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:490.51,492.3 1 0 -github.com/echovault/echovault/pkg/modules/list/commands.go:493.2,496.9 3 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:496.9,498.3 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:500.2,500.33 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:501.10,502.60 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:502.60,504.4 1 0 -github.com/echovault/echovault/pkg/modules/list/commands.go:505.3,505.54 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:506.14,507.70 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:507.70,509.4 1 0 -github.com/echovault/echovault/pkg/modules/list/commands.go:510.3,510.64 1 1 -github.com/echovault/echovault/pkg/modules/list/commands.go:514.33,634.2 1 0 -github.com/echovault/echovault/pkg/modules/list/key_funcs.go:23.59,24.18 1 1 -github.com/echovault/echovault/pkg/modules/list/key_funcs.go:24.18,26.3 1 1 -github.com/echovault/echovault/pkg/modules/list/key_funcs.go:27.2,31.8 1 1 -github.com/echovault/echovault/pkg/modules/list/key_funcs.go:34.57,35.19 1 1 -github.com/echovault/echovault/pkg/modules/list/key_funcs.go:35.19,37.3 1 1 -github.com/echovault/echovault/pkg/modules/list/key_funcs.go:38.2,42.8 1 1 -github.com/echovault/echovault/pkg/modules/list/key_funcs.go:45.58,46.19 1 1 -github.com/echovault/echovault/pkg/modules/list/key_funcs.go:46.19,48.3 1 1 -github.com/echovault/echovault/pkg/modules/list/key_funcs.go:49.2,53.8 1 1 -github.com/echovault/echovault/pkg/modules/list/key_funcs.go:56.60,57.19 1 1 -github.com/echovault/echovault/pkg/modules/list/key_funcs.go:57.19,59.3 1 1 -github.com/echovault/echovault/pkg/modules/list/key_funcs.go:60.2,64.8 1 1 -github.com/echovault/echovault/pkg/modules/list/key_funcs.go:67.60,68.19 1 1 -github.com/echovault/echovault/pkg/modules/list/key_funcs.go:68.19,70.3 1 1 -github.com/echovault/echovault/pkg/modules/list/key_funcs.go:71.2,75.8 1 1 -github.com/echovault/echovault/pkg/modules/list/key_funcs.go:78.58,79.19 1 1 -github.com/echovault/echovault/pkg/modules/list/key_funcs.go:79.19,81.3 1 1 -github.com/echovault/echovault/pkg/modules/list/key_funcs.go:82.2,86.8 1 1 -github.com/echovault/echovault/pkg/modules/list/key_funcs.go:89.59,90.19 1 1 -github.com/echovault/echovault/pkg/modules/list/key_funcs.go:90.19,92.3 1 1 -github.com/echovault/echovault/pkg/modules/list/key_funcs.go:93.2,97.8 1 1 -github.com/echovault/echovault/pkg/modules/list/key_funcs.go:100.58,101.19 1 1 -github.com/echovault/echovault/pkg/modules/list/key_funcs.go:101.19,103.3 1 1 -github.com/echovault/echovault/pkg/modules/list/key_funcs.go:104.2,108.8 1 1 -github.com/echovault/echovault/pkg/modules/list/key_funcs.go:111.59,112.18 1 1 -github.com/echovault/echovault/pkg/modules/list/key_funcs.go:112.18,114.3 1 1 -github.com/echovault/echovault/pkg/modules/list/key_funcs.go:115.2,119.8 1 1 -github.com/echovault/echovault/pkg/modules/list/key_funcs.go:122.59,123.19 1 1 -github.com/echovault/echovault/pkg/modules/list/key_funcs.go:123.19,125.3 1 1 -github.com/echovault/echovault/pkg/modules/list/key_funcs.go:126.2,130.8 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:36.104,38.16 2 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:38.16,40.3 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:42.2,48.16 6 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:48.16,50.3 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:54.2,54.16 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:54.16,55.34 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:55.34,57.4 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:57.9,59.4 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:62.2,62.44 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:62.44,64.34 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:64.34,66.4 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:67.3,67.36 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:68.8,68.51 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:68.51,70.33 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:70.33,72.4 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:73.3,73.45 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:74.8,76.34 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:76.34,79.4 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:79.9,82.4 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:84.2,84.16 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:84.16,86.3 1 0 -github.com/echovault/echovault/pkg/modules/generic/commands.go:87.2,89.76 2 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:89.76,91.3 1 0 -github.com/echovault/echovault/pkg/modules/generic/commands.go:94.2,94.28 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:94.28,96.3 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:98.2,98.17 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:101.105,103.16 2 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:103.16,105.3 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:107.2,110.15 2 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:110.15,111.29 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:111.29,112.16 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:112.16,118.5 2 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:123.2,123.30 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:123.30,124.15 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:124.15,129.4 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:134.2,134.28 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:134.28,135.31 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:135.31,136.52 1 0 -github.com/echovault/echovault/pkg/modules/generic/commands.go:136.52,138.5 1 0 -github.com/echovault/echovault/pkg/modules/generic/commands.go:139.4,140.12 2 0 -github.com/echovault/echovault/pkg/modules/generic/commands.go:142.3,142.60 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:142.60,144.4 1 0 -github.com/echovault/echovault/pkg/modules/generic/commands.go:145.3,145.55 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:149.2,149.28 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:149.28,150.58 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:150.58,152.4 1 0 -github.com/echovault/echovault/pkg/modules/generic/commands.go:155.2,155.42 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:158.104,160.16 2 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:160.16,162.3 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:163.2,165.33 2 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:165.33,167.3 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:169.2,170.16 2 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:170.16,172.3 1 0 -github.com/echovault/echovault/pkg/modules/generic/commands.go:173.2,177.51 3 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:180.105,182.16 2 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:182.16,184.3 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:186.2,189.36 3 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:189.36,190.31 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:190.31,192.12 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:194.3,194.33 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:194.33,196.18 2 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:196.18,198.5 1 0 -github.com/echovault/echovault/pkg/modules/generic/commands.go:199.4,200.12 2 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:202.3,202.19 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:204.2,204.15 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:204.15,205.34 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:205.34,206.14 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:206.14,209.5 2 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:213.2,213.28 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:213.28,215.3 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:217.2,219.30 2 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:219.30,220.24 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:220.24,222.12 2 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:224.3,224.96 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:227.2,227.19 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:230.104,232.16 2 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:232.16,234.3 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:235.2,236.37 2 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:236.37,238.17 2 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:238.17,240.12 2 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:242.3,242.13 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:244.2,244.51 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:247.108,249.16 2 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:249.16,251.3 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:253.2,255.33 2 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:255.33,257.3 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:259.2,259.51 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:259.51,261.3 1 0 -github.com/echovault/echovault/pkg/modules/generic/commands.go:262.2,265.31 3 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:265.31,267.3 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:269.2,271.30 2 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:274.111,276.16 2 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:276.16,278.3 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:280.2,282.33 2 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:282.33,284.3 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:286.2,286.52 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:286.52,288.3 1 0 -github.com/echovault/echovault/pkg/modules/generic/commands.go:289.2,293.31 3 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:293.31,295.3 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:297.2,298.46 2 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:298.46,300.3 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:302.2,302.47 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:305.104,307.16 2 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:307.16,309.3 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:311.2,315.33 3 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:315.33,317.3 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:319.2,319.52 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:319.52,321.3 1 0 -github.com/echovault/echovault/pkg/modules/generic/commands.go:322.2,326.31 3 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:326.31,328.3 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:330.2,331.39 2 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:331.39,333.3 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:335.2,335.12 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:335.12,337.3 1 0 -github.com/echovault/echovault/pkg/modules/generic/commands.go:339.2,339.47 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:342.107,344.16 2 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:344.16,346.3 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:348.2,352.16 3 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:352.16,354.3 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:355.2,356.42 2 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:356.42,358.3 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:360.2,360.33 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:360.33,362.3 1 0 -github.com/echovault/echovault/pkg/modules/generic/commands.go:364.2,364.51 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:364.51,366.3 1 0 -github.com/echovault/echovault/pkg/modules/generic/commands.go:367.2,369.19 2 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:369.19,372.3 2 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:374.2,376.33 2 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:377.12,378.39 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:378.39,380.4 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:381.3,381.46 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:382.12,383.39 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:383.39,385.4 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:386.3,386.46 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:387.12,388.39 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:388.39,390.4 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:391.3,391.39 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:391.39,393.4 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:394.3,394.46 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:395.12,396.39 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:396.39,397.40 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:397.40,399.5 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:400.4,400.47 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:402.3,402.46 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:403.10,404.71 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:407.2,407.30 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:410.109,412.16 2 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:412.16,414.3 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:416.2,420.16 3 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:420.16,422.3 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:423.2,424.44 2 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:424.44,426.3 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:428.2,428.33 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:428.33,430.3 1 0 -github.com/echovault/echovault/pkg/modules/generic/commands.go:432.2,432.51 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:432.51,434.3 1 0 -github.com/echovault/echovault/pkg/modules/generic/commands.go:435.2,437.19 2 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:437.19,440.3 2 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:442.2,444.33 2 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:445.12,446.39 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:446.39,448.4 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:449.3,449.46 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:450.12,451.39 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:451.39,453.4 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:454.3,454.46 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:455.12,456.39 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:456.39,458.4 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:459.3,459.39 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:459.39,461.4 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:462.3,462.46 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:463.12,464.39 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:464.39,465.40 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:465.40,467.5 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:468.4,468.47 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:470.3,470.46 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:471.10,472.71 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:475.2,475.30 1 1 -github.com/echovault/echovault/pkg/modules/generic/commands.go:478.33,647.2 1 0 -github.com/echovault/echovault/pkg/modules/generic/key_funcs.go:23.57,24.34 1 1 -github.com/echovault/echovault/pkg/modules/generic/key_funcs.go:24.34,26.3 1 1 -github.com/echovault/echovault/pkg/modules/generic/key_funcs.go:27.2,31.8 1 1 -github.com/echovault/echovault/pkg/modules/generic/key_funcs.go:34.58,35.25 1 1 -github.com/echovault/echovault/pkg/modules/generic/key_funcs.go:35.25,37.3 1 1 -github.com/echovault/echovault/pkg/modules/generic/key_funcs.go:38.2,39.30 2 1 -github.com/echovault/echovault/pkg/modules/generic/key_funcs.go:39.30,40.15 1 1 -github.com/echovault/echovault/pkg/modules/generic/key_funcs.go:40.15,42.4 1 1 -github.com/echovault/echovault/pkg/modules/generic/key_funcs.go:44.2,48.8 1 1 -github.com/echovault/echovault/pkg/modules/generic/key_funcs.go:51.57,52.19 1 1 -github.com/echovault/echovault/pkg/modules/generic/key_funcs.go:52.19,54.3 1 1 -github.com/echovault/echovault/pkg/modules/generic/key_funcs.go:55.2,59.8 1 1 -github.com/echovault/echovault/pkg/modules/generic/key_funcs.go:62.58,63.18 1 1 -github.com/echovault/echovault/pkg/modules/generic/key_funcs.go:63.18,65.3 1 1 -github.com/echovault/echovault/pkg/modules/generic/key_funcs.go:66.2,70.8 1 1 -github.com/echovault/echovault/pkg/modules/generic/key_funcs.go:73.57,74.18 1 1 -github.com/echovault/echovault/pkg/modules/generic/key_funcs.go:74.18,76.3 1 1 -github.com/echovault/echovault/pkg/modules/generic/key_funcs.go:77.2,81.8 1 1 -github.com/echovault/echovault/pkg/modules/generic/key_funcs.go:84.61,85.19 1 1 -github.com/echovault/echovault/pkg/modules/generic/key_funcs.go:85.19,87.3 1 1 -github.com/echovault/echovault/pkg/modules/generic/key_funcs.go:88.2,92.8 1 1 -github.com/echovault/echovault/pkg/modules/generic/key_funcs.go:95.64,96.19 1 1 -github.com/echovault/echovault/pkg/modules/generic/key_funcs.go:96.19,98.3 1 1 -github.com/echovault/echovault/pkg/modules/generic/key_funcs.go:99.2,103.8 1 1 -github.com/echovault/echovault/pkg/modules/generic/key_funcs.go:106.57,107.19 1 1 -github.com/echovault/echovault/pkg/modules/generic/key_funcs.go:107.19,109.3 1 1 -github.com/echovault/echovault/pkg/modules/generic/key_funcs.go:110.2,114.8 1 1 -github.com/echovault/echovault/pkg/modules/generic/key_funcs.go:117.60,118.34 1 1 -github.com/echovault/echovault/pkg/modules/generic/key_funcs.go:118.34,120.3 1 1 -github.com/echovault/echovault/pkg/modules/generic/key_funcs.go:121.2,125.8 1 1 -github.com/echovault/echovault/pkg/modules/generic/key_funcs.go:128.62,129.34 1 0 -github.com/echovault/echovault/pkg/modules/generic/key_funcs.go:129.34,131.3 1 0 -github.com/echovault/echovault/pkg/modules/generic/key_funcs.go:132.2,136.8 1 0 -github.com/echovault/echovault/pkg/modules/generic/utils.go:32.96,33.19 1 1 -github.com/echovault/echovault/pkg/modules/generic/utils.go:33.19,35.3 1 1 -github.com/echovault/echovault/pkg/modules/generic/utils.go:36.2,36.33 1 1 -github.com/echovault/echovault/pkg/modules/generic/utils.go:37.13,39.53 2 1 -github.com/echovault/echovault/pkg/modules/generic/utils.go:41.12,42.26 1 1 -github.com/echovault/echovault/pkg/modules/generic/utils.go:42.26,44.4 1 1 -github.com/echovault/echovault/pkg/modules/generic/utils.go:45.3,46.53 2 1 -github.com/echovault/echovault/pkg/modules/generic/utils.go:48.12,49.26 1 1 -github.com/echovault/echovault/pkg/modules/generic/utils.go:49.26,51.4 1 1 -github.com/echovault/echovault/pkg/modules/generic/utils.go:52.3,53.53 2 1 -github.com/echovault/echovault/pkg/modules/generic/utils.go:55.12,56.19 1 1 -github.com/echovault/echovault/pkg/modules/generic/utils.go:56.19,58.4 1 1 -github.com/echovault/echovault/pkg/modules/generic/utils.go:59.3,59.29 1 1 -github.com/echovault/echovault/pkg/modules/generic/utils.go:59.29,61.4 1 1 -github.com/echovault/echovault/pkg/modules/generic/utils.go:62.3,64.17 3 1 -github.com/echovault/echovault/pkg/modules/generic/utils.go:64.17,66.4 1 1 -github.com/echovault/echovault/pkg/modules/generic/utils.go:67.3,68.53 2 1 -github.com/echovault/echovault/pkg/modules/generic/utils.go:70.12,71.19 1 1 -github.com/echovault/echovault/pkg/modules/generic/utils.go:71.19,73.4 1 1 -github.com/echovault/echovault/pkg/modules/generic/utils.go:74.3,74.29 1 1 -github.com/echovault/echovault/pkg/modules/generic/utils.go:74.29,76.4 1 1 -github.com/echovault/echovault/pkg/modules/generic/utils.go:77.3,79.17 3 1 -github.com/echovault/echovault/pkg/modules/generic/utils.go:79.17,81.4 1 1 -github.com/echovault/echovault/pkg/modules/generic/utils.go:82.3,83.53 2 1 -github.com/echovault/echovault/pkg/modules/generic/utils.go:85.14,86.19 1 1 -github.com/echovault/echovault/pkg/modules/generic/utils.go:86.19,88.4 1 1 -github.com/echovault/echovault/pkg/modules/generic/utils.go:89.3,89.29 1 1 -github.com/echovault/echovault/pkg/modules/generic/utils.go:89.29,91.4 1 1 -github.com/echovault/echovault/pkg/modules/generic/utils.go:92.3,94.17 3 1 -github.com/echovault/echovault/pkg/modules/generic/utils.go:94.17,96.4 1 1 -github.com/echovault/echovault/pkg/modules/generic/utils.go:97.3,98.53 2 1 -github.com/echovault/echovault/pkg/modules/generic/utils.go:100.14,101.19 1 1 -github.com/echovault/echovault/pkg/modules/generic/utils.go:101.19,103.4 1 1 -github.com/echovault/echovault/pkg/modules/generic/utils.go:104.3,104.29 1 1 -github.com/echovault/echovault/pkg/modules/generic/utils.go:104.29,106.4 1 1 -github.com/echovault/echovault/pkg/modules/generic/utils.go:107.3,109.17 3 1 -github.com/echovault/echovault/pkg/modules/generic/utils.go:109.17,111.4 1 1 -github.com/echovault/echovault/pkg/modules/generic/utils.go:112.3,113.53 2 1 -github.com/echovault/echovault/pkg/modules/generic/utils.go:115.10,116.95 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:31.105,33.16 2 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:33.16,35.3 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:37.2,40.25 3 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:40.25,42.3 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:44.2,44.38 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:44.38,46.3 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:48.2,48.33 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:48.33,50.17 2 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:50.17,52.4 1 0 -github.com/echovault/echovault/pkg/modules/hash/commands.go:53.3,54.59 2 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:54.59,56.4 1 0 -github.com/echovault/echovault/pkg/modules/hash/commands.go:57.3,57.59 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:60.2,60.51 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:60.51,62.3 1 0 -github.com/echovault/echovault/pkg/modules/hash/commands.go:63.2,66.9 3 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:66.9,68.3 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:70.2,71.36 2 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:71.36,72.42 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:72.42,73.26 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:73.26,76.5 2 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:77.4,77.12 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:79.3,80.13 2 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:82.2,82.55 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:82.55,84.3 1 0 -github.com/echovault/echovault/pkg/modules/hash/commands.go:86.2,86.51 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:89.105,91.16 2 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:91.16,93.3 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:95.2,98.33 3 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:98.33,100.3 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:102.2,102.52 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:102.52,104.3 1 0 -github.com/echovault/echovault/pkg/modules/hash/commands.go:105.2,108.9 3 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:108.9,110.3 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:112.2,115.31 3 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:115.31,117.19 2 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:117.19,119.12 2 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:121.3,121.34 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:121.34,123.12 2 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:125.3,125.31 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:125.31,127.12 2 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:129.3,129.35 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:129.35,132.12 3 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:134.3,134.32 1 0 -github.com/echovault/echovault/pkg/modules/hash/commands.go:137.2,137.25 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:140.108,142.16 2 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:142.16,144.3 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:146.2,149.33 3 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:149.33,151.3 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:153.2,153.52 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:153.52,155.3 1 0 -github.com/echovault/echovault/pkg/modules/hash/commands.go:156.2,159.9 3 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:159.9,161.3 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:163.2,166.31 3 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:166.31,168.19 2 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:168.19,170.12 2 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:172.3,172.34 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:172.34,174.12 2 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:176.3,176.35 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:176.35,179.12 3 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:181.3,181.31 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:181.31,183.12 2 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:185.3,185.18 1 0 -github.com/echovault/echovault/pkg/modules/hash/commands.go:188.2,188.25 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:191.106,193.16 2 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:193.16,195.3 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:197.2,199.33 2 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:199.33,201.3 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:203.2,203.52 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:203.52,205.3 1 0 -github.com/echovault/echovault/pkg/modules/hash/commands.go:206.2,209.9 3 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:209.9,211.3 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:213.2,214.27 2 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:214.27,215.32 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:215.32,217.12 2 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:219.3,219.33 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:219.33,222.12 3 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:224.3,224.29 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:224.29,226.4 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:229.2,229.25 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:232.111,234.16 2 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:234.16,236.3 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:238.2,241.19 3 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:241.19,243.17 2 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:243.17,245.4 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:246.3,246.13 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:246.13,248.4 1 0 -github.com/echovault/echovault/pkg/modules/hash/commands.go:249.3,249.12 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:252.2,253.19 2 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:253.19,254.46 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:254.46,256.4 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:256.9,258.4 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:261.2,261.33 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:261.33,263.3 1 0 -github.com/echovault/echovault/pkg/modules/hash/commands.go:265.2,265.52 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:265.52,267.3 1 0 -github.com/echovault/echovault/pkg/modules/hash/commands.go:268.2,271.9 3 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:271.9,273.3 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:276.2,276.24 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:276.24,278.17 2 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:278.17,280.4 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:281.3,281.34 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:281.34,283.18 2 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:283.18,284.36 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:284.36,286.14 2 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:288.5,288.37 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:288.37,291.14 3 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:293.5,293.33 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:293.33,295.14 2 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:299.3,299.26 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:303.2,304.29 2 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:304.29,306.3 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:309.2,311.46 3 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:311.46,315.16 3 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:315.16,316.59 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:316.59,318.5 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:322.2,323.16 2 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:323.16,325.3 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:326.2,326.38 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:326.38,328.17 2 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:328.17,329.41 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:329.41,331.13 2 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:333.4,333.42 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:333.42,336.13 3 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:338.4,338.38 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:338.38,340.13 2 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:345.2,345.25 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:348.105,350.16 2 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:350.16,352.3 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:354.2,356.33 2 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:356.33,358.3 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:360.2,360.52 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:360.52,362.3 1 0 -github.com/echovault/echovault/pkg/modules/hash/commands.go:363.2,366.9 3 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:366.9,368.3 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:370.2,370.55 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:373.106,375.16 2 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:375.16,377.3 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:379.2,381.33 2 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:381.33,383.3 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:385.2,385.52 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:385.52,387.3 1 0 -github.com/echovault/echovault/pkg/modules/hash/commands.go:388.2,391.9 3 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:391.9,393.3 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:395.2,396.29 2 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:396.29,398.3 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:400.2,400.25 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:403.108,405.16 2 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:405.16,407.3 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:409.2,415.47 5 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:415.47,417.17 2 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:417.17,419.4 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:420.3,420.21 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:421.8,423.17 2 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:423.17,425.4 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:426.3,426.19 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:429.2,429.33 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:429.33,430.62 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:430.62,432.4 1 0 -github.com/echovault/echovault/pkg/modules/hash/commands.go:433.3,435.48 3 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:435.48,437.57 2 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:437.57,439.5 1 0 -github.com/echovault/echovault/pkg/modules/hash/commands.go:440.4,440.96 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:441.9,443.57 2 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:443.57,445.5 1 0 -github.com/echovault/echovault/pkg/modules/hash/commands.go:446.4,446.60 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:450.2,450.52 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:450.52,452.3 1 0 -github.com/echovault/echovault/pkg/modules/hash/commands.go:453.2,456.9 3 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:456.9,458.3 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:460.2,460.24 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:460.24,462.3 1 0 -github.com/echovault/echovault/pkg/modules/hash/commands.go:464.2,464.28 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:465.10,466.69 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:467.11,469.48 2 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:469.48,471.4 1 0 -github.com/echovault/echovault/pkg/modules/hash/commands.go:471.9,473.4 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:474.15,476.48 2 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:476.48,478.4 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:478.9,480.4 1 0 -github.com/echovault/echovault/pkg/modules/hash/commands.go:483.2,483.55 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:483.55,485.3 1 0 -github.com/echovault/echovault/pkg/modules/hash/commands.go:487.2,487.40 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:487.40,489.3 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:491.2,492.47 2 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:495.108,497.16 2 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:497.16,499.3 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:501.2,503.33 2 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:503.33,505.3 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:507.2,507.52 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:507.52,509.3 1 0 -github.com/echovault/echovault/pkg/modules/hash/commands.go:510.2,513.9 3 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:513.9,515.3 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:517.2,518.33 2 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:518.33,520.34 2 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:520.34,522.4 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:523.3,523.35 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:523.35,526.4 2 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:527.3,527.31 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:527.31,529.4 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:532.2,532.25 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:535.108,537.16 2 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:537.16,539.3 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:541.2,544.33 3 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:544.33,546.3 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:548.2,548.52 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:548.52,550.3 1 0 -github.com/echovault/echovault/pkg/modules/hash/commands.go:551.2,554.9 3 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:554.9,556.3 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:558.2,558.24 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:558.24,560.3 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:562.2,562.30 1 0 -github.com/echovault/echovault/pkg/modules/hash/commands.go:565.105,567.16 2 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:567.16,569.3 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:571.2,574.33 3 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:574.33,576.3 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:578.2,578.51 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:578.51,580.3 1 0 -github.com/echovault/echovault/pkg/modules/hash/commands.go:581.2,584.9 3 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:584.9,586.3 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:588.2,590.31 2 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:590.31,591.25 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:591.25,594.4 2 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:597.2,597.55 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:597.55,599.3 1 0 -github.com/echovault/echovault/pkg/modules/hash/commands.go:601.2,601.51 1 1 -github.com/echovault/echovault/pkg/modules/hash/commands.go:604.33,725.2 1 0 -github.com/echovault/echovault/pkg/modules/hash/key_funcs.go:23.58,24.18 1 1 -github.com/echovault/echovault/pkg/modules/hash/key_funcs.go:24.18,26.3 1 1 -github.com/echovault/echovault/pkg/modules/hash/key_funcs.go:27.2,31.8 1 1 -github.com/echovault/echovault/pkg/modules/hash/key_funcs.go:34.60,35.18 1 0 -github.com/echovault/echovault/pkg/modules/hash/key_funcs.go:35.18,37.3 1 0 -github.com/echovault/echovault/pkg/modules/hash/key_funcs.go:38.2,42.8 1 0 -github.com/echovault/echovault/pkg/modules/hash/key_funcs.go:45.58,46.18 1 1 -github.com/echovault/echovault/pkg/modules/hash/key_funcs.go:46.18,48.3 1 1 -github.com/echovault/echovault/pkg/modules/hash/key_funcs.go:49.2,53.8 1 1 -github.com/echovault/echovault/pkg/modules/hash/key_funcs.go:56.61,57.18 1 1 -github.com/echovault/echovault/pkg/modules/hash/key_funcs.go:57.18,59.3 1 1 -github.com/echovault/echovault/pkg/modules/hash/key_funcs.go:60.2,64.8 1 1 -github.com/echovault/echovault/pkg/modules/hash/key_funcs.go:67.59,68.19 1 1 -github.com/echovault/echovault/pkg/modules/hash/key_funcs.go:68.19,70.3 1 1 -github.com/echovault/echovault/pkg/modules/hash/key_funcs.go:71.2,75.8 1 1 -github.com/echovault/echovault/pkg/modules/hash/key_funcs.go:78.64,79.34 1 1 -github.com/echovault/echovault/pkg/modules/hash/key_funcs.go:79.34,81.3 1 1 -github.com/echovault/echovault/pkg/modules/hash/key_funcs.go:82.2,82.19 1 1 -github.com/echovault/echovault/pkg/modules/hash/key_funcs.go:82.19,88.3 1 1 -github.com/echovault/echovault/pkg/modules/hash/key_funcs.go:89.2,93.8 1 1 -github.com/echovault/echovault/pkg/modules/hash/key_funcs.go:96.58,97.19 1 1 -github.com/echovault/echovault/pkg/modules/hash/key_funcs.go:97.19,99.3 1 1 -github.com/echovault/echovault/pkg/modules/hash/key_funcs.go:100.2,104.8 1 1 -github.com/echovault/echovault/pkg/modules/hash/key_funcs.go:107.59,108.19 1 1 -github.com/echovault/echovault/pkg/modules/hash/key_funcs.go:108.19,110.3 1 1 -github.com/echovault/echovault/pkg/modules/hash/key_funcs.go:111.2,115.8 1 1 -github.com/echovault/echovault/pkg/modules/hash/key_funcs.go:118.61,119.19 1 1 -github.com/echovault/echovault/pkg/modules/hash/key_funcs.go:119.19,121.3 1 1 -github.com/echovault/echovault/pkg/modules/hash/key_funcs.go:122.2,126.8 1 1 -github.com/echovault/echovault/pkg/modules/hash/key_funcs.go:129.61,130.19 1 1 -github.com/echovault/echovault/pkg/modules/hash/key_funcs.go:130.19,132.3 1 1 -github.com/echovault/echovault/pkg/modules/hash/key_funcs.go:133.2,137.8 1 1 -github.com/echovault/echovault/pkg/modules/hash/key_funcs.go:140.61,141.19 1 1 -github.com/echovault/echovault/pkg/modules/hash/key_funcs.go:141.19,143.3 1 1 -github.com/echovault/echovault/pkg/modules/hash/key_funcs.go:144.2,148.8 1 1 -github.com/echovault/echovault/pkg/modules/hash/key_funcs.go:151.58,152.18 1 1 -github.com/echovault/echovault/pkg/modules/hash/key_funcs.go:152.18,154.3 1 1 -github.com/echovault/echovault/pkg/modules/hash/key_funcs.go:155.2,159.8 1 1 -github.com/echovault/echovault/pkg/modules/connection/commands.go:26.108,27.18 1 1 -github.com/echovault/echovault/pkg/modules/connection/commands.go:28.10,29.54 1 1 -github.com/echovault/echovault/pkg/modules/connection/commands.go:30.9,31.34 1 1 -github.com/echovault/echovault/pkg/modules/connection/commands.go:32.9,33.72 1 1 -github.com/echovault/echovault/pkg/modules/connection/commands.go:37.33,45.68 1 0 -github.com/echovault/echovault/pkg/modules/connection/commands.go:45.68,51.5 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:125.72,127.23 2 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:127.23,129.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:130.2,131.16 2 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:131.16,133.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:134.2,134.45 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:138.56,140.16 2 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:140.16,142.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:143.2,143.45 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:154.65,157.18 2 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:157.18,159.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:159.8,161.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:163.2,163.21 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:163.21,165.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:167.2,167.17 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:167.17,169.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:171.2,171.21 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:171.21,173.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:175.2,175.20 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:175.20,177.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:179.2,179.20 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:179.20,181.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:183.2,183.24 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:183.24,185.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:187.2,187.50 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:187.50,189.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:191.2,191.53 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:191.53,193.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:195.2,195.49 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:195.49,197.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:199.2,199.52 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:199.52,201.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:203.2,203.50 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:203.50,205.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:207.2,207.50 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:207.50,209.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:211.2,211.47 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:211.47,213.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:215.2,215.47 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:215.47,217.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:219.2,219.48 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:219.48,221.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:223.2,223.43 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:223.43,225.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:227.2,227.44 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:227.44,229.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:231.2,231.47 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:231.47,233.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:235.2,235.47 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:235.47,237.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:239.2,240.16 2 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:240.16,242.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:244.2,244.40 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:291.84,293.16 2 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:293.16,295.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:297.2,299.16 3 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:299.16,301.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:303.2,306.35 3 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:306.35,312.35 4 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:312.35,314.4 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:317.2,317.20 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:327.75,330.16 3 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:330.16,332.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:333.2,333.40 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:337.55,339.16 2 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:339.16,341.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:342.2,342.45 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:353.75,355.9 2 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:356.21,357.29 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:358.23,359.31 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:360.10,361.31 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:364.2,365.16 2 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:365.16,367.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:369.2,369.40 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:375.53,377.16 2 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:377.16,379.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_acl.go:380.2,380.40 1 0 -github.com/echovault/echovault/pkg/echovault/api_admin.go:39.85,42.9 2 0 -github.com/echovault/echovault/pkg/echovault/api_admin.go:43.28,44.71 1 0 -github.com/echovault/echovault/pkg/echovault/api_admin.go:45.29,46.73 1 0 -github.com/echovault/echovault/pkg/echovault/api_admin.go:47.28,48.71 1 0 -github.com/echovault/echovault/pkg/echovault/api_admin.go:51.2,52.16 2 0 -github.com/echovault/echovault/pkg/echovault/api_admin.go:52.16,54.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_admin.go:56.2,56.45 1 0 -github.com/echovault/echovault/pkg/echovault/api_admin.go:62.55,64.16 2 0 -github.com/echovault/echovault/pkg/echovault/api_admin.go:64.16,66.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_admin.go:67.2,67.41 1 0 -github.com/echovault/echovault/pkg/echovault/api_admin.go:71.49,73.16 2 0 -github.com/echovault/echovault/pkg/echovault/api_admin.go:73.16,75.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_admin.go:76.2,76.40 1 0 -github.com/echovault/echovault/pkg/echovault/api_admin.go:80.50,82.16 2 0 -github.com/echovault/echovault/pkg/echovault/api_admin.go:82.16,84.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_admin.go:85.2,85.41 1 0 -github.com/echovault/echovault/pkg/echovault/api_admin.go:89.55,91.16 2 0 -github.com/echovault/echovault/pkg/echovault/api_admin.go:91.16,93.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_admin.go:94.2,94.40 1 0 -github.com/echovault/echovault/pkg/echovault/api_generic.go:87.85,90.9 2 1 -github.com/echovault/echovault/pkg/echovault/api_generic.go:91.18,92.26 1 1 -github.com/echovault/echovault/pkg/echovault/api_generic.go:93.18,94.26 1 1 -github.com/echovault/echovault/pkg/echovault/api_generic.go:97.2,97.9 1 1 -github.com/echovault/echovault/pkg/echovault/api_generic.go:98.23,99.65 1 1 -github.com/echovault/echovault/pkg/echovault/api_generic.go:100.23,101.65 1 1 -github.com/echovault/echovault/pkg/echovault/api_generic.go:102.25,103.69 1 1 -github.com/echovault/echovault/pkg/echovault/api_generic.go:104.25,105.69 1 1 -github.com/echovault/echovault/pkg/echovault/api_generic.go:108.2,108.17 1 1 -github.com/echovault/echovault/pkg/echovault/api_generic.go:108.17,110.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_generic.go:112.2,113.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_generic.go:113.16,115.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_generic.go:117.2,117.40 1 1 -github.com/echovault/echovault/pkg/echovault/api_generic.go:132.74,135.28 2 1 -github.com/echovault/echovault/pkg/echovault/api_generic.go:135.28,137.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_generic.go:139.2,140.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_generic.go:140.16,142.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_generic.go:144.2,144.40 1 1 -github.com/echovault/echovault/pkg/echovault/api_generic.go:155.58,157.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_generic.go:157.16,159.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_generic.go:160.2,160.40 1 1 -github.com/echovault/echovault/pkg/echovault/api_generic.go:171.65,173.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_generic.go:173.16,175.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_generic.go:176.2,176.45 1 1 -github.com/echovault/echovault/pkg/echovault/api_generic.go:186.59,188.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_generic.go:188.16,190.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_generic.go:191.2,191.41 1 1 -github.com/echovault/echovault/pkg/echovault/api_generic.go:202.60,204.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_generic.go:204.16,206.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_generic.go:207.2,207.41 1 1 -github.com/echovault/echovault/pkg/echovault/api_generic.go:217.62,219.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_generic.go:219.16,221.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_generic.go:222.2,222.41 1 1 -github.com/echovault/echovault/pkg/echovault/api_generic.go:232.63,234.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_generic.go:234.16,236.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_generic.go:237.2,237.41 1 1 -github.com/echovault/echovault/pkg/echovault/api_generic.go:247.55,249.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_generic.go:249.16,251.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_generic.go:252.2,252.41 1 1 -github.com/echovault/echovault/pkg/echovault/api_generic.go:262.56,264.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_generic.go:264.16,266.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_generic.go:267.2,267.41 1 1 -github.com/echovault/echovault/pkg/echovault/api_generic.go:282.94,285.9 2 1 -github.com/echovault/echovault/pkg/echovault/api_generic.go:286.18,287.26 1 1 -github.com/echovault/echovault/pkg/echovault/api_generic.go:288.18,289.26 1 1 -github.com/echovault/echovault/pkg/echovault/api_generic.go:290.18,291.26 1 1 -github.com/echovault/echovault/pkg/echovault/api_generic.go:292.18,293.26 1 1 -github.com/echovault/echovault/pkg/echovault/api_generic.go:296.2,297.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_generic.go:297.16,299.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_generic.go:301.2,301.41 1 1 -github.com/echovault/echovault/pkg/echovault/api_generic.go:316.101,319.9 2 1 -github.com/echovault/echovault/pkg/echovault/api_generic.go:320.18,321.26 1 0 -github.com/echovault/echovault/pkg/echovault/api_generic.go:322.18,323.26 1 0 -github.com/echovault/echovault/pkg/echovault/api_generic.go:324.18,325.26 1 0 -github.com/echovault/echovault/pkg/echovault/api_generic.go:326.18,327.26 1 0 -github.com/echovault/echovault/pkg/echovault/api_generic.go:330.2,331.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_generic.go:331.16,333.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_generic.go:335.2,335.41 1 1 -github.com/echovault/echovault/pkg/echovault/api_generic.go:350.102,353.9 2 1 -github.com/echovault/echovault/pkg/echovault/api_generic.go:354.18,355.26 1 1 -github.com/echovault/echovault/pkg/echovault/api_generic.go:356.18,357.26 1 1 -github.com/echovault/echovault/pkg/echovault/api_generic.go:358.18,359.26 1 1 -github.com/echovault/echovault/pkg/echovault/api_generic.go:360.18,361.26 1 1 -github.com/echovault/echovault/pkg/echovault/api_generic.go:364.2,365.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_generic.go:365.16,367.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_generic.go:369.2,369.41 1 1 -github.com/echovault/echovault/pkg/echovault/api_generic.go:384.109,387.9 2 1 -github.com/echovault/echovault/pkg/echovault/api_generic.go:388.18,389.26 1 0 -github.com/echovault/echovault/pkg/echovault/api_generic.go:390.18,391.26 1 0 -github.com/echovault/echovault/pkg/echovault/api_generic.go:392.18,393.26 1 0 -github.com/echovault/echovault/pkg/echovault/api_generic.go:394.18,395.26 1 0 -github.com/echovault/echovault/pkg/echovault/api_generic.go:398.2,399.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_generic.go:399.16,401.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_generic.go:403.2,403.41 1 1 -github.com/echovault/echovault/pkg/echovault/api_hash.go:46.91,49.36 2 1 -github.com/echovault/echovault/pkg/echovault/api_hash.go:49.36,51.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_hash.go:53.2,54.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_hash.go:54.16,56.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_hash.go:58.2,58.41 1 1 -github.com/echovault/echovault/pkg/echovault/api_hash.go:76.93,79.36 2 1 -github.com/echovault/echovault/pkg/echovault/api_hash.go:79.36,81.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_hash.go:83.2,84.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_hash.go:84.16,86.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_hash.go:88.2,88.41 1 1 -github.com/echovault/echovault/pkg/echovault/api_hash.go:105.79,109.16 3 1 -github.com/echovault/echovault/pkg/echovault/api_hash.go:109.16,111.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_hash.go:113.2,113.46 1 1 -github.com/echovault/echovault/pkg/echovault/api_hash.go:127.62,129.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_hash.go:129.16,131.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_hash.go:132.2,132.45 1 1 -github.com/echovault/echovault/pkg/echovault/api_hash.go:148.94,151.24 2 1 -github.com/echovault/echovault/pkg/echovault/api_hash.go:151.24,153.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_hash.go:153.8,155.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_hash.go:157.2,157.24 1 1 -github.com/echovault/echovault/pkg/echovault/api_hash.go:157.24,159.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_hash.go:161.2,162.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_hash.go:162.16,164.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_hash.go:166.2,166.45 1 1 -github.com/echovault/echovault/pkg/echovault/api_hash.go:180.56,182.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_hash.go:182.16,184.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_hash.go:185.2,185.41 1 1 -github.com/echovault/echovault/pkg/echovault/api_hash.go:199.62,201.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_hash.go:201.16,203.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_hash.go:204.2,204.45 1 1 -github.com/echovault/echovault/pkg/echovault/api_hash.go:223.85,225.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_hash.go:225.16,227.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_hash.go:228.2,228.39 1 1 -github.com/echovault/echovault/pkg/echovault/api_hash.go:232.94,234.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_hash.go:234.16,236.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_hash.go:237.2,237.39 1 1 -github.com/echovault/echovault/pkg/echovault/api_hash.go:252.64,254.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_hash.go:254.16,256.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_hash.go:257.2,257.45 1 1 -github.com/echovault/echovault/pkg/echovault/api_hash.go:273.67,275.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_hash.go:275.16,277.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_hash.go:278.2,278.41 1 1 -github.com/echovault/echovault/pkg/echovault/api_hash.go:294.74,297.16 3 1 -github.com/echovault/echovault/pkg/echovault/api_hash.go:297.16,299.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_hash.go:300.2,300.41 1 1 -github.com/echovault/echovault/pkg/echovault/api_list.go:34.56,37.16 3 1 -github.com/echovault/echovault/pkg/echovault/api_list.go:37.16,39.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_list.go:40.2,40.41 1 1 -github.com/echovault/echovault/pkg/echovault/api_list.go:63.79,65.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_list.go:65.16,67.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_list.go:68.2,68.45 1 1 -github.com/echovault/echovault/pkg/echovault/api_list.go:86.73,88.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_list.go:88.16,90.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_list.go:91.2,91.40 1 1 -github.com/echovault/echovault/pkg/echovault/api_list.go:111.84,113.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_list.go:113.16,115.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_list.go:116.2,116.40 1 1 -github.com/echovault/echovault/pkg/echovault/api_list.go:121.80,123.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_list.go:123.16,125.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_list.go:126.2,126.40 1 1 -github.com/echovault/echovault/pkg/echovault/api_list.go:144.84,146.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_list.go:146.16,148.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_list.go:149.2,149.40 1 1 -github.com/echovault/echovault/pkg/echovault/api_list.go:173.96,175.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_list.go:175.16,177.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_list.go:178.2,178.40 1 1 -github.com/echovault/echovault/pkg/echovault/api_list.go:192.59,194.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_list.go:194.16,196.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_list.go:197.2,197.40 1 1 -github.com/echovault/echovault/pkg/echovault/api_list.go:211.59,213.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_list.go:213.16,215.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_list.go:216.2,216.40 1 1 -github.com/echovault/echovault/pkg/echovault/api_list.go:233.78,236.16 3 1 -github.com/echovault/echovault/pkg/echovault/api_list.go:236.16,238.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_list.go:239.2,239.40 1 1 -github.com/echovault/echovault/pkg/echovault/api_list.go:255.79,258.16 3 1 -github.com/echovault/echovault/pkg/echovault/api_list.go:258.16,260.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_list.go:261.2,261.40 1 1 -github.com/echovault/echovault/pkg/echovault/api_list.go:278.78,281.16 3 1 -github.com/echovault/echovault/pkg/echovault/api_list.go:281.16,283.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_list.go:284.2,284.40 1 1 -github.com/echovault/echovault/pkg/echovault/api_list.go:300.79,303.16 3 1 -github.com/echovault/echovault/pkg/echovault/api_list.go:303.16,305.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_list.go:306.2,306.40 1 0 -github.com/echovault/echovault/pkg/echovault/api_pubsub.go:49.86,51.24 1 0 -github.com/echovault/echovault/pkg/echovault/api_pubsub.go:51.24,53.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_pubsub.go:56.2,58.36 3 0 -github.com/echovault/echovault/pkg/echovault/api_pubsub.go:58.36,64.3 2 0 -github.com/echovault/echovault/pkg/echovault/api_pubsub.go:67.2,68.12 2 0 -github.com/echovault/echovault/pkg/echovault/api_pubsub.go:68.12,70.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_pubsub.go:72.2,72.25 1 0 -github.com/echovault/echovault/pkg/echovault/api_pubsub.go:72.25,77.33 4 0 -github.com/echovault/echovault/pkg/echovault/api_pubsub.go:77.33,79.4 1 0 -github.com/echovault/echovault/pkg/echovault/api_pubsub.go:81.3,81.13 1 0 -github.com/echovault/echovault/pkg/echovault/api_pubsub.go:92.70,93.24 1 0 -github.com/echovault/echovault/pkg/echovault/api_pubsub.go:93.24,95.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_pubsub.go:97.2,97.36 1 0 -github.com/echovault/echovault/pkg/echovault/api_pubsub.go:97.36,99.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_pubsub.go:101.2,102.115 2 0 -github.com/echovault/echovault/pkg/echovault/api_pubsub.go:115.87,117.24 1 0 -github.com/echovault/echovault/pkg/echovault/api_pubsub.go:117.24,119.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_pubsub.go:122.2,124.36 3 0 -github.com/echovault/echovault/pkg/echovault/api_pubsub.go:124.36,130.3 2 0 -github.com/echovault/echovault/pkg/echovault/api_pubsub.go:133.2,134.12 2 0 -github.com/echovault/echovault/pkg/echovault/api_pubsub.go:134.12,136.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_pubsub.go:138.2,138.25 1 0 -github.com/echovault/echovault/pkg/echovault/api_pubsub.go:138.25,143.33 4 0 -github.com/echovault/echovault/pkg/echovault/api_pubsub.go:143.33,145.4 1 0 -github.com/echovault/echovault/pkg/echovault/api_pubsub.go:147.3,147.13 1 0 -github.com/echovault/echovault/pkg/echovault/api_pubsub.go:158.71,159.24 1 0 -github.com/echovault/echovault/pkg/echovault/api_pubsub.go:159.24,161.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_pubsub.go:163.2,163.36 1 0 -github.com/echovault/echovault/pkg/echovault/api_pubsub.go:163.36,165.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_pubsub.go:167.2,168.115 2 0 -github.com/echovault/echovault/pkg/echovault/api_pubsub.go:181.75,183.16 2 0 -github.com/echovault/echovault/pkg/echovault/api_pubsub.go:183.16,185.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_pubsub.go:186.2,186.40 1 0 -github.com/echovault/echovault/pkg/echovault/api_pubsub.go:196.76,198.19 2 0 -github.com/echovault/echovault/pkg/echovault/api_pubsub.go:198.19,200.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_pubsub.go:201.2,202.16 2 0 -github.com/echovault/echovault/pkg/echovault/api_pubsub.go:202.16,204.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_pubsub.go:205.2,205.45 1 0 -github.com/echovault/echovault/pkg/echovault/api_pubsub.go:211.55,213.16 2 0 -github.com/echovault/echovault/pkg/echovault/api_pubsub.go:213.16,215.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_pubsub.go:216.2,216.41 1 0 -github.com/echovault/echovault/pkg/echovault/api_pubsub.go:226.84,230.16 3 0 -github.com/echovault/echovault/pkg/echovault/api_pubsub.go:230.16,232.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_pubsub.go:234.2,236.16 3 0 -github.com/echovault/echovault/pkg/echovault/api_pubsub.go:236.16,238.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_pubsub.go:240.2,243.28 3 0 -github.com/echovault/echovault/pkg/echovault/api_pubsub.go:243.28,246.3 2 0 -github.com/echovault/echovault/pkg/echovault/api_pubsub.go:248.2,248.20 1 0 -github.com/echovault/echovault/pkg/echovault/api_set.go:36.75,39.16 3 1 -github.com/echovault/echovault/pkg/echovault/api_set.go:39.16,41.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_set.go:42.2,42.41 1 1 -github.com/echovault/echovault/pkg/echovault/api_set.go:56.57,58.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_set.go:58.16,60.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_set.go:61.2,61.41 1 1 -github.com/echovault/echovault/pkg/echovault/api_set.go:78.66,81.16 3 1 -github.com/echovault/echovault/pkg/echovault/api_set.go:81.16,83.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_set.go:84.2,84.45 1 1 -github.com/echovault/echovault/pkg/echovault/api_set.go:89.86,92.16 3 1 -github.com/echovault/echovault/pkg/echovault/api_set.go:92.16,94.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_set.go:95.2,95.41 1 1 -github.com/echovault/echovault/pkg/echovault/api_set.go:112.67,115.16 3 1 -github.com/echovault/echovault/pkg/echovault/api_set.go:115.16,117.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_set.go:118.2,118.45 1 1 -github.com/echovault/echovault/pkg/echovault/api_set.go:136.77,138.15 2 1 -github.com/echovault/echovault/pkg/echovault/api_set.go:138.15,140.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_set.go:141.2,142.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_set.go:142.16,144.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_set.go:145.2,145.41 1 1 -github.com/echovault/echovault/pkg/echovault/api_set.go:150.87,153.16 3 1 -github.com/echovault/echovault/pkg/echovault/api_set.go:153.16,155.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_set.go:156.2,156.41 1 1 -github.com/echovault/echovault/pkg/echovault/api_set.go:172.70,174.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_set.go:174.16,176.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_set.go:177.2,177.41 1 1 -github.com/echovault/echovault/pkg/echovault/api_set.go:191.65,193.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_set.go:193.16,195.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_set.go:196.2,196.45 1 1 -github.com/echovault/echovault/pkg/echovault/api_set.go:213.84,216.16 3 1 -github.com/echovault/echovault/pkg/echovault/api_set.go:216.16,218.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_set.go:219.2,219.46 1 1 -github.com/echovault/echovault/pkg/echovault/api_set.go:241.82,243.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_set.go:243.16,245.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_set.go:246.2,246.41 1 1 -github.com/echovault/echovault/pkg/echovault/api_set.go:262.73,264.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_set.go:264.16,266.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_set.go:267.2,267.45 1 1 -github.com/echovault/echovault/pkg/echovault/api_set.go:284.79,286.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_set.go:286.16,288.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_set.go:289.2,289.45 1 1 -github.com/echovault/echovault/pkg/echovault/api_set.go:305.75,308.16 3 1 -github.com/echovault/echovault/pkg/echovault/api_set.go:308.16,310.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_set.go:311.2,311.41 1 1 -github.com/echovault/echovault/pkg/echovault/api_set.go:326.67,329.16 3 1 -github.com/echovault/echovault/pkg/echovault/api_set.go:329.16,331.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_set.go:332.2,332.45 1 1 -github.com/echovault/echovault/pkg/echovault/api_set.go:337.87,340.16 3 1 -github.com/echovault/echovault/pkg/echovault/api_set.go:340.16,342.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_set.go:343.2,343.41 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:102.87,104.28 2 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:104.28,105.17 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:105.17,107.18 2 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:107.18,109.5 1 0 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:110.4,111.12 2 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:113.3,113.23 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:115.2,115.20 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:118.85,120.28 2 0 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:120.28,122.17 2 0 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:122.17,124.4 1 0 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:125.3,126.17 2 0 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:126.17,128.18 2 0 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:128.18,130.5 1 0 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:131.4,131.24 1 0 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:134.2,134.20 1 0 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:158.105,161.9 2 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:162.18,163.26 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:164.18,165.26 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:168.2,168.9 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:169.18,170.26 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:171.18,172.26 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:175.2,175.16 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:175.16,177.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:179.2,179.18 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:179.18,181.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:183.2,183.37 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:183.37,185.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:187.2,188.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:188.16,190.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:192.2,192.41 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:206.57,208.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:208.16,210.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:211.2,211.41 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:229.76,237.16 3 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:237.16,239.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:240.2,240.41 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:258.93,260.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:260.16,262.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:263.2,264.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:264.16,266.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:268.2,269.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:269.16,271.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:273.2,273.45 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:290.86,293.16 3 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:293.16,295.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:296.2,296.41 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:313.99,316.30 2 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:316.30,318.45 2 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:318.45,320.4 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:323.2,323.29 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:323.29,325.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:327.2,327.24 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:327.24,329.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:331.2,332.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:332.16,334.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:336.2,337.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:337.16,339.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:341.2,341.53 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:360.114,363.30 2 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:363.30,365.42 2 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:365.42,367.4 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:370.2,370.29 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:370.29,372.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:374.2,374.24 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:374.24,376.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:378.2,379.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:379.16,381.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:383.2,383.41 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:400.99,403.30 2 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:403.30,405.42 2 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:405.42,407.4 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:410.2,410.29 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:410.29,412.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:414.2,414.24 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:414.24,416.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:418.2,419.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:419.16,421.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:423.2,424.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:424.16,426.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:428.2,428.53 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:447.114,450.30 2 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:450.30,452.42 2 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:452.42,454.4 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:457.2,457.29 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:457.29,459.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:461.2,461.24 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:461.24,463.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:465.2,466.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:466.16,468.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:470.2,470.41 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:489.97,492.16 3 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:492.16,494.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:495.2,496.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:496.16,498.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:499.2,499.15 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:516.89,519.9 2 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:520.19,521.27 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:522.19,523.27 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:524.10,525.27 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:528.2,528.9 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:529.26,530.76 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:531.10,532.59 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:535.2,536.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:536.16,538.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:540.2,540.51 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:558.88,560.33 2 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:560.33,562.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:564.2,565.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:565.16,567.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:569.2,570.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:570.16,572.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:574.2,575.24 2 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:575.24,576.14 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:576.14,578.12 2 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:580.3,581.17 2 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:581.17,583.4 1 0 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:584.3,584.20 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:587.2,587.20 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:607.71,610.16 3 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:610.16,612.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:613.2,613.41 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:632.78,634.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:634.16,636.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:637.2,637.51 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:656.78,658.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:658.16,660.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:661.2,661.51 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:685.98,687.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:687.16,689.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:690.2,690.16 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:690.16,692.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:694.2,695.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:695.16,697.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:699.2,699.51 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:720.101,722.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:722.16,724.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:726.2,727.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:727.16,729.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:731.2,733.19 2 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:733.19,735.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:737.2,738.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:738.16,740.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:742.2,744.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:744.16,746.17 2 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:746.17,748.4 1 0 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:749.3,749.13 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:752.2,752.17 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:757.104,759.16 2 0 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:759.16,761.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:763.2,764.16 2 0 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:764.16,766.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:768.2,770.46 2 0 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:787.81,790.16 3 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:790.16,792.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:794.2,795.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:795.16,797.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:799.2,799.11 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:799.11,801.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:803.2,804.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:804.16,806.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:808.2,808.19 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:824.75,826.33 2 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:826.33,828.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:829.2,830.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:830.16,832.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:833.2,833.41 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:851.94,860.16 3 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:860.16,862.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:864.2,864.41 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:884.109,887.9 2 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:888.23,889.31 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:890.21,891.29 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:892.10,893.31 1 0 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:896.2,896.24 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:896.24,898.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:900.2,900.47 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:900.47,902.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:904.2,905.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:905.16,907.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:909.2,910.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:910.16,912.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:914.2,914.53 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:936.120,939.9 2 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:940.23,941.31 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:942.21,943.29 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:944.10,945.31 1 0 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:948.2,948.47 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:948.47,950.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:952.2,953.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:953.16,955.3 1 1 -github.com/echovault/echovault/pkg/echovault/api_sorted_set.go:957.2,957.41 1 1 -github.com/echovault/echovault/pkg/echovault/api_string.go:30.84,32.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_string.go:32.16,34.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_string.go:35.2,35.41 1 1 -github.com/echovault/echovault/pkg/echovault/api_string.go:45.58,47.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_string.go:47.16,49.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_string.go:50.2,50.41 1 1 -github.com/echovault/echovault/pkg/echovault/api_string.go:63.77,65.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_string.go:65.16,67.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_string.go:68.2,68.40 1 1 -github.com/echovault/echovault/pkg/echovault/api_string.go:72.79,74.16 2 1 -github.com/echovault/echovault/pkg/echovault/api_string.go:74.16,76.3 1 0 -github.com/echovault/echovault/pkg/echovault/api_string.go:77.2,77.40 1 1 -github.com/echovault/echovault/pkg/echovault/cluster.go:25.45,27.2 1 1 -github.com/echovault/echovault/pkg/echovault/cluster.go:29.84,40.16 4 0 -github.com/echovault/echovault/pkg/echovault/cluster.go:40.16,42.3 1 0 -github.com/echovault/echovault/pkg/echovault/cluster.go:44.2,46.43 2 0 -github.com/echovault/echovault/pkg/echovault/cluster.go:46.43,48.3 1 0 -github.com/echovault/echovault/pkg/echovault/cluster.go:50.2,52.9 2 0 -github.com/echovault/echovault/pkg/echovault/cluster.go:52.9,54.3 1 0 -github.com/echovault/echovault/pkg/echovault/cluster.go:56.2,56.20 1 0 -github.com/echovault/echovault/pkg/echovault/cluster.go:56.20,58.3 1 0 -github.com/echovault/echovault/pkg/echovault/cluster.go:60.2,60.12 1 0 -github.com/echovault/echovault/pkg/echovault/cluster.go:63.94,75.16 5 0 -github.com/echovault/echovault/pkg/echovault/cluster.go:75.16,77.3 1 0 -github.com/echovault/echovault/pkg/echovault/cluster.go:79.2,81.43 2 0 -github.com/echovault/echovault/pkg/echovault/cluster.go:81.43,83.3 1 0 -github.com/echovault/echovault/pkg/echovault/cluster.go:85.2,87.9 2 0 -github.com/echovault/echovault/pkg/echovault/cluster.go:87.9,89.3 1 0 -github.com/echovault/echovault/pkg/echovault/cluster.go:91.2,91.20 1 0 -github.com/echovault/echovault/pkg/echovault/cluster.go:91.20,93.3 1 0 -github.com/echovault/echovault/pkg/echovault/cluster.go:95.2,95.24 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:99.66,100.36 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:100.36,102.3 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:108.66,109.36 1 1 -github.com/echovault/echovault/pkg/echovault/echovault.go:109.36,111.3 1 1 -github.com/echovault/echovault/pkg/echovault/echovault.go:117.72,118.36 1 1 -github.com/echovault/echovault/pkg/echovault/echovault.go:118.36,120.3 1 1 -github.com/echovault/echovault/pkg/echovault/echovault.go:125.78,136.33 2 1 -github.com/echovault/echovault/pkg/echovault/echovault.go:136.33,138.3 1 1 -github.com/echovault/echovault/pkg/echovault/echovault.go:140.2,151.29 4 1 -github.com/echovault/echovault/pkg/echovault/echovault.go:151.29,160.49 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:160.49,162.44 2 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:162.44,163.46 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:163.46,165.7 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:167.5,167.17 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:170.3,178.5 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:179.8,190.65 1 1 -github.com/echovault/echovault/pkg/echovault/echovault.go:190.65,192.44 2 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:192.44,193.46 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:193.46,195.7 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:197.5,197.17 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:199.72,201.67 2 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:201.67,203.6 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:204.5,204.68 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:204.68,206.6 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:207.5,208.34 2 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:212.3,218.60 1 1 -github.com/echovault/echovault/pkg/echovault/echovault.go:218.60,220.44 2 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:220.44,221.46 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:221.46,223.7 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:225.5,225.17 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:227.68,229.67 2 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:229.67,231.6 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:232.5,232.69 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:232.69,234.6 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:235.5,236.34 2 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:238.51,240.19 2 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:240.19,242.6 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:248.2,248.61 1 1 -github.com/echovault/echovault/pkg/echovault/echovault.go:248.61,249.13 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:249.13,250.8 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:250.8,252.83 2 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:252.83,254.6 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:259.2,259.69 1 1 -github.com/echovault/echovault/pkg/echovault/echovault.go:259.69,261.3 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:263.2,263.29 1 1 -github.com/echovault/echovault/pkg/echovault/echovault.go:263.29,267.36 3 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:267.36,269.4 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:272.2,272.30 1 1 -github.com/echovault/echovault/pkg/echovault/echovault.go:272.30,275.34 2 1 -github.com/echovault/echovault/pkg/echovault/echovault.go:275.34,277.18 2 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:277.18,279.5 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:283.3,283.71 1 1 -github.com/echovault/echovault/pkg/echovault/echovault.go:283.71,285.18 2 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:285.18,287.5 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:291.2,291.23 1 1 -github.com/echovault/echovault/pkg/echovault/echovault.go:294.37,303.16 4 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:303.16,305.3 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:307.2,307.15 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:307.15,310.3 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:312.2,312.27 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:312.27,314.15 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:314.15,316.4 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:316.9,318.4 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:320.3,321.49 2 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:321.49,323.18 2 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:323.18,325.5 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:326.4,326.42 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:329.3,332.16 3 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:332.16,334.37 2 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:334.37,336.19 2 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:336.19,338.6 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:339.5,340.19 2 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:340.19,342.6 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:343.5,343.61 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:343.61,345.6 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:349.3,353.5 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:357.2,357.6 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:357.6,359.17 2 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:359.17,361.12 2 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:364.3,364.35 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:368.58,370.23 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:370.23,372.3 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:374.2,380.6 4 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:380.6,383.43 2 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:383.43,386.9 2 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:389.3,389.17 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:389.17,391.9 2 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:394.3,396.43 2 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:396.43,397.9 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:400.3,400.17 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:400.17,401.87 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:401.87,403.5 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:404.4,404.12 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:407.3,410.20 2 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:410.20,411.12 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:414.3,414.28 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:414.28,416.12 2 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:420.3,421.7 2 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:421.7,423.41 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:423.41,425.19 2 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:425.19,427.6 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:428.5,428.10 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:430.4,431.21 2 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:431.21,432.10 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:434.4,434.27 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:438.2,438.37 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:438.37,440.3 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:448.34,450.2 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:453.47,454.38 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:454.38,456.3 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:458.2,458.12 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:458.12,459.27 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:459.27,461.53 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:461.53,463.5 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:464.4,464.10 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:467.3,467.62 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:467.62,469.4 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:472.2,472.12 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:476.49,478.2 1 1 -github.com/echovault/echovault/pkg/echovault/echovault.go:480.42,482.2 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:484.43,486.2 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:488.56,490.2 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:493.56,495.2 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:497.44,499.2 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:501.45,503.2 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:506.45,507.40 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:507.40,509.3 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:510.2,510.12 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:510.12,511.55 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:511.55,513.4 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:515.2,515.12 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:520.37,521.26 1 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:521.26,524.3 2 0 -github.com/echovault/echovault/pkg/echovault/echovault.go:527.45,544.2 2 1 -github.com/echovault/echovault/pkg/echovault/keyspace.go:37.81,40.34 2 1 -github.com/echovault/echovault/pkg/echovault/keyspace.go:40.34,43.3 2 1 -github.com/echovault/echovault/pkg/echovault/keyspace.go:45.2,45.6 1 1 -github.com/echovault/echovault/pkg/echovault/keyspace.go:45.6,46.10 1 1 -github.com/echovault/echovault/pkg/echovault/keyspace.go:47.11,48.35 1 1 -github.com/echovault/echovault/pkg/echovault/keyspace.go:48.35,50.5 1 1 -github.com/echovault/echovault/pkg/echovault/keyspace.go:51.4,52.10 2 1 -github.com/echovault/echovault/pkg/echovault/keyspace.go:52.10,54.5 1 1 -github.com/echovault/echovault/pkg/echovault/keyspace.go:55.21,56.36 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:65.67,66.39 1 1 -github.com/echovault/echovault/pkg/echovault/keyspace.go:66.39,68.3 1 1 -github.com/echovault/echovault/pkg/echovault/keyspace.go:76.82,79.34 2 1 -github.com/echovault/echovault/pkg/echovault/keyspace.go:79.34,82.3 2 1 -github.com/echovault/echovault/pkg/echovault/keyspace.go:84.2,84.6 1 1 -github.com/echovault/echovault/pkg/echovault/keyspace.go:84.6,85.10 1 1 -github.com/echovault/echovault/pkg/echovault/keyspace.go:86.11,87.35 1 1 -github.com/echovault/echovault/pkg/echovault/keyspace.go:87.35,89.5 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:90.4,91.10 2 1 -github.com/echovault/echovault/pkg/echovault/keyspace.go:91.10,93.5 1 1 -github.com/echovault/echovault/pkg/echovault/keyspace.go:94.21,95.36 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:104.68,105.39 1 1 -github.com/echovault/echovault/pkg/echovault/keyspace.go:105.39,107.3 1 1 -github.com/echovault/echovault/pkg/echovault/keyspace.go:115.74,117.9 2 1 -github.com/echovault/echovault/pkg/echovault/keyspace.go:117.9,119.3 1 1 -github.com/echovault/echovault/pkg/echovault/keyspace.go:121.2,121.82 1 1 -github.com/echovault/echovault/pkg/echovault/keyspace.go:121.82,122.28 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:122.28,125.18 2 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:125.18,127.5 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:128.9,128.64 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:128.64,131.18 2 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:131.18,133.5 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:134.9,134.65 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:134.65,139.4 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:141.3,141.15 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:144.2,144.13 1 1 -github.com/echovault/echovault/pkg/echovault/keyspace.go:152.90,153.115 1 1 -github.com/echovault/echovault/pkg/echovault/keyspace.go:153.115,155.3 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:157.2,160.33 3 1 -github.com/echovault/echovault/pkg/echovault/keyspace.go:160.33,171.3 5 1 -github.com/echovault/echovault/pkg/echovault/keyspace.go:173.2,173.33 1 1 -github.com/echovault/echovault/pkg/echovault/keyspace.go:178.80,179.58 1 1 -github.com/echovault/echovault/pkg/echovault/keyspace.go:179.58,181.3 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:182.2,182.32 1 1 -github.com/echovault/echovault/pkg/echovault/keyspace.go:189.93,190.115 1 1 -github.com/echovault/echovault/pkg/echovault/keyspace.go:190.115,192.3 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:194.2,200.16 3 1 -github.com/echovault/echovault/pkg/echovault/keyspace.go:200.16,202.3 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:204.2,204.27 1 1 -github.com/echovault/echovault/pkg/echovault/keyspace.go:204.27,206.3 1 1 -github.com/echovault/echovault/pkg/echovault/keyspace.go:208.2,208.12 1 1 -github.com/echovault/echovault/pkg/echovault/keyspace.go:213.79,214.58 1 1 -github.com/echovault/echovault/pkg/echovault/keyspace.go:214.58,216.3 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:217.2,217.35 1 1 -github.com/echovault/echovault/pkg/echovault/keyspace.go:226.101,234.55 3 1 -github.com/echovault/echovault/pkg/echovault/keyspace.go:234.55,236.3 1 1 -github.com/echovault/echovault/pkg/echovault/keyspace.go:237.2,240.11 2 1 -github.com/echovault/echovault/pkg/echovault/keyspace.go:240.11,242.17 2 1 -github.com/echovault/echovault/pkg/echovault/keyspace.go:242.17,244.4 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:250.51,259.97 4 1 -github.com/echovault/echovault/pkg/echovault/keyspace.go:259.97,261.3 1 1 -github.com/echovault/echovault/pkg/echovault/keyspace.go:269.60,271.6 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:271.6,272.83 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:272.83,274.9 2 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:277.2,278.33 2 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:278.33,280.3 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:281.2,282.13 2 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:289.75,290.52 1 1 -github.com/echovault/echovault/pkg/echovault/keyspace.go:290.52,292.3 1 1 -github.com/echovault/echovault/pkg/echovault/keyspace.go:295.2,302.9 4 1 -github.com/echovault/echovault/pkg/echovault/keyspace.go:303.108,304.36 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:305.108,306.36 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:309.2,311.12 2 1 -github.com/echovault/echovault/pkg/echovault/keyspace.go:316.82,318.83 1 1 -github.com/echovault/echovault/pkg/echovault/keyspace.go:318.83,320.3 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:322.2,322.34 1 1 -github.com/echovault/echovault/pkg/echovault/keyspace.go:322.34,324.3 1 1 -github.com/echovault/echovault/pkg/echovault/keyspace.go:325.2,325.55 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:326.28,329.36 3 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:330.28,333.36 3 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:334.29,337.50 3 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:337.50,339.4 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:340.29,343.50 3 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:343.50,345.4 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:347.2,347.54 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:347.54,349.3 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:350.2,350.12 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:354.71,356.34 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:356.34,358.3 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:361.2,364.50 3 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:364.50,366.3 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:368.2,370.50 3 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:370.50,372.3 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:376.2,376.9 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:377.125,382.7 3 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:382.7,384.40 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:384.40,386.5 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:388.4,389.29 2 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:389.29,391.54 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:391.54,393.6 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:394.10,394.65 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:394.65,396.63 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:396.63,398.6 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:402.4,405.52 3 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:405.52,407.5 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:409.125,414.7 3 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:414.7,416.40 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:416.40,418.5 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:420.4,421.29 2 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:421.29,423.54 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:423.54,425.6 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:426.10,426.65 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:426.65,429.63 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:429.63,431.6 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:435.4,438.52 3 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:438.52,440.5 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:442.105,445.7 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:445.7,447.33 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:447.33,450.5 2 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:452.4,453.40 2 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:453.40,454.17 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:454.17,455.31 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:455.31,457.56 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:457.56,459.8 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:460.12,460.67 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:460.67,461.65 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:461.65,463.8 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:466.6,469.54 3 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:469.54,471.7 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:473.5,473.10 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:476.106,479.7 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:479.7,486.29 5 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:486.29,488.54 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:488.54,490.6 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:491.10,491.65 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:491.65,492.63 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:492.63,494.6 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:498.4,501.52 3 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:501.52,503.5 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:505.10,506.13 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:515.77,517.57 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:517.57,519.3 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:521.2,526.50 3 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:526.50,528.3 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:529.2,536.33 6 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:536.33,537.7 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:537.7,541.35 3 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:541.35,543.10 2 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:547.2,550.25 2 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:550.25,551.52 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:551.52,552.12 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:556.3,556.57 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:556.57,558.12 2 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:562.3,564.28 3 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:564.28,565.51 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:565.51,567.5 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:568.9,568.64 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:568.64,569.60 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:569.60,571.5 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:576.2,576.21 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:576.21,579.3 2 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:581.2,584.58 2 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:584.58,588.3 2 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:590.2,590.12 1 0 -github.com/echovault/echovault/pkg/echovault/keyspace.go:593.68,597.2 3 1 -github.com/echovault/echovault/pkg/echovault/keyspace.go:599.74,604.2 4 1 -github.com/echovault/echovault/pkg/echovault/modules.go:28.59,30.2 1 0 -github.com/echovault/echovault/pkg/echovault/modules.go:32.47,34.2 1 0 -github.com/echovault/echovault/pkg/echovault/modules.go:36.50,38.2 1 0 -github.com/echovault/echovault/pkg/echovault/modules.go:40.72,41.42 1 1 -github.com/echovault/echovault/pkg/echovault/modules.go:41.42,42.46 1 1 -github.com/echovault/echovault/pkg/echovault/modules.go:42.46,44.4 1 1 -github.com/echovault/echovault/pkg/echovault/modules.go:46.2,46.69 1 0 -github.com/echovault/echovault/pkg/echovault/modules.go:49.137,51.16 2 1 -github.com/echovault/echovault/pkg/echovault/modules.go:51.16,53.3 1 0 -github.com/echovault/echovault/pkg/echovault/modules.go:55.2,56.16 2 1 -github.com/echovault/echovault/pkg/echovault/modules.go:56.16,58.3 1 0 -github.com/echovault/echovault/pkg/echovault/modules.go:60.2,64.8 4 1 -github.com/echovault/echovault/pkg/echovault/modules.go:64.8,67.3 2 0 -github.com/echovault/echovault/pkg/echovault/modules.go:69.2,69.51 1 1 -github.com/echovault/echovault/pkg/echovault/modules.go:69.51,72.87 1 0 -github.com/echovault/echovault/pkg/echovault/modules.go:72.87,74.4 1 0 -github.com/echovault/echovault/pkg/echovault/modules.go:78.2,78.50 1 1 -github.com/echovault/echovault/pkg/echovault/modules.go:78.50,79.7 1 1 -github.com/echovault/echovault/pkg/echovault/modules.go:79.7,80.42 1 1 -github.com/echovault/echovault/pkg/echovault/modules.go:80.42,82.10 2 1 -github.com/echovault/echovault/pkg/echovault/modules.go:87.2,87.43 1 1 -github.com/echovault/echovault/pkg/echovault/modules.go:87.43,89.17 2 1 -github.com/echovault/echovault/pkg/echovault/modules.go:89.17,91.4 1 1 -github.com/echovault/echovault/pkg/echovault/modules.go:93.3,93.62 1 1 -github.com/echovault/echovault/pkg/echovault/modules.go:93.62,95.4 1 1 -github.com/echovault/echovault/pkg/echovault/modules.go:97.3,99.18 2 1 -github.com/echovault/echovault/pkg/echovault/modules.go:103.2,103.32 1 0 -github.com/echovault/echovault/pkg/echovault/modules.go:103.32,106.17 3 0 -github.com/echovault/echovault/pkg/echovault/modules.go:106.17,108.4 1 0 -github.com/echovault/echovault/pkg/echovault/modules.go:109.3,109.18 1 0 -github.com/echovault/echovault/pkg/echovault/modules.go:113.2,113.34 1 0 -github.com/echovault/echovault/pkg/echovault/modules.go:113.34,116.3 2 0 -github.com/echovault/echovault/pkg/echovault/modules.go:118.2,118.72 1 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:29.111,35.29 4 1 -github.com/echovault/echovault/pkg/modules/admin/commands.go:35.29,36.54 1 1 -github.com/echovault/echovault/pkg/modules/admin/commands.go:36.54,42.42 4 1 -github.com/echovault/echovault/pkg/modules/admin/commands.go:42.42,44.5 1 1 -github.com/echovault/echovault/pkg/modules/admin/commands.go:46.4,49.12 3 1 -github.com/echovault/echovault/pkg/modules/admin/commands.go:52.3,52.36 1 1 -github.com/echovault/echovault/pkg/modules/admin/commands.go:52.36,59.43 5 1 -github.com/echovault/echovault/pkg/modules/admin/commands.go:59.43,61.5 1 1 -github.com/echovault/echovault/pkg/modules/admin/commands.go:63.4,65.21 2 1 -github.com/echovault/echovault/pkg/modules/admin/commands.go:69.2,71.25 2 1 -github.com/echovault/echovault/pkg/modules/admin/commands.go:74.109,78.35 3 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:78.35,79.65 1 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:79.65,80.41 1 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:80.41,82.5 1 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:83.4,83.12 1 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:85.3,85.13 1 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:88.2,88.51 1 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:91.110,92.18 1 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:93.9,98.36 4 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:98.36,99.66 1 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:99.66,100.52 1 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:100.52,104.6 3 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:105.5,105.13 1 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:107.4,108.14 2 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:110.3,111.26 2 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:113.9,117.45 3 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:117.45,119.4 1 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:120.3,120.42 1 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:120.42,124.37 3 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:124.37,125.67 1 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:125.67,126.53 1 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:126.53,127.59 1 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:127.59,131.8 3 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:133.6,133.14 1 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:135.5,135.54 1 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:135.54,138.6 2 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:140.9,140.50 1 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:140.50,144.37 3 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:144.37,145.67 1 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:145.67,146.53 1 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:146.53,148.24 2 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:148.24,151.8 2 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:153.6,153.14 1 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:155.5,155.33 1 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:155.33,158.6 2 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:160.9,160.49 1 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:160.49,164.37 3 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:164.37,165.67 1 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:165.67,166.53 1 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:166.53,167.55 1 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:167.55,171.8 3 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:173.6,173.14 1 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:175.5,175.50 1 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:175.50,178.6 2 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:180.9,182.4 1 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:183.3,184.26 2 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:185.10,186.54 1 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:190.103,192.2 1 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:194.33,202.68 1 1 -github.com/echovault/echovault/pkg/modules/admin/commands.go:202.68,208.5 1 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:217.68,223.5 1 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:231.70,237.7 1 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:246.70,252.7 1 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:262.70,268.7 1 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:279.68,285.5 1 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:286.113,287.49 1 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:287.49,289.6 1 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:290.5,290.45 1 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:299.68,305.5 1 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:306.113,308.18 2 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:308.18,310.6 1 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:311.5,311.53 1 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:320.68,326.5 1 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:327.113,328.47 1 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:328.47,330.6 1 0 -github.com/echovault/echovault/pkg/modules/admin/commands.go:331.5,331.45 1 0 -github.com/echovault/echovault/pkg/modules/pubsub/commands.go:28.113,30.9 2 1 -github.com/echovault/echovault/pkg/modules/pubsub/commands.go:30.9,32.3 1 0 -github.com/echovault/echovault/pkg/modules/pubsub/commands.go:34.2,36.24 2 1 -github.com/echovault/echovault/pkg/modules/pubsub/commands.go:36.24,38.3 1 0 -github.com/echovault/echovault/pkg/modules/pubsub/commands.go:40.2,43.17 3 1 -github.com/echovault/echovault/pkg/modules/pubsub/commands.go:46.115,48.9 2 1 -github.com/echovault/echovault/pkg/modules/pubsub/commands.go:48.9,50.3 1 0 -github.com/echovault/echovault/pkg/modules/pubsub/commands.go:52.2,56.66 3 1 -github.com/echovault/echovault/pkg/modules/pubsub/commands.go:59.108,61.9 2 1 -github.com/echovault/echovault/pkg/modules/pubsub/commands.go:61.9,63.3 1 0 -github.com/echovault/echovault/pkg/modules/pubsub/commands.go:64.2,64.19 1 1 -github.com/echovault/echovault/pkg/modules/pubsub/commands.go:64.19,66.3 1 0 -github.com/echovault/echovault/pkg/modules/pubsub/commands.go:67.2,68.42 2 1 -github.com/echovault/echovault/pkg/modules/pubsub/commands.go:71.113,72.18 1 1 -github.com/echovault/echovault/pkg/modules/pubsub/commands.go:72.18,74.3 1 0 -github.com/echovault/echovault/pkg/modules/pubsub/commands.go:76.2,77.9 2 1 -github.com/echovault/echovault/pkg/modules/pubsub/commands.go:77.9,79.3 1 0 -github.com/echovault/echovault/pkg/modules/pubsub/commands.go:81.2,82.19 2 1 -github.com/echovault/echovault/pkg/modules/pubsub/commands.go:82.19,84.3 1 1 -github.com/echovault/echovault/pkg/modules/pubsub/commands.go:86.2,86.38 1 1 -github.com/echovault/echovault/pkg/modules/pubsub/commands.go:89.109,91.9 2 1 -github.com/echovault/echovault/pkg/modules/pubsub/commands.go:91.9,93.3 1 0 -github.com/echovault/echovault/pkg/modules/pubsub/commands.go:94.2,95.49 2 1 -github.com/echovault/echovault/pkg/modules/pubsub/commands.go:98.112,100.9 2 1 -github.com/echovault/echovault/pkg/modules/pubsub/commands.go:100.9,102.3 1 0 -github.com/echovault/echovault/pkg/modules/pubsub/commands.go:103.2,103.36 1 1 -github.com/echovault/echovault/pkg/modules/pubsub/commands.go:106.33,114.68 1 1 -github.com/echovault/echovault/pkg/modules/pubsub/commands.go:114.68,116.21 1 1 -github.com/echovault/echovault/pkg/modules/pubsub/commands.go:116.21,118.6 1 0 -github.com/echovault/echovault/pkg/modules/pubsub/commands.go:119.5,123.11 1 1 -github.com/echovault/echovault/pkg/modules/pubsub/commands.go:133.68,135.21 1 1 -github.com/echovault/echovault/pkg/modules/pubsub/commands.go:135.21,137.6 1 0 -github.com/echovault/echovault/pkg/modules/pubsub/commands.go:138.5,142.11 1 1 -github.com/echovault/echovault/pkg/modules/pubsub/commands.go:152.68,154.22 1 1 -github.com/echovault/echovault/pkg/modules/pubsub/commands.go:154.22,156.6 1 0 -github.com/echovault/echovault/pkg/modules/pubsub/commands.go:157.5,161.11 1 1 -github.com/echovault/echovault/pkg/modules/pubsub/commands.go:173.68,180.5 1 0 -github.com/echovault/echovault/pkg/modules/pubsub/commands.go:191.68,197.5 1 0 -github.com/echovault/echovault/pkg/modules/pubsub/commands.go:206.68,212.5 1 0 -github.com/echovault/echovault/pkg/modules/pubsub/commands.go:213.101,215.5 1 0 -github.com/echovault/echovault/pkg/modules/pubsub/commands.go:225.70,231.7 1 0 -github.com/echovault/echovault/pkg/modules/pubsub/commands.go:240.70,246.7 1 0 -github.com/echovault/echovault/pkg/modules/pubsub/commands.go:256.70,262.7 1 0 -github.com/echovault/echovault/pkg/modules/set/commands.go:30.105,32.16 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:32.16,34.3 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:36.2,40.33 3 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:40.33,42.70 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:42.70,44.4 1 0 -github.com/echovault/echovault/pkg/modules/set/commands.go:45.3,45.55 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:45.55,47.4 1 0 -github.com/echovault/echovault/pkg/modules/set/commands.go:48.3,49.59 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:52.2,52.51 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:52.51,54.3 1 0 -github.com/echovault/echovault/pkg/modules/set/commands.go:55.2,58.9 3 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:58.9,60.3 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:62.2,64.51 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:67.106,69.16 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:69.16,71.3 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:73.2,75.33 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:75.33,77.3 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:79.2,79.52 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:79.52,81.3 1 0 -github.com/echovault/echovault/pkg/modules/set/commands.go:82.2,85.9 3 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:85.9,87.3 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:89.2,91.57 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:94.106,96.16 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:96.16,98.3 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:101.2,101.46 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:101.46,103.3 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:104.2,104.65 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:104.65,106.3 1 0 -github.com/echovault/echovault/pkg/modules/set/commands.go:107.2,109.9 3 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:109.9,111.3 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:113.2,114.15 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:114.15,115.34 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:115.34,116.14 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:116.14,118.5 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:122.2,122.40 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:122.40,123.34 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:123.34,124.12 1 0 -github.com/echovault/echovault/pkg/modules/set/commands.go:126.3,126.53 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:126.53,127.12 1 0 -github.com/echovault/echovault/pkg/modules/set/commands.go:129.3,129.20 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:132.2,133.30 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:133.30,135.10 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:135.10,136.12 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:138.3,138.27 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:141.2,145.26 4 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:145.26,147.24 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:147.24,149.4 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:152.2,152.25 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:155.111,157.16 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:157.16,159.3 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:161.2,164.46 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:164.46,166.3 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:167.2,167.66 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:167.66,169.3 1 0 -github.com/echovault/echovault/pkg/modules/set/commands.go:170.2,172.9 3 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:172.9,174.3 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:176.2,177.15 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:177.15,178.34 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:178.34,179.14 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:179.14,181.5 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:185.2,185.40 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:185.40,186.34 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:186.34,187.12 1 0 -github.com/echovault/echovault/pkg/modules/set/commands.go:189.3,189.53 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:189.53,190.12 1 0 -github.com/echovault/echovault/pkg/modules/set/commands.go:192.3,192.20 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:195.2,196.40 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:196.40,198.10 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:198.10,199.12 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:201.3,201.27 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:204.2,209.40 4 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:209.40,210.60 1 0 -github.com/echovault/echovault/pkg/modules/set/commands.go:210.60,212.4 1 0 -github.com/echovault/echovault/pkg/modules/set/commands.go:213.3,213.64 1 0 -github.com/echovault/echovault/pkg/modules/set/commands.go:213.64,215.4 1 0 -github.com/echovault/echovault/pkg/modules/set/commands.go:216.3,217.26 2 0 -github.com/echovault/echovault/pkg/modules/set/commands.go:220.2,220.68 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:220.68,222.3 1 0 -github.com/echovault/echovault/pkg/modules/set/commands.go:223.2,223.63 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:223.63,225.3 1 0 -github.com/echovault/echovault/pkg/modules/set/commands.go:226.2,228.25 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:231.107,233.16 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:233.16,235.3 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:237.2,238.15 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:238.15,239.34 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:239.34,240.14 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:240.14,242.5 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:246.2,246.36 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:246.36,247.34 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:247.34,250.4 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:251.3,251.53 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:251.53,253.4 1 0 -github.com/echovault/echovault/pkg/modules/set/commands.go:254.3,254.20 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:257.2,259.28 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:259.28,261.10 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:261.10,264.4 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:265.3,265.27 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:268.2,268.20 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:268.20,270.3 1 0 -github.com/echovault/echovault/pkg/modules/set/commands.go:272.2,276.26 4 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:276.26,278.24 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:278.24,280.4 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:283.2,283.25 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:286.111,288.16 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:288.16,290.3 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:293.2,294.56 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:294.56,296.3 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:297.2,297.35 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:297.35,299.3 1 0 -github.com/echovault/echovault/pkg/modules/set/commands.go:300.2,300.20 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:300.20,302.27 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:302.27,304.4 1 0 -github.com/echovault/echovault/pkg/modules/set/commands.go:306.3,306.60 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:306.60,308.4 1 0 -github.com/echovault/echovault/pkg/modules/set/commands.go:308.9,310.4 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:313.2,314.15 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:314.15,315.34 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:315.34,316.14 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:316.14,318.5 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:322.2,322.36 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:322.36,323.34 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:323.34,326.4 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:327.3,327.53 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:327.53,329.4 1 0 -github.com/echovault/echovault/pkg/modules/set/commands.go:330.3,330.20 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:333.2,335.28 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:335.28,337.10 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:337.10,340.4 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:341.3,341.27 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:344.2,344.20 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:344.20,346.3 1 0 -github.com/echovault/echovault/pkg/modules/set/commands.go:348.2,350.69 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:353.112,355.16 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:355.16,357.3 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:359.2,360.15 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:360.15,361.34 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:361.34,362.14 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:362.14,364.5 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:368.2,368.36 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:368.36,369.34 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:369.34,372.4 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:373.3,373.53 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:373.53,375.4 1 0 -github.com/echovault/echovault/pkg/modules/set/commands.go:376.3,376.20 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:379.2,381.28 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:381.28,383.10 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:383.10,386.4 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:387.3,387.27 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:390.2,393.40 3 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:393.40,394.60 1 0 -github.com/echovault/echovault/pkg/modules/set/commands.go:394.60,396.4 1 0 -github.com/echovault/echovault/pkg/modules/set/commands.go:397.8,398.69 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:398.69,400.4 1 0 -github.com/echovault/echovault/pkg/modules/set/commands.go:403.2,403.68 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:403.68,405.3 1 0 -github.com/echovault/echovault/pkg/modules/set/commands.go:406.2,408.69 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:411.110,413.16 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:413.16,415.3 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:417.2,419.33 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:419.33,421.3 1 0 -github.com/echovault/echovault/pkg/modules/set/commands.go:423.2,423.52 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:423.52,425.3 1 0 -github.com/echovault/echovault/pkg/modules/set/commands.go:426.2,429.9 3 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:429.9,431.3 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:433.2,433.27 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:433.27,435.3 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:437.2,437.30 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:440.109,442.16 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:442.16,444.3 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:446.2,448.33 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:448.33,450.3 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:452.2,452.52 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:452.52,454.3 1 0 -github.com/echovault/echovault/pkg/modules/set/commands.go:455.2,458.9 3 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:458.9,460.3 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:462.2,465.26 3 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:465.26,467.24 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:467.24,469.4 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:472.2,472.25 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:475.111,477.16 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:477.16,479.3 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:481.2,484.33 3 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:484.33,486.29 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:486.29,488.27 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:488.27,490.5 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:492.3,492.26 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:495.2,495.52 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:495.52,497.3 1 0 -github.com/echovault/echovault/pkg/modules/set/commands.go:498.2,501.9 3 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:501.9,503.3 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:505.2,506.36 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:506.36,507.31 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:507.31,509.4 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:509.9,511.4 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:513.2,515.25 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:518.106,520.16 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:520.16,522.3 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:524.2,527.36 3 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:527.36,529.3 1 0 -github.com/echovault/echovault/pkg/modules/set/commands.go:531.2,531.54 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:531.54,533.3 1 0 -github.com/echovault/echovault/pkg/modules/set/commands.go:534.2,537.9 3 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:537.9,539.3 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:541.2,543.41 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:543.41,545.69 1 0 -github.com/echovault/echovault/pkg/modules/set/commands.go:545.69,547.4 1 0 -github.com/echovault/echovault/pkg/modules/set/commands.go:548.3,550.74 3 0 -github.com/echovault/echovault/pkg/modules/set/commands.go:550.74,552.4 1 0 -github.com/echovault/echovault/pkg/modules/set/commands.go:553.8,555.61 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:555.61,557.4 1 0 -github.com/echovault/echovault/pkg/modules/set/commands.go:558.3,560.10 3 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:560.10,562.4 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:563.3,563.22 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:566.2,568.49 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:571.105,573.16 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:573.16,575.3 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:577.2,580.19 3 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:580.19,582.10 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:582.10,584.4 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:585.3,585.12 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:588.2,588.33 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:588.33,590.3 1 0 -github.com/echovault/echovault/pkg/modules/set/commands.go:592.2,592.51 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:592.51,594.3 1 0 -github.com/echovault/echovault/pkg/modules/set/commands.go:595.2,598.9 3 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:598.9,600.3 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:602.2,605.28 3 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:605.28,607.26 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:607.26,609.4 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:612.2,612.25 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:615.112,617.16 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:617.16,619.3 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:621.2,624.19 3 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:624.19,626.10 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:626.10,628.4 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:629.3,629.12 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:632.2,632.33 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:632.33,634.3 1 0 -github.com/echovault/echovault/pkg/modules/set/commands.go:636.2,636.51 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:636.51,638.3 1 0 -github.com/echovault/echovault/pkg/modules/set/commands.go:639.2,642.9 3 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:642.9,644.3 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:646.2,649.28 3 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:649.28,651.26 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:651.26,653.4 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:656.2,656.25 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:659.105,661.16 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:661.16,663.3 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:665.2,668.33 3 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:668.33,670.3 1 0 -github.com/echovault/echovault/pkg/modules/set/commands.go:672.2,672.51 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:672.51,674.3 1 0 -github.com/echovault/echovault/pkg/modules/set/commands.go:675.2,678.9 3 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:678.9,680.3 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:682.2,684.51 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:687.107,689.16 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:689.16,691.3 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:693.2,694.15 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:694.15,695.34 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:695.34,696.14 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:696.14,698.5 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:702.2,702.36 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:702.36,703.34 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:703.34,704.12 1 0 -github.com/echovault/echovault/pkg/modules/set/commands.go:706.3,706.53 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:706.53,708.4 1 0 -github.com/echovault/echovault/pkg/modules/set/commands.go:709.3,709.20 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:712.2,714.33 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:714.33,715.14 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:715.14,716.12 1 0 -github.com/echovault/echovault/pkg/modules/set/commands.go:718.3,719.10 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:719.10,721.4 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:722.3,722.27 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:725.2,728.35 3 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:728.35,730.33 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:730.33,732.4 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:735.2,735.25 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:738.112,740.16 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:740.16,742.3 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:744.2,745.15 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:745.15,746.34 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:746.34,747.14 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:747.14,749.5 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:753.2,753.36 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:753.36,754.34 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:754.34,755.12 1 0 -github.com/echovault/echovault/pkg/modules/set/commands.go:757.3,757.53 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:757.53,759.4 1 0 -github.com/echovault/echovault/pkg/modules/set/commands.go:760.3,760.20 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:763.2,765.33 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:765.33,766.14 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:766.14,767.12 1 0 -github.com/echovault/echovault/pkg/modules/set/commands.go:769.3,770.10 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:770.10,772.4 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:773.3,773.27 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:776.2,780.40 3 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:780.40,781.60 1 0 -github.com/echovault/echovault/pkg/modules/set/commands.go:781.60,783.4 1 0 -github.com/echovault/echovault/pkg/modules/set/commands.go:784.8,785.69 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:785.69,787.4 1 0 -github.com/echovault/echovault/pkg/modules/set/commands.go:789.2,791.64 2 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:791.64,793.3 1 0 -github.com/echovault/echovault/pkg/modules/set/commands.go:794.2,794.65 1 1 -github.com/echovault/echovault/pkg/modules/set/commands.go:797.33,948.2 1 0 -github.com/echovault/echovault/pkg/modules/set/key_funcs.go:25.58,26.18 1 1 -github.com/echovault/echovault/pkg/modules/set/key_funcs.go:26.18,28.3 1 1 -github.com/echovault/echovault/pkg/modules/set/key_funcs.go:29.2,33.8 1 1 -github.com/echovault/echovault/pkg/modules/set/key_funcs.go:36.59,37.19 1 1 -github.com/echovault/echovault/pkg/modules/set/key_funcs.go:37.19,39.3 1 1 -github.com/echovault/echovault/pkg/modules/set/key_funcs.go:40.2,44.8 1 1 -github.com/echovault/echovault/pkg/modules/set/key_funcs.go:47.59,48.18 1 1 -github.com/echovault/echovault/pkg/modules/set/key_funcs.go:48.18,50.3 1 1 -github.com/echovault/echovault/pkg/modules/set/key_funcs.go:51.2,55.8 1 1 -github.com/echovault/echovault/pkg/modules/set/key_funcs.go:58.64,59.18 1 1 -github.com/echovault/echovault/pkg/modules/set/key_funcs.go:59.18,61.3 1 1 -github.com/echovault/echovault/pkg/modules/set/key_funcs.go:62.2,66.8 1 1 -github.com/echovault/echovault/pkg/modules/set/key_funcs.go:69.60,70.18 1 1 -github.com/echovault/echovault/pkg/modules/set/key_funcs.go:70.18,72.3 1 1 -github.com/echovault/echovault/pkg/modules/set/key_funcs.go:73.2,77.8 1 1 -github.com/echovault/echovault/pkg/modules/set/key_funcs.go:80.64,81.18 1 1 -github.com/echovault/echovault/pkg/modules/set/key_funcs.go:81.18,83.3 1 1 -github.com/echovault/echovault/pkg/modules/set/key_funcs.go:85.2,85.56 1 1 -github.com/echovault/echovault/pkg/modules/set/key_funcs.go:85.56,87.3 1 1 -github.com/echovault/echovault/pkg/modules/set/key_funcs.go:89.2,89.20 1 1 -github.com/echovault/echovault/pkg/modules/set/key_funcs.go:89.20,95.3 1 1 -github.com/echovault/echovault/pkg/modules/set/key_funcs.go:97.2,101.8 1 1 -github.com/echovault/echovault/pkg/modules/set/key_funcs.go:104.65,105.18 1 1 -github.com/echovault/echovault/pkg/modules/set/key_funcs.go:105.18,107.3 1 1 -github.com/echovault/echovault/pkg/modules/set/key_funcs.go:108.2,112.8 1 1 -github.com/echovault/echovault/pkg/modules/set/key_funcs.go:115.63,116.19 1 1 -github.com/echovault/echovault/pkg/modules/set/key_funcs.go:116.19,118.3 1 1 -github.com/echovault/echovault/pkg/modules/set/key_funcs.go:119.2,123.8 1 1 -github.com/echovault/echovault/pkg/modules/set/key_funcs.go:126.62,127.19 1 1 -github.com/echovault/echovault/pkg/modules/set/key_funcs.go:127.19,129.3 1 1 -github.com/echovault/echovault/pkg/modules/set/key_funcs.go:130.2,134.8 1 1 -github.com/echovault/echovault/pkg/modules/set/key_funcs.go:137.64,138.18 1 1 -github.com/echovault/echovault/pkg/modules/set/key_funcs.go:138.18,140.3 1 1 -github.com/echovault/echovault/pkg/modules/set/key_funcs.go:141.2,145.8 1 1 -github.com/echovault/echovault/pkg/modules/set/key_funcs.go:148.59,149.19 1 1 -github.com/echovault/echovault/pkg/modules/set/key_funcs.go:149.19,151.3 1 1 -github.com/echovault/echovault/pkg/modules/set/key_funcs.go:152.2,156.8 1 1 -github.com/echovault/echovault/pkg/modules/set/key_funcs.go:159.58,160.34 1 1 -github.com/echovault/echovault/pkg/modules/set/key_funcs.go:160.34,162.3 1 1 -github.com/echovault/echovault/pkg/modules/set/key_funcs.go:163.2,167.8 1 1 -github.com/echovault/echovault/pkg/modules/set/key_funcs.go:170.65,171.34 1 1 -github.com/echovault/echovault/pkg/modules/set/key_funcs.go:171.34,173.3 1 1 -github.com/echovault/echovault/pkg/modules/set/key_funcs.go:174.2,178.8 1 1 -github.com/echovault/echovault/pkg/modules/set/key_funcs.go:181.58,182.18 1 1 -github.com/echovault/echovault/pkg/modules/set/key_funcs.go:182.18,184.3 1 1 -github.com/echovault/echovault/pkg/modules/set/key_funcs.go:185.2,189.8 1 1 -github.com/echovault/echovault/pkg/modules/set/key_funcs.go:192.60,193.18 1 1 -github.com/echovault/echovault/pkg/modules/set/key_funcs.go:193.18,195.3 1 1 -github.com/echovault/echovault/pkg/modules/set/key_funcs.go:196.2,200.8 1 1 -github.com/echovault/echovault/pkg/modules/set/key_funcs.go:203.65,204.18 1 1 -github.com/echovault/echovault/pkg/modules/set/key_funcs.go:204.18,206.3 1 1 -github.com/echovault/echovault/pkg/modules/set/key_funcs.go:207.2,211.8 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:33.105,35.16 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:35.16,37.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:39.2,48.32 7 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:48.32,49.29 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:49.29,50.9 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:52.3,52.44 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:53.15,54.74 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:54.74,56.5 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:57.16,58.25 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:59.12,60.25 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:64.2,64.66 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:64.66,66.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:68.2,70.52 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:70.52,71.15 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:71.15,72.12 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:74.3,75.23 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:76.11,77.64 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:78.15,80.49 2 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:80.49,86.5 2 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:87.4,87.49 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:87.49,93.5 2 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:94.16,99.6 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:100.12,105.6 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:110.2,110.27 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:110.27,112.34 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:112.34,113.70 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:113.70,116.61 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:116.61,118.6 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:119.5,119.13 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:121.4,121.70 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:121.70,125.36 3 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:125.36,127.6 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:128.5,128.13 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:130.4,130.39 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:130.39,132.13 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:134.4,134.41 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:134.41,137.25 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:137.25,139.6 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:140.5,140.13 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:142.4,142.55 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:146.2,146.32 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:146.32,149.17 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:149.17,151.4 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:152.3,154.10 3 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:154.10,156.4 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:157.3,158.17 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:158.17,160.4 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:162.3,162.18 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:162.18,165.4 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:167.3,167.52 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:171.2,171.60 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:171.60,173.3 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:174.2,177.54 3 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:177.54,179.3 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:181.2,181.63 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:184.106,186.16 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:186.16,188.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:189.2,191.33 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:191.33,193.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:195.2,195.52 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:195.52,197.3 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:198.2,201.9 3 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:201.9,203.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:205.2,205.63 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:208.107,210.16 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:210.16,212.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:214.2,217.43 3 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:218.10,219.60 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:220.14,221.40 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:221.40,223.4 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:223.9,225.4 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:226.15,228.32 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:229.11,231.32 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:234.2,235.43 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:236.10,237.60 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:238.14,239.40 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:239.40,241.4 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:241.9,243.4 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:244.15,246.32 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:247.11,249.32 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:252.2,252.33 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:252.33,254.3 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:256.2,256.52 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:256.52,258.3 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:259.2,262.9 3 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:262.9,264.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:266.2,267.33 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:267.33,268.47 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:268.47,270.4 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:273.2,273.58 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:276.110,278.16 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:278.16,280.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:282.2,286.33 4 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:286.33,288.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:290.2,290.52 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:290.52,292.3 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:293.2,296.9 3 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:296.9,298.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:300.2,303.38 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:303.38,304.45 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:304.45,306.4 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:309.2,311.28 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:311.28,313.81 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:313.81,315.4 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:318.2,318.51 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:321.106,323.16 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:323.16,325.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:327.2,327.63 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:327.63,329.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:330.2,330.49 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:330.49,332.3 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:334.2,335.15 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:335.15,336.34 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:336.34,337.14 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:337.14,339.5 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:344.2,344.46 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:344.46,347.3 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:348.2,348.65 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:348.65,350.3 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:351.2,353.9 3 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:353.9,355.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:358.2,360.42 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:360.42,361.47 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:361.47,362.12 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:364.3,365.17 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:365.17,367.4 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:368.3,370.10 3 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:370.10,372.4 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:373.3,373.27 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:376.2,381.34 4 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:381.34,382.20 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:382.20,384.4 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:384.9,386.4 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:389.2,391.25 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:394.111,396.16 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:396.16,398.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:400.2,403.15 3 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:403.15,404.34 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:404.34,405.14 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:405.14,407.5 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:412.2,412.46 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:412.46,415.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:416.2,416.65 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:416.65,418.3 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:419.2,421.9 3 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:421.9,423.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:425.2,427.42 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:427.42,428.46 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:428.46,429.67 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:429.67,431.5 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:432.4,433.11 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:433.11,435.5 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:436.4,436.28 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:440.2,442.40 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:442.40,443.60 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:443.60,445.4 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:446.8,447.69 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:447.69,449.4 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:451.2,453.63 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:453.63,455.3 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:457.2,457.64 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:460.108,462.16 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:462.16,464.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:466.2,470.43 4 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:471.10,472.55 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:473.14,474.57 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:474.57,476.4 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:476.9,476.64 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:476.64,478.4 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:478.9,480.4 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:481.15,483.34 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:484.11,486.34 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:489.2,489.33 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:489.33,492.61 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:492.61,494.4 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:495.3,499.17 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:499.17,501.4 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:502.3,503.99 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:506.2,506.51 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:506.51,508.3 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:509.2,511.9 3 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:511.9,513.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:514.2,520.23 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:520.23,522.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:523.2,524.74 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:527.107,529.16 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:529.16,531.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:533.2,534.16 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:534.16,536.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:538.2,539.15 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:539.15,540.34 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:540.34,541.14 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:541.14,543.5 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:547.2,549.33 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:549.33,550.38 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:550.38,553.4 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:554.3,554.57 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:554.57,556.4 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:557.3,559.10 3 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:559.10,561.4 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:562.3,565.5 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:568.2,572.33 3 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:572.33,573.40 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:573.40,574.18 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:574.18,576.5 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:576.10,578.5 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:582.2,584.25 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:587.112,589.16 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:589.16,591.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:593.2,596.51 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:596.51,598.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:600.2,601.16 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:601.16,603.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:605.2,606.15 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:606.15,607.34 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:607.34,608.14 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:608.14,610.5 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:614.2,616.33 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:616.33,617.38 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:617.38,619.4 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:620.3,620.57 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:620.57,622.4 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:623.3,625.10 3 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:625.10,627.4 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:628.3,631.5 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:634.2,636.71 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:636.71,637.60 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:637.60,639.4 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:640.8,640.40 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:640.40,641.69 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:641.69,643.4 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:645.2,647.68 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:647.68,649.3 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:651.2,651.69 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:654.106,656.16 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:656.16,658.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:660.2,665.56 4 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:665.56,667.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:668.2,668.20 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:668.20,669.19 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:669.19,671.4 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:672.3,672.29 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:672.29,674.4 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:675.3,676.17 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:676.17,678.4 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:679.3,679.13 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:679.13,681.4 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:682.3,683.25 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:687.2,687.57 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:687.57,689.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:690.2,690.21 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:690.21,691.20 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:691.20,693.4 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:694.3,695.53 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:695.53,697.4 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:700.2,700.43 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:700.43,701.47 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:701.47,702.67 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:702.67,703.13 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:705.4,706.35 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:706.35,708.13 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:710.4,711.18 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:711.18,714.5 2 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:715.4,719.38 3 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:719.38,721.5 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:723.4,725.27 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:729.2,729.30 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:732.105,734.16 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:734.16,736.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:738.2,742.42 4 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:742.42,744.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:746.2,746.19 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:746.19,748.17 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:748.17,750.4 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:751.3,751.12 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:751.12,753.4 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:756.2,756.33 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:756.33,758.3 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:760.2,760.51 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:760.51,762.3 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:763.2,766.9 3 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:766.9,768.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:770.2,771.16 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:771.16,773.3 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:775.2,776.36 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:776.36,778.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:780.2,782.25 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:785.108,787.16 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:787.16,789.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:791.2,793.33 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:793.33,795.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:797.2,797.52 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:797.52,799.3 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:800.2,803.9 3 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:803.9,805.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:807.2,813.36 4 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:813.36,815.21 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:815.21,817.4 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:817.9,819.4 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:822.2,824.25 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:827.112,829.16 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:829.16,831.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:833.2,836.19 3 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:836.19,838.17 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:838.17,840.4 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:841.3,841.13 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:841.13,843.4 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:846.2,847.19 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:847.19,848.46 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:848.46,850.4 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:850.9,852.4 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:855.2,855.33 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:855.33,857.3 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:859.2,859.52 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:859.52,861.3 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:862.2,865.9 3 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:865.9,867.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:869.2,872.28 3 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:872.28,873.17 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:873.17,875.4 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:875.9,877.4 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:880.2,882.25 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:885.106,887.16 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:887.16,889.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:891.2,895.62 4 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:895.62,897.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:899.2,899.33 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:899.33,901.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:903.2,903.52 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:903.52,905.3 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:906.2,909.9 3 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:909.9,911.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:913.2,914.65 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:914.65,915.44 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:915.44,917.4 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:918.3,918.39 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:921.2,921.36 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:921.36,922.51 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:922.51,923.18 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:923.18,926.5 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:926.10,928.5 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:932.2,932.31 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:935.105,937.16 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:937.16,939.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:941.2,943.33 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:943.33,945.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:947.2,947.51 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:947.51,949.3 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:950.2,953.9 3 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:953.9,955.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:957.2,958.28 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:958.28,959.38 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:959.38,961.4 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:964.2,964.58 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:967.107,969.16 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:969.16,971.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:973.2,975.33 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:975.33,977.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:978.2,978.52 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:978.52,980.3 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:981.2,983.9 3 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:983.9,985.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:986.2,987.20 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:987.20,989.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:991.2,993.69 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:996.117,998.16 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:998.16,1000.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1002.2,1007.16 4 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1007.16,1009.3 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1011.2,1012.16 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1012.16,1014.3 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1016.2,1016.33 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1016.33,1018.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1020.2,1020.51 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1020.51,1022.3 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1023.2,1026.9 3 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1026.9,1028.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1030.2,1030.33 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1030.33,1031.83 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1031.83,1034.4 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1037.2,1037.58 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1040.116,1042.16 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1042.16,1044.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1046.2,1049.16 3 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1049.16,1051.3 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1053.2,1054.16 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1054.16,1056.3 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1058.2,1058.33 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1058.33,1060.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1062.2,1062.51 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1062.51,1064.3 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1065.2,1068.9 3 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1068.9,1070.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1072.2,1072.15 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1072.15,1074.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1075.2,1075.14 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1075.14,1077.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1079.2,1079.88 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1079.88,1081.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1083.2,1084.65 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1084.65,1086.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1088.2,1090.18 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1090.18,1091.34 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1091.34,1094.4 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1095.8,1096.34 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1096.34,1099.4 2 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1102.2,1102.58 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1105.115,1107.16 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1107.16,1109.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1111.2,1115.33 4 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1115.33,1117.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1119.2,1119.51 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1119.51,1121.3 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1122.2,1125.9 3 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1125.9,1127.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1129.2,1132.38 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1132.38,1133.45 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1133.45,1135.4 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1138.2,1141.28 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1141.28,1143.81 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1143.81,1146.4 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1149.2,1149.58 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1152.107,1154.16 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1154.16,1156.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1158.2,1167.65 9 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1167.65,1169.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1171.2,1171.62 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1171.62,1173.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1175.2,1175.54 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1175.54,1177.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1177.5,1179.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1179.8,1182.17 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1182.17,1184.4 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1185.3,1186.17 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1186.17,1188.4 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1191.2,1191.54 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1191.54,1193.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1193.5,1194.61 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1194.61,1196.4 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1197.3,1197.50 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1197.50,1199.4 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1200.3,1201.17 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1201.17,1203.4 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1204.3,1204.17 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1204.17,1206.4 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1207.3,1208.17 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1208.17,1210.4 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1213.2,1213.33 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1213.33,1215.3 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1217.2,1217.52 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1217.52,1219.3 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1220.2,1223.9 3 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1223.9,1225.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1227.2,1227.32 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1227.32,1229.3 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1230.2,1230.15 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1230.15,1232.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1234.2,1235.42 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1235.42,1236.66 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1236.66,1238.15 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1238.15,1240.5 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1241.4,1241.40 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1244.2,1244.40 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1244.40,1246.39 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1246.39,1247.46 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1247.46,1249.5 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1251.3,1251.66 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1251.66,1252.15 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1252.15,1254.5 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1255.4,1255.64 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1259.2,1261.35 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1261.35,1262.24 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1262.24,1263.9 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1265.3,1265.43 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1265.43,1266.107 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1266.107,1268.5 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1269.4,1269.12 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1271.3,1272.90 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1272.90,1274.4 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1277.2,1279.34 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1279.34,1280.17 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1280.17,1282.4 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1282.9,1284.4 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1287.2,1289.25 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1292.112,1294.16 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1294.16,1296.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1298.2,1308.62 10 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1308.62,1310.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1312.2,1312.54 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1312.54,1314.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1314.5,1316.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1316.8,1319.17 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1319.17,1321.4 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1322.3,1323.17 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1323.17,1325.4 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1328.2,1328.54 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1328.54,1330.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1330.5,1331.61 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1331.61,1333.4 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1334.3,1334.50 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1334.50,1336.4 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1337.3,1338.17 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1338.17,1340.4 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1341.3,1341.17 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1341.17,1343.4 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1344.3,1345.17 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1345.17,1347.4 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1350.2,1350.36 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1350.36,1352.3 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1354.2,1354.55 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1354.55,1356.3 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1357.2,1360.9 3 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1360.9,1362.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1364.2,1364.32 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1364.32,1366.3 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1367.2,1367.15 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1367.15,1369.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1371.2,1372.42 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1372.42,1373.66 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1373.66,1375.15 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1375.15,1377.5 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1378.4,1378.40 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1381.2,1381.40 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1381.40,1383.39 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1383.39,1384.46 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1384.46,1386.5 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1388.3,1388.66 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1388.66,1389.15 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1389.15,1391.5 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1392.4,1392.64 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1396.2,1398.35 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1398.35,1399.24 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1399.24,1400.9 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1402.3,1402.43 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1402.43,1403.107 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1403.107,1405.5 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1406.4,1406.12 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1408.3,1409.90 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1409.90,1411.4 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1414.2,1416.40 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1416.40,1417.60 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1417.60,1419.4 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1420.8,1421.69 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1421.69,1423.4 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1425.2,1427.71 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1427.71,1429.3 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1431.2,1431.72 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1434.107,1435.46 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1435.46,1437.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1439.2,1440.16 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1440.16,1442.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1444.2,1445.15 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1445.15,1446.34 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1446.34,1447.14 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1447.14,1449.5 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1453.2,1455.33 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1455.33,1456.37 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1456.37,1457.58 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1457.58,1459.5 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1460.4,1462.11 3 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1462.11,1464.5 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1465.4,1468.6 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1472.2,1475.35 3 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1475.35,1476.17 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1476.17,1478.4 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1478.9,1480.4 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1483.2,1485.25 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1488.112,1490.16 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1490.16,1492.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1494.2,1497.51 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1497.51,1499.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1501.2,1502.16 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1502.16,1504.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1506.2,1507.15 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1507.15,1508.34 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1508.34,1509.14 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1509.14,1511.5 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1515.2,1517.33 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1517.33,1518.37 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1518.37,1519.58 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1519.58,1521.5 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1522.4,1524.11 3 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1524.11,1526.5 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1527.4,1530.6 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1534.2,1536.40 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1536.40,1537.60 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1537.60,1539.4 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1540.8,1541.69 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1541.69,1543.4 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1545.2,1547.64 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1547.64,1549.3 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1551.2,1551.65 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/commands.go:1554.33,1826.2 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:25.58,26.18 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:26.18,28.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:29.2,33.8 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:36.59,37.19 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:37.19,39.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:40.2,44.8 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:47.60,48.19 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:48.19,50.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:51.2,55.8 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:58.59,59.18 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:59.18,61.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:63.2,63.63 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:63.63,65.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:67.2,67.27 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:67.27,73.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:75.2,79.8 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:82.64,83.18 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:83.18,85.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:86.2,90.8 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:93.61,94.19 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:94.19,96.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:97.2,101.8 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:104.60,105.18 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:105.18,107.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:108.2,108.58 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:108.58,111.39 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:111.39,113.4 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:114.3,114.15 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:116.2,116.18 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:116.18,122.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:123.2,123.17 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:123.17,129.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:130.2,130.68 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:133.65,134.18 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:134.18,136.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:137.2,137.58 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:137.58,140.39 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:140.39,142.4 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:143.3,143.15 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:145.2,145.18 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:145.18,151.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:152.2,152.17 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:152.17,158.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:159.2,159.68 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:162.59,163.18 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:163.18,165.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:166.2,166.54 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:166.54,168.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:169.2,169.18 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:169.18,175.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:176.2,176.17 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:176.17,182.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:183.2,183.68 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:186.61,187.18 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:187.18,189.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:190.2,194.8 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:197.58,198.34 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:198.34,200.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:201.2,205.8 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:208.65,209.34 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:209.34,211.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:212.2,216.8 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:219.59,220.34 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:220.34,222.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:223.2,227.8 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:230.58,231.18 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:231.18,233.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:234.2,238.8 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:241.62,242.18 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:242.18,244.3 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:245.2,249.8 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:252.60,253.19 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:253.19,255.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:256.2,260.8 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:263.68,264.19 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:264.19,266.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:267.2,271.8 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:274.69,275.19 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:275.19,277.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:278.2,282.8 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:285.70,286.19 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:286.19,288.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:289.2,293.8 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:296.63,297.19 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:297.19,299.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:300.2,304.8 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:307.61,308.35 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:308.35,310.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:311.2,315.8 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:318.65,319.35 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:319.35,321.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:322.2,326.8 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:329.60,330.18 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:330.18,332.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:333.2,333.58 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:333.58,336.39 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:336.39,338.4 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:339.3,339.15 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:341.2,341.18 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:341.18,347.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:348.2,348.17 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:348.17,354.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:355.2,355.68 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:358.65,359.18 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:359.18,361.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:362.2,362.58 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:362.58,365.39 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:365.39,367.4 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:368.3,368.15 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:370.2,370.18 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:370.18,376.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:377.2,377.17 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:377.17,383.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/key_funcs.go:384.2,384.68 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/utils.go:24.97,26.60 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/utils.go:26.60,28.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/utils.go:29.2,29.24 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/utils.go:29.24,30.48 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/utils.go:30.48,31.85 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/utils.go:31.85,32.10 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/utils.go:34.4,35.18 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/utils.go:35.18,37.5 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/utils.go:38.4,38.32 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/utils.go:42.2,43.62 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/utils.go:43.62,45.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/utils.go:46.2,46.26 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/utils.go:46.26,47.94 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/utils.go:47.94,49.4 1 0 -github.com/echovault/echovault/pkg/modules/sorted_set/utils.go:50.3,50.53 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/utils.go:53.2,54.63 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/utils.go:54.63,56.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/utils.go:57.2,57.27 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/utils.go:57.27,59.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/utils.go:62.2,63.85 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/utils.go:63.85,64.26 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/utils.go:64.26,65.12 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/utils.go:67.3,67.31 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/utils.go:67.31,69.12 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/utils.go:71.3,71.41 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/utils.go:71.41,73.4 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/utils.go:76.2,77.30 2 1 -github.com/echovault/echovault/pkg/modules/sorted_set/utils.go:77.30,79.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/utils.go:79.8,81.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/utils.go:83.2,83.55 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/utils.go:83.55,85.3 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/utils.go:85.8,85.31 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/utils.go:85.31,86.34 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/utils.go:86.34,88.4 1 1 -github.com/echovault/echovault/pkg/modules/sorted_set/utils.go:91.2,91.50 1 1 -github.com/echovault/echovault/pkg/modules/string/commands.go:27.109,29.16 2 1 -github.com/echovault/echovault/pkg/modules/string/commands.go:29.16,31.3 1 1 -github.com/echovault/echovault/pkg/modules/string/commands.go:33.2,36.9 3 1 -github.com/echovault/echovault/pkg/modules/string/commands.go:36.9,38.3 1 1 -github.com/echovault/echovault/pkg/modules/string/commands.go:40.2,42.33 2 1 -github.com/echovault/echovault/pkg/modules/string/commands.go:42.33,43.61 1 1 -github.com/echovault/echovault/pkg/modules/string/commands.go:43.61,45.4 1 0 -github.com/echovault/echovault/pkg/modules/string/commands.go:46.3,46.58 1 1 -github.com/echovault/echovault/pkg/modules/string/commands.go:46.58,48.4 1 0 -github.com/echovault/echovault/pkg/modules/string/commands.go:49.3,50.58 2 1 -github.com/echovault/echovault/pkg/modules/string/commands.go:53.2,53.52 1 1 -github.com/echovault/echovault/pkg/modules/string/commands.go:53.52,55.3 1 0 -github.com/echovault/echovault/pkg/modules/string/commands.go:56.2,59.9 3 1 -github.com/echovault/echovault/pkg/modules/string/commands.go:59.9,61.3 1 1 -github.com/echovault/echovault/pkg/modules/string/commands.go:64.2,64.24 1 1 -github.com/echovault/echovault/pkg/modules/string/commands.go:64.24,66.58 2 1 -github.com/echovault/echovault/pkg/modules/string/commands.go:66.58,68.4 1 0 -github.com/echovault/echovault/pkg/modules/string/commands.go:69.3,69.58 1 1 -github.com/echovault/echovault/pkg/modules/string/commands.go:73.2,73.16 1 1 -github.com/echovault/echovault/pkg/modules/string/commands.go:73.16,75.58 2 1 -github.com/echovault/echovault/pkg/modules/string/commands.go:75.58,77.4 1 0 -github.com/echovault/echovault/pkg/modules/string/commands.go:78.3,78.58 1 1 -github.com/echovault/echovault/pkg/modules/string/commands.go:81.2,83.35 2 1 -github.com/echovault/echovault/pkg/modules/string/commands.go:83.35,85.24 1 1 -github.com/echovault/echovault/pkg/modules/string/commands.go:85.24,88.12 3 1 -github.com/echovault/echovault/pkg/modules/string/commands.go:91.3,92.8 2 1 -github.com/echovault/echovault/pkg/modules/string/commands.go:95.2,95.67 1 1 -github.com/echovault/echovault/pkg/modules/string/commands.go:95.67,97.3 1 0 -github.com/echovault/echovault/pkg/modules/string/commands.go:99.2,99.59 1 1 -github.com/echovault/echovault/pkg/modules/string/commands.go:102.110,104.16 2 1 -github.com/echovault/echovault/pkg/modules/string/commands.go:104.16,106.3 1 1 -github.com/echovault/echovault/pkg/modules/string/commands.go:108.2,110.33 2 1 -github.com/echovault/echovault/pkg/modules/string/commands.go:110.33,112.3 1 1 -github.com/echovault/echovault/pkg/modules/string/commands.go:114.2,114.53 1 1 -github.com/echovault/echovault/pkg/modules/string/commands.go:114.53,116.3 1 0 -github.com/echovault/echovault/pkg/modules/string/commands.go:117.2,121.9 3 1 -github.com/echovault/echovault/pkg/modules/string/commands.go:121.9,123.3 1 0 -github.com/echovault/echovault/pkg/modules/string/commands.go:125.2,125.56 1 1 -github.com/echovault/echovault/pkg/modules/string/commands.go:128.107,130.16 2 1 -github.com/echovault/echovault/pkg/modules/string/commands.go:130.16,132.3 1 1 -github.com/echovault/echovault/pkg/modules/string/commands.go:134.2,140.24 5 1 -github.com/echovault/echovault/pkg/modules/string/commands.go:140.24,142.3 1 1 -github.com/echovault/echovault/pkg/modules/string/commands.go:144.2,144.33 1 1 -github.com/echovault/echovault/pkg/modules/string/commands.go:144.33,146.3 1 1 -github.com/echovault/echovault/pkg/modules/string/commands.go:148.2,148.52 1 1 -github.com/echovault/echovault/pkg/modules/string/commands.go:148.52,150.3 1 0 -github.com/echovault/echovault/pkg/modules/string/commands.go:151.2,154.9 3 1 -github.com/echovault/echovault/pkg/modules/string/commands.go:154.9,156.3 1 0 -github.com/echovault/echovault/pkg/modules/string/commands.go:158.2,158.15 1 1 -github.com/echovault/echovault/pkg/modules/string/commands.go:158.15,160.3 1 1 -github.com/echovault/echovault/pkg/modules/string/commands.go:161.2,161.13 1 1 -github.com/echovault/echovault/pkg/modules/string/commands.go:161.13,163.3 1 0 -github.com/echovault/echovault/pkg/modules/string/commands.go:165.2,165.30 1 1 -github.com/echovault/echovault/pkg/modules/string/commands.go:165.30,167.3 1 1 -github.com/echovault/echovault/pkg/modules/string/commands.go:169.2,169.22 1 1 -github.com/echovault/echovault/pkg/modules/string/commands.go:169.22,171.3 1 1 -github.com/echovault/echovault/pkg/modules/string/commands.go:173.2,173.17 1 1 -github.com/echovault/echovault/pkg/modules/string/commands.go:173.17,176.3 2 1 -github.com/echovault/echovault/pkg/modules/string/commands.go:178.2,180.14 2 1 -github.com/echovault/echovault/pkg/modules/string/commands.go:180.14,182.38 2 1 -github.com/echovault/echovault/pkg/modules/string/commands.go:182.38,184.4 1 1 -github.com/echovault/echovault/pkg/modules/string/commands.go:185.3,185.12 1 1 -github.com/echovault/echovault/pkg/modules/string/commands.go:188.2,188.65 1 1 -github.com/echovault/echovault/pkg/modules/string/commands.go:191.33,231.2 1 0 -github.com/echovault/echovault/pkg/modules/string/key_funcs.go:23.62,24.19 1 1 -github.com/echovault/echovault/pkg/modules/string/key_funcs.go:24.19,26.3 1 1 -github.com/echovault/echovault/pkg/modules/string/key_funcs.go:27.2,31.8 1 1 -github.com/echovault/echovault/pkg/modules/string/key_funcs.go:34.60,35.19 1 1 -github.com/echovault/echovault/pkg/modules/string/key_funcs.go:35.19,37.3 1 1 -github.com/echovault/echovault/pkg/modules/string/key_funcs.go:38.2,42.8 1 1 -github.com/echovault/echovault/pkg/modules/string/key_funcs.go:45.60,46.19 1 1 -github.com/echovault/echovault/pkg/modules/string/key_funcs.go:46.19,48.3 1 1 -github.com/echovault/echovault/pkg/modules/string/key_funcs.go:49.2,53.8 1 1 diff --git a/internal/acl/acl.go b/internal/modules/acl/acl.go similarity index 98% rename from internal/acl/acl.go rename to internal/modules/acl/acl.go index 94adf91f..f2e158ed 100644 --- a/internal/acl/acl.go +++ b/internal/modules/acl/acl.go @@ -20,9 +20,9 @@ import ( "encoding/json" "errors" "fmt" + "github.com/echovault/echovault/internal" "github.com/echovault/echovault/internal/config" "github.com/echovault/echovault/pkg/constants" - "github.com/echovault/echovault/pkg/types" "github.com/gobwas/glob" "gopkg.in/yaml.v3" "log" @@ -286,7 +286,7 @@ func (acl *ACL) AuthenticateConnection(_ context.Context, conn *net.Conn, cmd [] return errors.New("could not authenticate user") } -func (acl *ACL) AuthorizeConnection(conn *net.Conn, cmd []string, command types.Command, subCommand types.SubCommand) error { +func (acl *ACL) AuthorizeConnection(conn *net.Conn, cmd []string, command internal.Command, subCommand internal.SubCommand) error { acl.RLockUsers() defer acl.RUnlockUsers() @@ -303,7 +303,7 @@ func (acl *ACL) AuthorizeConnection(conn *net.Conn, cmd []string, command types. readKeys := keys.ReadKeys writeKeys := keys.WriteKeys - if !reflect.DeepEqual(subCommand, types.SubCommand{}) { + if !reflect.DeepEqual(subCommand, internal.SubCommand{}) { comm = fmt.Sprintf("%s|%s", comm, subCommand.Command) categories = append(categories, subCommand.Categories...) keys, err = subCommand.KeyExtractionFunc(cmd) diff --git a/pkg/modules/acl/commands.go b/internal/modules/acl/commands.go similarity index 81% rename from pkg/modules/acl/commands.go rename to internal/modules/acl/commands.go index 2392cb83..64cb5c3a 100644 --- a/pkg/modules/acl/commands.go +++ b/internal/modules/acl/commands.go @@ -15,50 +15,47 @@ package acl import ( - "context" "encoding/json" "errors" "fmt" - internal_acl "github.com/echovault/echovault/internal/acl" + "github.com/echovault/echovault/internal" "github.com/echovault/echovault/pkg/constants" - "github.com/echovault/echovault/pkg/types" "gopkg.in/yaml.v3" "log" - "net" "os" "path" "slices" "strings" ) -func handleAuth(ctx context.Context, cmd []string, server types.EchoVault, conn *net.Conn) ([]byte, error) { - if len(cmd) < 2 || len(cmd) > 3 { +func handleAuth(params internal.HandlerFuncParams) ([]byte, error) { + if len(params.Command) < 2 || len(params.Command) > 3 { return nil, errors.New(constants.WrongArgsResponse) } - acl, ok := server.GetACL().(*internal_acl.ACL) + acl, ok := params.GetACL().(*ACL) if !ok { return nil, errors.New("could not load ACL") } - if err := acl.AuthenticateConnection(ctx, conn, cmd); err != nil { + if err := acl.AuthenticateConnection(params.Context, params.Connection, params.Command); err != nil { return nil, err } return []byte(constants.OkResponse), nil } -func handleGetUser(_ context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - if len(cmd) != 3 { +func handleGetUser(params internal.HandlerFuncParams) ([]byte, error) { + if len(params.Command) != 3 { return nil, errors.New(constants.WrongArgsResponse) } - acl, ok := server.GetACL().(*internal_acl.ACL) + acl, ok := params.GetACL().(*ACL) if !ok { return nil, errors.New("could not load ACL") } - var user *internal_acl.User + var user *User userFound := false for _, u := range acl.Users { - if u.Username == cmd[2] { + if u.Username == params.Command[2] { user = u userFound = true break @@ -162,14 +159,14 @@ func handleGetUser(_ context.Context, cmd []string, server types.EchoVault, _ *n return []byte(res), nil } -func handleCat(_ context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - if len(cmd) > 3 { +func handleCat(params internal.HandlerFuncParams) ([]byte, error) { + if len(params.Command) > 3 { return nil, errors.New(constants.WrongArgsResponse) } categories := make(map[string][]string) - commands := server.GetAllCommands() + commands := params.GetAllCommands() for _, command := range commands { if len(command.SubCommands) == 0 { @@ -186,7 +183,7 @@ func handleCat(_ context.Context, cmd []string, server types.EchoVault, _ *net.C } } - if len(cmd) == 2 { + if len(params.Command) == 2 { var cats []string length := 0 for key, _ := range categories { @@ -203,10 +200,10 @@ func handleCat(_ context.Context, cmd []string, server types.EchoVault, _ *net.C return []byte(res), nil } - if len(cmd) == 3 { + if len(params.Command) == 3 { var res string for category, commands := range categories { - if strings.EqualFold(category, cmd[2]) { + if strings.EqualFold(category, params.Command[2]) { res = fmt.Sprintf("*%d", len(commands)) for i, command := range commands { res = fmt.Sprintf("%s\r\n+%s", res, command) @@ -219,11 +216,11 @@ func handleCat(_ context.Context, cmd []string, server types.EchoVault, _ *net.C } } - return nil, fmt.Errorf("category %s not found", strings.ToUpper(cmd[2])) + return nil, fmt.Errorf("category %s not found", strings.ToUpper(params.Command[2])) } -func handleUsers(_ context.Context, _ []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - acl, ok := server.GetACL().(*internal_acl.ACL) +func handleUsers(params internal.HandlerFuncParams) ([]byte, error) { + acl, ok := params.GetACL().(*ACL) if !ok { return nil, errors.New("could not load ACL") } @@ -235,45 +232,45 @@ func handleUsers(_ context.Context, _ []string, server types.EchoVault, _ *net.C return []byte(res), nil } -func handleSetUser(_ context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - acl, ok := server.GetACL().(*internal_acl.ACL) +func handleSetUser(params internal.HandlerFuncParams) ([]byte, error) { + acl, ok := params.GetACL().(*ACL) if !ok { return nil, errors.New("could not load ACL") } - if err := acl.SetUser(cmd[2:]); err != nil { + if err := acl.SetUser(params.Command[2:]); err != nil { return nil, err } return []byte(constants.OkResponse), nil } -func handleDelUser(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - if len(cmd) < 3 { +func handleDelUser(params internal.HandlerFuncParams) ([]byte, error) { + if len(params.Command) < 3 { return nil, errors.New(constants.WrongArgsResponse) } - acl, ok := server.GetACL().(*internal_acl.ACL) + acl, ok := params.GetACL().(*ACL) if !ok { return nil, errors.New("could not load ACL") } - if err := acl.DeleteUser(ctx, cmd[2:]); err != nil { + if err := acl.DeleteUser(params.Context, params.Command[2:]); err != nil { return nil, err } return []byte(constants.OkResponse), nil } -func handleWhoAmI(_ context.Context, _ []string, server types.EchoVault, conn *net.Conn) ([]byte, error) { - acl, ok := server.GetACL().(*internal_acl.ACL) +func handleWhoAmI(params internal.HandlerFuncParams) ([]byte, error) { + acl, ok := params.GetACL().(*ACL) if !ok { return nil, errors.New("could not load ACL") } - connectionInfo := acl.Connections[conn] + connectionInfo := acl.Connections[params.Connection] return []byte(fmt.Sprintf("+%s\r\n", connectionInfo.User.Username)), nil } -func handleList(_ context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - if len(cmd) > 2 { +func handleList(params internal.HandlerFuncParams) ([]byte, error) { + if len(params.Command) > 2 { return nil, errors.New(constants.WrongArgsResponse) } - acl, ok := server.GetACL().(*internal_acl.ACL) + acl, ok := params.GetACL().(*ACL) if !ok { return nil, errors.New("could not load ACL") } @@ -365,12 +362,12 @@ func handleList(_ context.Context, cmd []string, server types.EchoVault, _ *net. return []byte(res), nil } -func handleLoad(_ context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - if len(cmd) != 3 { +func handleLoad(params internal.HandlerFuncParams) ([]byte, error) { + if len(params.Command) != 3 { return nil, errors.New(constants.WrongArgsResponse) } - acl, ok := server.GetACL().(*internal_acl.ACL) + acl, ok := params.GetACL().(*ACL) if !ok { return nil, errors.New("could not load ACL") } @@ -391,7 +388,7 @@ func handleLoad(_ context.Context, cmd []string, server types.EchoVault, _ *net. ext := path.Ext(f.Name()) - var users []*internal_acl.User + var users []*User if ext == ".json" { if err := json.NewDecoder(f).Decode(&users); err != nil { @@ -414,7 +411,7 @@ func handleLoad(_ context.Context, cmd []string, server types.EchoVault, _ *net. if u.Username == user.Username { userFound = true // If we have a user with the current username and are in merge mode, merge the two users. - if strings.EqualFold(cmd[2], "merge") { + if strings.EqualFold(params.Command[2], "merge") { u.Merge(user) } else { // If we have a user with the current username and are in replace mode, merge the two users. @@ -432,12 +429,12 @@ func handleLoad(_ context.Context, cmd []string, server types.EchoVault, _ *net. return []byte(constants.OkResponse), nil } -func handleSave(_ context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - if len(cmd) > 2 { +func handleSave(params internal.HandlerFuncParams) ([]byte, error) { + if len(params.Command) > 2 { return nil, errors.New(constants.WrongArgsResponse) } - acl, ok := server.GetACL().(*internal_acl.ACL) + acl, ok := params.GetACL().(*ACL) if !ok { return nil, errors.New("could not load ACL") } @@ -490,16 +487,16 @@ func handleSave(_ context.Context, cmd []string, server types.EchoVault, _ *net. return []byte(constants.OkResponse), nil } -func Commands() []types.Command { - return []types.Command{ +func Commands() []internal.Command { + return []internal.Command{ { Command: "auth", Module: constants.ACLModule, Categories: []string{constants.ConnectionCategory, constants.SlowCategory}, Description: "(AUTH [username] password) Authenticates the connection", Sync: false, - KeyExtractionFunc: func(cmd []string) (types.AccessKeys, error) { - return types.AccessKeys{ + KeyExtractionFunc: func(cmd []string) (internal.AccessKeys, error) { + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: make([]string, 0), @@ -513,14 +510,14 @@ func Commands() []types.Command { Categories: []string{}, Description: "Access-Control-List commands", Sync: false, - KeyExtractionFunc: func(cmd []string) (types.AccessKeys, error) { - return types.AccessKeys{ + KeyExtractionFunc: func(cmd []string) (internal.AccessKeys, error) { + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: make([]string, 0), }, nil }, - SubCommands: []types.SubCommand{ + SubCommands: []internal.SubCommand{ { Command: "cat", Module: constants.ACLModule, @@ -528,8 +525,8 @@ func Commands() []types.Command { Description: `(ACL CAT [category]) List all the categories. If the optional category is provided, list all the commands in the category`, Sync: false, - KeyExtractionFunc: func(cmd []string) (types.AccessKeys, error) { - return types.AccessKeys{ + KeyExtractionFunc: func(cmd []string) (internal.AccessKeys, error) { + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: make([]string, 0), @@ -543,8 +540,8 @@ If the optional category is provided, list all the commands in the category`, Categories: []string{constants.AdminCategory, constants.SlowCategory, constants.DangerousCategory}, Description: "(ACL USERS) List all usernames of the configured ACL users", Sync: false, - KeyExtractionFunc: func(cmd []string) (types.AccessKeys, error) { - return types.AccessKeys{ + KeyExtractionFunc: func(cmd []string) (internal.AccessKeys, error) { + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: make([]string, 0), @@ -558,8 +555,8 @@ If the optional category is provided, list all the commands in the category`, Categories: []string{constants.AdminCategory, constants.SlowCategory, constants.DangerousCategory}, Description: "(ACL SETUSER) Configure a new or existing user", Sync: true, - KeyExtractionFunc: func(cmd []string) (types.AccessKeys, error) { - return types.AccessKeys{ + KeyExtractionFunc: func(cmd []string) (internal.AccessKeys, error) { + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: make([]string, 0), @@ -573,8 +570,8 @@ If the optional category is provided, list all the commands in the category`, Categories: []string{constants.AdminCategory, constants.SlowCategory, constants.DangerousCategory}, Description: "(ACL GETUSER username) List the ACL rules of a user", Sync: false, - KeyExtractionFunc: func(cmd []string) (types.AccessKeys, error) { - return types.AccessKeys{ + KeyExtractionFunc: func(cmd []string) (internal.AccessKeys, error) { + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: make([]string, 0), @@ -588,8 +585,8 @@ If the optional category is provided, list all the commands in the category`, Categories: []string{constants.AdminCategory, constants.SlowCategory, constants.DangerousCategory}, Description: "(ACL DELUSER username [username ...]) Deletes users and terminates their connections. Cannot delete default user", Sync: true, - KeyExtractionFunc: func(cmd []string) (types.AccessKeys, error) { - return types.AccessKeys{ + KeyExtractionFunc: func(cmd []string) (internal.AccessKeys, error) { + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: make([]string, 0), @@ -603,8 +600,8 @@ If the optional category is provided, list all the commands in the category`, Categories: []string{constants.FastCategory}, Description: "(ACL WHOAMI) Returns the authenticated user of the current connection", Sync: true, - KeyExtractionFunc: func(cmd []string) (types.AccessKeys, error) { - return types.AccessKeys{ + KeyExtractionFunc: func(cmd []string) (internal.AccessKeys, error) { + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: make([]string, 0), @@ -618,8 +615,8 @@ If the optional category is provided, list all the commands in the category`, Categories: []string{constants.AdminCategory, constants.SlowCategory, constants.DangerousCategory}, Description: "(ACL LIST) Dumps effective acl rules in acl config file format", Sync: true, - KeyExtractionFunc: func(cmd []string) (types.AccessKeys, error) { - return types.AccessKeys{ + KeyExtractionFunc: func(cmd []string) (internal.AccessKeys, error) { + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: make([]string, 0), @@ -636,8 +633,8 @@ If the optional category is provided, list all the commands in the category`, When 'MERGE' is passed, users from config file who share a username with users in memory will be merged. When 'REPLACE' is passed, users from config file who share a username with users in memory will replace the user in memory.`, Sync: true, - KeyExtractionFunc: func(cmd []string) (types.AccessKeys, error) { - return types.AccessKeys{ + KeyExtractionFunc: func(cmd []string) (internal.AccessKeys, error) { + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: make([]string, 0), @@ -651,8 +648,8 @@ When 'REPLACE' is passed, users from config file who share a username with users Categories: []string{constants.AdminCategory, constants.SlowCategory, constants.DangerousCategory}, Description: "(ACL SAVE) Saves the effective ACL rules the configured ACL config file", Sync: true, - KeyExtractionFunc: func(cmd []string) (types.AccessKeys, error) { - return types.AccessKeys{ + KeyExtractionFunc: func(cmd []string) (internal.AccessKeys, error) { + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: make([]string, 0), diff --git a/internal/acl/user.go b/internal/modules/acl/user.go similarity index 100% rename from internal/acl/user.go rename to internal/modules/acl/user.go diff --git a/pkg/modules/admin/commands.go b/internal/modules/admin/commands.go similarity index 77% rename from pkg/modules/admin/commands.go rename to internal/modules/admin/commands.go index b1edf83e..26b87dc3 100644 --- a/pkg/modules/admin/commands.go +++ b/internal/modules/admin/commands.go @@ -15,19 +15,17 @@ package admin import ( - "context" "errors" "fmt" + "github.com/echovault/echovault/internal" "github.com/echovault/echovault/pkg/constants" - "github.com/echovault/echovault/pkg/types" "github.com/gobwas/glob" - "net" "slices" "strings" ) -func handleGetAllCommands(_ context.Context, _ []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - commands := server.GetAllCommands() +func handleGetAllCommands(params internal.HandlerFuncParams) ([]byte, error) { + commands := params.GetAllCommands() res := "" commandCount := 0 @@ -71,10 +69,10 @@ func handleGetAllCommands(_ context.Context, _ []string, server types.EchoVault, return []byte(res), nil } -func handleCommandCount(_ context.Context, _ []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { +func handleCommandCount(params internal.HandlerFuncParams) ([]byte, error) { var count int - commands := server.GetAllCommands() + commands := params.GetAllCommands() for _, command := range commands { if command.SubCommands != nil && len(command.SubCommands) > 0 { for _, _ = range command.SubCommands { @@ -88,13 +86,13 @@ func handleCommandCount(_ context.Context, _ []string, server types.EchoVault, _ return []byte(fmt.Sprintf(":%d\r\n", count)), nil } -func handleCommandList(_ context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - switch len(cmd) { +func handleCommandList(params internal.HandlerFuncParams) ([]byte, error) { + switch len(params.Command) { case 2: // Command is COMMAND LIST var count int var res string - commands := server.GetAllCommands() + commands := params.GetAllCommands() for _, command := range commands { if command.SubCommands != nil && len(command.SubCommands) > 0 { for _, subcommand := range command.SubCommands { @@ -114,13 +112,13 @@ func handleCommandList(_ context.Context, cmd []string, server types.EchoVault, var count int var res string // Command has filter - if !strings.EqualFold("FILTERBY", cmd[2]) { - return nil, fmt.Errorf("expected FILTERBY, got %s", strings.ToUpper(cmd[2])) + if !strings.EqualFold("FILTERBY", params.Command[2]) { + return nil, fmt.Errorf("expected FILTERBY, got %s", strings.ToUpper(params.Command[2])) } - if strings.EqualFold("ACLCAT", cmd[3]) { + if strings.EqualFold("ACLCAT", params.Command[3]) { // ACL Category filter - commands := server.GetAllCommands() - category := strings.ToLower(cmd[4]) + commands := params.GetAllCommands() + category := strings.ToLower(params.Command[4]) for _, command := range commands { if command.SubCommands != nil && len(command.SubCommands) > 0 { for _, subcommand := range command.SubCommands { @@ -137,10 +135,10 @@ func handleCommandList(_ context.Context, cmd []string, server types.EchoVault, count += 1 } } - } else if strings.EqualFold("PATTERN", cmd[3]) { + } else if strings.EqualFold("PATTERN", params.Command[3]) { // Pattern filter - commands := server.GetAllCommands() - g := glob.MustCompile(cmd[4]) + commands := params.GetAllCommands() + g := glob.MustCompile(params.Command[4]) for _, command := range commands { if command.SubCommands != nil && len(command.SubCommands) > 0 { for _, subcommand := range command.SubCommands { @@ -157,10 +155,10 @@ func handleCommandList(_ context.Context, cmd []string, server types.EchoVault, count += 1 } } - } else if strings.EqualFold("MODULE", cmd[3]) { + } else if strings.EqualFold("MODULE", params.Command[3]) { // Module filter - commands := server.GetAllCommands() - module := strings.ToLower(cmd[4]) + commands := params.GetAllCommands() + module := strings.ToLower(params.Command[4]) for _, command := range commands { if command.SubCommands != nil && len(command.SubCommands) > 0 { for _, subcommand := range command.SubCommands { @@ -178,7 +176,7 @@ func handleCommandList(_ context.Context, cmd []string, server types.EchoVault, } } } else { - return nil, fmt.Errorf("expected filter to be ACLCAT or PATTERN, got %s", strings.ToUpper(cmd[3])) + return nil, fmt.Errorf("expected filter to be ACLCAT or PATTERN, got %s", strings.ToUpper(params.Command[3])) } res = fmt.Sprintf("*%d\r\n%s", count, res) return []byte(res), nil @@ -187,20 +185,20 @@ func handleCommandList(_ context.Context, cmd []string, server types.EchoVault, } } -func handleCommandDocs(_ context.Context, _ []string, _ types.EchoVault, _ *net.Conn) ([]byte, error) { +func handleCommandDocs(params internal.HandlerFuncParams) ([]byte, error) { return []byte("*0\r\n"), nil } -func Commands() []types.Command { - return []types.Command{ +func Commands() []internal.Command { + return []internal.Command{ { Command: "commands", Module: constants.AdminModule, Categories: []string{constants.AdminCategory, constants.SlowCategory}, Description: "Get a list of all the commands in available on the echovault with categories and descriptions", Sync: false, - KeyExtractionFunc: func(cmd []string) (types.AccessKeys, error) { - return types.AccessKeys{ + KeyExtractionFunc: func(cmd []string) (internal.AccessKeys, error) { + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: make([]string, 0), @@ -214,22 +212,22 @@ func Commands() []types.Command { Categories: []string{}, Description: "Commands pertaining to echovault commands", Sync: false, - KeyExtractionFunc: func(cmd []string) (types.AccessKeys, error) { - return types.AccessKeys{ + KeyExtractionFunc: func(cmd []string) (internal.AccessKeys, error) { + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: make([]string, 0), }, nil }, - SubCommands: []types.SubCommand{ + SubCommands: []internal.SubCommand{ { Command: "docs", Module: constants.AdminModule, Categories: []string{constants.SlowCategory, constants.ConnectionCategory}, Description: "Get command documentation", Sync: false, - KeyExtractionFunc: func(cmd []string) (types.AccessKeys, error) { - return types.AccessKeys{ + KeyExtractionFunc: func(cmd []string) (internal.AccessKeys, error) { + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: make([]string, 0), @@ -243,8 +241,8 @@ func Commands() []types.Command { Categories: []string{constants.SlowCategory}, Description: "Get the dumber of commands in the echovault", Sync: false, - KeyExtractionFunc: func(cmd []string) (types.AccessKeys, error) { - return types.AccessKeys{ + KeyExtractionFunc: func(cmd []string) (internal.AccessKeys, error) { + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: make([]string, 0), @@ -259,8 +257,8 @@ func Commands() []types.Command { Description: `(COMMAND LIST [FILTERBY ]) Get the list of command names. Allows for filtering by ACL category or glob pattern.`, Sync: false, - KeyExtractionFunc: func(cmd []string) (types.AccessKeys, error) { - return types.AccessKeys{ + KeyExtractionFunc: func(cmd []string) (internal.AccessKeys, error) { + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: make([]string, 0), @@ -276,15 +274,15 @@ Allows for filtering by ACL category or glob pattern.`, Categories: []string{constants.AdminCategory, constants.SlowCategory, constants.DangerousCategory}, Description: "(SAVE) Trigger a snapshot save", Sync: true, - KeyExtractionFunc: func(cmd []string) (types.AccessKeys, error) { - return types.AccessKeys{ + KeyExtractionFunc: func(cmd []string) (internal.AccessKeys, error) { + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: make([]string, 0), }, nil }, - HandlerFunc: func(ctx context.Context, cmd []string, server types.EchoVault, conn *net.Conn) ([]byte, error) { - if err := server.TakeSnapshot(); err != nil { + HandlerFunc: func(params internal.HandlerFuncParams) ([]byte, error) { + if err := params.TakeSnapshot(); err != nil { return nil, err } return []byte(constants.OkResponse), nil @@ -296,15 +294,15 @@ Allows for filtering by ACL category or glob pattern.`, Categories: []string{constants.AdminCategory, constants.FastCategory, constants.DangerousCategory}, Description: "(LASTSAVE) Get unix timestamp for the latest snapshot in milliseconds.", Sync: false, - KeyExtractionFunc: func(cmd []string) (types.AccessKeys, error) { - return types.AccessKeys{ + KeyExtractionFunc: func(cmd []string) (internal.AccessKeys, error) { + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: make([]string, 0), }, nil }, - HandlerFunc: func(ctx context.Context, cmd []string, server types.EchoVault, conn *net.Conn) ([]byte, error) { - msec := server.GetLatestSnapshotTime() + HandlerFunc: func(params internal.HandlerFuncParams) ([]byte, error) { + msec := params.GetLatestSnapshotTime() if msec == 0 { return nil, errors.New("no snapshot") } @@ -317,15 +315,15 @@ Allows for filtering by ACL category or glob pattern.`, Categories: []string{constants.AdminCategory, constants.SlowCategory, constants.DangerousCategory}, Description: "(REWRITEAOF) Trigger re-writing of append process", Sync: false, - KeyExtractionFunc: func(cmd []string) (types.AccessKeys, error) { - return types.AccessKeys{ + KeyExtractionFunc: func(cmd []string) (internal.AccessKeys, error) { + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: make([]string, 0), }, nil }, - HandlerFunc: func(ctx context.Context, cmd []string, server types.EchoVault, conn *net.Conn) ([]byte, error) { - if err := server.RewriteAOF(); err != nil { + HandlerFunc: func(params internal.HandlerFuncParams) ([]byte, error) { + if err := params.RewriteAOF(); err != nil { return nil, err } return []byte(constants.OkResponse), nil diff --git a/pkg/modules/connection/commands.go b/internal/modules/connection/commands.go similarity index 73% rename from pkg/modules/connection/commands.go rename to internal/modules/connection/commands.go index f9eac58b..d3ddf334 100644 --- a/pkg/modules/connection/commands.go +++ b/internal/modules/connection/commands.go @@ -15,35 +15,33 @@ package connection import ( - "context" "errors" "fmt" + "github.com/echovault/echovault/internal" "github.com/echovault/echovault/pkg/constants" - "github.com/echovault/echovault/pkg/types" - "net" ) -func handlePing(ctx context.Context, cmd []string, server types.EchoVault, conn *net.Conn) ([]byte, error) { - switch len(cmd) { +func handlePing(params internal.HandlerFuncParams) ([]byte, error) { + switch len(params.Command) { default: return nil, errors.New(constants.WrongArgsResponse) case 1: return []byte("+PONG\r\n"), nil case 2: - return []byte(fmt.Sprintf("$%d\r\n%s\r\n", len(cmd[1]), cmd[1])), nil + return []byte(fmt.Sprintf("$%d\r\n%s\r\n", len(params.Command[1]), params.Command[1])), nil } } -func Commands() []types.Command { - return []types.Command{ +func Commands() []internal.Command { + return []internal.Command{ { - Command: "connection", + Command: "ping", Module: constants.ConnectionModule, Categories: []string{constants.FastCategory, constants.ConnectionCategory}, Description: "(PING [value]) Ping the echovault. If a value is provided, the value will be echoed.", Sync: false, - KeyExtractionFunc: func(cmd []string) (types.AccessKeys, error) { - return types.AccessKeys{ + KeyExtractionFunc: func(cmd []string) (internal.AccessKeys, error) { + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: make([]string, 0), diff --git a/pkg/modules/generic/commands.go b/internal/modules/generic/commands.go similarity index 71% rename from pkg/modules/generic/commands.go rename to internal/modules/generic/commands.go index a9e454d7..49c0bbb4 100644 --- a/pkg/modules/generic/commands.go +++ b/internal/modules/generic/commands.go @@ -15,14 +15,11 @@ package generic import ( - "context" "errors" "fmt" "github.com/echovault/echovault/internal" "github.com/echovault/echovault/pkg/constants" - "github.com/echovault/echovault/pkg/types" "log" - "net" "strconv" "strings" "time" @@ -33,73 +30,73 @@ type KeyObject struct { locked bool } -func handleSet(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := setKeyFunc(cmd) +func handleSet(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := setKeyFunc(params.Command) if err != nil { return nil, err } key := keys.WriteKeys[0] - value := cmd[2] + value := params.Command[2] res := []byte(constants.OkResponse) - clock := server.GetClock() + clock := params.GetClock() - params, err := getSetCommandParams(clock, cmd[3:], SetParams{}) + options, err := getSetCommandOptions(clock, params.Command[3:], SetOptions{}) if err != nil { return nil, err } // If GET is provided, the response should be the current stored value. // If there's no current value, then the response should be nil. - if params.get { - if !server.KeyExists(ctx, key) { + if options.get { + if !params.KeyExists(params.Context, key) { res = []byte("$-1\r\n") } else { - res = []byte(fmt.Sprintf("+%v\r\n", server.GetValue(ctx, key))) + res = []byte(fmt.Sprintf("+%v\r\n", params.GetValue(params.Context, key))) } } - if "xx" == strings.ToLower(params.exists) { + if "xx" == strings.ToLower(options.exists) { // If XX is specified, make sure the key exists. - if !server.KeyExists(ctx, key) { + if !params.KeyExists(params.Context, key) { return nil, fmt.Errorf("key %s does not exist", key) } - _, err = server.KeyLock(ctx, key) - } else if "nx" == strings.ToLower(params.exists) { + _, err = params.KeyLock(params.Context, key) + } else if "nx" == strings.ToLower(options.exists) { // If NX is specified, make sure that the key does not currently exist. - if server.KeyExists(ctx, key) { + if params.KeyExists(params.Context, key) { return nil, fmt.Errorf("key %s already exists", key) } - _, err = server.CreateKeyAndLock(ctx, key) + _, err = params.CreateKeyAndLock(params.Context, key) } else { // Neither XX not NX are specified, lock or create the lock - if !server.KeyExists(ctx, key) { + if !params.KeyExists(params.Context, key) { // Key does not exist, create it - _, err = server.CreateKeyAndLock(ctx, key) + _, err = params.CreateKeyAndLock(params.Context, key) } else { // Key exists, acquire the lock - _, err = server.KeyLock(ctx, key) + _, err = params.KeyLock(params.Context, key) } } if err != nil { return nil, err } - defer server.KeyUnlock(ctx, key) + defer params.KeyUnlock(params.Context, key) - if err = server.SetValue(ctx, key, internal.AdaptType(value)); err != nil { + if err = params.SetValue(params.Context, key, internal.AdaptType(value)); err != nil { return nil, err } // If expiresAt is set, set the key's expiry time as well - if params.expireAt != nil { - server.SetExpiry(ctx, key, params.expireAt.(time.Time), false) + if options.expireAt != nil { + params.SetExpiry(params.Context, key, options.expireAt.(time.Time), false) } return res, nil } -func handleMSet(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - _, err := msetKeyFunc(cmd) +func handleMSet(params internal.HandlerFuncParams) ([]byte, error) { + _, err := msetKeyFunc(params.Command) if err != nil { return nil, err } @@ -110,7 +107,7 @@ func handleMSet(ctx context.Context, cmd []string, server types.EchoVault, _ *ne defer func() { for k, v := range entries { if v.locked { - server.KeyUnlock(ctx, k) + params.KeyUnlock(params.Context, k) entries[k] = KeyObject{ value: v.value, locked: false, @@ -120,10 +117,10 @@ func handleMSet(ctx context.Context, cmd []string, server types.EchoVault, _ *ne }() // Extract all the key/value pairs - for i, key := range cmd[1:] { + for i, key := range params.Command[1:] { if i%2 == 0 { entries[key] = KeyObject{ - value: internal.AdaptType(cmd[1:][i+1]), + value: internal.AdaptType(params.Command[1:][i+1]), locked: false, } } @@ -132,14 +129,14 @@ func handleMSet(ctx context.Context, cmd []string, server types.EchoVault, _ *ne // Acquire all the locks for each key first // If any key cannot be acquired, abandon transaction and release all currently held keys for k, v := range entries { - if server.KeyExists(ctx, k) { - if _, err := server.KeyLock(ctx, k); err != nil { + if params.KeyExists(params.Context, k) { + if _, err := params.KeyLock(params.Context, k); err != nil { return nil, err } entries[k] = KeyObject{value: v.value, locked: true} continue } - if _, err := server.CreateKeyAndLock(ctx, k); err != nil { + if _, err := params.CreateKeyAndLock(params.Context, k); err != nil { return nil, err } entries[k] = KeyObject{value: v.value, locked: true} @@ -147,7 +144,7 @@ func handleMSet(ctx context.Context, cmd []string, server types.EchoVault, _ *ne // Set all the values for k, v := range entries { - if err := server.SetValue(ctx, k, v.value); err != nil { + if err := params.SetValue(params.Context, k, v.value); err != nil { return nil, err } } @@ -155,30 +152,30 @@ func handleMSet(ctx context.Context, cmd []string, server types.EchoVault, _ *ne return []byte(constants.OkResponse), nil } -func handleGet(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := getKeyFunc(cmd) +func handleGet(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := getKeyFunc(params.Command) if err != nil { return nil, err } key := keys.ReadKeys[0] - if !server.KeyExists(ctx, key) { + if !params.KeyExists(params.Context, key) { return []byte("$-1\r\n"), nil } - _, err = server.KeyRLock(ctx, key) + _, err = params.KeyRLock(params.Context, key) if err != nil { return nil, err } - defer server.KeyRUnlock(ctx, key) + defer params.KeyRUnlock(params.Context, key) - value := server.GetValue(ctx, key) + value := params.GetValue(params.Context, key) return []byte(fmt.Sprintf("+%v\r\n", value)), nil } -func handleMGet(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := mgetKeyFunc(cmd) +func handleMGet(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := mgetKeyFunc(params.Command) if err != nil { return nil, err } @@ -191,8 +188,8 @@ func handleMGet(ctx context.Context, cmd []string, server types.EchoVault, _ *ne // Skip if we have already locked this key continue } - if server.KeyExists(ctx, key) { - _, err = server.KeyRLock(ctx, key) + if params.KeyExists(params.Context, key) { + _, err = params.KeyRLock(params.Context, key) if err != nil { return nil, fmt.Errorf("could not obtain lock for %s key", key) } @@ -204,19 +201,19 @@ func handleMGet(ctx context.Context, cmd []string, server types.EchoVault, _ *ne defer func() { for key, locked := range locks { if locked { - server.KeyRUnlock(ctx, key) + params.KeyRUnlock(params.Context, key) locks[key] = false } } }() for key, _ := range locks { - values[key] = fmt.Sprintf("%v", server.GetValue(ctx, key)) + values[key] = fmt.Sprintf("%v", params.GetValue(params.Context, key)) } - bytes := []byte(fmt.Sprintf("*%d\r\n", len(cmd[1:]))) + bytes := []byte(fmt.Sprintf("*%d\r\n", len(params.Command[1:]))) - for _, key := range cmd[1:] { + for _, key := range params.Command[1:] { if values[key] == "" { bytes = append(bytes, []byte("$-1\r\n")...) continue @@ -227,14 +224,14 @@ func handleMGet(ctx context.Context, cmd []string, server types.EchoVault, _ *ne return bytes, nil } -func handleDel(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := delKeyFunc(cmd) +func handleDel(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := delKeyFunc(params.Command) if err != nil { return nil, err } count := 0 for _, key := range keys.WriteKeys { - err = server.DeleteKey(ctx, key) + err = params.DeleteKey(params.Context, key) if err != nil { log.Printf("could not delete key %s due to error: %+v\n", key, err) continue @@ -244,91 +241,91 @@ func handleDel(ctx context.Context, cmd []string, server types.EchoVault, _ *net return []byte(fmt.Sprintf(":%d\r\n", count)), nil } -func handlePersist(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := persistKeyFunc(cmd) +func handlePersist(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := persistKeyFunc(params.Command) if err != nil { return nil, err } key := keys.WriteKeys[0] - if !server.KeyExists(ctx, key) { + if !params.KeyExists(params.Context, key) { return []byte(":0\r\n"), nil } - if _, err = server.KeyLock(ctx, key); err != nil { + if _, err = params.KeyLock(params.Context, key); err != nil { return nil, err } - defer server.KeyUnlock(ctx, key) + defer params.KeyUnlock(params.Context, key) - expireAt := server.GetExpiry(ctx, key) + expireAt := params.GetExpiry(params.Context, key) if expireAt == (time.Time{}) { return []byte(":0\r\n"), nil } - server.SetExpiry(ctx, key, time.Time{}, false) + params.SetExpiry(params.Context, key, time.Time{}, false) return []byte(":1\r\n"), nil } -func handleExpireTime(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := expireTimeKeyFunc(cmd) +func handleExpireTime(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := expireTimeKeyFunc(params.Command) if err != nil { return nil, err } key := keys.ReadKeys[0] - if !server.KeyExists(ctx, key) { + if !params.KeyExists(params.Context, key) { return []byte(":-2\r\n"), nil } - if _, err = server.KeyRLock(ctx, key); err != nil { + if _, err = params.KeyRLock(params.Context, key); err != nil { return nil, err } - defer server.KeyRUnlock(ctx, key) + defer params.KeyRUnlock(params.Context, key) - expireAt := server.GetExpiry(ctx, key) + expireAt := params.GetExpiry(params.Context, key) if expireAt == (time.Time{}) { return []byte(":-1\r\n"), nil } t := expireAt.Unix() - if strings.ToLower(cmd[0]) == "pexpiretime" { + if strings.ToLower(params.Command[0]) == "pexpiretime" { t = expireAt.UnixMilli() } return []byte(fmt.Sprintf(":%d\r\n", t)), nil } -func handleTTL(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := ttlKeyFunc(cmd) +func handleTTL(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := ttlKeyFunc(params.Command) if err != nil { return nil, err } key := keys.ReadKeys[0] - clock := server.GetClock() + clock := params.GetClock() - if !server.KeyExists(ctx, key) { + if !params.KeyExists(params.Context, key) { return []byte(":-2\r\n"), nil } - if _, err = server.KeyRLock(ctx, key); err != nil { + if _, err = params.KeyRLock(params.Context, key); err != nil { return nil, err } - defer server.KeyRUnlock(ctx, key) + defer params.KeyRUnlock(params.Context, key) - expireAt := server.GetExpiry(ctx, key) + expireAt := params.GetExpiry(params.Context, key) if expireAt == (time.Time{}) { return []byte(":-1\r\n"), nil } t := expireAt.Unix() - clock.Now().Unix() - if strings.ToLower(cmd[0]) == "pttl" { + if strings.ToLower(params.Command[0]) == "pttl" { t = expireAt.UnixMilli() - clock.Now().UnixMilli() } @@ -339,8 +336,8 @@ func handleTTL(ctx context.Context, cmd []string, server types.EchoVault, _ *net return []byte(fmt.Sprintf(":%d\r\n", t)), nil } -func handleExpire(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := expireKeyFunc(cmd) +func handleExpire(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := expireKeyFunc(params.Command) if err != nil { return nil, err } @@ -348,42 +345,42 @@ func handleExpire(ctx context.Context, cmd []string, server types.EchoVault, _ * key := keys.WriteKeys[0] // Extract time - n, err := strconv.ParseInt(cmd[2], 10, 64) + n, err := strconv.ParseInt(params.Command[2], 10, 64) if err != nil { return nil, errors.New("expire time must be integer") } - expireAt := server.GetClock().Now().Add(time.Duration(n) * time.Second) - if strings.ToLower(cmd[0]) == "pexpire" { - expireAt = server.GetClock().Now().Add(time.Duration(n) * time.Millisecond) + expireAt := params.GetClock().Now().Add(time.Duration(n) * time.Second) + if strings.ToLower(params.Command[0]) == "pexpire" { + expireAt = params.GetClock().Now().Add(time.Duration(n) * time.Millisecond) } - if !server.KeyExists(ctx, key) { + if !params.KeyExists(params.Context, key) { return []byte(":0\r\n"), nil } - if _, err = server.KeyLock(ctx, key); err != nil { + if _, err = params.KeyLock(params.Context, key); err != nil { return nil, err } - defer server.KeyUnlock(ctx, key) + defer params.KeyUnlock(params.Context, key) - if len(cmd) == 3 { - server.SetExpiry(ctx, key, expireAt, true) + if len(params.Command) == 3 { + params.SetExpiry(params.Context, key, expireAt, true) return []byte(":1\r\n"), nil } - currentExpireAt := server.GetExpiry(ctx, key) + currentExpireAt := params.GetExpiry(params.Context, key) - switch strings.ToLower(cmd[3]) { + switch strings.ToLower(params.Command[3]) { case "nx": if currentExpireAt != (time.Time{}) { return []byte(":0\r\n"), nil } - server.SetExpiry(ctx, key, expireAt, false) + params.SetExpiry(params.Context, key, expireAt, false) case "xx": if currentExpireAt == (time.Time{}) { return []byte(":0\r\n"), nil } - server.SetExpiry(ctx, key, expireAt, false) + params.SetExpiry(params.Context, key, expireAt, false) case "gt": if currentExpireAt == (time.Time{}) { return []byte(":0\r\n"), nil @@ -391,24 +388,24 @@ func handleExpire(ctx context.Context, cmd []string, server types.EchoVault, _ * if expireAt.Before(currentExpireAt) { return []byte(":0\r\n"), nil } - server.SetExpiry(ctx, key, expireAt, false) + params.SetExpiry(params.Context, key, expireAt, false) case "lt": if currentExpireAt != (time.Time{}) { if currentExpireAt.Before(expireAt) { return []byte(":0\r\n"), nil } - server.SetExpiry(ctx, key, expireAt, false) + params.SetExpiry(params.Context, key, expireAt, false) } - server.SetExpiry(ctx, key, expireAt, false) + params.SetExpiry(params.Context, key, expireAt, false) default: - return nil, fmt.Errorf("unknown option %s", strings.ToUpper(cmd[3])) + return nil, fmt.Errorf("unknown option %s", strings.ToUpper(params.Command[3])) } return []byte(":1\r\n"), nil } -func handleExpireAt(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := expireKeyFunc(cmd) +func handleExpireAt(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := expireKeyFunc(params.Command) if err != nil { return nil, err } @@ -416,42 +413,42 @@ func handleExpireAt(ctx context.Context, cmd []string, server types.EchoVault, _ key := keys.WriteKeys[0] // Extract time - n, err := strconv.ParseInt(cmd[2], 10, 64) + n, err := strconv.ParseInt(params.Command[2], 10, 64) if err != nil { return nil, errors.New("expire time must be integer") } expireAt := time.Unix(n, 0) - if strings.ToLower(cmd[0]) == "pexpireat" { + if strings.ToLower(params.Command[0]) == "pexpireat" { expireAt = time.UnixMilli(n) } - if !server.KeyExists(ctx, key) { + if !params.KeyExists(params.Context, key) { return []byte(":0\r\n"), nil } - if _, err = server.KeyLock(ctx, key); err != nil { + if _, err = params.KeyLock(params.Context, key); err != nil { return nil, err } - defer server.KeyUnlock(ctx, key) + defer params.KeyUnlock(params.Context, key) - if len(cmd) == 3 { - server.SetExpiry(ctx, key, expireAt, true) + if len(params.Command) == 3 { + params.SetExpiry(params.Context, key, expireAt, true) return []byte(":1\r\n"), nil } - currentExpireAt := server.GetExpiry(ctx, key) + currentExpireAt := params.GetExpiry(params.Context, key) - switch strings.ToLower(cmd[3]) { + switch strings.ToLower(params.Command[3]) { case "nx": if currentExpireAt != (time.Time{}) { return []byte(":0\r\n"), nil } - server.SetExpiry(ctx, key, expireAt, false) + params.SetExpiry(params.Context, key, expireAt, false) case "xx": if currentExpireAt == (time.Time{}) { return []byte(":0\r\n"), nil } - server.SetExpiry(ctx, key, expireAt, false) + params.SetExpiry(params.Context, key, expireAt, false) case "gt": if currentExpireAt == (time.Time{}) { return []byte(":0\r\n"), nil @@ -459,24 +456,24 @@ func handleExpireAt(ctx context.Context, cmd []string, server types.EchoVault, _ if expireAt.Before(currentExpireAt) { return []byte(":0\r\n"), nil } - server.SetExpiry(ctx, key, expireAt, false) + params.SetExpiry(params.Context, key, expireAt, false) case "lt": if currentExpireAt != (time.Time{}) { if currentExpireAt.Before(expireAt) { return []byte(":0\r\n"), nil } - server.SetExpiry(ctx, key, expireAt, false) + params.SetExpiry(params.Context, key, expireAt, false) } - server.SetExpiry(ctx, key, expireAt, false) + params.SetExpiry(params.Context, key, expireAt, false) default: - return nil, fmt.Errorf("unknown option %s", strings.ToUpper(cmd[3])) + return nil, fmt.Errorf("unknown option %s", strings.ToUpper(params.Command[3])) } return []byte(":1\r\n"), nil } -func Commands() []types.Command { - return []types.Command{ +func Commands() []internal.Command { + return []internal.Command{ { Command: "set", Module: constants.GenericModule, diff --git a/pkg/modules/generic/key_funcs.go b/internal/modules/generic/key_funcs.go similarity index 54% rename from pkg/modules/generic/key_funcs.go rename to internal/modules/generic/key_funcs.go index 5584af57..e9a42d95 100644 --- a/pkg/modules/generic/key_funcs.go +++ b/internal/modules/generic/key_funcs.go @@ -16,24 +16,24 @@ package generic import ( "errors" + "github.com/echovault/echovault/internal" "github.com/echovault/echovault/pkg/constants" - "github.com/echovault/echovault/pkg/types" ) -func setKeyFunc(cmd []string) (types.AccessKeys, error) { +func setKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) < 3 || len(cmd) > 7 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: cmd[1:2], }, nil } -func msetKeyFunc(cmd []string) (types.AccessKeys, error) { +func msetKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd[1:])%2 != 0 { - return types.AccessKeys{}, errors.New("each key must be paired with a value") + return internal.AccessKeys{}, errors.New("each key must be paired with a value") } var keys []string for i, key := range cmd[1:] { @@ -41,95 +41,95 @@ func msetKeyFunc(cmd []string) (types.AccessKeys, error) { keys = append(keys, key) } } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: keys, }, nil } -func getKeyFunc(cmd []string) (types.AccessKeys, error) { +func getKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) != 2 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: cmd[1:], WriteKeys: make([]string, 0), }, nil } -func mgetKeyFunc(cmd []string) (types.AccessKeys, error) { +func mgetKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) < 2 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: cmd[1:], WriteKeys: make([]string, 0), }, nil } -func delKeyFunc(cmd []string) (types.AccessKeys, error) { +func delKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) < 2 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: cmd[1:], }, nil } -func persistKeyFunc(cmd []string) (types.AccessKeys, error) { +func persistKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) != 2 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: cmd[1:], }, nil } -func expireTimeKeyFunc(cmd []string) (types.AccessKeys, error) { +func expireTimeKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) != 2 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: cmd[1:], WriteKeys: make([]string, 0), }, nil } -func ttlKeyFunc(cmd []string) (types.AccessKeys, error) { +func ttlKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) != 2 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: cmd[1:], WriteKeys: make([]string, 0), }, nil } -func expireKeyFunc(cmd []string) (types.AccessKeys, error) { +func expireKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) < 3 || len(cmd) > 4 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: cmd[1:2], }, nil } -func expireAtKeyFunc(cmd []string) (types.AccessKeys, error) { +func expireAtKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) < 3 || len(cmd) > 4 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: cmd[1:2], diff --git a/internal/modules/generic/utils.go b/internal/modules/generic/utils.go new file mode 100644 index 00000000..3e5b215f --- /dev/null +++ b/internal/modules/generic/utils.go @@ -0,0 +1,118 @@ +// 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 generic + +import ( + "errors" + "fmt" + "github.com/echovault/echovault/internal/clock" + "strconv" + "strings" + "time" +) + +type SetOptions struct { + exists string + get bool + expireAt interface{} // Exact expireAt time un unix milliseconds +} + +func getSetCommandOptions(clock clock.Clock, cmd []string, options SetOptions) (SetOptions, error) { + if len(cmd) == 0 { + return options, nil + } + switch strings.ToLower(cmd[0]) { + case "get": + options.get = true + return getSetCommandOptions(clock, cmd[1:], options) + + case "nx": + if options.exists != "" { + return SetOptions{}, fmt.Errorf("cannot specify NX when %s is already specified", strings.ToUpper(options.exists)) + } + options.exists = "NX" + return getSetCommandOptions(clock, cmd[1:], options) + + case "xx": + if options.exists != "" { + return SetOptions{}, fmt.Errorf("cannot specify XX when %s is already specified", strings.ToUpper(options.exists)) + } + options.exists = "XX" + return getSetCommandOptions(clock, cmd[1:], options) + + case "ex": + if len(cmd) < 2 { + return SetOptions{}, errors.New("seconds value required after EX") + } + if options.expireAt != nil { + return SetOptions{}, errors.New("cannot specify EX when expiry time is already set") + } + secondsStr := cmd[1] + seconds, err := strconv.ParseInt(secondsStr, 10, 64) + if err != nil { + return SetOptions{}, errors.New("seconds value should be an integer") + } + options.expireAt = clock.Now().Add(time.Duration(seconds) * time.Second) + return getSetCommandOptions(clock, cmd[2:], options) + + case "px": + if len(cmd) < 2 { + return SetOptions{}, errors.New("milliseconds value required after PX") + } + if options.expireAt != nil { + return SetOptions{}, errors.New("cannot specify PX when expiry time is already set") + } + millisecondsStr := cmd[1] + milliseconds, err := strconv.ParseInt(millisecondsStr, 10, 64) + if err != nil { + return SetOptions{}, errors.New("milliseconds value should be an integer") + } + options.expireAt = clock.Now().Add(time.Duration(milliseconds) * time.Millisecond) + return getSetCommandOptions(clock, cmd[2:], options) + + case "exat": + if len(cmd) < 2 { + return SetOptions{}, errors.New("seconds value required after EXAT") + } + if options.expireAt != nil { + return SetOptions{}, errors.New("cannot specify EXAT when expiry time is already set") + } + secondsStr := cmd[1] + seconds, err := strconv.ParseInt(secondsStr, 10, 64) + if err != nil { + return SetOptions{}, errors.New("seconds value should be an integer") + } + options.expireAt = time.Unix(seconds, 0) + return getSetCommandOptions(clock, cmd[2:], options) + + case "pxat": + if len(cmd) < 2 { + return SetOptions{}, errors.New("milliseconds value required after PXAT") + } + if options.expireAt != nil { + return SetOptions{}, errors.New("cannot specify PXAT when expiry time is already set") + } + millisecondsStr := cmd[1] + milliseconds, err := strconv.ParseInt(millisecondsStr, 10, 64) + if err != nil { + return SetOptions{}, errors.New("milliseconds value should be an integer") + } + options.expireAt = time.UnixMilli(milliseconds) + return getSetCommandOptions(clock, cmd[2:], options) + + default: + return SetOptions{}, fmt.Errorf("unknown option %s for set command", strings.ToUpper(cmd[0])) + } +} diff --git a/pkg/modules/hash/commands.go b/internal/modules/hash/commands.go similarity index 72% rename from pkg/modules/hash/commands.go rename to internal/modules/hash/commands.go index 3399db31..5a679d2e 100644 --- a/pkg/modules/hash/commands.go +++ b/internal/modules/hash/commands.go @@ -15,21 +15,18 @@ package hash import ( - "context" "errors" "fmt" "github.com/echovault/echovault/internal" "github.com/echovault/echovault/pkg/constants" - "github.com/echovault/echovault/pkg/types" "math/rand" - "net" "slices" "strconv" "strings" ) -func handleHSET(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := hsetKeyFunc(cmd) +func handleHSET(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := hsetKeyFunc(params.Command) if err != nil { return nil, err } @@ -37,39 +34,39 @@ func handleHSET(ctx context.Context, cmd []string, server types.EchoVault, _ *ne key := keys.WriteKeys[0] entries := make(map[string]interface{}) - if len(cmd[2:])%2 != 0 { + if len(params.Command[2:])%2 != 0 { return nil, errors.New("each field must have a corresponding value") } - for i := 2; i <= len(cmd)-2; i += 2 { - entries[cmd[i]] = internal.AdaptType(cmd[i+1]) + for i := 2; i <= len(params.Command)-2; i += 2 { + entries[params.Command[i]] = internal.AdaptType(params.Command[i+1]) } - if !server.KeyExists(ctx, key) { - _, err = server.CreateKeyAndLock(ctx, key) + if !params.KeyExists(params.Context, key) { + _, err = params.CreateKeyAndLock(params.Context, key) if err != nil { return nil, err } - defer server.KeyUnlock(ctx, key) - if err = server.SetValue(ctx, key, entries); err != nil { + defer params.KeyUnlock(params.Context, key) + if err = params.SetValue(params.Context, key, entries); err != nil { return nil, err } return []byte(fmt.Sprintf(":%d\r\n", len(entries))), nil } - if _, err = server.KeyLock(ctx, key); err != nil { + if _, err = params.KeyLock(params.Context, key); err != nil { return nil, err } - defer server.KeyUnlock(ctx, key) + defer params.KeyUnlock(params.Context, key) - hash, ok := server.GetValue(ctx, key).(map[string]interface{}) + hash, ok := params.GetValue(params.Context, key).(map[string]interface{}) if !ok { return nil, fmt.Errorf("value at %s is not a hash", key) } count := 0 for field, value := range entries { - if strings.EqualFold(cmd[0], "hsetnx") { + if strings.EqualFold(params.Command[0], "hsetnx") { if hash[field] == nil { hash[field] = value count += 1 @@ -79,32 +76,32 @@ func handleHSET(ctx context.Context, cmd []string, server types.EchoVault, _ *ne hash[field] = value count += 1 } - if err = server.SetValue(ctx, key, hash); err != nil { + if err = params.SetValue(params.Context, key, hash); err != nil { return nil, err } return []byte(fmt.Sprintf(":%d\r\n", count)), nil } -func handleHGET(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := hgetKeyFunc(cmd) +func handleHGET(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := hgetKeyFunc(params.Command) if err != nil { return nil, err } key := keys.ReadKeys[0] - fields := cmd[2:] + fields := params.Command[2:] - if !server.KeyExists(ctx, key) { + if !params.KeyExists(params.Context, key) { return []byte("$-1\r\n"), nil } - if _, err = server.KeyRLock(ctx, key); err != nil { + if _, err = params.KeyRLock(params.Context, key); err != nil { return nil, err } - defer server.KeyRUnlock(ctx, key) + defer params.KeyRUnlock(params.Context, key) - hash, ok := server.GetValue(ctx, key).(map[string]interface{}) + hash, ok := params.GetValue(params.Context, key).(map[string]interface{}) if !ok { return nil, fmt.Errorf("value at %s is not a hash", key) } @@ -137,25 +134,25 @@ func handleHGET(ctx context.Context, cmd []string, server types.EchoVault, _ *ne return []byte(res), nil } -func handleHSTRLEN(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := hstrlenKeyFunc(cmd) +func handleHSTRLEN(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := hstrlenKeyFunc(params.Command) if err != nil { return nil, err } key := keys.ReadKeys[0] - fields := cmd[2:] + fields := params.Command[2:] - if !server.KeyExists(ctx, key) { + if !params.KeyExists(params.Context, key) { return []byte("$-1\r\n"), nil } - if _, err = server.KeyRLock(ctx, key); err != nil { + if _, err = params.KeyRLock(params.Context, key); err != nil { return nil, err } - defer server.KeyRUnlock(ctx, key) + defer params.KeyRUnlock(params.Context, key) - hash, ok := server.GetValue(ctx, key).(map[string]interface{}) + hash, ok := params.GetValue(params.Context, key).(map[string]interface{}) if !ok { return nil, fmt.Errorf("value at %s is not a hash", key) } @@ -188,24 +185,24 @@ func handleHSTRLEN(ctx context.Context, cmd []string, server types.EchoVault, _ return []byte(res), nil } -func handleHVALS(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := hvalsKeyFunc(cmd) +func handleHVALS(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := hvalsKeyFunc(params.Command) if err != nil { return nil, err } key := keys.ReadKeys[0] - if !server.KeyExists(ctx, key) { + if !params.KeyExists(params.Context, key) { return []byte("*0\r\n"), nil } - if _, err = server.KeyRLock(ctx, key); err != nil { + if _, err = params.KeyRLock(params.Context, key); err != nil { return nil, err } - defer server.KeyRUnlock(ctx, key) + defer params.KeyRUnlock(params.Context, key) - hash, ok := server.GetValue(ctx, key).(map[string]interface{}) + hash, ok := params.GetValue(params.Context, key).(map[string]interface{}) if !ok { return nil, fmt.Errorf("value at %s is not a hash", key) } @@ -229,8 +226,8 @@ func handleHVALS(ctx context.Context, cmd []string, server types.EchoVault, _ *n return []byte(res), nil } -func handleHRANDFIELD(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := hrandfieldKeyFunc(cmd) +func handleHRANDFIELD(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := hrandfieldKeyFunc(params.Command) if err != nil { return nil, err } @@ -238,8 +235,8 @@ func handleHRANDFIELD(ctx context.Context, cmd []string, server types.EchoVault, key := keys.ReadKeys[0] count := 1 - if len(cmd) >= 3 { - c, err := strconv.Atoi(cmd[2]) + if len(params.Command) >= 3 { + c, err := strconv.Atoi(params.Command[2]) if err != nil { return nil, errors.New("count must be an integer") } @@ -250,24 +247,24 @@ func handleHRANDFIELD(ctx context.Context, cmd []string, server types.EchoVault, } withvalues := false - if len(cmd) == 4 { - if strings.EqualFold(cmd[3], "withvalues") { + if len(params.Command) == 4 { + if strings.EqualFold(params.Command[3], "withvalues") { withvalues = true } else { return nil, errors.New("result modifier must be withvalues") } } - if !server.KeyExists(ctx, key) { + if !params.KeyExists(params.Context, key) { return []byte("*0\r\n"), nil } - if _, err = server.KeyRLock(ctx, key); err != nil { + if _, err = params.KeyRLock(params.Context, key); err != nil { return nil, err } - defer server.KeyRUnlock(ctx, key) + defer params.KeyRUnlock(params.Context, key) - hash, ok := server.GetValue(ctx, key).(map[string]interface{}) + hash, ok := params.GetValue(params.Context, key).(map[string]interface{}) if !ok { return nil, fmt.Errorf("value at %s is not a hash", key) } @@ -345,24 +342,24 @@ func handleHRANDFIELD(ctx context.Context, cmd []string, server types.EchoVault, return []byte(res), nil } -func handleHLEN(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := hlenKeyFunc(cmd) +func handleHLEN(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := hlenKeyFunc(params.Command) if err != nil { return nil, err } key := keys.ReadKeys[0] - if !server.KeyExists(ctx, key) { + if !params.KeyExists(params.Context, key) { return []byte(":0\r\n"), nil } - if _, err = server.KeyRLock(ctx, key); err != nil { + if _, err = params.KeyRLock(params.Context, key); err != nil { return nil, err } - defer server.KeyRUnlock(ctx, key) + defer params.KeyRUnlock(params.Context, key) - hash, ok := server.GetValue(ctx, key).(map[string]interface{}) + hash, ok := params.GetValue(params.Context, key).(map[string]interface{}) if !ok { return nil, fmt.Errorf("value at %s is not a hash", key) } @@ -370,24 +367,24 @@ func handleHLEN(ctx context.Context, cmd []string, server types.EchoVault, _ *ne return []byte(fmt.Sprintf(":%d\r\n", len(hash))), nil } -func handleHKEYS(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := hkeysKeyFunc(cmd) +func handleHKEYS(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := hkeysKeyFunc(params.Command) if err != nil { return nil, err } key := keys.ReadKeys[0] - if !server.KeyExists(ctx, key) { + if !params.KeyExists(params.Context, key) { return []byte("*0\r\n"), nil } - if _, err = server.KeyRLock(ctx, key); err != nil { + if _, err = params.KeyRLock(params.Context, key); err != nil { return nil, err } - defer server.KeyRUnlock(ctx, key) + defer params.KeyRUnlock(params.Context, key) - hash, ok := server.GetValue(ctx, key).(map[string]interface{}) + hash, ok := params.GetValue(params.Context, key).(map[string]interface{}) if !ok { return nil, fmt.Errorf("value at %s is not a hash", key) } @@ -400,59 +397,59 @@ func handleHKEYS(ctx context.Context, cmd []string, server types.EchoVault, _ *n return []byte(res), nil } -func handleHINCRBY(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := hincrbyKeyFunc(cmd) +func handleHINCRBY(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := hincrbyKeyFunc(params.Command) if err != nil { return nil, err } key := keys.WriteKeys[0] - field := cmd[2] + field := params.Command[2] var intIncrement int var floatIncrement float64 - if strings.EqualFold(cmd[0], "hincrbyfloat") { - f, err := strconv.ParseFloat(cmd[3], 64) + if strings.EqualFold(params.Command[0], "hincrbyfloat") { + f, err := strconv.ParseFloat(params.Command[3], 64) if err != nil { return nil, errors.New("increment must be a float") } floatIncrement = f } else { - i, err := strconv.Atoi(cmd[3]) + i, err := strconv.Atoi(params.Command[3]) if err != nil { return nil, errors.New("increment must be an integer") } intIncrement = i } - if !server.KeyExists(ctx, key) { - if _, err := server.CreateKeyAndLock(ctx, key); err != nil { + if !params.KeyExists(params.Context, key) { + if _, err := params.CreateKeyAndLock(params.Context, key); err != nil { return nil, err } - defer server.KeyUnlock(ctx, key) + defer params.KeyUnlock(params.Context, key) hash := make(map[string]interface{}) - if strings.EqualFold(cmd[0], "hincrbyfloat") { + if strings.EqualFold(params.Command[0], "hincrbyfloat") { hash[field] = floatIncrement - if err = server.SetValue(ctx, key, hash); err != nil { + if err = params.SetValue(params.Context, key, hash); err != nil { return nil, err } return []byte(fmt.Sprintf("+%s\r\n", strconv.FormatFloat(floatIncrement, 'f', -1, 64))), nil } else { hash[field] = intIncrement - if err = server.SetValue(ctx, key, hash); err != nil { + if err = params.SetValue(params.Context, key, hash); err != nil { return nil, err } return []byte(fmt.Sprintf(":%d\r\n", intIncrement)), nil } } - if _, err := server.KeyLock(ctx, key); err != nil { + if _, err := params.KeyLock(params.Context, key); err != nil { return nil, err } - defer server.KeyUnlock(ctx, key) + defer params.KeyUnlock(params.Context, key) - hash, ok := server.GetValue(ctx, key).(map[string]interface{}) + hash, ok := params.GetValue(params.Context, key).(map[string]interface{}) if !ok { return nil, fmt.Errorf("value at %s is not a hash", key) } @@ -466,21 +463,21 @@ func handleHINCRBY(ctx context.Context, cmd []string, server types.EchoVault, _ return nil, fmt.Errorf("value at field %s is not a number", field) case int: i, _ := hash[field].(int) - if strings.EqualFold(cmd[0], "hincrbyfloat") { + if strings.EqualFold(params.Command[0], "hincrbyfloat") { hash[field] = float64(i) + floatIncrement } else { hash[field] = i + intIncrement } case float64: f, _ := hash[field].(float64) - if strings.EqualFold(cmd[0], "hincrbyfloat") { + if strings.EqualFold(params.Command[0], "hincrbyfloat") { hash[field] = f + floatIncrement } else { hash[field] = f + float64(intIncrement) } } - if err = server.SetValue(ctx, key, hash); err != nil { + if err = params.SetValue(params.Context, key, hash); err != nil { return nil, err } @@ -492,24 +489,24 @@ func handleHINCRBY(ctx context.Context, cmd []string, server types.EchoVault, _ return []byte(fmt.Sprintf(":%d\r\n", i)), nil } -func handleHGETALL(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := hgetallKeyFunc(cmd) +func handleHGETALL(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := hgetallKeyFunc(params.Command) if err != nil { return nil, err } key := keys.ReadKeys[0] - if !server.KeyExists(ctx, key) { + if !params.KeyExists(params.Context, key) { return []byte("*0\r\n"), nil } - if _, err = server.KeyRLock(ctx, key); err != nil { + if _, err = params.KeyRLock(params.Context, key); err != nil { return nil, err } - defer server.KeyRUnlock(ctx, key) + defer params.KeyRUnlock(params.Context, key) - hash, ok := server.GetValue(ctx, key).(map[string]interface{}) + hash, ok := params.GetValue(params.Context, key).(map[string]interface{}) if !ok { return nil, fmt.Errorf("value at %s is not a hash", key) } @@ -532,25 +529,25 @@ func handleHGETALL(ctx context.Context, cmd []string, server types.EchoVault, _ return []byte(res), nil } -func handleHEXISTS(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := hexistsKeyFunc(cmd) +func handleHEXISTS(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := hexistsKeyFunc(params.Command) if err != nil { return nil, err } key := keys.ReadKeys[0] - field := cmd[2] + field := params.Command[2] - if !server.KeyExists(ctx, key) { + if !params.KeyExists(params.Context, key) { return []byte(":0\r\n"), nil } - if _, err = server.KeyRLock(ctx, key); err != nil { + if _, err = params.KeyRLock(params.Context, key); err != nil { return nil, err } - defer server.KeyRUnlock(ctx, key) + defer params.KeyRUnlock(params.Context, key) - hash, ok := server.GetValue(ctx, key).(map[string]interface{}) + hash, ok := params.GetValue(params.Context, key).(map[string]interface{}) if !ok { return nil, fmt.Errorf("value at %s is not a hash", key) } @@ -562,25 +559,25 @@ func handleHEXISTS(ctx context.Context, cmd []string, server types.EchoVault, _ return []byte(":0\r\n"), nil } -func handleHDEL(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := hdelKeyFunc(cmd) +func handleHDEL(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := hdelKeyFunc(params.Command) if err != nil { return nil, err } key := keys.WriteKeys[0] - fields := cmd[2:] + fields := params.Command[2:] - if !server.KeyExists(ctx, key) { + if !params.KeyExists(params.Context, key) { return []byte(":0\r\n"), nil } - if _, err = server.KeyLock(ctx, key); err != nil { + if _, err = params.KeyLock(params.Context, key); err != nil { return nil, err } - defer server.KeyUnlock(ctx, key) + defer params.KeyUnlock(params.Context, key) - hash, ok := server.GetValue(ctx, key).(map[string]interface{}) + hash, ok := params.GetValue(params.Context, key).(map[string]interface{}) if !ok { return nil, fmt.Errorf("value at %s is not a hash", key) } @@ -594,15 +591,15 @@ func handleHDEL(ctx context.Context, cmd []string, server types.EchoVault, _ *ne } } - if err = server.SetValue(ctx, key, hash); err != nil { + if err = params.SetValue(params.Context, key, hash); err != nil { return nil, err } return []byte(fmt.Sprintf(":%d\r\n", count)), nil } -func Commands() []types.Command { - return []types.Command{ +func Commands() []internal.Command { + return []internal.Command{ { Command: "hset", Module: constants.HashModule, diff --git a/pkg/modules/hash/key_funcs.go b/internal/modules/hash/key_funcs.go similarity index 52% rename from pkg/modules/hash/key_funcs.go rename to internal/modules/hash/key_funcs.go index 3e2f72db..847ea9cd 100644 --- a/pkg/modules/hash/key_funcs.go +++ b/internal/modules/hash/key_funcs.go @@ -16,143 +16,143 @@ package hash import ( "errors" + "github.com/echovault/echovault/internal" "github.com/echovault/echovault/pkg/constants" - "github.com/echovault/echovault/pkg/types" ) -func hsetKeyFunc(cmd []string) (types.AccessKeys, error) { +func hsetKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) < 4 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: cmd[1:2], }, nil } -func hsetnxKeyFunc(cmd []string) (types.AccessKeys, error) { +func hsetnxKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) < 4 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: cmd[1:2], }, nil } -func hgetKeyFunc(cmd []string) (types.AccessKeys, error) { +func hgetKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) < 3 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: cmd[1:2], WriteKeys: make([]string, 0), }, nil } -func hstrlenKeyFunc(cmd []string) (types.AccessKeys, error) { +func hstrlenKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) < 3 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: cmd[1:2], WriteKeys: make([]string, 0), }, nil } -func hvalsKeyFunc(cmd []string) (types.AccessKeys, error) { +func hvalsKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) != 2 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: cmd[1:], WriteKeys: make([]string, 0), }, nil } -func hrandfieldKeyFunc(cmd []string) (types.AccessKeys, error) { +func hrandfieldKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) < 2 || len(cmd) > 4 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } if len(cmd) == 2 { - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: cmd[1:], WriteKeys: make([]string, 0), }, nil } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: cmd[1:2], WriteKeys: make([]string, 0), }, nil } -func hlenKeyFunc(cmd []string) (types.AccessKeys, error) { +func hlenKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) != 2 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: cmd[1:], WriteKeys: make([]string, 0), }, nil } -func hkeysKeyFunc(cmd []string) (types.AccessKeys, error) { +func hkeysKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) != 2 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: cmd[1:], WriteKeys: make([]string, 0), }, nil } -func hincrbyKeyFunc(cmd []string) (types.AccessKeys, error) { +func hincrbyKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) != 4 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: cmd[1:2], }, nil } -func hgetallKeyFunc(cmd []string) (types.AccessKeys, error) { +func hgetallKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) != 2 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: cmd[1:], WriteKeys: make([]string, 0), }, nil } -func hexistsKeyFunc(cmd []string) (types.AccessKeys, error) { +func hexistsKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) != 3 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: cmd[1:2], WriteKeys: make([]string, 0), }, nil } -func hdelKeyFunc(cmd []string) (types.AccessKeys, error) { +func hdelKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) < 3 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: cmd[1:2], diff --git a/pkg/modules/list/commands.go b/internal/modules/list/commands.go similarity index 67% rename from pkg/modules/list/commands.go rename to internal/modules/list/commands.go index 145dc1c9..448f3415 100644 --- a/pkg/modules/list/commands.go +++ b/internal/modules/list/commands.go @@ -15,65 +15,62 @@ package list import ( - "context" "errors" "fmt" "github.com/echovault/echovault/internal" "github.com/echovault/echovault/pkg/constants" - "github.com/echovault/echovault/pkg/types" "math" - "net" "slices" "strings" ) -func handleLLen(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := llenKeyFunc(cmd) +func handleLLen(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := llenKeyFunc(params.Command) if err != nil { return nil, err } key := keys.ReadKeys[0] - if !server.KeyExists(ctx, key) { + if !params.KeyExists(params.Context, key) { // If key does not exist, return 0 return []byte(":0\r\n"), nil } - if _, err = server.KeyRLock(ctx, key); err != nil { + if _, err = params.KeyRLock(params.Context, key); err != nil { return nil, err } - defer server.KeyRUnlock(ctx, key) + defer params.KeyRUnlock(params.Context, key) - if list, ok := server.GetValue(ctx, key).([]interface{}); ok { + if list, ok := params.GetValue(params.Context, key).([]interface{}); ok { return []byte(fmt.Sprintf(":%d\r\n", len(list))), nil } return nil, errors.New("LLEN command on non-list item") } -func handleLIndex(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := lindexKeyFunc(cmd) +func handleLIndex(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := lindexKeyFunc(params.Command) if err != nil { return nil, err } key := keys.ReadKeys[0] - index, ok := internal.AdaptType(cmd[2]).(int) + index, ok := internal.AdaptType(params.Command[2]).(int) if !ok { return nil, errors.New("index must be an integer") } - if !server.KeyExists(ctx, key) { + if !params.KeyExists(params.Context, key) { return nil, errors.New("LINDEX command on non-list item") } - if _, err = server.KeyRLock(ctx, key); err != nil { + if _, err = params.KeyRLock(params.Context, key); err != nil { return nil, err } - list, ok := server.GetValue(ctx, key).([]interface{}) - server.KeyRUnlock(ctx, key) + list, ok := params.GetValue(params.Context, key).([]interface{}) + params.KeyRUnlock(params.Context, key) if !ok { return nil, errors.New("LINDEX command on non-list item") @@ -86,30 +83,30 @@ func handleLIndex(ctx context.Context, cmd []string, server types.EchoVault, _ * return []byte(fmt.Sprintf("+%s\r\n", list[index])), nil } -func handleLRange(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := lrangeKeyFunc(cmd) +func handleLRange(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := lrangeKeyFunc(params.Command) if err != nil { return nil, err } key := keys.ReadKeys[0] - start, startOk := internal.AdaptType(cmd[2]).(int) - end, endOk := internal.AdaptType(cmd[3]).(int) + start, startOk := internal.AdaptType(params.Command[2]).(int) + end, endOk := internal.AdaptType(params.Command[3]).(int) if !startOk || !endOk { return nil, errors.New("start and end indices must be integers") } - if !server.KeyExists(ctx, key) { + if !params.KeyExists(params.Context, key) { return nil, errors.New("LRANGE command on non-list item") } - if _, err = server.KeyRLock(ctx, key); err != nil { + if _, err = params.KeyRLock(params.Context, key); err != nil { return nil, err } - defer server.KeyRUnlock(ctx, key) + defer params.KeyRUnlock(params.Context, key) - list, ok := server.GetValue(ctx, key).([]interface{}) + list, ok := params.GetValue(params.Context, key).([]interface{}) if !ok { return nil, errors.New("LRANGE command on non-list item") } @@ -165,29 +162,29 @@ func handleLRange(ctx context.Context, cmd []string, server types.EchoVault, _ * return bytes, nil } -func handleLSet(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := lsetKeyFunc(cmd) +func handleLSet(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := lsetKeyFunc(params.Command) if err != nil { return nil, err } key := keys.WriteKeys[0] - index, ok := internal.AdaptType(cmd[2]).(int) + index, ok := internal.AdaptType(params.Command[2]).(int) if !ok { return nil, errors.New("index must be an integer") } - if !server.KeyExists(ctx, key) { + if !params.KeyExists(params.Context, key) { return nil, errors.New("LSET command on non-list item") } - if _, err = server.KeyLock(ctx, key); err != nil { + if _, err = params.KeyLock(params.Context, key); err != nil { return nil, err } - defer server.KeyUnlock(ctx, key) + defer params.KeyUnlock(params.Context, key) - list, ok := server.GetValue(ctx, key).([]interface{}) + list, ok := params.GetValue(params.Context, key).([]interface{}) if !ok { return nil, errors.New("LSET command on non-list item") } @@ -196,23 +193,23 @@ func handleLSet(ctx context.Context, cmd []string, server types.EchoVault, _ *ne return nil, errors.New("index must be within list range") } - list[index] = internal.AdaptType(cmd[3]) - if err = server.SetValue(ctx, key, list); err != nil { + list[index] = internal.AdaptType(params.Command[3]) + if err = params.SetValue(params.Context, key, list); err != nil { return nil, err } return []byte(constants.OkResponse), nil } -func handleLTrim(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := ltrimKeyFunc(cmd) +func handleLTrim(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := ltrimKeyFunc(params.Command) if err != nil { return nil, err } key := keys.WriteKeys[0] - start, startOk := internal.AdaptType(cmd[2]).(int) - end, endOk := internal.AdaptType(cmd[3]).(int) + start, startOk := internal.AdaptType(params.Command[2]).(int) + end, endOk := internal.AdaptType(params.Command[3]).(int) if !startOk || !endOk { return nil, errors.New("start and end indices must be integers") @@ -222,16 +219,16 @@ func handleLTrim(ctx context.Context, cmd []string, server types.EchoVault, _ *n return nil, errors.New("end index must be greater than start index or -1") } - if !server.KeyExists(ctx, key) { + if !params.KeyExists(params.Context, key) { return nil, errors.New("LTRIM command on non-list item") } - if _, err = server.KeyLock(ctx, key); err != nil { + if _, err = params.KeyLock(params.Context, key); err != nil { return nil, err } - defer server.KeyUnlock(ctx, key) + defer params.KeyUnlock(params.Context, key) - list, ok := server.GetValue(ctx, key).([]interface{}) + list, ok := params.GetValue(params.Context, key).([]interface{}) if !ok { return nil, errors.New("LTRIM command on non-list item") } @@ -241,44 +238,44 @@ func handleLTrim(ctx context.Context, cmd []string, server types.EchoVault, _ *n } if end == -1 || end > len(list) { - if err = server.SetValue(ctx, key, list[start:]); err != nil { + if err = params.SetValue(params.Context, key, list[start:]); err != nil { return nil, err } return []byte(constants.OkResponse), nil } - if err = server.SetValue(ctx, key, list[start:end]); err != nil { + if err = params.SetValue(params.Context, key, list[start:end]); err != nil { return nil, err } return []byte(constants.OkResponse), nil } -func handleLRem(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := lremKeyFunc(cmd) +func handleLRem(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := lremKeyFunc(params.Command) if err != nil { return nil, err } key := keys.WriteKeys[0] - value := cmd[3] + value := params.Command[3] - count, ok := internal.AdaptType(cmd[2]).(int) + count, ok := internal.AdaptType(params.Command[2]).(int) if !ok { return nil, errors.New("count must be an integer") } absoluteCount := internal.AbsInt(count) - if !server.KeyExists(ctx, key) { + if !params.KeyExists(params.Context, key) { return nil, errors.New("LREM command on non-list item") } - if _, err = server.KeyLock(ctx, key); err != nil { + if _, err = params.KeyLock(params.Context, key); err != nil { return nil, err } - defer server.KeyUnlock(ctx, key) + defer params.KeyUnlock(params.Context, key) - list, ok := server.GetValue(ctx, key).([]interface{}) + list, ok := params.GetValue(params.Context, key).([]interface{}) if !ok { return nil, errors.New("LREM command on non-list item") } @@ -314,44 +311,44 @@ func handleLRem(ctx context.Context, cmd []string, server types.EchoVault, _ *ne return elem == nil }) - if err = server.SetValue(ctx, key, list); err != nil { + if err = params.SetValue(params.Context, key, list); err != nil { return nil, err } return []byte(constants.OkResponse), nil } -func handleLMove(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := lmoveKeyFunc(cmd) +func handleLMove(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := lmoveKeyFunc(params.Command) if err != nil { return nil, err } source, destination := keys.WriteKeys[0], keys.WriteKeys[1] - whereFrom := strings.ToLower(cmd[3]) - whereTo := strings.ToLower(cmd[4]) + whereFrom := strings.ToLower(params.Command[3]) + whereTo := strings.ToLower(params.Command[4]) if !slices.Contains([]string{"left", "right"}, whereFrom) || !slices.Contains([]string{"left", "right"}, whereTo) { return nil, errors.New("wherefrom and whereto arguments must be either LEFT or RIGHT") } - if !server.KeyExists(ctx, source) || !server.KeyExists(ctx, destination) { + if !params.KeyExists(params.Context, source) || !params.KeyExists(params.Context, destination) { return nil, errors.New("both source and destination must be lists") } - if _, err = server.KeyLock(ctx, source); err != nil { + if _, err = params.KeyLock(params.Context, source); err != nil { return nil, err } - defer server.KeyUnlock(ctx, source) + defer params.KeyUnlock(params.Context, source) - _, err = server.KeyLock(ctx, destination) + _, err = params.KeyLock(params.Context, destination) if err != nil { return nil, err } - defer server.KeyUnlock(ctx, destination) + defer params.KeyUnlock(params.Context, destination) - sourceList, sourceOk := server.GetValue(ctx, source).([]interface{}) - destinationList, destinationOk := server.GetValue(ctx, destination).([]interface{}) + sourceList, sourceOk := params.GetValue(params.Context, source).([]interface{}) + destinationList, destinationOk := params.GetValue(params.Context, destination).([]interface{}) if !sourceOk || !destinationOk { return nil, errors.New("both source and destination must be lists") @@ -359,18 +356,18 @@ func handleLMove(ctx context.Context, cmd []string, server types.EchoVault, _ *n switch whereFrom { case "left": - err = server.SetValue(ctx, source, append([]interface{}{}, sourceList[1:]...)) + err = params.SetValue(params.Context, source, append([]interface{}{}, sourceList[1:]...)) if whereTo == "left" { - err = server.SetValue(ctx, destination, append(sourceList[0:1], destinationList...)) + err = params.SetValue(params.Context, destination, append(sourceList[0:1], destinationList...)) } else if whereTo == "right" { - err = server.SetValue(ctx, destination, append(destinationList, sourceList[0])) + err = params.SetValue(params.Context, destination, append(destinationList, sourceList[0])) } case "right": - err = server.SetValue(ctx, source, append([]interface{}{}, sourceList[:len(sourceList)-1]...)) + err = params.SetValue(params.Context, source, append([]interface{}{}, sourceList[:len(sourceList)-1]...)) if whereTo == "left" { - err = server.SetValue(ctx, destination, append(sourceList[len(sourceList)-1:], destinationList...)) + err = params.SetValue(params.Context, destination, append(sourceList[len(sourceList)-1:], destinationList...)) } else if whereTo == "right" { - err = server.SetValue(ctx, destination, append(destinationList, sourceList[len(sourceList)-1])) + err = params.SetValue(params.Context, destination, append(destinationList, sourceList[len(sourceList)-1])) } } @@ -381,54 +378,54 @@ func handleLMove(ctx context.Context, cmd []string, server types.EchoVault, _ *n return []byte(constants.OkResponse), nil } -func handleLPush(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := lpushKeyFunc(cmd) +func handleLPush(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := lpushKeyFunc(params.Command) if err != nil { return nil, err } var newElems []interface{} - for _, elem := range cmd[2:] { + for _, elem := range params.Command[2:] { newElems = append(newElems, internal.AdaptType(elem)) } key := keys.WriteKeys[0] - if !server.KeyExists(ctx, key) { - switch strings.ToLower(cmd[0]) { + if !params.KeyExists(params.Context, key) { + switch strings.ToLower(params.Command[0]) { case "lpushx": return nil, errors.New("LPUSHX command on non-list item") default: - if _, err = server.CreateKeyAndLock(ctx, key); err != nil { + if _, err = params.CreateKeyAndLock(params.Context, key); err != nil { return nil, err } - if err = server.SetValue(ctx, key, []interface{}{}); err != nil { + if err = params.SetValue(params.Context, key, []interface{}{}); err != nil { return nil, err } } } else { - if _, err = server.KeyLock(ctx, key); err != nil { + if _, err = params.KeyLock(params.Context, key); err != nil { return nil, err } } - defer server.KeyUnlock(ctx, key) + defer params.KeyUnlock(params.Context, key) - currentList := server.GetValue(ctx, key) + currentList := params.GetValue(params.Context, key) l, ok := currentList.([]interface{}) if !ok { return nil, errors.New("LPUSH command on non-list item") } - if err = server.SetValue(ctx, key, append(newElems, l...)); err != nil { + if err = params.SetValue(params.Context, key, append(newElems, l...)); err != nil { return nil, err } return []byte(constants.OkResponse), nil } -func handleRPush(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := rpushKeyFunc(cmd) +func handleRPush(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := rpushKeyFunc(params.Command) if err != nil { return nil, err } @@ -437,31 +434,31 @@ func handleRPush(ctx context.Context, cmd []string, server types.EchoVault, _ *n var newElems []interface{} - for _, elem := range cmd[2:] { + for _, elem := range params.Command[2:] { newElems = append(newElems, internal.AdaptType(elem)) } - if !server.KeyExists(ctx, key) { - switch strings.ToLower(cmd[0]) { + if !params.KeyExists(params.Context, key) { + switch strings.ToLower(params.Command[0]) { case "rpushx": return nil, errors.New("RPUSHX command on non-list item") default: - if _, err = server.CreateKeyAndLock(ctx, key); err != nil { + if _, err = params.CreateKeyAndLock(params.Context, key); err != nil { return nil, err } - defer server.KeyUnlock(ctx, key) - if err = server.SetValue(ctx, key, []interface{}{}); err != nil { + defer params.KeyUnlock(params.Context, key) + if err = params.SetValue(params.Context, key, []interface{}{}); err != nil { return nil, err } } } else { - if _, err = server.KeyLock(ctx, key); err != nil { + if _, err = params.KeyLock(params.Context, key); err != nil { return nil, err } - defer server.KeyUnlock(ctx, key) + defer params.KeyUnlock(params.Context, key) } - currentList := server.GetValue(ctx, key) + currentList := params.GetValue(params.Context, key) l, ok := currentList.([]interface{}) @@ -469,50 +466,50 @@ func handleRPush(ctx context.Context, cmd []string, server types.EchoVault, _ *n return nil, errors.New("RPUSH command on non-list item") } - if err = server.SetValue(ctx, key, append(l, newElems...)); err != nil { + if err = params.SetValue(params.Context, key, append(l, newElems...)); err != nil { return nil, err } return []byte(constants.OkResponse), nil } -func handlePop(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := popKeyFunc(cmd) +func handlePop(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := popKeyFunc(params.Command) if err != nil { return nil, err } key := keys.WriteKeys[0] - if !server.KeyExists(ctx, key) { - return nil, fmt.Errorf("%s command on non-list item", strings.ToUpper(cmd[0])) + if !params.KeyExists(params.Context, key) { + return nil, fmt.Errorf("%s command on non-list item", strings.ToUpper(params.Command[0])) } - if _, err = server.KeyLock(ctx, key); err != nil { + if _, err = params.KeyLock(params.Context, key); err != nil { return nil, err } - defer server.KeyUnlock(ctx, key) + defer params.KeyUnlock(params.Context, key) - list, ok := server.GetValue(ctx, key).([]interface{}) + list, ok := params.GetValue(params.Context, key).([]interface{}) if !ok { - return nil, fmt.Errorf("%s command on non-list item", strings.ToUpper(cmd[0])) + return nil, fmt.Errorf("%s command on non-list item", strings.ToUpper(params.Command[0])) } - switch strings.ToLower(cmd[0]) { + switch strings.ToLower(params.Command[0]) { default: - if err = server.SetValue(ctx, key, list[1:]); err != nil { + if err = params.SetValue(params.Context, key, list[1:]); err != nil { return nil, err } return []byte(fmt.Sprintf("+%v\r\n", list[0])), nil case "rpop": - if err = server.SetValue(ctx, key, list[:len(list)-1]); err != nil { + if err = params.SetValue(params.Context, key, list[:len(list)-1]); err != nil { return nil, err } return []byte(fmt.Sprintf("+%v\r\n", list[len(list)-1])), nil } } -func Commands() []types.Command { - return []types.Command{ +func Commands() []internal.Command { + return []internal.Command{ { Command: "lpush", Module: constants.ListModule, diff --git a/pkg/modules/list/key_funcs.go b/internal/modules/list/key_funcs.go similarity index 52% rename from pkg/modules/list/key_funcs.go rename to internal/modules/list/key_funcs.go index 3bd55262..2025d039 100644 --- a/pkg/modules/list/key_funcs.go +++ b/internal/modules/list/key_funcs.go @@ -16,114 +16,114 @@ package list import ( "errors" + "github.com/echovault/echovault/internal" "github.com/echovault/echovault/pkg/constants" - "github.com/echovault/echovault/pkg/types" ) -func lpushKeyFunc(cmd []string) (types.AccessKeys, error) { +func lpushKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) < 3 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: cmd[1:2], }, nil } -func popKeyFunc(cmd []string) (types.AccessKeys, error) { +func popKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) != 2 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: cmd[1:], }, nil } -func llenKeyFunc(cmd []string) (types.AccessKeys, error) { +func llenKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) != 2 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: cmd[1:], WriteKeys: make([]string, 0), }, nil } -func lrangeKeyFunc(cmd []string) (types.AccessKeys, error) { +func lrangeKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) != 4 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: cmd[1:2], WriteKeys: make([]string, 0), }, nil } -func lindexKeyFunc(cmd []string) (types.AccessKeys, error) { +func lindexKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) != 3 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: cmd[1:2], WriteKeys: make([]string, 0), }, nil } -func lsetKeyFunc(cmd []string) (types.AccessKeys, error) { +func lsetKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) != 4 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: cmd[1:2], }, nil } -func ltrimKeyFunc(cmd []string) (types.AccessKeys, error) { +func ltrimKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) != 4 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: cmd[1:2], }, nil } -func lremKeyFunc(cmd []string) (types.AccessKeys, error) { +func lremKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) != 4 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: cmd[1:2], }, nil } -func rpushKeyFunc(cmd []string) (types.AccessKeys, error) { +func rpushKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) < 3 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: cmd[1:2], }, nil } -func lmoveKeyFunc(cmd []string) (types.AccessKeys, error) { +func lmoveKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) != 5 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: cmd[1:3], diff --git a/internal/pubsub/channel.go b/internal/modules/pubsub/channel.go similarity index 100% rename from internal/pubsub/channel.go rename to internal/modules/pubsub/channel.go diff --git a/pkg/modules/pubsub/commands.go b/internal/modules/pubsub/commands.go similarity index 68% rename from pkg/modules/pubsub/commands.go rename to internal/modules/pubsub/commands.go index 6fe9fab9..81a60a3b 100644 --- a/pkg/modules/pubsub/commands.go +++ b/internal/modules/pubsub/commands.go @@ -15,79 +15,76 @@ package pubsub import ( - "context" "errors" "fmt" - internal_pubsub "github.com/echovault/echovault/internal/pubsub" + "github.com/echovault/echovault/internal" "github.com/echovault/echovault/pkg/constants" - "github.com/echovault/echovault/pkg/types" - "net" "strings" ) -func handleSubscribe(ctx context.Context, cmd []string, server types.EchoVault, conn *net.Conn) ([]byte, error) { - pubsub, ok := server.GetPubSub().(*internal_pubsub.PubSub) +func handleSubscribe(params internal.HandlerFuncParams) ([]byte, error) { + pubsub, ok := params.GetPubSub().(*PubSub) if !ok { return nil, errors.New("could not load pubsub module") } - channels := cmd[1:] + channels := params.Command[1:] if len(channels) == 0 { return nil, errors.New(constants.WrongArgsResponse) } - withPattern := strings.EqualFold(cmd[0], "psubscribe") - pubsub.Subscribe(ctx, conn, channels, withPattern) + withPattern := strings.EqualFold(params.Command[0], "psubscribe") + pubsub.Subscribe(params.Context, params.Connection, channels, withPattern) return nil, nil } -func handleUnsubscribe(ctx context.Context, cmd []string, server types.EchoVault, conn *net.Conn) ([]byte, error) { - pubsub, ok := server.GetPubSub().(*internal_pubsub.PubSub) +func handleUnsubscribe(params internal.HandlerFuncParams) ([]byte, error) { + pubsub, ok := params.GetPubSub().(*PubSub) if !ok { return nil, errors.New("could not load pubsub module") } - channels := cmd[1:] + channels := params.Command[1:] - withPattern := strings.EqualFold(cmd[0], "punsubscribe") + withPattern := strings.EqualFold(params.Command[0], "punsubscribe") - return pubsub.Unsubscribe(ctx, conn, channels, withPattern), nil + return pubsub.Unsubscribe(params.Context, params.Connection, channels, withPattern), nil } -func handlePublish(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - pubsub, ok := server.GetPubSub().(*internal_pubsub.PubSub) +func handlePublish(params internal.HandlerFuncParams) ([]byte, error) { + pubsub, ok := params.GetPubSub().(*PubSub) if !ok { return nil, errors.New("could not load pubsub module") } - if len(cmd) != 3 { + if len(params.Command) != 3 { return nil, errors.New(constants.WrongArgsResponse) } - pubsub.Publish(ctx, cmd[2], cmd[1]) + pubsub.Publish(params.Context, params.Command[2], params.Command[1]) return []byte(constants.OkResponse), nil } -func handlePubSubChannels(_ context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - if len(cmd) > 3 { +func handlePubSubChannels(params internal.HandlerFuncParams) ([]byte, error) { + if len(params.Command) > 3 { return nil, errors.New(constants.WrongArgsResponse) } - pubsub, ok := server.GetPubSub().(*internal_pubsub.PubSub) + pubsub, ok := params.GetPubSub().(*PubSub) if !ok { return nil, errors.New("could not load pubsub module") } pattern := "" - if len(cmd) == 3 { - pattern = cmd[2] + if len(params.Command) == 3 { + pattern = params.Command[2] } return pubsub.Channels(pattern), nil } -func handlePubSubNumPat(_ context.Context, _ []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - pubsub, ok := server.GetPubSub().(*internal_pubsub.PubSub) +func handlePubSubNumPat(params internal.HandlerFuncParams) ([]byte, error) { + pubsub, ok := params.GetPubSub().(*PubSub) if !ok { return nil, errors.New("could not load pubsub module") } @@ -95,28 +92,28 @@ func handlePubSubNumPat(_ context.Context, _ []string, server types.EchoVault, _ return []byte(fmt.Sprintf(":%d\r\n", num)), nil } -func handlePubSubNumSubs(_ context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - pubsub, ok := server.GetPubSub().(*internal_pubsub.PubSub) +func handlePubSubNumSubs(params internal.HandlerFuncParams) ([]byte, error) { + pubsub, ok := params.GetPubSub().(*PubSub) if !ok { return nil, errors.New("could not load pubsub module") } - return pubsub.NumSub(cmd[2:]), nil + return pubsub.NumSub(params.Command[2:]), nil } -func Commands() []types.Command { - return []types.Command{ +func Commands() []internal.Command { + return []internal.Command{ { Command: "subscribe", Module: constants.PubSubModule, Categories: []string{constants.PubSubCategory, constants.ConnectionCategory, constants.SlowCategory}, Description: "(SUBSCRIBE channel [channel ...]) Subscribe to one or more channels.", Sync: false, - KeyExtractionFunc: func(cmd []string) (types.AccessKeys, error) { + KeyExtractionFunc: func(cmd []string) (internal.AccessKeys, error) { // Treat the channels as keys if len(cmd) < 2 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: cmd[1:], ReadKeys: make([]string, 0), WriteKeys: make([]string, 0), @@ -130,12 +127,12 @@ func Commands() []types.Command { Categories: []string{constants.PubSubCategory, constants.ConnectionCategory, constants.SlowCategory}, Description: "(PSUBSCRIBE pattern [pattern ...]) Subscribe to one or more glob patterns.", Sync: false, - KeyExtractionFunc: func(cmd []string) (types.AccessKeys, error) { + KeyExtractionFunc: func(cmd []string) (internal.AccessKeys, error) { // Treat the patterns as keys if len(cmd) < 2 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: cmd[1:], ReadKeys: make([]string, 0), WriteKeys: make([]string, 0), @@ -149,12 +146,12 @@ func Commands() []types.Command { Categories: []string{constants.PubSubCategory, constants.FastCategory}, Description: "(PUBLISH channel message) Publish a message to the specified channel.", Sync: true, - KeyExtractionFunc: func(cmd []string) (types.AccessKeys, error) { + KeyExtractionFunc: func(cmd []string) (internal.AccessKeys, error) { // Treat the channel as a key if len(cmd) != 3 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: cmd[1:2], ReadKeys: make([]string, 0), WriteKeys: make([]string, 0), @@ -170,9 +167,9 @@ func Commands() []types.Command { If the channel list is not provided, then the connection will be unsubscribed from all the channels that it's currently subscribe to.`, Sync: false, - KeyExtractionFunc: func(cmd []string) (types.AccessKeys, error) { + KeyExtractionFunc: func(cmd []string) (internal.AccessKeys, error) { // Treat the channels as keys - return types.AccessKeys{ + return internal.AccessKeys{ Channels: cmd[1:], ReadKeys: make([]string, 0), WriteKeys: make([]string, 0), @@ -188,8 +185,8 @@ it's currently subscribe to.`, If the pattern list is not provided, then the connection will be unsubscribed from all the patterns that it's currently subscribe to.`, Sync: false, - KeyExtractionFunc: func(cmd []string) (types.AccessKeys, error) { - return types.AccessKeys{ + KeyExtractionFunc: func(cmd []string) (internal.AccessKeys, error) { + return internal.AccessKeys{ Channels: cmd[1:], ReadKeys: make([]string, 0), WriteKeys: make([]string, 0), @@ -203,17 +200,17 @@ it's currently subscribe to.`, Categories: []string{}, Description: "", Sync: false, - KeyExtractionFunc: func(cmd []string) (types.AccessKeys, error) { - return types.AccessKeys{ + KeyExtractionFunc: func(cmd []string) (internal.AccessKeys, error) { + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: make([]string, 0), }, nil }, - HandlerFunc: func(_ context.Context, _ []string, _ types.EchoVault, _ *net.Conn) ([]byte, error) { + HandlerFunc: func(_ internal.HandlerFuncParams) ([]byte, error) { return nil, errors.New("provide CHANNELS, NUMPAT, or NUMSUB subcommand") }, - SubCommands: []types.SubCommand{ + SubCommands: []internal.SubCommand{ { Command: "channels", Module: constants.PubSubModule, @@ -222,8 +219,8 @@ it's currently subscribe to.`, match the given pattern. If no pattern is provided, all active channels are returned. Active channels are channels with 1 or more subscribers.`, Sync: false, - KeyExtractionFunc: func(cmd []string) (types.AccessKeys, error) { - return types.AccessKeys{ + KeyExtractionFunc: func(cmd []string) (internal.AccessKeys, error) { + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: make([]string, 0), @@ -237,8 +234,8 @@ channels with 1 or more subscribers.`, Categories: []string{constants.PubSubCategory, constants.SlowCategory}, Description: `(PUBSUB NUMPAT) Return the number of patterns that are currently subscribed to by clients.`, Sync: false, - KeyExtractionFunc: func(cmd []string) (types.AccessKeys, error) { - return types.AccessKeys{ + KeyExtractionFunc: func(cmd []string) (internal.AccessKeys, error) { + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: make([]string, 0), @@ -253,8 +250,8 @@ channels with 1 or more subscribers.`, Description: `(PUBSUB NUMSUB [channel [channel ...]]) Return an array of arrays containing the provided channel name and how many clients are currently subscribed to the channel.`, Sync: false, - KeyExtractionFunc: func(cmd []string) (types.AccessKeys, error) { - return types.AccessKeys{ + KeyExtractionFunc: func(cmd []string) (internal.AccessKeys, error) { + return internal.AccessKeys{ Channels: cmd[2:], ReadKeys: make([]string, 0), WriteKeys: make([]string, 0), diff --git a/internal/pubsub/pubsub.go b/internal/modules/pubsub/pubsub.go similarity index 100% rename from internal/pubsub/pubsub.go rename to internal/modules/pubsub/pubsub.go diff --git a/pkg/modules/set/commands.go b/internal/modules/set/commands.go similarity index 65% rename from pkg/modules/set/commands.go rename to internal/modules/set/commands.go index f258283e..fd77af8d 100644 --- a/pkg/modules/set/commands.go +++ b/internal/modules/set/commands.go @@ -15,73 +15,69 @@ package set import ( - "context" "errors" "fmt" "github.com/echovault/echovault/internal" - internal_set "github.com/echovault/echovault/internal/set" "github.com/echovault/echovault/pkg/constants" - "github.com/echovault/echovault/pkg/types" - "net" "slices" "strings" ) -func handleSADD(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := saddKeyFunc(cmd) +func handleSADD(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := saddKeyFunc(params.Command) if err != nil { return nil, err } key := keys.WriteKeys[0] - var set *internal_set.Set + var set *Set - if !server.KeyExists(ctx, key) { - set = internal_set.NewSet(cmd[2:]) - if ok, err := server.CreateKeyAndLock(ctx, key); !ok && err != nil { + if !params.KeyExists(params.Context, key) { + set = NewSet(params.Command[2:]) + if ok, err := params.CreateKeyAndLock(params.Context, key); !ok && err != nil { return nil, err } - if err = server.SetValue(ctx, key, set); err != nil { + if err = params.SetValue(params.Context, key, set); err != nil { return nil, err } - server.KeyUnlock(ctx, key) - return []byte(fmt.Sprintf(":%d\r\n", len(cmd[2:]))), nil + params.KeyUnlock(params.Context, key) + return []byte(fmt.Sprintf(":%d\r\n", len(params.Command[2:]))), nil } - if _, err = server.KeyLock(ctx, key); err != nil { + if _, err = params.KeyLock(params.Context, key); err != nil { return nil, err } - defer server.KeyUnlock(ctx, key) + defer params.KeyUnlock(params.Context, key) - set, ok := server.GetValue(ctx, key).(*internal_set.Set) + set, ok := params.GetValue(params.Context, key).(*Set) if !ok { return nil, fmt.Errorf("value at key %s is not a set", key) } - count := set.Add(cmd[2:]) + count := set.Add(params.Command[2:]) return []byte(fmt.Sprintf(":%d\r\n", count)), nil } -func handleSCARD(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := scardKeyFunc(cmd) +func handleSCARD(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := scardKeyFunc(params.Command) if err != nil { return nil, err } key := keys.ReadKeys[0] - if !server.KeyExists(ctx, key) { + if !params.KeyExists(params.Context, key) { return []byte(fmt.Sprintf(":0\r\n")), nil } - if _, err = server.KeyRLock(ctx, key); err != nil { + if _, err = params.KeyRLock(params.Context, key); err != nil { return nil, err } - defer server.KeyRUnlock(ctx, key) + defer params.KeyRUnlock(params.Context, key) - set, ok := server.GetValue(ctx, key).(*internal_set.Set) + set, ok := params.GetValue(params.Context, key).(*Set) if !ok { return nil, fmt.Errorf("value at key %s is not a set", key) } @@ -91,21 +87,21 @@ func handleSCARD(ctx context.Context, cmd []string, server types.EchoVault, _ *n return []byte(fmt.Sprintf(":%d\r\n", cardinality)), nil } -func handleSDIFF(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := sdiffKeyFunc(cmd) +func handleSDIFF(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := sdiffKeyFunc(params.Command) if err != nil { return nil, err } // Extract base set first - if !server.KeyExists(ctx, keys.ReadKeys[0]) { + if !params.KeyExists(params.Context, keys.ReadKeys[0]) { return nil, fmt.Errorf("key for base set \"%s\" does not exist", keys.ReadKeys[0]) } - if _, err = server.KeyRLock(ctx, keys.ReadKeys[0]); err != nil { + if _, err = params.KeyRLock(params.Context, keys.ReadKeys[0]); err != nil { return nil, err } - defer server.KeyRUnlock(ctx, keys.ReadKeys[0]) - baseSet, ok := server.GetValue(ctx, keys.ReadKeys[0]).(*internal_set.Set) + defer params.KeyRUnlock(params.Context, keys.ReadKeys[0]) + baseSet, ok := params.GetValue(params.Context, keys.ReadKeys[0]).(*Set) if !ok { return nil, fmt.Errorf("value at key %s is not a set", keys.ReadKeys[0]) } @@ -114,24 +110,24 @@ func handleSDIFF(ctx context.Context, cmd []string, server types.EchoVault, _ *n defer func() { for key, locked := range locks { if locked { - server.KeyRUnlock(ctx, key) + params.KeyRUnlock(params.Context, key) } } }() for _, key := range keys.ReadKeys[1:] { - if !server.KeyExists(ctx, key) { + if !params.KeyExists(params.Context, key) { continue } - if _, err = server.KeyRLock(ctx, key); err != nil { + if _, err = params.KeyRLock(params.Context, key); err != nil { continue } locks[key] = true } - var sets []*internal_set.Set - for _, key := range cmd[2:] { - set, ok := server.GetValue(ctx, key).(*internal_set.Set) + var sets []*Set + for _, key := range params.Command[2:] { + set, ok := params.GetValue(params.Context, key).(*Set) if !ok { continue } @@ -152,8 +148,8 @@ func handleSDIFF(ctx context.Context, cmd []string, server types.EchoVault, _ *n return []byte(res), nil } -func handleSDIFFSTORE(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := sdiffstoreKeyFunc(cmd) +func handleSDIFFSTORE(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := sdiffstoreKeyFunc(params.Command) if err != nil { return nil, err } @@ -161,14 +157,14 @@ func handleSDIFFSTORE(ctx context.Context, cmd []string, server types.EchoVault, destination := keys.WriteKeys[0] // Extract base set first - if !server.KeyExists(ctx, keys.ReadKeys[0]) { + if !params.KeyExists(params.Context, keys.ReadKeys[0]) { return nil, fmt.Errorf("key for base set \"%s\" does not exist", keys.ReadKeys[0]) } - if _, err := server.KeyRLock(ctx, keys.ReadKeys[0]); err != nil { + if _, err := params.KeyRLock(params.Context, keys.ReadKeys[0]); err != nil { return nil, err } - defer server.KeyRUnlock(ctx, keys.ReadKeys[0]) - baseSet, ok := server.GetValue(ctx, keys.ReadKeys[0]).(*internal_set.Set) + defer params.KeyRUnlock(params.Context, keys.ReadKeys[0]) + baseSet, ok := params.GetValue(params.Context, keys.ReadKeys[0]).(*Set) if !ok { return nil, fmt.Errorf("value at key %s is not a set", keys.ReadKeys[0]) } @@ -177,24 +173,24 @@ func handleSDIFFSTORE(ctx context.Context, cmd []string, server types.EchoVault, defer func() { for key, locked := range locks { if locked { - server.KeyRUnlock(ctx, key) + params.KeyRUnlock(params.Context, key) } } }() for _, key := range keys.ReadKeys[1:] { - if !server.KeyExists(ctx, key) { + if !params.KeyExists(params.Context, key) { continue } - if _, err = server.KeyRLock(ctx, key); err != nil { + if _, err = params.KeyRLock(params.Context, key); err != nil { continue } locks[key] = true } - var sets []*internal_set.Set + var sets []*Set for _, key := range keys.ReadKeys[1:] { - set, ok := server.GetValue(ctx, key).(*internal_set.Set) + set, ok := params.GetValue(params.Context, key).(*Set) if !ok { continue } @@ -206,30 +202,30 @@ func handleSDIFFSTORE(ctx context.Context, cmd []string, server types.EchoVault, res := fmt.Sprintf(":%d\r\n", len(elems)) - if server.KeyExists(ctx, destination) { - if _, err = server.KeyLock(ctx, destination); err != nil { + if params.KeyExists(params.Context, destination) { + if _, err = params.KeyLock(params.Context, destination); err != nil { return nil, err } - if err = server.SetValue(ctx, destination, diff); err != nil { + if err = params.SetValue(params.Context, destination, diff); err != nil { return nil, err } - server.KeyUnlock(ctx, destination) + params.KeyUnlock(params.Context, destination) return []byte(res), nil } - if _, err = server.CreateKeyAndLock(ctx, destination); err != nil { + if _, err = params.CreateKeyAndLock(params.Context, destination); err != nil { return nil, err } - if err = server.SetValue(ctx, destination, diff); err != nil { + if err = params.SetValue(params.Context, destination, diff); err != nil { return nil, err } - server.KeyUnlock(ctx, destination) + params.KeyUnlock(params.Context, destination) return []byte(res), nil } -func handleSINTER(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := sinterKeyFunc(cmd) +func handleSINTER(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := sinterKeyFunc(params.Command) if err != nil { return nil, err } @@ -238,26 +234,26 @@ func handleSINTER(ctx context.Context, cmd []string, server types.EchoVault, _ * defer func() { for key, locked := range locks { if locked { - server.KeyRUnlock(ctx, key) + params.KeyRUnlock(params.Context, key) } } }() for _, key := range keys.ReadKeys { - if !server.KeyExists(ctx, key) { + if !params.KeyExists(params.Context, key) { // If key does not exist, then there is no intersection return []byte("*0\r\n"), nil } - if _, err = server.KeyRLock(ctx, key); err != nil { + if _, err = params.KeyRLock(params.Context, key); err != nil { return nil, err } locks[key] = true } - var sets []*internal_set.Set + var sets []*Set for key, _ := range locks { - set, ok := server.GetValue(ctx, key).(*internal_set.Set) + set, ok := params.GetValue(params.Context, key).(*Set) if !ok { // If the value at the key is not a set, return error return nil, fmt.Errorf("value at key %s is not a set", key) @@ -269,7 +265,7 @@ func handleSINTER(ctx context.Context, cmd []string, server types.EchoVault, _ * return nil, fmt.Errorf("not enough sets in the keys provided") } - intersect, _ := internal_set.Intersection(0, sets...) + intersect, _ := Intersection(0, sets...) elems := intersect.GetAll() res := fmt.Sprintf("*%d", len(elems)) @@ -283,15 +279,15 @@ func handleSINTER(ctx context.Context, cmd []string, server types.EchoVault, _ * return []byte(res), nil } -func handleSINTERCARD(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := sintercardKeyFunc(cmd) +func handleSINTERCARD(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := sintercardKeyFunc(params.Command) if err != nil { return nil, err } // Extract the limit from the command var limit int - limitIdx := slices.IndexFunc(cmd, func(s string) bool { + limitIdx := slices.IndexFunc(params.Command, func(s string) bool { return strings.EqualFold(s, "limit") }) if limitIdx >= 0 && limitIdx < 2 { @@ -299,11 +295,11 @@ func handleSINTERCARD(ctx context.Context, cmd []string, server types.EchoVault, } if limitIdx != -1 { limitIdx += 1 - if limitIdx >= len(cmd) { + if limitIdx >= len(params.Command) { return nil, errors.New("provide limit after LIMIT keyword") } - if l, ok := internal.AdaptType(cmd[limitIdx]).(int); !ok { + if l, ok := internal.AdaptType(params.Command[limitIdx]).(int); !ok { return nil, errors.New("limit must be an integer") } else { limit = l @@ -314,26 +310,26 @@ func handleSINTERCARD(ctx context.Context, cmd []string, server types.EchoVault, defer func() { for key, locked := range locks { if locked { - server.KeyRUnlock(ctx, key) + params.KeyRUnlock(params.Context, key) } } }() for _, key := range keys.ReadKeys { - if !server.KeyExists(ctx, key) { + if !params.KeyExists(params.Context, key) { // If key does not exist, then there is no intersection return []byte(":0\r\n"), nil } - if _, err = server.KeyRLock(ctx, key); err != nil { + if _, err = params.KeyRLock(params.Context, key); err != nil { return nil, err } locks[key] = true } - var sets []*internal_set.Set + var sets []*Set for key, _ := range locks { - set, ok := server.GetValue(ctx, key).(*internal_set.Set) + set, ok := params.GetValue(params.Context, key).(*Set) if !ok { // If the value at the key is not a set, return error return nil, fmt.Errorf("value at key %s is not a set", key) @@ -345,13 +341,13 @@ func handleSINTERCARD(ctx context.Context, cmd []string, server types.EchoVault, return nil, fmt.Errorf("not enough sets in the keys provided") } - intersect, _ := internal_set.Intersection(limit, sets...) + intersect, _ := Intersection(limit, sets...) return []byte(fmt.Sprintf(":%d\r\n", intersect.Cardinality())), nil } -func handleSINTERSTORE(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := sinterstoreKeyFunc(cmd) +func handleSINTERSTORE(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := sinterstoreKeyFunc(params.Command) if err != nil { return nil, err } @@ -360,26 +356,26 @@ func handleSINTERSTORE(ctx context.Context, cmd []string, server types.EchoVault defer func() { for key, locked := range locks { if locked { - server.KeyRUnlock(ctx, key) + params.KeyRUnlock(params.Context, key) } } }() for _, key := range keys.ReadKeys { - if !server.KeyExists(ctx, key) { + if !params.KeyExists(params.Context, key) { // If key does not exist, then there is no intersection return []byte(":0\r\n"), nil } - if _, err = server.KeyRLock(ctx, key); err != nil { + if _, err = params.KeyRLock(params.Context, key); err != nil { return nil, err } locks[key] = true } - var sets []*internal_set.Set + var sets []*Set for key, _ := range locks { - set, ok := server.GetValue(ctx, key).(*internal_set.Set) + set, ok := params.GetValue(params.Context, key).(*Set) if !ok { // If the value at the key is not a set, return error return nil, fmt.Errorf("value at key %s is not a set", key) @@ -387,74 +383,74 @@ func handleSINTERSTORE(ctx context.Context, cmd []string, server types.EchoVault sets = append(sets, set) } - intersect, _ := internal_set.Intersection(0, sets...) + intersect, _ := Intersection(0, sets...) destination := keys.WriteKeys[0] - if server.KeyExists(ctx, destination) { - if _, err = server.KeyLock(ctx, destination); err != nil { + if params.KeyExists(params.Context, destination) { + if _, err = params.KeyLock(params.Context, destination); err != nil { return nil, err } } else { - if _, err = server.CreateKeyAndLock(ctx, destination); err != nil { + if _, err = params.CreateKeyAndLock(params.Context, destination); err != nil { return nil, err } } - if err = server.SetValue(ctx, destination, intersect); err != nil { + if err = params.SetValue(params.Context, destination, intersect); err != nil { return nil, err } - server.KeyUnlock(ctx, destination) + params.KeyUnlock(params.Context, destination) return []byte(fmt.Sprintf(":%d\r\n", intersect.Cardinality())), nil } -func handleSISMEMBER(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := sismemberKeyFunc(cmd) +func handleSISMEMBER(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := sismemberKeyFunc(params.Command) if err != nil { return nil, err } key := keys.ReadKeys[0] - if !server.KeyExists(ctx, key) { + if !params.KeyExists(params.Context, key) { return []byte(":0\r\n"), nil } - if _, err = server.KeyRLock(ctx, key); err != nil { + if _, err = params.KeyRLock(params.Context, key); err != nil { return nil, err } - defer server.KeyRUnlock(ctx, key) + defer params.KeyRUnlock(params.Context, key) - set, ok := server.GetValue(ctx, key).(*internal_set.Set) + set, ok := params.GetValue(params.Context, key).(*Set) if !ok { return nil, fmt.Errorf("value at key %s is not a set", key) } - if !set.Contains(cmd[2]) { + if !set.Contains(params.Command[2]) { return []byte(":0\r\n"), nil } return []byte(":1\r\n"), nil } -func handleSMEMBERS(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := smembersKeyFunc(cmd) +func handleSMEMBERS(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := smembersKeyFunc(params.Command) if err != nil { return nil, err } key := keys.ReadKeys[0] - if !server.KeyExists(ctx, key) { + if !params.KeyExists(params.Context, key) { return []byte("*0\r\n"), nil } - if _, err = server.KeyRLock(ctx, key); err != nil { + if _, err = params.KeyRLock(params.Context, key); err != nil { return nil, err } - defer server.KeyRUnlock(ctx, key) + defer params.KeyRUnlock(params.Context, key) - set, ok := server.GetValue(ctx, key).(*internal_set.Set) + set, ok := params.GetValue(params.Context, key).(*Set) if !ok { return nil, fmt.Errorf("value at key %s is not a set", key) } @@ -472,16 +468,16 @@ func handleSMEMBERS(ctx context.Context, cmd []string, server types.EchoVault, _ return []byte(res), nil } -func handleSMISMEMBER(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := smismemberKeyFunc(cmd) +func handleSMISMEMBER(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := smismemberKeyFunc(params.Command) if err != nil { return nil, err } key := keys.ReadKeys[0] - members := cmd[2:] + members := params.Command[2:] - if !server.KeyExists(ctx, key) { + if !params.KeyExists(params.Context, key) { res := fmt.Sprintf("*%d", len(members)) for i, _ := range members { res = fmt.Sprintf("%s\r\n:0", res) @@ -492,12 +488,12 @@ func handleSMISMEMBER(ctx context.Context, cmd []string, server types.EchoVault, return []byte(res), nil } - if _, err = server.KeyRLock(ctx, key); err != nil { + if _, err = params.KeyRLock(params.Context, key); err != nil { return nil, err } - defer server.KeyRUnlock(ctx, key) + defer params.KeyRUnlock(params.Context, key) - set, ok := server.GetValue(ctx, key).(*internal_set.Set) + set, ok := params.GetValue(params.Context, key).(*Set) if !ok { return nil, fmt.Errorf("value at key %s is not a set", key) } @@ -515,48 +511,48 @@ func handleSMISMEMBER(ctx context.Context, cmd []string, server types.EchoVault, return []byte(res), nil } -func handleSMOVE(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := smoveKeyFunc(cmd) +func handleSMOVE(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := smoveKeyFunc(params.Command) if err != nil { return nil, err } source, destination := keys.WriteKeys[0], keys.WriteKeys[1] - member := cmd[3] + member := params.Command[3] - if !server.KeyExists(ctx, source) { + if !params.KeyExists(params.Context, source) { return []byte(":0\r\n"), nil } - if _, err = server.KeyLock(ctx, source); err != nil { + if _, err = params.KeyLock(params.Context, source); err != nil { return nil, err } - defer server.KeyUnlock(ctx, source) + defer params.KeyUnlock(params.Context, source) - sourceSet, ok := server.GetValue(ctx, source).(*internal_set.Set) + sourceSet, ok := params.GetValue(params.Context, source).(*Set) if !ok { return nil, errors.New("source is not a set") } - var destinationSet *internal_set.Set + var destinationSet *Set - if !server.KeyExists(ctx, destination) { + if !params.KeyExists(params.Context, destination) { // Destination key does not exist - if _, err = server.CreateKeyAndLock(ctx, destination); err != nil { + if _, err = params.CreateKeyAndLock(params.Context, destination); err != nil { return nil, err } - defer server.KeyUnlock(ctx, destination) - destinationSet = internal_set.NewSet([]string{}) - if err = server.SetValue(ctx, destination, destinationSet); err != nil { + defer params.KeyUnlock(params.Context, destination) + destinationSet = NewSet([]string{}) + if err = params.SetValue(params.Context, destination, destinationSet); err != nil { return nil, err } } else { // Destination key exists - if _, err := server.KeyLock(ctx, destination); err != nil { + if _, err := params.KeyLock(params.Context, destination); err != nil { return nil, err } - defer server.KeyUnlock(ctx, destination) - ds, ok := server.GetValue(ctx, destination).(*internal_set.Set) + defer params.KeyUnlock(params.Context, destination) + ds, ok := params.GetValue(params.Context, destination).(*Set) if !ok { return nil, errors.New("destination is not a set") } @@ -568,8 +564,8 @@ func handleSMOVE(ctx context.Context, cmd []string, server types.EchoVault, _ *n return []byte(fmt.Sprintf(":%d\r\n", res)), nil } -func handleSPOP(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := spopKeyFunc(cmd) +func handleSPOP(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := spopKeyFunc(params.Command) if err != nil { return nil, err } @@ -577,24 +573,24 @@ func handleSPOP(ctx context.Context, cmd []string, server types.EchoVault, _ *ne key := keys.WriteKeys[0] count := 1 - if len(cmd) == 3 { - c, ok := internal.AdaptType(cmd[2]).(int) + if len(params.Command) == 3 { + c, ok := internal.AdaptType(params.Command[2]).(int) if !ok { return nil, errors.New("count must be an integer") } count = c } - if !server.KeyExists(ctx, key) { + if !params.KeyExists(params.Context, key) { return []byte("*-1\r\n"), nil } - if _, err = server.KeyLock(ctx, key); err != nil { + if _, err = params.KeyLock(params.Context, key); err != nil { return nil, err } - defer server.KeyUnlock(ctx, key) + defer params.KeyUnlock(params.Context, key) - set, ok := server.GetValue(ctx, key).(*internal_set.Set) + set, ok := params.GetValue(params.Context, key).(*Set) if !ok { return nil, fmt.Errorf("value at %s is not a set", key) } @@ -612,8 +608,8 @@ func handleSPOP(ctx context.Context, cmd []string, server types.EchoVault, _ *ne return []byte(res), nil } -func handleSRANDMEMBER(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := srandmemberKeyFunc(cmd) +func handleSRANDMEMBER(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := srandmemberKeyFunc(params.Command) if err != nil { return nil, err } @@ -621,24 +617,24 @@ func handleSRANDMEMBER(ctx context.Context, cmd []string, server types.EchoVault key := keys.ReadKeys[0] count := 1 - if len(cmd) == 3 { - c, ok := internal.AdaptType(cmd[2]).(int) + if len(params.Command) == 3 { + c, ok := internal.AdaptType(params.Command[2]).(int) if !ok { return nil, errors.New("count must be an integer") } count = c } - if !server.KeyExists(ctx, key) { + if !params.KeyExists(params.Context, key) { return []byte("*-1\r\n"), nil } - if _, err = server.KeyLock(ctx, key); err != nil { + if _, err = params.KeyLock(params.Context, key); err != nil { return nil, err } - defer server.KeyUnlock(ctx, key) + defer params.KeyUnlock(params.Context, key) - set, ok := server.GetValue(ctx, key).(*internal_set.Set) + set, ok := params.GetValue(params.Context, key).(*Set) if !ok { return nil, fmt.Errorf("value at %s is not a set", key) } @@ -656,25 +652,25 @@ func handleSRANDMEMBER(ctx context.Context, cmd []string, server types.EchoVault return []byte(res), nil } -func handleSREM(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := sremKeyFunc(cmd) +func handleSREM(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := sremKeyFunc(params.Command) if err != nil { return nil, err } key := keys.WriteKeys[0] - members := cmd[2:] + members := params.Command[2:] - if !server.KeyExists(ctx, key) { + if !params.KeyExists(params.Context, key) { return []byte(":0\r\n"), nil } - if _, err = server.KeyLock(ctx, key); err != nil { + if _, err = params.KeyLock(params.Context, key); err != nil { return nil, err } - defer server.KeyUnlock(ctx, key) + defer params.KeyUnlock(params.Context, key) - set, ok := server.GetValue(ctx, key).(*internal_set.Set) + set, ok := params.GetValue(params.Context, key).(*Set) if !ok { return nil, fmt.Errorf("value at key %s is not a set", key) } @@ -684,8 +680,8 @@ func handleSREM(ctx context.Context, cmd []string, server types.EchoVault, _ *ne return []byte(fmt.Sprintf(":%d\r\n", count)), nil } -func handleSUNION(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := sunionKeyFunc(cmd) +func handleSUNION(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := sunionKeyFunc(params.Command) if err != nil { return nil, err } @@ -694,35 +690,35 @@ func handleSUNION(ctx context.Context, cmd []string, server types.EchoVault, _ * defer func() { for key, locked := range locks { if locked { - server.KeyRUnlock(ctx, key) + params.KeyRUnlock(params.Context, key) } } }() for _, key := range keys.ReadKeys { - if !server.KeyExists(ctx, key) { + if !params.KeyExists(params.Context, key) { continue } - if _, err = server.KeyRLock(ctx, key); err != nil { + if _, err = params.KeyRLock(params.Context, key); err != nil { return nil, err } locks[key] = true } - var sets []*internal_set.Set + var sets []*Set for key, locked := range locks { if !locked { continue } - set, ok := server.GetValue(ctx, key).(*internal_set.Set) + set, ok := params.GetValue(params.Context, key).(*Set) if !ok { return nil, fmt.Errorf("value at key %s is not a set", key) } sets = append(sets, set) } - union := internal_set.Union(sets...) + union := Union(sets...) res := fmt.Sprintf("*%d", union.Cardinality()) for i, e := range union.GetAll() { @@ -735,8 +731,8 @@ func handleSUNION(ctx context.Context, cmd []string, server types.EchoVault, _ * return []byte(res), nil } -func handleSUNIONSTORE(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := sunionstoreKeyFunc(cmd) +func handleSUNIONSTORE(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := sunionstoreKeyFunc(params.Command) if err != nil { return nil, err } @@ -745,57 +741,57 @@ func handleSUNIONSTORE(ctx context.Context, cmd []string, server types.EchoVault defer func() { for key, locked := range locks { if locked { - server.KeyRUnlock(ctx, key) + params.KeyRUnlock(params.Context, key) } } }() for _, key := range keys.ReadKeys { - if !server.KeyExists(ctx, key) { + if !params.KeyExists(params.Context, key) { continue } - if _, err = server.KeyRLock(ctx, key); err != nil { + if _, err = params.KeyRLock(params.Context, key); err != nil { return nil, err } locks[key] = true } - var sets []*internal_set.Set + var sets []*Set for key, locked := range locks { if !locked { continue } - set, ok := server.GetValue(ctx, key).(*internal_set.Set) + set, ok := params.GetValue(params.Context, key).(*Set) if !ok { return nil, fmt.Errorf("value at key %s is not a set", key) } sets = append(sets, set) } - union := internal_set.Union(sets...) + union := Union(sets...) destination := keys.WriteKeys[0] - if server.KeyExists(ctx, destination) { - if _, err = server.KeyLock(ctx, destination); err != nil { + if params.KeyExists(params.Context, destination) { + if _, err = params.KeyLock(params.Context, destination); err != nil { return nil, err } } else { - if _, err = server.CreateKeyAndLock(ctx, destination); err != nil { + if _, err = params.CreateKeyAndLock(params.Context, destination); err != nil { return nil, err } } - defer server.KeyUnlock(ctx, destination) + defer params.KeyUnlock(params.Context, destination) - if err = server.SetValue(ctx, destination, union); err != nil { + if err = params.SetValue(params.Context, destination, union); err != nil { return nil, err } return []byte(fmt.Sprintf(":%d\r\n", union.Cardinality())), nil } -func Commands() []types.Command { - return []types.Command{ +func Commands() []internal.Command { + return []internal.Command{ { Command: "sadd", Module: constants.SetModule, diff --git a/pkg/modules/set/key_funcs.go b/internal/modules/set/key_funcs.go similarity index 51% rename from pkg/modules/set/key_funcs.go rename to internal/modules/set/key_funcs.go index 36910de7..3e703c48 100644 --- a/pkg/modules/set/key_funcs.go +++ b/internal/modules/set/key_funcs.go @@ -16,70 +16,70 @@ package set import ( "errors" + "github.com/echovault/echovault/internal" "github.com/echovault/echovault/pkg/constants" - "github.com/echovault/echovault/pkg/types" "slices" "strings" ) -func saddKeyFunc(cmd []string) (types.AccessKeys, error) { +func saddKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) < 3 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: cmd[1:2], }, nil } -func scardKeyFunc(cmd []string) (types.AccessKeys, error) { +func scardKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) != 2 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: cmd[1:2], WriteKeys: make([]string, 0), }, nil } -func sdiffKeyFunc(cmd []string) (types.AccessKeys, error) { +func sdiffKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) < 2 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: cmd[1:], WriteKeys: make([]string, 0), }, nil } -func sdiffstoreKeyFunc(cmd []string) (types.AccessKeys, error) { +func sdiffstoreKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) < 3 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: cmd[2:], WriteKeys: cmd[1:2], }, nil } -func sinterKeyFunc(cmd []string) (types.AccessKeys, error) { +func sinterKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) < 2 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: cmd[1:], WriteKeys: make([]string, 0), }, nil } -func sintercardKeyFunc(cmd []string) (types.AccessKeys, error) { +func sintercardKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) < 2 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } limitIdx := slices.IndexFunc(cmd, func(s string) bool { @@ -87,124 +87,124 @@ func sintercardKeyFunc(cmd []string) (types.AccessKeys, error) { }) if limitIdx == -1 { - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: cmd[1:], WriteKeys: make([]string, 0), }, nil } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: cmd[1:limitIdx], WriteKeys: make([]string, 0), }, nil } -func sinterstoreKeyFunc(cmd []string) (types.AccessKeys, error) { +func sinterstoreKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) < 3 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: cmd[2:], WriteKeys: cmd[1:2], }, nil } -func sismemberKeyFunc(cmd []string) (types.AccessKeys, error) { +func sismemberKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) != 3 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: cmd[1:], WriteKeys: make([]string, 0), }, nil } -func smembersKeyFunc(cmd []string) (types.AccessKeys, error) { +func smembersKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) != 2 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: cmd[1:], WriteKeys: make([]string, 0), }, nil } -func smismemberKeyFunc(cmd []string) (types.AccessKeys, error) { +func smismemberKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) < 3 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: cmd[1:2], WriteKeys: make([]string, 0), }, nil } -func smoveKeyFunc(cmd []string) (types.AccessKeys, error) { +func smoveKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) != 4 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: cmd[1:3], }, nil } -func spopKeyFunc(cmd []string) (types.AccessKeys, error) { +func spopKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) < 2 || len(cmd) > 3 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: cmd[1:2], }, nil } -func srandmemberKeyFunc(cmd []string) (types.AccessKeys, error) { +func srandmemberKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) < 2 || len(cmd) > 3 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: cmd[1:2], WriteKeys: make([]string, 0), }, nil } -func sremKeyFunc(cmd []string) (types.AccessKeys, error) { +func sremKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) < 3 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: cmd[1:2], }, nil } -func sunionKeyFunc(cmd []string) (types.AccessKeys, error) { +func sunionKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) < 2 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: cmd[1:], WriteKeys: make([]string, 0), }, nil } -func sunionstoreKeyFunc(cmd []string) (types.AccessKeys, error) { +func sunionstoreKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) < 3 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: cmd[2:], WriteKeys: cmd[1:2], diff --git a/internal/set/set.go b/internal/modules/set/set.go similarity index 100% rename from internal/set/set.go rename to internal/modules/set/set.go diff --git a/pkg/modules/sorted_set/commands.go b/internal/modules/sorted_set/commands.go similarity index 68% rename from pkg/modules/sorted_set/commands.go rename to internal/modules/sorted_set/commands.go index 325844c4..5e3fd6e2 100644 --- a/pkg/modules/sorted_set/commands.go +++ b/internal/modules/sorted_set/commands.go @@ -16,22 +16,18 @@ package sorted_set import ( "cmp" - "context" "errors" "fmt" "github.com/echovault/echovault/internal" - "github.com/echovault/echovault/internal/sorted_set" "github.com/echovault/echovault/pkg/constants" - "github.com/echovault/echovault/pkg/types" "math" - "net" "slices" "strconv" "strings" ) -func handleZADD(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := zaddKeyFunc(cmd) +func handleZADD(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := zaddKeyFunc(params.Command) if err != nil { return nil, err } @@ -45,13 +41,13 @@ func handleZADD(ctx context.Context, cmd []string, server types.EchoVault, _ *ne // Find the first valid score and this will be the start of the score/member pairs var membersStartIndex int - for i := 0; i < len(cmd); i++ { + for i := 0; i < len(params.Command); i++ { if membersStartIndex != 0 { break } - switch internal.AdaptType(cmd[i]).(type) { + switch internal.AdaptType(params.Command[i]).(type) { case string: - if slices.Contains([]string{"-inf", "+inf"}, strings.ToLower(cmd[i])) { + if slices.Contains([]string{"-inf", "+inf"}, strings.ToLower(params.Command[i])) { membersStartIndex = i } case float64: @@ -61,17 +57,17 @@ func handleZADD(ctx context.Context, cmd []string, server types.EchoVault, _ *ne } } - if membersStartIndex < 2 || len(cmd[membersStartIndex:])%2 != 0 { + if membersStartIndex < 2 || len(params.Command[membersStartIndex:])%2 != 0 { return nil, errors.New("score/member pairs must be float/string") } - var members []sorted_set.MemberParam + var members []MemberParam - for i := 0; i < len(cmd[membersStartIndex:]); i++ { + for i := 0; i < len(params.Command[membersStartIndex:]); i++ { if i%2 != 0 { continue } - score := internal.AdaptType(cmd[membersStartIndex:][i]) + score := internal.AdaptType(params.Command[membersStartIndex:][i]) switch score.(type) { default: return nil, errors.New("invalid score in score/member list") @@ -79,36 +75,36 @@ func handleZADD(ctx context.Context, cmd []string, server types.EchoVault, _ *ne var s float64 if strings.ToLower(score.(string)) == "-inf" { s = math.Inf(-1) - members = append(members, sorted_set.MemberParam{ - Value: sorted_set.Value(cmd[membersStartIndex:][i+1]), - Score: sorted_set.Score(s), + members = append(members, MemberParam{ + Value: Value(params.Command[membersStartIndex:][i+1]), + Score: Score(s), }) } if strings.ToLower(score.(string)) == "+inf" { s = math.Inf(1) - members = append(members, sorted_set.MemberParam{ - Value: sorted_set.Value(cmd[membersStartIndex:][i+1]), - Score: sorted_set.Score(s), + members = append(members, MemberParam{ + Value: Value(params.Command[membersStartIndex:][i+1]), + Score: Score(s), }) } case float64: s, _ := score.(float64) - members = append(members, sorted_set.MemberParam{ - Value: sorted_set.Value(cmd[membersStartIndex:][i+1]), - Score: sorted_set.Score(s), + members = append(members, MemberParam{ + Value: Value(params.Command[membersStartIndex:][i+1]), + Score: Score(s), }) case int: s, _ := score.(int) - members = append(members, sorted_set.MemberParam{ - Value: sorted_set.Value(cmd[membersStartIndex:][i+1]), - Score: sorted_set.Score(s), + members = append(members, MemberParam{ + Value: Value(params.Command[membersStartIndex:][i+1]), + Score: Score(s), }) } } // Parse options using membersStartIndex as the upper limit if membersStartIndex > 2 { - options := cmd[2:membersStartIndex] + options := params.Command[2:membersStartIndex] for _, option := range options { if slices.Contains([]string{"xx", "nx"}, strings.ToLower(option)) { updatePolicy = option @@ -143,14 +139,14 @@ func handleZADD(ctx context.Context, cmd []string, server types.EchoVault, _ *ne } } - if server.KeyExists(ctx, key) { + if params.KeyExists(params.Context, key) { // Key exists - _, err = server.KeyLock(ctx, key) + _, err = params.KeyLock(params.Context, key) if err != nil { return nil, err } - defer server.KeyUnlock(ctx, key) - set, ok := server.GetValue(ctx, key).(*sorted_set.SortedSet) + defer params.KeyUnlock(params.Context, key) + set, ok := params.GetValue(params.Context, key).(*SortedSet) if !ok { return nil, fmt.Errorf("value at %s is not a sorted set", key) } @@ -168,36 +164,36 @@ func handleZADD(ctx context.Context, cmd []string, server types.EchoVault, _ *ne } // Key does not exist - if _, err = server.CreateKeyAndLock(ctx, key); err != nil { + if _, err = params.CreateKeyAndLock(params.Context, key); err != nil { return nil, err } - defer server.KeyUnlock(ctx, key) + defer params.KeyUnlock(params.Context, key) - set := sorted_set.NewSortedSet(members) - if err = server.SetValue(ctx, key, set); err != nil { + set := NewSortedSet(members) + if err = params.SetValue(params.Context, key, set); err != nil { return nil, err } return []byte(fmt.Sprintf(":%d\r\n", set.Cardinality())), nil } -func handleZCARD(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := zcardKeyFunc(cmd) +func handleZCARD(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := zcardKeyFunc(params.Command) if err != nil { return nil, err } key := keys.ReadKeys[0] - if !server.KeyExists(ctx, key) { + if !params.KeyExists(params.Context, key) { return []byte(":0\r\n"), nil } - if _, err = server.KeyRLock(ctx, key); err != nil { + if _, err = params.KeyRLock(params.Context, key); err != nil { return nil, err } - defer server.KeyRUnlock(ctx, key) + defer params.KeyRUnlock(params.Context, key) - set, ok := server.GetValue(ctx, key).(*sorted_set.SortedSet) + set, ok := params.GetValue(params.Context, key).(*SortedSet) if !ok { return nil, fmt.Errorf("value at %s is not a sorted set", key) } @@ -205,65 +201,65 @@ func handleZCARD(ctx context.Context, cmd []string, server types.EchoVault, _ *n return []byte(fmt.Sprintf(":%d\r\n", set.Cardinality())), nil } -func handleZCOUNT(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := zcountKeyFunc(cmd) +func handleZCOUNT(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := zcountKeyFunc(params.Command) if err != nil { return nil, err } key := keys.ReadKeys[0] - minimum := sorted_set.Score(math.Inf(-1)) - switch internal.AdaptType(cmd[2]).(type) { + minimum := Score(math.Inf(-1)) + switch internal.AdaptType(params.Command[2]).(type) { default: return nil, errors.New("min constraint must be a double") case string: - if strings.ToLower(cmd[2]) == "+inf" { - minimum = sorted_set.Score(math.Inf(1)) + if strings.ToLower(params.Command[2]) == "+inf" { + minimum = Score(math.Inf(1)) } else { return nil, errors.New("min constraint must be a double") } case float64: - s, _ := internal.AdaptType(cmd[2]).(float64) - minimum = sorted_set.Score(s) + s, _ := internal.AdaptType(params.Command[2]).(float64) + minimum = Score(s) case int: - s, _ := internal.AdaptType(cmd[2]).(int) - minimum = sorted_set.Score(s) + s, _ := internal.AdaptType(params.Command[2]).(int) + minimum = Score(s) } - maximum := sorted_set.Score(math.Inf(1)) - switch internal.AdaptType(cmd[3]).(type) { + maximum := Score(math.Inf(1)) + switch internal.AdaptType(params.Command[3]).(type) { default: return nil, errors.New("max constraint must be a double") case string: - if strings.ToLower(cmd[3]) == "-inf" { - maximum = sorted_set.Score(math.Inf(-1)) + if strings.ToLower(params.Command[3]) == "-inf" { + maximum = Score(math.Inf(-1)) } else { return nil, errors.New("max constraint must be a double") } case float64: - s, _ := internal.AdaptType(cmd[3]).(float64) - maximum = sorted_set.Score(s) + s, _ := internal.AdaptType(params.Command[3]).(float64) + maximum = Score(s) case int: - s, _ := internal.AdaptType(cmd[3]).(int) - maximum = sorted_set.Score(s) + s, _ := internal.AdaptType(params.Command[3]).(int) + maximum = Score(s) } - if !server.KeyExists(ctx, key) { + if !params.KeyExists(params.Context, key) { return []byte(":0\r\n"), nil } - if _, err = server.KeyRLock(ctx, key); err != nil { + if _, err = params.KeyRLock(params.Context, key); err != nil { return nil, err } - defer server.KeyRUnlock(ctx, key) + defer params.KeyRUnlock(params.Context, key) - set, ok := server.GetValue(ctx, key).(*sorted_set.SortedSet) + set, ok := params.GetValue(params.Context, key).(*SortedSet) if !ok { return nil, fmt.Errorf("value at %s is not a sorted set", key) } - var members []sorted_set.MemberParam + var members []MemberParam for _, m := range set.GetAll() { if m.Score >= minimum && m.Score <= maximum { members = append(members, m) @@ -273,26 +269,26 @@ func handleZCOUNT(ctx context.Context, cmd []string, server types.EchoVault, _ * return []byte(fmt.Sprintf(":%d\r\n", len(members))), nil } -func handleZLEXCOUNT(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := zlexcountKeyFunc(cmd) +func handleZLEXCOUNT(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := zlexcountKeyFunc(params.Command) if err != nil { return nil, err } key := keys.ReadKeys[0] - minimum := cmd[2] - maximum := cmd[3] + minimum := params.Command[2] + maximum := params.Command[3] - if !server.KeyExists(ctx, key) { + if !params.KeyExists(params.Context, key) { return []byte(":0\r\n"), nil } - if _, err = server.KeyRLock(ctx, key); err != nil { + if _, err = params.KeyRLock(params.Context, key); err != nil { return nil, err } - defer server.KeyRUnlock(ctx, key) + defer params.KeyRUnlock(params.Context, key) - set, ok := server.GetValue(ctx, key).(*sorted_set.SortedSet) + set, ok := params.GetValue(params.Context, key).(*SortedSet) if !ok { return nil, fmt.Errorf("value at %s is not a sorted set", key) } @@ -318,13 +314,13 @@ func handleZLEXCOUNT(ctx context.Context, cmd []string, server types.EchoVault, return []byte(fmt.Sprintf(":%d\r\n", count)), nil } -func handleZDIFF(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := zdiffKeyFunc(cmd) +func handleZDIFF(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := zdiffKeyFunc(params.Command) if err != nil { return nil, err } - withscoresIndex := slices.IndexFunc(cmd, func(s string) bool { + withscoresIndex := slices.IndexFunc(params.Command, func(s string) bool { return strings.EqualFold(s, "withscores") }) if withscoresIndex > -1 && withscoresIndex < 2 { @@ -335,38 +331,38 @@ func handleZDIFF(ctx context.Context, cmd []string, server types.EchoVault, _ *n defer func() { for key, locked := range locks { if locked { - server.KeyRUnlock(ctx, key) + params.KeyRUnlock(params.Context, key) } } }() // Extract base set - if !server.KeyExists(ctx, keys.ReadKeys[0]) { + if !params.KeyExists(params.Context, keys.ReadKeys[0]) { // If base set does not exist, return an empty array return []byte("*0\r\n"), nil } - if _, err = server.KeyRLock(ctx, keys.ReadKeys[0]); err != nil { + if _, err = params.KeyRLock(params.Context, keys.ReadKeys[0]); err != nil { return nil, err } - defer server.KeyRUnlock(ctx, keys.ReadKeys[0]) - baseSortedSet, ok := server.GetValue(ctx, keys.ReadKeys[0]).(*sorted_set.SortedSet) + defer params.KeyRUnlock(params.Context, keys.ReadKeys[0]) + baseSortedSet, ok := params.GetValue(params.Context, keys.ReadKeys[0]).(*SortedSet) if !ok { return nil, fmt.Errorf("value at %s is not a sorted set", keys.ReadKeys[0]) } // Extract the remaining sets - var sets []*sorted_set.SortedSet + var sets []*SortedSet for i := 1; i < len(keys.ReadKeys); i++ { - if !server.KeyExists(ctx, keys.ReadKeys[i]) { + if !params.KeyExists(params.Context, keys.ReadKeys[i]) { continue } - locked, err := server.KeyRLock(ctx, keys.ReadKeys[i]) + locked, err := params.KeyRLock(params.Context, keys.ReadKeys[i]) if err != nil { return nil, err } locks[keys.ReadKeys[i]] = locked - set, ok := server.GetValue(ctx, keys.ReadKeys[i]).(*sorted_set.SortedSet) + set, ok := params.GetValue(params.Context, keys.ReadKeys[i]).(*SortedSet) if !ok { return nil, fmt.Errorf("value at %s is not a sorted set", keys.ReadKeys[i]) } @@ -391,8 +387,8 @@ func handleZDIFF(ctx context.Context, cmd []string, server types.EchoVault, _ *n return []byte(res), nil } -func handleZDIFFSTORE(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := zdiffstoreKeyFunc(cmd) +func handleZDIFFSTORE(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := zdiffstoreKeyFunc(params.Command) if err != nil { return nil, err } @@ -403,33 +399,33 @@ func handleZDIFFSTORE(ctx context.Context, cmd []string, server types.EchoVault, defer func() { for key, locked := range locks { if locked { - server.KeyRUnlock(ctx, key) + params.KeyRUnlock(params.Context, key) } } }() // Extract base set - if !server.KeyExists(ctx, keys.ReadKeys[0]) { + if !params.KeyExists(params.Context, keys.ReadKeys[0]) { // If base set does not exist, return 0 return []byte(":0\r\n"), nil } - if _, err = server.KeyRLock(ctx, keys.ReadKeys[0]); err != nil { + if _, err = params.KeyRLock(params.Context, keys.ReadKeys[0]); err != nil { return nil, err } - defer server.KeyRUnlock(ctx, keys.ReadKeys[0]) - baseSortedSet, ok := server.GetValue(ctx, keys.ReadKeys[0]).(*sorted_set.SortedSet) + defer params.KeyRUnlock(params.Context, keys.ReadKeys[0]) + baseSortedSet, ok := params.GetValue(params.Context, keys.ReadKeys[0]).(*SortedSet) if !ok { return nil, fmt.Errorf("value at %s is not a sorted set", keys.ReadKeys[0]) } - var sets []*sorted_set.SortedSet + var sets []*SortedSet for i := 1; i < len(keys.ReadKeys); i++ { - if server.KeyExists(ctx, keys.ReadKeys[i]) { - if _, err = server.KeyRLock(ctx, keys.ReadKeys[i]); err != nil { + if params.KeyExists(params.Context, keys.ReadKeys[i]) { + if _, err = params.KeyRLock(params.Context, keys.ReadKeys[i]); err != nil { return nil, err } - set, ok := server.GetValue(ctx, keys.ReadKeys[i]).(*sorted_set.SortedSet) + set, ok := params.GetValue(params.Context, keys.ReadKeys[i]).(*SortedSet) if !ok { return nil, fmt.Errorf("value at %s is not a sorted set", keys.ReadKeys[i]) } @@ -439,80 +435,80 @@ func handleZDIFFSTORE(ctx context.Context, cmd []string, server types.EchoVault, diff := baseSortedSet.Subtract(sets) - if server.KeyExists(ctx, destination) { - if _, err = server.KeyLock(ctx, destination); err != nil { + if params.KeyExists(params.Context, destination) { + if _, err = params.KeyLock(params.Context, destination); err != nil { return nil, err } } else { - if _, err = server.CreateKeyAndLock(ctx, destination); err != nil { + if _, err = params.CreateKeyAndLock(params.Context, destination); err != nil { return nil, err } } - defer server.KeyUnlock(ctx, destination) + defer params.KeyUnlock(params.Context, destination) - if err = server.SetValue(ctx, destination, diff); err != nil { + if err = params.SetValue(params.Context, destination, diff); err != nil { return nil, err } return []byte(fmt.Sprintf(":%d\r\n", diff.Cardinality())), nil } -func handleZINCRBY(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := zincrbyKeyFunc(cmd) +func handleZINCRBY(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := zincrbyKeyFunc(params.Command) if err != nil { return nil, err } key := keys.WriteKeys[0] - member := sorted_set.Value(cmd[3]) - var increment sorted_set.Score + member := Value(params.Command[3]) + var increment Score - switch internal.AdaptType(cmd[2]).(type) { + switch internal.AdaptType(params.Command[2]).(type) { default: return nil, errors.New("increment must be a double") case string: - if strings.EqualFold("-inf", strings.ToLower(cmd[2])) { - increment = sorted_set.Score(math.Inf(-1)) - } else if strings.EqualFold("+inf", strings.ToLower(cmd[2])) { - increment = sorted_set.Score(math.Inf(1)) + if strings.EqualFold("-inf", strings.ToLower(params.Command[2])) { + increment = Score(math.Inf(-1)) + } else if strings.EqualFold("+inf", strings.ToLower(params.Command[2])) { + increment = Score(math.Inf(1)) } else { return nil, errors.New("increment must be a double") } case float64: - s, _ := internal.AdaptType(cmd[2]).(float64) - increment = sorted_set.Score(s) + s, _ := internal.AdaptType(params.Command[2]).(float64) + increment = Score(s) case int: - s, _ := internal.AdaptType(cmd[2]).(int) - increment = sorted_set.Score(s) + s, _ := internal.AdaptType(params.Command[2]).(int) + increment = Score(s) } - if !server.KeyExists(ctx, key) { + if !params.KeyExists(params.Context, key) { // If the key does not exist, create a new sorted set at the key with // the member and increment as the first value - if _, err = server.CreateKeyAndLock(ctx, key); err != nil { + if _, err = params.CreateKeyAndLock(params.Context, key); err != nil { return nil, err } - if err = server.SetValue( - ctx, + if err = params.SetValue( + params.Context, key, - sorted_set.NewSortedSet([]sorted_set.MemberParam{{Value: member, Score: increment}}), + NewSortedSet([]MemberParam{{Value: member, Score: increment}}), ); err != nil { return nil, err } - server.KeyUnlock(ctx, key) + params.KeyUnlock(params.Context, key) return []byte(fmt.Sprintf("+%s\r\n", strconv.FormatFloat(float64(increment), 'f', -1, 64))), nil } - if _, err = server.KeyLock(ctx, key); err != nil { + if _, err = params.KeyLock(params.Context, key); err != nil { return nil, err } - defer server.KeyUnlock(ctx, key) - set, ok := server.GetValue(ctx, key).(*sorted_set.SortedSet) + defer params.KeyUnlock(params.Context, key) + set, ok := params.GetValue(params.Context, key).(*SortedSet) if !ok { return nil, fmt.Errorf("value at %s is not a sorted set", key) } if _, err = set.AddOrUpdate( - []sorted_set.MemberParam{ + []MemberParam{ {Value: member, Score: increment}}, "xx", nil, @@ -524,13 +520,13 @@ func handleZINCRBY(ctx context.Context, cmd []string, server types.EchoVault, _ strconv.FormatFloat(float64(set.Get(member).Score), 'f', -1, 64))), nil } -func handleZINTER(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - _, err := zinterKeyFunc(cmd) +func handleZINTER(params internal.HandlerFuncParams) ([]byte, error) { + _, err := zinterKeyFunc(params.Command) if err != nil { return nil, err } - keys, weights, aggregate, withscores, err := extractKeysWeightsAggregateWithScores(cmd) + keys, weights, aggregate, withscores, err := extractKeysWeightsAggregateWithScores(params.Command) if err != nil { return nil, err } @@ -539,33 +535,33 @@ func handleZINTER(ctx context.Context, cmd []string, server types.EchoVault, _ * defer func() { for key, locked := range locks { if locked { - server.KeyRUnlock(ctx, key) + params.KeyRUnlock(params.Context, key) } } }() - var setParams []sorted_set.SortedSetParam + var setParams []SortedSetParam for i := 0; i < len(keys); i++ { - if !server.KeyExists(ctx, keys[i]) { + if !params.KeyExists(params.Context, keys[i]) { // If any of the keys is non-existent, return an empty array as there's no intersect return []byte("*0\r\n"), nil } - if _, err = server.KeyRLock(ctx, keys[i]); err != nil { + if _, err = params.KeyRLock(params.Context, keys[i]); err != nil { return nil, err } locks[keys[i]] = true - set, ok := server.GetValue(ctx, keys[i]).(*sorted_set.SortedSet) + set, ok := params.GetValue(params.Context, keys[i]).(*SortedSet) if !ok { return nil, fmt.Errorf("value at %s is not a sorted set", keys[i]) } - setParams = append(setParams, sorted_set.SortedSetParam{ + setParams = append(setParams, SortedSetParam{ Set: set, Weight: weights[i], }) } - intersect := sorted_set.Intersect(aggregate, setParams...) + intersect := Intersect(aggregate, setParams...) res := fmt.Sprintf("*%d", intersect.Cardinality()) @@ -584,8 +580,8 @@ func handleZINTER(ctx context.Context, cmd []string, server types.EchoVault, _ * return []byte(res), nil } -func handleZINTERSTORE(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - k, err := zinterstoreKeyFunc(cmd) +func handleZINTERSTORE(params internal.HandlerFuncParams) ([]byte, error) { + k, err := zinterstoreKeyFunc(params.Command) if err != nil { return nil, err } @@ -593,7 +589,7 @@ func handleZINTERSTORE(ctx context.Context, cmd []string, server types.EchoVault destination := k.WriteKeys[0] // Remove the destination keys from the command before parsing it - cmd = slices.DeleteFunc(cmd, func(s string) bool { + cmd := slices.DeleteFunc(params.Command, func(s string) bool { return s == destination }) @@ -606,53 +602,53 @@ func handleZINTERSTORE(ctx context.Context, cmd []string, server types.EchoVault defer func() { for key, locked := range locks { if locked { - server.KeyRUnlock(ctx, key) + params.KeyRUnlock(params.Context, key) } } }() - var setParams []sorted_set.SortedSetParam + var setParams []SortedSetParam for i := 0; i < len(keys); i++ { - if !server.KeyExists(ctx, keys[i]) { + if !params.KeyExists(params.Context, keys[i]) { return []byte(":0\r\n"), nil } - if _, err = server.KeyRLock(ctx, keys[i]); err != nil { + if _, err = params.KeyRLock(params.Context, keys[i]); err != nil { return nil, err } locks[keys[i]] = true - set, ok := server.GetValue(ctx, keys[i]).(*sorted_set.SortedSet) + set, ok := params.GetValue(params.Context, keys[i]).(*SortedSet) if !ok { return nil, fmt.Errorf("value at %s is not a sorted set", keys[i]) } - setParams = append(setParams, sorted_set.SortedSetParam{ + setParams = append(setParams, SortedSetParam{ Set: set, Weight: weights[i], }) } - intersect := sorted_set.Intersect(aggregate, setParams...) + intersect := Intersect(aggregate, setParams...) - if server.KeyExists(ctx, destination) && intersect.Cardinality() > 0 { - if _, err = server.KeyLock(ctx, destination); err != nil { + if params.KeyExists(params.Context, destination) && intersect.Cardinality() > 0 { + if _, err = params.KeyLock(params.Context, destination); err != nil { return nil, err } } else if intersect.Cardinality() > 0 { - if _, err = server.CreateKeyAndLock(ctx, destination); err != nil { + if _, err = params.CreateKeyAndLock(params.Context, destination); err != nil { return nil, err } } - defer server.KeyUnlock(ctx, destination) + defer params.KeyUnlock(params.Context, destination) - if err = server.SetValue(ctx, destination, intersect); err != nil { + if err = params.SetValue(params.Context, destination, intersect); err != nil { return nil, err } return []byte(fmt.Sprintf(":%d\r\n", intersect.Cardinality())), nil } -func handleZMPOP(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := zmpopKeyFunc(cmd) +func handleZMPOP(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := zmpopKeyFunc(params.Command) if err != nil { return nil, err } @@ -662,17 +658,17 @@ func handleZMPOP(ctx context.Context, cmd []string, server types.EchoVault, _ *n modifierIdx := -1 // Parse COUNT from command - countIdx := slices.IndexFunc(cmd, func(s string) bool { + countIdx := slices.IndexFunc(params.Command, func(s string) bool { return strings.ToLower(s) == "count" }) if countIdx != -1 { if countIdx < 2 { return nil, errors.New(constants.WrongArgsResponse) } - if countIdx == len(cmd)-1 { + if countIdx == len(params.Command)-1 { return nil, errors.New("count must be a positive integer") } - c, err := strconv.Atoi(cmd[countIdx+1]) + c, err := strconv.Atoi(params.Command[countIdx+1]) if err != nil { return nil, err } @@ -684,35 +680,35 @@ func handleZMPOP(ctx context.Context, cmd []string, server types.EchoVault, _ *n } // Parse MIN/MAX from the command - policyIdx := slices.IndexFunc(cmd, func(s string) bool { + policyIdx := slices.IndexFunc(params.Command, func(s string) bool { return slices.Contains([]string{"min", "max"}, strings.ToLower(s)) }) if policyIdx != -1 { if policyIdx < 2 { return nil, errors.New(constants.WrongArgsResponse) } - policy = strings.ToLower(cmd[policyIdx]) + policy = strings.ToLower(params.Command[policyIdx]) if modifierIdx == -1 || (policyIdx < modifierIdx) { modifierIdx = policyIdx } } for i := 0; i < len(keys.WriteKeys); i++ { - if server.KeyExists(ctx, keys.WriteKeys[i]) { - if _, err = server.KeyLock(ctx, keys.WriteKeys[i]); err != nil { + if params.KeyExists(params.Context, keys.WriteKeys[i]) { + if _, err = params.KeyLock(params.Context, keys.WriteKeys[i]); err != nil { continue } - v, ok := server.GetValue(ctx, keys.WriteKeys[i]).(*sorted_set.SortedSet) + v, ok := params.GetValue(params.Context, keys.WriteKeys[i]).(*SortedSet) if !ok || v.Cardinality() == 0 { - server.KeyUnlock(ctx, keys.WriteKeys[i]) + params.KeyUnlock(params.Context, keys.WriteKeys[i]) continue } popped, err := v.Pop(count, policy) if err != nil { - server.KeyUnlock(ctx, keys.WriteKeys[i]) + params.KeyUnlock(params.Context, keys.WriteKeys[i]) return nil, err } - server.KeyUnlock(ctx, keys.WriteKeys[i]) + params.KeyUnlock(params.Context, keys.WriteKeys[i]) res := fmt.Sprintf("*%d", popped.Cardinality()) @@ -729,8 +725,8 @@ func handleZMPOP(ctx context.Context, cmd []string, server types.EchoVault, _ *n return []byte("*0\r\n"), nil } -func handleZPOP(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := zpopKeyFunc(cmd) +func handleZPOP(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := zpopKeyFunc(params.Command) if err != nil { return nil, err } @@ -739,12 +735,12 @@ func handleZPOP(ctx context.Context, cmd []string, server types.EchoVault, _ *ne count := 1 policy := "min" - if strings.EqualFold(cmd[0], "zpopmax") { + if strings.EqualFold(params.Command[0], "zpopmax") { policy = "max" } - if len(cmd) == 3 { - c, err := strconv.Atoi(cmd[2]) + if len(params.Command) == 3 { + c, err := strconv.Atoi(params.Command[2]) if err != nil { return nil, err } @@ -753,16 +749,16 @@ func handleZPOP(ctx context.Context, cmd []string, server types.EchoVault, _ *ne } } - if !server.KeyExists(ctx, key) { + if !params.KeyExists(params.Context, key) { return []byte("*0\r\n"), nil } - if _, err = server.KeyLock(ctx, key); err != nil { + if _, err = params.KeyLock(params.Context, key); err != nil { return nil, err } - defer server.KeyUnlock(ctx, key) + defer params.KeyUnlock(params.Context, key) - set, ok := server.GetValue(ctx, key).(*sorted_set.SortedSet) + set, ok := params.GetValue(params.Context, key).(*SortedSet) if !ok { return nil, fmt.Errorf("value at key %s is not a sorted set", key) } @@ -782,36 +778,36 @@ func handleZPOP(ctx context.Context, cmd []string, server types.EchoVault, _ *ne return []byte(res), nil } -func handleZMSCORE(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := zmscoreKeyFunc(cmd) +func handleZMSCORE(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := zmscoreKeyFunc(params.Command) if err != nil { return nil, err } key := keys.ReadKeys[0] - if !server.KeyExists(ctx, key) { + if !params.KeyExists(params.Context, key) { return []byte("*0\r\n"), nil } - if _, err = server.KeyRLock(ctx, key); err != nil { + if _, err = params.KeyRLock(params.Context, key); err != nil { return nil, err } - defer server.KeyRUnlock(ctx, key) + defer params.KeyRUnlock(params.Context, key) - set, ok := server.GetValue(ctx, key).(*sorted_set.SortedSet) + set, ok := params.GetValue(params.Context, key).(*SortedSet) if !ok { return nil, fmt.Errorf("value at %s is not a sorted set", key) } - members := cmd[2:] + members := params.Command[2:] res := fmt.Sprintf("*%d", len(members)) - var member sorted_set.MemberObject + var member MemberObject for i := 0; i < len(members); i++ { - member = set.Get(sorted_set.Value(members[i])) + member = set.Get(Value(members[i])) if !member.Exists { res = fmt.Sprintf("%s\r\n$-1", res) } else { @@ -824,8 +820,8 @@ func handleZMSCORE(ctx context.Context, cmd []string, server types.EchoVault, _ return []byte(res), nil } -func handleZRANDMEMBER(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := zrandmemberKeyFunc(cmd) +func handleZRANDMEMBER(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := zrandmemberKeyFunc(params.Command) if err != nil { return nil, err } @@ -833,8 +829,8 @@ func handleZRANDMEMBER(ctx context.Context, cmd []string, server types.EchoVault key := keys.ReadKeys[0] count := 1 - if len(cmd) >= 3 { - c, err := strconv.Atoi(cmd[2]) + if len(params.Command) >= 3 { + c, err := strconv.Atoi(params.Command[2]) if err != nil { return nil, errors.New("count must be an integer") } @@ -844,24 +840,24 @@ func handleZRANDMEMBER(ctx context.Context, cmd []string, server types.EchoVault } withscores := false - if len(cmd) == 4 { - if strings.EqualFold(cmd[3], "withscores") { + if len(params.Command) == 4 { + if strings.EqualFold(params.Command[3], "withscores") { withscores = true } else { return nil, errors.New("last option must be WITHSCORES") } } - if !server.KeyExists(ctx, key) { + if !params.KeyExists(params.Context, key) { return []byte("$-1\r\n"), nil } - if _, err = server.KeyRLock(ctx, key); err != nil { + if _, err = params.KeyRLock(params.Context, key); err != nil { return nil, err } - defer server.KeyRUnlock(ctx, key) + defer params.KeyRUnlock(params.Context, key) - set, ok := server.GetValue(ctx, key).(*sorted_set.SortedSet) + set, ok := params.GetValue(params.Context, key).(*SortedSet) if !ok { return nil, fmt.Errorf("value at %s is not a sorted set", key) } @@ -882,44 +878,44 @@ func handleZRANDMEMBER(ctx context.Context, cmd []string, server types.EchoVault return []byte(res), nil } -func handleZRANK(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := zrankKeyFunc(cmd) +func handleZRANK(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := zrankKeyFunc(params.Command) if err != nil { return nil, err } key := keys.ReadKeys[0] - member := cmd[2] + member := params.Command[2] withscores := false - if len(cmd) == 4 && strings.EqualFold(cmd[3], "withscores") { + if len(params.Command) == 4 && strings.EqualFold(params.Command[3], "withscores") { withscores = true } - if !server.KeyExists(ctx, key) { + if !params.KeyExists(params.Context, key) { return []byte("$-1\r\n"), nil } - if _, err = server.KeyRLock(ctx, key); err != nil { + if _, err = params.KeyRLock(params.Context, key); err != nil { return nil, err } - defer server.KeyRUnlock(ctx, key) + defer params.KeyRUnlock(params.Context, key) - set, ok := server.GetValue(ctx, key).(*sorted_set.SortedSet) + set, ok := params.GetValue(params.Context, key).(*SortedSet) if !ok { return nil, fmt.Errorf("value at %s is not a sorted set", key) } members := set.GetAll() - slices.SortFunc(members, func(a, b sorted_set.MemberParam) int { - if strings.EqualFold(cmd[0], "zrevrank") { + slices.SortFunc(members, func(a, b MemberParam) int { + if strings.EqualFold(params.Command[0], "zrevrank") { return cmp.Compare(b.Score, a.Score) } return cmp.Compare(a.Score, b.Score) }) for i := 0; i < len(members); i++ { - if members[i].Value == sorted_set.Value(member) { + if members[i].Value == Value(member) { if withscores { score := strconv.FormatFloat(float64(members[i].Score), 'f', -1, 64) return []byte(fmt.Sprintf("*2\r\n:%d\r\n$%d\r\n%s\r\n", i, len(score), score)), nil @@ -932,31 +928,31 @@ func handleZRANK(ctx context.Context, cmd []string, server types.EchoVault, _ *n return []byte("$-1\r\n"), nil } -func handleZREM(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := zremKeyFunc(cmd) +func handleZREM(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := zremKeyFunc(params.Command) if err != nil { return nil, err } key := keys.WriteKeys[0] - if !server.KeyExists(ctx, key) { + if !params.KeyExists(params.Context, key) { return []byte(":0\r\n"), nil } - if _, err = server.KeyLock(ctx, key); err != nil { + if _, err = params.KeyLock(params.Context, key); err != nil { return nil, err } - defer server.KeyUnlock(ctx, key) + defer params.KeyUnlock(params.Context, key) - set, ok := server.GetValue(ctx, key).(*sorted_set.SortedSet) + set, ok := params.GetValue(params.Context, key).(*SortedSet) if !ok { return nil, fmt.Errorf("value at %s is not a sorted set", key) } deletedCount := 0 - for _, m := range cmd[2:] { - if set.Remove(sorted_set.Value(m)) { + for _, m := range params.Command[2:] { + if set.Remove(Value(m)) { deletedCount += 1 } } @@ -964,26 +960,26 @@ func handleZREM(ctx context.Context, cmd []string, server types.EchoVault, _ *ne return []byte(fmt.Sprintf(":%d\r\n", deletedCount)), nil } -func handleZSCORE(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := zscoreKeyFunc(cmd) +func handleZSCORE(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := zscoreKeyFunc(params.Command) if err != nil { return nil, err } key := keys.ReadKeys[0] - if !server.KeyExists(ctx, key) { + if !params.KeyExists(params.Context, key) { return []byte("$-1\r\n"), nil } - if _, err = server.KeyRLock(ctx, key); err != nil { + if _, err = params.KeyRLock(params.Context, key); err != nil { return nil, err } - defer server.KeyRUnlock(ctx, key) - set, ok := server.GetValue(ctx, key).(*sorted_set.SortedSet) + defer params.KeyRUnlock(params.Context, key) + set, ok := params.GetValue(params.Context, key).(*SortedSet) if !ok { return nil, fmt.Errorf("value at %s is not a sorted set", key) } - member := set.Get(sorted_set.Value(cmd[2])) + member := set.Get(Value(params.Command[2])) if !member.Exists { return []byte("$-1\r\n"), nil } @@ -993,8 +989,8 @@ func handleZSCORE(ctx context.Context, cmd []string, server types.EchoVault, _ * return []byte(fmt.Sprintf("$%d\r\n%s\r\n", len(score), score)), nil } -func handleZREMRANGEBYSCORE(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := zremrangebyscoreKeyFunc(cmd) +func handleZREMRANGEBYSCORE(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := zremrangebyscoreKeyFunc(params.Command) if err != nil { return nil, err } @@ -1003,32 +999,32 @@ func handleZREMRANGEBYSCORE(ctx context.Context, cmd []string, server types.Echo deletedCount := 0 - minimum, err := strconv.ParseFloat(cmd[2], 64) + minimum, err := strconv.ParseFloat(params.Command[2], 64) if err != nil { return nil, err } - maximum, err := strconv.ParseFloat(cmd[3], 64) + maximum, err := strconv.ParseFloat(params.Command[3], 64) if err != nil { return nil, err } - if !server.KeyExists(ctx, key) { + if !params.KeyExists(params.Context, key) { return []byte(":0\r\n"), nil } - if _, err = server.KeyLock(ctx, key); err != nil { + if _, err = params.KeyLock(params.Context, key); err != nil { return nil, err } - defer server.KeyUnlock(ctx, key) + defer params.KeyUnlock(params.Context, key) - set, ok := server.GetValue(ctx, key).(*sorted_set.SortedSet) + set, ok := params.GetValue(params.Context, key).(*SortedSet) if !ok { return nil, fmt.Errorf("value at %s is not a sorted set", key) } for _, m := range set.GetAll() { - if m.Score >= sorted_set.Score(minimum) && m.Score <= sorted_set.Score(maximum) { + if m.Score >= Score(minimum) && m.Score <= Score(maximum) { set.Remove(m.Value) deletedCount += 1 } @@ -1037,34 +1033,34 @@ func handleZREMRANGEBYSCORE(ctx context.Context, cmd []string, server types.Echo return []byte(fmt.Sprintf(":%d\r\n", deletedCount)), nil } -func handleZREMRANGEBYRANK(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := zremrangebyrankKeyFunc(cmd) +func handleZREMRANGEBYRANK(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := zremrangebyrankKeyFunc(params.Command) if err != nil { return nil, err } key := keys.WriteKeys[0] - start, err := strconv.Atoi(cmd[2]) + start, err := strconv.Atoi(params.Command[2]) if err != nil { return nil, err } - stop, err := strconv.Atoi(cmd[3]) + stop, err := strconv.Atoi(params.Command[3]) if err != nil { return nil, err } - if !server.KeyExists(ctx, key) { + if !params.KeyExists(params.Context, key) { return []byte(":0\r\n"), nil } - if _, err = server.KeyLock(ctx, key); err != nil { + if _, err = params.KeyLock(params.Context, key); err != nil { return nil, err } - defer server.KeyUnlock(ctx, key) + defer params.KeyUnlock(params.Context, key) - set, ok := server.GetValue(ctx, key).(*sorted_set.SortedSet) + set, ok := params.GetValue(params.Context, key).(*SortedSet) if !ok { return nil, fmt.Errorf("value at %s is not a sorted set", key) } @@ -1081,7 +1077,7 @@ func handleZREMRANGEBYRANK(ctx context.Context, cmd []string, server types.EchoV } members := set.GetAll() - slices.SortFunc(members, func(a, b sorted_set.MemberParam) int { + slices.SortFunc(members, func(a, b MemberParam) int { return cmp.Compare(a.Score, b.Score) }) @@ -1102,26 +1098,26 @@ func handleZREMRANGEBYRANK(ctx context.Context, cmd []string, server types.EchoV return []byte(fmt.Sprintf(":%d\r\n", deletedCount)), nil } -func handleZREMRANGEBYLEX(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := zremrangebylexKeyFunc(cmd) +func handleZREMRANGEBYLEX(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := zremrangebylexKeyFunc(params.Command) if err != nil { return nil, err } key := keys.WriteKeys[0] - minimum := cmd[2] - maximum := cmd[3] + minimum := params.Command[2] + maximum := params.Command[3] - if !server.KeyExists(ctx, key) { + if !params.KeyExists(params.Context, key) { return []byte(":0\r\n"), nil } - if _, err = server.KeyLock(ctx, key); err != nil { + if _, err = params.KeyLock(params.Context, key); err != nil { return nil, err } - defer server.KeyUnlock(ctx, key) + defer params.KeyUnlock(params.Context, key) - set, ok := server.GetValue(ctx, key).(*sorted_set.SortedSet) + set, ok := params.GetValue(params.Context, key).(*SortedSet) if !ok { return nil, fmt.Errorf("value at %s is not a sorted set", key) } @@ -1149,77 +1145,77 @@ func handleZREMRANGEBYLEX(ctx context.Context, cmd []string, server types.EchoVa return []byte(fmt.Sprintf(":%d\r\n", deletedCount)), nil } -func handleZRANGE(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := zrangeKeyCount(cmd) +func handleZRANGE(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := zrangeKeyCount(params.Command) if err != nil { return nil, err } key := keys.ReadKeys[0] policy := "byscore" - scoreStart := math.Inf(-1) // Lower bound if policy is "byscore" - scoreStop := math.Inf(1) // Upper bound if policy is "byscore" - lexStart := cmd[2] // Lower bound if policy is "bylex" - lexStop := cmd[3] // Upper bound if policy is "bylex" + scoreStart := math.Inf(-1) // Lower bound if policy is "byscore" + scoreStop := math.Inf(1) // Upper bound if policy is "byscore" + lexStart := params.Command[2] // Lower bound if policy is "bylex" + lexStop := params.Command[3] // Upper bound if policy is "bylex" offset := 0 count := -1 - withscores := slices.ContainsFunc(cmd[4:], func(s string) bool { + withscores := slices.ContainsFunc(params.Command[4:], func(s string) bool { return strings.EqualFold(s, "withscores") }) - reverse := slices.ContainsFunc(cmd[4:], func(s string) bool { + reverse := slices.ContainsFunc(params.Command[4:], func(s string) bool { return strings.EqualFold(s, "rev") }) - if slices.ContainsFunc(cmd[4:], func(s string) bool { + if slices.ContainsFunc(params.Command[4:], func(s string) bool { return strings.EqualFold(s, "bylex") }) { policy = "bylex" } else { // policy is "byscore" make sure start and stop are valid float values - scoreStart, err = strconv.ParseFloat(cmd[2], 64) + scoreStart, err = strconv.ParseFloat(params.Command[2], 64) if err != nil { return nil, err } - scoreStop, err = strconv.ParseFloat(cmd[3], 64) + scoreStop, err = strconv.ParseFloat(params.Command[3], 64) if err != nil { return nil, err } } - if slices.ContainsFunc(cmd[4:], func(s string) bool { + if slices.ContainsFunc(params.Command[4:], func(s string) bool { return strings.EqualFold(s, "limit") }) { - limitIdx := slices.IndexFunc(cmd[4:], func(s string) bool { + limitIdx := slices.IndexFunc(params.Command[4:], func(s string) bool { return strings.EqualFold(s, "limit") }) - if limitIdx != -1 && limitIdx > len(cmd[4:])-3 { + if limitIdx != -1 && limitIdx > len(params.Command[4:])-3 { return nil, errors.New("limit should contain offset and count as integers") } - offset, err = strconv.Atoi(cmd[4:][limitIdx+1]) + offset, err = strconv.Atoi(params.Command[4:][limitIdx+1]) if err != nil { return nil, errors.New("limit offset must be integer") } if offset < 0 { return nil, errors.New("limit offset must be >= 0") } - count, err = strconv.Atoi(cmd[4:][limitIdx+2]) + count, err = strconv.Atoi(params.Command[4:][limitIdx+2]) if err != nil { return nil, errors.New("limit count must be integer") } } - if !server.KeyExists(ctx, key) { + if !params.KeyExists(params.Context, key) { return []byte("*0\r\n"), nil } - if _, err = server.KeyRLock(ctx, key); err != nil { + if _, err = params.KeyRLock(params.Context, key); err != nil { return nil, err } - defer server.KeyRUnlock(ctx, key) + defer params.KeyRUnlock(params.Context, key) - set, ok := server.GetValue(ctx, key).(*sorted_set.SortedSet) + set, ok := params.GetValue(params.Context, key).(*SortedSet) if !ok { return nil, fmt.Errorf("value at %s is not a sorted set", key) } @@ -1233,7 +1229,7 @@ func handleZRANGE(ctx context.Context, cmd []string, server types.EchoVault, _ * members := set.GetAll() if strings.EqualFold(policy, "byscore") { - slices.SortFunc(members, func(a, b sorted_set.MemberParam) int { + slices.SortFunc(members, func(a, b MemberParam) int { // Do a score sort if reverse { return cmp.Compare(b.Score, a.Score) @@ -1248,7 +1244,7 @@ func handleZRANGE(ctx context.Context, cmd []string, server types.EchoVault, _ * return []byte("*0\r\n"), nil } } - slices.SortFunc(members, func(a, b sorted_set.MemberParam) int { + slices.SortFunc(members, func(a, b MemberParam) int { if reverse { return internal.CompareLex(string(b.Value), string(a.Value)) } @@ -1256,14 +1252,14 @@ func handleZRANGE(ctx context.Context, cmd []string, server types.EchoVault, _ * }) } - var resultMembers []sorted_set.MemberParam + var resultMembers []MemberParam for i := offset; i <= count; i++ { if i >= len(members) { break } if strings.EqualFold(policy, "byscore") { - if members[i].Score >= sorted_set.Score(scoreStart) && members[i].Score <= sorted_set.Score(scoreStop) { + if members[i].Score >= Score(scoreStart) && members[i].Score <= Score(scoreStop) { resultMembers = append(resultMembers, members[i]) } continue @@ -1289,8 +1285,8 @@ func handleZRANGE(ctx context.Context, cmd []string, server types.EchoVault, _ * return []byte(res), nil } -func handleZRANGESTORE(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := zrangeStoreKeyFunc(cmd) +func handleZRANGESTORE(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := zrangeStoreKeyFunc(params.Command) if err != nil { return nil, err } @@ -1298,65 +1294,65 @@ func handleZRANGESTORE(ctx context.Context, cmd []string, server types.EchoVault destination := keys.WriteKeys[0] source := keys.ReadKeys[0] policy := "byscore" - scoreStart := math.Inf(-1) // Lower bound if policy is "byscore" - scoreStop := math.Inf(1) // Upper bound if policy is "byfloat" - lexStart := cmd[3] // Lower bound if policy is "bylex" - lexStop := cmd[4] // Upper bound if policy is "bylex" + scoreStart := math.Inf(-1) // Lower bound if policy is "byscore" + scoreStop := math.Inf(1) // Upper bound if policy is "byfloat" + lexStart := params.Command[3] // Lower bound if policy is "bylex" + lexStop := params.Command[4] // Upper bound if policy is "bylex" offset := 0 count := -1 - reverse := slices.ContainsFunc(cmd[5:], func(s string) bool { + reverse := slices.ContainsFunc(params.Command[5:], func(s string) bool { return strings.EqualFold(s, "rev") }) - if slices.ContainsFunc(cmd[5:], func(s string) bool { + if slices.ContainsFunc(params.Command[5:], func(s string) bool { return strings.EqualFold(s, "bylex") }) { policy = "bylex" } else { // policy is "byscore" make sure start and stop are valid float values - scoreStart, err = strconv.ParseFloat(cmd[3], 64) + scoreStart, err = strconv.ParseFloat(params.Command[3], 64) if err != nil { return nil, err } - scoreStop, err = strconv.ParseFloat(cmd[4], 64) + scoreStop, err = strconv.ParseFloat(params.Command[4], 64) if err != nil { return nil, err } } - if slices.ContainsFunc(cmd[5:], func(s string) bool { + if slices.ContainsFunc(params.Command[5:], func(s string) bool { return strings.EqualFold(s, "limit") }) { - limitIdx := slices.IndexFunc(cmd[5:], func(s string) bool { + limitIdx := slices.IndexFunc(params.Command[5:], func(s string) bool { return strings.EqualFold(s, "limit") }) - if limitIdx != -1 && limitIdx > len(cmd[5:])-3 { + if limitIdx != -1 && limitIdx > len(params.Command[5:])-3 { return nil, errors.New("limit should contain offset and count as integers") } - offset, err = strconv.Atoi(cmd[5:][limitIdx+1]) + offset, err = strconv.Atoi(params.Command[5:][limitIdx+1]) if err != nil { return nil, errors.New("limit offset must be integer") } if offset < 0 { return nil, errors.New("limit offset must be >= 0") } - count, err = strconv.Atoi(cmd[5:][limitIdx+2]) + count, err = strconv.Atoi(params.Command[5:][limitIdx+2]) if err != nil { return nil, errors.New("limit count must be integer") } } - if !server.KeyExists(ctx, source) { + if !params.KeyExists(params.Context, source) { return []byte("*0\r\n"), nil } - if _, err = server.KeyRLock(ctx, source); err != nil { + if _, err = params.KeyRLock(params.Context, source); err != nil { return nil, err } - defer server.KeyRUnlock(ctx, source) + defer params.KeyRUnlock(params.Context, source) - set, ok := server.GetValue(ctx, source).(*sorted_set.SortedSet) + set, ok := params.GetValue(params.Context, source).(*SortedSet) if !ok { return nil, fmt.Errorf("value at %s is not a sorted set", source) } @@ -1370,7 +1366,7 @@ func handleZRANGESTORE(ctx context.Context, cmd []string, server types.EchoVault members := set.GetAll() if strings.EqualFold(policy, "byscore") { - slices.SortFunc(members, func(a, b sorted_set.MemberParam) int { + slices.SortFunc(members, func(a, b MemberParam) int { // Do a score sort if reverse { return cmp.Compare(b.Score, a.Score) @@ -1385,7 +1381,7 @@ func handleZRANGESTORE(ctx context.Context, cmd []string, server types.EchoVault return []byte(":0\r\n"), nil } } - slices.SortFunc(members, func(a, b sorted_set.MemberParam) int { + slices.SortFunc(members, func(a, b MemberParam) int { if reverse { return internal.CompareLex(string(b.Value), string(a.Value)) } @@ -1393,14 +1389,14 @@ func handleZRANGESTORE(ctx context.Context, cmd []string, server types.EchoVault }) } - var resultMembers []sorted_set.MemberParam + var resultMembers []MemberParam for i := offset; i <= count; i++ { if i >= len(members) { break } if strings.EqualFold(policy, "byscore") { - if members[i].Score >= sorted_set.Score(scoreStart) && members[i].Score <= sorted_set.Score(scoreStop) { + if members[i].Score >= Score(scoreStart) && members[i].Score <= Score(scoreStop) { resultMembers = append(resultMembers, members[i]) } continue @@ -1411,32 +1407,32 @@ func handleZRANGESTORE(ctx context.Context, cmd []string, server types.EchoVault } } - newSortedSet := sorted_set.NewSortedSet(resultMembers) + newSortedSet := NewSortedSet(resultMembers) - if server.KeyExists(ctx, destination) { - if _, err = server.KeyLock(ctx, destination); err != nil { + if params.KeyExists(params.Context, destination) { + if _, err = params.KeyLock(params.Context, destination); err != nil { return nil, err } } else { - if _, err = server.CreateKeyAndLock(ctx, destination); err != nil { + if _, err = params.CreateKeyAndLock(params.Context, destination); err != nil { return nil, err } } - defer server.KeyUnlock(ctx, destination) + defer params.KeyUnlock(params.Context, destination) - if err = server.SetValue(ctx, destination, newSortedSet); err != nil { + if err = params.SetValue(params.Context, destination, newSortedSet); err != nil { return nil, err } return []byte(fmt.Sprintf(":%d\r\n", newSortedSet.Cardinality())), nil } -func handleZUNION(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - if _, err := zunionKeyFunc(cmd); err != nil { +func handleZUNION(params internal.HandlerFuncParams) ([]byte, error) { + if _, err := zunionKeyFunc(params.Command); err != nil { return nil, err } - keys, weights, aggregate, withscores, err := extractKeysWeightsAggregateWithScores(cmd) + keys, weights, aggregate, withscores, err := extractKeysWeightsAggregateWithScores(params.Command) if err != nil { return nil, err } @@ -1445,31 +1441,31 @@ func handleZUNION(ctx context.Context, cmd []string, server types.EchoVault, _ * defer func() { for key, locked := range locks { if locked { - server.KeyRUnlock(ctx, key) + params.KeyRUnlock(params.Context, key) } } }() - var setParams []sorted_set.SortedSetParam + var setParams []SortedSetParam for i := 0; i < len(keys); i++ { - if server.KeyExists(ctx, keys[i]) { - if _, err = server.KeyRLock(ctx, keys[i]); err != nil { + if params.KeyExists(params.Context, keys[i]) { + if _, err = params.KeyRLock(params.Context, keys[i]); err != nil { return nil, err } locks[keys[i]] = true - set, ok := server.GetValue(ctx, keys[i]).(*sorted_set.SortedSet) + set, ok := params.GetValue(params.Context, keys[i]).(*SortedSet) if !ok { return nil, fmt.Errorf("value at %s is not a sorted set", keys[i]) } - setParams = append(setParams, sorted_set.SortedSetParam{ + setParams = append(setParams, SortedSetParam{ Set: set, Weight: weights[i], }) } } - union := sorted_set.Union(aggregate, setParams...) + union := Union(aggregate, setParams...) res := fmt.Sprintf("*%d", union.Cardinality()) for _, m := range union.GetAll() { @@ -1485,8 +1481,8 @@ func handleZUNION(ctx context.Context, cmd []string, server types.EchoVault, _ * return []byte(res), nil } -func handleZUNIONSTORE(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - k, err := zunionstoreKeyFunc(cmd) +func handleZUNIONSTORE(params internal.HandlerFuncParams) ([]byte, error) { + k, err := zunionstoreKeyFunc(params.Command) if err != nil { return nil, err } @@ -1494,11 +1490,11 @@ func handleZUNIONSTORE(ctx context.Context, cmd []string, server types.EchoVault destination := k.WriteKeys[0] // Remove destination key from list of keys - cmd = slices.DeleteFunc(cmd, func(s string) bool { + params.Command = slices.DeleteFunc(params.Command, func(s string) bool { return s == destination }) - keys, weights, aggregate, _, err := extractKeysWeightsAggregateWithScores(cmd) + keys, weights, aggregate, _, err := extractKeysWeightsAggregateWithScores(params.Command) if err != nil { return nil, err } @@ -1507,52 +1503,52 @@ func handleZUNIONSTORE(ctx context.Context, cmd []string, server types.EchoVault defer func() { for key, locked := range locks { if locked { - server.KeyRUnlock(ctx, key) + params.KeyRUnlock(params.Context, key) } } }() - var setParams []sorted_set.SortedSetParam + var setParams []SortedSetParam for i := 0; i < len(keys); i++ { - if server.KeyExists(ctx, keys[i]) { - if _, err = server.KeyRLock(ctx, keys[i]); err != nil { + if params.KeyExists(params.Context, keys[i]) { + if _, err = params.KeyRLock(params.Context, keys[i]); err != nil { return nil, err } locks[keys[i]] = true - set, ok := server.GetValue(ctx, keys[i]).(*sorted_set.SortedSet) + set, ok := params.GetValue(params.Context, keys[i]).(*SortedSet) if !ok { return nil, fmt.Errorf("value at %s is not a sorted set", keys[i]) } - setParams = append(setParams, sorted_set.SortedSetParam{ + setParams = append(setParams, SortedSetParam{ Set: set, Weight: weights[i], }) } } - union := sorted_set.Union(aggregate, setParams...) + union := Union(aggregate, setParams...) - if server.KeyExists(ctx, destination) { - if _, err = server.KeyLock(ctx, destination); err != nil { + if params.KeyExists(params.Context, destination) { + if _, err = params.KeyLock(params.Context, destination); err != nil { return nil, err } } else { - if _, err = server.CreateKeyAndLock(ctx, destination); err != nil { + if _, err = params.CreateKeyAndLock(params.Context, destination); err != nil { return nil, err } } - defer server.KeyUnlock(ctx, destination) + defer params.KeyUnlock(params.Context, destination) - if err = server.SetValue(ctx, destination, union); err != nil { + if err = params.SetValue(params.Context, destination, union); err != nil { return nil, err } return []byte(fmt.Sprintf(":%d\r\n", union.Cardinality())), nil } -func Commands() []types.Command { - return []types.Command{ +func Commands() []internal.Command { + return []internal.Command{ { Command: "zadd", Module: constants.SortedSetModule, diff --git a/pkg/modules/sorted_set/key_funcs.go b/internal/modules/sorted_set/key_funcs.go similarity index 55% rename from pkg/modules/sorted_set/key_funcs.go rename to internal/modules/sorted_set/key_funcs.go index 0a655834..42e8988e 100644 --- a/pkg/modules/sorted_set/key_funcs.go +++ b/internal/modules/sorted_set/key_funcs.go @@ -16,48 +16,48 @@ package sorted_set import ( "errors" + "github.com/echovault/echovault/internal" "github.com/echovault/echovault/pkg/constants" - "github.com/echovault/echovault/pkg/types" "slices" "strings" ) -func zaddKeyFunc(cmd []string) (types.AccessKeys, error) { +func zaddKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) < 4 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: cmd[1:2], }, nil } -func zcardKeyFunc(cmd []string) (types.AccessKeys, error) { +func zcardKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) != 2 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: cmd[1:], WriteKeys: make([]string, 0), }, nil } -func zcountKeyFunc(cmd []string) (types.AccessKeys, error) { +func zcountKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) != 4 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: cmd[1:2], WriteKeys: make([]string, 0), }, nil } -func zdiffKeyFunc(cmd []string) (types.AccessKeys, error) { +func zdiffKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) < 2 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } withscoresIndex := slices.IndexFunc(cmd, func(s string) bool { @@ -65,45 +65,45 @@ func zdiffKeyFunc(cmd []string) (types.AccessKeys, error) { }) if withscoresIndex == -1 { - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: cmd[1:], WriteKeys: make([]string, 0), }, nil } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: cmd[1:withscoresIndex], WriteKeys: make([]string, 0), }, nil } -func zdiffstoreKeyFunc(cmd []string) (types.AccessKeys, error) { +func zdiffstoreKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) < 3 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: cmd[2:], WriteKeys: cmd[1:2], }, nil } -func zincrbyKeyFunc(cmd []string) (types.AccessKeys, error) { +func zincrbyKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) != 4 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: cmd[1:2], }, nil } -func zinterKeyFunc(cmd []string) (types.AccessKeys, error) { +func zinterKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) < 2 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } endIdx := slices.IndexFunc(cmd[1:], func(s string) bool { if strings.EqualFold(s, "WEIGHTS") || @@ -114,25 +114,25 @@ func zinterKeyFunc(cmd []string) (types.AccessKeys, error) { return false }) if endIdx == -1 { - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: cmd[1:], WriteKeys: make([]string, 0), }, nil } if endIdx >= 1 { - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: cmd[1:endIdx], WriteKeys: make([]string, 0), }, nil } - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } -func zinterstoreKeyFunc(cmd []string) (types.AccessKeys, error) { +func zinterstoreKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) < 3 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } endIdx := slices.IndexFunc(cmd[1:], func(s string) bool { if strings.EqualFold(s, "WEIGHTS") || @@ -143,192 +143,192 @@ func zinterstoreKeyFunc(cmd []string) (types.AccessKeys, error) { return false }) if endIdx == -1 { - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: cmd[2:], WriteKeys: cmd[1:2], }, nil } if endIdx >= 3 { - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: cmd[2:endIdx], WriteKeys: cmd[1:2], }, nil } - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } -func zmpopKeyFunc(cmd []string) (types.AccessKeys, error) { +func zmpopKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) < 2 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } endIdx := slices.IndexFunc(cmd, func(s string) bool { return slices.Contains([]string{"MIN", "MAX", "COUNT"}, strings.ToUpper(s)) }) if endIdx == -1 { - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: cmd[1:], }, nil } if endIdx >= 2 { - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: cmd[1:endIdx], }, nil } - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } -func zmscoreKeyFunc(cmd []string) (types.AccessKeys, error) { +func zmscoreKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) < 3 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: cmd[1:2], WriteKeys: make([]string, 0), }, nil } -func zpopKeyFunc(cmd []string) (types.AccessKeys, error) { +func zpopKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) < 2 || len(cmd) > 3 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: cmd[1:2], }, nil } -func zrandmemberKeyFunc(cmd []string) (types.AccessKeys, error) { +func zrandmemberKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) < 2 || len(cmd) > 4 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: cmd[1:2], WriteKeys: make([]string, 0), }, nil } -func zrankKeyFunc(cmd []string) (types.AccessKeys, error) { +func zrankKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) < 3 || len(cmd) > 4 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: cmd[1:2], WriteKeys: make([]string, 0), }, nil } -func zremKeyFunc(cmd []string) (types.AccessKeys, error) { +func zremKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) < 3 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: cmd[1:2], }, nil } -func zrevrankKeyFunc(cmd []string) (types.AccessKeys, error) { +func zrevrankKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) < 3 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: cmd[1:2], WriteKeys: make([]string, 0), }, nil } -func zscoreKeyFunc(cmd []string) (types.AccessKeys, error) { +func zscoreKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) != 3 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: cmd[1:2], WriteKeys: make([]string, 0), }, nil } -func zremrangebylexKeyFunc(cmd []string) (types.AccessKeys, error) { +func zremrangebylexKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) != 4 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: cmd[1:2], }, nil } -func zremrangebyrankKeyFunc(cmd []string) (types.AccessKeys, error) { +func zremrangebyrankKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) != 4 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: cmd[1:2], }, nil } -func zremrangebyscoreKeyFunc(cmd []string) (types.AccessKeys, error) { +func zremrangebyscoreKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) != 4 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: cmd[1:2], }, nil } -func zlexcountKeyFunc(cmd []string) (types.AccessKeys, error) { +func zlexcountKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) != 4 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: cmd[1:2], WriteKeys: make([]string, 0), }, nil } -func zrangeKeyCount(cmd []string) (types.AccessKeys, error) { +func zrangeKeyCount(cmd []string) (internal.AccessKeys, error) { if len(cmd) < 4 || len(cmd) > 10 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: cmd[1:2], WriteKeys: make([]string, 0), }, nil } -func zrangeStoreKeyFunc(cmd []string) (types.AccessKeys, error) { +func zrangeStoreKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) < 5 || len(cmd) > 11 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: cmd[2:3], WriteKeys: cmd[1:2], }, nil } -func zunionKeyFunc(cmd []string) (types.AccessKeys, error) { +func zunionKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) < 2 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } endIdx := slices.IndexFunc(cmd[1:], func(s string) bool { if strings.EqualFold(s, "WEIGHTS") || @@ -339,25 +339,25 @@ func zunionKeyFunc(cmd []string) (types.AccessKeys, error) { return false }) if endIdx == -1 { - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: cmd[1:], WriteKeys: make([]string, 0), }, nil } if endIdx >= 1 { - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: cmd[1:endIdx], WriteKeys: cmd[1:endIdx], }, nil } - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } -func zunionstoreKeyFunc(cmd []string) (types.AccessKeys, error) { +func zunionstoreKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) < 3 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } endIdx := slices.IndexFunc(cmd[1:], func(s string) bool { if strings.EqualFold(s, "WEIGHTS") || @@ -368,18 +368,18 @@ func zunionstoreKeyFunc(cmd []string) (types.AccessKeys, error) { return false }) if endIdx == -1 { - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: cmd[2:], WriteKeys: cmd[1:2], }, nil } if endIdx >= 1 { - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: cmd[2:endIdx], WriteKeys: cmd[1:2], }, nil } - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } diff --git a/internal/sorted_set/sorted_set.go b/internal/modules/sorted_set/sorted_set.go similarity index 100% rename from internal/sorted_set/sorted_set.go rename to internal/modules/sorted_set/sorted_set.go diff --git a/pkg/modules/sorted_set/utils.go b/internal/modules/sorted_set/utils.go similarity index 62% rename from pkg/modules/sorted_set/utils.go rename to internal/modules/sorted_set/utils.go index bde1a246..11638e26 100644 --- a/pkg/modules/sorted_set/utils.go +++ b/internal/modules/sorted_set/utils.go @@ -90,3 +90,80 @@ func extractKeysWeightsAggregateWithScores(cmd []string) ([]string, []int, strin return keys, weights, aggregate, withscores, nil } + +func validateUpdatePolicy(updatePolicy interface{}) (string, error) { + if updatePolicy == nil { + return "", nil + } + err := errors.New("update policy must be a string of Value NX or XX") + policy, ok := updatePolicy.(string) + if !ok { + return "", err + } + if !slices.Contains([]string{"nx", "xx"}, strings.ToLower(policy)) { + return "", err + } + return policy, nil +} + +func validateComparison(comparison interface{}) (string, error) { + if comparison == nil { + return "", nil + } + err := errors.New("comparison condition must be a string of Value LT or GT") + comp, ok := comparison.(string) + if !ok { + return "", err + } + if !slices.Contains([]string{"lt", "gt"}, strings.ToLower(comp)) { + return "", err + } + return comp, nil +} + +func validateChanged(changed interface{}) (string, error) { + if changed == nil { + return "", nil + } + err := errors.New("changed condition should be a string of Value CH") + ch, ok := changed.(string) + if !ok { + return "", err + } + if !strings.EqualFold(ch, "ch") { + return "", err + } + return ch, nil +} + +func validateIncr(incr interface{}) (string, error) { + if incr == nil { + return "", nil + } + err := errors.New("incr condition should be a string of Value INCR") + i, ok := incr.(string) + if !ok { + return "", err + } + if !strings.EqualFold(i, "incr") { + return "", err + } + return i, nil +} + +func compareScores(old Score, new Score, comp string) Score { + switch strings.ToLower(comp) { + default: + return new + case "lt": + if new < old { + return new + } + return old + case "gt": + if new > old { + return new + } + return old + } +} diff --git a/pkg/modules/string/commands.go b/internal/modules/string/commands.go similarity index 73% rename from pkg/modules/string/commands.go rename to internal/modules/string/commands.go index b1586a88..ce3ce904 100644 --- a/pkg/modules/string/commands.go +++ b/internal/modules/string/commands.go @@ -15,47 +15,44 @@ package str import ( - "context" "errors" "fmt" "github.com/echovault/echovault/internal" "github.com/echovault/echovault/pkg/constants" - "github.com/echovault/echovault/pkg/types" - "net" ) -func handleSetRange(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := setRangeKeyFunc(cmd) +func handleSetRange(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := setRangeKeyFunc(params.Command) if err != nil { return nil, err } key := keys.WriteKeys[0] - offset, ok := internal.AdaptType(cmd[2]).(int) + offset, ok := internal.AdaptType(params.Command[2]).(int) if !ok { return nil, errors.New("offset must be an integer") } - newStr := cmd[3] + newStr := params.Command[3] - if !server.KeyExists(ctx, key) { - if _, err = server.CreateKeyAndLock(ctx, key); err != nil { + if !params.KeyExists(params.Context, key) { + if _, err = params.CreateKeyAndLock(params.Context, key); err != nil { return nil, err } - if err = server.SetValue(ctx, key, newStr); err != nil { + if err = params.SetValue(params.Context, key, newStr); err != nil { return nil, err } - server.KeyUnlock(ctx, key) + params.KeyUnlock(params.Context, key) return []byte(fmt.Sprintf(":%d\r\n", len(newStr))), nil } - if _, err := server.KeyLock(ctx, key); err != nil { + if _, err := params.KeyLock(params.Context, key); err != nil { return nil, err } - defer server.KeyUnlock(ctx, key) + defer params.KeyUnlock(params.Context, key) - str, ok := server.GetValue(ctx, key).(string) + str, ok := params.GetValue(params.Context, key).(string) if !ok { return nil, fmt.Errorf("value at key %s is not a string", key) } @@ -63,7 +60,7 @@ func handleSetRange(ctx context.Context, cmd []string, server types.EchoVault, _ // If the offset >= length of the string, append the new string to the old one. if offset >= len(str) { newStr = str + newStr - if err = server.SetValue(ctx, key, newStr); err != nil { + if err = params.SetValue(params.Context, key, newStr); err != nil { return nil, err } return []byte(fmt.Sprintf(":%d\r\n", len(newStr))), nil @@ -72,7 +69,7 @@ func handleSetRange(ctx context.Context, cmd []string, server types.EchoVault, _ // If the offset is < 0, prepend the new string to the old one. if offset < 0 { newStr = newStr + str - if err = server.SetValue(ctx, key, newStr); err != nil { + if err = params.SetValue(params.Context, key, newStr); err != nil { return nil, err } return []byte(fmt.Sprintf(":%d\r\n", len(newStr))), nil @@ -92,31 +89,31 @@ func handleSetRange(ctx context.Context, cmd []string, server types.EchoVault, _ break } - if err = server.SetValue(ctx, key, string(strRunes)); err != nil { + if err = params.SetValue(params.Context, key, string(strRunes)); err != nil { return nil, err } return []byte(fmt.Sprintf(":%d\r\n", len(strRunes))), nil } -func handleStrLen(ctx context.Context, cmd []string, server types.EchoVault, conn *net.Conn) ([]byte, error) { - keys, err := strLenKeyFunc(cmd) +func handleStrLen(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := strLenKeyFunc(params.Command) if err != nil { return nil, err } key := keys.ReadKeys[0] - if !server.KeyExists(ctx, key) { + if !params.KeyExists(params.Context, key) { return []byte(":0\r\n"), nil } - if _, err := server.KeyRLock(ctx, key); err != nil { + if _, err := params.KeyRLock(params.Context, key); err != nil { return nil, err } - defer server.KeyRUnlock(ctx, key) + defer params.KeyRUnlock(params.Context, key) - value, ok := server.GetValue(ctx, key).(string) + value, ok := params.GetValue(params.Context, key).(string) if !ok { return nil, fmt.Errorf("value at key %s is not a string", key) @@ -125,32 +122,32 @@ func handleStrLen(ctx context.Context, cmd []string, server types.EchoVault, con return []byte(fmt.Sprintf(":%d\r\n", len(value))), nil } -func handleSubStr(ctx context.Context, cmd []string, server types.EchoVault, _ *net.Conn) ([]byte, error) { - keys, err := subStrKeyFunc(cmd) +func handleSubStr(params internal.HandlerFuncParams) ([]byte, error) { + keys, err := subStrKeyFunc(params.Command) if err != nil { return nil, err } key := keys.ReadKeys[0] - start, startOk := internal.AdaptType(cmd[2]).(int) - end, endOk := internal.AdaptType(cmd[3]).(int) + start, startOk := internal.AdaptType(params.Command[2]).(int) + end, endOk := internal.AdaptType(params.Command[3]).(int) reversed := false if !startOk || !endOk { return nil, errors.New("start and end indices must be integers") } - if !server.KeyExists(ctx, key) { + if !params.KeyExists(params.Context, key) { return nil, fmt.Errorf("key %s does not exist", key) } - if _, err = server.KeyRLock(ctx, key); err != nil { + if _, err = params.KeyRLock(params.Context, key); err != nil { return nil, err } - defer server.KeyRUnlock(ctx, key) + defer params.KeyRUnlock(params.Context, key) - value, ok := server.GetValue(ctx, key).(string) + value, ok := params.GetValue(params.Context, key).(string) if !ok { return nil, fmt.Errorf("value at key %s is not a string", key) } @@ -188,8 +185,8 @@ func handleSubStr(ctx context.Context, cmd []string, server types.EchoVault, _ * return []byte(fmt.Sprintf("$%d\r\n%s\r\n", len(str), str)), nil } -func Commands() []types.Command { - return []types.Command{ +func Commands() []internal.Command { + return []internal.Command{ { Command: "setrange", Module: constants.StringModule, diff --git a/pkg/modules/string/key_funcs.go b/internal/modules/string/key_funcs.go similarity index 65% rename from pkg/modules/string/key_funcs.go rename to internal/modules/string/key_funcs.go index 92e57364..95ee5b5f 100644 --- a/pkg/modules/string/key_funcs.go +++ b/internal/modules/string/key_funcs.go @@ -16,37 +16,37 @@ package str import ( "errors" + "github.com/echovault/echovault/internal" "github.com/echovault/echovault/pkg/constants" - "github.com/echovault/echovault/pkg/types" ) -func setRangeKeyFunc(cmd []string) (types.AccessKeys, error) { +func setRangeKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) != 4 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: cmd[1:2], }, nil } -func strLenKeyFunc(cmd []string) (types.AccessKeys, error) { +func strLenKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) != 2 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: cmd[1:2], WriteKeys: make([]string, 0), }, nil } -func subStrKeyFunc(cmd []string) (types.AccessKeys, error) { +func subStrKeyFunc(cmd []string) (internal.AccessKeys, error) { if len(cmd) != 4 { - return types.AccessKeys{}, errors.New(constants.WrongArgsResponse) + return internal.AccessKeys{}, errors.New(constants.WrongArgsResponse) } - return types.AccessKeys{ + return internal.AccessKeys{ Channels: make([]string, 0), ReadKeys: cmd[1:2], WriteKeys: make([]string, 0), diff --git a/internal/raft/fsm.go b/internal/raft/fsm.go index 1979e7eb..8f7aa2e9 100644 --- a/internal/raft/fsm.go +++ b/internal/raft/fsm.go @@ -24,6 +24,7 @@ import ( "github.com/hashicorp/raft" "io" "log" + "net" "strings" ) @@ -31,11 +32,12 @@ type FSMOpts struct { Config config.Config EchoVault types.EchoVault GetState func() map[string]internal.KeyData - GetCommand func(command string) (types.Command, error) + GetCommand func(command string) (internal.Command, error) DeleteKey func(ctx context.Context, key string) error StartSnapshot func() FinishSnapshot func() SetLatestSnapshotTime func(msec int64) + GetHandlerFuncParams func(ctx context.Context, cmd []string, conn *net.Conn) internal.HandlerFuncParams } type FSM struct { @@ -97,12 +99,12 @@ func (fsm *FSM) Apply(log *raft.Log) interface{} { handler := command.HandlerFunc - subCommand, ok := internal.GetSubCommand(command, request.CMD).(types.SubCommand) + subCommand, ok := internal.GetSubCommand(command, request.CMD).(internal.SubCommand) if ok { handler = subCommand.HandlerFunc } - if res, err := handler(ctx, request.CMD, fsm.options.EchoVault, nil); err != nil { + if res, err := handler(fsm.options.GetHandlerFuncParams(ctx, request.CMD, nil)); err != nil { return internal.ApplyResponse{ Error: err, Response: nil, diff --git a/internal/raft/raft.go b/internal/raft/raft.go index 3a113545..f74532ac 100644 --- a/internal/raft/raft.go +++ b/internal/raft/raft.go @@ -36,11 +36,12 @@ type Opts struct { Config config.Config EchoVault types.EchoVault GetState func() map[string]internal.KeyData - GetCommand func(command string) (types.Command, error) + GetCommand func(command string) (internal.Command, error) DeleteKey func(ctx context.Context, key string) error StartSnapshot func() FinishSnapshot func() SetLatestSnapshotTime func(msec int64) + GetHandlerFuncParams func(ctx context.Context, cmd []string, conn *net.Conn) internal.HandlerFuncParams } type Raft struct { @@ -120,6 +121,7 @@ func (r *Raft) RaftInit(ctx context.Context) { StartSnapshot: r.options.StartSnapshot, FinishSnapshot: r.options.FinishSnapshot, SetLatestSnapshotTime: r.options.SetLatestSnapshotTime, + GetHandlerFuncParams: r.options.GetHandlerFuncParams, }), logStore, stableStore, diff --git a/internal/sorted_set/utils.go b/internal/sorted_set/utils.go deleted file mode 100644 index 73a531cf..00000000 --- a/internal/sorted_set/utils.go +++ /dev/null @@ -1,98 +0,0 @@ -// 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 sorted_set - -import ( - "errors" - "slices" - "strings" -) - -func validateUpdatePolicy(updatePolicy interface{}) (string, error) { - if updatePolicy == nil { - return "", nil - } - err := errors.New("update policy must be a string of Value NX or XX") - policy, ok := updatePolicy.(string) - if !ok { - return "", err - } - if !slices.Contains([]string{"nx", "xx"}, strings.ToLower(policy)) { - return "", err - } - return policy, nil -} - -func validateComparison(comparison interface{}) (string, error) { - if comparison == nil { - return "", nil - } - err := errors.New("comparison condition must be a string of Value LT or GT") - comp, ok := comparison.(string) - if !ok { - return "", err - } - if !slices.Contains([]string{"lt", "gt"}, strings.ToLower(comp)) { - return "", err - } - return comp, nil -} - -func validateChanged(changed interface{}) (string, error) { - if changed == nil { - return "", nil - } - err := errors.New("changed condition should be a string of Value CH") - ch, ok := changed.(string) - if !ok { - return "", err - } - if !strings.EqualFold(ch, "ch") { - return "", err - } - return ch, nil -} - -func validateIncr(incr interface{}) (string, error) { - if incr == nil { - return "", nil - } - err := errors.New("incr condition should be a string of Value INCR") - i, ok := incr.(string) - if !ok { - return "", err - } - if !strings.EqualFold(i, "incr") { - return "", err - } - return i, nil -} - -func compareScores(old Score, new Score, comp string) Score { - switch strings.ToLower(comp) { - default: - return new - case "lt": - if new < old { - return new - } - return old - case "gt": - if new > old { - return new - } - return old - } -} diff --git a/internal/types.go b/internal/types.go index 277af0de..56990e28 100644 --- a/internal/types.go +++ b/internal/types.go @@ -14,7 +14,12 @@ package internal -import "time" +import ( + "context" + "github.com/echovault/echovault/internal/clock" + "net" + "time" +) type KeyData struct { Value interface{} @@ -41,3 +46,59 @@ type SnapshotObject struct { State map[string]KeyData LatestSnapshotMilliseconds int64 } + +type AccessKeys struct { + Channels []string + ReadKeys []string + WriteKeys []string +} + +type KeyExtractionFunc func(cmd []string) (AccessKeys, error) + +type HandlerFuncParams struct { + Context context.Context + Command []string + Connection *net.Conn + KeyLock func(ctx context.Context, key string) (bool, error) + KeyUnlock func(ctx context.Context, key string) + KeyRLock func(ctx context.Context, key string) (bool, error) + KeyRUnlock func(ctx context.Context, key string) + KeyExists func(ctx context.Context, key string) bool + CreateKeyAndLock func(ctx context.Context, key string) (bool, error) + GetValue func(ctx context.Context, key string) interface{} + SetValue func(ctx context.Context, key string, value interface{}) error + GetExpiry func(ctx context.Context, key string) time.Time + SetExpiry func(ctx context.Context, key string, expire time.Time, touch bool) + RemoveExpiry func(ctx context.Context, key string) + DeleteKey func(ctx context.Context, key string) error + GetClock func() clock.Clock + GetAllCommands func() []Command + GetACL func() interface{} + GetPubSub func() interface{} + TakeSnapshot func() error + RewriteAOF func() error + GetLatestSnapshotTime func() int64 +} + +type HandlerFunc func(params HandlerFuncParams) ([]byte, error) + +type Command struct { + Command string + Module string + Categories []string + Description string + SubCommands []SubCommand + Sync bool // Specifies if command should be synced across replication cluster + KeyExtractionFunc + HandlerFunc +} + +type SubCommand struct { + Command string + Module string + Categories []string + Description string + Sync bool // Specifies if sub-command should be synced across replication cluster + KeyExtractionFunc + HandlerFunc +} diff --git a/internal/utils.go b/internal/utils.go index 6f417c23..f27b0155 100644 --- a/internal/utils.go +++ b/internal/utils.go @@ -21,7 +21,6 @@ import ( "errors" "fmt" "github.com/echovault/echovault/pkg/constants" - "github.com/echovault/echovault/pkg/types" "io" "log" "math/big" @@ -129,7 +128,7 @@ func GetIPAddress() (string, error) { return localAddr, nil } -func GetSubCommand(command types.Command, cmd []string) interface{} { +func GetSubCommand(command Command, cmd []string) interface{} { if len(command.SubCommands) == 0 || len(cmd) < 2 { return nil } @@ -141,7 +140,7 @@ func GetSubCommand(command types.Command, cmd []string) interface{} { return nil } -func IsWriteCommand(command types.Command, subCommand types.SubCommand) bool { +func IsWriteCommand(command Command, subCommand SubCommand) bool { return slices.Contains(append(command.Categories, subCommand.Categories...), constants.WriteCategory) } diff --git a/pkg/commands/commands.go b/pkg/commands/commands.go deleted file mode 100644 index 4cc7643e..00000000 --- a/pkg/commands/commands.go +++ /dev/null @@ -1,31 +0,0 @@ -package commands - -import ( - "github.com/echovault/echovault/pkg/modules/acl" - "github.com/echovault/echovault/pkg/modules/admin" - "github.com/echovault/echovault/pkg/modules/connection" - "github.com/echovault/echovault/pkg/modules/generic" - "github.com/echovault/echovault/pkg/modules/hash" - "github.com/echovault/echovault/pkg/modules/list" - "github.com/echovault/echovault/pkg/modules/pubsub" - "github.com/echovault/echovault/pkg/modules/set" - "github.com/echovault/echovault/pkg/modules/sorted_set" - str "github.com/echovault/echovault/pkg/modules/string" - "github.com/echovault/echovault/pkg/types" -) - -// All returns all the commands currently available on EchoVault -func All() []types.Command { - var commands []types.Command - commands = append(commands, acl.Commands()...) - commands = append(commands, admin.Commands()...) - commands = append(commands, generic.Commands()...) - commands = append(commands, hash.Commands()...) - commands = append(commands, list.Commands()...) - commands = append(commands, connection.Commands()...) - commands = append(commands, pubsub.Commands()...) - commands = append(commands, set.Commands()...) - commands = append(commands, sorted_set.Commands()...) - commands = append(commands, str.Commands()...) - return commands -} diff --git a/pkg/echovault/echovault.go b/pkg/echovault/echovault.go index b56b7425..b5b1ba34 100644 --- a/pkg/echovault/echovault.go +++ b/pkg/echovault/echovault.go @@ -21,17 +21,24 @@ import ( "errors" "fmt" "github.com/echovault/echovault/internal" - "github.com/echovault/echovault/internal/acl" "github.com/echovault/echovault/internal/aof" "github.com/echovault/echovault/internal/clock" "github.com/echovault/echovault/internal/config" "github.com/echovault/echovault/internal/eviction" "github.com/echovault/echovault/internal/memberlist" - "github.com/echovault/echovault/internal/pubsub" + "github.com/echovault/echovault/internal/modules/acl" + "github.com/echovault/echovault/internal/modules/admin" + "github.com/echovault/echovault/internal/modules/connection" + "github.com/echovault/echovault/internal/modules/generic" + "github.com/echovault/echovault/internal/modules/hash" + "github.com/echovault/echovault/internal/modules/list" + "github.com/echovault/echovault/internal/modules/pubsub" + "github.com/echovault/echovault/internal/modules/set" + "github.com/echovault/echovault/internal/modules/sorted_set" + str "github.com/echovault/echovault/internal/modules/string" "github.com/echovault/echovault/internal/raft" "github.com/echovault/echovault/internal/snapshot" "github.com/echovault/echovault/pkg/constants" - "github.com/echovault/echovault/pkg/types" "io" "log" "net" @@ -43,7 +50,8 @@ import ( type EchoVault struct { // clock is an implementation of a time interface that allows mocking of time functions during testing. - clock clock.Clock + clock clock.Clock + getClock func() clock.Clock // config holds the echovault configuration variables. config config.Config @@ -74,7 +82,8 @@ type EchoVault struct { } // Holds the list of all commands supported by the echovault. - commands []types.Command + commands []internal.Command + getCommands func() []internal.Command raft *raft.Raft // The raft replication layer for the echovault. memberList *memberlist.MemberList // The memberlist layer for the echovault. @@ -82,7 +91,10 @@ type EchoVault struct { context context.Context acl *acl.ACL - pubSub *pubsub.PubSub + getACL func() interface{} + + pubSub *pubsub.PubSub + getPubSub func() interface{} snapshotInProgress atomic.Bool // Atomic boolean that's true when actively taking a snapshot. rewriteAOFInProgress atomic.Bool // Atomic boolean that's true when actively rewriting AOF file is in progress. @@ -111,26 +123,30 @@ func WithConfig(config config.Config) func(echovault *EchoVault) { } } -// WithCommands is an options for the NewEchoVault function that allows you to pass a -// list of commands that should be supported by your EchoVault instance. -// If you don't pass this option, EchoVault will start with no commands loaded. -func WithCommands(commands []types.Command) func(echovault *EchoVault) { - return func(echovault *EchoVault) { - echovault.commands = commands - } -} - // NewEchoVault creates a new EchoVault instance. // This functions accepts the WithContext, WithConfig and WithCommands options. func NewEchoVault(options ...func(echovault *EchoVault)) (*EchoVault, error) { echovault := &EchoVault{ clock: clock.NewClock(), context: context.Background(), - commands: make([]types.Command, 0), config: config.DefaultConfig(), store: make(map[string]internal.KeyData), keyLocks: make(map[string]*sync.RWMutex), keyCreationLock: &sync.Mutex{}, + commands: func() []internal.Command { + var commands []internal.Command + commands = append(commands, acl.Commands()...) + commands = append(commands, admin.Commands()...) + commands = append(commands, generic.Commands()...) + commands = append(commands, hash.Commands()...) + commands = append(commands, list.Commands()...) + commands = append(commands, connection.Commands()...) + commands = append(commands, pubsub.Commands()...) + commands = append(commands, set.Commands()...) + commands = append(commands, sorted_set.Commands()...) + commands = append(commands, str.Commands()...) + return commands + }(), } for _, option := range options { @@ -142,11 +158,27 @@ func NewEchoVault(options ...func(echovault *EchoVault)) (*EchoVault, error) { internal.ContextServerID(echovault.config.ServerID), ) + // Function for server commands retrieval + echovault.getCommands = func() []internal.Command { + return echovault.commands + } + + // Function for clock retrieval + echovault.getClock = func() clock.Clock { + return echovault.clock + } + // Set up ACL module echovault.acl = acl.NewACL(echovault.config) + echovault.getACL = func() interface{} { + return echovault.acl + } // Set up Pub/Sub module echovault.pubSub = pubsub.NewPubSub() + echovault.getPubSub = func() interface{} { + return echovault.pubSub + } if echovault.isInCluster() { echovault.raft = raft.NewRaft(raft.Opts{ @@ -157,6 +189,7 @@ func NewEchoVault(options ...func(echovault *EchoVault)) (*EchoVault, error) { StartSnapshot: echovault.startSnapshot, FinishSnapshot: echovault.finishSnapshot, SetLatestSnapshotTime: echovault.setLatestSnapshot, + GetHandlerFuncParams: echovault.getHandlerFuncParams, GetState: func() map[string]internal.KeyData { state := make(map[string]internal.KeyData) for k, v := range echovault.getState() { @@ -186,7 +219,7 @@ func NewEchoVault(options ...func(echovault *EchoVault)) (*EchoVault, error) { snapshot.WithStartSnapshotFunc(echovault.startSnapshot), snapshot.WithFinishSnapshotFunc(echovault.finishSnapshot), snapshot.WithSetLatestSnapshotTimeFunc(echovault.setLatestSnapshot), - snapshot.WithGetLatestSnapshotTimeFunc(echovault.GetLatestSnapshotTime), + snapshot.WithGetLatestSnapshotTimeFunc(echovault.getLatestSnapshotTime), snapshot.WithGetStateFunc(func() map[string]internal.KeyData { state := make(map[string]internal.KeyData) for k, v := range echovault.getState() { @@ -449,8 +482,8 @@ func (server *EchoVault) Start() { server.startTCP() } -// TakeSnapshot triggers a snapshot when called. -func (server *EchoVault) TakeSnapshot() error { +// takeSnapshot triggers a snapshot when called. +func (server *EchoVault) takeSnapshot() error { if server.snapshotInProgress.Load() { return errors.New("snapshot already in progress") } @@ -472,11 +505,6 @@ func (server *EchoVault) TakeSnapshot() error { return nil } -// GetClock returns the server's clock implementation -func (server *EchoVault) GetClock() clock.Clock { - return server.clock -} - func (server *EchoVault) startSnapshot() { server.snapshotInProgress.Store(true) } @@ -489,8 +517,8 @@ func (server *EchoVault) setLatestSnapshot(msec int64) { server.latestSnapshotMilliseconds.Store(msec) } -// GetLatestSnapshotTime returns the latest snapshot time in unix epoch milliseconds. -func (server *EchoVault) GetLatestSnapshotTime() int64 { +// getLatestSnapshotTime returns the latest snapshot time in unix epoch milliseconds. +func (server *EchoVault) getLatestSnapshotTime() int64 { return server.latestSnapshotMilliseconds.Load() } @@ -502,8 +530,8 @@ func (server *EchoVault) finishRewriteAOF() { server.rewriteAOFInProgress.Store(false) } -// RewriteAOF triggers an AOF compaction when running in standalone mode. -func (server *EchoVault) RewriteAOF() error { +// rewriteAOF triggers an AOF compaction when running in standalone mode. +func (server *EchoVault) rewriteAOF() error { if server.rewriteAOFInProgress.Load() { return errors.New("aof rewrite in progress") } diff --git a/pkg/echovault/keyspace.go b/pkg/echovault/keyspace.go index a6ae9ba7..1ba9d542 100644 --- a/pkg/echovault/keyspace.go +++ b/pkg/echovault/keyspace.go @@ -247,7 +247,7 @@ func (server *EchoVault) SetExpiry(ctx context.Context, key string, expireAt tim // RemoveExpiry is called by commands that remove key expiry (e.g. PERSIST). // The key must be locked prior ro calling this function. -func (server *EchoVault) RemoveExpiry(key string) { +func (server *EchoVault) RemoveExpiry(_ context.Context, key string) { // Reset expiry time server.store[key] = internal.KeyData{ Value: server.store[key].Value, @@ -292,7 +292,7 @@ func (server *EchoVault) DeleteKey(ctx context.Context, key string) error { } // Remove key expiry. - server.RemoveExpiry(key) + server.RemoveExpiry(ctx, key) // Delete the key from keyLocks and store. delete(server.keyLocks, key) @@ -589,16 +589,3 @@ func (server *EchoVault) evictKeysWithExpiredTTL(ctx context.Context) error { return nil } - -func presetValue(server *EchoVault, key string, value interface{}) { - _, _ = server.CreateKeyAndLock(server.context, key) - _ = server.SetValue(server.context, key, value) - server.KeyUnlock(server.context, key) -} - -func presetKeyData(server *EchoVault, key string, data internal.KeyData) { - _, _ = server.CreateKeyAndLock(server.context, key) - defer server.KeyUnlock(server.context, key) - _ = server.SetValue(server.context, key, data.Value) - server.SetExpiry(server.context, key, data.ExpireAt, false) -} diff --git a/pkg/echovault/modules.go b/pkg/echovault/modules.go index 2baeb0a9..f228b371 100644 --- a/pkg/echovault/modules.go +++ b/pkg/echovault/modules.go @@ -20,30 +20,43 @@ import ( "fmt" "github.com/echovault/echovault/internal" "github.com/echovault/echovault/pkg/constants" - "github.com/echovault/echovault/pkg/types" "net" "strings" ) -func (server *EchoVault) GetAllCommands() []types.Command { - return server.commands -} - -func (server *EchoVault) GetACL() interface{} { - return server.acl -} - -func (server *EchoVault) GetPubSub() interface{} { - return server.pubSub -} - -func (server *EchoVault) getCommand(cmd string) (types.Command, error) { +func (server *EchoVault) getCommand(cmd string) (internal.Command, error) { for _, command := range server.commands { if strings.EqualFold(command.Command, cmd) { return command, nil } } - return types.Command{}, fmt.Errorf("command %s not supported", cmd) + return internal.Command{}, fmt.Errorf("command %s not supported", cmd) +} + +func (server *EchoVault) getHandlerFuncParams(ctx context.Context, cmd []string, conn *net.Conn) internal.HandlerFuncParams { + return internal.HandlerFuncParams{ + Context: ctx, + Command: cmd, + Connection: conn, + KeyExists: server.KeyExists, + CreateKeyAndLock: server.CreateKeyAndLock, + KeyLock: server.KeyLock, + KeyRLock: server.KeyRLock, + KeyUnlock: server.KeyUnlock, + KeyRUnlock: server.KeyRUnlock, + GetValue: server.GetValue, + SetValue: server.SetValue, + GetExpiry: server.GetExpiry, + SetExpiry: server.SetExpiry, + DeleteKey: server.DeleteKey, + TakeSnapshot: server.takeSnapshot, + GetLatestSnapshotTime: server.getLatestSnapshotTime, + RewriteAOF: server.rewriteAOF, + GetClock: server.getClock, + GetPubSub: server.getPubSub, + GetACL: server.getACL, + GetAllCommands: server.getCommands, + } } func (server *EchoVault) handleCommand(ctx context.Context, message []byte, conn *net.Conn, replay bool, embedded bool) ([]byte, error) { @@ -60,7 +73,7 @@ func (server *EchoVault) handleCommand(ctx context.Context, message []byte, conn synchronize := command.Sync handler := command.HandlerFunc - subCommand, ok := internal.GetSubCommand(command, cmd).(types.SubCommand) + subCommand, ok := internal.GetSubCommand(command, cmd).(internal.SubCommand) if ok { synchronize = subCommand.Sync handler = subCommand.HandlerFunc @@ -85,7 +98,7 @@ func (server *EchoVault) handleCommand(ctx context.Context, message []byte, conn } if !server.isInCluster() || !synchronize { - res, err := handler(ctx, cmd, server, conn) + res, err := handler(server.getHandlerFuncParams(ctx, cmd, conn)) if err != nil { return nil, err } diff --git a/pkg/modules/admin/commands_test.go b/pkg/modules/admin/commands_test.go deleted file mode 100644 index cd558ee2..00000000 --- a/pkg/modules/admin/commands_test.go +++ /dev/null @@ -1,51 +0,0 @@ -// 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 admin - -import ( - "bytes" - "context" - "fmt" - "github.com/echovault/echovault/internal/config" - "github.com/echovault/echovault/pkg/constants" - "github.com/echovault/echovault/pkg/echovault" - "github.com/tidwall/resp" - "testing" -) - -func Test_CommandsHandler(t *testing.T) { - mockServer, _ := echovault.NewEchoVault( - echovault.WithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.NoEviction, - }), - echovault.WithCommands(Commands()), - ) - - res, err := handleGetAllCommands(context.Background(), []string{"commands"}, mockServer, nil) - if err != nil { - t.Error(err) - } - - rd := resp.NewReader(bytes.NewReader(res)) - rv, _, err := rd.ReadValue() - if err != nil { - t.Error(err) - } - - for _, element := range rv.Array() { - fmt.Println(element) - } -} diff --git a/pkg/modules/generic/utils.go b/pkg/modules/generic/utils.go deleted file mode 100644 index 17a3f5db..00000000 --- a/pkg/modules/generic/utils.go +++ /dev/null @@ -1,118 +0,0 @@ -// 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 generic - -import ( - "errors" - "fmt" - "github.com/echovault/echovault/internal/clock" - "strconv" - "strings" - "time" -) - -type SetParams struct { - exists string - get bool - expireAt interface{} // Exact expireAt time un unix milliseconds -} - -func getSetCommandParams(clock clock.Clock, cmd []string, params SetParams) (SetParams, error) { - if len(cmd) == 0 { - return params, nil - } - switch strings.ToLower(cmd[0]) { - case "get": - params.get = true - return getSetCommandParams(clock, cmd[1:], params) - - case "nx": - if params.exists != "" { - return SetParams{}, fmt.Errorf("cannot specify NX when %s is already specified", strings.ToUpper(params.exists)) - } - params.exists = "NX" - return getSetCommandParams(clock, cmd[1:], params) - - case "xx": - if params.exists != "" { - return SetParams{}, fmt.Errorf("cannot specify XX when %s is already specified", strings.ToUpper(params.exists)) - } - params.exists = "XX" - return getSetCommandParams(clock, cmd[1:], params) - - case "ex": - if len(cmd) < 2 { - return SetParams{}, errors.New("seconds value required after EX") - } - if params.expireAt != nil { - return SetParams{}, errors.New("cannot specify EX when expiry time is already set") - } - secondsStr := cmd[1] - seconds, err := strconv.ParseInt(secondsStr, 10, 64) - if err != nil { - return SetParams{}, errors.New("seconds value should be an integer") - } - params.expireAt = clock.Now().Add(time.Duration(seconds) * time.Second) - return getSetCommandParams(clock, cmd[2:], params) - - case "px": - if len(cmd) < 2 { - return SetParams{}, errors.New("milliseconds value required after PX") - } - if params.expireAt != nil { - return SetParams{}, errors.New("cannot specify PX when expiry time is already set") - } - millisecondsStr := cmd[1] - milliseconds, err := strconv.ParseInt(millisecondsStr, 10, 64) - if err != nil { - return SetParams{}, errors.New("milliseconds value should be an integer") - } - params.expireAt = clock.Now().Add(time.Duration(milliseconds) * time.Millisecond) - return getSetCommandParams(clock, cmd[2:], params) - - case "exat": - if len(cmd) < 2 { - return SetParams{}, errors.New("seconds value required after EXAT") - } - if params.expireAt != nil { - return SetParams{}, errors.New("cannot specify EXAT when expiry time is already set") - } - secondsStr := cmd[1] - seconds, err := strconv.ParseInt(secondsStr, 10, 64) - if err != nil { - return SetParams{}, errors.New("seconds value should be an integer") - } - params.expireAt = time.Unix(seconds, 0) - return getSetCommandParams(clock, cmd[2:], params) - - case "pxat": - if len(cmd) < 2 { - return SetParams{}, errors.New("milliseconds value required after PXAT") - } - if params.expireAt != nil { - return SetParams{}, errors.New("cannot specify PXAT when expiry time is already set") - } - millisecondsStr := cmd[1] - milliseconds, err := strconv.ParseInt(millisecondsStr, 10, 64) - if err != nil { - return SetParams{}, errors.New("milliseconds value should be an integer") - } - params.expireAt = time.UnixMilli(milliseconds) - return getSetCommandParams(clock, cmd[2:], params) - - default: - return SetParams{}, fmt.Errorf("unknown option %s for set command", strings.ToUpper(cmd[0])) - } -} diff --git a/pkg/types/types.go b/pkg/types/types.go index 8a53730c..2b4f46cc 100644 --- a/pkg/types/types.go +++ b/pkg/types/types.go @@ -16,8 +16,6 @@ package types import ( "context" - "github.com/echovault/echovault/internal/clock" - "net" "time" ) @@ -32,50 +30,6 @@ type EchoVault interface { SetValue(ctx context.Context, key string, value interface{}) error GetExpiry(ctx context.Context, key string) time.Time SetExpiry(ctx context.Context, key string, expire time.Time, touch bool) - RemoveExpiry(key string) + RemoveExpiry(ctx context.Context, key string) DeleteKey(ctx context.Context, key string) error - GetClock() clock.Clock - GetAllCommands() []Command - GetACL() interface{} - GetPubSub() interface{} - TakeSnapshot() error - RewriteAOF() error - GetLatestSnapshotTime() int64 } - -type AccessKeys struct { - Channels []string - ReadKeys []string - WriteKeys []string -} -type KeyExtractionFunc func(cmd []string) (AccessKeys, error) - -type HandlerFunc func(ctx context.Context, cmd []string, echovault EchoVault, conn *net.Conn) ([]byte, error) - -type SubCommand struct { - Command string - Module string - Categories []string - Description string - Sync bool // Specifies if sub-command should be synced across cluster - KeyExtractionFunc - HandlerFunc -} - -type Command struct { - Command string - Module string - Categories []string - Description string - SubCommands []SubCommand - Sync bool // Specifies if command should be synced across cluster - KeyExtractionFunc - HandlerFunc -} - -type ACL interface { - RegisterConnection(conn *net.Conn) - AuthorizeConnection(conn *net.Conn, cmd []string, command Command, subCommand SubCommand) error -} - -type PubSub interface{} diff --git a/pkg/echovault/api_acl_test.go b/test/modules/acl/api_test.go similarity index 97% rename from pkg/echovault/api_acl_test.go rename to test/modules/acl/api_test.go index f6598c81..96769fed 100644 --- a/pkg/echovault/api_acl_test.go +++ b/test/modules/acl/api_test.go @@ -12,4 +12,4 @@ // See the License for the specific language governing permissions and // limitations under the License. -package echovault +package acl diff --git a/pkg/modules/acl/commands_test.go b/test/modules/acl/commands_test.go similarity index 83% rename from pkg/modules/acl/commands_test.go rename to test/modules/acl/commands_test.go index da8b5577..bbc2b3ed 100644 --- a/pkg/modules/acl/commands_test.go +++ b/test/modules/acl/commands_test.go @@ -17,16 +17,18 @@ package acl import ( "crypto/sha256" "fmt" - internal_acl "github.com/echovault/echovault/internal/acl" "github.com/echovault/echovault/internal/config" + "github.com/echovault/echovault/internal/modules/acl" "github.com/echovault/echovault/pkg/constants" "github.com/echovault/echovault/pkg/echovault" "github.com/tidwall/resp" "net" + "reflect" "slices" "strings" "sync" "testing" + "unsafe" ) var bindAddr string @@ -61,43 +63,52 @@ func setUpServer(bindAddr string, port uint16, requirePass bool, aclConfig strin mockServer, _ := echovault.NewEchoVault( echovault.WithConfig(conf), - echovault.WithCommands(Commands()), ) // Add the initial test users to the ACL module - acl := mockServer.GetACL().(*internal_acl.ACL) - acl.AddUsers(generateInitialTestUsers()) + a := getACL(mockServer) + a.AddUsers(generateInitialTestUsers()) return mockServer } -func generateInitialTestUsers() []*internal_acl.User { +func getUnexportedField(field reflect.Value) interface{} { + return reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())).Elem().Interface() +} + +func getACL(mockServer *echovault.EchoVault) *acl.ACL { + method := getUnexportedField(reflect.ValueOf(mockServer).Elem().FieldByName("getACL")) + f := method.(func() interface{}) + return f().(*acl.ACL) +} + +func generateInitialTestUsers() []*acl.User { // User with both hash password and plaintext password - withPasswordUser := internal_acl.CreateUser("with_password_user") + withPasswordUser := acl.CreateUser("with_password_user") h := sha256.New() h.Write([]byte("password3")) - withPasswordUser.Passwords = []internal_acl.Password{ - {PasswordType: internal_acl.PasswordPlainText, PasswordValue: "password2"}, - {PasswordType: internal_acl.PasswordSHA256, PasswordValue: string(h.Sum(nil))}, + withPasswordUser.Passwords = []acl.Password{ + {PasswordType: acl.PasswordPlainText, PasswordValue: "password2"}, + {PasswordType: acl.PasswordSHA256, PasswordValue: string(h.Sum(nil))}, } withPasswordUser.IncludedCategories = []string{"*"} withPasswordUser.IncludedCommands = []string{"*"} // User with NoPassword option - noPasswordUser := internal_acl.CreateUser("no_password_user") - noPasswordUser.Passwords = []internal_acl.Password{ - {PasswordType: internal_acl.PasswordPlainText, PasswordValue: "password4"}, + noPasswordUser := acl.CreateUser("no_password_user") + noPasswordUser.Passwords = []acl.Password{ + {PasswordType: acl.PasswordPlainText, PasswordValue: "password4"}, } noPasswordUser.NoPassword = true // Disabled user - disabledUser := internal_acl.CreateUser("disabled_user") - disabledUser.Passwords = []internal_acl.Password{ - {PasswordType: internal_acl.PasswordPlainText, PasswordValue: "password5"}, + disabledUser := acl.CreateUser("disabled_user") + disabledUser.Passwords = []acl.Password{ + {PasswordType: acl.PasswordPlainText, PasswordValue: "password5"}, } disabledUser.Enabled = false - return []*internal_acl.User{ + return []*acl.User{ withPasswordUser, noPasswordUser, disabledUser, @@ -128,7 +139,7 @@ func compareSlices[T comparable](res, expected []T) error { } // compareUsers compares 2 users and checks if all their fields are equal -func compareUsers(user1, user2 *internal_acl.User) error { +func compareUsers(user1, user2 *acl.User) error { // Compare flags if user1.Username != user2.Username { return fmt.Errorf("mismatched usernames \"%s\", and \"%s\"", user1.Username, user2.Username) @@ -145,14 +156,14 @@ func compareUsers(user1, user2 *internal_acl.User) error { // Compare passwords for _, password1 := range user1.Passwords { - if !slices.ContainsFunc(user2.Passwords, func(password2 internal_acl.Password) bool { + if !slices.ContainsFunc(user2.Passwords, func(password2 acl.Password) bool { return password1.PasswordType == password2.PasswordType && password1.PasswordValue == password2.PasswordValue }) { return fmt.Errorf("found password %+v in user1 that was not found in user2", password1) } } for _, password2 := range user2.Passwords { - if !slices.ContainsFunc(user1.Passwords, func(password1 internal_acl.Password) bool { + if !slices.ContainsFunc(user1.Passwords, func(password1 acl.Password) bool { return password1.PasswordType == password2.PasswordType && password1.PasswordValue == password2.PasswordValue }) { return fmt.Errorf("found password %+v in user2 that was not found in user1", password2) @@ -391,14 +402,6 @@ func Test_HandleCat(t *testing.T) { t.Errorf("could not find expected command \"%s\" in the response array for category", expected) } } - // Check if all the elements in the response array are in the expected array - for _, value := range resArr { - if !slices.ContainsFunc(test.wantRes, func(expected string) bool { - return value.String() == expected - }) { - t.Errorf("could not find response command \"%s\" in the expected array", value.String()) - } - } } } @@ -468,10 +471,7 @@ func Test_HandleSetUser(t *testing.T) { }() wg.Wait() - acl, ok := mockServer.GetACL().(*internal_acl.ACL) - if !ok { - t.Error("error loading ACL module") - } + a := getACL(mockServer) conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", bindAddr, port)) if err != nil { @@ -486,11 +486,11 @@ func Test_HandleSetUser(t *testing.T) { r := resp.NewConn(conn) tests := []struct { - presetUser *internal_acl.User + presetUser *acl.User cmd []resp.Value wantRes string wantErr string - wantUser *internal_acl.User + wantUser *acl.User }{ { // 1. Create new enabled user @@ -503,8 +503,8 @@ func Test_HandleSetUser(t *testing.T) { }, wantRes: "OK", wantErr: "", - wantUser: func() *internal_acl.User { - user := internal_acl.CreateUser("set_user_1") + wantUser: func() *acl.User { + user := acl.CreateUser("set_user_1") user.Enabled = true user.Normalise() return user @@ -521,8 +521,8 @@ func Test_HandleSetUser(t *testing.T) { }, wantRes: "OK", wantErr: "", - wantUser: func() *internal_acl.User { - user := internal_acl.CreateUser("set_user_2") + wantUser: func() *acl.User { + user := acl.CreateUser("set_user_2") user.Enabled = false user.Normalise() return user @@ -543,14 +543,14 @@ func Test_HandleSetUser(t *testing.T) { }, wantRes: "OK", wantErr: "", - wantUser: func() *internal_acl.User { - user := internal_acl.CreateUser("set_user_3") + wantUser: func() *acl.User { + user := acl.CreateUser("set_user_3") user.Enabled = true - user.Passwords = []internal_acl.Password{ - {PasswordType: internal_acl.PasswordPlainText, PasswordValue: "set_user_3_plaintext_password_1"}, - {PasswordType: internal_acl.PasswordPlainText, PasswordValue: "set_user_3_plaintext_password_2"}, - {PasswordType: internal_acl.PasswordSHA256, PasswordValue: generateSHA256Password("set_user_3_hash_password_1")}, - {PasswordType: internal_acl.PasswordSHA256, PasswordValue: generateSHA256Password("set_user_3_hash_password_2")}, + user.Passwords = []acl.Password{ + {PasswordType: acl.PasswordPlainText, PasswordValue: "set_user_3_plaintext_password_1"}, + {PasswordType: acl.PasswordPlainText, PasswordValue: "set_user_3_plaintext_password_2"}, + {PasswordType: acl.PasswordSHA256, PasswordValue: generateSHA256Password("set_user_3_hash_password_1")}, + {PasswordType: acl.PasswordSHA256, PasswordValue: generateSHA256Password("set_user_3_hash_password_2")}, } user.Normalise() return user @@ -558,14 +558,14 @@ func Test_HandleSetUser(t *testing.T) { }, { // 4. Remove plaintext and SHA256 password from existing user - presetUser: func() *internal_acl.User { - user := internal_acl.CreateUser("set_user_4") + presetUser: func() *acl.User { + user := acl.CreateUser("set_user_4") user.Enabled = true - user.Passwords = []internal_acl.Password{ - {PasswordType: internal_acl.PasswordPlainText, PasswordValue: "set_user_3_plaintext_password_1"}, - {PasswordType: internal_acl.PasswordPlainText, PasswordValue: "set_user_3_plaintext_password_2"}, - {PasswordType: internal_acl.PasswordSHA256, PasswordValue: generateSHA256Password("set_user_3_hash_password_1")}, - {PasswordType: internal_acl.PasswordSHA256, PasswordValue: generateSHA256Password("set_user_3_hash_password_2")}, + user.Passwords = []acl.Password{ + {PasswordType: acl.PasswordPlainText, PasswordValue: "set_user_3_plaintext_password_1"}, + {PasswordType: acl.PasswordPlainText, PasswordValue: "set_user_3_plaintext_password_2"}, + {PasswordType: acl.PasswordSHA256, PasswordValue: generateSHA256Password("set_user_3_hash_password_1")}, + {PasswordType: acl.PasswordSHA256, PasswordValue: generateSHA256Password("set_user_3_hash_password_2")}, } user.Normalise() return user @@ -580,12 +580,12 @@ func Test_HandleSetUser(t *testing.T) { }, wantRes: "OK", wantErr: "", - wantUser: func() *internal_acl.User { - user := internal_acl.CreateUser("set_user_4") + wantUser: func() *acl.User { + user := acl.CreateUser("set_user_4") user.Enabled = true - user.Passwords = []internal_acl.Password{ - {PasswordType: internal_acl.PasswordPlainText, PasswordValue: "set_user_3_plaintext_password_1"}, - {PasswordType: internal_acl.PasswordSHA256, PasswordValue: generateSHA256Password("set_user_3_hash_password_1")}, + user.Passwords = []acl.Password{ + {PasswordType: acl.PasswordPlainText, PasswordValue: "set_user_3_plaintext_password_1"}, + {PasswordType: acl.PasswordSHA256, PasswordValue: generateSHA256Password("set_user_3_hash_password_1")}, } user.Normalise() return user @@ -603,8 +603,8 @@ func Test_HandleSetUser(t *testing.T) { }, wantRes: "OK", wantErr: "", - wantUser: func() *internal_acl.User { - user := internal_acl.CreateUser("set_user_5") + wantUser: func() *acl.User { + user := acl.CreateUser("set_user_5") user.Enabled = true user.ExcludedCommands = []string{"*"} user.ExcludedCategories = []string{"*"} @@ -624,8 +624,8 @@ func Test_HandleSetUser(t *testing.T) { }, wantRes: "OK", wantErr: "", - wantUser: func() *internal_acl.User { - user := internal_acl.CreateUser("set_user_6") + wantUser: func() *acl.User { + user := acl.CreateUser("set_user_6") user.Enabled = true user.IncludedCategories = []string{"*"} user.ExcludedCategories = []string{} @@ -645,8 +645,8 @@ func Test_HandleSetUser(t *testing.T) { }, wantRes: "OK", wantErr: "", - wantUser: func() *internal_acl.User { - user := internal_acl.CreateUser("set_user_7") + wantUser: func() *acl.User { + user := acl.CreateUser("set_user_7") user.Enabled = true user.IncludedCategories = []string{"*"} user.ExcludedCategories = []string{} @@ -671,8 +671,8 @@ func Test_HandleSetUser(t *testing.T) { }, wantRes: "OK", wantErr: "", - wantUser: func() *internal_acl.User { - user := internal_acl.CreateUser("set_user_8") + wantUser: func() *acl.User { + user := acl.CreateUser("set_user_8") user.Enabled = true user.IncludedCategories = []string{constants.WriteCategory, constants.ReadCategory, constants.PubSubCategory} user.ExcludedCategories = []string{constants.AdminCategory, constants.ConnectionCategory, constants.DangerousCategory} @@ -692,8 +692,8 @@ func Test_HandleSetUser(t *testing.T) { }, wantRes: "OK", wantErr: "", - wantUser: func() *internal_acl.User { - user := internal_acl.CreateUser("set_user_9") + wantUser: func() *acl.User { + user := acl.CreateUser("set_user_9") user.Enabled = true user.NoKeys = true user.IncludedReadKeys = []string{} @@ -722,8 +722,8 @@ func Test_HandleSetUser(t *testing.T) { }, wantRes: "OK", wantErr: "", - wantUser: func() *internal_acl.User { - user := internal_acl.CreateUser("set_user_10") + wantUser: func() *acl.User { + user := acl.CreateUser("set_user_10") user.Enabled = true user.NoKeys = false user.IncludedReadKeys = []string{"key1", "key2", "key3", "key4", "key5", "key6"} @@ -744,8 +744,8 @@ func Test_HandleSetUser(t *testing.T) { }, wantRes: "OK", wantErr: "", - wantUser: func() *internal_acl.User { - user := internal_acl.CreateUser("set_user_11") + wantUser: func() *acl.User { + user := acl.CreateUser("set_user_11") user.Enabled = true user.IncludedPubSubChannels = []string{"*"} user.ExcludedPubSubChannels = []string{} @@ -765,8 +765,8 @@ func Test_HandleSetUser(t *testing.T) { }, wantRes: "OK", wantErr: "", - wantUser: func() *internal_acl.User { - user := internal_acl.CreateUser("set_user_12") + wantUser: func() *acl.User { + user := acl.CreateUser("set_user_12") user.Enabled = true user.IncludedPubSubChannels = []string{"*"} user.ExcludedPubSubChannels = []string{} @@ -789,8 +789,8 @@ func Test_HandleSetUser(t *testing.T) { }, wantRes: "OK", wantErr: "", - wantUser: func() *internal_acl.User { - user := internal_acl.CreateUser("set_user_13") + wantUser: func() *acl.User { + user := acl.CreateUser("set_user_13") user.Enabled = true user.IncludedPubSubChannels = []string{"channel1", "channel2"} user.ExcludedPubSubChannels = []string{"channel3", "channel4"} @@ -810,8 +810,8 @@ func Test_HandleSetUser(t *testing.T) { }, wantRes: "OK", wantErr: "", - wantUser: func() *internal_acl.User { - user := internal_acl.CreateUser("set_user_14") + wantUser: func() *acl.User { + user := acl.CreateUser("set_user_14") user.Enabled = true user.IncludedCommands = []string{"*"} user.ExcludedCommands = []string{} @@ -836,8 +836,8 @@ func Test_HandleSetUser(t *testing.T) { }, wantRes: "OK", wantErr: "", - wantUser: func() *internal_acl.User { - user := internal_acl.CreateUser("set_user_15") + wantUser: func() *acl.User { + user := acl.CreateUser("set_user_15") user.Enabled = true user.IncludedCommands = []string{"acl|getuser", "acl|setuser", "acl|deluser"} user.ExcludedCommands = []string{"rewriteaof", "save", "publish"} @@ -860,24 +860,24 @@ func Test_HandleSetUser(t *testing.T) { }, wantRes: "OK", wantErr: "", - wantUser: func() *internal_acl.User { - user := internal_acl.CreateUser("set_user_16") + wantUser: func() *acl.User { + user := acl.CreateUser("set_user_16") user.Enabled = true user.NoPassword = true - user.Passwords = []internal_acl.Password{} + user.Passwords = []acl.Password{} user.Normalise() return user }(), }, { // 17. Delete all existing users passwords using 'nopass' - presetUser: func() *internal_acl.User { - user := internal_acl.CreateUser("set_user_17") + presetUser: func() *acl.User { + user := acl.CreateUser("set_user_17") user.Enabled = true user.NoPassword = true - user.Passwords = []internal_acl.Password{ - {PasswordType: internal_acl.PasswordPlainText, PasswordValue: "password1"}, - {PasswordType: internal_acl.PasswordSHA256, PasswordValue: generateSHA256Password("password2")}, + user.Passwords = []acl.Password{ + {PasswordType: acl.PasswordPlainText, PasswordValue: "password1"}, + {PasswordType: acl.PasswordSHA256, PasswordValue: generateSHA256Password("password2")}, } user.Normalise() return user @@ -891,24 +891,24 @@ func Test_HandleSetUser(t *testing.T) { }, wantRes: "OK", wantErr: "", - wantUser: func() *internal_acl.User { - user := internal_acl.CreateUser("set_user_17") + wantUser: func() *acl.User { + user := acl.CreateUser("set_user_17") user.Enabled = true user.NoPassword = true - user.Passwords = []internal_acl.Password{} + user.Passwords = []acl.Password{} user.Normalise() return user }(), }, { // 18. Clear all of an existing user's passwords using 'resetpass' - presetUser: func() *internal_acl.User { - user := internal_acl.CreateUser("set_user_18") + presetUser: func() *acl.User { + user := acl.CreateUser("set_user_18") user.Enabled = true user.NoPassword = true - user.Passwords = []internal_acl.Password{ - {PasswordType: internal_acl.PasswordPlainText, PasswordValue: "password1"}, - {PasswordType: internal_acl.PasswordSHA256, PasswordValue: generateSHA256Password("password2")}, + user.Passwords = []acl.Password{ + {PasswordType: acl.PasswordPlainText, PasswordValue: "password1"}, + {PasswordType: acl.PasswordSHA256, PasswordValue: generateSHA256Password("password2")}, } user.Normalise() return user @@ -922,19 +922,19 @@ func Test_HandleSetUser(t *testing.T) { }, wantRes: "OK", wantErr: "", - wantUser: func() *internal_acl.User { - user := internal_acl.CreateUser("set_user_18") + wantUser: func() *acl.User { + user := acl.CreateUser("set_user_18") user.Enabled = true user.NoPassword = true - user.Passwords = []internal_acl.Password{} + user.Passwords = []acl.Password{} user.Normalise() return user }(), }, { // 19. Clear all of an existing user's command privileges using 'nocommands' - presetUser: func() *internal_acl.User { - user := internal_acl.CreateUser("set_user_19") + presetUser: func() *acl.User { + user := acl.CreateUser("set_user_19") user.Enabled = true user.IncludedCommands = []string{"acl|getuser", "acl|setuser", "acl|deluser"} user.ExcludedCommands = []string{"rewriteaof", "save"} @@ -950,8 +950,8 @@ func Test_HandleSetUser(t *testing.T) { }, wantRes: "OK", wantErr: "", - wantUser: func() *internal_acl.User { - user := internal_acl.CreateUser("set_user_19") + wantUser: func() *acl.User { + user := acl.CreateUser("set_user_19") user.Enabled = true user.IncludedCommands = []string{} user.ExcludedCommands = []string{"*"} @@ -963,8 +963,8 @@ func Test_HandleSetUser(t *testing.T) { }, { // 20. Clear all of an existing user's allowed keys using 'resetkeys' - presetUser: func() *internal_acl.User { - user := internal_acl.CreateUser("set_user_20") + presetUser: func() *acl.User { + user := acl.CreateUser("set_user_20") user.Enabled = true user.IncludedWriteKeys = []string{"key1", "key2", "key3", "key4", "key5", "key6"} user.IncludedReadKeys = []string{"key1", "key2", "key3", "key7", "key8", "key9"} @@ -980,8 +980,8 @@ func Test_HandleSetUser(t *testing.T) { }, wantRes: "OK", wantErr: "", - wantUser: func() *internal_acl.User { - user := internal_acl.CreateUser("set_user_20") + wantUser: func() *acl.User { + user := acl.CreateUser("set_user_20") user.Enabled = true user.NoKeys = true user.IncludedReadKeys = []string{} @@ -992,8 +992,8 @@ func Test_HandleSetUser(t *testing.T) { }, { // 21. Allow user to access all channels using 'resetchannels' - presetUser: func() *internal_acl.User { - user := internal_acl.CreateUser("set_user_21") + presetUser: func() *acl.User { + user := acl.CreateUser("set_user_21") user.IncludedPubSubChannels = []string{"channel1", "channel2"} user.ExcludedPubSubChannels = []string{"channel3", "channel4"} user.Normalise() @@ -1007,8 +1007,8 @@ func Test_HandleSetUser(t *testing.T) { }, wantRes: "OK", wantErr: "", - wantUser: func() *internal_acl.User { - user := internal_acl.CreateUser("set_user_21") + wantUser: func() *acl.User { + user := acl.CreateUser("set_user_21") user.IncludedPubSubChannels = []string{} user.ExcludedPubSubChannels = []string{"*"} user.Normalise() @@ -1019,7 +1019,7 @@ func Test_HandleSetUser(t *testing.T) { for i, test := range tests { if test.presetUser != nil { - acl.AddUsers([]*internal_acl.User{test.presetUser}) + a.AddUsers([]*acl.User{test.presetUser}) } if err = r.WriteArray(test.cmd); err != nil { t.Error(err) @@ -1041,13 +1041,13 @@ func Test_HandleSetUser(t *testing.T) { continue } expectedUser := test.wantUser - currUserIdx := slices.IndexFunc(acl.Users, func(user *internal_acl.User) bool { + currUserIdx := slices.IndexFunc(a.Users, func(user *acl.User) bool { return user.Username == expectedUser.Username }) if currUserIdx == -1 { t.Errorf("expected to find user with username \"%s\" but could not find them.", expectedUser.Username) } - if err = compareUsers(expectedUser, acl.Users[currUserIdx]); err != nil { + if err = compareUsers(expectedUser, a.Users[currUserIdx]); err != nil { t.Errorf("test idx: %d, %+v", i, err) } } @@ -1064,7 +1064,7 @@ func Test_HandleGetUser(t *testing.T) { }() wg.Wait() - acl, _ := mockServer.GetACL().(*internal_acl.ACL) + a := getACL(mockServer) conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", bindAddr, port)) if err != nil { @@ -1079,20 +1079,20 @@ func Test_HandleGetUser(t *testing.T) { r := resp.NewConn(conn) tests := []struct { - presetUser *internal_acl.User + presetUser *acl.User cmd []resp.Value wantRes []resp.Value wantErr string }{ { // 1. Get the user and all their details - presetUser: &internal_acl.User{ + presetUser: &acl.User{ Username: "get_user_1", Enabled: true, NoPassword: false, NoKeys: false, - Passwords: []internal_acl.Password{ - {PasswordType: internal_acl.PasswordPlainText, PasswordValue: "get_user_password_1"}, - {PasswordType: internal_acl.PasswordSHA256, PasswordValue: generateSHA256Password("get_user_password_2")}, + Passwords: []acl.Password{ + {PasswordType: acl.PasswordPlainText, PasswordValue: "get_user_password_1"}, + {PasswordType: acl.PasswordSHA256, PasswordValue: generateSHA256Password("get_user_password_2")}, }, IncludedCategories: []string{constants.WriteCategory, constants.ReadCategory, constants.PubSubCategory}, ExcludedCategories: []string{constants.AdminCategory, constants.ConnectionCategory, constants.DangerousCategory}, @@ -1164,7 +1164,7 @@ func Test_HandleGetUser(t *testing.T) { for _, test := range tests { if test.presetUser != nil { - acl.AddUsers([]*internal_acl.User{test.presetUser}) + a.AddUsers([]*acl.User{test.presetUser}) } if err = r.WriteArray(test.cmd); err != nil { t.Error(err) @@ -1217,7 +1217,7 @@ func Test_HandleDelUser(t *testing.T) { }() wg.Wait() - acl, _ := mockServer.GetACL().(*internal_acl.ACL) + a := getACL(mockServer) conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", bindAddr, port)) if err != nil { @@ -1232,14 +1232,14 @@ func Test_HandleDelUser(t *testing.T) { r := resp.NewConn(conn) tests := []struct { - presetUser *internal_acl.User + presetUser *acl.User cmd []resp.Value wantRes string wantErr string }{ { // 1. Delete existing user while skipping default user and non-existent user - presetUser: internal_acl.CreateUser("user_to_delete"), + presetUser: acl.CreateUser("user_to_delete"), cmd: []resp.Value{ resp.StringValue("ACL"), resp.StringValue("DELUSER"), @@ -1261,7 +1261,7 @@ func Test_HandleDelUser(t *testing.T) { for _, test := range tests { if test.presetUser != nil { - acl.AddUsers([]*internal_acl.User{test.presetUser}) + a.AddUsers([]*acl.User{test.presetUser}) } if err = r.WriteArray(test.cmd); err != nil { t.Error(err) @@ -1277,13 +1277,13 @@ func Test_HandleDelUser(t *testing.T) { continue } // Check that default user still exists in the list of users - if !slices.ContainsFunc(acl.Users, func(user *internal_acl.User) bool { + if !slices.ContainsFunc(a.Users, func(user *acl.User) bool { return user.Username == "default" }) { t.Error("could not find user with username \"default\" in the ACL after deleting user") } // Check that the deleted user is no longer in the list - if slices.ContainsFunc(acl.Users, func(user *internal_acl.User) bool { + if slices.ContainsFunc(a.Users, func(user *acl.User) bool { return user.Username == "user_to_delete" }) { t.Error("deleted user found in the ACL") @@ -1367,7 +1367,7 @@ func Test_HandleList(t *testing.T) { }() wg.Wait() - acl, _ := mockServer.GetACL().(*internal_acl.ACL) + a := getACL(mockServer) conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", bindAddr, port)) if err != nil { @@ -1382,21 +1382,21 @@ func Test_HandleList(t *testing.T) { r := resp.NewConn(conn) tests := []struct { - presetUsers []*internal_acl.User + presetUsers []*acl.User cmd []resp.Value wantRes []string wantErr string }{ { // 1. Get the user and all their details - presetUsers: []*internal_acl.User{ + presetUsers: []*acl.User{ { Username: "list_user_1", Enabled: true, NoPassword: false, NoKeys: false, - Passwords: []internal_acl.Password{ - {PasswordType: internal_acl.PasswordPlainText, PasswordValue: "list_user_password_1"}, - {PasswordType: internal_acl.PasswordSHA256, PasswordValue: generateSHA256Password("list_user_password_2")}, + Passwords: []acl.Password{ + {PasswordType: acl.PasswordPlainText, PasswordValue: "list_user_password_1"}, + {PasswordType: acl.PasswordSHA256, PasswordValue: generateSHA256Password("list_user_password_2")}, }, IncludedCategories: []string{constants.WriteCategory, constants.ReadCategory, constants.PubSubCategory}, ExcludedCategories: []string{constants.AdminCategory, constants.ConnectionCategory, constants.DangerousCategory}, @@ -1412,7 +1412,7 @@ func Test_HandleList(t *testing.T) { Enabled: true, NoPassword: true, NoKeys: true, - Passwords: []internal_acl.Password{}, + Passwords: []acl.Password{}, IncludedCategories: []string{constants.WriteCategory, constants.ReadCategory, constants.PubSubCategory}, ExcludedCategories: []string{constants.AdminCategory, constants.ConnectionCategory, constants.DangerousCategory}, IncludedCommands: []string{"acl|setuser", "acl|getuser", "acl|deluser"}, @@ -1427,9 +1427,9 @@ func Test_HandleList(t *testing.T) { Enabled: true, NoPassword: false, NoKeys: false, - Passwords: []internal_acl.Password{ - {PasswordType: internal_acl.PasswordPlainText, PasswordValue: "list_user_password_3"}, - {PasswordType: internal_acl.PasswordSHA256, PasswordValue: generateSHA256Password("list_user_password_4")}, + Passwords: []acl.Password{ + {PasswordType: acl.PasswordPlainText, PasswordValue: "list_user_password_3"}, + {PasswordType: acl.PasswordSHA256, PasswordValue: generateSHA256Password("list_user_password_4")}, }, IncludedCategories: []string{constants.WriteCategory, constants.ReadCategory, constants.PubSubCategory}, ExcludedCategories: []string{constants.AdminCategory, constants.ConnectionCategory, constants.DangerousCategory}, @@ -1456,7 +1456,7 @@ func Test_HandleList(t *testing.T) { } for _, test := range tests { - acl.AddUsers(test.presetUsers) + a.AddUsers(test.presetUsers) if err = r.WriteArray(test.cmd); err != nil { t.Error(err) diff --git a/pkg/echovault/api_connection_test.go b/test/modules/admin/api_test.go similarity index 97% rename from pkg/echovault/api_connection_test.go rename to test/modules/admin/api_test.go index afd234ac..d643816b 100644 --- a/pkg/echovault/api_connection_test.go +++ b/test/modules/admin/api_test.go @@ -12,4 +12,4 @@ // See the License for the specific language governing permissions and // limitations under the License. -package echovault +package admin diff --git a/test/modules/admin/commands_test.go b/test/modules/admin/commands_test.go new file mode 100644 index 00000000..fd6ea317 --- /dev/null +++ b/test/modules/admin/commands_test.go @@ -0,0 +1,97 @@ +// 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 admin + +import ( + "bytes" + "context" + "fmt" + "github.com/echovault/echovault/internal" + "github.com/echovault/echovault/internal/config" + "github.com/echovault/echovault/pkg/constants" + "github.com/echovault/echovault/pkg/echovault" + "github.com/tidwall/resp" + "net" + "reflect" + "strings" + "testing" + "unsafe" +) + +var mockServer *echovault.EchoVault + +func init() { + mockServer, _ = echovault.NewEchoVault( + echovault.WithConfig(config.Config{ + DataDir: "", + EvictionPolicy: constants.NoEviction, + }), + ) +} + +func getUnexportedField(field reflect.Value) interface{} { + return reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())).Elem().Interface() +} + +func getHandler(commands ...string) internal.HandlerFunc { + if len(commands) == 0 { + return nil + } + getCommands := + getUnexportedField(reflect.ValueOf(mockServer).Elem().FieldByName("getCommands")).(func() []internal.Command) + for _, c := range getCommands() { + if strings.EqualFold(commands[0], c.Command) && len(commands) == 1 { + // Get command handler + return c.HandlerFunc + } + if strings.EqualFold(commands[0], c.Command) { + // Get sub-command handler + for _, sc := range c.SubCommands { + if strings.EqualFold(commands[1], sc.Command) { + return sc.HandlerFunc + } + } + } + } + return nil +} + +func getHandlerFuncParams(ctx context.Context, cmd []string, conn *net.Conn) internal.HandlerFuncParams { + getCommands := + getUnexportedField(reflect.ValueOf(mockServer).Elem().FieldByName("getCommands")).(func() []internal.Command) + return internal.HandlerFuncParams{ + Context: ctx, + Command: cmd, + Connection: conn, + GetAllCommands: getCommands, + } +} + +func Test_CommandsHandler(t *testing.T) { + res, err := getHandler("COMMANDS")(getHandlerFuncParams(context.Background(), []string{"commands"}, nil)) + if err != nil { + t.Error(err) + } + + rd := resp.NewReader(bytes.NewReader(res)) + rv, _, err := rd.ReadValue() + if err != nil { + t.Error(err) + } + + for _, element := range rv.Array() { + fmt.Println(element) + } +} diff --git a/pkg/echovault/api_admin_test.go b/test/modules/connection/api_test.go similarity index 96% rename from pkg/echovault/api_admin_test.go rename to test/modules/connection/api_test.go index afd234ac..dacccb84 100644 --- a/pkg/echovault/api_admin_test.go +++ b/test/modules/connection/api_test.go @@ -12,4 +12,4 @@ // See the License for the specific language governing permissions and // limitations under the License. -package echovault +package connection diff --git a/pkg/modules/connection/commands_test.go b/test/modules/connection/commands_test.go similarity index 64% rename from pkg/modules/connection/commands_test.go rename to test/modules/connection/commands_test.go index 3d96fa55..6e3a05b6 100644 --- a/pkg/modules/connection/commands_test.go +++ b/test/modules/connection/commands_test.go @@ -18,11 +18,16 @@ import ( "bytes" "context" "errors" + "github.com/echovault/echovault/internal" "github.com/echovault/echovault/internal/config" "github.com/echovault/echovault/pkg/constants" "github.com/echovault/echovault/pkg/echovault" "github.com/tidwall/resp" + "net" + "reflect" + "strings" "testing" + "unsafe" ) var mockServer *echovault.EchoVault @@ -36,6 +41,41 @@ func init() { ) } +func getUnexportedField(field reflect.Value) interface{} { + return reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())).Elem().Interface() +} + +func getHandler(commands ...string) internal.HandlerFunc { + if len(commands) == 0 { + return nil + } + getCommands := + getUnexportedField(reflect.ValueOf(mockServer).Elem().FieldByName("getCommands")).(func() []internal.Command) + for _, c := range getCommands() { + if strings.EqualFold(commands[0], c.Command) && len(commands) == 1 { + // Get command handler + return c.HandlerFunc + } + if strings.EqualFold(commands[0], c.Command) { + // Get sub-command handler + for _, sc := range c.SubCommands { + if strings.EqualFold(commands[1], sc.Command) { + return sc.HandlerFunc + } + } + } + } + return nil +} + +func getHandlerFuncParams(ctx context.Context, cmd []string, conn *net.Conn) internal.HandlerFuncParams { + return internal.HandlerFuncParams{ + Context: ctx, + Command: cmd, + Connection: conn, + } +} + func Test_HandlePing(t *testing.T) { ctx := context.Background() @@ -62,7 +102,7 @@ func Test_HandlePing(t *testing.T) { } for _, test := range tests { - res, err := handlePing(ctx, test.command, mockServer, nil) + res, err := getHandler("PING")(getHandlerFuncParams(ctx, test.command, nil)) if test.expectedErr != nil && err != nil { if err.Error() != test.expectedErr.Error() { t.Errorf("expected error %s, got: %s", test.expectedErr.Error(), err.Error()) diff --git a/pkg/echovault/api_generic_test.go b/test/modules/generic/api_test.go similarity index 85% rename from pkg/echovault/api_generic_test.go rename to test/modules/generic/api_test.go index 9ec5b451..b18281b9 100644 --- a/pkg/echovault/api_generic_test.go +++ b/test/modules/generic/api_test.go @@ -12,14 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -package echovault +package generic import ( + "context" "github.com/echovault/echovault/internal" "github.com/echovault/echovault/internal/clock" "github.com/echovault/echovault/internal/config" - "github.com/echovault/echovault/pkg/commands" - "github.com/echovault/echovault/pkg/constants" + "github.com/echovault/echovault/pkg/echovault" "reflect" "slices" "strings" @@ -27,13 +27,35 @@ import ( "time" ) -func TestEchoVault_DEL(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - EvictionPolicy: constants.NoEviction, +func createEchoVault() *echovault.EchoVault { + ev, _ := echovault.NewEchoVault( + echovault.WithConfig(config.Config{ + DataDir: "", }), ) + return ev +} + +func presetValue(server *echovault.EchoVault, ctx context.Context, key string, value interface{}) error { + if _, err := server.CreateKeyAndLock(ctx, key); err != nil { + return err + } + if err := server.SetValue(ctx, key, value); err != nil { + return err + } + server.KeyUnlock(ctx, key) + return nil +} + +func presetKeyData(server *echovault.EchoVault, ctx context.Context, key string, data internal.KeyData) { + _, _ = server.CreateKeyAndLock(ctx, key) + defer server.KeyUnlock(ctx, key) + _ = server.SetValue(ctx, key, data.Value) + server.SetExpiry(ctx, key, data.ExpireAt, false) +} + +func TestEchoVault_DEL(t *testing.T) { + server := createEchoVault() tests := []struct { name string @@ -59,7 +81,7 @@ func TestEchoVault_DEL(t *testing.T) { t.Run(tt.name, func(t *testing.T) { if tt.presetValues != nil { for k, d := range tt.presetValues { - presetKeyData(server, k, d) + presetKeyData(server, context.Background(), k, d) } } got, err := server.DEL(tt.keys...) @@ -77,12 +99,7 @@ func TestEchoVault_DEL(t *testing.T) { func TestEchoVault_EXPIRE(t *testing.T) { mockClock := clock.NewClock() - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string @@ -90,8 +107,8 @@ func TestEchoVault_EXPIRE(t *testing.T) { cmd string key string time int - expireOpts EXPIREOptions - pexpireOpts PEXPIREOptions + expireOpts echovault.EXPIREOptions + pexpireOpts echovault.PEXPIREOptions want int wantErr bool }{ @@ -100,7 +117,7 @@ func TestEchoVault_EXPIRE(t *testing.T) { cmd: "EXPIRE", key: "key1", time: 100, - expireOpts: EXPIREOptions{}, + expireOpts: echovault.EXPIREOptions{}, presetValues: map[string]internal.KeyData{ "key1": {Value: "value1", ExpireAt: time.Time{}}, }, @@ -112,7 +129,7 @@ func TestEchoVault_EXPIRE(t *testing.T) { cmd: "PEXPIRE", key: "key2", time: 1000, - pexpireOpts: PEXPIREOptions{}, + pexpireOpts: echovault.PEXPIREOptions{}, presetValues: map[string]internal.KeyData{ "key2": {Value: "value2", ExpireAt: time.Time{}}, }, @@ -124,7 +141,7 @@ func TestEchoVault_EXPIRE(t *testing.T) { cmd: "EXPIRE", key: "key3", time: 1000, - expireOpts: EXPIREOptions{NX: true}, + expireOpts: echovault.EXPIREOptions{NX: true}, presetValues: map[string]internal.KeyData{ "key3": {Value: "value3", ExpireAt: time.Time{}}, }, @@ -136,7 +153,7 @@ func TestEchoVault_EXPIRE(t *testing.T) { cmd: "EXPIRE", key: "key4", time: 1000, - expireOpts: EXPIREOptions{NX: true}, + expireOpts: echovault.EXPIREOptions{NX: true}, presetValues: map[string]internal.KeyData{ "key4": {Value: "value4", ExpireAt: mockClock.Now().Add(1000 * time.Second)}, }, @@ -148,7 +165,7 @@ func TestEchoVault_EXPIRE(t *testing.T) { cmd: "EXPIRE", key: "key5", time: 1000, - expireOpts: EXPIREOptions{XX: true}, + expireOpts: echovault.EXPIREOptions{XX: true}, presetValues: map[string]internal.KeyData{ "key5": {Value: "value5", ExpireAt: mockClock.Now().Add(30 * time.Second)}, }, @@ -159,7 +176,7 @@ func TestEchoVault_EXPIRE(t *testing.T) { name: "Return 0 when key does not have an expiry and the XX flag is provided", cmd: "EXPIRE", time: 1000, - expireOpts: EXPIREOptions{XX: true}, + expireOpts: echovault.EXPIREOptions{XX: true}, key: "key6", presetValues: map[string]internal.KeyData{ "key6": {Value: "value6", ExpireAt: time.Time{}}, @@ -172,7 +189,7 @@ func TestEchoVault_EXPIRE(t *testing.T) { cmd: "EXPIRE", key: "key7", time: 100000, - expireOpts: EXPIREOptions{GT: true}, + expireOpts: echovault.EXPIREOptions{GT: true}, presetValues: map[string]internal.KeyData{ "key7": {Value: "value7", ExpireAt: mockClock.Now().Add(30 * time.Second)}, }, @@ -184,7 +201,7 @@ func TestEchoVault_EXPIRE(t *testing.T) { cmd: "EXPIRE", key: "key8", time: 1000, - expireOpts: EXPIREOptions{GT: true}, + expireOpts: echovault.EXPIREOptions{GT: true}, presetValues: map[string]internal.KeyData{ "key8": {Value: "value8", ExpireAt: mockClock.Now().Add(3000 * time.Second)}, }, @@ -196,7 +213,7 @@ func TestEchoVault_EXPIRE(t *testing.T) { cmd: "EXPIRE", key: "key9", time: 1000, - expireOpts: EXPIREOptions{GT: true}, + expireOpts: echovault.EXPIREOptions{GT: true}, presetValues: map[string]internal.KeyData{ "key9": {Value: "value9", ExpireAt: time.Time{}}, }, @@ -208,7 +225,7 @@ func TestEchoVault_EXPIRE(t *testing.T) { cmd: "EXPIRE", key: "key10", time: 1000, - expireOpts: EXPIREOptions{LT: true}, + expireOpts: echovault.EXPIREOptions{LT: true}, presetValues: map[string]internal.KeyData{ "key10": {Value: "value10", ExpireAt: mockClock.Now().Add(3000 * time.Second)}, }, @@ -220,7 +237,7 @@ func TestEchoVault_EXPIRE(t *testing.T) { cmd: "EXPIRE", key: "key11", time: 50000, - expireOpts: EXPIREOptions{LT: true}, + expireOpts: echovault.EXPIREOptions{LT: true}, presetValues: map[string]internal.KeyData{ "key11": {Value: "value11", ExpireAt: mockClock.Now().Add(30 * time.Second)}, }, @@ -232,7 +249,7 @@ func TestEchoVault_EXPIRE(t *testing.T) { t.Run(tt.name, func(t *testing.T) { if tt.presetValues != nil { for k, d := range tt.presetValues { - presetKeyData(server, k, d) + presetKeyData(server, context.Background(), k, d) } } var got int @@ -256,12 +273,7 @@ func TestEchoVault_EXPIRE(t *testing.T) { func TestEchoVault_EXPIREAT(t *testing.T) { mockClock := clock.NewClock() - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string @@ -269,8 +281,8 @@ func TestEchoVault_EXPIREAT(t *testing.T) { cmd string key string time int - expireAtOpts EXPIREATOptions - pexpireAtOpts PEXPIREATOptions + expireAtOpts echovault.EXPIREATOptions + pexpireAtOpts echovault.PEXPIREATOptions want int wantErr bool }{ @@ -278,7 +290,7 @@ func TestEchoVault_EXPIREAT(t *testing.T) { name: "Set new expire by unix seconds", cmd: "EXPIREAT", key: "key1", - expireAtOpts: EXPIREATOptions{}, + expireAtOpts: echovault.EXPIREATOptions{}, time: int(mockClock.Now().Add(1000 * time.Second).Unix()), presetValues: map[string]internal.KeyData{ "key1": {Value: "value1", ExpireAt: time.Time{}}, @@ -290,7 +302,7 @@ func TestEchoVault_EXPIREAT(t *testing.T) { name: "Set new expire by milliseconds", cmd: "PEXPIREAT", key: "key2", - pexpireAtOpts: PEXPIREATOptions{}, + pexpireAtOpts: echovault.PEXPIREATOptions{}, time: int(mockClock.Now().Add(1000 * time.Second).UnixMilli()), presetValues: map[string]internal.KeyData{ "key2": {Value: "value2", ExpireAt: time.Time{}}, @@ -303,7 +315,7 @@ func TestEchoVault_EXPIREAT(t *testing.T) { cmd: "EXPIREAT", key: "key3", time: int(mockClock.Now().Add(1000 * time.Second).Unix()), - expireAtOpts: EXPIREATOptions{NX: true}, + expireAtOpts: echovault.EXPIREATOptions{NX: true}, presetValues: map[string]internal.KeyData{ "key3": {Value: "value3", ExpireAt: time.Time{}}, }, @@ -314,7 +326,7 @@ func TestEchoVault_EXPIREAT(t *testing.T) { name: "Return 0, when NX flag is provided and key already has an expiry time", cmd: "EXPIREAT", time: int(mockClock.Now().Add(1000 * time.Second).Unix()), - expireAtOpts: EXPIREATOptions{NX: true}, + expireAtOpts: echovault.EXPIREATOptions{NX: true}, key: "key4", presetValues: map[string]internal.KeyData{ "key4": {Value: "value4", ExpireAt: mockClock.Now().Add(1000 * time.Second)}, @@ -327,7 +339,7 @@ func TestEchoVault_EXPIREAT(t *testing.T) { cmd: "EXPIREAT", time: int(mockClock.Now().Add(1000 * time.Second).Unix()), key: "key5", - expireAtOpts: EXPIREATOptions{XX: true}, + expireAtOpts: echovault.EXPIREATOptions{XX: true}, presetValues: map[string]internal.KeyData{ "key5": {Value: "value5", ExpireAt: mockClock.Now().Add(30 * time.Second)}, }, @@ -339,7 +351,7 @@ func TestEchoVault_EXPIREAT(t *testing.T) { cmd: "EXPIREAT", key: "key6", time: int(mockClock.Now().Add(1000 * time.Second).Unix()), - expireAtOpts: EXPIREATOptions{XX: true}, + expireAtOpts: echovault.EXPIREATOptions{XX: true}, presetValues: map[string]internal.KeyData{ "key6": {Value: "value6", ExpireAt: time.Time{}}, }, @@ -351,7 +363,7 @@ func TestEchoVault_EXPIREAT(t *testing.T) { cmd: "EXPIREAT", key: "key7", time: int(mockClock.Now().Add(1000 * time.Second).Unix()), - expireAtOpts: EXPIREATOptions{GT: true}, + expireAtOpts: echovault.EXPIREATOptions{GT: true}, presetValues: map[string]internal.KeyData{ "key7": {Value: "value7", ExpireAt: mockClock.Now().Add(30 * time.Second)}, }, @@ -363,7 +375,7 @@ func TestEchoVault_EXPIREAT(t *testing.T) { cmd: "EXPIREAT", key: "key8", time: int(mockClock.Now().Add(1000 * time.Second).Unix()), - expireAtOpts: EXPIREATOptions{GT: true}, + expireAtOpts: echovault.EXPIREATOptions{GT: true}, presetValues: map[string]internal.KeyData{ "key8": {Value: "value8", ExpireAt: mockClock.Now().Add(3000 * time.Second)}, }, @@ -375,7 +387,7 @@ func TestEchoVault_EXPIREAT(t *testing.T) { cmd: "EXPIREAT", key: "key9", time: int(mockClock.Now().Add(1000 * time.Second).Unix()), - expireAtOpts: EXPIREATOptions{GT: true}, + expireAtOpts: echovault.EXPIREATOptions{GT: true}, presetValues: map[string]internal.KeyData{ "key9": {Value: "value9", ExpireAt: time.Time{}}, }, @@ -386,7 +398,7 @@ func TestEchoVault_EXPIREAT(t *testing.T) { cmd: "EXPIREAT", key: "key10", time: int(mockClock.Now().Add(1000 * time.Second).Unix()), - expireAtOpts: EXPIREATOptions{LT: true}, + expireAtOpts: echovault.EXPIREATOptions{LT: true}, presetValues: map[string]internal.KeyData{ "key10": {Value: "value10", ExpireAt: mockClock.Now().Add(3000 * time.Second)}, }, @@ -398,7 +410,7 @@ func TestEchoVault_EXPIREAT(t *testing.T) { cmd: "EXPIREAT", key: "key11", time: int(mockClock.Now().Add(3000 * time.Second).Unix()), - expireAtOpts: EXPIREATOptions{LT: true}, + expireAtOpts: echovault.EXPIREATOptions{LT: true}, presetValues: map[string]internal.KeyData{ "key11": {Value: "value11", ExpireAt: mockClock.Now().Add(1000 * time.Second)}, }, @@ -410,7 +422,7 @@ func TestEchoVault_EXPIREAT(t *testing.T) { cmd: "EXPIREAT", key: "key12", time: int(mockClock.Now().Add(1000 * time.Second).Unix()), - expireAtOpts: EXPIREATOptions{LT: true}, + expireAtOpts: echovault.EXPIREATOptions{LT: true}, presetValues: map[string]internal.KeyData{ "key12": {Value: "value12", ExpireAt: time.Time{}}, }, @@ -422,7 +434,7 @@ func TestEchoVault_EXPIREAT(t *testing.T) { t.Run(tt.name, func(t *testing.T) { if tt.presetValues != nil { for k, d := range tt.presetValues { - presetKeyData(server, k, d) + presetKeyData(server, context.Background(), k, d) } } var got int @@ -446,12 +458,7 @@ func TestEchoVault_EXPIREAT(t *testing.T) { func TestEchoVault_EXPIRETIME(t *testing.T) { mockClock := clock.NewClock() - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string @@ -503,7 +510,7 @@ func TestEchoVault_EXPIRETIME(t *testing.T) { t.Run(tt.name, func(t *testing.T) { if tt.presetValues != nil { for k, d := range tt.presetValues { - presetKeyData(server, k, d) + presetKeyData(server, context.Background(), k, d) } } got, err := tt.expiretimeFunc(tt.key) @@ -519,12 +526,7 @@ func TestEchoVault_EXPIRETIME(t *testing.T) { } func TestEchoVault_GET(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string @@ -551,7 +553,11 @@ func TestEchoVault_GET(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.presetValue != nil { - presetValue(server, tt.key, tt.presetValue) + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } } got, err := server.GET(tt.key) if (err != nil) != tt.wantErr { @@ -566,12 +572,7 @@ func TestEchoVault_GET(t *testing.T) { } func TestEchoVault_MGET(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string @@ -599,7 +600,11 @@ func TestEchoVault_MGET(t *testing.T) { t.Run(tt.name, func(t *testing.T) { if tt.presetValues != nil { for k, v := range tt.presetValues { - presetValue(server, k, v) + err := presetValue(server, context.Background(), k, v) + if err != nil { + t.Error(err) + return + } } } got, err := server.MGET(tt.keys...) @@ -625,19 +630,14 @@ func TestEchoVault_MGET(t *testing.T) { func TestEchoVault_SET(t *testing.T) { mockClock := clock.NewClock() - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string presetValues map[string]internal.KeyData key string value string - options SETOptions + options echovault.SETOptions want string wantErr bool }{ @@ -646,7 +646,7 @@ func TestEchoVault_SET(t *testing.T) { presetValues: nil, key: "key1", value: "value1", - options: SETOptions{}, + options: echovault.SETOptions{}, want: "OK", wantErr: false, }, @@ -655,7 +655,7 @@ func TestEchoVault_SET(t *testing.T) { presetValues: nil, key: "key2", value: "value2", - options: SETOptions{NX: true}, + options: echovault.SETOptions{NX: true}, want: "OK", wantErr: false, }, @@ -669,7 +669,7 @@ func TestEchoVault_SET(t *testing.T) { }, key: "key3", value: "value3", - options: SETOptions{NX: true}, + options: echovault.SETOptions{NX: true}, want: "", wantErr: true, }, @@ -683,7 +683,7 @@ func TestEchoVault_SET(t *testing.T) { }, key: "key4", value: "value4", - options: SETOptions{XX: true}, + options: echovault.SETOptions{XX: true}, want: "OK", wantErr: false, }, @@ -692,7 +692,7 @@ func TestEchoVault_SET(t *testing.T) { presetValues: nil, key: "key5", value: "value5", - options: SETOptions{XX: true}, + options: echovault.SETOptions{XX: true}, want: "", wantErr: true, }, @@ -701,7 +701,7 @@ func TestEchoVault_SET(t *testing.T) { presetValues: nil, key: "key6", value: "value6", - options: SETOptions{EX: 100}, + options: echovault.SETOptions{EX: 100}, want: "OK", wantErr: false, }, @@ -710,7 +710,7 @@ func TestEchoVault_SET(t *testing.T) { presetValues: nil, key: "key7", value: "value7", - options: SETOptions{PX: 4096}, + options: echovault.SETOptions{PX: 4096}, want: "OK", wantErr: false, }, @@ -719,7 +719,7 @@ func TestEchoVault_SET(t *testing.T) { presetValues: nil, key: "key8", value: "value8", - options: SETOptions{EXAT: int(mockClock.Now().Add(200 * time.Second).Unix())}, + options: echovault.SETOptions{EXAT: int(mockClock.Now().Add(200 * time.Second).Unix())}, want: "OK", wantErr: false, }, @@ -727,7 +727,7 @@ func TestEchoVault_SET(t *testing.T) { name: "Set exact expiry time in milliseconds from unix epoch", key: "key9", value: "value9", - options: SETOptions{PXAT: int(mockClock.Now().Add(4096 * time.Millisecond).UnixMilli())}, + options: echovault.SETOptions{PXAT: int(mockClock.Now().Add(4096 * time.Millisecond).UnixMilli())}, presetValues: nil, want: "OK", wantErr: false, @@ -742,7 +742,7 @@ func TestEchoVault_SET(t *testing.T) { }, key: "key10", value: "value10", - options: SETOptions{GET: true, EX: 1000}, + options: echovault.SETOptions{GET: true, EX: 1000}, want: "previous-value", wantErr: false, }, @@ -751,7 +751,7 @@ func TestEchoVault_SET(t *testing.T) { presetValues: nil, key: "key11", value: "value11", - options: SETOptions{GET: true, EX: 1000}, + options: echovault.SETOptions{GET: true, EX: 1000}, want: "", wantErr: false, }, @@ -760,7 +760,7 @@ func TestEchoVault_SET(t *testing.T) { t.Run(tt.name, func(t *testing.T) { if tt.presetValues != nil { for k, d := range tt.presetValues { - presetKeyData(server, k, d) + presetKeyData(server, context.Background(), k, d) } } got, err := server.SET(tt.key, tt.value, tt.options) @@ -776,12 +776,7 @@ func TestEchoVault_SET(t *testing.T) { } func TestEchoVault_MSET(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string @@ -813,12 +808,7 @@ func TestEchoVault_MSET(t *testing.T) { func TestEchoVault_PERSIST(t *testing.T) { mockClock := clock.NewClock() - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string @@ -857,7 +847,7 @@ func TestEchoVault_PERSIST(t *testing.T) { t.Run(tt.name, func(t *testing.T) { if tt.presetValues != nil { for k, d := range tt.presetValues { - presetKeyData(server, k, d) + presetKeyData(server, context.Background(), k, d) } } got, err := server.PERSIST(tt.key) @@ -875,12 +865,7 @@ func TestEchoVault_PERSIST(t *testing.T) { func TestEchoVault_TTL(t *testing.T) { mockClock := clock.NewClock() - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string @@ -933,7 +918,7 @@ func TestEchoVault_TTL(t *testing.T) { t.Run(tt.name, func(t *testing.T) { if tt.presetValues != nil { for k, d := range tt.presetValues { - presetKeyData(server, k, d) + presetKeyData(server, context.Background(), k, d) } } got, err := tt.ttlFunc(tt.key) diff --git a/pkg/modules/generic/commands_test.go b/test/modules/generic/commands_test.go similarity index 91% rename from pkg/modules/generic/commands_test.go rename to test/modules/generic/commands_test.go index fd64081a..9598de09 100644 --- a/pkg/modules/generic/commands_test.go +++ b/test/modules/generic/commands_test.go @@ -19,13 +19,18 @@ import ( "context" "errors" "fmt" + "github.com/echovault/echovault/internal" "github.com/echovault/echovault/internal/clock" "github.com/echovault/echovault/internal/config" "github.com/echovault/echovault/pkg/constants" "github.com/echovault/echovault/pkg/echovault" "github.com/tidwall/resp" + "net" + "reflect" + "strings" "testing" "time" + "unsafe" ) var mockServer *echovault.EchoVault @@ -48,6 +53,55 @@ func init() { ) } +func getUnexportedField(field reflect.Value) interface{} { + return reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())).Elem().Interface() +} + +func getHandler(commands ...string) internal.HandlerFunc { + if len(commands) == 0 { + return nil + } + getCommands := + getUnexportedField(reflect.ValueOf(mockServer).Elem().FieldByName("getCommands")).(func() []internal.Command) + for _, c := range getCommands() { + if strings.EqualFold(commands[0], c.Command) && len(commands) == 1 { + // Get command handler + return c.HandlerFunc + } + if strings.EqualFold(commands[0], c.Command) { + // Get sub-command handler + for _, sc := range c.SubCommands { + if strings.EqualFold(commands[1], sc.Command) { + return sc.HandlerFunc + } + } + } + } + return nil +} + +func getHandlerFuncParams(ctx context.Context, cmd []string, conn *net.Conn) internal.HandlerFuncParams { + getClock := + getUnexportedField(reflect.ValueOf(mockServer).Elem().FieldByName("getClock")).(func() clock.Clock) + return internal.HandlerFuncParams{ + Context: ctx, + Command: cmd, + Connection: conn, + KeyExists: mockServer.KeyExists, + CreateKeyAndLock: mockServer.CreateKeyAndLock, + KeyLock: mockServer.KeyLock, + KeyRLock: mockServer.KeyRLock, + KeyUnlock: mockServer.KeyUnlock, + KeyRUnlock: mockServer.KeyRUnlock, + GetValue: mockServer.GetValue, + SetValue: mockServer.SetValue, + GetExpiry: mockServer.GetExpiry, + SetExpiry: mockServer.SetExpiry, + DeleteKey: mockServer.DeleteKey, + GetClock: getClock, + } +} + func Test_HandleSET(t *testing.T) { tests := []struct { name string @@ -372,7 +426,13 @@ func Test_HandleSET(t *testing.T) { } } - res, err := handleSet(ctx, test.command, mockServer, nil) + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) if test.expectedErr != nil { if err == nil { t.Errorf("expected error \"%s\", got nil", test.expectedErr.Error()) @@ -454,7 +514,14 @@ func Test_HandleMSET(t *testing.T) { for i, test := range tests { t.Run(test.name, func(t *testing.T) { ctx := context.WithValue(context.Background(), "test_name", fmt.Sprintf("MSET, %d", i)) - res, err := handleMSet(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) if test.expectedErr != nil { if err.Error() != test.expectedErr.Error() { t.Errorf("expected error %s, got %s", test.expectedErr.Error(), err.Error()) @@ -547,7 +614,14 @@ func Test_HandleGET(t *testing.T) { } mockServer.KeyUnlock(ctx, key) - res, err := handleGet(ctx, []string{"GET", key}, mockServer, nil) + handler := getHandler("GET") + if handler == nil { + t.Error("no handler found for command GET") + return + } + + res, err := handler(getHandlerFuncParams(ctx, []string{"GET", key}, nil)) + if err != nil { t.Error(err) } @@ -559,7 +633,7 @@ func Test_HandleGET(t *testing.T) { } // Test get non-existent key - res, err := handleGet(context.Background(), []string{"GET", "test4"}, mockServer, nil) + res, err := getHandler("GET")(getHandlerFuncParams(context.Background(), []string{"GET", "test4"}, nil)) if err != nil { t.Error(err) } @@ -585,7 +659,12 @@ func Test_HandleGET(t *testing.T) { } for _, test := range errorTests { t.Run(test.name, func(t *testing.T) { - res, err = handleGet(context.Background(), test.command, mockServer, nil) + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + res, err = handler(getHandlerFuncParams(context.Background(), test.command, nil)) if res != nil { t.Errorf("expected nil response, got: %+v", res) } @@ -631,21 +710,28 @@ func Test_HandleMGET(t *testing.T) { }, } - for _, test := range tests { + for i, test := range tests { t.Run(test.name, func(t *testing.T) { + ctx := context.WithValue(context.Background(), "test_name", fmt.Sprintf("MGET, %d", i)) // Set up the values for i, key := range test.presetKeys { - _, err := mockServer.CreateKeyAndLock(context.Background(), key) + _, err := mockServer.CreateKeyAndLock(ctx, key) if err != nil { t.Error(err) } - if err = mockServer.SetValue(context.Background(), key, test.presetValues[i]); err != nil { + if err = mockServer.SetValue(ctx, key, test.presetValues[i]); err != nil { t.Error(err) } - mockServer.KeyUnlock(context.Background(), key) + mockServer.KeyUnlock(ctx, key) } // Test the command and its results - res, err := handleMGet(context.Background(), test.command, mockServer, nil) + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) if test.expectedError != nil { // If we expect and error, branch out and check error if err.Error() != test.expectedError.Error() { @@ -734,7 +820,13 @@ func Test_HandleDEL(t *testing.T) { } } - res, err := handleDel(ctx, test.command, mockServer, nil) + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) if test.expectedErr != nil { if err == nil { t.Errorf("exected error \"%s\", got nil", test.expectedErr.Error()) @@ -844,7 +936,13 @@ func Test_HandlePERSIST(t *testing.T) { } } - res, err := handlePersist(ctx, test.command, mockServer, nil) + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) if test.expectedError != nil { if err == nil { @@ -965,7 +1063,13 @@ func Test_HandleEXPIRETIME(t *testing.T) { } } - res, err := handleExpireTime(ctx, test.command, mockServer, nil) + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) if test.expectedError != nil { if err == nil { @@ -1067,7 +1171,13 @@ func Test_HandleTTL(t *testing.T) { } } - res, err := handleTTL(ctx, test.command, mockServer, nil) + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) if test.expectedError != nil { if err == nil { @@ -1300,7 +1410,13 @@ func Test_HandleEXPIRE(t *testing.T) { } } - res, err := handleExpire(ctx, test.command, mockServer, nil) + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) if test.expectedError != nil { if err == nil { @@ -1576,7 +1692,13 @@ func Test_HandleEXPIREAT(t *testing.T) { } } - res, err := handleExpireAt(ctx, test.command, mockServer, nil) + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) if test.expectedError != nil { if err == nil { diff --git a/pkg/echovault/api_hash_test.go b/test/modules/hash/api_test.go similarity index 87% rename from pkg/echovault/api_hash_test.go rename to test/modules/hash/api_test.go index 0aaf7d9b..6ef4db5e 100644 --- a/pkg/echovault/api_hash_test.go +++ b/test/modules/hash/api_test.go @@ -12,25 +12,39 @@ // See the License for the specific language governing permissions and // limitations under the License. -package echovault +package hash import ( + "context" "github.com/echovault/echovault/internal/config" - "github.com/echovault/echovault/pkg/commands" - "github.com/echovault/echovault/pkg/constants" + "github.com/echovault/echovault/pkg/echovault" "reflect" "slices" "testing" ) -func TestEchoVault_HDEL(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.NoEviction, +func createEchoVault() *echovault.EchoVault { + ev, _ := echovault.NewEchoVault( + echovault.WithConfig(config.Config{ + DataDir: "", }), ) + return ev +} + +func presetValue(server *echovault.EchoVault, ctx context.Context, key string, value interface{}) error { + if _, err := server.CreateKeyAndLock(ctx, key); err != nil { + return err + } + if err := server.SetValue(ctx, key, value); err != nil { + return err + } + server.KeyUnlock(ctx, key) + return nil +} + +func TestEchoVault_HDEL(t *testing.T) { + server := createEchoVault() tests := []struct { name string @@ -76,7 +90,11 @@ func TestEchoVault_HDEL(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.presetValue != nil { - presetValue(server, tt.key, tt.presetValue) + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } } got, err := server.HDEL(tt.key, tt.fields...) if (err != nil) != tt.wantErr { @@ -91,13 +109,7 @@ func TestEchoVault_HDEL(t *testing.T) { } func TestEchoVault_HEXISTS(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string @@ -135,7 +147,11 @@ func TestEchoVault_HEXISTS(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.presetValue != nil { - presetValue(server, tt.key, tt.presetValue) + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } } got, err := server.HEXISTS(tt.key, tt.field) if (err != nil) != tt.wantErr { @@ -150,13 +166,7 @@ func TestEchoVault_HEXISTS(t *testing.T) { } func TestEchoVault_HGETALL(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string @@ -190,7 +200,11 @@ func TestEchoVault_HGETALL(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.presetValue != nil { - presetValue(server, tt.key, tt.presetValue) + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } } got, err := server.HGETALL(tt.key) if (err != nil) != tt.wantErr { @@ -212,13 +226,7 @@ func TestEchoVault_HGETALL(t *testing.T) { } func TestEchoVault_HINCRBY(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() const ( HINCRBY = "HINCRBY" @@ -300,7 +308,11 @@ func TestEchoVault_HINCRBY(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.presetValue != nil { - presetValue(server, tt.key, tt.presetValue) + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } } var got float64 var err error @@ -326,13 +338,7 @@ func TestEchoVault_HINCRBY(t *testing.T) { } func TestEchoVault_HKEYS(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string @@ -366,7 +372,11 @@ func TestEchoVault_HKEYS(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.presetValue != nil { - presetValue(server, tt.key, tt.presetValue) + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } } got, err := server.HKEYS(tt.key) if (err != nil) != tt.wantErr { @@ -386,13 +396,7 @@ func TestEchoVault_HKEYS(t *testing.T) { } func TestEchoVault_HLEN(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string @@ -426,7 +430,11 @@ func TestEchoVault_HLEN(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.presetValue != nil { - presetValue(server, tt.key, tt.presetValue) + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } } got, err := server.HLEN(tt.key) if (err != nil) != tt.wantErr { @@ -441,19 +449,13 @@ func TestEchoVault_HLEN(t *testing.T) { } func TestEchoVault_HRANDFIELD(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string presetValue interface{} key string - options HRANDFIELDOptions + options echovault.HRANDFIELDOptions wantCount int want []string wantErr bool @@ -462,7 +464,7 @@ func TestEchoVault_HRANDFIELD(t *testing.T) { name: "Get a random field", presetValue: map[string]interface{}{"field1": "value1", "field2": 123456789, "field3": 3.142}, key: "key1", - options: HRANDFIELDOptions{Count: 1}, + options: echovault.HRANDFIELDOptions{Count: 1}, wantCount: 1, want: []string{"field1", "field2", "field3"}, wantErr: false, @@ -471,7 +473,7 @@ func TestEchoVault_HRANDFIELD(t *testing.T) { name: "Get a random field with a value", presetValue: map[string]interface{}{"field1": "value1", "field2": 123456789, "field3": 3.142}, key: "key2", - options: HRANDFIELDOptions{WithValues: true, Count: 1}, + options: echovault.HRANDFIELDOptions{WithValues: true, Count: 1}, wantCount: 2, want: []string{"field1", "value1", "field2", "123456789", "field3", "3.142"}, wantErr: false, @@ -486,7 +488,7 @@ func TestEchoVault_HRANDFIELD(t *testing.T) { "field5": "value5", }, key: "key3", - options: HRANDFIELDOptions{Count: 3}, + options: echovault.HRANDFIELDOptions{Count: 3}, wantCount: 3, want: []string{"field1", "field2", "field3", "field4", "field5"}, wantErr: false, @@ -501,7 +503,7 @@ func TestEchoVault_HRANDFIELD(t *testing.T) { "field5": "value5", }, key: "key4", - options: HRANDFIELDOptions{WithValues: true, Count: 3}, + options: echovault.HRANDFIELDOptions{WithValues: true, Count: 3}, wantCount: 6, want: []string{ "field1", "value1", "field2", "123456789", "field3", @@ -519,7 +521,7 @@ func TestEchoVault_HRANDFIELD(t *testing.T) { "field5": "value5", }, key: "key5", - options: HRANDFIELDOptions{Count: 5}, + options: echovault.HRANDFIELDOptions{Count: 5}, wantCount: 5, want: []string{"field1", "field2", "field3", "field4", "field5"}, wantErr: false, @@ -534,7 +536,7 @@ func TestEchoVault_HRANDFIELD(t *testing.T) { "field5": "value5", }, key: "key5", - options: HRANDFIELDOptions{WithValues: true, Count: 5}, + options: echovault.HRANDFIELDOptions{WithValues: true, Count: 5}, wantCount: 10, want: []string{ "field1", "value1", "field2", "123456789", "field3", @@ -546,7 +548,7 @@ func TestEchoVault_HRANDFIELD(t *testing.T) { name: "Trying to get random field on a non hash map returns error", presetValue: "Default value", key: "key12", - options: HRANDFIELDOptions{}, + options: echovault.HRANDFIELDOptions{}, wantCount: 0, want: nil, wantErr: true, @@ -555,7 +557,11 @@ func TestEchoVault_HRANDFIELD(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.presetValue != nil { - presetValue(server, tt.key, tt.presetValue) + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } } got, err := server.HRANDFIELD(tt.key, tt.options) if (err != nil) != tt.wantErr { @@ -575,13 +581,7 @@ func TestEchoVault_HRANDFIELD(t *testing.T) { } func TestEchoVault_HSET(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string @@ -650,7 +650,11 @@ func TestEchoVault_HSET(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.presetValue != nil { - presetValue(server, tt.key, tt.presetValue) + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } } got, err := tt.hsetFunc(tt.key, tt.fieldValuePairs) if (err != nil) != tt.wantErr { @@ -665,13 +669,7 @@ func TestEchoVault_HSET(t *testing.T) { } func TestEchoVault_HSTRLEN(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string @@ -719,7 +717,11 @@ func TestEchoVault_HSTRLEN(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.presetValue != nil { - presetValue(server, tt.key, tt.presetValue) + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } } got, err := server.HSTRLEN(tt.key, tt.fields...) if (err != nil) != tt.wantErr { @@ -734,13 +736,7 @@ func TestEchoVault_HSTRLEN(t *testing.T) { } func TestEchoVault_HVALS(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string @@ -774,7 +770,11 @@ func TestEchoVault_HVALS(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.presetValue != nil { - presetValue(server, tt.key, tt.presetValue) + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } } got, err := server.HVALS(tt.key) if (err != nil) != tt.wantErr { diff --git a/pkg/modules/hash/commands_test.go b/test/modules/hash/commands_test.go similarity index 91% rename from pkg/modules/hash/commands_test.go rename to test/modules/hash/commands_test.go index 1b9e2ac4..27a05897 100644 --- a/pkg/modules/hash/commands_test.go +++ b/test/modules/hash/commands_test.go @@ -19,12 +19,17 @@ import ( "context" "errors" "fmt" + "github.com/echovault/echovault/internal" "github.com/echovault/echovault/internal/config" "github.com/echovault/echovault/pkg/constants" "github.com/echovault/echovault/pkg/echovault" "github.com/tidwall/resp" + "net" + "reflect" "slices" + "strings" "testing" + "unsafe" ) var mockServer *echovault.EchoVault @@ -38,6 +43,49 @@ func init() { ) } +func getUnexportedField(field reflect.Value) interface{} { + return reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())).Elem().Interface() +} + +func getHandler(commands ...string) internal.HandlerFunc { + if len(commands) == 0 { + return nil + } + getCommands := + getUnexportedField(reflect.ValueOf(mockServer).Elem().FieldByName("getCommands")).(func() []internal.Command) + for _, c := range getCommands() { + if strings.EqualFold(commands[0], c.Command) && len(commands) == 1 { + // Get command handler + return c.HandlerFunc + } + if strings.EqualFold(commands[0], c.Command) { + // Get sub-command handler + for _, sc := range c.SubCommands { + if strings.EqualFold(commands[1], sc.Command) { + return sc.HandlerFunc + } + } + } + } + return nil +} + +func getHandlerFuncParams(ctx context.Context, cmd []string, conn *net.Conn) internal.HandlerFuncParams { + return internal.HandlerFuncParams{ + Context: ctx, + Command: cmd, + Connection: conn, + KeyExists: mockServer.KeyExists, + CreateKeyAndLock: mockServer.CreateKeyAndLock, + KeyLock: mockServer.KeyLock, + KeyRLock: mockServer.KeyRLock, + KeyUnlock: mockServer.KeyUnlock, + KeyRUnlock: mockServer.KeyRUnlock, + GetValue: mockServer.GetValue, + SetValue: mockServer.SetValue, + } +} + func Test_HandleHSET(t *testing.T) { // Tests for both HSET and HSETNX tests := []struct { @@ -144,7 +192,14 @@ func Test_HandleHSET(t *testing.T) { } mockServer.KeyUnlock(ctx, test.key) } - res, err := handleHSET(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) @@ -163,11 +218,11 @@ func Test_HandleHSET(t *testing.T) { if _, err = mockServer.KeyRLock(ctx, test.key); err != nil { t.Error(err) } - hash, ok := mockServer.GetValue(ctx, test.key).(map[string]interface{}) + h, ok := mockServer.GetValue(ctx, test.key).(map[string]interface{}) if !ok { t.Errorf("value at key \"%s\" is not a hash map", test.key) } - for field, value := range hash { + for field, value := range h { if value != test.expectedValue[field] { t.Errorf("expected value \"%+v\" for field \"%+v\", got \"%+v\"", test.expectedValue[field], field, value) } @@ -303,7 +358,14 @@ func Test_HandleHINCRBY(t *testing.T) { } mockServer.KeyUnlock(ctx, test.key) } - res, err := handleHINCRBY(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) @@ -331,11 +393,11 @@ func Test_HandleHINCRBY(t *testing.T) { if _, err = mockServer.KeyRLock(ctx, test.key); err != nil { t.Error(err) } - hash, ok := mockServer.GetValue(ctx, test.key).(map[string]interface{}) + h, ok := mockServer.GetValue(ctx, test.key).(map[string]interface{}) if !ok { t.Errorf("value at key \"%s\" is not a hash map", test.key) } - for field, value := range hash { + for field, value := range h { if value != test.expectedValue[field] { t.Errorf("expected value \"%+v\" for field \"%+v\", got \"%+v\"", test.expectedValue[field], field, value) } @@ -410,7 +472,14 @@ func Test_HandleHGET(t *testing.T) { } mockServer.KeyUnlock(ctx, test.key) } - res, err := handleHGET(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) @@ -519,7 +588,14 @@ func Test_HandleHSTRLEN(t *testing.T) { } mockServer.KeyUnlock(ctx, test.key) } - res, err := handleHSTRLEN(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) @@ -623,7 +699,14 @@ func Test_HandleHVALS(t *testing.T) { } mockServer.KeyUnlock(ctx, test.key) } - res, err := handleHVALS(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) @@ -831,7 +914,14 @@ func Test_HandleHRANDFIELD(t *testing.T) { } mockServer.KeyUnlock(ctx, test.key) } - res, err := handleHRANDFIELD(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) @@ -954,7 +1044,14 @@ func Test_HandleHLEN(t *testing.T) { } mockServer.KeyUnlock(ctx, test.key) } - res, err := handleHLEN(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) @@ -1053,7 +1150,14 @@ func Test_HandleHKeys(t *testing.T) { } mockServer.KeyUnlock(ctx, test.key) } - res, err := handleHKEYS(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) @@ -1159,7 +1263,14 @@ func Test_HandleHGETALL(t *testing.T) { } mockServer.KeyUnlock(ctx, test.key) } - res, err := handleHGETALL(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) @@ -1276,7 +1387,14 @@ func Test_HandleHEXISTS(t *testing.T) { } mockServer.KeyUnlock(ctx, test.key) } - res, err := handleHEXISTS(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) @@ -1375,7 +1493,14 @@ func Test_HandleHDEL(t *testing.T) { } mockServer.KeyUnlock(ctx, test.key) } - res, err := handleHDEL(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) @@ -1396,8 +1521,8 @@ func Test_HandleHDEL(t *testing.T) { if _, err = mockServer.KeyRLock(ctx, test.key); err != nil { t.Error(err) } - if hash, ok := mockServer.GetValue(ctx, test.key).(map[string]interface{}); ok { - for field, value := range hash { + if h, ok := mockServer.GetValue(ctx, test.key).(map[string]interface{}); ok { + for field, value := range h { if value != test.expectedValue[field] { t.Errorf("expected value \"%+v\", got \"%+v\"", test.expectedValue[field], value) } diff --git a/pkg/echovault/api_list_test.go b/test/modules/list/api_test.go similarity index 91% rename from pkg/echovault/api_list_test.go rename to test/modules/list/api_test.go index 22ec6fc0..44e85526 100644 --- a/pkg/echovault/api_list_test.go +++ b/test/modules/list/api_test.go @@ -12,24 +12,38 @@ // See the License for the specific language governing permissions and // limitations under the License. -package echovault +package list import ( + "context" "github.com/echovault/echovault/internal/config" - "github.com/echovault/echovault/pkg/commands" - "github.com/echovault/echovault/pkg/constants" + "github.com/echovault/echovault/pkg/echovault" "reflect" "testing" ) -func TestEchoVault_LLEN(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.NoEviction, +func createEchoVault() *echovault.EchoVault { + ev, _ := echovault.NewEchoVault( + echovault.WithConfig(config.Config{ + DataDir: "", }), ) + return ev +} + +func presetValue(server *echovault.EchoVault, ctx context.Context, key string, value interface{}) error { + if _, err := server.CreateKeyAndLock(ctx, key); err != nil { + return err + } + if err := server.SetValue(ctx, key, value); err != nil { + return err + } + server.KeyUnlock(ctx, key) + return nil +} + +func TestEchoVault_LLEN(t *testing.T) { + server := createEchoVault() tests := []struct { preset bool @@ -67,7 +81,11 @@ func TestEchoVault_LLEN(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.preset { - presetValue(server, tt.key, tt.presetValue) + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } } got, err := server.LLEN(tt.key) if (err != nil) != tt.wantErr { @@ -82,13 +100,7 @@ func TestEchoVault_LLEN(t *testing.T) { } func TestEchoVault_LINDEX(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { preset bool @@ -156,7 +168,11 @@ func TestEchoVault_LINDEX(t *testing.T) { } for _, tt := range tests { if tt.preset { - presetValue(server, tt.key, tt.presetValue) + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } } t.Run(tt.name, func(t *testing.T) { got, err := server.LINDEX(tt.key, tt.index) @@ -172,13 +188,7 @@ func TestEchoVault_LINDEX(t *testing.T) { } func TestEchoVault_LMOVE(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string @@ -328,7 +338,11 @@ func TestEchoVault_LMOVE(t *testing.T) { t.Run(tt.name, func(t *testing.T) { if tt.preset { for k, v := range tt.presetValue { - presetValue(server, k, v) + err := presetValue(server, context.Background(), k, v) + if err != nil { + t.Error(err) + return + } } } got, err := server.LMOVE(tt.source, tt.destination, tt.whereFrom, tt.whereTo) @@ -344,13 +358,7 @@ func TestEchoVault_LMOVE(t *testing.T) { } func TestEchoVault_POP(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string @@ -401,7 +409,11 @@ func TestEchoVault_POP(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.preset { - presetValue(server, tt.key, tt.presetValue) + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } } got, err := tt.popFunc(tt.key) if (err != nil) != tt.wantErr { @@ -416,13 +428,7 @@ func TestEchoVault_POP(t *testing.T) { } func TestEchoVault_LPUSH(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string @@ -478,7 +484,11 @@ func TestEchoVault_LPUSH(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.preset { - presetValue(server, tt.key, tt.presetValue) + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } } got, err := tt.lpushFunc(tt.key, tt.values...) if (err != nil) != tt.wantErr { @@ -493,13 +503,7 @@ func TestEchoVault_LPUSH(t *testing.T) { } func TestEchoVault_RPUSH(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string @@ -535,7 +539,11 @@ func TestEchoVault_RPUSH(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.preset { - presetValue(server, tt.key, tt.presetValue) + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } } got, err := tt.rpushFunc(tt.key, tt.values...) if (err != nil) != tt.wantErr { @@ -550,13 +558,7 @@ func TestEchoVault_RPUSH(t *testing.T) { } func TestEchoVault_LRANGE(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string @@ -656,7 +658,11 @@ func TestEchoVault_LRANGE(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.preset { - presetValue(server, tt.key, tt.presetValue) + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } } got, err := server.LRANGE(tt.key, tt.start, tt.end) if (err != nil) != tt.wantErr { @@ -671,13 +677,7 @@ func TestEchoVault_LRANGE(t *testing.T) { } func TestEchoVault_LREM(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string @@ -722,7 +722,11 @@ func TestEchoVault_LREM(t *testing.T) { } for _, tt := range tests { if tt.preset { - presetValue(server, tt.key, tt.presetValue) + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } } t.Run(tt.name, func(t *testing.T) { got, err := server.LREM(tt.key, tt.count, tt.value) @@ -738,13 +742,7 @@ func TestEchoVault_LREM(t *testing.T) { } func TestEchoVault_LSET(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string @@ -830,7 +828,11 @@ func TestEchoVault_LSET(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.preset { - presetValue(server, tt.key, tt.presetValue) + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } } got, err := server.LSET(tt.key, tt.index, tt.value) if (err != nil) != tt.wantErr { @@ -845,13 +847,7 @@ func TestEchoVault_LSET(t *testing.T) { } func TestEchoVault_LTRIM(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string @@ -940,7 +936,11 @@ func TestEchoVault_LTRIM(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.preset { - presetValue(server, tt.key, tt.presetValue) + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } } got, err := server.LTRIM(tt.key, tt.start, tt.end) if (err != nil) != tt.wantErr { diff --git a/pkg/modules/list/commands_test.go b/test/modules/list/commands_test.go similarity index 90% rename from pkg/modules/list/commands_test.go rename to test/modules/list/commands_test.go index 94ecab10..a2272d28 100644 --- a/pkg/modules/list/commands_test.go +++ b/test/modules/list/commands_test.go @@ -19,11 +19,16 @@ import ( "context" "errors" "fmt" + "github.com/echovault/echovault/internal" "github.com/echovault/echovault/internal/config" "github.com/echovault/echovault/pkg/constants" "github.com/echovault/echovault/pkg/echovault" "github.com/tidwall/resp" + "net" + "reflect" + "strings" "testing" + "unsafe" ) var mockServer *echovault.EchoVault @@ -37,6 +42,49 @@ func init() { ) } +func getUnexportedField(field reflect.Value) interface{} { + return reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())).Elem().Interface() +} + +func getHandler(commands ...string) internal.HandlerFunc { + if len(commands) == 0 { + return nil + } + getCommands := + getUnexportedField(reflect.ValueOf(mockServer).Elem().FieldByName("getCommands")).(func() []internal.Command) + for _, c := range getCommands() { + if strings.EqualFold(commands[0], c.Command) && len(commands) == 1 { + // Get command handler + return c.HandlerFunc + } + if strings.EqualFold(commands[0], c.Command) { + // Get sub-command handler + for _, sc := range c.SubCommands { + if strings.EqualFold(commands[1], sc.Command) { + return sc.HandlerFunc + } + } + } + } + return nil +} + +func getHandlerFuncParams(ctx context.Context, cmd []string, conn *net.Conn) internal.HandlerFuncParams { + return internal.HandlerFuncParams{ + Context: ctx, + Command: cmd, + Connection: conn, + KeyExists: mockServer.KeyExists, + CreateKeyAndLock: mockServer.CreateKeyAndLock, + KeyLock: mockServer.KeyLock, + KeyRLock: mockServer.KeyRLock, + KeyUnlock: mockServer.KeyUnlock, + KeyRUnlock: mockServer.KeyRUnlock, + GetValue: mockServer.GetValue, + SetValue: mockServer.SetValue, + } +} + func Test_HandleLLEN(t *testing.T) { tests := []struct { name string @@ -113,7 +161,14 @@ func Test_HandleLLEN(t *testing.T) { } mockServer.KeyUnlock(ctx, test.key) } - res, err := handleLLen(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) @@ -258,7 +313,14 @@ func Test_HandleLINDEX(t *testing.T) { } mockServer.KeyUnlock(ctx, test.key) } - res, err := handleLIndex(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) @@ -426,7 +488,14 @@ func Test_HandleLRANGE(t *testing.T) { } mockServer.KeyUnlock(ctx, test.key) } - res, err := handleLRange(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) @@ -577,7 +646,14 @@ func Test_HandleLSET(t *testing.T) { } mockServer.KeyUnlock(ctx, test.key) } - res, err := handleLSet(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) @@ -595,16 +671,16 @@ func Test_HandleLSET(t *testing.T) { if _, err = mockServer.KeyRLock(ctx, test.key); err != nil { t.Error(err) } - list, ok := mockServer.GetValue(ctx, test.key).([]interface{}) + l, ok := mockServer.GetValue(ctx, test.key).([]interface{}) if !ok { t.Error("expected value to be list, got another type") } - if len(list) != len(test.expectedValue) { - t.Errorf("expected list length to be %d, got %d", len(test.expectedValue), len(list)) + if len(l) != len(test.expectedValue) { + t.Errorf("expected list length to be %d, got %d", len(test.expectedValue), len(l)) } - for i := 0; i < len(list); i++ { - if list[i] != test.expectedValue[i] { - t.Errorf("expected element at index %d to be %+v, got %+v", i, test.expectedValue[i], list[i]) + for i := 0; i < len(l); i++ { + if l[i] != test.expectedValue[i] { + t.Errorf("expected element at index %d to be %+v, got %+v", i, test.expectedValue[i], l[i]) } } mockServer.KeyRUnlock(ctx, test.key) @@ -751,7 +827,14 @@ func Test_HandleLTRIM(t *testing.T) { } mockServer.KeyUnlock(ctx, test.key) } - res, err := handleLTrim(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) @@ -769,16 +852,16 @@ func Test_HandleLTRIM(t *testing.T) { if _, err = mockServer.KeyRLock(ctx, test.key); err != nil { t.Error(err) } - list, ok := mockServer.GetValue(ctx, test.key).([]interface{}) + l, ok := mockServer.GetValue(ctx, test.key).([]interface{}) if !ok { t.Error("expected value to be list, got another type") } - if len(list) != len(test.expectedValue) { - t.Errorf("expected list length to be %d, got %d", len(test.expectedValue), len(list)) + if len(l) != len(test.expectedValue) { + t.Errorf("expected list length to be %d, got %d", len(test.expectedValue), len(l)) } - for i := 0; i < len(list); i++ { - if list[i] != test.expectedValue[i] { - t.Errorf("expected element at index %d to be %+v, got %+v", i, test.expectedValue[i], list[i]) + for i := 0; i < len(l); i++ { + if l[i] != test.expectedValue[i] { + t.Errorf("expected element at index %d to be %+v, got %+v", i, test.expectedValue[i], l[i]) } } mockServer.KeyRUnlock(ctx, test.key) @@ -882,7 +965,14 @@ func Test_HandleLREM(t *testing.T) { } mockServer.KeyUnlock(ctx, test.key) } - res, err := handleLRem(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) @@ -900,16 +990,16 @@ func Test_HandleLREM(t *testing.T) { if _, err = mockServer.KeyRLock(ctx, test.key); err != nil { t.Error(err) } - list, ok := mockServer.GetValue(ctx, test.key).([]interface{}) + l, ok := mockServer.GetValue(ctx, test.key).([]interface{}) if !ok { t.Error("expected value to be list, got another type") } - if len(list) != len(test.expectedValue) { - t.Errorf("expected list length to be %d, got %d", len(test.expectedValue), len(list)) + if len(l) != len(test.expectedValue) { + t.Errorf("expected list length to be %d, got %d", len(test.expectedValue), len(l)) } - for i := 0; i < len(list); i++ { - if list[i] != test.expectedValue[i] { - t.Errorf("expected element at index %d to be %+v, got %+v", i, test.expectedValue[i], list[i]) + for i := 0; i < len(l); i++ { + if l[i] != test.expectedValue[i] { + t.Errorf("expected element at index %d to be %+v, got %+v", i, test.expectedValue[i], l[i]) } } mockServer.KeyRUnlock(ctx, test.key) @@ -1096,7 +1186,14 @@ func Test_HandleLMOVE(t *testing.T) { mockServer.KeyUnlock(ctx, key) } } - res, err := handleLMove(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) @@ -1115,7 +1212,7 @@ func Test_HandleLMOVE(t *testing.T) { if _, err = mockServer.KeyRLock(ctx, key); err != nil { t.Error(err) } - list, ok := mockServer.GetValue(ctx, key).([]interface{}) + l, ok := mockServer.GetValue(ctx, key).([]interface{}) if !ok { t.Error("expected value to be list, got another type") } @@ -1123,12 +1220,12 @@ func Test_HandleLMOVE(t *testing.T) { if !ok { t.Error("expected test value to be list, got another type") } - if len(list) != len(expectedList) { - t.Errorf("expected list length to be %d, got %d", len(expectedList), len(list)) + if len(l) != len(expectedList) { + t.Errorf("expected list length to be %d, got %d", len(expectedList), len(l)) } - for i := 0; i < len(list); i++ { - if list[i] != expectedList[i] { - t.Errorf("expected element at index %d to be %+v, got %+v", i, expectedList[i], list[i]) + for i := 0; i < len(l); i++ { + if l[i] != expectedList[i] { + t.Errorf("expected element at index %d to be %+v, got %+v", i, expectedList[i], l[i]) } } mockServer.KeyRUnlock(ctx, key) @@ -1213,7 +1310,14 @@ func Test_HandleLPUSH(t *testing.T) { } mockServer.KeyUnlock(ctx, test.key) } - res, err := handleLPush(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) @@ -1231,16 +1335,16 @@ func Test_HandleLPUSH(t *testing.T) { if _, err = mockServer.KeyRLock(ctx, test.key); err != nil { t.Error(err) } - list, ok := mockServer.GetValue(ctx, test.key).([]interface{}) + l, ok := mockServer.GetValue(ctx, test.key).([]interface{}) if !ok { t.Error("expected value to be list, got another type") } - if len(list) != len(test.expectedValue) { - t.Errorf("expected list length to be %d, got %d", len(test.expectedValue), len(list)) + if len(l) != len(test.expectedValue) { + t.Errorf("expected list length to be %d, got %d", len(test.expectedValue), len(l)) } - for i := 0; i < len(list); i++ { - if list[i] != test.expectedValue[i] { - t.Errorf("expected element at index %d to be %+v, got %+v", i, test.expectedValue[i], list[i]) + for i := 0; i < len(l); i++ { + if l[i] != test.expectedValue[i] { + t.Errorf("expected element at index %d to be %+v, got %+v", i, test.expectedValue[i], l[i]) } } mockServer.KeyRUnlock(ctx, test.key) @@ -1324,7 +1428,14 @@ func Test_HandleRPUSH(t *testing.T) { } mockServer.KeyUnlock(ctx, test.key) } - res, err := handleRPush(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) @@ -1342,16 +1453,16 @@ func Test_HandleRPUSH(t *testing.T) { if _, err = mockServer.KeyRLock(ctx, test.key); err != nil { t.Error(err) } - list, ok := mockServer.GetValue(ctx, test.key).([]interface{}) + l, ok := mockServer.GetValue(ctx, test.key).([]interface{}) if !ok { t.Error("expected value to be list, got another type") } - if len(list) != len(test.expectedValue) { - t.Errorf("expected list length to be %d, got %d", len(test.expectedValue), len(list)) + if len(l) != len(test.expectedValue) { + t.Errorf("expected list length to be %d, got %d", len(test.expectedValue), len(l)) } - for i := 0; i < len(list); i++ { - if list[i] != test.expectedValue[i] { - t.Errorf("expected element at index %d to be %+v, got %+v", i, test.expectedValue[i], list[i]) + for i := 0; i < len(l); i++ { + if l[i] != test.expectedValue[i] { + t.Errorf("expected element at index %d to be %+v, got %+v", i, test.expectedValue[i], l[i]) } } mockServer.KeyRUnlock(ctx, test.key) @@ -1445,7 +1556,14 @@ func Test_HandlePOP(t *testing.T) { } mockServer.KeyUnlock(ctx, test.key) } - res, err := handlePop(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) @@ -1463,16 +1581,16 @@ func Test_HandlePOP(t *testing.T) { if _, err = mockServer.KeyRLock(ctx, test.key); err != nil { t.Error(err) } - list, ok := mockServer.GetValue(ctx, test.key).([]interface{}) + l, ok := mockServer.GetValue(ctx, test.key).([]interface{}) if !ok { t.Error("expected value to be list, got another type") } - if len(list) != len(test.expectedValue) { - t.Errorf("expected list length to be %d, got %d", len(test.expectedValue), len(list)) + if len(l) != len(test.expectedValue) { + t.Errorf("expected list length to be %d, got %d", len(test.expectedValue), len(l)) } - for i := 0; i < len(list); i++ { - if list[i] != test.expectedValue[i] { - t.Errorf("expected element at index %d to be %+v, got %+v", i, test.expectedValue[i], list[i]) + for i := 0; i < len(l); i++ { + if l[i] != test.expectedValue[i] { + t.Errorf("expected element at index %d to be %+v, got %+v", i, test.expectedValue[i], l[i]) } } mockServer.KeyRUnlock(ctx, test.key) diff --git a/pkg/echovault/api_pubsub_test.go b/test/modules/pubsub/api_test.go similarity index 97% rename from pkg/echovault/api_pubsub_test.go rename to test/modules/pubsub/api_test.go index afd234ac..5a6d9c1c 100644 --- a/pkg/echovault/api_pubsub_test.go +++ b/test/modules/pubsub/api_test.go @@ -12,4 +12,4 @@ // See the License for the specific language governing permissions and // limitations under the License. -package echovault +package pubsub diff --git a/pkg/modules/pubsub/commands_test.go b/test/modules/pubsub/commands_test.go similarity index 82% rename from pkg/modules/pubsub/commands_test.go rename to test/modules/pubsub/commands_test.go index 03ca8a73..4774c8c0 100644 --- a/pkg/modules/pubsub/commands_test.go +++ b/test/modules/pubsub/commands_test.go @@ -18,19 +18,23 @@ import ( "bytes" "context" "fmt" + "github.com/echovault/echovault/internal" "github.com/echovault/echovault/internal/config" - internal_pubsub "github.com/echovault/echovault/internal/pubsub" + "github.com/echovault/echovault/internal/modules/pubsub" "github.com/echovault/echovault/pkg/constants" "github.com/echovault/echovault/pkg/echovault" "github.com/tidwall/resp" "net" + "reflect" "slices" + "strings" "sync" "testing" "time" + "unsafe" ) -var pubsub *internal_pubsub.PubSub +var ps *pubsub.PubSub var mockServer *echovault.EchoVault var bindAddr = "localhost" @@ -38,7 +42,9 @@ var port uint16 = 7490 func init() { mockServer = setUpServer(bindAddr, port) - pubsub = mockServer.GetPubSub().(*internal_pubsub.PubSub) + + getPubSub := getUnexportedField(reflect.ValueOf(mockServer).Elem().FieldByName("getPubSub")).(func() interface{}) + ps = getPubSub().(*pubsub.PubSub) wg := sync.WaitGroup{} wg.Add(1) @@ -51,7 +57,6 @@ func init() { func setUpServer(bindAddr string, port uint16) *echovault.EchoVault { server, _ := echovault.NewEchoVault( - echovault.WithCommands(Commands()), echovault.WithConfig(config.Config{ BindAddr: bindAddr, Port: port, @@ -62,6 +67,44 @@ func setUpServer(bindAddr string, port uint16) *echovault.EchoVault { return server } +func getUnexportedField(field reflect.Value) interface{} { + return reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())).Elem().Interface() +} + +func getHandler(commands ...string) internal.HandlerFunc { + if len(commands) == 0 { + return nil + } + getCommands := + getUnexportedField(reflect.ValueOf(mockServer).Elem().FieldByName("getCommands")).(func() []internal.Command) + for _, c := range getCommands() { + if strings.EqualFold(commands[0], c.Command) && len(commands) == 1 { + // Get command handler + return c.HandlerFunc + } + if strings.EqualFold(commands[0], c.Command) { + // Get sub-command handler + for _, sc := range c.SubCommands { + if strings.EqualFold(commands[1], sc.Command) { + return sc.HandlerFunc + } + } + } + } + return nil +} + +func getHandlerFuncParams(ctx context.Context, cmd []string, conn *net.Conn, mockServer *echovault.EchoVault) internal.HandlerFuncParams { + getPubSub := + getUnexportedField(reflect.ValueOf(mockServer).Elem().FieldByName("getPubSub")).(func() interface{}) + return internal.HandlerFuncParams{ + Context: ctx, + Command: cmd, + Connection: conn, + GetPubSub: getPubSub, + } +} + func Test_HandleSubscribe(t *testing.T) { ctx := context.WithValue(context.Background(), "test_name", "SUBSCRIBE/PSUBSCRIBE") @@ -86,18 +129,19 @@ func Test_HandleSubscribe(t *testing.T) { // Test subscribe to channels channels := []string{"sub_channel1", "sub_channel2", "sub_channel3"} for _, conn := range connections { - if _, err := handleSubscribe(ctx, append([]string{"SUBSCRIBE"}, channels...), mockServer, conn); err != nil { + _, err := getHandler("SUBSCRIBE")(getHandlerFuncParams(ctx, append([]string{"SUBSCRIBE"}, channels...), conn, mockServer)) + if err != nil { t.Error(err) } } for _, channel := range channels { // Check if the channel exists in the pubsub module - if !slices.ContainsFunc(pubsub.GetAllChannels(), func(c *internal_pubsub.Channel) bool { + if !slices.ContainsFunc(ps.GetAllChannels(), func(c *pubsub.Channel) bool { return c.Name() == channel }) { t.Errorf("expected pubsub to contain channel \"%s\" but it was not found", channel) } - for _, c := range pubsub.GetAllChannels() { + for _, c := range ps.GetAllChannels() { if c.Name() == channel { // Check if channel has nil pattern if c.Pattern() != nil { @@ -116,18 +160,19 @@ func Test_HandleSubscribe(t *testing.T) { // Test subscribe to patterns patterns := []string{"psub_channel*"} for _, conn := range connections { - if _, err := handleSubscribe(ctx, append([]string{"PSUBSCRIBE"}, patterns...), mockServer, conn); err != nil { + _, err := getHandler("PSUBSCRIBE")(getHandlerFuncParams(ctx, append([]string{"PSUBSCRIBE"}, patterns...), conn, mockServer)) + if err != nil { t.Error(err) } } for _, pattern := range patterns { // Check if pattern channel exists in pubsub module - if !slices.ContainsFunc(pubsub.GetAllChannels(), func(c *internal_pubsub.Channel) bool { + if !slices.ContainsFunc(ps.GetAllChannels(), func(c *pubsub.Channel) bool { return c.Name() == pattern }) { t.Errorf("expected pubsub to contain pattern channel \"%s\" but it was not found", pattern) } - for _, c := range pubsub.GetAllChannels() { + for _, c := range ps.GetAllChannels() { if c.Name() == pattern { // Check if channel has non-nil pattern if c.Pattern() == nil { @@ -263,31 +308,31 @@ func Test_HandleUnsubscribe(t *testing.T) { // Subscribe all the connections to the channels and patterns for _, conn := range append(test.otherConnections, test.targetConn) { - _, err := handleSubscribe(ctx, append([]string{"SUBSCRIBE"}, test.subChannels...), mockServer, conn) + _, err := getHandler("SUBSCRIBE")(getHandlerFuncParams(ctx, append([]string{"SUBSCRIBE"}, test.subChannels...), conn, mockServer)) if err != nil { t.Error(err) } - _, err = handleSubscribe(ctx, append([]string{"PSUBSCRIBE"}, test.subPatterns...), mockServer, conn) + _, err = getHandler("PSUBSCRIBE")(getHandlerFuncParams(ctx, append([]string{"PSUBSCRIBE"}, test.subPatterns...), conn, mockServer)) if err != nil { t.Error(err) } } // Unsubscribe the target connection from the unsub channels and patterns - res, err := handleUnsubscribe(ctx, append([]string{"UNSUBSCRIBE"}, test.unSubChannels...), mockServer, test.targetConn) + res, err := getHandler("UNSUBSCRIBE")(getHandlerFuncParams(ctx, append([]string{"UNSUBSCRIBE"}, test.unSubChannels...), test.targetConn, mockServer)) if err != nil { t.Error(err) } verifyResponse(res, test.expectedResponses["channel"]) - res, err = handleUnsubscribe(ctx, append([]string{"PUNSUBSCRIBE"}, test.unSubPatterns...), mockServer, test.targetConn) + res, err = getHandler("PUNSUBSCRIBE")(getHandlerFuncParams(ctx, append([]string{"PUNSUBSCRIBE"}, test.unSubPatterns...), test.targetConn, mockServer)) if err != nil { t.Error(err) } verifyResponse(res, test.expectedResponses["pattern"]) for _, channel := range append(test.unSubChannels, test.unSubPatterns...) { - for _, pubsubChannel := range pubsub.GetAllChannels() { + for _, pubsubChannel := range ps.GetAllChannels() { if pubsubChannel.Name() == channel { // Assert that target connection is no longer in the unsub channels and patterns if _, ok := pubsubChannel.Subscribers()[test.targetConn]; ok { @@ -304,7 +349,7 @@ func Test_HandleUnsubscribe(t *testing.T) { // Assert that the target connection is still in the remain channels and patterns for _, channel := range append(test.remainChannels, test.remainPatterns...) { - for _, pubsubChannel := range pubsub.GetAllChannels() { + for _, pubsubChannel := range ps.GetAllChannels() { if pubsubChannel.Name() == channel { if _, ok := pubsubChannel.Subscribers()[test.targetConn]; !ok { t.Errorf("could not find expected target connection in channel \"%s\"", channel) @@ -347,7 +392,7 @@ func Test_HandlePublish(t *testing.T) { subscribe := func(ctx context.Context, channels []string, patterns []string, c *net.Conn, r *resp.Conn) { // Subscribe to channels go func() { - _, _ = handleSubscribe(ctx, append([]string{"SUBSCRIBE"}, channels...), mockServer, c) + _, _ = getHandler("SUBSCRIBE")(getHandlerFuncParams(ctx, append([]string{"SUBSCRIBE"}, channels...), c, mockServer)) }() // Verify all the responses for each channel subscription for i := 0; i < len(channels); i++ { @@ -355,7 +400,7 @@ func Test_HandlePublish(t *testing.T) { } // Subscribe to all the patterns go func() { - _, _ = handleSubscribe(ctx, append([]string{"PSUBSCRIBE"}, patterns...), mockServer, c) + _, _ = getHandler("PSUBSCRIBE")(getHandlerFuncParams(ctx, append([]string{"PSUBSCRIBE"}, patterns...), c, mockServer)) }() // Verify all the responses for each pattern subscription for i := 0; i < len(patterns); i++ { @@ -518,7 +563,7 @@ func Test_HandlePubSubChannels(t *testing.T) { // Subscribe connections to channels go func() { - _, err := handleSubscribe(ctx, append([]string{"SUBSCRIBE"}, channels...), mockServer, &wConn1) + _, err := getHandler("SUBSCRIBE")(getHandlerFuncParams(ctx, append([]string{"SUBSCRIBE"}, channels...), &wConn1, mockServer)) if err != nil { t.Error(err) } @@ -535,7 +580,7 @@ func Test_HandlePubSubChannels(t *testing.T) { } } go func() { - _, err := handleSubscribe(ctx, append([]string{"PSUBSCRIBE"}, patterns...), mockServer, &wConn2) + _, err := getHandler("PSUBSCRIBE")(getHandlerFuncParams(ctx, append([]string{"PSUBSCRIBE"}, patterns...), &wConn2, mockServer)) if err != nil { t.Error(err) } @@ -571,7 +616,7 @@ func Test_HandlePubSubChannels(t *testing.T) { } // Check if all subscriptions are returned - res, err := handlePubSubChannels(ctx, []string{"PUBSUB", "CHANNELS"}, mockServer, nil) + res, err := getHandler("PUBSUB", "CHANNELS")(getHandlerFuncParams(ctx, []string{"PUBSUB", "CHANNELS"}, nil, mockServer)) if err != nil { t.Error(err) } @@ -579,45 +624,45 @@ func Test_HandlePubSubChannels(t *testing.T) { // Unsubscribe from one pattern and one channel before checking against a new slice of // expected channels/patterns in the response of the "PUBSUB CHANNELS" command - _, err = handleUnsubscribe( + _, err = getHandler("UNSUBSCRIBE")(getHandlerFuncParams( ctx, append([]string{"UNSUBSCRIBE"}, []string{"channel_2", "channel_3"}...), - mockServer, &wConn1, - ) + mockServer, + )) if err != nil { t.Error(err) } - _, err = handleUnsubscribe( + _, err = getHandler("UNSUBSCRIBE")(getHandlerFuncParams( ctx, append([]string{"UNSUBSCRIBE"}, "channel_[456]"), - mockServer, &wConn2, - ) + mockServer, + )) if err != nil { t.Error(err) } // Return all the remaining channels - res, err = handlePubSubChannels(ctx, []string{"PUBSUB", "CHANNELS"}, mockServer, nil) + res, err = getHandler("PUBSUB", "CHANNELS")(getHandlerFuncParams(ctx, []string{"PUBSUB", "CHANNELS"}, nil, mockServer)) if err != nil { t.Error(err) } verifyExpectedResponse(res, []string{"channel_1", "channel_[123]"}) // Return only one of the remaining channels when passed a pattern that matches it - res, err = handlePubSubChannels(ctx, []string{"PUBSUB", "CHANNELS", "channel_[189]"}, mockServer, nil) + res, err = getHandler("PUBSUB", "CHANNELS")(getHandlerFuncParams(ctx, []string{"PUBSUB", "CHANNELS", "channel_[189]"}, nil, mockServer)) if err != nil { t.Error(err) } verifyExpectedResponse(res, []string{"channel_1"}) // Return both remaining channels when passed a pattern that matches them - res, err = handlePubSubChannels(ctx, []string{"PUBSUB", "CHANNELS", "channel_[123]"}, mockServer, nil) + res, err = getHandler("PUBSUB", "CHANNELS")(getHandlerFuncParams(ctx, []string{"PUBSUB", "CHANNELS", "channel_[123]"}, nil, mockServer)) if err != nil { t.Error(err) } verifyExpectedResponse(res, []string{"channel_1", "channel_[123]"}) // Return none channels when passed a pattern that does not match either channel - res, err = handlePubSubChannels(ctx, []string{"PUBSUB", "CHANNELS", "channel_[456]"}, mockServer, nil) + res, err = getHandler("PUBSUB", "CHANNELS")(getHandlerFuncParams(ctx, []string{"PUBSUB", "CHANNELS", "channel_[456]"}, nil, mockServer)) if err != nil { t.Error(err) } @@ -655,7 +700,8 @@ func Test_HandleNumPat(t *testing.T) { r *resp.Conn }{w: &w, r: resp.NewConn(r)} go func() { - if _, err := handleSubscribe(ctx, append([]string{"PSUBSCRIBE"}, patterns...), mockServer, &w); err != nil { + _, err := getHandler("PSUBSCRIBE")(getHandlerFuncParams(ctx, append([]string{"PSUBSCRIBE"}, patterns...), &w, mockServer)) + if err != nil { t.Error(err) } }() @@ -685,7 +731,7 @@ func Test_HandleNumPat(t *testing.T) { } // Check that we receive all the patterns with NUMPAT commands - res, err := handlePubSubNumPat(ctx, []string{"PUBSUB", "NUMPAT"}, mockServer, nil) + res, err := getHandler("PUBSUB", "NUMPAT")(getHandlerFuncParams(ctx, []string{"PUBSUB", "NUMPAT"}, nil, mockServer)) if err != nil { t.Error(err) } @@ -693,12 +739,12 @@ func Test_HandleNumPat(t *testing.T) { // Unsubscribe from a channel and check if the number of active channels is updated for _, conn := range connections { - _, err = handleUnsubscribe(ctx, []string{"PUNSUBSCRIBE", patterns[0]}, mockServer, conn.w) + _, err = getHandler("PUNSUBSCRIBE")(getHandlerFuncParams(ctx, []string{"PUNSUBSCRIBE", patterns[0]}, conn.w, mockServer)) if err != nil { t.Error(err) } } - res, err = handlePubSubNumPat(ctx, []string{"PUBSUB", "NUMPAT"}, mockServer, nil) + res, err = getHandler("PUBSUB", "NUMPAT")(getHandlerFuncParams(ctx, []string{"PUBSUB", "NUMPAT"}, nil, mockServer)) if err != nil { t.Error(err) } @@ -706,12 +752,12 @@ func Test_HandleNumPat(t *testing.T) { // Unsubscribe from all the channels and check if we get a 0 response for _, conn := range connections { - _, err = handleUnsubscribe(ctx, []string{"PUNSUBSCRIBE"}, mockServer, conn.w) + _, err = getHandler("PUNSUBSCRIBE")(getHandlerFuncParams(ctx, []string{"PUNSUBSCRIBE"}, conn.w, mockServer)) if err != nil { t.Error(err) } } - res, err = handlePubSubNumPat(ctx, []string{"PUBSUB", "NUMPAT"}, mockServer, nil) + res, err = getHandler("PUBSUB", "NUMPAT")(getHandlerFuncParams(ctx, []string{"PUBSUB", "NUMPAT"}, nil, mockServer)) if err != nil { t.Error(err) } @@ -748,7 +794,8 @@ func Test_HandleNumSub(t *testing.T) { r *resp.Conn }{w: &w, r: resp.NewConn(r)} go func() { - if _, err := handleSubscribe(ctx, append([]string{"SUBSCRIBE"}, channels...), mockServer, &w); err != nil { + _, err := getHandler("SUBSCRIBE")(getHandlerFuncParams(ctx, append([]string{"SUBSCRIBE"}, channels...), &w, mockServer)) + if err != nil { t.Error(err) } }() @@ -793,7 +840,7 @@ func Test_HandleNumSub(t *testing.T) { for i, test := range tests { ctx = context.WithValue(ctx, "test_index", i) - res, err := handlePubSubNumSubs(ctx, test.cmd, mockServer, nil) + res, err := getHandler("PUBSUB", "NUMSUB")(getHandlerFuncParams(ctx, test.cmd, nil, mockServer)) if err != nil { t.Error(err) } diff --git a/pkg/echovault/api_set_test.go b/test/modules/set/api_test.go similarity index 90% rename from pkg/echovault/api_set_test.go rename to test/modules/set/api_test.go index 36f25a18..6c085b8a 100644 --- a/pkg/echovault/api_set_test.go +++ b/test/modules/set/api_test.go @@ -12,26 +12,40 @@ // See the License for the specific language governing permissions and // limitations under the License. -package echovault +package set import ( + "context" "github.com/echovault/echovault/internal/config" - "github.com/echovault/echovault/internal/set" - "github.com/echovault/echovault/pkg/commands" - "github.com/echovault/echovault/pkg/constants" + "github.com/echovault/echovault/internal/modules/set" + "github.com/echovault/echovault/pkg/echovault" "reflect" "slices" "testing" ) -func TestEchoVault_SADD(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.NoEviction, +func createEchoVault() *echovault.EchoVault { + ev, _ := echovault.NewEchoVault( + echovault.WithConfig(config.Config{ + DataDir: "", }), ) + return ev +} + +func presetValue(server *echovault.EchoVault, ctx context.Context, key string, value interface{}) error { + if _, err := server.CreateKeyAndLock(ctx, key); err != nil { + return err + } + if err := server.SetValue(ctx, key, value); err != nil { + return err + } + server.KeyUnlock(ctx, key) + return nil +} + +func TestEchoVault_SADD(t *testing.T) { + server := createEchoVault() tests := []struct { name string @@ -69,7 +83,11 @@ func TestEchoVault_SADD(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.presetValue != nil { - presetValue(server, tt.key, tt.presetValue) + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } } got, err := server.SADD(tt.key, tt.members...) if (err != nil) != tt.wantErr { @@ -84,13 +102,7 @@ func TestEchoVault_SADD(t *testing.T) { } func TestEchoVault_SCARD(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string @@ -124,7 +136,11 @@ func TestEchoVault_SCARD(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.presetValue != nil { - presetValue(server, tt.key, tt.presetValue) + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } } got, err := server.SCARD(tt.key) if (err != nil) != tt.wantErr { @@ -139,13 +155,7 @@ func TestEchoVault_SCARD(t *testing.T) { } func TestEchoVault_SDIFF(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string @@ -212,7 +222,11 @@ func TestEchoVault_SDIFF(t *testing.T) { t.Run(tt.name, func(t *testing.T) { if tt.presetValues != nil { for k, v := range tt.presetValues { - presetValue(server, k, v) + err := presetValue(server, context.Background(), k, v) + if err != nil { + t.Error(err) + return + } } } got, err := server.SDIFF(tt.keys...) @@ -233,13 +247,7 @@ func TestEchoVault_SDIFF(t *testing.T) { } func TestEchoVault_SDIFFSTORE(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string @@ -312,7 +320,11 @@ func TestEchoVault_SDIFFSTORE(t *testing.T) { t.Run(tt.name, func(t *testing.T) { if tt.presetValues != nil { for k, v := range tt.presetValues { - presetValue(server, k, v) + err := presetValue(server, context.Background(), k, v) + if err != nil { + t.Error(err) + return + } } } got, err := server.SDIFFSTORE(tt.destination, tt.keys...) @@ -328,13 +340,7 @@ func TestEchoVault_SDIFFSTORE(t *testing.T) { } func TestEchoVault_SINTER(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string @@ -401,7 +407,11 @@ func TestEchoVault_SINTER(t *testing.T) { t.Run(tt.name, func(t *testing.T) { if tt.presetValues != nil { for k, v := range tt.presetValues { - presetValue(server, k, v) + err := presetValue(server, context.Background(), k, v) + if err != nil { + t.Error(err) + return + } } } got, err := server.SINTER(tt.keys...) @@ -422,13 +432,7 @@ func TestEchoVault_SINTER(t *testing.T) { } func TestEchoVault_SINTERCARD(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string @@ -512,7 +516,11 @@ func TestEchoVault_SINTERCARD(t *testing.T) { t.Run(tt.name, func(t *testing.T) { if tt.presetValues != nil { for k, v := range tt.presetValues { - presetValue(server, k, v) + err := presetValue(server, context.Background(), k, v) + if err != nil { + t.Error(err) + return + } } } got, err := server.SINTERCARD(tt.keys, tt.limit) @@ -528,13 +536,7 @@ func TestEchoVault_SINTERCARD(t *testing.T) { } func TestEchoVault_SINTERSTORE(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string @@ -607,7 +609,11 @@ func TestEchoVault_SINTERSTORE(t *testing.T) { t.Run(tt.name, func(t *testing.T) { if tt.presetValues != nil { for k, v := range tt.presetValues { - presetValue(server, k, v) + err := presetValue(server, context.Background(), k, v) + if err != nil { + t.Error(err) + return + } } } got, err := server.SINTERSTORE(tt.destination, tt.keys...) @@ -623,13 +629,7 @@ func TestEchoVault_SINTERSTORE(t *testing.T) { } func TestEchoVault_SISMEMBER(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string @@ -667,7 +667,11 @@ func TestEchoVault_SISMEMBER(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.presetValue != nil { - presetValue(server, tt.key, tt.presetValue) + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } } got, err := server.SISMEMBER(tt.key, tt.member) if (err != nil) != tt.wantErr { @@ -682,13 +686,7 @@ func TestEchoVault_SISMEMBER(t *testing.T) { } func TestEchoVault_SMEMBERS(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string @@ -722,7 +720,11 @@ func TestEchoVault_SMEMBERS(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.presetValue != nil { - presetValue(server, tt.key, tt.presetValue) + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } } got, err := server.SMEMBERS(tt.key) if (err != nil) != tt.wantErr { @@ -742,13 +744,7 @@ func TestEchoVault_SMEMBERS(t *testing.T) { } func TestEchoVault_SMISMEMBER(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string @@ -805,7 +801,11 @@ func TestEchoVault_SMISMEMBER(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.presetValue != nil { - presetValue(server, tt.key, tt.presetValue) + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } } got, err := server.SMISMEMBER(tt.key, tt.members...) if (err != nil) != tt.wantErr { @@ -820,13 +820,7 @@ func TestEchoVault_SMISMEMBER(t *testing.T) { } func TestEchoVault_SMOVE(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string @@ -890,7 +884,11 @@ func TestEchoVault_SMOVE(t *testing.T) { t.Run(tt.name, func(t *testing.T) { if tt.presetValues != nil { for k, v := range tt.presetValues { - presetValue(server, k, v) + err := presetValue(server, context.Background(), k, v) + if err != nil { + t.Error(err) + return + } } } got, err := server.SMOVE(tt.source, tt.destination, tt.member) @@ -906,13 +904,7 @@ func TestEchoVault_SMOVE(t *testing.T) { } func TestEchoVault_SPOP(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string @@ -942,7 +934,11 @@ func TestEchoVault_SPOP(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.presetValue != nil { - presetValue(server, tt.key, tt.presetValue) + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } } got, err := server.SPOP(tt.key, tt.count) if (err != nil) != tt.wantErr { @@ -959,13 +955,7 @@ func TestEchoVault_SPOP(t *testing.T) { } func TestEchoVault_SRANDMEMBER(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string @@ -1007,7 +997,11 @@ func TestEchoVault_SRANDMEMBER(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.presetValue != nil { - presetValue(server, tt.key, tt.presetValue) + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } } got, err := server.SRANDMEMBER(tt.key, tt.count) if (err != nil) != tt.wantErr { @@ -1028,13 +1022,7 @@ func TestEchoVault_SRANDMEMBER(t *testing.T) { } func TestEchoVault_SREM(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string @@ -1072,7 +1060,11 @@ func TestEchoVault_SREM(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.presetValue != nil { - presetValue(server, tt.key, tt.presetValue) + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } } got, err := server.SREM(tt.key, tt.members...) if (err != nil) != tt.wantErr { @@ -1087,13 +1079,7 @@ func TestEchoVault_SREM(t *testing.T) { } func TestEchoVault_SUNION(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string @@ -1153,7 +1139,11 @@ func TestEchoVault_SUNION(t *testing.T) { t.Run(tt.name, func(t *testing.T) { if tt.presetValues != nil { for k, v := range tt.presetValues { - presetValue(server, k, v) + err := presetValue(server, context.Background(), k, v) + if err != nil { + t.Error(err) + return + } } } got, err := server.SUNION(tt.keys...) @@ -1174,13 +1164,7 @@ func TestEchoVault_SUNION(t *testing.T) { } func TestEchoVault_SUNIONSTORE(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string @@ -1230,7 +1214,11 @@ func TestEchoVault_SUNIONSTORE(t *testing.T) { t.Run(tt.name, func(t *testing.T) { if tt.presetValues != nil { for k, v := range tt.presetValues { - presetValue(server, k, v) + err := presetValue(server, context.Background(), k, v) + if err != nil { + t.Error(err) + return + } } } got, err := server.SUNIONSTORE(tt.destination, tt.keys...) diff --git a/pkg/modules/set/commant_test.go b/test/modules/set/commands_test.go similarity index 92% rename from pkg/modules/set/commant_test.go rename to test/modules/set/commands_test.go index fdd35d54..5a107ab5 100644 --- a/pkg/modules/set/commant_test.go +++ b/test/modules/set/commands_test.go @@ -19,13 +19,18 @@ import ( "context" "errors" "fmt" + "github.com/echovault/echovault/internal" "github.com/echovault/echovault/internal/config" - "github.com/echovault/echovault/internal/set" + "github.com/echovault/echovault/internal/modules/set" "github.com/echovault/echovault/pkg/constants" "github.com/echovault/echovault/pkg/echovault" "github.com/tidwall/resp" + "net" + "reflect" "slices" + "strings" "testing" + "unsafe" ) var mockServer *echovault.EchoVault @@ -39,6 +44,49 @@ func init() { ) } +func getUnexportedField(field reflect.Value) interface{} { + return reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())).Elem().Interface() +} + +func getHandler(commands ...string) internal.HandlerFunc { + if len(commands) == 0 { + return nil + } + getCommands := + getUnexportedField(reflect.ValueOf(mockServer).Elem().FieldByName("getCommands")).(func() []internal.Command) + for _, c := range getCommands() { + if strings.EqualFold(commands[0], c.Command) && len(commands) == 1 { + // Get command handler + return c.HandlerFunc + } + if strings.EqualFold(commands[0], c.Command) { + // Get sub-command handler + for _, sc := range c.SubCommands { + if strings.EqualFold(commands[1], sc.Command) { + return sc.HandlerFunc + } + } + } + } + return nil +} + +func getHandlerFuncParams(ctx context.Context, cmd []string, conn *net.Conn) internal.HandlerFuncParams { + return internal.HandlerFuncParams{ + Context: ctx, + Command: cmd, + Connection: conn, + KeyExists: mockServer.KeyExists, + CreateKeyAndLock: mockServer.CreateKeyAndLock, + KeyLock: mockServer.KeyLock, + KeyRLock: mockServer.KeyRLock, + KeyUnlock: mockServer.KeyUnlock, + KeyRUnlock: mockServer.KeyRUnlock, + GetValue: mockServer.GetValue, + SetValue: mockServer.SetValue, + } +} + func Test_HandleSADD(t *testing.T) { tests := []struct { name string @@ -103,7 +151,15 @@ func Test_HandleSADD(t *testing.T) { } mockServer.KeyUnlock(ctx, test.key) } - res, err := handleSADD(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) + if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) @@ -214,7 +270,15 @@ func Test_HandleSCARD(t *testing.T) { } mockServer.KeyUnlock(ctx, test.key) } - res, err := handleSCARD(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) + if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) @@ -327,7 +391,15 @@ func Test_HandleSDIFF(t *testing.T) { mockServer.KeyUnlock(ctx, key) } } - res, err := handleSDIFF(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) + if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) @@ -454,7 +526,15 @@ func Test_HandleSDIFFSTORE(t *testing.T) { mockServer.KeyUnlock(ctx, key) } } - res, err := handleSDIFFSTORE(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) + if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) @@ -582,7 +662,15 @@ func Test_HandleSINTER(t *testing.T) { mockServer.KeyUnlock(ctx, key) } } - res, err := handleSINTER(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) + if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) @@ -709,7 +797,15 @@ func Test_HandleSINTERCARD(t *testing.T) { mockServer.KeyUnlock(ctx, key) } } - res, err := handleSINTERCARD(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) + if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) @@ -834,7 +930,13 @@ func Test_HandleSINTERSTORE(t *testing.T) { mockServer.KeyUnlock(ctx, key) } } - res, err := handleSINTERSTORE(ctx, test.command, mockServer, nil) + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) @@ -939,7 +1041,14 @@ func Test_HandleSISMEMBER(t *testing.T) { } mockServer.KeyUnlock(ctx, test.key) } - res, err := handleSISMEMBER(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) @@ -1027,7 +1136,14 @@ func Test_HandleSMEMBERS(t *testing.T) { } mockServer.KeyUnlock(ctx, test.key) } - res, err := handleSMEMBERS(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) @@ -1118,7 +1234,14 @@ func Test_HandleSMISMEMBER(t *testing.T) { } mockServer.KeyUnlock(ctx, test.key) } - res, err := handleSMISMEMBER(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) @@ -1160,7 +1283,7 @@ func Test_HandleSMOVE(t *testing.T) { "SmoveSource1": set.NewSet([]string{"one", "two", "three", "four"}), "SmoveDestination1": set.NewSet([]string{"five", "six", "seven", "eight"}), }, - command: []string{"MOVE", "SmoveSource1", "SmoveDestination1", "four"}, + command: []string{"SMOVE", "SmoveSource1", "SmoveDestination1", "four"}, expectedValues: map[string]interface{}{ "SmoveSource1": set.NewSet([]string{"one", "two", "three"}), "SmoveDestination1": set.NewSet([]string{"four", "five", "six", "seven", "eight"}), @@ -1242,7 +1365,14 @@ func Test_HandleSMOVE(t *testing.T) { mockServer.KeyUnlock(ctx, key) } } - res, err := handleSMOVE(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) @@ -1350,7 +1480,14 @@ func Test_HandleSPOP(t *testing.T) { } mockServer.KeyUnlock(ctx, test.key) } - res, err := handleSPOP(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) @@ -1473,7 +1610,14 @@ func Test_HandleSRANDMEMBER(t *testing.T) { } mockServer.KeyUnlock(ctx, test.key) } - res, err := handleSRANDMEMBER(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) @@ -1588,7 +1732,14 @@ func Test_HandleSREM(t *testing.T) { } mockServer.KeyUnlock(ctx, test.key) } - res, err := handleSREM(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) @@ -1708,7 +1859,14 @@ func Test_HandleSUNION(t *testing.T) { mockServer.KeyUnlock(ctx, key) } } - res, err := handleSUNION(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) @@ -1811,7 +1969,14 @@ func Test_HandleSUNIONSTORE(t *testing.T) { mockServer.KeyUnlock(ctx, key) } } - res, err := handleSUNIONSTORE(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) diff --git a/pkg/echovault/api_sorted_set_test.go b/test/modules/sorted_set/api_test.go similarity index 76% rename from pkg/echovault/api_sorted_set_test.go rename to test/modules/sorted_set/api_test.go index 69598773..8dcd57f5 100644 --- a/pkg/echovault/api_sorted_set_test.go +++ b/test/modules/sorted_set/api_test.go @@ -12,36 +12,50 @@ // See the License for the specific language governing permissions and // limitations under the License. -package echovault +package sorted_set import ( + "context" "github.com/echovault/echovault/internal" "github.com/echovault/echovault/internal/config" - "github.com/echovault/echovault/internal/sorted_set" - "github.com/echovault/echovault/pkg/commands" - "github.com/echovault/echovault/pkg/constants" + ss "github.com/echovault/echovault/internal/modules/sorted_set" + "github.com/echovault/echovault/pkg/echovault" "math" "reflect" "strconv" "testing" ) -func TestEchoVault_ZADD(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.NoEviction, +func createEchoVault() *echovault.EchoVault { + ev, _ := echovault.NewEchoVault( + echovault.WithConfig(config.Config{ + DataDir: "", }), ) + return ev +} + +func presetValue(server *echovault.EchoVault, ctx context.Context, key string, value interface{}) error { + if _, err := server.CreateKeyAndLock(ctx, key); err != nil { + return err + } + if err := server.SetValue(ctx, key, value); err != nil { + return err + } + server.KeyUnlock(ctx, key) + return nil +} + +func TestEchoVault_ZADD(t *testing.T) { + server := createEchoVault() tests := []struct { name string preset bool - presetValue *sorted_set.SortedSet + presetValue *ss.SortedSet key string entries map[string]float64 - options ZADDOptions + options echovault.ZADDOptions want int wantErr bool }{ @@ -57,17 +71,17 @@ func TestEchoVault_ZADD(t *testing.T) { "member4": math.Inf(-1), "member5": math.Inf(1), }, - options: ZADDOptions{}, + options: echovault.ZADDOptions{}, want: 5, wantErr: false, }, { name: "Only add the elements that do not currently exist in the sorted set when NX flag is provided", preset: true, - presetValue: sorted_set.NewSortedSet([]sorted_set.MemberParam{ - {Value: "member1", Score: sorted_set.Score(5.5)}, - {Value: "member2", Score: sorted_set.Score(67.77)}, - {Value: "member3", Score: sorted_set.Score(10)}, + presetValue: ss.NewSortedSet([]ss.MemberParam{ + {Value: "member1", Score: ss.Score(5.5)}, + {Value: "member2", Score: ss.Score(67.77)}, + {Value: "member3", Score: ss.Score(10)}, }), key: "key2", entries: map[string]float64{ @@ -75,17 +89,17 @@ func TestEchoVault_ZADD(t *testing.T) { "member4": 67.77, "member5": 10, }, - options: ZADDOptions{NX: true}, + options: echovault.ZADDOptions{NX: true}, want: 2, wantErr: false, }, { name: "Do not add any elements when providing existing members with NX flag", preset: true, - presetValue: sorted_set.NewSortedSet([]sorted_set.MemberParam{ - {Value: "member1", Score: sorted_set.Score(5.5)}, - {Value: "member2", Score: sorted_set.Score(67.77)}, - {Value: "member3", Score: sorted_set.Score(10)}, + presetValue: ss.NewSortedSet([]ss.MemberParam{ + {Value: "member1", Score: ss.Score(5.5)}, + {Value: "member2", Score: ss.Score(67.77)}, + {Value: "member3", Score: ss.Score(10)}, }), key: "key3", entries: map[string]float64{ @@ -93,17 +107,17 @@ func TestEchoVault_ZADD(t *testing.T) { "member2": 67.77, "member3": 10, }, - options: ZADDOptions{NX: true}, + options: echovault.ZADDOptions{NX: true}, want: 0, wantErr: false, }, { name: "Successfully add elements to an existing set when XX flag is provided with existing elements", preset: true, - presetValue: sorted_set.NewSortedSet([]sorted_set.MemberParam{ - {Value: "member1", Score: sorted_set.Score(5.5)}, - {Value: "member2", Score: sorted_set.Score(67.77)}, - {Value: "member3", Score: sorted_set.Score(10)}, + presetValue: ss.NewSortedSet([]ss.MemberParam{ + {Value: "member1", Score: ss.Score(5.5)}, + {Value: "member2", Score: ss.Score(67.77)}, + {Value: "member3", Score: ss.Score(10)}, }), key: "key4", entries: map[string]float64{ @@ -112,17 +126,17 @@ func TestEchoVault_ZADD(t *testing.T) { "member3": 15, "member4": 99.75, }, - options: ZADDOptions{XX: true, CH: true}, + options: echovault.ZADDOptions{XX: true, CH: true}, want: 3, wantErr: false, }, { name: "Fail to add element when providing XX flag with elements that do not exist in the sorted set", preset: true, - presetValue: sorted_set.NewSortedSet([]sorted_set.MemberParam{ - {Value: "member1", Score: sorted_set.Score(5.5)}, - {Value: "member2", Score: sorted_set.Score(67.77)}, - {Value: "member3", Score: sorted_set.Score(10)}, + presetValue: ss.NewSortedSet([]ss.MemberParam{ + {Value: "member1", Score: ss.Score(5.5)}, + {Value: "member2", Score: ss.Score(67.77)}, + {Value: "member3", Score: ss.Score(10)}, }), key: "key5", entries: map[string]float64{ @@ -130,7 +144,7 @@ func TestEchoVault_ZADD(t *testing.T) { "member5": 100.5, "member6": 15, }, - options: ZADDOptions{XX: true}, + options: echovault.ZADDOptions{XX: true}, want: 0, wantErr: false, }, @@ -139,10 +153,10 @@ func TestEchoVault_ZADD(t *testing.T) { // Return only the new elements added by default name: "Only update the elements where provided score is greater than current score if GT flag", preset: true, - presetValue: sorted_set.NewSortedSet([]sorted_set.MemberParam{ - {Value: "member1", Score: sorted_set.Score(5.5)}, - {Value: "member2", Score: sorted_set.Score(67.77)}, - {Value: "member3", Score: sorted_set.Score(10)}, + presetValue: ss.NewSortedSet([]ss.MemberParam{ + {Value: "member1", Score: ss.Score(5.5)}, + {Value: "member2", Score: ss.Score(67.77)}, + {Value: "member3", Score: ss.Score(10)}, }), key: "key6", entries: map[string]float64{ @@ -150,7 +164,7 @@ func TestEchoVault_ZADD(t *testing.T) { "member4": 100.5, "member5": 15, }, - options: ZADDOptions{XX: true, CH: true, GT: true}, + options: echovault.ZADDOptions{XX: true, CH: true, GT: true}, want: 1, wantErr: false, }, @@ -159,10 +173,10 @@ func TestEchoVault_ZADD(t *testing.T) { // Return only the new elements added by default. name: "Only update the elements where provided score is less than current score if LT flag is provided", preset: true, - presetValue: sorted_set.NewSortedSet([]sorted_set.MemberParam{ - {Value: "member1", Score: sorted_set.Score(5.5)}, - {Value: "member2", Score: sorted_set.Score(67.77)}, - {Value: "member3", Score: sorted_set.Score(10)}, + presetValue: ss.NewSortedSet([]ss.MemberParam{ + {Value: "member1", Score: ss.Score(5.5)}, + {Value: "member2", Score: ss.Score(67.77)}, + {Value: "member3", Score: ss.Score(10)}, }), key: "key7", entries: map[string]float64{ @@ -170,17 +184,17 @@ func TestEchoVault_ZADD(t *testing.T) { "member4": 100.5, "member5": 15, }, - options: ZADDOptions{XX: true, LT: true}, + options: echovault.ZADDOptions{XX: true, LT: true}, want: 0, wantErr: false, }, { name: "Return all the elements that were updated AND added when CH flag is provided", preset: true, - presetValue: sorted_set.NewSortedSet([]sorted_set.MemberParam{ - {Value: "member1", Score: sorted_set.Score(5.5)}, - {Value: "member2", Score: sorted_set.Score(67.77)}, - {Value: "member3", Score: sorted_set.Score(10)}, + presetValue: ss.NewSortedSet([]ss.MemberParam{ + {Value: "member1", Score: ss.Score(5.5)}, + {Value: "member2", Score: ss.Score(67.77)}, + {Value: "member3", Score: ss.Score(10)}, }), key: "key8", entries: map[string]float64{ @@ -188,23 +202,23 @@ func TestEchoVault_ZADD(t *testing.T) { "member4": 100.5, "member5": 15, }, - options: ZADDOptions{XX: true, LT: true, CH: true}, + options: echovault.ZADDOptions{XX: true, LT: true, CH: true}, want: 1, wantErr: false, }, { name: "Increment the member by score", preset: true, - presetValue: sorted_set.NewSortedSet([]sorted_set.MemberParam{ - {Value: "member1", Score: sorted_set.Score(5.5)}, - {Value: "member2", Score: sorted_set.Score(67.77)}, - {Value: "member3", Score: sorted_set.Score(10)}, + presetValue: ss.NewSortedSet([]ss.MemberParam{ + {Value: "member1", Score: ss.Score(5.5)}, + {Value: "member2", Score: ss.Score(67.77)}, + {Value: "member3", Score: ss.Score(10)}, }), key: "key9", entries: map[string]float64{ "member3": 5.5, }, - options: ZADDOptions{INCR: true}, + options: echovault.ZADDOptions{INCR: true}, want: 0, wantErr: false, }, @@ -217,7 +231,7 @@ func TestEchoVault_ZADD(t *testing.T) { "member1": 3.5, "member5": 15, }, - options: ZADDOptions{NX: true, LT: true, CH: true}, + options: echovault.ZADDOptions{NX: true, LT: true, CH: true}, want: 0, wantErr: true, }, @@ -230,7 +244,7 @@ func TestEchoVault_ZADD(t *testing.T) { "member1": 10.5, "member2": 12.5, }, - options: ZADDOptions{INCR: true}, + options: echovault.ZADDOptions{INCR: true}, want: 0, wantErr: true, }, @@ -238,7 +252,11 @@ func TestEchoVault_ZADD(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.preset { - presetValue(server, tt.key, tt.presetValue) + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } } got, err := server.ZADD(tt.key, tt.entries, tt.options) if (err != nil) != tt.wantErr { @@ -253,13 +271,7 @@ func TestEchoVault_ZADD(t *testing.T) { } func TestEchoVault_ZCARD(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string @@ -272,10 +284,10 @@ func TestEchoVault_ZCARD(t *testing.T) { { name: "Get cardinality of valid sorted set", preset: true, - presetValue: sorted_set.NewSortedSet([]sorted_set.MemberParam{ - {Value: "member1", Score: sorted_set.Score(5.5)}, - {Value: "member2", Score: sorted_set.Score(67.77)}, - {Value: "member3", Score: sorted_set.Score(10)}, + presetValue: ss.NewSortedSet([]ss.MemberParam{ + {Value: "member1", Score: ss.Score(5.5)}, + {Value: "member2", Score: ss.Score(67.77)}, + {Value: "member3", Score: ss.Score(10)}, }), key: "key1", want: 3, @@ -301,7 +313,11 @@ func TestEchoVault_ZCARD(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.preset { - presetValue(server, tt.key, tt.presetValue) + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } } got, err := server.ZCARD(tt.key) if (err != nil) != tt.wantErr { @@ -316,13 +332,7 @@ func TestEchoVault_ZCARD(t *testing.T) { } func TestEchoVault_ZCOUNT(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string @@ -337,14 +347,14 @@ func TestEchoVault_ZCOUNT(t *testing.T) { { name: "Get entire count using infinity boundaries", preset: true, - presetValue: sorted_set.NewSortedSet([]sorted_set.MemberParam{ - {Value: "member1", Score: sorted_set.Score(5.5)}, - {Value: "member2", Score: sorted_set.Score(67.77)}, - {Value: "member3", Score: sorted_set.Score(10)}, - {Value: "member4", Score: sorted_set.Score(1083.13)}, - {Value: "member5", Score: sorted_set.Score(11)}, - {Value: "member6", Score: sorted_set.Score(math.Inf(-1))}, - {Value: "member7", Score: sorted_set.Score(math.Inf(1))}, + presetValue: ss.NewSortedSet([]ss.MemberParam{ + {Value: "member1", Score: ss.Score(5.5)}, + {Value: "member2", Score: ss.Score(67.77)}, + {Value: "member3", Score: ss.Score(10)}, + {Value: "member4", Score: ss.Score(1083.13)}, + {Value: "member5", Score: ss.Score(11)}, + {Value: "member6", Score: ss.Score(math.Inf(-1))}, + {Value: "member7", Score: ss.Score(math.Inf(1))}, }), key: "key1", min: math.Inf(-1), @@ -355,14 +365,14 @@ func TestEchoVault_ZCOUNT(t *testing.T) { { name: "Get count of sub-set from -inf to limit", preset: true, - presetValue: sorted_set.NewSortedSet([]sorted_set.MemberParam{ - {Value: "member1", Score: sorted_set.Score(5.5)}, - {Value: "member2", Score: sorted_set.Score(67.77)}, - {Value: "member3", Score: sorted_set.Score(10)}, - {Value: "member4", Score: sorted_set.Score(1083.13)}, - {Value: "member5", Score: sorted_set.Score(11)}, - {Value: "member6", Score: sorted_set.Score(math.Inf(-1))}, - {Value: "member7", Score: sorted_set.Score(math.Inf(1))}, + presetValue: ss.NewSortedSet([]ss.MemberParam{ + {Value: "member1", Score: ss.Score(5.5)}, + {Value: "member2", Score: ss.Score(67.77)}, + {Value: "member3", Score: ss.Score(10)}, + {Value: "member4", Score: ss.Score(1083.13)}, + {Value: "member5", Score: ss.Score(11)}, + {Value: "member6", Score: ss.Score(math.Inf(-1))}, + {Value: "member7", Score: ss.Score(math.Inf(1))}, }), key: "key2", min: math.Inf(-1), @@ -373,14 +383,14 @@ func TestEchoVault_ZCOUNT(t *testing.T) { { name: "Get count of sub-set from bottom boundary to +inf limit", preset: true, - presetValue: sorted_set.NewSortedSet([]sorted_set.MemberParam{ - {Value: "member1", Score: sorted_set.Score(5.5)}, - {Value: "member2", Score: sorted_set.Score(67.77)}, - {Value: "member3", Score: sorted_set.Score(10)}, - {Value: "member4", Score: sorted_set.Score(1083.13)}, - {Value: "member5", Score: sorted_set.Score(11)}, - {Value: "member6", Score: sorted_set.Score(math.Inf(-1))}, - {Value: "member7", Score: sorted_set.Score(math.Inf(1))}, + presetValue: ss.NewSortedSet([]ss.MemberParam{ + {Value: "member1", Score: ss.Score(5.5)}, + {Value: "member2", Score: ss.Score(67.77)}, + {Value: "member3", Score: ss.Score(10)}, + {Value: "member4", Score: ss.Score(1083.13)}, + {Value: "member5", Score: ss.Score(11)}, + {Value: "member6", Score: ss.Score(math.Inf(-1))}, + {Value: "member7", Score: ss.Score(math.Inf(1))}, }), key: "key3", min: 1000, @@ -402,7 +412,11 @@ func TestEchoVault_ZCOUNT(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.preset { - presetValue(server, tt.key, tt.presetValue) + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } } got, err := server.ZCOUNT(tt.key, tt.min, tt.max) if (err != nil) != tt.wantErr { @@ -417,13 +431,7 @@ func TestEchoVault_ZCOUNT(t *testing.T) { } func TestEchoVault_ZDIFF(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string @@ -438,13 +446,13 @@ func TestEchoVault_ZDIFF(t *testing.T) { name: "Get the difference between 2 sorted sets without scores", preset: true, presetValues: map[string]interface{}{ - "key1": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key1": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, }), - "key2": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key2": ss.NewSortedSet([]ss.MemberParam{ {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, @@ -462,13 +470,13 @@ func TestEchoVault_ZDIFF(t *testing.T) { name: "Get the difference between 2 sorted sets with scores", preset: true, presetValues: map[string]interface{}{ - "key3": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key3": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, }), - "key4": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key4": ss.NewSortedSet([]ss.MemberParam{ {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, @@ -486,18 +494,18 @@ func TestEchoVault_ZDIFF(t *testing.T) { name: "Get the difference between 3 sets with scores", preset: true, presetValues: map[string]interface{}{ - "key5": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key5": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, {Value: "six", Score: 6}, {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, }), - "key6": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key6": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, {Value: "eleven", Score: 11}, }), - "key7": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key7": ss.NewSortedSet([]ss.MemberParam{ {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, {Value: "twelve", Score: 12}, @@ -512,7 +520,7 @@ func TestEchoVault_ZDIFF(t *testing.T) { name: "Return sorted set if only one key exists and is a sorted set", preset: true, presetValues: map[string]interface{}{ - "key8": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key8": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, {Value: "six", Score: 6}, @@ -532,12 +540,12 @@ func TestEchoVault_ZDIFF(t *testing.T) { preset: true, presetValues: map[string]interface{}{ "key9": "Default value", - "key10": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key10": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, {Value: "eleven", Score: 11}, }), - "key11": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key11": ss.NewSortedSet([]ss.MemberParam{ {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, {Value: "twelve", Score: 12}, @@ -553,7 +561,11 @@ func TestEchoVault_ZDIFF(t *testing.T) { t.Run(tt.name, func(t *testing.T) { if tt.preset { for k, v := range tt.presetValues { - presetValue(server, k, v) + err := presetValue(server, context.Background(), k, v) + if err != nil { + t.Error(err) + return + } } } got, err := server.ZDIFF(tt.withscores, tt.keys...) @@ -569,13 +581,7 @@ func TestEchoVault_ZDIFF(t *testing.T) { } func TestEchoVault_ZDIFFSTORE(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string @@ -590,12 +596,12 @@ func TestEchoVault_ZDIFFSTORE(t *testing.T) { name: "Get the difference between 2 sorted sets", preset: true, presetValues: map[string]interface{}{ - "key1": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key1": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, }), - "key2": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key2": ss.NewSortedSet([]ss.MemberParam{ {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, {Value: "six", Score: 6}, {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, @@ -610,18 +616,18 @@ func TestEchoVault_ZDIFFSTORE(t *testing.T) { name: "Get the difference between 3 sorted sets", preset: true, presetValues: map[string]interface{}{ - "key3": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key3": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, {Value: "six", Score: 6}, {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, }), - "key4": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key4": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, {Value: "eleven", Score: 11}, }), - "key5": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key5": ss.NewSortedSet([]ss.MemberParam{ {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, {Value: "twelve", Score: 12}, @@ -636,7 +642,7 @@ func TestEchoVault_ZDIFFSTORE(t *testing.T) { name: "Return base sorted set element if base set is the only existing key provided and is a valid sorted set", preset: true, presetValues: map[string]interface{}{ - "key6": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key6": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, {Value: "six", Score: 6}, @@ -653,12 +659,12 @@ func TestEchoVault_ZDIFFSTORE(t *testing.T) { preset: true, presetValues: map[string]interface{}{ "key7": "Default value", - "key8": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key8": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, {Value: "eleven", Score: 11}, }), - "key9": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key9": ss.NewSortedSet([]ss.MemberParam{ {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, {Value: "twelve", Score: 12}, @@ -674,7 +680,11 @@ func TestEchoVault_ZDIFFSTORE(t *testing.T) { t.Run(tt.name, func(t *testing.T) { if tt.preset { for k, v := range tt.presetValues { - presetValue(server, k, v) + err := presetValue(server, context.Background(), k, v) + if err != nil { + t.Error(err) + return + } } } got, err := server.ZDIFFSTORE(tt.destination, tt.keys...) @@ -690,13 +700,7 @@ func TestEchoVault_ZDIFFSTORE(t *testing.T) { } func TestEchoVault_ZINCRBY(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string @@ -711,7 +715,7 @@ func TestEchoVault_ZINCRBY(t *testing.T) { { name: "Successfully increment by int. Return the new score", preset: true, - presetValue: sorted_set.NewSortedSet([]sorted_set.MemberParam{ + 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}, @@ -725,7 +729,7 @@ func TestEchoVault_ZINCRBY(t *testing.T) { { name: "Successfully increment by float. Return new score", preset: true, - presetValue: sorted_set.NewSortedSet([]sorted_set.MemberParam{ + 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}, @@ -748,7 +752,7 @@ func TestEchoVault_ZINCRBY(t *testing.T) { { // 4. name: "Increment score to +inf", preset: true, - presetValue: sorted_set.NewSortedSet([]sorted_set.MemberParam{ + 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}, @@ -762,7 +766,7 @@ func TestEchoVault_ZINCRBY(t *testing.T) { { name: "Increment score to -inf", preset: true, - presetValue: sorted_set.NewSortedSet([]sorted_set.MemberParam{ + 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}, @@ -776,7 +780,7 @@ func TestEchoVault_ZINCRBY(t *testing.T) { { name: "Incrementing score by negative increment should lower the score", preset: true, - presetValue: sorted_set.NewSortedSet([]sorted_set.MemberParam{ + 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}, @@ -800,8 +804,8 @@ func TestEchoVault_ZINCRBY(t *testing.T) { { name: "Return error when trying to increment a member that already has score -inf", preset: true, - presetValue: sorted_set.NewSortedSet([]sorted_set.MemberParam{ - {Value: "one", Score: sorted_set.Score(math.Inf(-1))}, + presetValue: ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: ss.Score(math.Inf(-1))}, }), key: "key8", increment: 2.5, @@ -812,8 +816,8 @@ func TestEchoVault_ZINCRBY(t *testing.T) { { name: "Return error when trying to increment a member that already has score +inf", preset: true, - presetValue: sorted_set.NewSortedSet([]sorted_set.MemberParam{ - {Value: "one", Score: sorted_set.Score(math.Inf(1))}, + presetValue: ss.NewSortedSet([]ss.MemberParam{ + {Value: "one", Score: ss.Score(math.Inf(1))}, }), key: "key9", increment: 2.5, @@ -825,7 +829,11 @@ func TestEchoVault_ZINCRBY(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.preset { - presetValue(server, tt.key, tt.presetValue) + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } } got, err := server.ZINCRBY(tt.key, tt.increment, tt.member) if (err != nil) != tt.wantErr { @@ -840,20 +848,14 @@ func TestEchoVault_ZINCRBY(t *testing.T) { } func TestEchoVault_ZINTER(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string preset bool presetValues map[string]interface{} keys []string - options ZINTEROptions + options echovault.ZINTEROptions want map[string]float64 wantErr bool }{ @@ -861,19 +863,19 @@ func TestEchoVault_ZINTER(t *testing.T) { name: "Get the intersection between 2 sorted sets", preset: true, presetValues: map[string]interface{}{ - "key1": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key1": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, }), - "key2": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key2": ss.NewSortedSet([]ss.MemberParam{ {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, {Value: "six", Score: 6}, {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, }), }, keys: []string{"key1", "key2"}, - options: ZINTEROptions{}, + options: echovault.ZINTEROptions{}, want: map[string]float64{"three": 0, "four": 0, "five": 0}, wantErr: false, }, @@ -883,25 +885,25 @@ func TestEchoVault_ZINTER(t *testing.T) { name: "Get the intersection between 3 sorted sets with scores", preset: true, presetValues: map[string]interface{}{ - "key3": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key3": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, {Value: "six", Score: 6}, {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, }), - "key4": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key4": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, {Value: "eleven", Score: 11}, {Value: "eight", Score: 8}, }), - "key5": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key5": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "eight", Score: 8}, {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, {Value: "twelve", Score: 12}, }), }, keys: []string{"key3", "key4", "key5"}, - options: ZINTEROptions{WithScores: true}, + options: echovault.ZINTEROptions{WithScores: true}, want: map[string]float64{"one": 3, "eight": 24}, wantErr: false, }, @@ -911,25 +913,25 @@ func TestEchoVault_ZINTER(t *testing.T) { name: "Get the intersection between 3 sorted sets with scores", preset: true, presetValues: map[string]interface{}{ - "key6": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key6": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 100}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, {Value: "six", Score: 6}, {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, }), - "key7": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key7": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, {Value: "eleven", Score: 11}, {Value: "eight", Score: 80}, }), - "key8": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key8": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, {Value: "twelve", Score: 12}, }), }, keys: []string{"key6", "key7", "key8"}, - options: ZINTEROptions{Aggregate: "MIN", WithScores: true}, + options: echovault.ZINTEROptions{Aggregate: "MIN", WithScores: true}, want: map[string]float64{"one": 1, "eight": 8}, wantErr: false, }, @@ -938,25 +940,25 @@ func TestEchoVault_ZINTER(t *testing.T) { // Use MAX aggregate. preset: true, presetValues: map[string]interface{}{ - "key9": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key9": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 100}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, {Value: "six", Score: 6}, {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, }), - "key10": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key10": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, {Value: "eleven", Score: 11}, {Value: "eight", Score: 80}, }), - "key11": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key11": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, {Value: "twelve", Score: 12}, }), }, keys: []string{"key9", "key10", "key11"}, - options: ZINTEROptions{WithScores: true, Aggregate: "MAX"}, + options: echovault.ZINTEROptions{WithScores: true, Aggregate: "MAX"}, want: map[string]float64{"one": 1000, "eight": 800}, wantErr: false, }, @@ -966,25 +968,25 @@ func TestEchoVault_ZINTER(t *testing.T) { name: "Get the intersection between 3 sorted sets with scores", preset: true, presetValues: map[string]interface{}{ - "key12": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key12": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 100}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, {Value: "six", Score: 6}, {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, }), - "key13": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key13": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, {Value: "eleven", Score: 11}, {Value: "eight", Score: 80}, }), - "key14": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key14": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, {Value: "twelve", Score: 12}, }), }, keys: []string{"key12", "key13", "key14"}, - options: ZINTEROptions{WithScores: true, Aggregate: "SUM", Weights: []float64{1, 5, 3}}, + options: echovault.ZINTEROptions{WithScores: true, Aggregate: "SUM", Weights: []float64{1, 5, 3}}, want: map[string]float64{"one": 3105, "eight": 2808}, wantErr: false, }, @@ -994,25 +996,25 @@ func TestEchoVault_ZINTER(t *testing.T) { name: "Get the intersection between 3 sorted sets with scores", preset: true, presetValues: map[string]interface{}{ - "key15": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key15": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 100}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, {Value: "six", Score: 6}, {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, }), - "key16": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key16": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, {Value: "eleven", Score: 11}, {Value: "eight", Score: 80}, }), - "key17": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key17": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, {Value: "twelve", Score: 12}, }), }, keys: []string{"key15", "key16", "key17"}, - options: ZINTEROptions{WithScores: true, Aggregate: "MAX", Weights: []float64{1, 5, 3}}, + options: echovault.ZINTEROptions{WithScores: true, Aggregate: "MAX", Weights: []float64{1, 5, 3}}, want: map[string]float64{"one": 3000, "eight": 2400}, wantErr: false, }, @@ -1022,25 +1024,25 @@ func TestEchoVault_ZINTER(t *testing.T) { name: "Get the intersection between 3 sorted sets with scores", preset: true, presetValues: map[string]interface{}{ - "key18": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key18": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 100}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, {Value: "six", Score: 6}, {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, }), - "key19": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key19": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, {Value: "eleven", Score: 11}, {Value: "eight", Score: 80}, }), - "key20": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key20": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, {Value: "twelve", Score: 12}, }), }, keys: []string{"key18", "key19", "key20"}, - options: ZINTEROptions{WithScores: true, Aggregate: "MIN", Weights: []float64{1, 5, 3}}, + options: echovault.ZINTEROptions{WithScores: true, Aggregate: "MIN", Weights: []float64{1, 5, 3}}, want: map[string]float64{"one": 5, "eight": 8}, wantErr: false, }, @@ -1048,16 +1050,16 @@ func TestEchoVault_ZINTER(t *testing.T) { name: "Throw an error if there are more weights than keys", preset: true, presetValues: map[string]interface{}{ - "key21": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key21": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, {Value: "six", Score: 6}, {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, }), - "key22": sorted_set.NewSortedSet([]sorted_set.MemberParam{{Value: "one", Score: 1}}), + "key22": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), }, keys: []string{"key21", "key22"}, - options: ZINTEROptions{Weights: []float64{1, 2, 3}}, + options: echovault.ZINTEROptions{Weights: []float64{1, 2, 3}}, want: nil, wantErr: true, }, @@ -1065,19 +1067,19 @@ func TestEchoVault_ZINTER(t *testing.T) { name: "Throw an error if there are fewer weights than keys", preset: true, presetValues: map[string]interface{}{ - "key23": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key23": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, {Value: "six", Score: 6}, {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, }), - "key24": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key24": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, }), - "key25": sorted_set.NewSortedSet([]sorted_set.MemberParam{{Value: "one", Score: 1}}), + "key25": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), }, keys: []string{"key23", "key24", "key25"}, - options: ZINTEROptions{Weights: []float64{5, 4}}, + options: echovault.ZINTEROptions{Weights: []float64{5, 4}}, want: nil, wantErr: true, }, @@ -1085,12 +1087,12 @@ func TestEchoVault_ZINTER(t *testing.T) { name: "Throw an error if there are no keys provided", preset: true, presetValues: map[string]interface{}{ - "key26": sorted_set.NewSortedSet([]sorted_set.MemberParam{{Value: "one", Score: 1}}), - "key27": sorted_set.NewSortedSet([]sorted_set.MemberParam{{Value: "one", Score: 1}}), - "key28": sorted_set.NewSortedSet([]sorted_set.MemberParam{{Value: "one", Score: 1}}), + "key26": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), + "key27": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), + "key28": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), }, keys: []string{}, - options: ZINTEROptions{}, + options: echovault.ZINTEROptions{}, want: nil, wantErr: true, }, @@ -1098,17 +1100,17 @@ func TestEchoVault_ZINTER(t *testing.T) { name: "Throw an error if any of the provided keys are not sorted sets", preset: true, presetValues: map[string]interface{}{ - "key29": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key29": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, {Value: "six", Score: 6}, {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, }), "key30": "Default value", - "key31": sorted_set.NewSortedSet([]sorted_set.MemberParam{{Value: "one", Score: 1}}), + "key31": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), }, keys: []string{"key29", "key30", "key31"}, - options: ZINTEROptions{}, + options: echovault.ZINTEROptions{}, want: nil, wantErr: true, }, @@ -1116,19 +1118,19 @@ func TestEchoVault_ZINTER(t *testing.T) { name: "If any of the keys does not exist, return an empty array", preset: true, presetValues: map[string]interface{}{ - "key32": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key32": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, {Value: "eleven", Score: 11}, }), - "key33": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key33": ss.NewSortedSet([]ss.MemberParam{ {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, {Value: "twelve", Score: 12}, }), }, keys: []string{"non-existent", "key32", "key33"}, - options: ZINTEROptions{}, + options: echovault.ZINTEROptions{}, want: map[string]float64{}, wantErr: false, }, @@ -1137,7 +1139,11 @@ func TestEchoVault_ZINTER(t *testing.T) { t.Run(tt.name, func(t *testing.T) { if tt.preset { for k, v := range tt.presetValues { - presetValue(server, k, v) + err := presetValue(server, context.Background(), k, v) + if err != nil { + t.Error(err) + return + } } } got, err := server.ZINTER(tt.keys, tt.options) @@ -1153,13 +1159,7 @@ func TestEchoVault_ZINTER(t *testing.T) { } func TestEchoVault_ZINTERSTORE(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string @@ -1167,7 +1167,7 @@ func TestEchoVault_ZINTERSTORE(t *testing.T) { presetValues map[string]interface{} destination string keys []string - options ZINTERSTOREOptions + options echovault.ZINTERSTOREOptions want int wantErr bool }{ @@ -1175,12 +1175,12 @@ func TestEchoVault_ZINTERSTORE(t *testing.T) { name: "Get the intersection between 2 sorted sets", preset: true, presetValues: map[string]interface{}{ - "key1": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key1": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, }), - "key2": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key2": ss.NewSortedSet([]ss.MemberParam{ {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, {Value: "six", Score: 6}, {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, @@ -1188,7 +1188,7 @@ func TestEchoVault_ZINTERSTORE(t *testing.T) { }, destination: "destination1", keys: []string{"key1", "key2"}, - options: ZINTERSTOREOptions{}, + options: echovault.ZINTERSTOREOptions{}, want: 3, wantErr: false, }, @@ -1198,18 +1198,18 @@ func TestEchoVault_ZINTERSTORE(t *testing.T) { name: "Get the intersection between 3 sorted sets with scores", preset: true, presetValues: map[string]interface{}{ - "key3": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key3": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, {Value: "six", Score: 6}, {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, }), - "key4": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key4": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, {Value: "eleven", Score: 11}, {Value: "eight", Score: 8}, }), - "key5": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key5": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "eight", Score: 8}, {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, {Value: "twelve", Score: 12}, @@ -1217,7 +1217,7 @@ func TestEchoVault_ZINTERSTORE(t *testing.T) { }, destination: "destination2", keys: []string{"key3", "key4", "key5"}, - options: ZINTERSTOREOptions{WithScores: true}, + options: echovault.ZINTERSTOREOptions{WithScores: true}, want: 2, wantErr: false, }, @@ -1227,18 +1227,18 @@ func TestEchoVault_ZINTERSTORE(t *testing.T) { name: "Get the intersection between 3 sorted sets with scores", preset: true, presetValues: map[string]interface{}{ - "key6": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key6": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 100}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, {Value: "six", Score: 6}, {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, }), - "key7": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key7": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, {Value: "eleven", Score: 11}, {Value: "eight", Score: 80}, }), - "key8": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key8": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, {Value: "twelve", Score: 12}, @@ -1246,7 +1246,7 @@ func TestEchoVault_ZINTERSTORE(t *testing.T) { }, destination: "destination3", keys: []string{"key6", "key7", "key8"}, - options: ZINTERSTOREOptions{WithScores: true, Aggregate: "MIN"}, + options: echovault.ZINTERSTOREOptions{WithScores: true, Aggregate: "MIN"}, want: 2, wantErr: false, }, @@ -1256,18 +1256,18 @@ func TestEchoVault_ZINTERSTORE(t *testing.T) { name: "Get the intersection between 3 sorted sets with scores", preset: true, presetValues: map[string]interface{}{ - "key9": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key9": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 100}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, {Value: "six", Score: 6}, {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, }), - "key10": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key10": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, {Value: "eleven", Score: 11}, {Value: "eight", Score: 80}, }), - "key11": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key11": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, {Value: "twelve", Score: 12}, @@ -1275,7 +1275,7 @@ func TestEchoVault_ZINTERSTORE(t *testing.T) { }, destination: "destination4", keys: []string{"key9", "key10", "key11"}, - options: ZINTERSTOREOptions{WithScores: true, Aggregate: "MAX"}, + options: echovault.ZINTERSTOREOptions{WithScores: true, Aggregate: "MAX"}, want: 2, wantErr: false, }, @@ -1285,18 +1285,18 @@ func TestEchoVault_ZINTERSTORE(t *testing.T) { name: "Get the intersection between 3 sorted sets with scores", preset: true, presetValues: map[string]interface{}{ - "key12": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key12": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 100}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, {Value: "six", Score: 6}, {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, }), - "key13": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key13": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, {Value: "eleven", Score: 11}, {Value: "eight", Score: 80}, }), - "key14": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key14": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, {Value: "twelve", Score: 12}, @@ -1304,7 +1304,7 @@ func TestEchoVault_ZINTERSTORE(t *testing.T) { }, destination: "destination5", keys: []string{"key12", "key13", "key14"}, - options: ZINTERSTOREOptions{WithScores: true, Aggregate: "SUM", Weights: []float64{1, 5, 3}}, + options: echovault.ZINTERSTOREOptions{WithScores: true, Aggregate: "SUM", Weights: []float64{1, 5, 3}}, want: 2, wantErr: false, }, @@ -1314,18 +1314,18 @@ func TestEchoVault_ZINTERSTORE(t *testing.T) { name: "Get the intersection between 3 sorted sets with scores", preset: true, presetValues: map[string]interface{}{ - "key15": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key15": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 100}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, {Value: "six", Score: 6}, {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, }), - "key16": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key16": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, {Value: "eleven", Score: 11}, {Value: "eight", Score: 80}, }), - "key17": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key17": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, {Value: "twelve", Score: 12}, @@ -1333,7 +1333,7 @@ func TestEchoVault_ZINTERSTORE(t *testing.T) { }, destination: "destination6", keys: []string{"key15", "key16", "key17"}, - options: ZINTERSTOREOptions{WithScores: true, Aggregate: "MAX", Weights: []float64{1, 5, 3}}, + options: echovault.ZINTERSTOREOptions{WithScores: true, Aggregate: "MAX", Weights: []float64{1, 5, 3}}, want: 2, wantErr: false, }, @@ -1343,18 +1343,18 @@ func TestEchoVault_ZINTERSTORE(t *testing.T) { name: "Get the intersection between 3 sorted sets with scores", preset: true, presetValues: map[string]interface{}{ - "key18": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key18": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 100}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, {Value: "six", Score: 6}, {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, }), - "key19": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key19": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, {Value: "eleven", Score: 11}, {Value: "eight", Score: 80}, }), - "key20": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key20": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, {Value: "twelve", Score: 12}, @@ -1362,7 +1362,7 @@ func TestEchoVault_ZINTERSTORE(t *testing.T) { }, destination: "destination7", keys: []string{"key18", "key19", "key20"}, - options: ZINTERSTOREOptions{WithScores: true, Aggregate: "MIN", Weights: []float64{1, 5, 3}}, + options: echovault.ZINTERSTOREOptions{WithScores: true, Aggregate: "MIN", Weights: []float64{1, 5, 3}}, want: 2, wantErr: false, }, @@ -1370,17 +1370,17 @@ func TestEchoVault_ZINTERSTORE(t *testing.T) { name: "Throw an error if there are more weights than keys", preset: true, presetValues: map[string]interface{}{ - "key21": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key21": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, {Value: "six", Score: 6}, {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, }), - "key22": sorted_set.NewSortedSet([]sorted_set.MemberParam{{Value: "one", Score: 1}}), + "key22": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), }, destination: "destination8", keys: []string{"key21", "key22"}, - options: ZINTERSTOREOptions{Weights: []float64{1, 2, 3}}, + options: echovault.ZINTERSTOREOptions{Weights: []float64{1, 2, 3}}, want: 0, wantErr: true, }, @@ -1388,20 +1388,20 @@ func TestEchoVault_ZINTERSTORE(t *testing.T) { name: "Throw an error if there are fewer weights than keys", preset: true, presetValues: map[string]interface{}{ - "key23": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key23": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, {Value: "six", Score: 6}, {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, }), - "key24": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key24": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, }), - "key25": sorted_set.NewSortedSet([]sorted_set.MemberParam{{Value: "one", Score: 1}}), + "key25": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), }, destination: "destination9", keys: []string{"key23", "key24"}, - options: ZINTERSTOREOptions{Weights: []float64{5}}, + options: echovault.ZINTERSTOREOptions{Weights: []float64{5}}, want: 0, wantErr: true, }, @@ -1409,13 +1409,13 @@ func TestEchoVault_ZINTERSTORE(t *testing.T) { name: "Throw an error if there are no keys provided", preset: true, presetValues: map[string]interface{}{ - "key26": sorted_set.NewSortedSet([]sorted_set.MemberParam{{Value: "one", Score: 1}}), - "key27": sorted_set.NewSortedSet([]sorted_set.MemberParam{{Value: "one", Score: 1}}), - "key28": sorted_set.NewSortedSet([]sorted_set.MemberParam{{Value: "one", Score: 1}}), + "key26": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), + "key27": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), + "key28": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), }, destination: "destination10", keys: []string{}, - options: ZINTERSTOREOptions{Weights: []float64{5, 4}}, + options: echovault.ZINTERSTOREOptions{Weights: []float64{5, 4}}, want: 0, wantErr: true, }, @@ -1423,18 +1423,18 @@ func TestEchoVault_ZINTERSTORE(t *testing.T) { name: "Throw an error if any of the provided keys are not sorted sets", preset: true, presetValues: map[string]interface{}{ - "key29": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key29": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, {Value: "six", Score: 6}, {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, }), "key30": "Default value", - "key31": sorted_set.NewSortedSet([]sorted_set.MemberParam{{Value: "one", Score: 1}}), + "key31": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), }, destination: "destination11", keys: []string{"key29", "key30", "key31"}, - options: ZINTERSTOREOptions{}, + options: echovault.ZINTERSTOREOptions{}, want: 0, wantErr: true, }, @@ -1442,12 +1442,12 @@ func TestEchoVault_ZINTERSTORE(t *testing.T) { name: "If any of the keys does not exist, return an empty array", preset: true, presetValues: map[string]interface{}{ - "key32": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key32": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, {Value: "eleven", Score: 11}, }), - "key33": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key33": ss.NewSortedSet([]ss.MemberParam{ {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, {Value: "twelve", Score: 12}, @@ -1455,7 +1455,7 @@ func TestEchoVault_ZINTERSTORE(t *testing.T) { }, destination: "destination12", keys: []string{"non-existent", "key32", "key33"}, - options: ZINTERSTOREOptions{}, + options: echovault.ZINTERSTOREOptions{}, want: 0, wantErr: false, }, @@ -1464,7 +1464,11 @@ func TestEchoVault_ZINTERSTORE(t *testing.T) { t.Run(tt.name, func(t *testing.T) { if tt.preset { for k, v := range tt.presetValues { - presetValue(server, k, v) + err := presetValue(server, context.Background(), k, v) + if err != nil { + t.Error(err) + return + } } } got, err := server.ZINTERSTORE(tt.destination, tt.keys, tt.options) @@ -1480,13 +1484,7 @@ func TestEchoVault_ZINTERSTORE(t *testing.T) { } func TestEchoVault_ZLEXCOUNT(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string @@ -1501,14 +1499,14 @@ func TestEchoVault_ZLEXCOUNT(t *testing.T) { { name: "Get entire count using infinity boundaries", preset: true, - presetValue: sorted_set.NewSortedSet([]sorted_set.MemberParam{ - {Value: "e", Score: sorted_set.Score(1)}, - {Value: "f", Score: sorted_set.Score(1)}, - {Value: "g", Score: sorted_set.Score(1)}, - {Value: "h", Score: sorted_set.Score(1)}, - {Value: "i", Score: sorted_set.Score(1)}, - {Value: "j", Score: sorted_set.Score(1)}, - {Value: "k", Score: sorted_set.Score(1)}, + presetValue: ss.NewSortedSet([]ss.MemberParam{ + {Value: "e", Score: ss.Score(1)}, + {Value: "f", Score: ss.Score(1)}, + {Value: "g", Score: ss.Score(1)}, + {Value: "h", Score: ss.Score(1)}, + {Value: "i", Score: ss.Score(1)}, + {Value: "j", Score: ss.Score(1)}, + {Value: "k", Score: ss.Score(1)}, }), key: "key1", min: "f", @@ -1519,14 +1517,14 @@ func TestEchoVault_ZLEXCOUNT(t *testing.T) { { name: "Return 0 when the members do not have the same score", preset: true, - presetValue: sorted_set.NewSortedSet([]sorted_set.MemberParam{ - {Value: "a", Score: sorted_set.Score(5.5)}, - {Value: "b", Score: sorted_set.Score(67.77)}, - {Value: "c", Score: sorted_set.Score(10)}, - {Value: "d", Score: sorted_set.Score(1083.13)}, - {Value: "e", Score: sorted_set.Score(11)}, - {Value: "f", Score: sorted_set.Score(math.Inf(-1))}, - {Value: "g", Score: sorted_set.Score(math.Inf(1))}, + presetValue: ss.NewSortedSet([]ss.MemberParam{ + {Value: "a", Score: ss.Score(5.5)}, + {Value: "b", Score: ss.Score(67.77)}, + {Value: "c", Score: ss.Score(10)}, + {Value: "d", Score: ss.Score(1083.13)}, + {Value: "e", Score: ss.Score(11)}, + {Value: "f", Score: ss.Score(math.Inf(-1))}, + {Value: "g", Score: ss.Score(math.Inf(1))}, }), key: "key2", min: "a", @@ -1558,7 +1556,11 @@ func TestEchoVault_ZLEXCOUNT(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.preset { - presetValue(server, tt.key, tt.presetValue) + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } } got, err := server.ZLEXCOUNT(tt.key, tt.min, tt.max) if (err != nil) != tt.wantErr { @@ -1573,20 +1575,14 @@ func TestEchoVault_ZLEXCOUNT(t *testing.T) { } func TestEchoVault_ZMPOP(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string preset bool presetValues map[string]interface{} keys []string - options ZMPOPOptions + options echovault.ZMPOPOptions want [][]string wantErr bool }{ @@ -1594,14 +1590,14 @@ func TestEchoVault_ZMPOP(t *testing.T) { name: "Successfully pop one min element by default", preset: true, presetValues: map[string]interface{}{ - "key1": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key1": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, }), }, keys: []string{"key1"}, - options: ZMPOPOptions{}, + options: echovault.ZMPOPOptions{}, want: [][]string{ {"one", "1"}, }, @@ -1611,14 +1607,14 @@ func TestEchoVault_ZMPOP(t *testing.T) { name: "Successfully pop one min element by specifying MIN", preset: true, presetValues: map[string]interface{}{ - "key2": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key2": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, }), }, keys: []string{"key2"}, - options: ZMPOPOptions{Min: true}, + options: echovault.ZMPOPOptions{Min: true}, want: [][]string{ {"one", "1"}, }, @@ -1628,14 +1624,14 @@ func TestEchoVault_ZMPOP(t *testing.T) { name: "Successfully pop one max element by specifying MAX modifier", preset: true, presetValues: map[string]interface{}{ - "key3": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key3": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, }), }, keys: []string{"key3"}, - options: ZMPOPOptions{Max: true}, + options: echovault.ZMPOPOptions{Max: true}, want: [][]string{ {"five", "5"}, }, @@ -1645,14 +1641,14 @@ func TestEchoVault_ZMPOP(t *testing.T) { name: "Successfully pop multiple min elements", preset: true, presetValues: map[string]interface{}{ - "key4": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key4": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, {Value: "six", Score: 6}, }), }, keys: []string{"key4"}, - options: ZMPOPOptions{Min: true, Count: 5}, + options: echovault.ZMPOPOptions{Min: true, Count: 5}, want: [][]string{ {"one", "1"}, {"two", "2"}, {"three", "3"}, {"four", "4"}, {"five", "5"}, @@ -1663,14 +1659,14 @@ func TestEchoVault_ZMPOP(t *testing.T) { name: "Successfully pop multiple max elements", preset: true, presetValues: map[string]interface{}{ - "key5": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key5": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, {Value: "six", Score: 6}, }), }, keys: []string{"key5"}, - options: ZMPOPOptions{Max: true, Count: 5}, + options: echovault.ZMPOPOptions{Max: true, Count: 5}, want: [][]string{{"two", "2"}, {"three", "3"}, {"four", "4"}, {"five", "5"}, {"six", "6"}}, wantErr: false, }, @@ -1678,15 +1674,15 @@ func TestEchoVault_ZMPOP(t *testing.T) { name: "Successfully pop elements from the first set which is non-empty", preset: true, presetValues: map[string]interface{}{ - "key6": sorted_set.NewSortedSet([]sorted_set.MemberParam{}), - "key7": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key6": ss.NewSortedSet([]ss.MemberParam{}), + "key7": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, {Value: "six", Score: 6}, }), }, keys: []string{"key6", "key7"}, - options: ZMPOPOptions{Max: true, Count: 5}, + options: echovault.ZMPOPOptions{Max: true, Count: 5}, want: [][]string{{"two", "2"}, {"three", "3"}, {"four", "4"}, {"five", "5"}, {"six", "6"}}, wantErr: false, }, @@ -1696,15 +1692,15 @@ func TestEchoVault_ZMPOP(t *testing.T) { presetValues: map[string]interface{}{ "key8": "Default value", "key9": 56, - "key10": sorted_set.NewSortedSet([]sorted_set.MemberParam{}), - "key11": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key10": ss.NewSortedSet([]ss.MemberParam{}), + "key11": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, {Value: "six", Score: 6}, }), }, keys: []string{"key8", "key9", "key10", "key11"}, - options: ZMPOPOptions{Min: true, Count: 5}, + options: echovault.ZMPOPOptions{Min: true, Count: 5}, want: [][]string{{"one", "1"}, {"two", "2"}, {"three", "3"}, {"four", "4"}, {"five", "5"}}, wantErr: false, }, @@ -1713,7 +1709,11 @@ func TestEchoVault_ZMPOP(t *testing.T) { t.Run(tt.name, func(t *testing.T) { if tt.preset { for k, v := range tt.presetValues { - presetValue(server, k, v) + err := presetValue(server, context.Background(), k, v) + if err != nil { + t.Error(err) + return + } } } got, err := server.ZMPOP(tt.keys, tt.options) @@ -1729,13 +1729,7 @@ func TestEchoVault_ZMPOP(t *testing.T) { } func TestEchoVault_ZMSCORE(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string @@ -1750,7 +1744,7 @@ func TestEchoVault_ZMSCORE(t *testing.T) { // Return nil for elements that do not exist in the sorted set. name: "Return multiple scores from the sorted set", preset: true, - presetValue: sorted_set.NewSortedSet([]sorted_set.MemberParam{ + 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}, @@ -1782,7 +1776,11 @@ func TestEchoVault_ZMSCORE(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.preset { - presetValue(server, tt.key, tt.presetValue) + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } } got, err := server.ZMSCORE(tt.key, tt.members...) if (err != nil) != tt.wantErr { @@ -1810,13 +1808,7 @@ func TestEchoVault_ZMSCORE(t *testing.T) { } func TestEchoVault_ZPOP(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string @@ -1831,7 +1823,7 @@ func TestEchoVault_ZPOP(t *testing.T) { { name: "Successfully pop one min element", preset: true, - presetValue: sorted_set.NewSortedSet([]sorted_set.MemberParam{ + 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}, @@ -1847,7 +1839,7 @@ func TestEchoVault_ZPOP(t *testing.T) { { name: "Successfully pop one max element", preset: true, - presetValue: sorted_set.NewSortedSet([]sorted_set.MemberParam{ + 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}, @@ -1861,7 +1853,7 @@ func TestEchoVault_ZPOP(t *testing.T) { { name: "Successfully pop multiple min elements", preset: true, - presetValue: sorted_set.NewSortedSet([]sorted_set.MemberParam{ + 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}, @@ -1878,7 +1870,7 @@ func TestEchoVault_ZPOP(t *testing.T) { { name: "Successfully pop multiple max elements", preset: true, - presetValue: sorted_set.NewSortedSet([]sorted_set.MemberParam{ + 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}, @@ -1903,7 +1895,11 @@ func TestEchoVault_ZPOP(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.preset { - presetValue(server, tt.key, tt.presetValue) + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } } got, err := tt.popFunc(tt.key, tt.count) if (err != nil) != tt.wantErr { @@ -1918,13 +1914,7 @@ func TestEchoVault_ZPOP(t *testing.T) { } func TestEchoVault_ZRANDMEMBER(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string @@ -1941,7 +1931,7 @@ func TestEchoVault_ZRANDMEMBER(t *testing.T) { name: "Return multiple random elements without removing them", preset: true, key: "key1", - presetValue: sorted_set.NewSortedSet([]sorted_set.MemberParam{ + 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}, }), @@ -1956,7 +1946,7 @@ func TestEchoVault_ZRANDMEMBER(t *testing.T) { name: "Return multiple random elements and their scores without removing them", preset: true, key: "key2", - presetValue: sorted_set.NewSortedSet([]sorted_set.MemberParam{ + 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}, }), @@ -1979,7 +1969,11 @@ func TestEchoVault_ZRANDMEMBER(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.preset { - presetValue(server, tt.key, tt.presetValue) + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } } got, err := server.ZRANDMEMBER(tt.key, tt.count, tt.withscores) if (err != nil) != tt.wantErr { @@ -1994,13 +1988,7 @@ func TestEchoVault_ZRANDMEMBER(t *testing.T) { } func TestEchoVault_ZRANGE(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string @@ -2009,14 +1997,14 @@ func TestEchoVault_ZRANGE(t *testing.T) { key string start string stop string - options ZRANGEOptions + options echovault.ZRANGEOptions want map[string]float64 wantErr bool }{ { name: "Get elements withing score range without score", preset: true, - presetValue: sorted_set.NewSortedSet([]sorted_set.MemberParam{ + 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}, @@ -2025,14 +2013,14 @@ func TestEchoVault_ZRANGE(t *testing.T) { key: "key1", start: "3", stop: "7", - options: ZRANGEOptions{ByScore: true}, + options: echovault.ZRANGEOptions{ByScore: true}, want: map[string]float64{"three": 0, "four": 0, "five": 0, "six": 0, "seven": 0}, wantErr: false, }, { name: "Get elements within score range with score", preset: true, - presetValue: sorted_set.NewSortedSet([]sorted_set.MemberParam{ + 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}, @@ -2041,7 +2029,7 @@ func TestEchoVault_ZRANGE(t *testing.T) { key: "key2", start: "3", stop: "7", - options: ZRANGEOptions{ByScore: true, WithScores: true}, + options: echovault.ZRANGEOptions{ByScore: true, WithScores: true}, want: map[string]float64{"three": 3, "four": 4, "five": 5, "six": 6, "seven": 7}, wantErr: false, }, @@ -2050,7 +2038,7 @@ func TestEchoVault_ZRANGE(t *testing.T) { // Offset and limit are in where we start and stop counting in the original sorted set (NOT THE RESULT). name: "Get elements within score range with offset and limit", preset: true, - presetValue: sorted_set.NewSortedSet([]sorted_set.MemberParam{ + 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}, @@ -2059,14 +2047,14 @@ func TestEchoVault_ZRANGE(t *testing.T) { key: "key3", start: "3", stop: "7", - options: ZRANGEOptions{WithScores: true, ByScore: true, Offset: 2, Count: 4}, + options: echovault.ZRANGEOptions{WithScores: true, ByScore: true, Offset: 2, Count: 4}, want: map[string]float64{"three": 3, "four": 4, "five": 5}, wantErr: false, }, { name: "Get elements within lex range without score", preset: true, - presetValue: sorted_set.NewSortedSet([]sorted_set.MemberParam{ + presetValue: ss.NewSortedSet([]ss.MemberParam{ {Value: "a", Score: 1}, {Value: "e", Score: 1}, {Value: "b", Score: 1}, {Value: "f", Score: 1}, {Value: "c", Score: 1}, {Value: "g", Score: 1}, @@ -2075,14 +2063,14 @@ func TestEchoVault_ZRANGE(t *testing.T) { key: "key4", start: "c", stop: "g", - options: ZRANGEOptions{ByLex: true}, + options: echovault.ZRANGEOptions{ByLex: true}, want: map[string]float64{"c": 0, "d": 0, "e": 0, "f": 0, "g": 0}, wantErr: false, }, { name: "Get elements within lex range with score", preset: true, - presetValue: sorted_set.NewSortedSet([]sorted_set.MemberParam{ + presetValue: ss.NewSortedSet([]ss.MemberParam{ {Value: "a", Score: 1}, {Value: "e", Score: 1}, {Value: "b", Score: 1}, {Value: "f", Score: 1}, {Value: "c", Score: 1}, {Value: "g", Score: 1}, @@ -2091,7 +2079,7 @@ func TestEchoVault_ZRANGE(t *testing.T) { key: "key5", start: "a", stop: "f", - options: ZRANGEOptions{ByLex: true, WithScores: true}, + options: echovault.ZRANGEOptions{ByLex: true, WithScores: true}, want: map[string]float64{"a": 1, "b": 1, "c": 1, "d": 1, "e": 1, "f": 1}, wantErr: false, }, @@ -2100,7 +2088,7 @@ func TestEchoVault_ZRANGE(t *testing.T) { // Offset and limit are in where we start and stop counting in the original sorted set (NOT THE RESULT). name: "Get elements within lex range with offset and limit", preset: true, - presetValue: sorted_set.NewSortedSet([]sorted_set.MemberParam{ + 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}, @@ -2109,14 +2097,14 @@ func TestEchoVault_ZRANGE(t *testing.T) { key: "key6", start: "a", stop: "h", - options: ZRANGEOptions{WithScores: true, ByLex: true, Offset: 2, Count: 4}, + options: echovault.ZRANGEOptions{WithScores: true, ByLex: true, Offset: 2, Count: 4}, want: map[string]float64{"c": 1, "d": 1, "e": 1}, wantErr: false, }, { name: "Return an empty map when we use BYLEX while elements have different scores", preset: true, - presetValue: sorted_set.NewSortedSet([]sorted_set.MemberParam{ + presetValue: ss.NewSortedSet([]ss.MemberParam{ {Value: "a", Score: 1}, {Value: "b", Score: 5}, {Value: "c", Score: 2}, {Value: "d", Score: 6}, {Value: "e", Score: 3}, {Value: "f", Score: 7}, @@ -2125,7 +2113,7 @@ func TestEchoVault_ZRANGE(t *testing.T) { key: "key7", start: "a", stop: "h", - options: ZRANGEOptions{WithScores: true, ByLex: true, Offset: 2, Count: 4}, + options: echovault.ZRANGEOptions{WithScores: true, ByLex: true, Offset: 2, Count: 4}, want: map[string]float64{}, wantErr: false, }, @@ -2136,7 +2124,7 @@ func TestEchoVault_ZRANGE(t *testing.T) { key: "key10", start: "a", stop: "h", - options: ZRANGEOptions{WithScores: true, ByLex: true, Offset: 2, Count: 4}, + options: echovault.ZRANGEOptions{WithScores: true, ByLex: true, Offset: 2, Count: 4}, want: nil, wantErr: true, }, @@ -2144,7 +2132,11 @@ func TestEchoVault_ZRANGE(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.preset { - presetValue(server, tt.key, tt.presetValue) + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } } got, err := server.ZRANGE(tt.key, tt.start, tt.stop, tt.options) if (err != nil) != tt.wantErr { @@ -2159,13 +2151,7 @@ func TestEchoVault_ZRANGE(t *testing.T) { } func TestEchoVault_ZRANGESTORE(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string @@ -2175,7 +2161,7 @@ func TestEchoVault_ZRANGESTORE(t *testing.T) { source string start string stop string - options ZRANGESTOREOptions + options echovault.ZRANGESTOREOptions want int wantErr bool }{ @@ -2183,7 +2169,7 @@ func TestEchoVault_ZRANGESTORE(t *testing.T) { name: "Get elements within score range without score", preset: true, presetValues: map[string]interface{}{ - "key1": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key1": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, {Value: "six", Score: 6}, @@ -2194,7 +2180,7 @@ func TestEchoVault_ZRANGESTORE(t *testing.T) { source: "key1", start: "3", stop: "7", - options: ZRANGESTOREOptions{ByScore: true}, + options: echovault.ZRANGESTOREOptions{ByScore: true}, want: 5, wantErr: false, }, @@ -2202,7 +2188,7 @@ func TestEchoVault_ZRANGESTORE(t *testing.T) { name: "Get elements within score range with score", preset: true, presetValues: map[string]interface{}{ - "key2": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key2": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, {Value: "six", Score: 6}, @@ -2213,7 +2199,7 @@ func TestEchoVault_ZRANGESTORE(t *testing.T) { source: "key2", start: "3", stop: "7", - options: ZRANGESTOREOptions{WithScores: true, ByScore: true}, + options: echovault.ZRANGESTOREOptions{WithScores: true, ByScore: true}, want: 5, wantErr: false, }, @@ -2223,7 +2209,7 @@ func TestEchoVault_ZRANGESTORE(t *testing.T) { name: "Get elements within score range with offset and limit", preset: true, presetValues: map[string]interface{}{ - "key3": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key3": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, {Value: "six", Score: 6}, @@ -2234,7 +2220,7 @@ func TestEchoVault_ZRANGESTORE(t *testing.T) { source: "key3", start: "3", stop: "7", - options: ZRANGESTOREOptions{ByScore: true, WithScores: true, Offset: 2, Count: 4}, + options: echovault.ZRANGESTOREOptions{ByScore: true, WithScores: true, Offset: 2, Count: 4}, want: 3, wantErr: false, }, @@ -2242,7 +2228,7 @@ func TestEchoVault_ZRANGESTORE(t *testing.T) { name: "Get elements within lex range without score", preset: true, presetValues: map[string]interface{}{ - "key4": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key4": ss.NewSortedSet([]ss.MemberParam{ {Value: "a", Score: 1}, {Value: "e", Score: 1}, {Value: "b", Score: 1}, {Value: "f", Score: 1}, {Value: "c", Score: 1}, {Value: "g", Score: 1}, @@ -2253,7 +2239,7 @@ func TestEchoVault_ZRANGESTORE(t *testing.T) { source: "key4", start: "c", stop: "g", - options: ZRANGESTOREOptions{ByLex: true}, + options: echovault.ZRANGESTOREOptions{ByLex: true}, want: 5, wantErr: false, }, @@ -2261,7 +2247,7 @@ func TestEchoVault_ZRANGESTORE(t *testing.T) { name: "Get elements within lex range with score", preset: true, presetValues: map[string]interface{}{ - "key5": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key5": ss.NewSortedSet([]ss.MemberParam{ {Value: "a", Score: 1}, {Value: "e", Score: 1}, {Value: "b", Score: 1}, {Value: "f", Score: 1}, {Value: "c", Score: 1}, {Value: "g", Score: 1}, @@ -2272,7 +2258,7 @@ func TestEchoVault_ZRANGESTORE(t *testing.T) { source: "key5", start: "a", stop: "f", - options: ZRANGESTOREOptions{ByLex: true, WithScores: true}, + options: echovault.ZRANGESTOREOptions{ByLex: true, WithScores: true}, want: 6, wantErr: false, }, @@ -2282,7 +2268,7 @@ func TestEchoVault_ZRANGESTORE(t *testing.T) { name: "Get elements within lex range with offset and limit", preset: true, presetValues: map[string]interface{}{ - "key6": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key6": ss.NewSortedSet([]ss.MemberParam{ {Value: "a", Score: 1}, {Value: "b", Score: 1}, {Value: "c", Score: 1}, {Value: "d", Score: 1}, {Value: "e", Score: 1}, {Value: "f", Score: 1}, @@ -2293,7 +2279,7 @@ func TestEchoVault_ZRANGESTORE(t *testing.T) { source: "key6", start: "a", stop: "h", - options: ZRANGESTOREOptions{WithScores: true, ByLex: true, Offset: 2, Count: 4}, + options: echovault.ZRANGESTOREOptions{WithScores: true, ByLex: true, Offset: 2, Count: 4}, want: 3, wantErr: false, }, @@ -2304,7 +2290,7 @@ func TestEchoVault_ZRANGESTORE(t *testing.T) { name: "Get elements within lex range with offset and limit + reverse the results", preset: true, presetValues: map[string]interface{}{ - "key7": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key7": ss.NewSortedSet([]ss.MemberParam{ {Value: "a", Score: 1}, {Value: "b", Score: 1}, {Value: "c", Score: 1}, {Value: "d", Score: 1}, {Value: "e", Score: 1}, {Value: "f", Score: 1}, @@ -2315,7 +2301,7 @@ func TestEchoVault_ZRANGESTORE(t *testing.T) { source: "key7", start: "a", stop: "h", - options: ZRANGESTOREOptions{WithScores: true, ByLex: true, Offset: 2, Count: 4}, + options: echovault.ZRANGESTOREOptions{WithScores: true, ByLex: true, Offset: 2, Count: 4}, want: 3, wantErr: false, }, @@ -2323,7 +2309,7 @@ func TestEchoVault_ZRANGESTORE(t *testing.T) { name: "Return an empty slice when we use BYLEX while elements have different scores", preset: true, presetValues: map[string]interface{}{ - "key8": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key8": ss.NewSortedSet([]ss.MemberParam{ {Value: "a", Score: 1}, {Value: "b", Score: 5}, {Value: "c", Score: 2}, {Value: "d", Score: 6}, {Value: "e", Score: 3}, {Value: "f", Score: 7}, @@ -2334,7 +2320,7 @@ func TestEchoVault_ZRANGESTORE(t *testing.T) { source: "key8", start: "a", stop: "h", - options: ZRANGESTOREOptions{WithScores: true, ByLex: true, Offset: 2, Count: 4}, + options: echovault.ZRANGESTOREOptions{WithScores: true, ByLex: true, Offset: 2, Count: 4}, want: 0, wantErr: false, }, @@ -2348,7 +2334,7 @@ func TestEchoVault_ZRANGESTORE(t *testing.T) { source: "key9", start: "a", stop: "h", - options: ZRANGESTOREOptions{WithScores: true, ByLex: true, Offset: 2, Count: 4}, + options: echovault.ZRANGESTOREOptions{WithScores: true, ByLex: true, Offset: 2, Count: 4}, want: 0, wantErr: true, }, @@ -2357,7 +2343,11 @@ func TestEchoVault_ZRANGESTORE(t *testing.T) { t.Run(tt.name, func(t *testing.T) { if tt.preset { for k, v := range tt.presetValues { - presetValue(server, k, v) + err := presetValue(server, context.Background(), k, v) + if err != nil { + t.Error(err) + return + } } } got, err := server.ZRANGESTORE(tt.destination, tt.source, tt.start, tt.stop, tt.options) @@ -2373,13 +2363,7 @@ func TestEchoVault_ZRANGESTORE(t *testing.T) { } func TestEchoVault_ZRANK(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string @@ -2394,7 +2378,7 @@ func TestEchoVault_ZRANK(t *testing.T) { { name: "Return element's rank from a sorted set", preset: true, - presetValue: sorted_set.NewSortedSet([]sorted_set.MemberParam{ + 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}, @@ -2408,7 +2392,7 @@ func TestEchoVault_ZRANK(t *testing.T) { { name: "Return element's rank from a sorted set with its score", preset: true, - presetValue: sorted_set.NewSortedSet([]sorted_set.MemberParam{ + 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}, @@ -2432,7 +2416,7 @@ func TestEchoVault_ZRANK(t *testing.T) { { name: "If key exists and is a sorted set, but the member does not exist, return nil", preset: true, - presetValue: sorted_set.NewSortedSet([]sorted_set.MemberParam{ + 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}, @@ -2457,7 +2441,11 @@ func TestEchoVault_ZRANK(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.preset { - presetValue(server, tt.key, tt.presetValue) + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } } got, err := server.ZRANK(tt.key, tt.member, tt.withscores) if (err != nil) != tt.wantErr { @@ -2472,13 +2460,7 @@ func TestEchoVault_ZRANK(t *testing.T) { } func TestEchoVault_ZREM(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string @@ -2494,7 +2476,7 @@ func TestEchoVault_ZREM(t *testing.T) { // Return deleted count. name: "Successfully remove multiple elements from sorted set, skipping non-existent members", preset: true, - presetValue: sorted_set.NewSortedSet([]sorted_set.MemberParam{ + 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}, @@ -2528,7 +2510,11 @@ func TestEchoVault_ZREM(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.preset { - presetValue(server, tt.key, tt.presetValue) + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } } got, err := server.ZREM(tt.key, tt.members...) if (err != nil) != tt.wantErr { @@ -2543,13 +2529,7 @@ func TestEchoVault_ZREM(t *testing.T) { } func TestEchoVault_ZREMRANGEBYSCORE(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string @@ -2564,7 +2544,7 @@ func TestEchoVault_ZREMRANGEBYSCORE(t *testing.T) { { name: "Successfully remove multiple elements with scores inside the provided range", preset: true, - presetValue: sorted_set.NewSortedSet([]sorted_set.MemberParam{ + 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}, @@ -2600,7 +2580,11 @@ func TestEchoVault_ZREMRANGEBYSCORE(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.preset { - presetValue(server, tt.key, tt.presetValue) + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } } got, err := server.ZREMRANGEBYSCORE(tt.key, tt.min, tt.max) if (err != nil) != tt.wantErr { @@ -2615,13 +2599,7 @@ func TestEchoVault_ZREMRANGEBYSCORE(t *testing.T) { } func TestEchoVault_ZSCORE(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string @@ -2635,7 +2613,7 @@ func TestEchoVault_ZSCORE(t *testing.T) { { name: "Return score from a sorted set", preset: true, - presetValue: sorted_set.NewSortedSet([]sorted_set.MemberParam{ + 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}, @@ -2657,7 +2635,7 @@ func TestEchoVault_ZSCORE(t *testing.T) { { name: "If key exists and is a sorted set, but the member does not exist, return nil", preset: true, - presetValue: sorted_set.NewSortedSet([]sorted_set.MemberParam{ + 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}, @@ -2680,7 +2658,11 @@ func TestEchoVault_ZSCORE(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.preset { - presetValue(server, tt.key, tt.presetValue) + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } } got, err := server.ZSCORE(tt.key, tt.member) if (err != nil) != tt.wantErr { @@ -2695,20 +2677,14 @@ func TestEchoVault_ZSCORE(t *testing.T) { } func TestEchoVault_ZUNION(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string preset bool presetValues map[string]interface{} keys []string - options ZUNIONOptions + options echovault.ZUNIONOptions want map[string]float64 wantErr bool }{ @@ -2716,19 +2692,19 @@ func TestEchoVault_ZUNION(t *testing.T) { name: "Get the union between 2 sorted sets", preset: true, presetValues: map[string]interface{}{ - "key1": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key1": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, }), - "key2": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key2": ss.NewSortedSet([]ss.MemberParam{ {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, {Value: "six", Score: 6}, {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, }), }, keys: []string{"key1", "key2"}, - options: ZUNIONOptions{}, + options: echovault.ZUNIONOptions{}, want: map[string]float64{ "one": 0, "two": 0, "three": 0, "four": 0, "five": 0, "six": 0, "seven": 0, "eight": 0, @@ -2741,25 +2717,25 @@ func TestEchoVault_ZUNION(t *testing.T) { name: "Get the union between 3 sorted sets with scores", preset: true, presetValues: map[string]interface{}{ - "key3": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key3": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, {Value: "six", Score: 6}, {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, }), - "key4": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key4": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, {Value: "eleven", Score: 11}, {Value: "eight", Score: 8}, }), - "key5": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key5": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "eight", Score: 8}, {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, {Value: "twelve", Score: 12}, {Value: "thirty-six", Score: 36}, }), }, keys: []string{"key3", "key4", "key5"}, - options: ZUNIONOptions{WithScores: true}, + options: echovault.ZUNIONOptions{WithScores: true}, want: map[string]float64{ "one": 3, "two": 4, "three": 3, "four": 4, "five": 5, "six": 6, "seven": 7, "eight": 24, "nine": 9, "ten": 10, "eleven": 11, "twelve": 24, "thirty-six": 72, @@ -2772,25 +2748,25 @@ func TestEchoVault_ZUNION(t *testing.T) { name: "Get the union between 3 sorted sets with scores", preset: true, presetValues: map[string]interface{}{ - "key6": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key6": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 100}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, {Value: "six", Score: 6}, {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, }), - "key7": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key7": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, {Value: "eleven", Score: 11}, {Value: "eight", Score: 80}, }), - "key8": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key8": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, {Value: "twelve", Score: 12}, {Value: "thirty-six", Score: 72}, }), }, keys: []string{"key6", "key7", "key8"}, - options: ZUNIONOptions{WithScores: true, Aggregate: "MIN"}, + options: echovault.ZUNIONOptions{WithScores: true, Aggregate: "MIN"}, want: map[string]float64{ "one": 1, "two": 2, "three": 3, "four": 4, "five": 5, "six": 6, "seven": 7, "eight": 8, "nine": 9, "ten": 10, "eleven": 11, "twelve": 12, "thirty-six": 36, @@ -2803,25 +2779,25 @@ func TestEchoVault_ZUNION(t *testing.T) { name: "Get the union between 3 sorted sets with scores", preset: true, presetValues: map[string]interface{}{ - "key9": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key9": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 100}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, {Value: "six", Score: 6}, {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, }), - "key10": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key10": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, {Value: "eleven", Score: 11}, {Value: "eight", Score: 80}, }), - "key11": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key11": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, {Value: "twelve", Score: 12}, {Value: "thirty-six", Score: 72}, }), }, keys: []string{"key9", "key10", "key11"}, - options: ZUNIONOptions{WithScores: true, Aggregate: "MAX"}, + options: echovault.ZUNIONOptions{WithScores: true, Aggregate: "MAX"}, want: map[string]float64{ "one": 1000, "two": 2, "three": 3, "four": 4, "five": 5, "six": 6, "seven": 7, "eight": 800, "nine": 9, "ten": 10, "eleven": 11, "twelve": 12, "thirty-six": 72, @@ -2834,25 +2810,25 @@ func TestEchoVault_ZUNION(t *testing.T) { name: "Get the union between 3 sorted sets with scores", preset: true, presetValues: map[string]interface{}{ - "key12": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key12": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 100}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, {Value: "six", Score: 6}, {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, }), - "key13": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key13": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, {Value: "eleven", Score: 11}, {Value: "eight", Score: 80}, }), - "key14": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key14": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, {Value: "twelve", Score: 12}, }), }, keys: []string{"key12", "key13", "key14"}, - options: ZUNIONOptions{WithScores: true, Aggregate: "SUM", Weights: []float64{1, 2, 3}}, + options: echovault.ZUNIONOptions{WithScores: true, Aggregate: "SUM", Weights: []float64{1, 2, 3}}, want: map[string]float64{ "one": 3102, "two": 6, "three": 3, "four": 4, "five": 5, "six": 6, "seven": 7, "eight": 2568, "nine": 27, "ten": 30, "eleven": 22, "twelve": 60, "thirty-six": 72, @@ -2865,25 +2841,25 @@ func TestEchoVault_ZUNION(t *testing.T) { name: "Get the union between 3 sorted sets with scores", preset: true, presetValues: map[string]interface{}{ - "key15": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key15": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 100}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, {Value: "six", Score: 6}, {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, }), - "key16": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key16": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, {Value: "eleven", Score: 11}, {Value: "eight", Score: 80}, }), - "key17": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key17": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, {Value: "twelve", Score: 12}, }), }, keys: []string{"key15", "key16", "key17"}, - options: ZUNIONOptions{WithScores: true, Aggregate: "MAX", Weights: []float64{1, 2, 3}}, + options: echovault.ZUNIONOptions{WithScores: true, Aggregate: "MAX", Weights: []float64{1, 2, 3}}, want: map[string]float64{ "one": 3000, "two": 4, "three": 3, "four": 4, "five": 5, "six": 6, "seven": 7, "eight": 2400, "nine": 27, "ten": 30, "eleven": 22, "twelve": 36, "thirty-six": 72, @@ -2896,25 +2872,25 @@ func TestEchoVault_ZUNION(t *testing.T) { name: "Get the union between 3 sorted sets with scores", preset: true, presetValues: map[string]interface{}{ - "key18": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key18": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 100}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, {Value: "six", Score: 6}, {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, }), - "key19": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key19": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, {Value: "eleven", Score: 11}, {Value: "eight", Score: 80}, }), - "key20": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key20": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, {Value: "twelve", Score: 12}, }), }, keys: []string{"key18", "key19", "key20"}, - options: ZUNIONOptions{WithScores: true, Aggregate: "MIN", Weights: []float64{1, 2, 3}}, + options: echovault.ZUNIONOptions{WithScores: true, Aggregate: "MIN", Weights: []float64{1, 2, 3}}, want: map[string]float64{ "one": 2, "two": 2, "three": 3, "four": 4, "five": 5, "six": 6, "seven": 7, "eight": 8, "nine": 27, "ten": 30, "eleven": 22, "twelve": 24, "thirty-six": 72, @@ -2925,16 +2901,16 @@ func TestEchoVault_ZUNION(t *testing.T) { name: "Throw an error if there are more weights than keys", preset: true, presetValues: map[string]interface{}{ - "key21": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key21": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, {Value: "six", Score: 6}, {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, }), - "key22": sorted_set.NewSortedSet([]sorted_set.MemberParam{{Value: "one", Score: 1}}), + "key22": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), }, keys: []string{"key21", "key22"}, - options: ZUNIONOptions{Weights: []float64{1, 2, 3}}, + options: echovault.ZUNIONOptions{Weights: []float64{1, 2, 3}}, want: nil, wantErr: true, }, @@ -2942,19 +2918,19 @@ func TestEchoVault_ZUNION(t *testing.T) { name: "Throw an error if there are fewer weights than keys", preset: true, presetValues: map[string]interface{}{ - "key23": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key23": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, {Value: "six", Score: 6}, {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, }), - "key24": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key24": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, }), - "key25": sorted_set.NewSortedSet([]sorted_set.MemberParam{{Value: "one", Score: 1}}), + "key25": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), }, keys: []string{"key23", "key24", "key25"}, - options: ZUNIONOptions{Weights: []float64{5, 4}}, + options: echovault.ZUNIONOptions{Weights: []float64{5, 4}}, want: nil, wantErr: true, }, @@ -2962,12 +2938,12 @@ func TestEchoVault_ZUNION(t *testing.T) { name: "Throw an error if there are no keys provided", preset: true, presetValues: map[string]interface{}{ - "key26": sorted_set.NewSortedSet([]sorted_set.MemberParam{{Value: "one", Score: 1}}), - "key27": sorted_set.NewSortedSet([]sorted_set.MemberParam{{Value: "one", Score: 1}}), - "key28": sorted_set.NewSortedSet([]sorted_set.MemberParam{{Value: "one", Score: 1}}), + "key26": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), + "key27": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), + "key28": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), }, keys: []string{}, - options: ZUNIONOptions{Weights: []float64{5, 4}}, + options: echovault.ZUNIONOptions{Weights: []float64{5, 4}}, want: nil, wantErr: true, }, @@ -2975,17 +2951,17 @@ func TestEchoVault_ZUNION(t *testing.T) { name: "Throw an error if any of the provided keys are not sorted sets", preset: true, presetValues: map[string]interface{}{ - "key29": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key29": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, {Value: "six", Score: 6}, {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, }), "key30": "Default value", - "key31": sorted_set.NewSortedSet([]sorted_set.MemberParam{{Value: "one", Score: 1}}), + "key31": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), }, keys: []string{"key29", "key30", "key31"}, - options: ZUNIONOptions{}, + options: echovault.ZUNIONOptions{}, want: nil, wantErr: true, }, @@ -2993,19 +2969,19 @@ func TestEchoVault_ZUNION(t *testing.T) { name: "If any of the keys does not exist, skip it", preset: true, presetValues: map[string]interface{}{ - "key32": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key32": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, {Value: "eleven", Score: 11}, }), - "key33": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key33": ss.NewSortedSet([]ss.MemberParam{ {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, {Value: "twelve", Score: 12}, }), }, keys: []string{"non-existent", "key32", "key33"}, - options: ZUNIONOptions{}, + options: echovault.ZUNIONOptions{}, want: map[string]float64{ "one": 0, "two": 0, "thirty-six": 0, "twelve": 0, "eleven": 0, "seven": 0, "eight": 0, "nine": 0, "ten": 0, @@ -3017,7 +2993,11 @@ func TestEchoVault_ZUNION(t *testing.T) { t.Run(tt.name, func(t *testing.T) { if tt.preset { for k, v := range tt.presetValues { - presetValue(server, k, v) + err := presetValue(server, context.Background(), k, v) + if err != nil { + t.Error(err) + return + } } } got, err := server.ZUNION(tt.keys, tt.options) @@ -3033,13 +3013,7 @@ func TestEchoVault_ZUNION(t *testing.T) { } func TestEchoVault_ZUNIONSTORE(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string @@ -3047,7 +3021,7 @@ func TestEchoVault_ZUNIONSTORE(t *testing.T) { presetValues map[string]interface{} destination string keys []string - options ZUNIONSTOREOptions + options echovault.ZUNIONSTOREOptions want int wantErr bool }{ @@ -3055,12 +3029,12 @@ func TestEchoVault_ZUNIONSTORE(t *testing.T) { name: "Get the union between 2 sorted sets", preset: true, presetValues: map[string]interface{}{ - "key1": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key1": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, }), - "key2": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key2": ss.NewSortedSet([]ss.MemberParam{ {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, {Value: "six", Score: 6}, {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, @@ -3068,7 +3042,7 @@ func TestEchoVault_ZUNIONSTORE(t *testing.T) { }, destination: "destination1", keys: []string{"key1", "key2"}, - options: ZUNIONSTOREOptions{}, + options: echovault.ZUNIONSTOREOptions{}, want: 8, wantErr: false, }, @@ -3078,18 +3052,18 @@ func TestEchoVault_ZUNIONSTORE(t *testing.T) { name: "Get the union between 3 sorted sets with scores", preset: true, presetValues: map[string]interface{}{ - "key3": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key3": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, {Value: "six", Score: 6}, {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, }), - "key4": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key4": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, {Value: "eleven", Score: 11}, {Value: "eight", Score: 8}, }), - "key5": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key5": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "eight", Score: 8}, {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, {Value: "twelve", Score: 12}, {Value: "thirty-six", Score: 36}, @@ -3097,7 +3071,7 @@ func TestEchoVault_ZUNIONSTORE(t *testing.T) { }, destination: "destination2", keys: []string{"key3", "key4", "key5"}, - options: ZUNIONSTOREOptions{WithScores: true}, + options: echovault.ZUNIONSTOREOptions{WithScores: true}, want: 13, wantErr: false, }, @@ -3107,18 +3081,18 @@ func TestEchoVault_ZUNIONSTORE(t *testing.T) { name: "Get the union between 3 sorted sets with scores", preset: true, presetValues: map[string]interface{}{ - "key6": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key6": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 100}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, {Value: "six", Score: 6}, {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, }), - "key7": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key7": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, {Value: "eleven", Score: 11}, {Value: "eight", Score: 80}, }), - "key8": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key8": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, {Value: "twelve", Score: 12}, {Value: "thirty-six", Score: 72}, @@ -3126,7 +3100,7 @@ func TestEchoVault_ZUNIONSTORE(t *testing.T) { }, destination: "destination3", keys: []string{"key6", "key7", "key8"}, - options: ZUNIONSTOREOptions{WithScores: true, Aggregate: "MIN"}, + options: echovault.ZUNIONSTOREOptions{WithScores: true, Aggregate: "MIN"}, want: 13, wantErr: false, }, @@ -3136,18 +3110,18 @@ func TestEchoVault_ZUNIONSTORE(t *testing.T) { name: "Get the union between 3 sorted sets with scores", preset: true, presetValues: map[string]interface{}{ - "key9": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key9": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 100}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, {Value: "six", Score: 6}, {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, }), - "key10": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key10": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, {Value: "eleven", Score: 11}, {Value: "eight", Score: 80}, }), - "key11": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key11": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, {Value: "twelve", Score: 12}, {Value: "thirty-six", Score: 72}, @@ -3155,7 +3129,7 @@ func TestEchoVault_ZUNIONSTORE(t *testing.T) { }, destination: "destination4", keys: []string{"key9", "key10", "key11"}, - options: ZUNIONSTOREOptions{WithScores: true, Aggregate: "MAX"}, + options: echovault.ZUNIONSTOREOptions{WithScores: true, Aggregate: "MAX"}, want: 13, wantErr: false, }, @@ -3165,18 +3139,18 @@ func TestEchoVault_ZUNIONSTORE(t *testing.T) { name: "Get the union between 3 sorted sets with scores", preset: true, presetValues: map[string]interface{}{ - "key12": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key12": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 100}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, {Value: "six", Score: 6}, {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, }), - "key13": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key13": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, {Value: "eleven", Score: 11}, {Value: "eight", Score: 80}, }), - "key14": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key14": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, {Value: "twelve", Score: 12}, @@ -3184,7 +3158,7 @@ func TestEchoVault_ZUNIONSTORE(t *testing.T) { }, destination: "destination5", keys: []string{"key12", "key13", "key14"}, - options: ZUNIONSTOREOptions{WithScores: true, Aggregate: "SUM", Weights: []float64{1, 2, 3}}, + options: echovault.ZUNIONSTOREOptions{WithScores: true, Aggregate: "SUM", Weights: []float64{1, 2, 3}}, want: 13, wantErr: false, }, @@ -3194,18 +3168,18 @@ func TestEchoVault_ZUNIONSTORE(t *testing.T) { name: "Get the union between 3 sorted sets with scores", preset: true, presetValues: map[string]interface{}{ - "key15": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key15": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 100}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, {Value: "six", Score: 6}, {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, }), - "key16": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key16": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, {Value: "eleven", Score: 11}, {Value: "eight", Score: 80}, }), - "key17": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key17": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, {Value: "twelve", Score: 12}, @@ -3213,7 +3187,7 @@ func TestEchoVault_ZUNIONSTORE(t *testing.T) { }, destination: "destination6", keys: []string{"key15", "key16", "key17"}, - options: ZUNIONSTOREOptions{WithScores: true, Aggregate: "MAX", Weights: []float64{1, 2, 3}}, + options: echovault.ZUNIONSTOREOptions{WithScores: true, Aggregate: "MAX", Weights: []float64{1, 2, 3}}, want: 13, wantErr: false, }, @@ -3223,18 +3197,18 @@ func TestEchoVault_ZUNIONSTORE(t *testing.T) { name: "Get the union between 3 sorted sets with scores", preset: true, presetValues: map[string]interface{}{ - "key18": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key18": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 100}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, {Value: "six", Score: 6}, {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, }), - "key19": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key19": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, {Value: "eleven", Score: 11}, {Value: "eight", Score: 80}, }), - "key20": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key20": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, {Value: "twelve", Score: 12}, @@ -3242,7 +3216,7 @@ func TestEchoVault_ZUNIONSTORE(t *testing.T) { }, destination: "destination7", keys: []string{"destination7", "key18", "key19", "key20"}, - options: ZUNIONSTOREOptions{WithScores: true, Aggregate: "MIN", Weights: []float64{1, 2, 3}}, + options: echovault.ZUNIONSTOREOptions{WithScores: true, Aggregate: "MIN", Weights: []float64{1, 2, 3}}, want: 13, wantErr: false, }, @@ -3250,17 +3224,17 @@ func TestEchoVault_ZUNIONSTORE(t *testing.T) { name: "Throw an error if there are more weights than keys", preset: true, presetValues: map[string]interface{}{ - "key21": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key21": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, {Value: "six", Score: 6}, {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, }), - "key22": sorted_set.NewSortedSet([]sorted_set.MemberParam{{Value: "one", Score: 1}}), + "key22": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), }, destination: "destination8", keys: []string{"key21", "key22"}, - options: ZUNIONSTOREOptions{Weights: []float64{1, 2, 3}}, + options: echovault.ZUNIONSTOREOptions{Weights: []float64{1, 2, 3}}, want: 0, wantErr: true, }, @@ -3268,20 +3242,20 @@ func TestEchoVault_ZUNIONSTORE(t *testing.T) { name: "Throw an error if there are fewer weights than keys", preset: true, presetValues: map[string]interface{}{ - "key23": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key23": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, {Value: "six", Score: 6}, {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, }), - "key24": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key24": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, }), - "key25": sorted_set.NewSortedSet([]sorted_set.MemberParam{{Value: "one", Score: 1}}), + "key25": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), }, destination: "destination9", keys: []string{"key23", "key24", "key25"}, - options: ZUNIONSTOREOptions{Weights: []float64{5, 4}}, + options: echovault.ZUNIONSTOREOptions{Weights: []float64{5, 4}}, want: 0, wantErr: true, }, @@ -3289,18 +3263,18 @@ func TestEchoVault_ZUNIONSTORE(t *testing.T) { name: "Throw an error if any of the provided keys are not sorted sets", preset: true, presetValues: map[string]interface{}{ - "key29": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key29": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, {Value: "six", Score: 6}, {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, }), "key30": "Default value", - "key31": sorted_set.NewSortedSet([]sorted_set.MemberParam{{Value: "one", Score: 1}}), + "key31": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), }, destination: "destination11", keys: []string{"key29", "key30", "key31"}, - options: ZUNIONSTOREOptions{}, + options: echovault.ZUNIONSTOREOptions{}, want: 0, wantErr: true, }, @@ -3308,12 +3282,12 @@ func TestEchoVault_ZUNIONSTORE(t *testing.T) { name: "If any of the keys does not exist, skip it", preset: true, presetValues: map[string]interface{}{ - "key32": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key32": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, {Value: "eleven", Score: 11}, }), - "key33": sorted_set.NewSortedSet([]sorted_set.MemberParam{ + "key33": ss.NewSortedSet([]ss.MemberParam{ {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, {Value: "twelve", Score: 12}, @@ -3329,7 +3303,11 @@ func TestEchoVault_ZUNIONSTORE(t *testing.T) { t.Run(tt.name, func(t *testing.T) { if tt.preset { for k, v := range tt.presetValues { - presetValue(server, k, v) + err := presetValue(server, context.Background(), k, v) + if err != nil { + t.Error(err) + return + } } } got, err := server.ZUNIONSTORE(tt.destination, tt.keys, tt.options) diff --git a/pkg/modules/sorted_set/commands_test.go b/test/modules/sorted_set/commands_test.go similarity index 96% rename from pkg/modules/sorted_set/commands_test.go rename to test/modules/sorted_set/commands_test.go index bf8ed36c..bdf233ed 100644 --- a/pkg/modules/sorted_set/commands_test.go +++ b/test/modules/sorted_set/commands_test.go @@ -19,15 +19,20 @@ import ( "context" "errors" "fmt" + "github.com/echovault/echovault/internal" "github.com/echovault/echovault/internal/config" - "github.com/echovault/echovault/internal/sorted_set" + "github.com/echovault/echovault/internal/modules/sorted_set" "github.com/echovault/echovault/pkg/constants" "github.com/echovault/echovault/pkg/echovault" "github.com/tidwall/resp" "math" + "net" + "reflect" "slices" "strconv" + "strings" "testing" + "unsafe" ) var mockServer *echovault.EchoVault @@ -41,6 +46,49 @@ func init() { ) } +func getUnexportedField(field reflect.Value) interface{} { + return reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())).Elem().Interface() +} + +func getHandler(commands ...string) internal.HandlerFunc { + if len(commands) == 0 { + return nil + } + getCommands := + getUnexportedField(reflect.ValueOf(mockServer).Elem().FieldByName("getCommands")).(func() []internal.Command) + for _, c := range getCommands() { + if strings.EqualFold(commands[0], c.Command) && len(commands) == 1 { + // Get command handler + return c.HandlerFunc + } + if strings.EqualFold(commands[0], c.Command) { + // Get sub-command handler + for _, sc := range c.SubCommands { + if strings.EqualFold(commands[1], sc.Command) { + return sc.HandlerFunc + } + } + } + } + return nil +} + +func getHandlerFuncParams(ctx context.Context, cmd []string, conn *net.Conn) internal.HandlerFuncParams { + return internal.HandlerFuncParams{ + Context: ctx, + Command: cmd, + Connection: conn, + KeyExists: mockServer.KeyExists, + CreateKeyAndLock: mockServer.CreateKeyAndLock, + KeyLock: mockServer.KeyLock, + KeyRLock: mockServer.KeyRLock, + KeyUnlock: mockServer.KeyUnlock, + KeyRUnlock: mockServer.KeyRUnlock, + GetValue: mockServer.GetValue, + SetValue: mockServer.SetValue, + } +} + func Test_HandleZADD(t *testing.T) { tests := []struct { name string @@ -273,7 +321,15 @@ func Test_HandleZADD(t *testing.T) { } mockServer.KeyUnlock(ctx, test.key) } - res, err := handleZADD(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) + if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) @@ -390,7 +446,15 @@ func Test_HandleZCARD(t *testing.T) { } mockServer.KeyUnlock(ctx, test.key) } - res, err := handleZCARD(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) + if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) @@ -542,7 +606,15 @@ func Test_HandleZCOUNT(t *testing.T) { } mockServer.KeyUnlock(ctx, test.key) } - res, err := handleZCOUNT(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) + if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) @@ -666,7 +738,15 @@ func Test_HandleZLEXCOUNT(t *testing.T) { } mockServer.KeyUnlock(ctx, test.key) } - res, err := handleZLEXCOUNT(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) + if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) @@ -830,7 +910,15 @@ func Test_HandleZDIFF(t *testing.T) { mockServer.KeyUnlock(ctx, key) } } - res, err := handleZDIFF(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) + if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) @@ -1016,7 +1104,15 @@ func Test_HandleZDIFFSTORE(t *testing.T) { mockServer.KeyUnlock(ctx, key) } } - res, err := handleZDIFFSTORE(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) + if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) @@ -1246,7 +1342,15 @@ func Test_HandleZINCRBY(t *testing.T) { } mockServer.KeyUnlock(ctx, test.key) } - res, err := handleZINCRBY(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) + if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) @@ -1482,7 +1586,15 @@ func Test_HandleZMPOP(t *testing.T) { mockServer.KeyUnlock(ctx, key) } } - res, err := handleZMPOP(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) + if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) @@ -1665,7 +1777,15 @@ func Test_HandleZPOP(t *testing.T) { mockServer.KeyUnlock(ctx, key) } } - res, err := handleZPOP(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) + if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) @@ -1775,7 +1895,15 @@ func Test_HandleZMSCORE(t *testing.T) { mockServer.KeyUnlock(ctx, key) } } - res, err := handleZMSCORE(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) + if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) @@ -1886,7 +2014,15 @@ func Test_HandleZSCORE(t *testing.T) { mockServer.KeyUnlock(ctx, key) } } - res, err := handleZSCORE(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) + if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) @@ -2012,7 +2148,15 @@ func Test_HandleZRANDMEMBER(t *testing.T) { } mockServer.KeyUnlock(ctx, test.key) } - res, err := handleZRANDMEMBER(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) + if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) @@ -2181,7 +2325,15 @@ func Test_HandleZRANK(t *testing.T) { mockServer.KeyUnlock(ctx, key) } } - res, err := handleZRANK(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) + if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) @@ -2289,7 +2441,15 @@ func Test_HandleZREM(t *testing.T) { mockServer.KeyUnlock(ctx, key) } } - res, err := handleZREM(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) + if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) @@ -2405,7 +2565,15 @@ func Test_HandleZREMRANGEBYSCORE(t *testing.T) { mockServer.KeyUnlock(ctx, key) } } - res, err := handleZREMRANGEBYSCORE(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) + if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) @@ -2578,7 +2746,15 @@ func Test_HandleZREMRANGEBYRANK(t *testing.T) { mockServer.KeyUnlock(ctx, key) } } - res, err := handleZREMRANGEBYRANK(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) + if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) @@ -2720,7 +2896,15 @@ func Test_HandleZREMRANGEBYLEX(t *testing.T) { mockServer.KeyUnlock(ctx, key) } } - res, err := handleZREMRANGEBYLEX(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) + if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) @@ -2990,7 +3174,15 @@ func Test_HandleZRANGE(t *testing.T) { mockServer.KeyUnlock(ctx, key) } } - res, err := handleZRANGE(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) + if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) @@ -3297,7 +3489,15 @@ func Test_HandleZRANGESTORE(t *testing.T) { mockServer.KeyUnlock(ctx, key) } } - res, err := handleZRANGESTORE(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) + if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) @@ -3629,7 +3829,15 @@ func Test_HandleZINTER(t *testing.T) { mockServer.KeyUnlock(ctx, key) } } - res, err := handleZINTER(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) + if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) @@ -3992,7 +4200,15 @@ func Test_HandleZINTERSTORE(t *testing.T) { mockServer.KeyUnlock(ctx, key) } } - res, err := handleZINTERSTORE(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) + if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) @@ -4351,7 +4567,15 @@ func Test_HandleZUNION(t *testing.T) { mockServer.KeyUnlock(ctx, key) } } - res, err := handleZUNION(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) + if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) @@ -4753,7 +4977,15 @@ func Test_HandleZUNIONSTORE(t *testing.T) { mockServer.KeyUnlock(ctx, key) } } - res, err := handleZUNIONSTORE(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) + if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) diff --git a/pkg/echovault/api_string_test.go b/test/modules/string/api_test.go similarity index 89% rename from pkg/echovault/api_string_test.go rename to test/modules/string/api_test.go index 499f1dbb..6bb96f47 100644 --- a/pkg/echovault/api_string_test.go +++ b/test/modules/string/api_test.go @@ -12,23 +12,37 @@ // See the License for the specific language governing permissions and // limitations under the License. -package echovault +package str import ( + "context" "github.com/echovault/echovault/internal/config" - "github.com/echovault/echovault/pkg/commands" - "github.com/echovault/echovault/pkg/constants" + "github.com/echovault/echovault/pkg/echovault" "testing" ) -func TestEchoVault_SUBSTR(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.NoEviction, +func createEchoVault() *echovault.EchoVault { + ev, _ := echovault.NewEchoVault( + echovault.WithConfig(config.Config{ + DataDir: "", }), ) + return ev +} + +func presetValue(server *echovault.EchoVault, ctx context.Context, key string, value interface{}) error { + if _, err := server.CreateKeyAndLock(ctx, key); err != nil { + return err + } + if err := server.SetValue(ctx, key, value); err != nil { + return err + } + server.KeyUnlock(ctx, key) + return nil +} + +func TestEchoVault_SUBSTR(t *testing.T) { + server := createEchoVault() tests := []struct { name string @@ -168,7 +182,11 @@ func TestEchoVault_SUBSTR(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.presetValue != nil { - presetValue(server, tt.key, tt.presetValue) + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } } got, err := tt.substrFunc(tt.key, tt.start, tt.end) if (err != nil) != tt.wantErr { @@ -183,13 +201,7 @@ func TestEchoVault_SUBSTR(t *testing.T) { } func TestEchoVault_SETRANGE(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string @@ -258,7 +270,11 @@ func TestEchoVault_SETRANGE(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.presetValue != nil { - presetValue(server, tt.key, tt.presetValue) + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } } got, err := server.SETRANGE(tt.key, tt.offset, tt.new) if (err != nil) != tt.wantErr { @@ -273,13 +289,7 @@ func TestEchoVault_SETRANGE(t *testing.T) { } func TestEchoVault_STRLEN(t *testing.T) { - server, _ := NewEchoVault( - WithCommands(commands.All()), - WithConfig(config.Config{ - DataDir: "", - EvictionPolicy: constants.NoEviction, - }), - ) + server := createEchoVault() tests := []struct { name string @@ -306,7 +316,11 @@ func TestEchoVault_STRLEN(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.presetValue != nil { - presetValue(server, tt.key, tt.presetValue) + err := presetValue(server, context.Background(), tt.key, tt.presetValue) + if err != nil { + t.Error(err) + return + } } got, err := server.STRLEN(tt.key) if (err != nil) != tt.wantErr { diff --git a/pkg/modules/string/commands_test.go b/test/modules/string/commands_test.go similarity index 86% rename from pkg/modules/string/commands_test.go rename to test/modules/string/commands_test.go index 1851c352..5eac8292 100644 --- a/pkg/modules/string/commands_test.go +++ b/test/modules/string/commands_test.go @@ -24,8 +24,12 @@ import ( "github.com/echovault/echovault/pkg/constants" "github.com/echovault/echovault/pkg/echovault" "github.com/tidwall/resp" + "net" + "reflect" "strconv" + "strings" "testing" + "unsafe" ) var mockServer *echovault.EchoVault @@ -39,6 +43,49 @@ func init() { ) } +func getUnexportedField(field reflect.Value) interface{} { + return reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())).Elem().Interface() +} + +func getHandler(commands ...string) internal.HandlerFunc { + if len(commands) == 0 { + return nil + } + getCommands := + getUnexportedField(reflect.ValueOf(mockServer).Elem().FieldByName("getCommands")).(func() []internal.Command) + for _, c := range getCommands() { + if strings.EqualFold(commands[0], c.Command) && len(commands) == 1 { + // Get command handler + return c.HandlerFunc + } + if strings.EqualFold(commands[0], c.Command) { + // Get sub-command handler + for _, sc := range c.SubCommands { + if strings.EqualFold(commands[1], sc.Command) { + return sc.HandlerFunc + } + } + } + } + return nil +} + +func getHandlerFuncParams(ctx context.Context, cmd []string, conn *net.Conn) internal.HandlerFuncParams { + return internal.HandlerFuncParams{ + Context: ctx, + Command: cmd, + Connection: conn, + KeyExists: mockServer.KeyExists, + CreateKeyAndLock: mockServer.CreateKeyAndLock, + KeyLock: mockServer.KeyLock, + KeyRLock: mockServer.KeyRLock, + KeyUnlock: mockServer.KeyUnlock, + KeyRUnlock: mockServer.KeyRUnlock, + GetValue: mockServer.GetValue, + SetValue: mockServer.SetValue, + } +} + func Test_HandleSetRange(t *testing.T) { tests := []struct { name string @@ -157,7 +204,14 @@ func Test_HandleSetRange(t *testing.T) { mockServer.KeyUnlock(ctx, test.key) } - res, err := handleSetRange(ctx, test.command, mockServer, nil) + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) + if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) @@ -254,7 +308,15 @@ func Test_HandleStrLen(t *testing.T) { } mockServer.KeyUnlock(ctx, test.key) } - res, err := handleStrLen(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) + if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) @@ -382,7 +444,15 @@ func Test_HandleSubStr(t *testing.T) { } mockServer.KeyUnlock(ctx, test.key) } - res, err := handleSubStr(ctx, test.command, mockServer, nil) + + handler := getHandler(test.command[0]) + if handler == nil { + t.Errorf("no handler found for command %s", test.command[0]) + return + } + + res, err := handler(getHandlerFuncParams(ctx, test.command, nil)) + if test.expectedError != nil { if err.Error() != test.expectedError.Error() { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error())