Skip to content

Commit 14bc31e

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 14bc31e

9 files changed

+335
-6
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

+4
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ type Connector interface {
1818
Call17(functionName string, args interface{}) (resp *Response, err error)
1919
Eval(expr string, args interface{}) (resp *Response, err error)
2020
Execute(expr string, args interface{}) (resp *Response, err error)
21+
Prepare(expr string) (stmt *PreparedStatement, err error)
2122

2223
GetTyped(space, index interface{}, key interface{}, result interface{}) (err error)
2324
SelectTyped(space, index interface{}, offset, limit, iterator uint32, key interface{}, result interface{}) (err error)
@@ -28,6 +29,7 @@ type Connector interface {
2829
CallTyped(functionName string, args interface{}, result interface{}) (err error)
2930
Call17Typed(functionName string, args interface{}, result interface{}) (err error)
3031
EvalTyped(expr string, args interface{}, result interface{}) (err error)
32+
ExecuteTyped(expr string, args interface{}, result interface{}) (SQLInfo, []ColumnMetaData, error)
3133

3234
SelectAsync(space, index interface{}, offset, limit, iterator uint32, key interface{}) *Future
3335
InsertAsync(space interface{}, tuple interface{}) *Future
@@ -38,4 +40,6 @@ type Connector interface {
3840
CallAsync(functionName string, args interface{}) *Future
3941
Call17Async(functionName string, args interface{}) *Future
4042
EvalAsync(expr string, args interface{}) *Future
43+
ExecuteAsync(expr string, args interface{}) *Future
44+
PrepareAsync(expr string) *PreparedStatement
4145
}

const.go

+3
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

@@ -31,9 +32,11 @@ const (
3132
KeyData = 0x30
3233
KeyError = 0x31
3334
KeyMetaData = 0x32
35+
KeyBindCount = 0x34
3436
KeySQLText = 0x40
3537
KeySQLBind = 0x41
3638
KeySQLInfo = 0x42
39+
KeyStmtID = 0x43
3740

3841
KeyFieldName = 0x00
3942
KeyFieldType = 0x01

example_test.go

+36
Original file line numberDiff line numberDiff line change
@@ -499,3 +499,39 @@ 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("Statement ID", stmtResp.StatementID)
528+
529+
// pass the id of the statement to Execute
530+
resp, err := stmtResp.Execute([]interface{}{2, "test"})
531+
fmt.Println("Execute")
532+
fmt.Println("Error", err)
533+
fmt.Println("Code", resp.Code)
534+
fmt.Println("Data", resp.Data)
535+
fmt.Println("MetaData", resp.MetaData)
536+
fmt.Println("SQL Info", resp.SQLInfo)
537+
}

multi/multi.go

+29-1
Original file line numberDiff line numberDiff line change
@@ -340,13 +340,22 @@ 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
346346
func (connMulti *ConnectionMulti) Execute(expr string, 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+
// Returns a PreparedStatement object for the current connection.
353+
//
354+
// Since 1.6.0
355+
func (connMulti *ConnectionMulti) Prepare(expr string) (resp *tarantool.PreparedStatement, err error) {
356+
return connMulti.getCurrentConnection().Prepare(expr)
357+
}
358+
350359
// GetTyped performs select (with limit = 1 and offset = 0) to box space and
351360
// fills typed result.
352361
func (connMulti *ConnectionMulti) GetTyped(space, index interface{}, key interface{}, result interface{}) (err error) {
@@ -401,6 +410,11 @@ func (connMulti *ConnectionMulti) EvalTyped(expr string, args interface{}, resul
401410
return connMulti.getCurrentConnection().EvalTyped(expr, args, result)
402411
}
403412

413+
// ExecuteTyped passes a sql expression for execution.
414+
func (connMulti *ConnectionMulti) ExecuteTyped(expr string, args interface{}, result interface{}) (tarantool.SQLInfo, []tarantool.ColumnMetaData, error) {
415+
return connMulti.getCurrentConnection().ExecuteTyped(expr, args, result)
416+
}
417+
404418
// SelectAsync sends select request to Tarantool and returns Future.
405419
func (connMulti *ConnectionMulti) SelectAsync(space, index interface{}, offset, limit, iterator uint32, key interface{}) *tarantool.Future {
406420
return connMulti.getCurrentConnection().SelectAsync(space, index, offset, limit, iterator, key)
@@ -454,3 +468,17 @@ func (connMulti *ConnectionMulti) Call17Async(functionName string, args interfac
454468
func (connMulti *ConnectionMulti) EvalAsync(expr string, args interface{}) *tarantool.Future {
455469
return connMulti.getCurrentConnection().EvalAsync(expr, args)
456470
}
471+
472+
// ExecuteAsync passes a sql expression for execution.
473+
//
474+
// Since 1.6.0
475+
func (connMulti *ConnectionMulti) ExecuteAsync(expr string, args interface{}) *tarantool.Future {
476+
return connMulti.getCurrentConnection().ExecuteAsync(expr, args)
477+
}
478+
479+
// PrepareAsync passes a SQL statement to prepare.
480+
//
481+
// Since 1.6.0
482+
func (connMulti *ConnectionMulti) PrepareAsync(expr string) *tarantool.PreparedStatement {
483+
return connMulti.getCurrentConnection().PrepareAsync(expr)
484+
}

request.go

+84
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,16 @@ func (conn *Connection) Execute(expr string, args interface{}) (resp *Response,
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) (stmt *PreparedStatement, err error) {
139+
stmt = conn.PrepareAsync(expr)
140+
err = stmt.wait()
141+
return stmt, err
142+
}
143+
134144
// single used for conn.GetTyped for decode one tuple.
135145
type single struct {
136146
res interface{}
@@ -380,6 +390,80 @@ func (conn *Connection) ExecuteAsync(expr string, args interface{}) *Future {
380390
})
381391
}
382392

393+
// PrepareAsync sends a sql statement to prepare and returns Future.
394+
//
395+
// Since 1.6.0
396+
func (conn *Connection) PrepareAsync(expr string) *PreparedStatement {
397+
future := conn.newFuture(PrepareRequest)
398+
stmt := newPreparedStatement(future.send(conn, func(enc *msgpack.Encoder) error {
399+
enc.EncodeMapLen(1)
400+
enc.EncodeUint64(KeySQLText)
401+
return enc.EncodeString(expr)
402+
}), conn)
403+
return stmt
404+
}
405+
406+
type PreparedStatementID uint64
407+
408+
type PreparedStatement struct {
409+
StatementID PreparedStatementID
410+
MetaData []ColumnMetaData
411+
ParamCount uint64
412+
conn *Connection
413+
fut *Future
414+
}
415+
416+
func newPreparedStatement(fut *Future, conn *Connection) *PreparedStatement {
417+
stmt := new(PreparedStatement)
418+
stmt.fut = fut
419+
stmt.conn = conn
420+
return stmt
421+
}
422+
423+
// wait until the prepared statement is ready and fill the statement object
424+
func (stmt *PreparedStatement) wait() error {
425+
resp, err := stmt.fut.Get()
426+
stmt.StatementID = PreparedStatementID(resp.StmtID)
427+
stmt.MetaData = resp.MetaData
428+
stmt.ParamCount = resp.BindCount
429+
return err
430+
}
431+
432+
func (stmt *PreparedStatement) UnprepareAsync() *Future {
433+
err := stmt.wait()
434+
if err != nil {
435+
return NewErrorFuture(err)
436+
}
437+
future := stmt.conn.newFuture(PrepareRequest)
438+
return future.send(stmt.conn, func(enc *msgpack.Encoder) error {
439+
enc.EncodeMapLen(1)
440+
enc.EncodeUint64(KeyStmtID)
441+
return enc.EncodeUint64(uint64(stmt.StatementID))
442+
})
443+
}
444+
445+
func (stmt *PreparedStatement) Unprepare() (resp *Response, err error) {
446+
return stmt.UnprepareAsync().Get()
447+
}
448+
449+
func (stmt *PreparedStatement) ExecuteAsync(args interface{}) *Future {
450+
err := stmt.wait()
451+
if err != nil {
452+
return NewErrorFuture(err)
453+
}
454+
return stmt.fut.send(stmt.conn, func(enc *msgpack.Encoder) error {
455+
enc.EncodeMapLen(2)
456+
enc.EncodeUint64(KeyStmtID)
457+
enc.EncodeUint64(uint64(stmt.StatementID))
458+
enc.EncodeUint64(KeySQLBind)
459+
return encodeSQLBind(enc, args)
460+
})
461+
}
462+
463+
func (stmt *PreparedStatement) Execute(args interface{}) (resp *Response, err error) {
464+
return stmt.ExecuteAsync(args).Get()
465+
}
466+
383467
// KeyValueBind is a type for encoding named SQL parameters
384468
type KeyValueBind struct {
385469
Key string

response.go

+14-4
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@ type Response struct {
1111
Code uint32
1212
Error string // error message
1313
// Data contains deserialized data for untyped requests
14-
Data []interface{}
15-
MetaData []ColumnMetaData
16-
SQLInfo SQLInfo
17-
buf smallBuf
14+
Data []interface{}
15+
MetaData []ColumnMetaData
16+
SQLInfo SQLInfo
17+
StmtID uint64
18+
BindCount uint64
19+
buf smallBuf
1820
}
1921

2022
type ColumnMetaData struct {
@@ -178,6 +180,14 @@ func (resp *Response) decodeBody() (err error) {
178180
if err = d.Decode(&resp.MetaData); err != nil {
179181
return err
180182
}
183+
case KeyStmtID:
184+
if resp.StmtID, err = d.DecodeUint64(); err != nil {
185+
return err
186+
}
187+
case KeyBindCount:
188+
if resp.BindCount, err = d.DecodeUint64(); err != nil {
189+
return err
190+
}
181191
default:
182192
if err = d.Skip(); err != nil {
183193
return err

0 commit comments

Comments
 (0)