diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 6b4bdd16..f7f3233c 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -16,3 +16,9 @@ updates: - "๐Ÿค– Dependencies" schedule: interval: "daily" + - package-ecosystem: "gomod" + directory: "/clickhouse/" # Location of package manifests + labels: + - "๐Ÿค– Dependencies" + schedule: + interval: "daily" diff --git a/.github/release-drafter-clickhouse.yml b/.github/release-drafter-clickhouse.yml new file mode 100644 index 00000000..c08b7f25 --- /dev/null +++ b/.github/release-drafter-clickhouse.yml @@ -0,0 +1,50 @@ +name-template: 'ClickHouse - v$RESOLVED_VERSION' +tag-template: 'clickhouse/v$RESOLVED_VERSION' +tag-prefix: clickhouse/v +include-paths: + - clickhouse +categories: + - title: 'โ— Breaking Changes' + labels: + - 'โ— BreakingChange' + - title: '๐Ÿš€ New' + labels: + - 'โœ๏ธ Feature' + - title: '๐Ÿงน Updates' + labels: + - '๐Ÿงน Updates' + - '๐Ÿค– Dependencies' + - title: '๐Ÿ› Fixes' + labels: + - 'โ˜ข๏ธ Bug' + - title: '๐Ÿ“š Documentation' + labels: + - '๐Ÿ“’ Documentation' +change-template: '- $TITLE (#$NUMBER)' +change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks. +exclude-contributors: + - dependabot + - dependabot[bot] +version-resolver: + major: + labels: + - 'major' + - 'โ— BreakingChange' + minor: + labels: + - 'minor' + - 'โœ๏ธ Feature' + patch: + labels: + - 'patch' + - '๐Ÿ“’ Documentation' + - 'โ˜ข๏ธ Bug' + - '๐Ÿค– Dependencies' + - '๐Ÿงน Updates' + default: patch +template: | + $CHANGES + + **Full Changelog**: https://github.com/$OWNER/$REPOSITORY/compare/$PREVIOUS_TAG...clickhouse/v$RESOLVED_VERSION + + Thank you $CONTRIBUTORS for making this update possible. diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 20af27d9..c293e17d 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -134,6 +134,11 @@ jobs: docker run --name scylladb -p 9042:9042 -p 19042:19042 -p 9160:9160 -p 7000:7000 -p 7001:7001 -p 7199:7199 -p 9180:9180 -d scylladb/scylla:latest --broadcast-address 127.0.0.1 --listen-address 0.0.0.0 --broadcast-rpc-address 127.0.0.1 sleep 15 # Wait for ScyllaDb to initialize + - name: Startup Clickhouse + run: | + docker run -d -p 9001:9000 --name clickhouse --ulimit nofile=262144:262144 clickhouse/clickhouse-server + sleep 10 # Wait for Clickhouse to initialize + - name: Setup Redis uses: shogo82148/actions-setup-redis@v1 with: diff --git a/.github/workflows/release-drafter-clickhouse.yml b/.github/workflows/release-drafter-clickhouse.yml new file mode 100644 index 00000000..62043170 --- /dev/null +++ b/.github/workflows/release-drafter-clickhouse.yml @@ -0,0 +1,19 @@ +name: Release Drafter Clickhouse +on: + push: + # branches to consider in the event; optional, defaults to all + branches: + - master + - main + paths: + - 'clickhouse/**' +jobs: + draft_release_clickhouse: + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: release-drafter/release-drafter@v6 + with: + config-name: release-drafter-clickhouse.yml + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test-clickhouse.yml b/.github/workflows/test-clickhouse.yml new file mode 100644 index 00000000..a56a54de --- /dev/null +++ b/.github/workflows/test-clickhouse.yml @@ -0,0 +1,32 @@ +on: + push: + branches: + - master + - main + paths: + - 'clickhouse/**' + pull_request: + paths: + - 'clickhouse/**' +name: 'Tests Clickhouse' +jobs: + Tests: + runs-on: ubuntu-latest + strategy: + matrix: + go-version: + - 1.21.x + - 1.22.x + steps: + - name: Fetch Repository + uses: actions/checkout@v4 + - name: Startup Clickhouse + run: | + docker run -d -p 9001:9000 --name clickhouse --ulimit nofile=262144:262144 clickhouse/clickhouse-server + sleep 30 + - name: Install Go + uses: actions/setup-go@v5 + with: + go-version: '${{ matrix.go-version }}' + - name: Run Test + run: cd ./clickhouse && go clean -testcache && go test ./... -v -race diff --git a/.github/workflows/test-cloudflarekv.yml b/.github/workflows/test-cloudflarekv.yml index 4d819ba2..89d00e87 100644 --- a/.github/workflows/test-cloudflarekv.yml +++ b/.github/workflows/test-cloudflarekv.yml @@ -3,10 +3,13 @@ name: Tests CloudflareKV on: push: branches: + - master - main + paths: + - 'cloudflarekv/**' pull_request: - branches: - - main + paths: + - 'cloudflarekv/**' jobs: Tests: @@ -16,7 +19,6 @@ jobs: go-version: - 1.21.x - 1.22.x - steps: - name: Checkout Repository uses: actions/checkout@v4 diff --git a/README.md b/README.md index 532bebab..40f61d30 100644 --- a/README.md +++ b/README.md @@ -74,3 +74,4 @@ type Storage interface { - [S3](./s3/README.md) - [ScyllaDB](./scylladb/README.md) - [SQLite3](./sqlite3/README.md) +- [ClickHouse](./clickhouse/README.md) diff --git a/clickhouse/README.md b/clickhouse/README.md new file mode 100644 index 00000000..772f5527 --- /dev/null +++ b/clickhouse/README.md @@ -0,0 +1,117 @@ +# Clickhouse + +A Clickhouse storage driver using [https://github.com/ClickHouse/clickhouse-go](https://github.com/ClickHouse/clickhouse-go). + +### Table of Contents + +- [Signatures](#signatures) +- [Installation](#installation) +- [Examples](#examples) +- [Config](#config) +- [Default Config](#default-config) + +### Signatures + +```go +func New(config ...Config) (*Storage, error) +func (s *Storage) Get(key string) ([]byte, error) +func (s *Storage) Set(key string, val []byte, exp time.Duration) error +func (s *Storage) Delete(key string) error +func (s *Storage) Reset() error +func (s *Storage) Close() error +func (s *Storage) Conn() *Session +``` + +### Installation + +Clickhouse is supported on the latest two versions of Go: + +Install the clickhouse implementation: +```bash +go get github.com/gofiber/storage/clickhouse +``` + +Before running or testing this implementation, you must ensure a Clickhouse cluster is available. +For local development, we recommend using the Clickhouse Docker image; it contains everything +necessary for the client to operate correctly. + +To start Clickhouse using Docker, issue the following: + +```bash +docker run -d -p 9000:9000 --name some-clickhouse-server --ulimit nofile=262144:262144 clickhouse/clickhouse-server +``` + +After running this command you're ready to start using the storage and connecting to the database. + +### Examples + +You can use the following options to create a clickhouse storage driver: +```go +import "github.com/gofiber/storage/clickhouse" + +// Initialize default config, to connect to localhost:9000 using the memory engine and with a clean table. +store, err := clickhouse.New(clickhouse.Config{ + Host: "localhost", + Port: 9000, + Clean: true, +}) + +// Initialize custom config to connect to a different host/port and use custom engine and with clean table. +store, err := clickhouse.New(clickhouse.Config{ + Host: "some-ip-address", + Port: 9000, + Engine: clickhouse.MergeTree, + Clean: true, +}) + +// Initialize to connect with TLS enabled with your own tls.Config and with clean table. +tlsConfig := config := &tls.Config{...} + +store, err := clickhouse.New(clickhouse.Config{ + Host: "some-ip-address", + Port: 9000, + Clean: true, + TLSConfig: tlsConfig, +}) +``` + +### Config + +```go +// Config defines configuration options for Clickhouse connection. +type Config struct { + // The host of the database. Ex: 127.0.0.1 + Host string + // The port where the database is supposed to listen to. Ex: 9000 + Port int + // The database that the connection should authenticate from + Database string + // The username to be used in the authentication + Username string + // The password to be used in the authentication + Password string + // The name of the table that will store the data + Table string + // The engine that should be used in the table + Engine string + // Should start a clean table, default false + Clean bool + // TLS configuration, default nil + TLSConfig *tls.Config + // Should the connection be in debug mode, default false + Debug bool + // The function to use with the debug config, default print function. It only works when debug is true + Debugf func(format string, v ...any) +} +``` + +### Default Config + +```go +var DefaultConfig = Config{ + Host: "localhost", + Port: 9000, + Engine: "Memory", + Clean: false, +} +``` diff --git a/clickhouse/clickhouse.go b/clickhouse/clickhouse.go new file mode 100644 index 00000000..1056bc7b --- /dev/null +++ b/clickhouse/clickhouse.go @@ -0,0 +1,126 @@ +package clickhouse + +import ( + "context" + "database/sql" + "errors" + "fmt" + "time" + + driver "github.com/ClickHouse/clickhouse-go/v2" +) + +type Storage struct { + session driver.Conn + context context.Context + table string +} + +// New returns a new [*Storage] given a [Config]. +func New(configuration Config) (*Storage, error) { + cfg, engine, err := defaultConfig(configuration) + if err != nil { + return nil, err + } + + conn, err := driver.Open(&cfg) + if err != nil { + return nil, err + } + + ctx := context.Background() + + queryWithEngine := fmt.Sprintf(createTableString, engine) + if err := conn.Exec(ctx, queryWithEngine, driver.Named("table", configuration.Table)); err != nil { + return nil, err + } + + if configuration.Clean { + if err := conn.Exec(ctx, resetDataString, driver.Named("table", configuration.Table)); err != nil { + return nil, err + } + } + + if err := conn.Ping(ctx); err != nil { + return nil, err + } + + return &Storage{ + session: conn, + context: ctx, + table: configuration.Table, + }, nil +} + +func (s *Storage) Set(key string, value []byte, expiration time.Duration) error { + if len(key) <= 0 || len(value) <= 0 { + return nil + } + + exp := time.Time{} + if expiration != 0 { + exp = time.Now().Add(expiration).UTC() + } + + return s. + session. + Exec( + s.context, + insertDataString, + driver.Named("table", s.table), + driver.Named("key", key), + driver.Named("value", string(value)), + driver.Named("expiration", exp.Format("2006-01-02 15:04:05")), + ) +} + +func (s *Storage) Get(key string) ([]byte, error) { + if len(key) == 0 { + return []byte{}, nil + } + + var result schema + + row := s.session.QueryRow( + s.context, + selectDataString, + driver.Named("table", s.table), + driver.Named("key", key), + ) + if row.Err() != nil { + return []byte{}, row.Err() + } + + if err := row.ScanStruct(&result); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return []byte{}, nil + } + + return []byte{}, err + } + + // The result.Expiration.IsZero() was returning a false value even when the time was + // set to be the zero value of the time.Time struct (Jan 1st 1970, 00:00:00 UTC) + // so we had to change the comparison + if !time.Unix(0, 0).Equal(result.Expiration) && result.Expiration.Before(time.Now().UTC()) { + return []byte{}, nil + } + + return []byte(result.Value), nil +} + +func (s *Storage) Delete(key string) error { + if len(key) == 0 { + return nil + } + + return s.session.Exec(s.context, deleteDataString, driver.Named("table", s.table), driver.Named("key", key)) +} + +func (s *Storage) Reset() error { + return s.session.Exec(s.context, resetDataString, driver.Named("table", s.table)) +} + +func (s *Storage) Close() error { + return s.session.Close() +} diff --git a/clickhouse/clickhouse_test.go b/clickhouse/clickhouse_test.go new file mode 100644 index 00000000..f1278ce2 --- /dev/null +++ b/clickhouse/clickhouse_test.go @@ -0,0 +1,229 @@ +package clickhouse + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type TestOrBench interface { + Helper() +} + +func getTestConnection(t TestOrBench, cfg Config) (*Storage, error) { + t.Helper() + + client, err := New(cfg) + + return client, err +} + +func Test_Connection(t *testing.T) { + _, err := getTestConnection(t, Config{ + Host: "127.0.0.1", + Port: 9001, + Engine: Memory, + Table: "test_table", + Clean: true, + }) + + require.NoError(t, err) +} + +func Test_Set(t *testing.T) { + client, err := getTestConnection(t, Config{ + Host: "127.0.0.1", + Port: 9001, + Engine: Memory, + Table: "test_table", + Clean: true, + }) + require.NoError(t, err) + defer client.Close() + + err = client.Set("somekey", []byte("somevalue"), 0) + require.NoError(t, err) +} + +func Test_Set_With_Exp(t *testing.T) { + client, err := getTestConnection(t, Config{ + Host: "127.0.0.1", + Port: 9001, + Engine: Memory, + Table: "test_table", + Clean: true, + }) + require.NoError(t, err) + defer client.Close() + + err = client.Set("setsomekeywithexp", []byte("somevalue"), time.Second*1) + require.NoError(t, err) +} + +func Test_Get(t *testing.T) { + client, err := getTestConnection(t, Config{ + Host: "127.0.0.1", + Port: 9001, + Engine: Memory, + Table: "test_table", + Clean: true, + }) + require.NoError(t, err) + defer client.Close() + + err = client.Set("somekey", []byte("somevalue"), 0) + require.NoError(t, err) + + value, err := client.Get("somekey") + + require.NoError(t, err) + assert.NotNil(t, value) + assert.Equal(t, "somevalue", string(value)) +} + +func Test_Get_With_Exp(t *testing.T) { + client, err := getTestConnection(t, Config{ + Host: "127.0.0.1", + Port: 9001, + Engine: Memory, + Table: "test_table", + Clean: true, + }) + require.NoError(t, err) + defer client.Close() + + err = client.Set("getsomekeywithexp", []byte("somevalue"), time.Second*2) + require.NoError(t, err) + + value, err := client.Get("getsomekeywithexp") + + require.NoError(t, err) + assert.NotNil(t, value) + assert.Equal(t, "somevalue", string(value)) + + time.Sleep(time.Second * 5) + + value, err = client.Get("getsomekeywithexp") + + require.NoError(t, err) + assert.Equal(t, []byte{}, value) +} + +func Test_Delete(t *testing.T) { + client, err := getTestConnection(t, Config{ + Host: "127.0.0.1", + Port: 9001, + Engine: Memory, + Table: "test_table", + Clean: true, + }) + require.NoError(t, err) + defer client.Close() + + err = client.Set("somekeytodelete", []byte("somevalue"), time.Second*5) + require.NoError(t, err) + + err = client.Delete("somekeytodelete") + + require.NoError(t, err) + + value, err := client.Get("somekeytodelete") + + require.NoError(t, err) + assert.Equal(t, []byte{}, value) +} + +func Test_Reset(t *testing.T) { + client, err := getTestConnection(t, Config{ + Host: "127.0.0.1", + Port: 9001, + Engine: Memory, + Table: "test_table", + Clean: true, + }) + require.NoError(t, err) + defer client.Close() + + err = client.Set("testkey", []byte("somevalue"), 0) + require.NoError(t, err) + + err = client.Reset() + + require.NoError(t, err) + + value, err := client.Get("testkey") + + require.NoError(t, err) + assert.Equal(t, []byte{}, value) +} + +func Benchmark_Clickhouse_Set(b *testing.B) { + b.ReportAllocs() + b.ResetTimer() + + client, err := getTestConnection(b, Config{ + Host: "127.0.0.1", + Port: 9001, + Engine: Memory, + Table: "test_table", + Clean: true, + }) + require.NoError(b, err) + + defer client.Close() + + for i := 0; i < b.N; i++ { + err = client.Set("john", []byte("doe"), 0) + } + + require.NoError(b, err) +} + +func Benchmark_Clickhouse_Get(b *testing.B) { + b.ReportAllocs() + b.ResetTimer() + + client, err := getTestConnection(b, Config{ + Host: "127.0.0.1", + Port: 9001, + Engine: Memory, + Table: "test_table", + Clean: true, + }) + require.NoError(b, err) + + defer client.Close() + + err = client.Set("john", []byte("doe"), 0) + + for i := 0; i < b.N; i++ { + _, err = client.Get("john") + } + + require.NoError(b, err) +} + +func Benchmark_Clickhouse_Set_And_Delete(b *testing.B) { + b.ReportAllocs() + b.ResetTimer() + + client, err := getTestConnection(b, Config{ + Host: "127.0.0.1", + Port: 9001, + Engine: Memory, + Table: "test_table", + Clean: true, + }) + + require.NoError(b, err) + defer client.Close() + + for i := 0; i < b.N; i++ { + _ = client.Set("john", []byte("doe"), 0) + err = client.Delete("john") + } + + require.NoError(b, err) +} diff --git a/clickhouse/config.go b/clickhouse/config.go new file mode 100644 index 00000000..d9b820ce --- /dev/null +++ b/clickhouse/config.go @@ -0,0 +1,116 @@ +package clickhouse + +import ( + "crypto/tls" + "errors" + "fmt" + "log" + "time" + + driver "github.com/ClickHouse/clickhouse-go/v2" +) + +type ClickhouseEngine string + +type schema struct { + Value string `ch:"value"` + Expiration time.Time `ch:"expiration"` +} + +const ( + Memory ClickhouseEngine = "Memory" + MergeTree ClickhouseEngine = "MergeTree" + StripeLog ClickhouseEngine = "StripeLog" + TinyLog ClickhouseEngine = "TinyLog" + Log ClickhouseEngine = "Log" +) + +// Config defines configuration options for Clickhouse connection. +type Config struct { + // The host of the database. Ex: 127.0.0.1 + Host string + // The port where the database is supposed to listen to. Ex: 9000 + Port int + // The database that the connection should authenticate from + Database string + // The username to be used in the authentication + Username string + // The password to be used in the authentication + Password string + // The name of the table that will store the data + Table string + // The engine that should be used in the table + Engine ClickhouseEngine + // Should start a clean table, default false + Clean bool + // TLS configuration, default nil + TLSConfig *tls.Config + // Should the connection be in debug mode, default false + Debug bool + // The function to use with the debug config, default print function. It only works when debug is true + Debugf func(format string, v ...any) +} + +func defaultConfig(configuration Config) (driver.Options, ClickhouseEngine, error) { + if configuration.Table == "" { + return driver.Options{}, "", errors.New("table name not provided") + } + + if configuration.Host == "" { + configuration.Host = "localhost" + } + + if configuration.Port == 0 { + configuration.Port = 9000 + } + + if configuration.Engine == "" { + configuration.Engine = Memory + } + + config := driver.Options{ + Addr: []string{fmt.Sprintf("%s:%d", configuration.Host, configuration.Port)}, + } + + if configuration.Username != "" && configuration.Password != "" { + config.Auth = driver.Auth{ + Database: configuration.Database, + Username: configuration.Username, + Password: configuration.Password, + } + } + + if configuration.TLSConfig != nil { + config.TLS = configuration.TLSConfig + } + + if configuration.Debug && config.Debugf == nil { + config.Debugf = log.Printf + } + + return config, configuration.Engine, nil +} + +const resetDataString = ` + TRUNCATE TABLE {table:Identifier} +` + +const deleteDataString = ` + ALTER TABLE {table:Identifier} DELETE WHERE key = {key:String} +` + +const selectDataString = ` + SELECT value, expiration FROM {table:Identifier} WHERE key = {key:String} +` + +const insertDataString = ` + INSERT INTO {table:Identifier} (*) VALUES ({key:String}, {value:String}, {expiration:Datetime}) +` + +const createTableString = ` + CREATE TABLE IF NOT EXISTS {table:Identifier} ( + key String CODEC(ZSTD(1)) + , value String CODEC(ZSTD(1)) + , expiration Datetime CODEC(ZSTD(1)) + ) ENGINE=%s +` diff --git a/clickhouse/go.mod b/clickhouse/go.mod new file mode 100644 index 00000000..f40124ae --- /dev/null +++ b/clickhouse/go.mod @@ -0,0 +1,28 @@ +module github.com/gofiber/storage/clickhouse + +go 1.21 + +require ( + github.com/ClickHouse/clickhouse-go/v2 v2.26.0 + github.com/stretchr/testify v1.9.0 +) + +require ( + github.com/ClickHouse/ch-go v0.61.5 // indirect + github.com/andybalholm/brotli v1.1.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/go-faster/city v1.0.1 // indirect + github.com/go-faster/errors v0.7.1 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/klauspost/compress v1.17.9 // indirect + github.com/paulmach/orb v0.11.1 // indirect + github.com/pierrec/lz4/v4 v4.1.21 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/segmentio/asm v1.2.0 // indirect + github.com/shopspring/decimal v1.4.0 // indirect + go.opentelemetry.io/otel v1.28.0 // indirect + go.opentelemetry.io/otel/trace v1.28.0 // indirect + golang.org/x/sys v0.21.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/clickhouse/go.sum b/clickhouse/go.sum new file mode 100644 index 00000000..46d90598 --- /dev/null +++ b/clickhouse/go.sum @@ -0,0 +1,110 @@ +github.com/ClickHouse/ch-go v0.61.5 h1:zwR8QbYI0tsMiEcze/uIMK+Tz1D3XZXLdNrlaOpeEI4= +github.com/ClickHouse/ch-go v0.61.5/go.mod h1:s1LJW/F/LcFs5HJnuogFMta50kKDO0lf9zzfrbl0RQg= +github.com/ClickHouse/clickhouse-go/v2 v2.26.0 h1:j4/y6NYaCcFkJwN/TU700ebW+nmsIy34RmUAAcZKy9w= +github.com/ClickHouse/clickhouse-go/v2 v2.26.0/go.mod h1:iDTViXk2Fgvf1jn2dbJd1ys+fBkdD1UMRnXlwmhijhQ= +github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= +github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-faster/city v1.0.1 h1:4WAxSZ3V2Ws4QRDrscLEDcibJY8uf41H6AhXDrNDcGw= +github.com/go-faster/city v1.0.1/go.mod h1:jKcUJId49qdW3L1qKHH/3wPeUstCVpVSXTM6vO3VcTw= +github.com/go-faster/errors v0.7.1 h1:MkJTnDoEdi9pDabt1dpWf7AA8/BaSYZqibYyhZ20AYg= +github.com/go-faster/errors v0.7.1/go.mod h1:5ySTjWFiphBs07IKuiL69nxdfd5+fzh1u7FPGZP2quo= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/paulmach/orb v0.11.1 h1:3koVegMC4X/WeiXYz9iswopaTwMem53NzTJuTF20JzU= +github.com/paulmach/orb v0.11.1/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU= +github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY= +github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= +github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= +github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= +github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.mongodb.org/mongo-driver v1.11.4/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g= +go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= +go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= +go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= +go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=