├── .github ├── CODEOWNERS ├── dependabot.yml └── workflows │ ├── codeql-analysis.yml │ ├── docker.yml │ ├── lint.yml │ └── test.yml ├── .gitignore ├── .gitpod.yml ├── .golangci.yml ├── .vscode └── settings.json ├── CHANGELOG.md ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── RELEASING.md ├── backend_test.go ├── common_test.go ├── db.go ├── db_test.go ├── flake.lock ├── flake.nix ├── go.mod ├── go.sum ├── goleveldb.go ├── goleveldb_batch.go ├── goleveldb_iterator.go ├── goleveldb_test.go ├── memdb.go ├── memdb_batch.go ├── memdb_iterator.go ├── memdb_test.go ├── pebble.go ├── pebble_test.go ├── prefixdb.go ├── prefixdb_batch.go ├── prefixdb_iterator.go ├── prefixdb_test.go ├── rocksdb.go ├── rocksdb_batch.go ├── rocksdb_iterator.go ├── rocksdb_test.go ├── test_helpers.go ├── types.go ├── util.go └── util_test.go /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # CODEOWNERS: https://help.github.com/articles/about-codeowners/ 2 | 3 | * @cosmos/sdk-core-dev 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | 2 | version: 2 3 | updates: 4 | - package-ecosystem: github-actions 5 | directory: "/" 6 | schedule: 7 | interval: daily 8 | open-pull-requests-limit: 10 9 | - package-ecosystem: gomod 10 | directory: "/" 11 | schedule: 12 | interval: daily 13 | open-pull-requests-limit: 10 14 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "main" ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ "main" ] 20 | schedule: 21 | - cron: '26 1 * * 5' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | container: ghcr.io/cosmos/cosmos-db/build-test:latest 28 | permissions: 29 | actions: read 30 | contents: read 31 | security-events: write 32 | 33 | strategy: 34 | fail-fast: false 35 | matrix: 36 | language: [ 'go' ] 37 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 38 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 39 | 40 | steps: 41 | - name: Checkout repository 42 | uses: actions/checkout@v4 43 | 44 | # Initializes the CodeQL tools for scanning. 45 | - name: Initialize CodeQL 46 | uses: github/codeql-action/init@v3 47 | with: 48 | languages: ${{ matrix.language }} 49 | # If you wish to specify custom queries, you can do so here or in a config file. 50 | # By default, queries listed here will override any specified in a config file. 51 | # Prefix the list here with "+" to use these queries and those in the config file. 52 | 53 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 54 | queries: security-extended,security-and-quality 55 | 56 | 57 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 58 | # If this step fails, then you should remove it and run the build manually (see below) 59 | - name: Autobuild 60 | uses: github/codeql-action/autobuild@v3 61 | 62 | # ℹ️ Command-line programs to run using the OS shell. 63 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 64 | 65 | # If the Autobuild fails above, remove it and uncomment the following three lines. 66 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 67 | 68 | # - run: | 69 | # echo "Run, Build Application using script" 70 | # ./location_of_script_within_repo/buildscript.sh 71 | 72 | - name: Perform CodeQL Analysis 73 | uses: github/codeql-action/analyze@v3 74 | -------------------------------------------------------------------------------- /.github/workflows/docker.yml: -------------------------------------------------------------------------------- 1 | name: build docker image 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | push: 7 | branches: 8 | - main 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v4 16 | 17 | - name: Login to GitHub Container Registry 18 | uses: docker/login-action@v3 19 | with: 20 | registry: ghcr.io 21 | username: ${{ github.repository_owner }} 22 | password: ${{ secrets.GITHUB_TOKEN }} 23 | 24 | - name: Build and push 25 | uses: docker/build-push-action@v5 26 | with: 27 | push: ${{ github.event_name != 'pull_request' }} 28 | tags: ghcr.io/cosmos/cosmos-db/build-test:latest 29 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | 8 | jobs: 9 | golangci: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | - uses: DeterminateSystems/nix-installer-action@main 14 | - uses: DeterminateSystems/magic-nix-cache-action@main 15 | - uses: actions/setup-go@v5 16 | with: 17 | go-version: "1.20" 18 | check-latest: true 19 | - name: run lint 20 | run: nix develop -c make lint 21 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | jobs: 8 | Test: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | - uses: DeterminateSystems/nix-installer-action@main 13 | - uses: DeterminateSystems/magic-nix-cache-action@main 14 | - uses: actions/setup-go@v5 15 | with: 16 | go-version: "1.20" 17 | check-latest: true 18 | cache: true 19 | cache-dependency-path: store/go.sum 20 | - name: run tests 21 | run: nix develop . -c go test ./... -mod=readonly -timeout 8m -race -coverprofile=coverage.txt -covermode=atomic -tags=memdb,goleveldb,rocksdb -v 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Test binary, build with `go test -c` 2 | *.test 3 | 4 | # Output of the go coverage tool, specifically when used with LiteIDE 5 | *.out 6 | 7 | .idea 8 | .vscode/* 9 | .vscode/settings.json 10 | vendor/* 11 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | # This configuration file was automatically generated by Gitpod. 2 | # Please adjust to your needs (see https://www.gitpod.io/docs/config-gitpod-file) 3 | # and commit this file to your remote git repository to share the goodness with others. 4 | 5 | image: ghcr.io/cosmos/cosmos-db/build-test:latest 6 | # this means that there's a one-click known good environment available to developers. 7 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | run: 2 | build-tags: 3 | - rocksdb 4 | concurrency: 4 5 | sort-results: true 6 | allow-parallel-runners: true 7 | tests: true 8 | 9 | linters: 10 | disable-all: true 11 | enable: 12 | - dogsled 13 | - dupl 14 | - errcheck 15 | - exportloopref 16 | - goconst 17 | - gocritic 18 | - gofumpt 19 | - revive 20 | - gosec 21 | - gosimple 22 | - govet 23 | - ineffassign 24 | - lll 25 | - misspell 26 | - nakedret 27 | - prealloc 28 | - staticcheck 29 | - stylecheck 30 | - typecheck 31 | - revive 32 | - unconvert 33 | - unused 34 | - nolintlint 35 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "go.buildTags": "cleveldb rocksdb pebbledb", 3 | "go.lintTool": "golangci-lint", 4 | "go.lintOnSave": "workspace", 5 | "go.formatTool": "gofumpt" 6 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## UNRELEASED 4 | 5 | * Allow full control in rocksdb opening 6 | 7 | ## [v1.0.2] - 2024-02-26 8 | 9 | * Downgrade Go version in go.mod to 1.19 10 | 11 | ## [v1.0.1] - 2024-02-25 12 | 13 | ## [v1.0.0] - 2023-05-25 14 | 15 | > Note this repository was forked from [github.com/tendermint/tm-db](https://github.com/tendermint/tm-db). Minor modifications were made after the fork to better support the Cosmos SDK. Notably, this repo removes badger, boltdb and cleveldb. 16 | 17 | * added bloom filter: 18 | * Removed Badger & Boltdb 19 | * Add `NewBatchWithSize` to `DB` interface: 20 | * Add `NewRocksDBWithRaw` to support different rocksdb open mode (read-only, secondary-standby). 21 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # This file makes a development and test environment that includes the latest versions of relevant databases. 2 | 3 | FROM archlinux 4 | 5 | ENV GOPATH /go 6 | ENV PATH $PATH:/go/bin 7 | 8 | RUN pacman -Syyu --noconfirm go base-devel rocksdb leveldb git 9 | 10 | RUN mkdir /go && \ 11 | chmod -R 777 /go 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2016 All in Bits, Inc 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: lint test 2 | 3 | ### go tests 4 | ## By default this will only test memdb, goleveldb, and pebbledb, which do not require cgo 5 | test: 6 | @echo "--> Running go test" 7 | @go test $(PACKAGES) -tags pebbledb -v 8 | 9 | test-rocksdb: 10 | @echo "--> Running go test" 11 | @go test $(PACKAGES) -tags rocksdb -v 12 | 13 | test-pebble: 14 | @echo "--> Running go test" 15 | @go test $(PACKAGES) -tags pebbledb -v 16 | 17 | 18 | test-all: 19 | @echo "--> Running go test" 20 | @go test $(PACKAGES) -tags rocksdb,pebbledb -v 21 | 22 | golangci_version=v1.55.0 23 | 24 | #? lint-install: Install golangci-lint 25 | lint-install: 26 | @echo "--> Installing golangci-lint $(golangci_version)" 27 | @go install github.com/golangci/golangci-lint/cmd/golangci-lint@$(golangci_version) 28 | .PHONY: lint-install 29 | 30 | lint: 31 | @echo "--> Running linter" 32 | $(MAKE) lint-install 33 | @golangci-lint run 34 | @go mod verify 35 | .PHONY: lint 36 | 37 | format: 38 | find . -name '*.go' -type f -not -path "*.git*" -not -name '*.pb.go' -not -name '*pb_test.go' | xargs gofumpt -w -l . 39 | find . -name '*.go' -type f -not -path "*.git*" -not -name '*.pb.go' -not -name '*pb_test.go' | xargs golangci-lint run --fix . 40 | .PHONY: format 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cosmos DB 2 | 3 | [![version](https://img.shields.io/github/tag/cosmos/cosmos-db.svg)](https://github.com/cosmos/cosmos-db/releases/latest) 4 | [![license](https://img.shields.io/github/license/cosmos/cosmos-db.svg)](https://github.com/cosmos/cosmos-db/blob/master/LICENSE) 5 | [![API Reference](https://camo.githubusercontent.com/915b7be44ada53c290eb157634330494ebe3e30a/68747470733a2f2f676f646f632e6f72672f6769746875622e636f6d2f676f6c616e672f6764646f3f7374617475732e737667)](https://pkg.go.dev/github.com/cosmos/cosmos-db) 6 | [![codecov](https://codecov.io/gh/cosmos/cosmos-db/branch/master/graph/badge.svg)](https://codecov.io/gh/cosmos/cosmos-db) 7 | ![Lint](https://github.com/cosmos/cosmos-db/workflows/Lint/badge.svg?branch=master) 8 | ![Test](https://github.com/cosmos/cosmos-db/workflows/Test/badge.svg?branch=master) 9 | [![Discord chat](https://img.shields.io/discord/669268347736686612.svg)](https://discord.gg/cosmosnetwork) 10 | 11 | Common database interface for various database backends. Primarily meant for applications built on [Tendermint](https://github.com/tendermint/tendermint), such as the [Cosmos SDK](https://github.com/cosmos/cosmos-sdk), but can be used independently of these as well. 12 | 13 | ### Minimum Go Version 14 | 15 | Go 1.19+ 16 | 17 | ## Supported Database Backends 18 | 19 | - **MemDB [stable]:** An in-memory database using [Google's B-tree package](https://github.com/google/btree). Has very high performance both for reads, writes, and range scans, but is not durable and will lose all data on process exit. Does not support transactions. Suitable for e.g. caches, working sets, and tests. Used for [IAVL](https://github.com/tendermint/iavl) working sets when the pruning strategy allows it. 20 | 21 | - **[GoLevelDB](https://github.com/syndtr/goleveldb)**: a pure Go implementation of [LevelDB](https://github.com/google/leveldb) (see below). Currently the default on-disk database used in the Cosmos SDK. 22 | 23 | - **[LevelDB](https://github.com/google/leveldb)** using [levigo Go wrapper](https://github.com/jmhodges/levigo). Uses LSM-trees for on-disk storage, which have good performance for write-heavy workloads, particularly on spinning disks, but requires periodic compaction to maintain decent read performance and reclaim disk space. Does not support transactions. 24 | 25 | - **[RocksDB](https://github.com/cosmos/gorocksdb):** A [Go wrapper](https://github.com/cosmos/gorocksdb) around [RocksDB](https://rocksdb.org). Similarly to LevelDB (above) it uses LSM-trees for on-disk storage, but is optimized for fast storage media such as SSDs and memory. Supports atomic transactions, but not full ACID transactions. 26 | 27 | - **[Pebble](https://github.com/cockroachdb/pebble):** a RocksDB/LevelDB inspired key-value database in Go using RocksDB file format and LSM-trees for on-disk storage. Supports snapshots. 28 | 29 | ## Meta-databases 30 | 31 | - **PrefixDB [stable]:** A database which wraps another database and uses a static prefix for all keys. This allows multiple logical databases to be stored in a common underlying databases by using different namespaces. Used by the Cosmos SDK to give different modules their own namespaced database in a single application database. 32 | 33 | ## Tests 34 | 35 | To test common databases, run `make test`. If all databases are available on the local machine, use `make test-all` to test them all. 36 | -------------------------------------------------------------------------------- /RELEASING.md: -------------------------------------------------------------------------------- 1 | # How To Release cosmos-db 2 | 3 | This document provides a step-by-step guide for creating a release of cosmos-db. 4 | 5 | ## 1. Update the changelog 6 | 7 | Open the `CHANGELOG.md` at the root of the repository. 8 | Amend the top of this file with a section for the latest version (eg: v1.0.x). 9 | Be sure to include any bug fixes, improvements, dependency upgrades, and breaking changes included in this version. 10 | (It's OK to exclude changes to tooling dependencies, like updates to Github Actions.) 11 | Finally, create a pull request for the changelog update. 12 | Once the tests pass and the pull request is approved, merge the change into master. 13 | 14 | ## 2. Tag the latest commit with the latest version 15 | 16 | cosmos-db is provided as a golang [module](https://blog.golang.org/publishing-go-modules), which rely on git tags for versioning information. 17 | 18 | Tag the changelog commit in master created in step 1 with the latest version. 19 | Be sure to prefix the version tag with `v`. For example, `v1.0.0` for version 1.0.0. 20 | This tagging can be done [using github](https://docs.github.com/en/desktop/contributing-and-collaborating-using-github-desktop/managing-commits/managing-tags#creating-a-tag) or [using git](https://git-scm.com/book/en/v2/Git-Basics-Tagging) on the command line. 21 | 22 | Note that the golang modules tooling expects tags to be immutable. 23 | If you make a mistake after pushing a tag, make a new tag and start over rather than fix and re-push the old tag. 24 | ## 3. Create a github release 25 | 26 | Finally, create a github release. 27 | To create a github release, follow the steps in the [github release documentation](https://docs.github.com/en/github/administering-a-repository/releasing-projects-on-github/managing-releases-in-a-repository#creating-a-release). 28 | 29 | When creating the github release, select the `Tag version` created in step 2. 30 | Use the version tag as the release title and paste in the changelog information for this release in the `Describe this release` section. 31 | -------------------------------------------------------------------------------- /backend_test.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "os" 7 | "path/filepath" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | "github.com/stretchr/testify/require" 12 | ) 13 | 14 | // Register a test backend for PrefixDB as well, with some unrelated junk data 15 | func init() { 16 | registerDBCreator("prefixdb", func(name, dir string, opts Options) (DB, error) { 17 | mdb := NewMemDB() 18 | mdb.Set([]byte("a"), []byte{1}) //nolint:errcheck 19 | mdb.Set([]byte("b"), []byte{2}) //nolint:errcheck 20 | mdb.Set([]byte("t"), []byte{20}) //nolint:errcheck 21 | mdb.Set([]byte("test"), []byte{0}) //nolint:errcheck 22 | mdb.Set([]byte("u"), []byte{21}) //nolint:errcheck 23 | mdb.Set([]byte("z"), []byte{26}) //nolint:errcheck 24 | return NewPrefixDB(mdb, []byte("test/")), nil 25 | }, false) 26 | } 27 | 28 | func cleanupDBDir(dir, name string) { 29 | err := os.RemoveAll(filepath.Join(dir, name) + DBFileSuffix) 30 | if err != nil { 31 | panic(err) 32 | } 33 | } 34 | 35 | func testBackendGetSetDelete(t *testing.T, backend BackendType) { 36 | // Default 37 | dirname, err := os.MkdirTemp("", fmt.Sprintf("test_backend_%s_", backend)) 38 | require.Nil(t, err) 39 | db, err := NewDB("testdb", backend, dirname) 40 | require.NoError(t, err) 41 | defer cleanupDBDir(dirname, "testdb") 42 | 43 | // A nonexistent key should return nil. 44 | value, err := db.Get([]byte("a")) 45 | require.NoError(t, err) 46 | require.Nil(t, value) 47 | 48 | ok, err := db.Has([]byte("a")) 49 | require.NoError(t, err) 50 | require.False(t, ok) 51 | 52 | // Set and get a value. 53 | err = db.Set([]byte("a"), []byte{0x01}) 54 | require.NoError(t, err) 55 | 56 | ok, err = db.Has([]byte("a")) 57 | require.NoError(t, err) 58 | require.True(t, ok) 59 | 60 | value, err = db.Get([]byte("a")) 61 | require.NoError(t, err) 62 | require.Equal(t, []byte{0x01}, value) 63 | 64 | err = db.SetSync([]byte("b"), []byte{0x02}) 65 | require.NoError(t, err) 66 | 67 | value, err = db.Get([]byte("b")) 68 | require.NoError(t, err) 69 | require.Equal(t, []byte{0x02}, value) 70 | 71 | // Deleting a non-existent value is fine. 72 | err = db.Delete([]byte("x")) 73 | require.NoError(t, err) 74 | 75 | err = db.DeleteSync([]byte("x")) 76 | require.NoError(t, err) 77 | 78 | // Delete a value. 79 | err = db.Delete([]byte("a")) 80 | require.NoError(t, err) 81 | 82 | value, err = db.Get([]byte("a")) 83 | require.NoError(t, err) 84 | require.Nil(t, value) 85 | 86 | err = db.DeleteSync([]byte("b")) 87 | require.NoError(t, err) 88 | 89 | value, err = db.Get([]byte("b")) 90 | require.NoError(t, err) 91 | require.Nil(t, value) 92 | 93 | // Setting, getting, and deleting an empty key should error. 94 | _, err = db.Get([]byte{}) 95 | require.Equal(t, errKeyEmpty, err) 96 | _, err = db.Get(nil) 97 | require.Equal(t, errKeyEmpty, err) 98 | 99 | _, err = db.Has([]byte{}) 100 | require.Equal(t, errKeyEmpty, err) 101 | _, err = db.Has(nil) 102 | require.Equal(t, errKeyEmpty, err) 103 | 104 | err = db.Set([]byte{}, []byte{0x01}) 105 | require.Equal(t, errKeyEmpty, err) 106 | err = db.Set(nil, []byte{0x01}) 107 | require.Equal(t, errKeyEmpty, err) 108 | err = db.SetSync([]byte{}, []byte{0x01}) 109 | require.Equal(t, errKeyEmpty, err) 110 | err = db.SetSync(nil, []byte{0x01}) 111 | require.Equal(t, errKeyEmpty, err) 112 | 113 | err = db.Delete([]byte{}) 114 | require.Equal(t, errKeyEmpty, err) 115 | err = db.Delete(nil) 116 | require.Equal(t, errKeyEmpty, err) 117 | err = db.DeleteSync([]byte{}) 118 | require.Equal(t, errKeyEmpty, err) 119 | err = db.DeleteSync(nil) 120 | require.Equal(t, errKeyEmpty, err) 121 | 122 | // Setting a nil value should error, but an empty value is fine. 123 | err = db.Set([]byte("x"), nil) 124 | require.Equal(t, errValueNil, err) 125 | err = db.SetSync([]byte("x"), nil) 126 | require.Equal(t, errValueNil, err) 127 | 128 | err = db.Set([]byte("x"), []byte{}) 129 | require.NoError(t, err) 130 | err = db.SetSync([]byte("x"), []byte{}) 131 | require.NoError(t, err) 132 | value, err = db.Get([]byte("x")) 133 | require.NoError(t, err) 134 | require.Equal(t, []byte{}, value) 135 | } 136 | 137 | func TestBackendsGetSetDelete(t *testing.T) { 138 | for dbType := range backends { 139 | t.Run(string(dbType), func(t *testing.T) { 140 | testBackendGetSetDelete(t, dbType) 141 | }) 142 | } 143 | } 144 | 145 | func TestGoLevelDBBackend(t *testing.T) { 146 | name := fmt.Sprintf("test_%x", randStr(12)) 147 | db, err := NewDB(name, GoLevelDBBackend, "") 148 | require.NoError(t, err) 149 | defer cleanupDBDir("", name) 150 | 151 | _, ok := db.(*GoLevelDB) 152 | assert.True(t, ok) 153 | } 154 | 155 | func TestDBIterator(t *testing.T) { 156 | for dbType := range backends { 157 | t.Run(string(dbType), func(t *testing.T) { 158 | testDBIterator(t, dbType) 159 | }) 160 | } 161 | } 162 | 163 | func testDBIterator(t *testing.T, backend BackendType) { 164 | name := fmt.Sprintf("test_%x", randStr(12)) 165 | dir := os.TempDir() 166 | db, err := NewDB(name, backend, dir) 167 | require.NoError(t, err) 168 | defer cleanupDBDir(dir, name) 169 | 170 | for i := 0; i < 10; i++ { 171 | if i != 6 { // but skip 6. 172 | err := db.Set(int642Bytes(int64(i)), []byte{}) 173 | require.NoError(t, err) 174 | } 175 | } 176 | 177 | // Blank iterator keys should error 178 | _, err = db.Iterator([]byte{}, nil) 179 | require.Equal(t, errKeyEmpty, err) 180 | _, err = db.Iterator(nil, []byte{}) 181 | require.Equal(t, errKeyEmpty, err) 182 | _, err = db.ReverseIterator([]byte{}, nil) 183 | require.Equal(t, errKeyEmpty, err) 184 | _, err = db.ReverseIterator(nil, []byte{}) 185 | require.Equal(t, errKeyEmpty, err) 186 | 187 | itr, err := db.Iterator(nil, nil) 188 | require.NoError(t, err) 189 | verifyIterator(t, itr, []int64{0, 1, 2, 3, 4, 5, 7, 8, 9}, "forward iterator") 190 | 191 | ritr, err := db.ReverseIterator(nil, nil) 192 | require.NoError(t, err) 193 | verifyIterator(t, ritr, []int64{9, 8, 7, 5, 4, 3, 2, 1, 0}, "reverse iterator") 194 | 195 | itr, err = db.Iterator(nil, int642Bytes(0)) 196 | require.NoError(t, err) 197 | verifyIterator(t, itr, []int64(nil), "forward iterator to 0") 198 | 199 | ritr, err = db.ReverseIterator(int642Bytes(10), nil) 200 | require.NoError(t, err) 201 | verifyIterator(t, ritr, []int64(nil), "reverse iterator from 10 (ex)") 202 | 203 | itr, err = db.Iterator(int642Bytes(0), nil) 204 | require.NoError(t, err) 205 | verifyIterator(t, itr, []int64{0, 1, 2, 3, 4, 5, 7, 8, 9}, "forward iterator from 0") 206 | 207 | itr, err = db.Iterator(int642Bytes(1), nil) 208 | require.NoError(t, err) 209 | verifyIterator(t, itr, []int64{1, 2, 3, 4, 5, 7, 8, 9}, "forward iterator from 1") 210 | 211 | ritr, err = db.ReverseIterator(nil, int642Bytes(10)) 212 | require.NoError(t, err) 213 | verifyIterator(t, ritr, 214 | []int64{9, 8, 7, 5, 4, 3, 2, 1, 0}, "reverse iterator from 10 (ex)") 215 | 216 | ritr, err = db.ReverseIterator(nil, int642Bytes(9)) 217 | require.NoError(t, err) 218 | verifyIterator(t, ritr, 219 | []int64{8, 7, 5, 4, 3, 2, 1, 0}, "reverse iterator from 9 (ex)") 220 | 221 | ritr, err = db.ReverseIterator(nil, int642Bytes(8)) 222 | require.NoError(t, err) 223 | verifyIterator(t, ritr, 224 | []int64{7, 5, 4, 3, 2, 1, 0}, "reverse iterator from 8 (ex)") 225 | 226 | itr, err = db.Iterator(int642Bytes(5), int642Bytes(6)) 227 | require.NoError(t, err) 228 | verifyIterator(t, itr, []int64{5}, "forward iterator from 5 to 6") 229 | 230 | itr, err = db.Iterator(int642Bytes(5), int642Bytes(7)) 231 | require.NoError(t, err) 232 | verifyIterator(t, itr, []int64{5}, "forward iterator from 5 to 7") 233 | 234 | itr, err = db.Iterator(int642Bytes(5), int642Bytes(8)) 235 | require.NoError(t, err) 236 | verifyIterator(t, itr, []int64{5, 7}, "forward iterator from 5 to 8") 237 | 238 | itr, err = db.Iterator(int642Bytes(6), int642Bytes(7)) 239 | require.NoError(t, err) 240 | verifyIterator(t, itr, []int64(nil), "forward iterator from 6 to 7") 241 | 242 | itr, err = db.Iterator(int642Bytes(6), int642Bytes(8)) 243 | require.NoError(t, err) 244 | verifyIterator(t, itr, []int64{7}, "forward iterator from 6 to 8") 245 | 246 | itr, err = db.Iterator(int642Bytes(7), int642Bytes(8)) 247 | require.NoError(t, err) 248 | verifyIterator(t, itr, []int64{7}, "forward iterator from 7 to 8") 249 | 250 | ritr, err = db.ReverseIterator(int642Bytes(4), int642Bytes(5)) 251 | require.NoError(t, err) 252 | verifyIterator(t, ritr, []int64{4}, "reverse iterator from 5 (ex) to 4") 253 | 254 | ritr, err = db.ReverseIterator(int642Bytes(4), int642Bytes(6)) 255 | require.NoError(t, err) 256 | verifyIterator(t, ritr, 257 | []int64{5, 4}, "reverse iterator from 6 (ex) to 4") 258 | 259 | ritr, err = db.ReverseIterator(int642Bytes(4), int642Bytes(7)) 260 | require.NoError(t, err) 261 | verifyIterator(t, ritr, 262 | []int64{5, 4}, "reverse iterator from 7 (ex) to 4") 263 | 264 | ritr, err = db.ReverseIterator(int642Bytes(5), int642Bytes(6)) 265 | require.NoError(t, err) 266 | verifyIterator(t, ritr, []int64{5}, "reverse iterator from 6 (ex) to 5") 267 | 268 | ritr, err = db.ReverseIterator(int642Bytes(5), int642Bytes(7)) 269 | require.NoError(t, err) 270 | verifyIterator(t, ritr, []int64{5}, "reverse iterator from 7 (ex) to 5") 271 | 272 | ritr, err = db.ReverseIterator(int642Bytes(6), int642Bytes(7)) 273 | require.NoError(t, err) 274 | verifyIterator(t, ritr, 275 | []int64(nil), "reverse iterator from 7 (ex) to 6") 276 | 277 | ritr, err = db.ReverseIterator(int642Bytes(10), nil) 278 | require.NoError(t, err) 279 | verifyIterator(t, ritr, []int64(nil), "reverse iterator to 10") 280 | 281 | ritr, err = db.ReverseIterator(int642Bytes(6), nil) 282 | require.NoError(t, err) 283 | verifyIterator(t, ritr, []int64{9, 8, 7}, "reverse iterator to 6") 284 | 285 | ritr, err = db.ReverseIterator(int642Bytes(5), nil) 286 | require.NoError(t, err) 287 | verifyIterator(t, ritr, []int64{9, 8, 7, 5}, "reverse iterator to 5") 288 | 289 | ritr, err = db.ReverseIterator(int642Bytes(8), int642Bytes(9)) 290 | require.NoError(t, err) 291 | verifyIterator(t, ritr, []int64{8}, "reverse iterator from 9 (ex) to 8") 292 | 293 | ritr, err = db.ReverseIterator(int642Bytes(2), int642Bytes(4)) 294 | require.NoError(t, err) 295 | verifyIterator(t, ritr, 296 | []int64{3, 2}, "reverse iterator from 4 (ex) to 2") 297 | 298 | ritr, err = db.ReverseIterator(int642Bytes(4), int642Bytes(2)) 299 | require.NoError(t, err) 300 | verifyIterator(t, ritr, 301 | []int64(nil), "reverse iterator from 2 (ex) to 4") 302 | 303 | // Ensure that the iterators don't panic with an empty database. 304 | dir2, err := os.MkdirTemp("", "tm-db-test") 305 | require.NoError(t, err) 306 | db2, err := NewDB(name, backend, dir2) 307 | require.NoError(t, err) 308 | defer cleanupDBDir(dir2, name) 309 | 310 | itr, err = db2.Iterator(nil, nil) 311 | require.NoError(t, err) 312 | verifyIterator(t, itr, nil, "forward iterator with empty db") 313 | 314 | ritr, err = db2.ReverseIterator(nil, nil) 315 | require.NoError(t, err) 316 | verifyIterator(t, ritr, nil, "reverse iterator with empty db") 317 | } 318 | 319 | func verifyIterator(t *testing.T, itr Iterator, expected []int64, msg string) { 320 | var list []int64 321 | for itr.Valid() { 322 | key := itr.Key() 323 | list = append(list, bytes2Int64(key)) 324 | itr.Next() 325 | } 326 | assert.Equal(t, expected, list, msg) 327 | } 328 | 329 | func TestDBBatchGetByteSize(t *testing.T) { 330 | for dbType := range backends { 331 | t.Run(fmt.Sprintf("%v", dbType), func(t *testing.T) { 332 | testDBBatchGetByteSize(t, dbType) 333 | }) 334 | } 335 | } 336 | 337 | func testDBBatchGetByteSize(t *testing.T, backend BackendType) { 338 | name := fmt.Sprintf("test_%x", randStr(12)) 339 | dir := os.TempDir() 340 | db, err := NewDB(name, backend, dir) 341 | require.NoError(t, err) 342 | defer cleanupDBDir(dir, name) 343 | 344 | // create a new batch 345 | batch := db.NewBatch() 346 | batchSize, err := batch.GetByteSize() 347 | require.NoError(t, err) 348 | // size of newly created batch should be 0 or negligible because of the metadata in the batch, 349 | // for example peppble's batchHeaderLen is 12 so 350 | // peppble's batch size will always be equal or greater than 12 even for empty batch 351 | require.LessOrEqual(t, batchSize, 32) 352 | 353 | totalSizeOfKeyAndValue := 0 354 | // set 100 random keys and values 355 | for i := 0; i < 100; i++ { 356 | keySize := rand.Intn(32) + 1 //nolint:gosec 357 | valueSize := rand.Intn(32) + 1 //nolint:gosec 358 | totalSizeOfKeyAndValue += keySize + valueSize 359 | require.NoError(t, batch.Set([]byte(randStr(keySize)), []byte(randStr(valueSize)))) 360 | } 361 | 362 | batchSize, err = batch.GetByteSize() 363 | require.NoError(t, err) 364 | // because we set a lot of keys and values with considerable size, 365 | // ratio of batchSize / totalSizeOfKeyAndValue should be roughly 1 366 | require.Equal(t, 1, batchSize/totalSizeOfKeyAndValue) 367 | 368 | err = batch.Write() 369 | require.NoError(t, err) 370 | 371 | _, err = batch.GetByteSize() 372 | // calling GetByteSize on a written batch should error 373 | require.Error(t, err) 374 | } 375 | 376 | func TestDBBatchOperations(t *testing.T) { 377 | for dbType := range backends { 378 | t.Run(fmt.Sprintf("%v", dbType), func(t *testing.T) { 379 | testDBBatchOperations(t, dbType) 380 | }) 381 | } 382 | } 383 | 384 | func testDBBatchOperations(t *testing.T, backend BackendType) { 385 | name := fmt.Sprintf("test_%x", randStr(12)) 386 | dir := os.TempDir() 387 | db, err := NewDB(name, backend, dir) 388 | require.NoError(t, err) 389 | defer cleanupDBDir(dir, name) 390 | 391 | // create a new batch, and some items - they should not be visible until we write 392 | batch := db.NewBatch() 393 | require.NoError(t, batch.Set([]byte("a"), []byte{1})) 394 | require.NoError(t, batch.Set([]byte("b"), []byte{2})) 395 | require.NoError(t, batch.Set([]byte("c"), []byte{3})) 396 | assertKeyValues(t, db, map[string][]byte{}) 397 | 398 | err = batch.Write() 399 | require.NoError(t, err) 400 | assertKeyValues(t, db, map[string][]byte{"a": {1}, "b": {2}, "c": {3}}) 401 | 402 | // trying to modify or rewrite a written batch should error, but closing it should work 403 | require.Error(t, batch.Set([]byte("a"), []byte{9})) 404 | require.Error(t, batch.Delete([]byte("a"))) 405 | require.Error(t, batch.Write()) 406 | require.Error(t, batch.WriteSync()) 407 | require.NoError(t, batch.Close()) 408 | 409 | // batches should write changes in order 410 | batch = db.NewBatch() 411 | require.NoError(t, batch.Delete([]byte("a"))) 412 | require.NoError(t, batch.Set([]byte("a"), []byte{1})) 413 | require.NoError(t, batch.Set([]byte("b"), []byte{1})) 414 | require.NoError(t, batch.Set([]byte("b"), []byte{2})) 415 | require.NoError(t, batch.Set([]byte("c"), []byte{3})) 416 | require.NoError(t, batch.Delete([]byte("c"))) 417 | require.NoError(t, batch.Write()) 418 | require.NoError(t, batch.Close()) 419 | assertKeyValues(t, db, map[string][]byte{"a": {1}, "b": {2}}) 420 | 421 | // empty and nil keys, as well as nil values, should be disallowed 422 | batch = db.NewBatch() 423 | err = batch.Set([]byte{}, []byte{0x01}) 424 | require.Equal(t, errKeyEmpty, err) 425 | err = batch.Set(nil, []byte{0x01}) 426 | require.Equal(t, errKeyEmpty, err) 427 | err = batch.Set([]byte("a"), nil) 428 | require.Equal(t, errValueNil, err) 429 | 430 | err = batch.Delete([]byte{}) 431 | require.Equal(t, errKeyEmpty, err) 432 | err = batch.Delete(nil) 433 | require.Equal(t, errKeyEmpty, err) 434 | 435 | err = batch.Close() 436 | require.NoError(t, err) 437 | 438 | // it should be possible to write an empty batch 439 | batch = db.NewBatch() 440 | err = batch.Write() 441 | require.NoError(t, err) 442 | assertKeyValues(t, db, map[string][]byte{"a": {1}, "b": {2}}) 443 | 444 | // it should be possible to close an empty batch, and to re-close a closed batch 445 | batch = db.NewBatch() 446 | batch.Close() 447 | batch.Close() 448 | 449 | // all other operations on a closed batch should error 450 | require.Error(t, batch.Set([]byte("a"), []byte{9})) 451 | require.Error(t, batch.Delete([]byte("a"))) 452 | require.Error(t, batch.Write()) 453 | require.Error(t, batch.WriteSync()) 454 | } 455 | 456 | func assertKeyValues(t *testing.T, db DB, expect map[string][]byte) { 457 | iter, err := db.Iterator(nil, nil) 458 | require.NoError(t, err) 459 | defer iter.Close() 460 | 461 | actual := make(map[string][]byte) 462 | for ; iter.Valid(); iter.Next() { 463 | require.NoError(t, iter.Error()) 464 | actual[string(iter.Key())] = iter.Value() 465 | } 466 | 467 | assert.Equal(t, expect, actual) 468 | } 469 | -------------------------------------------------------------------------------- /common_test.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "math/rand" 7 | "os" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | "github.com/stretchr/testify/require" 12 | ) 13 | 14 | //---------------------------------------- 15 | // Helper functions. 16 | 17 | func checkValue(t *testing.T, db DB, key []byte, valueWanted []byte) { 18 | valueGot, err := db.Get(key) 19 | assert.NoError(t, err) 20 | assert.Equal(t, valueWanted, valueGot) 21 | } 22 | 23 | func checkValid(t *testing.T, itr Iterator, expected bool) { 24 | valid := itr.Valid() 25 | require.Equal(t, expected, valid) 26 | } 27 | 28 | func checkNext(t *testing.T, itr Iterator, expected bool) { 29 | itr.Next() 30 | // assert.NoError(t, err) TODO: look at fixing this 31 | valid := itr.Valid() 32 | require.Equal(t, expected, valid) 33 | } 34 | 35 | func checkNextPanics(t *testing.T, itr Iterator) { 36 | assert.Panics(t, func() { itr.Next() }, "checkNextPanics expected an error but didn't") 37 | } 38 | 39 | func checkDomain(t *testing.T, itr Iterator, start, end []byte) { 40 | ds, de := itr.Domain() 41 | assert.Equal(t, start, ds, "checkDomain domain start incorrect") 42 | assert.Equal(t, end, de, "checkDomain domain end incorrect") 43 | } 44 | 45 | func checkItem(t *testing.T, itr Iterator, key []byte, value []byte) { 46 | v := itr.Value() 47 | 48 | k := itr.Key() 49 | 50 | assert.Exactly(t, key, k) 51 | assert.Exactly(t, value, v) 52 | } 53 | 54 | func checkInvalid(t *testing.T, itr Iterator) { 55 | checkValid(t, itr, false) 56 | checkKeyPanics(t, itr) 57 | checkValuePanics(t, itr) 58 | checkNextPanics(t, itr) 59 | } 60 | 61 | func checkKeyPanics(t *testing.T, itr Iterator) { 62 | assert.Panics(t, func() { itr.Key() }, "checkKeyPanics expected panic but didn't") 63 | } 64 | 65 | func checkValuePanics(t *testing.T, itr Iterator) { 66 | assert.Panics(t, func() { itr.Value() }) 67 | } 68 | 69 | func newTempDB(t *testing.T, backend BackendType) (db DB, dbDir string) { 70 | dirname, err := os.MkdirTemp("", "db_common_test") 71 | require.NoError(t, err) 72 | db, err = NewDB("testdb", backend, dirname) 73 | require.NoError(t, err) 74 | return db, dirname 75 | } 76 | 77 | func benchmarkRangeScans(b *testing.B, db DB, dbSize int64) { 78 | b.StopTimer() 79 | 80 | rangeSize := int64(10000) 81 | if dbSize < rangeSize { 82 | b.Errorf("db size %v cannot be less than range size %v", dbSize, rangeSize) 83 | } 84 | 85 | for i := int64(0); i < dbSize; i++ { 86 | bytes := int642Bytes(i) 87 | err := db.Set(bytes, bytes) 88 | if err != nil { 89 | // require.NoError() is very expensive (according to profiler), so check manually 90 | b.Fatal(b, err) 91 | } 92 | } 93 | b.StartTimer() 94 | 95 | for i := 0; i < b.N; i++ { 96 | start := rand.Int63n(dbSize - rangeSize) //nolint:gosec 97 | end := start + rangeSize 98 | iter, err := db.Iterator(int642Bytes(start), int642Bytes(end)) 99 | require.NoError(b, err) 100 | count := 0 101 | for ; iter.Valid(); iter.Next() { 102 | count++ 103 | } 104 | iter.Close() 105 | require.EqualValues(b, rangeSize, count) 106 | } 107 | } 108 | 109 | func benchmarkRandomReadsWrites(b *testing.B, db DB) { 110 | b.StopTimer() 111 | 112 | // create dummy data 113 | const numItems = int64(1000000) 114 | internal := map[int64]int64{} 115 | for i := 0; i < int(numItems); i++ { 116 | internal[int64(i)] = int64(0) 117 | } 118 | 119 | // fmt.Println("ok, starting") 120 | b.StartTimer() 121 | 122 | for i := 0; i < b.N; i++ { 123 | // Write something 124 | { 125 | idx := rand.Int63n(numItems) //nolint:gosec 126 | internal[idx]++ 127 | val := internal[idx] 128 | idxBytes := int642Bytes(idx) 129 | valBytes := int642Bytes(val) 130 | err := db.Set(idxBytes, valBytes) 131 | if err != nil { 132 | // require.NoError() is very expensive (according to profiler), so check manually 133 | b.Fatal(b, err) 134 | } 135 | } 136 | 137 | // Read something 138 | { 139 | idx := rand.Int63n(numItems) //nolint:gosec 140 | valExp := internal[idx] 141 | idxBytes := int642Bytes(idx) 142 | valBytes, err := db.Get(idxBytes) 143 | if err != nil { 144 | // require.NoError() is very expensive (according to profiler), so check manually 145 | b.Fatal(b, err) 146 | } 147 | if valExp == 0 { 148 | if !bytes.Equal(valBytes, nil) { 149 | b.Errorf("Expected %v for %v, got %X", nil, idx, valBytes) 150 | break 151 | } 152 | } else { 153 | if len(valBytes) != 8 { 154 | b.Errorf("Expected length 8 for %v, got %X", idx, valBytes) 155 | break 156 | } 157 | valGot := bytes2Int64(valBytes) 158 | if valExp != valGot { 159 | b.Errorf("Expected %v for %v, got %v", valExp, idx, valGot) 160 | break 161 | } 162 | } 163 | } 164 | 165 | } 166 | } 167 | 168 | func int642Bytes(i int64) []byte { 169 | buf := make([]byte, 8) 170 | binary.BigEndian.PutUint64(buf, uint64(i)) 171 | return buf 172 | } 173 | 174 | func bytes2Int64(buf []byte) int64 { 175 | return int64(binary.BigEndian.Uint64(buf)) 176 | } 177 | -------------------------------------------------------------------------------- /db.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | type BackendType string 9 | 10 | // These are valid backend types. 11 | const ( 12 | // GoLevelDBBackend represents goleveldb (github.com/syndtr/goleveldb - most 13 | // popular implementation) 14 | // - pure go 15 | // - stable 16 | GoLevelDBBackend BackendType = "goleveldb" 17 | // MemDBBackend represents in-memory key value store, which is mostly used 18 | // for testing. 19 | MemDBBackend BackendType = "memdb" 20 | // RocksDBBackend represents rocksdb (uses github.com/linxGnu/grocksdb) 21 | // - requires gcc 22 | // - use rocksdb build tag (go build -tags rocksdb) 23 | RocksDBBackend BackendType = "rocksdb" 24 | // PebbleDBDBBackend represents pebble (uses github.com/cockroachdb/pebble) 25 | // - pure go 26 | // - use pebble build tag (go build -tags pebbledb) 27 | PebbleDBBackend BackendType = "pebbledb" 28 | ) 29 | 30 | type ( 31 | dbCreator func(name string, dir string, opts Options) (DB, error) 32 | 33 | Options interface { 34 | Get(string) interface{} 35 | } 36 | ) 37 | 38 | var backends = map[BackendType]dbCreator{} 39 | 40 | func registerDBCreator(backend BackendType, creator dbCreator, force bool) { 41 | _, ok := backends[backend] 42 | if !force && ok { 43 | return 44 | } 45 | backends[backend] = creator 46 | } 47 | 48 | // NewDB creates a new database of type backend with the given name. 49 | func NewDB(name string, backend BackendType, dir string) (DB, error) { 50 | return NewDBwithOptions(name, backend, dir, nil) 51 | } 52 | 53 | func NewDBwithOptions(name string, backend BackendType, dir string, opts Options) (DB, error) { 54 | dbCreator, ok := backends[backend] 55 | if !ok { 56 | keys := make([]string, 0, len(backends)) 57 | for k := range backends { 58 | keys = append(keys, string(k)) 59 | } 60 | return nil, fmt.Errorf("unknown db_backend %s, expected one of %v", 61 | backend, strings.Join(keys, ",")) 62 | } 63 | 64 | db, err := dbCreator(name, dir, opts) 65 | if err != nil { 66 | return nil, fmt.Errorf("failed to initialize database: %w", err) 67 | } 68 | return db, nil 69 | } 70 | -------------------------------------------------------------------------------- /db_test.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestDBIteratorSingleKey(t *testing.T) { 12 | for backend := range backends { 13 | t.Run(fmt.Sprintf("Backend %s", backend), func(t *testing.T) { 14 | db, dir := newTempDB(t, backend) 15 | defer os.RemoveAll(dir) 16 | 17 | err := db.SetSync(bz("1"), bz("value_1")) 18 | assert.NoError(t, err) 19 | itr, err := db.Iterator(nil, nil) 20 | assert.NoError(t, err) 21 | 22 | checkValid(t, itr, true) 23 | checkNext(t, itr, false) 24 | checkValid(t, itr, false) 25 | checkNextPanics(t, itr) 26 | 27 | // Once invalid... 28 | checkInvalid(t, itr) 29 | }) 30 | } 31 | } 32 | 33 | func TestDBIteratorTwoKeys(t *testing.T) { 34 | for backend := range backends { 35 | t.Run(fmt.Sprintf("Backend %s", backend), func(t *testing.T) { 36 | db, dir := newTempDB(t, backend) 37 | defer os.RemoveAll(dir) 38 | 39 | err := db.SetSync(bz("1"), bz("value_1")) 40 | assert.NoError(t, err) 41 | 42 | err = db.SetSync(bz("2"), bz("value_1")) 43 | assert.NoError(t, err) 44 | 45 | { // Fail by calling Next too much 46 | itr, err := db.Iterator(nil, nil) 47 | assert.NoError(t, err) 48 | checkValid(t, itr, true) 49 | 50 | checkNext(t, itr, true) 51 | checkValid(t, itr, true) 52 | 53 | checkNext(t, itr, false) 54 | checkValid(t, itr, false) 55 | 56 | checkNextPanics(t, itr) 57 | 58 | // Once invalid... 59 | checkInvalid(t, itr) 60 | } 61 | }) 62 | } 63 | } 64 | 65 | func TestDBIteratorMany(t *testing.T) { 66 | for backend := range backends { 67 | t.Run(fmt.Sprintf("Backend %s", backend), func(t *testing.T) { 68 | db, dir := newTempDB(t, backend) 69 | defer os.RemoveAll(dir) 70 | 71 | keys := make([][]byte, 100) 72 | for i := 0; i < 100; i++ { 73 | keys[i] = []byte{byte(i)} 74 | } 75 | 76 | value := []byte{5} 77 | for _, k := range keys { 78 | err := db.Set(k, value) 79 | assert.NoError(t, err) 80 | } 81 | 82 | itr, err := db.Iterator(nil, nil) 83 | assert.NoError(t, err) 84 | 85 | defer itr.Close() 86 | for ; itr.Valid(); itr.Next() { 87 | key := itr.Key() 88 | value = itr.Value() 89 | value1, err := db.Get(key) 90 | assert.NoError(t, err) 91 | assert.Equal(t, value1, value) 92 | } 93 | }) 94 | } 95 | } 96 | 97 | func TestDBIteratorEmpty(t *testing.T) { 98 | for backend := range backends { 99 | t.Run(fmt.Sprintf("Backend %s", backend), func(t *testing.T) { 100 | db, dir := newTempDB(t, backend) 101 | defer os.RemoveAll(dir) 102 | 103 | itr, err := db.Iterator(nil, nil) 104 | assert.NoError(t, err) 105 | 106 | checkInvalid(t, itr) 107 | }) 108 | } 109 | } 110 | 111 | func TestDBIteratorEmptyBeginAfter(t *testing.T) { 112 | for backend := range backends { 113 | t.Run(fmt.Sprintf("Backend %s", backend), func(t *testing.T) { 114 | db, dir := newTempDB(t, backend) 115 | defer os.RemoveAll(dir) 116 | 117 | itr, err := db.Iterator(bz("1"), nil) 118 | assert.NoError(t, err) 119 | 120 | checkInvalid(t, itr) 121 | }) 122 | } 123 | } 124 | 125 | func TestDBIteratorNonemptyBeginAfter(t *testing.T) { 126 | for backend := range backends { 127 | t.Run(fmt.Sprintf("Backend %s", backend), func(t *testing.T) { 128 | db, dir := newTempDB(t, backend) 129 | defer os.RemoveAll(dir) 130 | 131 | err := db.SetSync(bz("1"), bz("value_1")) 132 | assert.NoError(t, err) 133 | itr, err := db.Iterator(bz("2"), nil) 134 | assert.NoError(t, err) 135 | 136 | checkInvalid(t, itr) 137 | }) 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "inputs": { 5 | "systems": "systems" 6 | }, 7 | "locked": { 8 | "lastModified": 1705309234, 9 | "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=", 10 | "owner": "numtide", 11 | "repo": "flake-utils", 12 | "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26", 13 | "type": "github" 14 | }, 15 | "original": { 16 | "owner": "numtide", 17 | "repo": "flake-utils", 18 | "type": "github" 19 | } 20 | }, 21 | "flake-utils_2": { 22 | "inputs": { 23 | "systems": "systems_2" 24 | }, 25 | "locked": { 26 | "lastModified": 1694529238, 27 | "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=", 28 | "owner": "numtide", 29 | "repo": "flake-utils", 30 | "rev": "ff7b65b44d01cf9ba6a71320833626af21126384", 31 | "type": "github" 32 | }, 33 | "original": { 34 | "owner": "numtide", 35 | "repo": "flake-utils", 36 | "type": "github" 37 | } 38 | }, 39 | "gomod2nix": { 40 | "inputs": { 41 | "flake-utils": "flake-utils_2", 42 | "nixpkgs": [ 43 | "nixpkgs" 44 | ] 45 | }, 46 | "locked": { 47 | "lastModified": 1705314449, 48 | "narHash": "sha256-yfQQ67dLejP0FLK76LKHbkzcQqNIrux6MFe32MMFGNQ=", 49 | "owner": "nix-community", 50 | "repo": "gomod2nix", 51 | "rev": "30e3c3a9ec4ac8453282ca7f67fca9e1da12c3e6", 52 | "type": "github" 53 | }, 54 | "original": { 55 | "owner": "nix-community", 56 | "repo": "gomod2nix", 57 | "type": "github" 58 | } 59 | }, 60 | "nixpkgs": { 61 | "locked": { 62 | "lastModified": 1708939921, 63 | "narHash": "sha256-lUPEqED/qB7Z7QiX542055zH17e3tzt+1+UsvPCAhoY=", 64 | "owner": "NixOS", 65 | "repo": "nixpkgs", 66 | "rev": "bfa86752190cd87271f76daffb8c7e031f0d1423", 67 | "type": "github" 68 | }, 69 | "original": { 70 | "owner": "NixOS", 71 | "ref": "master", 72 | "repo": "nixpkgs", 73 | "type": "github" 74 | } 75 | }, 76 | "root": { 77 | "inputs": { 78 | "flake-utils": "flake-utils", 79 | "gomod2nix": "gomod2nix", 80 | "nixpkgs": "nixpkgs" 81 | } 82 | }, 83 | "systems": { 84 | "locked": { 85 | "lastModified": 1681028828, 86 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 87 | "owner": "nix-systems", 88 | "repo": "default", 89 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 90 | "type": "github" 91 | }, 92 | "original": { 93 | "owner": "nix-systems", 94 | "repo": "default", 95 | "type": "github" 96 | } 97 | }, 98 | "systems_2": { 99 | "locked": { 100 | "lastModified": 1681028828, 101 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 102 | "owner": "nix-systems", 103 | "repo": "default", 104 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 105 | "type": "github" 106 | }, 107 | "original": { 108 | "owner": "nix-systems", 109 | "repo": "default", 110 | "type": "github" 111 | } 112 | } 113 | }, 114 | "root": "root", 115 | "version": 7 116 | } 117 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | nixpkgs.url = "github:NixOS/nixpkgs/master"; 4 | flake-utils.url = "github:numtide/flake-utils"; 5 | gomod2nix = { 6 | url = "github:nix-community/gomod2nix"; 7 | inputs.nixpkgs.follows = "nixpkgs"; 8 | inputs.utils.follows = "flake-utils"; 9 | }; 10 | }; 11 | 12 | outputs = { self, nixpkgs, gomod2nix, flake-utils }: 13 | { 14 | overlays.default = self: super: { 15 | rocksdb = super.rocksdb.overrideAttrs (_: rec { 16 | version = "8.9.1"; 17 | src = self.fetchFromGitHub { 18 | owner = "facebook"; 19 | repo = "rocksdb"; 20 | rev = "v${version}"; 21 | sha256 = "sha256-Pl7t4FVOvnORWFS+gjy2EEUQlPxjLukWW5I5gzCQwkI="; 22 | }; 23 | }); 24 | }; 25 | } // 26 | (flake-utils.lib.eachDefaultSystem 27 | (system: 28 | let 29 | pkgs = import nixpkgs { 30 | inherit system; 31 | config = { }; 32 | overlays = [ 33 | gomod2nix.overlays.default 34 | self.overlays.default 35 | ]; 36 | }; 37 | in 38 | rec { 39 | devShells = rec { 40 | default = with pkgs; mkShell { 41 | buildInputs = [ 42 | go_1_20 # Use Go 1.20 version 43 | rocksdb 44 | ]; 45 | }; 46 | }; 47 | legacyPackages = pkgs; 48 | } 49 | )); 50 | } 51 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/cosmos/cosmos-db 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/cockroachdb/pebble v1.1.0 7 | github.com/google/btree v1.1.2 8 | github.com/linxGnu/grocksdb v1.8.12 9 | github.com/spf13/cast v1.6.0 10 | github.com/stretchr/testify v1.8.4 11 | // Pinned to this version to avoid bugs in following commits. See https://github.com/cosmos/cosmos-sdk/pull/14952 12 | github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 13 | ) 14 | 15 | require ( 16 | github.com/DataDog/zstd v1.4.5 // indirect 17 | github.com/beorn7/perks v1.0.1 // indirect 18 | github.com/cespare/xxhash/v2 v2.2.0 // indirect 19 | github.com/cockroachdb/errors v1.11.1 // indirect 20 | github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect 21 | github.com/cockroachdb/redact v1.1.5 // indirect 22 | github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect 23 | github.com/davecgh/go-spew v1.1.1 // indirect 24 | github.com/getsentry/sentry-go v0.18.0 // indirect 25 | github.com/gogo/protobuf v1.3.2 // indirect 26 | github.com/golang/protobuf v1.5.2 // indirect 27 | github.com/golang/snappy v0.0.4 // indirect 28 | github.com/klauspost/compress v1.15.15 // indirect 29 | github.com/kr/pretty v0.3.1 // indirect 30 | github.com/kr/text v0.2.0 // indirect 31 | github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect 32 | github.com/pkg/errors v0.9.1 // indirect 33 | github.com/pmezard/go-difflib v1.0.0 // indirect 34 | github.com/prometheus/client_golang v1.12.0 // indirect 35 | github.com/prometheus/client_model v0.2.1-0.20210607210712-147c58e9608a // indirect 36 | github.com/prometheus/common v0.32.1 // indirect 37 | github.com/prometheus/procfs v0.7.3 // indirect 38 | github.com/rogpeppe/go-internal v1.9.0 // indirect 39 | golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect 40 | golang.org/x/sys v0.11.0 // indirect 41 | golang.org/x/text v0.7.0 // indirect 42 | google.golang.org/protobuf v1.28.1 // indirect 43 | gopkg.in/yaml.v3 v3.0.1 // indirect 44 | ) 45 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= 4 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= 5 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 6 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= 7 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= 8 | cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= 9 | cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= 10 | cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= 11 | cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= 12 | cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= 13 | cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= 14 | cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= 15 | cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= 16 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= 17 | cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= 18 | cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= 19 | cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= 20 | cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= 21 | cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= 22 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= 23 | cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= 24 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= 25 | cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= 26 | cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= 27 | cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= 28 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= 29 | cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= 30 | cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= 31 | cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= 32 | cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= 33 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 34 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 35 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 36 | github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ= 37 | github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= 38 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 39 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 40 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 41 | github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 42 | github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= 43 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 44 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 45 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 46 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 47 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 48 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 49 | github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 50 | github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= 51 | github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 52 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 53 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 54 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 55 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 56 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 57 | github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4= 58 | github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= 59 | github.com/cockroachdb/errors v1.11.1 h1:xSEW75zKaKCWzR3OfxXUxgrk/NtT4G1MiOv5lWZazG8= 60 | github.com/cockroachdb/errors v1.11.1/go.mod h1:8MUxA3Gi6b25tYlFEBGLf+D8aISL+M4MIpiWMSNRfxw= 61 | github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE= 62 | github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= 63 | github.com/cockroachdb/pebble v1.1.0 h1:pcFh8CdCIt2kmEpK0OIatq67Ln9uGDYY3d5XnE0LJG4= 64 | github.com/cockroachdb/pebble v1.1.0/go.mod h1:sEHm5NOXxyiAoKWhoFxT8xMgd/f3RA6qUqQ1BXKrh2E= 65 | github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30= 66 | github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= 67 | github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo= 68 | github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ= 69 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 70 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 71 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 72 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 73 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 74 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 75 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 76 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 77 | github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= 78 | github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= 79 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 80 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= 81 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 82 | github.com/getsentry/sentry-go v0.18.0 h1:MtBW5H9QgdcJabtZcuJG80BMOwaBpkRDZkxRkNC1sN0= 83 | github.com/getsentry/sentry-go v0.18.0/go.mod h1:Kgon4Mby+FJ7ZWHFUAZgVaIa8sxHtnRJRLTXZr51aKQ= 84 | github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= 85 | github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= 86 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 87 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 88 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 89 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 90 | github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 91 | github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= 92 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 93 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 94 | github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= 95 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 96 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 97 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 98 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 99 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 100 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 101 | github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 102 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 103 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 104 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 105 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 106 | github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 107 | github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 108 | github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 109 | github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= 110 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 111 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 112 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 113 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 114 | github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 115 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 116 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 117 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 118 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 119 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 120 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 121 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 122 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 123 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 124 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 125 | github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= 126 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 127 | github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= 128 | github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 129 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 130 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 131 | github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= 132 | github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= 133 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 134 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 135 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 136 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 137 | github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 138 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 139 | github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 140 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 141 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 142 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 143 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 144 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 145 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 146 | github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= 147 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 148 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 149 | github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 150 | github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 151 | github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 152 | github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 153 | github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 154 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 155 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 156 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 157 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 158 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 159 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 160 | github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 161 | github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= 162 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 163 | github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 164 | github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 165 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 166 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 167 | github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= 168 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 169 | github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= 170 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 171 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 172 | github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw= 173 | github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4= 174 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 175 | github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 176 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 177 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 178 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 179 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 180 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 181 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 182 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 183 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 184 | github.com/linxGnu/grocksdb v1.8.12 h1:1/pCztQUOa3BX/1gR3jSZDoaKFpeHFvQ1XrqZpSvZVo= 185 | github.com/linxGnu/grocksdb v1.8.12/go.mod h1:xZCIb5Muw+nhbDK4Y5UJuOrin5MceOuiXkVUR7vp4WY= 186 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 187 | github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= 188 | github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= 189 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 190 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 191 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 192 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 193 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 194 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 195 | github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 196 | github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= 197 | github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= 198 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 199 | github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= 200 | github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= 201 | github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= 202 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= 203 | github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= 204 | github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= 205 | github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= 206 | github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= 207 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 208 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 209 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 210 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 211 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 212 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 213 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 214 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 215 | github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= 216 | github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= 217 | github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= 218 | github.com/prometheus/client_golang v1.12.0 h1:C+UIj/QWtmqY13Arb8kwMt5j34/0Z2iKamrJ+ryC0Gg= 219 | github.com/prometheus/client_golang v1.12.0/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= 220 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 221 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 222 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 223 | github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 224 | github.com/prometheus/client_model v0.2.1-0.20210607210712-147c58e9608a h1:CmF68hwI0XsOQ5UwlBopMi2Ow4Pbg32akc4KIVCOm+Y= 225 | github.com/prometheus/client_model v0.2.1-0.20210607210712-147c58e9608a/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= 226 | github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 227 | github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= 228 | github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= 229 | github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= 230 | github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= 231 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 232 | github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 233 | github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= 234 | github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= 235 | github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= 236 | github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= 237 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 238 | github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= 239 | github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 240 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 241 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 242 | github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= 243 | github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= 244 | github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= 245 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 246 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 247 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 248 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 249 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 250 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 251 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 252 | github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= 253 | github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= 254 | github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 255 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 256 | github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 257 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 258 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 259 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 260 | go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 261 | go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 262 | go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 263 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 264 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 265 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 266 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 267 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 268 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 269 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 270 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 271 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 272 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= 273 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= 274 | golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 275 | golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 276 | golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 277 | golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= 278 | golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= 279 | golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME= 280 | golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= 281 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 282 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 283 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 284 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 285 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 286 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 287 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 288 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 289 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 290 | golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= 291 | golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 292 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 293 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 294 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 295 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 296 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 297 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 298 | golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 299 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 300 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 301 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 302 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 303 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 304 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 305 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 306 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 307 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 308 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 309 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 310 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 311 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 312 | golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 313 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 314 | golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 315 | golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 316 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 317 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 318 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 319 | golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 320 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 321 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 322 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 323 | golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 324 | golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 325 | golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 326 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 327 | golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 328 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 329 | golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 330 | golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 331 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 332 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 333 | golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 334 | golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= 335 | golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 336 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 337 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 338 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 339 | golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 340 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 341 | golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 342 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 343 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 344 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 345 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 346 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 347 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 348 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 349 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 350 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 351 | golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 352 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw= 353 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 354 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 355 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 356 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 357 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 358 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 359 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 360 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 361 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 362 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 363 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 364 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 365 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 366 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 367 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 368 | golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 369 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 370 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 371 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 372 | golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 373 | golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 374 | golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 375 | golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 376 | golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 377 | golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 378 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 379 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 380 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 381 | golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 382 | golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 383 | golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 384 | golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 385 | golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 386 | golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 387 | golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 388 | golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 389 | golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 390 | golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 391 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 392 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 393 | golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 394 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 395 | golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 396 | golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 397 | golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= 398 | golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 399 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 400 | golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 401 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 402 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 403 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 404 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 405 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 406 | golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= 407 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 408 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 409 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 410 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 411 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 412 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 413 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 414 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 415 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 416 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 417 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 418 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 419 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 420 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 421 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 422 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 423 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 424 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 425 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 426 | golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 427 | golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 428 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 429 | golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 430 | golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 431 | golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 432 | golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 433 | golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 434 | golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 435 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 436 | golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 437 | golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 438 | golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 439 | golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 440 | golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 441 | golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 442 | golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 443 | golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= 444 | golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 445 | golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 446 | golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 447 | golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 448 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 449 | golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 450 | golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 451 | golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 452 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 453 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 454 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 455 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 456 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 457 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 458 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 459 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= 460 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 461 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 462 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 463 | google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 464 | google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 465 | google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 466 | google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 467 | google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 468 | google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 469 | google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 470 | google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 471 | google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 472 | google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= 473 | google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= 474 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 475 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 476 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 477 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 478 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 479 | google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 480 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 481 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 482 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 483 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 484 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 485 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 486 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 487 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= 488 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 489 | google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 490 | google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 491 | google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 492 | google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 493 | google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 494 | google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= 495 | google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 496 | google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 497 | google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 498 | google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 499 | google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 500 | google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 501 | google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 502 | google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 503 | google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= 504 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 505 | google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= 506 | google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 507 | google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 508 | google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 509 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 510 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 511 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 512 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 513 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 514 | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 515 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 516 | google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 517 | google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= 518 | google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= 519 | google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 520 | google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 521 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 522 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 523 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 524 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 525 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 526 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 527 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 528 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 529 | google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= 530 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 531 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 532 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 533 | google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= 534 | google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 535 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 536 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 537 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 538 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 539 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 540 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 541 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 542 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 543 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 544 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 545 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 546 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 547 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 548 | gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 549 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 550 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 551 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 552 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 553 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 554 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 555 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 556 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 557 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 558 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 559 | honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 560 | honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 561 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= 562 | rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= 563 | rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= 564 | -------------------------------------------------------------------------------- /goleveldb.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "fmt" 5 | "path/filepath" 6 | 7 | "github.com/spf13/cast" 8 | "github.com/syndtr/goleveldb/leveldb" 9 | "github.com/syndtr/goleveldb/leveldb/errors" 10 | "github.com/syndtr/goleveldb/leveldb/filter" 11 | "github.com/syndtr/goleveldb/leveldb/opt" 12 | "github.com/syndtr/goleveldb/leveldb/util" 13 | ) 14 | 15 | func init() { 16 | dbCreator := func(name string, dir string, opts Options) (DB, error) { 17 | return NewGoLevelDB(name, dir, opts) 18 | } 19 | registerDBCreator(GoLevelDBBackend, dbCreator, false) 20 | } 21 | 22 | type GoLevelDB struct { 23 | db *leveldb.DB 24 | } 25 | 26 | var _ DB = (*GoLevelDB)(nil) 27 | 28 | func NewGoLevelDB(name string, dir string, opts Options) (*GoLevelDB, error) { 29 | defaultOpts := &opt.Options{ 30 | Filter: filter.NewBloomFilter(10), // by default, goleveldb doesn't use a bloom filter. 31 | } 32 | if opts != nil { 33 | files := cast.ToInt(opts.Get("maxopenfiles")) 34 | if files > 0 { 35 | defaultOpts.OpenFilesCacheCapacity = files 36 | } 37 | } 38 | 39 | return NewGoLevelDBWithOpts(name, dir, defaultOpts) 40 | } 41 | 42 | func NewGoLevelDBWithOpts(name string, dir string, o *opt.Options) (*GoLevelDB, error) { 43 | dbPath := filepath.Join(dir, name+DBFileSuffix) 44 | db, err := leveldb.OpenFile(dbPath, o) 45 | if err != nil { 46 | return nil, err 47 | } 48 | database := &GoLevelDB{ 49 | db: db, 50 | } 51 | return database, nil 52 | } 53 | 54 | // Get implements DB. 55 | func (db *GoLevelDB) Get(key []byte) ([]byte, error) { 56 | if len(key) == 0 { 57 | return nil, errKeyEmpty 58 | } 59 | res, err := db.db.Get(key, nil) 60 | if err != nil { 61 | if err == errors.ErrNotFound { 62 | return nil, nil 63 | } 64 | return nil, err 65 | } 66 | return res, nil 67 | } 68 | 69 | // Has implements DB. 70 | func (db *GoLevelDB) Has(key []byte) (bool, error) { 71 | bytes, err := db.Get(key) 72 | if err != nil { 73 | return false, err 74 | } 75 | return bytes != nil, nil 76 | } 77 | 78 | // Set implements DB. 79 | func (db *GoLevelDB) Set(key []byte, value []byte) error { 80 | if len(key) == 0 { 81 | return errKeyEmpty 82 | } 83 | if value == nil { 84 | return errValueNil 85 | } 86 | if err := db.db.Put(key, value, nil); err != nil { 87 | return err 88 | } 89 | return nil 90 | } 91 | 92 | // SetSync implements DB. 93 | func (db *GoLevelDB) SetSync(key []byte, value []byte) error { 94 | if len(key) == 0 { 95 | return errKeyEmpty 96 | } 97 | if value == nil { 98 | return errValueNil 99 | } 100 | if err := db.db.Put(key, value, &opt.WriteOptions{Sync: true}); err != nil { 101 | return err 102 | } 103 | return nil 104 | } 105 | 106 | // Delete implements DB. 107 | func (db *GoLevelDB) Delete(key []byte) error { 108 | if len(key) == 0 { 109 | return errKeyEmpty 110 | } 111 | if err := db.db.Delete(key, nil); err != nil { 112 | return err 113 | } 114 | return nil 115 | } 116 | 117 | // DeleteSync implements DB. 118 | func (db *GoLevelDB) DeleteSync(key []byte) error { 119 | if len(key) == 0 { 120 | return errKeyEmpty 121 | } 122 | err := db.db.Delete(key, &opt.WriteOptions{Sync: true}) 123 | if err != nil { 124 | return err 125 | } 126 | return nil 127 | } 128 | 129 | func (db *GoLevelDB) DB() *leveldb.DB { 130 | return db.db 131 | } 132 | 133 | // Close implements DB. 134 | func (db *GoLevelDB) Close() error { 135 | if err := db.db.Close(); err != nil { 136 | return err 137 | } 138 | return nil 139 | } 140 | 141 | // Print implements DB. 142 | func (db *GoLevelDB) Print() error { 143 | str, err := db.db.GetProperty("leveldb.stats") 144 | if err != nil { 145 | return err 146 | } 147 | fmt.Printf("%v\n", str) 148 | 149 | itr := db.db.NewIterator(nil, nil) 150 | for itr.Next() { 151 | key := itr.Key() 152 | value := itr.Value() 153 | fmt.Printf("[%X]:\t[%X]\n", key, value) 154 | } 155 | return nil 156 | } 157 | 158 | // Stats implements DB. 159 | func (db *GoLevelDB) Stats() map[string]string { 160 | keys := []string{ 161 | "leveldb.num-files-at-level{n}", 162 | "leveldb.stats", 163 | "leveldb.sstables", 164 | "leveldb.blockpool", 165 | "leveldb.cachedblock", 166 | "leveldb.openedtables", 167 | "leveldb.alivesnaps", 168 | "leveldb.aliveiters", 169 | } 170 | 171 | stats := make(map[string]string) 172 | for _, key := range keys { 173 | str, err := db.db.GetProperty(key) 174 | if err == nil { 175 | stats[key] = str 176 | } 177 | } 178 | return stats 179 | } 180 | 181 | func (db *GoLevelDB) ForceCompact(start, limit []byte) error { 182 | return db.db.CompactRange(util.Range{Start: start, Limit: limit}) 183 | } 184 | 185 | // NewBatch implements DB. 186 | func (db *GoLevelDB) NewBatch() Batch { 187 | return newGoLevelDBBatch(db) 188 | } 189 | 190 | // NewBatchWithSize implements DB. 191 | func (db *GoLevelDB) NewBatchWithSize(size int) Batch { 192 | return newGoLevelDBBatchWithSize(db, size) 193 | } 194 | 195 | // Iterator implements DB. 196 | func (db *GoLevelDB) Iterator(start, end []byte) (Iterator, error) { 197 | if (start != nil && len(start) == 0) || (end != nil && len(end) == 0) { 198 | return nil, errKeyEmpty 199 | } 200 | itr := db.db.NewIterator(&util.Range{Start: start, Limit: end}, nil) 201 | return newGoLevelDBIterator(itr, start, end, false), nil 202 | } 203 | 204 | // ReverseIterator implements DB. 205 | func (db *GoLevelDB) ReverseIterator(start, end []byte) (Iterator, error) { 206 | if (start != nil && len(start) == 0) || (end != nil && len(end) == 0) { 207 | return nil, errKeyEmpty 208 | } 209 | itr := db.db.NewIterator(&util.Range{Start: start, Limit: end}, nil) 210 | return newGoLevelDBIterator(itr, start, end, true), nil 211 | } 212 | -------------------------------------------------------------------------------- /goleveldb_batch.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "github.com/syndtr/goleveldb/leveldb" 5 | "github.com/syndtr/goleveldb/leveldb/opt" 6 | ) 7 | 8 | type goLevelDBBatch struct { 9 | db *GoLevelDB 10 | batch *leveldb.Batch 11 | } 12 | 13 | var _ Batch = (*goLevelDBBatch)(nil) 14 | 15 | func newGoLevelDBBatch(db *GoLevelDB) *goLevelDBBatch { 16 | return &goLevelDBBatch{ 17 | db: db, 18 | batch: new(leveldb.Batch), 19 | } 20 | } 21 | 22 | func newGoLevelDBBatchWithSize(db *GoLevelDB, size int) *goLevelDBBatch { 23 | return &goLevelDBBatch{ 24 | db: db, 25 | batch: leveldb.MakeBatch(size), 26 | } 27 | } 28 | 29 | // Set implements Batch. 30 | func (b *goLevelDBBatch) Set(key, value []byte) error { 31 | if len(key) == 0 { 32 | return errKeyEmpty 33 | } 34 | if value == nil { 35 | return errValueNil 36 | } 37 | if b.batch == nil { 38 | return errBatchClosed 39 | } 40 | b.batch.Put(key, value) 41 | return nil 42 | } 43 | 44 | // Delete implements Batch. 45 | func (b *goLevelDBBatch) Delete(key []byte) error { 46 | if len(key) == 0 { 47 | return errKeyEmpty 48 | } 49 | if b.batch == nil { 50 | return errBatchClosed 51 | } 52 | b.batch.Delete(key) 53 | return nil 54 | } 55 | 56 | // Write implements Batch. 57 | func (b *goLevelDBBatch) Write() error { 58 | return b.write(false) 59 | } 60 | 61 | // WriteSync implements Batch. 62 | func (b *goLevelDBBatch) WriteSync() error { 63 | return b.write(true) 64 | } 65 | 66 | func (b *goLevelDBBatch) write(sync bool) error { 67 | if b.batch == nil { 68 | return errBatchClosed 69 | } 70 | err := b.db.db.Write(b.batch, &opt.WriteOptions{Sync: sync}) 71 | if err != nil { 72 | return err 73 | } 74 | // Make sure batch cannot be used afterwards. Callers should still call Close(), for errors. 75 | return b.Close() 76 | } 77 | 78 | // Close implements Batch. 79 | func (b *goLevelDBBatch) Close() error { 80 | if b.batch != nil { 81 | b.batch.Reset() 82 | b.batch = nil 83 | } 84 | return nil 85 | } 86 | 87 | // GetByteSize implements Batch 88 | func (b *goLevelDBBatch) GetByteSize() (int, error) { 89 | if b.batch == nil { 90 | return 0, errBatchClosed 91 | } 92 | return len(b.batch.Dump()), nil 93 | } 94 | -------------------------------------------------------------------------------- /goleveldb_iterator.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "bytes" 5 | 6 | "github.com/syndtr/goleveldb/leveldb/iterator" 7 | ) 8 | 9 | type goLevelDBIterator struct { 10 | source iterator.Iterator 11 | start []byte 12 | end []byte 13 | isReverse bool 14 | isInvalid bool 15 | } 16 | 17 | var _ Iterator = (*goLevelDBIterator)(nil) 18 | 19 | func newGoLevelDBIterator(source iterator.Iterator, start, end []byte, isReverse bool) *goLevelDBIterator { 20 | if isReverse { 21 | if end == nil { 22 | source.Last() 23 | } else { 24 | valid := source.Seek(end) 25 | if valid { 26 | eoakey := source.Key() // end or after key 27 | if bytes.Compare(end, eoakey) <= 0 { 28 | source.Prev() 29 | } 30 | } else { 31 | source.Last() 32 | } 33 | } 34 | } else { 35 | if start == nil { 36 | source.First() 37 | } else { 38 | source.Seek(start) 39 | } 40 | } 41 | return &goLevelDBIterator{ 42 | source: source, 43 | start: start, 44 | end: end, 45 | isReverse: isReverse, 46 | isInvalid: false, 47 | } 48 | } 49 | 50 | // Domain implements Iterator. 51 | func (itr *goLevelDBIterator) Domain() ([]byte, []byte) { 52 | return itr.start, itr.end 53 | } 54 | 55 | // Valid implements Iterator. 56 | func (itr *goLevelDBIterator) Valid() bool { 57 | // Once invalid, forever invalid. 58 | if itr.isInvalid { 59 | return false 60 | } 61 | 62 | // If source errors, invalid. 63 | if err := itr.Error(); err != nil { 64 | itr.isInvalid = true 65 | return false 66 | } 67 | 68 | // If source is invalid, invalid. 69 | if !itr.source.Valid() { 70 | itr.isInvalid = true 71 | return false 72 | } 73 | 74 | // If key is end or past it, invalid. 75 | start := itr.start 76 | end := itr.end 77 | key := itr.source.Key() 78 | 79 | if itr.isReverse { 80 | if start != nil && bytes.Compare(key, start) < 0 { 81 | itr.isInvalid = true 82 | return false 83 | } 84 | } else { 85 | if end != nil && bytes.Compare(end, key) <= 0 { 86 | itr.isInvalid = true 87 | return false 88 | } 89 | } 90 | 91 | // Valid 92 | return true 93 | } 94 | 95 | // Key implements Iterator. 96 | func (itr *goLevelDBIterator) Key() []byte { 97 | // Key returns a copy of the current key. 98 | // See https://github.com/syndtr/goleveldb/blob/52c212e6c196a1404ea59592d3f1c227c9f034b2/leveldb/iterator/iter.go#L88 99 | itr.assertIsValid() 100 | return cp(itr.source.Key()) 101 | } 102 | 103 | // Value implements Iterator. 104 | func (itr *goLevelDBIterator) Value() []byte { 105 | // Value returns a copy of the current value. 106 | // See https://github.com/syndtr/goleveldb/blob/52c212e6c196a1404ea59592d3f1c227c9f034b2/leveldb/iterator/iter.go#L88 107 | itr.assertIsValid() 108 | return cp(itr.source.Value()) 109 | } 110 | 111 | // Next implements Iterator. 112 | func (itr *goLevelDBIterator) Next() { 113 | itr.assertIsValid() 114 | if itr.isReverse { 115 | itr.source.Prev() 116 | } else { 117 | itr.source.Next() 118 | } 119 | } 120 | 121 | // Error implements Iterator. 122 | func (itr *goLevelDBIterator) Error() error { 123 | return itr.source.Error() 124 | } 125 | 126 | // Close implements Iterator. 127 | func (itr *goLevelDBIterator) Close() error { 128 | itr.source.Release() 129 | return nil 130 | } 131 | 132 | func (itr goLevelDBIterator) assertIsValid() { 133 | if !itr.Valid() { 134 | panic("iterator is invalid") 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /goleveldb_test.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/require" 8 | "github.com/syndtr/goleveldb/leveldb/opt" 9 | ) 10 | 11 | func TestGoLevelDBNewGoLevelDB(t *testing.T) { 12 | name := fmt.Sprintf("test_%x", randStr(12)) 13 | defer cleanupDBDir("", name) 14 | 15 | // Test we can't open the db twice for writing 16 | wr1, err := NewGoLevelDB(name, "", nil) 17 | require.Nil(t, err) 18 | _, err = NewGoLevelDB(name, "", nil) 19 | require.NotNil(t, err) 20 | wr1.Close() // Close the db to release the lock 21 | 22 | // Test we can open the db twice for reading only 23 | ro1, err := NewGoLevelDBWithOpts(name, "", &opt.Options{ReadOnly: true}) 24 | require.Nil(t, err) 25 | defer ro1.Close() 26 | ro2, err := NewGoLevelDBWithOpts(name, "", &opt.Options{ReadOnly: true}) 27 | require.Nil(t, err) 28 | defer ro2.Close() 29 | } 30 | 31 | func BenchmarkGoLevelDBRandomReadsWrites(b *testing.B) { 32 | name := fmt.Sprintf("test_%x", randStr(12)) 33 | db, err := NewGoLevelDB(name, "", nil) 34 | if err != nil { 35 | b.Fatal(err) 36 | } 37 | defer func() { 38 | db.Close() 39 | cleanupDBDir("", name) 40 | }() 41 | 42 | benchmarkRandomReadsWrites(b, db) 43 | } 44 | -------------------------------------------------------------------------------- /memdb.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "sync" 7 | 8 | "github.com/google/btree" 9 | ) 10 | 11 | const ( 12 | // The approximate number of items and children per B-tree node. Tuned with benchmarks. 13 | bTreeDegree = 32 14 | ) 15 | 16 | func init() { 17 | registerDBCreator(MemDBBackend, func(name, dir string, opts Options) (DB, error) { 18 | return NewMemDB(), nil 19 | }, false) 20 | } 21 | 22 | // item is a btree.Item with byte slices as keys and values 23 | type item struct { 24 | key []byte 25 | value []byte 26 | } 27 | 28 | // Less implements btree.Item. 29 | func (i item) Less(other btree.Item) bool { 30 | // this considers nil == []byte{}, but that's ok since we handle nil endpoints 31 | // in iterators specially anyway 32 | return bytes.Compare(i.key, other.(item).key) == -1 33 | } 34 | 35 | // newKey creates a new key item. 36 | func newKey(key []byte) item { 37 | return item{key: key} 38 | } 39 | 40 | // newPair creates a new pair item. 41 | func newPair(key, value []byte) item { 42 | return item{key: key, value: value} 43 | } 44 | 45 | // MemDB is an in-memory database backend using a B-tree for storage. 46 | // 47 | // For performance reasons, all given and returned keys and values are pointers to the in-memory 48 | // database, so modifying them will cause the stored values to be modified as well. All DB methods 49 | // already specify that keys and values should be considered read-only, but this is especially 50 | // important with MemDB. 51 | type MemDB struct { 52 | mtx sync.RWMutex 53 | btree *btree.BTree 54 | } 55 | 56 | var _ DB = (*MemDB)(nil) 57 | 58 | // NewMemDB creates a new in-memory database. 59 | func NewMemDB() *MemDB { 60 | database := &MemDB{ 61 | btree: btree.New(bTreeDegree), 62 | } 63 | return database 64 | } 65 | 66 | // Get implements DB. 67 | func (db *MemDB) Get(key []byte) ([]byte, error) { 68 | if len(key) == 0 { 69 | return nil, errKeyEmpty 70 | } 71 | db.mtx.RLock() 72 | defer db.mtx.RUnlock() 73 | 74 | i := db.btree.Get(newKey(key)) 75 | if i != nil { 76 | return i.(item).value, nil 77 | } 78 | return nil, nil 79 | } 80 | 81 | // Has implements DB. 82 | func (db *MemDB) Has(key []byte) (bool, error) { 83 | if len(key) == 0 { 84 | return false, errKeyEmpty 85 | } 86 | db.mtx.RLock() 87 | defer db.mtx.RUnlock() 88 | 89 | return db.btree.Has(newKey(key)), nil 90 | } 91 | 92 | // Set implements DB. 93 | func (db *MemDB) Set(key []byte, value []byte) error { 94 | if len(key) == 0 { 95 | return errKeyEmpty 96 | } 97 | if value == nil { 98 | return errValueNil 99 | } 100 | db.mtx.Lock() 101 | defer db.mtx.Unlock() 102 | 103 | db.set(key, value) 104 | return nil 105 | } 106 | 107 | // set sets a value without locking the mutex. 108 | func (db *MemDB) set(key []byte, value []byte) { 109 | db.btree.ReplaceOrInsert(newPair(key, value)) 110 | } 111 | 112 | // SetSync implements DB. 113 | func (db *MemDB) SetSync(key []byte, value []byte) error { 114 | return db.Set(key, value) 115 | } 116 | 117 | // Delete implements DB. 118 | func (db *MemDB) Delete(key []byte) error { 119 | if len(key) == 0 { 120 | return errKeyEmpty 121 | } 122 | db.mtx.Lock() 123 | defer db.mtx.Unlock() 124 | 125 | db.delete(key) 126 | return nil 127 | } 128 | 129 | // delete deletes a key without locking the mutex. 130 | func (db *MemDB) delete(key []byte) { 131 | db.btree.Delete(newKey(key)) 132 | } 133 | 134 | // DeleteSync implements DB. 135 | func (db *MemDB) DeleteSync(key []byte) error { 136 | return db.Delete(key) 137 | } 138 | 139 | // Close implements DB. 140 | func (db *MemDB) Close() error { 141 | // Close is a noop since for an in-memory database, we don't have a destination to flush 142 | // contents to nor do we want any data loss on invoking Close(). 143 | // See the discussion in https://github.com/tendermint/tendermint/libs/pull/56 144 | return nil 145 | } 146 | 147 | // Print implements DB. 148 | func (db *MemDB) Print() error { 149 | db.mtx.RLock() 150 | defer db.mtx.RUnlock() 151 | 152 | db.btree.Ascend(func(i btree.Item) bool { 153 | item := i.(item) 154 | fmt.Printf("[%X]:\t[%X]\n", item.key, item.value) 155 | return true 156 | }) 157 | return nil 158 | } 159 | 160 | // Stats implements DB. 161 | func (db *MemDB) Stats() map[string]string { 162 | db.mtx.RLock() 163 | defer db.mtx.RUnlock() 164 | 165 | stats := make(map[string]string) 166 | stats["database.type"] = "memDB" 167 | stats["database.size"] = fmt.Sprintf("%d", db.btree.Len()) 168 | return stats 169 | } 170 | 171 | // NewBatch implements DB. 172 | func (db *MemDB) NewBatch() Batch { 173 | return newMemDBBatch(db) 174 | } 175 | 176 | // NewBatchWithSize implements DB. 177 | // It does the same thing as NewBatch because we can't pre-allocate memDBBatch 178 | func (db *MemDB) NewBatchWithSize(_ int) Batch { 179 | return newMemDBBatch(db) 180 | } 181 | 182 | // Iterator implements DB. 183 | // Takes out a read-lock on the database until the iterator is closed. 184 | func (db *MemDB) Iterator(start, end []byte) (Iterator, error) { 185 | if (start != nil && len(start) == 0) || (end != nil && len(end) == 0) { 186 | return nil, errKeyEmpty 187 | } 188 | return newMemDBIterator(db, start, end, false), nil 189 | } 190 | 191 | // ReverseIterator implements DB. 192 | // Takes out a read-lock on the database until the iterator is closed. 193 | func (db *MemDB) ReverseIterator(start, end []byte) (Iterator, error) { 194 | if (start != nil && len(start) == 0) || (end != nil && len(end) == 0) { 195 | return nil, errKeyEmpty 196 | } 197 | return newMemDBIterator(db, start, end, true), nil 198 | } 199 | 200 | // IteratorNoMtx makes an iterator with no mutex. 201 | func (db *MemDB) IteratorNoMtx(start, end []byte) (Iterator, error) { 202 | if (start != nil && len(start) == 0) || (end != nil && len(end) == 0) { 203 | return nil, errKeyEmpty 204 | } 205 | return newMemDBIteratorMtxChoice(db, start, end, false, false), nil 206 | } 207 | 208 | // ReverseIteratorNoMtx makes an iterator with no mutex. 209 | func (db *MemDB) ReverseIteratorNoMtx(start, end []byte) (Iterator, error) { 210 | if (start != nil && len(start) == 0) || (end != nil && len(end) == 0) { 211 | return nil, errKeyEmpty 212 | } 213 | return newMemDBIteratorMtxChoice(db, start, end, true, false), nil 214 | } 215 | -------------------------------------------------------------------------------- /memdb_batch.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import "fmt" 4 | 5 | // memDBBatch operations 6 | type opType int 7 | 8 | const ( 9 | opTypeSet opType = iota + 1 10 | opTypeDelete 11 | ) 12 | 13 | type operation struct { 14 | opType 15 | key []byte 16 | value []byte 17 | } 18 | 19 | // memDBBatch handles in-memory batching. 20 | type memDBBatch struct { 21 | db *MemDB 22 | ops []operation 23 | size int 24 | } 25 | 26 | var _ Batch = (*memDBBatch)(nil) 27 | 28 | // newMemDBBatch creates a new memDBBatch 29 | func newMemDBBatch(db *MemDB) *memDBBatch { 30 | return &memDBBatch{ 31 | db: db, 32 | ops: []operation{}, 33 | size: 0, 34 | } 35 | } 36 | 37 | // Set implements Batch. 38 | func (b *memDBBatch) Set(key, value []byte) error { 39 | if len(key) == 0 { 40 | return errKeyEmpty 41 | } 42 | if value == nil { 43 | return errValueNil 44 | } 45 | if b.ops == nil { 46 | return errBatchClosed 47 | } 48 | b.size += len(key) + len(value) 49 | b.ops = append(b.ops, operation{opTypeSet, key, value}) 50 | return nil 51 | } 52 | 53 | // Delete implements Batch. 54 | func (b *memDBBatch) Delete(key []byte) error { 55 | if len(key) == 0 { 56 | return errKeyEmpty 57 | } 58 | if b.ops == nil { 59 | return errBatchClosed 60 | } 61 | b.size += len(key) 62 | b.ops = append(b.ops, operation{opTypeDelete, key, nil}) 63 | return nil 64 | } 65 | 66 | // Write implements Batch. 67 | func (b *memDBBatch) Write() error { 68 | if b.ops == nil { 69 | return errBatchClosed 70 | } 71 | b.db.mtx.Lock() 72 | defer b.db.mtx.Unlock() 73 | 74 | for _, op := range b.ops { 75 | switch op.opType { 76 | case opTypeSet: 77 | b.db.set(op.key, op.value) 78 | case opTypeDelete: 79 | b.db.delete(op.key) 80 | default: 81 | return fmt.Errorf("unknown operation type %v (%v)", op.opType, op) 82 | } 83 | } 84 | 85 | // Make sure batch cannot be used afterwards. Callers should still call Close(), for errors. 86 | return b.Close() 87 | } 88 | 89 | // WriteSync implements Batch. 90 | func (b *memDBBatch) WriteSync() error { 91 | return b.Write() 92 | } 93 | 94 | // Close implements Batch. 95 | func (b *memDBBatch) Close() error { 96 | b.ops = nil 97 | b.size = 0 98 | return nil 99 | } 100 | 101 | // GetByteSize implements Batch 102 | func (b *memDBBatch) GetByteSize() (int, error) { 103 | if b.ops == nil { 104 | return 0, errBatchClosed 105 | } 106 | return b.size, nil 107 | } 108 | -------------------------------------------------------------------------------- /memdb_iterator.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | 7 | "github.com/google/btree" 8 | ) 9 | 10 | const ( 11 | // Size of the channel buffer between traversal goroutine and iterator. Using an unbuffered 12 | // channel causes two context switches per item sent, while buffering allows more work per 13 | // context switch. Tuned with benchmarks. 14 | chBufferSize = 64 15 | ) 16 | 17 | // memDBIterator is a memDB iterator. 18 | type memDBIterator struct { 19 | ch <-chan *item 20 | cancel context.CancelFunc 21 | item *item 22 | start []byte 23 | end []byte 24 | useMtx bool 25 | } 26 | 27 | var _ Iterator = (*memDBIterator)(nil) 28 | 29 | // newMemDBIterator creates a new memDBIterator. 30 | func newMemDBIterator(db *MemDB, start []byte, end []byte, reverse bool) *memDBIterator { 31 | return newMemDBIteratorMtxChoice(db, start, end, reverse, true) 32 | } 33 | 34 | func newMemDBIteratorMtxChoice(db *MemDB, start []byte, end []byte, reverse bool, useMtx bool) *memDBIterator { 35 | ctx, cancel := context.WithCancel(context.Background()) 36 | ch := make(chan *item, chBufferSize) 37 | iter := &memDBIterator{ 38 | ch: ch, 39 | cancel: cancel, 40 | start: start, 41 | end: end, 42 | useMtx: useMtx, 43 | } 44 | 45 | if useMtx { 46 | db.mtx.RLock() 47 | } 48 | go func() { 49 | if useMtx { 50 | defer db.mtx.RUnlock() 51 | } 52 | // Because we use [start, end) for reverse ranges, while btree uses (start, end], we need 53 | // the following variables to handle some reverse iteration conditions ourselves. 54 | var ( 55 | skipEqual []byte 56 | abortLessThan []byte 57 | ) 58 | visitor := func(i btree.Item) bool { 59 | item := i.(item) 60 | if skipEqual != nil && bytes.Equal(item.key, skipEqual) { 61 | skipEqual = nil 62 | return true 63 | } 64 | if abortLessThan != nil && bytes.Compare(item.key, abortLessThan) == -1 { 65 | return false 66 | } 67 | select { 68 | case <-ctx.Done(): 69 | return false 70 | case ch <- &item: 71 | return true 72 | } 73 | } 74 | switch { 75 | case start == nil && end == nil && !reverse: 76 | db.btree.Ascend(visitor) 77 | case start == nil && end == nil && reverse: 78 | db.btree.Descend(visitor) 79 | case end == nil && !reverse: 80 | // must handle this specially, since nil is considered less than anything else 81 | db.btree.AscendGreaterOrEqual(newKey(start), visitor) 82 | case !reverse: 83 | db.btree.AscendRange(newKey(start), newKey(end), visitor) 84 | case end == nil: 85 | // abort after start, since we use [start, end) while btree uses (start, end] 86 | abortLessThan = start 87 | db.btree.Descend(visitor) 88 | default: 89 | // skip end and abort after start, since we use [start, end) while btree uses (start, end] 90 | skipEqual = end 91 | abortLessThan = start 92 | db.btree.DescendLessOrEqual(newKey(end), visitor) 93 | } 94 | close(ch) 95 | }() 96 | 97 | // prime the iterator with the first value, if any 98 | if item, ok := <-ch; ok { 99 | iter.item = item 100 | } 101 | 102 | return iter 103 | } 104 | 105 | // Close implements Iterator. 106 | func (i *memDBIterator) Close() error { 107 | i.cancel() 108 | for range i.ch { //nolint:revive 109 | } // drain channel 110 | i.item = nil 111 | return nil 112 | } 113 | 114 | // Domain implements Iterator. 115 | func (i *memDBIterator) Domain() ([]byte, []byte) { 116 | return i.start, i.end 117 | } 118 | 119 | // Valid implements Iterator. 120 | func (i *memDBIterator) Valid() bool { 121 | return i.item != nil 122 | } 123 | 124 | // Next implements Iterator. 125 | func (i *memDBIterator) Next() { 126 | i.assertIsValid() 127 | item, ok := <-i.ch 128 | switch { 129 | case ok: 130 | i.item = item 131 | default: 132 | i.item = nil 133 | } 134 | } 135 | 136 | // Error implements Iterator. 137 | func (i *memDBIterator) Error() error { 138 | return nil // famous last words 139 | } 140 | 141 | // Key implements Iterator. 142 | func (i *memDBIterator) Key() []byte { 143 | i.assertIsValid() 144 | return i.item.key 145 | } 146 | 147 | // Value implements Iterator. 148 | func (i *memDBIterator) Value() []byte { 149 | i.assertIsValid() 150 | return i.item.value 151 | } 152 | 153 | func (i *memDBIterator) assertIsValid() { 154 | if !i.Valid() { 155 | panic("iterator is invalid") 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /memdb_test.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func BenchmarkMemDBRangeScans1M(b *testing.B) { 8 | db := NewMemDB() 9 | defer db.Close() 10 | 11 | benchmarkRangeScans(b, db, int64(1e6)) 12 | } 13 | 14 | func BenchmarkMemDBRangeScans10M(b *testing.B) { 15 | db := NewMemDB() 16 | defer db.Close() 17 | 18 | benchmarkRangeScans(b, db, int64(10e6)) 19 | } 20 | 21 | func BenchmarkMemDBRandomReadsWrites(b *testing.B) { 22 | db := NewMemDB() 23 | defer db.Close() 24 | 25 | benchmarkRandomReadsWrites(b, db) 26 | } 27 | -------------------------------------------------------------------------------- /pebble.go: -------------------------------------------------------------------------------- 1 | //go:build pebbledb 2 | 3 | package db 4 | 5 | import ( 6 | "bytes" 7 | "fmt" 8 | "path/filepath" 9 | 10 | "github.com/cockroachdb/pebble" 11 | "github.com/spf13/cast" 12 | ) 13 | 14 | // ForceSync 15 | /* 16 | This is set at compile time. Could be 0 or 1, defaults is 0. 17 | It will force using Sync for NoSync functions (Set, Delete, Write) 18 | 19 | Used as a workaround for chain-upgrade issue: At the upgrade-block, the sdk will panic without flushing data to disk or 20 | closing dbs properly. 21 | 22 | Upgrade guide: 23 | 1. After seeing `UPGRADE "xxxx" NEED at height....`, restart current version with `-X github.com/tendermint/tm-db.ForceSync=1` 24 | 2. Restart new version as normal 25 | 26 | 27 | Example: Upgrading sifchain from v0.14.0 to v0.15.0 28 | 29 | # log: 30 | panic: UPGRADE "0.15.0" NEEDED at height: 8170210: {"binaries":{"linux/amd64":"https://github.com/Sifchain/sifnode/releases/download/v0.15.0/sifnoded-v0.15.0-linux-amd64.zip?checksum=0c03b5846c5a13dcc0d9d3127e4f0cee0aeddcf2165177b2f2e0d60dbcf1a5ea"}} 31 | 32 | # step1 33 | git reset --hard 34 | git checkout v0.14.0 35 | go mod edit -replace github.com/tendermint/tm-db=github.com/baabeetaa/tm-db@pebble 36 | go mod tidy 37 | go install -tags pebbledb -ldflags "-w -s -X github.com/cosmos/cosmos-sdk/types.DBBackend=pebbledb -X github.com/tendermint/tm-db.ForceSync=1" ./cmd/sifnoded 38 | 39 | $HOME/go/bin/sifnoded start --db_backend=pebbledb 40 | 41 | 42 | # step 2 43 | git reset --hard 44 | git checkout v0.15.0 45 | go mod edit -replace github.com/tendermint/tm-db=github.com/baabeetaa/tm-db@pebble 46 | go mod tidy 47 | go install -tags pebbledb -ldflags "-w -s -X github.com/cosmos/cosmos-sdk/types.DBBackend=pebbledb" ./cmd/sifnoded 48 | 49 | $HOME/go/bin/sifnoded start --db_backend=pebbledb 50 | 51 | */ 52 | var ( 53 | ForceSync = "0" 54 | isForceSync = false 55 | ) 56 | 57 | func init() { 58 | dbCreator := func(name string, dir string, opts Options) (DB, error) { 59 | return NewPebbleDB(name, dir, opts) 60 | } 61 | registerDBCreator(PebbleDBBackend, dbCreator, false) 62 | 63 | if ForceSync == "1" { 64 | isForceSync = true 65 | } 66 | } 67 | 68 | // PebbleDB is a PebbleDB backend. 69 | type PebbleDB struct { 70 | db *pebble.DB 71 | } 72 | 73 | var _ DB = (*PebbleDB)(nil) 74 | 75 | func NewPebbleDB(name string, dir string, opts Options) (DB, error) { 76 | do := &pebble.Options{ 77 | MaxConcurrentCompactions: func() int { return 3 }, // default 1 78 | } 79 | 80 | do.EnsureDefaults() 81 | 82 | if opts != nil { 83 | files := cast.ToInt(opts.Get("maxopenfiles")) 84 | if files > 0 { 85 | do.MaxOpenFiles = files 86 | } 87 | } 88 | 89 | dbPath := filepath.Join(dir, name+DBFileSuffix) 90 | p, err := pebble.Open(dbPath, do) 91 | if err != nil { 92 | return nil, err 93 | } 94 | return &PebbleDB{ 95 | db: p, 96 | }, err 97 | } 98 | 99 | // Get implements DB. 100 | func (db *PebbleDB) Get(key []byte) ([]byte, error) { 101 | // fmt.Println("PebbleDB.Get") 102 | if len(key) == 0 { 103 | return nil, errKeyEmpty 104 | } 105 | 106 | res, closer, err := db.db.Get(key) 107 | if err != nil { 108 | if err == pebble.ErrNotFound { 109 | return nil, nil 110 | } 111 | return nil, err 112 | } 113 | defer closer.Close() 114 | 115 | return cp(res), nil 116 | } 117 | 118 | // Has implements DB. 119 | func (db *PebbleDB) Has(key []byte) (bool, error) { 120 | // fmt.Println("PebbleDB.Has") 121 | if len(key) == 0 { 122 | return false, errKeyEmpty 123 | } 124 | bytes, err := db.Get(key) 125 | if err != nil { 126 | return false, err 127 | } 128 | return bytes != nil, nil 129 | } 130 | 131 | // Set implements DB. 132 | func (db *PebbleDB) Set(key []byte, value []byte) error { 133 | // fmt.Println("PebbleDB.Set") 134 | if len(key) == 0 { 135 | return errKeyEmpty 136 | } 137 | if value == nil { 138 | return errValueNil 139 | } 140 | 141 | wopts := pebble.NoSync 142 | if isForceSync { 143 | wopts = pebble.Sync 144 | } 145 | 146 | err := db.db.Set(key, value, wopts) 147 | if err != nil { 148 | return err 149 | } 150 | return nil 151 | } 152 | 153 | // SetSync implements DB. 154 | func (db *PebbleDB) SetSync(key []byte, value []byte) error { 155 | // fmt.Println("PebbleDB.SetSync") 156 | if len(key) == 0 { 157 | return errKeyEmpty 158 | } 159 | if value == nil { 160 | return errValueNil 161 | } 162 | err := db.db.Set(key, value, pebble.Sync) 163 | if err != nil { 164 | return err 165 | } 166 | return nil 167 | } 168 | 169 | // Delete implements DB. 170 | func (db *PebbleDB) Delete(key []byte) error { 171 | // fmt.Println("PebbleDB.Delete") 172 | if len(key) == 0 { 173 | return errKeyEmpty 174 | } 175 | 176 | wopts := pebble.NoSync 177 | if isForceSync { 178 | wopts = pebble.Sync 179 | } 180 | return db.db.Delete(key, wopts) 181 | } 182 | 183 | // DeleteSync implements DB. 184 | func (db PebbleDB) DeleteSync(key []byte) error { 185 | // fmt.Println("PebbleDB.DeleteSync") 186 | if len(key) == 0 { 187 | return errKeyEmpty 188 | } 189 | return db.db.Delete(key, pebble.Sync) 190 | } 191 | 192 | func (db *PebbleDB) DB() *pebble.DB { 193 | return db.db 194 | } 195 | 196 | // Close implements DB. 197 | func (db PebbleDB) Close() error { 198 | // fmt.Println("PebbleDB.Close") 199 | db.db.Close() 200 | return nil 201 | } 202 | 203 | // Print implements DB. 204 | func (db *PebbleDB) Print() error { 205 | itr, err := db.Iterator(nil, nil) 206 | if err != nil { 207 | return err 208 | } 209 | defer itr.Close() 210 | for ; itr.Valid(); itr.Next() { 211 | key := itr.Key() 212 | value := itr.Value() 213 | fmt.Printf("[%X]:\t[%X]\n", key, value) 214 | } 215 | return nil 216 | } 217 | 218 | // Stats implements DB. 219 | func (db *PebbleDB) Stats() map[string]string { 220 | /* 221 | keys := []string{"rocksdb.stats"} 222 | stats := make(map[string]string, len(keys)) 223 | for _, key := range keys { 224 | stats[key] = db.(key) 225 | } 226 | */ 227 | return nil 228 | } 229 | 230 | // NewBatch implements DB. 231 | func (db *PebbleDB) NewBatch() Batch { 232 | return newPebbleDBBatch(db) 233 | } 234 | 235 | // NewBatchWithSize implements DB. 236 | // It does the same thing as NewBatch because we can't pre-allocate pebbleDBBatch 237 | func (db *PebbleDB) NewBatchWithSize(size int) Batch { 238 | return newPebbleDBBatch(db) 239 | } 240 | 241 | // Iterator implements DB. 242 | func (db *PebbleDB) Iterator(start, end []byte) (Iterator, error) { 243 | // fmt.Println("PebbleDB.Iterator") 244 | if (start != nil && len(start) == 0) || (end != nil && len(end) == 0) { 245 | return nil, errKeyEmpty 246 | } 247 | o := pebble.IterOptions{ 248 | LowerBound: start, 249 | UpperBound: end, 250 | } 251 | itr, err := db.db.NewIter(&o) 252 | if err != nil { 253 | return nil, err 254 | } 255 | itr.First() 256 | 257 | return newPebbleDBIterator(itr, start, end, false), nil 258 | } 259 | 260 | // ReverseIterator implements DB. 261 | func (db *PebbleDB) ReverseIterator(start, end []byte) (Iterator, error) { 262 | // fmt.Println("PebbleDB.ReverseIterator") 263 | if (start != nil && len(start) == 0) || (end != nil && len(end) == 0) { 264 | return nil, errKeyEmpty 265 | } 266 | o := pebble.IterOptions{ 267 | LowerBound: start, 268 | UpperBound: end, 269 | } 270 | itr, err := db.db.NewIter(&o) 271 | if err != nil { 272 | return nil, err 273 | } 274 | itr.Last() 275 | return newPebbleDBIterator(itr, start, end, true), nil 276 | } 277 | 278 | var _ Batch = (*pebbleDBBatch)(nil) 279 | 280 | type pebbleDBBatch struct { 281 | db *PebbleDB 282 | batch *pebble.Batch 283 | } 284 | 285 | var _ Batch = (*pebbleDBBatch)(nil) 286 | 287 | func newPebbleDBBatch(db *PebbleDB) *pebbleDBBatch { 288 | return &pebbleDBBatch{ 289 | batch: db.db.NewBatch(), 290 | } 291 | } 292 | 293 | // Set implements Batch. 294 | func (b *pebbleDBBatch) Set(key, value []byte) error { 295 | // fmt.Println("pebbleDBBatch.Set") 296 | if len(key) == 0 { 297 | return errKeyEmpty 298 | } 299 | if value == nil { 300 | return errValueNil 301 | } 302 | if b.batch == nil { 303 | return errBatchClosed 304 | } 305 | b.batch.Set(key, value, nil) 306 | return nil 307 | } 308 | 309 | // Delete implements Batch. 310 | func (b *pebbleDBBatch) Delete(key []byte) error { 311 | // fmt.Println("pebbleDBBatch.Delete") 312 | if len(key) == 0 { 313 | return errKeyEmpty 314 | } 315 | if b.batch == nil { 316 | return errBatchClosed 317 | } 318 | b.batch.Delete(key, nil) 319 | return nil 320 | } 321 | 322 | // Write implements Batch. 323 | func (b *pebbleDBBatch) Write() error { 324 | // fmt.Println("pebbleDBBatch.Write") 325 | if b.batch == nil { 326 | return errBatchClosed 327 | } 328 | 329 | wopts := pebble.NoSync 330 | if isForceSync { 331 | wopts = pebble.Sync 332 | } 333 | err := b.batch.Commit(wopts) 334 | if err != nil { 335 | return err 336 | } 337 | // Make sure batch cannot be used afterwards. Callers should still call Close(), for errors. 338 | 339 | return b.Close() 340 | } 341 | 342 | // WriteSync implements Batch. 343 | func (b *pebbleDBBatch) WriteSync() error { 344 | // fmt.Println("pebbleDBBatch.WriteSync") 345 | if b.batch == nil { 346 | return errBatchClosed 347 | } 348 | err := b.batch.Commit(pebble.Sync) 349 | if err != nil { 350 | return err 351 | } 352 | // Make sure batch cannot be used afterwards. Callers should still call Close(), for errors. 353 | return b.Close() 354 | } 355 | 356 | // Close implements Batch. 357 | func (b *pebbleDBBatch) Close() error { 358 | // fmt.Println("pebbleDBBatch.Close") 359 | if b.batch != nil { 360 | err := b.batch.Close() 361 | if err != nil { 362 | return err 363 | } 364 | b.batch = nil 365 | } 366 | 367 | return nil 368 | } 369 | 370 | // GetByteSize implements Batch 371 | func (b *pebbleDBBatch) GetByteSize() (int, error) { 372 | if b.batch == nil { 373 | return 0, errBatchClosed 374 | } 375 | return b.batch.Len(), nil 376 | } 377 | 378 | type pebbleDBIterator struct { 379 | source *pebble.Iterator 380 | start, end []byte 381 | isReverse bool 382 | isInvalid bool 383 | } 384 | 385 | var _ Iterator = (*pebbleDBIterator)(nil) 386 | 387 | func newPebbleDBIterator(source *pebble.Iterator, start, end []byte, isReverse bool) *pebbleDBIterator { 388 | if isReverse { 389 | if end == nil { 390 | source.Last() 391 | } 392 | } else { 393 | if start == nil { 394 | source.First() 395 | } 396 | } 397 | return &pebbleDBIterator{ 398 | source: source, 399 | start: start, 400 | end: end, 401 | isReverse: isReverse, 402 | isInvalid: false, 403 | } 404 | } 405 | 406 | // Domain implements Iterator. 407 | func (itr *pebbleDBIterator) Domain() ([]byte, []byte) { 408 | // fmt.Println("pebbleDBIterator.Domain") 409 | return itr.start, itr.end 410 | } 411 | 412 | // Valid implements Iterator. 413 | func (itr *pebbleDBIterator) Valid() bool { 414 | // fmt.Println("pebbleDBIterator.Valid") 415 | // Once invalid, forever invalid. 416 | if itr.isInvalid { 417 | return false 418 | } 419 | 420 | // If source has error, invalid. 421 | if err := itr.source.Error(); err != nil { 422 | itr.isInvalid = true 423 | 424 | return false 425 | } 426 | 427 | // If source is invalid, invalid. 428 | if !itr.source.Valid() { 429 | itr.isInvalid = true 430 | 431 | return false 432 | } 433 | 434 | // If key is end or past it, invalid. 435 | start := itr.start 436 | end := itr.end 437 | key := itr.source.Key() 438 | if itr.isReverse { 439 | if start != nil && bytes.Compare(key, start) < 0 { 440 | itr.isInvalid = true 441 | 442 | return false 443 | } 444 | } else { 445 | if end != nil && bytes.Compare(end, key) <= 0 { 446 | itr.isInvalid = true 447 | 448 | return false 449 | } 450 | } 451 | 452 | // It's valid. 453 | return true 454 | } 455 | 456 | // Key implements Iterator. 457 | func (itr *pebbleDBIterator) Key() []byte { 458 | // fmt.Println("pebbleDBIterator.Key") 459 | itr.assertIsValid() 460 | return cp(itr.source.Key()) 461 | } 462 | 463 | // Value implements Iterator. 464 | func (itr *pebbleDBIterator) Value() []byte { 465 | // fmt.Println("pebbleDBIterator.Value") 466 | itr.assertIsValid() 467 | return cp(itr.source.Value()) 468 | } 469 | 470 | // Next implements Iterator. 471 | func (itr pebbleDBIterator) Next() { 472 | // fmt.Println("pebbleDBIterator.Next") 473 | itr.assertIsValid() 474 | if itr.isReverse { 475 | itr.source.Prev() 476 | } else { 477 | itr.source.Next() 478 | } 479 | } 480 | 481 | // Error implements Iterator. 482 | func (itr *pebbleDBIterator) Error() error { 483 | return itr.source.Error() 484 | } 485 | 486 | // Close implements Iterator. 487 | func (itr *pebbleDBIterator) Close() error { 488 | // fmt.Println("pebbleDBIterator.Close") 489 | return itr.source.Close() 490 | } 491 | 492 | func (itr *pebbleDBIterator) assertIsValid() { 493 | if !itr.Valid() { 494 | panic("iterator is invalid") 495 | } 496 | } 497 | -------------------------------------------------------------------------------- /pebble_test.go: -------------------------------------------------------------------------------- 1 | //go:build pebbledb 2 | 3 | package db 4 | 5 | import ( 6 | "fmt" 7 | "os" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | "github.com/stretchr/testify/require" 12 | ) 13 | 14 | func TestPebbleDBBackend(t *testing.T) { 15 | name := fmt.Sprintf("test_%x", randStr(12)) 16 | dir := os.TempDir() 17 | db, err := NewDB(name, PebbleDBBackend, dir) 18 | require.NoError(t, err) 19 | defer cleanupDBDir(dir, name) 20 | 21 | _, ok := db.(*PebbleDB) 22 | assert.True(t, ok) 23 | } 24 | 25 | // func TestPebbleDBStats(t *testing.T) { 26 | // name := fmt.Sprintf("test_%x", randStr(12)) 27 | // dir := os.TempDir() 28 | // db, err := NewDB(name, PebbleDBBackend, dir) 29 | // require.NoError(t, err) 30 | // defer cleanupDBDir(dir, name) 31 | 32 | // assert.NotEmpty(t, db.Stats()) 33 | // } 34 | 35 | func BenchmarkPebbleDBRandomReadsWrites(b *testing.B) { 36 | name := fmt.Sprintf("test_%x", randStr(12)) 37 | dir := os.TempDir() 38 | db, err := NewDB(name, PebbleDBBackend, dir) 39 | if err != nil { 40 | b.Fatal(err) 41 | } 42 | defer func() { 43 | db.Close() 44 | cleanupDBDir("", name) 45 | }() 46 | 47 | benchmarkRandomReadsWrites(b, db) 48 | } 49 | 50 | // TODO: Add tests for pebble 51 | -------------------------------------------------------------------------------- /prefixdb.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | // PrefixDB wraps a namespace of another database as a logical database. 9 | type PrefixDB struct { 10 | mtx sync.Mutex 11 | prefix []byte 12 | db DB 13 | } 14 | 15 | var _ DB = (*PrefixDB)(nil) 16 | 17 | // NewPrefixDB lets you namespace multiple DBs within a single DB. 18 | func NewPrefixDB(db DB, prefix []byte) *PrefixDB { 19 | return &PrefixDB{ 20 | prefix: prefix, 21 | db: db, 22 | } 23 | } 24 | 25 | // Get implements DB. 26 | func (pdb *PrefixDB) Get(key []byte) ([]byte, error) { 27 | if len(key) == 0 { 28 | return nil, errKeyEmpty 29 | } 30 | 31 | pkey := pdb.prefixed(key) 32 | value, err := pdb.db.Get(pkey) 33 | if err != nil { 34 | return nil, err 35 | } 36 | return value, nil 37 | } 38 | 39 | // Has implements DB. 40 | func (pdb *PrefixDB) Has(key []byte) (bool, error) { 41 | if len(key) == 0 { 42 | return false, errKeyEmpty 43 | } 44 | 45 | ok, err := pdb.db.Has(pdb.prefixed(key)) 46 | if err != nil { 47 | return ok, err 48 | } 49 | 50 | return ok, nil 51 | } 52 | 53 | // Set implements DB. 54 | func (pdb *PrefixDB) Set(key []byte, value []byte) error { 55 | if len(key) == 0 { 56 | return errKeyEmpty 57 | } 58 | if value == nil { 59 | return errValueNil 60 | } 61 | 62 | pkey := pdb.prefixed(key) 63 | if err := pdb.db.Set(pkey, value); err != nil { 64 | return err 65 | } 66 | return nil 67 | } 68 | 69 | // SetSync implements DB. 70 | func (pdb *PrefixDB) SetSync(key []byte, value []byte) error { 71 | if len(key) == 0 { 72 | return errKeyEmpty 73 | } 74 | if value == nil { 75 | return errValueNil 76 | } 77 | 78 | return pdb.db.SetSync(pdb.prefixed(key), value) 79 | } 80 | 81 | // Delete implements DB. 82 | func (pdb *PrefixDB) Delete(key []byte) error { 83 | if len(key) == 0 { 84 | return errKeyEmpty 85 | } 86 | 87 | return pdb.db.Delete(pdb.prefixed(key)) 88 | } 89 | 90 | // DeleteSync implements DB. 91 | func (pdb *PrefixDB) DeleteSync(key []byte) error { 92 | if len(key) == 0 { 93 | return errKeyEmpty 94 | } 95 | 96 | return pdb.db.DeleteSync(pdb.prefixed(key)) 97 | } 98 | 99 | // Iterator implements DB. 100 | func (pdb *PrefixDB) Iterator(start, end []byte) (Iterator, error) { 101 | if (start != nil && len(start) == 0) || (end != nil && len(end) == 0) { 102 | return nil, errKeyEmpty 103 | } 104 | 105 | var pstart, pend []byte 106 | pstart = append(cp(pdb.prefix), start...) 107 | if end == nil { 108 | pend = cpIncr(pdb.prefix) 109 | } else { 110 | pend = append(cp(pdb.prefix), end...) 111 | } 112 | itr, err := pdb.db.Iterator(pstart, pend) 113 | if err != nil { 114 | return nil, err 115 | } 116 | 117 | return newPrefixIterator(pdb.prefix, start, end, itr) 118 | } 119 | 120 | // ReverseIterator implements DB. 121 | func (pdb *PrefixDB) ReverseIterator(start, end []byte) (Iterator, error) { 122 | if (start != nil && len(start) == 0) || (end != nil && len(end) == 0) { 123 | return nil, errKeyEmpty 124 | } 125 | 126 | var pstart, pend []byte 127 | pstart = append(cp(pdb.prefix), start...) 128 | if end == nil { 129 | pend = cpIncr(pdb.prefix) 130 | } else { 131 | pend = append(cp(pdb.prefix), end...) 132 | } 133 | ritr, err := pdb.db.ReverseIterator(pstart, pend) 134 | if err != nil { 135 | return nil, err 136 | } 137 | 138 | return newPrefixIterator(pdb.prefix, start, end, ritr) 139 | } 140 | 141 | // NewBatch implements DB. 142 | func (pdb *PrefixDB) NewBatch() Batch { 143 | return newPrefixBatch(pdb.prefix, pdb.db.NewBatch()) 144 | } 145 | 146 | // NewBatchWithSize implements DB. 147 | func (pdb *PrefixDB) NewBatchWithSize(size int) Batch { 148 | return newPrefixBatch(pdb.prefix, pdb.db.NewBatchWithSize(size)) 149 | } 150 | 151 | // Close implements DB. 152 | func (pdb *PrefixDB) Close() error { 153 | pdb.mtx.Lock() 154 | defer pdb.mtx.Unlock() 155 | 156 | return pdb.db.Close() 157 | } 158 | 159 | // Print implements DB. 160 | func (pdb *PrefixDB) Print() error { 161 | fmt.Printf("prefix: %X\n", pdb.prefix) 162 | 163 | itr, err := pdb.Iterator(nil, nil) 164 | if err != nil { 165 | return err 166 | } 167 | defer itr.Close() 168 | for ; itr.Valid(); itr.Next() { 169 | key := itr.Key() 170 | value := itr.Value() 171 | fmt.Printf("[%X]:\t[%X]\n", key, value) 172 | } 173 | return nil 174 | } 175 | 176 | // Stats implements DB. 177 | func (pdb *PrefixDB) Stats() map[string]string { 178 | stats := make(map[string]string) 179 | stats["prefixdb.prefix.string"] = string(pdb.prefix) 180 | stats["prefixdb.prefix.hex"] = fmt.Sprintf("%X", pdb.prefix) 181 | source := pdb.db.Stats() 182 | for key, value := range source { 183 | stats["prefixdb.source."+key] = value 184 | } 185 | return stats 186 | } 187 | 188 | func (pdb *PrefixDB) prefixed(key []byte) []byte { 189 | return append(cp(pdb.prefix), key...) 190 | } 191 | -------------------------------------------------------------------------------- /prefixdb_batch.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | type prefixDBBatch struct { 4 | prefix []byte 5 | source Batch 6 | } 7 | 8 | var _ Batch = (*prefixDBBatch)(nil) 9 | 10 | func newPrefixBatch(prefix []byte, source Batch) prefixDBBatch { 11 | return prefixDBBatch{ 12 | prefix: prefix, 13 | source: source, 14 | } 15 | } 16 | 17 | // Set implements Batch. 18 | func (pb prefixDBBatch) Set(key, value []byte) error { 19 | if len(key) == 0 { 20 | return errKeyEmpty 21 | } 22 | if value == nil { 23 | return errValueNil 24 | } 25 | pkey := append(cp(pb.prefix), key...) 26 | return pb.source.Set(pkey, value) 27 | } 28 | 29 | // Delete implements Batch. 30 | func (pb prefixDBBatch) Delete(key []byte) error { 31 | if len(key) == 0 { 32 | return errKeyEmpty 33 | } 34 | pkey := append(cp(pb.prefix), key...) 35 | return pb.source.Delete(pkey) 36 | } 37 | 38 | // Write implements Batch. 39 | func (pb prefixDBBatch) Write() error { 40 | return pb.source.Write() 41 | } 42 | 43 | // WriteSync implements Batch. 44 | func (pb prefixDBBatch) WriteSync() error { 45 | return pb.source.WriteSync() 46 | } 47 | 48 | // Close implements Batch. 49 | func (pb prefixDBBatch) Close() error { 50 | return pb.source.Close() 51 | } 52 | 53 | // GetByteSize implements Batch 54 | func (pb prefixDBBatch) GetByteSize() (int, error) { 55 | if pb.source == nil { 56 | return 0, errBatchClosed 57 | } 58 | return pb.source.GetByteSize() 59 | } 60 | -------------------------------------------------------------------------------- /prefixdb_iterator.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | ) 7 | 8 | // IteratePrefix is a convenience function for iterating over a key domain 9 | // restricted by prefix. 10 | func IteratePrefix(db DB, prefix []byte) (Iterator, error) { 11 | var start, end []byte 12 | if len(prefix) == 0 { 13 | start = nil 14 | end = nil 15 | } else { 16 | start = cp(prefix) 17 | end = cpIncr(prefix) 18 | } 19 | itr, err := db.Iterator(start, end) 20 | if err != nil { 21 | return nil, err 22 | } 23 | return itr, nil 24 | } 25 | 26 | // Strips prefix while iterating from Iterator. 27 | type prefixDBIterator struct { 28 | prefix []byte 29 | start []byte 30 | end []byte 31 | source Iterator 32 | valid bool 33 | err error 34 | } 35 | 36 | var _ Iterator = (*prefixDBIterator)(nil) 37 | 38 | func newPrefixIterator(prefix, start, end []byte, source Iterator) (*prefixDBIterator, error) { 39 | pitrInvalid := &prefixDBIterator{ 40 | prefix: prefix, 41 | start: start, 42 | end: end, 43 | source: source, 44 | valid: false, 45 | } 46 | 47 | // Empty keys are not allowed, so if a key exists in the database that exactly matches the 48 | // prefix we need to skip it. 49 | if source.Valid() && bytes.Equal(source.Key(), prefix) { 50 | source.Next() 51 | } 52 | 53 | if !source.Valid() || !bytes.HasPrefix(source.Key(), prefix) { 54 | return pitrInvalid, nil 55 | } 56 | 57 | return &prefixDBIterator{ 58 | prefix: prefix, 59 | start: start, 60 | end: end, 61 | source: source, 62 | valid: true, 63 | }, nil 64 | } 65 | 66 | // Domain implements Iterator. 67 | func (itr *prefixDBIterator) Domain() (start []byte, end []byte) { 68 | return itr.start, itr.end 69 | } 70 | 71 | // Valid implements Iterator. 72 | func (itr *prefixDBIterator) Valid() bool { 73 | if !itr.valid || itr.err != nil || !itr.source.Valid() { 74 | return false 75 | } 76 | 77 | key := itr.source.Key() 78 | if len(key) < len(itr.prefix) || !bytes.Equal(key[:len(itr.prefix)], itr.prefix) { 79 | itr.err = fmt.Errorf("received invalid key from backend: %x (expected prefix %x)", 80 | key, itr.prefix) 81 | return false 82 | } 83 | 84 | return true 85 | } 86 | 87 | // Next implements Iterator. 88 | func (itr *prefixDBIterator) Next() { 89 | itr.assertIsValid() 90 | itr.source.Next() 91 | 92 | if !itr.source.Valid() || !bytes.HasPrefix(itr.source.Key(), itr.prefix) { 93 | itr.valid = false 94 | } else if bytes.Equal(itr.source.Key(), itr.prefix) { 95 | // Empty keys are not allowed, so if a key exists in the database that exactly matches the 96 | // prefix we need to skip it. 97 | itr.Next() 98 | } 99 | } 100 | 101 | // Next implements Iterator. 102 | func (itr *prefixDBIterator) Key() []byte { 103 | itr.assertIsValid() 104 | key := itr.source.Key() 105 | return key[len(itr.prefix):] // we have checked the key in Valid() 106 | } 107 | 108 | // Value implements Iterator. 109 | func (itr *prefixDBIterator) Value() []byte { 110 | itr.assertIsValid() 111 | return itr.source.Value() 112 | } 113 | 114 | // Error implements Iterator. 115 | func (itr *prefixDBIterator) Error() error { 116 | if err := itr.source.Error(); err != nil { 117 | return err 118 | } 119 | return itr.err 120 | } 121 | 122 | // Close implements Iterator. 123 | func (itr *prefixDBIterator) Close() error { 124 | return itr.source.Close() 125 | } 126 | 127 | func (itr *prefixDBIterator) assertIsValid() { 128 | if !itr.Valid() { 129 | panic("iterator is invalid") 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /prefixdb_test.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "bytes" 5 | "crypto/rand" 6 | "encoding/binary" 7 | "fmt" 8 | "path/filepath" 9 | "sync" 10 | "testing" 11 | 12 | "github.com/stretchr/testify/require" 13 | ) 14 | 15 | func mockDBWithStuff(t *testing.T) DB { 16 | db := NewMemDB() 17 | // Under "key" prefix 18 | require.NoError(t, db.Set(bz("key"), bz("value"))) 19 | require.NoError(t, db.Set(bz("key1"), bz("value1"))) 20 | require.NoError(t, db.Set(bz("key2"), bz("value2"))) 21 | require.NoError(t, db.Set(bz("key3"), bz("value3"))) 22 | require.NoError(t, db.Set(bz("something"), bz("else"))) 23 | require.NoError(t, db.Set(bz("k"), bz("val"))) 24 | require.NoError(t, db.Set(bz("ke"), bz("valu"))) 25 | require.NoError(t, db.Set(bz("kee"), bz("valuu"))) 26 | return db 27 | } 28 | 29 | func taskKey(i, k int) []byte { 30 | return []byte(fmt.Sprintf("task-%d-key-%d", i, k)) 31 | } 32 | 33 | func randomValue() []byte { 34 | b := make([]byte, 16) 35 | _, err := rand.Read(b) 36 | if err != nil { 37 | panic(fmt.Sprintf("random value generation failed: %v", err)) 38 | } 39 | return b 40 | } 41 | 42 | func TestGolevelDB(t *testing.T) { 43 | path := filepath.Join(t.TempDir(), "goleveldb") 44 | 45 | db, err := NewGoLevelDB(path, "", nil) 46 | require.NoError(t, err) 47 | 48 | Run(t, db) 49 | } 50 | 51 | /* We don't seem to test badger anywhere. 52 | func TestWithBadgerDB(t *testing.T) { 53 | dir := t.TempDir() 54 | path := filepath.Join(dir, "badgerdb") 55 | 56 | db, err := NewBadgerDB(path, "") 57 | require.NoError(t, err) 58 | 59 | t.Run("BadgerDB", func(t *testing.T) { Run(t, db) }) 60 | } 61 | */ 62 | 63 | func TestWithMemDB(t *testing.T) { 64 | db := NewMemDB() 65 | 66 | t.Run("MemDB", func(t *testing.T) { Run(t, db) }) 67 | } 68 | 69 | // Run generates concurrent reads and writes to db so the race detector can 70 | // verify concurrent operations are properly synchronized. 71 | // The contents of db are garbage after Run returns. 72 | func Run(t *testing.T, db DB) { 73 | t.Helper() 74 | 75 | const numWorkers = 10 76 | const numKeys = 64 77 | 78 | var wg sync.WaitGroup 79 | for i := 0; i < numWorkers; i++ { 80 | wg.Add(1) 81 | i := i 82 | go func() { 83 | defer wg.Done() 84 | 85 | // Insert a bunch of keys with random data. 86 | for k := 1; k <= numKeys; k++ { 87 | key := taskKey(i, k) // say, "task--key-" 88 | value := randomValue() 89 | if err := db.Set(key, value); err != nil { 90 | t.Errorf("Task %d: db.Set(%q=%q) failed: %v", 91 | i, string(key), string(value), err) 92 | } 93 | } 94 | 95 | // Iterate over the database to make sure our keys are there. 96 | it, err := db.Iterator(nil, nil) 97 | if err != nil { 98 | t.Errorf("Iterator[%d]: %v", i, err) 99 | return 100 | } 101 | found := make(map[string][]byte) 102 | mine := []byte(fmt.Sprintf("task-%d-", i)) 103 | for { 104 | if key := it.Key(); bytes.HasPrefix(key, mine) { 105 | found[string(key)] = it.Value() 106 | } 107 | it.Next() 108 | if !it.Valid() { 109 | break 110 | } 111 | } 112 | if err := it.Error(); err != nil { 113 | t.Errorf("Iterator[%d] reported error: %v", i, err) 114 | } 115 | if err := it.Close(); err != nil { 116 | t.Errorf("Close iterator[%d]: %v", i, err) 117 | } 118 | if len(found) != numKeys { 119 | t.Errorf("Task %d: found %d keys, wanted %d", i, len(found), numKeys) 120 | } 121 | 122 | // Delete all the keys we inserted. 123 | for key := range mine { 124 | bs := make([]byte, 4) 125 | binary.LittleEndian.PutUint32(bs, uint32(key)) 126 | if err := db.Delete(bs); err != nil { 127 | t.Errorf("Delete %q: %v", key, err) 128 | } 129 | } 130 | }() 131 | } 132 | wg.Wait() 133 | } 134 | 135 | func TestPrefixDBSimple(t *testing.T) { 136 | db := mockDBWithStuff(t) 137 | pdb := NewPrefixDB(db, bz("key")) 138 | 139 | checkValue(t, pdb, bz("key"), nil) 140 | checkValue(t, pdb, bz("key1"), nil) 141 | checkValue(t, pdb, bz("1"), bz("value1")) 142 | checkValue(t, pdb, bz("key2"), nil) 143 | checkValue(t, pdb, bz("2"), bz("value2")) 144 | checkValue(t, pdb, bz("key3"), nil) 145 | checkValue(t, pdb, bz("3"), bz("value3")) 146 | checkValue(t, pdb, bz("something"), nil) 147 | checkValue(t, pdb, bz("k"), nil) 148 | checkValue(t, pdb, bz("ke"), nil) 149 | checkValue(t, pdb, bz("kee"), nil) 150 | } 151 | 152 | func TestPrefixDBIterator1(t *testing.T) { 153 | db := mockDBWithStuff(t) 154 | pdb := NewPrefixDB(db, bz("key")) 155 | 156 | itr, err := pdb.Iterator(nil, nil) 157 | require.NoError(t, err) 158 | checkDomain(t, itr, nil, nil) 159 | checkItem(t, itr, bz("1"), bz("value1")) 160 | checkNext(t, itr, true) 161 | checkItem(t, itr, bz("2"), bz("value2")) 162 | checkNext(t, itr, true) 163 | checkItem(t, itr, bz("3"), bz("value3")) 164 | checkNext(t, itr, false) 165 | checkInvalid(t, itr) 166 | itr.Close() 167 | } 168 | 169 | func TestPrefixDBReverseIterator1(t *testing.T) { 170 | db := mockDBWithStuff(t) 171 | pdb := NewPrefixDB(db, bz("key")) 172 | 173 | itr, err := pdb.ReverseIterator(nil, nil) 174 | require.NoError(t, err) 175 | checkDomain(t, itr, nil, nil) 176 | checkItem(t, itr, bz("3"), bz("value3")) 177 | checkNext(t, itr, true) 178 | checkItem(t, itr, bz("2"), bz("value2")) 179 | checkNext(t, itr, true) 180 | checkItem(t, itr, bz("1"), bz("value1")) 181 | checkNext(t, itr, false) 182 | checkInvalid(t, itr) 183 | itr.Close() 184 | } 185 | 186 | func TestPrefixDBReverseIterator5(t *testing.T) { 187 | db := mockDBWithStuff(t) 188 | pdb := NewPrefixDB(db, bz("key")) 189 | 190 | itr, err := pdb.ReverseIterator(bz("1"), nil) 191 | require.NoError(t, err) 192 | checkDomain(t, itr, bz("1"), nil) 193 | checkItem(t, itr, bz("3"), bz("value3")) 194 | checkNext(t, itr, true) 195 | checkItem(t, itr, bz("2"), bz("value2")) 196 | checkNext(t, itr, true) 197 | checkItem(t, itr, bz("1"), bz("value1")) 198 | checkNext(t, itr, false) 199 | checkInvalid(t, itr) 200 | itr.Close() 201 | } 202 | 203 | func TestPrefixDBReverseIterator6(t *testing.T) { 204 | db := mockDBWithStuff(t) 205 | pdb := NewPrefixDB(db, bz("key")) 206 | 207 | itr, err := pdb.ReverseIterator(bz("2"), nil) 208 | require.NoError(t, err) 209 | checkDomain(t, itr, bz("2"), nil) 210 | checkItem(t, itr, bz("3"), bz("value3")) 211 | checkNext(t, itr, true) 212 | checkItem(t, itr, bz("2"), bz("value2")) 213 | checkNext(t, itr, false) 214 | checkInvalid(t, itr) 215 | itr.Close() 216 | } 217 | 218 | func TestPrefixDBReverseIterator7(t *testing.T) { 219 | db := mockDBWithStuff(t) 220 | pdb := NewPrefixDB(db, bz("key")) 221 | 222 | itr, err := pdb.ReverseIterator(nil, bz("2")) 223 | require.NoError(t, err) 224 | checkDomain(t, itr, nil, bz("2")) 225 | checkItem(t, itr, bz("1"), bz("value1")) 226 | checkNext(t, itr, false) 227 | checkInvalid(t, itr) 228 | itr.Close() 229 | } 230 | -------------------------------------------------------------------------------- /rocksdb.go: -------------------------------------------------------------------------------- 1 | //go:build rocksdb 2 | // +build rocksdb 3 | 4 | package db 5 | 6 | import ( 7 | "fmt" 8 | "path/filepath" 9 | "runtime" 10 | 11 | "github.com/linxGnu/grocksdb" 12 | "github.com/spf13/cast" 13 | ) 14 | 15 | func init() { 16 | dbCreator := func(name string, dir string, opts Options) (DB, error) { 17 | return NewRocksDB(name, dir, opts) 18 | } 19 | registerDBCreator(RocksDBBackend, dbCreator, false) 20 | } 21 | 22 | // RocksDB is a RocksDB backend. 23 | type RocksDB struct { 24 | db *grocksdb.DB 25 | ro *grocksdb.ReadOptions 26 | wo *grocksdb.WriteOptions 27 | woSync *grocksdb.WriteOptions 28 | } 29 | 30 | var _ DB = (*RocksDB)(nil) 31 | 32 | // defaultRocksdbOptions, good enough for most cases, including heavy workloads. 33 | // 1GB table cache, 512MB write buffer(may use 50% more on heavy workloads). 34 | // compression: snappy as default, need to -lsnappy to enable. 35 | func defaultRocksdbOptions() *grocksdb.Options { 36 | bbto := grocksdb.NewDefaultBlockBasedTableOptions() 37 | bbto.SetBlockCache(grocksdb.NewLRUCache(1 << 30)) 38 | bbto.SetFilterPolicy(grocksdb.NewBloomFilter(10)) 39 | 40 | rocksdbOpts := grocksdb.NewDefaultOptions() 41 | rocksdbOpts.SetBlockBasedTableFactory(bbto) 42 | // SetMaxOpenFiles to 4096 seems to provide a reliable performance boost 43 | rocksdbOpts.SetMaxOpenFiles(4096) 44 | rocksdbOpts.SetCreateIfMissing(true) 45 | rocksdbOpts.IncreaseParallelism(runtime.NumCPU()) 46 | // 1.5GB maximum memory use for writebuffer. 47 | rocksdbOpts.OptimizeLevelStyleCompaction(512 * 1024 * 1024) 48 | return rocksdbOpts 49 | } 50 | 51 | func NewRocksDB(name string, dir string, opts Options) (*RocksDB, error) { 52 | defaultOpts := defaultRocksdbOptions() 53 | 54 | if opts != nil { 55 | files := cast.ToInt(opts.Get("maxopenfiles")) 56 | if files > 0 { 57 | defaultOpts.SetMaxOpenFiles(files) 58 | } 59 | } 60 | 61 | return NewRocksDBWithOptions(name, dir, defaultOpts) 62 | } 63 | 64 | func NewRocksDBWithOptions(name string, dir string, opts *grocksdb.Options) (*RocksDB, error) { 65 | dbPath := filepath.Join(dir, name+DBFileSuffix) 66 | db, err := grocksdb.OpenDb(opts, dbPath) 67 | if err != nil { 68 | return nil, err 69 | } 70 | ro := grocksdb.NewDefaultReadOptions() 71 | wo := grocksdb.NewDefaultWriteOptions() 72 | woSync := grocksdb.NewDefaultWriteOptions() 73 | woSync.SetSync(true) 74 | return NewRocksDBWithRawDB(db, ro, wo, woSync), nil 75 | } 76 | 77 | // NewRocksDBWithRawDB lets caller has full control on how the db instance is constructed 78 | func NewRocksDBWithRawDB( 79 | db *grocksdb.DB, 80 | ro *grocksdb.ReadOptions, 81 | wo *grocksdb.WriteOptions, 82 | woSync *grocksdb.WriteOptions, 83 | ) *RocksDB { 84 | return NewRocksDBWithRaw(db, ro, wo, woSync) 85 | } 86 | 87 | // NewRocksDBWithRaw is useful if user want to create the db in read-only or seconday-standby mode, 88 | // or customize the default read/write options. 89 | func NewRocksDBWithRaw( 90 | db *grocksdb.DB, ro *grocksdb.ReadOptions, 91 | wo *grocksdb.WriteOptions, woSync *grocksdb.WriteOptions, 92 | ) *RocksDB { 93 | return &RocksDB{ 94 | db: db, 95 | ro: ro, 96 | wo: wo, 97 | woSync: woSync, 98 | } 99 | } 100 | 101 | // Get implements DB. 102 | func (db *RocksDB) Get(key []byte) ([]byte, error) { 103 | if len(key) == 0 { 104 | return nil, errKeyEmpty 105 | } 106 | res, err := db.db.Get(db.ro, key) 107 | if err != nil { 108 | return nil, err 109 | } 110 | return moveSliceToBytes(res), nil 111 | } 112 | 113 | // Has implements DB. 114 | func (db *RocksDB) Has(key []byte) (bool, error) { 115 | bytes, err := db.Get(key) 116 | if err != nil { 117 | return false, err 118 | } 119 | return bytes != nil, nil 120 | } 121 | 122 | // Set implements DB. 123 | func (db *RocksDB) Set(key []byte, value []byte) error { 124 | if len(key) == 0 { 125 | return errKeyEmpty 126 | } 127 | if value == nil { 128 | return errValueNil 129 | } 130 | return db.db.Put(db.wo, key, value) 131 | } 132 | 133 | // SetSync implements DB. 134 | func (db *RocksDB) SetSync(key []byte, value []byte) error { 135 | if len(key) == 0 { 136 | return errKeyEmpty 137 | } 138 | if value == nil { 139 | return errValueNil 140 | } 141 | return db.db.Put(db.woSync, key, value) 142 | } 143 | 144 | // Delete implements DB. 145 | func (db *RocksDB) Delete(key []byte) error { 146 | if len(key) == 0 { 147 | return errKeyEmpty 148 | } 149 | return db.db.Delete(db.wo, key) 150 | } 151 | 152 | // DeleteSync implements DB. 153 | func (db *RocksDB) DeleteSync(key []byte) error { 154 | if len(key) == 0 { 155 | return errKeyEmpty 156 | } 157 | return db.db.Delete(db.woSync, key) 158 | } 159 | 160 | func (db *RocksDB) DB() *grocksdb.DB { 161 | return db.db 162 | } 163 | 164 | // Close implements DB. 165 | func (db *RocksDB) Close() error { 166 | db.ro.Destroy() 167 | db.wo.Destroy() 168 | db.woSync.Destroy() 169 | db.db.Close() 170 | return nil 171 | } 172 | 173 | // Print implements DB. 174 | func (db *RocksDB) Print() error { 175 | itr, err := db.Iterator(nil, nil) 176 | if err != nil { 177 | return err 178 | } 179 | defer itr.Close() 180 | for ; itr.Valid(); itr.Next() { 181 | key := itr.Key() 182 | value := itr.Value() 183 | fmt.Printf("[%X]:\t[%X]\n", key, value) 184 | } 185 | return nil 186 | } 187 | 188 | // Stats implements DB. 189 | func (db *RocksDB) Stats() map[string]string { 190 | keys := []string{"rocksdb.stats"} 191 | stats := make(map[string]string, len(keys)) 192 | for _, key := range keys { 193 | stats[key] = db.db.GetProperty(key) 194 | } 195 | return stats 196 | } 197 | 198 | // NewBatch implements DB. 199 | func (db *RocksDB) NewBatch() Batch { 200 | return newRocksDBBatch(db) 201 | } 202 | 203 | // NewBatchWithSize implements DB. 204 | // It does the same thing as NewBatch because we can't pre-allocate rocksDBBatch 205 | func (db *RocksDB) NewBatchWithSize(_ int) Batch { 206 | return newRocksDBBatch(db) 207 | } 208 | 209 | // Iterator implements DB. 210 | func (db *RocksDB) Iterator(start, end []byte) (Iterator, error) { 211 | if (start != nil && len(start) == 0) || (end != nil && len(end) == 0) { 212 | return nil, errKeyEmpty 213 | } 214 | itr := db.db.NewIterator(db.ro) 215 | return newRocksDBIterator(itr, start, end, false), nil 216 | } 217 | 218 | // ReverseIterator implements DB. 219 | func (db *RocksDB) ReverseIterator(start, end []byte) (Iterator, error) { 220 | if (start != nil && len(start) == 0) || (end != nil && len(end) == 0) { 221 | return nil, errKeyEmpty 222 | } 223 | itr := db.db.NewIterator(db.ro) 224 | return newRocksDBIterator(itr, start, end, true), nil 225 | } 226 | -------------------------------------------------------------------------------- /rocksdb_batch.go: -------------------------------------------------------------------------------- 1 | //go:build rocksdb 2 | // +build rocksdb 3 | 4 | package db 5 | 6 | import "github.com/linxGnu/grocksdb" 7 | 8 | type rocksDBBatch struct { 9 | db *RocksDB 10 | batch *grocksdb.WriteBatch 11 | } 12 | 13 | var _ Batch = (*rocksDBBatch)(nil) 14 | 15 | func newRocksDBBatch(db *RocksDB) *rocksDBBatch { 16 | return &rocksDBBatch{ 17 | db: db, 18 | batch: grocksdb.NewWriteBatch(), 19 | } 20 | } 21 | 22 | // Set implements Batch. 23 | func (b *rocksDBBatch) Set(key, value []byte) error { 24 | if len(key) == 0 { 25 | return errKeyEmpty 26 | } 27 | if value == nil { 28 | return errValueNil 29 | } 30 | if b.batch == nil { 31 | return errBatchClosed 32 | } 33 | b.batch.Put(key, value) 34 | return nil 35 | } 36 | 37 | // Delete implements Batch. 38 | func (b *rocksDBBatch) Delete(key []byte) error { 39 | if len(key) == 0 { 40 | return errKeyEmpty 41 | } 42 | if b.batch == nil { 43 | return errBatchClosed 44 | } 45 | b.batch.Delete(key) 46 | return nil 47 | } 48 | 49 | // Write implements Batch. 50 | func (b *rocksDBBatch) Write() error { 51 | if b.batch == nil { 52 | return errBatchClosed 53 | } 54 | err := b.db.db.Write(b.db.wo, b.batch) 55 | if err != nil { 56 | return err 57 | } 58 | // Make sure batch cannot be used afterwards. Callers should still call Close(), for errors. 59 | b.Close() 60 | return nil 61 | } 62 | 63 | // WriteSync implements Batch. 64 | func (b *rocksDBBatch) WriteSync() error { 65 | if b.batch == nil { 66 | return errBatchClosed 67 | } 68 | err := b.db.db.Write(b.db.woSync, b.batch) 69 | if err != nil { 70 | return err 71 | } 72 | // Make sure batch cannot be used afterwards. Callers should still call Close(), for errors. 73 | return b.Close() 74 | } 75 | 76 | // Close implements Batch. 77 | func (b *rocksDBBatch) Close() error { 78 | if b.batch != nil { 79 | b.batch.Destroy() 80 | b.batch = nil 81 | } 82 | return nil 83 | } 84 | 85 | // GetByteSize implements Batch 86 | func (b *rocksDBBatch) GetByteSize() (int, error) { 87 | if b.batch == nil { 88 | return 0, errBatchClosed 89 | } 90 | return len(b.batch.Data()), nil 91 | } 92 | -------------------------------------------------------------------------------- /rocksdb_iterator.go: -------------------------------------------------------------------------------- 1 | //go:build rocksdb 2 | // +build rocksdb 3 | 4 | package db 5 | 6 | import ( 7 | "bytes" 8 | 9 | "github.com/linxGnu/grocksdb" 10 | ) 11 | 12 | type rocksDBIterator struct { 13 | source *grocksdb.Iterator 14 | start, end []byte 15 | isReverse bool 16 | isInvalid bool 17 | } 18 | 19 | var _ Iterator = (*rocksDBIterator)(nil) 20 | 21 | func newRocksDBIterator(source *grocksdb.Iterator, start, end []byte, isReverse bool) *rocksDBIterator { 22 | if isReverse { 23 | if end == nil { 24 | source.SeekToLast() 25 | } else { 26 | source.Seek(end) 27 | if source.Valid() { 28 | eoakey := moveSliceToBytes(source.Key()) // end or after key 29 | if bytes.Compare(end, eoakey) <= 0 { 30 | source.Prev() 31 | } 32 | } else { 33 | source.SeekToLast() 34 | } 35 | } 36 | } else { 37 | if start == nil { 38 | source.SeekToFirst() 39 | } else { 40 | source.Seek(start) 41 | } 42 | } 43 | return &rocksDBIterator{ 44 | source: source, 45 | start: start, 46 | end: end, 47 | isReverse: isReverse, 48 | isInvalid: false, 49 | } 50 | } 51 | 52 | // Domain implements Iterator. 53 | func (itr *rocksDBIterator) Domain() ([]byte, []byte) { 54 | return itr.start, itr.end 55 | } 56 | 57 | // Valid implements Iterator. 58 | func (itr *rocksDBIterator) Valid() bool { 59 | // Once invalid, forever invalid. 60 | if itr.isInvalid { 61 | return false 62 | } 63 | 64 | // If source has error, invalid. 65 | if err := itr.source.Err(); err != nil { 66 | itr.isInvalid = true 67 | return false 68 | } 69 | 70 | // If source is invalid, invalid. 71 | if !itr.source.Valid() { 72 | itr.isInvalid = true 73 | return false 74 | } 75 | 76 | // If key is end or past it, invalid. 77 | start := itr.start 78 | end := itr.end 79 | key := moveSliceToBytes(itr.source.Key()) 80 | if itr.isReverse { 81 | if start != nil && bytes.Compare(key, start) < 0 { 82 | itr.isInvalid = true 83 | return false 84 | } 85 | } else { 86 | if end != nil && bytes.Compare(end, key) <= 0 { 87 | itr.isInvalid = true 88 | return false 89 | } 90 | } 91 | 92 | // It's valid. 93 | return true 94 | } 95 | 96 | // Key implements Iterator. 97 | func (itr *rocksDBIterator) Key() []byte { 98 | itr.assertIsValid() 99 | return moveSliceToBytes(itr.source.Key()) 100 | } 101 | 102 | // Value implements Iterator. 103 | func (itr *rocksDBIterator) Value() []byte { 104 | itr.assertIsValid() 105 | return moveSliceToBytes(itr.source.Value()) 106 | } 107 | 108 | // Next implements Iterator. 109 | func (itr rocksDBIterator) Next() { 110 | itr.assertIsValid() 111 | if itr.isReverse { 112 | itr.source.Prev() 113 | } else { 114 | itr.source.Next() 115 | } 116 | } 117 | 118 | // Error implements Iterator. 119 | func (itr *rocksDBIterator) Error() error { 120 | return itr.source.Err() 121 | } 122 | 123 | // Close implements Iterator. 124 | func (itr *rocksDBIterator) Close() error { 125 | itr.source.Close() 126 | return nil 127 | } 128 | 129 | func (itr *rocksDBIterator) assertIsValid() { 130 | if !itr.Valid() { 131 | panic("iterator is invalid") 132 | } 133 | } 134 | 135 | // moveSliceToBytes will free the slice and copy out a go []byte 136 | // This function can be applied on *Slice returned from Key() and Value() 137 | // of an Iterator, because they are marked as freed. 138 | func moveSliceToBytes(s *grocksdb.Slice) []byte { 139 | defer s.Free() 140 | if !s.Exists() { 141 | return nil 142 | } 143 | v := make([]byte, len(s.Data())) 144 | copy(v, s.Data()) 145 | return v 146 | } 147 | -------------------------------------------------------------------------------- /rocksdb_test.go: -------------------------------------------------------------------------------- 1 | //go:build rocksdb 2 | // +build rocksdb 3 | 4 | package db 5 | 6 | import ( 7 | "fmt" 8 | "os" 9 | "path/filepath" 10 | "testing" 11 | 12 | "github.com/spf13/cast" 13 | "github.com/stretchr/testify/assert" 14 | "github.com/stretchr/testify/require" 15 | ) 16 | 17 | func TestRocksDBBackend(t *testing.T) { 18 | name := fmt.Sprintf("test_%x", randStr(12)) 19 | dir := os.TempDir() 20 | db, err := NewDB(name, RocksDBBackend, dir) 21 | require.NoError(t, err) 22 | defer cleanupDBDir(dir, name) 23 | 24 | _, ok := db.(*RocksDB) 25 | assert.True(t, ok) 26 | } 27 | 28 | func TestWithRocksDB(t *testing.T) { 29 | dir := t.TempDir() 30 | path := filepath.Join(dir, "rocksdb") 31 | 32 | db, err := NewRocksDB(path, "", nil) 33 | require.NoError(t, err) 34 | 35 | t.Run("RocksDB", func(t *testing.T) { Run(t, db) }) 36 | } 37 | 38 | func TestRocksDBStats(t *testing.T) { 39 | name := fmt.Sprintf("test_%x", randStr(12)) 40 | dir := os.TempDir() 41 | db, err := NewDB(name, RocksDBBackend, dir) 42 | require.NoError(t, err) 43 | defer cleanupDBDir(dir, name) 44 | 45 | assert.NotEmpty(t, db.Stats()) 46 | } 47 | 48 | func TestRocksDBWithOptions(t *testing.T) { 49 | dir := t.TempDir() 50 | path := filepath.Join(dir, "rocksdb") 51 | 52 | opts := make(OptionsMap, 0) 53 | opts["maxopenfiles"] = 1000 54 | 55 | defaultOpts := defaultRocksdbOptions() 56 | files := cast.ToInt(opts.Get("maxopenfiles")) 57 | defaultOpts.SetMaxOpenFiles(files) 58 | require.Equal(t, opts["maxopenfiles"], defaultOpts.GetMaxOpenFiles()) 59 | 60 | db, err := NewRocksDB(path, "", opts) 61 | require.NoError(t, err) 62 | 63 | t.Run("RocksDB", func(t *testing.T) { Run(t, db) }) 64 | } 65 | -------------------------------------------------------------------------------- /test_helpers.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import "math/rand" 4 | 5 | const ( 6 | strChars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" // 62 characters 7 | ) 8 | 9 | // For testing convenience. 10 | func bz(s string) []byte { 11 | return []byte(s) 12 | } 13 | 14 | // Str constructs a random alphanumeric string of given length. 15 | func randStr(length int) string { 16 | chars := []byte{} 17 | MAIN_LOOP: 18 | for { 19 | val := rand.Int63() //nolint:gosec 20 | for i := 0; i < 10; i++ { 21 | v := int(val & 0x3f) // rightmost 6 bits 22 | if v >= 62 { // only 62 characters in strChars 23 | val >>= 6 24 | continue 25 | } 26 | 27 | chars = append(chars, strChars[v]) 28 | if len(chars) == length { 29 | break MAIN_LOOP 30 | } 31 | val >>= 6 32 | } 33 | } 34 | 35 | return string(chars) 36 | } 37 | -------------------------------------------------------------------------------- /types.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import "errors" 4 | 5 | const DBFileSuffix = ".db" 6 | 7 | var ( 8 | // errBatchClosed is returned when a closed or written batch is used. 9 | errBatchClosed = errors.New("batch has been written or closed") 10 | 11 | // errKeyEmpty is returned when attempting to use an empty or nil key. 12 | errKeyEmpty = errors.New("key cannot be empty") 13 | 14 | // errValueNil is returned when attempting to set a nil value. 15 | errValueNil = errors.New("value cannot be nil") 16 | ) 17 | 18 | // DB is the main interface for all database backends. DBs are concurrency-safe. Callers must call 19 | // Close on the database when done. 20 | // 21 | // Keys cannot be nil or empty, while values cannot be nil. Keys and values should be considered 22 | // read-only, both when returned and when given, and must be copied before they are modified. 23 | type DB interface { 24 | // Get fetches the value of the given key, or nil if it does not exist. 25 | // CONTRACT: key, value readonly []byte 26 | Get([]byte) ([]byte, error) 27 | 28 | // Has checks if a key exists. 29 | // CONTRACT: key, value readonly []byte 30 | Has(key []byte) (bool, error) 31 | 32 | // Set sets the value for the given key, replacing it if it already exists. 33 | // CONTRACT: key, value readonly []byte 34 | Set([]byte, []byte) error 35 | 36 | // SetSync sets the value for the given key, and flushes it to storage before returning. 37 | SetSync([]byte, []byte) error 38 | 39 | // Delete deletes the key, or does nothing if the key does not exist. 40 | // CONTRACT: key readonly []byte 41 | Delete([]byte) error 42 | 43 | // DeleteSync deletes the key, and flushes the delete to storage before returning. 44 | DeleteSync([]byte) error 45 | 46 | // Iterator returns an iterator over a domain of keys, in ascending order. The caller must call 47 | // Close when done. End is exclusive, and start must be less than end. A nil start iterates 48 | // from the first key, and a nil end iterates to the last key (inclusive). Empty keys are not 49 | // valid. 50 | // CONTRACT: No writes may happen within a domain while an iterator exists over it. 51 | // CONTRACT: start, end readonly []byte 52 | Iterator(start, end []byte) (Iterator, error) 53 | 54 | // ReverseIterator returns an iterator over a domain of keys, in descending order. The caller 55 | // must call Close when done. End is exclusive, and start must be less than end. A nil end 56 | // iterates from the last key (inclusive), and a nil start iterates to the first key (inclusive). 57 | // Empty keys are not valid. 58 | // CONTRACT: No writes may happen within a domain while an iterator exists over it. 59 | // CONTRACT: start, end readonly []byte 60 | ReverseIterator(start, end []byte) (Iterator, error) 61 | 62 | // Close closes the database connection. 63 | Close() error 64 | 65 | // NewBatch creates a batch for atomic updates. The caller must call Batch.Close. 66 | NewBatch() Batch 67 | 68 | // NewBatchWithSize create a new batch for atomic updates, but with pre-allocated size. 69 | // This will does the same thing as NewBatch if the batch implementation doesn't support pre-allocation. 70 | NewBatchWithSize(int) Batch 71 | 72 | // Print is used for debugging. 73 | Print() error 74 | 75 | // Stats returns a map of property values for all keys and the size of the cache. 76 | Stats() map[string]string 77 | } 78 | 79 | // Batch represents a group of writes. They may or may not be written atomically depending on the 80 | // backend. Callers must call Close on the batch when done. 81 | // 82 | // As with DB, given keys and values should be considered read-only, and must not be modified after 83 | // passing them to the batch. 84 | type Batch interface { 85 | // Set sets a key/value pair. 86 | // CONTRACT: key, value readonly []byte 87 | Set(key, value []byte) error 88 | 89 | // Delete deletes a key/value pair. 90 | // CONTRACT: key readonly []byte 91 | Delete(key []byte) error 92 | 93 | // Write writes the batch, possibly without flushing to disk. Only Close() can be called after, 94 | // other methods will error. 95 | Write() error 96 | 97 | // WriteSync writes the batch and flushes it to disk. Only Close() can be called after, other 98 | // methods will error. 99 | WriteSync() error 100 | 101 | // Close closes the batch. It is idempotent, but calls to other methods afterwards will error. 102 | Close() error 103 | 104 | // GetByteSize that returns the current size of the batch in bytes. Depending on the implementation, 105 | // this may return the size of the underlying LSM batch, including the size of additional metadata 106 | // on top of the expected key and value total byte count. 107 | GetByteSize() (int, error) 108 | } 109 | 110 | // Iterator represents an iterator over a domain of keys. Callers must call Close when done. 111 | // No writes can happen to a domain while there exists an iterator over it, some backends may take 112 | // out database locks to ensure this will not happen. 113 | // 114 | // Callers must make sure the iterator is valid before calling any methods on it, otherwise 115 | // these methods will panic. This is in part caused by most backend databases using this convention. 116 | // 117 | // As with DB, keys and values should be considered read-only, and must be copied before they are 118 | // modified. 119 | // 120 | // Typical usage: 121 | // 122 | // var itr Iterator = ... 123 | // defer itr.Close() 124 | // 125 | // for ; itr.Valid(); itr.Next() { 126 | // k, v := itr.Key(); itr.Value() 127 | // ... 128 | // } 129 | // 130 | // if err := itr.Error(); err != nil { 131 | // ... 132 | // } 133 | type Iterator interface { 134 | // Domain returns the start (inclusive) and end (exclusive) limits of the iterator. 135 | // CONTRACT: start, end readonly []byte 136 | Domain() (start []byte, end []byte) 137 | 138 | // Valid returns whether the current iterator is valid. Once invalid, the Iterator remains 139 | // invalid forever. 140 | Valid() bool 141 | 142 | // Next moves the iterator to the next key in the database, as defined by order of iteration. 143 | // If Valid returns false, this method will panic. 144 | Next() 145 | 146 | // Key returns the key at the current position. Panics if the iterator is invalid. 147 | // CONTRACT: key readonly []byte 148 | Key() (key []byte) 149 | 150 | // Value returns the value at the current position. Panics if the iterator is invalid. 151 | // CONTRACT: value readonly []byte 152 | Value() (value []byte) 153 | 154 | // Error returns the last error encountered by the iterator, if any. 155 | Error() error 156 | 157 | // Close closes the iterator, relasing any allocated resources. 158 | Close() error 159 | } 160 | -------------------------------------------------------------------------------- /util.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "bytes" 5 | "os" 6 | ) 7 | 8 | func cp(bz []byte) (ret []byte) { 9 | ret = make([]byte, len(bz)) 10 | copy(ret, bz) 11 | return ret 12 | } 13 | 14 | // Returns a slice of the same length (big endian) 15 | // except incremented by one. 16 | // Returns nil on overflow (e.g. if bz bytes are all 0xFF) 17 | // CONTRACT: len(bz) > 0 18 | func cpIncr(bz []byte) (ret []byte) { 19 | if len(bz) == 0 { 20 | panic("cpIncr expects non-zero bz length") 21 | } 22 | ret = cp(bz) 23 | for i := len(bz) - 1; i >= 0; i-- { 24 | if ret[i] < byte(0xFF) { 25 | ret[i]++ 26 | return 27 | } 28 | ret[i] = byte(0x00) 29 | if i == 0 { 30 | // Overflow 31 | return nil 32 | } 33 | } 34 | return nil 35 | } 36 | 37 | // See DB interface documentation for more information. 38 | func IsKeyInDomain(key, start, end []byte) bool { 39 | if bytes.Compare(key, start) < 0 { 40 | return false 41 | } 42 | if end != nil && bytes.Compare(end, key) <= 0 { 43 | return false 44 | } 45 | return true 46 | } 47 | 48 | func FileExists(filePath string) bool { 49 | _, err := os.Stat(filePath) 50 | return !os.IsNotExist(err) 51 | } 52 | 53 | // OptionsMap is a stub implementing Options which can get data from a map 54 | type OptionsMap map[string]interface{} 55 | 56 | func (m OptionsMap) Get(key string) interface{} { 57 | v, ok := m[key] 58 | if !ok { 59 | return interface{}(nil) 60 | } 61 | 62 | return v 63 | } 64 | -------------------------------------------------------------------------------- /util_test.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | // Empty iterator for empty db. 12 | func TestPrefixIteratorNoMatchNil(t *testing.T) { 13 | for backend := range backends { 14 | t.Run(fmt.Sprintf("Prefix w/ backend %s", backend), func(t *testing.T) { 15 | db, dir := newTempDB(t, backend) 16 | defer os.RemoveAll(dir) 17 | itr, err := IteratePrefix(db, []byte("2")) 18 | require.NoError(t, err) 19 | 20 | checkInvalid(t, itr) 21 | }) 22 | } 23 | } 24 | 25 | // Empty iterator for db populated after iterator created. 26 | func TestPrefixIteratorNoMatch1(t *testing.T) { 27 | for backend := range backends { 28 | t.Run(fmt.Sprintf("Prefix w/ backend %s", backend), func(t *testing.T) { 29 | db, dir := newTempDB(t, backend) 30 | defer os.RemoveAll(dir) 31 | itr, err := IteratePrefix(db, []byte("2")) 32 | require.NoError(t, err) 33 | err = db.SetSync(bz("1"), bz("value_1")) 34 | require.NoError(t, err) 35 | 36 | checkInvalid(t, itr) 37 | }) 38 | } 39 | } 40 | 41 | // Empty iterator for prefix starting after db entry. 42 | func TestPrefixIteratorNoMatch2(t *testing.T) { 43 | for backend := range backends { 44 | t.Run(fmt.Sprintf("Prefix w/ backend %s", backend), func(t *testing.T) { 45 | db, dir := newTempDB(t, backend) 46 | defer os.RemoveAll(dir) 47 | err := db.SetSync(bz("3"), bz("value_3")) 48 | require.NoError(t, err) 49 | itr, err := IteratePrefix(db, []byte("4")) 50 | require.NoError(t, err) 51 | 52 | checkInvalid(t, itr) 53 | }) 54 | } 55 | } 56 | 57 | // Iterator with single val for db with single val, starting from that val. 58 | func TestPrefixIteratorMatch1(t *testing.T) { 59 | for backend := range backends { 60 | t.Run(fmt.Sprintf("Prefix w/ backend %s", backend), func(t *testing.T) { 61 | db, dir := newTempDB(t, backend) 62 | defer os.RemoveAll(dir) 63 | err := db.SetSync(bz("2"), bz("value_2")) 64 | require.NoError(t, err) 65 | itr, err := IteratePrefix(db, bz("2")) 66 | require.NoError(t, err) 67 | 68 | checkValid(t, itr, true) 69 | checkItem(t, itr, bz("2"), bz("value_2")) 70 | checkNext(t, itr, false) 71 | 72 | // Once invalid... 73 | checkInvalid(t, itr) 74 | }) 75 | } 76 | } 77 | 78 | // Iterator with prefix iterates over everything with same prefix. 79 | func TestPrefixIteratorMatches1N(t *testing.T) { 80 | for backend := range backends { 81 | t.Run(fmt.Sprintf("Prefix w/ backend %s", backend), func(t *testing.T) { 82 | db, dir := newTempDB(t, backend) 83 | defer os.RemoveAll(dir) 84 | 85 | // prefixed 86 | err := db.SetSync(bz("a/1"), bz("value_1")) 87 | require.NoError(t, err) 88 | err = db.SetSync(bz("a/3"), bz("value_3")) 89 | require.NoError(t, err) 90 | 91 | // not 92 | err = db.SetSync(bz("b/3"), bz("value_3")) 93 | require.NoError(t, err) 94 | err = db.SetSync(bz("a-3"), bz("value_3")) 95 | require.NoError(t, err) 96 | err = db.SetSync(bz("a.3"), bz("value_3")) 97 | require.NoError(t, err) 98 | err = db.SetSync(bz("abcdefg"), bz("value_3")) 99 | require.NoError(t, err) 100 | itr, err := IteratePrefix(db, bz("a/")) 101 | require.NoError(t, err) 102 | 103 | checkValid(t, itr, true) 104 | checkItem(t, itr, bz("a/1"), bz("value_1")) 105 | checkNext(t, itr, true) 106 | checkItem(t, itr, bz("a/3"), bz("value_3")) 107 | 108 | // Bad! 109 | checkNext(t, itr, false) 110 | 111 | // Once invalid... 112 | checkInvalid(t, itr) 113 | }) 114 | } 115 | } 116 | --------------------------------------------------------------------------------