From be0642f80a20d02a32a8639ff3c423f733a59a79 Mon Sep 17 00:00:00 2001 From: Georgy Moiseev Date: Tue, 13 Dec 2022 11:26:53 +0300 Subject: [PATCH 1/6] code health: rewrite ext error test skip Use skipIfLess helpers like in other test conditions. Follows #119 --- test_helpers/utils.go | 49 ++++++++++++++----------------------------- 1 file changed, 16 insertions(+), 33 deletions(-) diff --git a/test_helpers/utils.go b/test_helpers/utils.go index 8cf8e2b4b..5747aeeec 100644 --- a/test_helpers/utils.go +++ b/test_helpers/utils.go @@ -142,6 +142,22 @@ func SkipIfIdSupported(t *testing.T) { skipIfGreaterOrEqual(t, "id requests", 2, 10, 0) } +// SkipIfErrorExtendedInfoUnsupported skips test run if Tarantool without +// IPROTO_ERROR (0x52) support is used. +func SkipIfErrorExtendedInfoUnsupported(t *testing.T) { + t.Helper() + + skipIfLess(t, "error extended info", 2, 4, 1) +} + +// SkipIfErrorMessagePackTypeUnsupported skips test run if Tarantool without +// MP_ERROR type over iproto support is used. +func SkipIfErrorMessagePackTypeUnsupported(t *testing.T) { + t.Helper() + + skipIfLess(t, "error type in MessagePack", 2, 10, 0) +} + // CheckEqualBoxErrors checks equivalence of tarantool.BoxError objects. // // Tarantool errors are not comparable by nature: @@ -180,36 +196,3 @@ func CheckEqualBoxErrors(t *testing.T, expected tarantool.BoxError, actual taran } } } - -// SkipIfErrorExtendedInfoUnsupported skips test run if Tarantool without -// IPROTO_ERROR (0x52) support is used. -func SkipIfErrorExtendedInfoUnsupported(t *testing.T) { - t.Helper() - - // Tarantool provides extended error info only since 2.4.1 version. - isLess, err := IsTarantoolVersionLess(2, 4, 1) - if err != nil { - t.Fatalf("Could not check the Tarantool version") - } - - if isLess { - t.Skip("Skipping test for Tarantool without error extended info support") - } -} - -// SkipIfErrorExtendedInfoUnsupported skips test run if Tarantool without -// MP_ERROR type over iproto support is used. -func SkipIfErrorMessagePackTypeUnsupported(t *testing.T) { - t.Helper() - - // Tarantool error type over MessagePack supported only since 2.10.0 version. - isLess, err := IsTarantoolVersionLess(2, 10, 0) - if err != nil { - t.Fatalf("Could not check the Tarantool version") - } - - if isLess { - t.Skip("Skipping test for Tarantool without support of error type over MessagePack") - t.Skip("Skipping test for Tarantool without error extended info support") - } -} From abcafcf8295fd630b11ae9ce9d2629c12a0dae14 Mon Sep 17 00:00:00 2001 From: Georgy Moiseev Date: Mon, 12 Dec 2022 17:14:09 +0300 Subject: [PATCH 2/6] test: change space id range In Tarantool, user space ids starts from 512. You can set arbitrary id or use autoincrement (sequence also starts from 512). Unfortunately, mixing spaces with autoincremented ids and spaces with explicit ids may cause id conflict [1]. Since there are cases when we cannot explicitly set space id (creating a space with SQL), a short range of free ids (from 512 to 515) causes problems. This patch increases range of free ids (now it's from 512 to 615) so it should be ok until [1] is resolved. 1. https://github.com/tarantool/tarantool/issues/8036 Part of #215 --- config.lua | 14 +++++++------- example_custom_unpacking_test.go | 2 +- example_test.go | 24 ++++++++++++------------ multi/config.lua | 4 ++-- multi/multi_test.go | 2 +- tarantool_test.go | 32 ++++++++++++++++---------------- 6 files changed, 39 insertions(+), 39 deletions(-) diff --git a/config.lua b/config.lua index bb976ef43..c8a853ff4 100644 --- a/config.lua +++ b/config.lua @@ -7,7 +7,7 @@ box.cfg{ box.once("init", function() local st = box.schema.space.create('schematest', { - id = 516, + id = 616, temporary = true, if_not_exists = true, field_count = 7, @@ -36,7 +36,7 @@ box.once("init", function() st:truncate() local s = box.schema.space.create('test', { - id = 517, + id = 617, if_not_exists = true, }) s:create_index('primary', { @@ -46,7 +46,7 @@ box.once("init", function() }) local s = box.schema.space.create('teststring', { - id = 518, + id = 618, if_not_exists = true, }) s:create_index('primary', { @@ -56,7 +56,7 @@ box.once("init", function() }) local s = box.schema.space.create('testintint', { - id = 519, + id = 619, if_not_exists = true, }) s:create_index('primary', { @@ -66,7 +66,7 @@ box.once("init", function() }) local s = box.schema.space.create('SQL_TEST', { - id = 520, + id = 620, if_not_exists = true, format = { {name = "NAME0", type = "unsigned"}, @@ -82,7 +82,7 @@ box.once("init", function() s:insert{1, "test", "test"} local s = box.schema.space.create('test_perf', { - id = 521, + id = 621, temporary = true, if_not_exists = true, field_count = 3, @@ -117,7 +117,7 @@ box.once("init", function() end local s = box.schema.space.create('test_error_type', { - id = 522, + id = 622, temporary = true, if_not_exists = true, field_count = 2, diff --git a/example_custom_unpacking_test.go b/example_custom_unpacking_test.go index 4087c5620..26d19f3af 100644 --- a/example_custom_unpacking_test.go +++ b/example_custom_unpacking_test.go @@ -87,7 +87,7 @@ func Example_customUnpacking() { log.Fatalf("Failed to connect: %s", err.Error()) } - spaceNo := uint32(517) + spaceNo := uint32(617) indexNo := uint32(0) tuple := Tuple2{Cid: 777, Orig: "orig", Members: []Member{{"lol", "", 1}, {"wut", "", 3}}} diff --git a/example_test.go b/example_test.go index 538fa93b1..27a962da0 100644 --- a/example_test.go +++ b/example_test.go @@ -51,7 +51,7 @@ func ExampleConnection_Select() { conn.Replace(spaceNo, []interface{}{uint(1111), "hello", "world"}) conn.Replace(spaceNo, []interface{}{uint(1112), "hallo", "werld"}) - resp, err := conn.Select(517, 0, 0, 100, tarantool.IterEq, []interface{}{uint(1111)}) + resp, err := conn.Select(617, 0, 0, 100, tarantool.IterEq, []interface{}{uint(1111)}) if err != nil { fmt.Printf("error in select is %v", err) @@ -75,7 +75,7 @@ func ExampleConnection_SelectTyped() { defer conn.Close() var res []Tuple - err := conn.SelectTyped(517, 0, 0, 100, tarantool.IterEq, tarantool.IntKey{1111}, &res) + err := conn.SelectTyped(617, 0, 0, 100, tarantool.IterEq, tarantool.IntKey{1111}, &res) if err != nil { fmt.Printf("error in select is %v", err) @@ -96,7 +96,7 @@ func ExampleConnection_SelectTyped() { func ExampleConnection_SelectAsync() { conn := example_connect(opts) defer conn.Close() - spaceNo := uint32(517) + spaceNo := uint32(617) conn.Insert(spaceNo, []interface{}{uint(16), "test", "one"}) conn.Insert(spaceNo, []interface{}{uint(17), "test", "one"}) @@ -223,7 +223,7 @@ func ExampleSelectRequest() { conn := example_connect(opts) defer conn.Close() - req := tarantool.NewSelectRequest(517). + req := tarantool.NewSelectRequest(617). Limit(100). Key(tarantool.IntKey{1111}) resp, err := conn.Do(req).Get() @@ -253,7 +253,7 @@ func ExampleUpdateRequest() { conn := example_connect(opts) defer conn.Close() - req := tarantool.NewUpdateRequest(517). + req := tarantool.NewUpdateRequest(617). Key(tarantool.IntKey{1111}). Operations(tarantool.NewOperations().Assign(1, "bye")) resp, err := conn.Do(req).Get() @@ -284,7 +284,7 @@ func ExampleUpsertRequest() { defer conn.Close() var req tarantool.Request - req = tarantool.NewUpsertRequest(517). + req = tarantool.NewUpsertRequest(617). Tuple([]interface{}{uint(1113), "first", "first"}). Operations(tarantool.NewOperations().Assign(1, "updated")) resp, err := conn.Do(req).Get() @@ -305,7 +305,7 @@ func ExampleUpsertRequest() { } fmt.Printf("response is %#v\n", resp.Data) - req = tarantool.NewSelectRequest(517). + req = tarantool.NewSelectRequest(617). Limit(100). Key(tarantool.IntKey{1113}) resp, err = conn.Do(req).Get() @@ -830,12 +830,12 @@ func ExampleSchema() { } space1 := schema.Spaces["test"] - space2 := schema.SpacesById[516] + space2 := schema.SpacesById[616] fmt.Printf("Space 1 ID %d %s\n", space1.Id, space1.Name) fmt.Printf("Space 2 ID %d %s\n", space2.Id, space2.Name) // Output: - // Space 1 ID 517 test - // Space 2 ID 516 schematest + // Space 1 ID 617 test + // Space 2 ID 616 schematest } // Example demonstrates how to retrieve information with space schema. @@ -854,7 +854,7 @@ func ExampleSpace() { // Access Space objects by name or ID. space1 := schema.Spaces["test"] - space2 := schema.SpacesById[516] // It's a map. + space2 := schema.SpacesById[616] // It's a map. fmt.Printf("Space 1 ID %d %s %s\n", space1.Id, space1.Name, space1.Engine) fmt.Printf("Space 1 ID %d %t\n", space1.FieldsCount, space1.Temporary) @@ -875,7 +875,7 @@ func ExampleSpace() { fmt.Printf("SpaceField 2 %s %s\n", spaceField2.Name, spaceField2.Type) // Output: - // Space 1 ID 517 test memtx + // Space 1 ID 617 test memtx // Space 1 ID 0 false // Index 0 primary // &{0 unsigned} &{2 string} diff --git a/multi/config.lua b/multi/config.lua index aca4db9b3..7364c0bd6 100644 --- a/multi/config.lua +++ b/multi/config.lua @@ -13,7 +13,7 @@ rawset(_G, 'get_cluster_nodes', get_cluster_nodes) box.once("init", function() local s = box.schema.space.create('test', { - id = 517, + id = 617, if_not_exists = true, }) s:create_index('primary', {type = 'tree', parts = {1, 'string'}, if_not_exists = true}) @@ -22,7 +22,7 @@ box.once("init", function() box.schema.user.grant('test', 'read,write,execute', 'universe') local sp = box.schema.space.create('SQL_TEST', { - id = 521, + id = 621, if_not_exists = true, format = { {name = "NAME0", type = "unsigned"}, diff --git a/multi/multi_test.go b/multi/multi_test.go index ef07d629b..d3a111a9f 100644 --- a/multi/multi_test.go +++ b/multi/multi_test.go @@ -16,7 +16,7 @@ import ( var server1 = "127.0.0.1:3013" var server2 = "127.0.0.1:3014" -var spaceNo = uint32(517) +var spaceNo = uint32(617) var spaceName = "test" var indexNo = uint32(0) var connOpts = tarantool.Opts{ diff --git a/tarantool_test.go b/tarantool_test.go index de7af9e91..fdb129d07 100644 --- a/tarantool_test.go +++ b/tarantool_test.go @@ -99,7 +99,7 @@ func convertUint64(v interface{}) (result uint64, err error) { } var server = "127.0.0.1:3013" -var spaceNo = uint32(517) +var spaceNo = uint32(617) var spaceName = "test" var indexNo = uint32(0) var indexName = "primary" @@ -1776,29 +1776,29 @@ func TestSchema(t *testing.T) { } var space, space2 *Space var ok bool - if space, ok = schema.SpacesById[516]; !ok { - t.Errorf("space with id = 516 was not found in schema.SpacesById") + if space, ok = schema.SpacesById[616]; !ok { + t.Errorf("space with id = 616 was not found in schema.SpacesById") } if space2, ok = schema.Spaces["schematest"]; !ok { t.Errorf("space with name 'schematest' was not found in schema.SpacesById") } if space != space2 { - t.Errorf("space with id = 516 and space with name schematest are different") + t.Errorf("space with id = 616 and space with name schematest are different") } - if space.Id != 516 { - t.Errorf("space 516 has incorrect Id") + if space.Id != 616 { + t.Errorf("space 616 has incorrect Id") } if space.Name != "schematest" { - t.Errorf("space 516 has incorrect Name") + t.Errorf("space 616 has incorrect Name") } if !space.Temporary { - t.Errorf("space 516 should be temporary") + t.Errorf("space 616 should be temporary") } if space.Engine != "memtx" { - t.Errorf("space 516 engine should be memtx") + t.Errorf("space 616 engine should be memtx") } if space.FieldsCount != 7 { - t.Errorf("space 516 has incorrect fields count") + t.Errorf("space 616 has incorrect fields count") } if space.FieldsById == nil { @@ -1908,20 +1908,20 @@ func TestSchema(t *testing.T) { } var rSpaceNo, rIndexNo uint32 - rSpaceNo, rIndexNo, err = schema.ResolveSpaceIndex(516, 3) - if err != nil || rSpaceNo != 516 || rIndexNo != 3 { + rSpaceNo, rIndexNo, err = schema.ResolveSpaceIndex(616, 3) + if err != nil || rSpaceNo != 616 || rIndexNo != 3 { t.Errorf("numeric space and index params not resolved as-is") } - rSpaceNo, _, err = schema.ResolveSpaceIndex(516, nil) - if err != nil || rSpaceNo != 516 { + rSpaceNo, _, err = schema.ResolveSpaceIndex(616, nil) + if err != nil || rSpaceNo != 616 { t.Errorf("numeric space param not resolved as-is") } rSpaceNo, rIndexNo, err = schema.ResolveSpaceIndex("schematest", "secondary") - if err != nil || rSpaceNo != 516 || rIndexNo != 3 { + if err != nil || rSpaceNo != 616 || rIndexNo != 3 { t.Errorf("symbolic space and index params not resolved") } rSpaceNo, _, err = schema.ResolveSpaceIndex("schematest", nil) - if err != nil || rSpaceNo != 516 { + if err != nil || rSpaceNo != 616 { t.Errorf("symbolic space param not resolved") } _, _, err = schema.ResolveSpaceIndex("schematest22", "secondary") From 6cfd45b78cdec109d0bd436cbc69919d66ad1a9e Mon Sep 17 00:00:00 2001 From: Georgy Moiseev Date: Tue, 13 Dec 2022 16:09:09 +0300 Subject: [PATCH 3/6] test: configure module paths for queue init Set up package paths so queue test instances could be run with arbitrary work_dir. Part of #215 --- Makefile | 2 +- queue/testdata/config.lua | 25 +++++++++++++++++++++++++ queue/testdata/pool.lua | 25 +++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 59961774b..7b6f8ec5f 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,7 @@ clean: .PHONY: deps deps: clean - ( cd ./queue; tarantoolctl rocks install queue 1.2.1 ) + ( cd ./queue/testdata; tarantoolctl rocks install queue 1.2.1 ) .PHONY: datetime-timezones datetime-timezones: diff --git a/queue/testdata/config.lua b/queue/testdata/config.lua index eccb19a68..e0adc069c 100644 --- a/queue/testdata/config.lua +++ b/queue/testdata/config.lua @@ -1,3 +1,28 @@ +-- configure path so that you can run application +-- from outside the root directory +if package.setsearchroot ~= nil then + package.setsearchroot() +else + -- Workaround for rocks loading in tarantool 1.10 + -- It can be removed in tarantool > 2.2 + -- By default, when you do require('mymodule'), tarantool looks into + -- the current working directory and whatever is specified in + -- package.path and package.cpath. If you run your app while in the + -- root directory of that app, everything goes fine, but if you try to + -- start your app with "tarantool myapp/init.lua", it will fail to load + -- its modules, and modules from myapp/.rocks. + local fio = require('fio') + local app_dir = fio.abspath(fio.dirname(arg[0])) + package.path = app_dir .. '/?.lua;' .. package.path + package.path = app_dir .. '/?/init.lua;' .. package.path + package.path = app_dir .. '/.rocks/share/tarantool/?.lua;' .. package.path + package.path = app_dir .. '/.rocks/share/tarantool/?/init.lua;' .. package.path + package.cpath = app_dir .. '/?.so;' .. package.cpath + package.cpath = app_dir .. '/?.dylib;' .. package.cpath + package.cpath = app_dir .. '/.rocks/lib/tarantool/?.so;' .. package.cpath + package.cpath = app_dir .. '/.rocks/lib/tarantool/?.dylib;' .. package.cpath +end + local queue = require('queue') rawset(_G, 'queue', queue) diff --git a/queue/testdata/pool.lua b/queue/testdata/pool.lua index 7c63aa787..1bcc11654 100644 --- a/queue/testdata/pool.lua +++ b/queue/testdata/pool.lua @@ -1,3 +1,28 @@ +-- configure path so that you can run application +-- from outside the root directory +if package.setsearchroot ~= nil then + package.setsearchroot() +else + -- Workaround for rocks loading in tarantool 1.10 + -- It can be removed in tarantool > 2.2 + -- By default, when you do require('mymodule'), tarantool looks into + -- the current working directory and whatever is specified in + -- package.path and package.cpath. If you run your app while in the + -- root directory of that app, everything goes fine, but if you try to + -- start your app with "tarantool myapp/init.lua", it will fail to load + -- its modules, and modules from myapp/.rocks. + local fio = require('fio') + local app_dir = fio.abspath(fio.dirname(arg[0])) + package.path = app_dir .. '/?.lua;' .. package.path + package.path = app_dir .. '/?/init.lua;' .. package.path + package.path = app_dir .. '/.rocks/share/tarantool/?.lua;' .. package.path + package.path = app_dir .. '/.rocks/share/tarantool/?/init.lua;' .. package.path + package.cpath = app_dir .. '/?.so;' .. package.cpath + package.cpath = app_dir .. '/?.dylib;' .. package.cpath + package.cpath = app_dir .. '/.rocks/lib/tarantool/?.so;' .. package.cpath + package.cpath = app_dir .. '/.rocks/lib/tarantool/?.dylib;' .. package.cpath +end + local queue = require('queue') rawset(_G, 'queue', queue) From d092de68096ccc4efd8a256120224f0c7a9ca5c7 Mon Sep 17 00:00:00 2001 From: Georgy Moiseev Date: Mon, 12 Dec 2022 18:01:31 +0300 Subject: [PATCH 4/6] test: generate tmp work dirs Generate tmp work directories for each new Tarantool instance, if not specified. It prevents possible directory clash issues. Later `ioutil.TempDir` (deprecated since Go 1.17) must be replaced with `os.MkdirTemp` (introduced in Go 1.16 as a replacement) when we drop support of Go 1.16 an older. Part of #215 --- connection_pool/connection_pool_test.go | 6 +--- datetime/datetime_test.go | 1 - decimal/decimal_test.go | 1 - multi/multi_test.go | 2 -- queue/queue_test.go | 4 +-- ssl_test.go | 1 - tarantool_test.go | 2 -- test_helpers/main.go | 45 ++++++++++++++++--------- test_helpers/pool_helper.go | 9 +++-- uuid/uuid_test.go | 1 - 10 files changed, 39 insertions(+), 33 deletions(-) diff --git a/connection_pool/connection_pool_test.go b/connection_pool/connection_pool_test.go index 8dc8cc9da..970612b5e 100644 --- a/connection_pool/connection_pool_test.go +++ b/connection_pool/connection_pool_test.go @@ -2379,10 +2379,6 @@ func runTestMain(m *testing.M) int { waitStart := 100 * time.Millisecond connectRetry := 3 retryTimeout := 500 * time.Millisecond - workDirs := []string{ - "work_dir1", "work_dir2", - "work_dir3", "work_dir4", - "work_dir5"} // Tarantool supports streams and interactive transactions since version 2.10.0 isStreamUnsupported, err := test_helpers.IsTarantoolVersionLess(2, 10, 0) @@ -2390,7 +2386,7 @@ func runTestMain(m *testing.M) int { log.Fatalf("Could not check the Tarantool version") } - instances, err = test_helpers.StartTarantoolInstances(servers, workDirs, test_helpers.StartOpts{ + instances, err = test_helpers.StartTarantoolInstances(servers, nil, test_helpers.StartOpts{ InitScript: initScript, User: connOpts.User, Pass: connOpts.Pass, diff --git a/datetime/datetime_test.go b/datetime/datetime_test.go index 5c63d0f4c..f4cc8b2a1 100644 --- a/datetime/datetime_test.go +++ b/datetime/datetime_test.go @@ -1141,7 +1141,6 @@ func runTestMain(m *testing.M) int { instance, err := test_helpers.StartTarantool(test_helpers.StartOpts{ InitScript: "config.lua", Listen: server, - WorkDir: "work_dir", User: opts.User, Pass: opts.Pass, WaitStart: 100 * time.Millisecond, diff --git a/decimal/decimal_test.go b/decimal/decimal_test.go index 6fba0cc8f..d81e6c488 100644 --- a/decimal/decimal_test.go +++ b/decimal/decimal_test.go @@ -613,7 +613,6 @@ func runTestMain(m *testing.M) int { instance, err := test_helpers.StartTarantool(test_helpers.StartOpts{ InitScript: "config.lua", Listen: server, - WorkDir: "work_dir", User: opts.User, Pass: opts.Pass, WaitStart: 100 * time.Millisecond, diff --git a/multi/multi_test.go b/multi/multi_test.go index d3a111a9f..3b395864c 100644 --- a/multi/multi_test.go +++ b/multi/multi_test.go @@ -592,7 +592,6 @@ func runTestMain(m *testing.M) int { inst1, err := test_helpers.StartTarantool(test_helpers.StartOpts{ InitScript: initScript, Listen: server1, - WorkDir: "work_dir1", User: connOpts.User, Pass: connOpts.Pass, WaitStart: waitStart, @@ -609,7 +608,6 @@ func runTestMain(m *testing.M) int { inst2, err := test_helpers.StartTarantool(test_helpers.StartOpts{ InitScript: initScript, Listen: server2, - WorkDir: "work_dir2", User: connOpts.User, Pass: connOpts.Pass, WaitStart: waitStart, diff --git a/queue/queue_test.go b/queue/queue_test.go index 85276e4a8..905fefc32 100644 --- a/queue/queue_test.go +++ b/queue/queue_test.go @@ -899,7 +899,6 @@ func runTestMain(m *testing.M) int { inst, err := test_helpers.StartTarantool(test_helpers.StartOpts{ InitScript: "testdata/config.lua", Listen: server, - WorkDir: "work_dir", User: opts.User, Pass: opts.Pass, WaitStart: 100 * time.Millisecond, @@ -913,7 +912,6 @@ func runTestMain(m *testing.M) int { defer test_helpers.StopTarantoolWithCleanup(inst) - workDirs := []string{"work_dir1", "work_dir2"} poolOpts := test_helpers.StartOpts{ InitScript: "testdata/pool.lua", User: opts.User, @@ -921,7 +919,7 @@ func runTestMain(m *testing.M) int { WaitStart: 3 * time.Second, // replication_timeout * 3 ConnectRetry: -1, } - instances, err = test_helpers.StartTarantoolInstances(serversPool, workDirs, poolOpts) + instances, err = test_helpers.StartTarantoolInstances(serversPool, nil, poolOpts) if err != nil { log.Fatalf("Failed to prepare test tarantool pool: %s", err) diff --git a/ssl_test.go b/ssl_test.go index 36ce4905f..769508284 100644 --- a/ssl_test.go +++ b/ssl_test.go @@ -126,7 +126,6 @@ func serverTnt(serverOpts, clientOpts SslOpts) (test_helpers.TarantoolInstance, ClientServer: tntHost, ClientTransport: "ssl", ClientSsl: clientOpts, - WorkDir: "work_dir_ssl", User: "test", Pass: "test", WaitStart: 100 * time.Millisecond, diff --git a/tarantool_test.go b/tarantool_test.go index fdb129d07..96bf02254 100644 --- a/tarantool_test.go +++ b/tarantool_test.go @@ -23,7 +23,6 @@ import ( var startOpts test_helpers.StartOpts = test_helpers.StartOpts{ InitScript: "config.lua", Listen: server, - WorkDir: "work_dir", User: opts.User, Pass: opts.Pass, WaitStart: 100 * time.Millisecond, @@ -3317,7 +3316,6 @@ func TestConnection_NewWatcher_reconnect(t *testing.T) { inst, err := test_helpers.StartTarantool(test_helpers.StartOpts{ InitScript: "config.lua", Listen: server, - WorkDir: "work_dir", User: opts.User, Pass: opts.Pass, WaitStart: 100 * time.Millisecond, diff --git a/test_helpers/main.go b/test_helpers/main.go index 77e22c535..f6e745617 100644 --- a/test_helpers/main.go +++ b/test_helpers/main.go @@ -47,9 +47,10 @@ type StartOpts struct { // a Tarantool instance. ClientSsl tarantool.SslOpts - // WorkDir is box.cfg work_dir parameter for tarantool. - // Specify folder to store tarantool data files. - // Folder must be unique for each tarantool process used simultaneously. + // WorkDir is box.cfg work_dir parameter for a Tarantool instance: + // a folder to store data files. If not specified, helpers create a + // new temporary directory. + // Folder must be unique for each Tarantool process used simultaneously. // https://www.tarantool.io/en/doc/latest/reference/configuration/#confval-work_dir WorkDir string @@ -189,6 +190,32 @@ func RestartTarantool(inst *TarantoolInstance) error { func StartTarantool(startOpts StartOpts) (TarantoolInstance, error) { // Prepare tarantool command. var inst TarantoolInstance + var dir string + var err error + + if startOpts.WorkDir == "" { + // Create work_dir for a new instance. + // TO DO: replace with `os.MkdirTemp` when we drop support of + // Go 1.16 an older + dir, err = ioutil.TempDir("", "work_dir") + if err != nil { + return inst, err + } + startOpts.WorkDir = dir + } else { + // Clean up existing work_dir. + err = os.RemoveAll(startOpts.WorkDir) + if err != nil { + return inst, err + } + + // Create work_dir. + err = os.Mkdir(startOpts.WorkDir, 0755) + if err != nil { + return inst, err + } + } + inst.Cmd = exec.Command("tarantool", startOpts.InitScript) inst.Cmd.Env = append( @@ -198,18 +225,6 @@ func StartTarantool(startOpts StartOpts) (TarantoolInstance, error) { fmt.Sprintf("TEST_TNT_MEMTX_USE_MVCC_ENGINE=%t", startOpts.MemtxUseMvccEngine), ) - // Clean up existing work_dir. - err := os.RemoveAll(startOpts.WorkDir) - if err != nil { - return inst, err - } - - // Create work_dir. - err = os.Mkdir(startOpts.WorkDir, 0755) - if err != nil { - return inst, err - } - // Copy SSL certificates. if startOpts.SslCertsDir != "" { err = copySslCerts(startOpts.WorkDir, startOpts.SslCertsDir) diff --git a/test_helpers/pool_helper.go b/test_helpers/pool_helper.go index 0061870de..8c3a7e6ff 100644 --- a/test_helpers/pool_helper.go +++ b/test_helpers/pool_helper.go @@ -219,7 +219,8 @@ func SetClusterRO(servers []string, connOpts tarantool.Opts, roles []bool) error } func StartTarantoolInstances(servers []string, workDirs []string, opts StartOpts) ([]TarantoolInstance, error) { - if len(servers) != len(workDirs) { + isUserWorkDirs := (workDirs != nil) + if isUserWorkDirs && (len(servers) != len(workDirs)) { return nil, fmt.Errorf("number of servers should be equal to number of workDirs") } @@ -227,7 +228,11 @@ func StartTarantoolInstances(servers []string, workDirs []string, opts StartOpts for i, server := range servers { opts.Listen = server - opts.WorkDir = workDirs[i] + if isUserWorkDirs { + opts.WorkDir = workDirs[i] + } else { + opts.WorkDir = "" + } instance, err := StartTarantool(opts) if err != nil { diff --git a/uuid/uuid_test.go b/uuid/uuid_test.go index 6224a938b..e04ab5ab9 100644 --- a/uuid/uuid_test.go +++ b/uuid/uuid_test.go @@ -155,7 +155,6 @@ func runTestMain(m *testing.M) int { inst, err := test_helpers.StartTarantool(test_helpers.StartOpts{ InitScript: "config.lua", Listen: server, - WorkDir: "work_dir", User: opts.User, Pass: opts.Pass, WaitStart: 100 * time.Millisecond, From 0ce5ea54ee1e7558a81b8bc87fd4881d5b265960 Mon Sep 17 00:00:00 2001 From: Georgy Moiseev Date: Tue, 13 Dec 2022 16:15:11 +0300 Subject: [PATCH 5/6] test: improve skip helpers Make skip helpers public. Add more wrappers to reduce code duplication. Part of #215 --- test_helpers/utils.go | 50 +++++++++++++++++++++++++++++++++---------- 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/test_helpers/utils.go b/test_helpers/utils.go index 5747aeeec..4862d90d8 100644 --- a/test_helpers/utils.go +++ b/test_helpers/utils.go @@ -1,6 +1,7 @@ package test_helpers import ( + "fmt" "testing" "time" @@ -75,7 +76,8 @@ func SkipIfSQLUnsupported(t testing.TB) { } } -func skipIfLess(t *testing.T, feature string, major, minor, patch uint64) { +// SkipIfLess skips test run if Tarantool version is less than expected. +func SkipIfLess(t *testing.T, reason string, major, minor, patch uint64) { t.Helper() isLess, err := IsTarantoolVersionLess(major, minor, patch) @@ -84,11 +86,13 @@ func skipIfLess(t *testing.T, feature string, major, minor, patch uint64) { } if isLess { - t.Skipf("Skipping test for Tarantool without %s support", feature) + t.Skipf("Skipping test for Tarantool %s", reason) } } -func skipIfGreaterOrEqual(t *testing.T, feature string, major, minor, patch uint64) { +// SkipIfGreaterOrEqual skips test run if Tarantool version is greater or equal +// than expected. +func SkipIfGreaterOrEqual(t *testing.T, reason string, major, minor, patch uint64) { t.Helper() isLess, err := IsTarantoolVersionLess(major, minor, patch) @@ -97,16 +101,40 @@ func skipIfGreaterOrEqual(t *testing.T, feature string, major, minor, patch uint } if !isLess { - t.Skipf("Skipping test for Tarantool with %s support", feature) + t.Skipf("Skipping test for Tarantool %s", reason) } } +// SkipIfFeatureUnsupported skips test run if Tarantool does not yet support a feature. +func SkipIfFeatureUnsupported(t *testing.T, feature string, major, minor, patch uint64) { + t.Helper() + + SkipIfLess(t, fmt.Sprintf("without %s support", feature), major, minor, patch) +} + +// SkipIfFeatureSupported skips test run if Tarantool supports a feature. +// Helper if useful when we want to test if everything is alright +// on older versions. +func SkipIfFeatureSupported(t *testing.T, feature string, major, minor, patch uint64) { + t.Helper() + + SkipIfGreaterOrEqual(t, fmt.Sprintf("with %s support", feature), major, minor, patch) +} + +// SkipIfFeatureDropped skips test run if Tarantool had dropped +// support of a feature. +func SkipIfFeatureDropped(t *testing.T, feature string, major, minor, patch uint64) { + t.Helper() + + SkipIfGreaterOrEqual(t, fmt.Sprintf("with %s support dropped", feature), major, minor, patch) +} + // SkipOfStreamsUnsupported skips test run if Tarantool without streams // support is used. func SkipIfStreamsUnsupported(t *testing.T) { t.Helper() - skipIfLess(t, "streams", 2, 10, 0) + SkipIfFeatureUnsupported(t, "streams", 2, 10, 0) } // SkipOfStreamsUnsupported skips test run if Tarantool without watchers @@ -114,7 +142,7 @@ func SkipIfStreamsUnsupported(t *testing.T) { func SkipIfWatchersUnsupported(t *testing.T) { t.Helper() - skipIfLess(t, "watchers", 2, 10, 0) + SkipIfFeatureUnsupported(t, "watchers", 2, 10, 0) } // SkipIfWatchersSupported skips test run if Tarantool with watchers @@ -122,7 +150,7 @@ func SkipIfWatchersUnsupported(t *testing.T) { func SkipIfWatchersSupported(t *testing.T) { t.Helper() - skipIfGreaterOrEqual(t, "watchers", 2, 10, 0) + SkipIfFeatureSupported(t, "watchers", 2, 10, 0) } // SkipIfIdUnsupported skips test run if Tarantool without @@ -130,7 +158,7 @@ func SkipIfWatchersSupported(t *testing.T) { func SkipIfIdUnsupported(t *testing.T) { t.Helper() - skipIfLess(t, "id requests", 2, 10, 0) + SkipIfFeatureUnsupported(t, "id requests", 2, 10, 0) } // SkipIfIdSupported skips test run if Tarantool with @@ -139,7 +167,7 @@ func SkipIfIdUnsupported(t *testing.T) { func SkipIfIdSupported(t *testing.T) { t.Helper() - skipIfGreaterOrEqual(t, "id requests", 2, 10, 0) + SkipIfFeatureSupported(t, "id requests", 2, 10, 0) } // SkipIfErrorExtendedInfoUnsupported skips test run if Tarantool without @@ -147,7 +175,7 @@ func SkipIfIdSupported(t *testing.T) { func SkipIfErrorExtendedInfoUnsupported(t *testing.T) { t.Helper() - skipIfLess(t, "error extended info", 2, 4, 1) + SkipIfFeatureUnsupported(t, "error extended info", 2, 4, 1) } // SkipIfErrorMessagePackTypeUnsupported skips test run if Tarantool without @@ -155,7 +183,7 @@ func SkipIfErrorExtendedInfoUnsupported(t *testing.T) { func SkipIfErrorMessagePackTypeUnsupported(t *testing.T) { t.Helper() - skipIfLess(t, "error type in MessagePack", 2, 10, 0) + SkipIfFeatureUnsupported(t, "error type in MessagePack", 2, 10, 0) } // CheckEqualBoxErrors checks equivalence of tarantool.BoxError objects. From 7665568eb61778f40561c1f5cf88e1e3a27feb3e Mon Sep 17 00:00:00 2001 From: Georgy Moiseev Date: Tue, 13 Dec 2022 14:14:47 +0300 Subject: [PATCH 6/6] api: support session settings Support session settings in a separate subpackage "settings" [1]. It allows to create a specific requests to get or set session settings. Settings are independent for each connection. 1. https://www.tarantool.io/en/doc/latest/reference/reference_lua/box_space/_session_settings/ Closes #215 --- CHANGELOG.md | 1 + Makefile | 6 + settings/const.go | 21 + settings/example_test.go | 78 ++++ settings/msgpack.go | 10 + settings/msgpack_helper_test.go | 22 + settings/msgpack_v5.go | 10 + settings/msgpack_v5_helper_test.go | 25 ++ settings/request.go | 273 ++++++++++++ settings/request_test.go | 118 ++++++ settings/tarantool_test.go | 656 +++++++++++++++++++++++++++++ settings/testdata/config.lua | 15 + 12 files changed, 1235 insertions(+) create mode 100644 settings/const.go create mode 100644 settings/example_test.go create mode 100644 settings/msgpack.go create mode 100644 settings/msgpack_helper_test.go create mode 100644 settings/msgpack_v5.go create mode 100644 settings/msgpack_v5_helper_test.go create mode 100644 settings/request.go create mode 100644 settings/request_test.go create mode 100644 settings/tarantool_test.go create mode 100644 settings/testdata/config.lua diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c2c49d39..7930b34d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release. - Support errors extended information (#209) - Error type support in MessagePack (#209) - Event subscription support (#119) +- Session settings support (#215) ### Changed diff --git a/Makefile b/Makefile index 7b6f8ec5f..718139967 100644 --- a/Makefile +++ b/Makefile @@ -81,6 +81,12 @@ test-uuid: go clean -testcache go test -tags "$(TAGS)" ./uuid/ -v -p 1 +.PHONY: test-settings +test-settings: + @echo "Running tests in settings package" + go clean -testcache + go test -tags "$(TAGS)" ./settings/ -v -p 1 + .PHONY: test-main test-main: @echo "Running tests in main package" diff --git a/settings/const.go b/settings/const.go new file mode 100644 index 000000000..cc980cd7a --- /dev/null +++ b/settings/const.go @@ -0,0 +1,21 @@ +package settings + +const sessionSettingsSpace string = "_session_settings" + +// In Go and IPROTO_UPDATE count starts with 0. +const sessionSettingValueField int = 1 + +const ( + errorMarshalingEnabled string = "error_marshaling_enabled" + sqlDefaultEngine string = "sql_default_engine" + sqlDeferForeignKeys string = "sql_defer_foreign_keys" + sqlFullColumnNames string = "sql_full_column_names" + sqlFullMetadata string = "sql_full_metadata" + sqlParserDebug string = "sql_parser_debug" + sqlRecursiveTriggers string = "sql_recursive_triggers" + sqlReverseUnorderedSelects string = "sql_reverse_unordered_selects" + sqlSelectDebug string = "sql_select_debug" + sqlVDBEDebug string = "sql_vdbe_debug" +) + +const selectAllLimit uint32 = 1000 diff --git a/settings/example_test.go b/settings/example_test.go new file mode 100644 index 000000000..a2391328f --- /dev/null +++ b/settings/example_test.go @@ -0,0 +1,78 @@ +package settings_test + +import ( + "fmt" + + "github.com/tarantool/go-tarantool" + "github.com/tarantool/go-tarantool/settings" + "github.com/tarantool/go-tarantool/test_helpers" +) + +func example_connect(opts tarantool.Opts) *tarantool.Connection { + conn, err := tarantool.Connect(server, opts) + if err != nil { + panic("Connection is not established: " + err.Error()) + } + return conn +} + +func Example_sqlFullColumnNames() { + var resp *tarantool.Response + var err error + var isLess bool + + conn := example_connect(opts) + defer conn.Close() + + // Tarantool supports session settings since version 2.3.1 + isLess, err = test_helpers.IsTarantoolVersionLess(2, 3, 1) + if err != nil || isLess { + return + } + + // Create a space. + _, err = conn.Execute("CREATE TABLE example(id INT PRIMARY KEY, x INT);", []interface{}{}) + if err != nil { + fmt.Printf("error in create table: %v\n", err) + return + } + + // Insert some tuple into space. + _, err = conn.Execute("INSERT INTO example VALUES (1, 1);", []interface{}{}) + if err != nil { + fmt.Printf("error on insert: %v\n", err) + return + } + + // Enable showing full column names in SQL responses. + _, err = conn.Do(settings.NewSQLFullColumnNamesSetRequest(true)).Get() + if err != nil { + fmt.Printf("error on setting setup: %v\n", err) + return + } + + // Get some data with SQL query. + resp, err = conn.Execute("SELECT x FROM example WHERE id = 1;", []interface{}{}) + if err != nil { + fmt.Printf("error on select: %v\n", err) + return + } + // Show response metadata. + fmt.Printf("full column name: %v\n", resp.MetaData[0].FieldName) + + // Disable showing full column names in SQL responses. + _, err = conn.Do(settings.NewSQLFullColumnNamesSetRequest(false)).Get() + if err != nil { + fmt.Printf("error on setting setup: %v\n", err) + return + } + + // Get some data with SQL query. + resp, err = conn.Execute("SELECT x FROM example WHERE id = 1;", []interface{}{}) + if err != nil { + fmt.Printf("error on select: %v\n", err) + return + } + // Show response metadata. + fmt.Printf("short column name: %v\n", resp.MetaData[0].FieldName) +} diff --git a/settings/msgpack.go b/settings/msgpack.go new file mode 100644 index 000000000..295620aba --- /dev/null +++ b/settings/msgpack.go @@ -0,0 +1,10 @@ +//go:build !go_tarantool_msgpack_v5 +// +build !go_tarantool_msgpack_v5 + +package settings + +import ( + "gopkg.in/vmihailenco/msgpack.v2" +) + +type encoder = msgpack.Encoder diff --git a/settings/msgpack_helper_test.go b/settings/msgpack_helper_test.go new file mode 100644 index 000000000..0c002213a --- /dev/null +++ b/settings/msgpack_helper_test.go @@ -0,0 +1,22 @@ +//go:build !go_tarantool_msgpack_v5 +// +build !go_tarantool_msgpack_v5 + +package settings_test + +import ( + "io" + + "github.com/tarantool/go-tarantool" + "gopkg.in/vmihailenco/msgpack.v2" +) + +type encoder = msgpack.Encoder + +func NewEncoder(w io.Writer) *encoder { + return msgpack.NewEncoder(w) +} + +func toBoxError(i interface{}) (v tarantool.BoxError, ok bool) { + v, ok = i.(tarantool.BoxError) + return +} diff --git a/settings/msgpack_v5.go b/settings/msgpack_v5.go new file mode 100644 index 000000000..288418ec6 --- /dev/null +++ b/settings/msgpack_v5.go @@ -0,0 +1,10 @@ +//go:build go_tarantool_msgpack_v5 +// +build go_tarantool_msgpack_v5 + +package settings + +import ( + "github.com/vmihailenco/msgpack/v5" +) + +type encoder = msgpack.Encoder diff --git a/settings/msgpack_v5_helper_test.go b/settings/msgpack_v5_helper_test.go new file mode 100644 index 000000000..96df6bae1 --- /dev/null +++ b/settings/msgpack_v5_helper_test.go @@ -0,0 +1,25 @@ +//go:build go_tarantool_msgpack_v5 +// +build go_tarantool_msgpack_v5 + +package settings_test + +import ( + "io" + + "github.com/tarantool/go-tarantool" + "github.com/vmihailenco/msgpack/v5" +) + +type encoder = msgpack.Encoder + +func NewEncoder(w io.Writer) *encoder { + return msgpack.NewEncoder(w) +} + +func toBoxError(i interface{}) (v tarantool.BoxError, ok bool) { + var ptr *tarantool.BoxError + if ptr, ok = i.(*tarantool.BoxError); ok { + v = *ptr + } + return +} diff --git a/settings/request.go b/settings/request.go new file mode 100644 index 000000000..84723bb2d --- /dev/null +++ b/settings/request.go @@ -0,0 +1,273 @@ +// Package settings is a collection of requests to set a connection session setting +// or get current session configuration. +// +// +============================+=========================+=========+===========================+ +// | Setting | Meaning | Default | Supported in | +// | | | | Tarantool versions | +// +============================+=========================+=========+===========================+ +// | ErrorMarshalingEnabled | Defines whether error | false | Since 2.4.1 till 2.10.0, | +// | | objectshave a special | | replaced with IPROTO_ID | +// | | structure. | | feature flag. | +// +----------------------------+-------------------------+---------+---------------------------+ +// | SQLDefaultEngine | Defines default storage | "memtx" | Since 2.3.1. | +// | | engine for new SQL | | | +// | | tables. | | | +// +----------------------------+-------------------------+---------+---------------------------+ +// | SQLDeferForeignKeys | Defines whether | false | Since 2.3.1 till master | +// | | foreign-key checks can | | commit 14618c4 (possible | +// | | wait till commit. | | 2.10.5 or 2.11.0) | +// +----------------------------+-------------------------+---------+---------------------------+ +// | SQLFullColumnNames | Defines whether full | false | Since 2.3.1. | +// | | column names is | | | +// | | displayed in SQL result | | | +// | | set metadata. | | | +// +----------------------------+-------------------------+---------+---------------------------+ +// | SQLFullMetadata | Defines whether SQL | false | Since 2.3.1. | +// | | result set metadata | | | +// | | will have more than | | | +// | | just name and type. | | | +// +----------------------------+-------------------------+---------+---------------------------+ +// | SQLParserDebug | Defines whether to show | false | Since 2.3.1 (only if | +// | | parser steps for | | built with | +// | | following statements. | | -DCMAKE_BUILD_TYPE=Debug) | +// +----------------------------+-------------------------+---------+---------------------------+ +// | SQLRecursiveTriggers | Defines whether a | true | Since 2.3.1. | +// | | triggered statement can | | | +// | | activate a trigger. | | | +// +----------------------------+-------------------------+---------+---------------------------+ +// | SQLReverseUnorderedSelects | Defines defines whether | false | Since 2.3.1. | +// | | result rows are usually | | | +// | | in reverse order if | | | +// | | there is no ORDER BY | | | +// | | clause. | | | +// +----------------------------+-------------------------+---------+---------------------------+ +// | SQLSelectDebug | Defines whether to show | false | Since 2.3.1 (only if | +// | | to show execution steps | | built with | +// | | during SELECT. | | -DCMAKE_BUILD_TYPE=Debug) | +// +----------------------------+-------------------------+---------+---------------------------+ +// | SQLVDBEDebug | Defines whether VDBE | false | Since 2.3.1 (only if | +// | | debug mode is enabled. | | built with | +// | | | | -DCMAKE_BUILD_TYPE=Debug) | +// +----------------------------+-------------------------+---------+---------------------------+ +// +// Since: 1.10.0 +// +// See also: +// +// * Session settings https://www.tarantool.io/en/doc/latest/reference/reference_lua/box_space/_session_settings/ +package settings + +import ( + "context" + + "github.com/tarantool/go-tarantool" +) + +// SetRequest helps to set session settings. +type SetRequest struct { + impl *tarantool.UpdateRequest +} + +func newSetRequest(setting string, value interface{}) *SetRequest { + return &SetRequest{ + impl: tarantool.NewUpdateRequest(sessionSettingsSpace). + Key(tarantool.StringKey{S: setting}). + Operations(tarantool.NewOperations().Assign(sessionSettingValueField, value)), + } +} + +// Context sets a passed context to set session settings request. +func (req *SetRequest) Context(ctx context.Context) *SetRequest { + req.impl = req.impl.Context(ctx) + + return req +} + +// Code returns IPROTO code for set session settings request. +func (req *SetRequest) Code() int32 { + return req.impl.Code() +} + +// Body fills an encoder with set session settings request body. +func (req *SetRequest) Body(res tarantool.SchemaResolver, enc *encoder) error { + return req.impl.Body(res, enc) +} + +// Ctx returns a context of set session settings request. +func (req *SetRequest) Ctx() context.Context { + return req.impl.Ctx() +} + +// Async returns is set session settings request expects a response. +func (req *SetRequest) Async() bool { + return req.impl.Async() +} + +// GetRequest helps to get session settings. +type GetRequest struct { + impl *tarantool.SelectRequest +} + +func newGetRequest(setting string) *GetRequest { + return &GetRequest{ + impl: tarantool.NewSelectRequest(sessionSettingsSpace). + Key(tarantool.StringKey{S: setting}). + Limit(1), + } +} + +// Context sets a passed context to get session settings request. +func (req *GetRequest) Context(ctx context.Context) *GetRequest { + req.impl = req.impl.Context(ctx) + + return req +} + +// Code returns IPROTO code for get session settings request. +func (req *GetRequest) Code() int32 { + return req.impl.Code() +} + +// Body fills an encoder with get session settings request body. +func (req *GetRequest) Body(res tarantool.SchemaResolver, enc *encoder) error { + return req.impl.Body(res, enc) +} + +// Ctx returns a context of get session settings request. +func (req *GetRequest) Ctx() context.Context { + return req.impl.Ctx() +} + +// Async returns is get session settings request expects a response. +func (req *GetRequest) Async() bool { + return req.impl.Async() +} + +// NewErrorMarshalingEnabledSetRequest creates a request to +// update current session ErrorMarshalingEnabled setting. +func NewErrorMarshalingEnabledSetRequest(value bool) *SetRequest { + return newSetRequest(errorMarshalingEnabled, value) +} + +// NewErrorMarshalingEnabledGetRequest creates a request to get +// current session ErrorMarshalingEnabled setting in tuple format. +func NewErrorMarshalingEnabledGetRequest() *GetRequest { + return newGetRequest(errorMarshalingEnabled) +} + +// NewSQLDefaultEngineSetRequest creates a request to +// update current session SQLDefaultEngine setting. +func NewSQLDefaultEngineSetRequest(value string) *SetRequest { + return newSetRequest(sqlDefaultEngine, value) +} + +// NewSQLDefaultEngineGetRequest creates a request to get +// current session SQLDefaultEngine setting in tuple format. +func NewSQLDefaultEngineGetRequest() *GetRequest { + return newGetRequest(sqlDefaultEngine) +} + +// NewSQLDeferForeignKeysSetRequest creates a request to +// update current session SQLDeferForeignKeys setting. +func NewSQLDeferForeignKeysSetRequest(value bool) *SetRequest { + return newSetRequest(sqlDeferForeignKeys, value) +} + +// NewSQLDeferForeignKeysGetRequest creates a request to get +// current session SQLDeferForeignKeys setting in tuple format. +func NewSQLDeferForeignKeysGetRequest() *GetRequest { + return newGetRequest(sqlDeferForeignKeys) +} + +// NewSQLFullColumnNamesSetRequest creates a request to +// update current session SQLFullColumnNames setting. +func NewSQLFullColumnNamesSetRequest(value bool) *SetRequest { + return newSetRequest(sqlFullColumnNames, value) +} + +// NewSQLFullColumnNamesGetRequest creates a request to get +// current session SQLFullColumnNames setting in tuple format. +func NewSQLFullColumnNamesGetRequest() *GetRequest { + return newGetRequest(sqlFullColumnNames) +} + +// NewSQLFullMetadataSetRequest creates a request to +// update current session SQLFullMetadata setting. +func NewSQLFullMetadataSetRequest(value bool) *SetRequest { + return newSetRequest(sqlFullMetadata, value) +} + +// NewSQLFullMetadataGetRequest creates a request to get +// current session SQLFullMetadata setting in tuple format. +func NewSQLFullMetadataGetRequest() *GetRequest { + return newGetRequest(sqlFullMetadata) +} + +// NewSQLParserDebugSetRequest creates a request to +// update current session SQLParserDebug setting. +func NewSQLParserDebugSetRequest(value bool) *SetRequest { + return newSetRequest(sqlParserDebug, value) +} + +// NewSQLParserDebugGetRequest creates a request to get +// current session SQLParserDebug setting in tuple format. +func NewSQLParserDebugGetRequest() *GetRequest { + return newGetRequest(sqlParserDebug) +} + +// NewSQLRecursiveTriggersSetRequest creates a request to +// update current session SQLRecursiveTriggers setting. +func NewSQLRecursiveTriggersSetRequest(value bool) *SetRequest { + return newSetRequest(sqlRecursiveTriggers, value) +} + +// NewSQLRecursiveTriggersGetRequest creates a request to get +// current session SQLRecursiveTriggers setting in tuple format. +func NewSQLRecursiveTriggersGetRequest() *GetRequest { + return newGetRequest(sqlRecursiveTriggers) +} + +// NewSQLReverseUnorderedSelectsSetRequest creates a request to +// update current session SQLReverseUnorderedSelects setting. +func NewSQLReverseUnorderedSelectsSetRequest(value bool) *SetRequest { + return newSetRequest(sqlReverseUnorderedSelects, value) +} + +// NewSQLReverseUnorderedSelectsGetRequest creates a request to get +// current session SQLReverseUnorderedSelects setting in tuple format. +func NewSQLReverseUnorderedSelectsGetRequest() *GetRequest { + return newGetRequest(sqlReverseUnorderedSelects) +} + +// NewSQLSelectDebugSetRequest creates a request to +// update current session SQLSelectDebug setting. +func NewSQLSelectDebugSetRequest(value bool) *SetRequest { + return newSetRequest(sqlSelectDebug, value) +} + +// NewSQLSelectDebugGetRequest creates a request to get +// current session SQLSelectDebug setting in tuple format. +func NewSQLSelectDebugGetRequest() *GetRequest { + return newGetRequest(sqlSelectDebug) +} + +// NewSQLVDBEDebugSetRequest creates a request to +// update current session SQLVDBEDebug setting. +func NewSQLVDBEDebugSetRequest(value bool) *SetRequest { + return newSetRequest(sqlVDBEDebug, value) +} + +// NewSQLVDBEDebugGetRequest creates a request to get +// current session SQLVDBEDebug setting in tuple format. +func NewSQLVDBEDebugGetRequest() *GetRequest { + return newGetRequest(sqlVDBEDebug) +} + +// NewSessionSettingsGetRequest creates a request to get all +// current session settings in tuple format. +func NewSessionSettingsGetRequest() *GetRequest { + return &GetRequest{ + impl: tarantool.NewSelectRequest(sessionSettingsSpace). + Limit(selectAllLimit), + } +} diff --git a/settings/request_test.go b/settings/request_test.go new file mode 100644 index 000000000..bc26ff058 --- /dev/null +++ b/settings/request_test.go @@ -0,0 +1,118 @@ +package settings_test + +import ( + "bytes" + "context" + "testing" + + "github.com/stretchr/testify/require" + "github.com/tarantool/go-tarantool" + . "github.com/tarantool/go-tarantool/settings" +) + +type ValidSchemeResolver struct { +} + +func (*ValidSchemeResolver) ResolveSpaceIndex(s, i interface{}) (spaceNo, indexNo uint32, err error) { + if s == nil { + if s == "_session_settings" { + spaceNo = 380 + } else { + spaceNo = uint32(s.(int)) + } + } else { + spaceNo = 0 + } + if i != nil { + indexNo = uint32(i.(int)) + } else { + indexNo = 0 + } + + return spaceNo, indexNo, nil +} + +var resolver ValidSchemeResolver + +func TestRequestsAPI(t *testing.T) { + tests := []struct { + req tarantool.Request + async bool + code int32 + }{ + {req: NewErrorMarshalingEnabledSetRequest(false), async: false, code: tarantool.UpdateRequestCode}, + {req: NewErrorMarshalingEnabledGetRequest(), async: false, code: tarantool.SelectRequestCode}, + {req: NewSQLDefaultEngineSetRequest("memtx"), async: false, code: tarantool.UpdateRequestCode}, + {req: NewSQLDefaultEngineGetRequest(), async: false, code: tarantool.SelectRequestCode}, + {req: NewSQLDeferForeignKeysSetRequest(false), async: false, code: tarantool.UpdateRequestCode}, + {req: NewSQLDeferForeignKeysGetRequest(), async: false, code: tarantool.SelectRequestCode}, + {req: NewSQLFullColumnNamesSetRequest(false), async: false, code: tarantool.UpdateRequestCode}, + {req: NewSQLFullColumnNamesGetRequest(), async: false, code: tarantool.SelectRequestCode}, + {req: NewSQLFullMetadataSetRequest(false), async: false, code: tarantool.UpdateRequestCode}, + {req: NewSQLFullMetadataGetRequest(), async: false, code: tarantool.SelectRequestCode}, + {req: NewSQLParserDebugSetRequest(false), async: false, code: tarantool.UpdateRequestCode}, + {req: NewSQLParserDebugGetRequest(), async: false, code: tarantool.SelectRequestCode}, + {req: NewSQLRecursiveTriggersSetRequest(false), async: false, code: tarantool.UpdateRequestCode}, + {req: NewSQLRecursiveTriggersGetRequest(), async: false, code: tarantool.SelectRequestCode}, + {req: NewSQLReverseUnorderedSelectsSetRequest(false), async: false, code: tarantool.UpdateRequestCode}, + {req: NewSQLReverseUnorderedSelectsGetRequest(), async: false, code: tarantool.SelectRequestCode}, + {req: NewSQLSelectDebugSetRequest(false), async: false, code: tarantool.UpdateRequestCode}, + {req: NewSQLSelectDebugGetRequest(), async: false, code: tarantool.SelectRequestCode}, + {req: NewSQLVDBEDebugSetRequest(false), async: false, code: tarantool.UpdateRequestCode}, + {req: NewSQLVDBEDebugGetRequest(), async: false, code: tarantool.SelectRequestCode}, + {req: NewSessionSettingsGetRequest(), async: false, code: tarantool.SelectRequestCode}, + } + + for _, test := range tests { + require.Equal(t, test.async, test.req.Async()) + require.Equal(t, test.code, test.req.Code()) + + var reqBuf bytes.Buffer + enc := NewEncoder(&reqBuf) + require.Nilf(t, test.req.Body(&resolver, enc), "No errors on fill") + } +} + +func TestRequestsCtx(t *testing.T) { + // tarantool.Request interface doesn't have Context() + getTests := []struct { + req *GetRequest + }{ + {req: NewErrorMarshalingEnabledGetRequest()}, + {req: NewSQLDefaultEngineGetRequest()}, + {req: NewSQLDeferForeignKeysGetRequest()}, + {req: NewSQLFullColumnNamesGetRequest()}, + {req: NewSQLFullMetadataGetRequest()}, + {req: NewSQLParserDebugGetRequest()}, + {req: NewSQLRecursiveTriggersGetRequest()}, + {req: NewSQLReverseUnorderedSelectsGetRequest()}, + {req: NewSQLSelectDebugGetRequest()}, + {req: NewSQLVDBEDebugGetRequest()}, + {req: NewSessionSettingsGetRequest()}, + } + + for _, test := range getTests { + var ctx context.Context + require.Equal(t, ctx, test.req.Context(ctx).Ctx()) + } + + setTests := []struct { + req *SetRequest + }{ + {req: NewErrorMarshalingEnabledSetRequest(false)}, + {req: NewSQLDefaultEngineSetRequest("memtx")}, + {req: NewSQLDeferForeignKeysSetRequest(false)}, + {req: NewSQLFullColumnNamesSetRequest(false)}, + {req: NewSQLFullMetadataSetRequest(false)}, + {req: NewSQLParserDebugSetRequest(false)}, + {req: NewSQLRecursiveTriggersSetRequest(false)}, + {req: NewSQLReverseUnorderedSelectsSetRequest(false)}, + {req: NewSQLSelectDebugSetRequest(false)}, + {req: NewSQLVDBEDebugSetRequest(false)}, + } + + for _, test := range setTests { + var ctx context.Context + require.Equal(t, ctx, test.req.Context(ctx).Ctx()) + } +} diff --git a/settings/tarantool_test.go b/settings/tarantool_test.go new file mode 100644 index 000000000..d32767116 --- /dev/null +++ b/settings/tarantool_test.go @@ -0,0 +1,656 @@ +package settings_test + +import ( + "log" + "os" + "testing" + "time" + + "github.com/stretchr/testify/require" + "github.com/tarantool/go-tarantool" + . "github.com/tarantool/go-tarantool/settings" + "github.com/tarantool/go-tarantool/test_helpers" +) + +// There is no way to skip tests in testing.M, +// so we use this variable to pass info +// to each testing.T that it should skip. +var isSettingsSupported = false + +var server = "127.0.0.1:3013" +var opts = tarantool.Opts{ + Timeout: 500 * time.Millisecond, + User: "test", + Pass: "test", +} + +func skipIfSettingsUnsupported(t *testing.T) { + t.Helper() + + if isSettingsSupported == false { + t.Skip("Skipping test for Tarantool without session settings support") + } +} + +func skipIfErrorMarshalingEnabledSettingUnsupported(t *testing.T) { + t.Helper() + + test_helpers.SkipIfFeatureUnsupported(t, "error_marshaling_enabled session setting", 2, 4, 1) + test_helpers.SkipIfFeatureDropped(t, "error_marshaling_enabled session setting", 2, 10, 0) +} + +func skipIfSQLDeferForeignKeysSettingUnsupported(t *testing.T) { + t.Helper() + + test_helpers.SkipIfFeatureUnsupported(t, "sql_defer_foreign_keys session setting", 2, 3, 1) + test_helpers.SkipIfFeatureDropped(t, "sql_defer_foreign_keys session setting", 2, 10, 5) +} + +func TestErrorMarshalingEnabledSetting(t *testing.T) { + skipIfErrorMarshalingEnabledSettingUnsupported(t) + + var resp *tarantool.Response + var err error + + conn := test_helpers.ConnectWithValidation(t, server, opts) + defer conn.Close() + + // Disable receiving box.error as MP_EXT 3. + resp, err = conn.Do(NewErrorMarshalingEnabledSetRequest(false)).Get() + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, []interface{}{[]interface{}{"error_marshaling_enabled", false}}, resp.Data) + + // Fetch current setting value. + resp, err = conn.Do(NewErrorMarshalingEnabledGetRequest()).Get() + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, []interface{}{[]interface{}{"error_marshaling_enabled", false}}, resp.Data) + + // Get a box.Error value. + resp, err = conn.Eval("return box.error.new(box.error.UNKNOWN)", []interface{}{}) + require.Nil(t, err) + require.NotNil(t, resp) + require.IsType(t, "string", resp.Data[0]) + + // Enable receiving box.error as MP_EXT 3. + resp, err = conn.Do(NewErrorMarshalingEnabledSetRequest(true)).Get() + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, []interface{}{[]interface{}{"error_marshaling_enabled", true}}, resp.Data) + + // Fetch current setting value. + resp, err = conn.Do(NewErrorMarshalingEnabledGetRequest()).Get() + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, []interface{}{[]interface{}{"error_marshaling_enabled", true}}, resp.Data) + + // Get a box.Error value. + resp, err = conn.Eval("return box.error.new(box.error.UNKNOWN)", []interface{}{}) + require.Nil(t, err) + require.NotNil(t, resp) + _, ok := toBoxError(resp.Data[0]) + require.True(t, ok) +} + +func TestSQLDefaultEngineSetting(t *testing.T) { + // https://github.com/tarantool/tarantool/blob/680990a082374e4790539215f69d9e9ee39c3307/test/sql/engine.test.lua + skipIfSettingsUnsupported(t) + + var resp *tarantool.Response + var err error + + conn := test_helpers.ConnectWithValidation(t, server, opts) + defer conn.Close() + + // Set default SQL "CREATE TABLE" engine to "vinyl". + resp, err = conn.Do(NewSQLDefaultEngineSetRequest("vinyl")).Get() + require.Nil(t, err) + require.NotNil(t, resp) + require.EqualValues(t, []interface{}{[]interface{}{"sql_default_engine", "vinyl"}}, resp.Data) + + // Fetch current setting value. + resp, err = conn.Do(NewSQLDefaultEngineGetRequest()).Get() + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, []interface{}{[]interface{}{"sql_default_engine", "vinyl"}}, resp.Data) + + // Create a space with "CREATE TABLE". + resp, err = conn.Execute("CREATE TABLE t1_vinyl(a INT PRIMARY KEY, b INT, c INT);", []interface{}{}) + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, uint64(1), resp.SQLInfo.AffectedCount) + + // Check new space engine. + resp, err = conn.Eval("return box.space['T1_VINYL'].engine", []interface{}{}) + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, "vinyl", resp.Data[0]) + + // Set default SQL "CREATE TABLE" engine to "memtx". + resp, err = conn.Do(NewSQLDefaultEngineSetRequest("memtx")).Get() + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, []interface{}{[]interface{}{"sql_default_engine", "memtx"}}, resp.Data) + + // Fetch current setting value. + resp, err = conn.Do(NewSQLDefaultEngineGetRequest()).Get() + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, []interface{}{[]interface{}{"sql_default_engine", "memtx"}}, resp.Data) + + // Create a space with "CREATE TABLE". + resp, err = conn.Execute("CREATE TABLE t2_memtx(a INT PRIMARY KEY, b INT, c INT);", []interface{}{}) + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, uint64(1), resp.SQLInfo.AffectedCount) + + // Check new space engine. + resp, err = conn.Eval("return box.space['T2_MEMTX'].engine", []interface{}{}) + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, "memtx", resp.Data[0]) +} + +func TestSQLDeferForeignKeysSetting(t *testing.T) { + // https://github.com/tarantool/tarantool/blob/eafadc13425f14446d7aaa49dea67dfc1d5f45e9/test/sql/transitive-transactions.result + skipIfSQLDeferForeignKeysSettingUnsupported(t) + + var resp *tarantool.Response + var err error + + conn := test_helpers.ConnectWithValidation(t, server, opts) + defer conn.Close() + + // Create a parent space. + resp, err = conn.Execute("CREATE TABLE parent(id INT PRIMARY KEY, y INT UNIQUE);", []interface{}{}) + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, uint64(1), resp.SQLInfo.AffectedCount) + + // Create a space with reference to the parent space. + resp, err = conn.Execute("CREATE TABLE child(id INT PRIMARY KEY, x INT REFERENCES parent(y));", []interface{}{}) + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, uint64(1), resp.SQLInfo.AffectedCount) + + deferEval := ` + box.begin() + local _, err = box.execute('INSERT INTO child VALUES (2, 2);') + if err ~= nil then + box.rollback() + error(err) + end + box.execute('INSERT INTO parent VALUES (2, 2);') + box.commit() + return true + ` + + // Disable foreign key constraint checks before commit. + resp, err = conn.Do(NewSQLDeferForeignKeysSetRequest(false)).Get() + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, []interface{}{[]interface{}{"sql_defer_foreign_keys", false}}, resp.Data) + + // Fetch current setting value. + resp, err = conn.Do(NewSQLDeferForeignKeysGetRequest()).Get() + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, []interface{}{[]interface{}{"sql_defer_foreign_keys", false}}, resp.Data) + + // Evaluate a scenario when foreign key not exists + // on INSERT, but exists on commit. + _, err = conn.Eval(deferEval, []interface{}{}) + require.NotNil(t, err) + require.ErrorContains(t, err, "Failed to execute SQL statement: FOREIGN KEY constraint failed") + + resp, err = conn.Do(NewSQLDeferForeignKeysSetRequest(true)).Get() + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, []interface{}{[]interface{}{"sql_defer_foreign_keys", true}}, resp.Data) + + // Fetch current setting value. + resp, err = conn.Do(NewSQLDeferForeignKeysGetRequest()).Get() + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, []interface{}{[]interface{}{"sql_defer_foreign_keys", true}}, resp.Data) + + // Evaluate a scenario when foreign key not exists + // on INSERT, but exists on commit. + resp, err = conn.Eval(deferEval, []interface{}{}) + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, true, resp.Data[0]) +} + +func TestSQLFullColumnNamesSetting(t *testing.T) { + skipIfSettingsUnsupported(t) + + var resp *tarantool.Response + var err error + + conn := test_helpers.ConnectWithValidation(t, server, opts) + defer conn.Close() + + // Create a space. + resp, err = conn.Execute("CREATE TABLE fkname(id INT PRIMARY KEY, x INT);", []interface{}{}) + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, uint64(1), resp.SQLInfo.AffectedCount) + + // Fill it with some data. + resp, err = conn.Execute("INSERT INTO fkname VALUES (1, 1);", []interface{}{}) + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, uint64(1), resp.SQLInfo.AffectedCount) + + // Disable displaying full column names in metadata. + resp, err = conn.Do(NewSQLFullColumnNamesSetRequest(false)).Get() + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, []interface{}{[]interface{}{"sql_full_column_names", false}}, resp.Data) + + // Fetch current setting value. + resp, err = conn.Do(NewSQLFullColumnNamesGetRequest()).Get() + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, []interface{}{[]interface{}{"sql_full_column_names", false}}, resp.Data) + + // Get a data with short column names in metadata. + resp, err = conn.Execute("SELECT x FROM fkname WHERE id = 1;", []interface{}{}) + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, "X", resp.MetaData[0].FieldName) + + // Enable displaying full column names in metadata. + resp, err = conn.Do(NewSQLFullColumnNamesSetRequest(true)).Get() + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, []interface{}{[]interface{}{"sql_full_column_names", true}}, resp.Data) + + // Fetch current setting value. + resp, err = conn.Do(NewSQLFullColumnNamesGetRequest()).Get() + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, []interface{}{[]interface{}{"sql_full_column_names", true}}, resp.Data) + + // Get a data with full column names in metadata. + resp, err = conn.Execute("SELECT x FROM fkname WHERE id = 1;", []interface{}{}) + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, "FKNAME.X", resp.MetaData[0].FieldName) +} + +func TestSQLFullMetadataSetting(t *testing.T) { + skipIfSettingsUnsupported(t) + + var resp *tarantool.Response + var err error + + conn := test_helpers.ConnectWithValidation(t, server, opts) + defer conn.Close() + + // Create a space. + resp, err = conn.Execute("CREATE TABLE fmt(id INT PRIMARY KEY, x INT);", []interface{}{}) + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, uint64(1), resp.SQLInfo.AffectedCount) + + // Fill it with some data. + resp, err = conn.Execute("INSERT INTO fmt VALUES (1, 1);", []interface{}{}) + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, uint64(1), resp.SQLInfo.AffectedCount) + + // Disable displaying additional fields in metadata. + resp, err = conn.Do(NewSQLFullMetadataSetRequest(false)).Get() + require.Nil(t, err) + require.Equal(t, []interface{}{[]interface{}{"sql_full_metadata", false}}, resp.Data) + + // Fetch current setting value. + resp, err = conn.Do(NewSQLFullMetadataGetRequest()).Get() + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, []interface{}{[]interface{}{"sql_full_metadata", false}}, resp.Data) + + // Get a data without additional fields in metadata. + resp, err = conn.Execute("SELECT x FROM fmt WHERE id = 1;", []interface{}{}) + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, "", resp.MetaData[0].FieldSpan) + + // Enable displaying full column names in metadata. + resp, err = conn.Do(NewSQLFullMetadataSetRequest(true)).Get() + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, []interface{}{[]interface{}{"sql_full_metadata", true}}, resp.Data) + + // Fetch current setting value. + resp, err = conn.Do(NewSQLFullMetadataGetRequest()).Get() + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, []interface{}{[]interface{}{"sql_full_metadata", true}}, resp.Data) + + // Get a data with additional fields in metadata. + resp, err = conn.Execute("SELECT x FROM fmt WHERE id = 1;", []interface{}{}) + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, "x", resp.MetaData[0].FieldSpan) +} + +func TestSQLParserDebugSetting(t *testing.T) { + skipIfSettingsUnsupported(t) + + var resp *tarantool.Response + var err error + + conn := test_helpers.ConnectWithValidation(t, server, opts) + defer conn.Close() + + // Disable parser debug mode. + resp, err = conn.Do(NewSQLParserDebugSetRequest(false)).Get() + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, []interface{}{[]interface{}{"sql_parser_debug", false}}, resp.Data) + + // Fetch current setting value. + resp, err = conn.Do(NewSQLParserDebugGetRequest()).Get() + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, []interface{}{[]interface{}{"sql_parser_debug", false}}, resp.Data) + + // Enable parser debug mode. + resp, err = conn.Do(NewSQLParserDebugSetRequest(true)).Get() + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, []interface{}{[]interface{}{"sql_parser_debug", true}}, resp.Data) + + // Fetch current setting value. + resp, err = conn.Do(NewSQLParserDebugGetRequest()).Get() + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, []interface{}{[]interface{}{"sql_parser_debug", true}}, resp.Data) + + // To test real effect we need a Tarantool instance built with + // `-DCMAKE_BUILD_TYPE=Debug`. +} + +func TestSQLRecursiveTriggersSetting(t *testing.T) { + // https://github.com/tarantool/tarantool/blob/d11fb3061e15faf4e0eb5375fb8056b4e64348ae/test/sql-tap/triggerC.test.lua + skipIfSettingsUnsupported(t) + + var resp *tarantool.Response + var err error + + conn := test_helpers.ConnectWithValidation(t, server, opts) + defer conn.Close() + + // Create a space. + resp, err = conn.Execute("CREATE TABLE rec(id INTEGER PRIMARY KEY, a INT, b INT);", []interface{}{}) + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, uint64(1), resp.SQLInfo.AffectedCount) + + // Fill it with some data. + resp, err = conn.Execute("INSERT INTO rec VALUES(1, 1, 2);", []interface{}{}) + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, uint64(1), resp.SQLInfo.AffectedCount) + + // Create a recursive trigger (with infinite depth). + resp, err = conn.Execute(` + CREATE TRIGGER tr12 AFTER UPDATE ON rec FOR EACH ROW BEGIN + UPDATE rec SET a=new.a+1, b=new.b+1; + END;`, []interface{}{}) + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, uint64(1), resp.SQLInfo.AffectedCount) + + // Enable SQL recursive triggers. + resp, err = conn.Do(NewSQLRecursiveTriggersSetRequest(true)).Get() + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, []interface{}{[]interface{}{"sql_recursive_triggers", true}}, resp.Data) + + // Fetch current setting value. + resp, err = conn.Do(NewSQLRecursiveTriggersGetRequest()).Get() + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, []interface{}{[]interface{}{"sql_recursive_triggers", true}}, resp.Data) + + // Trigger the recursion. + _, err = conn.Execute("UPDATE rec SET a=a+1, b=b+1;", []interface{}{}) + require.NotNil(t, err) + require.ErrorContains(t, err, "Failed to execute SQL statement: too many levels of trigger recursion") + + // Disable SQL recursive triggers. + resp, err = conn.Do(NewSQLRecursiveTriggersSetRequest(false)).Get() + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, []interface{}{[]interface{}{"sql_recursive_triggers", false}}, resp.Data) + + // Fetch current setting value. + resp, err = conn.Do(NewSQLRecursiveTriggersGetRequest()).Get() + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, []interface{}{[]interface{}{"sql_recursive_triggers", false}}, resp.Data) + + // Trigger the recursion. + resp, err = conn.Execute("UPDATE rec SET a=a+1, b=b+1;", []interface{}{}) + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, uint64(1), resp.SQLInfo.AffectedCount) +} + +func TestSQLReverseUnorderedSelectsSetting(t *testing.T) { + skipIfSettingsUnsupported(t) + + var resp *tarantool.Response + var err error + + conn := test_helpers.ConnectWithValidation(t, server, opts) + defer conn.Close() + + // Create a space. + resp, err = conn.Execute("CREATE TABLE data(id STRING PRIMARY KEY);", []interface{}{}) + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, uint64(1), resp.SQLInfo.AffectedCount) + + // Fill it with some data. + resp, err = conn.Execute("INSERT INTO data VALUES('1');", []interface{}{}) + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, uint64(1), resp.SQLInfo.AffectedCount) + + resp, err = conn.Execute("INSERT INTO data VALUES('2');", []interface{}{}) + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, uint64(1), resp.SQLInfo.AffectedCount) + + // Disable reverse order in unordered selects. + resp, err = conn.Do(NewSQLReverseUnorderedSelectsSetRequest(false)).Get() + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, []interface{}{[]interface{}{"sql_reverse_unordered_selects", false}}, resp.Data) + + // Fetch current setting value. + resp, err = conn.Do(NewSQLReverseUnorderedSelectsGetRequest()).Get() + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, []interface{}{[]interface{}{"sql_reverse_unordered_selects", false}}, resp.Data) + + // Select multiple records. + resp, err = conn.Execute("SELECT * FROM data;", []interface{}{}) + require.Nil(t, err) + require.NotNil(t, resp) + require.EqualValues(t, []interface{}{"1"}, resp.Data[0]) + require.EqualValues(t, []interface{}{"2"}, resp.Data[1]) + + // Enable reverse order in unordered selects. + resp, err = conn.Do(NewSQLReverseUnorderedSelectsSetRequest(true)).Get() + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, []interface{}{[]interface{}{"sql_reverse_unordered_selects", true}}, resp.Data) + + // Fetch current setting value. + resp, err = conn.Do(NewSQLReverseUnorderedSelectsGetRequest()).Get() + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, []interface{}{[]interface{}{"sql_reverse_unordered_selects", true}}, resp.Data) + + // Select multiple records. + resp, err = conn.Execute("SELECT * FROM data;", []interface{}{}) + require.Nil(t, err) + require.NotNil(t, resp) + require.EqualValues(t, []interface{}{"2"}, resp.Data[0]) + require.EqualValues(t, []interface{}{"1"}, resp.Data[1]) +} + +func TestSQLSelectDebugSetting(t *testing.T) { + skipIfSettingsUnsupported(t) + + var resp *tarantool.Response + var err error + + conn := test_helpers.ConnectWithValidation(t, server, opts) + defer conn.Close() + + // Disable select debug mode. + resp, err = conn.Do(NewSQLSelectDebugSetRequest(false)).Get() + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, []interface{}{[]interface{}{"sql_select_debug", false}}, resp.Data) + + // Fetch current setting value. + resp, err = conn.Do(NewSQLSelectDebugGetRequest()).Get() + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, []interface{}{[]interface{}{"sql_select_debug", false}}, resp.Data) + + // Enable select debug mode. + resp, err = conn.Do(NewSQLSelectDebugSetRequest(true)).Get() + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, []interface{}{[]interface{}{"sql_select_debug", true}}, resp.Data) + + // Fetch current setting value. + resp, err = conn.Do(NewSQLSelectDebugGetRequest()).Get() + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, []interface{}{[]interface{}{"sql_select_debug", true}}, resp.Data) + + // To test real effect we need a Tarantool instance built with + // `-DCMAKE_BUILD_TYPE=Debug`. +} + +func TestSQLVDBEDebugSetting(t *testing.T) { + skipIfSettingsUnsupported(t) + + var resp *tarantool.Response + var err error + + conn := test_helpers.ConnectWithValidation(t, server, opts) + defer conn.Close() + + // Disable VDBE debug mode. + resp, err = conn.Do(NewSQLVDBEDebugSetRequest(false)).Get() + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, []interface{}{[]interface{}{"sql_vdbe_debug", false}}, resp.Data) + + // Fetch current setting value. + resp, err = conn.Do(NewSQLVDBEDebugGetRequest()).Get() + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, []interface{}{[]interface{}{"sql_vdbe_debug", false}}, resp.Data) + + // Enable VDBE debug mode. + resp, err = conn.Do(NewSQLVDBEDebugSetRequest(true)).Get() + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, []interface{}{[]interface{}{"sql_vdbe_debug", true}}, resp.Data) + + // Fetch current setting value. + resp, err = conn.Do(NewSQLVDBEDebugGetRequest()).Get() + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, []interface{}{[]interface{}{"sql_vdbe_debug", true}}, resp.Data) + + // To test real effect we need a Tarantool instance built with + // `-DCMAKE_BUILD_TYPE=Debug`. +} + +func TestSessionSettings(t *testing.T) { + skipIfSettingsUnsupported(t) + + var resp *tarantool.Response + var err error + + conn := test_helpers.ConnectWithValidation(t, server, opts) + defer conn.Close() + + // Set some settings values. + resp, err = conn.Do(NewSQLDefaultEngineSetRequest("memtx")).Get() + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, []interface{}{[]interface{}{"sql_default_engine", "memtx"}}, resp.Data) + + resp, err = conn.Do(NewSQLFullColumnNamesSetRequest(true)).Get() + require.Nil(t, err) + require.NotNil(t, resp) + require.Equal(t, []interface{}{[]interface{}{"sql_full_column_names", true}}, resp.Data) + + // Fetch current settings values. + resp, err = conn.Do(NewSessionSettingsGetRequest()).Get() + require.Nil(t, err) + require.NotNil(t, resp) + require.Subset(t, resp.Data, + []interface{}{ + []interface{}{"sql_default_engine", "memtx"}, + []interface{}{"sql_full_column_names", true}, + }) +} + +// runTestMain is a body of TestMain function +// (see https://pkg.go.dev/testing#hdr-Main). +// Using defer + os.Exit is not works so TestMain body +// is a separate function, see +// https://stackoverflow.com/questions/27629380/how-to-exit-a-go-program-honoring-deferred-calls +func runTestMain(m *testing.M) int { + isLess, err := test_helpers.IsTarantoolVersionLess(2, 3, 1) + if err != nil { + log.Fatalf("Failed to extract tarantool version: %s", err) + } + + if isLess { + log.Println("Skipping session settings tests...") + isSettingsSupported = false + return m.Run() + } + + isSettingsSupported = true + + inst, err := test_helpers.StartTarantool(test_helpers.StartOpts{ + InitScript: "testdata/config.lua", + Listen: server, + User: opts.User, + Pass: opts.Pass, + WaitStart: 100 * time.Millisecond, + ConnectRetry: 3, + RetryTimeout: 500 * time.Millisecond, + }) + + if err != nil { + log.Fatalf("Failed to prepare test tarantool: %s", err) + } + + defer test_helpers.StopTarantoolWithCleanup(inst) + + return m.Run() +} + +func TestMain(m *testing.M) { + code := runTestMain(m) + os.Exit(code) +} diff --git a/settings/testdata/config.lua b/settings/testdata/config.lua new file mode 100644 index 000000000..7f6af1db2 --- /dev/null +++ b/settings/testdata/config.lua @@ -0,0 +1,15 @@ +-- Do not set listen for now so connector won't be +-- able to send requests until everything is configured. +box.cfg{ + work_dir = os.getenv("TEST_TNT_WORK_DIR"), +} + +box.schema.user.create('test', { password = 'test' , if_not_exists = true }) +box.schema.user.grant('test', 'execute', 'universe', nil, { if_not_exists = true }) +box.schema.user.grant('test', 'create,read,write,drop,alter', 'space', nil, { if_not_exists = true }) +box.schema.user.grant('test', 'create', 'sequence', nil, { if_not_exists = true }) + +-- Set listen only when every other thing is configured. +box.cfg{ + listen = os.getenv("TEST_TNT_LISTEN"), +}