Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 530fe4f

Browse files
committedJul 7, 2022
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 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 9fb2337 commit 530fe4f

15 files changed

+472
-8
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

-1
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,6 @@ local function push_func(cnt)
117117
return cnt
118118
end
119119
rawset(_G, 'push_func', push_func)
120-
121120
box.space.test:truncate()
122121

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

‎connection.go

+10
Original file line numberDiff line numberDiff line change
@@ -1009,3 +1009,13 @@ func (conn *Connection) OverrideSchema(s *Schema) {
10091009
conn.Schema = s
10101010
}
10111011
}
1012+
1013+
// Prepare passes a sql statement to Tarantool for preparation synchronously.
1014+
func (conn *Connection) Prepare(expr string) (*PreparedStatement, error) {
1015+
req := NewPrepareRequest(expr)
1016+
resp, err := conn.Do(req).Get()
1017+
if err != nil {
1018+
return nil, err
1019+
}
1020+
return newPreparedStatement(conn, resp), nil
1021+
}

‎connection_pool/config.lua

+15
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,21 @@ box.once("init", function()
2121
parts = {{ field = 1, type = 'string' }},
2222
if_not_exists = true
2323
})
24+
25+
local sp = box.schema.space.create('SQL_TEST', {
26+
id = 521,
27+
if_not_exists = true,
28+
format = {
29+
{name = "NAME0", type = "unsigned"},
30+
{name = "NAME1", type = "string"},
31+
{name = "NAME2", type = "string"},
32+
}
33+
})
34+
sp:create_index('primary', {type = 'tree', parts = {1, 'uint'}, if_not_exists = true})
35+
sp:insert{1, "test", "test"}
36+
-- grants for sql tests
37+
box.schema.user.grant('test', 'create,read,write,drop,alter', 'space')
38+
box.schema.user.grant('test', 'create', 'sequence')
2439
end)
2540

2641
local function simple_incr(a)

‎connection_pool/connection_pool.go

