Skip to content

Commit 2badb4c

Browse files
committed
use slice len/cap for stmt cache instead of separate counters
1 parent 7716c20 commit 2badb4c

2 files changed

Lines changed: 49 additions & 53 deletions

File tree

sqlite3.go

Lines changed: 29 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -451,15 +451,14 @@ type SQLiteConn struct {
451451
txlock string
452452
funcs []*functionInfo
453453
aggregators []*aggInfo
454-
// Prepared-statement cache. stmtCacheBuf is a preallocated slice of
455-
// length stmtCacheSize holding up to stmtCacheCount live entries at
456-
// indices [0, stmtCacheCount). Ordering is LRU-first: index 0 is the
457-
// oldest (next to be evicted), index stmtCacheCount-1 is the most
458-
// recently put. put at the tail is O(1) when not full; eviction shifts
459-
// the remaining entries left by one.
460-
stmtCacheBuf []*SQLiteStmt
461-
stmtCacheSize int
462-
stmtCacheCount int
454+
// Prepared-statement cache. The slice is allocated at Open with a
455+
// fixed capacity equal to the configured cache size; cap bounds the
456+
// cache, len is the live count, and entries are ordered LRU-first
457+
// (index 0 is the oldest, the tail is most recently put). Access
458+
// requires mu; stmtCacheEnabled is immutable after Open and is the
459+
// only field safe to read without the lock.
460+
stmtCache []*SQLiteStmt
461+
stmtCacheEnabled bool
463462
}
464463

