Skip to content

Commit 6b7fe6b

Browse files
committed
sql: support prepared statements
This patch adds the support of prepared statements. Added a new type for handling prepared statements. 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 b73b8b7 commit 6b7fe6b

10 files changed

+443
-7
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release.
1515
- Public API with request object types (#126)
1616
- Support decimal type in msgpack (#96)
1717
- Support datetime type in msgpack (#118)
18+
- Prepared SQL statements (#117)
1819

1920
### Changed
2021

config.lua

+4
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,10 @@ local function push_func(cnt)
118118
end
119119
rawset(_G, 'push_func', push_func)
120120

121+
local function test_stmt_count_check()
122+
return box.info().sql().cache.stmt_count
123+
end
124+
rawset(_G, 'test_stmt_count_check', test_stmt_count_check)
121125
box.space.test:truncate()
122126

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

connection_pool/connection_pool.go

+25-1
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,18 @@ func (connPool *ConnectionPool) Execute(expr string, args interface{}, userMode
314314
return conn.Execute(expr, args)
315315
}
316316

317+
// Prepare sends a sql statement to prepare.
318+
//
319+
// Since 1.7.0
320+
func (connPool *ConnectionPool) Prepare(expr string, userMode Mode) (stmt *tarantool.PreparedStatement, err error) {
321+
conn, err := connPool.getNextConnection(userMode)
322+
if err != nil {
323+
return nil, err
324+
}
325+
326+
return conn.Prepare(expr)
327+
}
328+
317329
// GetTyped performs select (with limit = 1 and offset = 0)
318330
// to box space and fills typed result.
319331
func (connPool *ConnectionPool) GetTyped(space, index interface{}, key interface{}, result interface{}, userMode ...Mode) (err error) {
@@ -424,7 +436,7 @@ func (connPool *ConnectionPool) EvalTyped(expr string, args interface{}, result
424436
return conn.EvalTyped(expr, args, result)
425437
}
426438

427-
// ExecuteTyped passes a sql expression for execution.
439+
// ExecuteTyped passes an sql expression for execution.
428440
//
429441
// Since 1.7.0
430442
func (connPool *ConnectionPool) ExecuteTyped(expr string, args interface{}, result interface{}, userMode Mode) (tarantool.SQLInfo, []tarantool.ColumnMetaData, error) {
@@ -560,6 +572,18 @@ func (connPool *ConnectionPool) ExecuteAsync(expr string, args interface{}, user
560572
return conn.ExecuteAsync(expr, args)
561573
}
562574

575+
// PrepareAsync sends a sql statement to prepare and returns PreparedStatement object.
576+
//
577+
// Since 1.7.0
578+
func (connPool *ConnectionPool) PrepareAsync(expr string, userMode Mode) *tarantool.PreparedStatement {
579+
conn, err := connPool.getNextConnection(userMode)
580+
if err != nil {
581+
return nil
582+
}
583+
584+
return conn.PrepareAsync(expr)
585+
}
586+
563587
// Do sends the request and returns a response.
564588
func (connPool *ConnectionPool) Do(req tarantool.Request, userMode Mode) (*tarantool.Response, error) {
565589
conn, err := connPool.getNextConnection(userMode)

connector.go

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

2324
GetTyped(space, index interface{}, key interface{}, result interface{}) (err error)
2425
SelectTyped(space, index interface{}, offset, limit, iterator uint32, key interface{}, result interface{}) (err error)
@@ -30,6 +31,7 @@ type Connector interface {
3031
Call16Typed(functionName string, args interface{}, result interface{}) (err error)
3132
Call17Typed(functionName string, args interface{}, result interface{}) (err error)
3233
EvalTyped(expr string, args interface{}, result interface{}) (err error)
34+
ExecuteTyped(expr string, args interface{}, result interface{}) (SQLInfo, []ColumnMetaData, error)
3335

3436
SelectAsync(space, index interface{}, offset, limit, iterator uint32, key interface{}) *Future
3537
InsertAsync(space interface{}, tuple interface{}) *Future
@@ -41,6 +43,8 @@ type Connector interface {
4143
Call16Async(functionName string, args interface{}) *Future
4244
Call17Async(functionName string, args interface{}) *Future
4345
EvalAsync(expr string, args interface{}) *Future
46+
ExecuteAsync(expr string, args interface{}) *Future
47+
PrepareAsync(expr string) *PreparedStatement
4448

4549
Do(req Request) (resp *Response, err error)
4650
DoTyped(req Request, result interface{}) (err error)

const.go

+3
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const (
1212
UpsertRequestCode = 9
1313
Call17RequestCode = 10 /* call in >= 1.7 format */
1414
ExecuteRequestCode = 11
15+
PrepareRequestCode = 13
1516
PingRequestCode = 64
1617
SubscribeRequestCode = 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

+45
Original file line numberDiff line numberDiff line change
@@ -651,3 +651,48 @@ func ExampleConnection_Execute() {
651651
fmt.Println("MetaData", resp.MetaData)
652652
fmt.Println("SQL Info", resp.SQLInfo)
653653
}
654+
655+
// To use prepared statements to query a tarantool instance, call Prepare.
656+
func ExampleConnection_Prepare() {
657+
// Tarantool supports SQL since version 2.0.0
658+
isLess, _ := test_helpers.IsTarantoolVersionLess(2, 0, 0)
659+
if isLess {
660+
return
661+
}
662+
server := "127.0.0.1:3013"
663+
opts := tarantool.Opts{
664+
Timeout: 500 * time.Millisecond,
665+
Reconnect: 1 * time.Second,
666+
MaxReconnects: 3,
667+
User: "test",
668+
Pass: "test",
669+
}
670+
client, err := tarantool.Connect(server, opts)
671+
if err != nil {
672+
fmt.Printf("Failed to connect: %s", err.Error())
673+
}
674+
675+
// pass a query to prepare
676+
stmtResp, err := client.Prepare("SELECT id FROM SQL_TEST WHERE id=? AND name=?")
677+
fmt.Println("Prepare")
678+
fmt.Println("Error", err)
679+
fmt.Println("Statement ID", stmtResp.StatementID)
680+
681+
// pass the id of the statement to Execute
682+
resp, err := stmtResp.Execute([]interface{}{1, "test_1"})
683+
fmt.Println("Execute")
684+
fmt.Println("Error", err)
685+
fmt.Println("Code", resp.Code)
686+
fmt.Println("Data", resp.Data)
687+
fmt.Println("MetaData", resp.MetaData)
688+
fmt.Println("SQL Info", resp.SQLInfo)
689+
690+
// use the same object for execute with other arguments
691+
resp, err = stmtResp.Execute([]interface{}{2, "test_2"})
692+
fmt.Println("Execute")
693+
fmt.Println("Error", err)
694+
fmt.Println("Code", resp.Code)
695+
fmt.Println("Data", resp.Data)
696+
fmt.Println("MetaData", resp.MetaData)
697+
fmt.Println("SQL Info", resp.SQLInfo)
698+
}

multi/multi.go

+18-2
Original file line numberDiff line numberDiff line change
@@ -349,13 +349,22 @@ func (connMulti *ConnectionMulti) Eval(expr string, args interface{}) (resp *tar
349349
return connMulti.getCurrentConnection().Eval(expr, args)
350350
}
351351

352-
// Execute passes a sql expression to Tarantool for execution.
352+
// Execute passes an sql expression to Tarantool for execution.
353353
//
354354
// Since 1.6.0
355355
func (connMulti *ConnectionMulti) Execute(expr string, args interface{}) (resp *tarantool.Response, err error) {
356356
return connMulti.getCurrentConnection().Execute(expr, args)
357357
}
358358

359+
// Prepare sends a sql statement to prepare.
360+
//
361+
// Returns a PreparedStatement object for the current connection.
362+
//
363+
// Since 1.7.0
364+
func (connMulti *ConnectionMulti) Prepare(expr string) (resp *tarantool.PreparedStatement, err error) {
365+
return connMulti.getCurrentConnection().Prepare(expr)
366+
}
367+
359368
// GetTyped performs select (with limit = 1 and offset = 0) to box space and
360369
// fills typed result.
361370
func (connMulti *ConnectionMulti) GetTyped(space, index interface{}, key interface{}, result interface{}) (err error) {
@@ -419,7 +428,7 @@ func (connMulti *ConnectionMulti) EvalTyped(expr string, args interface{}, resul
419428
return connMulti.getCurrentConnection().EvalTyped(expr, args, result)
420429
}
421430

422-
// ExecuteTyped passes a sql expression for execution.
431+
// ExecuteTyped passes an sql expression for execution.
423432
func (connMulti *ConnectionMulti) ExecuteTyped(expr string, args interface{}, result interface{}) (tarantool.SQLInfo, []tarantool.ColumnMetaData, error) {
424433
return connMulti.getCurrentConnection().ExecuteTyped(expr, args, result)
425434
}
@@ -494,6 +503,13 @@ func (connMulti *ConnectionMulti) ExecuteAsync(expr string, args interface{}) *t
494503
return connMulti.getCurrentConnection().ExecuteAsync(expr, args)
495504
}
496505

506+
// PrepareAsync passes a sql statement to prepare.
507+
//
508+
// Since 1.7.0
509+
func (connMulti *ConnectionMulti) PrepareAsync(expr string) *tarantool.PreparedStatement {
510+
return connMulti.getCurrentConnection().PrepareAsync(expr)
511+
}
512+
497513
// Do sends the request and returns a response.
498514
func (connMulti *ConnectionMulti) Do(req tarantool.Request) (*tarantool.Response, error) {
499515
return connMulti.getCurrentConnection().Do(req)

request.go

+162
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,26 @@ func fillExecute(enc *msgpack.Encoder, expr string, args interface{}) error {
9191
return encodeSQLBind(enc, args)
9292
}
9393

94+
func fillPrepare(enc *msgpack.Encoder, expr string) error {
95+
enc.EncodeMapLen(1)
96+
enc.EncodeUint64(KeySQLText)
97+
return enc.EncodeString(expr)
98+
}
99+
100+
func fillUnprepare(enc *msgpack.Encoder, stmt PreparedStatement) error {
101+
enc.EncodeMapLen(1)
102+
enc.EncodeUint64(KeyStmtID)
103+
return enc.EncodeUint64(uint64(stmt.StatementID))
104+
}
105+
106+
func fillPreparedExecute(enc *msgpack.Encoder, stmt PreparedStatement, args interface{}) error {
107+
enc.EncodeMapLen(2)
108+
enc.EncodeUint64(KeyStmtID)
109+
enc.EncodeUint64(uint64(stmt.StatementID))
110+
enc.EncodeUint64(KeySQLBind)
111+
return encodeSQLBind(enc, args)
112+
}
113+
94114
func fillPing(enc *msgpack.Encoder) error {
95115
return enc.EncodeMapLen(0)
96116
}
@@ -190,6 +210,16 @@ func (conn *Connection) Execute(expr string, args interface{}) (resp *Response,
190210
return conn.ExecuteAsync(expr, args).Get()
191211
}
192212

213+
// Prepare sends a sql statement to prepare.
214+
//
215+
// It is equal to conn.PrepareAsync(expr).Get().
216+
// Since 1.7.0
217+
func (conn *Connection) Prepare(expr string) (stmt *PreparedStatement, err error) {
218+
stmt = conn.PrepareAsync(expr)
219+
err = stmt.wait()
220+
return stmt, err
221+
}
222+
193223
// single used for conn.GetTyped for decode one tuple.
194224
type single struct {
195225
res interface{}
@@ -389,6 +419,78 @@ func (conn *Connection) ExecuteAsync(expr string, args interface{}) *Future {
389419
return conn.DoAsync(req)
390420
}
391421

422+
// PrepareAsync sends a sql statement to prepare and returns PreparedStatement object.
423+
//
424+
// Since 1.7.0
425+
func (conn *Connection) PrepareAsync(expr string) *PreparedStatement {
426+
req := newPrepareRequest(expr)
427+
fut := conn.DoAsync(req)
428+
stmt := newPreparedStatement(fut, conn)
429+
return stmt
430+
}
431+
432+
type PreparedStatementID uint64
433+
434+
// PreparedStatement is a type for handling prepared statements
435+
//
436+
// Since 1.7.0
437+
type PreparedStatement struct {
438+
StatementID PreparedStatementID
439+
MetaData []ColumnMetaData
440+
ParamCount uint64
441+
conn *Connection
442+
fut *Future
443+
}
444+
445+
func newPreparedStatement(fut *Future, conn *Connection) *PreparedStatement {
446+
stmt := new(PreparedStatement)
447+
stmt.fut = fut
448+
stmt.conn = conn
449+
return stmt
450+
}
451+
452+
// wait until the prepared statement is ready and fill the statement object
453+
func (stmt *PreparedStatement) wait() error {
454+
resp, err := stmt.fut.Get()
455+
stmt.StatementID = PreparedStatementID(resp.StmtID)
456+
stmt.MetaData = resp.MetaData
457+
stmt.ParamCount = resp.BindCount
458+
return err
459+
}
460+
461+
// UnprepareAsync sends an undo request and returns Future
462+
func (stmt *PreparedStatement) UnprepareAsync() *Future {
463+
err := stmt.wait()
464+
if err != nil {
465+
return NewErrorFuture(err)
466+
}
467+
req := newUnprepareRequest(*stmt)
468+
fut := stmt.conn.DoAsync(req)
469+
return fut
470+
}
471+
472+
// Unprepare undo the prepared statement
473+
func (stmt *PreparedStatement) Unprepare() (resp *Response, err error) {
474+
return stmt.UnprepareAsync().Get()
475+
}
476+
477+
// ExecuteAsync sends the prepared SQL statement for execution and returns Future
478+
func (stmt *PreparedStatement) ExecuteAsync(args interface{}) *Future {
479+
err := stmt.wait()
480+
if err != nil {
481+
return NewErrorFuture(err)
482+
}
483+
req := newPreparedExecuteRequest(*stmt)
484+
req.Args(args)
485+
fut := stmt.conn.DoAsync(req)
486+
return fut
487+
}
488+
489+
// Execute sends the prepared SQL statement for execution
490+
func (stmt *PreparedStatement) Execute(args interface{}) (resp *Response, err error) {
491+
return stmt.ExecuteAsync(args).Get()
492+
}
493+
392494
// KeyValueBind is a type for encoding named SQL parameters
393495
type KeyValueBind struct {
394496
Key string
@@ -981,3 +1083,63 @@ func (req *ExecuteRequest) Args(args interface{}) *ExecuteRequest {
9811083
func (req *ExecuteRequest) Body(res SchemaResolver, enc *msgpack.Encoder) error {
9821084
return fillExecute(enc, req.expr, req.args)
9831085
}
1086+
1087+
type prepareRequest struct {
1088+
baseRequest
1089+
expr string
1090+
}
1091+
1092+
// newPrepareRequest returns a new empty prepareRequest.
1093+
func newPrepareRequest(expr string) *prepareRequest {
1094+
req := new(prepareRequest)
1095+
req.requestCode = PrepareRequestCode
1096+
req.expr = expr
1097+
return req
1098+
}
1099+
1100+
// Body fills an encoder with the execute request body.
1101+
func (req *prepareRequest) Body(res SchemaResolver, enc *msgpack.Encoder) error {
1102+
return fillPrepare(enc, req.expr)
1103+
}
1104+
1105+
type unprepareRequest struct {
1106+
baseRequest
1107+
stmt PreparedStatement
1108+
}
1109+
1110+
// newUnprepareRequest returns a new empty unprepareRequest.
1111+
func newUnprepareRequest(stmt PreparedStatement) *unprepareRequest {
1112+
req := new(unprepareRequest)
1113+
req.requestCode = PrepareRequestCode
1114+
req.stmt = stmt
1115+
return req
1116+
}
1117+
1118+
// Body fills an encoder with the execute request body.
1119+
func (req *unprepareRequest) Body(res SchemaResolver, enc *msgpack.Encoder) error {
1120+
return fillUnprepare(enc, req.stmt)
1121+
}
1122+
1123+
type preparedExecuteRequest struct {
1124+
baseRequest
1125+
stmt PreparedStatement
1126+
args interface{}
1127+
}
1128+
1129+
// newPreparedExecuteRequest returns a new empty preparedExecuteRequest.
1130+
func newPreparedExecuteRequest(stmt PreparedStatement) *preparedExecuteRequest {
1131+
req := new(preparedExecuteRequest)
1132+
req.requestCode = ExecuteRequestCode
1133+
req.stmt = stmt
1134+
return req
1135+
}
1136+
1137+
func (req *preparedExecuteRequest) Args(args interface{}) *preparedExecuteRequest {
1138+
req.args = args
1139+
return req
1140+
}
1141+
1142+
// Body fills an encoder with the execute request body.
1143+
func (req *preparedExecuteRequest) Body(res SchemaResolver, enc *msgpack.Encoder) error {
1144+
return fillPreparedExecute(enc, req.stmt, req.args)
1145+
}

0 commit comments

Comments
 (0)