feat: Add Table-Store (aka ORM) package - AutoUInt64Table and PrimaryKeyTable (#10415)

<!--
The default pull request template is for types feat, fix, or refactor.
For other templates, add one of the following parameters to the url:
- template=docs.md
- template=other.md
-->

## Description

ref: #9237, #9156

This PR is a follow-up of #9751.
It introduces 2 new public table types: `AutoUInt64Table` and `PrimaryKeyTable` based on the parent `table` struct introduced by #9751.

Upcoming work will include:
- multi-key secondary indexes
- iterator and pagination
- import/export genesis

---

### Author Checklist

*All items are required. Please add a note to the item if the item is not applicable and
please add links to any relevant follow up issues.*

I have...

- [x] included the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title
- [ ] added `!` to the type prefix if API or client breaking change
- [x] targeted the correct branch (see [PR Targeting](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#pr-targeting))
- [x] provided a link to the relevant issue or specification
- [ ] followed the guidelines for [building modules](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules)
- [x] included the necessary unit and integration [tests](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#testing)
- [ ] added a changelog entry to `CHANGELOG.md`
- [x] included comments for [documenting Go code](https://blog.golang.org/godoc)
- [x] updated the relevant documentation or specification
- [x] reviewed "Files changed" and left comments if necessary
- [ ] confirmed all CI checks have passed

### Reviewers Checklist

*All items are required. Please add a note if the item is not applicable and please add
your handle next to the items reviewed if you only reviewed selected items.*

I have...

- [ ] confirmed the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title
- [ ] confirmed `!` in the type prefix if API or client breaking change
- [ ] confirmed all author checklist items have been addressed 
- [ ] reviewed state machine logic
- [ ] reviewed API design and naming
- [ ] reviewed documentation is accurate
- [ ] reviewed tests and test coverage
- [ ] manually tested (if applicable)
This commit is contained in:
Marie Gauthier
2021-10-27 12:49:37 +00:00
committed by GitHub
parent 33737f4d8a
commit d8196920aa
20 changed files with 1089 additions and 68 deletions
+8 -4
View File
@@ -17,7 +17,7 @@ require github.com/tendermint/tm-db v0.6.4
require (
github.com/DataDog/zstd v1.4.5 // indirect
github.com/armon/go-metrics v0.3.9 // indirect
github.com/armon/go-metrics v0.3.10 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/btcsuite/btcd v0.22.0-beta // indirect
github.com/cespare/xxhash v1.1.0 // indirect
@@ -27,12 +27,13 @@ require (
github.com/cosmos/iavl v0.17.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgraph-io/badger/v2 v2.2007.2 // indirect
github.com/dgraph-io/ristretto v0.0.3 // indirect
github.com/dgraph-io/ristretto v0.1.0 // 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.1 // indirect
github.com/go-kit/kit v0.10.0 // indirect
github.com/go-logfmt/logfmt v0.5.0 // indirect
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/snappy v0.0.3 // indirect
github.com/google/btree v1.0.0 // indirect
@@ -44,6 +45,7 @@ require (
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/jmhodges/levigo v1.0.0 // indirect
github.com/lazyledger/smt v0.2.1-0.20210709230900-03ea40719554 // indirect
github.com/libp2p/go-buffer-pool v0.0.2 // indirect
github.com/magiconair/properties v1.8.5 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
@@ -56,7 +58,7 @@ require (
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.11.0 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.31.1 // indirect
github.com/prometheus/common v0.32.1 // indirect
github.com/prometheus/procfs v0.6.0 // indirect
github.com/sasha-s/go-deadlock v0.2.1-0.20190427202633-1595213edefa // indirect
github.com/spf13/afero v1.6.0 // indirect
@@ -69,7 +71,7 @@ require (
github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca // indirect
github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c // indirect
github.com/tendermint/go-amino v0.16.0 // indirect
github.com/tendermint/tendermint v0.34.13 // indirect
github.com/tendermint/tendermint v0.34.14 // indirect
go.etcd.io/bbolt v1.3.5 // indirect
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect
golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f // indirect
@@ -87,3 +89,5 @@ replace google.golang.org/grpc => google.golang.org/grpc v1.33.2
replace github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1
replace github.com/cosmos/cosmos-sdk => ../../
replace github.com/cosmos/cosmos-sdk/db => ../../db
+18 -10
View File
@@ -101,8 +101,8 @@ github.com/aristanetworks/goarista v0.0.0-20170210015632-ea17b1a17847/go.mod h1:
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-metrics v0.3.9 h1:O2sNqxBdvq8Eq5xmzljcYzAORli6RWCvEym4cJf9m18=
github.com/armon/go-metrics v0.3.9/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc=
github.com/armon/go-metrics v0.3.10 h1:FR+drcQStOe+32sYyJYyZ7FIdgoGGBnwLl+flodp8Uo=
github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
@@ -162,8 +162,8 @@ github.com/cockroachdb/apd/v2 v2.0.2 h1:weh8u7Cneje73dDh+2tEVLUvyBc89iwepWCD8b80
github.com/cockroachdb/apd/v2 v2.0.2/go.mod h1:DDxRlzC2lo3/vSlmSoS7JkqbbrARPuFOGr0B9pvN3Gw=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
github.com/coinbase/rosetta-sdk-go v0.6.10 h1:rgHD/nHjxLh0lMEdfGDqpTtlvtSBwULqrrZ2qPdNaCM=
github.com/coinbase/rosetta-sdk-go v0.6.10/go.mod h1:J/JFMsfcePrjJZkwQFLh+hJErkAmdm9Iyy3D5Y0LfXo=
github.com/coinbase/rosetta-sdk-go v0.7.0 h1:lmTO/JEpCvZgpbkOITL95rA80CPKb5CtMzLaqF2mCNg=
github.com/coinbase/rosetta-sdk-go v0.7.0/go.mod h1:7nD3oBPIiHqhRprqvMgPoGxe/nyq3yftRmpsy29coWE=
github.com/confio/ics23/go v0.0.0-20200817220745-f173e6211efb/go.mod h1:E45NqnlpxGnpfTWL/xauN7MRwEE28T4Dd4uraToOaKg=
github.com/confio/ics23/go v0.6.3/go.mod h1:E45NqnlpxGnpfTWL/xauN7MRwEE28T4Dd4uraToOaKg=
github.com/confio/ics23/go v0.6.6 h1:pkOy18YxxJ/r0XFDCnrl4Bjv6h4LkBSpLS6F38mrKL8=
@@ -217,9 +217,11 @@ github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f/go.mod h1:xH/i4TFM
github.com/dgraph-io/badger/v2 v2.2007.1/go.mod h1:26P/7fbL4kUZVEVKLAKXkBXKOydDmM2p1e+NhhnBCAE=
github.com/dgraph-io/badger/v2 v2.2007.2 h1:EjjK0KqwaFMlPin1ajhP943VPENHJdEz1KLIegjaI3k=
github.com/dgraph-io/badger/v2 v2.2007.2/go.mod h1:26P/7fbL4kUZVEVKLAKXkBXKOydDmM2p1e+NhhnBCAE=
github.com/dgraph-io/badger/v3 v3.2103.1/go.mod h1:dULbq6ehJ5K0cGW/1TQ9iSfUk0gbSiToDWmWmTsJ53E=
github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
github.com/dgraph-io/ristretto v0.0.3 h1:jh22xisGBjrEVnRZ1DVTpBVQm0Xndu8sMl0CWDzSIBI=
github.com/dgraph-io/ristretto v0.0.3/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/LuerPI=
github.com/dgraph-io/ristretto v0.1.0/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu6v9Vcb3Q9ug=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y=
@@ -300,6 +302,7 @@ github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/gateway v1.1.0 h1:u0SuhL9+Il+UbjM9VIE3ntfRujKbvVpFvNB4HbjeVQ0=
github.com/gogo/gateway v1.1.0/go.mod h1:S7rR8FRQyG3QFESeSv4l2WnsyzlCLG0CzBbUUo/mbic=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -342,6 +345,7 @@ github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/flatbuffers v1.12.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
@@ -480,7 +484,7 @@ github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458/go.mod h1:QPH
github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e/go.mod h1:G1CVv03EnqU1wYL2dFwXxW2An0az9JTl/ZsqXQeBlkU=
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jhump/protoreflect v1.10.0/go.mod h1:7GcYQDdMU/O/BBrl/cX6PNHpXh6cenjd8pneu5yW7Tg=
github.com/jhump/protoreflect v1.10.1/go.mod h1:7GcYQDdMU/O/BBrl/cX6PNHpXh6cenjd8pneu5yW7Tg=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U=
github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ=
@@ -506,8 +510,9 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.11.7 h1:0hzRabrMN4tSTvMfnL3SCv1ZGeAP23ynzodBgaHeMeg=
github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.12.3 h1:G5AfA94pHPysR56qqrkO2pxEexdDzrpFJ6yt/VqWxVU=
github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
@@ -520,6 +525,8 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lazyledger/smt v0.2.1-0.20210709230900-03ea40719554 h1:nDOkLO7klmnEw1s4AyKt1Arvpgyh33uj1JmkYlJaDsk=
github.com/lazyledger/smt v0.2.1-0.20210709230900-03ea40719554/go.mod h1:9+Pb2/tg1PvEgW7aFx4bFhDE4bvbI03zuJ8kb7nJ9Jc=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
@@ -689,8 +696,8 @@ github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB8
github.com/prometheus/common v0.14.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s=
github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/common v0.31.1 h1:d18hG4PkHnNAKNMOmFuXFaiY8Us0nird/2m60uS1AMs=
github.com/prometheus/common v0.31.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4=
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
@@ -809,8 +816,9 @@ github.com/tendermint/go-amino v0.16.0/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoM
github.com/tendermint/tendermint v0.34.0-rc4/go.mod h1:yotsojf2C1QBOw4dZrTcxbyxmPUrT4hNuOQWX9XUwB4=
github.com/tendermint/tendermint v0.34.0-rc6/go.mod h1:ugzyZO5foutZImv0Iyx/gOFCX6mjJTgbLHTwi17VDVg=
github.com/tendermint/tendermint v0.34.0/go.mod h1:Aj3PIipBFSNO21r+Lq3TtzQ+uKESxkbA3yo/INM4QwQ=
github.com/tendermint/tendermint v0.34.13 h1:fu+tsHudbOr5PvepjH0q47Jae59hQAvn3IqAHv2EbC8=
github.com/tendermint/tendermint v0.34.13/go.mod h1:6RVVRBqwtKhA+H59APKumO+B7Nye4QXSFc6+TYxAxCI=
github.com/tendermint/tendermint v0.34.14 h1:GCXmlS8Bqd2Ix3TQCpwYLUNHe+Y+QyJsm5YE+S/FkPo=
github.com/tendermint/tendermint v0.34.14/go.mod h1:FrwVm3TvsVicI9Z7FlucHV6Znfd5KBc/Lpp69cCwtk0=
github.com/tendermint/tm-db v0.6.2/go.mod h1:GYtQ67SUvATOcoY8/+x6ylk8Qo02BQyLrAs+yAcLvGI=
github.com/tendermint/tm-db v0.6.3/go.mod h1:lfA1dL9/Y/Y8wwyPp2NMLyn5P5Ptr/gvDFNWtrCWSf8=
github.com/tendermint/tm-db v0.6.4 h1:3N2jlnYQkXNQclQwd/eKV/NzlqPlfK21cpRRIx80XXQ=
+75
View File
@@ -0,0 +1,75 @@
package orm
import (
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
)
var _ Indexable = &AutoUInt64Table{}
// AutoUInt64Table is the table type with an auto incrementing ID.
type AutoUInt64Table struct {
*table
seq Sequence
}
// NewAutoUInt64Table creates a new AutoUInt64Table.
func NewAutoUInt64Table(prefixData [2]byte, prefixSeq byte, model codec.ProtoMarshaler, cdc codec.Codec) (*AutoUInt64Table, error) {
table, err := newTable(prefixData, model, cdc)
if err != nil {
return nil, err
}
return &AutoUInt64Table{
table: table,
seq: NewSequence(prefixSeq),
}, nil
}
// Create a new persistent object with an auto generated uint64 primary key. The
// key is returned.
//
// Create iterates through the registered callbacks that may add secondary index
// keys.
func (a AutoUInt64Table) Create(store sdk.KVStore, obj codec.ProtoMarshaler) (uint64, error) {
autoIncID := a.seq.NextVal(store)
err := a.table.Create(store, EncodeSequence(autoIncID), obj)
if err != nil {
return 0, err
}
return autoIncID, nil
}
// Update updates the given object under the rowID key. It expects the key to
// exists already and fails with an `ErrNotFound` otherwise. Any caller must
// therefore make sure that this contract is fulfilled. Parameters must not be
// nil.
//
// Update iterates through the registered callbacks that may add or remove
// secondary index keys.
func (a AutoUInt64Table) Update(store sdk.KVStore, rowID uint64, newValue codec.ProtoMarshaler) error {
return a.table.Update(store, EncodeSequence(rowID), newValue)
}
// Delete removes the object under the rowID key. It expects the key to exists already
// and fails with a `ErrNotFound` otherwise. Any caller must therefore make sure that this contract
// is fulfilled.
//
// Delete iterates though the registered callbacks and removes secondary index keys by them.
func (a AutoUInt64Table) Delete(store sdk.KVStore, rowID uint64) error {
return a.table.Delete(store, EncodeSequence(rowID))
}
// Has checks if a rowID exists.
func (a AutoUInt64Table) Has(store sdk.KVStore, rowID uint64) bool {
return a.table.Has(store, EncodeSequence(rowID))
}
// GetOne load the object persisted for the given RowID into the dest parameter.
// If none exists `ErrNotFound` is returned instead. Parameters must not be nil.
func (a AutoUInt64Table) GetOne(store sdk.KVStore, rowID uint64, dest codec.ProtoMarshaler) (RowID, error) {
rawRowID := EncodeSequence(rowID)
if err := a.table.GetOne(store, rawRowID, dest); err != nil {
return nil, err
}
return rawRowID, nil
}
+35
View File
@@ -0,0 +1,35 @@
package orm
import (
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/testutil/testdata"
)
type TestKeeper struct {
autoUInt64Table *AutoUInt64Table
primaryKeyTable *PrimaryKeyTable
}
var (
AutoUInt64TableTablePrefix [2]byte = [2]byte{0x0}
PrimaryKeyTablePrefix [2]byte = [2]byte{0x1}
AutoUInt64TableSeqPrefix byte = 0x2
)
func NewTestKeeper(cdc codec.Codec) TestKeeper {
k := TestKeeper{}
autoUInt64Table, err := NewAutoUInt64Table(AutoUInt64TableTablePrefix, AutoUInt64TableSeqPrefix, &testdata.TableModel{}, cdc)
if err != nil {
panic(err.Error())
}
k.autoUInt64Table = autoUInt64Table
primaryKeyTable, err := NewPrimaryKeyTable(PrimaryKeyTablePrefix, &testdata.TableModel{}, cdc)
if err != nil {
panic(err.Error())
}
k.primaryKeyTable = primaryKeyTable
return k
}
+18
View File
@@ -0,0 +1,18 @@
package orm
import (
"pgregory.net/rapid"
"github.com/cosmos/cosmos-sdk/testutil/testdata"
)
// genTableModel generates a new table model. At the moment it doesn't
// generate empty strings for Name.
var genTableModel = rapid.Custom(func(t *rapid.T) *testdata.TableModel {
return &testdata.TableModel{
Id: rapid.Uint64().Draw(t, "id").(uint64),
Name: rapid.StringN(1, 100, 150).Draw(t, "name").(string),
Number: rapid.Uint64().Draw(t, "number ").(uint64),
Metadata: []byte(rapid.StringN(1, 100, 150).Draw(t, "metadata").(string)),
}
})
+75
View File
@@ -0,0 +1,75 @@
package orm
import (
"fmt"
"github.com/cosmos/cosmos-sdk/types/errors"
)
// MaxBytesLen is the maximum allowed length for a key part of type []byte
const MaxBytesLen = 255
// buildKeyFromParts encodes and concatenates primary key and index parts.
// They can be []byte, string, and integer types. The function will return
// an error if there is a part of any other type.
// Key parts, except the last part, follow these rules:
// - []byte is encoded with a single byte length prefix
// - strings are null-terminated
// - integers are encoded using 8 byte big endian.
func buildKeyFromParts(parts []interface{}) ([]byte, error) {
bytesSlice := make([][]byte, len(parts))
totalLen := 0
var err error
for i, part := range parts {
bytesSlice[i], err = keyPartBytes(part, len(parts) > 1 && i == len(parts)-1)
if err != nil {
return nil, err
}
totalLen += len(bytesSlice[i])
}
key := make([]byte, 0, totalLen)
for _, bs := range bytesSlice {
key = append(key, bs...)
}
return key, nil
}
func keyPartBytes(part interface{}, last bool) ([]byte, error) {
switch v := part.(type) {
case []byte:
if last || len(v) == 0 {
return v, nil
}
return AddLengthPrefix(v), nil
case string:
if last || len(v) == 0 {
return []byte(v), nil
}
return NullTerminatedBytes(v), nil
case uint64:
return EncodeSequence(v), nil
default:
return nil, fmt.Errorf("type %T not allowed as key part", v)
}
}
// AddLengthPrefix prefixes the byte array with its length as 8 bytes. The function will panic
// if the bytes length is bigger than 255.
func AddLengthPrefix(bytes []byte) []byte {
byteLen := len(bytes)
if byteLen > MaxBytesLen {
panic(errors.Wrap(errors.ErrORMKeyMaxLength, "Cannot create key part with an []byte of length greater than 255 bytes. Try again with a smaller []byte."))
}
prefixedBytes := make([]byte, 1+len(bytes))
copy(prefixedBytes, []byte{uint8(byteLen)})
copy(prefixedBytes[1:], bytes)
return prefixedBytes
}
// NullTerminatedBytes converts string to byte array and null terminate it
func NullTerminatedBytes(s string) []byte {
bytes := make([]byte, len(s)+1)
copy(bytes, s)
return bytes
}
+46
View File
@@ -0,0 +1,46 @@
package orm
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestAddLengthPrefix(t *testing.T) {
tcs := []struct {
name string
in []byte
expected []byte
}{
{"empty", []byte{}, []byte{0}},
{"nil", nil, []byte{0}},
{"some data", []byte{0, 1, 100, 200}, []byte{4, 0, 1, 100, 200}},
}
for _, tc := range tcs {
t.Run(tc.name, func(t *testing.T) {
out := AddLengthPrefix(tc.in)
require.Equal(t, tc.expected, out)
})
}
require.Panics(t, func() {
AddLengthPrefix(make([]byte, 256))
})
}
func TestNullTerminatedBytes(t *testing.T) {
tcs := []struct {
name string
in string
expected []byte
}{
{"empty", "", []byte{0}},
{"some data", "abc", []byte{0x61, 0x62, 0x63, 0}},
}
for _, tc := range tcs {
t.Run(tc.name, func(t *testing.T) {
out := NullTerminatedBytes(tc.in)
require.Equal(t, tc.expected, out)
})
}
}
+207
View File
@@ -0,0 +1,207 @@
package orm
import (
"encoding/binary"
"fmt"
"testing"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/codec/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/errors"
"github.com/stretchr/testify/require"
"github.com/cosmos/cosmos-sdk/testutil/testdata"
)
func TestKeeperEndToEndWithAutoUInt64Table(t *testing.T) {
interfaceRegistry := types.NewInterfaceRegistry()
cdc := codec.NewProtoCodec(interfaceRegistry)
ctx := NewMockContext()
store := ctx.KVStore(sdk.NewKVStoreKey("test"))
k := NewTestKeeper(cdc)
tm := testdata.TableModel{
Id: 1,
Name: "name",
Number: 123,
Metadata: []byte("metadata"),
}
// when stored
rowID, err := k.autoUInt64Table.Create(store, &tm)
require.NoError(t, err)
// then we should find it
exists := k.autoUInt64Table.Has(store, rowID)
require.True(t, exists)
// and load it
var loaded testdata.TableModel
binKey, err := k.autoUInt64Table.GetOne(store, rowID, &loaded)
require.NoError(t, err)
require.Equal(t, rowID, binary.BigEndian.Uint64(binKey))
require.Equal(t, tm, loaded)
// when updated
tm.Name = "new name"
err = k.autoUInt64Table.Update(store, rowID, &tm)
require.NoError(t, err)
binKey, err = k.autoUInt64Table.GetOne(store, rowID, &loaded)
require.NoError(t, err)
require.Equal(t, rowID, binary.BigEndian.Uint64(binKey))
require.Equal(t, tm, loaded)
// when deleted
err = k.autoUInt64Table.Delete(store, rowID)
require.NoError(t, err)
exists = k.autoUInt64Table.Has(store, rowID)
require.False(t, exists)
}
func TestKeeperEndToEndWithPrimaryKeyTable(t *testing.T) {
interfaceRegistry := types.NewInterfaceRegistry()
cdc := codec.NewProtoCodec(interfaceRegistry)
ctx := NewMockContext()
store := ctx.KVStore(sdk.NewKVStoreKey("test"))
k := NewTestKeeper(cdc)
tm := testdata.TableModel{
Id: 1,
Name: "name",
Number: 123,
Metadata: []byte("metadata"),
}
// when stored
err := k.primaryKeyTable.Create(store, &tm)
require.NoError(t, err)
// then we should find it by primary key
primaryKey := PrimaryKey(&tm)
exists := k.primaryKeyTable.Has(store, primaryKey)
require.True(t, exists)
// and load it by primary key
var loaded testdata.TableModel
err = k.primaryKeyTable.GetOne(store, primaryKey, &loaded)
require.NoError(t, err)
// then values should match expectations
require.Equal(t, tm, loaded)
// and when we create another entry with the same primary key
err = k.primaryKeyTable.Create(store, &tm)
// then it should fail as the primary key must be unique
require.True(t, errors.ErrORMUniqueConstraint.Is(err), err)
// and when entity updated with new primary key
updatedMember := &testdata.TableModel{
Id: 2,
Name: tm.Name,
Number: tm.Number,
Metadata: tm.Metadata,
}
// then it should fail as the primary key is immutable
err = k.primaryKeyTable.Update(store, updatedMember)
require.Error(t, err)
// and when entity updated with non primary key attribute modified
updatedMember = &testdata.TableModel{
Id: 1,
Name: "new name",
Number: tm.Number,
Metadata: tm.Metadata,
}
// then it should not fail
err = k.primaryKeyTable.Update(store, updatedMember)
require.NoError(t, err)
// and when entity deleted
err = k.primaryKeyTable.Delete(store, &tm)
require.NoError(t, err)
exists = k.primaryKeyTable.Has(store, primaryKey)
require.False(t, exists)
}
func TestGasCostsPrimaryKeyTable(t *testing.T) {
interfaceRegistry := types.NewInterfaceRegistry()
cdc := codec.NewProtoCodec(interfaceRegistry)
ctx := NewMockContext()
store := ctx.KVStore(sdk.NewKVStoreKey("test"))
k := NewTestKeeper(cdc)
tm := testdata.TableModel{
Id: 1,
Name: "name",
Number: 123,
Metadata: []byte("metadata"),
}
rowID, err := k.autoUInt64Table.Create(store, &tm)
require.NoError(t, err)
require.Equal(t, uint64(1), rowID)
gCtx := NewGasCountingMockContext()
err = k.primaryKeyTable.Create(gCtx.KVStore(store), &tm)
require.NoError(t, err)
t.Logf("gas consumed on create: %d", gCtx.GasConsumed())
// get by primary key
gCtx.ResetGasMeter()
var loaded testdata.TableModel
err = k.primaryKeyTable.GetOne(gCtx.KVStore(store), PrimaryKey(&tm), &loaded)
require.NoError(t, err)
t.Logf("gas consumed on get by primary key: %d", gCtx.GasConsumed())
// delete
gCtx.ResetGasMeter()
err = k.primaryKeyTable.Delete(gCtx.KVStore(store), &tm)
require.NoError(t, err)
t.Logf("gas consumed on delete by primary key: %d", gCtx.GasConsumed())
// with 3 elements
var tms []testdata.TableModel
for i := 1; i < 4; i++ {
gCtx.ResetGasMeter()
tm := testdata.TableModel{
Id: uint64(i),
Name: fmt.Sprintf("name%d", i),
Number: 123,
Metadata: []byte("metadata"),
}
err = k.primaryKeyTable.Create(gCtx.KVStore(store), &tm)
require.NoError(t, err)
t.Logf("%d: gas consumed on create: %d", i, gCtx.GasConsumed())
tms = append(tms, tm)
}
for i := 1; i < 4; i++ {
gCtx.ResetGasMeter()
tm := testdata.TableModel{
Id: uint64(i),
Name: fmt.Sprintf("name%d", i),
Number: 123,
Metadata: []byte("metadata"),
}
err = k.primaryKeyTable.GetOne(gCtx.KVStore(store), PrimaryKey(&tm), &loaded)
require.NoError(t, err)
t.Logf("%d: gas consumed on get by primary key: %d", i, gCtx.GasConsumed())
}
// delete
for i, m := range tms {
gCtx.ResetGasMeter()
err = k.primaryKeyTable.Delete(gCtx.KVStore(store), &m)
require.NoError(t, err)
t.Logf("%d: gas consumed on delete: %d", i, gCtx.GasConsumed())
}
}
+113
View File
@@ -0,0 +1,113 @@
package orm
import (
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
)
var _ Indexable = &PrimaryKeyTable{}
// PrimaryKeyTable provides simpler object style orm methods without passing database RowIDs.
// Entries are persisted and loaded with a reference to their unique primary key.
type PrimaryKeyTable struct {
*table
}
// NewPrimaryKeyTable creates a new PrimaryKeyTable.
func NewPrimaryKeyTable(prefixData [2]byte, model PrimaryKeyed, cdc codec.Codec) (*PrimaryKeyTable, error) {
table, err := newTable(prefixData, model, cdc)
if err != nil {
return nil, err
}
return &PrimaryKeyTable{
table: table,
}, nil
}
// PrimaryKeyed defines an object type that is aware of its immutable primary key.
type PrimaryKeyed interface {
// PrimaryKeyFields returns the fields of the object that will make up
// the primary key. The PrimaryKey function will encode and concatenate
// the fields to build the primary key.
//
// PrimaryKey parts can be []byte, string, and integer types. []byte is
// encoded with a length prefix, strings are null-terminated, and
// integers are encoded using 8 byte big endian.
//
// IMPORTANT: []byte parts are encoded with a single byte length prefix,
// so cannot be longer than 255 bytes.
PrimaryKeyFields() []interface{}
codec.ProtoMarshaler
}
// PrimaryKey returns the immutable and serialized primary key of this object.
// The primary key has to be unique within it's domain so that not two with same
// value can exist in the same table. This means PrimaryKeyFields() has to
// return a unique value for each object.
func PrimaryKey(obj PrimaryKeyed) []byte {
fields := obj.PrimaryKeyFields()
key, err := buildKeyFromParts(fields)
if err != nil {
panic(err)
}
return key
}
// Create persists the given object under their primary key. It checks if the
// key already exists and may return an `ErrUniqueConstraint`.
//
// Create iterates through the registered callbacks that may add secondary
// index keys.
func (a PrimaryKeyTable) Create(store sdk.KVStore, obj PrimaryKeyed) error {
rowID := PrimaryKey(obj)
return a.table.Create(store, rowID, obj)
}
// Update updates the given object under the primary key. It expects the key to
// exists already and fails with an `ErrNotFound` otherwise. Any caller must
// therefore make sure that this contract is fulfilled. Parameters must not be
// nil.
//
// Update iterates through the registered callbacks that may add or remove
// secondary index keys.
func (a PrimaryKeyTable) Update(store sdk.KVStore, newValue PrimaryKeyed) error {
return a.table.Update(store, PrimaryKey(newValue), newValue)
}
// Set persists the given object under the rowID key. It does not check if the
// key already exists and overwrites the value if it does.
//
// Set iterates through the registered callbacks that may add secondary index
// keys.
func (a PrimaryKeyTable) Set(store sdk.KVStore, newValue PrimaryKeyed) error {
return a.table.Set(store, PrimaryKey(newValue), newValue)
}
// Delete removes the object. It expects the primary key to exists already and
// fails with a `ErrNotFound` otherwise. Any caller must therefore make sure
// that this contract is fulfilled.
//
// Delete iterates through the registered callbacks that remove secondary index
// keys.
func (a PrimaryKeyTable) Delete(store sdk.KVStore, obj PrimaryKeyed) error {
return a.table.Delete(store, PrimaryKey(obj))
}
// Has checks if a key exists. Always returns false on nil or empty key.
func (a PrimaryKeyTable) Has(store sdk.KVStore, primaryKey RowID) bool {
return a.table.Has(store, primaryKey)
}
// Contains returns true when an object with same type and primary key is persisted in this table.
func (a PrimaryKeyTable) Contains(store sdk.KVStore, obj PrimaryKeyed) bool {
if err := assertCorrectType(a.table.model, obj); err != nil {
return false
}
return a.table.Has(store, PrimaryKey(obj))
}
// GetOne loads the object persisted for the given primary Key into the dest parameter.
// If none exists `ErrNotFound` is returned instead. Parameters must not be nil.
func (a PrimaryKeyTable) GetOne(store sdk.KVStore, primKey RowID, dest codec.ProtoMarshaler) error {
return a.table.GetOne(store, primKey, dest)
}
@@ -0,0 +1,188 @@
package orm
import (
"testing"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/codec/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/require"
"pgregory.net/rapid"
"github.com/cosmos/cosmos-sdk/testutil/testdata"
)
func TestPrimaryKeyTable(t *testing.T) {
rapid.Check(t, rapid.Run(&primaryKeyMachine{}))
}
// primaryKeyMachine is a state machine model of the PrimaryKeyTable. The state
// is modelled as a map of strings to TableModels.
type primaryKeyMachine struct {
store sdk.KVStore
table *PrimaryKeyTable
state map[string]*testdata.TableModel
}
// stateKeys gets all the keys in the model map
func (m *primaryKeyMachine) stateKeys() []string {
keys := make([]string, len(m.state))
i := 0
for k := range m.state {
keys[i] = k
i++
}
return keys
}
// Generate a TableModel that has a 50% chance of being a part of the existing
// state
func (m *primaryKeyMachine) genTableModel() *rapid.Generator {
genStateTableModel := rapid.Custom(func(t *rapid.T) *testdata.TableModel {
pk := rapid.SampledFrom(m.stateKeys()).Draw(t, "key").(string)
return m.state[pk]
})
if len(m.stateKeys()) == 0 {
return genTableModel
} else {
return rapid.OneOf(genTableModel, genStateTableModel)
}
}
// Init creates a new instance of the state machine model by building the real
// table and making the empty model map
func (m *primaryKeyMachine) Init(t *rapid.T) {
// Create context
ctx := NewMockContext()
m.store = ctx.KVStore(sdk.NewKVStoreKey("test"))
// Create primary key table
interfaceRegistry := types.NewInterfaceRegistry()
cdc := codec.NewProtoCodec(interfaceRegistry)
table, err := NewPrimaryKeyTable(
[2]byte{0x1},
&testdata.TableModel{},
cdc,
)
require.NoError(t, err)
m.table = table
// Create model state
m.state = make(map[string]*testdata.TableModel)
}
// Check that the real values match the state values.
func (m *primaryKeyMachine) Check(t *rapid.T) {
for i := range m.state {
has := m.table.Has(m.store, []byte(i))
require.Equal(t, true, has)
}
}
// Create is one of the model commands. It adds an object to the table, creating
// an error if it already exists.
func (m *primaryKeyMachine) Create(t *rapid.T) {
g := genTableModel.Draw(t, "g").(*testdata.TableModel)
pk := string(PrimaryKey(g))
t.Logf("pk: %v", pk)
t.Logf("m.state: %v", m.state)
err := m.table.Create(m.store, g)
if m.state[pk] != nil {
require.Error(t, err)
} else {
require.NoError(t, err)
m.state[pk] = g
}
}
// Update is one of the model commands. It updates the value at a given primary
// key and fails if that primary key doesn't already exist in the table.
func (m *primaryKeyMachine) Update(t *rapid.T) {
tm := m.genTableModel().Draw(t, "tm").(*testdata.TableModel)
newName := rapid.StringN(1, 100, 150).Draw(t, "newName").(string)
tm.Name = newName
// Perform the real Update
err := m.table.Update(m.store, tm)
if m.state[string(PrimaryKey(tm))] == nil {
// If there's no value in the model, we expect an error
require.Error(t, err)
} else {
// If we have a value in the model, expect no error
require.NoError(t, err)
// Update the model with the new value
m.state[string(PrimaryKey(tm))] = tm
}
}
// Set is one of the model commands. It sets the value at a key in the table
// whether it exists or not.
func (m *primaryKeyMachine) Set(t *rapid.T) {
g := genTableModel.Draw(t, "g").(*testdata.TableModel)
pk := string(PrimaryKey(g))
err := m.table.Set(m.store, g)
require.NoError(t, err)
m.state[pk] = g
}
// Delete is one of the model commands. It removes the object with the given
// primary key from the table and returns an error if that primary key doesn't
// already exist in the table.
func (m *primaryKeyMachine) Delete(t *rapid.T) {
tm := m.genTableModel().Draw(t, "tm").(*testdata.TableModel)
// Perform the real Delete
err := m.table.Delete(m.store, tm)
if m.state[string(PrimaryKey(tm))] == nil {
// If there's no value in the model, we expect an error
require.Error(t, err)
} else {
// If we have a value in the model, expect no error
require.NoError(t, err)
// Delete the value from the model
delete(m.state, string(PrimaryKey(tm)))
}
}
// Has is one of the model commands. It checks whether a key already exists in
// the table.
func (m *primaryKeyMachine) Has(t *rapid.T) {
pk := PrimaryKey(m.genTableModel().Draw(t, "g").(*testdata.TableModel))
realHas := m.table.Has(m.store, pk)
modelHas := m.state[string(pk)] != nil
require.Equal(t, realHas, modelHas)
}
// GetOne is one of the model commands. It fetches an object from the table by
// its primary key and returns an error if that primary key isn't in the table.
func (m *primaryKeyMachine) GetOne(t *rapid.T) {
pk := PrimaryKey(m.genTableModel().Draw(t, "tm").(*testdata.TableModel))
var tm testdata.TableModel
err := m.table.GetOne(m.store, pk, &tm)
t.Logf("tm: %v", tm)
if m.state[string(pk)] == nil {
require.Error(t, err)
} else {
require.NoError(t, err)
require.Equal(t, *m.state[string(pk)], tm)
}
}
+67
View File
@@ -0,0 +1,67 @@
package orm
import (
"testing"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/codec/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/cosmos/cosmos-sdk/testutil/testdata"
)
func TestContains(t *testing.T) {
interfaceRegistry := types.NewInterfaceRegistry()
cdc := codec.NewProtoCodec(interfaceRegistry)
ctx := NewMockContext()
store := ctx.KVStore(sdk.NewKVStoreKey("test"))
tb, err := NewPrimaryKeyTable([2]byte{0x1}, &testdata.TableModel{}, cdc)
require.NoError(t, err)
obj := testdata.TableModel{
Id: 1,
Name: "Some name",
}
err = tb.Create(store, &obj)
require.NoError(t, err)
specs := map[string]struct {
src PrimaryKeyed
exp bool
}{
"same object": {src: &obj, exp: true},
"clone": {
src: &testdata.TableModel{
Id: 1,
Name: "Some name",
},
exp: true,
},
"different primary key": {
src: &testdata.TableModel{
Id: 2,
Name: "Some name",
},
exp: false,
},
"different type, same key": {
src: mockPrimaryKeyed{&obj},
exp: false,
},
}
for msg, spec := range specs {
t.Run(msg, func(t *testing.T) {
got := tb.Contains(store, spec.src)
assert.Equal(t, spec.exp, got)
})
}
}
type mockPrimaryKeyed struct {
*testdata.TableModel
}
+38
View File
@@ -0,0 +1,38 @@
# Table
A table can be built given a `codec.ProtoMarshaler` model type, a prefix to access the underlying prefix store used to store table data as well as a `Codec` for marshalling/unmarshalling.
+++ https://github.com/cosmos/cosmos-sdk/blob/9f78f16ae75cc42fc5fe636bde18a453ba74831f/x/group/internal/orm/table.go#L24-L30
In the prefix store, entities should be stored by an unique identifier called `RowID` which can be based either on an `uint64` auto-increment counter, string or dynamic size bytes.
Regular CRUD operations can be performed on a table, these methods take a `sdk.KVStore` as parameter to get the table prefix store.
The `table` struct does not:
- enforce uniqueness of the `RowID`
- enforce prefix uniqueness of keys, i.e. not allowing one key to be a prefix
of another
- optimize Gas usage conditions
The `table` struct is private, so that we only have custom tables built on top of it, that do satisfy these requirements.
## AutoUInt64Table
`AutoUInt64Table` is a table type with an auto incrementing `uint64` ID.
+++ https://github.com/cosmos/cosmos-sdk/blob/9f78f16ae75cc42fc5fe636bde18a453ba74831f/x/group/internal/orm/auto_uint64.go#L11-L14
It's based on the `Sequence` struct which is a persistent unique key generator based on a counter encoded using 8 byte big endian.
## PrimaryKeyTable
`PrimaryKeyTable` provides simpler object style orm methods where are persisted and loaded with a reference to their unique primary key.
The model provided for creating a `PrimaryKeyTable` should implement the `PrimaryKeyed` interface:
+++ https://github.com/cosmos/cosmos-sdk/blob/9f78f16ae75cc42fc5fe636bde18a453ba74831f/x/group/internal/orm/primary_key.go#L28-L41
`PrimaryKeyFields()` method returns the list of key parts for a given object.
The primary key parts can be []byte, string, and `uint64` types.
Key parts, except the last part, follow these rules:
- []byte is encoded with a single byte length prefix
- strings are null-terminated
- `uint64` are encoded using 8 byte big endian.
+5 -22
View File
@@ -1,26 +1,9 @@
## Abstract
# Abstract
The orm package provides a framework for creating relational database tables with primary and secondary keys.
### Tables
## Contents
```go
type table struct {
model reflect.Type
prefix [2]byte
afterSet []AfterSetInterceptor
afterDelete []AfterDeleteInterceptor
cdc codec.Codec
}
```
A table can be built given a `codec.ProtoMarshaler` model type, a prefix to access the underlying prefix store used to store table data as well as a `Codec` for marshalling/unmarshalling.
In the prefix store, entities should be stored by an unique identifier called `RowID` which can be based either on an `uint64` auto-increment counter, string or dynamic size bytes.
Regular CRUD operations can be performed on a table, these methods take a `sdk.KVStore` as parameter to get the table prefix store.
The `table` struct does not:
- enforce uniqueness of the `RowID`
- enforce prefix uniqueness of keys, i.e. not allowing one key to be a prefix
of another
- optimize Gas usage conditions
The `table` struct is private, so that we only have custom tables built on top of it, that do satisfy these requirements.
1. **[Table](01_table.md)**
- [AutoUInt64Table](01_table.md#autouint64table)
- [PrimaryKeyTable](01_table.md#primarykeytable)
+70
View File
@@ -1,7 +1,10 @@
package orm
import (
"fmt"
"github.com/cosmos/cosmos-sdk/store"
"github.com/cosmos/cosmos-sdk/store/gaskv"
"github.com/cosmos/cosmos-sdk/store/types"
storetypes "github.com/cosmos/cosmos-sdk/store/types"
sdk "github.com/cosmos/cosmos-sdk/types"
@@ -31,3 +34,70 @@ func (m MockContext) KVStore(key storetypes.StoreKey) sdk.KVStore {
}
return m.store.GetCommitKVStore(key)
}
type debuggingGasMeter struct {
g types.GasMeter
}
func (d debuggingGasMeter) GasConsumed() types.Gas {
return d.g.GasConsumed()
}
func (d debuggingGasMeter) GasRemaining() types.Gas {
return d.g.GasRemaining()
}
func (d debuggingGasMeter) GasConsumedToLimit() types.Gas {
return d.g.GasConsumedToLimit()
}
func (d debuggingGasMeter) RefundGas(amount uint64, descriptor string) {
d.g.RefundGas(amount, descriptor)
}
func (d debuggingGasMeter) Limit() types.Gas {
return d.g.Limit()
}
func (d debuggingGasMeter) ConsumeGas(amount types.Gas, descriptor string) {
fmt.Printf("++ Consuming gas: %q :%d\n", descriptor, amount)
d.g.ConsumeGas(amount, descriptor)
}
func (d debuggingGasMeter) IsPastLimit() bool {
return d.g.IsPastLimit()
}
func (d debuggingGasMeter) IsOutOfGas() bool {
return d.g.IsOutOfGas()
}
func (d debuggingGasMeter) String() string {
return d.g.String()
}
type GasCountingMockContext struct {
GasMeter sdk.GasMeter
}
func NewGasCountingMockContext() *GasCountingMockContext {
return &GasCountingMockContext{
GasMeter: &debuggingGasMeter{sdk.NewInfiniteGasMeter()},
}
}
func (g GasCountingMockContext) KVStore(store sdk.KVStore) sdk.KVStore {
return gaskv.NewStore(store, g.GasMeter, types.KVGasConfig())
}
func (g GasCountingMockContext) GasConsumed() types.Gas {
return g.GasMeter.GasConsumed()
}
func (g GasCountingMockContext) GasRemaining() types.Gas {
return g.GasMeter.GasRemaining()
}
func (g *GasCountingMockContext) ResetGasMeter() {
g.GasMeter = sdk.NewInfiniteGasMeter()
}
+1 -1
View File
@@ -32,7 +32,7 @@ type Iterator interface {
// LoadNext loads the next value in the sequence into the pointer passed as dest and returns the key. If there
// are no more items the ErrORMIteratorDone error is returned
// The key is the rowID.
LoadNext(dest codec.ProtoMarshaler) (RowID, error)
LoadNext(store sdk.KVStore, dest codec.ProtoMarshaler) (RowID, error)
// Close releases the iterator and should be called at the end of iteration
io.Closer
}