+12
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,9 @@ func (connPool *ConnectionPool) EvalAsync(expr string, args interface{}, userMod
526526

527527
// Do sends the request and returns a future.
528528
func (connPool *ConnectionPool) Do(req tarantool.Request, userMode Mode) *tarantool.Future {
529+
if stickyReq, ok := req.(tarantool.StickyRequest); ok {
530+
return stickyReq.Conn().Do(req)
531+
}
529532
conn, err := connPool.getNextConnection(userMode)
530533
if err != nil {
531534
return newErrorFuture(err)
@@ -788,3 +791,12 @@ func newErrorFuture(err error) *tarantool.Future {
788791
fut.SetError(err)
789792
return fut
790793
}
794+
795+
// Prepare passes a sql statement to Tarantool for preparation synchronously.
796+
func (connPool *ConnectionPool) Prepare(expr string, userMode Mode) (*tarantool.PreparedStatement, error) {
797+
conn, err := connPool.getNextConnection(userMode)
798+
if err != nil {
799+
return nil, err
800+
}
801+
return conn.Prepare(expr)
802+
}

‎connection_pool/connection_pool_test.go

+60
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package connection_pool_test
33
import (
44
"log"
55
"os"
6+
"reflect"
67
"strings"
78
"testing"
89
"time"
@@ -1276,6 +1277,65 @@ func TestDo(t *testing.T) {
12761277
require.NotNilf(t, resp, "response is nil after Ping")
12771278
}
12781279

1280+
func TestPrepare(t *testing.T) {
1281+
roles := []bool{true, true, false, true, false}
1282+
1283+
err := test_helpers.SetClusterRO(servers, connOpts, roles)
1284+
require.Nilf(t, err, "fail to set roles for cluster")
1285+
1286+
connPool, err := connection_pool.Connect(servers, connOpts)
1287+
require.Nilf(t, err, "failed to connect")
1288+
require.NotNilf(t, connPool, "conn is nil after Connect")
1289+
1290+
defer connPool.Close()
1291+
1292+
stmt, err := connPool.Prepare("SELECT NAME0, NAME1 FROM SQL_TEST WHERE NAME0=:id AND NAME1=:name;", connection_pool.ANY)
1293+
require.Nilf(t, err, "fail to prepare statement: %v", err)
1294+
1295+
executeReq := tarantool.NewExecutePreparedRequest(stmt)
1296+
unprepareReq := tarantool.NewUnprepareRequest(stmt)
1297+
1298+
resp, err := connPool.Do(executeReq.Args([]interface{}{1, "test"}), connection_pool.ANY).Get()
1299+
if err != nil {
1300+
t.Fatalf("failed to execute prepared: %v", err)
1301+
}
1302+
if resp == nil {
1303+
t.Fatalf("nil response")
1304+
}
1305+
if resp.Code != tarantool.OkCode {
1306+
t.Fatalf("failed to execute prepared: code %d", resp.Code)
1307+
}
1308+
if reflect.DeepEqual(resp.Data[0], []interface{}{1, "test"}) {
1309+
t.Error("Select with named arguments failed")
1310+
}
1311+
if resp.MetaData[0].FieldType != "unsigned" ||
1312+
resp.MetaData[0].FieldName != "NAME0" ||
1313+
resp.MetaData[1].FieldType != "string" ||
1314+
resp.MetaData[1].FieldName != "NAME1" {
1315+
t.Error("Wrong metadata")
1316+
}
1317+
1318+
resp, err = connPool.Do(unprepareReq, connection_pool.ANY).Get()
1319+
if err != nil {
1320+
t.Errorf("failed to unprepare prepared statement: %v", err)
1321+
}
1322+
if resp.Code != tarantool.OkCode {
1323+
t.Errorf("failed to unprepare prepared statement: code %d", resp.Code)
1324+
}
1325+
1326+
_, err = connPool.Do(unprepareReq, connection_pool.ANY).Get()
1327+
if err == nil {
1328+
t.Errorf("the statement must be already unprepared")
1329+
}
1330+
require.Contains(t, err.Error(), "Prepared statement with id")
1331+
1332+
_, err = connPool.Do(executeReq, connection_pool.ANY).Get()
1333+
if err == nil {
1334+
t.Errorf("the statement must be already unprepared")
1335+
}
1336+
require.Contains(t, err.Error(), "Prepared statement with id")
1337+
}
1338+
12791339
// runTestMain is a body of TestMain function
12801340
// (see https://pkg.go.dev/testing#hdr-Main).
12811341
// Using defer + os.Exit is not works so TestMain body

‎connection_pool/example_test.go

+25
Original file line numberDiff line numberDiff line change
@@ -548,3 +548,28 @@ func ExampleConnectionPool_Do() {
548548
// Ping Data []
549549
// Ping Error <nil>
550550
}
551+
552+
func ExampleConnectionPool_Prepare() {
553+
pool, err := examplePool(testRoles)
554+
if err != nil {
555+
fmt.Println(err)
556+
}
557+
defer pool.Close()
558+
559+
stmt, err := pool.Prepare("SELECT 1", connection_pool.ANY)
560+
if err != nil {
561+
fmt.Println(err)
562+
}
563+
564+
executeReq := tarantool.NewExecutePreparedRequest(stmt)
565+
unprepareReq := tarantool.NewUnprepareRequest(stmt)
566+
567+
_, err = pool.Do(executeReq, connection_pool.ANY).Get()
568+
if err != nil {
569+
fmt.Printf("Failed to execute prepared stmt")
570+
}
571+
_, err = pool.Do(unprepareReq, connection_pool.ANY).Get()
572+
if err != nil {
573+
fmt.Printf("Failed to prepare")
574+
}
575+
}

‎connector.go

+4
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ type Connector interface {
3030
Call16Typed(functionName string, args interface{}, result interface{}) (err error)
3131
Call17Typed(functionName string, args interface{}, result interface{}) (err error)
3232
EvalTyped(expr string, args interface{}, result interface{}) (err error)
33+
ExecuteTyped(expr string, args interface{}, result interface{}) (SQLInfo, []ColumnMetaData, error)
3334

3435
SelectAsync(space, index interface{}, offset, limit, iterator uint32, key interface{}) *Future
3536
InsertAsync(space interface{}, tuple interface{}) *Future
@@ -41,6 +42,9 @@ type Connector interface {
4142
Call16Async(functionName string, args interface{}) *Future
4243
Call17Async(functionName string, args interface{}) *Future
4344
EvalAsync(expr string, args interface{}) *Future
45+
ExecuteAsync(expr string, args interface{}) *Future
46+
47+
Prepare(expr string) (*PreparedStatement, error)
4448

4549
Do(req Request) (fut *Future)
4650
}

‎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

+40
Original file line numberDiff line numberDiff line change
@@ -651,3 +651,43 @@ 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_Do() {
657+
// Tarantool supports SQL since version 2.0.0
658+
isLess, _ := test_helpers.IsTarantoolVersionLess(2, 0, 0)
659+
if isLess {
660+
return
661+
}
662+
663+
server := "127.0.0.1:3013"
664+
opts := tarantool.Opts{
665+
Timeout: 500 * time.Millisecond,
666+
Reconnect: 1 * time.Second,
667+
MaxReconnects: 3,
668+
User: "test",
669+
Pass: "test",
670+
}
671+
conn, err := tarantool.Connect(server, opts)
672+
if err != nil {
673+
fmt.Printf("Failed to connect: %s", err.Error())
674+
}
675+
676+
stmt, err := conn.Prepare("SELECT 1")
677+
if err != nil {
678+
fmt.Printf("Failed to connect: %s", err.Error())
679+
}
680+
681+
executeReq := tarantool.NewExecutePreparedRequest(stmt)
682+
unprepareReq := tarantool.NewUnprepareRequest(stmt)
683+
684+
_, err = conn.Do(executeReq).Get()
685+
if err != nil {
686+
fmt.Printf("Failed to execute prepared stmt")
687+
}
688+
689+
_, err = conn.Do(unprepareReq).Get()
690+
if err != nil {
691+
fmt.Printf("Failed to prepare")
692+
}
693+
}

‎future.go

+13
Original file line numberDiff line numberDiff line change
@@ -239,3 +239,16 @@ func (fut *Future) Err() error {
239239
fut.wait()
240240
return fut.err
241241
}
242+
243+
func (fut *Future) GetPreparedStatement(conn *Connection) (*PreparedStatement, error) {
244+
resp, err := fut.Get()
245+
if err != nil {
246+
return nil, err
247+
}
248+
return &PreparedStatement{
249+
StatementID: PreparedStatementID(resp.StmtID),
250+
ParamCount: resp.BindCount,
251+
Conn: conn,
252+
MetaData: resp.MetaData,
253+
}, nil
254+
}

‎multi/multi.go

+15
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,11 @@ func (connMulti *ConnectionMulti) EvalTyped(expr string, args interface{}, resul
419419
return connMulti.getCurrentConnection().EvalTyped(expr, args, result)
420420
}
421421

422+
// ExecuteTyped passes sql expression to Tarantool for execution.
423+
func (connMulti *ConnectionMulti) ExecuteTyped(expr string, args interface{}, result interface{}) (tarantool.SQLInfo, []tarantool.ColumnMetaData, error) {
424+
return connMulti.getCurrentConnection().ExecuteTyped(expr, args, result)
425+
}
426+
422427
// SelectAsync sends select request to Tarantool and returns Future.
423428
func (connMulti *ConnectionMulti) SelectAsync(space, index interface{}, offset, limit, iterator uint32, key interface{}) *tarantool.Future {
424429
return connMulti.getCurrentConnection().SelectAsync(space, index, offset, limit, iterator, key)
@@ -482,6 +487,16 @@ func (connMulti *ConnectionMulti) EvalAsync(expr string, args interface{}) *tara
482487
return connMulti.getCurrentConnection().EvalAsync(expr, args)
483488
}
484489

490+
// ExecuteAsync passes sql expression to Tarantool for execution.
491+
func (connMulti *ConnectionMulti) ExecuteAsync(expr string, args interface{}) *tarantool.Future {
492+
return connMulti.getCurrentConnection().ExecuteAsync(expr, args)
493+
}
494+
495+
// Prepare passes a sql statement to Tarantool for preparation synchronously.
496+
func (connMulti *ConnectionMulti) Prepare(expr string) (*tarantool.PreparedStatement, error) {
497+
return connMulti.getCurrentConnection().Prepare(expr)
498+
}
499+
485500
// Do sends the request and returns a future.
486501
func (connMulti *ConnectionMulti) Do(req tarantool.Request) *tarantool.Future {
487502
return connMulti.getCurrentConnection().Do(req)

‎request.go

+118
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
}
@@ -389,6 +409,27 @@ func (conn *Connection) ExecuteAsync(expr string, args interface{}) *Future {
389409
return conn.Do(req)
390410
}
391411

412+
type PreparedStatementID uint64
413+
414+
// PreparedStatement is a type for handling prepared statements
415+
//
416+
// Since 1.7.0
417+
type PreparedStatement struct {
418+
StatementID PreparedStatementID
419+
MetaData []ColumnMetaData
420+
ParamCount uint64
421+
Conn *Connection
422+
}
423+
424+
func newPreparedStatement(conn *Connection, resp *Response) *PreparedStatement {
425+
stmt := new(PreparedStatement)
426+
stmt.Conn = conn
427+
stmt.ParamCount = resp.BindCount
428+
stmt.MetaData = resp.MetaData
429+
stmt.StatementID = PreparedStatementID(resp.StmtID)
430+
return stmt
431+
}
432+
392433
// KeyValueBind is a type for encoding named SQL parameters
393434
type KeyValueBind struct {
394435
Key string
@@ -981,3 +1022,80 @@ func (req *ExecuteRequest) Args(args interface{}) *ExecuteRequest {
9811022
func (req *ExecuteRequest) Body(res SchemaResolver, enc *msgpack.Encoder) error {
9821023
return fillExecute(enc, req.expr, req.args)
9831024
}
1025+
1026+
// StickyRequest is an interface that provides the info about a Connection
1027+
// the request belongs to.
1028+
type StickyRequest interface {
1029+
Conn() *Connection
1030+
}
1031+
1032+
type PrepareRequest struct {
1033+
baseRequest
1034+
expr string
1035+
}
1036+
1037+
// NewPrepareRequest returns a new empty PrepareRequest.
1038+
func NewPrepareRequest(expr string) *PrepareRequest {
1039+
req := new(PrepareRequest)
1040+
req.requestCode = PrepareRequestCode
1041+
req.expr = expr
1042+
return req
1043+
}
1044+
1045+
// Body fills an encoder with the execute request body.
1046+
func (req *PrepareRequest) Body(res SchemaResolver, enc *msgpack.Encoder) error {
1047+
return fillPrepare(enc, req.expr)
1048+
}
1049+
1050+
type UnprepareRequest struct {
1051+
StickyRequest
1052+
baseRequest
1053+
stmt *PreparedStatement
1054+
}
1055+
1056+
// NewUnprepareRequest returns a new empty UnprepareRequest.
1057+
func NewUnprepareRequest(stmt *PreparedStatement) *UnprepareRequest {
1058+
req := new(UnprepareRequest)
1059+
req.requestCode = PrepareRequestCode
1060+
req.stmt = stmt
1061+
return req
1062+
}
1063+
1064+
func (req *UnprepareRequest) Conn() *Connection {
1065+
return req.stmt.Conn
1066+
}
1067+
1068+
// Body fills an encoder with the execute request body.
1069+
func (req *UnprepareRequest) Body(res SchemaResolver, enc *msgpack.Encoder) error {
1070+
return fillUnprepare(enc, *req.stmt)
1071+
}
1072+
1073+
type ExecutePreparedRequest struct {
1074+
StickyRequest
1075+
baseRequest
1076+
stmt *PreparedStatement
1077+
args interface{}
1078+
}
1079+
1080+
// NewPreparedExecuteRequest returns a new empty preparedExecuteRequest.
1081+
func NewExecutePreparedRequest(stmt *PreparedStatement) *ExecutePreparedRequest {
1082+
req := new(ExecutePreparedRequest)
1083+
req.requestCode = ExecuteRequestCode
1084+
req.stmt = stmt
1085+
req.args = []interface{}{}
1086+
return req
1087+
}
1088+
1089+
func (req *ExecutePreparedRequest) Conn() *Connection {
1090+
return req.stmt.Conn
1091+
}
1092+
1093+
func (req *ExecutePreparedRequest) Args(args interface{}) *ExecutePreparedRequest {
1094+
req.args = args
1095+
return req
1096+
}
1097+
1098+
// Body fills an encoder with the execute request body.
1099+
func (req *ExecutePreparedRequest) Body(res SchemaResolver, enc *msgpack.Encoder) error {
1100+
return fillPreparedExecute(enc, *req.stmt, req.args)
1101+
}

‎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

‎tarantool_test.go

+142-3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package tarantool_test
22

33
import (
44
"fmt"
5+
"github.com/stretchr/testify/require"
56
"log"
67
"os"
78
"reflect"
@@ -59,9 +60,9 @@ var spaceName = "test"
5960
var indexNo = uint32(0)
6061
var indexName = "primary"
6162
var opts = Opts{
62-
Timeout: 500 * time.Millisecond,
63-
User: "test",
64-
Pass: "test",
63+
//Timeout: 500 * time.Millisecond,
64+
User: "test",
65+
Pass: "test",
6566
//Concurrency: 32,
6667
//RateLimit: 4*1024,
6768
}
@@ -169,6 +170,50 @@ func BenchmarkClientSerialSQL(b *testing.B) {
169170
}
170171
}
171172

173+
func BenchmarkClientSerialSQLPrepared(b *testing.B) {
174+
// Tarantool supports SQL since version 2.0.0
175+
isLess, err := test_helpers.IsTarantoolVersionLess(2, 0, 0)
176+
if err != nil {
177+
b.Fatal("Could not check the Tarantool version")
178+
}
179+
if isLess {
180+
b.Skip()
181+
}
182+
183+
conn, err := Connect(server, opts)
184+
if err != nil {
185+
b.Errorf("Failed to connect: %s", err)
186+
return
187+
}
188+
defer conn.Close()
189+
190+
spaceNo := 519
191+
_, err = conn.Replace(spaceNo, []interface{}{uint(1111), "hello", "world"})
192+
if err != nil {
193+
b.Errorf("Failed to replace: %s", err)
194+
}
195+
196+
stmt, err := conn.Prepare("SELECT NAME0,NAME1,NAME2 FROM SQL_TEST WHERE NAME0=?")
197+
if err != nil {
198+
b.Fatalf("failed to prepare a SQL statement")
199+
}
200+
executeReq := NewExecutePreparedRequest(stmt)
201+
unprepareReq := NewUnprepareRequest(stmt)
202+
203+
b.ResetTimer()
204+
for i := 0; i < b.N; i++ {
205+
_, err := conn.Do(executeReq.Args([]interface{}{uint(1111)})).Get()
206+
if err != nil {
207+
b.Errorf("Select failed: %s", err.Error())
208+
break
209+
}
210+
}
211+
_, err = conn.Do(unprepareReq).Get()
212+
if err != nil {
213+
b.Fatalf("failed to unprepare a SQL statement")
214+
}
215+
}
216+
172217
func BenchmarkClientFuture(b *testing.B) {
173218
var err error
174219

@@ -462,6 +507,48 @@ func BenchmarkClientParallelSQL(b *testing.B) {
462507
})
463508
}
464509

510+
func BenchmarkClientParallelSQLPrepared(b *testing.B) {
511+
// Tarantool supports SQL since version 2.0.0
512+
isLess, err := test_helpers.IsTarantoolVersionLess(2, 0, 0)
513+
if err != nil {
514+
b.Fatal("Could not check the Tarantool version")
515+
}
516+
if isLess {
517+
b.Skip()
518+
}
519+
520+
conn := test_helpers.ConnectWithValidation(b, server, opts)
521+
defer conn.Close()
522+
523+
spaceNo := 519
524+
_, err = conn.Replace(spaceNo, []interface{}{uint(1111), "hello", "world"})
525+
if err != nil {
526+
b.Errorf("No connection available")
527+
}
528+
529+
stmt, err := conn.Prepare("SELECT NAME0,NAME1,NAME2 FROM SQL_TEST WHERE NAME0=?")
530+
if err != nil {
531+
b.Fatalf("failed to prepare a SQL statement")
532+
}
533+
executeReq := NewExecutePreparedRequest(stmt)
534+
unprepareReq := NewUnprepareRequest(stmt)
535+
536+
b.ResetTimer()
537+
b.RunParallel(func(pb *testing.PB) {
538+
for pb.Next() {
539+
_, err := conn.Do(executeReq.Args([]interface{}{uint(1111)})).Get()
540+
if err != nil {
541+
b.Errorf("Select failed: %s", err.Error())
542+
break
543+
}
544+
}
545+
})
546+
_, err = conn.Do(unprepareReq).Get()
547+
if err != nil {
548+
b.Fatalf("failed to unprepare a SQL statement")
549+
}
550+
}
551+
465552
func BenchmarkSQLSerial(b *testing.B) {
466553
// Tarantool supports SQL since version 2.0.0
467554
isLess, err := test_helpers.IsTarantoolVersionLess(2, 0, 0)
@@ -1341,6 +1428,58 @@ func TestStressSQL(t *testing.T) {
13411428
}
13421429
}
13431430

1431+
func TestPrepare(t *testing.T) {
1432+
var err error
1433+
1434+
conn := test_helpers.ConnectWithValidation(t, server, opts)
1435+
defer conn.Close()
1436+
1437+
stmt, err := conn.Prepare(selectNamedQuery2)
1438+
if err != nil {
1439+
t.Errorf("failed to prepare: %v", err)
1440+
}
1441+
1442+
executeReq := NewExecutePreparedRequest(stmt)
1443+
unprepareReq := NewUnprepareRequest(stmt)
1444+
1445+
resp, err := conn.Do(executeReq.Args([]interface{}{1, "test"})).Get()
1446+
if err != nil {
1447+
t.Errorf("failed to execute prepared: %v", err)
1448+
}
1449+
if resp.Code != OkCode {
1450+
t.Errorf("failed to execute prepared: code %d", resp.Code)
1451+
}
1452+
if reflect.DeepEqual(resp.Data[0], []interface{}{1, "test"}) {
1453+
t.Error("Select with named arguments failed")
1454+
}
1455+
if resp.MetaData[0].FieldType != "unsigned" ||
1456+
resp.MetaData[0].FieldName != "NAME0" ||
1457+
resp.MetaData[1].FieldType != "string" ||
1458+
resp.MetaData[1].FieldName != "NAME1" {
1459+
t.Error("Wrong metadata")
1460+
}
1461+
1462+
resp, err = conn.Do(unprepareReq).Get()
1463+
if err != nil {
1464+
t.Errorf("failed to unprepare prepared statement: %v", err)
1465+
}
1466+
if resp.Code != OkCode {
1467+
t.Errorf("failed to unprepare prepared statement: code %d", resp.Code)
1468+
}
1469+
1470+
_, err = conn.Do(unprepareReq).Get()
1471+
if err == nil {
1472+
t.Errorf("the statement must be already unprepared")
1473+
}
1474+
require.Contains(t, err.Error(), "Prepared statement with id")
1475+
1476+
_, err = conn.Do(executeReq).Get()
1477+
if err == nil {
1478+
t.Errorf("the statement must be already unprepared")
1479+
}
1480+
require.Contains(t, err.Error(), "Prepared statement with id")
1481+
}
1482+
13441483
func TestSchema(t *testing.T) {
13451484
var err error
13461485

0 commit comments

Comments
 (0)
Please sign in to comment.