Skip to content

Commit a9e491f

Browse files
committed
sql: add minimal sql support
This patch adds the support of SQL in connector. Added support of positional and named arguments. Added ExecuteTyped() method for use with custom packing/unpacking for a type. Added all required constants to const.go for encoding SQL in msgpack and decoding response. Added SQL tests. Updated config.lua for creation the space for using SQL in tests. Added the check of Tarantool version to skip SQL tests if tarantool version < 2.0.0. Changed id of the test spaces with id=512 and id=514, cause if using SQL in tarantool there is no ability to set space id explicitly, so it gets created with id=512 by default and conflicts with already existing space with the same id. Added new dependency in go.sum, go.mod for using assert package. Added examples of using SQL queries in example_test.go for compiling the future documentation from sources. Added notes about the version since which Execute() is supported. Closes #62
1 parent a54d9f6 commit a9e491f

10 files changed

+1012
-30
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release.
1717
- Go modules support (#91)
1818
- queue-utube handling (#85)
1919
- Master discovery (#113)
20+
- SQL support (#62)
2021

2122
### Fixed
2223

config.lua

+18-2
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,25 @@ box.cfg{
66

77
box.once("init", function()
88
local s = box.schema.space.create('test', {
9-
id = 512,
9+
id = 517,
1010
if_not_exists = true,
1111
})
1212
s:create_index('primary', {type = 'tree', parts = {1, 'uint'}, if_not_exists = true})
1313

14+
local sp = box.schema.space.create('SQL_TEST', {
15+
id = 519,
16+
if_not_exists = true,
17+
format = {
18+
{name = "NAME0", type = "unsigned"},
19+
{name = "NAME1", type = "string"},
20+
{name = "NAME2", type = "string"},
21+
}
22+
})
23+
sp:create_index('primary', {type = 'tree', parts = {1, 'uint'}, if_not_exists = true})
24+
sp:insert{1, "test", "test"}
25+
1426
local st = box.schema.space.create('schematest', {
15-
id = 514,
27+
id = 516,
1628
temporary = true,
1729
if_not_exists = true,
1830
field_count = 7,
@@ -75,6 +87,10 @@ box.once("init", function()
7587
box.schema.user.grant('test', 'read,write', 'space', 'test')
7688
box.schema.user.grant('test', 'read,write', 'space', 'schematest')
7789
box.schema.user.grant('test', 'read,write', 'space', 'test_perf')
90+
91+
-- grants for sql tests
92+
box.schema.user.grant('test', 'create,read,write,drop,alter', 'space')
93+
box.schema.user.grant('test', 'create', 'sequence')
7894
end)
7995

8096
local function func_name()

connector.go

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ type Connector interface {
1717
Call(functionName string, args interface{}) (resp *Response, err error)
1818
Call17(functionName string, args interface{}) (resp *Response, err error)
1919
Eval(expr string, args interface{}) (resp *Response, err error)
20+
Execute(expr string, args interface{}) (resp *Response, err error)
2021

2122
GetTyped(space, index interface{}, key interface{}, result interface{}) (err error)
2223
SelectTyped(space, index interface{}, offset, limit, iterator uint32, key interface{}, result interface{}) (err error)

const.go

+16
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const (
1111
EvalRequest = 8
1212
UpsertRequest = 9
1313
Call17Request = 10
14+
ExecuteRequest = 11
1415
PingRequest = 64
1516
SubscribeRequest = 66
1617

@@ -29,6 +30,19 @@ const (
2930
KeyDefTuple = 0x28
3031
KeyData = 0x30
3132
KeyError = 0x31
33+
KeyMetaData = 0x32
34+
KeySQLText = 0x40
35+
KeySQLBind = 0x41
36+
KeySQLInfo = 0x42
37+
38+
KeyFieldName = 0x00
39+
KeyFieldType = 0x01
40+
KeyFieldColl = 0x02
41+
KeyFieldIsNullable = 0x03
42+
KeyIsAutoincrement = 0x04
43+
KeyFieldSpan = 0x05
44+
KeySQLInfoRowCount = 0x00
45+
KeySQLInfoAutoincrementIds = 0x01
3246

3347
// https://github.com/fl00r/go-tarantool-1.6/issues/2
3448

@@ -49,4 +63,6 @@ const (
4963
OkCode = uint32(0)
5064
ErrorCodeBit = 0x8000
5165
PacketLengthBytes = 5
66+
ErSpaceExistsCode = 0xa
67+
IteratorCode = 0x14
5268
)

example_custom_unpacking_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ func Example_customUnpacking() {
8787
log.Fatalf("Failed to connect: %s", err.Error())
8888
}
8989

90-
spaceNo := uint32(512)
90+
spaceNo := uint32(517)
9191
indexNo := uint32(0)
9292

9393
tuple := Tuple2{Cid: 777, Orig: "orig", Members: []Member{{"lol", "", 1}, {"wut", "", 3}}}

example_test.go

+134-7
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package tarantool_test
22

33
import (
44
"fmt"
5+
"github.com/tarantool/go-tarantool/test_helpers"
56
"time"
67

78
"github.com/tarantool/go-tarantool"
@@ -31,7 +32,8 @@ func ExampleConnection_Select() {
3132
conn.Replace(spaceNo, []interface{}{uint(1111), "hello", "world"})
3233
conn.Replace(spaceNo, []interface{}{uint(1112), "hallo", "werld"})
3334

34-
resp, err := conn.Select(512, 0, 0, 100, tarantool.IterEq, []interface{}{uint(1111)})
35+
resp, err := conn.Select(517, 0, 0, 100, tarantool.IterEq, []interface{}{uint(1111)})
36+
3537
if err != nil {
3638
fmt.Printf("error in select is %v", err)
3739
return
@@ -53,7 +55,9 @@ func ExampleConnection_SelectTyped() {
5355
conn := example_connect()
5456
defer conn.Close()
5557
var res []Tuple
56-
err := conn.SelectTyped(512, 0, 0, 100, tarantool.IterEq, tarantool.IntKey{1111}, &res)
58+
59+
err := conn.SelectTyped(517, 0, 0, 100, tarantool.IterEq, tarantool.IntKey{1111}, &res)
60+
5761
if err != nil {
5862
fmt.Printf("error in select is %v", err)
5963
return
@@ -73,6 +77,7 @@ func ExampleConnection_SelectTyped() {
7377
func ExampleConnection_SelectAsync() {
7478
conn := example_connect()
7579
defer conn.Close()
80+
spaceNo := uint32(517)
7681

7782
conn.Insert(spaceNo, []interface{}{uint(16), "test", "one"})
7883
conn.Insert(spaceNo, []interface{}{uint(17), "test", "one"})
@@ -320,12 +325,12 @@ func ExampleSchema() {
320325
}
321326

322327
space1 := schema.Spaces["test"]
323-
space2 := schema.SpacesById[514]
328+
space2 := schema.SpacesById[516]
324329
fmt.Printf("Space 1 ID %d %s\n", space1.Id, space1.Name)
325330
fmt.Printf("Space 2 ID %d %s\n", space2.Id, space2.Name)
326331
// Output:
327-
// Space 1 ID 512 test
328-
// Space 2 ID 514 schematest
332+
// Space 1 ID 517 test
333+
// Space 2 ID 516 schematest
329334
}
330335

331336
// Example demonstrates how to retrieve information with space schema.
@@ -344,7 +349,7 @@ func ExampleSpace() {
344349

345350
// Access Space objects by name or ID.
346351
space1 := schema.Spaces["test"]
347-
space2 := schema.SpacesById[514] // It's a map.
352+
space2 := schema.SpacesById[516] // It's a map.
348353
fmt.Printf("Space 1 ID %d %s %s\n", space1.Id, space1.Name, space1.Engine)
349354
fmt.Printf("Space 1 ID %d %t\n", space1.FieldsCount, space1.Temporary)
350355

@@ -365,10 +370,132 @@ func ExampleSpace() {
365370
fmt.Printf("SpaceField 2 %s %s\n", spaceField2.Name, spaceField2.Type)
366371

367372
// Output:
368-
// Space 1 ID 512 test memtx
373+
// Space 1 ID 517 test memtx
369374
// Space 1 ID 0 false
370375
// Index 0 primary
371376
// &{0 unsigned} &{2 string}
372377
// SpaceField 1 name0 unsigned
373378
// SpaceField 2 name3 unsigned
374379
}
380+
381+
// To use SQL to query a tarantool instance, call Execute.
382+
//
383+
// Pay attention that with different types of queries (DDL, DQL, DML etc.)
384+
// some fields of the response structure (MetaData and InfoAutoincrementIds in SQLInfo) may be nil.
385+
func ExampleConnection_Execute() {
386+
// Tarantool supports SQL since version 2.0.0
387+
isLess, _ := test_helpers.IsTarantoolVersionLess(2, 0, 0)
388+
if isLess {
389+
return
390+
}
391+
server := "127.0.0.1:3013"
392+
opts := tarantool.Opts{
393+
Timeout: 500 * time.Millisecond,
394+
Reconnect: 1 * time.Second,
395+
MaxReconnects: 3,
396+
User: "test",
397+
Pass: "test",
398+
}
399+
client, err := tarantool.Connect(server, opts)
400+
if err != nil {
401+
fmt.Printf("Failed to connect: %s", err.Error())
402+
}
403+
404+
resp, err := client.Execute("CREATE TABLE SQL_TEST (id INTEGER PRIMARY KEY, name STRING)", []interface{}{})
405+
fmt.Println("Execute")
406+
fmt.Println("Error", err)
407+
fmt.Println("Code", resp.Code)
408+
fmt.Println("Data", resp.Data)
409+
fmt.Println("MetaData", resp.MetaData)
410+
fmt.Println("SQL Info", resp.SQLInfo)
411+
412+
// there are 4 options to pass named parameters to an SQL query
413+
// the simple map:
414+
sqlBind1 := map[string]interface{}{
415+
"id": 1,
416+
"name": "test",
417+
}
418+
419+
// any type of structure
420+
sqlBind2 := struct {
421+
Id int
422+
Name string
423+
}{1, "test"}
424+
425+
// it is possible to use []tarantool.KeyValueBind
426+
sqlBind3 := []interface{}{
427+
tarantool.KeyValueBind{Key: "id", Value: 1},
428+
tarantool.KeyValueBind{Key: "name", Value: "test"},
429+
}
430+
431+
// or []interface{} slice with tarantool.KeyValueBind items inside
432+
sqlBind4 := []tarantool.KeyValueBind{
433+
{"id", 1},
434+
{"name", "test"},
435+
}
436+
437+
// the next usage
438+
resp, err = client.Execute("SELECT id FROM SQL_TEST WHERE id=:id AND name=:name", sqlBind1)
439+
fmt.Println("Execute")
440+
fmt.Println("Error", err)
441+
fmt.Println("Code", resp.Code)
442+
fmt.Println("Data", resp.Data)
443+
fmt.Println("MetaData", resp.MetaData)
444+
fmt.Println("SQL Info", resp.SQLInfo)
445+
446+
// the same as
447+
resp, err = client.Execute("SELECT id FROM SQL_TEST WHERE id=:id AND name=:name", sqlBind2)
448+
fmt.Println("Execute")
449+
fmt.Println("Error", err)
450+
fmt.Println("Code", resp.Code)
451+
fmt.Println("Data", resp.Data)
452+
fmt.Println("MetaData", resp.MetaData)
453+
fmt.Println("SQL Info", resp.SQLInfo)
454+
455+
// the same as
456+
resp, err = client.Execute("SELECT id FROM SQL_TEST WHERE id=:id AND name=:name", sqlBind3)
457+
fmt.Println("Execute")
458+
fmt.Println("Error", err)
459+
fmt.Println("Code", resp.Code)
460+
fmt.Println("Data", resp.Data)
461+
fmt.Println("MetaData", resp.MetaData)
462+
fmt.Println("SQL Info", resp.SQLInfo)
463+
464+
// the same as
465+
resp, err = client.Execute("SELECT id FROM SQL_TEST WHERE id=:id AND name=:name", sqlBind4)
466+
fmt.Println("Execute")
467+
fmt.Println("Error", err)
468+
fmt.Println("Code", resp.Code)
469+
fmt.Println("Data", resp.Data)
470+
fmt.Println("MetaData", resp.MetaData)
471+
fmt.Println("SQL Info", resp.SQLInfo)
472+
473+
// the way to pass positional arguments to an SQL query
474+
resp, err = client.Execute("SELECT id FROM SQL_TEST WHERE id=? AND name=?", []interface{}{2, "test"})
475+
fmt.Println("Execute")
476+
fmt.Println("Error", err)
477+
fmt.Println("Code", resp.Code)
478+
fmt.Println("Data", resp.Data)
479+
fmt.Println("MetaData", resp.MetaData)
480+
fmt.Println("SQL Info", resp.SQLInfo)
481+
482+
// the way to pass SQL expression with using custom packing/unpacking for a type
483+
var res []Tuple
484+
sqlInfo, metaData, err := client.ExecuteTyped("SELECT id, name, name FROM SQL_TEST WHERE id=?", []interface{}{2}, &res)
485+
fmt.Println("ExecuteTyped")
486+
fmt.Println("Error", err)
487+
fmt.Println("Data", res)
488+
fmt.Println("MetaData", metaData)
489+
fmt.Println("SQL Info", sqlInfo)
490+
491+
// for using different types of parameters (positioned/named), collect all items in []interface{}
492+
// all "named" items must be passed with tarantool.KeyValueBind{}
493+
resp, err = client.Execute("SELECT id FROM SQL_TEST WHERE id=:id AND name=?",
494+
[]interface{}{tarantool.KeyValueBind{"id", 1}, "test"})
495+
fmt.Println("Execute")
496+
fmt.Println("Error", err)
497+
fmt.Println("Code", resp.Code)
498+
fmt.Println("Data", resp.Data)
499+
fmt.Println("MetaData", resp.MetaData)
500+
fmt.Println("SQL Info", resp.SQLInfo)
501+
}

multi/multi.go

+7
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,13 @@ func (connMulti *ConnectionMulti) Eval(expr string, args interface{}) (resp *tar
340340
return connMulti.getCurrentConnection().Eval(expr, args)
341341
}
342342

343+
// Execute passes sql expression to Tarantool for execution.
344+
//
345+
// Since 1.6.0
346+
func (connMulti *ConnectionMulti) Execute(expr string, args interface{}) (resp *tarantool.Response, err error) {
347+
return connMulti.getCurrentConnection().Execute(expr, args)
348+
}
349+
343350
// GetTyped performs select (with limit = 1 and offset = 0) to box space and
344351
// fills typed result.
345352
func (connMulti *ConnectionMulti) GetTyped(space, index interface{}, key interface{}, result interface{}) (err error) {

0 commit comments

Comments
 (0)