Skip to content

Commit b5e671b

Browse files
authored
Added Statement caching (#403)
- added parameter `stmt_cache_size` to DSN string
1 parent 3a51645 commit b5e671b

File tree

9 files changed

+271
-26
lines changed

9 files changed

+271
-26
lines changed

connection.go

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,21 +109,40 @@ func (conn *Conn) PrepareContext(ctx context.Context, query string) (driver.Stmt
109109
go conn.ociBreakDone(ctx, done)
110110
defer func() { close(done) }()
111111

112+
if conn.stmtCacheSize == 0 {
113+
if rv := C.OCIStmtPrepare2(
114+
conn.svc, // service context handle
115+
stmt, // pointer to the statement handle returned
116+
conn.errHandle, // error handle
117+
queryP, // statement text
118+
C.ub4(len(query)), // statement text length
119+
nil, // key to be used for searching the statement in the statement cache
120+
C.ub4(0), // length of the key
121+
C.ub4(C.OCI_NTV_SYNTAX), // syntax - OCI_NTV_SYNTAX: syntax depends upon the version of the server
122+
C.ub4(C.OCI_DEFAULT), // mode
123+
); rv != C.OCI_SUCCESS {
124+
return nil, conn.getError(rv)
125+
}
126+
127+
return &Stmt{conn: conn, stmt: *stmt, ctx: ctx, releaseMode: C.OCI_DEFAULT}, nil
128+
}
129+
112130
if rv := C.OCIStmtPrepare2(
113131
conn.svc, // service context handle
114132
stmt, // pointer to the statement handle returned
115133
conn.errHandle, // error handle
116134
queryP, // statement text
117135
C.ub4(len(query)), // statement text length
118-
nil, // key to be used for searching the statement in the statement cache
119-
C.ub4(0), // length of the key
136+
queryP, // key to be used for searching the statement in the statement cache
137+
C.ub4(len(query)), // length of the key
120138
C.ub4(C.OCI_NTV_SYNTAX), // syntax - OCI_NTV_SYNTAX: syntax depends upon the version of the server
121139
C.ub4(C.OCI_DEFAULT), // mode
122-
); rv != C.OCI_SUCCESS {
140+
); rv != C.OCI_SUCCESS && rv != C.OCI_SUCCESS_WITH_INFO {
141+
// Note that C.OCI_SUCCESS_WITH_INFO is returned the first time a statement it put into the cache
123142
return nil, conn.getError(rv)
124143
}
125144

126-
return &Stmt{conn: conn, stmt: *stmt, ctx: ctx}, nil
145+
return &Stmt{conn: conn, stmt: *stmt, ctx: ctx, releaseMode: C.OCI_DEFAULT, cacheKey: query}, nil
127146
}
128147

129148
// Begin starts a transaction

globals.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ type (
4040
transactionMode C.ub4
4141
enableQMPlaceholders bool
4242
operationMode C.ub4
43+
stmtCacheSize C.ub4
4344
}
4445

4546
// DriverStruct is Oracle driver struct
@@ -66,6 +67,7 @@ type (
6667
prefetchMemory C.ub4
6768
transactionMode C.ub4
6869
operationMode C.ub4
70+
stmtCacheSize C.ub4
6971
inTransaction bool
7072
enableQMPlaceholders bool
7173
closed bool
@@ -80,10 +82,12 @@ type (
8082

8183
// Stmt is Oracle statement
8284
Stmt struct {
83-
conn *Conn
84-
stmt *C.OCIStmt
85-
closed bool
86-
ctx context.Context
85+
conn *Conn
86+
stmt *C.OCIStmt
87+
closed bool
88+
ctx context.Context
89+
cacheKey string // if statement caching is enabled, this is the key for this statement into the cache
90+
releaseMode C.ub4
8791
}
8892

8993
// Rows is Oracle rows

oci8.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ func ParseDSN(dsnString string) (dsn *DSN, err error) {
5151
dsn = &DSN{
5252
prefetchRows: 0,
5353
prefetchMemory: 4096,
54+
stmtCacheSize: 0,
5455
operationMode: C.OCI_DEFAULT,
5556
timeLocation: time.UTC,
5657
}
@@ -119,7 +120,12 @@ func ParseDSN(dsnString string) (dsn *DSN, err error) {
119120
default:
120121
return nil, fmt.Errorf("Invalid as: %v", v[0])
121122
}
122-
123+
case "stmt_cache_size":
124+
z, err := strconv.ParseUint(v[0], 10, 32)
125+
if err != nil {
126+
return nil, fmt.Errorf("invalid stmt_cache_size: %v", v[0])
127+
}
128+
dsn.stmtCacheSize = C.ub4(z)
123129
}
124130
}
125131

@@ -162,6 +168,7 @@ func (drv *DriverStruct) Open(dsnString string) (driver.Conn, error) {
162168

163169
conn := Conn{
164170
operationMode: dsn.operationMode,
171+
stmtCacheSize: dsn.stmtCacheSize,
165172
logger: drv.Logger,
166173
}
167174
if conn.logger == nil {
@@ -353,6 +360,14 @@ func (drv *DriverStruct) Open(dsnString string) (driver.Conn, error) {
353360
return nil, fmt.Errorf("authentication context attribute set error: %v", err)
354361
}
355362

363+
if dsn.stmtCacheSize > 0 {
364+
stmtCacheSize := dsn.stmtCacheSize
365+
err = conn.ociAttrSet(unsafe.Pointer(conn.svc), C.OCI_HTYPE_SVCCTX, unsafe.Pointer(&stmtCacheSize), 0, C.OCI_ATTR_STMTCACHESIZE)
366+
if err != nil {
367+
return nil, fmt.Errorf("stmt cache size attribute set error: %v", err)
368+
}
369+
}
370+
356371
} else {
357372

358373
var svcCtxP *C.OCISvcCtx

oci8_sql_go_113_test.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
// +build go1.13
2+
3+
package oci8
4+
5+
import (
6+
"context"
7+
"testing"
8+
)
9+
10+
// TestStatementCaching tests to ensure statement caching is working
11+
func TestStatementCaching(t *testing.T) {
12+
if TestDisableDatabase {
13+
t.SkipNow()
14+
}
15+
16+
t.Parallel()
17+
18+
var err error
19+
20+
db := testGetDB("?stmt_cache_size=10")
21+
if db == nil {
22+
t.Fatal("db is null")
23+
}
24+
25+
defer func() {
26+
err = db.Close()
27+
if err != nil {
28+
t.Fatal("db close error:", err)
29+
}
30+
}()
31+
32+
ctx, cancel := context.WithTimeout(context.Background(), TestContextTimeout)
33+
conn, err := db.Conn(ctx)
34+
cancel()
35+
// we need to get access to the raw connection so we can access the different fields on the oci8.Stmt
36+
var rawConn *Conn
37+
// NOTE that conn.Raw() is only available with Go >= 1.13
38+
_ = conn.Raw(func(driverConn interface{}) error {
39+
rawConn = driverConn.(*Conn)
40+
return nil
41+
})
42+
43+
ctx, cancel = context.WithTimeout(context.Background(), TestContextTimeout)
44+
stmt, err := rawConn.PrepareContext(ctx, "select ?, ?, ? from dual")
45+
cancel()
46+
if err != nil {
47+
t.Fatal("prepare error:", err)
48+
}
49+
50+
rawStmt := stmt.(*Stmt)
51+
if rawStmt.cacheKey != "select ?, ?, ? from dual" {
52+
err := stmt.Close()
53+
if err != nil {
54+
t.Fatal("stmt close error:", err)
55+
}
56+
t.Fatalf("cacheKey not equal: expected %s, but got %s", "select ?, ?, ? from dual", rawStmt.cacheKey)
57+
}
58+
59+
// closing the statement should put the statement into the cache
60+
err = stmt.Close()
61+
if err != nil {
62+
t.Fatal("stmt close error:", err)
63+
}
64+
65+
ctx, cancel = context.WithTimeout(context.Background(), TestContextTimeout)
66+
stmt, err = rawConn.PrepareContext(ctx, "select ?, ?, ? from dual")
67+
cancel()
68+
if err != nil {
69+
t.Fatal("prepare error:", err)
70+
}
71+
72+
rawStmt = stmt.(*Stmt)
73+
if rawStmt.cacheKey != "select ?, ?, ? from dual" {
74+
err := stmt.Close()
75+
if err != nil {
76+
t.Fatal("stmt close error:", err)
77+
}
78+
t.Fatalf("cacheKey not equal: expected %s, but got %s", "select ?, ?, ? from dual", rawStmt.cacheKey)
79+
}
80+
err = stmt.Close()
81+
if err != nil {
82+
t.Fatal("stmt close error:", err)
83+
}
84+
}

oci8_sql_test.go

Lines changed: 101 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ func testExecQuery(t *testing.T, query string, args []interface{}) {
6666
}
6767

6868
// testGetRows runs a statement and returns the rows as [][]interface{}
69-
func testGetRows(t *testing.T, stmt *sql.Stmt, args []interface{}) ([][]interface{}, error) {
69+
func testGetRows(t testing.TB, stmt *sql.Stmt, args []interface{}) ([][]interface{}, error) {
7070
// get rows
7171
ctx, cancel := context.WithTimeout(context.Background(), TestContextTimeout)
7272
defer cancel()
@@ -468,7 +468,7 @@ end;
468468
_, err = stmt.ExecContext(ctx)
469469
cancel()
470470
expected := "ORA-01013"
471-
if err == nil || len(err.Error()) < len(expected) || err.Error()[:len(expected)] != expected {
471+
if err == nil || len(err.Error()) < len(expected) || !strings.Contains(err.Error(), expected) {
472472
t.Fatalf("stmt exec - expected: %v - received: %v", expected, err)
473473
}
474474

@@ -488,7 +488,7 @@ end;
488488
ctx, cancel = context.WithTimeout(context.Background(), 200*time.Millisecond)
489489
_, err = stmt.QueryContext(ctx)
490490
cancel()
491-
if err == nil || len(err.Error()) < len(expected) || err.Error()[:len(expected)] != expected {
491+
if err == nil || len(err.Error()) < len(expected) || !strings.Contains(err.Error(), expected) {
492492
t.Fatalf("stmt query - expected: %v - received: %v", expected, err)
493493
}
494494

@@ -1475,6 +1475,104 @@ func benchmarkPrefetchSelect(b *testing.B, prefetchRows int64, prefetchMemory in
14751475
}
14761476
}
14771477

1478+
// TestSelectParallelWithStatementCaching checks parallel select from dual but with statement caching enabled
1479+
func TestSelectParallelWithStatementCaching(t *testing.T) {
1480+
if TestDisableDatabase {
1481+
t.SkipNow()
1482+
}
1483+
db := testGetDB("?stmt_cache_size=100")
1484+
if db == nil {
1485+
t.Fatal("db is null")
1486+
}
1487+
1488+
var waitGroup sync.WaitGroup
1489+
waitGroup.Add(50)
1490+
1491+
for i := 0; i < 50; i++ {
1492+
go func(num int) {
1493+
defer waitGroup.Done()
1494+
1495+
selectNumFromDual(t, db, float64(num))
1496+
}(i)
1497+
}
1498+
1499+
waitGroup.Wait()
1500+
}
1501+
1502+
// selectNumFromDual will execute a "select :1 from dual" where the parameter is the num param of this function
1503+
func selectNumFromDual(t testing.TB, db *sql.DB, num float64) {
1504+
ctx, cancel := context.WithTimeout(context.Background(), TestContextTimeout)
1505+
stmt, err := db.PrepareContext(ctx, "select :1 from dual")
1506+
cancel()
1507+
if err != nil {
1508+
t.Fatal("prepare error:", err)
1509+
}
1510+
defer func() {
1511+
if stmt != nil {
1512+
err := stmt.Close()
1513+
if err != nil {
1514+
t.Fatal("stmt close error:", err)
1515+
}
1516+
}
1517+
}()
1518+
1519+
var result [][]interface{}
1520+
result, err = testGetRows(t, stmt, []interface{}{num})
1521+
if err != nil {
1522+
t.Fatal("get rows error:", err)
1523+
}
1524+
if result == nil {
1525+
t.Fatal("result is nil")
1526+
}
1527+
if len(result) != 1 {
1528+
t.Fatal("len result not equal to 1")
1529+
}
1530+
if len(result[0]) != 1 {
1531+
t.Fatal("len result[0] not equal to 1")
1532+
}
1533+
data, ok := result[0][0].(float64)
1534+
if !ok {
1535+
t.Fatal("result not float64")
1536+
}
1537+
if data != num {
1538+
t.Fatal("result not equal to:", num)
1539+
}
1540+
}
1541+
1542+
func BenchmarkSelectNoCaching(b *testing.B) {
1543+
if TestDisableDatabase || TestDisableDestructive {
1544+
b.SkipNow()
1545+
}
1546+
for i := 0; i < b.N; i++ {
1547+
selectNumFromDual(b, TestDB, float64(i))
1548+
}
1549+
}
1550+
1551+
func BenchmarkSelectWithCaching(b *testing.B) {
1552+
b.StopTimer()
1553+
1554+
if TestDisableDatabase || TestDisableDestructive {
1555+
b.SkipNow()
1556+
}
1557+
1558+
db := testGetDB("?stmt_cache_size=100")
1559+
if db == nil {
1560+
b.Fatal("db is null")
1561+
}
1562+
1563+
defer func() {
1564+
err := db.Close()
1565+
if err != nil {
1566+
b.Fatal("db close error:", err)
1567+
}
1568+
}()
1569+
1570+
b.StartTimer()
1571+
for i := 0; i < b.N; i++ {
1572+
selectNumFromDual(b, db, float64(i))
1573+
}
1574+
}
1575+
14781576
func BenchmarkPrefetchR0M32768(b *testing.B) {
14791577
b.StopTimer()
14801578

oci8_test.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -185,16 +185,18 @@ func TestParseDSN(t *testing.T) {
185185

186186
const prefetchRows = 0
187187
const prefetchMemory = 4096
188+
const stmtCacheSize = 0
188189

189190
var dsnTests = []struct {
190191
dsnString string
191192
expectedDSN *DSN
192193
}{
193-
{"oracle://xxmc:xxmc@107.20.30.169:1521/ORCL?loc=America%2FPhoenix", &DSN{Username: "xxmc", Password: "xxmc", Connect: "107.20.30.169:1521/ORCL", prefetchRows: prefetchRows, prefetchMemory: prefetchMemory, timeLocation: timeLocations[5]}},
194-
{"xxmc/xxmc@107.20.30.169:1521/ORCL?loc=America%2FPhoenix", &DSN{Username: "xxmc", Password: "xxmc", Connect: "107.20.30.169:1521/ORCL", prefetchRows: prefetchRows, prefetchMemory: prefetchMemory, timeLocation: timeLocations[5]}},
195-
{"sys/syspwd@107.20.30.169:1521/ORCL?loc=America%2FPhoenix&as=sysdba", &DSN{Username: "sys", Password: "syspwd", Connect: "107.20.30.169:1521/ORCL", prefetchRows: prefetchRows, prefetchMemory: prefetchMemory, timeLocation: timeLocations[5], operationMode: 0x00000002}}, // with operationMode: 0x00000002 = C.OCI_SYDBA
196-
{"xxmc/xxmc@107.20.30.169:1521/ORCL", &DSN{Username: "xxmc", Password: "xxmc", Connect: "107.20.30.169:1521/ORCL", prefetchRows: prefetchRows, prefetchMemory: prefetchMemory, timeLocation: time.UTC}},
197-
{"xxmc/xxmc@107.20.30.169/ORCL", &DSN{Username: "xxmc", Password: "xxmc", Connect: "107.20.30.169/ORCL", prefetchRows: prefetchRows, prefetchMemory: prefetchMemory, timeLocation: time.UTC}},
194+
{"oracle://xxmc:xxmc@107.20.30.169:1521/ORCL?loc=America%2FPhoenix", &DSN{Username: "xxmc", Password: "xxmc", Connect: "107.20.30.169:1521/ORCL", prefetchRows: prefetchRows, prefetchMemory: prefetchMemory, stmtCacheSize: stmtCacheSize, timeLocation: timeLocations[5]}},
195+
{"xxmc/xxmc@107.20.30.169:1521/ORCL?loc=America%2FPhoenix", &DSN{Username: "xxmc", Password: "xxmc", Connect: "107.20.30.169:1521/ORCL", prefetchRows: prefetchRows, prefetchMemory: prefetchMemory, stmtCacheSize: stmtCacheSize, timeLocation: timeLocations[5]}},
196+
{"sys/syspwd@107.20.30.169:1521/ORCL?loc=America%2FPhoenix&as=sysdba", &DSN{Username: "sys", Password: "syspwd", Connect: "107.20.30.169:1521/ORCL", prefetchRows: prefetchRows, prefetchMemory: prefetchMemory, stmtCacheSize: stmtCacheSize, timeLocation: timeLocations[5], operationMode: 0x00000002}}, // with operationMode: 0x00000002 = C.OCI_SYDBA
197+
{"xxmc/xxmc@107.20.30.169:1521/ORCL", &DSN{Username: "xxmc", Password: "xxmc", Connect: "107.20.30.169:1521/ORCL", prefetchRows: prefetchRows, prefetchMemory: prefetchMemory, stmtCacheSize: stmtCacheSize, timeLocation: time.UTC}},
198+
{"xxmc/xxmc@107.20.30.169/ORCL", &DSN{Username: "xxmc", Password: "xxmc", Connect: "107.20.30.169/ORCL", prefetchRows: prefetchRows, prefetchMemory: prefetchMemory, stmtCacheSize: stmtCacheSize, timeLocation: time.UTC}},
199+
{"xxmc/xxmc@107.20.30.169/ORCL?stmt_cache_size=50", &DSN{Username: "xxmc", Password: "xxmc", Connect: "107.20.30.169/ORCL", prefetchRows: prefetchRows, prefetchMemory: prefetchMemory, stmtCacheSize: 50, timeLocation: time.UTC}},
198200
}
199201

200202
for _, tt := range dsnTests {

rows.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ func (rows *Rows) Next(dest []driver.Value) error {
202202
// SQLT_RSET - ref cursor
203203
case C.SQLT_RSET:
204204
stmtP := (**C.OCIStmt)(rows.defines[i].pbuf)
205-
subStmt := &Stmt{conn: rows.stmt.conn, stmt: *stmtP, ctx: rows.stmt.ctx}
205+
subStmt := &Stmt{conn: rows.stmt.conn, stmt: *stmtP, ctx: rows.stmt.ctx, releaseMode: C.ub4(C.OCI_DEFAULT)}
206206
if rows.defines[i].subDefines == nil {
207207
var err error
208208
rows.defines[i].subDefines, err = subStmt.makeDefines()

0 commit comments

Comments
 (0)