Skip to content

Commit f2cd82f

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 all required constants in const.go for encoding SQL in msgpack and decoding response. Fixes #62
1 parent 31ebde8 commit f2cd82f

File tree

5 files changed

+188
-2
lines changed

5 files changed

+188
-2
lines changed

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
)

multi/multi.go

+5
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,11 @@ func (connMulti *ConnectionMulti) Eval(expr string, args interface{}) (resp *tar
294294
return connMulti.getCurrentConnection().Eval(expr, args)
295295
}
296296

297+
// Execute passes sql expression to Tarantool for execution.
298+
func (connMulti *ConnectionMulti) Execute(expr string, args ...interface{}) (resp *tarantool.Response, err error) {
299+
return connMulti.getCurrentConnection().Execute(expr, args...)
300+
}
301+
297302
func (connMulti *ConnectionMulti) GetTyped(space, index interface{}, key interface{}, result interface{}) (err error) {
298303
return connMulti.getCurrentConnection().GetTyped(space, index, key, result)
299304
}

request.go

+85
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package tarantool
22

33
import (
44
"errors"
5+
"reflect"
6+
"strings"
57
"time"
68

79
"gopkg.in/vmihailenco/msgpack.v2"
@@ -120,6 +122,16 @@ func (conn *Connection) Eval(expr string, args interface{}) (resp *Response, err
120122
return conn.EvalAsync(expr, args).Get()
121123
}
122124

125+
// Execute passes sql expression to Tarantool for execution.
126+
//
127+
// It is equal to conn.ExecuteAsync(expr, args).Get().
128+
func (conn *Connection) Execute(expr string, args ...interface{}) (resp *Response, err error) {
129+
if len(args) > 1 {
130+
return conn.ExecuteAsync(expr, args).Get()
131+
}
132+
return conn.ExecuteAsync(expr, args[0]).Get()
133+
}
134+
123135
// single used for conn.GetTyped for decode one tuple
124136
type single struct {
125137
res interface{}
@@ -346,9 +358,82 @@ func (conn *Connection) EvalAsync(expr string, args interface{}) *Future {
346358
})
347359
}
348360

361+
// ExecuteAsync sends a sql expression for execution and returns Future.
362+
func (conn *Connection) ExecuteAsync(expr string, args interface{}) *Future {
363+
future := conn.newFuture(ExecuteRequest)
364+
return future.send(conn, func(enc *msgpack.Encoder) error {
365+
enc.EncodeMapLen(2)
366+
enc.EncodeUint64(KeySQLText)
367+
enc.EncodeString(expr)
368+
enc.EncodeUint64(KeySQLBind)
369+
return encodeSQLBind(enc, args)
370+
})
371+
}
372+
349373
//
350374
// private
351375
//
376+
func encodeSQLBind(enc *msgpack.Encoder, from interface{}) error {
377+
// internal function for encoding single map in msgpack
378+
encodeKeyVal := func(enc *msgpack.Encoder, key, val reflect.Value) error {
379+
if err := enc.EncodeMapLen(1); err != nil {
380+
return err
381+
}
382+
if err := enc.EncodeValue(key); err != nil {
383+
return err
384+
}
385+
if err := enc.EncodeValue(val); err != nil {
386+
return err
387+
}
388+
return nil
389+
}
390+
391+
val := reflect.ValueOf(from)
392+
switch val.Kind() {
393+
case reflect.Map:
394+
if err := enc.EncodeSliceLen(val.Len()); err != nil {
395+
return err
396+
}
397+
it := val.MapRange()
398+
for it.Next() {
399+
k := reflect.ValueOf(":" + it.Key().String())
400+
v := it.Value()
401+
if err := encodeKeyVal(enc, k, v); err != nil {
402+
return err
403+
}
404+
}
405+
case reflect.Struct:
406+
if err := enc.EncodeSliceLen(val.NumField()); err != nil {
407+
return err
408+
}
409+
for i := 0; i < val.NumField(); i++ {
410+
key := val.Type().Field(i).Name
411+
k := reflect.ValueOf(":" + strings.ToLower(key))
412+
v := reflect.ValueOf(from).FieldByName(key)
413+
if err := encodeKeyVal(enc, k, v); err != nil {
414+
return err
415+
}
416+
}
417+
case reflect.Slice, reflect.Array:
418+
if val.Len() > 0 {
419+
sliceType := reflect.ValueOf(val.Index(0).Interface()).Kind()
420+
if sliceType != reflect.Struct {
421+
return enc.Encode(from)
422+
}
423+
}
424+
if err := enc.EncodeSliceLen(val.Len()); err != nil {
425+
return err
426+
}
427+
for i := 0; i < val.Len(); i++ {
428+
k := ":" + val.Index(i).Field(0).String()
429+
v := val.Index(i).Field(1)
430+
if err := encodeKeyVal(enc, reflect.ValueOf(k), v); err != nil {
431+
return err
432+
}
433+
}
434+
}
435+
return nil
436+
}
352437

