diff --git a/.github/workflows/breaking.yml b/.github/workflows/breaking.yml index 1670c6f64..17eeb19c3 100644 --- a/.github/workflows/breaking.yml +++ b/.github/workflows/breaking.yml @@ -11,6 +11,9 @@ jobs: group: broken-changes-${{ github.ref }} cancel-in-progress: true runs-on: ubuntu-latest + permissions: + pull-requests: write + contents: read steps: - name: Install Go uses: actions/setup-go@v3 @@ -22,6 +25,8 @@ jobs: 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/check-codegen.yml b/.github/workflows/check-codegen.yml index 6ef0f1ed1..0ee6bd406 100644 --- a/.github/workflows/check-codegen.yml +++ b/.github/workflows/check-codegen.yml @@ -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/examples.yml b/.github/workflows/examples.yml index 39fc2f4d7..745864a37 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -15,11 +15,11 @@ jobs: strategy: fail-fast: false matrix: - ydb-version: [ 22.5, 23.1, 23.2 ] - application: [ native, database_sql, gorm, xorm ] + ydb-version: [ 23.3, 24.1 ] + application: [ native/table, native/query, database_sql, gorm, xorm ] services: ydb: - image: cr.yandex/crpsjg1coh47p81vh2lc/yandex-docker-local-ydb:${{ matrix.ydb-version }} + image: ydbplatform/local-ydb:${{ matrix.ydb-version }} ports: - 2135:2135 - 2136:2136 @@ -29,11 +29,13 @@ jobs: env: YDB_LOCAL_SURVIVE_RESTART: true YDB_USE_IN_MEMORY_PDISKS: true + YDB_TABLE_ENABLE_PREPARED_DDL: true options: '-h localhost' env: OS: ubuntu-latest YDB_CONNECTION_STRING: grpc://localhost:2136/local YDB_ANONYMOUS_CREDENTIALS: 1 + YDB_VERSION: ${{ matrix.ydb-version }} steps: - name: Checkout code uses: actions/checkout@v3 diff --git a/.github/workflows/slo.yml b/.github/workflows/slo.yml index f60064935..d1922303e 100644 --- a/.github/workflows/slo.yml +++ b/.github/workflows/slo.yml @@ -43,29 +43,35 @@ jobs: timeBetweenPhases: 30 shutdownTime: 30 - language_id0: 'native' + language_id0: 'native-table' workload_path0: 'tests/slo' - language0: 'Go SDK native' + language0: 'Native ydb-go-sdk/v3 over table-service' workload_build_context0: ../.. - workload_build_options0: -f Dockerfile --build-arg SRC_PATH=native --build-arg JOB_NAME=workload-native + workload_build_options0: -f Dockerfile --build-arg SRC_PATH=native/table --build-arg JOB_NAME=workload-native-table - language_id1: 'databasesql' + language_id1: 'native-query' workload_path1: 'tests/slo' - language1: 'Go SDK database/sql' + language1: 'Native ydb-go-sdk/v3 over query-service' workload_build_context1: ../.. - workload_build_options1: -f Dockerfile --build-arg SRC_PATH=database/sql --build-arg JOB_NAME=workload-databasesql + workload_build_options1: -f Dockerfile --build-arg SRC_PATH=native/query --build-arg JOB_NAME=workload-native-query - language_id2: 'gorm' + language_id2: 'database-sql' workload_path2: 'tests/slo' - language2: 'Go SDK gorm' + language2: 'Go SDK database/sql' workload_build_context2: ../.. - workload_build_options2: -f Dockerfile --build-arg SRC_PATH=gorm --build-arg JOB_NAME=workload-gorm + workload_build_options2: -f Dockerfile --build-arg SRC_PATH=database/sql --build-arg JOB_NAME=workload-database-sql - language_id3: 'xorm' + language_id3: 'gorm' workload_path3: 'tests/slo' - language3: 'Go SDK xorm' + language3: 'Go SDK gorm' workload_build_context3: ../.. - workload_build_options3: -f Dockerfile --build-arg SRC_PATH=xorm --build-arg JOB_NAME=workload-xorm + workload_build_options3: -f Dockerfile --build-arg SRC_PATH=gorm --build-arg JOB_NAME=workload-gorm + + language_id4: 'xorm' + workload_path4: 'tests/slo' + language4: 'Go SDK xorm' + workload_build_context4: ../.. + workload_build_options4: -f Dockerfile --build-arg SRC_PATH=xorm --build-arg JOB_NAME=workload-xorm - uses: actions/upload-artifact@v3 if: always() diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 11ec91715..756b2fd38 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -45,10 +45,10 @@ jobs: fail-fast: false matrix: go-version: [1.21.x, 1.22.x] - ydb-version: [22.5, 23.1, 23.2, 23.3, 24.1] + ydb-version: [23.3, 24.1] services: ydb: - image: cr.yandex/yc/yandex-docker-local-ydb:${{ matrix.ydb-version }} + image: ydbplatform/local-ydb:${{ matrix.ydb-version }} ports: - 2135:2135 - 2136:2136 @@ -58,6 +58,7 @@ jobs: env: YDB_LOCAL_SURVIVE_RESTART: true YDB_USE_IN_MEMORY_PDISKS: true + YDB_TABLE_ENABLE_PREPARED_DDL: true options: '-h localhost' env: OS: ubuntu-latest diff --git a/.golangci.yml b/.golangci.yml index c9a37ffb2..f14d77b30 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -223,7 +223,6 @@ linters: - cyclop - depguard - dupl - - errname - exhaustive - exhaustivestruct - exhaustruct @@ -239,10 +238,8 @@ linters: - interfacebloat - ireturn - maintidx - - maligned - nonamedreturns - paralleltest - - protogetter - scopelint - structcheck - testableexamples @@ -297,6 +294,7 @@ issues: - predeclared - path: _test\.go linters: + - funlen - unused - unparam - gocritic diff --git a/CHANGELOG.md b/CHANGELOG.md index 8cc8d8da0..b6746f299 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,99 @@ +* 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 +* 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 +* Added `trace.Query.OnResult{New,NextPart,NextResultSet,Close}` events +* Added `trace.Query.OnRow{Scan,ScanNamed,ScanStruct}` events + +## v3.58.1 +* Dropped all deprecated callbacks and events from traces +* Added `trace.Driver.OnConnStream{SendMsg,RecvMsg,CloseSend}` events +* Added `trace.Query.OnSessionExecute` event + +## v3.58.0 +* Changed `List` constructor from `ydb.ParamsBuilder().List().Build().Build()` to `ydb.ParamsBuilder().BeginList().EndList().Build()` +* Changed `Set` constructor from `ydb.ParamsBuilder().Set().Build().Build()` to `ydb.ParamsBuilder().BeginSet().EndSet().Build()` +* Changed `Dict` constructor from `ydb.ParamsBuilder().Dict().Build().Build()` to `ydb.ParamsBuilder().BeginDict().EndDict().Build()` +* Changed `Optional` constructor from `ydb.ParamsBuilder().Set().Build().Build()` to `ydb.ParamsBuilder().BeginOptional().EndOptional().Build()` +* Added events into `trace.Query` trace +* Rewrote `internal/pool` to buffered channel +* Added `internal/xcontext.WithDone()` +* Added `internal/xsync.{OnceFunc,OnceValue}` +* 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()` + +## v3.57.4 +* Added client pid to each gRPC requests to YDB over header `x-ydb-client-pid` +* Added `ydb.WithApplicationName` option +* Added `Dict` support for `ydb.ParamsBuilder()` + +## v3.57.3 +* Added metrics over query service internals +* Added session create and delete events into `trace.Query` +* Moved public type `query.SessionStatus` into `internal/query` package + +## v3.57.2 +* Fixed cases when some option is nil + +## v3.57.1 +* Added logs over query service internals +* Changed `trace.Query` events +* Changed visibility of `query.{Do,DoTx}Options` from public to private + +## v3.57.0 +* Added experimental implementation of query service client +* 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 + +## v3.56.2 +* Fixed return private error for commit to stopped partition in topic reader. +* Stopped wrapping err error as transport error at topic streams (internals) + ## v3.56.1 * Fixed fixenv usage (related to tests only) @@ -32,14 +128,14 @@ ## v3.54.2 * Added context to some internal methods for better tracing -* Added `trace.FunctionID` helper and `FunctionID` field to trace start info's +* Added `trace.FunctionID` helper and `FunctionID` field to trace start info's * Replaced lazy initialization of ydb clients (table, topic, etc.) to explicit initialization on `ydb.Open` step ## v3.54.1 -* Fixed inconsistent labels in `metrics` +* Fixed inconsistent labels in `metrics` ## v3.54.0 -* Allowed `sql.LevelSerializable` isolation level in read-write mode in `database/sql` transactions +* Allowed `sql.LevelSerializable` isolation level in read-write mode in `database/sql` transactions * Refactored traces and metrics * Added `{retry,table}.WithLabel` options for mark retriers calls * Added `ydb.WithTraceRetry` option @@ -63,7 +159,7 @@ * Fixed stringification of credentials object ## v3.53.2 -* Fixed panic when try to unwrap values with more than 127 columns with custom ydb unmarshaler +* Fixed panic when try to unwrap values with more than 127 columns with custom ydb unmarshaler ## v3.53.1 * Bumps `github.com/ydb-platform/ydb-go-genproto` for support `query` service @@ -212,7 +308,7 @@ * Added `table/options.WithCallOptions` options for append custom grpc call options into `session.{BulkUpsert,Execute,StreamExecuteScanQuery}` * Supported fake transactions in `database/sql` driver over connector option `ydb.WithFakeTx(queryMode)` and connection string param `go_fake_tx` * Removed `testutil/timeutil` package (all usages replaced with `clockwork` package) -* Changed behaviour of retryer on transport errors `cancelled` and `deadline exceeded` - will retry idempotent operation if context is not done +* Changed behaviour of retryer on transport errors `cancelled` and `deadline exceeded` - will retry idempotent operation if context is not done * Added address of node to operation error description as optional * Fixed bug with put session from unknown node * Fixed bug with parsing of `TzTimestamp` without microseconds diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 53e4c6849..3760cd632 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -56,7 +56,7 @@ go test -race -tags fast ./... ##### All tests ```sh -docker run -itd --name ydb -dp 2135:2135 -dp 2136:2136 -dp 8765:8765 -v `pwd`/ydb_certs:/ydb_certs -e YDB_LOCAL_SURVIVE_RESTART=true -e YDB_USE_IN_MEMORY_PDISKS=true -h localhost cr.yandex/yc/yandex-docker-local-ydb:latest +docker run -itd --name ydb -dp 2135:2135 -dp 2136:2136 -dp 8765:8765 -v `pwd`/ydb_certs:/ydb_certs -e YDB_LOCAL_SURVIVE_RESTART=true -e YDB_USE_IN_MEMORY_PDISKS=true -h localhost ydbplatform/local-ydb:latest export YDB_CONNECTION_STRING="grpcs://localhost:2135/local" export YDB_SSL_ROOT_CERTIFICATES_FILE="`pwd`/ydb_certs/ca.pem" export YDB_SESSIONS_SHUTDOWN_URLS="http://localhost:8765/actors/kqp_proxy?force_shutdown=all" diff --git a/README.md b/README.md index eb51ff130..66967a039 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. @@ -31,19 +31,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 +63,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 +146,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 +174,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/balancers/config.go b/balancers/config.go index ee70ec57f..8bc38199c 100644 --- a/balancers/config.go +++ b/balancers/config.go @@ -116,9 +116,9 @@ func FromConfig(config string, opts ...fromConfigOption) *balancerConfig.Config b *balancerConfig.Config err error ) - for _, o := range opts { - if o != nil { - o(&h) + for _, opt := range opts { + if opt != nil { + opt(&h) } } diff --git a/config/config.go b/config/config.go index e3578ee58..96756e9ec 100644 --- a/config/config.go +++ b/config/config.go @@ -161,9 +161,19 @@ func WithTraceRetry(t *trace.Retry, opts ...trace.RetryComposeOption) Option { } } +// WithApplicationName add provided application name to all api requests +func WithApplicationName(applicationName string) Option { + return func(c *Config) { + c.metaOptions = append(c.metaOptions, meta.WithApplicationNameOption(applicationName)) + } +} + +// WithUserAgent add provided user agent to all api requests +// +// Deprecated: use WithApplicationName instead func WithUserAgent(userAgent string) Option { return func(c *Config) { - c.metaOptions = append(c.metaOptions, meta.WithUserAgentOption(userAgent)) + c.metaOptions = append(c.metaOptions, meta.WithApplicationNameOption(userAgent)) } } @@ -268,9 +278,9 @@ func ExcludeGRPCCodesForPessimization(codes ...grpcCodes.Code) Option { func New(opts ...Option) *Config { c := defaultConfig() - for _, o := range opts { - if o != nil { - o(c) + for _, opt := range opts { + if opt != nil { + opt(c) } } @@ -281,9 +291,9 @@ func New(opts ...Option) *Config { // With makes copy of current Config with specified options func (c *Config) With(opts ...Option) *Config { - for _, o := range opts { - if o != nil { - o(c) + for _, opt := range opts { + if opt != nil { + opt(c) } } c.meta = meta.New( diff --git a/connection.go b/connection.go index 3e8e3d2a4..9bff94e31 100644 --- a/connection.go +++ b/connection.go @@ -5,6 +5,7 @@ import ( "github.com/ydb-platform/ydb-go-sdk/v3/coordination" "github.com/ydb-platform/ydb-go-sdk/v3/discovery" + "github.com/ydb-platform/ydb-go-sdk/v3/query" "github.com/ydb-platform/ydb-go-sdk/v3/ratelimiter" "github.com/ydb-platform/ydb-go-sdk/v3/scheme" "github.com/ydb-platform/ydb-go-sdk/v3/scripting" @@ -34,6 +35,13 @@ type Connection interface { // Table returns table client Table() table.Client + // Query returns query client + // + // # Experimental + // + // Notice: This API is EXPERIMENTAL and may be changed or removed in a later release. + Query() query.Client + // Scheme returns scheme client Scheme() scheme.Client diff --git a/coordination/coordination.go b/coordination/coordination.go index 7bd9f6dbf..bfe6e9870 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,187 @@ 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 + // + // Notice: This API is EXPERIMENTAL and may be changed or removed in a later release. + 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/driver.go b/driver.go index 65eb86a81..7adfab361 100644 --- a/driver.go +++ b/driver.go @@ -20,6 +20,8 @@ import ( discoveryConfig "github.com/ydb-platform/ydb-go-sdk/v3/internal/discovery/config" "github.com/ydb-platform/ydb-go-sdk/v3/internal/dsn" "github.com/ydb-platform/ydb-go-sdk/v3/internal/endpoint" + internalQuery "github.com/ydb-platform/ydb-go-sdk/v3/internal/query" + queryConfig "github.com/ydb-platform/ydb-go-sdk/v3/internal/query/config" internalRatelimiter "github.com/ydb-platform/ydb-go-sdk/v3/internal/ratelimiter" ratelimiterConfig "github.com/ydb-platform/ydb-go-sdk/v3/internal/ratelimiter/config" internalScheme "github.com/ydb-platform/ydb-go-sdk/v3/internal/scheme" @@ -35,6 +37,7 @@ import ( "github.com/ydb-platform/ydb-go-sdk/v3/internal/xsql" "github.com/ydb-platform/ydb-go-sdk/v3/internal/xsync" "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/ratelimiter" "github.com/ydb-platform/ydb-go-sdk/v3/scheme" "github.com/ydb-platform/ydb-go-sdk/v3/scripting" @@ -47,7 +50,7 @@ import ( var _ Connection = (*Driver)(nil) // Driver type provide access to YDB service clients -type Driver struct { //nolint:maligned +type Driver struct { ctx context.Context // cancel while Driver.Close called. ctxCancel context.CancelFunc @@ -62,25 +65,28 @@ type Driver struct { //nolint:maligned config *config.Config options []config.Option - discovery *internalDiscovery.Client + discovery *xsync.Once[*internalDiscovery.Client] discoveryOptions []discoveryConfig.Option - table *internalTable.Client + table *xsync.Once[*internalTable.Client] tableOptions []tableConfig.Option - scripting *internalScripting.Client + query *xsync.Once[*internalQuery.Client] + queryOptions []queryConfig.Option + + scripting *xsync.Once[*internalScripting.Client] scriptingOptions []scriptingConfig.Option - scheme *internalScheme.Client + scheme *xsync.Once[*internalScheme.Client] schemeOptions []schemeConfig.Option - coordination *internalCoordination.Client + coordination *xsync.Once[*internalCoordination.Client] coordinationOptions []coordinationConfig.Option - ratelimiter *internalRatelimiter.Client + ratelimiter *xsync.Once[*internalRatelimiter.Client] ratelimiterOptions []ratelimiterConfig.Option - topic *topicclientinternal.Client + topic *xsync.Once[*topicclientinternal.Client] topicOptions []topicoptions.TopicOption databaseSQLOptions []xsql.ConnectorOption @@ -109,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) }() @@ -141,6 +149,7 @@ func (d *Driver) Close(ctx context.Context) (finalErr error) { d.scheme.Close, d.scripting.Close, d.table.Close, + d.query.Close, d.topic.Close, d.balancer.Close, d.pool.Release, @@ -177,37 +186,46 @@ func (d *Driver) Secure() bool { // Table returns table client func (d *Driver) Table() table.Client { - return d.table + return d.table.Get() +} + +// Query returns query client +// +// # Experimental +// +// Notice: This API is EXPERIMENTAL and may be changed or removed in a later release. +func (d *Driver) Query() query.Client { + return d.query.Get() } // Scheme returns scheme client func (d *Driver) Scheme() scheme.Client { - return d.scheme + return d.scheme.Get() } // Coordination returns coordination client func (d *Driver) Coordination() coordination.Client { - return d.coordination + return d.coordination.Get() } // Ratelimiter returns ratelimiter client func (d *Driver) Ratelimiter() ratelimiter.Client { - return d.ratelimiter + return d.ratelimiter.Get() } // Discovery returns discovery client func (d *Driver) Discovery() discovery.Client { - return d.discovery + return d.discovery.Get() } // Scripting returns scripting client func (d *Driver) Scripting() scripting.Client { - return d.scripting + return d.scripting.Get() } // Topic returns topic client func (d *Driver) Topic() topic.Client { - return d.topic + return d.topic.Get() } // Open connects to database by DSN and return driver runtime holder @@ -232,7 +250,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() { @@ -268,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() { @@ -284,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() @@ -332,6 +350,7 @@ func newConnectionFromOptions(ctx context.Context, opts ...Option) (_ *Driver, e for _, opt := range []Option{ WithTraceDriver(log.Driver(d.logger, d.loggerDetails, d.loggerOpts...)), //nolint:contextcheck WithTraceTable(log.Table(d.logger, d.loggerDetails, d.loggerOpts...)), //nolint:contextcheck + WithTraceQuery(log.Query(d.logger, d.loggerDetails, d.loggerOpts...)), //nolint:contextcheck WithTraceScripting(log.Scripting(d.logger, d.loggerDetails, d.loggerOpts...)), //nolint:contextcheck WithTraceScheme(log.Scheme(d.logger, d.loggerDetails, d.loggerOpts...)), WithTraceCoordination(log.Coordination(d.logger, d.loggerDetails, d.loggerOpts...)), @@ -383,122 +402,133 @@ func (d *Driver) connect(ctx context.Context) (err error) { return xerrors.WithStackTrace(err) } - d.table, err = internalTable.New(ctx, - d.balancer, - tableConfig.New( - append( - // prepend common params from root config - []tableConfig.Option{ - tableConfig.With(d.config.Common), - }, - d.tableOptions..., - )..., - ), - ) - if err != nil { - return xerrors.WithStackTrace(err) - } + d.table = xsync.OnceValue(func() *internalTable.Client { + return internalTable.New(xcontext.ValueOnly(ctx), + d.balancer, + tableConfig.New( + append( + // prepend common params from root config + []tableConfig.Option{ + tableConfig.With(d.config.Common), + }, + d.tableOptions..., + )..., + ), + ) + }) - d.scheme, err = internalScheme.New(ctx, - d.balancer, - schemeConfig.New( - append( - // prepend common params from root config - []schemeConfig.Option{ - schemeConfig.WithDatabaseName(d.Name()), - schemeConfig.With(d.config.Common), - }, - d.schemeOptions..., - )..., - ), - ) + d.query = xsync.OnceValue(func() *internalQuery.Client { + return internalQuery.New(xcontext.ValueOnly(ctx), + d.balancer, + queryConfig.New( + append( + // prepend common params from root config + []queryConfig.Option{ + queryConfig.With(d.config.Common), + }, + d.queryOptions..., + )..., + ), + ) + }) if err != nil { return xerrors.WithStackTrace(err) } - d.coordination, err = internalCoordination.New(ctx, - d.balancer, - coordinationConfig.New( - append( - // prepend common params from root config - []coordinationConfig.Option{ - coordinationConfig.With(d.config.Common), - }, - d.coordinationOptions..., - )..., - ), - ) - if err != nil { - return xerrors.WithStackTrace(err) - } + d.scheme = xsync.OnceValue(func() *internalScheme.Client { + return internalScheme.New(xcontext.ValueOnly(ctx), + d.balancer, + schemeConfig.New( + append( + // prepend common params from root config + []schemeConfig.Option{ + schemeConfig.WithDatabaseName(d.Name()), + schemeConfig.With(d.config.Common), + }, + d.schemeOptions..., + )..., + ), + ) + }) - d.ratelimiter, err = internalRatelimiter.New(ctx, - d.balancer, - ratelimiterConfig.New( - append( - // prepend common params from root config - []ratelimiterConfig.Option{ - ratelimiterConfig.With(d.config.Common), - }, - d.ratelimiterOptions..., - )..., - ), - ) - if err != nil { - return xerrors.WithStackTrace(err) - } + d.coordination = xsync.OnceValue(func() *internalCoordination.Client { + return internalCoordination.New(xcontext.ValueOnly(ctx), + d.balancer, + coordinationConfig.New( + append( + // prepend common params from root config + []coordinationConfig.Option{ + coordinationConfig.With(d.config.Common), + }, + d.coordinationOptions..., + )..., + ), + ) + }) - d.discovery, err = internalDiscovery.New(ctx, - d.pool.Get(endpoint.New(d.config.Endpoint())), - discoveryConfig.New( - append( - // prepend common params from root config - []discoveryConfig.Option{ - discoveryConfig.With(d.config.Common), - discoveryConfig.WithEndpoint(d.Endpoint()), - discoveryConfig.WithDatabase(d.Name()), - discoveryConfig.WithSecure(d.Secure()), - discoveryConfig.WithMeta(d.config.Meta()), - }, - d.discoveryOptions..., - )..., - ), - ) - if err != nil { - return xerrors.WithStackTrace(err) - } + d.ratelimiter = xsync.OnceValue(func() *internalRatelimiter.Client { + return internalRatelimiter.New(xcontext.ValueOnly(ctx), + d.balancer, + ratelimiterConfig.New( + append( + // prepend common params from root config + []ratelimiterConfig.Option{ + ratelimiterConfig.With(d.config.Common), + }, + d.ratelimiterOptions..., + )..., + ), + ) + }) - d.scripting, err = internalScripting.New(ctx, - d.balancer, - scriptingConfig.New( + d.discovery = xsync.OnceValue(func() *internalDiscovery.Client { + return internalDiscovery.New(xcontext.ValueOnly(ctx), + d.pool.Get(endpoint.New(d.config.Endpoint())), + discoveryConfig.New( + append( + // prepend common params from root config + []discoveryConfig.Option{ + discoveryConfig.With(d.config.Common), + discoveryConfig.WithEndpoint(d.Endpoint()), + discoveryConfig.WithDatabase(d.Name()), + discoveryConfig.WithSecure(d.Secure()), + discoveryConfig.WithMeta(d.config.Meta()), + }, + d.discoveryOptions..., + )..., + ), + ) + }) + + d.scripting = xsync.OnceValue(func() *internalScripting.Client { + return internalScripting.New(xcontext.ValueOnly(ctx), + d.balancer, + scriptingConfig.New( + append( + // prepend common params from root config + []scriptingConfig.Option{ + scriptingConfig.With(d.config.Common), + }, + d.scriptingOptions..., + )..., + ), + ) + }) + + d.topic = xsync.OnceValue(func() *topicclientinternal.Client { + return topicclientinternal.New(xcontext.ValueOnly(ctx), + d.balancer, + d.config.Credentials(), append( // prepend common params from root config - []scriptingConfig.Option{ - scriptingConfig.With(d.config.Common), + []topicoptions.TopicOption{ + topicoptions.WithOperationTimeout(d.config.OperationTimeout()), + topicoptions.WithOperationCancelAfter(d.config.OperationCancelAfter()), }, - d.scriptingOptions..., + d.topicOptions..., )..., - ), - ) - if err != nil { - return xerrors.WithStackTrace(err) - } - - d.topic, err = topicclientinternal.New(ctx, - d.balancer, - d.config.Credentials(), - append( - // prepend common params from root config - []topicoptions.TopicOption{ - topicoptions.WithOperationTimeout(d.config.OperationTimeout()), - topicoptions.WithOperationCancelAfter(d.config.OperationCancelAfter()), - }, - d.topicOptions..., - )..., - ) - if err != nil { - return xerrors.WithStackTrace(err) - } + ) + }) return nil } diff --git a/example_test.go b/example_test.go index 3b06248e6..399411e68 100644 --- a/example_test.go +++ b/example_test.go @@ -3,6 +3,7 @@ package ydb_test import ( "context" "database/sql" + "errors" "fmt" "io" "log" @@ -14,6 +15,7 @@ import ( "github.com/ydb-platform/ydb-go-sdk/v3" "github.com/ydb-platform/ydb-go-sdk/v3/balancers" "github.com/ydb-platform/ydb-go-sdk/v3/config" + "github.com/ydb-platform/ydb-go-sdk/v3/query" "github.com/ydb-platform/ydb-go-sdk/v3/retry" "github.com/ydb-platform/ydb-go-sdk/v3/table" "github.com/ydb-platform/ydb-go-sdk/v3/table/result/named" @@ -21,6 +23,69 @@ import ( "github.com/ydb-platform/ydb-go-sdk/v3/topic/topicoptions" ) +//nolint:testableexamples, nonamedreturns +func Example_query() { + ctx := context.TODO() + db, err := ydb.Open(ctx, "grpc://localhost:2136/local") + if err != nil { + log.Fatal(err) + } + defer db.Close(ctx) // cleanup resources + + 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 $id as myId, $str as myStr`, + query.WithParameters( + ydb.ParamsBuilder(). + Param("$id").Uint64(42). + Param("$str").Text("my string"). + Build(), + ), + ) + 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.Printf("unexpected error: %v", err) + } +} + //nolint:testableexamples, nonamedreturns func Example_table() { ctx := context.TODO() diff --git a/examples/basic/native/README.md b/examples/basic/native/query/README.md similarity index 100% rename from examples/basic/native/README.md rename to examples/basic/native/query/README.md diff --git a/examples/basic/native/query/data.go b/examples/basic/native/query/data.go new file mode 100644 index 000000000..cbdbde95c --- /dev/null +++ b/examples/basic/native/query/data.go @@ -0,0 +1,208 @@ +package main + +import ( + "time" + + "github.com/google/uuid" + "github.com/ydb-platform/ydb-go-sdk/v3/table/types" +) + +func seriesData(id string, released time.Time, title, info, comment string) types.Value { + var commentv types.Value + if comment == "" { + commentv = types.NullValue(types.TypeUTF8) + } else { + commentv = types.OptionalValue(types.TextValue(comment)) + } + + return types.StructValue( + types.StructFieldValue("series_id", types.BytesValueFromString(id)), + types.StructFieldValue("release_date", types.DateValueFromTime(released)), + types.StructFieldValue("title", types.TextValue(title)), + types.StructFieldValue("series_info", types.TextValue(info)), + types.StructFieldValue("comment", commentv), + ) +} + +func seasonData(seriesID, seasonID, title string, first, last time.Time) types.Value { + return types.StructValue( + types.StructFieldValue("series_id", types.BytesValueFromString(seriesID)), + types.StructFieldValue("season_id", types.BytesValueFromString(seasonID)), + types.StructFieldValue("title", types.TextValue(title)), + types.StructFieldValue("first_aired", types.DateValueFromTime(first)), + types.StructFieldValue("last_aired", types.DateValueFromTime(last)), + ) +} + +func episodeData(seriesID, seasonID, episodeID, title string, date time.Time) types.Value { + return types.StructValue( + types.StructFieldValue("series_id", types.BytesValueFromString(seriesID)), + types.StructFieldValue("season_id", types.BytesValueFromString(seasonID)), + types.StructFieldValue("episode_id", types.BytesValueFromString(episodeID)), + types.StructFieldValue("title", types.TextValue(title)), + types.StructFieldValue("air_date", types.DateValueFromTime(date)), + ) +} + +func getData() (series, seasons, episodes []types.Value) { + for seriesID, fill := range map[string]func(seriesID string) ( + seriesData types.Value, seasons []types.Value, episodes []types.Value, + ){ + uuid.New().String(): getDataForITCrowd, + uuid.New().String(): getDataForSiliconValley, + } { + seriesData, seasonsData, episodesData := fill(seriesID) + series = append(series, seriesData) + seasons = append(seasons, seasonsData...) + episodes = append(episodes, episodesData...) + } + + return +} + +func getDataForITCrowd(seriesID string) (series types.Value, seasons, episodes []types.Value) { + series = seriesData( + seriesID, date("2006-02-03"), "IT Crowd", ""+ + "The IT Crowd is a British sitcom produced by Channel 4, written by Graham Linehan, produced by "+ + "Ash Atalla and starring Chris O'Dowd, Richard Ayoade, Katherine Parkinson, and Matt Berry.", + "", // NULL comment. + ) + for _, season := range []struct { //nolint:gocritic + title string + first time.Time + last time.Time + episodes map[string]time.Time + }{ + {"Season 1", date("2006-02-03"), date("2006-03-03"), map[string]time.Time{ + "Yesterday's Jam": date("2006-02-03"), + "Calamity Jen": date("2006-02-03"), + "Fifty-Fifty": date("2006-02-10"), + "The Red Door": date("2006-02-17"), + "The Haunting of Bill Crouse": date("2006-02-24"), + "Aunt Irma Visits": date("2006-03-03"), + }}, + {"Season 2", date("2007-08-24"), date("2007-09-28"), map[string]time.Time{ + "The Work Outing": date("2006-08-24"), + "Return of the Golden Child": date("2007-08-31"), + "Moss and the German": date("2007-09-07"), + "The Dinner Party": date("2007-09-14"), + "Smoke and Mirrors": date("2007-09-21"), + "Men Without Women": date("2007-09-28"), + }}, + {"Season 3", date("2008-11-21"), date("2008-12-26"), map[string]time.Time{ + "From Hell": date("2008-11-21"), + "Are We Not Men?": date("2008-11-28"), + "Tramps Like Us": date("2008-12-05"), + "The Speech": date("2008-12-12"), + "Friendface": date("2008-12-19"), + "Calendar Geeks": date("2008-12-26"), + }}, + {"Season 4", date("2010-06-25"), date("2010-07-30"), map[string]time.Time{ + "Jen The Fredo": date("2010-06-25"), + "The Final Countdown": date("2010-07-02"), + "Something Happened": date("2010-07-09"), + "Italian For Beginners": date("2010-07-16"), + "Bad Boys": date("2010-07-23"), + "Reynholm vs Reynholm": date("2010-07-30"), + }}, + } { + seasonID := uuid.New().String() + seasons = append(seasons, seasonData(seriesID, seasonID, season.title, season.first, season.last)) + for title, date := range season.episodes { + episodes = append(episodes, episodeData(seriesID, seasonID, uuid.New().String(), title, date)) + } + } + + return series, seasons, episodes +} + +func getDataForSiliconValley(seriesID string) (series types.Value, seasons, episodes []types.Value) { + series = seriesData( + seriesID, date("2014-04-06"), "Silicon Valley", ""+ + "Silicon Valley is an American comedy television series created by Mike Judge, John Altschuler and "+ + "Dave Krinsky. The series focuses on five young men who founded a startup company in Silicon Valley.", + "Some comment here", + ) + for _, season := range []struct { //nolint:gocritic + title string + first time.Time + last time.Time + episodes map[string]time.Time + }{ + {"Season 1", date("2006-02-03"), date("2006-03-03"), map[string]time.Time{ + "Minimum Viable Product": date("2014-04-06"), + "The Cap Table": date("2014-04-13"), + "Articles of Incorporation": date("2014-04-20"), + "Fiduciary Duties": date("2014-04-27"), + "Signaling Risk": date("2014-05-04"), + "Third Party Insourcing": date("2014-05-11"), + "Proof of Concept": date("2014-05-18"), + "Optimal Tip-to-Tip Efficiency": date("2014-06-01"), + }}, + {"Season 2", date("2007-08-24"), date("2007-09-28"), map[string]time.Time{ + "Sand Hill Shuffle": date("2015-04-12"), + "Runaway Devaluation": date("2015-04-19"), + "Bad Money": date("2015-04-26"), + "The Lady": date("2015-05-03"), + "Server Space": date("2015-05-10"), + "Homicide": date("2015-05-17"), + "Adult Content": date("2015-05-24"), + "White Hat/Black Hat": date("2015-05-31"), + "Binding Arbitration": date("2015-06-07"), + "Two Days of the Condor": date("2015-06-14"), + }}, + {"Season 3", date("2008-11-21"), date("2008-12-26"), map[string]time.Time{ + "Founder Friendly": date("2016-04-24"), + "Two in the Box": date("2016-05-01"), + "Meinertzhagen's Haversack": date("2016-05-08"), + "Maleant Data Systems Solutions": date("2016-05-15"), + "The Empty Chair": date("2016-05-22"), + "Bachmanity Insanity": date("2016-05-29"), + "To Build a Better Beta": date("2016-06-05"), + "Bachman's Earnings Over-Ride": date("2016-06-12"), + "Daily Active Users": date("2016-06-19"), + "The Uptick": date("2016-06-26"), + }}, + {"Season 4", date("2010-06-25"), date("2010-07-30"), map[string]time.Time{ + "Success Failure": date("2017-04-23"), + "Terms of Service": date("2017-04-30"), + "Intellectual Property": date("2017-05-07"), + "Teambuilding Exercise": date("2017-05-14"), + "The Blood Boy": date("2017-05-21"), + "Customer Service": date("2017-05-28"), + "The Patent Troll": date("2017-06-04"), + "The Keenan Vortex": date("2017-06-11"), + "Hooli-Con": date("2017-06-18"), + "Server Error": date("2017-06-25"), + }}, + {"Season 5", date("2018-03-25"), date("2018-05-13"), map[string]time.Time{ + "Grow Fast or Die Slow": date("2018-03-25"), + "Reorientation": date("2018-04-01"), + "Chief Operating Officer": date("2018-04-08"), + "Tech Evangelist": date("2018-04-15"), + "Facial Recognition": date("2018-04-22"), + "Artificial Emotional Intelligence": date("2018-04-29"), + "Initial Coin Offering": date("2018-05-06"), + "Fifty-One Percent": date("2018-05-13"), + }}, + } { + seasonID := uuid.New().String() + seasons = append(seasons, seasonData(seriesID, seasonID, season.title, season.first, season.last)) + for title, date := range season.episodes { + episodes = append(episodes, episodeData(seriesID, seasonID, uuid.New().String(), title, date)) + } + } + + return series, seasons, episodes +} + +const dateISO8601 = "2006-01-02" + +func date(date string) time.Time { + t, err := time.Parse(dateISO8601, date) + if err != nil { + panic(err) + } + + return t +} diff --git a/examples/basic/native/query/main.go b/examples/basic/native/query/main.go new file mode 100644 index 000000000..bf13c8f2f --- /dev/null +++ b/examples/basic/native/query/main.go @@ -0,0 +1,87 @@ +package main + +import ( + "context" + "fmt" + "os" + "path" + "strings" + "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/sugar" +) + +func isYdbVersionHaveQueryService() error { + minYdbVersion := strings.Split("24.1", ".") + ydbVersion := strings.Split(os.Getenv("YDB_VERSION"), ".") + for i, component := range ydbVersion { + if i < len(minYdbVersion) { + if r := strings.Compare(component, minYdbVersion[i]); r < 0 { + return fmt.Errorf("example '%s' run on minimal YDB version '%v', but current version is '%s'", + os.Args[0], + strings.Join(minYdbVersion, "."), + func() string { + if len(ydbVersion) > 0 && ydbVersion[0] != "" { + return strings.Join(ydbVersion, ".") + } + + return "undefined" + }(), + ) + } else if r > 0 { + return nil + } + } + } + + return nil +} + +func main() { + if err := isYdbVersionHaveQueryService(); err != nil { + fmt.Println(err.Error()) + + return + } + + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + dsn, exists := os.LookupEnv("YDB_CONNECTION_STRING") + if !exists { + panic("YDB_CONNECTION_STRING environment variable not defined") + } + + db, err := ydb.Open(ctx, + dsn, + environ.WithEnvironCredentials(ctx), + ) + if err != nil { + panic(fmt.Errorf("connect error: %w", err)) + } + defer func() { _ = db.Close(ctx) }() + + prefix := path.Join(db.Name(), "native/query") + + err = sugar.RemoveRecursive(ctx, db, prefix) + if err != nil { + panic(err) + } + + err = createTables(ctx, db.Query(), prefix) + if err != nil { + panic(fmt.Errorf("create tables error: %w", err)) + } + + err = fillTablesWithData(ctx, db.Query(), prefix) + if err != nil { + panic(fmt.Errorf("fill tables with data error: %w", err)) + } + + err = read(ctx, db.Query(), prefix) + if err != nil { + panic(fmt.Errorf("select simple error: %w", err)) + } +} diff --git a/examples/basic/native/query/series.go b/examples/basic/native/query/series.go new file mode 100644 index 000000000..6c66d3912 --- /dev/null +++ b/examples/basic/native/query/series.go @@ -0,0 +1,202 @@ +package main + +import ( + "context" + "errors" + "fmt" + "io" + "log" + "path" + "time" + + ydb "github.com/ydb-platform/ydb-go-sdk/v3" + "github.com/ydb-platform/ydb-go-sdk/v3/query" +) + +func read(ctx context.Context, c query.Client, prefix string) error { + return c.Do(ctx, + func(ctx context.Context, s query.Session) (err error) { + _, result, err := s.Execute(ctx, fmt.Sprintf(` + PRAGMA TablePathPrefix("%s"); + DECLARE $seriesID AS Uint64; + SELECT + series_id, + title, + release_date + FROM + series + `, prefix), + query.WithTxControl(query.TxControl(query.BeginTx(query.WithOnlineReadOnly()))), + query.WithStatsMode(query.StatsModeBasic), + ) + if err != nil { + return err + } + + defer func() { + _ = result.Close(ctx) + }() + + for { + resultSet, err := result.NextResultSet(ctx) + if err != nil { + if errors.Is(err, io.EOF) { + return result.Err() + } + + return err + } + for { + row, err := resultSet.NextRow(ctx) + if err != nil { + if errors.Is(err, io.EOF) { + return result.Err() + } + + return err + } + + var info struct { + SeriesID string `sql:"series_id"` + Title string `sql:"title"` + ReleaseDate time.Time `sql:"release_date"` + } + err = row.ScanStruct(&info) + if err != nil { + return err + } + log.Printf("%+v", info) + } + } + }, + ) +} + +func fillTablesWithData(ctx context.Context, c query.Client, prefix string) error { + series, seasons, episodes := getData() + + return c.Do(ctx, + func(ctx context.Context, s query.Session) (err error) { + _, _, err = s.Execute(ctx, + fmt.Sprintf(` + PRAGMA TablePathPrefix("%s"); + + DECLARE $seriesData AS List>>; + + DECLARE $seasonsData AS List>; + + DECLARE $episodesData AS List>; + + REPLACE INTO series + SELECT + series_id, + title, + series_info, + release_date, + comment + FROM AS_TABLE($seriesData); + + REPLACE INTO seasons + SELECT + series_id, + season_id, + title, + first_aired, + last_aired + FROM AS_TABLE($seasonsData); + + REPLACE INTO episodes + SELECT + series_id, + season_id, + episode_id, + title, + air_date + FROM AS_TABLE($episodesData); + `, prefix), + query.WithParameters(ydb.ParamsBuilder(). + Param("$seriesData").BeginList().AddItems(series...).EndList(). + Param("$seasonsData").BeginList().AddItems(seasons...).EndList(). + Param("$episodesData").BeginList().AddItems(episodes...).EndList(). + Build(), + ), + ) + + return err + }, + ) +} + +func createTables(ctx context.Context, c query.Client, prefix string) error { + return c.Do(ctx, + func(ctx context.Context, s query.Session) error { + _, _, err := s.Execute(ctx, fmt.Sprintf(` + CREATE TABLE IF NOT EXISTS %s ( + series_id Bytes, + title Text, + series_info Text, + release_date Date, + comment Text, + + PRIMARY KEY(series_id) + ) + `, "`"+path.Join(prefix, "series")+"`"), + query.WithTxControl(query.NoTx()), + ) + if err != nil { + return err + } + + _, _, err = s.Execute(ctx, fmt.Sprintf(` + CREATE TABLE IF NOT EXISTS %s ( + series_id Bytes, + season_id Bytes, + title Text, + first_aired Date, + last_aired Date, + + PRIMARY KEY(series_id,season_id) + ) + `, "`"+path.Join(prefix, "seasons")+"`"), + query.WithTxControl(query.NoTx()), + ) + if err != nil { + return err + } + + _, _, err = s.Execute(ctx, fmt.Sprintf(` + CREATE TABLE IF NOT EXISTS %s ( + series_id Bytes, + season_id Bytes, + episode_id Bytes, + title Text, + air_date Date, + + PRIMARY KEY(series_id,season_id,episode_id) + ) + `, "`"+path.Join(prefix, "episodes")+"`"), + query.WithTxControl(query.NoTx()), + ) + if err != nil { + return err + } + + return nil + }, + ) +} diff --git a/examples/basic/native/table/README.md b/examples/basic/native/table/README.md new file mode 100644 index 000000000..1f2543f35 --- /dev/null +++ b/examples/basic/native/table/README.md @@ -0,0 +1,8 @@ +# Basic example via native driver + +Basic example demonstrates the possibilities of `YDB`: + - create/drop/describe tables + - upsert data + - select with data query (request-response API) + - select with scan query (streaming API) + - read table (streaming API) \ No newline at end of file diff --git a/examples/basic/native/data.go b/examples/basic/native/table/data.go similarity index 100% rename from examples/basic/native/data.go rename to examples/basic/native/table/data.go diff --git a/examples/basic/native/main.go b/examples/basic/native/table/main.go similarity index 97% rename from examples/basic/native/main.go rename to examples/basic/native/table/main.go index 43be4ae9a..b624c4f12 100644 --- a/examples/basic/native/main.go +++ b/examples/basic/native/table/main.go @@ -30,7 +30,7 @@ func main() { } defer func() { _ = db.Close(ctx) }() - prefix := path.Join(db.Name(), "native") + prefix := path.Join(db.Name(), "native/table") err = sugar.RemoveRecursive(ctx, db, prefix) if err != nil { diff --git a/examples/basic/native/series.go b/examples/basic/native/table/series.go similarity index 64% rename from examples/basic/native/series.go rename to examples/basic/native/table/series.go index 625c5967e..2a5bc91b4 100644 --- a/examples/basic/native/series.go +++ b/examples/basic/native/table/series.go @@ -9,7 +9,6 @@ import ( "github.com/ydb-platform/ydb-go-sdk/v3/table" "github.com/ydb-platform/ydb-go-sdk/v3/table/options" - "github.com/ydb-platform/ydb-go-sdk/v3/table/result" "github.com/ydb-platform/ydb-go-sdk/v3/table/result/named" "github.com/ydb-platform/ydb-go-sdk/v3/table/types" ) @@ -70,128 +69,127 @@ SELECT FROM AS_TABLE($episodesData); `)) -func readTable(ctx context.Context, c table.Client, path string) (err error) { - var res result.StreamResult - err = c.Do(ctx, - func(ctx context.Context, s table.Session) (err error) { - res, err = s.StreamReadTable(ctx, path, +func readTable(ctx context.Context, c table.Client, path string) error { + return c.Do(ctx, + func(ctx context.Context, s table.Session) error { + res, err := s.StreamReadTable(ctx, path, options.ReadOrdered(), options.ReadColumn("series_id"), options.ReadColumn("title"), options.ReadColumn("release_date"), ) - - return - }, - ) - if err != nil { - return err - } - defer func() { - _ = res.Close() - }() - log.Printf("> read_table:") - var ( - id *uint64 - title *string - date *uint64 - ) - for res.NextResultSet(ctx) { - for res.NextRow() { - err = res.ScanNamed( - named.Optional("series_id", &id), - named.Optional("title", &title), - named.Optional("release_date", &date), - ) if err != nil { return err } - log.Printf("# %d %s %d", *id, *title, *date) - } - } - if err := res.Err(); err != nil { - return err - } - if stats := res.Stats(); stats != nil { - for i := 0; ; i++ { - phase, ok := stats.NextPhase() - if !ok { - break - } - log.Printf( - "# phase #%d: took %s", - i, phase.Duration(), + + defer func() { + _ = res.Close() + }() + + log.Printf("> read_table:") + + var ( + id *uint64 + title *string + date *uint64 ) - for { - tbl, ok := phase.NextTableAccess() - if !ok { - break + + for res.NextResultSet(ctx) { + for res.NextRow() { + err = res.ScanNamed( + named.Optional("series_id", &id), + named.Optional("title", &title), + named.Optional("release_date", &date), + ) + if err != nil { + return err + } + log.Printf("# %d %s %d", *id, *title, *date) + } + } + if err := res.Err(); err != nil { + return err + } + if stats := res.Stats(); stats != nil { + for i := 0; ; i++ { + phase, ok := stats.NextPhase() + if !ok { + break + } + log.Printf( + "# phase #%d: took %s", + i, phase.Duration(), + ) + for { + tbl, ok := phase.NextTableAccess() + if !ok { + break + } + log.Printf( + "# accessed %s: read=(%drows, %dbytes)", + tbl.Name, tbl.Reads.Rows, tbl.Reads.Bytes, + ) + } } - log.Printf( - "# accessed %s: read=(%drows, %dbytes)", - tbl.Name, tbl.Reads.Rows, tbl.Reads.Bytes, - ) } - } - } - return res.Err() + return res.Err() + }, + ) } -func describeTableOptions(ctx context.Context, c table.Client) (err error) { - var desc options.TableOptionsDescription - err = c.Do(ctx, +func describeTableOptions(ctx context.Context, c table.Client) error { + return c.Do(ctx, func(ctx context.Context, s table.Session) (err error) { - desc, err = s.DescribeTableOptions(ctx) + desc, err := s.DescribeTableOptions(ctx) + if err != nil { + return err + } - return - }, - ) - if err != nil { - return err - } - log.Println("> describe_table_options:") + log.Println("> describe_table_options:") - for i := range desc.TableProfilePresets { - log.Printf("TableProfilePresets: %d/%d: %+v", i+1, - len(desc.TableProfilePresets), desc.TableProfilePresets[i], - ) - } - for i := range desc.StoragePolicyPresets { - log.Printf("StoragePolicyPresets: %d/%d: %+v", i+1, - len(desc.StoragePolicyPresets), desc.StoragePolicyPresets[i], - ) - } - for i := range desc.CompactionPolicyPresets { - log.Printf("CompactionPolicyPresets: %d/%d: %+v", i+1, - len(desc.CompactionPolicyPresets), desc.CompactionPolicyPresets[i], - ) - } - for i := range desc.PartitioningPolicyPresets { - log.Printf("PartitioningPolicyPresets: %d/%d: %+v", i+1, - len(desc.PartitioningPolicyPresets), desc.PartitioningPolicyPresets[i], - ) - } - for i := range desc.ExecutionPolicyPresets { - log.Printf("ExecutionPolicyPresets: %d/%d: %+v", i+1, - len(desc.ExecutionPolicyPresets), desc.ExecutionPolicyPresets[i], - ) - } - for i := range desc.ReplicationPolicyPresets { - log.Printf("ReplicationPolicyPresets: %d/%d: %+v", i+1, - len(desc.ReplicationPolicyPresets), desc.ReplicationPolicyPresets[i], - ) - } - for i := range desc.CachingPolicyPresets { - log.Printf("CachingPolicyPresets: %d/%d: %+v", i+1, - len(desc.CachingPolicyPresets), desc.CachingPolicyPresets[i], - ) - } + for i := range desc.TableProfilePresets { + log.Printf("TableProfilePresets: %d/%d: %+v", i+1, + len(desc.TableProfilePresets), desc.TableProfilePresets[i], + ) + } + for i := range desc.StoragePolicyPresets { + log.Printf("StoragePolicyPresets: %d/%d: %+v", i+1, + len(desc.StoragePolicyPresets), desc.StoragePolicyPresets[i], + ) + } + for i := range desc.CompactionPolicyPresets { + log.Printf("CompactionPolicyPresets: %d/%d: %+v", i+1, + len(desc.CompactionPolicyPresets), desc.CompactionPolicyPresets[i], + ) + } + for i := range desc.PartitioningPolicyPresets { + log.Printf("PartitioningPolicyPresets: %d/%d: %+v", i+1, + len(desc.PartitioningPolicyPresets), desc.PartitioningPolicyPresets[i], + ) + } + for i := range desc.ExecutionPolicyPresets { + log.Printf("ExecutionPolicyPresets: %d/%d: %+v", i+1, + len(desc.ExecutionPolicyPresets), desc.ExecutionPolicyPresets[i], + ) + } + for i := range desc.ReplicationPolicyPresets { + log.Printf("ReplicationPolicyPresets: %d/%d: %+v", i+1, + len(desc.ReplicationPolicyPresets), desc.ReplicationPolicyPresets[i], + ) + } + for i := range desc.CachingPolicyPresets { + log.Printf("CachingPolicyPresets: %d/%d: %+v", i+1, + len(desc.CachingPolicyPresets), desc.CachingPolicyPresets[i], + ) + } - return nil + return nil + }, + ) } -func selectSimple(ctx context.Context, c table.Client, prefix string) (err error) { +func selectSimple(ctx context.Context, c table.Client, prefix string) error { query := render( template.Must(template.New("").Parse(` PRAGMA TablePathPrefix("{{ .TablePathPrefix }}"); @@ -220,53 +218,51 @@ func selectSimple(ctx context.Context, c table.Client, prefix string) (err error ), table.CommitTx(), ) - var res result.Result - err = c.Do(ctx, - func(ctx context.Context, s table.Session) (err error) { - _, res, err = s.Execute(ctx, readTx, query, + + return c.Do(ctx, + func(ctx context.Context, s table.Session) error { + _, res, err := s.Execute(ctx, readTx, query, table.NewQueryParameters( table.ValueParam("$seriesID", types.Uint64Value(1)), ), options.WithCollectStatsModeBasic(), ) - - return - }, - ) - if err != nil { - return err - } - - defer func() { - _ = res.Close() - }() - - var ( - id *uint64 - title *string - date *[]byte - ) - for res.NextResultSet(ctx) { - for res.NextRow() { - err = res.ScanNamed( - named.Optional("series_id", &id), - named.Optional("title", &title), - named.Optional("release_date", &date), - ) if err != nil { return err } - log.Printf( - "> select_simple_transaction: %d %s %s", - *id, *title, *date, + + defer func() { + _ = res.Close() + }() + + var ( + id *uint64 + title *string + date *[]byte ) - } - } + for res.NextResultSet(ctx) { + for res.NextRow() { + err = res.ScanNamed( + named.Optional("series_id", &id), + named.Optional("title", &title), + named.Optional("release_date", &date), + ) + if err != nil { + return err + } + log.Printf( + "> select_simple_transaction: %d %s %s", + *id, *title, *date, + ) + } + } - return res.Err() + return res.Err() + }, + ) } -func scanQuerySelect(ctx context.Context, c table.Client, prefix string) (err error) { +func scanQuerySelect(ctx context.Context, c table.Client, prefix string) error { query := render( template.Must(template.New("").Parse(` PRAGMA TablePathPrefix("{{ .TablePathPrefix }}"); @@ -327,14 +323,15 @@ func scanQuerySelect(ctx context.Context, c table.Client, prefix string) (err er ) } -func fillTablesWithData(ctx context.Context, c table.Client, prefix string) (err error) { +func fillTablesWithData(ctx context.Context, c table.Client, prefix string) error { writeTx := table.TxControl( table.BeginTx( table.WithSerializableReadWrite(), ), table.CommitTx(), ) - err = c.Do(ctx, + + return c.Do(ctx, func(ctx context.Context, s table.Session) (err error) { _, _, err = s.Execute(ctx, writeTx, render(fill, templateConfig{ TablePathPrefix: prefix, @@ -347,14 +344,12 @@ func fillTablesWithData(ctx context.Context, c table.Client, prefix string) (err return err }, ) - - return err } -func createTables(ctx context.Context, c table.Client, prefix string) (err error) { - err = c.Do(ctx, +func createTables(ctx context.Context, c table.Client, prefix string) error { + return c.Do(ctx, func(ctx context.Context, s table.Session) error { - return s.CreateTable(ctx, path.Join(prefix, "series"), + err := s.CreateTable(ctx, path.Join(prefix, "series"), options.WithColumn("series_id", types.Optional(types.TypeUint64)), options.WithColumn("title", types.Optional(types.TypeUTF8)), options.WithColumn("series_info", types.Optional(types.TypeUTF8)), @@ -362,15 +357,11 @@ func createTables(ctx context.Context, c table.Client, prefix string) (err error options.WithColumn("comment", types.Optional(types.TypeUTF8)), options.WithPrimaryKeyColumn("series_id"), ) - }, - ) - if err != nil { - return err - } + if err != nil { + return err + } - err = c.Do(ctx, - func(ctx context.Context, s table.Session) error { - return s.CreateTable(ctx, path.Join(prefix, "seasons"), + err = s.CreateTable(ctx, path.Join(prefix, "seasons"), options.WithColumn("series_id", types.Optional(types.TypeUint64)), options.WithColumn("season_id", types.Optional(types.TypeUint64)), options.WithColumn("title", types.Optional(types.TypeUTF8)), @@ -378,15 +369,11 @@ func createTables(ctx context.Context, c table.Client, prefix string) (err error options.WithColumn("last_aired", types.Optional(types.TypeUint64)), options.WithPrimaryKeyColumn("series_id", "season_id"), ) - }, - ) - if err != nil { - return err - } + if err != nil { + return err + } - err = c.Do(ctx, - func(ctx context.Context, s table.Session) error { - return s.CreateTable(ctx, path.Join(prefix, "episodes"), + err = s.CreateTable(ctx, path.Join(prefix, "episodes"), options.WithColumn("series_id", types.Optional(types.TypeUint64)), options.WithColumn("season_id", types.Optional(types.TypeUint64)), options.WithColumn("episode_id", types.Optional(types.TypeUint64)), @@ -394,17 +381,17 @@ func createTables(ctx context.Context, c table.Client, prefix string) (err error options.WithColumn("air_date", types.Optional(types.TypeUint64)), options.WithPrimaryKeyColumn("series_id", "season_id", "episode_id"), ) + if err != nil { + return err + } + + return nil }, ) - if err != nil { - return err - } - - return nil } -func describeTable(ctx context.Context, c table.Client, path string) (err error) { - err = c.Do(ctx, +func describeTable(ctx context.Context, c table.Client, path string) error { + return c.Do(ctx, func(ctx context.Context, s table.Session) error { desc, err := s.DescribeTable(ctx, path) if err != nil { @@ -418,8 +405,6 @@ func describeTable(ctx context.Context, c table.Client, path string) (err error) return nil }, ) - - return } func render(t *template.Template, data interface{}) 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 1a74e556d..4e15e2a0b 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -9,9 +9,10 @@ require ( github.com/prometheus/client_golang v1.13.0 github.com/ydb-platform/gorm-driver v0.0.5 github.com/ydb-platform/ydb-go-sdk-auth-environ v0.1.2 - github.com/ydb-platform/ydb-go-sdk-prometheus v0.11.10 - github.com/ydb-platform/ydb-go-sdk/v3 v3.47.3 + 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 @@ -32,7 +33,8 @@ require ( github.com/golang/snappy v0.0.4 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect - github.com/jackc/pgx/v5 v5.3.0 // indirect + github.com/jackc/pgx/v5 v5.5.4 // indirect + github.com/jackc/puddle/v2 v2.2.1 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/jonboulle/clockwork v0.4.0 // indirect @@ -50,19 +52,17 @@ require ( github.com/syndtr/goleveldb v1.0.0 // indirect github.com/yandex-cloud/go-genproto v0.0.0-20220815090733-4c139c0154e2 // indirect github.com/ydb-platform/ydb-go-genproto v0.0.0-20240126124512-dbb0e1720dbf // indirect - github.com/ydb-platform/ydb-go-sdk-metrics v0.16.3 // indirect 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/sys v0.15.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/tools v0.7.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 // indirect google.golang.org/grpc v1.57.1 // indirect - google.golang.org/protobuf v1.31.0 // indirect + google.golang.org/protobuf v1.33.0 // indirect lukechampine.com/uint128 v1.2.0 // indirect modernc.org/cc/v3 v3.36.3 // indirect modernc.org/ccgo/v3 v3.16.9 // indirect diff --git a/examples/go.sum b/examples/go.sum index 594e5ce12..79278f3b3 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -944,14 +944,17 @@ github.com/jackc/pgx/v4 v4.6.1-0.20200510190926-94ba730bb1e9/go.mod h1:t3/cdRQl6 github.com/jackc/pgx/v4 v4.6.1-0.20200606145419-4e5062306904/go.mod h1:ZDaNWkt9sW1JMiNn0kdYBaLelIhw7Pg4qd+Vk6tw7Hg= github.com/jackc/pgx/v4 v4.11.0/go.mod h1:i62xJgdrtVDsnL3U8ekyrQXEwGNTRoG7/8r+CIdYfcc= github.com/jackc/pgx/v4 v4.12.0/go.mod h1:fE547h6VulLPA3kySjfnSG/e2D861g/50JlVUa/ub60= -github.com/jackc/pgx/v5 v5.3.0 h1:/NQi8KHMpKWHInxXesC8yD4DhkXPrVhmnwYkjp9AmBA= github.com/jackc/pgx/v5 v5.3.0/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8= +github.com/jackc/pgx/v5 v5.5.4 h1:Xp2aQS8uXButQdnCMWNmvx6UysWQQC+u1EoizjguY+8= +github.com/jackc/pgx/v5 v5.5.4/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle/v2 v2.2.0/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= +github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= @@ -1097,7 +1100,6 @@ github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeD github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= -github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_golang v1.13.0 h1:b71QUfeo5M8gq2+evJdTPfZhYMAU0uKPkyPJ7TPsloU= github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= @@ -1126,7 +1128,8 @@ github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1 github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/rekby/fixenv v0.3.2/go.mod h1:/b5LRc06BYJtslRtHKxsPWFT/ySpHV+rWvzTg+XWk4c= +github.com/rekby/fixenv v0.6.1 h1:jUFiSPpajT4WY2cYuc++7Y1zWrnCxnovGCIX72PZniM= +github.com/rekby/fixenv v0.6.1/go.mod h1:/b5LRc06BYJtslRtHKxsPWFT/ySpHV+rWvzTg+XWk4c= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= @@ -1194,16 +1197,12 @@ github.com/ydb-platform/gorm-driver v0.0.5 h1:q6Cg/iSFw4TAmSyMh25YM0GRmr6LVM2gnF github.com/ydb-platform/gorm-driver v0.0.5/go.mod h1:fkCvWZlA3PzL5MiMc7yFOzxUOzLpY1uT8yZo+e4SV4Y= github.com/ydb-platform/xorm v0.0.3 h1:MXk42lANB6r/MMLg/XdJfyXJycGUDlCeLiMlLGDKVPw= github.com/ydb-platform/xorm v0.0.3/go.mod h1:hFsU7EUF0o3S+l5c0eyP2yPVjJ0d4gsFdqCsyazzwBc= -github.com/ydb-platform/ydb-go-genproto v0.0.0-20240125100710-96fd3a874780 h1:E8Z7Zy/fKKDN4bYE1GvXX3DSjp9j7bPJy8Nnpe4Hxqg= -github.com/ydb-platform/ydb-go-genproto v0.0.0-20240125100710-96fd3a874780/go.mod h1:Er+FePu1dNUieD+XTMDduGpQuCPssK5Q4BjF+IIXJ3I= github.com/ydb-platform/ydb-go-genproto v0.0.0-20240126124512-dbb0e1720dbf h1:ckwNHVo4bv2tqNkgx3W3HANh3ta1j6TR5qw08J1A7Tw= github.com/ydb-platform/ydb-go-genproto v0.0.0-20240126124512-dbb0e1720dbf/go.mod h1:Er+FePu1dNUieD+XTMDduGpQuCPssK5Q4BjF+IIXJ3I= github.com/ydb-platform/ydb-go-sdk-auth-environ v0.1.2 h1:EYSI1kulnHb0H0zt3yOw4cRj4ABMSMGwNe43D+fX7e4= github.com/ydb-platform/ydb-go-sdk-auth-environ v0.1.2/go.mod h1:Xfjce+VMU9yJVr1lj60yK2fFPWjB4jr/4cp3K7cjzi4= -github.com/ydb-platform/ydb-go-sdk-metrics v0.16.3 h1:30D5jErLAiGjchVG2D9JiCLbST5LpAiyS7DoUtHkWsU= -github.com/ydb-platform/ydb-go-sdk-metrics v0.16.3/go.mod h1:bqOjIBSt5LtA8fcTprRPGLvlQGkNlqBSRqnL+yZUJh4= -github.com/ydb-platform/ydb-go-sdk-prometheus v0.11.10 h1:eXRJ8nKGv5Dyz7qTDFraahyqlSmOf1/8JqUtlxGlA4o= -github.com/ydb-platform/ydb-go-sdk-prometheus v0.11.10/go.mod h1:7OffPa+OmsJgIP5G+2Cg5oP9+xB5UJSLm5AUpLxi5Uc= +github.com/ydb-platform/ydb-go-sdk-prometheus/v2 v2.0.1 h1:Lsir3AC2VQOTlp8UjZY9zQdCVfWvBNHT3hZn+jSGoo0= +github.com/ydb-platform/ydb-go-sdk-prometheus/v2 v2.0.1/go.mod h1:vofSH6XG0Cr04RV+V3fLp5apOhwDqj1kSoYD9/lmzmE= github.com/ydb-platform/ydb-go-yc v0.8.3/go.mod h1:zUolAFGzJ5XG8uwiseTLr9Lapm7L7hdVdZgLSuv9FXE= github.com/ydb-platform/ydb-go-yc v0.10.1 h1:9SBUpR94tzasEzqYSbBuuEp9mY/jV6xbwPMy3muvV7U= github.com/ydb-platform/ydb-go-yc v0.10.1/go.mod h1:9HaZmOHUWy2MpJ4GZw9j9gR2I82/kb6H8fjsu8b2lxQ= @@ -1240,8 +1239,8 @@ go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/mock v0.3.1-0.20231011042131-892b665398ec h1:aB0WVMCyiVcqL1yMRLM4htiFlMvgdOml97GYnw9su5Q= -go.uber.org/mock v0.3.1-0.20231011042131-892b665398ec/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= +go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= +go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= @@ -1955,8 +1954,8 @@ google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/examples/serverless/url_shortener/service.go b/examples/serverless/url_shortener/service.go index b7a435458..041c55d9d 100644 --- a/examples/serverless/url_shortener/service.go +++ b/examples/serverless/url_shortener/service.go @@ -21,7 +21,7 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" environ "github.com/ydb-platform/ydb-go-sdk-auth-environ" - ydbMetrics "github.com/ydb-platform/ydb-go-sdk-prometheus" + ydbMetrics "github.com/ydb-platform/ydb-go-sdk-prometheus/v2" ydb "github.com/ydb-platform/ydb-go-sdk/v3" "github.com/ydb-platform/ydb-go-sdk/v3/table" "github.com/ydb-platform/ydb-go-sdk/v3/table/options" diff --git a/go.mod b/go.mod index 396a84e87..339491ffc 100644 --- a/go.mod +++ b/go.mod @@ -10,14 +10,14 @@ require ( golang.org/x/sync v0.3.0 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 google.golang.org/grpc v1.57.1 - google.golang.org/protobuf v1.31.0 + google.golang.org/protobuf v1.33.0 ) // requires for tests only require ( github.com/rekby/fixenv v0.6.1 github.com/stretchr/testify v1.7.1 - go.uber.org/mock v0.3.1-0.20231011042131-892b665398ec // indirect + go.uber.org/mock v0.4.0 ) require ( diff --git a/go.sum b/go.sum index 5ab333592..4cfd88ee1 100644 --- a/go.sum +++ b/go.sum @@ -24,8 +24,6 @@ github.com/golang-jwt/jwt/v4 v4.4.1 h1:pC5DB52sCeK48Wlb9oPcdhnjkz1TKt1D/P7WKJ0kU github.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= @@ -49,6 +47,7 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -58,8 +57,6 @@ github.com/jonboulle/clockwork v0.3.0/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUB github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/rekby/fixenv v0.3.2 h1:6AOdQ9Boaa/lOQJTY8GDmQRIhg3S3SD0mIEPkuDSkoQ= -github.com/rekby/fixenv v0.3.2/go.mod h1:/b5LRc06BYJtslRtHKxsPWFT/ySpHV+rWvzTg+XWk4c= github.com/rekby/fixenv v0.6.1 h1:jUFiSPpajT4WY2cYuc++7Y1zWrnCxnovGCIX72PZniM= github.com/rekby/fixenv v0.6.1/go.mod h1:/b5LRc06BYJtslRtHKxsPWFT/ySpHV+rWvzTg+XWk4c= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= @@ -68,34 +65,25 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/ydb-platform/ydb-go-genproto v0.0.0-20231215113745-46f6d30f974a h1:9wx+kCrCQCdwmDe1AFW5yAHdzlo+RV7lcy6y7Zq661s= -github.com/ydb-platform/ydb-go-genproto v0.0.0-20231215113745-46f6d30f974a/go.mod h1:Er+FePu1dNUieD+XTMDduGpQuCPssK5Q4BjF+IIXJ3I= -github.com/ydb-platform/ydb-go-genproto v0.0.0-20240125100710-96fd3a874780 h1:E8Z7Zy/fKKDN4bYE1GvXX3DSjp9j7bPJy8Nnpe4Hxqg= -github.com/ydb-platform/ydb-go-genproto v0.0.0-20240125100710-96fd3a874780/go.mod h1:Er+FePu1dNUieD+XTMDduGpQuCPssK5Q4BjF+IIXJ3I= github.com/ydb-platform/ydb-go-genproto v0.0.0-20240126124512-dbb0e1720dbf h1:ckwNHVo4bv2tqNkgx3W3HANh3ta1j6TR5qw08J1A7Tw= github.com/ydb-platform/ydb-go-genproto v0.0.0-20240126124512-dbb0e1720dbf/go.mod h1:Er+FePu1dNUieD+XTMDduGpQuCPssK5Q4BjF+IIXJ3I= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.uber.org/mock v0.3.1-0.20231011042131-892b665398ec h1:aB0WVMCyiVcqL1yMRLM4htiFlMvgdOml97GYnw9su5Q= -go.uber.org/mock v0.3.1-0.20231011042131-892b665398ec/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= +go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= +go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-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.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -104,7 +92,6 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -112,13 +99,9 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= @@ -128,10 +111,6 @@ golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -165,8 +144,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/internal/allocator/allocator.go b/internal/allocator/allocator.go index f87881079..c86a4f718 100644 --- a/internal/allocator/allocator.go +++ b/internal/allocator/allocator.go @@ -4,6 +4,7 @@ import ( "sync" "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-genproto/protos/Ydb_Table" ) @@ -50,6 +51,15 @@ type ( tableQueryAllocator tableQueryYqlTextAllocator tableQueryIDAllocator + queryExecuteQueryRequestAllocator + queryExecuteQueryRequestQueryContentAllocator + queryExecuteQueryResponsePartAllocator + queryQueryContentAllocator + queryTransactionControlAllocator + queryTransactionControlBeginTxAllocator + queryTransactionControlTxIDAllocator + queryTransactionSettingsAllocator + queryTransactionSettingsSerializableReadWriteAllocator } ) @@ -99,6 +109,15 @@ func (a *Allocator) Free() { a.tableQueryAllocator.free() a.tableQueryYqlTextAllocator.free() a.tableQueryIDAllocator.free() + a.queryExecuteQueryRequestAllocator.free() + a.queryExecuteQueryRequestQueryContentAllocator.free() + a.queryExecuteQueryResponsePartAllocator.free() + a.queryQueryContentAllocator.free() + a.queryTransactionControlAllocator.free() + a.queryTransactionControlBeginTxAllocator.free() + a.queryTransactionControlTxIDAllocator.free() + a.queryTransactionSettingsAllocator.free() + a.queryTransactionSettingsSerializableReadWriteAllocator.free() allocatorPool.Put(a) } @@ -385,7 +404,7 @@ func (a *structAllocator) Struct() (v *Ydb.StructType) { func (a *structAllocator) free() { for _, v := range a.allocations { - members := v.Members + members := v.GetMembers() for i := range members { members[i] = nil } @@ -447,7 +466,7 @@ func (a *tupleAllocator) Tuple() (v *Ydb.TupleType) { func (a *tupleAllocator) free() { for _, v := range a.allocations { - elements := v.Elements + elements := v.GetElements() for i := range elements { elements[i] = nil } @@ -718,8 +737,8 @@ func (a *valueAllocator) Value() (v *Ydb.Value) { func (a *valueAllocator) free() { for _, v := range a.allocations { - items := v.Items - pairs := v.Pairs + items := v.GetItems() + pairs := v.GetPairs() for i := range items { items[i] = nil } @@ -902,6 +921,183 @@ func (a *tableQueryIDAllocator) free() { a.allocations = a.allocations[:0] } +type queryExecuteQueryRequestAllocator struct { + allocations []*Ydb_Query.ExecuteQueryRequest +} + +func (a *queryExecuteQueryRequestAllocator) QueryExecuteQueryRequest() ( + v *Ydb_Query.ExecuteQueryRequest, +) { + v = queryExecuteQueryRequestPool.Get() + a.allocations = append(a.allocations, v) + + return v +} + +func (a *queryExecuteQueryRequestAllocator) free() { + for _, v := range a.allocations { + v.Reset() + queryExecuteQueryRequestPool.Put(v) + } + a.allocations = a.allocations[:0] +} + +type queryExecuteQueryResponsePartAllocator struct { + allocations []*Ydb_Query.ExecuteQueryResponsePart +} + +func (a *queryExecuteQueryResponsePartAllocator) QueryExecuteQueryResponsePart() ( + v *Ydb_Query.ExecuteQueryResponsePart, +) { + v = queryExecuteQueryResponsePartPool.Get() + a.allocations = append(a.allocations, v) + + return v +} + +func (a *queryExecuteQueryResponsePartAllocator) free() { + for _, v := range a.allocations { + v.Reset() + queryExecuteQueryResponsePartPool.Put(v) + } + a.allocations = a.allocations[:0] +} + +type queryExecuteQueryRequestQueryContentAllocator struct { + allocations []*Ydb_Query.ExecuteQueryRequest_QueryContent +} + +func (a *queryExecuteQueryRequestQueryContentAllocator) QueryExecuteQueryRequestQueryContent() ( + v *Ydb_Query.ExecuteQueryRequest_QueryContent, +) { + v = queryExecuteQueryRequestQueryContentPool.Get() + a.allocations = append(a.allocations, v) + + return v +} + +func (a *queryExecuteQueryRequestQueryContentAllocator) free() { + for _, v := range a.allocations { + queryExecuteQueryRequestQueryContentPool.Put(v) + } + a.allocations = a.allocations[:0] +} + +type queryTransactionControlAllocator struct { + allocations []*Ydb_Query.TransactionControl +} + +func (a *queryTransactionControlAllocator) QueryTransactionControl() (v *Ydb_Query.TransactionControl) { + v = queryTransactionControlPool.Get() + a.allocations = append(a.allocations, v) + + return v +} + +func (a *queryTransactionControlAllocator) free() { + for _, v := range a.allocations { + v.Reset() + queryTransactionControlPool.Put(v) + } + a.allocations = a.allocations[:0] +} + +type queryTransactionControlBeginTxAllocator struct { + allocations []*Ydb_Query.TransactionControl_BeginTx +} + +func (a *queryTransactionControlBeginTxAllocator) QueryTransactionControlBeginTx() ( + v *Ydb_Query.TransactionControl_BeginTx, +) { + v = queryTransactionControlBeginTxPool.Get() + a.allocations = append(a.allocations, v) + + return v +} + +func (a *queryTransactionControlBeginTxAllocator) free() { + for _, v := range a.allocations { + queryTransactionControlBeginTxPool.Put(v) + } + a.allocations = a.allocations[:0] +} + +type queryTransactionControlTxIDAllocator struct { + allocations []*Ydb_Query.TransactionControl_TxId +} + +func (a *queryTransactionControlTxIDAllocator) QueryTransactionControlTxID() (v *Ydb_Query.TransactionControl_TxId) { + v = queryTransactionControlTxIDPool.Get() + a.allocations = append(a.allocations, v) + + return v +} + +func (a *queryTransactionControlTxIDAllocator) free() { + for _, v := range a.allocations { + queryTransactionControlTxIDPool.Put(v) + } + a.allocations = a.allocations[:0] +} + +type queryTransactionSettingsAllocator struct { + allocations []*Ydb_Query.TransactionSettings +} + +func (a *queryTransactionSettingsAllocator) QueryTransactionSettings() (v *Ydb_Query.TransactionSettings) { + v = queryTransactionSettingsPool.Get() + a.allocations = append(a.allocations, v) + + return v +} + +func (a *queryTransactionSettingsAllocator) free() { + for _, v := range a.allocations { + v.Reset() + queryTransactionSettingsPool.Put(v) + } + a.allocations = a.allocations[:0] +} + +type queryTransactionSettingsSerializableReadWriteAllocator struct { + allocations []*Ydb_Query.TransactionSettings_SerializableReadWrite +} + +func (a *queryTransactionSettingsSerializableReadWriteAllocator) QueryTransactionSettingsSerializableReadWrite() ( + v *Ydb_Query.TransactionSettings_SerializableReadWrite, +) { + v = queryTransactionSettingsSerializableReadWritePool.Get() + a.allocations = append(a.allocations, v) + + return v +} + +func (a *queryTransactionSettingsSerializableReadWriteAllocator) free() { + for _, v := range a.allocations { + queryTransactionSettingsSerializableReadWritePool.Put(v) + } + a.allocations = a.allocations[:0] +} + +type queryQueryContentAllocator struct { + allocations []*Ydb_Query.QueryContent +} + +func (a *queryQueryContentAllocator) QueryQueryContent() (v *Ydb_Query.QueryContent) { + v = queryQueryContentPool.Get() + a.allocations = append(a.allocations, v) + + return v +} + +func (a *queryQueryContentAllocator) free() { + for _, v := range a.allocations { + v.Reset() + queryQueryContentPool.Put(v) + } + a.allocations = a.allocations[:0] +} + type Pool[T any] sync.Pool func (p *Pool[T]) Get() *T { @@ -919,46 +1115,55 @@ func (p *Pool[T]) Put(t *T) { } var ( - allocatorPool Pool[Allocator] - valuePool Pool[Ydb.Value] - typePool Pool[Ydb.Type] - typeDecimalPool Pool[Ydb.Type_DecimalType] - typeListPool Pool[Ydb.Type_ListType] - typeEmptyListPool Pool[Ydb.Type_EmptyListType] - typeEmptyDictPool Pool[Ydb.Type_EmptyDictType] - typeTuplePool Pool[Ydb.Type_TupleType] - typeStructPool Pool[Ydb.Type_StructType] - typeDictPool Pool[Ydb.Type_DictType] - typeVariantPool Pool[Ydb.Type_VariantType] - decimalPool Pool[Ydb.DecimalType] - listPool Pool[Ydb.ListType] - tuplePool Pool[Ydb.TupleType] - structPool Pool[Ydb.StructType] - dictPool Pool[Ydb.DictType] - variantPool Pool[Ydb.VariantType] - variantTupleItemsPool Pool[Ydb.VariantType_TupleItems] - variantStructItemsPool Pool[Ydb.VariantType_StructItems] - structMemberPool Pool[Ydb.StructMember] - typeOptionalPool Pool[Ydb.Type_OptionalType] - optionalPool Pool[Ydb.OptionalType] - typedValuePool Pool[Ydb.TypedValue] - boolPool Pool[Ydb.Value_BoolValue] - bytesPool Pool[Ydb.Value_BytesValue] - textPool Pool[Ydb.Value_TextValue] - int32Pool Pool[Ydb.Value_Int32Value] - uint32Pool Pool[Ydb.Value_Uint32Value] - low128Pool Pool[Ydb.Value_Low_128] - int64Pool Pool[Ydb.Value_Int64Value] - uint64Pool Pool[Ydb.Value_Uint64Value] - floatPool Pool[Ydb.Value_FloatValue] - doublePool Pool[Ydb.Value_DoubleValue] - nestedPool Pool[Ydb.Value_NestedValue] - nullFlagPool Pool[Ydb.Value_NullFlagValue] - pairPool Pool[Ydb.ValuePair] - tableExecuteQueryResultPool Pool[Ydb_Table.ExecuteQueryResult] - tableExecuteDataQueryRequestPool Pool[Ydb_Table.ExecuteDataQueryRequest] - tableQueryCachePolicyPool Pool[Ydb_Table.QueryCachePolicy] - tableQueryPool Pool[Ydb_Table.Query] - tableQueryYqlTextPool Pool[Ydb_Table.Query_YqlText] - tableQueryIDPool Pool[Ydb_Table.Query_Id] + allocatorPool Pool[Allocator] + valuePool Pool[Ydb.Value] + typePool Pool[Ydb.Type] + typeDecimalPool Pool[Ydb.Type_DecimalType] + typeListPool Pool[Ydb.Type_ListType] + typeEmptyListPool Pool[Ydb.Type_EmptyListType] + typeEmptyDictPool Pool[Ydb.Type_EmptyDictType] + typeTuplePool Pool[Ydb.Type_TupleType] + typeStructPool Pool[Ydb.Type_StructType] + typeDictPool Pool[Ydb.Type_DictType] + typeVariantPool Pool[Ydb.Type_VariantType] + decimalPool Pool[Ydb.DecimalType] + listPool Pool[Ydb.ListType] + tuplePool Pool[Ydb.TupleType] + structPool Pool[Ydb.StructType] + dictPool Pool[Ydb.DictType] + variantPool Pool[Ydb.VariantType] + variantTupleItemsPool Pool[Ydb.VariantType_TupleItems] + variantStructItemsPool Pool[Ydb.VariantType_StructItems] + structMemberPool Pool[Ydb.StructMember] + typeOptionalPool Pool[Ydb.Type_OptionalType] + optionalPool Pool[Ydb.OptionalType] + typedValuePool Pool[Ydb.TypedValue] + boolPool Pool[Ydb.Value_BoolValue] + bytesPool Pool[Ydb.Value_BytesValue] + textPool Pool[Ydb.Value_TextValue] + int32Pool Pool[Ydb.Value_Int32Value] + uint32Pool Pool[Ydb.Value_Uint32Value] + low128Pool Pool[Ydb.Value_Low_128] + int64Pool Pool[Ydb.Value_Int64Value] + uint64Pool Pool[Ydb.Value_Uint64Value] + floatPool Pool[Ydb.Value_FloatValue] + doublePool Pool[Ydb.Value_DoubleValue] + nestedPool Pool[Ydb.Value_NestedValue] + nullFlagPool Pool[Ydb.Value_NullFlagValue] + pairPool Pool[Ydb.ValuePair] + tableExecuteQueryResultPool Pool[Ydb_Table.ExecuteQueryResult] + tableExecuteDataQueryRequestPool Pool[Ydb_Table.ExecuteDataQueryRequest] + tableQueryCachePolicyPool Pool[Ydb_Table.QueryCachePolicy] + tableQueryPool Pool[Ydb_Table.Query] + tableQueryYqlTextPool Pool[Ydb_Table.Query_YqlText] + tableQueryIDPool Pool[Ydb_Table.Query_Id] + queryExecuteQueryRequestPool Pool[Ydb_Query.ExecuteQueryRequest] + queryExecuteQueryRequestQueryContentPool Pool[Ydb_Query.ExecuteQueryRequest_QueryContent] + queryExecuteQueryResponsePartPool Pool[Ydb_Query.ExecuteQueryResponsePart] + queryQueryContentPool Pool[Ydb_Query.QueryContent] + queryTransactionControlPool Pool[Ydb_Query.TransactionControl] + queryTransactionControlBeginTxPool Pool[Ydb_Query.TransactionControl_BeginTx] + queryTransactionControlTxIDPool Pool[Ydb_Query.TransactionControl_TxId] + queryTransactionSettingsPool Pool[Ydb_Query.TransactionSettings] + queryTransactionSettingsSerializableReadWritePool Pool[Ydb_Query.TransactionSettings_SerializableReadWrite] ) diff --git a/internal/background/worker.go b/internal/background/worker.go index 5f1e90a9a..91c0d1686 100644 --- a/internal/background/worker.go +++ b/internal/background/worker.go @@ -19,19 +19,15 @@ var ( // A Worker must not be copied after first use type Worker struct { - ctx context.Context - workers sync.WaitGroup - onceInit sync.Once - + ctx context.Context + workers sync.WaitGroup + closeReason error tasksCompleted empty.Chan - - m xsync.Mutex - - tasks chan backgroundTask - - closed bool - stop context.CancelFunc - closeReason error + tasks chan backgroundTask + stop context.CancelFunc + onceInit sync.Once + m xsync.Mutex + closed bool } type CallbackFunc func(ctx context.Context) diff --git a/internal/backoff/backoff.go b/internal/backoff/backoff.go index 6069a4204..d4b9cceeb 100644 --- a/internal/backoff/backoff.go +++ b/internal/backoff/backoff.go @@ -86,9 +86,9 @@ func New(opts ...option) logBackoff { b := logBackoff{ r: xrand.New(xrand.WithLock()), } - for _, o := range opts { - if o != nil { - o(&b) + for _, opt := range opts { + if opt != nil { + opt(&b) } } diff --git a/internal/balancer/balancer.go b/internal/balancer/balancer.go index 2ee0140e6..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,14 +174,15 @@ 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 ) defer func() { nodes, added, dropped := endpointsDiff(endpoints, previousConns) - onDone(nodes, added, dropped, localDC, nil) + onDone(nodes, added, dropped, localDC) }() connections := endpointsToConnections(b.pool, endpoints) @@ -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, @@ -257,12 +259,9 @@ func New( pool: pool, localDCDetector: detectLocalDC, } - d, err := internalDiscovery.New(ctx, pool.Get( + d := internalDiscovery.New(ctx, pool.Get( endpoint.New(driverConfig.Endpoint()), ), discoveryConfig) - if err != nil { - return nil, err - } b.discoveryClient = d @@ -283,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()), @@ -374,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/bind/bind.go b/internal/bind/bind.go index 2b4f0a346..c6d1111c6 100644 --- a/internal/bind/bind.go +++ b/internal/bind/bind.go @@ -3,9 +3,9 @@ package bind import ( "sort" + "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" ) type blockID int @@ -27,34 +27,34 @@ type Bind interface { type Bindings []Bind func (bindings Bindings) RewriteQuery(query string, args ...interface{}) ( - yql string, _ *table.QueryParameters, err error, + yql string, parameters []*params.Parameter, err error, ) { if len(bindings) == 0 { - var params []table.ParameterOption - params, err = Params(args...) + parameters, err = Params(args...) if err != nil { return "", nil, xerrors.WithStackTrace(err) } - return query, table.NewQueryParameters(params...), nil + return query, parameters, nil } buffer := xstring.Buffer() defer buffer.Free() for i := range bindings { - query, args, err = bindings[len(bindings)-1-i].RewriteQuery(query, args...) - if err != nil { - return "", nil, xerrors.WithStackTrace(err) + var e error + query, args, e = bindings[len(bindings)-1-i].RewriteQuery(query, args...) + if e != nil { + return "", nil, xerrors.WithStackTrace(e) } } - params, err := Params(args...) + parameters, err = Params(args...) if err != nil { return "", nil, xerrors.WithStackTrace(err) } - return query, table.NewQueryParameters(params...), nil + return query, parameters, nil } func Sort(bindings []Bind) []Bind { diff --git a/internal/bind/numeric_args.go b/internal/bind/numeric_args.go index 89c9c4ecc..fac536068 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,38 +52,20 @@ 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 - if newArgs[p-1] == nil { - param, err = toYdbParam(paramName, args[p-1]) - if err != nil { - return "", nil, xerrors.WithStackTrace(err) - } - newArgs[p-1] = param - buffer.WriteString(param.Name()) - } else { - buffer.WriteString(newArgs[p-1].(table.ParameterOption).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)), - ) + paramIndex := int(p - 1) + buffer.WriteString(newArgs[paramIndex].(table.ParameterOption).Name()) } } + 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 +115,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 a817dd2cb..95653b996 100644 --- a/internal/bind/params.go +++ b/internal/bind/params.go @@ -9,9 +9,9 @@ import ( "sort" "time" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/params" "github.com/ydb-platform/ydb-go-sdk/v3/internal/value" "github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors" - "github.com/ydb-platform/ydb-go-sdk/v3/table" "github.com/ydb-platform/ydb-go-sdk/v3/table/types" ) @@ -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() @@ -142,7 +142,7 @@ func supportNewTypeLink(x interface{}) string { return "https://github.com/ydb-platform/ydb-go-sdk/issues/new?" + v.Encode() } -func toYdbParam(name string, value interface{}) (table.ParameterOption, error) { +func toYdbParam(name string, value interface{}) (*params.Parameter, error) { if na, ok := value.(driver.NamedValue); ok { n, v := na.Name, na.Value if n != "" { @@ -157,7 +157,7 @@ func toYdbParam(name string, value interface{}) (table.ParameterOption, error) { } value = v } - if v, ok := value.(table.ParameterOption); ok { + if v, ok := value.(*params.Parameter); ok { return v, nil } v, err := toValue(value) @@ -171,69 +171,73 @@ func toYdbParam(name string, value interface{}) (table.ParameterOption, error) { name = "$" + name } - return table.ValueParam(name, v), nil + return params.Named(name, v), nil } -func Params(args ...interface{}) (params []table.ParameterOption, _ error) { - params = make([]table.ParameterOption, 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 *table.QueryParameters: - if len(args) > 1 { - return nil, xerrors.WithStackTrace(errMultipleQueryParameters) - } - xx.Each(func(name string, v types.Value) { - params = append(params, table.ValueParam(name, v)) - }) - case table.ParameterOption: - params = append(params, xx) - default: - x.Name = fmt.Sprintf("$p%d", i) - param, err := toYdbParam(x.Name, x.Value) - if err != nil { - return nil, xerrors.WithStackTrace(err) - } - params = append(params, param) - } - } else { - param, err := toYdbParam(x.Name, x.Value) - if err != nil { - return nil, xerrors.WithStackTrace(err) - } - params = append(params, 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) - } - params = append(params, param) - case *table.QueryParameters: + newParam, err = toYdbParam(x.Name, x.Value) + newParams = append(newParams, newParam) + case *params.Parameters: if len(args) > 1 { return nil, xerrors.WithStackTrace(errMultipleQueryParameters) } - x.Each(func(name string, v types.Value) { - params = append(params, table.ValueParam(name, v)) - }) - case table.ParameterOption: - params = append(params, x) + parameters = *x + case *params.Parameter: + newParams = append(newParams, x) default: - param, err := toYdbParam(fmt.Sprintf("$p%d", i), x) + 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() + }) + + 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) } - params = append(params, param) + + return []*params.Parameter{param}, nil + } + } else { + param, err := toYdbParam(arg.Name, arg.Value) + if err != nil { + return nil, xerrors.WithStackTrace(err) } - } - sort.Slice(params, func(i, j int) bool { - return params[i].Name() < params[j].Name() - }) - return params, nil + return []*params.Parameter{param}, nil + } } diff --git a/internal/bind/params_test.go b/internal/bind/params_test.go index 80d47b4a3..d715c07a1 100644 --- a/internal/bind/params_test.go +++ b/internal/bind/params_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/require" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/params" "github.com/ydb-platform/ydb-go-sdk/v3/table" "github.com/ydb-platform/ydb-go-sdk/v3/table/types" ) @@ -346,27 +347,27 @@ func named(name string, value interface{}) driver.NamedValue { func TestYdbParam(t *testing.T) { for _, tt := range []struct { src interface{} - dst table.ParameterOption + dst *params.Parameter err error }{ { - src: table.ValueParam("$a", types.Int32Value(42)), - dst: table.ValueParam("$a", types.Int32Value(42)), + src: params.Named("$a", types.Int32Value(42)), + dst: params.Named("$a", types.Int32Value(42)), err: nil, }, { src: named("a", int(42)), - dst: table.ValueParam("$a", types.Int32Value(42)), + dst: params.Named("$a", types.Int32Value(42)), err: nil, }, { src: named("$a", int(42)), - dst: table.ValueParam("$a", types.Int32Value(42)), + dst: params.Named("$a", types.Int32Value(42)), err: nil, }, { src: named("a", uint(42)), - dst: table.ValueParam("$a", types.Uint32Value(42)), + dst: params.Named("$a", types.Uint32Value(42)), err: nil, }, { @@ -389,50 +390,50 @@ func TestYdbParam(t *testing.T) { func TestArgsToParams(t *testing.T) { for _, tt := range []struct { args []interface{} - params []table.ParameterOption + params []*params.Parameter err error }{ { args: []interface{}{}, - params: []table.ParameterOption{}, + params: []*params.Parameter{}, err: nil, }, { args: []interface{}{ 1, uint64(2), "3", }, - params: []table.ParameterOption{ - table.ValueParam("$p0", types.Int32Value(1)), - table.ValueParam("$p1", types.Uint64Value(2)), - table.ValueParam("$p2", types.TextValue("3")), + params: []*params.Parameter{ + params.Named("$p0", types.Int32Value(1)), + params.Named("$p1", types.Uint64Value(2)), + params.Named("$p2", types.TextValue("3")), }, err: nil, }, { args: []interface{}{ table.NewQueryParameters( - table.ValueParam("$p0", types.Int32Value(1)), - table.ValueParam("$p1", types.Uint64Value(2)), - table.ValueParam("$p2", types.TextValue("3")), + params.Named("$p0", types.Int32Value(1)), + params.Named("$p1", types.Uint64Value(2)), + params.Named("$p2", types.TextValue("3")), ), table.NewQueryParameters( - table.ValueParam("$p0", types.Int32Value(1)), - table.ValueParam("$p1", types.Uint64Value(2)), - table.ValueParam("$p2", types.TextValue("3")), + params.Named("$p0", types.Int32Value(1)), + params.Named("$p1", types.Uint64Value(2)), + params.Named("$p2", types.TextValue("3")), ), }, err: errMultipleQueryParameters, }, { args: []interface{}{ - table.ValueParam("$p0", types.Int32Value(1)), - table.ValueParam("$p1", types.Uint64Value(2)), - table.ValueParam("$p2", types.TextValue("3")), + params.Named("$p0", types.Int32Value(1)), + params.Named("$p1", types.Uint64Value(2)), + params.Named("$p2", types.TextValue("3")), }, - params: []table.ParameterOption{ - table.ValueParam("$p0", types.Int32Value(1)), - table.ValueParam("$p1", types.Uint64Value(2)), - table.ValueParam("$p2", types.TextValue("3")), + params: []*params.Parameter{ + params.Named("$p0", types.Int32Value(1)), + params.Named("$p1", types.Uint64Value(2)), + params.Named("$p2", types.TextValue("3")), }, err: nil, }, @@ -442,10 +443,10 @@ func TestArgsToParams(t *testing.T) { sql.Named("$p1", types.Uint64Value(2)), sql.Named("$p2", types.TextValue("3")), }, - params: []table.ParameterOption{ - table.ValueParam("$p0", types.Int32Value(1)), - table.ValueParam("$p1", types.Uint64Value(2)), - table.ValueParam("$p2", types.TextValue("3")), + params: []*params.Parameter{ + params.Named("$p0", types.Int32Value(1)), + params.Named("$p1", types.Uint64Value(2)), + params.Named("$p2", types.TextValue("3")), }, err: nil, }, @@ -455,23 +456,23 @@ func TestArgsToParams(t *testing.T) { driver.NamedValue{Name: "$p1", Value: types.Uint64Value(2)}, driver.NamedValue{Name: "$p2", Value: types.TextValue("3")}, }, - params: []table.ParameterOption{ - table.ValueParam("$p0", types.Int32Value(1)), - table.ValueParam("$p1", types.Uint64Value(2)), - table.ValueParam("$p2", types.TextValue("3")), + params: []*params.Parameter{ + params.Named("$p0", types.Int32Value(1)), + params.Named("$p1", types.Uint64Value(2)), + params.Named("$p2", types.TextValue("3")), }, err: nil, }, { args: []interface{}{ - driver.NamedValue{Value: table.ValueParam("$p0", types.Int32Value(1))}, - driver.NamedValue{Value: table.ValueParam("$p1", types.Uint64Value(2))}, - driver.NamedValue{Value: table.ValueParam("$p2", types.TextValue("3"))}, + driver.NamedValue{Value: params.Named("$p0", types.Int32Value(1))}, + driver.NamedValue{Value: params.Named("$p1", types.Uint64Value(2))}, + driver.NamedValue{Value: params.Named("$p2", types.TextValue("3"))}, }, - params: []table.ParameterOption{ - table.ValueParam("$p0", types.Int32Value(1)), - table.ValueParam("$p1", types.Uint64Value(2)), - table.ValueParam("$p2", types.TextValue("3")), + params: []*params.Parameter{ + params.Named("$p0", types.Int32Value(1)), + params.Named("$p1", types.Uint64Value(2)), + params.Named("$p2", types.TextValue("3")), }, err: nil, }, @@ -481,37 +482,37 @@ func TestArgsToParams(t *testing.T) { driver.NamedValue{Value: uint64(2)}, driver.NamedValue{Value: "3"}, }, - params: []table.ParameterOption{ - table.ValueParam("$p0", types.Int32Value(1)), - table.ValueParam("$p1", types.Uint64Value(2)), - table.ValueParam("$p2", types.TextValue("3")), + params: []*params.Parameter{ + params.Named("$p0", types.Int32Value(1)), + params.Named("$p1", types.Uint64Value(2)), + params.Named("$p2", types.TextValue("3")), }, err: nil, }, { args: []interface{}{ driver.NamedValue{Value: table.NewQueryParameters( - table.ValueParam("$p0", types.Int32Value(1)), - table.ValueParam("$p1", types.Uint64Value(2)), - table.ValueParam("$p2", types.TextValue("3")), + params.Named("$p0", types.Int32Value(1)), + params.Named("$p1", types.Uint64Value(2)), + params.Named("$p2", types.TextValue("3")), )}, }, - params: []table.ParameterOption{ - table.ValueParam("$p0", types.Int32Value(1)), - table.ValueParam("$p1", types.Uint64Value(2)), - table.ValueParam("$p2", types.TextValue("3")), + params: []*params.Parameter{ + params.Named("$p0", types.Int32Value(1)), + params.Named("$p1", types.Uint64Value(2)), + params.Named("$p2", types.TextValue("3")), }, err: nil, }, { args: []interface{}{ driver.NamedValue{Value: table.NewQueryParameters( - table.ValueParam("$p0", types.Int32Value(1)), - table.ValueParam("$p1", types.Uint64Value(2)), - table.ValueParam("$p2", types.TextValue("3")), + params.Named("$p0", types.Int32Value(1)), + params.Named("$p1", types.Uint64Value(2)), + params.Named("$p2", types.TextValue("3")), )}, - driver.NamedValue{Value: table.ValueParam("$p1", types.Uint64Value(2))}, - driver.NamedValue{Value: table.ValueParam("$p2", types.TextValue("3"))}, + driver.NamedValue{Value: params.Named("$p1", types.Uint64Value(2))}, + driver.NamedValue{Value: params.Named("$p2", types.TextValue("3"))}, }, err: errMultipleQueryParameters, }, 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/.gitignore b/internal/cmd/gtrace/.gitignore new file mode 100644 index 000000000..5239aed1b --- /dev/null +++ b/internal/cmd/gtrace/.gitignore @@ -0,0 +1,2 @@ +gtrace +gtrace.exe diff --git a/internal/cmd/gtrace/gtrace b/internal/cmd/gtrace/gtrace deleted file mode 100755 index 634cde9a3..000000000 Binary files a/internal/cmd/gtrace/gtrace and /dev/null differ diff --git a/internal/cmd/gtrace/main.go b/internal/cmd/gtrace/main.go index 685fba4df..30bc5c67d 100644 --- a/internal/cmd/gtrace/main.go +++ b/internal/cmd/gtrace/main.go @@ -16,7 +16,6 @@ import ( "os" "path/filepath" "strings" - _ "unsafe" // For go:linkname. "github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors" ) @@ -70,8 +69,6 @@ func main() { var writers []*Writer if isGoGenerate { - // We should respect Go suffixes like `_linux.go`. - name, tags, ext := splitOSArchTags(&buildCtx, gofile) openFile := func(name string) (*os.File, func()) { var f *os.File //nolint:gofumpt @@ -88,7 +85,9 @@ func main() { return f, func() { f.Close() } } - f, clean := openFile(name + "_gtrace" + tags + ext) + ext := filepath.Ext(gofile) + name := strings.TrimSuffix(gofile, ext) + f, clean := openFile(name + "_gtrace" + ext) defer clean() writers = append(writers, &Writer{ Context: buildCtx, @@ -109,7 +108,7 @@ func main() { ) fset := token.NewFileSet() for _, name := range buildPkg.GoFiles { - base, _, _ := splitOSArchTags(&buildCtx, name) + base := strings.TrimSuffix(name, filepath.Ext(name)) if isGenerated(base, "_gtrace") { continue } diff --git a/internal/conn/config.go b/internal/conn/config.go index 305fc8c71..df82f3a85 100644 --- a/internal/conn/config.go +++ b/internal/conn/config.go @@ -10,7 +10,7 @@ import ( type Config interface { DialTimeout() time.Duration - Trace() *trace.Driver ConnectionTTL() time.Duration + Trace() *trace.Driver GrpcDialOptions() []grpc.DialOption } diff --git a/internal/conn/conn.go b/internal/conn/conn.go index cec97c202..66278bb94 100644 --- a/internal/conn/conn.go +++ b/internal/conn/conn.go @@ -55,7 +55,7 @@ type conn struct { endpoint endpoint.Endpoint // ro access closed bool state atomic.Uint32 - lastUsage time.Time + lastUsage *lastUsage onClose []func(*conn) onTransportErrors []func(ctx context.Context, cc Conn, cause error) } @@ -80,7 +80,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 { @@ -94,10 +94,18 @@ func (c *conn) IsState(states ...State) bool { return false } +func (c *conn) NodeID() uint32 { + if c != nil { + return c.endpoint.NodeID() + } + + return 0 +} + func (c *conn) park(ctx context.Context) (err error) { onDone := trace.DriverOnConnPark( c.config.Trace(), &ctx, - stack.FunctionID(""), + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/conn.(*conn).park"), c.Endpoint(), ) defer func() { @@ -124,14 +132,6 @@ func (c *conn) park(ctx context.Context) (err error) { return nil } -func (c *conn) NodeID() uint32 { - if c != nil { - return c.endpoint.NodeID() - } - - return 0 -} - func (c *conn) Endpoint() endpoint.Endpoint { if c != nil { return c.endpoint @@ -148,7 +148,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) } @@ -196,7 +196,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() { @@ -213,6 +213,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) }() @@ -240,12 +244,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 } @@ -279,7 +277,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() { @@ -312,7 +310,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 @@ -328,8 +326,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 { @@ -340,6 +338,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) }() @@ -384,6 +386,7 @@ func (c *conn) Invoke( return err } +//nolint:funlen func (c *conn) NewStream( ctx context.Context, desc *grpc.StreamDesc, @@ -391,9 +394,9 @@ func (c *conn) NewStream( opts ...grpc.CallOption, ) (_ grpc.ClientStream, err error) { var ( - streamRecv = trace.DriverOnConnNewStream( + 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) @@ -402,18 +405,7 @@ func (c *conn) NewStream( ) defer func() { - if err != nil { - streamRecv(err)(err, c.GetState(), metadata.MD{}) - } - }() - - var cancel context.CancelFunc - ctx, cancel = xcontext.WithCancel(ctx) - - defer func() { - if err != nil { - cancel() - } + onDone(err, c.GetState()) }() cc, err = c.realConn(ctx) @@ -421,8 +413,8 @@ func (c *conn) NewStream( 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 { @@ -433,6 +425,10 @@ func (c *conn) NewStream( s, err = cc.NewStream(ctx, desc, method, opts...) if err != nil { + if xerrors.IsContextError(err) { + return nil, xerrors.WithStackTrace(err) + } + defer func() { c.onTransportError(ctx, err) }() @@ -454,15 +450,14 @@ func (c *conn) NewStream( return &grpcClientStream{ ClientStream: s, + ctx: ctx, c: c, wrapping: useWrapping, traceID: traceID, sentMark: sentMark, onDone: func(ctx context.Context, md metadata.MD) { - cancel() meta.CallTrailerCallback(ctx, md) }, - recv: streamRecv, }, nil } @@ -495,14 +490,15 @@ 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: newLastUsage(nil), } c.state.Store(uint32(Created)) - for _, o := range opts { - if o != nil { - o(c) + for _, opt := range opts { + if opt != nil { + opt(c) } } diff --git a/internal/conn/error_test.go b/internal/conn/error_test.go index cd9e68b71..569a38a31 100644 --- a/internal/conn/error_test.go +++ b/internal/conn/error_test.go @@ -32,30 +32,30 @@ func TestNodeErrorIs(t *testing.T) { require.NotErrorIs(t, nodeErr, testErr2) } -type testErrorType1 struct { +type testType1Error struct { msg string } -func (t testErrorType1) Error() string { +func (t testType1Error) Error() string { return "1 - " + t.msg } -type testErrorType2 struct { +type testType2Error struct { msg string } -func (t testErrorType2) Error() string { +func (t testType2Error) Error() string { return "2 - " + t.msg } func TestNodeErrorAs(t *testing.T) { - testErr := testErrorType1{msg: "test"} + testErr := testType1Error{msg: "test"} nodeErr := newConnError(1, "localhost:1234", testErr) - target := testErrorType1{} + target := testType1Error{} require.ErrorAs(t, nodeErr, &target) require.Equal(t, testErr, target) - target2 := testErrorType2{} + target2 := testType2Error{} require.False(t, errors.As(nodeErr, &target2)) } diff --git a/internal/conn/grpc_client_stream.go b/internal/conn/grpc_client_stream.go index ca07acb79..32377e5ab 100644 --- a/internal/conn/grpc_client_stream.go +++ b/internal/conn/grpc_client_stream.go @@ -3,32 +3,45 @@ package conn import ( "context" "io" - "time" "github.com/ydb-platform/ydb-go-genproto/protos/Ydb" "google.golang.org/grpc" "google.golang.org/grpc/metadata" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/stack" "github.com/ydb-platform/ydb-go-sdk/v3/internal/wrap" - "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/trace" ) type grpcClientStream struct { grpc.ClientStream + ctx context.Context c *conn wrapping bool traceID string sentMark *modificationMark onDone func(ctx context.Context, md metadata.MD) - recv func(error) func(error, trace.ConnState, map[string][]string) } func (s *grpcClientStream) CloseSend() (err error) { + 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( @@ -46,12 +59,23 @@ func (s *grpcClientStream) CloseSend() (err error) { } func (s *grpcClientStream) SendMsg(m interface{}) (err error) { - cancel := createPinger(s.c) - defer cancel() + 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) }() @@ -77,21 +101,30 @@ func (s *grpcClientStream) SendMsg(m interface{}) (err error) { } func (s *grpcClientStream) RecvMsg(m interface{}) (err error) { - cancel := createPinger(s.c) - defer cancel() + 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() { - onDone := s.recv(xerrors.HideEOF(err)) if err != nil { md := s.ClientStream.Trailer() - onDone(xerrors.HideEOF(err), s.c.GetState(), md) - s.onDone(s.ClientStream.Context(), md) + s.onDone(s.ctx, md) } }() 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) @@ -135,28 +168,8 @@ func (s *grpcClientStream) wrapError(err error) error { return nil } - nodeErr := newConnError(s.c.endpoint.NodeID(), s.c.endpoint.Address(), err) - - return xerrors.WithStackTrace(nodeErr, xerrors.WithSkipDepth(1)) -} - -func createPinger(c *conn) context.CancelFunc { - c.touchLastUsage() - ctx, cancel := xcontext.WithCancel(context.Background()) - go func() { - ticker := time.NewTicker(time.Second) - ctxDone := ctx.Done() - for { - select { - case <-ctxDone: - ticker.Stop() - - return - case <-ticker.C: - c.touchLastUsage() - } - } - }() - - return cancel + return xerrors.WithStackTrace( + newConnError(s.c.endpoint.NodeID(), s.c.endpoint.Address(), err), + xerrors.WithSkipDepth(1), + ) } diff --git a/internal/conn/last_usage.go b/internal/conn/last_usage.go new file mode 100644 index 000000000..b0ca293a9 --- /dev/null +++ b/internal/conn/last_usage.go @@ -0,0 +1,47 @@ +package conn + +import ( + "sync" + "sync/atomic" + "time" + + "github.com/jonboulle/clockwork" +) + +type lastUsage struct { + locks atomic.Int64 + t atomic.Pointer[time.Time] + clock clockwork.Clock +} + +func newLastUsage(clock clockwork.Clock) *lastUsage { + if clock == nil { + clock = clockwork.NewRealClock() + } + now := clock.Now() + usage := &lastUsage{ + clock: clock, + } + usage.t.Store(&now) + + return usage +} + +func (l *lastUsage) Get() time.Time { + if l.locks.Load() == 0 { + return *l.t.Load() + } + + return l.clock.Now() +} + +func (l *lastUsage) Start() (stop func()) { + l.locks.Add(1) + + return sync.OnceFunc(func() { + if l.locks.Add(-1) == 0 { + now := l.clock.Now() + l.t.Store(&now) + } + }) +} diff --git a/internal/conn/last_usage_test.go b/internal/conn/last_usage_test.go new file mode 100644 index 000000000..b7c79695e --- /dev/null +++ b/internal/conn/last_usage_test.go @@ -0,0 +1,98 @@ +package conn + +import ( + "testing" + "time" + + "github.com/jonboulle/clockwork" + "github.com/stretchr/testify/require" +) + +func Test_lastUsage_Lock(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/conn/pool.go b/internal/conn/pool.go index 48a9713a0..246627333 100644 --- a/internal/conn/pool.go +++ b/internal/conn/pool.go @@ -7,6 +7,7 @@ import ( "time" "google.golang.org/grpc" + grpcCodes "google.golang.org/grpc/codes" "github.com/ydb-platform/ydb-go-sdk/v3/internal/closer" "github.com/ydb-platform/ydb-go-sdk/v3/internal/endpoint" @@ -79,6 +80,28 @@ func (p *Pool) Ban(ctx context.Context, cc Conn, cause error) { return } + if !xerrors.IsTransportError(cause, + grpcCodes.ResourceExhausted, + 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 + } + e := cc.Endpoint().Copy() p.mtx.RLock() @@ -91,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)) } @@ -113,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)) } @@ -125,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) }() @@ -207,7 +232,9 @@ func (p *Pool) collectConns() []*conn { } 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{ @@ -217,6 +244,7 @@ func NewPool(ctx context.Context, config Config) *Pool { conns: make(map[connsKey]*conn), done: make(chan struct{}), } + if ttl := config.ConnectionTTL(); ttl > 0 { go p.connParker(xcontext.WithoutDeadline(ctx), ttl, ttl/2) //nolint:gomnd } diff --git a/internal/coordination/client.go b/internal/coordination/client.go index c6ceede63..70322f702 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, error) { +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), - }, nil + 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)) + + if !c.config.AutoRetry() { + return createNode(ctx, c.client, request) + } - return xerrors.WithStackTrace(err) + 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, + } +} + +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 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) + } - return entry, config, xerrors.WithStackTrace(err) + 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 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, + SessionStartTimeout: time.Second * 1, + SessionStopTimeout: time.Second * 1, + SessionKeepAliveTimeout: time.Second * 10, + SessionReconnectDelay: time.Millisecond * 500, + } +} + +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 a4525bfd1..692777a43 100644 --- a/internal/coordination/config/config.go +++ b/internal/coordination/config/config.go @@ -6,8 +6,6 @@ import ( ) // Config is an configuration of coordination client -// -//nolint:maligned type Config struct { config.Common @@ -22,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...) } } @@ -39,9 +37,9 @@ func New(opts ...Option) Config { c := Config{ trace: &trace.Coordination{}, } - for _, o := range opts { - if o != nil { - o(&c) + for _, opt := range opts { + if opt != nil { + opt(&c) } } 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..af10c064c --- /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) + 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) + } + + 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) + 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/access_error.go b/internal/credentials/access_error.go index 70f801bc3..777bc3d80 100644 --- a/internal/credentials/access_error.go +++ b/internal/credentials/access_error.go @@ -94,10 +94,12 @@ func AccessError(msg string, err error, opts ...authErrorOption) error { buffer.WriteString(msg) buffer.WriteString(" (") for i, opt := range opts { - if i != 0 { - buffer.WriteString(",") + if opt != nil { + if i != 0 { + buffer.WriteString(",") + } + opt.applyAuthErrorOption(buffer) } - opt.applyAuthErrorOption(buffer) } buffer.WriteString("): %w") diff --git a/internal/credentials/access_token.go b/internal/credentials/access_token.go index 2d1365827..c6e205f32 100644 --- a/internal/credentials/access_token.go +++ b/internal/credentials/access_token.go @@ -32,7 +32,9 @@ func NewAccessTokenCredentials(token string, opts ...AccessTokenCredentialsOptio sourceInfo: stack.Record(1), } for _, opt := range opts { - opt.ApplyAccessTokenCredentialsOption(c) + if opt != nil { + opt.ApplyAccessTokenCredentialsOption(c) + } } return c diff --git a/internal/credentials/anonymous.go b/internal/credentials/anonymous.go index 7bd75f4c3..88d937095 100644 --- a/internal/credentials/anonymous.go +++ b/internal/credentials/anonymous.go @@ -28,7 +28,9 @@ func NewAnonymousCredentials(opts ...AnonymousCredentialsOption) *Anonymous { sourceInfo: stack.Record(1), } for _, opt := range opts { - opt.ApplyAnonymousCredentialsOption(c) + if opt != nil { + opt.ApplyAnonymousCredentialsOption(c) + } } return c diff --git a/internal/credentials/static.go b/internal/credentials/static.go index 00dcce029..7d9437dfb 100644 --- a/internal/credentials/static.go +++ b/internal/credentials/static.go @@ -49,7 +49,9 @@ func NewStaticCredentials(user, password, endpoint string, opts ...StaticCredent sourceInfo: stack.Record(1), } for _, opt := range opts { - opt.ApplyStaticCredentialsOption(c) + if opt != nil { + opt.ApplyStaticCredentialsOption(c) + } } return c diff --git a/internal/decimal/type.go b/internal/decimal/type.go new file mode 100644 index 000000000..89956a761 --- /dev/null +++ b/internal/decimal/type.go @@ -0,0 +1,19 @@ +package decimal + +import "math/big" + +type Decimal struct { + Bytes [16]byte + Precision uint32 + Scale uint32 +} + +func (d *Decimal) String() string { + v := FromInt128(d.Bytes, d.Precision, d.Scale) + + return Format(v, d.Precision, d.Scale) +} + +func (d *Decimal) BigInt() *big.Int { + return FromInt128(d.Bytes, d.Precision, d.Scale) +} diff --git a/internal/discovery/config/config.go b/internal/discovery/config/config.go index 6ea99d21b..782f3b6b2 100644 --- a/internal/discovery/config/config.go +++ b/internal/discovery/config/config.go @@ -29,9 +29,9 @@ func New(opts ...Option) *Config { interval: DefaultInterval, trace: &trace.Discovery{}, } - for _, o := range opts { - if o != nil { - o(c) + for _, opt := range opts { + if opt != nil { + opt(c) } } diff --git a/internal/discovery/discovery.go b/internal/discovery/discovery.go index 8cd0953a0..dfda2660c 100644 --- a/internal/discovery/discovery.go +++ b/internal/discovery/discovery.go @@ -19,12 +19,12 @@ import ( "github.com/ydb-platform/ydb-go-sdk/v3/trace" ) -func New(ctx context.Context, cc grpc.ClientConnInterface, config *config.Config) (*Client, error) { +func New(ctx context.Context, cc grpc.ClientConnInterface, config *config.Config) *Client { return &Client{ config: config, cc: cc, client: Ydb_Discovery_V1.NewDiscoveryServiceClient(cc), - }, nil + } } var _ discovery.Client = &Client{} @@ -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{ @@ -80,9 +80,9 @@ func (c *Client) Discover(ctx context.Context) (endpoints []endpoint.Endpoint, e } location = result.GetSelfLocation() - endpoints = make([]endpoint.Endpoint, 0, len(result.Endpoints)) - for _, e := range result.Endpoints { - if e.Ssl == c.config.Secure() { + endpoints = make([]endpoint.Endpoint, 0, len(result.GetEndpoints())) + for _, e := range result.GetEndpoints() { + if e.GetSsl() == c.config.Secure() { endpoints = append(endpoints, endpoint.New( net.JoinHostPort(e.GetAddress(), strconv.Itoa(int(e.GetPort()))), endpoint.WithLocation(e.GetLocation()), @@ -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 cdaba3710..823d721d0 100644 --- a/internal/endpoint/endpoint.go +++ b/internal/endpoint/endpoint.go @@ -110,13 +110,10 @@ func (e *endpoint) LastUpdated() time.Time { func (e *endpoint) Touch(opts ...Option) { e.mu.Lock() defer e.mu.Unlock() - for _, o := range append( - []Option{ - withLastUpdated(time.Now()), - }, - opts..., - ) { - o(e) + for _, opt := range append([]Option{withLastUpdated(time.Now())}, opts...) { + if opt != nil { + opt(e) + } } } @@ -163,9 +160,9 @@ func New(address string, opts ...Option) *endpoint { address: address, lastUpdated: time.Now(), } - for _, o := range opts { - if o != nil { - o(e) + for _, opt := range opts { + if opt != nil { + opt(e) } } diff --git a/internal/grpcwrapper/rawscheme/entry.go b/internal/grpcwrapper/rawscheme/entry.go index e0ec8f808..f1fb68bde 100644 --- a/internal/grpcwrapper/rawscheme/entry.go +++ b/internal/grpcwrapper/rawscheme/entry.go @@ -26,25 +26,25 @@ func (e *Entry) FromProto(proto *Ydb_Scheme.Entry) error { if proto == nil { return xerrors.WithStackTrace(errUnexpectedNilForSchemeEntry) } - e.Name = proto.Name - e.Owner = proto.Owner - e.Type = EntryType(proto.Type) + e.Name = proto.GetName() + e.Owner = proto.GetOwner() + e.Type = EntryType(proto.GetType()) - e.EffectivePermissions = make([]Permissions, len(proto.EffectivePermissions)) - for i := range proto.EffectivePermissions { - if err := e.EffectivePermissions[i].FromProto(proto.EffectivePermissions[i]); err != nil { + e.EffectivePermissions = make([]Permissions, len(proto.GetEffectivePermissions())) + for i := range proto.GetEffectivePermissions() { + if err := e.EffectivePermissions[i].FromProto(proto.GetEffectivePermissions()[i]); err != nil { return err } } - e.Permissions = make([]Permissions, len(proto.Permissions)) - for i := range proto.Permissions { - if err := e.Permissions[i].FromProto(proto.Permissions[i]); err != nil { + e.Permissions = make([]Permissions, len(proto.GetPermissions())) + for i := range proto.GetPermissions() { + if err := e.Permissions[i].FromProto(proto.GetPermissions()[i]); err != nil { return err } } - e.SizeBytes = proto.SizeBytes + e.SizeBytes = proto.GetSizeBytes() return nil } @@ -74,8 +74,8 @@ func (p *Permissions) FromProto(proto *Ydb_Scheme.Permissions) error { if proto == nil { return xerrors.WithStackTrace(errUnexpectedNilForSchemePermissions) } - p.Subject = proto.Subject - p.PermissionNames = proto.PermissionNames + p.Subject = proto.GetSubject() + p.PermissionNames = proto.GetPermissionNames() return nil } diff --git a/internal/grpcwrapper/rawtopic/alter_topic.go b/internal/grpcwrapper/rawtopic/alter_topic.go index 125311e01..d7db57d74 100644 --- a/internal/grpcwrapper/rawtopic/alter_topic.go +++ b/internal/grpcwrapper/rawtopic/alter_topic.go @@ -64,7 +64,7 @@ type AlterTopicResult struct { } func (r *AlterTopicResult) FromProto(proto *Ydb_Topic.AlterTopicResponse) error { - return r.Operation.FromProtoWithStatusCheck(proto.Operation) + return r.Operation.FromProtoWithStatusCheck(proto.GetOperation()) } type AlterConsumer struct { diff --git a/internal/grpcwrapper/rawtopic/controlplane_types.go b/internal/grpcwrapper/rawtopic/controlplane_types.go index 3b2070746..6df4322fb 100644 --- a/internal/grpcwrapper/rawtopic/controlplane_types.go +++ b/internal/grpcwrapper/rawtopic/controlplane_types.go @@ -25,7 +25,7 @@ func (c *Consumer) MustFromProto(consumer *Ydb_Topic.Consumer) { c.Important = consumer.GetImportant() c.Attributes = consumer.GetAttributes() c.ReadFrom.MustFromProto(consumer.GetReadFrom()) - c.SupportedCodecs.MustFromProto(consumer.SupportedCodecs) + c.SupportedCodecs.MustFromProto(consumer.GetSupportedCodecs()) } func (c *Consumer) ToProto() *Ydb_Topic.Consumer { @@ -56,8 +56,8 @@ func (s *PartitioningSettings) FromProto(proto *Ydb_Topic.PartitioningSettings) return xerrors.WithStackTrace(errUnexpectedNilPartitioningSettings) } - s.MinActivePartitions = proto.MinActivePartitions - s.PartitionCountLimit = proto.PartitionCountLimit + s.MinActivePartitions = proto.GetMinActivePartitions() + s.PartitionCountLimit = proto.GetPartitionCountLimit() return nil } diff --git a/internal/grpcwrapper/rawtopic/create_topic.go b/internal/grpcwrapper/rawtopic/create_topic.go index f8ecb6980..fd17da84e 100644 --- a/internal/grpcwrapper/rawtopic/create_topic.go +++ b/internal/grpcwrapper/rawtopic/create_topic.go @@ -41,7 +41,7 @@ func (req *CreateTopicRequest) ToProto() *Ydb_Topic.CreateTopicRequest { proto.Attributes = req.Attributes proto.Consumers = make([]*Ydb_Topic.Consumer, len(req.Consumers)) - for i := range proto.Consumers { + for i := range proto.GetConsumers() { proto.Consumers[i] = req.Consumers[i].ToProto() } @@ -55,5 +55,5 @@ type CreateTopicResult struct { } func (r *CreateTopicResult) FromProto(proto *Ydb_Topic.CreateTopicResponse) error { - return r.Operation.FromProtoWithStatusCheck(proto.Operation) + return r.Operation.FromProtoWithStatusCheck(proto.GetOperation()) } diff --git a/internal/grpcwrapper/rawtopic/describe_topic.go b/internal/grpcwrapper/rawtopic/describe_topic.go index 50662e37f..a1c3d4838 100644 --- a/internal/grpcwrapper/rawtopic/describe_topic.go +++ b/internal/grpcwrapper/rawtopic/describe_topic.go @@ -42,7 +42,7 @@ type DescribeTopicResult struct { } func (res *DescribeTopicResult) FromProto(protoResponse *Ydb_Topic.DescribeTopicResponse) error { - if err := res.Operation.FromProtoWithStatusCheck(protoResponse.Operation); err != nil { + if err := res.Operation.FromProtoWithStatusCheck(protoResponse.GetOperation()); err != nil { return err } @@ -51,11 +51,11 @@ func (res *DescribeTopicResult) FromProto(protoResponse *Ydb_Topic.DescribeTopic return xerrors.WithStackTrace(fmt.Errorf("ydb: describe topic result failed on unmarshal grpc result: %w", err)) } - if err := res.Self.FromProto(protoResult.Self); err != nil { + if err := res.Self.FromProto(protoResult.GetSelf()); err != nil { return err } - if err := res.PartitioningSettings.FromProto(protoResult.PartitioningSettings); err != nil { + if err := res.PartitioningSettings.FromProto(protoResult.GetPartitioningSettings()); err != nil { return err } @@ -72,17 +72,17 @@ func (res *DescribeTopicResult) FromProto(protoResponse *Ydb_Topic.DescribeTopic res.SupportedCodecs = append(res.SupportedCodecs, rawtopiccommon.Codec(v)) } - res.PartitionWriteSpeedBytesPerSecond = protoResult.PartitionWriteSpeedBytesPerSecond - res.PartitionWriteBurstBytes = protoResult.PartitionWriteBurstBytes + res.PartitionWriteSpeedBytesPerSecond = protoResult.GetPartitionWriteSpeedBytesPerSecond() + res.PartitionWriteBurstBytes = protoResult.GetPartitionWriteBurstBytes() - res.Attributes = protoResult.Attributes + res.Attributes = protoResult.GetAttributes() - res.Consumers = make([]Consumer, len(protoResult.Consumers)) + res.Consumers = make([]Consumer, len(protoResult.GetConsumers())) for i := range res.Consumers { - res.Consumers[i].MustFromProto(protoResult.Consumers[i]) + res.Consumers[i].MustFromProto(protoResult.GetConsumers()[i]) } - res.MeteringMode = MeteringMode(protoResult.MeteringMode) + res.MeteringMode = MeteringMode(protoResult.GetMeteringMode()) return nil } diff --git a/internal/grpcwrapper/rawtopic/drop_topic.go b/internal/grpcwrapper/rawtopic/drop_topic.go index 586284fa8..0db29973a 100644 --- a/internal/grpcwrapper/rawtopic/drop_topic.go +++ b/internal/grpcwrapper/rawtopic/drop_topic.go @@ -23,5 +23,5 @@ type DropTopicResult struct { } func (r *DropTopicResult) FromProto(proto *Ydb_Topic.DropTopicResponse) error { - return r.Operation.FromProtoWithStatusCheck(proto.Operation) + return r.Operation.FromProtoWithStatusCheck(proto.GetOperation()) } diff --git a/internal/grpcwrapper/rawtopic/rawtopiccommon/codec.go b/internal/grpcwrapper/rawtopic/rawtopiccommon/codec.go index 34b87ebf9..166818331 100644 --- a/internal/grpcwrapper/rawtopic/rawtopiccommon/codec.go +++ b/internal/grpcwrapper/rawtopic/rawtopiccommon/codec.go @@ -98,7 +98,7 @@ func (c *SupportedCodecs) ToProto() *Ydb_Topic.SupportedCodecs { func (c *SupportedCodecs) MustFromProto(proto *Ydb_Topic.SupportedCodecs) { res := make([]Codec, len(proto.GetCodecs())) for i := range proto.GetCodecs() { - res[i].MustFromProto(Ydb_Topic.Codec(proto.Codecs[i])) + res[i].MustFromProto(Ydb_Topic.Codec(proto.GetCodecs()[i])) } *c = res } diff --git a/internal/grpcwrapper/rawtopic/rawtopicreader/messages.go b/internal/grpcwrapper/rawtopic/rawtopicreader/messages.go index b7b32da1c..ad60427fa 100644 --- a/internal/grpcwrapper/rawtopic/rawtopicreader/messages.go +++ b/internal/grpcwrapper/rawtopic/rawtopicreader/messages.go @@ -90,11 +90,11 @@ type UpdateTokenRequest struct { } type UpdateTokenResponse struct { + rawtopiccommon.UpdateTokenResponse + serverMessageImpl rawtopiccommon.ServerMessageMetadata - - rawtopiccommon.UpdateTokenResponse } // @@ -160,7 +160,7 @@ type InitResponse struct { } func (g *InitResponse) fromProto(p *Ydb_Topic.StreamReadMessage_InitResponse) { - g.SessionID = p.SessionId + g.SessionID = p.GetSessionId() } // @@ -209,52 +209,52 @@ func (r *ReadResponse) fromProto(p *Ydb_Topic.StreamReadMessage_ReadResponse) er if p == nil { return xerrors.WithStackTrace(errUnexpectedNilStreamReadMessageReadResponse) } - r.BytesSize = int(p.BytesSize) + r.BytesSize = int(p.GetBytesSize()) - r.PartitionData = make([]PartitionData, len(p.PartitionData)) - for partitionIndex := range p.PartitionData { - srcPartition := p.PartitionData[partitionIndex] + r.PartitionData = make([]PartitionData, len(p.GetPartitionData())) + for partitionIndex := range p.GetPartitionData() { + srcPartition := p.GetPartitionData()[partitionIndex] if srcPartition == nil { return xerrors.WithStackTrace(errNilPartitionData) } dstPartition := &r.PartitionData[partitionIndex] - dstPartition.PartitionSessionID.FromInt64(srcPartition.PartitionSessionId) + dstPartition.PartitionSessionID.FromInt64(srcPartition.GetPartitionSessionId()) - dstPartition.Batches = make([]Batch, len(srcPartition.Batches)) + dstPartition.Batches = make([]Batch, len(srcPartition.GetBatches())) - for batchIndex := range srcPartition.Batches { - srcBatch := srcPartition.Batches[batchIndex] + for batchIndex := range srcPartition.GetBatches() { + srcBatch := srcPartition.GetBatches()[batchIndex] if srcBatch == nil { return xerrors.WithStackTrace(errUnexpectedNilBatchInPartitionData) } dstBatch := &dstPartition.Batches[batchIndex] - dstBatch.ProducerID = srcBatch.ProducerId - dstBatch.WriteSessionMeta = srcBatch.WriteSessionMeta - dstBatch.Codec.MustFromProto(Ydb_Topic.Codec(srcBatch.Codec)) + dstBatch.ProducerID = srcBatch.GetProducerId() + dstBatch.WriteSessionMeta = srcBatch.GetWriteSessionMeta() + dstBatch.Codec.MustFromProto(Ydb_Topic.Codec(srcBatch.GetCodec())) - dstBatch.WrittenAt = srcBatch.WrittenAt.AsTime() + dstBatch.WrittenAt = srcBatch.GetWrittenAt().AsTime() - dstBatch.MessageData = make([]MessageData, len(srcBatch.MessageData)) - for messageIndex := range srcBatch.MessageData { - srcMessage := srcBatch.MessageData[messageIndex] + dstBatch.MessageData = make([]MessageData, len(srcBatch.GetMessageData())) + for messageIndex := range srcBatch.GetMessageData() { + srcMessage := srcBatch.GetMessageData()[messageIndex] if srcMessage == nil { return xerrors.WithStackTrace(errUnexpectedMessageNilInPartitionData) } dstMessage := &dstBatch.MessageData[messageIndex] - dstMessage.Offset.FromInt64(srcMessage.Offset) - dstMessage.SeqNo = srcMessage.SeqNo - dstMessage.CreatedAt = srcMessage.CreatedAt.AsTime() - dstMessage.Data = srcMessage.Data - dstMessage.UncompressedSize = srcMessage.UncompressedSize - dstMessage.MessageGroupID = srcMessage.MessageGroupId - if len(srcMessage.MetadataItems) > 0 { - dstMessage.MetadataItems = make([]rawtopiccommon.MetadataItem, 0, len(srcMessage.MetadataItems)) - for _, protoItem := range srcMessage.MetadataItems { + dstMessage.Offset.FromInt64(srcMessage.GetOffset()) + dstMessage.SeqNo = srcMessage.GetSeqNo() + dstMessage.CreatedAt = srcMessage.GetCreatedAt().AsTime() + dstMessage.Data = srcMessage.GetData() + dstMessage.UncompressedSize = srcMessage.GetUncompressedSize() + dstMessage.MessageGroupID = srcMessage.GetMessageGroupId() + if len(srcMessage.GetMetadataItems()) > 0 { + dstMessage.MetadataItems = make([]rawtopiccommon.MetadataItem, 0, len(srcMessage.GetMetadataItems())) + for _, protoItem := range srcMessage.GetMetadataItems() { dstMessage.MetadataItems = append(dstMessage.MetadataItems, rawtopiccommon.MetadataItem{ - Key: protoItem.Key, - Value: protoItem.Value[:len(protoItem.Value):len(protoItem.Value)], + Key: protoItem.GetKey(), + Value: protoItem.GetValue()[:len(protoItem.GetValue()):len(protoItem.GetValue())], }) } } @@ -338,8 +338,8 @@ func (r *OffsetRange) FromProto(p *Ydb_Topic.OffsetsRange) error { return xerrors.WithStackTrace(errUnexpectedProtobufInOffsets) } - r.Start.FromInt64(p.Start) - r.End.FromInt64(p.End) + r.Start.FromInt64(p.GetStart()) + r.End.FromInt64(p.GetEnd()) return nil } @@ -360,16 +360,16 @@ type CommitOffsetResponse struct { } func (r *CommitOffsetResponse) fromProto(proto *Ydb_Topic.StreamReadMessage_CommitOffsetResponse) error { - r.PartitionsCommittedOffsets = make([]PartitionCommittedOffset, len(proto.PartitionsCommittedOffsets)) + r.PartitionsCommittedOffsets = make([]PartitionCommittedOffset, len(proto.GetPartitionsCommittedOffsets())) for i := range r.PartitionsCommittedOffsets { - srcCommitted := proto.PartitionsCommittedOffsets[i] + srcCommitted := proto.GetPartitionsCommittedOffsets()[i] if srcCommitted == nil { return xerrors.WithStackTrace(errors.New("unexpected nil while parse commit offset response")) } dstCommitted := &r.PartitionsCommittedOffsets[i] - dstCommitted.PartitionSessionID.FromInt64(srcCommitted.PartitionSessionId) - dstCommitted.CommittedOffset.FromInt64(srcCommitted.CommittedOffset) + dstCommitted.PartitionSessionID.FromInt64(srcCommitted.GetPartitionSessionId()) + dstCommitted.CommittedOffset.FromInt64(srcCommitted.GetCommittedOffset()) } return nil @@ -437,16 +437,16 @@ func (r *StartPartitionSessionRequest) fromProto(p *Ydb_Topic.StreamReadMessage_ return xerrors.WithStackTrace(errUnexpectedProtoNilStartPartitionSessionRequest) } - if p.PartitionSession == nil { + if p.GetPartitionSession() == nil { return xerrors.WithStackTrace(errUnexpectedNilPartitionSession) } - r.PartitionSession.PartitionID = p.PartitionSession.PartitionId - r.PartitionSession.Path = p.PartitionSession.Path - r.PartitionSession.PartitionSessionID.FromInt64(p.PartitionSession.PartitionSessionId) + r.PartitionSession.PartitionID = p.GetPartitionSession().GetPartitionId() + r.PartitionSession.Path = p.GetPartitionSession().GetPath() + r.PartitionSession.PartitionSessionID.FromInt64(p.GetPartitionSession().GetPartitionSessionId()) - r.CommittedOffset.FromInt64(p.CommittedOffset) + r.CommittedOffset.FromInt64(p.GetCommittedOffset()) - return r.PartitionOffsets.FromProto(p.PartitionOffsets) + return r.PartitionOffsets.FromProto(p.GetPartitionOffsets()) } type PartitionSession struct { @@ -491,9 +491,9 @@ func (r *StopPartitionSessionRequest) fromProto(proto *Ydb_Topic.StreamReadMessa if proto == nil { return xerrors.WithStackTrace(errUnexpectedGrpcNilStopPartitionSessionRequest) } - r.PartitionSessionID.FromInt64(proto.PartitionSessionId) - r.Graceful = proto.Graceful - r.CommittedOffset.FromInt64(proto.CommittedOffset) + r.PartitionSessionID.FromInt64(proto.GetPartitionSessionId()) + r.Graceful = proto.GetGraceful() + r.CommittedOffset.FromInt64(proto.GetCommittedOffset()) return nil } diff --git a/internal/grpcwrapper/rawtopic/rawtopicreader/rawtopicreader.go b/internal/grpcwrapper/rawtopic/rawtopicreader/rawtopicreader.go index 55520e54b..17ccc026e 100644 --- a/internal/grpcwrapper/rawtopic/rawtopicreader/rawtopicreader.go +++ b/internal/grpcwrapper/rawtopic/rawtopicreader/rawtopicreader.go @@ -3,6 +3,7 @@ package rawtopicreader import ( "errors" "fmt" + "io" "reflect" "github.com/ydb-platform/ydb-go-genproto/protos/Ydb_Topic" @@ -30,6 +31,9 @@ func (s StreamReader) CloseSend() error { func (s StreamReader) Recv() (ServerMessage, error) { grpcMess, err := s.Stream.Recv() + if xerrors.Is(err, io.EOF) { + return nil, err + } if err != nil { if !xerrors.IsErrorFromServer(err) { err = xerrors.Transport(err) @@ -46,7 +50,7 @@ func (s StreamReader) Recv() (ServerMessage, error) { return nil, xerrors.WithStackTrace(fmt.Errorf("ydb: bad status from topic server: %v", meta.Status)) } - switch m := grpcMess.ServerMessage.(type) { + switch m := grpcMess.GetServerMessage().(type) { case *Ydb_Topic.StreamReadMessage_FromServer_InitResponse: resp := &InitResponse{} resp.ServerMessageMetadata = meta @@ -102,7 +106,7 @@ func (s StreamReader) Recv() (ServerMessage, error) { default: return nil, xerrors.WithStackTrace(fmt.Errorf( "ydb: receive unexpected message (%v): %w", - reflect.TypeOf(grpcMess.ServerMessage), + reflect.TypeOf(grpcMess.GetServerMessage()), ErrUnexpectedMessageType, )) } diff --git a/internal/grpcwrapper/rawtopic/rawtopicwriter/messages.go b/internal/grpcwrapper/rawtopic/rawtopicwriter/messages.go index 5f89a2f6b..252b4b3cb 100644 --- a/internal/grpcwrapper/rawtopic/rawtopicwriter/messages.go +++ b/internal/grpcwrapper/rawtopic/rawtopicwriter/messages.go @@ -133,10 +133,10 @@ type InitResult struct { } func (r *InitResult) mustFromProto(response *Ydb_Topic.StreamWriteMessage_InitResponse) { - r.SessionID = response.SessionId - r.PartitionID = response.PartitionId - r.LastSeqNo = response.LastSeqNo - r.SupportedCodecs.MustFromProto(response.SupportedCodecs) + r.SessionID = response.GetSessionId() + r.PartitionID = response.GetPartitionId() + r.LastSeqNo = response.GetLastSeqNo() + r.SupportedCodecs.MustFromProto(response.GetSupportedCodecs()) } type WriteRequest struct { @@ -188,7 +188,7 @@ func (d *MessageData) ToProto() (*Ydb_Topic.StreamWriteMessage_WriteRequest_Mess } for i := range d.MetadataItems { - res.MetadataItems = append(res.MetadataItems, &Ydb_Topic.MetadataItem{ + res.MetadataItems = append(res.GetMetadataItems(), &Ydb_Topic.MetadataItem{ Key: d.MetadataItems[i].Key, Value: d.MetadataItems[i].Value, }) @@ -210,15 +210,15 @@ func (r *WriteResult) fromProto(response *Ydb_Topic.StreamWriteMessage_WriteResp if response == nil { return xerrors.WithStackTrace(errWriteResultProtoIsNil) } - r.Acks = make([]WriteAck, len(response.Acks)) - for i := range response.Acks { - if err := r.Acks[i].fromProto(response.Acks[i]); err != nil { + r.Acks = make([]WriteAck, len(response.GetAcks())) + for i := range response.GetAcks() { + if err := r.Acks[i].fromProto(response.GetAcks()[i]); err != nil { return err } } - r.PartitionID = response.PartitionId + r.PartitionID = response.GetPartitionId() - return r.WriteStatistics.fromProto(response.WriteStatistics) + return r.WriteStatistics.fromProto(response.GetWriteStatistics()) } type WriteAck struct { @@ -230,9 +230,9 @@ func (wa *WriteAck) fromProto(pb *Ydb_Topic.StreamWriteMessage_WriteResponse_Wri if pb == nil { return xerrors.WithStackTrace(errWriteResultResponseWriteAckIsNil) } - wa.SeqNo = pb.SeqNo + wa.SeqNo = pb.GetSeqNo() - return wa.MessageWriteStatus.fromProto(pb.MessageWriteStatus) + return wa.MessageWriteStatus.fromProto(pb.GetMessageWriteStatus()) } // MessageWriteStatus is struct because it included in per-message structure and @@ -248,12 +248,12 @@ func (s *MessageWriteStatus) fromProto(status interface{}) error { switch v := status.(type) { case *Ydb_Topic.StreamWriteMessage_WriteResponse_WriteAck_Written_: s.Type = WriteStatusTypeWritten - s.WrittenOffset = v.Written.Offset + s.WrittenOffset = v.Written.GetOffset() return nil case *Ydb_Topic.StreamWriteMessage_WriteResponse_WriteAck_Skipped_: s.Type = WriteStatusTypeSkipped - s.SkippedReason = WriteStatusSkipReason(v.Skipped.Reason) + s.SkippedReason = WriteStatusSkipReason(v.Skipped.GetReason()) return nil default: @@ -288,10 +288,10 @@ func (s *WriteStatistics) fromProto(statistics *Ydb_Topic.StreamWriteMessage_Wri return xerrors.WithStackTrace(errWriteResultResponseStatisticIsNil) } - s.PersistingTime = statistics.PersistingTime.AsDuration() - s.MinQueueWaitTime = statistics.MinQueueWaitTime.AsDuration() - s.MaxQueueWaitTime = statistics.MaxQueueWaitTime.AsDuration() - s.TopicQuotaWaitTime = statistics.TopicQuotaWaitTime.AsDuration() + s.PersistingTime = statistics.GetPersistingTime().AsDuration() + s.MinQueueWaitTime = statistics.GetMinQueueWaitTime().AsDuration() + s.MaxQueueWaitTime = statistics.GetMaxQueueWaitTime().AsDuration() + s.TopicQuotaWaitTime = statistics.GetTopicQuotaWaitTime().AsDuration() return nil } @@ -303,9 +303,9 @@ type UpdateTokenRequest struct { } type UpdateTokenResponse struct { + rawtopiccommon.UpdateTokenResponse + serverMessageImpl rawtopiccommon.ServerMessageMetadata - - rawtopiccommon.UpdateTokenResponse } diff --git a/internal/grpcwrapper/rawtopic/rawtopicwriter/streamwriter.go b/internal/grpcwrapper/rawtopic/rawtopicwriter/streamwriter.go index 8e11a2372..8ff0b9727 100644 --- a/internal/grpcwrapper/rawtopic/rawtopicwriter/streamwriter.go +++ b/internal/grpcwrapper/rawtopic/rawtopicwriter/streamwriter.go @@ -59,7 +59,7 @@ func (w *StreamWriter) Recv() (ServerMessage, error) { return nil, xerrors.WithStackTrace(fmt.Errorf("ydb: bad status from topic server: %v", meta.Status)) } - switch v := grpcMsg.ServerMessage.(type) { + switch v := grpcMsg.GetServerMessage().(type) { case *Ydb_Topic.StreamWriteMessage_FromServer_InitResponse: var res InitResult res.ServerMessageMetadata = meta @@ -172,7 +172,7 @@ func sendWriteRequest(send sendFunc, req *Ydb_Topic.StreamWriteMessage_FromClien return sendErr } - grpcMessages := req.WriteRequest.Messages + grpcMessages := req.WriteRequest.GetMessages() if grpcStatus.Code() != codes.ResourceExhausted || len(grpcMessages) < 2 { return sendErr } diff --git a/internal/grpcwrapper/rawtopic/rawtopicwriter/streamwriter_test.go b/internal/grpcwrapper/rawtopic/rawtopicwriter/streamwriter_test.go index e8b1957b1..f076adab9 100644 --- a/internal/grpcwrapper/rawtopic/rawtopicwriter/streamwriter_test.go +++ b/internal/grpcwrapper/rawtopic/rawtopicwriter/streamwriter_test.go @@ -25,7 +25,7 @@ func TestSendWriteRequest(t *testing.T) { sendCounter := 0 var send sendFunc = func(req *Ydb_Topic.StreamWriteMessage_FromClient) error { sendCounter++ - require.Equal(t, expected, req.ClientMessage) + require.Equal(t, expected, req.GetClientMessage()) return nil } @@ -72,7 +72,7 @@ func TestSendWriteRequest(t *testing.T) { } getWriteRequest := func(req *Ydb_Topic.StreamWriteMessage_FromClient) *Ydb_Topic.StreamWriteMessage_WriteRequest { - return req.ClientMessage.(*Ydb_Topic.StreamWriteMessage_FromClient_WriteRequest).WriteRequest + return req.GetClientMessage().(*Ydb_Topic.StreamWriteMessage_FromClient_WriteRequest).WriteRequest } sendCounter := 0 diff --git a/internal/grpcwrapper/rawydb/operation.go b/internal/grpcwrapper/rawydb/operation.go index 05635a214..5073cb8aa 100644 --- a/internal/grpcwrapper/rawydb/operation.go +++ b/internal/grpcwrapper/rawydb/operation.go @@ -22,7 +22,7 @@ func (o *Operation) FromProto(proto *Ydb_Operations.Operation) error { return err } - return o.Issues.FromProto(proto.Issues) + return o.Issues.FromProto(proto.GetIssues()) } func (o *Operation) OperationStatusToError() error { diff --git a/internal/meta/context.go b/internal/meta/context.go index 041f7a5da..f0d0f767b 100644 --- a/internal/meta/context.go +++ b/internal/meta/context.go @@ -23,9 +23,15 @@ func traceID(ctx context.Context) (string, bool) { return "", false } -// WithUserAgent returns a copy of parent context with custom user-agent info -func WithUserAgent(ctx context.Context, userAgent string) context.Context { - return metadata.AppendToOutgoingContext(ctx, HeaderUserAgent, userAgent) +// WithApplicationName returns a copy of parent context with custom user-agent info +func WithApplicationName(ctx context.Context, applicationName string) context.Context { + md, has := metadata.FromOutgoingContext(ctx) + if !has { + md = metadata.MD{} + } + md.Set(HeaderApplicationName, applicationName) + + return metadata.NewOutgoingContext(ctx, md) } // WithRequestType returns a copy of parent context with custom request type diff --git a/internal/meta/context_test.go b/internal/meta/context_test.go new file mode 100644 index 000000000..1bfdabcc5 --- /dev/null +++ b/internal/meta/context_test.go @@ -0,0 +1,61 @@ +package meta + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + "google.golang.org/grpc/metadata" +) + +func TestContext(t *testing.T) { + for _, tt := range []struct { + name string + ctx context.Context + header string + values []string + }{ + { + name: "WithApplicationName", + ctx: WithApplicationName(context.Background(), "test"), + header: HeaderApplicationName, + values: []string{"test"}, + }, + { + name: "WithApplicationName", + ctx: WithApplicationName( + WithApplicationName( + context.Background(), + "test1", + ), + "test2", + ), + header: HeaderApplicationName, + values: []string{"test2"}, + }, + { + name: "WithTraceID", + ctx: WithTraceID(context.Background(), "my-trace-id"), + header: HeaderTraceID, + values: []string{"my-trace-id"}, + }, + { + name: "WithRequestType", + ctx: WithRequestType(context.Background(), "my-request-type"), + header: HeaderRequestType, + values: []string{"my-request-type"}, + }, + { + name: "WithAllowFeatures", + ctx: WithAllowFeatures(context.Background(), "feature-1", "feature-2", "feature-3"), + header: HeaderClientCapabilities, + values: []string{"feature-1", "feature-2", "feature-3"}, + }, + } { + t.Run(tt.name, func(t *testing.T) { + md, has := metadata.FromOutgoingContext(tt.ctx) + require.True(t, has) + require.Equal(t, tt.values, md.Get(tt.header)) + }) + } +} diff --git a/internal/meta/headers.go b/internal/meta/headers.go index e68ca4ad4..41f025941 100644 --- a/internal/meta/headers.go +++ b/internal/meta/headers.go @@ -7,8 +7,9 @@ const ( HeaderVersion = "x-ydb-sdk-build-info" HeaderRequestType = "x-ydb-request-type" HeaderTraceID = "x-ydb-trace-id" - HeaderUserAgent = "x-ydb-user-agent" + HeaderApplicationName = "x-ydb-application-name" HeaderClientCapabilities = "x-ydb-client-capabilities" + HeaderClientPid = "x-ydb-client-pid" // outgoing hints HintSessionBalancer = "session-balancer" diff --git a/internal/meta/meta.go b/internal/meta/meta.go index 4f1564732..8f856379d 100644 --- a/internal/meta/meta.go +++ b/internal/meta/meta.go @@ -3,6 +3,8 @@ package meta import ( "context" "fmt" + "os" + "strconv" "google.golang.org/grpc/metadata" @@ -13,6 +15,8 @@ import ( "github.com/ydb-platform/ydb-go-sdk/v3/trace" ) +var pid = os.Getpid() + func New( database string, credentials credentials.Credentials, @@ -20,13 +24,14 @@ func New( opts ...Option, ) *Meta { m := &Meta{ + pid: strconv.Itoa(pid), trace: trace, credentials: credentials, database: database, } - for _, o := range opts { - if o != nil { - o(m) + for _, opt := range opts { + if opt != nil { + opt(m) } } @@ -35,9 +40,9 @@ func New( type Option func(m *Meta) -func WithUserAgentOption(userAgent string) Option { +func WithApplicationNameOption(applicationName string) Option { return func(m *Meta) { - m.userAgents = append(m.userAgents, userAgent) + m.applicationName = applicationName } } @@ -67,12 +72,13 @@ func ForbidOption(feature string) Option { } type Meta struct { - trace *trace.Driver - credentials credentials.Credentials - database string - requestsType string - userAgents []string - capabilities []string + pid string + trace *trace.Driver + credentials credentials.Credentials + database string + requestsType string + applicationName string + capabilities []string } func (m *Meta) meta(ctx context.Context) (_ metadata.MD, err error) { @@ -81,6 +87,8 @@ func (m *Meta) meta(ctx context.Context) (_ metadata.MD, err error) { md = metadata.MD{} } + md.Set(HeaderClientPid, m.pid) + if len(md.Get(HeaderDatabase)) == 0 { md.Set(HeaderDatabase, m.database) } @@ -95,8 +103,8 @@ func (m *Meta) meta(ctx context.Context) (_ metadata.MD, err error) { } } - if len(m.userAgents) != 0 { - md.Append(HeaderUserAgent, m.userAgents...) + if m.applicationName != "" { + md.Append(HeaderApplicationName, m.applicationName) } if len(m.capabilities) > 0 { @@ -109,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/meta/test/meta_test.go b/internal/meta/test/meta_test.go index e077b7f6b..139187ef6 100644 --- a/internal/meta/test/meta_test.go +++ b/internal/meta/test/meta_test.go @@ -20,13 +20,11 @@ func TestMetaRequiredHeaders(t *testing.T) { credentials.NewAccessTokenCredentials("token"), &trace.Driver{}, internal.WithRequestTypeOption("requestType"), - internal.WithUserAgentOption("user-agent"), + internal.WithApplicationNameOption("test app"), ) ctx := context.Background() - ctx = meta.WithUserAgent(ctx, "userAgent") - ctx = meta.WithTraceID(ctx, "traceID") ctx = metadata.AppendToOutgoingContext(ctx, "some-user-header", "some-user-value") @@ -43,7 +41,9 @@ func TestMetaRequiredHeaders(t *testing.T) { require.Equal(t, []string{"database"}, md.Get(internal.HeaderDatabase)) require.Equal(t, []string{"requestType"}, md.Get(internal.HeaderRequestType)) require.Equal(t, []string{"token"}, md.Get(internal.HeaderTicket)) - require.Equal(t, []string{"userAgent", "user-agent"}, md.Get(internal.HeaderUserAgent)) + require.NotEmpty(t, md.Get(internal.HeaderClientPid)) + require.NotEmpty(t, md.Get(internal.HeaderClientPid)[0]) + require.Equal(t, []string{"test app"}, md.Get(internal.HeaderApplicationName)) require.Equal(t, []string{"traceID"}, md.Get(internal.HeaderTraceID)) require.Equal(t, []string{ "ydb-go-sdk/" + version.Major + "." + version.Minor + "." + version.Patch, diff --git a/internal/meta/trace_id.go b/internal/meta/trace_id.go index 926722579..182c57dac 100644 --- a/internal/meta/trace_id.go +++ b/internal/meta/trace_id.go @@ -19,7 +19,9 @@ func TraceID(ctx context.Context, opts ...func(opts *newTraceIDOpts)) (context.C } options := newTraceIDOpts{newRandom: uuid.NewRandom} for _, opt := range opts { - opt(&options) + if opt != nil { + opt(&options) + } } uuid, err := options.newRandom() if err != nil { 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/operation/params_test.go b/internal/operation/params_test.go index 93ac17c5d..605453279 100644 --- a/internal/operation/params_test.go +++ b/internal/operation/params_test.go @@ -313,25 +313,25 @@ func TestParams(t *testing.T) { return } - if !reflect.DeepEqual(got.OperationMode, tt.exp.OperationMode) { + if !reflect.DeepEqual(got.GetOperationMode(), tt.exp.GetOperationMode()) { t.Errorf( "Params().OperationMode: %v, want: %v", - got.OperationMode, - tt.exp.OperationMode, + got.GetOperationMode(), + tt.exp.GetOperationMode(), ) } - if !reflect.DeepEqual(got.CancelAfter, tt.exp.CancelAfter) { + if !reflect.DeepEqual(got.GetCancelAfter(), tt.exp.GetCancelAfter()) { t.Errorf( "Params().CancelAfter: %v, want: %v", - got.CancelAfter.AsDuration(), - tt.exp.CancelAfter.AsDuration(), + got.GetCancelAfter().AsDuration(), + tt.exp.GetCancelAfter().AsDuration(), ) } - if got.OperationTimeout.AsDuration() > tt.exp.OperationTimeout.AsDuration() { + if got.GetOperationTimeout().AsDuration() > tt.exp.GetOperationTimeout().AsDuration() { t.Errorf( "Params().OperationTimeout: %v, want: <= %v", - got.OperationTimeout.AsDuration(), - tt.exp.OperationTimeout.AsDuration(), + got.GetOperationTimeout().AsDuration(), + tt.exp.GetOperationTimeout().AsDuration(), ) } }) diff --git a/internal/params/builder.go b/internal/params/builder.go new file mode 100644 index 000000000..ce8421fb0 --- /dev/null +++ b/internal/params/builder.go @@ -0,0 +1,19 @@ +package params + +type ( + Builder struct { + params Parameters + } +) + +func (b Builder) Build() *Parameters { + return &b.params +} + +func (b Builder) Param(name string) *Parameter { + return &Parameter{ + parent: b, + name: name, + value: nil, + } +} diff --git a/internal/params/builder_test.go b/internal/params/builder_test.go new file mode 100644 index 000000000..da1a7dda7 --- /dev/null +++ b/internal/params/builder_test.go @@ -0,0 +1,434 @@ +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 TestBuilder(t *testing.T) { + type expected struct { + Type *Ydb.Type + Value *Ydb.Value + } + + tests := []struct { + method string + args []any + + expected expected + }{ + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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", + }, + }, + }, + }, + { + 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"), + }, + }, + }, + }, + { + 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), + }, + }, + }, + }, + { + 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), + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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{ + High_128: 72623859790382856, + Value: &Ydb.Value_Low_128{ + Low_128: 648519454493508870, + }, + }, + }, + }, + { + 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"}`, + }, + }, + }, + }, + { + 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"}`, + }, + }, + }, + }, + { + 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"}`), + }, + }, + }, + }, + { + 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, + }, + }, + }, + { + 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", + }, + }, + }, + }, + { + 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", + }, + }, + }, + }, + { + 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") + + result, ok := xtest.CallMethod(item, tc.method, tc.args...)[0].(Builder) + require.True(t, ok) + + params := result.Build().ToYDB(a) + + require.Equal(t, + xtest.ToJSON( + map[string]*Ydb.TypedValue{ + "$x": { + Type: tc.expected.Type, + Value: tc.expected.Value, + }, + }), + xtest.ToJSON(params), + ) + }) + } +} diff --git a/internal/params/dict.go b/internal/params/dict.go new file mode 100644 index 000000000..d772f659e --- /dev/null +++ b/internal/params/dict.go @@ -0,0 +1,469 @@ +package params + +import ( + "time" + + "github.com/ydb-platform/ydb-go-sdk/v3/internal/value" +) + +type ( + dict struct { + parent Builder + name string + values []value.DictValueField + } + dictPair struct { + parent *dict + keyValue value.Value + } + dictValue struct { + pair *dictPair + } +) + +func (d *dict) Add() *dictPair { + return &dictPair{ + parent: d, + } +} + +func (d *dict) AddPairs(pairs ...value.DictValueField) *dict { + d.values = append(d.values, pairs...) + + return d +} + +func (d *dictPair) Text(v string) *dictValue { + d.keyValue = value.TextValue(v) + + return &dictValue{ + pair: d, + } +} + +func (d *dictPair) Bytes(v []byte) *dictValue { + d.keyValue = value.BytesValue(v) + + return &dictValue{ + pair: d, + } +} + +func (d *dictPair) Bool(v bool) *dictValue { + d.keyValue = value.BoolValue(v) + + return &dictValue{ + pair: d, + } +} + +func (d *dictPair) Uint64(v uint64) *dictValue { + d.keyValue = value.Uint64Value(v) + + return &dictValue{ + pair: d, + } +} + +func (d *dictPair) Int64(v int64) *dictValue { + d.keyValue = value.Int64Value(v) + + return &dictValue{ + pair: d, + } +} + +func (d *dictPair) Uint32(v uint32) *dictValue { + d.keyValue = value.Uint32Value(v) + + return &dictValue{ + pair: d, + } +} + +func (d *dictPair) Int32(v int32) *dictValue { + d.keyValue = value.Int32Value(v) + + return &dictValue{ + pair: d, + } +} + +func (d *dictPair) Uint16(v uint16) *dictValue { + d.keyValue = value.Uint16Value(v) + + return &dictValue{ + pair: d, + } +} + +func (d *dictPair) Int16(v int16) *dictValue { + d.keyValue = value.Int16Value(v) + + return &dictValue{ + pair: d, + } +} + +func (d *dictPair) Uint8(v uint8) *dictValue { + d.keyValue = value.Uint8Value(v) + + return &dictValue{ + pair: d, + } +} + +func (d *dictPair) Int8(v int8) *dictValue { + d.keyValue = value.Int8Value(v) + + return &dictValue{ + pair: d, + } +} + +func (d *dictPair) Float(v float32) *dictValue { + d.keyValue = value.FloatValue(v) + + return &dictValue{ + pair: d, + } +} + +func (d *dictPair) Double(v float64) *dictValue { + d.keyValue = value.DoubleValue(v) + + return &dictValue{ + pair: d, + } +} + +func (d *dictPair) Decimal(v [16]byte, precision, scale uint32) *dictValue { + d.keyValue = value.DecimalValue(v, precision, scale) + + return &dictValue{ + pair: d, + } +} + +func (d *dictPair) Timestamp(v time.Time) *dictValue { + d.keyValue = value.TimestampValueFromTime(v) + + return &dictValue{ + pair: d, + } +} + +func (d *dictPair) Date(v time.Time) *dictValue { + d.keyValue = value.DateValueFromTime(v) + + return &dictValue{ + pair: d, + } +} + +func (d *dictPair) Datetime(v time.Time) *dictValue { + d.keyValue = value.DatetimeValueFromTime(v) + + return &dictValue{ + pair: d, + } +} + +func (d *dictPair) Interval(v time.Duration) *dictValue { + d.keyValue = value.IntervalValueFromDuration(v) + + return &dictValue{ + pair: d, + } +} + +func (d *dictPair) JSON(v string) *dictValue { + d.keyValue = value.JSONValue(v) + + return &dictValue{ + pair: d, + } +} + +func (d *dictPair) JSONDocument(v string) *dictValue { + d.keyValue = value.JSONDocumentValue(v) + + return &dictValue{ + pair: d, + } +} + +func (d *dictPair) YSON(v []byte) *dictValue { + d.keyValue = value.YSONValue(v) + + return &dictValue{ + pair: d, + } +} + +func (d *dictPair) UUID(v [16]byte) *dictValue { + d.keyValue = value.UUIDValue(v) + + return &dictValue{ + pair: d, + } +} + +func (d *dictPair) TzDate(v time.Time) *dictValue { + d.keyValue = value.TzDateValueFromTime(v) + + return &dictValue{ + pair: d, + } +} + +func (d *dictPair) TzTimestamp(v time.Time) *dictValue { + d.keyValue = value.TzTimestampValueFromTime(v) + + return &dictValue{ + pair: d, + } +} + +func (d *dictPair) TzDatetime(v time.Time) *dictValue { + d.keyValue = value.TzDatetimeValueFromTime(v) + + return &dictValue{ + pair: d, + } +} + +func (d *dictValue) Text(v string) *dict { + d.pair.parent.values = append(d.pair.parent.values, value.DictValueField{ + K: d.pair.keyValue, + V: value.TextValue(v), + }) + + return d.pair.parent +} + +func (d *dictValue) Bytes(v []byte) *dict { + d.pair.parent.values = append(d.pair.parent.values, value.DictValueField{ + K: d.pair.keyValue, + V: value.BytesValue(v), + }) + + return d.pair.parent +} + +func (d *dictValue) Bool(v bool) *dict { + d.pair.parent.values = append(d.pair.parent.values, value.DictValueField{ + K: d.pair.keyValue, + V: value.BoolValue(v), + }) + + return d.pair.parent +} + +func (d *dictValue) Uint64(v uint64) *dict { + d.pair.parent.values = append(d.pair.parent.values, value.DictValueField{ + K: d.pair.keyValue, + V: value.Uint64Value(v), + }) + + return d.pair.parent +} + +func (d *dictValue) Int64(v int64) *dict { + d.pair.parent.values = append(d.pair.parent.values, value.DictValueField{ + K: d.pair.keyValue, + V: value.Int64Value(v), + }) + + return d.pair.parent +} + +func (d *dictValue) Uint32(v uint32) *dict { + d.pair.parent.values = append(d.pair.parent.values, value.DictValueField{ + K: d.pair.keyValue, + V: value.Uint32Value(v), + }) + + return d.pair.parent +} + +func (d *dictValue) Int32(v int32) *dict { + d.pair.parent.values = append(d.pair.parent.values, value.DictValueField{ + K: d.pair.keyValue, + V: value.Int32Value(v), + }) + + return d.pair.parent +} + +func (d *dictValue) Uint16(v uint16) *dict { + d.pair.parent.values = append(d.pair.parent.values, value.DictValueField{ + K: d.pair.keyValue, + V: value.Uint16Value(v), + }) + + return d.pair.parent +} + +func (d *dictValue) Int16(v int16) *dict { + d.pair.parent.values = append(d.pair.parent.values, value.DictValueField{ + K: d.pair.keyValue, + V: value.Int16Value(v), + }) + + return d.pair.parent +} + +func (d *dictValue) Uint8(v uint8) *dict { + d.pair.parent.values = append(d.pair.parent.values, value.DictValueField{ + K: d.pair.keyValue, + V: value.Uint8Value(v), + }) + + return d.pair.parent +} + +func (d *dictValue) Int8(v int8) *dict { + d.pair.parent.values = append(d.pair.parent.values, value.DictValueField{ + K: d.pair.keyValue, + V: value.Int8Value(v), + }) + + return d.pair.parent +} + +func (d *dictValue) Float(v float32) *dict { + d.pair.parent.values = append(d.pair.parent.values, value.DictValueField{ + K: d.pair.keyValue, + V: value.FloatValue(v), + }) + + return d.pair.parent +} + +func (d *dictValue) Double(v float64) *dict { + d.pair.parent.values = append(d.pair.parent.values, value.DictValueField{ + K: d.pair.keyValue, + V: value.DoubleValue(v), + }) + + return d.pair.parent +} + +func (d *dictValue) Decimal(v [16]byte, precision, scale uint32) *dict { + d.pair.parent.values = append(d.pair.parent.values, value.DictValueField{ + K: d.pair.keyValue, + V: value.DecimalValue(v, precision, scale), + }) + + return d.pair.parent +} + +func (d *dictValue) Timestamp(v time.Time) *dict { + d.pair.parent.values = append(d.pair.parent.values, value.DictValueField{ + K: d.pair.keyValue, + V: value.TimestampValueFromTime(v), + }) + + return d.pair.parent +} + +func (d *dictValue) Date(v time.Time) *dict { + d.pair.parent.values = append(d.pair.parent.values, value.DictValueField{ + K: d.pair.keyValue, + V: value.DateValueFromTime(v), + }) + + return d.pair.parent +} + +func (d *dictValue) Datetime(v time.Time) *dict { + d.pair.parent.values = append(d.pair.parent.values, value.DictValueField{ + K: d.pair.keyValue, + V: value.DatetimeValueFromTime(v), + }) + + return d.pair.parent +} + +func (d *dictValue) Interval(v time.Duration) *dict { + d.pair.parent.values = append(d.pair.parent.values, value.DictValueField{ + K: d.pair.keyValue, + V: value.IntervalValueFromDuration(v), + }) + + return d.pair.parent +} + +func (d *dictValue) JSON(v string) *dict { + d.pair.parent.values = append(d.pair.parent.values, value.DictValueField{ + K: d.pair.keyValue, + V: value.JSONValue(v), + }) + + return d.pair.parent +} + +func (d *dictValue) JSONDocument(v string) *dict { + d.pair.parent.values = append(d.pair.parent.values, value.DictValueField{ + K: d.pair.keyValue, + V: value.JSONDocumentValue(v), + }) + + return d.pair.parent +} + +func (d *dictValue) YSON(v []byte) *dict { + d.pair.parent.values = append(d.pair.parent.values, value.DictValueField{ + K: d.pair.keyValue, + V: value.YSONValue(v), + }) + + return d.pair.parent +} + +func (d *dictValue) UUID(v [16]byte) *dict { + d.pair.parent.values = append(d.pair.parent.values, value.DictValueField{ + K: d.pair.keyValue, + V: value.UUIDValue(v), + }) + + return d.pair.parent +} + +func (d *dictValue) TzDate(v time.Time) *dict { + d.pair.parent.values = append(d.pair.parent.values, value.DictValueField{ + K: d.pair.keyValue, + V: value.TzDateValueFromTime(v), + }) + + return d.pair.parent +} + +func (d *dictValue) TzTimestamp(v time.Time) *dict { + d.pair.parent.values = append(d.pair.parent.values, value.DictValueField{ + K: d.pair.keyValue, + V: value.TzTimestampValueFromTime(v), + }) + + return d.pair.parent +} + +func (d *dictValue) TzDatetime(v time.Time) *dict { + d.pair.parent.values = append(d.pair.parent.values, value.DictValueField{ + K: d.pair.keyValue, + V: value.TzDatetimeValueFromTime(v), + }) + + return d.pair.parent +} + +func (d *dict) EndDict() Builder { + d.parent.params = append(d.parent.params, &Parameter{ + parent: d.parent, + name: d.name, + value: value.DictValue(d.values...), + }) + + return d.parent +} diff --git a/internal/params/dict_test.go b/internal/params/dict_test.go new file mode 100644 index 000000000..0dc6b246c --- /dev/null +++ b/internal/params/dict_test.go @@ -0,0 +1,519 @@ +package params + +import ( + "fmt" + "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/value" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/xtest" +) + +func TestDict(t *testing.T) { + type expected struct { + Type *Ydb.Type + Value *Ydb.Value + } + + tests := []struct { + method string + args []any + + expected expected + }{ + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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", + }, + }, + }, + }, + { + 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"), + }, + }, + }, + }, + { + 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), + }, + }, + }, + }, + { + 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), + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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{ + High_128: 72623859790382856, + Value: &Ydb.Value_Low_128{ + Low_128: 648519454493508870, + }, + }, + }, + }, + { + 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"}`, + }, + }, + }, + }, + { + 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"}`, + }, + }, + }, + }, + { + 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"}`), + }, + }, + }, + }, + { + 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, + }, + }, + }, + { + 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", + }, + }, + }, + }, + { + 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", + }, + }, + }, + }, + { + 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 _, key := range tests { + for _, val := range tests { + t.Run(fmt.Sprintf("%s:%s", key.method, val.method), func(t *testing.T) { + a := allocator.New() + defer a.Free() + + item := Builder{}.Param("$x").BeginDict().Add() + + addedKey, ok := xtest.CallMethod(item, key.method, key.args...)[0].(*dictValue) + require.True(t, ok) + + d, ok := xtest.CallMethod(addedKey, val.method, val.args...)[0].(*dict) + require.True(t, ok) + + params := d.EndDict().Build().ToYDB(a) + require.Equal(t, xtest.ToJSON( + map[string]*Ydb.TypedValue{ + "$x": { + Type: &Ydb.Type{ + Type: &Ydb.Type_DictType{ + DictType: &Ydb.DictType{ + Key: key.expected.Type, + Payload: val.expected.Type, + }, + }, + }, + Value: &Ydb.Value{ + Pairs: []*Ydb.ValuePair{ + { + Key: key.expected.Value, + Payload: val.expected.Value, + }, + }, + }, + }, + }), xtest.ToJSON(params)) + }) + } + } +} + +func TestDict_AddPairs(t *testing.T) { + a := allocator.New() + defer a.Free() + + pairs := []value.DictValueField{ + { + K: value.Int64Value(123), + V: value.BoolValue(true), + }, + { + K: value.Int64Value(321), + V: value.BoolValue(false), + }, + } + + params := Builder{}.Param("$x").BeginDict().AddPairs(pairs...).EndDict().Build().ToYDB(a) + + require.Equal(t, xtest.ToJSON( + map[string]*Ydb.TypedValue{ + "$x": { + Type: &Ydb.Type{ + Type: &Ydb.Type_DictType{ + DictType: &Ydb.DictType{ + Key: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_INT64, + }, + }, + Payload: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_BOOL, + }, + }, + }, + }, + }, + Value: &Ydb.Value{ + Pairs: []*Ydb.ValuePair{ + { + Key: &Ydb.Value{ + Value: &Ydb.Value_Int64Value{ + Int64Value: 123, + }, + }, + Payload: &Ydb.Value{ + Value: &Ydb.Value_BoolValue{ + BoolValue: true, + }, + }, + }, + { + Key: &Ydb.Value{ + Value: &Ydb.Value_Int64Value{ + Int64Value: 321, + }, + }, + Payload: &Ydb.Value{ + Value: &Ydb.Value_BoolValue{ + BoolValue: false, + }, + }, + }, + }, + }, + }, + }), xtest.ToJSON(params)) +} diff --git a/internal/params/list.go b/internal/params/list.go new file mode 100644 index 000000000..b70501e5c --- /dev/null +++ b/internal/params/list.go @@ -0,0 +1,190 @@ +package params + +import ( + "time" + + "github.com/ydb-platform/ydb-go-sdk/v3/internal/value" +) + +type ( + list struct { + parent Builder + name string + values []value.Value + } + listItem struct { + parent *list + } +) + +func (l *list) Add() *listItem { + return &listItem{ + parent: l, + } +} + +func (l *list) AddItems(items ...value.Value) *list { + l.values = append(l.values, items...) + + return l +} + +func (l *list) EndList() Builder { + l.parent.params = append(l.parent.params, &Parameter{ + parent: l.parent, + name: l.name, + value: value.ListValue(l.values...), + }) + + return l.parent +} + +func (l *listItem) Text(v string) *list { + l.parent.values = append(l.parent.values, value.TextValue(v)) + + return l.parent +} + +func (l *listItem) Bytes(v []byte) *list { + l.parent.values = append(l.parent.values, value.BytesValue(v)) + + return l.parent +} + +func (l *listItem) Bool(v bool) *list { + l.parent.values = append(l.parent.values, value.BoolValue(v)) + + return l.parent +} + +func (l *listItem) Uint64(v uint64) *list { + l.parent.values = append(l.parent.values, value.Uint64Value(v)) + + return l.parent +} + +func (l *listItem) Int64(v int64) *list { + l.parent.values = append(l.parent.values, value.Int64Value(v)) + + return l.parent +} + +func (l *listItem) Uint32(v uint32) *list { + l.parent.values = append(l.parent.values, value.Uint32Value(v)) + + return l.parent +} + +func (l *listItem) Int32(v int32) *list { + l.parent.values = append(l.parent.values, value.Int32Value(v)) + + return l.parent +} + +func (l *listItem) Uint16(v uint16) *list { + l.parent.values = append(l.parent.values, value.Uint16Value(v)) + + return l.parent +} + +func (l *listItem) Int16(v int16) *list { + l.parent.values = append(l.parent.values, value.Int16Value(v)) + + return l.parent +} + +func (l *listItem) Uint8(v uint8) *list { + l.parent.values = append(l.parent.values, value.Uint8Value(v)) + + return l.parent +} + +func (l *listItem) Int8(v int8) *list { + l.parent.values = append(l.parent.values, value.Int8Value(v)) + + return l.parent +} + +func (l *listItem) Float(v float32) *list { + l.parent.values = append(l.parent.values, value.FloatValue(v)) + + return l.parent +} + +func (l *listItem) Double(v float64) *list { + l.parent.values = append(l.parent.values, value.DoubleValue(v)) + + return l.parent +} + +func (l *listItem) Decimal(v [16]byte, precision, scale uint32) *list { + l.parent.values = append(l.parent.values, value.DecimalValue(v, precision, scale)) + + return l.parent +} + +func (l *listItem) Timestamp(v time.Time) *list { + l.parent.values = append(l.parent.values, value.TimestampValueFromTime(v)) + + return l.parent +} + +func (l *listItem) Date(v time.Time) *list { + l.parent.values = append(l.parent.values, value.DateValueFromTime(v)) + + return l.parent +} + +func (l *listItem) Datetime(v time.Time) *list { + l.parent.values = append(l.parent.values, value.DatetimeValueFromTime(v)) + + return l.parent +} + +func (l *listItem) Interval(v time.Duration) *list { + l.parent.values = append(l.parent.values, value.IntervalValueFromDuration(v)) + + return l.parent +} + +func (l *listItem) JSON(v string) *list { + l.parent.values = append(l.parent.values, value.JSONValue(v)) + + return l.parent +} + +func (l *listItem) JSONDocument(v string) *list { + l.parent.values = append(l.parent.values, value.JSONDocumentValue(v)) + + return l.parent +} + +func (l *listItem) YSON(v []byte) *list { + l.parent.values = append(l.parent.values, value.YSONValue(v)) + + return l.parent +} + +func (l *listItem) UUID(v [16]byte) *list { + l.parent.values = append(l.parent.values, value.UUIDValue(v)) + + return l.parent +} + +func (l *listItem) TzDate(v time.Time) *list { + l.parent.values = append(l.parent.values, value.TzDateValueFromTime(v)) + + return l.parent +} + +func (l *listItem) TzTimestamp(v time.Time) *list { + l.parent.values = append(l.parent.values, value.TzTimestampValueFromTime(v)) + + return l.parent +} + +func (l *listItem) TzDatetime(v time.Time) *list { + l.parent.values = append(l.parent.values, value.TzDatetimeValueFromTime(v)) + + return l.parent +} diff --git a/internal/params/list_test.go b/internal/params/list_test.go new file mode 100644 index 000000000..288e0a5f7 --- /dev/null +++ b/internal/params/list_test.go @@ -0,0 +1,475 @@ +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/value" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/xtest" +) + +func TestList(t *testing.T) { + type expected struct { + Type *Ydb.Type + Value *Ydb.Value + } + + tests := []struct { + method string + args []any + + expected expected + }{ + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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", + }, + }, + }, + }, + { + 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"), + }, + }, + }, + }, + { + 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), + }, + }, + }, + }, + { + 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), + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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{ + High_128: 72623859790382856, + Value: &Ydb.Value_Low_128{ + Low_128: 648519454493508870, + }, + }, + }, + }, + { + 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"}`, + }, + }, + }, + }, + { + 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"}`, + }, + }, + }, + }, + { + 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"}`), + }, + }, + }, + }, + { + 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, + }, + }, + }, + { + 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", + }, + }, + }, + }, + { + 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", + }, + }, + }, + }, + { + 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").BeginList().Add() + + result, ok := xtest.CallMethod(item, tc.method, tc.args...)[0].(*list) + require.True(t, ok) + + params := result.EndList().Build().ToYDB(a) + require.Equal(t, xtest.ToJSON( + map[string]*Ydb.TypedValue{ + "$x": { + Type: &Ydb.Type{ + Type: &Ydb.Type_ListType{ + ListType: &Ydb.ListType{ + Item: tc.expected.Type, + }, + }, + }, + Value: &Ydb.Value{ + Items: []*Ydb.Value{ + tc.expected.Value, + }, + }, + }, + }), xtest.ToJSON(params)) + }) + } +} + +func TestList_AddItems(t *testing.T) { + a := allocator.New() + defer a.Free() + params := Builder{}.Param("$x").BeginList(). + AddItems(value.Uint64Value(123), value.Uint64Value(321)). + EndList().Build().ToYDB(a) + require.Equal(t, xtest.ToJSON( + map[string]*Ydb.TypedValue{ + "$x": { + Type: &Ydb.Type{ + Type: &Ydb.Type_ListType{ + ListType: &Ydb.ListType{ + Item: &Ydb.Type{Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_UINT64}}, + }, + }, + }, + Value: &Ydb.Value{ + Items: []*Ydb.Value{ + { + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 123, + }, + }, + { + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 321, + }, + }, + }, + }, + }, + }), xtest.ToJSON(params)) +} diff --git a/internal/params/optional.go b/internal/params/optional.go new file mode 100644 index 000000000..f5aa02efb --- /dev/null +++ b/internal/params/optional.go @@ -0,0 +1,178 @@ +package params + +import ( + "time" + + "github.com/ydb-platform/ydb-go-sdk/v3/internal/value" +) + +type ( + optional struct { + parent Builder + name string + value value.Value + } + optionalBuilder struct { + opt *optional + } +) + +func (b *optionalBuilder) EndOptional() Builder { + b.opt.parent.params = append(b.opt.parent.params, &Parameter{ + parent: b.opt.parent, + name: b.opt.name, + value: value.OptionalValue(b.opt.value), + }) + + return b.opt.parent +} + +func (p *optional) Text(v string) *optionalBuilder { + p.value = value.TextValue(v) + + return &optionalBuilder{opt: p} +} + +func (p *optional) Bytes(v []byte) *optionalBuilder { + p.value = value.BytesValue(v) + + return &optionalBuilder{opt: p} +} + +func (p *optional) Bool(v bool) *optionalBuilder { + p.value = value.BoolValue(v) + + return &optionalBuilder{opt: p} +} + +func (p *optional) Uint64(v uint64) *optionalBuilder { + p.value = value.Uint64Value(v) + + return &optionalBuilder{opt: p} +} + +func (p *optional) Int64(v int64) *optionalBuilder { + p.value = value.Int64Value(v) + + return &optionalBuilder{opt: p} +} + +func (p *optional) Uint32(v uint32) *optionalBuilder { + p.value = value.Uint32Value(v) + + return &optionalBuilder{opt: p} +} + +func (p *optional) Int32(v int32) *optionalBuilder { + p.value = value.Int32Value(v) + + return &optionalBuilder{opt: p} +} + +func (p *optional) Uint16(v uint16) *optionalBuilder { + p.value = value.Uint16Value(v) + + return &optionalBuilder{opt: p} +} + +func (p *optional) Int16(v int16) *optionalBuilder { + p.value = value.Int16Value(v) + + return &optionalBuilder{opt: p} +} + +func (p *optional) Uint8(v uint8) *optionalBuilder { + p.value = value.Uint8Value(v) + + return &optionalBuilder{opt: p} +} + +func (p *optional) Int8(v int8) *optionalBuilder { + p.value = value.Int8Value(v) + + return &optionalBuilder{opt: p} +} + +func (p *optional) Float(v float32) *optionalBuilder { + p.value = value.FloatValue(v) + + return &optionalBuilder{opt: p} +} + +func (p *optional) Double(v float64) *optionalBuilder { + p.value = value.DoubleValue(v) + + return &optionalBuilder{opt: p} +} + +func (p *optional) Decimal(v [16]byte, precision, scale uint32) *optionalBuilder { + p.value = value.DecimalValue(v, precision, scale) + + return &optionalBuilder{opt: p} +} + +func (p *optional) Timestamp(v time.Time) *optionalBuilder { + p.value = value.TimestampValueFromTime(v) + + return &optionalBuilder{opt: p} +} + +func (p *optional) Date(v time.Time) *optionalBuilder { + p.value = value.DateValueFromTime(v) + + return &optionalBuilder{opt: p} +} + +func (p *optional) Datetime(v time.Time) *optionalBuilder { + p.value = value.DatetimeValueFromTime(v) + + return &optionalBuilder{opt: p} +} + +func (p *optional) Interval(v time.Duration) *optionalBuilder { + p.value = value.IntervalValueFromDuration(v) + + return &optionalBuilder{opt: p} +} + +func (p *optional) JSON(v string) *optionalBuilder { + p.value = value.JSONValue(v) + + return &optionalBuilder{opt: p} +} + +func (p *optional) JSONDocument(v string) *optionalBuilder { + p.value = value.JSONDocumentValue(v) + + return &optionalBuilder{opt: p} +} + +func (p *optional) YSON(v []byte) *optionalBuilder { + p.value = value.YSONValue(v) + + return &optionalBuilder{opt: p} +} + +func (p *optional) UUID(v [16]byte) *optionalBuilder { + p.value = value.UUIDValue(v) + + return &optionalBuilder{opt: p} +} + +func (p *optional) TzDate(v time.Time) *optionalBuilder { + p.value = value.TzDateValueFromTime(v) + + return &optionalBuilder{opt: p} +} + +func (p *optional) TzTimestamp(v time.Time) *optionalBuilder { + p.value = value.TzTimestampValueFromTime(v) + + return &optionalBuilder{opt: p} +} + +func (p *optional) TzDatetime(v time.Time) *optionalBuilder { + p.value = value.TzDatetimeValueFromTime(v) + + return &optionalBuilder{opt: p} +} diff --git a/internal/params/optional_test.go b/internal/params/optional_test.go new file mode 100644 index 000000000..0c21718a1 --- /dev/null +++ b/internal/params/optional_test.go @@ -0,0 +1,436 @@ +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 TestOptional(t *testing.T) { + type expected struct { + Type *Ydb.Type + Value *Ydb.Value + } + + tests := []struct { + method string + args []any + + expected expected + }{ + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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", + }, + }, + }, + }, + { + 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"), + }, + }, + }, + }, + { + 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), + }, + }, + }, + }, + { + 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), + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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{ + High_128: 72623859790382856, + Value: &Ydb.Value_Low_128{ + Low_128: 648519454493508870, + }, + }, + }, + }, + { + 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"}`, + }, + }, + }, + }, + { + 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"}`, + }, + }, + }, + }, + { + 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"}`), + }, + }, + }, + }, + { + 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, + }, + }, + }, + { + 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", + }, + }, + }, + }, + { + 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", + }, + }, + }, + }, + { + 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").BeginOptional() + + result, ok := xtest.CallMethod(item, tc.method, tc.args...)[0].(*optionalBuilder) + require.True(t, ok) + + params := result.EndOptional().Build().ToYDB(a) + require.Equal(t, xtest.ToJSON( + map[string]*Ydb.TypedValue{ + "$x": { + Type: &Ydb.Type{ + Type: &Ydb.Type_OptionalType{ + OptionalType: &Ydb.OptionalType{ + Item: tc.expected.Type, + }, + }, + }, + Value: tc.expected.Value, + }, + }), xtest.ToJSON(params)) + }) + } +} diff --git a/internal/params/parameters.go b/internal/params/parameters.go new file mode 100644 index 000000000..aae4e38d0 --- /dev/null +++ b/internal/params/parameters.go @@ -0,0 +1,332 @@ +package params + +import ( + "fmt" + "time" + + "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/value" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/xstring" +) + +type ( + NamedValue interface { + Name() string + Value() value.Value + } + Parameter struct { + parent Builder + name string + value value.Value + } + Parameters []*Parameter +) + +func Named(name string, value value.Value) *Parameter { + return &Parameter{ + name: name, + value: value, + } +} + +func (p *Parameter) Name() string { + return p.name +} + +func (p *Parameter) Value() value.Value { + return p.value +} + +func (p *Parameters) String() string { + buffer := xstring.Buffer() + defer buffer.Free() + + buffer.WriteByte('{') + if p != nil { + for i, param := range *p { + if i != 0 { + buffer.WriteByte(',') + } + buffer.WriteByte('"') + buffer.WriteString(param.name) + buffer.WriteString("\":") + buffer.WriteString(param.value.Yql()) + } + } + buffer.WriteByte('}') + + return buffer.String() +} + +func (p *Parameters) ToYDB(a *allocator.Allocator) map[string]*Ydb.TypedValue { + if p == nil { + return nil + } + parameters := make(map[string]*Ydb.TypedValue, len(*p)) + for _, param := range *p { + parameters[param.name] = value.ToYDB(param.value, a) + } + + return parameters +} + +func (p *Parameters) Each(it func(name string, v value.Value)) { + if p == nil { + return + } + for _, p := range *p { + it(p.name, p.value) + } +} + +func (p *Parameters) Count() int { + if p == nil { + return 0 + } + + return len(*p) +} + +func (p *Parameters) Add(params ...NamedValue) { + for _, param := range params { + *p = append(*p, Named(param.Name(), param.Value())) + } +} + +func (p *Parameter) BeginOptional() *optional { + return &optional{ + parent: p.parent, + name: p.name, + } +} + +func (p *Parameter) BeginList() *list { + return &list{ + parent: p.parent, + name: p.name, + } +} + +func (p *Parameter) Pg() pgParam { + return pgParam{p} +} + +func (p *Parameter) BeginSet() *set { + return &set{ + parent: p.parent, + name: p.name, + } +} + +func (p *Parameter) BeginDict() *dict { + return &dict{ + parent: p.parent, + name: p.name, + } +} + +func (p *Parameter) BeginTuple() *tuple { + return &tuple{ + parent: p.parent, + name: p.name, + } +} + +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) + + return p.parent +} + +func (p *Parameter) Bytes(v []byte) Builder { + p.value = value.BytesValue(v) + p.parent.params = append(p.parent.params, p) + + return p.parent +} + +func (p *Parameter) Bool(v bool) Builder { + p.value = value.BoolValue(v) + p.parent.params = append(p.parent.params, p) + + return p.parent +} + +func (p *Parameter) Uint64(v uint64) Builder { + p.value = value.Uint64Value(v) + p.parent.params = append(p.parent.params, p) + + return p.parent +} + +func (p *Parameter) Int64(v int64) Builder { + p.value = value.Int64Value(v) + p.parent.params = append(p.parent.params, p) + + return p.parent +} + +func (p *Parameter) Uint32(v uint32) Builder { + p.value = value.Uint32Value(v) + p.parent.params = append(p.parent.params, p) + + return p.parent +} + +func (p *Parameter) Int32(v int32) Builder { + p.value = value.Int32Value(v) + p.parent.params = append(p.parent.params, p) + + return p.parent +} + +func (p *Parameter) Uint16(v uint16) Builder { + p.value = value.Uint16Value(v) + p.parent.params = append(p.parent.params, p) + + return p.parent +} + +func (p *Parameter) Int16(v int16) Builder { + p.value = value.Int16Value(v) + p.parent.params = append(p.parent.params, p) + + return p.parent +} + +func (p *Parameter) Uint8(v uint8) Builder { + p.value = value.Uint8Value(v) + p.parent.params = append(p.parent.params, p) + + return p.parent +} + +func (p *Parameter) Int8(v int8) Builder { + p.value = value.Int8Value(v) + p.parent.params = append(p.parent.params, p) + + return p.parent +} + +func (p *Parameter) Float(v float32) Builder { + p.value = value.FloatValue(v) + p.parent.params = append(p.parent.params, p) + + return p.parent +} + +func (p *Parameter) Double(v float64) Builder { + p.value = value.DoubleValue(v) + p.parent.params = append(p.parent.params, p) + + return p.parent +} + +func (p *Parameter) Decimal(v [16]byte, precision, scale uint32) Builder { + p.value = value.DecimalValue(v, precision, scale) + p.parent.params = append(p.parent.params, p) + + return p.parent +} + +func (p *Parameter) Timestamp(v time.Time) Builder { + p.value = value.TimestampValueFromTime(v) + p.parent.params = append(p.parent.params, p) + + return p.parent +} + +func (p *Parameter) Date(v time.Time) Builder { + p.value = value.DateValueFromTime(v) + p.parent.params = append(p.parent.params, p) + + return p.parent +} + +func (p *Parameter) Datetime(v time.Time) Builder { + p.value = value.DatetimeValueFromTime(v) + p.parent.params = append(p.parent.params, p) + + return p.parent +} + +func (p *Parameter) Interval(v time.Duration) Builder { + p.value = value.IntervalValueFromDuration(v) + p.parent.params = append(p.parent.params, p) + + return p.parent +} + +func (p *Parameter) JSON(v string) Builder { + p.value = value.JSONValue(v) + p.parent.params = append(p.parent.params, p) + + return p.parent +} + +func (p *Parameter) JSONDocument(v string) Builder { + p.value = value.JSONDocumentValue(v) + p.parent.params = append(p.parent.params, p) + + return p.parent +} + +func (p *Parameter) YSON(v []byte) Builder { + p.value = value.YSONValue(v) + p.parent.params = append(p.parent.params, p) + + return p.parent +} + +func (p *Parameter) UUID(v [16]byte) Builder { + p.value = value.UUIDValue(v) + p.parent.params = append(p.parent.params, p) + + return p.parent +} + +func (p *Parameter) TzDate(v time.Time) Builder { + p.value = value.TzDateValueFromTime(v) + p.parent.params = append(p.parent.params, p) + + return p.parent +} + +func (p *Parameter) TzTimestamp(v time.Time) Builder { + p.value = value.TzTimestampValueFromTime(v) + p.parent.params = append(p.parent.params, p) + + return p.parent +} + +func (p *Parameter) TzDatetime(v time.Time) Builder { + p.value = value.TzDatetimeValueFromTime(v) + p.parent.params = append(p.parent.params, p) + + return p.parent +} + +func Declare(p *Parameter) string { + return fmt.Sprintf( + "DECLARE %s AS %s", + p.name, + p.value.Type().Yql(), + ) +} diff --git a/internal/params/parameters_test.go b/internal/params/parameters_test.go new file mode 100644 index 000000000..1c49c3f45 --- /dev/null +++ b/internal/params/parameters_test.go @@ -0,0 +1,70 @@ +package params + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ydb-platform/ydb-go-sdk/v3/internal/allocator" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/value" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/xtest" +) + +func TestParameter(t *testing.T) { + p := Named("x", value.TextValue("X")) + require.Equal(t, "x", p.Name()) + require.EqualValues(t, "X", p.Value()) + require.Equal(t, "DECLARE x AS Utf8", Declare(p)) +} + +func TestParameters(t *testing.T) { + p := &Parameters{} + p.Add( + Named("x", value.TextValue("X")), + Named("y", value.TextValue("Y")), + ) + require.Equal(t, "{\"x\":\"X\"u,\"y\":\"Y\"u}", p.String()) + require.Equal(t, 2, p.Count()) + visited := make(map[string]value.Value, 2) + p.Each(func(name string, v value.Value) { + visited[name] = v + }) + require.Len(t, visited, 2) + require.EqualValues(t, map[string]value.Value{ + "x": value.TextValue("X"), + "y": value.TextValue("Y"), + }, visited) +} + +func TestNil(t *testing.T) { + for _, tt := range []struct { + name string + p *Parameters + }{ + { + name: xtest.CurrentFileLine(), + p: nil, + }, + { + name: xtest.CurrentFileLine(), + p: &Parameters{}, + }, + { + name: xtest.CurrentFileLine(), + p: Builder{}.Build(), + }, + } { + t.Run(tt.name, func(t *testing.T) { + require.Equal(t, "{}", tt.p.String()) + require.Equal(t, 0, tt.p.Count()) + visited := make(map[string]value.Value, 1) + tt.p.Each(func(name string, v value.Value) { + visited[name] = v + }) + require.Empty(t, visited) + a := allocator.New() + defer a.Free() + require.Empty(t, tt.p.ToYDB(a)) + }) + } +} diff --git a/internal/params/pg.go b/internal/params/pg.go new file mode 100644 index 000000000..542957859 --- /dev/null +++ b/internal/params/pg.go @@ -0,0 +1,31 @@ +package params + +import ( + "strconv" + + "github.com/ydb-platform/ydb-go-sdk/v3/internal/pg" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/value" +) + +type pgParam struct { + param *Parameter +} + +func (p pgParam) Unknown(val string) Builder { + return p.Value(pg.OIDUnknown, val) +} + +func (p pgParam) Value(oid uint32, val string) Builder { + p.param.value = value.PgValue(oid, val) + p.param.parent.params = append(p.param.parent.params, p.param) + + return p.param.parent +} + +func (p pgParam) Int4(val int32) Builder { + return p.Value(pg.OIDInt4, strconv.FormatInt(int64(val), 10)) +} + +func (p pgParam) Int8(val int64) Builder { + return p.Value(pg.OIDInt8, strconv.FormatInt(val, 10)) +} diff --git a/internal/params/pg_test.go b/internal/params/pg_test.go new file mode 100644 index 000000000..ce470864c --- /dev/null +++ b/internal/params/pg_test.go @@ -0,0 +1,100 @@ +package params + +import ( + "testing" + + "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/pg" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/xtest" +) + +func TestPg(t *testing.T) { + type expected struct { + Type *Ydb.Type + Value *Ydb.Value + } + + tests := []struct { + method string + args []any + + expected expected + }{ + { + method: "Unknown", + args: []any{"123"}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_PgType{ + PgType: &Ydb.PgType{ + Oid: pg.OIDUnknown, + }, + }, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_TextValue{TextValue: "123"}, + }, + }, + }, + { + method: "Int4", + args: []any{int32(123)}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_PgType{ + PgType: &Ydb.PgType{ + Oid: pg.OIDInt4, + }, + }, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_TextValue{TextValue: "123"}, + }, + }, + }, + { + method: "Int8", + args: []any{int64(123)}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_PgType{ + PgType: &Ydb.PgType{ + Oid: pg.OIDInt8, + }, + }, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_TextValue{TextValue: "123"}, + }, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.method, func(t *testing.T) { + a := allocator.New() + defer a.Free() + + item := Builder{}.Param("$x").Pg() + + result, ok := xtest.CallMethod(item, tc.method, tc.args...)[0].(Builder) + require.True(t, ok) + + params := result.Build().ToYDB(a) + + require.Equal(t, xtest.ToJSON( + map[string]*Ydb.TypedValue{ + "$x": { + Type: tc.expected.Type, + Value: tc.expected.Value, + }, + }), xtest.ToJSON(params)) + }) + } +} diff --git a/internal/params/set.go b/internal/params/set.go new file mode 100644 index 000000000..cb2303fde --- /dev/null +++ b/internal/params/set.go @@ -0,0 +1,191 @@ +package params + +import ( + "time" + + "github.com/ydb-platform/ydb-go-sdk/v3/internal/value" +) + +type ( + set struct { + parent Builder + name string + values []value.Value + } + + setItem struct { + parent *set + } +) + +func (s *set) Add() *setItem { + return &setItem{ + parent: s, + } +} + +func (s *set) AddItems(items ...value.Value) *set { + s.values = append(s.values, items...) + + return s +} + +func (s *set) EndSet() Builder { + s.parent.params = append(s.parent.params, &Parameter{ + parent: s.parent, + name: s.name, + value: value.SetValue(s.values...), + }) + + return s.parent +} + +func (s *setItem) Text(v string) *set { + s.parent.values = append(s.parent.values, value.TextValue(v)) + + return s.parent +} + +func (s *setItem) Bytes(v []byte) *set { + s.parent.values = append(s.parent.values, value.BytesValue(v)) + + return s.parent +} + +func (s *setItem) Bool(v bool) *set { + s.parent.values = append(s.parent.values, value.BoolValue(v)) + + return s.parent +} + +func (s *setItem) Uint64(v uint64) *set { + s.parent.values = append(s.parent.values, value.Uint64Value(v)) + + return s.parent +} + +func (s *setItem) Int64(v int64) *set { + s.parent.values = append(s.parent.values, value.Int64Value(v)) + + return s.parent +} + +func (s *setItem) Uint32(v uint32) *set { + s.parent.values = append(s.parent.values, value.Uint32Value(v)) + + return s.parent +} + +func (s *setItem) Int32(v int32) *set { + s.parent.values = append(s.parent.values, value.Int32Value(v)) + + return s.parent +} + +func (s *setItem) Uint16(v uint16) *set { + s.parent.values = append(s.parent.values, value.Uint16Value(v)) + + return s.parent +} + +func (s *setItem) Int16(v int16) *set { + s.parent.values = append(s.parent.values, value.Int16Value(v)) + + return s.parent +} + +func (s *setItem) Uint8(v uint8) *set { + s.parent.values = append(s.parent.values, value.Uint8Value(v)) + + return s.parent +} + +func (s *setItem) Int8(v int8) *set { + s.parent.values = append(s.parent.values, value.Int8Value(v)) + + return s.parent +} + +func (s *setItem) Float(v float32) *set { + s.parent.values = append(s.parent.values, value.FloatValue(v)) + + return s.parent +} + +func (s *setItem) Double(v float64) *set { + s.parent.values = append(s.parent.values, value.DoubleValue(v)) + + return s.parent +} + +func (s *setItem) Decimal(v [16]byte, precision, scale uint32) *set { + s.parent.values = append(s.parent.values, value.DecimalValue(v, precision, scale)) + + return s.parent +} + +func (s *setItem) Timestamp(v time.Time) *set { + s.parent.values = append(s.parent.values, value.TimestampValueFromTime(v)) + + return s.parent +} + +func (s *setItem) Date(v time.Time) *set { + s.parent.values = append(s.parent.values, value.DateValueFromTime(v)) + + return s.parent +} + +func (s *setItem) Datetime(v time.Time) *set { + s.parent.values = append(s.parent.values, value.DatetimeValueFromTime(v)) + + return s.parent +} + +func (s *setItem) Interval(v time.Duration) *set { + s.parent.values = append(s.parent.values, value.IntervalValueFromDuration(v)) + + return s.parent +} + +func (s *setItem) JSON(v string) *set { + s.parent.values = append(s.parent.values, value.JSONValue(v)) + + return s.parent +} + +func (s *setItem) JSONDocument(v string) *set { + s.parent.values = append(s.parent.values, value.JSONDocumentValue(v)) + + return s.parent +} + +func (s *setItem) YSON(v []byte) *set { + s.parent.values = append(s.parent.values, value.YSONValue(v)) + + return s.parent +} + +func (s *setItem) UUID(v [16]byte) *set { + s.parent.values = append(s.parent.values, value.UUIDValue(v)) + + return s.parent +} + +func (s *setItem) TzDate(v time.Time) *set { + s.parent.values = append(s.parent.values, value.TzDateValueFromTime(v)) + + return s.parent +} + +func (s *setItem) TzTimestamp(v time.Time) *set { + s.parent.values = append(s.parent.values, value.TzTimestampValueFromTime(v)) + + return s.parent +} + +func (s *setItem) TzDatetime(v time.Time) *set { + s.parent.values = append(s.parent.values, value.TzDatetimeValueFromTime(v)) + + return s.parent +} diff --git a/internal/params/set_test.go b/internal/params/set_test.go new file mode 100644 index 000000000..6ff75d464 --- /dev/null +++ b/internal/params/set_test.go @@ -0,0 +1,500 @@ +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/value" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/xtest" +) + +func TestSet(t *testing.T) { + type expected struct { + Type *Ydb.Type + Value *Ydb.Value + } + + tests := []struct { + method string + args []any + + expected expected + }{ + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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", + }, + }, + }, + }, + { + 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"), + }, + }, + }, + }, + { + 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), + }, + }, + }, + }, + { + 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), + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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{ + High_128: 72623859790382856, + Value: &Ydb.Value_Low_128{ + Low_128: 648519454493508870, + }, + }, + }, + }, + { + 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"}`, + }, + }, + }, + }, + { + 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"}`, + }, + }, + }, + }, + { + 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"}`), + }, + }, + }, + }, + { + 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, + }, + }, + }, + { + 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", + }, + }, + }, + }, + { + 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", + }, + }, + }, + }, + { + 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").BeginSet().Add() + + result, ok := xtest.CallMethod(item, tc.method, tc.args...)[0].(*set) + require.True(t, ok) + + params := result.EndSet().Build().ToYDB(a) + require.Equal(t, xtest.ToJSON( + map[string]*Ydb.TypedValue{ + "$x": { + Type: &Ydb.Type{ + Type: &Ydb.Type_DictType{ + DictType: &Ydb.DictType{ + Key: tc.expected.Type, + Payload: &Ydb.Type{ + Type: &Ydb.Type_VoidType{}, + }, + }, + }, + }, + Value: &Ydb.Value{ + Pairs: []*Ydb.ValuePair{ + { + Key: tc.expected.Value, + Payload: &Ydb.Value{ + Value: &Ydb.Value_NullFlagValue{}, + }, + }, + }, + }, + }, + }), xtest.ToJSON(params)) + }) + } +} + +func TestSet_AddItems(t *testing.T) { + a := allocator.New() + defer a.Free() + params := Builder{}.Param("$x").BeginSet(). + AddItems(value.Uint64Value(123), value.Uint64Value(321)). + EndSet().Build().ToYDB(a) + require.Equal(t, xtest.ToJSON( + map[string]*Ydb.TypedValue{ + "$x": { + Type: &Ydb.Type{ + Type: &Ydb.Type_DictType{ + DictType: &Ydb.DictType{ + Key: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UINT64, + }, + }, + Payload: &Ydb.Type{ + Type: &Ydb.Type_VoidType{}, + }, + }, + }, + }, + Value: &Ydb.Value{ + Pairs: []*Ydb.ValuePair{ + { + Key: &Ydb.Value{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 123, + }, + }, + Payload: &Ydb.Value{ + Value: &Ydb.Value_NullFlagValue{}, + }, + }, + { + Key: &Ydb.Value{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 321, + }, + }, + Payload: &Ydb.Value{ + Value: &Ydb.Value_NullFlagValue{}, + }, + }, + }, + }, + }, + }), 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 new file mode 100644 index 000000000..7718a5026 --- /dev/null +++ b/internal/params/tuple.go @@ -0,0 +1,190 @@ +package params + +import ( + "time" + + "github.com/ydb-platform/ydb-go-sdk/v3/internal/value" +) + +type ( + tuple struct { + parent Builder + name string + values []value.Value + } + tupleItem struct { + parent *tuple + } +) + +func (t *tuple) Add() *tupleItem { + return &tupleItem{ + parent: t, + } +} + +func (t *tuple) AddItems(items ...value.Value) *tuple { + t.values = append(t.values, items...) + + return t +} + +func (t *tuple) EndTuple() Builder { + t.parent.params = append(t.parent.params, &Parameter{ + parent: t.parent, + name: t.name, + value: value.TupleValue(t.values...), + }) + + return t.parent +} + +func (t *tupleItem) Text(v string) *tuple { + t.parent.values = append(t.parent.values, value.TextValue(v)) + + return t.parent +} + +func (t *tupleItem) Bytes(v []byte) *tuple { + t.parent.values = append(t.parent.values, value.BytesValue(v)) + + return t.parent +} + +func (t *tupleItem) Bool(v bool) *tuple { + t.parent.values = append(t.parent.values, value.BoolValue(v)) + + return t.parent +} + +func (t *tupleItem) Uint64(v uint64) *tuple { + t.parent.values = append(t.parent.values, value.Uint64Value(v)) + + return t.parent +} + +func (t *tupleItem) Int64(v int64) *tuple { + t.parent.values = append(t.parent.values, value.Int64Value(v)) + + return t.parent +} + +func (t *tupleItem) Uint32(v uint32) *tuple { + t.parent.values = append(t.parent.values, value.Uint32Value(v)) + + return t.parent +} + +func (t *tupleItem) Int32(v int32) *tuple { + t.parent.values = append(t.parent.values, value.Int32Value(v)) + + return t.parent +} + +func (t *tupleItem) Uint16(v uint16) *tuple { + t.parent.values = append(t.parent.values, value.Uint16Value(v)) + + return t.parent +} + +func (t *tupleItem) Int16(v int16) *tuple { + t.parent.values = append(t.parent.values, value.Int16Value(v)) + + return t.parent +} + +func (t *tupleItem) Uint8(v uint8) *tuple { + t.parent.values = append(t.parent.values, value.Uint8Value(v)) + + return t.parent +} + +func (t *tupleItem) Int8(v int8) *tuple { + t.parent.values = append(t.parent.values, value.Int8Value(v)) + + return t.parent +} + +func (t *tupleItem) Float(v float32) *tuple { + t.parent.values = append(t.parent.values, value.FloatValue(v)) + + return t.parent +} + +func (t *tupleItem) Double(v float64) *tuple { + t.parent.values = append(t.parent.values, value.DoubleValue(v)) + + return t.parent +} + +func (t *tupleItem) Decimal(v [16]byte, precision, scale uint32) *tuple { + t.parent.values = append(t.parent.values, value.DecimalValue(v, precision, scale)) + + return t.parent +} + +func (t *tupleItem) Timestamp(v time.Time) *tuple { + t.parent.values = append(t.parent.values, value.TimestampValueFromTime(v)) + + return t.parent +} + +func (t *tupleItem) Date(v time.Time) *tuple { + t.parent.values = append(t.parent.values, value.DateValueFromTime(v)) + + return t.parent +} + +func (t *tupleItem) Datetime(v time.Time) *tuple { + t.parent.values = append(t.parent.values, value.DatetimeValueFromTime(v)) + + return t.parent +} + +func (t *tupleItem) Interval(v time.Duration) *tuple { + t.parent.values = append(t.parent.values, value.IntervalValueFromDuration(v)) + + return t.parent +} + +func (t *tupleItem) JSON(v string) *tuple { + t.parent.values = append(t.parent.values, value.JSONValue(v)) + + return t.parent +} + +func (t *tupleItem) JSONDocument(v string) *tuple { + t.parent.values = append(t.parent.values, value.JSONDocumentValue(v)) + + return t.parent +} + +func (t *tupleItem) YSON(v []byte) *tuple { + t.parent.values = append(t.parent.values, value.YSONValue(v)) + + return t.parent +} + +func (t *tupleItem) UUID(v [16]byte) *tuple { + t.parent.values = append(t.parent.values, value.UUIDValue(v)) + + 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 new file mode 100644 index 000000000..1c6510603 --- /dev/null +++ b/internal/params/tuple_test.go @@ -0,0 +1,488 @@ +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/value" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/xtest" +) + +func TestTuple(t *testing.T) { + type expected struct { + Type *Ydb.Type + Value *Ydb.Value + } + + tests := []struct { + method string + args []any + + expected expected + }{ + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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", + }, + }, + }, + }, + { + 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"), + }, + }, + }, + }, + { + 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), + }, + }, + }, + }, + { + 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), + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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, + }, + }, + }, + }, + { + 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{ + High_128: 72623859790382856, + Value: &Ydb.Value_Low_128{ + Low_128: 648519454493508870, + }, + }, + }, + }, + { + 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"}`, + }, + }, + }, + }, + { + 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"}`, + }, + }, + }, + }, + { + 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"}`), + }, + }, + }, + }, + { + 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, + }, + }, + }, + { + 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", + }, + }, + }, + }, + { + 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", + }, + }, + }, + }, + { + 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{ + tc.expected.Value, + }, + }, + }, + }), 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, + }, + }, + }, + }, + }, + }, + Value: &Ydb.Value{ + Items: []*Ydb.Value{ + { + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 123, + }, + }, + { + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 321, + }, + }, + }, + }, + }, + }), 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/pg/pgconst.go b/internal/pg/pgconst.go new file mode 100644 index 000000000..f6a8273c8 --- /dev/null +++ b/internal/pg/pgconst.go @@ -0,0 +1,9 @@ +package pg + +const ( + // https://github.com/postgres/postgres/blob/master/src/include/catalog/pg_type.dat + + OIDInt4 = 23 + OIDInt8 = 20 + OIDUnknown = 705 +) diff --git a/internal/pool/defaults.go b/internal/pool/defaults.go new file mode 100644 index 000000000..2591e8438 --- /dev/null +++ b/internal/pool/defaults.go @@ -0,0 +1,31 @@ +package pool + +const DefaultLimit = 50 + +var defaultTrace = &Trace{ + OnNew: func(info *NewStartInfo) func(info *NewDoneInfo) { + return func(info *NewDoneInfo) { + } + }, + OnClose: func(info *CloseStartInfo) func(info *CloseDoneInfo) { + return func(info *CloseDoneInfo) { + } + }, + OnTry: func(info *TryStartInfo) func(info *TryDoneInfo) { + return func(info *TryDoneInfo) { + } + }, + OnWith: func(info *WithStartInfo) func(info *WithDoneInfo) { + return func(info *WithDoneInfo) { + } + }, + OnPut: func(info *PutStartInfo) func(info *PutDoneInfo) { + return func(info *PutDoneInfo) { + } + }, + OnGet: func(info *GetStartInfo) func(info *GetDoneInfo) { + return func(info *GetDoneInfo) { + } + }, + OnChange: func(info ChangeInfo) {}, +} diff --git a/internal/pool/errors.go b/internal/pool/errors.go new file mode 100644 index 000000000..36b6526c7 --- /dev/null +++ b/internal/pool/errors.go @@ -0,0 +1,10 @@ +package pool + +import ( + "errors" +) + +var ( + errClosedPool = errors.New("closed pool") + errItemIsNotAlive = errors.New("item is not alive") +) diff --git a/internal/pool/pool.go b/internal/pool/pool.go new file mode 100644 index 000000000..b7a5e8a3f --- /dev/null +++ b/internal/pool/pool.go @@ -0,0 +1,484 @@ +package pool + +import ( + "context" + "time" + + "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" + "github.com/ydb-platform/ydb-go-sdk/v3/trace" +) + +type ( + Item[T any] interface { + *T + IsAlive() bool + Close(ctx context.Context) error + } + 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 + + 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{} + + stats *safeStats + } + option[PT Item[T], T any] func(p *Pool[PT, T]) +) + +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 (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.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 + } +} + +func WithLimit[PT Item[T], T any](size int) option[PT, T] { + return func(p *Pool[PT, T]) { + p.limit = size + } +} + +func WithTrace[PT Item[T], T any](t *Trace) option[PT, T] { + return func(p *Pool[PT, T]) { + p.trace = t + } +} + +func New[PT Item[T], T any]( + ctx context.Context, + opts ...option[PT, T], +) *Pool[PT, T] { + p := &Pool[PT, T]{ + trace: defaultTrace, + limit: DefaultLimit, + createItem: func(ctx context.Context) (PT, error) { + var item T + + return &item, nil + }, + done: make(chan struct{}), + } + + for _, opt := range opts { + if opt != nil { + opt(p) + } + } + + onDone := p.trace.OnNew(&NewStartInfo{ + Context: &ctx, + Call: stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/pool.New"), + }) + + defer func() { + onDone(&NewDoneInfo{ + Limit: p.limit, + }) + }() + + 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 = &safeStats{ + v: stats.Stats{Limit: p.limit}, + onChange: p.trace.OnChange, + } + + return p +} + +func (p *Pool[PT, T]) Stats() stats.Stats { + return p.stats.Get() +} + +func (p *Pool[PT, T]) getItem(ctx context.Context) (_ PT, finalErr error) { + onDone := p.trace.OnGet(&GetStartInfo{ + Context: &ctx, + Call: stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/pool.(*Pool).getItem"), + }) + defer func() { + onDone(&GetDoneInfo{ + Error: finalErr, + }) + }() + + if err := ctx.Err(); err != nil { + return nil, xerrors.WithStackTrace(err) + } + + select { + case <-p.done: + return nil, xerrors.WithStackTrace(errClosedPool) + 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() + } + }) + + 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() + } + + item, err := p.createItem(ctx) + if err != nil { + return nil, xerrors.WithStackTrace(err) + } + + addedToIndex := false + p.mu.WithLock(func() { + if len(p.index) < p.limit { + p.index[item] = struct{}{} + addedToIndex = true + } + }) + if addedToIndex { + p.stats.Index().Inc() + } + + return item, nil + } +} + +func (p *Pool[PT, T]) putItem(ctx context.Context, item PT) (finalErr error) { + onDone := p.trace.OnPut(&PutStartInfo{ + Context: &ctx, + Call: stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/pool.(*Pool).putItem"), + }) + defer func() { + onDone(&PutDoneInfo{ + Error: finalErr, + }) + }() + + if err := ctx.Err(); err != nil { + return xerrors.WithStackTrace(err) + } + + select { + case <-p.done: + return xerrors.WithStackTrace(errClosedPool) + default: + if !item.IsAlive() { + _ = p.closeItem(ctx, item) + + p.mu.WithLock(func() { + delete(p.index, item) + }) + p.stats.Index().Dec() + + return xerrors.WithStackTrace(errItemIsNotAlive) + } + + p.mu.WithLock(func() { + p.idle = append(p.idle, item) + }) + p.stats.Idle().Inc() + + return nil + } +} + +func (p *Pool[PT, T]) closeItem(ctx context.Context, item PT) error { + ctx = xcontext.ValueOnly(ctx) + + 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 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("github.com/ydb-platform/ydb-go-sdk/3/internal/pool.(*Pool).try"), + }) + defer func() { + onDone(&TryDoneInfo{ + Error: finalErr, + }) + }() + + 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.putItem(ctx, item) + }() + + p.stats.InUse().Inc() + defer p.stats.InUse().Dec() + + err = f(ctx, item) + if err != nil { + return xerrors.WithStackTrace(err) + } + + return nil +} + +func (p *Pool[PT, T]) With( + ctx context.Context, + f func(ctx context.Context, item PT) error, + opts ...retry.Option, +) (finalErr error) { + var ( + onDone = p.trace.OnWith(&WithStartInfo{ + Context: &ctx, + Call: stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/pool.(*Pool).With"), + }) + attempts int + ) + defer func() { + onDone(&WithDoneInfo{ + Error: finalErr, + Attempts: attempts, + }) + }() + + err := retry.Retry(ctx, func(ctx context.Context) error { + err := p.try(ctx, f) + if err != nil { + return xerrors.WithStackTrace(err) + } + + return nil + }, append(opts, retry.WithTrace(&trace.Retry{ + OnRetry: func(info trace.RetryLoopStartInfo) func(trace.RetryLoopDoneInfo) { + return func(info trace.RetryLoopDoneInfo) { + attempts = info.Attempts + } + }, + }))...) + if err != nil { + return xerrors.WithStackTrace(err) + } + + return nil +} + +func (p *Pool[PT, T]) Close(ctx context.Context) (finalErr error) { + onDone := p.trace.OnClose(&CloseStartInfo{ + Context: &ctx, + Call: stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/pool.(*Pool).Close"), + }) + defer func() { + onDone(&CloseDoneInfo{ + Error: finalErr, + }) + }() + + close(p.done) + + p.mu.Lock() + defer p.mu.Unlock() + + errs := make([]error, 0, len(p.index)) + + for item := range p.index { + if err := item.Close(ctx); err != nil { + errs = append(errs, err) + } + } + + switch len(errs) { + case 0: + return nil + case 1: + return errs[0] + default: + return xerrors.Join(errs...) + } +} diff --git a/internal/pool/pool_test.go b/internal/pool/pool_test.go new file mode 100644 index 000000000..63f8a1c11 --- /dev/null +++ b/internal/pool/pool_test.go @@ -0,0 +1,320 @@ +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" +) + +type testItem struct { + v uint32 + + onClose func() error + onIsAlive func() bool +} + +func (t testItem) IsAlive() bool { + if t.onIsAlive != nil { + return t.onIsAlive() + } + + return true +} + +func (t testItem) ID() string { + return "" +} + +func (t testItem) Close(context.Context) error { + if t.onClose != nil { + return t.onClose() + } + + return nil +} + +func TestPool(t *testing.T) { + rootCtx := xtest.Context(t) + t.Run("New", func(t *testing.T) { + t.Run("Default", func(t *testing.T) { + p := New[*testItem, testItem](rootCtx) + err := p.With(rootCtx, func(ctx context.Context, testItem *testItem) error { + return nil + }) + require.NoError(t, err) + }) + t.Run("WithLimit", func(t *testing.T) { + p := New[*testItem, testItem](rootCtx, WithLimit[*testItem, testItem](1)) + require.EqualValues(t, 1, p.limit) + }) + t.Run("WithCreateFunc", func(t *testing.T) { + var newCounter int64 + p := New(rootCtx, + WithLimit[*testItem, testItem](1), + WithCreateFunc(func(context.Context) (*testItem, error) { + atomic.AddInt64(&newCounter, 1) + var v testItem + + return &v, nil + }), + ) + err := p.With(rootCtx, func(ctx context.Context, item *testItem) error { + return nil + }) + require.NoError(t, err) + require.EqualValues(t, p.limit, atomic.LoadInt64(&newCounter)) + }) + }) + 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) + cancel() + p := New[*testItem, testItem](ctx, WithLimit[*testItem, testItem](1)) + err := p.With(ctx, func(ctx context.Context, testItem *testItem) error { + return nil + }) + require.ErrorIs(t, err, context.Canceled) + }) + t.Run("DeadlineExceeded", func(t *testing.T) { + ctx, cancel := context.WithTimeout(rootCtx, 0) + cancel() + p := New[*testItem, testItem](ctx, WithLimit[*testItem, testItem](1)) + err := p.With(ctx, func(ctx context.Context, testItem *testItem) error { + return nil + }) + require.ErrorIs(t, err, context.DeadlineExceeded) + }) + }) + }) + t.Run("Item", func(t *testing.T) { + t.Run("Close", func(t *testing.T) { + xtest.TestManyTimes(t, func(t testing.TB) { + var ( + createCounter int64 + closeCounter int64 + ) + p := New(rootCtx, + WithLimit[*testItem, testItem](1), + WithCreateFunc(func(context.Context) (*testItem, error) { + atomic.AddInt64(&createCounter, 1) + + v := &testItem{ + onClose: func() error { + atomic.AddInt64(&closeCounter, 1) + + return nil + }, + } + + return v, nil + }), + ) + err := p.With(rootCtx, func(ctx context.Context, testItem *testItem) error { + return nil + }) + require.NoError(t, err) + require.GreaterOrEqual(t, atomic.LoadInt64(&createCounter), atomic.LoadInt64(&closeCounter)) + err = p.Close(rootCtx) + require.NoError(t, err) + require.EqualValues(t, atomic.LoadInt64(&createCounter), atomic.LoadInt64(&closeCounter)) + }, xtest.StopAfter(time.Second)) + }) + t.Run("IsAlive", func(t *testing.T) { + xtest.TestManyTimes(t, func(t testing.TB) { + var ( + newItems int64 + deleteItems int64 + expErr = xerrors.Retryable(errors.New("expected error"), xerrors.InvalidObject()) + ) + p := New(rootCtx, + WithLimit[*testItem, testItem](1), + WithCreateFunc(func(context.Context) (*testItem, error) { + atomic.AddInt64(&newItems, 1) + + v := &testItem{ + onClose: func() error { + atomic.AddInt64(&deleteItems, 1) + + return nil + }, + onIsAlive: func() bool { + return atomic.LoadInt64(&newItems) >= 10 + }, + } + + return v, nil + }), + ) + err := p.With(rootCtx, func(ctx context.Context, testItem *testItem) error { + if atomic.LoadInt64(&newItems) < 10 { + return expErr + } + + return nil + }) + require.NoError(t, err) + require.GreaterOrEqual(t, atomic.LoadInt64(&newItems), int64(9)) + require.GreaterOrEqual(t, atomic.LoadInt64(&newItems), atomic.LoadInt64(&deleteItems)) + err = p.Close(rootCtx) + require.NoError(t, err) + require.EqualValues(t, atomic.LoadInt64(&newItems), atomic.LoadInt64(&deleteItems)) + }, xtest.StopAfter(5*time.Second)) + }) + }) + t.Run("Stress", func(t *testing.T) { + xtest.TestManyTimes(t, func(t testing.TB) { + p := New[*testItem, testItem](rootCtx) + var wg sync.WaitGroup + wg.Add(DefaultLimit*2 + 1) + for range make([]struct{}, DefaultLimit*2) { + go func() { + defer wg.Done() + err := p.With(rootCtx, func(ctx context.Context, testItem *testItem) error { + return nil + }) + if err != nil && !xerrors.Is(err, errClosedPool, context.Canceled) { + t.Failed() + } + }() + } + go func() { + defer wg.Done() + time.Sleep(time.Millisecond) + err := p.Close(rootCtx) + require.NoError(t, err) + }() + wg.Wait() + }, 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/pool/stats/stats.go b/internal/pool/stats/stats.go new file mode 100644 index 000000000..dff03eaeb --- /dev/null +++ b/internal/pool/stats/stats.go @@ -0,0 +1,8 @@ +package stats + +type Stats struct { + Limit int + Index int + Idle int + InUse int +} diff --git a/internal/pool/trace.go b/internal/pool/trace.go new file mode 100644 index 000000000..40adef256 --- /dev/null +++ b/internal/pool/trace.go @@ -0,0 +1,89 @@ +package pool + +import ( + "context" + + "github.com/ydb-platform/ydb-go-sdk/v3/internal/pool/stats" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/stack" +) + +type ( + Trace struct { + OnNew func(*NewStartInfo) func(*NewDoneInfo) + OnClose func(*CloseStartInfo) func(*CloseDoneInfo) + OnTry func(*TryStartInfo) func(*TryDoneInfo) + OnWith func(*WithStartInfo) func(*WithDoneInfo) + OnPut func(*PutStartInfo) func(*PutDoneInfo) + OnGet func(*GetStartInfo) func(*GetDoneInfo) + OnChange func(ChangeInfo) + } + NewStartInfo struct { + // Context make available context in trace stack.Callerback function. + // Pointer to context provide replacement of context in trace stack.Callerback function. + // Warning: concurrent access to pointer on client side must be excluded. + // Safe replacement of context are provided only inside stack.Callerback function + Context *context.Context + Call stack.Caller + } + NewDoneInfo struct { + Limit int + } + CloseStartInfo struct { + // Context make available context in trace stack.Callerback function. + // Pointer to context provide replacement of context in trace stack.Callerback function. + // Warning: concurrent access to pointer on client side must be excluded. + // Safe replacement of context are provided only inside stack.Callerback function + Context *context.Context + Call stack.Caller + } + CloseDoneInfo struct { + Error error + } + TryStartInfo struct { + // Context make available context in trace stack.Callerback function. + // Pointer to context provide replacement of context in trace stack.Callerback function. + // Warning: concurrent access to pointer on client side must be excluded. + // Safe replacement of context are provided only inside stack.Callerback function + Context *context.Context + Call stack.Caller + } + TryDoneInfo struct { + Error error + } + WithStartInfo struct { + // Context make available context in trace stack.Callerback function. + // Pointer to context provide replacement of context in trace stack.Callerback function. + // Warning: concurrent access to pointer on client side must be excluded. + // Safe replacement of context are provided only inside stack.Callerback function + Context *context.Context + Call stack.Caller + } + WithDoneInfo struct { + Error error + + Attempts int + } + PutStartInfo struct { + // Context make available context in trace stack.Callerback function. + // Pointer to context provide replacement of context in trace stack.Callerback function. + // Warning: concurrent access to pointer on client side must be excluded. + // Safe replacement of context are provided only inside stack.Callerback function + Context *context.Context + Call stack.Caller + } + PutDoneInfo struct { + Error error + } + GetStartInfo struct { + // Context make available context in trace stack.Callerback function. + // Pointer to context provide replacement of context in trace stack.Callerback function. + // Warning: concurrent access to pointer on client side must be excluded. + // Safe replacement of context are provided only inside stack.Callerback function + Context *context.Context + Call stack.Caller + } + GetDoneInfo struct { + Error error + } + ChangeInfo = stats.Stats +) diff --git a/internal/query/client.go b/internal/query/client.go new file mode 100644 index 000000000..3019e83cd --- /dev/null +++ b/internal/query/client.go @@ -0,0 +1,262 @@ +package query + +import ( + "context" + + "github.com/ydb-platform/ydb-go-genproto/Ydb_Query_V1" + "google.golang.org/grpc" + + "github.com/ydb-platform/ydb-go-sdk/v3/internal/pool" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/pool/stats" + "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" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors" + "github.com/ydb-platform/ydb-go-sdk/v3/query" + "github.com/ydb-platform/ydb-go-sdk/v3/retry" + "github.com/ydb-platform/ydb-go-sdk/v3/trace" +) + +//go:generate mockgen -destination grpc_client_mock_test.go -package query -write_package_comment=false github.com/ydb-platform/ydb-go-genproto/Ydb_Query_V1 QueryServiceClient,QueryService_AttachSessionClient,QueryService_ExecuteQueryClient + +type nodeChecker interface { + HasNode(id uint32) bool +} + +type balancer interface { + grpc.ClientConnInterface + nodeChecker +} + +var _ query.Client = (*Client)(nil) + +type Client struct { + config *config.Config + grpcClient Ydb_Query_V1.QueryServiceClient + pool *pool.Pool[*Session, Session] + + done chan struct{} +} + +func (c *Client) Stats() *stats.Stats { + s := c.pool.Stats() + + return &s +} + +func (c *Client) Close(ctx context.Context) error { + close(c.done) + + err := c.pool.Close(ctx) + if err != nil { + return xerrors.WithStackTrace(err) + } + + return nil +} + +func do( + ctx context.Context, + pool *pool.Pool[*Session, Session], + op query.Operation, + t *trace.Query, + opts ...options.DoOption, +) (attempts int, finalErr error) { + doOpts := options.ParseDoOpts(t, opts...) + + err := pool.With(ctx, func(ctx context.Context, s *Session) error { + s.setStatus(statusInUse) + + err := op(ctx, s) + if err != nil { + if !xerrors.IsRetryObjectValid(err) { + s.setStatus(statusError) + } + + return xerrors.WithStackTrace(err) + } + + s.setStatus(statusIdle) + + return nil + }, append(doOpts.RetryOpts(), retry.WithTrace(&trace.Retry{ + OnRetry: func(info trace.RetryLoopStartInfo) func(trace.RetryLoopDoneInfo) { + return func(info trace.RetryLoopDoneInfo) { + attempts = info.Attempts + } + }, + }))...) + if err != nil { + return attempts, xerrors.WithStackTrace(err) + } + + return attempts, nil +} + +func (c *Client) Do(ctx context.Context, op query.Operation, opts ...options.DoOption) error { + select { + case <-c.done: + return xerrors.WithStackTrace(errClosedClient) + default: + 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) + + return err + } +} + +func doTx( + ctx context.Context, + pool *pool.Pool[*Session, Session], + op query.TxOperation, + t *trace.Query, + opts ...options.DoTxOption, +) (attempts int, err error) { + doTxOpts := options.ParseDoTxOpts(t, opts...) + + attempts, err = do(ctx, pool, func(ctx context.Context, s query.Session) (err error) { + tx, err := s.Begin(ctx, doTxOpts.TxSettings()) + if err != nil { + return xerrors.WithStackTrace(err) + } + err = op(ctx, tx) + if err != nil { + errRollback := tx.Rollback(ctx) + if errRollback != nil { + return xerrors.WithStackTrace(xerrors.Join(err, errRollback)) + } + + return xerrors.WithStackTrace(err) + } + err = tx.CommitTx(ctx) + if err != nil { + errRollback := tx.Rollback(ctx) + if errRollback != nil { + return xerrors.WithStackTrace(xerrors.Join(err, errRollback)) + } + + return xerrors.WithStackTrace(err) + } + + return nil + }, t, doTxOpts.DoOpts()...) + if err != nil { + return attempts, xerrors.WithStackTrace(err) + } + + return attempts, nil +} + +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("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) + + return err + } +} + +func New(ctx context.Context, balancer balancer, cfg *config.Config) *Client { + onDone := trace.QueryOnNew(cfg.Trace(), &ctx, + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/query.New"), + ) + defer onDone() + + client := &Client{ + config: cfg, + grpcClient: Ydb_Query_V1.NewQueryServiceClient(balancer), + done: make(chan struct{}), + } + + 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 ( + createCtx context.Context + cancelCreate context.CancelFunc + ) + if d := cfg.SessionCreateTimeout(); d > 0 { + createCtx, cancelCreate = xcontext.WithTimeout(ctx, d) + } else { + createCtx, cancelCreate = xcontext.WithCancel(ctx) + } + defer cancelCreate() + + s, err := createSession(createCtx, client.grpcClient, cfg, + withSessionCheck(func(s *Session) bool { + return balancer.HasNode(uint32(s.nodeID)) + }), + ) + if err != nil { + return nil, xerrors.WithStackTrace(err) + } + + return s, nil + }), + ) + + return client +} + +func poolTrace(t *trace.Query) *pool.Trace { + return &pool.Trace{ + OnNew: func(info *pool.NewStartInfo) func(*pool.NewDoneInfo) { + onDone := trace.QueryOnPoolNew(t, info.Context, info.Call) + + return func(info *pool.NewDoneInfo) { + onDone(info.Limit) + } + }, + OnClose: func(info *pool.CloseStartInfo) func(*pool.CloseDoneInfo) { + onDone := trace.QueryOnClose(t, info.Context, info.Call) + + return func(info *pool.CloseDoneInfo) { + onDone(info.Error) + } + }, + OnTry: func(info *pool.TryStartInfo) func(*pool.TryDoneInfo) { + onDone := trace.QueryOnPoolTry(t, info.Context, info.Call) + + return func(info *pool.TryDoneInfo) { + onDone(info.Error) + } + }, + OnWith: func(info *pool.WithStartInfo) func(*pool.WithDoneInfo) { + onDone := trace.QueryOnPoolWith(t, info.Context, info.Call) + + return func(info *pool.WithDoneInfo) { + onDone(info.Error, info.Attempts) + } + }, + OnPut: func(info *pool.PutStartInfo) func(*pool.PutDoneInfo) { + onDone := trace.QueryOnPoolPut(t, info.Context, info.Call) + + return func(info *pool.PutDoneInfo) { + onDone(info.Error) + } + }, + OnGet: func(info *pool.GetStartInfo) func(*pool.GetDoneInfo) { + onDone := trace.QueryOnPoolGet(t, info.Context, info.Call) + + return func(info *pool.GetDoneInfo) { + onDone(info.Error) + } + }, + OnChange: func(info pool.ChangeInfo) { + trace.QueryOnPoolChange(t, info.Limit, info.Index, info.Idle, info.InUse) + }, + } +} diff --git a/internal/query/client_test.go b/internal/query/client_test.go new file mode 100644 index 000000000..1b7260750 --- /dev/null +++ b/internal/query/client_test.go @@ -0,0 +1,258 @@ +package query + +import ( + "context" + "errors" + "testing" + "time" + + "github.com/stretchr/testify/require" + "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" + "go.uber.org/mock/gomock" + grpcCodes "google.golang.org/grpc/codes" + 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" + "github.com/ydb-platform/ydb-go-sdk/v3/trace" +) + +func TestCreateSession(t *testing.T) { + t.Run("HappyWay", func(t *testing.T) { + xtest.TestManyTimes(t, func(t testing.TB) { + ctx := xtest.Context(t) + ctrl := gomock.NewController(t) + attachStream := NewMockQueryService_AttachSessionClient(ctrl) + attachStream.EXPECT().Recv().Return(&Ydb_Query.SessionState{ + Status: Ydb.StatusIds_SUCCESS, + }, nil).AnyTimes() + service := NewMockQueryServiceClient(ctrl) + service.EXPECT().CreateSession(gomock.Any(), gomock.Any()).Return(&Ydb_Query.CreateSessionResponse{ + Status: Ydb.StatusIds_SUCCESS, + SessionId: "test", + }, nil) + service.EXPECT().AttachSession(gomock.Any(), gomock.Any()).Return(attachStream, nil) + service.EXPECT().DeleteSession(gomock.Any(), gomock.Any()).Return(&Ydb_Query.DeleteSessionResponse{ + Status: Ydb.StatusIds_SUCCESS, + }, nil) + attached := 0 + 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) { + if info.Error == nil { + attached++ + } + } + }, + OnSessionDelete: func(info trace.QuerySessionDeleteStartInfo) func(info trace.QuerySessionDeleteDoneInfo) { + attached-- + + return nil + }, + }, + ))) + require.NoError(t, err) + require.EqualValues(t, "test", s.id) + require.EqualValues(t, 1, attached) + err = s.Close(ctx) + require.NoError(t, err) + require.EqualValues(t, 0, attached) + }, xtest.StopAfter(time.Second)) + }) + t.Run("TransportError", func(t *testing.T) { + t.Run("OnCall", func(t *testing.T) { + xtest.TestManyTimes(t, func(t testing.TB) { + ctx := xtest.Context(t) + ctrl := gomock.NewController(t) + service := NewMockQueryServiceClient(ctrl) + service.EXPECT().CreateSession(gomock.Any(), gomock.Any()).Return(nil, grpcStatus.Error(grpcCodes.Unavailable, "")) + _, err := createSession(ctx, service, config.New()) + require.Error(t, err) + require.True(t, xerrors.IsTransportError(err, grpcCodes.Unavailable)) + }, xtest.StopAfter(time.Second)) + }) + t.Run("OnAttach", func(t *testing.T) { + xtest.TestManyTimes(t, func(t testing.TB) { + 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_SUCCESS, + SessionId: "test", + }, 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, "")) + _, err := createSession(ctx, service, config.New()) + require.Error(t, err) + require.True(t, xerrors.IsTransportError(err, grpcCodes.Unavailable)) + }, xtest.StopAfter(time.Second)) + }) + t.Run("OnRecv", func(t *testing.T) { + xtest.TestManyTimes(t, func(t testing.TB) { + ctx := xtest.Context(t) + ctrl := gomock.NewController(t) + attachStream := NewMockQueryService_AttachSessionClient(ctrl) + attachStream.EXPECT().Recv().Return(nil, grpcStatus.Error(grpcCodes.Unavailable, "")).AnyTimes() + service := NewMockQueryServiceClient(ctrl) + service.EXPECT().CreateSession(gomock.Any(), gomock.Any()).Return(&Ydb_Query.CreateSessionResponse{ + Status: Ydb.StatusIds_SUCCESS, + SessionId: "test", + }, nil) + service.EXPECT().AttachSession(gomock.Any(), gomock.Any()).Return(attachStream, nil) + service.EXPECT().DeleteSession(gomock.Any(), gomock.Any()).Return(&Ydb_Query.DeleteSessionResponse{ + Status: Ydb.StatusIds_SUCCESS, + }, nil) + _, err := createSession(ctx, service, config.New()) + require.Error(t, err) + require.True(t, xerrors.IsTransportError(err, grpcCodes.Unavailable)) + }, xtest.StopAfter(time.Second)) + }) + }) + t.Run("OperationError", func(t *testing.T) { + t.Run("OnCall", func(t *testing.T) { + xtest.TestManyTimes(t, func(t testing.TB) { + ctx := xtest.Context(t) + ctrl := gomock.NewController(t) + service := NewMockQueryServiceClient(ctrl) + 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)) + }) + t.Run("OnRecv", func(t *testing.T) { + xtest.TestManyTimes(t, func(t testing.TB) { + ctx := xtest.Context(t) + ctrl := gomock.NewController(t) + attachStream := NewMockQueryService_AttachSessionClient(ctrl) + 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, + SessionId: "test", + }, nil) + service.EXPECT().AttachSession(gomock.Any(), gomock.Any()).Return(attachStream, nil) + service.EXPECT().DeleteSession(gomock.Any(), gomock.Any()).Return(&Ydb_Query.DeleteSessionResponse{ + Status: Ydb.StatusIds_SUCCESS, + }, nil) + _, err := createSession(ctx, service, config.New()) + require.Error(t, err) + require.True(t, xerrors.IsOperationError(err, Ydb.StatusIds_UNAVAILABLE)) + }, xtest.StopAfter(time.Second)) + }) + }) +} + +func newTestSession(id string) *Session { + return &Session{ + id: id, + statusCode: statusIdle, + cfg: config.New(), + } +} + +func newTestSessionWithClient(id string, client Ydb_Query_V1.QueryServiceClient) *Session { + return &Session{ + id: id, + grpcClient: client, + statusCode: statusIdle, + cfg: config.New(), + } +} + +func testPool( + ctx context.Context, + createSession func(ctx context.Context) (*Session, error), +) *pool.Pool[*Session, Session] { + return pool.New[*Session, Session](ctx, + pool.WithLimit[*Session, Session](1), + pool.WithCreateFunc(createSession), + ) +} + +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("123"), nil + }), func(ctx context.Context, s query.Session) error { + return nil + }, &trace.Query{}) + require.NoError(t, err) + require.EqualValues(t, 1, attempts) + }) + t.Run("RetryableError", func(t *testing.T) { + counter := 0 + attempts, err := do(ctx, testPool(ctx, func(ctx context.Context) (*Session, error) { + return newTestSession("123"), nil + }), func(ctx context.Context, s query.Session) error { + counter++ + if counter < 10 { + return xerrors.Retryable(errors.New("")) + } + + return nil + }, &trace.Query{}) + require.NoError(t, err) + require.EqualValues(t, 10, attempts) + require.Equal(t, 10, counter) + }) +} + +func TestDoTx(t *testing.T) { + ctx := xtest.Context(t) + t.Run("HappyWay", func(t *testing.T) { + ctrl := gomock.NewController(t) + client := NewMockQueryServiceClient(ctrl) + client.EXPECT().BeginTransaction(gomock.Any(), gomock.Any()).Return(&Ydb_Query.BeginTransactionResponse{ + Status: Ydb.StatusIds_SUCCESS, + }, nil) + client.EXPECT().CommitTransaction(gomock.Any(), gomock.Any()).Return(&Ydb_Query.CommitTransactionResponse{ + Status: Ydb.StatusIds_SUCCESS, + }, nil) + attempts, err := doTx(ctx, testPool(ctx, func(ctx context.Context) (*Session, error) { + return newTestSessionWithClient("123", client), nil + }), func(ctx context.Context, tx query.TxActor) error { + return nil + }, &trace.Query{}) + require.NoError(t, err) + require.EqualValues(t, 1, attempts) + }) + t.Run("RetryableError", func(t *testing.T) { + counter := 0 + ctrl := gomock.NewController(t) + client := NewMockQueryServiceClient(ctrl) + client.EXPECT().BeginTransaction(gomock.Any(), gomock.Any()).Return(&Ydb_Query.BeginTransactionResponse{ + Status: Ydb.StatusIds_SUCCESS, + }, nil).AnyTimes() + client.EXPECT().RollbackTransaction(gomock.Any(), gomock.Any()).Return(&Ydb_Query.RollbackTransactionResponse{ + Status: Ydb.StatusIds_SUCCESS, + }, nil).AnyTimes() + client.EXPECT().CommitTransaction(gomock.Any(), gomock.Any()).Return(&Ydb_Query.CommitTransactionResponse{ + Status: Ydb.StatusIds_SUCCESS, + }, nil).AnyTimes() + attempts, err := doTx(ctx, testPool(ctx, func(ctx context.Context) (*Session, error) { + return newTestSessionWithClient("123", client), nil + }), func(ctx context.Context, tx query.TxActor) error { + counter++ + if counter < 10 { + return xerrors.Retryable(errors.New("")) + } + + return nil + }, &trace.Query{}) + require.NoError(t, err) + require.EqualValues(t, 10, attempts) + require.Equal(t, 10, counter) + }) +} diff --git a/internal/query/config/config.go b/internal/query/config/config.go new file mode 100644 index 000000000..7adb08242 --- /dev/null +++ b/internal/query/config/config.go @@ -0,0 +1,70 @@ +package config + +import ( + "time" + + "github.com/ydb-platform/ydb-go-sdk/v3/internal/config" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/pool" + "github.com/ydb-platform/ydb-go-sdk/v3/trace" +) + +const ( + DefaultSessionDeleteTimeout = 500 * time.Millisecond + DefaultSessionCreateTimeout = 500 * time.Millisecond + DefaultPoolMaxSize = pool.DefaultLimit +) + +type Config struct { + config.Common + + poolLimit int + + sessionCreateTimeout time.Duration + sessionDeleteTimeout time.Duration + + trace *trace.Query +} + +func New(opts ...Option) *Config { + c := defaults() + for _, opt := range opts { + if opt != nil { + opt(c) + } + } + + return c +} + +func defaults() *Config { + return &Config{ + poolLimit: DefaultPoolMaxSize, + sessionCreateTimeout: DefaultSessionCreateTimeout, + sessionDeleteTimeout: DefaultSessionDeleteTimeout, + trace: &trace.Query{}, + } +} + +// Trace defines trace over table client calls +func (c *Config) Trace() *trace.Query { + return c.trace +} + +// PoolLimit is an upper bound of pooled sessions. +// If PoolLimit is less than or equal to zero then the +// DefaultPoolMaxSize variable is used as a pool limit. +func (c *Config) PoolLimit() int { + return c.poolLimit +} + +// SessionCreateTimeout limits maximum time spent on Create session request +func (c *Config) SessionCreateTimeout() time.Duration { + return c.sessionCreateTimeout +} + +// SessionDeleteTimeout limits maximum time spent on Delete request +// +// 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/config/options.go b/internal/query/config/options.go new file mode 100644 index 000000000..2b30c5be2 --- /dev/null +++ b/internal/query/config/options.go @@ -0,0 +1,57 @@ +package config + +import ( + "time" + + "github.com/ydb-platform/ydb-go-sdk/v3/internal/config" + "github.com/ydb-platform/ydb-go-sdk/v3/trace" +) + +type Option func(*Config) + +// With applies common configuration params +func With(config config.Common) Option { + return func(c *Config) { + c.Common = config + } +} + +// WithTrace appends table trace to early defined traces +func WithTrace(trace *trace.Query, opts ...trace.QueryComposeOption) Option { + return func(c *Config) { + c.trace = c.trace.Compose(trace, opts...) + } +} + +// WithPoolLimit defines upper bound of pooled sessions. +// If poolLimit is less than or equal to zero then the +// DefaultPoolMaxSize variable is used as a poolLimit. +func WithPoolLimit(size int) Option { + return func(c *Config) { + if size > 0 { + c.poolLimit = size + } + } +} + +// WithSessionCreateTimeout limits maximum time spent on Create session request +// If sessionCreateTimeout is less than or equal to zero then no used timeout on create session request +func WithSessionCreateTimeout(createSessionTimeout time.Duration) Option { + return func(c *Config) { + if createSessionTimeout > 0 { + c.sessionCreateTimeout = createSessionTimeout + } else { + c.sessionCreateTimeout = 0 + } + } +} + +// WithSessionDeleteTimeout limits maximum time spent on Delete request +// If sessionDeleteTimeout is less than or equal to zero then the DefaultSessionDeleteTimeout is used. +func WithSessionDeleteTimeout(deleteTimeout time.Duration) Option { + return func(c *Config) { + if deleteTimeout > 0 { + c.sessionDeleteTimeout = deleteTimeout + } + } +} diff --git a/internal/query/errors.go b/internal/query/errors.go new file mode 100644 index 000000000..923ef8ed8 --- /dev/null +++ b/internal/query/errors.go @@ -0,0 +1,13 @@ +package query + +import ( + "errors" +) + +var ( + ErrNotImplemented = errors.New("not implemented yet") + errWrongNextResultSetIndex = errors.New("wrong result set index") + errClosedResult = errors.New("result closed early") + errClosedClient = errors.New("query client closed early") + errWrongResultSetIndex = errors.New("critical violation of the logic - wrong result set index") +) diff --git a/internal/query/execute_query.go b/internal/query/execute_query.go new file mode 100644 index 000000000..bf2fef314 --- /dev/null +++ b/internal/query/execute_query.go @@ -0,0 +1,82 @@ +package query + +import ( + "context" + + "github.com/ydb-platform/ydb-go-genproto/Ydb_Query_V1" + "github.com/ydb-platform/ydb-go-genproto/protos/Ydb_Query" + "google.golang.org/grpc" + + "github.com/ydb-platform/ydb-go-sdk/v3/internal/allocator" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/params" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/query/options" + "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/query" +) + +type executeConfig interface { + ExecMode() options.ExecMode + StatsMode() options.StatsMode + TxControl() *query.TransactionControl + Syntax() options.Syntax + Params() *params.Parameters + CallOptions() []grpc.CallOption +} + +func executeQueryRequest(a *allocator.Allocator, sessionID, q string, cfg executeConfig) ( + *Ydb_Query.ExecuteQueryRequest, + []grpc.CallOption, +) { + request := a.QueryExecuteQueryRequest() + + request.SessionId = sessionID + request.ExecMode = Ydb_Query.ExecMode(cfg.ExecMode()) + request.TxControl = cfg.TxControl().ToYDB(a) + request.Query = queryFromText(a, q, Ydb_Query.Syntax(cfg.Syntax())) + request.Parameters = cfg.Params().ToYDB(a) + request.StatsMode = Ydb_Query.StatsMode(cfg.StatsMode()) + request.ConcurrentResultSets = false + + return request, cfg.CallOptions() +} + +func queryFromText( + a *allocator.Allocator, q string, syntax Ydb_Query.Syntax, +) *Ydb_Query.ExecuteQueryRequest_QueryContent { + content := a.QueryExecuteQueryRequestQueryContent() + content.QueryContent = a.QueryQueryContent() + content.QueryContent.Syntax = syntax + content.QueryContent.Text = q + + return content +} + +func execute(ctx context.Context, s *Session, c Ydb_Query_V1.QueryServiceClient, q string, cfg executeConfig) ( + _ *transaction, _ *result, finalErr error, +) { + a := allocator.New() + defer a.Free() + + request, callOptions := executeQueryRequest(a, s.id, q, cfg) + + 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.cfg.Trace(), cancelExecute) + if err != nil { + cancelExecute() + + return nil, nil, xerrors.WithStackTrace(err) + } + + if txID == "" { + return nil, r, nil + } + + return newTransaction(txID, s), r, nil +} diff --git a/internal/query/execute_query_test.go b/internal/query/execute_query_test.go new file mode 100644 index 000000000..c3c14d754 --- /dev/null +++ b/internal/query/execute_query_test.go @@ -0,0 +1,1012 @@ +package query + +import ( + "context" + "io" + "testing" + + "github.com/stretchr/testify/require" + "github.com/ydb-platform/ydb-go-genproto/protos/Ydb" + "github.com/ydb-platform/ydb-go-genproto/protos/Ydb_Query" + "go.uber.org/mock/gomock" + "google.golang.org/grpc" + grpcCodes "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" + grpcStatus "google.golang.org/grpc/status" + + "github.com/ydb-platform/ydb-go-sdk/v3/internal/allocator" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/params" + "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) { + t.Run("HappyWay", func(t *testing.T) { + ctx := xtest.Context(t) + ctrl := gomock.NewController(t) + stream := NewMockQueryService_ExecuteQueryClient(ctrl) + stream.EXPECT().Recv().Return(&Ydb_Query.ExecuteQueryResponsePart{ + Status: Ydb.StatusIds_SUCCESS, + TxMeta: &Ydb_Query.TransactionMeta{ + Id: "456", + }, + 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{ + { + Items: []*Ydb.Value{{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 4, + }, + }, { + Value: &Ydb.Value_TextValue{ + TextValue: "4", + }, + }}, + }, + { + Items: []*Ydb.Value{{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 5, + }, + }, { + Value: &Ydb.Value_TextValue{ + TextValue: "5", + }, + }}, + }, + }, + }, + }, nil) + stream.EXPECT().Recv().Return(&Ydb_Query.ExecuteQueryResponsePart{ + Status: Ydb.StatusIds_SUCCESS, + ResultSetIndex: 1, + ResultSet: &Ydb.ResultSet{ + Columns: []*Ydb.Column{ + { + Name: "c", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UINT64, + }, + }, + }, + { + Name: "d", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UTF8, + }, + }, + }, + { + Name: "e", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_BOOL, + }, + }, + }, + }, + Rows: []*Ydb.Value{ + { + Items: []*Ydb.Value{{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 1, + }, + }, { + Value: &Ydb.Value_TextValue{ + TextValue: "1", + }, + }, { + Value: &Ydb.Value_BoolValue{ + BoolValue: true, + }, + }}, + }, + { + Items: []*Ydb.Value{{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 2, + }, + }, { + Value: &Ydb.Value_TextValue{ + TextValue: "2", + }, + }, { + Value: &Ydb.Value_BoolValue{ + BoolValue: false, + }, + }}, + }, + }, + }, + }, nil) + stream.EXPECT().Recv().Return(&Ydb_Query.ExecuteQueryResponsePart{ + Status: Ydb.StatusIds_SUCCESS, + ResultSetIndex: 1, + ResultSet: &Ydb.ResultSet{ + Rows: []*Ydb.Value{ + { + Items: []*Ydb.Value{{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 3, + }, + }, { + Value: &Ydb.Value_TextValue{ + TextValue: "3", + }, + }, { + Value: &Ydb.Value_BoolValue{ + BoolValue: true, + }, + }}, + }, + { + Items: []*Ydb.Value{{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 4, + }, + }, { + Value: &Ydb.Value_TextValue{ + TextValue: "4", + }, + }, { + Value: &Ydb.Value_BoolValue{ + BoolValue: false, + }, + }}, + }, + { + Items: []*Ydb.Value{{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 5, + }, + }, { + Value: &Ydb.Value_TextValue{ + TextValue: "5", + }, + }, { + Value: &Ydb.Value_BoolValue{ + BoolValue: false, + }, + }}, + }, + }, + }, + }, nil) + stream.EXPECT().Recv().Return(&Ydb_Query.ExecuteQueryResponsePart{ + Status: Ydb.StatusIds_SUCCESS, + ResultSetIndex: 2, + ResultSet: &Ydb.ResultSet{ + Columns: []*Ydb.Column{ + { + Name: "c", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UINT64, + }, + }, + }, + { + Name: "d", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UTF8, + }, + }, + }, + { + Name: "e", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_BOOL, + }, + }, + }, + }, + Rows: []*Ydb.Value{ + { + Items: []*Ydb.Value{{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 1, + }, + }, { + Value: &Ydb.Value_TextValue{ + TextValue: "1", + }, + }, { + Value: &Ydb.Value_BoolValue{ + BoolValue: true, + }, + }}, + }, + { + Items: []*Ydb.Value{{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 2, + }, + }, { + Value: &Ydb.Value_TextValue{ + TextValue: "2", + }, + }, { + Value: &Ydb.Value_BoolValue{ + BoolValue: false, + }, + }}, + }, + }, + }, + }, nil) + stream.EXPECT().Recv().Return(&Ydb_Query.ExecuteQueryResponsePart{ + Status: Ydb.StatusIds_SUCCESS, + ResultSetIndex: 2, + ResultSet: &Ydb.ResultSet{ + Rows: []*Ydb.Value{ + { + Items: []*Ydb.Value{{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 3, + }, + }, { + Value: &Ydb.Value_TextValue{ + TextValue: "3", + }, + }, { + Value: &Ydb.Value_BoolValue{ + BoolValue: true, + }, + }}, + }, + { + Items: []*Ydb.Value{{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 4, + }, + }, { + Value: &Ydb.Value_TextValue{ + TextValue: "4", + }, + }, { + Value: &Ydb.Value_BoolValue{ + BoolValue: false, + }, + }}, + }, + { + Items: []*Ydb.Value{{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 5, + }, + }, { + Value: &Ydb.Value_TextValue{ + TextValue: "5", + }, + }, { + Value: &Ydb.Value_BoolValue{ + BoolValue: false, + }, + }}, + }, + }, + }, + }, nil) + stream.EXPECT().Recv().Return(nil, io.EOF) + service := NewMockQueryServiceClient(ctrl) + service.EXPECT().ExecuteQuery(gomock.Any(), gomock.Any()).Return(stream, nil) + tx, r, err := execute(ctx, newTestSession("123"), service, "", options.ExecuteSettings()) + require.NoError(t, err) + defer r.Close(ctx) + require.EqualValues(t, "456", tx.id) + require.EqualValues(t, "123", tx.s.id) + require.EqualValues(t, -1, r.resultSetIndex) + { + t.Log("nextResultSet") + rs, err := r.nextResultSet(ctx) + require.NoError(t, err) + require.EqualValues(t, 0, rs.index) + { + t.Log("next (row=1)") + _, err := rs.nextRow(ctx) + require.NoError(t, err) + require.EqualValues(t, 0, rs.rowIndex) + } + { + t.Log("next (row=2)") + _, err := rs.nextRow(ctx) + require.NoError(t, err) + require.EqualValues(t, 1, rs.rowIndex) + } + { + t.Log("next (row=3)") + _, err := rs.nextRow(ctx) + require.NoError(t, err) + require.EqualValues(t, 2, rs.rowIndex) + } + { + t.Log("next (row=4)") + _, err := rs.nextRow(ctx) + require.NoError(t, err) + require.EqualValues(t, 0, rs.rowIndex) + } + { + t.Log("next (row=5)") + _, err := rs.nextRow(ctx) + require.NoError(t, err) + require.EqualValues(t, 1, rs.rowIndex) + } + { + t.Log("next (row=6)") + _, err := rs.nextRow(ctx) + require.ErrorIs(t, err, io.EOF) + } + } + { + t.Log("nextResultSet") + rs, err := r.nextResultSet(ctx) + require.NoError(t, err) + require.EqualValues(t, 1, rs.index) + } + { + t.Log("nextResultSet") + rs, err := r.nextResultSet(ctx) + require.NoError(t, err) + require.EqualValues(t, 2, rs.index) + { + t.Log("next (row=1)") + _, err := rs.nextRow(ctx) + require.NoError(t, err) + require.EqualValues(t, 0, rs.rowIndex) + } + { + t.Log("next (row=2)") + _, err := rs.nextRow(ctx) + require.NoError(t, err) + require.EqualValues(t, 1, rs.rowIndex) + } + { + t.Log("next (row=3)") + _, err := rs.nextRow(ctx) + require.NoError(t, err) + require.EqualValues(t, 0, rs.rowIndex) + } + { + t.Log("next (row=4)") + _, err := rs.nextRow(ctx) + require.NoError(t, err) + require.EqualValues(t, 1, rs.rowIndex) + } + { + t.Log("next (row=5)") + _, err := rs.nextRow(ctx) + require.NoError(t, err) + require.EqualValues(t, 2, rs.rowIndex) + } + { + t.Log("next (row=6)") + _, err := rs.nextRow(ctx) + require.ErrorIs(t, err, io.EOF) + } + } + { + t.Log("close result") + r.Close(context.Background()) + } + { + t.Log("nextResultSet") + _, err := r.nextResultSet(context.Background()) + require.ErrorIs(t, err, errClosedResult) + } + t.Log("check final error") + require.NoError(t, r.Err()) + }) + t.Run("TransportError", func(t *testing.T) { + t.Run("OnCall", func(t *testing.T) { + ctx := xtest.Context(t) + ctrl := gomock.NewController(t) + service := NewMockQueryServiceClient(ctrl) + service.EXPECT().ExecuteQuery(gomock.Any(), gomock.Any()).Return(nil, grpcStatus.Error(grpcCodes.Unavailable, "")) + t.Log("execute") + _, _, err := execute(ctx, newTestSession("123"), service, "", options.ExecuteSettings()) + require.Error(t, err) + require.True(t, xerrors.IsTransportError(err, grpcCodes.Unavailable)) + }) + t.Run("OnStream", func(t *testing.T) { + ctx := xtest.Context(t) + ctrl := gomock.NewController(t) + stream := NewMockQueryService_ExecuteQueryClient(ctrl) + stream.EXPECT().Recv().Return(&Ydb_Query.ExecuteQueryResponsePart{ + Status: Ydb.StatusIds_SUCCESS, + TxMeta: &Ydb_Query.TransactionMeta{ + Id: "456", + }, + 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{ + { + Items: []*Ydb.Value{{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 4, + }, + }, { + Value: &Ydb.Value_TextValue{ + TextValue: "4", + }, + }}, + }, + { + Items: []*Ydb.Value{{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 5, + }, + }, { + Value: &Ydb.Value_TextValue{ + TextValue: "5", + }, + }}, + }, + }, + }, + }, nil) + stream.EXPECT().Recv().Return(nil, grpcStatus.Error(grpcCodes.Unavailable, "")) + service := NewMockQueryServiceClient(ctrl) + service.EXPECT().ExecuteQuery(gomock.Any(), gomock.Any()).Return(stream, nil) + t.Log("execute") + tx, r, err := execute(ctx, newTestSession("123"), service, "", options.ExecuteSettings()) + require.NoError(t, err) + defer r.Close(ctx) + require.EqualValues(t, "456", tx.id) + require.EqualValues(t, "123", tx.s.id) + require.EqualValues(t, -1, r.resultSetIndex) + { + t.Log("nextResultSet") + rs, err := r.nextResultSet(ctx) + require.NoError(t, err) + require.EqualValues(t, 0, rs.index) + { + t.Log("next (row=1)") + _, err := rs.nextRow(ctx) + require.NoError(t, err) + require.EqualValues(t, 0, rs.rowIndex) + } + { + t.Log("next (row=2)") + _, err := rs.nextRow(ctx) + require.NoError(t, err) + require.EqualValues(t, 1, rs.rowIndex) + } + { + t.Log("next (row=3)") + _, err := rs.nextRow(ctx) + require.NoError(t, err) + require.EqualValues(t, 2, rs.rowIndex) + } + { + t.Log("next (row=4)") + _, err := rs.nextRow(ctx) + require.NoError(t, err) + require.EqualValues(t, 0, rs.rowIndex) + } + { + t.Log("next (row=5)") + _, err := rs.nextRow(ctx) + require.NoError(t, err) + require.EqualValues(t, 1, rs.rowIndex) + } + { + t.Log("next (row=6)") + _, err := rs.nextRow(ctx) + require.Error(t, err) + require.True(t, xerrors.IsTransportError(err, grpcCodes.Unavailable)) + } + } + t.Log("check final error") + require.Error(t, r.Err()) + require.True(t, xerrors.IsTransportError(r.Err(), grpcCodes.Unavailable)) + }) + }) + t.Run("OperationError", func(t *testing.T) { + t.Run("OnCall", func(t *testing.T) { + ctx := xtest.Context(t) + ctrl := gomock.NewController(t) + stream := NewMockQueryService_ExecuteQueryClient(ctrl) + stream.EXPECT().Recv().Return(nil, xerrors.Operation(xerrors.WithStatusCode( + Ydb.StatusIds_UNAVAILABLE, + ))) + service := NewMockQueryServiceClient(ctrl) + service.EXPECT().ExecuteQuery(gomock.Any(), gomock.Any()).Return(stream, nil) + t.Log("execute") + _, _, err := execute(ctx, newTestSession("123"), service, "", options.ExecuteSettings()) + require.Error(t, err) + require.True(t, xerrors.IsOperationError(err, Ydb.StatusIds_UNAVAILABLE)) + }) + t.Run("OnStream", func(t *testing.T) { + ctx := xtest.Context(t) + ctrl := gomock.NewController(t) + stream := NewMockQueryService_ExecuteQueryClient(ctrl) + stream.EXPECT().Recv().Return(&Ydb_Query.ExecuteQueryResponsePart{ + Status: Ydb.StatusIds_SUCCESS, + TxMeta: &Ydb_Query.TransactionMeta{ + Id: "456", + }, + 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, xerrors.Operation(xerrors.WithStatusCode( + Ydb.StatusIds_UNAVAILABLE, + ))) + service := NewMockQueryServiceClient(ctrl) + service.EXPECT().ExecuteQuery(gomock.Any(), gomock.Any()).Return(stream, nil) + t.Log("execute") + tx, r, err := execute(ctx, newTestSession("123"), service, "", options.ExecuteSettings()) + require.NoError(t, err) + defer r.Close(ctx) + require.EqualValues(t, "456", tx.id) + require.EqualValues(t, "123", tx.s.id) + require.EqualValues(t, -1, r.resultSetIndex) + { + t.Log("nextResultSet") + rs, err := r.nextResultSet(ctx) + require.NoError(t, err) + require.EqualValues(t, 0, rs.index) + { + t.Log("next (row=1)") + _, err := rs.nextRow(ctx) + require.NoError(t, err) + require.EqualValues(t, 0, rs.rowIndex) + } + { + t.Log("next (row=2)") + _, err := rs.nextRow(ctx) + require.NoError(t, err) + require.EqualValues(t, 1, rs.rowIndex) + } + { + t.Log("next (row=3)") + _, err := rs.nextRow(ctx) + require.NoError(t, err) + require.EqualValues(t, 2, rs.rowIndex) + } + { + t.Log("next (row=4)") + _, err := rs.nextRow(ctx) + require.Error(t, err) + require.True(t, xerrors.IsOperationError(err, Ydb.StatusIds_UNAVAILABLE)) + } + } + t.Log("check final error") + require.Error(t, r.Err()) + require.True(t, xerrors.IsOperationError(r.Err(), Ydb.StatusIds_UNAVAILABLE)) + }) + }) +} + +func TestExecuteQueryRequest(t *testing.T) { + a := allocator.New() + for _, tt := range []struct { + name string + opts []options.ExecuteOption + request *Ydb_Query.ExecuteQueryRequest + callOptions []grpc.CallOption + }{ + { + name: "WithoutOptions", + 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{ + 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, + Text: "WithTxControl", + }, + }, + StatsMode: Ydb_Query.StatsMode_STATS_MODE_NONE, + ConcurrentResultSets: false, + }, + }, + { + name: "WithParams", + opts: []options.ExecuteOption{ + options.WithParameters( + params.Builder{}. + Param("$a").Text("A"). + Param("$b").Text("B"). + Param("$c").Text("C"). + Build(), + ), + }, + request: &Ydb_Query.ExecuteQueryRequest{ + SessionId: "WithParams", + ExecMode: Ydb_Query.ExecMode_EXEC_MODE_EXECUTE, + Query: &Ydb_Query.ExecuteQueryRequest_QueryContent{ + QueryContent: &Ydb_Query.QueryContent{ + Syntax: Ydb_Query.Syntax_SYNTAX_YQL_V1, + Text: "WithParams", + }, + }, + Parameters: map[string]*Ydb.TypedValue{ + "$a": { + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UTF8, + }, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_TextValue{ + TextValue: "A", + }, + }, + }, + "$b": { + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UTF8, + }, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_TextValue{ + TextValue: "B", + }, + }, + }, + "$c": { + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UTF8, + }, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_TextValue{ + TextValue: "C", + }, + }, + }, + }, + StatsMode: Ydb_Query.StatsMode_STATS_MODE_NONE, + ConcurrentResultSets: false, + }, + }, + { + name: "WithExplain", + opts: []options.ExecuteOption{ + options.WithExecMode(options.ExecModeExplain), + }, + request: &Ydb_Query.ExecuteQueryRequest{ + SessionId: "WithExplain", + ExecMode: Ydb_Query.ExecMode_EXEC_MODE_EXPLAIN, + Query: &Ydb_Query.ExecuteQueryRequest_QueryContent{ + QueryContent: &Ydb_Query.QueryContent{ + Syntax: Ydb_Query.Syntax_SYNTAX_YQL_V1, + Text: "WithExplain", + }, + }, + StatsMode: Ydb_Query.StatsMode_STATS_MODE_NONE, + ConcurrentResultSets: false, + }, + }, + { + name: "WithValidate", + opts: []options.ExecuteOption{ + options.WithExecMode(options.ExecModeValidate), + }, + request: &Ydb_Query.ExecuteQueryRequest{ + SessionId: "WithValidate", + ExecMode: Ydb_Query.ExecMode_EXEC_MODE_VALIDATE, + Query: &Ydb_Query.ExecuteQueryRequest_QueryContent{ + QueryContent: &Ydb_Query.QueryContent{ + Syntax: Ydb_Query.Syntax_SYNTAX_YQL_V1, + Text: "WithValidate", + }, + }, + StatsMode: Ydb_Query.StatsMode_STATS_MODE_NONE, + ConcurrentResultSets: false, + }, + }, + { + name: "WithValidate", + opts: []options.ExecuteOption{ + options.WithExecMode(options.ExecModeParse), + }, + request: &Ydb_Query.ExecuteQueryRequest{ + SessionId: "WithValidate", + ExecMode: Ydb_Query.ExecMode_EXEC_MODE_PARSE, + Query: &Ydb_Query.ExecuteQueryRequest_QueryContent{ + QueryContent: &Ydb_Query.QueryContent{ + Syntax: Ydb_Query.Syntax_SYNTAX_YQL_V1, + Text: "WithValidate", + }, + }, + StatsMode: Ydb_Query.StatsMode_STATS_MODE_NONE, + ConcurrentResultSets: false, + }, + }, + { + name: "WithStatsFull", + opts: []options.ExecuteOption{ + options.WithStatsMode(options.StatsModeFull), + }, + request: &Ydb_Query.ExecuteQueryRequest{ + SessionId: "WithStatsFull", + ExecMode: Ydb_Query.ExecMode_EXEC_MODE_EXECUTE, + Query: &Ydb_Query.ExecuteQueryRequest_QueryContent{ + QueryContent: &Ydb_Query.QueryContent{ + Syntax: Ydb_Query.Syntax_SYNTAX_YQL_V1, + Text: "WithStatsFull", + }, + }, + StatsMode: Ydb_Query.StatsMode_STATS_MODE_FULL, + ConcurrentResultSets: false, + }, + }, + { + name: "WithStatsBasic", + opts: []options.ExecuteOption{ + options.WithStatsMode(options.StatsModeBasic), + }, + request: &Ydb_Query.ExecuteQueryRequest{ + SessionId: "WithStatsBasic", + ExecMode: Ydb_Query.ExecMode_EXEC_MODE_EXECUTE, + Query: &Ydb_Query.ExecuteQueryRequest_QueryContent{ + QueryContent: &Ydb_Query.QueryContent{ + Syntax: Ydb_Query.Syntax_SYNTAX_YQL_V1, + Text: "WithStatsBasic", + }, + }, + StatsMode: Ydb_Query.StatsMode_STATS_MODE_BASIC, + ConcurrentResultSets: false, + }, + }, + { + name: "WithStatsProfile", + opts: []options.ExecuteOption{ + options.WithStatsMode(options.StatsModeProfile), + }, + request: &Ydb_Query.ExecuteQueryRequest{ + SessionId: "WithStatsProfile", + ExecMode: Ydb_Query.ExecMode_EXEC_MODE_EXECUTE, + Query: &Ydb_Query.ExecuteQueryRequest_QueryContent{ + QueryContent: &Ydb_Query.QueryContent{ + Syntax: Ydb_Query.Syntax_SYNTAX_YQL_V1, + Text: "WithStatsProfile", + }, + }, + StatsMode: Ydb_Query.StatsMode_STATS_MODE_PROFILE, + ConcurrentResultSets: false, + }, + }, + { + name: "WithGrpcCallOptions", + opts: []options.ExecuteOption{ + options.WithCallOptions(grpc.Header(&metadata.MD{ + "ext-header": []string{"test"}, + })), + }, + request: &Ydb_Query.ExecuteQueryRequest{ + SessionId: "WithGrpcCallOptions", + ExecMode: Ydb_Query.ExecMode_EXEC_MODE_EXECUTE, + Query: &Ydb_Query.ExecuteQueryRequest_QueryContent{ + QueryContent: &Ydb_Query.QueryContent{ + Syntax: Ydb_Query.Syntax_SYNTAX_YQL_V1, + Text: "WithGrpcCallOptions", + }, + }, + StatsMode: Ydb_Query.StatsMode_STATS_MODE_NONE, + ConcurrentResultSets: false, + }, + callOptions: []grpc.CallOption{ + grpc.Header(&metadata.MD{ + "ext-header": []string{"test"}, + }), + }, + }, + } { + t.Run(tt.name, func(t *testing.T) { + request, callOptions := executeQueryRequest(a, tt.name, tt.name, options.ExecuteSettings(tt.opts...)) + require.Equal(t, request.String(), tt.request.String()) + require.Equal(t, tt.callOptions, callOptions) + }) + } +} diff --git a/internal/query/grpc_client_mock_test.go b/internal/query/grpc_client_mock_test.go new file mode 100644 index 000000000..d81f0cfab --- /dev/null +++ b/internal/query/grpc_client_mock_test.go @@ -0,0 +1,468 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/ydb-platform/ydb-go-genproto/Ydb_Query_V1 (interfaces: QueryServiceClient,QueryService_AttachSessionClient,QueryService_ExecuteQueryClient) +// +// Generated by this command: +// +// mockgen -destination grpc_client_mock_test.go -package query -write_package_comment=false github.com/ydb-platform/ydb-go-genproto/Ydb_Query_V1 QueryServiceClient,QueryService_AttachSessionClient,QueryService_ExecuteQueryClient +package query + +import ( + context "context" + reflect "reflect" + + Ydb_Query_V1 "github.com/ydb-platform/ydb-go-genproto/Ydb_Query_V1" + Ydb_Operations "github.com/ydb-platform/ydb-go-genproto/protos/Ydb_Operations" + Ydb_Query "github.com/ydb-platform/ydb-go-genproto/protos/Ydb_Query" + gomock "go.uber.org/mock/gomock" + grpc "google.golang.org/grpc" + metadata "google.golang.org/grpc/metadata" +) + +// MockQueryServiceClient is a mock of QueryServiceClient interface. +type MockQueryServiceClient struct { + ctrl *gomock.Controller + recorder *MockQueryServiceClientMockRecorder +} + +// MockQueryServiceClientMockRecorder is the mock recorder for MockQueryServiceClient. +type MockQueryServiceClientMockRecorder struct { + mock *MockQueryServiceClient +} + +// NewMockQueryServiceClient creates a new mock instance. +func NewMockQueryServiceClient(ctrl *gomock.Controller) *MockQueryServiceClient { + mock := &MockQueryServiceClient{ctrl: ctrl} + mock.recorder = &MockQueryServiceClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockQueryServiceClient) EXPECT() *MockQueryServiceClientMockRecorder { + return m.recorder +} + +// AttachSession mocks base method. +func (m *MockQueryServiceClient) AttachSession(arg0 context.Context, arg1 *Ydb_Query.AttachSessionRequest, arg2 ...grpc.CallOption) (Ydb_Query_V1.QueryService_AttachSessionClient, error) { + m.ctrl.T.Helper() + varargs := []any{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "AttachSession", varargs...) + ret0, _ := ret[0].(Ydb_Query_V1.QueryService_AttachSessionClient) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// AttachSession indicates an expected call of AttachSession. +func (mr *MockQueryServiceClientMockRecorder) AttachSession(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, "AttachSession", reflect.TypeOf((*MockQueryServiceClient)(nil).AttachSession), varargs...) +} + +// BeginTransaction mocks base method. +func (m *MockQueryServiceClient) BeginTransaction(arg0 context.Context, arg1 *Ydb_Query.BeginTransactionRequest, arg2 ...grpc.CallOption) (*Ydb_Query.BeginTransactionResponse, error) { + m.ctrl.T.Helper() + varargs := []any{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "BeginTransaction", varargs...) + ret0, _ := ret[0].(*Ydb_Query.BeginTransactionResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// BeginTransaction indicates an expected call of BeginTransaction. +func (mr *MockQueryServiceClientMockRecorder) BeginTransaction(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, "BeginTransaction", reflect.TypeOf((*MockQueryServiceClient)(nil).BeginTransaction), varargs...) +} + +// CommitTransaction mocks base method. +func (m *MockQueryServiceClient) CommitTransaction(arg0 context.Context, arg1 *Ydb_Query.CommitTransactionRequest, arg2 ...grpc.CallOption) (*Ydb_Query.CommitTransactionResponse, error) { + m.ctrl.T.Helper() + varargs := []any{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "CommitTransaction", varargs...) + ret0, _ := ret[0].(*Ydb_Query.CommitTransactionResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CommitTransaction indicates an expected call of CommitTransaction. +func (mr *MockQueryServiceClientMockRecorder) CommitTransaction(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, "CommitTransaction", reflect.TypeOf((*MockQueryServiceClient)(nil).CommitTransaction), varargs...) +} + +// CreateSession mocks base method. +func (m *MockQueryServiceClient) CreateSession(arg0 context.Context, arg1 *Ydb_Query.CreateSessionRequest, arg2 ...grpc.CallOption) (*Ydb_Query.CreateSessionResponse, error) { + m.ctrl.T.Helper() + varargs := []any{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "CreateSession", varargs...) + ret0, _ := ret[0].(*Ydb_Query.CreateSessionResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateSession indicates an expected call of CreateSession. +func (mr *MockQueryServiceClientMockRecorder) CreateSession(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, "CreateSession", reflect.TypeOf((*MockQueryServiceClient)(nil).CreateSession), varargs...) +} + +// DeleteSession mocks base method. +func (m *MockQueryServiceClient) DeleteSession(arg0 context.Context, arg1 *Ydb_Query.DeleteSessionRequest, arg2 ...grpc.CallOption) (*Ydb_Query.DeleteSessionResponse, error) { + m.ctrl.T.Helper() + varargs := []any{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "DeleteSession", varargs...) + ret0, _ := ret[0].(*Ydb_Query.DeleteSessionResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DeleteSession indicates an expected call of DeleteSession. +func (mr *MockQueryServiceClientMockRecorder) DeleteSession(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, "DeleteSession", reflect.TypeOf((*MockQueryServiceClient)(nil).DeleteSession), varargs...) +} + +// ExecuteQuery mocks base method. +func (m *MockQueryServiceClient) ExecuteQuery(arg0 context.Context, arg1 *Ydb_Query.ExecuteQueryRequest, arg2 ...grpc.CallOption) (Ydb_Query_V1.QueryService_ExecuteQueryClient, error) { + m.ctrl.T.Helper() + varargs := []any{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "ExecuteQuery", varargs...) + ret0, _ := ret[0].(Ydb_Query_V1.QueryService_ExecuteQueryClient) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ExecuteQuery indicates an expected call of ExecuteQuery. +func (mr *MockQueryServiceClientMockRecorder) ExecuteQuery(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, "ExecuteQuery", reflect.TypeOf((*MockQueryServiceClient)(nil).ExecuteQuery), varargs...) +} + +// ExecuteScript mocks base method. +func (m *MockQueryServiceClient) ExecuteScript(arg0 context.Context, arg1 *Ydb_Query.ExecuteScriptRequest, arg2 ...grpc.CallOption) (*Ydb_Operations.Operation, error) { + m.ctrl.T.Helper() + varargs := []any{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "ExecuteScript", varargs...) + ret0, _ := ret[0].(*Ydb_Operations.Operation) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ExecuteScript indicates an expected call of ExecuteScript. +func (mr *MockQueryServiceClientMockRecorder) ExecuteScript(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, "ExecuteScript", reflect.TypeOf((*MockQueryServiceClient)(nil).ExecuteScript), varargs...) +} + +// FetchScriptResults mocks base method. +func (m *MockQueryServiceClient) FetchScriptResults(arg0 context.Context, arg1 *Ydb_Query.FetchScriptResultsRequest, arg2 ...grpc.CallOption) (*Ydb_Query.FetchScriptResultsResponse, error) { + m.ctrl.T.Helper() + varargs := []any{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "FetchScriptResults", varargs...) + ret0, _ := ret[0].(*Ydb_Query.FetchScriptResultsResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// FetchScriptResults indicates an expected call of FetchScriptResults. +func (mr *MockQueryServiceClientMockRecorder) FetchScriptResults(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, "FetchScriptResults", reflect.TypeOf((*MockQueryServiceClient)(nil).FetchScriptResults), varargs...) +} + +// RollbackTransaction mocks base method. +func (m *MockQueryServiceClient) RollbackTransaction(arg0 context.Context, arg1 *Ydb_Query.RollbackTransactionRequest, arg2 ...grpc.CallOption) (*Ydb_Query.RollbackTransactionResponse, error) { + m.ctrl.T.Helper() + varargs := []any{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "RollbackTransaction", varargs...) + ret0, _ := ret[0].(*Ydb_Query.RollbackTransactionResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// RollbackTransaction indicates an expected call of RollbackTransaction. +func (mr *MockQueryServiceClientMockRecorder) RollbackTransaction(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, "RollbackTransaction", reflect.TypeOf((*MockQueryServiceClient)(nil).RollbackTransaction), varargs...) +} + +// MockQueryService_AttachSessionClient is a mock of QueryService_AttachSessionClient interface. +type MockQueryService_AttachSessionClient struct { + ctrl *gomock.Controller + recorder *MockQueryService_AttachSessionClientMockRecorder +} + +// MockQueryService_AttachSessionClientMockRecorder is the mock recorder for MockQueryService_AttachSessionClient. +type MockQueryService_AttachSessionClientMockRecorder struct { + mock *MockQueryService_AttachSessionClient +} + +// NewMockQueryService_AttachSessionClient creates a new mock instance. +func NewMockQueryService_AttachSessionClient(ctrl *gomock.Controller) *MockQueryService_AttachSessionClient { + mock := &MockQueryService_AttachSessionClient{ctrl: ctrl} + mock.recorder = &MockQueryService_AttachSessionClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockQueryService_AttachSessionClient) EXPECT() *MockQueryService_AttachSessionClientMockRecorder { + return m.recorder +} + +// CloseSend mocks base method. +func (m *MockQueryService_AttachSessionClient) 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 *MockQueryService_AttachSessionClientMockRecorder) CloseSend() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CloseSend", reflect.TypeOf((*MockQueryService_AttachSessionClient)(nil).CloseSend)) +} + +// Context mocks base method. +func (m *MockQueryService_AttachSessionClient) 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 *MockQueryService_AttachSessionClientMockRecorder) Context() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Context", reflect.TypeOf((*MockQueryService_AttachSessionClient)(nil).Context)) +} + +// Header mocks base method. +func (m *MockQueryService_AttachSessionClient) 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 *MockQueryService_AttachSessionClientMockRecorder) Header() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Header", reflect.TypeOf((*MockQueryService_AttachSessionClient)(nil).Header)) +} + +// Recv mocks base method. +func (m *MockQueryService_AttachSessionClient) Recv() (*Ydb_Query.SessionState, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Recv") + ret0, _ := ret[0].(*Ydb_Query.SessionState) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Recv indicates an expected call of Recv. +func (mr *MockQueryService_AttachSessionClientMockRecorder) Recv() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Recv", reflect.TypeOf((*MockQueryService_AttachSessionClient)(nil).Recv)) +} + +// RecvMsg mocks base method. +func (m *MockQueryService_AttachSessionClient) 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 *MockQueryService_AttachSessionClientMockRecorder) RecvMsg(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecvMsg", reflect.TypeOf((*MockQueryService_AttachSessionClient)(nil).RecvMsg), arg0) +} + +// SendMsg mocks base method. +func (m *MockQueryService_AttachSessionClient) 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 *MockQueryService_AttachSessionClientMockRecorder) SendMsg(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendMsg", reflect.TypeOf((*MockQueryService_AttachSessionClient)(nil).SendMsg), arg0) +} + +// Trailer mocks base method. +func (m *MockQueryService_AttachSessionClient) 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 *MockQueryService_AttachSessionClientMockRecorder) Trailer() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Trailer", reflect.TypeOf((*MockQueryService_AttachSessionClient)(nil).Trailer)) +} + +// MockQueryService_ExecuteQueryClient is a mock of QueryService_ExecuteQueryClient interface. +type MockQueryService_ExecuteQueryClient struct { + ctrl *gomock.Controller + recorder *MockQueryService_ExecuteQueryClientMockRecorder +} + +// MockQueryService_ExecuteQueryClientMockRecorder is the mock recorder for MockQueryService_ExecuteQueryClient. +type MockQueryService_ExecuteQueryClientMockRecorder struct { + mock *MockQueryService_ExecuteQueryClient +} + +// NewMockQueryService_ExecuteQueryClient creates a new mock instance. +func NewMockQueryService_ExecuteQueryClient(ctrl *gomock.Controller) *MockQueryService_ExecuteQueryClient { + mock := &MockQueryService_ExecuteQueryClient{ctrl: ctrl} + mock.recorder = &MockQueryService_ExecuteQueryClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockQueryService_ExecuteQueryClient) EXPECT() *MockQueryService_ExecuteQueryClientMockRecorder { + return m.recorder +} + +// CloseSend mocks base method. +func (m *MockQueryService_ExecuteQueryClient) 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 *MockQueryService_ExecuteQueryClientMockRecorder) CloseSend() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CloseSend", reflect.TypeOf((*MockQueryService_ExecuteQueryClient)(nil).CloseSend)) +} + +// Context mocks base method. +func (m *MockQueryService_ExecuteQueryClient) 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 *MockQueryService_ExecuteQueryClientMockRecorder) Context() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Context", reflect.TypeOf((*MockQueryService_ExecuteQueryClient)(nil).Context)) +} + +// Header mocks base method. +func (m *MockQueryService_ExecuteQueryClient) 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 *MockQueryService_ExecuteQueryClientMockRecorder) Header() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Header", reflect.TypeOf((*MockQueryService_ExecuteQueryClient)(nil).Header)) +} + +// Recv mocks base method. +func (m *MockQueryService_ExecuteQueryClient) Recv() (*Ydb_Query.ExecuteQueryResponsePart, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Recv") + ret0, _ := ret[0].(*Ydb_Query.ExecuteQueryResponsePart) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Recv indicates an expected call of Recv. +func (mr *MockQueryService_ExecuteQueryClientMockRecorder) Recv() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Recv", reflect.TypeOf((*MockQueryService_ExecuteQueryClient)(nil).Recv)) +} + +// RecvMsg mocks base method. +func (m *MockQueryService_ExecuteQueryClient) 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 *MockQueryService_ExecuteQueryClientMockRecorder) RecvMsg(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecvMsg", reflect.TypeOf((*MockQueryService_ExecuteQueryClient)(nil).RecvMsg), arg0) +} + +// SendMsg mocks base method. +func (m *MockQueryService_ExecuteQueryClient) 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 *MockQueryService_ExecuteQueryClientMockRecorder) SendMsg(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendMsg", reflect.TypeOf((*MockQueryService_ExecuteQueryClient)(nil).SendMsg), arg0) +} + +// Trailer mocks base method. +func (m *MockQueryService_ExecuteQueryClient) 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 *MockQueryService_ExecuteQueryClientMockRecorder) Trailer() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Trailer", reflect.TypeOf((*MockQueryService_ExecuteQueryClient)(nil).Trailer)) +} diff --git a/internal/query/options/execute.go b/internal/query/options/execute.go new file mode 100644 index 000000000..6a306c26d --- /dev/null +++ b/internal/query/options/execute.go @@ -0,0 +1,224 @@ +package options + +import ( + "github.com/ydb-platform/ydb-go-genproto/protos/Ydb_Query" + "google.golang.org/grpc" + + "github.com/ydb-platform/ydb-go-sdk/v3/internal/params" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/query/tx" +) + +type ( + Syntax Ydb_Query.Syntax + ExecMode Ydb_Query.ExecMode + StatsMode Ydb_Query.StatsMode + CallOptions []grpc.CallOption + commonExecuteSettings struct { + syntax Syntax + params params.Parameters + execMode ExecMode + statsMode StatsMode + callOptions []grpc.CallOption + } + Execute struct { + commonExecuteSettings + + txControl *tx.Control + } + ExecuteOption interface { + applyExecuteOption(s *Execute) + } + txExecuteSettings struct { + ExecuteSettings *Execute + + commitTx bool + } + TxExecuteOption interface { + applyTxExecuteOption(s *txExecuteSettings) + } + txCommitOption struct{} + ParametersOption params.Parameters + TxControlOption struct { + txControl *tx.Control + } +) + +func (opt TxControlOption) applyExecuteOption(s *Execute) { + s.txControl = opt.txControl +} + +func (t txCommitOption) applyTxExecuteOption(s *txExecuteSettings) { + s.commitTx = true +} + +func (syntax Syntax) applyTxExecuteOption(s *txExecuteSettings) { + syntax.applyExecuteOption(s.ExecuteSettings) +} + +func (syntax Syntax) applyExecuteOption(s *Execute) { + s.syntax = syntax +} + +const ( + SyntaxYQL = Syntax(Ydb_Query.Syntax_SYNTAX_YQL_V1) + SyntaxPostgreSQL = Syntax(Ydb_Query.Syntax_SYNTAX_PG) +) + +func (params ParametersOption) applyTxExecuteOption(s *txExecuteSettings) { + params.applyExecuteOption(s.ExecuteSettings) +} + +func (params ParametersOption) applyExecuteOption(s *Execute) { + s.params = append(s.params, params...) +} + +func (opts CallOptions) applyExecuteOption(s *Execute) { + s.callOptions = append(s.callOptions, opts...) +} + +func (opts CallOptions) applyTxExecuteOption(s *txExecuteSettings) { + opts.applyExecuteOption(s.ExecuteSettings) +} + +func (mode StatsMode) applyTxExecuteOption(s *txExecuteSettings) { + mode.applyExecuteOption(s.ExecuteSettings) +} + +func (mode StatsMode) applyExecuteOption(s *Execute) { + s.statsMode = mode +} + +func (mode ExecMode) applyTxExecuteOption(s *txExecuteSettings) { + mode.applyExecuteOption(s.ExecuteSettings) +} + +func (mode ExecMode) applyExecuteOption(s *Execute) { + s.execMode = mode +} + +const ( + ExecModeParse = ExecMode(Ydb_Query.ExecMode_EXEC_MODE_PARSE) + ExecModeValidate = ExecMode(Ydb_Query.ExecMode_EXEC_MODE_VALIDATE) + ExecModeExplain = ExecMode(Ydb_Query.ExecMode_EXEC_MODE_EXPLAIN) + ExecModeExecute = ExecMode(Ydb_Query.ExecMode_EXEC_MODE_EXECUTE) +) + +const ( + StatsModeBasic = StatsMode(Ydb_Query.StatsMode_STATS_MODE_BASIC) + StatsModeNone = StatsMode(Ydb_Query.StatsMode_STATS_MODE_NONE) + StatsModeFull = StatsMode(Ydb_Query.StatsMode_STATS_MODE_FULL) + StatsModeProfile = StatsMode(Ydb_Query.StatsMode_STATS_MODE_PROFILE) +) + +func defaultCommonExecuteSettings() commonExecuteSettings { + return commonExecuteSettings{ + syntax: SyntaxYQL, + execMode: ExecModeExecute, + statsMode: StatsModeNone, + } +} + +func ExecuteSettings(opts ...ExecuteOption) (settings *Execute) { + settings = &Execute{ + commonExecuteSettings: defaultCommonExecuteSettings(), + } + settings.commonExecuteSettings = defaultCommonExecuteSettings() + settings.txControl = tx.DefaultTxControl() + for _, opt := range opts { + if opt != nil { + opt.applyExecuteOption(settings) + } + } + + return settings +} + +func (s *Execute) TxControl() *tx.Control { + return s.txControl +} + +func (s *Execute) SetTxControl(ctrl *tx.Control) { + s.txControl = ctrl +} + +func (s *commonExecuteSettings) CallOptions() []grpc.CallOption { + return s.callOptions +} + +func (s *commonExecuteSettings) Syntax() Syntax { + return s.syntax +} + +func (s *commonExecuteSettings) ExecMode() ExecMode { + return s.execMode +} + +func (s *commonExecuteSettings) StatsMode() StatsMode { + return s.statsMode +} + +func (s *commonExecuteSettings) Params() *params.Parameters { + if len(s.params) == 0 { + return nil + } + + return &s.params +} + +func TxExecuteSettings(id string, opts ...TxExecuteOption) (settings *txExecuteSettings) { + settings = &txExecuteSettings{ + ExecuteSettings: ExecuteSettings(WithTxControl(tx.NewControl(tx.WithTxID(id)))), + } + for _, opt := range opts { + if opt != nil { + opt.applyTxExecuteOption(settings) + } + } + + return settings +} + +var _ ExecuteOption = ParametersOption{} + +func WithParameters(parameters *params.Parameters) ParametersOption { + return ParametersOption(*parameters) +} + +var ( + _ ExecuteOption = ExecMode(0) + _ ExecuteOption = StatsMode(0) + _ TxExecuteOption = ExecMode(0) + _ TxExecuteOption = StatsMode(0) + _ TxExecuteOption = txCommitOption{} + _ ExecuteOption = TxControlOption{} +) + +func WithCommit() txCommitOption { + return txCommitOption{} +} + +type ExecModeOption = ExecMode + +func WithExecMode(mode ExecMode) ExecMode { + return mode +} + +type SyntaxOption = Syntax + +func WithSyntax(syntax Syntax) SyntaxOption { + return syntax +} + +type StatsModeOption = StatsMode + +func WithStatsMode(mode StatsMode) StatsMode { + return mode +} + +func WithCallOptions(opts ...grpc.CallOption) CallOptions { + return opts +} + +func WithTxControl(txControl *tx.Control) TxControlOption { + return TxControlOption{txControl} +} diff --git a/internal/query/options/retry.go b/internal/query/options/retry.go new file mode 100644 index 000000000..b604152e3 --- /dev/null +++ b/internal/query/options/retry.go @@ -0,0 +1,138 @@ +package options + +import ( + "github.com/ydb-platform/ydb-go-sdk/v3/internal/query/tx" + "github.com/ydb-platform/ydb-go-sdk/v3/retry" + "github.com/ydb-platform/ydb-go-sdk/v3/trace" +) + +var ( + _ DoOption = idempotentOption{} + _ DoOption = labelOption("") + _ DoOption = traceOption{} + + _ DoTxOption = idempotentOption{} + _ DoTxOption = labelOption("") + _ DoTxOption = traceOption{} + _ DoTxOption = doTxSettingsOption{} +) + +type ( + DoOption interface { + applyDoOption(s *doSettings) + } + + doSettings struct { + retryOpts []retry.Option + trace *trace.Query + } + + DoTxOption interface { + applyDoTxOption(o *doTxSettings) + } + + doTxSettings struct { + doOpts []DoOption + txSettings tx.Settings + } + + idempotentOption struct{} + labelOption string + traceOption struct { + t *trace.Query + } + doTxSettingsOption struct { + txSettings tx.Settings + } +) + +func (s *doSettings) Trace() *trace.Query { + return s.trace +} + +func (s *doSettings) RetryOpts() []retry.Option { + return s.retryOpts +} + +func (s *doTxSettings) DoOpts() []DoOption { + return s.doOpts +} + +func (s *doTxSettings) TxSettings() tx.Settings { + return s.txSettings +} + +func (opt idempotentOption) applyDoTxOption(s *doTxSettings) { + s.doOpts = append(s.doOpts, opt) +} + +func (idempotentOption) applyDoOption(s *doSettings) { + s.retryOpts = append(s.retryOpts, retry.WithIdempotent(true)) +} + +func (opt traceOption) applyDoOption(s *doSettings) { + s.trace = s.trace.Compose(opt.t) +} + +func (opt traceOption) applyDoTxOption(s *doTxSettings) { + s.doOpts = append(s.doOpts, opt) +} + +func (opt labelOption) applyDoOption(s *doSettings) { + s.retryOpts = append(s.retryOpts, retry.WithLabel(string(opt))) +} + +func (opt labelOption) applyDoTxOption(s *doTxSettings) { + s.doOpts = append(s.doOpts, opt) +} + +func (opt doTxSettingsOption) applyDoTxOption(opts *doTxSettings) { + opts.txSettings = opt.txSettings +} + +func WithTxSettings(txSettings tx.Settings) doTxSettingsOption { + return doTxSettingsOption{txSettings: txSettings} +} + +func WithIdempotent() idempotentOption { + return idempotentOption{} +} + +func WithLabel(lbl string) labelOption { + return labelOption(lbl) +} + +func WithTrace(t *trace.Query) traceOption { + return traceOption{t: t} +} + +func ParseDoOpts(t *trace.Query, opts ...DoOption) (s *doSettings) { + s = &doSettings{ + trace: t, + } + + for _, opt := range opts { + if opt != nil { + opt.applyDoOption(s) + } + } + + return s +} + +func ParseDoTxOpts(t *trace.Query, opts ...DoTxOption) (s *doTxSettings) { + s = &doTxSettings{ + txSettings: tx.NewSettings(tx.WithDefaultTxMode()), + doOpts: []DoOption{ + WithTrace(t), + }, + } + + for _, opt := range opts { + if opt != nil { + opt.applyDoTxOption(s) + } + } + + return s +} diff --git a/internal/query/result.go b/internal/query/result.go new file mode 100644 index 000000000..78478610d --- /dev/null +++ b/internal/query/result.go @@ -0,0 +1,204 @@ +package query + +import ( + "context" + "fmt" + "io" + + "github.com/ydb-platform/ydb-go-genproto/Ydb_Query_V1" + "github.com/ydb-platform/ydb-go-genproto/protos/Ydb_Query" + + "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/xsync" + "github.com/ydb-platform/ydb-go-sdk/v3/query" + "github.com/ydb-platform/ydb-go-sdk/v3/trace" +) + +var _ query.Result = (*result)(nil) + +type result struct { + stream Ydb_Query_V1.QueryService_ExecuteQueryClient + closeOnce func(ctx context.Context) error + lastPart *Ydb_Query.ExecuteQueryResponsePart + resultSetIndex int64 + errs []error + closed chan struct{} + trace *trace.Query +} + +func newResult( + ctx context.Context, + stream Ydb_Query_V1.QueryService_ExecuteQueryClient, + t *trace.Query, + closeResult context.CancelFunc, +) (_ *result, txID string, err error) { + if t == nil { + t = &trace.Query{} + } + if closeResult == nil { + closeResult = func() {} + } + + onDone := trace.QueryOnResultNew(t, &ctx, + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/query.newResult"), + ) + defer func() { + onDone(err) + }() + + select { + case <-ctx.Done(): + return nil, txID, xerrors.WithStackTrace(ctx.Err()) + default: + part, err := nextPart(ctx, stream, t) + if err != nil { + return nil, txID, xerrors.WithStackTrace(err) + } + var ( + interrupted = make(chan struct{}) + closed = make(chan struct{}) + closeOnce = xsync.OnceFunc(func(ctx context.Context) error { + closeResult() + + close(interrupted) + close(closed) + + return nil + }) + ) + + return &result{ + stream: stream, + resultSetIndex: -1, + lastPart: part, + closed: closed, + closeOnce: closeOnce, + trace: t, + }, part.GetTxMeta().GetId(), nil + } +} + +func nextPart( + ctx context.Context, + stream Ydb_Query_V1.QueryService_ExecuteQueryClient, + t *trace.Query, +) (_ *Ydb_Query.ExecuteQueryResponsePart, finalErr error) { + if t == nil { + t = &trace.Query{} + } + + onDone := trace.QueryOnResultNextPart(t, &ctx, + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/query.nextPart"), + ) + defer func() { + onDone(finalErr) + }() + + part, err := stream.Recv() + if err != nil { + return nil, xerrors.WithStackTrace(err) + } + + return part, nil +} + +func (r *result) Close(ctx context.Context) (err error) { + onDone := trace.QueryOnResultClose(r.trace, &ctx, + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/query.(*result).Close"), + ) + defer func() { + onDone(err) + }() + + return r.closeOnce(ctx) +} + +func (r *result) nextResultSet(ctx context.Context) (_ *resultSet, err error) { + defer func() { + if err != nil && !xerrors.Is(err, + io.EOF, errClosedResult, context.Canceled, + ) { + r.errs = append(r.errs, err) + } + }() + nextResultSetIndex := r.resultSetIndex + 1 + for { + select { + case <-r.closed: + return nil, xerrors.WithStackTrace(errClosedResult) + case <-ctx.Done(): + return nil, xerrors.WithStackTrace(ctx.Err()) + default: + if resultSetIndex := r.lastPart.GetResultSetIndex(); resultSetIndex >= nextResultSetIndex { //nolint:nestif + r.resultSetIndex = resultSetIndex + + return newResultSet(func() (_ *Ydb_Query.ExecuteQueryResponsePart, err error) { + defer func() { + if err != nil && !xerrors.Is(err, + io.EOF, context.Canceled, + ) { + r.errs = append(r.errs, err) + } + }() + select { + case <-r.closed: + return nil, errClosedResult + default: + part, err := nextPart(ctx, r.stream, r.trace) + if err != nil { + if xerrors.Is(err, io.EOF) { + _ = r.closeOnce(ctx) + } + + return nil, xerrors.WithStackTrace(err) + } + r.lastPart = part + if part.GetResultSetIndex() > nextResultSetIndex { + return nil, xerrors.WithStackTrace(fmt.Errorf( + "result set (index=%d) receive part (index=%d) for next result set: %w", + nextResultSetIndex, part.GetResultSetIndex(), io.EOF, + )) + } + + return part, nil + } + }, r.lastPart, r.trace), nil + } + part, err := nextPart(ctx, r.stream, r.trace) + if err != nil { + return nil, xerrors.WithStackTrace(err) + } + if part.GetResultSetIndex() < r.resultSetIndex { + return nil, xerrors.WithStackTrace(fmt.Errorf( + "next result set index %d less than last result set index %d: %w", + part.GetResultSetIndex(), r.resultSetIndex, errWrongNextResultSetIndex, + )) + } + r.lastPart = part + r.resultSetIndex = part.GetResultSetIndex() + } + } +} + +func (r *result) NextResultSet(ctx context.Context) (_ query.ResultSet, err error) { + onDone := trace.QueryOnResultNextResultSet(r.trace, &ctx, + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/query.(*result).NextResultSet"), + ) + defer func() { + onDone(err) + }() + + return r.nextResultSet(ctx) +} + +func (r *result) Err() error { + switch { + case len(r.errs) == 0: + return nil + case len(r.errs) == 1: + return r.errs[0] + default: + return xerrors.WithStackTrace(xerrors.Join(r.errs...)) + } +} diff --git a/internal/query/result_set.go b/internal/query/result_set.go new file mode 100644 index 000000000..47d90301f --- /dev/null +++ b/internal/query/result_set.go @@ -0,0 +1,96 @@ +package query + +import ( + "context" + "fmt" + "io" + + "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/stack" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors" + "github.com/ydb-platform/ydb-go-sdk/v3/query" + "github.com/ydb-platform/ydb-go-sdk/v3/trace" +) + +var _ query.ResultSet = (*resultSet)(nil) + +type resultSet struct { + index int64 + recv func() (*Ydb_Query.ExecuteQueryResponsePart, error) + columns []*Ydb.Column + currentPart *Ydb_Query.ExecuteQueryResponsePart + rowIndex int + trace *trace.Query + done chan struct{} +} + +func newResultSet( + recv func() (*Ydb_Query.ExecuteQueryResponsePart, error), + part *Ydb_Query.ExecuteQueryResponsePart, + t *trace.Query, +) *resultSet { + if t == nil { + t = &trace.Query{} + } + + return &resultSet{ + index: part.GetResultSetIndex(), + recv: recv, + currentPart: part, + rowIndex: -1, + columns: part.GetResultSet().GetColumns(), + trace: t, + done: make(chan struct{}), + } +} + +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) + } + + return nil, xerrors.WithStackTrace(err) + } + rs.rowIndex = 0 + rs.currentPart = part + if part == nil { + close(rs.done) + + return nil, xerrors.WithStackTrace(io.EOF) + } + } + 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, + )) + } + + 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("github.com/ydb-platform/ydb-go-sdk/3/internal/query.(*resultSet).NextRow"), + ) + defer func() { + onDone(err) + }() + + return rs.nextRow(ctx) +} diff --git a/internal/query/result_set_test.go b/internal/query/result_set_test.go new file mode 100644 index 000000000..a011d2781 --- /dev/null +++ b/internal/query/result_set_test.go @@ -0,0 +1,602 @@ +package query + +import ( + "context" + "fmt" + "io" + "testing" + + "github.com/stretchr/testify/require" + "github.com/ydb-platform/ydb-go-genproto/protos/Ydb" + "github.com/ydb-platform/ydb-go-genproto/protos/Ydb_Query" + "go.uber.org/mock/gomock" + 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" +) + +func TestResultSetNext(t *testing.T) { + ctx := xtest.Context(t) + ctrl := gomock.NewController(t) + t.Run("OverTwoParts", 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{ + { + Items: []*Ydb.Value{{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 4, + }, + }, { + Value: &Ydb.Value_TextValue{ + TextValue: "4", + }, + }}, + }, + { + Items: []*Ydb.Value{{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 5, + }, + }, { + Value: &Ydb.Value_TextValue{ + TextValue: "5", + }, + }}, + }, + }, + }, + }, 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.ErrorIs(t, err, io.EOF) + } + }) + t.Run("CanceledContext", func(t *testing.T) { + ctx, cancel := context.WithCancel(xtest.Context(t)) + ctrl := gomock.NewController(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) + 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) + } + cancel() + { + _, err := rs.nextRow(ctx) + require.ErrorIs(t, err, context.Canceled) + } + }) + t.Run("OperationError", func(t *testing.T) { + ctx := xtest.Context(t) + ctrl := gomock.NewController(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(nil, xerrors.Operation(xerrors.WithStatusCode( + Ydb.StatusIds_OVERLOADED, + ))) + recv, err := stream.Recv() + require.NoError(t, err) + rs := newResultSet(func() (*Ydb_Query.ExecuteQueryResponsePart, error) { + part, err := nextPart(ctx, stream, nil) + if err != nil { + return nil, xerrors.WithStackTrace(err) + } + if resultSetIndex := part.GetResultSetIndex(); resultSetIndex != 0 { + return nil, xerrors.WithStackTrace(fmt.Errorf( + "critical violation of the logic: wrong result set index: %d != %d", + resultSetIndex, 0, + )) + } + + 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.True(t, xerrors.IsOperationError(err, Ydb.StatusIds_OVERLOADED)) + } + }) + t.Run("TransportError", func(t *testing.T) { + ctx := xtest.Context(t) + ctrl := gomock.NewController(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(nil, grpcStatus.Error(grpcCodes.Unavailable, "")) + recv, err := stream.Recv() + require.NoError(t, err) + rs := newResultSet(func() (*Ydb_Query.ExecuteQueryResponsePart, error) { + part, err := nextPart(ctx, stream, nil) + if err != nil { + return nil, xerrors.WithStackTrace(err) + } + if resultSetIndex := part.GetResultSetIndex(); resultSetIndex != 0 { + return nil, xerrors.WithStackTrace(fmt.Errorf( + "critical violation of the logic: wrong result set index: %d != %d", + resultSetIndex, 0, + )) + } + + 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.True(t, xerrors.IsTransportError(err, grpcCodes.Unavailable)) + } + }) + t.Run("WrongResultSetIndex", func(t *testing.T) { + ctx := xtest.Context(t) + ctrl := gomock.NewController(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: 1, + 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) + recv, err := stream.Recv() + require.NoError(t, err) + rs := newResultSet(func() (*Ydb_Query.ExecuteQueryResponsePart, error) { + part, err := nextPart(ctx, stream, nil) + 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, errWrongResultSetIndex) + } + { + _, err := rs.nextRow(ctx) + require.ErrorIs(t, err, io.EOF) + } + }) +} diff --git a/internal/query/result_test.go b/internal/query/result_test.go new file mode 100644 index 000000000..10d408f03 --- /dev/null +++ b/internal/query/result_test.go @@ -0,0 +1,895 @@ +package query + +import ( + "context" + "io" + "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_Query" + "go.uber.org/mock/gomock" + + "github.com/ydb-platform/ydb-go-sdk/v3/internal/xtest" +) + +func TestResultNextResultSet(t *testing.T) { + t.Run("HappyWay", func(t *testing.T) { + xtest.TestManyTimes(t, func(t testing.TB) { + ctx, cancel := context.WithCancel(xtest.Context(t)) + defer cancel() + ctrl := gomock.NewController(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{ + { + Items: []*Ydb.Value{{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 4, + }, + }, { + Value: &Ydb.Value_TextValue{ + TextValue: "4", + }, + }}, + }, + { + Items: []*Ydb.Value{{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 5, + }, + }, { + Value: &Ydb.Value_TextValue{ + TextValue: "5", + }, + }}, + }, + }, + }, + }, nil) + stream.EXPECT().Recv().Return(&Ydb_Query.ExecuteQueryResponsePart{ + Status: Ydb.StatusIds_SUCCESS, + ResultSetIndex: 1, + ResultSet: &Ydb.ResultSet{ + Columns: []*Ydb.Column{ + { + Name: "c", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UINT64, + }, + }, + }, + { + Name: "d", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UTF8, + }, + }, + }, + { + Name: "e", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_BOOL, + }, + }, + }, + }, + Rows: []*Ydb.Value{ + { + Items: []*Ydb.Value{{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 1, + }, + }, { + Value: &Ydb.Value_TextValue{ + TextValue: "1", + }, + }, { + Value: &Ydb.Value_BoolValue{ + BoolValue: true, + }, + }}, + }, + { + Items: []*Ydb.Value{{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 2, + }, + }, { + Value: &Ydb.Value_TextValue{ + TextValue: "2", + }, + }, { + Value: &Ydb.Value_BoolValue{ + BoolValue: false, + }, + }}, + }, + }, + }, + }, nil) + stream.EXPECT().Recv().Return(&Ydb_Query.ExecuteQueryResponsePart{ + Status: Ydb.StatusIds_SUCCESS, + ResultSetIndex: 1, + ResultSet: &Ydb.ResultSet{ + Rows: []*Ydb.Value{ + { + Items: []*Ydb.Value{{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 3, + }, + }, { + Value: &Ydb.Value_TextValue{ + TextValue: "3", + }, + }, { + Value: &Ydb.Value_BoolValue{ + BoolValue: true, + }, + }}, + }, + { + Items: []*Ydb.Value{{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 4, + }, + }, { + Value: &Ydb.Value_TextValue{ + TextValue: "4", + }, + }, { + Value: &Ydb.Value_BoolValue{ + BoolValue: false, + }, + }}, + }, + { + Items: []*Ydb.Value{{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 5, + }, + }, { + Value: &Ydb.Value_TextValue{ + TextValue: "5", + }, + }, { + Value: &Ydb.Value_BoolValue{ + BoolValue: false, + }, + }}, + }, + }, + }, + }, nil) + stream.EXPECT().Recv().Return(&Ydb_Query.ExecuteQueryResponsePart{ + Status: Ydb.StatusIds_SUCCESS, + ResultSetIndex: 2, + ResultSet: &Ydb.ResultSet{ + Columns: []*Ydb.Column{ + { + Name: "c", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UINT64, + }, + }, + }, + { + Name: "d", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UTF8, + }, + }, + }, + { + Name: "e", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_BOOL, + }, + }, + }, + }, + Rows: []*Ydb.Value{ + { + Items: []*Ydb.Value{{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 1, + }, + }, { + Value: &Ydb.Value_TextValue{ + TextValue: "1", + }, + }, { + Value: &Ydb.Value_BoolValue{ + BoolValue: true, + }, + }}, + }, + { + Items: []*Ydb.Value{{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 2, + }, + }, { + Value: &Ydb.Value_TextValue{ + TextValue: "2", + }, + }, { + Value: &Ydb.Value_BoolValue{ + BoolValue: false, + }, + }}, + }, + }, + }, + }, nil) + stream.EXPECT().Recv().Return(&Ydb_Query.ExecuteQueryResponsePart{ + Status: Ydb.StatusIds_SUCCESS, + ResultSetIndex: 2, + ResultSet: &Ydb.ResultSet{ + Rows: []*Ydb.Value{ + { + Items: []*Ydb.Value{{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 3, + }, + }, { + Value: &Ydb.Value_TextValue{ + TextValue: "3", + }, + }, { + Value: &Ydb.Value_BoolValue{ + BoolValue: true, + }, + }}, + }, + { + Items: []*Ydb.Value{{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 4, + }, + }, { + Value: &Ydb.Value_TextValue{ + TextValue: "4", + }, + }, { + Value: &Ydb.Value_BoolValue{ + BoolValue: false, + }, + }}, + }, + { + Items: []*Ydb.Value{{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 5, + }, + }, { + Value: &Ydb.Value_TextValue{ + TextValue: "5", + }, + }, { + Value: &Ydb.Value_BoolValue{ + BoolValue: false, + }, + }}, + }, + }, + }, + }, nil) + stream.EXPECT().Recv().Return(nil, io.EOF) + r, _, err := newResult(ctx, stream, nil, nil) + require.NoError(t, err) + defer r.Close(ctx) + { + t.Log("nextResultSet") + rs, err := r.nextResultSet(ctx) + require.NoError(t, err) + require.EqualValues(t, 0, rs.index) + { + t.Log("next (row=1)") + _, err := rs.nextRow(ctx) + require.NoError(t, err) + require.EqualValues(t, 0, rs.rowIndex) + } + { + t.Log("next (row=2)") + _, err := rs.nextRow(ctx) + require.NoError(t, err) + require.EqualValues(t, 1, rs.rowIndex) + } + { + t.Log("next (row=3)") + _, err := rs.nextRow(ctx) + require.NoError(t, err) + require.EqualValues(t, 2, rs.rowIndex) + } + { + t.Log("next (row=4)") + _, err := rs.nextRow(ctx) + require.NoError(t, err) + require.EqualValues(t, 0, rs.rowIndex) + } + { + t.Log("next (row=5)") + _, err := rs.nextRow(ctx) + require.NoError(t, err) + require.EqualValues(t, 1, rs.rowIndex) + } + { + t.Log("next (row=6)") + _, err := rs.nextRow(ctx) + require.ErrorIs(t, err, io.EOF) + } + } + { + t.Log("nextResultSet") + rs, err := r.nextResultSet(ctx) + require.NoError(t, err) + require.EqualValues(t, 1, rs.index) + } + { + t.Log("nextResultSet") + rs, err := r.nextResultSet(ctx) + require.NoError(t, err) + require.EqualValues(t, 2, rs.index) + { + t.Log("next (row=1)") + _, err := rs.nextRow(ctx) + require.NoError(t, err) + require.EqualValues(t, 0, rs.rowIndex) + } + { + t.Log("next (row=2)") + _, err := rs.nextRow(ctx) + require.NoError(t, err) + require.EqualValues(t, 1, rs.rowIndex) + } + { + t.Log("next (row=3)") + _, err := rs.nextRow(ctx) + require.NoError(t, err) + require.EqualValues(t, 0, rs.rowIndex) + } + { + t.Log("next (row=4)") + _, err := rs.nextRow(ctx) + require.NoError(t, err) + require.EqualValues(t, 1, rs.rowIndex) + } + { + t.Log("next (row=5)") + _, err := rs.nextRow(ctx) + require.NoError(t, err) + require.EqualValues(t, 2, rs.rowIndex) + } + { + t.Log("next (row=6)") + _, err := rs.nextRow(ctx) + require.ErrorIs(t, err, io.EOF) + } + } + { + t.Log("close result") + r.Close(context.Background()) + } + { + t.Log("nextResultSet") + _, err := r.nextResultSet(context.Background()) + require.ErrorIs(t, err, errClosedResult) + } + t.Log("check final error") + require.NoError(t, r.Err()) + }, xtest.StopAfter(time.Second)) + }) + t.Run("InterruptStream", func(t *testing.T) { + xtest.TestManyTimes(t, func(t testing.TB) { + ctx, cancel := context.WithCancel(xtest.Context(t)) + defer cancel() + ctrl := gomock.NewController(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) + r, _, err := newResult(ctx, stream, nil, nil) + require.NoError(t, err) + defer r.Close(ctx) + { + t.Log("nextResultSet") + rs, err := r.nextResultSet(ctx) + require.NoError(t, err) + require.EqualValues(t, 0, rs.index) + { + t.Log("next (row=1)") + _, err := rs.nextRow(ctx) + require.NoError(t, err) + require.EqualValues(t, 0, rs.rowIndex) + } + { + t.Log("next (row=2)") + _, err := rs.nextRow(ctx) + require.NoError(t, err) + require.EqualValues(t, 1, rs.rowIndex) + } + t.Log("explicit interrupt stream") + require.NoError(t, r.closeOnce(ctx)) + { + t.Log("next (row=3)") + _, err := rs.nextRow(context.Background()) + require.NoError(t, err) + require.EqualValues(t, 2, rs.rowIndex) + } + { + t.Log("next (row=4)") + _, err := rs.nextRow(context.Background()) + require.ErrorIs(t, err, errClosedResult) + } + } + { + t.Log("nextResultSet") + _, err := r.nextResultSet(context.Background()) + require.ErrorIs(t, err, errClosedResult) + } + t.Log("check final error") + require.ErrorIs(t, r.Err(), errClosedResult) + }, xtest.StopAfter(time.Second)) + }) + t.Run("WrongResultSetIndex", func(t *testing.T) { + xtest.TestManyTimes(t, func(t testing.TB) { + ctx, cancel := context.WithCancel(xtest.Context(t)) + defer cancel() + ctrl := gomock.NewController(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{ + { + Items: []*Ydb.Value{{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 4, + }, + }, { + Value: &Ydb.Value_TextValue{ + TextValue: "4", + }, + }}, + }, + { + Items: []*Ydb.Value{{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 5, + }, + }, { + Value: &Ydb.Value_TextValue{ + TextValue: "5", + }, + }}, + }, + }, + }, + }, nil) + stream.EXPECT().Recv().Return(&Ydb_Query.ExecuteQueryResponsePart{ + Status: Ydb.StatusIds_SUCCESS, + ResultSetIndex: 2, + ResultSet: &Ydb.ResultSet{ + Columns: []*Ydb.Column{ + { + Name: "c", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UINT64, + }, + }, + }, + { + Name: "d", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UTF8, + }, + }, + }, + { + Name: "e", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_BOOL, + }, + }, + }, + }, + Rows: []*Ydb.Value{ + { + Items: []*Ydb.Value{{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 1, + }, + }, { + Value: &Ydb.Value_TextValue{ + TextValue: "1", + }, + }, { + Value: &Ydb.Value_BoolValue{ + BoolValue: true, + }, + }}, + }, + { + Items: []*Ydb.Value{{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 2, + }, + }, { + Value: &Ydb.Value_TextValue{ + TextValue: "2", + }, + }, { + Value: &Ydb.Value_BoolValue{ + BoolValue: false, + }, + }}, + }, + }, + }, + }, nil) + stream.EXPECT().Recv().Return(&Ydb_Query.ExecuteQueryResponsePart{ + Status: Ydb.StatusIds_SUCCESS, + ResultSetIndex: 2, + ResultSet: &Ydb.ResultSet{ + Rows: []*Ydb.Value{ + { + Items: []*Ydb.Value{{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 3, + }, + }, { + Value: &Ydb.Value_TextValue{ + TextValue: "3", + }, + }, { + Value: &Ydb.Value_BoolValue{ + BoolValue: true, + }, + }}, + }, + { + Items: []*Ydb.Value{{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 4, + }, + }, { + Value: &Ydb.Value_TextValue{ + TextValue: "4", + }, + }, { + Value: &Ydb.Value_BoolValue{ + BoolValue: false, + }, + }}, + }, + { + Items: []*Ydb.Value{{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 5, + }, + }, { + Value: &Ydb.Value_TextValue{ + TextValue: "5", + }, + }, { + Value: &Ydb.Value_BoolValue{ + BoolValue: false, + }, + }}, + }, + }, + }, + }, nil) + stream.EXPECT().Recv().Return(&Ydb_Query.ExecuteQueryResponsePart{ + Status: Ydb.StatusIds_SUCCESS, + ResultSetIndex: 1, + ResultSet: &Ydb.ResultSet{ + Columns: []*Ydb.Column{ + { + Name: "c", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UINT64, + }, + }, + }, + { + Name: "d", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UTF8, + }, + }, + }, + { + Name: "e", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_BOOL, + }, + }, + }, + }, + Rows: []*Ydb.Value{ + { + Items: []*Ydb.Value{{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 1, + }, + }, { + Value: &Ydb.Value_TextValue{ + TextValue: "1", + }, + }, { + Value: &Ydb.Value_BoolValue{ + BoolValue: true, + }, + }}, + }, + { + Items: []*Ydb.Value{{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 2, + }, + }, { + Value: &Ydb.Value_TextValue{ + TextValue: "2", + }, + }, { + Value: &Ydb.Value_BoolValue{ + BoolValue: false, + }, + }}, + }, + }, + }, + }, nil) + r, _, err := newResult(ctx, stream, nil, nil) + require.NoError(t, err) + defer r.Close(ctx) + { + t.Log("nextResultSet") + rs, err := r.nextResultSet(ctx) + require.NoError(t, err) + require.EqualValues(t, 0, rs.index) + { + t.Log("next (row=1)") + _, err := rs.nextRow(ctx) + require.NoError(t, err) + require.EqualValues(t, 0, rs.rowIndex) + } + { + t.Log("next (row=2)") + _, err := rs.nextRow(ctx) + require.NoError(t, err) + require.EqualValues(t, 1, rs.rowIndex) + } + { + t.Log("next (row=3)") + _, err := rs.nextRow(ctx) + require.NoError(t, err) + require.EqualValues(t, 2, rs.rowIndex) + } + { + t.Log("next (row=4)") + _, err := rs.nextRow(ctx) + require.NoError(t, err) + require.EqualValues(t, 0, rs.rowIndex) + } + { + t.Log("next (row=5)") + _, err := rs.nextRow(ctx) + require.NoError(t, err) + require.EqualValues(t, 1, rs.rowIndex) + } + { + t.Log("next (row=6)") + _, err := rs.nextRow(ctx) + require.ErrorIs(t, err, io.EOF) + } + } + { + t.Log("nextResultSet") + rs, err := r.nextResultSet(ctx) + require.NoError(t, err) + require.EqualValues(t, 2, rs.index) + } + { + t.Log("nextResultSet") + _, err := r.nextResultSet(ctx) + require.ErrorIs(t, err, errWrongNextResultSetIndex) + } + t.Log("check final error") + require.ErrorIs(t, r.Err(), errWrongNextResultSetIndex) + }, xtest.StopAfter(time.Second)) + }) +} diff --git a/internal/query/row.go b/internal/query/row.go new file mode 100644 index 000000000..476b6aa14 --- /dev/null +++ b/internal/query/row.go @@ -0,0 +1,68 @@ +package query + +import ( + "context" + + "github.com/ydb-platform/ydb-go-genproto/protos/Ydb" + + "github.com/ydb-platform/ydb-go-sdk/v3/internal/query/scanner" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/stack" + "github.com/ydb-platform/ydb-go-sdk/v3/query" + "github.com/ydb-platform/ydb-go-sdk/v3/trace" +) + +var _ query.Row = (*row)(nil) + +type row struct { + ctx context.Context + trace *trace.Query + + indexedScanner scanner.IndexedScanner + namedScanner scanner.NamedScanner + structScanner scanner.StructScanner +} + +func newRow(ctx context.Context, columns []*Ydb.Column, v *Ydb.Value, t *trace.Query) (*row, error) { + data := scanner.Data(columns, v.GetItems()) + + return &row{ + ctx: ctx, + trace: t, + indexedScanner: scanner.Indexed(data), + namedScanner: scanner.Named(data), + structScanner: scanner.Struct(data), + }, nil +} + +func (r row) Scan(dst ...interface{}) (err error) { + 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) + }() + + return r.indexedScanner.Scan(dst...) +} + +func (r row) ScanNamed(dst ...scanner.NamedDestination) (err error) { + 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) + }() + + return r.namedScanner.ScanNamed(dst...) +} + +func (r row) ScanStruct(dst interface{}, opts ...scanner.ScanStructOption) (err error) { + 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) + }() + + return r.structScanner.ScanStruct(dst, opts...) +} diff --git a/internal/query/scanner/data.go b/internal/query/scanner/data.go new file mode 100644 index 000000000..d1a806097 --- /dev/null +++ b/internal/query/scanner/data.go @@ -0,0 +1,36 @@ +package scanner + +import ( + "fmt" + + "github.com/ydb-platform/ydb-go-genproto/protos/Ydb" + + "github.com/ydb-platform/ydb-go-sdk/v3/internal/value" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors" +) + +type data struct { + columns []*Ydb.Column + values []*Ydb.Value +} + +func Data(columns []*Ydb.Column, values []*Ydb.Value) *data { + return &data{ + columns: columns, + values: values, + } +} + +func (s data) seekByName(name string) (value.Value, error) { + for i := range s.columns { + if s.columns[i].GetName() == name { + return value.FromYDB(s.columns[i].GetType(), s.values[i]), nil + } + } + + return nil, xerrors.WithStackTrace(fmt.Errorf("'%s': %w", name, errColumnsNotFoundInRow)) +} + +func (s data) seekByIndex(idx int) value.Value { + return value.FromYDB(s.columns[idx].GetType(), s.values[idx]) +} diff --git a/internal/query/scanner/errors.go b/internal/query/scanner/errors.go new file mode 100644 index 000000000..cac104f10 --- /dev/null +++ b/internal/query/scanner/errors.go @@ -0,0 +1,13 @@ +package scanner + +import ( + "errors" +) + +var ( + errColumnsNotFoundInRow = errors.New("some columns not found in row") + errFieldsNotFoundInStruct = errors.New("some fields not found in struct") + errIncompatibleColumnsAndDestinations = errors.New("incompatible columns and destinations") + errDstTypeIsNotAPointer = errors.New("dst type is not a pointer") + errDstTypeIsNotAPointerToStruct = errors.New("dst type is not a pointer to struct") +) diff --git a/internal/query/scanner/indexed.go b/internal/query/scanner/indexed.go new file mode 100644 index 000000000..e826a08c0 --- /dev/null +++ b/internal/query/scanner/indexed.go @@ -0,0 +1,37 @@ +package scanner + +import ( + "fmt" + + "github.com/ydb-platform/ydb-go-sdk/v3/internal/value" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors" +) + +type IndexedScanner struct { + data *data +} + +func Indexed(data *data) IndexedScanner { + return IndexedScanner{ + data: data, + } +} + +func (s IndexedScanner) Scan(dst ...interface{}) (err error) { + if len(dst) != len(s.data.columns) { + return xerrors.WithStackTrace( + fmt.Errorf("%w: %d != %d", + errIncompatibleColumnsAndDestinations, + len(dst), len(s.data.columns), + ), + ) + } + for i := range dst { + v := s.data.seekByIndex(i) + if err := value.CastTo(v, dst[i]); err != nil { + return xerrors.WithStackTrace(err) + } + } + + return nil +} diff --git a/internal/query/scanner/indexed_test.go b/internal/query/scanner/indexed_test.go new file mode 100644 index 000000000..ac2a9b40d --- /dev/null +++ b/internal/query/scanner/indexed_test.go @@ -0,0 +1,558 @@ +package scanner + +import ( + "reflect" + "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/value" +) + +func TestIndexed(t *testing.T) { + for _, tt := range []struct { + name string + s IndexedScanner + dst [][]interface{} + exp [][]interface{} + }{ + { + name: "Ydb.Type_UTF8", + s: Indexed(Data( + []*Ydb.Column{ + { + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UTF8, + }, + }, + }, + }, + []*Ydb.Value{ + { + Value: &Ydb.Value_TextValue{ + TextValue: "test", + }, + }, + }, + )), + dst: [][]interface{}{ + {func(v string) *string { return &v }("")}, + {func(v []byte) *[]byte { return &v }([]byte(""))}, + }, + exp: [][]interface{}{ + {func(v string) *string { return &v }("test")}, + {func(v []byte) *[]byte { return &v }([]byte("test"))}, + }, + }, + { + name: "Ydb.Type_STRING", + s: Indexed(Data( + []*Ydb.Column{ + { + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_STRING, + }, + }, + }, + }, + []*Ydb.Value{ + { + Value: &Ydb.Value_BytesValue{ + BytesValue: []byte("test"), + }, + }, + }, + )), + dst: [][]interface{}{ + {func(v string) *string { return &v }("")}, + {func(v []byte) *[]byte { return &v }([]byte(""))}, + }, + exp: [][]interface{}{ + {func(v string) *string { return &v }("test")}, + {func(v []byte) *[]byte { return &v }([]byte("test"))}, + }, + }, + { + name: "Ydb.Type_UINT64", + s: Indexed(Data( + []*Ydb.Column{ + { + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UINT64, + }, + }, + }, + }, + []*Ydb.Value{ + { + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 123, + }, + }, + }, + )), + dst: [][]interface{}{ + {func(v uint64) *uint64 { return &v }(0)}, + }, + exp: [][]interface{}{ + {func(v uint64) *uint64 { return &v }(123)}, + }, + }, + { + name: "Ydb.Type_INT64", + s: Indexed(Data( + []*Ydb.Column{ + { + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_INT64, + }, + }, + }, + }, + []*Ydb.Value{ + { + Value: &Ydb.Value_Int64Value{ + Int64Value: 123, + }, + }, + }, + )), + dst: [][]interface{}{ + {func(v int64) *int64 { return &v }(0)}, + }, + exp: [][]interface{}{ + {func(v int64) *int64 { return &v }(123)}, + }, + }, + { + name: "Ydb.Type_UINT32", + s: Indexed(Data( + []*Ydb.Column{ + { + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UINT32, + }, + }, + }, + }, + []*Ydb.Value{ + { + Value: &Ydb.Value_Uint32Value{ + Uint32Value: 123, + }, + }, + }, + )), + dst: [][]interface{}{ + {func(v uint64) *uint64 { return &v }(0)}, + {func(v int64) *int64 { return &v }(0)}, + {func(v uint32) *uint32 { return &v }(0)}, + {func(v float64) *float64 { return &v }(0)}, + }, + exp: [][]interface{}{ + {func(v uint64) *uint64 { return &v }(123)}, + {func(v int64) *int64 { return &v }(123)}, + {func(v uint32) *uint32 { return &v }(123)}, + {func(v float64) *float64 { return &v }(123)}, + }, + }, + { + name: "Ydb.Type_INT32", + s: Indexed(Data( + []*Ydb.Column{ + { + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_INT32, + }, + }, + }, + }, + []*Ydb.Value{ + { + Value: &Ydb.Value_Int32Value{ + Int32Value: 123, + }, + }, + }, + )), + dst: [][]interface{}{ + {func(v int64) *int64 { return &v }(0)}, + {func(v int32) *int32 { return &v }(0)}, + {func(v int) *int { return &v }(0)}, + {func(v float32) *float32 { return &v }(0)}, + {func(v float64) *float64 { return &v }(0)}, + }, + exp: [][]interface{}{ + {func(v int64) *int64 { return &v }(123)}, + {func(v int32) *int32 { return &v }(123)}, + {func(v int) *int { return &v }(123)}, + {func(v float32) *float32 { return &v }(123)}, + {func(v float64) *float64 { return &v }(123)}, + }, + }, + { + name: "Ydb.Type_UINT16", + s: Indexed(Data( + []*Ydb.Column{ + { + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UINT16, + }, + }, + }, + }, + []*Ydb.Value{ + { + Value: &Ydb.Value_Uint32Value{ + Uint32Value: 123, + }, + }, + }, + )), + dst: [][]interface{}{ + {func(v uint64) *uint64 { return &v }(0)}, + {func(v int64) *int64 { return &v }(0)}, + {func(v uint32) *uint32 { return &v }(0)}, + {func(v int32) *int32 { return &v }(0)}, + {func(v float32) *float32 { return &v }(0)}, + {func(v float64) *float64 { return &v }(0)}, + }, + exp: [][]interface{}{ + {func(v uint64) *uint64 { return &v }(123)}, + {func(v int64) *int64 { return &v }(123)}, + {func(v uint32) *uint32 { return &v }(123)}, + {func(v int32) *int32 { return &v }(123)}, + {func(v float32) *float32 { return &v }(123)}, + {func(v float64) *float64 { return &v }(123)}, + }, + }, + { + name: "Ydb.Type_INT16", + s: Indexed(Data( + []*Ydb.Column{ + { + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_INT16, + }, + }, + }, + }, + []*Ydb.Value{ + { + Value: &Ydb.Value_Int32Value{ + Int32Value: 123, + }, + }, + }, + )), + dst: [][]interface{}{ + {func(v int64) *int64 { return &v }(0)}, + {func(v int32) *int32 { return &v }(0)}, + {func(v float32) *float32 { return &v }(0)}, + {func(v float64) *float64 { return &v }(0)}, + }, + exp: [][]interface{}{ + {func(v int64) *int64 { return &v }(123)}, + {func(v int32) *int32 { return &v }(123)}, + {func(v float32) *float32 { return &v }(123)}, + {func(v float64) *float64 { return &v }(123)}, + }, + }, + { + name: "Ydb.Type_UINT8", + s: Indexed(Data( + []*Ydb.Column{ + { + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UINT8, + }, + }, + }, + }, + []*Ydb.Value{ + { + Value: &Ydb.Value_Uint32Value{ + Uint32Value: 123, + }, + }, + }, + )), + dst: [][]interface{}{ + {func(v uint64) *uint64 { return &v }(0)}, + {func(v int64) *int64 { return &v }(0)}, + {func(v uint32) *uint32 { return &v }(0)}, + {func(v int32) *int32 { return &v }(0)}, + {func(v uint8) *uint8 { return &v }(0)}, + {func(v float32) *float32 { return &v }(0)}, + {func(v float64) *float64 { return &v }(0)}, + }, + exp: [][]interface{}{ + {func(v uint64) *uint64 { return &v }(123)}, + {func(v int64) *int64 { return &v }(123)}, + {func(v uint32) *uint32 { return &v }(123)}, + {func(v int32) *int32 { return &v }(123)}, + {func(v uint8) *uint8 { return &v }(123)}, + {func(v float32) *float32 { return &v }(123)}, + {func(v float64) *float64 { return &v }(123)}, + }, + }, + { + name: "Ydb.Type_INT8", + s: Indexed(Data( + []*Ydb.Column{ + { + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_INT8, + }, + }, + }, + }, + []*Ydb.Value{ + { + Value: &Ydb.Value_Int32Value{ + Int32Value: 123, + }, + }, + }, + )), + dst: [][]interface{}{ + {func(v int64) *int64 { return &v }(0)}, + {func(v int32) *int32 { return &v }(0)}, + {func(v int8) *int8 { return &v }(0)}, + {func(v float32) *float32 { return &v }(0)}, + {func(v float64) *float64 { return &v }(0)}, + }, + exp: [][]interface{}{ + {func(v int64) *int64 { return &v }(123)}, + {func(v int32) *int32 { return &v }(123)}, + {func(v int8) *int8 { return &v }(123)}, + {func(v float32) *float32 { return &v }(123)}, + {func(v float64) *float64 { return &v }(123)}, + }, + }, + { + name: "Ydb.Type_BOOL", + s: Indexed(Data( + []*Ydb.Column{ + { + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_BOOL, + }, + }, + }, + }, + []*Ydb.Value{ + { + Value: &Ydb.Value_BoolValue{ + BoolValue: true, + }, + }, + }, + )), + dst: [][]interface{}{ + {func(v bool) *bool { return &v }(false)}, + }, + exp: [][]interface{}{ + {func(v bool) *bool { return &v }(true)}, + }, + }, + { + name: "Ydb.Type_DATE", + s: Indexed(Data( + []*Ydb.Column{ + { + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_DATE, + }, + }, + }, + }, + []*Ydb.Value{ + { + Value: &Ydb.Value_Uint32Value{ + Uint32Value: 100500, + }, + }, + }, + )), + dst: [][]interface{}{ + {func(v uint64) *uint64 { return &v }(0)}, + {func(v int64) *int64 { return &v }(0)}, + {func(v int32) *int32 { return &v }(0)}, + {func(v time.Time) *time.Time { return &v }(time.Unix(0, 0))}, + }, + exp: [][]interface{}{ + {func(v uint64) *uint64 { return &v }(100500)}, + {func(v int64) *int64 { return &v }(100500)}, + {func(v int32) *int32 { return &v }(100500)}, + {func(v time.Time) *time.Time { return &v }(time.Unix(8683200000, 0))}, + }, + }, + { + name: "Ydb.Type_DATETIME", + s: Indexed(Data( + []*Ydb.Column{ + { + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_DATETIME, + }, + }, + }, + }, + []*Ydb.Value{ + { + Value: &Ydb.Value_Uint32Value{ + Uint32Value: 100500, + }, + }, + }, + )), + dst: [][]interface{}{ + {func(v uint64) *uint64 { return &v }(0)}, + {func(v int64) *int64 { return &v }(0)}, + {func(v uint32) *uint32 { return &v }(0)}, + {func(v time.Time) *time.Time { return &v }(time.Unix(0, 0))}, + }, + exp: [][]interface{}{ + {func(v uint64) *uint64 { return &v }(100500)}, + {func(v int64) *int64 { return &v }(100500)}, + {func(v uint32) *uint32 { return &v }(100500)}, + {func(v time.Time) *time.Time { return &v }(time.Unix(100500, 0))}, + }, + }, + { + name: "Ydb.Type_TIMESTAMP", + s: Indexed(Data( + []*Ydb.Column{ + { + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_TIMESTAMP, + }, + }, + }, + }, + []*Ydb.Value{ + { + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 12345678987654321, + }, + }, + }, + )), + dst: [][]interface{}{ + {func(v uint64) *uint64 { return &v }(0)}, + {func(v time.Time) *time.Time { return &v }(time.Unix(0, 0))}, + }, + exp: [][]interface{}{ + {func(v uint64) *uint64 { return &v }(12345678987654321)}, + {func(v time.Time) *time.Time { return &v }(time.Unix(12345678987, 654321000))}, + }, + }, + { + name: "Ydb.Type_INTERVAL", + s: Indexed(Data( + []*Ydb.Column{ + { + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_INTERVAL, + }, + }, + }, + }, + []*Ydb.Value{ + { + Value: &Ydb.Value_Int64Value{ + Int64Value: 100500, + }, + }, + }, + )), + dst: [][]interface{}{ + {func(v int64) *int64 { return &v }(0)}, + {func(v time.Duration) *time.Duration { return &v }(time.Duration(0))}, + }, + exp: [][]interface{}{ + {func(v int64) *int64 { return &v }(100500)}, + {func(v time.Duration) *time.Duration { return &v }(time.Duration(100500000))}, + }, + }, + } { + for i := range tt.dst { + t.Run(tt.name+"→"+reflect.TypeOf(tt.dst[i][0]).Elem().String(), func(t *testing.T) { + err := tt.s.Scan(tt.dst[i]...) + require.NoError(t, err) + require.Equal(t, tt.exp[i], tt.dst[i]) + }) + } + } +} + +func TestIndexedIncompatibleColumnsAndDestinations(t *testing.T) { + scanner := &IndexedScanner{data: Data( + []*Ydb.Column{ + { + Name: "a", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UTF8, + }, + }, + }, + }, + []*Ydb.Value{ + { + Value: &Ydb.Value_TextValue{ + TextValue: "test", + }, + }, + }, + )} + var ( + B string + C string + ) + err := scanner.Scan(&B, &C) + require.ErrorIs(t, err, errIncompatibleColumnsAndDestinations) +} + +func TestIndexedCastFailed(t *testing.T) { + scanner := Indexed(Data( + []*Ydb.Column{ + { + Name: "a", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UTF8, + }, + }, + }, + }, + []*Ydb.Value{ + { + Value: &Ydb.Value_TextValue{ + TextValue: "test", + }, + }, + }, + )) + var A uint64 + err := scanner.Scan(&A) + require.ErrorIs(t, err, value.ErrCannotCast) +} diff --git a/internal/query/scanner/named.go b/internal/query/scanner/named.go new file mode 100644 index 000000000..07f31aa94 --- /dev/null +++ b/internal/query/scanner/named.go @@ -0,0 +1,53 @@ +package scanner + +import ( + "fmt" + "reflect" + + "github.com/ydb-platform/ydb-go-sdk/v3/internal/value" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors" +) + +type ( + NamedScanner struct { + data *data + } + NamedDestination struct { + name string + ref interface{} + } +) + +func NamedRef(columnName string, destinationValueReference interface{}) (dst NamedDestination) { + if columnName == "" { + panic("columnName must be not empty") + } + dst.name = columnName + v := reflect.TypeOf(destinationValueReference) + if v.Kind() != reflect.Ptr { + panic(fmt.Errorf("%T is not reference type", destinationValueReference)) + } + dst.ref = destinationValueReference + + return dst +} + +func Named(data *data) NamedScanner { + return NamedScanner{ + data: data, + } +} + +func (s NamedScanner) ScanNamed(dst ...NamedDestination) (err error) { + for i := range dst { + v, err := s.data.seekByName(dst[i].name) + if err != nil { + return xerrors.WithStackTrace(err) + } + if err = value.CastTo(v, dst[i].ref); err != nil { + return xerrors.WithStackTrace(err) + } + } + + return nil +} diff --git a/internal/query/scanner/named_test.go b/internal/query/scanner/named_test.go new file mode 100644 index 000000000..7c3fe9d8b --- /dev/null +++ b/internal/query/scanner/named_test.go @@ -0,0 +1,701 @@ +package scanner + +import ( + "reflect" + "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/value" +) + +func TestNamed(t *testing.T) { + for _, tt := range []struct { + name string + s NamedScanner + dst [][]interface{} + exp [][]interface{} + }{ + { + name: "Ydb.Type_UTF8", + s: Named(Data( + []*Ydb.Column{ + { + Name: "a", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UTF8, + }, + }, + }, + }, + []*Ydb.Value{ + { + Value: &Ydb.Value_TextValue{ + TextValue: "test", + }, + }, + }, + )), + dst: [][]interface{}{ + {func(v string) *string { return &v }("")}, + {func(v []byte) *[]byte { return &v }([]byte(""))}, + }, + exp: [][]interface{}{ + {func(v string) *string { return &v }("test")}, + {func(v []byte) *[]byte { return &v }([]byte("test"))}, + }, + }, + { + name: "Ydb.Type_STRING", + s: Named(Data( + []*Ydb.Column{ + { + Name: "a", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_STRING, + }, + }, + }, + }, + []*Ydb.Value{ + { + Value: &Ydb.Value_BytesValue{ + BytesValue: []byte("test"), + }, + }, + }, + )), + dst: [][]interface{}{ + {func(v string) *string { return &v }("")}, + {func(v []byte) *[]byte { return &v }([]byte(""))}, + }, + exp: [][]interface{}{ + {func(v string) *string { return &v }("test")}, + {func(v []byte) *[]byte { return &v }([]byte("test"))}, + }, + }, + { + name: "Ydb.Type_UINT64", + s: Named(Data( + []*Ydb.Column{ + { + Name: "a", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UINT64, + }, + }, + }, + }, + []*Ydb.Value{ + { + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 123, + }, + }, + }, + )), + dst: [][]interface{}{ + {func(v uint64) *uint64 { return &v }(0)}, + }, + exp: [][]interface{}{ + {func(v uint64) *uint64 { return &v }(123)}, + }, + }, + { + name: "Ydb.Type_INT64", + s: Named(Data( + []*Ydb.Column{ + { + Name: "a", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_INT64, + }, + }, + }, + }, + []*Ydb.Value{ + { + Value: &Ydb.Value_Int64Value{ + Int64Value: 123, + }, + }, + }, + )), + dst: [][]interface{}{ + {func(v int64) *int64 { return &v }(0)}, + }, + exp: [][]interface{}{ + {func(v int64) *int64 { return &v }(123)}, + }, + }, + { + name: "Ydb.Type_UINT32", + s: Named(Data( + []*Ydb.Column{ + { + Name: "a", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UINT32, + }, + }, + }, + }, + []*Ydb.Value{ + { + Value: &Ydb.Value_Uint32Value{ + Uint32Value: 123, + }, + }, + }, + )), + dst: [][]interface{}{ + {func(v uint64) *uint64 { return &v }(0)}, + {func(v int64) *int64 { return &v }(0)}, + {func(v uint32) *uint32 { return &v }(0)}, + {func(v float64) *float64 { return &v }(0)}, + }, + exp: [][]interface{}{ + {func(v uint64) *uint64 { return &v }(123)}, + {func(v int64) *int64 { return &v }(123)}, + {func(v uint32) *uint32 { return &v }(123)}, + {func(v float64) *float64 { return &v }(123)}, + }, + }, + { + name: "Ydb.Type_INT32", + s: Named(Data( + []*Ydb.Column{ + { + Name: "a", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_INT32, + }, + }, + }, + }, + []*Ydb.Value{ + { + Value: &Ydb.Value_Int32Value{ + Int32Value: 123, + }, + }, + }, + )), + dst: [][]interface{}{ + {func(v int64) *int64 { return &v }(0)}, + {func(v int32) *int32 { return &v }(0)}, + {func(v int) *int { return &v }(0)}, + {func(v float32) *float32 { return &v }(0)}, + {func(v float64) *float64 { return &v }(0)}, + }, + exp: [][]interface{}{ + {func(v int64) *int64 { return &v }(123)}, + {func(v int32) *int32 { return &v }(123)}, + {func(v int) *int { return &v }(123)}, + {func(v float32) *float32 { return &v }(123)}, + {func(v float64) *float64 { return &v }(123)}, + }, + }, + { + name: "Ydb.Type_UINT16", + s: Named(Data( + []*Ydb.Column{ + { + Name: "a", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UINT16, + }, + }, + }, + }, + []*Ydb.Value{ + { + Value: &Ydb.Value_Uint32Value{ + Uint32Value: 123, + }, + }, + }, + )), + dst: [][]interface{}{ + {func(v uint64) *uint64 { return &v }(0)}, + {func(v int64) *int64 { return &v }(0)}, + {func(v uint32) *uint32 { return &v }(0)}, + {func(v int32) *int32 { return &v }(0)}, + {func(v float32) *float32 { return &v }(0)}, + {func(v float64) *float64 { return &v }(0)}, + }, + exp: [][]interface{}{ + {func(v uint64) *uint64 { return &v }(123)}, + {func(v int64) *int64 { return &v }(123)}, + {func(v uint32) *uint32 { return &v }(123)}, + {func(v int32) *int32 { return &v }(123)}, + {func(v float32) *float32 { return &v }(123)}, + {func(v float64) *float64 { return &v }(123)}, + }, + }, + { + name: "Ydb.Type_INT16", + s: Named(Data( + []*Ydb.Column{ + { + Name: "a", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_INT16, + }, + }, + }, + }, + []*Ydb.Value{ + { + Value: &Ydb.Value_Int32Value{ + Int32Value: 123, + }, + }, + }, + )), + dst: [][]interface{}{ + {func(v int64) *int64 { return &v }(0)}, + {func(v int32) *int32 { return &v }(0)}, + {func(v float32) *float32 { return &v }(0)}, + {func(v float64) *float64 { return &v }(0)}, + }, + exp: [][]interface{}{ + {func(v int64) *int64 { return &v }(123)}, + {func(v int32) *int32 { return &v }(123)}, + {func(v float32) *float32 { return &v }(123)}, + {func(v float64) *float64 { return &v }(123)}, + }, + }, + { + name: "Ydb.Type_UINT8", + s: Named(Data( + []*Ydb.Column{ + { + Name: "a", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UINT8, + }, + }, + }, + }, + []*Ydb.Value{ + { + Value: &Ydb.Value_Uint32Value{ + Uint32Value: 123, + }, + }, + }, + )), + dst: [][]interface{}{ + {func(v uint64) *uint64 { return &v }(0)}, + {func(v int64) *int64 { return &v }(0)}, + {func(v uint32) *uint32 { return &v }(0)}, + {func(v int32) *int32 { return &v }(0)}, + {func(v uint8) *uint8 { return &v }(0)}, + {func(v float32) *float32 { return &v }(0)}, + {func(v float64) *float64 { return &v }(0)}, + }, + exp: [][]interface{}{ + {func(v uint64) *uint64 { return &v }(123)}, + {func(v int64) *int64 { return &v }(123)}, + {func(v uint32) *uint32 { return &v }(123)}, + {func(v int32) *int32 { return &v }(123)}, + {func(v uint8) *uint8 { return &v }(123)}, + {func(v float32) *float32 { return &v }(123)}, + {func(v float64) *float64 { return &v }(123)}, + }, + }, + { + name: "Ydb.Type_INT8", + s: Named(Data( + []*Ydb.Column{ + { + Name: "a", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_INT8, + }, + }, + }, + }, + []*Ydb.Value{ + { + Value: &Ydb.Value_Int32Value{ + Int32Value: 123, + }, + }, + }, + )), + dst: [][]interface{}{ + {func(v int64) *int64 { return &v }(0)}, + {func(v int32) *int32 { return &v }(0)}, + {func(v int8) *int8 { return &v }(0)}, + {func(v float32) *float32 { return &v }(0)}, + {func(v float64) *float64 { return &v }(0)}, + }, + exp: [][]interface{}{ + {func(v int64) *int64 { return &v }(123)}, + {func(v int32) *int32 { return &v }(123)}, + {func(v int8) *int8 { return &v }(123)}, + {func(v float32) *float32 { return &v }(123)}, + {func(v float64) *float64 { return &v }(123)}, + }, + }, + { + name: "Ydb.Type_BOOL", + s: Named(Data( + []*Ydb.Column{ + { + Name: "a", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_BOOL, + }, + }, + }, + }, + []*Ydb.Value{ + { + Value: &Ydb.Value_BoolValue{ + BoolValue: true, + }, + }, + }, + )), + dst: [][]interface{}{ + {func(v bool) *bool { return &v }(false)}, + }, + exp: [][]interface{}{ + {func(v bool) *bool { return &v }(true)}, + }, + }, + { + name: "Ydb.Type_DATE", + s: Named(Data( + []*Ydb.Column{ + { + Name: "a", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_DATE, + }, + }, + }, + }, + []*Ydb.Value{ + { + Value: &Ydb.Value_Uint32Value{ + Uint32Value: 100500, + }, + }, + }, + )), + dst: [][]interface{}{ + {func(v uint64) *uint64 { return &v }(0)}, + {func(v int64) *int64 { return &v }(0)}, + {func(v int32) *int32 { return &v }(0)}, + {func(v time.Time) *time.Time { return &v }(time.Unix(0, 0))}, + }, + exp: [][]interface{}{ + {func(v uint64) *uint64 { return &v }(100500)}, + {func(v int64) *int64 { return &v }(100500)}, + {func(v int32) *int32 { return &v }(100500)}, + {func(v time.Time) *time.Time { return &v }(time.Unix(8683200000, 0))}, + }, + }, + { + name: "Ydb.Type_DATETIME", + s: Named(Data( + []*Ydb.Column{ + { + Name: "a", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_DATETIME, + }, + }, + }, + }, + []*Ydb.Value{ + { + Value: &Ydb.Value_Uint32Value{ + Uint32Value: 100500, + }, + }, + }, + )), + dst: [][]interface{}{ + {func(v uint64) *uint64 { return &v }(0)}, + {func(v int64) *int64 { return &v }(0)}, + {func(v uint32) *uint32 { return &v }(0)}, + {func(v time.Time) *time.Time { return &v }(time.Unix(0, 0))}, + }, + exp: [][]interface{}{ + {func(v uint64) *uint64 { return &v }(100500)}, + {func(v int64) *int64 { return &v }(100500)}, + {func(v uint32) *uint32 { return &v }(100500)}, + {func(v time.Time) *time.Time { return &v }(time.Unix(100500, 0))}, + }, + }, + { + name: "Ydb.Type_TIMESTAMP", + s: Named(Data( + []*Ydb.Column{ + { + Name: "a", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_TIMESTAMP, + }, + }, + }, + }, + []*Ydb.Value{ + { + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 12345678987654321, + }, + }, + }, + )), + dst: [][]interface{}{ + {func(v uint64) *uint64 { return &v }(0)}, + {func(v time.Time) *time.Time { return &v }(time.Unix(0, 0))}, + }, + exp: [][]interface{}{ + {func(v uint64) *uint64 { return &v }(12345678987654321)}, + {func(v time.Time) *time.Time { return &v }(time.Unix(12345678987, 654321000))}, + }, + }, + { + name: "Ydb.Type_INTERVAL", + s: Named(Data( + []*Ydb.Column{ + { + Name: "a", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_INTERVAL, + }, + }, + }, + }, + []*Ydb.Value{ + { + Value: &Ydb.Value_Int64Value{ + Int64Value: 100500, + }, + }, + }, + )), + dst: [][]interface{}{ + {func(v int64) *int64 { return &v }(0)}, + {func(v time.Duration) *time.Duration { return &v }(time.Duration(0))}, + }, + exp: [][]interface{}{ + {func(v int64) *int64 { return &v }(100500)}, + {func(v time.Duration) *time.Duration { return &v }(time.Duration(100500000))}, + }, + }, + } { + for i := range tt.dst { + t.Run(tt.name+"→"+reflect.TypeOf(tt.dst[i][0]).Elem().String(), func(t *testing.T) { + err := tt.s.ScanNamed(func() (dst []NamedDestination) { + for j := range tt.dst[i] { + dst = append(dst, NamedRef("a", tt.dst[i][j])) + } + + return dst + }()...) + require.NoError(t, err) + require.Equal(t, tt.exp[i], tt.dst[i]) + }) + } + } +} + +func TestScannerNamedNotFoundByName(t *testing.T) { + scanner := Named(Data( + []*Ydb.Column{ + { + Name: "a", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UTF8, + }, + }, + }, + }, + []*Ydb.Value{ + { + Value: &Ydb.Value_TextValue{ + TextValue: "test", + }, + }, + }, + )) + var s string + err := scanner.ScanNamed(NamedRef("b", &s)) + require.ErrorIs(t, err, errColumnsNotFoundInRow) +} + +func TestScannerNamedOrdering(t *testing.T) { + scanner := Named(Data( + []*Ydb.Column{ + { + Name: "a", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UTF8, + }, + }, + }, + { + Name: "b", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UTF8, + }, + }, + }, + { + Name: "c", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UTF8, + }, + }, + }, + }, + []*Ydb.Value{ + { + Value: &Ydb.Value_TextValue{ + TextValue: "A", + }, + }, + { + Value: &Ydb.Value_TextValue{ + TextValue: "B", + }, + }, + { + Value: &Ydb.Value_TextValue{ + TextValue: "C", + }, + }, + }, + )) + var a, b, c string + err := scanner.ScanNamed( + NamedRef("c", &c), + NamedRef("b", &b), + NamedRef("a", &a), + ) + require.NoError(t, err) + require.Equal(t, "A", a) + require.Equal(t, "B", b) + require.Equal(t, "C", c) +} + +func TestNamedRef(t *testing.T) { + for _, tt := range []struct { + name string + ref interface{} + dst NamedDestination + panic bool + }{ + { + name: "", + ref: nil, + dst: NamedDestination{}, + panic: true, + }, + { + name: "nil_ref", + ref: nil, + dst: NamedDestination{}, + panic: true, + }, + { + name: "not_ref", + ref: 123, + dst: NamedDestination{}, + panic: true, + }, + { + name: "int_ptr", + ref: func(v int) *int { return &v }(123), + dst: NamedDestination{ + name: "int_ptr", + ref: func(v int) *int { return &v }(123), + }, + panic: false, + }, + { + name: "int_dbl_ptr", + ref: func(v int) **int { + vv := &v + + return &vv + }(123), + dst: NamedDestination{ + name: "int_dbl_ptr", + ref: func(v int) **int { + vv := &v + + return &vv + }(123), + }, + panic: false, + }, + } { + t.Run(tt.name, func(t *testing.T) { + if tt.panic { + defer func() { + require.NotNil(t, recover()) + }() + } else { + defer func() { + require.Nil(t, recover()) + }() + } + require.Equal(t, tt.dst, NamedRef(tt.name, tt.ref)) + }) + } +} + +func TestNamedCastFailed(t *testing.T) { + scanner := Named(Data( + []*Ydb.Column{ + { + Name: "a", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UTF8, + }, + }, + }, + }, + []*Ydb.Value{ + { + Value: &Ydb.Value_TextValue{ + TextValue: "test", + }, + }, + }, + )) + var A uint64 + err := scanner.ScanNamed(NamedRef("a", &A)) + require.ErrorIs(t, err, value.ErrCannotCast) +} diff --git a/internal/query/scanner/struct.go b/internal/query/scanner/struct.go new file mode 100644 index 000000000..5f73ff095 --- /dev/null +++ b/internal/query/scanner/struct.go @@ -0,0 +1,91 @@ +package scanner + +import ( + "fmt" + "reflect" + "strings" + + "github.com/ydb-platform/ydb-go-sdk/v3/internal/value" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors" +) + +type scanStructSettings struct { + TagName string + AllowMissingColumnsFromSelect bool + AllowMissingFieldsInStruct bool +} + +type StructScanner struct { + data *data +} + +func Struct(data *data) StructScanner { + return StructScanner{ + data: data, + } +} + +func fieldName(f reflect.StructField, tagName string) string { //nolint:gocritic + if name, has := f.Tag.Lookup(tagName); has { + return name + } + + return f.Name +} + +func (s StructScanner) ScanStruct(dst interface{}, opts ...ScanStructOption) (err error) { + settings := scanStructSettings{ + TagName: "sql", + AllowMissingColumnsFromSelect: false, + AllowMissingFieldsInStruct: false, + } + for _, opt := range opts { + if opt != nil { + opt.applyScanStructOption(&settings) + } + } + ptr := reflect.ValueOf(dst) + if ptr.Kind() != reflect.Pointer { + return xerrors.WithStackTrace(fmt.Errorf("%w: '%s'", errDstTypeIsNotAPointer, ptr.Kind().String())) + } + if ptr.Elem().Kind() != reflect.Struct { + return xerrors.WithStackTrace(fmt.Errorf("%w: '%s'", errDstTypeIsNotAPointerToStruct, ptr.Elem().Kind().String())) + } + tt := ptr.Elem().Type() + missingColumns := make([]string, 0, len(s.data.columns)) + existingFields := make(map[string]struct{}, tt.NumField()) + for i := 0; i < tt.NumField(); i++ { + name := fieldName(tt.Field(i), settings.TagName) + v, err := s.data.seekByName(name) + if err != nil { + missingColumns = append(missingColumns, name) + } else { + if err = value.CastTo(v, ptr.Elem().Field(i).Addr().Interface()); err != nil { + return xerrors.WithStackTrace(err) + } + existingFields[name] = struct{}{} + } + } + + if !settings.AllowMissingColumnsFromSelect && len(missingColumns) > 0 { + return xerrors.WithStackTrace( + fmt.Errorf("%w: '%v'", errColumnsNotFoundInRow, strings.Join(missingColumns, "','")), + ) + } + + if !settings.AllowMissingFieldsInStruct { + missingFields := make([]string, 0, tt.NumField()) + for _, c := range s.data.columns { + if _, has := existingFields[c.GetName()]; !has { + missingFields = append(missingFields, c.GetName()) + } + } + if len(missingFields) > 0 { + return xerrors.WithStackTrace( + fmt.Errorf("%w: '%v'", errFieldsNotFoundInStruct, strings.Join(missingFields, "','")), + ) + } + } + + return nil +} diff --git a/internal/query/scanner/struct_options.go b/internal/query/scanner/struct_options.go new file mode 100644 index 000000000..3146f6750 --- /dev/null +++ b/internal/query/scanner/struct_options.go @@ -0,0 +1,40 @@ +package scanner + +type ( + ScanStructOption interface { + applyScanStructOption(settings *scanStructSettings) + } + tagName string + allowMissingColumnsFromSelect struct{} + allowMissingFieldsInStruct struct{} +) + +var ( + _ ScanStructOption = tagName("") + _ ScanStructOption = allowMissingColumnsFromSelect{} + _ ScanStructOption = allowMissingFieldsInStruct{} +) + +func (allowMissingFieldsInStruct) applyScanStructOption(settings *scanStructSettings) { + settings.AllowMissingFieldsInStruct = true +} + +func (allowMissingColumnsFromSelect) applyScanStructOption(settings *scanStructSettings) { + settings.AllowMissingColumnsFromSelect = true +} + +func (name tagName) applyScanStructOption(settings *scanStructSettings) { + settings.TagName = string(name) +} + +func WithTagName(name string) tagName { + return tagName(name) +} + +func WithAllowMissingColumnsFromSelect() allowMissingColumnsFromSelect { + return allowMissingColumnsFromSelect{} +} + +func WithAllowMissingFieldsInStruct() allowMissingFieldsInStruct { + return allowMissingFieldsInStruct{} +} diff --git a/internal/query/scanner/struct_test.go b/internal/query/scanner/struct_test.go new file mode 100644 index 000000000..d6d1918ea --- /dev/null +++ b/internal/query/scanner/struct_test.go @@ -0,0 +1,853 @@ +package scanner + +import ( + "reflect" + "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/value" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/xtest" +) + +func TestFieldName(t *testing.T) { + for _, tt := range []struct { + name string + in interface{} + out string + }{ + { + name: xtest.CurrentFileLine(), + in: struct { + Col0 string + }{}, + out: "Col0", + }, + { + name: xtest.CurrentFileLine(), + in: struct { + Col0 string `sql:"col0"` + }{}, + out: "col0", + }, + } { + t.Run(tt.name, func(t *testing.T) { + require.Equal(t, tt.out, fieldName(reflect.ValueOf(tt.in).Type().Field(0), "sql")) + }) + } +} + +func TestStruct(t *testing.T) { + newScannerData := func(mapping map[*Ydb.Column]*Ydb.Value) *data { + data := &data{ + columns: make([]*Ydb.Column, 0, len(mapping)), + values: make([]*Ydb.Value, 0, len(mapping)), + } + for c, v := range mapping { + data.columns = append(data.columns, c) + data.values = append(data.values, v) + } + + return data + } + + type scanData struct { //nolint:maligned + Utf8String string + Utf8Bytes []byte + StringString string + StringBytes []byte + Uint64Uint64 uint64 + Int64Int64 int64 + Uint32Uint64 uint64 + Uint32Int64 int64 + Uint32Uint32 uint32 + Int32Int64 int64 + Int32Int32 int32 + Uint16Uint64 uint64 + Uint16Int64 int64 + Uint16Uint32 uint32 + Uint16Int32 int32 + Uint16Uint16 uint16 + Int16Int64 int64 + Int16Int32 int32 + Uint8Uint64 uint64 + Uint8Int64 int64 + Uint8Uint32 uint32 + Uint8Int32 int32 + Uint8Uint16 uint16 + Int8Int64 int64 + Int8Int32 int32 + Int8Int16 int16 + BoolBool bool + DateTime time.Time + DatetimeTime time.Time + TimestampTime time.Time + } + var dst scanData + err := Struct(newScannerData(map[*Ydb.Column]*Ydb.Value{ + { + Name: "Utf8String", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UTF8, + }, + }, + }: { + Value: &Ydb.Value_TextValue{ + TextValue: "A", + }, + }, + { + Name: "Utf8Bytes", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UTF8, + }, + }, + }: { + Value: &Ydb.Value_TextValue{ + TextValue: "A", + }, + }, + { + Name: "StringString", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_STRING, + }, + }, + }: { + Value: &Ydb.Value_BytesValue{ + BytesValue: []byte("A"), + }, + }, + { + Name: "StringBytes", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_STRING, + }, + }, + }: { + Value: &Ydb.Value_BytesValue{ + BytesValue: []byte("A"), + }, + }, + { + Name: "Uint64Uint64", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UINT64, + }, + }, + }: { + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 123, + }, + }, + { + Name: "Int64Int64", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_INT64, + }, + }, + }: { + Value: &Ydb.Value_Int64Value{ + Int64Value: 123, + }, + }, + { + Name: "Uint32Uint64", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UINT32, + }, + }, + }: { + Value: &Ydb.Value_Uint32Value{ + Uint32Value: 123, + }, + }, + { + Name: "Uint32Int64", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UINT32, + }, + }, + }: { + Value: &Ydb.Value_Uint32Value{ + Uint32Value: 123, + }, + }, + { + Name: "Uint32Uint32", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UINT32, + }, + }, + }: { + Value: &Ydb.Value_Uint32Value{ + Uint32Value: 123, + }, + }, + { + Name: "Int32Int64", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_INT32, + }, + }, + }: { + Value: &Ydb.Value_Int32Value{ + Int32Value: 123, + }, + }, + { + Name: "Int32Int32", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_INT32, + }, + }, + }: { + Value: &Ydb.Value_Int32Value{ + Int32Value: 123, + }, + }, + { + Name: "Uint16Uint64", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UINT16, + }, + }, + }: { + Value: &Ydb.Value_Uint32Value{ + Uint32Value: 123, + }, + }, + { + Name: "Uint16Int64", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UINT16, + }, + }, + }: { + Value: &Ydb.Value_Uint32Value{ + Uint32Value: 123, + }, + }, + { + Name: "Uint16Uint32", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UINT16, + }, + }, + }: { + Value: &Ydb.Value_Uint32Value{ + Uint32Value: 123, + }, + }, + { + Name: "Uint16Int32", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UINT16, + }, + }, + }: { + Value: &Ydb.Value_Uint32Value{ + Uint32Value: 123, + }, + }, + { + Name: "Uint16Uint16", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UINT16, + }, + }, + }: { + Value: &Ydb.Value_Uint32Value{ + Uint32Value: 123, + }, + }, + { + Name: "Int16Int64", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_INT16, + }, + }, + }: { + Value: &Ydb.Value_Int32Value{ + Int32Value: 123, + }, + }, + { + Name: "Int16Int32", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_INT16, + }, + }, + }: { + Value: &Ydb.Value_Int32Value{ + Int32Value: 123, + }, + }, + { + Name: "Uint8Uint64", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UINT16, + }, + }, + }: { + Value: &Ydb.Value_Uint32Value{ + Uint32Value: 123, + }, + }, + { + Name: "Uint8Int64", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UINT16, + }, + }, + }: { + Value: &Ydb.Value_Uint32Value{ + Uint32Value: 123, + }, + }, + { + Name: "Uint8Uint32", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UINT16, + }, + }, + }: { + Value: &Ydb.Value_Uint32Value{ + Uint32Value: 123, + }, + }, + { + Name: "Uint8Int32", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UINT16, + }, + }, + }: { + Value: &Ydb.Value_Uint32Value{ + Uint32Value: 123, + }, + }, + { + Name: "Uint8Uint16", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UINT16, + }, + }, + }: { + Value: &Ydb.Value_Uint32Value{ + Uint32Value: 123, + }, + }, + { + Name: "Int8Int64", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_INT16, + }, + }, + }: { + Value: &Ydb.Value_Int32Value{ + Int32Value: 123, + }, + }, + { + Name: "Int8Int32", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_INT16, + }, + }, + }: { + Value: &Ydb.Value_Int32Value{ + Int32Value: 123, + }, + }, + { + Name: "Int8Int16", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_INT16, + }, + }, + }: { + Value: &Ydb.Value_Int32Value{ + Int32Value: 123, + }, + }, + { + Name: "BoolBool", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_BOOL, + }, + }, + }: { + Value: &Ydb.Value_BoolValue{ + BoolValue: true, + }, + }, + { + Name: "DateTime", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_DATE, + }, + }, + }: { + Value: &Ydb.Value_Uint32Value{ + Uint32Value: 100500, + }, + }, + { + Name: "DatetimeTime", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_DATETIME, + }, + }, + }: { + Value: &Ydb.Value_Uint32Value{ + Uint32Value: 100500, + }, + }, + { + Name: "TimestampTime", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_TIMESTAMP, + }, + }, + }: { + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 12345678987654321, + }, + }, + })).ScanStruct(&dst) + require.NoError(t, err) + require.Equal(t, scanData{ + Utf8String: "A", + Utf8Bytes: []byte("A"), + StringString: "A", + StringBytes: []byte("A"), + Uint64Uint64: 123, + Int64Int64: 123, + Uint32Uint64: 123, + Uint32Int64: 123, + Uint32Uint32: 123, + Int32Int64: 123, + Int32Int32: 123, + Uint16Uint64: 123, + Uint16Int64: 123, + Uint16Uint32: 123, + Uint16Int32: 123, + Uint16Uint16: 123, + Int16Int64: 123, + Int16Int32: 123, + Uint8Uint64: 123, + Uint8Int64: 123, + Uint8Uint32: 123, + Uint8Int32: 123, + Uint8Uint16: 123, + Int8Int64: 123, + Int8Int32: 123, + Int8Int16: 123, + BoolBool: true, + DateTime: time.Unix(8683200000, 0), + DatetimeTime: time.Unix(100500, 0), + TimestampTime: time.Unix(12345678987, 654321000), + }, dst) +} + +func TestStructNotAPointer(t *testing.T) { + scanner := Struct(Data( + []*Ydb.Column{ + { + Name: "a", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UTF8, + }, + }, + }, + }, + []*Ydb.Value{ + { + Value: &Ydb.Value_TextValue{ + TextValue: "test", + }, + }, + }, + )) + var row struct { + B string + C string + } + err := scanner.ScanStruct(row) + require.ErrorIs(t, err, errDstTypeIsNotAPointer) +} + +func TestStructNotAPointerToStruct(t *testing.T) { + scanner := Struct(Data( + []*Ydb.Column{ + { + Name: "a", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UTF8, + }, + }, + }, + }, + []*Ydb.Value{ + { + Value: &Ydb.Value_TextValue{ + TextValue: "test", + }, + }, + }, + )) + var row string + err := scanner.ScanStruct(&row) + require.ErrorIs(t, err, errDstTypeIsNotAPointerToStruct) +} + +func TestStructCastFailed(t *testing.T) { + scanner := Struct(Data( + []*Ydb.Column{ + { + Name: "A", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UTF8, + }, + }, + }, + }, + []*Ydb.Value{ + { + Value: &Ydb.Value_TextValue{ + TextValue: "test", + }, + }, + }, + )) + var row struct { + A uint64 + } + err := scanner.ScanStruct(&row) + require.ErrorIs(t, err, value.ErrCannotCast) +} + +func TestStructNotFoundColumns(t *testing.T) { + scanner := Struct(Data( + []*Ydb.Column{ + { + Name: "a", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UTF8, + }, + }, + }, + }, + []*Ydb.Value{ + { + Value: &Ydb.Value_TextValue{ + TextValue: "test", + }, + }, + }, + )) + var row struct { + B string + C string + } + err := scanner.ScanStruct(&row) + require.ErrorIs(t, err, errColumnsNotFoundInRow) +} + +func TestStructWithAllowMissingColumnsFromSelect(t *testing.T) { + scanner := Struct(Data( + []*Ydb.Column{ + { + Name: "A", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UTF8, + }, + }, + }, + }, + []*Ydb.Value{ + { + Value: &Ydb.Value_TextValue{ + TextValue: "test", + }, + }, + }, + )) + var row struct { + A string + B string + C string + } + err := scanner.ScanStruct(&row, + WithAllowMissingColumnsFromSelect(), + ) + require.NoError(t, err) + require.Equal(t, "test", row.A) + require.Equal(t, "", row.B) + require.Equal(t, "", row.C) +} + +func TestStructNotFoundFields(t *testing.T) { + scanner := Struct(Data( + []*Ydb.Column{ + { + Name: "A", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UTF8, + }, + }, + }, + { + Name: "B", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UTF8, + }, + }, + }, + { + Name: "C", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UTF8, + }, + }, + }, + }, + []*Ydb.Value{ + { + Value: &Ydb.Value_TextValue{ + TextValue: "test", + }, + }, + { + Value: &Ydb.Value_TextValue{ + TextValue: "test", + }, + }, + { + Value: &Ydb.Value_TextValue{ + TextValue: "test", + }, + }, + }, + )) + var row struct { + A string + } + err := scanner.ScanStruct(&row) + require.ErrorIs(t, err, errFieldsNotFoundInStruct) +} + +func TestStructWithAllowMissingFieldsInStruct(t *testing.T) { + scanner := Struct(Data( + []*Ydb.Column{ + { + Name: "A", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UTF8, + }, + }, + }, + { + Name: "B", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UTF8, + }, + }, + }, + { + Name: "C", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UTF8, + }, + }, + }, + }, + []*Ydb.Value{ + { + Value: &Ydb.Value_TextValue{ + TextValue: "test", + }, + }, + { + Value: &Ydb.Value_TextValue{ + TextValue: "test", + }, + }, + { + Value: &Ydb.Value_TextValue{ + TextValue: "test", + }, + }, + }, + )) + var row struct { + A string + } + err := scanner.ScanStruct(&row, + WithAllowMissingFieldsInStruct(), + ) + require.NoError(t, err) + require.Equal(t, "test", row.A) +} + +func TestStructWithTagName(t *testing.T) { + scanner := Struct(Data( + []*Ydb.Column{ + { + Name: "A", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UTF8, + }, + }, + }, + { + Name: "B", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UTF8, + }, + }, + }, + { + Name: "C", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UTF8, + }, + }, + }, + }, + []*Ydb.Value{ + { + Value: &Ydb.Value_TextValue{ + TextValue: "AA", + }, + }, + { + Value: &Ydb.Value_TextValue{ + TextValue: "BB", + }, + }, + { + Value: &Ydb.Value_TextValue{ + TextValue: "CC", + }, + }, + }, + )) + var row struct { + A string `test:"A"` + B string `test:"B"` + C string `test:"C"` + } + err := scanner.ScanStruct(&row, + WithTagName("test"), + ) + require.NoError(t, err) + require.Equal(t, "AA", row.A) + require.Equal(t, "BB", row.B) + require.Equal(t, "CC", row.C) +} + +func TestScannerStructOrdering(t *testing.T) { + scanner := Struct(Data( + []*Ydb.Column{ + { + Name: "B", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UTF8, + }, + }, + }, + { + Name: "A", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UTF8, + }, + }, + }, + { + Name: "C", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UTF8, + }, + }, + }, + }, + []*Ydb.Value{ + { + Value: &Ydb.Value_TextValue{ + TextValue: "B", + }, + }, + { + Value: &Ydb.Value_TextValue{ + TextValue: "A", + }, + }, + { + Value: &Ydb.Value_TextValue{ + TextValue: "C", + }, + }, + }, + )) + var row struct { + A string + B string + C string + } + err := scanner.ScanStruct(&row) + require.NoError(t, err) + require.Equal(t, "A", row.A) + require.Equal(t, "B", row.B) + require.Equal(t, "C", row.C) +} diff --git a/internal/query/session.go b/internal/query/session.go new file mode 100644 index 000000000..708b36df6 --- /dev/null +++ b/internal/query/session.go @@ -0,0 +1,287 @@ +package query + +import ( + "context" + "io" + "sync/atomic" + + "github.com/ydb-platform/ydb-go-genproto/Ydb_Query_V1" + "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" + "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/query" + "github.com/ydb-platform/ydb-go-sdk/v3/trace" +) + +var _ query.Session = (*Session)(nil) + +type ( + Session struct { + cfg *config.Config + id string + nodeID int64 + grpcClient Ydb_Query_V1.QueryServiceClient + statusCode statusCode + closeOnce func(ctx context.Context) error + checks []func(s *Session) bool + } + sessionOption func(session *Session) +) + +func withSessionCheck(f func(*Session) bool) sessionOption { + return func(s *Session) { + s.checks = append(s.checks, f) + } +} + +func createSession( + 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, + checks: []func(*Session) bool{ + func(s *Session) bool { + switch s.status() { + case statusIdle, statusInUse: + return true + default: + return false + } + }, + }, + } + defer func() { + if finalErr != nil && s != nil { + s.setStatus(statusError) + } + }() + + for _, opt := range opts { + opt(s) + } + + 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(err) + } + + defer func() { + if finalErr != nil { + _ = deleteSession(ctx, client, response.GetSessionId()) + } + }() + + s.id = response.GetSessionId() + s.nodeID = response.GetNodeId() + + err = s.attach(ctx) + if err != nil { + return nil, xerrors.WithStackTrace(err) + } + + s.setStatus(statusIdle) + + return s, nil +} + +func (s *Session) attach(ctx context.Context) (finalErr error) { + 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.ValueOnly(ctx)) + + attach, err := s.grpcClient.AttachSession(attachCtx, &Ydb_Query.AttachSessionRequest{ + SessionId: s.id, + }) + if err != nil { + return xerrors.WithStackTrace(err) + } + + _, err = attach.Recv() + if err != nil { + cancelAttach() + + return xerrors.WithStackTrace(err) + } + + s.closeOnce = xsync.OnceFunc(func(ctx context.Context) (err error) { + 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) + } + + return nil + }) + + go func() { + defer func() { + _ = s.closeOnce(xcontext.ValueOnly(ctx)) + }() + + for { + if !s.IsAlive() { + return + } + _, recvErr := attach.Recv() + if recvErr != nil { + if xerrors.Is(recvErr, io.EOF) { + s.setStatus(statusClosed) + } else { + s.setStatus(statusError) + } + + return + } + } + }() + + return nil +} + +func deleteSession(ctx context.Context, client Ydb_Query_V1.QueryServiceClient, sessionID string) error { + _, err := client.DeleteSession(ctx, + &Ydb_Query.DeleteSessionRequest{ + SessionId: sessionID, + }, + ) + if err != nil { + return xerrors.WithStackTrace(err) + } + + return nil +} + +func (s *Session) IsAlive() bool { + for _, check := range s.checks { + if !check(s) { + return false + } + } + + return true +} + +func (s *Session) Close(ctx context.Context) (err error) { + 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) + }() + + if s.closeOnce != nil { + return s.closeOnce(ctx) + } + + return nil +} + +func begin( + ctx context.Context, + client Ydb_Query_V1.QueryServiceClient, + s *Session, + txSettings query.TransactionSettings, +) (*transaction, error) { + a := allocator.New() + defer a.Free() + response, err := client.BeginTransaction(ctx, + &Ydb_Query.BeginTransactionRequest{ + SessionId: s.id, + TxSettings: txSettings.ToYDB(a), + }, + ) + if err != nil { + return nil, xerrors.WithStackTrace(err) + } + + return newTransaction(response.GetTxMeta().GetId(), s), nil +} + +func (s *Session) Begin( + ctx context.Context, + txSettings query.TransactionSettings, +) ( + _ query.Transaction, err error, +) { + var tx *transaction + + 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) + }() + + tx, err = begin(ctx, s.grpcClient, s, txSettings) + if err != nil { + return nil, xerrors.WithStackTrace(err) + } + tx.s = s + + return tx, nil +} + +func (s *Session) ID() string { + return s.id +} + +func (s *Session) NodeID() int64 { + return s.nodeID +} + +func (s *Session) status() statusCode { + return statusCode(atomic.LoadUint32((*uint32)(&s.statusCode))) +} + +func (s *Session) setStatus(code statusCode) { + atomic.StoreUint32((*uint32)(&s.statusCode), uint32(code)) +} + +func (s *Session) Status() string { + return s.status().String() +} + +func (s *Session) Execute( + ctx context.Context, q string, opts ...options.ExecuteOption, +) (_ query.Transaction, _ query.Result, err error) { + 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) + }() + + tx, r, err := execute(ctx, s, s.grpcClient, q, options.ExecuteSettings(opts...)) + if err != nil { + return nil, nil, xerrors.WithStackTrace(err) + } + + return tx, r, nil +} diff --git a/internal/query/session_status.go b/internal/query/session_status.go new file mode 100644 index 000000000..134ade32d --- /dev/null +++ b/internal/query/session_status.go @@ -0,0 +1,37 @@ +package query + +import ( + "fmt" + + "github.com/ydb-platform/ydb-go-sdk/v3/internal/session" +) + +type statusCode uint32 + +const ( + statusUnknown = statusCode(iota) + statusIdle + statusInUse + statusClosing + statusClosed + statusError +) + +func (s statusCode) String() string { + switch s { + case statusUnknown: + return session.StatusUnknown + case statusIdle: + return session.StatusIdle + case statusInUse: + return session.StatusInUse + case statusClosing: + return session.StatusClosing + case statusClosed: + return session.StatusClosed + case statusError: + return session.StatusError + default: + return fmt.Sprintf("Unknown%d", s) + } +} diff --git a/internal/query/session_test.go b/internal/query/session_test.go new file mode 100644 index 000000000..b75faf5d7 --- /dev/null +++ b/internal/query/session_test.go @@ -0,0 +1,56 @@ +package query + +import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/ydb-platform/ydb-go-genproto/protos/Ydb" + "github.com/ydb-platform/ydb-go-genproto/protos/Ydb_Query" + "go.uber.org/mock/gomock" + 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" + "github.com/ydb-platform/ydb-go-sdk/v3/query" +) + +func TestBegin(t *testing.T) { + t.Run("HappyWay", func(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_SUCCESS, + TxMeta: &Ydb_Query.TransactionMeta{ + Id: "123", + }, + }, nil) + t.Log("begin") + tx, err := begin(ctx, service, &Session{id: "123"}, query.TxSettings()) + require.NoError(t, err) + require.Equal(t, "123", tx.id) + }) + t.Run("TransportError", func(t *testing.T) { + ctx := xtest.Context(t) + ctrl := gomock.NewController(t) + service := NewMockQueryServiceClient(ctrl) + service.EXPECT().BeginTransaction(gomock.Any(), gomock.Any()).Return(nil, grpcStatus.Error(grpcCodes.Unavailable, "")) + t.Log("begin") + _, err := begin(ctx, service, &Session{id: "123"}, query.TxSettings()) + require.Error(t, err) + require.True(t, xerrors.IsTransportError(err, grpcCodes.Unavailable)) + }) + t.Run("OperationError", func(t *testing.T) { + ctx := xtest.Context(t) + ctrl := gomock.NewController(t) + service := NewMockQueryServiceClient(ctrl) + 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) + require.True(t, xerrors.IsOperationError(err, Ydb.StatusIds_UNAVAILABLE)) + }) +} diff --git a/internal/query/transaction.go b/internal/query/transaction.go new file mode 100644 index 000000000..fbfcd9151 --- /dev/null +++ b/internal/query/transaction.go @@ -0,0 +1,81 @@ +package query + +import ( + "context" + + "github.com/ydb-platform/ydb-go-genproto/Ydb_Query_V1" + "github.com/ydb-platform/ydb-go-genproto/protos/Ydb_Query" + + "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/xerrors" + "github.com/ydb-platform/ydb-go-sdk/v3/query" + "github.com/ydb-platform/ydb-go-sdk/v3/trace" +) + +var _ query.Transaction = (*transaction)(nil) + +type transaction struct { + id string + s *Session +} + +func newTransaction(id string, s *Session) *transaction { + return &transaction{ + id: id, + s: s, + } +} + +func (tx transaction) ID() string { + return tx.id +} + +func (tx transaction) Execute(ctx context.Context, q string, opts ...options.TxExecuteOption) ( + r query.Result, finalErr error, +) { + 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) + }() + + _, res, err := execute(ctx, tx.s, tx.s.grpcClient, q, options.TxExecuteSettings(tx.id, opts...).ExecuteSettings) + if err != nil { + return nil, xerrors.WithStackTrace(err) + } + + return res, nil +} + +func commitTx(ctx context.Context, client Ydb_Query_V1.QueryServiceClient, sessionID, txID string) error { + _, err := client.CommitTransaction(ctx, &Ydb_Query.CommitTransactionRequest{ + SessionId: sessionID, + TxId: txID, + }) + if err != nil { + return xerrors.WithStackTrace(err) + } + + return nil +} + +func (tx transaction) CommitTx(ctx context.Context) (err error) { + return commitTx(ctx, tx.s.grpcClient, tx.s.id, tx.id) +} + +func rollback(ctx context.Context, client Ydb_Query_V1.QueryServiceClient, sessionID, txID string) error { + _, err := client.RollbackTransaction(ctx, &Ydb_Query.RollbackTransactionRequest{ + SessionId: sessionID, + TxId: txID, + }) + if err != nil { + return xerrors.WithStackTrace(err) + } + + return nil +} + +func (tx transaction) Rollback(ctx context.Context) (err error) { + return rollback(ctx, tx.s.grpcClient, tx.s.id, tx.id) +} diff --git a/internal/query/transaction_test.go b/internal/query/transaction_test.go new file mode 100644 index 000000000..83ecdfc6c --- /dev/null +++ b/internal/query/transaction_test.go @@ -0,0 +1,233 @@ +package query + +import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/ydb-platform/ydb-go-genproto/protos/Ydb" + "github.com/ydb-platform/ydb-go-genproto/protos/Ydb_Query" + "go.uber.org/mock/gomock" + "google.golang.org/grpc" + grpcCodes "google.golang.org/grpc/codes" + grpcStatus "google.golang.org/grpc/status" + + "github.com/ydb-platform/ydb-go-sdk/v3/internal/allocator" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/params" + "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 TestCommitTx(t *testing.T) { + t.Run("HappyWay", func(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_SUCCESS, + }, nil, + ) + t.Log("commit") + err := commitTx(ctx, service, "123", "456") + require.NoError(t, err) + }) + t.Run("TransportError", func(t *testing.T) { + ctx := xtest.Context(t) + ctrl := gomock.NewController(t) + service := NewMockQueryServiceClient(ctrl) + service.EXPECT().CommitTransaction(gomock.Any(), gomock.Any()).Return( + nil, grpcStatus.Error(grpcCodes.Unavailable, ""), + ) + t.Log("commit") + err := commitTx(ctx, service, "123", "456") + require.Error(t, err) + require.True(t, xerrors.IsTransportError(err, grpcCodes.Unavailable)) + }) + t.Run("OperationError", func(t *testing.T) { + ctx := xtest.Context(t) + ctrl := gomock.NewController(t) + service := NewMockQueryServiceClient(ctrl) + 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") + require.Error(t, err) + require.True(t, xerrors.IsOperationError(err, Ydb.StatusIds_UNAVAILABLE)) + }) +} + +func TestRollback(t *testing.T) { + t.Run("HappyWay", func(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_SUCCESS, + }, nil, + ) + t.Log("rollback") + err := rollback(ctx, service, "123", "456") + require.NoError(t, err) + }) + t.Run("TransportError", func(t *testing.T) { + ctx := xtest.Context(t) + ctrl := gomock.NewController(t) + service := NewMockQueryServiceClient(ctrl) + service.EXPECT().RollbackTransaction(gomock.Any(), gomock.Any()).Return( + nil, grpcStatus.Error(grpcCodes.Unavailable, ""), + ) + t.Log("rollback") + err := rollback(ctx, service, "123", "456") + require.Error(t, err) + require.True(t, xerrors.IsTransportError(err, grpcCodes.Unavailable)) + }) + t.Run("OperationError", func(t *testing.T) { + ctx := xtest.Context(t) + ctrl := gomock.NewController(t) + service := NewMockQueryServiceClient(ctrl) + 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") + require.Error(t, err) + require.True(t, xerrors.IsOperationError(err, Ydb.StatusIds_UNAVAILABLE)) + }) +} + +type testExecuteSettings struct { + execMode options.ExecMode + statsMode options.StatsMode + txControl *query.TransactionControl + syntax options.Syntax + params *params.Parameters + callOptions []grpc.CallOption +} + +func (s testExecuteSettings) ExecMode() options.ExecMode { + return s.execMode +} + +func (s testExecuteSettings) StatsMode() options.StatsMode { + return s.statsMode +} + +func (s testExecuteSettings) TxControl() *query.TransactionControl { + return s.txControl +} + +func (s testExecuteSettings) Syntax() options.Syntax { + return s.syntax +} + +func (s testExecuteSettings) Params() *params.Parameters { + return s.params +} + +func (s testExecuteSettings) CallOptions() []grpc.CallOption { + return s.callOptions +} + +var _ executeConfig = testExecuteSettings{} + +func TestTxExecuteSettings(t *testing.T) { + for _, tt := range []struct { + name string + txID string + txOpts []options.TxExecuteOption + settings executeConfig + }{ + { + name: "WithTxID", + txID: "test", + txOpts: nil, + settings: testExecuteSettings{ + execMode: options.ExecModeExecute, + statsMode: options.StatsModeNone, + txControl: query.TxControl(query.WithTxID("test")), + syntax: options.SyntaxYQL, + }, + }, + { + name: "WithStats", + txOpts: []options.TxExecuteOption{ + options.WithStatsMode(options.StatsModeFull), + }, + settings: testExecuteSettings{ + execMode: options.ExecModeExecute, + statsMode: options.StatsModeFull, + txControl: query.TxControl(query.WithTxID("")), + syntax: options.SyntaxYQL, + }, + }, + { + name: "WithExecMode", + txOpts: []options.TxExecuteOption{ + options.WithExecMode(options.ExecModeExplain), + }, + settings: testExecuteSettings{ + execMode: options.ExecModeExplain, + statsMode: options.StatsModeNone, + txControl: query.TxControl(query.WithTxID("")), + syntax: options.SyntaxYQL, + }, + }, + { + name: "WithSyntax", + txOpts: []options.TxExecuteOption{ + options.WithSyntax(options.SyntaxPostgreSQL), + }, + settings: testExecuteSettings{ + execMode: options.ExecModeExecute, + statsMode: options.StatsModeNone, + txControl: query.TxControl(query.WithTxID("")), + syntax: options.SyntaxPostgreSQL, + }, + }, + { + name: "WithGrpcOptions", + txOpts: []options.TxExecuteOption{ + options.WithCallOptions(grpc.CallContentSubtype("test")), + }, + settings: testExecuteSettings{ + execMode: options.ExecModeExecute, + statsMode: options.StatsModeNone, + txControl: query.TxControl(query.WithTxID("")), + syntax: options.SyntaxYQL, + callOptions: []grpc.CallOption{ + grpc.CallContentSubtype("test"), + }, + }, + }, + { + name: "WithParams", + txOpts: []options.TxExecuteOption{ + options.WithParameters( + params.Builder{}.Param("$a").Text("A").Build(), + ), + }, + settings: testExecuteSettings{ + execMode: options.ExecModeExecute, + statsMode: options.StatsModeNone, + txControl: query.TxControl(query.WithTxID("")), + syntax: options.SyntaxYQL, + params: params.Builder{}.Param("$a").Text("A").Build(), + }, + }, + } { + t.Run(tt.name, func(t *testing.T) { + a := allocator.New() + settings := options.TxExecuteSettings(tt.txID, tt.txOpts...).ExecuteSettings + require.Equal(t, tt.settings.Syntax(), settings.Syntax()) + require.Equal(t, tt.settings.ExecMode(), settings.ExecMode()) + require.Equal(t, tt.settings.StatsMode(), settings.StatsMode()) + require.Equal(t, tt.settings.TxControl().ToYDB(a).String(), settings.TxControl().ToYDB(a).String()) + require.Equal(t, tt.settings.Params().ToYDB(a), settings.Params().ToYDB(a)) + require.Equal(t, tt.settings.CallOptions(), settings.CallOptions()) + }) + } +} diff --git a/internal/query/tx/control.go b/internal/query/tx/control.go new file mode 100644 index 000000000..a5be2fb21 --- /dev/null +++ b/internal/query/tx/control.go @@ -0,0 +1,162 @@ +package tx + +import ( + "github.com/ydb-platform/ydb-go-genproto/protos/Ydb_Query" + + "github.com/ydb-platform/ydb-go-sdk/v3/internal/allocator" +) + +var ( + _ interface { + ToYDB(a *allocator.Allocator) *Ydb_Query.TransactionControl + } = (*Control)(nil) + _ Selector = (*Settings)(nil) +) + +type ( + Selector interface { + applyTxSelector(a *allocator.Allocator, txControl *Ydb_Query.TransactionControl) + } + ControlOption interface { + applyTxControlOption(txControl *Control) + } + Control struct { + selector Selector + commit bool + } + Identifier interface { + ID() string + } +) + +func (ctrl *Control) ToYDB(a *allocator.Allocator) *Ydb_Query.TransactionControl { + if ctrl == nil { + return nil + } + + txControl := a.QueryTransactionControl() + ctrl.selector.applyTxSelector(a, txControl) + txControl.CommitTx = ctrl.commit + + return txControl +} + +var ( + _ ControlOption = beginTxOptions{} + _ Selector = beginTxOptions{} +) + +type beginTxOptions []Option + +func (opts beginTxOptions) applyTxControlOption(txControl *Control) { + txControl.selector = opts +} + +func (opts beginTxOptions) applyTxSelector(a *allocator.Allocator, txControl *Ydb_Query.TransactionControl) { + selector := a.QueryTransactionControlBeginTx() + selector.BeginTx = a.QueryTransactionSettings() + for _, opt := range opts { + if opt != nil { + opt.ApplyTxSettingsOption(a, selector.BeginTx) + } + } + txControl.TxSelector = selector +} + +// BeginTx returns selector transaction control option +func BeginTx(opts ...Option) beginTxOptions { + return opts +} + +var ( + _ ControlOption = txIDTxControlOption("") + _ Selector = txIDTxControlOption("") +) + +type txIDTxControlOption string + +func (id txIDTxControlOption) applyTxControlOption(txControl *Control) { + txControl.selector = id +} + +func (id txIDTxControlOption) applyTxSelector(a *allocator.Allocator, txControl *Ydb_Query.TransactionControl) { + selector := a.QueryTransactionControlTxID() + selector.TxId = string(id) + txControl.TxSelector = selector +} + +func WithTx(t Identifier) txIDTxControlOption { + return txIDTxControlOption(t.ID()) +} + +func WithTxID(txID string) txIDTxControlOption { + return txIDTxControlOption(txID) +} + +type commitTxOption struct{} + +func (c commitTxOption) applyTxControlOption(txControl *Control) { + txControl.commit = true +} + +// CommitTx returns commit transaction control option +func CommitTx() ControlOption { + return commitTxOption{} +} + +// NewControl makes transaction control from given options +func NewControl(opts ...ControlOption) *Control { + txControl := &Control{ + selector: BeginTx(WithSerializableReadWrite()), + commit: false, + } + for _, opt := range opts { + if opt != nil { + opt.applyTxControlOption(txControl) + } + } + + return txControl +} + +func NoTx() *Control { + return nil +} + +// DefaultTxControl returns default transaction control with serializable read-write isolation mode and auto-commit +func DefaultTxControl() *Control { + return NoTx() +} + +// SerializableReadWriteTxControl returns transaction control with serializable read-write isolation mode +func SerializableReadWriteTxControl(opts ...ControlOption) *Control { + return NewControl( + append([]ControlOption{ + BeginTx(WithSerializableReadWrite()), + }, opts...)..., + ) +} + +// OnlineReadOnlyTxControl returns online read-only transaction control +func OnlineReadOnlyTxControl(opts ...OnlineReadOnlyOption) *Control { + return NewControl( + BeginTx(WithOnlineReadOnly(opts...)), + CommitTx(), // open transactions not supported for OnlineReadOnly + ) +} + +// StaleReadOnlyTxControl returns stale read-only transaction control +func StaleReadOnlyTxControl() *Control { + return NewControl( + BeginTx(WithStaleReadOnly()), + CommitTx(), // open transactions not supported for StaleReadOnly + ) +} + +// SnapshotReadOnlyTxControl returns snapshot read-only transaction control +func SnapshotReadOnlyTxControl() *Control { + return NewControl( + BeginTx(WithSnapshotReadOnly()), + CommitTx(), // open transactions not supported for StaleReadOnly + ) +} diff --git a/internal/query/tx/settings.go b/internal/query/tx/settings.go new file mode 100644 index 000000000..ac051d132 --- /dev/null +++ b/internal/query/tx/settings.go @@ -0,0 +1,149 @@ +package tx + +import ( + "github.com/ydb-platform/ydb-go-genproto/protos/Ydb_Query" + + "github.com/ydb-platform/ydb-go-sdk/v3/internal/allocator" +) + +var ( + serializableReadWrite = &Ydb_Query.TransactionSettings_SerializableReadWrite{ + SerializableReadWrite: &Ydb_Query.SerializableModeSettings{}, + } + staleReadOnly = &Ydb_Query.TransactionSettings_StaleReadOnly{ + StaleReadOnly: &Ydb_Query.StaleModeSettings{}, + } + snapshotReadOnly = &Ydb_Query.TransactionSettings_SnapshotReadOnly{ + SnapshotReadOnly: &Ydb_Query.SnapshotModeSettings{}, + } + onlineReadOnlyAllowInconsistentReads = &Ydb_Query.TransactionSettings_OnlineReadOnly{ + OnlineReadOnly: &Ydb_Query.OnlineModeSettings{AllowInconsistentReads: true}, + } + onlineReadOnlyForbidInconsistentReads = &Ydb_Query.TransactionSettings_OnlineReadOnly{ + OnlineReadOnly: &Ydb_Query.OnlineModeSettings{AllowInconsistentReads: false}, + } +) + +// Transaction settings options +type ( + Option interface { + ApplyTxSettingsOption(a *allocator.Allocator, txSettings *Ydb_Query.TransactionSettings) + } + Settings []Option +) + +func (opts Settings) applyTxSelector(a *allocator.Allocator, txControl *Ydb_Query.TransactionControl) { + beginTx := a.QueryTransactionControlBeginTx() + beginTx.BeginTx = a.QueryTransactionSettings() + for _, opt := range opts { + if opt != nil { + opt.ApplyTxSettingsOption(a, beginTx.BeginTx) + } + } + txControl.TxSelector = beginTx +} + +func (opts Settings) ToYDB(a *allocator.Allocator) *Ydb_Query.TransactionSettings { + txSettings := a.QueryTransactionSettings() + for _, opt := range opts { + if opt != nil { + opt.ApplyTxSettingsOption(a, txSettings) + } + } + + return txSettings +} + +// NewSettings returns transaction settings +func NewSettings(opts ...Option) Settings { + return opts +} + +func WithDefaultTxMode() Option { + return WithSerializableReadWrite() +} + +var _ Option = serializableReadWriteTxSettingsOption{} + +type serializableReadWriteTxSettingsOption struct{} + +func (o serializableReadWriteTxSettingsOption) ApplyTxSettingsOption( + a *allocator.Allocator, txSettings *Ydb_Query.TransactionSettings, +) { + txSettings.TxMode = serializableReadWrite +} + +func WithSerializableReadWrite() Option { + return serializableReadWriteTxSettingsOption{} +} + +var _ Option = snapshotReadOnlyTxSettingsOption{} + +type snapshotReadOnlyTxSettingsOption struct{} + +func (snapshotReadOnlyTxSettingsOption) ApplyTxSettingsOption( + a *allocator.Allocator, settings *Ydb_Query.TransactionSettings, +) { + settings.TxMode = snapshotReadOnly +} + +func WithSnapshotReadOnly() Option { + return snapshotReadOnlyTxSettingsOption{} +} + +var _ Option = staleReadOnlySettingsOption{} + +type staleReadOnlySettingsOption struct{} + +func (staleReadOnlySettingsOption) ApplyTxSettingsOption( + a *allocator.Allocator, settings *Ydb_Query.TransactionSettings, +) { + settings.TxMode = staleReadOnly +} + +func WithStaleReadOnly() Option { + return staleReadOnlySettingsOption{} +} + +type ( + onlineReadOnly bool + OnlineReadOnlyOption interface { + applyTxOnlineReadOnlyOption(opt *onlineReadOnly) + } +) + +var _ OnlineReadOnlyOption = inconsistentReadsTxOnlineReadOnlyOption{} + +type inconsistentReadsTxOnlineReadOnlyOption struct{} + +func (i inconsistentReadsTxOnlineReadOnlyOption) applyTxOnlineReadOnlyOption(b *onlineReadOnly) { + *b = true +} + +func WithInconsistentReads() OnlineReadOnlyOption { + return inconsistentReadsTxOnlineReadOnlyOption{} +} + +var _ Option = onlineReadOnlySettingsOption{} + +type onlineReadOnlySettingsOption []OnlineReadOnlyOption + +func (opts onlineReadOnlySettingsOption) ApplyTxSettingsOption( + a *allocator.Allocator, settings *Ydb_Query.TransactionSettings, +) { + var ro onlineReadOnly + for _, opt := range opts { + if opt != nil { + opt.applyTxOnlineReadOnlyOption(&ro) + } + } + if ro { + settings.TxMode = onlineReadOnlyAllowInconsistentReads + } else { + settings.TxMode = onlineReadOnlyForbidInconsistentReads + } +} + +func WithOnlineReadOnly(opts ...OnlineReadOnlyOption) onlineReadOnlySettingsOption { + return opts +} diff --git a/internal/ratelimiter/client.go b/internal/ratelimiter/client.go index 206cbf5f9..eec07b118 100644 --- a/internal/ratelimiter/client.go +++ b/internal/ratelimiter/client.go @@ -37,11 +37,11 @@ func (c *Client) Close(ctx context.Context) error { return nil } -func New(ctx context.Context, cc grpc.ClientConnInterface, config config.Config) (*Client, error) { +func New(ctx context.Context, cc grpc.ClientConnInterface, config config.Config) *Client { return &Client{ config: config, service: Ydb_RateLimiter_V1.NewRateLimiterServiceClient(cc), - }, nil + } } func (c *Client) CreateResource( diff --git a/internal/ratelimiter/config/config.go b/internal/ratelimiter/config/config.go index a3f2f48e6..d0761f779 100644 --- a/internal/ratelimiter/config/config.go +++ b/internal/ratelimiter/config/config.go @@ -6,8 +6,6 @@ import ( ) // Config is a configuration of ratelimiter client -// -//nolint:maligned type Config struct { config.Common @@ -39,9 +37,9 @@ func New(opts ...Option) Config { c := Config{ trace: &trace.Ratelimiter{}, } - for _, o := range opts { - if o != nil { - o(&c) + for _, opt := range opts { + if opt != nil { + opt(&c) } } diff --git a/internal/ratelimiter/options/acquire.go b/internal/ratelimiter/options/acquire.go index d1f91d64f..1febf3e63 100644 --- a/internal/ratelimiter/options/acquire.go +++ b/internal/ratelimiter/options/acquire.go @@ -74,9 +74,9 @@ func NewAcquire(opts ...AcquireOption) Acquire { h := &acquireOptionsHolder{ acquireType: AcquireTypeDefault, } - for _, o := range opts { - if o != nil { - o(h) + for _, opt := range opts { + if opt != nil { + opt(h) } } diff --git a/internal/repeater/repeater.go b/internal/repeater/repeater.go index fad8da8c1..ec75be3c1 100644 --- a/internal/repeater/repeater.go +++ b/internal/repeater/repeater.go @@ -108,9 +108,9 @@ func New( trace: &trace.Driver{}, } - for _, o := range opts { - if o != nil { - o(r) + for _, opt := range opts { + if opt != nil { + opt(r) } } @@ -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() { diff --git a/internal/scanner/scanner.go b/internal/scanner/scanner.go new file mode 100644 index 000000000..19c91de52 --- /dev/null +++ b/internal/scanner/scanner.go @@ -0,0 +1,145 @@ +package scanner + +import ( + "io" + "time" + + "github.com/ydb-platform/ydb-go-sdk/v3/internal/decimal" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/types" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/value" +) + +// RawValue scanning non-primitive yql types or for own implementation scanner native API +type RawValue interface { + Path() string + WritePathTo(w io.Writer) (n int64, err error) + Type() types.Type + Bool() (v bool) + Int8() (v int8) + Uint8() (v uint8) + Int16() (v int16) + Uint16() (v uint16) + Int32() (v int32) + Uint32() (v uint32) + Int64() (v int64) + Uint64() (v uint64) + Float() (v float32) + Double() (v float64) + Date() (v time.Time) + Datetime() (v time.Time) + Timestamp() (v time.Time) + Interval() (v time.Duration) + TzDate() (v time.Time) + TzDatetime() (v time.Time) + TzTimestamp() (v time.Time) + String() (v []byte) + UTF8() (v string) + YSON() (v []byte) + JSON() (v []byte) + UUID() (v [16]byte) + JSONDocument() (v []byte) + DyNumber() (v string) + Value() value.Value + + // Any returns any primitive or optional value. + // Currently, it may return one of these types: + // + // bool + // int8 + // uint8 + // int16 + // uint16 + // int32 + // uint32 + // int64 + // uint64 + // float32 + // float64 + // []byte + // string + // [16]byte + // + Any() interface{} + + // Unwrap unwraps current item under scan interpreting it as Optional types. + Unwrap() + AssertType(t types.Type) bool + IsNull() bool + IsOptional() bool + + // ListIn interprets current item under scan as a ydb's list. + // It returns the size of the nested items. + // If current item under scan is not a list types, it returns -1. + ListIn() (size int) + + // ListItem selects current item i-th element as an item to scan. + // ListIn() must be called before. + ListItem(i int) + + // ListOut leaves list entered before by ListIn() call. + ListOut() + + // TupleIn interprets current item under scan as a ydb's tuple. + // It returns the size of the nested items. + TupleIn() (size int) + + // TupleItem selects current item i-th element as an item to scan. + // Note that TupleIn() must be called before. + // It panics if it is out of bounds. + TupleItem(i int) + + // TupleOut leaves tuple entered before by TupleIn() call. + TupleOut() + + // StructIn interprets current item under scan as a ydb's struct. + // It returns the size of the nested items – the struct fields values. + // If there is no current item under scan it returns -1. + StructIn() (size int) + + // StructField selects current item i-th field value as an item to scan. + // Note that StructIn() must be called before. + // It panics if i is out of bounds. + StructField(i int) (name string) + + // StructOut leaves struct entered before by StructIn() call. + StructOut() + + // DictIn interprets current item under scan as a ydb's dict. + // It returns the size of the nested items pairs. + // If there is no current item under scan it returns -1. + DictIn() (size int) + + // DictKey selects current item i-th pair key as an item to scan. + // Note that DictIn() must be called before. + // It panics if i is out of bounds. + DictKey(i int) + + // DictPayload selects current item i-th pair value as an item to scan. + // Note that DictIn() must be called before. + // It panics if i is out of bounds. + DictPayload(i int) + + // DictOut leaves dict entered before by DictIn() call. + DictOut() + + // Variant unwraps current item under scan interpreting it as Variant types. + // It returns non-empty name of a field that is filled for struct-based + // variant. + // It always returns an index of filled field of a Type. + Variant() (name string, index uint32) + + // Decimal returns decimal value represented by big-endian 128 bit signed integer. + Decimal(t types.Type) (v [16]byte) + + // UnwrapDecimal returns decimal value represented by big-endian 128 bit signed + // integer and its types information. + UnwrapDecimal() decimal.Decimal + IsDecimal() bool + Err() error +} + +// Scanner scanning raw ydb types +type Scanner interface { + // UnmarshalYDB must be implemented on client-side for unmarshal raw ydb value. + UnmarshalYDB(raw RawValue) error +} diff --git a/internal/scheme/client.go b/internal/scheme/client.go index f9dd3a621..6f46ed736 100644 --- a/internal/scheme/client.go +++ b/internal/scheme/client.go @@ -38,16 +38,16 @@ func (c *Client) Close(_ context.Context) error { return nil } -func New(ctx context.Context, cc grpc.ClientConnInterface, config config.Config) (*Client, error) { +func New(ctx context.Context, cc grpc.ClientConnInterface, config config.Config) *Client { return &Client{ config: config, service: Ydb_Scheme_V1.NewSchemeServiceClient(cc), - }, nil + } } 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) }() @@ -173,16 +175,16 @@ func (c *Client) listDirectory(ctx context.Context, path string) (scheme.Directo if err != nil { return d, xerrors.WithStackTrace(err) } - d.From(result.Self) - d.Children = make([]scheme.Entry, len(result.Children)) - putEntry(d.Children, result.Children) + d.From(result.GetSelf()) + d.Children = make([]scheme.Entry, len(result.GetChildren())) + putEntry(d.Children, result.GetChildren()) return d, nil } 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() { @@ -234,7 +236,7 @@ func (c *Client) describePath(ctx context.Context, path string) (e scheme.Entry, if err != nil { return e, xerrors.WithStackTrace(err) } - e.From(result.Self) + e.From(result.GetSelf()) return e, nil } @@ -243,16 +245,16 @@ 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() { onDone(finalErr) }() var desc permissionsDesc - for _, o := range opts { - if o != nil { - o(&desc) + for _, opt := range opts { + if opt != nil { + opt(&desc) } } call := func(ctx context.Context) error { diff --git a/internal/scheme/config/config.go b/internal/scheme/config/config.go index 87677eb48..a684fc83e 100644 --- a/internal/scheme/config/config.go +++ b/internal/scheme/config/config.go @@ -6,8 +6,6 @@ import ( ) // Config is a configuration of scheme client -// -//nolint:maligned type Config struct { config.Common @@ -52,9 +50,9 @@ func New(opts ...Option) Config { c := Config{ trace: &trace.Scheme{}, } - for _, o := range opts { - if o != nil { - o(&c) + for _, opt := range opts { + if opt != nil { + opt(&c) } } diff --git a/internal/scheme/options_test.go b/internal/scheme/options_test.go index 5f99dfd32..b8ef769e9 100644 --- a/internal/scheme/options_test.go +++ b/internal/scheme/options_test.go @@ -28,9 +28,9 @@ func TestSchemeOptions(t *testing.T) { } var desc permissionsDesc - for _, o := range opts { - if o != nil { - o(&desc) + for _, opt := range opts { + if opt != nil { + opt(&desc) } } @@ -40,7 +40,7 @@ func TestSchemeOptions(t *testing.T) { count := len(desc.actions) for _, a := range desc.actions { - switch a := a.Action.(type) { + switch a := a.GetAction().(type) { case *Ydb_Scheme.PermissionsAction_ChangeOwner: count-- if a.ChangeOwner != "ow" { @@ -48,17 +48,19 @@ func TestSchemeOptions(t *testing.T) { } case *Ydb_Scheme.PermissionsAction_Grant: count-- - if a.Grant.Subject != "grant" || len(a.Grant.PermissionNames) != 3 { + if a.Grant.GetSubject() != "grant" || len(a.Grant.GetPermissionNames()) != 3 { t.Errorf("Grant is not as expected") } case *Ydb_Scheme.PermissionsAction_Set: count-- - if a.Set.Subject != "set" || len(a.Set.PermissionNames) != 1 || a.Set.PermissionNames[0] != "d" { + if a.Set.GetSubject() != "set" || len(a.Set.GetPermissionNames()) != 1 || a.Set.GetPermissionNames()[0] != "d" { t.Errorf("Set is not as expected") } case *Ydb_Scheme.PermissionsAction_Revoke: count-- - if a.Revoke.Subject != "revoke" || len(a.Revoke.PermissionNames) != 1 || a.Revoke.PermissionNames[0] != "e" { + revokeSubject := a.Revoke.GetSubject() + permissionNames := a.Revoke.GetPermissionNames() + if revokeSubject != "revoke" || len(permissionNames) != 1 || permissionNames[0] != "e" { t.Errorf("Revoke is not as expected") } } diff --git a/internal/scripting/client.go b/internal/scripting/client.go index bf57a3edd..a8ecc18bb 100644 --- a/internal/scripting/client.go +++ b/internal/scripting/client.go @@ -12,17 +12,17 @@ import ( "github.com/ydb-platform/ydb-go-sdk/v3/internal/allocator" "github.com/ydb-platform/ydb-go-sdk/v3/internal/operation" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/params" "github.com/ydb-platform/ydb-go-sdk/v3/internal/scripting/config" "github.com/ydb-platform/ydb-go-sdk/v3/internal/stack" "github.com/ydb-platform/ydb-go-sdk/v3/internal/table/scanner" - "github.com/ydb-platform/ydb-go-sdk/v3/internal/value" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/types" "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/retry" "github.com/ydb-platform/ydb-go-sdk/v3/scripting" "github.com/ydb-platform/ydb-go-sdk/v3/table" "github.com/ydb-platform/ydb-go-sdk/v3/table/result" - "github.com/ydb-platform/ydb-go-sdk/v3/table/types" "github.com/ydb-platform/ydb-go-sdk/v3/trace" ) @@ -32,21 +32,23 @@ var ( errNilClient = xerrors.Wrap(errors.New("scripting client is not initialized")) ) -type Client struct { - config config.Config - service Ydb_Scripting_V1.ScriptingServiceClient -} +type ( + Client struct { + config config.Config + service Ydb_Scripting_V1.ScriptingServiceClient + } +) func (c *Client) Execute( ctx context.Context, query string, - params *table.QueryParameters, + parameters *params.Parameters, ) (r result.Result, err error) { if c == nil { return r, xerrors.WithStackTrace(errNilClient) } call := func(ctx context.Context) error { - r, err = c.execute(ctx, query, params) + r, err = c.execute(ctx, query, parameters) return xerrors.WithStackTrace(err) } @@ -66,17 +68,17 @@ func (c *Client) Execute( func (c *Client) execute( ctx context.Context, query string, - params *table.QueryParameters, + parameters *params.Parameters, ) (r result.Result, err error) { var ( onDone = trace.ScriptingOnExecute(c.config.Trace(), &ctx, - stack.FunctionID(""), - query, params, + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/scripting.(*Client).execute"), + query, parameters, ) a = allocator.New() request = &Ydb_Scripting.ExecuteYqlRequest{ Script: query, - Parameters: params.Params().ToYDB(a), + Parameters: parameters.ToYDB(a), OperationParams: operation.Params( ctx, c.config.OperationTimeout(), @@ -149,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{ @@ -184,7 +186,7 @@ func (c *Client) explain( ParameterTypes: make(map[string]types.Type, len(result.GetParametersTypes())), } for k, v := range result.GetParametersTypes() { - e.ParameterTypes[k] = value.TypeFromYDB(v) + e.ParameterTypes[k] = types.TypeFromYDB(v) } return e, nil @@ -193,7 +195,7 @@ func (c *Client) explain( func (c *Client) StreamExecute( ctx context.Context, query string, - params *table.QueryParameters, + params *params.Parameters, ) (r result.StreamResult, err error) { if c == nil { return r, xerrors.WithStackTrace(errNilClient) @@ -219,17 +221,17 @@ func (c *Client) StreamExecute( func (c *Client) streamExecute( ctx context.Context, query string, - params *table.QueryParameters, + parameters *params.Parameters, ) (r result.StreamResult, err error) { var ( onIntermediate = trace.ScriptingOnStreamExecute(c.config.Trace(), &ctx, - stack.FunctionID(""), - query, params, + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/scripting.(*Client).streamExecute"), + query, parameters, ) a = allocator.New() request = &Ydb_Scripting.ExecuteYqlRequest{ Script: query, - Parameters: params.Params().ToYDB(a), + Parameters: parameters.ToYDB(a), OperationParams: operation.Params( ctx, c.config.OperationTimeout(), @@ -290,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) }() @@ -298,9 +302,9 @@ func (c *Client) Close(ctx context.Context) (err error) { return nil } -func New(ctx context.Context, cc grpc.ClientConnInterface, config config.Config) (*Client, error) { +func New(ctx context.Context, cc grpc.ClientConnInterface, config config.Config) *Client { return &Client{ config: config, service: Ydb_Scripting_V1.NewScriptingServiceClient(cc), - }, nil + } } diff --git a/internal/scripting/config/config.go b/internal/scripting/config/config.go index 1b8ee0ff3..35f90651d 100644 --- a/internal/scripting/config/config.go +++ b/internal/scripting/config/config.go @@ -36,9 +36,9 @@ func New(opts ...Option) Config { c := Config{ trace: &trace.Scripting{}, } - for _, o := range opts { - if o != nil { - o(&c) + for _, opt := range opts { + if opt != nil { + opt(&c) } } diff --git a/internal/session/status.go b/internal/session/status.go new file mode 100644 index 000000000..49fbe7425 --- /dev/null +++ b/internal/session/status.go @@ -0,0 +1,12 @@ +package session + +type Status = string + +const ( + StatusUnknown = Status("Unknown") + StatusIdle = Status("Idle") + StatusInUse = Status("InUse") + StatusClosing = Status("Closing") + StatusClosed = Status("Closed") + StatusError = Status("Error") +) diff --git a/internal/stack/function_id.go b/internal/stack/function_id.go index bbd0f17f3..646a7f5ea 100644 --- a/internal/stack/function_id.go +++ b/internal/stack/function_id.go @@ -1,10 +1,10 @@ package stack -type caller interface { +type Caller interface { FunctionID() string } -var _ caller = functionID("") +var _ Caller = functionID("") type functionID string @@ -12,7 +12,7 @@ func (id functionID) FunctionID() string { return string(id) } -func FunctionID(id string) caller { +func FunctionID(id string) Caller { if id != "" { return functionID(id) } diff --git a/internal/stack/function_id_test.go b/internal/stack/function_id_test.go new file mode 100644 index 000000000..b7adabee5 --- /dev/null +++ b/internal/stack/function_id_test.go @@ -0,0 +1,69 @@ +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() +} + +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, + "github.com/ydb-platform/ydb-go-sdk/v3/internal/stack.staticCall", + staticCall(), + ) + }) + t.Run("GenericTypeCall", func(t *testing.T) { + require.Equal(t, + "github.com/ydb-platform/ydb-go-sdk/v3/internal/stack.genericType.Call", + 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 2fbf29ae5..2098c1ec8 100644 --- a/internal/stack/record.go +++ b/internal/stack/record.go @@ -62,7 +62,7 @@ func PackagePath(b bool) recordOption { } } -var _ caller = call{} +var _ Caller = call{} type call struct { function uintptr @@ -87,7 +87,9 @@ func (c call) Record(opts ...recordOption) string { lambdas: true, } for _, opt := range opts { - opt(&optionsHolder) + if opt != nil { + opt(&optionsHolder) + } } name := runtime.FuncForPC(c.function).Name() var ( @@ -103,6 +105,7 @@ func (c call) Record(opts ...recordOption) string { if i := strings.LastIndex(name, "/"); i > -1 { pkgPath, name = name[:i], name[i+1:] } + name = strings.ReplaceAll(name, "[...]", "") split := strings.Split(name, ".") lambdas := make([]string, 0, len(split)) for i := range split { diff --git a/internal/table/client.go b/internal/table/client.go index 1ad82c518..7d063346b 100644 --- a/internal/table/client.go +++ b/internal/table/client.go @@ -34,7 +34,14 @@ type balancer interface { nodeChecker } -func New(ctx context.Context, balancer balancer, config *config.Config) (*Client, error) { +func New(ctx context.Context, balancer balancer, config *config.Config) *Client { + onDone := trace.TableOnInit(config.Trace(), &ctx, + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/table.New"), + ) + defer func() { + onDone(config.SizeLimit()) + }() + return newClient(ctx, balancer, func(ctx context.Context) (s *session, err error) { return newSession(ctx, balancer, config) }, config) @@ -45,12 +52,8 @@ func newClient( balancer balancer, builder sessionBuilder, config *config.Config, -) (c *Client, finalErr error) { - onDone := trace.TableOnInit(config.Trace(), &ctx, stack.FunctionID("")) - defer func() { - onDone(config.SizeLimit(), finalErr) - }() - c = &Client{ +) *Client { + c := &Client{ clock: config.Clock(), config: config, cc: balancer, @@ -74,7 +77,7 @@ func newClient( go c.internalPoolGC(ctx, idleThreshold) } - return c, nil + return c } // Client is a set of session instances that may be reused. @@ -121,9 +124,9 @@ func withCreateSessionOnClose(onClose func(s *session)) createSessionOption { func (c *Client) createSession(ctx context.Context, opts ...createSessionOption) (s *session, err error) { options := createSessionOptions{} - for _, o := range opts { - if o != nil { - o(&options) + for _, opt := range opts { + if opt != nil { + opt(&options) } } @@ -165,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 @@ -178,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 @@ -260,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("")) + 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.RetryLoopIntermediateInfo) func(trace.RetryLoopDoneInfo) { - onDone := onIntermediate(info.Error) - - return func(info trace.RetryLoopDoneInfo) { - onDone(s, info.Attempts, info.Error) - } + return func(info trace.RetryLoopDoneInfo) { + onDone(s, info.Attempts, info.Error) } }, }), @@ -380,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) }() @@ -476,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) @@ -484,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 { @@ -538,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() { @@ -597,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) }() @@ -642,17 +652,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(""), - config.Label, config.Label, config.Idempotent, xcontext.IsNestedCall(ctx), + 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) @@ -672,22 +681,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(""), - config.Label, config.Label, config.Idempotent, xcontext.IsNestedCall(ctx), + 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) diff --git a/internal/table/client_test.go b/internal/table/client_test.go index f1f1cc2f9..82b7eb6af 100644 --- a/internal/table/client_test.go +++ b/internal/table/client_test.go @@ -408,7 +408,7 @@ func TestSessionPoolRacyGet(t *testing.T) { session *session } create := make(chan createReq) - p, err := newClient( + p := newClient( context.Background(), nil, (&StubBuilder{ @@ -429,10 +429,10 @@ func TestSessionPoolRacyGet(t *testing.T) { config.WithIdleThreshold(-1), ), ) - require.NoError(t, err) var ( expSession *session done = make(chan struct{}, 2) + err error ) for i := 0; i < 2; i++ { go func() { @@ -872,7 +872,7 @@ func newClientWithStubBuilder( stubLimit int, options ...config.Option, ) *Client { - c, err := newClient( + c := newClient( context.Background(), balancer, (&StubBuilder{ @@ -882,7 +882,6 @@ func newClientWithStubBuilder( }).createSession, config.New(options...), ) - require.NoError(t, err) return c } diff --git a/internal/table/config/config.go b/internal/table/config/config.go index 5013ab332..de94fb3e6 100644 --- a/internal/table/config/config.go +++ b/internal/table/config/config.go @@ -27,9 +27,9 @@ const ( func New(opts ...Option) *Config { c := defaults() - for _, o := range opts { - if o != nil { - o(c) + for _, opt := range opts { + if opt != nil { + opt(c) } } diff --git a/internal/table/params.go b/internal/table/params.go deleted file mode 100644 index bb1f1e327..000000000 --- a/internal/table/params.go +++ /dev/null @@ -1,41 +0,0 @@ -package table - -import ( - "bytes" - "fmt" - "sort" - - "github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors" - "github.com/ydb-platform/ydb-go-sdk/v3/table" - "github.com/ydb-platform/ydb-go-sdk/v3/table/types" -) - -var ErrNameRequired = xerrors.Wrap(fmt.Errorf("only named parameters are supported")) - -// GenerateDeclareSection generates DECLARE section text in YQL query by params -// -// Warning: This is an experimental feature and could change at any time -func GenerateDeclareSection(params *table.QueryParameters) (string, error) { - var ( - buf bytes.Buffer - names []string - declares = make(map[string]string, len(params.Params())) - ) - params.Each(func(name string, v types.Value) { - names = append(names, name) - declares[name] = fmt.Sprintf( - "DECLARE %s AS %s;\n", - name, - v.Type().Yql(), - ) - }) - sort.Strings(names) - for _, name := range names { - if name == "" { - return "", xerrors.WithStackTrace(ErrNameRequired) - } - buf.WriteString(declares[name]) - } - - return buf.String(), nil -} diff --git a/internal/table/scanner/result.go b/internal/table/scanner/result.go index 1d931489c..fdebc2859 100644 --- a/internal/table/scanner/result.go +++ b/internal/table/scanner/result.go @@ -18,7 +18,7 @@ import ( var errAlreadyClosed = xerrors.Wrap(errors.New("result closed early")) type baseResult struct { - scanner + valueScanner nextResultSetCounter atomic.Uint64 statsMtx xsync.RWMutex @@ -36,7 +36,7 @@ type streamResult struct { // Err returns error caused Scanner to be broken. func (r *streamResult) Err() error { - err := r.scanner.Err() + err := r.valueScanner.Err() if err != nil { return xerrors.WithStackTrace(err) } @@ -53,7 +53,7 @@ type unaryResult struct { // Err returns error caused Scanner to be broken. func (r *unaryResult) Err() error { - err := r.scanner.Err() + err := r.valueScanner.Err() if err != nil { return xerrors.WithStackTrace(err) } @@ -96,13 +96,13 @@ type option func(r *baseResult) func WithIgnoreTruncated(ignoreTruncated bool) option { return func(r *baseResult) { - r.scanner.ignoreTruncated = ignoreTruncated + r.valueScanner.ignoreTruncated = ignoreTruncated } } func WithMarkTruncatedAsRetryable() option { return func(r *baseResult) { - r.scanner.markTruncatedAsRetryable = true + r.valueScanner.markTruncatedAsRetryable = true } } @@ -116,9 +116,9 @@ func NewStream( recv: recv, close: onClose, } - for _, o := range opts { - if o != nil { - o(&r.baseResult) + for _, opt := range opts { + if opt != nil { + opt(&r.baseResult) } } if err := r.nextResultSetErr(ctx); err != nil { @@ -135,9 +135,9 @@ func NewUnary(sets []*Ydb.ResultSet, stats *Ydb_TableStats.QueryStats, opts ...o }, sets: sets, } - for _, o := range opts { - if o != nil { - o(&r.baseResult) + for _, opt := range opts { + if opt != nil { + opt(&r.baseResult) } } diff --git a/internal/table/scanner/result_test.go b/internal/table/scanner/result_test.go index e3f71b62e..dc81b0203 100644 --- a/internal/table/scanner/result_test.go +++ b/internal/table/scanner/result_test.go @@ -13,29 +13,29 @@ import ( "github.com/ydb-platform/ydb-go-genproto/protos/Ydb_TableStats" "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/value" "github.com/ydb-platform/ydb-go-sdk/v3/table/options" - "github.com/ydb-platform/ydb-go-sdk/v3/table/types" ) func TestResultAny(t *testing.T) { for _, test := range []struct { name string columns []options.Column - values []types.Value + values []value.Value exp []interface{} }{ { columns: []options.Column{ { Name: "column0", - Type: types.Optional(types.TypeUint32), + Type: types.NewOptional(types.Uint32), Family: "family0", }, }, - values: []types.Value{ - types.OptionalValue(types.Uint32Value(43)), - types.NullValue(types.TypeUint32), + values: []value.Value{ + value.OptionalValue(value.Uint32Value(43)), + value.NullValue(types.Uint32), }, exp: []interface{}{ uint32(43), @@ -83,25 +83,25 @@ func TestResultOUint32(t *testing.T) { for _, test := range []struct { name string columns []options.Column - values []types.Value + values []value.Value exp []uint32 }{ { columns: []options.Column{ { Name: "column0", - Type: types.Optional(types.TypeUint32), + Type: types.NewOptional(types.Uint32), Family: "family0", }, { Name: "column1", - Type: types.TypeUint32, + Type: types.Uint32, Family: "family0", }, }, - values: []types.Value{ - types.OptionalValue(types.Uint32Value(43)), - types.Uint32Value(43), + values: []value.Value{ + value.OptionalValue(value.Uint32Value(43)), + value.Uint32Value(43), }, exp: []uint32{ 43, @@ -151,13 +151,13 @@ func WithColumns(cs ...options.Column) ResultSetOption { for _, c := range cs { r.Columns = append(r.Columns, &Ydb.Column{ Name: c.Name, - Type: value.TypeToYDB(c.Type, a), + Type: types.TypeToYDB(c.Type, a), }) } } } -func WithValues(vs ...types.Value) ResultSetOption { +func WithValues(vs ...value.Value) ResultSetOption { return func(r *resultSetDesc, a *allocator.Allocator) { n := len(r.Columns) if n == 0 { @@ -178,15 +178,15 @@ func WithValues(vs ...types.Value) ResultSetOption { } } tv := value.ToYDB(v, a) - act := value.TypeFromYDB(tv.Type) - exp := value.TypeFromYDB(r.Columns[j].Type) - if !value.TypesEqual(act, exp) { + act := types.TypeFromYDB(tv.GetType()) + exp := types.TypeFromYDB(r.Columns[j].GetType()) + if !types.Equal(act, exp) { panic(fmt.Sprintf( "unexpected types for #%d column: %s; want %s", j, act, exp, )) } - row.Items[j] = tv.Value + row.Items[j] = tv.GetValue() } if row != nil { r.Rows = append(r.Rows, row) diff --git a/internal/table/scanner/scan_raw.go b/internal/table/scanner/scan_raw.go index 1a0c8be98..7c0782f3c 100644 --- a/internal/table/scanner/scan_raw.go +++ b/internal/table/scanner/scan_raw.go @@ -10,14 +10,15 @@ import ( "github.com/ydb-platform/ydb-go-genproto/protos/Ydb" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/decimal" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/types" "github.com/ydb-platform/ydb-go-sdk/v3/internal/value" "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/types" ) type rawConverter struct { - *scanner + *valueScanner } func (s *rawConverter) String() (v []byte) { @@ -31,7 +32,7 @@ func (s *rawConverter) HasItems() bool { } func (s *rawConverter) HasNextItem() bool { - return s.hasItems() && s.nextItem < len(s.row.Items) + return s.hasItems() && s.nextItem < len(s.row.GetItems()) } func (s *rawConverter) Path() string { @@ -282,8 +283,8 @@ func (s *rawConverter) Any() interface{} { return s.any() } -// Value returns current item under scan as ydb.Value types. -func (s *rawConverter) Value() types.Value { +// Value returns current item under scan as value +func (s *rawConverter) Value() value.Value { if s.Err() != nil { return nil } @@ -338,14 +339,14 @@ func (s *rawConverter) ListItem(i int) { return } p := s.stack.parent() - if !s.itemsBoundsCheck(p.v.Items, i) { + if !s.itemsBoundsCheck(p.v.GetItems(), i) { return } if t := s.assertTypeList(p.t); t != nil { s.stack.set(item{ i: i, - t: t.ListType.Item, - v: p.v.Items[i], + t: t.ListType.GetItem(), + v: p.v.GetItems()[i], }) } } @@ -377,14 +378,14 @@ func (s *rawConverter) TupleItem(i int) { return } p := s.stack.parent() - if !s.itemsBoundsCheck(p.v.Items, i) { + if !s.itemsBoundsCheck(p.v.GetItems(), i) { return } if t := s.assertTypeTuple(p.t); t != nil { s.stack.set(item{ i: i, - t: t.TupleType.Elements[i], - v: p.v.Items[i], + t: t.TupleType.GetElements()[i], + v: p.v.GetItems()[i], }) } } @@ -416,17 +417,17 @@ func (s *rawConverter) StructField(i int) (name string) { return } p := s.stack.parent() - if !s.itemsBoundsCheck(p.v.Items, i) { + if !s.itemsBoundsCheck(p.v.GetItems(), i) { return } if t := s.assertTypeStruct(p.t); t != nil { - m := t.StructType.Members[i] - name = m.Name + m := t.StructType.GetMembers()[i] + name = m.GetName() s.stack.set(item{ - name: m.Name, + name: m.GetName(), i: i, - t: m.Type, - v: p.v.Items[i], + t: m.GetType(), + v: p.v.GetItems()[i], }) } @@ -460,14 +461,14 @@ func (s *rawConverter) DictKey(i int) { return } p := s.stack.parent() - if !s.pairsBoundsCheck(p.v.Pairs, i) { + if !s.pairsBoundsCheck(p.v.GetPairs(), i) { return } if t := s.assertTypeDict(p.t); t != nil { s.stack.set(item{ i: i, - t: t.DictType.Key, - v: p.v.Pairs[i].Key, + t: t.DictType.GetKey(), + v: p.v.GetPairs()[i].GetKey(), }) } } @@ -477,14 +478,14 @@ func (s *rawConverter) DictPayload(i int) { return } p := s.stack.parent() - if !s.pairsBoundsCheck(p.v.Pairs, i) { + if !s.pairsBoundsCheck(p.v.GetPairs(), i) { return } if t := s.assertTypeDict(p.t); t != nil { s.stack.set(item{ i: i, - t: t.DictType.Payload, - v: p.v.Pairs[i].Payload, + t: t.DictType.GetPayload(), + v: p.v.GetPairs()[i].GetPayload(), }) } } @@ -534,13 +535,13 @@ func (s *rawConverter) Unwrap() { return } v := x.v - if isOptional(t.OptionalType.Item) { + if isOptional(t.OptionalType.GetItem()) { v = s.unwrapValue() } s.stack.enter() s.stack.set(item{ name: "*", - t: t.OptionalType.Item, + t: t.OptionalType.GetItem(), v: v, }) } @@ -557,20 +558,20 @@ func (s *rawConverter) Decimal(t types.Type) (v [16]byte) { return s.uint128() } -func (s *rawConverter) UnwrapDecimal() (v types.Decimal) { +func (s *rawConverter) UnwrapDecimal() decimal.Decimal { if s.Err() != nil { - return + return decimal.Decimal{} } s.unwrap() d := s.assertTypeDecimal(s.stack.current().t) if d == nil { - return + return decimal.Decimal{} } - return types.Decimal{ + return decimal.Decimal{ Bytes: s.uint128(), - Precision: d.DecimalType.Precision, - Scale: d.DecimalType.Scale, + Precision: d.DecimalType.GetPrecision(), + Scale: d.DecimalType.GetScale(), } } @@ -583,39 +584,39 @@ func (s *rawConverter) IsDecimal() bool { } func isEqualDecimal(d *Ydb.DecimalType, t types.Type) bool { - w := t.(*value.DecimalType) + w := t.(*types.Decimal) - return d.Precision == w.Precision && d.Scale == w.Scale + return d.GetPrecision() == w.Precision() && d.GetScale() == w.Scale() } func (s *rawConverter) isCurrentTypeDecimal() bool { c := s.stack.current() - _, ok := c.t.Type.(*Ydb.Type_DecimalType) + _, ok := c.t.GetType().(*Ydb.Type_DecimalType) return ok } func (s *rawConverter) unwrapVariantType(typ *Ydb.Type_VariantType, index uint32) (name string, t *Ydb.Type) { i := int(index) - switch x := typ.VariantType.Type.(type) { + switch x := typ.VariantType.GetType().(type) { case *Ydb.VariantType_TupleItems: - if i >= len(x.TupleItems.Elements) { + if i >= len(x.TupleItems.GetElements()) { _ = s.errorf(0, "unimplemented") return } - return "", x.TupleItems.Elements[i] + return "", x.TupleItems.GetElements()[i] case *Ydb.VariantType_StructItems: - if i >= len(x.StructItems.Members) { + if i >= len(x.StructItems.GetMembers()) { _ = s.errorf(0, "unimplemented") return } - m := x.StructItems.Members[i] + m := x.StructItems.GetMembers()[i] - return m.Name, m.Type + return m.GetName(), m.GetType() default: panic("unexpected variant items types") @@ -628,7 +629,7 @@ func (s *rawConverter) variant() (v *Ydb.Value, index uint32) { return } x := s.stack.current() // Is not nil if unwrapValue succeeded. - index = x.v.VariantIndex + index = x.v.GetVariantIndex() return } @@ -640,7 +641,7 @@ func (s *rawConverter) itemsIn() int { } s.stack.enter() - return len(x.v.Items) + return len(x.v.GetItems()) } func (s *rawConverter) itemsOut() { @@ -658,7 +659,7 @@ func (s *rawConverter) pairsIn() int { } s.stack.enter() - return len(x.v.Pairs) + return len(x.v.GetPairs()) } func (s *rawConverter) pairsOut() { @@ -679,8 +680,8 @@ func (s *rawConverter) boundsCheck(n, i int) bool { return true } -func (s *scanner) assertTypeOptional(typ *Ydb.Type) (t *Ydb.Type_OptionalType) { - x := typ.Type +func (s *valueScanner) assertTypeOptional(typ *Ydb.Type) (t *Ydb.Type_OptionalType) { + x := typ.GetType() if t, _ = x.(*Ydb.Type_OptionalType); t == nil { s.typeError(x, t) } @@ -712,8 +713,8 @@ func (s *rawConverter) assertCurrentTypeNullable() bool { func (s *rawConverter) assertCurrentTypeIs(t types.Type) bool { c := s.stack.current() - act := value.TypeFromYDB(c.t) - if !value.TypesEqual(act, t) { + act := types.TypeFromYDB(c.t) + if !types.Equal(act, t) { _ = s.errorf( 1, "unexpected types at %q %s: %s; want %s", @@ -744,7 +745,7 @@ func (s *rawConverter) assertCurrentTypeDecimal(t types.Type) bool { } func (s *rawConverter) assertTypeList(typ *Ydb.Type) (t *Ydb.Type_ListType) { - x := typ.Type + x := typ.GetType() if t, _ = x.(*Ydb.Type_ListType); t == nil { s.typeError(x, t) } @@ -753,7 +754,7 @@ func (s *rawConverter) assertTypeList(typ *Ydb.Type) (t *Ydb.Type_ListType) { } func (s *rawConverter) assertTypeTuple(typ *Ydb.Type) (t *Ydb.Type_TupleType) { - x := typ.Type + x := typ.GetType() if t, _ = x.(*Ydb.Type_TupleType); t == nil { s.typeError(x, t) } @@ -762,7 +763,7 @@ func (s *rawConverter) assertTypeTuple(typ *Ydb.Type) (t *Ydb.Type_TupleType) { } func (s *rawConverter) assertTypeStruct(typ *Ydb.Type) (t *Ydb.Type_StructType) { - x := typ.Type + x := typ.GetType() if t, _ = x.(*Ydb.Type_StructType); t == nil { s.typeError(x, t) } @@ -771,7 +772,7 @@ func (s *rawConverter) assertTypeStruct(typ *Ydb.Type) (t *Ydb.Type_StructType) } func (s *rawConverter) assertTypeDict(typ *Ydb.Type) (t *Ydb.Type_DictType) { - x := typ.Type + x := typ.GetType() if t, _ = x.(*Ydb.Type_DictType); t == nil { s.typeError(x, t) } @@ -780,7 +781,7 @@ func (s *rawConverter) assertTypeDict(typ *Ydb.Type) (t *Ydb.Type_DictType) { } func (s *rawConverter) assertTypeDecimal(typ *Ydb.Type) (t *Ydb.Type_DecimalType) { - x := typ.Type + x := typ.GetType() if t, _ = x.(*Ydb.Type_DecimalType); t == nil { s.typeError(x, t) } @@ -789,7 +790,7 @@ func (s *rawConverter) assertTypeDecimal(typ *Ydb.Type) (t *Ydb.Type_DecimalType } func (s *rawConverter) assertTypeVariant(typ *Ydb.Type) (t *Ydb.Type_VariantType) { - x := typ.Type + x := typ.GetType() if t, _ = x.(*Ydb.Type_VariantType); t == nil { s.typeError(x, t) } @@ -818,7 +819,7 @@ func nameIface(v interface{}) string { t := reflect.TypeOf(v) s := t.String() s = strings.TrimPrefix(s, "*Ydb.Value_") - s = strings.TrimSuffix(s, "Value") + s = strings.TrimSuffix(s, "valueType") s = strings.TrimPrefix(s, "*Ydb.Type_") s = strings.TrimSuffix(s, "Type") diff --git a/internal/table/scanner/scanner.go b/internal/table/scanner/scanner.go index 5cda9e3fd..8a3effb2b 100644 --- a/internal/table/scanner/scanner.go +++ b/internal/table/scanner/scanner.go @@ -11,6 +11,9 @@ import ( "github.com/ydb-platform/ydb-go-genproto/protos/Ydb" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/decimal" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/scanner" + internalTypes "github.com/ydb-platform/ydb-go-sdk/v3/internal/types" "github.com/ydb-platform/ydb-go-sdk/v3/internal/value" "github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors" "github.com/ydb-platform/ydb-go-sdk/v3/internal/xstring" @@ -19,10 +22,9 @@ import ( "github.com/ydb-platform/ydb-go-sdk/v3/table/result" "github.com/ydb-platform/ydb-go-sdk/v3/table/result/indexed" "github.com/ydb-platform/ydb-go-sdk/v3/table/result/named" - "github.com/ydb-platform/ydb-go-sdk/v3/table/types" ) -type scanner struct { +type valueScanner struct { set *Ydb.ResultSet row *Ydb.Value converter *rawConverter @@ -39,59 +41,59 @@ type scanner struct { } // ColumnCount returns number of columns in the current result set. -func (s *scanner) ColumnCount() int { +func (s *valueScanner) ColumnCount() int { if s.set == nil { return 0 } - return len(s.set.Columns) + return len(s.set.GetColumns()) } // Columns allows to iterate over all columns of the current result set. -func (s *scanner) Columns(it func(options.Column)) { +func (s *valueScanner) Columns(it func(options.Column)) { if s.set == nil { return } - for _, m := range s.set.Columns { + for _, m := range s.set.GetColumns() { it(options.Column{ - Name: m.Name, - Type: value.TypeFromYDB(m.Type), + Name: m.GetName(), + Type: internalTypes.TypeFromYDB(m.GetType()), }) } } // RowCount returns number of rows in the result set. -func (s *scanner) RowCount() int { +func (s *valueScanner) RowCount() int { if s.set == nil { return 0 } - return len(s.set.Rows) + return len(s.set.GetRows()) } // ItemCount returns number of items in the current row. -func (s *scanner) ItemCount() int { +func (s *valueScanner) ItemCount() int { if s.row == nil { return 0 } - return len(s.row.Items) + return len(s.row.GetItems()) } // HasNextRow reports whether result row may be advanced. // It may be useful to call HasNextRow() instead of NextRow() to look ahead // without advancing the result rows. -func (s *scanner) HasNextRow() bool { - return s.err == nil && s.set != nil && s.nextRow < len(s.set.Rows) +func (s *valueScanner) HasNextRow() bool { + return s.err == nil && s.set != nil && s.nextRow < len(s.set.GetRows()) } // NextRow selects next row in the current result set. // It returns false if there are no more rows in the result set. -func (s *scanner) NextRow() bool { +func (s *valueScanner) NextRow() bool { if !s.HasNextRow() { return false } - s.row = s.set.Rows[s.nextRow] + s.row = s.set.GetRows()[s.nextRow] s.nextRow++ s.nextItem = 0 s.stack.reset() @@ -99,7 +101,7 @@ func (s *scanner) NextRow() bool { return true } -func (s *scanner) preScanChecks(lenValues int) (err error) { +func (s *valueScanner) preScanChecks(lenValues int) (err error) { if s.columnIndexes != nil { if len(s.columnIndexes) != lenValues { return s.errorf( @@ -120,7 +122,7 @@ func (s *scanner) preScanChecks(lenValues int) (err error) { return s.Err() } -func (s *scanner) ScanWithDefaults(values ...indexed.Required) (err error) { +func (s *valueScanner) ScanWithDefaults(values ...indexed.Required) (err error) { if err = s.preScanChecks(len(values)); err != nil { return } @@ -148,7 +150,7 @@ func (s *scanner) ScanWithDefaults(values ...indexed.Required) (err error) { return s.Err() } -func (s *scanner) Scan(values ...indexed.RequiredOrOptional) (err error) { +func (s *valueScanner) Scan(values ...indexed.RequiredOrOptional) (err error) { if err = s.preScanChecks(len(values)); err != nil { return } @@ -176,7 +178,7 @@ func (s *scanner) Scan(values ...indexed.RequiredOrOptional) (err error) { return s.Err() } -func (s *scanner) ScanNamed(namedValues ...named.Value) error { +func (s *valueScanner) ScanNamed(namedValues ...named.Value) error { if err := s.Err(); err != nil { return err } @@ -198,7 +200,7 @@ func (s *scanner) ScanNamed(namedValues ...named.Value) error { case named.TypeOptionalWithUseDefault: s.scanOptional(namedValues[i].Value, true) default: - panic(fmt.Sprintf("unknown type of named.Value: %d", t)) + panic(fmt.Sprintf("unknown type of named.valueType: %d", t)) } } s.nextItem += len(namedValues) @@ -207,27 +209,27 @@ func (s *scanner) ScanNamed(namedValues ...named.Value) error { } // Truncated returns true if current result set has been truncated by server -func (s *scanner) Truncated() bool { +func (s *valueScanner) Truncated() bool { if s.set == nil { _ = s.errorf(0, "there are no sets in the scanner") return false } - return s.set.Truncated + return s.set.GetTruncated() } // Truncated returns true if current result set has been truncated by server -func (s *scanner) truncated() bool { +func (s *valueScanner) truncated() bool { if s.set == nil { return false } - return s.set.Truncated + return s.set.GetTruncated() } // Err returns error caused Scanner to be broken. -func (s *scanner) Err() error { +func (s *valueScanner) Err() error { s.errMtx.RLock() defer s.errMtx.RUnlock() if s.err != nil { @@ -248,7 +250,7 @@ func (s *scanner) Err() error { } // Must not be exported. -func (s *scanner) reset(set *Ydb.ResultSet, columnNames ...string) { +func (s *valueScanner) reset(set *Ydb.ResultSet, columnNames ...string) { s.set = set s.row = nil s.nextRow = 0 @@ -257,11 +259,11 @@ func (s *scanner) reset(set *Ydb.ResultSet, columnNames ...string) { s.setColumnIndexes(columnNames) s.stack.reset() s.converter = &rawConverter{ - scanner: s, + valueScanner: s, } } -func (s *scanner) path() string { +func (s *valueScanner) path() string { buf := xstring.Buffer() defer buf.Free() _, _ = s.writePathTo(buf) @@ -269,7 +271,7 @@ func (s *scanner) path() string { return buf.String() } -func (s *scanner) writePathTo(w io.Writer) (n int64, err error) { +func (s *valueScanner) writePathTo(w io.Writer) (n int64, err error) { x := s.stack.current() st := x.name m, err := io.WriteString(w, st) @@ -281,42 +283,42 @@ func (s *scanner) writePathTo(w io.Writer) (n int64, err error) { return n, nil } -func (s *scanner) getType() types.Type { +func (s *valueScanner) getType() internalTypes.Type { x := s.stack.current() if x.isEmpty() { return nil } - return value.TypeFromYDB(x.t) + return internalTypes.TypeFromYDB(x.t) } -func (s *scanner) hasItems() bool { +func (s *valueScanner) hasItems() bool { return s.err == nil && s.set != nil && s.row != nil } -func (s *scanner) seekItemByID(id int) error { - if !s.hasItems() || id >= len(s.set.Columns) { +func (s *valueScanner) seekItemByID(id int) error { + if !s.hasItems() || id >= len(s.set.GetColumns()) { return s.notFoundColumnByIndex(id) } - col := s.set.Columns[id] - s.stack.scanItem.name = col.Name - s.stack.scanItem.t = col.Type - s.stack.scanItem.v = s.row.Items[id] + col := s.set.GetColumns()[id] + s.stack.scanItem.name = col.GetName() + s.stack.scanItem.t = col.GetType() + s.stack.scanItem.v = s.row.GetItems()[id] return nil } -func (s *scanner) seekItemByName(name string) error { +func (s *valueScanner) seekItemByName(name string) error { if !s.hasItems() { return s.notFoundColumnName(name) } - for i, c := range s.set.Columns { - if name != c.Name { + for i, c := range s.set.GetColumns() { + if name != c.GetName() { continue } - s.stack.scanItem.name = c.Name - s.stack.scanItem.t = c.Type - s.stack.scanItem.v = s.row.Items[i] + s.stack.scanItem.name = c.GetName() + s.stack.scanItem.t = c.GetType() + s.stack.scanItem.v = s.row.GetItems()[i] return s.Err() } @@ -324,7 +326,7 @@ func (s *scanner) seekItemByName(name string) error { return s.notFoundColumnName(name) } -func (s *scanner) setColumnIndexes(columns []string) { +func (s *valueScanner) setColumnIndexes(columns []string) { if columns == nil { s.columnIndexes = nil @@ -333,8 +335,8 @@ func (s *scanner) setColumnIndexes(columns []string) { s.columnIndexes = make([]int, len(columns)) for i, col := range columns { found := false - for j, c := range s.set.Columns { - if c.Name == col { + for j, c := range s.set.GetColumns() { + if c.GetName() == col { s.columnIndexes[i] = j found = true @@ -368,7 +370,7 @@ func (s *scanner) setColumnIndexes(columns []string) { // [16]byte // //nolint:gocyclo -func (s *scanner) any() interface{} { +func (s *valueScanner) any() interface{} { x := s.stack.current() if s.Err() != nil || x.isEmpty() { return nil @@ -383,74 +385,74 @@ func (s *scanner) any() interface{} { x = s.stack.current() } - t := value.TypeFromYDB(x.t) - p, primitive := t.(value.PrimitiveType) + t := internalTypes.TypeFromYDB(x.t) + p, primitive := t.(internalTypes.Primitive) if !primitive { return s.value() } switch p { - case value.TypeBool: + case internalTypes.Bool: return s.bool() - case value.TypeInt8: + case internalTypes.Int8: return s.int8() - case value.TypeUint8: + case internalTypes.Uint8: return s.uint8() - case value.TypeInt16: + case internalTypes.Int16: return s.int16() - case value.TypeUint16: + case internalTypes.Uint16: return s.uint16() - case value.TypeInt32: + case internalTypes.Int32: return s.int32() - case value.TypeFloat: + case internalTypes.Float: return s.float() - case value.TypeDouble: + case internalTypes.Double: return s.double() - case value.TypeBytes: + case internalTypes.Bytes: return s.bytes() - case value.TypeUUID: + case internalTypes.UUID: return s.uint128() - case value.TypeUint32: + case internalTypes.Uint32: return s.uint32() - case value.TypeDate: + case internalTypes.Date: return value.DateToTime(s.uint32()) - case value.TypeDatetime: + case internalTypes.Datetime: return value.DatetimeToTime(s.uint32()) - case value.TypeUint64: + case internalTypes.Uint64: return s.uint64() - case value.TypeTimestamp: + case internalTypes.Timestamp: return value.TimestampToTime(s.uint64()) - case value.TypeInt64: + case internalTypes.Int64: return s.int64() - case value.TypeInterval: + case internalTypes.Interval: return value.IntervalToDuration(s.int64()) - case value.TypeTzDate: + case internalTypes.TzDate: src, err := value.TzDateToTime(s.text()) if err != nil { - _ = s.errorf(0, "scanner.any(): %w", err) + _ = s.errorf(0, "valueScanner.any(): %w", err) } return src - case value.TypeTzDatetime: + case internalTypes.TzDatetime: src, err := value.TzDatetimeToTime(s.text()) if err != nil { - _ = s.errorf(0, "scanner.any(): %w", err) + _ = s.errorf(0, "valueScanner.any(): %w", err) } return src - case value.TypeTzTimestamp: + case internalTypes.TzTimestamp: src, err := value.TzTimestampToTime(s.text()) if err != nil { - _ = s.errorf(0, "scanner.any(): %w", err) + _ = s.errorf(0, "valueScanner.any(): %w", err) } return src - case value.TypeText, value.TypeDyNumber: + case internalTypes.Text, internalTypes.DyNumber: return s.text() case - value.TypeYSON, - value.TypeJSON, - value.TypeJSONDocument: + internalTypes.YSON, + internalTypes.JSON, + internalTypes.JSONDocument: return xstring.ToBytes(s.text()) default: _ = s.errorf(0, "unknown primitive types") @@ -459,28 +461,28 @@ func (s *scanner) any() interface{} { } } -// Value returns current item under scan as ydb.Value types. -func (s *scanner) value() types.Value { +// valueType returns current item under scan as ydb.valueType types +func (s *valueScanner) value() value.Value { x := s.stack.current() return value.FromYDB(x.t, x.v) } -func (s *scanner) isCurrentTypeOptional() bool { +func (s *valueScanner) isCurrentTypeOptional() bool { c := s.stack.current() return isOptional(c.t) } -func (s *scanner) isNull() bool { +func (s *valueScanner) isNull() bool { _, yes := s.stack.currentValue().(*Ydb.Value_NullFlagValue) return yes } -// unwrap current item under scan interpreting it as Optional types. +// unwrap current item under scan interpreting it as Optional types // ignores if type is not optional -func (s *scanner) unwrap() { +func (s *valueScanner) unwrap() { if s.Err() != nil { return } @@ -490,13 +492,13 @@ func (s *scanner) unwrap() { return } - if isOptional(t.OptionalType.Item) { + if isOptional(t.OptionalType.GetItem()) { s.stack.scanItem.v = s.unwrapValue() } - s.stack.scanItem.t = t.OptionalType.Item + s.stack.scanItem.t = t.OptionalType.GetItem() } -func (s *scanner) unwrapValue() (v *Ydb.Value) { +func (s *valueScanner) unwrapValue() (v *Ydb.Value) { x, _ := s.stack.currentValue().(*Ydb.Value_NestedValue) if x == nil { s.valueTypeError(s.stack.currentValue(), x) @@ -507,25 +509,25 @@ func (s *scanner) unwrapValue() (v *Ydb.Value) { return x.NestedValue } -func (s *scanner) unwrapDecimal() (v types.Decimal) { +func (s *valueScanner) unwrapDecimal() decimal.Decimal { if s.Err() != nil { - return + return decimal.Decimal{} } s.unwrap() d := s.assertTypeDecimal(s.stack.current().t) if d == nil { - return + return decimal.Decimal{} } - return types.Decimal{ + return decimal.Decimal{ Bytes: s.uint128(), - Precision: d.DecimalType.Precision, - Scale: d.DecimalType.Scale, + Precision: d.DecimalType.GetPrecision(), + Scale: d.DecimalType.GetScale(), } } -func (s *scanner) assertTypeDecimal(typ *Ydb.Type) (t *Ydb.Type_DecimalType) { - x := typ.Type +func (s *valueScanner) assertTypeDecimal(typ *Ydb.Type) (t *Ydb.Type_DecimalType) { + x := typ.GetType() if t, _ = x.(*Ydb.Type_DecimalType); t == nil { s.typeError(x, t) } @@ -533,7 +535,7 @@ func (s *scanner) assertTypeDecimal(typ *Ydb.Type) (t *Ydb.Type_DecimalType) { return } -func (s *scanner) bool() (v bool) { +func (s *valueScanner) bool() (v bool) { x, _ := s.stack.currentValue().(*Ydb.Value_BoolValue) if x == nil { s.valueTypeError(s.stack.currentValue(), x) @@ -544,7 +546,7 @@ func (s *scanner) bool() (v bool) { return x.BoolValue } -func (s *scanner) int8() (v int8) { +func (s *valueScanner) int8() (v int8) { d := s.int32() if d < math.MinInt8 || math.MaxInt8 < d { _ = s.overflowError(d, v) @@ -555,7 +557,7 @@ func (s *scanner) int8() (v int8) { return int8(d) } -func (s *scanner) uint8() (v uint8) { +func (s *valueScanner) uint8() (v uint8) { d := s.uint32() if d > math.MaxUint8 { _ = s.overflowError(d, v) @@ -566,7 +568,7 @@ func (s *scanner) uint8() (v uint8) { return uint8(d) } -func (s *scanner) int16() (v int16) { +func (s *valueScanner) int16() (v int16) { d := s.int32() if d < math.MinInt16 || math.MaxInt16 < d { _ = s.overflowError(d, v) @@ -577,7 +579,7 @@ func (s *scanner) int16() (v int16) { return int16(d) } -func (s *scanner) uint16() (v uint16) { +func (s *valueScanner) uint16() (v uint16) { d := s.uint32() if d > math.MaxUint16 { _ = s.overflowError(d, v) @@ -588,7 +590,7 @@ func (s *scanner) uint16() (v uint16) { return uint16(d) } -func (s *scanner) int32() (v int32) { +func (s *valueScanner) int32() (v int32) { x, _ := s.stack.currentValue().(*Ydb.Value_Int32Value) if x == nil { s.valueTypeError(s.stack.currentValue(), x) @@ -599,7 +601,7 @@ func (s *scanner) int32() (v int32) { return x.Int32Value } -func (s *scanner) uint32() (v uint32) { +func (s *valueScanner) uint32() (v uint32) { x, _ := s.stack.currentValue().(*Ydb.Value_Uint32Value) if x == nil { s.valueTypeError(s.stack.currentValue(), x) @@ -610,7 +612,7 @@ func (s *scanner) uint32() (v uint32) { return x.Uint32Value } -func (s *scanner) int64() (v int64) { +func (s *valueScanner) int64() (v int64) { x, _ := s.stack.currentValue().(*Ydb.Value_Int64Value) if x == nil { s.valueTypeError(s.stack.currentValue(), x) @@ -621,7 +623,7 @@ func (s *scanner) int64() (v int64) { return x.Int64Value } -func (s *scanner) uint64() (v uint64) { +func (s *valueScanner) uint64() (v uint64) { x, _ := s.stack.currentValue().(*Ydb.Value_Uint64Value) if x == nil { s.valueTypeError(s.stack.currentValue(), x) @@ -632,7 +634,7 @@ func (s *scanner) uint64() (v uint64) { return x.Uint64Value } -func (s *scanner) float() (v float32) { +func (s *valueScanner) float() (v float32) { x, _ := s.stack.currentValue().(*Ydb.Value_FloatValue) if x == nil { s.valueTypeError(s.stack.currentValue(), x) @@ -643,7 +645,7 @@ func (s *scanner) float() (v float32) { return x.FloatValue } -func (s *scanner) double() (v float64) { +func (s *valueScanner) double() (v float64) { x, _ := s.stack.currentValue().(*Ydb.Value_DoubleValue) if x == nil { s.valueTypeError(s.stack.currentValue(), x) @@ -654,7 +656,7 @@ func (s *scanner) double() (v float64) { return x.DoubleValue } -func (s *scanner) bytes() (v []byte) { +func (s *valueScanner) bytes() (v []byte) { x, _ := s.stack.currentValue().(*Ydb.Value_BytesValue) if x == nil { s.valueTypeError(s.stack.currentValue(), x) @@ -665,7 +667,7 @@ func (s *scanner) bytes() (v []byte) { return x.BytesValue } -func (s *scanner) text() (v string) { +func (s *valueScanner) text() (v string) { x, _ := s.stack.currentValue().(*Ydb.Value_TextValue) if x == nil { s.valueTypeError(s.stack.currentValue(), x) @@ -676,7 +678,7 @@ func (s *scanner) text() (v string) { return x.TextValue } -func (s *scanner) low128() (v uint64) { +func (s *valueScanner) low128() (v uint64) { x, _ := s.stack.currentValue().(*Ydb.Value_Low_128) if x == nil { s.valueTypeError(s.stack.currentValue(), x) @@ -687,7 +689,7 @@ func (s *scanner) low128() (v uint64) { return x.Low_128 } -func (s *scanner) uint128() (v [16]byte) { +func (s *valueScanner) uint128() (v [16]byte) { c := s.stack.current() if c.isEmpty() { _ = s.errorf(0, "not implemented convert to [16]byte") @@ -695,19 +697,19 @@ func (s *scanner) uint128() (v [16]byte) { return } lo := s.low128() - hi := c.v.High_128 + hi := c.v.GetHigh_128() return value.BigEndianUint128(hi, lo) } -func (s *scanner) null() { +func (s *valueScanner) null() { x, _ := s.stack.currentValue().(*Ydb.Value_NullFlagValue) if x == nil { s.valueTypeError(s.stack.currentValue(), x) } } -func (s *scanner) setTime(dst *time.Time) { +func (s *valueScanner) setTime(dst *time.Time) { switch t := s.stack.current().t.GetTypeId(); t { case Ydb.Type_DATE: *dst = value.DateToTime(s.uint32()) @@ -718,27 +720,27 @@ func (s *scanner) setTime(dst *time.Time) { case Ydb.Type_TZ_DATE: src, err := value.TzDateToTime(s.text()) if err != nil { - _ = s.errorf(0, "scanner.setTime(): %w", err) + _ = s.errorf(0, "valueScanner.setTime(): %w", err) } *dst = src case Ydb.Type_TZ_DATETIME: src, err := value.TzDatetimeToTime(s.text()) if err != nil { - _ = s.errorf(0, "scanner.setTime(): %w", err) + _ = s.errorf(0, "valueScanner.setTime(): %w", err) } *dst = src case Ydb.Type_TZ_TIMESTAMP: src, err := value.TzTimestampToTime(s.text()) if err != nil { - _ = s.errorf(0, "scanner.setTime(): %w", err) + _ = s.errorf(0, "valueScanner.setTime(): %w", err) } *dst = src default: - _ = s.errorf(0, "scanner.setTime(): incorrect source types %s", t) + _ = s.errorf(0, "valueScanner.setTime(): incorrect source types %s", t) } } -func (s *scanner) setString(dst *string) { +func (s *valueScanner) setString(dst *string) { switch t := s.stack.current().t.GetTypeId(); t { case Ydb.Type_UUID: src := s.uint128() @@ -752,7 +754,7 @@ func (s *scanner) setString(dst *string) { } } -func (s *scanner) setByte(dst *[]byte) { +func (s *valueScanner) setByte(dst *[]byte) { switch t := s.stack.current().t.GetTypeId(); t { case Ydb.Type_UUID: src := s.uint128() @@ -766,7 +768,7 @@ func (s *scanner) setByte(dst *[]byte) { } } -func (s *scanner) trySetByteArray(v interface{}, optional, def bool) bool { +func (s *valueScanner) trySetByteArray(v interface{}, optional, def bool) bool { rv := reflect.ValueOf(v) if rv.Kind() == reflect.Ptr { rv = rv.Elem() @@ -808,7 +810,7 @@ func (s *scanner) trySetByteArray(v interface{}, optional, def bool) bool { } //nolint:gocyclo -func (s *scanner) scanRequired(v interface{}) { +func (s *valueScanner) scanRequired(v interface{}) { switch v := v.(type) { case *bool: *v = s.bool() @@ -848,11 +850,11 @@ func (s *scanner) scanRequired(v interface{}) { *v = s.uint128() case *interface{}: *v = s.any() - case *types.Value: + case *value.Value: *v = s.value() - case *types.Decimal: + case *decimal.Decimal: *v = s.unwrapDecimal() - case types.Scanner: + case scanner.Scanner: err := v.UnmarshalYDB(s.converter) if err != nil { _ = s.errorf(0, "ydb.Scanner error: %w", err) @@ -865,9 +867,9 @@ func (s *scanner) scanRequired(v interface{}) { case json.Unmarshaler: var err error switch s.getType() { - case types.TypeJSON: + case internalTypes.JSON: err = v.UnmarshalJSON(s.converter.JSON()) - case types.TypeJSONDocument: + case internalTypes.JSONDocument: err = v.UnmarshalJSON(s.converter.JSONDocument()) default: _ = s.errorf(0, "ydb required type %T not unsupported for applying to json.Unmarshaler", s.getType()) @@ -884,7 +886,7 @@ func (s *scanner) scanRequired(v interface{}) { } //nolint:gocyclo -func (s *scanner) scanOptional(v interface{}, defaultValueForOptional bool) { +func (s *valueScanner) scanOptional(v interface{}, defaultValueForOptional bool) { if defaultValueForOptional { if s.isNull() { s.setDefaultValue(v) @@ -1035,16 +1037,16 @@ func (s *scanner) scanOptional(v interface{}, defaultValueForOptional bool) { src := s.any() *v = &src } - case *types.Value: + case *value.Value: *v = s.value() - case **types.Decimal: + case **decimal.Decimal: if s.isNull() { *v = nil } else { src := s.unwrapDecimal() *v = &src } - case types.Scanner: + case scanner.Scanner: err := v.UnmarshalYDB(s.converter) if err != nil { _ = s.errorf(0, "ydb.Scanner error: %w", err) @@ -1058,13 +1060,13 @@ func (s *scanner) scanOptional(v interface{}, defaultValueForOptional bool) { s.unwrap() var err error switch s.getType() { - case types.TypeJSON: + case internalTypes.JSON: if s.isNull() { err = v.UnmarshalJSON(nil) } else { err = v.UnmarshalJSON(s.converter.JSON()) } - case types.TypeJSONDocument: + case internalTypes.JSONDocument: if s.isNull() { err = v.UnmarshalJSON(nil) } else { @@ -1090,7 +1092,7 @@ func (s *scanner) scanOptional(v interface{}, defaultValueForOptional bool) { } } -func (s *scanner) setDefaultValue(dst interface{}) { +func (s *valueScanner) setDefaultValue(dst interface{}) { switch v := dst.(type) { case *bool: *v = false @@ -1126,16 +1128,16 @@ func (s *scanner) setDefaultValue(dst interface{}) { *v = [16]byte{} case *interface{}: *v = nil - case *types.Value: + case *value.Value: *v = s.value() - case *types.Decimal: - *v = types.Decimal{} + case *decimal.Decimal: + *v = decimal.Decimal{} case sql.Scanner: err := v.Scan(nil) if err != nil { _ = s.errorf(0, "sql.Scanner error: %w", err) } - case types.Scanner: + case scanner.Scanner: err := v.UnmarshalYDB(s.converter) if err != nil { _ = s.errorf(0, "ydb.Scanner error: %w", err) @@ -1159,7 +1161,7 @@ func (r *baseResult) SetErr(err error) { }) } -func (s *scanner) errorf(depth int, f string, args ...interface{}) error { +func (s *valueScanner) errorf(depth int, f string, args ...interface{}) error { s.errMtx.Lock() defer s.errMtx.Unlock() if s.err != nil { @@ -1170,7 +1172,7 @@ func (s *scanner) errorf(depth int, f string, args ...interface{}) error { return s.err } -func (s *scanner) typeError(act, exp interface{}) { +func (s *valueScanner) typeError(act, exp interface{}) { _ = s.errorf( 2, //nolint:gomnd "unexpected types during scan at %q %s: %s; want %s", @@ -1181,7 +1183,7 @@ func (s *scanner) typeError(act, exp interface{}) { ) } -func (s *scanner) valueTypeError(act, exp interface{}) { +func (s *valueScanner) valueTypeError(act, exp interface{}) { // unexpected value during scan at \"migration_status\" Int64: NullFlag; want Int64 _ = s.errorf( 2, //nolint:gomnd @@ -1193,7 +1195,7 @@ func (s *scanner) valueTypeError(act, exp interface{}) { ) } -func (s *scanner) notFoundColumnByIndex(idx int) error { +func (s *valueScanner) notFoundColumnByIndex(idx int) error { return s.errorf( 2, //nolint:gomnd "not found %d column", @@ -1201,7 +1203,7 @@ func (s *scanner) notFoundColumnByIndex(idx int) error { ) } -func (s *scanner) notFoundColumnName(name string) error { +func (s *valueScanner) notFoundColumnName(name string) error { return s.errorf( 2, //nolint:gomnd "not found column '%s'", @@ -1209,7 +1211,7 @@ func (s *scanner) notFoundColumnName(name string) error { ) } -func (s *scanner) noColumnError(name string) error { +func (s *valueScanner) noColumnError(name string) error { return s.errorf( 2, //nolint:gomnd "no column %q", @@ -1217,7 +1219,7 @@ func (s *scanner) noColumnError(name string) error { ) } -func (s *scanner) overflowError(i, n interface{}) error { +func (s *valueScanner) overflowError(i, n interface{}) error { return s.errorf( 2, //nolint:gomnd "overflow error: %d overflows capacity of %t", @@ -1230,7 +1232,7 @@ var emptyItem item type item struct { name string - i int // Index in listing types. + i int // Index in listing types t *Ydb.Type v *Ydb.Value } @@ -1307,7 +1309,7 @@ func (s *scanStack) current() item { func (s *scanStack) currentValue() interface{} { if v := s.current().v; v != nil { - return v.Value + return v.GetValue() } return nil @@ -1315,7 +1317,7 @@ func (s *scanStack) currentValue() interface{} { func (s *scanStack) currentType() interface{} { if t := s.current().t; t != nil { - return t.Type + return t.GetType() } return nil @@ -1325,7 +1327,7 @@ func isOptional(typ *Ydb.Type) bool { if typ == nil { return false } - _, yes := typ.Type.(*Ydb.Type_OptionalType) + _, yes := typ.GetType().(*Ydb.Type_OptionalType) return yes } diff --git a/internal/table/scanner/scanner_data_test.go b/internal/table/scanner/scanner_data_test.go index 396dd3d28..f47fffbac 100644 --- a/internal/table/scanner/scanner_data_test.go +++ b/internal/table/scanner/scanner_data_test.go @@ -208,7 +208,7 @@ var scannerData = []struct { setColumnIndexes: []int{0, 2, 1}, }, { - name: "Scan int64, float, json as ydb.Value", + name: "Scan int64, float, json as ydb.valueType", count: 100, columns: []*column{{ name: "valueint64", @@ -470,8 +470,8 @@ var scannerData = []struct { }, } -func initScanner() *scanner { - res := scanner{ +func initScanner() *valueScanner { + res := valueScanner{ set: &Ydb.ResultSet{ Columns: nil, Rows: nil, @@ -491,7 +491,7 @@ func initScanner() *scanner { return &res } -func PrepareScannerPerformanceTest(count int) *scanner { +func PrepareScannerPerformanceTest(count int) *valueScanner { res := initScanner() res.set.Columns = []*Ydb.Column{{ Name: "series_id", @@ -529,7 +529,7 @@ func PrepareScannerPerformanceTest(count int) *scanner { }} res.set.Rows = []*Ydb.Value{} for i := 0; i < count; i++ { - res.set.Rows = append(res.set.Rows, &Ydb.Value{ + res.set.Rows = append(res.set.GetRows(), &Ydb.Value{ Items: []*Ydb.Value{{ Value: &Ydb.Value_Uint64Value{ Uint64Value: uint64(i), diff --git a/internal/table/scanner/scanner_test.go b/internal/table/scanner/scanner_test.go index cc595229a..44f09ff45 100644 --- a/internal/table/scanner/scanner_test.go +++ b/internal/table/scanner/scanner_test.go @@ -530,7 +530,7 @@ func getResultSet(count int, col []*column) (result *Ydb.ResultSet, testValues [ } } result.Columns = append( - result.Columns, + result.GetColumns(), &Ydb.Column{ Name: c.name, Type: t, @@ -543,12 +543,12 @@ func getResultSet(count int, col []*column) (result *Ydb.ResultSet, testValues [ for i := 0; i < count; i++ { var items []*Ydb.Value var vals []indexed.RequiredOrOptional - for j := range result.Columns { + for j := range result.GetColumns() { v, val := valueFromPrimitiveTypeID(col[j], r) vals = append(vals, val) items = append(items, v) } - result.Rows = append(result.Rows, &Ydb.Value{ + result.Rows = append(result.GetRows(), &Ydb.Value{ Items: items, }) testValues[i] = vals diff --git a/internal/table/scanner/stats.go b/internal/table/scanner/stats.go index 250205c5b..d9772e34c 100644 --- a/internal/table/scanner/stats.go +++ b/internal/table/scanner/stats.go @@ -20,14 +20,14 @@ func (s *queryStats) ProcessCPUTime() time.Duration { } func (s *queryStats) Compilation() (c *stats.CompilationStats) { - if s.stats == nil || s.stats.Compilation == nil { + if s.stats == nil || s.stats.GetCompilation() == nil { return nil } return &stats.CompilationStats{ - FromCache: s.stats.Compilation.FromCache, - Duration: time.Microsecond * time.Duration(s.stats.Compilation.DurationUs), - CPUTime: time.Microsecond * time.Duration(s.stats.Compilation.CpuTimeUs), + FromCache: s.stats.GetCompilation().GetFromCache(), + Duration: time.Microsecond * time.Duration(s.stats.GetCompilation().GetDurationUs()), + CPUTime: time.Microsecond * time.Duration(s.stats.GetCompilation().GetCpuTimeUs()), } } @@ -40,32 +40,32 @@ func (s *queryStats) QueryAST() string { } func (s *queryStats) TotalCPUTime() time.Duration { - return time.Microsecond * time.Duration(s.stats.TotalCpuTimeUs) + return time.Microsecond * time.Duration(s.stats.GetTotalCpuTimeUs()) } func (s *queryStats) TotalDuration() time.Duration { - return time.Microsecond * time.Duration(s.stats.TotalDurationUs) + return time.Microsecond * time.Duration(s.stats.GetTotalDurationUs()) } // NextPhase returns next execution phase within query. // If ok flag is false, then there are no more phases and p is invalid. func (s *queryStats) NextPhase() (p stats.QueryPhase, ok bool) { - if s.pos >= len(s.stats.QueryPhases) { + if s.pos >= len(s.stats.GetQueryPhases()) { return } - x := s.stats.QueryPhases[s.pos] + x := s.stats.GetQueryPhases()[s.pos] if x == nil { return } s.pos++ return &queryPhase{ - tables: x.TableAccess, + tables: x.GetTableAccess(), pos: 0, - duration: time.Microsecond * time.Duration(x.DurationUs), - cpuTime: time.Microsecond * time.Duration(x.CpuTimeUs), - affectedShards: x.AffectedShards, - literalPhase: x.LiteralPhase, + duration: time.Microsecond * time.Duration(x.GetDurationUs()), + cpuTime: time.Microsecond * time.Duration(x.GetCpuTimeUs()), + affectedShards: x.GetAffectedShards(), + literalPhase: x.GetLiteralPhase(), }, true } @@ -91,10 +91,10 @@ func (q *queryPhase) NextTableAccess() (t *stats.TableAccess, ok bool) { q.pos++ return &stats.TableAccess{ - Name: x.Name, - Reads: initOperationStats(x.Reads), - Updates: initOperationStats(x.Updates), - Deletes: initOperationStats(x.Deletes), + Name: x.GetName(), + Reads: initOperationStats(x.GetReads()), + Updates: initOperationStats(x.GetUpdates()), + Deletes: initOperationStats(x.GetDeletes()), }, true } @@ -120,7 +120,7 @@ func initOperationStats(x *Ydb_TableStats.OperationStats) stats.OperationStats { } return stats.OperationStats{ - Rows: x.Rows, - Bytes: x.Bytes, + Rows: x.GetRows(), + Bytes: x.GetBytes(), } } diff --git a/internal/table/session.go b/internal/table/session.go index 121d58f14..3c9fe585c 100644 --- a/internal/table/session.go +++ b/internal/table/session.go @@ -22,9 +22,11 @@ import ( "github.com/ydb-platform/ydb-go-sdk/v3/internal/feature" "github.com/ydb-platform/ydb-go-sdk/v3/internal/meta" "github.com/ydb-platform/ydb-go-sdk/v3/internal/operation" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/params" "github.com/ydb-platform/ydb-go-sdk/v3/internal/stack" "github.com/ydb-platform/ydb-go-sdk/v3/internal/table/config" "github.com/ydb-platform/ydb-go-sdk/v3/internal/table/scanner" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/types" "github.com/ydb-platform/ydb-go-sdk/v3/internal/value" "github.com/ydb-platform/ydb-go-sdk/v3/internal/xcontext" "github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors" @@ -32,7 +34,6 @@ import ( "github.com/ydb-platform/ydb-go-sdk/v3/table" "github.com/ydb-platform/ydb-go-sdk/v3/table/options" "github.com/ydb-platform/ydb-go-sdk/v3/table/result" - "github.com/ydb-platform/ydb-go-sdk/v3/table/types" "github.com/ydb-platform/ydb-go-sdk/v3/trace" ) @@ -44,17 +45,15 @@ import ( // Note that after session is no longer needed it should be destroyed by // Close() call. type session struct { + onClose []func(s *session) id string tableService Ydb_Table_V1.TableServiceClient + status table.SessionStatus config *config.Config - - status table.SessionStatus - statusMtx sync.RWMutex - nodeID atomic.Uint32 - lastUsage atomic.Int64 - - onClose []func(s *session) - closeOnce sync.Once + lastUsage atomic.Int64 + statusMtx sync.RWMutex + closeOnce sync.Once + nodeID atomic.Uint32 } func (s *session) LastUsage() time.Time { @@ -117,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) }() @@ -180,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() { @@ -232,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, ) ) @@ -260,7 +261,7 @@ func (s *session) KeepAlive(ctx context.Context) (err error) { return xerrors.WithStackTrace(err) } - switch result.SessionStatus { + switch result.GetSessionStatus() { case Ydb_Table.KeepAliveResult_SESSION_STATUS_READY: s.SetStatus(table.SessionReady) case Ydb_Table.KeepAliveResult_SESSION_STATUS_BUSY: @@ -341,10 +342,10 @@ func (s *session) DescribeTable( []options.Column, len(result.GetColumns()), ) - for i, c := range result.Columns { + for i, c := range result.GetColumns() { cs[i] = options.Column{ Name: c.GetName(), - Type: value.TypeFromYDB(c.GetType()), + Type: types.TypeFromYDB(c.GetType()), Family: c.GetFamily(), } } @@ -353,7 +354,7 @@ func (s *session) DescribeTable( []options.KeyRange, len(result.GetShardKeyBounds())+1, ) - var last types.Value + var last value.Value for i, b := range result.GetShardKeyBounds() { if last != nil { rs[i].From = last @@ -376,18 +377,18 @@ func (s *session) DescribeTable( []options.PartitionStats, len(result.GetTableStats().GetPartitionStats()), ) - for i, v := range result.TableStats.PartitionStats { + for i, v := range result.GetTableStats().GetPartitionStats() { partStats[i].RowsEstimate = v.GetRowsEstimate() partStats[i].StoreSize = v.GetStoreSize() } var creationTime, modificationTime time.Time - if resStats.CreationTime.GetSeconds() != 0 { + if resStats.GetCreationTime().GetSeconds() != 0 { creationTime = time.Unix( resStats.GetCreationTime().GetSeconds(), int64(resStats.GetCreationTime().GetNanos()), ) } - if resStats.ModificationTime.GetSeconds() != 0 { + if resStats.GetModificationTime().GetSeconds() != 0 { modificationTime = time.Unix( resStats.GetModificationTime().GetSeconds(), int64(resStats.GetModificationTime().GetNanos()), @@ -414,10 +415,10 @@ func (s *session) DescribeTable( attrs[k] = v } - indexes := make([]options.IndexDescription, len(result.Indexes)) + indexes := make([]options.IndexDescription, len(result.GetIndexes())) for i, idx := range result.GetIndexes() { var typ options.IndexType - switch idx.Type.(type) { + switch idx.GetType().(type) { case *Ydb_Table.TableIndexDescription_GlobalAsyncIndex: typ = options.IndexTypeGlobalAsync case *Ydb_Table.TableIndexDescription_GlobalIndex: @@ -432,7 +433,7 @@ func (s *session) DescribeTable( } } - changeFeeds := make([]options.ChangefeedDescription, len(result.Changefeeds)) + changeFeeds := make([]options.ChangefeedDescription, len(result.GetChangefeeds())) for i, proto := range result.GetChangefeeds() { changeFeeds[i] = options.NewChangefeedDescription(proto) } @@ -486,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) } } @@ -577,7 +578,7 @@ func copyTables( opt((*options.CopyTablesDesc)(&request)) } } - if len(request.Tables) == 0 { + if len(request.GetTables()) == 0 { return xerrors.WithStackTrace(fmt.Errorf("no CopyTablesItem: %w", errParamsRequired)) } _, err = service.CopyTables(ctx, &request) @@ -614,7 +615,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, ) ) @@ -651,7 +652,7 @@ func (s *session) Explain( Explanation: table.Explanation{ Plan: result.GetQueryPlan(), }, - AST: result.QueryAst, + AST: result.GetQueryAst(), }, nil } @@ -663,7 +664,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, ) ) @@ -699,7 +700,7 @@ func (s *session) Prepare(ctx context.Context, queryText string) (_ table.Statem stmt = &statement{ session: s, query: queryPrepared(result.GetQueryId(), queryText), - params: result.ParametersTypes, + params: result.GetParametersTypes(), } return stmt, nil @@ -710,7 +711,7 @@ func (s *session) Execute( ctx context.Context, txControl *table.TransactionControl, query string, - params *table.QueryParameters, + parameters *params.Parameters, opts ...options.ExecuteDataQueryOption, ) ( txr table.Transaction, r result.Result, err error, @@ -728,10 +729,10 @@ func (s *session) Execute( request.SessionId = s.id request.TxControl = txControl.Desc() - request.Parameters = params.Params().ToYDB(a) + request.Parameters = parameters.ToYDB(a) request.Query = q.toYDB(a) request.QueryCachePolicy = a.TableQueryCachePolicy() - request.QueryCachePolicy.KeepInCache = len(params.Params()) > 0 + request.QueryCachePolicy.KeepInCache = len(request.Parameters) > 0 request.OperationParams = operation.Params(ctx, s.config.OperationTimeout(), s.config.OperationCancelAfter(), @@ -746,8 +747,8 @@ func (s *session) Execute( onDone := trace.TableOnSessionQueryExecute( s.config.Trace(), &ctx, - stack.FunctionID(""), - s, q, params, + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/table.(*session).Execute"), + s, q, parameters, request.QueryCachePolicy.GetKeepInCache(), ) defer func() { @@ -775,7 +776,7 @@ func (s *session) executeQueryResult( id: res.GetTxMeta().GetId(), s: s, } - if txControl.CommitTx { + if txControl.GetCommitTx() { tx.state.Store(txStateCommitted) } else { tx.state.Store(txStateInitialized) @@ -985,8 +986,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{ @@ -998,9 +999,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 { @@ -1024,9 +1023,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()) @@ -1043,7 +1039,7 @@ func (s *session) StreamReadTable( }, func(err error) error { cancel() - onIntermediate(xerrors.HideEOF(err))(xerrors.HideEOF(err)) + onDone(xerrors.HideEOF(err)) return err }, @@ -1054,7 +1050,7 @@ func (s *session) StreamReadTable( func (s *session) ReadRows( ctx context.Context, path string, - keys types.Value, + keys value.Value, opts ...options.ReadRowsOption, ) (_ result.Result, err error) { var ( @@ -1102,20 +1098,20 @@ func (s *session) ReadRows( func (s *session) StreamExecuteScanQuery( ctx context.Context, query string, - params *table.QueryParameters, + parameters *params.Parameters, 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(""), - s, q, params, + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/table.(*session).StreamExecuteScanQuery"), + s, q, parameters, ) request = Ydb_Table.ExecuteScanQueryRequest{ Query: q.toYDB(a), - Parameters: params.Params().ToYDB(a), + Parameters: parameters.ToYDB(a), Mode: Ydb_Table.ExecuteScanQueryRequest_MODE_EXEC, // set default } stream Ydb_Table_V1.TableService_StreamExecuteScanQueryClient @@ -1123,9 +1119,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 { @@ -1149,9 +1143,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()) @@ -1168,7 +1159,7 @@ func (s *session) StreamExecuteScanQuery( }, func(err error) error { cancel() - onIntermediate(xerrors.HideEOF(err))(xerrors.HideEOF(err)) + onDone(xerrors.HideEOF(err)) return err }, @@ -1178,7 +1169,7 @@ func (s *session) StreamExecuteScanQuery( } // BulkUpsert uploads given list of ydb struct values to the table. -func (s *session) BulkUpsert(ctx context.Context, table string, rows types.Value, +func (s *session) BulkUpsert(ctx context.Context, table string, rows value.Value, opts ...options.BulkUpsertOption, ) (err error) { var ( @@ -1186,7 +1177,7 @@ func (s *session) BulkUpsert(ctx context.Context, table string, rows types.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, ) ) @@ -1196,7 +1187,9 @@ func (s *session) BulkUpsert(ctx context.Context, table string, rows types.Value }() for _, opt := range opts { - callOptions = append(callOptions, opt.ApplyBulkUpsertOption()...) + if opt != nil { + callOptions = append(callOptions, opt.ApplyBulkUpsertOption()...) + } } _, err = s.tableService.BulkUpsert(ctx, @@ -1228,9 +1221,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 cd581a9fd..006086369 100644 --- a/internal/table/session_test.go +++ b/internal/table/session_test.go @@ -22,12 +22,12 @@ import ( "github.com/ydb-platform/ydb-go-sdk/v3/internal/allocator" "github.com/ydb-platform/ydb-go-sdk/v3/internal/operation" "github.com/ydb-platform/ydb-go-sdk/v3/internal/table/config" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/types" "github.com/ydb-platform/ydb-go-sdk/v3/internal/value" "github.com/ydb-platform/ydb-go-sdk/v3/internal/xcontext" "github.com/ydb-platform/ydb-go-sdk/v3/internal/xtest" "github.com/ydb-platform/ydb-go-sdk/v3/table" "github.com/ydb-platform/ydb-go-sdk/v3/table/options" - "github.com/ydb-platform/ydb-go-sdk/v3/table/types" "github.com/ydb-platform/ydb-go-sdk/v3/testutil" ) @@ -133,17 +133,17 @@ func TestSessionDescribeTable(t *testing.T) { Columns: []options.Column{ { Name: "testColumn", - Type: types.Void(), + Type: types.NewVoid(), Family: "testFamily", }, }, KeyRanges: []options.KeyRange{ { From: nil, - To: types.Int64Value(100500), + To: value.Int64Value(100500), }, { - From: types.Int64Value(100500), + From: value.Int64Value(100500), To: nil, }, }, @@ -182,7 +182,7 @@ func TestSessionDescribeTable(t *testing.T) { Columns: []*Ydb_Table.ColumnMeta{ { Name: expect.Columns[0].Name, - Type: value.TypeToYDB(expect.Columns[0].Type, a), + Type: types.TypeToYDB(expect.Columns[0].Type, a), Family: "testFamily", }, }, @@ -339,7 +339,7 @@ func TestSessionOperationModeOnExecuteDataQuery(t *testing.T) { func(t *testing.T) { for _, srcDst := range fromTo { t.Run(srcDst.srcMode.String()+"->"+srcDst.dstMode.String(), func(t *testing.T) { - client, err := New(context.Background(), testutil.NewBalancer( + client := New(context.Background(), testutil.NewBalancer( testutil.WithInvokeHandlers( testutil.InvokeHandlers{ testutil.TableExecuteDataQuery: func(interface{}) (proto.Message, error) { @@ -382,7 +382,6 @@ func TestSessionOperationModeOnExecuteDataQuery(t *testing.T) { }, ), ), config.New()) - require.NoError(t, err) ctx, cancel := xcontext.WithTimeout( context.Background(), time.Second, @@ -397,7 +396,7 @@ func TestSessionOperationModeOnExecuteDataQuery(t *testing.T) { } func TestCreateTableRegression(t *testing.T) { - client, err := New(context.Background(), testutil.NewBalancer( + client := New(context.Background(), testutil.NewBalancer( testutil.WithInvokeHandlers( testutil.InvokeHandlers{ testutil.TableCreateSession: func(request interface{}) (proto.Message, error) { @@ -474,21 +473,19 @@ func TestCreateTableRegression(t *testing.T) { ), ), config.New()) - require.NoError(t, err) - ctx, cancel := xcontext.WithTimeout( context.Background(), time.Second, ) defer cancel() - err = client.Do(ctx, func(ctx context.Context, s table.Session) error { + err := client.Do(ctx, func(ctx context.Context, s table.Session) error { return s.CreateTable(ctx, "episodes", - options.WithColumn("series_id", types.Optional(types.TypeUint64)), - options.WithColumn("season_id", types.Optional(types.TypeUint64)), - options.WithColumn("episode_id", types.Optional(types.TypeUint64)), - options.WithColumn("title", types.Optional(types.TypeText)), - options.WithColumn("air_date", types.Optional(types.TypeUint64)), + options.WithColumn("series_id", types.NewOptional(types.Uint64)), + options.WithColumn("season_id", types.NewOptional(types.Uint64)), + options.WithColumn("episode_id", types.NewOptional(types.Uint64)), + options.WithColumn("title", types.NewOptional(types.Text)), + options.WithColumn("air_date", types.NewOptional(types.Uint64)), options.WithPrimaryKeyColumn("series_id", "season_id", "episode_id"), options.WithAttribute("attr", "attr_value"), ) @@ -498,7 +495,7 @@ func TestCreateTableRegression(t *testing.T) { } func TestDescribeTableRegression(t *testing.T) { - client, err := New(context.Background(), testutil.NewBalancer( + client := New(context.Background(), testutil.NewBalancer( testutil.WithInvokeHandlers( testutil.InvokeHandlers{ testutil.TableCreateSession: func(request interface{}) (proto.Message, error) { @@ -567,8 +564,6 @@ func TestDescribeTableRegression(t *testing.T) { ), ), config.New()) - require.NoError(t, err) - ctx, cancel := xcontext.WithTimeout( context.Background(), time.Second, @@ -577,7 +572,7 @@ func TestDescribeTableRegression(t *testing.T) { var act options.Description - err = client.Do(ctx, func(ctx context.Context, s table.Session) (err error) { + err := client.Do(ctx, func(ctx context.Context, s table.Session) (err error) { act, err = s.DescribeTable(ctx, "episodes") return err @@ -590,23 +585,23 @@ func TestDescribeTableRegression(t *testing.T) { Columns: []options.Column{ { Name: "series_id", - Type: types.Optional(types.TypeUint64), + Type: types.NewOptional(types.Uint64), }, { Name: "season_id", - Type: types.Optional(types.TypeUint64), + Type: types.NewOptional(types.Uint64), }, { Name: "episode_id", - Type: types.Optional(types.TypeUint64), + Type: types.NewOptional(types.Uint64), }, { Name: "title", - Type: types.Optional(types.TypeText), + Type: types.NewOptional(types.Text), }, { Name: "air_date", - Type: types.Optional(types.TypeUint64), + Type: types.NewOptional(types.Uint64), }, }, KeyRanges: []options.KeyRange{ diff --git a/internal/table/statement.go b/internal/table/statement.go index d0cf27c54..eb797e2ef 100644 --- a/internal/table/statement.go +++ b/internal/table/statement.go @@ -9,6 +9,7 @@ import ( "github.com/ydb-platform/ydb-go-sdk/v3/internal/allocator" "github.com/ydb-platform/ydb-go-sdk/v3/internal/operation" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/params" "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/table" @@ -26,7 +27,7 @@ type statement struct { // Execute executes prepared data query. func (s *statement) Execute( ctx context.Context, txControl *table.TransactionControl, - params *table.QueryParameters, + parameters *params.Parameters, opts ...options.ExecuteDataQueryOption, ) ( txr table.Transaction, r result.Result, err error, @@ -43,10 +44,10 @@ func (s *statement) Execute( request.SessionId = s.session.id request.TxControl = txControl.Desc() - request.Parameters = params.Params().ToYDB(a) + request.Parameters = parameters.ToYDB(a) request.Query = s.query.toYDB(a) request.QueryCachePolicy = a.TableQueryCachePolicy() - request.QueryCachePolicy.KeepInCache = len(params.Params()) > 0 + request.QueryCachePolicy.KeepInCache = len(request.Parameters) > 0 request.OperationParams = operation.Params(ctx, s.session.config.OperationTimeout(), s.session.config.OperationCancelAfter(), @@ -61,8 +62,8 @@ func (s *statement) Execute( onDone := trace.TableOnSessionQueryExecute( s.session.config.Trace(), &ctx, - stack.FunctionID(""), - s.session, s.query, params, + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/table.(*statement).Execute"), + s.session, s.query, parameters, request.QueryCachePolicy.GetKeepInCache(), ) defer func() { diff --git a/internal/table/transaction.go b/internal/table/transaction.go index 90f5bd21b..a07576196 100644 --- a/internal/table/transaction.go +++ b/internal/table/transaction.go @@ -9,6 +9,7 @@ import ( "github.com/ydb-platform/ydb-go-sdk/v3/internal/allocator" "github.com/ydb-platform/ydb-go-sdk/v3/internal/operation" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/params" "github.com/ydb-platform/ydb-go-sdk/v3/internal/stack" "github.com/ydb-platform/ydb-go-sdk/v3/internal/table/scanner" "github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors" @@ -57,13 +58,13 @@ func (tx *transaction) ID() string { // Execute executes query represented by text within transaction tx. func (tx *transaction) Execute( ctx context.Context, - query string, params *table.QueryParameters, + 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(""), - tx.s, tx, queryFromText(query), params, + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/table.(*transaction).Execute"), + tx.s, tx, queryFromText(query), parameters, ) defer func() { onDone(r, err) @@ -75,12 +76,12 @@ func (tx *transaction) Execute( case txStateRollbacked: return nil, xerrors.WithStackTrace(errTxRollbackedEarly) default: - _, r, err = tx.s.Execute(ctx, tx.control, query, params, opts...) + _, r, err = tx.s.Execute(ctx, tx.control, query, parameters, opts...) if err != nil { return nil, xerrors.WithStackTrace(err) } - if tx.control.Desc().CommitTx { + if tx.control.Desc().GetCommitTx() { tx.state.Store(txStateCommitted) } @@ -91,19 +92,16 @@ func (tx *transaction) Execute( // ExecuteStatement executes prepared statement stmt within transaction tx. func (tx *transaction) ExecuteStatement( ctx context.Context, - stmt table.Statement, params *table.QueryParameters, + stmt table.Statement, parameters *params.Parameters, opts ...options.ExecuteDataQueryOption, ) (r result.Result, err error) { - if params == nil { - params = table.NewQueryParameters() - } a := allocator.New() defer a.Free() - onDone := trace.TableOnSessionTransactionExecuteStatement( + onDone := trace.TableOnTxExecuteStatement( tx.s.config.Trace(), &ctx, - stack.FunctionID(""), - tx.s, tx, stmt.(*statement).query, params, + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/table.(*transaction).ExecuteStatement"), + tx.s, tx, stmt.(*statement).query, parameters, ) defer func() { onDone(r, err) @@ -115,12 +113,12 @@ func (tx *transaction) ExecuteStatement( case txStateRollbacked: return nil, xerrors.WithStackTrace(errTxRollbackedEarly) default: - _, r, err = stmt.Execute(ctx, tx.control, params, opts...) + _, r, err = stmt.Execute(ctx, tx.control, parameters, opts...) if err != nil { return nil, xerrors.WithStackTrace(err) } - if tx.control.Desc().CommitTx { + if tx.control.Desc().GetCommitTx() { tx.state.Store(txStateCommitted) } @@ -133,9 +131,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() { @@ -191,9 +189,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/table/ttl.go b/internal/table/ttl.go index a91e9729f..fd3ad945e 100644 --- a/internal/table/ttl.go +++ b/internal/table/ttl.go @@ -10,19 +10,19 @@ func NewTimeToLiveSettings(settings *Ydb_Table.TtlSettings) *options.TimeToLiveS if settings == nil { return nil } - switch mode := settings.Mode.(type) { + switch mode := settings.GetMode().(type) { case *Ydb_Table.TtlSettings_DateTypeColumn: return &options.TimeToLiveSettings{ - ColumnName: mode.DateTypeColumn.ColumnName, - ExpireAfterSeconds: mode.DateTypeColumn.ExpireAfterSeconds, + ColumnName: mode.DateTypeColumn.GetColumnName(), + ExpireAfterSeconds: mode.DateTypeColumn.GetExpireAfterSeconds(), Mode: options.TimeToLiveModeDateType, } case *Ydb_Table.TtlSettings_ValueSinceUnixEpoch: return &options.TimeToLiveSettings{ - ColumnName: mode.ValueSinceUnixEpoch.ColumnName, - ColumnUnit: timeToLiveUnit(mode.ValueSinceUnixEpoch.ColumnUnit), - ExpireAfterSeconds: mode.ValueSinceUnixEpoch.ExpireAfterSeconds, + ColumnName: mode.ValueSinceUnixEpoch.GetColumnName(), + ColumnUnit: timeToLiveUnit(mode.ValueSinceUnixEpoch.GetColumnUnit()), + ExpireAfterSeconds: mode.ValueSinceUnixEpoch.GetExpireAfterSeconds(), Mode: options.TimeToLiveModeValueSinceUnixEpoch, } } diff --git a/internal/topic/retriable_error.go b/internal/topic/retriable_error.go index aebcd44f8..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 ) diff --git a/internal/topic/topicclientinternal/client.go b/internal/topic/topicclientinternal/client.go index a023b1471..2318d97b1 100644 --- a/internal/topic/topicclientinternal/client.go +++ b/internal/topic/topicclientinternal/client.go @@ -32,7 +32,7 @@ func New( conn grpc.ClientConnInterface, cred credentials.Credentials, opts ...topicoptions.TopicOption, -) (*Client, error) { +) *Client { rawClient := rawtopic.NewClient(Ydb_Topic_V1.NewTopicServiceClient(conn)) cfg := newTopicConfig(opts...) @@ -45,16 +45,16 @@ func New( cred: cred, defaultOperationParams: defaultOperationParams, rawClient: rawClient, - }, nil + } } func newTopicConfig(opts ...topicoptions.TopicOption) topic.Config { c := topic.Config{ Trace: &trace.Topic{}, } - for _, o := range opts { - if o != nil { - o(&c) + for _, opt := range opts { + if opt != nil { + opt(&c) } } @@ -71,9 +71,9 @@ func (c *Client) Alter(ctx context.Context, path string, opts ...topicoptions.Al req := &rawtopic.AlterTopicRequest{} req.OperationParams = c.defaultOperationParams req.Path = path - for _, o := range opts { - if o != nil { - o.ApplyAlterOption(req) + for _, opt := range opts { + if opt != nil { + opt.ApplyAlterOption(req) } } @@ -103,9 +103,9 @@ func (c *Client) Create( req.OperationParams = c.defaultOperationParams req.Path = path - for _, o := range opts { - if o != nil { - o.ApplyCreateOption(req) + for _, opt := range opts { + if opt != nil { + opt.ApplyCreateOption(req) } } @@ -136,9 +136,9 @@ func (c *Client) Describe( Path: path, } - for _, o := range opts { - if o != nil { - o(&req) + for _, opt := range opts { + if opt != nil { + opt(&req) } } @@ -176,9 +176,9 @@ func (c *Client) Drop(ctx context.Context, path string, opts ...topicoptions.Dro req.OperationParams = c.defaultOperationParams req.Path = path - for _, o := range opts { - if o != nil { - o.ApplyDropOption(&req) + for _, opt := range opts { + if opt != nil { + opt.ApplyDropOption(&req) } } 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..c926e7076 100644 --- a/internal/topic/topicreaderinternal/batcher_test.go +++ b/internal/topic/topicreaderinternal/batcher_test.go @@ -407,7 +407,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 +426,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/committer.go b/internal/topic/topicreaderinternal/committer.go index acb9e5b27..f0fa1c425 100644 --- a/internal/topic/topicreaderinternal/committer.go +++ b/internal/topic/topicreaderinternal/committer.go @@ -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/decoders.go b/internal/topic/topicreaderinternal/decoders.go index a4598192c..87773cdc7 100644 --- a/internal/topic/topicreaderinternal/decoders.go +++ b/internal/topic/topicreaderinternal/decoders.go @@ -36,7 +36,7 @@ func (m *decoderMap) Decode(codec rawtopiccommon.Codec, input io.Reader) (io.Rea } return nil, xerrors.WithStackTrace(xerrors.Wrap( - fmt.Errorf("ydb: failed decompress message with codec %v: %w", codec, PublicErrUnexpectedCodec), + fmt.Errorf("ydb: failed decompress message with codec %v: %w", codec, ErrPublicUnexpectedCodec), )) } diff --git a/internal/topic/topicreaderinternal/message.go b/internal/topic/topicreaderinternal/message.go index 7e5818eaf..13cc98f0c 100644 --- a/internal/topic/topicreaderinternal/message.go +++ b/internal/topic/topicreaderinternal/message.go @@ -14,8 +14,8 @@ import ( var errMessageWasReadEarly = xerrors.Wrap(errors.New("ydb: message was read early")) -// PublicErrUnexpectedCodec return when try to read message content with unknown codec -var PublicErrUnexpectedCodec = errors.New("unexpected codec") //nolint:revive,stylecheck +// ErrPublicUnexpectedCodec return when try to read message content with unknown codec +var ErrPublicUnexpectedCodec = errors.New("unexpected codec") // PublicMessage is representation of topic message type PublicMessage struct { diff --git a/internal/topic/topicreaderinternal/reader.go b/internal/topic/topicreaderinternal/reader.go index 030a5ca4b..b499221d1 100644 --- a/internal/topic/topicreaderinternal/reader.go +++ b/internal/topic/topicreaderinternal/reader.go @@ -258,9 +258,9 @@ func convertNewParamsToStreamConfig( cfg.ReadSelectors[i] = readSelectors[i].Clone() } - for _, f := range opts { - if f != nil { - f(&cfg) + for _, opt := range opts { + if opt != nil { + opt(&cfg) } } diff --git a/internal/topic/topicreaderinternal/stream_reader_impl.go b/internal/topic/topicreaderinternal/stream_reader_impl.go index 92c28f19f..fec186ffc 100644 --- a/internal/topic/topicreaderinternal/stream_reader_impl.go +++ b/internal/topic/topicreaderinternal/stream_reader_impl.go @@ -27,8 +27,7 @@ const defaultBufferSize = 1024 * 1024 var ( PublicErrCommitSessionToExpiredSession = xerrors.Wrap(errors.New("ydb: commit to expired session")) - errPartitionSessionStoppedByServer = xerrors.Wrap(errors.New("ydb: topic partition session stopped by server")) - errCommitWithNilPartitionSession = xerrors.Wrap(errors.New("ydb: commit with nil partition session")) + errCommitWithNilPartitionSession = xerrors.Wrap(errors.New("ydb: commit with nil partition session")) ) type partitionSessionID = rawtopicreader.PartitionSessionID @@ -387,7 +386,7 @@ func (r *topicStreamReaderImpl) checkCommitRange(commitRange commitRange) error } if session.Context().Err() != nil { - return xerrors.WithStackTrace(fmt.Errorf("ydb: commit error: %w", errPartitionSessionStoppedByServer)) + return xerrors.WithStackTrace(PublicErrCommitSessionToExpiredSession) } ownSession, err := r.sessionController.Get(session.partitionSessionID) diff --git a/internal/topic/topicreaderinternal/stream_reconnector.go b/internal/topic/topicreaderinternal/stream_reconnector.go index fa3ba6594..ec601ba67 100644 --- a/internal/topic/topicreaderinternal/stream_reconnector.go +++ b/internal/topic/topicreaderinternal/stream_reconnector.go @@ -29,30 +29,24 @@ var ( type readerConnectFunc func(ctx context.Context) (batchedStreamReader, error) type readerReconnector struct { - clock clockwork.Clock - background background.Worker - - tracer *trace.Topic - baseContext context.Context - retrySettings topic.RetrySettings - - readerConnect readerConnectFunc - - reconnectFromBadStream chan reconnectRequest - connectTimeout time.Duration - - closeOnce sync.Once - readerID int64 - - m xsync.RWMutex - streamConnectionInProgress empty.Chan // opened if connection in progress, closed if connection established + background background.Worker + clock clockwork.Clock + baseContext context.Context + retrySettings topic.RetrySettings streamVal batchedStreamReader streamErr error closedErr error - - initErr error - initDone bool - initDoneCh empty.Chan + initErr error + tracer *trace.Topic + readerConnect readerConnectFunc + reconnectFromBadStream chan reconnectRequest + connectTimeout time.Duration + readerID int64 + streamConnectionInProgress empty.Chan // opened if connection in progress, closed if connection established + initDoneCh empty.Chan + m xsync.RWMutex + closeOnce sync.Once + initDone bool } //nolint:revive @@ -334,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/message.go b/internal/topic/topicwriterinternal/message.go index 1e07f2587..5c618e631 100644 --- a/internal/topic/topicwriterinternal/message.go +++ b/internal/topic/topicwriterinternal/message.go @@ -58,14 +58,14 @@ type messageWithDataContent struct { PublicMessage dataWasRead bool - encoders *EncoderMap hasRawContent bool - rawBuf bytes.Buffer hasEncodedContent bool + metadataCached bool bufCodec rawtopiccommon.Codec bufEncoded bytes.Buffer + rawBuf bytes.Buffer + encoders *EncoderMap BufUncompressedSize int - metadataCached bool } func (m *messageWithDataContent) GetEncodedBytes(codec rawtopiccommon.Codec) ([]byte, error) { diff --git a/internal/topic/topicwriterinternal/queue.go b/internal/topic/topicwriterinternal/queue.go index 338f917b9..799b02d16 100644 --- a/internal/topic/topicwriterinternal/queue.go +++ b/internal/topic/topicwriterinternal/queue.go @@ -14,6 +14,7 @@ import ( var ( errCloseClosedMessageQueue = xerrors.Wrap(errors.New("ydb: close closed message queue")) + errAckOnClosedMessageQueue = xerrors.Wrap(errors.New("ydb: ack on closed message queue")) errGetMessageFromClosedQueue = xerrors.Wrap(errors.New("ydb: get message from closed message queue")) errAddUnorderedMessages = xerrors.Wrap(errors.New("ydb: add unordered messages")) errAckUnexpectedMessage = xerrors.Wrap(errors.New("ydb: ack unexpected message")) @@ -152,6 +153,9 @@ func (q *messageQueue) AcksReceived(acks []rawtopicwriter.WriteAck) error { q.OnAckReceived(ackReceivedCounter) } }() + if q.closed { + return xerrors.WithStackTrace(errAckOnClosedMessageQueue) + } for i := range acks { if err := q.ackReceivedNeedLock(acks[i].SeqNo); err != nil { diff --git a/internal/topic/topicwriterinternal/queue_test.go b/internal/topic/topicwriterinternal/queue_test.go index 8cc6ade34..d62e6b16e 100644 --- a/internal/topic/topicwriterinternal/queue_test.go +++ b/internal/topic/topicwriterinternal/queue_test.go @@ -407,6 +407,26 @@ func TestQueuePanicOnOverflow(t *testing.T) { }) } +func TestRegressionIssue1038_ReceiveAckAfterCloseQueue(t *testing.T) { + counter := 0 + + q := newMessageQueue() + q.OnAckReceived = func(count int) { + counter -= count + } + require.NoError(t, q.AddMessages(newTestMessagesWithContent(1))) + counter++ + + require.NoError(t, q.Close(errors.New("test err"))) + require.ErrorIs(t, q.AcksReceived([]rawtopicwriter.WriteAck{ + { + SeqNo: 1, + MessageWriteStatus: rawtopicwriter.MessageWriteStatus{}, + }, + }), errAckOnClosedMessageQueue) + require.Zero(t, counter) +} + func TestQueue_Ack(t *testing.T) { t.Run("First", func(t *testing.T) { q := newMessageQueue() diff --git a/internal/topic/topicwriterinternal/writer_reconnector.go b/internal/topic/topicwriterinternal/writer_reconnector.go index 81571248e..5e88be376 100644 --- a/internal/topic/topicwriterinternal/writer_reconnector.go +++ b/internal/topic/topicwriterinternal/writer_reconnector.go @@ -119,25 +119,22 @@ func newWriterReconnectorConfig(options ...PublicWriterOption) WriterReconnector } type WriterReconnector struct { - cfg WriterReconnectorConfig - retrySettings topic.RetrySettings - - semaphore *semaphore.Weighted + cfg WriterReconnectorConfig queue messageQueue background background.Worker + retrySettings topic.RetrySettings clock clockwork.Clock - firstConnectionHandled atomic.Bool - firstInitResponseProcessedChan empty.Chan writerInstanceID string - - m xsync.RWMutex - sessionID string - lastSeqNo int64 - encodersMap *EncoderMap - - initDone bool - initDoneCh empty.Chan - initInfo InitialInfo + sessionID string + semaphore *semaphore.Weighted + firstInitResponseProcessedChan empty.Chan + lastSeqNo int64 + encodersMap *EncoderMap + initDoneCh empty.Chan + initInfo InitialInfo + m xsync.RWMutex + firstConnectionHandled atomic.Bool + initDone bool } func newWriterReconnector( @@ -358,7 +355,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 @@ -388,16 +385,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_single_stream.go b/internal/topic/topicwriterinternal/writer_single_stream.go index 444ae3d3d..4f01c56d1 100644 --- a/internal/topic/topicwriterinternal/writer_single_stream.go +++ b/internal/topic/topicwriterinternal/writer_single_stream.go @@ -47,19 +47,18 @@ func newSingleStreamWriterConfig( } type SingleStreamWriter struct { - ReceivedLastSeqNum int64 - LastSeqNumRequested bool + cfg SingleStreamWriterConfig + Encoder EncoderSelector + background background.Worker + CodecsFromServer rawtopiccommon.SupportedCodecs + allowedCodecs rawtopiccommon.SupportedCodecs SessionID string + closeReason error + ReceivedLastSeqNum int64 PartitionID int64 - CodecsFromServer rawtopiccommon.SupportedCodecs - Encoder EncoderSelector - - cfg SingleStreamWriterConfig - allowedCodecs rawtopiccommon.SupportedCodecs - background background.Worker - closed atomic.Bool - closeReason error - closeCompleted empty.Chan + closeCompleted empty.Chan + closed atomic.Bool + LastSeqNumRequested bool } func NewSingleStreamWriter( @@ -84,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), } } @@ -189,7 +188,7 @@ func (w *SingleStreamWriter) receiveMessagesLoop(ctx context.Context) { switch m := mess.(type) { case *rawtopicwriter.WriteResult: - if err = w.cfg.queue.AcksReceived(m.Acks); err != nil { + if err = w.cfg.queue.AcksReceived(m.Acks); err != nil && !errors.Is(err, errCloseClosedMessageQueue) { reason := xerrors.WithStackTrace(err) closeCtx, closeCtxCancel := xcontext.WithCancel(ctx) closeCtxCancel() diff --git a/internal/types/types.go b/internal/types/types.go new file mode 100644 index 000000000..4942cd787 --- /dev/null +++ b/internal/types/types.go @@ -0,0 +1,948 @@ +package types + +import ( + "fmt" + + "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/xstring" +) + +type Type interface { + Yql() string + String() string + + ToYDB(a *allocator.Allocator) *Ydb.Type + equalsTo(rhs Type) bool +} + +func TypeToYDB(t Type, a *allocator.Allocator) *Ydb.Type { + return t.ToYDB(a) +} + +func TypeFromYDB(x *Ydb.Type) Type { + switch v := x.GetType().(type) { + case *Ydb.Type_TypeId: + return primitiveTypeFromYDB(v.TypeId) + + case *Ydb.Type_OptionalType: + return NewOptional(TypeFromYDB(v.OptionalType.GetItem())) + + case *Ydb.Type_ListType: + return NewList(TypeFromYDB(v.ListType.GetItem())) + + case *Ydb.Type_DecimalType: + d := v.DecimalType + + return NewDecimal(d.GetPrecision(), d.GetScale()) + + case *Ydb.Type_TupleType: + t := v.TupleType + + return NewTuple(FromYDB(t.GetElements())...) + + case *Ydb.Type_StructType: + s := v.StructType + + return NewStruct(StructFields(s.GetMembers())...) + + case *Ydb.Type_DictType: + keyType, valueType := TypeFromYDB(v.DictType.GetKey()), TypeFromYDB(v.DictType.GetPayload()) + if valueType.equalsTo(NewVoid()) { + return NewSet(keyType) + } + + return NewDict(keyType, valueType) + + case *Ydb.Type_VariantType: + t := v.VariantType + switch x := t.GetType().(type) { + case *Ydb.VariantType_TupleItems: + return NewVariantTuple(FromYDB(x.TupleItems.GetElements())...) + case *Ydb.VariantType_StructItems: + return NewVariantStruct(StructFields(x.StructItems.GetMembers())...) + default: + panic("ydb: unknown variant type") + } + + case *Ydb.Type_VoidType: + return NewVoid() + + case *Ydb.Type_NullType: + return NewNull() + + case *Ydb.Type_PgType: + return &PgType{ + OID: x.GetPgType().GetOid(), + } + + default: + panic("ydb: unknown type") + } +} + +func primitiveTypeFromYDB(t Ydb.Type_PrimitiveTypeId) Type { + switch t { + case Ydb.Type_BOOL: + return Bool + case Ydb.Type_INT8: + return Int8 + case Ydb.Type_UINT8: + return Uint8 + case Ydb.Type_INT16: + return Int16 + case Ydb.Type_UINT16: + return Uint16 + case Ydb.Type_INT32: + return Int32 + case Ydb.Type_UINT32: + return Uint32 + case Ydb.Type_INT64: + return Int64 + case Ydb.Type_UINT64: + return Uint64 + case Ydb.Type_FLOAT: + return Float + case Ydb.Type_DOUBLE: + return Double + case Ydb.Type_DATE: + return Date + case Ydb.Type_DATETIME: + return Datetime + case Ydb.Type_TIMESTAMP: + return Timestamp + case Ydb.Type_INTERVAL: + return Interval + case Ydb.Type_TZ_DATE: + return TzDate + case Ydb.Type_TZ_DATETIME: + return TzDatetime + case Ydb.Type_TZ_TIMESTAMP: + return TzTimestamp + case Ydb.Type_STRING: + return Bytes + case Ydb.Type_UTF8: + return Text + case Ydb.Type_YSON: + return YSON + case Ydb.Type_JSON: + return JSON + case Ydb.Type_UUID: + return UUID + case Ydb.Type_JSON_DOCUMENT: + return JSONDocument + case Ydb.Type_DYNUMBER: + return DyNumber + default: + panic("ydb: unexpected type") + } +} + +func FromYDB(es []*Ydb.Type) []Type { + ts := make([]Type, len(es)) + for i, el := range es { + ts[i] = TypeFromYDB(el) + } + + return ts +} + +func Equal(a, b Type) bool { + return a.equalsTo(b) +} + +type Decimal struct { + precision uint32 + scale uint32 +} + +func (v *Decimal) Precision() uint32 { + return v.precision +} + +func (v *Decimal) Scale() uint32 { + return v.scale +} + +func (v *Decimal) String() string { + return v.Yql() +} + +func (v *Decimal) Name() string { + return "Decimal" +} + +func (v *Decimal) Yql() string { + return fmt.Sprintf("%s(%d,%d)", v.Name(), v.precision, v.scale) +} + +func (v *Decimal) equalsTo(rhs Type) bool { + vv, ok := rhs.(*Decimal) + + return ok && *v == *vv +} + +func (v *Decimal) ToYDB(a *allocator.Allocator) *Ydb.Type { + decimal := a.Decimal() + + decimal.Scale = v.scale + decimal.Precision = v.precision + + typeDecimal := a.TypeDecimal() + typeDecimal.DecimalType = decimal + + t := a.Type() + t.Type = typeDecimal + + return t +} + +func NewDecimal(precision, scale uint32) *Decimal { + return &Decimal{ + precision: precision, + scale: scale, + } +} + +type Dict struct { + keyType Type + valueType Type +} + +func (v *Dict) KeyType() Type { + return v.keyType +} + +func (v *Dict) ValueType() Type { + return v.valueType +} + +func (v *Dict) String() string { + return v.Yql() +} + +func (v *Dict) Yql() string { + buffer := xstring.Buffer() + defer buffer.Free() + buffer.WriteString("Dict<") + buffer.WriteString(v.keyType.Yql()) + buffer.WriteByte(',') + buffer.WriteString(v.valueType.Yql()) + buffer.WriteByte('>') + + return buffer.String() +} + +func (v *Dict) equalsTo(rhs Type) bool { + vv, ok := rhs.(*Dict) + if !ok { + return false + } + if !v.keyType.equalsTo(vv.keyType) { + return false + } + if !v.valueType.equalsTo(vv.valueType) { + return false + } + + return true +} + +func (v *Dict) ToYDB(a *allocator.Allocator) *Ydb.Type { + t := a.Type() + + typeDict := a.TypeDict() + + typeDict.DictType = a.Dict() + + typeDict.DictType.Key = v.keyType.ToYDB(a) + typeDict.DictType.Payload = v.valueType.ToYDB(a) + + t.Type = typeDict + + return t +} + +func NewDict(key, value Type) (v *Dict) { + return &Dict{ + keyType: key, + valueType: value, + } +} + +type EmptyList struct{} + +func (v EmptyList) Yql() string { + return "EmptyList" +} + +func (v EmptyList) String() string { + return v.Yql() +} + +func (EmptyList) equalsTo(rhs Type) bool { + _, ok := rhs.(EmptyList) + + return ok +} + +func (EmptyList) ToYDB(a *allocator.Allocator) *Ydb.Type { + t := a.Type() + + t.Type = a.TypeEmptyList() + + return t +} + +func NewEmptyList() EmptyList { + return EmptyList{} +} + +type EmptyDict struct{} + +func (v EmptyDict) String() string { + return v.Yql() +} + +func (v EmptyDict) Yql() string { + return "EmptyDict" +} + +func (EmptyDict) equalsTo(rhs Type) bool { + _, ok := rhs.(EmptyDict) + + return ok +} + +func (EmptyDict) ToYDB(a *allocator.Allocator) *Ydb.Type { + t := a.Type() + + t.Type = a.TypeEmptyDict() + + return t +} + +func EmptySet() EmptyDict { + return EmptyDict{} +} + +func NewEmptyDict() EmptyDict { + return EmptyDict{} +} + +type List struct { + itemType Type +} + +func (v *List) ItemType() Type { + return v.itemType +} + +func (v *List) String() string { + return v.Yql() +} + +func (v *List) Yql() string { + return "List<" + v.itemType.Yql() + ">" +} + +func (v *List) equalsTo(rhs Type) bool { + vv, ok := rhs.(*List) + if !ok { + return false + } + + return v.itemType.equalsTo(vv.itemType) +} + +func (v *List) ToYDB(a *allocator.Allocator) *Ydb.Type { + t := a.Type() + + list := a.List() + + list.Item = v.itemType.ToYDB(a) + + typeList := a.TypeList() + typeList.ListType = list + + t.Type = typeList + + return t +} + +func NewList(t Type) *List { + return &List{ + itemType: t, + } +} + +type Set struct { + itemType Type +} + +func (v *Set) ItemType() Type { + return v.itemType +} + +func (v *Set) String() string { + return v.Yql() +} + +func (v *Set) Yql() string { + return "Set<" + v.itemType.Yql() + ">" +} + +func (v *Set) equalsTo(rhs Type) bool { + vv, ok := rhs.(*Set) + if !ok { + return false + } + + return v.itemType.equalsTo(vv.itemType) +} + +func (v *Set) ToYDB(a *allocator.Allocator) *Ydb.Type { + t := a.Type() + + typeDict := a.TypeDict() + + typeDict.DictType = a.Dict() + + typeDict.DictType.Key = v.itemType.ToYDB(a) + typeDict.DictType.Payload = _voidType + + t.Type = typeDict + + return t +} + +func NewSet(t Type) *Set { + return &Set{ + itemType: t, + } +} + +type Optional struct { + innerType Type +} + +func (v Optional) IsOptional() {} + +func (v Optional) InnerType() Type { + return v.innerType +} + +func (v Optional) String() string { + return v.Yql() +} + +func (v Optional) Yql() string { + return "Optional<" + v.innerType.Yql() + ">" +} + +func (v Optional) equalsTo(rhs Type) bool { + vv, ok := rhs.(Optional) + if !ok { + return false + } + + return v.innerType.equalsTo(vv.innerType) +} + +func (v Optional) ToYDB(a *allocator.Allocator) *Ydb.Type { + t := a.Type() + + typeOptional := a.TypeOptional() + + typeOptional.OptionalType = a.Optional() + + typeOptional.OptionalType.Item = v.innerType.ToYDB(a) + + t.Type = typeOptional + + return t +} + +func NewOptional(t Type) Optional { + return Optional{ + innerType: t, + } +} + +type PgType struct { + OID uint32 +} + +func (v PgType) String() string { + return v.Yql() +} + +func (v PgType) Yql() string { + return fmt.Sprintf("PgType(%v)", v.OID) +} + +func (v PgType) ToYDB(a *allocator.Allocator) *Ydb.Type { + //nolint:godox + // TODO: make allocator + return &Ydb.Type{Type: &Ydb.Type_PgType{ + PgType: &Ydb.PgType{ + Oid: v.OID, + }, + }} +} + +func (v PgType) equalsTo(rhs Type) bool { + vv, ok := rhs.(PgType) + if !ok { + return false + } + + return v.OID == vv.OID +} + +type Primitive uint + +func (v Primitive) String() string { + return v.Yql() +} + +func (v Primitive) Yql() string { + return primitiveString[v] +} + +const ( + Unknown Primitive = iota + Bool + Int8 + Uint8 + Int16 + Uint16 + Int32 + Uint32 + Int64 + Uint64 + Float + Double + Date + Datetime + Timestamp + Interval + TzDate + TzDatetime + TzTimestamp + Bytes + Text + YSON + JSON + UUID + JSONDocument + DyNumber +) + +var primitive = [...]*Ydb.Type{ + Bool: {Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_BOOL}}, + Int8: {Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_INT8}}, + Uint8: {Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_UINT8}}, + Int16: {Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_INT16}}, + Uint16: {Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_UINT16}}, + Int32: {Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_INT32}}, + Uint32: {Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_UINT32}}, + Int64: {Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_INT64}}, + Uint64: {Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_UINT64}}, + Float: {Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_FLOAT}}, + Double: {Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_DOUBLE}}, + Date: {Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_DATE}}, + Datetime: {Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_DATETIME}}, + Timestamp: {Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_TIMESTAMP}}, + Interval: {Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_INTERVAL}}, + TzDate: {Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_TZ_DATE}}, + TzDatetime: {Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_TZ_DATETIME}}, + TzTimestamp: {Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_TZ_TIMESTAMP}}, + Bytes: {Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_STRING}}, + Text: {Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_UTF8}}, + YSON: {Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_YSON}}, + JSON: {Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_JSON}}, + UUID: {Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_UUID}}, + JSONDocument: {Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_JSON_DOCUMENT}}, + DyNumber: {Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_DYNUMBER}}, +} + +var primitiveString = [...]string{ + Unknown: "", + Bool: "Bool", + Int8: "Int8", + Uint8: "Uint8", + Int16: "Int16", + Uint16: "Uint16", + Int32: "Int32", + Uint32: "Uint32", + Int64: "Int64", + Uint64: "Uint64", + Float: "Float", + Double: "Double", + Date: "Date", + Datetime: "Datetime", + Timestamp: "Timestamp", + Interval: "Interval", + TzDate: "TzDate", + TzDatetime: "TzDatetime", + TzTimestamp: "TzTimestamp", + Bytes: "String", + Text: "Utf8", + YSON: "Yson", + JSON: "Json", + UUID: "Uuid", + JSONDocument: "JsonDocument", + DyNumber: "DyNumber", +} + +func (v Primitive) equalsTo(rhs Type) bool { + vv, ok := rhs.(Primitive) + if !ok { + return false + } + + return v == vv +} + +func (v Primitive) ToYDB(*allocator.Allocator) *Ydb.Type { + return primitive[v] +} + +type ( + StructField struct { + Name string + T Type + } + Struct struct { + fields []StructField + } +) + +func (v *Struct) Field(i int) StructField { + return v.fields[i] +} + +func (v *Struct) Fields() []StructField { + return v.fields +} + +func (v *Struct) String() string { + return v.Yql() +} + +func (v *Struct) Yql() string { + buffer := xstring.Buffer() + defer buffer.Free() + buffer.WriteString("Struct<") + for i := range v.fields { + if i > 0 { + buffer.WriteByte(',') + } + buffer.WriteString("'" + v.fields[i].Name + "'") + buffer.WriteByte(':') + buffer.WriteString(v.fields[i].T.Yql()) + } + buffer.WriteByte('>') + + return buffer.String() +} + +func (v *Struct) equalsTo(rhs Type) bool { + vv, ok := rhs.(*Struct) + if !ok { + return false + } + if len(v.fields) != len(vv.fields) { + return false + } + for i := range v.fields { + if v.fields[i].Name != vv.fields[i].Name { + return false + } + if !v.fields[i].T.equalsTo(vv.fields[i].T) { + return false + } + } + + return true +} + +func (v *Struct) ToYDB(a *allocator.Allocator) *Ydb.Type { + t := a.Type() + + typeStruct := a.TypeStruct() + + typeStruct.StructType = a.Struct() + + for i := range v.fields { + structMember := a.StructMember() + structMember.Name = v.fields[i].Name + structMember.Type = v.fields[i].T.ToYDB(a) + typeStruct.StructType.Members = append( + typeStruct.StructType.GetMembers(), + structMember, + ) + } + + t.Type = typeStruct + + return t +} + +func NewStruct(fields ...StructField) (v *Struct) { + return &Struct{ + fields: fields, + } +} + +func StructFields(ms []*Ydb.StructMember) []StructField { + fs := make([]StructField, len(ms)) + for i, m := range ms { + fs[i] = StructField{ + Name: m.GetName(), + T: TypeFromYDB(m.GetType()), + } + } + + return fs +} + +type Tuple struct { + innerTypes []Type +} + +func (v *Tuple) InnerTypes() []Type { + return v.innerTypes +} + +func (v *Tuple) ItemType(i int) Type { + return v.innerTypes[i] +} + +func (v *Tuple) String() string { + return v.Yql() +} + +func (v *Tuple) Yql() string { + buffer := xstring.Buffer() + defer buffer.Free() + buffer.WriteString("Tuple<") + for i, t := range v.innerTypes { + if i > 0 { + buffer.WriteByte(',') + } + buffer.WriteString(t.Yql()) + } + buffer.WriteByte('>') + + return buffer.String() +} + +func (v *Tuple) equalsTo(rhs Type) bool { + vv, ok := rhs.(*Tuple) + if !ok { + return false + } + if len(v.innerTypes) != len(vv.innerTypes) { + return false + } + for i := range v.innerTypes { + if !v.innerTypes[i].equalsTo(vv.innerTypes[i]) { + return false + } + } + + return true +} + +func (v *Tuple) ToYDB(a *allocator.Allocator) *Ydb.Type { + var items []Type + if v != nil { + items = v.innerTypes + } + t := a.Type() + + typeTuple := a.TypeTuple() + + typeTuple.TupleType = a.Tuple() + + for _, vv := range items { + typeTuple.TupleType.Elements = append(typeTuple.TupleType.GetElements(), vv.ToYDB(a)) + } + + t.Type = typeTuple + + return t +} + +func NewTuple(items ...Type) (v *Tuple) { + return &Tuple{ + innerTypes: items, + } +} + +type VariantStruct struct { + *Struct +} + +func (v *VariantStruct) Yql() string { + buffer := xstring.Buffer() + defer buffer.Free() + buffer.WriteString("Variant<") + for i := range v.fields { + if i > 0 { + buffer.WriteByte(',') + } + buffer.WriteString("'" + v.fields[i].Name + "'") + buffer.WriteByte(':') + buffer.WriteString(v.fields[i].T.Yql()) + } + buffer.WriteByte('>') + + return buffer.String() +} + +func (v *VariantStruct) equalsTo(rhs Type) bool { + switch t := rhs.(type) { + case *VariantStruct: + return v.Struct.equalsTo(t.Struct) + case *Struct: + return v.Struct.equalsTo(t) + default: + return false + } +} + +func (v *VariantStruct) ToYDB(a *allocator.Allocator) *Ydb.Type { + t := a.Type() + + typeVariant := a.TypeVariant() + + typeVariant.VariantType = a.Variant() + + structItems := a.VariantStructItems() + structItems.StructItems = v.Struct.ToYDB(a).GetType().(*Ydb.Type_StructType).StructType + + typeVariant.VariantType.Type = structItems + + t.Type = typeVariant + + return t +} + +func NewVariantStruct(fields ...StructField) *VariantStruct { + return &VariantStruct{ + Struct: NewStruct(fields...), + } +} + +type VariantTuple struct { + *Tuple +} + +func (v *VariantTuple) Yql() string { + buffer := xstring.Buffer() + defer buffer.Free() + buffer.WriteString("Variant<") + for i, t := range v.innerTypes { + if i > 0 { + buffer.WriteByte(',') + } + buffer.WriteString(t.Yql()) + } + buffer.WriteByte('>') + + return buffer.String() +} + +func (v *VariantTuple) equalsTo(rhs Type) bool { + switch t := rhs.(type) { + case *VariantTuple: + return v.Tuple.equalsTo(t.Tuple) + case *Tuple: + return v.Tuple.equalsTo(t) + default: + return false + } +} + +func (v *VariantTuple) ToYDB(a *allocator.Allocator) *Ydb.Type { + t := a.Type() + + typeVariant := a.TypeVariant() + + typeVariant.VariantType = a.Variant() + + tupleItems := a.VariantTupleItems() + tupleItems.TupleItems = v.Tuple.ToYDB(a).GetType().(*Ydb.Type_TupleType).TupleType + + typeVariant.VariantType.Type = tupleItems + + t.Type = typeVariant + + return t +} + +func NewVariantTuple(items ...Type) *VariantTuple { + return &VariantTuple{ + Tuple: NewTuple(items...), + } +} + +type Void struct{} + +func (v Void) String() string { + return v.Yql() +} + +func (v Void) Yql() string { + return "Void" +} + +var _voidType = &Ydb.Type{ + Type: &Ydb.Type_VoidType{}, +} + +func (v Void) equalsTo(rhs Type) bool { + _, ok := rhs.(Void) + + return ok +} + +func (Void) ToYDB(*allocator.Allocator) *Ydb.Type { + return _voidType +} + +func NewVoid() Void { + return Void{} +} + +type Null struct{} + +func (v Null) String() string { + return v.Yql() +} + +func (v Null) Yql() string { + return "Null" +} + +var _nullType = &Ydb.Type{ + Type: &Ydb.Type_NullType{}, +} + +func (v Null) equalsTo(rhs Type) bool { + _, ok := rhs.(Null) + + return ok +} + +func (Null) ToYDB(*allocator.Allocator) *Ydb.Type { + return _nullType +} + +func NewNull() Null { + return Null{} +} diff --git a/internal/types/types_test.go b/internal/types/types_test.go new file mode 100644 index 000000000..3c9936a2a --- /dev/null +++ b/internal/types/types_test.go @@ -0,0 +1,381 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ydb-platform/ydb-go-sdk/v3/internal/pg" +) + +func TestTypeToString(t *testing.T) { + for _, tt := range []struct { + t Type + s string + }{ + { + t: NewVoid(), + s: "Void", + }, + { + t: NewNull(), + s: "Null", + }, + { + t: Bool, + s: "Bool", + }, + { + t: Int8, + s: "Int8", + }, + { + t: Uint8, + s: "Uint8", + }, + { + t: Int16, + s: "Int16", + }, + { + t: Uint16, + s: "Uint16", + }, + { + t: Int32, + s: "Int32", + }, + { + t: Uint32, + s: "Uint32", + }, + { + t: Int64, + s: "Int64", + }, + { + t: Uint64, + s: "Uint64", + }, + { + t: Float, + s: "Float", + }, + { + t: Double, + s: "Double", + }, + { + t: Date, + s: "Date", + }, + { + t: Datetime, + s: "Datetime", + }, + { + t: Timestamp, + s: "Timestamp", + }, + { + t: Interval, + s: "Interval", + }, + { + t: TzDate, + s: "TzDate", + }, + { + t: TzDatetime, + s: "TzDatetime", + }, + { + t: TzTimestamp, + s: "TzTimestamp", + }, + { + t: Bytes, + s: "String", + }, + { + t: Text, + s: "Utf8", + }, + { + t: YSON, + s: "Yson", + }, + { + t: JSON, + s: "Json", + }, + { + t: UUID, + s: "Uuid", + }, + { + t: JSONDocument, + s: "JsonDocument", + }, + { + t: DyNumber, + s: "DyNumber", + }, + { + t: NewOptional(Bool), + s: "Optional", + }, + { + t: NewOptional(Int8), + s: "Optional", + }, + { + t: NewOptional(Uint8), + s: "Optional", + }, + { + t: NewOptional(Int16), + s: "Optional", + }, + { + t: NewOptional(Uint16), + s: "Optional", + }, + { + t: NewOptional(Int32), + s: "Optional", + }, + { + t: NewOptional(Uint32), + s: "Optional", + }, + { + t: NewOptional(Int64), + s: "Optional", + }, + { + t: NewOptional(Uint64), + s: "Optional", + }, + { + t: NewOptional(Float), + s: "Optional", + }, + { + t: NewOptional(Double), + s: "Optional", + }, + { + t: NewOptional(Date), + s: "Optional", + }, + { + t: NewOptional(Datetime), + s: "Optional", + }, + { + t: NewOptional(Timestamp), + s: "Optional", + }, + { + t: NewOptional(Interval), + s: "Optional", + }, + { + t: NewOptional(TzDate), + s: "Optional", + }, + { + t: NewOptional(TzDatetime), + s: "Optional", + }, + { + t: NewOptional(TzTimestamp), + s: "Optional", + }, + { + t: NewOptional(Bytes), + s: "Optional", + }, + { + t: NewOptional(Text), + s: "Optional", + }, + { + t: NewOptional(YSON), + s: "Optional", + }, + { + t: NewOptional(JSON), + s: "Optional", + }, + { + t: NewOptional(UUID), + s: "Optional", + }, + { + t: NewOptional(JSONDocument), + s: "Optional", + }, + { + t: NewOptional(DyNumber), + s: "Optional", + }, + { + t: NewDecimal(22, 9), + s: "Decimal(22,9)", + }, + { + t: NewDict(Text, Timestamp), + s: "Dict", + }, + { + t: NewEmptyList(), + s: "EmptyList", + }, + { + t: NewList(Uint32), + s: "List", + }, + { + t: NewSet(Uint32), + s: "Set", + }, + { + t: EmptySet(), + s: "EmptyDict", + }, + { + t: NewEmptyDict(), + s: "EmptyDict", + }, + { + t: NewVariantStruct( + StructField{ + Name: "a", + T: Bool, + }, + StructField{ + Name: "b", + T: Float, + }, + ), + s: "Variant<'a':Bool,'b':Float>", + }, + { + t: NewVariantTuple( + Bool, + Float, + ), + s: "Variant", + }, + { + t: PgType{OID: pg.OIDUnknown}, + s: "PgType(705)", + }, + } { + t.Run(tt.s, func(t *testing.T) { + if got := tt.t.Yql(); got != tt.s { + t.Errorf("s representations not equals:\n\n - got: %s\n\n - want: %s", got, tt.s) + } + }) + } +} + +func TestEqual(t *testing.T) { + tests := []struct { + lhs Type + rhs Type + equal bool + }{ + { + Bool, + Bool, + true, + }, + { + Bool, + Text, + false, + }, + { + Text, + Text, + true, + }, + { + NewOptional(Bool), + NewOptional(Bool), + true, + }, + { + NewOptional(Bool), + NewOptional(Text), + false, + }, + { + NewOptional(Text), + NewOptional(Text), + true, + }, + } + for _, tt := range tests { + t.Run("", func(t *testing.T) { + if equal := Equal(tt.lhs, tt.rhs); equal != tt.equal { + t.Errorf("Equal(%s, %s) = %v, want %v", tt.lhs, tt.rhs, equal, tt.equal) + } + }) + } +} + +func TestOptionalInnerType(t *testing.T) { + tests := []struct { + src Type + innerType Type + isOptional bool + }{ + { + Bool, + nil, + false, + }, + { + Text, + nil, + false, + }, + { + NewOptional(Bool), + Bool, + true, + }, + { + NewOptional(Text), + Text, + true, + }, + { + NewOptional(NewTuple(Text, Bool, Uint64, NewOptional(Int64))), + NewTuple(Text, Bool, Uint64, NewOptional(Int64)), + true, + }, + } + for _, tt := range tests { + t.Run("", func(t *testing.T) { + optional, isOptional := tt.src.(interface { + IsOptional() + InnerType() Type + }) + require.Equal(t, tt.isOptional, isOptional) + var innerType Type + if isOptional { + innerType = optional.InnerType() + } + if tt.innerType == nil { + require.Nil(t, innerType) + } else { + require.True(t, Equal(tt.innerType, innerType)) + } + }) + } +} diff --git a/internal/value/errors.go b/internal/value/errors.go new file mode 100644 index 000000000..0c3212bbf --- /dev/null +++ b/internal/value/errors.go @@ -0,0 +1,8 @@ +package value + +import "errors" + +var ( + ErrCannotCast = errors.New("cannot cast") + errDestinationTypeIsNotAPointer = errors.New("destination type is not a pointer") +) diff --git a/internal/value/nullable.go b/internal/value/nullable.go new file mode 100644 index 000000000..1553d3eb1 --- /dev/null +++ b/internal/value/nullable.go @@ -0,0 +1,450 @@ +package value + +import ( + "fmt" + "time" + + "github.com/ydb-platform/ydb-go-sdk/v3/internal/types" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/xstring" +) + +func NullableBoolValue(v *bool) Value { + if v == nil { + return NullValue(types.Bool) + } + + return OptionalValue(BoolValue(*v)) +} + +func NullableInt8Value(v *int8) Value { + if v == nil { + return NullValue(types.Int8) + } + + return OptionalValue(Int8Value(*v)) +} + +func NullableInt16Value(v *int16) Value { + if v == nil { + return NullValue(types.Int16) + } + + return OptionalValue(Int16Value(*v)) +} + +func NullableInt32Value(v *int32) Value { + if v == nil { + return NullValue(types.Int32) + } + + return OptionalValue(Int32Value(*v)) +} + +func NullableInt64Value(v *int64) Value { + if v == nil { + return NullValue(types.Int64) + } + + return OptionalValue(Int64Value(*v)) +} + +func NullableUint8Value(v *uint8) Value { + if v == nil { + return NullValue(types.Uint8) + } + + return OptionalValue(Uint8Value(*v)) +} + +func NullableUint16Value(v *uint16) Value { + if v == nil { + return NullValue(types.Uint16) + } + + return OptionalValue(Uint16Value(*v)) +} + +func NullableUint32Value(v *uint32) Value { + if v == nil { + return NullValue(types.Uint32) + } + + return OptionalValue(Uint32Value(*v)) +} + +func NullableUint64Value(v *uint64) Value { + if v == nil { + return NullValue(types.Uint64) + } + + return OptionalValue(Uint64Value(*v)) +} + +func NullableFloatValue(v *float32) Value { + if v == nil { + return NullValue(types.Float) + } + + return OptionalValue(FloatValue(*v)) +} + +func NullableDoubleValue(v *float64) Value { + if v == nil { + return NullValue(types.Double) + } + + return OptionalValue(DoubleValue(*v)) +} + +func NullableDateValue(v *uint32) Value { + if v == nil { + return NullValue(types.Date) + } + + return OptionalValue(DateValue(*v)) +} + +func NullableDateValueFromTime(v *time.Time) Value { + if v == nil { + return NullValue(types.Date) + } + + return OptionalValue(DateValueFromTime(*v)) +} + +func NullableDatetimeValue(v *uint32) Value { + if v == nil { + return NullValue(types.Datetime) + } + + return OptionalValue(DatetimeValue(*v)) +} + +func NullableDatetimeValueFromTime(v *time.Time) Value { + if v == nil { + return NullValue(types.Datetime) + } + + return OptionalValue(DatetimeValueFromTime(*v)) +} + +func NullableTzDateValue(v *string) Value { + if v == nil { + return NullValue(types.TzDate) + } + + return OptionalValue(TzDateValue(*v)) +} + +func NullableTzDateValueFromTime(v *time.Time) Value { + if v == nil { + return NullValue(types.TzDate) + } + + return OptionalValue(TzDateValueFromTime(*v)) +} + +func NullableTzDatetimeValue(v *string) Value { + if v == nil { + return NullValue(types.TzDatetime) + } + + return OptionalValue(TzDatetimeValue(*v)) +} + +func NullableTzDatetimeValueFromTime(v *time.Time) Value { + if v == nil { + return NullValue(types.TzDatetime) + } + + return OptionalValue(TzDatetimeValueFromTime(*v)) +} + +func NullableTimestampValue(v *uint64) Value { + if v == nil { + return NullValue(types.Timestamp) + } + + return OptionalValue(TimestampValue(*v)) +} + +func NullableTimestampValueFromTime(v *time.Time) Value { + if v == nil { + return NullValue(types.Timestamp) + } + + return OptionalValue(TimestampValueFromTime(*v)) +} + +func NullableTzTimestampValue(v *string) Value { + if v == nil { + return NullValue(types.TzTimestamp) + } + + return OptionalValue(TzTimestampValue(*v)) +} + +func NullableTzTimestampValueFromTime(v *time.Time) Value { + if v == nil { + return NullValue(types.TzTimestamp) + } + + return OptionalValue(TzTimestampValueFromTime(*v)) +} + +func NullableIntervalValueFromMicroseconds(v *int64) Value { + if v == nil { + return NullValue(types.Interval) + } + + return OptionalValue(IntervalValue(*v)) +} + +func NullableIntervalValueFromDuration(v *time.Duration) Value { + if v == nil { + return NullValue(types.Interval) + } + + return OptionalValue(IntervalValueFromDuration(*v)) +} + +func NullableBytesValue(v *[]byte) Value { + if v == nil { + return NullValue(types.Bytes) + } + + return OptionalValue(BytesValue(*v)) +} + +func NullableBytesValueFromString(v *string) Value { + if v == nil { + return NullValue(types.Bytes) + } + + return OptionalValue(BytesValue(xstring.ToBytes(*v))) +} + +func NullableTextValue(v *string) Value { + if v == nil { + return NullValue(types.Text) + } + + return OptionalValue(TextValue(*v)) +} + +func NullableYSONValue(v *string) Value { + if v == nil { + return NullValue(types.YSON) + } + + return OptionalValue(YSONValue(xstring.ToBytes(*v))) +} + +func NullableYSONValueFromBytes(v *[]byte) Value { + if v == nil { + return NullValue(types.YSON) + } + + return OptionalValue(YSONValue(*v)) +} + +func NullableJSONValue(v *string) Value { + if v == nil { + return NullValue(types.JSON) + } + + return OptionalValue(JSONValue(*v)) +} + +func NullableJSONValueFromBytes(v *[]byte) Value { + if v == nil { + return NullValue(types.JSON) + } + + return OptionalValue(JSONValue(xstring.FromBytes(*v))) +} + +func NullableUUIDValue(v *[16]byte) Value { + if v == nil { + return NullValue(types.UUID) + } + + return OptionalValue(UUIDValue(*v)) +} + +func NullableJSONDocumentValue(v *string) Value { + if v == nil { + return NullValue(types.JSONDocument) + } + + return OptionalValue(JSONDocumentValue(*v)) +} + +func NullableJSONDocumentValueFromBytes(v *[]byte) Value { + if v == nil { + return NullValue(types.JSONDocument) + } + + return OptionalValue(JSONDocumentValue(xstring.FromBytes(*v))) +} + +func NullableDyNumberValue(v *string) Value { + if v == nil { + return NullValue(types.DyNumber) + } + + return OptionalValue(DyNumberValue(*v)) +} + +// Nullable makes optional value from nullable type +// Warning: type interface will be replaced in the future with typed parameters pattern from go1.18 +// +//nolint:gocyclo +func Nullable(t types.Type, v interface{}) Value { + switch t { + case types.Bool: + return NullableBoolValue(v.(*bool)) + case types.Int8: + return NullableInt8Value(v.(*int8)) + case types.Uint8: + return NullableUint8Value(v.(*uint8)) + case types.Int16: + return NullableInt16Value(v.(*int16)) + case types.Uint16: + return NullableUint16Value(v.(*uint16)) + case types.Int32: + return NullableInt32Value(v.(*int32)) + case types.Uint32: + return NullableUint32Value(v.(*uint32)) + case types.Int64: + return NullableInt64Value(v.(*int64)) + case types.Uint64: + return NullableUint64Value(v.(*uint64)) + case types.Float: + return NullableFloatValue(v.(*float32)) + case types.Double: + return NullableDoubleValue(v.(*float64)) + case types.Date: + switch tt := v.(type) { + case *uint32: + return NullableDateValue(tt) + case *time.Time: + return NullableDateValueFromTime(tt) + default: + panic(fmt.Sprintf("unsupported type conversion from %T to TypeDate", tt)) + } + case types.Datetime: + switch tt := v.(type) { + case *uint32: + return NullableDatetimeValue(tt) + case *time.Time: + return NullableDatetimeValueFromTime(tt) + default: + panic(fmt.Sprintf("unsupported type conversion from %T to TypeDatetime", tt)) + } + case types.Timestamp: + switch tt := v.(type) { + case *uint64: + return NullableTimestampValue(tt) + case *time.Time: + return NullableTimestampValueFromTime(tt) + default: + panic(fmt.Sprintf("unsupported type conversion from %T to TypeTimestamp", tt)) + } + case types.Interval: + switch tt := v.(type) { + case *int64: + return NullableIntervalValueFromMicroseconds(tt) + case *time.Duration: + return NullableIntervalValueFromDuration(tt) + default: + panic(fmt.Sprintf("unsupported type conversion from %T to TypeInterval", tt)) + } + case types.TzDate: + switch tt := v.(type) { + case *string: + return NullableTzDateValue(tt) + case *time.Time: + return NullableTzDateValueFromTime(tt) + default: + panic(fmt.Sprintf("unsupported type conversion from %T to TypeTzDate", tt)) + } + case types.TzDatetime: + switch tt := v.(type) { + case *string: + return NullableTzDatetimeValue(tt) + case *time.Time: + return NullableTzDatetimeValueFromTime(tt) + default: + panic(fmt.Sprintf("unsupported type conversion from %T to TypeTzDatetime", tt)) + } + case types.TzTimestamp: + switch tt := v.(type) { + case *string: + return NullableTzTimestampValue(tt) + case *time.Time: + return NullableTzTimestampValueFromTime(tt) + default: + panic(fmt.Sprintf("unsupported type conversion from %T to TypeTzTimestamp", tt)) + } + case types.Bytes: + switch tt := v.(type) { + case *[]byte: + return NullableBytesValue(tt) + case *string: + return NullableBytesValueFromString(tt) + default: + panic(fmt.Sprintf("unsupported type conversion from %T to TypeBytes", tt)) + } + case types.Text: + switch tt := v.(type) { + case *string: + return NullableTextValue(tt) + default: + panic(fmt.Sprintf("unsupported type conversion from %T to TypeText", tt)) + } + case types.YSON: + switch tt := v.(type) { + case *string: + return NullableYSONValue(tt) + case *[]byte: + return NullableYSONValueFromBytes(tt) + default: + panic(fmt.Sprintf("unsupported type conversion from %T to TypeYSON", tt)) + } + case types.JSON: + switch tt := v.(type) { + case *string: + return NullableJSONValue(tt) + case *[]byte: + return NullableJSONValueFromBytes(tt) + default: + panic(fmt.Sprintf("unsupported type conversion from %T to TypeJSON", tt)) + } + case types.UUID: + switch tt := v.(type) { + case *[16]byte: + return NullableUUIDValue(tt) + default: + panic(fmt.Sprintf("unsupported type conversion from %T to TypeUUID", tt)) + } + case types.JSONDocument: + switch tt := v.(type) { + case *string: + return NullableJSONDocumentValue(tt) + case *[]byte: + return NullableJSONDocumentValueFromBytes(tt) + default: + panic(fmt.Sprintf("unsupported type conversion from %T to TypeJSONDocument", tt)) + } + case types.DyNumber: + switch tt := v.(type) { + case *string: + return NullableDyNumberValue(tt) + default: + panic(fmt.Sprintf("unsupported type conversion from %T to TypeDyNumber", tt)) + } + default: + panic(fmt.Sprintf("unsupported type: %T", t)) + } +} diff --git a/internal/value/type.go b/internal/value/type.go deleted file mode 100644 index dd2cc3b07..000000000 --- a/internal/value/type.go +++ /dev/null @@ -1,872 +0,0 @@ -package value - -import ( - "fmt" - - "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/xstring" -) - -type Type interface { - Yql() string - String() string - - toYDB(a *allocator.Allocator) *Ydb.Type - equalsTo(rhs Type) bool -} - -func TypeToYDB(t Type, a *allocator.Allocator) *Ydb.Type { - return t.toYDB(a) -} - -func TypeFromYDB(x *Ydb.Type) Type { - switch v := x.Type.(type) { - case *Ydb.Type_TypeId: - return primitiveTypeFromYDB(v.TypeId) - - case *Ydb.Type_OptionalType: - return Optional(TypeFromYDB(v.OptionalType.Item)) - - case *Ydb.Type_ListType: - return List(TypeFromYDB(v.ListType.Item)) - - case *Ydb.Type_DecimalType: - d := v.DecimalType - - return Decimal(d.Precision, d.Scale) - - case *Ydb.Type_TupleType: - t := v.TupleType - - return Tuple(TypesFromYDB(t.Elements)...) - - case *Ydb.Type_StructType: - s := v.StructType - - return Struct(StructFields(s.Members)...) - - case *Ydb.Type_DictType: - keyType, valueType := TypeFromYDB(v.DictType.Key), TypeFromYDB(v.DictType.Payload) - if valueType.equalsTo(Void()) { - return Set(keyType) - } - - return Dict(keyType, valueType) - - case *Ydb.Type_VariantType: - t := v.VariantType - switch x := t.Type.(type) { - case *Ydb.VariantType_TupleItems: - return VariantTuple(TypesFromYDB(x.TupleItems.Elements)...) - case *Ydb.VariantType_StructItems: - return VariantStruct(StructFields(x.StructItems.Members)...) - default: - panic("ydb: unknown variant type") - } - - case *Ydb.Type_VoidType: - return Void() - - case *Ydb.Type_NullType: - return Null() - - default: - panic("ydb: unknown type") - } -} - -func primitiveTypeFromYDB(t Ydb.Type_PrimitiveTypeId) Type { - switch t { - case Ydb.Type_BOOL: - return TypeBool - case Ydb.Type_INT8: - return TypeInt8 - case Ydb.Type_UINT8: - return TypeUint8 - case Ydb.Type_INT16: - return TypeInt16 - case Ydb.Type_UINT16: - return TypeUint16 - case Ydb.Type_INT32: - return TypeInt32 - case Ydb.Type_UINT32: - return TypeUint32 - case Ydb.Type_INT64: - return TypeInt64 - case Ydb.Type_UINT64: - return TypeUint64 - case Ydb.Type_FLOAT: - return TypeFloat - case Ydb.Type_DOUBLE: - return TypeDouble - case Ydb.Type_DATE: - return TypeDate - case Ydb.Type_DATETIME: - return TypeDatetime - case Ydb.Type_TIMESTAMP: - return TypeTimestamp - case Ydb.Type_INTERVAL: - return TypeInterval - case Ydb.Type_TZ_DATE: - return TypeTzDate - case Ydb.Type_TZ_DATETIME: - return TypeTzDatetime - case Ydb.Type_TZ_TIMESTAMP: - return TypeTzTimestamp - case Ydb.Type_STRING: - return TypeBytes - case Ydb.Type_UTF8: - return TypeText - case Ydb.Type_YSON: - return TypeYSON - case Ydb.Type_JSON: - return TypeJSON - case Ydb.Type_UUID: - return TypeUUID - case Ydb.Type_JSON_DOCUMENT: - return TypeJSONDocument - case Ydb.Type_DYNUMBER: - return TypeDyNumber - default: - panic("ydb: unexpected type") - } -} - -func TypesFromYDB(es []*Ydb.Type) []Type { - ts := make([]Type, len(es)) - for i, el := range es { - ts[i] = TypeFromYDB(el) - } - - return ts -} - -func TypesEqual(a, b Type) bool { - return a.equalsTo(b) -} - -type DecimalType struct { - Precision uint32 - Scale uint32 -} - -func (v *DecimalType) String() string { - return v.Yql() -} - -func (v *DecimalType) Name() string { - return "Decimal" -} - -func (v *DecimalType) Yql() string { - return fmt.Sprintf("%s(%d,%d)", v.Name(), v.Precision, v.Scale) -} - -func (v *DecimalType) equalsTo(rhs Type) bool { - vv, ok := rhs.(*DecimalType) - - return ok && *v == *vv -} - -func (v *DecimalType) toYDB(a *allocator.Allocator) *Ydb.Type { - decimal := a.Decimal() - - decimal.Scale = v.Scale - decimal.Precision = v.Precision - - typeDecimal := a.TypeDecimal() - typeDecimal.DecimalType = decimal - - t := a.Type() - t.Type = typeDecimal - - return t -} - -func Decimal(precision, scale uint32) *DecimalType { - return &DecimalType{ - Precision: precision, - Scale: scale, - } -} - -type dictType struct { - keyType Type - valueType Type -} - -func (v *dictType) String() string { - return v.Yql() -} - -func (v *dictType) Yql() string { - buffer := xstring.Buffer() - defer buffer.Free() - buffer.WriteString("Dict<") - buffer.WriteString(v.keyType.Yql()) - buffer.WriteByte(',') - buffer.WriteString(v.valueType.Yql()) - buffer.WriteByte('>') - - return buffer.String() -} - -func (v *dictType) equalsTo(rhs Type) bool { - vv, ok := rhs.(*dictType) - if !ok { - return false - } - if !v.keyType.equalsTo(vv.keyType) { - return false - } - if !v.valueType.equalsTo(vv.valueType) { - return false - } - - return true -} - -func (v *dictType) toYDB(a *allocator.Allocator) *Ydb.Type { - t := a.Type() - - typeDict := a.TypeDict() - - typeDict.DictType = a.Dict() - - typeDict.DictType.Key = v.keyType.toYDB(a) - typeDict.DictType.Payload = v.valueType.toYDB(a) - - t.Type = typeDict - - return t -} - -func Dict(key, value Type) (v *dictType) { - return &dictType{ - keyType: key, - valueType: value, - } -} - -type emptyListType struct{} - -func (v emptyListType) Yql() string { - return "EmptyList" -} - -func (v emptyListType) String() string { - return v.Yql() -} - -func (emptyListType) equalsTo(rhs Type) bool { - _, ok := rhs.(emptyListType) - - return ok -} - -func (emptyListType) toYDB(a *allocator.Allocator) *Ydb.Type { - t := a.Type() - - t.Type = a.TypeEmptyList() - - return t -} - -func EmptyList() emptyListType { - return emptyListType{} -} - -type emptyDictType struct{} - -func (v emptyDictType) String() string { - return v.Yql() -} - -func (v emptyDictType) Yql() string { - return "EmptyDict" -} - -func (emptyDictType) equalsTo(rhs Type) bool { - _, ok := rhs.(emptyDictType) - - return ok -} - -func (emptyDictType) toYDB(a *allocator.Allocator) *Ydb.Type { - t := a.Type() - - t.Type = a.TypeEmptyDict() - - return t -} - -func EmptySet() emptyDictType { - return emptyDictType{} -} - -func EmptyDict() emptyDictType { - return emptyDictType{} -} - -type listType struct { - itemType Type -} - -func (v *listType) String() string { - return v.Yql() -} - -func (v *listType) Yql() string { - return "List<" + v.itemType.Yql() + ">" -} - -func (v *listType) equalsTo(rhs Type) bool { - vv, ok := rhs.(*listType) - if !ok { - return false - } - - return v.itemType.equalsTo(vv.itemType) -} - -func (v *listType) toYDB(a *allocator.Allocator) *Ydb.Type { - t := a.Type() - - list := a.List() - - list.Item = v.itemType.toYDB(a) - - typeList := a.TypeList() - typeList.ListType = list - - t.Type = typeList - - return t -} - -func List(t Type) *listType { - return &listType{ - itemType: t, - } -} - -type setType struct { - itemType Type -} - -func (v *setType) String() string { - return v.Yql() -} - -func (v *setType) Yql() string { - return "Set<" + v.itemType.Yql() + ">" -} - -func (v *setType) equalsTo(rhs Type) bool { - vv, ok := rhs.(*setType) - if !ok { - return false - } - - return v.itemType.equalsTo(vv.itemType) -} - -func (v *setType) toYDB(a *allocator.Allocator) *Ydb.Type { - t := a.Type() - - typeDict := a.TypeDict() - - typeDict.DictType = a.Dict() - - typeDict.DictType.Key = v.itemType.toYDB(a) - typeDict.DictType.Payload = _voidType - - t.Type = typeDict - - return t -} - -func Set(t Type) *setType { - return &setType{ - itemType: t, - } -} - -type optionalType struct { - innerType Type -} - -func (v optionalType) IsOptional() {} - -func (v optionalType) InnerType() Type { - return v.innerType -} - -func (v optionalType) String() string { - return v.Yql() -} - -func (v optionalType) Yql() string { - return "Optional<" + v.innerType.Yql() + ">" -} - -func (v optionalType) equalsTo(rhs Type) bool { - vv, ok := rhs.(optionalType) - if !ok { - return false - } - - return v.innerType.equalsTo(vv.innerType) -} - -func (v optionalType) toYDB(a *allocator.Allocator) *Ydb.Type { - t := a.Type() - - typeOptional := a.TypeOptional() - - typeOptional.OptionalType = a.Optional() - - typeOptional.OptionalType.Item = v.innerType.toYDB(a) - - t.Type = typeOptional - - return t -} - -func Optional(t Type) optionalType { - return optionalType{ - innerType: t, - } -} - -type PrimitiveType uint - -func (v PrimitiveType) String() string { - return v.Yql() -} - -func (v PrimitiveType) Yql() string { - return primitiveString[v] -} - -const ( - TypeUnknown PrimitiveType = iota - TypeBool - TypeInt8 - TypeUint8 - TypeInt16 - TypeUint16 - TypeInt32 - TypeUint32 - TypeInt64 - TypeUint64 - TypeFloat - TypeDouble - TypeDate - TypeDatetime - TypeTimestamp - TypeInterval - TypeTzDate - TypeTzDatetime - TypeTzTimestamp - TypeBytes - TypeText - TypeYSON - TypeJSON - TypeUUID - TypeJSONDocument - TypeDyNumber -) - -var primitive = [...]*Ydb.Type{ - TypeBool: {Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_BOOL}}, - TypeInt8: {Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_INT8}}, - TypeUint8: {Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_UINT8}}, - TypeInt16: {Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_INT16}}, - TypeUint16: {Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_UINT16}}, - TypeInt32: {Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_INT32}}, - TypeUint32: {Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_UINT32}}, - TypeInt64: {Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_INT64}}, - TypeUint64: {Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_UINT64}}, - TypeFloat: {Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_FLOAT}}, - TypeDouble: {Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_DOUBLE}}, - TypeDate: {Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_DATE}}, - TypeDatetime: {Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_DATETIME}}, - TypeTimestamp: {Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_TIMESTAMP}}, - TypeInterval: {Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_INTERVAL}}, - TypeTzDate: {Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_TZ_DATE}}, - TypeTzDatetime: {Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_TZ_DATETIME}}, - TypeTzTimestamp: {Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_TZ_TIMESTAMP}}, - TypeBytes: {Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_STRING}}, - TypeText: {Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_UTF8}}, - TypeYSON: {Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_YSON}}, - TypeJSON: {Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_JSON}}, - TypeUUID: {Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_UUID}}, - TypeJSONDocument: {Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_JSON_DOCUMENT}}, - TypeDyNumber: {Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_DYNUMBER}}, -} - -var primitiveString = [...]string{ - TypeUnknown: "", - TypeBool: "Bool", - TypeInt8: "Int8", - TypeUint8: "Uint8", - TypeInt16: "Int16", - TypeUint16: "Uint16", - TypeInt32: "Int32", - TypeUint32: "Uint32", - TypeInt64: "Int64", - TypeUint64: "Uint64", - TypeFloat: "Float", - TypeDouble: "Double", - TypeDate: "Date", - TypeDatetime: "Datetime", - TypeTimestamp: "Timestamp", - TypeInterval: "Interval", - TypeTzDate: "TzDate", - TypeTzDatetime: "TzDatetime", - TypeTzTimestamp: "TzTimestamp", - TypeBytes: "String", - TypeText: "Utf8", - TypeYSON: "Yson", - TypeJSON: "Json", - TypeUUID: "Uuid", - TypeJSONDocument: "JsonDocument", - TypeDyNumber: "DyNumber", -} - -func (v PrimitiveType) equalsTo(rhs Type) bool { - vv, ok := rhs.(PrimitiveType) - if !ok { - return false - } - - return v == vv -} - -func (v PrimitiveType) toYDB(*allocator.Allocator) *Ydb.Type { - return primitive[v] -} - -type ( - StructField struct { - Name string - T Type - } - StructType struct { - fields []StructField - } -) - -func (v *StructType) String() string { - return v.Yql() -} - -func (v *StructType) Yql() string { - buffer := xstring.Buffer() - defer buffer.Free() - buffer.WriteString("Struct<") - for i := range v.fields { - if i > 0 { - buffer.WriteByte(',') - } - buffer.WriteString("'" + v.fields[i].Name + "'") - buffer.WriteByte(':') - buffer.WriteString(v.fields[i].T.Yql()) - } - buffer.WriteByte('>') - - return buffer.String() -} - -func (v *StructType) equalsTo(rhs Type) bool { - vv, ok := rhs.(*StructType) - if !ok { - return false - } - if len(v.fields) != len(vv.fields) { - return false - } - for i := range v.fields { - if v.fields[i].Name != vv.fields[i].Name { - return false - } - if !v.fields[i].T.equalsTo(vv.fields[i].T) { - return false - } - } - - return true -} - -func (v *StructType) toYDB(a *allocator.Allocator) *Ydb.Type { - t := a.Type() - - typeStruct := a.TypeStruct() - - typeStruct.StructType = a.Struct() - - for i := range v.fields { - structMember := a.StructMember() - structMember.Name = v.fields[i].Name - structMember.Type = v.fields[i].T.toYDB(a) - typeStruct.StructType.Members = append( - typeStruct.StructType.Members, - structMember, - ) - } - - t.Type = typeStruct - - return t -} - -func Struct(fields ...StructField) (v *StructType) { - return &StructType{ - fields: fields, - } -} - -func StructFields(ms []*Ydb.StructMember) []StructField { - fs := make([]StructField, len(ms)) - for i, m := range ms { - fs[i] = StructField{ - Name: m.Name, - T: TypeFromYDB(m.Type), - } - } - - return fs -} - -type TupleType struct { - items []Type -} - -func (v *TupleType) String() string { - return v.Yql() -} - -func (v *TupleType) Yql() string { - buffer := xstring.Buffer() - defer buffer.Free() - buffer.WriteString("Tuple<") - for i, t := range v.items { - if i > 0 { - buffer.WriteByte(',') - } - buffer.WriteString(t.Yql()) - } - buffer.WriteByte('>') - - return buffer.String() -} - -func (v *TupleType) equalsTo(rhs Type) bool { - vv, ok := rhs.(*TupleType) - if !ok { - return false - } - if len(v.items) != len(vv.items) { - return false - } - for i := range v.items { - if !v.items[i].equalsTo(vv.items[i]) { - return false - } - } - - return true -} - -func (v *TupleType) toYDB(a *allocator.Allocator) *Ydb.Type { - var items []Type - if v != nil { - items = v.items - } - t := a.Type() - - typeTuple := a.TypeTuple() - - typeTuple.TupleType = a.Tuple() - - for _, vv := range items { - typeTuple.TupleType.Elements = append(typeTuple.TupleType.Elements, vv.toYDB(a)) - } - - t.Type = typeTuple - - return t -} - -func Tuple(items ...Type) (v *TupleType) { - return &TupleType{ - items: items, - } -} - -type variantStructType struct { - *StructType -} - -func (v *variantStructType) Yql() string { - buffer := xstring.Buffer() - defer buffer.Free() - buffer.WriteString("Variant<") - for i := range v.fields { - if i > 0 { - buffer.WriteByte(',') - } - buffer.WriteString("'" + v.fields[i].Name + "'") - buffer.WriteByte(':') - buffer.WriteString(v.fields[i].T.Yql()) - } - buffer.WriteByte('>') - - return buffer.String() -} - -func (v *variantStructType) equalsTo(rhs Type) bool { - switch t := rhs.(type) { - case *variantStructType: - return v.StructType.equalsTo(t.StructType) - case *StructType: - return v.StructType.equalsTo(t) - default: - return false - } -} - -func (v *variantStructType) toYDB(a *allocator.Allocator) *Ydb.Type { - t := a.Type() - - typeVariant := a.TypeVariant() - - typeVariant.VariantType = a.Variant() - - structItems := a.VariantStructItems() - structItems.StructItems = v.StructType.toYDB(a).Type.(*Ydb.Type_StructType).StructType - - typeVariant.VariantType.Type = structItems - - t.Type = typeVariant - - return t -} - -func VariantStruct(fields ...StructField) *variantStructType { - return &variantStructType{ - StructType: Struct(fields...), - } -} - -type variantTupleType struct { - *TupleType -} - -func (v *variantTupleType) Yql() string { - buffer := xstring.Buffer() - defer buffer.Free() - buffer.WriteString("Variant<") - for i, t := range v.items { - if i > 0 { - buffer.WriteByte(',') - } - buffer.WriteString(t.Yql()) - } - buffer.WriteByte('>') - - return buffer.String() -} - -func (v *variantTupleType) equalsTo(rhs Type) bool { - switch t := rhs.(type) { - case *variantTupleType: - return v.TupleType.equalsTo(t.TupleType) - case *TupleType: - return v.TupleType.equalsTo(t) - default: - return false - } -} - -func (v *variantTupleType) toYDB(a *allocator.Allocator) *Ydb.Type { - t := a.Type() - - typeVariant := a.TypeVariant() - - typeVariant.VariantType = a.Variant() - - tupleItems := a.VariantTupleItems() - tupleItems.TupleItems = v.TupleType.toYDB(a).Type.(*Ydb.Type_TupleType).TupleType - - typeVariant.VariantType.Type = tupleItems - - t.Type = typeVariant - - return t -} - -func VariantTuple(items ...Type) *variantTupleType { - return &variantTupleType{ - TupleType: Tuple(items...), - } -} - -type voidType struct{} - -func (v voidType) String() string { - return v.Yql() -} - -func (v voidType) Yql() string { - return "Void" -} - -var _voidType = &Ydb.Type{ - Type: &Ydb.Type_VoidType{}, -} - -func (v voidType) equalsTo(rhs Type) bool { - _, ok := rhs.(voidType) - - return ok -} - -func (voidType) toYDB(*allocator.Allocator) *Ydb.Type { - return _voidType -} - -func Void() voidType { - return voidType{} -} - -type nullType struct{} - -func (v nullType) String() string { - return v.Yql() -} - -func (v nullType) Yql() string { - return "Null" -} - -var _nullType = &Ydb.Type{ - Type: &Ydb.Type_NullType{}, -} - -func (v nullType) equalsTo(rhs Type) bool { - _, ok := rhs.(nullType) - - return ok -} - -func (nullType) toYDB(*allocator.Allocator) *Ydb.Type { - return _nullType -} - -func Null() nullType { - return nullType{} -} diff --git a/internal/value/type_test.go b/internal/value/type_test.go deleted file mode 100644 index 22236684e..000000000 --- a/internal/value/type_test.go +++ /dev/null @@ -1,275 +0,0 @@ -package value - -import ( - "testing" -) - -func TestTypeToString(t *testing.T) { - for _, tt := range []struct { - t Type - s string - }{ - { - t: Void(), - s: "Void", - }, - { - t: Null(), - s: "Null", - }, - { - t: TypeBool, - s: "Bool", - }, - { - t: TypeInt8, - s: "Int8", - }, - { - t: TypeUint8, - s: "Uint8", - }, - { - t: TypeInt16, - s: "Int16", - }, - { - t: TypeUint16, - s: "Uint16", - }, - { - t: TypeInt32, - s: "Int32", - }, - { - t: TypeUint32, - s: "Uint32", - }, - { - t: TypeInt64, - s: "Int64", - }, - { - t: TypeUint64, - s: "Uint64", - }, - { - t: TypeFloat, - s: "Float", - }, - { - t: TypeDouble, - s: "Double", - }, - { - t: TypeDate, - s: "Date", - }, - { - t: TypeDatetime, - s: "Datetime", - }, - { - t: TypeTimestamp, - s: "Timestamp", - }, - { - t: TypeInterval, - s: "Interval", - }, - { - t: TypeTzDate, - s: "TzDate", - }, - { - t: TypeTzDatetime, - s: "TzDatetime", - }, - { - t: TypeTzTimestamp, - s: "TzTimestamp", - }, - { - t: TypeBytes, - s: "String", - }, - { - t: TypeText, - s: "Utf8", - }, - { - t: TypeYSON, - s: "Yson", - }, - { - t: TypeJSON, - s: "Json", - }, - { - t: TypeUUID, - s: "Uuid", - }, - { - t: TypeJSONDocument, - s: "JsonDocument", - }, - { - t: TypeDyNumber, - s: "DyNumber", - }, - { - t: Optional(TypeBool), - s: "Optional", - }, - { - t: Optional(TypeInt8), - s: "Optional", - }, - { - t: Optional(TypeUint8), - s: "Optional", - }, - { - t: Optional(TypeInt16), - s: "Optional", - }, - { - t: Optional(TypeUint16), - s: "Optional", - }, - { - t: Optional(TypeInt32), - s: "Optional", - }, - { - t: Optional(TypeUint32), - s: "Optional", - }, - { - t: Optional(TypeInt64), - s: "Optional", - }, - { - t: Optional(TypeUint64), - s: "Optional", - }, - { - t: Optional(TypeFloat), - s: "Optional", - }, - { - t: Optional(TypeDouble), - s: "Optional", - }, - { - t: Optional(TypeDate), - s: "Optional", - }, - { - t: Optional(TypeDatetime), - s: "Optional", - }, - { - t: Optional(TypeTimestamp), - s: "Optional", - }, - { - t: Optional(TypeInterval), - s: "Optional", - }, - { - t: Optional(TypeTzDate), - s: "Optional", - }, - { - t: Optional(TypeTzDatetime), - s: "Optional", - }, - { - t: Optional(TypeTzTimestamp), - s: "Optional", - }, - { - t: Optional(TypeBytes), - s: "Optional", - }, - { - t: Optional(TypeText), - s: "Optional", - }, - { - t: Optional(TypeYSON), - s: "Optional", - }, - { - t: Optional(TypeJSON), - s: "Optional", - }, - { - t: Optional(TypeUUID), - s: "Optional", - }, - { - t: Optional(TypeJSONDocument), - s: "Optional", - }, - { - t: Optional(TypeDyNumber), - s: "Optional", - }, - { - t: Decimal(22, 9), - s: "Decimal(22,9)", - }, - { - t: Dict(TypeText, TypeTimestamp), - s: "Dict", - }, - { - t: EmptyList(), - s: "EmptyList", - }, - { - t: List(TypeUint32), - s: "List", - }, - { - t: Set(TypeUint32), - s: "Set", - }, - { - t: EmptySet(), - s: "EmptyDict", - }, - { - t: EmptyDict(), - s: "EmptyDict", - }, - { - t: VariantStruct( - StructField{ - Name: "a", - T: TypeBool, - }, - StructField{ - Name: "b", - T: TypeFloat, - }, - ), - s: "Variant<'a':Bool,'b':Float>", - }, - { - t: VariantTuple( - TypeBool, - TypeFloat, - ), - s: "Variant", - }, - } { - t.Run(tt.s, func(t *testing.T) { - if got := tt.t.Yql(); got != tt.s { - t.Errorf("s representations not equals:\n\n - got: %s\n\n - want: %s", got, tt.s) - } - }) - } -} diff --git a/internal/value/value.go b/internal/value/value.go index c86483d65..32a2b7b60 100644 --- a/internal/value/value.go +++ b/internal/value/value.go @@ -2,9 +2,9 @@ package value import ( "encoding/binary" - "errors" "fmt" "math/big" + "reflect" "sort" "strconv" "time" @@ -14,6 +14,7 @@ import ( "github.com/ydb-platform/ydb-go-sdk/v3/internal/allocator" "github.com/ydb-platform/ydb-go-sdk/v3/internal/decimal" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/types" "github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors" "github.com/ydb-platform/ydb-go-sdk/v3/internal/xstring" ) @@ -24,7 +25,7 @@ const ( ) type Value interface { - Type() Type + Type() types.Type Yql() string castTo(dst interface{}) error @@ -34,7 +35,7 @@ type Value interface { func ToYDB(v Value, a *allocator.Allocator) *Ydb.TypedValue { tv := a.TypedValue() - tv.Type = v.Type().toYDB(a) + tv.Type = v.Type().ToYDB(a) tv.Value = v.toYDB(a) return tv @@ -57,16 +58,16 @@ func FromYDB(t *Ydb.Type, v *Ydb.Value) Value { return vv } -func nullValueFromYDB(x *Ydb.Value, t Type) (_ Value, ok bool) { +func nullValueFromYDB(x *Ydb.Value, t types.Type) (_ Value, ok bool) { for { - switch xx := x.Value.(type) { + switch xx := x.GetValue().(type) { case *Ydb.Value_NestedValue: x = xx.NestedValue case *Ydb.Value_NullFlagValue: switch tt := t.(type) { - case optionalType: - return NullValue(tt.innerType), true - case voidType: + case types.Optional: + return NullValue(tt.InnerType()), true + case types.Void: return VoidValue(), true default: return nil, false @@ -77,57 +78,57 @@ func nullValueFromYDB(x *Ydb.Value, t Type) (_ Value, ok bool) { } } -func primitiveValueFromYDB(t PrimitiveType, v *Ydb.Value) (Value, error) { +func primitiveValueFromYDB(t types.Primitive, v *Ydb.Value) (Value, error) { switch t { - case TypeBool: + case types.Bool: return BoolValue(v.GetBoolValue()), nil - case TypeInt8: + case types.Int8: return Int8Value(int8(v.GetInt32Value())), nil - case TypeInt16: + case types.Int16: return Int16Value(int16(v.GetInt32Value())), nil - case TypeInt32: + case types.Int32: return Int32Value(v.GetInt32Value()), nil - case TypeInt64: + case types.Int64: return Int64Value(v.GetInt64Value()), nil - case TypeUint8: + case types.Uint8: return Uint8Value(uint8(v.GetUint32Value())), nil - case TypeUint16: + case types.Uint16: return Uint16Value(uint16(v.GetUint32Value())), nil - case TypeUint32: + case types.Uint32: return Uint32Value(v.GetUint32Value()), nil - case TypeUint64: + case types.Uint64: return Uint64Value(v.GetUint64Value()), nil - case TypeDate: + case types.Date: return DateValue(v.GetUint32Value()), nil - case TypeDatetime: + case types.Datetime: return DatetimeValue(v.GetUint32Value()), nil - case TypeInterval: + case types.Interval: return IntervalValue(v.GetInt64Value()), nil - case TypeTimestamp: + case types.Timestamp: return TimestampValue(v.GetUint64Value()), nil - case TypeFloat: + case types.Float: return FloatValue(v.GetFloatValue()), nil - case TypeDouble: + case types.Double: return DoubleValue(v.GetDoubleValue()), nil - case TypeText: + case types.Text: return TextValue(v.GetTextValue()), nil - case TypeYSON: + case types.YSON: switch vv := v.GetValue().(type) { case *Ydb.Value_TextValue: return YSONValue(xstring.ToBytes(vv.TextValue)), nil @@ -137,29 +138,29 @@ func primitiveValueFromYDB(t PrimitiveType, v *Ydb.Value) (Value, error) { return nil, xerrors.WithStackTrace(fmt.Errorf("uncovered YSON internal type: %T", vv)) } - case TypeJSON: + case types.JSON: return JSONValue(v.GetTextValue()), nil - case TypeJSONDocument: + case types.JSONDocument: return JSONDocumentValue(v.GetTextValue()), nil - case TypeDyNumber: + case types.DyNumber: return DyNumberValue(v.GetTextValue()), nil - case TypeTzDate: + case types.TzDate: return TzDateValue(v.GetTextValue()), nil - case TypeTzDatetime: + case types.TzDatetime: return TzDatetimeValue(v.GetTextValue()), nil - case TypeTzTimestamp: + case types.TzTimestamp: return TzTimestampValue(v.GetTextValue()), nil - case TypeBytes: + case types.Bytes: return BytesValue(v.GetBytesValue()), nil - case TypeUUID: - return UUIDValue(BigEndianUint128(v.High_128, v.GetLow_128())), nil + case types.UUID: + return UUIDValue(BigEndianUint128(v.GetHigh_128(), v.GetLow_128())), nil default: return nil, xerrors.WithStackTrace(fmt.Errorf("uncovered primitive type: %T", t)) @@ -167,125 +168,133 @@ func primitiveValueFromYDB(t PrimitiveType, v *Ydb.Value) (Value, error) { } func fromYDB(t *Ydb.Type, v *Ydb.Value) (Value, error) { - tt := TypeFromYDB(t) + tt := types.TypeFromYDB(t) if vv, ok := nullValueFromYDB(v, tt); ok { return vv, nil } switch ttt := tt.(type) { - case PrimitiveType: + case types.Primitive: return primitiveValueFromYDB(ttt, v) - case voidType: + case types.Void: return VoidValue(), nil - case nullType: + case types.Null: return NullValue(tt), nil - case *DecimalType: - return DecimalValue(BigEndianUint128(v.High_128, v.GetLow_128()), ttt.Precision, ttt.Scale), nil + case *types.Decimal: + return DecimalValue(BigEndianUint128(v.GetHigh_128(), v.GetLow_128()), ttt.Precision(), ttt.Scale()), nil - case optionalType: - t = t.Type.(*Ydb.Type_OptionalType).OptionalType.Item - if nestedValue, ok := v.Value.(*Ydb.Value_NestedValue); ok { + case types.Optional: + t = t.GetType().(*Ydb.Type_OptionalType).OptionalType.GetItem() + if nestedValue, ok := v.GetValue().(*Ydb.Value_NestedValue); ok { return OptionalValue(FromYDB(t, nestedValue.NestedValue)), nil } return OptionalValue(FromYDB(t, v)), nil - case *listType: + case *types.List: return ListValue(func() []Value { vv := make([]Value, len(v.GetItems())) a := allocator.New() defer a.Free() for i, vvv := range v.GetItems() { - vv[i] = FromYDB(ttt.itemType.toYDB(a), vvv) + vv[i] = FromYDB(ttt.ItemType().ToYDB(a), vvv) } return vv }()...), nil - case *TupleType: + case *types.Tuple: return TupleValue(func() []Value { vv := make([]Value, len(v.GetItems())) a := allocator.New() defer a.Free() for i, vvv := range v.GetItems() { - vv[i] = FromYDB(ttt.items[i].toYDB(a), vvv) + vv[i] = FromYDB(ttt.ItemType(i).ToYDB(a), vvv) } return vv }()...), nil - case *StructType: + case *types.Struct: return StructValue(func() []StructValueField { vv := make([]StructValueField, len(v.GetItems())) a := allocator.New() defer a.Free() for i, vvv := range v.GetItems() { vv[i] = StructValueField{ - Name: ttt.fields[i].Name, - V: FromYDB(ttt.fields[i].T.toYDB(a), vvv), + Name: ttt.Field(i).Name, + V: FromYDB(ttt.Field(i).T.ToYDB(a), vvv), } } return vv }()...), nil - case *dictType: + case *types.Dict: return DictValue(func() []DictValueField { vv := make([]DictValueField, len(v.GetPairs())) a := allocator.New() defer a.Free() for i, vvv := range v.GetPairs() { vv[i] = DictValueField{ - K: FromYDB(ttt.keyType.toYDB(a), vvv.Key), - V: FromYDB(ttt.valueType.toYDB(a), vvv.Payload), + K: FromYDB(ttt.KeyType().ToYDB(a), vvv.GetKey()), + V: FromYDB(ttt.ValueType().ToYDB(a), vvv.GetPayload()), } } return vv }()...), nil - case *setType: + case *types.Set: return SetValue(func() []Value { vv := make([]Value, len(v.GetPairs())) a := allocator.New() defer a.Free() for i, vvv := range v.GetPairs() { - vv[i] = FromYDB(ttt.itemType.toYDB(a), vvv.Key) + vv[i] = FromYDB(ttt.ItemType().ToYDB(a), vvv.GetKey()) } return vv }()...), nil - case *variantStructType: + case *types.VariantStruct: a := allocator.New() defer a.Free() return VariantValueStruct( FromYDB( - ttt.StructType.fields[v.VariantIndex].T.toYDB(a), - v.Value.(*Ydb.Value_NestedValue).NestedValue, + ttt.Struct.Field(int(v.GetVariantIndex())).T.ToYDB(a), + v.GetValue().(*Ydb.Value_NestedValue).NestedValue, ), - ttt.StructType.fields[v.VariantIndex].Name, - ttt.StructType, + ttt.Struct.Field(int(v.GetVariantIndex())).Name, + ttt.Struct, ), nil - case *variantTupleType: + case *types.VariantTuple: a := allocator.New() defer a.Free() return VariantValueTuple( FromYDB( - ttt.TupleType.items[v.VariantIndex].toYDB(a), - v.Value.(*Ydb.Value_NestedValue).NestedValue, + ttt.Tuple.ItemType(int(v.GetVariantIndex())).ToYDB(a), + v.GetValue().(*Ydb.Value_NestedValue).NestedValue, ), - v.VariantIndex, - ttt.TupleType, + v.GetVariantIndex(), + ttt.Tuple, ), nil + case *types.PgType: + return &pgValue{ + t: types.PgType{ + OID: ttt.OID, + }, + val: v.GetTextValue(), + }, nil + default: return nil, xerrors.WithStackTrace(fmt.Errorf("uncovered type: %T", ttt)) } @@ -304,7 +313,10 @@ func (v boolValue) castTo(dst interface{}) error { return nil default: - return xerrors.WithStackTrace(fmt.Errorf("cannot cast '%+v' (type '%s') to '%T' destination", v, v.Type().Yql(), vv)) + return xerrors.WithStackTrace(fmt.Errorf( + "%w '%+v' (type '%s') to '%T' destination", + ErrCannotCast, v, v.Type().Yql(), vv, + )) } } @@ -312,8 +324,8 @@ func (v boolValue) Yql() string { return strconv.FormatBool(bool(v)) } -func (boolValue) Type() Type { - return TypeBool +func (boolValue) Type() types.Type { + return types.Bool } func (v boolValue) toYDB(a *allocator.Allocator) *Ydb.Value { @@ -352,7 +364,10 @@ func (v dateValue) castTo(dst interface{}) error { return nil default: - return xerrors.WithStackTrace(fmt.Errorf("cannot cast '%+v' (type '%s') to '%T' destination", v, v.Type().Yql(), vv)) + return xerrors.WithStackTrace(fmt.Errorf( + "%w '%+v' (type '%s') to '%T' destination", + ErrCannotCast, v, v.Type().Yql(), vv, + )) } } @@ -360,8 +375,8 @@ func (v dateValue) Yql() string { return fmt.Sprintf("%s(%q)", v.Type().Yql(), DateToTime(uint32(v)).UTC().Format(LayoutDate)) } -func (dateValue) Type() Type { - return TypeDate +func (dateValue) Type() types.Type { + return types.Date } func (v dateValue) toYDB(a *allocator.Allocator) *Ydb.Value { @@ -405,7 +420,10 @@ func (v datetimeValue) castTo(dst interface{}) error { return nil default: - return xerrors.WithStackTrace(fmt.Errorf("cannot cast '%+v' (type '%s') to '%T' destination", v, v.Type().Yql(), vv)) + return xerrors.WithStackTrace(fmt.Errorf( + "%w '%+v' (type '%s') to '%T' destination", + ErrCannotCast, v, v.Type().Yql(), vv, + )) } } @@ -413,8 +431,8 @@ func (v datetimeValue) Yql() string { return fmt.Sprintf("%s(%q)", v.Type().Yql(), DatetimeToTime(uint32(v)).UTC().Format(LayoutDatetime)) } -func (datetimeValue) Type() Type { - return TypeDatetime +func (datetimeValue) Type() types.Type { + return types.Datetime } func (v datetimeValue) toYDB(a *allocator.Allocator) *Ydb.Value { @@ -440,7 +458,7 @@ var _ DecimalValuer = (*decimalValue)(nil) type decimalValue struct { value [16]byte - innerType *DecimalType + innerType *types.Decimal } func (v *decimalValue) Value() [16]byte { @@ -448,11 +466,11 @@ func (v *decimalValue) Value() [16]byte { } func (v *decimalValue) Precision() uint32 { - return v.innerType.Precision + return v.innerType.Precision() } func (v *decimalValue) Scale() uint32 { - return v.innerType.Scale + return v.innerType.Scale() } type DecimalValuer interface { @@ -462,7 +480,10 @@ type DecimalValuer interface { } func (v *decimalValue) castTo(dst interface{}) error { - return xerrors.WithStackTrace(fmt.Errorf("cannot cast '%+v' to '%T' destination", v, dst)) + return xerrors.WithStackTrace(fmt.Errorf( + "%w '%+v' to '%T' destination", + ErrCannotCast, v, dst, + )) } func (v *decimalValue) Yql() string { @@ -471,19 +492,19 @@ func (v *decimalValue) Yql() string { buffer.WriteString(v.innerType.Name()) buffer.WriteByte('(') buffer.WriteByte('"') - s := decimal.FromBytes(v.value[:], v.innerType.Precision, v.innerType.Scale).String() - buffer.WriteString(s[:len(s)-int(v.innerType.Scale)] + "." + s[len(s)-int(v.innerType.Scale):]) + s := decimal.FromBytes(v.value[:], v.innerType.Precision(), v.innerType.Scale()).String() + buffer.WriteString(s[:len(s)-int(v.innerType.Scale())] + "." + s[len(s)-int(v.innerType.Scale()):]) buffer.WriteByte('"') buffer.WriteByte(',') - buffer.WriteString(strconv.FormatUint(uint64(v.innerType.Precision), 10)) + buffer.WriteString(strconv.FormatUint(uint64(v.innerType.Precision()), 10)) buffer.WriteByte(',') - buffer.WriteString(strconv.FormatUint(uint64(v.innerType.Scale), 10)) + buffer.WriteString(strconv.FormatUint(uint64(v.innerType.Scale()), 10)) buffer.WriteByte(')') return buffer.String() } -func (v *decimalValue) Type() Type { +func (v *decimalValue) Type() types.Type { return v.innerType } @@ -511,10 +532,10 @@ func DecimalValueFromBigInt(v *big.Int, precision, scale uint32) *decimalValue { func DecimalValue(v [16]byte, precision, scale uint32) *decimalValue { return &decimalValue{ value: v, - innerType: &DecimalType{ - Precision: precision, - Scale: scale, - }, + innerType: types.NewDecimal( + precision, + scale, + ), } } @@ -524,7 +545,7 @@ type ( V Value } dictValue struct { - t Type + t types.Type values []DictValueField } ) @@ -539,7 +560,10 @@ func (v *dictValue) DictValues() map[Value]Value { } func (v *dictValue) castTo(dst interface{}) error { - return xerrors.WithStackTrace(fmt.Errorf("cannot cast '%+v' to '%T' destination", v, dst)) + return xerrors.WithStackTrace(fmt.Errorf( + "%w '%+v' to '%T' destination", + ErrCannotCast, v, dst, + )) } func (v *dictValue) Yql() string { @@ -559,7 +583,7 @@ func (v *dictValue) Yql() string { return buffer.String() } -func (v *dictValue) Type() Type { +func (v *dictValue) Type() types.Type { return v.t } @@ -576,7 +600,7 @@ func (v *dictValue) toYDB(a *allocator.Allocator) *Ydb.Value { pair.Key = values[i].K.toYDB(a) pair.Payload = values[i].V.toYDB(a) - vvv.Pairs = append(vvv.Pairs, pair) + vvv.Pairs = append(vvv.GetPairs(), pair) } return vvv @@ -586,12 +610,12 @@ func DictValue(values ...DictValueField) *dictValue { sort.Slice(values, func(i, j int) bool { return values[i].K.Yql() < values[j].K.Yql() }) - var t Type + var t types.Type switch { case len(values) > 0: - t = Dict(values[0].K.Type(), values[0].V.Type()) + t = types.NewDict(values[0].K.Type(), values[0].V.Type()) default: - t = EmptyDict() + t = types.NewEmptyDict() } return &dictValue{ @@ -619,7 +643,10 @@ func (v *doubleValue) castTo(dst interface{}) error { return nil default: - return xerrors.WithStackTrace(fmt.Errorf("cannot cast '%+v' (type '%s') to '%T' destination", v, v.Type().Yql(), vv)) + return xerrors.WithStackTrace(fmt.Errorf( + "%w '%+v' (type '%s') to '%T' destination", + ErrCannotCast, v, v.Type().Yql(), vv, + )) } } @@ -627,8 +654,8 @@ func (v *doubleValue) Yql() string { return fmt.Sprintf("%s(\"%v\")", v.Type().Yql(), v.value) } -func (*doubleValue) Type() Type { - return TypeDouble +func (*doubleValue) Type() types.Type { + return types.Double } func (v *doubleValue) toYDB(a *allocator.Allocator) *Ydb.Value { @@ -660,7 +687,10 @@ func (v dyNumberValue) castTo(dst interface{}) error { return nil default: - return xerrors.WithStackTrace(fmt.Errorf("cannot cast '%+v' (type '%s') to '%T' destination", v, v.Type().Yql(), vv)) + return xerrors.WithStackTrace(fmt.Errorf( + "%w '%+v' (type '%s') to '%T' destination", + ErrCannotCast, v, v.Type().Yql(), vv, + )) } } @@ -668,8 +698,8 @@ func (v dyNumberValue) Yql() string { return fmt.Sprintf("%s(%q)", v.Type().Yql(), string(v)) } -func (dyNumberValue) Type() Type { - return TypeDyNumber +func (dyNumberValue) Type() types.Type { + return types.DyNumber } func (v dyNumberValue) toYDB(a *allocator.Allocator) *Ydb.Value { @@ -709,7 +739,10 @@ func (v *floatValue) castTo(dst interface{}) error { return nil default: - return xerrors.WithStackTrace(fmt.Errorf("cannot cast '%+v' (type '%s') to '%T' destination", v, v.Type().Yql(), vv)) + return xerrors.WithStackTrace(fmt.Errorf( + "%w '%+v' (type '%s') to '%T' destination", + ErrCannotCast, v, v.Type().Yql(), vv, + )) } } @@ -717,8 +750,8 @@ func (v *floatValue) Yql() string { return fmt.Sprintf("%s(\"%v\")", v.Type().Yql(), v.value) } -func (*floatValue) Type() Type { - return TypeFloat +func (*floatValue) Type() types.Type { + return types.Float } func (v *floatValue) toYDB(a *allocator.Allocator) *Ydb.Value { @@ -774,7 +807,10 @@ func (v int8Value) castTo(dst interface{}) error { return nil default: - return xerrors.WithStackTrace(fmt.Errorf("cannot cast '%+v' (type '%s') to '%T' destination", v, v.Type().Yql(), vv)) + return xerrors.WithStackTrace(fmt.Errorf( + "%w '%+v' (type '%s') to '%T' destination", + ErrCannotCast, v, v.Type().Yql(), vv, + )) } } @@ -782,8 +818,8 @@ func (v int8Value) Yql() string { return strconv.FormatUint(uint64(v), 10) + "t" } -func (int8Value) Type() Type { - return TypeInt8 +func (int8Value) Type() types.Type { + return types.Int8 } func (v int8Value) toYDB(a *allocator.Allocator) *Ydb.Value { @@ -833,7 +869,10 @@ func (v int16Value) castTo(dst interface{}) error { return nil default: - return xerrors.WithStackTrace(fmt.Errorf("cannot cast '%+v' (type '%s') to '%T' destination", v, v.Type().Yql(), vv)) + return xerrors.WithStackTrace(fmt.Errorf( + "%w '%+v' (type '%s') to '%T' destination", + ErrCannotCast, v, v.Type().Yql(), vv, + )) } } @@ -841,8 +880,8 @@ func (v int16Value) Yql() string { return strconv.FormatUint(uint64(v), 10) + "s" } -func (int16Value) Type() Type { - return TypeInt16 +func (int16Value) Type() types.Type { + return types.Int16 } func (v int16Value) toYDB(a *allocator.Allocator) *Ydb.Value { @@ -892,7 +931,10 @@ func (v int32Value) castTo(dst interface{}) error { return nil default: - return xerrors.WithStackTrace(fmt.Errorf("cannot cast '%+v' (type '%s') to '%T' destination", v, v.Type().Yql(), vv)) + return xerrors.WithStackTrace(fmt.Errorf( + "%w '%+v' (type '%s') to '%T' destination", + ErrCannotCast, v, v.Type().Yql(), vv, + )) } } @@ -900,8 +942,8 @@ func (v int32Value) Yql() string { return strconv.FormatInt(int64(v), 10) } -func (int32Value) Type() Type { - return TypeInt32 +func (int32Value) Type() types.Type { + return types.Int32 } func (v int32Value) toYDB(a *allocator.Allocator) *Ydb.Value { @@ -939,7 +981,10 @@ func (v int64Value) castTo(dst interface{}) error { return nil default: - return xerrors.WithStackTrace(fmt.Errorf("cannot cast '%+v' (type '%s') to '%T' destination", v, v.Type().Yql(), vv)) + return xerrors.WithStackTrace(fmt.Errorf( + "%w '%+v' (type '%s') to '%T' destination", + ErrCannotCast, v, v.Type().Yql(), vv, + )) } } @@ -947,8 +992,8 @@ func (v int64Value) Yql() string { return strconv.FormatUint(uint64(v), 10) + "l" } -func (int64Value) Type() Type { - return TypeInt64 +func (int64Value) Type() types.Type { + return types.Int64 } func (v int64Value) toYDB(a *allocator.Allocator) *Ydb.Value { @@ -978,7 +1023,10 @@ func (v intervalValue) castTo(dst interface{}) error { return nil default: - return xerrors.WithStackTrace(fmt.Errorf("cannot cast '%+v' (type '%s') to '%T' destination", v, v.Type().Yql(), vv)) + return xerrors.WithStackTrace(fmt.Errorf( + "%w '%+v' (type '%s') to '%T' destination", + ErrCannotCast, v, v.Type().Yql(), vv, + )) } } @@ -1024,8 +1072,8 @@ func (v intervalValue) Yql() string { return buffer.String() } -func (intervalValue) Type() Type { - return TypeInterval +func (intervalValue) Type() types.Type { + return types.Interval } func (v intervalValue) toYDB(a *allocator.Allocator) *Ydb.Value { @@ -1060,7 +1108,10 @@ func (v jsonValue) castTo(dst interface{}) error { return nil default: - return xerrors.WithStackTrace(fmt.Errorf("cannot cast '%+v' (type '%s') to '%T' destination", v, v.Type().Yql(), vv)) + return xerrors.WithStackTrace(fmt.Errorf( + "%w '%+v' (type '%s') to '%T' destination", + ErrCannotCast, v, v.Type().Yql(), vv, + )) } } @@ -1068,8 +1119,8 @@ func (v jsonValue) Yql() string { return fmt.Sprintf("%s(@@%s@@)", v.Type().Yql(), string(v)) } -func (jsonValue) Type() Type { - return TypeJSON +func (jsonValue) Type() types.Type { + return types.JSON } func (v jsonValue) toYDB(a *allocator.Allocator) *Ydb.Value { @@ -1099,7 +1150,10 @@ func (v jsonDocumentValue) castTo(dst interface{}) error { return nil default: - return xerrors.WithStackTrace(fmt.Errorf("cannot cast '%+v' (type '%s') to '%T' destination", v, v.Type().Yql(), vv)) + return xerrors.WithStackTrace(fmt.Errorf( + "%w '%+v' (type '%s') to '%T' destination", + ErrCannotCast, v, v.Type().Yql(), vv, + )) } } @@ -1107,8 +1161,8 @@ func (v jsonDocumentValue) Yql() string { return fmt.Sprintf("%s(@@%s@@)", v.Type().Yql(), string(v)) } -func (jsonDocumentValue) Type() Type { - return TypeJSONDocument +func (jsonDocumentValue) Type() types.Type { + return types.JSONDocument } func (v jsonDocumentValue) toYDB(a *allocator.Allocator) *Ydb.Value { @@ -1126,7 +1180,7 @@ func JSONDocumentValue(v string) jsonDocumentValue { } type listValue struct { - t Type + t types.Type items []Value } @@ -1135,7 +1189,10 @@ func (v *listValue) ListItems() []Value { } func (v *listValue) castTo(dst interface{}) error { - return xerrors.WithStackTrace(fmt.Errorf("cannot cast '%+v' (type '%s') to '%T' destination", v, v.Type().Yql(), dst)) + return xerrors.WithStackTrace(fmt.Errorf( + "%w '%+v' (type '%s') to '%T' destination", + ErrCannotCast, v, v.Type().Yql(), dst, + )) } func (v *listValue) Yql() string { @@ -1153,7 +1210,7 @@ func (v *listValue) Yql() string { return buffer.String() } -func (v *listValue) Type() Type { +func (v *listValue) Type() types.Type { return v.t } @@ -1165,19 +1222,19 @@ func (v *listValue) toYDB(a *allocator.Allocator) *Ydb.Value { vvv := a.Value() for _, vv := range items { - vvv.Items = append(vvv.Items, vv.toYDB(a)) + vvv.Items = append(vvv.GetItems(), vv.toYDB(a)) } return vvv } func ListValue(items ...Value) *listValue { - var t Type + var t types.Type switch { case len(items) > 0: - t = List(items[0].Type()) + t = types.NewList(items[0].Type()) default: - t = EmptyList() + t = types.NewEmptyList() } return &listValue{ @@ -1186,13 +1243,49 @@ func ListValue(items ...Value) *listValue { } } +type pgValue struct { + t types.PgType + val string +} + +func (v pgValue) castTo(dst interface{}) error { + return xerrors.WithStackTrace(fmt.Errorf( + "%w PgType to '%T' destination", + ErrCannotCast, dst, + )) +} + +func (v pgValue) Type() types.Type { + return v.t +} + +func (v pgValue) toYDB(_ *allocator.Allocator) *Ydb.Value { + //nolint:godox + // TODO: make allocator + return &Ydb.Value{ + Value: &Ydb.Value_TextValue{ + TextValue: v.val, + }, + } +} + +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(`PgConst("%v", PgType(%v))`, v.val, v.t.OID) +} + type setValue struct { - t Type + t types.Type items []Value } func (v *setValue) castTo(dst interface{}) error { - return xerrors.WithStackTrace(fmt.Errorf("cannot cast '%+v' to '%T' destination", v, dst)) + return xerrors.WithStackTrace(fmt.Errorf( + "%w '%+v' to '%T' destination", + ErrCannotCast, v, dst, + )) } func (v *setValue) Yql() string { @@ -1210,7 +1303,7 @@ func (v *setValue) Yql() string { return buffer.String() } -func (v *setValue) Type() Type { +func (v *setValue) Type() types.Type { return v.t } @@ -1223,23 +1316,32 @@ func (v *setValue) toYDB(a *allocator.Allocator) *Ydb.Value { pair.Key = vv.toYDB(a) pair.Payload = _voidValue - vvv.Pairs = append(vvv.Pairs, pair) + vvv.Pairs = append(vvv.GetPairs(), pair) } return vvv } +func PgValue(oid uint32, val string) pgValue { + return pgValue{ + t: types.PgType{ + OID: oid, + }, + val: val, + } +} + func SetValue(items ...Value) *setValue { sort.Slice(items, func(i, j int) bool { return items[i].Yql() < items[j].Yql() }) - var t Type + var t types.Type switch { case len(items) > 0: - t = Set(items[0].Type()) + t = types.NewSet(items[0].Type()) default: - t = EmptySet() + t = types.EmptySet() } return &setValue{ @@ -1248,26 +1350,55 @@ func SetValue(items ...Value) *setValue { } } -func NullValue(t Type) *optionalValue { +func NullValue(t types.Type) *optionalValue { return &optionalValue{ - innerType: Optional(t), + innerType: types.NewOptional(t), value: nil, } } type optionalValue struct { - innerType Type + innerType types.Type value Value } -var errOptionalNilValue = errors.New("optional contains nil value") - func (v *optionalValue) castTo(dst interface{}) error { + ptr := reflect.ValueOf(dst) + if ptr.Kind() != reflect.Pointer { + return xerrors.WithStackTrace(fmt.Errorf("%w: '%s'", errDestinationTypeIsNotAPointer, ptr.Kind().String())) + } + + inner := reflect.Indirect(ptr) + + if inner.Kind() != reflect.Pointer { + if v.value == nil { + if ptr.CanAddr() { + ptr.SetZero() + } + + return nil + } + + if err := v.value.castTo(ptr.Interface()); err != nil { + return xerrors.WithStackTrace(err) + } + + return nil + } + if v.value == nil { - return xerrors.WithStackTrace(errOptionalNilValue) + inner.SetZero() + + return nil } - return v.value.castTo(dst) + inner.Set(reflect.New(inner.Type().Elem())) + + if err := v.value.castTo(inner.Interface()); err != nil { + return xerrors.WithStackTrace(err) + } + + return nil } func (v *optionalValue) Yql() string { @@ -1278,7 +1409,7 @@ func (v *optionalValue) Yql() string { return fmt.Sprintf("Just(%s)", v.value.Yql()) } -func (v *optionalValue) Type() Type { +func (v *optionalValue) Type() types.Type { return v.innerType } @@ -1290,7 +1421,7 @@ func (v *optionalValue) toYDB(a *allocator.Allocator) *Ydb.Value { vv.Value = vvv } else { if v.value != nil { - vv.Value = v.value.toYDB(a).Value + vv = v.value.toYDB(a) } else { vv.Value = a.NullFlag() } @@ -1301,7 +1432,7 @@ func (v *optionalValue) toYDB(a *allocator.Allocator) *Ydb.Value { func OptionalValue(v Value) *optionalValue { return &optionalValue{ - innerType: Optional(v.Type()), + innerType: types.NewOptional(v.Type()), value: v, } } @@ -1312,7 +1443,7 @@ type ( V Value } structValue struct { - t Type + t types.Type fields []StructValueField } ) @@ -1327,7 +1458,10 @@ func (v *structValue) StructFields() map[string]Value { } func (v *structValue) castTo(dst interface{}) error { - return xerrors.WithStackTrace(fmt.Errorf("cannot cast '%+v' to '%T' destination", v, dst)) + return xerrors.WithStackTrace(fmt.Errorf( + "%w '%+v' to '%T' destination", + ErrCannotCast, v, dst, + )) } func (v *structValue) Yql() string { @@ -1346,7 +1480,7 @@ func (v *structValue) Yql() string { return buffer.String() } -func (v *structValue) Type() Type { +func (v *structValue) Type() types.Type { return v.t } @@ -1354,7 +1488,7 @@ func (v *structValue) toYDB(a *allocator.Allocator) *Ydb.Value { vvv := a.Value() for i := range v.fields { - vvv.Items = append(vvv.Items, v.fields[i].V.toYDB(a)) + vvv.Items = append(vvv.GetItems(), v.fields[i].V.toYDB(a)) } return vvv @@ -1364,13 +1498,16 @@ func StructValue(fields ...StructValueField) *structValue { sort.Slice(fields, func(i, j int) bool { return fields[i].Name < fields[j].Name }) - structFields := make([]StructField, 0, len(fields)) + structFields := make([]types.StructField, 0, len(fields)) for i := range fields { - structFields = append(structFields, StructField{fields[i].Name, fields[i].V.Type()}) + structFields = append(structFields, types.StructField{ + Name: fields[i].Name, + T: fields[i].V.Type(), + }) } return &structValue{ - t: Struct(structFields...), + t: types.NewStruct(structFields...), fields: fields, } } @@ -1388,7 +1525,10 @@ func (v timestampValue) castTo(dst interface{}) error { return nil default: - return xerrors.WithStackTrace(fmt.Errorf("cannot cast '%+v' (type '%s') to '%T' destination", v, v.Type().Yql(), vv)) + return xerrors.WithStackTrace(fmt.Errorf( + "%w '%+v' (type '%s') to '%T' destination", + ErrCannotCast, v, v.Type().Yql(), vv, + )) } } @@ -1396,8 +1536,8 @@ func (v timestampValue) Yql() string { return fmt.Sprintf("%s(%q)", v.Type().Yql(), TimestampToTime(uint64(v)).UTC().Format(LayoutTimestamp)) } -func (timestampValue) Type() Type { - return TypeTimestamp +func (timestampValue) Type() types.Type { + return types.Timestamp } func (v timestampValue) toYDB(a *allocator.Allocator) *Ydb.Value { @@ -1420,7 +1560,7 @@ func TimestampValueFromTime(t time.Time) timestampValue { } type tupleValue struct { - t Type + t types.Type items []Value } @@ -1433,7 +1573,10 @@ func (v *tupleValue) castTo(dst interface{}) error { return v.items[0].castTo(dst) } - return xerrors.WithStackTrace(fmt.Errorf("cannot cast '%+v' to '%T' destination", v, dst)) + return xerrors.WithStackTrace(fmt.Errorf( + "%w '%+v' to '%T' destination", + ErrCannotCast, v, dst, + )) } func (v *tupleValue) Yql() string { @@ -1451,7 +1594,7 @@ func (v *tupleValue) Yql() string { return buffer.String() } -func (v *tupleValue) Type() Type { +func (v *tupleValue) Type() types.Type { return v.t } @@ -1463,20 +1606,20 @@ func (v *tupleValue) toYDB(a *allocator.Allocator) *Ydb.Value { vvv := a.Value() for _, vv := range items { - vvv.Items = append(vvv.Items, vv.toYDB(a)) + vvv.Items = append(vvv.GetItems(), vv.toYDB(a)) } return vvv } func TupleValue(values ...Value) *tupleValue { - tupleItems := make([]Type, 0, len(values)) + tupleItems := make([]types.Type, 0, len(values)) for _, v := range values { tupleItems = append(tupleItems, v.Type()) } return &tupleValue{ - t: Tuple(tupleItems...), + t: types.NewTuple(tupleItems...), items: values, } } @@ -1494,7 +1637,10 @@ func (v tzDateValue) castTo(dst interface{}) error { return nil default: - return xerrors.WithStackTrace(fmt.Errorf("cannot cast '%+v' (type '%s') to '%T' destination", v, v.Type().Yql(), vv)) + return xerrors.WithStackTrace(fmt.Errorf( + "%w '%+v' (type '%s') to '%T' destination", + ErrCannotCast, v, v.Type().Yql(), vv, + )) } } @@ -1502,8 +1648,8 @@ func (v tzDateValue) Yql() string { return fmt.Sprintf("%s(%q)", v.Type().Yql(), string(v)) } -func (tzDateValue) Type() Type { - return TypeTzDate +func (tzDateValue) Type() types.Type { + return types.TzDate } func (v tzDateValue) toYDB(a *allocator.Allocator) *Ydb.Value { @@ -1537,7 +1683,10 @@ func (v tzDatetimeValue) castTo(dst interface{}) error { return nil default: - return xerrors.WithStackTrace(fmt.Errorf("cannot cast '%+v' (type '%s') to '%T' destination", v, v.Type().Yql(), vv)) + return xerrors.WithStackTrace(fmt.Errorf( + "%w '%+v' (type '%s') to '%T' destination", + ErrCannotCast, v, v.Type().Yql(), vv, + )) } } @@ -1545,8 +1694,8 @@ func (v tzDatetimeValue) Yql() string { return fmt.Sprintf("%s(%q)", v.Type().Yql(), string(v)) } -func (tzDatetimeValue) Type() Type { - return TypeTzDatetime +func (tzDatetimeValue) Type() types.Type { + return types.TzDatetime } func (v tzDatetimeValue) toYDB(a *allocator.Allocator) *Ydb.Value { @@ -1580,7 +1729,10 @@ func (v tzTimestampValue) castTo(dst interface{}) error { return nil default: - return xerrors.WithStackTrace(fmt.Errorf("cannot cast '%+v' (type '%s') to '%T' destination", v, v.Type().Yql(), vv)) + return xerrors.WithStackTrace(fmt.Errorf( + "%w '%+v' (type '%s') to '%T' destination", + ErrCannotCast, v, v.Type().Yql(), vv, + )) } } @@ -1588,8 +1740,8 @@ func (v tzTimestampValue) Yql() string { return fmt.Sprintf("%s(%q)", v.Type().Yql(), string(v)) } -func (tzTimestampValue) Type() Type { - return TypeTzTimestamp +func (tzTimestampValue) Type() types.Type { + return types.TzTimestamp } func (v tzTimestampValue) toYDB(a *allocator.Allocator) *Ydb.Value { @@ -1659,7 +1811,10 @@ func (v uint8Value) castTo(dst interface{}) error { return nil default: - return xerrors.WithStackTrace(fmt.Errorf("cannot cast '%+v' (type '%s') to '%T' destination", v, v.Type().Yql(), vv)) + return xerrors.WithStackTrace(fmt.Errorf( + "%w '%+v' (type '%s') to '%T' destination", + ErrCannotCast, v, v.Type().Yql(), vv, + )) } } @@ -1667,8 +1822,8 @@ func (v uint8Value) Yql() string { return strconv.FormatUint(uint64(v), 10) + "ut" } -func (uint8Value) Type() Type { - return TypeUint8 +func (uint8Value) Type() types.Type { + return types.Uint8 } func (v uint8Value) toYDB(a *allocator.Allocator) *Ydb.Value { @@ -1726,7 +1881,10 @@ func (v uint16Value) castTo(dst interface{}) error { return nil default: - return xerrors.WithStackTrace(fmt.Errorf("cannot cast '%+v' (type '%s') to '%T' destination", v, v.Type().Yql(), vv)) + return xerrors.WithStackTrace(fmt.Errorf( + "%w '%+v' (type '%s') to '%T' destination", + ErrCannotCast, v, v.Type().Yql(), vv, + )) } } @@ -1734,8 +1892,8 @@ func (v uint16Value) Yql() string { return strconv.FormatUint(uint64(v), 10) + "us" } -func (uint16Value) Type() Type { - return TypeUint16 +func (uint16Value) Type() types.Type { + return types.Uint16 } func (v uint16Value) toYDB(a *allocator.Allocator) *Ydb.Value { @@ -1781,7 +1939,10 @@ func (v uint32Value) castTo(dst interface{}) error { return nil default: - return xerrors.WithStackTrace(fmt.Errorf("cannot cast '%+v' (type '%s') to '%T' destination", v, v.Type().Yql(), vv)) + return xerrors.WithStackTrace(fmt.Errorf( + "%w '%+v' (type '%s') to '%T' destination", + ErrCannotCast, v, v.Type().Yql(), vv, + )) } } @@ -1789,8 +1950,8 @@ func (v uint32Value) Yql() string { return strconv.FormatUint(uint64(v), 10) + "u" } -func (uint32Value) Type() Type { - return TypeUint32 +func (uint32Value) Type() types.Type { + return types.Uint32 } func (v uint32Value) toYDB(a *allocator.Allocator) *Ydb.Value { @@ -1824,7 +1985,10 @@ func (v uint64Value) castTo(dst interface{}) error { return nil default: - return xerrors.WithStackTrace(fmt.Errorf("cannot cast '%+v' (type '%s') to '%T' destination", v, v.Type().Yql(), vv)) + return xerrors.WithStackTrace(fmt.Errorf( + "%w '%+v' (type '%s') to '%T' destination", + ErrCannotCast, v, v.Type().Yql(), vv, + )) } } @@ -1832,8 +1996,8 @@ func (v uint64Value) Yql() string { return strconv.FormatUint(uint64(v), 10) + "ul" } -func (uint64Value) Type() Type { - return TypeUint64 +func (uint64Value) Type() types.Type { + return types.Uint64 } func (v uint64Value) toYDB(a *allocator.Allocator) *Ydb.Value { @@ -1863,7 +2027,10 @@ func (v textValue) castTo(dst interface{}) error { return nil default: - return xerrors.WithStackTrace(fmt.Errorf("cannot cast '%+v' (type '%s') to '%T' destination", v, v.Type().Yql(), vv)) + return xerrors.WithStackTrace(fmt.Errorf( + "%w '%+v' (type '%s') to '%T' destination", + ErrCannotCast, v, v.Type().Yql(), vv, + )) } } @@ -1871,8 +2038,8 @@ func (v textValue) Yql() string { return fmt.Sprintf("%qu", string(v)) } -func (textValue) Type() Type { - return TypeText +func (textValue) Type() types.Type { + return types.Text } func (v textValue) toYDB(a *allocator.Allocator) *Ydb.Value { @@ -1908,7 +2075,10 @@ func (v *uuidValue) castTo(dst interface{}) error { return nil default: - return xerrors.WithStackTrace(fmt.Errorf("cannot cast '%+v' (type '%s') to '%T' destination", v, v.Type().Yql(), vv)) + return xerrors.WithStackTrace(fmt.Errorf( + "%w '%+v' (type '%s') to '%T' destination", + ErrCannotCast, v, v.Type().Yql(), vv, + )) } } @@ -1925,8 +2095,8 @@ func (v *uuidValue) Yql() string { return buffer.String() } -func (*uuidValue) Type() Type { - return TypeUUID +func (*uuidValue) Type() types.Type { + return types.UUID } func (v *uuidValue) toYDB(a *allocator.Allocator) *Ydb.Value { @@ -1949,15 +2119,15 @@ func UUIDValue(v [16]byte) *uuidValue { } type variantValue struct { - innerType Type + innerType types.Type value Value idx uint32 } func (v *variantValue) Variant() (name string, index uint32) { switch t := v.innerType.(type) { - case *variantStructType: - return t.fields[v.idx].Name, v.idx + case *types.VariantStruct: + return t.Field(int(v.idx)).Name, v.idx default: return "", v.idx } @@ -1978,9 +2148,9 @@ func (v *variantValue) Yql() string { buffer.WriteString(v.value.Yql()) buffer.WriteByte(',') switch t := v.innerType.(type) { - case *variantStructType: - fmt.Fprintf(buffer, "%q", t.fields[v.idx].Name) - case *variantTupleType: + case *types.VariantStruct: + fmt.Fprintf(buffer, "%q", t.Field(int(v.idx)).Name) + case *types.VariantTuple: fmt.Fprintf(buffer, "\""+strconv.FormatUint(uint64(v.idx), 10)+"\"") } buffer.WriteByte(',') @@ -1990,7 +2160,7 @@ func (v *variantValue) Yql() string { return buffer.String() } -func (v *variantValue) Type() Type { +func (v *variantValue) Type() types.Type { return v.innerType } @@ -2006,9 +2176,9 @@ func (v *variantValue) toYDB(a *allocator.Allocator) *Ydb.Value { return vvv } -func VariantValueTuple(v Value, idx uint32, t Type) *variantValue { - if tt, has := t.(*TupleType); has { - t = VariantTuple(tt.items...) +func VariantValueTuple(v Value, idx uint32, t types.Type) *variantValue { + if tt, has := t.(*types.Tuple); has { + t = types.NewVariantTuple(tt.InnerTypes()...) } return &variantValue{ @@ -2018,23 +2188,25 @@ func VariantValueTuple(v Value, idx uint32, t Type) *variantValue { } } -func VariantValueStruct(v Value, name string, t Type) *variantValue { +func VariantValueStruct(v Value, name string, t types.Type) *variantValue { var idx int switch tt := t.(type) { - case *StructType: - sort.Slice(tt.fields, func(i, j int) bool { - return tt.fields[i].Name < tt.fields[j].Name + case *types.Struct: + fields := tt.Fields() + sort.Slice(fields, func(i, j int) bool { + return fields[i].Name < fields[j].Name }) - idx = sort.Search(len(tt.fields), func(i int) bool { - return tt.fields[i].Name >= name + idx = sort.Search(len(fields), func(i int) bool { + return fields[i].Name >= name }) - t = VariantStruct(tt.fields...) - case *variantStructType: - sort.Slice(tt.fields, func(i, j int) bool { - return tt.fields[i].Name < tt.fields[j].Name + t = types.NewVariantStruct(fields...) + case *types.VariantStruct: + fields := tt.Fields() + sort.Slice(fields, func(i, j int) bool { + return fields[i].Name < fields[j].Name }) - idx = sort.Search(len(tt.fields), func(i int) bool { - return tt.fields[i].Name >= name + idx = sort.Search(len(fields), func(i int) bool { + return fields[i].Name >= name }) } @@ -2048,7 +2220,10 @@ func VariantValueStruct(v Value, name string, t Type) *variantValue { type voidValue struct{} func (v voidValue) castTo(dst interface{}) error { - return xerrors.WithStackTrace(fmt.Errorf("cannot cast '%s' to '%T' destination", v.Type().Yql(), dst)) + return xerrors.WithStackTrace(fmt.Errorf( + "%w '%s' to '%T' destination", + ErrCannotCast, v.Type().Yql(), dst, + )) } func (v voidValue) Yql() string { @@ -2056,13 +2231,13 @@ func (v voidValue) Yql() string { } var ( - _voidValueType = voidType{} + _voidValueType = types.Void{} _voidValue = &Ydb.Value{ Value: new(Ydb.Value_NullFlagValue), } ) -func (voidValue) Type() Type { +func (voidValue) Type() types.Type { return _voidValueType } @@ -2087,7 +2262,10 @@ func (v ysonValue) castTo(dst interface{}) error { return nil default: - return xerrors.WithStackTrace(fmt.Errorf("cannot cast '%+v' (type '%s') to '%T' destination", v, v.Type().Yql(), vv)) + return xerrors.WithStackTrace(fmt.Errorf( + "%w '%+v' (type '%s') to '%T' destination", + ErrCannotCast, v, v.Type().Yql(), vv, + )) } } @@ -2095,8 +2273,8 @@ func (v ysonValue) Yql() string { return fmt.Sprintf("%s(%q)", v.Type().Yql(), string(v)) } -func (ysonValue) Type() Type { - return TypeYSON +func (ysonValue) Type() types.Type { + return types.YSON } func (v ysonValue) toYDB(a *allocator.Allocator) *Ydb.Value { @@ -2115,81 +2293,81 @@ func YSONValue(v []byte) ysonValue { return v } -func zeroPrimitiveValue(t PrimitiveType) Value { +func zeroPrimitiveValue(t types.Primitive) Value { switch t { - case TypeBool: + case types.Bool: return BoolValue(false) - case TypeInt8: + case types.Int8: return Int8Value(0) - case TypeUint8: + case types.Uint8: return Uint8Value(0) - case TypeInt16: + case types.Int16: return Int16Value(0) - case TypeUint16: + case types.Uint16: return Uint16Value(0) - case TypeInt32: + case types.Int32: return Int32Value(0) - case TypeUint32: + case types.Uint32: return Uint32Value(0) - case TypeInt64: + case types.Int64: return Int64Value(0) - case TypeUint64: + case types.Uint64: return Uint64Value(0) - case TypeFloat: + case types.Float: return FloatValue(0) - case TypeDouble: + case types.Double: return DoubleValue(0) - case TypeDate: + case types.Date: return DateValue(0) - case TypeDatetime: + case types.Datetime: return DatetimeValue(0) - case TypeTimestamp: + case types.Timestamp: return TimestampValue(0) - case TypeInterval: + case types.Interval: return IntervalValue(0) - case TypeText: + case types.Text: return TextValue("") - case TypeYSON: + case types.YSON: return YSONValue([]byte("")) - case TypeJSON: + case types.JSON: return JSONValue("") - case TypeJSONDocument: + case types.JSONDocument: return JSONDocumentValue("") - case TypeDyNumber: + case types.DyNumber: return DyNumberValue("") - case TypeTzDate: + case types.TzDate: return TzDateValue("") - case TypeTzDatetime: + case types.TzDatetime: return TzDatetimeValue("") - case TypeTzTimestamp: + case types.TzTimestamp: return TzTimestampValue("") - case TypeBytes: + case types.Bytes: return BytesValue([]byte{}) - case TypeUUID: + case types.UUID: return UUIDValue([16]byte{}) default: @@ -2197,53 +2375,55 @@ func zeroPrimitiveValue(t PrimitiveType) Value { } } -func ZeroValue(t Type) Value { +func ZeroValue(t types.Type) Value { switch t := t.(type) { - case PrimitiveType: + case types.Primitive: return zeroPrimitiveValue(t) - case optionalType: - return NullValue(t.innerType) + case types.Optional: + return NullValue(t.InnerType()) - case *voidType: + case *types.Void: return VoidValue() - case *listType, *emptyListType: + case *types.List, *types.EmptyList: return &listValue{ t: t, } - case *setType: + case *types.Set: return &setValue{ t: t, } - case *dictType: + case *types.Dict: return &dictValue{ - t: t.valueType, + t: t.ValueType(), } - case *emptyDictType: + case *types.EmptyDict: return &dictValue{ t: t, } - case *TupleType: + case *types.Tuple: return TupleValue(func() []Value { - values := make([]Value, len(t.items)) - for i, tt := range t.items { + innerTypes := t.InnerTypes() + values := make([]Value, len(innerTypes)) + for i, tt := range innerTypes { values[i] = ZeroValue(tt) } return values }()...) - case *StructType: + case *types.Struct: return StructValue(func() []StructValueField { - fields := make([]StructValueField, len(t.fields)) - for i := range t.fields { - fields[i] = StructValueField{ - Name: t.fields[i].Name, - V: ZeroValue(t.fields[i].T), + fields := t.Fields() + values := make([]StructValueField, len(fields)) + for i := range fields { + values[i] = StructValueField{ + Name: fields[i].Name, + V: ZeroValue(fields[i].T), } } - return fields + return values }()...) case *DecimalType: return DecimalValue([16]byte{}, decimalPrecision, decimalScale) @@ -2266,7 +2446,10 @@ func (v bytesValue) castTo(dst interface{}) error { return nil default: - return xerrors.WithStackTrace(fmt.Errorf("cannot cast '%+v' (type '%s') to '%T' destination", v, v.Type().Yql(), vv)) + return xerrors.WithStackTrace(fmt.Errorf( + "%w '%+v' (type '%s') to '%T' destination", + ErrCannotCast, v, v.Type().Yql(), vv, + )) } } @@ -2274,8 +2457,8 @@ func (v bytesValue) Yql() string { return fmt.Sprintf("%q", string(v)) } -func (bytesValue) Type() Type { - return TypeBytes +func (bytesValue) Type() types.Type { + return types.Bytes } func (v bytesValue) toYDB(a *allocator.Allocator) *Ydb.Value { diff --git a/internal/value/value_test.go b/internal/value/value_test.go index e8b283fb1..f7adfc7e9 100644 --- a/internal/value/value_test.go +++ b/internal/value/value_test.go @@ -1,8 +1,10 @@ package value import ( + "fmt" "math" "math/big" + "reflect" "strconv" "testing" "time" @@ -11,6 +13,9 @@ import ( "google.golang.org/protobuf/proto" "github.com/ydb-platform/ydb-go-sdk/v3/internal/allocator" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/pg" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/types" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/xtest" ) func BenchmarkMemory(b *testing.B) { @@ -68,24 +73,24 @@ func BenchmarkMemory(b *testing.B) { DictValueField{TextValue("air_date"), Uint64Value(3)}, DictValueField{TextValue("remove_date"), Uint64Value(4)}, ), - NullValue(Optional(Optional(Optional(TypeBool)))), - VariantValueTuple(Int32Value(42), 1, Tuple( - TypeBytes, - TypeInt32, + NullValue(types.NewOptional(types.NewOptional(types.NewOptional(types.Bool)))), + VariantValueTuple(Int32Value(42), 1, types.NewTuple( + types.Bytes, + types.Int32, )), - VariantValueStruct(Int32Value(42), "bar", Struct( - StructField{ + VariantValueStruct(Int32Value(42), "bar", types.NewStruct( + types.StructField{ Name: "foo", - T: TypeBytes, + T: types.Bytes, }, - StructField{ + types.StructField{ Name: "bar", - T: TypeInt32, + T: types.Int32, }, )), - ZeroValue(TypeText), - ZeroValue(Struct()), - ZeroValue(Tuple()), + ZeroValue(types.Text), + ZeroValue(types.NewStruct()), + ZeroValue(types.NewTuple()), ) for i := 0; i < b.N; i++ { a := allocator.New() @@ -153,31 +158,32 @@ func TestToYDBFromYDB(t *testing.T) { DictValueField{TextValue("air_date"), Uint64Value(3)}, DictValueField{TextValue("remove_date"), Uint64Value(4)}, ), - NullValue(TypeBool), - NullValue(Optional(TypeBool)), - VariantValueTuple(Int32Value(42), 1, Tuple( - TypeBytes, - TypeInt32, + NullValue(types.Bool), + NullValue(types.NewOptional(types.Bool)), + VariantValueTuple(Int32Value(42), 1, types.NewTuple( + types.Bytes, + types.Int32, )), - VariantValueStruct(Int32Value(42), "bar", Struct( - StructField{ + VariantValueStruct(Int32Value(42), "bar", types.NewStruct( + types.StructField{ Name: "foo", - T: TypeBytes, + T: types.Bytes, }, - StructField{ + types.StructField{ Name: "bar", - T: TypeInt32, + T: types.Int32, }, )), - ZeroValue(TypeText), - ZeroValue(Struct()), - ZeroValue(Tuple()), + ZeroValue(types.Text), + ZeroValue(types.NewStruct()), + ZeroValue(types.NewTuple()), + PgValue(pg.OIDInt4, "123"), } { t.Run(strconv.Itoa(i)+"."+v.Yql(), func(t *testing.T) { a := allocator.New() defer a.Free() value := ToYDB(v, a) - dualConversedValue, err := fromYDB(value.Type, value.Value) + dualConversedValue, err := fromYDB(value.GetType(), value.GetValue()) require.NoError(t, err) if !proto.Equal(value, ToYDB(dualConversedValue, a)) { t.Errorf("dual conversion failed:\n\n - got: %v\n\n - want: %v", ToYDB(dualConversedValue, a), value) @@ -329,11 +335,11 @@ func TestValueYql(t *testing.T) { literal: `TzTimestamp("1997-12-14T03:09:42.123456,Europe/Berlin")`, }, { - value: NullValue(TypeInt32), + value: NullValue(types.Int32), literal: `Nothing(Optional)`, }, { - value: NullValue(Optional(TypeBool)), + value: NullValue(types.NewOptional(types.Bool)), literal: `Nothing(Optional>)`, }, { @@ -390,30 +396,36 @@ func TestValueYql(t *testing.T) { literal: `(0,1l,Float("2"),"3"u)`, }, { - value: VariantValueTuple(Int32Value(42), 1, Tuple( - TypeBytes, - TypeInt32, + value: VariantValueTuple(Int32Value(42), 1, types.NewTuple( + types.Bytes, + types.Int32, )), literal: `Variant(42,"1",Variant)`, }, { - value: VariantValueTuple(TextValue("foo"), 1, Tuple( - TypeBytes, - TypeText, + value: VariantValueTuple(TextValue("foo"), 1, types.NewTuple( + types.Bytes, + types.Text, )), literal: `Variant("foo"u,"1",Variant)`, }, { - value: VariantValueTuple(BoolValue(true), 0, Tuple( - TypeBytes, - TypeInt32, + value: VariantValueTuple(BoolValue(true), 0, types.NewTuple( + types.Bytes, + types.Int32, )), literal: `Variant(true,"0",Variant)`, }, { - value: VariantValueStruct(Int32Value(42), "bar", Struct( - StructField{"foo", TypeBytes}, - StructField{"bar", TypeInt32}, + value: VariantValueStruct(Int32Value(42), "bar", types.NewStruct( + types.StructField{ + Name: "foo", + T: types.Bytes, + }, + types.StructField{ + Name: "bar", + T: types.Int32, + }, )), literal: `Variant(42,"bar",Variant<'bar':Int32,'foo':String>)`, }, @@ -440,26 +452,32 @@ func TestValueYql(t *testing.T) { literal: `{"bar"u:Void(),"foo"u:Void()}`, }, { - value: ZeroValue(TypeBool), + value: ZeroValue(types.Bool), literal: `false`, }, { - value: ZeroValue(Optional(TypeBool)), + value: ZeroValue(types.NewOptional(types.Bool)), literal: `Nothing(Optional)`, }, { - value: ZeroValue(Tuple(TypeBool, TypeDouble)), + value: ZeroValue(types.NewTuple(types.Bool, types.Double)), literal: `(false,Double("0"))`, }, { - value: ZeroValue(Struct( - StructField{"foo", TypeBool}, - StructField{"bar", TypeText}, + value: ZeroValue(types.NewStruct( + types.StructField{ + Name: "foo", + T: types.Bool, + }, + types.StructField{ + Name: "bar", + T: types.Text, + }, )), literal: "<|`bar`:\"\"u,`foo`:false|>", }, { - value: ZeroValue(TypeUUID), + value: ZeroValue(types.UUID), literal: `Uuid("00000000-0000-0000-0000-000000000000")`, }, { @@ -482,9 +500,667 @@ func TestValueYql(t *testing.T) { value: YSONValue([]byte("[3;%false]")), literal: `Yson("[3;%false]")`, }, + { + value: PgValue(pg.OIDUnknown, "123"), + literal: `PgConst("123", PgType(705))`, + }, } { t.Run(strconv.Itoa(i)+"."+tt.literal, func(t *testing.T) { require.Equal(t, tt.literal, tt.value.Yql()) }) } } + +func TestOptionalValueCastTo(t *testing.T) { + for _, tt := range []struct { + name string + v *optionalValue + dst **string + exp interface{} + err error + }{ + { + name: xtest.CurrentFileLine(), + v: OptionalValue(TextValue("test")), + dst: func(v *string) **string { return &v }(func(s string) *string { return &s }("")), + exp: func(v *string) **string { return &v }(func(s string) *string { return &s }("test")), + err: nil, + }, + { + name: xtest.CurrentFileLine(), + v: OptionalValue(TextValue("test")), + dst: func(v *string) **string { return &v }(func() *string { return nil }()), + exp: func(v *string) **string { return &v }(func(s string) *string { return &s }("test")), + err: nil, + }, + { + name: xtest.CurrentFileLine(), + v: NullValue(types.Text), + dst: func(v *string) **string { return &v }(func(s string) *string { return &s }("")), + exp: func(v *string) **string { return &v }(func() *string { return nil }()), + err: nil, + }, + { + name: xtest.CurrentFileLine(), + v: NullValue(types.Text), + dst: func(v *string) **string { return &v }(func() *string { return nil }()), + exp: func(v *string) **string { return &v }(func() *string { return nil }()), + err: nil, + }, + } { + t.Run(tt.name, func(t *testing.T) { + err := tt.v.castTo(tt.dst) + if tt.err != nil { + require.ErrorIs(t, err, tt.err) + } else { + require.NoError(t, err) + require.Equal(t, tt.exp, tt.dst) + } + }) + } +} + +func TestNullable(t *testing.T) { + for _, test := range []struct { + name string + t types.Type + v interface{} + exp Value + }{ + { + name: "bool", + t: types.Bool, + v: func(v bool) *bool { return &v }(true), + exp: OptionalValue(BoolValue(true)), + }, + { + name: "nil bool", + t: types.Bool, + v: func() *bool { return nil }(), + exp: NullValue(types.Bool), + }, + { + name: "int8", + t: types.Int8, + v: func(v int8) *int8 { return &v }(123), + exp: OptionalValue(Int8Value(123)), + }, + { + name: "nil int8", + t: types.Int8, + v: func() *int8 { return nil }(), + exp: NullValue(types.Int8), + }, + { + name: "uint8", + t: types.Uint8, + v: func(v uint8) *uint8 { return &v }(123), + exp: OptionalValue(Uint8Value(123)), + }, + { + name: "nil uint8", + t: types.Uint8, + v: func() *uint8 { return nil }(), + exp: NullValue(types.Uint8), + }, + { + name: "int16", + t: types.Int16, + v: func(v int16) *int16 { return &v }(123), + exp: OptionalValue(Int16Value(123)), + }, + { + name: "nil int16", + t: types.Int16, + v: func() *int16 { return nil }(), + exp: NullValue(types.Int16), + }, + { + name: "uint16", + t: types.Uint16, + v: func(v uint16) *uint16 { return &v }(123), + exp: OptionalValue(Uint16Value(123)), + }, + { + name: "nil uint16", + t: types.Uint16, + v: func() *uint16 { return nil }(), + exp: NullValue(types.Uint16), + }, + { + name: "int32", + t: types.Int32, + v: func(v int32) *int32 { return &v }(123), + exp: OptionalValue(Int32Value(123)), + }, + { + name: "nil int32", + t: types.Int32, + v: func() *int32 { return nil }(), + exp: NullValue(types.Int32), + }, + { + name: "uint32", + t: types.Uint32, + v: func(v uint32) *uint32 { return &v }(123), + exp: OptionalValue(Uint32Value(123)), + }, + { + name: "nil uint32", + t: types.Uint32, + v: func() *uint32 { return nil }(), + exp: NullValue(types.Uint32), + }, + { + name: "int64", + t: types.Int64, + v: func(v int64) *int64 { return &v }(123), + exp: OptionalValue(Int64Value(123)), + }, + { + name: "nil int64", + t: types.Int64, + v: func() *int64 { return nil }(), + exp: NullValue(types.Int64), + }, + { + name: "uint64", + t: types.Uint64, + v: func(v uint64) *uint64 { return &v }(123), + exp: OptionalValue(Uint64Value(123)), + }, + { + name: "nil uint64", + t: types.Uint64, + v: func() *uint64 { return nil }(), + exp: NullValue(types.Uint64), + }, + { + name: "float", + t: types.Float, + v: func(v float32) *float32 { return &v }(123), + exp: OptionalValue(FloatValue(123)), + }, + { + name: "nil float", + t: types.Float, + v: func() *float32 { return nil }(), + exp: NullValue(types.Float), + }, + { + name: "double", + t: types.Double, + v: func(v float64) *float64 { return &v }(123), + exp: OptionalValue(DoubleValue(123)), + }, + { + name: "nil float", + t: types.Double, + v: func() *float64 { return nil }(), + exp: NullValue(types.Double), + }, + { + name: "date from int32", + t: types.Date, + v: func(v uint32) *uint32 { return &v }(123), + exp: OptionalValue(DateValue(123)), + }, + { + name: "date from time.Time", + t: types.Date, + v: func(v time.Time) *time.Time { return &v }(time.Unix(123, 456)), + exp: OptionalValue(DateValueFromTime(time.Unix(123, 456))), + }, + { + name: "nil date", + t: types.Date, + v: func() *uint32 { return nil }(), + exp: NullValue(types.Date), + }, + { + name: "datetime from int32", + t: types.Datetime, + v: func(v uint32) *uint32 { return &v }(123), + exp: OptionalValue(DatetimeValue(123)), + }, + { + name: "datetime from time.Time", + t: types.Datetime, + v: func(v time.Time) *time.Time { return &v }(time.Unix(123, 456)), + exp: OptionalValue(DatetimeValueFromTime(time.Unix(123, 456))), + }, + { + name: "nil datetime", + t: types.Datetime, + v: func() *uint32 { return nil }(), + exp: NullValue(types.Datetime), + }, + { + name: "timestamp from int32", + t: types.Timestamp, + v: func(v uint64) *uint64 { return &v }(123), + exp: OptionalValue(TimestampValue(123)), + }, + { + name: "timestamp from time.Time", + t: types.Timestamp, + v: func(v time.Time) *time.Time { return &v }(time.Unix(123, 456)), + exp: OptionalValue(TimestampValueFromTime(time.Unix(123, 456))), + }, + { + name: "nil timestamp", + t: types.Timestamp, + v: func() *uint64 { return nil }(), + exp: NullValue(types.Timestamp), + }, + { + name: "tzDate from int32", + t: types.TzDate, + v: func(v string) *string { return &v }(""), + exp: OptionalValue(TzDateValue("")), + }, + { + name: "tzDate from time.Time", + t: types.TzDate, + v: func(v time.Time) *time.Time { return &v }(time.Unix(123, 456)), + exp: OptionalValue(TzDateValueFromTime(time.Unix(123, 456))), + }, + { + name: "nil tzDate", + t: types.TzDate, + v: func() *string { return nil }(), + exp: NullValue(types.TzDate), + }, + { + name: "interval from int64", + t: types.Interval, + v: func(v int64) *int64 { return &v }(123), + exp: OptionalValue(IntervalValue(123)), + }, + { + name: "interval from time.Time", + t: types.Interval, + v: func(v time.Duration) *time.Duration { return &v }(time.Second), + exp: OptionalValue(IntervalValueFromDuration(time.Second)), + }, + { + name: "nil interval", + t: types.Interval, + v: func() *int64 { return nil }(), + exp: NullValue(types.Interval), + }, + { + name: "tzDatetime from int32", + t: types.TzDatetime, + v: func(v string) *string { return &v }(""), + exp: OptionalValue(TzDatetimeValue("")), + }, + { + name: "tzTzDatetime from time.Time", + t: types.TzDatetime, + v: func(v time.Time) *time.Time { return &v }(time.Unix(123, 456)), + exp: OptionalValue(TzDatetimeValueFromTime(time.Unix(123, 456))), + }, + { + name: "nil tzTzDatetime", + t: types.TzDatetime, + v: func() *string { return nil }(), + exp: NullValue(types.TzDatetime), + }, + { + name: "tzTimestamp from int32", + t: types.TzTimestamp, + v: func(v string) *string { return &v }(""), + exp: OptionalValue(TzTimestampValue("")), + }, + { + name: "TzTimestamp from time.Time", + t: types.TzTimestamp, + v: func(v time.Time) *time.Time { return &v }(time.Unix(123, 456)), + exp: OptionalValue(TzTimestampValueFromTime(time.Unix(123, 456))), + }, + { + name: "nil TzTimestamp", + t: types.TzTimestamp, + v: func() *string { return nil }(), + exp: NullValue(types.TzTimestamp), + }, + { + name: "string", + t: types.Bytes, + v: func(v string) *string { return &v }("test"), + exp: OptionalValue(BytesValue([]byte("test"))), + }, + { + name: "string", + t: types.Bytes, + v: func(v []byte) *[]byte { return &v }([]byte("test")), + exp: OptionalValue(BytesValue([]byte("test"))), + }, + { + name: "nil string", + t: types.Bytes, + v: func() *string { return nil }(), + exp: NullValue(types.Bytes), + }, + { + name: "utf8", + t: types.Text, + v: func(v string) *string { return &v }("test"), + exp: OptionalValue(TextValue("test")), + }, + { + name: "nil utf8", + t: types.Text, + v: func() *string { return nil }(), + exp: NullValue(types.Text), + }, + { + name: "yson", + t: types.YSON, + v: func(v string) *string { return &v }("test"), + exp: OptionalValue(YSONValue([]byte("test"))), + }, + { + name: "yson", + t: types.YSON, + v: func(v []byte) *[]byte { return &v }([]byte("test")), + exp: OptionalValue(YSONValue([]byte("test"))), + }, + { + name: "nil yson", + t: types.YSON, + v: func() *string { return nil }(), + exp: NullValue(types.YSON), + }, + { + name: "json", + t: types.JSON, + v: func(v string) *string { return &v }("test"), + exp: OptionalValue(JSONValue("test")), + }, + { + name: "json", + t: types.JSON, + v: func(v []byte) *[]byte { return &v }([]byte("test")), + exp: OptionalValue(JSONValue("test")), + }, + { + name: "nil json", + t: types.JSON, + v: func() *string { return nil }(), + exp: NullValue(types.JSON), + }, + { + name: "uuid", + t: types.UUID, + v: func(v [16]byte) *[16]byte { return &v }([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}), + exp: OptionalValue(UUIDValue([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16})), + }, + { + name: "jsonDocument", + t: types.JSONDocument, + v: func(v string) *string { return &v }("test"), + exp: OptionalValue(JSONDocumentValue("test")), + }, + { + name: "jsonDocument", + t: types.JSONDocument, + v: func(v []byte) *[]byte { return &v }([]byte("test")), + exp: OptionalValue(JSONDocumentValue("test")), + }, + { + name: "nil jsonDocument", + t: types.JSONDocument, + v: func() *string { return nil }(), + exp: NullValue(types.JSONDocument), + }, + { + name: "dyNumber", + t: types.DyNumber, + v: func(v string) *string { return &v }("test"), + exp: OptionalValue(DyNumberValue("test")), + }, + { + name: "nil dyNumber", + t: types.DyNumber, + v: func() *string { return nil }(), + exp: NullValue(types.DyNumber), + }, + } { + t.Run(test.name, func(t *testing.T) { + a := allocator.New() + defer a.Free() + v := Nullable(test.t, test.v) + if !proto.Equal(ToYDB(v, a), ToYDB(test.exp, a)) { + t.Fatalf("unexpected value: %v, exp: %v", v, test.exp) + } + }) + } +} + +func TestCastNumbers(t *testing.T) { + numberValues := []struct { + value Value + signed bool + len int + }{ + { + value: Uint64Value(1), + signed: false, + len: 8, + }, + { + value: Int64Value(2), + signed: true, + len: 8, + }, + { + value: Uint32Value(3), + signed: false, + len: 4, + }, + { + value: Int32Value(4), + signed: true, + len: 4, + }, + { + value: Uint16Value(5), + signed: false, + len: 2, + }, + { + value: Int16Value(6), + signed: true, + len: 2, + }, + { + value: Uint8Value(7), + signed: false, + len: 1, + }, + { + value: Int8Value(8), + signed: true, + len: 1, + }, + } + numberDestinations := []struct { + destination interface{} + signed bool + len int + }{ + { + destination: func(v uint64) *uint64 { return &v }(1), + signed: false, + len: 8, + }, + { + destination: func(v int64) *int64 { return &v }(2), + signed: true, + len: 8, + }, + { + destination: func(v uint32) *uint32 { return &v }(3), + signed: false, + len: 4, + }, + { + destination: func(v int32) *int32 { return &v }(4), + signed: true, + len: 4, + }, + { + destination: func(v uint16) *uint16 { return &v }(5), + signed: false, + len: 2, + }, + { + destination: func(v int16) *int16 { return &v }(6), + signed: true, + len: 2, + }, + { + destination: func(v uint8) *uint8 { return &v }(7), + signed: false, + len: 1, + }, + { + destination: func(v int8) *int8 { return &v }(8), + signed: true, + len: 1, + }, + { + destination: func(v float32) *float32 { return &v }(7), + signed: true, + len: 4, + }, + { + destination: func(v float64) *float64 { return &v }(8), + signed: true, + len: 8, + }, + } + for _, dst := range numberDestinations { + for _, src := range numberValues { + t.Run(fmt.Sprintf("%s→%s", + src.value.Yql(), reflect.ValueOf(dst.destination).Type().Elem().String(), + ), func(t *testing.T) { + mustErr := false + switch { + case src.len == dst.len && src.signed != dst.signed, + src.len > dst.len, + src.signed && !dst.signed: + mustErr = true + } + err := CastTo(src.value, dst.destination) + if mustErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + t.Run(fmt.Sprintf("Optional(%s)→%s", + src.value.Yql(), reflect.ValueOf(dst.destination).Type().Elem().String(), + ), func(t *testing.T) { + mustErr := false + switch { + case src.len == dst.len && src.signed != dst.signed, + src.len > dst.len, + src.signed && !dst.signed: + mustErr = true + } + err := CastTo(OptionalValue(src.value), dst.destination) + if mustErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } + } +} + +func TestCastOtherTypes(t *testing.T) { + for _, tt := range []struct { + v Value + dst interface{} + result interface{} + error bool + }{ + { + v: BytesValue([]byte("test")), + dst: func(v []byte) *[]byte { return &v }(make([]byte, 0, 10)), + result: func(v []byte) *[]byte { return &v }([]byte("test")), + error: false, + }, + { + v: TextValue("test"), + dst: func(v []byte) *[]byte { return &v }(make([]byte, 0, 10)), + result: func(v []byte) *[]byte { return &v }([]byte("test")), + error: false, + }, + { + v: BytesValue([]byte("test")), + dst: func(v string) *string { return &v }(""), + result: func(v string) *string { return &v }("test"), + error: false, + }, + { + v: DoubleValue(123), + dst: func(v float64) *float64 { return &v }(9), + result: func(v float64) *float64 { return &v }(123), + error: false, + }, + { + v: DoubleValue(123), + dst: func(v float32) *float32 { return &v }(9), + result: func(v float32) *float32 { return &v }(9), + error: true, + }, + { + v: FloatValue(123), + dst: func(v float64) *float64 { return &v }(9), + result: func(v float64) *float64 { return &v }(123), + error: false, + }, + { + v: FloatValue(123), + dst: func(v float32) *float32 { return &v }(9), + result: func(v float32) *float32 { return &v }(123), + error: false, + }, + { + v: Uint64Value(123), + dst: func(v float32) *float32 { return &v }(9), + result: func(v float32) *float32 { return &v }(9), + error: true, + }, + { + v: Uint64Value(123), + dst: func(v float64) *float64 { return &v }(9), + result: func(v float64) *float64 { return &v }(9), + error: true, + }, + { + v: OptionalValue(DoubleValue(123)), + dst: func(v float64) *float64 { return &v }(9), + result: func(v float64) *float64 { return &v }(123), + error: false, + }, + } { + t.Run(fmt.Sprintf("%s→%v", tt.v.Type().Yql(), reflect.ValueOf(tt.dst).Type().Elem()), + func(t *testing.T) { + if err := CastTo(tt.v, tt.dst); (err != nil) != tt.error { + t.Errorf("castTo() error = %v, want %v", err, tt.error) + } else if !reflect.DeepEqual(tt.dst, tt.result) { + t.Errorf("castTo() result = %+v, want %+v", + reflect.ValueOf(tt.dst).Elem(), + reflect.ValueOf(tt.result).Elem(), + ) + } + }, + ) + } +} diff --git a/internal/version/version.go b/internal/version/version.go index 72df04221..a20971e2f 100644 --- a/internal/version/version.go +++ b/internal/version/version.go @@ -2,8 +2,8 @@ package version const ( Major = "3" - Minor = "56" - Patch = "1" + Minor = "61" + Patch = "2" Prefix = "ydb-go-sdk" ) diff --git a/internal/xcontext/context_error.go b/internal/xcontext/context_error.go index ddd11553c..419ec12c9 100644 --- a/internal/xcontext/context_error.go +++ b/internal/xcontext/context_error.go @@ -4,7 +4,7 @@ import ( "github.com/ydb-platform/ydb-go-sdk/v3/internal/stack" ) -var _ error = (*ctxErr)(nil) +var _ error = (*ctxError)(nil) const ( atWord = "at" @@ -12,7 +12,7 @@ const ( ) func errAt(err error, skipDepth int) error { - return &ctxErr{ + return &ctxError{ err: err, stackRecord: stack.Record(skipDepth + 1), linkWord: atWord, @@ -20,23 +20,23 @@ func errAt(err error, skipDepth int) error { } func errFrom(err error, from string) error { - return &ctxErr{ + return &ctxError{ err: err, stackRecord: from, linkWord: fromWord, } } -type ctxErr struct { +type ctxError struct { err error stackRecord string linkWord string } -func (e *ctxErr) Error() string { +func (e *ctxError) Error() string { return "'" + e.err.Error() + "' " + e.linkWord + " `" + e.stackRecord + "`" } -func (e *ctxErr) Unwrap() error { +func (e *ctxError) Unwrap() error { return e.err } 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 47b2b1603..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,11 +28,15 @@ func Check(err error) ( false } -func MustDeleteSession(err error) bool { +func IsRetryObjectValid(err error) bool { + if err == nil { + return true + } + var e Error if As(err, &e) { - return e.MustDeleteSession() + return !e.IsRetryObjectValid() } - return false + return true } diff --git a/internal/xerrors/issues.go b/internal/xerrors/issues.go index f981207b4..066bb94eb 100644 --- a/internal/xerrors/issues.go +++ b/internal/xerrors/issues.go @@ -44,9 +44,9 @@ func (ii issues) String() string { b.WriteByte('\'') b.WriteString(strings.TrimSuffix(m.GetMessage(), ".")) b.WriteByte('\'') - if len(m.Issues) > 0 { + if len(m.GetIssues()) > 0 { b.WriteByte(' ') - b.WriteString(issues(m.Issues).String()) + b.WriteString(issues(m.GetIssues()).String()) } b.WriteByte('}') } @@ -57,7 +57,7 @@ func (ii issues) String() string { // NewWithIssues returns error which contains child issues func NewWithIssues(text string, issues ...error) error { - err := &errorWithIssues{ + err := &withIssuesError{ reason: text, } for i := range issues { @@ -69,14 +69,14 @@ func NewWithIssues(text string, issues ...error) error { return err } -type errorWithIssues struct { +type withIssuesError struct { reason string issues []error } -func (e *errorWithIssues) isYdbError() {} +func (e *withIssuesError) isYdbError() {} -func (e *errorWithIssues) Error() string { +func (e *withIssuesError) Error() string { var b bytes.Buffer if len(e.reason) > 0 { b.WriteString(e.reason) @@ -95,7 +95,7 @@ func (e *errorWithIssues) Error() string { return b.String() } -func (e *errorWithIssues) As(target interface{}) bool { +func (e *withIssuesError) As(target interface{}) bool { for _, err := range e.issues { if As(err, target) { return true @@ -105,7 +105,7 @@ func (e *errorWithIssues) As(target interface{}) bool { return false } -func (e *errorWithIssues) Is(target error) bool { +func (e *withIssuesError) Is(target error) bool { for _, err := range e.issues { if Is(err, target) { return true @@ -130,7 +130,7 @@ func (it IssueIterator) Len() int { func (it IssueIterator) Get(i int) (issue Issue, nested IssueIterator) { x := it[i] - if xs := x.Issues; len(xs) > 0 { + if xs := x.GetIssues(); len(xs) > 0 { nested = IssueIterator(xs) } diff --git a/internal/xerrors/join.go b/internal/xerrors/join.go index 1f2e37887..80ec421c1 100644 --- a/internal/xerrors/join.go +++ b/internal/xerrors/join.go @@ -7,16 +7,20 @@ import ( ) func Join(errs ...error) joinError { - return errs + return joinError{ + errs: errs, + } } -type joinError []error +type joinError struct { + errs []error +} -func (errs joinError) Error() string { +func (e joinError) Error() string { b := xstring.Buffer() defer b.Free() b.WriteByte('[') - for i, err := range errs { + for i, err := range e.errs { if i > 0 { _ = b.WriteByte(',') } @@ -27,8 +31,8 @@ func (errs joinError) Error() string { return b.String() } -func (errs joinError) As(target interface{}) bool { - for _, err := range errs { +func (e joinError) As(target interface{}) bool { + for _, err := range e.errs { if As(err, target) { return true } @@ -37,8 +41,8 @@ func (errs joinError) As(target interface{}) bool { return false } -func (errs joinError) Is(target error) bool { - for _, err := range errs { +func (e joinError) Is(target error) bool { + for _, err := range e.errs { if Is(err, target) { return true } @@ -47,6 +51,6 @@ func (errs joinError) Is(target error) bool { return false } -func (errs joinError) Unwrap() []error { - return errs +func (e joinError) Unwrap() []error { + return e.errs } 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 f37bc2eb6..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,20 +66,21 @@ 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() } - for _, o := range opts { - if o != nil { - o(re) + for _, opt := range opts { + if opt != nil { + opt(re) } } diff --git a/internal/xerrors/stacktrace.go b/internal/xerrors/stacktrace.go index d59c092e7..3140547d5 100644 --- a/internal/xerrors/stacktrace.go +++ b/internal/xerrors/stacktrace.go @@ -24,9 +24,9 @@ func WithStackTrace(err error, opts ...withStackTraceOption) error { return nil } options := withStackTraceOptions{} - for _, o := range opts { - if o != nil { - o(&options) + for _, opt := range opts { + if opt != nil { + opt(&options) } } if s, has := grpcStatus.FromError(err); has { 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/xrand/xrand.go b/internal/xrand/xrand.go index 10c974ef0..2f8ed0021 100644 --- a/internal/xrand/xrand.go +++ b/internal/xrand/xrand.go @@ -35,9 +35,9 @@ func New(opts ...option) Rand { r := &r{ r: rand.New(rand.NewSource(time.Now().Unix())), //nolint:gosec } - for _, o := range opts { - if o != nil { - o(r) + for _, opt := range opts { + if opt != nil { + opt(r) } } 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 5dd13743d..c5d670517 100644 --- a/internal/xsql/conn.go +++ b/internal/xsql/conn.go @@ -11,6 +11,7 @@ import ( "sync/atomic" "time" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/params" "github.com/ydb-platform/ydb-go-sdk/v3/internal/scheme/helpers" "github.com/ydb-platform/ydb-go-sdk/v3/internal/stack" "github.com/ydb-platform/ydb-go-sdk/v3/internal/xcontext" @@ -135,9 +136,9 @@ func newConn(ctx context.Context, c *Connector, s table.ClosableSession, opts .. cc.beginTxFuncs = map[QueryMode]beginTxFunc{ DataQueryMode: cc.beginTx, } - for _, o := range opts { - if o != nil { - o(cc) + for _, opt := range opts { + if opt != nil { + opt(cc) } } c.attach(cc) @@ -154,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() { @@ -197,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(), ) ) @@ -207,13 +208,13 @@ func (c *conn) execContext(ctx context.Context, query string, args []driver.Name switch m { case DataQueryMode: - normalizedQuery, params, err := c.normalize(query, args...) + normalizedQuery, parameters, err := c.normalize(query, args...) if err != nil { return nil, xerrors.WithStackTrace(err) } _, res, err := c.session.Execute(ctx, txControl(ctx, c.defaultTxControl), - normalizedQuery, params, c.dataQueryOptions(ctx)..., + normalizedQuery, ¶meters, c.dataQueryOptions(ctx)..., ) if err != nil { return nil, badconn.Map(xerrors.WithStackTrace(err)) @@ -241,15 +242,12 @@ func (c *conn) execContext(ctx context.Context, query string, args []driver.Name return resultNoRows{}, nil case ScriptingQueryMode: - var ( - res result.StreamResult - params *table.QueryParameters - ) - normalizedQuery, params, err := c.normalize(query, args...) + var res result.StreamResult + normalizedQuery, parameters, err := c.normalize(query, args...) if err != nil { return nil, xerrors.WithStackTrace(err) } - res, err = c.connector.parent.Scripting().StreamExecute(ctx, normalizedQuery, params) + res, err = c.connector.parent.Scripting().StreamExecute(ctx, normalizedQuery, ¶meters) if err != nil { return nil, badconn.Map(xerrors.WithStackTrace(err)) } @@ -310,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(), ) ) @@ -320,13 +318,13 @@ func (c *conn) queryContext(ctx context.Context, query string, args []driver.Nam switch m { case DataQueryMode: - normalizedQuery, params, err := c.normalize(query, args...) + normalizedQuery, parameters, err := c.normalize(query, args...) if err != nil { return nil, xerrors.WithStackTrace(err) } _, res, err := c.session.Execute(ctx, txControl(ctx, c.defaultTxControl), - normalizedQuery, params, c.dataQueryOptions(ctx)..., + normalizedQuery, ¶meters, c.dataQueryOptions(ctx)..., ) if err != nil { return nil, badconn.Map(xerrors.WithStackTrace(err)) @@ -340,12 +338,12 @@ func (c *conn) queryContext(ctx context.Context, query string, args []driver.Nam result: res, }, nil case ScanQueryMode: - normalizedQuery, params, err := c.normalize(query, args...) + normalizedQuery, parameters, err := c.normalize(query, args...) if err != nil { return nil, xerrors.WithStackTrace(err) } res, err := c.session.StreamExecuteScanQuery(ctx, - normalizedQuery, params, c.scanQueryOptions(ctx)..., + normalizedQuery, ¶meters, c.scanQueryOptions(ctx)..., ) if err != nil { return nil, badconn.Map(xerrors.WithStackTrace(err)) @@ -375,11 +373,11 @@ func (c *conn) queryContext(ctx context.Context, query string, args []driver.Nam }, }, nil case ScriptingQueryMode: - normalizedQuery, params, err := c.normalize(query, args...) + normalizedQuery, parameters, err := c.normalize(query, args...) if err != nil { return nil, xerrors.WithStackTrace(err) } - res, err := c.connector.parent.Scripting().StreamExecute(ctx, normalizedQuery, params) + res, err := c.connector.parent.Scripting().StreamExecute(ctx, normalizedQuery, ¶meters) if err != nil { return nil, badconn.Map(xerrors.WithStackTrace(err)) } @@ -397,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) }() @@ -416,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) @@ -424,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)) } @@ -443,7 +443,7 @@ func (c *conn) Begin() (driver.Tx, error) { return nil, errDeprecated } -func (c *conn) normalize(q string, args ...driver.NamedValue) (query string, _ *table.QueryParameters, _ error) { +func (c *conn) normalize(q string, args ...driver.NamedValue) (query string, _ params.Parameters, _ error) { return c.connector.Bindings.RewriteQuery(q, func() (ii []interface{}) { for i := range args { ii = append(ii, args[i]) @@ -459,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) }() @@ -467,10 +469,10 @@ func (c *conn) BeginTx(ctx context.Context, txOptions driver.TxOptions) (_ drive if c.currentTx != nil { return nil, xerrors.WithStackTrace( xerrors.Retryable( - &ErrConnAlreadyHaveTx{ + &ConnAlreadyHaveTxError{ currentTx: c.currentTx.ID(), }, - xerrors.WithDeleteSession(), + xerrors.InvalidObject(), ), ) } @@ -483,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"), ), ), @@ -508,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/dsn_test.go b/internal/xsql/dsn_test.go index 557072701..d28c96ac3 100644 --- a/internal/xsql/dsn_test.go +++ b/internal/xsql/dsn_test.go @@ -13,8 +13,10 @@ func TestParse(t *testing.T) { newConnector := func(opts ...ConnectorOption) *Connector { c := &Connector{} for _, opt := range opts { - if err := opt.Apply(c); err != nil { - t.Error(err) + if opt != nil { + if err := opt.Apply(c); err != nil { + t.Error(err) + } } } diff --git a/internal/xsql/errors.go b/internal/xsql/errors.go index 9d1af54c0..8dbcc7ce9 100644 --- a/internal/xsql/errors.go +++ b/internal/xsql/errors.go @@ -10,21 +10,21 @@ 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 ErrConnAlreadyHaveTx struct { +type ConnAlreadyHaveTxError struct { currentTx string } -func (err *ErrConnAlreadyHaveTx) Error() string { +func (err *ConnAlreadyHaveTxError) Error() string { return "conn already have an open currentTx: " + err.currentTx } -func (err *ErrConnAlreadyHaveTx) As(target interface{}) bool { +func (err *ConnAlreadyHaveTxError) As(target interface{}) bool { switch t := target.(type) { - case *ErrConnAlreadyHaveTx: + case *ConnAlreadyHaveTxError: t.currentTx = err.currentTx return true diff --git a/internal/xsql/rows.go b/internal/xsql/rows.go index 01dccaeb3..a9bb1572e 100644 --- a/internal/xsql/rows.go +++ b/internal/xsql/rows.go @@ -8,12 +8,12 @@ import ( "strings" "sync" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/scanner" "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/table/options" "github.com/ydb-platform/ydb-go-sdk/v3/table/result" "github.com/ydb-platform/ydb-go-sdk/v3/table/result/indexed" - "github.com/ydb-platform/ydb-go-sdk/v3/table/types" ) var ( @@ -23,7 +23,7 @@ var ( _ driver.RowsColumnTypeNullable = &rows{} _ driver.Rows = &single{} - _ types.Scanner = &valuer{} + _ scanner.Scanner = &valuer{} ignoreColumnPrefixName = "__discard_column_" ) 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 24eab6e73..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,8 +120,8 @@ 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(""), - tx.txCtx, tx, query, true, + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/xsql.(*tx).QueryContext"), + tx.txCtx, tx, query, ) defer func() { onDone(finalErr) @@ -132,18 +132,18 @@ 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"), ), ), ) } - query, params, err := tx.conn.normalize(query, args...) + query, parameters, err := tx.conn.normalize(query, args...) if err != nil { return nil, xerrors.WithStackTrace(err) } res, err := tx.tx.Execute(ctx, - query, params, tx.conn.dataQueryOptions(ctx)..., + query, ¶meters, tx.conn.dataQueryOptions(ctx)..., ) if err != nil { return nil, badconn.Map(xerrors.WithStackTrace(err)) @@ -162,8 +162,8 @@ 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(""), - tx.txCtx, tx, query, true, + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/xsql.(*tx).ExecContext"), + tx.txCtx, tx, query, ) defer func() { onDone(finalErr) @@ -174,18 +174,18 @@ 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"), ), ), ) } - query, params, err := tx.conn.normalize(query, args...) + query, parameters, err := tx.conn.normalize(query, args...) if err != nil { return nil, xerrors.WithStackTrace(err) } _, err = tx.tx.Execute(ctx, - query, params, tx.conn.dataQueryOptions(ctx)..., + query, ¶meters, tx.conn.dataQueryOptions(ctx)..., ) if err != nil { return nil, badconn.Map(xerrors.WithStackTrace(err)) @@ -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 662fcd078..459aba718 100644 --- a/internal/xsql/tx_fake.go +++ b/internal/xsql/tx_fake.go @@ -5,7 +5,6 @@ import ( "database/sql/driver" "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/xsql/badconn" "github.com/ydb-platform/ydb-go-sdk/v3/table" @@ -20,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() { @@ -59,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() { @@ -77,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() { @@ -98,8 +97,8 @@ func (tx *txFake) QueryContext(ctx context.Context, query string, args []driver. ) { onDone := trace.DatabaseSQLOnTxQuery( tx.conn.trace, &ctx, - stack.FunctionID(""), - tx.ctx, tx, query, xcontext.IsIdempotent(ctx), + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/xsql.(*txFake).QueryContext"), + tx.ctx, tx, query, ) defer func() { onDone(err) @@ -117,8 +116,8 @@ func (tx *txFake) ExecContext(ctx context.Context, query string, args []driver.N ) { onDone := trace.DatabaseSQLOnTxExec( tx.conn.trace, &ctx, - stack.FunctionID(""), - tx.ctx, tx, query, xcontext.IsIdempotent(ctx), + stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/3/internal/xsql.(*txFake).ExecContext"), + tx.ctx, tx, query, ) defer func() { onDone(err) diff --git a/internal/xsql/valuer.go b/internal/xsql/valuer.go index e99b54b0f..6171908b1 100644 --- a/internal/xsql/valuer.go +++ b/internal/xsql/valuer.go @@ -1,12 +1,14 @@ package xsql -import "github.com/ydb-platform/ydb-go-sdk/v3/table/types" +import ( + "github.com/ydb-platform/ydb-go-sdk/v3/internal/scanner" +) type valuer struct { v interface{} } -func (v *valuer) UnmarshalYDB(raw types.RawValue) error { +func (v *valuer) UnmarshalYDB(raw scanner.RawValue) error { v.v = raw.Any() return nil diff --git a/internal/xsync/once.go b/internal/xsync/once.go new file mode 100644 index 000000000..35f5ed0aa --- /dev/null +++ b/internal/xsync/once.go @@ -0,0 +1,61 @@ +package xsync + +import ( + "context" + "sync" + + "github.com/ydb-platform/ydb-go-sdk/v3/internal/closer" +) + +func OnceFunc(f func(ctx context.Context) error) func(ctx context.Context) error { + var once sync.Once + + return func(ctx context.Context) (err error) { + once.Do(func() { + err = f(ctx) + }) + + return err + } +} + +type Once[T closer.Closer] struct { + f func() T + once sync.Once + mutex sync.RWMutex + t T +} + +func OnceValue[T closer.Closer](f func() T) *Once[T] { + return &Once[T]{f: f} +} + +func (v *Once[T]) Close(ctx context.Context) (err error) { + has := true + v.once.Do(func() { + has = false + }) + + if has { + v.mutex.RLock() + defer v.mutex.RUnlock() + + return v.t.Close(ctx) + } + + return nil +} + +func (v *Once[T]) Get() T { + v.once.Do(func() { + v.mutex.Lock() + defer v.mutex.Unlock() + + v.t = v.f() + }) + + v.mutex.RLock() + defer v.mutex.RUnlock() + + return v.t +} diff --git a/internal/xsync/once_test.go b/internal/xsync/once_test.go new file mode 100644 index 000000000..003e9a7e7 --- /dev/null +++ b/internal/xsync/once_test.go @@ -0,0 +1,93 @@ +package xsync + +import ( + "context" + "errors" + "sync" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ydb-platform/ydb-go-sdk/v3/internal/xtest" +) + +func TestOnceFunc(t *testing.T) { + var ( + ctx = xtest.Context(t) + cnt = 0 + ) + f := OnceFunc(func(ctx context.Context) error { + cnt++ + + return nil + }) + require.Equal(t, 0, cnt) + require.NoError(t, f(ctx)) + require.Equal(t, 1, cnt) + require.NoError(t, f(ctx)) + require.Equal(t, 1, cnt) +} + +type testCloser struct { + value int + inited bool + closed bool + closeErr error +} + +func (c *testCloser) Close(ctx context.Context) error { + c.closed = true + + return c.closeErr +} + +func TestOnceValue(t *testing.T) { + ctx := xtest.Context(t) + t.Run("Race", func(t *testing.T) { + counter := 0 + once := OnceValue(func() *testCloser { + counter++ + + return &testCloser{value: counter} + }) + var wg sync.WaitGroup + wg.Add(1000) + for range make([]struct{}, 1000) { + go func() { + defer wg.Done() + v := once.Get() + require.Equal(t, 1, v.value) + }() + } + wg.Wait() + }) + t.Run("GetBeforeClose", func(t *testing.T) { + constCloseErr := errors.New("") + once := OnceValue(func() *testCloser { + return &testCloser{ + inited: true, + closeErr: constCloseErr, + } + }) + v := once.Get() + require.True(t, v.inited) + require.False(t, v.closed) + err := once.Close(ctx) + require.ErrorIs(t, err, constCloseErr) + require.True(t, v.inited) + require.True(t, v.closed) + }) + t.Run("CloseBeforeGet", func(t *testing.T) { + constCloseErr := errors.New("") + once := OnceValue(func() *testCloser { + return &testCloser{ + inited: true, + closeErr: constCloseErr, + } + }) + err := once.Close(ctx) + require.NoError(t, err) + v := once.Get() + require.Nil(t, v) + }) +} diff --git a/internal/xtest/call_method.go b/internal/xtest/call_method.go new file mode 100644 index 000000000..a7e060a8a --- /dev/null +++ b/internal/xtest/call_method.go @@ -0,0 +1,25 @@ +package xtest + +import ( + "reflect" +) + +func CallMethod(object any, name string, args ...any) []any { + method := reflect.ValueOf(object).MethodByName(name) + + inputs := make([]reflect.Value, len(args)) + + for i := range args { + inputs[i] = reflect.ValueOf(args[i]) + } + + output := method.Call(inputs) + + result := make([]any, len(output)) + + for i := range output { + result[i] = output[i].Interface() + } + + return result +} diff --git a/internal/xtest/call_method_test.go b/internal/xtest/call_method_test.go new file mode 100644 index 000000000..fe721761d --- /dev/null +++ b/internal/xtest/call_method_test.go @@ -0,0 +1,39 @@ +package xtest + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestCallMethod(t *testing.T) { + object := bytes.NewBuffer(nil) + + result := CallMethod(object, "WriteString", "Hello world!") + n := result[0].(int) + err := result[1] + + require.Equal(t, 12, n) + require.Nil(t, err) + + result = CallMethod(object, "String") + + str, ok := result[0].(string) + require.True(t, ok) + + require.Equal(t, object.String(), str) + + require.Panics(t, func() { + CallMethod(object, "NonameMethod") + }) + + require.Panics(t, func() { + CallMethod(object, "String", "wrong", "arguments", "count") + }) + + require.Panics(t, func() { + // Wrong argument type. + CallMethod(object, "WriteString", 123) + }) +} diff --git a/internal/xtest/grpclogger.go b/internal/xtest/grpclogger.go index b36c05483..3506d4ccb 100644 --- a/internal/xtest/grpclogger.go +++ b/internal/xtest/grpclogger.go @@ -16,7 +16,7 @@ var globalLastStreamID = int64(0) // // db, err := ydb.Open(context.Background(), connectionString, // ... -// ydb.With(config.WithGrpcOptions(grpc.WithChainUnaryInterceptor(xtest.NewGrpcLogger(t).UnaryClientInterceptor))), +// ydb.Change(config.WithGrpcOptions(grpc.WithChainUnaryInterceptor(xtest.NewGrpcLogger(t).UnaryClientInterceptor))), // ) type GrpcLogger struct { t testing.TB diff --git a/internal/xtest/manytimes.go b/internal/xtest/manytimes.go index cd84603fb..4dc7f1ccb 100644 --- a/internal/xtest/manytimes.go +++ b/internal/xtest/manytimes.go @@ -31,9 +31,9 @@ func TestManyTimes(t testing.TB, test TestFunc, opts ...TestManyTimesOption) { stopAfter: time.Second, } - for _, o := range opts { - if o != nil { - o(&options) + for _, opt := range opts { + if opt != nil { + opt(&options) } } 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/driver.go b/log/driver.go index a94ffb262..fb9c8b1a1 100644 --- a/log/driver.go +++ b/log/driver.go @@ -13,267 +13,185 @@ func Driver(l Logger, d trace.Detailer, opts ...Option) (t trace.Driver) { return internalDriver(wrapLogger(l, opts...), d) } -func internalDriver(l Logger, d trace.Detailer) (t trace.Driver) { //nolint:gocyclo - t.OnResolve = func( - info trace.DriverResolveStartInfo, - ) func( - trace.DriverResolveDoneInfo, - ) { - if d.Details()&trace.DriverResolverEvents == 0 { - return nil - } - ctx := with(context.Background(), TRACE, "ydb", "driver", "resolver", "update") - target := info.Target - addresses := info.Resolved - l.Log(ctx, "start", - String("target", target), - Strings("resolved", addresses), - ) - - return func(info trace.DriverResolveDoneInfo) { - if info.Error == nil { - l.Log(ctx, "done", - String("target", target), - Strings("resolved", addresses), - ) - } else { - l.Log(WithLevel(ctx, WARN), "failed", - Error(info.Error), - String("target", target), - Strings("resolved", addresses), - versionField(), - ) +func internalDriver(l Logger, d trace.Detailer) trace.Driver { //nolint:gocyclo + return trace.Driver{ + OnResolve: func( + info trace.DriverResolveStartInfo, + ) func( + trace.DriverResolveDoneInfo, + ) { + if d.Details()&trace.DriverResolverEvents == 0 { + return nil } - } - } - t.OnInit = func(info trace.DriverInitStartInfo) func(trace.DriverInitDoneInfo) { - if d.Details()&trace.DriverEvents == 0 { - return nil - } - endpoint := info.Endpoint - database := info.Database - secure := info.Secure - ctx := with(*info.Context, DEBUG, "ydb", "driver", "resolver", "init") - l.Log(ctx, "start", - String("endpoint", endpoint), - String("database", database), - Bool("secure", secure), - ) - start := time.Now() + ctx := with(context.Background(), TRACE, "ydb", "driver", "resolver", "update") + target := info.Target + addresses := info.Resolved + l.Log(ctx, "start", + String("target", target), + Strings("resolved", addresses), + ) - return func(info trace.DriverInitDoneInfo) { - if info.Error == nil { - l.Log(ctx, "done", - String("endpoint", endpoint), - String("database", database), - Bool("secure", secure), - latencyField(start), - ) - } else { - l.Log(WithLevel(ctx, ERROR), "failed", - Error(info.Error), - String("endpoint", endpoint), - String("database", database), - Bool("secure", secure), - latencyField(start), - versionField(), - ) + return func(info trace.DriverResolveDoneInfo) { + if info.Error == nil { + l.Log(ctx, "done", + String("target", target), + Strings("resolved", addresses), + ) + } else { + l.Log(WithLevel(ctx, WARN), "failed", + Error(info.Error), + String("target", target), + Strings("resolved", addresses), + versionField(), + ) + } } - } - } - t.OnClose = func(info trace.DriverCloseStartInfo) func(trace.DriverCloseDoneInfo) { - if d.Details()&trace.DriverEvents == 0 { - return nil - } - ctx := with(*info.Context, TRACE, "ydb", "driver", "resolver", "close") - l.Log(ctx, "start") - start := time.Now() + }, + OnInit: func(info trace.DriverInitStartInfo) func(trace.DriverInitDoneInfo) { + if d.Details()&trace.DriverEvents == 0 { + return nil + } + endpoint := info.Endpoint + database := info.Database + secure := info.Secure + ctx := with(*info.Context, DEBUG, "ydb", "driver", "resolver", "init") + l.Log(ctx, "start", + String("endpoint", endpoint), + String("database", database), + Bool("secure", secure), + ) + start := time.Now() - return func(info trace.DriverCloseDoneInfo) { - if info.Error == nil { - l.Log(ctx, "done", - latencyField(start), - ) - } else { - l.Log(WithLevel(ctx, WARN), "failed", - Error(info.Error), - latencyField(start), - versionField(), - ) + return func(info trace.DriverInitDoneInfo) { + if info.Error == nil { + l.Log(ctx, "done", + String("endpoint", endpoint), + String("database", database), + Bool("secure", secure), + latencyField(start), + ) + } else { + l.Log(WithLevel(ctx, ERROR), "failed", + Error(info.Error), + String("endpoint", endpoint), + String("database", database), + Bool("secure", secure), + latencyField(start), + versionField(), + ) + } } - } - } - t.OnConnDial = func(info trace.DriverConnDialStartInfo) func(trace.DriverConnDialDoneInfo) { - if d.Details()&trace.DriverConnEvents == 0 { - return nil - } - ctx := with(*info.Context, TRACE, "ydb", "driver", "conn", "dial") - endpoint := info.Endpoint - l.Log(ctx, "start", - Stringer("endpoint", endpoint), - ) - start := time.Now() + }, + OnClose: func(info trace.DriverCloseStartInfo) func(trace.DriverCloseDoneInfo) { + if d.Details()&trace.DriverEvents == 0 { + return nil + } + ctx := with(*info.Context, TRACE, "ydb", "driver", "resolver", "close") + l.Log(ctx, "start") + start := time.Now() - return func(info trace.DriverConnDialDoneInfo) { - if info.Error == nil { - l.Log(ctx, "done", - Stringer("endpoint", endpoint), - latencyField(start), - ) - } else { - l.Log(WithLevel(ctx, WARN), "failed", - Error(info.Error), - Stringer("endpoint", endpoint), - latencyField(start), - versionField(), - ) + return func(info trace.DriverCloseDoneInfo) { + if info.Error == nil { + l.Log(ctx, "done", + latencyField(start), + ) + } else { + l.Log(WithLevel(ctx, WARN), "failed", + Error(info.Error), + latencyField(start), + versionField(), + ) + } } - } - } - t.OnConnStateChange = func(info trace.DriverConnStateChangeStartInfo) func(trace.DriverConnStateChangeDoneInfo) { - if d.Details()&trace.DriverConnEvents == 0 { - return nil - } - ctx := with(context.Background(), TRACE, "ydb", "driver", "conn", "state", "change") - endpoint := info.Endpoint - l.Log(ctx, "start", - Stringer("endpoint", endpoint), - Stringer("state", info.State), - ) - start := time.Now() + }, + OnConnDial: func(info trace.DriverConnDialStartInfo) func(trace.DriverConnDialDoneInfo) { + if d.Details()&trace.DriverConnEvents == 0 { + return nil + } + ctx := with(*info.Context, TRACE, "ydb", "driver", "conn", "dial") + endpoint := info.Endpoint + l.Log(ctx, "start", + Stringer("endpoint", endpoint), + ) + start := time.Now() - return func(info trace.DriverConnStateChangeDoneInfo) { - l.Log(ctx, "done", + return func(info trace.DriverConnDialDoneInfo) { + if info.Error == nil { + l.Log(ctx, "done", + Stringer("endpoint", endpoint), + latencyField(start), + ) + } else { + l.Log(WithLevel(ctx, WARN), "failed", + Error(info.Error), + Stringer("endpoint", endpoint), + latencyField(start), + versionField(), + ) + } + } + }, + OnConnStateChange: func(info trace.DriverConnStateChangeStartInfo) func(trace.DriverConnStateChangeDoneInfo) { + if d.Details()&trace.DriverConnEvents == 0 { + return nil + } + ctx := with(context.Background(), TRACE, "ydb", "driver", "conn", "state", "change") + endpoint := info.Endpoint + l.Log(ctx, "start", Stringer("endpoint", endpoint), - latencyField(start), Stringer("state", info.State), ) - } - } - t.OnConnPark = func(info trace.DriverConnParkStartInfo) func(trace.DriverConnParkDoneInfo) { - if d.Details()&trace.DriverConnEvents == 0 { - return nil - } - ctx := with(*info.Context, TRACE, "ydb", "driver", "conn", "park") - endpoint := info.Endpoint - l.Log(ctx, "start", - Stringer("endpoint", endpoint), - ) - start := time.Now() + start := time.Now() - return func(info trace.DriverConnParkDoneInfo) { - if info.Error == nil { + return func(info trace.DriverConnStateChangeDoneInfo) { l.Log(ctx, "done", Stringer("endpoint", endpoint), latencyField(start), - ) - } else { - l.Log(WithLevel(ctx, WARN), "failed", - Error(info.Error), - Stringer("endpoint", endpoint), - latencyField(start), - versionField(), + Stringer("state", info.State), ) } - } - } - t.OnConnClose = func(info trace.DriverConnCloseStartInfo) func(trace.DriverConnCloseDoneInfo) { - if d.Details()&trace.DriverConnEvents == 0 { - return nil - } - ctx := with(*info.Context, TRACE, "ydb", "driver", "conn", "close") - endpoint := info.Endpoint - l.Log(ctx, "start", - Stringer("endpoint", endpoint), - ) - start := time.Now() - - return func(info trace.DriverConnCloseDoneInfo) { - if info.Error == nil { - l.Log(ctx, "done", - Stringer("endpoint", endpoint), - latencyField(start), - ) - } else { - l.Log(WithLevel(ctx, WARN), "failed", - Error(info.Error), - Stringer("endpoint", endpoint), - latencyField(start), - versionField(), - ) + }, + OnConnClose: func(info trace.DriverConnCloseStartInfo) func(trace.DriverConnCloseDoneInfo) { + if d.Details()&trace.DriverConnEvents == 0 { + return nil } - } - } - t.OnConnInvoke = func(info trace.DriverConnInvokeStartInfo) func(trace.DriverConnInvokeDoneInfo) { - if d.Details()&trace.DriverConnEvents == 0 { - return nil - } - ctx := with(*info.Context, TRACE, "ydb", "driver", "conn", "invoke") - endpoint := info.Endpoint - method := string(info.Method) - l.Log(ctx, "start", - Stringer("endpoint", endpoint), - String("method", method), - ) - start := time.Now() + ctx := with(*info.Context, TRACE, "ydb", "driver", "conn", "close") + endpoint := info.Endpoint + l.Log(ctx, "start", + Stringer("endpoint", endpoint), + ) + start := time.Now() - return func(info trace.DriverConnInvokeDoneInfo) { - if info.Error == nil { - l.Log(ctx, "done", - Stringer("endpoint", endpoint), - String("method", method), - latencyField(start), - Stringer("metadata", metadata(info.Metadata)), - ) - } else { - l.Log(WithLevel(ctx, WARN), "failed", - Error(info.Error), - Stringer("endpoint", endpoint), - String("method", method), - latencyField(start), - Stringer("metadata", metadata(info.Metadata)), - versionField(), - ) + return func(info trace.DriverConnCloseDoneInfo) { + if info.Error == nil { + l.Log(ctx, "done", + Stringer("endpoint", endpoint), + latencyField(start), + ) + } else { + l.Log(WithLevel(ctx, WARN), "failed", + Error(info.Error), + Stringer("endpoint", endpoint), + latencyField(start), + versionField(), + ) + } } - } - } - t.OnConnNewStream = func( - info trace.DriverConnNewStreamStartInfo, - ) func( - trace.DriverConnNewStreamRecvInfo, - ) func( - trace.DriverConnNewStreamDoneInfo, - ) { - if d.Details()&trace.DriverConnEvents == 0 { - return nil - } - ctx := with(*info.Context, TRACE, "ydb", "driver", "conn", "new", "stream") - endpoint := info.Endpoint - method := string(info.Method) - l.Log(ctx, "start", - Stringer("endpoint", endpoint), - String("method", method), - ) - start := time.Now() - - return func(info trace.DriverConnNewStreamRecvInfo) func(trace.DriverConnNewStreamDoneInfo) { - if info.Error == nil { - l.Log(ctx, "intermediate receive", - Stringer("endpoint", endpoint), - String("method", method), - latencyField(start), - ) - } else { - l.Log(WithLevel(ctx, WARN), "intermediate fail", - Error(info.Error), - Stringer("endpoint", endpoint), - String("method", method), - latencyField(start), - versionField(), - ) + }, + OnConnInvoke: func(info trace.DriverConnInvokeStartInfo) func(trace.DriverConnInvokeDoneInfo) { + if d.Details()&trace.DriverConnEvents == 0 { + return nil } + ctx := with(*info.Context, TRACE, "ydb", "driver", "conn", "invoke") + endpoint := info.Endpoint + method := string(info.Method) + l.Log(ctx, "start", + Stringer("endpoint", endpoint), + String("method", method), + ) + start := time.Now() - return func(info trace.DriverConnNewStreamDoneInfo) { + return func(info trace.DriverConnInvokeDoneInfo) { if info.Error == nil { l.Log(ctx, "done", Stringer("endpoint", endpoint), @@ -292,191 +210,293 @@ func internalDriver(l Logger, d trace.Detailer) (t trace.Driver) { //nolint:gocy ) } } - } - } - t.OnConnBan = func(info trace.DriverConnBanStartInfo) func(trace.DriverConnBanDoneInfo) { - if d.Details()&trace.DriverConnEvents == 0 { - return nil - } - ctx := with(*info.Context, TRACE, "ydb", "driver", "conn", "ban") - endpoint := info.Endpoint - l.Log(ctx, "start", - Stringer("endpoint", endpoint), - NamedError("cause", info.Cause), - versionField(), - ) - start := time.Now() - - return func(info trace.DriverConnBanDoneInfo) { - l.Log(WithLevel(ctx, WARN), "done", + }, + OnConnNewStream: func( + info trace.DriverConnNewStreamStartInfo, + ) func( + trace.DriverConnNewStreamDoneInfo, + ) { + if d.Details()&trace.DriverConnEvents == 0 { + return nil + } + ctx := with(*info.Context, TRACE, "ydb", "driver", "conn", "stream", "New") + endpoint := info.Endpoint + method := string(info.Method) + l.Log(ctx, "start", Stringer("endpoint", endpoint), - latencyField(start), - Stringer("state", info.State), - versionField(), + String("method", method), ) - } - } - t.OnConnAllow = func(info trace.DriverConnAllowStartInfo) func(trace.DriverConnAllowDoneInfo) { - if d.Details()&trace.DriverConnEvents == 0 { - return nil - } - ctx := with(*info.Context, TRACE, "ydb", "driver", "conn", "allow") - endpoint := info.Endpoint - l.Log(ctx, "start", - Stringer("endpoint", endpoint), - ) - start := time.Now() + start := time.Now() + + return func(info trace.DriverConnNewStreamDoneInfo) { + if info.Error == nil { + l.Log(ctx, "done", + Stringer("endpoint", endpoint), + String("method", method), + latencyField(start), + ) + } else { + l.Log(WithLevel(ctx, WARN), "failed", + Error(info.Error), + Stringer("endpoint", endpoint), + String("method", method), + latencyField(start), + versionField(), + ) + } + } + }, + OnConnStreamCloseSend: func(info trace.DriverConnStreamCloseSendStartInfo) func( + trace.DriverConnStreamCloseSendDoneInfo, + ) { + if d.Details()&trace.DriverConnEvents == 0 { + return nil + } + ctx := with(*info.Context, TRACE, "ydb", "driver", "conn", "stream", "CloseSend") + l.Log(ctx, "start") + start := time.Now() + + return func(info trace.DriverConnStreamCloseSendDoneInfo) { + if info.Error == nil { + l.Log(ctx, "done", + latencyField(start), + ) + } else { + l.Log(WithLevel(ctx, WARN), "failed", + Error(info.Error), + latencyField(start), + versionField(), + ) + } + } + }, + OnConnStreamSendMsg: func(info trace.DriverConnStreamSendMsgStartInfo) func(trace.DriverConnStreamSendMsgDoneInfo) { + if d.Details()&trace.DriverConnEvents == 0 { + return nil + } + ctx := with(*info.Context, TRACE, "ydb", "driver", "conn", "stream", "SendMsg") + l.Log(ctx, "start") + start := time.Now() + + return func(info trace.DriverConnStreamSendMsgDoneInfo) { + if info.Error == nil { + l.Log(ctx, "done", + latencyField(start), + ) + } else { + l.Log(WithLevel(ctx, WARN), "failed", + Error(info.Error), + latencyField(start), + versionField(), + ) + } + } + }, + OnConnStreamRecvMsg: func(info trace.DriverConnStreamRecvMsgStartInfo) func(trace.DriverConnStreamRecvMsgDoneInfo) { + if d.Details()&trace.DriverConnEvents == 0 { + return nil + } + ctx := with(*info.Context, TRACE, "ydb", "driver", "conn", "stream", "RecvMsg") + l.Log(ctx, "start") + start := time.Now() - return func(info trace.DriverConnAllowDoneInfo) { - l.Log(ctx, "done", + return func(info trace.DriverConnStreamRecvMsgDoneInfo) { + if info.Error == nil { + l.Log(ctx, "done", + latencyField(start), + ) + } else { + l.Log(WithLevel(ctx, WARN), "failed", + Error(info.Error), + latencyField(start), + versionField(), + ) + } + } + }, + OnConnBan: func(info trace.DriverConnBanStartInfo) func(trace.DriverConnBanDoneInfo) { + if d.Details()&trace.DriverConnEvents == 0 { + return nil + } + ctx := with(*info.Context, TRACE, "ydb", "driver", "conn", "ban") + endpoint := info.Endpoint + cause := info.Cause + l.Log(ctx, "start", Stringer("endpoint", endpoint), - latencyField(start), - Stringer("state", info.State), + NamedError("cause", cause), ) - } - } - t.OnRepeaterWakeUp = func(info trace.DriverRepeaterWakeUpStartInfo) func(trace.DriverRepeaterWakeUpDoneInfo) { - if d.Details()&trace.DriverRepeaterEvents == 0 { - return nil - } - ctx := with(*info.Context, TRACE, "ydb", "driver", "repeater", "wake", "up") - name := info.Name - event := info.Event - l.Log(ctx, "start", - String("name", name), - String("event", event), - ) - start := time.Now() + start := time.Now() - return func(info trace.DriverRepeaterWakeUpDoneInfo) { - if info.Error == nil { - l.Log(ctx, "done", - String("name", name), - String("event", event), - latencyField(start), - ) - } else { - l.Log(WithLevel(ctx, ERROR), "failed", - Error(info.Error), - String("name", name), - String("event", event), + return func(info trace.DriverConnBanDoneInfo) { + l.Log(WithLevel(ctx, WARN), "done", + Stringer("endpoint", endpoint), latencyField(start), + Stringer("state", info.State), + NamedError("cause", cause), versionField(), ) } - } - } - t.OnBalancerInit = func(info trace.DriverBalancerInitStartInfo) func(trace.DriverBalancerInitDoneInfo) { - if d.Details()&trace.DriverBalancerEvents == 0 { - return nil - } - ctx := with(*info.Context, TRACE, "ydb", "driver", "balancer", "init") - l.Log(ctx, "start") - start := time.Now() - - return func(info trace.DriverBalancerInitDoneInfo) { - l.Log(WithLevel(ctx, INFO), "done", - latencyField(start), + }, + OnConnAllow: func(info trace.DriverConnAllowStartInfo) func(trace.DriverConnAllowDoneInfo) { + if d.Details()&trace.DriverConnEvents == 0 { + return nil + } + ctx := with(*info.Context, TRACE, "ydb", "driver", "conn", "allow") + endpoint := info.Endpoint + l.Log(ctx, "start", + Stringer("endpoint", endpoint), ) - } - } - t.OnBalancerClose = func(info trace.DriverBalancerCloseStartInfo) func(trace.DriverBalancerCloseDoneInfo) { - if d.Details()&trace.DriverBalancerEvents == 0 { - return nil - } - ctx := with(*info.Context, TRACE, "ydb", "driver", "balancer", "close") - l.Log(ctx, "start") - start := time.Now() + start := time.Now() - return func(info trace.DriverBalancerCloseDoneInfo) { - if info.Error == nil { + return func(info trace.DriverConnAllowDoneInfo) { l.Log(ctx, "done", + Stringer("endpoint", endpoint), latencyField(start), - ) - } else { - l.Log(WithLevel(ctx, WARN), "failed", - Error(info.Error), - latencyField(start), - versionField(), + Stringer("state", info.State), ) } - } - } - t.OnBalancerChooseEndpoint = func( - info trace.DriverBalancerChooseEndpointStartInfo, - ) func( - trace.DriverBalancerChooseEndpointDoneInfo, - ) { - if d.Details()&trace.DriverBalancerEvents == 0 { - return nil - } - ctx := with(*info.Context, TRACE, "ydb", "driver", "balancer", "choose", "endpoint") - l.Log(ctx, "start") - start := time.Now() + }, + OnRepeaterWakeUp: func(info trace.DriverRepeaterWakeUpStartInfo) func(trace.DriverRepeaterWakeUpDoneInfo) { + if d.Details()&trace.DriverRepeaterEvents == 0 { + return nil + } + ctx := with(*info.Context, TRACE, "ydb", "driver", "repeater", "wake", "up") + name := info.Name + event := info.Event + l.Log(ctx, "start", + String("name", name), + String("event", event), + ) + start := time.Now() - return func(info trace.DriverBalancerChooseEndpointDoneInfo) { - if info.Error == nil { - l.Log(ctx, "done", - latencyField(start), - Stringer("endpoint", info.Endpoint), - ) - } else { - l.Log(WithLevel(ctx, ERROR), "failed", - Error(info.Error), + return func(info trace.DriverRepeaterWakeUpDoneInfo) { + if info.Error == nil { + l.Log(ctx, "done", + String("name", name), + String("event", event), + latencyField(start), + ) + } else { + l.Log(WithLevel(ctx, ERROR), "failed", + Error(info.Error), + String("name", name), + String("event", event), + latencyField(start), + versionField(), + ) + } + } + }, + OnBalancerInit: func(info trace.DriverBalancerInitStartInfo) func(trace.DriverBalancerInitDoneInfo) { + if d.Details()&trace.DriverBalancerEvents == 0 { + return nil + } + ctx := with(*info.Context, TRACE, "ydb", "driver", "balancer", "init") + l.Log(ctx, "start") + start := time.Now() + + return func(info trace.DriverBalancerInitDoneInfo) { + l.Log(WithLevel(ctx, INFO), "done", latencyField(start), - versionField(), ) } - } - } - t.OnBalancerUpdate = func( - info trace.DriverBalancerUpdateStartInfo, - ) func( - trace.DriverBalancerUpdateDoneInfo, - ) { - if d.Details()&trace.DriverBalancerEvents == 0 { - return nil - } - ctx := with(*info.Context, TRACE, "ydb", "driver", "balancer", "update") - l.Log(ctx, "start", - Bool("needLocalDC", info.NeedLocalDC), - ) - start := time.Now() + }, + OnBalancerClose: func(info trace.DriverBalancerCloseStartInfo) func(trace.DriverBalancerCloseDoneInfo) { + if d.Details()&trace.DriverBalancerEvents == 0 { + return nil + } + ctx := with(*info.Context, TRACE, "ydb", "driver", "balancer", "close") + l.Log(ctx, "start") + start := time.Now() - return func(info trace.DriverBalancerUpdateDoneInfo) { - l.Log(ctx, "done", - latencyField(start), - Stringer("endpoints", endpoints(info.Endpoints)), - Stringer("added", endpoints(info.Added)), - Stringer("dropped", endpoints(info.Dropped)), - String("detectedLocalDC", info.LocalDC), + return func(info trace.DriverBalancerCloseDoneInfo) { + if info.Error == nil { + l.Log(ctx, "done", + latencyField(start), + ) + } else { + l.Log(WithLevel(ctx, WARN), "failed", + Error(info.Error), + latencyField(start), + versionField(), + ) + } + } + }, + OnBalancerChooseEndpoint: func( + info trace.DriverBalancerChooseEndpointStartInfo, + ) func( + trace.DriverBalancerChooseEndpointDoneInfo, + ) { + if d.Details()&trace.DriverBalancerEvents == 0 { + return nil + } + ctx := with(*info.Context, TRACE, "ydb", "driver", "balancer", "choose", "endpoint") + l.Log(ctx, "start") + start := time.Now() + + return func(info trace.DriverBalancerChooseEndpointDoneInfo) { + if info.Error == nil { + l.Log(ctx, "done", + latencyField(start), + Stringer("endpoint", info.Endpoint), + ) + } else { + l.Log(WithLevel(ctx, ERROR), "failed", + Error(info.Error), + latencyField(start), + versionField(), + ) + } + } + }, + OnBalancerUpdate: func( + info trace.DriverBalancerUpdateStartInfo, + ) func( + trace.DriverBalancerUpdateDoneInfo, + ) { + if d.Details()&trace.DriverBalancerEvents == 0 { + return nil + } + ctx := with(*info.Context, TRACE, "ydb", "driver", "balancer", "update") + l.Log(ctx, "start", + Bool("needLocalDC", info.NeedLocalDC), ) - } - } - t.OnGetCredentials = func(info trace.DriverGetCredentialsStartInfo) func(trace.DriverGetCredentialsDoneInfo) { - if d.Details()&trace.DriverCredentialsEvents == 0 { - return nil - } - ctx := with(*info.Context, TRACE, "ydb", "driver", "credentials", "get") - l.Log(ctx, "start") - start := time.Now() + start := time.Now() - return func(info trace.DriverGetCredentialsDoneInfo) { - if info.Error == nil { + return func(info trace.DriverBalancerUpdateDoneInfo) { l.Log(ctx, "done", latencyField(start), - String("token", secret.Token(info.Token)), - ) - } else { - l.Log(WithLevel(ctx, ERROR), "done", - Error(info.Error), - latencyField(start), - String("token", secret.Token(info.Token)), - versionField(), + Stringer("endpoints", endpoints(info.Endpoints)), + Stringer("added", endpoints(info.Added)), + Stringer("dropped", endpoints(info.Dropped)), + String("detectedLocalDC", info.LocalDC), ) } - } - } + }, + OnGetCredentials: func(info trace.DriverGetCredentialsStartInfo) func(trace.DriverGetCredentialsDoneInfo) { + if d.Details()&trace.DriverCredentialsEvents == 0 { + return nil + } + ctx := with(*info.Context, TRACE, "ydb", "driver", "credentials", "get") + l.Log(ctx, "start") + start := time.Now() - return t + return func(info trace.DriverGetCredentialsDoneInfo) { + if info.Error == nil { + l.Log(ctx, "done", + latencyField(start), + String("token", secret.Token(info.Token)), + ) + } else { + l.Log(WithLevel(ctx, ERROR), "done", + Error(info.Error), + latencyField(start), + String("token", secret.Token(info.Token)), + versionField(), + ) + } + } + }, + } } diff --git a/log/logger.go b/log/logger.go index e3981058e..8caf7229a 100644 --- a/log/logger.go +++ b/log/logger.go @@ -33,8 +33,10 @@ func Default(w io.Writer, opts ...simpleLoggerOption) *defaultLogger { clock: clockwork.NewRealClock(), w: w, } - for _, o := range opts { - o.applySimpleOption(l) + for _, opt := range opts { + if opt != nil { + opt.applySimpleOption(l) + } } return l @@ -42,9 +44,9 @@ func Default(w io.Writer, opts ...simpleLoggerOption) *defaultLogger { type defaultLogger struct { coloring bool - clock clockwork.Clock logQuery bool minLevel Level + clock clockwork.Clock w io.Writer } @@ -104,9 +106,9 @@ func wrapLogger(l Logger, opts ...Option) *wrapper { ll := &wrapper{ logger: l, } - for _, o := range opts { - if o != nil { - o.applyHolderOption(ll) + for _, opt := range opts { + if opt != nil { + opt.applyHolderOption(ll) } } 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/query.go b/log/query.go new file mode 100644 index 000000000..e44e9e3ac --- /dev/null +++ b/log/query.go @@ -0,0 +1,653 @@ +package log + +import ( + "time" + + "github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors" + "github.com/ydb-platform/ydb-go-sdk/v3/trace" +) + +// Query makes trace.Query with logging events from details +func Query(l Logger, d trace.Detailer, opts ...Option) (t trace.Query) { + return internalQuery(wrapLogger(l, opts...), d) +} + +//nolint:gocyclo +func internalQuery( + l *wrapper, //nolint:interfacer + d trace.Detailer, +) trace.Query { + return trace.Query{ + OnNew: func(info trace.QueryNewStartInfo) func(info trace.QueryNewDoneInfo) { + if d.Details()&trace.QueryEvents == 0 { + return nil + } + ctx := with(*info.Context, TRACE, "ydb", "query", "new") + l.Log(ctx, "start") + start := time.Now() + + return func(info trace.QueryNewDoneInfo) { + l.Log(WithLevel(ctx, INFO), "done", + latencyField(start), + ) + } + }, + OnClose: func(info trace.QueryCloseStartInfo) func(info trace.QueryCloseDoneInfo) { + if d.Details()&trace.QueryEvents == 0 { + return nil + } + ctx := with(*info.Context, TRACE, "ydb", "query", "close") + l.Log(ctx, "start") + start := time.Now() + + return func(info trace.QueryCloseDoneInfo) { + if info.Error == nil { + l.Log(ctx, "done", + latencyField(start), + ) + } else { + lvl := WARN + if !xerrors.IsYdb(info.Error) { + lvl = DEBUG + } + l.Log(WithLevel(ctx, lvl), "failed", + latencyField(start), + Error(info.Error), + versionField(), + ) + } + } + }, + OnPoolNew: func(info trace.QueryPoolNewStartInfo) func(trace.QueryPoolNewDoneInfo) { + if d.Details()&trace.QueryPoolEvents == 0 { + return nil + } + ctx := with(*info.Context, TRACE, "ydb", "query", "pool", "new") + l.Log(ctx, "start") + start := time.Now() + + return func(info trace.QueryPoolNewDoneInfo) { + l.Log(WithLevel(ctx, INFO), "done", + latencyField(start), + Int("Limit", info.Limit), + ) + } + }, + OnPoolClose: func(info trace.QueryPoolCloseStartInfo) func(trace.QueryPoolCloseDoneInfo) { + if d.Details()&trace.QueryPoolEvents == 0 { + return nil + } + ctx := with(*info.Context, TRACE, "ydb", "query", "pool", "close") + l.Log(ctx, "start") + start := time.Now() + + return func(info trace.QueryPoolCloseDoneInfo) { + if info.Error == nil { + l.Log(ctx, "done", + latencyField(start), + ) + } else { + lvl := WARN + if !xerrors.IsYdb(info.Error) { + lvl = DEBUG + } + l.Log(WithLevel(ctx, lvl), "failed", + latencyField(start), + Error(info.Error), + versionField(), + ) + } + } + }, + OnPoolTry: func(info trace.QueryPoolTryStartInfo) func(trace.QueryPoolTryDoneInfo) { + if d.Details()&trace.QueryPoolEvents == 0 { + return nil + } + ctx := with(*info.Context, TRACE, "ydb", "query", "pool", "try") + l.Log(ctx, "start") + start := time.Now() + + return func(info trace.QueryPoolTryDoneInfo) { + if info.Error == nil { + l.Log(ctx, "done", + latencyField(start), + ) + } else { + lvl := WARN + if !xerrors.IsYdb(info.Error) { + lvl = DEBUG + } + l.Log(WithLevel(ctx, lvl), "failed", + latencyField(start), + Error(info.Error), + versionField(), + ) + } + } + }, + OnPoolWith: func(info trace.QueryPoolWithStartInfo) func(trace.QueryPoolWithDoneInfo) { + if d.Details()&trace.QueryPoolEvents == 0 { + return nil + } + ctx := with(*info.Context, DEBUG, "ydb", "query", "pool", "with") + l.Log(ctx, "start") + start := time.Now() + + return func(info trace.QueryPoolWithDoneInfo) { + if info.Error == nil { + l.Log(ctx, "done", + latencyField(start), + Int("Attempts", info.Attempts), + ) + } else { + lvl := ERROR + if !xerrors.IsYdb(info.Error) { + lvl = DEBUG + } + l.Log(WithLevel(ctx, lvl), "failed", + latencyField(start), + Error(info.Error), + Int("Attempts", info.Attempts), + versionField(), + ) + } + } + }, + OnPoolPut: func(info trace.QueryPoolPutStartInfo) func(trace.QueryPoolPutDoneInfo) { + if d.Details()&trace.QueryPoolEvents == 0 { + return nil + } + ctx := with(*info.Context, TRACE, "ydb", "query", "pool", "put") + l.Log(ctx, "start") + start := time.Now() + + return func(info trace.QueryPoolPutDoneInfo) { + if info.Error == nil { + l.Log(ctx, "done", + latencyField(start), + ) + } else { + lvl := WARN + if !xerrors.IsYdb(info.Error) { + lvl = DEBUG + } + l.Log(WithLevel(ctx, lvl), "failed", + latencyField(start), + Error(info.Error), + versionField(), + ) + } + } + }, + OnPoolGet: func(info trace.QueryPoolGetStartInfo) func(trace.QueryPoolGetDoneInfo) { + if d.Details()&trace.QueryPoolEvents == 0 { + return nil + } + ctx := with(*info.Context, TRACE, "ydb", "query", "pool", "get") + l.Log(ctx, "start") + start := time.Now() + + return func(info trace.QueryPoolGetDoneInfo) { + if info.Error == nil { + l.Log(ctx, "done", + latencyField(start), + ) + } else { + lvl := WARN + if !xerrors.IsYdb(info.Error) { + lvl = DEBUG + } + l.Log(WithLevel(ctx, lvl), "failed", + latencyField(start), + Error(info.Error), + versionField(), + ) + } + } + }, + OnDo: func(info trace.QueryDoStartInfo) func(trace.QueryDoDoneInfo) { + if d.Details()&trace.QueryEvents == 0 { + return nil + } + ctx := with(*info.Context, TRACE, "ydb", "query", "do") + l.Log(ctx, "start") + start := time.Now() + + return func(info trace.QueryDoDoneInfo) { + if info.Error == nil { + l.Log(ctx, "done", + latencyField(start), + Int("attempts", info.Attempts), + ) + } else { + lvl := ERROR + if !xerrors.IsYdb(info.Error) { + lvl = DEBUG + } + l.Log(WithLevel(ctx, lvl), "failed", + latencyField(start), + Error(info.Error), + Int("attempts", info.Attempts), + versionField(), + ) + } + } + }, + OnDoTx: func(info trace.QueryDoTxStartInfo) func(trace.QueryDoTxDoneInfo) { + if d.Details()&trace.QueryEvents == 0 { + return nil + } + ctx := with(*info.Context, TRACE, "ydb", "query", "do", "tx") + l.Log(ctx, "start") + start := time.Now() + + return func(info trace.QueryDoTxDoneInfo) { + if info.Error == nil { + l.Log(ctx, "done", + latencyField(start), + Int("attempts", info.Attempts), + ) + } else { + lvl := ERROR + if !xerrors.IsYdb(info.Error) { + lvl = DEBUG + } + l.Log(WithLevel(ctx, lvl), "failed", + latencyField(start), + Error(info.Error), + Int("attempts", info.Attempts), + versionField(), + ) + } + } + }, + OnSessionCreate: func(info trace.QuerySessionCreateStartInfo) func(info trace.QuerySessionCreateDoneInfo) { + if d.Details()&trace.QuerySessionEvents == 0 { + return nil + } + ctx := with(*info.Context, TRACE, "ydb", "query", "session", "create") + l.Log(ctx, "start") + start := time.Now() + + return func(info trace.QuerySessionCreateDoneInfo) { + if info.Error == nil { + l.Log(ctx, "done", + latencyField(start), + String("session_id", info.Session.ID()), + String("session_status", info.Session.Status()), + ) + } else { + lvl := WARN + if !xerrors.IsYdb(info.Error) { + lvl = DEBUG + } + l.Log(WithLevel(ctx, lvl), "done", + latencyField(start), + Error(info.Error), + versionField(), + ) + } + } + }, + OnSessionAttach: func(info trace.QuerySessionAttachStartInfo) func(info trace.QuerySessionAttachDoneInfo) { + if d.Details()&trace.QuerySessionEvents == 0 { + return nil + } + ctx := with(*info.Context, TRACE, "ydb", "query", "session", "attach") + l.Log(ctx, "start", + String("session_id", info.Session.ID()), + String("session_status", info.Session.Status()), + ) + start := time.Now() + + return func(info trace.QuerySessionAttachDoneInfo) { + if info.Error == nil { + l.Log(ctx, "done", + latencyField(start), + ) + } else { + lvl := WARN + if !xerrors.IsYdb(info.Error) { + lvl = DEBUG + } + l.Log(WithLevel(ctx, lvl), "failed", + latencyField(start), + Error(info.Error), + versionField(), + ) + } + } + }, + OnSessionDelete: func(info trace.QuerySessionDeleteStartInfo) func(info trace.QuerySessionDeleteDoneInfo) { + if d.Details()&trace.QuerySessionEvents == 0 { + return nil + } + ctx := with(*info.Context, TRACE, "ydb", "query", "session", "delete") + l.Log(ctx, "start", + String("session_id", info.Session.ID()), + String("session_status", info.Session.Status()), + ) + start := time.Now() + + return func(info trace.QuerySessionDeleteDoneInfo) { + if info.Error == nil { + l.Log(ctx, "done", + latencyField(start), + ) + } else { + lvl := WARN + if !xerrors.IsYdb(info.Error) { + lvl = DEBUG + } + l.Log(WithLevel(ctx, lvl), "failed", + latencyField(start), + Error(info.Error), + versionField(), + ) + } + } + }, + OnSessionExecute: func(info trace.QuerySessionExecuteStartInfo) func(info trace.QuerySessionExecuteDoneInfo) { + if d.Details()&trace.QuerySessionEvents == 0 { + return nil + } + ctx := with(*info.Context, TRACE, "ydb", "query", "session", "execute") + l.Log(ctx, "start", + String("SessionID", info.Session.ID()), + String("SessionStatus", info.Session.Status()), + String("Query", info.Query), + ) + start := time.Now() + + return func(info trace.QuerySessionExecuteDoneInfo) { + if info.Error == nil { + l.Log(ctx, "done", + latencyField(start), + ) + } else { + lvl := WARN + if !xerrors.IsYdb(info.Error) { + lvl = DEBUG + } + l.Log(WithLevel(ctx, lvl), "failed", + latencyField(start), + Error(info.Error), + versionField(), + ) + } + } + }, + OnSessionBegin: func(info trace.QuerySessionBeginStartInfo) func(info trace.QuerySessionBeginDoneInfo) { + if d.Details()&trace.QuerySessionEvents == 0 { + return nil + } + ctx := with(*info.Context, TRACE, "ydb", "query", "session", "begin") + l.Log(ctx, "start", + String("SessionID", info.Session.ID()), + String("SessionStatus", info.Session.Status()), + ) + start := time.Now() + + return func(info trace.QuerySessionBeginDoneInfo) { + if info.Error == nil { + l.Log(WithLevel(ctx, DEBUG), "done", + latencyField(start), + String("TransactionID", info.Tx.ID()), + ) + } else { + lvl := WARN + if !xerrors.IsYdb(info.Error) { + lvl = DEBUG + } + l.Log(WithLevel(ctx, lvl), "failed", + latencyField(start), + Error(info.Error), + versionField(), + ) + } + } + }, + OnTxExecute: func(info trace.QueryTxExecuteStartInfo) func(info trace.QueryTxExecuteDoneInfo) { + if d.Details()&trace.QueryTransactionEvents == 0 { + return nil + } + ctx := with(*info.Context, TRACE, "ydb", "query", "transaction", "execute") + l.Log(ctx, "start", + String("SessionID", info.Session.ID()), + String("TransactionID", info.Tx.ID()), + String("SessionStatus", info.Session.Status()), + ) + start := time.Now() + + return func(info trace.QueryTxExecuteDoneInfo) { + if info.Error == nil { + l.Log(WithLevel(ctx, DEBUG), "done", + latencyField(start), + ) + } else { + lvl := WARN + if !xerrors.IsYdb(info.Error) { + lvl = DEBUG + } + l.Log(WithLevel(ctx, lvl), "failed", + latencyField(start), + Error(info.Error), + versionField(), + ) + } + } + }, + OnResultNew: func(info trace.QueryResultNewStartInfo) func(info trace.QueryResultNewDoneInfo) { + if d.Details()&trace.QueryResultEvents == 0 { + return nil + } + ctx := with(*info.Context, TRACE, "ydb", "query", "result", "new") + l.Log(ctx, "start") + start := time.Now() + + return func(info trace.QueryResultNewDoneInfo) { + if info.Error == nil { + l.Log(ctx, "done", + latencyField(start), + ) + } else { + lvl := WARN + if !xerrors.IsYdb(info.Error) { + lvl = DEBUG + } + l.Log(WithLevel(ctx, lvl), "failed", + latencyField(start), + Error(info.Error), + versionField(), + ) + } + } + }, + OnResultNextPart: func(info trace.QueryResultNextPartStartInfo) func(info trace.QueryResultNextPartDoneInfo) { + if d.Details()&trace.QueryResultEvents == 0 { + return nil + } + ctx := with(*info.Context, TRACE, "ydb", "query", "result", "next", "part") + l.Log(ctx, "start") + start := time.Now() + + return func(info trace.QueryResultNextPartDoneInfo) { + if info.Error == nil { + l.Log(ctx, "done", + latencyField(start), + ) + } else { + lvl := WARN + if !xerrors.IsYdb(info.Error) { + lvl = DEBUG + } + l.Log(WithLevel(ctx, lvl), "failed", + latencyField(start), + Error(info.Error), + versionField(), + ) + } + } + }, + OnResultNextResultSet: func( + info trace.QueryResultNextResultSetStartInfo, + ) func( + info trace.QueryResultNextResultSetDoneInfo, + ) { + if d.Details()&trace.QueryResultEvents == 0 { + return nil + } + ctx := with(*info.Context, TRACE, "ydb", "query", "result", "next", "result", "set") + l.Log(ctx, "start") + start := time.Now() + + return func(info trace.QueryResultNextResultSetDoneInfo) { + if info.Error == nil { + l.Log(ctx, "done", + latencyField(start), + ) + } else { + lvl := WARN + if !xerrors.IsYdb(info.Error) { + lvl = DEBUG + } + l.Log(WithLevel(ctx, lvl), "failed", + latencyField(start), + Error(info.Error), + versionField(), + ) + } + } + }, + OnResultClose: func(info trace.QueryResultCloseStartInfo) func(info trace.QueryResultCloseDoneInfo) { + if d.Details()&trace.QueryResultEvents == 0 { + return nil + } + ctx := with(*info.Context, TRACE, "ydb", "query", "result", "close") + l.Log(ctx, "start") + start := time.Now() + + return func(info trace.QueryResultCloseDoneInfo) { + if info.Error == nil { + l.Log(ctx, "done", + latencyField(start), + ) + } else { + lvl := WARN + if !xerrors.IsYdb(info.Error) { + lvl = DEBUG + } + l.Log(WithLevel(ctx, lvl), "failed", + latencyField(start), + Error(info.Error), + versionField(), + ) + } + } + }, + OnResultSetNextRow: func(info trace.QueryResultSetNextRowStartInfo) func(info trace.QueryResultSetNextRowDoneInfo) { + if d.Details()&trace.QueryResultEvents == 0 { + return nil + } + ctx := with(*info.Context, TRACE, "ydb", "query", "result", "set", "next", "row") + l.Log(ctx, "start") + start := time.Now() + + return func(info trace.QueryResultSetNextRowDoneInfo) { + if info.Error == nil { + l.Log(ctx, "done", + latencyField(start), + ) + } else { + lvl := WARN + if !xerrors.IsYdb(info.Error) { + lvl = DEBUG + } + l.Log(WithLevel(ctx, lvl), "failed", + latencyField(start), + Error(info.Error), + versionField(), + ) + } + } + }, + OnRowScan: func(info trace.QueryRowScanStartInfo) func(info trace.QueryRowScanDoneInfo) { + if d.Details()&trace.QueryResultEvents == 0 { + return nil + } + ctx := with(*info.Context, TRACE, "ydb", "query", "row", "scan") + l.Log(ctx, "start") + start := time.Now() + + return func(info trace.QueryRowScanDoneInfo) { + if info.Error == nil { + l.Log(ctx, "done", + latencyField(start), + ) + } else { + lvl := WARN + if !xerrors.IsYdb(info.Error) { + lvl = DEBUG + } + l.Log(WithLevel(ctx, lvl), "failed", + latencyField(start), + Error(info.Error), + versionField(), + ) + } + } + }, + OnRowScanNamed: func(info trace.QueryRowScanNamedStartInfo) func(info trace.QueryRowScanNamedDoneInfo) { + if d.Details()&trace.QueryResultEvents == 0 { + return nil + } + ctx := with(*info.Context, TRACE, "ydb", "query", "row", "scan", "named") + l.Log(ctx, "start") + start := time.Now() + + return func(info trace.QueryRowScanNamedDoneInfo) { + if info.Error == nil { + l.Log(ctx, "done", + latencyField(start), + ) + } else { + lvl := WARN + if !xerrors.IsYdb(info.Error) { + lvl = DEBUG + } + l.Log(WithLevel(ctx, lvl), "failed", + latencyField(start), + Error(info.Error), + versionField(), + ) + } + } + }, + OnRowScanStruct: func(info trace.QueryRowScanStructStartInfo) func(info trace.QueryRowScanStructDoneInfo) { + if d.Details()&trace.QueryResultEvents == 0 { + return nil + } + ctx := with(*info.Context, TRACE, "ydb", "query", "row", "scan", "struct") + l.Log(ctx, "start") + start := time.Now() + + return func(info trace.QueryRowScanStructDoneInfo) { + if info.Error == nil { + l.Log(ctx, "done", + latencyField(start), + ) + } else { + lvl := WARN + if !xerrors.IsYdb(info.Error) { + lvl = DEBUG + } + l.Log(WithLevel(ctx, lvl), "failed", + latencyField(start), + Error(info.Error), + versionField(), + ) + } + } + }, + } +} 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/context.go b/meta/context.go index b2f95b5be..5ea200db6 100644 --- a/meta/context.go +++ b/meta/context.go @@ -14,8 +14,15 @@ func WithTraceID(ctx context.Context, traceID string) context.Context { } // WithUserAgent returns a copy of parent context with custom user-agent info -func WithUserAgent(ctx context.Context, userAgent string) context.Context { - return meta.WithUserAgent(ctx, userAgent) +// +// Deprecated: use WithApplicationName instead +func WithUserAgent(ctx context.Context, _ string) context.Context { + return ctx +} + +// WithApplicationName returns a copy of parent context with application name +func WithApplicationName(ctx context.Context, applicationName string) context.Context { + return meta.WithApplicationName(ctx, applicationName) } // WithRequestType returns a copy of parent context with custom request type @@ -25,7 +32,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 { - return meta.WithAllowFeatures(ctx, features) + return meta.WithAllowFeatures(ctx, features...) } // WithTrailerCallback attaches callback to context for listening incoming metadata diff --git a/metrics/driver.go b/metrics/driver.go index f20e7ae09..5b2f12fbc 100644 --- a/metrics/driver.go +++ b/metrics/driver.go @@ -47,8 +47,6 @@ func driver(config Config) (t trace.Driver) { } } t.OnConnNewStream = func(info trace.DriverConnNewStreamStartInfo) func( - trace.DriverConnNewStreamRecvInfo, - ) func( trace.DriverConnNewStreamDoneInfo, ) { var ( @@ -57,16 +55,14 @@ func driver(config Config) (t trace.Driver) { nodeID = info.Endpoint.NodeID() ) - return func(info trace.DriverConnNewStreamRecvInfo) func(trace.DriverConnNewStreamDoneInfo) { - return func(info trace.DriverConnNewStreamDoneInfo) { - if config.Details()&trace.DriverConnEvents != 0 { - requests.With(map[string]string{ - "status": errorBrief(info.Error), - "method": string(method), - "endpoint": endpoint, - "node_id": strconv.FormatUint(uint64(nodeID), 10), - }).Inc() - } + return func(info trace.DriverConnNewStreamDoneInfo) { + if config.Details()&trace.DriverConnEvents != 0 { + requests.With(map[string]string{ + "status": errorBrief(info.Error), + "method": string(method), + "endpoint": endpoint, + "node_id": strconv.FormatUint(uint64(nodeID), 10), + }).Inc() } } } diff --git a/metrics/query.go b/metrics/query.go new file mode 100644 index 000000000..2d48cfd52 --- /dev/null +++ b/metrics/query.go @@ -0,0 +1,210 @@ +package metrics + +import ( + "time" + + "github.com/ydb-platform/ydb-go-sdk/v3/trace" +) + +func query(config Config) (t trace.Query) { + queryConfig := config.WithSystem("query") + { + poolConfig := queryConfig.WithSystem("pool") + { + withConfig := poolConfig.WithSystem("with") + errs := withConfig.CounterVec("errs", "status") + latency := withConfig.TimerVec("latency") + attempts := withConfig.HistogramVec("attempts", []float64{0, 1, 2, 3, 4, 5, 7, 10}) + t.OnPoolWith = func( + info trace.QueryPoolWithStartInfo, + ) func( + info trace.QueryPoolWithDoneInfo, + ) { + if withConfig.Details()&trace.QueryPoolEvents == 0 { + return nil + } + + start := time.Now() + + return func(info trace.QueryPoolWithDoneInfo) { + attempts.With(nil).Record(float64(info.Attempts)) + if info.Error != nil { + errs.With(map[string]string{ + "status": errorBrief(info.Error), + }).Inc() + } + latency.With(nil).Record(time.Since(start)) + } + } + } + { + sizeConfig := poolConfig.WithSystem("size") + limit := sizeConfig.GaugeVec("limit") + idle := sizeConfig.GaugeVec("idle") + index := sizeConfig.GaugeVec("index") + inUse := sizeConfig.WithSystem("in").GaugeVec("use") + + t.OnPoolChange = func(stats trace.QueryPoolChange) { + if sizeConfig.Details()&trace.QueryPoolEvents == 0 { + return + } + + limit.With(nil).Set(float64(stats.Limit)) + idle.With(nil).Set(float64(stats.Idle)) + inUse.With(nil).Set(float64(stats.InUse)) + index.With(nil).Set(float64(stats.Index)) + } + } + } + { + doConfig := queryConfig.WithSystem("do") + { + errs := doConfig.CounterVec("errs", "status") + attempts := doConfig.HistogramVec("attempts", []float64{0, 1, 2, 3, 4, 5, 7, 10}) + latency := doConfig.TimerVec("latency") + t.OnDo = func( + info trace.QueryDoStartInfo, + ) func( + trace.QueryDoDoneInfo, + ) { + start := time.Now() + + return func(info trace.QueryDoDoneInfo) { + if doConfig.Details()&trace.QueryEvents != 0 { + errs.With(map[string]string{ + "status": errorBrief(info.Error), + }).Inc() + attempts.With(nil).Record(float64(info.Attempts)) + latency.With(nil).Record(time.Since(start)) + } + } + } + } + { + doTxConfig := doConfig.WithSystem("tx") + errs := doTxConfig.CounterVec("errs", "status") + attempts := doTxConfig.HistogramVec("attempts", []float64{0, 1, 2, 3, 4, 5, 7, 10}) + latency := doTxConfig.TimerVec("latency") + t.OnDoTx = func( + info trace.QueryDoTxStartInfo, + ) func( + trace.QueryDoTxDoneInfo, + ) { + start := time.Now() + + return func(info trace.QueryDoTxDoneInfo) { + if doTxConfig.Details()&trace.QueryEvents != 0 { + attempts.With(nil).Record(float64(info.Attempts)) + errs.With(map[string]string{ + "status": errorBrief(info.Error), + }).Inc() + latency.With(nil).Record(time.Since(start)) + } + } + } + } + } + { + sessionConfig := queryConfig.WithSystem("session") + count := sessionConfig.GaugeVec("count") + { + createConfig := sessionConfig.WithSystem("create") + errs := createConfig.CounterVec("errs", "status") + latency := createConfig.TimerVec("latency") + t.OnSessionCreate = func( + info trace.QuerySessionCreateStartInfo, + ) func( + info trace.QuerySessionCreateDoneInfo, + ) { + start := time.Now() + + return func(info trace.QuerySessionCreateDoneInfo) { + if createConfig.Details()&trace.QuerySessionEvents != 0 { + if info.Error == nil { + count.With(nil).Add(1) + } + errs.With(map[string]string{ + "status": errorBrief(info.Error), + }).Inc() + } + latency.With(nil).Record(time.Since(start)) + } + } + } + { + deleteConfig := sessionConfig.WithSystem("delete") + errs := deleteConfig.CounterVec("errs", "status") + latency := deleteConfig.TimerVec("latency") + t.OnSessionDelete = func(info trace.QuerySessionDeleteStartInfo) func(info trace.QuerySessionDeleteDoneInfo) { + count.With(nil).Add(-1) + + start := time.Now() + + return func(info trace.QuerySessionDeleteDoneInfo) { + if deleteConfig.Details()&trace.QuerySessionEvents != 0 { + errs.With(map[string]string{ + "status": errorBrief(info.Error), + }).Inc() + latency.With(nil).Record(time.Since(start)) + } + } + } + } + { + executeConfig := sessionConfig.WithSystem("execute") + errs := executeConfig.CounterVec("errs", "status") + latency := executeConfig.TimerVec("latency") + t.OnSessionExecute = func(info trace.QuerySessionExecuteStartInfo) func(info trace.QuerySessionExecuteDoneInfo) { + start := time.Now() + + return func(info trace.QuerySessionExecuteDoneInfo) { + if executeConfig.Details()&trace.QuerySessionEvents != 0 { + errs.With(map[string]string{ + "status": errorBrief(info.Error), + }).Inc() + latency.With(nil).Record(time.Since(start)) + } + } + } + } + { + beginConfig := sessionConfig.WithSystem("begin") + errs := beginConfig.CounterVec("errs", "status") + latency := beginConfig.TimerVec("latency") + t.OnSessionBegin = func(info trace.QuerySessionBeginStartInfo) func(info trace.QuerySessionBeginDoneInfo) { + start := time.Now() + + return func(info trace.QuerySessionBeginDoneInfo) { + if beginConfig.Details()&trace.QuerySessionEvents != 0 { + errs.With(map[string]string{ + "status": errorBrief(info.Error), + }).Inc() + latency.With(nil).Record(time.Since(start)) + } + } + } + } + } + { + txConfig := queryConfig.WithSystem("tx") + { + executeConfig := txConfig.WithSystem("execute") + errs := executeConfig.CounterVec("errs", "status") + latency := executeConfig.TimerVec("latency") + t.OnTxExecute = func(info trace.QueryTxExecuteStartInfo) func(info trace.QueryTxExecuteDoneInfo) { + start := time.Now() + + return func(info trace.QueryTxExecuteDoneInfo) { + if executeConfig.Details()&trace.QuerySessionEvents != 0 { + errs.With(map[string]string{ + "status": errorBrief(info.Error), + }).Inc() + latency.With(nil).Record(time.Since(start)) + } + } + } + } + } + + return t +} 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/traces.go b/metrics/traces.go index 1feaab9a3..7744ebbcb 100644 --- a/metrics/traces.go +++ b/metrics/traces.go @@ -13,6 +13,7 @@ func WithTraces(config Config) ydb.Option { return ydb.MergeOptions( ydb.WithTraceDriver(driver(config)), ydb.WithTraceTable(table(config)), + ydb.WithTraceQuery(query(config)), ydb.WithTraceScripting(scripting(config)), ydb.WithTraceScheme(scheme(config)), ydb.WithTraceCoordination(coordination(config)), diff --git a/options.go b/options.go index 41c995f97..23d0ef1ee 100644 --- a/options.go +++ b/options.go @@ -17,6 +17,7 @@ import ( coordinationConfig "github.com/ydb-platform/ydb-go-sdk/v3/internal/coordination/config" discoveryConfig "github.com/ydb-platform/ydb-go-sdk/v3/internal/discovery/config" "github.com/ydb-platform/ydb-go-sdk/v3/internal/dsn" + queryConfig "github.com/ydb-platform/ydb-go-sdk/v3/internal/query/config" ratelimiterConfig "github.com/ydb-platform/ydb-go-sdk/v3/internal/ratelimiter/config" schemeConfig "github.com/ydb-platform/ydb-go-sdk/v3/internal/scheme/config" scriptingConfig "github.com/ydb-platform/ydb-go-sdk/v3/internal/scripting/config" @@ -53,10 +54,21 @@ func WithAccessTokenCredentials(accessToken string) Option { ) } +// WithApplicationName add provided application name to all api requests +func WithApplicationName(applicationName string) Option { + return func(ctx context.Context, c *Driver) error { + c.options = append(c.options, config.WithApplicationName(applicationName)) + + return nil + } +} + // WithUserAgent add provided user agent value to all api requests +// +// Deprecated: use WithApplicationName instead func WithUserAgent(userAgent string) Option { return func(ctx context.Context, c *Driver) error { - c.options = append(c.options, config.WithUserAgent(userAgent)) + c.options = append(c.options, config.WithApplicationName(userAgent)) return nil } @@ -246,9 +258,9 @@ func With(options ...config.Option) Option { // MergeOptions concatentaes provided options to one cumulative value. func MergeOptions(opts ...Option) Option { return func(ctx context.Context, c *Driver) error { - for _, o := range opts { - if o != nil { - if err := o(ctx, c); err != nil { + for _, opt := range opts { + if opt != nil { + if err := opt(ctx, c); err != nil { return xerrors.WithStackTrace(err) } } @@ -367,22 +379,26 @@ func WithTableConfigOption(option tableConfig.Option) Option { } } +// WithQueryConfigOption collects additional configuration options for query.Client. +// This option does not replace collected option, instead it will appen provided options. +func WithQueryConfigOption(option queryConfig.Option) Option { + return func(ctx context.Context, c *Driver) error { + c.queryOptions = append(c.queryOptions, option) + + return nil + } +} + // WithSessionPoolSizeLimit set max size of internal sessions pool in table.Client func WithSessionPoolSizeLimit(sizeLimit int) Option { return func(ctx context.Context, c *Driver) error { c.tableOptions = append(c.tableOptions, tableConfig.WithSizeLimit(sizeLimit)) + c.queryOptions = append(c.queryOptions, queryConfig.WithPoolLimit(sizeLimit)) return nil } } -// WithSessionPoolKeepAliveMinSize set minimum sessions should be keeped alive in table.Client -// -// Deprecated: table client do not supports background session keep-aliving now -func WithSessionPoolKeepAliveMinSize(keepAliveMinSize int) Option { - return func(ctx context.Context, c *Driver) error { return nil } -} - // WithSessionPoolIdleThreshold defines interval for idle sessions func WithSessionPoolIdleThreshold(idleThreshold time.Duration) Option { return func(ctx context.Context, c *Driver) error { @@ -396,15 +412,11 @@ func WithSessionPoolIdleThreshold(idleThreshold time.Duration) Option { } } -// WithSessionPoolKeepAliveTimeout set timeout of keep alive requests for session in table.Client -func WithSessionPoolKeepAliveTimeout(keepAliveTimeout time.Duration) Option { - return func(ctx context.Context, c *Driver) error { return nil } -} - // WithSessionPoolCreateSessionTimeout set timeout for new session creation process in table.Client func WithSessionPoolCreateSessionTimeout(createSessionTimeout time.Duration) Option { return func(ctx context.Context, c *Driver) error { c.tableOptions = append(c.tableOptions, tableConfig.WithCreateSessionTimeout(createSessionTimeout)) + c.queryOptions = append(c.queryOptions, queryConfig.WithSessionCreateTimeout(createSessionTimeout)) return nil } @@ -414,11 +426,26 @@ func WithSessionPoolCreateSessionTimeout(createSessionTimeout time.Duration) Opt func WithSessionPoolDeleteTimeout(deleteTimeout time.Duration) Option { return func(ctx context.Context, c *Driver) error { c.tableOptions = append(c.tableOptions, tableConfig.WithDeleteTimeout(deleteTimeout)) + c.queryOptions = append(c.queryOptions, queryConfig.WithSessionDeleteTimeout(deleteTimeout)) return nil } } +// WithSessionPoolKeepAliveMinSize set minimum sessions should be keeped alive in table.Client +// +// Deprecated: table client do not support background session keep-aliving now +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: table client do not support background session keep-aliving now +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 { @@ -461,6 +488,25 @@ func WithTraceTable(t trace.Table, opts ...trace.TableComposeOption) Option { // } } +// WithTraceQuery appends trace.Query into query traces +func WithTraceQuery(t trace.Query, opts ...trace.QueryComposeOption) Option { //nolint:gocritic + return func(ctx context.Context, c *Driver) error { + c.queryOptions = append( + c.queryOptions, + queryConfig.WithTrace(&t, + append( + []trace.QueryComposeOption{ + trace.WithQueryPanicCallback(c.panicCallback), + }, + opts..., + )..., + ), + ) + + return nil + } +} + // WithTraceScripting scripting trace option func WithTraceScripting(t trace.Scripting, opts ...trace.ScriptingComposeOption) Option { return func(ctx context.Context, c *Driver) error { @@ -502,12 +548,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 new file mode 100644 index 000000000..e9e3f5dfb --- /dev/null +++ b/params_builder.go @@ -0,0 +1,12 @@ +package ydb + +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. +func ParamsBuilder() params.Builder { + return params.Builder{} +} diff --git a/query/client.go b/query/client.go new file mode 100644 index 000000000..87fedc52b --- /dev/null +++ b/query/client.go @@ -0,0 +1,68 @@ +package query + +import ( + "context" + + "github.com/ydb-platform/ydb-go-sdk/v3/internal/closer" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/query/options" + "github.com/ydb-platform/ydb-go-sdk/v3/trace" +) + +type Client interface { + // Do provide the best effort for execute operation. + // + // Do implements internal busy loop until one of the following conditions is met: + // - deadline was canceled or deadlined + // - retry operation returned nil as error + // + // Warning: if context without deadline or cancellation func than Do can run indefinitely. + Do(ctx context.Context, op Operation, opts ...options.DoOption) error + + // DoTx provide the best effort for execute transaction. + // + // DoTx implements internal busy loop until one of the following conditions is met: + // - deadline was canceled or deadlined + // - retry operation returned nil as error + // + // DoTx makes auto selector (with TransactionSettings, by default - SerializableReadWrite), commit and + // rollback (on error) of transaction. + // + // If op TxOperation returns nil - transaction will be committed + // If op TxOperation return non nil - transaction will be rollback + // Warning: if context without deadline or cancellation func than DoTx can run indefinitely + DoTx(ctx context.Context, op TxOperation, opts ...options.DoTxOption) error +} + +type ( + // Operation is the interface that holds an operation for retry. + // if Operation returns not nil - operation will retry + // if Operation returns nil - retry loop will break + Operation func(ctx context.Context, s Session) error + + // TxOperation is the interface that holds an operation for retry. + // if TxOperation returns not nil - operation will retry + // if TxOperation returns nil - retry loop will break + TxOperation func(ctx context.Context, tx TxActor) error + + ClosableSession interface { + closer.Closer + + Session + } + bothDoAndDoTxOption interface { + options.DoOption + options.DoTxOption + } +) + +func WithIdempotent() bothDoAndDoTxOption { + return options.WithIdempotent() +} + +func WithTrace(t *trace.Query) bothDoAndDoTxOption { + return options.WithTrace(t) +} + +func WithLabel(lbl string) bothDoAndDoTxOption { + return options.WithLabel(lbl) +} diff --git a/query/example_test.go b/query/example_test.go new file mode 100644 index 000000000..c0dec4c30 --- /dev/null +++ b/query/example_test.go @@ -0,0 +1,198 @@ +package query_test + +import ( + "context" + "errors" + "fmt" + "io" + + "github.com/ydb-platform/ydb-go-sdk/v3" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/query/options" + "github.com/ydb-platform/ydb-go-sdk/v3/query" +) + +func Example_selectWithoutParameters() { + ctx := context.TODO() + db, err := ydb.Open(ctx, "grpc://localhost:2136/local") + if err != nil { + fmt.Printf("failed connect: %v", err) + + return + } + defer db.Close(ctx) // cleanup resources + var ( + id int32 // required value + myStr string // optional value + ) + // Do retry operation on errors with best effort + err = db.Query().Do(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, "my string" 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 + } + if err = row.Scan(&id, &myStr); 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 { + fmt.Printf("unexpected error: %v", err) + } + fmt.Printf("id=%v, myStr='%s'\n", id, myStr) +} + +func Example_selectWithParameters() { + ctx := context.TODO() + db, err := ydb.Open(ctx, "grpc://localhost:2136/local") + if err != nil { + fmt.Printf("failed connect: %v", err) + + return + } + defer db.Close(ctx) // cleanup resources + var ( + id int32 // required value + myStr string // optional value + ) + // Do retry operation on errors with best effort + err = db.Query().Do(ctx, // context manage exiting from Do + func(ctx context.Context, s query.Session) (err error) { // retry operation + _, res, err := s.Execute(ctx, + `SELECT CAST($id AS Uint64) AS id, CAST($myStr AS Text) AS myStr`, + options.WithParameters( + ydb.ParamsBuilder(). + Param("$id").Uint64(123). + Param("$myStr").Text("123"). + Build(), + ), + ) + 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 + } + if err = row.ScanNamed( + query.Named("id", &id), + query.Named("myStr", &myStr), + ); 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 + }, + options.WithIdempotent(), + ) + if err != nil { + fmt.Printf("unexpected error: %v", err) + } + fmt.Printf("id=%v, myStr='%s'\n", id, myStr) +} + +func Example_txSelect() { + ctx := context.TODO() + db, err := ydb.Open(ctx, "grpc://localhost:2136/local") + if err != nil { + fmt.Printf("failed connect: %v", err) + + return + } + defer db.Close(ctx) // cleanup resources + var ( + id int32 // required value + myStr string // optional value + ) + // Do retry operation on errors with best effort + err = db.Query().DoTx(ctx, // context manage exiting from Do + func(ctx context.Context, tx query.TxActor) (err error) { // retry operation + res, err := tx.Execute(ctx, + `SELECT 42 as id, "my string" 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 + } + if err = row.ScanNamed( + query.Named("id", &id), + query.Named("myStr", &myStr), + ); 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 + }, + options.WithIdempotent(), + options.WithTxSettings(query.TxSettings( + query.WithSnapshotReadOnly(), + )), + ) + if err != nil { + fmt.Printf("unexpected error: %v", err) + } + fmt.Printf("id=%v, myStr='%s'\n", id, myStr) +} diff --git a/query/result.go b/query/result.go new file mode 100644 index 000000000..3cef8e9ee --- /dev/null +++ b/query/result.go @@ -0,0 +1,41 @@ +package query + +import ( + "context" + + "github.com/ydb-platform/ydb-go-sdk/v3/internal/closer" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/query/scanner" +) + +type ( + Result interface { + closer.Closer + + NextResultSet(ctx context.Context) (ResultSet, error) + Err() error + } + ResultSet interface { + NextRow(ctx context.Context) (Row, error) + } + Row interface { + Scan(dst ...interface{}) error + ScanNamed(dst ...scanner.NamedDestination) error + ScanStruct(dst interface{}, opts ...scanner.ScanStructOption) error + } +) + +func Named(columnName string, destinationValueReference interface{}) (dst scanner.NamedDestination) { + return scanner.NamedRef(columnName, destinationValueReference) +} + +func WithScanStructTagName(name string) scanner.ScanStructOption { + return scanner.WithTagName(name) +} + +func WithScanStructAllowMissingColumnsFromSelect() scanner.ScanStructOption { + return scanner.WithAllowMissingColumnsFromSelect() +} + +func WithScanStructAllowMissingFieldsInStruct() scanner.ScanStructOption { + return scanner.WithAllowMissingFieldsInStruct() +} diff --git a/query/session.go b/query/session.go new file mode 100644 index 000000000..14da2f3d5 --- /dev/null +++ b/query/session.go @@ -0,0 +1,83 @@ +package query + +import ( + "context" + + "google.golang.org/grpc" + + "github.com/ydb-platform/ydb-go-sdk/v3/internal/params" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/query/options" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/query/tx" +) + +type ( + SessionInfo interface { + ID() string + NodeID() int64 + Status() string + } + + Session interface { + SessionInfo + + // Execute executes query. + // + // Execute used by default: + // - 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) + + Begin(ctx context.Context, txSettings TransactionSettings) (Transaction, error) + } +) + +const ( + SyntaxYQL = options.SyntaxYQL + SyntaxPostgreSQL = options.SyntaxPostgreSQL +) + +const ( + ExecModeParse = options.ExecModeParse + ExecModeValidate = options.ExecModeValidate + ExecModeExplain = options.ExecModeExplain + ExecModeExecute = options.ExecModeExecute +) + +const ( + StatsModeBasic = options.StatsModeBasic + StatsModeNone = options.StatsModeNone + StatsModeFull = options.StatsModeFull + StatsModeProfile = options.StatsModeProfile +) + +func WithParameters(parameters *params.Parameters) options.ParametersOption { + return options.WithParameters(parameters) +} + +func WithTxControl(txControl *tx.Control) options.TxControlOption { + return options.WithTxControl(txControl) +} + +func WithTxSettings(txSettings tx.Settings) options.DoTxOption { + return options.WithTxSettings(txSettings) +} + +func WithCommit() options.TxExecuteOption { + return options.WithCommit() +} + +func WithExecMode(mode options.ExecMode) options.ExecModeOption { + return options.WithExecMode(mode) +} + +func WithSyntax(syntax options.Syntax) options.SyntaxOption { + return options.WithSyntax(syntax) +} + +func WithStatsMode(mode options.StatsMode) options.StatsModeOption { + return options.WithStatsMode(mode) +} + +func WithCallOptions(opts ...grpc.CallOption) options.CallOptions { + return options.WithCallOptions(opts...) +} diff --git a/query/stats.go b/query/stats.go new file mode 100644 index 000000000..f93b8867c --- /dev/null +++ b/query/stats.go @@ -0,0 +1,18 @@ +package query + +import ( + "fmt" + + "github.com/ydb-platform/ydb-go-sdk/v3/internal/pool/stats" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors" +) + +func Stats(client Client) (*stats.Stats, error) { + if c, has := client.(interface { + Stats() *stats.Stats + }); has { + return c.Stats(), nil + } + + return nil, xerrors.WithStackTrace(fmt.Errorf("client %T not supported stats", client)) +} diff --git a/query/transaction.go b/query/transaction.go new file mode 100644 index 000000000..877c93296 --- /dev/null +++ b/query/transaction.go @@ -0,0 +1,125 @@ +package query + +import ( + "context" + + "github.com/ydb-platform/ydb-go-sdk/v3/internal/query/options" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/query/tx" +) + +type ( + TxIdentifier interface { + ID() string + } + TxActor interface { + TxIdentifier + + // Execute executes query. + // + // Execute used by default: + // - DefaultTxControl + // - flag WithKeepInCache(true) if params is not empty. + Execute(ctx context.Context, query string, opts ...options.TxExecuteOption) (r Result, err error) + } + Transaction interface { + TxActor + + CommitTx(ctx context.Context) (err error) + Rollback(ctx context.Context) (err error) + } + TransactionControl = tx.Control + TransactionSettings = tx.Settings +) + +// BeginTx returns selector transaction control option +func BeginTx(opts ...tx.Option) tx.ControlOption { + return tx.BeginTx(opts...) +} + +func WithTx(t tx.Identifier) tx.ControlOption { + return tx.WithTx(t) +} + +func WithTxID(txID string) tx.ControlOption { + return tx.WithTxID(txID) +} + +// CommitTx returns commit transaction control option +func CommitTx() tx.ControlOption { + return tx.CommitTx() +} + +// TxControl makes transaction control from given options +func TxControl(opts ...tx.ControlOption) *TransactionControl { + return tx.NewControl(opts...) +} + +func NoTx() *TransactionControl { + return nil +} + +// DefaultTxControl returns default transaction control with serializable read-write isolation mode and auto-commit +func DefaultTxControl() *TransactionControl { + return TxControl( + BeginTx(WithSerializableReadWrite()), + CommitTx(), + ) +} + +// SerializableReadWriteTxControl returns transaction control with serializable read-write isolation mode +func SerializableReadWriteTxControl(opts ...tx.ControlOption) *TransactionControl { + return tx.SerializableReadWriteTxControl(opts...) +} + +// OnlineReadOnlyTxControl returns online read-only transaction control +func OnlineReadOnlyTxControl(opts ...tx.OnlineReadOnlyOption) *TransactionControl { + return TxControl( + BeginTx(WithOnlineReadOnly(opts...)), + CommitTx(), // open transactions not supported for OnlineReadOnly + ) +} + +// StaleReadOnlyTxControl returns stale read-only transaction control +func StaleReadOnlyTxControl() *TransactionControl { + return TxControl( + BeginTx(WithStaleReadOnly()), + CommitTx(), // open transactions not supported for StaleReadOnly + ) +} + +// SnapshotReadOnlyTxControl returns snapshot read-only transaction control +func SnapshotReadOnlyTxControl() *TransactionControl { + return TxControl( + BeginTx(WithSnapshotReadOnly()), + CommitTx(), // open transactions not supported for StaleReadOnly + ) +} + +// TxSettings returns transaction settings +func TxSettings(opts ...tx.Option) TransactionSettings { + return opts +} + +func WithDefaultTxMode() tx.Option { + return tx.WithDefaultTxMode() +} + +func WithSerializableReadWrite() tx.Option { + return tx.WithSerializableReadWrite() +} + +func WithSnapshotReadOnly() tx.Option { + return tx.WithSnapshotReadOnly() +} + +func WithStaleReadOnly() tx.Option { + return tx.WithStaleReadOnly() +} + +func WithInconsistentReads() tx.OnlineReadOnlyOption { + return tx.WithInconsistentReads() +} + +func WithOnlineReadOnly(opts ...tx.OnlineReadOnlyOption) tx.Option { + return tx.WithOnlineReadOnly(opts...) +} diff --git a/query_bind_test.go b/query_bind_test.go index 24870c0c9..3ffa07c80 100644 --- a/query_bind_test.go +++ b/query_bind_test.go @@ -9,6 +9,7 @@ import ( "github.com/ydb-platform/ydb-go-sdk/v3" "github.com/ydb-platform/ydb-go-sdk/v3/internal/bind" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/params" "github.com/ydb-platform/ydb-go-sdk/v3/table" "github.com/ydb-platform/ydb-go-sdk/v3/table/types" "github.com/ydb-platform/ydb-go-sdk/v3/testutil" @@ -567,14 +568,14 @@ SELECT $param1, $param2`, }, } { t.Run("", func(t *testing.T) { - yql, params, err := tt.b.RewriteQuery(tt.sql, tt.args...) + yql, parameters, err := tt.b.RewriteQuery(tt.sql, tt.args...) if tt.err != nil { require.Error(t, err) require.ErrorIs(t, err, tt.err) } else { require.NoError(t, err) require.Equal(t, tt.yql, yql) - require.Equal(t, tt.params, params) + require.Equal(t, []*params.Parameter(*tt.params), parameters) } }) } 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 5a3617e58..1c97f877f 100644 --- a/retry/retry.go +++ b/retry/retry.go @@ -232,7 +232,7 @@ func WithPanicCallback(panicCallback func(e interface{})) panicCallbackOption { // 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, @@ -258,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, - options.label, options.call, options.label, options.idempotent, xcontext.IsNestedCall(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++ @@ -331,8 +331,6 @@ func Retry(ctx context.Context, op retryOperation, opts ...Option) (finalErr err } code = m.StatusCode() - - onIntermediate(err) } } } @@ -342,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 728c85e4e..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, ) } @@ -148,18 +148,20 @@ func TestRetryTransportDeadlineExceeded(t *testing.T) { grpcCodes.DeadlineExceeded, grpcCodes.Canceled, } { - counter := 0 - ctx, cancel := xcontext.WithTimeout(context.Background(), time.Hour) - err := Retry(ctx, func(ctx context.Context) error { - counter++ - if !(counter < cancelCounterValue) { - cancel() - } + t.Run(code.String(), func(t *testing.T) { + counter := 0 + ctx, cancel := xcontext.WithTimeout(context.Background(), time.Hour) + err := Retry(ctx, func(ctx context.Context) error { + counter++ + if !(counter < cancelCounterValue) { + cancel() + } - return xerrors.Transport(grpcStatus.Error(code, "")) - }, WithIdempotent(true)) - require.ErrorIs(t, err, context.Canceled) - require.Equal(t, cancelCounterValue, counter) + return xerrors.Transport(grpcStatus.Error(code, "")) + }, WithIdempotent(true)) + require.ErrorIs(t, err, context.Canceled) + require.Equal(t, cancelCounterValue, counter) + }) } } @@ -169,17 +171,19 @@ func TestRetryTransportCancelled(t *testing.T) { grpcCodes.DeadlineExceeded, grpcCodes.Canceled, } { - counter := 0 - ctx, cancel := xcontext.WithCancel(context.Background()) - err := Retry(ctx, func(ctx context.Context) error { - counter++ - if !(counter < cancelCounterValue) { - cancel() - } + t.Run(code.String(), func(t *testing.T) { + counter := 0 + ctx, cancel := xcontext.WithCancel(context.Background()) + err := Retry(ctx, func(ctx context.Context) error { + counter++ + if !(counter < cancelCounterValue) { + cancel() + } - return xerrors.Transport(grpcStatus.Error(code, "")) - }, WithIdempotent(true)) - require.ErrorIs(t, err, context.Canceled) - require.Equal(t, cancelCounterValue, counter) + return xerrors.Transport(grpcStatus.Error(code, "")) + }, WithIdempotent(true)) + require.ErrorIs(t, err, context.Canceled) + require.Equal(t, cancelCounterValue, counter) + }) } } diff --git a/retry/retryable_error.go b/retry/retryable_error.go index e0778c1df..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 @@ -29,9 +29,9 @@ func RetryableError(err error, opts ...retryableErrorOption) error { return xerrors.Retryable( err, func() (retryableErrorOptions []xerrors.RetryableErrorOption) { - for _, o := range opts { - if o != nil { - retryableErrorOptions = append(retryableErrorOptions, xerrors.RetryableErrorOption(o)) + for _, opt := range opts { + if opt != nil { + retryableErrorOptions = append(retryableErrorOptions, xerrors.RetryableErrorOption(opt)) } } diff --git a/retry/sql.go b/retry/sql.go index 115b685ad..affde98e2 100644 --- a/retry/sql.go +++ b/retry/sql.go @@ -42,7 +42,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 @@ -129,7 +129,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/scheme/scheme.go b/scheme/scheme.go index c7a3e9274..1aeac9777 100644 --- a/scheme/scheme.go +++ b/scheme/scheme.go @@ -110,11 +110,11 @@ func (e *Entry) IsTopic() bool { func (e *Entry) From(y *Ydb_Scheme.Entry) { *e = Entry{ - Name: y.Name, - Owner: y.Owner, - Type: entryType(y.Type), - Permissions: makePermissions(y.Permissions), - EffectivePermissions: makePermissions(y.EffectivePermissions), + Name: y.GetName(), + Owner: y.GetOwner(), + Type: entryType(y.GetType()), + Permissions: makePermissions(y.GetPermissions()), + EffectivePermissions: makePermissions(y.GetEffectivePermissions()), } } @@ -155,8 +155,8 @@ func makePermissions(src []*Ydb_Scheme.Permissions) (dst []Permissions) { func from(y *Ydb_Scheme.Permissions) (p Permissions) { return Permissions{ - Subject: y.Subject, - PermissionNames: y.PermissionNames, + Subject: y.GetSubject(), + PermissionNames: y.GetPermissionNames(), } } diff --git a/scripting/scripting.go b/scripting/scripting.go index dc31def50..048529fee 100644 --- a/scripting/scripting.go +++ b/scripting/scripting.go @@ -3,6 +3,7 @@ package scripting import ( "context" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/params" "github.com/ydb-platform/ydb-go-sdk/v3/table" "github.com/ydb-platform/ydb-go-sdk/v3/table/result" ) @@ -21,7 +22,7 @@ type Client interface { Execute( ctx context.Context, query string, - params *table.QueryParameters, + params *params.Parameters, ) (result.Result, error) Explain( ctx context.Context, @@ -31,6 +32,6 @@ type Client interface { StreamExecute( ctx context.Context, query string, - params *table.QueryParameters, + params *params.Parameters, ) (result.StreamResult, error) } diff --git a/sugar/params.go b/sugar/params.go index 24c7f897c..612268260 100644 --- a/sugar/params.go +++ b/sugar/params.go @@ -3,37 +3,32 @@ package sugar import ( "database/sql" "fmt" + "sort" "github.com/ydb-platform/ydb-go-sdk/v3/internal/bind" - internal "github.com/ydb-platform/ydb-go-sdk/v3/internal/table" + "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" ) +type constraint interface { + params.Parameters | []*params.Parameter | *table.QueryParameters | []table.ParameterOption | []sql.NamedArg +} + // GenerateDeclareSection generates DECLARE section text in YQL query by params // // Deprecated: use testutil.QueryBind(ydb.WithAutoDeclare()) helper -func GenerateDeclareSection[T *table.QueryParameters | []table.ParameterOption | []sql.NamedArg]( - params T, -) (string, error) { - switch v := any(params).(type) { - case *table.QueryParameters: - return internal.GenerateDeclareSection(v) +func GenerateDeclareSection[T constraint](parameters T) (string, error) { + switch v := any(parameters).(type) { + case *params.Parameters: + return parametersToDeclares(*v), nil + case []*params.Parameter: + return parametersToDeclares(v), nil case []table.ParameterOption: - return internal.GenerateDeclareSection(table.NewQueryParameters(v...)) + return parameterOptionsToDeclares(v), nil case []sql.NamedArg: - values, err := bind.Params(func() (newArgs []interface{}) { - for i := range v { - newArgs = append(newArgs, v[i]) - } - - return newArgs - }()...) - if err != nil { - return "", xerrors.WithStackTrace(err) - } - - return internal.GenerateDeclareSection(table.NewQueryParameters(values...)) + return namedArgsToDeclares(v) default: return "", xerrors.WithStackTrace(fmt.Errorf("unsupported type: %T", v)) } @@ -42,7 +37,7 @@ func GenerateDeclareSection[T *table.QueryParameters | []table.ParameterOption | // ToYdbParam converts // // Deprecated: use testutil/QueryBind helper -func ToYdbParam(param sql.NamedArg) (table.ParameterOption, error) { +func ToYdbParam(param sql.NamedArg) (*params.Parameter, error) { params, err := bind.Params(param) if err != nil { return nil, xerrors.WithStackTrace(err) @@ -53,3 +48,51 @@ func ToYdbParam(param sql.NamedArg) (table.ParameterOption, error) { return params[0], nil } + +func parametersToDeclares(v []*params.Parameter) string { + var ( + buf = xstring.Buffer() + names = make([]string, 0, len(v)) + declares = make(map[string]string, len(v)) + ) + defer buf.Free() + + for _, p := range v { + name := p.Name() + names = append(names, name) + declares[name] = params.Declare(p) + } + + sort.Strings(names) + + for _, name := range names { + buf.WriteString(declares[name]) + buf.WriteString(";\n") + } + + return buf.String() +} + +func parameterOptionsToDeclares(v []table.ParameterOption) string { + parameters := make([]*params.Parameter, len(v)) + for i, p := range v { + parameters[i] = params.Named(p.Name(), p.Value()) + } + + return parametersToDeclares(parameters) +} + +func namedArgsToDeclares(v []sql.NamedArg) (string, error) { + vv, err := bind.Params(func() (newArgs []interface{}) { + for i := range v { + newArgs = append(newArgs, v[i]) + } + + return newArgs + }()...) + if err != nil { + return "", xerrors.WithStackTrace(err) + } + + return parametersToDeclares(vv), nil +} diff --git a/sugar/result.go b/sugar/result.go new file mode 100644 index 000000000..33c2339d9 --- /dev/null +++ b/sugar/result.go @@ -0,0 +1,83 @@ +package sugar + +import ( + "context" + "errors" + "io" + + "github.com/ydb-platform/ydb-go-sdk/v3/internal/query/scanner" + "github.com/ydb-platform/ydb-go-sdk/v3/query" + "github.com/ydb-platform/ydb-go-sdk/v3/table/result/indexed" + "github.com/ydb-platform/ydb-go-sdk/v3/table/result/named" +) + +type result struct { + r query.Result + rs query.ResultSet + row query.Row +} + +func (r *result) NextResultSet(ctx context.Context) bool { + var err error + r.rs, err = r.r.NextResultSet(ctx) + if err != nil && errors.Is(err, io.EOF) { + return false + } + + return err == nil && r.rs != nil && r.r.Err() == nil +} + +func (r *result) NextRow() bool { + if r.rs == nil { + return false + } + + var err error + r.row, err = r.rs.NextRow(context.Background()) + if err != nil && errors.Is(err, io.EOF) { + return false + } + + return r.row != nil && r.r.Err() == nil +} + +func (r *result) Scan(indexedValues ...indexed.RequiredOrOptional) error { + values := make([]interface{}, 0, len(indexedValues)) + for _, value := range indexedValues { + values = append(values, value) + } + + return r.row.Scan(values...) +} + +func (r *result) ScanNamed(namedValues ...named.Value) error { + values := make([]scanner.NamedDestination, 0, len(namedValues)) + for i := range namedValues { + values = append(values, scanner.NamedRef(namedValues[i].Name, namedValues[i].Value)) + } + + return r.row.ScanNamed(values...) +} + +func (r *result) ScanStruct(dst interface{}) error { + return r.row.ScanStruct(dst) +} + +func (r *result) Err() error { + return r.r.Err() +} + +func (r *result) Close() error { + return r.r.Close(context.Background()) +} + +// 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. +func Result(r query.Result) *result { + return &result{ + r: r, + } +} diff --git a/table/options/models.go b/table/options/models.go index 1464defe4..4692d9d4a 100644 --- a/table/options/models.go +++ b/table/options/models.go @@ -9,8 +9,8 @@ import ( "github.com/ydb-platform/ydb-go-sdk/v3/internal/allocator" "github.com/ydb-platform/ydb-go-sdk/v3/internal/feature" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/types" "github.com/ydb-platform/ydb-go-sdk/v3/internal/value" - "github.com/ydb-platform/ydb-go-sdk/v3/table/types" ) type Column struct { @@ -22,7 +22,7 @@ type Column struct { func (c Column) toYDB(a *allocator.Allocator) *Ydb_Table.ColumnMeta { return &Ydb_Table.ColumnMeta{ Name: c.Name, - Type: value.TypeToYDB(c.Type, a), + Type: types.TypeToYDB(c.Type, a), Family: c.Family, } } @@ -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 @@ -406,8 +397,8 @@ type ( ) type KeyRange struct { - From types.Value - To types.Value + From value.Value + To value.Value } func (kr KeyRange) String() string { diff --git a/table/options/options.go b/table/options/options.go index df33cf0c8..db85556a7 100644 --- a/table/options/options.go +++ b/table/options/options.go @@ -6,8 +6,8 @@ import ( "google.golang.org/grpc" "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/value" - "github.com/ydb-platform/ydb-go-sdk/v3/table/types" ) func WithShardKeyBounds() DescribeTableOption { @@ -59,14 +59,14 @@ type column struct { func (c column) ApplyAlterTableOption(d *AlterTableDesc, a *allocator.Allocator) { d.AddColumns = append(d.AddColumns, &Ydb_Table.ColumnMeta{ Name: c.name, - Type: value.TypeToYDB(c.typ, a), + Type: types.TypeToYDB(c.typ, a), }) } func (c column) ApplyCreateTableOption(d *CreateTableDesc, a *allocator.Allocator) { d.Columns = append(d.Columns, &Ydb_Table.ColumnMeta{ Name: c.name, - Type: value.TypeToYDB(c.typ, a), + Type: types.TypeToYDB(c.typ, a), }) } @@ -161,7 +161,9 @@ func (i index) ApplyAlterTableOption(d *AlterTableDesc, a *allocator.Allocator) Name: i.name, } for _, opt := range i.opts { - opt.ApplyIndexOption((*indexDesc)(x)) + if opt != nil { + opt.ApplyIndexOption((*indexDesc)(x)) + } } d.AddIndexes = append(d.AddIndexes, x) } @@ -171,7 +173,9 @@ func (i index) ApplyCreateTableOption(d *CreateTableDesc, a *allocator.Allocator Name: i.name, } for _, opt := range i.opts { - opt.ApplyIndexOption((*indexDesc)(x)) + if opt != nil { + opt.ApplyIndexOption((*indexDesc)(x)) + } } d.Indexes = append(d.Indexes, x) } @@ -304,7 +308,7 @@ func WithUniformPartitions(n uint64) Partitions { return uniformPartitions(n) } -type explicitPartitions []types.Value +type explicitPartitions []value.Value func (e explicitPartitions) ApplyCreateTableOption(d *CreateTableDesc, a *allocator.Allocator) { values := make([]*Ydb.TypedValue, len(e)) @@ -320,7 +324,7 @@ func (e explicitPartitions) ApplyCreateTableOption(d *CreateTableDesc, a *alloca func (e explicitPartitions) isPartitions() {} -func WithExplicitPartitions(splitPoints ...types.Value) Partitions { +func WithExplicitPartitions(splitPoints ...value.Value) Partitions { return explicitPartitions(splitPoints) } @@ -524,7 +528,7 @@ func WithPartitioningPolicyUniformPartitions(n uint64) PartitioningPolicyOption } // Deprecated: use WithExplicitPartitions instead -func WithPartitioningPolicyExplicitPartitions(splitPoints ...types.Value) PartitioningPolicyOption { +func WithPartitioningPolicyExplicitPartitions(splitPoints ...value.Value) PartitioningPolicyOption { return func(p *partitioningPolicy, a *allocator.Allocator) { values := make([]*Ydb.TypedValue, len(splitPoints)) for i := range values { @@ -1036,10 +1040,10 @@ type ( readOrderedOption struct{} readSnapshotOption bool readKeyRangeOption KeyRange - readGreaterOrEqualOption struct{ types.Value } - readLessOrEqualOption struct{ types.Value } - readLessOption struct{ types.Value } - readGreaterOption struct{ types.Value } + readGreaterOrEqualOption struct{ value.Value } + readLessOrEqualOption struct{ value.Value } + readLessOption struct{ value.Value } + readGreaterOption struct{ value.Value } readRowLimitOption uint64 ) @@ -1133,19 +1137,19 @@ func ReadKeyRange(x KeyRange) ReadTableOption { return readKeyRangeOption(x) } -func ReadGreater(x types.Value) ReadTableOption { +func ReadGreater(x value.Value) ReadTableOption { return readGreaterOption{x} } -func ReadGreaterOrEqual(x types.Value) ReadTableOption { +func ReadGreaterOrEqual(x value.Value) ReadTableOption { return readGreaterOrEqualOption{x} } -func ReadLess(x types.Value) ReadTableOption { +func ReadLess(x value.Value) ReadTableOption { return readLessOption{x} } -func ReadLessOrEqual(x types.Value) ReadTableOption { +func ReadLessOrEqual(x value.Value) ReadTableOption { return readLessOrEqualOption{x} } diff --git a/table/options/options_test.go b/table/options/options_test.go index d702a8fb0..3b639ce6a 100644 --- a/table/options/options_test.go +++ b/table/options/options_test.go @@ -9,8 +9,8 @@ import ( "github.com/ydb-platform/ydb-go-sdk/v3/internal/allocator" "github.com/ydb-platform/ydb-go-sdk/v3/internal/feature" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/types" "github.com/ydb-platform/ydb-go-sdk/v3/internal/value" - "github.com/ydb-platform/ydb-go-sdk/v3/table/types" ) var abc = "abc" @@ -24,7 +24,7 @@ func TestSessionOptionsProfile(t *testing.T) { ) req := Ydb_Table.CreateTableRequest{} opt.ApplyCreateTableOption((*CreateTableDesc)(&req), a) - if req.Profile.PresetName != abc { + if req.GetProfile().GetPresetName() != abc { t.Errorf("Preset is not as expected") } } @@ -34,7 +34,7 @@ func TestSessionOptionsProfile(t *testing.T) { ) req := Ydb_Table.CreateTableRequest{} opt.ApplyCreateTableOption((*CreateTableDesc)(&req), a) - if req.Profile.CompactionPolicy.PresetName != abc { + if req.GetProfile().GetCompactionPolicy().GetPresetName() != abc { t.Errorf("Compaction policy is not as expected") } } @@ -47,8 +47,8 @@ func TestSessionOptionsProfile(t *testing.T) { ) req := Ydb_Table.CreateTableRequest{} opt.ApplyCreateTableOption((*CreateTableDesc)(&req), a) - p := req.Profile.PartitioningPolicy - if p.PresetName != abc || p.AutoPartitioning != Ydb_Table.PartitioningPolicy_AUTO_SPLIT { + p := req.GetProfile().GetPartitioningPolicy() + if p.GetPresetName() != abc || p.GetAutoPartitioning() != Ydb_Table.PartitioningPolicy_AUTO_SPLIT { t.Errorf("Partitioning policy is not as expected") } } @@ -58,26 +58,26 @@ func TestSessionOptionsProfile(t *testing.T) { ) req := Ydb_Table.CreateTableRequest{} opt.ApplyCreateTableOption((*CreateTableDesc)(&req), a) - if p, ok := req.Partitions.(*Ydb_Table.CreateTableRequest_UniformPartitions); !ok || p.UniformPartitions != 3 { + if p, ok := req.GetPartitions().(*Ydb_Table.CreateTableRequest_UniformPartitions); !ok || p.UniformPartitions != 3 { t.Errorf("Uniform partitioning policy is not as expected") } } { opt := WithPartitions( WithExplicitPartitions( - types.Int64Value(1), + value.Int64Value(1), ), ) req := Ydb_Table.CreateTableRequest{} opt.ApplyCreateTableOption((*CreateTableDesc)(&req), a) - p, ok := req.Partitions.(*Ydb_Table.CreateTableRequest_PartitionAtKeys) + p, ok := req.GetPartitions().(*Ydb_Table.CreateTableRequest_PartitionAtKeys) if !ok { t.Errorf("Explicitly partitioning policy is not as expected") } else { require.Equal( t, - []*Ydb.TypedValue{value.ToYDB(types.Int64Value(1), a)}, - p.PartitionAtKeys.SplitPoints, + []*Ydb.TypedValue{value.ToYDB(value.Int64Value(1), a)}, + p.PartitionAtKeys.GetSplitPoints(), ) } } @@ -87,7 +87,7 @@ func TestSessionOptionsProfile(t *testing.T) { ) req := Ydb_Table.CreateTableRequest{} opt.ApplyCreateTableOption((*CreateTableDesc)(&req), a) - if req.Profile.ExecutionPolicy.PresetName != abc { + if req.GetProfile().GetExecutionPolicy().GetPresetName() != abc { t.Errorf("Execution policy is not as expected") } } @@ -102,11 +102,11 @@ func TestSessionOptionsProfile(t *testing.T) { ) req := Ydb_Table.CreateTableRequest{} opt.ApplyCreateTableOption((*CreateTableDesc)(&req), a) - p := req.Profile.ReplicationPolicy - if p.PresetName != abc || - p.ReplicasCount != 3 || - p.CreatePerAvailabilityZone != Ydb.FeatureFlag_ENABLED || - p.AllowPromotion != Ydb.FeatureFlag_DISABLED { + p := req.GetProfile().GetReplicationPolicy() + if p.GetPresetName() != abc || + p.GetReplicasCount() != 3 || + p.GetCreatePerAvailabilityZone() != Ydb.FeatureFlag_ENABLED || + p.GetAllowPromotion() != Ydb.FeatureFlag_DISABLED { t.Errorf("Replication policy is not as expected") } } @@ -116,7 +116,7 @@ func TestSessionOptionsProfile(t *testing.T) { ) req := Ydb_Table.CreateTableRequest{} opt.ApplyCreateTableOption((*CreateTableDesc)(&req), a) - if req.Profile.CachingPolicy.PresetName != abc { + if req.GetProfile().GetCachingPolicy().GetPresetName() != abc { t.Errorf("Caching policy is not as expected") } } @@ -138,13 +138,13 @@ func TestStoragePolicyOptions(t *testing.T) { ) req := Ydb_Table.CreateTableRequest{} opt.ApplyCreateTableOption((*CreateTableDesc)(&req), a) - p := req.Profile.StoragePolicy - if p.PresetName != abc || - p.Syslog.Media != "any1" || - p.Log.Media != "any2" || - p.Data.Media != "any3" || - p.External.Media != "any4" || - p.KeepInMemory != Ydb.FeatureFlag_ENABLED { + p := req.GetProfile().GetStoragePolicy() + if p.GetPresetName() != abc || + p.GetSyslog().GetMedia() != "any1" || + p.GetLog().GetMedia() != "any2" || + p.GetData().GetMedia() != "any3" || + p.GetExternal().GetMedia() != "any4" || + p.GetKeepInMemory() != Ydb.FeatureFlag_ENABLED { t.Errorf("Storage policy is not as expected") } } @@ -154,27 +154,27 @@ func TestAlterTableOptions(t *testing.T) { a := allocator.New() defer a.Free() { - opt := WithAddColumn("a", types.TypeBool) + opt := WithAddColumn("a", types.Bool) req := Ydb_Table.AlterTableRequest{} opt.ApplyAlterTableOption((*AlterTableDesc)(&req), a) - if len(req.AddColumns) != 1 || - req.AddColumns[0].Name != "a" { + if len(req.GetAddColumns()) != 1 || + req.GetAddColumns()[0].GetName() != "a" { t.Errorf("Alter table options is not as expected") } } { column := Column{ Name: "a", - Type: types.TypeBool, + Type: types.Bool, Family: "b", } opt := WithAddColumnMeta(column) req := Ydb_Table.AlterTableRequest{} opt.ApplyAlterTableOption((*AlterTableDesc)(&req), a) - if len(req.AddColumns) != 1 || - req.AddColumns[0].Name != column.Name || - req.AddColumns[0].Type != value.TypeToYDB(column.Type, a) || - req.AddColumns[0].Family != column.Family { + if len(req.GetAddColumns()) != 1 || + req.GetAddColumns()[0].GetName() != column.Name || + req.GetAddColumns()[0].GetType() != types.TypeToYDB(column.Type, a) || + req.GetAddColumns()[0].GetFamily() != column.Family { t.Errorf("Alter table options is not as expected") } } @@ -182,8 +182,8 @@ func TestAlterTableOptions(t *testing.T) { opt := WithDropColumn("a") req := Ydb_Table.AlterTableRequest{} opt.ApplyAlterTableOption((*AlterTableDesc)(&req), a) - if len(req.DropColumns) != 1 || - req.DropColumns[0] != "a" { + if len(req.GetDropColumns()) != 1 || + req.GetDropColumns()[0] != "a" { t.Errorf("Alter table options is not as expected") } } @@ -199,11 +199,11 @@ func TestAlterTableOptions(t *testing.T) { opt := WithAlterColumnFamilies(cf) req := Ydb_Table.AlterTableRequest{} opt.ApplyAlterTableOption((*AlterTableDesc)(&req), a) - if len(req.AddColumnFamilies) != 1 || - req.AddColumnFamilies[0].Name != cf.Name || - req.AddColumnFamilies[0].Data.Media != cf.Data.Media || - req.AddColumnFamilies[0].Compression != cf.Compression.toYDB() || - req.AddColumnFamilies[0].KeepInMemory != cf.KeepInMemory.ToYDB() { + if len(req.GetAddColumnFamilies()) != 1 || + req.GetAddColumnFamilies()[0].GetName() != cf.Name || + req.GetAddColumnFamilies()[0].GetData().GetMedia() != cf.Data.Media || + req.GetAddColumnFamilies()[0].GetCompression() != cf.Compression.toYDB() || + req.GetAddColumnFamilies()[0].GetKeepInMemory() != cf.KeepInMemory.ToYDB() { t.Errorf("Alter table options is not as expected") } } @@ -215,11 +215,11 @@ func TestAlterTableOptions(t *testing.T) { opt := WithAlterColumnFamilies(cf) req := Ydb_Table.AlterTableRequest{} opt.ApplyAlterTableOption((*AlterTableDesc)(&req), a) - if len(req.AddColumnFamilies) != 1 || - req.AddColumnFamilies[0].Name != cf.Name || - req.AddColumnFamilies[0].Data != nil || - req.AddColumnFamilies[0].Compression != cf.Compression.toYDB() || - req.AddColumnFamilies[0].KeepInMemory != Ydb.FeatureFlag_STATUS_UNSPECIFIED { + if len(req.GetAddColumnFamilies()) != 1 || + req.GetAddColumnFamilies()[0].GetName() != cf.Name || + req.GetAddColumnFamilies()[0].GetData() != nil || + req.GetAddColumnFamilies()[0].GetCompression() != cf.Compression.toYDB() || + req.GetAddColumnFamilies()[0].GetKeepInMemory() != Ydb.FeatureFlag_STATUS_UNSPECIFIED { t.Errorf("Alter table options is not as expected") } } diff --git a/table/result/indexed/indexed.go b/table/result/indexed/indexed.go index f92b205c5..af8a637d6 100644 --- a/table/result/indexed/indexed.go +++ b/table/result/indexed/indexed.go @@ -27,7 +27,7 @@ type Optional interface{} // RequiredOrOptional is a type scan destination of ydb values // This is a proxy type for preparing go1.18 type set constrains such as // -// type Value interface { +// type valueType interface { // Required | Optional // } type RequiredOrOptional interface{} diff --git a/table/result/result.go b/table/result/result.go index c8514187d..e82860f3d 100644 --- a/table/result/result.go +++ b/table/result/result.go @@ -91,7 +91,7 @@ type BaseResult interface { // string // time.Time // time.Duration - // ydb.Value + // ydb.valueType // For custom types implement sql.Scanner or json.Unmarshaler interface. // For optional types use double pointer construction. // For unknown types use interface types. diff --git a/table/table.go b/table/table.go index d2d4b1001..6d2305a1e 100644 --- a/table/table.go +++ b/table/table.go @@ -2,20 +2,17 @@ package table import ( "context" - "sort" "time" - "github.com/ydb-platform/ydb-go-genproto/protos/Ydb" "github.com/ydb-platform/ydb-go-genproto/protos/Ydb_Table" - "github.com/ydb-platform/ydb-go-sdk/v3/internal/allocator" "github.com/ydb-platform/ydb-go-sdk/v3/internal/closer" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/params" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/types" "github.com/ydb-platform/ydb-go-sdk/v3/internal/value" - "github.com/ydb-platform/ydb-go-sdk/v3/internal/xstring" "github.com/ydb-platform/ydb-go-sdk/v3/retry" "github.com/ydb-platform/ydb-go-sdk/v3/table/options" "github.com/ydb-platform/ydb-go-sdk/v3/table/result" - "github.com/ydb-platform/ydb-go-sdk/v3/table/types" "github.com/ydb-platform/ydb-go-sdk/v3/trace" ) @@ -144,7 +141,7 @@ type Session interface { ctx context.Context, tx *TransactionControl, query string, - params *QueryParameters, + params *params.Parameters, opts ...options.ExecuteDataQueryOption, ) (txr Transaction, r result.Result, err error) @@ -167,21 +164,21 @@ type Session interface { StreamExecuteScanQuery( ctx context.Context, query string, - params *QueryParameters, + params *params.Parameters, opts ...options.ExecuteScanQueryOption, ) (_ result.StreamResult, err error) BulkUpsert( ctx context.Context, table string, - rows types.Value, + rows value.Value, opts ...options.BulkUpsertOption, ) (err error) ReadRows( ctx context.Context, path string, - keys types.Value, + keys value.Value, opts ...options.ReadRowsOption, ) (_ result.Result, err error) @@ -243,13 +240,13 @@ type TransactionActor interface { Execute( ctx context.Context, query string, - params *QueryParameters, + params *params.Parameters, opts ...options.ExecuteDataQueryOption, ) (result.Result, error) ExecuteStatement( ctx context.Context, stmt Statement, - params *QueryParameters, + params *params.Parameters, opts ...options.ExecuteDataQueryOption, ) (result.Result, error) } @@ -270,7 +267,7 @@ type Statement interface { Execute( ctx context.Context, tx *TransactionControl, - params *QueryParameters, + params *params.Parameters, opts ...options.ExecuteDataQueryOption, ) (txr Transaction, r result.Result, err error) NumInput() int @@ -455,113 +452,22 @@ func SnapshotReadOnlyTxControl() *TransactionControl { // QueryParameters type ( - queryParams map[string]types.Value - ParameterOption interface { - Name() string - Value() types.Value - } - parameterOption struct { - name string - value types.Value - } - QueryParameters struct { - m queryParams - } + ParameterOption = params.NamedValue + QueryParameters = params.Parameters ) -func (p parameterOption) Name() string { - return p.name -} - -func (p parameterOption) Value() types.Value { - return p.value -} - -func (qp queryParams) ToYDB(a *allocator.Allocator) map[string]*Ydb.TypedValue { - if qp == nil { - return nil - } - params := make(map[string]*Ydb.TypedValue, len(qp)) - for k, v := range qp { - params[k] = value.ToYDB(v, a) - } - - return params -} - -func (q *QueryParameters) Params() queryParams { - if q == nil { - return nil - } - - return q.m -} - -func (q *QueryParameters) Count() int { - if q == nil { - return 0 - } - - return len(q.m) -} - -func (q *QueryParameters) Each(it func(name string, v types.Value)) { - if q == nil { - return - } - for key, v := range q.m { - it(key, v) - } -} - -func (q *QueryParameters) names() []string { - if q == nil { - return nil - } - names := make([]string, 0, len(q.m)) - for k := range q.m { - names = append(names, k) - } - sort.Strings(names) - - return names -} - -func (q *QueryParameters) String() string { - buffer := xstring.Buffer() - defer buffer.Free() - - buffer.WriteByte('{') - for i, name := range q.names() { - if i != 0 { - buffer.WriteByte(',') - } - buffer.WriteByte('"') - buffer.WriteString(name) - buffer.WriteString("\":") - buffer.WriteString(q.m[name].Yql()) - } - buffer.WriteByte('}') - - return buffer.String() -} - func NewQueryParameters(opts ...ParameterOption) *QueryParameters { - q := &QueryParameters{ - m: make(queryParams, len(opts)), + qp := QueryParameters(make([]*params.Parameter, len(opts))) + for i, opt := range opts { + if opt != nil { + qp[i] = params.Named(opt.Name(), opt.Value()) + } } - q.Add(opts...) - return q + return &qp } -func (q *QueryParameters) Add(params ...ParameterOption) { - for _, param := range params { - q.m[param.Name()] = param.Value() - } -} - -func ValueParam(name string, v types.Value) ParameterOption { +func ValueParam(name string, v value.Value) ParameterOption { switch len(name) { case 0: panic("empty name") @@ -571,10 +477,7 @@ func ValueParam(name string, v types.Value) ParameterOption { } } - return ¶meterOption{ - name: name, - value: v, - } + return params.Named(name, v) } type Options struct { diff --git a/table/types/types.go b/table/types/types.go index f492e7b30..f6184943f 100644 --- a/table/types/types.go +++ b/table/types/types.go @@ -2,10 +2,9 @@ package types import ( "bytes" - "io" - "time" - "github.com/ydb-platform/ydb-go-sdk/v3/internal/value" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/scanner" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/types" ) const ( @@ -14,30 +13,30 @@ const ( ) // Type describes YDB data types. -type Type = value.Type +type Type = types.Type // Equal checks for type equivalence func Equal(lhs, rhs Type) bool { - return value.TypesEqual(lhs, rhs) + return types.Equal(lhs, rhs) } func List(t Type) Type { - return value.List(t) + return types.NewList(t) } func Tuple(elems ...Type) Type { - return value.Tuple(elems...) + return types.NewTuple(elems...) } type tStructType struct { - fields []value.StructField + fields []types.StructField } type StructOption func(*tStructType) func StructField(name string, t Type) StructOption { return func(s *tStructType) { - s.fields = append(s.fields, value.StructField{ + s.fields = append(s.fields, types.StructField{ Name: name, T: t, }) @@ -52,11 +51,11 @@ func Struct(opts ...StructOption) Type { } } - return value.Struct(s.fields...) + return types.NewStruct(s.fields...) } func Dict(k, v Type) Type { - return value.Dict(k, v) + return types.NewDict(k, v) } func VariantStruct(opts ...StructOption) Type { @@ -67,61 +66,61 @@ func VariantStruct(opts ...StructOption) Type { } } - return value.VariantStruct(s.fields...) + return types.NewVariantStruct(s.fields...) } func VariantTuple(elems ...Type) Type { - return value.VariantTuple(elems...) + return types.NewVariantTuple(elems...) } func Void() Type { - return value.Void() + return types.NewVoid() } func Optional(t Type) Type { - return value.Optional(t) + return types.NewOptional(t) } var DefaultDecimal = DecimalType(decimalPrecision, decimalScale) func DecimalType(precision, scale uint32) Type { - return value.Decimal(precision, scale) + return types.NewDecimal(precision, scale) } func DecimalTypeFromDecimal(d *Decimal) Type { - return value.Decimal(d.Precision, d.Scale) + return types.NewDecimal(d.Precision, d.Scale) } // Primitive types known by YDB. const ( - TypeUnknown = value.TypeUnknown - TypeBool = value.TypeBool - TypeInt8 = value.TypeInt8 - TypeUint8 = value.TypeUint8 - TypeInt16 = value.TypeInt16 - TypeUint16 = value.TypeUint16 - TypeInt32 = value.TypeInt32 - TypeUint32 = value.TypeUint32 - TypeInt64 = value.TypeInt64 - TypeUint64 = value.TypeUint64 - TypeFloat = value.TypeFloat - TypeDouble = value.TypeDouble - TypeDate = value.TypeDate - TypeDatetime = value.TypeDatetime - TypeTimestamp = value.TypeTimestamp - TypeInterval = value.TypeInterval - TypeTzDate = value.TypeTzDate - TypeTzDatetime = value.TypeTzDatetime - TypeTzTimestamp = value.TypeTzTimestamp - TypeString = value.TypeBytes - TypeBytes = value.TypeBytes - TypeUTF8 = value.TypeText - TypeText = value.TypeText - TypeYSON = value.TypeYSON - TypeJSON = value.TypeJSON - TypeUUID = value.TypeUUID - TypeJSONDocument = value.TypeJSONDocument - TypeDyNumber = value.TypeDyNumber + TypeUnknown = types.Unknown + TypeBool = types.Bool + TypeInt8 = types.Int8 + TypeUint8 = types.Uint8 + TypeInt16 = types.Int16 + TypeUint16 = types.Uint16 + TypeInt32 = types.Int32 + TypeUint32 = types.Uint32 + TypeInt64 = types.Int64 + TypeUint64 = types.Uint64 + TypeFloat = types.Float + TypeDouble = types.Double + TypeDate = types.Date + TypeDatetime = types.Datetime + TypeTimestamp = types.Timestamp + TypeInterval = types.Interval + TypeTzDate = types.TzDate + TypeTzDatetime = types.TzDatetime + TypeTzTimestamp = types.TzTimestamp + TypeString = types.Bytes + TypeBytes = types.Bytes + TypeUTF8 = types.Text + TypeText = types.Text + TypeYSON = types.YSON + TypeJSON = types.JSON + TypeUUID = types.UUID + TypeJSONDocument = types.JSONDocument + TypeDyNumber = types.DyNumber ) // WriteTypeStringTo writes ydb type string representation into buffer @@ -131,137 +130,7 @@ func WriteTypeStringTo(buf *bytes.Buffer, t Type) { //nolint: interfacer buf.WriteString(t.Yql()) } -// RawValue scanning non-primitive yql types or for own implementation scanner native API -type RawValue interface { - Path() string - WritePathTo(w io.Writer) (n int64, err error) - Type() Type - Bool() (v bool) - Int8() (v int8) - Uint8() (v uint8) - Int16() (v int16) - Uint16() (v uint16) - Int32() (v int32) - Uint32() (v uint32) - Int64() (v int64) - Uint64() (v uint64) - Float() (v float32) - Double() (v float64) - Date() (v time.Time) - Datetime() (v time.Time) - Timestamp() (v time.Time) - Interval() (v time.Duration) - TzDate() (v time.Time) - TzDatetime() (v time.Time) - TzTimestamp() (v time.Time) - String() (v []byte) - UTF8() (v string) - YSON() (v []byte) - JSON() (v []byte) - UUID() (v [16]byte) - JSONDocument() (v []byte) - DyNumber() (v string) - Value() Value - - // Any returns any primitive or optional value. - // Currently, it may return one of these types: - // - // bool - // int8 - // uint8 - // int16 - // uint16 - // int32 - // uint32 - // int64 - // uint64 - // float32 - // float64 - // []byte - // string - // [16]byte - // - Any() interface{} - - // Unwrap unwraps current item under scan interpreting it as Optional types. - Unwrap() - AssertType(t Type) bool - IsNull() bool - IsOptional() bool - - // ListIn interprets current item under scan as a ydb's list. - // It returns the size of the nested items. - // If current item under scan is not a list types, it returns -1. - ListIn() (size int) - - // ListItem selects current item i-th element as an item to scan. - // ListIn() must be called before. - ListItem(i int) - - // ListOut leaves list entered before by ListIn() call. - ListOut() - - // TupleIn interprets current item under scan as a ydb's tuple. - // It returns the size of the nested items. - TupleIn() (size int) - - // TupleItem selects current item i-th element as an item to scan. - // Note that TupleIn() must be called before. - // It panics if it is out of bounds. - TupleItem(i int) - - // TupleOut leaves tuple entered before by TupleIn() call. - TupleOut() - - // StructIn interprets current item under scan as a ydb's struct. - // It returns the size of the nested items – the struct fields values. - // If there is no current item under scan it returns -1. - StructIn() (size int) - - // StructField selects current item i-th field value as an item to scan. - // Note that StructIn() must be called before. - // It panics if i is out of bounds. - StructField(i int) (name string) - - // StructOut leaves struct entered before by StructIn() call. - StructOut() - - // DictIn interprets current item under scan as a ydb's dict. - // It returns the size of the nested items pairs. - // If there is no current item under scan it returns -1. - DictIn() (size int) - - // DictKey selects current item i-th pair key as an item to scan. - // Note that DictIn() must be called before. - // It panics if i is out of bounds. - DictKey(i int) - - // DictPayload selects current item i-th pair value as an item to scan. - // Note that DictIn() must be called before. - // It panics if i is out of bounds. - DictPayload(i int) - - // DictOut leaves dict entered before by DictIn() call. - DictOut() - - // Variant unwraps current item under scan interpreting it as Variant types. - // It returns non-empty name of a field that is filled for struct-based - // variant. - // It always returns an index of filled field of a Type. - Variant() (name string, index uint32) - - // Decimal returns decimal value represented by big-endian 128 bit signed integer. - Decimal(t Type) (v [16]byte) - - // UnwrapDecimal returns decimal value represented by big-endian 128 bit signed - // integer and its types information. - UnwrapDecimal() Decimal - IsDecimal() bool - Err() error -} - -// Scanner scanning raw ydb types -type Scanner interface { - // UnmarshalYDB must be implemented on client-side for unmarshal raw ydb value. - UnmarshalYDB(raw RawValue) error -} +type ( + RawValue = scanner.RawValue + Scanner = scanner.Scanner +) diff --git a/table/types/types_test.go b/table/types/types_test.go deleted file mode 100644 index 9d4d46c4d..000000000 --- a/table/types/types_test.go +++ /dev/null @@ -1,105 +0,0 @@ -package types - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestEqual(t *testing.T) { - tests := []struct { - lhs Type - rhs Type - equal bool - }{ - { - TypeBool, - TypeBool, - true, - }, - { - TypeBool, - TypeText, - false, - }, - { - TypeText, - TypeText, - true, - }, - { - Optional(TypeBool), - Optional(TypeBool), - true, - }, - { - Optional(TypeBool), - Optional(TypeText), - false, - }, - { - Optional(TypeText), - Optional(TypeText), - true, - }, - } - for _, tt := range tests { - t.Run("", func(t *testing.T) { - if equal := Equal(tt.lhs, tt.rhs); equal != tt.equal { - t.Errorf("Equal(%s, %s) = %v, want %v", tt.lhs, tt.rhs, equal, tt.equal) - } - }) - } -} - -func TestOptionalInnerType(t *testing.T) { - tests := []struct { - src Type - innerType Type - isOptional bool - }{ - { - TypeBool, - nil, - false, - }, - { - TypeText, - nil, - false, - }, - { - Optional(TypeBool), - TypeBool, - true, - }, - { - Optional(TypeText), - TypeText, - true, - }, - { - Optional(Tuple(TypeText, TypeBool, TypeUint64, Optional(TypeInt64))), - Tuple(TypeText, TypeBool, TypeUint64, Optional(TypeInt64)), - true, - }, - } - for _, tt := range tests { - t.Run("", func(t *testing.T) { - optional, isOptional := tt.src.(interface { - IsOptional() - InnerType() Type - }) - require.Equal(t, tt.isOptional, isOptional) - var innerType Type - if isOptional { - innerType = optional.InnerType() - } - if tt.innerType == nil { - require.Nil(t, innerType) - } else { - require.True(t, Equal(tt.innerType, innerType)) - } - }) - } -} diff --git a/table/types/value.go b/table/types/value.go index 75b252c00..9249c4e3c 100644 --- a/table/types/value.go +++ b/table/types/value.go @@ -1,7 +1,6 @@ package types import ( - "fmt" "math/big" "time" @@ -174,21 +173,7 @@ func ZeroValue(t Type) Value { return value.ZeroValue(t) } func OptionalValue(v Value) Value { return value.OptionalValue(v) } // Decimal supported in scanner API -type Decimal struct { - Bytes [16]byte - Precision uint32 - Scale uint32 -} - -func (d *Decimal) String() string { - v := decimal.FromInt128(d.Bytes, d.Precision, d.Scale) - - return decimal.Format(v, d.Precision, d.Scale) -} - -func (d *Decimal) BigInt() *big.Int { - return decimal.FromInt128(d.Bytes, d.Precision, d.Scale) -} +type Decimal = decimal.Decimal // DecimalValue creates decimal value of given types t and value v. // Note that Decimal.Bytes interpreted as big-endian int128. @@ -267,480 +252,171 @@ func VariantValueTuple(v Value, i uint32, variantT Type) Value { } func NullableBoolValue(v *bool) Value { - if v == nil { - return NullValue(TypeBool) - } - - return OptionalValue(BoolValue(*v)) + return value.NullableBoolValue(v) } func NullableInt8Value(v *int8) Value { - if v == nil { - return NullValue(TypeInt8) - } - - return OptionalValue(Int8Value(*v)) + return value.NullableInt8Value(v) } func NullableInt16Value(v *int16) Value { - if v == nil { - return NullValue(TypeInt16) - } - - return OptionalValue(Int16Value(*v)) + return value.NullableInt16Value(v) } func NullableInt32Value(v *int32) Value { - if v == nil { - return NullValue(TypeInt32) - } - - return OptionalValue(Int32Value(*v)) + return value.NullableInt32Value(v) } func NullableInt64Value(v *int64) Value { - if v == nil { - return NullValue(TypeInt64) - } - - return OptionalValue(Int64Value(*v)) + return value.NullableInt64Value(v) } func NullableUint8Value(v *uint8) Value { - if v == nil { - return NullValue(TypeUint8) - } - - return OptionalValue(Uint8Value(*v)) + return value.NullableUint8Value(v) } func NullableUint16Value(v *uint16) Value { - if v == nil { - return NullValue(TypeUint16) - } - - return OptionalValue(Uint16Value(*v)) + return value.NullableUint16Value(v) } func NullableUint32Value(v *uint32) Value { - if v == nil { - return NullValue(TypeUint32) - } - - return OptionalValue(Uint32Value(*v)) + return value.NullableUint32Value(v) } func NullableUint64Value(v *uint64) Value { - if v == nil { - return NullValue(TypeUint64) - } - - return OptionalValue(Uint64Value(*v)) + return value.NullableUint64Value(v) } func NullableFloatValue(v *float32) Value { - if v == nil { - return NullValue(TypeFloat) - } - - return OptionalValue(FloatValue(*v)) + return value.NullableFloatValue(v) } func NullableDoubleValue(v *float64) Value { - if v == nil { - return NullValue(TypeDouble) - } - - return OptionalValue(DoubleValue(*v)) + return value.NullableDoubleValue(v) } func NullableDateValue(v *uint32) Value { - if v == nil { - return NullValue(TypeDate) - } - - return OptionalValue(DateValue(*v)) + return value.NullableDateValue(v) } func NullableDateValueFromTime(v *time.Time) Value { - if v == nil { - return NullValue(TypeDate) - } - - return OptionalValue(DateValueFromTime(*v)) + return value.NullableDateValueFromTime(v) } func NullableDatetimeValue(v *uint32) Value { - if v == nil { - return NullValue(TypeDatetime) - } - - return OptionalValue(DatetimeValue(*v)) + return value.NullableDatetimeValue(v) } func NullableDatetimeValueFromTime(v *time.Time) Value { - if v == nil { - return NullValue(TypeDatetime) - } - - return OptionalValue(DatetimeValueFromTime(*v)) + return value.NullableDatetimeValueFromTime(v) } func NullableTzDateValue(v *string) Value { - if v == nil { - return NullValue(TypeTzDate) - } - - return OptionalValue(TzDateValue(*v)) + return value.NullableTzDateValue(v) } func NullableTzDateValueFromTime(v *time.Time) Value { - if v == nil { - return NullValue(TypeTzDate) - } - - return OptionalValue(TzDateValueFromTime(*v)) + return value.NullableTzDateValueFromTime(v) } func NullableTzDatetimeValue(v *string) Value { - if v == nil { - return NullValue(TypeTzDatetime) - } - - return OptionalValue(TzDatetimeValue(*v)) + return value.NullableTzDatetimeValue(v) } func NullableTzDatetimeValueFromTime(v *time.Time) Value { - if v == nil { - return NullValue(TypeTzDatetime) - } - - return OptionalValue(TzDatetimeValueFromTime(*v)) + return value.NullableTzDatetimeValueFromTime(v) } func NullableTimestampValue(v *uint64) Value { - if v == nil { - return NullValue(TypeTimestamp) - } - - return OptionalValue(TimestampValue(*v)) + return value.NullableTimestampValue(v) } func NullableTimestampValueFromTime(v *time.Time) Value { - if v == nil { - return NullValue(TypeTimestamp) - } - - return OptionalValue(TimestampValueFromTime(*v)) + return value.NullableTimestampValueFromTime(v) } func NullableTzTimestampValue(v *string) Value { - if v == nil { - return NullValue(TypeTzTimestamp) - } - - return OptionalValue(TzTimestampValue(*v)) + return value.NullableTzTimestampValue(v) } func NullableTzTimestampValueFromTime(v *time.Time) Value { - if v == nil { - return NullValue(TypeTzTimestamp) - } - - return OptionalValue(TzTimestampValueFromTime(*v)) + return value.NullableTzTimestampValueFromTime(v) } // NullableIntervalValue makes Value which maybe nil or valued // // Deprecated: use NullableIntervalValueFromMicroseconds instead func NullableIntervalValue(v *int64) Value { - if v == nil { - return NullValue(TypeInterval) - } - - return OptionalValue(IntervalValue(*v)) + return value.NullableIntervalValueFromMicroseconds(v) } func NullableIntervalValueFromMicroseconds(v *int64) Value { - if v == nil { - return NullValue(TypeInterval) - } - - return OptionalValue(IntervalValueFromMicroseconds(*v)) + return value.NullableIntervalValueFromMicroseconds(v) } func NullableIntervalValueFromDuration(v *time.Duration) Value { - if v == nil { - return NullValue(TypeInterval) - } - - return OptionalValue(IntervalValueFromDuration(*v)) + return value.NullableIntervalValueFromDuration(v) } // NullableStringValue // // Deprecated: use NullableBytesValue instead func NullableStringValue(v *[]byte) Value { - if v == nil { - return NullValue(TypeBytes) - } - - return OptionalValue(StringValue(*v)) + return value.NullableBytesValue(v) } func NullableBytesValue(v *[]byte) Value { - if v == nil { - return NullValue(TypeBytes) - } - - return OptionalValue(BytesValue(*v)) + return value.NullableBytesValue(v) } func NullableStringValueFromString(v *string) Value { - if v == nil { - return NullValue(TypeBytes) - } - - return OptionalValue(BytesValueFromString(*v)) + return value.NullableBytesValueFromString(v) } func NullableBytesValueFromString(v *string) Value { - if v == nil { - return NullValue(TypeBytes) - } - - return OptionalValue(BytesValueFromString(*v)) + return value.NullableBytesValueFromString(v) } func NullableUTF8Value(v *string) Value { - if v == nil { - return NullValue(TypeText) - } - - return OptionalValue(TextValue(*v)) + return value.NullableTextValue(v) } func NullableTextValue(v *string) Value { - if v == nil { - return NullValue(TypeText) - } - - return OptionalValue(TextValue(*v)) + return value.NullableTextValue(v) } func NullableYSONValue(v *string) Value { - if v == nil { - return NullValue(TypeYSON) - } - - return OptionalValue(YSONValue(*v)) + return value.NullableYSONValue(v) } func NullableYSONValueFromBytes(v *[]byte) Value { - if v == nil { - return NullValue(TypeYSON) - } - - return OptionalValue(YSONValueFromBytes(*v)) + return value.NullableYSONValueFromBytes(v) } func NullableJSONValue(v *string) Value { - if v == nil { - return NullValue(TypeJSON) - } - - return OptionalValue(JSONValue(*v)) + return value.NullableJSONValue(v) } func NullableJSONValueFromBytes(v *[]byte) Value { - if v == nil { - return NullValue(TypeJSON) - } - - return OptionalValue(JSONValueFromBytes(*v)) + return value.NullableJSONValueFromBytes(v) } func NullableUUIDValue(v *[16]byte) Value { - if v == nil { - return NullValue(TypeUUID) - } - - return OptionalValue(UUIDValue(*v)) + return value.NullableUUIDValue(v) } func NullableJSONDocumentValue(v *string) Value { - if v == nil { - return NullValue(TypeJSONDocument) - } - - return OptionalValue(JSONDocumentValue(*v)) + return value.NullableJSONDocumentValue(v) } func NullableJSONDocumentValueFromBytes(v *[]byte) Value { - if v == nil { - return NullValue(TypeJSONDocument) - } - - return OptionalValue(JSONDocumentValueFromBytes(*v)) + return value.NullableJSONDocumentValueFromBytes(v) } func NullableDyNumberValue(v *string) Value { - if v == nil { - return NullValue(TypeDyNumber) - } - - return OptionalValue(DyNumberValue(*v)) + return value.NullableDyNumberValue(v) } -// Nullable makes optional value from nullable type -// Warning: type interface will be replaced in the future with typed parameters pattern from go1.18 -// -//nolint:gocyclo func Nullable(t Type, v interface{}) Value { - switch t { - case TypeBool: - return NullableBoolValue(v.(*bool)) - case TypeInt8: - return NullableInt8Value(v.(*int8)) - case TypeUint8: - return NullableUint8Value(v.(*uint8)) - case TypeInt16: - return NullableInt16Value(v.(*int16)) - case TypeUint16: - return NullableUint16Value(v.(*uint16)) - case TypeInt32: - return NullableInt32Value(v.(*int32)) - case TypeUint32: - return NullableUint32Value(v.(*uint32)) - case TypeInt64: - return NullableInt64Value(v.(*int64)) - case TypeUint64: - return NullableUint64Value(v.(*uint64)) - case TypeFloat: - return NullableFloatValue(v.(*float32)) - case TypeDouble: - return NullableDoubleValue(v.(*float64)) - case TypeDate: - switch tt := v.(type) { - case *uint32: - return NullableDateValue(tt) - case *time.Time: - return NullableDateValueFromTime(tt) - default: - panic(fmt.Sprintf("unsupported type conversion from %T to TypeDate", tt)) - } - case TypeDatetime: - switch tt := v.(type) { - case *uint32: - return NullableDatetimeValue(tt) - case *time.Time: - return NullableDatetimeValueFromTime(tt) - default: - panic(fmt.Sprintf("unsupported type conversion from %T to TypeDatetime", tt)) - } - case TypeTimestamp: - switch tt := v.(type) { - case *uint64: - return NullableTimestampValue(tt) - case *time.Time: - return NullableTimestampValueFromTime(tt) - default: - panic(fmt.Sprintf("unsupported type conversion from %T to TypeTimestamp", tt)) - } - case TypeInterval: - switch tt := v.(type) { - case *int64: - return NullableIntervalValueFromMicroseconds(tt) - case *time.Duration: - return NullableIntervalValueFromDuration(tt) - default: - panic(fmt.Sprintf("unsupported type conversion from %T to TypeInterval", tt)) - } - case TypeTzDate: - switch tt := v.(type) { - case *string: - return NullableTzDateValue(tt) - case *time.Time: - return NullableTzDateValueFromTime(tt) - default: - panic(fmt.Sprintf("unsupported type conversion from %T to TypeTzDate", tt)) - } - case TypeTzDatetime: - switch tt := v.(type) { - case *string: - return NullableTzDatetimeValue(tt) - case *time.Time: - return NullableTzDatetimeValueFromTime(tt) - default: - panic(fmt.Sprintf("unsupported type conversion from %T to TypeTzDatetime", tt)) - } - case TypeTzTimestamp: - switch tt := v.(type) { - case *string: - return NullableTzTimestampValue(tt) - case *time.Time: - return NullableTzTimestampValueFromTime(tt) - default: - panic(fmt.Sprintf("unsupported type conversion from %T to TypeTzTimestamp", tt)) - } - case TypeBytes: - switch tt := v.(type) { - case *[]byte: - return NullableBytesValue(tt) - case *string: - return NullableStringValueFromString(tt) - default: - panic(fmt.Sprintf("unsupported type conversion from %T to TypeBytes", tt)) - } - case TypeText: - switch tt := v.(type) { - case *string: - return NullableTextValue(tt) - default: - panic(fmt.Sprintf("unsupported type conversion from %T to TypeText", tt)) - } - case TypeYSON: - switch tt := v.(type) { - case *string: - return NullableYSONValue(tt) - case *[]byte: - return NullableYSONValueFromBytes(tt) - default: - panic(fmt.Sprintf("unsupported type conversion from %T to TypeYSON", tt)) - } - case TypeJSON: - switch tt := v.(type) { - case *string: - return NullableJSONValue(tt) - case *[]byte: - return NullableJSONValueFromBytes(tt) - default: - panic(fmt.Sprintf("unsupported type conversion from %T to TypeJSON", tt)) - } - case TypeUUID: - switch tt := v.(type) { - case *[16]byte: - return NullableUUIDValue(tt) - default: - panic(fmt.Sprintf("unsupported type conversion from %T to TypeUUID", tt)) - } - case TypeJSONDocument: - switch tt := v.(type) { - case *string: - return NullableJSONDocumentValue(tt) - case *[]byte: - return NullableJSONDocumentValueFromBytes(tt) - default: - panic(fmt.Sprintf("unsupported type conversion from %T to TypeJSONDocument", tt)) - } - case TypeDyNumber: - switch tt := v.(type) { - case *string: - return NullableDyNumberValue(tt) - default: - panic(fmt.Sprintf("unsupported type conversion from %T to TypeDyNumber", tt)) - } - default: - panic(fmt.Sprintf("unsupported type: %T", t)) - } + return value.Nullable(t, v) } diff --git a/table/types/value_test.go b/table/types/value_test.go deleted file mode 100644 index 598272c09..000000000 --- a/table/types/value_test.go +++ /dev/null @@ -1,617 +0,0 @@ -package types - -import ( - "fmt" - "reflect" - "testing" - "time" - - "github.com/stretchr/testify/require" - "google.golang.org/protobuf/proto" - - "github.com/ydb-platform/ydb-go-sdk/v3/internal/allocator" - "github.com/ydb-platform/ydb-go-sdk/v3/internal/value" -) - -func TestNullable(t *testing.T) { - for _, test := range []struct { - name string - t Type - v interface{} - exp Value - }{ - { - name: "bool", - t: TypeBool, - v: func(v bool) *bool { return &v }(true), - exp: OptionalValue(BoolValue(true)), - }, - { - name: "nil bool", - t: TypeBool, - v: func() *bool { return nil }(), - exp: NullValue(TypeBool), - }, - { - name: "int8", - t: TypeInt8, - v: func(v int8) *int8 { return &v }(123), - exp: OptionalValue(Int8Value(123)), - }, - { - name: "nil int8", - t: TypeInt8, - v: func() *int8 { return nil }(), - exp: NullValue(TypeInt8), - }, - { - name: "uint8", - t: TypeUint8, - v: func(v uint8) *uint8 { return &v }(123), - exp: OptionalValue(Uint8Value(123)), - }, - { - name: "nil uint8", - t: TypeUint8, - v: func() *uint8 { return nil }(), - exp: NullValue(TypeUint8), - }, - { - name: "int16", - t: TypeInt16, - v: func(v int16) *int16 { return &v }(123), - exp: OptionalValue(Int16Value(123)), - }, - { - name: "nil int16", - t: TypeInt16, - v: func() *int16 { return nil }(), - exp: NullValue(TypeInt16), - }, - { - name: "uint16", - t: TypeUint16, - v: func(v uint16) *uint16 { return &v }(123), - exp: OptionalValue(Uint16Value(123)), - }, - { - name: "nil uint16", - t: TypeUint16, - v: func() *uint16 { return nil }(), - exp: NullValue(TypeUint16), - }, - { - name: "int32", - t: TypeInt32, - v: func(v int32) *int32 { return &v }(123), - exp: OptionalValue(Int32Value(123)), - }, - { - name: "nil int32", - t: TypeInt32, - v: func() *int32 { return nil }(), - exp: NullValue(TypeInt32), - }, - { - name: "uint32", - t: TypeUint32, - v: func(v uint32) *uint32 { return &v }(123), - exp: OptionalValue(Uint32Value(123)), - }, - { - name: "nil uint32", - t: TypeUint32, - v: func() *uint32 { return nil }(), - exp: NullValue(TypeUint32), - }, - { - name: "int64", - t: TypeInt64, - v: func(v int64) *int64 { return &v }(123), - exp: OptionalValue(Int64Value(123)), - }, - { - name: "nil int64", - t: TypeInt64, - v: func() *int64 { return nil }(), - exp: NullValue(TypeInt64), - }, - { - name: "uint64", - t: TypeUint64, - v: func(v uint64) *uint64 { return &v }(123), - exp: OptionalValue(Uint64Value(123)), - }, - { - name: "nil uint64", - t: TypeUint64, - v: func() *uint64 { return nil }(), - exp: NullValue(TypeUint64), - }, - { - name: "float", - t: TypeFloat, - v: func(v float32) *float32 { return &v }(123), - exp: OptionalValue(FloatValue(123)), - }, - { - name: "nil float", - t: TypeFloat, - v: func() *float32 { return nil }(), - exp: NullValue(TypeFloat), - }, - { - name: "double", - t: TypeDouble, - v: func(v float64) *float64 { return &v }(123), - exp: OptionalValue(DoubleValue(123)), - }, - { - name: "nil float", - t: TypeDouble, - v: func() *float64 { return nil }(), - exp: NullValue(TypeDouble), - }, - { - name: "date from int32", - t: TypeDate, - v: func(v uint32) *uint32 { return &v }(123), - exp: OptionalValue(DateValue(123)), - }, - { - name: "date from time.Time", - t: TypeDate, - v: func(v time.Time) *time.Time { return &v }(time.Unix(123, 456)), - exp: OptionalValue(DateValueFromTime(time.Unix(123, 456))), - }, - { - name: "nil date", - t: TypeDate, - v: func() *uint32 { return nil }(), - exp: NullValue(TypeDate), - }, - { - name: "datetime from int32", - t: TypeDatetime, - v: func(v uint32) *uint32 { return &v }(123), - exp: OptionalValue(DatetimeValue(123)), - }, - { - name: "datetime from time.Time", - t: TypeDatetime, - v: func(v time.Time) *time.Time { return &v }(time.Unix(123, 456)), - exp: OptionalValue(DatetimeValueFromTime(time.Unix(123, 456))), - }, - { - name: "nil datetime", - t: TypeDatetime, - v: func() *uint32 { return nil }(), - exp: NullValue(TypeDatetime), - }, - { - name: "timestamp from int32", - t: TypeTimestamp, - v: func(v uint64) *uint64 { return &v }(123), - exp: OptionalValue(TimestampValue(123)), - }, - { - name: "timestamp from time.Time", - t: TypeTimestamp, - v: func(v time.Time) *time.Time { return &v }(time.Unix(123, 456)), - exp: OptionalValue(TimestampValueFromTime(time.Unix(123, 456))), - }, - { - name: "nil timestamp", - t: TypeTimestamp, - v: func() *uint64 { return nil }(), - exp: NullValue(TypeTimestamp), - }, - { - name: "tzDate from int32", - t: TypeTzDate, - v: func(v string) *string { return &v }(""), - exp: OptionalValue(TzDateValue("")), - }, - { - name: "tzDate from time.Time", - t: TypeTzDate, - v: func(v time.Time) *time.Time { return &v }(time.Unix(123, 456)), - exp: OptionalValue(TzDateValueFromTime(time.Unix(123, 456))), - }, - { - name: "nil tzDate", - t: TypeTzDate, - v: func() *string { return nil }(), - exp: NullValue(TypeTzDate), - }, - { - name: "interval from int64", - t: TypeInterval, - v: func(v int64) *int64 { return &v }(123), - exp: OptionalValue(IntervalValueFromMicroseconds(123)), - }, - { - name: "interval from time.Time", - t: TypeInterval, - v: func(v time.Duration) *time.Duration { return &v }(time.Second), - exp: OptionalValue(IntervalValueFromDuration(time.Second)), - }, - { - name: "nil interval", - t: TypeInterval, - v: func() *int64 { return nil }(), - exp: NullValue(TypeInterval), - }, - { - name: "tzDatetime from int32", - t: TypeTzDatetime, - v: func(v string) *string { return &v }(""), - exp: OptionalValue(TzDatetimeValue("")), - }, - { - name: "tzTzDatetime from time.Time", - t: TypeTzDatetime, - v: func(v time.Time) *time.Time { return &v }(time.Unix(123, 456)), - exp: OptionalValue(TzDatetimeValueFromTime(time.Unix(123, 456))), - }, - { - name: "nil tzTzDatetime", - t: TypeTzDatetime, - v: func() *string { return nil }(), - exp: NullValue(TypeTzDatetime), - }, - { - name: "tzTimestamp from int32", - t: TypeTzTimestamp, - v: func(v string) *string { return &v }(""), - exp: OptionalValue(TzTimestampValue("")), - }, - { - name: "TzTimestamp from time.Time", - t: TypeTzTimestamp, - v: func(v time.Time) *time.Time { return &v }(time.Unix(123, 456)), - exp: OptionalValue(TzTimestampValueFromTime(time.Unix(123, 456))), - }, - { - name: "nil TzTimestamp", - t: TypeTzTimestamp, - v: func() *string { return nil }(), - exp: NullValue(TypeTzTimestamp), - }, - { - name: "string", - t: TypeBytes, - v: func(v string) *string { return &v }("test"), - exp: OptionalValue(BytesValueFromString("test")), - }, - { - name: "string", - t: TypeBytes, - v: func(v []byte) *[]byte { return &v }([]byte("test")), - exp: OptionalValue(BytesValueFromString("test")), - }, - { - name: "nil string", - t: TypeBytes, - v: func() *string { return nil }(), - exp: NullValue(TypeBytes), - }, - { - name: "utf8", - t: TypeText, - v: func(v string) *string { return &v }("test"), - exp: OptionalValue(TextValue("test")), - }, - { - name: "nil utf8", - t: TypeText, - v: func() *string { return nil }(), - exp: NullValue(TypeText), - }, - { - name: "yson", - t: TypeYSON, - v: func(v string) *string { return &v }("test"), - exp: OptionalValue(YSONValue("test")), - }, - { - name: "yson", - t: TypeYSON, - v: func(v []byte) *[]byte { return &v }([]byte("test")), - exp: OptionalValue(YSONValueFromBytes([]byte("test"))), - }, - { - name: "nil yson", - t: TypeYSON, - v: func() *string { return nil }(), - exp: NullValue(TypeYSON), - }, - { - name: "json", - t: TypeJSON, - v: func(v string) *string { return &v }("test"), - exp: OptionalValue(JSONValue("test")), - }, - { - name: "json", - t: TypeJSON, - v: func(v []byte) *[]byte { return &v }([]byte("test")), - exp: OptionalValue(JSONValueFromBytes([]byte("test"))), - }, - { - name: "nil json", - t: TypeJSON, - v: func() *string { return nil }(), - exp: NullValue(TypeJSON), - }, - { - name: "uuid", - t: TypeUUID, - v: func(v [16]byte) *[16]byte { return &v }([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}), - exp: OptionalValue(UUIDValue([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16})), - }, - { - name: "jsonDocument", - t: TypeJSONDocument, - v: func(v string) *string { return &v }("test"), - exp: OptionalValue(JSONDocumentValue("test")), - }, - { - name: "jsonDocument", - t: TypeJSONDocument, - v: func(v []byte) *[]byte { return &v }([]byte("test")), - exp: OptionalValue(JSONDocumentValueFromBytes([]byte("test"))), - }, - { - name: "nil jsonDocument", - t: TypeJSONDocument, - v: func() *string { return nil }(), - exp: NullValue(TypeJSONDocument), - }, - { - name: "dyNumber", - t: TypeDyNumber, - v: func(v string) *string { return &v }("test"), - exp: OptionalValue(DyNumberValue("test")), - }, - { - name: "nil dyNumber", - t: TypeDyNumber, - v: func() *string { return nil }(), - exp: NullValue(TypeDyNumber), - }, - } { - t.Run(test.name, func(t *testing.T) { - a := allocator.New() - defer a.Free() - v := Nullable(test.t, test.v) - if !proto.Equal(value.ToYDB(v, a), value.ToYDB(test.exp, a)) { - t.Fatalf("unexpected value: %v, exp: %v", v, test.exp) - } - }) - } -} - -func TestCastNumbers(t *testing.T) { - numberValues := []struct { - value Value - signed bool - len int - }{ - { - value: Uint64Value(1), - signed: false, - len: 8, - }, - { - value: Int64Value(2), - signed: true, - len: 8, - }, - { - value: Uint32Value(3), - signed: false, - len: 4, - }, - { - value: Int32Value(4), - signed: true, - len: 4, - }, - { - value: Uint16Value(5), - signed: false, - len: 2, - }, - { - value: Int16Value(6), - signed: true, - len: 2, - }, - { - value: Uint8Value(7), - signed: false, - len: 1, - }, - { - value: Int8Value(8), - signed: true, - len: 1, - }, - } - numberDestinations := []struct { - destination interface{} - signed bool - len int - }{ - { - destination: func(v uint64) *uint64 { return &v }(1), - signed: false, - len: 8, - }, - { - destination: func(v int64) *int64 { return &v }(2), - signed: true, - len: 8, - }, - { - destination: func(v uint32) *uint32 { return &v }(3), - signed: false, - len: 4, - }, - { - destination: func(v int32) *int32 { return &v }(4), - signed: true, - len: 4, - }, - { - destination: func(v uint16) *uint16 { return &v }(5), - signed: false, - len: 2, - }, - { - destination: func(v int16) *int16 { return &v }(6), - signed: true, - len: 2, - }, - { - destination: func(v uint8) *uint8 { return &v }(7), - signed: false, - len: 1, - }, - { - destination: func(v int8) *int8 { return &v }(8), - signed: true, - len: 1, - }, - { - destination: func(v float32) *float32 { return &v }(7), - signed: true, - len: 4, - }, - { - destination: func(v float64) *float64 { return &v }(8), - signed: true, - len: 8, - }, - } - for _, dst := range numberDestinations { - t.Run(reflect.ValueOf(dst.destination).Type().Elem().String(), func(t *testing.T) { - for _, src := range numberValues { - t.Run(src.value.Yql(), func(t *testing.T) { - mustErr := false - switch { - case src.len == dst.len && src.signed != dst.signed, - src.len > dst.len, - src.signed && !dst.signed: - mustErr = true - } - err := CastTo(src.value, dst.destination) - if mustErr { - require.Error(t, err) - } else { - require.NoError(t, err) - } - }) - t.Run(OptionalValue(src.value).Yql(), func(t *testing.T) { - mustErr := false - switch { - case src.len == dst.len && src.signed != dst.signed, - src.len > dst.len, - src.signed && !dst.signed: - mustErr = true - } - err := CastTo(OptionalValue(src.value), dst.destination) - if mustErr { - require.Error(t, err) - } else { - require.NoError(t, err) - } - }) - } - }) - } -} - -func TestCastOtherTypes(t *testing.T) { - for _, tt := range []struct { - v Value - dst interface{} - result interface{} - error bool - }{ - { - v: BytesValue([]byte("test")), - dst: func(v []byte) *[]byte { return &v }(make([]byte, 0, 10)), - result: func(v []byte) *[]byte { return &v }([]byte("test")), - error: false, - }, - { - v: TextValue("test"), - dst: func(v []byte) *[]byte { return &v }(make([]byte, 0, 10)), - result: func(v []byte) *[]byte { return &v }([]byte("test")), - error: false, - }, - { - v: BytesValue([]byte("test")), - dst: func(v string) *string { return &v }(""), - result: func(v string) *string { return &v }("test"), - error: false, - }, - { - v: DoubleValue(123), - dst: func(v float64) *float64 { return &v }(9), - result: func(v float64) *float64 { return &v }(123), - error: false, - }, - { - v: DoubleValue(123), - dst: func(v float32) *float32 { return &v }(9), - result: func(v float32) *float32 { return &v }(9), - error: true, - }, - { - v: FloatValue(123), - dst: func(v float64) *float64 { return &v }(9), - result: func(v float64) *float64 { return &v }(123), - error: false, - }, - { - v: FloatValue(123), - dst: func(v float32) *float32 { return &v }(9), - result: func(v float32) *float32 { return &v }(123), - error: false, - }, - { - v: Uint64Value(123), - dst: func(v float32) *float32 { return &v }(9), - result: func(v float32) *float32 { return &v }(9), - error: true, - }, - { - v: Uint64Value(123), - dst: func(v float64) *float64 { return &v }(9), - result: func(v float64) *float64 { return &v }(9), - error: true, - }, - { - v: OptionalValue(DoubleValue(123)), - dst: func(v float64) *float64 { return &v }(9), - result: func(v float64) *float64 { return &v }(123), - error: false, - }, - } { - t.Run(fmt.Sprintf("cast %s to %v", tt.v.Type().Yql(), reflect.ValueOf(tt.dst).Type().Elem()), - func(t *testing.T) { - if err := CastTo(tt.v, tt.dst); (err != nil) != tt.error { - t.Errorf("castTo() error = %v, want %v", err, tt.error) - } else if !reflect.DeepEqual(tt.dst, tt.result) { - t.Errorf("castTo() result = %+v, want %+v", - reflect.ValueOf(tt.dst).Elem(), - reflect.ValueOf(tt.result).Elem(), - ) - } - }, - ) - } -} diff --git a/tests/integration/basic_example_native_test.go b/tests/integration/basic_example_native_test.go index f6f4b7dde..1e4231e5f 100644 --- a/tests/integration/basic_example_native_test.go +++ b/tests/integration/basic_example_native_test.go @@ -232,7 +232,7 @@ func TestBasicExampleNative(t *testing.T) { //nolint:gocyclo db, err := ydb.Open(ctx, os.Getenv("YDB_CONNECTION_STRING"), ydb.WithAccessTokenCredentials(os.Getenv("YDB_ACCESS_TOKEN_CREDENTIALS")), - ydb.WithUserAgent("table/e2e"), + ydb.WithApplicationName("table/e2e"), withMetrics(t, trace.DetailsAll, time.Second), ydb.With( config.WithOperationTimeout(time.Second*5), diff --git a/tests/integration/connection_test.go b/tests/integration/connection_test.go index d4aecc63c..fcbcfb837 100644 --- a/tests/integration/connection_test.go +++ b/tests/integration/connection_test.go @@ -46,7 +46,7 @@ func TestConnection(t *testing.T) { if !has { t.Fatalf("no medatada") } - userAgents := md.Get(meta.HeaderUserAgent) + userAgents := md.Get(meta.HeaderApplicationName) if len(userAgents) == 0 { t.Fatalf("no user agent") } @@ -87,7 +87,7 @@ func TestConnection(t *testing.T) { newLoggerWithMinLevel(t, log.WARN), trace.MatchDetails(`ydb\.(driver|discovery|retry|scheme).*`), ), - ydb.WithUserAgent(userAgent), + ydb.WithApplicationName(userAgent), ydb.WithRequestsType(requestType), ydb.With( config.WithGrpcOptions( diff --git a/tests/integration/connection_with_compression_test.go b/tests/integration/connection_with_compression_test.go index cdb15ddac..c0be662b4 100644 --- a/tests/integration/connection_with_compression_test.go +++ b/tests/integration/connection_with_compression_test.go @@ -45,7 +45,7 @@ func TestConnectionWithCompression(t *testing.T) { if !has { t.Fatalf("no medatada") } - userAgents := md.Get(meta.HeaderUserAgent) + userAgents := md.Get(meta.HeaderApplicationName) if len(userAgents) == 0 { t.Fatalf("no user agent") } @@ -79,7 +79,7 @@ func TestConnectionWithCompression(t *testing.T) { newLoggerWithMinLevel(t, log.WARN), trace.MatchDetails(`ydb\.(driver|discovery|retry|scheme).*`), ), - ydb.WithUserAgent(userAgent), + ydb.WithApplicationName(userAgent), ydb.WithRequestsType(requestType), ydb.With( config.WithGrpcOptions( 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/database_sql_containers_test.go b/tests/integration/database_sql_containers_test.go index bbb8cc2db..5e8ae71f8 100644 --- a/tests/integration/database_sql_containers_test.go +++ b/tests/integration/database_sql_containers_test.go @@ -149,7 +149,7 @@ func (s *testDatabaseSqlContainersExampleStruct) Scan(res interface{}) error { return nil } } - return fmt.Errorf("type '%T' is not a `types.Value` type", res) + return fmt.Errorf("type '%T' is not a `types.value` type", res) } type testDatabaseSqlContainersExampleList []string diff --git a/tests/integration/discovery_test.go b/tests/integration/discovery_test.go index 410605bbf..511043ebd 100644 --- a/tests/integration/discovery_test.go +++ b/tests/integration/discovery_test.go @@ -31,12 +31,12 @@ func TestDiscovery(t *testing.T) { if !has { t.Fatalf("no medatada") } - userAgents := md.Get(meta.HeaderUserAgent) - if len(userAgents) == 0 { - t.Fatalf("no user agent") + applicationName := md.Get(meta.HeaderApplicationName) + if len(applicationName) == 0 { + t.Fatalf("no application name") } - if userAgents[0] != userAgent { - t.Fatalf("unknown user agent: %s", userAgents[0]) + if applicationName[0] != userAgent { + t.Fatalf("unknown user agent: %s", applicationName[0]) } requestTypes := md.Get(meta.HeaderRequestType) if len(requestTypes) == 0 { @@ -66,7 +66,7 @@ func TestDiscovery(t *testing.T) { newLoggerWithMinLevel(t, log.WARN), trace.MatchDetails(`ydb\.(driver|discovery|repeater).*`), ), - ydb.WithUserAgent(userAgent), + ydb.WithApplicationName(userAgent), ydb.WithRequestsType(requestType), ydb.With( config.WithGrpcOptions( diff --git a/tests/integration/helpers_test.go b/tests/integration/helpers_test.go index 9184ad86c..73363250d 100644 --- a/tests/integration/helpers_test.go +++ b/tests/integration/helpers_test.go @@ -275,7 +275,9 @@ func (scope *scopeT) TableName(opts ...func(t *tableNameParams)) string { `, } for _, opt := range opts { - opt(¶ms) + if opt != nil { + opt(¶ms) + } } return scope.Cache(params.tableName, nil, func() (res interface{}, err error) { err = scope.Driver().Table().Do(scope.Ctx, func(ctx context.Context, s table.Session) (err error) { diff --git a/tests/integration/query_execute_test.go b/tests/integration/query_execute_test.go new file mode 100644 index 000000000..81687bae8 --- /dev/null +++ b/tests/integration/query_execute_test.go @@ -0,0 +1,276 @@ +//go:build integration +// +build integration + +package integration + +import ( + "context" + "fmt" + "os" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/ydb-platform/ydb-go-sdk/v3" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/version" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/xtest" + "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" +) + +func TestQueryExecute(t *testing.T) { + if version.Lt(os.Getenv("YDB_VERSION"), "24.1") { + t.Skip("query service not allowed in YDB version '" + os.Getenv("YDB_VERSION") + "'") + } + + ctx, cancel := context.WithCancel(xtest.Context(t)) + defer cancel() + + 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, + log.WithLogQuery(), + log.WithColoring(), + log.WithMinLevel(log.INFO), + ), + trace.QueryEvents, + ), + ), + ) + 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 + p2 uint64 + p3 time.Duration + ) + err = db.Query().Do(ctx, func(ctx context.Context, s query.Session) (err error) { + _, res, err := s.Execute(ctx, ` + DECLARE $p1 AS Text; + DECLARE $p2 AS Uint64; + DECLARE $p3 AS Interval; + SELECT $p1, $p2, $p3; + `, + query.WithParameters( + ydb.ParamsBuilder(). + Param("$p1").Text("test"). + Param("$p2").Uint64(100500000000). + Param("$p3").Interval(time.Duration(100500000000)). + Build(), + ), + query.WithSyntax(query.SyntaxYQL), + ) + if err != nil { + return err + } + rs, err := res.NextResultSet(ctx) + if err != nil { + return err + } + row, err := rs.NextRow(ctx) + if err != nil { + return err + } + err = row.Scan(&p1, &p2, &p3) + if err != nil { + return err + } + return res.Err() + }, query.WithIdempotent()) + require.NoError(t, err) + require.EqualValues(t, "test", p1) + require.EqualValues(t, 100500000000, p2) + require.EqualValues(t, time.Duration(100500000000), p3) + }) + t.Run("ScanNamed", func(t *testing.T) { + var ( + p1 string + p2 uint64 + p3 time.Duration + ) + err = db.Query().Do(ctx, func(ctx context.Context, s query.Session) (err error) { + _, res, err := s.Execute(ctx, ` + DECLARE $p1 AS Text; + DECLARE $p2 AS Uint64; + DECLARE $p3 AS Interval; + SELECT $p1 AS p1, $p2 AS p2, $p3 AS p3; + `, + query.WithParameters( + ydb.ParamsBuilder(). + Param("$p1").Text("test"). + Param("$p2").Uint64(100500000000). + Param("$p3").Interval(time.Duration(100500000000)). + Build(), + ), + query.WithSyntax(query.SyntaxYQL), + ) + if err != nil { + return err + } + rs, err := res.NextResultSet(ctx) + if err != nil { + return err + } + row, err := rs.NextRow(ctx) + if err != nil { + return err + } + err = row.ScanNamed( + query.Named("p1", &p1), + query.Named("p2", &p2), + query.Named("p3", &p3), + ) + if err != nil { + return err + } + return res.Err() + }, query.WithIdempotent()) + require.NoError(t, err) + require.EqualValues(t, "test", p1) + require.EqualValues(t, 100500000000, p2) + require.EqualValues(t, time.Duration(100500000000), p3) + }) + t.Run("ScanStruct", func(t *testing.T) { + var data struct { + P1 *string `sql:"p1"` + P2 uint64 `sql:"p2"` + P3 time.Duration `sql:"p3"` + P4 *string `sql:"p4"` + } + err = db.Query().Do(ctx, func(ctx context.Context, s query.Session) (err error) { + _, res, err := s.Execute(ctx, ` + DECLARE $p1 AS Text; + DECLARE $p2 AS Uint64; + DECLARE $p3 AS Interval; + SELECT CAST($p1 AS Optional) AS p1, $p2 AS p2, $p3 AS p3, CAST(NULL AS Optional) AS p4; + `, + query.WithParameters( + ydb.ParamsBuilder(). + Param("$p1").Text("test"). + Param("$p2").Uint64(100500000000). + Param("$p3").Interval(time.Duration(100500000000)). + Build(), + ), + query.WithSyntax(query.SyntaxYQL), + ) + if err != nil { + return err + } + rs, err := res.NextResultSet(ctx) + if err != nil { + return err + } + row, err := rs.NextRow(ctx) + if err != nil { + return err + } + err = row.ScanStruct(&data) + if err != nil { + return err + } + return res.Err() + }, query.WithIdempotent()) + require.NoError(t, err) + require.NotNil(t, data.P1) + require.EqualValues(t, "test", *data.P1) + require.EqualValues(t, 100500000000, data.P2) + require.EqualValues(t, time.Duration(100500000000), data.P3) + require.Nil(t, data.P4) + }) + t.Run("Tx", func(t *testing.T) { + t.Run("Explicit", func(t *testing.T) { + err = db.Query().Do(ctx, func(ctx context.Context, s query.Session) (err error) { + tx, err := s.Begin(ctx, query.TxSettings(query.WithSerializableReadWrite())) + if err != nil { + return err + } + res, err := tx.Execute(ctx, `SELECT 1`) + if err != nil { + return err + } + rs, err := res.NextResultSet(ctx) + if err != nil { + return err + } + row, err := rs.NextRow(ctx) + if err != nil { + return err + } + var v int32 + err = row.Scan(&v) + if err != nil { + return err + } + if v != 1 { + return fmt.Errorf("unexpected value from database: %d", v) + } + if err = res.Err(); err != nil { + return err + } + return tx.CommitTx(ctx) + }, query.WithIdempotent()) + require.NoError(t, err) + }) + t.Run("Lazy", func(t *testing.T) { + err = db.Query().Do(ctx, func(ctx context.Context, s query.Session) (err error) { + tx, res, err := s.Execute(ctx, `SELECT 1`, + query.WithTxControl(query.TxControl(query.BeginTx(query.WithSerializableReadWrite()))), + ) + if err != nil { + return err + } + rs, err := res.NextResultSet(ctx) + if err != nil { + return err + } + row, err := rs.NextRow(ctx) + if err != nil { + return err + } + var v int32 + err = row.Scan(&v) + if err != nil { + return err + } + if v != 1 { + return fmt.Errorf("unexpected value from database: %d", v) + } + if err = res.Err(); err != nil { + return err + } + res, err = tx.Execute(ctx, `SELECT 2`, query.WithCommit()) + if err != nil { + return err + } + rs, err = res.NextResultSet(ctx) + if err != nil { + return err + } + row, err = rs.NextRow(ctx) + if err != nil { + return err + } + err = row.Scan(&v) + if err != nil { + return err + } + if v != 2 { + return fmt.Errorf("unexpected value from database: %d", v) + } + return res.Err() + }, query.WithIdempotent()) + require.NoError(t, err) + }) + }) +} diff --git a/tests/integration/query_tx_execute_test.go b/tests/integration/query_tx_execute_test.go new file mode 100644 index 000000000..91a70029a --- /dev/null +++ b/tests/integration/query_tx_execute_test.go @@ -0,0 +1,64 @@ +//go:build integration +// +build integration + +package integration + +import ( + "context" + "os" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ydb-platform/ydb-go-sdk/v3" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/version" + "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" +) + +func TestQueryTxExecute(t *testing.T) { + if version.Lt(os.Getenv("YDB_VERSION"), "24.1") { + t.Skip("query service not allowed in YDB version '" + os.Getenv("YDB_VERSION") + "'") + } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + db, err := ydb.Open(ctx, + os.Getenv("YDB_CONNECTION_STRING"), + ydb.WithAccessTokenCredentials(os.Getenv("YDB_ACCESS_TOKEN_CREDENTIALS")), + ydb.WithTraceQuery( + log.Query( + log.Default(os.Stdout, + log.WithLogQuery(), + log.WithColoring(), + log.WithMinLevel(log.INFO), + ), + trace.QueryEvents, + ), + ), + ) + require.NoError(t, err) + err = db.Query().DoTx(ctx, func(ctx context.Context, tx query.TxActor) (err error) { + res, err := tx.Execute(ctx, "SELECT 1 AS col1") + if err != nil { + return err + } + rs, err := res.NextResultSet(ctx) + if err != nil { + return err + } + row, err := rs.NextRow(ctx) + if err != nil { + return err + } + var col1 int + err = row.ScanNamed(query.Named("col1", &col1)) + if err != nil { + return err + } + return res.Err() + }, query.WithIdempotent(), query.WithTxSettings(query.TxSettings(query.WithSerializableReadWrite()))) + require.NoError(t, err) +} diff --git a/tests/integration/scripting_test.go b/tests/integration/scripting_test.go index fb3cc9d49..b1c908e0d 100644 --- a/tests/integration/scripting_test.go +++ b/tests/integration/scripting_test.go @@ -40,7 +40,7 @@ func TestScripting(t *testing.T) { newLogger(t), trace.MatchDetails(`ydb\.(driver|discovery|retry|scheme).*`), ), - ydb.WithUserAgent("scripting"), + ydb.WithApplicationName("scripting"), ) if err != nil { t.Fatal(err) diff --git a/tests/integration/sugar_result_test.go b/tests/integration/sugar_result_test.go new file mode 100644 index 000000000..ba863dd72 --- /dev/null +++ b/tests/integration/sugar_result_test.go @@ -0,0 +1,248 @@ +//go:build integration +// +build integration + +package integration + +import ( + "context" + "os" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/ydb-platform/ydb-go-sdk/v3" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/version" + "github.com/ydb-platform/ydb-go-sdk/v3/query" + "github.com/ydb-platform/ydb-go-sdk/v3/sugar" + "github.com/ydb-platform/ydb-go-sdk/v3/table" + "github.com/ydb-platform/ydb-go-sdk/v3/table/result/named" + "github.com/ydb-platform/ydb-go-sdk/v3/table/types" +) + +func TestSugarResult(t *testing.T) { + if version.Lt(os.Getenv("YDB_VERSION"), "24.1") { + t.Skip("query service not allowed in YDB version '" + os.Getenv("YDB_VERSION") + "'") + } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + db, err := ydb.Open(ctx, + os.Getenv("YDB_CONNECTION_STRING"), + ydb.WithAccessTokenCredentials(os.Getenv("YDB_ACCESS_TOKEN_CREDENTIALS")), + ) + require.NoError(t, err) + t.Run("Scan", func(t *testing.T) { + t.Run("Table", func(t *testing.T) { + var ( + p1 string + p2 uint64 + p3 time.Duration + ) + err = db.Table().Do(ctx, func(ctx context.Context, s table.Session) (err error) { + _, res, err := s.Execute(ctx, table.DefaultTxControl(), ` + DECLARE $p1 AS Text; + DECLARE $p2 AS Uint64; + DECLARE $p3 AS Interval; + SELECT $p1, $p2, $p3; + `, + table.NewQueryParameters( + table.ValueParam("$p1", types.TextValue("test")), + table.ValueParam("$p2", types.Uint64Value(100500000000)), + table.ValueParam("$p3", types.IntervalValueFromDuration(time.Duration(100500000000))), + ), + ) + if err != nil { + return err + } + for res.NextResultSet(ctx) { + for res.NextRow() { + if err = res.Scan(&p1, &p2, &p3); err != nil { + return err + } + } + } + + return res.Err() + }, table.WithIdempotent()) + require.NoError(t, err) + require.EqualValues(t, "test", p1) + require.EqualValues(t, 100500000000, p2) + require.EqualValues(t, time.Duration(100500000000), p3) + }) + t.Run("Sugar", func(t *testing.T) { + var ( + p1 string + p2 uint64 + p3 time.Duration + ) + err = db.Query().Do(ctx, func(ctx context.Context, s query.Session) (err error) { + _, r, err := s.Execute(ctx, ` + DECLARE $p1 AS Text; + DECLARE $p2 AS Uint64; + DECLARE $p3 AS Interval; + SELECT $p1, $p2, $p3; + `, + query.WithParameters( + table.NewQueryParameters( + table.ValueParam("$p1", types.TextValue("test")), + table.ValueParam("$p2", types.Uint64Value(100500000000)), + table.ValueParam("$p3", types.IntervalValueFromDuration(time.Duration(100500000000))), + ), + ), + ) + if err != nil { + return err + } + res := sugar.Result(r) + for res.NextResultSet(ctx) { + for res.NextRow() { + if err = res.Scan(&p1, &p2, &p3); err != nil { + return err + } + } + } + + return res.Err() + }, query.WithIdempotent()) + require.NoError(t, err) + require.EqualValues(t, "test", p1) + require.EqualValues(t, 100500000000, p2) + require.EqualValues(t, time.Duration(100500000000), p3) + }) + }) + t.Run("ScanNamed", func(t *testing.T) { + t.Run("Table", func(t *testing.T) { + var ( + p1 string + p2 uint64 + p3 time.Duration + ) + err = db.Table().Do(ctx, func(ctx context.Context, s table.Session) (err error) { + _, res, err := s.Execute(ctx, table.DefaultTxControl(), ` + DECLARE $p1 AS Text; + DECLARE $p2 AS Uint64; + DECLARE $p3 AS Interval; + SELECT $p1 AS p1, $p2 AS p2, $p3 AS p3; + `, + table.NewQueryParameters( + table.ValueParam("$p1", types.TextValue("test")), + table.ValueParam("$p2", types.Uint64Value(100500000000)), + table.ValueParam("$p3", types.IntervalValueFromDuration(time.Duration(100500000000))), + ), + ) + if err != nil { + return err + } + for res.NextResultSet(ctx) { + for res.NextRow() { + if err = res.ScanNamed( + named.Required("p1", &p1), + named.Required("p2", &p2), + named.Required("p3", &p3), + ); err != nil { + return err + } + } + } + + return res.Err() + }, table.WithIdempotent()) + require.NoError(t, err) + require.EqualValues(t, "test", p1) + require.EqualValues(t, 100500000000, p2) + require.EqualValues(t, time.Duration(100500000000), p3) + }) + t.Run("Sugar", func(t *testing.T) { + var ( + p1 string + p2 uint64 + p3 time.Duration + ) + err = db.Query().Do(ctx, func(ctx context.Context, s query.Session) (err error) { + _, r, err := s.Execute(ctx, ` + DECLARE $p1 AS Text; + DECLARE $p2 AS Uint64; + DECLARE $p3 AS Interval; + SELECT $p1 AS p1, $p2 AS p2, $p3 AS p3; + `, + query.WithParameters( + table.NewQueryParameters( + table.ValueParam("$p1", types.TextValue("test")), + table.ValueParam("$p2", types.Uint64Value(100500000000)), + table.ValueParam("$p3", types.IntervalValueFromDuration(time.Duration(100500000000))), + ), + ), + ) + if err != nil { + return err + } + res := sugar.Result(r) + for res.NextResultSet(ctx) { + for res.NextRow() { + if err = res.ScanNamed( + named.Required("p1", &p1), + named.Required("p2", &p2), + named.Required("p3", &p3), + ); err != nil { + return err + } + } + } + + return res.Err() + }, query.WithIdempotent()) + require.NoError(t, err) + require.EqualValues(t, "test", p1) + require.EqualValues(t, 100500000000, p2) + require.EqualValues(t, time.Duration(100500000000), p3) + }) + }) + t.Run("ScanStruct", func(t *testing.T) { + t.Run("Sugar", func(t *testing.T) { + var data struct { + P1 *string `sql:"p1"` + P2 uint64 `sql:"p2"` + P3 time.Duration `sql:"p3"` + P4 *string `sql:"p4"` + } + err = db.Query().Do(ctx, func(ctx context.Context, s query.Session) (err error) { + _, r, err := s.Execute(ctx, ` + DECLARE $p1 AS Text; + DECLARE $p2 AS Uint64; + DECLARE $p3 AS Interval; + SELECT CAST($p1 AS Optional) AS p1, $p2 AS p2, $p3 AS p3, CAST(NULL AS Optional) AS p4; + `, + query.WithParameters( + ydb.ParamsBuilder(). + Param("$p1").Text("test"). + Param("$p2").Uint64(100500000000). + Param("$p3").Interval(time.Duration(100500000000)). + Build(), + ), + query.WithSyntax(query.SyntaxYQL), + ) + if err != nil { + return err + } + res := sugar.Result(r) + for res.NextResultSet(ctx) { + for res.NextRow() { + if err = res.ScanStruct(&data); err != nil { + return err + } + } + } + + return res.Err() + }, query.WithIdempotent()) + require.NoError(t, err) + require.NotNil(t, data.P1) + require.EqualValues(t, "test", *data.P1) + require.EqualValues(t, 100500000000, data.P2) + require.EqualValues(t, time.Duration(100500000000), data.P3) + require.Nil(t, data.P4) + }) + }) +} diff --git a/tests/integration/tx_test.go b/tests/integration/tx_test.go index a3c3db081..3c5666116 100644 --- a/tests/integration/tx_test.go +++ b/tests/integration/tx_test.go @@ -138,14 +138,14 @@ func TestNoEffectsIfForgetCommitTx(t *testing.T) { // check row for NO write var ( - value string - errConnAlreadyHaveTx *xsql.ErrConnAlreadyHaveTx + value string + connAlreadyHaveTxError *xsql.ConnAlreadyHaveTxError ) err = db.QueryRowContext(ctx, `SELECT val FROM table WHERE id = $1`, id).Scan(&value) require.ErrorIs(t, err, sql.ErrNoRows) // second tx on existing conn === session _, err = cc.BeginTx(ctx, &sql.TxOptions{}) - require.ErrorAs(t, err, &errConnAlreadyHaveTx) + require.ErrorAs(t, err, &connAlreadyHaveTxError) }) } 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/Dockerfile b/tests/slo/Dockerfile index 743535336..49226b98d 100644 --- a/tests/slo/Dockerfile +++ b/tests/slo/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.21 as build +FROM golang:1.22 as build ARG SRC_PATH ARG JOB_NAME COPY . /src diff --git a/tests/slo/database/sql/storage.go b/tests/slo/database/sql/storage.go index e04473c9a..8c740803c 100755 --- a/tests/slo/database/sql/storage.go +++ b/tests/slo/database/sql/storage.go @@ -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 b2c7d4951..e8f010142 100644 --- a/tests/slo/go.mod +++ b/tests/slo/go.mod @@ -5,8 +5,9 @@ go 1.21 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.49.0 - golang.org/x/sync v0.3.0 + 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 xorm.io/xorm v1.3.2 @@ -15,16 +16,19 @@ 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/uuid v1.3.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 github.com/jonboulle/clockwork v0.4.0 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/mattn/go-sqlite3 v1.14.16 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect @@ -37,18 +41,20 @@ 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 - golang.org/x/net v0.15.0 // indirect - golang.org/x/sys v0.12.0 // indirect - golang.org/x/text v0.13.0 // indirect - google.golang.org/genproto v0.0.0-20230526161137-0005af68ea54 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 // indirect - google.golang.org/grpc v1.57.1 // indirect - google.golang.org/protobuf v1.31.0 // 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/sys v0.16.0 // indirect + golang.org/x/text v0.14.0 // indirect + google.golang.org/genproto v0.0.0-20240102182953-50ed04b92917 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240108191215-35c7eff3a6b1 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1 // indirect + google.golang.org/grpc v1.60.1 // indirect + google.golang.org/protobuf v1.33.0 // indirect modernc.org/sqlite v1.24.0 // indirect xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978 // indirect ) replace github.com/ydb-platform/ydb-go-sdk/v3 => ../../. -replace xorm.io/xorm => github.com/ydb-platform/xorm v0.0.6 +replace xorm.io/xorm => github.com/ydb-platform/xorm v0.0.3 diff --git a/tests/slo/go.sum b/tests/slo/go.sum index 41160165a..78bb98723 100644 --- a/tests/slo/go.sum +++ b/tests/slo/go.sum @@ -598,14 +598,16 @@ git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3p gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s= gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU= gitee.com/travelliu/dm v1.8.11192/go.mod h1:DHTzyhCrM843x9VdKVbZ+GKXGRbKM2sJ4LxihRxShkE= -github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0/go.mod h1:h6H6c8enJmmocHUbLiiGY6sx7f9i+X3m1CHdd5c6Rdw= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0/go.mod h1:HcM1YX14R7CJcghJGOYCgdezslRSVzqwLf/q+4Y2r/0= -github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY= github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= @@ -619,13 +621,25 @@ github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHG github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0= github.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4xei5aX110hRiI= +github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= +github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= +github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= +github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= @@ -635,11 +649,9 @@ github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -655,18 +667,31 @@ github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/denisenkom/go-mssqldb v0.12.3/go.mod h1:k0mtMFOnU+AihqFxPMiF05rtiDrorD1Vrm1KEz5hxDo= -github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= +github.com/denisenkom/go-mssqldb v0.10.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -682,8 +707,11 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo= github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w= github.com/envoyproxy/protoc-gen-validate v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= +github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g= @@ -696,6 +724,7 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U= @@ -704,16 +733,26 @@ 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.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= -github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/goccy/go-json v0.8.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk= github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= @@ -721,11 +760,11 @@ github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= -github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -779,8 +818,9 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -802,11 +842,12 @@ github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= +github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= @@ -825,77 +866,119 @@ github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57Q github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w= +github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= +github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= +github.com/jackc/pgconn v1.4.0/go.mod h1:Y2O3ZDF0q4mMacyWV3AstPJpeHXWGEetiFttmq5lahk= +github.com/jackc/pgconn v1.5.0/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI= +github.com/jackc/pgconn v1.5.1-0.20200601181101-fa742c524853/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI= github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= +github.com/jackc/pgconn v1.8.1/go.mod h1:JV6m6b6jhjdmzchES0drzCcYcAHS1OPD5xu3OZ/lE2g= github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= -github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= -github.com/jackc/pgconn v1.14.0/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E= github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= -github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.3.2/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= -github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= -github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= -github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgtype v1.2.0/go.mod h1:5m2OfMh1wTK7x+Fk952IDmI4nw3nPrvtQdM0ZT4WpC0= +github.com/jackc/pgtype v1.3.1-0.20200510190516-8cd94a14c75a/go.mod h1:vaogEUkALtxZMCH411K+tKzNpwzCKU+AnPzBKZ+I+Po= +github.com/jackc/pgtype v1.3.1-0.20200606141011-f6355165a91c/go.mod h1:cvk9Bgu/VzJ9/lxTO5R5sf80p0DiucVtN7ZxvaC4GmQ= +github.com/jackc/pgtype v1.7.0/go.mod h1:ZnHF+rMePVqDKaOfJVI4Q8IVvAQMryDlDkZnKOI75BE= +github.com/jackc/pgtype v1.8.0/go.mod h1:PqDKcEBtllAtk/2p6z6SHdXW5UB+MhE75tUol2OKexE= github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= -github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= -github.com/jackc/pgx/v4 v4.18.0/go.mod h1:FydWkUyadDmdNH/mHnGob881GawxeEm7TcMCzkb+qQE= +github.com/jackc/pgx/v4 v4.5.0/go.mod h1:EpAKPLdnTorwmPUUsqrPxy5fphV18j9q3wrfRXgo+kA= +github.com/jackc/pgx/v4 v4.6.1-0.20200510190926-94ba730bb1e9/go.mod h1:t3/cdRQl6fOLDxqtlyhe9UWgfIi9R8+8v8GKV5TRA/o= +github.com/jackc/pgx/v4 v4.6.1-0.20200606145419-4e5062306904/go.mod h1:ZDaNWkt9sW1JMiNn0kdYBaLelIhw7Pg4qd+Vk6tw7Hg= +github.com/jackc/pgx/v4 v4.11.0/go.mod h1:i62xJgdrtVDsnL3U8ekyrQXEwGNTRoG7/8r+CIdYfcc= +github.com/jackc/pgx/v4 v4.12.0/go.mod h1:fE547h6VulLPA3kySjfnSG/e2D861g/50JlVUa/ub60= github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jonboulle/clockwork v0.3.0/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4= github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= @@ -915,26 +998,44 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= +github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= github.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o= +github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= -github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -942,56 +1043,93 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= +github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= +github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= +github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= +github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= +github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= +github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= +github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= github.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= +github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= -github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE= github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= -github.com/rekby/fixenv v0.3.2/go.mod h1:/b5LRc06BYJtslRtHKxsPWFT/ySpHV+rWvzTg+XWk4c= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rekby/fixenv v0.6.1 h1:jUFiSPpajT4WY2cYuc++7Y1zWrnCxnovGCIX72PZniM= +github.com/rekby/fixenv v0.6.1/go.mod h1:/b5LRc06BYJtslRtHKxsPWFT/ySpHV+rWvzTg+XWk4c= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= @@ -999,20 +1137,34 @@ github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/f github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= @@ -1029,17 +1181,20 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= +github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yandex-cloud/go-genproto v0.0.0-20211115083454-9ca41db5ed9e/go.mod h1:HEUYX/p8966tMUHHT+TsS0hF/Ca/NYwqprC5WXSDMfE= github.com/yandex-cloud/go-genproto v0.0.0-20230403093326-123923969dc6 h1:BkuaOCK1nc1eHSb/jCMwM2JZR00lJ9xNvnHvsZQgbRc= github.com/yandex-cloud/go-genproto v0.0.0-20230403093326-123923969dc6/go.mod h1:HEUYX/p8966tMUHHT+TsS0hF/Ca/NYwqprC5WXSDMfE= github.com/ydb-platform/gorm-driver v0.1.1 h1:PkN+sGSJehOZn9jQIFEmAOfhE73FNDMq+uzsnf7LVAM= github.com/ydb-platform/gorm-driver v0.1.1/go.mod h1:Zv368SD5tHqkblmaOG6r2KTvtSIzPuB5p8rBaE6wVmw= -github.com/ydb-platform/xorm v0.0.6 h1:mlclMIXR7Obwho3cYIIgBoMlMZ+APJZ9gnJQICyVAYY= -github.com/ydb-platform/xorm v0.0.6/go.mod h1:vLAI6Xqpa+48y9I9HJnjD6IDKp/GnATYbtDgWzQb88c= -github.com/ydb-platform/ydb-go-genproto v0.0.0-20240125100710-96fd3a874780 h1:E8Z7Zy/fKKDN4bYE1GvXX3DSjp9j7bPJy8Nnpe4Hxqg= -github.com/ydb-platform/ydb-go-genproto v0.0.0-20240125100710-96fd3a874780/go.mod h1:Er+FePu1dNUieD+XTMDduGpQuCPssK5Q4BjF+IIXJ3I= +github.com/ydb-platform/xorm v0.0.3 h1:MXk42lANB6r/MMLg/XdJfyXJycGUDlCeLiMlLGDKVPw= +github.com/ydb-platform/xorm v0.0.3/go.mod h1:hFsU7EUF0o3S+l5c0eyP2yPVjJ0d4gsFdqCsyazzwBc= github.com/ydb-platform/ydb-go-genproto v0.0.0-20240126124512-dbb0e1720dbf h1:ckwNHVo4bv2tqNkgx3W3HANh3ta1j6TR5qw08J1A7Tw= github.com/ydb-platform/ydb-go-genproto v0.0.0-20240126124512-dbb0e1720dbf/go.mod h1:Er+FePu1dNUieD+XTMDduGpQuCPssK5Q4BjF+IIXJ3I= github.com/ydb-platform/ydb-go-sdk-auth-environ v0.2.0 h1:IG5bPd+Lqyc+zsw2kmxqfGLkaDHuAEnWX63/8RBBiA4= @@ -1060,6 +1215,10 @@ github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= +go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -1068,6 +1227,12 @@ 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= @@ -1075,8 +1240,8 @@ go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/mock v0.3.1-0.20231011042131-892b665398ec h1:aB0WVMCyiVcqL1yMRLM4htiFlMvgdOml97GYnw9su5Q= -go.uber.org/mock v0.3.1-0.20231011042131-892b665398ec/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= +go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= +go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= @@ -1085,23 +1250,25 @@ go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 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.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 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/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= @@ -1166,8 +1333,12 @@ golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -1204,10 +1375,8 @@ golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLd golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= @@ -1229,8 +1398,9 @@ 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.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= 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= @@ -1276,12 +1446,17 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1294,9 +1469,11 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1318,6 +1495,7 @@ golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1341,6 +1519,7 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210902050250-f475640dd07b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1351,7 +1530,6 @@ golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1372,8 +1550,9 @@ 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 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= golang.org/x/sys v0.12.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= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1402,8 +1581,10 @@ golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1411,7 +1592,9 @@ golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1419,6 +1602,7 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= @@ -1497,6 +1681,7 @@ gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6d gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= gonum.org/v1/plot v0.9.0/go.mod h1:3Pcqqmp6RHvJI72kgb8fThyUnav364FOsdDo2aGW5lY= gonum.org/v1/plot v0.10.1/go.mod h1:VZW5OlhkL1mysU9vaqNHnsy86inf6Ot+jB3r+BczCEo= +google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -1555,6 +1740,7 @@ google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60c google.golang.org/api v0.111.0/go.mod h1:qtFHvU9mhgTJegR31csQ+rwxyUTHOKFqCKWp1J0fdw0= google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= @@ -1566,6 +1752,7 @@ google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRn google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= @@ -1682,6 +1869,7 @@ google.golang.org/genproto v0.0.0-20230123190316-2c411cf9d197/go.mod h1:RGgjbofJ google.golang.org/genproto v0.0.0-20230124163310-31e0e69b6fc2/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= google.golang.org/genproto v0.0.0-20230125152338-dcaf20b6aeaa/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= google.golang.org/genproto v0.0.0-20230127162408-596548ed4efa/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230131230820-1c016267d619/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= google.golang.org/genproto v0.0.0-20230209215440-0dfe4f8abfcc/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= google.golang.org/genproto v0.0.0-20230216225411-c8e22ba71e44/go.mod h1:8B0gmkoRebU8ukX6HP+4wrVQUY1+6PkQ44BSyIlflHA= google.golang.org/genproto v0.0.0-20230222225845-10f96fb3dbec/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw= @@ -1693,18 +1881,26 @@ google.golang.org/genproto v0.0.0-20230323212658-478b75c54725/go.mod h1:UUQDJDOl google.golang.org/genproto v0.0.0-20230330154414-c0448cd141ea/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= google.golang.org/genproto v0.0.0-20230525234025-438c736192d0/go.mod h1:9ExIQyXL5hZrHzQceCwuSYwZZ5QZBazOcprJ5rgs3lY= -google.golang.org/genproto v0.0.0-20230526161137-0005af68ea54 h1:9NWlQfY2ePejTmfwUH1OWwmznFa+0kKcHGPDvcPza9M= google.golang.org/genproto v0.0.0-20230526161137-0005af68ea54/go.mod h1:zqTuNwFlFRsw5zIts5VnzLQxSRqh+CGOTVMlYbY0Eyk= +google.golang.org/genproto v0.0.0-20240102182953-50ed04b92917 h1:nz5NESFLZbJGPFxDT/HCn+V1mZ8JGNoY4nUpmW/Y2eg= +google.golang.org/genproto v0.0.0-20240102182953-50ed04b92917/go.mod h1:pZqR+glSb11aJ+JQcczCvgf47+duRuzNSKqE8YAQnV0= google.golang.org/genproto/googleapis/api v0.0.0-20230525234020-1aefcd67740a/go.mod h1:ts19tUU+Z0ZShN1y3aPyq2+O3d5FUNNgT6FtOzmrNn8= -google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9 h1:m8v1xLLLzMe1m5P+gCTF8nJB9epwZQUBERm20Oy1poQ= google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= +google.golang.org/genproto/googleapis/api v0.0.0-20240108191215-35c7eff3a6b1 h1:OPXtXn7fNMaXwO3JvOmF1QyTc00jsSFFz1vXXBOdCDo= +google.golang.org/genproto/googleapis/api v0.0.0-20240108191215-35c7eff3a6b1/go.mod h1:B5xPO//w8qmBDjGReYLpR6UJPnkldGkCSMoH/2vxJeg= google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234015-3fc162c6f38a/go.mod h1:xURIpW9ES5+/GZhnV6beoEtxQrnkRGIfP5VQG2tCBLc= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 h1:0nDDozoAU19Qb2HwhXadU8OcsiO/09cnTqhUtq2MEOM= google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1 h1:gphdwh0npgs8elJ4T6J+DQJHPVF7RsuJHCfwztUb4J4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1/go.mod h1:daQN87bsDqDoe316QbbvX60nMoJQa4r6Ds0ZuoAe5yA= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= @@ -1741,8 +1937,9 @@ google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsA google.golang.org/grpc v1.52.3/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY= google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= -google.golang.org/grpc v1.57.1 h1:upNTNqv0ES+2ZOOqACwVtS3Il8M12/+Hz41RCPzAjQg= google.golang.org/grpc v1.57.1/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= +google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU= +google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -1761,35 +1958,39 @@ google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/gorm v1.25.1 h1:nsSALe5Pr+cM3V1qwwQ7rOkw+6UeLrX5O4v3llhHa64= gorm.io/gorm v1.25.1/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -1801,70 +2002,150 @@ honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +modernc.org/cc/v3 v3.33.6/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.33.9/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.33.11/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.34.0/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.35.0/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.35.4/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.35.5/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.35.7/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.35.8/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.35.10/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.35.15/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.35.16/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.35.17/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.35.18/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= modernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= modernc.org/cc/v3 v3.36.2/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= modernc.org/cc/v3 v3.36.3/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= -modernc.org/cc/v3 v3.37.0/go.mod h1:vtL+3mdHx/wcj3iEGz84rQa8vEqR6XM84v5Lcvfph20= -modernc.org/cc/v3 v3.38.1/go.mod h1:vtL+3mdHx/wcj3iEGz84rQa8vEqR6XM84v5Lcvfph20= modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw= modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0= modernc.org/ccgo/v3 v3.0.0-20220428102840-41399a37e894/go.mod h1:eI31LL8EwEBKPpNpA4bU1/i+sKOwOrQy8D87zWUcRZc= modernc.org/ccgo/v3 v3.0.0-20220430103911-bc99d88307be/go.mod h1:bwdAnOoaIt8Ax9YdWGjxWsdkPcZyRPHqrOvJxaKAKGw= -modernc.org/ccgo/v3 v3.0.0-20220904174949-82d86e1b6d56/go.mod h1:YSXjPL62P2AMSxBphRHPn7IkzhVHqkvOnRKAKh+W6ZI= -modernc.org/ccgo/v3 v3.0.0-20220910160915-348f15de615a/go.mod h1:8p47QxPkdugex9J4n9P2tLZ9bK01yngIVp00g4nomW0= +modernc.org/ccgo/v3 v3.9.5/go.mod h1:umuo2EP2oDSBnD3ckjaVUXMrmeAw8C8OSICVa0iFf60= +modernc.org/ccgo/v3 v3.10.0/go.mod h1:c0yBmkRFi7uW4J7fwx/JiijwOjeAeR2NoSaRVFPmjMw= +modernc.org/ccgo/v3 v3.11.0/go.mod h1:dGNposbDp9TOZ/1KBxghxtUp/bzErD0/0QW4hhSaBMI= +modernc.org/ccgo/v3 v3.11.1/go.mod h1:lWHxfsn13L3f7hgGsGlU28D9eUOf6y3ZYHKoPaKU0ag= +modernc.org/ccgo/v3 v3.11.3/go.mod h1:0oHunRBMBiXOKdaglfMlRPBALQqsfrCKXgw9okQ3GEw= +modernc.org/ccgo/v3 v3.12.4/go.mod h1:Bk+m6m2tsooJchP/Yk5ji56cClmN6R1cqc9o/YtbgBQ= +modernc.org/ccgo/v3 v3.12.6/go.mod h1:0Ji3ruvpFPpz+yu+1m0wk68pdr/LENABhTrDkMDWH6c= +modernc.org/ccgo/v3 v3.12.8/go.mod h1:Hq9keM4ZfjCDuDXxaHptpv9N24JhgBZmUG5q60iLgUo= +modernc.org/ccgo/v3 v3.12.11/go.mod h1:0jVcmyDwDKDGWbcrzQ+xwJjbhZruHtouiBEvDfoIsdg= +modernc.org/ccgo/v3 v3.12.14/go.mod h1:GhTu1k0YCpJSuWwtRAEHAol5W7g1/RRfS4/9hc9vF5I= +modernc.org/ccgo/v3 v3.12.18/go.mod h1:jvg/xVdWWmZACSgOiAhpWpwHWylbJaSzayCqNOJKIhs= +modernc.org/ccgo/v3 v3.12.20/go.mod h1:aKEdssiu7gVgSy/jjMastnv/q6wWGRbszbheXgWRHc8= +modernc.org/ccgo/v3 v3.12.21/go.mod h1:ydgg2tEprnyMn159ZO/N4pLBqpL7NOkJ88GT5zNU2dE= +modernc.org/ccgo/v3 v3.12.22/go.mod h1:nyDVFMmMWhMsgQw+5JH6B6o4MnZ+UQNw1pp52XYFPRk= +modernc.org/ccgo/v3 v3.12.25/go.mod h1:UaLyWI26TwyIT4+ZFNjkyTbsPsY3plAEB6E7L/vZV3w= +modernc.org/ccgo/v3 v3.12.29/go.mod h1:FXVjG7YLf9FetsS2OOYcwNhcdOLGt8S9bQ48+OP75cE= +modernc.org/ccgo/v3 v3.12.36/go.mod h1:uP3/Fiezp/Ga8onfvMLpREq+KUjUmYMxXPO8tETHtA8= +modernc.org/ccgo/v3 v3.12.38/go.mod h1:93O0G7baRST1vNj4wnZ49b1kLxt0xCW5Hsa2qRaZPqc= +modernc.org/ccgo/v3 v3.12.43/go.mod h1:k+DqGXd3o7W+inNujK15S5ZYuPoWYLpF5PYougCmthU= +modernc.org/ccgo/v3 v3.12.46/go.mod h1:UZe6EvMSqOxaJ4sznY7b23/k13R8XNlyWsO5bAmSgOE= +modernc.org/ccgo/v3 v3.12.47/go.mod h1:m8d6p0zNps187fhBwzY/ii6gxfjob1VxWb919Nk1HUk= +modernc.org/ccgo/v3 v3.12.50/go.mod h1:bu9YIwtg+HXQxBhsRDE+cJjQRuINuT9PUK4orOco/JI= +modernc.org/ccgo/v3 v3.12.51/go.mod h1:gaIIlx4YpmGO2bLye04/yeblmvWEmE4BBBls4aJXFiE= +modernc.org/ccgo/v3 v3.12.53/go.mod h1:8xWGGTFkdFEWBEsUmi+DBjwu/WLy3SSOrqEmKUjMeEg= +modernc.org/ccgo/v3 v3.12.54/go.mod h1:yANKFTm9llTFVX1FqNKHE0aMcQb1fuPJx6p8AcUx+74= +modernc.org/ccgo/v3 v3.12.55/go.mod h1:rsXiIyJi9psOwiBkplOaHye5L4MOOaCjHg1Fxkj7IeU= +modernc.org/ccgo/v3 v3.12.56/go.mod h1:ljeFks3faDseCkr60JMpeDb2GSO3TKAmrzm7q9YOcMU= +modernc.org/ccgo/v3 v3.12.57/go.mod h1:hNSF4DNVgBl8wYHpMvPqQWDQx8luqxDnNGCMM4NFNMc= +modernc.org/ccgo/v3 v3.12.60/go.mod h1:k/Nn0zdO1xHVWjPYVshDeWKqbRWIfif5dtsIOCUVMqM= +modernc.org/ccgo/v3 v3.12.65/go.mod h1:D6hQtKxPNZiY6wDBtehSGKFKmyXn53F8nGTpH+POmS4= +modernc.org/ccgo/v3 v3.12.66/go.mod h1:jUuxlCFZTUZLMV08s7B1ekHX5+LIAurKTTaugUr/EhQ= +modernc.org/ccgo/v3 v3.12.67/go.mod h1:Bll3KwKvGROizP2Xj17GEGOTrlvB1XcVaBrC90ORO84= +modernc.org/ccgo/v3 v3.12.73/go.mod h1:hngkB+nUUqzOf3iqsM48Gf1FZhY599qzVg1iX+BT3cQ= +modernc.org/ccgo/v3 v3.12.81/go.mod h1:p2A1duHoBBg1mFtYvnhAnQyI6vL0uw5PGYLSIgF6rYY= +modernc.org/ccgo/v3 v3.12.82/go.mod h1:ApbflUfa5BKadjHynCficldU1ghjen84tuM5jRynB7w= modernc.org/ccgo/v3 v3.16.4/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= modernc.org/ccgo/v3 v3.16.6/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= modernc.org/ccgo/v3 v3.16.8/go.mod h1:zNjwkizS+fIFDrDjIAgBSCLkWbJuHF+ar3QRn+Z9aws= modernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo= -modernc.org/ccgo/v3 v3.16.13-0.20221017192402-261537637ce8/go.mod h1:fUB3Vn0nVPReA+7IG7yZDfjv1TMWjhQP8gCxrFAtL5g= modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw= modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY= +modernc.org/ccorpus v1.11.1/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= modernc.org/libc v0.0.0-20220428101251-2d5f3daf273b/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= +modernc.org/libc v1.9.8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w= +modernc.org/libc v1.9.11/go.mod h1:NyF3tsA5ArIjJ83XB0JlqhjTabTCHm9aX4XMPHyQn0Q= +modernc.org/libc v1.11.0/go.mod h1:2lOfPmj7cz+g1MrPNmX65QCzVxgNq2C5o0jdLY2gAYg= +modernc.org/libc v1.11.2/go.mod h1:ioIyrl3ETkugDO3SGZ+6EOKvlP3zSOycUETe4XM4n8M= +modernc.org/libc v1.11.5/go.mod h1:k3HDCP95A6U111Q5TmG3nAyUcp3kR5YFZTeDS9v8vSU= +modernc.org/libc v1.11.6/go.mod h1:ddqmzR6p5i4jIGK1d/EiSw97LBcE3dK24QEwCFvgNgE= +modernc.org/libc v1.11.11/go.mod h1:lXEp9QOOk4qAYOtL3BmMve99S5Owz7Qyowzvg6LiZso= +modernc.org/libc v1.11.13/go.mod h1:ZYawJWlXIzXy2Pzghaf7YfM8OKacP3eZQI81PDLFdY8= +modernc.org/libc v1.11.16/go.mod h1:+DJquzYi+DMRUtWI1YNxrlQO6TcA5+dRRiq8HWBWRC8= +modernc.org/libc v1.11.19/go.mod h1:e0dgEame6mkydy19KKaVPBeEnyJB4LGNb0bBH1EtQ3I= +modernc.org/libc v1.11.24/go.mod h1:FOSzE0UwookyT1TtCJrRkvsOrX2k38HoInhw+cSCUGk= +modernc.org/libc v1.11.26/go.mod h1:SFjnYi9OSd2W7f4ct622o/PAYqk7KHv6GS8NZULIjKY= +modernc.org/libc v1.11.27/go.mod h1:zmWm6kcFXt/jpzeCgfvUNswM0qke8qVwxqZrnddlDiE= +modernc.org/libc v1.11.28/go.mod h1:Ii4V0fTFcbq3qrv3CNn+OGHAvzqMBvC7dBNyC4vHZlg= +modernc.org/libc v1.11.31/go.mod h1:FpBncUkEAtopRNJj8aRo29qUiyx5AvAlAxzlx9GNaVM= +modernc.org/libc v1.11.34/go.mod h1:+Tzc4hnb1iaX/SKAutJmfzES6awxfU1BPvrrJO0pYLg= +modernc.org/libc v1.11.37/go.mod h1:dCQebOwoO1046yTrfUE5nX1f3YpGZQKNcITUYWlrAWo= +modernc.org/libc v1.11.39/go.mod h1:mV8lJMo2S5A31uD0k1cMu7vrJbSA3J3waQJxpV4iqx8= +modernc.org/libc v1.11.42/go.mod h1:yzrLDU+sSjLE+D4bIhS7q1L5UwXDOw99PLSX0BlZvSQ= +modernc.org/libc v1.11.44/go.mod h1:KFq33jsma7F5WXiYelU8quMJasCCTnHK0mkri4yPHgA= +modernc.org/libc v1.11.45/go.mod h1:Y192orvfVQQYFzCNsn+Xt0Hxt4DiO4USpLNXBlXg/tM= +modernc.org/libc v1.11.47/go.mod h1:tPkE4PzCTW27E6AIKIR5IwHAQKCAtudEIeAV1/SiyBg= +modernc.org/libc v1.11.49/go.mod h1:9JrJuK5WTtoTWIFQ7QjX2Mb/bagYdZdscI3xrvHbXjE= +modernc.org/libc v1.11.51/go.mod h1:R9I8u9TS+meaWLdbfQhq2kFknTW0O3aw3kEMqDDxMaM= +modernc.org/libc v1.11.53/go.mod h1:5ip5vWYPAoMulkQ5XlSJTy12Sz5U6blOQiYasilVPsU= +modernc.org/libc v1.11.54/go.mod h1:S/FVnskbzVUrjfBqlGFIPA5m7UwB3n9fojHhCNfSsnw= +modernc.org/libc v1.11.55/go.mod h1:j2A5YBRm6HjNkoSs/fzZrSxCuwWqcMYTDPLNx0URn3M= +modernc.org/libc v1.11.56/go.mod h1:pakHkg5JdMLt2OgRadpPOTnyRXm/uzu+Yyg/LSLdi18= +modernc.org/libc v1.11.58/go.mod h1:ns94Rxv0OWyoQrDqMFfWwka2BcaF6/61CqJRK9LP7S8= +modernc.org/libc v1.11.70/go.mod h1:DUOmMYe+IvKi9n6Mycyx3DbjfzSKrdr/0Vgt3j7P5gw= +modernc.org/libc v1.11.71/go.mod h1:DUOmMYe+IvKi9n6Mycyx3DbjfzSKrdr/0Vgt3j7P5gw= +modernc.org/libc v1.11.75/go.mod h1:dGRVugT6edz361wmD9gk6ax1AbDSe0x5vji0dGJiPT0= +modernc.org/libc v1.11.82/go.mod h1:NF+Ek1BOl2jeC7lw3a7Jj5PWyHPwWD4aq3wVKxqV1fI= +modernc.org/libc v1.11.86/go.mod h1:ePuYgoQLmvxdNT06RpGnaDKJmDNEkV7ZPKI2jnsvZoE= +modernc.org/libc v1.11.87/go.mod h1:Qvd5iXTeLhI5PS0XSyqMY99282y+3euapQFxM7jYnpY= modernc.org/libc v1.16.0/go.mod h1:N4LD6DBE9cf+Dzf9buBlzVJndKr/iJHG97vGLHYnb5A= modernc.org/libc v1.16.1/go.mod h1:JjJE0eu4yeK7tab2n4S1w8tlWd9MxXLRzheaRnAKymU= modernc.org/libc v1.16.17/go.mod h1:hYIV5VZczAmGZAnG15Vdngn5HSF5cSkbvfz2B7GRuVU= modernc.org/libc v1.16.19/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= modernc.org/libc v1.17.0/go.mod h1:XsgLldpP4aWlPlsjqKRdHPqCxCjISdHfM/yeWC5GyW0= modernc.org/libc v1.17.1/go.mod h1:FZ23b+8LjxZs7XtFMbSzL/EhPxNbfZbErxEHc7cbD9s= -modernc.org/libc v1.17.4/go.mod h1:WNg2ZH56rDEwdropAJeZPQkXmDwh+JCA1s/htl6r2fA= -modernc.org/libc v1.18.0/go.mod h1:vj6zehR5bfc98ipowQOM2nIDUZnVew/wNC/2tOGS+q0= -modernc.org/libc v1.19.0/go.mod h1:ZRfIaEkgrYgZDl6pa4W39HgN5G/yDW+NRmNKZBDFrk0= -modernc.org/libc v1.20.3/go.mod h1:ZRfIaEkgrYgZDl6pa4W39HgN5G/yDW+NRmNKZBDFrk0= -modernc.org/libc v1.21.4/go.mod h1:przBsL5RDOZajTVslkugzLBj1evTue36jEomFQOoYuI= -modernc.org/libc v1.22.2/go.mod h1:uvQavJ1pZ0hIoC/jfqNoMLURIMhKzINIWypNM17puug= modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE= +modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY= +modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/mathutil v1.4.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/memory v1.0.4/go.mod h1:nV2OApxradM3/OVbs2/0OsP6nPfakXpi50C7dcoHXlc= +modernc.org/memory v1.0.5/go.mod h1:B7OYswTRnfGg+4tDH1t1OeUNnsy2viGTdME4tzd+IjM= modernc.org/memory v1.1.1/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= modernc.org/memory v1.2.0/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= modernc.org/memory v1.2.1/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= -modernc.org/memory v1.3.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= -modernc.org/memory v1.4.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds= +modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/sqlite v1.14.2/go.mod h1:yqfn85u8wVOE6ub5UT8VI9JjhrwBUUCNyTACN0h6Sx8= modernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4= -modernc.org/sqlite v1.20.4/go.mod h1:zKcGyrICaxNTMEHSr1HQ2GUraP0j+845GYw37+EyT6A= modernc.org/sqlite v1.24.0 h1:EsClRIWHGhLTCX44p+Ri/JLD+vFGo0QGjasg2/F9TlI= modernc.org/sqlite v1.24.0/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk= modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY= modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= +modernc.org/tcl v1.8.13/go.mod h1:V+q/Ef0IJaNUSECieLU4o+8IScapxnMyFV6i/7uQlAY= modernc.org/tcl v1.13.1/go.mod h1:XOLfOwzhkljL4itZkK6T72ckMgvj0BDsnKNdZVUOecw= -modernc.org/tcl v1.15.0/go.mod h1:xRoGotBZ6dU+Zo2tca+2EqVEeMmOUBzHnhIwq4YrVnE= modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg= modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +modernc.org/z v1.2.19/go.mod h1:+ZpP0pc4zz97eukOzW3xagV/lS82IpPN9NGG5pNF9vY= modernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8= -modernc.org/z v1.7.0/go.mod h1:hVdgNMh8ggTuRG1rGU8x+xGRFfiQUIAw0ZqlPy8+HyQ= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978 h1:bvLlAPW1ZMTWA32LuZMBEGHAUOcATZjzHcotf3SWweM= xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE= 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/generator/row.go b/tests/slo/internal/generator/row.go index 7a2d6aa2b..2eb2c1fc7 100644 --- a/tests/slo/internal/generator/row.go +++ b/tests/slo/internal/generator/row.go @@ -4,11 +4,12 @@ import "time" type RowID = uint64 +//nolint:tagalign type Row struct { - Hash uint64 `gorm:"column:hash;primarykey;autoIncrement:false" xorm:"pk 'hash'"` - ID RowID `gorm:"column:id;primarykey;autoIncrement:false" xorm:"pk 'id'"` //nolint:tagalign - PayloadStr *string `gorm:"column:payload_str" xorm:"'payload_str'"` //nolint:tagalign - PayloadDouble *float64 `gorm:"column:payload_double" xorm:"'payload_double'"` //nolint:tagalign - PayloadTimestamp *time.Time `gorm:"column:payload_timestamp" xorm:"'payload_timestamp'"` //nolint:tagalign - PayloadHash uint64 `gorm:"column:payload_hash" xorm:"'payload_hash'"` //nolint:tagalign + Hash uint64 `sql:"hash" gorm:"column:hash;primarykey;autoIncrement:false" xorm:"pk 'hash'"` + ID RowID `sql:"id" gorm:"column:id;primarykey;autoIncrement:false" xorm:"pk 'id'"` + PayloadStr *string `sql:"payload_str" gorm:"column:payload_str" xorm:"'payload_str'"` + PayloadDouble *float64 `sql:"payload_double" gorm:"column:payload_double" xorm:"'payload_double'"` + PayloadTimestamp *time.Time `sql:"payload_timestamp" gorm:"column:payload_timestamp" xorm:"'payload_timestamp'"` + PayloadHash uint64 `sql:"payload_hash" gorm:"column:payload_hash" xorm:"'payload_hash'"` } diff --git a/tests/slo/native/query/main.go b/tests/slo/native/query/main.go new file mode 100644 index 000000000..138187877 --- /dev/null +++ b/tests/slo/native/query/main.go @@ -0,0 +1,139 @@ +package main + +import ( + "context" + "fmt" + "os/signal" + "sync" + "syscall" + "time" + + "golang.org/x/sync/errgroup" + "golang.org/x/time/rate" + + "slo/internal/config" + "slo/internal/generator" + "slo/internal/workers" +) + +var ( + label string + jobName string +) + +func main() { + ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT) + defer cancel() + + cfg, err := config.New() + if err != nil { + panic(fmt.Errorf("create config failed: %w", err)) + } + + fmt.Println("program started") + defer fmt.Println("program finished") + + ctx, cancel = context.WithTimeout(ctx, time.Duration(cfg.Time)*time.Second) + defer cancel() + + s, err := NewStorage(ctx, cfg, cfg.ReadRPS+cfg.WriteRPS) + if err != nil { + panic(fmt.Errorf("create storage failed: %w", err)) + } + defer func() { + 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) + }() + + fmt.Println("db init ok") + + switch cfg.Mode { + case config.CreateMode: + err = s.createTable(ctx) + if err != nil { + panic(fmt.Errorf("create table failed: %w", err)) + } + fmt.Println("create table ok") + + gen := generator.New(0) + + g := errgroup.Group{} + + for i := uint64(0); i < cfg.InitialDataCount; i++ { + g.Go(func() (err error) { + e, err := gen.Generate() + if err != nil { + return err + } + + _, err = s.Write(ctx, e) + if err != nil { + return err + } + + return nil + }) + } + + err = g.Wait() + if err != nil { + panic(err) + } + + fmt.Println("entries write ok") + case config.CleanupMode: + err = s.dropTable(ctx) + if err != nil { + panic(fmt.Errorf("create table failed: %w", err)) + } + + fmt.Println("cleanup table ok") + case config.RunMode: + gen := generator.New(cfg.InitialDataCount) + + w, err := workers.New(cfg, s, label, jobName) + if err != nil { + panic(fmt.Errorf("create workers failed: %w", err)) + } + defer func() { + err := w.Close() + if err != nil { + panic(fmt.Errorf("workers close failed: %w", err)) + } + fmt.Println("workers close ok") + }() + + wg := sync.WaitGroup{} + + readRL := rate.NewLimiter(rate.Limit(cfg.ReadRPS), 1) + wg.Add(cfg.ReadRPS) + for i := 0; i < cfg.ReadRPS; i++ { + go w.Read(ctx, &wg, readRL) + } + + writeRL := rate.NewLimiter(rate.Limit(cfg.WriteRPS), 1) + wg.Add(cfg.WriteRPS) + for i := 0; i < cfg.WriteRPS; i++ { + go w.Write(ctx, &wg, writeRL, gen) + } + + metricsRL := rate.NewLimiter(rate.Every(time.Duration(cfg.ReportPeriod)*time.Millisecond), 1) + wg.Add(1) + go w.Metrics(ctx, &wg, metricsRL) + + wg.Wait() + default: + panic(fmt.Errorf("unknown mode: %v", cfg.Mode)) + } +} diff --git a/tests/slo/native/query/storage.go b/tests/slo/native/query/storage.go new file mode 100755 index 000000000..89377d012 --- /dev/null +++ b/tests/slo/native/query/storage.go @@ -0,0 +1,269 @@ +package main + +import ( + "context" + "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" + + "slo/internal/config" + "slo/internal/generator" +) + +type Storage struct { + db *ydb.Driver + cfg *config.Config + tablePath string +} + +const writeQuery = ` +DECLARE $id AS Uint64; +DECLARE $payload_str AS Utf8; +DECLARE $payload_double AS Double; +DECLARE $payload_timestamp AS Timestamp; + +UPSERT INTO %s ( + id, hash, payload_str, payload_double, payload_timestamp +) VALUES ( + $id, Digest::NumericHash($id), $payload_str, $payload_double, $payload_timestamp +); +` + +const readQuery = ` +DECLARE $id AS Uint64; +SELECT id, payload_str, payload_double, payload_timestamp, payload_hash +FROM %s WHERE id = $id AND hash = Digest::NumericHash($id); +` + +const createTableQuery = ` +CREATE TABLE IF NOT EXISTS %s ( + hash Uint64?, + id Uint64?, + payload_str Text?, + payload_double Double?, + payload_timestamp Timestamp?, + payload_hash Uint64?, + PRIMARY KEY (hash, id) +) WITH ( + UNIFORM_PARTITIONS = %d, + AUTO_PARTITIONING_BY_SIZE = ENABLED, + AUTO_PARTITIONING_PARTITION_SIZE_MB = %d, + AUTO_PARTITIONING_MIN_PARTITIONS_COUNT = %d, + AUTO_PARTITIONING_MAX_PARTITIONS_COUNT = %d +) +` + +const dropTableQuery = ` +DROP TABLE %s +` + +func NewStorage(ctx context.Context, cfg *config.Config, poolSize int) (*Storage, error) { + ctx, cancel := context.WithTimeout(ctx, time.Minute*5) + defer cancel() + + db, err := ydb.Open(ctx, + cfg.Endpoint+cfg.DB, + ydb.WithSessionPoolSizeLimit(poolSize), + ydb.WithLogger(log.Default(os.Stderr, log.WithMinLevel(log.ERROR)), trace.DetailsAll), + ) + if err != nil { + return nil, err + } + + prefix := path.Join(db.Name(), label) + + s := &Storage{ + db: db, + cfg: cfg, + tablePath: "`" + path.Join(prefix, cfg.Table) + "`", + } + + return s, nil +} + +func (s *Storage) Read(ctx context.Context, entryID generator.RowID) (_ generator.Row, attempts int, finalErr error) { + if err := ctx.Err(); err != nil { + return generator.Row{}, attempts, err + } + + ctx, cancel := context.WithTimeout(ctx, time.Duration(s.cfg.ReadTimeout)*time.Millisecond) + defer cancel() + + e := generator.Row{} + + err := s.db.Query().Do(ctx, + func(ctx context.Context, session query.Session) (err error) { + if err = ctx.Err(); err != nil { + return err + } + + _, res, err := session.Execute(ctx, + fmt.Sprintf(readQuery, s.tablePath), + query.WithParameters( + ydb.ParamsBuilder(). + Param("$id").Uint64(entryID). + Build(), + ), + query.WithTxControl(query.TxControl( + query.BeginTx(query.WithOnlineReadOnly()), + query.CommitTx(), + )), + ) + if err != nil { + return err + } + defer func() { + _ = res.Close(ctx) + }() + + rs, err := res.NextResultSet(ctx) + if err != nil { + if errors.Is(err, io.EOF) { + return nil + } + + return err + } + + row, err := rs.NextRow(ctx) + if err != nil { + if errors.Is(err, io.EOF) { + return nil + } + + return err + } + + err = row.ScanStruct(&e, query.WithScanStructAllowMissingColumnsFromSelect()) + if err != nil { + return err + } + + return res.Err() + }, + query.WithIdempotent(), + query.WithTrace(&trace.Query{ + OnDo: func(info trace.QueryDoStartInfo) func(trace.QueryDoDoneInfo) { + return func(info trace.QueryDoDoneInfo) { + attempts = info.Attempts + } + }, + }), + query.WithLabel("READ"), + ) + + return e, attempts, err +} + +func (s *Storage) Write(ctx context.Context, e generator.Row) (attempts int, finalErr error) { + if err := ctx.Err(); err != nil { + return attempts, err + } + + ctx, cancel := context.WithTimeout(ctx, time.Duration(s.cfg.WriteTimeout)*time.Millisecond) + defer cancel() + + err := s.db.Query().Do(ctx, + func(ctx context.Context, session query.Session) (err error) { + if err = ctx.Err(); err != nil { + return err + } + + _, res, err := session.Execute(ctx, + fmt.Sprintf(writeQuery, s.tablePath), + query.WithParameters( + ydb.ParamsBuilder(). + Param("$id").Uint64(e.ID). + Param("$payload_str").Text(*e.PayloadStr). + Param("$payload_double").Double(*e.PayloadDouble). + Param("$payload_timestamp").Timestamp(*e.PayloadTimestamp). + Build(), + ), + ) + if err != nil { + return err + } + + defer func() { + _ = res.Close(ctx) + }() + + return res.Err() + }, + query.WithIdempotent(), + query.WithTrace(&trace.Query{ + OnDo: func(info trace.QueryDoStartInfo) func(trace.QueryDoDoneInfo) { + return func(info trace.QueryDoDoneInfo) { + attempts = info.Attempts + } + }, + }), + query.WithLabel("WRITE"), + ) + + return attempts, err +} + +func (s *Storage) createTable(ctx context.Context) error { + ctx, cancel := context.WithTimeout(ctx, time.Duration(s.cfg.WriteTimeout)*time.Millisecond) + defer cancel() + + return s.db.Query().Do(ctx, + func(ctx context.Context, session query.Session) error { + _, _, err := session.Execute(ctx, + fmt.Sprintf(createTableQuery, s.tablePath, s.cfg.MinPartitionsCount, s.cfg.PartitionSize, + s.cfg.MinPartitionsCount, s.cfg.MaxPartitionsCount, + ), + query.WithTxControl(query.NoTx())) + + return err + }, query.WithIdempotent(), + query.WithLabel("CREATE TABLE"), + ) +} + +func (s *Storage) dropTable(ctx context.Context) error { + err := ctx.Err() + if err != nil { + return err + } + + ctx, cancel := context.WithTimeout(ctx, time.Duration(s.cfg.WriteTimeout)*time.Millisecond) + defer cancel() + + return s.db.Query().Do(ctx, + func(ctx context.Context, session query.Session) error { + _, _, err := session.Execute(ctx, + fmt.Sprintf(dropTableQuery, s.tablePath), + query.WithTxControl(query.NoTx()), + ) + + return err + }, + query.WithIdempotent(), + query.WithLabel("DROP TABLE"), + ) +} + +func (s *Storage) close(ctx context.Context) error { + 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(shutdownCtx) +} diff --git a/tests/slo/native/main.go b/tests/slo/native/table/main.go similarity index 100% rename from tests/slo/native/main.go rename to tests/slo/native/table/main.go diff --git a/tests/slo/native/storage.go b/tests/slo/native/table/storage.go similarity index 91% rename from tests/slo/native/storage.go rename to tests/slo/native/table/storage.go index 0024266a8..220054aa0 100755 --- a/tests/slo/native/storage.go +++ b/tests/slo/native/table/storage.go @@ -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 13d3e7369..8caf4b9bf 100644 --- a/testutil/compare.go +++ b/testutil/compare.go @@ -34,10 +34,10 @@ func Compare(l, r value.Value) (int, error) { } func unwrapTypedValue(v *Ydb.TypedValue) *Ydb.TypedValue { - typ := v.Type - val := v.Value + typ := v.GetType() + val := v.GetValue() for opt := typ.GetOptionalType(); opt != nil; opt = typ.GetOptionalType() { - typ = opt.Item + typ = opt.GetItem() if nested := val.GetNestedValue(); nested != nil { val = nested } @@ -47,18 +47,18 @@ func unwrapTypedValue(v *Ydb.TypedValue) *Ydb.TypedValue { } func compare(lhs, rhs *Ydb.TypedValue) (int, error) { - lTypeID := lhs.Type.GetTypeId() - rTypeID := rhs.Type.GetTypeId() + lTypeID := lhs.GetType().GetTypeId() + rTypeID := rhs.GetType().GetTypeId() switch { case lTypeID != rTypeID: return 0, notComparableError(lhs, rhs) case lTypeID != Ydb.Type_PRIMITIVE_TYPE_ID_UNSPECIFIED: - return comparePrimitives(lTypeID, lhs.Value, rhs.Value) - case lhs.Type.GetTupleType() != nil && rhs.Type.GetTupleType() != nil: + return comparePrimitives(lTypeID, lhs.GetValue(), rhs.GetValue()) + case lhs.GetType().GetTupleType() != nil && rhs.GetType().GetTupleType() != nil: return compareTuplesOrLists(expandTuple(lhs), expandTuple(rhs)) - case lhs.Type.GetListType() != nil && rhs.Type.GetListType() != nil: + case lhs.GetType().GetListType() != nil && rhs.GetType().GetListType() != nil: return compareTuplesOrLists(expandList(lhs), expandList(rhs)) - case lhs.Type.GetStructType() != nil && rhs.Type.GetStructType() != nil: + case lhs.GetType().GetStructType() != nil && rhs.GetType().GetStructType() != nil: return compareStructs(expandStruct(lhs), expandStruct(rhs)) default: return 0, notComparableError(lhs, rhs) @@ -88,11 +88,11 @@ func expandStruct(v *Ydb.TypedValue) []*Ydb.TypedValue { } func expandTuple(v *Ydb.TypedValue) []*Ydb.TypedValue { - tuple := v.Type.GetTupleType() - size := len(tuple.Elements) + tuple := v.GetType().GetTupleType() + size := len(tuple.GetElements()) values := make([]*Ydb.TypedValue, 0, size) - for idx, typ := range tuple.Elements { - values = append(values, unwrapTypedValue(&Ydb.TypedValue{Type: typ, Value: v.Value.Items[idx]})) + for idx, typ := range tuple.GetElements() { + values = append(values, unwrapTypedValue(&Ydb.TypedValue{Type: typ, Value: v.GetValue().GetItems()[idx]})) } return values @@ -103,8 +103,8 @@ func notComparableError(lhs, rhs interface{}) error { } func comparePrimitives(t Ydb.Type_PrimitiveTypeId, lhs, rhs *Ydb.Value) (int, error) { - _, lIsNull := lhs.Value.(*Ydb.Value_NullFlagValue) - _, rIsNull := rhs.Value.(*Ydb.Value_NullFlagValue) + _, lIsNull := lhs.GetValue().(*Ydb.Value_NullFlagValue) + _, rIsNull := rhs.GetValue().(*Ydb.Value_NullFlagValue) if lIsNull { if rIsNull { return 0, nil diff --git a/testutil/compare_test.go b/testutil/compare_test.go index 5041b1b74..45f2ed2c1 100644 --- a/testutil/compare_test.go +++ b/testutil/compare_test.go @@ -8,8 +8,8 @@ import ( "google.golang.org/protobuf/types/known/structpb" "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/value" - "github.com/ydb-platform/ydb-go-sdk/v3/table/types" ) func TestUnwrapOptionalValue(t *testing.T) { @@ -17,11 +17,11 @@ func TestUnwrapOptionalValue(t *testing.T) { defer a.Free() v := value.OptionalValue(value.OptionalValue(value.TextValue("a"))) val := unwrapTypedValue(value.ToYDB(v, a)) - typeID := val.Type.GetTypeId() + typeID := val.GetType().GetTypeId() if typeID != Ydb.Type_UTF8 { t.Errorf("Types are different: expected %d, actual %d", Ydb.Type_UTF8, typeID) } - textValue := val.Value.Value.(*Ydb.Value_TextValue) + textValue := val.GetValue().GetValue().(*Ydb.Value_TextValue) text := textValue.TextValue if text != "a" { t.Errorf("Values are different: expected %q, actual %q", "a", text) @@ -33,11 +33,11 @@ func TestUnwrapPrimitiveValue(t *testing.T) { defer a.Free() v := value.TextValue("a") val := unwrapTypedValue(value.ToYDB(v, a)) - typeID := val.Type.GetTypeId() + typeID := val.GetType().GetTypeId() if typeID != Ydb.Type_UTF8 { t.Errorf("Types are different: expected %d, actual %d", Ydb.Type_UTF8, typeID) } - textValue := val.Value.Value.(*Ydb.Value_TextValue) + textValue := val.GetValue().GetValue().(*Ydb.Value_TextValue) text := textValue.TextValue if text != "a" { t.Errorf("Values are different: expected %q, actual %q", "a", text) @@ -47,21 +47,21 @@ func TestUnwrapPrimitiveValue(t *testing.T) { func TestUnwrapNullValue(t *testing.T) { a := allocator.New() defer a.Free() - v := value.NullValue(value.TypeText) + v := value.NullValue(types.Text) val := unwrapTypedValue(value.ToYDB(v, a)) - typeID := val.Type.GetTypeId() + typeID := val.GetType().GetTypeId() if typeID != Ydb.Type_UTF8 { t.Errorf("Types are different: expected %d, actual %d", Ydb.Type_UTF8, typeID) } - nullFlagValue := val.Value.Value.(*Ydb.Value_NullFlagValue) + nullFlagValue := val.GetValue().GetValue().(*Ydb.Value_NullFlagValue) if nullFlagValue.NullFlagValue != structpb.NullValue_NULL_VALUE { t.Errorf("Values are different: expected %d, actual %d", structpb.NullValue_NULL_VALUE, nullFlagValue.NullFlagValue) } } func TestUint8(t *testing.T) { - l := types.Uint8Value(byte(1)) - r := types.Uint8Value(byte(10)) + l := value.Uint8Value(byte(1)) + r := value.Uint8Value(byte(10)) c, err := Compare(l, r) requireNoError(t, err) requireEqualValues(t, -1, c) @@ -76,8 +76,8 @@ func TestUint8(t *testing.T) { } func TestInt8(t *testing.T) { - l := types.Int8Value(int8(1)) - r := types.Int8Value(int8(10)) + l := value.Int8Value(int8(1)) + r := value.Int8Value(int8(10)) c, err := Compare(l, r) requireNoError(t, err) requireEqualValues(t, -1, c) @@ -92,8 +92,8 @@ func TestInt8(t *testing.T) { } func TestTimestamp(t *testing.T) { - l := types.TimestampValue(1) - r := types.TimestampValue(10) + l := value.TimestampValue(1) + r := value.TimestampValue(10) c, err := Compare(l, r) requireNoError(t, err) requireEqualValues(t, -1, c) @@ -108,8 +108,8 @@ func TestTimestamp(t *testing.T) { } func TestDateTime(t *testing.T) { - l := types.DatetimeValue(1) - r := types.DatetimeValue(10) + l := value.DatetimeValue(1) + r := value.DatetimeValue(10) c, err := Compare(l, r) requireNoError(t, err) requireEqualValues(t, -1, c) @@ -124,8 +124,8 @@ func TestDateTime(t *testing.T) { } func TestUint64(t *testing.T) { - l := types.Uint64Value(uint64(1)) - r := types.Uint64Value(uint64(10)) + l := value.Uint64Value(uint64(1)) + r := value.Uint64Value(uint64(10)) c, err := Compare(l, r) requireNoError(t, err) requireEqualValues(t, -1, c) @@ -140,8 +140,8 @@ func TestUint64(t *testing.T) { } func TestInt64(t *testing.T) { - l := types.Int64Value(int64(1)) - r := types.Int64Value(int64(10)) + l := value.Int64Value(int64(1)) + r := value.Int64Value(int64(10)) c, err := Compare(l, r) requireNoError(t, err) requireEqualValues(t, -1, c) @@ -156,8 +156,8 @@ func TestInt64(t *testing.T) { } func TestDouble(t *testing.T) { - l := types.DoubleValue(1.0) - r := types.DoubleValue(2.0) + l := value.DoubleValue(1.0) + r := value.DoubleValue(2.0) c, err := Compare(l, r) requireNoError(t, err) requireEqualValues(t, -1, c) @@ -172,8 +172,8 @@ func TestDouble(t *testing.T) { } func TestFloat(t *testing.T) { - l := types.FloatValue(1.0) - r := types.FloatValue(2.0) + l := value.FloatValue(1.0) + r := value.FloatValue(2.0) c, err := Compare(l, r) requireNoError(t, err) requireEqualValues(t, -1, c) @@ -188,8 +188,8 @@ func TestFloat(t *testing.T) { } func TestUTF8(t *testing.T) { - l := types.TextValue("abc") - r := types.TextValue("abx") + l := value.TextValue("abc") + r := value.TextValue("abx") c, err := Compare(l, r) requireNoError(t, err) requireEqualValues(t, -1, c) @@ -204,8 +204,8 @@ func TestUTF8(t *testing.T) { } func TestOptionalUTF8(t *testing.T) { - l := types.OptionalValue(types.OptionalValue(types.TextValue("abc"))) - r := types.TextValue("abx") + l := value.OptionalValue(value.OptionalValue(value.TextValue("abc"))) + r := value.TextValue("abx") c, err := Compare(l, r) requireNoError(t, err) requireEqualValues(t, -1, c) @@ -220,8 +220,8 @@ func TestOptionalUTF8(t *testing.T) { } func TestBytes(t *testing.T) { - l := types.BytesValue([]byte{1, 2, 3}) - r := types.BytesValue([]byte{1, 2, 5}) + l := value.BytesValue([]byte{1, 2, 3}) + r := value.BytesValue([]byte{1, 2, 5}) c, err := Compare(l, r) requireNoError(t, err) requireEqualValues(t, -1, c) @@ -236,8 +236,8 @@ func TestBytes(t *testing.T) { } func TestNull(t *testing.T) { - l := types.NullValue(types.TypeText) - r := types.TextValue("abc") + l := value.NullValue(types.Text) + r := value.TextValue("abc") c, err := Compare(l, r) requireNoError(t, err) @@ -253,10 +253,10 @@ func TestNull(t *testing.T) { } func TestTuple(t *testing.T) { - withNull := types.TupleValue(types.Uint64Value(1), types.NullValue(types.TypeText)) - least := types.TupleValue(types.Uint64Value(1), types.TextValue("abc")) - medium := types.TupleValue(types.Uint64Value(1), types.TextValue("def")) - largest := types.TupleValue(types.Uint64Value(2), types.TextValue("abc")) + withNull := value.TupleValue(value.Uint64Value(1), value.NullValue(types.Text)) + least := value.TupleValue(value.Uint64Value(1), value.TextValue("abc")) + medium := value.TupleValue(value.Uint64Value(1), value.TextValue("def")) + largest := value.TupleValue(value.Uint64Value(2), value.TextValue("abc")) c, err := Compare(least, medium) requireNoError(t, err) @@ -280,9 +280,9 @@ func TestTuple(t *testing.T) { } func TestList(t *testing.T) { - least := types.ListValue(types.Uint64Value(1), types.Uint64Value(1)) - medium := types.ListValue(types.Uint64Value(1), types.Uint64Value(2)) - largest := types.ListValue(types.Uint64Value(2), types.Uint64Value(1)) + least := value.ListValue(value.Uint64Value(1), value.Uint64Value(1)) + medium := value.ListValue(value.Uint64Value(1), value.Uint64Value(2)) + largest := value.ListValue(value.Uint64Value(2), value.Uint64Value(1)) c, err := Compare(least, medium) requireNoError(t, err) @@ -298,8 +298,8 @@ func TestList(t *testing.T) { } func TestDyNumber(t *testing.T) { - l := types.DyNumberValue("2") - r := types.DyNumberValue("12") + l := value.DyNumberValue("2") + r := value.DyNumberValue("12") c, err := Compare(l, r) requireNoError(t, err) requireEqualValues(t, -1, c) @@ -314,9 +314,9 @@ func TestDyNumber(t *testing.T) { } func TestUUID(t *testing.T) { - l := types.UUIDValue([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}) - r := types.UUIDValue([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17}) - g := types.UUIDValue([16]byte{100, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17}) + l := value.UUIDValue([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}) + r := value.UUIDValue([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17}) + g := value.UUIDValue([16]byte{100, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17}) c, err := Compare(l, r) requireNoError(t, err) requireEqualValues(t, -1, c) @@ -335,8 +335,8 @@ func TestUUID(t *testing.T) { } func TestIncompatiblePrimitives(t *testing.T) { - l := types.Uint64Value(1) - r := types.TimestampValue(2) + l := value.Uint64Value(1) + r := value.TimestampValue(2) _, err := Compare(l, r) if err == nil { t.Errorf("WithStackTrace expected") @@ -347,8 +347,8 @@ func TestIncompatiblePrimitives(t *testing.T) { } func TestIncompatibleTuples(t *testing.T) { - l := types.TupleValue(types.Uint64Value(1), types.TextValue("abc")) - r := types.TupleValue(types.Uint64Value(1), types.BytesValue([]byte("abc"))) + l := value.TupleValue(value.Uint64Value(1), value.TextValue("abc")) + r := value.TupleValue(value.Uint64Value(1), value.BytesValue([]byte("abc"))) _, err := Compare(l, r) if err == nil { t.Error("WithStackTrace expected") @@ -358,8 +358,8 @@ func TestIncompatibleTuples(t *testing.T) { } func TestTupleOfDifferentLength(t *testing.T) { - l := types.TupleValue(types.Uint64Value(1), types.TextValue("abc")) - r := types.TupleValue(types.Uint64Value(1), types.TextValue("abc"), types.TextValue("def")) + l := value.TupleValue(value.Uint64Value(1), value.TextValue("abc")) + r := value.TupleValue(value.Uint64Value(1), value.TextValue("abc"), value.TextValue("def")) cmp, err := Compare(l, r) requireNoError(t, err) @@ -371,8 +371,8 @@ func TestTupleOfDifferentLength(t *testing.T) { } func TestTupleInTuple(t *testing.T) { - l := types.TupleValue(types.Uint64Value(1), types.TupleValue(types.TextValue("abc"), types.BytesValue([]byte("xyz")))) - r := types.TupleValue(types.Uint64Value(1), types.TupleValue(types.TextValue("def"), types.BytesValue([]byte("xyz")))) + l := value.TupleValue(value.Uint64Value(1), value.TupleValue(value.TextValue("abc"), value.BytesValue([]byte("xyz")))) + r := value.TupleValue(value.Uint64Value(1), value.TupleValue(value.TextValue("def"), value.BytesValue([]byte("xyz")))) cmp, err := Compare(l, r) requireNoError(t, err) @@ -388,18 +388,18 @@ func TestTupleInTuple(t *testing.T) { } func TestListInList(t *testing.T) { - l := types.ListValue( - types.ListValue( - types.TextValue("abc"), types.TextValue("def"), - ), types.ListValue( - types.TextValue("uvw"), types.TextValue("xyz"), + l := value.ListValue( + value.ListValue( + value.TextValue("abc"), value.TextValue("def"), + ), value.ListValue( + value.TextValue("uvw"), value.TextValue("xyz"), ), ) - r := types.ListValue( - types.ListValue( - types.TextValue("abc"), types.TextValue("deg"), - ), types.ListValue( - types.TextValue("uvw"), types.TextValue("xyz"), + r := value.ListValue( + value.ListValue( + value.TextValue("abc"), value.TextValue("deg"), + ), value.ListValue( + value.TextValue("uvw"), value.TextValue("xyz"), ), ) diff --git a/testutil/session.go b/testutil/session.go index e53caedf2..d5ca81899 100644 --- a/testutil/session.go +++ b/testutil/session.go @@ -35,9 +35,9 @@ func SessionID(opts ...sessionIDOption) string { nodeID: uint32(xrand.New().Int64(math.MaxUint32)), hash: strconv.FormatInt(xrand.New().Int64(math.MaxInt64), 16), } - for _, o := range opts { - if o != nil { - o(h) + for _, opt := range opts { + if opt != nil { + opt(h) } } diff --git a/topic/topicreader/errors.go b/topic/topicreader/errors.go index 199a68257..f5f4db2d0 100644 --- a/topic/topicreader/errors.go +++ b/topic/topicreader/errors.go @@ -9,7 +9,7 @@ import ( // ErrUnexpectedCodec will return if topicreader receive message with unknown codec. // client side must check error with errors.Is -var ErrUnexpectedCodec = topicreaderinternal.PublicErrUnexpectedCodec +var ErrUnexpectedCodec = topicreaderinternal.ErrPublicUnexpectedCodec // ErrConcurrencyCall return if method on reader called in concurrency // client side must check error with errors.Is diff --git a/trace/coordination.go b/trace/coordination.go index 551064671..8c9ac18a0 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,168 @@ package trace type ( // Coordination specified trace of coordination client activity. // gtrace:gen - Coordination struct{} + Coordination struct { + OnNew func(CoordinationNewStartInfo) func(CoordinationNewDoneInfo) + OnCreateNode func(CoordinationCreateNodeStartInfo) func(CoordinationCreateNodeDoneInfo) + OnAlterNode func(CoordinationAlterNodeStartInfo) func(CoordinationAlterNodeDoneInfo) + OnDropNode func(CoordinationDropNodeStartInfo) func(CoordinationDropNodeDoneInfo) + OnDescribeNode func(CoordinationDescribeNodeStartInfo) func(CoordinationDescribeNodeDoneInfo) + OnSession func(CoordinationSessionStartInfo) func(CoordinationSessionDoneInfo) + OnClose func(CoordinationCloseStartInfo) func(CoordinationCloseDoneInfo) + + OnStreamNew func(CoordinationStreamNewStartInfo) func(CoordinationStreamNewDoneInfo) + OnSessionStarted func(CoordinationSessionStartedInfo) + OnSessionStartTimeout func(CoordinationSessionStartTimeoutInfo) + OnSessionKeepAliveTimeout func(CoordinationSessionKeepAliveTimeoutInfo) + OnSessionStopped func(CoordinationSessionStoppedInfo) + OnSessionStopTimeout func(CoordinationSessionStopTimeoutInfo) + OnSessionClientTimeout func(CoordinationSessionClientTimeoutInfo) + OnSessionServerExpire func(CoordinationSessionServerExpireInfo) + OnSessionServerError func(CoordinationSessionServerErrorInfo) + + OnSessionReceive func(CoordinationSessionReceiveStartInfo) func(CoordinationSessionReceiveDoneInfo) + OnSessionReceiveUnexpected func(CoordinationSessionReceiveUnexpectedInfo) + + OnSessionStop func(CoordinationSessionStopInfo) + OnSessionStart func(CoordinationSessionStartStartInfo) func(CoordinationSessionStartDoneInfo) + OnSessionSend func(CoordinationSessionSendStartInfo) func(CoordinationSessionSendDoneInfo) + } + 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 + } + CoordinationNewDoneInfo struct{} + 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 + } + CoordinationCloseDoneInfo struct { + Error error + } + 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 + } + CoordinationCreateNodeDoneInfo struct { + Error error + } + 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 + } + CoordinationAlterNodeDoneInfo struct { + Error error + } + 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 + } + CoordinationDropNodeDoneInfo struct { + Error error + } + 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 + } + CoordinationDescribeNodeDoneInfo struct { + Error error + } + 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 + } + CoordinationSessionDoneInfo struct { + Error error + } + CoordinationStreamNewStartInfo struct{} + CoordinationStreamNewDoneInfo struct { + Error error + } + CoordinationSessionStartedInfo struct { + SessionID uint64 + ExpectedSessionID uint64 + } + CoordinationSessionStartTimeoutInfo struct { + Timeout time.Duration + } + CoordinationSessionKeepAliveTimeoutInfo struct { + LastGoodResponseTime time.Time + Timeout time.Duration + } + CoordinationSessionStoppedInfo struct { + SessionID uint64 + ExpectedSessionID uint64 + } + CoordinationSessionStopTimeoutInfo struct { + Timeout time.Duration + } + CoordinationSessionClientTimeoutInfo struct { + LastGoodResponseTime time.Time + Timeout time.Duration + } + CoordinationSessionServerExpireInfo struct { + Failure *Ydb_Coordination.SessionResponse_Failure + } + CoordinationSessionServerErrorInfo struct { + Failure *Ydb_Coordination.SessionResponse_Failure + } + CoordinationSessionReceiveStartInfo struct{} + CoordinationSessionReceiveDoneInfo struct { + Response *Ydb_Coordination.SessionResponse + Error error + } + CoordinationSessionReceiveUnexpectedInfo struct { + Response *Ydb_Coordination.SessionResponse + } + CoordinationSessionStartStartInfo struct{} + CoordinationSessionStartDoneInfo struct { + Error error + } + CoordinationSessionStopInfo struct { + SessionID uint64 + } + CoordinationSessionSendStartInfo struct { + Request *Ydb_Coordination.SessionRequest + } + CoordinationSessionSendDoneInfo struct { + Error error + } ) diff --git a/trace/coordination_gtrace.go b/trace/coordination_gtrace.go index 609198059..91ccf3747 100644 --- a/trace/coordination_gtrace.go +++ b/trace/coordination_gtrace.go @@ -2,6 +2,13 @@ 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{}) @@ -20,5 +27,994 @@ func WithCoordinationPanicCallback(cb func(e interface{})) CoordinationComposeOp // Compose returns a new Coordination which has functional fields composed both from t and x. 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 +} +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) + } +} +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) + } +} +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) + } +} +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) + } +} +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) + } +} +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) + } +} +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) + } +} +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) + } +} +func CoordinationOnSessionStarted(t *Coordination, sessionID uint64, expectedSessionID uint64) { + var p CoordinationSessionStartedInfo + p.SessionID = sessionID + p.ExpectedSessionID = expectedSessionID + t.onSessionStarted(p) +} +func CoordinationOnSessionStartTimeout(t *Coordination, timeout time.Duration) { + var p CoordinationSessionStartTimeoutInfo + p.Timeout = timeout + t.onSessionStartTimeout(p) +} +func CoordinationOnSessionKeepAliveTimeout(t *Coordination, lastGoodResponseTime time.Time, timeout time.Duration) { + var p CoordinationSessionKeepAliveTimeoutInfo + p.LastGoodResponseTime = lastGoodResponseTime + p.Timeout = timeout + t.onSessionKeepAliveTimeout(p) +} +func CoordinationOnSessionStopped(t *Coordination, sessionID uint64, expectedSessionID uint64) { + var p CoordinationSessionStoppedInfo + p.SessionID = sessionID + p.ExpectedSessionID = expectedSessionID + t.onSessionStopped(p) +} +func CoordinationOnSessionStopTimeout(t *Coordination, timeout time.Duration) { + var p CoordinationSessionStopTimeoutInfo + p.Timeout = timeout + t.onSessionStopTimeout(p) +} +func CoordinationOnSessionClientTimeout(t *Coordination, lastGoodResponseTime time.Time, timeout time.Duration) { + var p CoordinationSessionClientTimeoutInfo + p.LastGoodResponseTime = lastGoodResponseTime + p.Timeout = timeout + t.onSessionClientTimeout(p) +} +func CoordinationOnSessionServerExpire(t *Coordination, failure *Ydb_Coordination.SessionResponse_Failure) { + var p CoordinationSessionServerExpireInfo + p.Failure = failure + t.onSessionServerExpire(p) +} +func CoordinationOnSessionServerError(t *Coordination, failure *Ydb_Coordination.SessionResponse_Failure) { + var p CoordinationSessionServerErrorInfo + p.Failure = failure + t.onSessionServerError(p) +} +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) + } +} +func CoordinationOnSessionReceiveUnexpected(t *Coordination, response *Ydb_Coordination.SessionResponse) { + var p CoordinationSessionReceiveUnexpectedInfo + p.Response = response + t.onSessionReceiveUnexpected(p) +} +func CoordinationOnSessionStop(t *Coordination, sessionID uint64) { + var p CoordinationSessionStopInfo + p.SessionID = sessionID + t.onSessionStop(p) +} +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) + } +} +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/details.go b/trace/details.go index d2a65ff89..124885135 100644 --- a/trace/details.go +++ b/trace/details.go @@ -44,6 +44,11 @@ const ( TablePoolSessionLifeCycleEvents TablePoolAPIEvents + QuerySessionEvents + QueryResultEvents + QueryTransactionEvents + QueryPoolEvents + TopicControlPlaneEvents TopicReaderCustomerEvents @@ -73,9 +78,6 @@ const ( CoordinationEvents - // Deprecated: has no effect now - DriverClusterEvents - DriverEvents = DriverConnEvents | DriverBalancerEvents | DriverResolverEvents | @@ -90,6 +92,11 @@ const ( TablePoolSessionLifeCycleEvents | TablePoolAPIEvents + QueryEvents = QuerySessionEvents | + QueryPoolEvents | + QueryResultEvents | + QueryTransactionEvents + TablePoolEvents = TablePoolLifeCycleEvents | TablePoolSessionLifeCycleEvents | TablePoolAPIEvents @@ -144,6 +151,12 @@ var ( TablePoolSessionLifeCycleEvents: "ydb.table.pool.session", TablePoolAPIEvents: "ydb.table.pool.api", + QueryEvents: "ydb.query", + QueryPoolEvents: "ydb.query.pool", + QuerySessionEvents: "ydb.query.session", + QueryResultEvents: "ydb.query.result", + QueryTransactionEvents: "ydb.query.tx", + DatabaseSQLEvents: "ydb.database.sql", DatabaseSQLConnectorEvents: "ydb.database.sql.connector", DatabaseSQLConnEvents: "ydb.database.sql.conn", @@ -191,9 +204,9 @@ func MatchDetails(pattern string, opts ...matchDetailsOption) (d Details) { err error ) - for _, o := range opts { - if o != nil { - o(h) + for _, opt := range opts { + if opt != nil { + opt(h) } } if h.posixMatch { diff --git a/trace/details_test.go b/trace/details_test.go index 1202daaf4..362090f68 100644 --- a/trace/details_test.go +++ b/trace/details_test.go @@ -35,6 +35,10 @@ func TestDetailsMatch(t *testing.T) { pattern: `^ydb\.table`, details: TableEvents, }, + { + pattern: `^ydb\.query`, + details: QueryEvents, + }, { pattern: `^ydb\.scripting$`, details: ScriptingEvents, @@ -63,6 +67,10 @@ func TestDetailsMatch(t *testing.T) { pattern: `^ydb\.table\.(pool\.(session|api)|session).*$`, details: TablePoolSessionLifeCycleEvents | TablePoolAPIEvents | TableSessionEvents, }, + { + pattern: `^ydb\.query\.(pool|session|tx|result).*$`, + details: QueryPoolEvents | QuerySessionEvents | QueryTransactionEvents | QueryResultEvents, + }, { pattern: `^ydb\.((database.sql.tx)|driver.(balancer|conn)|(table\.pool)|retry)$`, details: DriverBalancerEvents | DriverConnEvents | TablePoolLifeCycleEvents | DatabaseSQLTxEvents | RetryEvents, diff --git a/trace/driver.go b/trace/driver.go index 9e954874c..e33dac555 100644 --- a/trace/driver.go +++ b/trace/driver.go @@ -24,35 +24,21 @@ type ( OnPoolNew func(DriverConnPoolNewStartInfo) func(DriverConnPoolNewDoneInfo) OnPoolRelease func(DriverConnPoolReleaseStartInfo) func(DriverConnPoolReleaseDoneInfo) - // Deprecated: driver not notificate about this event - OnNetRead func(DriverNetReadStartInfo) func(DriverNetReadDoneInfo) - // Deprecated: driver not notificate about this event - OnNetWrite func(DriverNetWriteStartInfo) func(DriverNetWriteDoneInfo) - // Deprecated: driver not notificate about this event - OnNetDial func(DriverNetDialStartInfo) func(DriverNetDialDoneInfo) - // Deprecated: driver not notificate about this event - OnNetClose func(DriverNetCloseStartInfo) func(DriverNetCloseDoneInfo) - // Resolver events OnResolve func(DriverResolveStartInfo) func(DriverResolveDoneInfo) // Conn events - OnConnStateChange func(DriverConnStateChangeStartInfo) func(DriverConnStateChangeDoneInfo) - OnConnInvoke func(DriverConnInvokeStartInfo) func(DriverConnInvokeDoneInfo) - OnConnNewStream func( - DriverConnNewStreamStartInfo, - ) func( - DriverConnNewStreamRecvInfo, - ) func( - DriverConnNewStreamDoneInfo, - ) - // Deprecated: driver not notificate about this event - OnConnTake func(DriverConnTakeStartInfo) func(DriverConnTakeDoneInfo) - OnConnDial func(DriverConnDialStartInfo) func(DriverConnDialDoneInfo) - OnConnPark func(DriverConnParkStartInfo) func(DriverConnParkDoneInfo) - OnConnBan func(DriverConnBanStartInfo) func(DriverConnBanDoneInfo) - OnConnAllow func(DriverConnAllowStartInfo) func(DriverConnAllowDoneInfo) - OnConnClose func(DriverConnCloseStartInfo) func(DriverConnCloseDoneInfo) + 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) + OnConnStreamCloseSend func(DriverConnStreamCloseSendStartInfo) func(DriverConnStreamCloseSendDoneInfo) + OnConnDial func(DriverConnDialStartInfo) func(DriverConnDialDoneInfo) + OnConnBan func(DriverConnBanStartInfo) func(DriverConnBanDoneInfo) + OnConnAllow func(DriverConnAllowStartInfo) func(DriverConnAllowDoneInfo) + OnConnPark func(DriverConnParkStartInfo) func(DriverConnParkDoneInfo) + OnConnClose func(DriverConnCloseStartInfo) func(DriverConnCloseDoneInfo) // Repeater events OnRepeaterWakeUp func(DriverRepeaterWakeUpStartInfo) func(DriverRepeaterWakeUpDoneInfo) @@ -60,12 +46,6 @@ type ( // Balancer events OnBalancerInit func(DriverBalancerInitStartInfo) func(DriverBalancerInitDoneInfo) - // Deprecated: driver not notificate about this event - OnBalancerDialEntrypoint func( - DriverBalancerDialEntrypointStartInfo, - ) func( - DriverBalancerDialEntrypointDoneInfo, - ) OnBalancerClose func(DriverBalancerCloseStartInfo) func(DriverBalancerCloseDoneInfo) OnBalancerChooseEndpoint func( DriverBalancerChooseEndpointStartInfo, @@ -172,8 +152,6 @@ type ( Added []EndpointInfo Dropped []EndpointInfo LocalDC string - // Deprecated: this field always nil - Error error } DriverBalancerClusterDiscoveryAttemptStartInfo struct { // Context make available context in trace callback function. @@ -326,13 +304,42 @@ type ( Endpoint EndpointInfo Method Method } - DriverConnNewStreamRecvInfo struct { + DriverConnNewStreamDoneInfo struct { Error error + State ConnState } - DriverConnNewStreamDoneInfo struct { - Error error - State ConnState - Metadata map[string][]string + DriverConnStreamRecvMsgStartInfo 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 + } + DriverConnStreamRecvMsgDoneInfo struct { + Error error + } + DriverConnStreamSendMsgStartInfo 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 + } + DriverConnStreamSendMsgDoneInfo struct { + Error error + } + DriverConnStreamCloseSendStartInfo 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 + } + DriverConnStreamCloseSendDoneInfo struct { + Error error } DriverBalancerInitStartInfo struct { // Context make available context in trace callback function. diff --git a/trace/driver_gtrace.go b/trace/driver_gtrace.go index 0fe4e4fe7..4e737f5db 100644 --- a/trace/driver_gtrace.go +++ b/trace/driver_gtrace.go @@ -206,44 +206,9 @@ func (t *Driver) Compose(x *Driver, opts ...DriverComposeOption) *Driver { } } { - h1 := t.OnNetRead - h2 := x.OnNetRead - ret.OnNetRead = func(d DriverNetReadStartInfo) func(DriverNetReadDoneInfo) { - if options.panicCallback != nil { - defer func() { - if e := recover(); e != nil { - options.panicCallback(e) - } - }() - } - var r, r1 func(DriverNetReadDoneInfo) - if h1 != nil { - r = h1(d) - } - if h2 != nil { - r1 = h2(d) - } - return func(d DriverNetReadDoneInfo) { - 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.OnNetWrite - h2 := x.OnNetWrite - ret.OnNetWrite = func(d DriverNetWriteStartInfo) func(DriverNetWriteDoneInfo) { + h1 := t.OnResolve + h2 := x.OnResolve + ret.OnResolve = func(d DriverResolveStartInfo) func(DriverResolveDoneInfo) { if options.panicCallback != nil { defer func() { if e := recover(); e != nil { @@ -251,14 +216,14 @@ func (t *Driver) Compose(x *Driver, opts ...DriverComposeOption) *Driver { } }() } - var r, r1 func(DriverNetWriteDoneInfo) + var r, r1 func(DriverResolveDoneInfo) if h1 != nil { r = h1(d) } if h2 != nil { r1 = h2(d) } - return func(d DriverNetWriteDoneInfo) { + return func(d DriverResolveDoneInfo) { if options.panicCallback != nil { defer func() { if e := recover(); e != nil { @@ -276,9 +241,9 @@ func (t *Driver) Compose(x *Driver, opts ...DriverComposeOption) *Driver { } } { - h1 := t.OnNetDial - h2 := x.OnNetDial - ret.OnNetDial = func(d DriverNetDialStartInfo) func(DriverNetDialDoneInfo) { + h1 := t.OnConnStateChange + h2 := x.OnConnStateChange + ret.OnConnStateChange = func(d DriverConnStateChangeStartInfo) func(DriverConnStateChangeDoneInfo) { if options.panicCallback != nil { defer func() { if e := recover(); e != nil { @@ -286,14 +251,14 @@ func (t *Driver) Compose(x *Driver, opts ...DriverComposeOption) *Driver { } }() } - var r, r1 func(DriverNetDialDoneInfo) + var r, r1 func(DriverConnStateChangeDoneInfo) if h1 != nil { r = h1(d) } if h2 != nil { r1 = h2(d) } - return func(d DriverNetDialDoneInfo) { + return func(d DriverConnStateChangeDoneInfo) { if options.panicCallback != nil { defer func() { if e := recover(); e != nil { @@ -311,9 +276,9 @@ func (t *Driver) Compose(x *Driver, opts ...DriverComposeOption) *Driver { } } { - h1 := t.OnNetClose - h2 := x.OnNetClose - ret.OnNetClose = func(d DriverNetCloseStartInfo) func(DriverNetCloseDoneInfo) { + h1 := t.OnConnInvoke + h2 := x.OnConnInvoke + ret.OnConnInvoke = func(d DriverConnInvokeStartInfo) func(DriverConnInvokeDoneInfo) { if options.panicCallback != nil { defer func() { if e := recover(); e != nil { @@ -321,14 +286,14 @@ func (t *Driver) Compose(x *Driver, opts ...DriverComposeOption) *Driver { } }() } - var r, r1 func(DriverNetCloseDoneInfo) + var r, r1 func(DriverConnInvokeDoneInfo) if h1 != nil { r = h1(d) } if h2 != nil { r1 = h2(d) } - return func(d DriverNetCloseDoneInfo) { + return func(d DriverConnInvokeDoneInfo) { if options.panicCallback != nil { defer func() { if e := recover(); e != nil { @@ -346,9 +311,9 @@ func (t *Driver) Compose(x *Driver, opts ...DriverComposeOption) *Driver { } } { - h1 := t.OnResolve - h2 := x.OnResolve - ret.OnResolve = func(d DriverResolveStartInfo) func(DriverResolveDoneInfo) { + h1 := t.OnConnNewStream + h2 := x.OnConnNewStream + ret.OnConnNewStream = func(d DriverConnNewStreamStartInfo) func(DriverConnNewStreamDoneInfo) { if options.panicCallback != nil { defer func() { if e := recover(); e != nil { @@ -356,14 +321,14 @@ func (t *Driver) Compose(x *Driver, opts ...DriverComposeOption) *Driver { } }() } - var r, r1 func(DriverResolveDoneInfo) + var r, r1 func(DriverConnNewStreamDoneInfo) if h1 != nil { r = h1(d) } if h2 != nil { r1 = h2(d) } - return func(d DriverResolveDoneInfo) { + return func(d DriverConnNewStreamDoneInfo) { if options.panicCallback != nil { defer func() { if e := recover(); e != nil { @@ -381,9 +346,9 @@ func (t *Driver) Compose(x *Driver, opts ...DriverComposeOption) *Driver { } } { - h1 := t.OnConnStateChange - h2 := x.OnConnStateChange - ret.OnConnStateChange = func(d DriverConnStateChangeStartInfo) func(DriverConnStateChangeDoneInfo) { + h1 := t.OnConnStreamRecvMsg + h2 := x.OnConnStreamRecvMsg + ret.OnConnStreamRecvMsg = func(d DriverConnStreamRecvMsgStartInfo) func(DriverConnStreamRecvMsgDoneInfo) { if options.panicCallback != nil { defer func() { if e := recover(); e != nil { @@ -391,14 +356,14 @@ func (t *Driver) Compose(x *Driver, opts ...DriverComposeOption) *Driver { } }() } - var r, r1 func(DriverConnStateChangeDoneInfo) + var r, r1 func(DriverConnStreamRecvMsgDoneInfo) if h1 != nil { r = h1(d) } if h2 != nil { r1 = h2(d) } - return func(d DriverConnStateChangeDoneInfo) { + return func(d DriverConnStreamRecvMsgDoneInfo) { if options.panicCallback != nil { defer func() { if e := recover(); e != nil { @@ -416,9 +381,9 @@ func (t *Driver) Compose(x *Driver, opts ...DriverComposeOption) *Driver { } } { - h1 := t.OnConnInvoke - h2 := x.OnConnInvoke - ret.OnConnInvoke = func(d DriverConnInvokeStartInfo) func(DriverConnInvokeDoneInfo) { + h1 := t.OnConnStreamSendMsg + h2 := x.OnConnStreamSendMsg + ret.OnConnStreamSendMsg = func(d DriverConnStreamSendMsgStartInfo) func(DriverConnStreamSendMsgDoneInfo) { if options.panicCallback != nil { defer func() { if e := recover(); e != nil { @@ -426,14 +391,14 @@ func (t *Driver) Compose(x *Driver, opts ...DriverComposeOption) *Driver { } }() } - var r, r1 func(DriverConnInvokeDoneInfo) + var r, r1 func(DriverConnStreamSendMsgDoneInfo) if h1 != nil { r = h1(d) } if h2 != nil { r1 = h2(d) } - return func(d DriverConnInvokeDoneInfo) { + return func(d DriverConnStreamSendMsgDoneInfo) { if options.panicCallback != nil { defer func() { if e := recover(); e != nil { @@ -451,60 +416,9 @@ func (t *Driver) Compose(x *Driver, opts ...DriverComposeOption) *Driver { } } { - h1 := t.OnConnNewStream - h2 := x.OnConnNewStream - ret.OnConnNewStream = func(d DriverConnNewStreamStartInfo) func(DriverConnNewStreamRecvInfo) func(DriverConnNewStreamDoneInfo) { - if options.panicCallback != nil { - defer func() { - if e := recover(); e != nil { - options.panicCallback(e) - } - }() - } - var r, r1 func(DriverConnNewStreamRecvInfo) func(DriverConnNewStreamDoneInfo) - if h1 != nil { - r = h1(d) - } - if h2 != nil { - r1 = h2(d) - } - return func(d DriverConnNewStreamRecvInfo) func(DriverConnNewStreamDoneInfo) { - if options.panicCallback != nil { - defer func() { - if e := recover(); e != nil { - options.panicCallback(e) - } - }() - } - var r2, r3 func(DriverConnNewStreamDoneInfo) - if r != nil { - r2 = r(d) - } - if r1 != nil { - r3 = r1(d) - } - return func(d DriverConnNewStreamDoneInfo) { - if options.panicCallback != nil { - defer func() { - if e := recover(); e != nil { - options.panicCallback(e) - } - }() - } - if r2 != nil { - r2(d) - } - if r3 != nil { - r3(d) - } - } - } - } - } - { - h1 := t.OnConnTake - h2 := x.OnConnTake - ret.OnConnTake = func(d DriverConnTakeStartInfo) func(DriverConnTakeDoneInfo) { + h1 := t.OnConnStreamCloseSend + h2 := x.OnConnStreamCloseSend + ret.OnConnStreamCloseSend = func(d DriverConnStreamCloseSendStartInfo) func(DriverConnStreamCloseSendDoneInfo) { if options.panicCallback != nil { defer func() { if e := recover(); e != nil { @@ -512,14 +426,14 @@ func (t *Driver) Compose(x *Driver, opts ...DriverComposeOption) *Driver { } }() } - var r, r1 func(DriverConnTakeDoneInfo) + var r, r1 func(DriverConnStreamCloseSendDoneInfo) if h1 != nil { r = h1(d) } if h2 != nil { r1 = h2(d) } - return func(d DriverConnTakeDoneInfo) { + return func(d DriverConnStreamCloseSendDoneInfo) { if options.panicCallback != nil { defer func() { if e := recover(); e != nil { @@ -572,9 +486,9 @@ func (t *Driver) Compose(x *Driver, opts ...DriverComposeOption) *Driver { } } { - h1 := t.OnConnPark - h2 := x.OnConnPark - ret.OnConnPark = func(d DriverConnParkStartInfo) func(DriverConnParkDoneInfo) { + h1 := t.OnConnBan + h2 := x.OnConnBan + ret.OnConnBan = func(d DriverConnBanStartInfo) func(DriverConnBanDoneInfo) { if options.panicCallback != nil { defer func() { if e := recover(); e != nil { @@ -582,14 +496,14 @@ func (t *Driver) Compose(x *Driver, opts ...DriverComposeOption) *Driver { } }() } - var r, r1 func(DriverConnParkDoneInfo) + var r, r1 func(DriverConnBanDoneInfo) if h1 != nil { r = h1(d) } if h2 != nil { r1 = h2(d) } - return func(d DriverConnParkDoneInfo) { + return func(d DriverConnBanDoneInfo) { if options.panicCallback != nil { defer func() { if e := recover(); e != nil { @@ -607,9 +521,9 @@ func (t *Driver) Compose(x *Driver, opts ...DriverComposeOption) *Driver { } } { - h1 := t.OnConnBan - h2 := x.OnConnBan - ret.OnConnBan = func(d DriverConnBanStartInfo) func(DriverConnBanDoneInfo) { + h1 := t.OnConnAllow + h2 := x.OnConnAllow + ret.OnConnAllow = func(d DriverConnAllowStartInfo) func(DriverConnAllowDoneInfo) { if options.panicCallback != nil { defer func() { if e := recover(); e != nil { @@ -617,14 +531,14 @@ func (t *Driver) Compose(x *Driver, opts ...DriverComposeOption) *Driver { } }() } - var r, r1 func(DriverConnBanDoneInfo) + var r, r1 func(DriverConnAllowDoneInfo) if h1 != nil { r = h1(d) } if h2 != nil { r1 = h2(d) } - return func(d DriverConnBanDoneInfo) { + return func(d DriverConnAllowDoneInfo) { if options.panicCallback != nil { defer func() { if e := recover(); e != nil { @@ -642,9 +556,9 @@ func (t *Driver) Compose(x *Driver, opts ...DriverComposeOption) *Driver { } } { - h1 := t.OnConnAllow - h2 := x.OnConnAllow - ret.OnConnAllow = func(d DriverConnAllowStartInfo) func(DriverConnAllowDoneInfo) { + h1 := t.OnConnPark + h2 := x.OnConnPark + ret.OnConnPark = func(d DriverConnParkStartInfo) func(DriverConnParkDoneInfo) { if options.panicCallback != nil { defer func() { if e := recover(); e != nil { @@ -652,14 +566,14 @@ func (t *Driver) Compose(x *Driver, opts ...DriverComposeOption) *Driver { } }() } - var r, r1 func(DriverConnAllowDoneInfo) + var r, r1 func(DriverConnParkDoneInfo) if h1 != nil { r = h1(d) } if h2 != nil { r1 = h2(d) } - return func(d DriverConnAllowDoneInfo) { + return func(d DriverConnParkDoneInfo) { if options.panicCallback != nil { defer func() { if e := recover(); e != nil { @@ -781,41 +695,6 @@ func (t *Driver) Compose(x *Driver, opts ...DriverComposeOption) *Driver { } } } - { - h1 := t.OnBalancerDialEntrypoint - h2 := x.OnBalancerDialEntrypoint - ret.OnBalancerDialEntrypoint = func(d DriverBalancerDialEntrypointStartInfo) func(DriverBalancerDialEntrypointDoneInfo) { - if options.panicCallback != nil { - defer func() { - if e := recover(); e != nil { - options.panicCallback(e) - } - }() - } - var r, r1 func(DriverBalancerDialEntrypointDoneInfo) - if h1 != nil { - r = h1(d) - } - if h2 != nil { - r1 = h2(d) - } - return func(d DriverBalancerDialEntrypointDoneInfo) { - 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.OnBalancerClose h2 := x.OnBalancerClose @@ -1068,148 +947,106 @@ func (t *Driver) onPoolRelease(d DriverConnPoolReleaseStartInfo) func(DriverConn } return res } -func (t *Driver) onNetRead(d DriverNetReadStartInfo) func(DriverNetReadDoneInfo) { - fn := t.OnNetRead - if fn == nil { - return func(DriverNetReadDoneInfo) { - return - } - } - res := fn(d) - if res == nil { - return func(DriverNetReadDoneInfo) { - return - } - } - return res -} -func (t *Driver) onNetWrite(d DriverNetWriteStartInfo) func(DriverNetWriteDoneInfo) { - fn := t.OnNetWrite +func (t *Driver) onResolve(d DriverResolveStartInfo) func(DriverResolveDoneInfo) { + fn := t.OnResolve if fn == nil { - return func(DriverNetWriteDoneInfo) { + return func(DriverResolveDoneInfo) { return } } res := fn(d) if res == nil { - return func(DriverNetWriteDoneInfo) { + return func(DriverResolveDoneInfo) { return } } return res } -func (t *Driver) onNetDial(d DriverNetDialStartInfo) func(DriverNetDialDoneInfo) { - fn := t.OnNetDial +func (t *Driver) onConnStateChange(d DriverConnStateChangeStartInfo) func(DriverConnStateChangeDoneInfo) { + fn := t.OnConnStateChange if fn == nil { - return func(DriverNetDialDoneInfo) { + return func(DriverConnStateChangeDoneInfo) { return } } res := fn(d) if res == nil { - return func(DriverNetDialDoneInfo) { + return func(DriverConnStateChangeDoneInfo) { return } } return res } -func (t *Driver) onNetClose(d DriverNetCloseStartInfo) func(DriverNetCloseDoneInfo) { - fn := t.OnNetClose +func (t *Driver) onConnInvoke(d DriverConnInvokeStartInfo) func(DriverConnInvokeDoneInfo) { + fn := t.OnConnInvoke if fn == nil { - return func(DriverNetCloseDoneInfo) { + return func(DriverConnInvokeDoneInfo) { return } } res := fn(d) if res == nil { - return func(DriverNetCloseDoneInfo) { + return func(DriverConnInvokeDoneInfo) { return } } return res } -func (t *Driver) onResolve(d DriverResolveStartInfo) func(DriverResolveDoneInfo) { - fn := t.OnResolve +func (t *Driver) onConnNewStream(d DriverConnNewStreamStartInfo) func(DriverConnNewStreamDoneInfo) { + fn := t.OnConnNewStream if fn == nil { - return func(DriverResolveDoneInfo) { + return func(DriverConnNewStreamDoneInfo) { return } } res := fn(d) if res == nil { - return func(DriverResolveDoneInfo) { + return func(DriverConnNewStreamDoneInfo) { return } } return res } -func (t *Driver) onConnStateChange(d DriverConnStateChangeStartInfo) func(DriverConnStateChangeDoneInfo) { - fn := t.OnConnStateChange +func (t *Driver) onConnStreamRecvMsg(d DriverConnStreamRecvMsgStartInfo) func(DriverConnStreamRecvMsgDoneInfo) { + fn := t.OnConnStreamRecvMsg if fn == nil { - return func(DriverConnStateChangeDoneInfo) { + return func(DriverConnStreamRecvMsgDoneInfo) { return } } res := fn(d) if res == nil { - return func(DriverConnStateChangeDoneInfo) { + return func(DriverConnStreamRecvMsgDoneInfo) { return } } return res } -func (t *Driver) onConnInvoke(d DriverConnInvokeStartInfo) func(DriverConnInvokeDoneInfo) { - fn := t.OnConnInvoke +func (t *Driver) onConnStreamSendMsg(d DriverConnStreamSendMsgStartInfo) func(DriverConnStreamSendMsgDoneInfo) { + fn := t.OnConnStreamSendMsg if fn == nil { - return func(DriverConnInvokeDoneInfo) { + return func(DriverConnStreamSendMsgDoneInfo) { return } } res := fn(d) if res == nil { - return func(DriverConnInvokeDoneInfo) { + return func(DriverConnStreamSendMsgDoneInfo) { return } } return res } -func (t *Driver) onConnNewStream(d DriverConnNewStreamStartInfo) func(DriverConnNewStreamRecvInfo) func(DriverConnNewStreamDoneInfo) { - fn := t.OnConnNewStream - if fn == nil { - return func(DriverConnNewStreamRecvInfo) func(DriverConnNewStreamDoneInfo) { - return func(DriverConnNewStreamDoneInfo) { - return - } - } - } - res := fn(d) - if res == nil { - return func(DriverConnNewStreamRecvInfo) func(DriverConnNewStreamDoneInfo) { - return func(DriverConnNewStreamDoneInfo) { - return - } - } - } - return func(d DriverConnNewStreamRecvInfo) func(DriverConnNewStreamDoneInfo) { - res := res(d) - if res == nil { - return func(DriverConnNewStreamDoneInfo) { - return - } - } - return res - } -} -func (t *Driver) onConnTake(d DriverConnTakeStartInfo) func(DriverConnTakeDoneInfo) { - fn := t.OnConnTake +func (t *Driver) onConnStreamCloseSend(d DriverConnStreamCloseSendStartInfo) func(DriverConnStreamCloseSendDoneInfo) { + fn := t.OnConnStreamCloseSend if fn == nil { - return func(DriverConnTakeDoneInfo) { + return func(DriverConnStreamCloseSendDoneInfo) { return } } res := fn(d) if res == nil { - return func(DriverConnTakeDoneInfo) { + return func(DriverConnStreamCloseSendDoneInfo) { return } } @@ -1230,46 +1067,46 @@ func (t *Driver) onConnDial(d DriverConnDialStartInfo) func(DriverConnDialDoneIn } return res } -func (t *Driver) onConnPark(d DriverConnParkStartInfo) func(DriverConnParkDoneInfo) { - fn := t.OnConnPark +func (t *Driver) onConnBan(d DriverConnBanStartInfo) func(DriverConnBanDoneInfo) { + fn := t.OnConnBan if fn == nil { - return func(DriverConnParkDoneInfo) { + return func(DriverConnBanDoneInfo) { return } } res := fn(d) if res == nil { - return func(DriverConnParkDoneInfo) { + return func(DriverConnBanDoneInfo) { return } } return res } -func (t *Driver) onConnBan(d DriverConnBanStartInfo) func(DriverConnBanDoneInfo) { - fn := t.OnConnBan +func (t *Driver) onConnAllow(d DriverConnAllowStartInfo) func(DriverConnAllowDoneInfo) { + fn := t.OnConnAllow if fn == nil { - return func(DriverConnBanDoneInfo) { + return func(DriverConnAllowDoneInfo) { return } } res := fn(d) if res == nil { - return func(DriverConnBanDoneInfo) { + return func(DriverConnAllowDoneInfo) { return } } return res } -func (t *Driver) onConnAllow(d DriverConnAllowStartInfo) func(DriverConnAllowDoneInfo) { - fn := t.OnConnAllow +func (t *Driver) onConnPark(d DriverConnParkStartInfo) func(DriverConnParkDoneInfo) { + fn := t.OnConnPark if fn == nil { - return func(DriverConnAllowDoneInfo) { + return func(DriverConnParkDoneInfo) { return } } res := fn(d) if res == nil { - return func(DriverConnAllowDoneInfo) { + return func(DriverConnParkDoneInfo) { return } } @@ -1320,21 +1157,6 @@ func (t *Driver) onBalancerInit(d DriverBalancerInitStartInfo) func(DriverBalanc } return res } -func (t *Driver) onBalancerDialEntrypoint(d DriverBalancerDialEntrypointStartInfo) func(DriverBalancerDialEntrypointDoneInfo) { - fn := t.OnBalancerDialEntrypoint - if fn == nil { - return func(DriverBalancerDialEntrypointDoneInfo) { - return - } - } - res := fn(d) - if res == nil { - return func(DriverBalancerDialEntrypointDoneInfo) { - return - } - } - return res -} func (t *Driver) onBalancerClose(d DriverBalancerCloseStartInfo) func(DriverBalancerCloseDoneInfo) { fn := t.OnBalancerClose if fn == nil { @@ -1470,55 +1292,6 @@ func DriverOnPoolRelease(t *Driver, c *context.Context, call call) func(error) { res(p) } } -func DriverOnNetRead(t *Driver, call call, address string, buffer int) func(received int, _ error) { - var p DriverNetReadStartInfo - p.Call = call - p.Address = address - p.Buffer = buffer - res := t.onNetRead(p) - return func(received int, e error) { - var p DriverNetReadDoneInfo - p.Received = received - p.Error = e - res(p) - } -} -func DriverOnNetWrite(t *Driver, call call, address string, bytes int) func(sent int, _ error) { - var p DriverNetWriteStartInfo - p.Call = call - p.Address = address - p.Bytes = bytes - res := t.onNetWrite(p) - return func(sent int, e error) { - var p DriverNetWriteDoneInfo - p.Sent = sent - p.Error = e - res(p) - } -} -func DriverOnNetDial(t *Driver, c *context.Context, call call, address string) func(error) { - var p DriverNetDialStartInfo - p.Context = c - p.Call = call - p.Address = address - res := t.onNetDial(p) - return func(e error) { - var p DriverNetDialDoneInfo - p.Error = e - res(p) - } -} -func DriverOnNetClose(t *Driver, call call, address string) func(error) { - var p DriverNetCloseStartInfo - p.Call = call - p.Address = address - res := t.onNetClose(p) - return func(e error) { - var p DriverNetCloseDoneInfo - p.Error = e - res(p) - } -} func DriverOnResolve(t *Driver, call call, target string, resolved []string) func(error) { var p DriverResolveStartInfo p.Call = call @@ -1561,58 +1334,61 @@ func DriverOnConnInvoke(t *Driver, c *context.Context, call call, endpoint Endpo res(p) } } -func DriverOnConnNewStream(t *Driver, c *context.Context, call call, endpoint EndpointInfo, m Method) func(error) func(_ error, state ConnState, metadata map[string][]string) { +func DriverOnConnNewStream(t *Driver, c *context.Context, call call, endpoint EndpointInfo, m Method) func(_ error, state ConnState) { var p DriverConnNewStreamStartInfo p.Context = c p.Call = call p.Endpoint = endpoint p.Method = m res := t.onConnNewStream(p) - return func(e error) func(error, ConnState, map[string][]string) { - var p DriverConnNewStreamRecvInfo + return func(e error, state ConnState) { + var p DriverConnNewStreamDoneInfo p.Error = e - res := res(p) - return func(e error, state ConnState, metadata map[string][]string) { - var p DriverConnNewStreamDoneInfo - p.Error = e - p.State = state - p.Metadata = metadata - res(p) - } + p.State = state + res(p) } } -func DriverOnConnTake(t *Driver, c *context.Context, call call, endpoint EndpointInfo) func(error) { - var p DriverConnTakeStartInfo +func DriverOnConnStreamRecvMsg(t *Driver, c *context.Context, call call) func(error) { + var p DriverConnStreamRecvMsgStartInfo p.Context = c p.Call = call - p.Endpoint = endpoint - res := t.onConnTake(p) + res := t.onConnStreamRecvMsg(p) return func(e error) { - var p DriverConnTakeDoneInfo + var p DriverConnStreamRecvMsgDoneInfo p.Error = e res(p) } } -func DriverOnConnDial(t *Driver, c *context.Context, call call, endpoint EndpointInfo) func(error) { - var p DriverConnDialStartInfo +func DriverOnConnStreamSendMsg(t *Driver, c *context.Context, call call) func(error) { + var p DriverConnStreamSendMsgStartInfo p.Context = c p.Call = call - p.Endpoint = endpoint - res := t.onConnDial(p) + res := t.onConnStreamSendMsg(p) return func(e error) { - var p DriverConnDialDoneInfo + var p DriverConnStreamSendMsgDoneInfo p.Error = e res(p) } } -func DriverOnConnPark(t *Driver, c *context.Context, call call, endpoint EndpointInfo) func(error) { - var p DriverConnParkStartInfo +func DriverOnConnStreamCloseSend(t *Driver, c *context.Context, call call) func(error) { + var p DriverConnStreamCloseSendStartInfo + p.Context = c + p.Call = call + res := t.onConnStreamCloseSend(p) + return func(e error) { + var p DriverConnStreamCloseSendDoneInfo + p.Error = e + res(p) + } +} +func DriverOnConnDial(t *Driver, c *context.Context, call call, endpoint EndpointInfo) func(error) { + var p DriverConnDialStartInfo p.Context = c p.Call = call p.Endpoint = endpoint - res := t.onConnPark(p) + res := t.onConnDial(p) return func(e error) { - var p DriverConnParkDoneInfo + var p DriverConnDialDoneInfo p.Error = e res(p) } @@ -1644,6 +1420,18 @@ func DriverOnConnAllow(t *Driver, c *context.Context, call call, endpoint Endpoi res(p) } } +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) + } +} func DriverOnConnClose(t *Driver, c *context.Context, call call, endpoint EndpointInfo) func(error) { var p DriverConnCloseStartInfo p.Context = c @@ -1681,18 +1469,6 @@ func DriverOnBalancerInit(t *Driver, c *context.Context, call call, name string) res(p) } } -func DriverOnBalancerDialEntrypoint(t *Driver, c *context.Context, call call, address string) func(error) { - var p DriverBalancerDialEntrypointStartInfo - p.Context = c - p.Call = call - p.Address = address - res := t.onBalancerDialEntrypoint(p) - return func(e error) { - var p DriverBalancerDialEntrypointDoneInfo - p.Error = e - res(p) - } -} func DriverOnBalancerClose(t *Driver, c *context.Context, call call) func(error) { var p DriverBalancerCloseStartInfo p.Context = c @@ -1728,19 +1504,18 @@ func DriverOnBalancerClusterDiscoveryAttempt(t *Driver, c *context.Context, call res(p) } } -func DriverOnBalancerUpdate(t *Driver, c *context.Context, call call, needLocalDC bool) func(endpoints []EndpointInfo, added []EndpointInfo, dropped []EndpointInfo, localDC string, _ error) { +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 p.Call = call p.NeedLocalDC = needLocalDC res := t.onBalancerUpdate(p) - return func(endpoints []EndpointInfo, added []EndpointInfo, dropped []EndpointInfo, localDC string, e error) { + return func(endpoints []EndpointInfo, added []EndpointInfo, dropped []EndpointInfo, localDC string) { var p DriverBalancerUpdateDoneInfo p.Endpoints = endpoints p.Added = added p.Dropped = dropped p.LocalDC = localDC - p.Error = e res(p) } } diff --git a/trace/query.go b/trace/query.go new file mode 100644 index 000000000..aefb54ddd --- /dev/null +++ b/trace/query.go @@ -0,0 +1,338 @@ +package trace + +import ( + "context" +) + +// tool gtrace used from ./internal/cmd/gtrace + +//go:generate gtrace + +type ( + querySessionInfo interface { + ID() string + NodeID() int64 + Status() string + } + queryTransactionInfo interface { + ID() string + } + + // Query specified trace of retry call activity. + // gtrace:gen + Query struct { + OnNew func(QueryNewStartInfo) func(info QueryNewDoneInfo) + 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) + OnPoolChange func(QueryPoolChange) + + OnDo func(QueryDoStartInfo) func(QueryDoDoneInfo) + 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) + OnSessionBegin func(QuerySessionBeginStartInfo) func(info QuerySessionBeginDoneInfo) + OnTxExecute func(QueryTxExecuteStartInfo) func(info QueryTxExecuteDoneInfo) + OnResultNew func(QueryResultNewStartInfo) func(info QueryResultNewDoneInfo) + OnResultNextPart func(QueryResultNextPartStartInfo) func(info QueryResultNextPartDoneInfo) + OnResultNextResultSet func(QueryResultNextResultSetStartInfo) func(info QueryResultNextResultSetDoneInfo) + OnResultClose func(QueryResultCloseStartInfo) func(info QueryResultCloseDoneInfo) + OnResultSetNextRow func(QueryResultSetNextRowStartInfo) func(info QueryResultSetNextRowDoneInfo) + OnRowScan func(QueryRowScanStartInfo) func(info QueryRowScanDoneInfo) + OnRowScanNamed func(QueryRowScanNamedStartInfo) func(info QueryRowScanNamedDoneInfo) + OnRowScanStruct func(QueryRowScanStructStartInfo) func(info QueryRowScanStructDoneInfo) + } + + QueryDoStartInfo 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 + } + QueryDoDoneInfo struct { + Attempts int + Error error + } + QueryDoTxStartInfo 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 + } + QueryDoTxDoneInfo struct { + Attempts int + Error error + } + QuerySessionCreateStartInfo 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 + } + QuerySessionCreateDoneInfo struct { + Session querySessionInfo + Error error + } + QuerySessionExecuteStartInfo 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 + + Session querySessionInfo + Query string + } + QuerySessionExecuteDoneInfo struct { + Error error + } + QueryTxExecuteStartInfo 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 + + Session querySessionInfo + Tx queryTransactionInfo + Query string + } + QueryTxExecuteDoneInfo struct { + Error error + } + QuerySessionAttachStartInfo 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 + Session querySessionInfo + } + QuerySessionAttachDoneInfo struct { + Error error + } + QuerySessionBeginStartInfo 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 + Session querySessionInfo + } + QuerySessionBeginDoneInfo struct { + Error error + Tx queryTransactionInfo + } + QueryResultNewStartInfo 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 + } + QueryResultNewDoneInfo struct { + Error error + } + QueryResultCloseStartInfo 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 + } + QueryResultCloseDoneInfo struct { + Error error + } + QueryResultNextPartStartInfo 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 + } + QueryResultNextPartDoneInfo struct { + Error error + } + QueryResultNextResultSetStartInfo 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 + } + QueryResultNextResultSetDoneInfo struct { + Error error + } + QueryResultSetNextRowStartInfo 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 + } + QueryResultSetNextRowDoneInfo struct { + Error error + } + QueryRowScanStartInfo 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 + } + QueryRowScanDoneInfo struct { + Error error + } + QueryRowScanNamedStartInfo 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 + } + QueryRowScanNamedDoneInfo struct { + Error error + } + QueryRowScanStructStartInfo 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 + } + QueryRowScanStructDoneInfo struct { + Error error + } + QuerySessionDeleteStartInfo 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 + Session querySessionInfo + } + QuerySessionDeleteDoneInfo struct { + Error error + } + QueryNewStartInfo 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 + } + QueryNewDoneInfo struct{} + QueryCloseStartInfo 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 + } + QueryCloseDoneInfo struct { + Error error + } + QueryPoolNewStartInfo 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 + } + QueryPoolNewDoneInfo struct { + Limit int + } + QueryPoolCloseStartInfo 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 + } + QueryPoolCloseDoneInfo struct { + Error error + } + QueryPoolTryStartInfo 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 + } + QueryPoolTryDoneInfo struct { + Error error + } + QueryPoolWithStartInfo 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 + } + QueryPoolWithDoneInfo struct { + Error error + + Attempts int + } + QueryPoolPutStartInfo 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 + } + QueryPoolPutDoneInfo struct { + Error error + } + QueryPoolGetStartInfo 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 + } + QueryPoolGetDoneInfo struct { + Error error + } + QueryPoolChange struct { + Limit int + Index int + Idle int + InUse int + } +) diff --git a/trace/query_gtrace.go b/trace/query_gtrace.go new file mode 100644 index 000000000..2353b77b4 --- /dev/null +++ b/trace/query_gtrace.go @@ -0,0 +1,1544 @@ +// Code generated by gtrace. DO NOT EDIT. + +package trace + +import ( + "context" +) + +// queryComposeOptions is a holder of options +type queryComposeOptions struct { + panicCallback func(e interface{}) +} + +// QueryOption specified Query compose option +type QueryComposeOption func(o *queryComposeOptions) + +// WithQueryPanicCallback specified behavior on panic +func WithQueryPanicCallback(cb func(e interface{})) QueryComposeOption { + return func(o *queryComposeOptions) { + o.panicCallback = cb + } +} + +// Compose returns a new Query which has functional fields composed both from t and x. +func (t *Query) Compose(x *Query, opts ...QueryComposeOption) *Query { + var ret Query + options := queryComposeOptions{} + for _, opt := range opts { + if opt != nil { + opt(&options) + } + } + { + h1 := t.OnNew + h2 := x.OnNew + ret.OnNew = func(q QueryNewStartInfo) func(QueryNewDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + var r, r1 func(QueryNewDoneInfo) + if h1 != nil { + r = h1(q) + } + if h2 != nil { + r1 = h2(q) + } + return func(info QueryNewDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + if r != nil { + r(info) + } + if r1 != nil { + r1(info) + } + } + } + } + { + h1 := t.OnClose + h2 := x.OnClose + ret.OnClose = func(q QueryCloseStartInfo) func(QueryCloseDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + var r, r1 func(QueryCloseDoneInfo) + if h1 != nil { + r = h1(q) + } + if h2 != nil { + r1 = h2(q) + } + return func(info QueryCloseDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + if r != nil { + r(info) + } + if r1 != nil { + r1(info) + } + } + } + } + { + h1 := t.OnPoolNew + h2 := x.OnPoolNew + ret.OnPoolNew = func(q QueryPoolNewStartInfo) func(QueryPoolNewDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + var r, r1 func(QueryPoolNewDoneInfo) + if h1 != nil { + r = h1(q) + } + if h2 != nil { + r1 = h2(q) + } + return func(q QueryPoolNewDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + if r != nil { + r(q) + } + if r1 != nil { + r1(q) + } + } + } + } + { + h1 := t.OnPoolClose + h2 := x.OnPoolClose + ret.OnPoolClose = func(q QueryPoolCloseStartInfo) func(QueryPoolCloseDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + var r, r1 func(QueryPoolCloseDoneInfo) + if h1 != nil { + r = h1(q) + } + if h2 != nil { + r1 = h2(q) + } + return func(q QueryPoolCloseDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + if r != nil { + r(q) + } + if r1 != nil { + r1(q) + } + } + } + } + { + h1 := t.OnPoolTry + h2 := x.OnPoolTry + ret.OnPoolTry = func(q QueryPoolTryStartInfo) func(QueryPoolTryDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + var r, r1 func(QueryPoolTryDoneInfo) + if h1 != nil { + r = h1(q) + } + if h2 != nil { + r1 = h2(q) + } + return func(q QueryPoolTryDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + if r != nil { + r(q) + } + if r1 != nil { + r1(q) + } + } + } + } + { + h1 := t.OnPoolWith + h2 := x.OnPoolWith + ret.OnPoolWith = func(q QueryPoolWithStartInfo) func(QueryPoolWithDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + var r, r1 func(QueryPoolWithDoneInfo) + if h1 != nil { + r = h1(q) + } + if h2 != nil { + r1 = h2(q) + } + return func(q QueryPoolWithDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + if r != nil { + r(q) + } + if r1 != nil { + r1(q) + } + } + } + } + { + h1 := t.OnPoolPut + h2 := x.OnPoolPut + ret.OnPoolPut = func(q QueryPoolPutStartInfo) func(QueryPoolPutDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + var r, r1 func(QueryPoolPutDoneInfo) + if h1 != nil { + r = h1(q) + } + if h2 != nil { + r1 = h2(q) + } + return func(q QueryPoolPutDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + if r != nil { + r(q) + } + if r1 != nil { + r1(q) + } + } + } + } + { + h1 := t.OnPoolGet + h2 := x.OnPoolGet + ret.OnPoolGet = func(q QueryPoolGetStartInfo) func(QueryPoolGetDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + var r, r1 func(QueryPoolGetDoneInfo) + if h1 != nil { + r = h1(q) + } + if h2 != nil { + r1 = h2(q) + } + return func(q QueryPoolGetDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + if r != nil { + r(q) + } + if r1 != nil { + r1(q) + } + } + } + } + { + h1 := t.OnPoolChange + h2 := x.OnPoolChange + ret.OnPoolChange = func(q QueryPoolChange) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + if h1 != nil { + h1(q) + } + if h2 != nil { + h2(q) + } + } + } + { + h1 := t.OnDo + h2 := x.OnDo + ret.OnDo = func(q QueryDoStartInfo) func(QueryDoDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + var r, r1 func(QueryDoDoneInfo) + if h1 != nil { + r = h1(q) + } + if h2 != nil { + r1 = h2(q) + } + return func(q QueryDoDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + if r != nil { + r(q) + } + if r1 != nil { + r1(q) + } + } + } + } + { + h1 := t.OnDoTx + h2 := x.OnDoTx + ret.OnDoTx = func(q QueryDoTxStartInfo) func(QueryDoTxDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + var r, r1 func(QueryDoTxDoneInfo) + if h1 != nil { + r = h1(q) + } + if h2 != nil { + r1 = h2(q) + } + return func(q QueryDoTxDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + if r != nil { + r(q) + } + if r1 != nil { + r1(q) + } + } + } + } + { + h1 := t.OnSessionCreate + h2 := x.OnSessionCreate + ret.OnSessionCreate = func(q QuerySessionCreateStartInfo) func(QuerySessionCreateDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + var r, r1 func(QuerySessionCreateDoneInfo) + if h1 != nil { + r = h1(q) + } + if h2 != nil { + r1 = h2(q) + } + return func(info QuerySessionCreateDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + if r != nil { + r(info) + } + if r1 != nil { + r1(info) + } + } + } + } + { + h1 := t.OnSessionAttach + h2 := x.OnSessionAttach + ret.OnSessionAttach = func(q QuerySessionAttachStartInfo) func(QuerySessionAttachDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + var r, r1 func(QuerySessionAttachDoneInfo) + if h1 != nil { + r = h1(q) + } + if h2 != nil { + r1 = h2(q) + } + return func(info QuerySessionAttachDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + if r != nil { + r(info) + } + if r1 != nil { + r1(info) + } + } + } + } + { + h1 := t.OnSessionDelete + h2 := x.OnSessionDelete + ret.OnSessionDelete = func(q QuerySessionDeleteStartInfo) func(QuerySessionDeleteDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + var r, r1 func(QuerySessionDeleteDoneInfo) + if h1 != nil { + r = h1(q) + } + if h2 != nil { + r1 = h2(q) + } + return func(info QuerySessionDeleteDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + if r != nil { + r(info) + } + if r1 != nil { + r1(info) + } + } + } + } + { + h1 := t.OnSessionExecute + h2 := x.OnSessionExecute + ret.OnSessionExecute = func(q QuerySessionExecuteStartInfo) func(QuerySessionExecuteDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + var r, r1 func(QuerySessionExecuteDoneInfo) + if h1 != nil { + r = h1(q) + } + if h2 != nil { + r1 = h2(q) + } + return func(info QuerySessionExecuteDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + if r != nil { + r(info) + } + if r1 != nil { + r1(info) + } + } + } + } + { + h1 := t.OnSessionBegin + h2 := x.OnSessionBegin + ret.OnSessionBegin = func(q QuerySessionBeginStartInfo) func(QuerySessionBeginDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + var r, r1 func(QuerySessionBeginDoneInfo) + if h1 != nil { + r = h1(q) + } + if h2 != nil { + r1 = h2(q) + } + return func(info QuerySessionBeginDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + if r != nil { + r(info) + } + if r1 != nil { + r1(info) + } + } + } + } + { + h1 := t.OnTxExecute + h2 := x.OnTxExecute + ret.OnTxExecute = func(q QueryTxExecuteStartInfo) func(QueryTxExecuteDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + var r, r1 func(QueryTxExecuteDoneInfo) + if h1 != nil { + r = h1(q) + } + if h2 != nil { + r1 = h2(q) + } + return func(info QueryTxExecuteDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + if r != nil { + r(info) + } + if r1 != nil { + r1(info) + } + } + } + } + { + h1 := t.OnResultNew + h2 := x.OnResultNew + ret.OnResultNew = func(q QueryResultNewStartInfo) func(QueryResultNewDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + var r, r1 func(QueryResultNewDoneInfo) + if h1 != nil { + r = h1(q) + } + if h2 != nil { + r1 = h2(q) + } + return func(info QueryResultNewDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + if r != nil { + r(info) + } + if r1 != nil { + r1(info) + } + } + } + } + { + h1 := t.OnResultNextPart + h2 := x.OnResultNextPart + ret.OnResultNextPart = func(q QueryResultNextPartStartInfo) func(QueryResultNextPartDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + var r, r1 func(QueryResultNextPartDoneInfo) + if h1 != nil { + r = h1(q) + } + if h2 != nil { + r1 = h2(q) + } + return func(info QueryResultNextPartDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + if r != nil { + r(info) + } + if r1 != nil { + r1(info) + } + } + } + } + { + h1 := t.OnResultNextResultSet + h2 := x.OnResultNextResultSet + ret.OnResultNextResultSet = func(q QueryResultNextResultSetStartInfo) func(QueryResultNextResultSetDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + var r, r1 func(QueryResultNextResultSetDoneInfo) + if h1 != nil { + r = h1(q) + } + if h2 != nil { + r1 = h2(q) + } + return func(info QueryResultNextResultSetDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + if r != nil { + r(info) + } + if r1 != nil { + r1(info) + } + } + } + } + { + h1 := t.OnResultClose + h2 := x.OnResultClose + ret.OnResultClose = func(q QueryResultCloseStartInfo) func(QueryResultCloseDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + var r, r1 func(QueryResultCloseDoneInfo) + if h1 != nil { + r = h1(q) + } + if h2 != nil { + r1 = h2(q) + } + return func(info QueryResultCloseDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + if r != nil { + r(info) + } + if r1 != nil { + r1(info) + } + } + } + } + { + h1 := t.OnResultSetNextRow + h2 := x.OnResultSetNextRow + ret.OnResultSetNextRow = func(q QueryResultSetNextRowStartInfo) func(QueryResultSetNextRowDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + var r, r1 func(QueryResultSetNextRowDoneInfo) + if h1 != nil { + r = h1(q) + } + if h2 != nil { + r1 = h2(q) + } + return func(info QueryResultSetNextRowDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + if r != nil { + r(info) + } + if r1 != nil { + r1(info) + } + } + } + } + { + h1 := t.OnRowScan + h2 := x.OnRowScan + ret.OnRowScan = func(q QueryRowScanStartInfo) func(QueryRowScanDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + var r, r1 func(QueryRowScanDoneInfo) + if h1 != nil { + r = h1(q) + } + if h2 != nil { + r1 = h2(q) + } + return func(info QueryRowScanDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + if r != nil { + r(info) + } + if r1 != nil { + r1(info) + } + } + } + } + { + h1 := t.OnRowScanNamed + h2 := x.OnRowScanNamed + ret.OnRowScanNamed = func(q QueryRowScanNamedStartInfo) func(QueryRowScanNamedDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + var r, r1 func(QueryRowScanNamedDoneInfo) + if h1 != nil { + r = h1(q) + } + if h2 != nil { + r1 = h2(q) + } + return func(info QueryRowScanNamedDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + if r != nil { + r(info) + } + if r1 != nil { + r1(info) + } + } + } + } + { + h1 := t.OnRowScanStruct + h2 := x.OnRowScanStruct + ret.OnRowScanStruct = func(q QueryRowScanStructStartInfo) func(QueryRowScanStructDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + var r, r1 func(QueryRowScanStructDoneInfo) + if h1 != nil { + r = h1(q) + } + if h2 != nil { + r1 = h2(q) + } + return func(info QueryRowScanStructDoneInfo) { + if options.panicCallback != nil { + defer func() { + if e := recover(); e != nil { + options.panicCallback(e) + } + }() + } + if r != nil { + r(info) + } + if r1 != nil { + r1(info) + } + } + } + } + return &ret +} +func (t *Query) onNew(q QueryNewStartInfo) func(info QueryNewDoneInfo) { + fn := t.OnNew + if fn == nil { + return func(QueryNewDoneInfo) { + return + } + } + res := fn(q) + if res == nil { + return func(QueryNewDoneInfo) { + return + } + } + return res +} +func (t *Query) onClose(q QueryCloseStartInfo) func(info QueryCloseDoneInfo) { + fn := t.OnClose + if fn == nil { + return func(QueryCloseDoneInfo) { + return + } + } + res := fn(q) + if res == nil { + return func(QueryCloseDoneInfo) { + return + } + } + return res +} +func (t *Query) onPoolNew(q QueryPoolNewStartInfo) func(QueryPoolNewDoneInfo) { + fn := t.OnPoolNew + if fn == nil { + return func(QueryPoolNewDoneInfo) { + return + } + } + res := fn(q) + if res == nil { + return func(QueryPoolNewDoneInfo) { + return + } + } + return res +} +func (t *Query) onPoolClose(q QueryPoolCloseStartInfo) func(QueryPoolCloseDoneInfo) { + fn := t.OnPoolClose + if fn == nil { + return func(QueryPoolCloseDoneInfo) { + return + } + } + res := fn(q) + if res == nil { + return func(QueryPoolCloseDoneInfo) { + return + } + } + return res +} +func (t *Query) onPoolTry(q QueryPoolTryStartInfo) func(QueryPoolTryDoneInfo) { + fn := t.OnPoolTry + if fn == nil { + return func(QueryPoolTryDoneInfo) { + return + } + } + res := fn(q) + if res == nil { + return func(QueryPoolTryDoneInfo) { + return + } + } + return res +} +func (t *Query) onPoolWith(q QueryPoolWithStartInfo) func(QueryPoolWithDoneInfo) { + fn := t.OnPoolWith + if fn == nil { + return func(QueryPoolWithDoneInfo) { + return + } + } + res := fn(q) + if res == nil { + return func(QueryPoolWithDoneInfo) { + return + } + } + return res +} +func (t *Query) onPoolPut(q QueryPoolPutStartInfo) func(QueryPoolPutDoneInfo) { + fn := t.OnPoolPut + if fn == nil { + return func(QueryPoolPutDoneInfo) { + return + } + } + res := fn(q) + if res == nil { + return func(QueryPoolPutDoneInfo) { + return + } + } + return res +} +func (t *Query) onPoolGet(q QueryPoolGetStartInfo) func(QueryPoolGetDoneInfo) { + fn := t.OnPoolGet + if fn == nil { + return func(QueryPoolGetDoneInfo) { + return + } + } + res := fn(q) + if res == nil { + return func(QueryPoolGetDoneInfo) { + return + } + } + return res +} +func (t *Query) onPoolChange(q QueryPoolChange) { + fn := t.OnPoolChange + if fn == nil { + return + } + fn(q) +} +func (t *Query) onDo(q QueryDoStartInfo) func(QueryDoDoneInfo) { + fn := t.OnDo + if fn == nil { + return func(QueryDoDoneInfo) { + return + } + } + res := fn(q) + if res == nil { + return func(QueryDoDoneInfo) { + return + } + } + return res +} +func (t *Query) onDoTx(q QueryDoTxStartInfo) func(QueryDoTxDoneInfo) { + fn := t.OnDoTx + if fn == nil { + return func(QueryDoTxDoneInfo) { + return + } + } + res := fn(q) + if res == nil { + return func(QueryDoTxDoneInfo) { + return + } + } + return res +} +func (t *Query) onSessionCreate(q QuerySessionCreateStartInfo) func(info QuerySessionCreateDoneInfo) { + fn := t.OnSessionCreate + if fn == nil { + return func(QuerySessionCreateDoneInfo) { + return + } + } + res := fn(q) + if res == nil { + return func(QuerySessionCreateDoneInfo) { + return + } + } + return res +} +func (t *Query) onSessionAttach(q QuerySessionAttachStartInfo) func(info QuerySessionAttachDoneInfo) { + fn := t.OnSessionAttach + if fn == nil { + return func(QuerySessionAttachDoneInfo) { + return + } + } + res := fn(q) + if res == nil { + return func(QuerySessionAttachDoneInfo) { + return + } + } + return res +} +func (t *Query) onSessionDelete(q QuerySessionDeleteStartInfo) func(info QuerySessionDeleteDoneInfo) { + fn := t.OnSessionDelete + if fn == nil { + return func(QuerySessionDeleteDoneInfo) { + return + } + } + res := fn(q) + if res == nil { + return func(QuerySessionDeleteDoneInfo) { + return + } + } + return res +} +func (t *Query) onSessionExecute(q QuerySessionExecuteStartInfo) func(info QuerySessionExecuteDoneInfo) { + fn := t.OnSessionExecute + if fn == nil { + return func(QuerySessionExecuteDoneInfo) { + return + } + } + res := fn(q) + if res == nil { + return func(QuerySessionExecuteDoneInfo) { + return + } + } + return res +} +func (t *Query) onSessionBegin(q QuerySessionBeginStartInfo) func(info QuerySessionBeginDoneInfo) { + fn := t.OnSessionBegin + if fn == nil { + return func(QuerySessionBeginDoneInfo) { + return + } + } + res := fn(q) + if res == nil { + return func(QuerySessionBeginDoneInfo) { + return + } + } + return res +} +func (t *Query) onTxExecute(q QueryTxExecuteStartInfo) func(info QueryTxExecuteDoneInfo) { + fn := t.OnTxExecute + if fn == nil { + return func(QueryTxExecuteDoneInfo) { + return + } + } + res := fn(q) + if res == nil { + return func(QueryTxExecuteDoneInfo) { + return + } + } + return res +} +func (t *Query) onResultNew(q QueryResultNewStartInfo) func(info QueryResultNewDoneInfo) { + fn := t.OnResultNew + if fn == nil { + return func(QueryResultNewDoneInfo) { + return + } + } + res := fn(q) + if res == nil { + return func(QueryResultNewDoneInfo) { + return + } + } + return res +} +func (t *Query) onResultNextPart(q QueryResultNextPartStartInfo) func(info QueryResultNextPartDoneInfo) { + fn := t.OnResultNextPart + if fn == nil { + return func(QueryResultNextPartDoneInfo) { + return + } + } + res := fn(q) + if res == nil { + return func(QueryResultNextPartDoneInfo) { + return + } + } + return res +} +func (t *Query) onResultNextResultSet(q QueryResultNextResultSetStartInfo) func(info QueryResultNextResultSetDoneInfo) { + fn := t.OnResultNextResultSet + if fn == nil { + return func(QueryResultNextResultSetDoneInfo) { + return + } + } + res := fn(q) + if res == nil { + return func(QueryResultNextResultSetDoneInfo) { + return + } + } + return res +} +func (t *Query) onResultClose(q QueryResultCloseStartInfo) func(info QueryResultCloseDoneInfo) { + fn := t.OnResultClose + if fn == nil { + return func(QueryResultCloseDoneInfo) { + return + } + } + res := fn(q) + if res == nil { + return func(QueryResultCloseDoneInfo) { + return + } + } + return res +} +func (t *Query) onResultSetNextRow(q QueryResultSetNextRowStartInfo) func(info QueryResultSetNextRowDoneInfo) { + fn := t.OnResultSetNextRow + if fn == nil { + return func(QueryResultSetNextRowDoneInfo) { + return + } + } + res := fn(q) + if res == nil { + return func(QueryResultSetNextRowDoneInfo) { + return + } + } + return res +} +func (t *Query) onRowScan(q QueryRowScanStartInfo) func(info QueryRowScanDoneInfo) { + fn := t.OnRowScan + if fn == nil { + return func(QueryRowScanDoneInfo) { + return + } + } + res := fn(q) + if res == nil { + return func(QueryRowScanDoneInfo) { + return + } + } + return res +} +func (t *Query) onRowScanNamed(q QueryRowScanNamedStartInfo) func(info QueryRowScanNamedDoneInfo) { + fn := t.OnRowScanNamed + if fn == nil { + return func(QueryRowScanNamedDoneInfo) { + return + } + } + res := fn(q) + if res == nil { + return func(QueryRowScanNamedDoneInfo) { + return + } + } + return res +} +func (t *Query) onRowScanStruct(q QueryRowScanStructStartInfo) func(info QueryRowScanStructDoneInfo) { + fn := t.OnRowScanStruct + if fn == nil { + return func(QueryRowScanStructDoneInfo) { + return + } + } + res := fn(q) + if res == nil { + return func(QueryRowScanStructDoneInfo) { + return + } + } + return res +} +func QueryOnNew(t *Query, c *context.Context, call call) func() { + var p QueryNewStartInfo + p.Context = c + p.Call = call + res := t.onNew(p) + return func() { + var p QueryNewDoneInfo + res(p) + } +} +func QueryOnClose(t *Query, c *context.Context, call call) func(error) { + var p QueryCloseStartInfo + p.Context = c + p.Call = call + res := t.onClose(p) + return func(e error) { + var p QueryCloseDoneInfo + p.Error = e + res(p) + } +} +func QueryOnPoolNew(t *Query, c *context.Context, call call) func(limit int) { + var p QueryPoolNewStartInfo + p.Context = c + p.Call = call + res := t.onPoolNew(p) + return func(limit int) { + var p QueryPoolNewDoneInfo + p.Limit = limit + res(p) + } +} +func QueryOnPoolClose(t *Query, c *context.Context, call call) func(error) { + var p QueryPoolCloseStartInfo + p.Context = c + p.Call = call + res := t.onPoolClose(p) + return func(e error) { + var p QueryPoolCloseDoneInfo + p.Error = e + res(p) + } +} +func QueryOnPoolTry(t *Query, c *context.Context, call call) func(error) { + var p QueryPoolTryStartInfo + p.Context = c + p.Call = call + res := t.onPoolTry(p) + return func(e error) { + var p QueryPoolTryDoneInfo + p.Error = e + res(p) + } +} +func QueryOnPoolWith(t *Query, c *context.Context, call call) func(_ error, attempts int) { + var p QueryPoolWithStartInfo + p.Context = c + p.Call = call + res := t.onPoolWith(p) + return func(e error, attempts int) { + var p QueryPoolWithDoneInfo + p.Error = e + p.Attempts = attempts + res(p) + } +} +func QueryOnPoolPut(t *Query, c *context.Context, call call) func(error) { + var p QueryPoolPutStartInfo + p.Context = c + p.Call = call + res := t.onPoolPut(p) + return func(e error) { + var p QueryPoolPutDoneInfo + p.Error = e + res(p) + } +} +func QueryOnPoolGet(t *Query, c *context.Context, call call) func(error) { + var p QueryPoolGetStartInfo + p.Context = c + p.Call = call + res := t.onPoolGet(p) + return func(e error) { + var p QueryPoolGetDoneInfo + p.Error = e + res(p) + } +} +func QueryOnPoolChange(t *Query, limit int, index int, idle int, inUse int) { + var p QueryPoolChange + p.Limit = limit + p.Index = index + p.Idle = idle + p.InUse = inUse + t.onPoolChange(p) +} +func QueryOnDo(t *Query, c *context.Context, call call) func(attempts int, _ error) { + var p QueryDoStartInfo + p.Context = c + p.Call = call + res := t.onDo(p) + return func(attempts int, e error) { + var p QueryDoDoneInfo + p.Attempts = attempts + p.Error = e + res(p) + } +} +func QueryOnDoTx(t *Query, c *context.Context, call call) func(attempts int, _ error) { + var p QueryDoTxStartInfo + p.Context = c + p.Call = call + res := t.onDoTx(p) + return func(attempts int, e error) { + var p QueryDoTxDoneInfo + p.Attempts = attempts + p.Error = e + res(p) + } +} +func QueryOnSessionCreate(t *Query, c *context.Context, call call) func(session querySessionInfo, _ error) { + var p QuerySessionCreateStartInfo + p.Context = c + p.Call = call + res := t.onSessionCreate(p) + return func(session querySessionInfo, e error) { + var p QuerySessionCreateDoneInfo + p.Session = session + p.Error = e + res(p) + } +} +func QueryOnSessionAttach(t *Query, c *context.Context, call call, session querySessionInfo) func(error) { + var p QuerySessionAttachStartInfo + p.Context = c + p.Call = call + p.Session = session + res := t.onSessionAttach(p) + return func(e error) { + var p QuerySessionAttachDoneInfo + p.Error = e + res(p) + } +} +func QueryOnSessionDelete(t *Query, c *context.Context, call call, session querySessionInfo) func(error) { + var p QuerySessionDeleteStartInfo + p.Context = c + p.Call = call + p.Session = session + res := t.onSessionDelete(p) + return func(e error) { + var p QuerySessionDeleteDoneInfo + p.Error = e + res(p) + } +} +func QueryOnSessionExecute(t *Query, c *context.Context, call call, session querySessionInfo, query string) func(error) { + var p QuerySessionExecuteStartInfo + p.Context = c + p.Call = call + p.Session = session + p.Query = query + res := t.onSessionExecute(p) + return func(e error) { + var p QuerySessionExecuteDoneInfo + p.Error = e + res(p) + } +} +func QueryOnSessionBegin(t *Query, c *context.Context, call call, session querySessionInfo) func(_ error, tx queryTransactionInfo) { + var p QuerySessionBeginStartInfo + p.Context = c + p.Call = call + p.Session = session + res := t.onSessionBegin(p) + return func(e error, tx queryTransactionInfo) { + var p QuerySessionBeginDoneInfo + p.Error = e + p.Tx = tx + res(p) + } +} +func QueryOnTxExecute(t *Query, c *context.Context, call call, session querySessionInfo, tx queryTransactionInfo, query string) func(error) { + var p QueryTxExecuteStartInfo + p.Context = c + p.Call = call + p.Session = session + p.Tx = tx + p.Query = query + res := t.onTxExecute(p) + return func(e error) { + var p QueryTxExecuteDoneInfo + p.Error = e + res(p) + } +} +func QueryOnResultNew(t *Query, c *context.Context, call call) func(error) { + var p QueryResultNewStartInfo + p.Context = c + p.Call = call + res := t.onResultNew(p) + return func(e error) { + var p QueryResultNewDoneInfo + p.Error = e + res(p) + } +} +func QueryOnResultNextPart(t *Query, c *context.Context, call call) func(error) { + var p QueryResultNextPartStartInfo + p.Context = c + p.Call = call + res := t.onResultNextPart(p) + return func(e error) { + var p QueryResultNextPartDoneInfo + p.Error = e + res(p) + } +} +func QueryOnResultNextResultSet(t *Query, c *context.Context, call call) func(error) { + var p QueryResultNextResultSetStartInfo + p.Context = c + p.Call = call + res := t.onResultNextResultSet(p) + return func(e error) { + var p QueryResultNextResultSetDoneInfo + p.Error = e + res(p) + } +} +func QueryOnResultClose(t *Query, c *context.Context, call call) func(error) { + var p QueryResultCloseStartInfo + p.Context = c + p.Call = call + res := t.onResultClose(p) + return func(e error) { + var p QueryResultCloseDoneInfo + p.Error = e + res(p) + } +} +func QueryOnResultSetNextRow(t *Query, c *context.Context, call call) func(error) { + var p QueryResultSetNextRowStartInfo + p.Context = c + p.Call = call + res := t.onResultSetNextRow(p) + return func(e error) { + var p QueryResultSetNextRowDoneInfo + p.Error = e + res(p) + } +} +func QueryOnRowScan(t *Query, c *context.Context, call call) func(error) { + var p QueryRowScanStartInfo + p.Context = c + p.Call = call + res := t.onRowScan(p) + return func(e error) { + var p QueryRowScanDoneInfo + p.Error = e + res(p) + } +} +func QueryOnRowScanNamed(t *Query, c *context.Context, call call) func(error) { + var p QueryRowScanNamedStartInfo + p.Context = c + p.Call = call + res := t.onRowScanNamed(p) + return func(e error) { + var p QueryRowScanNamedDoneInfo + p.Error = e + res(p) + } +} +func QueryOnRowScanStruct(t *Query, c *context.Context, call call) func(error) { + var p QueryRowScanStructStartInfo + p.Context = c + p.Call = call + res := t.onRowScanStruct(p) + return func(e error) { + var p QueryRowScanStructDoneInfo + p.Error = e + res(p) + } +} diff --git a/trace/retry.go b/trace/retry.go index 0ac2c483b..c87a149d2 100644 --- a/trace/retry.go +++ b/trace/retry.go @@ -12,7 +12,7 @@ type ( // Retry specified trace of retry call activity. // gtrace:gen Retry struct { - OnRetry func(RetryLoopStartInfo) func(RetryLoopIntermediateInfo) func(RetryLoopDoneInfo) + OnRetry func(RetryLoopStartInfo) func(RetryLoopDoneInfo) } RetryLoopStartInfo struct { // Context make available context in trace callback function. @@ -21,18 +21,12 @@ type ( // Safe replacement of context are provided only inside callback function Context *context.Context - // Deprecated: use Label field instead - ID string - Call call Label string Idempotent bool NestedCall bool // a sign for detect Retry calls inside head Retry } - RetryLoopIntermediateInfo struct { - Error error - } RetryLoopDoneInfo struct { Attempts int Error error diff --git a/trace/retry_gtrace.go b/trace/retry_gtrace.go index ea80a385f..14a35e413 100644 --- a/trace/retry_gtrace.go +++ b/trace/retry_gtrace.go @@ -33,7 +33,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 +41,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,78 +56,44 @@ 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, iD string, call call, label string, idempotent bool, nestedCall bool) func(error) func(attempts int, _ error) { +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.ID = iD p.Call = call p.Label = label 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/sql.go b/trace/sql.go index d98c905b5..e6d5de6ec 100644 --- a/trace/sql.go +++ b/trace/sql.go @@ -159,9 +159,6 @@ type ( TxContext context.Context Tx tableTransactionInfo Query string - - // Deprecated: all transactions are idempotent - Idempotent bool } DatabaseSQLTxQueryDoneInfo struct { Error error @@ -176,9 +173,6 @@ type ( TxContext context.Context Tx tableTransactionInfo Query string - - // Deprecated: all transactions are idempotent - Idempotent bool } DatabaseSQLTxExecDoneInfo struct { Error error diff --git a/trace/sql_gtrace.go b/trace/sql_gtrace.go index a9e59a2ef..e5d2a484f 100644 --- a/trace/sql_gtrace.go +++ b/trace/sql_gtrace.go @@ -1012,14 +1012,13 @@ func DatabaseSQLOnConnIsTableExists(t *DatabaseSQL, c *context.Context, call cal res(p) } } -func DatabaseSQLOnTxQuery(t *DatabaseSQL, c *context.Context, call call, txContext context.Context, tx tableTransactionInfo, query string, idempotent bool) func(error) { +func DatabaseSQLOnTxQuery(t *DatabaseSQL, c *context.Context, call call, txContext context.Context, tx tableTransactionInfo, query string) func(error) { var p DatabaseSQLTxQueryStartInfo p.Context = c p.Call = call p.TxContext = txContext p.Tx = tx p.Query = query - p.Idempotent = idempotent res := t.onTxQuery(p) return func(e error) { var p DatabaseSQLTxQueryDoneInfo @@ -1027,14 +1026,13 @@ func DatabaseSQLOnTxQuery(t *DatabaseSQL, c *context.Context, call call, txConte res(p) } } -func DatabaseSQLOnTxExec(t *DatabaseSQL, c *context.Context, call call, txContext context.Context, tx tableTransactionInfo, query string, idempotent bool) func(error) { +func DatabaseSQLOnTxExec(t *DatabaseSQL, c *context.Context, call call, txContext context.Context, tx tableTransactionInfo, query string) func(error) { var p DatabaseSQLTxExecStartInfo p.Context = c p.Call = call p.TxContext = txContext p.Tx = tx p.Query = query - p.Idempotent = idempotent res := t.onTxExec(p) return func(e error) { var p DatabaseSQLTxExecDoneInfo diff --git a/trace/table.go b/trace/table.go index 81e298324..4e944435a 100644 --- a/trace/table.go +++ b/trace/table.go @@ -16,15 +16,9 @@ type ( // 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, - ) + OnDo func(TableDoStartInfo) func(TableDoDoneInfo) + OnDoTx func(TableDoTxStartInfo) func(TableDoTxDoneInfo) + OnCreateSession func(TableCreateSessionStartInfo) func(TableCreateSessionDoneInfo) // Session events OnSessionNew func(TableSessionNewStartInfo) func(TableSessionNewDoneInfo) OnSessionDelete func(TableSessionDeleteStartInfo) func(TableSessionDeleteDoneInfo) @@ -35,36 +29,22 @@ type ( OnSessionQueryExecute func(TableExecuteDataQueryStartInfo) func(TableExecuteDataQueryDoneInfo) OnSessionQueryExplain func(TableExplainQueryStartInfo) func(TableExplainQueryDoneInfo) // Stream events - OnSessionQueryStreamExecute func( - TableSessionQueryStreamExecuteStartInfo, - ) func( - TableSessionQueryStreamExecuteIntermediateInfo, - ) func( - TableSessionQueryStreamExecuteDoneInfo, - ) - OnSessionQueryStreamRead func( - TableSessionQueryStreamReadStartInfo, - ) func( - TableSessionQueryStreamReadIntermediateInfo, - ) func( - TableSessionQueryStreamReadDoneInfo, - ) + OnSessionQueryStreamExecute func(TableSessionQueryStreamExecuteStartInfo) func(TableSessionQueryStreamExecuteDoneInfo) + OnSessionQueryStreamRead func(TableSessionQueryStreamReadStartInfo) func(TableSessionQueryStreamReadDoneInfo) // Transaction events - OnSessionTransactionBegin func(TableSessionTransactionBeginStartInfo) func( - TableSessionTransactionBeginDoneInfo, + OnTxBegin func(TableTxBeginStartInfo) func( + TableTxBeginDoneInfo, ) - OnSessionTransactionExecute func(TableTransactionExecuteStartInfo) func( + OnTxExecute func(TableTransactionExecuteStartInfo) func( TableTransactionExecuteDoneInfo, ) - OnSessionTransactionExecuteStatement func(TableTransactionExecuteStatementStartInfo) func( + OnTxExecuteStatement func(TableTransactionExecuteStatementStartInfo) func( TableTransactionExecuteStatementDoneInfo, ) - OnSessionTransactionCommit func(TableSessionTransactionCommitStartInfo) func( - TableSessionTransactionCommitDoneInfo, - ) - OnSessionTransactionRollback func(TableSessionTransactionRollbackStartInfo) func( - TableSessionTransactionRollbackDoneInfo, + OnTxCommit func(TableTxCommitStartInfo) func( + TableTxCommitDoneInfo, ) + OnTxRollback func(TableTxRollbackStartInfo) func(TableTxRollbackDoneInfo) // Pool state event OnPoolStateChange func(TablePoolStateChangeInfo) @@ -72,16 +52,6 @@ type ( OnPoolSessionAdd func(info TablePoolSessionAddInfo) OnPoolSessionRemove func(info TablePoolSessionRemoveInfo) - // OnPoolSessionNew is user-defined callback for listening events about creating sessions with - // internal session pool calls - // - // Deprecated: use OnPoolSessionAdd callback - OnPoolSessionNew func(TablePoolSessionNewStartInfo) func(TablePoolSessionNewDoneInfo) - - // OnPoolSessionClose is user-defined callback for listening sessionClose calls - // - // Deprecated: use OnPoolSessionRemove callback - OnPoolSessionClose func(TablePoolSessionCloseStartInfo) func(TablePoolSessionCloseDoneInfo) // Pool common API events OnPoolPut func(TablePoolPutStartInfo) func(TablePoolPutDoneInfo) OnPoolGet func(TablePoolGetStartInfo) func(TablePoolGetDoneInfo) @@ -250,9 +220,6 @@ type ( Call call Session tableSessionInfo } - TableSessionQueryStreamReadIntermediateInfo struct { - Error error - } TableSessionQueryStreamReadDoneInfo struct { Error error } @@ -267,13 +234,10 @@ type ( Query tableDataQuery Parameters tableQueryParameters } - TableSessionQueryStreamExecuteIntermediateInfo struct { - Error error - } TableSessionQueryStreamExecuteDoneInfo struct { Error error } - TableSessionTransactionBeginStartInfo struct { + 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. @@ -282,11 +246,11 @@ type ( Call call Session tableSessionInfo } - TableSessionTransactionBeginDoneInfo struct { + TableTxBeginDoneInfo struct { Tx tableTransactionInfo Error error } - TableSessionTransactionCommitStartInfo struct { + 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. @@ -296,10 +260,10 @@ type ( Session tableSessionInfo Tx tableTransactionInfo } - TableSessionTransactionCommitDoneInfo struct { + TableTxCommitDoneInfo struct { Error error } - TableSessionTransactionRollbackStartInfo struct { + 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. @@ -309,7 +273,7 @@ type ( Session tableSessionInfo Tx tableTransactionInfo } - TableSessionTransactionRollbackDoneInfo struct { + TableTxRollbackDoneInfo struct { Error error } TableInitStartInfo struct { @@ -322,7 +286,6 @@ type ( } TableInitDoneInfo struct { Limit int - Error error } TablePoolStateChangeInfo struct { Size int @@ -415,16 +378,10 @@ type ( Context *context.Context Call call - // Deprecated: use Label field instead - ID string - Label string Idempotent bool NestedCall bool // flag when Retry called inside head Retry } - TableDoIntermediateInfo struct { - Error error - } TableDoDoneInfo struct { Attempts int Error error @@ -437,16 +394,10 @@ type ( Context *context.Context Call call - // Deprecated: use Label field instead - ID string - Label string Idempotent bool NestedCall bool // flag when Retry called inside head Retry } - TableDoTxIntermediateInfo struct { - Error error - } TableDoTxDoneInfo struct { Attempts int Error error @@ -459,9 +410,6 @@ type ( Context *context.Context Call call } - TableCreateSessionIntermediateInfo struct { - Error error - } TableCreateSessionDoneInfo struct { Session tableSessionInfo Attempts int diff --git a/trace/table_gtrace.go b/trace/table_gtrace.go index 95630c8f8..6834d38b9 100644 --- a/trace/table_gtrace.go +++ b/trace/table_gtrace.go @@ -103,7 +103,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 +111,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 +126,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 +138,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 +146,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 +161,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 +173,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 +181,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 +196,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 +453,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 +461,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 +476,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 +488,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 +496,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 +511,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 +531,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 +556,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 +591,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 +626,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 +636,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 +661,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 +671,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 { @@ -832,76 +752,6 @@ func (t *Table) Compose(x *Table, opts ...TableComposeOption) *Table { } } } - { - h1 := t.OnPoolSessionNew - h2 := x.OnPoolSessionNew - ret.OnPoolSessionNew = func(t TablePoolSessionNewStartInfo) func(TablePoolSessionNewDoneInfo) { - if options.panicCallback != nil { - defer func() { - if e := recover(); e != nil { - options.panicCallback(e) - } - }() - } - var r, r1 func(TablePoolSessionNewDoneInfo) - if h1 != nil { - r = h1(t) - } - if h2 != nil { - r1 = h2(t) - } - return func(t TablePoolSessionNewDoneInfo) { - if options.panicCallback != nil { - defer func() { - if e := recover(); e != nil { - options.panicCallback(e) - } - }() - } - if r != nil { - r(t) - } - if r1 != nil { - r1(t) - } - } - } - } - { - h1 := t.OnPoolSessionClose - h2 := x.OnPoolSessionClose - ret.OnPoolSessionClose = func(t TablePoolSessionCloseStartInfo) func(TablePoolSessionCloseDoneInfo) { - if options.panicCallback != nil { - defer func() { - if e := recover(); e != nil { - options.panicCallback(e) - } - }() - } - var r, r1 func(TablePoolSessionCloseDoneInfo) - if h1 != nil { - r = h1(t) - } - if h2 != nil { - r1 = h2(t) - } - return func(t TablePoolSessionCloseDoneInfo) { - if options.panicCallback != nil { - defer func() { - if e := recover(); e != nil { - options.panicCallback(e) - } - }() - } - if r != nil { - r(t) - } - if r1 != nil { - r1(t) - } - } - } - } { h1 := t.OnPoolPut h2 := x.OnPoolPut @@ -1039,86 +889,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 @@ -1225,77 +1039,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 @@ -1309,8 +1099,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 @@ -1324,31 +1114,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 } } @@ -1375,36 +1165,6 @@ func (t *Table) onPoolSessionRemove(info TablePoolSessionRemoveInfo) { } fn(info) } -func (t *Table) onPoolSessionNew(t1 TablePoolSessionNewStartInfo) func(TablePoolSessionNewDoneInfo) { - fn := t.OnPoolSessionNew - if fn == nil { - return func(TablePoolSessionNewDoneInfo) { - return - } - } - res := fn(t1) - if res == nil { - return func(TablePoolSessionNewDoneInfo) { - return - } - } - return res -} -func (t *Table) onPoolSessionClose(t1 TablePoolSessionCloseStartInfo) func(TablePoolSessionCloseDoneInfo) { - fn := t.OnPoolSessionClose - if fn == nil { - return func(TablePoolSessionCloseDoneInfo) { - return - } - } - res := fn(t1) - if res == nil { - return func(TablePoolSessionCloseDoneInfo) { - return - } - } - return res -} func (t *Table) onPoolPut(t1 TablePoolPutStartInfo) func(TablePoolPutDoneInfo) { fn := t.OnPoolPut if fn == nil { @@ -1450,15 +1210,14 @@ func (t *Table) onPoolWait(t1 TablePoolWaitStartInfo) func(TablePoolWaitDoneInfo } return res } -func TableOnInit(t *Table, c *context.Context, call call) func(limit int, _ error) { +func TableOnInit(t *Table, c *context.Context, call call) func(limit int) { var p TableInitStartInfo p.Context = c p.Call = call res := t.onInit(p) - return func(limit int, e error) { + return func(limit int) { var p TableInitDoneInfo p.Limit = limit - p.Error = e res(p) } } @@ -1473,64 +1232,47 @@ func TableOnClose(t *Table, c *context.Context, call call) func(error) { res(p) } } -func TableOnDo(t *Table, c *context.Context, call call, iD string, label string, idempotent bool, nestedCall bool) func(error) func(attempts int, _ error) { +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 - p.ID = iD p.Label = label 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, iD string, label string, idempotent bool, nestedCall bool) func(error) func(attempts int, _ error) { +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 - p.ID = iD p.Label = label 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) { +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) } } func TableOnSessionNew(t *Table, c *context.Context, call call) func(session tableSessionInfo, _ error) { @@ -1628,7 +1370,7 @@ 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) { +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 @@ -1636,48 +1378,38 @@ 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) { +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 +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) { +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 @@ -1685,7 +1417,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 @@ -1693,7 +1425,7 @@ 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) { +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 @@ -1701,7 +1433,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 @@ -1709,28 +1441,28 @@ 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 +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 +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) } @@ -1751,29 +1483,6 @@ func TableOnPoolSessionRemove(t *Table, session tableSessionInfo) { p.Session = session t.onPoolSessionRemove(p) } -func TableOnPoolSessionNew(t *Table, c *context.Context, call call) func(session tableSessionInfo, _ error) { - var p TablePoolSessionNewStartInfo - p.Context = c - p.Call = call - res := t.onPoolSessionNew(p) - return func(session tableSessionInfo, e error) { - var p TablePoolSessionNewDoneInfo - p.Session = session - p.Error = e - res(p) - } -} -func TableOnPoolSessionClose(t *Table, c *context.Context, call call, session tableSessionInfo) func() { - var p TablePoolSessionCloseStartInfo - p.Context = c - p.Call = call - p.Session = session - res := t.onPoolSessionClose(p) - return func() { - var p TablePoolSessionCloseDoneInfo - res(p) - } -} func TableOnPoolPut(t *Table, c *context.Context, call call, session tableSessionInfo) func(error) { var p TablePoolPutStartInfo p.Context = c 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() {