diff --git a/.github/workflows/breaking.yml b/.github/workflows/breaking.yml index cbfa052c8..7594b1f82 100644 --- a/.github/workflows/breaking.yml +++ b/.github/workflows/breaking.yml @@ -12,18 +12,21 @@ jobs: cancel-in-progress: true runs-on: ubuntu-latest permissions: - pull-requests: write + pull-requests: write + contents: read steps: - name: Install Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v5 with: go-version: "1.21" - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install gorelease run: test -e ~/go/bin/gorelease || go install golang.org/x/exp/cmd/gorelease@latest - name: Check broken API changes run: gorelease -base=$GITHUB_BASE_REF 2>&1 > changes.txt | true + - name: Print API changes + run: cat changes.txt - name: Comment Report if: always() uses: marocchino/sticky-pull-request-comment@v2 diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml index 5c3508bd1..65f19da5b 100644 --- a/.github/workflows/changelog.yml +++ b/.github/workflows/changelog.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Changelog updated uses: Zomzog/changelog-checker@v1.3.0 diff --git a/.github/workflows/check-codegen.yml b/.github/workflows/check-codegen.yml index 6ef0f1ed1..8d9f701e3 100644 --- a/.github/workflows/check-codegen.yml +++ b/.github/workflows/check-codegen.yml @@ -18,10 +18,10 @@ jobs: runs-on: ${{ matrix.os }} steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v5 with: go-version: ${{ matrix.go-version }} @@ -32,6 +32,7 @@ jobs: - name: Build run: | go install ./internal/cmd/gtrace + go install ./internal/cmd/gstack go install go.uber.org/mock/mockgen@v0.4.0 - name: Clean and re-generate *_gtrace.go files @@ -40,5 +41,9 @@ jobs: go generate ./trace go generate ./... + - name: Re-generate stack.FunctionID calls + run: | + gstack . + - name: Check repository diff run: bash ./.github/scripts/check-work-copy-equals-to-committed.sh "code-generation not equal with committed" diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 678e69070..f37a6b49c 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -45,11 +45,11 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -63,7 +63,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v2 + uses: github/codeql-action/autobuild@v3 # ℹī¸ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun @@ -76,4 +76,4 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index 745864a37..4eca7b4b2 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -38,9 +38,9 @@ jobs: YDB_VERSION: ${{ matrix.ydb-version }} steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v5 with: cache: true - name: Run basic example ${{ matrix.application }} @@ -74,9 +74,9 @@ jobs: POSTGRES_CONNECTION_STRING: postgres://postgres:postgres@localhost:5432/basic?sslmode=disable steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v5 with: cache: true - name: Run basic example ${{ matrix.application }} with postgres @@ -97,9 +97,9 @@ jobs: SQLITE_CONNECTION_STRING: ${{ matrix.application }}.db steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v5 with: cache: true - name: Run basic example ${{ matrix.application }} with sqlite diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index c4b89aa7a..e922c39e2 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -16,9 +16,9 @@ jobs: cancel-in-progress: true runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: golangci-lint - uses: golangci/golangci-lint-action@v3 + uses: golangci/golangci-lint-action@v4 with: version: ${{ env.GOLANGCI_LINT_VERSION }} args: --timeout=5m @@ -29,11 +29,11 @@ jobs: cancel-in-progress: true runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: generate examples golangci-lint config run: sed 's/github.com\/ydb-platform\/ydb-go-sdk\/v3/examples/g' .golangci.yml > examples/.golangci.yml - name: golangci-lint - uses: golangci/golangci-lint-action@v3 + uses: golangci/golangci-lint-action@v4 with: version: ${{ env.GOLANGCI_LINT_VERSION }} args: --timeout=5m @@ -45,11 +45,11 @@ jobs: cancel-in-progress: true runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: generate slo golangci-lint config run: sed 's/github.com\/ydb-platform\/ydb-go-sdk\/v3/slo/g' .golangci.yml > tests/slo/.golangci.yml - name: golangci-lint - uses: golangci/golangci-lint-action@v3 + uses: golangci/golangci-lint-action@v4 with: version: ${{ env.GOLANGCI_LINT_VERSION }} args: --timeout=5m @@ -61,9 +61,9 @@ jobs: cancel-in-progress: true runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v5 with: go-version: "1.21" - name: Install utilities diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 61361f822..64f30b640 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -28,7 +28,7 @@ jobs: CHANGELOG_FILE: CHANGELOG.md GITHUB_TOKEN: ${{ secrets.YDB_PLATFORM_BOT_TOKEN_REPO }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: token: ${{ secrets.YDB_PLATFORM_BOT_TOKEN_REPO }} fetch-depth: 0 diff --git a/.github/workflows/slo.yml b/.github/workflows/slo.yml index d1922303e..bc63cacbe 100644 --- a/.github/workflows/slo.yml +++ b/.github/workflows/slo.yml @@ -21,7 +21,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Run SLO uses: ydb-platform/slo-tests@js-version @@ -73,7 +73,7 @@ jobs: workload_build_context4: ../.. workload_build_options4: -f Dockerfile --build-arg SRC_PATH=xorm --build-arg JOB_NAME=workload-xorm - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 if: always() with: name: slo-logs diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 756b2fd38..55e6629fe 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -22,16 +22,16 @@ jobs: runs-on: ${{ matrix.os }}-latest steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v5 with: go-version: ${{ matrix.go-version }} cache: true - name: Test run: go test -race -coverprofile unit.txt -covermode atomic ./... - name: Upload coverage report to Codecov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: file: ./unit.txt flags: unit,${{ matrix.os }},go-${{ matrix.go-version }} @@ -71,16 +71,16 @@ jobs: HIDE_APPLICATION_OUTPUT: 1 steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v5 with: go-version: ${{ matrix.go-version }} cache: true - name: Integration test run: go test -race -tags integration -coverpkg=./... -coverprofile integration-secure.txt -covermode atomic ./tests/integration - name: Upload Test secure connection coverage report to Codecov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: file: ./integration-secure.txt flags: integration,${{ matrix.os }},go-${{ matrix.go-version }},ydb-${{ matrix.ydb-version }} diff --git a/.golangci.yml b/.golangci.yml index b373a0322..85915e7b9 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -227,21 +227,18 @@ linters: - exhaustivestruct - exhaustruct - forbidigo - - forcetypeassert - funlen - gochecknoglobals - gocognit - godot - goerr113 - golint - - gomnd - ifshort - interfacebloat - ireturn - maintidx - nonamedreturns - paralleltest - - scopelint - structcheck - testableexamples - testpackage @@ -295,6 +292,8 @@ issues: - predeclared - path: _test\.go linters: + - scopelint + - funlen - unused - unparam - gocritic @@ -303,6 +302,9 @@ issues: - staticcheck - path: _test\.go text: "ydb.Connection is deprecated" + - path: examples + linters: + - gomnd # Allow underscore and capital camel case for readability # Examples: Type_PRIMITIVE_TYPE_ID_UNSPECIFIED, Ydb_Discovery_V1, _voidValue diff --git a/CHANGELOG.md b/CHANGELOG.md index d36d2f2dd..6a4a61efc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,58 @@ +## v3.65.0 +* Supported OAuth 2.0 Token Exchange credentials provider + +## v3.64.0 +* Supported `table.Session.RenameTables` method +* Fixed out of range panic if next query result set part is empty +* Updated the indirect dependencies `golang.org/x/net` to `v0.17.0` and `golang.org/x/sys` to `v0.13.0` due to vulnerability issue + +## v3.63.0 +* Added versioning policy + +## v3.62.0 +* Restored `WithSessionPoolKeepAliveMinSize` and `WithSessionPoolKeepAliveTimeout` for backward compatibility. +* Fixed leak timers +* Changed default StartTime (time of retries for connect to server) for topic writer from 1 minute to infinite (can be overrided by WithWriterStartTimeout topic option) +* Added `Struct` support for `Variant` in `ydb.ParamsBuilder()` +* Added `go` with anonymous function case in `gstack` + +## v3.61.2 +* Changed default transaction control to `NoTx` for execute query through query service client + +## v3.61.1 +* Renamed `db.Coordination().CreateSession()` to `db.Coordination().Session()` for compatibility with protos + +## v3.61.0 +* Added `Tuple` support for `Variant` in `ydb.ParamsBuilder()` + +## v3.60.1 +* Added additional traces for coordination service client internals + +## v3.60.0 +* Added experimental support of semaphores over coordination service client + +## v3.59.3 +* Fixed `gstack` logic for parsing `ast.BlockStmt` + +## v3.59.2 +* Added internal `gstack` codegen tool for filling `stack.FunctionID` with value from call stack + +## v3.59.1 +* Fixed updating last usage timestamp for smart parking of the conns + +## v3.59.0 +* Added `Struct` support for `ydb.ParamsBuilder()` * Added support of `TzDate`,`TzDateTime`,`TzTimestamp` types in `ydb.ParamsBuilder()` * Added `trace.Query.OnTransactionExecute` event * Added query pool metrics * Fixed logic of query session pool * Changed initialization of internal driver clients to lazy -* Disabled the logic of background grpc-connection parking +* Removed `ydb.WithSessionPoolSizeLimit()` option +* Added async put session into pool if external context is done +* Dropped intermediate callbacks from `trace.{Table,Retry,Query}` events +* Wrapped errors from `internal/pool.Pool.getItem` as retryable +* Disabled the logic of background grpc-connection parking +* Improved stringification for postgres types ## v3.58.2 * Added `trace.Query.OnSessionBegin` event @@ -27,6 +76,7 @@ * Updated `google.golang.org/protobuf` from `v1.31.0` to `v.33.0` * Added `ydb.ParamsBuilder().Pg().{Value,Int4,Int8,Unknown}` for postgres arguments * Added `Tuple` support for `ydb.ParamsBuilder()` +* Added type assertion checks to enhance type safety and prevent unexpected panics in critical sections of the codebase ## v3.57.4 * Added client pid to each gRPC requests to YDB over header `x-ydb-client-pid` @@ -51,7 +101,7 @@ * Fixed sometime panic on topic writer closing * Added experimental query parameters builder `ydb.ParamsBuilder()` * Changed types of `table/table.{QueryParameters,ParameterOption}` to aliases on `internal/params.{Parameters,NamedValue}` -* Fixed bug with optional decimal serialization +* Fixed bug with optional decimal serialization ## v3.56.2 * Fixed return private error for commit to stopped partition in topic reader. diff --git a/README.md b/README.md index eb51ff130..ef0c47e06 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ [![WebSite](https://img.shields.io/badge/website-ydb.tech-blue.svg)](https://ydb.tech) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/ydb-platform/ydb-go-sdk/blob/master/CONTRIBUTING.md) -Supports `table`, `discovery`, `coordination`, `ratelimiter`, `scheme`, `scripting` and `topic` clients for [YDB](https://ydb.tech). +Supports `table`, `query`, `discovery`, `coordination`, `ratelimiter`, `scheme`, `scripting` and `topic` clients for [YDB](https://ydb.tech). `YDB` is an open-source Distributed SQL Database that combines high availability and scalability with strict consistency and [ACID](https://en.wikipedia.org/wiki/ACID) transactions. `YDB` was created primarily for [OLTP](https://en.wikipedia.org/wiki/Online_transaction_processing) workloads and supports some [OLAP](https://en.wikipedia.org/wiki/Online_analytical_processing) scenarious. @@ -22,6 +22,10 @@ Supports `table`, `discovery`, `coordination`, `ratelimiter`, `scheme`, `scripti `ydb-go-sdk` supports all Go versions supported by the official [Go Release Policy](https://go.dev/doc/devel/release#policy). That is, the two most recent releases of Go. +## Versioning Policy + +`ydb-go-sdk` comply to guidelines from [SemVer2.0.0](https://semver.org/) with an several [exceptions](VERSIONING.md). + ## Installation ```sh @@ -31,19 +35,20 @@ go get -u github.com/ydb-platform/ydb-go-sdk/v3 ## Example Usage * connect to YDB -```golang +```go db, err := ydb.Open(ctx, "grpc://localhost:2136/local") if err != nil { log.Fatal(err) } ``` -* execute `SELECT` query - ```golang -const query = `SELECT 42 as id, "myStr" as myStr;` - +* execute `SELECT` query over `Table` service client + ```go // Do retry operation on errors with best effort -queryErr := db.Table().Do(ctx, func(ctx context.Context, s table.Session) (err error) { - _, res, err := s.Execute(ctx, table.DefaultTxControl(), query, nil) +err := db.Table().Do(ctx, func(ctx context.Context, s table.Session) (err error) { + _, res, err := s.Execute(ctx, table.DefaultTxControl(), + `SELECT 42 as id, "myStr" as myStr;`, + nil, // empty parameters + ) if err != nil { return err } @@ -62,12 +67,61 @@ queryErr := db.Table().Do(ctx, func(ctx context.Context, s table.Session) (err e } return res.Err() // for driver retry if not nil }) -if queryErr != nil { - log.Fatal(queryErr) +if err != nil { + log.Fatal(err) +} +``` +* execute `SELECT` query over `Query` service client + ```go +// Do retry operation on errors with best effort +err := db.Query().Do( // Do retry operation on errors with best effort + ctx, // context manage exiting from Do + func(ctx context.Context, s query.Session) (err error) { // retry operation + _, res, err := s.Execute(ctx, `SELECT 42 as id, "myStr" as myStr;`)) + if err != nil { + return err // for auto-retry with driver + } + defer func() { _ = res.Close(ctx) }() // cleanup resources + for { // iterate over result sets + rs, err := res.NextResultSet(ctx) + if err != nil { + if errors.Is(err, io.EOF) { + break + } + + return err + } + for { // iterate over rows + row, err := rs.NextRow(ctx) + if err != nil { + if errors.Is(err, io.EOF) { + break + } + + return err + } + type myStruct struct { + id uint64 `sql:"id"` + str string `sql:"myStr"` + } + var s myStruct + if err = row.ScanStruct(&s); err != nil { + return err // generally scan error not retryable, return it for driver check error + } + } + } + + return res.Err() // return finally result error for auto-retry with driver + }, + query.WithIdempotent(), +) +if err != nil { + log.Fatal(err) } ``` + * usage with `database/sql` (see additional docs in [SQL.md](SQL.md) ) -```golang +```go import ( "context" "database/sql" @@ -96,7 +150,7 @@ log.Printf("id = %d, myStr = \"%s\"", id, myStr) ``` -More examples of usage placed in [examples](https://github.com/ydb-platform/ydb-go-examples) repository. +More examples of usage placed in [examples](./examples) directory. ## Credentials @@ -124,8 +178,7 @@ Next packages provide debug tooling: | [ydb-go-sdk-zap](https://github.com/ydb-platform/ydb-go-sdk-zap) | logging | logging ydb-go-sdk events with `zap` package | [ydbZap.WithTraces](https://github.com/ydb-platform/ydb-go-sdk-zap/blob/master/internal/cmd/bench/main.go#L64) | | [ydb-go-sdk-zerolog](https://github.com/ydb-platform/ydb-go-sdk-zerolog) | logging | logging ydb-go-sdk events with `zerolog` package | [ydbZerolog.WithTraces](https://github.com/ydb-platform/ydb-go-sdk-zerolog/blob/master/internal/cmd/bench/main.go#L47) | | [ydb-go-sdk-logrus](https://github.com/ydb-platform/ydb-go-sdk-logrus) | logging | logging ydb-go-sdk events with `logrus` package | [ydbLogrus.WithTraces](https://github.com/ydb-platform/ydb-go-sdk-logrus/blob/master/internal/cmd/bench/main.go#L48) | -| [ydb-go-sdk-metrics](https://github.com/ydb-platform/ydb-go-sdk-metrics) | metrics | common metrics of ydb-go-sdk. Package declare interfaces such as `Registry`, `GaugeVec` and `Gauge` and use it for traces | | -| [ydb-go-sdk-prometheus](https://github.com/ydb-platform/ydb-go-sdk-prometheus) | metrics | prometheus wrapper over [ydb-go-sdk-metrics](https://github.com/ydb-platform/ydb-go-sdk-metrics) | [ydbPrometheus.WithTraces](https://github.com/ydb-platform/ydb-go-sdk-prometheus/blob/master/internal/cmd/bench/main.go#L56) | +| [ydb-go-sdk-prometheus](https://github.com/ydb-platform/ydb-go-sdk-prometheus/v2) | metrics | prometheus wrapper over [ydb-go-sdk/v3/metrics](https://github.com/ydb-platform/ydb-go-sdk/tree/master/metrics) | [ydbPrometheus.WithTraces](https://github.com/ydb-platform/ydb-go-sdk-prometheus/blob/master/internal/cmd/bench/main.go#L56) | | [ydb-go-sdk-opentracing](https://github.com/ydb-platform/ydb-go-sdk-opentracing) | tracing | OpenTracing plugin for trace internal ydb-go-sdk calls | [ydbOpentracing.WithTraces](https://github.com/ydb-platform/ydb-go-sdk-opentracing/blob/master/internal/cmd/bench/main.go#L86) | | [ydb-go-sdk-otel](https://github.com/ydb-platform/ydb-go-sdk-otel) | tracing | OpenTelemetry plugin for trace internal ydb-go-sdk calls | [ydbOtel.WithTraces](https://github.com/ydb-platform/ydb-go-sdk-otel/blob/master/internal/cmd/bench/main.go#L98) | diff --git a/VERSIONING.md b/VERSIONING.md new file mode 100644 index 000000000..40cb9171c --- /dev/null +++ b/VERSIONING.md @@ -0,0 +1,24 @@ +# YDB-Go-SDK Versioning Policy + +By adhering to these guidelines and exceptions, we aim to provide a stable and reliable development experience for our users (aka [LTS](https://en.wikipedia.org/wiki/Long-term_support)) while still allowing for innovation and improvement. + +We endeavor to adhere to versioning guidelines as defined by [SemVer2.0.0](https://semver.org/). + +We making the following exceptions to those guidelines: +## Experimental + - We use the `// Experimental` comment for new features in the `ydb-go-sdk`. + - Early adopters of newest feature can report bugs and imperfections in functionality. + - For fix this issues we can make broken changes in experimental API. + - We reserve the right to remove or modify these experimental features at any time without increase of major part of version. + - We want to make experimental API as stable in the future +## Deprecated + - We use the `// Deprecated` comment for deprecated features in the `ydb-go-sdk`. + - Usage of some entity marked with `// Deprecated` can be detected with linters such as [check-deprecated](https://github.com/black-06/check-deprecated), [staticcheck](https://github.com/dominikh/go-tools/tree/master/cmd/staticcheck) or [go-critic](https://github.com/go-critic/go-critic). + - This helps to our users to soft decline to use the deprecated feature without any impact on their code. + - Deprecated features will not be removed or changed for a minimum period of **six months** since the mark added. + - We reserve the right to remove or modify these deprecated features without increase of major part of version. +## Internals + - Some public API of `ydb-go-sdk` relate to the internals. + - We use the `// Internals` comment for public internals in the `ydb-go-sdk`. + - `ydb-go-sdk` internals can be changed at any time without increase of major part of version. + - Internals will never marked as stable diff --git a/balancers/balancers.go b/balancers/balancers.go index c73c8f503..2fe525a53 100644 --- a/balancers/balancers.go +++ b/balancers/balancers.go @@ -9,7 +9,9 @@ import ( "github.com/ydb-platform/ydb-go-sdk/v3/internal/xstring" ) -// Deprecated: RoundRobin is RandomChoice now +// Deprecated: RoundRobin is an alias to RandomChoice now +// Will be removed after Oct 2024. +// Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated func RoundRobin() *balancerConfig.Config { return &balancerConfig.Config{} } @@ -115,6 +117,8 @@ type Endpoint interface { // Deprecated: LocalDC check "local" by compare endpoint location with discovery "selflocation" field. // It work good only if connection url always point to local dc. + // Will be removed after Oct 2024. + // Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated LocalDC() bool } diff --git a/config/config.go b/config/config.go index 47d3b8cc1..28ffd0f90 100644 --- a/config/config.go +++ b/config/config.go @@ -21,6 +21,7 @@ type Config struct { trace *trace.Driver dialTimeout time.Duration + connectionTTL time.Duration balancerConfig *balancerConfig.Config secure bool endpoint string @@ -56,6 +57,13 @@ func (c *Config) Meta() *meta.Meta { return c.meta } +// ConnectionTTL defines interval for parking grpc connections. +// +// If ConnectionTTL is zero - connections are not park. +func (c *Config) ConnectionTTL() time.Duration { + return c.connectionTTL +} + // Secure is a flag for secure connection func (c *Config) Secure() bool { return c.secure @@ -99,7 +107,9 @@ type Option func(c *Config) // WithInternalDNSResolver // -// Deprecated: always used internal dns-resolver +// Deprecated: always used internal dns-resolver. +// Will be removed after Oct 2024. +// Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated func WithInternalDNSResolver() Option { return func(c *Config) {} } @@ -162,13 +172,21 @@ func WithApplicationName(applicationName string) Option { // WithUserAgent add provided user agent to all api requests // -// Deprecated: use WithApplicationName instead +// Deprecated: use WithApplicationName instead. +// Will be removed after Oct 2024. +// Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated func WithUserAgent(userAgent string) Option { return func(c *Config) { c.metaOptions = append(c.metaOptions, meta.WithApplicationNameOption(userAgent)) } } +func WithConnectionTTL(ttl time.Duration) Option { + return func(c *Config) { + c.connectionTTL = ttl + } +} + func WithCredentials(credentials credentials.Credentials) Option { return func(c *Config) { c.credentials = credentials diff --git a/connection.go b/connection.go index 9bff94e31..14adc7470 100644 --- a/connection.go +++ b/connection.go @@ -16,10 +16,10 @@ import ( // Connection interface provide access to YDB service clients // Interface and list of clients may be changed in the future // -// Deprecated: use directly *Driver type from ydb.Open instead -// -//nolint:interfacebloat -type Connection interface { +// Deprecated: use Driver instance instead. +// Will be removed at next major release. +// Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated +type Connection interface { //nolint:interfacebloat // Endpoint returns initial endpoint Endpoint() string @@ -37,9 +37,7 @@ type Connection interface { // Query returns query client // - // # Experimental - // - // Notice: This API is EXPERIMENTAL and may be changed or removed in a later release. + // Experimental: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#experimental Query() query.Client // Scheme returns scheme client diff --git a/coordination/coordination.go b/coordination/coordination.go index 7bd9f6dbf..b0ff0c011 100644 --- a/coordination/coordination.go +++ b/coordination/coordination.go @@ -2,7 +2,11 @@ package coordination import ( "context" + "fmt" + "math" + "time" + "github.com/ydb-platform/ydb-go-sdk/v3/coordination/options" "github.com/ydb-platform/ydb-go-sdk/v3/scheme" ) @@ -11,4 +15,185 @@ type Client interface { AlterNode(ctx context.Context, path string, config NodeConfig) (err error) DropNode(ctx context.Context, path string) (err error) DescribeNode(ctx context.Context, path string) (_ *scheme.Entry, _ *NodeConfig, err error) + + // Session starts a new session. This method blocks until the server session is created. The context provided + // may be used to cancel the invocation. If the method completes successfully, the session remains alive even if + // the context is canceled. + // + // To ensure resources are not leaked, one of the following actions must be performed: + // + // - call Close on the Session, + // - close the Client which the session was created with, + // - call any method of the Session until the ErrSessionClosed is returned. + // + // Experimental: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#experimental + Session(ctx context.Context, path string, opts ...options.SessionOption) (Session, error) +} + +const ( + // MaxSemaphoreLimit defines the maximum value of the limit parameter in the Session.CreateSemaphore method. + MaxSemaphoreLimit = math.MaxUint64 + + // Exclusive is just a shortcut for the maximum semaphore limit value. You can use this to acquire a semaphore in + // the exclusive mode if it was created with the limit value of MaxSemaphoreLimit, which is always true for + // ephemeral semaphores. + Exclusive = math.MaxUint64 + + // Shared is just a shortcut for the minimum semaphore limit value (1). You can use this to acquire a semaphore in + // the shared mode if it was created with the limit value of MaxSemaphoreLimit, which is always true for ephemeral + // semaphores. + Shared = 1 +) + +// Session defines a coordination service backed session. +// +// In general, Session API is concurrency-friendly, you can safely access all of its methods concurrently. +// +// The client guarantees that sequential calls of the methods are sent to the server in the same order. However, the +// session client may reorder and suspend some of the requests without violating correctness of the execution. This also +// applies to the situations when the underlying gRPC stream has been recreated due to network or server issues. +// +// The client automatically keeps the underlying gRPC stream alive by sending keep-alive (ping-pong) requests. If the +// client can no longer consider the session alive, it immediately cancels the session context which also leads to +// cancellation of contexts of all semaphore leases created by this session. +type Session interface { + // Close closes the coordination service session. It cancels all active requests on the server and notifies every + // pending or waiting for response request on the client side. It also cancels the session context and tries to + // stop the session gracefully on the server. If the ctx is canceled, this will not wait for the server session to + // become stopped and returns immediately with an error. Once this function returns with no error, all subsequent + // calls will be noop. + Close(ctx context.Context) error + + // Context returns the context of the session. It is canceled when the underlying server session is over or if the + // client could not get any successful response from the server before the session timeout (see + // options.WithSessionTimeout). + Context() context.Context + + // CreateSemaphore creates a new semaphore. This method waits until the server successfully creates a new semaphore + // or returns an error. + // + // This method is not idempotent. If the request has been sent to the server but no reply has been received, it + // returns the ErrOperationStatusUnknown error. + CreateSemaphore(ctx context.Context, name string, limit uint64, opts ...options.CreateSemaphoreOption) error + + // UpdateSemaphore changes semaphore data. This method waits until the server successfully updates the semaphore or + // returns an error. + // + // This method is not idempotent. The client will automatically retry in the case of network or server failure + // unless it leaves the client state inconsistent. + UpdateSemaphore(ctx context.Context, name string, opts ...options.UpdateSemaphoreOption) error + + // DeleteSemaphore deletes an existing semaphore. This method waits until the server successfully deletes the + // semaphore or returns an error. + // + // This method is not idempotent. If the request has been sent to the server but no reply has been received, it + // returns the ErrOperationStatusUnknown error. + DeleteSemaphore(ctx context.Context, name string, opts ...options.DeleteSemaphoreOption) error + + // DescribeSemaphore returns the state of the semaphore. + // + // This method is idempotent. The client will automatically retry in the case of network or server failure. + DescribeSemaphore( + ctx context.Context, + name string, + opts ...options.DescribeSemaphoreOption, + ) (*SemaphoreDescription, error) + + // AcquireSemaphore acquires the semaphore. If you acquire an ephemeral semaphore (see options.WithEphemeral), its + // limit will be set to MaxSemaphoreLimit. Later requests override previous operations with the same semaphore, e.g. + // to reduce acquired count, change timeout or attached data. + // + // This method blocks until the semaphore is acquired, an error is returned from the server or the session is + // closed. If the operation context was canceled but the server replied that the semaphore was actually acquired, + // the client will automatically release the semaphore. + // + // Semaphore waiting is fair: the semaphore guarantees that other sessions invoking the AcquireSemaphore method + // acquire permits in the order which they were called (FIFO). If a session invokes the AcquireSemaphore method + // multiple times while the first invocation is still in process, the position in the queue remains unchanged. + // + // This method is idempotent. The client will automatically retry in the case of network or server failure. + AcquireSemaphore( + ctx context.Context, + name string, + count uint64, + opts ...options.AcquireSemaphoreOption, + ) (Lease, error) + + // SessionID returns a server-generated identifier of the session. This value is permanent and unique within the + // coordination service node. + SessionID() uint64 + + // Reconnect forcibly shuts down the underlying gRPC stream and initiates a new one. This method is highly unlikely + // to be of use in a typical application but is extremely useful for testing an API implementation. + Reconnect() +} + +// Lease is the object which defines the rights of the session to the acquired semaphore. Lease is alive until its +// context is not canceled. This may happen implicitly, when the associated session becomes lost or closed, or +// explicitly, if someone calls the Release method of the lease. +type Lease interface { + // Context returns the context of the lease. It is canceled when the session it was created by was lost or closed, + // or if the lease was released by calling the Release method. + Context() context.Context + + // Release releases the acquired lease to the semaphore. It also cancels the context of the lease. This method does + // not take a ctx argument, but you can cancel the execution of it by closing the session or canceling its context. + Release() error + + // Session returns the session which this lease was created by. + Session() Session +} + +// SemaphoreDescription describes the state of a semaphore. +type SemaphoreDescription struct { + // Name is the name of the semaphore. + Name string + + // Limit is the maximum number of tokens that may be acquired. + Limit uint64 + + // Count is the number of tokens currently acquired by its owners. + Count uint64 + + // Ephemeral semaphores are deleted when there are no owners and waiters left. + Ephemeral bool + + // Data is user-defined data attached to the semaphore. + Data []byte + + // Owner is the list of current owners of the semaphore. + Owners []*SemaphoreSession + + // Waiter is the list of current waiters of the semaphore. + Waiters []*SemaphoreSession +} + +// SemaphoreSession describes an owner or a waiter of this semaphore. +type SemaphoreSession struct { + // SessionID is the id of the session which tried to acquire the semaphore. + SessionID uint64 + + // Count is the number of tokens for the acquire operation. + Count uint64 + + // OrderId is a monotonically increasing id which determines locking order. + OrderID uint64 + + // Data is user-defined data attached to the acquire operation. + Data []byte + + // Timeout is the timeout for the operation in the waiter queue. If this is time.Duration(math.MaxInt64) the session + // will wait for the semaphore until the operation is canceled. + Timeout time.Duration +} + +func (d *SemaphoreDescription) String() string { + return fmt.Sprintf( + "{Name: %q Limit: %d Count: %d Ephemeral: %t Data: %q Owners: %s Waiters: %s}", + d.Name, d.Limit, d.Count, d.Ephemeral, d.Data, d.Owners, d.Waiters) +} + +func (s *SemaphoreSession) String() string { + return fmt.Sprintf("{SessionID: %d Count: %d OrderID: %d Data: %q TimeoutMillis: %v}", + s.SessionID, s.Count, s.OrderID, s.Data, s.Timeout) } diff --git a/coordination/errors.go b/coordination/errors.go new file mode 100644 index 000000000..2f10cd557 --- /dev/null +++ b/coordination/errors.go @@ -0,0 +1,18 @@ +package coordination + +import "errors" + +var ( + // ErrOperationStatusUnknown indicates that the request has been sent to the server but no reply has been received. + // The client usually automatically retries calls of that kind, but there are cases when it is not possible: + // - the request is not idempotent, non-idempotent requests are never retried, + // - the session was lost and its context is canceled. + ErrOperationStatusUnknown = errors.New("operation status is unknown") + + // ErrSessionClosed indicates that the Session object is closed. + ErrSessionClosed = errors.New("session is closed") + + // ErrAcquireTimeout indicates that the Session.AcquireSemaphore method could not acquire the semaphore before the + // operation timeout (see options.WithAcquireTimeout). + ErrAcquireTimeout = errors.New("acquire semaphore timeout") +) diff --git a/coordination/example_test.go b/coordination/example_test.go index 57657d779..20ca57852 100644 --- a/coordination/example_test.go +++ b/coordination/example_test.go @@ -6,10 +6,11 @@ import ( "github.com/ydb-platform/ydb-go-sdk/v3" "github.com/ydb-platform/ydb-go-sdk/v3/coordination" + "github.com/ydb-platform/ydb-go-sdk/v3/coordination/options" ) //nolint:errcheck -func Example() { +func Example_createDropNode() { ctx := context.TODO() db, err := ydb.Open(ctx, "grpc://localhost:2136/local") if err != nil { @@ -41,3 +42,106 @@ func Example() { } fmt.Printf("node description: %+v\nnode config: %+v\n", e, c) } + +func Example_semaphore() { + ctx := context.TODO() + db, err := ydb.Open(ctx, "grpc://localhost:2136/local") + if err != nil { + fmt.Printf("failed to connect: %v", err) + + return + } + defer db.Close(ctx) // cleanup resources + // create node + err = db.Coordination().CreateNode(ctx, "/local/test", coordination.NodeConfig{ + Path: "", + SelfCheckPeriodMillis: 1000, + SessionGracePeriodMillis: 1000, + ReadConsistencyMode: coordination.ConsistencyModeStrict, + AttachConsistencyMode: coordination.ConsistencyModeStrict, + RatelimiterCountersMode: coordination.RatelimiterCountersModeDetailed, + }) + if err != nil { + fmt.Printf("failed to create node: %v", err) + + return + } + defer func() { + dropNodeErr := db.Coordination().DropNode(ctx, "/local/test") + if dropNodeErr != nil { + fmt.Printf("failed to drop node: %v\n", dropNodeErr) + } + }() + + e, c, err := db.Coordination().DescribeNode(ctx, "/local/test") + if err != nil { + fmt.Printf("failed to describe node: %v\n", err) + + return + } + fmt.Printf("node description: %+v\nnode config: %+v\n", e, c) + + s, err := db.Coordination().Session(ctx, "/local/test") + if err != nil { + fmt.Printf("failed to create session: %v\n", err) + + return + } + defer s.Close(ctx) + fmt.Printf("session 1 created, id: %d\n", s.SessionID()) + + err = s.CreateSemaphore(ctx, "my-semaphore", 20, options.WithCreateData([]byte{1, 2, 3})) + if err != nil { + fmt.Printf("failed to create semaphore: %v", err) + + return + } + fmt.Printf("semaphore my-semaphore created\n") + + lease, err := s.AcquireSemaphore(ctx, "my-semaphore", 10) + if err != nil { + fmt.Printf("failed to acquire semaphore: %v", err) + + return + } + defer func() { + releaseErr := lease.Release() + if releaseErr != nil { + fmt.Printf("failed to release lease: %v", releaseErr) + } + }() + + fmt.Printf("session 1 acquired semaphore 10\n") + + s.Reconnect() + fmt.Printf("session 1 reconnected\n") + + desc, err := s.DescribeSemaphore( + ctx, + "my-semaphore", + options.WithDescribeOwners(true), + options.WithDescribeWaiters(true), + ) + if err != nil { + fmt.Printf("failed to describe semaphore: %v", err) + + return + } + fmt.Printf("session 1 described semaphore %v\n", desc) + + err = lease.Release() + if err != nil { + fmt.Printf("failed to release semaphore: %v", err) + + return + } + fmt.Printf("session 1 released semaphore my-semaphore\n") + + err = s.DeleteSemaphore(ctx, "my-semaphore", options.WithForceDelete(true)) + if err != nil { + fmt.Printf("failed to delete semaphore: %v", err) + + return + } + fmt.Printf("deleted semaphore my-semaphore\n") +} diff --git a/coordination/options/options.go b/coordination/options/options.go new file mode 100644 index 000000000..e156bc068 --- /dev/null +++ b/coordination/options/options.go @@ -0,0 +1,173 @@ +package options + +import ( + "math" + "time" + + "github.com/ydb-platform/ydb-go-genproto/protos/Ydb_Coordination" +) + +// WithDescription returns an SessionOption that specifies a user-defined description that may be used to describe +// the client. +func WithDescription(description string) SessionOption { + return func(c *CreateSessionOptions) { + c.Description = description + } +} + +// WithSessionTimeout returns an SessionOption that specifies the timeout during which client may restore a +// detached session. The client is forced to terminate the session if the last successful session request occurred +// earlier than this time. +// +// If this is not set, the client uses the default 5 seconds. +func WithSessionTimeout(timeout time.Duration) SessionOption { + return func(c *CreateSessionOptions) { + c.SessionTimeout = timeout + } +} + +// WithSessionStartTimeout returns an SessionOption that specifies the time that the client should wait for a +// response to the StartSession request from the server before it terminates the gRPC stream and tries to reconnect. +// +// If this is not set, the client uses the default time 1 second. +func WithSessionStartTimeout(timeout time.Duration) SessionOption { + return func(c *CreateSessionOptions) { + c.SessionStartTimeout = timeout + } +} + +// WithSessionStopTimeout returns an SessionOption that specifies the time that the client should wait for a +// response to the StopSession request from the server before it terminates the gRPC stream and tries to reconnect. +// +// If this is not set, the client uses the default time 1 second. +func WithSessionStopTimeout(timeout time.Duration) SessionOption { + return func(c *CreateSessionOptions) { + c.SessionStartTimeout = timeout + } +} + +// WithSessionKeepAliveTimeout returns an SessionOption that specifies the time that the client will wait before +// it terminates the gRPC stream and tries to reconnect if no successful responses have been received from the server. +// +// If this is not set, the client uses the default time 10 seconds. +func WithSessionKeepAliveTimeout(timeout time.Duration) SessionOption { + return func(c *CreateSessionOptions) { + c.SessionKeepAliveTimeout = timeout + } +} + +// WithSessionReconnectDelay returns an SessionOption that specifies the time that the client will wait before it +// tries to reconnect the underlying gRPC stream in case of error. +// +// If this is not set, the client uses the default time 500 milliseconds. +func WithSessionReconnectDelay(delay time.Duration) SessionOption { + return func(c *CreateSessionOptions) { + c.SessionReconnectDelay = delay + } +} + +// SessionOption configures how we create a new session. +type SessionOption func(c *CreateSessionOptions) + +// CreateSessionOptions configure an Session call. CreateSessionOptions are set by the SessionOption values +// passed to the Session function. +type CreateSessionOptions struct { + Description string + SessionTimeout time.Duration + SessionStartTimeout time.Duration + SessionStopTimeout time.Duration + SessionKeepAliveTimeout time.Duration + SessionReconnectDelay time.Duration +} + +// WithEphemeral returns an AcquireSemaphoreOption that causes to create an ephemeral semaphore. +// +// Ephemeral semaphores are created with the first acquire operation and automatically deleted with the last release +// operation. Ephemeral semaphore are always created with the limit of coordination.MaxSemaphoreLimit. +func WithEphemeral(ephemeral bool) AcquireSemaphoreOption { + return func(c *Ydb_Coordination.SessionRequest_AcquireSemaphore) { + c.Ephemeral = ephemeral + } +} + +// WithAcquireTimeout returns an AcquireSemaphoreOption which sets the timeout after which the operation fails if it +// is still waiting in the queue. Use 0 to make the AcquireSemaphore method fail immediately if the semaphore is already +// acquired by another session. +// +// If this is not set, the client waits for the acquire operation result until the operation or session context is done. +// You can reset the default value of this timeout by calling the WithAcquireInfiniteTimeout method. +func WithAcquireTimeout(timeout time.Duration) AcquireSemaphoreOption { + return func(c *Ydb_Coordination.SessionRequest_AcquireSemaphore) { + c.TimeoutMillis = uint64(timeout.Milliseconds()) + } +} + +// WithAcquireInfiniteTimeout returns an AcquireSemaphoreOption which disables the timeout after which the operation +// fails if it is still waiting in the queue. +// +// This is the default behavior. You can set the specific timeout by calling the WithAcquireTimeout method. +func WithAcquireInfiniteTimeout() AcquireSemaphoreOption { + return func(c *Ydb_Coordination.SessionRequest_AcquireSemaphore) { + c.TimeoutMillis = math.MaxUint64 + } +} + +// WithAcquireData returns an AcquireSemaphoreOption which attaches user-defined data to the operation. +func WithAcquireData(data []byte) AcquireSemaphoreOption { + return func(c *Ydb_Coordination.SessionRequest_AcquireSemaphore) { + c.Data = data + } +} + +// AcquireSemaphoreOption configures how we acquire a semaphore. +type AcquireSemaphoreOption func(c *Ydb_Coordination.SessionRequest_AcquireSemaphore) + +// WithForceDelete return a DeleteSemaphoreOption which allows to delete a semaphore even if it is currently acquired +// by other sessions. +func WithForceDelete(force bool) DeleteSemaphoreOption { + return func(c *Ydb_Coordination.SessionRequest_DeleteSemaphore) { + c.Force = force + } +} + +// DeleteSemaphoreOption configures how we delete a semaphore. +type DeleteSemaphoreOption func(c *Ydb_Coordination.SessionRequest_DeleteSemaphore) + +// WithCreateData return a CreateSemaphoreOption which attaches user-defined data to the semaphore. +func WithCreateData(data []byte) CreateSemaphoreOption { + return func(c *Ydb_Coordination.SessionRequest_CreateSemaphore) { + c.Data = data + } +} + +// CreateSemaphoreOption configures how we create a semaphore. +type CreateSemaphoreOption func(c *Ydb_Coordination.SessionRequest_CreateSemaphore) + +// WithUpdateData return a UpdateSemaphoreOption which changes user-defined data in the semaphore. +func WithUpdateData(data []byte) UpdateSemaphoreOption { + return func(c *Ydb_Coordination.SessionRequest_UpdateSemaphore) { + c.Data = data + } +} + +// UpdateSemaphoreOption configures how we update a semaphore. +type UpdateSemaphoreOption func(c *Ydb_Coordination.SessionRequest_UpdateSemaphore) + +// WithDescribeOwners return a DescribeSemaphoreOption which causes server send the list of owners in the response +// to the DescribeSemaphore request. +func WithDescribeOwners(describeOwners bool) DescribeSemaphoreOption { + return func(c *Ydb_Coordination.SessionRequest_DescribeSemaphore) { + c.IncludeOwners = describeOwners + } +} + +// WithDescribeWaiters return a DescribeSemaphoreOption which causes server send the list of waiters in the response +// to the DescribeSemaphore request. +func WithDescribeWaiters(describeWaiters bool) DescribeSemaphoreOption { + return func(c *Ydb_Coordination.SessionRequest_DescribeSemaphore) { + c.IncludeWaiters = describeWaiters + } +} + +// DescribeSemaphoreOption configures how we update a semaphore. +type DescribeSemaphoreOption func(c *Ydb_Coordination.SessionRequest_DescribeSemaphore) diff --git a/credentials/credentials.go b/credentials/credentials.go index 8829eb973..b11106414 100644 --- a/credentials/credentials.go +++ b/credentials/credentials.go @@ -34,3 +34,21 @@ func NewStaticCredentials( ) *credentials.Static { return credentials.NewStaticCredentials(user, password, authEndpoint, opts...) } + +// NewOauth2TokenExchangeCredentials makes OAuth 2.0 token exchange protocol credentials object +// https://www.rfc-editor.org/rfc/rfc8693 +func NewOauth2TokenExchangeCredentials( + opts ...credentials.Oauth2TokenExchangeCredentialsOption, +) (Credentials, error) { + return credentials.NewOauth2TokenExchangeCredentials(opts...) +} + +// NewJWTTokenSource makes JWT token source for OAuth 2.0 token exchange credentials +func NewJWTTokenSource(opts ...credentials.JWTTokenSourceOption) (credentials.TokenSource, error) { + return credentials.NewJWTTokenSource(opts...) +} + +// NewFixedTokenSource makes fixed token source for OAuth 2.0 token exchange credentials +func NewFixedTokenSource(token, tokenType string) credentials.TokenSource { + return credentials.NewFixedTokenSource(token, tokenType) +} diff --git a/credentials/options.go b/credentials/options.go index 9da142092..5d944dae6 100644 --- a/credentials/options.go +++ b/credentials/options.go @@ -1,11 +1,20 @@ package credentials import ( + "time" + + "github.com/golang-jwt/jwt/v4" "google.golang.org/grpc" "github.com/ydb-platform/ydb-go-sdk/v3/internal/credentials" ) +type Oauth2TokenExchangeCredentialsOption = credentials.Oauth2TokenExchangeCredentialsOption + +type TokenSource = credentials.TokenSource + +type Token = credentials.Token + // WithSourceInfo option append to credentials object the source info for reporting source info details on error case func WithSourceInfo(sourceInfo string) credentials.SourceInfoOption { return credentials.WithSourceInfo(sourceInfo) @@ -15,3 +24,118 @@ func WithSourceInfo(sourceInfo string) credentials.SourceInfoOption { func WithGrpcDialOptions(opts ...grpc.DialOption) credentials.StaticCredentialsOption { return credentials.WithGrpcDialOptions(opts...) } + +// TokenEndpoint +func WithTokenEndpoint(endpoint string) Oauth2TokenExchangeCredentialsOption { + return credentials.WithTokenEndpoint(endpoint) +} + +// GrantType +func WithGrantType(grantType string) Oauth2TokenExchangeCredentialsOption { + return credentials.WithGrantType(grantType) +} + +// Resource +func WithResource(resource string) Oauth2TokenExchangeCredentialsOption { + return credentials.WithResource(resource) +} + +// RequestedTokenType +func WithRequestedTokenType(requestedTokenType string) Oauth2TokenExchangeCredentialsOption { + return credentials.WithRequestedTokenType(requestedTokenType) +} + +// Scope +func WithScope(scope ...string) Oauth2TokenExchangeCredentialsOption { + return credentials.WithScope(scope...) +} + +// RequestTimeout +func WithRequestTimeout(timeout time.Duration) Oauth2TokenExchangeCredentialsOption { + return credentials.WithRequestTimeout(timeout) +} + +// SubjectTokenSource +func WithSubjectToken(subjectToken credentials.TokenSource) Oauth2TokenExchangeCredentialsOption { + return credentials.WithSubjectToken(subjectToken) +} + +// SubjectTokenSource +func WithFixedSubjectToken(token, tokenType string) Oauth2TokenExchangeCredentialsOption { + return credentials.WithFixedSubjectToken(token, tokenType) +} + +// SubjectTokenSource +func WithJWTSubjectToken(opts ...credentials.JWTTokenSourceOption) Oauth2TokenExchangeCredentialsOption { + return credentials.WithJWTSubjectToken(opts...) +} + +// ActorTokenSource +func WithActorToken(actorToken credentials.TokenSource) Oauth2TokenExchangeCredentialsOption { + return credentials.WithActorToken(actorToken) +} + +// ActorTokenSource +func WithFixedActorToken(token, tokenType string) Oauth2TokenExchangeCredentialsOption { + return credentials.WithFixedActorToken(token, tokenType) +} + +// ActorTokenSource +func WithJWTActorToken(opts ...credentials.JWTTokenSourceOption) Oauth2TokenExchangeCredentialsOption { + return credentials.WithJWTActorToken(opts...) +} + +// Audience +type oauthCredentialsAndJWTCredentialsOption interface { + credentials.Oauth2TokenExchangeCredentialsOption + credentials.JWTTokenSourceOption +} + +func WithAudience(audience ...string) oauthCredentialsAndJWTCredentialsOption { + return credentials.WithAudience(audience...) +} + +// Issuer +func WithIssuer(issuer string) credentials.JWTTokenSourceOption { + return credentials.WithIssuer(issuer) +} + +// Subject +func WithSubject(subject string) credentials.JWTTokenSourceOption { + return credentials.WithSubject(subject) +} + +// ID +func WithID(id string) credentials.JWTTokenSourceOption { + return credentials.WithID(id) +} + +// TokenTTL +func WithTokenTTL(ttl time.Duration) credentials.JWTTokenSourceOption { + return credentials.WithTokenTTL(ttl) +} + +// SigningMethod +func WithSigningMethod(method jwt.SigningMethod) credentials.JWTTokenSourceOption { + return credentials.WithSigningMethod(method) +} + +// KeyID +func WithKeyID(id string) credentials.JWTTokenSourceOption { + return credentials.WithKeyID(id) +} + +// PrivateKey +func WithPrivateKey(key interface{}) credentials.JWTTokenSourceOption { + return credentials.WithPrivateKey(key) +} + +// PrivateKey +func WithRSAPrivateKeyPEMContent(key []byte) credentials.JWTTokenSourceOption { + return credentials.WithRSAPrivateKeyPEMContent(key) +} + +// PrivateKey +func WithRSAPrivateKeyPEMFile(path string) credentials.JWTTokenSourceOption { + return credentials.WithRSAPrivateKeyPEMFile(path) +} diff --git a/driver.go b/driver.go index 190254dce..592466443 100644 --- a/driver.go +++ b/driver.go @@ -115,7 +115,9 @@ func (d *Driver) trace() *trace.Driver { // //nolint:nonamedreturns func (d *Driver) Close(ctx context.Context) (finalErr error) { - onDone := trace.DriverOnClose(d.trace(), &ctx, stack.FunctionID("")) + onDone := trace.DriverOnClose(d.trace(), &ctx, + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/ydb.(*Driver).Close"), + ) defer func() { onDone(finalErr) }() @@ -189,9 +191,7 @@ func (d *Driver) Table() table.Client { // Query returns query client // -// # Experimental -// -// Notice: This API is EXPERIMENTAL and may be changed or removed in a later release. +// Experimental: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#experimental func (d *Driver) Query() query.Client { return d.query.Get() } @@ -248,7 +248,7 @@ func Open(ctx context.Context, dsn string, opts ...Option) (_ *Driver, err error onDone := trace.DriverOnInit( d.trace(), &ctx, - stack.FunctionID(""), + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/ydb.Open"), d.config.Endpoint(), d.config.Database(), d.config.Secure(), ) defer func() { @@ -273,10 +273,12 @@ func MustOpen(ctx context.Context, dsn string, opts ...Option) *Driver { // New connects to database and return driver runtime holder // -// Deprecated: use Open with required param connectionString instead -// -//nolint:nonamedreturns -func New(ctx context.Context, opts ...Option) (_ *Driver, err error) { +// Deprecated: use ydb.Open instead. +// New func have no required arguments, such as connection string. +// Thats why we recognize that New have wrong signature. +// Will be removed after Oct 2024. +// Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated +func New(ctx context.Context, opts ...Option) (_ *Driver, err error) { //nolint:nonamedreturns d, err := newConnectionFromOptions(ctx, opts...) if err != nil { return nil, xerrors.WithStackTrace(err) @@ -284,7 +286,7 @@ func New(ctx context.Context, opts ...Option) (_ *Driver, err error) { onDone := trace.DriverOnInit( d.trace(), &ctx, - stack.FunctionID(""), + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/ydb.New"), d.config.Endpoint(), d.config.Database(), d.config.Secure(), ) defer func() { @@ -300,7 +302,7 @@ func New(ctx context.Context, opts ...Option) (_ *Driver, err error) { //nolint:cyclop, nonamedreturns func newConnectionFromOptions(ctx context.Context, opts ...Option) (_ *Driver, err error) { - ctx, driverCtxCancel := xcontext.WithCancel(xcontext.WithoutDeadline(ctx)) + ctx, driverCtxCancel := xcontext.WithCancel(xcontext.ValueOnly(ctx)) defer func() { if err != nil { driverCtxCancel() @@ -401,7 +403,7 @@ func (d *Driver) connect(ctx context.Context) (err error) { } d.table = xsync.OnceValue(func() *internalTable.Client { - return internalTable.New(xcontext.WithoutDeadline(ctx), + return internalTable.New(xcontext.ValueOnly(ctx), d.balancer, tableConfig.New( append( @@ -416,7 +418,7 @@ func (d *Driver) connect(ctx context.Context) (err error) { }) d.query = xsync.OnceValue(func() *internalQuery.Client { - return internalQuery.New(xcontext.WithoutDeadline(ctx), + return internalQuery.New(xcontext.ValueOnly(ctx), d.balancer, queryConfig.New( append( @@ -434,7 +436,7 @@ func (d *Driver) connect(ctx context.Context) (err error) { } d.scheme = xsync.OnceValue(func() *internalScheme.Client { - return internalScheme.New(xcontext.WithoutDeadline(ctx), + return internalScheme.New(xcontext.ValueOnly(ctx), d.balancer, schemeConfig.New( append( @@ -450,7 +452,7 @@ func (d *Driver) connect(ctx context.Context) (err error) { }) d.coordination = xsync.OnceValue(func() *internalCoordination.Client { - return internalCoordination.New(xcontext.WithoutDeadline(ctx), + return internalCoordination.New(xcontext.ValueOnly(ctx), d.balancer, coordinationConfig.New( append( @@ -465,7 +467,7 @@ func (d *Driver) connect(ctx context.Context) (err error) { }) d.ratelimiter = xsync.OnceValue(func() *internalRatelimiter.Client { - return internalRatelimiter.New(xcontext.WithoutDeadline(ctx), + return internalRatelimiter.New(xcontext.ValueOnly(ctx), d.balancer, ratelimiterConfig.New( append( @@ -480,7 +482,7 @@ func (d *Driver) connect(ctx context.Context) (err error) { }) d.discovery = xsync.OnceValue(func() *internalDiscovery.Client { - return internalDiscovery.New(xcontext.WithoutDeadline(ctx), + return internalDiscovery.New(xcontext.ValueOnly(ctx), d.pool.Get(endpoint.New(d.config.Endpoint())), discoveryConfig.New( append( @@ -499,7 +501,7 @@ func (d *Driver) connect(ctx context.Context) (err error) { }) d.scripting = xsync.OnceValue(func() *internalScripting.Client { - return internalScripting.New(xcontext.WithoutDeadline(ctx), + return internalScripting.New(xcontext.ValueOnly(ctx), d.balancer, scriptingConfig.New( append( @@ -514,7 +516,7 @@ func (d *Driver) connect(ctx context.Context) (err error) { }) d.topic = xsync.OnceValue(func() *topicclientinternal.Client { - return topicclientinternal.New(xcontext.WithoutDeadline(ctx), + return topicclientinternal.New(xcontext.ValueOnly(ctx), d.balancer, d.config.Credentials(), append( diff --git a/examples/auth/README.md b/examples/auth/README.md index 273113372..e2b384488 100644 --- a/examples/auth/README.md +++ b/examples/auth/README.md @@ -4,6 +4,7 @@ Auth examples helps to understand YDB authentication: * `access_token_credentials` - example of use access token credentials * `anonymous_credentials` - example of use anonymous credentials * `metadata_credentials` - example of use metadata credentials +* `oauth2_token_exchange_credentials` - example of use oauth 2.0 token exchange credentials * `service_account_credentials` - example of use service account key file credentials * `static_credentials` - example of use static credentials * `environ` - example of use environment variables to configure YDB authenticate diff --git a/examples/auth/oauth2_token_exchange_credentials/README.md b/examples/auth/oauth2_token_exchange_credentials/README.md new file mode 100644 index 000000000..a13adf8d8 --- /dev/null +++ b/examples/auth/oauth2_token_exchange_credentials/README.md @@ -0,0 +1,8 @@ +# Authenticate with oauth 2.0 token exchange credentials + +`oauth2_token_exchange_credentials` example provides code snippet for authentication to YDB with oauth 2.0 token exchange credentials + +## Runing code snippet +```bash +oauth2_token_exchange_credentials -ydb="grpcs://endpoint/?database=database" -token-endpoint="https://exchange.token.endpoint/oauth2/token/exchange" -key-id="123" -private-key-file="path/to/key/file" -audience="test-aud" -issuer="test-issuer" -subject="test-subject" +``` diff --git a/examples/auth/oauth2_token_exchange_credentials/main.go b/examples/auth/oauth2_token_exchange_credentials/main.go new file mode 100644 index 000000000..e20e8a882 --- /dev/null +++ b/examples/auth/oauth2_token_exchange_credentials/main.go @@ -0,0 +1,107 @@ +package main + +import ( + "context" + "flag" + "fmt" + "os" + + "github.com/golang-jwt/jwt/v4" + ydb "github.com/ydb-platform/ydb-go-sdk/v3" + "github.com/ydb-platform/ydb-go-sdk/v3/credentials" +) + +var ( + dsn string + tokenEndpoint string + keyID string + privateKeyFile string + audience string + issuer string + subject string +) + +func init() { //nolint:gochecknoinits + required := []string{"ydb", "private-key-file", "key-id", "token-endpoint"} + flagSet := flag.NewFlagSet(os.Args[0], flag.ExitOnError) + flagSet.Usage = func() { + out := flagSet.Output() + _, _ = fmt.Fprintf(out, "Usage:\n%s [options]\n", os.Args[0]) + _, _ = fmt.Fprintf(out, "\nOptions:\n") + flagSet.PrintDefaults() + } + flagSet.StringVar(&dsn, + "ydb", "", + "YDB connection string", + ) + flagSet.StringVar(&tokenEndpoint, + "token-endpoint", "", + "oauth 2.0 token exchange endpoint", + ) + flagSet.StringVar(&keyID, + "key-id", "", + "key id for jwt token", + ) + flagSet.StringVar(&privateKeyFile, + "private-key-file", "", + "RSA private key file for jwt token in pem format", + ) + flagSet.StringVar(&audience, + "audience", "", + "audience", + ) + flagSet.StringVar(&issuer, + "issuer", "", + "jwt token issuer", + ) + flagSet.StringVar(&subject, + "subject", "", + "jwt token subject", + ) + if err := flagSet.Parse(os.Args[1:]); err != nil { + flagSet.Usage() + os.Exit(1) + } + flagSet.Visit(func(f *flag.Flag) { + for i, arg := range required { + if arg == f.Name { + required = append(required[:i], required[i+1:]...) + } + } + }) + if len(required) > 0 { + fmt.Printf("\nSome required options not defined: %v\n\n", required) + flagSet.Usage() + os.Exit(1) + } +} + +func main() { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + db, err := ydb.Open(ctx, dsn, + ydb.WithOauth2TokenExchangeCredentials( + credentials.WithTokenEndpoint(tokenEndpoint), + credentials.WithAudience(audience), + credentials.WithJWTSubjectToken( + credentials.WithSigningMethod(jwt.SigningMethodRS256), + credentials.WithKeyID(keyID), + credentials.WithRSAPrivateKeyPEMFile(privateKeyFile), + credentials.WithIssuer(issuer), + credentials.WithSubject(subject), + credentials.WithAudience(audience), + ), + ), + ) + if err != nil { + panic(err) + } + defer func() { _ = db.Close(ctx) }() + + whoAmI, err := db.Discovery().WhoAmI(ctx) + if err != nil { + panic(err) + } + + fmt.Println(whoAmI.String()) +} diff --git a/examples/coordination/README.md b/examples/coordination/README.md new file mode 100644 index 000000000..d5248aa99 --- /dev/null +++ b/examples/coordination/README.md @@ -0,0 +1,156 @@ +# Coordination Examples + +Coordination Examples demonstrate how to implement various distributed consistency primitives using the +`coordination.Client` API. + +## Locks + +Strictly speaking, this is an implementation of a [lease](https://en.wikipedia.org/wiki/Lease_(computer_science)), not a +lock in the traditional sense. + +Consider an application which is running a number of different instances. You want to ensure that some shared resource +is accessed by only one instance at a time. + +The `lock` application is an example of that instance. When it starts, it waits until the distributed lock is acquired. +When the application cannot consider the lock acquired anymore (for example, in the case of network issues) it stops +working and tries to acquire the lock again. + +You may start any number of applications, only one of them will consider itself the holder of the lock at a time. + +Although we call it a lock, this mechanism **cannot be used to implement mutual exclusion by itself**. Session relies on +physical time clocks. The server and the client clocks may be, and actually always are, out of sync. This results in a +situation where for example the server releases the lease but the client still assumes it owns the lease. However, +leases can be used as optimization in order to significantly reduce the possibility of resource contention. + +To start the application with the [YDB Docker database instance](https://ydb.tech/en/docs/getting_started/self_hosted/ydb_docker) run + +```bash +$ go build +$ YDB_ANONYMOUS_CREDENTIALS=1 ./lock -ydb grpc://localhost:2136/local --path /local/test --semaphore lock +``` + +When you stop the application which is currently holding the semaphore, another one should immediately acquire the lock +and start doing its work. + +This example uses an ephemeral semaphore which is always acquired exclusive. The following pseudocode shows how it is +used. + +```go +for { + session, err := db.Coordination().OpenSession(ctx, path) + if err != nil { + // The context is canceled. + break + } + + lease, err := session.AcquireSemaphore(ctx, semaphore, coordination.Exclusive, options.WithEphemeral(true)) + if err != nil { + session.Close(ctx) + continue + } + + // The lock is acquired. + go doWork(lease.Context()) + + // Wait until the lock is released. + <-lease.Context().Done(): + + // The lock is not acquired anymore. + cancelWork() +} +``` + +## Workers + +This is an example of distributing long-running tasks among multiple workers. Consider there is a number of tasks that +need to be processed simultaneously by a pool of workers. Each task is processed independently and the order of +processing does not matter. A worker has a fixed capacity that defines how many tasks it is ready to process at a time. + +Any single task is associated with a Coordination Service semaphore. When the application starts, it grabs all task +semaphores in order to acquire at least `capacity` of them. When it happens, it releases excessive ones and wait until +the application finishes. Until then, it starts a new task for every acquired semaphore and waits until the number of +running tasks becomes equal to the capacity of the worker. + +To start the application with the [YDB Docker database instance](https://ydb.tech/en/docs/getting_started/self_hosted/ydb_docker) run + +```bash +$ go build +$ YDB_ANONYMOUS_CREDENTIALS=1 ./lock -ydb grpc://localhost:2136/local -path /local/test --semaphore-prefix job- --tasks 10 --capacity 4 +``` + +This example uses ephemeral semaphores which are always acquired exclusive. However, in a real application you may +want to use persistent semaphores in order to store the state of workers in attached data. The following pseudocode +shows how it is used. + +```go +for { + session, err := db.Coordination().OpenSession(ctx, path) + if err != nil { + // The context is canceled. + break + } + + semaphoreCtx, semaphoreCancel := context.WithCancel(ctx) + capacitySemaphore := semaphore.NewWeighted(capacity) + leaseChan := make(chan *coordination.Lease) + for _, name := range tasks { + go awaitSemaphore(semaphoreCtx, semaphoreCancel, session, name, capacitySemaphore, leaseChan) + } + + tasksStarted := 0 +loop: + for { + lease := <-leaseChan + + // Run a new task every time we acquire a semaphore. + go doWork(lease.Context()) + + tasksStarted++ + if tasksStarted == capacity { + break + } + } + + // The capacity is full, cancel all Acquire operations. + semaphoreCancel() + + // Wait until the session is alive. + <-session.Context().Done() + + // The tasks must be stopped since we do not own the semaphores anymore. + cancelTasks() +} + +func awaitSemaphore( + ctx context.Context, + cancel context.CancelFunc, + session coordination.Session, + semaphoreName string, + capacitySemaphore *semaphore.Weighted, + leaseChan chan *coordination.Lease, +) { + lease, err := session.AcquireSemaphore( + ctx, + semaphoreName, + coordination.Exclusive, + options.WithEphemeral(true), + ) + if err != nil { + // Let the main loop know that something is wrong. + // ... + return + } + + // If there is a need in tasks for the current worker, provide it with a new lease. + if capacitySemaphore.TryAcquire(1) { + leaseChan <- lease + } else { + // This may happen since we are waiting for all existing semaphores trying to grab the first available to us. + err := lease.Release() + if err != nil { + // Let the main loop know that something is wrong. + // ... + } + } +} +``` diff --git a/examples/coordination/lock/main.go b/examples/coordination/lock/main.go new file mode 100644 index 000000000..905bb1e4b --- /dev/null +++ b/examples/coordination/lock/main.go @@ -0,0 +1,144 @@ +package main + +import ( + "context" + "flag" + "fmt" + "os" + "os/signal" + "sync" + "syscall" + "time" + + environ "github.com/ydb-platform/ydb-go-sdk-auth-environ" + ydb "github.com/ydb-platform/ydb-go-sdk/v3" + "github.com/ydb-platform/ydb-go-sdk/v3/coordination" + "github.com/ydb-platform/ydb-go-sdk/v3/coordination/options" +) + +var ( + dsn string + path string + semaphore string +) + +//nolint:gochecknoinits +func init() { + required := []string{"ydb", "path", "semaphore"} + flagSet := flag.NewFlagSet(os.Args[0], flag.ExitOnError) + flagSet.Usage = func() { + out := flagSet.Output() + _, _ = fmt.Fprintf(out, "Usage:\n%s [options]\n", os.Args[0]) + _, _ = fmt.Fprintf(out, "\nOptions:\n") + flagSet.PrintDefaults() + } + flagSet.StringVar(&dsn, + "ydb", "", + "YDB connection string", + ) + flagSet.StringVar(&path, + "path", "", + "coordination node path", + ) + flagSet.StringVar(&semaphore, + "semaphore", "", + "semaphore name", + ) + if err := flagSet.Parse(os.Args[1:]); err != nil { + flagSet.Usage() + os.Exit(1) + } + flagSet.Visit(func(f *flag.Flag) { + for i, arg := range required { + if arg == f.Name { + required = append(required[:i], required[i+1:]...) + } + } + }) + if len(required) > 0 { + fmt.Printf("\nSome required options not defined: %v\n\n", required) + flagSet.Usage() + os.Exit(1) + } +} + +func main() { + ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) + defer cancel() + + db, err := ydb.Open(ctx, dsn, + environ.WithEnvironCredentials(ctx), + ) + if err != nil { + panic(fmt.Errorf("connect error: %w", err)) + } + defer func() { _ = db.Close(ctx) }() + + err = db.Coordination().CreateNode(ctx, path, coordination.NodeConfig{ + Path: "", + SelfCheckPeriodMillis: 1000, + SessionGracePeriodMillis: 1000, + ReadConsistencyMode: coordination.ConsistencyModeStrict, + AttachConsistencyMode: coordination.ConsistencyModeStrict, + RatelimiterCountersMode: coordination.RatelimiterCountersModeDetailed, + }) + if err != nil { + fmt.Printf("failed to create coordination node: %v\n", err) + + return + } + + for { + fmt.Println("waiting for a lock...") + + session, err := db.Coordination().Session(ctx, path) + if err != nil { + fmt.Println("failed to open session", err) + + return + } + + lease, err := session.AcquireSemaphore(ctx, semaphore, coordination.Exclusive, options.WithEphemeral(true)) + if err != nil { + fmt.Printf("failed to acquire semaphore: %v\n", err) + _ = session.Close(ctx) + + continue + } + + fmt.Println("the lock is acquired") + + wg := sync.WaitGroup{} + wg.Add(1) + go doWork(lease.Context(), &wg) + + select { + case <-ctx.Done(): + fmt.Println("exiting") + + return + case <-lease.Context().Done(): + } + + fmt.Println("the lock is released") + wg.Wait() + } +} + +func doWork(ctx context.Context, wg *sync.WaitGroup) { + defer func() { + fmt.Println("suspending work") + wg.Done() + }() + + fmt.Println("starting work") + for { + fmt.Println("work is in progress...") + + select { + case <-ctx.Done(): + return + case <-time.After(time.Second): + } + } +} diff --git a/examples/coordination/workers/main.go b/examples/coordination/workers/main.go new file mode 100644 index 000000000..451336a7a --- /dev/null +++ b/examples/coordination/workers/main.go @@ -0,0 +1,229 @@ +package main + +import ( + "context" + "flag" + "fmt" + "math/rand" + "os" + "os/signal" + "sync" + "syscall" + "time" + + environ "github.com/ydb-platform/ydb-go-sdk-auth-environ" + ydb "github.com/ydb-platform/ydb-go-sdk/v3" + "github.com/ydb-platform/ydb-go-sdk/v3/coordination" + "github.com/ydb-platform/ydb-go-sdk/v3/coordination/options" + "golang.org/x/sync/semaphore" +) + +var ( + dsn string + path string + semaphorePrefix string + taskCount int + capacity int +) + +//nolint:gochecknoinits +func init() { + required := []string{"ydb", "path"} + flagSet := flag.NewFlagSet(os.Args[0], flag.ExitOnError) + flagSet.Usage = func() { + out := flagSet.Output() + _, _ = fmt.Fprintf(out, "Usage:\n%s [options]\n", os.Args[0]) + _, _ = fmt.Fprintf(out, "\nOptions:\n") + flagSet.PrintDefaults() + } + flagSet.StringVar(&dsn, + "ydb", "", + "YDB connection string", + ) + flagSet.StringVar(&path, + "path", "", + "coordination node path", + ) + flagSet.StringVar(&semaphorePrefix, + "semaphore-prefix", "job-", + "semaphore prefix", + ) + flagSet.IntVar(&taskCount, + "tasks", 10, + "the number of tasks", + ) + flagSet.IntVar(&capacity, + "capacity", 4, + "the maximum number of tasks a worker can run", + ) + if err := flagSet.Parse(os.Args[1:]); err != nil { + flagSet.Usage() + os.Exit(1) + } + flagSet.Visit(func(f *flag.Flag) { + for i, arg := range required { + if arg == f.Name { + required = append(required[:i], required[i+1:]...) + } + } + }) + if len(required) > 0 { + fmt.Printf("\nSome required options not defined: %v\n\n", required) + flagSet.Usage() + os.Exit(1) + } +} + +func main() { + ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) + defer cancel() + + db, err := ydb.Open(ctx, dsn, + environ.WithEnvironCredentials(ctx), + ) + if err != nil { + panic(fmt.Errorf("connect error: %w", err)) + } + defer func() { _ = db.Close(ctx) }() + + err = db.Coordination().CreateNode(ctx, path, coordination.NodeConfig{ + Path: "", + SelfCheckPeriodMillis: 1000, + SessionGracePeriodMillis: 1000, + ReadConsistencyMode: coordination.ConsistencyModeStrict, + AttachConsistencyMode: coordination.ConsistencyModeStrict, + RatelimiterCountersMode: coordination.RatelimiterCountersModeDetailed, + }) + if err != nil { + fmt.Printf("failed to create coordination node: %v\n", err) + + return + } + + tasks := make([]string, taskCount) + for i := 0; i < taskCount; i++ { + tasks[i] = fmt.Sprintf("%s%d", semaphorePrefix, i) + } + rand.Shuffle(taskCount, func(i int, j int) { + tasks[i], tasks[j] = tasks[j], tasks[i] + }) + + fmt.Println("starting tasks") + for { + session, err := db.Coordination().Session(ctx, path) + if err != nil { + fmt.Println("failed to open session", err) + + return + } + + semaphoreCtx, semaphoreCancel := context.WithCancel(ctx) + wg := sync.WaitGroup{} + wg.Add(taskCount) + leaseChan := make(chan *LeaseInfo) + sem := semaphore.NewWeighted(int64(capacity)) + + for _, name := range tasks { + go awaitSemaphore(semaphoreCtx, &wg, session, name, leaseChan, sem, semaphoreCancel) + } + + tasksStarted := 0 + loop: + for { + select { + case <-semaphoreCtx.Done(): + break loop + case lease := <-leaseChan: + go doWork(lease.lease, lease.semaphoreName) + tasksStarted++ + if tasksStarted == capacity { + break loop + } + case <-ctx.Done(): + fmt.Println("exiting") + + return + } + } + + fmt.Println("all workers are started") + + semaphoreCancel() + wg.Wait() + + select { + case <-ctx.Done(): + fmt.Println("exiting") + + return + case <-session.Context().Done(): + } + } +} + +type LeaseInfo struct { + lease coordination.Lease + semaphoreName string +} + +func awaitSemaphore( + ctx context.Context, + done *sync.WaitGroup, + session coordination.Session, + semaphoreName string, + leaseChan chan *LeaseInfo, + sem *semaphore.Weighted, + cancel context.CancelFunc, +) { + defer done.Done() + + lease, err := session.AcquireSemaphore( + ctx, + semaphoreName, + coordination.Exclusive, + options.WithEphemeral(true), + ) + if err != nil { + if ctx.Err() != nil { + return + } + + fmt.Println("failed to acquire semaphore", err) + cancel() + + return + } + + if sem.TryAcquire(1) { + leaseChan <- &LeaseInfo{lease: lease, semaphoreName: semaphoreName} + } else { + err := lease.Release() + if err != nil { + fmt.Println("failed to release semaphore", err) + cancel() + } + } +} + +func doWork( + lease coordination.Lease, + name string, +) { + fmt.Printf("worker %s: starting\n", name) + + for { + select { + case <-lease.Context().Done(): + fmt.Printf("worker %s: done\n", name) + err := lease.Release() + if err != nil { + fmt.Println("failed to release semaphore", err) + } + + return + case <-time.After(time.Second): + } + + fmt.Printf("worker %s: in progress\n", name) + } +} diff --git a/examples/go.mod b/examples/go.mod index 4b8c940bb..8e4d692d8 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -12,6 +12,7 @@ require ( github.com/ydb-platform/ydb-go-sdk-prometheus/v2 v2.0.1 github.com/ydb-platform/ydb-go-sdk/v3 v3.54.0 github.com/ydb-platform/ydb-go-yc v0.10.1 + golang.org/x/sync v0.3.0 google.golang.org/genproto v0.0.0-20230526161137-0005af68ea54 gorm.io/driver/postgres v1.5.0 gorm.io/driver/sqlite v1.5.0 @@ -54,8 +55,7 @@ require ( github.com/ydb-platform/ydb-go-yc-metadata v0.5.4 // indirect golang.org/x/crypto v0.17.0 // indirect golang.org/x/mod v0.11.0 // indirect - golang.org/x/net v0.15.0 // indirect - golang.org/x/sync v0.3.0 // indirect + golang.org/x/net v0.17.0 // indirect golang.org/x/sys v0.15.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/tools v0.7.0 // indirect diff --git a/examples/go.sum b/examples/go.sum index 79278f3b3..14380c891 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -1269,7 +1269,7 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1401,8 +1401,8 @@ golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= -golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1551,7 +1551,7 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= @@ -1565,7 +1565,7 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/examples/topic/topicreader/topicreader_advanced.go b/examples/topic/topicreader/topicreader_advanced.go index 962f4700b..256d38494 100644 --- a/examples/topic/topicreader/topicreader_advanced.go +++ b/examples/topic/topicreader/topicreader_advanced.go @@ -50,14 +50,14 @@ func UnmarshalMessageContentToOwnType(ctx context.Context, reader *topicreader.R // ProcessMessagesWithSyncCommit example about guarantee wait for commit accepted by server func ProcessMessagesWithSyncCommit(ctx context.Context, db *ydb.Driver) { reader, _ := db.Topic().StartReader("consumer", nil, - topicoptions.WithCommitMode(topicoptions.CommitModeSync), + topicoptions.WithReaderCommitMode(topicoptions.CommitModeSync), ) defer func() { _ = reader.Close(ctx) }() for { - batch, _ := reader.ReadMessageBatch(ctx) + batch, _ := reader.ReadMessagesBatch(ctx) processBatch(batch.Context(), batch) _ = reader.Commit(ctx, batch) // will wait response about commit from server } @@ -67,7 +67,7 @@ func ProcessMessagesWithSyncCommit(ctx context.Context, db *ydb.Driver) { // commit messages to YDB func OwnReadProgressStorage(ctx context.Context, db *ydb.Driver) { reader, _ := db.Topic().StartReader("consumer", topicoptions.ReadTopic("asd"), - topicoptions.WithGetPartitionStartOffset( + topicoptions.WithReaderGetPartitionStartOffset( func( ctx context.Context, req topicoptions.GetPartitionStartOffsetRequest, @@ -84,7 +84,7 @@ func OwnReadProgressStorage(ctx context.Context, db *ydb.Driver) { ) for { - batch, _ := reader.ReadMessageBatch(ctx) + batch, _ := reader.ReadMessagesBatch(ctx) processBatch(batch.Context(), batch) _ = externalSystemCommit( diff --git a/examples/topic/topicreader/topicreader_simple.go b/examples/topic/topicreader/topicreader_simple.go index 6fb568037..793485fbb 100644 --- a/examples/topic/topicreader/topicreader_simple.go +++ b/examples/topic/topicreader/topicreader_simple.go @@ -24,7 +24,7 @@ func PrintMessageContent(ctx context.Context, reader *topicreader.Reader) { // ReadMessagesByBatch it is recommended way for process messages func ReadMessagesByBatch(ctx context.Context, reader *topicreader.Reader) { for { - batch, _ := reader.ReadMessageBatch(ctx) + batch, _ := reader.ReadMessagesBatch(ctx) processBatch(batch.Context(), batch) _ = reader.Commit(batch.Context(), batch) } diff --git a/examples/topic/topicreader/topicreader_trace.go b/examples/topic/topicreader/topicreader_trace.go index 28ab11427..b31390fb2 100644 --- a/examples/topic/topicreader/topicreader_trace.go +++ b/examples/topic/topicreader/topicreader_trace.go @@ -71,7 +71,7 @@ func ExplicitPartitionStartStopHandler(ctx context.Context, db *ydb.Driver) { }() for { - batch, _ := reader.ReadMessageBatch(readContext) + batch, _ := reader.ReadMessagesBatch(readContext) processBatch(batch.Context(), batch) _ = externalSystemCommit( @@ -129,8 +129,7 @@ func PartitionStartStopHandlerAndOwnReadProgressStorage(ctx context.Context, db } r, _ := db.Topic().StartReader("consumer", topicoptions.ReadTopic("asd"), - - topicoptions.WithGetPartitionStartOffset(readStartPosition), + topicoptions.WithReaderGetPartitionStartOffset(readStartPosition), topicoptions.WithReaderTrace( trace.Topic{ OnReaderPartitionReadStartResponse: onPartitionStart, @@ -144,7 +143,7 @@ func PartitionStartStopHandlerAndOwnReadProgressStorage(ctx context.Context, db }() for { - batch, _ := r.ReadMessageBatch(readContext) + batch, _ := r.ReadMessagesBatch(readContext) processBatch(batch.Context(), batch) _ = externalSystemCommit(batch.Context(), batch.Topic(), batch.PartitionID(), getEndOffset(batch)) diff --git a/examples/topic/topicwriter/topicwriter.go b/examples/topic/topicwriter/topicwriter.go index a378f8a90..354d3f454 100644 --- a/examples/topic/topicwriter/topicwriter.go +++ b/examples/topic/topicwriter/topicwriter.go @@ -17,13 +17,13 @@ func ConnectSimple(ctx context.Context, db *ydb.Driver) *topicwriter.Writer { } func ConnectWithSyncWrite(ctx context.Context, db *ydb.Driver) *topicwriter.Writer { - writer, _ := db.Topic().StartWriter("topicName", topicoptions.WithSyncWrite(true)) + writer, _ := db.Topic().StartWriter("topicName", topicoptions.WithWriterWaitServerAck(true)) return writer } func ConnectSelectCodec(ctx context.Context, db *ydb.Driver) *topicwriter.Writer { - writer, _ := db.Topic().StartWriter("topicName", topicoptions.WithCodec(topictypes.CodecGzip)) + writer, _ := db.Topic().StartWriter("topicName", topicoptions.WithWriterCodec(topictypes.CodecGzip)) return writer } diff --git a/examples/ttl/series.go b/examples/ttl/series.go index a6e4753c3..67f7759dc 100644 --- a/examples/ttl/series.go +++ b/examples/ttl/series.go @@ -268,9 +268,10 @@ func createTables(ctx context.Context, c table.Client, prefix string) (err error } for i := 0; i < expirationQueueCount; i++ { + tableName := path.Join(prefix, fmt.Sprintf("expiration_queue_%v", i)) err = c.Do(ctx, func(ctx context.Context, s table.Session) error { - return s.CreateTable(ctx, path.Join(prefix, fmt.Sprintf("expiration_queue_%v", i)), + return s.CreateTable(ctx, tableName, options.WithColumn("doc_id", types.Optional(types.TypeUint64)), options.WithColumn("ts", types.Optional(types.TypeUint64)), options.WithPrimaryKeyColumn("ts", "doc_id"), diff --git a/go.mod b/go.mod index 339491ffc..e531a3b47 100644 --- a/go.mod +++ b/go.mod @@ -24,8 +24,8 @@ require ( github.com/davecgh/go-spew v1.1.0 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - golang.org/x/net v0.15.0 // indirect - golang.org/x/sys v0.12.0 // indirect + golang.org/x/net v0.17.0 // indirect + golang.org/x/sys v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 // indirect gopkg.in/yaml.v3 v3.0.0 // indirect diff --git a/go.sum b/go.sum index 4cfd88ee1..5af6dc2e2 100644 --- a/go.sum +++ b/go.sum @@ -84,8 +84,8 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= -golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -100,8 +100,8 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= diff --git a/internal/allocator/allocator.go b/internal/allocator/allocator.go index 378e8d244..981780697 100644 --- a/internal/allocator/allocator.go +++ b/internal/allocator/allocator.go @@ -1,6 +1,7 @@ package allocator import ( + "fmt" "sync" "github.com/ydb-platform/ydb-go-genproto/protos/Ydb" @@ -395,7 +396,7 @@ type structAllocator struct { func (a *structAllocator) Struct() (v *Ydb.StructType) { v = structPool.Get() if cap(v.GetMembers()) <= 0 { - v.Members = make([]*Ydb.StructMember, 0, 10) + v.Members = make([]*Ydb.StructMember, 0, 10) //nolint:gomnd } a.allocations = append(a.allocations, v) @@ -1107,7 +1108,12 @@ func (p *Pool[T]) Get() *T { v = &zero } - return v.(*T) + val, ok := v.(*T) + if !ok { + panic(fmt.Sprintf("assertion failed: expected type *T, got %T", v)) + } + + return val } func (p *Pool[T]) Put(t *T) { diff --git a/internal/backoff/backoff.go b/internal/backoff/backoff.go index c3a6902fe..d4b9cceeb 100644 --- a/internal/backoff/backoff.go +++ b/internal/backoff/backoff.go @@ -22,11 +22,11 @@ const ( var ( Fast = New( WithSlotDuration(fastSlot), - WithCeiling(6), + WithCeiling(6), //nolint:gomnd ) Slow = New( WithSlotDuration(slowSlot), - WithCeiling(6), + WithCeiling(6), //nolint:gomnd ) ) diff --git a/internal/balancer/balancer.go b/internal/balancer/balancer.go index 9bd58b451..f69ec11e2 100644 --- a/internal/balancer/balancer.go +++ b/internal/balancer/balancer.go @@ -97,7 +97,8 @@ func (b *Balancer) clusterDiscoveryAttempt(ctx context.Context) (err error) { address = "ydb:///" + b.driverConfig.Endpoint() onDone = trace.DriverOnBalancerClusterDiscoveryAttempt( b.driverConfig.Trace(), &ctx, - stack.FunctionID(""), + stack.FunctionID( + "github.com/ydb-platform/ydb-go-sdk/3/internal/balancer.(*Balancer).clusterDiscoveryAttempt"), address, ) endpoints []endpoint.Endpoint @@ -173,7 +174,8 @@ func (b *Balancer) applyDiscoveredEndpoints(ctx context.Context, endpoints []end var ( onDone = trace.DriverOnBalancerUpdate( b.driverConfig.Trace(), &ctx, - stack.FunctionID(""), + stack.FunctionID( + "github.com/ydb-platform/ydb-go-sdk/3/internal/balancer.(*Balancer).applyDiscoveredEndpoints"), b.config.DetectLocalDC, ) previousConns []conn.Conn @@ -211,7 +213,7 @@ func (b *Balancer) applyDiscoveredEndpoints(ctx context.Context, endpoints []end func (b *Balancer) Close(ctx context.Context) (err error) { onDone := trace.DriverOnBalancerClose( b.driverConfig.Trace(), &ctx, - stack.FunctionID(""), + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/balancer.(*Balancer).Close"), ) defer func() { onDone(err) @@ -237,7 +239,7 @@ func New( var ( onDone = trace.DriverOnBalancerInit( driverConfig.Trace(), &ctx, - stack.FunctionID(""), + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/balancer.New"), driverConfig.Balancer().String(), ) discoveryConfig = discoveryConfig.New(append(opts, @@ -280,7 +282,7 @@ func New( } // run background discovering if d := discoveryConfig.Interval(); d > 0 { - b.discoveryRepeater = repeater.New(xcontext.WithoutDeadline(ctx), + b.discoveryRepeater = repeater.New(xcontext.ValueOnly(ctx), d, b.clusterDiscoveryAttempt, repeater.WithName("discovery"), repeater.WithTrace(b.driverConfig.Trace()), @@ -371,7 +373,7 @@ func (b *Balancer) connections() *connectionsState { func (b *Balancer) getConn(ctx context.Context) (c conn.Conn, err error) { onDone := trace.DriverOnBalancerChooseEndpoint( b.driverConfig.Trace(), &ctx, - stack.FunctionID(""), + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/balancer.(*Balancer).getConn"), ) defer func() { if err == nil { diff --git a/internal/balancer/local_dc.go b/internal/balancer/local_dc.go index e764af7d8..b1ee2e086 100644 --- a/internal/balancer/local_dc.go +++ b/internal/balancer/local_dc.go @@ -69,7 +69,7 @@ func detectFastestEndpoint(ctx context.Context, endpoints []endpoint.Endpoint) ( var lastErr error // common is 2 ip address for every fqdn: ipv4 + ipv6 - initialAddressToEndpointCapacity := len(endpoints) * 2 + initialAddressToEndpointCapacity := len(endpoints) * 2 //nolint:gomnd addressToEndpoint := make(map[string]endpoint.Endpoint, initialAddressToEndpointCapacity) for _, ep := range endpoints { host, port, err := extractHostPort(ep.Address()) diff --git a/internal/bind/numeric_args.go b/internal/bind/numeric_args.go index 89c9c4ecc..6cb948906 100644 --- a/internal/bind/numeric_args.go +++ b/internal/bind/numeric_args.go @@ -5,6 +5,7 @@ import ( "strconv" "unicode/utf8" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/params" "github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors" "github.com/ydb-platform/ydb-go-sdk/v3/internal/xstring" "github.com/ydb-platform/ydb-go-sdk/v3/table" @@ -16,9 +17,7 @@ func (m NumericArgs) blockID() blockID { return blockYQL } -func (m NumericArgs) RewriteQuery(sql string, args ...interface{}) ( - yql string, newArgs []interface{}, err error, -) { +func (m NumericArgs) RewriteQuery(sql string, args ...interface{}) (yql string, newArgs []interface{}, err error) { l := &sqlLexer{ src: sql, stateFn: numericArgsStateFn, @@ -29,14 +28,18 @@ func (m NumericArgs) RewriteQuery(sql string, args ...interface{}) ( l.stateFn = l.stateFn(l) } - var ( - buffer = xstring.Buffer() - param table.ParameterOption - ) + buffer := xstring.Buffer() defer buffer.Free() if len(args) > 0 { - newArgs = make([]interface{}, len(args)) + parameters, err := parsePositionalParameters(args) + if err != nil { + return "", nil, err + } + newArgs = make([]interface{}, len(parameters)) + for i, param := range parameters { + newArgs[i] = param + } } for _, p := range l.parts { @@ -49,7 +52,7 @@ func (m NumericArgs) RewriteQuery(sql string, args ...interface{}) ( } if int(p) > len(args) { return "", nil, xerrors.WithStackTrace( - fmt.Errorf("%w: $p%d, len(args) = %d", ErrInconsistentArgs, p, len(args)), + fmt.Errorf("%w: $%d, len(args) = %d", ErrInconsistentArgs, p, len(args)), ) } paramName := "$p" + strconv.Itoa(int(p-1)) //nolint:goconst @@ -61,26 +64,21 @@ func (m NumericArgs) RewriteQuery(sql string, args ...interface{}) ( newArgs[p-1] = param buffer.WriteString(param.Name()) } else { - buffer.WriteString(newArgs[p-1].(table.ParameterOption).Name()) + val, ok := newArgs[p-1].(table.ParameterOption) + if !ok { + panic(fmt.Sprintf("unsupported type conversion from %T to table.ParameterOption", val)) + } + buffer.WriteString(val.Name()) } } } - for i, p := range newArgs { - if p == nil { - return "", nil, xerrors.WithStackTrace( - fmt.Errorf("%w: $p%d, len(args) = %d", ErrInconsistentArgs, i+1, len(args)), - ) - } - } - + yql = buffer.String() if len(newArgs) > 0 { - const prefix = "-- origin query with numeric args replacement\n" - - return prefix + buffer.String(), newArgs, nil + yql = "-- origin query with numeric args replacement\n" + yql } - return buffer.String(), newArgs, nil + return yql, newArgs, nil } func numericArgsStateFn(l *sqlLexer) stateFn { @@ -130,6 +128,20 @@ func numericArgsStateFn(l *sqlLexer) stateFn { } } +func parsePositionalParameters(args []interface{}) ([]*params.Parameter, error) { + newArgs := make([]*params.Parameter, len(args)) + for i, arg := range args { + paramName := fmt.Sprintf("$p%d", i) + param, err := toYdbParam(paramName, arg) + if err != nil { + return nil, err + } + newArgs[i] = param + } + + return newArgs, nil +} + func numericArgState(l *sqlLexer) stateFn { numbers := "" defer func() { diff --git a/internal/bind/params.go b/internal/bind/params.go index d0e6a82da..95653b996 100644 --- a/internal/bind/params.go +++ b/internal/bind/params.go @@ -21,7 +21,7 @@ var ( errMultipleQueryParameters = errors.New("only one query arg *table.QueryParameters allowed") ) -//nolint:gocyclo +//nolint:gocyclo,funlen func toValue(v interface{}) (_ types.Value, err error) { if valuer, ok := v.(driver.Valuer); ok { v, err = valuer.Value() @@ -174,58 +174,36 @@ func toYdbParam(name string, value interface{}) (*params.Parameter, error) { return params.Named(name, v), nil } -func Params(args ...interface{}) (parameters []*params.Parameter, _ error) { - parameters = make([]*params.Parameter, 0, len(args)) +func Params(args ...interface{}) ([]*params.Parameter, error) { + parameters := make([]*params.Parameter, 0, len(args)) for i, arg := range args { + var newParam *params.Parameter + var newParams []*params.Parameter + var err error switch x := arg.(type) { case driver.NamedValue: - if x.Name == "" { - switch xx := x.Value.(type) { - case *params.Parameters: - if len(args) > 1 { - return nil, xerrors.WithStackTrace(errMultipleQueryParameters) - } - parameters = *xx - case *params.Parameter: - parameters = append(parameters, xx) - default: - x.Name = fmt.Sprintf("$p%d", i) - param, err := toYdbParam(x.Name, x.Value) - if err != nil { - return nil, xerrors.WithStackTrace(err) - } - parameters = append(parameters, param) - } - } else { - param, err := toYdbParam(x.Name, x.Value) - if err != nil { - return nil, xerrors.WithStackTrace(err) - } - parameters = append(parameters, param) - } + newParams, err = paramHandleNamedValue(x, i, len(args)) case sql.NamedArg: if x.Name == "" { return nil, xerrors.WithStackTrace(errUnnamedParam) } - param, err := toYdbParam(x.Name, x.Value) - if err != nil { - return nil, xerrors.WithStackTrace(err) - } - parameters = append(parameters, param) + newParam, err = toYdbParam(x.Name, x.Value) + newParams = append(newParams, newParam) case *params.Parameters: if len(args) > 1 { return nil, xerrors.WithStackTrace(errMultipleQueryParameters) } parameters = *x case *params.Parameter: - parameters = append(parameters, x) + newParams = append(newParams, x) default: - param, err := toYdbParam(fmt.Sprintf("$p%d", i), x) - if err != nil { - return nil, xerrors.WithStackTrace(err) - } - parameters = append(parameters, param) + newParam, err = toYdbParam(fmt.Sprintf("$p%d", i), x) + newParams = append(newParams, newParam) + } + if err != nil { + return nil, xerrors.WithStackTrace(err) } + parameters = append(parameters, newParams...) } sort.Slice(parameters, func(i, j int) bool { return parameters[i].Name() < parameters[j].Name() @@ -233,3 +211,33 @@ func Params(args ...interface{}) (parameters []*params.Parameter, _ error) { return parameters, nil } + +func paramHandleNamedValue(arg driver.NamedValue, paramNumber, argsLen int) ([]*params.Parameter, error) { + if arg.Name == "" { + switch x := arg.Value.(type) { + case *params.Parameters: + if argsLen > 1 { + return nil, xerrors.WithStackTrace(errMultipleQueryParameters) + } + + return *x, nil + case *params.Parameter: + return []*params.Parameter{x}, nil + default: + arg.Name = fmt.Sprintf("$p%d", paramNumber) + param, err := toYdbParam(arg.Name, arg.Value) + if err != nil { + return nil, xerrors.WithStackTrace(err) + } + + return []*params.Parameter{param}, nil + } + } else { + param, err := toYdbParam(arg.Name, arg.Value) + if err != nil { + return nil, xerrors.WithStackTrace(err) + } + + return []*params.Parameter{param}, nil + } +} diff --git a/internal/cmd/gstack/main.go b/internal/cmd/gstack/main.go new file mode 100644 index 000000000..10f3ed061 --- /dev/null +++ b/internal/cmd/gstack/main.go @@ -0,0 +1,228 @@ +package main + +import ( + "bytes" + "flag" + "fmt" + "go/ast" + "go/parser" + "go/token" + "io/fs" + "os" + "path/filepath" + + "github.com/ydb-platform/ydb-go-sdk/v3/internal/cmd/gstack/utils" +) + +func usage() { + fmt.Fprintf(os.Stderr, "usage: gstack [path]\n") + flag.PrintDefaults() +} + +func getCallExpressionsFromExpr(expr ast.Expr) (listOfCalls []*ast.CallExpr) { + switch expr := expr.(type) { + case *ast.SelectorExpr: + listOfCalls = getCallExpressionsFromExpr(expr.X) + case *ast.IndexExpr: + listOfCalls = getCallExpressionsFromExpr(expr.X) + case *ast.StarExpr: + listOfCalls = getCallExpressionsFromExpr(expr.X) + case *ast.BinaryExpr: + listOfCalls = getCallExpressionsFromExpr(expr.X) + listOfCalls = append(listOfCalls, getCallExpressionsFromExpr(expr.Y)...) + case *ast.CallExpr: + listOfCalls = append(listOfCalls, expr) + listOfCalls = append(listOfCalls, getCallExpressionsFromExpr(expr.Fun)...) + for _, arg := range expr.Args { + listOfCalls = append(listOfCalls, getCallExpressionsFromExpr(arg)...) + } + case *ast.CompositeLit: + for _, elt := range expr.Elts { + listOfCalls = append(listOfCalls, getCallExpressionsFromExpr(elt)...) + } + case *ast.UnaryExpr: + listOfCalls = append(listOfCalls, getCallExpressionsFromExpr(expr.X)...) + case *ast.KeyValueExpr: + listOfCalls = append(listOfCalls, getCallExpressionsFromExpr(expr.Value)...) + case *ast.FuncLit: + listOfCalls = append(listOfCalls, getListOfCallExpressionsFromBlockStmt(expr.Body)...) + } + + return listOfCalls +} + +func getExprFromDeclStmt(statement *ast.DeclStmt) (listOfExpressions []ast.Expr) { + decl, ok := statement.Decl.(*ast.GenDecl) + if !ok { + return listOfExpressions + } + for _, spec := range decl.Specs { + if spec, ok := spec.(*ast.ValueSpec); ok { + listOfExpressions = append(listOfExpressions, spec.Values...) + } + } + + return listOfExpressions +} + +func getCallExpressionsFromStmt(statement ast.Stmt) (listOfCallExpressions []*ast.CallExpr) { + var body *ast.BlockStmt + var listOfExpressions []ast.Expr + switch stmt := statement.(type) { + case *ast.IfStmt: + body = stmt.Body + case *ast.SwitchStmt: + body = stmt.Body + case *ast.TypeSwitchStmt: + body = stmt.Body + case *ast.SelectStmt: + body = stmt.Body + case *ast.ForStmt: + body = stmt.Body + case *ast.GoStmt: + if fun, ok := stmt.Call.Fun.(*ast.FuncLit); ok { + listOfCallExpressions = append(listOfCallExpressions, getListOfCallExpressionsFromBlockStmt(fun.Body)...) + } else { + listOfCallExpressions = append(listOfCallExpressions, stmt.Call) + } + case *ast.RangeStmt: + body = stmt.Body + case *ast.DeclStmt: + listOfExpressions = append(listOfExpressions, getExprFromDeclStmt(stmt)...) + for _, expr := range listOfExpressions { + listOfCallExpressions = append(listOfCallExpressions, getCallExpressionsFromExpr(expr)...) + } + case *ast.CommClause: + stmts := stmt.Body + for _, stmt := range stmts { + listOfCallExpressions = append(listOfCallExpressions, getCallExpressionsFromStmt(stmt)...) + } + case *ast.ExprStmt: + listOfCallExpressions = append(listOfCallExpressions, getCallExpressionsFromExpr(stmt.X)...) + case *ast.AssignStmt: + for _, rh := range stmt.Rhs { + listOfCallExpressions = append(listOfCallExpressions, getCallExpressionsFromExpr(rh)...) + } + case *ast.ReturnStmt: + for _, result := range stmt.Results { + listOfCallExpressions = append(listOfCallExpressions, getCallExpressionsFromExpr(result)...) + } + } + if body != nil { + listOfCallExpressions = append( + listOfCallExpressions, + getListOfCallExpressionsFromBlockStmt(body)..., + ) + } + + return listOfCallExpressions +} + +func getListOfCallExpressionsFromBlockStmt(block *ast.BlockStmt) (listOfCallExpressions []*ast.CallExpr) { + for _, statement := range block.List { + listOfCallExpressions = append(listOfCallExpressions, getCallExpressionsFromStmt(statement)...) + } + + return listOfCallExpressions +} + +func format(src []byte, path string, fset *token.FileSet, file *ast.File) ([]byte, error) { + var listOfArgs []utils.FunctionIDArg + for _, f := range file.Decls { + var listOfCalls []*ast.CallExpr + fn, ok := f.(*ast.FuncDecl) + if !ok { + continue + } + listOfCalls = getListOfCallExpressionsFromBlockStmt(fn.Body) + for _, call := range listOfCalls { + if function, ok := call.Fun.(*ast.SelectorExpr); ok && function.Sel.Name == "FunctionID" { + pack, ok := function.X.(*ast.Ident) + if !ok { + continue + } + if pack.Name == "stack" && len(call.Args) == 1 { + listOfArgs = append(listOfArgs, utils.FunctionIDArg{ + FuncDecl: fn, + ArgPos: call.Args[0].Pos(), + ArgEnd: call.Args[0].End(), + }) + } + } + } + } + if len(listOfArgs) != 0 { + fixed, err := utils.FixSource(fset, path, src, listOfArgs) + if err != nil { + return nil, err + } + + return fixed, nil + } + + return src, nil +} + +func processFile(src []byte, path string, fset *token.FileSet, file *ast.File, info os.FileInfo) error { + formatted, err := format(src, path, fset, file) + if err != nil { + return err + } + if !bytes.Equal(src, formatted) { + err = utils.WriteFile(path, formatted, info.Mode().Perm()) + if err != nil { + return err + } + } + + return nil +} + +func main() { + flag.Usage = usage + flag.Parse() + args := flag.Args() + + if len(args) != 1 { + flag.Usage() + + return + } + _, err := os.Stat(args[0]) + if err != nil { + panic(err) + } + + fileSystem := os.DirFS(args[0]) + + err = fs.WalkDir(fileSystem, ".", func(path string, d fs.DirEntry, err error) error { + fset := token.NewFileSet() + if err != nil { + return err + } + if d.IsDir() { + return nil + } + if filepath.Ext(path) == ".go" { + info, err := os.Stat(path) + if err != nil { + return err + } + src, err := utils.ReadFile(path, info) + if err != nil { + return err + } + file, err := parser.ParseFile(fset, path, nil, 0) + if err != nil { + return err + } + + return processFile(src, path, fset, file, info) + } + + return nil + }) + if err != nil { + panic(err) + } +} diff --git a/internal/cmd/gstack/utils/utils.go b/internal/cmd/gstack/utils/utils.go new file mode 100644 index 000000000..19b35512d --- /dev/null +++ b/internal/cmd/gstack/utils/utils.go @@ -0,0 +1,135 @@ +package utils + +import ( + "fmt" + "go/ast" + "go/parser" + "go/token" + "io" + "io/fs" + "os" + "path/filepath" + + "github.com/ydb-platform/ydb-go-sdk/v3/internal/version" +) + +type FunctionIDArg struct { + FuncDecl *ast.FuncDecl + ArgPos token.Pos + ArgEnd token.Pos +} + +func ReadFile(filename string, info fs.FileInfo) ([]byte, error) { + f, err := os.Open(filename) + if err != nil { + return nil, err + } + defer f.Close() + size := int(info.Size()) + src := make([]byte, size) + n, err := io.ReadFull(f, src) + if err != nil { + return nil, err + } + if n < size { + return nil, fmt.Errorf("error: size of %q changed during reading (from %d to %d bytes)", filename, size, n) + } else if n > size { + return nil, fmt.Errorf("error: size of %q changed during reading (from %d to >=%d bytes)", filename, size, len(src)) + } + + return src, nil +} + +func FixSource(fset *token.FileSet, path string, src []byte, listOfArgs []FunctionIDArg) ([]byte, error) { + var fixed []byte + var previousArgEnd int + for _, arg := range listOfArgs { + argPosOffset := fset.Position(arg.ArgPos).Offset + argEndOffset := fset.Position(arg.ArgEnd).Offset + argument, err := makeCall(fset, path, arg) + if err != nil { + return nil, err + } + fixed = append(fixed, src[previousArgEnd:argPosOffset]...) + fixed = append(fixed, fmt.Sprintf("%q", argument)...) + previousArgEnd = argEndOffset + } + fixed = append(fixed, src[previousArgEnd:]...) + + return fixed, nil +} + +func WriteFile(filename string, formatted []byte, perm fs.FileMode) error { + fout, err := os.OpenFile(filename, os.O_WRONLY|os.O_TRUNC, perm) + if err != nil { + return err + } + + defer fout.Close() + + _, err = fout.Write(formatted) + if err != nil { + return err + } + + return nil +} + +func makeCall(fset *token.FileSet, path string, arg FunctionIDArg) (string, error) { + basePath := filepath.Join("github.com", "ydb-platform", version.Prefix, version.Major, "") + packageName, err := getPackageName(fset, arg) + if err != nil { + return "", err + } + filePath := filepath.Dir(filepath.Dir(path)) + funcName, err := getFuncName(arg.FuncDecl) + if err != nil { + return "", err + } + + return filepath.Join(basePath, filePath, packageName) + "." + funcName, nil +} + +func getFuncName(funcDecl *ast.FuncDecl) (string, error) { + if funcDecl.Recv != nil { + recvType := funcDecl.Recv.List[0].Type + prefix, err := getIdentNameFromExpr(recvType) + if err != nil { + return "", err + } + + return prefix + "." + funcDecl.Name.Name, nil + } + + return funcDecl.Name.Name, nil +} + +func getIdentNameFromExpr(expr ast.Expr) (string, error) { + switch expr := expr.(type) { + case *ast.Ident: + return expr.Name, nil + case *ast.StarExpr: + prefix, err := getIdentNameFromExpr(expr.X) + if err != nil { + return "", err + } + + return "(*" + prefix + ")", nil + case *ast.IndexExpr: + return getIdentNameFromExpr(expr.X) + case *ast.IndexListExpr: + return getIdentNameFromExpr(expr.X) + default: + return "", fmt.Errorf("error during getting ident from expr") + } +} + +func getPackageName(fset *token.FileSet, arg FunctionIDArg) (string, error) { + file := fset.File(arg.ArgPos) + parsedFile, err := parser.ParseFile(fset, file.Name(), nil, parser.PackageClauseOnly) + if err != nil { + return "", fmt.Errorf("error during get package name function") + } + + return parsedFile.Name.Name, nil +} diff --git a/internal/cmd/gtrace/main.go b/internal/cmd/gtrace/main.go index 99f2bf98a..84a16add4 100644 --- a/internal/cmd/gtrace/main.go +++ b/internal/cmd/gtrace/main.go @@ -67,7 +67,7 @@ func main() { f, err = os.OpenFile( filepath.Join(workDir, filepath.Clean(name)), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, - 0o600, + 0o600, //nolint:gomnd ) if err != nil { log.Fatal(err) diff --git a/internal/cmd/gtrace/writer.go b/internal/cmd/gtrace/writer.go index fcc87a43e..3616f425f 100644 --- a/internal/cmd/gtrace/writer.go +++ b/internal/cmd/gtrace/writer.go @@ -86,7 +86,10 @@ func (w *Writer) init() { } func (w *Writer) mustDeclare(name string) { - s := w.scope.Back().Value.(*scope) + s, ok := w.scope.Back().Value.(*scope) + if !ok { + panic(fmt.Sprintf("unsupported type conversion from %T to w.scope.Back()", s)) + } if !s.set(name) { where := s.where(name) panic(fmt.Sprintf( @@ -100,7 +103,10 @@ func (w *Writer) declare(name string) string { if isPredeclared(name) { name = firstChar(name) } - s := w.scope.Back().Value.(*scope) + s, ok := w.scope.Back().Value.(*scope) + if !ok { + panic(fmt.Sprintf("unsupported type conversion from %T to *scope", s)) + } for i := 0; ; i++ { v := name if i > 0 { @@ -127,7 +133,10 @@ func (w *Writer) isGlobalScope() bool { } func (w *Writer) capture(vars ...string) { - s := w.scope.Back().Value.(*scope) + s, ok := w.scope.Back().Value.(*scope) + if !ok { + panic(fmt.Sprintf("unsupported type conversion from %T to *scope", s)) + } for _, v := range vars { if !s.set(v) { panic(fmt.Sprintf("can't capture variable %q", v)) @@ -324,6 +333,7 @@ func (w *Writer) compose(trace *Trace) { w.line(`// Compose returns a new `, trace.Name, ` which has functional fields composed both from `, t, ` and `, x, `.`, ) + w.line(`// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals`) w.code(`func (`, t, ` *`, trace.Name, `) Compose(`, x, ` *`, trace.Name, `, opts ...`+trace.Name+`ComposeOption) `) w.line(`*`, trace.Name, ` {`) w.block(func() { @@ -405,9 +415,9 @@ func (w *Writer) composeHookCall(fn *Func, h1, h2 string) { w.line("if " + h + " != nil {") w.block(func() { if fn.HasResult() { - w.code(rs[i], ` = `) + w.code(rs[i], ` = `) //nolint:scopelint } - w.code(h) + w.code(h) //nolint:scopelint w.call(args) }) w.line("}") @@ -440,11 +450,13 @@ func (w *Writer) options(trace *Trace) { }) w.newScope(func() { w.line(fmt.Sprintf(`// %sOption specified %s compose option`, trace.Name, trace.Name)) + w.line(`// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals`) w.line(fmt.Sprintf(`type %sComposeOption func(o *%sComposeOptions)`, trace.Name, unexported(trace.Name))) _ = w.bw.WriteByte('\n') }) w.newScope(func() { w.line(fmt.Sprintf(`// With%sPanicCallback specified behavior on panic`, trace.Name)) + w.line(`// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals`) w.line(fmt.Sprintf(`func With%sPanicCallback(cb func(e interface{})) %sComposeOption {`, trace.Name, trace.Name)) w.block(func() { w.line(fmt.Sprintf(`return func(o *%sComposeOptions) {`, unexported(trace.Name))) @@ -641,6 +653,7 @@ func (w *Writer) hookShortcut(trace *Trace, hook Hook) { w.newScope(func() { t := w.declare("t") + w.line(`// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals`) w.code(`func `, name) w.code(`(`) var ctx string @@ -1028,7 +1041,7 @@ func (s *scope) set(v string) bool { if _, has := s.vars[v]; has { return false } - _, file, line, _ := runtime.Caller(2) + _, file, line, _ := runtime.Caller(2) //nolint:gomnd s.vars[v] = decl{ where: fmt.Sprintf("%s:%d", file, line), } diff --git a/internal/conn/config.go b/internal/conn/config.go index 27a0603a1..df82f3a85 100644 --- a/internal/conn/config.go +++ b/internal/conn/config.go @@ -10,6 +10,7 @@ import ( type Config interface { DialTimeout() time.Duration + ConnectionTTL() time.Duration Trace() *trace.Driver GrpcDialOptions() []grpc.DialOption } diff --git a/internal/conn/conn.go b/internal/conn/conn.go index a754923bd..2fd03991e 100644 --- a/internal/conn/conn.go +++ b/internal/conn/conn.go @@ -19,6 +19,7 @@ import ( "github.com/ydb-platform/ydb-go-sdk/v3/internal/stack" "github.com/ydb-platform/ydb-go-sdk/v3/internal/xcontext" "github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/xsync" "github.com/ydb-platform/ydb-go-sdk/v3/trace" ) @@ -55,7 +56,8 @@ type conn struct { endpoint endpoint.Endpoint // ro access closed bool state atomic.Uint32 - lastUsage time.Time + childStreams *xcontext.CancelsGuard + lastUsage xsync.LastUsage onClose []func(*conn) onTransportErrors []func(ctx context.Context, cc Conn, cause error) } @@ -80,7 +82,7 @@ func (c *conn) LastUsage() time.Time { c.mtx.RLock() defer c.mtx.RUnlock() - return c.lastUsage + return c.lastUsage.Get() } func (c *conn) IsState(states ...State) bool { @@ -102,6 +104,36 @@ func (c *conn) NodeID() uint32 { return 0 } +func (c *conn) park(ctx context.Context) (err error) { + onDone := trace.DriverOnConnPark( + c.config.Trace(), &ctx, + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/conn.(*conn).park"), + c.Endpoint(), + ) + defer func() { + onDone(err) + }() + + c.mtx.Lock() + defer c.mtx.Unlock() + + if c.closed { + return nil + } + + if c.cc == nil { + return nil + } + + err = c.close(ctx) + + if err != nil { + return c.wrapError(err) + } + + return nil +} + func (c *conn) Endpoint() endpoint.Endpoint { if c != nil { return c.endpoint @@ -118,7 +150,7 @@ func (c *conn) setState(ctx context.Context, s State) State { if state := State(c.state.Swap(uint32(s))); state != s { trace.DriverOnConnStateChange( c.config.Trace(), &ctx, - stack.FunctionID(""), + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/conn.(*conn).setState"), c.endpoint.Copy(), state, )(s) } @@ -166,7 +198,7 @@ func (c *conn) realConn(ctx context.Context) (cc *grpc.ClientConn, err error) { onDone := trace.DriverOnConnDial( c.config.Trace(), &ctx, - stack.FunctionID(""), + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/conn.(*conn).realConn"), c.endpoint.Copy(), ) defer func() { @@ -183,6 +215,10 @@ func (c *conn) realConn(ctx context.Context) (cc *grpc.ClientConn, err error) { }, c.config.GrpcDialOptions()..., )...) if err != nil { + if xerrors.IsContextError(err) { + return nil, xerrors.WithStackTrace(err) + } + defer func() { c.onTransportError(ctx, err) }() @@ -210,12 +246,6 @@ func (c *conn) onTransportError(ctx context.Context, cause error) { } } -func (c *conn) touchLastUsage() { - c.mtx.Lock() - defer c.mtx.Unlock() - c.lastUsage = time.Now() -} - func isAvailable(raw *grpc.ClientConn) bool { return raw != nil && raw.GetState() == connectivity.Ready } @@ -249,7 +279,7 @@ func (c *conn) Close(ctx context.Context) (err error) { onDone := trace.DriverOnConnClose( c.config.Trace(), &ctx, - stack.FunctionID(""), + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/conn.(*conn).Close"), c.Endpoint(), ) defer func() { @@ -282,7 +312,7 @@ func (c *conn) Invoke( useWrapping = UseWrapping(ctx) onDone = trace.DriverOnConnInvoke( c.config.Trace(), &ctx, - stack.FunctionID(""), + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/conn.(*conn).Invoke"), c.endpoint, trace.Method(method), ) cc *grpc.ClientConn @@ -298,8 +328,8 @@ func (c *conn) Invoke( return c.wrapError(err) } - c.touchLastUsage() - defer c.touchLastUsage() + stop := c.lastUsage.Start() + defer stop() ctx, traceID, err := meta.TraceID(ctx) if err != nil { @@ -310,6 +340,10 @@ func (c *conn) Invoke( err = cc.Invoke(ctx, method, req, res, append(opts, grpc.Trailer(&md))...) if err != nil { + if xerrors.IsContextError(err) { + return xerrors.WithStackTrace(err) + } + defer func() { c.onTransportError(ctx, err) }() @@ -354,34 +388,33 @@ func (c *conn) Invoke( return err } +//nolint:funlen func (c *conn) NewStream( ctx context.Context, desc *grpc.StreamDesc, method string, opts ...grpc.CallOption, -) (_ grpc.ClientStream, err error) { +) (_ grpc.ClientStream, finalErr error) { var ( onDone = trace.DriverOnConnNewStream( c.config.Trace(), &ctx, - stack.FunctionID(""), + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/conn.(*conn).NewStream"), c.endpoint.Copy(), trace.Method(method), ) useWrapping = UseWrapping(ctx) - cc *grpc.ClientConn - s grpc.ClientStream ) defer func() { - onDone(err, c.GetState()) + onDone(finalErr, c.GetState()) }() - cc, err = c.realConn(ctx) + cc, err := c.realConn(ctx) if err != nil { return nil, c.wrapError(err) } - c.touchLastUsage() - defer c.touchLastUsage() + stop := c.lastUsage.Start() + defer stop() ctx, traceID, err := meta.TraceID(ctx) if err != nil { @@ -390,8 +423,24 @@ func (c *conn) NewStream( ctx, sentMark := markContext(meta.WithTraceID(ctx, traceID)) - s, err = cc.NewStream(ctx, desc, method, opts...) + ctx, cancel := xcontext.WithCancel(ctx) + defer func() { + if finalErr != nil { + cancel() + } else { + c.childStreams.Remember(&cancel) + } + }() + + s, err := cc.NewStream(ctx, desc, method, append(opts, grpc.OnFinish(func(err error) { + cancel() + c.childStreams.Forget(&cancel) + }))...) if err != nil { + if xerrors.IsContextError(err) { + return nil, xerrors.WithStackTrace(err) + } + defer func() { c.onTransportError(ctx, err) }() @@ -453,9 +502,16 @@ func withOnTransportError(onTransportError func(ctx context.Context, cc Conn, ca func newConn(e endpoint.Endpoint, config Config, opts ...option) *conn { c := &conn{ - endpoint: e, - config: config, - done: make(chan struct{}), + endpoint: e, + config: config, + done: make(chan struct{}), + lastUsage: xsync.NewLastUsage(), + childStreams: xcontext.NewCancelsGuard(), + onClose: []func(*conn){ + func(c *conn) { + c.childStreams.Cancel() + }, + }, } c.state.Store(uint32(Created)) for _, opt := range opts { @@ -509,7 +565,11 @@ func getContextMark(ctx context.Context) *modificationMark { return &modificationMark{} } - return v.(*modificationMark) + val, ok := v.(*modificationMark) + if !ok { + panic(fmt.Sprintf("unsupported type conversion from %T to *modificationMark", val)) + } + return val } type modificationMark struct { diff --git a/internal/conn/grpc_client_stream.go b/internal/conn/grpc_client_stream.go index 0ea1cae86..32377e5ab 100644 --- a/internal/conn/grpc_client_stream.go +++ b/internal/conn/grpc_client_stream.go @@ -25,14 +25,23 @@ type grpcClientStream struct { } func (s *grpcClientStream) CloseSend() (err error) { - onDone := trace.DriverOnConnStreamCloseSend(s.c.config.Trace(), &s.ctx, stack.FunctionID("")) + onDone := trace.DriverOnConnStreamCloseSend(s.c.config.Trace(), &s.ctx, + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/conn.(*grpcClientStream).CloseSend"), + ) defer func() { onDone(err) }() + stop := s.c.lastUsage.Start() + defer stop() + err = s.ClientStream.CloseSend() if err != nil { + if xerrors.IsContextError(err) { + return xerrors.WithStackTrace(err) + } + if s.wrapping { return s.wrapError( xerrors.Transport( @@ -50,14 +59,23 @@ func (s *grpcClientStream) CloseSend() (err error) { } func (s *grpcClientStream) SendMsg(m interface{}) (err error) { - onDone := trace.DriverOnConnStreamSendMsg(s.c.config.Trace(), &s.ctx, stack.FunctionID("")) + onDone := trace.DriverOnConnStreamSendMsg(s.c.config.Trace(), &s.ctx, + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/conn.(*grpcClientStream).SendMsg"), + ) defer func() { onDone(err) }() + stop := s.c.lastUsage.Start() + defer stop() + err = s.ClientStream.SendMsg(m) if err != nil { + if xerrors.IsContextError(err) { + return xerrors.WithStackTrace(err) + } + defer func() { s.c.onTransportError(s.Context(), err) }() @@ -83,11 +101,16 @@ func (s *grpcClientStream) SendMsg(m interface{}) (err error) { } func (s *grpcClientStream) RecvMsg(m interface{}) (err error) { - onDone := trace.DriverOnConnStreamRecvMsg(s.c.config.Trace(), &s.ctx, stack.FunctionID("")) + onDone := trace.DriverOnConnStreamRecvMsg(s.c.config.Trace(), &s.ctx, + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/conn.(*grpcClientStream).RecvMsg"), + ) defer func() { onDone(err) }() + stop := s.c.lastUsage.Start() + defer stop() + defer func() { if err != nil { md := s.ClientStream.Trailer() @@ -97,7 +120,11 @@ func (s *grpcClientStream) RecvMsg(m interface{}) (err error) { err = s.ClientStream.RecvMsg(m) - if err != nil { + if err != nil { //nolint:nestif + if xerrors.IsContextError(err) { + return xerrors.WithStackTrace(err) + } + defer func() { if !xerrors.Is(err, io.EOF) { s.c.onTransportError(s.Context(), err) diff --git a/internal/conn/pool.go b/internal/conn/pool.go index 02072d67c..783b7a880 100644 --- a/internal/conn/pool.go +++ b/internal/conn/pool.go @@ -4,6 +4,7 @@ import ( "context" "sync" "sync/atomic" + "time" "google.golang.org/grpc" grpcCodes "google.golang.org/grpc/codes" @@ -11,6 +12,7 @@ import ( "github.com/ydb-platform/ydb-go-sdk/v3/internal/closer" "github.com/ydb-platform/ydb-go-sdk/v3/internal/endpoint" "github.com/ydb-platform/ydb-go-sdk/v3/internal/stack" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/xcontext" "github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors" "github.com/ydb-platform/ydb-go-sdk/v3/internal/xsync" "github.com/ydb-platform/ydb-go-sdk/v3/trace" @@ -78,11 +80,24 @@ func (p *Pool) Ban(ctx context.Context, cc Conn, cause error) { return } - if xerrors.IsTransportError(cause, - grpcCodes.OK, - grpcCodes.Canceled, + if !xerrors.IsTransportError(cause, grpcCodes.ResourceExhausted, - grpcCodes.OutOfRange, + grpcCodes.Unavailable, + // grpcCodes.OK, + // grpcCodes.Canceled, + // grpcCodes.Unknown, + // grpcCodes.InvalidArgument, + // grpcCodes.DeadlineExceeded, + // grpcCodes.NotFound, + // grpcCodes.AlreadyExists, + // grpcCodes.PermissionDenied, + // grpcCodes.FailedPrecondition, + // grpcCodes.Aborted, + // grpcCodes.OutOfRange, + // grpcCodes.Unimplemented, + // grpcCodes.Internal, + // grpcCodes.DataLoss, + // grpcCodes.Unauthenticated, ) { return } @@ -99,7 +114,7 @@ func (p *Pool) Ban(ctx context.Context, cc Conn, cause error) { trace.DriverOnConnBan( p.config.Trace(), &ctx, - stack.FunctionID(""), + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/conn.(*Pool).Ban"), e, cc.GetState(), cause, )(cc.SetState(ctx, Banned)) } @@ -121,7 +136,7 @@ func (p *Pool) Allow(ctx context.Context, cc Conn) { trace.DriverOnConnAllow( p.config.Trace(), &ctx, - stack.FunctionID(""), + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/conn.(*Pool).Allow"), e, cc.GetState(), )(cc.Unban(ctx)) } @@ -133,7 +148,9 @@ func (p *Pool) Take(context.Context) error { } func (p *Pool) Release(ctx context.Context) (finalErr error) { - onDone := trace.DriverOnPoolRelease(p.config.Trace(), &ctx, stack.FunctionID("")) + onDone := trace.DriverOnPoolRelease(p.config.Trace(), &ctx, + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/conn.(*Pool).Release"), + ) defer func() { onDone(finalErr) }() @@ -181,8 +198,43 @@ func (p *Pool) Release(ctx context.Context) (finalErr error) { return nil } +func (p *Pool) connParker(ctx context.Context, ttl, interval time.Duration) { + ticker := time.NewTicker(interval) + defer ticker.Stop() + for { + select { + case <-p.done: + return + case <-ticker.C: + for _, c := range p.collectConns() { + if time.Since(c.LastUsage()) > ttl { + switch c.GetState() { + case Online, Banned: + _ = c.park(ctx) + default: + // nop + } + } + } + } + } +} + +func (p *Pool) collectConns() []*conn { + p.mtx.RLock() + defer p.mtx.RUnlock() + conns := make([]*conn, 0, len(p.conns)) + for _, c := range p.conns { + conns = append(conns, c) + } + + return conns +} + func NewPool(ctx context.Context, config Config) *Pool { - onDone := trace.DriverOnPoolNew(config.Trace(), &ctx, stack.FunctionID("")) + onDone := trace.DriverOnPoolNew(config.Trace(), &ctx, + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/conn.NewPool"), + ) defer onDone() p := &Pool{ @@ -193,5 +245,9 @@ func NewPool(ctx context.Context, config Config) *Pool { done: make(chan struct{}), } + if ttl := config.ConnectionTTL(); ttl > 0 { + go p.connParker(xcontext.ValueOnly(ctx), ttl, ttl/2) //nolint:gomnd + } + return p } diff --git a/internal/coordination/client.go b/internal/coordination/client.go index fb9331414..e7c194049 100644 --- a/internal/coordination/client.go +++ b/internal/coordination/client.go @@ -3,157 +3,216 @@ package coordination import ( "context" "errors" + "sync" + "time" "github.com/ydb-platform/ydb-go-genproto/Ydb_Coordination_V1" "github.com/ydb-platform/ydb-go-genproto/protos/Ydb_Coordination" + "github.com/ydb-platform/ydb-go-genproto/protos/Ydb_Operations" "google.golang.org/grpc" "github.com/ydb-platform/ydb-go-sdk/v3/coordination" + "github.com/ydb-platform/ydb-go-sdk/v3/coordination/options" "github.com/ydb-platform/ydb-go-sdk/v3/internal/coordination/config" "github.com/ydb-platform/ydb-go-sdk/v3/internal/operation" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/stack" "github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors" "github.com/ydb-platform/ydb-go-sdk/v3/retry" "github.com/ydb-platform/ydb-go-sdk/v3/scheme" + "github.com/ydb-platform/ydb-go-sdk/v3/trace" ) -//nolint:gofumpt -//nolint:nolintlint -var ( - errNilClient = xerrors.Wrap(errors.New("coordination client is not initialized")) -) +//go:generate mockgen -destination grpc_client_mock_test.go -package coordination -write_package_comment=false github.com/ydb-platform/ydb-go-genproto/Ydb_Coordination_V1 CoordinationServiceClient,CoordinationService_SessionClient + +var errNilClient = xerrors.Wrap(errors.New("coordination client is not initialized")) type Client struct { - config config.Config - service Ydb_Coordination_V1.CoordinationServiceClient + config config.Config + client Ydb_Coordination_V1.CoordinationServiceClient + + mutex sync.Mutex // guards the fields below + sessions map[*session]struct{} } func New(ctx context.Context, cc grpc.ClientConnInterface, config config.Config) *Client { + onDone := trace.CoordinationOnNew(config.Trace(), &ctx, + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/coordination.New"), + ) + defer onDone() + return &Client{ - config: config, - service: Ydb_Coordination_V1.NewCoordinationServiceClient(cc), + config: config, + client: Ydb_Coordination_V1.NewCoordinationServiceClient(cc), + sessions: make(map[*session]struct{}), } } -func (c *Client) CreateNode(ctx context.Context, path string, config coordination.NodeConfig) error { - if c == nil { - return xerrors.WithStackTrace(errNilClient) - } - call := func(ctx context.Context) error { - return xerrors.WithStackTrace(c.createNode(ctx, path, config)) +func operationParams( + ctx context.Context, + config interface { + OperationTimeout() time.Duration + OperationCancelAfter() time.Duration + }, + mode operation.Mode, +) *Ydb_Operations.OperationParams { + return operation.Params( + ctx, + config.OperationTimeout(), + config.OperationCancelAfter(), + mode, + ) +} + +func createNodeRequest( + path string, config coordination.NodeConfig, operationParams *Ydb_Operations.OperationParams, +) *Ydb_Coordination.CreateNodeRequest { + return &Ydb_Coordination.CreateNodeRequest{ + Path: path, + Config: &Ydb_Coordination.Config{ + Path: config.Path, + SelfCheckPeriodMillis: config.SelfCheckPeriodMillis, + SessionGracePeriodMillis: config.SessionGracePeriodMillis, + ReadConsistencyMode: config.ReadConsistencyMode.To(), + AttachConsistencyMode: config.AttachConsistencyMode.To(), + RateLimiterCountersMode: config.RatelimiterCountersMode.To(), + }, + OperationParams: operationParams, } - if !c.config.AutoRetry() { - return xerrors.WithStackTrace(call(ctx)) +} + +func createNode( + ctx context.Context, client Ydb_Coordination_V1.CoordinationServiceClient, request *Ydb_Coordination.CreateNodeRequest, +) error { + _, err := client.CreateNode(ctx, request) + if err != nil { + return xerrors.WithStackTrace(err) } - return retry.Retry(ctx, - call, retry.WithStackTrace(), - retry.WithIdempotent(true), - retry.WithTrace(c.config.TraceRetry()), - ) + return nil } -func (c *Client) createNode(ctx context.Context, path string, config coordination.NodeConfig) error { - _, err := c.service.CreateNode( - ctx, - &Ydb_Coordination.CreateNodeRequest{ - Path: path, - Config: &Ydb_Coordination.Config{ - Path: config.Path, - SelfCheckPeriodMillis: config.SelfCheckPeriodMillis, - SessionGracePeriodMillis: config.SessionGracePeriodMillis, - ReadConsistencyMode: config.ReadConsistencyMode.To(), - AttachConsistencyMode: config.AttachConsistencyMode.To(), - RateLimiterCountersMode: config.RatelimiterCountersMode.To(), - }, - OperationParams: operation.Params( - ctx, - c.config.OperationTimeout(), - c.config.OperationCancelAfter(), - operation.ModeSync, - ), - }, +func (c *Client) CreateNode(ctx context.Context, path string, config coordination.NodeConfig) (finalErr error) { + if c == nil { + return xerrors.WithStackTrace(errNilClient) + } + + onDone := trace.CoordinationOnCreateNode(c.config.Trace(), &ctx, + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/coordination.(*Client).CreateNode"), + path, ) + defer func() { + onDone(finalErr) + }() + + request := createNodeRequest(path, config, operationParams(ctx, &c.config, operation.ModeSync)) - return xerrors.WithStackTrace(err) + if !c.config.AutoRetry() { + return createNode(ctx, c.client, request) + } + + return retry.Retry(ctx, func(ctx context.Context) error { + return createNode(ctx, c.client, request) + }, retry.WithStackTrace(), retry.WithIdempotent(true), retry.WithTrace(c.config.TraceRetry())) } -func (c *Client) AlterNode(ctx context.Context, path string, config coordination.NodeConfig) error { +func (c *Client) AlterNode(ctx context.Context, path string, config coordination.NodeConfig) (finalErr error) { if c == nil { return xerrors.WithStackTrace(errNilClient) } + + onDone := trace.CoordinationOnAlterNode(c.config.Trace(), &ctx, + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/coordination.(*Client).AlterNode"), + path, + ) + defer func() { + onDone(finalErr) + }() + + request := alterNodeRequest(path, config, operationParams(ctx, &c.config, operation.ModeSync)) + call := func(ctx context.Context) error { - return xerrors.WithStackTrace(c.alterNode(ctx, path, config)) + return alterNode(ctx, c.client, request) } if !c.config.AutoRetry() { return xerrors.WithStackTrace(call(ctx)) } - return retry.Retry(ctx, - call, - retry.WithStackTrace(), - retry.WithIdempotent(true), - retry.WithTrace(c.config.TraceRetry()), - ) + return retry.Retry(ctx, func(ctx context.Context) (err error) { + return alterNode(ctx, c.client, request) + }, retry.WithStackTrace(), retry.WithIdempotent(true), retry.WithTrace(c.config.TraceRetry())) } -func (c *Client) alterNode(ctx context.Context, path string, config coordination.NodeConfig) error { - _, err := c.service.AlterNode( - ctx, - &Ydb_Coordination.AlterNodeRequest{ - Path: path, - Config: &Ydb_Coordination.Config{ - Path: config.Path, - SelfCheckPeriodMillis: config.SelfCheckPeriodMillis, - SessionGracePeriodMillis: config.SessionGracePeriodMillis, - ReadConsistencyMode: config.ReadConsistencyMode.To(), - AttachConsistencyMode: config.AttachConsistencyMode.To(), - RateLimiterCountersMode: config.RatelimiterCountersMode.To(), - }, - OperationParams: operation.Params( - ctx, - c.config.OperationTimeout(), - c.config.OperationCancelAfter(), - operation.ModeSync, - ), +func alterNodeRequest( + path string, config coordination.NodeConfig, operationParams *Ydb_Operations.OperationParams, +) *Ydb_Coordination.AlterNodeRequest { + return &Ydb_Coordination.AlterNodeRequest{ + Path: path, + Config: &Ydb_Coordination.Config{ + Path: config.Path, + SelfCheckPeriodMillis: config.SelfCheckPeriodMillis, + SessionGracePeriodMillis: config.SessionGracePeriodMillis, + ReadConsistencyMode: config.ReadConsistencyMode.To(), + AttachConsistencyMode: config.AttachConsistencyMode.To(), + RateLimiterCountersMode: config.RatelimiterCountersMode.To(), }, - ) + OperationParams: operationParams, + } +} - return xerrors.WithStackTrace(err) +func alterNode( + ctx context.Context, client Ydb_Coordination_V1.CoordinationServiceClient, request *Ydb_Coordination.AlterNodeRequest, +) error { + _, err := client.AlterNode(ctx, request) + if err != nil { + return xerrors.WithStackTrace(err) + } + + return nil } -func (c *Client) DropNode(ctx context.Context, path string) error { +func (c *Client) DropNode(ctx context.Context, path string) (finalErr error) { if c == nil { return xerrors.WithStackTrace(errNilClient) } + + onDone := trace.CoordinationOnDropNode(c.config.Trace(), &ctx, + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/coordination.(*Client).DropNode"), + path, + ) + defer func() { + onDone(finalErr) + }() + + request := dropNodeRequest(path, operationParams(ctx, &c.config, operation.ModeSync)) + call := func(ctx context.Context) error { - return xerrors.WithStackTrace(c.dropNode(ctx, path)) + return dropNode(ctx, c.client, request) } if !c.config.AutoRetry() { return xerrors.WithStackTrace(call(ctx)) } - return retry.Retry(ctx, call, - retry.WithStackTrace(), - retry.WithIdempotent(true), - retry.WithTrace(c.config.TraceRetry()), - ) + return retry.Retry(ctx, func(ctx context.Context) (err error) { + return dropNode(ctx, c.client, request) + }, retry.WithStackTrace(), retry.WithIdempotent(true), retry.WithTrace(c.config.TraceRetry())) } -func (c *Client) dropNode(ctx context.Context, path string) error { - _, err := c.service.DropNode( - ctx, - &Ydb_Coordination.DropNodeRequest{ - Path: path, - OperationParams: operation.Params( - ctx, - c.config.OperationTimeout(), - c.config.OperationCancelAfter(), - operation.ModeSync, - ), - }, - ) +func dropNodeRequest(path string, operationParams *Ydb_Operations.OperationParams) *Ydb_Coordination.DropNodeRequest { + return &Ydb_Coordination.DropNodeRequest{ + Path: path, + OperationParams: operationParams, + } +} - return xerrors.WithStackTrace(err) +func dropNode( + ctx context.Context, client Ydb_Coordination_V1.CoordinationServiceClient, request *Ydb_Coordination.DropNodeRequest, +) error { + _, err := client.DropNode(ctx, request) + if err != nil { + return xerrors.WithStackTrace(err) + } + + return nil } func (c *Client) DescribeNode( @@ -162,34 +221,55 @@ func (c *Client) DescribeNode( ) ( entry *scheme.Entry, config *coordination.NodeConfig, - _ error, + finalErr error, ) { if c == nil { return nil, nil, xerrors.WithStackTrace(errNilClient) } - call := func(ctx context.Context) (err error) { - entry, config, err = c.describeNode(ctx, path) - return xerrors.WithStackTrace(err) - } + onDone := trace.CoordinationOnDescribeNode(c.config.Trace(), &ctx, + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/coordination.(*Client).DescribeNode"), + path, + ) + defer func() { + onDone(finalErr) + }() + + request := describeNodeRequest(path, operationParams(ctx, &c.config, operation.ModeSync)) + if !c.config.AutoRetry() { - err := call(ctx) + return describeNode(ctx, c.client, request) + } + + err := retry.Retry(ctx, func(ctx context.Context) (err error) { + entry, config, err = describeNode(ctx, c.client, request) + if err != nil { + return xerrors.WithStackTrace(err) + } - return entry, config, xerrors.WithStackTrace(err) + return nil + }, retry.WithStackTrace(), retry.WithIdempotent(true), retry.WithTrace(c.config.TraceRetry())) + if err != nil { + return nil, nil, xerrors.WithStackTrace(err) } - err := retry.Retry(ctx, call, - retry.WithStackTrace(), - retry.WithIdempotent(true), - retry.WithTrace(c.config.TraceRetry()), - ) - return entry, config, xerrors.WithStackTrace(err) + return entry, config, nil +} + +func describeNodeRequest( + path string, operationParams *Ydb_Operations.OperationParams, +) *Ydb_Coordination.DescribeNodeRequest { + return &Ydb_Coordination.DescribeNodeRequest{ + Path: path, + OperationParams: operationParams, + } } // DescribeNode describes a coordination node -func (c *Client) describeNode( +func describeNode( ctx context.Context, - path string, + client Ydb_Coordination_V1.CoordinationServiceClient, + request *Ydb_Coordination.DescribeNodeRequest, ) ( _ *scheme.Entry, _ *coordination.NodeConfig, @@ -199,21 +279,11 @@ func (c *Client) describeNode( response *Ydb_Coordination.DescribeNodeResponse result Ydb_Coordination.DescribeNodeResult ) - response, err = c.service.DescribeNode( - ctx, - &Ydb_Coordination.DescribeNodeRequest{ - Path: path, - OperationParams: operation.Params( - ctx, - c.config.OperationTimeout(), - c.config.OperationCancelAfter(), - operation.ModeSync, - ), - }, - ) + response, err = client.DescribeNode(ctx, request) if err != nil { return nil, nil, xerrors.WithStackTrace(err) } + err = response.GetOperation().GetResult().UnmarshalTo(&result) if err != nil { return nil, nil, xerrors.WithStackTrace(err) @@ -229,15 +299,85 @@ func (c *Client) describeNode( }, nil } -func (c *Client) Close(ctx context.Context) error { +func newCreateSessionConfig(opts ...options.SessionOption) *options.CreateSessionOptions { + c := defaultCreateSessionConfig() + for _, o := range opts { + if o != nil { + o(c) + } + } + + return c +} + +func (c *Client) sessionCreated(s *session) { + c.mutex.Lock() + defer c.mutex.Unlock() + + c.sessions[s] = struct{}{} +} + +func (c *Client) sessionClosed(s *session) { + c.mutex.Lock() + defer c.mutex.Unlock() + + delete(c.sessions, s) +} + +func (c *Client) closeSessions(ctx context.Context) { + c.mutex.Lock() + defer c.mutex.Unlock() + + for s := range c.sessions { + s.Close(ctx) + } +} + +func defaultCreateSessionConfig() *options.CreateSessionOptions { + return &options.CreateSessionOptions{ + Description: "YDB Go SDK", + SessionTimeout: time.Second * 5, //nolint:gomnd + SessionStartTimeout: time.Second * 1, + SessionStopTimeout: time.Second * 1, + SessionKeepAliveTimeout: time.Second * 10, //nolint:gomnd + SessionReconnectDelay: time.Millisecond * 500, //nolint:gomnd + } +} + +func (c *Client) Session( + ctx context.Context, + path string, + opts ...options.SessionOption, +) (_ coordination.Session, finalErr error) { if c == nil { - return xerrors.WithStackTrace(errNilClient) + return nil, xerrors.WithStackTrace(errNilClient) } - return c.close(ctx) + onDone := trace.CoordinationOnSession(c.config.Trace(), &ctx, + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/coordination.(*Client).Session"), + path, + ) + defer func() { + onDone(finalErr) + }() + + return createSession(ctx, c, path, newCreateSessionConfig(opts...)) } -func (c *Client) close(context.Context) error { +func (c *Client) Close(ctx context.Context) (finalErr error) { + if c == nil { + return xerrors.WithStackTrace(errNilClient) + } + + onDone := trace.CoordinationOnClose(c.config.Trace(), &ctx, + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/coordination.(*Client).Close"), + ) + defer func() { + onDone(finalErr) + }() + + c.closeSessions(ctx) + return nil } diff --git a/internal/coordination/client_test.go b/internal/coordination/client_test.go new file mode 100644 index 000000000..d7778be95 --- /dev/null +++ b/internal/coordination/client_test.go @@ -0,0 +1,388 @@ +package coordination + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/require" + "github.com/ydb-platform/ydb-go-genproto/protos/Ydb" + "github.com/ydb-platform/ydb-go-genproto/protos/Ydb_Coordination" + "github.com/ydb-platform/ydb-go-genproto/protos/Ydb_Operations" + "github.com/ydb-platform/ydb-go-genproto/protos/Ydb_Scheme" + "go.uber.org/mock/gomock" + grpcCodes "google.golang.org/grpc/codes" + grpcStatus "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/anypb" + "google.golang.org/protobuf/types/known/durationpb" + + "github.com/ydb-platform/ydb-go-sdk/v3/config" + "github.com/ydb-platform/ydb-go-sdk/v3/coordination" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/operation" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/xtest" + "github.com/ydb-platform/ydb-go-sdk/v3/scheme" +) + +func TestCreateNode(t *testing.T) { + t.Run("HappyWay", func(t *testing.T) { + ctx := xtest.Context(t) + ctrl := gomock.NewController(t) + client := NewMockCoordinationServiceClient(ctrl) + client.EXPECT().CreateNode(gomock.Any(), gomock.Any()).Return(&Ydb_Coordination.CreateNodeResponse{ + Operation: &Ydb_Operations.Operation{ + Ready: true, + Status: Ydb.StatusIds_SUCCESS, + }, + }, nil) + err := createNode(ctx, client, &Ydb_Coordination.CreateNodeRequest{}) + require.NoError(t, err) + }) + t.Run("TransportError", func(t *testing.T) { + ctx := xtest.Context(t) + ctrl := gomock.NewController(t) + client := NewMockCoordinationServiceClient(ctrl) + client.EXPECT().CreateNode(gomock.Any(), gomock.Any()).Return(nil, + xerrors.Transport(grpcStatus.Error(grpcCodes.ResourceExhausted, "")), + ) + err := createNode(ctx, client, &Ydb_Coordination.CreateNodeRequest{}) + require.True(t, xerrors.IsTransportError(err, grpcCodes.ResourceExhausted)) + require.True(t, xerrors.IsRetryObjectValid(err)) + }) + t.Run("OperationError", func(t *testing.T) { + ctx := xtest.Context(t) + ctrl := gomock.NewController(t) + client := NewMockCoordinationServiceClient(ctrl) + client.EXPECT().CreateNode(gomock.Any(), gomock.Any()).Return(nil, + xerrors.Operation(xerrors.WithStatusCode(Ydb.StatusIds_UNAVAILABLE)), + ) + err := createNode(ctx, client, &Ydb_Coordination.CreateNodeRequest{}) + require.True(t, xerrors.IsOperationError(err, Ydb.StatusIds_UNAVAILABLE)) + require.True(t, xerrors.IsRetryObjectValid(err)) + }) +} + +func TestCreateNodeRequest(t *testing.T) { + for _, tt := range []struct { + name string + path string + config coordination.NodeConfig + operationParams *Ydb_Operations.OperationParams + request *Ydb_Coordination.CreateNodeRequest + }{ + { + name: xtest.CurrentFileLine(), + path: "/abc", + config: coordination.NodeConfig{ + Path: "/cde", + }, + operationParams: operation.Params(context.Background(), time.Second, time.Second, operation.ModeSync), + request: &Ydb_Coordination.CreateNodeRequest{ + Path: "/abc", + Config: &Ydb_Coordination.Config{ + Path: "/cde", + }, + OperationParams: &Ydb_Operations.OperationParams{ + OperationMode: Ydb_Operations.OperationParams_SYNC, + OperationTimeout: durationpb.New(time.Second), + CancelAfter: durationpb.New(time.Second), + }, + }, + }, + } { + t.Run(tt.name, func(t *testing.T) { + request := createNodeRequest(tt.path, tt.config, tt.operationParams) + require.EqualValues(t, xtest.ToJSON(tt.request), xtest.ToJSON(request)) + }) + } +} + +func TestDescribeNodeRequest(t *testing.T) { + for _, tt := range []struct { + name string + path string + operationParams *Ydb_Operations.OperationParams + request *Ydb_Coordination.DescribeNodeRequest + }{ + { + name: xtest.CurrentFileLine(), + path: "/a/b/c", + operationParams: &Ydb_Operations.OperationParams{ + OperationMode: Ydb_Operations.OperationParams_SYNC, + }, + request: &Ydb_Coordination.DescribeNodeRequest{ + Path: "/a/b/c", + OperationParams: &Ydb_Operations.OperationParams{ + OperationMode: Ydb_Operations.OperationParams_SYNC, + }, + }, + }, + } { + t.Run(tt.name, func(t *testing.T) { + request := describeNodeRequest(tt.path, tt.operationParams) + require.Equal(t, xtest.ToJSON(tt.request), xtest.ToJSON(request)) + }) + } +} + +func TestOperationParams(t *testing.T) { + for _, tt := range []struct { + name string + ctx context.Context + config interface { + OperationTimeout() time.Duration + OperationCancelAfter() time.Duration + } + mode operation.Mode + operationParams *Ydb_Operations.OperationParams + }{ + { + name: xtest.CurrentFileLine(), + ctx: context.Background(), + config: config.New(config.WithOperationCancelAfter(time.Second), config.WithOperationTimeout(time.Second)), + mode: operation.ModeSync, + operationParams: &Ydb_Operations.OperationParams{ + OperationMode: Ydb_Operations.OperationParams_SYNC, + OperationTimeout: durationpb.New(time.Second), + CancelAfter: durationpb.New(time.Second), + }, + }, + { + name: xtest.CurrentFileLine(), + ctx: operation.WithCancelAfter(operation.WithTimeout(context.Background(), time.Second), time.Second), + config: config.New(), + mode: operation.ModeSync, + operationParams: &Ydb_Operations.OperationParams{ + OperationMode: Ydb_Operations.OperationParams_SYNC, + OperationTimeout: durationpb.New(time.Second), + CancelAfter: durationpb.New(time.Second), + }, + }, + } { + t.Run(tt.name, func(t *testing.T) { + params := operationParams(tt.ctx, tt.config, tt.mode) + require.Equal(t, xtest.ToJSON(tt.operationParams), xtest.ToJSON(params)) + }) + } +} + +func TestDescribeNode(t *testing.T) { + t.Run("HappyWay", func(t *testing.T) { + ctx := xtest.Context(t) + ctrl := gomock.NewController(t) + client := NewMockCoordinationServiceClient(ctrl) + client.EXPECT().DescribeNode(gomock.Any(), gomock.Any()).Return(&Ydb_Coordination.DescribeNodeResponse{ + Operation: &Ydb_Operations.Operation{ + Ready: true, + Status: Ydb.StatusIds_SUCCESS, + Result: func() *anypb.Any { + result, err := anypb.New(&Ydb_Coordination.DescribeNodeResult{ + Self: &Ydb_Scheme.Entry{ + Name: "/a/b/c", + Owner: "root", + Type: Ydb_Scheme.Entry_COORDINATION_NODE, + }, + Config: &Ydb_Coordination.Config{ + Path: "/a/b/c", + SelfCheckPeriodMillis: 100, + SessionGracePeriodMillis: 1000, + ReadConsistencyMode: Ydb_Coordination.ConsistencyMode_CONSISTENCY_MODE_STRICT, + AttachConsistencyMode: Ydb_Coordination.ConsistencyMode_CONSISTENCY_MODE_STRICT, + RateLimiterCountersMode: Ydb_Coordination.RateLimiterCountersMode_RATE_LIMITER_COUNTERS_MODE_AGGREGATED, + }, + }) + require.NoError(t, err) + + return result + }(), + }, + }, nil) + nodeScheme, nodeConfig, err := describeNode(ctx, client, &Ydb_Coordination.DescribeNodeRequest{ + Path: "/a/b/c", + OperationParams: nil, + }) + require.NoError(t, err) + require.Equal(t, xtest.ToJSON(&scheme.Entry{ + Name: "/a/b/c", + Owner: "root", + Type: scheme.EntryCoordinationNode, + }), xtest.ToJSON(nodeScheme)) + require.Equal(t, xtest.ToJSON(coordination.NodeConfig{ + Path: "/a/b/c", + SelfCheckPeriodMillis: 100, + SessionGracePeriodMillis: 1000, + ReadConsistencyMode: coordination.ConsistencyModeStrict, + AttachConsistencyMode: coordination.ConsistencyModeStrict, + RatelimiterCountersMode: coordination.RatelimiterCountersModeAggregated, + }), xtest.ToJSON(nodeConfig)) + }) + t.Run("TransportError", func(t *testing.T) { + ctx := xtest.Context(t) + ctrl := gomock.NewController(t) + client := NewMockCoordinationServiceClient(ctrl) + client.EXPECT().DescribeNode(gomock.Any(), gomock.Any()).Return(nil, + xerrors.Transport(grpcStatus.Error(grpcCodes.Unavailable, "")), + ) + nodeScheme, nodeConfig, err := describeNode(ctx, client, &Ydb_Coordination.DescribeNodeRequest{ + Path: "/a/b/c", + OperationParams: nil, + }) + require.True(t, xerrors.IsTransportError(err, grpcCodes.Unavailable)) + require.Nil(t, nodeScheme) + require.Nil(t, nodeConfig) + }) + t.Run("OperationError", func(t *testing.T) { + ctx := xtest.Context(t) + ctrl := gomock.NewController(t) + client := NewMockCoordinationServiceClient(ctrl) + client.EXPECT().DescribeNode(gomock.Any(), gomock.Any()).Return(nil, + xerrors.Operation(xerrors.WithStatusCode(Ydb.StatusIds_UNAVAILABLE)), + ) + nodeScheme, nodeConfig, err := describeNode(ctx, client, &Ydb_Coordination.DescribeNodeRequest{ + Path: "/a/b/c", + OperationParams: nil, + }) + require.True(t, xerrors.IsOperationError(err, Ydb.StatusIds_UNAVAILABLE)) + require.Nil(t, nodeScheme) + require.Nil(t, nodeConfig) + }) +} + +func TestAlterNodeRequest(t *testing.T) { + for _, tt := range []struct { + name string + path string + config coordination.NodeConfig + operationParams *Ydb_Operations.OperationParams + request *Ydb_Coordination.AlterNodeRequest + }{ + { + name: xtest.CurrentFileLine(), + path: "/a/b/c", + config: coordination.NodeConfig{ + Path: "/a/b/c", + }, + operationParams: &Ydb_Operations.OperationParams{ + OperationMode: Ydb_Operations.OperationParams_SYNC, + }, + request: &Ydb_Coordination.AlterNodeRequest{ + Path: "/a/b/c", + Config: &Ydb_Coordination.Config{ + Path: "/a/b/c", + }, + OperationParams: &Ydb_Operations.OperationParams{ + OperationMode: Ydb_Operations.OperationParams_SYNC, + }, + }, + }, + } { + t.Run(tt.name, func(t *testing.T) { + request := alterNodeRequest(tt.path, tt.config, tt.operationParams) + require.Equal(t, xtest.ToJSON(tt.request), xtest.ToJSON(request)) + }) + } +} + +func TestAlterNode(t *testing.T) { + t.Run("HappyWay", func(t *testing.T) { + ctx := xtest.Context(t) + ctrl := gomock.NewController(t) + client := NewMockCoordinationServiceClient(ctrl) + client.EXPECT().AlterNode(gomock.Any(), gomock.Any()).Return(&Ydb_Coordination.AlterNodeResponse{ + Operation: &Ydb_Operations.Operation{ + Ready: true, + Status: Ydb.StatusIds_SUCCESS, + }, + }, nil) + err := alterNode(ctx, client, &Ydb_Coordination.AlterNodeRequest{}) + require.NoError(t, err) + }) + t.Run("TransportError", func(t *testing.T) { + ctx := xtest.Context(t) + ctrl := gomock.NewController(t) + client := NewMockCoordinationServiceClient(ctrl) + client.EXPECT().AlterNode(gomock.Any(), gomock.Any()).Return(nil, + xerrors.Transport(grpcStatus.Error(grpcCodes.ResourceExhausted, "")), + ) + err := alterNode(ctx, client, &Ydb_Coordination.AlterNodeRequest{}) + require.True(t, xerrors.IsTransportError(err, grpcCodes.ResourceExhausted)) + require.True(t, xerrors.IsRetryObjectValid(err)) + }) + t.Run("OperationError", func(t *testing.T) { + ctx := xtest.Context(t) + ctrl := gomock.NewController(t) + client := NewMockCoordinationServiceClient(ctrl) + client.EXPECT().AlterNode(gomock.Any(), gomock.Any()).Return(nil, + xerrors.Operation(xerrors.WithStatusCode(Ydb.StatusIds_UNAVAILABLE)), + ) + err := alterNode(ctx, client, &Ydb_Coordination.AlterNodeRequest{}) + require.True(t, xerrors.IsOperationError(err, Ydb.StatusIds_UNAVAILABLE)) + require.True(t, xerrors.IsRetryObjectValid(err)) + }) +} + +func TestDropNodeRequest(t *testing.T) { + for _, tt := range []struct { + name string + path string + operationParams *Ydb_Operations.OperationParams + request *Ydb_Coordination.DropNodeRequest + }{ + { + name: xtest.CurrentFileLine(), + path: "/a/b/c", + operationParams: &Ydb_Operations.OperationParams{ + OperationMode: Ydb_Operations.OperationParams_SYNC, + }, + request: &Ydb_Coordination.DropNodeRequest{ + Path: "/a/b/c", + OperationParams: &Ydb_Operations.OperationParams{ + OperationMode: Ydb_Operations.OperationParams_SYNC, + }, + }, + }, + } { + t.Run(tt.name, func(t *testing.T) { + request := dropNodeRequest(tt.path, tt.operationParams) + require.Equal(t, xtest.ToJSON(tt.request), xtest.ToJSON(request)) + }) + } +} + +func TestDropNode(t *testing.T) { + t.Run("HappyWay", func(t *testing.T) { + ctx := xtest.Context(t) + ctrl := gomock.NewController(t) + client := NewMockCoordinationServiceClient(ctrl) + client.EXPECT().DropNode(gomock.Any(), gomock.Any()).Return(&Ydb_Coordination.DropNodeResponse{ + Operation: &Ydb_Operations.Operation{ + Ready: true, + Status: Ydb.StatusIds_SUCCESS, + }, + }, nil) + err := dropNode(ctx, client, &Ydb_Coordination.DropNodeRequest{}) + require.NoError(t, err) + }) + t.Run("TransportError", func(t *testing.T) { + ctx := xtest.Context(t) + ctrl := gomock.NewController(t) + client := NewMockCoordinationServiceClient(ctrl) + client.EXPECT().DropNode(gomock.Any(), gomock.Any()).Return(nil, + xerrors.Transport(grpcStatus.Error(grpcCodes.ResourceExhausted, "")), + ) + err := dropNode(ctx, client, &Ydb_Coordination.DropNodeRequest{}) + require.True(t, xerrors.IsTransportError(err, grpcCodes.ResourceExhausted)) + require.True(t, xerrors.IsRetryObjectValid(err)) + }) + t.Run("OperationError", func(t *testing.T) { + ctx := xtest.Context(t) + ctrl := gomock.NewController(t) + client := NewMockCoordinationServiceClient(ctrl) + client.EXPECT().DropNode(gomock.Any(), gomock.Any()).Return(nil, + xerrors.Operation(xerrors.WithStatusCode(Ydb.StatusIds_UNAVAILABLE)), + ) + err := dropNode(ctx, client, &Ydb_Coordination.DropNodeRequest{}) + require.True(t, xerrors.IsOperationError(err, Ydb.StatusIds_UNAVAILABLE)) + require.True(t, xerrors.IsRetryObjectValid(err)) + }) +} diff --git a/internal/coordination/config/config.go b/internal/coordination/config/config.go index ac684475f..692777a43 100644 --- a/internal/coordination/config/config.go +++ b/internal/coordination/config/config.go @@ -20,9 +20,9 @@ func (c Config) Trace() *trace.Coordination { type Option func(c *Config) // WithTrace appends coordination trace to early defined traces -func WithTrace(trace trace.Coordination, opts ...trace.CoordinationComposeOption) Option { +func WithTrace(trace *trace.Coordination, opts ...trace.CoordinationComposeOption) Option { return func(c *Config) { - c.trace = c.trace.Compose(&trace, opts...) + c.trace = c.trace.Compose(trace, opts...) } } diff --git a/internal/coordination/conversation/conversation.go b/internal/coordination/conversation/conversation.go new file mode 100644 index 000000000..c3c8e1863 --- /dev/null +++ b/internal/coordination/conversation/conversation.go @@ -0,0 +1,517 @@ +// Package conversation contains coordination session internal code that helps implement a typical conversation-like +// session protocol based on a bidirectional gRPC stream. +package conversation + +import ( + "context" + "sync" + + "github.com/ydb-platform/ydb-go-genproto/protos/Ydb_Coordination" + + "github.com/ydb-platform/ydb-go-sdk/v3/coordination" +) + +// Controller provides a simple mechanism to work with a session protocol using a gRPC bidirectional stream. Creating a +// bidirectional stream client may be quite tricky because messages are usually being processed independently and in +// parallel. Moreover, the gRPC client library puts strict limitations on an implementation of the client, e.g. multiple +// calls of the Send or Recv methods of the stub client must not be performed from different goroutines. Also, there are +// no guarantees that a message successfully dispatched by the Send method will actually reach the server, neither does +// the server enjoy same guarantees when delivering messages to the client. This usually ends up having two goroutines +// (one for sending outgoing messages and another one for receiving incoming ones) and a queue where messages are +// published to be eventually delivered to the server. The Controller simplifies working with this model providing +// generic implementation of the message queue and related routines, handling retries of sent and pending operations +// when the underlying gRPC stream needs to be reconnected. +// +// A typical coordination session looks like this (we are going to skip for now how the gRPC stream is created, handled +// and kept alive, you can find the details on that in the Session, and focus on the protocol): +// +// 1. The client opens a new gRPC bidirectional stream. +// 2. The client sends the SessionStart request and wait until the Failure or the SessionStarted reply. +// 3. The server sends the SessionStarted response with the SessionID. At this point the session is started. If the +// client needs to reconnect the gRPC stream in the future, it will use that SessionID to attach to the previously +// created session in the SessionStart request. +// 4. The client sends the AcquireSemaphore request to acquire a permit to the semaphore in this session with count 5. +// 5. After a moment, the client decides to acquire another semaphore, it sends one more AcquireSemaphore request with +// count 4. +// 6. The server replies with the AcquireSemaphoreResult response to the second AcquireSemaphore request to inform the +// client that the semaphore was successfully acquired. +// 7. The server replies with the AcquireSemaphorePending response in order to inform the client that the semaphore +// from the first request has been acquired by another session. +// 8. After a while, the server sends the AcquireSemaphoreResult response which implies that the semaphore from the +// first request is acquired in the current session. +// 9. Then the client sends the ReleaseSemaphore request in order to release the acquired semaphore. +// 10. The server replies with the ReleaseSemaphoreResult. +// 11. The client terminates the session with the SessionStop request. +// 12. The server let the client know that the session is over sending the SessionStopped response and closing the gRPC +// stream. +// +// We can notice five independent conversations here: +// +// 1. StartSession, SessionStarted — points 2–3; +// 2. AcquireSemaphore, AcquireSemaphoreResult — points 4, 6; +// 3. AcquireSemaphore, AcquireSemaphorePending, AcquireSemaphoreResult — points 5, 7 and 8; +// 4. ReleaseSemaphore, ReleaseSemaphoreResult — points 9–10; +// 5. SessionStop, SessionStopped — points 11–12. +// +// If at any time the client encounters an unrecoverable error (for example, the underlying gRPC stream becomes +// disconnected), the client will have to replay every conversation from their very beginning. Let us see why it is +// actually the case. But before we go into that, let us look at the grpc.ClientStream SendMsg method: +// +// "â€ĻSendMsg does not wait until the message is received by the server. An untimely stream closure may result in lost +// messages. To ensure delivery, users should ensure the RPC completed successfully using RecvMsgâ€Ļ" +// +// This is true for both, the client and the server. So when the server replies to the client it does not really know if +// the response is received by the client. And vice versa, when the client sends a request to the server it has no way +// to know if the request was delivered to the server unless the server sends another message to the client in reply. +// +// That is why conversation-like protocols typically use idempotent requests. Idempotent requests can be safely retried +// as long as you keep the original order of the conversations. For example, if the gRPC stream is terminated before +// the point 6, we cannot know if the server gets the requests. There may be one, two or none AcquireSemaphore requests +// successfully delivered to and handled by the server. Moreover, the server may have already sent to the client the +// corresponding responses. Nevertheless, if the requests are idempotent, we can safely retry them all in the newly +// created gRPC stream and get the same results as we would have got if we had sent them without stream termination. +// Note that if the stream is terminated before the point 8, we still need to replay the first AcquireSemaphore +// conversation because we have no knowledge if the server replied with the AcquireSemaphoreResult in the terminated +// stream. +// +// However, sometimes even idempotent requests cannot be safely retried. Consider the case wherein the point 5 from the +// original list is: +// +// 5. After a moment, the client decides to modify the AcquireSemaphore request and sends another one with the same +// semaphore but with count 4. +// +// If then the gRPC stream terminates, there are two likely outcomes: +// +// 1. The server received the first request but the second one was not delivered. The current semaphore count is 5. +// 2. The server received and processed the both requests. The current semaphore permit count is 4. +// +// If we retry the both requests, the observed result will be different depending on which outcome occurs: +// +// 1. The first retry will be a noop, the second one will decrease the semaphore count to 4. This is expected behavior. +// 2. The first retry will try to increase the semaphore count to 5, it causes an error. This is unexpected. +// +// In order to avoid that we could postpone a conversation if there is another one for the same semaphore which has been +// sent but has not been yet delivered to the server. For more details, see the WithConflictKey option. +type Controller struct { + mutex sync.Mutex // guards access to the fields below + + queue []*Conversation // the message queue, the front is in the end of the slice + conflicts map[string]struct{} + + notifyChan chan struct{} + closed bool +} + +// ResponseFilter defines the filter function called by the controller to know if a received message relates to the +// conversation. If a ResponseFilter returns true, the message is considered to be part of the conversation. +type ResponseFilter func(request *Ydb_Coordination.SessionRequest, response *Ydb_Coordination.SessionResponse) bool + +// Conversation is a core concept of the conversation package. It is an ordered sequence of connected request/reply +// messages. For example, the acquiring semaphore conversation may look like this: +// +// 1. The client sends the AcquireSemaphore request. +// 2. The server replies with the AcquireSemaphorePending response. +// 3. After a while, the server replies with the AcquireSemaphoreResult response. The conversation is ended. +// +// There may be many different conversations carried out simultaneously in one session, so the exact order of all the +// messages in the session is unspecified. In the example above, there may be other messages (from different +// conversations) between points 1 and 2, or 2 and 3. +type Conversation struct { + message func() *Ydb_Coordination.SessionRequest + responseFilter ResponseFilter + acknowledgeFilter ResponseFilter + cancelMessage func(req *Ydb_Coordination.SessionRequest) *Ydb_Coordination.SessionRequest + cancelFilter ResponseFilter + conflictKey string + requestSent *Ydb_Coordination.SessionRequest + cancelRequestSent *Ydb_Coordination.SessionRequest + response *Ydb_Coordination.SessionResponse + responseErr error + done chan struct{} + idempotent bool + canceled bool +} + +// NewController creates a new conversation controller. You usually have one controller per one session. +func NewController() *Controller { + return &Controller{ + notifyChan: make(chan struct{}, 1), + conflicts: make(map[string]struct{}), + } +} + +// WithResponseFilter returns an Option that specifies the filter function that is used to detect the last response +// message in the conversation. If such a message was found, the conversation is immediately ended and the response +// becomes available by the Conversation.Await method. +func WithResponseFilter(filter ResponseFilter) Option { + return func(c *Conversation) { + c.responseFilter = filter + } +} + +// WithAcknowledgeFilter returns an Option that specifies the filter function that is used to detect an intermediate +// response message in the conversation. If such a message was found, the conversation continues, but it lets the client +// know that the server successfully consumed the first request of the conversation. +func WithAcknowledgeFilter(filter ResponseFilter) Option { + return func(c *Conversation) { + c.acknowledgeFilter = filter + } +} + +// WithCancelMessage returns an Option that specifies the message and filter functions that are used to cancel the +// conversation which has been already sent. This message is sent in the background when the caller cancels the context +// of the Controller.Await function. The response is never received by the caller and is only used to end the +// conversation and remove it from the queue. +func WithCancelMessage( + message func(req *Ydb_Coordination.SessionRequest) *Ydb_Coordination.SessionRequest, + filter ResponseFilter, +) Option { + return func(c *Conversation) { + c.cancelMessage = message + c.cancelFilter = filter + } +} + +// WithConflictKey returns an Option that specifies the key that is used to find out messages that cannot be delivered +// to the server until the server acknowledged the request. If there is a conversation with the same conflict key in the +// queue that has not been yet delivered to the server, the controller will temporarily suspend other conversations with +// the same conflict key until the first one is acknowledged. +func WithConflictKey(key string) Option { + return func(c *Conversation) { + c.conflictKey = key + } +} + +// WithIdempotence returns an Option that enabled retries for this conversation when the underlying gRPC stream +// reconnects. The controller will replay the whole conversation from scratch unless it is not ended. +func WithIdempotence(idempotent bool) Option { + return func(c *Conversation) { + c.idempotent = idempotent + } +} + +// Option configures how we create a new conversation. +type Option func(c *Conversation) + +// NewConversation creates a new conversation that starts with a specified message. +func NewConversation(request func() *Ydb_Coordination.SessionRequest, opts ...Option) *Conversation { + conversation := Conversation{message: request} + for _, o := range opts { + if o != nil { + o(&conversation) + } + } + + return &conversation +} + +func (c *Controller) notify() { + select { + case c.notifyChan <- struct{}{}: + default: + } +} + +// PushBack puts a new conversation at the end of the queue. +func (c *Controller) PushBack(conversation *Conversation) error { + c.mutex.Lock() + defer c.mutex.Unlock() + + if c.closed { + return coordination.ErrSessionClosed + } + + conversation.enqueue() + c.queue = append([]*Conversation{conversation}, c.queue...) + c.notify() + + return nil +} + +// PushFront puts a new conversation at the beginning of the queue. +func (c *Controller) PushFront(conversation *Conversation) error { + c.mutex.Lock() + defer c.mutex.Unlock() + + if c.closed { + return coordination.ErrSessionClosed + } + + conversation.enqueue() + c.queue = append(c.queue, conversation) + c.notify() + + return nil +} + +func (c *Controller) sendFront() *Ydb_Coordination.SessionRequest { + c.mutex.Lock() + defer c.mutex.Unlock() + + // We are notified but there are no conversations in the queue. Return nil to make the loop in OnSend wait. + if len(c.queue) == 0 { + return nil + } + + for i := len(c.queue) - 1; i >= 0; i-- { + req := c.queue[i] + + if req.canceled && req.cancelRequestSent == nil { + req.sendCancel() + c.notify() + + return req.cancelRequestSent + } + + if req.requestSent != nil { + continue + } + + if _, ok := c.conflicts[req.conflictKey]; ok { + continue + } + + req.send() + + if req.conflictKey != "" { + c.conflicts[req.conflictKey] = struct{}{} + } + if req.responseFilter == nil && req.acknowledgeFilter == nil { + c.queue = append(c.queue[:i], c.queue[i+1:]...) + } + c.notify() + + return req.requestSent + } + + return nil +} + +// OnSend blocks until a new conversation request becomes available at the end of the queue. You should call this method +// in the goroutine that handles gRPC stream Send method. ctx can be used to cancel the call. +func (c *Controller) OnSend(ctx context.Context) (*Ydb_Coordination.SessionRequest, error) { + var req *Ydb_Coordination.SessionRequest + for { + select { + case <-ctx.Done(): + case <-c.notifyChan: + req = c.sendFront() + } + + // Process ctx.Done() first to make sure we cancel the call if conversations are too chatty. + if ctx.Err() != nil { + return nil, ctx.Err() + } + + // We were notified but there were no messages in the queue. Just wait for more messages become available. + if req != nil { + break + } + } + + return req, nil +} + +// OnRecv consumes a new conversation response and process with the corresponding conversation if any exists for it. The +// returned value indicates if any conversation considers the incoming message part of it or the controller is closed. +// You should call this method in the goroutine that handles gRPC stream Recv method. +func (c *Controller) OnRecv(resp *Ydb_Coordination.SessionResponse) bool { + c.mutex.Lock() + defer c.mutex.Unlock() + + notify := false + handled := false + for i := len(c.queue) - 1; i >= 0; i-- { + req := c.queue[i] + if req.requestSent == nil { + continue + } + + switch { + case req.responseFilter != nil && req.responseFilter(req.requestSent, resp): + if !req.canceled { + req.succeed(resp) + + if req.conflictKey != "" { + delete(c.conflicts, req.conflictKey) + notify = true + } + + c.queue = append(c.queue[:i], c.queue[i+1:]...) + } + + handled = true + case req.acknowledgeFilter != nil && req.acknowledgeFilter(req.requestSent, resp): + if !req.canceled { + if req.conflictKey != "" { + delete(c.conflicts, req.conflictKey) + notify = true + } + } + + handled = true + case req.cancelRequestSent != nil && req.cancelFilter(req.cancelRequestSent, resp): + if req.conflictKey != "" { + delete(c.conflicts, req.conflictKey) + notify = true + } + c.queue = append(c.queue[:i], c.queue[i+1:]...) + handled = true + } + } + + if notify { + c.notify() + } + + return c.closed || handled +} + +// OnDetach fails all non-idempotent conversations if there are any in the queue. You should call this method when the +// underlying gRPC stream of the session is closed. +func (c *Controller) OnDetach() { + c.mutex.Lock() + defer c.mutex.Unlock() + + for i := len(c.queue) - 1; i >= 0; i-- { + req := c.queue[i] + if !req.idempotent { + req.fail(coordination.ErrOperationStatusUnknown) + + if req.requestSent != nil && req.conflictKey != "" { + delete(c.conflicts, req.conflictKey) + } + + c.queue = append(c.queue[:i], c.queue[i+1:]...) + } + } +} + +// Close fails all conversations if there are any in the queue. It also does not allow pushing more conversations to the +// queue anymore. You may optionally specify the final conversation if needed. +func (c *Controller) Close(byeConversation *Conversation) { + c.mutex.Lock() + defer c.mutex.Unlock() + + c.closed = true + + for i := len(c.queue) - 1; i >= 0; i-- { + req := c.queue[i] + if !req.canceled { + req.fail(coordination.ErrSessionClosed) + } + } + + if byeConversation != nil { + byeConversation.enqueue() + c.queue = []*Conversation{byeConversation} + } + + c.notify() +} + +// OnAttach retries all idempotent conversations if there are any in the queue. You should call this method when the +// underlying gRPC stream of the session is connected. +func (c *Controller) OnAttach() { + c.mutex.Lock() + defer c.mutex.Unlock() + + notify := false + for i := len(c.queue) - 1; i >= 0; i-- { + req := c.queue[i] + if req.idempotent && req.requestSent != nil { + if req.conflictKey != "" { + delete(c.conflicts, req.conflictKey) + } + + req.requestSent = nil + notify = true + } + } + + if notify { + c.notify() + } +} + +// Cancel the conversation if it has been sent and there is no response ready. This returns false if the response is +// ready and the caller may safely return it instead of canceling the conversation. +func (c *Controller) cancel(conversation *Conversation) bool { + if conversation.cancelMessage == nil { + return true + } + + c.mutex.Lock() + defer c.mutex.Unlock() + + // The context is canceled but the response is ready, return it anyway. + if conversation.response != nil || conversation.responseErr != nil { + return false + } + + if conversation.requestSent != nil { + conversation.cancel() + c.notify() + } else { + // If the response has not been sent, just remove it from the queue. + for i := len(c.queue) - 1; i >= 0; i-- { + req := c.queue[i] + if req == conversation { + c.queue = append(c.queue[:i], c.queue[i+1:]...) + + break + } + } + } + + return true +} + +// Await waits until the conversation ends. ctx can be used to cancel the call. +func (c *Controller) Await( + ctx context.Context, + conversation *Conversation, +) (*Ydb_Coordination.SessionResponse, error) { + select { + case <-conversation.done: + case <-ctx.Done(): + } + + if ctx.Err() != nil && c.cancel(conversation) { + return nil, ctx.Err() + } + + if conversation.responseErr != nil { + return nil, conversation.responseErr + } + + return conversation.response, nil +} + +func (c *Conversation) enqueue() { + c.requestSent = nil + c.done = make(chan struct{}) +} + +func (c *Conversation) send() { + c.requestSent = c.message() +} + +func (c *Conversation) sendCancel() { + c.cancelRequestSent = c.cancelMessage(c.requestSent) +} + +func (c *Conversation) succeed(response *Ydb_Coordination.SessionResponse) { + c.response = response + close(c.done) +} + +func (c *Conversation) fail(err error) { + c.responseErr = err + close(c.done) +} + +func (c *Conversation) cancel() { + c.canceled = true + close(c.done) +} diff --git a/internal/coordination/grpc_client_mock_test.go b/internal/coordination/grpc_client_mock_test.go new file mode 100644 index 000000000..3c2d2a711 --- /dev/null +++ b/internal/coordination/grpc_client_mock_test.go @@ -0,0 +1,278 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/ydb-platform/ydb-go-genproto/Ydb_Coordination_V1 (interfaces: CoordinationServiceClient,CoordinationService_SessionClient) +// +// Generated by this command: +// +// mockgen -destination grpc_client_mock_test.go -package coordination -write_package_comment=false github.com/ydb-platform/ydb-go-genproto/Ydb_Coordination_V1 CoordinationServiceClient,CoordinationService_SessionClient +package coordination + +import ( + context "context" + reflect "reflect" + + Ydb_Coordination_V1 "github.com/ydb-platform/ydb-go-genproto/Ydb_Coordination_V1" + Ydb_Coordination "github.com/ydb-platform/ydb-go-genproto/protos/Ydb_Coordination" + gomock "go.uber.org/mock/gomock" + grpc "google.golang.org/grpc" + metadata "google.golang.org/grpc/metadata" +) + +// MockCoordinationServiceClient is a mock of CoordinationServiceClient interface. +type MockCoordinationServiceClient struct { + ctrl *gomock.Controller + recorder *MockCoordinationServiceClientMockRecorder +} + +// MockCoordinationServiceClientMockRecorder is the mock recorder for MockCoordinationServiceClient. +type MockCoordinationServiceClientMockRecorder struct { + mock *MockCoordinationServiceClient +} + +// NewMockCoordinationServiceClient creates a new mock instance. +func NewMockCoordinationServiceClient(ctrl *gomock.Controller) *MockCoordinationServiceClient { + mock := &MockCoordinationServiceClient{ctrl: ctrl} + mock.recorder = &MockCoordinationServiceClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockCoordinationServiceClient) EXPECT() *MockCoordinationServiceClientMockRecorder { + return m.recorder +} + +// AlterNode mocks base method. +func (m *MockCoordinationServiceClient) AlterNode(arg0 context.Context, arg1 *Ydb_Coordination.AlterNodeRequest, arg2 ...grpc.CallOption) (*Ydb_Coordination.AlterNodeResponse, error) { + m.ctrl.T.Helper() + varargs := []any{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "AlterNode", varargs...) + ret0, _ := ret[0].(*Ydb_Coordination.AlterNodeResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// AlterNode indicates an expected call of AlterNode. +func (mr *MockCoordinationServiceClientMockRecorder) AlterNode(arg0, arg1 any, arg2 ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AlterNode", reflect.TypeOf((*MockCoordinationServiceClient)(nil).AlterNode), varargs...) +} + +// CreateNode mocks base method. +func (m *MockCoordinationServiceClient) CreateNode(arg0 context.Context, arg1 *Ydb_Coordination.CreateNodeRequest, arg2 ...grpc.CallOption) (*Ydb_Coordination.CreateNodeResponse, error) { + m.ctrl.T.Helper() + varargs := []any{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "CreateNode", varargs...) + ret0, _ := ret[0].(*Ydb_Coordination.CreateNodeResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateNode indicates an expected call of CreateNode. +func (mr *MockCoordinationServiceClientMockRecorder) CreateNode(arg0, arg1 any, arg2 ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateNode", reflect.TypeOf((*MockCoordinationServiceClient)(nil).CreateNode), varargs...) +} + +// DescribeNode mocks base method. +func (m *MockCoordinationServiceClient) DescribeNode(arg0 context.Context, arg1 *Ydb_Coordination.DescribeNodeRequest, arg2 ...grpc.CallOption) (*Ydb_Coordination.DescribeNodeResponse, error) { + m.ctrl.T.Helper() + varargs := []any{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "DescribeNode", varargs...) + ret0, _ := ret[0].(*Ydb_Coordination.DescribeNodeResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DescribeNode indicates an expected call of DescribeNode. +func (mr *MockCoordinationServiceClientMockRecorder) DescribeNode(arg0, arg1 any, arg2 ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DescribeNode", reflect.TypeOf((*MockCoordinationServiceClient)(nil).DescribeNode), varargs...) +} + +// DropNode mocks base method. +func (m *MockCoordinationServiceClient) DropNode(arg0 context.Context, arg1 *Ydb_Coordination.DropNodeRequest, arg2 ...grpc.CallOption) (*Ydb_Coordination.DropNodeResponse, error) { + m.ctrl.T.Helper() + varargs := []any{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "DropNode", varargs...) + ret0, _ := ret[0].(*Ydb_Coordination.DropNodeResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DropNode indicates an expected call of DropNode. +func (mr *MockCoordinationServiceClientMockRecorder) DropNode(arg0, arg1 any, arg2 ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DropNode", reflect.TypeOf((*MockCoordinationServiceClient)(nil).DropNode), varargs...) +} + +// Session mocks base method. +func (m *MockCoordinationServiceClient) Session(arg0 context.Context, arg1 ...grpc.CallOption) (Ydb_Coordination_V1.CoordinationService_SessionClient, error) { + m.ctrl.T.Helper() + varargs := []any{arg0} + for _, a := range arg1 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Session", varargs...) + ret0, _ := ret[0].(Ydb_Coordination_V1.CoordinationService_SessionClient) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Session indicates an expected call of Session. +func (mr *MockCoordinationServiceClientMockRecorder) Session(arg0 any, arg1 ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{arg0}, arg1...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Session", reflect.TypeOf((*MockCoordinationServiceClient)(nil).Session), varargs...) +} + +// MockCoordinationService_SessionClient is a mock of CoordinationService_SessionClient interface. +type MockCoordinationService_SessionClient struct { + ctrl *gomock.Controller + recorder *MockCoordinationService_SessionClientMockRecorder +} + +// MockCoordinationService_SessionClientMockRecorder is the mock recorder for MockCoordinationService_SessionClient. +type MockCoordinationService_SessionClientMockRecorder struct { + mock *MockCoordinationService_SessionClient +} + +// NewMockCoordinationService_SessionClient creates a new mock instance. +func NewMockCoordinationService_SessionClient(ctrl *gomock.Controller) *MockCoordinationService_SessionClient { + mock := &MockCoordinationService_SessionClient{ctrl: ctrl} + mock.recorder = &MockCoordinationService_SessionClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockCoordinationService_SessionClient) EXPECT() *MockCoordinationService_SessionClientMockRecorder { + return m.recorder +} + +// CloseSend mocks base method. +func (m *MockCoordinationService_SessionClient) CloseSend() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CloseSend") + ret0, _ := ret[0].(error) + return ret0 +} + +// CloseSend indicates an expected call of CloseSend. +func (mr *MockCoordinationService_SessionClientMockRecorder) CloseSend() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CloseSend", reflect.TypeOf((*MockCoordinationService_SessionClient)(nil).CloseSend)) +} + +// Context mocks base method. +func (m *MockCoordinationService_SessionClient) Context() context.Context { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Context") + ret0, _ := ret[0].(context.Context) + return ret0 +} + +// Context indicates an expected call of Context. +func (mr *MockCoordinationService_SessionClientMockRecorder) Context() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Context", reflect.TypeOf((*MockCoordinationService_SessionClient)(nil).Context)) +} + +// Header mocks base method. +func (m *MockCoordinationService_SessionClient) Header() (metadata.MD, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Header") + ret0, _ := ret[0].(metadata.MD) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Header indicates an expected call of Header. +func (mr *MockCoordinationService_SessionClientMockRecorder) Header() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Header", reflect.TypeOf((*MockCoordinationService_SessionClient)(nil).Header)) +} + +// Recv mocks base method. +func (m *MockCoordinationService_SessionClient) Recv() (*Ydb_Coordination.SessionResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Recv") + ret0, _ := ret[0].(*Ydb_Coordination.SessionResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Recv indicates an expected call of Recv. +func (mr *MockCoordinationService_SessionClientMockRecorder) Recv() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Recv", reflect.TypeOf((*MockCoordinationService_SessionClient)(nil).Recv)) +} + +// RecvMsg mocks base method. +func (m *MockCoordinationService_SessionClient) RecvMsg(arg0 any) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RecvMsg", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// RecvMsg indicates an expected call of RecvMsg. +func (mr *MockCoordinationService_SessionClientMockRecorder) RecvMsg(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecvMsg", reflect.TypeOf((*MockCoordinationService_SessionClient)(nil).RecvMsg), arg0) +} + +// Send mocks base method. +func (m *MockCoordinationService_SessionClient) Send(arg0 *Ydb_Coordination.SessionRequest) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Send", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// Send indicates an expected call of Send. +func (mr *MockCoordinationService_SessionClientMockRecorder) Send(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Send", reflect.TypeOf((*MockCoordinationService_SessionClient)(nil).Send), arg0) +} + +// SendMsg mocks base method. +func (m *MockCoordinationService_SessionClient) SendMsg(arg0 any) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SendMsg", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// SendMsg indicates an expected call of SendMsg. +func (mr *MockCoordinationService_SessionClientMockRecorder) SendMsg(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendMsg", reflect.TypeOf((*MockCoordinationService_SessionClient)(nil).SendMsg), arg0) +} + +// Trailer mocks base method. +func (m *MockCoordinationService_SessionClient) Trailer() metadata.MD { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Trailer") + ret0, _ := ret[0].(metadata.MD) + return ret0 +} + +// Trailer indicates an expected call of Trailer. +func (mr *MockCoordinationService_SessionClientMockRecorder) Trailer() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Trailer", reflect.TypeOf((*MockCoordinationService_SessionClient)(nil).Trailer)) +} diff --git a/internal/coordination/session.go b/internal/coordination/session.go new file mode 100644 index 000000000..9e7e2bb23 --- /dev/null +++ b/internal/coordination/session.go @@ -0,0 +1,891 @@ +package coordination + +import ( + "context" + "encoding/binary" + "math" + "math/rand" + "sync" + "time" + + "github.com/ydb-platform/ydb-go-genproto/Ydb_Coordination_V1" + "github.com/ydb-platform/ydb-go-genproto/protos/Ydb" + "github.com/ydb-platform/ydb-go-genproto/protos/Ydb_Coordination" + + "github.com/ydb-platform/ydb-go-sdk/v3/coordination" + "github.com/ydb-platform/ydb-go-sdk/v3/coordination/options" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/coordination/conversation" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/xcontext" + "github.com/ydb-platform/ydb-go-sdk/v3/trace" +) + +type session struct { + options *options.CreateSessionOptions + client *Client + + ctx context.Context + cancel context.CancelFunc + sessionClosedChan chan struct{} + controller *conversation.Controller + sessionID uint64 + + mutex sync.Mutex // guards the field below + lastGoodResponseTime time.Time + cancelStream context.CancelFunc +} + +type lease struct { + session *session + name string + ctx context.Context + cancel context.CancelFunc +} + +func createSession( + ctx context.Context, + client *Client, + path string, + opts *options.CreateSessionOptions, +) (*session, error) { + sessionCtx, cancel := xcontext.WithCancel(xcontext.ValueOnly(ctx)) + s := session{ + options: opts, + client: client, + ctx: sessionCtx, + cancel: cancel, + sessionClosedChan: make(chan struct{}), + controller: conversation.NewController(), + } + client.sessionCreated(&s) + + sessionStartedChan := make(chan struct{}) + go s.mainLoop(path, sessionStartedChan) + + select { + case <-ctx.Done(): + cancel() + + return nil, ctx.Err() + case <-sessionStartedChan: + } + + return &s, nil +} + +func newProtectionKey() []byte { + key := make([]byte, 8) //nolint:gomnd + binary.LittleEndian.PutUint64(key, rand.Uint64()) //nolint:gosec + + return key +} + +func newReqID() uint64 { + return rand.Uint64() //nolint:gosec +} + +func (s *session) updateLastGoodResponseTime() { + s.mutex.Lock() + defer s.mutex.Unlock() + + now := time.Now() + + if now.After(s.lastGoodResponseTime) { + s.lastGoodResponseTime = now + } +} + +func (s *session) getLastGoodResponseTime() time.Time { + s.mutex.Lock() + defer s.mutex.Unlock() + + return s.lastGoodResponseTime +} + +func (s *session) updateCancelStream(cancel context.CancelFunc) { + s.mutex.Lock() + defer s.mutex.Unlock() + + s.cancelStream = cancel +} + +// Create a new gRPC stream using an independent context. +func (s *session) newStream( + streamCtx context.Context, + cancelStream context.CancelFunc, +) (Ydb_Coordination_V1.CoordinationService_SessionClient, error) { + // This deadline if final. If we have not got a session before it, the session is either expired or has never been + // created. + var deadline time.Time + if s.sessionID != 0 { + deadline = s.getLastGoodResponseTime().Add(s.options.SessionTimeout) + } else { + // Large enough to make the loop infinite, small enough to allow the maximum duration value (~290 years). + deadline = time.Now().Add(time.Hour * 24 * 365 * 100) //nolint:gomnd + } + + lastChance := false + for { + result := make(chan Ydb_Coordination_V1.CoordinationService_SessionClient, 1) + go func() { + var err error + onDone := trace.CoordinationOnStreamNew(s.client.config.Trace()) + defer func() { + onDone(err) + }() + + client, err := s.client.client.Session(streamCtx) + result <- client + }() + + var client Ydb_Coordination_V1.CoordinationService_SessionClient + if lastChance { + timer := time.NewTimer(s.options.SessionKeepAliveTimeout) + select { + case <-timer.C: + case client = <-result: + } + timer.Stop() + + if client != nil { + return client, nil + } + + cancelStream() + + return nil, s.ctx.Err() + } + + // Since the deadline is probably large enough, avoid the timer leak with time.After. + timer := time.NewTimer(time.Until(deadline)) + select { + case <-s.ctx.Done(): + case client = <-result: + case <-timer.C: + trace.CoordinationOnSessionClientTimeout( + s.client.config.Trace(), + s.getLastGoodResponseTime(), + s.options.SessionTimeout, + ) + cancelStream() + + return nil, coordination.ErrSessionClosed + } + timer.Stop() + + if client != nil { + return client, nil + } + + // Waiting for some time before trying to reconnect. + sessionReconnectDelay := time.NewTimer(s.options.SessionReconnectDelay) + select { + case <-sessionReconnectDelay.C: + case <-s.ctx.Done(): + } + sessionReconnectDelay.Stop() + + if s.ctx.Err() != nil { + // Give this session the last chance to stop gracefully if the session is canceled in the reconnect cycle. + if s.sessionID != 0 { + lastChance = true + } else { + cancelStream() + + return nil, s.ctx.Err() + } + } + } +} + +func (s *session) mainLoop(path string, sessionStartedChan chan struct{}) { + defer s.client.sessionClosed(s) + defer close(s.sessionClosedChan) + defer s.cancel() + + var seqNo uint64 + + protectionKey := newProtectionKey() + closing := false + + for { + // Create a new grpc stream and start the receiver and sender loops. + // + // We use the stream context as a way to inform the main loop that the session must be reconnected if an + // unrecoverable error occurs in the receiver or sender loop. This also helps stop the other loop if an error + // is caught on only one of them. + // + // We intentionally place a stream context outside the scope of any existing contexts to make an attempt to + // close the session gracefully at the end of the main loop. + + streamCtx, cancelStream := context.WithCancel(context.Background()) + sessionClient, err := s.newStream(streamCtx, cancelStream) + if err != nil { + // Giving up, we can do nothing without a stream. + s.controller.Close(nil) + + return + } + + s.updateCancelStream(cancelStream) + + // Start the loops. + wg := sync.WaitGroup{} + wg.Add(2) //nolint:gomnd + sessionStarted := make(chan *Ydb_Coordination.SessionResponse_SessionStarted, 1) + sessionStopped := make(chan *Ydb_Coordination.SessionResponse_SessionStopped, 1) + startSending := make(chan struct{}) + s.controller.OnAttach() + + go s.receiveLoop(&wg, sessionClient, cancelStream, sessionStarted, sessionStopped) + go s.sendLoop( + &wg, + sessionClient, + streamCtx, + cancelStream, + startSending, + path, + protectionKey, + s.sessionID, + seqNo, + ) + + // Wait for the session started response unless the stream context is done. We intentionally do not take into + // account stream context cancellation in order to proceed with the graceful shutdown if it requires reconnect. + sessionStartTimer := time.NewTimer(s.options.SessionStartTimeout) + select { + case start := <-sessionStarted: + trace.CoordinationOnSessionStarted(s.client.config.Trace(), start.GetSessionId(), s.sessionID) + if s.sessionID == 0 { + s.sessionID = start.GetSessionId() + close(sessionStartedChan) + } else if start.GetSessionId() != s.sessionID { + // Reconnect if the server response is invalid. + cancelStream() + } + close(startSending) + case <-sessionStartTimer.C: + // Reconnect if no response was received before the timeout occurred. + trace.CoordinationOnSessionStartTimeout(s.client.config.Trace(), s.options.SessionStartTimeout) + cancelStream() + case <-streamCtx.Done(): + case <-s.ctx.Done(): + } + sessionStartTimer.Stop() + + for { + // Respect the failure reason priority: if the session context is done, we must stop the session, even + // though the stream context may also be canceled. + if s.ctx.Err() != nil { + closing = true + + break + } + if streamCtx.Err() != nil { + // Reconnect if an error occurred during the start session conversation. + break + } + + keepAliveTime := time.Until(s.getLastGoodResponseTime().Add(s.options.SessionKeepAliveTimeout)) + keepAliveTimeTimer := time.NewTimer(keepAliveTime) + select { + case <-keepAliveTimeTimer.C: + last := s.getLastGoodResponseTime() + if time.Since(last) > s.options.SessionKeepAliveTimeout { + // Reconnect if the underlying stream is likely to be dead. + trace.CoordinationOnSessionKeepAliveTimeout( + s.client.config.Trace(), + last, + s.options.SessionKeepAliveTimeout, + ) + cancelStream() + } + case <-streamCtx.Done(): + case <-s.ctx.Done(): + } + keepAliveTimeTimer.Stop() + } + + if closing { + // No need to stop the session if it was not started. + if s.sessionID == 0 { + s.controller.Close(nil) + cancelStream() + + return + } + + trace.CoordinationOnSessionStop(s.client.config.Trace(), s.sessionID) + s.controller.Close(conversation.NewConversation( + func() *Ydb_Coordination.SessionRequest { + return &Ydb_Coordination.SessionRequest{ + Request: &Ydb_Coordination.SessionRequest_SessionStop_{ + SessionStop: &Ydb_Coordination.SessionRequest_SessionStop{}, + }, + } + }), + ) + + // Wait for the session stopped response unless the stream context is done. + sessionStopTimeout := time.NewTimer(s.options.SessionStopTimeout) + select { + case stop := <-sessionStopped: + sessionStopTimeout.Stop() + trace.CoordinationOnSessionStopped(s.client.config.Trace(), stop.GetSessionId(), s.sessionID) + if stop.GetSessionId() == s.sessionID { + cancelStream() + + return + } + + // Reconnect if the server response is invalid. + cancelStream() + case <-sessionStopTimeout.C: + sessionStopTimeout.Stop() // no really need, call stop for common style only + + // Reconnect if no response was received before the timeout occurred. + trace.CoordinationOnSessionStopTimeout(s.client.config.Trace(), s.options.SessionStopTimeout) + cancelStream() + case <-s.ctx.Done(): + sessionStopTimeout.Stop() + cancelStream() + + return + case <-streamCtx.Done(): + sessionStopTimeout.Stop() + } + } + + // Make sure no one is processing the stream anymore. + wg.Wait() + + s.controller.OnDetach() + seqNo++ + } +} + +func (s *session) receiveLoop( + wg *sync.WaitGroup, + sessionClient Ydb_Coordination_V1.CoordinationService_SessionClient, + cancelStream context.CancelFunc, + sessionStarted chan *Ydb_Coordination.SessionResponse_SessionStarted, + sessionStopped chan *Ydb_Coordination.SessionResponse_SessionStopped, +) { + // If the sendLoop is done, make sure the stream is also canceled to make the receiveLoop finish its work and cause + // reconnect. + defer wg.Done() + defer cancelStream() + + for { + onDone := trace.CoordinationOnSessionReceive(s.client.config.Trace()) + message, err := sessionClient.Recv() + if err != nil { + // Any stream error is unrecoverable, try to reconnect. + onDone(nil, err) + + return + } + onDone(message, nil) + + switch message.GetResponse().(type) { + case *Ydb_Coordination.SessionResponse_Failure_: + if message.GetFailure().GetStatus() == Ydb.StatusIds_SESSION_EXPIRED || + message.GetFailure().GetStatus() == Ydb.StatusIds_UNAUTHORIZED || + message.GetFailure().GetStatus() == Ydb.StatusIds_NOT_FOUND { + // Consider the session expired if we got an unrecoverable status. + trace.CoordinationOnSessionServerExpire(s.client.config.Trace(), message.GetFailure()) + + return + } + + trace.CoordinationOnSessionServerError(s.client.config.Trace(), message.GetFailure()) + + return + case *Ydb_Coordination.SessionResponse_SessionStarted_: + sessionStarted <- message.GetSessionStarted() + s.updateLastGoodResponseTime() + case *Ydb_Coordination.SessionResponse_SessionStopped_: + sessionStopped <- message.GetSessionStopped() + s.cancel() + + return + case *Ydb_Coordination.SessionResponse_Ping: + opaque := message.GetPing().GetOpaque() + err := s.controller.PushFront(conversation.NewConversation( + func() *Ydb_Coordination.SessionRequest { + return &Ydb_Coordination.SessionRequest{ + Request: &Ydb_Coordination.SessionRequest_Pong{ + Pong: &Ydb_Coordination.SessionRequest_PingPong{ + Opaque: opaque, + }, + }, + } + }), + ) + if err != nil { + // The session is closed if we cannot send the pong request back, so just exit the loop. + return + } + s.updateLastGoodResponseTime() + case *Ydb_Coordination.SessionResponse_Pong: + // Ignore pongs since we do not ping the server. + default: + if !s.controller.OnRecv(message) { + // Reconnect if the message is not from any known conversation. + trace.CoordinationOnSessionReceiveUnexpected(s.client.config.Trace(), message) + + return + } + + s.updateLastGoodResponseTime() + } + } +} + +//nolint:revive +func (s *session) sendLoop( + wg *sync.WaitGroup, + sessionClient Ydb_Coordination_V1.CoordinationService_SessionClient, + streamCtx context.Context, + cancelStream context.CancelFunc, + startSending chan struct{}, + path string, + protectionKey []byte, + sessionID uint64, + seqNo uint64, +) { + // If the sendLoop is done, make sure the stream is also canceled to make the receiveLoop finish its work and cause + // reconnect. + defer wg.Done() + defer cancelStream() + + // Start a new session. + onDone := trace.CoordinationOnSessionStart(s.client.config.Trace()) + startSession := Ydb_Coordination.SessionRequest{ + Request: &Ydb_Coordination.SessionRequest_SessionStart_{ + SessionStart: &Ydb_Coordination.SessionRequest_SessionStart{ + Path: path, + SessionId: sessionID, + TimeoutMillis: uint64(s.options.SessionTimeout.Milliseconds()), + ProtectionKey: protectionKey, + SeqNo: seqNo, + Description: s.options.Description, + }, + }, + } + err := sessionClient.Send(&startSession) + if err != nil { + // Reconnect if a session cannot be started in this stream. + onDone(err) + + return + } + onDone(nil) + + // Wait for a response to the session start request in order to carry over the accumulated conversations until the + // server confirms that the session is running. This is not absolutely necessary but helps the client to not fail + // non-idempotent requests in case of the session handshake errors. + select { + case <-streamCtx.Done(): + case <-startSending: + } + + for { + message, err := s.controller.OnSend(streamCtx) + if err != nil { + return + } + + onSendDone := trace.CoordinationOnSessionSend(s.client.config.Trace(), message) + err = sessionClient.Send(message) + if err != nil { + // Any stream error is unrecoverable, try to reconnect. + onSendDone(err) + + return + } + onSendDone(nil) + } +} + +func (s *session) Context() context.Context { + return s.ctx +} + +func (s *session) Close(ctx context.Context) error { + s.cancel() + + select { + case <-s.sessionClosedChan: + case <-ctx.Done(): + return ctx.Err() + } + + return nil +} + +func (s *session) Reconnect() { + s.mutex.Lock() + defer s.mutex.Unlock() + + if s.cancelStream != nil { + s.cancelStream() + } +} + +func (s *session) SessionID() uint64 { + return s.sessionID +} + +func (s *session) CreateSemaphore( + ctx context.Context, + name string, + limit uint64, + opts ...options.CreateSemaphoreOption, +) error { + req := conversation.NewConversation( + func() *Ydb_Coordination.SessionRequest { + createSemaphore := Ydb_Coordination.SessionRequest_CreateSemaphore{ + ReqId: newReqID(), + Name: name, + Limit: limit, + } + for _, o := range opts { + if o != nil { + o(&createSemaphore) + } + } + + return &Ydb_Coordination.SessionRequest{ + Request: &Ydb_Coordination.SessionRequest_CreateSemaphore_{ + CreateSemaphore: &createSemaphore, + }, + } + }, + conversation.WithResponseFilter(func( + request *Ydb_Coordination.SessionRequest, + response *Ydb_Coordination.SessionResponse, + ) bool { + return response.GetCreateSemaphoreResult().GetReqId() == request.GetCreateSemaphore().GetReqId() + }), + ) + if err := s.controller.PushBack(req); err != nil { + return err + } + + _, err := s.controller.Await(ctx, req) + if err != nil { + return err + } + + return nil +} + +func (s *session) UpdateSemaphore( + ctx context.Context, + name string, + opts ...options.UpdateSemaphoreOption, +) error { + req := conversation.NewConversation( + func() *Ydb_Coordination.SessionRequest { + updateSemaphore := Ydb_Coordination.SessionRequest_UpdateSemaphore{ + ReqId: newReqID(), + Name: name, + } + for _, o := range opts { + if o != nil { + o(&updateSemaphore) + } + } + + return &Ydb_Coordination.SessionRequest{ + Request: &Ydb_Coordination.SessionRequest_UpdateSemaphore_{ + UpdateSemaphore: &updateSemaphore, + }, + } + }, + conversation.WithResponseFilter(func( + request *Ydb_Coordination.SessionRequest, + response *Ydb_Coordination.SessionResponse, + ) bool { + return response.GetUpdateSemaphoreResult().GetReqId() == request.GetUpdateSemaphore().GetReqId() + }), + conversation.WithConflictKey(name), + conversation.WithIdempotence(true), + ) + if err := s.controller.PushBack(req); err != nil { + return err + } + + _, err := s.controller.Await(ctx, req) + if err != nil { + return err + } + + return nil +} + +func (s *session) DeleteSemaphore( + ctx context.Context, + name string, + opts ...options.DeleteSemaphoreOption, +) error { + req := conversation.NewConversation( + func() *Ydb_Coordination.SessionRequest { + deleteSemaphore := Ydb_Coordination.SessionRequest_DeleteSemaphore{ + ReqId: newReqID(), + Name: name, + } + for _, o := range opts { + if o != nil { + o(&deleteSemaphore) + } + } + + return &Ydb_Coordination.SessionRequest{ + Request: &Ydb_Coordination.SessionRequest_DeleteSemaphore_{ + DeleteSemaphore: &deleteSemaphore, + }, + } + }, + conversation.WithResponseFilter(func( + request *Ydb_Coordination.SessionRequest, + response *Ydb_Coordination.SessionResponse, + ) bool { + return response.GetDeleteSemaphoreResult().GetReqId() == request.GetDeleteSemaphore().GetReqId() + }), + conversation.WithConflictKey(name), + ) + if err := s.controller.PushBack(req); err != nil { + return err + } + + _, err := s.controller.Await(ctx, req) + if err != nil { + return err + } + + return nil +} + +func (s *session) DescribeSemaphore( + ctx context.Context, + name string, + opts ...options.DescribeSemaphoreOption, +) (*coordination.SemaphoreDescription, error) { + req := conversation.NewConversation( + func() *Ydb_Coordination.SessionRequest { + describeSemaphore := Ydb_Coordination.SessionRequest_DescribeSemaphore{ + ReqId: newReqID(), + Name: name, + } + for _, o := range opts { + if o != nil { + o(&describeSemaphore) + } + } + + return &Ydb_Coordination.SessionRequest{ + Request: &Ydb_Coordination.SessionRequest_DescribeSemaphore_{ + DescribeSemaphore: &describeSemaphore, + }, + } + }, + conversation.WithResponseFilter(func( + request *Ydb_Coordination.SessionRequest, + response *Ydb_Coordination.SessionResponse, + ) bool { + return response.GetDescribeSemaphoreResult().GetReqId() == request.GetDescribeSemaphore().GetReqId() + }), + conversation.WithConflictKey(name), + conversation.WithIdempotence(true), + ) + if err := s.controller.PushBack(req); err != nil { + return nil, err + } + + resp, err := s.controller.Await(ctx, req) + if err != nil { + return nil, err + } + + return convertSemaphoreDescription(resp.GetDescribeSemaphoreResult().GetSemaphoreDescription()), nil +} + +func convertSemaphoreDescription( + desc *Ydb_Coordination.SemaphoreDescription, +) *coordination.SemaphoreDescription { + var result coordination.SemaphoreDescription + + if desc != nil { + result.Name = desc.GetName() + result.Limit = desc.GetLimit() + result.Ephemeral = desc.GetEphemeral() + result.Count = desc.GetCount() + result.Data = desc.GetData() + result.Owners = convertSemaphoreSessions(desc.GetOwners()) + result.Waiters = convertSemaphoreSessions(desc.GetWaiters()) + } + + return &result +} + +func convertSemaphoreSessions( + sessions []*Ydb_Coordination.SemaphoreSession, +) []*coordination.SemaphoreSession { + if sessions == nil { + return nil + } + + result := make([]*coordination.SemaphoreSession, len(sessions)) + for i, s := range sessions { + result[i] = convertSemaphoreSession(s) + } + + return result +} + +func convertSemaphoreSession( + session *Ydb_Coordination.SemaphoreSession, +) *coordination.SemaphoreSession { + var result coordination.SemaphoreSession + + if session != nil { + result.SessionID = session.GetSessionId() + result.Count = session.GetCount() + result.OrderID = session.GetOrderId() + result.Data = session.GetData() + if session.GetTimeoutMillis() == math.MaxUint64 { + result.Timeout = time.Duration(math.MaxInt64) + } else { + // The service does not allow big timeout values, so the conversion seems to be safe. + result.Timeout = time.Duration(session.GetTimeoutMillis()) * time.Millisecond + } + } + + return &result +} + +func (s *session) AcquireSemaphore( + ctx context.Context, + name string, + count uint64, + opts ...options.AcquireSemaphoreOption, +) (coordination.Lease, error) { + req := conversation.NewConversation( + func() *Ydb_Coordination.SessionRequest { + acquireSemaphore := Ydb_Coordination.SessionRequest_AcquireSemaphore{ + ReqId: newReqID(), + Name: name, + Count: count, + TimeoutMillis: math.MaxUint64, + } + for _, o := range opts { + if o != nil { + o(&acquireSemaphore) + } + } + + return &Ydb_Coordination.SessionRequest{ + Request: &Ydb_Coordination.SessionRequest_AcquireSemaphore_{ + AcquireSemaphore: &acquireSemaphore, + }, + } + }, + conversation.WithResponseFilter(func( + request *Ydb_Coordination.SessionRequest, + response *Ydb_Coordination.SessionResponse, + ) bool { + return response.GetAcquireSemaphoreResult().GetReqId() == request.GetAcquireSemaphore().GetReqId() + }), + conversation.WithAcknowledgeFilter(func( + request *Ydb_Coordination.SessionRequest, + response *Ydb_Coordination.SessionResponse, + ) bool { + return response.GetAcquireSemaphorePending().GetReqId() == request.GetAcquireSemaphore().GetReqId() + }), + conversation.WithCancelMessage( + func(request *Ydb_Coordination.SessionRequest) *Ydb_Coordination.SessionRequest { + return &Ydb_Coordination.SessionRequest{ + Request: &Ydb_Coordination.SessionRequest_ReleaseSemaphore_{ + ReleaseSemaphore: &Ydb_Coordination.SessionRequest_ReleaseSemaphore{ + Name: name, + ReqId: newReqID(), + }, + }, + } + }, + func( + request *Ydb_Coordination.SessionRequest, + response *Ydb_Coordination.SessionResponse, + ) bool { + return response.GetReleaseSemaphoreResult().GetReqId() == request.GetReleaseSemaphore().GetReqId() + }, + ), + conversation.WithConflictKey(name), + conversation.WithIdempotence(true), + ) + if err := s.controller.PushBack(req); err != nil { + return nil, err + } + + resp, err := s.controller.Await(ctx, req) + if err != nil { + return nil, err + } + + if !resp.GetAcquireSemaphoreResult().GetAcquired() { + return nil, coordination.ErrAcquireTimeout + } + + ctx, cancel := context.WithCancel(s.ctx) + + return &lease{ + session: s, + name: name, + ctx: ctx, + cancel: cancel, + }, nil +} + +func (l *lease) Context() context.Context { + return l.ctx +} + +func (l *lease) Release() error { + req := conversation.NewConversation( + func() *Ydb_Coordination.SessionRequest { + return &Ydb_Coordination.SessionRequest{ + Request: &Ydb_Coordination.SessionRequest_ReleaseSemaphore_{ + ReleaseSemaphore: &Ydb_Coordination.SessionRequest_ReleaseSemaphore{ + ReqId: newReqID(), + Name: l.name, + }, + }, + } + }, + conversation.WithResponseFilter(func( + request *Ydb_Coordination.SessionRequest, + response *Ydb_Coordination.SessionResponse, + ) bool { + return response.GetReleaseSemaphoreResult().GetReqId() == request.GetReleaseSemaphore().GetReqId() + }), + conversation.WithConflictKey(l.name), + conversation.WithIdempotence(true), + ) + if err := l.session.controller.PushBack(req); err != nil { + return err + } + + _, err := l.session.controller.Await(l.session.ctx, req) + if err != nil { + return err + } + + l.cancel() + + return nil +} + +func (l *lease) Session() coordination.Session { + return l.session +} diff --git a/internal/credentials/oauth2.go b/internal/credentials/oauth2.go new file mode 100644 index 000000000..f49826127 --- /dev/null +++ b/internal/credentials/oauth2.go @@ -0,0 +1,846 @@ +package credentials + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "os" + "os/user" + "path/filepath" + "strconv" + "strings" + "sync" + "sync/atomic" + "time" + + "github.com/golang-jwt/jwt/v4" + + "github.com/ydb-platform/ydb-go-sdk/v3/internal/secret" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/stack" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/xstring" +) + +const ( + defaultRequestTimeout = time.Second * 10 + defaultJWTTokenTTL = 3600 * time.Second + updateTimeDivider = 2 +) + +var ( + errEmptyTokenEndpointError = errors.New("OAuth2 token exchange: empty token endpoint") + errCouldNotParseResponse = errors.New("OAuth2 token exchange: could not parse response") + errCouldNotExchangeToken = errors.New("OAuth2 token exchange: could not exchange token") + errUnsupportedTokenType = errors.New("OAuth2 token exchange: unsupported token type") + errIncorrectExpirationTime = errors.New("OAuth2 token exchange: incorrect expiration time") + errDifferentScope = errors.New("OAuth2 token exchange: got different scope") + errCouldNotMakeHTTPRequest = errors.New("OAuth2 token exchange: could not make http request") + errCouldNotApplyOption = errors.New("OAuth2 token exchange: could not apply option") + errCouldNotCreateTokenSource = errors.New("OAuth2 token exchange: could not createTokenSource") + errNoSigningMethodError = errors.New("JWT token source: no signing method") + errNoPrivateKeyError = errors.New("JWT token source: no private key") + errCouldNotSignJWTToken = errors.New("JWT token source: could not sign jwt token") + errCouldNotApplyJWTOption = errors.New("JWT token source: could not apply option") + errCouldNotparseRSAPrivateKey = errors.New("JWT token source: could not parse RSA private key from PEM") + errCouldNotParseHomeDir = errors.New("JWT token source: could not parse home dir for private key") + errCouldNotReadPrivateKeyFile = errors.New("JWT token source: could not read from private key file") +) + +type Oauth2TokenExchangeCredentialsOption interface { + ApplyOauth2CredentialsOption(c *oauth2TokenExchange) error +} + +// TokenEndpoint +type tokenEndpointOption string + +func (endpoint tokenEndpointOption) ApplyOauth2CredentialsOption(c *oauth2TokenExchange) error { + c.tokenEndpoint = string(endpoint) + + return nil +} + +func WithTokenEndpoint(endpoint string) tokenEndpointOption { + return tokenEndpointOption(endpoint) +} + +// GrantType +type grantTypeOption string + +func (grantType grantTypeOption) ApplyOauth2CredentialsOption(c *oauth2TokenExchange) error { + c.grantType = string(grantType) + + return nil +} + +func WithGrantType(grantType string) grantTypeOption { + return grantTypeOption(grantType) +} + +// Resource +type resourceOption string + +func (resource resourceOption) ApplyOauth2CredentialsOption(c *oauth2TokenExchange) error { + c.resource = string(resource) + + return nil +} + +func WithResource(resource string) resourceOption { + return resourceOption(resource) +} + +// RequestedTokenType +type requestedTokenTypeOption string + +func (requestedTokenType requestedTokenTypeOption) ApplyOauth2CredentialsOption(c *oauth2TokenExchange) error { + c.requestedTokenType = string(requestedTokenType) + + return nil +} + +func WithRequestedTokenType(requestedTokenType string) requestedTokenTypeOption { + return requestedTokenTypeOption(requestedTokenType) +} + +// Audience +type audienceOption []string + +func (audience audienceOption) ApplyOauth2CredentialsOption(c *oauth2TokenExchange) error { + c.audience = audience + + return nil +} + +func WithAudience(audience ...string) audienceOption { + return audience +} + +// Scope +type scopeOption []string + +func (scope scopeOption) ApplyOauth2CredentialsOption(c *oauth2TokenExchange) error { + c.scope = scope + + return nil +} + +func WithScope(scope ...string) scopeOption { + return scope +} + +// RequestTimeout +type requestTimeoutOption time.Duration + +func (timeout requestTimeoutOption) ApplyOauth2CredentialsOption(c *oauth2TokenExchange) error { + c.requestTimeout = time.Duration(timeout) + + return nil +} + +func WithRequestTimeout(timeout time.Duration) requestTimeoutOption { + return requestTimeoutOption(timeout) +} + +const ( + SubjectTokenSourceType = 1 + ActorTokenSourceType = 2 +) + +// SubjectTokenSource/ActorTokenSource +type tokenSourceOption struct { + source TokenSource + createFunc func() (TokenSource, error) + tokenSourceType int +} + +func (tokenSource *tokenSourceOption) ApplyOauth2CredentialsOption(c *oauth2TokenExchange) error { + src := tokenSource.source + var err error + if src == nil { + src, err = tokenSource.createFunc() + if err != nil { + return xerrors.WithStackTrace(fmt.Errorf("%w: %w", errCouldNotCreateTokenSource, err)) + } + } + switch tokenSource.tokenSourceType { + case SubjectTokenSourceType: + c.subjectTokenSource = src + case ActorTokenSourceType: + c.actorTokenSource = src + } + + return nil +} + +func WithSubjectToken(subjectToken TokenSource) *tokenSourceOption { + return &tokenSourceOption{ + source: subjectToken, + tokenSourceType: SubjectTokenSourceType, + } +} + +func WithFixedSubjectToken(token, tokenType string) *tokenSourceOption { + return &tokenSourceOption{ + createFunc: func() (TokenSource, error) { + return NewFixedTokenSource(token, tokenType), nil + }, + tokenSourceType: SubjectTokenSourceType, + } +} + +func WithJWTSubjectToken(opts ...JWTTokenSourceOption) *tokenSourceOption { + return &tokenSourceOption{ + createFunc: func() (TokenSource, error) { + return NewJWTTokenSource(opts...) + }, + tokenSourceType: SubjectTokenSourceType, + } +} + +// ActorTokenSource +func WithActorToken(actorToken TokenSource) *tokenSourceOption { + return &tokenSourceOption{ + source: actorToken, + tokenSourceType: ActorTokenSourceType, + } +} + +func WithFixedActorToken(token, tokenType string) *tokenSourceOption { + return &tokenSourceOption{ + createFunc: func() (TokenSource, error) { + return NewFixedTokenSource(token, tokenType), nil + }, + tokenSourceType: ActorTokenSourceType, + } +} + +func WithJWTActorToken(opts ...JWTTokenSourceOption) *tokenSourceOption { + return &tokenSourceOption{ + createFunc: func() (TokenSource, error) { + return NewJWTTokenSource(opts...) + }, + tokenSourceType: ActorTokenSourceType, + } +} + +type oauth2TokenExchange struct { + tokenEndpoint string + + // grant_type parameter + // urn:ietf:params:oauth:grant-type:token-exchange by default + grantType string + + resource string + audience []string + scope []string + + // requested_token_type parameter + // urn:ietf:params:oauth:token-type:access_token by default + requestedTokenType string + + subjectTokenSource TokenSource + + actorTokenSource TokenSource + + // Http request timeout + // 10 by default + requestTimeout time.Duration + + // Received data + receivedToken string + updateTokenTime time.Time + receivedTokenExpireTime time.Time + + mutex sync.RWMutex + updating atomic.Bool // true if separate goroutine is run and updates token in background + + sourceInfo string +} + +func NewOauth2TokenExchangeCredentials( + opts ...Oauth2TokenExchangeCredentialsOption, +) (*oauth2TokenExchange, error) { + c := &oauth2TokenExchange{ + grantType: "urn:ietf:params:oauth:grant-type:token-exchange", + requestedTokenType: "urn:ietf:params:oauth:token-type:access_token", + requestTimeout: defaultRequestTimeout, + sourceInfo: stack.Record(1), + } + + var err error + for _, opt := range opts { + if opt != nil { + err = opt.ApplyOauth2CredentialsOption(c) + if err != nil { + return nil, xerrors.WithStackTrace(fmt.Errorf("%w: %w", errCouldNotApplyOption, err)) + } + } + } + + if c.tokenEndpoint == "" { + return nil, xerrors.WithStackTrace(errEmptyTokenEndpointError) + } + + return c, nil +} + +func (provider *oauth2TokenExchange) getScopeParam() string { + var scope string + if len(provider.scope) != 0 { + for _, s := range provider.scope { + if s != "" { + if scope != "" { + scope += " " + } + scope += s + } + } + } + + return scope +} + +func (provider *oauth2TokenExchange) addTokenSrc(params *url.Values, src TokenSource, tName, tTypeName string) error { + if src != nil { + token, err := src.Token() + if err != nil { + return xerrors.WithStackTrace(err) + } + params.Set(tName, token.Token) + params.Set(tTypeName, token.TokenType) + } + + return nil +} + +func (provider *oauth2TokenExchange) getRequestParams() (string, error) { + params := url.Values{} + params.Set("grant_type", provider.grantType) + if provider.resource != "" { + params.Set("resource", provider.resource) + } + for _, aud := range provider.audience { + if aud != "" { + params.Add("audience", aud) + } + } + scope := provider.getScopeParam() + if scope != "" { + params.Set("scope", scope) + } + + params.Set("requested_token_type", provider.requestedTokenType) + + err := provider.addTokenSrc(¶ms, provider.subjectTokenSource, "subject_token", "subject_token_type") + if err != nil { + return "", xerrors.WithStackTrace(err) + } + + err = provider.addTokenSrc(¶ms, provider.actorTokenSource, "actor_token", "actor_token_type") + if err != nil { + return "", xerrors.WithStackTrace(err) + } + + return params.Encode(), nil +} + +func (provider *oauth2TokenExchange) processTokenExchangeResponse(result *http.Response, now time.Time) error { + var ( + data []byte + err error + ) + if result.Body != nil { + data, err = io.ReadAll(result.Body) + if err != nil { + return xerrors.WithStackTrace(err) + } + } else { + data = make([]byte, 0) + } + + if result.StatusCode != http.StatusOK { + description := result.Status + + //nolint:tagliatelle + type errorResponse struct { + ErrorName string `json:"error"` + ErrorDescription string `json:"error_description"` + ErrorURI string `json:"error_uri"` + } + var parsedErrorResponse errorResponse + if err := json.Unmarshal(data, &parsedErrorResponse); err != nil { + description += ", could not parse response: " + err.Error() + + return xerrors.WithStackTrace(fmt.Errorf("%w: %s", errCouldNotExchangeToken, description)) + } + + if parsedErrorResponse.ErrorName != "" { + description += ", error: " + parsedErrorResponse.ErrorName + } + + if parsedErrorResponse.ErrorDescription != "" { + description += fmt.Sprintf(", description: %q", parsedErrorResponse.ErrorDescription) + } + + if parsedErrorResponse.ErrorURI != "" { + description += ", error_uri: " + parsedErrorResponse.ErrorURI + } + + return xerrors.WithStackTrace(fmt.Errorf("%w: %s", errCouldNotExchangeToken, description)) + } + + //nolint:tagliatelle + type response struct { + AccessToken string `json:"access_token"` + TokenType string `json:"token_type"` + ExpiresIn int64 `json:"expires_in"` + Scope string `json:"scope"` + } + var parsedResponse response + if err := json.Unmarshal(data, &parsedResponse); err != nil { + return xerrors.WithStackTrace(fmt.Errorf("%w: %w", errCouldNotParseResponse, err)) + } + + if !strings.EqualFold(parsedResponse.TokenType, "bearer") { + return xerrors.WithStackTrace( + fmt.Errorf("%w: %q", errUnsupportedTokenType, parsedResponse.TokenType)) + } + + if parsedResponse.ExpiresIn <= 0 { + return xerrors.WithStackTrace( + fmt.Errorf("%w: %d", errIncorrectExpirationTime, parsedResponse.ExpiresIn)) + } + + if parsedResponse.Scope != "" { + scope := provider.getScopeParam() + if parsedResponse.Scope != scope { + return xerrors.WithStackTrace( + fmt.Errorf("%w. Expected %q, but got %q", errDifferentScope, scope, parsedResponse.Scope)) + } + } + + provider.receivedToken = "Bearer " + parsedResponse.AccessToken + + // Expire time + expireDelta := time.Duration(parsedResponse.ExpiresIn) + expireDelta *= time.Second + provider.receivedTokenExpireTime = now.Add(expireDelta) + + updateDelta := time.Duration(parsedResponse.ExpiresIn / updateTimeDivider) + updateDelta *= time.Second + provider.updateTokenTime = now.Add(updateDelta) + + return nil +} + +func (provider *oauth2TokenExchange) exchangeToken(ctx context.Context, now time.Time) error { + body, err := provider.getRequestParams() + if err != nil { + return xerrors.WithStackTrace(fmt.Errorf("%w: %w", errCouldNotMakeHTTPRequest, err)) + } + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, provider.tokenEndpoint, strings.NewReader(body)) + if err != nil { + return xerrors.WithStackTrace(fmt.Errorf("%w: %w", errCouldNotMakeHTTPRequest, err)) + } + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + req.Header.Add("Content-Length", strconv.Itoa(len(body))) + req.Close = true + + client := http.Client{ + Transport: http.DefaultTransport, + Timeout: provider.requestTimeout, + } + + result, err := client.Do(req) + if err != nil { + return xerrors.WithStackTrace(fmt.Errorf("%w: %w", errCouldNotExchangeToken, err)) + } + + defer result.Body.Close() + + return provider.processTokenExchangeResponse(result, now) +} + +func (provider *oauth2TokenExchange) exchangeTokenInBackground() { + provider.mutex.Lock() + defer provider.mutex.Unlock() + + now := time.Now() + if !provider.needUpdate(now) { + return + } + + ctx := context.Background() + _ = provider.exchangeToken(ctx, now) + + provider.updating.Store(false) +} + +func (provider *oauth2TokenExchange) checkBackgroundUpdate(now time.Time) { + if provider.needUpdate(now) && !provider.updating.Load() { + if provider.updating.CompareAndSwap(false, true) { + go provider.exchangeTokenInBackground() + } + } +} + +func (provider *oauth2TokenExchange) expired(now time.Time) bool { + return now.Compare(provider.receivedTokenExpireTime) > 0 +} + +func (provider *oauth2TokenExchange) needUpdate(now time.Time) bool { + return now.Compare(provider.updateTokenTime) > 0 +} + +func (provider *oauth2TokenExchange) fastCheck(now time.Time) string { + provider.mutex.RLock() + defer provider.mutex.RUnlock() + + if !provider.expired(now) { + provider.checkBackgroundUpdate(now) + + return provider.receivedToken + } + + return "" +} + +func (provider *oauth2TokenExchange) Token(ctx context.Context) (string, error) { + now := time.Now() + + token := provider.fastCheck(now) + if token != "" { + return token, nil + } + + provider.mutex.Lock() + defer provider.mutex.Unlock() + + if !provider.expired(now) { + return provider.receivedToken, nil + } + + if err := provider.exchangeToken(ctx, now); err != nil { + return "", err + } + + return provider.receivedToken, nil +} + +func (provider *oauth2TokenExchange) String() string { + buffer := xstring.Buffer() + defer buffer.Free() + fmt.Fprintf( + buffer, + "OAuth2TokenExchange{Endpoint:%q,GrantType:%s,Resource:%s,Audience:%v,Scope:%v,RequestedTokenType:%s", + provider.tokenEndpoint, + provider.grantType, + provider.resource, + provider.audience, + provider.scope, + provider.requestedTokenType, + ) + if provider.subjectTokenSource != nil { + fmt.Fprintf(buffer, ",SubjectToken:%s", provider.subjectTokenSource) + } + if provider.actorTokenSource != nil { + fmt.Fprintf(buffer, ",ActorToken:%s", provider.actorTokenSource) + } + if provider.sourceInfo != "" { + fmt.Fprintf(buffer, ",From:%q", provider.sourceInfo) + } + buffer.WriteByte('}') + + return buffer.String() +} + +type Token struct { + Token string + + // token type according to OAuth 2.0 token exchange protocol + // https://www.rfc-editor.org/rfc/rfc8693#TokenTypeIdentifiers + // for example urn:ietf:params:oauth:token-type:jwt + TokenType string +} + +type TokenSource interface { + Token() (Token, error) +} + +type fixedTokenSource struct { + fixedToken Token +} + +func (s *fixedTokenSource) Token() (Token, error) { + return s.fixedToken, nil +} + +func (s *fixedTokenSource) String() string { + buffer := xstring.Buffer() + defer buffer.Free() + fmt.Fprintf( + buffer, + "FixedTokenSource{Token:%q,Type:%s}", + secret.Token(s.fixedToken.Token), + s.fixedToken.TokenType, + ) + + return buffer.String() +} + +func NewFixedTokenSource(token, tokenType string) *fixedTokenSource { + return &fixedTokenSource{ + fixedToken: Token{ + Token: token, + TokenType: tokenType, + }, + } +} + +type JWTTokenSourceOption interface { + ApplyJWTTokenSourceOption(s *jwtTokenSource) error +} + +// Issuer +type issuerOption string + +func (issuer issuerOption) ApplyJWTTokenSourceOption(s *jwtTokenSource) error { + s.issuer = string(issuer) + + return nil +} + +func WithIssuer(issuer string) issuerOption { + return issuerOption(issuer) +} + +// Subject +type subjectOption string + +func (subject subjectOption) ApplyJWTTokenSourceOption(s *jwtTokenSource) error { + s.subject = string(subject) + + return nil +} + +func WithSubject(subject string) subjectOption { + return subjectOption(subject) +} + +// Audience +func (audience audienceOption) ApplyJWTTokenSourceOption(s *jwtTokenSource) error { + s.audience = audience + + return nil +} + +// ID +type idOption string + +func (id idOption) ApplyJWTTokenSourceOption(s *jwtTokenSource) error { + s.id = string(id) + + return nil +} + +func WithID(id string) idOption { + return idOption(id) +} + +// TokenTTL +type tokenTTLOption time.Duration + +func (ttl tokenTTLOption) ApplyJWTTokenSourceOption(s *jwtTokenSource) error { + s.tokenTTL = time.Duration(ttl) + + return nil +} + +func WithTokenTTL(ttl time.Duration) tokenTTLOption { + return tokenTTLOption(ttl) +} + +// SigningMethod +type signingMethodOption struct { + method jwt.SigningMethod +} + +func (method *signingMethodOption) ApplyJWTTokenSourceOption(s *jwtTokenSource) error { + s.signingMethod = method.method + + return nil +} + +func WithSigningMethod(method jwt.SigningMethod) *signingMethodOption { + return &signingMethodOption{method} +} + +// KeyID +type keyIDOption string + +func (id keyIDOption) ApplyJWTTokenSourceOption(s *jwtTokenSource) error { + s.keyID = string(id) + + return nil +} + +func WithKeyID(id string) keyIDOption { + return keyIDOption(id) +} + +// PrivateKey +type privateKeyOption struct { + key interface{} +} + +func (key *privateKeyOption) ApplyJWTTokenSourceOption(s *jwtTokenSource) error { + s.privateKey = key.key + + return nil +} + +func WithPrivateKey(key interface{}) *privateKeyOption { + return &privateKeyOption{key} +} + +// PrivateKey +type rsaPrivateKeyPemContentOption struct { + keyContent []byte +} + +func (key *rsaPrivateKeyPemContentOption) ApplyJWTTokenSourceOption(s *jwtTokenSource) error { + privateKey, err := jwt.ParseRSAPrivateKeyFromPEM(key.keyContent) + if err != nil { + return xerrors.WithStackTrace(fmt.Errorf("%w: %w", errCouldNotparseRSAPrivateKey, err)) + } + s.privateKey = privateKey + + return nil +} + +func WithRSAPrivateKeyPEMContent(key []byte) *rsaPrivateKeyPemContentOption { + return &rsaPrivateKeyPemContentOption{key} +} + +// PrivateKey +type rsaPrivateKeyPemFileOption struct { + path string +} + +func (key *rsaPrivateKeyPemFileOption) ApplyJWTTokenSourceOption(s *jwtTokenSource) error { + if len(key.path) > 0 && key.path[0] == '~' { + usr, err := user.Current() + if err != nil { + return xerrors.WithStackTrace(fmt.Errorf("%w: %w", errCouldNotParseHomeDir, err)) + } + key.path = filepath.Join(usr.HomeDir, key.path[1:]) + } + bytes, err := os.ReadFile(key.path) + if err != nil { + return xerrors.WithStackTrace(fmt.Errorf("%w: %w", errCouldNotReadPrivateKeyFile, err)) + } + + o := rsaPrivateKeyPemContentOption{bytes} + + return o.ApplyJWTTokenSourceOption(s) +} + +func WithRSAPrivateKeyPEMFile(path string) *rsaPrivateKeyPemFileOption { + return &rsaPrivateKeyPemFileOption{path} +} + +func NewJWTTokenSource(opts ...JWTTokenSourceOption) (*jwtTokenSource, error) { + s := &jwtTokenSource{ + tokenTTL: defaultJWTTokenTTL, + } + + var err error + for _, opt := range opts { + if opt != nil { + err = opt.ApplyJWTTokenSourceOption(s) + if err != nil { + return nil, xerrors.WithStackTrace(fmt.Errorf("%w: %w", errCouldNotApplyJWTOption, err)) + } + } + } + + if s.signingMethod == nil { + return nil, xerrors.WithStackTrace(errNoSigningMethodError) + } + + if s.privateKey == nil { + return nil, xerrors.WithStackTrace(errNoPrivateKeyError) + } + + return s, nil +} + +type jwtTokenSource struct { + signingMethod jwt.SigningMethod + keyID string + privateKey interface{} // symmetric key in case of symmetric algorithm + + // JWT claims + issuer string + subject string + audience []string + id string + tokenTTL time.Duration +} + +func (s *jwtTokenSource) Token() (Token, error) { + var ( + now = time.Now() + issued = jwt.NewNumericDate(now.UTC()) + expire = jwt.NewNumericDate(now.Add(s.tokenTTL).UTC()) + err error + ) + t := jwt.Token{ + Header: map[string]interface{}{ + "typ": "JWT", + "alg": s.signingMethod.Alg(), + "kid": s.keyID, + }, + Claims: jwt.RegisteredClaims{ + Issuer: s.issuer, + Subject: s.subject, + IssuedAt: issued, + Audience: s.audience, + ExpiresAt: expire, + ID: s.id, + }, + Method: s.signingMethod, + } + + var token Token + token.Token, err = t.SignedString(s.privateKey) + if err != nil { + return token, xerrors.WithStackTrace(fmt.Errorf("%w: %w", errCouldNotSignJWTToken, err)) + } + token.TokenType = "urn:ietf:params:oauth:token-type:jwt" + + return token, nil +} + +func (s *jwtTokenSource) String() string { + buffer := xstring.Buffer() + defer buffer.Free() + fmt.Fprintf( + buffer, + "JWTTokenSource{Method:%s,KeyID:%s,Issuer:%q,Subject:%q,Audience:%v,ID:%s,TokenTTL:%s}", + s.signingMethod.Alg(), + s.keyID, + s.issuer, + s.subject, + s.audience, + s.id, + s.tokenTTL, + ) + + return buffer.String() +} diff --git a/internal/credentials/oauth2_test.go b/internal/credentials/oauth2_test.go new file mode 100644 index 000000000..3ce391961 --- /dev/null +++ b/internal/credentials/oauth2_test.go @@ -0,0 +1,470 @@ +package credentials + +import ( + "context" + "errors" + "fmt" + "io" + "net" + "net/http" + "net/url" + "os" + "os/user" + "path/filepath" + "reflect" + "strconv" + "testing" + "time" + + "github.com/golang-jwt/jwt/v4" + "github.com/stretchr/testify/require" +) + +var ( + testPrivateKeyContent = "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC75/JS3rMcLJxv\nFgpOzF5+2gH+Yig3RE2MTl9uwC0BZKAv6foYr7xywQyWIK+W1cBhz8R4LfFmZo2j\nM0aCvdRmNBdW0EDSTnHLxCsFhoQWLVq+bI5f5jzkcoiioUtaEpADPqwgVULVtN/n\nnPJiZ6/dU30C3jmR6+LUgEntUtWt3eq3xQIn5lG3zC1klBY/HxtfH5Hu8xBvwRQT\nJnh3UpPLj8XwSmriDgdrhR7o6umWyVuGrMKlLHmeivlfzjYtfzO1MOIMG8t2/zxG\nR+xb4Vwks73sH1KruH/0/JMXU97npwpe+Um+uXhpldPygGErEia7abyZB2gMpXqr\nWYKMo02NAgMBAAECggEAO0BpC5OYw/4XN/optu4/r91bupTGHKNHlsIR2rDzoBhU\nYLd1evpTQJY6O07EP5pYZx9mUwUdtU4KRJeDGO/1/WJYp7HUdtxwirHpZP0lQn77\nuccuX/QQaHLrPekBgz4ONk+5ZBqukAfQgM7fKYOLk41jgpeDbM2Ggb6QUSsJISEp\nzrwpI/nNT/wn+Hvx4DxrzWU6wF+P8kl77UwPYlTA7GsT+T7eKGVH8xsxmK8pt6lg\nsvlBA5XosWBWUCGLgcBkAY5e4ZWbkdd183o+oMo78id6C+PQPE66PLDtHWfpRRmN\nm6XC03x6NVhnfvfozoWnmS4+e4qj4F/emCHvn0GMywKBgQDLXlj7YPFVXxZpUvg/\nrheVcCTGbNmQJ+4cZXx87huqwqKgkmtOyeWsRc7zYInYgraDrtCuDBCfP//ZzOh0\nLxepYLTPk5eNn/GT+VVrqsy35Ccr60g7Lp/bzb1WxyhcLbo0KX7/6jl0lP+VKtdv\nmto+4mbSBXSM1Y5BVVoVgJ3T/wKBgQDsiSvPRzVi5TTj13x67PFymTMx3HCe2WzH\nJUyepCmVhTm482zW95pv6raDr5CTO6OYpHtc5sTTRhVYEZoEYFTM9Vw8faBtluWG\nBjkRh4cIpoIARMn74YZKj0C/0vdX7SHdyBOU3bgRPHg08Hwu3xReqT1kEPSI/B2V\n4pe5fVrucwKBgQCNFgUxUA3dJjyMES18MDDYUZaRug4tfiYouRdmLGIxUxozv6CG\nZnbZzwxFt+GpvPUV4f+P33rgoCvFU+yoPctyjE6j+0aW0DFucPmb2kBwCu5J/856\nkFwCx3blbwFHAco+SdN7g2kcwgmV2MTg/lMOcU7XwUUcN0Obe7UlWbckzQKBgQDQ\nnXaXHL24GGFaZe4y2JFmujmNy1dEsoye44W9ERpf9h1fwsoGmmCKPp90az5+rIXw\nFXl8CUgk8lXW08db/r4r+ma8Lyx0GzcZyplAnaB5/6j+pazjSxfO4KOBy4Y89Tb+\nTP0AOcCi6ws13bgY+sUTa/5qKA4UVw+c5zlb7nRpgwKBgGXAXhenFw1666482iiN\ncHSgwc4ZHa1oL6aNJR1XWH+aboBSwR+feKHUPeT4jHgzRGo/aCNHD2FE5I8eBv33\nof1kWYjAO0YdzeKrW0rTwfvt9gGg+CS397aWu4cy+mTI+MNfBgeDAIVBeJOJXLlX\nhL8bFAuNNVrCOp79TNnNIsh7\n-----END PRIVATE KEY-----\n" //nolint:lll + testPublicKeyContent = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu+fyUt6zHCycbxYKTsxe\nftoB/mIoN0RNjE5fbsAtAWSgL+n6GK+8csEMliCvltXAYc/EeC3xZmaNozNGgr3U\nZjQXVtBA0k5xy8QrBYaEFi1avmyOX+Y85HKIoqFLWhKQAz6sIFVC1bTf55zyYmev\n3VN9At45kevi1IBJ7VLVrd3qt8UCJ+ZRt8wtZJQWPx8bXx+R7vMQb8EUEyZ4d1KT\ny4/F8Epq4g4Ha4Ue6OrplslbhqzCpSx5nor5X842LX8ztTDiDBvLdv88RkfsW+Fc\nJLO97B9Sq7h/9PyTF1Pe56cKXvlJvrl4aZXT8oBhKxImu2m8mQdoDKV6q1mCjKNN\njQIDAQAB\n-----END PUBLIC KEY-----\n" //nolint:lll +) + +type httpServerKey int + +const ( + keyServerAddr httpServerKey = 42 +) + +func WriteErr(w http.ResponseWriter, err error) { + WriteResponse(w, http.StatusInternalServerError, err.Error(), "text/html") +} + +func WriteResponse(w http.ResponseWriter, code int, body string, bodyType string) { + w.Header().Add("Content-Type", bodyType) + w.Header().Add("Content-Length", strconv.Itoa(len(body))) + w.WriteHeader(code) + _, _ = w.Write([]byte(body)) +} + +func runTokenExchangeServer( + ctx context.Context, + cancel context.CancelFunc, + port int, + currentTestParams *Oauth2TokenExchangeTestParams, +) { + defer cancel() + mux := http.NewServeMux() + mux.HandleFunc("/exchange", func(w http.ResponseWriter, r *http.Request) { + body, err := io.ReadAll(r.Body) + if err != nil { + WriteErr(w, err) + } + + fmt.Printf("got token exchange request: %s\n", body) + + params, err := url.ParseQuery(string(body)) + if err != nil { + WriteErr(w, err) + } + expectedParams := url.Values{} + expectedParams.Set("scope", "test_scope1 test_scope2") + expectedParams.Set("audience", "test_audience") + expectedParams.Set("grant_type", "urn:ietf:params:oauth:grant-type:token-exchange") + expectedParams.Set("requested_token_type", "urn:ietf:params:oauth:token-type:access_token") + expectedParams.Set("subject_token", "test_source_token") + expectedParams.Set("subject_token_type", "urn:ietf:params:oauth:token-type:test_jwt") + + if !reflect.DeepEqual(expectedParams, params) { + WriteResponse(w, 555, fmt.Sprintf("Params are not as expected: \"%s\" != \"%s\"", + expectedParams.Encode(), body), "text/html") // error will be checked in test thread + } else { + WriteResponse(w, currentTestParams.Status, currentTestParams.Response, "application/json") + } + }) + server := http.Server{ + Addr: fmt.Sprintf(":%d", port), + Handler: mux, + BaseContext: func(l net.Listener) context.Context { + ctx = context.WithValue(ctx, keyServerAddr, l.Addr().String()) + + return ctx + }, + ReadHeaderTimeout: 10 * time.Second, + } + err := server.ListenAndServe() + if err != nil { + fmt.Printf("Failed to run http server: %s", err.Error()) + } +} + +type Oauth2TokenExchangeTestParams struct { + Response string + Status int + ExpectedToken string + ExpectedError error + ExpectedErrorPart string +} + +func TestOauth2TokenExchange(t *testing.T) { + var currentTestParams Oauth2TokenExchangeTestParams + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + runCtx, runCancel := context.WithCancel(ctx) + go runTokenExchangeServer(runCtx, runCancel, 14321, ¤tTestParams) + + testsParams := []Oauth2TokenExchangeTestParams{ + { + Response: `{"access_token":"test_token","token_type":"BEARER","expires_in":42,"some_other_field":"x"}`, + Status: http.StatusOK, + ExpectedToken: "Bearer test_token", + }, + { + Response: `aaa`, + Status: http.StatusOK, + ExpectedToken: "", + ExpectedError: errCouldNotParseResponse, + }, + { + Response: `{}`, + Status: http.StatusBadRequest, + ExpectedToken: "", + ExpectedError: errCouldNotExchangeToken, + }, + { + Response: `not json`, + Status: http.StatusNotFound, + ExpectedToken: "", + ExpectedError: errCouldNotExchangeToken, + }, + { + Response: `{"error": "invalid_request"}`, + Status: http.StatusBadRequest, + ExpectedToken: "", + ExpectedError: errCouldNotExchangeToken, + ExpectedErrorPart: "400 Bad Request, error: invalid_request", + }, + { + Response: `{"error":"unauthorized_client","error_description":"something went bad"}`, + Status: http.StatusInternalServerError, + ExpectedToken: "", + ExpectedError: errCouldNotExchangeToken, + ExpectedErrorPart: "500 Internal Server Error, error: unauthorized_client, description: \"something went bad\"", //nolint:lll + }, + { + Response: `{"error_description":"something went bad","error_uri":"my_error_uri"}`, + Status: http.StatusForbidden, + ExpectedToken: "", + ExpectedError: errCouldNotExchangeToken, + ExpectedErrorPart: "403 Forbidden, description: \"something went bad\", error_uri: my_error_uri", + }, + { + Response: `{"access_token":"test_token","token_type":"","expires_in":42,"some_other_field":"x"}`, + Status: http.StatusOK, + ExpectedToken: "", + ExpectedError: errUnsupportedTokenType, + }, + { + Response: `{"access_token":"test_token","token_type":"basic","expires_in":42,"some_other_field":"x"}`, + Status: http.StatusOK, + ExpectedToken: "", + ExpectedError: errUnsupportedTokenType, + }, + { + Response: `{"access_token":"test_token","token_type":"Bearer","expires_in":-42,"some_other_field":"x"}`, + Status: http.StatusOK, + ExpectedToken: "", + ExpectedError: errIncorrectExpirationTime, + }, + { + Response: `{"access_token":"test_token","token_type":"Bearer","expires_in":42,"scope":"s"}`, + Status: http.StatusOK, + ExpectedToken: "", + ExpectedError: errDifferentScope, + ExpectedErrorPart: "Expected \"test_scope1 test_scope2\", but got \"s\"", + }, + } + + for _, params := range testsParams { + currentTestParams = params + + client, err := NewOauth2TokenExchangeCredentials( + WithTokenEndpoint("http://localhost:14321/exchange"), + WithAudience("test_audience"), + WithScope("test_scope1", "test_scope2"), + WithSubjectToken(NewFixedTokenSource("test_source_token", "urn:ietf:params:oauth:token-type:test_jwt")), + ) + require.NoError(t, err) + + token, err := client.Token(ctx) + if params.ExpectedErrorPart == "" && params.ExpectedError == nil { + require.NoError(t, err) + } else { + if params.ExpectedErrorPart != "" { + require.ErrorContains(t, err, params.ExpectedErrorPart) + } + if params.ExpectedError != nil { + require.ErrorIs(t, err, params.ExpectedError) + } + } + require.Equal(t, params.ExpectedToken, token) + } +} + +func TestOauth2TokenUpdate(t *testing.T) { + var currentTestParams Oauth2TokenExchangeTestParams + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + runCtx, runCancel := context.WithCancel(ctx) + go runTokenExchangeServer(runCtx, runCancel, 14322, ¤tTestParams) + + // First exchange + currentTestParams = Oauth2TokenExchangeTestParams{ + Response: `{"access_token":"test_token_1", "token_type":"Bearer","expires_in":2}`, + Status: http.StatusOK, + } + + client, err := NewOauth2TokenExchangeCredentials( + WithTokenEndpoint("http://localhost:14322/exchange"), + WithAudience("test_audience"), + WithScope("test_scope1", "test_scope2"), + WithFixedSubjectToken("test_source_token", "urn:ietf:params:oauth:token-type:test_jwt"), + ) + require.NoError(t, err) + + token, err := client.Token(ctx) + t1 := time.Now() + require.NoError(t, err) + require.Equal(t, "Bearer test_token_1", token) + + // Second exchange + currentTestParams = Oauth2TokenExchangeTestParams{ + Response: `{"access_token":"test_token_2", "token_type":"Bearer","expires_in":10000}`, + Status: http.StatusOK, + } + + token, err = client.Token(ctx) + t2 := time.Now() + require.NoError(t, err) + if t2.Sub(t1) <= time.Second { // half expire period => no attempts to update + require.Equal(t, "Bearer test_token_1", token) + } + + time.Sleep(time.Second) // wait half expire period + for i := 1; i <= 100; i++ { + t3 := time.Now() + token, err = client.Token(ctx) + require.NoError(t, err) + if t3.Sub(t1) >= 2*time.Second { + require.Equal(t, "Bearer test_token_2", token) // Must update at least sync + } + if token == "Bearer test_token_2" { // already updated + break + } + require.Equal(t, "Bearer test_token_1", token) + + time.Sleep(10 * time.Millisecond) + } + + // Third exchange (never got, because token will be expired later) + currentTestParams = Oauth2TokenExchangeTestParams{ + Response: `{}`, + Status: http.StatusInternalServerError, + } + + for i := 1; i <= 5; i++ { + token, err = client.Token(ctx) + require.NoError(t, err) + require.Equal(t, "Bearer test_token_2", token) + } +} + +func TestWrongParameters(t *testing.T) { + _, err := NewOauth2TokenExchangeCredentials( + // No endpoint + WithFixedActorToken("test_source_token", "urn:ietf:params:oauth:token-type:test_jwt"), + WithRequestedTokenType("access_token"), + ) + require.ErrorIs(t, err, errEmptyTokenEndpointError) +} + +type errorTokenSource struct{} + +var errTokenSource = errors.New("test error") + +func (s *errorTokenSource) Token() (Token, error) { + return Token{"", ""}, errTokenSource +} + +func TestErrorInSourceToken(t *testing.T) { + // Create + _, err := NewOauth2TokenExchangeCredentials( + WithTokenEndpoint("http:trololo"), + WithJWTSubjectToken( + WithRSAPrivateKeyPEMContent([]byte("invalid")), + WithKeyID("key_id"), + WithSigningMethod(jwt.SigningMethodRS256), + WithIssuer("test_issuer"), + WithAudience("test_audience"), + ), + ) + require.ErrorIs(t, err, errCouldNotCreateTokenSource) + + // Use + client, err := NewOauth2TokenExchangeCredentials( + WithTokenEndpoint("http:trololo"), + WithGrantType("grant_type"), + WithRequestTimeout(time.Second), + WithResource("res"), + WithFixedSubjectToken("t", "tt"), + WithActorToken(&errorTokenSource{}), + WithSourceInfo("TestErrorInSourceToken"), + ) + require.NoError(t, err) + + // Check that token prints well + formatted := fmt.Sprint(client) + require.Equal(t, `OAuth2TokenExchange{Endpoint:"http:trololo",GrantType:grant_type,Resource:res,Audience:[],Scope:[],RequestedTokenType:urn:ietf:params:oauth:token-type:access_token,SubjectToken:FixedTokenSource{Token:"****(CRC-32c: 856A5AA8)",Type:tt},ActorToken:&{},From:"TestErrorInSourceToken"}`, formatted) //nolint:lll + + token, err := client.Token(context.Background()) + require.ErrorIs(t, err, errTokenSource) + require.Equal(t, "", token) + + client, err = NewOauth2TokenExchangeCredentials( + WithTokenEndpoint("http:trololo"), + WithGrantType("grant_type"), + WithRequestTimeout(time.Second), + WithResource("res"), + WithSubjectToken(&errorTokenSource{}), + ) + require.NoError(t, err) + + token, err = client.Token(context.Background()) + require.ErrorIs(t, err, errTokenSource) + require.Equal(t, "", token) +} + +func TestErrorInHTTPRequest(t *testing.T) { + client, err := NewOauth2TokenExchangeCredentials( + WithTokenEndpoint("http://invalid_host:42/exchange"), + WithJWTSubjectToken( + WithRSAPrivateKeyPEMContent([]byte(testPrivateKeyContent)), + WithKeyID("key_id"), + WithSigningMethod(jwt.SigningMethodRS256), + WithIssuer("test_issuer"), + WithAudience("test_audience"), + ), + WithJWTActorToken( + WithRSAPrivateKeyPEMContent([]byte(testPrivateKeyContent)), + WithKeyID("key_id"), + WithSigningMethod(jwt.SigningMethodRS256), + WithIssuer("test_issuer"), + ), + WithScope("1", "2", "3"), + WithSourceInfo("TestErrorInHTTPRequest"), + ) + require.NoError(t, err) + + token, err := client.Token(context.Background()) + require.ErrorIs(t, err, errCouldNotExchangeToken) + require.Equal(t, "", token) + + // check format: + formatted := fmt.Sprint(client) + require.Equal(t, `OAuth2TokenExchange{Endpoint:"http://invalid_host:42/exchange",GrantType:urn:ietf:params:oauth:grant-type:token-exchange,Resource:,Audience:[],Scope:[1 2 3],RequestedTokenType:urn:ietf:params:oauth:token-type:access_token,SubjectToken:JWTTokenSource{Method:RS256,KeyID:key_id,Issuer:"test_issuer",Subject:"",Audience:[test_audience],ID:,TokenTTL:1h0m0s},ActorToken:JWTTokenSource{Method:RS256,KeyID:key_id,Issuer:"test_issuer",Subject:"",Audience:[],ID:,TokenTTL:1h0m0s},From:"TestErrorInHTTPRequest"}`, formatted) //nolint:lll +} + +func TestJWTTokenSource(t *testing.T) { + publicKey, err := jwt.ParseRSAPublicKeyFromPEM([]byte(testPublicKeyContent)) + require.NoError(t, err) + getPublicKey := func(*jwt.Token) (interface{}, error) { + return publicKey, nil + } + + var src TokenSource + src, err = NewJWTTokenSource( + WithRSAPrivateKeyPEMContent([]byte(testPrivateKeyContent)), + WithKeyID("key_id"), + WithSigningMethod(jwt.SigningMethodRS256), + WithIssuer("test_issuer"), + WithAudience("test_audience"), + ) + require.NoError(t, err) + + token, err := src.Token() + require.NoError(t, err) + require.Equal(t, "urn:ietf:params:oauth:token-type:jwt", token.TokenType) + + claims := jwt.RegisteredClaims{} + parsedToken, err := jwt.ParseWithClaims(token.Token, &claims, getPublicKey) + require.NoError(t, err) + + require.True(t, parsedToken.Valid) + require.NoError(t, parsedToken.Claims.Valid()) + require.Equal(t, "test_issuer", claims.Issuer) + require.Equal(t, "test_audience", claims.Audience[0]) + require.Equal(t, "key_id", parsedToken.Header["kid"].(string)) + require.Equal(t, "RS256", parsedToken.Header["alg"].(string)) +} + +func TestJWTTokenBadParams(t *testing.T) { + privateKey, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(testPrivateKeyContent)) + require.NoError(t, err) + + _, err = NewJWTTokenSource( + // no private key + WithKeyID("key_id"), + WithSigningMethod(jwt.SigningMethodRS256), + WithIssuer("test_issuer"), + WithAudience("test_audience"), + WithID("id"), + ) + require.ErrorIs(t, err, errNoPrivateKeyError) + + _, err = NewJWTTokenSource( + WithPrivateKey(privateKey), + WithKeyID("key_id"), + // no signing method + WithSubject("s"), + WithTokenTTL(time.Minute), + WithAudience("test_audience"), + ) + require.ErrorIs(t, err, errNoSigningMethodError) +} + +func TestJWTTokenSourceReadPrivateKeyFromFile(t *testing.T) { + const perm = 0o600 + usr, err := user.Current() + require.NoError(t, err) + fileName := strconv.Itoa(time.Now().Second()) + filePath := filepath.Join(usr.HomeDir, fileName) + beautifulFilePath := filepath.Join("~", fileName) + err = os.WriteFile( + filePath, + []byte(testPrivateKeyContent), + perm, + ) + require.NoError(t, err) + defer os.Remove(filePath) + + var src TokenSource + src, err = NewJWTTokenSource( + WithRSAPrivateKeyPEMFile(beautifulFilePath), + WithKeyID("key_id"), + WithSigningMethod(jwt.SigningMethodRS256), + WithIssuer("test_issuer"), + WithAudience("test_audience"), + ) + require.NoError(t, err) + + token, err := src.Token() + require.NoError(t, err) + + // parse token + publicKey, err := jwt.ParseRSAPublicKeyFromPEM([]byte(testPublicKeyContent)) + require.NoError(t, err) + getPublicKey := func(*jwt.Token) (interface{}, error) { + return publicKey, nil + } + + claims := jwt.RegisteredClaims{} + _, err = jwt.ParseWithClaims(token.Token, &claims, getPublicKey) + require.NoError(t, err) +} diff --git a/internal/credentials/source_info.go b/internal/credentials/source_info.go index 017a49a2e..53f7c5065 100644 --- a/internal/credentials/source_info.go +++ b/internal/credentials/source_info.go @@ -14,6 +14,12 @@ func (sourceInfo SourceInfoOption) ApplyAccessTokenCredentialsOption(h *AccessTo h.sourceInfo = string(sourceInfo) } +func (sourceInfo SourceInfoOption) ApplyOauth2CredentialsOption(h *oauth2TokenExchange) error { + h.sourceInfo = string(sourceInfo) + + return nil +} + // WithSourceInfo option append to credentials object the source info for reporting source info details on error case func WithSourceInfo(sourceInfo string) SourceInfoOption { return SourceInfoOption(sourceInfo) diff --git a/internal/credentials/static.go b/internal/credentials/static.go index 298785bf1..7d9437dfb 100644 --- a/internal/credentials/static.go +++ b/internal/credentials/static.go @@ -19,6 +19,8 @@ import ( "github.com/ydb-platform/ydb-go-sdk/v3/internal/xstring" ) +const TokenRefreshDivisor = 10 + var ( _ Credentials = (*Static)(nil) _ fmt.Stringer = (*Static)(nil) @@ -133,7 +135,7 @@ func (c *Static) Token(ctx context.Context) (token string, err error) { return "", xerrors.WithStackTrace(err) } - c.requestAt = time.Now().Add(time.Until(expiresAt) / 10) + c.requestAt = time.Now().Add(time.Until(expiresAt) / TokenRefreshDivisor) c.token = result.GetToken() return c.token, nil diff --git a/internal/decimal/decimal.go b/internal/decimal/decimal.go index a4753992a..4fdbd32e6 100644 --- a/internal/decimal/decimal.go +++ b/internal/decimal/decimal.go @@ -7,15 +7,19 @@ import ( "github.com/ydb-platform/ydb-go-sdk/v3/internal/xstring" ) -const wordSize = bits.UintSize / 8 +const ( + wordSize = bits.UintSize / 8 + bufferSize = 40 + negMask = 0x80 +) var ( - ten = big.NewInt(10) + ten = big.NewInt(10) //nolint:gomnd zero = big.NewInt(0) one = big.NewInt(1) inf = big.NewInt(0).Mul( - big.NewInt(100000000000000000), - big.NewInt(1000000000000000000), + big.NewInt(100000000000000000), //nolint:gomnd + big.NewInt(1000000000000000000), //nolint:gomnd ) nan = big.NewInt(0).Add(inf, one) err = big.NewInt(0).Add(nan, one) @@ -58,7 +62,7 @@ func FromBytes(bts []byte, precision, scale uint32) *big.Int { v.SetBytes(bts) - neg := bts[0]&0x80 != 0 + neg := bts[0]&negMask != 0 if neg { // Given bytes contains negative value. // Interpret is as two's complement. @@ -216,7 +220,7 @@ func Format(x *big.Int, precision, scale uint32) string { // log_{10}(2^120) ~= 36.12, 37 decimal places // plus dot, zero before dot, sign. - bts := make([]byte, 40) + bts := make([]byte, bufferSize) pos := len(bts) var digit big.Int diff --git a/internal/discovery/discovery.go b/internal/discovery/discovery.go index 65237078a..dfda2660c 100644 --- a/internal/discovery/discovery.go +++ b/internal/discovery/discovery.go @@ -40,7 +40,7 @@ func (c *Client) Discover(ctx context.Context) (endpoints []endpoint.Endpoint, e var ( onDone = trace.DiscoveryOnDiscover( c.config.Trace(), &ctx, - stack.FunctionID(""), + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/discovery.(*Client).Discover"), c.config.Endpoint(), c.config.Database(), ) request = Ydb_Discovery.ListEndpointsRequest{ @@ -99,7 +99,9 @@ func (c *Client) Discover(ctx context.Context) (endpoints []endpoint.Endpoint, e func (c *Client) WhoAmI(ctx context.Context) (whoAmI *discovery.WhoAmI, err error) { var ( - onDone = trace.DiscoveryOnWhoAmI(c.config.Trace(), &ctx, stack.FunctionID("")) + onDone = trace.DiscoveryOnWhoAmI(c.config.Trace(), &ctx, + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/discovery.(*Client).WhoAmI"), + ) request = Ydb_Discovery.WhoAmIRequest{} response *Ydb_Discovery.WhoAmIResponse whoAmIResultResult Ydb_Discovery.WhoAmIResult diff --git a/internal/endpoint/endpoint.go b/internal/endpoint/endpoint.go index 823d721d0..37a889b81 100644 --- a/internal/endpoint/endpoint.go +++ b/internal/endpoint/endpoint.go @@ -9,10 +9,15 @@ import ( type Info interface { NodeID() uint32 Address() string - LocalDC() bool Location() string LastUpdated() time.Time LoadFactor() float32 + + // Deprecated: LocalDC check "local" by compare endpoint location with discovery "selflocation" field. + // It work good only if connection url always point to local dc. + // Will be removed after Oct 2024. + // Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated + LocalDC() bool } type Endpoint interface { @@ -23,17 +28,17 @@ type Endpoint interface { Touch(opts ...Option) } -type endpoint struct { +type endpoint struct { //nolint:maligned mu sync.RWMutex id uint32 address string location string services []string - loadFactor float32 - local bool - + loadFactor float32 lastUpdated time.Time + + local bool } func (e *endpoint) Copy() Endpoint { @@ -86,6 +91,10 @@ func (e *endpoint) Location() string { return e.location } +// Deprecated: LocalDC check "local" by compare endpoint location with discovery "selflocation" field. +// It work good only if connection url always point to local dc. +// Will be removed after Oct 2024. +// Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated func (e *endpoint) LocalDC() bool { e.mu.RLock() defer e.mu.RUnlock() diff --git a/internal/grpcwrapper/rawtopic/rawtopicwriter/streamwriter.go b/internal/grpcwrapper/rawtopic/rawtopicwriter/streamwriter.go index e2689df5a..8ff0b9727 100644 --- a/internal/grpcwrapper/rawtopic/rawtopicwriter/streamwriter.go +++ b/internal/grpcwrapper/rawtopic/rawtopicwriter/streamwriter.go @@ -177,6 +177,7 @@ func sendWriteRequest(send sendFunc, req *Ydb_Topic.StreamWriteMessage_FromClien return sendErr } + //nolint:gomnd splitIndex := len(grpcMessages) / 2 firstMessages, lastMessages := grpcMessages[:splitIndex], grpcMessages[splitIndex:] defer func() { diff --git a/internal/grpcwrapper/rawtopic/rawtopicwriter/streamwriter_test.go b/internal/grpcwrapper/rawtopic/rawtopicwriter/streamwriter_test.go index f076adab9..b268ef0d7 100644 --- a/internal/grpcwrapper/rawtopic/rawtopicwriter/streamwriter_test.go +++ b/internal/grpcwrapper/rawtopic/rawtopicwriter/streamwriter_test.go @@ -1,6 +1,7 @@ package rawtopicwriter import ( + "fmt" "testing" "github.com/stretchr/testify/require" @@ -72,7 +73,11 @@ func TestSendWriteRequest(t *testing.T) { } getWriteRequest := func(req *Ydb_Topic.StreamWriteMessage_FromClient) *Ydb_Topic.StreamWriteMessage_WriteRequest { - return req.GetClientMessage().(*Ydb_Topic.StreamWriteMessage_FromClient_WriteRequest).WriteRequest + res, ok := req.ClientMessage.(*Ydb_Topic.StreamWriteMessage_FromClient_WriteRequest) + if !ok { + panic(fmt.Sprintf("unsupported type conversion from %T to *Ydb_Topic.StreamWriteMessage_FromClient_WriteRequest", res)) + } + return res.WriteRequest } sendCounter := 0 diff --git a/internal/meta/context.go b/internal/meta/context.go index b3a410dcc..049d0a6da 100644 --- a/internal/meta/context.go +++ b/internal/meta/context.go @@ -41,7 +41,7 @@ func WithRequestType(ctx context.Context, requestType string) context.Context { // WithAllowFeatures returns a copy of parent context with allowed client feature func WithAllowFeatures(ctx context.Context, features ...string) context.Context { - kv := make([]string, 0, len(features)*2) + kv := make([]string, 0, len(features)*2) //nolint:gomnd for _, feature := range features { kv = append(kv, HeaderClientCapabilities, feature) } diff --git a/internal/meta/meta.go b/internal/meta/meta.go index 7311f0f38..8f856379d 100644 --- a/internal/meta/meta.go +++ b/internal/meta/meta.go @@ -15,6 +15,8 @@ import ( "github.com/ydb-platform/ydb-go-sdk/v3/trace" ) +var pid = os.Getpid() + func New( database string, credentials credentials.Credentials, @@ -22,7 +24,7 @@ func New( opts ...Option, ) *Meta { m := &Meta{ - pid: strconv.Itoa(os.Getpid()), + pid: strconv.Itoa(pid), trace: trace, credentials: credentials, database: database, @@ -115,7 +117,9 @@ func (m *Meta) meta(ctx context.Context) (_ metadata.MD, err error) { var token string - done := trace.DriverOnGetCredentials(m.trace, &ctx, stack.FunctionID("")) + done := trace.DriverOnGetCredentials(m.trace, &ctx, + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/meta.(*Meta).meta"), + ) defer func() { done(token, err) }() diff --git a/internal/mock/conn.go b/internal/mock/conn.go index ac57f6c41..b4ceb9f69 100644 --- a/internal/mock/conn.go +++ b/internal/mock/conn.go @@ -95,6 +95,10 @@ func (e *Endpoint) Address() string { return e.AddrField } +// Deprecated: LocalDC check "local" by compare endpoint location with discovery "selflocation" field. +// It work good only if connection url always point to local dc. +// Will be removed after Oct 2024. +// Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated func (e *Endpoint) LocalDC() bool { return e.LocalDCField } diff --git a/internal/operation/context.go b/internal/operation/context.go index 3ce2e1ee5..2f76340a5 100644 --- a/internal/operation/context.go +++ b/internal/operation/context.go @@ -13,7 +13,7 @@ type ( // WithTimeout returns a copy of parent context in which YDB operation timeout // parameter is set to d. If parent context timeout is smaller than d, parent context is returned. func WithTimeout(ctx context.Context, operationTimeout time.Duration) context.Context { - if d, ok := Timeout(ctx); ok && operationTimeout >= d { + if d, ok := ctxTimeout(ctx); ok && operationTimeout >= d { // The current cancelation timeout is already smaller than the new one. return ctx } @@ -25,7 +25,7 @@ func WithTimeout(ctx context.Context, operationTimeout time.Duration) context.Co // cancel after parameter is set to d. If parent context cancellation timeout is smaller // than d, parent context is returned. func WithCancelAfter(ctx context.Context, operationCancelAfter time.Duration) context.Context { - if d, ok := CancelAfter(ctx); ok && operationCancelAfter >= d { + if d, ok := ctxCancelAfter(ctx); ok && operationCancelAfter >= d { // The current cancelation timeout is already smaller than the new one. return ctx } @@ -33,23 +33,23 @@ func WithCancelAfter(ctx context.Context, operationCancelAfter time.Duration) co return context.WithValue(ctx, ctxOperationCancelAfterKey{}, operationCancelAfter) } -// Timeout returns the timeout within given context after which +// ctxTimeout returns the timeout within given context after which // YDB should try to cancel operation and return result regardless of the cancelation. -func Timeout(ctx context.Context) (d time.Duration, ok bool) { +func ctxTimeout(ctx context.Context) (d time.Duration, ok bool) { d, ok = ctx.Value(ctxOperationTimeoutKey{}).(time.Duration) return } -// CancelAfter returns the timeout within given context after which +// ctxCancelAfter returns the timeout within given context after which // YDB should try to cancel operation and return result regardless of the cancellation. -func CancelAfter(ctx context.Context) (d time.Duration, ok bool) { +func ctxCancelAfter(ctx context.Context) (d time.Duration, ok bool) { d, ok = ctx.Value(ctxOperationCancelAfterKey{}).(time.Duration) return } -func untilDeadline(ctx context.Context) (time.Duration, bool) { +func ctxUntilDeadline(ctx context.Context) (time.Duration, bool) { deadline, ok := ctx.Deadline() if ok { return time.Until(deadline), true diff --git a/internal/operation/params.go b/internal/operation/params.go index f45e941f8..03d342e9a 100644 --- a/internal/operation/params.go +++ b/internal/operation/params.go @@ -13,13 +13,13 @@ func Params( cancelAfter time.Duration, mode Mode, ) *Ydb_Operations.OperationParams { - if d, ok := Timeout(ctx); ok { + if d, ok := ctxTimeout(ctx); ok { timeout = d } - if d, ok := CancelAfter(ctx); ok { + if d, ok := ctxCancelAfter(ctx); ok { cancelAfter = d } - if d, ok := untilDeadline(ctx); mode == ModeSync && ok && d < timeout { + if d, ok := ctxUntilDeadline(ctx); mode == ModeSync && ok && d < timeout { timeout = d } if timeout == 0 && cancelAfter == 0 && mode == 0 { diff --git a/internal/params/builder_test.go b/internal/params/builder_test.go index b1d004cbf..da1a7dda7 100644 --- a/internal/params/builder_test.go +++ b/internal/params/builder_test.go @@ -1,7 +1,6 @@ package params import ( - "encoding/json" "testing" "time" @@ -12,12 +11,6 @@ import ( "github.com/ydb-platform/ydb-go-sdk/v3/internal/xtest" ) -func paramsToJSON(params map[string]*Ydb.TypedValue) string { - b, _ := json.MarshalIndent(params, "", "\t") //nolint:errchkjson - - return string(b) -} - func TestBuilder(t *testing.T) { type expected struct { Type *Ydb.Type @@ -427,14 +420,14 @@ func TestBuilder(t *testing.T) { params := result.Build().ToYDB(a) require.Equal(t, - paramsToJSON( + xtest.ToJSON( map[string]*Ydb.TypedValue{ "$x": { Type: tc.expected.Type, Value: tc.expected.Value, }, }), - paramsToJSON(params), + xtest.ToJSON(params), ) }) } diff --git a/internal/params/dict_test.go b/internal/params/dict_test.go index 2fed82d33..0dc6b246c 100644 --- a/internal/params/dict_test.go +++ b/internal/params/dict_test.go @@ -424,7 +424,7 @@ func TestDict(t *testing.T) { require.True(t, ok) params := d.EndDict().Build().ToYDB(a) - require.Equal(t, paramsToJSON( + require.Equal(t, xtest.ToJSON( map[string]*Ydb.TypedValue{ "$x": { Type: &Ydb.Type{ @@ -444,7 +444,7 @@ func TestDict(t *testing.T) { }, }, }, - }), paramsToJSON(params)) + }), xtest.ToJSON(params)) }) } } @@ -467,7 +467,7 @@ func TestDict_AddPairs(t *testing.T) { params := Builder{}.Param("$x").BeginDict().AddPairs(pairs...).EndDict().Build().ToYDB(a) - require.Equal(t, paramsToJSON( + require.Equal(t, xtest.ToJSON( map[string]*Ydb.TypedValue{ "$x": { Type: &Ydb.Type{ @@ -515,5 +515,5 @@ func TestDict_AddPairs(t *testing.T) { }, }, }, - }), paramsToJSON(params)) + }), xtest.ToJSON(params)) } diff --git a/internal/params/list_test.go b/internal/params/list_test.go index 153212f89..288e0a5f7 100644 --- a/internal/params/list_test.go +++ b/internal/params/list_test.go @@ -419,7 +419,7 @@ func TestList(t *testing.T) { require.True(t, ok) params := result.EndList().Build().ToYDB(a) - require.Equal(t, paramsToJSON( + require.Equal(t, xtest.ToJSON( map[string]*Ydb.TypedValue{ "$x": { Type: &Ydb.Type{ @@ -435,7 +435,7 @@ func TestList(t *testing.T) { }, }, }, - }), paramsToJSON(params)) + }), xtest.ToJSON(params)) }) } } @@ -446,7 +446,7 @@ func TestList_AddItems(t *testing.T) { params := Builder{}.Param("$x").BeginList(). AddItems(value.Uint64Value(123), value.Uint64Value(321)). EndList().Build().ToYDB(a) - require.Equal(t, paramsToJSON( + require.Equal(t, xtest.ToJSON( map[string]*Ydb.TypedValue{ "$x": { Type: &Ydb.Type{ @@ -471,5 +471,5 @@ func TestList_AddItems(t *testing.T) { }, }, }, - }), paramsToJSON(params)) + }), xtest.ToJSON(params)) } diff --git a/internal/params/optional_test.go b/internal/params/optional_test.go index 37246e364..0c21718a1 100644 --- a/internal/params/optional_test.go +++ b/internal/params/optional_test.go @@ -418,7 +418,7 @@ func TestOptional(t *testing.T) { require.True(t, ok) params := result.EndOptional().Build().ToYDB(a) - require.Equal(t, paramsToJSON( + require.Equal(t, xtest.ToJSON( map[string]*Ydb.TypedValue{ "$x": { Type: &Ydb.Type{ @@ -430,7 +430,7 @@ func TestOptional(t *testing.T) { }, Value: tc.expected.Value, }, - }), paramsToJSON(params)) + }), xtest.ToJSON(params)) }) } } diff --git a/internal/params/parameters.go b/internal/params/parameters.go index 18210cc16..aae4e38d0 100644 --- a/internal/params/parameters.go +++ b/internal/params/parameters.go @@ -134,6 +134,20 @@ func (p *Parameter) BeginTuple() *tuple { } } +func (p *Parameter) BeginStruct() *structure { + return &structure{ + parent: p.parent, + name: p.name, + } +} + +func (p *Parameter) BeginVariant() *variant { + return &variant{ + parent: p.parent, + name: p.name, + } +} + func (p *Parameter) Text(v string) Builder { p.value = value.TextValue(v) p.parent.params = append(p.parent.params, p) diff --git a/internal/params/pg_test.go b/internal/params/pg_test.go index 4fc6f15f1..ce470864c 100644 --- a/internal/params/pg_test.go +++ b/internal/params/pg_test.go @@ -88,13 +88,13 @@ func TestPg(t *testing.T) { params := result.Build().ToYDB(a) - require.Equal(t, paramsToJSON( + require.Equal(t, xtest.ToJSON( map[string]*Ydb.TypedValue{ "$x": { Type: tc.expected.Type, Value: tc.expected.Value, }, - }), paramsToJSON(params)) + }), xtest.ToJSON(params)) }) } } diff --git a/internal/params/set_test.go b/internal/params/set_test.go index 0a97b866b..6ff75d464 100644 --- a/internal/params/set_test.go +++ b/internal/params/set_test.go @@ -419,7 +419,7 @@ func TestSet(t *testing.T) { require.True(t, ok) params := result.EndSet().Build().ToYDB(a) - require.Equal(t, paramsToJSON( + require.Equal(t, xtest.ToJSON( map[string]*Ydb.TypedValue{ "$x": { Type: &Ydb.Type{ @@ -443,7 +443,7 @@ func TestSet(t *testing.T) { }, }, }, - }), paramsToJSON(params)) + }), xtest.ToJSON(params)) }) } } @@ -454,7 +454,7 @@ func TestSet_AddItems(t *testing.T) { params := Builder{}.Param("$x").BeginSet(). AddItems(value.Uint64Value(123), value.Uint64Value(321)). EndSet().Build().ToYDB(a) - require.Equal(t, paramsToJSON( + require.Equal(t, xtest.ToJSON( map[string]*Ydb.TypedValue{ "$x": { Type: &Ydb.Type{ @@ -496,5 +496,5 @@ func TestSet_AddItems(t *testing.T) { }, }, }, - }), paramsToJSON(params)) + }), xtest.ToJSON(params)) } diff --git a/internal/params/struct.go b/internal/params/struct.go new file mode 100644 index 000000000..0a8af589b --- /dev/null +++ b/internal/params/struct.go @@ -0,0 +1,268 @@ +package params + +import ( + "time" + + "github.com/ydb-platform/ydb-go-sdk/v3/internal/value" +) + +type ( + structure struct { + parent Builder + name string + values []value.StructValueField + } + + structValue struct { + parent *structure + name string + } +) + +func (s *structure) AddItems(items ...value.StructValueField) *structure { + s.values = append(s.values, items...) + + return s +} + +func (s *structure) Field(v string) *structValue { + return &structValue{ + parent: s, + name: v, + } +} + +func (s *structure) EndStruct() Builder { + s.parent.params = append(s.parent.params, &Parameter{ + parent: s.parent, + name: s.name, + value: value.StructValue(s.values...), + }) + + return s.parent +} + +func (s *structValue) Text(v string) *structure { + s.parent.values = append(s.parent.values, value.StructValueField{ + Name: s.name, + V: value.TextValue(v), + }) + + return s.parent +} + +func (s *structValue) Bytes(v []byte) *structure { + s.parent.values = append(s.parent.values, value.StructValueField{ + Name: s.name, + V: value.BytesValue(v), + }) + + return s.parent +} + +func (s *structValue) Bool(v bool) *structure { + s.parent.values = append(s.parent.values, value.StructValueField{ + Name: s.name, + V: value.BoolValue(v), + }) + + return s.parent +} + +func (s *structValue) Uint64(v uint64) *structure { + s.parent.values = append(s.parent.values, value.StructValueField{ + Name: s.name, + V: value.Uint64Value(v), + }) + + return s.parent +} + +func (s *structValue) Int64(v int64) *structure { + s.parent.values = append(s.parent.values, value.StructValueField{ + Name: s.name, + V: value.Int64Value(v), + }) + + return s.parent +} + +func (s *structValue) Uint32(v uint32) *structure { + s.parent.values = append(s.parent.values, value.StructValueField{ + Name: s.name, + V: value.Uint32Value(v), + }) + + return s.parent +} + +func (s *structValue) Int32(v int32) *structure { + s.parent.values = append(s.parent.values, value.StructValueField{ + Name: s.name, + V: value.Int32Value(v), + }) + + return s.parent +} + +func (s *structValue) Uint16(v uint16) *structure { + s.parent.values = append(s.parent.values, value.StructValueField{ + Name: s.name, + V: value.Uint16Value(v), + }) + + return s.parent +} + +func (s *structValue) Int16(v int16) *structure { + s.parent.values = append(s.parent.values, value.StructValueField{ + Name: s.name, + V: value.Int16Value(v), + }) + + return s.parent +} + +func (s *structValue) Uint8(v uint8) *structure { + s.parent.values = append(s.parent.values, value.StructValueField{ + Name: s.name, + V: value.Uint8Value(v), + }) + + return s.parent +} + +func (s *structValue) Int8(v int8) *structure { + s.parent.values = append(s.parent.values, value.StructValueField{ + Name: s.name, + V: value.Int8Value(v), + }) + + return s.parent +} + +func (s *structValue) Float(v float32) *structure { + s.parent.values = append(s.parent.values, value.StructValueField{ + Name: s.name, + V: value.FloatValue(v), + }) + + return s.parent +} + +func (s *structValue) Double(v float64) *structure { + s.parent.values = append(s.parent.values, value.StructValueField{ + Name: s.name, + V: value.DoubleValue(v), + }) + + return s.parent +} + +func (s *structValue) Decimal(v [16]byte, precision, scale uint32) *structure { + s.parent.values = append(s.parent.values, value.StructValueField{ + Name: s.name, + V: value.DecimalValue(v, precision, scale), + }) + + return s.parent +} + +func (s *structValue) Timestamp(v time.Time) *structure { + s.parent.values = append(s.parent.values, value.StructValueField{ + Name: s.name, + V: value.TimestampValueFromTime(v), + }) + + return s.parent +} + +func (s *structValue) Date(v time.Time) *structure { + s.parent.values = append(s.parent.values, value.StructValueField{ + Name: s.name, + V: value.DateValueFromTime(v), + }) + + return s.parent +} + +func (s *structValue) Datetime(v time.Time) *structure { + s.parent.values = append(s.parent.values, value.StructValueField{ + Name: s.name, + V: value.DatetimeValueFromTime(v), + }) + + return s.parent +} + +func (s *structValue) Interval(v time.Duration) *structure { + s.parent.values = append(s.parent.values, value.StructValueField{ + Name: s.name, + V: value.IntervalValueFromDuration(v), + }) + + return s.parent +} + +func (s *structValue) JSON(v string) *structure { + s.parent.values = append(s.parent.values, value.StructValueField{ + Name: s.name, + V: value.JSONValue(v), + }) + + return s.parent +} + +func (s *structValue) JSONDocument(v string) *structure { + s.parent.values = append(s.parent.values, value.StructValueField{ + Name: s.name, + V: value.JSONDocumentValue(v), + }) + + return s.parent +} + +func (s *structValue) YSON(v []byte) *structure { + s.parent.values = append(s.parent.values, value.StructValueField{ + Name: s.name, + V: value.YSONValue(v), + }) + + return s.parent +} + +func (s *structValue) UUID(v [16]byte) *structure { + s.parent.values = append(s.parent.values, value.StructValueField{ + Name: s.name, + V: value.UUIDValue(v), + }) + + return s.parent +} + +func (s *structValue) TzDatetime(v time.Time) *structure { + s.parent.values = append(s.parent.values, value.StructValueField{ + Name: s.name, + V: value.TzDatetimeValueFromTime(v), + }) + + return s.parent +} + +func (s *structValue) TzTimestamp(v time.Time) *structure { + s.parent.values = append(s.parent.values, value.StructValueField{ + Name: s.name, + V: value.TzTimestampValueFromTime(v), + }) + + return s.parent +} + +func (s *structValue) TzDate(v time.Time) *structure { + s.parent.values = append(s.parent.values, value.StructValueField{ + Name: s.name, + V: value.TzDateValueFromTime(v), + }) + + return s.parent +} diff --git a/internal/params/struct_test.go b/internal/params/struct_test.go new file mode 100644 index 000000000..cad24fcaf --- /dev/null +++ b/internal/params/struct_test.go @@ -0,0 +1,922 @@ +package params + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" + "github.com/ydb-platform/ydb-go-genproto/protos/Ydb" + + "github.com/ydb-platform/ydb-go-sdk/v3/internal/allocator" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/xtest" +) + +func TestStruct(t *testing.T) { + for _, tt := range []struct { + name string + builder Builder + params map[string]*Ydb.TypedValue + }{ + { + name: xtest.CurrentFileLine(), + builder: Builder{}.Param("$x").BeginStruct().Field("col1").Uint64(123).EndStruct(), + params: map[string]*Ydb.TypedValue{ + "$x": { + Type: &Ydb.Type{ + Type: &Ydb.Type_StructType{ + StructType: &Ydb.StructType{ + Members: []*Ydb.StructMember{ + { + Name: "col1", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UINT64, + }, + }, + }, + }, + }, + }, + }, + Value: &Ydb.Value{ + Items: []*Ydb.Value{ + { + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 123, + }, + }, + }, + }, + }, + }, + }, + { + name: xtest.CurrentFileLine(), + builder: Builder{}.Param("$x").BeginStruct().Field("col1").Int64(123).EndStruct(), + params: map[string]*Ydb.TypedValue{ + "$x": { + Type: &Ydb.Type{ + Type: &Ydb.Type_StructType{ + StructType: &Ydb.StructType{ + Members: []*Ydb.StructMember{ + { + Name: "col1", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_INT64, + }, + }, + }, + }, + }, + }, + }, + Value: &Ydb.Value{ + Items: []*Ydb.Value{ + { + Value: &Ydb.Value_Int64Value{ + Int64Value: 123, + }, + }, + }, + }, + }, + }, + }, + { + name: xtest.CurrentFileLine(), + builder: Builder{}.Param("$x").BeginStruct().Field("col1").Uint32(123).EndStruct(), + params: map[string]*Ydb.TypedValue{ + "$x": { + Type: &Ydb.Type{ + Type: &Ydb.Type_StructType{ + StructType: &Ydb.StructType{ + Members: []*Ydb.StructMember{ + { + Name: "col1", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UINT32, + }, + }, + }, + }, + }, + }, + }, + Value: &Ydb.Value{ + Items: []*Ydb.Value{ + { + Value: &Ydb.Value_Uint32Value{ + Uint32Value: 123, + }, + }, + }, + }, + }, + }, + }, + { + name: xtest.CurrentFileLine(), + builder: Builder{}.Param("$x").BeginStruct().Field("col1").Int32(123).EndStruct(), + params: map[string]*Ydb.TypedValue{ + "$x": { + Type: &Ydb.Type{ + Type: &Ydb.Type_StructType{ + StructType: &Ydb.StructType{ + Members: []*Ydb.StructMember{ + { + Name: "col1", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_INT32, + }, + }, + }, + }, + }, + }, + }, + Value: &Ydb.Value{ + Items: []*Ydb.Value{ + { + Value: &Ydb.Value_Int32Value{ + Int32Value: 123, + }, + }, + }, + }, + }, + }, + }, + { + name: xtest.CurrentFileLine(), + builder: Builder{}.Param("$x").BeginStruct().Field("col1").Uint16(123).EndStruct(), + params: map[string]*Ydb.TypedValue{ + "$x": { + Type: &Ydb.Type{ + Type: &Ydb.Type_StructType{ + StructType: &Ydb.StructType{ + Members: []*Ydb.StructMember{ + { + Name: "col1", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UINT16, + }, + }, + }, + }, + }, + }, + }, + Value: &Ydb.Value{ + Items: []*Ydb.Value{ + { + Value: &Ydb.Value_Uint32Value{ + Uint32Value: 123, + }, + }, + }, + }, + }, + }, + }, + { + name: xtest.CurrentFileLine(), + builder: Builder{}.Param("$x").BeginStruct().Field("col1").Int16(123).EndStruct(), + params: map[string]*Ydb.TypedValue{ + "$x": { + Type: &Ydb.Type{ + Type: &Ydb.Type_StructType{ + StructType: &Ydb.StructType{ + Members: []*Ydb.StructMember{ + { + Name: "col1", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_INT16, + }, + }, + }, + }, + }, + }, + }, + Value: &Ydb.Value{ + Items: []*Ydb.Value{ + { + Value: &Ydb.Value_Int32Value{ + Int32Value: 123, + }, + }, + }, + }, + }, + }, + }, + { + name: xtest.CurrentFileLine(), + builder: Builder{}.Param("$x").BeginStruct().Field("col1").Uint8(123).EndStruct(), + params: map[string]*Ydb.TypedValue{ + "$x": { + Type: &Ydb.Type{ + Type: &Ydb.Type_StructType{ + StructType: &Ydb.StructType{ + Members: []*Ydb.StructMember{ + { + Name: "col1", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UINT8, + }, + }, + }, + }, + }, + }, + }, + Value: &Ydb.Value{ + Items: []*Ydb.Value{ + { + Value: &Ydb.Value_Uint32Value{ + Uint32Value: 123, + }, + }, + }, + }, + }, + }, + }, + { + name: xtest.CurrentFileLine(), + builder: Builder{}.Param("$x").BeginStruct().Field("col1").Int8(123).EndStruct(), + params: map[string]*Ydb.TypedValue{ + "$x": { + Type: &Ydb.Type{ + Type: &Ydb.Type_StructType{ + StructType: &Ydb.StructType{ + Members: []*Ydb.StructMember{ + { + Name: "col1", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_INT8, + }, + }, + }, + }, + }, + }, + }, + Value: &Ydb.Value{ + Items: []*Ydb.Value{ + { + Value: &Ydb.Value_Int32Value{ + Int32Value: 123, + }, + }, + }, + }, + }, + }, + }, + { + name: xtest.CurrentFileLine(), + builder: Builder{}.Param("$x").BeginStruct().Field("col1").Bool(true).EndStruct(), + params: map[string]*Ydb.TypedValue{ + "$x": { + Type: &Ydb.Type{ + Type: &Ydb.Type_StructType{ + StructType: &Ydb.StructType{ + Members: []*Ydb.StructMember{ + { + Name: "col1", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_BOOL, + }, + }, + }, + }, + }, + }, + }, + Value: &Ydb.Value{ + Items: []*Ydb.Value{ + { + Value: &Ydb.Value_BoolValue{ + BoolValue: true, + }, + }, + }, + }, + }, + }, + }, + { + name: xtest.CurrentFileLine(), + builder: Builder{}.Param("$x").BeginStruct().Field("col1").Text("test").EndStruct(), + params: map[string]*Ydb.TypedValue{ + "$x": { + Type: &Ydb.Type{ + Type: &Ydb.Type_StructType{ + StructType: &Ydb.StructType{ + Members: []*Ydb.StructMember{ + { + Name: "col1", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UTF8, + }, + }, + }, + }, + }, + }, + }, + Value: &Ydb.Value{ + Items: []*Ydb.Value{ + { + Value: &Ydb.Value_TextValue{ + TextValue: "test", + }, + }, + }, + }, + }, + }, + }, + { + name: xtest.CurrentFileLine(), + builder: Builder{}.Param("$x").BeginStruct().Field("col1").Bytes([]byte("test")).EndStruct(), + params: map[string]*Ydb.TypedValue{ + "$x": { + Type: &Ydb.Type{ + Type: &Ydb.Type_StructType{ + StructType: &Ydb.StructType{ + Members: []*Ydb.StructMember{ + { + Name: "col1", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_STRING, + }, + }, + }, + }, + }, + }, + }, + Value: &Ydb.Value{ + Items: []*Ydb.Value{ + { + Value: &Ydb.Value_BytesValue{ + BytesValue: []byte("test"), + }, + }, + }, + }, + }, + }, + }, + { + name: xtest.CurrentFileLine(), + builder: Builder{}.Param("$x").BeginStruct().Field("col1").Float(123).EndStruct(), + params: map[string]*Ydb.TypedValue{ + "$x": { + Type: &Ydb.Type{ + Type: &Ydb.Type_StructType{ + StructType: &Ydb.StructType{ + Members: []*Ydb.StructMember{ + { + Name: "col1", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_FLOAT, + }, + }, + }, + }, + }, + }, + }, + Value: &Ydb.Value{ + Items: []*Ydb.Value{ + { + Value: &Ydb.Value_FloatValue{ + FloatValue: 123, + }, + }, + }, + }, + }, + }, + }, + { + name: xtest.CurrentFileLine(), + builder: Builder{}.Param("$x").BeginStruct().Field("col1").Double(123).EndStruct(), + params: map[string]*Ydb.TypedValue{ + "$x": { + Type: &Ydb.Type{ + Type: &Ydb.Type_StructType{ + StructType: &Ydb.StructType{ + Members: []*Ydb.StructMember{ + { + Name: "col1", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_DOUBLE, + }, + }, + }, + }, + }, + }, + }, + Value: &Ydb.Value{ + Items: []*Ydb.Value{ + { + Value: &Ydb.Value_DoubleValue{ + DoubleValue: 123, + }, + }, + }, + }, + }, + }, + }, + { + name: xtest.CurrentFileLine(), + builder: Builder{}.Param("$x").BeginStruct().Field("col1").Interval(time.Second).EndStruct(), + params: map[string]*Ydb.TypedValue{ + "$x": { + Type: &Ydb.Type{ + Type: &Ydb.Type_StructType{ + StructType: &Ydb.StructType{ + Members: []*Ydb.StructMember{ + { + Name: "col1", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_INTERVAL, + }, + }, + }, + }, + }, + }, + }, + Value: &Ydb.Value{ + Items: []*Ydb.Value{ + { + Value: &Ydb.Value_Int64Value{ + Int64Value: 1000000, + }, + }, + }, + }, + }, + }, + }, + { + name: xtest.CurrentFileLine(), + builder: Builder{}.Param("$x").BeginStruct().Field("col1").Datetime(time.Unix(123456789, 456)).EndStruct(), + params: map[string]*Ydb.TypedValue{ + "$x": { + Type: &Ydb.Type{ + Type: &Ydb.Type_StructType{ + StructType: &Ydb.StructType{ + Members: []*Ydb.StructMember{ + { + Name: "col1", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_DATETIME, + }, + }, + }, + }, + }, + }, + }, + Value: &Ydb.Value{ + Items: []*Ydb.Value{ + { + Value: &Ydb.Value_Uint32Value{ + Uint32Value: 123456789, + }, + }, + }, + }, + }, + }, + }, + { + name: xtest.CurrentFileLine(), + builder: Builder{}.Param("$x").BeginStruct().Field("col1").Date(time.Unix(123456789, 456)).EndStruct(), + params: map[string]*Ydb.TypedValue{ + "$x": { + Type: &Ydb.Type{ + Type: &Ydb.Type_StructType{ + StructType: &Ydb.StructType{ + Members: []*Ydb.StructMember{ + { + Name: "col1", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_DATE, + }, + }, + }, + }, + }, + }, + }, + Value: &Ydb.Value{ + Items: []*Ydb.Value{ + { + Value: &Ydb.Value_Uint32Value{ + Uint32Value: 1428, + }, + }, + }, + }, + }, + }, + }, + { + name: xtest.CurrentFileLine(), + builder: Builder{}.Param("$x").BeginStruct().Field("col1").Timestamp(time.Unix(123456789, 456)).EndStruct(), + params: map[string]*Ydb.TypedValue{ + "$x": { + Type: &Ydb.Type{ + Type: &Ydb.Type_StructType{ + StructType: &Ydb.StructType{ + Members: []*Ydb.StructMember{ + { + Name: "col1", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_TIMESTAMP, + }, + }, + }, + }, + }, + }, + }, + Value: &Ydb.Value{ + Items: []*Ydb.Value{ + { + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 123456789000000, + }, + }, + }, + }, + }, + }, + }, + { + name: xtest.CurrentFileLine(), + builder: Builder{}.Param("$x").BeginStruct().Field("col1").Decimal([...]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, 22, 9).EndStruct(), //nolint:lll + params: map[string]*Ydb.TypedValue{ + "$x": { + Type: &Ydb.Type{ + Type: &Ydb.Type_StructType{ + StructType: &Ydb.StructType{ + Members: []*Ydb.StructMember{ + { + Name: "col1", + Type: &Ydb.Type{ + Type: &Ydb.Type_DecimalType{ + DecimalType: &Ydb.DecimalType{ + Precision: 22, + Scale: 9, + }, + }, + }, + }, + }, + }, + }, + }, + Value: &Ydb.Value{ + Items: []*Ydb.Value{ + { + High_128: 72623859790382856, + Value: &Ydb.Value_Low_128{ + Low_128: 648519454493508870, + }, + }, + }, + }, + }, + }, + }, + { + name: xtest.CurrentFileLine(), + builder: Builder{}.Param("$x").BeginStruct().Field("col1").JSON(`{"a": 1,"b": "B"}`).EndStruct(), + params: map[string]*Ydb.TypedValue{ + "$x": { + Type: &Ydb.Type{ + Type: &Ydb.Type_StructType{ + StructType: &Ydb.StructType{ + Members: []*Ydb.StructMember{ + { + Name: "col1", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_JSON, + }, + }, + }, + }, + }, + }, + }, + Value: &Ydb.Value{ + Items: []*Ydb.Value{ + { + Value: &Ydb.Value_TextValue{ + TextValue: `{"a": 1,"b": "B"}`, + }, + }, + }, + }, + }, + }, + }, + { + name: xtest.CurrentFileLine(), + builder: Builder{}.Param("$x").BeginStruct().Field("col1").JSONDocument(`{"a": 1,"b": "B"}`).EndStruct(), + params: map[string]*Ydb.TypedValue{ + "$x": { + Type: &Ydb.Type{ + Type: &Ydb.Type_StructType{ + StructType: &Ydb.StructType{ + Members: []*Ydb.StructMember{ + { + Name: "col1", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_JSON_DOCUMENT, + }, + }, + }, + }, + }, + }, + }, + Value: &Ydb.Value{ + Items: []*Ydb.Value{ + { + Value: &Ydb.Value_TextValue{ + TextValue: `{"a": 1,"b": "B"}`, + }, + }, + }, + }, + }, + }, + }, + { + name: xtest.CurrentFileLine(), + builder: Builder{}.Param("$x").BeginStruct().Field("col1").YSON([]byte(`[ 1; 2; 3; 4; 5 ]`)).EndStruct(), + params: map[string]*Ydb.TypedValue{ + "$x": { + Type: &Ydb.Type{ + Type: &Ydb.Type_StructType{ + StructType: &Ydb.StructType{ + Members: []*Ydb.StructMember{ + { + Name: "col1", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_YSON, + }, + }, + }, + }, + }, + }, + }, + Value: &Ydb.Value{ + Items: []*Ydb.Value{ + { + Value: &Ydb.Value_BytesValue{ + BytesValue: []byte(`[ 1; 2; 3; 4; 5 ]`), + }, + }, + }, + }, + }, + }, + }, + { + name: xtest.CurrentFileLine(), + builder: Builder{}.Param("$x").BeginStruct().Field("col1"). + UUID([...]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}).EndStruct(), + params: map[string]*Ydb.TypedValue{ + "$x": { + Type: &Ydb.Type{ + Type: &Ydb.Type_StructType{ + StructType: &Ydb.StructType{ + Members: []*Ydb.StructMember{ + { + Name: "col1", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UUID, + }, + }, + }, + }, + }, + }, + }, + Value: &Ydb.Value{ + Items: []*Ydb.Value{ + { + Value: &Ydb.Value_Low_128{ + Low_128: 651345242494996240, + }, + High_128: 72623859790382856, + }, + }, + }, + }, + }, + }, + { + name: xtest.CurrentFileLine(), + builder: Builder{}.Param("$x").BeginStruct(). + Field("col1").Text("text"). + Field("col2").Uint32(123). + Field("col3").Int64(456). + EndStruct(), + params: map[string]*Ydb.TypedValue{ + "$x": { + Type: &Ydb.Type{ + Type: &Ydb.Type_StructType{ + StructType: &Ydb.StructType{ + Members: []*Ydb.StructMember{ + { + Name: "col1", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UTF8, + }, + }, + }, + { + Name: "col2", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UINT32, + }, + }, + }, + { + Name: "col3", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_INT64, + }, + }, + }, + }, + }, + }, + }, + Value: &Ydb.Value{ + Items: []*Ydb.Value{ + { + Value: &Ydb.Value_TextValue{ + TextValue: "text", + }, + }, + { + Value: &Ydb.Value_Uint32Value{ + Uint32Value: 123, + }, + }, + { + Value: &Ydb.Value_Int64Value{ + Int64Value: 456, + }, + }, + }, + }, + }, + }, + }, + { + name: xtest.CurrentFileLine(), + builder: Builder{}.Param("$x").BeginStruct().Field("col1").TzDatetime(time.Unix(123456789, 456).UTC()).EndStruct(), + params: map[string]*Ydb.TypedValue{ + "$x": { + Type: &Ydb.Type{ + Type: &Ydb.Type_StructType{ + StructType: &Ydb.StructType{ + Members: []*Ydb.StructMember{ + { + Name: "col1", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_TZ_DATETIME, + }, + }, + }, + }, + }, + }, + }, + Value: &Ydb.Value{ + Items: []*Ydb.Value{ + { + Value: &Ydb.Value_TextValue{ + TextValue: "1973-11-29T21:33:09Z", + }, + }, + }, + }, + }, + }, + }, + { + name: xtest.CurrentFileLine(), + builder: Builder{}.Param("$x").BeginStruct().Field("col1").TzDate(time.Unix(123456789, 456).UTC()).EndStruct(), + params: map[string]*Ydb.TypedValue{ + "$x": { + Type: &Ydb.Type{ + Type: &Ydb.Type_StructType{ + StructType: &Ydb.StructType{ + Members: []*Ydb.StructMember{ + { + Name: "col1", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_TZ_DATE, + }, + }, + }, + }, + }, + }, + }, + Value: &Ydb.Value{ + Items: []*Ydb.Value{ + { + Value: &Ydb.Value_TextValue{ + TextValue: "1973-11-29", + }, + }, + }, + }, + }, + }, + }, + { + name: xtest.CurrentFileLine(), + builder: Builder{}.Param("$x").BeginStruct().Field("col1").TzTimestamp(time.Unix(123456789, 456).UTC()).EndStruct(), + params: map[string]*Ydb.TypedValue{ + "$x": { + Type: &Ydb.Type{ + Type: &Ydb.Type_StructType{ + StructType: &Ydb.StructType{ + Members: []*Ydb.StructMember{ + { + Name: "col1", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_TZ_TIMESTAMP, + }, + }, + }, + }, + }, + }, + }, + Value: &Ydb.Value{ + Items: []*Ydb.Value{ + { + Value: &Ydb.Value_TextValue{ + TextValue: "1973-11-29T21:33:09.000000Z", + }, + }, + }, + }, + }, + }, + }, + } { + t.Run(tt.name, func(t *testing.T) { + a := allocator.New() + defer a.Free() + params := tt.builder.Build().ToYDB(a) + require.Equal(t, xtest.ToJSON(tt.params), xtest.ToJSON(params)) + }) + } +} diff --git a/internal/params/tuple.go b/internal/params/tuple.go index 0762cdb05..7718a5026 100644 --- a/internal/params/tuple.go +++ b/internal/params/tuple.go @@ -170,3 +170,21 @@ func (t *tupleItem) UUID(v [16]byte) *tuple { return t.parent } + +func (t *tupleItem) TzDate(v time.Time) *tuple { + t.parent.values = append(t.parent.values, value.TzDateValueFromTime(v)) + + return t.parent +} + +func (t *tupleItem) TzTimestamp(v time.Time) *tuple { + t.parent.values = append(t.parent.values, value.TzTimestampValueFromTime(v)) + + return t.parent +} + +func (t *tupleItem) TzDatetime(v time.Time) *tuple { + t.parent.values = append(t.parent.values, value.TzDatetimeValueFromTime(v)) + + return t.parent +} diff --git a/internal/params/tuple_test.go b/internal/params/tuple_test.go index 9938fae0c..1c6510603 100644 --- a/internal/params/tuple_test.go +++ b/internal/params/tuple_test.go @@ -13,723 +13,476 @@ import ( ) func TestTuple(t *testing.T) { - for _, tt := range []struct { - name string - builder Builder - params map[string]*Ydb.TypedValue + type expected struct { + Type *Ydb.Type + Value *Ydb.Value + } + + tests := []struct { + method string + args []any + + expected expected }{ { - name: xtest.CurrentFileLine(), - builder: Builder{}.Param("$x").BeginTuple().Add().Uint64(123).EndTuple(), - params: map[string]*Ydb.TypedValue{ - "$x": { - Type: &Ydb.Type{ - Type: &Ydb.Type_TupleType{ - TupleType: &Ydb.TupleType{ - Elements: []*Ydb.Type{ - { - Type: &Ydb.Type_TypeId{ - TypeId: Ydb.Type_UINT64, - }, - }, - }, - }, - }, - }, - Value: &Ydb.Value{ - Items: []*Ydb.Value{ - { - Value: &Ydb.Value_Uint64Value{ - Uint64Value: 123, - }, - }, - }, + method: "Uint64", + args: []any{uint64(123)}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_UINT64}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 123, }, }, }, }, { - name: xtest.CurrentFileLine(), - builder: Builder{}.Param("$x").BeginTuple().Add().Int64(123).EndTuple(), - params: map[string]*Ydb.TypedValue{ - "$x": { - Type: &Ydb.Type{ - Type: &Ydb.Type_TupleType{ - TupleType: &Ydb.TupleType{ - Elements: []*Ydb.Type{ - { - Type: &Ydb.Type_TypeId{ - TypeId: Ydb.Type_INT64, - }, - }, - }, - }, - }, - }, - Value: &Ydb.Value{ - Items: []*Ydb.Value{ - { - Value: &Ydb.Value_Int64Value{ - Int64Value: 123, - }, - }, - }, + method: "Int64", + args: []any{int64(123)}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_INT64}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_Int64Value{ + Int64Value: 123, }, }, }, }, { - name: xtest.CurrentFileLine(), - builder: Builder{}.Param("$x").BeginTuple().Add().Uint32(123).EndTuple(), - params: map[string]*Ydb.TypedValue{ - "$x": { - Type: &Ydb.Type{ - Type: &Ydb.Type_TupleType{ - TupleType: &Ydb.TupleType{ - Elements: []*Ydb.Type{ - { - Type: &Ydb.Type_TypeId{ - TypeId: Ydb.Type_UINT32, - }, - }, - }, - }, - }, - }, - Value: &Ydb.Value{ - Items: []*Ydb.Value{ - { - Value: &Ydb.Value_Uint32Value{ - Uint32Value: 123, - }, - }, - }, + method: "Uint32", + args: []any{uint32(123)}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_UINT32}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_Uint32Value{ + Uint32Value: 123, }, }, }, }, { - name: xtest.CurrentFileLine(), - builder: Builder{}.Param("$x").BeginTuple().Add().Int32(123).EndTuple(), - params: map[string]*Ydb.TypedValue{ - "$x": { - Type: &Ydb.Type{ - Type: &Ydb.Type_TupleType{ - TupleType: &Ydb.TupleType{ - Elements: []*Ydb.Type{ - { - Type: &Ydb.Type_TypeId{ - TypeId: Ydb.Type_INT32, - }, - }, - }, - }, - }, - }, - Value: &Ydb.Value{ - Items: []*Ydb.Value{ - { - Value: &Ydb.Value_Int32Value{ - Int32Value: 123, - }, - }, - }, + method: "Int32", + args: []any{int32(123)}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_INT32}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_Int32Value{ + Int32Value: 123, }, }, }, }, { - name: xtest.CurrentFileLine(), - builder: Builder{}.Param("$x").BeginTuple().Add().Uint16(123).EndTuple(), - params: map[string]*Ydb.TypedValue{ - "$x": { - Type: &Ydb.Type{ - Type: &Ydb.Type_TupleType{ - TupleType: &Ydb.TupleType{ - Elements: []*Ydb.Type{ - { - Type: &Ydb.Type_TypeId{ - TypeId: Ydb.Type_UINT16, - }, - }, - }, - }, - }, - }, - Value: &Ydb.Value{ - Items: []*Ydb.Value{ - { - Value: &Ydb.Value_Uint32Value{ - Uint32Value: 123, - }, - }, - }, + method: "Uint16", + args: []any{uint16(123)}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_UINT16}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_Uint32Value{ + Uint32Value: 123, }, }, }, }, { - name: xtest.CurrentFileLine(), - builder: Builder{}.Param("$x").BeginTuple().Add().Int16(123).EndTuple(), - params: map[string]*Ydb.TypedValue{ - "$x": { - Type: &Ydb.Type{ - Type: &Ydb.Type_TupleType{ - TupleType: &Ydb.TupleType{ - Elements: []*Ydb.Type{ - { - Type: &Ydb.Type_TypeId{ - TypeId: Ydb.Type_INT16, - }, - }, - }, - }, - }, - }, - Value: &Ydb.Value{ - Items: []*Ydb.Value{ - { - Value: &Ydb.Value_Int32Value{ - Int32Value: 123, - }, - }, - }, + method: "Int16", + args: []any{int16(123)}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_INT16}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_Int32Value{ + Int32Value: 123, }, }, }, }, { - name: xtest.CurrentFileLine(), - builder: Builder{}.Param("$x").BeginTuple().Add().Uint8(123).EndTuple(), - params: map[string]*Ydb.TypedValue{ - "$x": { - Type: &Ydb.Type{ - Type: &Ydb.Type_TupleType{ - TupleType: &Ydb.TupleType{ - Elements: []*Ydb.Type{ - { - Type: &Ydb.Type_TypeId{ - TypeId: Ydb.Type_UINT8, - }, - }, - }, - }, - }, - }, - Value: &Ydb.Value{ - Items: []*Ydb.Value{ - { - Value: &Ydb.Value_Uint32Value{ - Uint32Value: 123, - }, - }, - }, + method: "Uint8", + args: []any{uint8(123)}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_UINT8}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_Uint32Value{ + Uint32Value: 123, }, }, }, }, { - name: xtest.CurrentFileLine(), - builder: Builder{}.Param("$x").BeginTuple().Add().Int8(123).EndTuple(), - params: map[string]*Ydb.TypedValue{ - "$x": { - Type: &Ydb.Type{ - Type: &Ydb.Type_TupleType{ - TupleType: &Ydb.TupleType{ - Elements: []*Ydb.Type{ - { - Type: &Ydb.Type_TypeId{ - TypeId: Ydb.Type_INT8, - }, - }, - }, - }, - }, - }, - Value: &Ydb.Value{ - Items: []*Ydb.Value{ - { - Value: &Ydb.Value_Int32Value{ - Int32Value: 123, - }, - }, - }, + method: "Int8", + args: []any{int8(123)}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_INT8}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_Int32Value{ + Int32Value: 123, }, }, }, }, { - name: xtest.CurrentFileLine(), - builder: Builder{}.Param("$x").BeginTuple().Add().Bool(true).EndTuple(), - params: map[string]*Ydb.TypedValue{ - "$x": { - Type: &Ydb.Type{ - Type: &Ydb.Type_TupleType{ - TupleType: &Ydb.TupleType{ - Elements: []*Ydb.Type{ - { - Type: &Ydb.Type_TypeId{ - TypeId: Ydb.Type_BOOL, - }, - }, - }, - }, - }, - }, - Value: &Ydb.Value{ - Items: []*Ydb.Value{ - { - Value: &Ydb.Value_BoolValue{ - BoolValue: true, - }, - }, - }, + method: "Bool", + args: []any{true}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_BOOL}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_BoolValue{ + BoolValue: true, }, }, }, }, { - name: xtest.CurrentFileLine(), - builder: Builder{}.Param("$x").BeginTuple().Add().Text("test").EndTuple(), - params: map[string]*Ydb.TypedValue{ - "$x": { - Type: &Ydb.Type{ - Type: &Ydb.Type_TupleType{ - TupleType: &Ydb.TupleType{ - Elements: []*Ydb.Type{ - { - Type: &Ydb.Type_TypeId{ - TypeId: Ydb.Type_UTF8, - }, - }, - }, - }, - }, - }, - Value: &Ydb.Value{ - Items: []*Ydb.Value{ - { - Value: &Ydb.Value_TextValue{ - TextValue: "test", - }, - }, - }, + method: "Text", + args: []any{"test"}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_UTF8}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_TextValue{ + TextValue: "test", }, }, }, }, { - name: xtest.CurrentFileLine(), - builder: Builder{}.Param("$x").BeginTuple().Add().Bytes([]byte("test")).EndTuple(), - params: map[string]*Ydb.TypedValue{ - "$x": { - Type: &Ydb.Type{ - Type: &Ydb.Type_TupleType{ - TupleType: &Ydb.TupleType{ - Elements: []*Ydb.Type{ - { - Type: &Ydb.Type_TypeId{ - TypeId: Ydb.Type_STRING, - }, - }, - }, - }, - }, - }, - Value: &Ydb.Value{ - Items: []*Ydb.Value{ - { - Value: &Ydb.Value_BytesValue{ - BytesValue: []byte("test"), - }, - }, - }, + method: "Bytes", + args: []any{[]byte("test")}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_STRING}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_BytesValue{ + BytesValue: []byte("test"), }, }, }, }, { - name: xtest.CurrentFileLine(), - builder: Builder{}.Param("$x").BeginTuple().Add().Float(123).EndTuple(), - params: map[string]*Ydb.TypedValue{ - "$x": { - Type: &Ydb.Type{ - Type: &Ydb.Type_TupleType{ - TupleType: &Ydb.TupleType{ - Elements: []*Ydb.Type{ - { - Type: &Ydb.Type_TypeId{ - TypeId: Ydb.Type_FLOAT, - }, - }, - }, - }, - }, - }, - Value: &Ydb.Value{ - Items: []*Ydb.Value{ - { - Value: &Ydb.Value_FloatValue{ - FloatValue: 123, - }, - }, - }, + method: "Float", + args: []any{float32(123)}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_FLOAT}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_FloatValue{ + FloatValue: float32(123), }, }, }, }, { - name: xtest.CurrentFileLine(), - builder: Builder{}.Param("$x").BeginTuple().Add().Double(123).EndTuple(), - params: map[string]*Ydb.TypedValue{ - "$x": { - Type: &Ydb.Type{ - Type: &Ydb.Type_TupleType{ - TupleType: &Ydb.TupleType{ - Elements: []*Ydb.Type{ - { - Type: &Ydb.Type_TypeId{ - TypeId: Ydb.Type_DOUBLE, - }, - }, - }, - }, - }, - }, - Value: &Ydb.Value{ - Items: []*Ydb.Value{ - { - Value: &Ydb.Value_DoubleValue{ - DoubleValue: 123, - }, - }, - }, + method: "Double", + args: []any{float64(123)}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_DOUBLE}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_DoubleValue{ + DoubleValue: float64(123), }, }, }, }, { - name: xtest.CurrentFileLine(), - builder: Builder{}.Param("$x").BeginTuple().Add().Interval(time.Second).EndTuple(), - params: map[string]*Ydb.TypedValue{ - "$x": { - Type: &Ydb.Type{ - Type: &Ydb.Type_TupleType{ - TupleType: &Ydb.TupleType{ - Elements: []*Ydb.Type{ - { - Type: &Ydb.Type_TypeId{ - TypeId: Ydb.Type_INTERVAL, - }, - }, - }, - }, - }, - }, - Value: &Ydb.Value{ - Items: []*Ydb.Value{ - { - Value: &Ydb.Value_Int64Value{ - Int64Value: 1000000, - }, - }, - }, + method: "Interval", + args: []any{time.Second}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_INTERVAL}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_Int64Value{ + Int64Value: 1000000, }, }, }, }, { - name: xtest.CurrentFileLine(), - builder: Builder{}.Param("$x").BeginTuple().Add().Datetime(time.Unix(123456789, 456)).EndTuple(), - params: map[string]*Ydb.TypedValue{ - "$x": { - Type: &Ydb.Type{ - Type: &Ydb.Type_TupleType{ - TupleType: &Ydb.TupleType{ - Elements: []*Ydb.Type{ - { - Type: &Ydb.Type_TypeId{ - TypeId: Ydb.Type_DATETIME, - }, - }, - }, - }, - }, - }, - Value: &Ydb.Value{ - Items: []*Ydb.Value{ - { - Value: &Ydb.Value_Uint32Value{ - Uint32Value: 123456789, - }, - }, - }, + method: "Datetime", + args: []any{time.Unix(123456789, 456)}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_DATETIME}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_Uint32Value{ + Uint32Value: 123456789, }, }, }, }, { - name: xtest.CurrentFileLine(), - builder: Builder{}.Param("$x").BeginTuple().Add().Date(time.Unix(123456789, 456)).EndTuple(), - params: map[string]*Ydb.TypedValue{ - "$x": { - Type: &Ydb.Type{ - Type: &Ydb.Type_TupleType{ - TupleType: &Ydb.TupleType{ - Elements: []*Ydb.Type{ - { - Type: &Ydb.Type_TypeId{ - TypeId: Ydb.Type_DATE, - }, - }, - }, - }, - }, - }, - Value: &Ydb.Value{ - Items: []*Ydb.Value{ - { - Value: &Ydb.Value_Uint32Value{ - Uint32Value: 1428, - }, - }, - }, + method: "Date", + args: []any{time.Unix(123456789, 456)}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_DATE}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_Uint32Value{ + Uint32Value: 1428, }, }, }, }, { - name: xtest.CurrentFileLine(), - builder: Builder{}.Param("$x").BeginTuple().Add().Timestamp(time.Unix(123456789, 456)).EndTuple(), - params: map[string]*Ydb.TypedValue{ - "$x": { - Type: &Ydb.Type{ - Type: &Ydb.Type_TupleType{ - TupleType: &Ydb.TupleType{ - Elements: []*Ydb.Type{ - { - Type: &Ydb.Type_TypeId{ - TypeId: Ydb.Type_TIMESTAMP, - }, - }, - }, - }, - }, - }, - Value: &Ydb.Value{ - Items: []*Ydb.Value{ - { - Value: &Ydb.Value_Uint64Value{ - Uint64Value: 123456789000000, - }, - }, - }, + method: "Timestamp", + args: []any{time.Unix(123456789, 456)}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_TIMESTAMP}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 123456789000000, }, }, }, }, { - name: xtest.CurrentFileLine(), - builder: Builder{}.Param("$x").BeginTuple().Add().Decimal([...]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, 22, 9).EndTuple(), //nolint:lll - params: map[string]*Ydb.TypedValue{ - "$x": { - Type: &Ydb.Type{ - Type: &Ydb.Type_TupleType{ - TupleType: &Ydb.TupleType{ - Elements: []*Ydb.Type{ - { - Type: &Ydb.Type_DecimalType{ - DecimalType: &Ydb.DecimalType{ - Precision: 22, - Scale: 9, - }, - }, - }, - }, - }, + method: "Decimal", + args: []any{[...]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, uint32(22), uint32(9)}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_DecimalType{ + DecimalType: &Ydb.DecimalType{ + Precision: 22, + Scale: 9, }, }, - Value: &Ydb.Value{ - Items: []*Ydb.Value{ - { - High_128: 72623859790382856, - Value: &Ydb.Value_Low_128{ - Low_128: 648519454493508870, - }, - }, - }, + }, + Value: &Ydb.Value{ + High_128: 72623859790382856, + Value: &Ydb.Value_Low_128{ + Low_128: 648519454493508870, }, }, }, }, { - name: xtest.CurrentFileLine(), - builder: Builder{}.Param("$x").BeginTuple().Add().JSON(`{"a": 1,"b": "B"}`).EndTuple(), - params: map[string]*Ydb.TypedValue{ - "$x": { - Type: &Ydb.Type{ - Type: &Ydb.Type_TupleType{ - TupleType: &Ydb.TupleType{ - Elements: []*Ydb.Type{ - { - Type: &Ydb.Type_TypeId{ - TypeId: Ydb.Type_JSON, - }, - }, - }, - }, - }, + method: "JSON", + args: []any{`{"a": 1,"b": "B"}`}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_JSON}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_TextValue{ + TextValue: `{"a": 1,"b": "B"}`, }, - Value: &Ydb.Value{ - Items: []*Ydb.Value{ - { - Value: &Ydb.Value_TextValue{ - TextValue: `{"a": 1,"b": "B"}`, - }, - }, - }, + }, + }, + }, + { + method: "JSONDocument", + args: []any{`{"a": 1,"b": "B"}`}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_JSON_DOCUMENT}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_TextValue{ + TextValue: `{"a": 1,"b": "B"}`, }, }, }, }, { - name: xtest.CurrentFileLine(), - builder: Builder{}.Param("$x").BeginTuple().Add().JSONDocument(`{"a": 1,"b": "B"}`).EndTuple(), - params: map[string]*Ydb.TypedValue{ - "$x": { - Type: &Ydb.Type{ - Type: &Ydb.Type_TupleType{ - TupleType: &Ydb.TupleType{ - Elements: []*Ydb.Type{ - { - Type: &Ydb.Type_TypeId{ - TypeId: Ydb.Type_JSON_DOCUMENT, - }, - }, - }, - }, - }, + method: "YSON", + args: []any{[]byte(`{"a": 1,"b": "B"}`)}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_YSON}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_BytesValue{ + BytesValue: []byte(`{"a": 1,"b": "B"}`), }, - Value: &Ydb.Value{ - Items: []*Ydb.Value{ - { - Value: &Ydb.Value_TextValue{ - TextValue: `{"a": 1,"b": "B"}`, - }, - }, - }, + }, + }, + }, + { + method: "UUID", + args: []any{[...]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_UUID}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_Low_128{ + Low_128: 651345242494996240, }, + High_128: 72623859790382856, }, }, }, { - name: xtest.CurrentFileLine(), - builder: Builder{}.Param("$x").BeginTuple().Add().YSON([]byte(`[ 1; 2; 3; 4; 5 ]`)).EndTuple(), - params: map[string]*Ydb.TypedValue{ - "$x": { - Type: &Ydb.Type{ - Type: &Ydb.Type_TupleType{ - TupleType: &Ydb.TupleType{ - Elements: []*Ydb.Type{ - { - Type: &Ydb.Type_TypeId{ - TypeId: Ydb.Type_YSON, - }, - }, - }, - }, - }, + method: "TzDatetime", + args: []any{time.Unix(123456789, 456).UTC()}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_TZ_DATETIME}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_TextValue{ + TextValue: "1973-11-29T21:33:09Z", }, - Value: &Ydb.Value{ - Items: []*Ydb.Value{ - { - Value: &Ydb.Value_BytesValue{ - BytesValue: []byte(`[ 1; 2; 3; 4; 5 ]`), - }, - }, - }, + }, + }, + }, + { + method: "TzDate", + args: []any{time.Unix(123456789, 456).UTC()}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_TZ_DATE}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_TextValue{ + TextValue: "1973-11-29", }, }, }, }, { - name: xtest.CurrentFileLine(), - builder: Builder{}.Param("$x").BeginTuple().Add(). - UUID([...]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}).EndTuple(), - params: map[string]*Ydb.TypedValue{ - "$x": { - Type: &Ydb.Type{ - Type: &Ydb.Type_TupleType{ - TupleType: &Ydb.TupleType{ - Elements: []*Ydb.Type{ - { - Type: &Ydb.Type_TypeId{ - TypeId: Ydb.Type_UUID, - }, + method: "TzTimestamp", + args: []any{time.Unix(123456789, 456).UTC()}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_TZ_TIMESTAMP}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_TextValue{ + TextValue: "1973-11-29T21:33:09.000000Z", + }, + }, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.method, func(t *testing.T) { + a := allocator.New() + defer a.Free() + + item := Builder{}.Param("$x").BeginTuple().Add() + + result, ok := xtest.CallMethod(item, tc.method, tc.args...)[0].(*tuple) + require.True(t, ok) + + params := result.EndTuple().Build().ToYDB(a) + require.Equal(t, xtest.ToJSON( + map[string]*Ydb.TypedValue{ + "$x": { + Type: &Ydb.Type{ + Type: &Ydb.Type_TupleType{ + TupleType: &Ydb.TupleType{ + Elements: []*Ydb.Type{ + tc.expected.Type, }, }, }, }, - }, - Value: &Ydb.Value{ - Items: []*Ydb.Value{ - { - Value: &Ydb.Value_Low_128{ - Low_128: 651345242494996240, - }, - High_128: 72623859790382856, + Value: &Ydb.Value{ + Items: []*Ydb.Value{ + tc.expected.Value, }, }, }, - }, - }, - }, - { - name: xtest.CurrentFileLine(), - builder: Builder{}.Param("$x").BeginTuple().AddItems(value.Uint64Value(123), value.Uint64Value(321)).EndTuple(), - params: map[string]*Ydb.TypedValue{ - "$x": { - Type: &Ydb.Type{ - Type: &Ydb.Type_TupleType{ - TupleType: &Ydb.TupleType{ - Elements: []*Ydb.Type{ - { - Type: &Ydb.Type_TypeId{ - TypeId: Ydb.Type_UINT64, - }, + }), xtest.ToJSON(params)) + }) + } +} + +func TestTuple_AddItems(t *testing.T) { + a := allocator.New() + defer a.Free() + params := Builder{}.Param("$x").BeginTuple(). + AddItems(value.Uint64Value(123), value.Uint64Value(321)). + EndTuple().Build().ToYDB(a) + require.Equal(t, xtest.ToJSON( + map[string]*Ydb.TypedValue{ + "$x": { + Type: &Ydb.Type{ + Type: &Ydb.Type_TupleType{ + TupleType: &Ydb.TupleType{ + Elements: []*Ydb.Type{ + { + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UINT64, }, - { - Type: &Ydb.Type_TypeId{ - TypeId: Ydb.Type_UINT64, - }, + }, + { + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UINT64, }, }, }, }, }, - Value: &Ydb.Value{ - Items: []*Ydb.Value{ - { - Value: &Ydb.Value_Uint64Value{ - Uint64Value: 123, - }, + }, + Value: &Ydb.Value{ + Items: []*Ydb.Value{ + { + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 123, }, - { - Value: &Ydb.Value_Uint64Value{ - Uint64Value: 321, - }, + }, + { + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 321, }, }, }, }, }, - }, - } { - t.Run(tt.name, func(t *testing.T) { - a := allocator.New() - defer a.Free() - params := tt.builder.Build().ToYDB(a) - require.Equal(t, paramsToJSON(tt.params), paramsToJSON(params)) - }) - } + }), xtest.ToJSON(params)) } diff --git a/internal/params/variant.go b/internal/params/variant.go new file mode 100644 index 000000000..16815e38e --- /dev/null +++ b/internal/params/variant.go @@ -0,0 +1,37 @@ +package params + +import "github.com/ydb-platform/ydb-go-sdk/v3/internal/value" + +type ( + variant struct { + parent Builder + name string + value value.Value + } + + variantBuilder struct { + variant *variant + } +) + +func (vb *variantBuilder) EndVariant() Builder { + vb.variant.parent.params = append(vb.variant.parent.params, &Parameter{ + parent: vb.variant.parent, + name: vb.variant.name, + value: vb.variant.value, + }) + + return vb.variant.parent +} + +func (v *variant) BeginTuple() *variantTuple { + return &variantTuple{ + parent: v, + } +} + +func (v *variant) BeginStruct() *variantStruct { + return &variantStruct{ + parent: v, + } +} diff --git a/internal/params/variant_struct.go b/internal/params/variant_struct.go new file mode 100644 index 000000000..190e7d85f --- /dev/null +++ b/internal/params/variant_struct.go @@ -0,0 +1,489 @@ +package params + +import ( + "time" + + "github.com/ydb-platform/ydb-go-sdk/v3/internal/types" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/value" +) + +type ( + variantStruct struct { + parent *variant + + fields []types.StructField + name string + value value.Value + } + + variantStructField struct { + name string + parent *variantStruct + } + + variantStructItem struct { + parent *variantStruct + } + + variantStructBuilder struct { + parent *variantStruct + } +) + +func (vs *variantStruct) Field(name string) *variantStructField { + return &variantStructField{ + name: name, + parent: vs, + } +} + +func (vs *variantStruct) AddFields(args ...types.StructField) *variantStruct { + vs.fields = append(vs.fields, args...) + + return vs +} + +func (vsf *variantStructField) Text() *variantStruct { + vsf.parent.fields = append(vsf.parent.fields, types.StructField{ + Name: vsf.name, + T: types.Text, + }) + + return vsf.parent +} + +func (vsf *variantStructField) Bytes() *variantStruct { + vsf.parent.fields = append(vsf.parent.fields, types.StructField{ + Name: vsf.name, + T: types.Bytes, + }) + + return vsf.parent +} + +func (vsf *variantStructField) Bool() *variantStruct { + vsf.parent.fields = append(vsf.parent.fields, types.StructField{ + Name: vsf.name, + T: types.Bool, + }) + + return vsf.parent +} + +func (vsf *variantStructField) Uint64() *variantStruct { + vsf.parent.fields = append(vsf.parent.fields, types.StructField{ + Name: vsf.name, + T: types.Uint64, + }) + + return vsf.parent +} + +func (vsf *variantStructField) Int64() *variantStruct { + vsf.parent.fields = append(vsf.parent.fields, types.StructField{ + Name: vsf.name, + T: types.Int64, + }) + + return vsf.parent +} + +func (vsf *variantStructField) Uint32() *variantStruct { + vsf.parent.fields = append(vsf.parent.fields, types.StructField{ + Name: vsf.name, + T: types.Uint32, + }) + + return vsf.parent +} + +func (vsf *variantStructField) Int32() *variantStruct { + vsf.parent.fields = append(vsf.parent.fields, types.StructField{ + Name: vsf.name, + T: types.Int32, + }) + + return vsf.parent +} + +func (vsf *variantStructField) Uint16() *variantStruct { + vsf.parent.fields = append(vsf.parent.fields, types.StructField{ + Name: vsf.name, + T: types.Uint16, + }) + + return vsf.parent +} + +func (vsf *variantStructField) Int16() *variantStruct { + vsf.parent.fields = append(vsf.parent.fields, types.StructField{ + Name: vsf.name, + T: types.Int16, + }) + + return vsf.parent +} + +func (vsf *variantStructField) Uint8() *variantStruct { + vsf.parent.fields = append(vsf.parent.fields, types.StructField{ + Name: vsf.name, + T: types.Uint8, + }) + + return vsf.parent +} + +func (vsf *variantStructField) Int8() *variantStruct { + vsf.parent.fields = append(vsf.parent.fields, types.StructField{ + Name: vsf.name, + T: types.Int8, + }) + + return vsf.parent +} + +func (vsf *variantStructField) Float() *variantStruct { + vsf.parent.fields = append(vsf.parent.fields, types.StructField{ + Name: vsf.name, + T: types.Float, + }) + + return vsf.parent +} + +func (vsf *variantStructField) Double() *variantStruct { + vsf.parent.fields = append(vsf.parent.fields, types.StructField{ + Name: vsf.name, + T: types.Double, + }) + + return vsf.parent +} + +func (vsf *variantStructField) Decimal(precision, scale uint32) *variantStruct { + vsf.parent.fields = append(vsf.parent.fields, types.StructField{ + Name: vsf.name, + T: types.NewDecimal(precision, scale), + }) + + return vsf.parent +} + +func (vsf *variantStructField) Timestamp() *variantStruct { + vsf.parent.fields = append(vsf.parent.fields, types.StructField{ + Name: vsf.name, + T: types.Timestamp, + }) + + return vsf.parent +} + +func (vsf *variantStructField) Date() *variantStruct { + vsf.parent.fields = append(vsf.parent.fields, types.StructField{ + Name: vsf.name, + T: types.Date, + }) + + return vsf.parent +} + +func (vsf *variantStructField) Datetime() *variantStruct { + vsf.parent.fields = append(vsf.parent.fields, types.StructField{ + Name: vsf.name, + T: types.Datetime, + }) + + return vsf.parent +} + +func (vsf *variantStructField) Interval() *variantStruct { + vsf.parent.fields = append(vsf.parent.fields, types.StructField{ + Name: vsf.name, + T: types.Interval, + }) + + return vsf.parent +} + +func (vsf *variantStructField) JSON() *variantStruct { + vsf.parent.fields = append(vsf.parent.fields, types.StructField{ + Name: vsf.name, + T: types.JSON, + }) + + return vsf.parent +} + +func (vsf *variantStructField) JSONDocument() *variantStruct { + vsf.parent.fields = append(vsf.parent.fields, types.StructField{ + Name: vsf.name, + T: types.JSONDocument, + }) + + return vsf.parent +} + +func (vsf *variantStructField) YSON() *variantStruct { + vsf.parent.fields = append(vsf.parent.fields, types.StructField{ + Name: vsf.name, + T: types.YSON, + }) + + return vsf.parent +} + +func (vsf *variantStructField) UUID() *variantStruct { + vsf.parent.fields = append(vsf.parent.fields, types.StructField{ + Name: vsf.name, + T: types.UUID, + }) + + return vsf.parent +} + +func (vsf *variantStructField) TzDate() *variantStruct { + vsf.parent.fields = append(vsf.parent.fields, types.StructField{ + Name: vsf.name, + T: types.TzDate, + }) + + return vsf.parent +} + +func (vsf *variantStructField) TzTimestamp() *variantStruct { + vsf.parent.fields = append(vsf.parent.fields, types.StructField{ + Name: vsf.name, + T: types.TzTimestamp, + }) + + return vsf.parent +} + +func (vsf *variantStructField) TzDatetime() *variantStruct { + vsf.parent.fields = append(vsf.parent.fields, types.StructField{ + Name: vsf.name, + T: types.TzDatetime, + }) + + return vsf.parent +} + +func (vs *variantStruct) Name(name string) *variantStructItem { + vs.name = name + + return &variantStructItem{ + parent: vs, + } +} + +func (vsi *variantStructItem) Text(v string) *variantStructBuilder { + vsi.parent.value = value.TextValue(v) + + return &variantStructBuilder{ + parent: vsi.parent, + } +} + +func (vsi *variantStructItem) Bytes(v []byte) *variantStructBuilder { + vsi.parent.value = value.BytesValue(v) + + return &variantStructBuilder{ + parent: vsi.parent, + } +} + +func (vsi *variantStructItem) Bool(v bool) *variantStructBuilder { + vsi.parent.value = value.BoolValue(v) + + return &variantStructBuilder{ + parent: vsi.parent, + } +} + +func (vsi *variantStructItem) Uint64(v uint64) *variantStructBuilder { + vsi.parent.value = value.Uint64Value(v) + + return &variantStructBuilder{ + parent: vsi.parent, + } +} + +func (vsi *variantStructItem) Int64(v int64) *variantStructBuilder { + vsi.parent.value = value.Int64Value(v) + + return &variantStructBuilder{ + parent: vsi.parent, + } +} + +func (vsi *variantStructItem) Uint32(v uint32) *variantStructBuilder { + vsi.parent.value = value.Uint32Value(v) + + return &variantStructBuilder{ + parent: vsi.parent, + } +} + +func (vsi *variantStructItem) Int32(v int32) *variantStructBuilder { + vsi.parent.value = value.Int32Value(v) + + return &variantStructBuilder{ + parent: vsi.parent, + } +} + +func (vsi *variantStructItem) Uint16(v uint16) *variantStructBuilder { + vsi.parent.value = value.Uint16Value(v) + + return &variantStructBuilder{ + parent: vsi.parent, + } +} + +func (vsi *variantStructItem) Int16(v int16) *variantStructBuilder { + vsi.parent.value = value.Int16Value(v) + + return &variantStructBuilder{ + parent: vsi.parent, + } +} + +func (vsi *variantStructItem) Uint8(v uint8) *variantStructBuilder { + vsi.parent.value = value.Uint8Value(v) + + return &variantStructBuilder{ + parent: vsi.parent, + } +} + +func (vsi *variantStructItem) Int8(v int8) *variantStructBuilder { + vsi.parent.value = value.Int8Value(v) + + return &variantStructBuilder{ + parent: vsi.parent, + } +} + +func (vsi *variantStructItem) Float(v float32) *variantStructBuilder { + vsi.parent.value = value.FloatValue(v) + + return &variantStructBuilder{ + parent: vsi.parent, + } +} + +func (vsi *variantStructItem) Double(v float64) *variantStructBuilder { + vsi.parent.value = value.DoubleValue(v) + + return &variantStructBuilder{ + parent: vsi.parent, + } +} + +func (vsi *variantStructItem) Decimal(v [16]byte, precision, scale uint32) *variantStructBuilder { + vsi.parent.value = value.DecimalValue(v, precision, scale) + + return &variantStructBuilder{ + parent: vsi.parent, + } +} + +func (vsi *variantStructItem) Timestamp(v time.Time) *variantStructBuilder { + vsi.parent.value = value.TimestampValueFromTime(v) + + return &variantStructBuilder{ + parent: vsi.parent, + } +} + +func (vsi *variantStructItem) Date(v time.Time) *variantStructBuilder { + vsi.parent.value = value.DateValueFromTime(v) + + return &variantStructBuilder{ + parent: vsi.parent, + } +} + +func (vsi *variantStructItem) Datetime(v time.Time) *variantStructBuilder { + vsi.parent.value = value.DatetimeValueFromTime(v) + + return &variantStructBuilder{ + parent: vsi.parent, + } +} + +func (vsi *variantStructItem) Interval(v time.Duration) *variantStructBuilder { + vsi.parent.value = value.IntervalValueFromDuration(v) + + return &variantStructBuilder{ + parent: vsi.parent, + } +} + +func (vsi *variantStructItem) JSON(v string) *variantStructBuilder { + vsi.parent.value = value.JSONValue(v) + + return &variantStructBuilder{ + parent: vsi.parent, + } +} + +func (vsi *variantStructItem) JSONDocument(v string) *variantStructBuilder { + vsi.parent.value = value.JSONDocumentValue(v) + + return &variantStructBuilder{ + parent: vsi.parent, + } +} + +func (vsi *variantStructItem) YSON(v []byte) *variantStructBuilder { + vsi.parent.value = value.YSONValue(v) + + return &variantStructBuilder{ + parent: vsi.parent, + } +} + +func (vsi *variantStructItem) UUID(v [16]byte) *variantStructBuilder { + vsi.parent.value = value.UUIDValue(v) + + return &variantStructBuilder{ + parent: vsi.parent, + } +} + +func (vsi *variantStructItem) TzDate(v time.Time) *variantStructBuilder { + vsi.parent.value = value.TzDateValueFromTime(v) + + return &variantStructBuilder{ + parent: vsi.parent, + } +} + +func (vsi *variantStructItem) TzTimestamp(v time.Time) *variantStructBuilder { + vsi.parent.value = value.TzTimestampValueFromTime(v) + + return &variantStructBuilder{ + parent: vsi.parent, + } +} + +func (vsi *variantStructItem) TzDatetime(v time.Time) *variantStructBuilder { + vsi.parent.value = value.TzDatetimeValueFromTime(v) + + return &variantStructBuilder{ + parent: vsi.parent, + } +} + +func (vsb *variantStructBuilder) EndStruct() *variantBuilder { + vsb.parent.parent.value = value.VariantValueStruct( + vsb.parent.value, + vsb.parent.name, + types.NewVariantStruct(vsb.parent.fields...), + ) + + return &variantBuilder{ + variant: vsb.parent.parent, + } +} diff --git a/internal/params/variant_struct_test.go b/internal/params/variant_struct_test.go new file mode 100644 index 000000000..cf8330da0 --- /dev/null +++ b/internal/params/variant_struct_test.go @@ -0,0 +1,557 @@ +package params + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" + "github.com/ydb-platform/ydb-go-genproto/protos/Ydb" + + "github.com/ydb-platform/ydb-go-sdk/v3/internal/allocator" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/types" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/xtest" +) + +func TestVariantStruct(t *testing.T) { + type expected struct { + Type *Ydb.Type + Value *Ydb.Value + } + + tests := []struct { + method string + + typeArgs []any + itemArgs []any + + expected expected + }{ + { + method: "Uint64", + itemArgs: []any{uint64(123)}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_UINT64}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 123, + }, + VariantIndex: 0, + }, + }, + }, + { + method: "Int64", + itemArgs: []any{int64(123)}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_INT64}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_Int64Value{ + Int64Value: 123, + }, + VariantIndex: 0, + }, + }, + }, + { + method: "Uint32", + itemArgs: []any{uint32(123)}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_UINT32}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_Uint32Value{ + Uint32Value: 123, + }, + VariantIndex: 0, + }, + }, + }, + { + method: "Int32", + itemArgs: []any{int32(123)}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_INT32}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_Int32Value{ + Int32Value: 123, + }, + VariantIndex: 0, + }, + }, + }, + { + method: "Uint16", + itemArgs: []any{uint16(123)}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_UINT16}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_Uint32Value{ + Uint32Value: 123, + }, + VariantIndex: 0, + }, + }, + }, + { + method: "Int16", + itemArgs: []any{int16(123)}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_INT16}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_Int32Value{ + Int32Value: 123, + }, + VariantIndex: 0, + }, + }, + }, + { + method: "Uint8", + itemArgs: []any{uint8(123)}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_UINT8}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_Uint32Value{ + Uint32Value: 123, + }, + VariantIndex: 0, + }, + }, + }, + { + method: "Int8", + itemArgs: []any{int8(123)}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_INT8}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_Int32Value{ + Int32Value: 123, + }, + VariantIndex: 0, + }, + }, + }, + { + method: "Bool", + itemArgs: []any{true}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_BOOL}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_BoolValue{ + BoolValue: true, + }, + VariantIndex: 0, + }, + }, + }, + { + method: "Text", + itemArgs: []any{"test"}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_UTF8}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_TextValue{ + TextValue: "test", + }, + VariantIndex: 0, + }, + }, + }, + { + method: "Bytes", + itemArgs: []any{[]byte("test")}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_STRING}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_BytesValue{ + BytesValue: []byte("test"), + }, + VariantIndex: 0, + }, + }, + }, + { + method: "Float", + itemArgs: []any{float32(123)}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_FLOAT}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_FloatValue{ + FloatValue: float32(123), + }, + VariantIndex: 0, + }, + }, + }, + { + method: "Double", + itemArgs: []any{float64(123)}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_DOUBLE}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_DoubleValue{ + DoubleValue: float64(123), + }, + VariantIndex: 0, + }, + }, + }, + { + method: "Interval", + itemArgs: []any{time.Second}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_INTERVAL}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_Int64Value{ + Int64Value: 1000000, + }, + VariantIndex: 0, + }, + }, + }, + { + method: "Datetime", + itemArgs: []any{time.Unix(123456789, 456)}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_DATETIME}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_Uint32Value{ + Uint32Value: 123456789, + }, + VariantIndex: 0, + }, + }, + }, + { + method: "Date", + itemArgs: []any{time.Unix(123456789, 456)}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_DATE}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_Uint32Value{ + Uint32Value: 1428, + }, + VariantIndex: 0, + }, + }, + }, + { + method: "Timestamp", + itemArgs: []any{time.Unix(123456789, 456)}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_TIMESTAMP}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 123456789000000, + }, + VariantIndex: 0, + }, + }, + }, + { + method: "Decimal", + typeArgs: []any{uint32(22), uint32(9)}, + itemArgs: []any{[...]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, uint32(22), uint32(9)}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_DecimalType{ + DecimalType: &Ydb.DecimalType{ + Precision: 22, + Scale: 9, + }, + }, + }, + Value: &Ydb.Value{ + High_128: 72623859790382856, + Value: &Ydb.Value_Low_128{ + Low_128: 648519454493508870, + }, + VariantIndex: 0, + }, + }, + }, + { + method: "JSON", + itemArgs: []any{`{"a": 1,"b": "B"}`}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_JSON}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_TextValue{ + TextValue: `{"a": 1,"b": "B"}`, + }, + VariantIndex: 0, + }, + }, + }, + { + method: "JSONDocument", + itemArgs: []any{`{"a": 1,"b": "B"}`}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_JSON_DOCUMENT}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_TextValue{ + TextValue: `{"a": 1,"b": "B"}`, + }, + VariantIndex: 0, + }, + }, + }, + { + method: "YSON", + itemArgs: []any{[]byte(`{"a": 1,"b": "B"}`)}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_YSON}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_BytesValue{ + BytesValue: []byte(`{"a": 1,"b": "B"}`), + }, + VariantIndex: 0, + }, + }, + }, + { + method: "UUID", + itemArgs: []any{[...]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_UUID}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_Low_128{ + Low_128: 651345242494996240, + }, + High_128: 72623859790382856, + VariantIndex: 0, + }, + }, + }, + { + method: "TzDatetime", + itemArgs: []any{time.Unix(123456789, 456).UTC()}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_TZ_DATETIME}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_TextValue{ + TextValue: "1973-11-29T21:33:09Z", + }, + VariantIndex: 0, + }, + }, + }, + { + method: "TzDate", + itemArgs: []any{time.Unix(123456789, 456).UTC()}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_TZ_DATE}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_TextValue{ + TextValue: "1973-11-29", + }, + VariantIndex: 0, + }, + }, + }, + { + method: "TzTimestamp", + itemArgs: []any{time.Unix(123456789, 456).UTC()}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_TZ_TIMESTAMP}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_TextValue{ + TextValue: "1973-11-29T21:33:09.000000Z", + }, + VariantIndex: 0, + }, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.method, func(t *testing.T) { + a := allocator.New() + defer a.Free() + + item := Builder{}.Param("$x").BeginVariant().BeginStruct().Field("key") + + vs, ok := xtest.CallMethod(item, tc.method, tc.typeArgs...)[0].(*variantStruct) + require.True(t, ok) + + builder, ok := xtest.CallMethod(vs.Name("key"), tc.method, tc.itemArgs...)[0].(*variantStructBuilder) + require.True(t, ok) + + params := builder.EndStruct().EndVariant().Build().ToYDB(a) + + require.Equal(t, xtest.ToJSON( + map[string]*Ydb.TypedValue{ + "$x": { + Type: &Ydb.Type{ + Type: &Ydb.Type_VariantType{ + VariantType: &Ydb.VariantType{ + Type: &Ydb.VariantType_StructItems{ + StructItems: &Ydb.StructType{ + Members: []*Ydb.StructMember{ + { + Name: "key", + Type: tc.expected.Type, + }, + }, + }, + }, + }, + }, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_NestedValue{ + NestedValue: tc.expected.Value, + }, + VariantIndex: 0, + }, + }, + }), xtest.ToJSON(params)) + }) + } +} + +func TestVariantStruct_AddFields(t *testing.T) { + a := allocator.New() + defer a.Free() + + params := Builder{}.Param("$x").BeginVariant().BeginStruct(). + AddFields([]types.StructField{ + { + Name: "key1", + T: types.Bool, + }, + { + Name: "key2", + T: types.Uint64, + }, + { + Name: "key3", + T: types.Text, + }, + }...).Name("key3").Text("Hello, World!").EndStruct(). + EndVariant().Build().ToYDB(a) + + require.Equal(t, xtest.ToJSON( + map[string]*Ydb.TypedValue{ + "$x": { + Type: &Ydb.Type{ + Type: &Ydb.Type_VariantType{ + VariantType: &Ydb.VariantType{ + Type: &Ydb.VariantType_StructItems{ + StructItems: &Ydb.StructType{ + Members: []*Ydb.StructMember{ + { + Name: "key1", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_BOOL, + }, + }, + }, + { + Name: "key2", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UINT64, + }, + }, + }, + { + Name: "key3", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UTF8, + }, + }, + }, + }, + }, + }, + }, + }, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_NestedValue{ + NestedValue: &Ydb.Value{ + Value: &Ydb.Value_TextValue{ + TextValue: "Hello, World!", + }, + }, + }, + VariantIndex: 2, + }, + }, + }), xtest.ToJSON(params)) +} diff --git a/internal/params/variant_tuple.go b/internal/params/variant_tuple.go new file mode 100644 index 000000000..9fea4fdaa --- /dev/null +++ b/internal/params/variant_tuple.go @@ -0,0 +1,412 @@ +package params + +import ( + "time" + + "github.com/ydb-platform/ydb-go-sdk/v3/internal/types" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/value" +) + +type ( + variantTuple struct { + parent *variant + + types []types.Type + index uint32 + value value.Value + } + + variantTupleTypes struct { + tuple *variantTuple + } + + variantTupleItem struct { + tuple *variantTuple + } + + variantTupleBuilder struct { + tuple *variantTuple + } +) + +func (vt *variantTuple) Types() *variantTupleTypes { + return &variantTupleTypes{ + tuple: vt, + } +} + +func (vtt *variantTupleTypes) AddTypes(args ...types.Type) *variantTupleTypes { + vtt.tuple.types = append(vtt.tuple.types, args...) + + return vtt +} + +func (vtt *variantTupleTypes) Text() *variantTupleTypes { + vtt.tuple.types = append(vtt.tuple.types, types.Text) + + return vtt +} + +func (vtt *variantTupleTypes) Bytes() *variantTupleTypes { + vtt.tuple.types = append(vtt.tuple.types, types.Bytes) + + return vtt +} + +func (vtt *variantTupleTypes) Bool() *variantTupleTypes { + vtt.tuple.types = append(vtt.tuple.types, types.Bool) + + return vtt +} + +func (vtt *variantTupleTypes) Uint64() *variantTupleTypes { + vtt.tuple.types = append(vtt.tuple.types, types.Uint64) + + return vtt +} + +func (vtt *variantTupleTypes) Int64() *variantTupleTypes { + vtt.tuple.types = append(vtt.tuple.types, types.Int64) + + return vtt +} + +func (vtt *variantTupleTypes) Uint32() *variantTupleTypes { + vtt.tuple.types = append(vtt.tuple.types, types.Uint32) + + return vtt +} + +func (vtt *variantTupleTypes) Int32() *variantTupleTypes { + vtt.tuple.types = append(vtt.tuple.types, types.Int32) + + return vtt +} + +func (vtt *variantTupleTypes) Uint16() *variantTupleTypes { + vtt.tuple.types = append(vtt.tuple.types, types.Uint16) + + return vtt +} + +func (vtt *variantTupleTypes) Int16() *variantTupleTypes { + vtt.tuple.types = append(vtt.tuple.types, types.Int16) + + return vtt +} + +func (vtt *variantTupleTypes) Uint8() *variantTupleTypes { + vtt.tuple.types = append(vtt.tuple.types, types.Uint8) + + return vtt +} + +func (vtt *variantTupleTypes) Int8() *variantTupleTypes { + vtt.tuple.types = append(vtt.tuple.types, types.Int8) + + return vtt +} + +func (vtt *variantTupleTypes) Float() *variantTupleTypes { + vtt.tuple.types = append(vtt.tuple.types, types.Float) + + return vtt +} + +func (vtt *variantTupleTypes) Double() *variantTupleTypes { + vtt.tuple.types = append(vtt.tuple.types, types.Double) + + return vtt +} + +func (vtt *variantTupleTypes) Decimal(precision, scale uint32) *variantTupleTypes { + vtt.tuple.types = append(vtt.tuple.types, types.NewDecimal(precision, scale)) + + return vtt +} + +func (vtt *variantTupleTypes) Timestamp() *variantTupleTypes { + vtt.tuple.types = append(vtt.tuple.types, types.Timestamp) + + return vtt +} + +func (vtt *variantTupleTypes) Date() *variantTupleTypes { + vtt.tuple.types = append(vtt.tuple.types, types.Date) + + return vtt +} + +func (vtt *variantTupleTypes) Datetime() *variantTupleTypes { + vtt.tuple.types = append(vtt.tuple.types, types.Datetime) + + return vtt +} + +func (vtt *variantTupleTypes) Interval() *variantTupleTypes { + vtt.tuple.types = append(vtt.tuple.types, types.Interval) + + return vtt +} + +func (vtt *variantTupleTypes) JSON() *variantTupleTypes { + vtt.tuple.types = append(vtt.tuple.types, types.JSON) + + return vtt +} + +func (vtt *variantTupleTypes) JSONDocument() *variantTupleTypes { + vtt.tuple.types = append(vtt.tuple.types, types.JSONDocument) + + return vtt +} + +func (vtt *variantTupleTypes) YSON() *variantTupleTypes { + vtt.tuple.types = append(vtt.tuple.types, types.YSON) + + return vtt +} + +func (vtt *variantTupleTypes) UUID() *variantTupleTypes { + vtt.tuple.types = append(vtt.tuple.types, types.UUID) + + return vtt +} + +func (vtt *variantTupleTypes) TzDate() *variantTupleTypes { + vtt.tuple.types = append(vtt.tuple.types, types.TzDate) + + return vtt +} + +func (vtt *variantTupleTypes) TzTimestamp() *variantTupleTypes { + vtt.tuple.types = append(vtt.tuple.types, types.TzTimestamp) + + return vtt +} + +func (vtt *variantTupleTypes) TzDatetime() *variantTupleTypes { + vtt.tuple.types = append(vtt.tuple.types, types.TzDatetime) + + return vtt +} + +func (vtt *variantTupleTypes) Index(i uint32) *variantTupleItem { + vtt.tuple.index = i + + return &variantTupleItem{ + tuple: vtt.tuple, + } +} + +func (vti *variantTupleItem) Text(v string) *variantTupleBuilder { + vti.tuple.value = value.TextValue(v) + + return &variantTupleBuilder{ + tuple: vti.tuple, + } +} + +func (vti *variantTupleItem) Bytes(v []byte) *variantTupleBuilder { + vti.tuple.value = value.BytesValue(v) + + return &variantTupleBuilder{ + tuple: vti.tuple, + } +} + +func (vti *variantTupleItem) Bool(v bool) *variantTupleBuilder { + vti.tuple.value = value.BoolValue(v) + + return &variantTupleBuilder{ + tuple: vti.tuple, + } +} + +func (vti *variantTupleItem) Uint64(v uint64) *variantTupleBuilder { + vti.tuple.value = value.Uint64Value(v) + + return &variantTupleBuilder{ + tuple: vti.tuple, + } +} + +func (vti *variantTupleItem) Int64(v int64) *variantTupleBuilder { + vti.tuple.value = value.Int64Value(v) + + return &variantTupleBuilder{ + tuple: vti.tuple, + } +} + +func (vti *variantTupleItem) Uint32(v uint32) *variantTupleBuilder { + vti.tuple.value = value.Uint32Value(v) + + return &variantTupleBuilder{ + tuple: vti.tuple, + } +} + +func (vti *variantTupleItem) Int32(v int32) *variantTupleBuilder { + vti.tuple.value = value.Int32Value(v) + + return &variantTupleBuilder{ + tuple: vti.tuple, + } +} + +func (vti *variantTupleItem) Uint16(v uint16) *variantTupleBuilder { + vti.tuple.value = value.Uint16Value(v) + + return &variantTupleBuilder{ + tuple: vti.tuple, + } +} + +func (vti *variantTupleItem) Int16(v int16) *variantTupleBuilder { + vti.tuple.value = value.Int16Value(v) + + return &variantTupleBuilder{ + tuple: vti.tuple, + } +} + +func (vti *variantTupleItem) Uint8(v uint8) *variantTupleBuilder { + vti.tuple.value = value.Uint8Value(v) + + return &variantTupleBuilder{ + tuple: vti.tuple, + } +} + +func (vti *variantTupleItem) Int8(v int8) *variantTupleBuilder { + vti.tuple.value = value.Int8Value(v) + + return &variantTupleBuilder{ + tuple: vti.tuple, + } +} + +func (vti *variantTupleItem) Float(v float32) *variantTupleBuilder { + vti.tuple.value = value.FloatValue(v) + + return &variantTupleBuilder{ + tuple: vti.tuple, + } +} + +func (vti *variantTupleItem) Double(v float64) *variantTupleBuilder { + vti.tuple.value = value.DoubleValue(v) + + return &variantTupleBuilder{ + tuple: vti.tuple, + } +} + +func (vti *variantTupleItem) Decimal(v [16]byte, precision, scale uint32) *variantTupleBuilder { + vti.tuple.value = value.DecimalValue(v, precision, scale) + + return &variantTupleBuilder{ + tuple: vti.tuple, + } +} + +func (vti *variantTupleItem) Timestamp(v time.Time) *variantTupleBuilder { + vti.tuple.value = value.TimestampValueFromTime(v) + + return &variantTupleBuilder{ + tuple: vti.tuple, + } +} + +func (vti *variantTupleItem) Date(v time.Time) *variantTupleBuilder { + vti.tuple.value = value.DateValueFromTime(v) + + return &variantTupleBuilder{ + tuple: vti.tuple, + } +} + +func (vti *variantTupleItem) Datetime(v time.Time) *variantTupleBuilder { + vti.tuple.value = value.DatetimeValueFromTime(v) + + return &variantTupleBuilder{ + tuple: vti.tuple, + } +} + +func (vti *variantTupleItem) Interval(v time.Duration) *variantTupleBuilder { + vti.tuple.value = value.IntervalValueFromDuration(v) + + return &variantTupleBuilder{ + tuple: vti.tuple, + } +} + +func (vti *variantTupleItem) JSON(v string) *variantTupleBuilder { + vti.tuple.value = value.JSONValue(v) + + return &variantTupleBuilder{ + tuple: vti.tuple, + } +} + +func (vti *variantTupleItem) JSONDocument(v string) *variantTupleBuilder { + vti.tuple.value = value.JSONDocumentValue(v) + + return &variantTupleBuilder{ + tuple: vti.tuple, + } +} + +func (vti *variantTupleItem) YSON(v []byte) *variantTupleBuilder { + vti.tuple.value = value.YSONValue(v) + + return &variantTupleBuilder{ + tuple: vti.tuple, + } +} + +func (vti *variantTupleItem) UUID(v [16]byte) *variantTupleBuilder { + vti.tuple.value = value.UUIDValue(v) + + return &variantTupleBuilder{ + tuple: vti.tuple, + } +} + +func (vti *variantTupleItem) TzDate(v time.Time) *variantTupleBuilder { + vti.tuple.value = value.TzDateValueFromTime(v) + + return &variantTupleBuilder{ + tuple: vti.tuple, + } +} + +func (vti *variantTupleItem) TzTimestamp(v time.Time) *variantTupleBuilder { + vti.tuple.value = value.TzTimestampValueFromTime(v) + + return &variantTupleBuilder{ + tuple: vti.tuple, + } +} + +func (vti *variantTupleItem) TzDatetime(v time.Time) *variantTupleBuilder { + vti.tuple.value = value.TzDatetimeValueFromTime(v) + + return &variantTupleBuilder{ + tuple: vti.tuple, + } +} + +func (vtb *variantTupleBuilder) EndTuple() *variantBuilder { + vtb.tuple.parent.value = value.VariantValueTuple( + vtb.tuple.value, + vtb.tuple.index, + types.NewVariantTuple(vtb.tuple.types...), + ) + + return &variantBuilder{ + variant: vtb.tuple.parent, + } +} diff --git a/internal/params/variant_tuple_test.go b/internal/params/variant_tuple_test.go new file mode 100644 index 000000000..b24480d38 --- /dev/null +++ b/internal/params/variant_tuple_test.go @@ -0,0 +1,528 @@ +package params + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" + "github.com/ydb-platform/ydb-go-genproto/protos/Ydb" + + "github.com/ydb-platform/ydb-go-sdk/v3/internal/allocator" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/types" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/xtest" +) + +func TestVariantTuple(t *testing.T) { + type expected struct { + Type *Ydb.Type + Value *Ydb.Value + } + + tests := []struct { + method string + + typeArgs []any + itemArgs []any + + expected expected + }{ + { + method: "Uint64", + itemArgs: []any{uint64(123)}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_UINT64}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 123, + }, + VariantIndex: 0, + }, + }, + }, + { + method: "Int64", + itemArgs: []any{int64(123)}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_INT64}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_Int64Value{ + Int64Value: 123, + }, + VariantIndex: 0, + }, + }, + }, + { + method: "Uint32", + itemArgs: []any{uint32(123)}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_UINT32}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_Uint32Value{ + Uint32Value: 123, + }, + VariantIndex: 0, + }, + }, + }, + { + method: "Int32", + itemArgs: []any{int32(123)}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_INT32}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_Int32Value{ + Int32Value: 123, + }, + VariantIndex: 0, + }, + }, + }, + { + method: "Uint16", + itemArgs: []any{uint16(123)}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_UINT16}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_Uint32Value{ + Uint32Value: 123, + }, + VariantIndex: 0, + }, + }, + }, + { + method: "Int16", + itemArgs: []any{int16(123)}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_INT16}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_Int32Value{ + Int32Value: 123, + }, + VariantIndex: 0, + }, + }, + }, + { + method: "Uint8", + itemArgs: []any{uint8(123)}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_UINT8}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_Uint32Value{ + Uint32Value: 123, + }, + VariantIndex: 0, + }, + }, + }, + { + method: "Int8", + itemArgs: []any{int8(123)}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_INT8}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_Int32Value{ + Int32Value: 123, + }, + VariantIndex: 0, + }, + }, + }, + { + method: "Bool", + itemArgs: []any{true}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_BOOL}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_BoolValue{ + BoolValue: true, + }, + VariantIndex: 0, + }, + }, + }, + { + method: "Text", + itemArgs: []any{"test"}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_UTF8}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_TextValue{ + TextValue: "test", + }, + VariantIndex: 0, + }, + }, + }, + { + method: "Bytes", + itemArgs: []any{[]byte("test")}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_STRING}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_BytesValue{ + BytesValue: []byte("test"), + }, + VariantIndex: 0, + }, + }, + }, + { + method: "Float", + itemArgs: []any{float32(123)}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_FLOAT}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_FloatValue{ + FloatValue: float32(123), + }, + VariantIndex: 0, + }, + }, + }, + { + method: "Double", + itemArgs: []any{float64(123)}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_DOUBLE}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_DoubleValue{ + DoubleValue: float64(123), + }, + VariantIndex: 0, + }, + }, + }, + { + method: "Interval", + itemArgs: []any{time.Second}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_INTERVAL}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_Int64Value{ + Int64Value: 1000000, + }, + VariantIndex: 0, + }, + }, + }, + { + method: "Datetime", + itemArgs: []any{time.Unix(123456789, 456)}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_DATETIME}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_Uint32Value{ + Uint32Value: 123456789, + }, + VariantIndex: 0, + }, + }, + }, + { + method: "Date", + itemArgs: []any{time.Unix(123456789, 456)}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_DATE}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_Uint32Value{ + Uint32Value: 1428, + }, + VariantIndex: 0, + }, + }, + }, + { + method: "Timestamp", + itemArgs: []any{time.Unix(123456789, 456)}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_TIMESTAMP}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 123456789000000, + }, + VariantIndex: 0, + }, + }, + }, + { + method: "Decimal", + typeArgs: []any{uint32(22), uint32(9)}, + itemArgs: []any{[...]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, uint32(22), uint32(9)}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_DecimalType{ + DecimalType: &Ydb.DecimalType{ + Precision: 22, + Scale: 9, + }, + }, + }, + Value: &Ydb.Value{ + High_128: 72623859790382856, + Value: &Ydb.Value_Low_128{ + Low_128: 648519454493508870, + }, + VariantIndex: 0, + }, + }, + }, + { + method: "JSON", + itemArgs: []any{`{"a": 1,"b": "B"}`}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_JSON}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_TextValue{ + TextValue: `{"a": 1,"b": "B"}`, + }, + VariantIndex: 0, + }, + }, + }, + { + method: "JSONDocument", + itemArgs: []any{`{"a": 1,"b": "B"}`}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_JSON_DOCUMENT}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_TextValue{ + TextValue: `{"a": 1,"b": "B"}`, + }, + VariantIndex: 0, + }, + }, + }, + { + method: "YSON", + itemArgs: []any{[]byte(`{"a": 1,"b": "B"}`)}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_YSON}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_BytesValue{ + BytesValue: []byte(`{"a": 1,"b": "B"}`), + }, + VariantIndex: 0, + }, + }, + }, + { + method: "UUID", + itemArgs: []any{[...]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_UUID}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_Low_128{ + Low_128: 651345242494996240, + }, + High_128: 72623859790382856, + VariantIndex: 0, + }, + }, + }, + { + method: "TzDatetime", + itemArgs: []any{time.Unix(123456789, 456).UTC()}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_TZ_DATETIME}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_TextValue{ + TextValue: "1973-11-29T21:33:09Z", + }, + VariantIndex: 0, + }, + }, + }, + { + method: "TzDate", + itemArgs: []any{time.Unix(123456789, 456).UTC()}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_TZ_DATE}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_TextValue{ + TextValue: "1973-11-29", + }, + VariantIndex: 0, + }, + }, + }, + { + method: "TzTimestamp", + itemArgs: []any{time.Unix(123456789, 456).UTC()}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_TZ_TIMESTAMP}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_TextValue{ + TextValue: "1973-11-29T21:33:09.000000Z", + }, + VariantIndex: 0, + }, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.method, func(t *testing.T) { + a := allocator.New() + defer a.Free() + + item := Builder{}.Param("$x").BeginVariant().BeginTuple().Types() + + types, ok := xtest.CallMethod(item, tc.method, tc.typeArgs...)[0].(*variantTupleTypes) + require.True(t, ok) + + builder, ok := xtest.CallMethod(types.Index(0), tc.method, tc.itemArgs...)[0].(*variantTupleBuilder) + require.True(t, ok) + + params := builder.EndTuple().EndVariant().Build().ToYDB(a) + + require.Equal(t, xtest.ToJSON( + map[string]*Ydb.TypedValue{ + "$x": { + Type: &Ydb.Type{ + Type: &Ydb.Type_VariantType{ + VariantType: &Ydb.VariantType{ + Type: &Ydb.VariantType_TupleItems{ + TupleItems: &Ydb.TupleType{ + Elements: []*Ydb.Type{ + tc.expected.Type, + }, + }, + }, + }, + }, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_NestedValue{ + NestedValue: tc.expected.Value, + }, + }, + }, + }), xtest.ToJSON(params)) + }) + } +} + +func TestVariantTuple_AddTypes(t *testing.T) { + a := allocator.New() + defer a.Free() + + params := Builder{}.Param("$x").BeginVariant().BeginTuple(). + Types().AddTypes(types.Int64, types.Bool). + Index(1). + Bool(true). + EndTuple().EndVariant().Build().ToYDB(a) + + require.Equal(t, xtest.ToJSON( + map[string]*Ydb.TypedValue{ + "$x": { + Type: &Ydb.Type{ + Type: &Ydb.Type_VariantType{ + VariantType: &Ydb.VariantType{ + Type: &Ydb.VariantType_TupleItems{ + TupleItems: &Ydb.TupleType{ + Elements: []*Ydb.Type{ + { + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_INT64, + }, + }, + { + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_BOOL, + }, + }, + }, + }, + }, + }, + }, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_NestedValue{ + NestedValue: &Ydb.Value{ + Value: &Ydb.Value_BoolValue{ + BoolValue: true, + }, + }, + }, + VariantIndex: 1, + }, + }, + }), xtest.ToJSON(params)) +} diff --git a/internal/pool/errors.go b/internal/pool/errors.go index 8e2eb6f21..36b6526c7 100644 --- a/internal/pool/errors.go +++ b/internal/pool/errors.go @@ -2,12 +2,9 @@ package pool import ( "errors" - - "github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors" ) var ( errClosedPool = errors.New("closed pool") - errPoolOverflow = xerrors.Retryable(errors.New("pool overflow")) errItemIsNotAlive = errors.New("item is not alive") ) diff --git a/internal/pool/pool.go b/internal/pool/pool.go index 82b2905ef..3368aadc8 100644 --- a/internal/pool/pool.go +++ b/internal/pool/pool.go @@ -2,10 +2,13 @@ package pool import ( "context" - "sync/atomic" + "time" + + "golang.org/x/sync/errgroup" "github.com/ydb-platform/ydb-go-sdk/v3/internal/pool/stats" "github.com/ydb-platform/ydb-go-sdk/v3/internal/stack" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/xcontext" "github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors" "github.com/ydb-platform/ydb-go-sdk/v3/internal/xsync" "github.com/ydb-platform/ydb-go-sdk/v3/retry" @@ -18,38 +21,112 @@ type ( IsAlive() bool Close(ctx context.Context) error } - Stats struct { - locked *xsync.Locked[stats.Stats] + safeStats struct { + mu xsync.RWMutex + v stats.Stats onChange func(stats.Stats) } + statsItemAddr struct { + v *int + onChange func(func()) + } Pool[PT Item[T], T any] struct { trace *Trace limit int - create func(ctx context.Context) (PT, error) + createItem func(ctx context.Context) (PT, error) + createTimeout time.Duration + closeTimeout time.Duration mu xsync.Mutex idle []PT index map[PT]struct{} + done chan struct{} - done atomic.Bool - - stats *Stats + stats *safeStats } option[PT Item[T], T any] func(p *Pool[PT, T]) ) -func (stats *Stats) Change(f func(s stats.Stats) stats.Stats) { - stats.onChange(stats.locked.Change(f)) +func (field statsItemAddr) Inc() { + field.onChange(func() { + *field.v++ + }) +} + +func (field statsItemAddr) Dec() { + field.onChange(func() { + *field.v-- + }) +} + +func (s *safeStats) Get() stats.Stats { + s.mu.RLock() + defer s.mu.RUnlock() + + return s.v +} + +func (s *safeStats) Index() statsItemAddr { + s.mu.RLock() + defer s.mu.RUnlock() + + return statsItemAddr{ + v: &s.v.Index, + onChange: func(f func()) { + s.mu.WithLock(f) + if s.onChange != nil { + s.onChange(s.Get()) + } + }, + } +} + +func (s *safeStats) Idle() statsItemAddr { + s.mu.RLock() + defer s.mu.RUnlock() + + return statsItemAddr{ + v: &s.v.Idle, + onChange: func(f func()) { + s.mu.WithLock(f) + if s.onChange != nil { + s.onChange(s.Get()) + } + }, + } } -func (stats *Stats) Get() stats.Stats { - return stats.locked.Get() +func (s *safeStats) InUse() statsItemAddr { + s.mu.RLock() + defer s.mu.RUnlock() + + return statsItemAddr{ + v: &s.v.InUse, + onChange: func(f func()) { + s.mu.WithLock(f) + if s.onChange != nil { + s.onChange(s.Get()) + } + }, + } } func WithCreateFunc[PT Item[T], T any](f func(ctx context.Context) (PT, error)) option[PT, T] { return func(p *Pool[PT, T]) { - p.create = f + p.createItem = f + } +} + +func WithCreateItemTimeout[PT Item[T], T any](t time.Duration) option[PT, T] { + return func(p *Pool[PT, T]) { + p.createTimeout = t + } +} + +func WithCloseItemTimeout[PT Item[T], T any](t time.Duration) option[PT, T] { + return func(p *Pool[PT, T]) { + p.closeTimeout = t } } @@ -72,11 +149,12 @@ func New[PT Item[T], T any]( p := &Pool[PT, T]{ trace: defaultTrace, limit: DefaultLimit, - create: func(ctx context.Context) (PT, error) { + createItem: func(ctx context.Context) (PT, error) { var item T return &item, nil }, + done: make(chan struct{}), } for _, opt := range opts { @@ -87,7 +165,7 @@ func New[PT Item[T], T any]( onDone := p.trace.OnNew(&NewStartInfo{ Context: &ctx, - Call: stack.FunctionID(""), + Call: stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/pool.New"), }) defer func() { @@ -96,12 +174,85 @@ func New[PT Item[T], T any]( }) }() + createItem := p.createItem + + p.createItem = func(ctx context.Context) (PT, error) { + var ( + ch = make(chan PT) + createErr error + ) + go func() { + defer close(ch) + createErr = func() error { + var ( + createCtx = xcontext.ValueOnly(ctx) + cancelCreate context.CancelFunc + ) + if d := p.createTimeout; d > 0 { + createCtx, cancelCreate = xcontext.WithTimeout(createCtx, d) + } else { + createCtx, cancelCreate = xcontext.WithCancel(createCtx) + } + defer cancelCreate() + + newItem, err := createItem(createCtx) + if err != nil { + return xerrors.WithStackTrace(err) + } + + needCloseItem := true + defer func() { + if needCloseItem { + _ = p.closeItem(ctx, newItem) + } + }() + + select { + case <-p.done: + return xerrors.WithStackTrace(errClosedPool) + + case <-ctx.Done(): + p.mu.Lock() + defer p.mu.Unlock() + + if len(p.index) < p.limit { + p.idle = append(p.idle, newItem) + p.index[newItem] = struct{}{} + p.stats.Index().Inc() + needCloseItem = false + } + + return xerrors.WithStackTrace(ctx.Err()) + + case ch <- newItem: + needCloseItem = false + + return nil + } + }() + }() + + select { + case <-p.done: + return nil, xerrors.WithStackTrace(errClosedPool) + case <-ctx.Done(): + return nil, xerrors.WithStackTrace(ctx.Err()) + case item, has := <-ch: + if !has { + if ctxErr := ctx.Err(); ctxErr == nil && xerrors.IsContextError(createErr) { + return nil, xerrors.WithStackTrace(xerrors.Retryable(createErr)) + } + + return nil, xerrors.WithStackTrace(createErr) + } + + return item, nil + } + } p.idle = make([]PT, 0, p.limit) p.index = make(map[PT]struct{}, p.limit) - p.stats = &Stats{ - locked: xsync.NewLocked[stats.Stats](stats.Stats{ - Limit: p.limit, - }), + p.stats = &safeStats{ + v: stats.Stats{Limit: p.limit}, onChange: p.trace.OnChange, } @@ -112,10 +263,10 @@ func (p *Pool[PT, T]) Stats() stats.Stats { return p.stats.Get() } -func (p *Pool[PT, T]) get(ctx context.Context) (_ PT, finalErr error) { +func (p *Pool[PT, T]) getItem(ctx context.Context) (_ PT, finalErr error) { onDone := p.trace.OnGet(&GetStartInfo{ Context: &ctx, - Call: stack.FunctionID(""), + Call: stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/pool.(*Pool).getItem"), }) defer func() { onDone(&GetDoneInfo{ @@ -127,63 +278,55 @@ func (p *Pool[PT, T]) get(ctx context.Context) (_ PT, finalErr error) { return nil, xerrors.WithStackTrace(err) } - if p.done.Load() { + select { + case <-p.done: return nil, xerrors.WithStackTrace(errClosedPool) - } - - var item PT - p.mu.WithLock(func() { - if len(p.idle) > 0 { - item, p.idle = p.idle[0], p.idle[1:] - p.stats.Change(func(v stats.Stats) stats.Stats { - v.Idle-- + case <-ctx.Done(): + return nil, xerrors.WithStackTrace(ctx.Err()) + default: + var item PT + p.mu.WithLock(func() { + if len(p.idle) > 0 { + item, p.idle = p.idle[0], p.idle[1:] + p.stats.Idle().Dec() + } + }) - return v + if item != nil { + if item.IsAlive() { + return item, nil + } + _ = p.closeItem(ctx, item) + p.mu.WithLock(func() { + delete(p.index, item) }) + p.stats.Index().Dec() } - }) - if item != nil { - if item.IsAlive() { - return item, nil + item, err := p.createItem(ctx) + if err != nil { + return nil, xerrors.WithStackTrace(err) } - _ = item.Close(ctx) - p.mu.WithLock(func() { - delete(p.index, item) - }) - p.stats.Change(func(v stats.Stats) stats.Stats { - v.Index-- - return v + addedToIndex := false + p.mu.WithLock(func() { + if len(p.index) < p.limit { + p.index[item] = struct{}{} + addedToIndex = true + } }) - } - - p.mu.Lock() - defer p.mu.Unlock() + if addedToIndex { + p.stats.Index().Inc() + } - if len(p.index) == p.limit { - return nil, xerrors.WithStackTrace(errPoolOverflow) + return item, nil } - - item, err := p.create(ctx) - if err != nil { - return nil, xerrors.WithStackTrace(err) - } - - p.index[item] = struct{}{} - p.stats.Change(func(v stats.Stats) stats.Stats { - v.Index++ - - return v - }) - - return item, nil } -func (p *Pool[PT, T]) put(ctx context.Context, item PT) (finalErr error) { +func (p *Pool[PT, T]) putItem(ctx context.Context, item PT) (finalErr error) { onDone := p.trace.OnPut(&PutStartInfo{ Context: &ctx, - Call: stack.FunctionID(""), + Call: stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/pool.(*Pool).putItem"), }) defer func() { onDone(&PutDoneInfo{ @@ -195,41 +338,48 @@ func (p *Pool[PT, T]) put(ctx context.Context, item PT) (finalErr error) { return xerrors.WithStackTrace(err) } - if p.done.Load() { + select { + case <-p.done: return xerrors.WithStackTrace(errClosedPool) - } + default: + if !item.IsAlive() { + _ = p.closeItem(ctx, item) - if !item.IsAlive() { - _ = item.Close(ctx) + p.mu.WithLock(func() { + delete(p.index, item) + }) + p.stats.Index().Dec() - p.mu.WithLock(func() { - delete(p.index, item) - }) - p.stats.Change(func(v stats.Stats) stats.Stats { - v.Index-- + return xerrors.WithStackTrace(errItemIsNotAlive) + } - return v + p.mu.WithLock(func() { + p.idle = append(p.idle, item) }) + p.stats.Idle().Inc() - return xerrors.WithStackTrace(errItemIsNotAlive) + return nil } +} - p.mu.WithLock(func() { - p.idle = append(p.idle, item) - }) - p.stats.Change(func(v stats.Stats) stats.Stats { - v.Idle++ +func (p *Pool[PT, T]) closeItem(ctx context.Context, item PT) error { + ctx = xcontext.ValueOnly(ctx) - return v - }) + var cancel context.CancelFunc + if d := p.closeTimeout; d > 0 { + ctx, cancel = xcontext.WithTimeout(ctx, d) + } else { + ctx, cancel = xcontext.WithCancel(ctx) + } + defer cancel() - return nil + return item.Close(ctx) } func (p *Pool[PT, T]) try(ctx context.Context, f func(ctx context.Context, item PT) error) (finalErr error) { onDone := p.trace.OnTry(&TryStartInfo{ Context: &ctx, - Call: stack.FunctionID(""), + Call: stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/pool.(*Pool).try"), }) defer func() { onDone(&TryDoneInfo{ @@ -237,27 +387,21 @@ func (p *Pool[PT, T]) try(ctx context.Context, f func(ctx context.Context, item }) }() - item, err := p.get(ctx) + item, err := p.getItem(ctx) if err != nil { + if xerrors.IsYdb(err) { + return xerrors.WithStackTrace(xerrors.Retryable(err)) + } + return xerrors.WithStackTrace(err) } defer func() { - _ = p.put(ctx, item) + _ = p.putItem(ctx, item) }() - p.stats.Change(func(v stats.Stats) stats.Stats { - v.InUse++ - - return v - }) - defer func() { - p.stats.Change(func(v stats.Stats) stats.Stats { - v.InUse-- - - return v - }) - }() + p.stats.InUse().Inc() + defer p.stats.InUse().Dec() err = f(ctx, item) if err != nil { @@ -275,7 +419,7 @@ func (p *Pool[PT, T]) With( var ( onDone = p.trace.OnWith(&WithStartInfo{ Context: &ctx, - Call: stack.FunctionID(""), + Call: stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/pool.(*Pool).With"), }) attempts int ) @@ -294,11 +438,9 @@ func (p *Pool[PT, T]) With( return nil }, append(opts, retry.WithTrace(&trace.Retry{ - OnRetry: func(info trace.RetryLoopStartInfo) func(trace.RetryLoopIntermediateInfo) func(trace.RetryLoopDoneInfo) { - return func(info trace.RetryLoopIntermediateInfo) func(trace.RetryLoopDoneInfo) { - return func(info trace.RetryLoopDoneInfo) { - attempts = info.Attempts - } + OnRetry: func(info trace.RetryLoopStartInfo) func(trace.RetryLoopDoneInfo) { + return func(info trace.RetryLoopDoneInfo) { + attempts = info.Attempts } }, }))...) @@ -312,7 +454,7 @@ func (p *Pool[PT, T]) With( func (p *Pool[PT, T]) Close(ctx context.Context) (finalErr error) { onDone := p.trace.OnClose(&CloseStartInfo{ Context: &ctx, - Call: stack.FunctionID(""), + Call: stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/pool.(*Pool).Close"), }) defer func() { onDone(&CloseDoneInfo{ @@ -320,25 +462,21 @@ func (p *Pool[PT, T]) Close(ctx context.Context) (finalErr error) { }) }() - p.done.Store(true) + close(p.done) p.mu.Lock() defer p.mu.Unlock() - errs := make([]error, 0, len(p.index)) - + var g errgroup.Group for item := range p.index { - if err := item.Close(ctx); err != nil { - errs = append(errs, err) - } + item := item + g.Go(func() error { + return item.Close(ctx) + }) } - - switch len(errs) { - case 0: - return nil - case 1: - return errs[0] - default: - return xerrors.Join(errs...) + if err := g.Wait(); err != nil { + return xerrors.WithStackTrace(err) } + + return nil } diff --git a/internal/pool/pool_test.go b/internal/pool/pool_test.go index f1f220804..63f8a1c11 100644 --- a/internal/pool/pool_test.go +++ b/internal/pool/pool_test.go @@ -3,12 +3,16 @@ package pool import ( "context" "errors" + "math/rand" "sync" "sync/atomic" "testing" "time" "github.com/stretchr/testify/require" + "github.com/ydb-platform/ydb-go-genproto/protos/Ydb" + grpcCodes "google.golang.org/grpc/codes" + grpcStatus "google.golang.org/grpc/status" "github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors" "github.com/ydb-platform/ydb-go-sdk/v3/internal/xtest" @@ -73,7 +77,97 @@ func TestPool(t *testing.T) { require.EqualValues(t, p.limit, atomic.LoadInt64(&newCounter)) }) }) - t.Run("Change", func(t *testing.T) { + t.Run("Retry", func(t *testing.T) { + t.Run("CreateItem", func(t *testing.T) { + t.Run("context", func(t *testing.T) { + t.Run("Cancelled", func(t *testing.T) { + var counter int64 + p := New(rootCtx, + WithCreateFunc(func(context.Context) (*testItem, error) { + atomic.AddInt64(&counter, 1) + + if atomic.LoadInt64(&counter) < 10 { + return nil, context.Canceled + } + + var v testItem + + return &v, nil + }), + ) + err := p.With(rootCtx, func(ctx context.Context, item *testItem) error { + return nil + }) + require.NoError(t, err) + require.GreaterOrEqual(t, atomic.LoadInt64(&counter), int64(10)) + }) + t.Run("DeadlineExceeded", func(t *testing.T) { + var counter int64 + p := New(rootCtx, + WithCreateFunc(func(context.Context) (*testItem, error) { + atomic.AddInt64(&counter, 1) + + if atomic.LoadInt64(&counter) < 10 { + return nil, context.DeadlineExceeded + } + + var v testItem + + return &v, nil + }), + ) + err := p.With(rootCtx, func(ctx context.Context, item *testItem) error { + return nil + }) + require.NoError(t, err) + require.GreaterOrEqual(t, atomic.LoadInt64(&counter), int64(10)) + }) + }) + t.Run("OnTransportError", func(t *testing.T) { + var counter int64 + p := New(rootCtx, + WithCreateFunc(func(context.Context) (*testItem, error) { + atomic.AddInt64(&counter, 1) + + if atomic.LoadInt64(&counter) < 10 { + return nil, xerrors.Transport(grpcStatus.Error(grpcCodes.Unavailable, "")) + } + + var v testItem + + return &v, nil + }), + ) + err := p.With(rootCtx, func(ctx context.Context, item *testItem) error { + return nil + }) + require.NoError(t, err) + require.GreaterOrEqual(t, atomic.LoadInt64(&counter), int64(10)) + }) + t.Run("OnOperationError", func(t *testing.T) { + var counter int64 + p := New(rootCtx, + WithCreateFunc(func(context.Context) (*testItem, error) { + atomic.AddInt64(&counter, 1) + + if atomic.LoadInt64(&counter) < 10 { + return nil, xerrors.Operation(xerrors.WithStatusCode(Ydb.StatusIds_UNAVAILABLE)) + } + + var v testItem + + return &v, nil + }), + ) + err := p.With(rootCtx, func(ctx context.Context, item *testItem) error { + return nil + }) + require.NoError(t, err) + require.GreaterOrEqual(t, atomic.LoadInt64(&counter), int64(10)) + }) + }) + }) + t.Run("On", func(t *testing.T) { t.Run("Context", func(t *testing.T) { t.Run("Canceled", func(t *testing.T) { ctx, cancel := context.WithCancel(rootCtx) @@ -133,7 +227,7 @@ func TestPool(t *testing.T) { var ( newItems int64 deleteItems int64 - expErr = xerrors.Retryable(errors.New("expected error"), xerrors.WithDeleteSession()) + expErr = xerrors.Retryable(errors.New("expected error"), xerrors.InvalidObject()) ) p := New(rootCtx, WithLimit[*testItem, testItem](1), @@ -196,3 +290,31 @@ func TestPool(t *testing.T) { }, xtest.StopAfter(42*time.Second)) }) } + +func TestSafeStatsRace(t *testing.T) { + xtest.TestManyTimes(t, func(t testing.TB) { + var ( + wg sync.WaitGroup + s = &safeStats{} + ) + wg.Add(10000) + for range make([]struct{}, 10000) { + go func() { + defer wg.Done() + require.NotPanics(t, func() { + switch rand.Int31n(4) { //nolint:gosec + case 0: + s.Index().Inc() + case 1: + s.InUse().Inc() + case 2: + s.Idle().Inc() + default: + s.Get() + } + }) + }() + } + wg.Wait() + }, xtest.StopAfter(5*time.Second)) +} diff --git a/internal/query/client.go b/internal/query/client.go index a8bfa8ad5..3019e83cd 100644 --- a/internal/query/client.go +++ b/internal/query/client.go @@ -70,7 +70,7 @@ func do( err := op(ctx, s) if err != nil { - if xerrors.MustDeleteSession(err) { + if !xerrors.IsRetryObjectValid(err) { s.setStatus(statusError) } @@ -81,17 +81,9 @@ func do( return nil }, append(doOpts.RetryOpts(), retry.WithTrace(&trace.Retry{ - OnRetry: func( - info trace.RetryLoopStartInfo, - ) func( - trace.RetryLoopIntermediateInfo, - ) func( - trace.RetryLoopDoneInfo, - ) { - return func(info trace.RetryLoopIntermediateInfo) func(trace.RetryLoopDoneInfo) { - return func(info trace.RetryLoopDoneInfo) { - attempts = info.Attempts - } + OnRetry: func(info trace.RetryLoopStartInfo) func(trace.RetryLoopDoneInfo) { + return func(info trace.RetryLoopDoneInfo) { + attempts = info.Attempts } }, }))...) @@ -107,7 +99,9 @@ func (c *Client) Do(ctx context.Context, op query.Operation, opts ...options.DoO case <-c.done: return xerrors.WithStackTrace(errClosedClient) default: - onDone := trace.QueryOnDo(c.config.Trace(), &ctx, stack.FunctionID("")) + onDone := trace.QueryOnDo(c.config.Trace(), &ctx, + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/query.(*Client).Do"), + ) attempts, err := do(ctx, c.pool, op, c.config.Trace(), opts...) onDone(attempts, err) @@ -157,12 +151,14 @@ func doTx( return attempts, nil } -func (c *Client) DoTx(ctx context.Context, op query.TxOperation, opts ...options.DoTxOption) error { +func (c *Client) DoTx(ctx context.Context, op query.TxOperation, opts ...options.DoTxOption) (err error) { select { case <-c.done: return xerrors.WithStackTrace(errClosedClient) default: - onDone := trace.QueryOnDoTx(c.config.Trace(), &ctx, stack.FunctionID("")) + onDone := trace.QueryOnDoTx(c.config.Trace(), &ctx, + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/query.(*Client).DoTx"), + ) attempts, err := doTx(ctx, c.pool, op, c.config.Trace(), opts...) onDone(attempts, err) @@ -171,7 +167,9 @@ func (c *Client) DoTx(ctx context.Context, op query.TxOperation, opts ...options } func New(ctx context.Context, balancer balancer, cfg *config.Config) *Client { - onDone := trace.QueryOnNew(cfg.Trace(), &ctx, stack.FunctionID("")) + onDone := trace.QueryOnNew(cfg.Trace(), &ctx, + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/query.New"), + ) defer onDone() client := &Client{ @@ -183,18 +181,21 @@ func New(ctx context.Context, balancer balancer, cfg *config.Config) *Client { client.pool = pool.New(ctx, pool.WithLimit[*Session, Session](cfg.PoolLimit()), pool.WithTrace[*Session, Session](poolTrace(cfg.Trace())), + pool.WithCreateItemTimeout[*Session, Session](cfg.SessionCreateTimeout()), + pool.WithCloseItemTimeout[*Session, Session](cfg.SessionDeleteTimeout()), pool.WithCreateFunc(func(ctx context.Context) (_ *Session, err error) { - var cancel context.CancelFunc + var ( + createCtx context.Context + cancelCreate context.CancelFunc + ) if d := cfg.SessionCreateTimeout(); d > 0 { - ctx, cancel = xcontext.WithTimeout(ctx, d) + createCtx, cancelCreate = xcontext.WithTimeout(ctx, d) } else { - ctx, cancel = xcontext.WithCancel(ctx) + createCtx, cancelCreate = xcontext.WithCancel(ctx) } - defer cancel() + defer cancelCreate() - s, err := createSession(ctx, - client.grpcClient, - withSessionTrace(cfg.Trace()), + s, err := createSession(createCtx, client.grpcClient, cfg, withSessionCheck(func(s *Session) bool { return balancer.HasNode(uint32(s.nodeID)) }), diff --git a/internal/query/client_test.go b/internal/query/client_test.go index 7c49b22e7..1b7260750 100644 --- a/internal/query/client_test.go +++ b/internal/query/client_test.go @@ -15,6 +15,7 @@ import ( grpcStatus "google.golang.org/grpc/status" "github.com/ydb-platform/ydb-go-sdk/v3/internal/pool" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/query/config" "github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors" "github.com/ydb-platform/ydb-go-sdk/v3/internal/xtest" "github.com/ydb-platform/ydb-go-sdk/v3/query" @@ -39,9 +40,8 @@ func TestCreateSession(t *testing.T) { service.EXPECT().DeleteSession(gomock.Any(), gomock.Any()).Return(&Ydb_Query.DeleteSessionResponse{ Status: Ydb.StatusIds_SUCCESS, }, nil) - t.Log("createSession") attached := 0 - s, err := createSession(ctx, service, withSessionTrace( + s, err := createSession(ctx, service, config.New(config.WithTrace( &trace.Query{ OnSessionAttach: func(info trace.QuerySessionAttachStartInfo) func(info trace.QuerySessionAttachDoneInfo) { return func(info trace.QuerySessionAttachDoneInfo) { @@ -56,7 +56,7 @@ func TestCreateSession(t *testing.T) { return nil }, }, - )) + ))) require.NoError(t, err) require.EqualValues(t, "test", s.id) require.EqualValues(t, 1, attached) @@ -72,8 +72,7 @@ func TestCreateSession(t *testing.T) { ctrl := gomock.NewController(t) service := NewMockQueryServiceClient(ctrl) service.EXPECT().CreateSession(gomock.Any(), gomock.Any()).Return(nil, grpcStatus.Error(grpcCodes.Unavailable, "")) - t.Log("execute") - _, err := createSession(ctx, service) + _, err := createSession(ctx, service, config.New()) require.Error(t, err) require.True(t, xerrors.IsTransportError(err, grpcCodes.Unavailable)) }, xtest.StopAfter(time.Second)) @@ -89,8 +88,7 @@ func TestCreateSession(t *testing.T) { }, nil) service.EXPECT().AttachSession(gomock.Any(), gomock.Any()).Return(nil, grpcStatus.Error(grpcCodes.Unavailable, "")) service.EXPECT().DeleteSession(gomock.Any(), gomock.Any()).Return(nil, grpcStatus.Error(grpcCodes.Unavailable, "")) - t.Log("execute") - _, err := createSession(ctx, service) + _, err := createSession(ctx, service, config.New()) require.Error(t, err) require.True(t, xerrors.IsTransportError(err, grpcCodes.Unavailable)) }, xtest.StopAfter(time.Second)) @@ -110,8 +108,7 @@ func TestCreateSession(t *testing.T) { service.EXPECT().DeleteSession(gomock.Any(), gomock.Any()).Return(&Ydb_Query.DeleteSessionResponse{ Status: Ydb.StatusIds_SUCCESS, }, nil) - t.Log("execute") - _, err := createSession(ctx, service) + _, err := createSession(ctx, service, config.New()) require.Error(t, err) require.True(t, xerrors.IsTransportError(err, grpcCodes.Unavailable)) }, xtest.StopAfter(time.Second)) @@ -123,11 +120,10 @@ func TestCreateSession(t *testing.T) { ctx := xtest.Context(t) ctrl := gomock.NewController(t) service := NewMockQueryServiceClient(ctrl) - service.EXPECT().CreateSession(gomock.Any(), gomock.Any()).Return(&Ydb_Query.CreateSessionResponse{ - Status: Ydb.StatusIds_UNAVAILABLE, - }, nil) - t.Log("execute") - _, err := createSession(ctx, service) + service.EXPECT().CreateSession(gomock.Any(), gomock.Any()).Return(nil, + xerrors.Operation(xerrors.WithStatusCode(Ydb.StatusIds_UNAVAILABLE)), + ) + _, err := createSession(ctx, service, config.New()) require.Error(t, err) require.True(t, xerrors.IsOperationError(err, Ydb.StatusIds_UNAVAILABLE)) }, xtest.StopAfter(time.Second)) @@ -137,9 +133,9 @@ func TestCreateSession(t *testing.T) { ctx := xtest.Context(t) ctrl := gomock.NewController(t) attachStream := NewMockQueryService_AttachSessionClient(ctrl) - attachStream.EXPECT().Recv().Return(&Ydb_Query.SessionState{ - Status: Ydb.StatusIds_UNAVAILABLE, - }, nil).AnyTimes() + attachStream.EXPECT().Recv().Return(nil, + xerrors.Operation(xerrors.WithStatusCode(Ydb.StatusIds_UNAVAILABLE)), + ) service := NewMockQueryServiceClient(ctrl) service.EXPECT().CreateSession(gomock.Any(), gomock.Any()).Return(&Ydb_Query.CreateSessionResponse{ Status: Ydb.StatusIds_SUCCESS, @@ -149,8 +145,7 @@ func TestCreateSession(t *testing.T) { service.EXPECT().DeleteSession(gomock.Any(), gomock.Any()).Return(&Ydb_Query.DeleteSessionResponse{ Status: Ydb.StatusIds_SUCCESS, }, nil) - t.Log("execute") - _, err := createSession(ctx, service) + _, err := createSession(ctx, service, config.New()) require.Error(t, err) require.True(t, xerrors.IsOperationError(err, Ydb.StatusIds_UNAVAILABLE)) }, xtest.StopAfter(time.Second)) @@ -158,19 +153,21 @@ func TestCreateSession(t *testing.T) { }) } -func newTestSession() (*Session, error) { +func newTestSession(id string) *Session { return &Session{ + id: id, statusCode: statusIdle, - trace: &trace.Query{}, - }, nil + cfg: config.New(), + } } -func newTestSessionWithClient(client Ydb_Query_V1.QueryServiceClient) (*Session, error) { +func newTestSessionWithClient(id string, client Ydb_Query_V1.QueryServiceClient) *Session { return &Session{ + id: id, grpcClient: client, statusCode: statusIdle, - trace: &trace.Query{}, - }, nil + cfg: config.New(), + } } func testPool( @@ -187,7 +184,7 @@ func TestDo(t *testing.T) { ctx := xtest.Context(t) t.Run("HappyWay", func(t *testing.T) { attempts, err := do(ctx, testPool(ctx, func(ctx context.Context) (*Session, error) { - return newTestSession() + return newTestSession("123"), nil }), func(ctx context.Context, s query.Session) error { return nil }, &trace.Query{}) @@ -197,7 +194,7 @@ func TestDo(t *testing.T) { t.Run("RetryableError", func(t *testing.T) { counter := 0 attempts, err := do(ctx, testPool(ctx, func(ctx context.Context) (*Session, error) { - return newTestSession() + return newTestSession("123"), nil }), func(ctx context.Context, s query.Session) error { counter++ if counter < 10 { @@ -224,7 +221,7 @@ func TestDoTx(t *testing.T) { Status: Ydb.StatusIds_SUCCESS, }, nil) attempts, err := doTx(ctx, testPool(ctx, func(ctx context.Context) (*Session, error) { - return newTestSessionWithClient(client) + return newTestSessionWithClient("123", client), nil }), func(ctx context.Context, tx query.TxActor) error { return nil }, &trace.Query{}) @@ -245,7 +242,7 @@ func TestDoTx(t *testing.T) { Status: Ydb.StatusIds_SUCCESS, }, nil).AnyTimes() attempts, err := doTx(ctx, testPool(ctx, func(ctx context.Context) (*Session, error) { - return newTestSessionWithClient(client) + return newTestSessionWithClient("123", client), nil }), func(ctx context.Context, tx query.TxActor) error { counter++ if counter < 10 { diff --git a/internal/query/config/config.go b/internal/query/config/config.go index 392f2ec4d..7adb08242 100644 --- a/internal/query/config/config.go +++ b/internal/query/config/config.go @@ -10,7 +10,7 @@ import ( const ( DefaultSessionDeleteTimeout = 500 * time.Millisecond - DefaultSessionCreateTimeout = 5 * time.Second + DefaultSessionCreateTimeout = 500 * time.Millisecond DefaultPoolMaxSize = pool.DefaultLimit ) @@ -62,9 +62,9 @@ func (c *Config) SessionCreateTimeout() time.Duration { return c.sessionCreateTimeout } -// DeleteTimeout limits maximum time spent on Delete request +// SessionDeleteTimeout limits maximum time spent on Delete request // -// If DeleteTimeout is less than or equal to zero then the DefaultSessionDeleteTimeout is used. -func (c *Config) DeleteTimeout() time.Duration { +// If SessionDeleteTimeout is less than or equal to zero then the DefaultSessionDeleteTimeout is used. +func (c *Config) SessionDeleteTimeout() time.Duration { return c.sessionDeleteTimeout } diff --git a/internal/query/execute_query.go b/internal/query/execute_query.go index 182e75c0b..bf2fef314 100644 --- a/internal/query/execute_query.go +++ b/internal/query/execute_query.go @@ -60,14 +60,14 @@ func execute(ctx context.Context, s *Session, c Ydb_Query_V1.QueryServiceClient, request, callOptions := executeQueryRequest(a, s.id, q, cfg) - executeCtx, cancelExecute := xcontext.WithCancel(xcontext.WithoutDeadline(ctx)) + executeCtx, cancelExecute := xcontext.WithCancel(xcontext.ValueOnly(ctx)) stream, err := c.ExecuteQuery(executeCtx, request, callOptions...) if err != nil { return nil, nil, xerrors.WithStackTrace(err) } - r, txID, err := newResult(ctx, stream, s.trace, cancelExecute) + r, txID, err := newResult(ctx, stream, s.cfg.Trace(), cancelExecute) if err != nil { cancelExecute() @@ -78,5 +78,5 @@ func execute(ctx context.Context, s *Session, c Ydb_Query_V1.QueryServiceClient, return nil, r, nil } - return newTransaction(txID, s, s.trace), r, nil + return newTransaction(txID, s), r, nil } diff --git a/internal/query/execute_query_test.go b/internal/query/execute_query_test.go index 9e6a2c2a0..c3c14d754 100644 --- a/internal/query/execute_query_test.go +++ b/internal/query/execute_query_test.go @@ -19,6 +19,7 @@ import ( "github.com/ydb-platform/ydb-go-sdk/v3/internal/query/options" "github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors" "github.com/ydb-platform/ydb-go-sdk/v3/internal/xtest" + "github.com/ydb-platform/ydb-go-sdk/v3/query" ) func TestExecute(t *testing.T) { @@ -355,8 +356,7 @@ func TestExecute(t *testing.T) { stream.EXPECT().Recv().Return(nil, io.EOF) service := NewMockQueryServiceClient(ctrl) service.EXPECT().ExecuteQuery(gomock.Any(), gomock.Any()).Return(stream, nil) - t.Log("execute") - tx, r, err := execute(ctx, &Session{id: "123"}, service, "", options.ExecuteSettings()) + tx, r, err := execute(ctx, newTestSession("123"), service, "", options.ExecuteSettings()) require.NoError(t, err) defer r.Close(ctx) require.EqualValues(t, "456", tx.id) @@ -469,7 +469,7 @@ func TestExecute(t *testing.T) { service := NewMockQueryServiceClient(ctrl) service.EXPECT().ExecuteQuery(gomock.Any(), gomock.Any()).Return(nil, grpcStatus.Error(grpcCodes.Unavailable, "")) t.Log("execute") - _, _, err := execute(ctx, &Session{id: "123"}, service, "", options.ExecuteSettings()) + _, _, err := execute(ctx, newTestSession("123"), service, "", options.ExecuteSettings()) require.Error(t, err) require.True(t, xerrors.IsTransportError(err, grpcCodes.Unavailable)) }) @@ -573,7 +573,7 @@ func TestExecute(t *testing.T) { service := NewMockQueryServiceClient(ctrl) service.EXPECT().ExecuteQuery(gomock.Any(), gomock.Any()).Return(stream, nil) t.Log("execute") - tx, r, err := execute(ctx, &Session{id: "123"}, service, "", options.ExecuteSettings()) + tx, r, err := execute(ctx, newTestSession("123"), service, "", options.ExecuteSettings()) require.NoError(t, err) defer r.Close(ctx) require.EqualValues(t, "456", tx.id) @@ -637,7 +637,7 @@ func TestExecute(t *testing.T) { service := NewMockQueryServiceClient(ctrl) service.EXPECT().ExecuteQuery(gomock.Any(), gomock.Any()).Return(stream, nil) t.Log("execute") - _, _, err := execute(ctx, &Session{id: "123"}, service, "", options.ExecuteSettings()) + _, _, err := execute(ctx, newTestSession("123"), service, "", options.ExecuteSettings()) require.Error(t, err) require.True(t, xerrors.IsOperationError(err, Ydb.StatusIds_UNAVAILABLE)) }) @@ -713,7 +713,7 @@ func TestExecute(t *testing.T) { service := NewMockQueryServiceClient(ctrl) service.EXPECT().ExecuteQuery(gomock.Any(), gomock.Any()).Return(stream, nil) t.Log("execute") - tx, r, err := execute(ctx, &Session{id: "123"}, service, "", options.ExecuteSettings()) + tx, r, err := execute(ctx, newTestSession("123"), service, "", options.ExecuteSettings()) require.NoError(t, err) defer r.Close(ctx) require.EqualValues(t, "456", tx.id) @@ -769,6 +769,24 @@ func TestExecuteQueryRequest(t *testing.T) { request: &Ydb_Query.ExecuteQueryRequest{ SessionId: "WithoutOptions", ExecMode: Ydb_Query.ExecMode_EXEC_MODE_EXECUTE, + Query: &Ydb_Query.ExecuteQueryRequest_QueryContent{ + QueryContent: &Ydb_Query.QueryContent{ + Syntax: Ydb_Query.Syntax_SYNTAX_YQL_V1, + Text: "WithoutOptions", + }, + }, + StatsMode: Ydb_Query.StatsMode_STATS_MODE_NONE, + ConcurrentResultSets: false, + }, + }, + { + name: "WithTxControl", + opts: []options.ExecuteOption{ + options.WithTxControl(query.SerializableReadWriteTxControl(query.CommitTx())), + }, + request: &Ydb_Query.ExecuteQueryRequest{ + SessionId: "WithTxControl", + ExecMode: Ydb_Query.ExecMode_EXEC_MODE_EXECUTE, TxControl: &Ydb_Query.TransactionControl{ TxSelector: &Ydb_Query.TransactionControl_BeginTx{ BeginTx: &Ydb_Query.TransactionSettings{ @@ -782,7 +800,7 @@ func TestExecuteQueryRequest(t *testing.T) { Query: &Ydb_Query.ExecuteQueryRequest_QueryContent{ QueryContent: &Ydb_Query.QueryContent{ Syntax: Ydb_Query.Syntax_SYNTAX_YQL_V1, - Text: "WithoutOptions", + Text: "WithTxControl", }, }, StatsMode: Ydb_Query.StatsMode_STATS_MODE_NONE, @@ -803,16 +821,6 @@ func TestExecuteQueryRequest(t *testing.T) { request: &Ydb_Query.ExecuteQueryRequest{ SessionId: "WithParams", ExecMode: Ydb_Query.ExecMode_EXEC_MODE_EXECUTE, - TxControl: &Ydb_Query.TransactionControl{ - TxSelector: &Ydb_Query.TransactionControl_BeginTx{ - BeginTx: &Ydb_Query.TransactionSettings{ - TxMode: &Ydb_Query.TransactionSettings_SerializableReadWrite{ - SerializableReadWrite: &Ydb_Query.SerializableModeSettings{}, - }, - }, - }, - CommitTx: true, - }, Query: &Ydb_Query.ExecuteQueryRequest_QueryContent{ QueryContent: &Ydb_Query.QueryContent{ Syntax: Ydb_Query.Syntax_SYNTAX_YQL_V1, @@ -869,16 +877,6 @@ func TestExecuteQueryRequest(t *testing.T) { request: &Ydb_Query.ExecuteQueryRequest{ SessionId: "WithExplain", ExecMode: Ydb_Query.ExecMode_EXEC_MODE_EXPLAIN, - TxControl: &Ydb_Query.TransactionControl{ - TxSelector: &Ydb_Query.TransactionControl_BeginTx{ - BeginTx: &Ydb_Query.TransactionSettings{ - TxMode: &Ydb_Query.TransactionSettings_SerializableReadWrite{ - SerializableReadWrite: &Ydb_Query.SerializableModeSettings{}, - }, - }, - }, - CommitTx: true, - }, Query: &Ydb_Query.ExecuteQueryRequest_QueryContent{ QueryContent: &Ydb_Query.QueryContent{ Syntax: Ydb_Query.Syntax_SYNTAX_YQL_V1, @@ -897,16 +895,6 @@ func TestExecuteQueryRequest(t *testing.T) { request: &Ydb_Query.ExecuteQueryRequest{ SessionId: "WithValidate", ExecMode: Ydb_Query.ExecMode_EXEC_MODE_VALIDATE, - TxControl: &Ydb_Query.TransactionControl{ - TxSelector: &Ydb_Query.TransactionControl_BeginTx{ - BeginTx: &Ydb_Query.TransactionSettings{ - TxMode: &Ydb_Query.TransactionSettings_SerializableReadWrite{ - SerializableReadWrite: &Ydb_Query.SerializableModeSettings{}, - }, - }, - }, - CommitTx: true, - }, Query: &Ydb_Query.ExecuteQueryRequest_QueryContent{ QueryContent: &Ydb_Query.QueryContent{ Syntax: Ydb_Query.Syntax_SYNTAX_YQL_V1, @@ -925,16 +913,6 @@ func TestExecuteQueryRequest(t *testing.T) { request: &Ydb_Query.ExecuteQueryRequest{ SessionId: "WithValidate", ExecMode: Ydb_Query.ExecMode_EXEC_MODE_PARSE, - TxControl: &Ydb_Query.TransactionControl{ - TxSelector: &Ydb_Query.TransactionControl_BeginTx{ - BeginTx: &Ydb_Query.TransactionSettings{ - TxMode: &Ydb_Query.TransactionSettings_SerializableReadWrite{ - SerializableReadWrite: &Ydb_Query.SerializableModeSettings{}, - }, - }, - }, - CommitTx: true, - }, Query: &Ydb_Query.ExecuteQueryRequest_QueryContent{ QueryContent: &Ydb_Query.QueryContent{ Syntax: Ydb_Query.Syntax_SYNTAX_YQL_V1, @@ -953,16 +931,6 @@ func TestExecuteQueryRequest(t *testing.T) { request: &Ydb_Query.ExecuteQueryRequest{ SessionId: "WithStatsFull", ExecMode: Ydb_Query.ExecMode_EXEC_MODE_EXECUTE, - TxControl: &Ydb_Query.TransactionControl{ - TxSelector: &Ydb_Query.TransactionControl_BeginTx{ - BeginTx: &Ydb_Query.TransactionSettings{ - TxMode: &Ydb_Query.TransactionSettings_SerializableReadWrite{ - SerializableReadWrite: &Ydb_Query.SerializableModeSettings{}, - }, - }, - }, - CommitTx: true, - }, Query: &Ydb_Query.ExecuteQueryRequest_QueryContent{ QueryContent: &Ydb_Query.QueryContent{ Syntax: Ydb_Query.Syntax_SYNTAX_YQL_V1, @@ -981,16 +949,6 @@ func TestExecuteQueryRequest(t *testing.T) { request: &Ydb_Query.ExecuteQueryRequest{ SessionId: "WithStatsBasic", ExecMode: Ydb_Query.ExecMode_EXEC_MODE_EXECUTE, - TxControl: &Ydb_Query.TransactionControl{ - TxSelector: &Ydb_Query.TransactionControl_BeginTx{ - BeginTx: &Ydb_Query.TransactionSettings{ - TxMode: &Ydb_Query.TransactionSettings_SerializableReadWrite{ - SerializableReadWrite: &Ydb_Query.SerializableModeSettings{}, - }, - }, - }, - CommitTx: true, - }, Query: &Ydb_Query.ExecuteQueryRequest_QueryContent{ QueryContent: &Ydb_Query.QueryContent{ Syntax: Ydb_Query.Syntax_SYNTAX_YQL_V1, @@ -1009,16 +967,6 @@ func TestExecuteQueryRequest(t *testing.T) { request: &Ydb_Query.ExecuteQueryRequest{ SessionId: "WithStatsProfile", ExecMode: Ydb_Query.ExecMode_EXEC_MODE_EXECUTE, - TxControl: &Ydb_Query.TransactionControl{ - TxSelector: &Ydb_Query.TransactionControl_BeginTx{ - BeginTx: &Ydb_Query.TransactionSettings{ - TxMode: &Ydb_Query.TransactionSettings_SerializableReadWrite{ - SerializableReadWrite: &Ydb_Query.SerializableModeSettings{}, - }, - }, - }, - CommitTx: true, - }, Query: &Ydb_Query.ExecuteQueryRequest_QueryContent{ QueryContent: &Ydb_Query.QueryContent{ Syntax: Ydb_Query.Syntax_SYNTAX_YQL_V1, @@ -1039,16 +987,6 @@ func TestExecuteQueryRequest(t *testing.T) { request: &Ydb_Query.ExecuteQueryRequest{ SessionId: "WithGrpcCallOptions", ExecMode: Ydb_Query.ExecMode_EXEC_MODE_EXECUTE, - TxControl: &Ydb_Query.TransactionControl{ - TxSelector: &Ydb_Query.TransactionControl_BeginTx{ - BeginTx: &Ydb_Query.TransactionSettings{ - TxMode: &Ydb_Query.TransactionSettings_SerializableReadWrite{ - SerializableReadWrite: &Ydb_Query.SerializableModeSettings{}, - }, - }, - }, - CommitTx: true, - }, Query: &Ydb_Query.ExecuteQueryRequest_QueryContent{ QueryContent: &Ydb_Query.QueryContent{ Syntax: Ydb_Query.Syntax_SYNTAX_YQL_V1, diff --git a/internal/query/result.go b/internal/query/result.go index 9a25da8e7..78478610d 100644 --- a/internal/query/result.go +++ b/internal/query/result.go @@ -40,7 +40,9 @@ func newResult( closeResult = func() {} } - onDone := trace.QueryOnResultNew(t, &ctx, stack.FunctionID("")) + onDone := trace.QueryOnResultNew(t, &ctx, + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/query.newResult"), + ) defer func() { onDone(err) }() @@ -86,7 +88,9 @@ func nextPart( t = &trace.Query{} } - onDone := trace.QueryOnResultNextPart(t, &ctx, stack.FunctionID("")) + onDone := trace.QueryOnResultNextPart(t, &ctx, + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/query.nextPart"), + ) defer func() { onDone(finalErr) }() @@ -100,7 +104,9 @@ func nextPart( } func (r *result) Close(ctx context.Context) (err error) { - onDone := trace.QueryOnResultClose(r.trace, &ctx, stack.FunctionID("")) + onDone := trace.QueryOnResultClose(r.trace, &ctx, + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/query.(*result).Close"), + ) defer func() { onDone(err) }() @@ -176,7 +182,9 @@ func (r *result) nextResultSet(ctx context.Context) (_ *resultSet, err error) { } func (r *result) NextResultSet(ctx context.Context) (_ query.ResultSet, err error) { - onDone := trace.QueryOnResultNextResultSet(r.trace, &ctx, stack.FunctionID("")) + onDone := trace.QueryOnResultNextResultSet(r.trace, &ctx, + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/query.(*result).NextResultSet"), + ) defer func() { onDone(err) }() diff --git a/internal/query/result_set.go b/internal/query/result_set.go index 445cc7050..8d2ae8b71 100644 --- a/internal/query/result_set.go +++ b/internal/query/result_set.go @@ -48,44 +48,50 @@ func newResultSet( func (rs *resultSet) nextRow(ctx context.Context) (*row, error) { rs.rowIndex++ - select { - case <-rs.done: - return nil, io.EOF - case <-ctx.Done(): - return nil, xerrors.WithStackTrace(ctx.Err()) - default: - if rs.rowIndex == len(rs.currentPart.GetResultSet().GetRows()) { - part, err := rs.recv() - if err != nil { - if xerrors.Is(err, io.EOF) { - close(rs.done) + for { + select { + case <-rs.done: + return nil, io.EOF + case <-ctx.Done(): + return nil, xerrors.WithStackTrace(ctx.Err()) + default: + if rs.rowIndex == len(rs.currentPart.GetResultSet().GetRows()) { + part, err := rs.recv() + if err != nil { + if xerrors.Is(err, io.EOF) { + close(rs.done) + } + + return nil, xerrors.WithStackTrace(err) } + rs.rowIndex = 0 + rs.currentPart = part + if part == nil { + close(rs.done) - return nil, xerrors.WithStackTrace(err) + return nil, xerrors.WithStackTrace(io.EOF) + } } - rs.rowIndex = 0 - rs.currentPart = part - if part == nil { + if rs.index != rs.currentPart.GetResultSetIndex() { close(rs.done) - return nil, xerrors.WithStackTrace(io.EOF) + return nil, xerrors.WithStackTrace(fmt.Errorf( + "received part with result set index = %d, current result set index = %d: %w", + rs.index, rs.currentPart.GetResultSetIndex(), errWrongResultSetIndex, + )) } - } - if rs.index != rs.currentPart.GetResultSetIndex() { - close(rs.done) - return nil, xerrors.WithStackTrace(fmt.Errorf( - "received part with result set index = %d, current result set index = %d: %w", - rs.index, rs.currentPart.GetResultSetIndex(), errWrongResultSetIndex, - )) + if rs.rowIndex < len(rs.currentPart.GetResultSet().GetRows()) { + return newRow(ctx, rs.columns, rs.currentPart.GetResultSet().GetRows()[rs.rowIndex], rs.trace) + } } - - return newRow(ctx, rs.columns, rs.currentPart.GetResultSet().GetRows()[rs.rowIndex], rs.trace) } } func (rs *resultSet) NextRow(ctx context.Context) (_ query.Row, err error) { - onDone := trace.QueryOnResultSetNextRow(rs.trace, &ctx, stack.FunctionID("")) + onDone := trace.QueryOnResultSetNextRow(rs.trace, &ctx, + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/query.(*resultSet).NextRow"), + ) defer func() { onDone(err) }() diff --git a/internal/query/result_set_test.go b/internal/query/result_set_test.go index a011d2781..93f0491bf 100644 --- a/internal/query/result_set_test.go +++ b/internal/query/result_set_test.go @@ -20,6 +20,324 @@ import ( func TestResultSetNext(t *testing.T) { ctx := xtest.Context(t) ctrl := gomock.NewController(t) + t.Run("EmptyResultSet", func(t *testing.T) { + stream := NewMockQueryService_ExecuteQueryClient(ctrl) + stream.EXPECT().Recv().Return(&Ydb_Query.ExecuteQueryResponsePart{ + Status: Ydb.StatusIds_SUCCESS, + ResultSetIndex: 0, + ResultSet: &Ydb.ResultSet{ + Columns: []*Ydb.Column{ + { + Name: "a", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UINT64, + }, + }, + }, + { + Name: "b", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UTF8, + }, + }, + }, + }, + Rows: []*Ydb.Value{}, + }, + }, nil) + stream.EXPECT().Recv().Return(nil, io.EOF) + recv, err := stream.Recv() + require.NoError(t, err) + rs := newResultSet(func() (*Ydb_Query.ExecuteQueryResponsePart, error) { + part, err := stream.Recv() + if err != nil { + return nil, xerrors.WithStackTrace(err) + } + + return part, nil + }, recv, nil) + require.EqualValues(t, 0, rs.index) + { + _, err := rs.nextRow(ctx) + require.ErrorIs(t, err, io.EOF) + } + }) + t.Run("SecondResultSetEmpty", func(t *testing.T) { + stream := NewMockQueryService_ExecuteQueryClient(ctrl) + stream.EXPECT().Recv().Return(&Ydb_Query.ExecuteQueryResponsePart{ + Status: Ydb.StatusIds_SUCCESS, + ResultSetIndex: 0, + ResultSet: &Ydb.ResultSet{ + Columns: []*Ydb.Column{ + { + Name: "a", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UINT64, + }, + }, + }, + { + Name: "b", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UTF8, + }, + }, + }, + }, + Rows: []*Ydb.Value{ + { + Items: []*Ydb.Value{{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 1, + }, + }, { + Value: &Ydb.Value_TextValue{ + TextValue: "1", + }, + }}, + }, + { + Items: []*Ydb.Value{{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 2, + }, + }, { + Value: &Ydb.Value_TextValue{ + TextValue: "2", + }, + }}, + }, + { + Items: []*Ydb.Value{{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 3, + }, + }, { + Value: &Ydb.Value_TextValue{ + TextValue: "3", + }, + }}, + }, + }, + }, + }, nil) + stream.EXPECT().Recv().Return(&Ydb_Query.ExecuteQueryResponsePart{ + Status: Ydb.StatusIds_SUCCESS, + ResultSetIndex: 0, + ResultSet: &Ydb.ResultSet{ + Rows: []*Ydb.Value{}, + }, + }, nil) + stream.EXPECT().Recv().Return(nil, io.EOF) + recv, err := stream.Recv() + require.NoError(t, err) + rs := newResultSet(func() (*Ydb_Query.ExecuteQueryResponsePart, error) { + part, err := stream.Recv() + if err != nil { + return nil, xerrors.WithStackTrace(err) + } + + return part, nil + }, recv, nil) + require.EqualValues(t, 0, rs.index) + { + _, err := rs.nextRow(ctx) + require.NoError(t, err) + require.EqualValues(t, 0, rs.rowIndex) + } + { + _, err := rs.nextRow(ctx) + require.NoError(t, err) + require.EqualValues(t, 1, rs.rowIndex) + } + { + _, err := rs.nextRow(ctx) + require.NoError(t, err) + require.EqualValues(t, 2, rs.rowIndex) + } + { + _, err := rs.nextRow(ctx) + require.ErrorIs(t, err, io.EOF) + } + }) + t.Run("IntermediateResultSetEmpty", func(t *testing.T) { + stream := NewMockQueryService_ExecuteQueryClient(ctrl) + stream.EXPECT().Recv().Return(&Ydb_Query.ExecuteQueryResponsePart{ + Status: Ydb.StatusIds_SUCCESS, + ResultSetIndex: 0, + ResultSet: &Ydb.ResultSet{ + Columns: []*Ydb.Column{ + { + Name: "a", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UINT64, + }, + }, + }, + { + Name: "b", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UTF8, + }, + }, + }, + }, + Rows: []*Ydb.Value{ + { + Items: []*Ydb.Value{{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 1, + }, + }, { + Value: &Ydb.Value_TextValue{ + TextValue: "1", + }, + }}, + }, + { + Items: []*Ydb.Value{{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 2, + }, + }, { + Value: &Ydb.Value_TextValue{ + TextValue: "2", + }, + }}, + }, + { + Items: []*Ydb.Value{{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 3, + }, + }, { + Value: &Ydb.Value_TextValue{ + TextValue: "3", + }, + }}, + }, + }, + }, + }, nil) + stream.EXPECT().Recv().Return(&Ydb_Query.ExecuteQueryResponsePart{ + Status: Ydb.StatusIds_SUCCESS, + ResultSetIndex: 0, + ResultSet: &Ydb.ResultSet{ + Rows: []*Ydb.Value{}, + }, + }, nil) + stream.EXPECT().Recv().Return(&Ydb_Query.ExecuteQueryResponsePart{ + Status: Ydb.StatusIds_SUCCESS, + ResultSetIndex: 0, + ResultSet: &Ydb.ResultSet{ + Columns: []*Ydb.Column{ + { + Name: "a", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UINT64, + }, + }, + }, + { + Name: "b", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UTF8, + }, + }, + }, + }, + Rows: []*Ydb.Value{ + { + Items: []*Ydb.Value{{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 1, + }, + }, { + Value: &Ydb.Value_TextValue{ + TextValue: "1", + }, + }}, + }, + { + Items: []*Ydb.Value{{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 2, + }, + }, { + Value: &Ydb.Value_TextValue{ + TextValue: "2", + }, + }}, + }, + { + Items: []*Ydb.Value{{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 3, + }, + }, { + Value: &Ydb.Value_TextValue{ + TextValue: "3", + }, + }}, + }, + }, + }, + }, nil) + stream.EXPECT().Recv().Return(nil, io.EOF) + recv, err := stream.Recv() + require.NoError(t, err) + rs := newResultSet(func() (*Ydb_Query.ExecuteQueryResponsePart, error) { + part, err := stream.Recv() + if err != nil { + return nil, xerrors.WithStackTrace(err) + } + + return part, nil + }, recv, nil) + require.EqualValues(t, 0, rs.index) + { + _, err := rs.nextRow(ctx) + require.NoError(t, err) + require.EqualValues(t, 0, rs.rowIndex) + } + { + _, err := rs.nextRow(ctx) + require.NoError(t, err) + require.EqualValues(t, 1, rs.rowIndex) + } + { + _, err := rs.nextRow(ctx) + require.NoError(t, err) + require.EqualValues(t, 2, rs.rowIndex) + } + { + _, err := rs.nextRow(ctx) + require.NoError(t, err) + require.EqualValues(t, 0, rs.rowIndex) + } + { + _, err := rs.nextRow(ctx) + require.NoError(t, err) + require.EqualValues(t, 1, rs.rowIndex) + } + { + _, err := rs.nextRow(ctx) + require.NoError(t, err) + require.EqualValues(t, 2, rs.rowIndex) + } + { + _, err := rs.nextRow(ctx) + require.ErrorIs(t, err, io.EOF) + } + }) t.Run("OverTwoParts", func(t *testing.T) { stream := NewMockQueryService_ExecuteQueryClient(ctrl) stream.EXPECT().Recv().Return(&Ydb_Query.ExecuteQueryResponsePart{ diff --git a/internal/query/row.go b/internal/query/row.go index 27c5cb168..476b6aa14 100644 --- a/internal/query/row.go +++ b/internal/query/row.go @@ -35,7 +35,9 @@ func newRow(ctx context.Context, columns []*Ydb.Column, v *Ydb.Value, t *trace.Q } func (r row) Scan(dst ...interface{}) (err error) { - onDone := trace.QueryOnRowScan(r.trace, &r.ctx, stack.FunctionID("")) + onDone := trace.QueryOnRowScan(r.trace, &r.ctx, + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/query.row.Scan"), + ) defer func() { onDone(err) }() @@ -44,7 +46,9 @@ func (r row) Scan(dst ...interface{}) (err error) { } func (r row) ScanNamed(dst ...scanner.NamedDestination) (err error) { - onDone := trace.QueryOnRowScanNamed(r.trace, &r.ctx, stack.FunctionID("")) + onDone := trace.QueryOnRowScanNamed(r.trace, &r.ctx, + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/query.row.ScanNamed"), + ) defer func() { onDone(err) }() @@ -53,7 +57,9 @@ func (r row) ScanNamed(dst ...scanner.NamedDestination) (err error) { } func (r row) ScanStruct(dst interface{}, opts ...scanner.ScanStructOption) (err error) { - onDone := trace.QueryOnRowScanStruct(r.trace, &r.ctx, stack.FunctionID("")) + onDone := trace.QueryOnRowScanStruct(r.trace, &r.ctx, + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/query.row.ScanStruct"), + ) defer func() { onDone(err) }() diff --git a/internal/query/session.go b/internal/query/session.go index 42a482339..708b36df6 100644 --- a/internal/query/session.go +++ b/internal/query/session.go @@ -6,10 +6,10 @@ import ( "sync/atomic" "github.com/ydb-platform/ydb-go-genproto/Ydb_Query_V1" - "github.com/ydb-platform/ydb-go-genproto/protos/Ydb" "github.com/ydb-platform/ydb-go-genproto/protos/Ydb_Query" "github.com/ydb-platform/ydb-go-sdk/v3/internal/allocator" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/query/config" "github.com/ydb-platform/ydb-go-sdk/v3/internal/query/options" "github.com/ydb-platform/ydb-go-sdk/v3/internal/stack" "github.com/ydb-platform/ydb-go-sdk/v3/internal/xcontext" @@ -23,11 +23,11 @@ var _ query.Session = (*Session)(nil) type ( Session struct { + cfg *config.Config id string nodeID int64 grpcClient Ydb_Query_V1.QueryServiceClient statusCode statusCode - trace *trace.Query closeOnce func(ctx context.Context) error checks []func(s *Session) bool } @@ -40,19 +40,13 @@ func withSessionCheck(f func(*Session) bool) sessionOption { } } -func withSessionTrace(t *trace.Query) sessionOption { - return func(s *Session) { - s.trace = s.trace.Compose(t) - } -} - func createSession( - ctx context.Context, client Ydb_Query_V1.QueryServiceClient, opts ...sessionOption, + ctx context.Context, client Ydb_Query_V1.QueryServiceClient, cfg *config.Config, opts ...sessionOption, ) (s *Session, finalErr error) { s = &Session{ + cfg: cfg, grpcClient: client, statusCode: statusUnknown, - trace: &trace.Query{}, checks: []func(*Session) bool{ func(s *Session) bool { switch s.status() { @@ -74,22 +68,16 @@ func createSession( opt(s) } - onDone := trace.QueryOnSessionCreate(s.trace, &ctx, stack.FunctionID("")) + onDone := trace.QueryOnSessionCreate(s.cfg.Trace(), &ctx, + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/query.createSession"), + ) defer func() { onDone(s, finalErr) }() response, err := client.CreateSession(ctx, &Ydb_Query.CreateSessionRequest{}) if err != nil { - return nil, xerrors.WithStackTrace( - xerrors.Transport(err), - ) - } - - if response.GetStatus() != Ydb.StatusIds_SUCCESS { - return nil, xerrors.WithStackTrace( - xerrors.FromOperation(response), - ) + return nil, xerrors.WithStackTrace(err) } defer func() { @@ -103,9 +91,7 @@ func createSession( err = s.attach(ctx) if err != nil { - return nil, xerrors.WithStackTrace( - xerrors.Transport(err), - ) + return nil, xerrors.WithStackTrace(err) } s.setStatus(statusIdle) @@ -114,41 +100,42 @@ func createSession( } func (s *Session) attach(ctx context.Context) (finalErr error) { - onDone := trace.QueryOnSessionAttach(s.trace, &ctx, stack.FunctionID(""), s) + onDone := trace.QueryOnSessionAttach(s.cfg.Trace(), &ctx, + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/query.(*Session).attach"), s) defer func() { onDone(finalErr) }() - attachCtx, cancelAttach := xcontext.WithCancel(xcontext.WithoutDeadline(ctx)) + attachCtx, cancelAttach := xcontext.WithCancel(xcontext.ValueOnly(ctx)) attach, err := s.grpcClient.AttachSession(attachCtx, &Ydb_Query.AttachSessionRequest{ SessionId: s.id, }) if err != nil { - return xerrors.WithStackTrace( - xerrors.Transport(err), - ) + return xerrors.WithStackTrace(err) } - state, err := attach.Recv() + _, err = attach.Recv() if err != nil { cancelAttach() - return xerrors.WithStackTrace(xerrors.Transport(err)) - } - - if state.GetStatus() != Ydb.StatusIds_SUCCESS { - cancelAttach() - - return xerrors.WithStackTrace(xerrors.FromOperation(state)) + return xerrors.WithStackTrace(err) } s.closeOnce = xsync.OnceFunc(func(ctx context.Context) (err error) { - cancelAttach() + defer cancelAttach() s.setStatus(statusClosing) defer s.setStatus(statusClosed) + var cancel context.CancelFunc + if d := s.cfg.SessionDeleteTimeout(); d > 0 { + ctx, cancel = xcontext.WithTimeout(ctx, d) + } else { + ctx, cancel = xcontext.WithCancel(ctx) + } + defer cancel() + if err = deleteSession(ctx, s.grpcClient, s.id); err != nil { return xerrors.WithStackTrace(err) } @@ -158,14 +145,14 @@ func (s *Session) attach(ctx context.Context) (finalErr error) { go func() { defer func() { - _ = s.closeOnce(ctx) + _ = s.closeOnce(xcontext.ValueOnly(ctx)) }() for { if !s.IsAlive() { return } - recv, recvErr := attach.Recv() + _, recvErr := attach.Recv() if recvErr != nil { if xerrors.Is(recvErr, io.EOF) { s.setStatus(statusClosed) @@ -173,11 +160,6 @@ func (s *Session) attach(ctx context.Context) (finalErr error) { s.setStatus(statusError) } - return - } - if recv.GetStatus() != Ydb.StatusIds_SUCCESS { - s.setStatus(statusError) - return } } @@ -187,16 +169,13 @@ func (s *Session) attach(ctx context.Context) (finalErr error) { } func deleteSession(ctx context.Context, client Ydb_Query_V1.QueryServiceClient, sessionID string) error { - response, err := client.DeleteSession(ctx, + _, err := client.DeleteSession(ctx, &Ydb_Query.DeleteSessionRequest{ SessionId: sessionID, }, ) if err != nil { - return xerrors.WithStackTrace(xerrors.Transport(err)) - } - if response.GetStatus() != Ydb.StatusIds_SUCCESS { - return xerrors.WithStackTrace(xerrors.FromOperation(response)) + return xerrors.WithStackTrace(err) } return nil @@ -213,7 +192,8 @@ func (s *Session) IsAlive() bool { } func (s *Session) Close(ctx context.Context) (err error) { - onDone := trace.QueryOnSessionDelete(s.trace, &ctx, stack.FunctionID(""), s) + onDone := trace.QueryOnSessionDelete(s.cfg.Trace(), &ctx, + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/query.(*Session).Close"), s) defer func() { onDone(err) }() @@ -240,13 +220,10 @@ func begin( }, ) if err != nil { - return nil, xerrors.WithStackTrace(xerrors.Transport(err)) - } - if response.GetStatus() != Ydb.StatusIds_SUCCESS { - return nil, xerrors.WithStackTrace(xerrors.FromOperation(response)) + return nil, xerrors.WithStackTrace(err) } - return newTransaction(response.GetTxMeta().GetId(), s, s.trace), nil + return newTransaction(response.GetTxMeta().GetId(), s), nil } func (s *Session) Begin( @@ -257,7 +234,8 @@ func (s *Session) Begin( ) { var tx *transaction - onDone := trace.QueryOnSessionBegin(s.trace, &ctx, stack.FunctionID(""), s) + onDone := trace.QueryOnSessionBegin(s.cfg.Trace(), &ctx, + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/query.(*Session).Begin"), s) defer func() { onDone(err, tx) }() @@ -294,7 +272,8 @@ func (s *Session) Status() string { func (s *Session) Execute( ctx context.Context, q string, opts ...options.ExecuteOption, ) (_ query.Transaction, _ query.Result, err error) { - onDone := trace.QueryOnSessionExecute(s.trace, &ctx, stack.FunctionID(""), s, q) + onDone := trace.QueryOnSessionExecute(s.cfg.Trace(), &ctx, + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/query.(*Session).Execute"), s, q) defer func() { onDone(err) }() diff --git a/internal/query/session_test.go b/internal/query/session_test.go index c96989855..b75faf5d7 100644 --- a/internal/query/session_test.go +++ b/internal/query/session_test.go @@ -45,9 +45,9 @@ func TestBegin(t *testing.T) { ctx := xtest.Context(t) ctrl := gomock.NewController(t) service := NewMockQueryServiceClient(ctrl) - service.EXPECT().BeginTransaction(gomock.Any(), gomock.Any()).Return(&Ydb_Query.BeginTransactionResponse{ - Status: Ydb.StatusIds_UNAVAILABLE, - }, nil) + service.EXPECT().BeginTransaction(gomock.Any(), gomock.Any()).Return(nil, + xerrors.Operation(xerrors.WithStatusCode(Ydb.StatusIds_UNAVAILABLE)), + ) t.Log("begin") _, err := begin(ctx, service, &Session{id: "123"}, query.TxSettings()) require.Error(t, err) diff --git a/internal/query/transaction.go b/internal/query/transaction.go index e62778890..fbfcd9151 100644 --- a/internal/query/transaction.go +++ b/internal/query/transaction.go @@ -4,7 +4,6 @@ import ( "context" "github.com/ydb-platform/ydb-go-genproto/Ydb_Query_V1" - "github.com/ydb-platform/ydb-go-genproto/protos/Ydb" "github.com/ydb-platform/ydb-go-genproto/protos/Ydb_Query" "github.com/ydb-platform/ydb-go-sdk/v3/internal/query/options" @@ -17,20 +16,14 @@ import ( var _ query.Transaction = (*transaction)(nil) type transaction struct { - id string - s *Session - trace *trace.Query + id string + s *Session } -func newTransaction(id string, s *Session, t *trace.Query) *transaction { - if t == nil { - t = &trace.Query{} - } - +func newTransaction(id string, s *Session) *transaction { return &transaction{ - id: id, - s: s, - trace: t, + id: id, + s: s, } } @@ -41,7 +34,8 @@ func (tx transaction) ID() string { func (tx transaction) Execute(ctx context.Context, q string, opts ...options.TxExecuteOption) ( r query.Result, finalErr error, ) { - onDone := trace.QueryOnTxExecute(tx.trace, &ctx, stack.FunctionID(""), tx.s, tx, q) + onDone := trace.QueryOnTxExecute(tx.s.cfg.Trace(), &ctx, + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/query.transaction.Execute"), tx.s, tx, q) defer func() { onDone(finalErr) }() @@ -55,15 +49,12 @@ func (tx transaction) Execute(ctx context.Context, q string, opts ...options.TxE } func commitTx(ctx context.Context, client Ydb_Query_V1.QueryServiceClient, sessionID, txID string) error { - response, err := client.CommitTransaction(ctx, &Ydb_Query.CommitTransactionRequest{ + _, err := client.CommitTransaction(ctx, &Ydb_Query.CommitTransactionRequest{ SessionId: sessionID, TxId: txID, }) if err != nil { - return xerrors.WithStackTrace(xerrors.Transport(err)) - } - if response.GetStatus() != Ydb.StatusIds_SUCCESS { - return xerrors.WithStackTrace(xerrors.FromOperation(response)) + return xerrors.WithStackTrace(err) } return nil @@ -74,15 +65,12 @@ func (tx transaction) CommitTx(ctx context.Context) (err error) { } func rollback(ctx context.Context, client Ydb_Query_V1.QueryServiceClient, sessionID, txID string) error { - response, err := client.RollbackTransaction(ctx, &Ydb_Query.RollbackTransactionRequest{ + _, err := client.RollbackTransaction(ctx, &Ydb_Query.RollbackTransactionRequest{ SessionId: sessionID, TxId: txID, }) if err != nil { - return xerrors.WithStackTrace(xerrors.Transport(err)) - } - if response.GetStatus() != Ydb.StatusIds_SUCCESS { - return xerrors.WithStackTrace(xerrors.FromOperation(response)) + return xerrors.WithStackTrace(err) } return nil diff --git a/internal/query/transaction_test.go b/internal/query/transaction_test.go index 90b305f85..83ecdfc6c 100644 --- a/internal/query/transaction_test.go +++ b/internal/query/transaction_test.go @@ -49,10 +49,8 @@ func TestCommitTx(t *testing.T) { ctx := xtest.Context(t) ctrl := gomock.NewController(t) service := NewMockQueryServiceClient(ctrl) - service.EXPECT().CommitTransaction(gomock.Any(), gomock.Any()).Return( - &Ydb_Query.CommitTransactionResponse{ - Status: Ydb.StatusIds_UNAVAILABLE, - }, nil, + service.EXPECT().CommitTransaction(gomock.Any(), gomock.Any()).Return(nil, + xerrors.Operation(xerrors.WithStatusCode(Ydb.StatusIds_UNAVAILABLE)), ) t.Log("commit") err := commitTx(ctx, service, "123", "456") @@ -91,10 +89,8 @@ func TestRollback(t *testing.T) { ctx := xtest.Context(t) ctrl := gomock.NewController(t) service := NewMockQueryServiceClient(ctrl) - service.EXPECT().RollbackTransaction(gomock.Any(), gomock.Any()).Return( - &Ydb_Query.RollbackTransactionResponse{ - Status: Ydb.StatusIds_UNAVAILABLE, - }, nil, + service.EXPECT().RollbackTransaction(gomock.Any(), gomock.Any()).Return(nil, + xerrors.Operation(xerrors.WithStatusCode(Ydb.StatusIds_UNAVAILABLE)), ) t.Log("rollback") err := rollback(ctx, service, "123", "456") diff --git a/internal/query/tx/control.go b/internal/query/tx/control.go index f6e6f15d5..a5be2fb21 100644 --- a/internal/query/tx/control.go +++ b/internal/query/tx/control.go @@ -125,10 +125,7 @@ func NoTx() *Control { // DefaultTxControl returns default transaction control with serializable read-write isolation mode and auto-commit func DefaultTxControl() *Control { - return NewControl( - BeginTx(WithSerializableReadWrite()), - CommitTx(), - ) + return NoTx() } // SerializableReadWriteTxControl returns transaction control with serializable read-write isolation mode diff --git a/internal/repeater/repeater.go b/internal/repeater/repeater.go index 033b44a14..ec75be3c1 100644 --- a/internal/repeater/repeater.go +++ b/internal/repeater/repeater.go @@ -147,7 +147,7 @@ func (r *repeater) wakeUp(ctx context.Context, e Event) (err error) { ctx = WithEvent(ctx, e) onDone := trace.DriverOnRepeaterWakeUp(r.trace, &ctx, - stack.FunctionID(""), + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/repeater.(*repeater).wakeUp"), r.name, e, ) defer func() { @@ -172,8 +172,8 @@ func (r *repeater) worker(ctx context.Context, tick clockwork.Ticker) { // force returns backoff with delays [500ms...32s] force := backoff.New( - backoff.WithSlotDuration(500*time.Millisecond), - backoff.WithCeiling(6), + backoff.WithSlotDuration(500*time.Millisecond), //nolint:gomnd + backoff.WithCeiling(6), //nolint:gomnd backoff.WithJitterLimit(1), ) diff --git a/internal/scheme/client.go b/internal/scheme/client.go index 9d0cf10e0..6f46ed736 100644 --- a/internal/scheme/client.go +++ b/internal/scheme/client.go @@ -47,7 +47,7 @@ func New(ctx context.Context, cc grpc.ClientConnInterface, config config.Config) func (c *Client) MakeDirectory(ctx context.Context, path string) (finalErr error) { onDone := trace.SchemeOnMakeDirectory(c.config.Trace(), &ctx, - stack.FunctionID(""), + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/scheme.(*Client).MakeDirectory"), path, ) defer func() { @@ -86,7 +86,7 @@ func (c *Client) makeDirectory(ctx context.Context, path string) (err error) { func (c *Client) RemoveDirectory(ctx context.Context, path string) (finalErr error) { onDone := trace.SchemeOnRemoveDirectory(c.config.Trace(), &ctx, - stack.FunctionID(""), + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/scheme.(*Client).RemoveDirectory"), path, ) defer func() { @@ -124,7 +124,9 @@ func (c *Client) removeDirectory(ctx context.Context, path string) (err error) { } func (c *Client) ListDirectory(ctx context.Context, path string) (d scheme.Directory, finalErr error) { - onDone := trace.SchemeOnListDirectory(c.config.Trace(), &ctx, stack.FunctionID("")) + onDone := trace.SchemeOnListDirectory(c.config.Trace(), &ctx, + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/scheme.(*Client).ListDirectory"), + ) defer func() { onDone(finalErr) }() @@ -182,7 +184,7 @@ func (c *Client) listDirectory(ctx context.Context, path string) (scheme.Directo func (c *Client) DescribePath(ctx context.Context, path string) (e scheme.Entry, finalErr error) { onDone := trace.SchemeOnDescribePath(c.config.Trace(), &ctx, - stack.FunctionID(""), + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/scheme.(*Client).DescribePath"), path, ) defer func() { @@ -243,7 +245,7 @@ func (c *Client) ModifyPermissions( ctx context.Context, path string, opts ...scheme.PermissionsOption, ) (finalErr error) { onDone := trace.SchemeOnModifyPermissions(c.config.Trace(), &ctx, - stack.FunctionID(""), + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/scheme.(*Client).ModifyPermissions"), path, ) defer func() { diff --git a/internal/scripting/client.go b/internal/scripting/client.go index 60b85a248..a8ecc18bb 100644 --- a/internal/scripting/client.go +++ b/internal/scripting/client.go @@ -72,7 +72,7 @@ func (c *Client) execute( ) (r result.Result, err error) { var ( onDone = trace.ScriptingOnExecute(c.config.Trace(), &ctx, - stack.FunctionID(""), + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/scripting.(*Client).execute"), query, parameters, ) a = allocator.New() @@ -151,7 +151,7 @@ func (c *Client) explain( ) (e table.ScriptingYQLExplanation, err error) { var ( onDone = trace.ScriptingOnExplain(c.config.Trace(), &ctx, - stack.FunctionID(""), + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/scripting.(*Client).explain"), query, ) request = &Ydb_Scripting.ExplainYqlRequest{ @@ -225,7 +225,7 @@ func (c *Client) streamExecute( ) (r result.StreamResult, err error) { var ( onIntermediate = trace.ScriptingOnStreamExecute(c.config.Trace(), &ctx, - stack.FunctionID(""), + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/scripting.(*Client).streamExecute"), query, parameters, ) a = allocator.New() @@ -292,7 +292,9 @@ func (c *Client) Close(ctx context.Context) (err error) { if c == nil { return xerrors.WithStackTrace(errNilClient) } - onDone := trace.ScriptingOnClose(c.config.Trace(), &ctx, stack.FunctionID("")) + onDone := trace.ScriptingOnClose(c.config.Trace(), &ctx, + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/scripting.(*Client).Close"), + ) defer func() { onDone(err) }() diff --git a/internal/secret/token.go b/internal/secret/token.go index dc90e0c59..5383a8e0f 100644 --- a/internal/secret/token.go +++ b/internal/secret/token.go @@ -6,9 +6,11 @@ import ( "hash/crc32" ) +const minTokenLength = 16 + func Token(token string) string { var mask bytes.Buffer - if len(token) > 16 { + if len(token) > minTokenLength { mask.WriteString(token[:4]) mask.WriteString("****") mask.WriteString(token[len(token)-4:]) diff --git a/internal/stack/function_id_test.go b/internal/stack/function_id_test.go index c913a6d00..b7adabee5 100644 --- a/internal/stack/function_id_test.go +++ b/internal/stack/function_id_test.go @@ -1,13 +1,17 @@ package stack import ( + "sync" "testing" + "time" "github.com/stretchr/testify/require" ) type genericType[T any] struct{} +type starType struct{} + func (t genericType[T]) Call() string { return FunctionID("").FunctionID() } @@ -16,6 +20,26 @@ func staticCall() string { return FunctionID("").FunctionID() } +func (e *starType) starredCall() string { + return FunctionID("").FunctionID() +} + +func anonymousFunctionCall() string { + var result string + var mu sync.Mutex + go func() { + mu.Lock() + defer mu.Unlock() + result = FunctionID("").FunctionID() + }() + time.Sleep(time.Second) + + mu.Lock() + defer mu.Unlock() + + return result +} + func TestFunctionIDForGenericType(t *testing.T) { t.Run("StaticFunc", func(t *testing.T) { require.Equal(t, @@ -29,4 +53,17 @@ func TestFunctionIDForGenericType(t *testing.T) { genericType[uint64]{}.Call(), ) }) + t.Run("StarTypeCall", func(t *testing.T) { + x := starType{} + require.Equal(t, + "github.com/ydb-platform/ydb-go-sdk/v3/internal/stack.(*starType).starredCall", + x.starredCall(), + ) + }) + t.Run("AnonymousFunctionCall", func(t *testing.T) { + require.Equal(t, + "github.com/ydb-platform/ydb-go-sdk/v3/internal/stack.anonymousFunctionCall", + anonymousFunctionCall(), + ) + }) } diff --git a/internal/stack/record.go b/internal/stack/record.go index efe5e9db6..2098c1ec8 100644 --- a/internal/stack/record.go +++ b/internal/stack/record.go @@ -122,7 +122,7 @@ func (c call) Record(opts ...recordOption) string { if len(split) > 1 { funcName = split[len(split)-1] } - if len(split) > 2 { + if len(split) > 2 { //nolint:gomnd structName = split[1] } diff --git a/internal/table/client.go b/internal/table/client.go index da0f84534..9a7794625 100644 --- a/internal/table/client.go +++ b/internal/table/client.go @@ -35,7 +35,9 @@ type balancer interface { } func New(ctx context.Context, balancer balancer, config *config.Config) *Client { - onDone := trace.TableOnInit(config.Trace(), &ctx, stack.FunctionID("")) + onDone := trace.TableOnInit(config.Trace(), &ctx, + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/table.New"), + ) defer func() { onDone(config.SizeLimit()) }() @@ -166,7 +168,7 @@ func (c *Client) createSession(ctx context.Context, opts ...createSessionOption) err error ) - createSessionCtx := xcontext.WithoutDeadline(ctx) + createSessionCtx := xcontext.ValueOnly(ctx) if timeout := c.config.CreateSessionTimeout(); timeout > 0 { var cancel context.CancelFunc @@ -179,7 +181,7 @@ func (c *Client) createSession(ctx context.Context, opts ...createSessionOption) return } - closeSessionCtx := xcontext.WithoutDeadline(ctx) + closeSessionCtx := xcontext.ValueOnly(ctx) if timeout := c.config.DeleteTimeout(); timeout > 0 { var cancel context.CancelFunc @@ -261,15 +263,13 @@ func (c *Client) CreateSession(ctx context.Context, opts ...table.Option) (_ tab []retry.Option{ retry.WithIdempotent(true), retry.WithTrace(&trace.Retry{ - OnRetry: func(info trace.RetryLoopStartInfo) func(trace.RetryLoopIntermediateInfo) func(trace.RetryLoopDoneInfo) { - onIntermediate := trace.TableOnCreateSession(c.config.Trace(), info.Context, stack.FunctionID("")) - - return func(info trace.RetryLoopIntermediateInfo) func(trace.RetryLoopDoneInfo) { - onDone := onIntermediate(info.Error) + OnRetry: func(info trace.RetryLoopStartInfo) func(trace.RetryLoopDoneInfo) { + onDone := trace.TableOnCreateSession(c.config.Trace(), info.Context, + stack.FunctionID( + "github.com/ydb-platform/ydb-go-sdk/3/internal/table.(*Client).CreateSession")) - return func(info trace.RetryLoopDoneInfo) { - onDone(s, info.Attempts, info.Error) - } + return func(info trace.RetryLoopDoneInfo) { + onDone(s, info.Attempts, info.Error) } }, }), @@ -381,7 +381,9 @@ func (c *Client) internalPoolGet(ctx context.Context, opts ...getOption) (s *ses } } - onDone := trace.TableOnPoolGet(o.t, &ctx, stack.FunctionID("")) + onDone := trace.TableOnPoolGet(o.t, &ctx, + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/table.(*Client).internalPoolGet"), + ) defer func() { onDone(s, i, err) }() @@ -477,7 +479,9 @@ func (c *Client) internalPoolWaitFromCh(ctx context.Context, t *trace.Table) (s el = c.waitQ.PushBack(ch) }) - waitDone := trace.TableOnPoolWait(t, &ctx, stack.FunctionID("")) + waitDone := trace.TableOnPoolWait(t, &ctx, + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/table.(*Client).internalPoolWaitFromCh"), + ) defer func() { waitDone(s, err) @@ -485,7 +489,10 @@ func (c *Client) internalPoolWaitFromCh(ctx context.Context, t *trace.Table) (s var createSessionTimeoutCh <-chan time.Time if timeout := c.config.CreateSessionTimeout(); timeout > 0 { - createSessionTimeoutCh = c.clock.After(timeout) + createSessionTimeoutChTimer := c.clock.NewTimer(timeout) + defer createSessionTimeoutChTimer.Stop() + + createSessionTimeoutCh = createSessionTimeoutChTimer.Chan() } select { @@ -539,7 +546,7 @@ func (c *Client) internalPoolWaitFromCh(ctx context.Context, t *trace.Table) (s // panic. func (c *Client) Put(ctx context.Context, s *session) (err error) { onDone := trace.TableOnPoolPut(c.config.Trace(), &ctx, - stack.FunctionID(""), + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/table.(*Client).Put"), s, ) defer func() { @@ -598,7 +605,9 @@ func (c *Client) Close(ctx context.Context) (err error) { default: close(c.done) - onDone := trace.TableOnClose(c.config.Trace(), &ctx, stack.FunctionID("")) + onDone := trace.TableOnClose(c.config.Trace(), &ctx, + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/table.(*Client).Close"), + ) defer func() { onDone(err) }() @@ -606,12 +615,18 @@ func (c *Client) Close(ctx context.Context) (err error) { c.limit = 0 for el := c.waitQ.Front(); el != nil; el = el.Next() { - ch := el.Value.(*chan *session) + ch, ok := el.Value.(*chan *session) + if !ok { + panic(fmt.Sprintf("unsupported type conversion from %T to *chan *session", ch)) + } close(*ch) } for e := c.idle.Front(); e != nil; e = e.Next() { - s := e.Value.(*session) + s, ok := e.Value.(*session) + if !ok { + panic(fmt.Sprintf("unsupported type conversion from %T to *chan *session", s)) + } s.SetStatus(table.SessionClosing) c.wg.Add(1) go func() { @@ -643,17 +658,16 @@ func (c *Client) Do(ctx context.Context, op table.Operation, opts ...table.Optio config := c.retryOptions(opts...) - attempts, onIntermediate := 0, trace.TableOnDo(config.Trace, &ctx, - stack.FunctionID(""), + attempts, onDone := 0, trace.TableOnDo(config.Trace, &ctx, + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/table.(*Client).Do"), config.Label, config.Idempotent, xcontext.IsNestedCall(ctx), ) defer func() { - onIntermediate(finalErr)(attempts, finalErr) + onDone(attempts, finalErr) }() err := do(ctx, c, c.config, op, func(err error) { attempts++ - onIntermediate(err) }, config.RetryOptions...) if err != nil { return xerrors.WithStackTrace(err) @@ -673,22 +687,18 @@ func (c *Client) DoTx(ctx context.Context, op table.TxOperation, opts ...table.O config := c.retryOptions(opts...) - attempts, onIntermediate := 0, trace.TableOnDoTx(config.Trace, &ctx, - stack.FunctionID(""), + attempts, onDone := 0, trace.TableOnDoTx(config.Trace, &ctx, + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/table.(*Client).DoTx"), config.Label, config.Idempotent, xcontext.IsNestedCall(ctx), ) defer func() { - onIntermediate(finalErr)(attempts, finalErr) + onDone(attempts, finalErr) }() return retryBackoff(ctx, c, func(ctx context.Context, s table.Session) (err error) { attempts++ - defer func() { - onIntermediate(err) - }() - tx, err := s.BeginTransaction(ctx, config.TxSettings) if err != nil { return xerrors.WithStackTrace(err) @@ -741,7 +751,10 @@ func (c *Client) internalPoolGCTick(ctx context.Context, idleThreshold time.Dura return } for e := c.idle.Front(); e != nil; e = e.Next() { - s := e.Value.(*session) + s, ok := e.Value.(*session) + if !ok { + panic(fmt.Sprintf("unsupported type conversion from %T to *session", s)) + } info, has := c.index[s] if !has { panic("session not found in pool") @@ -777,7 +790,7 @@ func (c *Client) internalPoolGC(ctx context.Context, idleThreshold time.Duration case <-timer.Chan(): c.internalPoolGCTick(ctx, idleThreshold) - timer.Reset(idleThreshold / 2) + timer.Reset(idleThreshold / 2) //nolint:gomnd } } } @@ -814,7 +827,10 @@ func (c *Client) internalPoolPeekFirstIdle() (s *session, touched time.Time) { if el == nil { return } - s = el.Value.(*session) + s, ok := el.Value.(*session) + if !ok { + panic(fmt.Sprintf("unsupported type conversion from %T to *session", s)) + } info, has := c.index[s] if !has || el != info.idle { panic("inconsistent session client index") @@ -853,7 +869,10 @@ func (c *Client) internalPoolNotify(s *session) (notified bool) { // missed something and may want to retry (especially for case (3)). // // After that we taking a next waiter and repeat the same. - ch := c.waitQ.Remove(el).(*chan *session) + ch, ok := c.waitQ.Remove(el).(*chan *session) + if !ok { + panic(fmt.Sprintf("unsupported type conversion from %T to *chan *session", ch)) + } select { case *ch <- s: // Case (1). diff --git a/internal/table/client_test.go b/internal/table/client_test.go index 82b7eb6af..6f1331b6e 100644 --- a/internal/table/client_test.go +++ b/internal/table/client_test.go @@ -912,7 +912,10 @@ func (s *StubBuilder) createSession(ctx context.Context) (session *session, err func (c *Client) debug() { fmt.Print("head ") for el := c.idle.Front(); el != nil; el = el.Next() { - s := el.Value.(*session) + s, ok := el.Value.(*session) + if !ok { + panic(fmt.Sprintf("unsupported type conversion from %T to *session", s)) + } x := c.index[s] fmt.Printf("<-> %s(%d) ", s.ID(), x.touched.Unix()) } diff --git a/internal/table/config/config.go b/internal/table/config/config.go index de94fb3e6..7c03b33e8 100644 --- a/internal/table/config/config.go +++ b/internal/table/config/config.go @@ -15,13 +15,19 @@ const ( DefaultSessionPoolSizeLimit = 50 DefaultSessionPoolIdleThreshold = 5 * time.Minute - // Deprecated: table client do not supports background session keep-aliving now + // Deprecated: table client do not supports background session keep-aliving now. + // Will be removed after Oct 2024. + // Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated DefaultKeepAliveMinSize = 10 - // Deprecated: table client do not supports background session keep-aliving now + // Deprecated: table client do not supports background session keep-aliving now. + // Will be removed after Oct 2024. + // Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated DefaultIdleKeepAliveThreshold = 2 - // Deprecated: table client do not supports background session keep-aliving now + // Deprecated: table client do not supports background session keep-aliving now. + // Will be removed after Oct 2024. + // Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated DefaultSessionPoolKeepAliveTimeout = 500 * time.Millisecond ) @@ -61,7 +67,9 @@ func WithSizeLimit(sizeLimit int) Option { // If keepAliveMinSize is less than zero, then no sessions will be preserved // If keepAliveMinSize is zero, the DefaultKeepAliveMinSize is used // -// Deprecated: table client do not supports background session keep-aliving now +// Deprecated: table client do not supports background session keep-aliving now. +// Will be removed after Oct 2024. +// Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated func WithKeepAliveMinSize(keepAliveMinSize int) Option { return func(c *Config) {} } @@ -73,7 +81,9 @@ func WithKeepAliveMinSize(keepAliveMinSize int) Option { // be removed ever. // If IdleKeepAliveThreshold is equal to zero, it will be set to DefaultIdleKeepAliveThreshold // -// Deprecated: table client do not support background session keep-aliving now +// Deprecated: table client do not supports background session keep-aliving now. +// Will be removed after Oct 2024. +// Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated func WithIdleKeepAliveThreshold(idleKeepAliveThreshold int) Option { return func(c *Config) {} } @@ -95,7 +105,9 @@ func WithIdleThreshold(idleThreshold time.Duration) Option { // WithKeepAliveTimeout limits maximum time spent on KeepAlive request // If keepAliveTimeout is less than or equal to zero then the DefaultSessionPoolKeepAliveTimeout is used. // -// Deprecated: table client do not support background session keep-aliving now +// Deprecated: table client do not supports background session keep-aliving now. +// Will be removed after Oct 2024. +// Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated func WithKeepAliveTimeout(keepAliveTimeout time.Duration) Option { return func(c *Config) {} } @@ -182,7 +194,9 @@ func (c *Config) SizeLimit() int { // If KeepAliveMinSize is less than zero, then no sessions will be preserved // If KeepAliveMinSize is zero, the DefaultKeepAliveMinSize is used // -// Deprecated: table client do not support background session keep-aliving now +// Deprecated: table client do not supports background session keep-aliving now. +// Will be removed after Oct 2024. +// Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated func (c *Config) KeepAliveMinSize() int { return DefaultKeepAliveMinSize } @@ -199,7 +213,9 @@ func (c *Config) IgnoreTruncated() bool { // be removed ever. // If IdleKeepAliveThreshold is equal to zero, it will be set to DefaultIdleKeepAliveThreshold // -// Deprecated: table client do not support background session keep-aliving now +// Deprecated: table client do not supports background session keep-aliving now. +// Will be removed after Oct 2024. +// Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated func (c *Config) IdleKeepAliveThreshold() int { return DefaultIdleKeepAliveThreshold } @@ -216,7 +232,9 @@ func (c *Config) IdleThreshold() time.Duration { // KeepAliveTimeout limits maximum time spent on KeepAlive request // If KeepAliveTimeout is less than or equal to zero then the DefaultSessionPoolKeepAliveTimeout is used. // -// Deprecated: table client do not support background session keep-aliving now +// Deprecated: table client do not supports background session keep-aliving now. +// Will be removed after Oct 2024. +// Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated func (c *Config) KeepAliveTimeout() time.Duration { return DefaultSessionPoolKeepAliveTimeout } diff --git a/internal/table/retry_test.go b/internal/table/retry_test.go index 008633786..70d58ed8b 100644 --- a/internal/table/retry_test.go +++ b/internal/table/retry_test.go @@ -160,7 +160,11 @@ func TestRetryerSessionClosing(t *testing.T) { config.New(), func(ctx context.Context, s table.Session) error { sessions = append(sessions, s) - s.(*session).SetStatus(table.SessionClosing) + val, ok := s.(*session) + if !ok { + panic(fmt.Sprintf("unsupported type conversion from %T to *session", val)) + } + val.SetStatus(table.SessionClosing) return nil }, diff --git a/internal/table/scanner/result.go b/internal/table/scanner/result.go index 1cbc88eb6..fdebc2859 100644 --- a/internal/table/scanner/result.go +++ b/internal/table/scanner/result.go @@ -170,7 +170,7 @@ func (r *unaryResult) NextResultSet(ctx context.Context, columns ...string) bool func (r *streamResult) nextResultSetErr(ctx context.Context, columns ...string) (err error) { // skipping second recv because first call of recv is from New Stream(), second call is from user - if r.nextResultSetCounter.Add(1) == 2 { + if r.nextResultSetCounter.Add(1) == 2 { //nolint:gomnd r.setColumnIndexes(columns) return ctx.Err() diff --git a/internal/table/scanner/result_test.go b/internal/table/scanner/result_test.go index dc81b0203..a564e6dc6 100644 --- a/internal/table/scanner/result_test.go +++ b/internal/table/scanner/result_test.go @@ -264,18 +264,30 @@ func TestNewStreamWithRecvFirstResultSet(t *testing.T) { require.NoError(t, err) require.NotNil(t, result) require.EqualValues(t, 1, tt.recvCounter) - require.EqualValues(t, 1, result.(*streamResult).nextResultSetCounter.Load()) + val, ok := result.(*streamResult) + if !ok { + panic(fmt.Sprintf("unsupported type conversion from %T to *streamResult", val)) + } + require.EqualValues(t, 1, val.nextResultSetCounter.Load()) for i := range make([]struct{}, 1000) { err = result.NextResultSetErr(tt.ctx) require.NoError(t, err) require.Equal(t, i+1, tt.recvCounter) - require.Equal(t, i+2, int(result.(*streamResult).nextResultSetCounter.Load())) + val, check := result.(*streamResult) + if !check { + panic(fmt.Sprintf("unsupported type conversion from %T to *streamResult", val)) + } + require.Equal(t, i+2, int(val.nextResultSetCounter.Load())) } err = result.NextResultSetErr(tt.ctx) require.ErrorIs(t, err, io.EOF) require.True(t, err == io.EOF) //nolint:errorlint,testifylint require.Equal(t, 1001, tt.recvCounter) - require.Equal(t, 1002, int(result.(*streamResult).nextResultSetCounter.Load())) + res, ok := result.(*streamResult) + if !ok { + panic(fmt.Sprintf("unsupported type conversion from %T to *streamResult", res)) + } + require.Equal(t, 1002, int(res.nextResultSetCounter.Load())) } }) } diff --git a/internal/table/scanner/scan_raw.go b/internal/table/scanner/scan_raw.go index 7c0782f3c..c3b114c2d 100644 --- a/internal/table/scanner/scan_raw.go +++ b/internal/table/scanner/scan_raw.go @@ -2,6 +2,7 @@ package scanner import ( "bytes" + "fmt" "io" "reflect" "strconv" @@ -584,8 +585,10 @@ func (s *rawConverter) IsDecimal() bool { } func isEqualDecimal(d *Ydb.DecimalType, t types.Type) bool { - w := t.(*types.Decimal) - + w, ok := t.(*types.Decimal) + if !ok { + panic(fmt.Sprintf("unsupported type conversion from %T to *types.Decimal", w)) + } return d.GetPrecision() == w.Precision() && d.GetScale() == w.Scale() } diff --git a/internal/table/scanner/scanner.go b/internal/table/scanner/scanner.go index f541a1235..8a3effb2b 100644 --- a/internal/table/scanner/scanner.go +++ b/internal/table/scanner/scanner.go @@ -1174,7 +1174,7 @@ func (s *valueScanner) errorf(depth int, f string, args ...interface{}) error { func (s *valueScanner) typeError(act, exp interface{}) { _ = s.errorf( - 2, + 2, //nolint:gomnd "unexpected types during scan at %q %s: %s; want %s", s.path(), s.getType(), @@ -1186,7 +1186,7 @@ func (s *valueScanner) typeError(act, exp interface{}) { func (s *valueScanner) valueTypeError(act, exp interface{}) { // unexpected value during scan at \"migration_status\" Int64: NullFlag; want Int64 _ = s.errorf( - 2, + 2, //nolint:gomnd "unexpected value during scan at %q %s: %s; want %s", s.path(), s.getType(), @@ -1197,7 +1197,7 @@ func (s *valueScanner) valueTypeError(act, exp interface{}) { func (s *valueScanner) notFoundColumnByIndex(idx int) error { return s.errorf( - 2, + 2, //nolint:gomnd "not found %d column", idx, ) @@ -1205,7 +1205,7 @@ func (s *valueScanner) notFoundColumnByIndex(idx int) error { func (s *valueScanner) notFoundColumnName(name string) error { return s.errorf( - 2, + 2, //nolint:gomnd "not found column '%s'", name, ) @@ -1213,7 +1213,7 @@ func (s *valueScanner) notFoundColumnName(name string) error { func (s *valueScanner) noColumnError(name string) error { return s.errorf( - 2, + 2, //nolint:gomnd "no column %q", name, ) @@ -1221,7 +1221,7 @@ func (s *valueScanner) noColumnError(name string) error { func (s *valueScanner) overflowError(i, n interface{}) error { return s.errorf( - 2, + 2, //nolint:gomnd "overflow error: %d overflows capacity of %t", i, n, diff --git a/internal/table/session.go b/internal/table/session.go index 0b8eba0ee..98e8ce4db 100644 --- a/internal/table/session.go +++ b/internal/table/session.go @@ -116,7 +116,9 @@ func (s *session) isClosing() bool { func newSession(ctx context.Context, cc grpc.ClientConnInterface, config *config.Config) ( s *session, err error, ) { - onDone := trace.TableOnSessionNew(config.Trace(), &ctx, stack.FunctionID("")) + onDone := trace.TableOnSessionNew(config.Trace(), &ctx, + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/table.newSession"), + ) defer func() { onDone(s, err) }() @@ -179,7 +181,7 @@ func (s *session) Close(ctx context.Context) (err error) { s.closeOnce.Do(func() { onDone := trace.TableOnSessionDelete(s.config.Trace(), &ctx, - stack.FunctionID(""), + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/table.(*session).Close"), s, ) defer func() { @@ -231,7 +233,7 @@ func (s *session) KeepAlive(ctx context.Context) (err error) { result Ydb_Table.KeepAliveResult onDone = trace.TableOnSessionKeepAlive( s.config.Trace(), &ctx, - stack.FunctionID(""), + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/table.(*session).KeepAlive"), s, ) ) @@ -485,7 +487,7 @@ func (s *session) checkError(err error) { if err == nil { return } - if m := retry.Check(err); m.MustDeleteSession() { + if m := retry.Check(err); m.IsRetryObjectValid() { s.SetStatus(table.SessionClosing) } } @@ -600,6 +602,56 @@ func (s *session) CopyTables( return nil } +func renameTables( + ctx context.Context, + sessionID string, + operationTimeout time.Duration, + operationCancelAfter time.Duration, + service interface { + RenameTables( + ctx context.Context, in *Ydb_Table.RenameTablesRequest, opts ...grpc.CallOption, + ) (*Ydb_Table.RenameTablesResponse, error) + }, + opts ...options.RenameTablesOption, +) (err error) { + request := Ydb_Table.RenameTablesRequest{ + SessionId: sessionID, + OperationParams: operation.Params( + ctx, + operationTimeout, + operationCancelAfter, + operation.ModeSync, + ), + } + for _, opt := range opts { + if opt != nil { + opt((*options.RenameTablesDesc)(&request)) + } + } + if len(request.GetTables()) == 0 { + return xerrors.WithStackTrace(fmt.Errorf("no RenameTablesItem: %w", errParamsRequired)) + } + _, err = service.RenameTables(ctx, &request) + if err != nil { + return xerrors.WithStackTrace(err) + } + + return nil +} + +// RenameTables renames tables. +func (s *session) RenameTables( + ctx context.Context, + opts ...options.RenameTablesOption, +) (err error) { + err = renameTables(ctx, s.id, s.config.OperationTimeout(), s.config.OperationCancelAfter(), s.tableService, opts...) + if err != nil { + return xerrors.WithStackTrace(err) + } + + return nil +} + // Explain explains data query represented by text. func (s *session) Explain( ctx context.Context, @@ -613,7 +665,7 @@ func (s *session) Explain( response *Ydb_Table.ExplainDataQueryResponse onDone = trace.TableOnSessionQueryExplain( s.config.Trace(), &ctx, - stack.FunctionID(""), + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/table.(*session).Explain"), s, query, ) ) @@ -662,7 +714,7 @@ func (s *session) Prepare(ctx context.Context, queryText string) (_ table.Statem result Ydb_Table.PrepareQueryResult onDone = trace.TableOnSessionQueryPrepare( s.config.Trace(), &ctx, - stack.FunctionID(""), + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/table.(*session).Prepare"), s, queryText, ) ) @@ -745,7 +797,7 @@ func (s *session) Execute( onDone := trace.TableOnSessionQueryExecute( s.config.Trace(), &ctx, - stack.FunctionID(""), + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/table.(*session).Execute"), s, q, parameters, request.QueryCachePolicy.GetKeepInCache(), ) @@ -984,8 +1036,8 @@ func (s *session) StreamReadTable( opts ...options.ReadTableOption, ) (_ result.StreamResult, err error) { var ( - onIntermediate = trace.TableOnSessionQueryStreamRead(s.config.Trace(), &ctx, - stack.FunctionID(""), + onDone = trace.TableOnSessionQueryStreamRead(s.config.Trace(), &ctx, + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/table.(*session).StreamReadTable"), s, ) request = Ydb_Table.ReadTableRequest{ @@ -997,9 +1049,7 @@ func (s *session) StreamReadTable( ) defer func() { a.Free() - if err != nil { - onIntermediate(xerrors.HideEOF(err))(xerrors.HideEOF(err)) - } + onDone(xerrors.HideEOF(err)) }() for _, opt := range opts { @@ -1023,9 +1073,6 @@ func (s *session) StreamReadTable( stats *Ydb_TableStats.QueryStats, err error, ) { - defer func() { - onIntermediate(xerrors.HideEOF(err)) - }() select { case <-ctx.Done(): return nil, nil, xerrors.WithStackTrace(ctx.Err()) @@ -1042,7 +1089,7 @@ func (s *session) StreamReadTable( }, func(err error) error { cancel() - onIntermediate(xerrors.HideEOF(err))(xerrors.HideEOF(err)) + onDone(xerrors.HideEOF(err)) return err }, @@ -1105,11 +1152,11 @@ func (s *session) StreamExecuteScanQuery( opts ...options.ExecuteScanQueryOption, ) (_ result.StreamResult, err error) { var ( - a = allocator.New() - q = queryFromText(query) - onIntermediate = trace.TableOnSessionQueryStreamExecute( + a = allocator.New() + q = queryFromText(query) + onDone = trace.TableOnSessionQueryStreamExecute( s.config.Trace(), &ctx, - stack.FunctionID(""), + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/table.(*session).StreamExecuteScanQuery"), s, q, parameters, ) request = Ydb_Table.ExecuteScanQueryRequest{ @@ -1122,9 +1169,7 @@ func (s *session) StreamExecuteScanQuery( ) defer func() { a.Free() - if err != nil { - onIntermediate(xerrors.HideEOF(err))(xerrors.HideEOF(err)) - } + onDone(xerrors.HideEOF(err)) }() for _, opt := range opts { @@ -1148,9 +1193,6 @@ func (s *session) StreamExecuteScanQuery( stats *Ydb_TableStats.QueryStats, err error, ) { - defer func() { - onIntermediate(xerrors.HideEOF(err)) - }() select { case <-ctx.Done(): return nil, nil, xerrors.WithStackTrace(ctx.Err()) @@ -1167,7 +1209,7 @@ func (s *session) StreamExecuteScanQuery( }, func(err error) error { cancel() - onIntermediate(xerrors.HideEOF(err))(xerrors.HideEOF(err)) + onDone(xerrors.HideEOF(err)) return err }, @@ -1185,7 +1227,7 @@ func (s *session) BulkUpsert(ctx context.Context, table string, rows value.Value callOptions []grpc.CallOption onDone = trace.TableOnSessionBulkUpsert( s.config.Trace(), &ctx, - stack.FunctionID(""), + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/table.(*session).BulkUpsert"), s, ) ) @@ -1229,9 +1271,9 @@ func (s *session) BeginTransaction( var ( result Ydb_Table.BeginTransactionResult response *Ydb_Table.BeginTransactionResponse - onDone = trace.TableOnSessionTransactionBegin( + onDone = trace.TableOnTxBegin( s.config.Trace(), &ctx, - stack.FunctionID(""), + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/table.(*session).BeginTransaction"), s, ) ) diff --git a/internal/table/session_test.go b/internal/table/session_test.go index 006086369..1c45a3c6a 100644 --- a/internal/table/session_test.go +++ b/internal/table/session_test.go @@ -462,7 +462,11 @@ func TestCreateTableRegression(t *testing.T) { "attr": "attr_value", }, } - if !proto.Equal(exp, act.(proto.Message)) { + val, ok := act.(proto.Message) + if !ok { + panic(fmt.Sprintf("unsupported type conversion from %T to proto.Message", val)) + } + if !proto.Equal(exp, val) { //nolint:revive return nil, fmt.Errorf("proto's not equal: \n\nact: %v\n\nexp: %s\n\n", act, exp) } @@ -639,7 +643,7 @@ func (mock *copyTablesMock) CopyTables( return nil, fmt.Errorf("%w: %s, exp: %s", errUnexpectedRequest, in, mock.String()) } -func Test_copyTables(t *testing.T) { +func TestCopyTables(t *testing.T) { ctx := xtest.Context(t) for _, tt := range []struct { sessionID string @@ -758,3 +762,137 @@ func Test_copyTables(t *testing.T) { }) } } + +type renameTablesMock struct { + *Ydb_Table.RenameTablesRequest +} + +func (mock *renameTablesMock) RenameTables( + _ context.Context, in *Ydb_Table.RenameTablesRequest, opts ...grpc.CallOption, +) (*Ydb_Table.RenameTablesResponse, error) { + if in.String() == mock.String() { + return &Ydb_Table.RenameTablesResponse{}, nil + } + + return nil, fmt.Errorf("%w: %s, exp: %s", errUnexpectedRequest, in, mock.String()) +} + +func TestRenameTables(t *testing.T) { + ctx := xtest.Context(t) + for _, tt := range []struct { + sessionID string + operationTimeout time.Duration + operationCancelAfter time.Duration + service *renameTablesMock + opts []options.RenameTablesOption + err error + }{ + { + sessionID: "1", + operationTimeout: time.Second, + operationCancelAfter: time.Second, + service: &renameTablesMock{ + RenameTablesRequest: &Ydb_Table.RenameTablesRequest{ + SessionId: "1", + Tables: []*Ydb_Table.RenameTableItem{ + { + SourcePath: "from", + DestinationPath: "to", + ReplaceDestination: true, + }, + }, + OperationParams: &Ydb_Operations.OperationParams{ + OperationMode: Ydb_Operations.OperationParams_SYNC, + OperationTimeout: durationpb.New(time.Second), + CancelAfter: durationpb.New(time.Second), + }, + }, + }, + opts: []options.RenameTablesOption{ + options.RenameTablesItem("from", "to", true), + }, + err: nil, + }, + { + sessionID: "2", + operationTimeout: 2 * time.Second, + operationCancelAfter: 2 * time.Second, + service: &renameTablesMock{ + RenameTablesRequest: &Ydb_Table.RenameTablesRequest{ + SessionId: "2", + Tables: []*Ydb_Table.RenameTableItem{ + { + SourcePath: "from1", + DestinationPath: "to1", + ReplaceDestination: true, + }, + { + SourcePath: "from2", + DestinationPath: "to2", + ReplaceDestination: false, + }, + { + SourcePath: "from3", + DestinationPath: "to3", + ReplaceDestination: true, + }, + }, + OperationParams: &Ydb_Operations.OperationParams{ + OperationMode: Ydb_Operations.OperationParams_SYNC, + OperationTimeout: durationpb.New(2 * time.Second), + CancelAfter: durationpb.New(2 * time.Second), + }, + }, + }, + opts: []options.RenameTablesOption{ + options.RenameTablesItem("from1", "to1", true), + options.RenameTablesItem("from2", "to2", false), + options.RenameTablesItem("from3", "to3", true), + }, + err: nil, + }, + { + sessionID: "3", + operationTimeout: time.Second, + operationCancelAfter: time.Second, + service: &renameTablesMock{ + RenameTablesRequest: &Ydb_Table.RenameTablesRequest{ + SessionId: "1", + Tables: []*Ydb_Table.RenameTableItem{ + { + SourcePath: "from", + DestinationPath: "to", + ReplaceDestination: true, + }, + }, + OperationParams: &Ydb_Operations.OperationParams{ + OperationMode: Ydb_Operations.OperationParams_SYNC, + OperationTimeout: durationpb.New(time.Second), + CancelAfter: durationpb.New(time.Second), + }, + }, + }, + opts: []options.RenameTablesOption{ + options.RenameTablesItem("from1", "to1", true), + }, + err: errUnexpectedRequest, + }, + { + sessionID: "4", + operationTimeout: time.Second, + operationCancelAfter: time.Second, + service: &renameTablesMock{}, + opts: nil, + err: errParamsRequired, + }, + } { + t.Run("", func(t *testing.T) { + err := renameTables(ctx, tt.sessionID, tt.operationTimeout, tt.operationCancelAfter, tt.service, tt.opts...) + if tt.err != nil { + require.ErrorIs(t, err, tt.err) + } else { + require.NoError(t, err) + } + }) + } +} diff --git a/internal/table/statement.go b/internal/table/statement.go index c203b36d5..eb797e2ef 100644 --- a/internal/table/statement.go +++ b/internal/table/statement.go @@ -62,7 +62,7 @@ func (s *statement) Execute( onDone := trace.TableOnSessionQueryExecute( s.session.config.Trace(), &ctx, - stack.FunctionID(""), + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/table.(*statement).Execute"), s.session, s.query, parameters, request.QueryCachePolicy.GetKeepInCache(), ) diff --git a/internal/table/transaction.go b/internal/table/transaction.go index 83045706a..6ee77ad7d 100644 --- a/internal/table/transaction.go +++ b/internal/table/transaction.go @@ -61,9 +61,9 @@ func (tx *transaction) Execute( query string, parameters *params.Parameters, opts ...options.ExecuteDataQueryOption, ) (r result.Result, err error) { - onDone := trace.TableOnSessionTransactionExecute( + onDone := trace.TableOnTxExecute( tx.s.config.Trace(), &ctx, - stack.FunctionID(""), + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/table.(*transaction).Execute"), tx.s, tx, queryFromText(query), parameters, ) defer func() { @@ -98,7 +98,17 @@ func (tx *transaction) ExecuteStatement( a := allocator.New() defer a.Free() - onDone := trace.TableOnSessionTransactionExecuteStatement( + val, ok := stmt.(*statement) + if !ok { + panic(fmt.Sprintf("unsupported type conversion from %T to *statement", val)) + } + + val, ok := stmt.(*statement) + if !ok { + panic(fmt.Sprintf("unsupported type conversion from %T to *statement", val)) + } + + onDone := trace.TableOnTxExecuteStatement( tx.s.config.Trace(), &ctx, stack.FunctionID(""), tx.s, tx, stmt.(*statement).query, parameters, @@ -131,9 +141,9 @@ func (tx *transaction) CommitTx( ctx context.Context, opts ...options.CommitTransactionOption, ) (r result.Result, err error) { - onDone := trace.TableOnSessionTransactionCommit( + onDone := trace.TableOnTxCommit( tx.s.config.Trace(), &ctx, - stack.FunctionID(""), + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/table.(*transaction).CommitTx"), tx.s, tx, ) defer func() { @@ -189,9 +199,9 @@ func (tx *transaction) CommitTx( // Rollback performs a rollback of the specified active transaction. func (tx *transaction) Rollback(ctx context.Context) (err error) { - onDone := trace.TableOnSessionTransactionRollback( + onDone := trace.TableOnTxRollback( tx.s.config.Trace(), &ctx, - stack.FunctionID(""), + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/table.(*transaction).Rollback"), tx.s, tx, ) defer func() { diff --git a/internal/topic/retriable_error.go b/internal/topic/retriable_error.go index 23ef3c49c..744aca97a 100644 --- a/internal/topic/retriable_error.go +++ b/internal/topic/retriable_error.go @@ -13,7 +13,7 @@ import ( ) const ( - DefaultStartTimeout = time.Minute + DefaultStartTimeout = value.InfiniteDuration connectionEstablishedTimeout = time.Minute ) @@ -41,7 +41,7 @@ type PublicCheckRetryResult struct { var ( PublicRetryDecisionDefault = PublicCheckRetryResult{val: 0} PublicRetryDecisionRetry = PublicCheckRetryResult{val: 1} - PublicRetryDecisionStop = PublicCheckRetryResult{val: 2} + PublicRetryDecisionStop = PublicCheckRetryResult{val: 2} //nolint:gomnd ) func CheckResetReconnectionCounters(lastTry, now time.Time, connectionTimeout time.Duration) bool { diff --git a/internal/topic/topicreaderinternal/batcher.go b/internal/topic/topicreaderinternal/batcher.go index deebbb209..8eaa5eb6b 100644 --- a/internal/topic/topicreaderinternal/batcher.go +++ b/internal/topic/topicreaderinternal/batcher.go @@ -176,7 +176,7 @@ func (b *batcher) Pop(ctx context.Context, opts batcherGetOptions) (_ batcherMes findRes = b.findNeedLock(opts) if findRes.Ok { - b.applyNeedLock(findRes) + b.applyNeedLock(&findRes) return } @@ -279,7 +279,7 @@ func (b *batcher) applyForceFlagToOptions(options batcherGetOptions) batcherGetO return res } -func (b *batcher) applyNeedLock(res batcherResultCandidate) { +func (b *batcher) applyNeedLock(res *batcherResultCandidate) { if res.Rest.IsEmpty() && res.WaiterIndex >= 0 { delete(b.messages, res.Key) } else { diff --git a/internal/topic/topicreaderinternal/batcher_test.go b/internal/topic/topicreaderinternal/batcher_test.go index 1bcdb33ef..1f7f0d0f0 100644 --- a/internal/topic/topicreaderinternal/batcher_test.go +++ b/internal/topic/topicreaderinternal/batcher_test.go @@ -3,6 +3,7 @@ package topicreaderinternal import ( "context" "errors" + "fmt" "sync/atomic" "testing" "time" @@ -338,11 +339,17 @@ func TestBatcherConcurency(t *testing.T) { for i := 0; i < count; i++ { res, err := b.Pop(ctx, batcherGetOptions{MinCount: 1}) + + val, ok := res.RawMessage.(*rawtopicreader.StartPartitionSessionRequest) + if !ok { + panic(fmt.Sprintf("unsupported type conversion from %T to *rawtopicreader.StartPartitionSessionRequest", val)) + } + require.NoError(tb, err) require.Equal( tb, rawtopicreader.NewOffset(int64(i)), - res.RawMessage.(*rawtopicreader.StartPartitionSessionRequest).CommittedOffset, + val.CommittedOffset, ) } }) @@ -407,7 +414,7 @@ func TestBatcher_Apply(t *testing.T) { Key: session, Rest: batcherMessageOrderItems{newBatcherItemBatch(batch)}, } - b.applyNeedLock(foundRes) + b.applyNeedLock(&foundRes) expectedMap := batcherMessagesMap{session: batcherMessageOrderItems{newBatcherItemBatch(batch)}} require.Equal(t, expectedMap, b.messages) @@ -426,7 +433,7 @@ func TestBatcher_Apply(t *testing.T) { b.messages = batcherMessagesMap{session: batcherMessageOrderItems{newBatcherItemBatch(batch)}} - b.applyNeedLock(foundRes) + b.applyNeedLock(&foundRes) require.Empty(t, b.messages) }) diff --git a/internal/topic/topicreaderinternal/commit_range.go b/internal/topic/topicreaderinternal/commit_range.go index c744cd49b..18d00b0d8 100644 --- a/internal/topic/topicreaderinternal/commit_range.go +++ b/internal/topic/topicreaderinternal/commit_range.go @@ -158,9 +158,7 @@ func (r *CommitRanges) toRawPartitionCommitOffset() []rawtopicreader.PartitionCo // PublicCommitRange contains data for commit messages range // -// # Experimental -// -// Notice: This API is EXPERIMENTAL and may be changed or removed in a later release. +// Experimental: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#experimental type PublicCommitRange struct { priv commitRange } diff --git a/internal/topic/topicreaderinternal/committer.go b/internal/topic/topicreaderinternal/committer.go index c3f22849f..f0fa1c425 100644 --- a/internal/topic/topicreaderinternal/committer.go +++ b/internal/topic/topicreaderinternal/committer.go @@ -126,7 +126,7 @@ func (c *committer) pushCommitsLoop(ctx context.Context) { var commits CommitRanges c.m.WithLock(func() { commits = c.commits - c.commits = NewCommitRangesWithCapacity(commits.len() * 2) + c.commits = NewCommitRangesWithCapacity(commits.len() * 2) //nolint:gomnd }) if commits.len() == 0 && c.backgroundWorker.Context().Err() != nil { @@ -166,7 +166,10 @@ func (c *committer) waitSendTrigger(ctx context.Context) { return } - finish := c.clock.After(c.BufferTimeLagTrigger) + bufferTimeLagTriggerTimer := c.clock.NewTimer(c.BufferTimeLagTrigger) + defer bufferTimeLagTriggerTimer.Stop() + + finish := bufferTimeLagTriggerTimer.Chan() if c.BufferCountTrigger == 0 { select { case <-ctxDone: diff --git a/internal/topic/topicreaderinternal/committer_test.go b/internal/topic/topicreaderinternal/committer_test.go index 7bf444d62..d94f7ec2a 100644 --- a/internal/topic/topicreaderinternal/committer_test.go +++ b/internal/topic/topicreaderinternal/committer_test.go @@ -3,6 +3,7 @@ package topicreaderinternal import ( "context" "errors" + "fmt" "testing" "time" @@ -231,7 +232,11 @@ func TestCommitterBuffer(t *testing.T) { c.clock = clock c.BufferTimeLagTrigger = time.Second c.send = func(msg rawtopicreader.ClientMessage) error { - commitMess := msg.(*rawtopicreader.CommitOffsetRequest) + val, ok := msg.(*rawtopicreader.CommitOffsetRequest) + if !ok { + panic(fmt.Sprintf("unsupported type conversion from %T to *rawtopicreader.CommitOffsetRequest", val)) + } + commitMess := val require.Len(t, commitMess.CommitOffsets, 2) close(sendCalled) @@ -264,7 +269,10 @@ func TestCommitterBuffer(t *testing.T) { c.BufferTimeLagTrigger = time.Second // for prevent send c.BufferCountTrigger = 2 c.send = func(msg rawtopicreader.ClientMessage) error { - commitMess := msg.(*rawtopicreader.CommitOffsetRequest) + commitMess, ok := msg.(*rawtopicreader.CommitOffsetRequest) + if !ok { + panic(fmt.Sprintf("unsupported type conversion from %T to *rawtopicreader.CommitOffsetRequest", commitMess)) + } require.Len(t, commitMess.CommitOffsets, 4) close(sendCalled) @@ -299,7 +307,10 @@ func TestCommitterBuffer(t *testing.T) { c.BufferTimeLagTrigger = time.Second // for prevent send c.BufferCountTrigger = 4 c.send = func(msg rawtopicreader.ClientMessage) error { - commitMess := msg.(*rawtopicreader.CommitOffsetRequest) + commitMess, ok := msg.(*rawtopicreader.CommitOffsetRequest) + if !ok { + panic(fmt.Sprintf("unsupported type conversion from %T to *rawtopicreader.CommitOffsetRequest", commitMess)) + } require.Len(t, commitMess.CommitOffsets, 4) close(sendCalled) diff --git a/internal/topic/topicreaderinternal/stream_reader_impl.go b/internal/topic/topicreaderinternal/stream_reader_impl.go index 381ccd690..fec186ffc 100644 --- a/internal/topic/topicreaderinternal/stream_reader_impl.go +++ b/internal/topic/topicreaderinternal/stream_reader_impl.go @@ -22,6 +22,8 @@ import ( "github.com/ydb-platform/ydb-go-sdk/v3/trace" ) +const defaultBufferSize = 1024 * 1024 + var ( PublicErrCommitSessionToExpiredSession = xerrors.Wrap(errors.New("ydb: commit to expired session")) @@ -73,7 +75,7 @@ type topicStreamReaderConfig struct { func newTopicStreamReaderConfig() topicStreamReaderConfig { return topicStreamReaderConfig{ BaseContext: context.Background(), - BufferSizeProtoBytes: 1024 * 1024, + BufferSizeProtoBytes: defaultBufferSize, Cred: credentials.NewAnonymousCredentials(), CredUpdateInterval: time.Hour, CommitMode: CommitModeAsync, diff --git a/internal/topic/topicreaderinternal/stream_reconnector.go b/internal/topic/topicreaderinternal/stream_reconnector.go index 393b0ecea..ec601ba67 100644 --- a/internal/topic/topicreaderinternal/stream_reconnector.go +++ b/internal/topic/topicreaderinternal/stream_reconnector.go @@ -328,9 +328,12 @@ func (r *readerReconnector) connectWithTimeout() (_ batchedStreamReader, err err result <- connectResult{stream: stream, err: err} }() + connectionTimoutTimer := r.clock.NewTimer(r.connectTimeout) + defer connectionTimoutTimer.Stop() + var res connectResult select { - case <-r.clock.After(r.connectTimeout): + case <-connectionTimoutTimer.Chan(): // cancel connection context only if timeout exceed while connection // because if cancel context after connect - it will break cancel() diff --git a/internal/topic/topicwriterinternal/encoders.go b/internal/topic/topicwriterinternal/encoders.go index 7ae89d534..a1ca73ad9 100644 --- a/internal/topic/topicwriterinternal/encoders.go +++ b/internal/topic/topicwriterinternal/encoders.go @@ -225,7 +225,7 @@ func cacheMessages(messages []messageWithDataContent, codec rawtopiccommon.Codec } // no need goroutines and synchronization for zero or one worker - if workerCount < 2 { + if workerCount < 2 { //nolint:gomnd for i := range messages { if _, err := messages[i].GetEncodedBytes(codec); err != nil { return err diff --git a/internal/topic/topicwriterinternal/queue.go b/internal/topic/topicwriterinternal/queue.go index 25ff072d3..799b02d16 100644 --- a/internal/topic/topicwriterinternal/queue.go +++ b/internal/topic/topicwriterinternal/queue.go @@ -21,6 +21,7 @@ var ( ) const ( + //nolint:gomnd intSize = 32 << (^uint(0) >> 63) // copy from math package for use in go <= 1.16 maxInt = 1<<(intSize-1) - 1 // copy from math package for use in go <= 1.16 minInt = -1 << (intSize - 1) // copy from math package for use in go <= 1.16 diff --git a/internal/topic/topicwriterinternal/writer_grpc_mock_test.go b/internal/topic/topicwriterinternal/writer_grpc_mock_test.go index 1a977cb75..14e4e9598 100644 --- a/internal/topic/topicwriterinternal/writer_grpc_mock_test.go +++ b/internal/topic/topicwriterinternal/writer_grpc_mock_test.go @@ -101,8 +101,12 @@ func (t *topicWriterOperationUnavailable) StreamWrite(server Ydb_Topic_V1.TopicS return errors.New("failed to read messages block") } - if len(messagesMsg.GetClientMessage().(*Ydb_Topic.StreamWriteMessage_FromClient_WriteRequest). - WriteRequest.GetMessages()) == 0 { + tt, ok := messagesMsg.GetClientMessage().(*Ydb_Topic.StreamWriteMessage_FromClient_WriteRequest) + if !ok { + panic(fmt.Sprintf("unsupported type conversion from %T to *Ydb_Topic.StreamWriteMessage_FromClient_WriteRequest", tt)) + } + + if len(tt.WriteRequest.GetMessages()) == 0 { return errors.New("received zero messages block") } diff --git a/internal/topic/topicwriterinternal/writer_options.go b/internal/topic/topicwriterinternal/writer_options.go index 51b21b93d..5c88bdbb3 100644 --- a/internal/topic/topicwriterinternal/writer_options.go +++ b/internal/topic/topicwriterinternal/writer_options.go @@ -51,9 +51,7 @@ func WithTokenUpdateInterval(interval time.Duration) PublicWriterOption { // WithCommonConfig // -// # Experimental -// -// Notice: This API is EXPERIMENTAL and may be changed or removed in a later release. +// Experimental: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#experimental func WithCommonConfig(common config.Common) PublicWriterOption { return func(cfg *WriterReconnectorConfig) { cfg.Common = common diff --git a/internal/topic/topicwriterinternal/writer_reconnector.go b/internal/topic/topicwriterinternal/writer_reconnector.go index a3427b81e..dd119e1c3 100644 --- a/internal/topic/topicwriterinternal/writer_reconnector.go +++ b/internal/topic/topicwriterinternal/writer_reconnector.go @@ -84,8 +84,8 @@ func newWriterReconnectorConfig(options ...PublicWriterOption) WriterReconnector }, AutoSetSeqNo: true, AutoSetCreatedTime: true, - MaxMessageSize: 50 * 1024 * 1024, - MaxQueueLen: 1000, + MaxMessageSize: 50 * 1024 * 1024, //nolint:gomnd + MaxQueueLen: 1000, //nolint:gomnd RetrySettings: topic.RetrySettings{ StartTimeout: topic.DefaultStartTimeout, }, @@ -350,7 +350,7 @@ func (w *WriterReconnector) connectionLoop(ctx context.Context) { createStreamContext := func() (context.Context, context.CancelFunc) { // need suppress parent context cancelation for flush buffer while close writer - return xcontext.WithCancel(xcontext.WithoutDeadline(ctx)) + return xcontext.WithCancel(xcontext.ValueOnly(ctx)) } //nolint:ineffassign,staticcheck,wastedassign @@ -380,16 +380,21 @@ func (w *WriterReconnector) connectionLoop(ctx context.Context) { prevAttemptTime = now if reconnectReason != nil { - if backoff, retry := topic.CheckRetryMode(reconnectReason, w.retrySettings, w.clock.Since(startOfRetries)); retry { + retryDuration := w.clock.Since(startOfRetries) + if backoff, retry := topic.CheckRetryMode(reconnectReason, w.retrySettings, retryDuration); retry { delay := backoff.Delay(attempt) + delayTimer := w.clock.NewTimer(delay) select { case <-doneCtx: + delayTimer.Stop() + return - case <-w.clock.After(delay): + case <-delayTimer.Chan(): + delayTimer.Stop() // no really need, stop for common style only // pass } } else { - _ = w.close(ctx, reconnectReason) + _ = w.close(ctx, fmt.Errorf("%w, was retried (%v)", reconnectReason, retryDuration)) return } diff --git a/internal/topic/topicwriterinternal/writer_reconnector_test.go b/internal/topic/topicwriterinternal/writer_reconnector_test.go index 4bf18c6ff..27247d4e9 100644 --- a/internal/topic/topicwriterinternal/writer_reconnector_test.go +++ b/internal/topic/topicwriterinternal/writer_reconnector_test.go @@ -187,7 +187,10 @@ func TestWriterImpl_WriteCodecs(t *testing.T) { messReceived := make(chan rawtopiccommon.Codec, 2) e.stream.EXPECT().Send(gomock.Any()).Do(func(message rawtopicwriter.ClientMessage) { - writeReq := message.(*rawtopicwriter.WriteRequest) + writeReq, ok := message.(*rawtopicwriter.WriteRequest) + if !ok { + panic(fmt.Sprintf("unsupported type conversion from %T to *rawtopicwriter.WriteRequest", writeReq)) + } messReceived <- writeReq.Codec }) @@ -215,7 +218,10 @@ func TestWriterImpl_WriteCodecs(t *testing.T) { messReceived := make(chan rawtopiccommon.Codec, 2) e.stream.EXPECT().Send(gomock.Any()).Do(func(message rawtopicwriter.ClientMessage) { - writeReq := message.(*rawtopicwriter.WriteRequest) + writeReq, ok := message.(*rawtopicwriter.WriteRequest) + if !ok { + panic(fmt.Sprintf("unsupported type conversion from %T to *rawtopicwriter.WriteRequest", writeReq)) + } messReceived <- writeReq.Codec }) @@ -240,7 +246,10 @@ func TestWriterImpl_WriteCodecs(t *testing.T) { messReceived := make(chan rawtopiccommon.Codec, 2) e.stream.EXPECT().Send(gomock.Any()).Do(func(message rawtopicwriter.ClientMessage) { - writeReq := message.(*rawtopicwriter.WriteRequest) + writeReq, ok := message.(*rawtopicwriter.WriteRequest) + if !ok { + panic(fmt.Sprintf("unsupported type conversion from %T to *rawtopicwriter.WriteRequest", writeReq)) + } messReceived <- writeReq.Codec }).Times(codecMeasureIntervalBatches * 2) diff --git a/internal/topic/topicwriterinternal/writer_single_stream.go b/internal/topic/topicwriterinternal/writer_single_stream.go index fa048b1ab..4f01c56d1 100644 --- a/internal/topic/topicwriterinternal/writer_single_stream.go +++ b/internal/topic/topicwriterinternal/writer_single_stream.go @@ -83,7 +83,7 @@ func newSingleStreamWriterStopped( ) *SingleStreamWriter { return &SingleStreamWriter{ cfg: cfg, - background: *background.NewWorker(xcontext.WithoutDeadline(ctxForPProfLabelsOnly)), + background: *background.NewWorker(xcontext.ValueOnly(ctxForPProfLabelsOnly)), closeCompleted: make(empty.Chan), } } diff --git a/internal/types/types.go b/internal/types/types.go index 536ab7ada..ae8bffeea 100644 --- a/internal/types/types.go +++ b/internal/types/types.go @@ -479,7 +479,7 @@ func (v PgType) String() string { } func (v PgType) Yql() string { - return "pgunknown" + return fmt.Sprintf("PgType(%v)", v.OID) } func (v PgType) ToYDB(a *allocator.Allocator) *Ydb.Type { @@ -823,7 +823,11 @@ func (v *VariantStruct) ToYDB(a *allocator.Allocator) *Ydb.Type { typeVariant.VariantType = a.Variant() structItems := a.VariantStructItems() - structItems.StructItems = v.Struct.ToYDB(a).GetType().(*Ydb.Type_StructType).StructType + tt, ok := v.Struct.ToYDB(a).GetType().(*Ydb.Type_StructType) + if !ok { + panic(fmt.Sprintf("unsupported type conversion from %T to *Ydb.Type_StructType", tt)) + } + structItems.StructItems = tt.StructType typeVariant.VariantType.Type = structItems @@ -876,7 +880,11 @@ func (v *VariantTuple) ToYDB(a *allocator.Allocator) *Ydb.Type { typeVariant.VariantType = a.Variant() tupleItems := a.VariantTupleItems() - tupleItems.TupleItems = v.Tuple.ToYDB(a).GetType().(*Ydb.Type_TupleType).TupleType + tt, ok := v.Tuple.ToYDB(a).GetType().(*Ydb.Type_TupleType) + if !ok { + panic(fmt.Sprintf("unsupported type conversion from %T to *Ydb.Type_TupleType", tt)) + } + tupleItems.TupleItems = tt.TupleType typeVariant.VariantType.Type = tupleItems diff --git a/internal/types/types_test.go b/internal/types/types_test.go index c2f81373e..3c9936a2a 100644 --- a/internal/types/types_test.go +++ b/internal/types/types_test.go @@ -271,7 +271,7 @@ func TestTypeToString(t *testing.T) { }, { t: PgType{OID: pg.OIDUnknown}, - s: "pgunknown", + s: "PgType(705)", }, } { t.Run(tt.s, func(t *testing.T) { diff --git a/internal/value/nullable.go b/internal/value/nullable.go index 1553d3eb1..ed03db18a 100644 --- a/internal/value/nullable.go +++ b/internal/value/nullable.go @@ -303,27 +303,71 @@ func NullableDyNumberValue(v *string) Value { func Nullable(t types.Type, v interface{}) Value { switch t { case types.Bool: - return NullableBoolValue(v.(*bool)) + tt, ok := v.(*bool) + if !ok { + panic(fmt.Sprintf("unsupported type conversion from %T to TypeBool", tt)) + } + return NullableBoolValue(tt) case types.Int8: - return NullableInt8Value(v.(*int8)) + tt, ok := v.(*int8) + if !ok { + panic(fmt.Sprintf("unsupported type conversion from %T to TypeBool", tt)) + } + return NullableInt8Value(tt) case types.Uint8: - return NullableUint8Value(v.(*uint8)) + tt, ok := v.(*uint8) + if !ok { + panic(fmt.Sprintf("unsupported type conversion from %T to TypeUint8", tt)) + } + return NullableUint8Value(tt) case types.Int16: - return NullableInt16Value(v.(*int16)) + tt, ok := v.(*int16) + if !ok { + panic(fmt.Sprintf("unsupported type conversion from %T to TypeInt16", tt)) + } + return NullableInt16Value(tt) case types.Uint16: - return NullableUint16Value(v.(*uint16)) + tt, ok := v.(*uint16) + if !ok { + panic(fmt.Sprintf("unsupported type conversion from %T to TypeUint16", tt)) + } + return NullableUint16Value(tt) case types.Int32: - return NullableInt32Value(v.(*int32)) + tt, ok := v.(*int32) + if !ok { + panic(fmt.Sprintf("unsupported type conversion from %T to TypeInt32", tt)) + } + return NullableInt32Value(tt) case types.Uint32: - return NullableUint32Value(v.(*uint32)) + tt, ok := v.(*uint32) + if !ok { + panic(fmt.Sprintf("unsupported type conversion from %T to Uint32", tt)) + } + return NullableUint32Value(tt) case types.Int64: - return NullableInt64Value(v.(*int64)) + tt, ok := v.(*int64) + if !ok { + panic(fmt.Sprintf("unsupported type conversion from %T to TypeInt64", tt)) + } + return NullableInt64Value(tt) case types.Uint64: - return NullableUint64Value(v.(*uint64)) + tt, ok := v.(*uint64) + if !ok { + panic(fmt.Sprintf("unsupported type conversion from %T to TypeUint64", tt)) + } + return NullableUint64Value(tt) case types.Float: - return NullableFloatValue(v.(*float32)) + tt, ok := v.(*float32) + if !ok { + panic(fmt.Sprintf("unsupported type conversion from %T to TypeFloat", tt)) + } + return NullableFloatValue(tt) case types.Double: - return NullableDoubleValue(v.(*float64)) + tt, ok := v.(*float64) + if !ok { + panic(fmt.Sprintf("unsupported type conversion from %T to TypeDouble", tt)) + } + return NullableDoubleValue(tt) case types.Date: switch tt := v.(type) { case *uint32: diff --git a/internal/value/time.go b/internal/value/time.go index 37f7aa374..f8d836098 100644 --- a/internal/value/time.go +++ b/internal/value/time.go @@ -12,9 +12,11 @@ import ( const InfiniteDuration = time.Duration(math.MaxInt64) const ( - secondsPerMinute uint64 = 60 - secondsPerHour = 60 * secondsPerMinute - secondsPerDay = 24 * secondsPerHour + secondsPerMinute uint64 = 60 + secondsPerHour = 60 * secondsPerMinute + secondsPerDay = 24 * secondsPerHour + microsecondsPerSecond = 1e6 + nanosecondsPerMicrosecond = 1000 ) // Date format layouts described in time.Format and time.ANSIC docs. @@ -52,15 +54,15 @@ func DatetimeToTime(n uint32) time.Time { // TimestampToTime converts given microseconds to time.Time // Up to 586524-01-19 08:01:49.000551615 +0000 UTC. func TimestampToTime(n uint64) time.Time { - sec := n / 1e6 - nsec := (n - (sec * 1e6)) * 1000 + sec := n / microsecondsPerSecond + nsec := (n - (sec * microsecondsPerSecond)) * nanosecondsPerMicrosecond return time.Unix(int64(sec), int64(nsec)) } func TzDateToTime(s string) (t time.Time, err error) { ss := strings.Split(s, ",") - if len(ss) != 2 { + if len(ss) != 2 { //nolint:gomnd return t, xerrors.WithStackTrace(fmt.Errorf("not found timezone location in '%s'", s)) } location, err := time.LoadLocation(ss[1]) @@ -77,7 +79,7 @@ func TzDateToTime(s string) (t time.Time, err error) { func TzDatetimeToTime(s string) (t time.Time, err error) { ss := strings.Split(s, ",") - if len(ss) != 2 { + if len(ss) != 2 { //nolint:gomnd return t, xerrors.WithStackTrace(fmt.Errorf("not found timezone location in '%s'", s)) } location, err := time.LoadLocation(ss[1]) @@ -94,7 +96,7 @@ func TzDatetimeToTime(s string) (t time.Time, err error) { func TzTimestampToTime(s string) (t time.Time, err error) { ss := strings.Split(s, ",") - if len(ss) != 2 { + if len(ss) != 2 { //nolint:gomnd return t, xerrors.WithStackTrace(fmt.Errorf("not found timezone location in '%s'", s)) } location, err := time.LoadLocation(ss[1]) diff --git a/internal/value/value.go b/internal/value/value.go index 77ff3d856..d424af060 100644 --- a/internal/value/value.go +++ b/internal/value/value.go @@ -19,6 +19,11 @@ import ( "github.com/ydb-platform/ydb-go-sdk/v3/internal/xstring" ) +const ( + decimalPrecision uint32 = 22 + decimalScale uint32 = 9 +) + type Value interface { Type() types.Type Yql() string @@ -183,7 +188,11 @@ func fromYDB(t *Ydb.Type, v *Ydb.Value) (Value, error) { return DecimalValue(BigEndianUint128(v.GetHigh_128(), v.GetLow_128()), ttt.Precision(), ttt.Scale()), nil case types.Optional: - t = t.GetType().(*Ydb.Type_OptionalType).OptionalType.GetItem() + tt, ok := t.GetType().(*Ydb.Type_OptionalType) + if !ok { + panic(fmt.Sprintf("unsupported type conversion from %T to *Ydb.Type_OptionalType", tt)) + } + t = tt.OptionalType.GetItem() if nestedValue, ok := v.GetValue().(*Ydb.Value_NestedValue); ok { return OptionalValue(FromYDB(t, nestedValue.NestedValue)), nil } @@ -260,10 +269,15 @@ func fromYDB(t *Ydb.Type, v *Ydb.Value) (Value, error) { a := allocator.New() defer a.Free() + tt, ok := v.GetValue().(*Ydb.Value_NestedValue) + if !ok { + panic(fmt.Sprintf("unsupported type conversion from %T to *Ydb.Value_NestedValue", tt)) + } + return VariantValueStruct( FromYDB( ttt.Struct.Field(int(v.GetVariantIndex())).T.ToYDB(a), - v.GetValue().(*Ydb.Value_NestedValue).NestedValue, + tt.NestedValue, ), ttt.Struct.Field(int(v.GetVariantIndex())).Name, ttt.Struct, @@ -273,10 +287,15 @@ func fromYDB(t *Ydb.Type, v *Ydb.Value) (Value, error) { a := allocator.New() defer a.Free() + tt, ok := v.GetValue().(*Ydb.Value_NestedValue) + if !ok { + panic(fmt.Sprintf("unsupported type conversion from %T to *Ydb.Value_NestedValue", tt)) + } + return VariantValueTuple( FromYDB( ttt.Tuple.ItemType(int(v.GetVariantIndex())).ToYDB(a), - v.GetValue().(*Ydb.Value_NestedValue).NestedValue, + tt.NestedValue, ), v.GetVariantIndex(), ttt.Tuple, @@ -1037,6 +1056,7 @@ func (v intervalValue) Yql() string { d = -d } buffer.WriteByte('P') + //nolint:gomnd if days := d / time.Hour / 24; days > 0 { d -= days * time.Hour * 24 //nolint:durationcheck buffer.WriteString(strconv.FormatInt(int64(days), 10)) @@ -1267,7 +1287,7 @@ func (v pgValue) Yql() string { //nolint:godox // TODO: call special function for unknown oids // https://github.com/ydb-platform/ydb/issues/2706 - return fmt.Sprintf("PgUnknown(%q)", v.val) + return fmt.Sprintf(`PgConst("%v", PgType(%v))`, v.val, v.t.OID) } type setValue struct { @@ -2420,7 +2440,7 @@ func ZeroValue(t types.Type) Value { return values }()...) case *types.Decimal: - return DecimalValue([16]byte{}, 22, 9) + return DecimalValue([16]byte{}, decimalPrecision, decimalScale) default: panic(fmt.Sprintf("type '%T' have not a zero value", t)) diff --git a/internal/value/value_test.go b/internal/value/value_test.go index e1dd3bde6..f7adfc7e9 100644 --- a/internal/value/value_test.go +++ b/internal/value/value_test.go @@ -502,7 +502,7 @@ func TestValueYql(t *testing.T) { }, { value: PgValue(pg.OIDUnknown, "123"), - literal: `PgUnknown("123")`, + literal: `PgConst("123", PgType(705))`, }, } { t.Run(strconv.Itoa(i)+"."+tt.literal, func(t *testing.T) { diff --git a/internal/version/parse.go b/internal/version/parse.go index e901ed031..a57a14718 100644 --- a/internal/version/parse.go +++ b/internal/version/parse.go @@ -68,6 +68,7 @@ func Gte(lhs, rhs string) bool { return true } +//nolint:gomnd func parse(s string) (v version, err error) { ss := strings.SplitN(s, "-", 2) if len(ss) == 2 { diff --git a/internal/version/version.go b/internal/version/version.go index 2483581c6..90e60ca06 100644 --- a/internal/version/version.go +++ b/internal/version/version.go @@ -2,8 +2,8 @@ package version const ( Major = "3" - Minor = "58" - Patch = "2" + Minor = "65" + Patch = "0" Prefix = "ydb-go-sdk" ) diff --git a/internal/xcontext/cancels_quard.go b/internal/xcontext/cancels_quard.go new file mode 100644 index 000000000..7fd344696 --- /dev/null +++ b/internal/xcontext/cancels_quard.go @@ -0,0 +1,38 @@ +package xcontext + +import ( + "context" + "sync" +) + +type CancelsGuard struct { + mu sync.Mutex + cancels map[*context.CancelFunc]struct{} +} + +func NewCancelsGuard() *CancelsGuard { + return &CancelsGuard{ + cancels: make(map[*context.CancelFunc]struct{}), + } +} + +func (g *CancelsGuard) Remember(cancel *context.CancelFunc) { + g.mu.Lock() + defer g.mu.Unlock() + g.cancels[cancel] = struct{}{} +} + +func (g *CancelsGuard) Forget(cancel *context.CancelFunc) { + g.mu.Lock() + defer g.mu.Unlock() + delete(g.cancels, cancel) +} + +func (g *CancelsGuard) Cancel() { + g.mu.Lock() + defer g.mu.Unlock() + for cancel := range g.cancels { + (*cancel)() + } + g.cancels = make(map[*context.CancelFunc]struct{}) +} diff --git a/internal/xcontext/cancels_quard_test.go b/internal/xcontext/cancels_quard_test.go new file mode 100644 index 000000000..98c2faf2c --- /dev/null +++ b/internal/xcontext/cancels_quard_test.go @@ -0,0 +1,24 @@ +package xcontext + +import ( + "testing" + + "github.com/stretchr/testify/require" + "golang.org/x/net/context" +) + +func TestCancelsGuard(t *testing.T) { + g := NewCancelsGuard() + ctx, cancel1 := context.WithCancel(context.Background()) + g.Remember(&cancel1) + require.Len(t, g.cancels, 1) + g.Forget(&cancel1) + require.Empty(t, g.cancels, 0) + cancel2 := context.CancelFunc(func() { + cancel1() + }) + g.Remember(&cancel2) + require.Len(t, g.cancels, 1) + g.Cancel() + require.Error(t, ctx.Err()) +} diff --git a/internal/xcontext/local_dc.go b/internal/xcontext/local_dc.go index 01f0602fd..c8de6cef7 100644 --- a/internal/xcontext/local_dc.go +++ b/internal/xcontext/local_dc.go @@ -1,6 +1,9 @@ package xcontext -import "context" +import ( + "context" + "fmt" +) type localDcKey struct{} @@ -10,7 +13,11 @@ func WithLocalDC(ctx context.Context, dc string) context.Context { func ExtractLocalDC(ctx context.Context) string { if val := ctx.Value(localDcKey{}); val != nil { - return val.(string) + res, ok := val.(string) + if !ok { + panic(fmt.Sprintf("unsupported type conversion from %T to string", res)) + } + return res } return "" diff --git a/internal/xcontext/without_deadline.go b/internal/xcontext/without_deadline.go index c0d92045c..2e80a284f 100644 --- a/internal/xcontext/without_deadline.go +++ b/internal/xcontext/without_deadline.go @@ -13,7 +13,7 @@ func (valueOnlyContext) Done() <-chan struct{} { return nil } func (valueOnlyContext) Err() error { return nil } -// WithoutDeadline helps to clear derived deadline from deadline -func WithoutDeadline(ctx context.Context) context.Context { +// ValueOnly helps to clear parent context from deadlines/cancels +func ValueOnly(ctx context.Context) context.Context { return valueOnlyContext{ctx} } diff --git a/internal/xerrors/check.go b/internal/xerrors/check.go index a6afc4c0c..2c6895f6b 100644 --- a/internal/xerrors/check.go +++ b/internal/xerrors/check.go @@ -9,7 +9,7 @@ func Check(err error) ( code int64, errType Type, backoffType backoff.Type, - deleteSession bool, + invalidObject bool, ) { if err == nil { return -1, @@ -19,7 +19,7 @@ func Check(err error) ( } var e Error if As(err, &e) { - return int64(e.Code()), e.Type(), e.BackoffType(), e.MustDeleteSession() + return int64(e.Code()), e.Type(), e.BackoffType(), e.IsRetryObjectValid() } return -1, @@ -28,16 +28,15 @@ func Check(err error) ( false } -func MustDeleteSession(err error) bool { +func IsRetryObjectValid(err error) bool { if err == nil { - return false + return true } var e Error - if As(err, &e) { - return e.MustDeleteSession() + return !e.IsRetryObjectValid() } - return false + return true } diff --git a/internal/xerrors/join_test.go b/internal/xerrors/join_test.go index a9ff7e297..762b38cfe 100644 --- a/internal/xerrors/join_test.go +++ b/internal/xerrors/join_test.go @@ -2,6 +2,7 @@ package xerrors import ( "context" + "fmt" "testing" "github.com/stretchr/testify/assert" @@ -50,7 +51,10 @@ func TestUnwrapJoined(t *testing.T) { var joined error = Join(err1, err2) - unwrappable := joined.(interface{ Unwrap() []error }) //nolint:errorlint + unwrappable, ok := joined.(interface{ Unwrap() []error }) //nolint:errorlint + if !ok { + panic(fmt.Sprintf("unsupported type conversion from %T to string", unwrappable)) + } inners := unwrappable.Unwrap() assert.Contains(t, inners, err1) assert.Contains(t, inners, err2) diff --git a/internal/xerrors/operation.go b/internal/xerrors/operation.go index ded028e9f..7020e4f42 100644 --- a/internal/xerrors/operation.go +++ b/internal/xerrors/operation.go @@ -192,7 +192,7 @@ func (e *operationError) BackoffType() backoff.Type { } } -func (e *operationError) MustDeleteSession() bool { +func (e *operationError) IsRetryObjectValid() bool { switch e.code { case Ydb.StatusIds_BAD_SESSION, diff --git a/internal/xerrors/retryable.go b/internal/xerrors/retryable.go index 03b8a894d..1c3141e8f 100644 --- a/internal/xerrors/retryable.go +++ b/internal/xerrors/retryable.go @@ -7,11 +7,11 @@ import ( ) type retryableError struct { - name string - err error - backoffType backoff.Type - mustDeleteSession bool - code int32 + name string + err error + backoffType backoff.Type + isRetryObjectValid bool + code int32 } func (e *retryableError) Code() int32 { @@ -30,8 +30,8 @@ func (e *retryableError) BackoffType() backoff.Type { return e.backoffType } -func (e *retryableError) MustDeleteSession() bool { - return e.mustDeleteSession +func (e *retryableError) IsRetryObjectValid() bool { + return e.isRetryObjectValid } func (e *retryableError) Error() string { @@ -56,9 +56,9 @@ func WithName(name string) RetryableErrorOption { } } -func WithDeleteSession() RetryableErrorOption { +func InvalidObject() RetryableErrorOption { return func(e *retryableError) { - e.mustDeleteSession = true + e.isRetryObjectValid = true } } @@ -66,14 +66,15 @@ func Retryable(err error, opts ...RetryableErrorOption) error { var ( e Error re = &retryableError{ - err: err, - name: "CUSTOM", - code: -1, + err: err, + name: "CUSTOM", + code: -1, + isRetryObjectValid: true, } ) if As(err, &e) { re.backoffType = e.BackoffType() - re.mustDeleteSession = e.MustDeleteSession() + re.isRetryObjectValid = e.IsRetryObjectValid() re.code = e.Code() re.name = e.Name() } diff --git a/internal/xerrors/transport.go b/internal/xerrors/transport.go index ad2bf00a6..b66f7735c 100644 --- a/internal/xerrors/transport.go +++ b/internal/xerrors/transport.go @@ -98,7 +98,7 @@ func (e *transportError) BackoffType() backoff.Type { } } -func (e *transportError) MustDeleteSession() bool { +func (e *transportError) IsRetryObjectValid() bool { switch e.status.Code() { case grpcCodes.ResourceExhausted, @@ -141,7 +141,7 @@ func Transport(err error, opts ...teOpt) error { } var te *transportError if errors.As(err, &te) { - return err + return te } if s, ok := grpcStatus.FromError(err); ok { te = &transportError{ diff --git a/internal/xerrors/xerrors.go b/internal/xerrors/xerrors.go index a15bf85f8..ce6a20260 100644 --- a/internal/xerrors/xerrors.go +++ b/internal/xerrors/xerrors.go @@ -19,7 +19,7 @@ type Error interface { Name() string Type() Type BackoffType() backoff.Type - MustDeleteSession() bool + IsRetryObjectValid() bool } func IsTimeoutError(err error) bool { @@ -99,3 +99,7 @@ func Is(err error, targets ...error) bool { return false } + +func IsContextError(err error) bool { + return Is(err, context.Canceled, context.DeadlineExceeded) +} diff --git a/internal/xresolver/xresolver.go b/internal/xresolver/xresolver.go index 5d78a7a55..7eb2046a1 100644 --- a/internal/xresolver/xresolver.go +++ b/internal/xresolver/xresolver.go @@ -33,7 +33,7 @@ func (c *clientConn) Endpoint() string { func (c *clientConn) UpdateState(state resolver.State) (err error) { onDone := trace.DriverOnResolve(c.trace, - stack.FunctionID(""), + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/xresolver.(*clientConn).UpdateState"), c.Endpoint(), func() (addrs []string) { for i := range state.Addresses { addrs = append(addrs, state.Addresses[i].Addr) diff --git a/internal/xsql/badconn/badconn.go b/internal/xsql/badconn/badconn.go index 0b91649ec..54f5deb8c 100644 --- a/internal/xsql/badconn/badconn.go +++ b/internal/xsql/badconn/badconn.go @@ -43,7 +43,7 @@ func Map(err error) error { return nil case xerrors.Is(err, io.EOF): return io.EOF - case xerrors.MustDeleteSession(err): + case !xerrors.IsRetryObjectValid(err): return Error{err: err} default: return err diff --git a/internal/xsql/badconn/badconn_test.go b/internal/xsql/badconn/badconn_test.go index 1d252c051..7366542e2 100644 --- a/internal/xsql/badconn/badconn_test.go +++ b/internal/xsql/badconn/badconn_test.go @@ -45,12 +45,12 @@ var errsToCheck = []error{ xerrors.Retryable( xerrors.Transport(grpcStatus.Error(grpcCodes.Unavailable, "")), xerrors.WithBackoff(backoff.TypeFast), - xerrors.WithDeleteSession(), + xerrors.InvalidObject(), ), xerrors.Retryable( grpcStatus.Error(grpcCodes.Unavailable, ""), xerrors.WithBackoff(backoff.TypeFast), - xerrors.WithDeleteSession(), + xerrors.InvalidObject(), ), xerrors.Transport(grpcStatus.Error(grpcCodes.DataLoss, "")), xerrors.Transport(grpcStatus.Error(grpcCodes.Unauthenticated, "")), @@ -112,7 +112,7 @@ var errsToCheck = []error{ xerrors.WithStatusCode(Ydb.StatusIds_SESSION_BUSY), ), xerrors.Retryable(errors.New("retryable error")), - xerrors.Retryable(errors.New("retryable error"), xerrors.WithDeleteSession()), + xerrors.Retryable(errors.New("retryable error"), xerrors.InvalidObject()), io.EOF, xerrors.WithStackTrace(io.EOF), } @@ -122,7 +122,7 @@ func Test_badConnError_Is(t *testing.T) { t.Run(err.Error(), func(t *testing.T) { err = Map(err) require.Equal(t, - xerrors.MustDeleteSession(err), + !xerrors.IsRetryObjectValid(err), xerrors.Is(err, driver.ErrBadConn), ) }) diff --git a/internal/xsql/conn.go b/internal/xsql/conn.go index a07a6400e..c5d670517 100644 --- a/internal/xsql/conn.go +++ b/internal/xsql/conn.go @@ -155,7 +155,7 @@ func (c *conn) PrepareContext(ctx context.Context, query string) (_ driver.Stmt, return c.currentTx.PrepareContext(ctx, query) } onDone := trace.DatabaseSQLOnConnPrepare(c.trace, &ctx, - stack.FunctionID(""), + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/xsql.(*conn).PrepareContext"), query, ) defer func() { @@ -198,7 +198,7 @@ func (c *conn) execContext(ctx context.Context, query string, args []driver.Name m = queryModeFromContext(ctx, c.defaultQueryMode) onDone = trace.DatabaseSQLOnConnExec( c.trace, &ctx, - stack.FunctionID(""), + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/xsql.(*conn).execContext"), query, m.String(), xcontext.IsIdempotent(ctx), c.sinceLastUsage(), ) ) @@ -308,7 +308,7 @@ func (c *conn) queryContext(ctx context.Context, query string, args []driver.Nam m = queryModeFromContext(ctx, c.defaultQueryMode) onDone = trace.DatabaseSQLOnConnQuery( c.trace, &ctx, - stack.FunctionID(""), + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/xsql.(*conn).queryContext"), query, m.String(), xcontext.IsIdempotent(ctx), c.sinceLastUsage(), ) ) @@ -395,7 +395,9 @@ func (c *conn) queryContext(ctx context.Context, query string, args []driver.Nam } func (c *conn) Ping(ctx context.Context) (finalErr error) { - onDone := trace.DatabaseSQLOnConnPing(c.trace, &ctx, stack.FunctionID("")) + onDone := trace.DatabaseSQLOnConnPing(c.trace, &ctx, + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/xsql.(*conn).Ping"), + ) defer func() { onDone(finalErr) }() @@ -414,7 +416,7 @@ func (c *conn) Close() (finalErr error) { c.connector.detach(c) onDone := trace.DatabaseSQLOnConnClose( c.trace, &c.openConnCtx, - stack.FunctionID(""), + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/xsql.(*conn).Close"), ) defer func() { onDone(finalErr) @@ -422,7 +424,7 @@ func (c *conn) Close() (finalErr error) { if c.currentTx != nil { _ = c.currentTx.Rollback() } - err := c.session.Close(xcontext.WithoutDeadline(c.openConnCtx)) + err := c.session.Close(xcontext.ValueOnly(c.openConnCtx)) if err != nil { return badconn.Map(xerrors.WithStackTrace(err)) } @@ -457,7 +459,9 @@ func (c *conn) ID() string { func (c *conn) BeginTx(ctx context.Context, txOptions driver.TxOptions) (_ driver.Tx, finalErr error) { var tx currentTx - onDone := trace.DatabaseSQLOnConnBegin(c.trace, &ctx, stack.FunctionID("")) + onDone := trace.DatabaseSQLOnConnBegin(c.trace, &ctx, + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/xsql.(*conn).BeginTx"), + ) defer func() { onDone(tx, finalErr) }() @@ -468,7 +472,7 @@ func (c *conn) BeginTx(ctx context.Context, txOptions driver.TxOptions) (_ drive &ConnAlreadyHaveTxError{ currentTx: c.currentTx.ID(), }, - xerrors.WithDeleteSession(), + xerrors.InvalidObject(), ), ) } @@ -481,7 +485,7 @@ func (c *conn) BeginTx(ctx context.Context, txOptions driver.TxOptions) (_ drive xerrors.WithStackTrace( xerrors.Retryable( fmt.Errorf("wrong query mode: %s", m.String()), - xerrors.WithDeleteSession(), + xerrors.InvalidObject(), xerrors.WithName("WRONG_QUERY_MODE"), ), ), @@ -506,7 +510,7 @@ func (c *conn) Version(_ context.Context) (_ string, _ error) { func (c *conn) IsTableExists(ctx context.Context, tableName string) (tableExists bool, finalErr error) { tableName = c.normalizePath(tableName) onDone := trace.DatabaseSQLOnConnIsTableExists(c.trace, &ctx, - stack.FunctionID(""), + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/xsql.(*conn).IsTableExists"), tableName, ) defer func() { diff --git a/internal/xsql/connector.go b/internal/xsql/connector.go index a00246969..8009f66d1 100644 --- a/internal/xsql/connector.go +++ b/internal/xsql/connector.go @@ -262,10 +262,14 @@ func (c *Connector) idleCloser() (idleStopper func()) { ctx, idleStopper = xcontext.WithCancel(context.Background()) go func() { for { + idleThresholdTimer := c.clock.NewTimer(c.idleThreshold) select { case <-ctx.Done(): + idleThresholdTimer.Stop() + return - case <-c.clock.After(c.idleThreshold): + case <-idleThresholdTimer.Chan(): + idleThresholdTimer.Stop() // no really need, stop for common style only c.connsMtx.RLock() conns := make([]*conn, 0, len(c.conns)) for cc := range c.conns { @@ -313,7 +317,7 @@ func (c *Connector) Connect(ctx context.Context) (_ driver.Conn, err error) { var ( onDone = trace.DatabaseSQLOnConnectorConnect( c.trace, &ctx, - stack.FunctionID(""), + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/xsql.(*Connector).Connect"), ) session table.ClosableSession ) diff --git a/internal/xsql/errors.go b/internal/xsql/errors.go index 922f7fd4c..8dbcc7ce9 100644 --- a/internal/xsql/errors.go +++ b/internal/xsql/errors.go @@ -10,8 +10,8 @@ import ( var ( ErrUnsupported = driver.ErrSkip errDeprecated = driver.ErrSkip - errConnClosedEarly = xerrors.Retryable(errors.New("conn closed early"), xerrors.WithDeleteSession()) - errNotReadyConn = xerrors.Retryable(errors.New("conn not ready"), xerrors.WithDeleteSession()) + errConnClosedEarly = xerrors.Retryable(errors.New("conn closed early"), xerrors.InvalidObject()) + errNotReadyConn = xerrors.Retryable(errors.New("conn not ready"), xerrors.InvalidObject()) ) type ConnAlreadyHaveTxError struct { diff --git a/internal/xsql/rows.go b/internal/xsql/rows.go index a9bb1572e..0451b8378 100644 --- a/internal/xsql/rows.go +++ b/internal/xsql/rows.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "database/sql/driver" + "fmt" "io" "strings" "sync" @@ -128,7 +129,11 @@ func (r *rows) Next(dst []driver.Value) error { return badconn.Map(xerrors.WithStackTrace(err)) } for i := range values { - dst[i] = values[i].(*valuer).Value() + val, ok := values[i].(*valuer) + if !ok { + panic(fmt.Sprintf("unsupported type conversion from %T to *valuer", val)) + } + dst[i] = val.Value() } if err = r.result.Err(); err != nil { return badconn.Map(xerrors.WithStackTrace(err)) diff --git a/internal/xsql/stmt.go b/internal/xsql/stmt.go index 82d80a916..75b571acd 100644 --- a/internal/xsql/stmt.go +++ b/internal/xsql/stmt.go @@ -31,7 +31,7 @@ var ( func (s *stmt) QueryContext(ctx context.Context, args []driver.NamedValue) (_ driver.Rows, finalErr error) { onDone := trace.DatabaseSQLOnStmtQuery(s.trace, &ctx, - stack.FunctionID(""), + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/xsql.(*stmt).QueryContext"), s.stmtCtx, s.query, ) defer func() { @@ -50,7 +50,7 @@ func (s *stmt) QueryContext(ctx context.Context, args []driver.NamedValue) (_ dr func (s *stmt) ExecContext(ctx context.Context, args []driver.NamedValue) (_ driver.Result, finalErr error) { onDone := trace.DatabaseSQLOnStmtExec(s.trace, &ctx, - stack.FunctionID(""), + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/xsql.(*stmt).ExecContext"), s.stmtCtx, s.query, ) defer func() { @@ -72,7 +72,9 @@ func (s *stmt) NumInput() int { } func (s *stmt) Close() (finalErr error) { - onDone := trace.DatabaseSQLOnStmtClose(s.trace, &s.stmtCtx, stack.FunctionID("")) + onDone := trace.DatabaseSQLOnStmtClose(s.trace, &s.stmtCtx, + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/xsql.(*stmt).Close"), + ) defer func() { onDone(finalErr) }() diff --git a/internal/xsql/tx.go b/internal/xsql/tx.go index 24afe529a..d67813437 100644 --- a/internal/xsql/tx.go +++ b/internal/xsql/tx.go @@ -74,7 +74,7 @@ func (tx *tx) checkTxState() error { func (tx *tx) Commit() (finalErr error) { onDone := trace.DatabaseSQLOnTxCommit(tx.conn.trace, &tx.txCtx, - stack.FunctionID(""), + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/xsql.(*tx).Commit"), tx, ) defer func() { @@ -96,7 +96,7 @@ func (tx *tx) Commit() (finalErr error) { func (tx *tx) Rollback() (finalErr error) { onDone := trace.DatabaseSQLOnTxRollback(tx.conn.trace, &tx.txCtx, - stack.FunctionID(""), + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/xsql.(*tx).Rollback"), tx, ) defer func() { @@ -120,7 +120,7 @@ func (tx *tx) QueryContext(ctx context.Context, query string, args []driver.Name _ driver.Rows, finalErr error, ) { onDone := trace.DatabaseSQLOnTxQuery(tx.conn.trace, &ctx, - stack.FunctionID(""), + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/xsql.(*tx).QueryContext"), tx.txCtx, tx, query, ) defer func() { @@ -132,7 +132,7 @@ func (tx *tx) QueryContext(ctx context.Context, query string, args []driver.Name xerrors.WithStackTrace( xerrors.Retryable( fmt.Errorf("wrong query mode: %s", m.String()), - xerrors.WithDeleteSession(), + xerrors.InvalidObject(), xerrors.WithName("WRONG_QUERY_MODE"), ), ), @@ -162,7 +162,7 @@ func (tx *tx) ExecContext(ctx context.Context, query string, args []driver.Named _ driver.Result, finalErr error, ) { onDone := trace.DatabaseSQLOnTxExec(tx.conn.trace, &ctx, - stack.FunctionID(""), + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/xsql.(*tx).ExecContext"), tx.txCtx, tx, query, ) defer func() { @@ -174,7 +174,7 @@ func (tx *tx) ExecContext(ctx context.Context, query string, args []driver.Named xerrors.WithStackTrace( xerrors.Retryable( fmt.Errorf("wrong query mode: %s", m.String()), - xerrors.WithDeleteSession(), + xerrors.InvalidObject(), xerrors.WithName("WRONG_QUERY_MODE"), ), ), @@ -196,7 +196,7 @@ func (tx *tx) ExecContext(ctx context.Context, query string, args []driver.Named func (tx *tx) PrepareContext(ctx context.Context, query string) (_ driver.Stmt, finalErr error) { onDone := trace.DatabaseSQLOnTxPrepare(tx.conn.trace, &ctx, - stack.FunctionID(""), + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/xsql.(*tx).PrepareContext"), &tx.txCtx, tx, query, ) defer func() { diff --git a/internal/xsql/tx_fake.go b/internal/xsql/tx_fake.go index 7909bf521..459aba718 100644 --- a/internal/xsql/tx_fake.go +++ b/internal/xsql/tx_fake.go @@ -19,7 +19,7 @@ type txFake struct { func (tx *txFake) PrepareContext(ctx context.Context, query string) (_ driver.Stmt, finalErr error) { onDone := trace.DatabaseSQLOnTxPrepare(tx.conn.trace, &ctx, - stack.FunctionID(""), + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/xsql.(*txFake).PrepareContext"), &tx.beginCtx, tx, query, ) defer func() { @@ -58,7 +58,7 @@ func (tx *txFake) ID() string { func (tx *txFake) Commit() (err error) { onDone := trace.DatabaseSQLOnTxCommit(tx.conn.trace, &tx.ctx, - stack.FunctionID(""), + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/xsql.(*txFake).Commit"), tx, ) defer func() { @@ -76,7 +76,7 @@ func (tx *txFake) Commit() (err error) { func (tx *txFake) Rollback() (err error) { onDone := trace.DatabaseSQLOnTxRollback(tx.conn.trace, &tx.ctx, - stack.FunctionID(""), + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/xsql.(*txFake).Rollback"), tx, ) defer func() { @@ -97,7 +97,7 @@ func (tx *txFake) QueryContext(ctx context.Context, query string, args []driver. ) { onDone := trace.DatabaseSQLOnTxQuery( tx.conn.trace, &ctx, - stack.FunctionID(""), + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/xsql.(*txFake).QueryContext"), tx.ctx, tx, query, ) defer func() { @@ -116,7 +116,7 @@ func (tx *txFake) ExecContext(ctx context.Context, query string, args []driver.N ) { onDone := trace.DatabaseSQLOnTxExec( tx.conn.trace, &ctx, - stack.FunctionID(""), + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/xsql.(*txFake).ExecContext"), tx.ctx, tx, query, ) defer func() { diff --git a/internal/xstring/buffer.go b/internal/xstring/buffer.go index acd4dbc01..da47a7756 100644 --- a/internal/xstring/buffer.go +++ b/internal/xstring/buffer.go @@ -2,6 +2,7 @@ package xstring import ( "bytes" + "fmt" "sync" ) @@ -19,5 +20,10 @@ func (b *buffer) Free() { } func Buffer() *buffer { - return buffersPool.Get().(*buffer) + val, ok := buffersPool.Get().(*buffer) + if !ok { + panic(fmt.Sprintf("unsupported type conversion from %T to *buffer", val)) + } + + return val } diff --git a/internal/xsync/last_usage_guard.go b/internal/xsync/last_usage_guard.go new file mode 100644 index 000000000..f11d5ba49 --- /dev/null +++ b/internal/xsync/last_usage_guard.go @@ -0,0 +1,62 @@ +package xsync + +import ( + "sync" + "sync/atomic" + "time" + + "github.com/jonboulle/clockwork" +) + +type ( + LastUsage interface { + Get() time.Time + Start() (stop func()) + } + lastUsage struct { + locks atomic.Int64 + t atomic.Pointer[time.Time] + clock clockwork.Clock + } + lastUsageOption func(g *lastUsage) +) + +func WithClock(clock clockwork.Clock) lastUsageOption { + return func(g *lastUsage) { + g.clock = clock + } +} + +func NewLastUsage(opts ...lastUsageOption) *lastUsage { + lastUsage := &lastUsage{ + clock: clockwork.NewRealClock(), + } + for _, opt := range opts { + opt(lastUsage) + } + + now := lastUsage.clock.Now() + + lastUsage.t.Store(&now) + + return lastUsage +} + +func (g *lastUsage) Get() time.Time { + if g.locks.Load() == 0 { + return *g.t.Load() + } + + return g.clock.Now() +} + +func (g *lastUsage) Start() (stop func()) { + g.locks.Add(1) + + return sync.OnceFunc(func() { + if g.locks.Add(-1) == 0 { + now := g.clock.Now() + g.t.Store(&now) + } + }) +} diff --git a/internal/xsync/last_usage_guard_test.go b/internal/xsync/last_usage_guard_test.go new file mode 100644 index 000000000..52b390a8d --- /dev/null +++ b/internal/xsync/last_usage_guard_test.go @@ -0,0 +1,98 @@ +package xsync + +import ( + "testing" + "time" + + "github.com/jonboulle/clockwork" + "github.com/stretchr/testify/require" +) + +func TestLastUsageGuardLock(t *testing.T) { + t.Run("NowFromLocked", func(t *testing.T) { + start := time.Unix(0, 0) + clock := clockwork.NewFakeClockAt(start) + lu := &lastUsage{ + clock: clock, + } + lu.t.Store(&start) + t1 := lu.Get() + require.Equal(t, start, t1) + f := lu.Start() + clock.Advance(time.Hour) + t2 := lu.Get() + require.Equal(t, start.Add(time.Hour), t2) + clock.Advance(time.Hour) + f() + t3 := lu.Get() + require.Equal(t, start.Add(2*time.Hour), t3) + clock.Advance(time.Hour) + t4 := lu.Get() + require.Equal(t, start.Add(2*time.Hour), t4) + }) + t.Run("UpdateAfterLastUnlock", func(t *testing.T) { + start := time.Unix(0, 0) + clock := clockwork.NewFakeClockAt(start) + lu := &lastUsage{ + clock: clock, + } + lu.t.Store(&start) + t1 := lu.Get() + require.Equal(t, start, t1) + f1 := lu.Start() + clock.Advance(time.Hour) + t2 := lu.Get() + require.Equal(t, start.Add(time.Hour), t2) + f2 := lu.Start() + clock.Advance(time.Hour) + f1() + f3 := lu.Start() + clock.Advance(time.Hour) + t3 := lu.Get() + require.Equal(t, start.Add(3*time.Hour), t3) + clock.Advance(time.Hour) + t4 := lu.Get() + require.Equal(t, start.Add(4*time.Hour), t4) + f3() + t5 := lu.Get() + require.Equal(t, start.Add(4*time.Hour), t5) + clock.Advance(time.Hour) + t6 := lu.Get() + require.Equal(t, start.Add(5*time.Hour), t6) + clock.Advance(time.Hour) + f2() + t7 := lu.Get() + require.Equal(t, start.Add(6*time.Hour), t7) + clock.Advance(time.Hour) + f2() + t8 := lu.Get() + require.Equal(t, start.Add(6*time.Hour), t8) + }) + t.Run("DeferRelease", func(t *testing.T) { + start := time.Unix(0, 0) + clock := clockwork.NewFakeClockAt(start) + lu := &lastUsage{ + clock: clock, + } + lu.t.Store(&start) + + func() { + t1 := lu.Get() + require.Equal(t, start, t1) + clock.Advance(time.Hour) + t2 := lu.Get() + require.Equal(t, start, t2) + clock.Advance(time.Hour) + defer lu.Start()() + t3 := lu.Get() + require.Equal(t, start.Add(2*time.Hour), t3) + clock.Advance(time.Hour) + t4 := lu.Get() + require.Equal(t, start.Add(3*time.Hour), t4) + clock.Advance(time.Hour) + }() + clock.Advance(time.Hour) + t5 := lu.Get() + require.Equal(t, start.Add(4*time.Hour), t5) + }) +} diff --git a/internal/xsync/locked.go b/internal/xsync/locked.go deleted file mode 100644 index 24639f439..000000000 --- a/internal/xsync/locked.go +++ /dev/null @@ -1,30 +0,0 @@ -package xsync - -import "sync" - -type Locked[T any] struct { - v T - mu sync.RWMutex -} - -func NewLocked[T any](v T) *Locked[T] { - return &Locked[T]{ - v: v, - } -} - -func (l *Locked[T]) Get() T { - l.mu.RLock() - defer l.mu.RUnlock() - - return l.v -} - -func (l *Locked[T]) Change(f func(prev T) T) T { - l.mu.Lock() - defer l.mu.Unlock() - - l.v = f(l.v) - - return l.v -} diff --git a/internal/xsync/locked_test.go b/internal/xsync/locked_test.go deleted file mode 100644 index b0e5aa6e9..000000000 --- a/internal/xsync/locked_test.go +++ /dev/null @@ -1,16 +0,0 @@ -package xsync - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestLocked(t *testing.T) { - l := NewLocked[int](1) - require.Equal(t, 1, l.Get()) - require.Equal(t, 2, l.Change(func(v int) int { - return 2 - })) - require.Equal(t, 2, l.Get()) -} diff --git a/internal/xtest/call_method_test.go b/internal/xtest/call_method_test.go index fe721761d..3f922ddbf 100644 --- a/internal/xtest/call_method_test.go +++ b/internal/xtest/call_method_test.go @@ -2,6 +2,7 @@ package xtest import ( "bytes" + "fmt" "testing" "github.com/stretchr/testify/require" @@ -11,7 +12,10 @@ func TestCallMethod(t *testing.T) { object := bytes.NewBuffer(nil) result := CallMethod(object, "WriteString", "Hello world!") - n := result[0].(int) + n, ok := result[0].(int) + if !ok { + panic(fmt.Sprintf("unsupported type conversion from %T to int", n)) + } err := result[1] require.Equal(t, 12, n) diff --git a/internal/xtest/to_json.go b/internal/xtest/to_json.go new file mode 100644 index 000000000..add66425c --- /dev/null +++ b/internal/xtest/to_json.go @@ -0,0 +1,9 @@ +package xtest + +import "encoding/json" + +func ToJSON(v interface{}) string { + b, _ := json.MarshalIndent(v, "", "\t") //nolint:errchkjson + + return string(b) +} diff --git a/internal/xtest/to_json_test.go b/internal/xtest/to_json_test.go new file mode 100644 index 000000000..861640068 --- /dev/null +++ b/internal/xtest/to_json_test.go @@ -0,0 +1,58 @@ +package xtest + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestToJSON(t *testing.T) { + for _, tt := range []struct { + name string + v interface{} + s string + }{ + { + name: CurrentFileLine(), + v: int64(123), + s: "123", + }, + { + name: CurrentFileLine(), + v: struct { + A string + B int64 + C bool + }{ + A: "123", + B: 123, + C: true, + }, + s: "{\n\t\"A\": \"123\",\n\t\"B\": 123,\n\t\"C\": true\n}", + }, + { + name: CurrentFileLine(), + v: map[string]struct { + A string + B int64 + C bool + }{ + "abc": { + A: "123", + B: 123, + C: true, + }, + "def": { + A: "456", + B: 456, + C: false, + }, + }, + s: "{\n\t\"abc\": {\n\t\t\"A\": \"123\",\n\t\t\"B\": 123,\n\t\t\"C\": true\n\t},\n\t\"def\": {\n\t\t\"A\": \"456\",\n\t\t\"B\": 456,\n\t\t\"C\": false\n\t}\n}", //nolint:lll + }, + } { + t.Run(tt.name, func(t *testing.T) { + require.Equal(t, tt.s, ToJSON(tt.v)) + }) + } +} diff --git a/log/coordination.go b/log/coordination.go index 1485118de..865b8cd9c 100644 --- a/log/coordination.go +++ b/log/coordination.go @@ -1,10 +1,348 @@ package log import ( + "context" + "strconv" + "time" + "github.com/ydb-platform/ydb-go-sdk/v3/trace" ) // Coordination makes trace.Coordination with logging events from details func Coordination(l Logger, d trace.Detailer, opts ...Option) (t trace.Coordination) { - return t + return internalCoordination(wrapLogger(l, opts...), d) +} + +func internalCoordination( + l *wrapper, //nolint:interfacer + d trace.Detailer, +) trace.Coordination { + return trace.Coordination{ + OnNew: func(info trace.CoordinationNewStartInfo) func(trace.CoordinationNewDoneInfo) { + if d.Details()&trace.CoordinationEvents == 0 { + return nil + } + ctx := with(*info.Context, TRACE, "ydb", "coordination", "new") + l.Log(ctx, "start") + start := time.Now() + + return func(info trace.CoordinationNewDoneInfo) { + l.Log(WithLevel(ctx, INFO), "done", + latencyField(start), + versionField(), + ) + } + }, + OnCreateNode: func(info trace.CoordinationCreateNodeStartInfo) func(trace.CoordinationCreateNodeDoneInfo) { + if d.Details()&trace.CoordinationEvents == 0 { + return nil + } + ctx := with(*info.Context, TRACE, "ydb", "coordination", "node", "create") + l.Log(ctx, "start", + String("path", info.Path), + ) + start := time.Now() + + return func(info trace.CoordinationCreateNodeDoneInfo) { + if info.Error == nil { + l.Log(WithLevel(ctx, INFO), "done", + latencyField(start), + ) + } else { + l.Log(WithLevel(ctx, ERROR), "fail", + latencyField(start), + versionField(), + ) + } + } + }, + OnAlterNode: func(info trace.CoordinationAlterNodeStartInfo) func(trace.CoordinationAlterNodeDoneInfo) { + if d.Details()&trace.CoordinationEvents == 0 { + return nil + } + ctx := with(*info.Context, TRACE, "ydb", "coordination", "node", "alter") + l.Log(ctx, "start", + String("path", info.Path), + ) + start := time.Now() + + return func(info trace.CoordinationAlterNodeDoneInfo) { + if info.Error == nil { + l.Log(WithLevel(ctx, INFO), "done", + latencyField(start), + ) + } else { + l.Log(WithLevel(ctx, ERROR), "fail", + latencyField(start), + versionField(), + ) + } + } + }, + OnDropNode: func(info trace.CoordinationDropNodeStartInfo) func(trace.CoordinationDropNodeDoneInfo) { + if d.Details()&trace.CoordinationEvents == 0 { + return nil + } + ctx := with(*info.Context, TRACE, "ydb", "coordination", "node", "drop") + l.Log(ctx, "start", + String("path", info.Path), + ) + start := time.Now() + + return func(info trace.CoordinationDropNodeDoneInfo) { + if info.Error == nil { + l.Log(WithLevel(ctx, INFO), "done", + latencyField(start), + ) + } else { + l.Log(WithLevel(ctx, ERROR), "fail", + latencyField(start), + versionField(), + ) + } + } + }, + OnDescribeNode: func(info trace.CoordinationDescribeNodeStartInfo) func(trace.CoordinationDescribeNodeDoneInfo) { + if d.Details()&trace.CoordinationEvents == 0 { + return nil + } + ctx := with(*info.Context, TRACE, "ydb", "coordination", "node", "describe") + l.Log(ctx, "start", + String("path", info.Path), + ) + start := time.Now() + + return func(info trace.CoordinationDescribeNodeDoneInfo) { + if info.Error == nil { + l.Log(WithLevel(ctx, INFO), "done", + latencyField(start), + ) + } else { + l.Log(WithLevel(ctx, ERROR), "fail", + latencyField(start), + versionField(), + ) + } + } + }, + OnSession: func(info trace.CoordinationSessionStartInfo) func(trace.CoordinationSessionDoneInfo) { + if d.Details()&trace.CoordinationEvents == 0 { + return nil + } + ctx := with(*info.Context, TRACE, "ydb", "coordination", "node", "describe") + l.Log(ctx, "start") + start := time.Now() + + return func(info trace.CoordinationSessionDoneInfo) { + if info.Error == nil { + l.Log(WithLevel(ctx, INFO), "done", + latencyField(start), + ) + } else { + l.Log(WithLevel(ctx, ERROR), "fail", + latencyField(start), + versionField(), + ) + } + } + }, + OnClose: func(info trace.CoordinationCloseStartInfo) func(trace.CoordinationCloseDoneInfo) { + if d.Details()&trace.CoordinationEvents == 0 { + return nil + } + ctx := with(*info.Context, TRACE, "ydb", "coordination", "close") + l.Log(ctx, "start") + start := time.Now() + + return func(info trace.CoordinationCloseDoneInfo) { + if info.Error == nil { + l.Log(WithLevel(ctx, INFO), "done", + latencyField(start), + ) + } else { + l.Log(WithLevel(ctx, ERROR), "fail", + latencyField(start), + versionField(), + ) + } + } + }, + OnStreamNew: func( + info trace.CoordinationStreamNewStartInfo, + ) func( + info trace.CoordinationStreamNewDoneInfo, + ) { + if d.Details()&trace.CoordinationEvents == 0 { + return nil + } + ctx := with(context.Background(), TRACE, "ydb", "coordination", "session", "stream", "new") + l.Log(ctx, "stream") + start := time.Now() + + return func(info trace.CoordinationStreamNewDoneInfo) { + l.Log(ctx, "done", + latencyField(start), + Error(info.Error), + versionField(), + ) + } + }, + OnSessionStarted: func(info trace.CoordinationSessionStartedInfo) { + if d.Details()&trace.CoordinationEvents == 0 { + return + } + ctx := with(context.Background(), TRACE, "ydb", "coordination", "session", "started") + l.Log(ctx, "", + String("sessionID", strconv.FormatUint(info.SessionID, 10)), + String("expectedSessionID", strconv.FormatUint(info.SessionID, 10)), + ) + }, + OnSessionStartTimeout: func(info trace.CoordinationSessionStartTimeoutInfo) { + if d.Details()&trace.CoordinationEvents == 0 { + return + } + ctx := with(context.Background(), TRACE, "ydb", "coordination", "session", "start", "timeout") + l.Log(ctx, "", + Stringer("timeout", info.Timeout), + ) + }, + OnSessionKeepAliveTimeout: func(info trace.CoordinationSessionKeepAliveTimeoutInfo) { + if d.Details()&trace.CoordinationEvents == 0 { + return + } + ctx := with(context.Background(), TRACE, "ydb", "coordination", "session", "keepAlive", "timeout") + l.Log(ctx, "", + Stringer("timeout", info.Timeout), + Stringer("lastGoodResponseTime", info.LastGoodResponseTime), + ) + }, + OnSessionStopped: func(info trace.CoordinationSessionStoppedInfo) { + if d.Details()&trace.CoordinationEvents == 0 { + return + } + ctx := with(context.Background(), TRACE, "ydb", "coordination", "session", "stopped") + l.Log(ctx, "", + String("sessionID", strconv.FormatUint(info.SessionID, 10)), + String("expectedSessionID", strconv.FormatUint(info.SessionID, 10)), + ) + }, + OnSessionStopTimeout: func(info trace.CoordinationSessionStopTimeoutInfo) { + if d.Details()&trace.CoordinationEvents == 0 { + return + } + ctx := with(context.Background(), TRACE, "ydb", "coordination", "session", "stop", "timeout") + l.Log(ctx, "", + Stringer("timeout", info.Timeout), + ) + }, + OnSessionClientTimeout: func(info trace.CoordinationSessionClientTimeoutInfo) { + if d.Details()&trace.CoordinationEvents == 0 { + return + } + ctx := with(context.Background(), TRACE, "ydb", "coordination", "session", "client", "timeout") + l.Log(ctx, "", + Stringer("timeout", info.Timeout), + Stringer("lastGoodResponseTime", info.LastGoodResponseTime), + ) + }, + OnSessionServerExpire: func(info trace.CoordinationSessionServerExpireInfo) { + if d.Details()&trace.CoordinationEvents == 0 { + return + } + ctx := with(context.Background(), TRACE, "ydb", "coordination", "session", "server", "expire") + l.Log(ctx, "", + Stringer("failure", info.Failure), + ) + }, + OnSessionServerError: func(info trace.CoordinationSessionServerErrorInfo) { + if d.Details()&trace.CoordinationEvents == 0 { + return + } + ctx := with(context.Background(), TRACE, "ydb", "coordination", "session", "server", "error") + l.Log(ctx, "", + Stringer("failure", info.Failure), + ) + }, + OnSessionReceive: func( + info trace.CoordinationSessionReceiveStartInfo, + ) func( + info trace.CoordinationSessionReceiveDoneInfo, + ) { + if d.Details()&trace.CoordinationEvents == 0 { + return nil + } + ctx := with(context.Background(), TRACE, "ydb", "coordination", "session", "receive") + l.Log(ctx, "receive") + start := time.Now() + + return func(info trace.CoordinationSessionReceiveDoneInfo) { + l.Log(ctx, "done", + latencyField(start), + Error(info.Error), + Stringer("response", info.Response), + versionField(), + ) + } + }, + OnSessionReceiveUnexpected: func(info trace.CoordinationSessionReceiveUnexpectedInfo) { + if d.Details()&trace.CoordinationEvents == 0 { + return + } + ctx := with(context.Background(), TRACE, "ydb", "coordination", "session", "receive", "unexpected") + l.Log(ctx, "", + Stringer("response", info.Response), + ) + }, + OnSessionStop: func(info trace.CoordinationSessionStopInfo) { + if d.Details()&trace.CoordinationEvents == 0 { + return + } + ctx := with(context.Background(), TRACE, "ydb", "coordination", "session", "stop") + l.Log(ctx, "", + String("sessionID", strconv.FormatUint(info.SessionID, 10)), + ) + }, + OnSessionStart: func( + info trace.CoordinationSessionStartStartInfo, + ) func( + info trace.CoordinationSessionStartDoneInfo, + ) { + if d.Details()&trace.CoordinationEvents == 0 { + return nil + } + ctx := with(context.Background(), TRACE, "ydb", "coordination", "session", "start") + l.Log(ctx, "start") + start := time.Now() + + return func(info trace.CoordinationSessionStartDoneInfo) { + l.Log(ctx, "done", + latencyField(start), + Error(info.Error), + versionField(), + ) + } + }, + OnSessionSend: func( + info trace.CoordinationSessionSendStartInfo, + ) func( + info trace.CoordinationSessionSendDoneInfo, + ) { + if d.Details()&trace.CoordinationEvents == 0 { + return nil + } + ctx := with(context.Background(), TRACE, "ydb", "coordination", "session", "send") + l.Log(ctx, "start", + Stringer("request", info.Request), + ) + start := time.Now() + + return func(info trace.CoordinationSessionSendDoneInfo) { + l.Log(ctx, "done", + latencyField(start), + Error(info.Error), + versionField(), + ) + } + }, + } } diff --git a/log/field.go b/log/field.go index af6f65598..a44990826 100644 --- a/log/field.go +++ b/log/field.go @@ -90,7 +90,11 @@ func (f Field) StringsValue() []string { return nil } - return f.vany.([]string) + res, ok := f.vany.([]string) + if !ok { + panic(fmt.Sprintf("unsupported type conversion from %T to []string", res)) + } + return res } // ErrorValue is a value getter for fields with ErrorType type @@ -100,7 +104,11 @@ func (f Field) ErrorValue() error { return nil } - return f.vany.(error) + res, ok := f.vany.(error) + if !ok { + panic(fmt.Sprintf("unsupported type conversion from %T to error", res)) + } + return res } // AnyValue is a value getter for fields with AnyType type @@ -136,7 +144,11 @@ func (f Field) Stringer() fmt.Stringer { return nil } - return f.vany.(fmt.Stringer) + res, ok := f.vany.(fmt.Stringer) + if !ok { + panic(fmt.Sprintf("unsupported type conversion from %T to fmt.Stringer", res)) + } + return res } // Panics on type mismatch @@ -161,10 +173,15 @@ func (f Field) String() string { case StringsType: return fmt.Sprintf("%v", f.StringsValue()) case ErrorType: - if f.vany == nil || f.vany.(error) == nil { + if f.vany == nil { return "" } + val, ok := f.vany.(error) + if !ok { + panic(fmt.Sprintf("unsupported type conversion from %T to error", val)) + } + return f.ErrorValue().Error() case AnyType: if f.vany == nil { diff --git a/log/logger_test.go b/log/logger_test.go index f7b12a71c..38e9afb75 100644 --- a/log/logger_test.go +++ b/log/logger_test.go @@ -9,9 +9,9 @@ import ( ) func TestColoring(t *testing.T) { - zeroClock := clockwork.NewFakeClock() - fullDuration := zeroClock.Now().Sub(time.Date(1984, 4, 4, 0, 0, 0, 0, time.UTC)) - zeroClock.Advance(-fullDuration) // set zero time + zeroClock := clockwork.NewFakeClockAt( + time.Date(1984, 4, 4, 0, 0, 0, 0, time.UTC), + ) for _, tt := range []struct { l *defaultLogger msg string diff --git a/log/retry.go b/log/retry.go index 8b52911b7..df772bdb3 100644 --- a/log/retry.go +++ b/log/retry.go @@ -14,13 +14,7 @@ func Retry(l Logger, d trace.Detailer, opts ...Option) (t trace.Retry) { } func internalRetry(l Logger, d trace.Detailer) (t trace.Retry) { - t.OnRetry = func( - info trace.RetryLoopStartInfo, - ) func( - trace.RetryLoopIntermediateInfo, - ) func( - trace.RetryLoopDoneInfo, - ) { + t.OnRetry = func(info trace.RetryLoopStartInfo) func(trace.RetryLoopDoneInfo) { if d.Details()&trace.RetryEvents == 0 { return nil } @@ -33,11 +27,12 @@ func internalRetry(l Logger, d trace.Detailer) (t trace.Retry) { ) start := time.Now() - return func(info trace.RetryLoopIntermediateInfo) func(trace.RetryLoopDoneInfo) { + return func(info trace.RetryLoopDoneInfo) { if info.Error == nil { - l.Log(ctx, "attempt done", + l.Log(ctx, "done", String("label", label), latencyField(start), + Int("attempts", info.Attempts), ) } else { lvl := ERROR @@ -45,42 +40,17 @@ func internalRetry(l Logger, d trace.Detailer) (t trace.Retry) { lvl = DEBUG } m := retry.Check(info.Error) - l.Log(WithLevel(ctx, lvl), "attempt failed", + l.Log(WithLevel(ctx, lvl), "failed", Error(info.Error), String("label", label), latencyField(start), + Int("attempts", info.Attempts), Bool("retryable", m.MustRetry(idempotent)), Int64("code", m.StatusCode()), - Bool("deleteSession", m.MustDeleteSession()), + Bool("deleteSession", m.IsRetryObjectValid()), versionField(), ) } - - return func(info trace.RetryLoopDoneInfo) { - if info.Error == nil { - l.Log(ctx, "done", - String("label", label), - latencyField(start), - Int("attempts", info.Attempts), - ) - } else { - lvl := ERROR - if !xerrors.IsYdb(info.Error) { - lvl = DEBUG - } - m := retry.Check(info.Error) - l.Log(WithLevel(ctx, lvl), "failed", - Error(info.Error), - String("label", label), - latencyField(start), - Int("attempts", info.Attempts), - Bool("retryable", m.MustRetry(idempotent)), - Int64("code", m.StatusCode()), - Bool("deleteSession", m.MustDeleteSession()), - versionField(), - ) - } - } } } diff --git a/log/sql.go b/log/sql.go index 3a71f3cb7..5ef2a56f3 100644 --- a/log/sql.go +++ b/log/sql.go @@ -165,7 +165,7 @@ func internalDatabaseSQL(l *wrapper, d trace.Detailer) (t trace.DatabaseSQL) { String("query", query), Bool("retryable", m.MustRetry(idempotent)), Int64("code", m.StatusCode()), - Bool("deleteSession", m.MustDeleteSession()), + Bool("deleteSession", m.IsRetryObjectValid()), Error(info.Error), latencyField(start), versionField(), @@ -200,7 +200,7 @@ func internalDatabaseSQL(l *wrapper, d trace.Detailer) (t trace.DatabaseSQL) { String("query", query), Bool("retryable", m.MustRetry(idempotent)), Int64("code", m.StatusCode()), - Bool("deleteSession", m.MustDeleteSession()), + Bool("deleteSession", m.IsRetryObjectValid()), Error(info.Error), latencyField(start), versionField(), diff --git a/log/table.go b/log/table.go index 5afa72bee..c12dd30ad 100644 --- a/log/table.go +++ b/log/table.go @@ -18,8 +18,6 @@ func Table(l Logger, d trace.Detailer, opts ...Option) (t trace.Table) { func internalTable(l *wrapper, d trace.Detailer) (t trace.Table) { t.OnDo = func( info trace.TableDoStartInfo, - ) func( - info trace.TableDoIntermediateInfo, ) func( trace.TableDoDoneInfo, ) { @@ -35,64 +33,36 @@ func internalTable(l *wrapper, d trace.Detailer) (t trace.Table) { ) start := time.Now() - return func(info trace.TableDoIntermediateInfo) func(trace.TableDoDoneInfo) { + return func(info trace.TableDoDoneInfo) { if info.Error == nil { l.Log(ctx, "done", latencyField(start), Bool("idempotent", idempotent), String("label", label), + Int("attempts", info.Attempts), ) } else { - lvl := WARN + lvl := ERROR if !xerrors.IsYdb(info.Error) { lvl = DEBUG } m := retry.Check(info.Error) - l.Log(WithLevel(ctx, lvl), "failed", + l.Log(WithLevel(ctx, lvl), "done", latencyField(start), Bool("idempotent", idempotent), String("label", label), + Int("attempts", info.Attempts), Error(info.Error), Bool("retryable", m.MustRetry(idempotent)), Int64("code", m.StatusCode()), - Bool("deleteSession", m.MustDeleteSession()), + Bool("deleteSession", m.IsRetryObjectValid()), versionField(), ) } - - return func(info trace.TableDoDoneInfo) { - if info.Error == nil { - l.Log(ctx, "done", - latencyField(start), - Bool("idempotent", idempotent), - String("label", label), - Int("attempts", info.Attempts), - ) - } else { - lvl := ERROR - if !xerrors.IsYdb(info.Error) { - lvl = DEBUG - } - m := retry.Check(info.Error) - l.Log(WithLevel(ctx, lvl), "done", - latencyField(start), - Bool("idempotent", idempotent), - String("label", label), - Int("attempts", info.Attempts), - Error(info.Error), - Bool("retryable", m.MustRetry(idempotent)), - Int64("code", m.StatusCode()), - Bool("deleteSession", m.MustDeleteSession()), - versionField(), - ) - } - } } } t.OnDoTx = func( info trace.TableDoTxStartInfo, - ) func( - info trace.TableDoTxIntermediateInfo, ) func( trace.TableDoTxDoneInfo, ) { @@ -108,15 +78,16 @@ func internalTable(l *wrapper, d trace.Detailer) (t trace.Table) { ) start := time.Now() - return func(info trace.TableDoTxIntermediateInfo) func(trace.TableDoTxDoneInfo) { + return func(info trace.TableDoTxDoneInfo) { if info.Error == nil { l.Log(ctx, "done", latencyField(start), Bool("idempotent", idempotent), String("label", label), + Int("attempts", info.Attempts), ) } else { - lvl := ERROR + lvl := WARN if !xerrors.IsYdb(info.Error) { lvl = DEBUG } @@ -125,47 +96,18 @@ func internalTable(l *wrapper, d trace.Detailer) (t trace.Table) { latencyField(start), Bool("idempotent", idempotent), String("label", label), + Int("attempts", info.Attempts), Error(info.Error), Bool("retryable", m.MustRetry(idempotent)), Int64("code", m.StatusCode()), - Bool("deleteSession", m.MustDeleteSession()), + Bool("deleteSession", m.IsRetryObjectValid()), versionField(), ) } - - return func(info trace.TableDoTxDoneInfo) { - if info.Error == nil { - l.Log(ctx, "done", - latencyField(start), - Bool("idempotent", idempotent), - String("label", label), - Int("attempts", info.Attempts), - ) - } else { - lvl := WARN - if !xerrors.IsYdb(info.Error) { - lvl = DEBUG - } - m := retry.Check(info.Error) - l.Log(WithLevel(ctx, lvl), "done", - latencyField(start), - Bool("idempotent", idempotent), - String("label", label), - Int("attempts", info.Attempts), - Error(info.Error), - Bool("retryable", m.MustRetry(idempotent)), - Int64("code", m.StatusCode()), - Bool("deleteSession", m.MustDeleteSession()), - versionField(), - ) - } - } } } t.OnCreateSession = func( info trace.TableCreateSessionStartInfo, - ) func( - info trace.TableCreateSessionIntermediateInfo, ) func( trace.TableCreateSessionDoneInfo, ) { @@ -176,36 +118,22 @@ func internalTable(l *wrapper, d trace.Detailer) (t trace.Table) { l.Log(ctx, "start") start := time.Now() - return func(info trace.TableCreateSessionIntermediateInfo) func(trace.TableCreateSessionDoneInfo) { + return func(info trace.TableCreateSessionDoneInfo) { if info.Error == nil { - l.Log(ctx, "intermediate", + l.Log(ctx, "done", latencyField(start), + Int("attempts", info.Attempts), + String("session_id", info.Session.ID()), + String("session_status", info.Session.Status()), ) } else { - l.Log(WithLevel(ctx, ERROR), "intermediate", + l.Log(WithLevel(ctx, ERROR), "failed", latencyField(start), + Int("attempts", info.Attempts), Error(info.Error), versionField(), ) } - - return func(info trace.TableCreateSessionDoneInfo) { - if info.Error == nil { - l.Log(ctx, "done", - latencyField(start), - Int("attempts", info.Attempts), - String("session_id", info.Session.ID()), - String("session_status", info.Session.Status()), - ) - } else { - l.Log(WithLevel(ctx, ERROR), "failed", - latencyField(start), - Int("attempts", info.Attempts), - Error(info.Error), - versionField(), - ) - } - } } } t.OnSessionNew = func(info trace.TableSessionNewStartInfo) func(trace.TableSessionNewDoneInfo) { @@ -396,8 +324,6 @@ func internalTable(l *wrapper, d trace.Detailer) (t trace.Table) { } t.OnSessionQueryStreamExecute = func( info trace.TableSessionQueryStreamExecuteStartInfo, - ) func( - trace.TableSessionQueryStreamExecuteIntermediateInfo, ) func( trace.TableSessionQueryStreamExecuteDoneInfo, ) { @@ -416,50 +342,33 @@ func internalTable(l *wrapper, d trace.Detailer) (t trace.Table) { ) start := time.Now() - return func( - info trace.TableSessionQueryStreamExecuteIntermediateInfo, - ) func( - trace.TableSessionQueryStreamExecuteDoneInfo, - ) { + return func(info trace.TableSessionQueryStreamExecuteDoneInfo) { if info.Error == nil { - l.Log(ctx, "intermediate") + l.Log(ctx, "done", + appendFieldByCondition(l.logQuery, + Stringer("query", query), + Error(info.Error), + String("id", session.ID()), + String("status", session.Status()), + latencyField(start), + )..., + ) } else { - l.Log(WithLevel(ctx, WARN), "failed", - Error(info.Error), - versionField(), + l.Log(WithLevel(ctx, ERROR), "failed", + appendFieldByCondition(l.logQuery, + Stringer("query", query), + Error(info.Error), + String("id", session.ID()), + String("status", session.Status()), + latencyField(start), + versionField(), + )..., ) } - - return func(info trace.TableSessionQueryStreamExecuteDoneInfo) { - if info.Error == nil { - l.Log(ctx, "done", - appendFieldByCondition(l.logQuery, - Stringer("query", query), - Error(info.Error), - String("id", session.ID()), - String("status", session.Status()), - latencyField(start), - )..., - ) - } else { - l.Log(WithLevel(ctx, ERROR), "failed", - appendFieldByCondition(l.logQuery, - Stringer("query", query), - Error(info.Error), - String("id", session.ID()), - String("status", session.Status()), - latencyField(start), - versionField(), - )..., - ) - } - } } } t.OnSessionQueryStreamRead = func( info trace.TableSessionQueryStreamReadStartInfo, - ) func( - intermediateInfo trace.TableSessionQueryStreamReadIntermediateInfo, ) func( trace.TableSessionQueryStreamReadDoneInfo, ) { @@ -474,43 +383,28 @@ func internalTable(l *wrapper, d trace.Detailer) (t trace.Table) { ) start := time.Now() - return func( - info trace.TableSessionQueryStreamReadIntermediateInfo, - ) func( - trace.TableSessionQueryStreamReadDoneInfo, - ) { + return func(info trace.TableSessionQueryStreamReadDoneInfo) { if info.Error == nil { - l.Log(ctx, "intermediate") + l.Log(ctx, "done", + latencyField(start), + String("id", session.ID()), + String("status", session.Status()), + ) } else { - l.Log(WithLevel(ctx, WARN), "failed", + l.Log(WithLevel(ctx, ERROR), "failed", + latencyField(start), + String("id", session.ID()), + String("status", session.Status()), Error(info.Error), versionField(), ) } - - return func(info trace.TableSessionQueryStreamReadDoneInfo) { - if info.Error == nil { - l.Log(ctx, "done", - latencyField(start), - String("id", session.ID()), - String("status", session.Status()), - ) - } else { - l.Log(WithLevel(ctx, ERROR), "failed", - latencyField(start), - String("id", session.ID()), - String("status", session.Status()), - Error(info.Error), - versionField(), - ) - } - } } } - t.OnSessionTransactionBegin = func( - info trace.TableSessionTransactionBeginStartInfo, + t.OnTxBegin = func( + info trace.TableTxBeginStartInfo, ) func( - trace.TableSessionTransactionBeginDoneInfo, + trace.TableTxBeginDoneInfo, ) { if d.Details()&trace.TableSessionTransactionEvents == 0 { return nil @@ -523,7 +417,7 @@ func internalTable(l *wrapper, d trace.Detailer) (t trace.Table) { ) start := time.Now() - return func(info trace.TableSessionTransactionBeginDoneInfo) { + return func(info trace.TableTxBeginDoneInfo) { if info.Error == nil { l.Log(ctx, "done", latencyField(start), @@ -542,11 +436,7 @@ func internalTable(l *wrapper, d trace.Detailer) (t trace.Table) { } } } - t.OnSessionTransactionCommit = func( - info trace.TableSessionTransactionCommitStartInfo, - ) func( - trace.TableSessionTransactionCommitDoneInfo, - ) { + t.OnTxCommit = func(info trace.TableTxCommitStartInfo) func(trace.TableTxCommitDoneInfo) { if d.Details()&trace.TableSessionTransactionEvents == 0 { return nil } @@ -560,7 +450,7 @@ func internalTable(l *wrapper, d trace.Detailer) (t trace.Table) { ) start := time.Now() - return func(info trace.TableSessionTransactionCommitDoneInfo) { + return func(info trace.TableTxCommitDoneInfo) { if info.Error == nil { l.Log(ctx, "done", latencyField(start), @@ -580,10 +470,10 @@ func internalTable(l *wrapper, d trace.Detailer) (t trace.Table) { } } } - t.OnSessionTransactionRollback = func( - info trace.TableSessionTransactionRollbackStartInfo, + t.OnTxRollback = func( + info trace.TableTxRollbackStartInfo, ) func( - trace.TableSessionTransactionRollbackDoneInfo, + trace.TableTxRollbackDoneInfo, ) { if d.Details()&trace.TableSessionTransactionEvents == 0 { return nil @@ -598,7 +488,7 @@ func internalTable(l *wrapper, d trace.Detailer) (t trace.Table) { ) start := time.Now() - return func(info trace.TableSessionTransactionRollbackDoneInfo) { + return func(info trace.TableTxRollbackDoneInfo) { if info.Error == nil { l.Log(ctx, "done", latencyField(start), diff --git a/meta.go b/meta.go index f039f40fe..34f192053 100644 --- a/meta.go +++ b/meta.go @@ -8,14 +8,18 @@ import ( // WithTraceID returns a copy of parent context with traceID // -// Deprecated: use meta.WithTraceID instead +// Deprecated: use meta.WithTraceID instead. +// Will be removed after Oct 2024. +// Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated func WithTraceID(ctx context.Context, traceID string) context.Context { return meta.WithTraceID(ctx, traceID) } // WithRequestType returns a copy of parent context with custom request type // -// Deprecated: use meta.WithRequestType instead +// Deprecated: use meta.WithRequestType instead. +// Will be removed after Oct 2024. +// Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated func WithRequestType(ctx context.Context, requestType string) context.Context { return meta.WithRequestType(ctx, requestType) } diff --git a/meta/context.go b/meta/context.go index 5ea200db6..3f8673003 100644 --- a/meta/context.go +++ b/meta/context.go @@ -15,7 +15,9 @@ func WithTraceID(ctx context.Context, traceID string) context.Context { // WithUserAgent returns a copy of parent context with custom user-agent info // -// Deprecated: use WithApplicationName instead +// Deprecated: use WithApplicationName instead. +// Will be removed after Oct 2024. +// Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated func WithUserAgent(ctx context.Context, _ string) context.Context { return ctx } diff --git a/metrics/config.go b/metrics/config.go index 85902821f..e87f190c7 100644 --- a/metrics/config.go +++ b/metrics/config.go @@ -2,7 +2,9 @@ package metrics import "github.com/ydb-platform/ydb-go-sdk/v3/trace" -// Config is experimental interface for metrics registry config +// Config is interface for metrics registry config +// +// Experimental: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#experimental type Config interface { Registry diff --git a/metrics/driver.go b/metrics/driver.go index 5b2f12fbc..b477f8f3d 100644 --- a/metrics/driver.go +++ b/metrics/driver.go @@ -11,7 +11,7 @@ import ( // driver makes driver with New publishing func driver(config Config) (t trace.Driver) { config = config.WithSystem("driver") - endpoints := config.WithSystem("balancer").GaugeVec("endpoints", "local_dc", "az") + endpoints := config.WithSystem("balancer").GaugeVec("endpoints", "az") balancersDiscoveries := config.WithSystem("balancer").CounterVec("discoveries", "status", "cause") balancerUpdates := config.WithSystem("balancer").CounterVec("updates", "cause") conns := config.GaugeVec("conns", "endpoint", "node_id") @@ -20,8 +20,7 @@ func driver(config Config) (t trace.Driver) { tli := config.CounterVec("transaction_locks_invalidated") type endpointKey struct { - localDC bool - az string + az string } knownEndpoints := make(map[endpointKey]struct{}) @@ -100,8 +99,7 @@ func driver(config Config) (t trace.Driver) { newEndpoints := make(map[endpointKey]int, len(info.Endpoints)) for _, e := range info.Endpoints { e := endpointKey{ - localDC: e.LocalDC(), - az: e.Location(), + az: e.Location(), } newEndpoints[e]++ } @@ -109,16 +107,14 @@ func driver(config Config) (t trace.Driver) { if _, has := newEndpoints[e]; !has { delete(knownEndpoints, e) endpoints.With(map[string]string{ - "local_dc": strconv.FormatBool(e.localDC), - "az": e.az, + "az": e.az, }).Set(0) } } for e, count := range newEndpoints { knownEndpoints[e] = struct{}{} endpoints.With(map[string]string{ - "local_dc": strconv.FormatBool(e.localDC), - "az": e.az, + "az": e.az, }).Set(float64(count)) } } diff --git a/metrics/registry.go b/metrics/registry.go index 738afd886..efadda86f 100644 --- a/metrics/registry.go +++ b/metrics/registry.go @@ -1,6 +1,8 @@ package metrics -// Registry is experimental interface for metrics registry +// Registry is interface for metrics registry +// +// Experimental: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#experimental type Registry interface { // CounterVec returns CounterVec by name, subsystem and labels // If counter by args already created - return counter from cache diff --git a/metrics/retry.go b/metrics/retry.go index 35b0fe9a0..994fd493e 100644 --- a/metrics/retry.go +++ b/metrics/retry.go @@ -11,36 +11,26 @@ func retry(config Config) (t trace.Retry) { errs := config.CounterVec("errors", "status", "retry_label", "final") attempts := config.HistogramVec("attempts", []float64{0, 1, 2, 3, 4, 5, 7, 10}, "retry_label") latency := config.TimerVec("latency", "retry_label") - t.OnRetry = func(info trace.RetryLoopStartInfo) func(trace.RetryLoopIntermediateInfo) func(trace.RetryLoopDoneInfo) { + t.OnRetry = func(info trace.RetryLoopStartInfo) func(trace.RetryLoopDoneInfo) { label := info.Label if label == "" { return nil } start := time.Now() - return func(info trace.RetryLoopIntermediateInfo) func(trace.RetryLoopDoneInfo) { - if info.Error != nil && config.Details()&trace.RetryEvents != 0 { + return func(info trace.RetryLoopDoneInfo) { + if config.Details()&trace.RetryEvents != 0 { + attempts.With(map[string]string{ + "retry_label": label, + }).Record(float64(info.Attempts)) errs.With(map[string]string{ "status": errorBrief(info.Error), "retry_label": label, - "final": "false", + "final": "true", }).Inc() - } - - return func(info trace.RetryLoopDoneInfo) { - if config.Details()&trace.RetryEvents != 0 { - attempts.With(map[string]string{ - "retry_label": label, - }).Record(float64(info.Attempts)) - errs.With(map[string]string{ - "status": errorBrief(info.Error), - "retry_label": label, - "final": "true", - }).Inc() - latency.With(map[string]string{ - "retry_label": label, - }).Record(time.Since(start)) - } + latency.With(map[string]string{ + "retry_label": label, + }).Record(time.Since(start)) } } } diff --git a/metrics/table.go b/metrics/table.go index 80dda3cec..c72cde245 100644 --- a/metrics/table.go +++ b/metrics/table.go @@ -72,7 +72,11 @@ func table(config Config) (t trace.Table) { if !ok { panic(fmt.Sprintf("unknown session '%s'", info.Session.ID())) } - inflightLatency.With(nil).Record(time.Since(start.(time.Time))) + val, ok := start.(time.Time) + if !ok { + panic(fmt.Sprintf("unsupported type conversion from %T to time.Time", val)) + } + inflightLatency.With(nil).Record(time.Since(val)) } return nil diff --git a/options.go b/options.go index 13bcabc70..305f700fc 100644 --- a/options.go +++ b/options.go @@ -54,6 +54,19 @@ func WithAccessTokenCredentials(accessToken string) Option { ) } +// WithOauth2TokenExchangeCredentials adds credentials that exchange token using +// OAuth 2.0 token exchange protocol: +// https://www.rfc-editor.org/rfc/rfc8693 +func WithOauth2TokenExchangeCredentials( + opts ...credentials.Oauth2TokenExchangeCredentialsOption, +) Option { + opts = append(opts, credentials.WithSourceInfo("ydb.WithOauth2TokenExchangeCredentials(opts)")) + + return WithCreateCredentialsFunc(func(context.Context) (credentials.Credentials, error) { + return credentials.NewOauth2TokenExchangeCredentials(opts...) + }) +} + // WithApplicationName add provided application name to all api requests func WithApplicationName(applicationName string) Option { return func(ctx context.Context, c *Driver) error { @@ -65,7 +78,9 @@ func WithApplicationName(applicationName string) Option { // WithUserAgent add provided user agent value to all api requests // -// Deprecated: use WithApplicationName instead +// Deprecated: use WithApplicationName instead. +// Will be removed after Oct 2024. +// Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated func WithUserAgent(userAgent string) Option { return func(ctx context.Context, c *Driver) error { c.options = append(c.options, config.WithApplicationName(userAgent)) @@ -108,10 +123,10 @@ func WithConnectionString(connectionString string) Option { } // WithConnectionTTL defines duration for parking idle connections -// -// Deprecated: background connection parking not available -func WithConnectionTTL(time.Duration) Option { +func WithConnectionTTL(ttl time.Duration) Option { return func(ctx context.Context, c *Driver) error { + c.options = append(c.options, config.WithConnectionTTL(ttl)) + return nil } } @@ -393,15 +408,7 @@ func WithQueryConfigOption(option queryConfig.Option) Option { func WithSessionPoolSizeLimit(sizeLimit int) Option { return func(ctx context.Context, c *Driver) error { c.tableOptions = append(c.tableOptions, tableConfig.WithSizeLimit(sizeLimit)) - - return nil - } -} - -// WithSessionPoolLimit set min size of internal sessions pool in query.Client -func WithSessionPoolLimit(size int) Option { - return func(ctx context.Context, c *Driver) error { - c.queryOptions = append(c.queryOptions, queryConfig.WithPoolLimit(size)) + c.queryOptions = append(c.queryOptions, queryConfig.WithPoolLimit(sizeLimit)) return nil } @@ -440,6 +447,24 @@ func WithSessionPoolDeleteTimeout(deleteTimeout time.Duration) Option { } } +// WithSessionPoolKeepAliveMinSize set minimum sessions should be keeped alive in table.Client +// +// Deprecated: use WithApplicationName instead. +// Will be removed after Oct 2024. +// Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated +func WithSessionPoolKeepAliveMinSize(keepAliveMinSize int) Option { + return func(ctx context.Context, c *Driver) error { return nil } +} + +// WithSessionPoolKeepAliveTimeout set timeout of keep alive requests for session in table.Client +// +// Deprecated: use WithApplicationName instead. +// Will be removed after Oct 2024. +// Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated +func WithSessionPoolKeepAliveTimeout(keepAliveTimeout time.Duration) Option { + return func(ctx context.Context, c *Driver) error { return nil } +} + // WithIgnoreTruncated disables errors on truncated flag func WithIgnoreTruncated() Option { return func(ctx context.Context, c *Driver) error { @@ -542,12 +567,12 @@ func WithTraceScheme(t trace.Scheme, opts ...trace.SchemeComposeOption) Option { } // WithTraceCoordination returns coordination trace option -func WithTraceCoordination(t trace.Coordination, opts ...trace.CoordinationComposeOption) Option { +func WithTraceCoordination(t trace.Coordination, opts ...trace.CoordinationComposeOption) Option { //nolint:gocritic return func(ctx context.Context, c *Driver) error { c.coordinationOptions = append( c.coordinationOptions, coordinationConfig.WithTrace( - t, + &t, append( []trace.CoordinationComposeOption{ trace.WithCoordinationPanicCallback(c.panicCallback), diff --git a/params_builder.go b/params_builder.go index e9e3f5dfb..64768b55c 100644 --- a/params_builder.go +++ b/params_builder.go @@ -4,9 +4,7 @@ import "github.com/ydb-platform/ydb-go-sdk/v3/internal/params" // ParamsBuilder used for create query arguments instead of tons options. // -// # Experimental -// -// Notice: This API is EXPERIMENTAL and may be changed or removed in a later release. +// Experimental: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#experimental func ParamsBuilder() params.Builder { return params.Builder{} } diff --git a/query/session.go b/query/session.go index 9a92449eb..14da2f3d5 100644 --- a/query/session.go +++ b/query/session.go @@ -23,7 +23,7 @@ type ( // Execute executes query. // // Execute used by default: - // - DefaultTxControl + // - DefaultTxControl (NoTx) // - flag WithKeepInCache(true) if params is not empty. Execute(ctx context.Context, query string, opts ...options.ExecuteOption) (tx Transaction, r Result, err error) diff --git a/retry/context.go b/retry/context.go index a3898fc0b..282d9543e 100644 --- a/retry/context.go +++ b/retry/context.go @@ -7,19 +7,28 @@ type ( ) // WithIdempotentOperation returns a copy of parent context with idempotent operation feature -// Deprecated: use retry.WithIdempotent option instead +// +// Deprecated: use retry.WithIdempotent option instead. +// Will be removed after Oct 2024. +// Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated func WithIdempotentOperation(ctx context.Context) context.Context { return context.WithValue(ctx, ctxIsOperationIdempotentKey{}, true) } // WithNonIdempotentOperation returns a copy of parent context with non-idempotent operation feature -// Deprecated: idempotent flag is false by default +// +// Deprecated: idempotent flag is false by default. +// Will be removed after Oct 2024. +// Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated func WithNonIdempotentOperation(ctx context.Context) context.Context { return context.WithValue(ctx, ctxIsOperationIdempotentKey{}, false) } // IsOperationIdempotent returns the flag for retry with no idempotent errors -// Deprecated: context cannot store idempotent value now +// +// Deprecated: context cannot store idempotent value now. +// Will be removed after Oct 2024. +// Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated func IsOperationIdempotent(ctx context.Context) bool { v, ok := ctx.Value(ctxIsOperationIdempotentKey{}).(bool) diff --git a/retry/errors_data_test.go b/retry/errors_data_test.go index a2ffafa91..5253b505f 100644 --- a/retry/errors_data_test.go +++ b/retry/errors_data_test.go @@ -208,7 +208,7 @@ var errsToCheck = []struct { err: xerrors.Retryable( xerrors.Transport(grpcStatus.Error(grpcCodes.Unavailable, "")), xerrors.WithBackoff(backoff.TypeFast), - xerrors.WithDeleteSession(), + xerrors.InvalidObject(), ), backoff: backoff.TypeFast, deleteSession: true, @@ -221,7 +221,7 @@ var errsToCheck = []struct { err: xerrors.Retryable( grpcStatus.Error(grpcCodes.Unavailable, ""), xerrors.WithBackoff(backoff.TypeFast), - xerrors.WithDeleteSession(), + xerrors.InvalidObject(), ), backoff: backoff.TypeFast, deleteSession: true, diff --git a/retry/mode.go b/retry/mode.go index 385cf170b..32736e9c3 100644 --- a/retry/mode.go +++ b/retry/mode.go @@ -7,10 +7,10 @@ import ( // retryMode reports whether operation is able retried and with which properties. type retryMode struct { - code int64 - errType xerrors.Type - backoff backoff.Type - deleteSession bool + code int64 + errType xerrors.Type + backoff backoff.Type + isRetryObjectValid bool } func (m retryMode) MustRetry(isOperationIdempotent bool) bool { @@ -33,4 +33,6 @@ func (m retryMode) MustBackoff() bool { return m.backoff&backoff.TypeAny != 0 } func (m retryMode) BackoffType() backoff.Type { return m.backoff } -func (m retryMode) MustDeleteSession() bool { return m.deleteSession } +func (m retryMode) MustDeleteSession() bool { return !m.isRetryObjectValid } + +func (m retryMode) IsRetryObjectValid() bool { return m.isRetryObjectValid } diff --git a/retry/retry.go b/retry/retry.go index 364e6076b..65e88ce91 100644 --- a/retry/retry.go +++ b/retry/retry.go @@ -227,12 +227,12 @@ func WithPanicCallback(panicCallback func(e interface{})) panicCallbackOption { // // - retry operation returned nil as error // -// Warning: if deadline without deadline or cancellation func Retry will be worked infinite +// Warning: if context without deadline or cancellation func was passed, Retry will work infinitely. // // If you need to retry your op func on some logic errors - you must return RetryableError() from retryOperation func Retry(ctx context.Context, op retryOperation, opts ...Option) (finalErr error) { options := &retryOptions{ - call: stack.FunctionID(""), + call: stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/retry.Retry"), trace: &trace.Retry{}, fastBackoff: backoff.Fast, slowBackoff: backoff.Slow, @@ -245,8 +245,10 @@ func Retry(ctx context.Context, op retryOperation, opts ...Option) (finalErr err if options.idempotent { ctx = xcontext.WithIdempotent(ctx, options.idempotent) } + defer func() { if finalErr != nil && options.stackTrace { + //nolint:gomnd finalErr = xerrors.WithStackTrace(finalErr, xerrors.WithSkipDepth(2), // 1 - exit from defer, 1 - exit from Retry call ) @@ -256,13 +258,13 @@ func Retry(ctx context.Context, op retryOperation, opts ...Option) (finalErr err i int attempts int - code = int64(0) - onIntermediate = trace.RetryOnRetry(options.trace, &ctx, + code = int64(0) + onDone = trace.RetryOnRetry(options.trace, &ctx, options.call, options.label, options.idempotent, xcontext.IsNestedCall(ctx), ) ) defer func() { - onIntermediate(finalErr)(attempts, finalErr) + onDone(attempts, finalErr) }() for { i++ @@ -329,8 +331,6 @@ func Retry(ctx context.Context, op retryOperation, opts ...Option) (finalErr err } code = m.StatusCode() - - onIntermediate(err) } } } @@ -340,9 +340,9 @@ func Check(err error) (m retryMode) { code, errType, backoffType, deleteSession := xerrors.Check(err) return retryMode{ - code: code, - errType: errType, - backoff: backoffType, - deleteSession: deleteSession, + code: code, + errType: errType, + backoff: backoffType, + isRetryObjectValid: deleteSession, } } diff --git a/retry/retry_test.go b/retry/retry_test.go index 71638e6e9..770f2ea65 100644 --- a/retry/retry_test.go +++ b/retry/retry_test.go @@ -45,10 +45,10 @@ func TestRetryModes(t *testing.T) { tt.backoff, ) } - if m.MustDeleteSession() != tt.deleteSession { + if m.IsRetryObjectValid() != tt.deleteSession { t.Errorf( "unexpected delete session status: %v, want: %v", - m.MustDeleteSession(), + m.IsRetryObjectValid(), tt.deleteSession, ) } diff --git a/retry/retryable_error.go b/retry/retryable_error.go index 7501223dc..ca273aac7 100644 --- a/retry/retryable_error.go +++ b/retry/retryable_error.go @@ -20,7 +20,7 @@ func WithBackoff(t backoff.Type) retryableErrorOption { // WithDeleteSession makes retryable error option with delete session flag func WithDeleteSession() retryableErrorOption { - return retryableErrorOption(xerrors.WithDeleteSession()) + return retryableErrorOption(xerrors.InvalidObject()) } // RetryableError makes retryable error from options diff --git a/retry/sql.go b/retry/sql.go index 115b685ad..9b826a1ca 100644 --- a/retry/sql.go +++ b/retry/sql.go @@ -32,7 +32,9 @@ func (retryOptions doRetryOptionsOption) ApplyDoOption(opts *doOptions) { } // WithDoRetryOptions specified retry options -// Deprecated: use implicit options instead +// Deprecated: use explicit options instead. +// Will be removed after Oct 2024. +// Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated func WithDoRetryOptions(opts ...Option) doRetryOptionsOption { return opts } @@ -42,7 +44,7 @@ func Do(ctx context.Context, db *sql.DB, op func(ctx context.Context, cc *sql.Co var ( options = doOptions{ retryOptions: []Option{ - withCaller(stack.FunctionID("")), + withCaller(stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/retry.Do")), }, } attempts = 0 @@ -102,7 +104,9 @@ func (doTxRetryOptions doTxRetryOptionsOption) ApplyDoTxOption(o *doTxOptions) { } // WithDoTxRetryOptions specified retry options -// Deprecated: use implicit options instead +// Deprecated: use explicit options instead. +// Will be removed after Oct 2024. +// Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated func WithDoTxRetryOptions(opts ...Option) doTxRetryOptionsOption { return opts } @@ -129,7 +133,7 @@ func DoTx(ctx context.Context, db *sql.DB, op func(context.Context, *sql.Tx) err var ( options = doTxOptions{ retryOptions: []Option{ - withCaller(stack.FunctionID("")), + withCaller(stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/retry.DoTx")), }, txOptions: &sql.TxOptions{ Isolation: sql.LevelDefault, diff --git a/retry/sql_test.go b/retry/sql_test.go index 935179834..d6552493b 100644 --- a/retry/sql_test.go +++ b/retry/sql_test.go @@ -11,7 +11,6 @@ import ( "github.com/ydb-platform/ydb-go-sdk/v3/internal/stack" "github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors" "github.com/ydb-platform/ydb-go-sdk/v3/internal/xsql/badconn" - "github.com/ydb-platform/ydb-go-sdk/v3/trace" ) type mockConnector struct { @@ -104,7 +103,7 @@ func (m *mockConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.T func (m *mockConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) { m.t.Log(stack.Record(0)) - if xerrors.MustDeleteSession(m.execErr) { + if !xerrors.IsRetryObjectValid(m.execErr) { m.closed = true } @@ -113,7 +112,7 @@ func (m *mockConn) QueryContext(ctx context.Context, query string, args []driver func (m *mockConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) { m.t.Log(stack.Record(0)) - if xerrors.MustDeleteSession(m.execErr) { + if !xerrors.IsRetryObjectValid(m.execErr) { m.closed = true } @@ -215,18 +214,6 @@ func TestDoTx(t *testing.T) { WithIdempotent(bool(idempotentType)), WithFastBackoff(backoff.New(backoff.WithSlotDuration(time.Nanosecond))), WithSlowBackoff(backoff.New(backoff.WithSlotDuration(time.Nanosecond))), - WithTrace(&trace.Retry{ - //nolint:lll - OnRetry: func(info trace.RetryLoopStartInfo) func(trace.RetryLoopIntermediateInfo) func(trace.RetryLoopDoneInfo) { - t.Logf("attempt %d, conn %d, mode: %+v", attempts, m.conns, Check(m.queryErr)) - - return func(info trace.RetryLoopIntermediateInfo) func(trace.RetryLoopDoneInfo) { - t.Logf("attempt %d, conn %d, mode: %+v", attempts, m.conns, Check(m.queryErr)) - - return nil - } - }, - }), ) if tt.canRetry[idempotentType] { if err != nil { diff --git a/sugar/params.go b/sugar/params.go index 612268260..4285969a5 100644 --- a/sugar/params.go +++ b/sugar/params.go @@ -18,7 +18,10 @@ type constraint interface { // GenerateDeclareSection generates DECLARE section text in YQL query by params // -// Deprecated: use testutil.QueryBind(ydb.WithAutoDeclare()) helper +// Deprecated: use testutil.QueryBind(ydb.WithAutoDeclare()) helper. +// In YDB since version 24.1 declare sections not requires. +// Will be removed after Oct 2024. +// Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated func GenerateDeclareSection[T constraint](parameters T) (string, error) { switch v := any(parameters).(type) { case *params.Parameters: @@ -36,7 +39,7 @@ func GenerateDeclareSection[T constraint](parameters T) (string, error) { // ToYdbParam converts // -// Deprecated: use testutil/QueryBind helper +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func ToYdbParam(param sql.NamedArg) (*params.Parameter, error) { params, err := bind.Params(param) if err != nil { diff --git a/sugar/result.go b/sugar/result.go index 33c2339d9..4a2275dcf 100644 --- a/sugar/result.go +++ b/sugar/result.go @@ -73,9 +73,7 @@ func (r *result) Close() error { // Result converts query.Result to iterable result for compatibility with table/result.Result usage // -// # Experimental -// -// Notice: This API is EXPERIMENTAL and may be changed or removed in a later release. +// Experimental: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#experimental func Result(r query.Result) *result { return &result{ r: r, diff --git a/table/options/models.go b/table/options/models.go index c993affbc..eb875e63d 100644 --- a/table/options/models.go +++ b/table/options/models.go @@ -43,15 +43,6 @@ type IndexDescription struct { Type IndexType } -//nolint:unused -func (i IndexDescription) toYDB() *Ydb_Table.TableIndexDescription { - return &Ydb_Table.TableIndexDescription{ - Name: i.Name, - IndexColumns: i.IndexColumns, - Status: i.Status, - } -} - type Description struct { Name string Columns []Column @@ -430,7 +421,8 @@ func (kr KeyRange) String() string { } // Deprecated: use TimeToLiveSettings instead. -// Will be removed after Jan 2022. +// Will be removed after Oct 2024. +// Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated type TTLSettings struct { DateTimeColumn string TTLSeconds uint32 diff --git a/table/options/options.go b/table/options/options.go index db85556a7..922333a17 100644 --- a/table/options/options.go +++ b/table/options/options.go @@ -518,7 +518,9 @@ func WithPartitioningPolicyMode(mode PartitioningMode) PartitioningPolicyOption } } -// Deprecated: use WithUniformPartitions instead +// Deprecated: use WithUniformPartitions instead. +// Will be removed after Oct 2024. +// Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated func WithPartitioningPolicyUniformPartitions(n uint64) PartitioningPolicyOption { return func(p *partitioningPolicy, a *allocator.Allocator) { p.Partitions = &Ydb_Table.PartitioningPolicy_UniformPartitions{ @@ -527,7 +529,9 @@ func WithPartitioningPolicyUniformPartitions(n uint64) PartitioningPolicyOption } } -// Deprecated: use WithExplicitPartitions instead +// Deprecated: use WithExplicitPartitions instead. +// Will be removed after Oct 2024. +// Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated func WithPartitioningPolicyExplicitPartitions(splitPoints ...value.Value) PartitioningPolicyOption { return func(p *partitioningPolicy, a *allocator.Allocator) { values := make([]*Ydb.TypedValue, len(splitPoints)) @@ -806,6 +810,21 @@ func CopyTablesItem(src, dst string, omitIndexes bool) CopyTablesOption { } } +type ( + RenameTablesDesc Ydb_Table.RenameTablesRequest + RenameTablesOption func(desc *RenameTablesDesc) +) + +func RenameTablesItem(src, dst string, replaceDestination bool) RenameTablesOption { + return func(desc *RenameTablesDesc) { + desc.Tables = append(desc.Tables, &Ydb_Table.RenameTableItem{ + SourcePath: src, + DestinationPath: dst, + ReplaceDestination: replaceDestination, + }) + } +} + type ( ExecuteSchemeQueryDesc Ydb_Table.ExecuteSchemeQueryRequest ExecuteSchemeQueryOption func(*ExecuteSchemeQueryDesc) @@ -892,7 +911,8 @@ func WithIgnoreTruncated() ExecuteDataQueryOption { // WithQueryCachePolicyKeepInCache manages keep-in-cache policy // // Deprecated: data queries always executes with enabled keep-in-cache policy. -// Use WithKeepInCache for disabling keep-in-cache policy +// Will be removed after Oct 2024. +// Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated func WithQueryCachePolicyKeepInCache() QueryCachePolicyOption { return withQueryCachePolicyKeepInCache(true) } @@ -905,7 +925,9 @@ func withQueryCachePolicyKeepInCache(keepInCache bool) QueryCachePolicyOption { // WithQueryCachePolicy manages query cache policy // -// Deprecated: use WithKeepInCache for disabling keep-in-cache policy +// Deprecated: use WithKeepInCache for disabling keep-in-cache policy. +// Will be removed after Oct 2024. +// Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated func WithQueryCachePolicy(opts ...QueryCachePolicyOption) ExecuteDataQueryOption { return withQueryCachePolicy(opts...) } diff --git a/table/table.go b/table/table.go index 6d2305a1e..1cb1ed8e0 100644 --- a/table/table.go +++ b/table/table.go @@ -39,8 +39,9 @@ type Client interface { // - context was canceled or deadlined // - session was created // - // Deprecated: don't use CreateSession explicitly. This method only for ORM's compatibility. - // Use Do for queries with session + // Deprecated: not for public usage. Because explicit session often leaked on server-side due to bad client-side usage. + // Will be removed after Oct 2024. + // Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated CreateSession(ctx context.Context, opts ...Option) (s ClosableSession, err error) // Do provide the best effort for execute operation. @@ -111,6 +112,9 @@ type Session interface { opts ...options.AlterTableOption, ) (err error) + // Deprecated: use CopyTables method instead + // Will be removed after Oct 2024. + // Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated CopyTable( ctx context.Context, dst, src string, @@ -122,6 +126,11 @@ type Session interface { opts ...options.CopyTablesOption, ) (err error) + RenameTables( + ctx context.Context, + opts ...options.RenameTablesOption, + ) (err error) + Explain( ctx context.Context, query string, diff --git a/table/types/cast.go b/table/types/cast.go index 71d8476ea..064967678 100644 --- a/table/types/cast.go +++ b/table/types/cast.go @@ -93,7 +93,9 @@ func VariantValue(v Value) (name string, idx uint32, _ Value, _ error) { // DictFields returns dict values from abstract Value // -// Deprecated: use DictValues instead +// Deprecated: use DictValues instead. +// Will be removed after Oct 2024. +// Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated func DictFields(v Value) (map[Value]Value, error) { return DictValues(v) } diff --git a/table/types/types.go b/table/types/types.go index cca6b5c09..530ed77b0 100644 --- a/table/types/types.go +++ b/table/types/types.go @@ -7,6 +7,11 @@ import ( "github.com/ydb-platform/ydb-go-sdk/v3/internal/types" ) +const ( + decimalPrecision uint32 = 22 + decimalScale uint32 = 9 +) + // Type describes YDB data types. type Type = types.Type @@ -76,7 +81,7 @@ func Optional(t Type) Type { return types.NewOptional(t) } -var DefaultDecimal = DecimalType(22, 9) +var DefaultDecimal = DecimalType(decimalPrecision, decimalScale) func DecimalType(precision, scale uint32) Type { return types.NewDecimal(precision, scale) @@ -120,7 +125,9 @@ const ( // WriteTypeStringTo writes ydb type string representation into buffer // -// Deprecated: use types.Type.Yql() instead +// Deprecated: use types.Type.Yql() instead. +// Will be removed after Oct 2024. +// Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated func WriteTypeStringTo(buf *bytes.Buffer, t Type) { //nolint: interfacer buf.WriteString(t.Yql()) } diff --git a/table/types/value.go b/table/types/value.go index 9249c4e3c..8f1ed8cb4 100644 --- a/table/types/value.go +++ b/table/types/value.go @@ -47,7 +47,9 @@ func IntervalValueFromMicroseconds(v int64) Value { return value.IntervalValue(v // IntervalValue makes Value from given microseconds value // -// Deprecated: use IntervalValueFromMicroseconds instead +// Deprecated: use IntervalValueFromMicroseconds instead. +// Will be removed after Oct 2024. +// Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated func IntervalValue(v int64) Value { return value.IntervalValue(v) } // TzDateValue makes TzDate value from string @@ -117,7 +119,9 @@ func TzTimestampValueFromTime(t time.Time) Value { // StringValue returns bytes value // -// Deprecated: use BytesValue instead +// Deprecated: use BytesValue instead. +// Will be removed after Oct 2024. +// Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated func StringValue(v []byte) Value { return value.BytesValue(v) } func BytesValue(v []byte) Value { return value.BytesValue(v) } @@ -345,7 +349,9 @@ func NullableTzTimestampValueFromTime(v *time.Time) Value { // NullableIntervalValue makes Value which maybe nil or valued // -// Deprecated: use NullableIntervalValueFromMicroseconds instead +// Deprecated: use NullableIntervalValueFromMicroseconds instead. +// Will be removed after Oct 2024. +// Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated func NullableIntervalValue(v *int64) Value { return value.NullableIntervalValueFromMicroseconds(v) } @@ -360,7 +366,9 @@ func NullableIntervalValueFromDuration(v *time.Duration) Value { // NullableStringValue // -// Deprecated: use NullableBytesValue instead +// Deprecated: use NullableBytesValue instead. +// Will be removed after Oct 2024. +// Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated func NullableStringValue(v *[]byte) Value { return value.NullableBytesValue(v) } diff --git a/tests/integration/coordination_test.go b/tests/integration/coordination_test.go new file mode 100644 index 000000000..c3551e604 --- /dev/null +++ b/tests/integration/coordination_test.go @@ -0,0 +1,102 @@ +//go:build integration +// +build integration + +package integration + +import ( + "context" + "fmt" + "os" + "testing" + + "github.com/ydb-platform/ydb-go-sdk/v3" + "github.com/ydb-platform/ydb-go-sdk/v3/coordination" + "github.com/ydb-platform/ydb-go-sdk/v3/coordination/options" + "github.com/ydb-platform/ydb-go-sdk/v3/log" + "github.com/ydb-platform/ydb-go-sdk/v3/trace" +) + +//nolint:errcheck +func TestCoordinationSemaphore(t *testing.T) { + ctx := context.TODO() + db, err := ydb.Open(ctx, + os.Getenv("YDB_CONNECTION_STRING"), + ydb.WithAccessTokenCredentials(os.Getenv("YDB_ACCESS_TOKEN_CREDENTIALS")), + ydb.WithLogger( + log.Default(os.Stderr, log.WithMinLevel(log.TRACE)), + trace.MatchDetails(`ydb\.(coordination).*`), + ), + ) + if err != nil { + t.Fatalf("failed to connect: %v", err) + } + defer db.Close(ctx) // cleanup resources + + const nodePath = "/local/coordination/node/test" + + // create node + err = db.Coordination().CreateNode(ctx, nodePath, coordination.NodeConfig{ + Path: "", + SelfCheckPeriodMillis: 1000, + SessionGracePeriodMillis: 1000, + ReadConsistencyMode: coordination.ConsistencyModeStrict, + AttachConsistencyMode: coordination.ConsistencyModeStrict, + RatelimiterCountersMode: coordination.RatelimiterCountersModeDetailed, + }) + if err != nil { + t.Fatalf("failed to create node: %v", err) + } + defer db.Coordination().DropNode(ctx, nodePath) + e, c, err := db.Coordination().DescribeNode(ctx, nodePath) + if err != nil { + t.Fatalf("failed to describe node: %v\n", err) + } + fmt.Printf("node description: %+v\nnode config: %+v\n", e, c) + + s, err := db.Coordination().Session(ctx, nodePath) + if err != nil { + t.Fatalf("failed to create session: %v\n", err) + } + defer s.Close(ctx) + fmt.Printf("session 1 created, id: %d\n", s.SessionID()) + + err = s.CreateSemaphore(ctx, "my-semaphore", 20, options.WithCreateData([]byte{1, 2, 3})) + if err != nil { + t.Fatalf("failed to create semaphore: %v", err) + } + fmt.Printf("semaphore my-semaphore created\n") + + lease, err := s.AcquireSemaphore(ctx, "my-semaphore", 10) + if err != nil { + t.Fatalf("failed to acquire semaphore: %v", err) + } + defer lease.Release() + fmt.Printf("session 1 acquired semaphore 10\n") + + s.Reconnect() + fmt.Printf("session 1 reconnected\n") + + desc, err := s.DescribeSemaphore( + ctx, + "my-semaphore", + options.WithDescribeOwners(true), + options.WithDescribeWaiters(true), + ) + if err != nil { + t.Fatalf("failed to describe semaphore: %v", err) + } + fmt.Printf("session 1 described semaphore %v\n", desc) + + err = lease.Release() + if err != nil { + t.Fatalf("failed to release semaphore: %v", err) + } + fmt.Printf("session 1 released semaphore my-semaphore\n") + + err = s.DeleteSemaphore(ctx, "my-semaphore", options.WithForceDelete(true)) + if err != nil { + t.Fatalf("failed to delete semaphore: %v", err) + } + + fmt.Printf("deleted semaphore my-semaphore\n") +} diff --git a/tests/integration/discovery_test.go b/tests/integration/discovery_test.go index ebd5a054e..511043ebd 100644 --- a/tests/integration/discovery_test.go +++ b/tests/integration/discovery_test.go @@ -46,7 +46,8 @@ func TestDiscovery(t *testing.T) { t.Fatalf("unknown request type: %s", requestTypes[0]) } } - ctx = xtest.Context(t) + parking = make(chan struct{}) + ctx = xtest.Context(t) ) db, err := ydb.Open(ctx, @@ -59,6 +60,7 @@ func TestDiscovery(t *testing.T) { config.WithOperationCancelAfter(time.Second*2), ), ydb.WithBalancer(balancers.SingleConn()), + ydb.WithConnectionTTL(time.Second*1), ydb.WithMinTLSVersion(tls.VersionTLS10), ydb.WithLogger( newLoggerWithMinLevel(t, log.WARN), @@ -92,6 +94,13 @@ func TestDiscovery(t *testing.T) { }), ), ), + ydb.WithTraceDriver(trace.Driver{ + OnConnPark: func(info trace.DriverConnParkStartInfo) func(trace.DriverConnParkDoneInfo) { + return func(info trace.DriverConnParkDoneInfo) { + parking <- struct{}{} + } + }, + }), ) if err != nil { t.Fatal(err) @@ -108,5 +117,17 @@ func TestDiscovery(t *testing.T) { } else { t.Log(endpoints) } + t.Run("wait", func(t *testing.T) { + t.Run("parking", func(t *testing.T) { + <-parking // wait for parking conn + t.Run("re-discover", func(t *testing.T) { + if endpoints, err := db.Discovery().Discover(ctx); err != nil { + t.Fatal(err) + } else { + t.Log(endpoints) + } + }) + }) + }) }) } diff --git a/tests/integration/helpers_test.go b/tests/integration/helpers_test.go index 83fd412f6..73363250d 100644 --- a/tests/integration/helpers_test.go +++ b/tests/integration/helpers_test.go @@ -85,9 +85,9 @@ func (scope *scopeT) Driver(opts ...ydb.Option) *ydb.Driver { token := scope.AuthToken() if token == "" { - scope.Logf("Change empty auth token") + scope.Logf("With empty auth token") } else { - scope.Logf("Change auth token") + scope.Logf("With auth token") } connectionContext, cancel := context.WithTimeout(scope.Ctx, time.Second*10) diff --git a/tests/integration/query_execute_test.go b/tests/integration/query_execute_test.go index b3d6c0339..81687bae8 100644 --- a/tests/integration/query_execute_test.go +++ b/tests/integration/query_execute_test.go @@ -31,6 +31,7 @@ func TestQueryExecute(t *testing.T) { db, err := ydb.Open(ctx, os.Getenv("YDB_CONNECTION_STRING"), ydb.WithAccessTokenCredentials(os.Getenv("YDB_ACCESS_TOKEN_CREDENTIALS")), + ydb.WithSessionPoolSizeLimit(10), ydb.WithTraceQuery( log.Query( log.Default(os.Stdout, @@ -43,6 +44,11 @@ func TestQueryExecute(t *testing.T) { ), ) require.NoError(t, err) + t.Run("Stats", func(t *testing.T) { + s, err := query.Stats(db.Query()) + require.NoError(t, err) + require.EqualValues(t, 10, s.Limit) + }) t.Run("Scan", func(t *testing.T) { var ( p1 string diff --git a/tests/integration/topic_grpc_stopper_helper_test.go b/tests/integration/topic_grpc_stopper_helper_test.go index f08930a83..d9b521bed 100644 --- a/tests/integration/topic_grpc_stopper_helper_test.go +++ b/tests/integration/topic_grpc_stopper_helper_test.go @@ -19,8 +19,8 @@ import ( // // db, err := ydb.Open(context.Background(), connectionString, // ... -// ydb.Change(config.WithGrpcOptions(grpc.WithChainUnaryInterceptor(grpcStopper.UnaryClientInterceptor)), -// ydb.Change(config.WithGrpcOptions(grpc.WithStreamInterceptor(grpcStopper.StreamClientInterceptor)), +// ydb.With(config.WithGrpcOptions(grpc.WithChainUnaryInterceptor(grpcStopper.UnaryClientInterceptor)), +// ydb.With(config.WithGrpcOptions(grpc.WithStreamInterceptor(grpcStopper.StreamClientInterceptor)), // ), // // grpcStopper.Stop(errors.New("test error")) diff --git a/tests/integration/with_trace_retry_test.go b/tests/integration/with_trace_retry_test.go index 42bc1958b..1cb5b6564 100644 --- a/tests/integration/with_trace_retry_test.go +++ b/tests/integration/with_trace_retry_test.go @@ -26,9 +26,7 @@ func TestWithTraceRetry(t *testing.T) { scope = newScope(t) db = scope.Driver( ydb.WithTraceRetry(trace.Retry{ - OnRetry: func( - info trace.RetryLoopStartInfo, - ) func(trace.RetryLoopIntermediateInfo) func(trace.RetryLoopDoneInfo) { + OnRetry: func(info trace.RetryLoopStartInfo) func(trace.RetryLoopDoneInfo) { retryCalled[info.Label] = true return nil }, @@ -64,9 +62,7 @@ func TestWithTraceRetry(t *testing.T) { scope = newScope(t) nativeDb = scope.Driver( ydb.WithTraceRetry(trace.Retry{ - OnRetry: func( - info trace.RetryLoopStartInfo, - ) func(trace.RetryLoopIntermediateInfo) func(trace.RetryLoopDoneInfo) { + OnRetry: func(info trace.RetryLoopStartInfo) func(trace.RetryLoopDoneInfo) { retryCalled[info.Label] = true return nil }, diff --git a/tests/slo/database/sql/storage.go b/tests/slo/database/sql/storage.go index 1fe6a2c38..8c740803c 100755 --- a/tests/slo/database/sql/storage.go +++ b/tests/slo/database/sql/storage.go @@ -77,7 +77,7 @@ type Storage struct { } func NewStorage(ctx context.Context, cfg *config.Config, poolSize int) (s *Storage, err error) { - ctx, cancel := context.WithTimeout(ctx, time.Minute*5) + ctx, cancel := context.WithTimeout(ctx, time.Minute*5) //nolint:gomnd defer cancel() s = &Storage{ @@ -139,11 +139,9 @@ func (s *Storage) Read(ctx context.Context, entryID generator.RowID) (res genera retry.WithIdempotent(true), retry.WithTrace( &trace.Retry{ - OnRetry: func(info trace.RetryLoopStartInfo) func(trace.RetryLoopIntermediateInfo) func(trace.RetryLoopDoneInfo) { - return func(info trace.RetryLoopIntermediateInfo) func(trace.RetryLoopDoneInfo) { - return func(info trace.RetryLoopDoneInfo) { - attempts = info.Attempts - } + OnRetry: func(info trace.RetryLoopStartInfo) func(trace.RetryLoopDoneInfo) { + return func(info trace.RetryLoopDoneInfo) { + attempts = info.Attempts } }, }, @@ -179,11 +177,9 @@ func (s *Storage) Write(ctx context.Context, e generator.Row) (attempts int, err retry.WithIdempotent(true), retry.WithTrace( &trace.Retry{ - OnRetry: func(info trace.RetryLoopStartInfo) func(trace.RetryLoopIntermediateInfo) func(trace.RetryLoopDoneInfo) { - return func(info trace.RetryLoopIntermediateInfo) func(trace.RetryLoopDoneInfo) { - return func(info trace.RetryLoopDoneInfo) { - attempts = info.Attempts - } + OnRetry: func(info trace.RetryLoopStartInfo) func(trace.RetryLoopDoneInfo) { + return func(info trace.RetryLoopDoneInfo) { + attempts = info.Attempts } }, }, diff --git a/tests/slo/go.mod b/tests/slo/go.mod index e8f010142..b39213d88 100644 --- a/tests/slo/go.mod +++ b/tests/slo/go.mod @@ -6,7 +6,6 @@ require ( github.com/prometheus/client_golang v1.14.0 github.com/ydb-platform/gorm-driver v0.1.1 github.com/ydb-platform/ydb-go-sdk/v3 v3.58.0 - go.opentelemetry.io/otel v1.21.0 golang.org/x/sync v0.6.0 golang.org/x/time v0.3.0 gorm.io/gorm v1.25.1 @@ -16,13 +15,12 @@ require ( require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/go-logr/logr v1.4.1 // indirect - github.com/go-logr/stdr v1.2.2 // indirect github.com/goccy/go-json v0.9.11 // indirect github.com/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/golang/snappy v0.0.4 // indirect + github.com/google/go-cmp v0.6.0 // indirect github.com/google/uuid v1.5.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect @@ -41,9 +39,7 @@ require ( github.com/ydb-platform/ydb-go-sdk-auth-environ v0.2.0 // indirect github.com/ydb-platform/ydb-go-yc v0.10.2 // indirect github.com/ydb-platform/ydb-go-yc-metadata v0.5.3 // indirect - go.opentelemetry.io/otel/metric v1.21.0 // indirect - go.opentelemetry.io/otel/trace v1.21.0 // indirect - golang.org/x/net v0.20.0 // indirect + golang.org/x/net v0.17.0 // indirect golang.org/x/sys v0.16.0 // indirect golang.org/x/text v0.14.0 // indirect google.golang.org/genproto v0.0.0-20240102182953-50ed04b92917 // indirect diff --git a/tests/slo/go.sum b/tests/slo/go.sum index 78bb98723..970122056 100644 --- a/tests/slo/go.sum +++ b/tests/slo/go.sum @@ -733,11 +733,6 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= -github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= @@ -1227,12 +1222,6 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc= -go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= -go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4= -go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= -go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc= -go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= @@ -1269,7 +1258,7 @@ golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= -golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1398,9 +1387,8 @@ golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= -golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= -golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1550,7 +1538,7 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= @@ -1564,7 +1552,7 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/tests/slo/gorm/storage.go b/tests/slo/gorm/storage.go index 2481014f1..b2cf92211 100644 --- a/tests/slo/gorm/storage.go +++ b/tests/slo/gorm/storage.go @@ -110,11 +110,9 @@ func (s *Storage) Read(ctx context.Context, id generator.RowID) (r generator.Row retry.WithIdempotent(true), retry.WithTrace( &trace.Retry{ - OnRetry: func(info trace.RetryLoopStartInfo) func(trace.RetryLoopIntermediateInfo) func(trace.RetryLoopDoneInfo) { - return func(info trace.RetryLoopIntermediateInfo) func(trace.RetryLoopDoneInfo) { - return func(info trace.RetryLoopDoneInfo) { - attempts = info.Attempts - } + OnRetry: func(info trace.RetryLoopStartInfo) func(trace.RetryLoopDoneInfo) { + return func(info trace.RetryLoopDoneInfo) { + attempts = info.Attempts } }, }, @@ -158,11 +156,9 @@ func (s *Storage) Write(ctx context.Context, row generator.Row) (attempts int, e retry.WithIdempotent(true), retry.WithTrace( &trace.Retry{ - OnRetry: func(info trace.RetryLoopStartInfo) func(trace.RetryLoopIntermediateInfo) func(trace.RetryLoopDoneInfo) { - return func(info trace.RetryLoopIntermediateInfo) func(trace.RetryLoopDoneInfo) { - return func(info trace.RetryLoopDoneInfo) { - attempts = info.Attempts - } + OnRetry: func(info trace.RetryLoopStartInfo) func(trace.RetryLoopDoneInfo) { + return func(info trace.RetryLoopDoneInfo) { + attempts = info.Attempts } }, }, diff --git a/tests/slo/internal/config/config.go b/tests/slo/internal/config/config.go index b858d27b8..612bc0ae3 100644 --- a/tests/slo/internal/config/config.go +++ b/tests/slo/internal/config/config.go @@ -35,6 +35,7 @@ type Config struct { ShutdownTime int } +//nolint:gomnd func New() (*Config, error) { cfg := &Config{} diff --git a/tests/slo/internal/metrics/metrics.go b/tests/slo/internal/metrics/metrics.go index ee7778152..67b4af470 100644 --- a/tests/slo/internal/metrics/metrics.go +++ b/tests/slo/internal/metrics/metrics.go @@ -63,7 +63,7 @@ func New(url, label, jobName string) (*Metrics, error) { 0.99: 0, 1.0: 0, }, - MaxAge: 15 * time.Second, + MaxAge: 15 * time.Second, //nolint:gomnd }, []string{"status", "jobName"}, ) @@ -71,7 +71,7 @@ func New(url, label, jobName string) (*Metrics, error) { prometheus.HistogramOpts{ Name: "attempts", Help: "summary of amount for request", - Buckets: prometheus.LinearBuckets(1, 1, 10), + Buckets: prometheus.LinearBuckets(1, 1, 10), //nolint:gomnd }, []string{"status", "jobName"}, ) diff --git a/tests/slo/native/query/main.go b/tests/slo/native/query/main.go index 9d5df4e72..138187877 100644 --- a/tests/slo/native/query/main.go +++ b/tests/slo/native/query/main.go @@ -41,8 +41,16 @@ func main() { panic(fmt.Errorf("create storage failed: %w", err)) } defer func() { - shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), - time.Duration(cfg.ShutdownTime)*time.Second) + var ( + shutdownCtx context.Context + shutdownCancel context.CancelFunc + ) + if cfg.ShutdownTime > 0 { + shutdownCtx, shutdownCancel = context.WithTimeout(context.Background(), + time.Duration(cfg.ShutdownTime)*time.Second) + } else { + shutdownCtx, shutdownCancel = context.WithCancel(context.Background()) + } defer shutdownCancel() _ = s.close(shutdownCtx) diff --git a/tests/slo/native/query/storage.go b/tests/slo/native/query/storage.go index b5e08a309..4c0132adc 100755 --- a/tests/slo/native/query/storage.go +++ b/tests/slo/native/query/storage.go @@ -5,10 +5,12 @@ import ( "errors" "fmt" "io" + "os" "path" "time" ydb "github.com/ydb-platform/ydb-go-sdk/v3" + "github.com/ydb-platform/ydb-go-sdk/v3/log" "github.com/ydb-platform/ydb-go-sdk/v3/query" "github.com/ydb-platform/ydb-go-sdk/v3/trace" @@ -64,12 +66,13 @@ DROP TABLE %s ` func NewStorage(ctx context.Context, cfg *config.Config, poolSize int) (*Storage, error) { - ctx, cancel := context.WithTimeout(ctx, time.Minute*5) + ctx, cancel := context.WithTimeout(ctx, time.Minute*5) //nolint:gomnd defer cancel() db, err := ydb.Open(ctx, cfg.Endpoint+cfg.DB, - ydb.WithSessionPoolLimit(poolSize), + ydb.WithSessionPoolSizeLimit(poolSize), + ydb.WithLogger(log.Default(os.Stderr, log.WithMinLevel(log.ERROR)), trace.DetailsAll), ) if err != nil { return nil, err @@ -251,8 +254,16 @@ func (s *Storage) dropTable(ctx context.Context) error { } func (s *Storage) close(ctx context.Context) error { - ctx, cancel := context.WithTimeout(ctx, time.Duration(s.cfg.ShutdownTime)*time.Second) - defer cancel() + var ( + shutdownCtx context.Context + shutdownCancel context.CancelFunc + ) + if s.cfg.ShutdownTime > 0 { + shutdownCtx, shutdownCancel = context.WithTimeout(ctx, time.Duration(s.cfg.ShutdownTime)*time.Second) + } else { + shutdownCtx, shutdownCancel = context.WithCancel(ctx) + } + defer shutdownCancel() - return s.db.Close(ctx) + return s.db.Close(shutdownCtx) } diff --git a/tests/slo/native/table/storage.go b/tests/slo/native/table/storage.go index 4a438bf10..220054aa0 100755 --- a/tests/slo/native/table/storage.go +++ b/tests/slo/native/table/storage.go @@ -65,7 +65,7 @@ type Storage struct { } func NewStorage(ctx context.Context, cfg *config.Config, poolSize int) (*Storage, error) { - ctx, cancel := context.WithTimeout(ctx, time.Minute*5) + ctx, cancel := context.WithTimeout(ctx, time.Minute*5) //nolint:gomnd defer cancel() db, err := ydb.Open( @@ -142,11 +142,9 @@ func (s *Storage) Read(ctx context.Context, entryID generator.RowID) (_ generato }, table.WithIdempotent(), table.WithTrace(trace.Table{ - OnDo: func(info trace.TableDoStartInfo) func(info trace.TableDoIntermediateInfo) func(trace.TableDoDoneInfo) { - return func(info trace.TableDoIntermediateInfo) func(trace.TableDoDoneInfo) { - return func(info trace.TableDoDoneInfo) { - attempts = info.Attempts - } + OnDo: func(info trace.TableDoStartInfo) func(trace.TableDoDoneInfo) { + return func(info trace.TableDoDoneInfo) { + attempts = info.Attempts } }, }), @@ -190,11 +188,9 @@ func (s *Storage) Write(ctx context.Context, e generator.Row) (attempts int, _ e }, table.WithIdempotent(), table.WithTrace(trace.Table{ - OnDo: func(info trace.TableDoStartInfo) func(info trace.TableDoIntermediateInfo) func(trace.TableDoDoneInfo) { - return func(info trace.TableDoIntermediateInfo) func(trace.TableDoDoneInfo) { - return func(info trace.TableDoDoneInfo) { - attempts = info.Attempts - } + OnDo: func(info trace.TableDoStartInfo) func(trace.TableDoDoneInfo) { + return func(info trace.TableDoDoneInfo) { + attempts = info.Attempts } }, }), diff --git a/tests/slo/xorm/storage.go b/tests/slo/xorm/storage.go index 87409a738..4550374c9 100644 --- a/tests/slo/xorm/storage.go +++ b/tests/slo/xorm/storage.go @@ -141,11 +141,9 @@ func (s *Storage) Read(ctx context.Context, id generator.RowID) (row generator.R retry.WithIdempotent(true), retry.WithTrace( &trace.Retry{ - OnRetry: func(info trace.RetryLoopStartInfo) func(trace.RetryLoopIntermediateInfo) func(trace.RetryLoopDoneInfo) { - return func(info trace.RetryLoopIntermediateInfo) func(trace.RetryLoopDoneInfo) { - return func(info trace.RetryLoopDoneInfo) { - attempts = info.Attempts - } + OnRetry: func(info trace.RetryLoopStartInfo) func(trace.RetryLoopDoneInfo) { + return func(info trace.RetryLoopDoneInfo) { + attempts = info.Attempts } }, }, @@ -176,11 +174,9 @@ func (s *Storage) Write(ctx context.Context, row generator.Row) (attempts int, e retry.WithIdempotent(true), retry.WithTrace( &trace.Retry{ - OnRetry: func(info trace.RetryLoopStartInfo) func(trace.RetryLoopIntermediateInfo) func(trace.RetryLoopDoneInfo) { - return func(info trace.RetryLoopIntermediateInfo) func(trace.RetryLoopDoneInfo) { - return func(info trace.RetryLoopDoneInfo) { - attempts = info.Attempts - } + OnRetry: func(info trace.RetryLoopStartInfo) func(trace.RetryLoopDoneInfo) { + return func(info trace.RetryLoopDoneInfo) { + attempts = info.Attempts } }, }, diff --git a/testutil/compare.go b/testutil/compare.go index a021ff909..8caf4b9bf 100644 --- a/testutil/compare.go +++ b/testutil/compare.go @@ -309,11 +309,11 @@ func compareBool(l, r *Ydb.Value) int { func compareDyNumber(l, r *Ydb.Value) (int, error) { ll := l.GetTextValue() rr := r.GetTextValue() - lf, _, err := big.ParseFloat(ll, 10, 127, big.ToNearestEven) + lf, _, err := big.ParseFloat(ll, 10, 127, big.ToNearestEven) //nolint:gomnd if err != nil { return 0, xerrors.WithStackTrace(err) } - rf, _, err := big.ParseFloat(rr, 10, 127, big.ToNearestEven) + rf, _, err := big.ParseFloat(rr, 10, 127, big.ToNearestEven) //nolint:gomnd if err != nil { return 0, err } diff --git a/testutil/compare_test.go b/testutil/compare_test.go index 45f2ed2c1..6d10fdcf1 100644 --- a/testutil/compare_test.go +++ b/testutil/compare_test.go @@ -2,6 +2,7 @@ package testutil import ( "errors" + "fmt" "testing" "github.com/ydb-platform/ydb-go-genproto/protos/Ydb" @@ -21,7 +22,11 @@ func TestUnwrapOptionalValue(t *testing.T) { if typeID != Ydb.Type_UTF8 { t.Errorf("Types are different: expected %d, actual %d", Ydb.Type_UTF8, typeID) } - textValue := val.GetValue().GetValue().(*Ydb.Value_TextValue) + textValue, ok := val.GetValue().GetValue().(*Ydb.Value_TextValue) + if !ok { + panic(fmt.Sprintf("unsupported type conversion from %T to *Ydb.Value_TextValue", val)) + } + text := textValue.TextValue if text != "a" { t.Errorf("Values are different: expected %q, actual %q", "a", text) @@ -37,7 +42,10 @@ func TestUnwrapPrimitiveValue(t *testing.T) { if typeID != Ydb.Type_UTF8 { t.Errorf("Types are different: expected %d, actual %d", Ydb.Type_UTF8, typeID) } - textValue := val.GetValue().GetValue().(*Ydb.Value_TextValue) + textValue, ok := val.GetValue().GetValue().(*Ydb.Value_TextValue) + if !ok { + panic(fmt.Sprintf("unsupported type conversion from %T to *Ydb.Value_TextValue", val)) + } text := textValue.TextValue if text != "a" { t.Errorf("Values are different: expected %q, actual %q", "a", text) @@ -53,7 +61,10 @@ func TestUnwrapNullValue(t *testing.T) { if typeID != Ydb.Type_UTF8 { t.Errorf("Types are different: expected %d, actual %d", Ydb.Type_UTF8, typeID) } - nullFlagValue := val.GetValue().GetValue().(*Ydb.Value_NullFlagValue) + nullFlagValue, ok := val.GetValue().GetValue().(*Ydb.Value_NullFlagValue) + if !ok { + panic(fmt.Sprintf("unsupported type conversion from %T to *Ydb.Value_NullFlagValue", nullFlagValue)) + } if nullFlagValue.NullFlagValue != structpb.NullValue_NULL_VALUE { t.Errorf("Values are different: expected %d, actual %d", structpb.NullValue_NULL_VALUE, nullFlagValue.NullFlagValue) } diff --git a/topic/topicoptions/topicoptions_reader.go b/topic/topicoptions/topicoptions_reader.go index 549211688..862f4e7de 100644 --- a/topic/topicoptions/topicoptions_reader.go +++ b/topic/topicoptions/topicoptions_reader.go @@ -26,9 +26,7 @@ type ReaderOption = topicreaderinternal.PublicReaderOption // WithReaderOperationTimeout // -// # Experimental -// -// Notice: This API is EXPERIMENTAL and may be changed or removed in a later release. +// Experimental: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#experimental func WithReaderOperationTimeout(timeout time.Duration) ReaderOption { return func(cfg *topicreaderinternal.ReaderConfig) { config.SetOperationTimeout(&cfg.Common, timeout) @@ -37,9 +35,7 @@ func WithReaderOperationTimeout(timeout time.Duration) ReaderOption { // WithReaderStartTimeout mean timeout for connect to reader stream and work some time without errors // -// # Experimental -// -// Notice: This API is EXPERIMENTAL and may be changed or removed in a later release. +// Experimental: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#experimental func WithReaderStartTimeout(timeout time.Duration) ReaderOption { return func(cfg *topicreaderinternal.ReaderConfig) { cfg.RetrySettings.StartTimeout = timeout @@ -58,9 +54,7 @@ func WithReaderCheckRetryErrorFunction(callback CheckErrorRetryFunction) ReaderO // WithReaderOperationCancelAfter // -// # Experimental -// -// Notice: This API is EXPERIMENTAL and may be changed or removed in a later release. +// Experimental: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#experimental func WithReaderOperationCancelAfter(cancelAfter time.Duration) ReaderOption { return func(cfg *topicreaderinternal.ReaderConfig) { config.SetOperationCancelAfter(&cfg.Common, cancelAfter) @@ -69,9 +63,7 @@ func WithReaderOperationCancelAfter(cancelAfter time.Duration) ReaderOption { // WithCommonConfig // -// # Experimental -// -// Notice: This API is EXPERIMENTAL and may be changed or removed in a later release. +// Experimental: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#experimental func WithCommonConfig(common config.Common) ReaderOption { return func(cfg *topicreaderinternal.ReaderConfig) { cfg.Common = common @@ -79,8 +71,11 @@ func WithCommonConfig(common config.Common) ReaderOption { } // WithCommitTimeLagTrigger -// Deprecated: (was experimental) will be removed soon. -// Use WithReaderCommitTimeLagTrigger instead +// +// Deprecated: was experimental and not actual now. +// Use WithReaderCommitTimeLagTrigger instead. +// Will be removed after Oct 2024. +// Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated func WithCommitTimeLagTrigger(lag time.Duration) ReaderOption { return WithReaderCommitTimeLagTrigger(lag) } @@ -96,8 +91,11 @@ func WithReaderCommitTimeLagTrigger(lag time.Duration) ReaderOption { } // WithCommitCountTrigger -// Deprecated: (was experimental) will be removed soon. -// Use WithReaderCommitCountTrigger instead +// +// Deprecated: was experimental and not actual now. +// Use WithReaderCommitCountTrigger instead. +// Will be removed after Oct 2024. +// Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated func WithCommitCountTrigger(count int) ReaderOption { return WithReaderCommitCountTrigger(count) } @@ -115,9 +113,10 @@ func WithReaderCommitCountTrigger(count int) ReaderOption { // prefer min count messages in batch // sometimes batch can contain fewer messages, for example if local buffer is full and SDK can't receive more messages // -// Deprecated: (was experimental) the method will be removed soon. -// -// The option will be removed for simplify code internals +// Deprecated: was experimental and not actual now. +// The option will be removed for simplify code internals. +// Will be removed after Oct 2024. +// Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated func WithBatchReadMinCount(count int) ReaderOption { return func(cfg *topicreaderinternal.ReaderConfig) { cfg.DefaultBatchConfig.MinCount = count @@ -125,8 +124,11 @@ func WithBatchReadMinCount(count int) ReaderOption { } // WithBatchReadMaxCount -// Deprecated: (was experimental) will be removed soon. +// +// Deprecated: was experimental and not actual now. // Use WithReaderBatchMaxCount instead. +// Will be removed after Oct 2024. +// Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated func WithBatchReadMaxCount(count int) ReaderOption { return func(cfg *topicreaderinternal.ReaderConfig) { cfg.DefaultBatchConfig.MaxCount = count @@ -141,8 +143,11 @@ func WithReaderBatchMaxCount(count int) ReaderOption { } // WithMessagesBufferSize -// Deprecated: (was experimental) will be removed soon +// +// Deprecated: was experimental and not actual now. // Use WithReaderBufferSizeBytes instead. +// Will be removed after Oct 2024. +// Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated func WithMessagesBufferSize(size int) ReaderOption { return WithReaderBufferSizeBytes(size) } @@ -191,8 +196,11 @@ const ( ) // WithCommitMode -// Deprecated: (was experimental) will be removed soon. +// +// Deprecated: was experimental and not actual now. // Use WithReaderCommitMode instead. +// Will be removed after Oct 2024. +// Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated func WithCommitMode(mode CommitMode) ReaderOption { return WithReaderCommitMode(mode) } @@ -217,8 +225,11 @@ type ( ) // WithGetPartitionStartOffset -// Deprecated: (was experimental) will be removed soon. -// Use WithReaderGetPartitionStartOffset instead +// +// Deprecated: was experimental and not actual now. +// Use WithReaderGetPartitionStartOffset instead. +// Will be removed after Oct 2024. +// Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated func WithGetPartitionStartOffset(f GetPartitionStartOffsetFunc) ReaderOption { return WithReaderGetPartitionStartOffset(f) } @@ -233,9 +244,7 @@ func WithReaderGetPartitionStartOffset(f GetPartitionStartOffsetFunc) ReaderOpti // WithReaderTrace set tracer for the topic reader // -// # Experimental -// -// Notice: This API is EXPERIMENTAL and may be changed or removed in a later release. +// Experimental: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#experimental func WithReaderTrace(t trace.Topic) ReaderOption { //nolint:gocritic return func(cfg *topicreaderinternal.ReaderConfig) { cfg.Trace = cfg.Trace.Compose(&t) diff --git a/topic/topicoptions/topicoptions_topic.go b/topic/topicoptions/topicoptions_topic.go index f79b710e2..f14522866 100644 --- a/topic/topicoptions/topicoptions_topic.go +++ b/topic/topicoptions/topicoptions_topic.go @@ -10,16 +10,12 @@ import ( // TopicOption // -// # Experimental -// -// Notice: This API is EXPERIMENTAL and may be changed or removed in a later release. +// Experimental: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#experimental type TopicOption func(c *topic.Config) // WithTrace defines trace over persqueue client calls // -// # Experimental -// -// Notice: This API is EXPERIMENTAL and may be changed or removed in a later release. +// Experimental: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#experimental func WithTrace(trace trace.Topic, opts ...trace.TopicComposeOption) TopicOption { //nolint:gocritic return func(c *topic.Config) { c.Trace = c.Trace.Compose(&trace, opts...) @@ -32,9 +28,7 @@ func WithTrace(trace trace.Topic, opts ...trace.TopicComposeOption) TopicOption // the client. // If OperationTimeout is zero then no timeout is used. // -// # Experimental -// -// Notice: This API is EXPERIMENTAL and may be changed or removed in a later release. +// Experimental: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#experimental func WithOperationTimeout(operationTimeout time.Duration) TopicOption { return func(c *topic.Config) { config.SetOperationTimeout(&c.Common, operationTimeout) @@ -47,9 +41,7 @@ func WithOperationTimeout(operationTimeout time.Duration) TopicOption { // processing will be continued. // If OperationCancelAfter is zero then no timeout is used. // -// # Experimental -// -// Notice: This API is EXPERIMENTAL and may be changed or removed in a later release. +// Experimental: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#experimental func WithOperationCancelAfter(operationCancelAfter time.Duration) TopicOption { return func(c *topic.Config) { config.SetOperationCancelAfter(&c.Common, operationCancelAfter) diff --git a/topic/topicoptions/topicoptions_writer.go b/topic/topicoptions/topicoptions_writer.go index a29c501cb..86d4b9c57 100644 --- a/topic/topicoptions/topicoptions_writer.go +++ b/topic/topicoptions/topicoptions_writer.go @@ -45,9 +45,7 @@ func WithWriterCompressorCount(num int) WriterOption { // WithWriterMaxQueueLen set max len of queue for wait ack // -// # Experimental -// -// Notice: This API is EXPERIMENTAL and may be changed or removed in a later release. +// Experimental: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#experimental func WithWriterMaxQueueLen(num int) WriterOption { return topicwriterinternal.WithMaxQueueLen(num) } @@ -61,8 +59,11 @@ func WithWriterMessageMaxBytesSize(size int) WriterOption { } // WithWriteSessionMeta -// Deprecated: (was experimental) will be removed soon. -// Use WithWriterSessionMeta instead +// +// Deprecated: was experimental and not actual now. +// Use WithWriterSessionMeta instead. +// Will be removed after Oct 2024. +// Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated func WithWriteSessionMeta(meta map[string]string) WriterOption { return WithWriterSessionMeta(meta) } @@ -73,8 +74,11 @@ func WithWriterSessionMeta(meta map[string]string) WriterOption { } // WithProducerID -// Deprecated: (was experimental) will be removed soon. -// Use WithWriterProducerID instead +// +// Deprecated: was experimental and not actual now. +// Use WithWriterProducerID instead. +// Will be removed after Oct 2024. +// Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated func WithProducerID(producerID string) WriterOption { return WithWriterProducerID(producerID) } @@ -85,8 +89,11 @@ func WithWriterProducerID(producerID string) WriterOption { } // WithPartitionID -// Deprecated: (was experimental) will be removed soon -// Use WithWriterPartitionID instead +// +// Deprecated: was experimental and not actual now. +// Use WithWriterPartitionID instead. +// Will be removed after Oct 2024. +// Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated func WithPartitionID(partitionID int64) WriterOption { return WithWriterPartitionID(partitionID) } @@ -97,7 +104,11 @@ func WithWriterPartitionID(partitionID int64) WriterOption { } // WithSyncWrite -// Deprecated: (was experimental) use WithWriterWaitServerAck instead +// +// Deprecated: was experimental and not actual now. +// Use WithWriterWaitServerAck instead. +// Will be removed after Oct 2024. +// Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated func WithSyncWrite(sync bool) WriterOption { return WithWriterWaitServerAck(sync) } @@ -110,17 +121,26 @@ func WithWriterWaitServerAck(wait bool) WriterOption { type ( // WithOnWriterConnectedInfo present information, received from server - // Deprecated: (was experimental) will be removed soon + // + // Deprecated: was experimental and not actual now. + // Will be removed after Oct 2024. + // Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated WithOnWriterConnectedInfo = topicwriterinternal.PublicWithOnWriterConnectedInfo // OnWriterInitResponseCallback - // Deprecated: (was experimental) will be removed soon. + // + // Deprecated: was experimental and not actual now. + // Will be removed after Oct 2024. + // Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated OnWriterInitResponseCallback = topicwriterinternal.PublicOnWriterInitResponseCallback ) // WithOnWriterFirstConnected set callback f, which will called once - after first successfully init topic writer stream -// Deprecated: (was experimental) will be removed soon. -// Use Writer.WaitInit function instead +// +// Deprecated: was experimental and not actual now. +// Use Writer.WaitInit function instead. +// Will be removed after Oct 2024. +// Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated func WithOnWriterFirstConnected(f OnWriterInitResponseCallback) WriterOption { return func(cfg *topicwriterinternal.WriterReconnectorConfig) { cfg.OnWriterInitResponseCallback = f @@ -128,8 +148,11 @@ func WithOnWriterFirstConnected(f OnWriterInitResponseCallback) WriterOption { } // WithCodec -// Deprecated: (was experimental) will be removed soon. -// Use WithWriterCodec instead +// +// Deprecated: was experimental and not actual now. +// Use WithWriterCodec instead. +// Will be removed after Oct 2024. +// Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated func WithCodec(codec topictypes.Codec) WriterOption { return WithWriterCodec(codec) } @@ -140,8 +163,11 @@ func WithWriterCodec(codec topictypes.Codec) WriterOption { } // WithCodecAutoSelect -// Deprecated: (was experimental) will be removed soon. +// +// Deprecated: was experimental and not actual now. // Use WithWriterCodecAutoSelect instead. +// Will be removed after Oct 2024. +// Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated func WithCodecAutoSelect() WriterOption { return topicwriterinternal.WithAutoCodec() } diff --git a/topic/topicreader/batch_options.go b/topic/topicreader/batch_options.go index 166432535..2566f161c 100644 --- a/topic/topicreader/batch_options.go +++ b/topic/topicreader/batch_options.go @@ -22,8 +22,10 @@ func (count WithBatchMaxCount) Apply( // count must be 1 or greater // it will panic if count < 1 // -// Deprecated: (was experimental) will be removed soon. -// The option will be removed for simplify code internals +// Deprecated: was experimental and not actual now. +// The option will be removed for simplify code internals. +// Will be removed after Oct 2024. +// Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated type WithBatchPreferMinCount int // Apply implements ReadBatchOption interface diff --git a/topic/topicreader/reader.go b/topic/topicreader/reader.go index 9c50d4a51..ec8f43b5e 100644 --- a/topic/topicreader/reader.go +++ b/topic/topicreader/reader.go @@ -75,8 +75,11 @@ func (r *Reader) Commit(ctx context.Context, obj CommitRangeGetter) error { type CommitRangeGetter = topicreaderinternal.PublicCommitRangeGetter // ReadMessageBatch -// Deprecated: (was experimental) will be removed soon. +// +// Deprecated: was experimental and not actual now. // Use ReadMessagesBatch instead. +// Will be removed after Oct 2024. +// Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated func (r *Reader) ReadMessageBatch(ctx context.Context, opts ...ReadBatchOption) (*Batch, error) { if err := r.inCall(&r.readInFlyght); err != nil { return nil, err diff --git a/trace/coordination.go b/trace/coordination.go index 551064671..41ca39d2c 100644 --- a/trace/coordination.go +++ b/trace/coordination.go @@ -1,5 +1,12 @@ package trace +import ( + "context" + "time" + + "github.com/ydb-platform/ydb-go-genproto/protos/Ydb_Coordination" +) + // tool gtrace used from ./internal/cmd/gtrace //go:generate gtrace @@ -7,5 +14,221 @@ package trace type ( // Coordination specified trace of coordination client activity. // gtrace:gen - Coordination struct{} + Coordination struct { + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnNew func(CoordinationNewStartInfo) func(CoordinationNewDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnCreateNode func(CoordinationCreateNodeStartInfo) func(CoordinationCreateNodeDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnAlterNode func(CoordinationAlterNodeStartInfo) func(CoordinationAlterNodeDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnDropNode func(CoordinationDropNodeStartInfo) func(CoordinationDropNodeDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnDescribeNode func(CoordinationDescribeNodeStartInfo) func(CoordinationDescribeNodeDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnSession func(CoordinationSessionStartInfo) func(CoordinationSessionDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnClose func(CoordinationCloseStartInfo) func(CoordinationCloseDoneInfo) + + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnStreamNew func(CoordinationStreamNewStartInfo) func(CoordinationStreamNewDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnSessionStarted func(CoordinationSessionStartedInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnSessionStartTimeout func(CoordinationSessionStartTimeoutInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnSessionKeepAliveTimeout func(CoordinationSessionKeepAliveTimeoutInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnSessionStopped func(CoordinationSessionStoppedInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnSessionStopTimeout func(CoordinationSessionStopTimeoutInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnSessionClientTimeout func(CoordinationSessionClientTimeoutInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnSessionServerExpire func(CoordinationSessionServerExpireInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnSessionServerError func(CoordinationSessionServerErrorInfo) + + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnSessionReceive func(CoordinationSessionReceiveStartInfo) func(CoordinationSessionReceiveDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnSessionReceiveUnexpected func(CoordinationSessionReceiveUnexpectedInfo) + + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnSessionStop func(CoordinationSessionStopInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnSessionStart func(CoordinationSessionStartStartInfo) func(CoordinationSessionStartDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnSessionSend func(CoordinationSessionSendStartInfo) func(CoordinationSessionSendDoneInfo) + } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + CoordinationNewStartInfo struct { + // Context make available context in trace callback function. + // Pointer to context provide replacement of context in trace callback function. + // Warning: concurrent access to pointer on client side must be excluded. + // Safe replacement of context are provided only inside callback function + Context *context.Context + Call call + } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + CoordinationNewDoneInfo struct{} + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + CoordinationCloseStartInfo struct { + // Context make available context in trace callback function. + // Pointer to context provide replacement of context in trace callback function. + // Warning: concurrent access to pointer on client side must be excluded. + // Safe replacement of context are provided only inside callback function + Context *context.Context + Call call + } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + CoordinationCloseDoneInfo struct { + Error error + } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + CoordinationCreateNodeStartInfo struct { + // Context make available context in trace callback function. + // Pointer to context provide replacement of context in trace callback function. + // Warning: concurrent access to pointer on client side must be excluded. + // Safe replacement of context are provided only inside callback function + Context *context.Context + Call call + + Path string + } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + CoordinationCreateNodeDoneInfo struct { + Error error + } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + CoordinationAlterNodeStartInfo struct { + // Context make available context in trace callback function. + // Pointer to context provide replacement of context in trace callback function. + // Warning: concurrent access to pointer on client side must be excluded. + // Safe replacement of context are provided only inside callback function + Context *context.Context + Call call + + Path string + } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + CoordinationAlterNodeDoneInfo struct { + Error error + } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + CoordinationDropNodeStartInfo struct { + // Context make available context in trace callback function. + // Pointer to context provide replacement of context in trace callback function. + // Warning: concurrent access to pointer on client side must be excluded. + // Safe replacement of context are provided only inside callback function + Context *context.Context + Call call + + Path string + } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + CoordinationDropNodeDoneInfo struct { + Error error + } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + CoordinationDescribeNodeStartInfo struct { + // Context make available context in trace callback function. + // Pointer to context provide replacement of context in trace callback function. + // Warning: concurrent access to pointer on client side must be excluded. + // Safe replacement of context are provided only inside callback function + Context *context.Context + Call call + + Path string + } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + CoordinationDescribeNodeDoneInfo struct { + Error error + } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + CoordinationSessionStartInfo struct { + // Context make available context in trace callback function. + // Pointer to context provide replacement of context in trace callback function. + // Warning: concurrent access to pointer on client side must be excluded. + // Safe replacement of context are provided only inside callback function + Context *context.Context + Call call + + Path string + } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + CoordinationSessionDoneInfo struct { + Error error + } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + CoordinationStreamNewStartInfo struct{} + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + CoordinationStreamNewDoneInfo struct { + Error error + } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + CoordinationSessionStartedInfo struct { + SessionID uint64 + ExpectedSessionID uint64 + } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + CoordinationSessionStartTimeoutInfo struct { + Timeout time.Duration + } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + CoordinationSessionKeepAliveTimeoutInfo struct { + LastGoodResponseTime time.Time + Timeout time.Duration + } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + CoordinationSessionStoppedInfo struct { + SessionID uint64 + ExpectedSessionID uint64 + } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + CoordinationSessionStopTimeoutInfo struct { + Timeout time.Duration + } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + CoordinationSessionClientTimeoutInfo struct { + LastGoodResponseTime time.Time + Timeout time.Duration + } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + CoordinationSessionServerExpireInfo struct { + Failure *Ydb_Coordination.SessionResponse_Failure + } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + CoordinationSessionServerErrorInfo struct { + Failure *Ydb_Coordination.SessionResponse_Failure + } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + CoordinationSessionReceiveStartInfo struct{} + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + CoordinationSessionReceiveDoneInfo struct { + Response *Ydb_Coordination.SessionResponse + Error error + } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + CoordinationSessionReceiveUnexpectedInfo struct { + Response *Ydb_Coordination.SessionResponse + } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + CoordinationSessionStartStartInfo struct{} + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + CoordinationSessionStartDoneInfo struct { + Error error + } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + CoordinationSessionStopInfo struct { + SessionID uint64 + } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + CoordinationSessionSendStartInfo struct { + Request *Ydb_Coordination.SessionRequest + } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + CoordinationSessionSendDoneInfo struct { + Error error + } ) diff --git a/trace/coordination_gtrace.go b/trace/coordination_gtrace.go index 609198059..422f61153 100644 --- a/trace/coordination_gtrace.go +++ b/trace/coordination_gtrace.go @@ -2,15 +2,24 @@ package trace +import ( + "context" + "time" + + "github.com/ydb-platform/ydb-go-genproto/protos/Ydb_Coordination" +) + // coordinationComposeOptions is a holder of options type coordinationComposeOptions struct { panicCallback func(e interface{}) } // CoordinationOption specified Coordination compose option +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals type CoordinationComposeOption func(o *coordinationComposeOptions) // WithCoordinationPanicCallback specified behavior on panic +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func WithCoordinationPanicCallback(cb func(e interface{})) CoordinationComposeOption { return func(o *coordinationComposeOptions) { o.panicCallback = cb @@ -18,7 +27,1018 @@ func WithCoordinationPanicCallback(cb func(e interface{})) CoordinationComposeOp } // Compose returns a new Coordination which has functional fields composed both from t and x. +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func (t *Coordination) Compose(x *Coordination, opts ...CoordinationComposeOption) *Coordination { var ret Coordination + options := coordinationComposeOptions{} + for _, opt := range opts { + if opt != nil { + opt(&options) + } + } + { + h1 := t.OnNew + h2 := x.OnNew + ret.OnNew = func(c CoordinationNewStartInfo) func(CoordinationNewDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + var r, r1 func(CoordinationNewDoneInfo) + if h1 != nil { + r = h1(c) + } + if h2 != nil { + r1 = h2(c) + } + return func(c CoordinationNewDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + if r != nil { + r(c) + } + if r1 != nil { + r1(c) + } + } + } + } + { + h1 := t.OnCreateNode + h2 := x.OnCreateNode + ret.OnCreateNode = func(c CoordinationCreateNodeStartInfo) func(CoordinationCreateNodeDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + var r, r1 func(CoordinationCreateNodeDoneInfo) + if h1 != nil { + r = h1(c) + } + if h2 != nil { + r1 = h2(c) + } + return func(c CoordinationCreateNodeDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + if r != nil { + r(c) + } + if r1 != nil { + r1(c) + } + } + } + } + { + h1 := t.OnAlterNode + h2 := x.OnAlterNode + ret.OnAlterNode = func(c CoordinationAlterNodeStartInfo) func(CoordinationAlterNodeDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + var r, r1 func(CoordinationAlterNodeDoneInfo) + if h1 != nil { + r = h1(c) + } + if h2 != nil { + r1 = h2(c) + } + return func(c CoordinationAlterNodeDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + if r != nil { + r(c) + } + if r1 != nil { + r1(c) + } + } + } + } + { + h1 := t.OnDropNode + h2 := x.OnDropNode + ret.OnDropNode = func(c CoordinationDropNodeStartInfo) func(CoordinationDropNodeDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + var r, r1 func(CoordinationDropNodeDoneInfo) + if h1 != nil { + r = h1(c) + } + if h2 != nil { + r1 = h2(c) + } + return func(c CoordinationDropNodeDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + if r != nil { + r(c) + } + if r1 != nil { + r1(c) + } + } + } + } + { + h1 := t.OnDescribeNode + h2 := x.OnDescribeNode + ret.OnDescribeNode = func(c CoordinationDescribeNodeStartInfo) func(CoordinationDescribeNodeDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + var r, r1 func(CoordinationDescribeNodeDoneInfo) + if h1 != nil { + r = h1(c) + } + if h2 != nil { + r1 = h2(c) + } + return func(c CoordinationDescribeNodeDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + if r != nil { + r(c) + } + if r1 != nil { + r1(c) + } + } + } + } + { + h1 := t.OnSession + h2 := x.OnSession + ret.OnSession = func(c CoordinationSessionStartInfo) func(CoordinationSessionDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + var r, r1 func(CoordinationSessionDoneInfo) + if h1 != nil { + r = h1(c) + } + if h2 != nil { + r1 = h2(c) + } + return func(c CoordinationSessionDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + if r != nil { + r(c) + } + if r1 != nil { + r1(c) + } + } + } + } + { + h1 := t.OnClose + h2 := x.OnClose + ret.OnClose = func(c CoordinationCloseStartInfo) func(CoordinationCloseDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + var r, r1 func(CoordinationCloseDoneInfo) + if h1 != nil { + r = h1(c) + } + if h2 != nil { + r1 = h2(c) + } + return func(c CoordinationCloseDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + if r != nil { + r(c) + } + if r1 != nil { + r1(c) + } + } + } + } + { + h1 := t.OnStreamNew + h2 := x.OnStreamNew + ret.OnStreamNew = func(c CoordinationStreamNewStartInfo) func(CoordinationStreamNewDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + var r, r1 func(CoordinationStreamNewDoneInfo) + if h1 != nil { + r = h1(c) + } + if h2 != nil { + r1 = h2(c) + } + return func(c CoordinationStreamNewDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + if r != nil { + r(c) + } + if r1 != nil { + r1(c) + } + } + } + } + { + h1 := t.OnSessionStarted + h2 := x.OnSessionStarted + ret.OnSessionStarted = func(c CoordinationSessionStartedInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + if h1 != nil { + h1(c) + } + if h2 != nil { + h2(c) + } + } + } + { + h1 := t.OnSessionStartTimeout + h2 := x.OnSessionStartTimeout + ret.OnSessionStartTimeout = func(c CoordinationSessionStartTimeoutInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + if h1 != nil { + h1(c) + } + if h2 != nil { + h2(c) + } + } + } + { + h1 := t.OnSessionKeepAliveTimeout + h2 := x.OnSessionKeepAliveTimeout + ret.OnSessionKeepAliveTimeout = func(c CoordinationSessionKeepAliveTimeoutInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + if h1 != nil { + h1(c) + } + if h2 != nil { + h2(c) + } + } + } + { + h1 := t.OnSessionStopped + h2 := x.OnSessionStopped + ret.OnSessionStopped = func(c CoordinationSessionStoppedInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + if h1 != nil { + h1(c) + } + if h2 != nil { + h2(c) + } + } + } + { + h1 := t.OnSessionStopTimeout + h2 := x.OnSessionStopTimeout + ret.OnSessionStopTimeout = func(c CoordinationSessionStopTimeoutInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + if h1 != nil { + h1(c) + } + if h2 != nil { + h2(c) + } + } + } + { + h1 := t.OnSessionClientTimeout + h2 := x.OnSessionClientTimeout + ret.OnSessionClientTimeout = func(c CoordinationSessionClientTimeoutInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + if h1 != nil { + h1(c) + } + if h2 != nil { + h2(c) + } + } + } + { + h1 := t.OnSessionServerExpire + h2 := x.OnSessionServerExpire + ret.OnSessionServerExpire = func(c CoordinationSessionServerExpireInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + if h1 != nil { + h1(c) + } + if h2 != nil { + h2(c) + } + } + } + { + h1 := t.OnSessionServerError + h2 := x.OnSessionServerError + ret.OnSessionServerError = func(c CoordinationSessionServerErrorInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + if h1 != nil { + h1(c) + } + if h2 != nil { + h2(c) + } + } + } + { + h1 := t.OnSessionReceive + h2 := x.OnSessionReceive + ret.OnSessionReceive = func(c CoordinationSessionReceiveStartInfo) func(CoordinationSessionReceiveDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + var r, r1 func(CoordinationSessionReceiveDoneInfo) + if h1 != nil { + r = h1(c) + } + if h2 != nil { + r1 = h2(c) + } + return func(c CoordinationSessionReceiveDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + if r != nil { + r(c) + } + if r1 != nil { + r1(c) + } + } + } + } + { + h1 := t.OnSessionReceiveUnexpected + h2 := x.OnSessionReceiveUnexpected + ret.OnSessionReceiveUnexpected = func(c CoordinationSessionReceiveUnexpectedInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + if h1 != nil { + h1(c) + } + if h2 != nil { + h2(c) + } + } + } + { + h1 := t.OnSessionStop + h2 := x.OnSessionStop + ret.OnSessionStop = func(c CoordinationSessionStopInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + if h1 != nil { + h1(c) + } + if h2 != nil { + h2(c) + } + } + } + { + h1 := t.OnSessionStart + h2 := x.OnSessionStart + ret.OnSessionStart = func(c CoordinationSessionStartStartInfo) func(CoordinationSessionStartDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + var r, r1 func(CoordinationSessionStartDoneInfo) + if h1 != nil { + r = h1(c) + } + if h2 != nil { + r1 = h2(c) + } + return func(c CoordinationSessionStartDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + if r != nil { + r(c) + } + if r1 != nil { + r1(c) + } + } + } + } + { + h1 := t.OnSessionSend + h2 := x.OnSessionSend + ret.OnSessionSend = func(c CoordinationSessionSendStartInfo) func(CoordinationSessionSendDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + var r, r1 func(CoordinationSessionSendDoneInfo) + if h1 != nil { + r = h1(c) + } + if h2 != nil { + r1 = h2(c) + } + return func(c CoordinationSessionSendDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + if r != nil { + r(c) + } + if r1 != nil { + r1(c) + } + } + } + } return &ret } +func (t *Coordination) onNew(c CoordinationNewStartInfo) func(CoordinationNewDoneInfo) { + fn := t.OnNew + if fn == nil { + return func(CoordinationNewDoneInfo) { + return + } + } + res := fn(c) + if res == nil { + return func(CoordinationNewDoneInfo) { + return + } + } + return res +} +func (t *Coordination) onCreateNode(c CoordinationCreateNodeStartInfo) func(CoordinationCreateNodeDoneInfo) { + fn := t.OnCreateNode + if fn == nil { + return func(CoordinationCreateNodeDoneInfo) { + return + } + } + res := fn(c) + if res == nil { + return func(CoordinationCreateNodeDoneInfo) { + return + } + } + return res +} +func (t *Coordination) onAlterNode(c CoordinationAlterNodeStartInfo) func(CoordinationAlterNodeDoneInfo) { + fn := t.OnAlterNode + if fn == nil { + return func(CoordinationAlterNodeDoneInfo) { + return + } + } + res := fn(c) + if res == nil { + return func(CoordinationAlterNodeDoneInfo) { + return + } + } + return res +} +func (t *Coordination) onDropNode(c CoordinationDropNodeStartInfo) func(CoordinationDropNodeDoneInfo) { + fn := t.OnDropNode + if fn == nil { + return func(CoordinationDropNodeDoneInfo) { + return + } + } + res := fn(c) + if res == nil { + return func(CoordinationDropNodeDoneInfo) { + return + } + } + return res +} +func (t *Coordination) onDescribeNode(c CoordinationDescribeNodeStartInfo) func(CoordinationDescribeNodeDoneInfo) { + fn := t.OnDescribeNode + if fn == nil { + return func(CoordinationDescribeNodeDoneInfo) { + return + } + } + res := fn(c) + if res == nil { + return func(CoordinationDescribeNodeDoneInfo) { + return + } + } + return res +} +func (t *Coordination) onSession(c CoordinationSessionStartInfo) func(CoordinationSessionDoneInfo) { + fn := t.OnSession + if fn == nil { + return func(CoordinationSessionDoneInfo) { + return + } + } + res := fn(c) + if res == nil { + return func(CoordinationSessionDoneInfo) { + return + } + } + return res +} +func (t *Coordination) onClose(c CoordinationCloseStartInfo) func(CoordinationCloseDoneInfo) { + fn := t.OnClose + if fn == nil { + return func(CoordinationCloseDoneInfo) { + return + } + } + res := fn(c) + if res == nil { + return func(CoordinationCloseDoneInfo) { + return + } + } + return res +} +func (t *Coordination) onStreamNew(c CoordinationStreamNewStartInfo) func(CoordinationStreamNewDoneInfo) { + fn := t.OnStreamNew + if fn == nil { + return func(CoordinationStreamNewDoneInfo) { + return + } + } + res := fn(c) + if res == nil { + return func(CoordinationStreamNewDoneInfo) { + return + } + } + return res +} +func (t *Coordination) onSessionStarted(c CoordinationSessionStartedInfo) { + fn := t.OnSessionStarted + if fn == nil { + return + } + fn(c) +} +func (t *Coordination) onSessionStartTimeout(c CoordinationSessionStartTimeoutInfo) { + fn := t.OnSessionStartTimeout + if fn == nil { + return + } + fn(c) +} +func (t *Coordination) onSessionKeepAliveTimeout(c CoordinationSessionKeepAliveTimeoutInfo) { + fn := t.OnSessionKeepAliveTimeout + if fn == nil { + return + } + fn(c) +} +func (t *Coordination) onSessionStopped(c CoordinationSessionStoppedInfo) { + fn := t.OnSessionStopped + if fn == nil { + return + } + fn(c) +} +func (t *Coordination) onSessionStopTimeout(c CoordinationSessionStopTimeoutInfo) { + fn := t.OnSessionStopTimeout + if fn == nil { + return + } + fn(c) +} +func (t *Coordination) onSessionClientTimeout(c CoordinationSessionClientTimeoutInfo) { + fn := t.OnSessionClientTimeout + if fn == nil { + return + } + fn(c) +} +func (t *Coordination) onSessionServerExpire(c CoordinationSessionServerExpireInfo) { + fn := t.OnSessionServerExpire + if fn == nil { + return + } + fn(c) +} +func (t *Coordination) onSessionServerError(c CoordinationSessionServerErrorInfo) { + fn := t.OnSessionServerError + if fn == nil { + return + } + fn(c) +} +func (t *Coordination) onSessionReceive(c CoordinationSessionReceiveStartInfo) func(CoordinationSessionReceiveDoneInfo) { + fn := t.OnSessionReceive + if fn == nil { + return func(CoordinationSessionReceiveDoneInfo) { + return + } + } + res := fn(c) + if res == nil { + return func(CoordinationSessionReceiveDoneInfo) { + return + } + } + return res +} +func (t *Coordination) onSessionReceiveUnexpected(c CoordinationSessionReceiveUnexpectedInfo) { + fn := t.OnSessionReceiveUnexpected + if fn == nil { + return + } + fn(c) +} +func (t *Coordination) onSessionStop(c CoordinationSessionStopInfo) { + fn := t.OnSessionStop + if fn == nil { + return + } + fn(c) +} +func (t *Coordination) onSessionStart(c CoordinationSessionStartStartInfo) func(CoordinationSessionStartDoneInfo) { + fn := t.OnSessionStart + if fn == nil { + return func(CoordinationSessionStartDoneInfo) { + return + } + } + res := fn(c) + if res == nil { + return func(CoordinationSessionStartDoneInfo) { + return + } + } + return res +} +func (t *Coordination) onSessionSend(c CoordinationSessionSendStartInfo) func(CoordinationSessionSendDoneInfo) { + fn := t.OnSessionSend + if fn == nil { + return func(CoordinationSessionSendDoneInfo) { + return + } + } + res := fn(c) + if res == nil { + return func(CoordinationSessionSendDoneInfo) { + return + } + } + return res +} +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals +func CoordinationOnNew(t *Coordination, c *context.Context, call call) func() { + var p CoordinationNewStartInfo + p.Context = c + p.Call = call + res := t.onNew(p) + return func() { + var p CoordinationNewDoneInfo + res(p) + } +} +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals +func CoordinationOnCreateNode(t *Coordination, c *context.Context, call call, path string) func(error) { + var p CoordinationCreateNodeStartInfo + p.Context = c + p.Call = call + p.Path = path + res := t.onCreateNode(p) + return func(e error) { + var p CoordinationCreateNodeDoneInfo + p.Error = e + res(p) + } +} +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals +func CoordinationOnAlterNode(t *Coordination, c *context.Context, call call, path string) func(error) { + var p CoordinationAlterNodeStartInfo + p.Context = c + p.Call = call + p.Path = path + res := t.onAlterNode(p) + return func(e error) { + var p CoordinationAlterNodeDoneInfo + p.Error = e + res(p) + } +} +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals +func CoordinationOnDropNode(t *Coordination, c *context.Context, call call, path string) func(error) { + var p CoordinationDropNodeStartInfo + p.Context = c + p.Call = call + p.Path = path + res := t.onDropNode(p) + return func(e error) { + var p CoordinationDropNodeDoneInfo + p.Error = e + res(p) + } +} +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals +func CoordinationOnDescribeNode(t *Coordination, c *context.Context, call call, path string) func(error) { + var p CoordinationDescribeNodeStartInfo + p.Context = c + p.Call = call + p.Path = path + res := t.onDescribeNode(p) + return func(e error) { + var p CoordinationDescribeNodeDoneInfo + p.Error = e + res(p) + } +} +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals +func CoordinationOnSession(t *Coordination, c *context.Context, call call, path string) func(error) { + var p CoordinationSessionStartInfo + p.Context = c + p.Call = call + p.Path = path + res := t.onSession(p) + return func(e error) { + var p CoordinationSessionDoneInfo + p.Error = e + res(p) + } +} +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals +func CoordinationOnClose(t *Coordination, c *context.Context, call call) func(error) { + var p CoordinationCloseStartInfo + p.Context = c + p.Call = call + res := t.onClose(p) + return func(e error) { + var p CoordinationCloseDoneInfo + p.Error = e + res(p) + } +} +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals +func CoordinationOnStreamNew(t *Coordination) func(error) { + var p CoordinationStreamNewStartInfo + res := t.onStreamNew(p) + return func(e error) { + var p CoordinationStreamNewDoneInfo + p.Error = e + res(p) + } +} +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals +func CoordinationOnSessionStarted(t *Coordination, sessionID uint64, expectedSessionID uint64) { + var p CoordinationSessionStartedInfo + p.SessionID = sessionID + p.ExpectedSessionID = expectedSessionID + t.onSessionStarted(p) +} +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals +func CoordinationOnSessionStartTimeout(t *Coordination, timeout time.Duration) { + var p CoordinationSessionStartTimeoutInfo + p.Timeout = timeout + t.onSessionStartTimeout(p) +} +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals +func CoordinationOnSessionKeepAliveTimeout(t *Coordination, lastGoodResponseTime time.Time, timeout time.Duration) { + var p CoordinationSessionKeepAliveTimeoutInfo + p.LastGoodResponseTime = lastGoodResponseTime + p.Timeout = timeout + t.onSessionKeepAliveTimeout(p) +} +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals +func CoordinationOnSessionStopped(t *Coordination, sessionID uint64, expectedSessionID uint64) { + var p CoordinationSessionStoppedInfo + p.SessionID = sessionID + p.ExpectedSessionID = expectedSessionID + t.onSessionStopped(p) +} +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals +func CoordinationOnSessionStopTimeout(t *Coordination, timeout time.Duration) { + var p CoordinationSessionStopTimeoutInfo + p.Timeout = timeout + t.onSessionStopTimeout(p) +} +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals +func CoordinationOnSessionClientTimeout(t *Coordination, lastGoodResponseTime time.Time, timeout time.Duration) { + var p CoordinationSessionClientTimeoutInfo + p.LastGoodResponseTime = lastGoodResponseTime + p.Timeout = timeout + t.onSessionClientTimeout(p) +} +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals +func CoordinationOnSessionServerExpire(t *Coordination, failure *Ydb_Coordination.SessionResponse_Failure) { + var p CoordinationSessionServerExpireInfo + p.Failure = failure + t.onSessionServerExpire(p) +} +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals +func CoordinationOnSessionServerError(t *Coordination, failure *Ydb_Coordination.SessionResponse_Failure) { + var p CoordinationSessionServerErrorInfo + p.Failure = failure + t.onSessionServerError(p) +} +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals +func CoordinationOnSessionReceive(t *Coordination) func(response *Ydb_Coordination.SessionResponse, _ error) { + var p CoordinationSessionReceiveStartInfo + res := t.onSessionReceive(p) + return func(response *Ydb_Coordination.SessionResponse, e error) { + var p CoordinationSessionReceiveDoneInfo + p.Response = response + p.Error = e + res(p) + } +} +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals +func CoordinationOnSessionReceiveUnexpected(t *Coordination, response *Ydb_Coordination.SessionResponse) { + var p CoordinationSessionReceiveUnexpectedInfo + p.Response = response + t.onSessionReceiveUnexpected(p) +} +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals +func CoordinationOnSessionStop(t *Coordination, sessionID uint64) { + var p CoordinationSessionStopInfo + p.SessionID = sessionID + t.onSessionStop(p) +} +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals +func CoordinationOnSessionStart(t *Coordination) func(error) { + var p CoordinationSessionStartStartInfo + res := t.onSessionStart(p) + return func(e error) { + var p CoordinationSessionStartDoneInfo + p.Error = e + res(p) + } +} +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals +func CoordinationOnSessionSend(t *Coordination, request *Ydb_Coordination.SessionRequest) func(error) { + var p CoordinationSessionSendStartInfo + p.Request = request + res := t.onSessionSend(p) + return func(e error) { + var p CoordinationSessionSendDoneInfo + p.Error = e + res(p) + } +} diff --git a/trace/discovery.go b/trace/discovery.go index 5b0496eef..e2888cb46 100644 --- a/trace/discovery.go +++ b/trace/discovery.go @@ -9,10 +9,14 @@ import "context" type ( // Discovery specified trace of discovery client activity. // gtrace:gen + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals Discovery struct { + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals OnDiscover func(DiscoveryDiscoverStartInfo) func(DiscoveryDiscoverDoneInfo) - OnWhoAmI func(DiscoveryWhoAmIStartInfo) func(DiscoveryWhoAmIDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnWhoAmI func(DiscoveryWhoAmIStartInfo) func(DiscoveryWhoAmIDoneInfo) } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DiscoveryDiscoverStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -23,11 +27,13 @@ type ( Address string Database string } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DiscoveryDiscoverDoneInfo struct { Location string Endpoints []EndpointInfo Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DiscoveryWhoAmIStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -36,6 +42,7 @@ type ( Context *context.Context Call call } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DiscoveryWhoAmIDoneInfo struct { User string Groups []string diff --git a/trace/discovery_gtrace.go b/trace/discovery_gtrace.go index 362d5c0e5..20d40ebda 100644 --- a/trace/discovery_gtrace.go +++ b/trace/discovery_gtrace.go @@ -12,9 +12,11 @@ type discoveryComposeOptions struct { } // DiscoveryOption specified Discovery compose option +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals type DiscoveryComposeOption func(o *discoveryComposeOptions) // WithDiscoveryPanicCallback specified behavior on panic +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func WithDiscoveryPanicCallback(cb func(e interface{})) DiscoveryComposeOption { return func(o *discoveryComposeOptions) { o.panicCallback = cb @@ -22,6 +24,7 @@ func WithDiscoveryPanicCallback(cb func(e interface{})) DiscoveryComposeOption { } // Compose returns a new Discovery which has functional fields composed both from t and x. +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func (t *Discovery) Compose(x *Discovery, opts ...DiscoveryComposeOption) *Discovery { var ret Discovery options := discoveryComposeOptions{} @@ -132,6 +135,7 @@ func (t *Discovery) onWhoAmI(d DiscoveryWhoAmIStartInfo) func(DiscoveryWhoAmIDon } return res } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func DiscoveryOnDiscover(t *Discovery, c *context.Context, call call, address string, database string) func(location string, endpoints []EndpointInfo, _ error) { var p DiscoveryDiscoverStartInfo p.Context = c @@ -147,6 +151,7 @@ func DiscoveryOnDiscover(t *Discovery, c *context.Context, call call, address st res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func DiscoveryOnWhoAmI(t *Discovery, c *context.Context, call call) func(user string, groups []string, _ error) { var p DiscoveryWhoAmIStartInfo p.Context = c diff --git a/trace/driver.go b/trace/driver.go index e9ece8712..8cf5dedc3 100644 --- a/trace/driver.go +++ b/trace/driver.go @@ -14,48 +14,73 @@ import ( type ( // Driver specified trace of common driver activity. // gtrace:gen + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals Driver struct { // Driver runtime events - OnInit func(DriverInitStartInfo) func(DriverInitDoneInfo) - OnWith func(DriverWithStartInfo) func(DriverWithDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnInit func(DriverInitStartInfo) func(DriverInitDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnWith func(DriverWithStartInfo) func(DriverWithDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals OnClose func(DriverCloseStartInfo) func(DriverCloseDoneInfo) // Pool of connections - OnPoolNew func(DriverConnPoolNewStartInfo) func(DriverConnPoolNewDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnPoolNew func(DriverConnPoolNewStartInfo) func(DriverConnPoolNewDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals OnPoolRelease func(DriverConnPoolReleaseStartInfo) func(DriverConnPoolReleaseDoneInfo) // Resolver events + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals OnResolve func(DriverResolveStartInfo) func(DriverResolveDoneInfo) // Conn events - OnConnStateChange func(DriverConnStateChangeStartInfo) func(DriverConnStateChangeDoneInfo) - OnConnInvoke func(DriverConnInvokeStartInfo) func(DriverConnInvokeDoneInfo) - OnConnNewStream func(DriverConnNewStreamStartInfo) func(DriverConnNewStreamDoneInfo) - OnConnStreamRecvMsg func(DriverConnStreamRecvMsgStartInfo) func(DriverConnStreamRecvMsgDoneInfo) - OnConnStreamSendMsg func(DriverConnStreamSendMsgStartInfo) func(DriverConnStreamSendMsgDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnConnStateChange func(DriverConnStateChangeStartInfo) func(DriverConnStateChangeDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnConnInvoke func(DriverConnInvokeStartInfo) func(DriverConnInvokeDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnConnNewStream func(DriverConnNewStreamStartInfo) func(DriverConnNewStreamDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnConnStreamRecvMsg func(DriverConnStreamRecvMsgStartInfo) func(DriverConnStreamRecvMsgDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnConnStreamSendMsg func(DriverConnStreamSendMsgStartInfo) func(DriverConnStreamSendMsgDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals OnConnStreamCloseSend func(DriverConnStreamCloseSendStartInfo) func(DriverConnStreamCloseSendDoneInfo) - OnConnDial func(DriverConnDialStartInfo) func(DriverConnDialDoneInfo) - OnConnBan func(DriverConnBanStartInfo) func(DriverConnBanDoneInfo) - OnConnAllow func(DriverConnAllowStartInfo) func(DriverConnAllowDoneInfo) - OnConnClose func(DriverConnCloseStartInfo) func(DriverConnCloseDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnConnDial func(DriverConnDialStartInfo) func(DriverConnDialDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnConnBan func(DriverConnBanStartInfo) func(DriverConnBanDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnConnAllow func(DriverConnAllowStartInfo) func(DriverConnAllowDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnConnPark func(DriverConnParkStartInfo) func(DriverConnParkDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnConnClose func(DriverConnCloseStartInfo) func(DriverConnCloseDoneInfo) // Repeater events + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals OnRepeaterWakeUp func(DriverRepeaterWakeUpStartInfo) func(DriverRepeaterWakeUpDoneInfo) // Balancer events + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals OnBalancerInit func(DriverBalancerInitStartInfo) func(DriverBalancerInitDoneInfo) - OnBalancerClose func(DriverBalancerCloseStartInfo) func(DriverBalancerCloseDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnBalancerClose func(DriverBalancerCloseStartInfo) func(DriverBalancerCloseDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals OnBalancerChooseEndpoint func( DriverBalancerChooseEndpointStartInfo, ) func( DriverBalancerChooseEndpointDoneInfo, ) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals OnBalancerClusterDiscoveryAttempt func( DriverBalancerClusterDiscoveryAttemptStartInfo, ) func( DriverBalancerClusterDiscoveryAttemptDoneInfo, ) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals OnBalancerUpdate func(DriverBalancerUpdateStartInfo) func(DriverBalancerUpdateDoneInfo) // Credentials events @@ -64,9 +89,11 @@ type ( ) // Method represents rpc method. +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals type Method string // Name returns the rpc method name. +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func (m Method) Name() (s string) { _, s = m.Split() @@ -74,6 +101,7 @@ func (m Method) Name() (s string) { } // Service returns the rpc service name. +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func (m Method) Service() (s string) { s, _ = m.Split() @@ -81,6 +109,7 @@ func (m Method) Service() (s string) { } // Issue declare interface of operation error issues +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals type Issue interface { GetMessage() string GetIssueCode() uint32 @@ -88,6 +117,7 @@ type Issue interface { } // Split returns service name and method. +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func (m Method) Split() (service, method string) { i := strings.LastIndex(string(m), "/") if i == -1 { @@ -97,6 +127,7 @@ func (m Method) Split() (service, method string) { return strings.TrimPrefix(string(m[:i]), "/"), string(m[i+1:]) } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals type ConnState interface { fmt.Stringer @@ -104,18 +135,25 @@ type ConnState interface { Code() int } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals type EndpointInfo interface { fmt.Stringer NodeID() uint32 Address() string - LocalDC() bool Location() string LoadFactor() float32 LastUpdated() time.Time + + // Deprecated: LocalDC check "local" by compare endpoint location with discovery "selflocation" field. + // It work good only if connection url always point to local dc. + // Will be removed after Oct 2024. + // Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated + LocalDC() bool } type ( + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DriverConnStateChangeStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -126,17 +164,21 @@ type ( Endpoint EndpointInfo State ConnState } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DriverConnStateChangeDoneInfo struct { State ConnState } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DriverResolveStartInfo struct { Call call Target string Resolved []string } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DriverResolveDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DriverBalancerUpdateStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -146,12 +188,14 @@ type ( Call call NeedLocalDC bool } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DriverBalancerUpdateDoneInfo struct { Endpoints []EndpointInfo Added []EndpointInfo Dropped []EndpointInfo LocalDC string } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DriverBalancerClusterDiscoveryAttemptStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -161,27 +205,33 @@ type ( Call call Address string } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DriverBalancerClusterDiscoveryAttemptDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DriverNetReadStartInfo struct { Call call Address string Buffer int } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DriverNetReadDoneInfo struct { Received int Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DriverNetWriteStartInfo struct { Call call Address string Bytes int } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DriverNetWriteDoneInfo struct { Sent int Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DriverNetDialStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -191,16 +241,20 @@ type ( Call call Address string } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DriverNetDialDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DriverNetCloseStartInfo struct { Call call Address string } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DriverNetCloseDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DriverConnTakeStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -210,9 +264,11 @@ type ( Call call Endpoint EndpointInfo } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DriverConnTakeDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DriverConnDialStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -222,9 +278,25 @@ type ( Call call Endpoint EndpointInfo } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DriverConnDialDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + DriverConnParkStartInfo struct { + // Context make available context in trace callback function. + // Pointer to context provide replacement of context in trace callback function. + // Warning: concurrent access to pointer on client side must be excluded. + // Safe replacement of context are provided only inside callback function + Context *context.Context + Call call + Endpoint EndpointInfo + } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + DriverConnParkDoneInfo struct { + Error error + } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DriverConnCloseStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -234,9 +306,11 @@ type ( Call call Endpoint EndpointInfo } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DriverConnCloseDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DriverConnBanStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -248,9 +322,11 @@ type ( State ConnState Cause error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DriverConnBanDoneInfo struct { State ConnState } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DriverConnAllowStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -261,9 +337,11 @@ type ( Endpoint EndpointInfo State ConnState } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DriverConnAllowDoneInfo struct { State ConnState } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DriverConnInvokeStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -274,6 +352,7 @@ type ( Endpoint EndpointInfo Method Method } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DriverConnInvokeDoneInfo struct { Error error Issues []Issue @@ -281,6 +360,7 @@ type ( State ConnState Metadata map[string][]string } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DriverConnNewStreamStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -291,10 +371,12 @@ type ( Endpoint EndpointInfo Method Method } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DriverConnNewStreamDoneInfo struct { Error error State ConnState } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DriverConnStreamRecvMsgStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -303,9 +385,11 @@ type ( Context *context.Context Call call } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DriverConnStreamRecvMsgDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DriverConnStreamSendMsgStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -314,9 +398,11 @@ type ( Context *context.Context Call call } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DriverConnStreamSendMsgDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DriverConnStreamCloseSendStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -325,9 +411,11 @@ type ( Context *context.Context Call call } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DriverConnStreamCloseSendDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DriverBalancerInitStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -337,9 +425,11 @@ type ( Call call Name string } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DriverBalancerInitDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DriverBalancerDialEntrypointStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -349,9 +439,11 @@ type ( Call call Address string } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DriverBalancerDialEntrypointDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DriverBalancerCloseStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -360,9 +452,11 @@ type ( Context *context.Context Call call } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DriverBalancerCloseDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DriverBalancerChooseEndpointStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -371,10 +465,12 @@ type ( Context *context.Context Call call } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DriverBalancerChooseEndpointDoneInfo struct { Endpoint EndpointInfo Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DriverRepeaterWakeUpStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -385,9 +481,11 @@ type ( Name string Event string } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DriverRepeaterWakeUpDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DriverGetCredentialsStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -396,10 +494,12 @@ type ( Context *context.Context Call call } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DriverGetCredentialsDoneInfo struct { Token string Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DriverInitStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -411,9 +511,11 @@ type ( Database string Secure bool } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DriverInitDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DriverWithStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -425,9 +527,11 @@ type ( Database string Secure bool } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DriverWithDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DriverConnPoolNewStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -436,7 +540,9 @@ type ( Context *context.Context Call call } - DriverConnPoolNewDoneInfo struct{} + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + DriverConnPoolNewDoneInfo struct{} + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DriverConnPoolReleaseStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -445,9 +551,11 @@ type ( Context *context.Context Call call } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DriverConnPoolReleaseDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DriverCloseStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -456,6 +564,7 @@ type ( Context *context.Context Call call } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DriverCloseDoneInfo struct { Error error } diff --git a/trace/driver_gtrace.go b/trace/driver_gtrace.go index bb2e951c0..50491225a 100644 --- a/trace/driver_gtrace.go +++ b/trace/driver_gtrace.go @@ -12,9 +12,11 @@ type driverComposeOptions struct { } // DriverOption specified Driver compose option +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals type DriverComposeOption func(o *driverComposeOptions) // WithDriverPanicCallback specified behavior on panic +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func WithDriverPanicCallback(cb func(e interface{})) DriverComposeOption { return func(o *driverComposeOptions) { o.panicCallback = cb @@ -22,6 +24,7 @@ func WithDriverPanicCallback(cb func(e interface{})) DriverComposeOption { } // Compose returns a new Driver which has functional fields composed both from t and x. +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func (t *Driver) Compose(x *Driver, opts ...DriverComposeOption) *Driver { var ret Driver options := driverComposeOptions{} @@ -555,6 +558,41 @@ func (t *Driver) Compose(x *Driver, opts ...DriverComposeOption) *Driver { } } } + { + h1 := t.OnConnPark + h2 := x.OnConnPark + ret.OnConnPark = func(d DriverConnParkStartInfo) func(DriverConnParkDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + var r, r1 func(DriverConnParkDoneInfo) + if h1 != nil { + r = h1(d) + } + if h2 != nil { + r1 = h2(d) + } + return func(d DriverConnParkDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + if r != nil { + r(d) + } + if r1 != nil { + r1(d) + } + } + } + } { h1 := t.OnConnClose h2 := x.OnConnClose @@ -1062,6 +1100,21 @@ func (t *Driver) onConnAllow(d DriverConnAllowStartInfo) func(DriverConnAllowDon } return res } +func (t *Driver) onConnPark(d DriverConnParkStartInfo) func(DriverConnParkDoneInfo) { + fn := t.OnConnPark + if fn == nil { + return func(DriverConnParkDoneInfo) { + return + } + } + res := fn(d) + if res == nil { + return func(DriverConnParkDoneInfo) { + return + } + } + return res +} func (t *Driver) onConnClose(d DriverConnCloseStartInfo) func(DriverConnCloseDoneInfo) { fn := t.OnConnClose if fn == nil { @@ -1182,6 +1235,7 @@ func (t *Driver) onGetCredentials(d DriverGetCredentialsStartInfo) func(DriverGe } return res } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func DriverOnInit(t *Driver, c *context.Context, call call, endpoint string, database string, secure bool) func(error) { var p DriverInitStartInfo p.Context = c @@ -1196,6 +1250,7 @@ func DriverOnInit(t *Driver, c *context.Context, call call, endpoint string, dat res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func DriverOnWith(t *Driver, c *context.Context, call call, endpoint string, database string, secure bool) func(error) { var p DriverWithStartInfo p.Context = c @@ -1210,6 +1265,7 @@ func DriverOnWith(t *Driver, c *context.Context, call call, endpoint string, dat res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func DriverOnClose(t *Driver, c *context.Context, call call) func(error) { var p DriverCloseStartInfo p.Context = c @@ -1221,6 +1277,7 @@ func DriverOnClose(t *Driver, c *context.Context, call call) func(error) { res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func DriverOnPoolNew(t *Driver, c *context.Context, call call) func() { var p DriverConnPoolNewStartInfo p.Context = c @@ -1231,6 +1288,7 @@ func DriverOnPoolNew(t *Driver, c *context.Context, call call) func() { res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func DriverOnPoolRelease(t *Driver, c *context.Context, call call) func(error) { var p DriverConnPoolReleaseStartInfo p.Context = c @@ -1242,6 +1300,7 @@ func DriverOnPoolRelease(t *Driver, c *context.Context, call call) func(error) { res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func DriverOnResolve(t *Driver, call call, target string, resolved []string) func(error) { var p DriverResolveStartInfo p.Call = call @@ -1254,6 +1313,7 @@ func DriverOnResolve(t *Driver, call call, target string, resolved []string) fun res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func DriverOnConnStateChange(t *Driver, c *context.Context, call call, endpoint EndpointInfo, state ConnState) func(state ConnState) { var p DriverConnStateChangeStartInfo p.Context = c @@ -1267,6 +1327,7 @@ func DriverOnConnStateChange(t *Driver, c *context.Context, call call, endpoint res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func DriverOnConnInvoke(t *Driver, c *context.Context, call call, endpoint EndpointInfo, m Method) func(_ error, issues []Issue, opID string, state ConnState, metadata map[string][]string) { var p DriverConnInvokeStartInfo p.Context = c @@ -1284,6 +1345,7 @@ func DriverOnConnInvoke(t *Driver, c *context.Context, call call, endpoint Endpo res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func DriverOnConnNewStream(t *Driver, c *context.Context, call call, endpoint EndpointInfo, m Method) func(_ error, state ConnState) { var p DriverConnNewStreamStartInfo p.Context = c @@ -1298,6 +1360,7 @@ func DriverOnConnNewStream(t *Driver, c *context.Context, call call, endpoint En res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func DriverOnConnStreamRecvMsg(t *Driver, c *context.Context, call call) func(error) { var p DriverConnStreamRecvMsgStartInfo p.Context = c @@ -1309,6 +1372,7 @@ func DriverOnConnStreamRecvMsg(t *Driver, c *context.Context, call call) func(er res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func DriverOnConnStreamSendMsg(t *Driver, c *context.Context, call call) func(error) { var p DriverConnStreamSendMsgStartInfo p.Context = c @@ -1320,6 +1384,7 @@ func DriverOnConnStreamSendMsg(t *Driver, c *context.Context, call call) func(er res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func DriverOnConnStreamCloseSend(t *Driver, c *context.Context, call call) func(error) { var p DriverConnStreamCloseSendStartInfo p.Context = c @@ -1331,6 +1396,7 @@ func DriverOnConnStreamCloseSend(t *Driver, c *context.Context, call call) func( res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func DriverOnConnDial(t *Driver, c *context.Context, call call, endpoint EndpointInfo) func(error) { var p DriverConnDialStartInfo p.Context = c @@ -1343,6 +1409,7 @@ func DriverOnConnDial(t *Driver, c *context.Context, call call, endpoint Endpoin res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func DriverOnConnBan(t *Driver, c *context.Context, call call, endpoint EndpointInfo, state ConnState, cause error) func(state ConnState) { var p DriverConnBanStartInfo p.Context = c @@ -1357,6 +1424,7 @@ func DriverOnConnBan(t *Driver, c *context.Context, call call, endpoint Endpoint res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func DriverOnConnAllow(t *Driver, c *context.Context, call call, endpoint EndpointInfo, state ConnState) func(state ConnState) { var p DriverConnAllowStartInfo p.Context = c @@ -1370,6 +1438,20 @@ func DriverOnConnAllow(t *Driver, c *context.Context, call call, endpoint Endpoi res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals +func DriverOnConnPark(t *Driver, c *context.Context, call call, endpoint EndpointInfo) func(error) { + var p DriverConnParkStartInfo + p.Context = c + p.Call = call + p.Endpoint = endpoint + res := t.onConnPark(p) + return func(e error) { + var p DriverConnParkDoneInfo + p.Error = e + res(p) + } +} +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func DriverOnConnClose(t *Driver, c *context.Context, call call, endpoint EndpointInfo) func(error) { var p DriverConnCloseStartInfo p.Context = c @@ -1382,6 +1464,7 @@ func DriverOnConnClose(t *Driver, c *context.Context, call call, endpoint Endpoi res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func DriverOnRepeaterWakeUp(t *Driver, c *context.Context, call call, name string, event string) func(error) { var p DriverRepeaterWakeUpStartInfo p.Context = c @@ -1395,6 +1478,7 @@ func DriverOnRepeaterWakeUp(t *Driver, c *context.Context, call call, name strin res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func DriverOnBalancerInit(t *Driver, c *context.Context, call call, name string) func(error) { var p DriverBalancerInitStartInfo p.Context = c @@ -1407,6 +1491,7 @@ func DriverOnBalancerInit(t *Driver, c *context.Context, call call, name string) res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func DriverOnBalancerClose(t *Driver, c *context.Context, call call) func(error) { var p DriverBalancerCloseStartInfo p.Context = c @@ -1418,6 +1503,7 @@ func DriverOnBalancerClose(t *Driver, c *context.Context, call call) func(error) res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func DriverOnBalancerChooseEndpoint(t *Driver, c *context.Context, call call) func(endpoint EndpointInfo, _ error) { var p DriverBalancerChooseEndpointStartInfo p.Context = c @@ -1430,6 +1516,7 @@ func DriverOnBalancerChooseEndpoint(t *Driver, c *context.Context, call call) fu res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func DriverOnBalancerClusterDiscoveryAttempt(t *Driver, c *context.Context, call call, address string) func(error) { var p DriverBalancerClusterDiscoveryAttemptStartInfo p.Context = c @@ -1442,6 +1529,7 @@ func DriverOnBalancerClusterDiscoveryAttempt(t *Driver, c *context.Context, call res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func DriverOnBalancerUpdate(t *Driver, c *context.Context, call call, needLocalDC bool) func(endpoints []EndpointInfo, added []EndpointInfo, dropped []EndpointInfo, localDC string) { var p DriverBalancerUpdateStartInfo p.Context = c @@ -1457,6 +1545,7 @@ func DriverOnBalancerUpdate(t *Driver, c *context.Context, call call, needLocalD res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func DriverOnGetCredentials(t *Driver, c *context.Context, call call) func(token string, _ error) { var p DriverGetCredentialsStartInfo p.Context = c diff --git a/trace/query.go b/trace/query.go index aefb54ddd..3fbaef522 100644 --- a/trace/query.go +++ b/trace/query.go @@ -20,25 +20,42 @@ type ( // Query specified trace of retry call activity. // gtrace:gen + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals Query struct { - OnNew func(QueryNewStartInfo) func(info QueryNewDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnNew func(QueryNewStartInfo) func(info QueryNewDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals OnClose func(QueryCloseStartInfo) func(info QueryCloseDoneInfo) - OnPoolNew func(QueryPoolNewStartInfo) func(QueryPoolNewDoneInfo) - OnPoolClose func(QueryPoolCloseStartInfo) func(QueryPoolCloseDoneInfo) - OnPoolTry func(QueryPoolTryStartInfo) func(QueryPoolTryDoneInfo) - OnPoolWith func(QueryPoolWithStartInfo) func(QueryPoolWithDoneInfo) - OnPoolPut func(QueryPoolPutStartInfo) func(QueryPoolPutDoneInfo) - OnPoolGet func(QueryPoolGetStartInfo) func(QueryPoolGetDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnPoolNew func(QueryPoolNewStartInfo) func(QueryPoolNewDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnPoolClose func(QueryPoolCloseStartInfo) func(QueryPoolCloseDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnPoolTry func(QueryPoolTryStartInfo) func(QueryPoolTryDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnPoolWith func(QueryPoolWithStartInfo) func(QueryPoolWithDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnPoolPut func(QueryPoolPutStartInfo) func(QueryPoolPutDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnPoolGet func(QueryPoolGetStartInfo) func(QueryPoolGetDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals OnPoolChange func(QueryPoolChange) - OnDo func(QueryDoStartInfo) func(QueryDoDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnDo func(QueryDoStartInfo) func(QueryDoDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals OnDoTx func(QueryDoTxStartInfo) func(QueryDoTxDoneInfo) - OnSessionCreate func(QuerySessionCreateStartInfo) func(info QuerySessionCreateDoneInfo) - OnSessionAttach func(QuerySessionAttachStartInfo) func(info QuerySessionAttachDoneInfo) - OnSessionDelete func(QuerySessionDeleteStartInfo) func(info QuerySessionDeleteDoneInfo) - OnSessionExecute func(QuerySessionExecuteStartInfo) func(info QuerySessionExecuteDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnSessionCreate func(QuerySessionCreateStartInfo) func(info QuerySessionCreateDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnSessionAttach func(QuerySessionAttachStartInfo) func(info QuerySessionAttachDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnSessionDelete func(QuerySessionDeleteStartInfo) func(info QuerySessionDeleteDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnSessionExecute func(QuerySessionExecuteStartInfo) func(info QuerySessionExecuteDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals OnSessionBegin func(QuerySessionBeginStartInfo) func(info QuerySessionBeginDoneInfo) OnTxExecute func(QueryTxExecuteStartInfo) func(info QueryTxExecuteDoneInfo) OnResultNew func(QueryResultNewStartInfo) func(info QueryResultNewDoneInfo) @@ -51,6 +68,7 @@ type ( OnRowScanStruct func(QueryRowScanStructStartInfo) func(info QueryRowScanStructDoneInfo) } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals QueryDoStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -59,10 +77,12 @@ type ( Context *context.Context Call call } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals QueryDoDoneInfo struct { Attempts int Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals QueryDoTxStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -71,10 +91,12 @@ type ( Context *context.Context Call call } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals QueryDoTxDoneInfo struct { Attempts int Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals QuerySessionCreateStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -83,10 +105,12 @@ type ( Context *context.Context Call call } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals QuerySessionCreateDoneInfo struct { Session querySessionInfo Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals QuerySessionExecuteStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -98,9 +122,11 @@ type ( Session querySessionInfo Query string } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals QuerySessionExecuteDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals QueryTxExecuteStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -113,9 +139,11 @@ type ( Tx queryTransactionInfo Query string } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals QueryTxExecuteDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals QuerySessionAttachStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -125,9 +153,11 @@ type ( Call call Session querySessionInfo } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals QuerySessionAttachDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals QuerySessionBeginStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -137,10 +167,12 @@ type ( Call call Session querySessionInfo } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals QuerySessionBeginDoneInfo struct { Error error Tx queryTransactionInfo } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals QueryResultNewStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -149,9 +181,11 @@ type ( Context *context.Context Call call } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals QueryResultNewDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals QueryResultCloseStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -160,9 +194,11 @@ type ( Context *context.Context Call call } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals QueryResultCloseDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals QueryResultNextPartStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -171,9 +207,11 @@ type ( Context *context.Context Call call } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals QueryResultNextPartDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals QueryResultNextResultSetStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -182,9 +220,11 @@ type ( Context *context.Context Call call } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals QueryResultNextResultSetDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals QueryResultSetNextRowStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -193,9 +233,11 @@ type ( Context *context.Context Call call } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals QueryResultSetNextRowDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals QueryRowScanStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -204,9 +246,11 @@ type ( Context *context.Context Call call } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals QueryRowScanDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals QueryRowScanNamedStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -215,9 +259,11 @@ type ( Context *context.Context Call call } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals QueryRowScanNamedDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals QueryRowScanStructStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -226,9 +272,11 @@ type ( Context *context.Context Call call } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals QueryRowScanStructDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals QuerySessionDeleteStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -238,9 +286,11 @@ type ( Call call Session querySessionInfo } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals QuerySessionDeleteDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals QueryNewStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -249,7 +299,9 @@ type ( Context *context.Context Call call } - QueryNewDoneInfo struct{} + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + QueryNewDoneInfo struct{} + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals QueryCloseStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -258,9 +310,11 @@ type ( Context *context.Context Call call } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals QueryCloseDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals QueryPoolNewStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -269,9 +323,11 @@ type ( Context *context.Context Call call } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals QueryPoolNewDoneInfo struct { Limit int } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals QueryPoolCloseStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -280,9 +336,11 @@ type ( Context *context.Context Call call } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals QueryPoolCloseDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals QueryPoolTryStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -291,9 +349,11 @@ type ( Context *context.Context Call call } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals QueryPoolTryDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals QueryPoolWithStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -302,11 +362,13 @@ type ( Context *context.Context Call call } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals QueryPoolWithDoneInfo struct { Error error Attempts int } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals QueryPoolPutStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -315,9 +377,11 @@ type ( Context *context.Context Call call } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals QueryPoolPutDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals QueryPoolGetStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -326,9 +390,11 @@ type ( Context *context.Context Call call } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals QueryPoolGetDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals QueryPoolChange struct { Limit int Index int diff --git a/trace/query_gtrace.go b/trace/query_gtrace.go index 2353b77b4..2bbc46f3c 100644 --- a/trace/query_gtrace.go +++ b/trace/query_gtrace.go @@ -12,9 +12,11 @@ type queryComposeOptions struct { } // QueryOption specified Query compose option +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals type QueryComposeOption func(o *queryComposeOptions) // WithQueryPanicCallback specified behavior on panic +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func WithQueryPanicCallback(cb func(e interface{})) QueryComposeOption { return func(o *queryComposeOptions) { o.panicCallback = cb @@ -22,6 +24,7 @@ func WithQueryPanicCallback(cb func(e interface{})) QueryComposeOption { } // Compose returns a new Query which has functional fields composed both from t and x. +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func (t *Query) Compose(x *Query, opts ...QueryComposeOption) *Query { var ret Query options := queryComposeOptions{} @@ -1258,6 +1261,7 @@ func (t *Query) onRowScanStruct(q QueryRowScanStructStartInfo) func(info QueryRo } return res } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func QueryOnNew(t *Query, c *context.Context, call call) func() { var p QueryNewStartInfo p.Context = c @@ -1268,6 +1272,7 @@ func QueryOnNew(t *Query, c *context.Context, call call) func() { res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func QueryOnClose(t *Query, c *context.Context, call call) func(error) { var p QueryCloseStartInfo p.Context = c @@ -1279,6 +1284,7 @@ func QueryOnClose(t *Query, c *context.Context, call call) func(error) { res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func QueryOnPoolNew(t *Query, c *context.Context, call call) func(limit int) { var p QueryPoolNewStartInfo p.Context = c @@ -1290,6 +1296,7 @@ func QueryOnPoolNew(t *Query, c *context.Context, call call) func(limit int) { res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func QueryOnPoolClose(t *Query, c *context.Context, call call) func(error) { var p QueryPoolCloseStartInfo p.Context = c @@ -1301,6 +1308,7 @@ func QueryOnPoolClose(t *Query, c *context.Context, call call) func(error) { res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func QueryOnPoolTry(t *Query, c *context.Context, call call) func(error) { var p QueryPoolTryStartInfo p.Context = c @@ -1312,6 +1320,7 @@ func QueryOnPoolTry(t *Query, c *context.Context, call call) func(error) { res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func QueryOnPoolWith(t *Query, c *context.Context, call call) func(_ error, attempts int) { var p QueryPoolWithStartInfo p.Context = c @@ -1324,6 +1333,7 @@ func QueryOnPoolWith(t *Query, c *context.Context, call call) func(_ error, atte res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func QueryOnPoolPut(t *Query, c *context.Context, call call) func(error) { var p QueryPoolPutStartInfo p.Context = c @@ -1335,6 +1345,7 @@ func QueryOnPoolPut(t *Query, c *context.Context, call call) func(error) { res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func QueryOnPoolGet(t *Query, c *context.Context, call call) func(error) { var p QueryPoolGetStartInfo p.Context = c @@ -1346,6 +1357,7 @@ func QueryOnPoolGet(t *Query, c *context.Context, call call) func(error) { res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func QueryOnPoolChange(t *Query, limit int, index int, idle int, inUse int) { var p QueryPoolChange p.Limit = limit @@ -1354,6 +1366,7 @@ func QueryOnPoolChange(t *Query, limit int, index int, idle int, inUse int) { p.InUse = inUse t.onPoolChange(p) } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func QueryOnDo(t *Query, c *context.Context, call call) func(attempts int, _ error) { var p QueryDoStartInfo p.Context = c @@ -1366,6 +1379,7 @@ func QueryOnDo(t *Query, c *context.Context, call call) func(attempts int, _ err res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func QueryOnDoTx(t *Query, c *context.Context, call call) func(attempts int, _ error) { var p QueryDoTxStartInfo p.Context = c @@ -1378,6 +1392,7 @@ func QueryOnDoTx(t *Query, c *context.Context, call call) func(attempts int, _ e res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func QueryOnSessionCreate(t *Query, c *context.Context, call call) func(session querySessionInfo, _ error) { var p QuerySessionCreateStartInfo p.Context = c @@ -1390,6 +1405,7 @@ func QueryOnSessionCreate(t *Query, c *context.Context, call call) func(session res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func QueryOnSessionAttach(t *Query, c *context.Context, call call, session querySessionInfo) func(error) { var p QuerySessionAttachStartInfo p.Context = c @@ -1402,6 +1418,7 @@ func QueryOnSessionAttach(t *Query, c *context.Context, call call, session query res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func QueryOnSessionDelete(t *Query, c *context.Context, call call, session querySessionInfo) func(error) { var p QuerySessionDeleteStartInfo p.Context = c @@ -1414,6 +1431,7 @@ func QueryOnSessionDelete(t *Query, c *context.Context, call call, session query res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func QueryOnSessionExecute(t *Query, c *context.Context, call call, session querySessionInfo, query string) func(error) { var p QuerySessionExecuteStartInfo p.Context = c @@ -1427,6 +1445,7 @@ func QueryOnSessionExecute(t *Query, c *context.Context, call call, session quer res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func QueryOnSessionBegin(t *Query, c *context.Context, call call, session querySessionInfo) func(_ error, tx queryTransactionInfo) { var p QuerySessionBeginStartInfo p.Context = c @@ -1440,6 +1459,7 @@ func QueryOnSessionBegin(t *Query, c *context.Context, call call, session queryS res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func QueryOnTxExecute(t *Query, c *context.Context, call call, session querySessionInfo, tx queryTransactionInfo, query string) func(error) { var p QueryTxExecuteStartInfo p.Context = c @@ -1454,6 +1474,7 @@ func QueryOnTxExecute(t *Query, c *context.Context, call call, session querySess res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func QueryOnResultNew(t *Query, c *context.Context, call call) func(error) { var p QueryResultNewStartInfo p.Context = c @@ -1465,6 +1486,7 @@ func QueryOnResultNew(t *Query, c *context.Context, call call) func(error) { res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func QueryOnResultNextPart(t *Query, c *context.Context, call call) func(error) { var p QueryResultNextPartStartInfo p.Context = c @@ -1476,6 +1498,7 @@ func QueryOnResultNextPart(t *Query, c *context.Context, call call) func(error) res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func QueryOnResultNextResultSet(t *Query, c *context.Context, call call) func(error) { var p QueryResultNextResultSetStartInfo p.Context = c @@ -1487,6 +1510,7 @@ func QueryOnResultNextResultSet(t *Query, c *context.Context, call call) func(er res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func QueryOnResultClose(t *Query, c *context.Context, call call) func(error) { var p QueryResultCloseStartInfo p.Context = c @@ -1498,6 +1522,7 @@ func QueryOnResultClose(t *Query, c *context.Context, call call) func(error) { res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func QueryOnResultSetNextRow(t *Query, c *context.Context, call call) func(error) { var p QueryResultSetNextRowStartInfo p.Context = c @@ -1509,6 +1534,7 @@ func QueryOnResultSetNextRow(t *Query, c *context.Context, call call) func(error res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func QueryOnRowScan(t *Query, c *context.Context, call call) func(error) { var p QueryRowScanStartInfo p.Context = c @@ -1520,6 +1546,7 @@ func QueryOnRowScan(t *Query, c *context.Context, call call) func(error) { res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func QueryOnRowScanNamed(t *Query, c *context.Context, call call) func(error) { var p QueryRowScanNamedStartInfo p.Context = c @@ -1531,6 +1558,7 @@ func QueryOnRowScanNamed(t *Query, c *context.Context, call call) func(error) { res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func QueryOnRowScanStruct(t *Query, c *context.Context, call call) func(error) { var p QueryRowScanStructStartInfo p.Context = c diff --git a/trace/ratelimiter.go b/trace/ratelimiter.go index 5aae16b93..6d411d13c 100644 --- a/trace/ratelimiter.go +++ b/trace/ratelimiter.go @@ -7,5 +7,6 @@ package trace type ( // Ratelimiter specified trace of ratelimiter client activity. // gtrace:gen + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals Ratelimiter struct{} ) diff --git a/trace/ratelimiter_gtrace.go b/trace/ratelimiter_gtrace.go index 015dba417..607e8a33d 100644 --- a/trace/ratelimiter_gtrace.go +++ b/trace/ratelimiter_gtrace.go @@ -8,9 +8,11 @@ type ratelimiterComposeOptions struct { } // RatelimiterOption specified Ratelimiter compose option +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals type RatelimiterComposeOption func(o *ratelimiterComposeOptions) // WithRatelimiterPanicCallback specified behavior on panic +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func WithRatelimiterPanicCallback(cb func(e interface{})) RatelimiterComposeOption { return func(o *ratelimiterComposeOptions) { o.panicCallback = cb @@ -18,6 +20,7 @@ func WithRatelimiterPanicCallback(cb func(e interface{})) RatelimiterComposeOpti } // Compose returns a new Ratelimiter which has functional fields composed both from t and x. +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func (t *Ratelimiter) Compose(x *Ratelimiter, opts ...RatelimiterComposeOption) *Ratelimiter { var ret Ratelimiter return &ret diff --git a/trace/retry.go b/trace/retry.go index 21307fe34..200ba783f 100644 --- a/trace/retry.go +++ b/trace/retry.go @@ -11,9 +11,12 @@ import ( type ( // Retry specified trace of retry call activity. // gtrace:gen + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals Retry struct { - OnRetry func(RetryLoopStartInfo) func(RetryLoopIntermediateInfo) func(RetryLoopDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnRetry func(RetryLoopStartInfo) func(RetryLoopDoneInfo) } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals RetryLoopStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -27,9 +30,7 @@ type ( NestedCall bool // a sign for detect Retry calls inside head Retry } - RetryLoopIntermediateInfo struct { - Error error - } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals RetryLoopDoneInfo struct { Attempts int Error error diff --git a/trace/retry_gtrace.go b/trace/retry_gtrace.go index c6fa15dd8..a8a6ba50e 100644 --- a/trace/retry_gtrace.go +++ b/trace/retry_gtrace.go @@ -12,9 +12,11 @@ type retryComposeOptions struct { } // RetryOption specified Retry compose option +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals type RetryComposeOption func(o *retryComposeOptions) // WithRetryPanicCallback specified behavior on panic +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func WithRetryPanicCallback(cb func(e interface{})) RetryComposeOption { return func(o *retryComposeOptions) { o.panicCallback = cb @@ -22,6 +24,7 @@ func WithRetryPanicCallback(cb func(e interface{})) RetryComposeOption { } // Compose returns a new Retry which has functional fields composed both from t and x. +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func (t *Retry) Compose(x *Retry, opts ...RetryComposeOption) *Retry { var ret Retry options := retryComposeOptions{} @@ -33,7 +36,7 @@ func (t *Retry) Compose(x *Retry, opts ...RetryComposeOption) *Retry { { h1 := t.OnRetry h2 := x.OnRetry - ret.OnRetry = func(r RetryLoopStartInfo) func(RetryLoopIntermediateInfo) func(RetryLoopDoneInfo) { + ret.OnRetry = func(r RetryLoopStartInfo) func(RetryLoopDoneInfo) { if options.panicCallback != nil { defer func() { if e := recover(); e != nil { @@ -41,14 +44,14 @@ func (t *Retry) Compose(x *Retry, opts ...RetryComposeOption) *Retry { } }() } - var r1, r2 func(RetryLoopIntermediateInfo) func(RetryLoopDoneInfo) + var r1, r2 func(RetryLoopDoneInfo) if h1 != nil { r1 = h1(r) } if h2 != nil { r2 = h2(r) } - return func(r RetryLoopIntermediateInfo) func(RetryLoopDoneInfo) { + return func(r RetryLoopDoneInfo) { if options.panicCallback != nil { defer func() { if e := recover(); e != nil { @@ -56,61 +59,34 @@ func (t *Retry) Compose(x *Retry, opts ...RetryComposeOption) *Retry { } }() } - var r3, r4 func(RetryLoopDoneInfo) if r1 != nil { - r3 = r1(r) + r1(r) } if r2 != nil { - r4 = r2(r) - } - return func(r RetryLoopDoneInfo) { - if options.panicCallback != nil { - defer func() { - if e := recover(); e != nil { - options.panicCallback(e) - } - }() - } - if r3 != nil { - r3(r) - } - if r4 != nil { - r4(r) - } + r2(r) } } } } return &ret } -func (t *Retry) onRetry(r RetryLoopStartInfo) func(RetryLoopIntermediateInfo) func(RetryLoopDoneInfo) { +func (t *Retry) onRetry(r RetryLoopStartInfo) func(RetryLoopDoneInfo) { fn := t.OnRetry if fn == nil { - return func(RetryLoopIntermediateInfo) func(RetryLoopDoneInfo) { - return func(RetryLoopDoneInfo) { - return - } + return func(RetryLoopDoneInfo) { + return } } res := fn(r) if res == nil { - return func(RetryLoopIntermediateInfo) func(RetryLoopDoneInfo) { - return func(RetryLoopDoneInfo) { - return - } + return func(RetryLoopDoneInfo) { + return } } - return func(r RetryLoopIntermediateInfo) func(RetryLoopDoneInfo) { - res := res(r) - if res == nil { - return func(RetryLoopDoneInfo) { - return - } - } - return res - } + return res } -func RetryOnRetry(t *Retry, c *context.Context, call call, label string, idempotent bool, nestedCall bool) func(error) func(attempts int, _ error) { +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals +func RetryOnRetry(t *Retry, c *context.Context, call call, label string, idempotent bool, nestedCall bool) func(attempts int, _ error) { var p RetryLoopStartInfo p.Context = c p.Call = call @@ -118,15 +94,10 @@ func RetryOnRetry(t *Retry, c *context.Context, call call, label string, idempot p.Idempotent = idempotent p.NestedCall = nestedCall res := t.onRetry(p) - return func(e error) func(int, error) { - var p RetryLoopIntermediateInfo + return func(attempts int, e error) { + var p RetryLoopDoneInfo + p.Attempts = attempts p.Error = e - res := res(p) - return func(attempts int, e error) { - var p RetryLoopDoneInfo - p.Attempts = attempts - p.Error = e - res(p) - } + res(p) } } diff --git a/trace/scheme.go b/trace/scheme.go index 2f4c6f48b..ed70a527e 100644 --- a/trace/scheme.go +++ b/trace/scheme.go @@ -11,14 +11,21 @@ import ( type ( // Scheme specified trace of scheme client activity. // gtrace:gen + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals Scheme struct { - OnListDirectory func(SchemeListDirectoryStartInfo) func(SchemeListDirectoryDoneInfo) - OnDescribePath func(SchemeDescribePathStartInfo) func(SchemeDescribePathDoneInfo) - OnMakeDirectory func(SchemeMakeDirectoryStartInfo) func(SchemeMakeDirectoryDoneInfo) - OnRemoveDirectory func(SchemeRemoveDirectoryStartInfo) func(SchemeRemoveDirectoryDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnListDirectory func(SchemeListDirectoryStartInfo) func(SchemeListDirectoryDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnDescribePath func(SchemeDescribePathStartInfo) func(SchemeDescribePathDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnMakeDirectory func(SchemeMakeDirectoryStartInfo) func(SchemeMakeDirectoryDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnRemoveDirectory func(SchemeRemoveDirectoryStartInfo) func(SchemeRemoveDirectoryDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals OnModifyPermissions func(SchemeModifyPermissionsStartInfo) func(SchemeModifyPermissionsDoneInfo) } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals SchemeListDirectoryStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -27,9 +34,11 @@ type ( Context *context.Context Call call } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals SchemeListDirectoryDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals SchemeDescribePathStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -39,10 +48,12 @@ type ( Call call Path string } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals SchemeDescribePathDoneInfo struct { EntryType string Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals SchemeMakeDirectoryStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -52,9 +63,11 @@ type ( Call call Path string } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals SchemeMakeDirectoryDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals SchemeRemoveDirectoryStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -64,9 +77,11 @@ type ( Call call Path string } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals SchemeRemoveDirectoryDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals SchemeModifyPermissionsStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -76,6 +91,7 @@ type ( Call call Path string } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals SchemeModifyPermissionsDoneInfo struct { Error error } diff --git a/trace/scheme_gtrace.go b/trace/scheme_gtrace.go index 0001d9e17..e960696ca 100644 --- a/trace/scheme_gtrace.go +++ b/trace/scheme_gtrace.go @@ -12,9 +12,11 @@ type schemeComposeOptions struct { } // SchemeOption specified Scheme compose option +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals type SchemeComposeOption func(o *schemeComposeOptions) // WithSchemePanicCallback specified behavior on panic +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func WithSchemePanicCallback(cb func(e interface{})) SchemeComposeOption { return func(o *schemeComposeOptions) { o.panicCallback = cb @@ -22,6 +24,7 @@ func WithSchemePanicCallback(cb func(e interface{})) SchemeComposeOption { } // Compose returns a new Scheme which has functional fields composed both from t and x. +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func (t *Scheme) Compose(x *Scheme, opts ...SchemeComposeOption) *Scheme { var ret Scheme options := schemeComposeOptions{} @@ -282,6 +285,7 @@ func (t *Scheme) onModifyPermissions(s SchemeModifyPermissionsStartInfo) func(Sc } return res } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func SchemeOnListDirectory(t *Scheme, c *context.Context, call call) func(error) { var p SchemeListDirectoryStartInfo p.Context = c @@ -293,6 +297,7 @@ func SchemeOnListDirectory(t *Scheme, c *context.Context, call call) func(error) res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func SchemeOnDescribePath(t *Scheme, c *context.Context, call call, path string) func(entryType string, _ error) { var p SchemeDescribePathStartInfo p.Context = c @@ -306,6 +311,7 @@ func SchemeOnDescribePath(t *Scheme, c *context.Context, call call, path string) res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func SchemeOnMakeDirectory(t *Scheme, c *context.Context, call call, path string) func(error) { var p SchemeMakeDirectoryStartInfo p.Context = c @@ -318,6 +324,7 @@ func SchemeOnMakeDirectory(t *Scheme, c *context.Context, call call, path string res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func SchemeOnRemoveDirectory(t *Scheme, c *context.Context, call call, path string) func(error) { var p SchemeRemoveDirectoryStartInfo p.Context = c @@ -330,6 +337,7 @@ func SchemeOnRemoveDirectory(t *Scheme, c *context.Context, call call, path stri res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func SchemeOnModifyPermissions(t *Scheme, c *context.Context, call call, path string) func(error) { var p SchemeModifyPermissionsStartInfo p.Context = c diff --git a/trace/scripting.go b/trace/scripting.go index 4e018e07d..51f672d79 100644 --- a/trace/scripting.go +++ b/trace/scripting.go @@ -11,8 +11,11 @@ import ( type ( // Scripting specified trace of scripting client activity. // gtrace:gen + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals Scripting struct { - OnExecute func(ScriptingExecuteStartInfo) func(ScriptingExecuteDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnExecute func(ScriptingExecuteStartInfo) func(ScriptingExecuteDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals OnStreamExecute func( ScriptingStreamExecuteStartInfo, ) func( @@ -20,8 +23,10 @@ type ( ) func( ScriptingStreamExecuteDoneInfo, ) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals OnExplain func(ScriptingExplainStartInfo) func(ScriptingExplainDoneInfo) - OnClose func(ScriptingCloseStartInfo) func(ScriptingCloseDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnClose func(ScriptingCloseStartInfo) func(ScriptingCloseDoneInfo) } scriptingQueryParameters interface { String() string @@ -33,6 +38,7 @@ type ( scriptingResultErr ResultSetCount() int } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals ScriptingExecuteStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -43,10 +49,12 @@ type ( Query string Parameters scriptingQueryParameters } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals ScriptingExecuteDoneInfo struct { Result scriptingResult Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals ScriptingStreamExecuteStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -57,12 +65,15 @@ type ( Query string Parameters scriptingQueryParameters } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals ScriptingStreamExecuteIntermediateInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals ScriptingStreamExecuteDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals ScriptingExplainStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -72,10 +83,12 @@ type ( Call call Query string } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals ScriptingExplainDoneInfo struct { Plan string Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals ScriptingCloseStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -84,6 +97,7 @@ type ( Context *context.Context Call call } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals ScriptingCloseDoneInfo struct { Error error } diff --git a/trace/scripting_gtrace.go b/trace/scripting_gtrace.go index 8af5fd07e..c3de7b27f 100644 --- a/trace/scripting_gtrace.go +++ b/trace/scripting_gtrace.go @@ -12,9 +12,11 @@ type scriptingComposeOptions struct { } // ScriptingOption specified Scripting compose option +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals type ScriptingComposeOption func(o *scriptingComposeOptions) // WithScriptingPanicCallback specified behavior on panic +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func WithScriptingPanicCallback(cb func(e interface{})) ScriptingComposeOption { return func(o *scriptingComposeOptions) { o.panicCallback = cb @@ -22,6 +24,7 @@ func WithScriptingPanicCallback(cb func(e interface{})) ScriptingComposeOption { } // Compose returns a new Scripting which has functional fields composed both from t and x. +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func (t *Scripting) Compose(x *Scripting, opts ...ScriptingComposeOption) *Scripting { var ret Scripting options := scriptingComposeOptions{} @@ -260,6 +263,7 @@ func (t *Scripting) onClose(s ScriptingCloseStartInfo) func(ScriptingCloseDoneIn } return res } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func ScriptingOnExecute(t *Scripting, c *context.Context, call call, query string, parameters scriptingQueryParameters) func(result scriptingResult, _ error) { var p ScriptingExecuteStartInfo p.Context = c @@ -274,6 +278,7 @@ func ScriptingOnExecute(t *Scripting, c *context.Context, call call, query strin res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func ScriptingOnStreamExecute(t *Scripting, c *context.Context, call call, query string, parameters scriptingQueryParameters) func(error) func(error) { var p ScriptingStreamExecuteStartInfo p.Context = c @@ -292,6 +297,7 @@ func ScriptingOnStreamExecute(t *Scripting, c *context.Context, call call, query } } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func ScriptingOnExplain(t *Scripting, c *context.Context, call call, query string) func(plan string, _ error) { var p ScriptingExplainStartInfo p.Context = c @@ -305,6 +311,7 @@ func ScriptingOnExplain(t *Scripting, c *context.Context, call call, query strin res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func ScriptingOnClose(t *Scripting, c *context.Context, call call) func(error) { var p ScriptingCloseStartInfo p.Context = c diff --git a/trace/sql.go b/trace/sql.go index e6d5de6ec..9ee597a39 100644 --- a/trace/sql.go +++ b/trace/sql.go @@ -10,30 +10,49 @@ import ( type ( // DatabaseSQL specified trace of `database/sql` call activity. // gtrace:gen + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DatabaseSQL struct { + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals OnConnectorConnect func(DatabaseSQLConnectorConnectStartInfo) func(DatabaseSQLConnectorConnectDoneInfo) - OnConnPing func(DatabaseSQLConnPingStartInfo) func(DatabaseSQLConnPingDoneInfo) - OnConnPrepare func(DatabaseSQLConnPrepareStartInfo) func(DatabaseSQLConnPrepareDoneInfo) - OnConnClose func(DatabaseSQLConnCloseStartInfo) func(DatabaseSQLConnCloseDoneInfo) - OnConnBegin func(DatabaseSQLConnBeginStartInfo) func(DatabaseSQLConnBeginDoneInfo) - OnConnQuery func(DatabaseSQLConnQueryStartInfo) func(DatabaseSQLConnQueryDoneInfo) - OnConnExec func(DatabaseSQLConnExecStartInfo) func(DatabaseSQLConnExecDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnConnPing func(DatabaseSQLConnPingStartInfo) func(DatabaseSQLConnPingDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnConnPrepare func(DatabaseSQLConnPrepareStartInfo) func(DatabaseSQLConnPrepareDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnConnClose func(DatabaseSQLConnCloseStartInfo) func(DatabaseSQLConnCloseDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnConnBegin func(DatabaseSQLConnBeginStartInfo) func(DatabaseSQLConnBeginDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnConnQuery func(DatabaseSQLConnQueryStartInfo) func(DatabaseSQLConnQueryDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnConnExec func(DatabaseSQLConnExecStartInfo) func(DatabaseSQLConnExecDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals OnConnIsTableExists func(DatabaseSQLConnIsTableExistsStartInfo) func(DatabaseSQLConnIsTableExistsDoneInfo) - OnTxQuery func(DatabaseSQLTxQueryStartInfo) func(DatabaseSQLTxQueryDoneInfo) - OnTxExec func(DatabaseSQLTxExecStartInfo) func(DatabaseSQLTxExecDoneInfo) - OnTxPrepare func(DatabaseSQLTxPrepareStartInfo) func(DatabaseSQLTxPrepareDoneInfo) - OnTxCommit func(DatabaseSQLTxCommitStartInfo) func(DatabaseSQLTxCommitDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnTxQuery func(DatabaseSQLTxQueryStartInfo) func(DatabaseSQLTxQueryDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnTxExec func(DatabaseSQLTxExecStartInfo) func(DatabaseSQLTxExecDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnTxPrepare func(DatabaseSQLTxPrepareStartInfo) func(DatabaseSQLTxPrepareDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnTxCommit func(DatabaseSQLTxCommitStartInfo) func(DatabaseSQLTxCommitDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals OnTxRollback func(DatabaseSQLTxRollbackStartInfo) func(DatabaseSQLTxRollbackDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals OnStmtQuery func(DatabaseSQLStmtQueryStartInfo) func(DatabaseSQLStmtQueryDoneInfo) - OnStmtExec func(DatabaseSQLStmtExecStartInfo) func(DatabaseSQLStmtExecDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnStmtExec func(DatabaseSQLStmtExecStartInfo) func(DatabaseSQLStmtExecDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals OnStmtClose func(DatabaseSQLStmtCloseStartInfo) func(DatabaseSQLStmtCloseDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals OnDoTx func(DatabaseSQLDoTxStartInfo) func(DatabaseSQLDoTxIntermediateInfo) func(DatabaseSQLDoTxDoneInfo) } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DatabaseSQLConnectorConnectStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -42,10 +61,12 @@ type ( Context *context.Context Call call } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DatabaseSQLConnectorConnectDoneInfo struct { Error error Session tableSessionInfo } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DatabaseSQLConnPingStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -54,9 +75,11 @@ type ( Context *context.Context Call call } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DatabaseSQLConnPingDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DatabaseSQLConnPrepareStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -66,9 +89,11 @@ type ( Call call Query string } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DatabaseSQLConnPrepareDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DatabaseSQLTxPrepareStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -80,9 +105,11 @@ type ( Tx tableTransactionInfo Query string } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DatabaseSQLTxPrepareDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DatabaseSQLConnCloseStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -91,9 +118,11 @@ type ( Context *context.Context Call call } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DatabaseSQLConnCloseDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DatabaseSQLConnBeginStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -102,10 +131,12 @@ type ( Context *context.Context Call call } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DatabaseSQLConnBeginDoneInfo struct { Tx tableTransactionInfo Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DatabaseSQLConnQueryStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -118,9 +149,11 @@ type ( Idempotent bool IdleTime time.Duration } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DatabaseSQLConnQueryDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DatabaseSQLConnExecStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -133,9 +166,11 @@ type ( Idempotent bool IdleTime time.Duration } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DatabaseSQLConnExecDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DatabaseSQLConnIsTableExistsStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -145,10 +180,12 @@ type ( Call call TableName string } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DatabaseSQLConnIsTableExistsDoneInfo struct { Exists bool Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DatabaseSQLTxQueryStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -160,9 +197,11 @@ type ( Tx tableTransactionInfo Query string } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DatabaseSQLTxQueryDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DatabaseSQLTxExecStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -174,9 +213,11 @@ type ( Tx tableTransactionInfo Query string } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DatabaseSQLTxExecDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DatabaseSQLTxCommitStartInfo struct { // TxContext make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -186,9 +227,11 @@ type ( Call call Tx tableTransactionInfo } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DatabaseSQLTxCommitDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DatabaseSQLTxRollbackStartInfo struct { // TxContext make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -198,16 +241,20 @@ type ( Call call Tx tableTransactionInfo } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DatabaseSQLTxRollbackDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DatabaseSQLStmtCloseStartInfo struct { StmtContext *context.Context Call call } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DatabaseSQLStmtCloseDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DatabaseSQLStmtQueryStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -218,9 +265,11 @@ type ( StmtContext context.Context Query string } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DatabaseSQLStmtQueryDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DatabaseSQLStmtExecStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -231,9 +280,11 @@ type ( StmtContext context.Context Query string } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DatabaseSQLStmtExecDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DatabaseSQLDoTxStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -244,9 +295,11 @@ type ( ID string Idempotent bool } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DatabaseSQLDoTxIntermediateInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals DatabaseSQLDoTxDoneInfo struct { Attempts int Error error diff --git a/trace/sql_gtrace.go b/trace/sql_gtrace.go index e5d2a484f..c4a686e1d 100644 --- a/trace/sql_gtrace.go +++ b/trace/sql_gtrace.go @@ -13,9 +13,11 @@ type databaseSQLComposeOptions struct { } // DatabaseSQLOption specified DatabaseSQL compose option +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals type DatabaseSQLComposeOption func(o *databaseSQLComposeOptions) // WithDatabaseSQLPanicCallback specified behavior on panic +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func WithDatabaseSQLPanicCallback(cb func(e interface{})) DatabaseSQLComposeOption { return func(o *databaseSQLComposeOptions) { o.panicCallback = cb @@ -23,6 +25,7 @@ func WithDatabaseSQLPanicCallback(cb func(e interface{})) DatabaseSQLComposeOpti } // Compose returns a new DatabaseSQL which has functional fields composed both from t and x. +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func (t *DatabaseSQL) Compose(x *DatabaseSQL, opts ...DatabaseSQLComposeOption) *DatabaseSQL { var ret DatabaseSQL options := databaseSQLComposeOptions{} @@ -911,6 +914,7 @@ func (t *DatabaseSQL) onDoTx(d DatabaseSQLDoTxStartInfo) func(DatabaseSQLDoTxInt return res } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func DatabaseSQLOnConnectorConnect(t *DatabaseSQL, c *context.Context, call call) func(_ error, session tableSessionInfo) { var p DatabaseSQLConnectorConnectStartInfo p.Context = c @@ -923,6 +927,7 @@ func DatabaseSQLOnConnectorConnect(t *DatabaseSQL, c *context.Context, call call res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func DatabaseSQLOnConnPing(t *DatabaseSQL, c *context.Context, call call) func(error) { var p DatabaseSQLConnPingStartInfo p.Context = c @@ -934,6 +939,7 @@ func DatabaseSQLOnConnPing(t *DatabaseSQL, c *context.Context, call call) func(e res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func DatabaseSQLOnConnPrepare(t *DatabaseSQL, c *context.Context, call call, query string) func(error) { var p DatabaseSQLConnPrepareStartInfo p.Context = c @@ -946,6 +952,7 @@ func DatabaseSQLOnConnPrepare(t *DatabaseSQL, c *context.Context, call call, que res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func DatabaseSQLOnConnClose(t *DatabaseSQL, c *context.Context, call call) func(error) { var p DatabaseSQLConnCloseStartInfo p.Context = c @@ -957,6 +964,7 @@ func DatabaseSQLOnConnClose(t *DatabaseSQL, c *context.Context, call call) func( res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func DatabaseSQLOnConnBegin(t *DatabaseSQL, c *context.Context, call call) func(tx tableTransactionInfo, _ error) { var p DatabaseSQLConnBeginStartInfo p.Context = c @@ -969,6 +977,7 @@ func DatabaseSQLOnConnBegin(t *DatabaseSQL, c *context.Context, call call) func( res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func DatabaseSQLOnConnQuery(t *DatabaseSQL, c *context.Context, call call, query string, mode string, idempotent bool, idleTime time.Duration) func(error) { var p DatabaseSQLConnQueryStartInfo p.Context = c @@ -984,6 +993,7 @@ func DatabaseSQLOnConnQuery(t *DatabaseSQL, c *context.Context, call call, query res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func DatabaseSQLOnConnExec(t *DatabaseSQL, c *context.Context, call call, query string, mode string, idempotent bool, idleTime time.Duration) func(error) { var p DatabaseSQLConnExecStartInfo p.Context = c @@ -999,6 +1009,7 @@ func DatabaseSQLOnConnExec(t *DatabaseSQL, c *context.Context, call call, query res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func DatabaseSQLOnConnIsTableExists(t *DatabaseSQL, c *context.Context, call call, tableName string) func(exists bool, _ error) { var p DatabaseSQLConnIsTableExistsStartInfo p.Context = c @@ -1012,6 +1023,7 @@ func DatabaseSQLOnConnIsTableExists(t *DatabaseSQL, c *context.Context, call cal res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func DatabaseSQLOnTxQuery(t *DatabaseSQL, c *context.Context, call call, txContext context.Context, tx tableTransactionInfo, query string) func(error) { var p DatabaseSQLTxQueryStartInfo p.Context = c @@ -1026,6 +1038,7 @@ func DatabaseSQLOnTxQuery(t *DatabaseSQL, c *context.Context, call call, txConte res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func DatabaseSQLOnTxExec(t *DatabaseSQL, c *context.Context, call call, txContext context.Context, tx tableTransactionInfo, query string) func(error) { var p DatabaseSQLTxExecStartInfo p.Context = c @@ -1040,6 +1053,7 @@ func DatabaseSQLOnTxExec(t *DatabaseSQL, c *context.Context, call call, txContex res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func DatabaseSQLOnTxPrepare(t *DatabaseSQL, c *context.Context, call call, txContext *context.Context, tx tableTransactionInfo, query string) func(error) { var p DatabaseSQLTxPrepareStartInfo p.Context = c @@ -1054,6 +1068,7 @@ func DatabaseSQLOnTxPrepare(t *DatabaseSQL, c *context.Context, call call, txCon res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func DatabaseSQLOnTxCommit(t *DatabaseSQL, c *context.Context, call call, tx tableTransactionInfo) func(error) { var p DatabaseSQLTxCommitStartInfo p.Context = c @@ -1066,6 +1081,7 @@ func DatabaseSQLOnTxCommit(t *DatabaseSQL, c *context.Context, call call, tx tab res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func DatabaseSQLOnTxRollback(t *DatabaseSQL, c *context.Context, call call, tx tableTransactionInfo) func(error) { var p DatabaseSQLTxRollbackStartInfo p.Context = c @@ -1078,6 +1094,7 @@ func DatabaseSQLOnTxRollback(t *DatabaseSQL, c *context.Context, call call, tx t res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func DatabaseSQLOnStmtQuery(t *DatabaseSQL, c *context.Context, call call, stmtContext context.Context, query string) func(error) { var p DatabaseSQLStmtQueryStartInfo p.Context = c @@ -1091,6 +1108,7 @@ func DatabaseSQLOnStmtQuery(t *DatabaseSQL, c *context.Context, call call, stmtC res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func DatabaseSQLOnStmtExec(t *DatabaseSQL, c *context.Context, call call, stmtContext context.Context, query string) func(error) { var p DatabaseSQLStmtExecStartInfo p.Context = c @@ -1104,6 +1122,7 @@ func DatabaseSQLOnStmtExec(t *DatabaseSQL, c *context.Context, call call, stmtCo res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func DatabaseSQLOnStmtClose(t *DatabaseSQL, stmtContext *context.Context, call call) func(error) { var p DatabaseSQLStmtCloseStartInfo p.StmtContext = stmtContext @@ -1115,6 +1134,7 @@ func DatabaseSQLOnStmtClose(t *DatabaseSQL, stmtContext *context.Context, call c res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func DatabaseSQLOnDoTx(t *DatabaseSQL, c *context.Context, call call, iD string, idempotent bool) func(error) func(attempts int, _ error) { var p DatabaseSQLDoTxStartInfo p.Context = c diff --git a/trace/table.go b/trace/table.go index 59d4f2791..d8f0622e7 100644 --- a/trace/table.go +++ b/trace/table.go @@ -12,69 +12,75 @@ import ( type ( // Table specified trace of table client activity. // gtrace:gen + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals Table struct { // Client events - OnInit func(TableInitStartInfo) func(TableInitDoneInfo) - OnClose func(TableCloseStartInfo) func(TableCloseDoneInfo) - OnDo func(TableDoStartInfo) func(info TableDoIntermediateInfo) func(TableDoDoneInfo) - OnDoTx func(TableDoTxStartInfo) func(info TableDoTxIntermediateInfo) func(TableDoTxDoneInfo) - OnCreateSession func( - TableCreateSessionStartInfo, - ) func( - info TableCreateSessionIntermediateInfo, - ) func( - TableCreateSessionDoneInfo, - ) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnInit func(TableInitStartInfo) func(TableInitDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnClose func(TableCloseStartInfo) func(TableCloseDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnDo func(TableDoStartInfo) func(TableDoDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnDoTx func(TableDoTxStartInfo) func(TableDoTxDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnCreateSession func(TableCreateSessionStartInfo) func(TableCreateSessionDoneInfo) // Session events - OnSessionNew func(TableSessionNewStartInfo) func(TableSessionNewDoneInfo) - OnSessionDelete func(TableSessionDeleteStartInfo) func(TableSessionDeleteDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnSessionNew func(TableSessionNewStartInfo) func(TableSessionNewDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnSessionDelete func(TableSessionDeleteStartInfo) func(TableSessionDeleteDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals OnSessionKeepAlive func(TableKeepAliveStartInfo) func(TableKeepAliveDoneInfo) // Query events - OnSessionBulkUpsert func(TableBulkUpsertStartInfo) func(TableBulkUpsertDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnSessionBulkUpsert func(TableBulkUpsertStartInfo) func(TableBulkUpsertDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals OnSessionQueryPrepare func(TablePrepareDataQueryStartInfo) func(TablePrepareDataQueryDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals OnSessionQueryExecute func(TableExecuteDataQueryStartInfo) func(TableExecuteDataQueryDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals OnSessionQueryExplain func(TableExplainQueryStartInfo) func(TableExplainQueryDoneInfo) // Stream events - OnSessionQueryStreamExecute func( - TableSessionQueryStreamExecuteStartInfo, - ) func( - TableSessionQueryStreamExecuteIntermediateInfo, - ) func( - TableSessionQueryStreamExecuteDoneInfo, - ) - OnSessionQueryStreamRead func( - TableSessionQueryStreamReadStartInfo, - ) func( - TableSessionQueryStreamReadIntermediateInfo, - ) func( - TableSessionQueryStreamReadDoneInfo, - ) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnSessionQueryStreamExecute func(TableSessionQueryStreamExecuteStartInfo) func(TableSessionQueryStreamExecuteDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnSessionQueryStreamRead func(TableSessionQueryStreamReadStartInfo) func(TableSessionQueryStreamReadDoneInfo) // Transaction events - OnSessionTransactionBegin func(TableSessionTransactionBeginStartInfo) func( - TableSessionTransactionBeginDoneInfo, + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnTxBegin func(TableTxBeginStartInfo) func( + TableTxBeginDoneInfo, ) - OnSessionTransactionExecute func(TableTransactionExecuteStartInfo) func( + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnTxExecute func(TableTransactionExecuteStartInfo) func( TableTransactionExecuteDoneInfo, ) - OnSessionTransactionExecuteStatement func(TableTransactionExecuteStatementStartInfo) func( + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnTxExecuteStatement func(TableTransactionExecuteStatementStartInfo) func( TableTransactionExecuteStatementDoneInfo, ) - OnSessionTransactionCommit func(TableSessionTransactionCommitStartInfo) func( - TableSessionTransactionCommitDoneInfo, - ) - OnSessionTransactionRollback func(TableSessionTransactionRollbackStartInfo) func( - TableSessionTransactionRollbackDoneInfo, + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnTxCommit func(TableTxCommitStartInfo) func( + TableTxCommitDoneInfo, ) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnTxRollback func(TableTxRollbackStartInfo) func(TableTxRollbackDoneInfo) // Pool state event + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals OnPoolStateChange func(TablePoolStateChangeInfo) // Pool session lifecycle events - OnPoolSessionAdd func(info TablePoolSessionAddInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnPoolSessionAdd func(info TablePoolSessionAddInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals OnPoolSessionRemove func(info TablePoolSessionRemoveInfo) // Pool common API events - OnPoolPut func(TablePoolPutStartInfo) func(TablePoolPutDoneInfo) - OnPoolGet func(TablePoolGetStartInfo) func(TablePoolGetDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnPoolPut func(TablePoolPutStartInfo) func(TablePoolPutDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnPoolGet func(TablePoolGetStartInfo) func(TablePoolGetDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals OnPoolWait func(TablePoolWaitStartInfo) func(TablePoolWaitDoneInfo) } ) @@ -104,6 +110,7 @@ type ( tableResultErr ResultSetCount() int } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TableSessionNewStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -112,10 +119,12 @@ type ( Context *context.Context Call call } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TableSessionNewDoneInfo struct { Session tableSessionInfo Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TableKeepAliveStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -125,9 +134,11 @@ type ( Call call Session tableSessionInfo } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TableKeepAliveDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TableBulkUpsertStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -137,9 +148,11 @@ type ( Call call Session tableSessionInfo } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TableBulkUpsertDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TableSessionDeleteStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -149,9 +162,11 @@ type ( Call call Session tableSessionInfo } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TableSessionDeleteDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TablePrepareDataQueryStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -162,10 +177,12 @@ type ( Session tableSessionInfo Query string } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TablePrepareDataQueryDoneInfo struct { Result tableDataQuery Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TableExecuteDataQueryStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -178,6 +195,7 @@ type ( Parameters tableQueryParameters KeepInCache bool } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TableTransactionExecuteStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -190,6 +208,7 @@ type ( Query tableDataQuery Parameters tableQueryParameters } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TableTransactionExecuteStatementStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -202,6 +221,7 @@ type ( StatementQuery tableDataQuery Parameters tableQueryParameters } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TableExplainQueryStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -212,25 +232,30 @@ type ( Session tableSessionInfo Query string } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TableExplainQueryDoneInfo struct { AST string Plan string Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TableExecuteDataQueryDoneInfo struct { Tx tableTransactionInfo Prepared bool Result tableResult Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TableTransactionExecuteDoneInfo struct { Result tableResult Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TableTransactionExecuteStatementDoneInfo struct { Result tableResult Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TableSessionQueryStreamReadStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -240,12 +265,11 @@ type ( Call call Session tableSessionInfo } - TableSessionQueryStreamReadIntermediateInfo struct { - Error error - } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TableSessionQueryStreamReadDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TableSessionQueryStreamExecuteStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -257,13 +281,12 @@ type ( Query tableDataQuery Parameters tableQueryParameters } - TableSessionQueryStreamExecuteIntermediateInfo struct { - Error error - } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TableSessionQueryStreamExecuteDoneInfo struct { Error error } - TableSessionTransactionBeginStartInfo struct { + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + TableTxBeginStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. // Warning: concurrent access to pointer on client side must be excluded. @@ -272,11 +295,13 @@ type ( Call call Session tableSessionInfo } - TableSessionTransactionBeginDoneInfo struct { + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + TableTxBeginDoneInfo struct { Tx tableTransactionInfo Error error } - TableSessionTransactionCommitStartInfo struct { + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + TableTxCommitStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. // Warning: concurrent access to pointer on client side must be excluded. @@ -286,10 +311,12 @@ type ( Session tableSessionInfo Tx tableTransactionInfo } - TableSessionTransactionCommitDoneInfo struct { + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + TableTxCommitDoneInfo struct { Error error } - TableSessionTransactionRollbackStartInfo struct { + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + TableTxRollbackStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. // Warning: concurrent access to pointer on client side must be excluded. @@ -299,9 +326,11 @@ type ( Session tableSessionInfo Tx tableTransactionInfo } - TableSessionTransactionRollbackDoneInfo struct { + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + TableTxRollbackDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TableInitStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -310,13 +339,16 @@ type ( Context *context.Context Call call } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TableInitDoneInfo struct { Limit int } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TablePoolStateChangeInfo struct { Size int Event string } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TablePoolSessionNewStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -325,10 +357,12 @@ type ( Context *context.Context Call call } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TablePoolSessionNewDoneInfo struct { Session tableSessionInfo Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TablePoolGetStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -337,11 +371,13 @@ type ( Context *context.Context Call call } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TablePoolGetDoneInfo struct { Session tableSessionInfo Attempts int Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TablePoolWaitStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -353,10 +389,12 @@ type ( // TablePoolWaitDoneInfo means a wait iteration inside Get call is done // Warning: Session and Error may be nil at the same time. This means // that a wait iteration donned without any significant tableResultErr + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TablePoolWaitDoneInfo struct { Session tableSessionInfo Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TablePoolPutStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -366,9 +404,11 @@ type ( Call call Session tableSessionInfo } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TablePoolPutDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TablePoolSessionCloseStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -378,13 +418,17 @@ type ( Call call Session tableSessionInfo } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TablePoolSessionCloseDoneInfo struct{} - TablePoolSessionAddInfo struct { + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + TablePoolSessionAddInfo struct { Session tableSessionInfo } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TablePoolSessionRemoveInfo struct { Session tableSessionInfo } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TableCloseStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -393,9 +437,11 @@ type ( Context *context.Context Call call } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TableCloseDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TableDoStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -408,13 +454,12 @@ type ( Idempotent bool NestedCall bool // flag when Retry called inside head Retry } - TableDoIntermediateInfo struct { - Error error - } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TableDoDoneInfo struct { Attempts int Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TableDoTxStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -427,13 +472,12 @@ type ( Idempotent bool NestedCall bool // flag when Retry called inside head Retry } - TableDoTxIntermediateInfo struct { - Error error - } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TableDoTxDoneInfo struct { Attempts int Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TableCreateSessionStartInfo struct { // Context make available context in trace callback function. // Pointer to context provide replacement of context in trace callback function. @@ -442,9 +486,7 @@ type ( Context *context.Context Call call } - TableCreateSessionIntermediateInfo struct { - Error error - } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TableCreateSessionDoneInfo struct { Session tableSessionInfo Attempts int diff --git a/trace/table_gtrace.go b/trace/table_gtrace.go index 3d50e4c40..e2cf30606 100644 --- a/trace/table_gtrace.go +++ b/trace/table_gtrace.go @@ -12,9 +12,11 @@ type tableComposeOptions struct { } // TableOption specified Table compose option +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals type TableComposeOption func(o *tableComposeOptions) // WithTablePanicCallback specified behavior on panic +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func WithTablePanicCallback(cb func(e interface{})) TableComposeOption { return func(o *tableComposeOptions) { o.panicCallback = cb @@ -22,6 +24,7 @@ func WithTablePanicCallback(cb func(e interface{})) TableComposeOption { } // Compose returns a new Table which has functional fields composed both from t and x. +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func (t *Table) Compose(x *Table, opts ...TableComposeOption) *Table { var ret Table options := tableComposeOptions{} @@ -103,7 +106,7 @@ func (t *Table) Compose(x *Table, opts ...TableComposeOption) *Table { { h1 := t.OnDo h2 := x.OnDo - ret.OnDo = func(t TableDoStartInfo) func(TableDoIntermediateInfo) func(TableDoDoneInfo) { + ret.OnDo = func(t TableDoStartInfo) func(TableDoDoneInfo) { if options.panicCallback != nil { defer func() { if e := recover(); e != nil { @@ -111,14 +114,14 @@ func (t *Table) Compose(x *Table, opts ...TableComposeOption) *Table { } }() } - var r, r1 func(TableDoIntermediateInfo) func(TableDoDoneInfo) + var r, r1 func(TableDoDoneInfo) if h1 != nil { r = h1(t) } if h2 != nil { r1 = h2(t) } - return func(info TableDoIntermediateInfo) func(TableDoDoneInfo) { + return func(t TableDoDoneInfo) { if options.panicCallback != nil { defer func() { if e := recover(); e != nil { @@ -126,27 +129,11 @@ func (t *Table) Compose(x *Table, opts ...TableComposeOption) *Table { } }() } - var r2, r3 func(TableDoDoneInfo) if r != nil { - r2 = r(info) + r(t) } if r1 != nil { - r3 = r1(info) - } - return func(t TableDoDoneInfo) { - if options.panicCallback != nil { - defer func() { - if e := recover(); e != nil { - options.panicCallback(e) - } - }() - } - if r2 != nil { - r2(t) - } - if r3 != nil { - r3(t) - } + r1(t) } } } @@ -154,7 +141,7 @@ func (t *Table) Compose(x *Table, opts ...TableComposeOption) *Table { { h1 := t.OnDoTx h2 := x.OnDoTx - ret.OnDoTx = func(t TableDoTxStartInfo) func(TableDoTxIntermediateInfo) func(TableDoTxDoneInfo) { + ret.OnDoTx = func(t TableDoTxStartInfo) func(TableDoTxDoneInfo) { if options.panicCallback != nil { defer func() { if e := recover(); e != nil { @@ -162,14 +149,14 @@ func (t *Table) Compose(x *Table, opts ...TableComposeOption) *Table { } }() } - var r, r1 func(TableDoTxIntermediateInfo) func(TableDoTxDoneInfo) + var r, r1 func(TableDoTxDoneInfo) if h1 != nil { r = h1(t) } if h2 != nil { r1 = h2(t) } - return func(info TableDoTxIntermediateInfo) func(TableDoTxDoneInfo) { + return func(t TableDoTxDoneInfo) { if options.panicCallback != nil { defer func() { if e := recover(); e != nil { @@ -177,27 +164,11 @@ func (t *Table) Compose(x *Table, opts ...TableComposeOption) *Table { } }() } - var r2, r3 func(TableDoTxDoneInfo) if r != nil { - r2 = r(info) + r(t) } if r1 != nil { - r3 = r1(info) - } - return func(t TableDoTxDoneInfo) { - if options.panicCallback != nil { - defer func() { - if e := recover(); e != nil { - options.panicCallback(e) - } - }() - } - if r2 != nil { - r2(t) - } - if r3 != nil { - r3(t) - } + r1(t) } } } @@ -205,7 +176,7 @@ func (t *Table) Compose(x *Table, opts ...TableComposeOption) *Table { { h1 := t.OnCreateSession h2 := x.OnCreateSession - ret.OnCreateSession = func(t TableCreateSessionStartInfo) func(TableCreateSessionIntermediateInfo) func(TableCreateSessionDoneInfo) { + ret.OnCreateSession = func(t TableCreateSessionStartInfo) func(TableCreateSessionDoneInfo) { if options.panicCallback != nil { defer func() { if e := recover(); e != nil { @@ -213,14 +184,14 @@ func (t *Table) Compose(x *Table, opts ...TableComposeOption) *Table { } }() } - var r, r1 func(TableCreateSessionIntermediateInfo) func(TableCreateSessionDoneInfo) + var r, r1 func(TableCreateSessionDoneInfo) if h1 != nil { r = h1(t) } if h2 != nil { r1 = h2(t) } - return func(info TableCreateSessionIntermediateInfo) func(TableCreateSessionDoneInfo) { + return func(t TableCreateSessionDoneInfo) { if options.panicCallback != nil { defer func() { if e := recover(); e != nil { @@ -228,27 +199,11 @@ func (t *Table) Compose(x *Table, opts ...TableComposeOption) *Table { } }() } - var r2, r3 func(TableCreateSessionDoneInfo) if r != nil { - r2 = r(info) + r(t) } if r1 != nil { - r3 = r1(info) - } - return func(t TableCreateSessionDoneInfo) { - if options.panicCallback != nil { - defer func() { - if e := recover(); e != nil { - options.panicCallback(e) - } - }() - } - if r2 != nil { - r2(t) - } - if r3 != nil { - r3(t) - } + r1(t) } } } @@ -501,7 +456,7 @@ func (t *Table) Compose(x *Table, opts ...TableComposeOption) *Table { { h1 := t.OnSessionQueryStreamExecute h2 := x.OnSessionQueryStreamExecute - ret.OnSessionQueryStreamExecute = func(t TableSessionQueryStreamExecuteStartInfo) func(TableSessionQueryStreamExecuteIntermediateInfo) func(TableSessionQueryStreamExecuteDoneInfo) { + ret.OnSessionQueryStreamExecute = func(t TableSessionQueryStreamExecuteStartInfo) func(TableSessionQueryStreamExecuteDoneInfo) { if options.panicCallback != nil { defer func() { if e := recover(); e != nil { @@ -509,14 +464,14 @@ func (t *Table) Compose(x *Table, opts ...TableComposeOption) *Table { } }() } - var r, r1 func(TableSessionQueryStreamExecuteIntermediateInfo) func(TableSessionQueryStreamExecuteDoneInfo) + var r, r1 func(TableSessionQueryStreamExecuteDoneInfo) if h1 != nil { r = h1(t) } if h2 != nil { r1 = h2(t) } - return func(t TableSessionQueryStreamExecuteIntermediateInfo) func(TableSessionQueryStreamExecuteDoneInfo) { + return func(t TableSessionQueryStreamExecuteDoneInfo) { if options.panicCallback != nil { defer func() { if e := recover(); e != nil { @@ -524,27 +479,11 @@ func (t *Table) Compose(x *Table, opts ...TableComposeOption) *Table { } }() } - var r2, r3 func(TableSessionQueryStreamExecuteDoneInfo) if r != nil { - r2 = r(t) + r(t) } if r1 != nil { - r3 = r1(t) - } - return func(t TableSessionQueryStreamExecuteDoneInfo) { - if options.panicCallback != nil { - defer func() { - if e := recover(); e != nil { - options.panicCallback(e) - } - }() - } - if r2 != nil { - r2(t) - } - if r3 != nil { - r3(t) - } + r1(t) } } } @@ -552,7 +491,7 @@ func (t *Table) Compose(x *Table, opts ...TableComposeOption) *Table { { h1 := t.OnSessionQueryStreamRead h2 := x.OnSessionQueryStreamRead - ret.OnSessionQueryStreamRead = func(t TableSessionQueryStreamReadStartInfo) func(TableSessionQueryStreamReadIntermediateInfo) func(TableSessionQueryStreamReadDoneInfo) { + ret.OnSessionQueryStreamRead = func(t TableSessionQueryStreamReadStartInfo) func(TableSessionQueryStreamReadDoneInfo) { if options.panicCallback != nil { defer func() { if e := recover(); e != nil { @@ -560,14 +499,14 @@ func (t *Table) Compose(x *Table, opts ...TableComposeOption) *Table { } }() } - var r, r1 func(TableSessionQueryStreamReadIntermediateInfo) func(TableSessionQueryStreamReadDoneInfo) + var r, r1 func(TableSessionQueryStreamReadDoneInfo) if h1 != nil { r = h1(t) } if h2 != nil { r1 = h2(t) } - return func(t TableSessionQueryStreamReadIntermediateInfo) func(TableSessionQueryStreamReadDoneInfo) { + return func(t TableSessionQueryStreamReadDoneInfo) { if options.panicCallback != nil { defer func() { if e := recover(); e != nil { @@ -575,35 +514,19 @@ func (t *Table) Compose(x *Table, opts ...TableComposeOption) *Table { } }() } - var r2, r3 func(TableSessionQueryStreamReadDoneInfo) if r != nil { - r2 = r(t) + r(t) } if r1 != nil { - r3 = r1(t) - } - return func(t TableSessionQueryStreamReadDoneInfo) { - if options.panicCallback != nil { - defer func() { - if e := recover(); e != nil { - options.panicCallback(e) - } - }() - } - if r2 != nil { - r2(t) - } - if r3 != nil { - r3(t) - } + r1(t) } } } } { - h1 := t.OnSessionTransactionBegin - h2 := x.OnSessionTransactionBegin - ret.OnSessionTransactionBegin = func(t TableSessionTransactionBeginStartInfo) func(TableSessionTransactionBeginDoneInfo) { + h1 := t.OnTxBegin + h2 := x.OnTxBegin + ret.OnTxBegin = func(t TableTxBeginStartInfo) func(TableTxBeginDoneInfo) { if options.panicCallback != nil { defer func() { if e := recover(); e != nil { @@ -611,14 +534,14 @@ func (t *Table) Compose(x *Table, opts ...TableComposeOption) *Table { } }() } - var r, r1 func(TableSessionTransactionBeginDoneInfo) + var r, r1 func(TableTxBeginDoneInfo) if h1 != nil { r = h1(t) } if h2 != nil { r1 = h2(t) } - return func(t TableSessionTransactionBeginDoneInfo) { + return func(t TableTxBeginDoneInfo) { if options.panicCallback != nil { defer func() { if e := recover(); e != nil { @@ -636,9 +559,9 @@ func (t *Table) Compose(x *Table, opts ...TableComposeOption) *Table { } } { - h1 := t.OnSessionTransactionExecute - h2 := x.OnSessionTransactionExecute - ret.OnSessionTransactionExecute = func(t TableTransactionExecuteStartInfo) func(TableTransactionExecuteDoneInfo) { + h1 := t.OnTxExecute + h2 := x.OnTxExecute + ret.OnTxExecute = func(t TableTransactionExecuteStartInfo) func(TableTransactionExecuteDoneInfo) { if options.panicCallback != nil { defer func() { if e := recover(); e != nil { @@ -671,9 +594,9 @@ func (t *Table) Compose(x *Table, opts ...TableComposeOption) *Table { } } { - h1 := t.OnSessionTransactionExecuteStatement - h2 := x.OnSessionTransactionExecuteStatement - ret.OnSessionTransactionExecuteStatement = func(t TableTransactionExecuteStatementStartInfo) func(TableTransactionExecuteStatementDoneInfo) { + h1 := t.OnTxExecuteStatement + h2 := x.OnTxExecuteStatement + ret.OnTxExecuteStatement = func(t TableTransactionExecuteStatementStartInfo) func(TableTransactionExecuteStatementDoneInfo) { if options.panicCallback != nil { defer func() { if e := recover(); e != nil { @@ -706,9 +629,9 @@ func (t *Table) Compose(x *Table, opts ...TableComposeOption) *Table { } } { - h1 := t.OnSessionTransactionCommit - h2 := x.OnSessionTransactionCommit - ret.OnSessionTransactionCommit = func(t TableSessionTransactionCommitStartInfo) func(TableSessionTransactionCommitDoneInfo) { + h1 := t.OnTxCommit + h2 := x.OnTxCommit + ret.OnTxCommit = func(t TableTxCommitStartInfo) func(TableTxCommitDoneInfo) { if options.panicCallback != nil { defer func() { if e := recover(); e != nil { @@ -716,14 +639,14 @@ func (t *Table) Compose(x *Table, opts ...TableComposeOption) *Table { } }() } - var r, r1 func(TableSessionTransactionCommitDoneInfo) + var r, r1 func(TableTxCommitDoneInfo) if h1 != nil { r = h1(t) } if h2 != nil { r1 = h2(t) } - return func(t TableSessionTransactionCommitDoneInfo) { + return func(t TableTxCommitDoneInfo) { if options.panicCallback != nil { defer func() { if e := recover(); e != nil { @@ -741,9 +664,9 @@ func (t *Table) Compose(x *Table, opts ...TableComposeOption) *Table { } } { - h1 := t.OnSessionTransactionRollback - h2 := x.OnSessionTransactionRollback - ret.OnSessionTransactionRollback = func(t TableSessionTransactionRollbackStartInfo) func(TableSessionTransactionRollbackDoneInfo) { + h1 := t.OnTxRollback + h2 := x.OnTxRollback + ret.OnTxRollback = func(t TableTxRollbackStartInfo) func(TableTxRollbackDoneInfo) { if options.panicCallback != nil { defer func() { if e := recover(); e != nil { @@ -751,14 +674,14 @@ func (t *Table) Compose(x *Table, opts ...TableComposeOption) *Table { } }() } - var r, r1 func(TableSessionTransactionRollbackDoneInfo) + var r, r1 func(TableTxRollbackDoneInfo) if h1 != nil { r = h1(t) } if h2 != nil { r1 = h2(t) } - return func(t TableSessionTransactionRollbackDoneInfo) { + return func(t TableTxRollbackDoneInfo) { if options.panicCallback != nil { defer func() { if e := recover(); e != nil { @@ -969,86 +892,50 @@ func (t *Table) onClose(t1 TableCloseStartInfo) func(TableCloseDoneInfo) { } return res } -func (t *Table) onDo(t1 TableDoStartInfo) func(info TableDoIntermediateInfo) func(TableDoDoneInfo) { +func (t *Table) onDo(t1 TableDoStartInfo) func(TableDoDoneInfo) { fn := t.OnDo if fn == nil { - return func(TableDoIntermediateInfo) func(TableDoDoneInfo) { - return func(TableDoDoneInfo) { - return - } + return func(TableDoDoneInfo) { + return } } res := fn(t1) if res == nil { - return func(TableDoIntermediateInfo) func(TableDoDoneInfo) { - return func(TableDoDoneInfo) { - return - } - } - } - return func(info TableDoIntermediateInfo) func(TableDoDoneInfo) { - res := res(info) - if res == nil { - return func(TableDoDoneInfo) { - return - } + return func(TableDoDoneInfo) { + return } - return res } + return res } -func (t *Table) onDoTx(t1 TableDoTxStartInfo) func(info TableDoTxIntermediateInfo) func(TableDoTxDoneInfo) { +func (t *Table) onDoTx(t1 TableDoTxStartInfo) func(TableDoTxDoneInfo) { fn := t.OnDoTx if fn == nil { - return func(TableDoTxIntermediateInfo) func(TableDoTxDoneInfo) { - return func(TableDoTxDoneInfo) { - return - } + return func(TableDoTxDoneInfo) { + return } } res := fn(t1) if res == nil { - return func(TableDoTxIntermediateInfo) func(TableDoTxDoneInfo) { - return func(TableDoTxDoneInfo) { - return - } - } - } - return func(info TableDoTxIntermediateInfo) func(TableDoTxDoneInfo) { - res := res(info) - if res == nil { - return func(TableDoTxDoneInfo) { - return - } + return func(TableDoTxDoneInfo) { + return } - return res } + return res } -func (t *Table) onCreateSession(t1 TableCreateSessionStartInfo) func(info TableCreateSessionIntermediateInfo) func(TableCreateSessionDoneInfo) { +func (t *Table) onCreateSession(t1 TableCreateSessionStartInfo) func(TableCreateSessionDoneInfo) { fn := t.OnCreateSession if fn == nil { - return func(TableCreateSessionIntermediateInfo) func(TableCreateSessionDoneInfo) { - return func(TableCreateSessionDoneInfo) { - return - } + return func(TableCreateSessionDoneInfo) { + return } } res := fn(t1) if res == nil { - return func(TableCreateSessionIntermediateInfo) func(TableCreateSessionDoneInfo) { - return func(TableCreateSessionDoneInfo) { - return - } - } - } - return func(info TableCreateSessionIntermediateInfo) func(TableCreateSessionDoneInfo) { - res := res(info) - if res == nil { - return func(TableCreateSessionDoneInfo) { - return - } + return func(TableCreateSessionDoneInfo) { + return } - return res } + return res } func (t *Table) onSessionNew(t1 TableSessionNewStartInfo) func(TableSessionNewDoneInfo) { fn := t.OnSessionNew @@ -1155,77 +1042,53 @@ func (t *Table) onSessionQueryExplain(t1 TableExplainQueryStartInfo) func(TableE } return res } -func (t *Table) onSessionQueryStreamExecute(t1 TableSessionQueryStreamExecuteStartInfo) func(TableSessionQueryStreamExecuteIntermediateInfo) func(TableSessionQueryStreamExecuteDoneInfo) { +func (t *Table) onSessionQueryStreamExecute(t1 TableSessionQueryStreamExecuteStartInfo) func(TableSessionQueryStreamExecuteDoneInfo) { fn := t.OnSessionQueryStreamExecute if fn == nil { - return func(TableSessionQueryStreamExecuteIntermediateInfo) func(TableSessionQueryStreamExecuteDoneInfo) { - return func(TableSessionQueryStreamExecuteDoneInfo) { - return - } + return func(TableSessionQueryStreamExecuteDoneInfo) { + return } } res := fn(t1) if res == nil { - return func(TableSessionQueryStreamExecuteIntermediateInfo) func(TableSessionQueryStreamExecuteDoneInfo) { - return func(TableSessionQueryStreamExecuteDoneInfo) { - return - } - } - } - return func(t TableSessionQueryStreamExecuteIntermediateInfo) func(TableSessionQueryStreamExecuteDoneInfo) { - res := res(t) - if res == nil { - return func(TableSessionQueryStreamExecuteDoneInfo) { - return - } + return func(TableSessionQueryStreamExecuteDoneInfo) { + return } - return res } + return res } -func (t *Table) onSessionQueryStreamRead(t1 TableSessionQueryStreamReadStartInfo) func(TableSessionQueryStreamReadIntermediateInfo) func(TableSessionQueryStreamReadDoneInfo) { +func (t *Table) onSessionQueryStreamRead(t1 TableSessionQueryStreamReadStartInfo) func(TableSessionQueryStreamReadDoneInfo) { fn := t.OnSessionQueryStreamRead if fn == nil { - return func(TableSessionQueryStreamReadIntermediateInfo) func(TableSessionQueryStreamReadDoneInfo) { - return func(TableSessionQueryStreamReadDoneInfo) { - return - } + return func(TableSessionQueryStreamReadDoneInfo) { + return } } res := fn(t1) if res == nil { - return func(TableSessionQueryStreamReadIntermediateInfo) func(TableSessionQueryStreamReadDoneInfo) { - return func(TableSessionQueryStreamReadDoneInfo) { - return - } - } - } - return func(t TableSessionQueryStreamReadIntermediateInfo) func(TableSessionQueryStreamReadDoneInfo) { - res := res(t) - if res == nil { - return func(TableSessionQueryStreamReadDoneInfo) { - return - } + return func(TableSessionQueryStreamReadDoneInfo) { + return } - return res } + return res } -func (t *Table) onSessionTransactionBegin(t1 TableSessionTransactionBeginStartInfo) func(TableSessionTransactionBeginDoneInfo) { - fn := t.OnSessionTransactionBegin +func (t *Table) onTxBegin(t1 TableTxBeginStartInfo) func(TableTxBeginDoneInfo) { + fn := t.OnTxBegin if fn == nil { - return func(TableSessionTransactionBeginDoneInfo) { + return func(TableTxBeginDoneInfo) { return } } res := fn(t1) if res == nil { - return func(TableSessionTransactionBeginDoneInfo) { + return func(TableTxBeginDoneInfo) { return } } return res } -func (t *Table) onSessionTransactionExecute(t1 TableTransactionExecuteStartInfo) func(TableTransactionExecuteDoneInfo) { - fn := t.OnSessionTransactionExecute +func (t *Table) onTxExecute(t1 TableTransactionExecuteStartInfo) func(TableTransactionExecuteDoneInfo) { + fn := t.OnTxExecute if fn == nil { return func(TableTransactionExecuteDoneInfo) { return @@ -1239,8 +1102,8 @@ func (t *Table) onSessionTransactionExecute(t1 TableTransactionExecuteStartInfo) } return res } -func (t *Table) onSessionTransactionExecuteStatement(t1 TableTransactionExecuteStatementStartInfo) func(TableTransactionExecuteStatementDoneInfo) { - fn := t.OnSessionTransactionExecuteStatement +func (t *Table) onTxExecuteStatement(t1 TableTransactionExecuteStatementStartInfo) func(TableTransactionExecuteStatementDoneInfo) { + fn := t.OnTxExecuteStatement if fn == nil { return func(TableTransactionExecuteStatementDoneInfo) { return @@ -1254,31 +1117,31 @@ func (t *Table) onSessionTransactionExecuteStatement(t1 TableTransactionExecuteS } return res } -func (t *Table) onSessionTransactionCommit(t1 TableSessionTransactionCommitStartInfo) func(TableSessionTransactionCommitDoneInfo) { - fn := t.OnSessionTransactionCommit +func (t *Table) onTxCommit(t1 TableTxCommitStartInfo) func(TableTxCommitDoneInfo) { + fn := t.OnTxCommit if fn == nil { - return func(TableSessionTransactionCommitDoneInfo) { + return func(TableTxCommitDoneInfo) { return } } res := fn(t1) if res == nil { - return func(TableSessionTransactionCommitDoneInfo) { + return func(TableTxCommitDoneInfo) { return } } return res } -func (t *Table) onSessionTransactionRollback(t1 TableSessionTransactionRollbackStartInfo) func(TableSessionTransactionRollbackDoneInfo) { - fn := t.OnSessionTransactionRollback +func (t *Table) onTxRollback(t1 TableTxRollbackStartInfo) func(TableTxRollbackDoneInfo) { + fn := t.OnTxRollback if fn == nil { - return func(TableSessionTransactionRollbackDoneInfo) { + return func(TableTxRollbackDoneInfo) { return } } res := fn(t1) if res == nil { - return func(TableSessionTransactionRollbackDoneInfo) { + return func(TableTxRollbackDoneInfo) { return } } @@ -1350,6 +1213,7 @@ func (t *Table) onPoolWait(t1 TablePoolWaitStartInfo) func(TablePoolWaitDoneInfo } return res } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func TableOnInit(t *Table, c *context.Context, call call) func(limit int) { var p TableInitStartInfo p.Context = c @@ -1361,6 +1225,7 @@ func TableOnInit(t *Table, c *context.Context, call call) func(limit int) { res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func TableOnClose(t *Table, c *context.Context, call call) func(error) { var p TableCloseStartInfo p.Context = c @@ -1372,7 +1237,8 @@ func TableOnClose(t *Table, c *context.Context, call call) func(error) { res(p) } } -func TableOnDo(t *Table, c *context.Context, call call, label string, idempotent bool, nestedCall bool) func(error) func(attempts int, _ error) { +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals +func TableOnDo(t *Table, c *context.Context, call call, label string, idempotent bool, nestedCall bool) func(attempts int, _ error) { var p TableDoStartInfo p.Context = c p.Call = call @@ -1380,19 +1246,15 @@ func TableOnDo(t *Table, c *context.Context, call call, label string, idempotent p.Idempotent = idempotent p.NestedCall = nestedCall res := t.onDo(p) - return func(e error) func(int, error) { - var p TableDoIntermediateInfo + return func(attempts int, e error) { + var p TableDoDoneInfo + p.Attempts = attempts p.Error = e - res := res(p) - return func(attempts int, e error) { - var p TableDoDoneInfo - p.Attempts = attempts - p.Error = e - res(p) - } + res(p) } } -func TableOnDoTx(t *Table, c *context.Context, call call, label string, idempotent bool, nestedCall bool) func(error) func(attempts int, _ error) { +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals +func TableOnDoTx(t *Table, c *context.Context, call call, label string, idempotent bool, nestedCall bool) func(attempts int, _ error) { var p TableDoTxStartInfo p.Context = c p.Call = call @@ -1400,36 +1262,28 @@ func TableOnDoTx(t *Table, c *context.Context, call call, label string, idempote p.Idempotent = idempotent p.NestedCall = nestedCall res := t.onDoTx(p) - return func(e error) func(int, error) { - var p TableDoTxIntermediateInfo + return func(attempts int, e error) { + var p TableDoTxDoneInfo + p.Attempts = attempts p.Error = e - res := res(p) - return func(attempts int, e error) { - var p TableDoTxDoneInfo - p.Attempts = attempts - p.Error = e - res(p) - } + res(p) } } -func TableOnCreateSession(t *Table, c *context.Context, call call) func(error) func(session tableSessionInfo, attempts int, _ error) { +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals +func TableOnCreateSession(t *Table, c *context.Context, call call) func(session tableSessionInfo, attempts int, _ error) { var p TableCreateSessionStartInfo p.Context = c p.Call = call res := t.onCreateSession(p) - return func(e error) func(tableSessionInfo, int, error) { - var p TableCreateSessionIntermediateInfo + return func(session tableSessionInfo, attempts int, e error) { + var p TableCreateSessionDoneInfo + p.Session = session + p.Attempts = attempts p.Error = e - res := res(p) - return func(session tableSessionInfo, attempts int, e error) { - var p TableCreateSessionDoneInfo - p.Session = session - p.Attempts = attempts - p.Error = e - res(p) - } + res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func TableOnSessionNew(t *Table, c *context.Context, call call) func(session tableSessionInfo, _ error) { var p TableSessionNewStartInfo p.Context = c @@ -1442,6 +1296,7 @@ func TableOnSessionNew(t *Table, c *context.Context, call call) func(session tab res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func TableOnSessionDelete(t *Table, c *context.Context, call call, session tableSessionInfo) func(error) { var p TableSessionDeleteStartInfo p.Context = c @@ -1454,6 +1309,7 @@ func TableOnSessionDelete(t *Table, c *context.Context, call call, session table res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func TableOnSessionKeepAlive(t *Table, c *context.Context, call call, session tableSessionInfo) func(error) { var p TableKeepAliveStartInfo p.Context = c @@ -1466,6 +1322,7 @@ func TableOnSessionKeepAlive(t *Table, c *context.Context, call call, session ta res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func TableOnSessionBulkUpsert(t *Table, c *context.Context, call call, session tableSessionInfo) func(error) { var p TableBulkUpsertStartInfo p.Context = c @@ -1478,6 +1335,7 @@ func TableOnSessionBulkUpsert(t *Table, c *context.Context, call call, session t res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func TableOnSessionQueryPrepare(t *Table, c *context.Context, call call, session tableSessionInfo, query string) func(result tableDataQuery, _ error) { var p TablePrepareDataQueryStartInfo p.Context = c @@ -1492,6 +1350,7 @@ func TableOnSessionQueryPrepare(t *Table, c *context.Context, call call, session res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func TableOnSessionQueryExecute(t *Table, c *context.Context, call call, session tableSessionInfo, query tableDataQuery, parameters tableQueryParameters, keepInCache bool) func(tx tableTransactionInfo, prepared bool, result tableResult, _ error) { var p TableExecuteDataQueryStartInfo p.Context = c @@ -1510,6 +1369,7 @@ func TableOnSessionQueryExecute(t *Table, c *context.Context, call call, session res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func TableOnSessionQueryExplain(t *Table, c *context.Context, call call, session tableSessionInfo, query string) func(aST string, plan string, _ error) { var p TableExplainQueryStartInfo p.Context = c @@ -1525,7 +1385,8 @@ func TableOnSessionQueryExplain(t *Table, c *context.Context, call call, session res(p) } } -func TableOnSessionQueryStreamExecute(t *Table, c *context.Context, call call, session tableSessionInfo, query tableDataQuery, parameters tableQueryParameters) func(error) func(error) { +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals +func TableOnSessionQueryStreamExecute(t *Table, c *context.Context, call call, session tableSessionInfo, query tableDataQuery, parameters tableQueryParameters) func(error) { var p TableSessionQueryStreamExecuteStartInfo p.Context = c p.Call = call @@ -1533,48 +1394,41 @@ func TableOnSessionQueryStreamExecute(t *Table, c *context.Context, call call, s p.Query = query p.Parameters = parameters res := t.onSessionQueryStreamExecute(p) - return func(e error) func(error) { - var p TableSessionQueryStreamExecuteIntermediateInfo + return func(e error) { + var p TableSessionQueryStreamExecuteDoneInfo p.Error = e - res := res(p) - return func(e error) { - var p TableSessionQueryStreamExecuteDoneInfo - p.Error = e - res(p) - } + res(p) } } -func TableOnSessionQueryStreamRead(t *Table, c *context.Context, call call, session tableSessionInfo) func(error) func(error) { +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals +func TableOnSessionQueryStreamRead(t *Table, c *context.Context, call call, session tableSessionInfo) func(error) { var p TableSessionQueryStreamReadStartInfo p.Context = c p.Call = call p.Session = session res := t.onSessionQueryStreamRead(p) - return func(e error) func(error) { - var p TableSessionQueryStreamReadIntermediateInfo + return func(e error) { + var p TableSessionQueryStreamReadDoneInfo p.Error = e - res := res(p) - return func(e error) { - var p TableSessionQueryStreamReadDoneInfo - p.Error = e - res(p) - } + res(p) } } -func TableOnSessionTransactionBegin(t *Table, c *context.Context, call call, session tableSessionInfo) func(tx tableTransactionInfo, _ error) { - var p TableSessionTransactionBeginStartInfo +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals +func TableOnTxBegin(t *Table, c *context.Context, call call, session tableSessionInfo) func(tx tableTransactionInfo, _ error) { + var p TableTxBeginStartInfo p.Context = c p.Call = call p.Session = session - res := t.onSessionTransactionBegin(p) + res := t.onTxBegin(p) return func(tx tableTransactionInfo, e error) { - var p TableSessionTransactionBeginDoneInfo + var p TableTxBeginDoneInfo p.Tx = tx p.Error = e res(p) } } -func TableOnSessionTransactionExecute(t *Table, c *context.Context, call call, session tableSessionInfo, tx tableTransactionInfo, query tableDataQuery, parameters tableQueryParameters) func(result tableResult, _ error) { +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals +func TableOnTxExecute(t *Table, c *context.Context, call call, session tableSessionInfo, tx tableTransactionInfo, query tableDataQuery, parameters tableQueryParameters) func(result tableResult, _ error) { var p TableTransactionExecuteStartInfo p.Context = c p.Call = call @@ -1582,7 +1436,7 @@ func TableOnSessionTransactionExecute(t *Table, c *context.Context, call call, s p.Tx = tx p.Query = query p.Parameters = parameters - res := t.onSessionTransactionExecute(p) + res := t.onTxExecute(p) return func(result tableResult, e error) { var p TableTransactionExecuteDoneInfo p.Result = result @@ -1590,7 +1444,8 @@ func TableOnSessionTransactionExecute(t *Table, c *context.Context, call call, s res(p) } } -func TableOnSessionTransactionExecuteStatement(t *Table, c *context.Context, call call, session tableSessionInfo, tx tableTransactionInfo, statementQuery tableDataQuery, parameters tableQueryParameters) func(result tableResult, _ error) { +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals +func TableOnTxExecuteStatement(t *Table, c *context.Context, call call, session tableSessionInfo, tx tableTransactionInfo, statementQuery tableDataQuery, parameters tableQueryParameters) func(result tableResult, _ error) { var p TableTransactionExecuteStatementStartInfo p.Context = c p.Call = call @@ -1598,7 +1453,7 @@ func TableOnSessionTransactionExecuteStatement(t *Table, c *context.Context, cal p.Tx = tx p.StatementQuery = statementQuery p.Parameters = parameters - res := t.onSessionTransactionExecuteStatement(p) + res := t.onTxExecuteStatement(p) return func(result tableResult, e error) { var p TableTransactionExecuteStatementDoneInfo p.Result = result @@ -1606,48 +1461,54 @@ func TableOnSessionTransactionExecuteStatement(t *Table, c *context.Context, cal res(p) } } -func TableOnSessionTransactionCommit(t *Table, c *context.Context, call call, session tableSessionInfo, tx tableTransactionInfo) func(error) { - var p TableSessionTransactionCommitStartInfo +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals +func TableOnTxCommit(t *Table, c *context.Context, call call, session tableSessionInfo, tx tableTransactionInfo) func(error) { + var p TableTxCommitStartInfo p.Context = c p.Call = call p.Session = session p.Tx = tx - res := t.onSessionTransactionCommit(p) + res := t.onTxCommit(p) return func(e error) { - var p TableSessionTransactionCommitDoneInfo + var p TableTxCommitDoneInfo p.Error = e res(p) } } -func TableOnSessionTransactionRollback(t *Table, c *context.Context, call call, session tableSessionInfo, tx tableTransactionInfo) func(error) { - var p TableSessionTransactionRollbackStartInfo +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals +func TableOnTxRollback(t *Table, c *context.Context, call call, session tableSessionInfo, tx tableTransactionInfo) func(error) { + var p TableTxRollbackStartInfo p.Context = c p.Call = call p.Session = session p.Tx = tx - res := t.onSessionTransactionRollback(p) + res := t.onTxRollback(p) return func(e error) { - var p TableSessionTransactionRollbackDoneInfo + var p TableTxRollbackDoneInfo p.Error = e res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func TableOnPoolStateChange(t *Table, size int, event string) { var p TablePoolStateChangeInfo p.Size = size p.Event = event t.onPoolStateChange(p) } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func TableOnPoolSessionAdd(t *Table, session tableSessionInfo) { var p TablePoolSessionAddInfo p.Session = session t.onPoolSessionAdd(p) } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func TableOnPoolSessionRemove(t *Table, session tableSessionInfo) { var p TablePoolSessionRemoveInfo p.Session = session t.onPoolSessionRemove(p) } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func TableOnPoolPut(t *Table, c *context.Context, call call, session tableSessionInfo) func(error) { var p TablePoolPutStartInfo p.Context = c @@ -1660,6 +1521,7 @@ func TableOnPoolPut(t *Table, c *context.Context, call call, session tableSessio res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func TableOnPoolGet(t *Table, c *context.Context, call call) func(session tableSessionInfo, attempts int, _ error) { var p TablePoolGetStartInfo p.Context = c @@ -1673,6 +1535,7 @@ func TableOnPoolGet(t *Table, c *context.Context, call call) func(session tableS res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func TableOnPoolWait(t *Table, c *context.Context, call call) func(session tableSessionInfo, _ error) { var p TablePoolWaitStartInfo p.Context = c diff --git a/trace/topic.go b/trace/topic.go index b6498b52e..53077a3b8 100644 --- a/trace/topic.go +++ b/trace/topic.go @@ -11,23 +11,29 @@ import ( type ( // Topic specified trace of topic reader client activity. // gtrace:gen + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals Topic struct { // TopicReaderCustomerEvents - upper level, on bridge with customer code + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals OnReaderStart func(info TopicReaderStartInfo) // TopicReaderStreamLifeCycleEvents - OnReaderReconnect func(TopicReaderReconnectStartInfo) func(TopicReaderReconnectDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnReaderReconnect func(TopicReaderReconnectStartInfo) func(TopicReaderReconnectDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals OnReaderReconnectRequest func(TopicReaderReconnectRequestInfo) // TopicReaderPartitionEvents + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals OnReaderPartitionReadStartResponse func( TopicReaderPartitionReadStartResponseStartInfo, ) func( TopicReaderPartitionReadStartResponseDoneInfo, ) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals OnReaderPartitionReadStopResponse func( TopicReaderPartitionReadStopResponseStartInfo, ) func( @@ -36,13 +42,20 @@ type ( // TopicReaderStreamEvents - OnReaderCommit func(TopicReaderCommitStartInfo) func(TopicReaderCommitDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnReaderCommit func(TopicReaderCommitStartInfo) func(TopicReaderCommitDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals OnReaderSendCommitMessage func(TopicReaderSendCommitMessageStartInfo) func(TopicReaderSendCommitMessageDoneInfo) - OnReaderCommittedNotify func(TopicReaderCommittedNotifyInfo) - OnReaderClose func(TopicReaderCloseStartInfo) func(TopicReaderCloseDoneInfo) - OnReaderInit func(TopicReaderInitStartInfo) func(TopicReaderInitDoneInfo) - OnReaderError func(TopicReaderErrorInfo) - OnReaderUpdateToken func( + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnReaderCommittedNotify func(TopicReaderCommittedNotifyInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnReaderClose func(TopicReaderCloseStartInfo) func(TopicReaderCloseDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnReaderInit func(TopicReaderInitStartInfo) func(TopicReaderInitDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnReaderError func(TopicReaderErrorInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnReaderUpdateToken func( OnReadUpdateTokenStartInfo, ) func( OnReadUpdateTokenMiddleTokenReceivedInfo, @@ -52,24 +65,35 @@ type ( // TopicReaderMessageEvents - OnReaderSentDataRequest func(TopicReaderSentDataRequestInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnReaderSentDataRequest func(TopicReaderSentDataRequestInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals OnReaderReceiveDataResponse func(TopicReaderReceiveDataResponseStartInfo) func(TopicReaderReceiveDataResponseDoneInfo) - OnReaderReadMessages func(TopicReaderReadMessagesStartInfo) func(TopicReaderReadMessagesDoneInfo) - OnReaderUnknownGrpcMessage func(OnReadUnknownGrpcMessageInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnReaderReadMessages func(TopicReaderReadMessagesStartInfo) func(TopicReaderReadMessagesDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnReaderUnknownGrpcMessage func(OnReadUnknownGrpcMessageInfo) // TopicWriterStreamLifeCycleEvents - OnWriterReconnect func(TopicWriterReconnectStartInfo) func(TopicWriterReconnectDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnWriterReconnect func(TopicWriterReconnectStartInfo) func(TopicWriterReconnectDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals OnWriterInitStream func(TopicWriterInitStreamStartInfo) func(TopicWriterInitStreamDoneInfo) - OnWriterClose func(TopicWriterCloseStartInfo) func(TopicWriterCloseDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnWriterClose func(TopicWriterCloseStartInfo) func(TopicWriterCloseDoneInfo) // TopicWriterStreamEvents - OnWriterCompressMessages func(TopicWriterCompressMessagesStartInfo) func(TopicWriterCompressMessagesDoneInfo) - OnWriterSendMessages func(TopicWriterSendMessagesStartInfo) func(TopicWriterSendMessagesDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnWriterCompressMessages func(TopicWriterCompressMessagesStartInfo) func(TopicWriterCompressMessagesDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnWriterSendMessages func(TopicWriterSendMessagesStartInfo) func(TopicWriterSendMessagesDoneInfo) + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals OnWriterReadUnknownGrpcMessage func(TopicOnWriterReadUnknownGrpcMessageInfo) } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TopicReaderPartitionReadStartResponseStartInfo struct { ReaderConnectionID string PartitionContext context.Context @@ -78,17 +102,20 @@ type ( PartitionSessionID int64 } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TopicReaderStartInfo struct { ReaderID int64 Consumer string } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TopicReaderPartitionReadStartResponseDoneInfo struct { ReadOffset *int64 CommitOffset *int64 Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TopicReaderPartitionReadStopResponseStartInfo struct { ReaderConnectionID string PartitionContext context.Context @@ -99,14 +126,17 @@ type ( Graceful bool } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TopicReaderPartitionReadStopResponseDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TopicReaderSendCommitMessageStartInfo struct { CommitsInfo TopicReaderStreamSendCommitMessageStartMessageInfo } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TopicReaderStreamCommitInfo struct { Topic string PartitionID int64 @@ -115,14 +145,17 @@ type ( EndOffset int64 } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TopicReaderStreamSendCommitMessageStartMessageInfo interface { GetCommitsInfo() []TopicReaderStreamCommitInfo } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TopicReaderSendCommitMessageDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TopicReaderCommittedNotifyInfo struct { ReaderConnectionID string Topic string @@ -131,32 +164,38 @@ type ( CommittedOffset int64 } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TopicReaderErrorInfo struct { ReaderConnectionID string Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TopicReaderSentDataRequestInfo struct { ReaderConnectionID string RequestBytes int LocalBufferSizeAfterSent int } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TopicReaderReceiveDataResponseStartInfo struct { ReaderConnectionID string LocalBufferSizeAfterReceive int DataResponse TopicReaderDataResponseInfo } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TopicReaderDataResponseInfo interface { GetBytesSize() int GetPartitionBatchMessagesCounts() (partitionCount, batchCount, messagesCount int) } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TopicReaderReceiveDataResponseDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TopicReaderReadMessagesStartInfo struct { RequestContext context.Context MinCount int @@ -164,6 +203,7 @@ type ( FreeBufferCapacity int } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TopicReaderReadMessagesDoneInfo struct { MessagesCount int Topic string @@ -175,24 +215,29 @@ type ( Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals OnReadUnknownGrpcMessageInfo struct { ReaderConnectionID string Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TopicReaderReconnectStartInfo struct { Reason error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TopicReaderReconnectDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TopicReaderReconnectRequestInfo struct { Reason error WasSent bool } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TopicReaderCommitStartInfo struct { RequestContext context.Context Topic string @@ -202,43 +247,52 @@ type ( EndOffset int64 } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TopicReaderCommitDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TopicReaderCloseStartInfo struct { ReaderConnectionID string CloseReason error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TopicReaderCloseDoneInfo struct { CloseError error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TopicReaderInitStartInfo struct { PreInitReaderConnectionID string InitRequestInfo TopicReadStreamInitRequestInfo } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TopicReadStreamInitRequestInfo interface { GetConsumer() string GetTopics() []string } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TopicReaderInitDoneInfo struct { ReaderConnectionID string Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals OnReadUpdateTokenStartInfo struct { ReaderConnectionID string } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals OnReadUpdateTokenMiddleTokenReceivedInfo struct { TokenLen int Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals OnReadStreamUpdateTokenDoneInfo struct { Error error } @@ -247,6 +301,7 @@ type ( //////////// TopicWriter //////////// + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TopicWriterReconnectStartInfo struct { WriterInstanceID string Topic string @@ -254,30 +309,36 @@ type ( Attempt int } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TopicWriterReconnectDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TopicWriterInitStreamStartInfo struct { WriterInstanceID string Topic string ProducerID string } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TopicWriterInitStreamDoneInfo struct { SessionID string Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TopicWriterCloseStartInfo struct { WriterInstanceID string Reason error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TopicWriterCloseDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TopicWriterCompressMessagesStartInfo struct { WriterInstanceID string SessionID string @@ -287,10 +348,12 @@ type ( Reason TopicWriterCompressMessagesReason } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TopicWriterCompressMessagesDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TopicWriterSendMessagesStartInfo struct { WriterInstanceID string SessionID string @@ -299,10 +362,12 @@ type ( MessagesCount int } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TopicWriterSendMessagesDoneInfo struct { Error error } + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals TopicOnWriterReadUnknownGrpcMessageInfo struct { WriterInstanceID string SessionID string @@ -310,14 +375,19 @@ type ( } ) +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals type TopicWriterCompressMessagesReason string const ( - TopicWriterCompressMessagesReasonCompressData = TopicWriterCompressMessagesReason("compress-on-send") //nolint:lll - TopicWriterCompressMessagesReasonCompressDataOnWriteReadData = TopicWriterCompressMessagesReason("compress-on-call-write") //nolint:lll - TopicWriterCompressMessagesReasonCodecsMeasure = TopicWriterCompressMessagesReason("compress-on-codecs-measure") //nolint:lll + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + TopicWriterCompressMessagesReasonCompressData = TopicWriterCompressMessagesReason("compress-on-send") + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + TopicWriterCompressMessagesReasonCompressDataOnWriteReadData = TopicWriterCompressMessagesReason("compress-on-call-write") //nolint:lll + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + TopicWriterCompressMessagesReasonCodecsMeasure = TopicWriterCompressMessagesReason("compress-on-codecs-measure") //nolint:lll ) +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func (r TopicWriterCompressMessagesReason) String() string { return string(r) } diff --git a/trace/topic_gtrace.go b/trace/topic_gtrace.go index 8fa1c6b45..5f9dcde80 100644 --- a/trace/topic_gtrace.go +++ b/trace/topic_gtrace.go @@ -12,9 +12,11 @@ type topicComposeOptions struct { } // TopicOption specified Topic compose option +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals type TopicComposeOption func(o *topicComposeOptions) // WithTopicPanicCallback specified behavior on panic +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func WithTopicPanicCallback(cb func(e interface{})) TopicComposeOption { return func(o *topicComposeOptions) { o.panicCallback = cb @@ -22,6 +24,7 @@ func WithTopicPanicCallback(cb func(e interface{})) TopicComposeOption { } // Compose returns a new Topic which has functional fields composed both from t and x. +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func (t *Topic) Compose(x *Topic, opts ...TopicComposeOption) *Topic { var ret Topic options := topicComposeOptions{} @@ -992,12 +995,14 @@ func (t *Topic) onWriterReadUnknownGrpcMessage(t1 TopicOnWriterReadUnknownGrpcMe } fn(t1) } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func TopicOnReaderStart(t *Topic, readerID int64, consumer string) { var p TopicReaderStartInfo p.ReaderID = readerID p.Consumer = consumer t.onReaderStart(p) } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func TopicOnReaderReconnect(t *Topic, reason error) func(error) { var p TopicReaderReconnectStartInfo p.Reason = reason @@ -1008,12 +1013,14 @@ func TopicOnReaderReconnect(t *Topic, reason error) func(error) { res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func TopicOnReaderReconnectRequest(t *Topic, reason error, wasSent bool) { var p TopicReaderReconnectRequestInfo p.Reason = reason p.WasSent = wasSent t.onReaderReconnectRequest(p) } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func TopicOnReaderPartitionReadStartResponse(t *Topic, readerConnectionID string, partitionContext context.Context, topic string, partitionID int64, partitionSessionID int64) func(readOffset *int64, commitOffset *int64, _ error) { var p TopicReaderPartitionReadStartResponseStartInfo p.ReaderConnectionID = readerConnectionID @@ -1030,6 +1037,7 @@ func TopicOnReaderPartitionReadStartResponse(t *Topic, readerConnectionID string res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func TopicOnReaderPartitionReadStopResponse(t *Topic, readerConnectionID string, partitionContext context.Context, topic string, partitionID int64, partitionSessionID int64, committedOffset int64, graceful bool) func(error) { var p TopicReaderPartitionReadStopResponseStartInfo p.ReaderConnectionID = readerConnectionID @@ -1046,6 +1054,7 @@ func TopicOnReaderPartitionReadStopResponse(t *Topic, readerConnectionID string, res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func TopicOnReaderCommit(t *Topic, requestContext context.Context, topic string, partitionID int64, partitionSessionID int64, startOffset int64, endOffset int64) func(error) { var p TopicReaderCommitStartInfo p.RequestContext = requestContext @@ -1061,6 +1070,7 @@ func TopicOnReaderCommit(t *Topic, requestContext context.Context, topic string, res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func TopicOnReaderSendCommitMessage(t *Topic, commitsInfo TopicReaderStreamSendCommitMessageStartMessageInfo) func(error) { var p TopicReaderSendCommitMessageStartInfo p.CommitsInfo = commitsInfo @@ -1071,6 +1081,7 @@ func TopicOnReaderSendCommitMessage(t *Topic, commitsInfo TopicReaderStreamSendC res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func TopicOnReaderCommittedNotify(t *Topic, readerConnectionID string, topic string, partitionID int64, partitionSessionID int64, committedOffset int64) { var p TopicReaderCommittedNotifyInfo p.ReaderConnectionID = readerConnectionID @@ -1080,6 +1091,7 @@ func TopicOnReaderCommittedNotify(t *Topic, readerConnectionID string, topic str p.CommittedOffset = committedOffset t.onReaderCommittedNotify(p) } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func TopicOnReaderClose(t *Topic, readerConnectionID string, closeReason error) func(closeError error) { var p TopicReaderCloseStartInfo p.ReaderConnectionID = readerConnectionID @@ -1091,6 +1103,7 @@ func TopicOnReaderClose(t *Topic, readerConnectionID string, closeReason error) res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func TopicOnReaderInit(t *Topic, preInitReaderConnectionID string, initRequestInfo TopicReadStreamInitRequestInfo) func(readerConnectionID string, _ error) { var p TopicReaderInitStartInfo p.PreInitReaderConnectionID = preInitReaderConnectionID @@ -1103,12 +1116,14 @@ func TopicOnReaderInit(t *Topic, preInitReaderConnectionID string, initRequestIn res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func TopicOnReaderError(t *Topic, readerConnectionID string, e error) { var p TopicReaderErrorInfo p.ReaderConnectionID = readerConnectionID p.Error = e t.onReaderError(p) } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func TopicOnReaderUpdateToken(t *Topic, readerConnectionID string) func(tokenLen int, _ error) func(error) { var p OnReadUpdateTokenStartInfo p.ReaderConnectionID = readerConnectionID @@ -1125,6 +1140,7 @@ func TopicOnReaderUpdateToken(t *Topic, readerConnectionID string) func(tokenLen } } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func TopicOnReaderSentDataRequest(t *Topic, readerConnectionID string, requestBytes int, localBufferSizeAfterSent int) { var p TopicReaderSentDataRequestInfo p.ReaderConnectionID = readerConnectionID @@ -1132,6 +1148,7 @@ func TopicOnReaderSentDataRequest(t *Topic, readerConnectionID string, requestBy p.LocalBufferSizeAfterSent = localBufferSizeAfterSent t.onReaderSentDataRequest(p) } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func TopicOnReaderReceiveDataResponse(t *Topic, readerConnectionID string, localBufferSizeAfterReceive int, dataResponse TopicReaderDataResponseInfo) func(error) { var p TopicReaderReceiveDataResponseStartInfo p.ReaderConnectionID = readerConnectionID @@ -1144,6 +1161,7 @@ func TopicOnReaderReceiveDataResponse(t *Topic, readerConnectionID string, local res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func TopicOnReaderReadMessages(t *Topic, requestContext context.Context, minCount int, maxCount int, freeBufferCapacity int) func(messagesCount int, topic string, partitionID int64, partitionSessionID int64, offsetStart int64, offsetEnd int64, freeBufferCapacity int, _ error) { var p TopicReaderReadMessagesStartInfo p.RequestContext = requestContext @@ -1164,12 +1182,14 @@ func TopicOnReaderReadMessages(t *Topic, requestContext context.Context, minCoun res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func TopicOnReaderUnknownGrpcMessage(t *Topic, readerConnectionID string, e error) { var p OnReadUnknownGrpcMessageInfo p.ReaderConnectionID = readerConnectionID p.Error = e t.onReaderUnknownGrpcMessage(p) } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func TopicOnWriterReconnect(t *Topic, writerInstanceID string, topic string, producerID string, attempt int) func(error) { var p TopicWriterReconnectStartInfo p.WriterInstanceID = writerInstanceID @@ -1183,6 +1203,7 @@ func TopicOnWriterReconnect(t *Topic, writerInstanceID string, topic string, pro res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func TopicOnWriterInitStream(t *Topic, writerInstanceID string, topic string, producerID string) func(sessionID string, _ error) { var p TopicWriterInitStreamStartInfo p.WriterInstanceID = writerInstanceID @@ -1196,6 +1217,7 @@ func TopicOnWriterInitStream(t *Topic, writerInstanceID string, topic string, pr res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func TopicOnWriterClose(t *Topic, writerInstanceID string, reason error) func(error) { var p TopicWriterCloseStartInfo p.WriterInstanceID = writerInstanceID @@ -1207,6 +1229,7 @@ func TopicOnWriterClose(t *Topic, writerInstanceID string, reason error) func(er res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func TopicOnWriterCompressMessages(t *Topic, writerInstanceID string, sessionID string, codec int32, firstSeqNo int64, messagesCount int, reason TopicWriterCompressMessagesReason) func(error) { var p TopicWriterCompressMessagesStartInfo p.WriterInstanceID = writerInstanceID @@ -1222,6 +1245,7 @@ func TopicOnWriterCompressMessages(t *Topic, writerInstanceID string, sessionID res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func TopicOnWriterSendMessages(t *Topic, writerInstanceID string, sessionID string, codec int32, firstSeqNo int64, messagesCount int) func(error) { var p TopicWriterSendMessagesStartInfo p.WriterInstanceID = writerInstanceID @@ -1236,6 +1260,7 @@ func TopicOnWriterSendMessages(t *Topic, writerInstanceID string, sessionID stri res(p) } } +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals func TopicOnWriterReadUnknownGrpcMessage(t *Topic, writerInstanceID string, sessionID string, e error) { var p TopicOnWriterReadUnknownGrpcMessageInfo p.WriterInstanceID = writerInstanceID diff --git a/trace/trace_test.go b/trace/trace_test.go index fbdf19668..b10945067 100644 --- a/trace/trace_test.go +++ b/trace/trace_test.go @@ -6,6 +6,82 @@ import ( "testing" ) +// Stub is a helper function that stubs all functional fields of x with given f. +func Stub(x interface{}, f func(name string, args ...interface{})) { + (FieldStubber{ + OnCall: f, + }).Stub(reflect.ValueOf(x)) +} + +func ClearContext(x interface{}) interface{} { + p := reflect.ValueOf(x).Index(0) + t := p.Elem().Type() + f, has := t.FieldByName("Context") + if has && f.Type.Kind() == reflect.Interface { + x := reflect.New(t) + x.Elem().Set(p.Elem()) + c := x.Elem().FieldByName(f.Name) + c.Set(reflect.Zero(c.Type())) + p.Set(x) + } + + return p.Interface() +} + +// FieldStubber contains options of filling all struct functional fields. +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals +type FieldStubber struct { + // OnStub is an optional callback that is called when field getting + // stubbed. + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnStub func(name string) + + // OnCall is an optional callback that will be called for each stubbed + // field getting called. + // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals + OnCall func(name string, args ...interface{}) +} + +// Stub fills in given x struct. +// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals +func (f FieldStubber) Stub(x reflect.Value) { + var ( + v = x.Elem() + t = v.Type() + ) + for i := 0; i < t.NumField(); i++ { + var ( + fx = v.Field(i) + ft = fx.Type() + ) + if ft.Kind() != reflect.Func { + continue + } + name := t.Field(i).Name + if f.OnStub != nil { + f.OnStub(name) + } + out := []reflect.Value{} + for i := 0; i < ft.NumOut(); i++ { + ti := reflect.New(ft.Out(i)).Elem() + out = append(out, ti) + } + fn := reflect.MakeFunc(ft, func(args []reflect.Value) []reflect.Value { + if f.OnCall == nil { + return out + } + params := make([]interface{}, len(args)) + for i, arg := range args { + params[i] = arg.Interface() + } + f.OnCall(name, params...) + + return out + }) + fx.Set(fn) + } +} + func TestTable(t *testing.T) { testSingleTrace(t, &Table{}, "Table") } diff --git a/trace/traceutil.go b/trace/traceutil.go deleted file mode 100644 index 8ad52bfb6..000000000 --- a/trace/traceutil.go +++ /dev/null @@ -1,78 +0,0 @@ -package trace - -import ( - "reflect" -) - -// Stub is a helper function that stubs all functional fields of x with given -// f. -func Stub(x interface{}, f func(name string, args ...interface{})) { - (FieldStubber{ - OnCall: f, - }).Stub(reflect.ValueOf(x)) -} - -func ClearContext(x interface{}) interface{} { - p := reflect.ValueOf(x).Index(0) - t := p.Elem().Type() - f, has := t.FieldByName("Context") - if has && f.Type.Kind() == reflect.Interface { - x := reflect.New(t) - x.Elem().Set(p.Elem()) - c := x.Elem().FieldByName(f.Name) - c.Set(reflect.Zero(c.Type())) - p.Set(x) - } - - return p.Interface() -} - -// FieldStubber contains options of filling all struct functional fields. -type FieldStubber struct { - // OnStub is an optional callback that is called when field getting - // stubbed. - OnStub func(name string) - - // OnCall is an optional callback that will be called for each stubbed - // field getting called. - OnCall func(name string, args ...interface{}) -} - -// Stub fills in given x struct. -func (f FieldStubber) Stub(x reflect.Value) { - var ( - v = x.Elem() - t = v.Type() - ) - for i := 0; i < t.NumField(); i++ { - var ( - fx = v.Field(i) - ft = fx.Type() - ) - if ft.Kind() != reflect.Func { - continue - } - name := t.Field(i).Name - if f.OnStub != nil { - f.OnStub(name) - } - out := []reflect.Value{} - for i := 0; i < ft.NumOut(); i++ { - ti := reflect.New(ft.Out(i)).Elem() - out = append(out, ti) - } - fn := reflect.MakeFunc(ft, func(args []reflect.Value) []reflect.Value { - if f.OnCall == nil { - return out - } - params := make([]interface{}, len(args)) - for i, arg := range args { - params[i] = arg.Interface() - } - f.OnCall(name, params...) - - return out - }) - fx.Set(fn) - } -} diff --git a/with.go b/with.go index 248ae9435..c226db991 100644 --- a/with.go +++ b/with.go @@ -49,7 +49,7 @@ func (d *Driver) With(ctx context.Context, opts ...Option) (*Driver, error) { onDone := trace.DriverOnWith( d.trace(), &ctx, - stack.FunctionID(""), + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/ydb.(*Driver).With"), d.config.Endpoint(), d.config.Database(), d.config.Secure(), ) defer func() {