353438
func (fut *Future) pack(h *smallWBuf, enc *msgpack.Encoder, body func(*msgpack.Encoder) error) (err error) {
354439
rid := fut.requestId

response.go

+81-2
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,65 @@ type Response struct {
1111
Code uint32
1212
Error string // error message
1313
// Data contains deserialized data for untyped requests
14-
Data []interface{}
15-
buf smallBuf
14+
Data []interface{}
15+
MetaData []ColumnMetaData
16+
SQLInfo SQLInfo
17+
buf smallBuf
18+
}
19+
20+
type ColumnMetaData struct {
21+
FieldName string
22+
FieldType string
23+
FieldCollation string
24+
FieldIsNullable bool
25+
FieldIsAutoincrement bool
26+
FieldSpan string
27+
}
28+
29+
type SQLInfo struct {
30+
AffectedCount uint64
31+
InfoAutoincrementIds []uint64
32+
}
33+
34+
func parseMetaData(from []interface{}) []ColumnMetaData {
35+
var metaData []ColumnMetaData
36+
37+
for i := 0; i < len(from); i++ {
38+
mp := from[i].(map[interface{}]interface{})
39+
cd := ColumnMetaData{}
40+
cd.FieldName = mp[uint64(KeyFieldName)].(string)
41+
cd.FieldType = mp[uint64(KeyFieldType)].(string)
42+
if mp[uint64(KeyFieldColl)] != nil {
43+
cd.FieldCollation = mp[uint64(KeyFieldColl)].(string)
44+
}
45+
if mp[uint64(KeyFieldIsNullable)] != nil {
46+
cd.FieldIsNullable = mp[uint64(KeyFieldIsNullable)].(bool)
47+
}
48+
if mp[uint64(KeyIsAutoincrement)] != nil {
49+
cd.FieldIsAutoincrement = mp[uint64(KeyIsAutoincrement)].(bool)
50+
}
51+
if mp[uint64(KeyFieldSpan)] != nil {
52+
cd.FieldSpan = mp[uint64(KeyFieldSpan)].(string)
53+
}
54+
metaData = append(metaData, cd)
55+
}
56+
57+
return metaData
58+
}
59+
60+
func parseSQLInfo(from map[interface{}]interface{}) SQLInfo {
61+
info := SQLInfo{}
62+
63+
info.AffectedCount = from[uint64(KeySQLInfoRowCount)].(uint64)
64+
if from[uint64(KeySqlInfoAutoincrementIds)] != nil {
65+
t := from[uint64(KeySqlInfoAutoincrementIds)].([]interface{})
66+
ids := make([]uint64, len(t))
67+
for i := range t {
68+
ids[i] = t[i].(uint64)
69+
}
70+
info.InfoAutoincrementIds = ids
71+
}
72+
return info
1673
}
1774

1875
func (resp *Response) fill(b []byte) {
@@ -90,6 +147,28 @@ func (resp *Response) decodeBody() (err error) {
90147
if resp.Error, err = d.DecodeString(); err != nil {
91148
return err
92149
}
150+
case KeySQLInfo:
151+
var res interface{}
152+
var castedRes map[interface{}]interface{}
153+
var ok bool
154+
if res, err = d.DecodeInterface(); err != nil {
155+
return err
156+
}
157+
if castedRes, ok = res.(map[interface{}]interface{}); !ok {
158+
return fmt.Errorf("sql info is not a map: %v", res)
159+
}
160+
resp.SQLInfo = parseSQLInfo(castedRes)
161+
case KeyMetaData:
162+
var res interface{}
163+
var castedRes []interface{}
164+
var ok bool
165+
if res, err = d.DecodeInterface(); err != nil {
166+
return err
167+
}
168+
if castedRes, ok = res.([]interface{}); !ok {
169+
return fmt.Errorf("meta data is not array: %v", res)
170+
}
171+
resp.MetaData = parseMetaData(castedRes)
93172
default:
94173
if err = d.Skip(); err != nil {
95174
return err

0 commit comments

Comments
 (0)