├── .gitignore ├── version.json ├── .github ├── workflows │ ├── stale.yml │ ├── generated-pr.yml │ ├── tagpush.yml │ ├── releaser.yml │ ├── go-check.yml │ ├── release-check.yml │ └── go-test.yml └── actions │ └── go-test-setup │ └── action.yml ├── .travis.yml ├── go.mod ├── options.go ├── LICENSE ├── batching.go ├── README.md ├── datastore_test.go ├── datastore.go └── go.sum /.gitignore: -------------------------------------------------------------------------------- 1 | data 2 | -------------------------------------------------------------------------------- /version.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "v0.2.0" 3 | } 4 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: Close Stale Issues 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | workflow_dispatch: 7 | 8 | permissions: 9 | issues: write 10 | pull-requests: write 11 | 12 | jobs: 13 | stale: 14 | uses: ipdxco/unified-github-workflows/.github/workflows/reusable-stale-issue.yml@v1 15 | -------------------------------------------------------------------------------- /.github/workflows/generated-pr.yml: -------------------------------------------------------------------------------- 1 | name: Close Generated PRs 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | workflow_dispatch: 7 | 8 | permissions: 9 | issues: write 10 | pull-requests: write 11 | 12 | jobs: 13 | stale: 14 | uses: ipdxco/unified-github-workflows/.github/workflows/reusable-generated-pr.yml@v1 15 | -------------------------------------------------------------------------------- /.github/workflows/tagpush.yml: -------------------------------------------------------------------------------- 1 | name: Tag Push Checker 2 | 3 | on: 4 | push: 5 | tags: 6 | - v* 7 | 8 | permissions: 9 | contents: read 10 | issues: write 11 | 12 | concurrency: 13 | group: ${{ github.workflow }}-${{ github.ref }} 14 | cancel-in-progress: true 15 | 16 | jobs: 17 | releaser: 18 | uses: ipdxco/unified-github-workflows/.github/workflows/tagpush.yml@v1.0 19 | -------------------------------------------------------------------------------- /.github/workflows/releaser.yml: -------------------------------------------------------------------------------- 1 | name: Releaser 2 | 3 | on: 4 | push: 5 | paths: [ 'version.json' ] 6 | workflow_dispatch: 7 | 8 | permissions: 9 | contents: write 10 | 11 | concurrency: 12 | group: ${{ github.workflow }}-${{ github.sha }} 13 | cancel-in-progress: true 14 | 15 | jobs: 16 | releaser: 17 | uses: ipdxco/unified-github-workflows/.github/workflows/releaser.yml@v1.0 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: 2 | - linux 3 | 4 | language: go 5 | 6 | go: 7 | - 1.14.x 8 | 9 | services: 10 | - postgresql 11 | 12 | cache: 13 | directories: 14 | - $GOPATH/pkg/mod 15 | - $HOME/.cache/go-build 16 | 17 | script: 18 | - go build ./... 19 | - go test -v -race -coverprofile=coverage.txt -covermode=atomic ./... 20 | 21 | after_success: 22 | - bash <(curl -s https://codecov.io/bash) 23 | 24 | notifications: 25 | email: false 26 | -------------------------------------------------------------------------------- /.github/workflows/go-check.yml: -------------------------------------------------------------------------------- 1 | name: Go Checks 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: ["master"] 7 | workflow_dispatch: 8 | 9 | permissions: 10 | contents: read 11 | 12 | concurrency: 13 | group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event_name == 'push' && github.sha || github.ref }} 14 | cancel-in-progress: true 15 | 16 | jobs: 17 | go-check: 18 | uses: ipdxco/unified-github-workflows/.github/workflows/go-check.yml@v1.0 19 | -------------------------------------------------------------------------------- /.github/workflows/release-check.yml: -------------------------------------------------------------------------------- 1 | name: Release Checker 2 | 3 | on: 4 | pull_request_target: 5 | paths: [ 'version.json' ] 6 | types: [ opened, synchronize, reopened, labeled, unlabeled ] 7 | workflow_dispatch: 8 | 9 | permissions: 10 | contents: write 11 | pull-requests: write 12 | 13 | concurrency: 14 | group: ${{ github.workflow }}-${{ github.ref }} 15 | cancel-in-progress: true 16 | 17 | jobs: 18 | release-check: 19 | uses: ipdxco/unified-github-workflows/.github/workflows/release-check.yml@v1.0 20 | -------------------------------------------------------------------------------- /.github/workflows/go-test.yml: -------------------------------------------------------------------------------- 1 | name: Go Test 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: ["master"] 7 | workflow_dispatch: 8 | 9 | permissions: 10 | contents: read 11 | 12 | concurrency: 13 | group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event_name == 'push' && github.sha || github.ref }} 14 | cancel-in-progress: true 15 | 16 | jobs: 17 | go-test: 18 | uses: ipdxco/unified-github-workflows/.github/workflows/go-test.yml@v1.0 19 | secrets: 20 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 21 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ipfs/ipfs-ds-postgres 2 | 3 | go 1.21 4 | 5 | require ( 6 | github.com/ipfs/go-datastore v0.5.1 7 | github.com/jackc/pgx/v4 v4.6.0 8 | ) 9 | 10 | require ( 11 | github.com/google/uuid v1.1.1 // indirect 12 | github.com/ipfs/go-detect-race v0.0.1 // indirect 13 | github.com/jackc/chunkreader/v2 v2.0.1 // indirect 14 | github.com/jackc/pgconn v1.5.0 // indirect 15 | github.com/jackc/pgio v1.0.0 // indirect 16 | github.com/jackc/pgpassfile v1.0.0 // indirect 17 | github.com/jackc/pgproto3/v2 v2.0.1 // indirect 18 | github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8 // indirect 19 | github.com/jackc/pgtype v1.3.0 // indirect 20 | github.com/jackc/puddle v1.1.0 // indirect 21 | github.com/jbenet/goprocess v0.1.4 // indirect 22 | golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59 // indirect 23 | golang.org/x/text v0.3.2 // indirect 24 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 // indirect 25 | ) 26 | -------------------------------------------------------------------------------- /options.go: -------------------------------------------------------------------------------- 1 | package pgds 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // Options are Datastore options 8 | type Options struct { 9 | Table string 10 | } 11 | 12 | // Option is the Datastore option type. 13 | type Option func(*Options) error 14 | 15 | // Apply applies the given options to this Option. 16 | func (o *Options) Apply(opts ...Option) error { 17 | for i, opt := range opts { 18 | if err := opt(o); err != nil { 19 | return fmt.Errorf("datastore option %d failed: %s", i, err) 20 | } 21 | } 22 | return nil 23 | } 24 | 25 | // OptionDefaults are the default datastore options. This option will be automatically 26 | // prepended to any options you pass to the Hydra Head constructor. 27 | var OptionDefaults = func(o *Options) error { 28 | o.Table = "blocks" 29 | return nil 30 | } 31 | 32 | // Table configures the name of the postgres database table to store data in. 33 | // Defaults to "blocks". 34 | func Table(t string) Option { 35 | return func(o *Options) error { 36 | if t != "" { 37 | o.Table = t 38 | } 39 | return nil 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /.github/actions/go-test-setup/action.yml: -------------------------------------------------------------------------------- 1 | name: postgresql-and-timeout 2 | description: Start PostgreSQL and increase timeout 3 | 4 | runs: 5 | using: "composite" 6 | steps: 7 | - if: ${{ runner.os == 'Linux' }} 8 | run: | 9 | PGCONF="$(ls /etc/postgresql/*/main/pg_hba.conf)" 10 | sudo sed -i.bak -E "s/peer|scram-sha-256/trust/g" "$PGCONF" 11 | sudo service postgresql start 12 | shell: bash 13 | - if: ${{ runner.os == 'Windows' }} 14 | run: echo "$PGBIN" >> $GITHUB_PATH 15 | shell: bash 16 | - if: ${{ runner.os == 'macOS' }} 17 | run: echo "PGDATA=/usr/local/var/postgres" >> $GITHUB_ENV 18 | shell: bash 19 | - if: ${{ runner.os == 'macOS' }} 20 | run: pg_ctl -D "$PGDATA" initdb 21 | shell: bash 22 | - if: ${{ runner.os == 'Windows' || runner.os == 'macOS' }} 23 | run: pg_ctl -D "$PGDATA" -l "$PGDATA/server.log" start 24 | shell: bash 25 | - if: ${{ runner.os == 'macOS' }} 26 | run: createuser -s postgres 27 | shell: bash 28 | - run: echo "GOFLAGS=$GOFLAGS -timeout=30m" >> $GITHUB_ENV 29 | shell: bash 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Alan Shaw 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /batching.go: -------------------------------------------------------------------------------- 1 | package pgds 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | ds "github.com/ipfs/go-datastore" 8 | "github.com/jackc/pgx/v4" 9 | ) 10 | 11 | type batch struct { 12 | ds *Datastore 13 | batch *pgx.Batch 14 | } 15 | 16 | // Batch creates a set of deferred updates to the database. 17 | func (d *Datastore) Batch(_ context.Context) (ds.Batch, error) { 18 | return &batch{ds: d, batch: &pgx.Batch{}}, nil 19 | } 20 | 21 | func (b *batch) Put(ctx context.Context, key ds.Key, value []byte) error { 22 | b.batch.Queue("BEGIN") 23 | sql := fmt.Sprintf("INSERT INTO %s (key, data) VALUES ($1, $2) ON CONFLICT (key) DO UPDATE SET data = $2", b.ds.table) 24 | b.batch.Queue(sql, key.String(), value) 25 | b.batch.Queue("COMMIT") 26 | return nil 27 | } 28 | 29 | func (b *batch) Delete(ctx context.Context, key ds.Key) error { 30 | b.batch.Queue("BEGIN") 31 | b.batch.Queue(fmt.Sprintf("DELETE FROM %s WHERE key = $1", b.ds.table), key.String()) 32 | b.batch.Queue("COMMIT") 33 | return nil 34 | } 35 | 36 | func (b *batch) Commit(ctx context.Context) error { 37 | res := b.ds.pool.SendBatch(ctx, b.batch) 38 | defer res.Close() 39 | 40 | for i := 0; i < b.batch.Len(); i++ { 41 | _, err := res.Exec() 42 | if err != nil { 43 | return err 44 | } 45 | } 46 | 47 | return nil 48 | } 49 | 50 | var _ ds.Batching = (*Datastore)(nil) 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ipfs-ds-postgres 2 | 3 | [![Build Status](https://travis-ci.org/ipfs/ipfs-ds-postgres.svg?branch=master)](https://travis-ci.org/ipfs/ipfs-ds-postgres) 4 | [![Coverage](https://codecov.io/gh/ipfs/ipfs-ds-postgres/branch/master/graph/badge.svg)](https://codecov.io/gh/ipfs/ipfs-ds-postgres) 5 | [![Standard README](https://img.shields.io/badge/readme%20style-standard-brightgreen.svg)](https://github.com/RichardLitt/standard-readme) 6 | [![GoDoc](http://img.shields.io/badge/godoc-reference-5272B4.svg)](https://godoc.org/github.com/alanshaw/ipfs-ds-postgres) 7 | [![golang version](https://img.shields.io/badge/golang-%3E%3D1.14.0-orange.svg)](https://golang.org/) 8 | [![Go Report Card](https://goreportcard.com/badge/github.com/ipfs/ipfs-ds-postgres)](https://goreportcard.com/report/github.com/ipfs/ipfs-ds-postgres) 9 | 10 | > An implementation of [the datastore interface](https://github.com/ipfs/go-datastore) for PostgreSQL that uses the [pgx](https://github.com/jackc/pgx) PostgreSQL driver. 11 | 12 | **Note: Currently implements `Datastore` and `Batching` interfaces.** 13 | 14 | ## Install 15 | 16 | ```sh 17 | go get github.com/ipfs/ipfs-ds-postgres 18 | ``` 19 | 20 | ## Usage 21 | 22 | Ensure a database is created and a table exists that has the following structure (replacing `table_name` with the name of the table the datastore will use - by default this is `blocks`): 23 | 24 | ```sql 25 | CREATE TABLE IF NOT EXISTS table_name (key TEXT NOT NULL UNIQUE, data BYTEA) 26 | ``` 27 | 28 | It's recommended to create a `text_pattern_ops` index on the table: 29 | 30 | ```sql 31 | CREATE INDEX IF NOT EXISTS table_name_key_text_pattern_ops_idx ON table_name (key text_pattern_ops) 32 | ``` 33 | 34 | Import and use in your application: 35 | 36 | ```go 37 | package main 38 | 39 | import ( 40 | "context" 41 | pgds "github.com/alanshaw/ipfs-ds-postgres" 42 | ) 43 | 44 | const ( 45 | connString = "postgresql://user:pass@host:12345/database?sslmode=require" 46 | tableName = "blocks" // (default) 47 | ) 48 | 49 | func main() { 50 | ds, err := pgds.NewDatastore(context.Background(), connString, pgds.Table(tableName)) 51 | if err != nil { 52 | panic(err) 53 | } 54 | } 55 | ``` 56 | 57 | ## API 58 | 59 | [GoDoc Reference](https://godoc.org/github.com/alanshaw/ipfs-ds-postgres) 60 | 61 | ## Contribute 62 | 63 | Feel free to dive in! [Open an issue](https://github.com/alanshaw/ipfs-ds-postgres/issues/new) or submit PRs. 64 | 65 | ## License 66 | 67 | [MIT](LICENSE) © Alan Shaw 68 | -------------------------------------------------------------------------------- /datastore_test.go: -------------------------------------------------------------------------------- 1 | package pgds 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "sync" 8 | "testing" 9 | 10 | dstest "github.com/ipfs/go-datastore/test" 11 | "github.com/jackc/pgx/v4" 12 | ) 13 | 14 | var initOnce sync.Once 15 | 16 | func envString(t *testing.T, key string, defaultValue string) string { 17 | v := os.Getenv(key) 18 | if v == "" { 19 | return defaultValue 20 | } 21 | return v 22 | } 23 | 24 | // Automatically re-create the test datastore. 25 | func initPG(t *testing.T) { 26 | initOnce.Do(func() { 27 | connConf, err := pgx.ParseConfig(fmt.Sprintf( 28 | "postgres://%s:%s@%s/%s?sslmode=disable", 29 | envString(t, "PG_USER", "postgres"), 30 | envString(t, "PG_PASS", ""), 31 | envString(t, "PG_HOST", "127.0.0.1"), 32 | envString(t, "PG_DB", envString(t, "PG_USER", "postgres")), 33 | )) 34 | if err != nil { 35 | t.Fatal(err) 36 | } 37 | conn, err := pgx.ConnectConfig(context.Background(), connConf) 38 | if err != nil { 39 | t.Fatal(err) 40 | } 41 | _, err = conn.Exec(context.Background(), "DROP DATABASE IF EXISTS test_datastore") 42 | if err != nil { 43 | t.Fatal(err) 44 | } 45 | _, err = conn.Exec(context.Background(), "CREATE DATABASE test_datastore") 46 | if err != nil { 47 | t.Fatal(err) 48 | } 49 | err = conn.Close(context.Background()) 50 | if err != nil { 51 | t.Fatal(err) 52 | } 53 | }) 54 | } 55 | 56 | // returns datastore, and a function to call on exit. 57 | // 58 | // d, close := newDS(t) 59 | // defer close() 60 | func newDS(t *testing.T) (*Datastore, func()) { 61 | initPG(t) 62 | connString := fmt.Sprintf( 63 | "postgres://%s:%s@%s/%s?sslmode=disable", 64 | envString(t, "PG_USER", "postgres"), 65 | envString(t, "PG_PASS", ""), 66 | envString(t, "PG_HOST", "127.0.0.1"), 67 | "test_datastore", 68 | ) 69 | connConf, err := pgx.ParseConfig(connString) 70 | if err != nil { 71 | t.Fatal(err) 72 | } 73 | conn, err := pgx.ConnectConfig(context.Background(), connConf) 74 | if err != nil { 75 | t.Fatal(err) 76 | } 77 | _, err = conn.Exec(context.Background(), "CREATE TABLE IF NOT EXISTS blocks (key TEXT NOT NULL UNIQUE, data BYTEA)") 78 | if err != nil { 79 | t.Fatal(err) 80 | } 81 | d, err := NewDatastore(context.Background(), connString) 82 | if err != nil { 83 | t.Fatal(err) 84 | } 85 | return d, func() { 86 | _, _ = conn.Exec(context.Background(), "DROP TABLE IF EXISTS blocks") 87 | _ = conn.Close(context.Background()) 88 | } 89 | } 90 | 91 | func TestSuite(t *testing.T) { 92 | d, done := newDS(t) 93 | defer done() 94 | dstest.SubtestAll(t, d) 95 | } 96 | -------------------------------------------------------------------------------- /datastore.go: -------------------------------------------------------------------------------- 1 | package pgds 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | ds "github.com/ipfs/go-datastore" 8 | dsq "github.com/ipfs/go-datastore/query" 9 | "github.com/jackc/pgx/v4" 10 | "github.com/jackc/pgx/v4/pgxpool" 11 | ) 12 | 13 | // Datastore is a PostgreSQL backed datastore. 14 | type Datastore struct { 15 | table string 16 | pool *pgxpool.Pool 17 | } 18 | 19 | // NewDatastore creates a new PostgreSQL datastore 20 | func NewDatastore(ctx context.Context, connString string, options ...Option) (*Datastore, error) { 21 | cfg := Options{} 22 | cfg.Apply(append([]Option{OptionDefaults}, options...)...) 23 | 24 | pool, err := pgxpool.Connect(ctx, connString) 25 | if err != nil { 26 | return nil, err 27 | } 28 | 29 | return &Datastore{table: cfg.Table, pool: pool}, nil 30 | } 31 | 32 | // PgxPool exposes the underlying pool of connections to Postgres. 33 | func (d *Datastore) PgxPool() *pgxpool.Pool { 34 | return d.pool 35 | } 36 | 37 | // Close closes the underying PostgreSQL database. 38 | func (d *Datastore) Close() error { 39 | if d.pool != nil { 40 | d.pool.Close() 41 | } 42 | return nil 43 | } 44 | 45 | // Delete removes a row from the PostgreSQL database by the given key. 46 | func (d *Datastore) Delete(ctx context.Context, key ds.Key) error { 47 | sql := fmt.Sprintf("DELETE FROM %s WHERE key = $1", d.table) 48 | _, err := d.pool.Exec(ctx, sql, key.String()) 49 | if err != nil { 50 | return err 51 | } 52 | return nil 53 | } 54 | 55 | // Get retrieves a value from the PostgreSQL database by the given key. 56 | func (d *Datastore) Get(ctx context.Context, key ds.Key) (value []byte, err error) { 57 | sql := fmt.Sprintf("SELECT data FROM %s WHERE key = $1", d.table) 58 | row := d.pool.QueryRow(ctx, sql, key.String()) 59 | var out []byte 60 | switch err := row.Scan(&out); err { 61 | case pgx.ErrNoRows: 62 | return nil, ds.ErrNotFound 63 | case nil: 64 | return out, nil 65 | default: 66 | return nil, err 67 | } 68 | } 69 | 70 | // Has determines if a value for the given key exists in the PostgreSQL database. 71 | func (d *Datastore) Has(ctx context.Context, key ds.Key) (bool, error) { 72 | sql := fmt.Sprintf("SELECT exists(SELECT 1 FROM %s WHERE key = $1)", d.table) 73 | row := d.pool.QueryRow(ctx, sql, key.String()) 74 | var exists bool 75 | switch err := row.Scan(&exists); err { 76 | case pgx.ErrNoRows: 77 | return exists, ds.ErrNotFound 78 | case nil: 79 | return exists, nil 80 | default: 81 | return exists, err 82 | } 83 | } 84 | 85 | // Put "upserts" a row into the SQL database. 86 | func (d *Datastore) Put(ctx context.Context, key ds.Key, value []byte) error { 87 | sql := fmt.Sprintf("INSERT INTO %s (key, data) VALUES ($1, $2) ON CONFLICT (key) DO UPDATE SET data = $2", d.table) 88 | _, err := d.pool.Exec(ctx, sql, key.String(), value) 89 | if err != nil { 90 | return err 91 | } 92 | return nil 93 | } 94 | 95 | // Query returns multiple rows from the SQL database based on the passed query parameters. 96 | func (d *Datastore) Query(ctx context.Context, q dsq.Query) (dsq.Results, error) { 97 | var sql string 98 | if q.KeysOnly && q.ReturnsSizes { 99 | sql = fmt.Sprintf("SELECT key, octet_length(data) FROM %s", d.table) 100 | } else if q.KeysOnly { 101 | sql = fmt.Sprintf("SELECT key FROM %s", d.table) 102 | } else { 103 | sql = fmt.Sprintf("SELECT key, data FROM %s", d.table) 104 | } 105 | 106 | if q.Prefix != "" { 107 | // normalize 108 | prefix := ds.NewKey(q.Prefix).String() 109 | if prefix != "/" { 110 | sql += fmt.Sprintf(` WHERE key LIKE '%s%%' ORDER BY key`, prefix+"/") 111 | } 112 | } 113 | 114 | // only apply limit and offset if we do not have to naive filter/order the results 115 | if len(q.Filters) == 0 && len(q.Orders) == 0 { 116 | if q.Limit != 0 { 117 | sql += fmt.Sprintf(" LIMIT %d", q.Limit) 118 | } 119 | if q.Offset != 0 { 120 | sql += fmt.Sprintf(" OFFSET %d", q.Offset) 121 | } 122 | } 123 | 124 | rows, err := d.pool.Query(ctx, sql) 125 | if err != nil { 126 | return nil, err 127 | } 128 | 129 | it := dsq.Iterator{ 130 | Next: func() (dsq.Result, bool) { 131 | if !rows.Next() { 132 | if rows.Err() != nil { 133 | return dsq.Result{Error: rows.Err()}, false 134 | } 135 | return dsq.Result{}, false 136 | } 137 | 138 | var key string 139 | var size int 140 | var data []byte 141 | 142 | if q.KeysOnly && q.ReturnsSizes { 143 | err := rows.Scan(&key, &size) 144 | if err != nil { 145 | return dsq.Result{Error: err}, false 146 | } 147 | return dsq.Result{Entry: dsq.Entry{Key: key, Size: size}}, true 148 | } else if q.KeysOnly { 149 | err := rows.Scan(&key) 150 | if err != nil { 151 | return dsq.Result{Error: err}, false 152 | } 153 | return dsq.Result{Entry: dsq.Entry{Key: key}}, true 154 | } 155 | 156 | err := rows.Scan(&key, &data) 157 | if err != nil { 158 | return dsq.Result{Error: err}, false 159 | } 160 | entry := dsq.Entry{Key: key, Value: data} 161 | if q.ReturnsSizes { 162 | entry.Size = len(data) 163 | } 164 | return dsq.Result{Entry: entry}, true 165 | }, 166 | Close: func() error { 167 | rows.Close() 168 | return nil 169 | }, 170 | } 171 | 172 | res := dsq.ResultsFromIterator(q, it) 173 | 174 | for _, f := range q.Filters { 175 | res = dsq.NaiveFilter(res, f) 176 | } 177 | 178 | res = dsq.NaiveOrder(res, q.Orders...) 179 | 180 | // if we have filters or orders, offset and limit won't have been applied in the query 181 | if len(q.Filters) > 0 || len(q.Orders) > 0 { 182 | if q.Offset != 0 { 183 | res = dsq.NaiveOffset(res, q.Offset) 184 | } 185 | if q.Limit != 0 { 186 | res = dsq.NaiveLimit(res, q.Limit) 187 | } 188 | } 189 | 190 | return res, nil 191 | } 192 | 193 | // Sync is noop for PostgreSQL databases. 194 | func (d *Datastore) Sync(ctx context.Context, key ds.Key) error { 195 | return nil 196 | } 197 | 198 | // GetSize determines the size in bytes of the value for a given key. 199 | func (d *Datastore) GetSize(ctx context.Context, key ds.Key) (int, error) { 200 | sql := fmt.Sprintf("SELECT octet_length(data) FROM %s WHERE key = $1", d.table) 201 | row := d.pool.QueryRow(ctx, sql, key.String()) 202 | var size int 203 | switch err := row.Scan(&size); err { 204 | case pgx.ErrNoRows: 205 | return -1, ds.ErrNotFound 206 | case nil: 207 | return size, nil 208 | default: 209 | return -1, err 210 | } 211 | } 212 | 213 | var _ ds.Datastore = (*Datastore)(nil) 214 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 2 | github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= 3 | github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= 4 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 5 | github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 6 | github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= 7 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 9 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 11 | github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= 12 | github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= 13 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 14 | github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= 15 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 16 | github.com/ipfs/go-datastore v0.5.1 h1:WkRhLuISI+XPD0uk3OskB0fYFSyqK8Ob5ZYew9Qa1nQ= 17 | github.com/ipfs/go-datastore v0.5.1/go.mod h1:9zhEApYMTl17C8YDp7JmU7sQZi2/wqiYh73hakZ90Bk= 18 | github.com/ipfs/go-detect-race v0.0.1 h1:qX/xay2W3E4Q1U7d9lNs1sU9nvguX0a7319XbyQ6cOk= 19 | github.com/ipfs/go-detect-race v0.0.1/go.mod h1:8BNT7shDZPo99Q74BpGMK+4D8Mn4j46UU0LZ723meps= 20 | github.com/ipfs/go-ipfs-delay v0.0.0-20181109222059-70721b86a9a8/go.mod h1:8SP1YXK1M1kXuc4KJZINY3TQQ03J2rwBG9QfXmbRPrw= 21 | github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= 22 | github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= 23 | github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= 24 | github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= 25 | github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= 26 | github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= 27 | github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= 28 | github.com/jackc/pgconn v1.5.0 h1:oFSOilzIZkyg787M1fEmyMfOUUvwj0daqYMfaWwNL4o= 29 | github.com/jackc/pgconn v1.5.0/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI= 30 | github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= 31 | github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= 32 | github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2 h1:JVX6jT/XfzNqIjye4717ITLaNwV9mWbJx0dLCpcRzdA= 33 | github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= 34 | github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= 35 | github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= 36 | github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= 37 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= 38 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= 39 | github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= 40 | github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= 41 | github.com/jackc/pgproto3/v2 v2.0.1 h1:Rdjp4NFjwHnEslx2b66FfCI2S0LhO4itac3hXz6WX9M= 42 | github.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= 43 | github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8 h1:Q3tB+ExeflWUW7AFcAhXqk40s9mnNYLk1nOkKNZ5GnU= 44 | github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= 45 | github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= 46 | github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= 47 | github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= 48 | github.com/jackc/pgtype v1.3.0 h1:l8JvKrby3RI7Kg3bYEeU9TA4vqC38QDpFCfcrC7KuN0= 49 | github.com/jackc/pgtype v1.3.0/go.mod h1:b0JqxHvPmljG+HQ5IsvQ0yqeSi4nGcDTVjFoiLDb0Ik= 50 | github.com/jackc/pgx v3.6.2+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= 51 | github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= 52 | github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= 53 | github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= 54 | github.com/jackc/pgx/v4 v4.6.0 h1:Fh0O9GdlG4gYpjpwOqjdEodJUQM9jzN3Hdv7PN0xmm0= 55 | github.com/jackc/pgx/v4 v4.6.0/go.mod h1:vPh43ZzxijXUVJ+t/EmXBtFmbFVO72cuneCT9oAlxAg= 56 | github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= 57 | github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= 58 | github.com/jackc/puddle v1.1.0 h1:musOWczZC/rSbqut475Vfcczg7jJsdUQf0D6oKPLgNU= 59 | github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= 60 | github.com/jbenet/go-cienv v0.1.0/go.mod h1:TqNnHUmJgXau0nCzC7kXWeotg3J9W34CUv5Djy1+FlA= 61 | github.com/jbenet/goprocess v0.1.4 h1:DRGOFReOMqqDNXwW70QkacFW0YN9QnwLV0Vqk+3oU0o= 62 | github.com/jbenet/goprocess v0.1.4/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4= 63 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 64 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 65 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 66 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 67 | github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= 68 | github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 69 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 70 | github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= 71 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 72 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 73 | github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 74 | github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 75 | github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0= 76 | github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 77 | github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= 78 | github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 79 | github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 80 | github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 81 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 82 | github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= 83 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 84 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 85 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 86 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 87 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 88 | github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= 89 | github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= 90 | github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= 91 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= 92 | github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24 h1:pntxY8Ary0t43dCZ5dqY4YTJCObLY1kIXl0uzMv+7DE= 93 | github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= 94 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= 95 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 96 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 97 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 98 | github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= 99 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 100 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 101 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 102 | github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= 103 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 104 | github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= 105 | go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 106 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 107 | go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= 108 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 109 | go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= 110 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= 111 | go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 112 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 113 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 114 | golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= 115 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 116 | golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 117 | golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59 h1:3zb4D3T4G8jdExgVU/95+vQXfpEPiMdCaZgmGVxjNHM= 118 | golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 119 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 120 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 121 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 122 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 123 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 124 | golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 125 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 126 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 127 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 128 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 129 | golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 130 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 131 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 132 | golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 133 | golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 134 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 135 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= 136 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 137 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 138 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 139 | golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 140 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 141 | golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 142 | golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 143 | golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 144 | golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 145 | golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 146 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc= 147 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 148 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 149 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 150 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 151 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 152 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 153 | gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= 154 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 155 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 156 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 157 | --------------------------------------------------------------------------------