Skip to content

Commit 42fb875

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. Fixes #62
1 parent de95e31 commit 42fb875

File tree

5 files changed

+250
-4
lines changed

5 files changed

+250
-4
lines changed

connector.go

Lines changed: 1 addition & 0 deletions
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

Lines changed: 16 additions & 0 deletions
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

Lines changed: 7 additions & 0 deletions
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) {

request.go

Lines changed: 122 additions & 0 deletions
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,14 @@ 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+
// Since 1.6.0
129+
func (conn *Connection) Execute(expr string, args interface{}) (resp *Response, err error) {
130+
return conn.ExecuteAsync(expr, args).Get()
131+
}
132+
123133
// single used for conn.GetTyped for decode one tuple.
124134
type single struct {
125135
res interface{}
@@ -212,6 +222,16 @@ func (conn *Connection) EvalTyped(expr string, args interface{}, result interfac
212222
return conn.EvalAsync(expr, args).GetTyped(result)
213223
}
214224

225+
// ExecuteTyped passes sql expression to Tarantool for execution.
226+
//
227+
// In addition to error returns sql info and columns meta data
228+
// Since 1.6.0
229+
func (conn *Connection) ExecuteTyped(expr string, args interface{}, result interface{}) (SQLInfo, []ColumnMetaData, error) {
230+
fut := conn.ExecuteAsync(expr, args)
231+
err := fut.GetTyped(&result)
232+
return fut.resp.SQLInfo, fut.resp.MetaData, err
233+
}
234+
215235
// SelectAsync sends select request to Tarantool and returns Future.
216236
func (conn *Connection) SelectAsync(space, index interface{}, offset, limit, iterator uint32, key interface{}) *Future {
217237
future := conn.newFuture(SelectRequest)
@@ -346,9 +366,111 @@ func (conn *Connection) EvalAsync(expr string, args interface{}) *Future {
346366
})
347367
}
348368

369+
// ExecuteAsync sends a sql expression for execution and returns Future.
370+
// Since 1.6.0
371+
func (conn *Connection) ExecuteAsync(expr string, args interface{}) *Future {
372+
future := conn.newFuture(ExecuteRequest)
373+
return future.send(conn, func(enc *msgpack.Encoder) error {
374+
enc.EncodeMapLen(2)
375+
enc.EncodeUint64(KeySQLText)
376+
enc.EncodeString(expr)
377+
enc.EncodeUint64(KeySQLBind)
378+
return encodeSQLBind(enc, args)
379+
})
380+
}
381+
382+
// KeyValueBind is a type for encoding named SQL parameters
383+
type KeyValueBind struct {
384+
Key string
385+
Value interface{}
386+
}
387+
349388
//
350389
// private
351390
//
391+
func encodeSQLBind(enc *msgpack.Encoder, from interface{}) error {
392+
// internal function for encoding single map in msgpack
393+
encodeKeyVal := func(enc *msgpack.Encoder, key string, val reflect.Value) error {
394+
if err := enc.EncodeMapLen(1); err != nil {
395+
return err
396+
}
397+
if err := enc.EncodeString(key); err != nil {
398+
return err
399+
}
400+
if err := enc.EncodeValue(val); err != nil {
401+
return err
402+
}
403+
return nil
404+
}
405+
406+
encodeNamedFromMap := func(val reflect.Value) error {
407+
if err := enc.EncodeSliceLen(val.Len()); err != nil {
408+
return err
409+
}
410+
it := val.MapRange()
411+
for it.Next() {
412+
k := ":" + it.Key().String()
413+
v := it.Value()
414+
if err := encodeKeyVal(enc, k, v); err != nil {
415+
return err
416+
}
417+
}
418+
return nil
419+
}
420+
421+
encodeNamedFromStruct := func(val reflect.Value) error {
422+
if err := enc.EncodeSliceLen(val.NumField()); err != nil {
423+
return err
424+
}
425+
for i := 0; i < val.NumField(); i++ {
426+
key := val.Type().Field(i).Name
427+
k := ":" + strings.ToLower(key)
428+
v := reflect.ValueOf(from).FieldByName(key)
429+
if err := encodeKeyVal(enc, k, v); err != nil {
430+
return err
431+
}
432+
}
433+
return nil
434+
}
435+
436+
val := reflect.ValueOf(from)
437+
switch val.Kind() {
438+
case reflect.Map:
439+
if err := encodeNamedFromMap(val); err != nil {
440+
return err
441+
}
442+
case reflect.Struct:
443+
if err := encodeNamedFromStruct(val); err != nil {
444+
return err
445+
}
446+
case reflect.Slice, reflect.Array:
447+
if err := enc.EncodeSliceLen(val.Len()); err != nil {
448+
return err
449+
}
450+
castedSlice, ok := from.([]interface{})
451+
if !ok {
452+
castedKVSlice := from.([]KeyValueBind)
453+
for _, v := range castedKVSlice {
454+
castedSlice = append(castedSlice, v)
455+
}
456+
}
457+
458+
for i := 0; i < len(castedSlice); i++ {
459+
if kvb, ok := castedSlice[i].(KeyValueBind); ok {
460+
k := ":" + kvb.Key
461+
v := kvb.Value
462+
if err := encodeKeyVal(enc, k, reflect.ValueOf(v)); err != nil {
463+
return err
464+
}
465+
} else {
466+
if err := enc.Encode(castedSlice[i]); err != nil {
467+
return err
468+
}
469+
}
470+
}
471+
}
472+
return nil
473+
}
352474

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

response.go

Lines changed: 104 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,94 @@ import (
99
type Response struct {
1010
RequestId uint32
1111
Code uint32
12-
Error string // Error message.
13-
// Data contains deserialized data for untyped requests.
14-
Data []interface{}
15-
buf smallBuf
12+
Error string // error message
13+
// Data contains deserialized data for untyped requests
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 (meta *ColumnMetaData) DecodeMsgpack(d *msgpack.Decoder) error {
35+
var err error
36+
var l int
37+
if l, err = d.DecodeMapLen(); err != nil {
38+
return err
39+
}
40+
if l == 0 {
41+
return fmt.Errorf("map len doesn't match: %d", l)
42+
}
43+
for i := 0; i < l; i++ {
44+
var mk uint64
45+
var mv interface{}
46+
if mk, err = d.DecodeUint64(); err != nil {
47+
return fmt.Errorf("failed to decode meta data")
48+
}
49+
if mv, err = d.DecodeInterface(); err != nil {
50+
return fmt.Errorf("failed to decode meta data")
51+
}
52+
switch mk {
53+
case KeyFieldName:
54+
meta.FieldName = mv.(string)
55+
case KeyFieldType:
56+
meta.FieldType = mv.(string)
57+
case KeyFieldColl:
58+
meta.FieldCollation = mv.(string)
59+
case KeyFieldIsNullable:
60+
meta.FieldIsNullable = mv.(bool)
61+
case KeyIsAutoincrement:
62+
meta.FieldIsAutoincrement = mv.(bool)
63+
case KeyFieldSpan:
64+
meta.FieldSpan = mv.(string)
65+
default:
66+
return fmt.Errorf("failed to decode meta data")
67+
}
68+
}
69+
return nil
70+
}
71+
72+
func (info *SQLInfo) DecodeMsgpack(d *msgpack.Decoder) error {
73+
var err error
74+
var l int
75+
if l, err = d.DecodeMapLen(); err != nil {
76+
return err
77+
}
78+
if l == 0 {
79+
return fmt.Errorf("map len doesn't match")
80+
}
81+
for i := 0; i < l; i++ {
82+
var mk uint64
83+
if mk, err = d.DecodeUint64(); err != nil {
84+
return fmt.Errorf("failed to decode meta data")
85+
}
86+
switch mk {
87+
case KeySQLInfoRowCount:
88+
if info.AffectedCount, err = d.DecodeUint64(); err != nil {
89+
return fmt.Errorf("failed to decode meta data")
90+
}
91+
case KeySQLInfoAutoincrementIds:
92+
if err = d.Decode(&info.InfoAutoincrementIds); err != nil {
93+
return fmt.Errorf("failed to decode meta data")
94+
}
95+
default:
96+
return fmt.Errorf("failed to decode meta data")
97+
}
98+
}
99+
return nil
16100
}
17101

18102
func (resp *Response) smallInt(d *msgpack.Decoder) (i int, err error) {
@@ -86,6 +170,14 @@ func (resp *Response) decodeBody() (err error) {
86170
if resp.Error, err = d.DecodeString(); err != nil {
87171
return err
88172
}
173+
case KeySQLInfo:
174+
if err = d.Decode(&resp.SQLInfo); err != nil {
175+
return err
176+
}
177+
case KeyMetaData:
178+
if err = d.Decode(&resp.MetaData); err != nil {
179+
return err
180+
}
89181
default:
90182
if err = d.Skip(); err != nil {
91183
return err
@@ -121,6 +213,14 @@ func (resp *Response) decodeBodyTyped(res interface{}) (err error) {
121213
if resp.Error, err = d.DecodeString(); err != nil {
122214
return err
123215
}
216+
case KeySQLInfo:
217+
if err = d.Decode(&resp.SQLInfo); err != nil {
218+
return err
219+
}
220+
case KeyMetaData:
221+
if err = d.Decode(&resp.MetaData); err != nil {
222+
return err
223+
}
124224
default:
125225
if err = d.Skip(); err != nil {
126226
return err

0 commit comments

Comments
 (0)