Skip to content

Commit f42b5a8

Browse files
committed
sql: support prepared statements
This patch adds the support of prepared statements. Changed the interface of the method Execute for accepting the first argument as a string (for sql queries) or uint64 (for statements id). Added new methods for connector's interface in connector.go. Added new IPROTO-constants for support of prepared statements in const.go. Updated multi-package for corresponding it to connector's interface. Added a new function for checking the count of prepared statements in config.lua in tests for Prepare and Unprepare. Added benchmarks for SQL-select prepared statement. Added examples of using Prepare in example_test.go. Fixed some grammar inconsistencies for the method Execute. Updated CHANGELOG.md. Follows up #62 Closes #117
1 parent 2bc07ed commit f42b5a8

9 files changed

+318
-8
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release.
1818
- queue-utube handling (#85)
1919
- Master discovery (#113)
2020
- SQL support (#62)
21+
- Prepared statements (#117)
2122

2223
### Fixed
2324

config.lua

+6-1
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ box.once("init", function()
9191
-- grants for sql tests
9292
box.schema.user.grant('test', 'create,read,write,drop,alter', 'space')
9393
box.schema.user.grant('test', 'create', 'sequence')
94+
box.schema.user.grant('guest', 'super')
9495
end)
9596

9697
local function func_name()
@@ -110,11 +111,15 @@ local function simple_incr(a)
110111
end
111112
rawset(_G, 'simple_incr', simple_incr)
112113

114+
local function test_stmt_count_check()
115+
return box.info().sql().cache.stmt_count
116+
end
117+
rawset(_G, 'test_stmt_count_check', test_stmt_count_check)
113118
box.space.test:truncate()
114119

115120
--box.schema.user.revoke('guest', 'read,write,execute', 'universe')
116121

117122
-- Set listen only when every other thing is configured.
118123
box.cfg{
119-
listen = os.getenv("TEST_TNT_LISTEN"),
124+
listen = 3013,
120125
}

connector.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ 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)
20+
Execute(expr interface{}, args interface{}) (resp *Response, err error)
21+
Prepare(expr string) (resp *Response, err error)
22+
Unprepare(stmt uint64) (resp *Response, err error)
2123

2224
GetTyped(space, index interface{}, key interface{}, result interface{}) (err error)
2325
SelectTyped(space, index interface{}, offset, limit, iterator uint32, key interface{}, result interface{}) (err error)
@@ -28,6 +30,7 @@ type Connector interface {
2830
CallTyped(functionName string, args interface{}, result interface{}) (err error)
2931
Call17Typed(functionName string, args interface{}, result interface{}) (err error)
3032
EvalTyped(expr string, args interface{}, result interface{}) (err error)
33+
ExecuteTyped(expr interface{}, args interface{}, result interface{}) (SQLInfo, []ColumnMetaData, error)
3134

3235
SelectAsync(space, index interface{}, offset, limit, iterator uint32, key interface{}) *Future
3336
InsertAsync(space interface{}, tuple interface{}) *Future
@@ -38,4 +41,7 @@ type Connector interface {
3841
CallAsync(functionName string, args interface{}) *Future
3942
Call17Async(functionName string, args interface{}) *Future
4043
EvalAsync(expr string, args interface{}) *Future
44+
ExecuteAsync(expr interface{}, args interface{}) *Future
45+
PrepareAsync(expr string) *Future
46+
UnprepareAsync(stmt uint64) *Future
4147
}

const.go

+2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const (
1212
UpsertRequest = 9
1313
Call17Request = 10
1414
ExecuteRequest = 11
15+
PrepareRequest = 13
1516
PingRequest = 64
1617
SubscribeRequest = 66
1718

@@ -34,6 +35,7 @@ const (
3435
KeySQLText = 0x40
3536
KeySQLBind = 0x41
3637
KeySQLInfo = 0x42
38+
KeyStmtID = 0x43
3739

3840
KeyFieldName = 0x00
3941
KeyFieldType = 0x01

example_test.go

+39
Original file line numberDiff line numberDiff line change
@@ -499,3 +499,42 @@ func ExampleConnection_Execute() {
499499
fmt.Println("MetaData", resp.MetaData)
500500
fmt.Println("SQL Info", resp.SQLInfo)
501501
}
502+
503+
// To use prepared statements to query a tarantool instance, call Prepare.
504+
func ExampleConnection_Prepare() {
505+
// Tarantool supports SQL since version 2.0.0
506+
isLess, _ := test_helpers.IsTarantoolVersionLess(2, 0, 0)
507+
if isLess {
508+
return
509+
}
510+
server := "127.0.0.1:3013"
511+
opts := tarantool.Opts{
512+
Timeout: 500 * time.Millisecond,
513+
Reconnect: 1 * time.Second,
514+
MaxReconnects: 3,
515+
User: "test",
516+
Pass: "test",
517+
}
518+
client, err := tarantool.Connect(server, opts)
519+
if err != nil {
520+
fmt.Printf("Failed to connect: %s", err.Error())
521+
}
522+
523+
// pass a query to prepare
524+
stmtResp, err := client.Prepare("SELECT id FROM SQL_TEST WHERE id=? AND name=?")
525+
fmt.Println("Prepare")
526+
fmt.Println("Error", err)
527+
fmt.Println("Code", stmtResp.Code)
528+
fmt.Println("Statement ID", stmtResp.StmtID)
529+
530+
stmtID := stmtResp.StmtID
531+
532+
// pass the id of the statement to Execute
533+
resp, err := client.Execute(stmtID, []interface{}{2, "test"})
534+
fmt.Println("Execute")
535+
fmt.Println("Error", err)
536+
fmt.Println("Code", resp.Code)
537+
fmt.Println("Data", resp.Data)
538+
fmt.Println("MetaData", resp.MetaData)
539+
fmt.Println("SQL Info", resp.SQLInfo)
540+
}

multi/multi.go

+43-2
Original file line numberDiff line numberDiff line change
@@ -340,13 +340,28 @@ 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.
343+
// Execute passes a sql expression to Tarantool for execution.
344344
//
345345
// Since 1.6.0
346-
func (connMulti *ConnectionMulti) Execute(expr string, args interface{}) (resp *tarantool.Response, err error) {
346+
func (connMulti *ConnectionMulti) Execute(expr interface{}, args interface{}) (resp *tarantool.Response, err error) {
347347
return connMulti.getCurrentConnection().Execute(expr, args)
348348
}
349349

350+
// Prepare sends a sql statement to prepare.
351+
//
352+
// Since 1.6.0
353+
func (connMulti *ConnectionMulti) Prepare(expr string) (resp *tarantool.Response, err error) {
354+
return connMulti.getCurrentConnection().Prepare(expr)
355+
}
356+
357+
// Unprepare undo the result of Prepare request.
358+
//
359+
// It is equal to conn.UnprepareAsync(stmt).Get().
360+
// Since 1.6.0
361+
func (connMulti *ConnectionMulti) Unprepare(stmt uint64) (resp *tarantool.Response, err error) {
362+
return connMulti.getCurrentConnection().Unprepare(stmt)
363+
}
364+
350365
// GetTyped performs select (with limit = 1 and offset = 0) to box space and
351366
// fills typed result.
352367
func (connMulti *ConnectionMulti) GetTyped(space, index interface{}, key interface{}, result interface{}) (err error) {
@@ -401,6 +416,11 @@ func (connMulti *ConnectionMulti) EvalTyped(expr string, args interface{}, resul
401416
return connMulti.getCurrentConnection().EvalTyped(expr, args, result)
402417
}
403418

419+
// ExecuteTyped passes a sql expression for execution.
420+
func (connMulti *ConnectionMulti) ExecuteTyped(expr interface{}, args interface{}, result interface{}) (tarantool.SQLInfo, []tarantool.ColumnMetaData, error) {
421+
return connMulti.getCurrentConnection().ExecuteTyped(expr, args, result)
422+
}
423+
404424
// SelectAsync sends select request to Tarantool and returns Future.
405425
func (connMulti *ConnectionMulti) SelectAsync(space, index interface{}, offset, limit, iterator uint32, key interface{}) *tarantool.Future {
406426
return connMulti.getCurrentConnection().SelectAsync(space, index, offset, limit, iterator, key)
@@ -454,3 +474,24 @@ func (connMulti *ConnectionMulti) Call17Async(functionName string, args interfac
454474
func (connMulti *ConnectionMulti) EvalAsync(expr string, args interface{}) *tarantool.Future {
455475
return connMulti.getCurrentConnection().EvalAsync(expr, args)
456476
}
477+
478+
// ExecuteAsync passes a sql expression for execution.
479+
//
480+
// Since 1.6.0
481+
func (connMulti *ConnectionMulti) ExecuteAsync(expr interface{}, args interface{}) *tarantool.Future {
482+
return connMulti.getCurrentConnection().ExecuteAsync(expr, args)
483+
}
484+
485+
// PrepareAsync passes a SQL statement to prepare.
486+
//
487+
// Since 1.6.0
488+
func (connMulti *ConnectionMulti) PrepareAsync(expr string) *tarantool.Future {
489+
return connMulti.getCurrentConnection().PrepareAsync(expr)
490+
}
491+
492+
// UnprepareAsync undo the result of Prepare request and returns Future.
493+
//
494+
// Since 1.6.0
495+
func (connMulti *ConnectionMulti) UnprepareAsync(stmt uint64) *tarantool.Future {
496+
return connMulti.getCurrentConnection().UnprepareAsync(stmt)
497+
}

request.go

+60-4
Original file line numberDiff line numberDiff line change
@@ -127,10 +127,26 @@ func (conn *Connection) Eval(expr string, args interface{}) (resp *Response, err
127127
//
128128
// It is equal to conn.ExecuteAsync(expr, args).Get().
129129
// Since 1.6.0
130-
func (conn *Connection) Execute(expr string, args interface{}) (resp *Response, err error) {
130+
func (conn *Connection) Execute(expr interface{}, args interface{}) (resp *Response, err error) {
131131
return conn.ExecuteAsync(expr, args).Get()
132132
}
133133

134+
// Prepare sends a sql statement to prepare.
135+
//
136+
// It is equal to conn.PrepareAsync(expr).Get().
137+
// Since 1.6.0
138+
func (conn *Connection) Prepare(expr string) (resp *Response, err error) {
139+
return conn.PrepareAsync(expr).Get()
140+
}
141+
142+
// Unprepare undo the result of Prepare request.
143+
//
144+
// It is equal to conn.UnprepareAsync(stmt).Get().
145+
// Since 1.6.0
146+
func (conn *Connection) Unprepare(stmt uint64) (resp *Response, err error) {
147+
return conn.UnprepareAsync(stmt).Get()
148+
}
149+
134150
// single used for conn.GetTyped for decode one tuple.
135151
type single struct {
136152
res interface{}
@@ -227,7 +243,7 @@ func (conn *Connection) EvalTyped(expr string, args interface{}, result interfac
227243
//
228244
// In addition to error returns sql info and columns meta data
229245
// Since 1.6.0
230-
func (conn *Connection) ExecuteTyped(expr string, args interface{}, result interface{}) (SQLInfo, []ColumnMetaData, error) {
246+
func (conn *Connection) ExecuteTyped(expr interface{}, args interface{}, result interface{}) (SQLInfo, []ColumnMetaData, error) {
231247
fut := conn.ExecuteAsync(expr, args)
232248
err := fut.GetTyped(&result)
233249
return fut.resp.SQLInfo, fut.resp.MetaData, err
@@ -369,17 +385,57 @@ func (conn *Connection) EvalAsync(expr string, args interface{}) *Future {
369385

370386
// ExecuteAsync sends a sql expression for execution and returns Future.
371387
// Since 1.6.0
372-
func (conn *Connection) ExecuteAsync(expr string, args interface{}) *Future {
388+
func (conn *Connection) ExecuteAsync(expr interface{}, args interface{}) *Future {
373389
future := conn.newFuture(ExecuteRequest)
390+
var stmtID uint64
391+
exprStr, ok := expr.(string)
392+
if !ok {
393+
stmtID, ok = expr.(uint64)
394+
if !ok {
395+
err := errors.New("expr argument must be either string or uint64")
396+
return NewErrorFuture(err)
397+
}
398+
return future.send(conn, func(enc *msgpack.Encoder) error {
399+
enc.EncodeMapLen(2)
400+
enc.EncodeUint64(KeyStmtID)
401+
enc.EncodeUint64(stmtID)
402+
enc.EncodeUint64(KeySQLBind)
403+
return encodeSQLBind(enc, args)
404+
})
405+
}
374406
return future.send(conn, func(enc *msgpack.Encoder) error {
375407
enc.EncodeMapLen(2)
376408
enc.EncodeUint64(KeySQLText)
377-
enc.EncodeString(expr)
409+
enc.EncodeString(exprStr)
378410
enc.EncodeUint64(KeySQLBind)
379411
return encodeSQLBind(enc, args)
380412
})
381413
}
382414

415+
// PrepareAsync sends a sql statement to prepare and returns Future.
416+
//
417+
// Since 1.6.0
418+
func (conn *Connection) PrepareAsync(expr string) *Future {
419+
future := conn.newFuture(PrepareRequest)
420+
return future.send(conn, func(enc *msgpack.Encoder) error {
421+
enc.EncodeMapLen(1)
422+
enc.EncodeUint64(KeySQLText)
423+
return enc.EncodeString(expr)
424+
})
425+
}
426+
427+
// UnprepareAsync undo the result of Prepare request and returns Future.
428+
//
429+
// Since 1.6.0
430+
func (conn *Connection) UnprepareAsync(stmt uint64) *Future {
431+
future := conn.newFuture(PrepareRequest)
432+
return future.send(conn, func(enc *msgpack.Encoder) error {
433+
enc.EncodeMapLen(1)
434+
enc.EncodeUint64(KeyStmtID)
435+
return enc.EncodeUint64(stmt)
436+
})
437+
}
438+
383439
// KeyValueBind is a type for encoding named SQL parameters
384440
type KeyValueBind struct {
385441
Key string

response.go

+5
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ type Response struct {
1414
Data []interface{}
1515
MetaData []ColumnMetaData
1616
SQLInfo SQLInfo
17+
StmtID uint64
1718
buf smallBuf
1819
}
1920

@@ -178,6 +179,10 @@ func (resp *Response) decodeBody() (err error) {
178179
if err = d.Decode(&resp.MetaData); err != nil {
179180
return err
180181
}
182+
case KeyStmtID:
183+
if resp.StmtID, err = d.DecodeUint64(); err != nil {
184+
return err
185+
}
181186
default:
182187
if err = d.Skip(); err != nil {
183188
return err

0 commit comments

Comments
 (0)