From ea53e84b9a562d6136c8904cb77287679cea0cc4 Mon Sep 17 00:00:00 2001 From: Aleksey Myasnikov Date: Tue, 16 Jan 2024 13:03:07 +0300 Subject: [PATCH 1/3] query client interface and simple implementation --- .github/workflows/check-codegen.yml | 2 +- .github/workflows/tests.yml | 2 +- connection.go | 4 + driver.go | 28 + go.mod | 3 +- go.sum | 28 +- internal/allocator/allocator.go | 270 ++++- internal/discovery/discovery.go | 10 +- internal/operation/status.go | 11 + internal/query/client.go | 243 +++++ internal/query/client_test.go | 241 +++++ internal/query/column.go | 38 + internal/query/config/config.go | 78 ++ internal/query/config/options.go | 49 + internal/query/errors.go | 15 + internal/query/execute_query.go | 75 ++ internal/query/execute_query_test.go | 1071 ++++++++++++++++++++ internal/query/grpc_client_mock_test.go | 468 +++++++++ internal/query/pool.go | 67 ++ internal/query/result.go | 168 +++ internal/query/result_set.go | 68 ++ internal/query/result_set_test.go | 598 +++++++++++ internal/query/result_test.go | 895 ++++++++++++++++ internal/query/row.go | 24 + internal/query/scanner_data.go | 19 + internal/query/scanner_indexed.go | 26 + internal/query/scanner_named.go | 22 + internal/query/scanner_struct.go | 22 + internal/query/session.go | 77 ++ internal/query/session_test.go | 56 + internal/query/transaction.go | 78 ++ internal/query/transaction_test.go | 213 ++++ internal/scripting/client.go | 4 +- internal/session/status.go | 11 + internal/table/scanner/result_test.go | 9 +- internal/table/scanner/scan_raw.go | 7 +- internal/table/scanner/scanner.go | 57 +- internal/table/session.go | 7 +- internal/table/session_test.go | 3 +- internal/{value => types}/type.go | 326 +++--- internal/{value => types}/type_test.go | 2 +- internal/value/value.go | 416 ++++---- internal/value/value_test.go | 93 +- internal/xerrors/operation.go | 35 +- options.go | 19 +- query/client.go | 73 ++ query/column.go | 6 + query/do_options.go | 22 + query/do_tx_options.go | 34 + query/example_test.go | 163 +++ query/execute_options.go | 165 +++ query/named.go | 32 + query/named_test.go | 72 ++ query/options.go | 7 + query/parameters.go | 135 +++ query/result.go | 36 + query/session.go | 26 + query/session_status.go | 33 + query/transaction.go | 27 + query/transaction_control.go | 157 +++ query/transaction_settings.go | 136 +++ query/type.go | 7 + query/value.go | 65 ++ table/options/models.go | 6 +- table/options/options.go | 4 +- table/options/options_test.go | 2 +- table/table.go | 13 +- table/types/types.go | 86 +- tests/integration/query_execute_test.go | 53 + tests/integration/query_tx_execute_test.go | 53 + testutil/compare_test.go | 112 +- trace/query.go | 11 + 72 files changed, 6839 insertions(+), 655 deletions(-) create mode 100644 internal/operation/status.go create mode 100644 internal/query/client.go create mode 100644 internal/query/client_test.go create mode 100644 internal/query/column.go create mode 100644 internal/query/config/config.go create mode 100644 internal/query/config/options.go create mode 100644 internal/query/errors.go create mode 100644 internal/query/execute_query.go create mode 100644 internal/query/execute_query_test.go create mode 100644 internal/query/grpc_client_mock_test.go create mode 100644 internal/query/pool.go create mode 100644 internal/query/result.go create mode 100644 internal/query/result_set.go create mode 100644 internal/query/result_set_test.go create mode 100644 internal/query/result_test.go create mode 100644 internal/query/row.go create mode 100644 internal/query/scanner_data.go create mode 100644 internal/query/scanner_indexed.go create mode 100644 internal/query/scanner_named.go create mode 100644 internal/query/scanner_struct.go create mode 100644 internal/query/session.go create mode 100644 internal/query/session_test.go create mode 100644 internal/query/transaction.go create mode 100644 internal/query/transaction_test.go create mode 100644 internal/session/status.go rename internal/{value => types}/type.go (67%) rename internal/{value => types}/type_test.go (99%) create mode 100644 query/client.go create mode 100644 query/column.go create mode 100644 query/do_options.go create mode 100644 query/do_tx_options.go create mode 100644 query/example_test.go create mode 100644 query/execute_options.go create mode 100644 query/named.go create mode 100644 query/named_test.go create mode 100644 query/options.go create mode 100644 query/parameters.go create mode 100644 query/result.go create mode 100644 query/session.go create mode 100644 query/session_status.go create mode 100644 query/transaction.go create mode 100644 query/transaction_control.go create mode 100644 query/transaction_settings.go create mode 100644 query/type.go create mode 100644 query/value.go create mode 100644 tests/integration/query_execute_test.go create mode 100644 tests/integration/query_tx_execute_test.go create mode 100644 trace/query.go diff --git a/.github/workflows/check-codegen.yml b/.github/workflows/check-codegen.yml index 327a9809d..61963d6f5 100644 --- a/.github/workflows/check-codegen.yml +++ b/.github/workflows/check-codegen.yml @@ -32,7 +32,7 @@ jobs: - name: Build run: | go install ./internal/cmd/gtrace - go install go.uber.org/mock/mockgen@892b665398ecece7c7a900d2a2f2fa3dac07e4c4 + go install go.uber.org/mock/mockgen@v0.4.0 - name: Clean and re-generate *_gtrace.go files run: | diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b814a92a8..11ec91715 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -45,7 +45,7 @@ jobs: fail-fast: false matrix: go-version: [1.21.x, 1.22.x] - ydb-version: [22.5, 23.1, 23.2, 23.3] + ydb-version: [22.5, 23.1, 23.2, 23.3, 24.1] services: ydb: image: cr.yandex/yc/yandex-docker-local-ydb:${{ matrix.ydb-version }} diff --git a/connection.go b/connection.go index 3e8e3d2a4..4a4c6440f 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,9 @@ type Connection interface { // Table returns table client Table() table.Client + // Query returns query client + Query() query.Client + // Scheme returns scheme client Scheme() scheme.Client diff --git a/driver.go b/driver.go index 65eb86a81..e3c371770 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" @@ -68,6 +71,9 @@ type Driver struct { //nolint:maligned table *internalTable.Client tableOptions []tableConfig.Option + query *internalQuery.Client + queryOptions []queryConfig.Option + scripting *internalScripting.Client scriptingOptions []scriptingConfig.Option @@ -141,6 +147,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, @@ -180,6 +187,11 @@ func (d *Driver) Table() table.Client { return d.table } +// Query returns query client +func (d *Driver) Query() query.Client { + return d.query +} + // Scheme returns scheme client func (d *Driver) Scheme() scheme.Client { return d.scheme @@ -399,6 +411,22 @@ func (d *Driver) connect(ctx context.Context) (err error) { return xerrors.WithStackTrace(err) } + d.query, err = internalQuery.New(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.scheme, err = internalScheme.New(ctx, d.balancer, schemeConfig.New( diff --git a/go.mod b/go.mod index 396a84e87..3a753d8e0 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.21 require ( github.com/golang-jwt/jwt/v4 v4.4.1 + github.com/golang/mock v1.1.1 github.com/google/uuid v1.3.0 github.com/jonboulle/clockwork v0.3.0 github.com/ydb-platform/ydb-go-genproto v0.0.0-20240126124512-dbb0e1720dbf @@ -17,7 +18,7 @@ require ( 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..9efe00d0f 100644 --- a/go.sum +++ b/go.sum @@ -23,9 +23,8 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME github.com/golang-jwt/jwt/v4 v4.4.1 h1:pC5DB52sCeK48Wlb9oPcdhnjkz1TKt1D/P7WKJ0kUcQ= 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 h1:G5FRp8JnTd7RQH5kemVNlMeyXQAztQ3mOWV95KxsXH8= 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 +48,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 +58,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 +66,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 +93,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 +100,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 +112,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= diff --git a/internal/allocator/allocator.go b/internal/allocator/allocator.go index 6c55c51b4..82e55c63b 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) } @@ -902,6 +921,164 @@ 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 +1096,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/discovery/discovery.go b/internal/discovery/discovery.go index dba4ca480..8cd0953a0 100644 --- a/internal/discovery/discovery.go +++ b/internal/discovery/discovery.go @@ -70,9 +70,7 @@ func (c *Client) Discover(ctx context.Context) (endpoints []endpoint.Endpoint, e if response.GetOperation().GetStatus() != Ydb.StatusIds_SUCCESS { return nil, xerrors.WithStackTrace( - xerrors.Operation( - xerrors.FromOperation(response.GetOperation()), - ), + xerrors.FromOperation(response.GetOperation()), ) } @@ -126,10 +124,8 @@ func (c *Client) WhoAmI(ctx context.Context) (whoAmI *discovery.WhoAmI, err erro if response.GetOperation().GetStatus() != Ydb.StatusIds_SUCCESS { return nil, xerrors.WithStackTrace( - xerrors.Operation( - xerrors.FromOperation( - response.GetOperation(), - ), + xerrors.FromOperation( + response.GetOperation(), ), ) } diff --git a/internal/operation/status.go b/internal/operation/status.go new file mode 100644 index 000000000..4b57ff53d --- /dev/null +++ b/internal/operation/status.go @@ -0,0 +1,11 @@ +package operation + +import ( + "github.com/ydb-platform/ydb-go-genproto/protos/Ydb" + "github.com/ydb-platform/ydb-go-genproto/protos/Ydb_Issue" +) + +type Status interface { + GetStatus() Ydb.StatusIds_StatusCode + GetIssues() []*Ydb_Issue.IssueMessage +} diff --git a/internal/query/client.go b/internal/query/client.go new file mode 100644 index 000000000..6eaa0bc1c --- /dev/null +++ b/internal/query/client.go @@ -0,0 +1,243 @@ +package query + +import ( + "context" + "sync" + "sync/atomic" + "time" + + "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" + "google.golang.org/grpc" + + "github.com/ydb-platform/ydb-go-sdk/v3/internal/query/config" + "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" +) + +//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 balancer interface { + grpc.ClientConnInterface +} + +var _ query.Client = (*Client)(nil) + +type Client struct { + grpcClient Ydb_Query_V1.QueryServiceClient + pool Pool +} + +func (c Client) Close(ctx context.Context) error { + err := c.pool.Close(ctx) + if err != nil { + return xerrors.WithStackTrace(err) + } + return nil +} + +func do(ctx context.Context, pool Pool, op query.Operation, opts query.DoOptions) error { + return retry.Retry(ctx, func(ctx context.Context) error { + err := pool.With(ctx, func(ctx context.Context, s *Session) error { + err := op(ctx, s) + if err != nil { + return xerrors.WithStackTrace(err) + } + return nil + }) + if err != nil { + return xerrors.WithStackTrace(err) + } + return nil + }, opts.RetryOptions...) +} + +func (c Client) Do(ctx context.Context, op query.Operation, opts ...query.DoOption) error { + doOptions := query.NewDoOptions(opts...) + if doOptions.Label != "" { + doOptions.RetryOptions = append(doOptions.RetryOptions, retry.WithLabel(doOptions.Label)) + } + if doOptions.Idempotent { + doOptions.RetryOptions = append(doOptions.RetryOptions, retry.WithIdempotent(doOptions.Idempotent)) + } + return do(ctx, c.pool, op, doOptions) +} + +func doTx(ctx context.Context, pool Pool, op query.TxOperation, opts query.DoTxOptions) error { + return do(ctx, pool, func(ctx context.Context, s query.Session) error { + tx, err := s.Begin(ctx, opts.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 + }, opts.DoOptions) +} + +func (c Client) DoTx(ctx context.Context, op query.TxOperation, opts ...query.DoTxOption) error { + doTxOptions := query.NewDoTxOptions(opts...) + if doTxOptions.Label != "" { + doTxOptions.RetryOptions = append(doTxOptions.RetryOptions, retry.WithLabel(doTxOptions.Label)) + } + if doTxOptions.Idempotent { + doTxOptions.RetryOptions = append(doTxOptions.RetryOptions, retry.WithIdempotent(doTxOptions.Idempotent)) + } + return doTx(ctx, c.pool, op, doTxOptions) +} + +func deleteSession(ctx context.Context, client Ydb_Query_V1.QueryServiceClient, sessionID string) error { + response, err := client.DeleteSession(ctx, + &Ydb_Query.DeleteSessionRequest{ + SessionId: sessionID, + }, + ) + if err != nil { + return xerrors.WithStackTrace(xerrors.Transport(err)) + } + if response.GetStatus() != Ydb.StatusIds_SUCCESS { + return xerrors.WithStackTrace(xerrors.FromOperation(response)) + } + return nil +} + +type createSessionSettings struct { + createSessionTimeout time.Duration + onDetach func(id string) + onAttach func(id string) +} + +func createSession(ctx context.Context, client Ydb_Query_V1.QueryServiceClient, settings createSessionSettings) (_ *Session, finalErr error) { + var ( + createSessionCtx context.Context + cancelCreateSession context.CancelFunc + ) + if settings.createSessionTimeout > 0 { + createSessionCtx, cancelCreateSession = xcontext.WithTimeout(ctx, settings.createSessionTimeout) + } else { + createSessionCtx, cancelCreateSession = xcontext.WithCancel(ctx) + } + defer cancelCreateSession() + s, err := client.CreateSession(createSessionCtx, &Ydb_Query.CreateSessionRequest{}) + if err != nil { + return nil, xerrors.WithStackTrace( + xerrors.Transport(err), + ) + } + if s.GetStatus() != Ydb.StatusIds_SUCCESS { + return nil, xerrors.WithStackTrace( + xerrors.FromOperation(s), + ) + } + defer func() { + if finalErr != nil { + deleteSession(ctx, client, s.GetSessionId()) + } + }() + attachCtx, cancelAttach := xcontext.WithCancel(context.Background()) + defer func() { + if finalErr != nil { + cancelAttach() + } + }() + attach, err := client.AttachSession(attachCtx, &Ydb_Query.AttachSessionRequest{ + SessionId: s.GetSessionId(), + }) + if err != nil { + return nil, xerrors.WithStackTrace( + xerrors.Transport(err), + ) + } + defer func() { + if finalErr != nil { + attach.CloseSend() + } + }() + state, err := attach.Recv() + if err != nil { + return nil, xerrors.WithStackTrace(xerrors.Transport(err)) + } + if state.GetStatus() != Ydb.StatusIds_SUCCESS { + return nil, xerrors.WithStackTrace(xerrors.FromOperation(state)) + } + if settings.onAttach != nil { + settings.onAttach(s.GetSessionId()) + } + session := &Session{ + id: s.GetSessionId(), + nodeID: s.GetNodeId(), + queryClient: client, + status: query.SessionStatusReady, + } + session.close = sync.OnceFunc(func() { + if settings.onDetach != nil { + settings.onDetach(session.id) + } + attach.CloseSend() + cancelAttach() + atomic.StoreUint32( + (*uint32)(&session.status), + uint32(query.SessionStatusClosed), + ) + }) + + go func() { + defer session.close() + for { + switch session.Status() { + case query.SessionStatusReady, query.SessionStatusInUse: + state, err := attach.Recv() + if err != nil || state.GetStatus() != Ydb.StatusIds_SUCCESS { + return + } + default: + return + } + } + }() + + return session, nil +} + +func New(ctx context.Context, balancer balancer, config *config.Config) (*Client, error) { + grpcClient := Ydb_Query_V1.NewQueryServiceClient(balancer) + return &Client{ + grpcClient: grpcClient, + pool: newStubPool( + //config.PoolMaxSize(), + func(ctx context.Context) (_ *Session, err error) { + s, err := createSession(ctx, grpcClient, createSessionSettings{ + createSessionTimeout: config.CreateSessionTimeout(), + }) + if err != nil { + return nil, xerrors.WithStackTrace(err) + } + return s, nil + }, + func(ctx context.Context, s *Session) error { + err := deleteSession(ctx, s.queryClient, s.id) + if err != nil { + return xerrors.WithStackTrace(err) + } + return nil + }, + ), + }, nil +} diff --git a/internal/query/client_test.go b/internal/query/client_test.go new file mode 100644 index 000000000..9b154317f --- /dev/null +++ b/internal/query/client_test.go @@ -0,0 +1,241 @@ +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/xerrors" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/xtest" + "github.com/ydb-platform/ydb-go-sdk/v3/query" +) + +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() + attachStream.EXPECT().CloseSend().Return(nil) + 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) + t.Log("execute") + var attached = 0 + s, err := createSession(ctx, service, createSessionSettings{ + onAttach: func(id string) { + attached++ + }, + onDetach: func(id string) { + attached-- + }, + }) + require.NoError(t, err) + require.EqualValues(t, "test", s.id) + require.EqualValues(t, 1, attached) + s.close() + 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, "")) + t.Log("execute") + _, err := createSession(ctx, service, createSessionSettings{}) + 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, "")) + t.Log("execute") + _, err := createSession(ctx, service, createSessionSettings{}) + 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() + attachStream.EXPECT().CloseSend().Return(nil) + 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) + t.Log("execute") + _, err := createSession(ctx, service, createSessionSettings{}) + 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(&Ydb_Query.CreateSessionResponse{ + Status: Ydb.StatusIds_UNAVAILABLE, + }, nil) + t.Log("execute") + _, err := createSession(ctx, service, createSessionSettings{}) + 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(&Ydb_Query.SessionState{ + Status: Ydb.StatusIds_UNAVAILABLE, + }, nil).AnyTimes() + attachStream.EXPECT().CloseSend().Return(nil) + 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) + t.Log("execute") + _, err := createSession(ctx, service, createSessionSettings{}) + require.Error(t, err) + require.True(t, xerrors.IsOperationError(err, Ydb.StatusIds_UNAVAILABLE)) + }, xtest.StopAfter(time.Second)) + }) + }) +} + +func newTestSession() (*Session, error) { + return &Session{ + close: func() {}, + }, nil +} + +func newTestSessionWithClient(client Ydb_Query_V1.QueryServiceClient) (*Session, error) { + return &Session{ + queryClient: client, + close: func() {}, + }, nil +} + +func newTestPool(t testing.TB, createSession func(ctx context.Context) (*Session, error)) *stubPool { + return newStubPool( + createSession, + func(ctx context.Context, s *Session) error { + return s.Close(ctx) + }, + ) +} + +func TestDo(t *testing.T) { + ctx := xtest.Context(t) + t.Run("HappyWay", func(t *testing.T) { + err := do(ctx, newTestPool(t, func(ctx context.Context) (*Session, error) { + return newTestSession() + }), func(ctx context.Context, s query.Session) error { + return nil + }, query.DoOptions{}) + require.NoError(t, err) + }) + t.Run("RetryableError", func(t *testing.T) { + counter := 0 + err := do(ctx, newTestPool(t, func(ctx context.Context) (*Session, error) { + return newTestSession() + }), func(ctx context.Context, s query.Session) error { + counter++ + if counter < 10 { + return xerrors.Retryable(errors.New("")) + } + return nil + }, query.DoOptions{}) + require.NoError(t, err) + 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) + err := doTx(ctx, newTestPool(t, func(ctx context.Context) (*Session, error) { + return newTestSessionWithClient(client) + }), func(ctx context.Context, tx query.TxActor) error { + return nil + }, query.DoTxOptions{}) + require.NoError(t, err) + }) + 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() + err := doTx(ctx, newTestPool(t, func(ctx context.Context) (*Session, error) { + return newTestSessionWithClient(client) + }), func(ctx context.Context, tx query.TxActor) error { + counter++ + if counter < 10 { + return xerrors.Retryable(errors.New("")) + } + return nil + }, query.DoTxOptions{}) + require.NoError(t, err) + require.Equal(t, 10, counter) + }) +} diff --git a/internal/query/column.go b/internal/query/column.go new file mode 100644 index 000000000..f320e06a5 --- /dev/null +++ b/internal/query/column.go @@ -0,0 +1,38 @@ +package query + +import ( + "github.com/ydb-platform/ydb-go-genproto/protos/Ydb" + + "github.com/ydb-platform/ydb-go-sdk/v3/internal/types" + "github.com/ydb-platform/ydb-go-sdk/v3/query" +) + +var _ query.Column = (*column)(nil) + +type column struct { + n string + t query.Type +} + +func newColumn(c *Ydb.Column) *column { + return &column{ + n: c.GetName(), + t: types.TypeFromYDB(c.GetType()), + } +} + +func newColumns(cc []*Ydb.Column) (columns []query.Column) { + columns = make([]query.Column, len(cc)) + for i := range cc { + columns[i] = newColumn(cc[i]) + } + return columns +} + +func (c *column) Name() string { + return c.n +} + +func (c *column) Type() query.Type { + return c.t +} diff --git a/internal/query/config/config.go b/internal/query/config/config.go new file mode 100644 index 000000000..06ab44f81 --- /dev/null +++ b/internal/query/config/config.go @@ -0,0 +1,78 @@ +package config + +import ( + "time" + + "github.com/jonboulle/clockwork" + + "github.com/ydb-platform/ydb-go-sdk/v3/internal/config" + "github.com/ydb-platform/ydb-go-sdk/v3/trace" +) + +const ( + DefaultPoolDeleteTimeout = 500 * time.Millisecond + DefaultPoolCreateSessionTimeout = 5 * time.Second + DefaultPoolMaxSize = 50 +) + +type Config struct { + config.Common + + sizeLimit int + + createSessionTimeout time.Duration + deleteTimeout time.Duration + + trace *trace.Query + + clock clockwork.Clock +} + +func New(opts ...Option) *Config { + c := defaults() + for _, o := range opts { + if o != nil { + o(c) + } + } + return c +} + +func defaults() *Config { + return &Config{ + sizeLimit: DefaultPoolMaxSize, + createSessionTimeout: DefaultPoolCreateSessionTimeout, + deleteTimeout: DefaultPoolDeleteTimeout, + clock: clockwork.NewRealClock(), + trace: &trace.Query{}, + } +} + +// Trace defines trace over table client calls +func (c *Config) Trace() *trace.Query { + return c.trace +} + +// Clock defines clock +func (c *Config) Clock() clockwork.Clock { + return c.clock +} + +// PoolMaxSize is an upper bound of pooled sessions. +// If PoolMaxSize is less than or equal to zero then the +// DefaultPoolMaxSize variable is used as a limit. +func (c *Config) PoolMaxSize() int { + return c.sizeLimit +} + +// CreateSessionTimeout limits maximum time spent on Create session request +func (c *Config) CreateSessionTimeout() time.Duration { + return c.createSessionTimeout +} + +// DeleteTimeout limits maximum time spent on Delete request +// +// If DeleteTimeout is less than or equal to zero then the DefaultPoolDeleteTimeout is used. +func (c *Config) DeleteTimeout() time.Duration { + return c.deleteTimeout +} diff --git a/internal/query/config/options.go b/internal/query/config/options.go new file mode 100644 index 000000000..347f15f54 --- /dev/null +++ b/internal/query/config/options.go @@ -0,0 +1,49 @@ +package config + +import ( + "time" + + "github.com/ydb-platform/ydb-go-sdk/v3/internal/config" +) + +type Option func(*Config) + +// With applies common configuration params +func With(config config.Common) Option { + return func(c *Config) { + c.Common = config + } +} + +// WithSizeLimit defines upper bound of pooled sessions. +// If sizeLimit is less than or equal to zero then the +// DefaultPoolMaxSize variable is used as a limit. +func WithSizeLimit(sizeLimit int) Option { + return func(c *Config) { + if sizeLimit > 0 { + c.sizeLimit = sizeLimit + } + } +} + +// WithCreateSessionTimeout limits maximum time spent on Create session request +// If createSessionTimeout is less than or equal to zero then no used timeout on create session request +func WithCreateSessionTimeout(createSessionTimeout time.Duration) Option { + return func(c *Config) { + if createSessionTimeout > 0 { + c.createSessionTimeout = createSessionTimeout + } else { + c.createSessionTimeout = 0 + } + } +} + +// WithDeleteTimeout limits maximum time spent on Delete request +// If deleteTimeout is less than or equal to zero then the DefaultPoolDeleteTimeout is used. +func WithDeleteTimeout(deleteTimeout time.Duration) Option { + return func(c *Config) { + if deleteTimeout > 0 { + c.deleteTimeout = deleteTimeout + } + } +} diff --git a/internal/query/errors.go b/internal/query/errors.go new file mode 100644 index 000000000..ffb9b5272 --- /dev/null +++ b/internal/query/errors.go @@ -0,0 +1,15 @@ +package query + +import ( + "errors" +) + +var ( + ErrNotImplemented = errors.New("not implemented yet") + errRedundantCallNextResultSet = errors.New("redundant call NextResultSet()") + errWrongNextResultSetIndex = errors.New("wrong result set index") + errInterruptedStream = errors.New("interrupted stream") + errClosedResult = errors.New("result closed early") + errWrongResultSetIndex = errors.New("critical violation of the logic - wrong result set index") + errWrongArgumentsCount = errors.New("wrong arguments count") +) diff --git a/internal/query/execute_query.go b/internal/query/execute_query.go new file mode 100644 index 000000000..e9bd8406f --- /dev/null +++ b/internal/query/execute_query.go @@ -0,0 +1,75 @@ +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/xcontext" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors" + "github.com/ydb-platform/ydb-go-sdk/v3/query" +) + +type executeSettings interface { + ExecMode() query.ExecMode + StatsMode() query.StatsMode + TxControl() *query.TransactionControl + Syntax() query.Syntax + Params() *query.Parameters + CallOptions() []grpc.CallOption +} + +func executeQueryRequest(a *allocator.Allocator, sessionID string, q string, settings executeSettings) ( + *Ydb_Query.ExecuteQueryRequest, + []grpc.CallOption, +) { + request := a.QueryExecuteQueryRequest() + + request.SessionId = sessionID + request.ExecMode = Ydb_Query.ExecMode(settings.ExecMode()) + request.TxControl = settings.TxControl().ToYDB(a) + request.Query = queryFromText(a, q, settings.Syntax()) + request.Parameters = settings.Params().ToYDB(a) + request.StatsMode = Ydb_Query.StatsMode(settings.StatsMode()) + request.ConcurrentResultSets = false + + return request, settings.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, session *Session, client Ydb_Query_V1.QueryServiceClient, q string, settings executeSettings, +) (*transaction, *result, error) { + a := allocator.New() + request, callOptions := executeQueryRequest(a, session.id, q, settings) + defer func() { + a.Free() + }() + + ctx, cancel := xcontext.WithCancel(ctx) + + stream, err := client.ExecuteQuery(ctx, request, callOptions...) + if err != nil { + cancel() + return nil, nil, xerrors.WithStackTrace(err) + } + r, txID, err := newResult(ctx, stream, cancel) + if err != nil { + cancel() + return nil, nil, xerrors.WithStackTrace(err) + } + return &transaction{ + id: txID, + s: session, + }, r, nil +} diff --git a/internal/query/execute_query_test.go b/internal/query/execute_query_test.go new file mode 100644 index 000000000..c68c3739e --- /dev/null +++ b/internal/query/execute_query_test.go @@ -0,0 +1,1071 @@ +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/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) + t.Log("execute") + tx, r, err := execute(ctx, &Session{id: "123"}, service, "", query.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.next(ctx) + require.NoError(t, err) + require.EqualValues(t, 0, rs.rowIndex) + } + { + t.Log("next (row=2)") + _, err := rs.next(ctx) + require.NoError(t, err) + require.EqualValues(t, 1, rs.rowIndex) + } + { + t.Log("next (row=3)") + _, err := rs.next(ctx) + require.NoError(t, err) + require.EqualValues(t, 2, rs.rowIndex) + } + { + t.Log("next (row=4)") + _, err := rs.next(ctx) + require.NoError(t, err) + require.EqualValues(t, 0, rs.rowIndex) + } + { + t.Log("next (row=5)") + _, err := rs.next(ctx) + require.NoError(t, err) + require.EqualValues(t, 1, rs.rowIndex) + } + { + t.Log("next (row=6)") + _, err := rs.next(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.next(ctx) + require.NoError(t, err) + require.EqualValues(t, 0, rs.rowIndex) + } + { + t.Log("next (row=2)") + _, err := rs.next(ctx) + require.NoError(t, err) + require.EqualValues(t, 1, rs.rowIndex) + } + { + t.Log("next (row=3)") + _, err := rs.next(ctx) + require.NoError(t, err) + require.EqualValues(t, 0, rs.rowIndex) + } + { + t.Log("next (row=4)") + _, err := rs.next(ctx) + require.NoError(t, err) + require.EqualValues(t, 1, rs.rowIndex) + } + { + t.Log("next (row=5)") + _, err := rs.next(ctx) + require.NoError(t, err) + require.EqualValues(t, 2, rs.rowIndex) + } + { + t.Log("next (row=6)") + _, err := rs.next(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, &Session{id: "123"}, service, "", query.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, &Session{id: "123"}, service, "", query.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.next(ctx) + require.NoError(t, err) + require.EqualValues(t, 0, rs.rowIndex) + } + { + t.Log("next (row=2)") + _, err := rs.next(ctx) + require.NoError(t, err) + require.EqualValues(t, 1, rs.rowIndex) + } + { + t.Log("next (row=3)") + _, err := rs.next(ctx) + require.NoError(t, err) + require.EqualValues(t, 2, rs.rowIndex) + } + { + t.Log("next (row=4)") + _, err := rs.next(ctx) + require.NoError(t, err) + require.EqualValues(t, 0, rs.rowIndex) + } + { + t.Log("next (row=5)") + _, err := rs.next(ctx) + require.NoError(t, err) + require.EqualValues(t, 1, rs.rowIndex) + } + { + t.Log("next (row=6)") + _, err := rs.next(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(&Ydb_Query.ExecuteQueryResponsePart{ + Status: Ydb.StatusIds_UNAVAILABLE, + }, nil) + service := NewMockQueryServiceClient(ctrl) + service.EXPECT().ExecuteQuery(gomock.Any(), gomock.Any()).Return(stream, nil) + t.Log("execute") + _, _, err := execute(ctx, &Session{id: "123"}, service, "", query.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(&Ydb_Query.ExecuteQueryResponsePart{ + Status: Ydb.StatusIds_UNAVAILABLE, + }, nil) + service := NewMockQueryServiceClient(ctrl) + service.EXPECT().ExecuteQuery(gomock.Any(), gomock.Any()).Return(stream, nil) + t.Log("execute") + tx, r, err := execute(ctx, &Session{id: "123"}, service, "", query.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.next(ctx) + require.NoError(t, err) + require.EqualValues(t, 0, rs.rowIndex) + } + { + t.Log("next (row=2)") + _, err := rs.next(ctx) + require.NoError(t, err) + require.EqualValues(t, 1, rs.rowIndex) + } + { + t.Log("next (row=3)") + _, err := rs.next(ctx) + require.NoError(t, err) + require.EqualValues(t, 2, rs.rowIndex) + } + { + t.Log("next (row=4)") + _, err := rs.next(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 []query.ExecuteOption + request *Ydb_Query.ExecuteQueryRequest + callOptions []grpc.CallOption + }{ + { + name: "WithoutOptions", + request: &Ydb_Query.ExecuteQueryRequest{ + SessionId: "WithoutOptions", + 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: "WithoutOptions", + }, + }, + StatsMode: Ydb_Query.StatsMode_STATS_MODE_NONE, + ConcurrentResultSets: false, + }, + }, + { + name: "WithParams", + opts: []query.ExecuteOption{ + query.WithParameters( + query.Param("$a", query.TextValue("A")), + query.Param("$b", query.TextValue("B")), + query.Param("$c", query.TextValue("C")), + ), + }, + request: &Ydb_Query.ExecuteQueryRequest{ + SessionId: "WithParams", + ExecMode: Ydb_Query.ExecMode_EXEC_MODE_EXECUTE, + TxControl: &Ydb_Query.TransactionControl{ + TxSelector: &Ydb_Query.TransactionControl_BeginTx{ + BeginTx: &Ydb_Query.TransactionSettings{ + TxMode: &Ydb_Query.TransactionSettings_SerializableReadWrite{ + SerializableReadWrite: &Ydb_Query.SerializableModeSettings{}, + }, + }, + }, + CommitTx: true, + }, + Query: &Ydb_Query.ExecuteQueryRequest_QueryContent{ + QueryContent: &Ydb_Query.QueryContent{ + Syntax: Ydb_Query.Syntax_SYNTAX_YQL_V1, + 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: []query.ExecuteOption{ + query.WithExecMode(query.ExecModeExplain), + }, + request: &Ydb_Query.ExecuteQueryRequest{ + SessionId: "WithExplain", + ExecMode: Ydb_Query.ExecMode_EXEC_MODE_EXPLAIN, + TxControl: &Ydb_Query.TransactionControl{ + TxSelector: &Ydb_Query.TransactionControl_BeginTx{ + BeginTx: &Ydb_Query.TransactionSettings{ + TxMode: &Ydb_Query.TransactionSettings_SerializableReadWrite{ + SerializableReadWrite: &Ydb_Query.SerializableModeSettings{}, + }, + }, + }, + CommitTx: true, + }, + Query: &Ydb_Query.ExecuteQueryRequest_QueryContent{ + QueryContent: &Ydb_Query.QueryContent{ + Syntax: Ydb_Query.Syntax_SYNTAX_YQL_V1, + Text: "WithExplain", + }, + }, + StatsMode: Ydb_Query.StatsMode_STATS_MODE_NONE, + ConcurrentResultSets: false, + }, + }, + { + name: "WithValidate", + opts: []query.ExecuteOption{ + query.WithExecMode(query.ExecModeValidate), + }, + request: &Ydb_Query.ExecuteQueryRequest{ + SessionId: "WithValidate", + ExecMode: Ydb_Query.ExecMode_EXEC_MODE_VALIDATE, + TxControl: &Ydb_Query.TransactionControl{ + TxSelector: &Ydb_Query.TransactionControl_BeginTx{ + BeginTx: &Ydb_Query.TransactionSettings{ + TxMode: &Ydb_Query.TransactionSettings_SerializableReadWrite{ + SerializableReadWrite: &Ydb_Query.SerializableModeSettings{}, + }, + }, + }, + CommitTx: true, + }, + Query: &Ydb_Query.ExecuteQueryRequest_QueryContent{ + QueryContent: &Ydb_Query.QueryContent{ + Syntax: Ydb_Query.Syntax_SYNTAX_YQL_V1, + Text: "WithValidate", + }, + }, + StatsMode: Ydb_Query.StatsMode_STATS_MODE_NONE, + ConcurrentResultSets: false, + }, + }, + { + name: "WithValidate", + opts: []query.ExecuteOption{ + query.WithExecMode(query.ExecModeParse), + }, + request: &Ydb_Query.ExecuteQueryRequest{ + SessionId: "WithValidate", + ExecMode: Ydb_Query.ExecMode_EXEC_MODE_PARSE, + TxControl: &Ydb_Query.TransactionControl{ + TxSelector: &Ydb_Query.TransactionControl_BeginTx{ + BeginTx: &Ydb_Query.TransactionSettings{ + TxMode: &Ydb_Query.TransactionSettings_SerializableReadWrite{ + SerializableReadWrite: &Ydb_Query.SerializableModeSettings{}, + }, + }, + }, + CommitTx: true, + }, + Query: &Ydb_Query.ExecuteQueryRequest_QueryContent{ + QueryContent: &Ydb_Query.QueryContent{ + Syntax: Ydb_Query.Syntax_SYNTAX_YQL_V1, + Text: "WithValidate", + }, + }, + StatsMode: Ydb_Query.StatsMode_STATS_MODE_NONE, + ConcurrentResultSets: false, + }, + }, + { + name: "WithStatsFull", + opts: []query.ExecuteOption{ + query.WithStatsMode(query.StatsModeFull), + }, + request: &Ydb_Query.ExecuteQueryRequest{ + SessionId: "WithStatsFull", + ExecMode: Ydb_Query.ExecMode_EXEC_MODE_EXECUTE, + TxControl: &Ydb_Query.TransactionControl{ + TxSelector: &Ydb_Query.TransactionControl_BeginTx{ + BeginTx: &Ydb_Query.TransactionSettings{ + TxMode: &Ydb_Query.TransactionSettings_SerializableReadWrite{ + SerializableReadWrite: &Ydb_Query.SerializableModeSettings{}, + }, + }, + }, + CommitTx: true, + }, + Query: &Ydb_Query.ExecuteQueryRequest_QueryContent{ + QueryContent: &Ydb_Query.QueryContent{ + Syntax: Ydb_Query.Syntax_SYNTAX_YQL_V1, + Text: "WithStatsFull", + }, + }, + StatsMode: Ydb_Query.StatsMode_STATS_MODE_FULL, + ConcurrentResultSets: false, + }, + }, + { + name: "WithStatsBasic", + opts: []query.ExecuteOption{ + query.WithStatsMode(query.StatsModeBasic), + }, + request: &Ydb_Query.ExecuteQueryRequest{ + SessionId: "WithStatsBasic", + ExecMode: Ydb_Query.ExecMode_EXEC_MODE_EXECUTE, + TxControl: &Ydb_Query.TransactionControl{ + TxSelector: &Ydb_Query.TransactionControl_BeginTx{ + BeginTx: &Ydb_Query.TransactionSettings{ + TxMode: &Ydb_Query.TransactionSettings_SerializableReadWrite{ + SerializableReadWrite: &Ydb_Query.SerializableModeSettings{}, + }, + }, + }, + CommitTx: true, + }, + Query: &Ydb_Query.ExecuteQueryRequest_QueryContent{ + QueryContent: &Ydb_Query.QueryContent{ + Syntax: Ydb_Query.Syntax_SYNTAX_YQL_V1, + Text: "WithStatsBasic", + }, + }, + StatsMode: Ydb_Query.StatsMode_STATS_MODE_BASIC, + ConcurrentResultSets: false, + }, + }, + { + name: "WithStatsProfile", + opts: []query.ExecuteOption{ + query.WithStatsMode(query.StatsModeProfile), + }, + request: &Ydb_Query.ExecuteQueryRequest{ + SessionId: "WithStatsProfile", + ExecMode: Ydb_Query.ExecMode_EXEC_MODE_EXECUTE, + TxControl: &Ydb_Query.TransactionControl{ + TxSelector: &Ydb_Query.TransactionControl_BeginTx{ + BeginTx: &Ydb_Query.TransactionSettings{ + TxMode: &Ydb_Query.TransactionSettings_SerializableReadWrite{ + SerializableReadWrite: &Ydb_Query.SerializableModeSettings{}, + }, + }, + }, + CommitTx: true, + }, + Query: &Ydb_Query.ExecuteQueryRequest_QueryContent{ + QueryContent: &Ydb_Query.QueryContent{ + Syntax: Ydb_Query.Syntax_SYNTAX_YQL_V1, + Text: "WithStatsProfile", + }, + }, + StatsMode: Ydb_Query.StatsMode_STATS_MODE_PROFILE, + ConcurrentResultSets: false, + }, + }, + { + name: "WithGrpcCallOptions", + opts: []query.ExecuteOption{ + query.WithCallOptions(grpc.Header(&metadata.MD{ + "ext-header": []string{"test"}, + })), + }, + request: &Ydb_Query.ExecuteQueryRequest{ + SessionId: "WithGrpcCallOptions", + ExecMode: Ydb_Query.ExecMode_EXEC_MODE_EXECUTE, + TxControl: &Ydb_Query.TransactionControl{ + TxSelector: &Ydb_Query.TransactionControl_BeginTx{ + BeginTx: &Ydb_Query.TransactionSettings{ + TxMode: &Ydb_Query.TransactionSettings_SerializableReadWrite{ + SerializableReadWrite: &Ydb_Query.SerializableModeSettings{}, + }, + }, + }, + CommitTx: true, + }, + Query: &Ydb_Query.ExecuteQueryRequest_QueryContent{ + QueryContent: &Ydb_Query.QueryContent{ + Syntax: Ydb_Query.Syntax_SYNTAX_YQL_V1, + 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, query.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/pool.go b/internal/query/pool.go new file mode 100644 index 000000000..3753c5ead --- /dev/null +++ b/internal/query/pool.go @@ -0,0 +1,67 @@ +package query + +import ( + "context" + + "github.com/ydb-platform/ydb-go-sdk/v3/internal/closer" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors" +) + +type Pool interface { + closer.Closer + + With(ctx context.Context, f func(ctx context.Context, s *Session) error) error +} + +var _ Pool = (*stubPool)(nil) + +type stubPool struct { + create func(ctx context.Context) (*Session, error) + close func(ctx context.Context, s *Session) error +} + +func newStubPool( + create func(ctx context.Context) (*Session, error), + close func(ctx context.Context, s *Session) error, +) *stubPool { + return &stubPool{ + create: create, + close: close, + } +} + +func (pool *stubPool) Close(ctx context.Context) error { + return nil +} + +func (pool *stubPool) get(ctx context.Context) (*Session, error) { + select { + case <-ctx.Done(): + return nil, xerrors.WithStackTrace(ctx.Err()) + default: + s, err := pool.create(ctx) + if err != nil { + return nil, xerrors.WithStackTrace(err) + } + return s, nil + } +} + +func (pool *stubPool) put(ctx context.Context, s *Session) { + pool.close(ctx, s) +} + +func (pool *stubPool) With(ctx context.Context, f func(ctx context.Context, s *Session) error) error { + s, err := pool.get(ctx) + if err != nil { + return xerrors.WithStackTrace(err) + } + defer func() { + pool.put(ctx, s) + }() + err = f(ctx, s) + if err != nil { + return xerrors.WithStackTrace(err) + } + return nil +} diff --git a/internal/query/result.go b/internal/query/result.go new file mode 100644 index 000000000..75cd8f888 --- /dev/null +++ b/internal/query/result.go @@ -0,0 +1,168 @@ +package query + +import ( + "context" + "fmt" + "io" + "sync" + + "github.com/ydb-platform/ydb-go-genproto/Ydb_Query_V1" + "github.com/ydb-platform/ydb-go-genproto/protos/Ydb" + "github.com/ydb-platform/ydb-go-genproto/protos/Ydb_Query" + + "github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors" + "github.com/ydb-platform/ydb-go-sdk/v3/query" +) + +var _ query.Result = (*result)(nil) + +type result struct { + stream Ydb_Query_V1.QueryService_ExecuteQueryClient + interrupt func() + close func() + lastPart *Ydb_Query.ExecuteQueryResponsePart + resultSetIndex int64 + errs []error + interrupted chan struct{} + closed chan struct{} +} + +func newResult( + ctx context.Context, + stream Ydb_Query_V1.QueryService_ExecuteQueryClient, + stop func(), +) (_ *result, txID string, _ error) { + interrupted := make(chan struct{}) + r := result{ + stream: stream, + resultSetIndex: -1, + interrupted: interrupted, + closed: make(chan struct{}), + interrupt: sync.OnceFunc(func() { + close(interrupted) + stop() + }), + } + select { + case <-ctx.Done(): + return nil, txID, xerrors.WithStackTrace(ctx.Err()) + default: + part, err := nextPart(stream) + if err != nil { + return nil, txID, xerrors.WithStackTrace(err) + } + r.lastPart = part + r.close = sync.OnceFunc(func() { + r.interrupt() + close(r.closed) + }) + return &r, part.GetTxMeta().GetId(), nil + } +} + +func nextPart(stream Ydb_Query_V1.QueryService_ExecuteQueryClient) (*Ydb_Query.ExecuteQueryResponsePart, error) { + part, err := stream.Recv() + if err != nil { + if xerrors.Is(err, io.EOF) { + return nil, xerrors.WithStackTrace(err) + } + return nil, xerrors.WithStackTrace(xerrors.Transport(err)) + } + if status := part.GetStatus(); status != Ydb.StatusIds_SUCCESS { + return nil, xerrors.WithStackTrace( + xerrors.FromOperation(part), + ) + } + return part, nil +} + +func (r *result) Close(ctx context.Context) error { + r.close() + return nil +} + +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: + select { + case <-r.interrupted: + return nil, xerrors.WithStackTrace(errInterruptedStream) + default: + if r.lastPart.GetResultSetIndex() >= nextResultSetIndex { + r.resultSetIndex = r.lastPart.GetResultSetIndex() + 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 + case <-r.interrupted: + return nil, errInterruptedStream + default: + part, err := nextPart(r.stream) + if err != nil { + if xerrors.Is(err, io.EOF) { + r.close() + } + return nil, xerrors.WithStackTrace(err) + } + r.lastPart = part + if part.GetResultSetIndex() > nextResultSetIndex { + return nil, xerrors.WithStackTrace(fmt.Errorf( + "result set (index=%d) recieve part (index=%d) for next result set: %w", + nextResultSetIndex, part.GetResultSetIndex(), io.EOF, + )) + } + return part, nil + } + }, r.lastPart), nil + } + part, err := nextPart(r.stream) + 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, error) { + 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..f4f62bb5e --- /dev/null +++ b/internal/query/result_set.go @@ -0,0 +1,68 @@ +package query + +import ( + "context" + "fmt" + "io" + + "github.com/ydb-platform/ydb-go-genproto/protos/Ydb_Query" + + "github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors" + "github.com/ydb-platform/ydb-go-sdk/v3/query" +) + +var _ query.ResultSet = (*resultSet)(nil) + +type resultSet struct { + index int64 + recv func() (*Ydb_Query.ExecuteQueryResponsePart, error) + columns []query.Column + currentPart *Ydb_Query.ExecuteQueryResponsePart + rowIndex int + done chan struct{} +} + +func newResultSet(recv func() (*Ydb_Query.ExecuteQueryResponsePart, error), part *Ydb_Query.ExecuteQueryResponsePart) *resultSet { + return &resultSet{ + index: part.ResultSetIndex, + recv: recv, + currentPart: part, + rowIndex: -1, + columns: newColumns(part.GetResultSet().GetColumns()), + done: make(chan struct{}), + } +} + +func (rs *resultSet) next(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 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(rs.columns, rs.currentPart.GetResultSet().GetRows()[rs.rowIndex]) + } +} + +func (rs *resultSet) NextRow(ctx context.Context) (query.Row, error) { + return rs.next(ctx) +} diff --git a/internal/query/result_set_test.go b/internal/query/result_set_test.go new file mode 100644 index 000000000..4be49016b --- /dev/null +++ b/internal/query/result_set_test.go @@ -0,0 +1,598 @@ +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) + part, 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 + }, part) + require.EqualValues(t, 0, rs.index) + { + _, err := rs.next(ctx) + require.NoError(t, err) + require.EqualValues(t, 0, rs.rowIndex) + } + { + _, err := rs.next(ctx) + require.NoError(t, err) + require.EqualValues(t, 1, rs.rowIndex) + } + { + _, err := rs.next(ctx) + require.NoError(t, err) + require.EqualValues(t, 2, rs.rowIndex) + } + { + _, err := rs.next(ctx) + require.NoError(t, err) + require.EqualValues(t, 0, rs.rowIndex) + } + { + _, err := rs.next(ctx) + require.NoError(t, err) + require.EqualValues(t, 1, rs.rowIndex) + } + { + _, err := rs.next(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) + part, 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 + }, part) + require.EqualValues(t, 0, rs.index) + { + _, err := rs.next(ctx) + require.NoError(t, err) + require.EqualValues(t, 0, rs.rowIndex) + } + cancel() + { + _, err := rs.next(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(&Ydb_Query.ExecuteQueryResponsePart{ + Status: Ydb.StatusIds_OVERLOADED, + ResultSetIndex: 0, + }, nil) + part, err := stream.Recv() + require.NoError(t, err) + rs := newResultSet(func() (*Ydb_Query.ExecuteQueryResponsePart, error) { + part, err := nextPart(stream) + if err != nil { + return nil, xerrors.WithStackTrace(err) + } + if part.ResultSetIndex != 0 { + return nil, xerrors.WithStackTrace(fmt.Errorf( + "critical violation of the logic: wrong result set index: %d != %d", + part.ResultSetIndex, 0, + )) + } + return part, nil + }, part) + require.EqualValues(t, 0, rs.index) + { + _, err := rs.next(ctx) + require.NoError(t, err) + require.EqualValues(t, 0, rs.rowIndex) + } + { + _, err := rs.next(ctx) + require.NoError(t, err) + require.EqualValues(t, 1, rs.rowIndex) + } + { + _, err := rs.next(ctx) + require.NoError(t, err) + require.EqualValues(t, 2, rs.rowIndex) + } + { + _, err := rs.next(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, "")) + part, err := stream.Recv() + require.NoError(t, err) + rs := newResultSet(func() (*Ydb_Query.ExecuteQueryResponsePart, error) { + part, err := nextPart(stream) + if err != nil { + return nil, xerrors.WithStackTrace(err) + } + if part.ResultSetIndex != 0 { + return nil, xerrors.WithStackTrace(fmt.Errorf( + "critical violation of the logic: wrong result set index: %d != %d", + part.ResultSetIndex, 0, + )) + } + return part, nil + }, part) + require.EqualValues(t, 0, rs.index) + { + _, err := rs.next(ctx) + require.NoError(t, err) + require.EqualValues(t, 0, rs.rowIndex) + } + { + _, err := rs.next(ctx) + require.NoError(t, err) + require.EqualValues(t, 1, rs.rowIndex) + } + { + _, err := rs.next(ctx) + require.NoError(t, err) + require.EqualValues(t, 2, rs.rowIndex) + } + { + _, err := rs.next(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) + part, err := stream.Recv() + require.NoError(t, err) + rs := newResultSet(func() (*Ydb_Query.ExecuteQueryResponsePart, error) { + part, err := nextPart(stream) + if err != nil { + return nil, xerrors.WithStackTrace(err) + } + return part, nil + }, part) + require.EqualValues(t, 0, rs.index) + { + _, err := rs.next(ctx) + require.NoError(t, err) + require.EqualValues(t, 0, rs.rowIndex) + } + { + _, err := rs.next(ctx) + require.NoError(t, err) + require.EqualValues(t, 1, rs.rowIndex) + } + { + _, err := rs.next(ctx) + require.NoError(t, err) + require.EqualValues(t, 2, rs.rowIndex) + } + { + _, err := rs.next(ctx) + require.ErrorIs(t, err, errWrongResultSetIndex) + } + { + _, err := rs.next(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..c5867e49b --- /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, cancel) + 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.next(ctx) + require.NoError(t, err) + require.EqualValues(t, 0, rs.rowIndex) + } + { + t.Log("next (row=2)") + _, err := rs.next(ctx) + require.NoError(t, err) + require.EqualValues(t, 1, rs.rowIndex) + } + { + t.Log("next (row=3)") + _, err := rs.next(ctx) + require.NoError(t, err) + require.EqualValues(t, 2, rs.rowIndex) + } + { + t.Log("next (row=4)") + _, err := rs.next(ctx) + require.NoError(t, err) + require.EqualValues(t, 0, rs.rowIndex) + } + { + t.Log("next (row=5)") + _, err := rs.next(ctx) + require.NoError(t, err) + require.EqualValues(t, 1, rs.rowIndex) + } + { + t.Log("next (row=6)") + _, err := rs.next(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.next(ctx) + require.NoError(t, err) + require.EqualValues(t, 0, rs.rowIndex) + } + { + t.Log("next (row=2)") + _, err := rs.next(ctx) + require.NoError(t, err) + require.EqualValues(t, 1, rs.rowIndex) + } + { + t.Log("next (row=3)") + _, err := rs.next(ctx) + require.NoError(t, err) + require.EqualValues(t, 0, rs.rowIndex) + } + { + t.Log("next (row=4)") + _, err := rs.next(ctx) + require.NoError(t, err) + require.EqualValues(t, 1, rs.rowIndex) + } + { + t.Log("next (row=5)") + _, err := rs.next(ctx) + require.NoError(t, err) + require.EqualValues(t, 2, rs.rowIndex) + } + { + t.Log("next (row=6)") + _, err := rs.next(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, cancel) + 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.next(ctx) + require.NoError(t, err) + require.EqualValues(t, 0, rs.rowIndex) + } + { + t.Log("next (row=2)") + _, err := rs.next(ctx) + require.NoError(t, err) + require.EqualValues(t, 1, rs.rowIndex) + } + t.Log("explicit interrupt stream") + r.interrupt() + { + t.Log("next (row=3)") + _, err := rs.next(context.Background()) + require.NoError(t, err) + require.EqualValues(t, 2, rs.rowIndex) + } + { + t.Log("next (row=4)") + _, err := rs.next(context.Background()) + require.ErrorIs(t, err, errInterruptedStream) + } + } + { + t.Log("nextResultSet") + _, err := r.nextResultSet(context.Background()) + require.ErrorIs(t, err, errInterruptedStream) + } + t.Log("check final error") + require.ErrorIs(t, r.Err(), errInterruptedStream) + }, 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, cancel) + 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.next(ctx) + require.NoError(t, err) + require.EqualValues(t, 0, rs.rowIndex) + } + { + t.Log("next (row=2)") + _, err := rs.next(ctx) + require.NoError(t, err) + require.EqualValues(t, 1, rs.rowIndex) + } + { + t.Log("next (row=3)") + _, err := rs.next(ctx) + require.NoError(t, err) + require.EqualValues(t, 2, rs.rowIndex) + } + { + t.Log("next (row=4)") + _, err := rs.next(ctx) + require.NoError(t, err) + require.EqualValues(t, 0, rs.rowIndex) + } + { + t.Log("next (row=5)") + _, err := rs.next(ctx) + require.NoError(t, err) + require.EqualValues(t, 1, rs.rowIndex) + } + { + t.Log("next (row=6)") + _, err := rs.next(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..7f3e347b8 --- /dev/null +++ b/internal/query/row.go @@ -0,0 +1,24 @@ +package query + +import ( + "github.com/ydb-platform/ydb-go-genproto/protos/Ydb" + + "github.com/ydb-platform/ydb-go-sdk/v3/query" +) + +var _ query.Row = (*row)(nil) + +type row struct { + scannerIndexed + scannerNamed + scannerStruct +} + +func newRow(columns []query.Column, v *Ydb.Value) (*row, error) { + data := newScannerData(columns, v.GetItems()) + return &row{ + newScannerIndexed(data), + newScannerNamed(data), + newScannerStruct(data), + }, nil +} diff --git a/internal/query/scanner_data.go b/internal/query/scanner_data.go new file mode 100644 index 000000000..8e5f1fbef --- /dev/null +++ b/internal/query/scanner_data.go @@ -0,0 +1,19 @@ +package query + +import ( + "github.com/ydb-platform/ydb-go-genproto/protos/Ydb" + + "github.com/ydb-platform/ydb-go-sdk/v3/query" +) + +type scannerData struct { + columns []query.Column + values []*Ydb.Value +} + +func newScannerData(columns []query.Column, values []*Ydb.Value) *scannerData { + return &scannerData{ + columns: columns, + values: values, + } +} diff --git a/internal/query/scanner_indexed.go b/internal/query/scanner_indexed.go new file mode 100644 index 000000000..d4364b213 --- /dev/null +++ b/internal/query/scanner_indexed.go @@ -0,0 +1,26 @@ +package query + +import ( + "github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors" + "github.com/ydb-platform/ydb-go-sdk/v3/query" +) + +var _ query.IndexedScanner = scannerIndexed{} + +type scannerIndexed struct { + data *scannerData +} + +func newScannerIndexed(data *scannerData) scannerIndexed { + return scannerIndexed{ + data: data, + } +} + +func (s scannerIndexed) Scan(dst ...interface{}) error { + if len(dst) != len(s.data.columns) { + return xerrors.WithStackTrace(errWrongArgumentsCount) + } + + return xerrors.WithStackTrace(ErrNotImplemented) +} diff --git a/internal/query/scanner_named.go b/internal/query/scanner_named.go new file mode 100644 index 000000000..d5df86a8f --- /dev/null +++ b/internal/query/scanner_named.go @@ -0,0 +1,22 @@ +package query + +import ( + "github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors" + "github.com/ydb-platform/ydb-go-sdk/v3/query" +) + +var _ query.NamedScanner = scannerNamed{} + +type scannerNamed struct { + data *scannerData +} + +func newScannerNamed(data *scannerData) scannerNamed { + return scannerNamed{ + data: data, + } +} + +func (s scannerNamed) ScanNamed(dst ...query.NamedDestination) error { + return xerrors.WithStackTrace(ErrNotImplemented) +} diff --git a/internal/query/scanner_struct.go b/internal/query/scanner_struct.go new file mode 100644 index 000000000..ec0b08d9d --- /dev/null +++ b/internal/query/scanner_struct.go @@ -0,0 +1,22 @@ +package query + +import ( + "github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors" + "github.com/ydb-platform/ydb-go-sdk/v3/query" +) + +var _ query.StructScanner = scannerStruct{} + +type scannerStruct struct { + data *scannerData +} + +func newScannerStruct(data *scannerData) scannerStruct { + return scannerStruct{ + data: data, + } +} + +func (s scannerStruct) ScanStruct(dst interface{}) error { + return xerrors.WithStackTrace(ErrNotImplemented) +} diff --git a/internal/query/session.go b/internal/query/session.go new file mode 100644 index 000000000..02bdbbd6b --- /dev/null +++ b/internal/query/session.go @@ -0,0 +1,77 @@ +package query + +import ( + "context" + "sync/atomic" + + "github.com/ydb-platform/ydb-go-genproto/Ydb_Query_V1" + "github.com/ydb-platform/ydb-go-genproto/protos/Ydb" + "github.com/ydb-platform/ydb-go-genproto/protos/Ydb_Query" + + "github.com/ydb-platform/ydb-go-sdk/v3/internal/allocator" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors" + "github.com/ydb-platform/ydb-go-sdk/v3/query" +) + +var _ query.Session = (*Session)(nil) + +type Session struct { + id string + nodeID int64 + queryClient Ydb_Query_V1.QueryServiceClient + status query.SessionStatus + close func() +} + +func (s *Session) Close(ctx context.Context) error { + s.close() + return nil +} + +func begin(ctx context.Context, client Ydb_Query_V1.QueryServiceClient, sessionID string, txSettings query.TransactionSettings) (*transaction, error) { + a := allocator.New() + defer a.Free() + response, err := client.BeginTransaction(ctx, + &Ydb_Query.BeginTransactionRequest{ + SessionId: sessionID, + TxSettings: txSettings.ToYDB(a), + }, + ) + if err != nil { + return nil, xerrors.WithStackTrace(xerrors.Transport(err)) + } + if response.GetStatus() != Ydb.StatusIds_SUCCESS { + return nil, xerrors.WithStackTrace(xerrors.FromOperation(response)) + } + return &transaction{ + id: response.GetTxMeta().GetId(), + }, nil +} + +func (s *Session) Begin(ctx context.Context, txSettings query.TransactionSettings) (query.Transaction, error) { + tx, err := begin(ctx, s.queryClient, s.id, 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() query.SessionStatus { + status := query.SessionStatus(atomic.LoadUint32((*uint32)(&s.status))) + return status +} + +func (s *Session) Execute( + ctx context.Context, q string, opts ...query.ExecuteOption, +) (query.Transaction, query.Result, error) { + return execute(ctx, s, s.queryClient, q, query.ExecuteSettings(opts...)) +} diff --git a/internal/query/session_test.go b/internal/query/session_test.go new file mode 100644 index 000000000..db6b41fc7 --- /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, "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, "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(&Ydb_Query.BeginTransactionResponse{ + Status: Ydb.StatusIds_UNAVAILABLE, + }, nil) + t.Log("begin") + _, err := begin(ctx, service, "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..b971261f2 --- /dev/null +++ b/internal/query/transaction.go @@ -0,0 +1,78 @@ +package query + +import ( + "context" + + "github.com/ydb-platform/ydb-go-genproto/Ydb_Query_V1" + "github.com/ydb-platform/ydb-go-genproto/protos/Ydb" + "github.com/ydb-platform/ydb-go-genproto/protos/Ydb_Query" + + "github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors" + "github.com/ydb-platform/ydb-go-sdk/v3/query" +) + +var _ query.Transaction = (*transaction)(nil) + +type transaction struct { + id string + s *Session +} + +func (tx transaction) ID() string { + return tx.id +} + +func fromTxOptions(txID string, txOpts ...query.TxExecuteOption) executeSettings { + opts := make([]query.ExecuteOption, 0, len(txOpts)+1) + for _, opt := range txOpts { + if executeOpt, has := opt.(query.ExecuteOption); has { + opts = append(opts, executeOpt) + } + } + opts = append(opts, query.WithTxControl(query.TxControl(query.WithTxID(txID)))) + return query.ExecuteSettings(opts...) +} + +func (tx transaction) Execute(ctx context.Context, q string, opts ...query.TxExecuteOption) (r query.Result, err error) { + _, res, err := execute(ctx, tx.s, tx.s.queryClient, q, fromTxOptions(tx.id, opts...)) + if err != nil { + return nil, xerrors.WithStackTrace(err) + } + return res, nil +} + +func commitTx(ctx context.Context, client Ydb_Query_V1.QueryServiceClient, sessionID string, txID string) error { + response, err := client.CommitTransaction(ctx, &Ydb_Query.CommitTransactionRequest{ + SessionId: sessionID, + TxId: txID, + }) + if err != nil { + return xerrors.WithStackTrace(xerrors.Transport(err)) + } + if response.GetStatus() != Ydb.StatusIds_SUCCESS { + return xerrors.WithStackTrace(xerrors.FromOperation(response)) + } + return nil +} + +func (tx transaction) CommitTx(ctx context.Context) (err error) { + return commitTx(ctx, tx.s.queryClient, tx.s.id, tx.id) +} + +func rollback(ctx context.Context, client Ydb_Query_V1.QueryServiceClient, sessionID string, txID string) error { + response, err := client.RollbackTransaction(ctx, &Ydb_Query.RollbackTransactionRequest{ + SessionId: sessionID, + TxId: txID, + }) + if err != nil { + return xerrors.WithStackTrace(xerrors.Transport(err)) + } + if response.GetStatus() != Ydb.StatusIds_SUCCESS { + return xerrors.WithStackTrace(xerrors.FromOperation(response)) + } + return nil +} + +func (tx transaction) Rollback(ctx context.Context) (err error) { + return rollback(ctx, tx.s.queryClient, 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..09c77021d --- /dev/null +++ b/internal/query/transaction_test.go @@ -0,0 +1,213 @@ +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/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(&Ydb_Query.CommitTransactionResponse{ + Status: Ydb.StatusIds_UNAVAILABLE, + }, nil) + 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(&Ydb_Query.RollbackTransactionResponse{ + Status: Ydb.StatusIds_UNAVAILABLE, + }, nil) + 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 query.ExecMode + statsMode query.StatsMode + txControl *query.TransactionControl + syntax query.Syntax + params *query.Parameters + callOptions []grpc.CallOption +} + +func (s testExecuteSettings) ExecMode() query.ExecMode { + return s.execMode +} + +func (s testExecuteSettings) StatsMode() query.StatsMode { + return s.statsMode +} + +func (s testExecuteSettings) TxControl() *query.TransactionControl { + return s.txControl +} + +func (s testExecuteSettings) Syntax() query.Syntax { + return s.syntax +} + +func (s testExecuteSettings) Params() *query.Parameters { + return s.params +} + +func (s testExecuteSettings) CallOptions() []grpc.CallOption { + return s.callOptions +} + +var _ executeSettings = testExecuteSettings{} + +func TestFromTxOptions(t *testing.T) { + for _, tt := range []struct { + name string + txID string + txOpts []query.TxExecuteOption + settings executeSettings + }{ + { + name: "WithTxID", + txID: "test", + txOpts: nil, + settings: testExecuteSettings{ + execMode: query.ExecModeExecute, + statsMode: query.StatsModeNone, + txControl: query.TxControl(query.WithTxID("test")), + syntax: Ydb_Query.Syntax_SYNTAX_YQL_V1, + }, + }, + { + name: "WithStats", + txOpts: []query.TxExecuteOption{ + query.WithStatsMode(query.StatsModeFull), + }, + settings: testExecuteSettings{ + execMode: query.ExecModeExecute, + statsMode: query.StatsModeFull, + txControl: query.TxControl(query.WithTxID("")), + syntax: Ydb_Query.Syntax_SYNTAX_YQL_V1, + }, + }, + { + name: "WithExecMode", + txOpts: []query.TxExecuteOption{ + query.WithExecMode(query.ExecModeExplain), + }, + settings: testExecuteSettings{ + execMode: query.ExecModeExplain, + statsMode: query.StatsModeNone, + txControl: query.TxControl(query.WithTxID("")), + syntax: Ydb_Query.Syntax_SYNTAX_YQL_V1, + }, + }, + { + name: "WithGrpcOptions", + txOpts: []query.TxExecuteOption{ + query.WithCallOptions(grpc.CallContentSubtype("test")), + }, + settings: testExecuteSettings{ + execMode: query.ExecModeExecute, + statsMode: query.StatsModeNone, + txControl: query.TxControl(query.WithTxID("")), + syntax: Ydb_Query.Syntax_SYNTAX_YQL_V1, + callOptions: []grpc.CallOption{ + grpc.CallContentSubtype("test"), + }, + }, + }, + { + name: "WithParams", + txOpts: []query.TxExecuteOption{ + query.WithParameters( + query.Param("$a", query.TextValue("A")), + ), + }, + settings: testExecuteSettings{ + execMode: query.ExecModeExecute, + statsMode: query.StatsModeNone, + txControl: query.TxControl(query.WithTxID("")), + syntax: Ydb_Query.Syntax_SYNTAX_YQL_V1, + params: query.Params( + query.Param("$a", query.TextValue("A")), + ), + }, + }, + } { + t.Run(tt.name, func(t *testing.T) { + a := allocator.New() + settings := fromTxOptions(tt.txID, tt.txOpts...) + 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/scripting/client.go b/internal/scripting/client.go index bf57a3edd..262490338 100644 --- a/internal/scripting/client.go +++ b/internal/scripting/client.go @@ -15,7 +15,7 @@ import ( "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" + types2 "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" @@ -184,7 +184,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] = types2.TypeFromYDB(v) } return e, nil diff --git a/internal/session/status.go b/internal/session/status.go new file mode 100644 index 000000000..7807fdbd5 --- /dev/null +++ b/internal/session/status.go @@ -0,0 +1,11 @@ +package session + +type Status = string + +const ( + StatusUnknown = Status("unknown") + StatusReady = Status("ready") + StatusBusy = Status("busy") + StatusClosing = Status("closing") + StatusClosed = Status("closed") +) diff --git a/internal/table/scanner/result_test.go b/internal/table/scanner/result_test.go index e3f71b62e..01a95152a 100644 --- a/internal/table/scanner/result_test.go +++ b/internal/table/scanner/result_test.go @@ -13,6 +13,7 @@ import ( "github.com/ydb-platform/ydb-go-genproto/protos/Ydb_TableStats" "github.com/ydb-platform/ydb-go-sdk/v3/internal/allocator" + types2 "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" @@ -151,7 +152,7 @@ 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: types2.TypeToYDB(c.Type, a), }) } } @@ -178,9 +179,9 @@ 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 := types2.TypeFromYDB(tv.Type) + exp := types2.TypeFromYDB(r.Columns[j].Type) + if !types2.TypesEqual(act, exp) { panic(fmt.Sprintf( "unexpected types for #%d column: %s; want %s", j, act, exp, diff --git a/internal/table/scanner/scan_raw.go b/internal/table/scanner/scan_raw.go index 1a0c8be98..b5c932af5 100644 --- a/internal/table/scanner/scan_raw.go +++ b/internal/table/scanner/scan_raw.go @@ -10,6 +10,7 @@ import ( "github.com/ydb-platform/ydb-go-genproto/protos/Ydb" + types2 "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" @@ -583,7 +584,7 @@ func (s *rawConverter) IsDecimal() bool { } func isEqualDecimal(d *Ydb.DecimalType, t types.Type) bool { - w := t.(*value.DecimalType) + w := t.(*types2.DecimalType) return d.Precision == w.Precision && d.Scale == w.Scale } @@ -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 := types2.TypeFromYDB(c.t) + if !types2.TypesEqual(act, t) { _ = s.errorf( 1, "unexpected types at %q %s: %s; want %s", diff --git a/internal/table/scanner/scanner.go b/internal/table/scanner/scanner.go index 415ebd669..02ba7912c 100644 --- a/internal/table/scanner/scanner.go +++ b/internal/table/scanner/scanner.go @@ -11,6 +11,7 @@ import ( "github.com/ydb-platform/ydb-go-genproto/protos/Ydb" + types2 "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" @@ -55,7 +56,7 @@ func (s *scanner) Columns(it func(options.Column)) { for _, m := range s.set.Columns { it(options.Column{ Name: m.Name, - Type: value.TypeFromYDB(m.Type), + Type: types2.TypeFromYDB(m.Type), }) } } @@ -287,7 +288,7 @@ func (s *scanner) getType() types.Type { return nil } - return value.TypeFromYDB(x.t) + return types2.TypeFromYDB(x.t) } func (s *scanner) hasItems() bool { @@ -383,74 +384,74 @@ func (s *scanner) any() interface{} { x = s.stack.current() } - t := value.TypeFromYDB(x.t) - p, primitive := t.(value.PrimitiveType) + t := types2.TypeFromYDB(x.t) + p, primitive := t.(types2.PrimitiveType) if !primitive { return s.value() } switch p { - case value.TypeBool: + case types2.TypeBool: return s.bool() - case value.TypeInt8: + case types2.TypeInt8: return s.int8() - case value.TypeUint8: + case types2.TypeUint8: return s.uint8() - case value.TypeInt16: + case types2.TypeInt16: return s.int16() - case value.TypeUint16: + case types2.TypeUint16: return s.uint16() - case value.TypeInt32: + case types2.TypeInt32: return s.int32() - case value.TypeFloat: + case types2.TypeFloat: return s.float() - case value.TypeDouble: + case types2.TypeDouble: return s.double() - case value.TypeBytes: + case types2.TypeBytes: return s.bytes() - case value.TypeUUID: + case types2.TypeUUID: return s.uint128() - case value.TypeUint32: + case types2.TypeUint32: return s.uint32() - case value.TypeDate: + case types2.TypeDate: return value.DateToTime(s.uint32()) - case value.TypeDatetime: + case types2.TypeDatetime: return value.DatetimeToTime(s.uint32()) - case value.TypeUint64: + case types2.TypeUint64: return s.uint64() - case value.TypeTimestamp: + case types2.TypeTimestamp: return value.TimestampToTime(s.uint64()) - case value.TypeInt64: + case types2.TypeInt64: return s.int64() - case value.TypeInterval: + case types2.TypeInterval: return value.IntervalToDuration(s.int64()) - case value.TypeTzDate: + case types2.TypeTzDate: src, err := value.TzDateToTime(s.text()) if err != nil { _ = s.errorf(0, "scanner.any(): %w", err) } return src - case value.TypeTzDatetime: + case types2.TypeTzDatetime: src, err := value.TzDatetimeToTime(s.text()) if err != nil { _ = s.errorf(0, "scanner.any(): %w", err) } return src - case value.TypeTzTimestamp: + case types2.TypeTzTimestamp: src, err := value.TzTimestampToTime(s.text()) if err != nil { _ = s.errorf(0, "scanner.any(): %w", err) } return src - case value.TypeText, value.TypeDyNumber: + case types2.TypeText, types2.TypeDyNumber: return s.text() case - value.TypeYSON, - value.TypeJSON, - value.TypeJSONDocument: + types2.TypeYSON, + types2.TypeJSON, + types2.TypeJSONDocument: return xstring.ToBytes(s.text()) default: _ = s.errorf(0, "unknown primitive types") diff --git a/internal/table/session.go b/internal/table/session.go index 3b40a3fe1..a9d4919af 100644 --- a/internal/table/session.go +++ b/internal/table/session.go @@ -25,6 +25,7 @@ import ( "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" + types2 "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" @@ -344,7 +345,7 @@ func (s *session) DescribeTable( for i, c := range result.Columns { cs[i] = options.Column{ Name: c.GetName(), - Type: value.TypeFromYDB(c.GetType()), + Type: types2.TypeFromYDB(c.GetType()), Family: c.GetFamily(), } } @@ -1083,9 +1084,7 @@ func (s *session) ReadRows( if response.GetStatus() != Ydb.StatusIds_SUCCESS { return nil, xerrors.WithStackTrace( - xerrors.Operation( - xerrors.FromOperation(response), - ), + xerrors.FromOperation(response), ) } diff --git a/internal/table/session_test.go b/internal/table/session_test.go index cd581a9fd..87642e36b 100644 --- a/internal/table/session_test.go +++ b/internal/table/session_test.go @@ -22,6 +22,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/table/config" + types2 "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" @@ -182,7 +183,7 @@ func TestSessionDescribeTable(t *testing.T) { Columns: []*Ydb_Table.ColumnMeta{ { Name: expect.Columns[0].Name, - Type: value.TypeToYDB(expect.Columns[0].Type, a), + Type: types2.TypeToYDB(expect.Columns[0].Type, a), Family: "testFamily", }, }, diff --git a/internal/value/type.go b/internal/types/type.go similarity index 67% rename from internal/value/type.go rename to internal/types/type.go index dd2cc3b07..e02bb74c4 100644 --- a/internal/value/type.go +++ b/internal/types/type.go @@ -1,4 +1,4 @@ -package value +package types import ( "fmt" @@ -13,12 +13,12 @@ type Type interface { Yql() string String() string - toYDB(a *allocator.Allocator) *Ydb.Type + ToYDB(a *allocator.Allocator) *Ydb.Type equalsTo(rhs Type) bool } func TypeToYDB(t Type, a *allocator.Allocator) *Ydb.Type { - return t.toYDB(a) + return t.ToYDB(a) } func TypeFromYDB(x *Ydb.Type) Type { @@ -170,7 +170,7 @@ func (v *DecimalType) equalsTo(rhs Type) bool { return ok && *v == *vv } -func (v *DecimalType) toYDB(a *allocator.Allocator) *Ydb.Type { +func (v *DecimalType) ToYDB(a *allocator.Allocator) *Ydb.Type { decimal := a.Decimal() decimal.Scale = v.Scale @@ -192,81 +192,89 @@ func Decimal(precision, scale uint32) *DecimalType { } } -type dictType struct { +type DictType struct { keyType Type valueType Type } -func (v *dictType) String() string { - return v.Yql() +func (t *DictType) KeyType() Type { + return t.keyType +} + +func (t *DictType) ValueType() Type { + return t.valueType +} + +func (t *DictType) String() string { + return t.Yql() } -func (v *dictType) Yql() string { +func (t *DictType) Yql() string { buffer := xstring.Buffer() defer buffer.Free() buffer.WriteString("Dict<") - buffer.WriteString(v.keyType.Yql()) + buffer.WriteString(t.keyType.Yql()) buffer.WriteByte(',') - buffer.WriteString(v.valueType.Yql()) + buffer.WriteString(t.valueType.Yql()) buffer.WriteByte('>') return buffer.String() } -func (v *dictType) equalsTo(rhs Type) bool { - vv, ok := rhs.(*dictType) +func (t *DictType) equalsTo(rhs Type) bool { + vv, ok := rhs.(*DictType) if !ok { return false } - if !v.keyType.equalsTo(vv.keyType) { + if !t.keyType.equalsTo(vv.keyType) { return false } - if !v.valueType.equalsTo(vv.valueType) { + if !t.valueType.equalsTo(vv.valueType) { return false } return true } -func (v *dictType) toYDB(a *allocator.Allocator) *Ydb.Type { - t := a.Type() +func (t *DictType) ToYDB(a *allocator.Allocator) *Ydb.Type { + tt := a.Type() typeDict := a.TypeDict() typeDict.DictType = a.Dict() - typeDict.DictType.Key = v.keyType.toYDB(a) - typeDict.DictType.Payload = v.valueType.toYDB(a) + typeDict.DictType.Key = t.keyType.ToYDB(a) + typeDict.DictType.Payload = t.valueType.ToYDB(a) - t.Type = typeDict + tt.Type = typeDict - return t + return tt } -func Dict(key, value Type) (v *dictType) { - return &dictType{ +func Dict(key, value Type) (v *DictType) { + return &DictType{ keyType: key, valueType: value, } } -type emptyListType struct{} +type EmptyListType struct{} -func (v emptyListType) Yql() string { +func (v EmptyListType) Yql() string { return "EmptyList" } -func (v emptyListType) String() string { +func (v EmptyListType) String() string { return v.Yql() } -func (emptyListType) equalsTo(rhs Type) bool { - _, ok := rhs.(emptyListType) +func (EmptyListType) equalsTo(rhs Type) bool { + _, ok := rhs.(EmptyListType) return ok } -func (emptyListType) toYDB(a *allocator.Allocator) *Ydb.Type { +func (EmptyListType) ToYDB(a *allocator.Allocator) *Ydb.Type { t := a.Type() t.Type = a.TypeEmptyList() @@ -274,27 +282,27 @@ func (emptyListType) toYDB(a *allocator.Allocator) *Ydb.Type { return t } -func EmptyList() emptyListType { - return emptyListType{} +func EmptyList() EmptyListType { + return EmptyListType{} } -type emptyDictType struct{} +type EmptyDictType struct{} -func (v emptyDictType) String() string { +func (v EmptyDictType) String() string { return v.Yql() } -func (v emptyDictType) Yql() string { +func (v EmptyDictType) Yql() string { return "EmptyDict" } -func (emptyDictType) equalsTo(rhs Type) bool { - _, ok := rhs.(emptyDictType) +func (EmptyDictType) equalsTo(rhs Type) bool { + _, ok := rhs.(EmptyDictType) return ok } -func (emptyDictType) toYDB(a *allocator.Allocator) *Ydb.Type { +func (EmptyDictType) ToYDB(a *allocator.Allocator) *Ydb.Type { t := a.Type() t.Type = a.TypeEmptyDict() @@ -302,118 +310,126 @@ func (emptyDictType) toYDB(a *allocator.Allocator) *Ydb.Type { return t } -func EmptySet() emptyDictType { - return emptyDictType{} +func EmptySet() EmptyDictType { + return EmptyDictType{} } -func EmptyDict() emptyDictType { - return emptyDictType{} +func EmptyDict() EmptyDictType { + return EmptyDictType{} } -type listType struct { +type ListType struct { itemType Type } -func (v *listType) String() string { - return v.Yql() +func (t *ListType) ItemType() Type { + return t.itemType +} + +func (t *ListType) String() string { + return t.Yql() } -func (v *listType) Yql() string { - return "List<" + v.itemType.Yql() + ">" +func (t *ListType) Yql() string { + return "List<" + t.itemType.Yql() + ">" } -func (v *listType) equalsTo(rhs Type) bool { - vv, ok := rhs.(*listType) +func (t *ListType) equalsTo(rhs Type) bool { + vv, ok := rhs.(*ListType) if !ok { return false } - return v.itemType.equalsTo(vv.itemType) + return t.itemType.equalsTo(vv.itemType) } -func (v *listType) toYDB(a *allocator.Allocator) *Ydb.Type { - t := a.Type() +func (t *ListType) ToYDB(a *allocator.Allocator) *Ydb.Type { + tt := a.Type() list := a.List() - list.Item = v.itemType.toYDB(a) + list.Item = t.itemType.ToYDB(a) typeList := a.TypeList() typeList.ListType = list - t.Type = typeList + tt.Type = typeList - return t + return tt } -func List(t Type) *listType { - return &listType{ +func List(t Type) *ListType { + return &ListType{ itemType: t, } } -type setType struct { +type SetType struct { itemType Type } -func (v *setType) String() string { - return v.Yql() +func (t *SetType) ItemType() Type { + return t.itemType +} + +func (t *SetType) String() string { + return t.Yql() } -func (v *setType) Yql() string { - return "Set<" + v.itemType.Yql() + ">" +func (t *SetType) Yql() string { + return "Set<" + t.itemType.Yql() + ">" } -func (v *setType) equalsTo(rhs Type) bool { - vv, ok := rhs.(*setType) +func (t *SetType) equalsTo(rhs Type) bool { + vv, ok := rhs.(*SetType) if !ok { return false } - return v.itemType.equalsTo(vv.itemType) + return t.itemType.equalsTo(vv.itemType) } -func (v *setType) toYDB(a *allocator.Allocator) *Ydb.Type { - t := a.Type() +func (t *SetType) ToYDB(a *allocator.Allocator) *Ydb.Type { + tt := a.Type() typeDict := a.TypeDict() typeDict.DictType = a.Dict() - typeDict.DictType.Key = v.itemType.toYDB(a) + typeDict.DictType.Key = t.itemType.ToYDB(a) typeDict.DictType.Payload = _voidType - t.Type = typeDict + tt.Type = typeDict - return t + return tt } -func Set(t Type) *setType { - return &setType{ +func Set(t Type) *SetType { + return &SetType{ itemType: t, } } -type optionalType struct { +type OptionalType struct { innerType Type } -func (v optionalType) IsOptional() {} +func (v OptionalType) IsOptional() {} -func (v optionalType) InnerType() Type { +func (v OptionalType) InnerType() Type { return v.innerType } -func (v optionalType) String() string { +func (v OptionalType) String() string { return v.Yql() } -func (v optionalType) Yql() string { +func (v OptionalType) Yql() string { return "Optional<" + v.innerType.Yql() + ">" } -func (v optionalType) equalsTo(rhs Type) bool { - vv, ok := rhs.(optionalType) +func (v OptionalType) equalsTo(rhs Type) bool { + vv, ok := rhs.(OptionalType) if !ok { return false } @@ -421,22 +437,22 @@ func (v optionalType) equalsTo(rhs Type) bool { return v.innerType.equalsTo(vv.innerType) } -func (v optionalType) toYDB(a *allocator.Allocator) *Ydb.Type { +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) + typeOptional.OptionalType.Item = v.innerType.ToYDB(a) t.Type = typeOptional return t } -func Optional(t Type) optionalType { - return optionalType{ +func Optional(t Type) OptionalType { + return OptionalType{ innerType: t, } } @@ -546,7 +562,7 @@ func (v PrimitiveType) equalsTo(rhs Type) bool { return v == vv } -func (v PrimitiveType) toYDB(*allocator.Allocator) *Ydb.Type { +func (v PrimitiveType) ToYDB(*allocator.Allocator) *Ydb.Type { return primitive[v] } @@ -560,40 +576,48 @@ type ( } ) -func (v *StructType) String() string { - return v.Yql() +func (t *StructType) Fields() []StructField { + return t.fields } -func (v *StructType) Yql() string { +func (t *StructType) Field(idx int) StructField { + return t.fields[idx] +} + +func (t *StructType) String() string { + return t.Yql() +} + +func (t *StructType) Yql() string { buffer := xstring.Buffer() defer buffer.Free() buffer.WriteString("Struct<") - for i := range v.fields { + for i := range t.fields { if i > 0 { buffer.WriteByte(',') } - buffer.WriteString("'" + v.fields[i].Name + "'") + buffer.WriteString("'" + t.fields[i].Name + "'") buffer.WriteByte(':') - buffer.WriteString(v.fields[i].T.Yql()) + buffer.WriteString(t.fields[i].T.Yql()) } buffer.WriteByte('>') return buffer.String() } -func (v *StructType) equalsTo(rhs Type) bool { +func (t *StructType) equalsTo(rhs Type) bool { vv, ok := rhs.(*StructType) if !ok { return false } - if len(v.fields) != len(vv.fields) { + if len(t.fields) != len(vv.fields) { return false } - for i := range v.fields { - if v.fields[i].Name != vv.fields[i].Name { + for i := range t.fields { + if t.fields[i].Name != vv.fields[i].Name { return false } - if !v.fields[i].T.equalsTo(vv.fields[i].T) { + if !t.fields[i].T.equalsTo(vv.fields[i].T) { return false } } @@ -601,26 +625,26 @@ func (v *StructType) equalsTo(rhs Type) bool { return true } -func (v *StructType) toYDB(a *allocator.Allocator) *Ydb.Type { - t := a.Type() +func (t *StructType) ToYDB(a *allocator.Allocator) *Ydb.Type { + tt := a.Type() typeStruct := a.TypeStruct() typeStruct.StructType = a.Struct() - for i := range v.fields { + for i := range t.fields { structMember := a.StructMember() - structMember.Name = v.fields[i].Name - structMember.Type = v.fields[i].T.toYDB(a) + structMember.Name = t.fields[i].Name + structMember.Type = t.fields[i].T.ToYDB(a) typeStruct.StructType.Members = append( typeStruct.StructType.Members, structMember, ) } - t.Type = typeStruct + tt.Type = typeStruct - return t + return tt } func Struct(fields ...StructField) (v *StructType) { @@ -645,15 +669,23 @@ type TupleType struct { items []Type } -func (v *TupleType) String() string { - return v.Yql() +func (t *TupleType) Item(idx int) Type { + return t.items[idx] } -func (v *TupleType) Yql() string { +func (t *TupleType) Items() []Type { + return t.items +} + +func (t *TupleType) String() string { + return t.Yql() +} + +func (t *TupleType) Yql() string { buffer := xstring.Buffer() defer buffer.Free() buffer.WriteString("Tuple<") - for i, t := range v.items { + for i, t := range t.items { if i > 0 { buffer.WriteByte(',') } @@ -664,16 +696,16 @@ func (v *TupleType) Yql() string { return buffer.String() } -func (v *TupleType) equalsTo(rhs Type) bool { +func (t *TupleType) equalsTo(rhs Type) bool { vv, ok := rhs.(*TupleType) if !ok { return false } - if len(v.items) != len(vv.items) { + if len(t.items) != len(vv.items) { return false } - for i := range v.items { - if !v.items[i].equalsTo(vv.items[i]) { + for i := range t.items { + if !t.items[i].equalsTo(vv.items[i]) { return false } } @@ -681,24 +713,24 @@ func (v *TupleType) equalsTo(rhs Type) bool { return true } -func (v *TupleType) toYDB(a *allocator.Allocator) *Ydb.Type { +func (t *TupleType) ToYDB(a *allocator.Allocator) *Ydb.Type { var items []Type - if v != nil { - items = v.items + if t != nil { + items = t.items } - t := a.Type() + tt := a.Type() typeTuple := a.TypeTuple() typeTuple.TupleType = a.Tuple() for _, vv := range items { - typeTuple.TupleType.Elements = append(typeTuple.TupleType.Elements, vv.toYDB(a)) + typeTuple.TupleType.Elements = append(typeTuple.TupleType.Elements, vv.ToYDB(a)) } - t.Type = typeTuple + tt.Type = typeTuple - return t + return tt } func Tuple(items ...Type) (v *TupleType) { @@ -707,11 +739,11 @@ func Tuple(items ...Type) (v *TupleType) { } } -type variantStructType struct { +type VariantStructType struct { *StructType } -func (v *variantStructType) Yql() string { +func (v *VariantStructType) Yql() string { buffer := xstring.Buffer() defer buffer.Free() buffer.WriteString("Variant<") @@ -728,9 +760,9 @@ func (v *variantStructType) Yql() string { return buffer.String() } -func (v *variantStructType) equalsTo(rhs Type) bool { +func (v *VariantStructType) equalsTo(rhs Type) bool { switch t := rhs.(type) { - case *variantStructType: + case *VariantStructType: return v.StructType.equalsTo(t.StructType) case *StructType: return v.StructType.equalsTo(t) @@ -739,7 +771,7 @@ func (v *variantStructType) equalsTo(rhs Type) bool { } } -func (v *variantStructType) toYDB(a *allocator.Allocator) *Ydb.Type { +func (v *VariantStructType) ToYDB(a *allocator.Allocator) *Ydb.Type { t := a.Type() typeVariant := a.TypeVariant() @@ -747,7 +779,7 @@ func (v *variantStructType) toYDB(a *allocator.Allocator) *Ydb.Type { typeVariant.VariantType = a.Variant() structItems := a.VariantStructItems() - structItems.StructItems = v.StructType.toYDB(a).Type.(*Ydb.Type_StructType).StructType + structItems.StructItems = v.StructType.ToYDB(a).Type.(*Ydb.Type_StructType).StructType typeVariant.VariantType.Type = structItems @@ -756,17 +788,17 @@ func (v *variantStructType) toYDB(a *allocator.Allocator) *Ydb.Type { return t } -func VariantStruct(fields ...StructField) *variantStructType { - return &variantStructType{ +func VariantStruct(fields ...StructField) *VariantStructType { + return &VariantStructType{ StructType: Struct(fields...), } } -type variantTupleType struct { +type VariantTupleType struct { *TupleType } -func (v *variantTupleType) Yql() string { +func (v *VariantTupleType) Yql() string { buffer := xstring.Buffer() defer buffer.Free() buffer.WriteString("Variant<") @@ -781,9 +813,9 @@ func (v *variantTupleType) Yql() string { return buffer.String() } -func (v *variantTupleType) equalsTo(rhs Type) bool { +func (v *VariantTupleType) equalsTo(rhs Type) bool { switch t := rhs.(type) { - case *variantTupleType: + case *VariantTupleType: return v.TupleType.equalsTo(t.TupleType) case *TupleType: return v.TupleType.equalsTo(t) @@ -792,7 +824,7 @@ func (v *variantTupleType) equalsTo(rhs Type) bool { } } -func (v *variantTupleType) toYDB(a *allocator.Allocator) *Ydb.Type { +func (v *VariantTupleType) ToYDB(a *allocator.Allocator) *Ydb.Type { t := a.Type() typeVariant := a.TypeVariant() @@ -800,7 +832,7 @@ func (v *variantTupleType) toYDB(a *allocator.Allocator) *Ydb.Type { typeVariant.VariantType = a.Variant() tupleItems := a.VariantTupleItems() - tupleItems.TupleItems = v.TupleType.toYDB(a).Type.(*Ydb.Type_TupleType).TupleType + tupleItems.TupleItems = v.TupleType.ToYDB(a).Type.(*Ydb.Type_TupleType).TupleType typeVariant.VariantType.Type = tupleItems @@ -809,19 +841,19 @@ func (v *variantTupleType) toYDB(a *allocator.Allocator) *Ydb.Type { return t } -func VariantTuple(items ...Type) *variantTupleType { - return &variantTupleType{ +func VariantTuple(items ...Type) *VariantTupleType { + return &VariantTupleType{ TupleType: Tuple(items...), } } -type voidType struct{} +type VoidType struct{} -func (v voidType) String() string { +func (v VoidType) String() string { return v.Yql() } -func (v voidType) Yql() string { +func (v VoidType) Yql() string { return "Void" } @@ -829,27 +861,27 @@ var _voidType = &Ydb.Type{ Type: &Ydb.Type_VoidType{}, } -func (v voidType) equalsTo(rhs Type) bool { - _, ok := rhs.(voidType) +func (v VoidType) equalsTo(rhs Type) bool { + _, ok := rhs.(VoidType) return ok } -func (voidType) toYDB(*allocator.Allocator) *Ydb.Type { +func (VoidType) ToYDB(*allocator.Allocator) *Ydb.Type { return _voidType } -func Void() voidType { - return voidType{} +func Void() VoidType { + return VoidType{} } -type nullType struct{} +type NullType struct{} -func (v nullType) String() string { +func (v NullType) String() string { return v.Yql() } -func (v nullType) Yql() string { +func (v NullType) Yql() string { return "Null" } @@ -857,16 +889,16 @@ var _nullType = &Ydb.Type{ Type: &Ydb.Type_NullType{}, } -func (v nullType) equalsTo(rhs Type) bool { - _, ok := rhs.(nullType) +func (v NullType) equalsTo(rhs Type) bool { + _, ok := rhs.(NullType) return ok } -func (nullType) toYDB(*allocator.Allocator) *Ydb.Type { +func (NullType) ToYDB(*allocator.Allocator) *Ydb.Type { return _nullType } -func Null() nullType { - return nullType{} +func Null() NullType { + return NullType{} } diff --git a/internal/value/type_test.go b/internal/types/type_test.go similarity index 99% rename from internal/value/type_test.go rename to internal/types/type_test.go index 22236684e..2731be60a 100644 --- a/internal/value/type_test.go +++ b/internal/types/type_test.go @@ -1,4 +1,4 @@ -package value +package types import ( "testing" diff --git a/internal/value/value.go b/internal/value/value.go index 4c8e3cbdd..81f07c89f 100644 --- a/internal/value/value.go +++ b/internal/value/value.go @@ -14,12 +14,13 @@ 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" ) type Value interface { - Type() Type + Type() types.Type Yql() string castTo(dst interface{}) error @@ -29,7 +30,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 @@ -52,16 +53,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) { 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.OptionalType: + return NullValue(tt.InnerType()), true + case types.VoidType: return VoidValue(), true default: return nil, false @@ -72,57 +73,57 @@ func nullValueFromYDB(x *Ydb.Value, t Type) (_ Value, ok bool) { } } -func primitiveValueFromYDB(t PrimitiveType, v *Ydb.Value) (Value, error) { +func primitiveValueFromYDB(t types.PrimitiveType, v *Ydb.Value) (Value, error) { switch t { - case TypeBool: + case types.TypeBool: return BoolValue(v.GetBoolValue()), nil - case TypeInt8: + case types.TypeInt8: return Int8Value(int8(v.GetInt32Value())), nil - case TypeInt16: + case types.TypeInt16: return Int16Value(int16(v.GetInt32Value())), nil - case TypeInt32: + case types.TypeInt32: return Int32Value(v.GetInt32Value()), nil - case TypeInt64: + case types.TypeInt64: return Int64Value(v.GetInt64Value()), nil - case TypeUint8: + case types.TypeUint8: return Uint8Value(uint8(v.GetUint32Value())), nil - case TypeUint16: + case types.TypeUint16: return Uint16Value(uint16(v.GetUint32Value())), nil - case TypeUint32: + case types.TypeUint32: return Uint32Value(v.GetUint32Value()), nil - case TypeUint64: + case types.TypeUint64: return Uint64Value(v.GetUint64Value()), nil - case TypeDate: + case types.TypeDate: return DateValue(v.GetUint32Value()), nil - case TypeDatetime: + case types.TypeDatetime: return DatetimeValue(v.GetUint32Value()), nil - case TypeInterval: + case types.TypeInterval: return IntervalValue(v.GetInt64Value()), nil - case TypeTimestamp: + case types.TypeTimestamp: return TimestampValue(v.GetUint64Value()), nil - case TypeFloat: + case types.TypeFloat: return FloatValue(v.GetFloatValue()), nil - case TypeDouble: + case types.TypeDouble: return DoubleValue(v.GetDoubleValue()), nil - case TypeText: + case types.TypeText: return TextValue(v.GetTextValue()), nil - case TypeYSON: + case types.TypeYSON: switch vv := v.GetValue().(type) { case *Ydb.Value_TextValue: return YSONValue(xstring.ToBytes(vv.TextValue)), nil @@ -132,28 +133,28 @@ 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.TypeJSON: return JSONValue(v.GetTextValue()), nil - case TypeJSONDocument: + case types.TypeJSONDocument: return JSONDocumentValue(v.GetTextValue()), nil - case TypeDyNumber: + case types.TypeDyNumber: return DyNumberValue(v.GetTextValue()), nil - case TypeTzDate: + case types.TypeTzDate: return TzDateValue(v.GetTextValue()), nil - case TypeTzDatetime: + case types.TypeTzDatetime: return TzDatetimeValue(v.GetTextValue()), nil - case TypeTzTimestamp: + case types.TypeTzTimestamp: return TzTimestampValue(v.GetTextValue()), nil - case TypeBytes: + case types.TypeBytes: return BytesValue(v.GetBytesValue()), nil - case TypeUUID: + case types.TypeUUID: return UUIDValue(BigEndianUint128(v.High_128, v.GetLow_128())), nil default: @@ -162,26 +163,26 @@ 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.PrimitiveType: return primitiveValueFromYDB(ttt, v) - case voidType: + case types.VoidType: return VoidValue(), nil - case nullType: + case types.NullType: return NullValue(tt), nil - case *DecimalType: + case *types.DecimalType: return DecimalValue(BigEndianUint128(v.High_128, v.GetLow_128()), ttt.Precision, ttt.Scale), nil - case optionalType: + case types.OptionalType: t = t.Type.(*Ydb.Type_OptionalType).OptionalType.Item if nestedValue, ok := v.Value.(*Ydb.Value_NestedValue); ok { return OptionalValue(FromYDB(t, nestedValue.NestedValue)), nil @@ -189,92 +190,92 @@ func fromYDB(t *Ydb.Type, v *Ydb.Value) (Value, error) { return OptionalValue(FromYDB(t, v)), nil - case *listType: + case *types.ListType: 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.TupleType: 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.Item(i).ToYDB(a), vvv) } return vv }()...), nil - case *StructType: + case *types.StructType: 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.DictType: 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.Key), + V: FromYDB(ttt.ValueType().ToYDB(a), vvv.Payload), } } return vv }()...), nil - case *setType: + case *types.SetType: 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.Key) } return vv }()...), nil - case *variantStructType: + case *types.VariantStructType: a := allocator.New() defer a.Free() return VariantValueStruct( FromYDB( - ttt.StructType.fields[v.VariantIndex].T.toYDB(a), + ttt.StructType.Field(int(v.VariantIndex)).T.ToYDB(a), v.Value.(*Ydb.Value_NestedValue).NestedValue, ), - ttt.StructType.fields[v.VariantIndex].Name, + ttt.StructType.Field(int(v.VariantIndex)).Name, ttt.StructType, ), nil - case *variantTupleType: + case *types.VariantTupleType: a := allocator.New() defer a.Free() return VariantValueTuple( FromYDB( - ttt.TupleType.items[v.VariantIndex].toYDB(a), + ttt.TupleType.Item(int(v.VariantIndex)).ToYDB(a), v.Value.(*Ydb.Value_NestedValue).NestedValue, ), v.VariantIndex, @@ -307,8 +308,8 @@ func (v boolValue) Yql() string { return strconv.FormatBool(bool(v)) } -func (boolValue) Type() Type { - return TypeBool +func (boolValue) Type() types.Type { + return types.TypeBool } func (v boolValue) toYDB(a *allocator.Allocator) *Ydb.Value { @@ -355,8 +356,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.TypeDate } func (v dateValue) toYDB(a *allocator.Allocator) *Ydb.Value { @@ -408,8 +409,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.TypeDatetime } func (v datetimeValue) toYDB(a *allocator.Allocator) *Ydb.Value { @@ -435,7 +436,7 @@ var _ DecimalValuer = (*decimalValue)(nil) type decimalValue struct { value [16]byte - innerType *DecimalType + innerType *types.DecimalType } func (v *decimalValue) Value() [16]byte { @@ -478,7 +479,7 @@ func (v *decimalValue) Yql() string { return buffer.String() } -func (v *decimalValue) Type() Type { +func (v *decimalValue) Type() types.Type { return v.innerType } @@ -506,7 +507,7 @@ func DecimalValueFromBigInt(v *big.Int, precision, scale uint32) *decimalValue { func DecimalValue(v [16]byte, precision, scale uint32) *decimalValue { return &decimalValue{ value: v, - innerType: &DecimalType{ + innerType: &types.DecimalType{ Precision: precision, Scale: scale, }, @@ -519,7 +520,7 @@ type ( V Value } dictValue struct { - t Type + t types.Type values []DictValueField } ) @@ -554,7 +555,7 @@ func (v *dictValue) Yql() string { return buffer.String() } -func (v *dictValue) Type() Type { +func (v *dictValue) Type() types.Type { return v.t } @@ -581,12 +582,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.Dict(values[0].K.Type(), values[0].V.Type()) default: - t = EmptyDict() + t = types.EmptyDict() } return &dictValue{ @@ -622,8 +623,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.TypeDouble } func (v *doubleValue) toYDB(a *allocator.Allocator) *Ydb.Value { @@ -663,8 +664,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.TypeDyNumber } func (v dyNumberValue) toYDB(a *allocator.Allocator) *Ydb.Value { @@ -712,8 +713,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.TypeFloat } func (v *floatValue) toYDB(a *allocator.Allocator) *Ydb.Value { @@ -777,8 +778,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.TypeInt8 } func (v int8Value) toYDB(a *allocator.Allocator) *Ydb.Value { @@ -836,8 +837,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.TypeInt16 } func (v int16Value) toYDB(a *allocator.Allocator) *Ydb.Value { @@ -895,8 +896,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.TypeInt32 } func (v int32Value) toYDB(a *allocator.Allocator) *Ydb.Value { @@ -942,8 +943,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.TypeInt64 } func (v int64Value) toYDB(a *allocator.Allocator) *Ydb.Value { @@ -1018,8 +1019,8 @@ func (v intervalValue) Yql() string { return buffer.String() } -func (intervalValue) Type() Type { - return TypeInterval +func (intervalValue) Type() types.Type { + return types.TypeInterval } func (v intervalValue) toYDB(a *allocator.Allocator) *Ydb.Value { @@ -1062,8 +1063,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.TypeJSON } func (v jsonValue) toYDB(a *allocator.Allocator) *Ydb.Value { @@ -1101,8 +1102,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.TypeJSONDocument } func (v jsonDocumentValue) toYDB(a *allocator.Allocator) *Ydb.Value { @@ -1120,7 +1121,7 @@ func JSONDocumentValue(v string) jsonDocumentValue { } type listValue struct { - t Type + t types.Type items []Value } @@ -1147,7 +1148,7 @@ func (v *listValue) Yql() string { return buffer.String() } -func (v *listValue) Type() Type { +func (v *listValue) Type() types.Type { return v.t } @@ -1166,12 +1167,12 @@ func (v *listValue) toYDB(a *allocator.Allocator) *Ydb.Value { } func ListValue(items ...Value) *listValue { - var t Type + var t types.Type switch { case len(items) > 0: - t = List(items[0].Type()) + t = types.List(items[0].Type()) default: - t = EmptyList() + t = types.EmptyList() } return &listValue{ @@ -1181,7 +1182,7 @@ func ListValue(items ...Value) *listValue { } type setValue struct { - t Type + t types.Type items []Value } @@ -1204,7 +1205,7 @@ func (v *setValue) Yql() string { return buffer.String() } -func (v *setValue) Type() Type { +func (v *setValue) Type() types.Type { return v.t } @@ -1228,12 +1229,12 @@ func SetValue(items ...Value) *setValue { 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.Set(items[0].Type()) default: - t = EmptySet() + t = types.EmptySet() } return &setValue{ @@ -1242,15 +1243,15 @@ func SetValue(items ...Value) *setValue { } } -func NullValue(t Type) *optionalValue { +func NullValue(t types.Type) *optionalValue { return &optionalValue{ - innerType: Optional(t), + innerType: types.Optional(t), value: nil, } } type optionalValue struct { - innerType Type + innerType types.Type value Value } @@ -1272,7 +1273,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 } @@ -1295,7 +1296,7 @@ func (v *optionalValue) toYDB(a *allocator.Allocator) *Ydb.Value { func OptionalValue(v Value) *optionalValue { return &optionalValue{ - innerType: Optional(v.Type()), + innerType: types.Optional(v.Type()), value: v, } } @@ -1306,7 +1307,7 @@ type ( V Value } structValue struct { - t Type + t types.Type fields []StructValueField } ) @@ -1340,7 +1341,7 @@ func (v *structValue) Yql() string { return buffer.String() } -func (v *structValue) Type() Type { +func (v *structValue) Type() types.Type { return v.t } @@ -1358,13 +1359,13 @@ 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{fields[i].Name, fields[i].V.Type()}) } return &structValue{ - t: Struct(structFields...), + t: types.Struct(structFields...), fields: fields, } } @@ -1390,8 +1391,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.TypeTimestamp } func (v timestampValue) toYDB(a *allocator.Allocator) *Ydb.Value { @@ -1414,7 +1415,7 @@ func TimestampValueFromTime(t time.Time) timestampValue { } type tupleValue struct { - t Type + t types.Type items []Value } @@ -1445,7 +1446,7 @@ func (v *tupleValue) Yql() string { return buffer.String() } -func (v *tupleValue) Type() Type { +func (v *tupleValue) Type() types.Type { return v.t } @@ -1464,13 +1465,13 @@ func (v *tupleValue) toYDB(a *allocator.Allocator) *Ydb.Value { } 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.Tuple(tupleItems...), items: values, } } @@ -1496,8 +1497,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.TypeTzDate } func (v tzDateValue) toYDB(a *allocator.Allocator) *Ydb.Value { @@ -1539,8 +1540,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.TypeTzDatetime } func (v tzDatetimeValue) toYDB(a *allocator.Allocator) *Ydb.Value { @@ -1582,8 +1583,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.TypeTzTimestamp } func (v tzTimestampValue) toYDB(a *allocator.Allocator) *Ydb.Value { @@ -1661,8 +1662,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.TypeUint8 } func (v uint8Value) toYDB(a *allocator.Allocator) *Ydb.Value { @@ -1728,8 +1729,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.TypeUint16 } func (v uint16Value) toYDB(a *allocator.Allocator) *Ydb.Value { @@ -1783,8 +1784,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.TypeUint32 } func (v uint32Value) toYDB(a *allocator.Allocator) *Ydb.Value { @@ -1826,8 +1827,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.TypeUint64 } func (v uint64Value) toYDB(a *allocator.Allocator) *Ydb.Value { @@ -1865,8 +1866,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.TypeText } func (v textValue) toYDB(a *allocator.Allocator) *Ydb.Value { @@ -1919,8 +1920,8 @@ func (v *uuidValue) Yql() string { return buffer.String() } -func (*uuidValue) Type() Type { - return TypeUUID +func (*uuidValue) Type() types.Type { + return types.TypeUUID } func (v *uuidValue) toYDB(a *allocator.Allocator) *Ydb.Value { @@ -1943,15 +1944,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.VariantStructType: + return t.Field(int(v.idx)).Name, v.idx default: return "", v.idx } @@ -1972,9 +1973,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.VariantStructType: + fmt.Fprintf(buffer, "%q", t.Field(int(v.idx)).Name) + case *types.VariantTupleType: fmt.Fprintf(buffer, "\""+strconv.FormatUint(uint64(v.idx), 10)+"\"") } buffer.WriteByte(',') @@ -1984,7 +1985,7 @@ func (v *variantValue) Yql() string { return buffer.String() } -func (v *variantValue) Type() Type { +func (v *variantValue) Type() types.Type { return v.innerType } @@ -2000,9 +2001,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.TupleType); has { + t = types.VariantTuple(tt.Items()...) } return &variantValue{ @@ -2012,23 +2013,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.StructType: + 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.VariantStruct(fields...) + case *types.VariantStructType: + 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 }) } @@ -2050,13 +2053,13 @@ func (v voidValue) Yql() string { } var ( - _voidValueType = voidType{} + _voidValueType = types.VoidType{} _voidValue = &Ydb.Value{ Value: new(Ydb.Value_NullFlagValue), } ) -func (voidValue) Type() Type { +func (voidValue) Type() types.Type { return _voidValueType } @@ -2089,8 +2092,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.TypeYSON } func (v ysonValue) toYDB(a *allocator.Allocator) *Ydb.Value { @@ -2109,81 +2112,81 @@ func YSONValue(v []byte) ysonValue { return v } -func zeroPrimitiveValue(t PrimitiveType) Value { +func zeroPrimitiveValue(t types.PrimitiveType) Value { switch t { - case TypeBool: + case types.TypeBool: return BoolValue(false) - case TypeInt8: + case types.TypeInt8: return Int8Value(0) - case TypeUint8: + case types.TypeUint8: return Uint8Value(0) - case TypeInt16: + case types.TypeInt16: return Int16Value(0) - case TypeUint16: + case types.TypeUint16: return Uint16Value(0) - case TypeInt32: + case types.TypeInt32: return Int32Value(0) - case TypeUint32: + case types.TypeUint32: return Uint32Value(0) - case TypeInt64: + case types.TypeInt64: return Int64Value(0) - case TypeUint64: + case types.TypeUint64: return Uint64Value(0) - case TypeFloat: + case types.TypeFloat: return FloatValue(0) - case TypeDouble: + case types.TypeDouble: return DoubleValue(0) - case TypeDate: + case types.TypeDate: return DateValue(0) - case TypeDatetime: + case types.TypeDatetime: return DatetimeValue(0) - case TypeTimestamp: + case types.TypeTimestamp: return TimestampValue(0) - case TypeInterval: + case types.TypeInterval: return IntervalValue(0) - case TypeText: + case types.TypeText: return TextValue("") - case TypeYSON: + case types.TypeYSON: return YSONValue([]byte("")) - case TypeJSON: + case types.TypeJSON: return JSONValue("") - case TypeJSONDocument: + case types.TypeJSONDocument: return JSONDocumentValue("") - case TypeDyNumber: + case types.TypeDyNumber: return DyNumberValue("") - case TypeTzDate: + case types.TypeTzDate: return TzDateValue("") - case TypeTzDatetime: + case types.TypeTzDatetime: return TzDatetimeValue("") - case TypeTzTimestamp: + case types.TypeTzTimestamp: return TzTimestampValue("") - case TypeBytes: + case types.TypeBytes: return BytesValue([]byte{}) - case TypeUUID: + case types.TypeUUID: return UUIDValue([16]byte{}) default: @@ -2191,55 +2194,56 @@ func zeroPrimitiveValue(t PrimitiveType) Value { } } -func ZeroValue(t Type) Value { +func ZeroValue(t types.Type) Value { switch t := t.(type) { - case PrimitiveType: + case types.PrimitiveType: return zeroPrimitiveValue(t) - case optionalType: - return NullValue(t.innerType) + case types.OptionalType: + return NullValue(t.InnerType()) - case *voidType: + case *types.VoidType: return VoidValue() - case *listType, *emptyListType: + case *types.ListType, *types.EmptyListType: return &listValue{ t: t, } - case *setType: + case *types.SetType: return &setValue{ t: t, } - case *dictType: + case *types.DictType: return &dictValue{ - t: t.valueType, + t: t.ValueType(), } - case *emptyDictType: + case *types.EmptyDictType: return &dictValue{ t: t, } - case *TupleType: + case *types.TupleType: return TupleValue(func() []Value { - values := make([]Value, len(t.items)) - for i, tt := range t.items { + items := t.Items() + values := make([]Value, len(items)) + for i, tt := range items { values[i] = ZeroValue(tt) } return values }()...) - case *StructType: + case *types.StructType: return StructValue(func() []StructValueField { - fields := make([]StructValueField, len(t.fields)) - for i := range t.fields { + 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), + Name: t.Field(i).Name, + V: ZeroValue(t.Field(i).T), } } return fields }()...) - case *DecimalType: + case *types.DecimalType: return DecimalValue([16]byte{}, 22, 9) default: @@ -2268,8 +2272,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.TypeBytes } 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..0aaddf9e7 100644 --- a/internal/value/value_test.go +++ b/internal/value/value_test.go @@ -11,6 +11,7 @@ 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/types" ) func BenchmarkMemory(b *testing.B) { @@ -68,24 +69,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.Optional(types.Optional(types.Optional(types.TypeBool)))), + VariantValueTuple(Int32Value(42), 1, types.Tuple( + types.TypeBytes, + types.TypeInt32, )), - VariantValueStruct(Int32Value(42), "bar", Struct( - StructField{ + VariantValueStruct(Int32Value(42), "bar", types.Struct( + types.StructField{ Name: "foo", - T: TypeBytes, + T: types.TypeBytes, }, - StructField{ + types.StructField{ Name: "bar", - T: TypeInt32, + T: types.TypeInt32, }, )), - ZeroValue(TypeText), - ZeroValue(Struct()), - ZeroValue(Tuple()), + ZeroValue(types.TypeText), + ZeroValue(types.Struct()), + ZeroValue(types.Tuple()), ) for i := 0; i < b.N; i++ { a := allocator.New() @@ -153,25 +154,25 @@ 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.TypeBool), + NullValue(types.Optional(types.TypeBool)), + VariantValueTuple(Int32Value(42), 1, types.Tuple( + types.TypeBytes, + types.TypeInt32, )), - VariantValueStruct(Int32Value(42), "bar", Struct( - StructField{ + VariantValueStruct(Int32Value(42), "bar", types.Struct( + types.StructField{ Name: "foo", - T: TypeBytes, + T: types.TypeBytes, }, - StructField{ + types.StructField{ Name: "bar", - T: TypeInt32, + T: types.TypeInt32, }, )), - ZeroValue(TypeText), - ZeroValue(Struct()), - ZeroValue(Tuple()), + ZeroValue(types.TypeText), + ZeroValue(types.Struct()), + ZeroValue(types.Tuple()), } { t.Run(strconv.Itoa(i)+"."+v.Yql(), func(t *testing.T) { a := allocator.New() @@ -329,11 +330,11 @@ func TestValueYql(t *testing.T) { literal: `TzTimestamp("1997-12-14T03:09:42.123456,Europe/Berlin")`, }, { - value: NullValue(TypeInt32), + value: NullValue(types.TypeInt32), literal: `Nothing(Optional)`, }, { - value: NullValue(Optional(TypeBool)), + value: NullValue(types.Optional(types.TypeBool)), literal: `Nothing(Optional>)`, }, { @@ -390,30 +391,30 @@ 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.Tuple( + types.TypeBytes, + types.TypeInt32, )), literal: `Variant(42,"1",Variant)`, }, { - value: VariantValueTuple(TextValue("foo"), 1, Tuple( - TypeBytes, - TypeText, + value: VariantValueTuple(TextValue("foo"), 1, types.Tuple( + types.TypeBytes, + types.TypeText, )), literal: `Variant("foo"u,"1",Variant)`, }, { - value: VariantValueTuple(BoolValue(true), 0, Tuple( - TypeBytes, - TypeInt32, + value: VariantValueTuple(BoolValue(true), 0, types.Tuple( + types.TypeBytes, + types.TypeInt32, )), literal: `Variant(true,"0",Variant)`, }, { - value: VariantValueStruct(Int32Value(42), "bar", Struct( - StructField{"foo", TypeBytes}, - StructField{"bar", TypeInt32}, + value: VariantValueStruct(Int32Value(42), "bar", types.Struct( + types.StructField{"foo", types.TypeBytes}, + types.StructField{"bar", types.TypeInt32}, )), literal: `Variant(42,"bar",Variant<'bar':Int32,'foo':String>)`, }, @@ -440,26 +441,26 @@ func TestValueYql(t *testing.T) { literal: `{"bar"u:Void(),"foo"u:Void()}`, }, { - value: ZeroValue(TypeBool), + value: ZeroValue(types.TypeBool), literal: `false`, }, { - value: ZeroValue(Optional(TypeBool)), + value: ZeroValue(types.Optional(types.TypeBool)), literal: `Nothing(Optional)`, }, { - value: ZeroValue(Tuple(TypeBool, TypeDouble)), + value: ZeroValue(types.Tuple(types.TypeBool, types.TypeDouble)), literal: `(false,Double("0"))`, }, { - value: ZeroValue(Struct( - StructField{"foo", TypeBool}, - StructField{"bar", TypeText}, + value: ZeroValue(types.Struct( + types.StructField{"foo", types.TypeBool}, + types.StructField{"bar", types.TypeText}, )), literal: "<|`bar`:\"\"u,`foo`:false|>", }, { - value: ZeroValue(TypeUUID), + value: ZeroValue(types.TypeUUID), literal: `Uuid("00000000-0000-0000-0000-000000000000")`, }, { diff --git a/internal/xerrors/operation.go b/internal/xerrors/operation.go index f2383077a..ded028e9f 100644 --- a/internal/xerrors/operation.go +++ b/internal/xerrors/operation.go @@ -8,10 +8,11 @@ import ( "github.com/ydb-platform/ydb-go-genproto/protos/Ydb_Issue" "github.com/ydb-platform/ydb-go-sdk/v3/internal/backoff" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/operation" "github.com/ydb-platform/ydb-go-sdk/v3/internal/xstring" ) -// operationError reports about operationStatus fail. +// operationError reports about operation fail. type operationError struct { code Ydb.StatusIds_StatusCode issues issues @@ -29,18 +30,13 @@ func (e *operationError) Name() string { return "operation/" + e.code.String() } -type operationStatus interface { - GetStatus() Ydb.StatusIds_StatusCode - GetIssues() []*Ydb_Issue.IssueMessage -} - type issuesOption []*Ydb_Issue.IssueMessage func (issues issuesOption) applyToOperationError(oe *operationError) { oe.issues = []*Ydb_Issue.IssueMessage(issues) } -// WithIssues is an option for construct operationStatus error with issues list +// WithIssues is an option for construct operation error with issues list // WithIssues must use as `Operation(WithIssues(issues))` func WithIssues(issues []*Ydb_Issue.IssueMessage) issuesOption { return issues @@ -52,7 +48,7 @@ func (code statusCodeOption) applyToOperationError(oe *operationError) { oe.code = Ydb.StatusIds_StatusCode(code) } -// WithStatusCode is an option for construct operationStatus error with reason code +// WithStatusCode is an option for construct operation error with reason code // WithStatusCode must use as `Operation(WithStatusCode(reason))` func WithStatusCode(code Ydb.StatusIds_StatusCode) statusCodeOption { return statusCodeOption(code) @@ -72,24 +68,25 @@ func (traceID traceIDOption) applyToOperationError(oe *operationError) { oe.traceID = string(traceID) } -// WithTraceID is an option for construct operationStatus error with traceID +// WithTraceID is an option for construct operation error with traceID func WithTraceID(traceID string) traceIDOption { return traceIDOption(traceID) } -type operationOption struct { - operationStatus -} +type operationOption = operationError -func (operation operationOption) applyToOperationError(oe *operationError) { - oe.code = operation.GetStatus() - oe.issues = operation.GetIssues() +func (e *operationOption) applyToOperationError(oe *operationError) { + oe.code = e.code + oe.issues = e.issues } -// FromOperation is an option for construct operationStatus error from operationStatus -// FromOperation must use as `Operation(FromOperation(operationStatus))` -func FromOperation(operation operationStatus) operationOption { - return operationOption{operation} +// FromOperation is an option for construct operation error from operation.Status +// FromOperation must use as `Operation(FromOperation(operation.Status))` +func FromOperation(operation operation.Status) *operationOption { + return &operationOption{ + code: operation.GetStatus(), + issues: operation.GetIssues(), + } } type oeOpt interface { diff --git a/options.go b/options.go index 41c995f97..6af346db3 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" @@ -352,7 +353,6 @@ func WithCertificatesFromPem(bytes []byte, opts ...certificates.FromPemOption) O for _, cert := range certs { _ = WithCertificate(cert)(ctx, c) } - return nil } } @@ -362,7 +362,15 @@ func WithCertificatesFromPem(bytes []byte, opts ...certificates.FromPemOption) O func WithTableConfigOption(option tableConfig.Option) Option { return func(ctx context.Context, c *Driver) error { c.tableOptions = append(c.tableOptions, option) + return nil + } +} +// 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 } } @@ -371,7 +379,7 @@ func WithTableConfigOption(option tableConfig.Option) Option { 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.WithSizeLimit(sizeLimit)) return nil } } @@ -405,7 +413,7 @@ func WithSessionPoolKeepAliveTimeout(keepAliveTimeout time.Duration) Option { 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.WithCreateSessionTimeout(createSessionTimeout)) return nil } } @@ -414,7 +422,7 @@ 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.WithDeleteTimeout(deleteTimeout)) return nil } } @@ -423,7 +431,6 @@ func WithSessionPoolDeleteTimeout(deleteTimeout time.Duration) Option { func WithIgnoreTruncated() Option { return func(ctx context.Context, c *Driver) error { c.tableOptions = append(c.tableOptions, tableConfig.WithIgnoreTruncated()) - return nil } } @@ -436,7 +443,6 @@ func WithPanicCallback(panicCallback func(e interface{})) Option { return func(ctx context.Context, c *Driver) error { c.panicCallback = panicCallback c.options = append(c.options, config.WithPanicCallback(panicCallback)) - return nil } } @@ -456,7 +462,6 @@ func WithTraceTable(t trace.Table, opts ...trace.TableComposeOption) Option { // )..., ), ) - return nil } } diff --git a/query/client.go b/query/client.go new file mode 100644 index 000000000..82215a6e7 --- /dev/null +++ b/query/client.go @@ -0,0 +1,73 @@ +package query + +import ( + "context" + + "github.com/ydb-platform/ydb-go-sdk/v3/internal/closer" + "github.com/ydb-platform/ydb-go-sdk/v3/retry" + "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 ...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 ...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 + } + + DoOption interface { + applyDoOption(o *DoOptions) + } + + DoOptions struct { + Label string + Idempotent bool + RetryOptions []retry.Option + Trace *trace.Query + } + + DoTxOption interface { + applyDoTxOption(o *DoTxOptions) + } + + DoTxOptions struct { + DoOptions + + TxSettings TransactionSettings + } +) diff --git a/query/column.go b/query/column.go new file mode 100644 index 000000000..49defc291 --- /dev/null +++ b/query/column.go @@ -0,0 +1,6 @@ +package query + +type Column interface { + Name() string + Type() Type +} diff --git a/query/do_options.go b/query/do_options.go new file mode 100644 index 000000000..36591176f --- /dev/null +++ b/query/do_options.go @@ -0,0 +1,22 @@ +package query + +import ( + "github.com/ydb-platform/ydb-go-sdk/v3/retry" +) + +var ( + _ DoOption = idempotentOption{} + _ DoTxOption = idempotentOption{} +) + +func (idempotentOption) applyDoOption(opts *DoOptions) { + opts.Idempotent = true + opts.RetryOptions = append(opts.RetryOptions, retry.WithIdempotent(true)) +} + +func NewDoOptions(opts ...DoOption) (doOptions DoOptions) { + for _, opt := range opts { + opt.applyDoOption(&doOptions) + } + return doOptions +} diff --git a/query/do_tx_options.go b/query/do_tx_options.go new file mode 100644 index 000000000..064de0794 --- /dev/null +++ b/query/do_tx_options.go @@ -0,0 +1,34 @@ +package query + +import ( + "github.com/ydb-platform/ydb-go-sdk/v3/retry" +) + +var _ DoTxOption = idempotentOption{} + +func (idempotentOption) applyDoTxOption(opts *DoTxOptions) { + opts.Idempotent = true + opts.RetryOptions = append(opts.RetryOptions, retry.WithIdempotent(true)) +} + +var _ DoTxOption = doTxSettingsOption{} + +type doTxSettingsOption struct { + txSettings TransactionSettings +} + +func (opt doTxSettingsOption) applyDoTxOption(opts *DoTxOptions) { + opts.TxSettings = opt.txSettings +} + +func WithTxSettings(txSettings TransactionSettings) doTxSettingsOption { + return doTxSettingsOption{txSettings: txSettings} +} + +func NewDoTxOptions(opts ...DoTxOption) (doTxOptions DoTxOptions) { + doTxOptions.TxSettings = TxSettings(WithDefaultTxMode()) + for _, opt := range opts { + opt.applyDoTxOption(&doTxOptions) + } + return doTxOptions +} diff --git a/query/example_test.go b/query/example_test.go new file mode 100644 index 000000000..31a2af40c --- /dev/null +++ b/query/example_test.go @@ -0,0 +1,163 @@ +package query_test + +import ( + "context" + "fmt" + + "github.com/ydb-platform/ydb-go-sdk/v3" + "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 { + return err + } + for { // iterate over rows + row, err := rs.NextRow(ctx) + if err != nil { + 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`, + query.WithParameters( + query.Param("$id", query.Uint64Value(123)), + query.Param("$myStr", query.TextValue("test")), + ), + ) + 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 { + return err + } + for { // iterate over rows + row, err := rs.NextRow(ctx) + if err != nil { + 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 + }, + query.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 { + return err + } + for { // iterate over rows + row, err := rs.NextRow(ctx) + if err != nil { + 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 + }, + query.WithIdempotent(), + query.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/execute_options.go b/query/execute_options.go new file mode 100644 index 000000000..640c6e7b3 --- /dev/null +++ b/query/execute_options.go @@ -0,0 +1,165 @@ +package query + +import ( + "github.com/ydb-platform/ydb-go-genproto/protos/Ydb_Query" + "google.golang.org/grpc" +) + +type ( + Syntax = Ydb_Query.Syntax + ExecMode Ydb_Query.ExecMode + StatsMode Ydb_Query.StatsMode + callOptions []grpc.CallOption + commonExecuteSettings struct { + syntax Syntax + params *Parameters + execMode ExecMode + statsMode StatsMode + callOptions []grpc.CallOption + txControl *TransactionControl + } + executeSettings struct { + commonExecuteSettings + } + ExecuteOption interface { + applyExecuteOption(s *executeSettings) + } + txExecuteSettings struct { + commonExecuteSettings + } + TxExecuteOption interface { + applyTxExecuteOption(s *txExecuteSettings) + } +) + +func (opts callOptions) applyExecuteOption(s *executeSettings) { + s.callOptions = append(s.callOptions, opts...) +} + +func (opts callOptions) applyTxExecuteOption(s *txExecuteSettings) { + s.callOptions = append(s.callOptions, opts...) +} + +func (mode StatsMode) applyTxExecuteOption(s *txExecuteSettings) { + s.statsMode = mode +} + +func (mode StatsMode) applyExecuteOption(s *executeSettings) { + s.statsMode = mode +} + +func (mode ExecMode) applyTxExecuteOption(s *txExecuteSettings) { + s.execMode = mode +} + +func (mode ExecMode) applyExecuteOption(s *executeSettings) { + 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: Ydb_Query.Syntax_SYNTAX_YQL_V1, + execMode: ExecModeExecute, + statsMode: StatsModeNone, + } +} + +func ExecuteSettings(opts ...ExecuteOption) (settings executeSettings) { + settings.commonExecuteSettings = defaultCommonExecuteSettings() + settings.txControl = DefaultTxControl() + for _, opt := range opts { + opt.applyExecuteOption(&settings) + } + return settings +} + +func (s executeSettings) TxControl() *TransactionControl { + return s.txControl +} + +func (s executeSettings) CallOptions() []grpc.CallOption { + return s.callOptions +} + +func (s executeSettings) Syntax() Syntax { + return s.syntax +} + +func (s executeSettings) ExecMode() ExecMode { + return s.execMode +} + +func (s executeSettings) StatsMode() StatsMode { + return s.statsMode +} + +func (s executeSettings) Params() *Parameters { + return s.params +} + +func TxExecuteSettings(opts ...TxExecuteOption) (settings txExecuteSettings) { + settings.commonExecuteSettings = defaultCommonExecuteSettings() + for _, opt := range opts { + opt.applyTxExecuteOption(&settings) + } + return settings +} + +var _ ExecuteOption = (*parametersOption)(nil) + +func WithParameters(params ...Parameter) *parametersOption { + opt := ¶metersOption{ + params: &Parameters{ + m: make(queryParams, len(params)), + }, + } + opt.params.Add(params...) + return opt +} + +func Params(params ...Parameter) *Parameters { + p := &Parameters{ + m: make(queryParams, len(params)), + } + p.Add(params...) + return p +} + +var ( + _ ExecuteOption = ExecMode(0) + _ ExecuteOption = StatsMode(0) + _ TxExecuteOption = ExecMode(0) + _ TxExecuteOption = StatsMode(0) +) + +func WithExecMode(mode ExecMode) ExecMode { + return mode +} + +func WithStatsMode(mode StatsMode) StatsMode { + return mode +} + +func WithCallOptions(opts ...grpc.CallOption) callOptions { + return opts +} + +func WithTxControl(txControl *TransactionControl) *transactionControlOption { + return &transactionControlOption{ + txControl: txControl, + } +} diff --git a/query/named.go b/query/named.go new file mode 100644 index 000000000..5ff7a1d66 --- /dev/null +++ b/query/named.go @@ -0,0 +1,32 @@ +package query + +import ( + "fmt" + "reflect" +) + +type namedDestination struct { + name string + ref interface{} +} + +func (dst namedDestination) Name() string { + return dst.name +} + +func (dst namedDestination) Destination() interface{} { + return dst.ref +} + +func Named(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 +} diff --git a/query/named_test.go b/query/named_test.go new file mode 100644 index 000000000..7b804a2c3 --- /dev/null +++ b/query/named_test.go @@ -0,0 +1,72 @@ +package query + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestNamed(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, Named(tt.name, tt.ref)) + }) + } +} diff --git a/query/options.go b/query/options.go new file mode 100644 index 000000000..62618839b --- /dev/null +++ b/query/options.go @@ -0,0 +1,7 @@ +package query + +type idempotentOption struct{} + +func WithIdempotent() idempotentOption { + return idempotentOption{} +} diff --git a/query/parameters.go b/query/parameters.go new file mode 100644 index 000000000..f0bced090 --- /dev/null +++ b/query/parameters.go @@ -0,0 +1,135 @@ +package query + +import ( + "sort" + + "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" +) + +// Parameters +type ( + queryParams map[string]Value + Parameter interface { + Name() string + Value() Value + } + parameter struct { + name string + value Value + } + Parameters struct { + m queryParams + } + parametersOption struct { + params *Parameters + } +) + +func (opt *parametersOption) applyTxExecuteOption(o *txExecuteSettings) { + o.params = opt.params +} + +func (opt *parametersOption) applyExecuteOption(o *executeSettings) { + o.params = opt.params +} + +func (p parameter) Name() string { + return p.name +} + +func (p parameter) Value() Value { + return p.value +} + +func (params *Parameters) ToYDB(a *allocator.Allocator) map[string]*Ydb.TypedValue { + if params == nil || params.m == nil { + return nil + } + return params.m.toYDB(a) +} + +func (qp queryParams) toYDB(a *allocator.Allocator) map[string]*Ydb.TypedValue { + params := make(map[string]*Ydb.TypedValue, len(qp)) + for k, v := range qp { + params[k] = value.ToYDB(v, a) + } + return params +} + +func (params *Parameters) Params() queryParams { + if params == nil { + return nil + } + return params.m +} + +func (params *Parameters) Count() int { + if params == nil { + return 0 + } + return len(params.m) +} + +func (params *Parameters) Each(it func(name string, v Value)) { + if params == nil { + return + } + for key, v := range params.m { + it(key, v) + } +} + +func (params *Parameters) names() []string { + if params == nil { + return nil + } + names := make([]string, 0, len(params.m)) + for k := range params.m { + names = append(names, k) + } + sort.Strings(names) + return names +} + +func (params *Parameters) String() string { + buffer := xstring.Buffer() + defer buffer.Free() + + buffer.WriteByte('{') + for i, name := range params.names() { + if i != 0 { + buffer.WriteByte(',') + } + buffer.WriteByte('"') + buffer.WriteString(name) + buffer.WriteString("\":") + buffer.WriteString(params.m[name].Yql()) + } + buffer.WriteByte('}') + return buffer.String() +} + +func (params *Parameters) Add(parameters ...Parameter) { + for _, param := range parameters { + params.m[param.Name()] = param.Value() + } +} + +func Param(name string, v Value) Parameter { + switch len(name) { + case 0: + panic("empty name") + default: + if name[0] != '$' { + panic("parameter name must be started from $") + } + } + return ¶meter{ + name: name, + value: v, + } +} diff --git a/query/result.go b/query/result.go new file mode 100644 index 000000000..771cf7551 --- /dev/null +++ b/query/result.go @@ -0,0 +1,36 @@ +package query + +import ( + "context" + + "github.com/ydb-platform/ydb-go-sdk/v3/internal/closer" +) + +type ( + Result interface { + closer.Closer + + NextResultSet(ctx context.Context) (ResultSet, error) + Err() error + } + ResultSet interface { + NextRow(ctx context.Context) (Row, error) + } + Row interface { + IndexedScanner + NamedScanner + } + IndexedScanner interface { + Scan(dst ...interface{}) error + } + NamedScanner interface { + ScanNamed(dst ...NamedDestination) error + } + StructScanner interface { + ScanStruct(dst interface{}) error + } + NamedDestination interface { + Name() string + Destination() interface{} + } +) diff --git a/query/session.go b/query/session.go new file mode 100644 index 000000000..9e00ed6c4 --- /dev/null +++ b/query/session.go @@ -0,0 +1,26 @@ +package query + +import ( + "context" +) + +type ( + SessionInfo interface { + ID() string + NodeID() int64 + Status() SessionStatus + } + + Session interface { + SessionInfo + + // Execute executes query. + // + // Execute used by default: + // - DefaultTxControl + // - flag WithKeepInCache(true) if params is not empty. + Execute(ctx context.Context, query string, opts ...ExecuteOption) (tx Transaction, r Result, err error) + + Begin(ctx context.Context, txSettings TransactionSettings) (Transaction, error) + } +) diff --git a/query/session_status.go b/query/session_status.go new file mode 100644 index 000000000..630246c05 --- /dev/null +++ b/query/session_status.go @@ -0,0 +1,33 @@ +package query + +import ( + "fmt" + + "github.com/ydb-platform/ydb-go-sdk/v3/internal/session" +) + +type SessionStatus uint32 + +const ( + SessionStatusUnknown = SessionStatus(iota) + SessionStatusReady + SessionStatusInUse + SessionStatusClosed +) + +func (s SessionStatus) String() string { + switch s { + case 0: + return session.StatusUnknown + case 1: + return session.StatusReady + case 2: + return session.StatusBusy + case 3: + return session.StatusClosing + case 4: + return session.StatusClosed + default: + return fmt.Sprintf("unknown_%d", s) + } +} diff --git a/query/transaction.go b/query/transaction.go new file mode 100644 index 000000000..c8159f68e --- /dev/null +++ b/query/transaction.go @@ -0,0 +1,27 @@ +package query + +import "context" + +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 ...TxExecuteOption) (r Result, err error) + } + + Transaction interface { + TxActor + + CommitTx(ctx context.Context) (err error) + Rollback(ctx context.Context) (err error) + } +) diff --git a/query/transaction_control.go b/query/transaction_control.go new file mode 100644 index 000000000..653768725 --- /dev/null +++ b/query/transaction_control.go @@ -0,0 +1,157 @@ +package query + +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 + } = (*TransactionControl)(nil) + _ txSelector = (*TransactionSettings)(nil) +) + +type ( + txSelector interface { + applyTxSelector(a *allocator.Allocator, txControl *Ydb_Query.TransactionControl) + } + txControlOption interface { + applyTxControlOption(txControl *TransactionControl) + } + TransactionControl struct { + selector txSelector + commit bool + } + transactionControlOption struct { + txControl *TransactionControl + } +) + +func (opt *transactionControlOption) applyExecuteOption(s *executeSettings) { + s.txControl = opt.txControl +} + +func (ctrl *TransactionControl) ToYDB(a *allocator.Allocator) *Ydb_Query.TransactionControl { + txControl := a.QueryTransactionControl() + ctrl.selector.applyTxSelector(a, txControl) + txControl.CommitTx = ctrl.commit + return txControl +} + +var ( + _ txControlOption = beginTxOptions{} + _ txSelector = beginTxOptions{} +) + +type beginTxOptions []txSettingsOption + +func (opts beginTxOptions) applyTxControlOption(txControl *TransactionControl) { + 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 { + opt.applyTxSettingsOption(a, selector.BeginTx) + } + txControl.TxSelector = selector +} + +// BeginTx returns selector transaction control option +func BeginTx(opts ...txSettingsOption) beginTxOptions { + return opts +} + +var ( + _ txControlOption = txIdTxControlOption("") + _ txSelector = txIdTxControlOption("") +) + +type txIdTxControlOption string + +func (id txIdTxControlOption) applyTxControlOption(txControl *TransactionControl) { + 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 TxIdentifier) txIdTxControlOption { + return txIdTxControlOption(t.ID()) +} + +func WithTxID(txID string) txIdTxControlOption { + return txIdTxControlOption(txID) +} + +type commitTxOption struct{} + +func (c commitTxOption) applyTxControlOption(txControl *TransactionControl) { + txControl.commit = true +} + +// CommitTx returns commit transaction control option +func CommitTx() txControlOption { + return commitTxOption{} +} + +// TxControl makes transaction control from given options +func TxControl(opts ...txControlOption) *TransactionControl { + txControl := &TransactionControl{ + selector: BeginTx(WithSerializableReadWrite()), + commit: false, + } + for _, opt := range opts { + if opt != nil { + opt.applyTxControlOption(txControl) + } + } + return txControl +} + +// 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 ...txControlOption) *TransactionControl { + return TxControl( + append([]txControlOption{ + BeginTx(WithSerializableReadWrite()), + }, opts...)..., + ) +} + +// OnlineReadOnlyTxControl returns online read-only transaction control +func OnlineReadOnlyTxControl(opts ...TxOnlineReadOnlyOption) *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 + ) +} diff --git a/query/transaction_settings.go b/query/transaction_settings.go new file mode 100644 index 000000000..45673017b --- /dev/null +++ b/query/transaction_settings.go @@ -0,0 +1,136 @@ +package query + +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 ( + txSettingsOption interface { + applyTxSettingsOption(*allocator.Allocator, *Ydb_Query.TransactionSettings) + } + TransactionSettings []txSettingsOption +) + +func (opts TransactionSettings) applyTxSelector(a *allocator.Allocator, txControl *Ydb_Query.TransactionControl) { + beginTx := a.QueryTransactionControlBeginTx() + beginTx.BeginTx = a.QueryTransactionSettings() + for _, opt := range opts { + opt.applyTxSettingsOption(a, beginTx.BeginTx) + } + txControl.TxSelector = beginTx +} + +func (opts TransactionSettings) ToYDB(a *allocator.Allocator) *Ydb_Query.TransactionSettings { + txSettings := a.QueryTransactionSettings() + for _, opt := range opts { + opt.applyTxSettingsOption(a, txSettings) + } + return txSettings +} + +// TxSettings returns transaction settings +func TxSettings(opts ...txSettingsOption) TransactionSettings { + return opts +} + +func WithDefaultTxMode() txSettingsOption { + return WithSerializableReadWrite() +} + +var _ txSettingsOption = serializableReadWriteTxSettingsOption{} + +type serializableReadWriteTxSettingsOption struct{} + +func (o serializableReadWriteTxSettingsOption) applyTxSettingsOption(a *allocator.Allocator, txSettings *Ydb_Query.TransactionSettings) { + txSettings.TxMode = serializableReadWrite +} + +func WithSerializableReadWrite() txSettingsOption { + return serializableReadWriteTxSettingsOption{} +} + +var _ txSettingsOption = snapshotReadOnlyTxSettingsOption{} + +type snapshotReadOnlyTxSettingsOption struct{} + +func (snapshotReadOnlyTxSettingsOption) applyTxSettingsOption(a *allocator.Allocator, settings *Ydb_Query.TransactionSettings) { + settings.TxMode = snapshotReadOnly +} + +func WithSnapshotReadOnly() txSettingsOption { + return snapshotReadOnlyTxSettingsOption{} +} + +var _ txSettingsOption = staleReadOnlySettingsOption{} + +type staleReadOnlySettingsOption struct{} + +func (staleReadOnlySettingsOption) applyTxSettingsOption(a *allocator.Allocator, settings *Ydb_Query.TransactionSettings) { + settings.TxMode = staleReadOnly +} + +func WithStaleReadOnly() txSettingsOption { + return staleReadOnlySettingsOption{} +} + +type ( + txOnlineReadOnly bool + TxOnlineReadOnlyOption interface { + applyTxOnlineReadOnlyOption(*txOnlineReadOnly) + } +) + +var _ TxOnlineReadOnlyOption = inconsistentReadsTxOnlineReadOnlyOption{} + +type inconsistentReadsTxOnlineReadOnlyOption struct{} + +func (i inconsistentReadsTxOnlineReadOnlyOption) applyTxOnlineReadOnlyOption(b *txOnlineReadOnly) { + *b = true +} + +func WithInconsistentReads() TxOnlineReadOnlyOption { + return inconsistentReadsTxOnlineReadOnlyOption{} +} + +var _ txSettingsOption = onlineReadOnlySettingsOption{} + +type onlineReadOnlySettingsOption []TxOnlineReadOnlyOption + +func (opts onlineReadOnlySettingsOption) applyTxSettingsOption(a *allocator.Allocator, settings *Ydb_Query.TransactionSettings) { + var ro txOnlineReadOnly + for _, opt := range opts { + if opt != nil { + opt.applyTxOnlineReadOnlyOption(&ro) + } + } + if ro { + settings.TxMode = onlineReadOnlyAllowInconsistentReads + } else { + settings.TxMode = onlineReadOnlyForbidInconsistentReads + } +} + +func WithOnlineReadOnly(opts ...TxOnlineReadOnlyOption) onlineReadOnlySettingsOption { + return opts +} diff --git a/query/type.go b/query/type.go new file mode 100644 index 000000000..092fc2ff0 --- /dev/null +++ b/query/type.go @@ -0,0 +1,7 @@ +package query + +import ( + "github.com/ydb-platform/ydb-go-sdk/v3/internal/types" +) + +type Type = types.Type diff --git a/query/value.go b/query/value.go new file mode 100644 index 000000000..10f8c2a19 --- /dev/null +++ b/query/value.go @@ -0,0 +1,65 @@ +package query + +import ( + "time" + + "github.com/ydb-platform/ydb-go-sdk/v3/internal/value" +) + +type Value = value.Value + +func BoolValue(v bool) Value { + return value.BoolValue(v) +} + +func Uint64Value(v uint64) Value { + return value.Uint64Value(v) +} + +func Int64Value(v int64) Value { + return value.Int64Value(v) +} + +func Uint32Value(v uint32) Value { + return value.Uint32Value(v) +} + +func Int32Value(v int32) Value { + return value.Int32Value(v) +} + +func Uint16Value(v uint16) Value { + return value.Uint16Value(v) +} + +func Int16Value(v int16) Value { + return value.Int16Value(v) +} + +func Uint8Value(v uint8) Value { + return value.Uint8Value(v) +} + +func Int8Value(v int8) Value { + return value.Int8Value(v) +} + +func TextValue(v string) Value { + return value.TextValue(v) +} + +func BytesValue(v []byte) Value { + return value.BytesValue(v) +} + +func IntervalValue(v time.Duration) Value { + return value.IntervalValueFromDuration(v) +} + +func TimestampValue(v time.Time) Value { + return value.TimestampValueFromTime(v) +} + +func DatetimeValue(v time.Time) Value { + return value.DatetimeValueFromTime(v) +} diff --git a/table/options/models.go b/table/options/models.go index 1464defe4..4a85d831d 100644 --- a/table/options/models.go +++ b/table/options/models.go @@ -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: c.Type.ToYDB(a), Family: c.Family, } } @@ -406,8 +406,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..4949557f8 100644 --- a/table/options/options.go +++ b/table/options/options.go @@ -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: c.typ.ToYDB(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: c.typ.ToYDB(a), }) } diff --git a/table/options/options_test.go b/table/options/options_test.go index d702a8fb0..0f4e4160a 100644 --- a/table/options/options_test.go +++ b/table/options/options_test.go @@ -173,7 +173,7 @@ func TestAlterTableOptions(t *testing.T) { 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].Type != column.Type.ToYDB(a) || req.AddColumns[0].Family != column.Family { t.Errorf("Alter table options is not as expected") } diff --git a/table/table.go b/table/table.go index d2d4b1001..2417ad970 100644 --- a/table/table.go +++ b/table/table.go @@ -10,6 +10,7 @@ import ( "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/session" "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" @@ -70,14 +71,14 @@ type Client interface { DoTx(ctx context.Context, op TxOperation, opts ...Option) error } -type SessionStatus = string +type SessionStatus = session.Status const ( - SessionStatusUnknown = SessionStatus("unknown") - SessionReady = SessionStatus("ready") - SessionBusy = SessionStatus("busy") - SessionClosing = SessionStatus("closing") - SessionClosed = SessionStatus("closed") + SessionStatusUnknown = session.StatusUnknown + SessionReady = session.StatusReady + SessionBusy = session.StatusBusy + SessionClosing = session.StatusClosing + SessionClosed = session.StatusClosed ) type SessionInfo interface { diff --git a/table/types/types.go b/table/types/types.go index 55a3dffdc..58da521e2 100644 --- a/table/types/types.go +++ b/table/types/types.go @@ -5,34 +5,34 @@ import ( "io" "time" - "github.com/ydb-platform/ydb-go-sdk/v3/internal/value" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/types" ) // 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.TypesEqual(lhs, rhs) } func List(t Type) Type { - return value.List(t) + return types.List(t) } func Tuple(elems ...Type) Type { - return value.Tuple(elems...) + return types.Tuple(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, }) @@ -47,11 +47,11 @@ func Struct(opts ...StructOption) Type { } } - return value.Struct(s.fields...) + return types.Struct(s.fields...) } func Dict(k, v Type) Type { - return value.Dict(k, v) + return types.Dict(k, v) } func VariantStruct(opts ...StructOption) Type { @@ -62,61 +62,61 @@ func VariantStruct(opts ...StructOption) Type { } } - return value.VariantStruct(s.fields...) + return types.VariantStruct(s.fields...) } func VariantTuple(elems ...Type) Type { - return value.VariantTuple(elems...) + return types.VariantTuple(elems...) } func Void() Type { - return value.Void() + return types.Void() } func Optional(t Type) Type { - return value.Optional(t) + return types.Optional(t) } var DefaultDecimal = DecimalType(22, 9) func DecimalType(precision, scale uint32) Type { - return value.Decimal(precision, scale) + return types.Decimal(precision, scale) } func DecimalTypeFromDecimal(d *Decimal) Type { - return value.Decimal(d.Precision, d.Scale) + return types.Decimal(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.TypeUnknown + TypeBool = types.TypeBool + TypeInt8 = types.TypeInt8 + TypeUint8 = types.TypeUint8 + TypeInt16 = types.TypeInt16 + TypeUint16 = types.TypeUint16 + TypeInt32 = types.TypeInt32 + TypeUint32 = types.TypeUint32 + TypeInt64 = types.TypeInt64 + TypeUint64 = types.TypeUint64 + TypeFloat = types.TypeFloat + TypeDouble = types.TypeDouble + TypeDate = types.TypeDate + TypeDatetime = types.TypeDatetime + TypeTimestamp = types.TypeTimestamp + TypeInterval = types.TypeInterval + TypeTzDate = types.TypeTzDate + TypeTzDatetime = types.TypeTzDatetime + TypeTzTimestamp = types.TypeTzTimestamp + TypeString = types.TypeBytes + TypeBytes = types.TypeBytes + TypeUTF8 = types.TypeText + TypeText = types.TypeText + TypeYSON = types.TypeYSON + TypeJSON = types.TypeJSON + TypeUUID = types.TypeUUID + TypeJSONDocument = types.TypeJSONDocument + TypeDyNumber = types.TypeDyNumber ) // WriteTypeStringTo writes ydb type string representation into buffer diff --git a/tests/integration/query_execute_test.go b/tests/integration/query_execute_test.go new file mode 100644 index 000000000..9d3421e35 --- /dev/null +++ b/tests/integration/query_execute_test.go @@ -0,0 +1,53 @@ +//go:build integration +// +build integration + +package integration + +import ( + "context" + "errors" + "os" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ydb-platform/ydb-go-sdk/v3" + internalQuery "github.com/ydb-platform/ydb-go-sdk/v3/internal/query" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/version" + "github.com/ydb-platform/ydb-go-sdk/v3/query" +) + +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(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) + err = db.Query().Do(ctx, func(ctx context.Context, s query.Session) (err error) { + _, res, err := s.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 + } + err = row.Scan(nil) + if err != nil && !errors.Is(err, internalQuery.ErrNotImplemented) { + return err + } + 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..cd281c328 --- /dev/null +++ b/tests/integration/query_tx_execute_test.go @@ -0,0 +1,53 @@ +//go:build integration +// +build integration + +package integration + +import ( + "context" + "errors" + "os" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ydb-platform/ydb-go-sdk/v3" + internalQuery "github.com/ydb-platform/ydb-go-sdk/v3/internal/query" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/version" + "github.com/ydb-platform/ydb-go-sdk/v3/query" +) + +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")), + ) + 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") + 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(nil) + if err != nil && !errors.Is(err, internalQuery.ErrNotImplemented) { + return err + } + return res.Err() + }, query.WithIdempotent()) + require.NoError(t, err) +} diff --git a/testutil/compare_test.go b/testutil/compare_test.go index 5041b1b74..6a78887dc 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) { @@ -47,7 +47,7 @@ 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.TypeText) val := unwrapTypedValue(value.ToYDB(v, a)) typeID := val.Type.GetTypeId() if typeID != Ydb.Type_UTF8 { @@ -60,8 +60,8 @@ func TestUnwrapNullValue(t *testing.T) { } 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.TypeText) + 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.TypeText)) + 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/trace/query.go b/trace/query.go new file mode 100644 index 000000000..c76a2d28a --- /dev/null +++ b/trace/query.go @@ -0,0 +1,11 @@ +package trace + +// tool gtrace used from ./internal/cmd/gtrace + +//go:generate gtrace + +type ( + // Query specified trace of retry call activity. + // gtrace:gen + Query struct{} +) From 37e9d8b50613d9c2a570deb1a0babecdb48b92db Mon Sep 17 00:00:00 2001 From: Aleksey Myasnikov Date: Wed, 28 Feb 2024 14:02:53 +0300 Subject: [PATCH 2/3] fixes (linters, query parameters) --- internal/allocator/allocator.go | 45 ++++++--- internal/query/client.go | 33 +++++-- internal/query/client_test.go | 12 ++- internal/query/column.go | 5 +- internal/query/config/config.go | 1 + internal/query/errors.go | 13 ++- internal/query/execute_query.go | 13 ++- internal/query/execute_query_test.go | 9 +- internal/query/pool.go | 18 ++-- internal/query/result.go | 13 ++- internal/query/result_set.go | 12 ++- internal/query/result_set_test.go | 33 ++++--- internal/query/row.go | 1 + internal/query/session.go | 10 +- internal/query/transaction.go | 12 ++- internal/query/transaction_test.go | 51 ++++++---- options.go | 9 ++ query/column.go | 4 +- query/do_options.go | 1 + query/do_tx_options.go | 1 + query/example_test.go | 38 +++++++- query/execute_options.go | 69 ++++++++------ query/named.go | 1 + query/named_test.go | 2 + query/parameters.go | 135 --------------------------- query/transaction_control.go | 22 +++-- query/transaction_settings.go | 21 +++-- query/type.go | 7 -- query/value.go | 65 ------------- 29 files changed, 305 insertions(+), 351 deletions(-) delete mode 100644 query/parameters.go delete mode 100644 query/type.go delete mode 100644 query/value.go diff --git a/internal/allocator/allocator.go b/internal/allocator/allocator.go index 31fae60ef..378e8d244 100644 --- a/internal/allocator/allocator.go +++ b/internal/allocator/allocator.go @@ -57,7 +57,7 @@ type ( queryQueryContentAllocator queryTransactionControlAllocator queryTransactionControlBeginTxAllocator - queryTransactionControlTxIdAllocator + queryTransactionControlTxIDAllocator queryTransactionSettingsAllocator queryTransactionSettingsSerializableReadWriteAllocator } @@ -115,7 +115,7 @@ func (a *Allocator) Free() { a.queryQueryContentAllocator.free() a.queryTransactionControlAllocator.free() a.queryTransactionControlBeginTxAllocator.free() - a.queryTransactionControlTxIdAllocator.free() + a.queryTransactionControlTxIDAllocator.free() a.queryTransactionSettingsAllocator.free() a.queryTransactionSettingsSerializableReadWriteAllocator.free() @@ -925,9 +925,12 @@ type queryExecuteQueryRequestAllocator struct { allocations []*Ydb_Query.ExecuteQueryRequest } -func (a *queryExecuteQueryRequestAllocator) QueryExecuteQueryRequest() (v *Ydb_Query.ExecuteQueryRequest) { +func (a *queryExecuteQueryRequestAllocator) QueryExecuteQueryRequest() ( + v *Ydb_Query.ExecuteQueryRequest, +) { v = queryExecuteQueryRequestPool.Get() a.allocations = append(a.allocations, v) + return v } @@ -943,9 +946,12 @@ type queryExecuteQueryResponsePartAllocator struct { allocations []*Ydb_Query.ExecuteQueryResponsePart } -func (a *queryExecuteQueryResponsePartAllocator) QueryExecuteQueryResponsePart() (v *Ydb_Query.ExecuteQueryResponsePart) { +func (a *queryExecuteQueryResponsePartAllocator) QueryExecuteQueryResponsePart() ( + v *Ydb_Query.ExecuteQueryResponsePart, +) { v = queryExecuteQueryResponsePartPool.Get() a.allocations = append(a.allocations, v) + return v } @@ -961,9 +967,12 @@ type queryExecuteQueryRequestQueryContentAllocator struct { allocations []*Ydb_Query.ExecuteQueryRequest_QueryContent } -func (a *queryExecuteQueryRequestQueryContentAllocator) QueryExecuteQueryRequestQueryContent() (v *Ydb_Query.ExecuteQueryRequest_QueryContent) { +func (a *queryExecuteQueryRequestQueryContentAllocator) QueryExecuteQueryRequestQueryContent() ( + v *Ydb_Query.ExecuteQueryRequest_QueryContent, +) { v = queryExecuteQueryRequestQueryContentPool.Get() a.allocations = append(a.allocations, v) + return v } @@ -981,6 +990,7 @@ type queryTransactionControlAllocator struct { func (a *queryTransactionControlAllocator) QueryTransactionControl() (v *Ydb_Query.TransactionControl) { v = queryTransactionControlPool.Get() a.allocations = append(a.allocations, v) + return v } @@ -996,9 +1006,12 @@ type queryTransactionControlBeginTxAllocator struct { allocations []*Ydb_Query.TransactionControl_BeginTx } -func (a *queryTransactionControlBeginTxAllocator) QueryTransactionControlBeginTx() (v *Ydb_Query.TransactionControl_BeginTx) { +func (a *queryTransactionControlBeginTxAllocator) QueryTransactionControlBeginTx() ( + v *Ydb_Query.TransactionControl_BeginTx, +) { v = queryTransactionControlBeginTxPool.Get() a.allocations = append(a.allocations, v) + return v } @@ -1009,19 +1022,20 @@ func (a *queryTransactionControlBeginTxAllocator) free() { a.allocations = a.allocations[:0] } -type queryTransactionControlTxIdAllocator struct { +type queryTransactionControlTxIDAllocator struct { allocations []*Ydb_Query.TransactionControl_TxId } -func (a *queryTransactionControlTxIdAllocator) QueryTransactionControlTxId() (v *Ydb_Query.TransactionControl_TxId) { - v = queryTransactionControlTxIdPool.Get() +func (a *queryTransactionControlTxIDAllocator) QueryTransactionControlTxID() (v *Ydb_Query.TransactionControl_TxId) { + v = queryTransactionControlTxIDPool.Get() a.allocations = append(a.allocations, v) + return v } -func (a *queryTransactionControlTxIdAllocator) free() { +func (a *queryTransactionControlTxIDAllocator) free() { for _, v := range a.allocations { - queryTransactionControlTxIdPool.Put(v) + queryTransactionControlTxIDPool.Put(v) } a.allocations = a.allocations[:0] } @@ -1033,6 +1047,7 @@ type queryTransactionSettingsAllocator struct { func (a *queryTransactionSettingsAllocator) QueryTransactionSettings() (v *Ydb_Query.TransactionSettings) { v = queryTransactionSettingsPool.Get() a.allocations = append(a.allocations, v) + return v } @@ -1048,9 +1063,12 @@ type queryTransactionSettingsSerializableReadWriteAllocator struct { allocations []*Ydb_Query.TransactionSettings_SerializableReadWrite } -func (a *queryTransactionSettingsSerializableReadWriteAllocator) QueryTransactionSettingsSerializableReadWrite() (v *Ydb_Query.TransactionSettings_SerializableReadWrite) { +func (a *queryTransactionSettingsSerializableReadWriteAllocator) QueryTransactionSettingsSerializableReadWrite() ( + v *Ydb_Query.TransactionSettings_SerializableReadWrite, +) { v = queryTransactionSettingsSerializableReadWritePool.Get() a.allocations = append(a.allocations, v) + return v } @@ -1068,6 +1086,7 @@ type queryQueryContentAllocator struct { func (a *queryQueryContentAllocator) QueryQueryContent() (v *Ydb_Query.QueryContent) { v = queryQueryContentPool.Get() a.allocations = append(a.allocations, v) + return v } @@ -1144,7 +1163,7 @@ var ( queryQueryContentPool Pool[Ydb_Query.QueryContent] queryTransactionControlPool Pool[Ydb_Query.TransactionControl] queryTransactionControlBeginTxPool Pool[Ydb_Query.TransactionControl_BeginTx] - queryTransactionControlTxIdPool Pool[Ydb_Query.TransactionControl_TxId] + queryTransactionControlTxIDPool Pool[Ydb_Query.TransactionControl_TxId] queryTransactionSettingsPool Pool[Ydb_Query.TransactionSettings] queryTransactionSettingsSerializableReadWritePool Pool[Ydb_Query.TransactionSettings_SerializableReadWrite] ) diff --git a/internal/query/client.go b/internal/query/client.go index 6eaa0bc1c..da29fa186 100644 --- a/internal/query/client.go +++ b/internal/query/client.go @@ -36,21 +36,24 @@ func (c Client) Close(ctx context.Context) error { if err != nil { return xerrors.WithStackTrace(err) } + return nil } -func do(ctx context.Context, pool Pool, op query.Operation, opts query.DoOptions) error { +func do(ctx context.Context, pool Pool, op query.Operation, opts *query.DoOptions) error { return retry.Retry(ctx, func(ctx context.Context) error { err := pool.With(ctx, func(ctx context.Context, s *Session) error { err := op(ctx, s) if err != nil { return xerrors.WithStackTrace(err) } + return nil }) if err != nil { return xerrors.WithStackTrace(err) } + return nil }, opts.RetryOptions...) } @@ -63,10 +66,11 @@ func (c Client) Do(ctx context.Context, op query.Operation, opts ...query.DoOpti if doOptions.Idempotent { doOptions.RetryOptions = append(doOptions.RetryOptions, retry.WithIdempotent(doOptions.Idempotent)) } - return do(ctx, c.pool, op, doOptions) + + return do(ctx, c.pool, op, &doOptions) } -func doTx(ctx context.Context, pool Pool, op query.TxOperation, opts query.DoTxOptions) error { +func doTx(ctx context.Context, pool Pool, op query.TxOperation, opts *query.DoTxOptions) error { return do(ctx, pool, func(ctx context.Context, s query.Session) error { tx, err := s.Begin(ctx, opts.TxSettings) if err != nil { @@ -78,6 +82,7 @@ func doTx(ctx context.Context, pool Pool, op query.TxOperation, opts query.DoTxO if errRollback != nil { return xerrors.WithStackTrace(xerrors.Join(err, errRollback)) } + return xerrors.WithStackTrace(err) } err = tx.CommitTx(ctx) @@ -86,10 +91,12 @@ func doTx(ctx context.Context, pool Pool, op query.TxOperation, opts query.DoTxO if errRollback != nil { return xerrors.WithStackTrace(xerrors.Join(err, errRollback)) } + return xerrors.WithStackTrace(err) } + return nil - }, opts.DoOptions) + }, &opts.DoOptions) } func (c Client) DoTx(ctx context.Context, op query.TxOperation, opts ...query.DoTxOption) error { @@ -100,7 +107,8 @@ func (c Client) DoTx(ctx context.Context, op query.TxOperation, opts ...query.Do if doTxOptions.Idempotent { doTxOptions.RetryOptions = append(doTxOptions.RetryOptions, retry.WithIdempotent(doTxOptions.Idempotent)) } - return doTx(ctx, c.pool, op, doTxOptions) + + return doTx(ctx, c.pool, op, &doTxOptions) } func deleteSession(ctx context.Context, client Ydb_Query_V1.QueryServiceClient, sessionID string) error { @@ -115,6 +123,7 @@ func deleteSession(ctx context.Context, client Ydb_Query_V1.QueryServiceClient, if response.GetStatus() != Ydb.StatusIds_SUCCESS { return xerrors.WithStackTrace(xerrors.FromOperation(response)) } + return nil } @@ -124,7 +133,9 @@ type createSessionSettings struct { onAttach func(id string) } -func createSession(ctx context.Context, client Ydb_Query_V1.QueryServiceClient, settings createSessionSettings) (_ *Session, finalErr error) { +func createSession( + ctx context.Context, client Ydb_Query_V1.QueryServiceClient, settings createSessionSettings, +) (_ *Session, finalErr error) { var ( createSessionCtx context.Context cancelCreateSession context.CancelFunc @@ -148,7 +159,7 @@ func createSession(ctx context.Context, client Ydb_Query_V1.QueryServiceClient, } defer func() { if finalErr != nil { - deleteSession(ctx, client, s.GetSessionId()) + _ = deleteSession(ctx, client, s.GetSessionId()) } }() attachCtx, cancelAttach := xcontext.WithCancel(context.Background()) @@ -167,7 +178,7 @@ func createSession(ctx context.Context, client Ydb_Query_V1.QueryServiceClient, } defer func() { if finalErr != nil { - attach.CloseSend() + _ = attach.CloseSend() } }() state, err := attach.Recv() @@ -190,7 +201,7 @@ func createSession(ctx context.Context, client Ydb_Query_V1.QueryServiceClient, if settings.onDetach != nil { settings.onDetach(session.id) } - attach.CloseSend() + _ = attach.CloseSend() cancelAttach() atomic.StoreUint32( (*uint32)(&session.status), @@ -218,10 +229,10 @@ func createSession(ctx context.Context, client Ydb_Query_V1.QueryServiceClient, func New(ctx context.Context, balancer balancer, config *config.Config) (*Client, error) { grpcClient := Ydb_Query_V1.NewQueryServiceClient(balancer) + return &Client{ grpcClient: grpcClient, pool: newStubPool( - //config.PoolMaxSize(), func(ctx context.Context) (_ *Session, err error) { s, err := createSession(ctx, grpcClient, createSessionSettings{ createSessionTimeout: config.CreateSessionTimeout(), @@ -229,6 +240,7 @@ func New(ctx context.Context, balancer balancer, config *config.Config) (*Client if err != nil { return nil, xerrors.WithStackTrace(err) } + return s, nil }, func(ctx context.Context, s *Session) error { @@ -236,6 +248,7 @@ func New(ctx context.Context, balancer balancer, config *config.Config) (*Client if err != nil { return xerrors.WithStackTrace(err) } + return nil }, ), diff --git a/internal/query/client_test.go b/internal/query/client_test.go index 9b154317f..37976659a 100644 --- a/internal/query/client_test.go +++ b/internal/query/client_test.go @@ -36,7 +36,7 @@ func TestCreateSession(t *testing.T) { }, nil) service.EXPECT().AttachSession(gomock.Any(), gomock.Any()).Return(attachStream, nil) t.Log("execute") - var attached = 0 + attached := 0 s, err := createSession(ctx, service, createSessionSettings{ onAttach: func(id string) { attached++ @@ -176,7 +176,7 @@ func TestDo(t *testing.T) { return newTestSession() }), func(ctx context.Context, s query.Session) error { return nil - }, query.DoOptions{}) + }, &query.DoOptions{}) require.NoError(t, err) }) t.Run("RetryableError", func(t *testing.T) { @@ -188,8 +188,9 @@ func TestDo(t *testing.T) { if counter < 10 { return xerrors.Retryable(errors.New("")) } + return nil - }, query.DoOptions{}) + }, &query.DoOptions{}) require.NoError(t, err) require.Equal(t, 10, counter) }) @@ -210,7 +211,7 @@ func TestDoTx(t *testing.T) { return newTestSessionWithClient(client) }), func(ctx context.Context, tx query.TxActor) error { return nil - }, query.DoTxOptions{}) + }, &query.DoTxOptions{}) require.NoError(t, err) }) t.Run("RetryableError", func(t *testing.T) { @@ -233,8 +234,9 @@ func TestDoTx(t *testing.T) { if counter < 10 { return xerrors.Retryable(errors.New("")) } + return nil - }, query.DoTxOptions{}) + }, &query.DoTxOptions{}) require.NoError(t, err) require.Equal(t, 10, counter) }) diff --git a/internal/query/column.go b/internal/query/column.go index f320e06a5..4532b24ad 100644 --- a/internal/query/column.go +++ b/internal/query/column.go @@ -11,7 +11,7 @@ var _ query.Column = (*column)(nil) type column struct { n string - t query.Type + t types.Type } func newColumn(c *Ydb.Column) *column { @@ -26,6 +26,7 @@ func newColumns(cc []*Ydb.Column) (columns []query.Column) { for i := range cc { columns[i] = newColumn(cc[i]) } + return columns } @@ -33,6 +34,6 @@ func (c *column) Name() string { return c.n } -func (c *column) Type() query.Type { +func (c *column) Type() types.Type { return c.t } diff --git a/internal/query/config/config.go b/internal/query/config/config.go index 06ab44f81..8163d519e 100644 --- a/internal/query/config/config.go +++ b/internal/query/config/config.go @@ -35,6 +35,7 @@ func New(opts ...Option) *Config { o(c) } } + return c } diff --git a/internal/query/errors.go b/internal/query/errors.go index ffb9b5272..7b866668d 100644 --- a/internal/query/errors.go +++ b/internal/query/errors.go @@ -5,11 +5,10 @@ import ( ) var ( - ErrNotImplemented = errors.New("not implemented yet") - errRedundantCallNextResultSet = errors.New("redundant call NextResultSet()") - errWrongNextResultSetIndex = errors.New("wrong result set index") - errInterruptedStream = errors.New("interrupted stream") - errClosedResult = errors.New("result closed early") - errWrongResultSetIndex = errors.New("critical violation of the logic - wrong result set index") - errWrongArgumentsCount = errors.New("wrong arguments count") + ErrNotImplemented = errors.New("not implemented yet") + errWrongNextResultSetIndex = errors.New("wrong result set index") + errInterruptedStream = errors.New("interrupted stream") + errClosedResult = errors.New("result closed early") + errWrongResultSetIndex = errors.New("critical violation of the logic - wrong result set index") + errWrongArgumentsCount = errors.New("wrong arguments count") ) diff --git a/internal/query/execute_query.go b/internal/query/execute_query.go index e9bd8406f..bb9d8b1f1 100644 --- a/internal/query/execute_query.go +++ b/internal/query/execute_query.go @@ -8,6 +8,7 @@ import ( "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/xcontext" "github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors" "github.com/ydb-platform/ydb-go-sdk/v3/query" @@ -18,11 +19,11 @@ type executeSettings interface { StatsMode() query.StatsMode TxControl() *query.TransactionControl Syntax() query.Syntax - Params() *query.Parameters + Params() *params.Parameters CallOptions() []grpc.CallOption } -func executeQueryRequest(a *allocator.Allocator, sessionID string, q string, settings executeSettings) ( +func executeQueryRequest(a *allocator.Allocator, sessionID, q string, settings executeSettings) ( *Ydb_Query.ExecuteQueryRequest, []grpc.CallOption, ) { @@ -39,11 +40,14 @@ func executeQueryRequest(a *allocator.Allocator, sessionID string, q string, set return request, settings.CallOptions() } -func queryFromText(a *allocator.Allocator, q string, syntax Ydb_Query.Syntax) *Ydb_Query.ExecuteQueryRequest_QueryContent { +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 } @@ -61,13 +65,16 @@ func execute( stream, err := client.ExecuteQuery(ctx, request, callOptions...) if err != nil { cancel() + return nil, nil, xerrors.WithStackTrace(err) } r, txID, err := newResult(ctx, stream, cancel) if err != nil { cancel() + return nil, nil, xerrors.WithStackTrace(err) } + return &transaction{ id: txID, s: session, diff --git a/internal/query/execute_query_test.go b/internal/query/execute_query_test.go index c68c3739e..1803b0b5b 100644 --- a/internal/query/execute_query_test.go +++ b/internal/query/execute_query_test.go @@ -15,6 +15,7 @@ import ( 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/xerrors" "github.com/ydb-platform/ydb-go-sdk/v3/internal/xtest" "github.com/ydb-platform/ydb-go-sdk/v3/query" @@ -792,9 +793,11 @@ func TestExecuteQueryRequest(t *testing.T) { name: "WithParams", opts: []query.ExecuteOption{ query.WithParameters( - query.Param("$a", query.TextValue("A")), - query.Param("$b", query.TextValue("B")), - query.Param("$c", query.TextValue("C")), + params.Builder{}. + Param("$a").Text("A"). + Param("$b").Text("B"). + Param("$c").Text("C"). + Build(), ), }, request: &Ydb_Query.ExecuteQueryRequest{ diff --git a/internal/query/pool.go b/internal/query/pool.go index 3753c5ead..0e633c408 100644 --- a/internal/query/pool.go +++ b/internal/query/pool.go @@ -16,17 +16,17 @@ type Pool interface { var _ Pool = (*stubPool)(nil) type stubPool struct { - create func(ctx context.Context) (*Session, error) - close func(ctx context.Context, s *Session) error + createFunc func(ctx context.Context) (*Session, error) + closeFunc func(ctx context.Context, s *Session) error } func newStubPool( - create func(ctx context.Context) (*Session, error), - close func(ctx context.Context, s *Session) error, + createFunc func(ctx context.Context) (*Session, error), + closeFunc func(ctx context.Context, s *Session) error, ) *stubPool { return &stubPool{ - create: create, - close: close, + createFunc: createFunc, + closeFunc: closeFunc, } } @@ -39,16 +39,17 @@ func (pool *stubPool) get(ctx context.Context) (*Session, error) { case <-ctx.Done(): return nil, xerrors.WithStackTrace(ctx.Err()) default: - s, err := pool.create(ctx) + s, err := pool.createFunc(ctx) if err != nil { return nil, xerrors.WithStackTrace(err) } + return s, nil } } func (pool *stubPool) put(ctx context.Context, s *Session) { - pool.close(ctx, s) + _ = pool.closeFunc(ctx, s) } func (pool *stubPool) With(ctx context.Context, f func(ctx context.Context, s *Session) error) error { @@ -63,5 +64,6 @@ func (pool *stubPool) With(ctx context.Context, f func(ctx context.Context, s *S if err != nil { return xerrors.WithStackTrace(err) } + return nil } diff --git a/internal/query/result.go b/internal/query/result.go index 75cd8f888..0af810e3d 100644 --- a/internal/query/result.go +++ b/internal/query/result.go @@ -56,6 +56,7 @@ func newResult( r.interrupt() close(r.closed) }) + return &r, part.GetTxMeta().GetId(), nil } } @@ -66,6 +67,7 @@ func nextPart(stream Ydb_Query_V1.QueryService_ExecuteQueryClient) (*Ydb_Query.E if xerrors.Is(err, io.EOF) { return nil, xerrors.WithStackTrace(err) } + return nil, xerrors.WithStackTrace(xerrors.Transport(err)) } if status := part.GetStatus(); status != Ydb.StatusIds_SUCCESS { @@ -73,11 +75,13 @@ func nextPart(stream Ydb_Query_V1.QueryService_ExecuteQueryClient) (*Ydb_Query.E xerrors.FromOperation(part), ) } + return part, nil } func (r *result) Close(ctx context.Context) error { r.close() + return nil } @@ -101,8 +105,9 @@ func (r *result) nextResultSet(ctx context.Context) (_ *resultSet, err error) { case <-r.interrupted: return nil, xerrors.WithStackTrace(errInterruptedStream) default: - if r.lastPart.GetResultSetIndex() >= nextResultSetIndex { - r.resultSetIndex = r.lastPart.GetResultSetIndex() + 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, @@ -122,15 +127,17 @@ func (r *result) nextResultSet(ctx context.Context) (_ *resultSet, err error) { if xerrors.Is(err, io.EOF) { r.close() } + return nil, xerrors.WithStackTrace(err) } r.lastPart = part if part.GetResultSetIndex() > nextResultSetIndex { return nil, xerrors.WithStackTrace(fmt.Errorf( - "result set (index=%d) recieve part (index=%d) for next result set: %w", + "result set (index=%d) receive part (index=%d) for next result set: %w", nextResultSetIndex, part.GetResultSetIndex(), io.EOF, )) } + return part, nil } }, r.lastPart), nil diff --git a/internal/query/result_set.go b/internal/query/result_set.go index f4f62bb5e..aeb1d6db6 100644 --- a/internal/query/result_set.go +++ b/internal/query/result_set.go @@ -22,9 +22,14 @@ type resultSet struct { done chan struct{} } -func newResultSet(recv func() (*Ydb_Query.ExecuteQueryResponsePart, error), part *Ydb_Query.ExecuteQueryResponsePart) *resultSet { +func newResultSet( + recv func() ( + *Ydb_Query.ExecuteQueryResponsePart, error, + ), + part *Ydb_Query.ExecuteQueryResponsePart, +) *resultSet { return &resultSet{ - index: part.ResultSetIndex, + index: part.GetResultSetIndex(), recv: recv, currentPart: part, rowIndex: -1, @@ -47,6 +52,7 @@ func (rs *resultSet) next(ctx context.Context) (*row, error) { if xerrors.Is(err, io.EOF) { close(rs.done) } + return nil, xerrors.WithStackTrace(err) } rs.rowIndex = 0 @@ -54,11 +60,13 @@ func (rs *resultSet) next(ctx context.Context) (*row, error) { } 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(rs.columns, rs.currentPart.GetResultSet().GetRows()[rs.rowIndex]) } } diff --git a/internal/query/result_set_test.go b/internal/query/result_set_test.go index 4be49016b..a5d090310 100644 --- a/internal/query/result_set_test.go +++ b/internal/query/result_set_test.go @@ -112,15 +112,16 @@ func TestResultSetNext(t *testing.T) { }, }, nil) stream.EXPECT().Recv().Return(nil, io.EOF) - part, err := stream.Recv() + 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 - }, part) + }, recv) require.EqualValues(t, 0, rs.index) { _, err := rs.next(ctx) @@ -215,15 +216,16 @@ func TestResultSetNext(t *testing.T) { }, }, }, nil) - part, err := stream.Recv() + 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 - }, part) + }, recv) require.EqualValues(t, 0, rs.index) { _, err := rs.next(ctx) @@ -303,21 +305,22 @@ func TestResultSetNext(t *testing.T) { Status: Ydb.StatusIds_OVERLOADED, ResultSetIndex: 0, }, nil) - part, err := stream.Recv() + recv, err := stream.Recv() require.NoError(t, err) rs := newResultSet(func() (*Ydb_Query.ExecuteQueryResponsePart, error) { part, err := nextPart(stream) if err != nil { return nil, xerrors.WithStackTrace(err) } - if part.ResultSetIndex != 0 { + if resultSetIndex := part.GetResultSetIndex(); resultSetIndex != 0 { return nil, xerrors.WithStackTrace(fmt.Errorf( "critical violation of the logic: wrong result set index: %d != %d", - part.ResultSetIndex, 0, + resultSetIndex, 0, )) } + return part, nil - }, part) + }, recv) require.EqualValues(t, 0, rs.index) { _, err := rs.next(ctx) @@ -403,21 +406,22 @@ func TestResultSetNext(t *testing.T) { }, }, nil) stream.EXPECT().Recv().Return(nil, grpcStatus.Error(grpcCodes.Unavailable, "")) - part, err := stream.Recv() + recv, err := stream.Recv() require.NoError(t, err) rs := newResultSet(func() (*Ydb_Query.ExecuteQueryResponsePart, error) { part, err := nextPart(stream) if err != nil { return nil, xerrors.WithStackTrace(err) } - if part.ResultSetIndex != 0 { + if resultSetIndex := part.GetResultSetIndex(); resultSetIndex != 0 { return nil, xerrors.WithStackTrace(fmt.Errorf( "critical violation of the logic: wrong result set index: %d != %d", - part.ResultSetIndex, 0, + resultSetIndex, 0, )) } + return part, nil - }, part) + }, recv) require.EqualValues(t, 0, rs.index) { _, err := rs.next(ctx) @@ -561,15 +565,16 @@ func TestResultSetNext(t *testing.T) { }, }, }, nil) - part, err := stream.Recv() + recv, err := stream.Recv() require.NoError(t, err) rs := newResultSet(func() (*Ydb_Query.ExecuteQueryResponsePart, error) { part, err := nextPart(stream) if err != nil { return nil, xerrors.WithStackTrace(err) } + return part, nil - }, part) + }, recv) require.EqualValues(t, 0, rs.index) { _, err := rs.next(ctx) diff --git a/internal/query/row.go b/internal/query/row.go index 7f3e347b8..cd456bcf6 100644 --- a/internal/query/row.go +++ b/internal/query/row.go @@ -16,6 +16,7 @@ type row struct { func newRow(columns []query.Column, v *Ydb.Value) (*row, error) { data := newScannerData(columns, v.GetItems()) + return &row{ newScannerIndexed(data), newScannerNamed(data), diff --git a/internal/query/session.go b/internal/query/session.go index 02bdbbd6b..130d4d5f0 100644 --- a/internal/query/session.go +++ b/internal/query/session.go @@ -25,10 +25,13 @@ type Session struct { func (s *Session) Close(ctx context.Context) error { s.close() + return nil } -func begin(ctx context.Context, client Ydb_Query_V1.QueryServiceClient, sessionID string, txSettings query.TransactionSettings) (*transaction, error) { +func begin( + ctx context.Context, client Ydb_Query_V1.QueryServiceClient, sessionID string, txSettings query.TransactionSettings, +) (*transaction, error) { a := allocator.New() defer a.Free() response, err := client.BeginTransaction(ctx, @@ -43,6 +46,7 @@ func begin(ctx context.Context, client Ydb_Query_V1.QueryServiceClient, sessionI if response.GetStatus() != Ydb.StatusIds_SUCCESS { return nil, xerrors.WithStackTrace(xerrors.FromOperation(response)) } + return &transaction{ id: response.GetTxMeta().GetId(), }, nil @@ -54,6 +58,7 @@ func (s *Session) Begin(ctx context.Context, txSettings query.TransactionSetting return nil, xerrors.WithStackTrace(err) } tx.s = s + return tx, nil } @@ -66,8 +71,7 @@ func (s *Session) NodeID() int64 { } func (s *Session) Status() query.SessionStatus { - status := query.SessionStatus(atomic.LoadUint32((*uint32)(&s.status))) - return status + return query.SessionStatus(atomic.LoadUint32((*uint32)(&s.status))) } func (s *Session) Execute( diff --git a/internal/query/transaction.go b/internal/query/transaction.go index b971261f2..5984ecaca 100644 --- a/internal/query/transaction.go +++ b/internal/query/transaction.go @@ -30,18 +30,22 @@ func fromTxOptions(txID string, txOpts ...query.TxExecuteOption) executeSettings } } opts = append(opts, query.WithTxControl(query.TxControl(query.WithTxID(txID)))) + return query.ExecuteSettings(opts...) } -func (tx transaction) Execute(ctx context.Context, q string, opts ...query.TxExecuteOption) (r query.Result, err error) { +func (tx transaction) Execute(ctx context.Context, q string, opts ...query.TxExecuteOption) ( + r query.Result, err error, +) { _, res, err := execute(ctx, tx.s, tx.s.queryClient, q, fromTxOptions(tx.id, opts...)) if err != nil { return nil, xerrors.WithStackTrace(err) } + return res, nil } -func commitTx(ctx context.Context, client Ydb_Query_V1.QueryServiceClient, sessionID string, txID string) error { +func commitTx(ctx context.Context, client Ydb_Query_V1.QueryServiceClient, sessionID, txID string) error { response, err := client.CommitTransaction(ctx, &Ydb_Query.CommitTransactionRequest{ SessionId: sessionID, TxId: txID, @@ -52,6 +56,7 @@ func commitTx(ctx context.Context, client Ydb_Query_V1.QueryServiceClient, sessi if response.GetStatus() != Ydb.StatusIds_SUCCESS { return xerrors.WithStackTrace(xerrors.FromOperation(response)) } + return nil } @@ -59,7 +64,7 @@ func (tx transaction) CommitTx(ctx context.Context) (err error) { return commitTx(ctx, tx.s.queryClient, tx.s.id, tx.id) } -func rollback(ctx context.Context, client Ydb_Query_V1.QueryServiceClient, sessionID string, txID string) error { +func rollback(ctx context.Context, client Ydb_Query_V1.QueryServiceClient, sessionID, txID string) error { response, err := client.RollbackTransaction(ctx, &Ydb_Query.RollbackTransactionRequest{ SessionId: sessionID, TxId: txID, @@ -70,6 +75,7 @@ func rollback(ctx context.Context, client Ydb_Query_V1.QueryServiceClient, sessi if response.GetStatus() != Ydb.StatusIds_SUCCESS { return xerrors.WithStackTrace(xerrors.FromOperation(response)) } + return nil } diff --git a/internal/query/transaction_test.go b/internal/query/transaction_test.go index 09c77021d..f9fc9c73c 100644 --- a/internal/query/transaction_test.go +++ b/internal/query/transaction_test.go @@ -12,6 +12,7 @@ import ( 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/xerrors" "github.com/ydb-platform/ydb-go-sdk/v3/internal/xtest" "github.com/ydb-platform/ydb-go-sdk/v3/query" @@ -22,9 +23,11 @@ func TestCommitTx(t *testing.T) { ctx := xtest.Context(t) ctrl := gomock.NewController(t) service := NewMockQueryServiceClient(ctrl) - service.EXPECT().CommitTransaction(gomock.Any(), gomock.Any()).Return(&Ydb_Query.CommitTransactionResponse{ - Status: Ydb.StatusIds_SUCCESS, - }, nil) + 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) @@ -33,7 +36,9 @@ func TestCommitTx(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, "")) + 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) @@ -43,9 +48,11 @@ func TestCommitTx(t *testing.T) { ctx := xtest.Context(t) ctrl := gomock.NewController(t) service := NewMockQueryServiceClient(ctrl) - service.EXPECT().CommitTransaction(gomock.Any(), gomock.Any()).Return(&Ydb_Query.CommitTransactionResponse{ - Status: Ydb.StatusIds_UNAVAILABLE, - }, nil) + service.EXPECT().CommitTransaction(gomock.Any(), gomock.Any()).Return( + &Ydb_Query.CommitTransactionResponse{ + Status: Ydb.StatusIds_UNAVAILABLE, + }, nil, + ) t.Log("commit") err := commitTx(ctx, service, "123", "456") require.Error(t, err) @@ -58,9 +65,11 @@ func TestRollback(t *testing.T) { ctx := xtest.Context(t) ctrl := gomock.NewController(t) service := NewMockQueryServiceClient(ctrl) - service.EXPECT().RollbackTransaction(gomock.Any(), gomock.Any()).Return(&Ydb_Query.RollbackTransactionResponse{ - Status: Ydb.StatusIds_SUCCESS, - }, nil) + 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) @@ -69,7 +78,9 @@ func TestRollback(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, "")) + 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) @@ -79,9 +90,11 @@ func TestRollback(t *testing.T) { ctx := xtest.Context(t) ctrl := gomock.NewController(t) service := NewMockQueryServiceClient(ctrl) - service.EXPECT().RollbackTransaction(gomock.Any(), gomock.Any()).Return(&Ydb_Query.RollbackTransactionResponse{ - Status: Ydb.StatusIds_UNAVAILABLE, - }, nil) + service.EXPECT().RollbackTransaction(gomock.Any(), gomock.Any()).Return( + &Ydb_Query.RollbackTransactionResponse{ + Status: Ydb.StatusIds_UNAVAILABLE, + }, nil, + ) t.Log("rollback") err := rollback(ctx, service, "123", "456") require.Error(t, err) @@ -94,7 +107,7 @@ type testExecuteSettings struct { statsMode query.StatsMode txControl *query.TransactionControl syntax query.Syntax - params *query.Parameters + params *params.Parameters callOptions []grpc.CallOption } @@ -114,7 +127,7 @@ func (s testExecuteSettings) Syntax() query.Syntax { return s.syntax } -func (s testExecuteSettings) Params() *query.Parameters { +func (s testExecuteSettings) Params() *params.Parameters { return s.params } @@ -185,7 +198,7 @@ func TestFromTxOptions(t *testing.T) { name: "WithParams", txOpts: []query.TxExecuteOption{ query.WithParameters( - query.Param("$a", query.TextValue("A")), + params.Builder{}.Param("$a").Text("A").Build(), ), }, settings: testExecuteSettings{ @@ -193,9 +206,7 @@ func TestFromTxOptions(t *testing.T) { statsMode: query.StatsModeNone, txControl: query.TxControl(query.WithTxID("")), syntax: Ydb_Query.Syntax_SYNTAX_YQL_V1, - params: query.Params( - query.Param("$a", query.TextValue("A")), - ), + params: params.Builder{}.Param("$a").Text("A").Build(), }, }, } { diff --git a/options.go b/options.go index 6af346db3..6cd920d2f 100644 --- a/options.go +++ b/options.go @@ -353,6 +353,7 @@ func WithCertificatesFromPem(bytes []byte, opts ...certificates.FromPemOption) O for _, cert := range certs { _ = WithCertificate(cert)(ctx, c) } + return nil } } @@ -362,6 +363,7 @@ func WithCertificatesFromPem(bytes []byte, opts ...certificates.FromPemOption) O func WithTableConfigOption(option tableConfig.Option) Option { return func(ctx context.Context, c *Driver) error { c.tableOptions = append(c.tableOptions, option) + return nil } } @@ -371,6 +373,7 @@ func WithTableConfigOption(option tableConfig.Option) Option { func WithQueryConfigOption(option queryConfig.Option) Option { return func(ctx context.Context, c *Driver) error { c.queryOptions = append(c.queryOptions, option) + return nil } } @@ -380,6 +383,7 @@ 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.WithSizeLimit(sizeLimit)) + return nil } } @@ -414,6 +418,7 @@ func WithSessionPoolCreateSessionTimeout(createSessionTimeout time.Duration) Opt return func(ctx context.Context, c *Driver) error { c.tableOptions = append(c.tableOptions, tableConfig.WithCreateSessionTimeout(createSessionTimeout)) c.queryOptions = append(c.queryOptions, queryConfig.WithCreateSessionTimeout(createSessionTimeout)) + return nil } } @@ -423,6 +428,7 @@ 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.WithDeleteTimeout(deleteTimeout)) + return nil } } @@ -431,6 +437,7 @@ func WithSessionPoolDeleteTimeout(deleteTimeout time.Duration) Option { func WithIgnoreTruncated() Option { return func(ctx context.Context, c *Driver) error { c.tableOptions = append(c.tableOptions, tableConfig.WithIgnoreTruncated()) + return nil } } @@ -443,6 +450,7 @@ func WithPanicCallback(panicCallback func(e interface{})) Option { return func(ctx context.Context, c *Driver) error { c.panicCallback = panicCallback c.options = append(c.options, config.WithPanicCallback(panicCallback)) + return nil } } @@ -462,6 +470,7 @@ func WithTraceTable(t trace.Table, opts ...trace.TableComposeOption) Option { // )..., ), ) + return nil } } diff --git a/query/column.go b/query/column.go index 49defc291..d8c689aca 100644 --- a/query/column.go +++ b/query/column.go @@ -1,6 +1,8 @@ package query +import "github.com/ydb-platform/ydb-go-sdk/v3/table/types" + type Column interface { Name() string - Type() Type + Type() types.Type } diff --git a/query/do_options.go b/query/do_options.go index 36591176f..ee6429dbd 100644 --- a/query/do_options.go +++ b/query/do_options.go @@ -18,5 +18,6 @@ func NewDoOptions(opts ...DoOption) (doOptions DoOptions) { for _, opt := range opts { opt.applyDoOption(&doOptions) } + return doOptions } diff --git a/query/do_tx_options.go b/query/do_tx_options.go index 064de0794..f04fc3c0f 100644 --- a/query/do_tx_options.go +++ b/query/do_tx_options.go @@ -30,5 +30,6 @@ func NewDoTxOptions(opts ...DoTxOption) (doTxOptions DoTxOptions) { for _, opt := range opts { opt.applyDoTxOption(&doTxOptions) } + return doTxOptions } diff --git a/query/example_test.go b/query/example_test.go index 31a2af40c..8f926018f 100644 --- a/query/example_test.go +++ b/query/example_test.go @@ -2,7 +2,9 @@ package query_test import ( "context" + "errors" "fmt" + "io" "github.com/ydb-platform/ydb-go-sdk/v3" "github.com/ydb-platform/ydb-go-sdk/v3/query" @@ -13,6 +15,7 @@ func Example_selectWithoutParameters() { 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 @@ -33,11 +36,19 @@ func Example_selectWithoutParameters() { 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 { @@ -45,6 +56,7 @@ func Example_selectWithoutParameters() { } } } + return res.Err() // return finally result error for auto-retry with driver }, query.WithIdempotent(), @@ -60,6 +72,7 @@ func Example_selectWithParameters() { 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 @@ -73,8 +86,10 @@ func Example_selectWithParameters() { _, res, err := s.Execute(ctx, `SELECT CAST($id AS Uint64) AS id, CAST($myStr AS Text) AS myStr`, query.WithParameters( - query.Param("$id", query.Uint64Value(123)), - query.Param("$myStr", query.TextValue("test")), + ydb.ParamsBuilder(). + Param("$id").Uint64(123). + Param("$myStr").Text("123"). + Build(), ), ) if err != nil { @@ -84,11 +99,19 @@ func Example_selectWithParameters() { 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( @@ -99,6 +122,7 @@ func Example_selectWithParameters() { } } } + return res.Err() // return finally result error for auto-retry with driver }, query.WithIdempotent(), @@ -114,6 +138,7 @@ func Example_txSelect() { 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 @@ -134,11 +159,19 @@ func Example_txSelect() { 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( @@ -149,6 +182,7 @@ func Example_txSelect() { } } } + return res.Err() // return finally result error for auto-retry with driver }, query.WithIdempotent(), diff --git a/query/execute_options.go b/query/execute_options.go index 640c6e7b3..83d66a51d 100644 --- a/query/execute_options.go +++ b/query/execute_options.go @@ -3,6 +3,8 @@ package query 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" ) type ( @@ -12,14 +14,15 @@ type ( callOptions []grpc.CallOption commonExecuteSettings struct { syntax Syntax - params *Parameters + params params.Parameters execMode ExecMode statsMode StatsMode callOptions []grpc.CallOption - txControl *TransactionControl } executeSettings struct { commonExecuteSettings + + txControl *TransactionControl } ExecuteOption interface { applyExecuteOption(s *executeSettings) @@ -30,8 +33,17 @@ type ( TxExecuteOption interface { applyTxExecuteOption(s *txExecuteSettings) } + parametersOption params.Parameters ) +func (params parametersOption) applyTxExecuteOption(s *txExecuteSettings) { + s.params = append(s.params, params...) +} + +func (params parametersOption) applyExecuteOption(s *executeSettings) { + s.params = append(s.params, params...) +} + func (opts callOptions) applyExecuteOption(s *executeSettings) { s.callOptions = append(s.callOptions, opts...) } @@ -78,65 +90,64 @@ func defaultCommonExecuteSettings() commonExecuteSettings { } } -func ExecuteSettings(opts ...ExecuteOption) (settings executeSettings) { +func ExecuteSettings(opts ...ExecuteOption) (settings *executeSettings) { + settings = &executeSettings{ + commonExecuteSettings: defaultCommonExecuteSettings(), + } settings.commonExecuteSettings = defaultCommonExecuteSettings() settings.txControl = DefaultTxControl() for _, opt := range opts { - opt.applyExecuteOption(&settings) + opt.applyExecuteOption(settings) } + return settings } -func (s executeSettings) TxControl() *TransactionControl { +func (s *executeSettings) TxControl() *TransactionControl { return s.txControl } -func (s executeSettings) CallOptions() []grpc.CallOption { +func (s *commonExecuteSettings) CallOptions() []grpc.CallOption { return s.callOptions } -func (s executeSettings) Syntax() Syntax { +func (s *commonExecuteSettings) Syntax() Syntax { return s.syntax } -func (s executeSettings) ExecMode() ExecMode { +func (s *commonExecuteSettings) ExecMode() ExecMode { return s.execMode } -func (s executeSettings) StatsMode() StatsMode { +func (s *commonExecuteSettings) StatsMode() StatsMode { return s.statsMode } -func (s executeSettings) Params() *Parameters { - return s.params +func (s *commonExecuteSettings) Params() *params.Parameters { + if len(s.params) == 0 { + return nil + } + + return &s.params } -func TxExecuteSettings(opts ...TxExecuteOption) (settings txExecuteSettings) { - settings.commonExecuteSettings = defaultCommonExecuteSettings() +func TxExecuteSettings(opts ...TxExecuteOption) (settings *txExecuteSettings) { + settings = &txExecuteSettings{ + commonExecuteSettings: defaultCommonExecuteSettings(), + } for _, opt := range opts { - opt.applyTxExecuteOption(&settings) + opt.applyTxExecuteOption(settings) } + return settings } var _ ExecuteOption = (*parametersOption)(nil) -func WithParameters(params ...Parameter) *parametersOption { - opt := ¶metersOption{ - params: &Parameters{ - m: make(queryParams, len(params)), - }, - } - opt.params.Add(params...) - return opt -} +func WithParameters(parameters *params.Parameters) *parametersOption { + params := parametersOption(*parameters) -func Params(params ...Parameter) *Parameters { - p := &Parameters{ - m: make(queryParams, len(params)), - } - p.Add(params...) - return p + return ¶ms } var ( diff --git a/query/named.go b/query/named.go index 5ff7a1d66..e25a3ef3a 100644 --- a/query/named.go +++ b/query/named.go @@ -28,5 +28,6 @@ func Named(columnName string, destinationValueReference interface{}) (dst namedD panic(fmt.Errorf("%T is not reference type", destinationValueReference)) } dst.ref = destinationValueReference + return dst } diff --git a/query/named_test.go b/query/named_test.go index 7b804a2c3..febbeb3f3 100644 --- a/query/named_test.go +++ b/query/named_test.go @@ -44,12 +44,14 @@ func TestNamed(t *testing.T) { 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), }, diff --git a/query/parameters.go b/query/parameters.go deleted file mode 100644 index f0bced090..000000000 --- a/query/parameters.go +++ /dev/null @@ -1,135 +0,0 @@ -package query - -import ( - "sort" - - "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" -) - -// Parameters -type ( - queryParams map[string]Value - Parameter interface { - Name() string - Value() Value - } - parameter struct { - name string - value Value - } - Parameters struct { - m queryParams - } - parametersOption struct { - params *Parameters - } -) - -func (opt *parametersOption) applyTxExecuteOption(o *txExecuteSettings) { - o.params = opt.params -} - -func (opt *parametersOption) applyExecuteOption(o *executeSettings) { - o.params = opt.params -} - -func (p parameter) Name() string { - return p.name -} - -func (p parameter) Value() Value { - return p.value -} - -func (params *Parameters) ToYDB(a *allocator.Allocator) map[string]*Ydb.TypedValue { - if params == nil || params.m == nil { - return nil - } - return params.m.toYDB(a) -} - -func (qp queryParams) toYDB(a *allocator.Allocator) map[string]*Ydb.TypedValue { - params := make(map[string]*Ydb.TypedValue, len(qp)) - for k, v := range qp { - params[k] = value.ToYDB(v, a) - } - return params -} - -func (params *Parameters) Params() queryParams { - if params == nil { - return nil - } - return params.m -} - -func (params *Parameters) Count() int { - if params == nil { - return 0 - } - return len(params.m) -} - -func (params *Parameters) Each(it func(name string, v Value)) { - if params == nil { - return - } - for key, v := range params.m { - it(key, v) - } -} - -func (params *Parameters) names() []string { - if params == nil { - return nil - } - names := make([]string, 0, len(params.m)) - for k := range params.m { - names = append(names, k) - } - sort.Strings(names) - return names -} - -func (params *Parameters) String() string { - buffer := xstring.Buffer() - defer buffer.Free() - - buffer.WriteByte('{') - for i, name := range params.names() { - if i != 0 { - buffer.WriteByte(',') - } - buffer.WriteByte('"') - buffer.WriteString(name) - buffer.WriteString("\":") - buffer.WriteString(params.m[name].Yql()) - } - buffer.WriteByte('}') - return buffer.String() -} - -func (params *Parameters) Add(parameters ...Parameter) { - for _, param := range parameters { - params.m[param.Name()] = param.Value() - } -} - -func Param(name string, v Value) Parameter { - switch len(name) { - case 0: - panic("empty name") - default: - if name[0] != '$' { - panic("parameter name must be started from $") - } - } - return ¶meter{ - name: name, - value: v, - } -} diff --git a/query/transaction_control.go b/query/transaction_control.go index 653768725..d3ba2b591 100644 --- a/query/transaction_control.go +++ b/query/transaction_control.go @@ -37,6 +37,7 @@ func (ctrl *TransactionControl) ToYDB(a *allocator.Allocator) *Ydb_Query.Transac txControl := a.QueryTransactionControl() ctrl.selector.applyTxSelector(a, txControl) txControl.CommitTx = ctrl.commit + return txControl } @@ -66,28 +67,28 @@ func BeginTx(opts ...txSettingsOption) beginTxOptions { } var ( - _ txControlOption = txIdTxControlOption("") - _ txSelector = txIdTxControlOption("") + _ txControlOption = txIDTxControlOption("") + _ txSelector = txIDTxControlOption("") ) -type txIdTxControlOption string +type txIDTxControlOption string -func (id txIdTxControlOption) applyTxControlOption(txControl *TransactionControl) { +func (id txIDTxControlOption) applyTxControlOption(txControl *TransactionControl) { txControl.selector = id } -func (id txIdTxControlOption) applyTxSelector(a *allocator.Allocator, txControl *Ydb_Query.TransactionControl) { - selector := a.QueryTransactionControlTxId() +func (id txIDTxControlOption) applyTxSelector(a *allocator.Allocator, txControl *Ydb_Query.TransactionControl) { + selector := a.QueryTransactionControlTxID() selector.TxId = string(id) txControl.TxSelector = selector } -func WithTx(t TxIdentifier) txIdTxControlOption { - return txIdTxControlOption(t.ID()) +func WithTx(t TxIdentifier) txIDTxControlOption { + return txIDTxControlOption(t.ID()) } -func WithTxID(txID string) txIdTxControlOption { - return txIdTxControlOption(txID) +func WithTxID(txID string) txIDTxControlOption { + return txIDTxControlOption(txID) } type commitTxOption struct{} @@ -112,6 +113,7 @@ func TxControl(opts ...txControlOption) *TransactionControl { opt.applyTxControlOption(txControl) } } + return txControl } diff --git a/query/transaction_settings.go b/query/transaction_settings.go index 45673017b..163166bc5 100644 --- a/query/transaction_settings.go +++ b/query/transaction_settings.go @@ -27,7 +27,7 @@ var ( // Transaction settings options type ( txSettingsOption interface { - applyTxSettingsOption(*allocator.Allocator, *Ydb_Query.TransactionSettings) + applyTxSettingsOption(a *allocator.Allocator, txSettings *Ydb_Query.TransactionSettings) } TransactionSettings []txSettingsOption ) @@ -46,6 +46,7 @@ func (opts TransactionSettings) ToYDB(a *allocator.Allocator) *Ydb_Query.Transac for _, opt := range opts { opt.applyTxSettingsOption(a, txSettings) } + return txSettings } @@ -62,7 +63,9 @@ var _ txSettingsOption = serializableReadWriteTxSettingsOption{} type serializableReadWriteTxSettingsOption struct{} -func (o serializableReadWriteTxSettingsOption) applyTxSettingsOption(a *allocator.Allocator, txSettings *Ydb_Query.TransactionSettings) { +func (o serializableReadWriteTxSettingsOption) applyTxSettingsOption( + a *allocator.Allocator, txSettings *Ydb_Query.TransactionSettings, +) { txSettings.TxMode = serializableReadWrite } @@ -74,7 +77,9 @@ var _ txSettingsOption = snapshotReadOnlyTxSettingsOption{} type snapshotReadOnlyTxSettingsOption struct{} -func (snapshotReadOnlyTxSettingsOption) applyTxSettingsOption(a *allocator.Allocator, settings *Ydb_Query.TransactionSettings) { +func (snapshotReadOnlyTxSettingsOption) applyTxSettingsOption( + a *allocator.Allocator, settings *Ydb_Query.TransactionSettings, +) { settings.TxMode = snapshotReadOnly } @@ -86,7 +91,9 @@ var _ txSettingsOption = staleReadOnlySettingsOption{} type staleReadOnlySettingsOption struct{} -func (staleReadOnlySettingsOption) applyTxSettingsOption(a *allocator.Allocator, settings *Ydb_Query.TransactionSettings) { +func (staleReadOnlySettingsOption) applyTxSettingsOption( + a *allocator.Allocator, settings *Ydb_Query.TransactionSettings, +) { settings.TxMode = staleReadOnly } @@ -97,7 +104,7 @@ func WithStaleReadOnly() txSettingsOption { type ( txOnlineReadOnly bool TxOnlineReadOnlyOption interface { - applyTxOnlineReadOnlyOption(*txOnlineReadOnly) + applyTxOnlineReadOnlyOption(opt *txOnlineReadOnly) } ) @@ -117,7 +124,9 @@ var _ txSettingsOption = onlineReadOnlySettingsOption{} type onlineReadOnlySettingsOption []TxOnlineReadOnlyOption -func (opts onlineReadOnlySettingsOption) applyTxSettingsOption(a *allocator.Allocator, settings *Ydb_Query.TransactionSettings) { +func (opts onlineReadOnlySettingsOption) applyTxSettingsOption( + a *allocator.Allocator, settings *Ydb_Query.TransactionSettings, +) { var ro txOnlineReadOnly for _, opt := range opts { if opt != nil { diff --git a/query/type.go b/query/type.go deleted file mode 100644 index 092fc2ff0..000000000 --- a/query/type.go +++ /dev/null @@ -1,7 +0,0 @@ -package query - -import ( - "github.com/ydb-platform/ydb-go-sdk/v3/internal/types" -) - -type Type = types.Type diff --git a/query/value.go b/query/value.go deleted file mode 100644 index 10f8c2a19..000000000 --- a/query/value.go +++ /dev/null @@ -1,65 +0,0 @@ -package query - -import ( - "time" - - "github.com/ydb-platform/ydb-go-sdk/v3/internal/value" -) - -type Value = value.Value - -func BoolValue(v bool) Value { - return value.BoolValue(v) -} - -func Uint64Value(v uint64) Value { - return value.Uint64Value(v) -} - -func Int64Value(v int64) Value { - return value.Int64Value(v) -} - -func Uint32Value(v uint32) Value { - return value.Uint32Value(v) -} - -func Int32Value(v int32) Value { - return value.Int32Value(v) -} - -func Uint16Value(v uint16) Value { - return value.Uint16Value(v) -} - -func Int16Value(v int16) Value { - return value.Int16Value(v) -} - -func Uint8Value(v uint8) Value { - return value.Uint8Value(v) -} - -func Int8Value(v int8) Value { - return value.Int8Value(v) -} - -func TextValue(v string) Value { - return value.TextValue(v) -} - -func BytesValue(v []byte) Value { - return value.BytesValue(v) -} - -func IntervalValue(v time.Duration) Value { - return value.IntervalValueFromDuration(v) -} - -func TimestampValue(v time.Time) Value { - return value.TimestampValueFromTime(v) -} - -func DatetimeValue(v time.Time) Value { - return value.DatetimeValueFromTime(v) -} From 5aad8dc9dd59349b1a8e872a56d484780bfb496a Mon Sep 17 00:00:00 2001 From: Aleksey Myasnikov Date: Wed, 28 Feb 2024 14:38:16 +0300 Subject: [PATCH 3/3] CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0cb5fdbbc..654d8bbea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ +* 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}`