refactor!(collections): don't panic when initializing collections (#14323)

This commit is contained in:
Aaron Craelius 2022-12-24 09:53:29 -05:00 committed by GitHub
parent bc46bbbec0
commit 1d2de854d1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 227 additions and 88 deletions

View File

@ -2,11 +2,12 @@ package collections
import (
"context"
"cosmossdk.io/core/store"
db "github.com/tendermint/tm-db"
"math"
"testing"
"cosmossdk.io/core/store"
db "github.com/tendermint/tm-db"
"github.com/stretchr/testify/require"
)

View File

@ -4,6 +4,7 @@ go 1.19
require (
cosmossdk.io/core v0.3.4
github.com/hashicorp/go-multierror v1.1.1 // TODO: remove with go 1.20 release
github.com/stretchr/testify v1.8.1
github.com/tendermint/tm-db v0.6.7
)
@ -17,13 +18,16 @@ require (
github.com/dgraph-io/ristretto v0.1.1 // indirect
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/fsnotify/fsnotify v1.5.4 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/golang/glog v1.0.0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/btree v1.1.2 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/jmhodges/levigo v1.0.0 // indirect
github.com/klauspost/compress v1.15.12 // indirect
github.com/onsi/ginkgo v1.16.4 // indirect
github.com/onsi/gomega v1.20.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect

View File

@ -33,8 +33,9 @@ github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojt
github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4 h1:7HZCaLC5+BZpmbhCOZJ293Lz68O7PYrF2EzeiFMwCLk=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ=
github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
@ -59,6 +60,11 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
@ -75,15 +81,18 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA=
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.20.0 h1:8W0cWlwFkflGPLltQvLRB7ZVD5HuP6ng320w2IS245Q=
github.com/onsi/gomega v1.20.0/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@ -105,6 +114,7 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
@ -115,19 +125,25 @@ github.com/tendermint/tm-db v0.6.7 h1:fE00Cbl0jayAoqlExN6oyQJ7fR/ZtoVOmvPJ//+shu
github.com/tendermint/tm-db v0.6.7/go.mod h1:byQDzFkZV1syXr/ReXS808NxA2xvyuuVgXOJ/088L6I=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.3.0 h1:VWL6FNY2bEEmsGVKabSlHu5Irp34xmMRoqb/9lF9lxk=
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -140,7 +156,9 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -149,8 +167,11 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
@ -171,7 +192,6 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -12,12 +12,12 @@ type Item[V any] Map[noKey, V]
// Name and prefix must be unique within the schema and name must match the format specified by NameRegex, or
// else this method will panic.
func NewItem[V any](
schema Schema,
schema *SchemaBuilder,
prefix Prefix,
name string,
valueCodec ValueCodec[V],
) Item[V] {
item := (Item[V])(newMap[noKey, V](schema, prefix, name, noKey{}, valueCodec))
item := (Item[V])(newMap[noKey](schema, prefix, name, noKey{}, valueCodec))
schema.addCollection(item)
return item
}

View File

@ -8,10 +8,13 @@ import (
func TestItem(t *testing.T) {
sk, ctx := deps()
schema := NewSchema(sk)
item := NewItem(schema, NewPrefix("item"), "item", Uint64Value)
schemaBuilder := NewSchemaBuilder(sk)
item := NewItem(schemaBuilder, NewPrefix("item"), "item", Uint64Value)
_, err := schemaBuilder.Build()
require.NoError(t, err)
// set
err := item.Set(ctx, 1000)
err = item.Set(ctx, 1000)
require.NoError(t, err)
// get

View File

@ -104,8 +104,10 @@ func (r *Range[K]) Descending() *Range[K] {
}
// test sentinel error
var errRange = errors.New("collections: range error")
var errOrder = errors.New("collections: invalid order")
var (
errRange = errors.New("collections: range error")
errOrder = errors.New("collections: invalid order")
)
func (r *Range[K]) RangeValues() (prefix *K, start *Bound[K], end *Bound[K], order Order, err error) {
if r.prefix != nil && (r.end != nil || r.start != nil) {

View File

@ -2,14 +2,17 @@ package collections
import (
"fmt"
"github.com/stretchr/testify/require"
"testing"
"github.com/stretchr/testify/require"
)
func TestIteratorBasic(t *testing.T) {
sk, ctx := deps()
schema := NewSchema(sk)
m := NewMap(schema, NewPrefix("some super amazing prefix"), "m", StringKey, Uint64Value)
schemaBuilder := NewSchemaBuilder(sk)
m := NewMap(schemaBuilder, NewPrefix("some super amazing prefix"), "m", StringKey, Uint64Value)
_, err := schemaBuilder.Build()
require.NoError(t, err)
for i := uint64(1); i <= 2; i++ {
require.NoError(t, m.Set(ctx, fmt.Sprintf("%d", i), i))
@ -55,8 +58,10 @@ func TestIteratorBasic(t *testing.T) {
func TestIteratorKeyValues(t *testing.T) {
sk, ctx := deps()
schema := NewSchema(sk)
m := NewMap(schema, NewPrefix("some super amazing prefix"), "m", StringKey, Uint64Value)
schemaBuilder := NewSchemaBuilder(sk)
m := NewMap(schemaBuilder, NewPrefix("some super amazing prefix"), "m", StringKey, Uint64Value)
_, err := schemaBuilder.Build()
require.NoError(t, err)
for i := uint64(0); i <= 5; i++ {
require.NoError(t, m.Set(ctx, fmt.Sprintf("%d", i), i))
@ -102,8 +107,10 @@ func TestIteratorKeyValues(t *testing.T) {
func TestIteratorPrefixing(t *testing.T) {
sk, ctx := deps()
schema := NewSchema(sk)
m := NewMap(schema, NewPrefix("cool"), "cool", StringKey, Uint64Value)
schemaBuilder := NewSchemaBuilder(sk)
m := NewMap(schemaBuilder, NewPrefix("cool"), "cool", StringKey, Uint64Value)
_, err := schemaBuilder.Build()
require.NoError(t, err)
require.NoError(t, m.Set(ctx, "A1", 11))
require.NoError(t, m.Set(ctx, "A2", 12))
@ -118,8 +125,10 @@ func TestIteratorPrefixing(t *testing.T) {
func TestIteratorRanging(t *testing.T) {
sk, ctx := deps()
schema := NewSchema(sk)
m := NewMap(schema, NewPrefix("cool"), "cool", Uint64Key, Uint64Value)
schemaBuilder := NewSchemaBuilder(sk)
m := NewMap(schemaBuilder, NewPrefix("cool"), "cool", Uint64Key, Uint64Value)
_, err := schemaBuilder.Build()
require.NoError(t, err)
for i := uint64(0); i <= 7; i++ {
require.NoError(t, m.Set(ctx, i, i))

View File

@ -12,7 +12,7 @@ import (
type KeySet[K any] Map[K, noValue]
// NewKeySet returns a KeySet given a Schema, Prefix a human name for the collection and a KeyCodec for the key K.
func NewKeySet[K any](schema Schema, prefix Prefix, name string, keyCodec KeyCodec[K]) KeySet[K] {
func NewKeySet[K any](schema *SchemaBuilder, prefix Prefix, name string, keyCodec KeyCodec[K]) KeySet[K] {
return (KeySet[K])(NewMap(schema, prefix, name, keyCodec, noValueCodec))
}

View File

@ -1,13 +1,14 @@
package collections
import (
"github.com/stretchr/testify/require"
"testing"
"github.com/stretchr/testify/require"
)
func TestKeySet(t *testing.T) {
sk, ctx := deps()
schema := NewSchema(sk)
schema := NewSchemaBuilder(sk)
ks := NewKeySet(schema, NewPrefix("keyset"), "keyset", StringKey)
// set
@ -65,5 +66,4 @@ func Test_noValue(t *testing.T) {
_, err = noValueCodec.Decode([]byte("bad"))
require.ErrorIs(t, err, ErrEncoding)
}

View File

@ -24,25 +24,25 @@ type Map[K, V any] struct {
// Name and prefix must be unique within the schema and name must match the format specified by NameRegex, or
// else this method will panic.
func NewMap[K, V any](
schema Schema,
schemaBuilder *SchemaBuilder,
prefix Prefix,
name string,
keyCodec KeyCodec[K],
valueCodec ValueCodec[V],
) Map[K, V] {
m := newMap(schema, prefix, name, keyCodec, valueCodec)
schema.addCollection(m)
m := newMap(schemaBuilder, prefix, name, keyCodec, valueCodec)
schemaBuilder.addCollection(m)
return m
}
func newMap[K, V any](
schema Schema, prefix Prefix, name string,
schemaBuilder *SchemaBuilder, prefix Prefix, name string,
keyCodec KeyCodec[K], valueCodec ValueCodec[V],
) Map[K, V] {
return Map[K, V]{
kc: keyCodec,
vc: valueCodec,
sa: schema.storeAccessor,
sa: schemaBuilder.schema.storeAccessor,
prefix: prefix.Bytes(),
name: name,
}
@ -60,7 +60,6 @@ func (m Map[K, V]) getPrefix() []byte {
// Errors with ErrEncoding if key or value encoding fails.
func (m Map[K, V]) Set(ctx context.Context, key K, value V) error {
bytesKey, err := encodeKeyWithPrefix(m.prefix, m.kc, key)
if err != nil {
return err
}

View File

@ -8,8 +8,10 @@ import (
func TestMap(t *testing.T) {
sk, ctx := deps()
schema := NewSchema(sk)
m := NewMap(schema, NewPrefix("hi"), "m", Uint64Key, Uint64Value)
schemaBuilder := NewSchemaBuilder(sk)
m := NewMap(schemaBuilder, NewPrefix("hi"), "m", Uint64Key, Uint64Value)
_, err := schemaBuilder.Build()
require.NoError(t, err)
// test not has
has, err := m.Has(ctx, 1)

View File

@ -2,11 +2,102 @@ package collections
import (
"context"
"cosmossdk.io/core/store"
"fmt"
"regexp"
"strings"
"cosmossdk.io/core/store"
"github.com/hashicorp/go-multierror"
)
// SchemaBuilder is used for building schemas. The Build method should always
// be called after all collections have been initialized. Initializing new
// collections with the builder after initialization will result in panics.
type SchemaBuilder struct {
schema *Schema
err *multierror.Error
}
// NewSchemaBuilder creates a new schema builder from the provided store key.
// Callers should always call the SchemaBuilder.Build method when they are
// done adding collections to the schema.
func NewSchemaBuilder(service store.KVStoreService) *SchemaBuilder {
return &SchemaBuilder{
schema: &Schema{
storeAccessor: service.OpenKVStore,
collectionsByName: map[string]collection{},
collectionsByPrefix: map[string]collection{},
},
}
}
// Build should be called after all collections that are part of the schema
// have been initialized in order to get a reference to the Schema. It is
// important to check the returned error for any initialization errors.
// The SchemaBuilder CANNOT be used after Build is called - doing so will
// result in panics.
func (s *SchemaBuilder) Build() (Schema, error) {
if s.err != nil {
return Schema{}, s.err
}
// check for any overlapping prefixes
for prefix := range s.schema.collectionsByPrefix {
for prefix2 := range s.schema.collectionsByPrefix {
// don't compare the prefix to itself
if prefix == prefix2 {
continue
}
// if one prefix is the prefix of the other we have an overlap and
// this schema is corrupt
if strings.HasPrefix(prefix, prefix2) {
return Schema{}, fmt.Errorf("schema has overlapping prefixes 0x%x and 0x%x", prefix, prefix2)
}
}
}
if s.schema == nil {
// explicit panic to avoid nil pointer dereference
panic("schema is nil")
}
schema := *s.schema
s.schema = nil // this makes the builder unusable
return schema, nil
}
func (s *SchemaBuilder) addCollection(collection collection) {
prefix := collection.getPrefix()
name := collection.getName()
if _, ok := s.schema.collectionsByPrefix[string(prefix)]; ok {
s.err = multierror.Append(s.err, fmt.Errorf("prefix %v already taken within schema", prefix))
return
}
if _, ok := s.schema.collectionsByName[name]; ok {
s.err = multierror.Append(s.err, fmt.Errorf("name %s already taken within schema", name))
return
}
if !nameRegex.MatchString(name) {
s.err = multierror.Append(s.err, fmt.Errorf("name must match regex %s, got %s", NameRegex, name))
return
}
s.schema.collectionsByPrefix[string(prefix)] = collection
s.schema.collectionsByName[name] = collection
}
// NameRegex is the regular expression that all valid collection names must match.
const NameRegex = "[A-Za-z][A-Za-z0-9_]*"
var nameRegex = regexp.MustCompile("^" + NameRegex + "$")
// Schema specifies a group of collections stored within the storage specified
// by a single store key. All the collections within the schema must have a
// unique binary prefix and human-readable name. Schema will eventually include
@ -47,28 +138,3 @@ func NewSchemaFromAccessor(accessor func(context.Context) store.KVStore) Schema
collectionsByPrefix: map[string]collection{},
}
}
func (s Schema) addCollection(collection collection) {
prefix := collection.getPrefix()
name := collection.getName()
if _, ok := s.collectionsByPrefix[string(prefix)]; ok {
panic(fmt.Errorf("prefix %v already taken within schema", prefix))
}
if _, ok := s.collectionsByName[name]; ok {
panic(fmt.Errorf("name %s already taken within schema", name))
}
if !nameRegex.MatchString(name) {
panic(fmt.Errorf("name must match regex %s, got %s", NameRegex, name))
}
s.collectionsByPrefix[string(prefix)] = collection
s.collectionsByName[name] = collection
}
// NameRegex is the regular expression that all valid collection names must match.
const NameRegex = "[A-Za-z][A-Za-z0-9_]*"
var nameRegex = regexp.MustCompile("^" + NameRegex + "$")

View File

@ -1,8 +1,9 @@
package collections
import (
"github.com/stretchr/testify/require"
"testing"
"github.com/stretchr/testify/require"
)
func TestNameRegex(t *testing.T) {
@ -14,27 +15,58 @@ func TestNameRegex(t *testing.T) {
require.NotRegexp(t, nameRegex, "abc-xyz")
}
func TestAddCollection(t *testing.T) {
require.NotPanics(t, func() {
schema := NewSchema(nil)
NewMap(schema, NewPrefix(1), "abc", Uint64Key, Uint64Value)
NewMap(schema, NewPrefix(2), "def", Uint64Key, Uint64Value)
})
func TestGoodSchema(t *testing.T) {
sk, _ := deps()
schemaBuilder := NewSchemaBuilder(sk)
NewMap(schemaBuilder, NewPrefix(1), "abc", Uint64Key, Uint64Value)
NewMap(schemaBuilder, NewPrefix(2), "def", Uint64Key, Uint64Value)
_, err := schemaBuilder.Build()
require.NoError(t, err)
}
require.PanicsWithError(t, "name must match regex [A-Za-z][A-Za-z0-9_]*, got 123", func() {
schema := NewSchema(nil)
NewMap(schema, NewPrefix(1), "123", Uint64Key, Uint64Value)
})
func TestBadName(t *testing.T) {
sk, _ := deps()
schemaBuilder := NewSchemaBuilder(sk)
NewMap(schemaBuilder, NewPrefix(1), "123", Uint64Key, Uint64Value)
_, err := schemaBuilder.Build()
require.ErrorContains(t, err, "name must match regex")
}
require.PanicsWithError(t, "prefix [1] already taken within schema", func() {
schema := NewSchema(nil)
NewMap(schema, NewPrefix(1), "abc", Uint64Key, Uint64Value)
NewMap(schema, NewPrefix(1), "def", Uint64Key, Uint64Value)
})
func TestDuplicatePrefix(t *testing.T) {
sk, _ := deps()
schemaBuilder := NewSchemaBuilder(sk)
NewMap(schemaBuilder, NewPrefix(1), "abc", Uint64Key, Uint64Value)
NewMap(schemaBuilder, NewPrefix(1), "def", Uint64Key, Uint64Value)
_, err := schemaBuilder.Build()
require.ErrorContains(t, err, "prefix [1] already taken")
}
require.PanicsWithError(t, "name abc already taken within schema", func() {
schema := NewSchema(nil)
NewMap(schema, NewPrefix(1), "abc", Uint64Key, Uint64Value)
NewMap(schema, NewPrefix(2), "abc", Uint64Key, Uint64Value)
func TestDuplicateName(t *testing.T) {
sk, _ := deps()
schemaBuilder := NewSchemaBuilder(sk)
NewMap(schemaBuilder, NewPrefix(1), "abc", Uint64Key, Uint64Value)
NewMap(schemaBuilder, NewPrefix(2), "abc", Uint64Key, Uint64Value)
_, err := schemaBuilder.Build()
require.ErrorContains(t, err, "name abc already taken")
}
func TestOverlappingPrefixes(t *testing.T) {
sk, _ := deps()
schemaBuilder := NewSchemaBuilder(sk)
NewMap(schemaBuilder, NewPrefix("ab"), "ab", Uint64Key, Uint64Value)
NewMap(schemaBuilder, NewPrefix("abc"), "abc", Uint64Key, Uint64Value)
_, err := schemaBuilder.Build()
require.ErrorContains(t, err, "overlapping prefixes")
}
func TestSchemaBuilderCantBeUsedAfterBuild(t *testing.T) {
sk, _ := deps()
schemaBuilder := NewSchemaBuilder(sk)
NewMap(schemaBuilder, NewPrefix(1), "abc", Uint64Key, Uint64Value)
_, err := schemaBuilder.Build()
require.NoError(t, err)
// can't use schema builder safely after calling build
require.Panics(t, func() {
NewMap(schemaBuilder, NewPrefix(2), "def", Uint64Key, Uint64Value)
})
}

View File

@ -13,7 +13,7 @@ type Sequence Item[uint64]
// NewSequence instantiates a new sequence given
// a Schema, a Prefix and humanised name for the sequence.
func NewSequence(schema Schema, prefix Prefix, name string) Sequence {
func NewSequence(schema *SchemaBuilder, prefix Prefix, name string) Sequence {
return (Sequence)(NewItem(schema, prefix, name, Uint64Value))
}

View File

@ -1,13 +1,14 @@
package collections
import (
"github.com/stretchr/testify/require"
"testing"
"github.com/stretchr/testify/require"
)
func TestSequence(t *testing.T) {
sk, ctx := deps()
schema := NewSchema(sk)
schema := NewSchemaBuilder(sk)
seq := NewSequence(schema, NewPrefix(0), "sequence")
// initially the first available number is DefaultSequenceStart
n, err := seq.Peek(ctx)