465464
// SQLiteTx implements driver.Tx.
@@ -1617,9 +1616,10 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) {
16171616
//
16181617

16191618
// Create connection to SQLite
1620-
conn := &SQLiteConn{db: db, loc: loc, txlock: txlock, stmtCacheSize: stmtCacheSize}
1619+
conn := &SQLiteConn{db: db, loc: loc, txlock: txlock}
16211620
if stmtCacheSize > 0 {
1622-
conn.stmtCacheBuf = make([]*SQLiteStmt, stmtCacheSize)
1621+
conn.stmtCache = make([]*SQLiteStmt, 0, stmtCacheSize)
1622+
conn.stmtCacheEnabled = true
16231623
}
16241624

16251625
// Password Cipher has to be registered before authentication
@@ -1913,7 +1913,7 @@ func (c *SQLiteConn) dbConnOpen() bool {
19131913
}
19141914

19151915
func (c *SQLiteConn) takeCachedStmt(query string) *SQLiteStmt {
1916-
if c == nil || query == "" || c.stmtCacheSize <= 0 {
1916+
if c == nil || query == "" || !c.stmtCacheEnabled {
19171917
return nil
19181918
}
19191919

@@ -1925,17 +1925,15 @@ func (c *SQLiteConn) takeCachedStmt(query string) *SQLiteStmt {
19251925
}
19261926
// Scan from the MRU end (tail) so that a stmt put just before is
19271927
// found immediately.
1928-
for i := c.stmtCacheCount - 1; i >= 0; i-- {
1929-
s := c.stmtCacheBuf[i]
1928+
for i := len(c.stmtCache) - 1; i >= 0; i-- {
1929+
s := c.stmtCache[i]
19301930
if s.cacheKey != query {
19311931
continue
19321932
}
1933-
// Remove s from the buffer by shifting subsequent entries left.
1934-
if i != c.stmtCacheCount-1 {
1935-
copy(c.stmtCacheBuf[i:c.stmtCacheCount-1], c.stmtCacheBuf[i+1:c.stmtCacheCount])
1936-
}
1937-
c.stmtCacheCount--
1938-
c.stmtCacheBuf[c.stmtCacheCount] = nil
1933+
n := len(c.stmtCache)
1934+
copy(c.stmtCache[i:n-1], c.stmtCache[i+1:n])
1935+
c.stmtCache[n-1] = nil
1936+
c.stmtCache = c.stmtCache[:n-1]
19391937
s.closed = false
19401938
s.cls = false
19411939
s.t = ""
@@ -1945,7 +1943,7 @@ func (c *SQLiteConn) takeCachedStmt(query string) *SQLiteStmt {
19451943
}
19461944

19471945
func (c *SQLiteConn) putCachedStmt(s *SQLiteStmt) bool {
1948-
if c == nil || s == nil || s.s == nil || s.cacheKey == "" || c.stmtCacheSize <= 0 {
1946+
if c == nil || s == nil || s.s == nil || s.cacheKey == "" || !c.stmtCacheEnabled {
19491947
return false
19501948
}
19511949

@@ -1959,30 +1957,27 @@ func (c *SQLiteConn) putCachedStmt(s *SQLiteStmt) bool {
19591957
if rv != C.SQLITE_ROW && rv != C.SQLITE_OK && rv != C.SQLITE_DONE {
19601958
return false
19611959
}
1962-
// If full, finalize the least-recently-used entry at index 0 and
1963-
// compact the remaining entries left by one.
1964-
if c.stmtCacheCount == c.stmtCacheSize {
1965-
victim := c.stmtCacheBuf[0]
1960+
// If full, finalize the LRU entry at index 0 and shift left; the
1961+
// freed tail slot is immediately reused by the append below.
1962+
if len(c.stmtCache) == cap(c.stmtCache) {
1963+
victim := c.stmtCache[0]
19661964
runtime.SetFinalizer(victim, nil)
19671965
if victim.s != nil {
19681966
C.sqlite3_finalize(victim.s)
19691967
victim.s = nil
19701968
}
19711969
victim.c = nil
19721970
victim.closed = true
1973-
copy(c.stmtCacheBuf[0:c.stmtCacheCount-1], c.stmtCacheBuf[1:c.stmtCacheCount])
1974-
c.stmtCacheCount--
1971+
copy(c.stmtCache, c.stmtCache[1:])
1972+
c.stmtCache = c.stmtCache[:len(c.stmtCache)-1]
19751973
}
1976-
// Append at the MRU tail.
1977-
c.stmtCacheBuf[c.stmtCacheCount] = s
1978-
c.stmtCacheCount++
1974+
c.stmtCache = append(c.stmtCache, s)
19791975
return true
19801976
}
19811977

19821978
func (c *SQLiteConn) closeCachedStmtsLocked() {
1983-
for i := 0; i < c.stmtCacheCount; i++ {
1984-
s := c.stmtCacheBuf[i]
1985-
c.stmtCacheBuf[i] = nil
1979+
for i, s := range c.stmtCache {
1980+
c.stmtCache[i] = nil
19861981
if s == nil || s.s == nil {
19871982
continue
19881983
}
@@ -1991,7 +1986,7 @@ func (c *SQLiteConn) closeCachedStmtsLocked() {
19911986
s.s = nil
19921987
s.c = nil
19931988
}
1994-
c.stmtCacheCount = 0
1989+
c.stmtCache = c.stmtCache[:0]
19951990
}
19961991

19971992
// Prepare the query string. Return a new statement.

sqlite3_stmt_cache_test.go

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -47,17 +47,17 @@ func TestStmtCacheLRUEviction(t *testing.T) {
4747
// Fill the cache with q1 and q2.
4848
prepareAndClose(q1)
4949
prepareAndClose(q2)
50-
if got, want := c.stmtCacheCount, 2; got != want {
51-
t.Fatalf("after filling: stmtCacheCount = %d, want %d", got, want)
50+
if got, want := len(c.stmtCache), 2; got != want {
51+
t.Fatalf("after filling: len(stmtCache) = %d, want %d", got, want)
5252
}
5353
if cacheCount(c, q1) != 1 || cacheCount(c, q2) != 1 {
5454
t.Fatalf("after filling: expected q1 and q2 cached, got %#v", cacheKeys(c))
5555
}
5656

5757
// Insert q3. q1 is the oldest entry and should be evicted.
5858
prepareAndClose(q3)
59-
if got, want := c.stmtCacheCount, 2; got != want {
60-
t.Fatalf("after q3: stmtCacheCount = %d, want %d", got, want)
59+
if got, want := len(c.stmtCache), 2; got != want {
60+
t.Fatalf("after q3: len(stmtCache) = %d, want %d", got, want)
6161
}
6262
if cacheCount(c, q1) != 0 {
6363
t.Fatalf("after q3: q1 should have been evicted, cache=%#v", cacheKeys(c))
@@ -66,14 +66,14 @@ func TestStmtCacheLRUEviction(t *testing.T) {
6666
t.Fatalf("after q3: expected q2 and q3 cached, got %#v", cacheKeys(c))
6767
}
6868

69-
// Touching q2 should make q3 the oldest (the entry at buf[0]).
69+
// Touching q2 should make q3 the oldest (the entry at index 0).
7070
prepareAndClose(q2)
71-
if c.stmtCacheCount == 0 || c.stmtCacheBuf[0].cacheKey != q3 {
71+
if len(c.stmtCache) == 0 || c.stmtCache[0].cacheKey != q3 {
7272
var head string
73-
if c.stmtCacheCount > 0 {
74-
head = c.stmtCacheBuf[0].cacheKey
73+
if len(c.stmtCache) > 0 {
74+
head = c.stmtCache[0].cacheKey
7575
}
76-
t.Fatalf("after touching q2: expected q3 at buf[0] (LRU), got %q", head)
76+
t.Fatalf("after touching q2: expected q3 at stmtCache[0] (LRU), got %q", head)
7777
}
7878

7979
// Insert q1 again. Now q3 should be evicted (q2 is newer).
@@ -84,14 +84,15 @@ func TestStmtCacheLRUEviction(t *testing.T) {
8484
if cacheCount(c, q1) != 1 || cacheCount(c, q2) != 1 {
8585
t.Fatalf("after reinserting q1: expected q1 and q2 cached, got %#v", cacheKeys(c))
8686
}
87-
if got, want := c.stmtCacheCount, 2; got != want {
88-
t.Fatalf("after reinserting q1: stmtCacheCount = %d, want %d", got, want)
87+
if got, want := len(c.stmtCache), 2; got != want {
88+
t.Fatalf("after reinserting q1: len(stmtCache) = %d, want %d", got, want)
8989
}
9090

91-
// Sanity-check: no dangling entries past stmtCacheCount.
92-
for i := c.stmtCacheCount; i < len(c.stmtCacheBuf); i++ {
93-
if c.stmtCacheBuf[i] != nil {
94-
t.Fatalf("stmtCacheBuf[%d] = %p, expected nil tail slot", i, c.stmtCacheBuf[i])
91+
// Sanity-check: no dangling entries past len(stmtCache).
92+
tail := c.stmtCache[:cap(c.stmtCache)]
93+
for i := len(c.stmtCache); i < len(tail); i++ {
94+
if tail[i] != nil {
95+
t.Fatalf("stmtCache tail slot %d = %p, expected nil", i, tail[i])
9596
}
9697
}
9798
}
@@ -135,16 +136,16 @@ func TestStmtCacheReuseReturnsSameHandle(t *testing.T) {
135136

136137
func cacheKeys(c *SQLiteConn) map[string]int {
137138
out := make(map[string]int)
138-
for i := 0; i < c.stmtCacheCount; i++ {
139-
out[c.stmtCacheBuf[i].cacheKey]++
139+
for _, s := range c.stmtCache {
140+
out[s.cacheKey]++
140141
}
141142
return out
142143
}
143144

144145
func cacheCount(c *SQLiteConn, q string) int {
145146
n := 0
146-
for i := 0; i < c.stmtCacheCount; i++ {
147-
if c.stmtCacheBuf[i].cacheKey == q {
147+
for _, s := range c.stmtCache {
148+
if s.cacheKey == q {
148149
n++
149150
}
150151
}

0 commit comments

Comments
 (0)