diff --git a/CHANGELOG.md b/CHANGELOG.md index 42bcc5421..63ed00b39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,8 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release. - Support `fetch_latest_metadata` option for crud requests with metadata (#335) - Support `noreturn` option for data change crud requests (#335) - Support `crud.schema` request (#336) +- Support `IPROTO_WATCH_ONCE` request type for Tarantool + version >= 3.0.0-alpha1 (#337) ### Changed @@ -38,6 +40,9 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release. `pool.Connect` and `pool.Add` now accept context as first argument, which user may cancel in process. If `pool.Connect` is canceled in progress, an error will be returned. All created connections will be closed. +- `iproto.Feature` type now used instead of `ProtocolFeature` (#337) +- `iproto.IPROTO_FEATURE_` constants now used instead of local `Feature` + constants for `protocol` (#337) ### Deprecated diff --git a/README.md b/README.md index 378c344b3..2c7de68c4 100644 --- a/README.md +++ b/README.md @@ -234,6 +234,11 @@ now does not attempt to reconnect and tries to establish a connection only once. Function might be canceled via context. Context accepted as first argument, and user may cancel it in process. +#### Protocol changes + +* `iproto.Feature` type used instead of `ProtocolFeature`. +* `iproto.IPROTO_FEATURE_` constants used instead of local ones. + ## Contributing See [the contributing guide](CONTRIBUTING.md) for detailed instructions on how diff --git a/connection.go b/connection.go index f304a12ba..018df6caa 100644 --- a/connection.go +++ b/connection.go @@ -564,7 +564,8 @@ func (conn *Connection) dial(ctx context.Context) error { // Subscribe shutdown event to process graceful shutdown. if conn.shutdownWatcher == nil && - isFeatureInSlice(WatchersFeature, conn.serverProtocolInfo.Features) { + isFeatureInSlice(iproto.IPROTO_FEATURE_WATCHERS, + conn.serverProtocolInfo.Features) { watcher, werr := conn.newWatcherImpl(shutdownEventKey, shutdownEventCallback) if werr != nil { return werr @@ -1425,7 +1426,7 @@ func subscribeWatchChannel(conn *Connection, key string) (chan watchState, error return st, nil } -func isFeatureInSlice(expected ProtocolFeature, actualSlice []ProtocolFeature) bool { +func isFeatureInSlice(expected iproto.Feature, actualSlice []iproto.Feature) bool { for _, actual := range actualSlice { if expected == actual { return true @@ -1436,8 +1437,8 @@ func isFeatureInSlice(expected ProtocolFeature, actualSlice []ProtocolFeature) b // NewWatcher creates a new Watcher object for the connection. // -// You need to require WatchersFeature to use watchers, see examples for the -// function. +// You need to require IPROTO_FEATURE_WATCHERS to use watchers, see examples +// for the function. // // After watcher creation, the watcher callback is invoked for the first time. // In this case, the callback is triggered whether or not the key has already @@ -1472,9 +1473,10 @@ func (conn *Connection) NewWatcher(key string, callback WatchCallback) (Watcher, // asynchronous. We do not expect any response from a Tarantool instance // That's why we can't just check the Tarantool response for an unsupported // request error. - if !isFeatureInSlice(WatchersFeature, conn.opts.RequiredProtocolInfo.Features) { + if !isFeatureInSlice(iproto.IPROTO_FEATURE_WATCHERS, + conn.opts.RequiredProtocolInfo.Features) { err := fmt.Errorf("the feature %s must be required by connection "+ - "options to create a watcher", WatchersFeature) + "options to create a watcher", iproto.IPROTO_FEATURE_WATCHERS) return nil, err } diff --git a/connection_test.go b/connection_test.go index 3e7be1966..20d06b183 100644 --- a/connection_test.go +++ b/connection_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/stretchr/testify/require" + "github.com/tarantool/go-iproto" . "github.com/tarantool/go-tarantool/v2" ) @@ -12,20 +13,20 @@ func TestOptsClonePreservesRequiredProtocolFeatures(t *testing.T) { original := Opts{ RequiredProtocolInfo: ProtocolInfo{ Version: ProtocolVersion(100), - Features: []ProtocolFeature{ProtocolFeature(99), ProtocolFeature(100)}, + Features: []iproto.Feature{iproto.Feature(99), iproto.Feature(100)}, }, } origCopy := original.Clone() - original.RequiredProtocolInfo.Features[1] = ProtocolFeature(98) + original.RequiredProtocolInfo.Features[1] = iproto.Feature(98) require.Equal(t, origCopy, Opts{ RequiredProtocolInfo: ProtocolInfo{ Version: ProtocolVersion(100), - Features: []ProtocolFeature{ProtocolFeature(99), ProtocolFeature(100)}, + Features: []iproto.Feature{iproto.Feature(99), iproto.Feature(100)}, }, }) } diff --git a/dial_test.go b/dial_test.go index acd6737c5..209fc50cd 100644 --- a/dial_test.go +++ b/dial_test.go @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/tarantool/go-iproto" "github.com/tarantool/go-tarantool/v2" "github.com/tarantool/go-tarantool/v2/test_helpers" @@ -72,8 +73,8 @@ func TestDialer_Dial_passedOpts(t *testing.T) { RequiredProtocol: tarantool.ProtocolInfo{ Auth: tarantool.ChapSha1Auth, Version: 33, - Features: []tarantool.ProtocolFeature{ - tarantool.ErrorExtensionFeature, + Features: []iproto.Feature{ + iproto.IPROTO_FEATURE_ERROR_EXTENSION, }, }, Auth: tarantool.ChapSha1Auth, @@ -302,8 +303,8 @@ func TestConn_ProtocolInfo(t *testing.T) { info := tarantool.ProtocolInfo{ Auth: tarantool.ChapSha1Auth, Version: 33, - Features: []tarantool.ProtocolFeature{ - tarantool.ErrorExtensionFeature, + Features: []iproto.Feature{ + iproto.IPROTO_FEATURE_ERROR_EXTENSION, }, } conn, dialer := dialIo(t, func(conn *mockIoConn) { diff --git a/example_test.go b/example_test.go index 77b2cff24..f85f532d0 100644 --- a/example_test.go +++ b/example_test.go @@ -5,6 +5,8 @@ import ( "fmt" "time" + "github.com/tarantool/go-iproto" + "github.com/tarantool/go-tarantool/v2" "github.com/tarantool/go-tarantool/v2/test_helpers" ) @@ -626,12 +628,13 @@ func ExampleProtocolVersion() { fmt.Println("Connector client protocol feature:", f) } // Output: - // Connector client protocol version: 4 - // Connector client protocol feature: StreamsFeature - // Connector client protocol feature: TransactionsFeature - // Connector client protocol feature: ErrorExtensionFeature - // Connector client protocol feature: WatchersFeature - // Connector client protocol feature: PaginationFeature + // Connector client protocol version: 6 + // Connector client protocol feature: IPROTO_FEATURE_STREAMS + // Connector client protocol feature: IPROTO_FEATURE_TRANSACTIONS + // Connector client protocol feature: IPROTO_FEATURE_ERROR_EXTENSION + // Connector client protocol feature: IPROTO_FEATURE_WATCHERS + // Connector client protocol feature: IPROTO_FEATURE_PAGINATION + // Connector client protocol feature: IPROTO_FEATURE_WATCH_ONCE } func getTestTxnOpts() tarantool.Opts { @@ -640,9 +643,9 @@ func getTestTxnOpts() tarantool.Opts { // Assert that server supports expected protocol features txnOpts.RequiredProtocolInfo = tarantool.ProtocolInfo{ Version: tarantool.ProtocolVersion(1), - Features: []tarantool.ProtocolFeature{ - tarantool.StreamsFeature, - tarantool.TransactionsFeature, + Features: []iproto.Feature{ + iproto.IPROTO_FEATURE_STREAMS, + iproto.IPROTO_FEATURE_TRANSACTIONS, }, } @@ -1167,7 +1170,7 @@ func ExampleConnection_NewWatcher() { Pass: "test", // You need to require the feature to create a watcher. RequiredProtocolInfo: tarantool.ProtocolInfo{ - Features: []tarantool.ProtocolFeature{tarantool.WatchersFeature}, + Features: []iproto.Feature{iproto.IPROTO_FEATURE_WATCHERS}, }, } ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) @@ -1230,3 +1233,26 @@ func ExampleConnection_CloseGraceful_force() { // Result: // connection closed by client (0x4001) } + +func ExampleWatchOnceRequest() { + const key = "foo" + const value = "bar" + + // WatchOnce request present in Tarantool since version 3.0 + isLess, err := test_helpers.IsTarantoolVersionLess(3, 0, 0) + if err != nil || isLess { + return + } + + conn := exampleConnect(opts) + defer conn.Close() + + conn.Do(tarantool.NewBroadcastRequest(key).Value(value)).Get() + + resp, err := conn.Do(tarantool.NewWatchOnceRequest(key)).Get() + if err != nil { + fmt.Printf("Failed to execute the request: %s\n", err) + } else { + fmt.Println(resp.Data) + } +} diff --git a/export_test.go b/export_test.go index fc5d90c34..38211a4fc 100644 --- a/export_test.go +++ b/export_test.go @@ -120,3 +120,9 @@ func RefImplRollbackBody(enc *msgpack.Encoder) error { func RefImplIdBody(enc *msgpack.Encoder, protocolInfo ProtocolInfo) error { return fillId(enc, protocolInfo) } + +// RefImplWatchOnceBody is reference implementation for filling of an watchOnce +// request's body. +func RefImplWatchOnceBody(enc *msgpack.Encoder, key string) error { + return fillWatchOnce(enc, key) +} diff --git a/go.mod b/go.mod index 22cb7aee3..440597972 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/shopspring/decimal v1.3.1 github.com/stretchr/testify v1.7.1 - github.com/tarantool/go-iproto v0.1.0 + github.com/tarantool/go-iproto v0.1.1-0.20231025103136-cb7894473931 github.com/tarantool/go-openssl v0.0.8-0.20231004103608-336ca939d2ca github.com/vmihailenco/msgpack/v5 v5.3.5 golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 // indirect diff --git a/go.sum b/go.sum index 44d00984f..038e8af34 100644 --- a/go.sum +++ b/go.sum @@ -19,8 +19,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/tarantool/go-iproto v0.1.0 h1:zHN9AA8LDawT+JBD0/Nxgr/bIsWkkpDzpcMuaNPSIAQ= -github.com/tarantool/go-iproto v0.1.0/go.mod h1:LNCtdyZxojUed8SbOiYHoc3v9NvaZTB7p96hUySMlIo= +github.com/tarantool/go-iproto v0.1.1-0.20231025103136-cb7894473931 h1:YrsRc1sDZ6HOZccvM2eJ3Nu2TMBq7NMZMsaT5KCu5qU= +github.com/tarantool/go-iproto v0.1.1-0.20231025103136-cb7894473931/go.mod h1:LNCtdyZxojUed8SbOiYHoc3v9NvaZTB7p96hUySMlIo= github.com/tarantool/go-openssl v0.0.8-0.20231004103608-336ca939d2ca h1:oOrBh73tDDyooIXajfr+0pfnM+89404ClAhJpTTHI7E= github.com/tarantool/go-openssl v0.0.8-0.20231004103608-336ca939d2ca/go.mod h1:M7H4xYSbzqpW/ZRBMyH0eyqQBsnhAMfsYk5mv0yid7A= github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= diff --git a/pool/connection_pool.go b/pool/connection_pool.go index e101b3208..aa84d8c24 100644 --- a/pool/connection_pool.go +++ b/pool/connection_pool.go @@ -17,6 +17,8 @@ import ( "sync" "time" + "github.com/tarantool/go-iproto" + "github.com/tarantool/go-tarantool/v2" ) @@ -911,8 +913,8 @@ func (p *ConnectionPool) NewPrepared(expr string, userMode Mode) (*tarantool.Pre // NewWatcher creates a new Watcher object for the connection pool. // -// You need to require WatchersFeature to use watchers, see examples for the -// function. +// You need to require IPROTO_FEATURE_WATCHERS to use watchers, see examples +// for the function. // // The behavior is same as if Connection.NewWatcher() called for each // connection with a suitable role. @@ -932,14 +934,14 @@ func (p *ConnectionPool) NewWatcher(key string, callback tarantool.WatchCallback, mode Mode) (tarantool.Watcher, error) { watchersRequired := false for _, feature := range p.connOpts.RequiredProtocolInfo.Features { - if tarantool.WatchersFeature == feature { + if iproto.IPROTO_FEATURE_WATCHERS == feature { watchersRequired = true break } } if !watchersRequired { - return nil, errors.New("the feature WatchersFeature must be " + - "required by connection options to create a watcher") + return nil, errors.New("the feature IPROTO_FEATURE_WATCHERS must " + + "be required by connection options to create a watcher") } watcher := &poolWatcher{ diff --git a/pool/connection_pool_test.go b/pool/connection_pool_test.go index b944d0c6c..27310be23 100644 --- a/pool/connection_pool_test.go +++ b/pool/connection_pool_test.go @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/tarantool/go-iproto" "github.com/vmihailenco/msgpack/v5" "github.com/tarantool/go-tarantool/v2" @@ -2832,7 +2833,7 @@ func TestConnectionPool_NewWatcher_noWatchersFeature(t *testing.T) { roles := []bool{true, false, false, true, true} opts := connOpts.Clone() - opts.RequiredProtocolInfo.Features = []tarantool.ProtocolFeature{} + opts.RequiredProtocolInfo.Features = []iproto.Feature{} err := test_helpers.SetClusterRO(servers, opts, roles) require.Nilf(t, err, "fail to set roles for cluster") @@ -2847,8 +2848,8 @@ func TestConnectionPool_NewWatcher_noWatchersFeature(t *testing.T) { func(event tarantool.WatchEvent) {}, pool.ANY) require.Nilf(t, watcher, "watcher must not be created") require.NotNilf(t, err, "an error is expected") - expected := "the feature WatchersFeature must be required by connection " + - "options to create a watcher" + expected := "the feature IPROTO_FEATURE_WATCHERS must be required by " + + "connection options to create a watcher" require.Equal(t, expected, err.Error()) } @@ -2860,8 +2861,8 @@ func TestConnectionPool_NewWatcher_modes(t *testing.T) { roles := []bool{true, false, false, true, true} opts := connOpts.Clone() - opts.RequiredProtocolInfo.Features = []tarantool.ProtocolFeature{ - tarantool.WatchersFeature, + opts.RequiredProtocolInfo.Features = []iproto.Feature{ + iproto.IPROTO_FEATURE_WATCHERS, } err := test_helpers.SetClusterRO(servers, opts, roles) require.Nilf(t, err, "fail to set roles for cluster") @@ -2941,8 +2942,8 @@ func TestConnectionPool_NewWatcher_update(t *testing.T) { roles := []bool{true, false, false, true, true} opts := connOpts.Clone() - opts.RequiredProtocolInfo.Features = []tarantool.ProtocolFeature{ - tarantool.WatchersFeature, + opts.RequiredProtocolInfo.Features = []iproto.Feature{ + iproto.IPROTO_FEATURE_WATCHERS, } err := test_helpers.SetClusterRO(servers, opts, roles) require.Nilf(t, err, "fail to set roles for cluster") @@ -3030,8 +3031,8 @@ func TestWatcher_Unregister(t *testing.T) { roles := []bool{true, false, false, true, true} opts := connOpts.Clone() - opts.RequiredProtocolInfo.Features = []tarantool.ProtocolFeature{ - tarantool.WatchersFeature, + opts.RequiredProtocolInfo.Features = []iproto.Feature{ + iproto.IPROTO_FEATURE_WATCHERS, } err := test_helpers.SetClusterRO(servers, opts, roles) require.Nilf(t, err, "fail to set roles for cluster") @@ -3091,8 +3092,8 @@ func TestConnectionPool_NewWatcher_concurrent(t *testing.T) { roles := []bool{true, false, false, true, true} opts := connOpts.Clone() - opts.RequiredProtocolInfo.Features = []tarantool.ProtocolFeature{ - tarantool.WatchersFeature, + opts.RequiredProtocolInfo.Features = []iproto.Feature{ + iproto.IPROTO_FEATURE_WATCHERS, } err := test_helpers.SetClusterRO(servers, opts, roles) require.Nilf(t, err, "fail to set roles for cluster") @@ -3133,8 +3134,8 @@ func TestWatcher_Unregister_concurrent(t *testing.T) { roles := []bool{true, false, false, true, true} opts := connOpts.Clone() - opts.RequiredProtocolInfo.Features = []tarantool.ProtocolFeature{ - tarantool.WatchersFeature, + opts.RequiredProtocolInfo.Features = []iproto.Feature{ + iproto.IPROTO_FEATURE_WATCHERS, } err := test_helpers.SetClusterRO(servers, opts, roles) require.Nilf(t, err, "fail to set roles for cluster") diff --git a/pool/example_test.go b/pool/example_test.go index dae28de90..a38ae2831 100644 --- a/pool/example_test.go +++ b/pool/example_test.go @@ -2,8 +2,11 @@ package pool_test import ( "fmt" + "strings" "time" + "github.com/tarantool/go-iproto" + "github.com/tarantool/go-tarantool/v2" "github.com/tarantool/go-tarantool/v2/pool" "github.com/tarantool/go-tarantool/v2/test_helpers" @@ -92,8 +95,8 @@ func ExampleConnectionPool_NewWatcher() { const value = "bar" opts := connOpts.Clone() - opts.RequiredProtocolInfo.Features = []tarantool.ProtocolFeature{ - tarantool.WatchersFeature, + opts.RequiredProtocolInfo.Features = []iproto.Feature{ + iproto.IPROTO_FEATURE_WATCHERS, } connPool, err := examplePool(testRoles, connOpts) @@ -123,7 +126,7 @@ func ExampleConnectionPool_NewWatcher_noWatchersFeature() { const key = "foo" opts := connOpts.Clone() - opts.RequiredProtocolInfo.Features = []tarantool.ProtocolFeature{} + opts.RequiredProtocolInfo.Features = []iproto.Feature{} connPool, err := examplePool(testRoles, connOpts) if err != nil { @@ -134,10 +137,19 @@ func ExampleConnectionPool_NewWatcher_noWatchersFeature() { callback := func(event tarantool.WatchEvent) {} watcher, err := connPool.NewWatcher(key, callback, pool.ANY) fmt.Println(watcher) - fmt.Println(err) + if err != nil { + // Need to split the error message into two lines to pass + // golangci-lint. + str := err.Error() + fmt.Println(strings.Trim(str[:56], " ")) + fmt.Println(str[56:]) + } else { + fmt.Println(err) + } // Output: // - // the feature WatchersFeature must be required by connection options to create a watcher + // the feature IPROTO_FEATURE_WATCHERS must be required by + // connection options to create a watcher } func getTestTxnOpts() tarantool.Opts { @@ -146,9 +158,9 @@ func getTestTxnOpts() tarantool.Opts { // Assert that server supports expected protocol features txnOpts.RequiredProtocolInfo = tarantool.ProtocolInfo{ Version: tarantool.ProtocolVersion(1), - Features: []tarantool.ProtocolFeature{ - tarantool.StreamsFeature, - tarantool.TransactionsFeature, + Features: []iproto.Feature{ + iproto.IPROTO_FEATURE_STREAMS, + iproto.IPROTO_FEATURE_TRANSACTIONS, }, } diff --git a/protocol.go b/protocol.go index 63424283b..7de8b197e 100644 --- a/protocol.go +++ b/protocol.go @@ -2,7 +2,6 @@ package tarantool import ( "context" - "fmt" "github.com/tarantool/go-iproto" "github.com/vmihailenco/msgpack/v5" @@ -11,9 +10,6 @@ import ( // ProtocolVersion type stores Tarantool protocol version. type ProtocolVersion uint64 -// ProtocolVersion type stores a Tarantool protocol feature. -type ProtocolFeature uint64 - // ProtocolInfo type aggregates Tarantool protocol version and features info. type ProtocolInfo struct { // Auth is an authentication method. @@ -21,7 +17,7 @@ type ProtocolInfo struct { // Version is the supported protocol version. Version ProtocolVersion // Features are supported protocol features. - Features []ProtocolFeature + Features []iproto.Feature } // Clone returns an exact copy of the ProtocolInfo object. @@ -30,56 +26,20 @@ func (info ProtocolInfo) Clone() ProtocolInfo { infoCopy := info if info.Features != nil { - infoCopy.Features = make([]ProtocolFeature, len(info.Features)) + infoCopy.Features = make([]iproto.Feature, len(info.Features)) copy(infoCopy.Features, info.Features) } return infoCopy } -const ( - // StreamsFeature represents streams support (supported by connector). - StreamsFeature ProtocolFeature = 0 - // TransactionsFeature represents interactive transactions support. - // (supported by connector). - TransactionsFeature ProtocolFeature = 1 - // ErrorExtensionFeature represents support of MP_ERROR objects over MessagePack - // (supported by connector). - ErrorExtensionFeature ProtocolFeature = 2 - // WatchersFeature represents support of watchers - // (supported by connector). - WatchersFeature ProtocolFeature = 3 - // PaginationFeature represents support of pagination - // (supported by connector). - PaginationFeature ProtocolFeature = 4 -) - -// String returns the name of a Tarantool feature. -// If value X is not a known feature, returns "Unknown feature (code X)" string. -func (ftr ProtocolFeature) String() string { - switch ftr { - case StreamsFeature: - return "StreamsFeature" - case TransactionsFeature: - return "TransactionsFeature" - case ErrorExtensionFeature: - return "ErrorExtensionFeature" - case WatchersFeature: - return "WatchersFeature" - case PaginationFeature: - return "PaginationFeature" - default: - return fmt.Sprintf("Unknown feature (code %d)", ftr) - } -} - var clientProtocolInfo ProtocolInfo = ProtocolInfo{ // Protocol version supported by connector. Version 3 // was introduced in Tarantool 2.10.0, version 4 was // introduced in master 948e5cd (possible 2.10.5 or 2.11.0). // Support of protocol version on connector side was introduced in // 1.10.0. - Version: ProtocolVersion(4), + Version: ProtocolVersion(6), // Streams and transactions were introduced in protocol version 1 // (Tarantool 2.10.0), in connector since 1.7.0. // Error extension type was introduced in protocol @@ -88,12 +48,15 @@ var clientProtocolInfo ProtocolInfo = ProtocolInfo{ // connector since 1.10.0. // Pagination were introduced in protocol version 4 (Tarantool 2.11.0), in // connector since 1.11.0. - Features: []ProtocolFeature{ - StreamsFeature, - TransactionsFeature, - ErrorExtensionFeature, - WatchersFeature, - PaginationFeature, + // WatchOnce request type was introduces in protocol version 6 + // (Tarantool 3.0.0), in connector since 2.0.0. + Features: []iproto.Feature{ + iproto.IPROTO_FEATURE_STREAMS, + iproto.IPROTO_FEATURE_TRANSACTIONS, + iproto.IPROTO_FEATURE_ERROR_EXTENSION, + iproto.IPROTO_FEATURE_WATCHERS, + iproto.IPROTO_FEATURE_PAGINATION, + iproto.IPROTO_FEATURE_WATCH_ONCE, }, } diff --git a/protocol_test.go b/protocol_test.go index 6aa94463b..81ed2d3b5 100644 --- a/protocol_test.go +++ b/protocol_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/stretchr/testify/require" + "github.com/tarantool/go-iproto" . "github.com/tarantool/go-tarantool/v2" ) @@ -11,27 +12,17 @@ import ( func TestProtocolInfoClonePreservesFeatures(t *testing.T) { original := ProtocolInfo{ Version: ProtocolVersion(100), - Features: []ProtocolFeature{ProtocolFeature(99), ProtocolFeature(100)}, + Features: []iproto.Feature{iproto.Feature(99), iproto.Feature(100)}, } origCopy := original.Clone() - original.Features[1] = ProtocolFeature(98) + original.Features[1] = iproto.Feature(98) require.Equal(t, origCopy, ProtocolInfo{ Version: ProtocolVersion(100), - Features: []ProtocolFeature{ProtocolFeature(99), ProtocolFeature(100)}, + Features: []iproto.Feature{iproto.Feature(99), iproto.Feature(100)}, }) } - -func TestFeatureStringRepresentation(t *testing.T) { - require.Equal(t, StreamsFeature.String(), "StreamsFeature") - require.Equal(t, TransactionsFeature.String(), "TransactionsFeature") - require.Equal(t, ErrorExtensionFeature.String(), "ErrorExtensionFeature") - require.Equal(t, WatchersFeature.String(), "WatchersFeature") - require.Equal(t, PaginationFeature.String(), "PaginationFeature") - - require.Equal(t, ProtocolFeature(15532).String(), "Unknown feature (code 15532)") -} diff --git a/request.go b/request.go index 917c1fb6a..3332486f4 100644 --- a/request.go +++ b/request.go @@ -166,6 +166,16 @@ func fillPing(enc *msgpack.Encoder) error { return enc.EncodeMapLen(0) } +func fillWatchOnce(enc *msgpack.Encoder, key string) error { + if err := enc.EncodeMapLen(1); err != nil { + return err + } + if err := enc.EncodeUint(uint64(iproto.IPROTO_EVENT_KEY)); err != nil { + return err + } + return enc.EncodeString(key) +} + // Ping sends empty request to Tarantool to check connection. // // Deprecated: the method will be removed in the next major version, @@ -1354,3 +1364,29 @@ func (req *ExecuteRequest) Context(ctx context.Context) *ExecuteRequest { req.ctx = ctx return req } + +// WatchOnceRequest synchronously fetches the value currently associated with a +// specified notification key without subscribing to changes. +type WatchOnceRequest struct { + baseRequest + key string +} + +// NewWatchOnceRequest returns a new watchOnceRequest. +func NewWatchOnceRequest(key string) *WatchOnceRequest { + req := new(WatchOnceRequest) + req.rtype = iproto.IPROTO_WATCH_ONCE + req.key = key + return req +} + +// Body fills an msgpack.Encoder with the watchOnce request body. +func (req *WatchOnceRequest) Body(res SchemaResolver, enc *msgpack.Encoder) error { + return fillWatchOnce(enc, req.key) +} + +// Context sets a passed context to the request. +func (req *WatchOnceRequest) Context(ctx context.Context) *WatchOnceRequest { + req.ctx = ctx + return req +} diff --git a/request_test.go b/request_test.go index f44b12d52..6d08533d3 100644 --- a/request_test.go +++ b/request_test.go @@ -35,7 +35,7 @@ var validStmt *Prepared = &Prepared{StatementID: 1, Conn: &Connection{}} var validProtocolInfo ProtocolInfo = ProtocolInfo{ Version: ProtocolVersion(3), - Features: []ProtocolFeature{StreamsFeature}, + Features: []iproto.Feature{iproto.IPROTO_FEATURE_STREAMS}, } type ValidSchemeResolver struct { @@ -196,6 +196,7 @@ func TestRequestsTypes(t *testing.T) { {req: NewRollbackRequest(), rtype: iproto.IPROTO_ROLLBACK}, {req: NewIdRequest(validProtocolInfo), rtype: iproto.IPROTO_ID}, {req: NewBroadcastRequest(validKey), rtype: iproto.IPROTO_CALL}, + {req: NewWatchOnceRequest(validKey), rtype: iproto.IPROTO_WATCH_ONCE}, } for _, test := range tests { @@ -231,6 +232,7 @@ func TestRequestsAsync(t *testing.T) { {req: NewRollbackRequest(), async: false}, {req: NewIdRequest(validProtocolInfo), async: false}, {req: NewBroadcastRequest(validKey), async: false}, + {req: NewWatchOnceRequest(validKey), async: false}, } for _, test := range tests { @@ -265,6 +267,7 @@ func TestRequestsCtx_default(t *testing.T) { {req: NewRollbackRequest(), expected: nil}, {req: NewIdRequest(validProtocolInfo), expected: nil}, {req: NewBroadcastRequest(validKey), expected: nil}, + {req: NewWatchOnceRequest(validKey), expected: nil}, } for _, test := range tests { @@ -300,6 +303,7 @@ func TestRequestsCtx_setter(t *testing.T) { {req: NewRollbackRequest().Context(ctx), expected: ctx}, {req: NewIdRequest(validProtocolInfo).Context(ctx), expected: ctx}, {req: NewBroadcastRequest(validKey).Context(ctx), expected: ctx}, + {req: NewWatchOnceRequest(validKey).Context(ctx), expected: ctx}, } for _, test := range tests { @@ -823,3 +827,17 @@ func TestBroadcastRequestSetters(t *testing.T) { req := NewBroadcastRequest(validKey).Value(value) assertBodyEqual(t, refBuf.Bytes(), req) } + +func TestWatchOnceRequestDefaultValues(t *testing.T) { + var refBuf bytes.Buffer + + refEnc := msgpack.NewEncoder(&refBuf) + err := RefImplWatchOnceBody(refEnc, validKey) + if err != nil { + t.Errorf("An unexpected RefImplCallBody() error: %q", err.Error()) + return + } + + req := NewWatchOnceRequest(validKey) + assertBodyEqual(t, refBuf.Bytes(), req) +} diff --git a/response.go b/response.go index 7ee314e04..0d6d062b8 100644 --- a/response.go +++ b/response.go @@ -156,7 +156,7 @@ func (resp *Response) decodeBody() (err error) { var l, larr int var stmtID, bindCount uint64 var serverProtocolInfo ProtocolInfo - var feature ProtocolFeature + var feature iproto.Feature var errorExtendedInfo *BoxError = nil d := msgpack.NewDecoder(&resp.buf) @@ -215,7 +215,7 @@ func (resp *Response) decodeBody() (err error) { return err } - serverProtocolInfo.Features = make([]ProtocolFeature, larr) + serverProtocolInfo.Features = make([]iproto.Feature, larr) for i := 0; i < larr; i++ { if err = d.Decode(&feature); err != nil { return err diff --git a/shutdown_test.go b/shutdown_test.go index 412d27ea4..cf4894344 100644 --- a/shutdown_test.go +++ b/shutdown_test.go @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/tarantool/go-iproto" . "github.com/tarantool/go-tarantool/v2" "github.com/tarantool/go-tarantool/v2/test_helpers" ) @@ -25,7 +26,7 @@ var shtdnClntOpts = Opts{ Timeout: 20 * time.Second, Reconnect: 500 * time.Millisecond, MaxReconnects: 10, - RequiredProtocolInfo: ProtocolInfo{Features: []ProtocolFeature{WatchersFeature}}, + RequiredProtocolInfo: ProtocolInfo{Features: []iproto.Feature{iproto.IPROTO_FEATURE_WATCHERS}}, } var shtdnSrvOpts = test_helpers.StartOpts{ InitScript: "config.lua", diff --git a/tarantool_test.go b/tarantool_test.go index d5237dbb1..0ad365286 100644 --- a/tarantool_test.go +++ b/tarantool_test.go @@ -2606,6 +2606,53 @@ func TestConnectionDoSelectRequest(t *testing.T) { testConnectionDoSelectRequestCheck(t, resp, err, false, 10, 1010) } +func TestConnectionDoWatchOnceRequest(t *testing.T) { + test_helpers.SkipIfWatchOnceUnsupported(t) + + conn := test_helpers.ConnectWithValidation(t, server, opts) + defer conn.Close() + + _, err := conn.Do(NewBroadcastRequest("hello").Value("world")).Get() + if err != nil { + t.Fatalf("Failed to create a broadcast : %s", err.Error()) + } + + resp, err := conn.Do(NewWatchOnceRequest("hello")).Get() + if err != nil { + t.Fatalf("Failed to WatchOnce: %s", err.Error()) + } + if resp.Code != OkCode { + t.Errorf("Failed to WatchOnce: wrong code returned %d", resp.Code) + } + if len(resp.Data) < 1 || resp.Data[0] != "world" { + t.Errorf("Failed to WatchOnce: wrong value returned %v", resp.Data) + } +} + +func TestConnectionDoWatchOnceOnEmptyKey(t *testing.T) { + watchOnceNotSupported, err := test_helpers.IsTarantoolVersionLess(3, 0, 0) + if err != nil { + log.Fatalf("Could not check the Tarantool version") + } + if watchOnceNotSupported { + return + } + + conn := test_helpers.ConnectWithValidation(t, server, opts) + defer conn.Close() + + resp, err := conn.Do(NewWatchOnceRequest("notexists!")).Get() + if err != nil { + t.Fatalf("Failed to WatchOnce: %s", err.Error()) + } + if resp.Code != OkCode { + t.Errorf("Failed to WatchOnce: wrong code returned %d", resp.Code) + } + if len(resp.Data) > 0 { + t.Errorf("Failed to WatchOnce: wrong value returned %v", resp.Data) + } +} + func TestConnectionDoSelectRequest_fetch_pos(t *testing.T) { test_helpers.SkipIfPaginationUnsupported(t) @@ -3226,20 +3273,21 @@ func TestConnectionProtocolInfoSupported(t *testing.T) { conn := test_helpers.ConnectWithValidation(t, server, opts) defer conn.Close() - // First Tarantool protocol version (1, StreamsFeature and TransactionsFeature) - // was introduced between 2.10.0-beta1 and 2.10.0-beta2. - // Versions 2 (ErrorExtensionFeature) and 3 (WatchersFeature) were also - // introduced between 2.10.0-beta1 and 2.10.0-beta2. Version 4 - // (PaginationFeature) was introduced in master 948e5cd (possible 2.10.5 or - // 2.11.0). So each release Tarantool >= 2.10 (same as each Tarantool with - // id support) has protocol version >= 3 and first four features. + // First Tarantool protocol version (1, IPROTO_FEATURE_STREAMS and + // IPROTO_FEATURE_TRANSACTIONS) was introduced between 2.10.0-beta1 and + // 2.10.0-beta2. Versions 2 (IPROTO_FEATURE_ERROR_EXTENSION) and + // 3 (IPROTO_FEATURE_WATCHERS) were also introduced between 2.10.0-beta1 and + // 2.10.0-beta2. Version 4 (IPROTO_FEATURE_PAGINATION) was introduced in + // master 948e5cd (possible 2.10.5 or 2.11.0). So each release + // Tarantool >= 2.10 (same as each Tarantool with id support) has protocol + // version >= 3 and first four features. tarantool210ProtocolInfo := ProtocolInfo{ Version: ProtocolVersion(3), - Features: []ProtocolFeature{ - StreamsFeature, - TransactionsFeature, - ErrorExtensionFeature, - WatchersFeature, + Features: []iproto.Feature{ + iproto.IPROTO_FEATURE_STREAMS, + iproto.IPROTO_FEATURE_TRANSACTIONS, + iproto.IPROTO_FEATURE_ERROR_EXTENSION, + iproto.IPROTO_FEATURE_WATCHERS, }, } @@ -3247,13 +3295,14 @@ func TestConnectionProtocolInfoSupported(t *testing.T) { require.Equal(t, clientProtocolInfo, ProtocolInfo{ - Version: ProtocolVersion(4), - Features: []ProtocolFeature{ - StreamsFeature, - TransactionsFeature, - ErrorExtensionFeature, - WatchersFeature, - PaginationFeature, + Version: ProtocolVersion(6), + Features: []iproto.Feature{ + iproto.IPROTO_FEATURE_STREAMS, + iproto.IPROTO_FEATURE_TRANSACTIONS, + iproto.IPROTO_FEATURE_ERROR_EXTENSION, + iproto.IPROTO_FEATURE_WATCHERS, + iproto.IPROTO_FEATURE_PAGINATION, + iproto.IPROTO_FEATURE_WATCH_ONCE, }, }) @@ -3274,17 +3323,17 @@ func TestClientIdRequestObject(t *testing.T) { tarantool210ProtocolInfo := ProtocolInfo{ Version: ProtocolVersion(3), - Features: []ProtocolFeature{ - StreamsFeature, - TransactionsFeature, - ErrorExtensionFeature, - WatchersFeature, + Features: []iproto.Feature{ + iproto.IPROTO_FEATURE_STREAMS, + iproto.IPROTO_FEATURE_TRANSACTIONS, + iproto.IPROTO_FEATURE_ERROR_EXTENSION, + iproto.IPROTO_FEATURE_WATCHERS, }, } req := NewIdRequest(ProtocolInfo{ Version: ProtocolVersion(1), - Features: []ProtocolFeature{StreamsFeature}, + Features: []iproto.Feature{iproto.IPROTO_FEATURE_STREAMS}, }) resp, err := conn.Do(req).Get() require.Nilf(t, err, "No errors on Id request execution") @@ -3310,17 +3359,17 @@ func TestClientIdRequestObjectWithNilContext(t *testing.T) { tarantool210ProtocolInfo := ProtocolInfo{ Version: ProtocolVersion(3), - Features: []ProtocolFeature{ - StreamsFeature, - TransactionsFeature, - ErrorExtensionFeature, - WatchersFeature, + Features: []iproto.Feature{ + iproto.IPROTO_FEATURE_STREAMS, + iproto.IPROTO_FEATURE_TRANSACTIONS, + iproto.IPROTO_FEATURE_ERROR_EXTENSION, + iproto.IPROTO_FEATURE_WATCHERS, }, } req := NewIdRequest(ProtocolInfo{ Version: ProtocolVersion(1), - Features: []ProtocolFeature{StreamsFeature}, + Features: []iproto.Feature{iproto.IPROTO_FEATURE_STREAMS}, }).Context(nil) //nolint resp, err := conn.Do(req).Get() require.Nilf(t, err, "No errors on Id request execution") @@ -3345,7 +3394,7 @@ func TestClientIdRequestObjectWithPassedCanceledContext(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) req := NewIdRequest(ProtocolInfo{ Version: ProtocolVersion(1), - Features: []ProtocolFeature{StreamsFeature}, + Features: []iproto.Feature{iproto.IPROTO_FEATURE_STREAMS}, }).Context(ctx) //nolint cancel() resp, err := conn.Do(req).Get() @@ -3364,13 +3413,14 @@ func TestConnectionProtocolInfoUnsupported(t *testing.T) { require.Equal(t, clientProtocolInfo, ProtocolInfo{ - Version: ProtocolVersion(4), - Features: []ProtocolFeature{ - StreamsFeature, - TransactionsFeature, - ErrorExtensionFeature, - WatchersFeature, - PaginationFeature, + Version: ProtocolVersion(6), + Features: []iproto.Feature{ + iproto.IPROTO_FEATURE_STREAMS, + iproto.IPROTO_FEATURE_TRANSACTIONS, + iproto.IPROTO_FEATURE_ERROR_EXTENSION, + iproto.IPROTO_FEATURE_WATCHERS, + iproto.IPROTO_FEATURE_PAGINATION, + iproto.IPROTO_FEATURE_WATCH_ONCE, }, }) @@ -3384,7 +3434,7 @@ func TestConnectionClientFeaturesUmmutable(t *testing.T) { info := conn.ClientProtocolInfo() infoOrig := info.Clone() - info.Features[0] = ProtocolFeature(15532) + info.Features[0] = iproto.Feature(15532) require.Equal(t, conn.ClientProtocolInfo(), infoOrig) require.NotEqual(t, conn.ClientProtocolInfo(), info) @@ -3398,7 +3448,7 @@ func TestConnectionServerFeaturesUmmutable(t *testing.T) { info := conn.ServerProtocolInfo() infoOrig := info.Clone() - info.Features[0] = ProtocolFeature(15532) + info.Features[0] = iproto.Feature(15532) require.Equal(t, conn.ServerProtocolInfo(), infoOrig) require.NotEqual(t, conn.ServerProtocolInfo(), info) @@ -3444,7 +3494,7 @@ func TestConnectionProtocolFeatureRequirementSuccess(t *testing.T) { connOpts := opts.Clone() connOpts.RequiredProtocolInfo = ProtocolInfo{ - Features: []ProtocolFeature{TransactionsFeature}, + Features: []iproto.Feature{iproto.IPROTO_FEATURE_TRANSACTIONS}, } ctx, cancel := test_helpers.GetConnectContext() @@ -3462,7 +3512,7 @@ func TestConnectionProtocolFeatureRequirementFail(t *testing.T) { connOpts := opts.Clone() connOpts.RequiredProtocolInfo = ProtocolInfo{ - Features: []ProtocolFeature{TransactionsFeature}, + Features: []iproto.Feature{iproto.IPROTO_FEATURE_TRANSACTIONS}, } ctx, cancel := test_helpers.GetConnectContext() @@ -3472,7 +3522,8 @@ func TestConnectionProtocolFeatureRequirementFail(t *testing.T) { require.Nilf(t, conn, "Connect fail") require.NotNilf(t, err, "Got error on connect") require.Contains(t, err.Error(), - "invalid server protocol: protocol feature TransactionsFeature is not supported") + "invalid server protocol: protocol feature "+ + "IPROTO_FEATURE_TRANSACTIONS is not supported") } func TestConnectionProtocolFeatureRequirementManyFail(t *testing.T) { @@ -3480,7 +3531,8 @@ func TestConnectionProtocolFeatureRequirementManyFail(t *testing.T) { connOpts := opts.Clone() connOpts.RequiredProtocolInfo = ProtocolInfo{ - Features: []ProtocolFeature{TransactionsFeature, ProtocolFeature(15532)}, + Features: []iproto.Feature{iproto.IPROTO_FEATURE_TRANSACTIONS, + iproto.Feature(15532)}, } ctx, cancel := test_helpers.GetConnectContext() @@ -3491,8 +3543,8 @@ func TestConnectionProtocolFeatureRequirementManyFail(t *testing.T) { require.NotNilf(t, err, "Got error on connect") require.Contains(t, err.Error(), - "invalid server protocol: protocol features TransactionsFeature, "+ - "Unknown feature (code 15532) are not supported") + "invalid server protocol: protocol features IPROTO_FEATURE_TRANSACTIONS, "+ + "Feature(15532) are not supported") } func TestConnectionFeatureOptsImmutable(t *testing.T) { @@ -3515,7 +3567,7 @@ func TestConnectionFeatureOptsImmutable(t *testing.T) { connOpts.Reconnect = timeout connOpts.MaxReconnects = retries connOpts.RequiredProtocolInfo = ProtocolInfo{ - Features: []ProtocolFeature{TransactionsFeature}, + Features: []iproto.Feature{iproto.IPROTO_FEATURE_TRANSACTIONS}, } // Connect with valid opts @@ -3523,7 +3575,7 @@ func TestConnectionFeatureOptsImmutable(t *testing.T) { defer conn.Close() // Change opts outside - connOpts.RequiredProtocolInfo.Features[0] = ProtocolFeature(15532) + connOpts.RequiredProtocolInfo.Features[0] = iproto.Feature(15532) // Trigger reconnect with opts re-check test_helpers.StopTarantool(inst) @@ -3636,8 +3688,8 @@ func TestConnection_NewWatcher(t *testing.T) { const key = "TestConnection_NewWatcher" connOpts := opts.Clone() - connOpts.RequiredProtocolInfo.Features = []ProtocolFeature{ - WatchersFeature, + connOpts.RequiredProtocolInfo.Features = []iproto.Feature{ + iproto.IPROTO_FEATURE_WATCHERS, } conn := test_helpers.ConnectWithValidation(t, server, connOpts) defer conn.Close() @@ -3672,15 +3724,15 @@ func TestConnection_NewWatcher(t *testing.T) { func TestConnection_NewWatcher_noWatchersFeature(t *testing.T) { const key = "TestConnection_NewWatcher_noWatchersFeature" connOpts := opts.Clone() - connOpts.RequiredProtocolInfo.Features = []ProtocolFeature{} + connOpts.RequiredProtocolInfo.Features = []iproto.Feature{} conn := test_helpers.ConnectWithValidation(t, server, connOpts) defer conn.Close() watcher, err := conn.NewWatcher(key, func(event WatchEvent) {}) require.Nilf(t, watcher, "watcher must not be created") require.NotNilf(t, err, "an error is expected") - expected := "the feature WatchersFeature must be required by connection " + - "options to create a watcher" + expected := "the feature IPROTO_FEATURE_WATCHERS must be required by " + + "connection options to create a watcher" require.Equal(t, expected, err.Error()) } @@ -3707,8 +3759,8 @@ func TestConnection_NewWatcher_reconnect(t *testing.T) { reconnectOpts := opts reconnectOpts.Reconnect = 100 * time.Millisecond reconnectOpts.MaxReconnects = 10 - reconnectOpts.RequiredProtocolInfo.Features = []ProtocolFeature{ - WatchersFeature, + reconnectOpts.RequiredProtocolInfo.Features = []iproto.Feature{ + iproto.IPROTO_FEATURE_WATCHERS, } conn := test_helpers.ConnectWithValidation(t, server, reconnectOpts) defer conn.Close() @@ -3745,8 +3797,8 @@ func TestBroadcastRequest(t *testing.T) { const value = "bar" connOpts := opts.Clone() - connOpts.RequiredProtocolInfo.Features = []ProtocolFeature{ - WatchersFeature, + connOpts.RequiredProtocolInfo.Features = []iproto.Feature{ + iproto.IPROTO_FEATURE_WATCHERS, } conn := test_helpers.ConnectWithValidation(t, server, connOpts) defer conn.Close() @@ -3795,8 +3847,8 @@ func TestBroadcastRequest_multi(t *testing.T) { const key = "TestBroadcastRequest_multi" connOpts := opts.Clone() - connOpts.RequiredProtocolInfo.Features = []ProtocolFeature{ - WatchersFeature, + connOpts.RequiredProtocolInfo.Features = []iproto.Feature{ + iproto.IPROTO_FEATURE_WATCHERS, } conn := test_helpers.ConnectWithValidation(t, server, connOpts) defer conn.Close() @@ -3843,8 +3895,8 @@ func TestConnection_NewWatcher_multiOnKey(t *testing.T) { const value = "bar" connOpts := opts.Clone() - connOpts.RequiredProtocolInfo.Features = []ProtocolFeature{ - WatchersFeature, + connOpts.RequiredProtocolInfo.Features = []iproto.Feature{ + iproto.IPROTO_FEATURE_WATCHERS, } conn := test_helpers.ConnectWithValidation(t, server, connOpts) defer conn.Close() @@ -3906,8 +3958,8 @@ func TestWatcher_Unregister(t *testing.T) { const value = "bar" connOpts := opts.Clone() - connOpts.RequiredProtocolInfo.Features = []ProtocolFeature{ - WatchersFeature, + connOpts.RequiredProtocolInfo.Features = []iproto.Feature{ + iproto.IPROTO_FEATURE_WATCHERS, } conn := test_helpers.ConnectWithValidation(t, server, connOpts) defer conn.Close() @@ -3942,8 +3994,8 @@ func TestConnection_NewWatcher_concurrent(t *testing.T) { const testConcurrency = 1000 const key = "TestConnection_NewWatcher_concurrent" connOpts := opts.Clone() - connOpts.RequiredProtocolInfo.Features = []ProtocolFeature{ - WatchersFeature, + connOpts.RequiredProtocolInfo.Features = []iproto.Feature{ + iproto.IPROTO_FEATURE_WATCHERS, } conn := test_helpers.ConnectWithValidation(t, server, connOpts) defer conn.Close() @@ -3987,8 +4039,8 @@ func TestWatcher_Unregister_concurrent(t *testing.T) { const testConcurrency = 1000 const key = "TestWatcher_Unregister_concurrent" connOpts := opts.Clone() - connOpts.RequiredProtocolInfo.Features = []ProtocolFeature{ - WatchersFeature, + connOpts.RequiredProtocolInfo.Features = []iproto.Feature{ + iproto.IPROTO_FEATURE_WATCHERS, } conn := test_helpers.ConnectWithValidation(t, server, connOpts) defer conn.Close() diff --git a/test_helpers/utils.go b/test_helpers/utils.go index 898ae84e3..d2a941775 100644 --- a/test_helpers/utils.go +++ b/test_helpers/utils.go @@ -192,6 +192,14 @@ func SkipIfPaginationUnsupported(t *testing.T) { SkipIfFeatureUnsupported(t, "pagination", 2, 11, 0) } +// SkipIfWatchOnceUnsupported skips test run if Tarantool without WatchOnce +// request type is used. +func SkipIfWatchOnceUnsupported(t *testing.T) { + t.Helper() + + SkipIfFeatureUnsupported(t, "watch once", 3, 0, 0) +} + // CheckEqualBoxErrors checks equivalence of tarantool.BoxError objects. // // Tarantool errors are not comparable by nature: