From bc65e89c9ff6f7efbf7b04634a970791beae3082 Mon Sep 17 00:00:00 2001 From: Aleksey Myasnikov Date: Tue, 16 Jan 2024 13:03:07 +0300 Subject: [PATCH] query client interface and simple implementation --- .github/workflows/tests.yml | 2 +- connection.go | 4 + driver.go | 28 + go.mod | 2 +- go.sum | 2 + internal/allocator/allocator.go | 270 +++++- internal/query/client.go | 233 +++++ internal/query/client_test.go | 142 +++ internal/query/column.go | 38 + internal/query/config/config.go | 78 ++ internal/query/config/options.go | 49 ++ internal/query/errors.go | 14 + internal/query/execute_query.go | 69 ++ internal/query/execute_query_test.go | 1071 +++++++++++++++++++++++ internal/query/mock_test.pb.go | 465 ++++++++++ internal/query/pool.go | 91 ++ internal/query/result.go | 170 ++++ internal/query/result_set.go | 67 ++ internal/query/result_set_test.go | 597 +++++++++++++ internal/query/result_test.go | 895 +++++++++++++++++++ internal/query/row.go | 24 + internal/query/scanner_data.go | 18 + internal/query/scanner_indexed.go | 22 + internal/query/scanner_named.go | 22 + internal/query/scanner_struct.go | 22 + internal/query/session.go | 68 ++ internal/query/session_test.go | 2 + internal/query/transaction.go | 33 + 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 | 3 +- 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 +- 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 | 151 ++++ query/named.go | 32 + query/named_test.go | 72 ++ query/options.go | 7 + query/parameters.go | 128 +++ query/result.go | 36 + query/session.go | 26 + query/session_status.go | 33 + query/transaction.go | 27 + query/transaction_control.go | 150 ++++ 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 | 52 ++ testutil/compare_test.go | 112 +-- trace/query.go | 11 + 66 files changed, 6301 insertions(+), 601 deletions(-) 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/mock_test.pb.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/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 trace/query.go 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..f50cf41b9 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,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 // indirect ) require ( diff --git a/go.sum b/go.sum index 5ab333592..b43d55ff4 100644 --- a/go.sum +++ b/go.sum @@ -78,6 +78,8 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1 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= diff --git a/internal/allocator/allocator.go b/internal/allocator/allocator.go index 6c55c51b4..9a81f53d8 100644 --- a/internal/allocator/allocator.go +++ b/internal/allocator/allocator.go @@ -1,6 +1,7 @@ package allocator import ( + "github.com/ydb-platform/ydb-go-genproto/protos/Ydb_Query" "sync" "github.com/ydb-platform/ydb-go-genproto/protos/Ydb" @@ -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/query/client.go b/internal/query/client.go new file mode 100644 index 000000000..60a788f9f --- /dev/null +++ b/internal/query/client.go @@ -0,0 +1,233 @@ +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 mock_test.pb.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 SessionPool +} + +func (c Client) Close(ctx context.Context) error { + err := c.pool.Close(ctx) + if err != nil { + return xerrors.WithStackTrace(err) + } + return nil +} + +func (c Client) do(ctx context.Context, op query.Operation, opts query.DoOptions) error { + return retry.Retry(ctx, func(ctx context.Context) error { + err := c.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 c.do(ctx, op, doOptions) +} + +func (c Client) DoTx(ctx context.Context, op query.TxOperation, opts ...query.DoTxOption) error { + doTxOptions := query.NewDoTxOptions(opts...) + return c.do(ctx, func(ctx context.Context, s query.Session) error { + tx, err := s.Begin(ctx, doTxOptions.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)) + } + } + 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 + }, doTxOptions.DoOptions) +} + +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.Operation(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.Operation(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.Operation(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, onDetach func(sessionID string)) (_ *Session, err error) { + s, err := createSession(ctx, grpcClient, createSessionSettings{ + createSessionTimeout: config.CreateSessionTimeout(), + onDetach: onDetach, + }) + 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..0de4f80dd --- /dev/null +++ b/internal/query/client_test.go @@ -0,0 +1,142 @@ +package query + +import ( + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" + "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/internal/xtest" + grpcCodes "google.golang.org/grpc/codes" + grpcStatus "google.golang.org/grpc/status" + "testing" + "time" +) + +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)) + }) + }) +} diff --git a/internal/query/column.go b/internal/query/column.go new file mode 100644 index 000000000..d219c9ebe --- /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..45e5dc828 --- /dev/null +++ b/internal/query/errors.go @@ -0,0 +1,14 @@ +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") +) diff --git a/internal/query/execute_query.go b/internal/query/execute_query.go new file mode 100644 index 000000000..242be8fd1 --- /dev/null +++ b/internal/query/execute_query.go @@ -0,0 +1,69 @@ +package query + +import ( + "context" + + "github.com/ydb-platform/ydb-go-genproto/Ydb_Query_V1" + "google.golang.org/grpc" + + "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/xcontext" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors" + "github.com/ydb-platform/ydb-go-sdk/v3/query" +) + +func makeExecuteQueryRequest(a *allocator.Allocator, sessionID string, q string, opts ...query.ExecuteOption) ( + *Ydb_Query.ExecuteQueryRequest, + []grpc.CallOption, +) { + var ( + settings = query.ExecuteSettings(opts...) + 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, opts ...query.ExecuteOption, +) (*transaction, *result, error) { + a := allocator.New() + request, callOptions := makeExecuteQueryRequest(a, session.id, q, opts...) + 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..21187967b --- /dev/null +++ b/internal/query/execute_query_test.go @@ -0,0 +1,1071 @@ +package query + +import ( + "context" + "io" + "testing" + + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" + "github.com/ydb-platform/ydb-go-genproto/protos/Ydb" + "github.com/ydb-platform/ydb-go-genproto/protos/Ydb_Query" + "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, "") + 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, "") + 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, "") + 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, "") + 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, "") + 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 TestMakeExecuteQueryRequest(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 := makeExecuteQueryRequest(a, tt.name, tt.name, tt.opts...) + require.Equal(t, request.String(), tt.request.String()) + require.Equal(t, tt.callOptions, callOptions) + }) + } +} diff --git a/internal/query/mock_test.pb.go b/internal/query/mock_test.pb.go new file mode 100644 index 000000000..7548e3c9e --- /dev/null +++ b/internal/query/mock_test.pb.go @@ -0,0 +1,465 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/ydb-platform/ydb-go-genproto/Ydb_Query_V1 (interfaces: QueryServiceClient,QueryService_AttachSessionClient,QueryService_ExecuteQueryClient) + +package query + +import ( + context "context" + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + 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" + 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 := []interface{}{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 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{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 := []interface{}{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 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{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 := []interface{}{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 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{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 := []interface{}{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 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{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 := []interface{}{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 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{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 := []interface{}{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 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{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 := []interface{}{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 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{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 := []interface{}{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 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{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 := []interface{}{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 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{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 interface{}) 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 interface{}) *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 interface{}) 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 interface{}) *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 interface{}) 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 interface{}) *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 interface{}) 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 interface{}) *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..4d1b330e2 --- /dev/null +++ b/internal/query/pool.go @@ -0,0 +1,91 @@ +package query + +import ( + "context" + "fmt" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/closer" + "github.com/ydb-platform/ydb-go-sdk/v3/query" + + "github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors" +) + +type SessionPool interface { + closer.Closer + + With(ctx context.Context, f func(ctx context.Context, s *Session) error) error +} + +var _ SessionPool = (*stubPool)(nil) + +type stubPool struct { + idle chan *Session + create func(ctx context.Context, onDetach func(sessionID string)) (*Session, error) + close func(ctx context.Context, s *Session) error +} + +func newStubPool( + limit int, + create func(ctx context.Context, onDetach func(sessionID string)) (*Session, error), + close func(ctx context.Context, s *Session) error, +) *stubPool { + return &stubPool{ + idle: make(chan *Session, limit), + create: create, + close: close, + } +} + +func (pool *stubPool) Close(ctx context.Context) error { + close(pool.idle) + for s := range pool.idle { + pool.close(ctx, s) + } + return nil +} + +func (pool *stubPool) get(ctx context.Context) (*Session, error) { + select { + case <-ctx.Done(): + return nil, xerrors.WithStackTrace(ctx.Err()) + case s, has := <-pool.idle: + if has { + return s, nil + } + if s.Status() != query.SessionStatusReady { + pool.close(ctx, s) + } + return nil, xerrors.WithStackTrace(fmt.Errorf("session pool closed")) + default: + s, err := pool.create(ctx, pool.detachSession) + if err != nil { + return nil, xerrors.WithStackTrace(err) + } + return s, nil + } +} + +func (pool *stubPool) put(ctx context.Context, s *Session) { + if s.Status() != query.SessionStatusReady { + pool.close(ctx, s) + } + pool.idle <- 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 +} + +func (pool *stubPool) detachSession(sessionID string) { + +} diff --git a/internal/query/result.go b/internal/query/result.go new file mode 100644 index 000000000..8f341278e --- /dev/null +++ b/internal/query/result.go @@ -0,0 +1,170 @@ +package query + +import ( + "context" + "fmt" + "github.com/ydb-platform/ydb-go-genproto/Ydb_Query_V1" + "github.com/ydb-platform/ydb-go-genproto/protos/Ydb_Query" + "io" + "sync" + + "github.com/ydb-platform/ydb-go-genproto/protos/Ydb" + + "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.Operation( + 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..f1a4aaf73 --- /dev/null +++ b/internal/query/result_set.go @@ -0,0 +1,67 @@ +package query + +import ( + "context" + "fmt" + "github.com/ydb-platform/ydb-go-genproto/protos/Ydb_Query" + "io" + + "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..268ea2cad --- /dev/null +++ b/internal/query/result_set_test.go @@ -0,0 +1,597 @@ +package query + +import ( + "context" + "fmt" + grpcCodes "google.golang.org/grpc/codes" + grpcStatus "google.golang.org/grpc/status" + "io" + "testing" + + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" + "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/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..b451afc9b --- /dev/null +++ b/internal/query/result_test.go @@ -0,0 +1,895 @@ +package query + +import ( + "context" + "io" + "testing" + "time" + + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" + "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/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..d6a8fedcb --- /dev/null +++ b/internal/query/scanner_data.go @@ -0,0 +1,18 @@ +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..ea50a029f --- /dev/null +++ b/internal/query/scanner_indexed.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.IndexedScanner = scannerIndexed{} + +type scannerIndexed struct { + data *scannerData +} + +func newScannerIndexed(data *scannerData) scannerIndexed { + return scannerIndexed{ + data: data, + } +} + +func (s scannerIndexed) Scan(dst ...interface{}) error { + 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..69c1d985b --- /dev/null +++ b/internal/query/session.go @@ -0,0 +1,68 @@ +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" + "sync/atomic" + + "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 (s *Session) Begin(ctx context.Context, txSettings query.TransactionSettings) (query.Transaction, error) { + a := allocator.New() + defer a.Free() + response, err := s.queryClient.BeginTransaction(ctx, + &Ydb_Query.BeginTransactionRequest{ + SessionId: s.id, + 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.Operation(xerrors.FromOperation(response))) + } + return transaction{ + id: response.GetTxMeta().GetId(), + s: s, + }, 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, opts...) +} diff --git a/internal/query/session_test.go b/internal/query/session_test.go new file mode 100644 index 000000000..4c60abc1b --- /dev/null +++ b/internal/query/session_test.go @@ -0,0 +1,2 @@ +package query + diff --git a/internal/query/transaction.go b/internal/query/transaction.go new file mode 100644 index 000000000..7c7022b5c --- /dev/null +++ b/internal/query/transaction.go @@ -0,0 +1,33 @@ +package query + +import ( + "context" + + "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 (tx transaction) Execute(ctx context.Context, query string, opts ...query.ExecuteOption) (r query.Result, err error) { + //TODO implement me + panic("implement me") +} + +func (tx transaction) CommitTx(ctx context.Context) (err error) { + //TODO implement me + panic("implement me") +} + +func (tx transaction) Rollback(ctx context.Context) (err error) { + //TODO implement me + panic("implement me") +} diff --git a/internal/scripting/client.go b/internal/scripting/client.go index bf57a3edd..59851f62c 100644 --- a/internal/scripting/client.go +++ b/internal/scripting/client.go @@ -3,6 +3,7 @@ package scripting import ( "context" "errors" + types2 "github.com/ydb-platform/ydb-go-sdk/v3/internal/types" "github.com/ydb-platform/ydb-go-genproto/Ydb_Scripting_V1" "github.com/ydb-platform/ydb-go-genproto/protos/Ydb" @@ -15,7 +16,6 @@ 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" "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..94d5423aa 100644 --- a/internal/table/scanner/result_test.go +++ b/internal/table/scanner/result_test.go @@ -3,6 +3,7 @@ package scanner import ( "context" "fmt" + types2 "github.com/ydb-platform/ydb-go-sdk/v3/internal/types" "io" "reflect" "testing" @@ -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..bd9c42e75 100644 --- a/internal/table/scanner/scan_raw.go +++ b/internal/table/scanner/scan_raw.go @@ -2,6 +2,7 @@ package scanner import ( "bytes" + types2 "github.com/ydb-platform/ydb-go-sdk/v3/internal/types" "io" "reflect" "strconv" @@ -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..081a4d764 100644 --- a/internal/table/scanner/scanner.go +++ b/internal/table/scanner/scanner.go @@ -4,6 +4,7 @@ import ( "database/sql" "encoding/json" "fmt" + types2 "github.com/ydb-platform/ydb-go-sdk/v3/internal/types" "io" "math" "reflect" @@ -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..166d873bd 100644 --- a/internal/table/session.go +++ b/internal/table/session.go @@ -3,6 +3,7 @@ package table import ( "context" "fmt" + types2 "github.com/ydb-platform/ydb-go-sdk/v3/internal/types" "net/url" "strconv" "sync" @@ -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(), } } diff --git a/internal/table/session_test.go b/internal/table/session_test.go index cd581a9fd..3c6a93902 100644 --- a/internal/table/session_test.go +++ b/internal/table/session_test.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + types2 "github.com/ydb-platform/ydb-go-sdk/v3/internal/types" "reflect" "testing" "time" @@ -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/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..c195b376d --- /dev/null +++ b/query/execute_options.go @@ -0,0 +1,151 @@ +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 + } + executeSettings struct { + commonExecuteSettings + txControl *txControl + } + 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 *executeSettings) { + 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() *txControl { + 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() queryParams { + return s.params.Params() +} + +func TxExecuteSettings(opts ...TxExecuteOption) (settings txExecuteSettings) { + settings.commonExecuteSettings = defaultCommonExecuteSettings() + for _, opt := range opts { + opt.applyTxExecuteOption(&settings) + } + return settings +} + +var _ ExecuteOption = (*Parameters)(nil) + +func WithParameters(params ...Parameter) *Parameters { + q := &Parameters{ + m: make(queryParams, len(params)), + } + q.Add(params...) + return q +} + +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 +} 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..3e13410ee --- /dev/null +++ b/query/parameters.go @@ -0,0 +1,128 @@ +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 + } +) + +func (params *Parameters) applyTxExecuteOption(o *txExecuteSettings) { + o.params = params +} + +func (params *Parameters) applyExecuteOption(o *executeSettings) { + o.params = params +} + +func (p parameter) Name() string { + return p.name +} + +func (p parameter) Value() Value { + return p.value +} + +func (qp queryParams) ToYDB(a *allocator.Allocator) map[string]*Ydb.TypedValue { + if qp == nil { + return nil + } + params := make(map[string]*Ydb.TypedValue, len(qp)) + for k, v := range qp { + params[k] = value.ToYDB(v, a) + } + return params +} + +func (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] != '$' { + name = "$" + name + } + } + 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..94174c4b8 --- /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 ...ExecuteOption) (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..dcc0f1715 --- /dev/null +++ b/query/transaction_control.go @@ -0,0 +1,150 @@ +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 + } = (*txControl)(nil) + _ txSelector = (*TransactionSettings)(nil) +) + +type ( + txSelector interface { + applyTxSelector(a *allocator.Allocator, txControl *Ydb_Query.TransactionControl) + } + txControlOption interface { + applyTxControlOption(txControl *txControl) + } + txControl struct { + selector txSelector + commit bool + } +) + +func (ctrl *txControl) 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 *txControl) { + 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 *txControl) { + 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 *txControl) { + 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) *txControl { + txControl := &txControl{ + 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() *txControl { + return TxControl( + BeginTx(WithSerializableReadWrite()), + CommitTx(), + ) +} + +// SerializableReadWriteTxControl returns transaction control with serializable read-write isolation mode +func SerializableReadWriteTxControl(opts ...txControlOption) *txControl { + return TxControl( + append([]txControlOption{ + BeginTx(WithSerializableReadWrite()), + }, opts...)..., + ) +} + +// OnlineReadOnlyTxControl returns online read-only transaction control +func OnlineReadOnlyTxControl(opts ...TxOnlineReadOnlyOption) *txControl { + return TxControl( + BeginTx(WithOnlineReadOnly(opts...)), + CommitTx(), // open transactions not supported for OnlineReadOnly + ) +} + +// StaleReadOnlyTxControl returns stale read-only transaction control +func StaleReadOnlyTxControl() *txControl { + return TxControl( + BeginTx(WithStaleReadOnly()), + CommitTx(), // open transactions not supported for StaleReadOnly + ) +} + +// SnapshotReadOnlyTxControl returns snapshot read-only transaction control +func SnapshotReadOnlyTxControl() *txControl { + 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..7bab9d116 --- /dev/null +++ b/tests/integration/query_execute_test.go @@ -0,0 +1,52 @@ +//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/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{} +)