cache column metadata for prepared and cached statements
Column names and declared types are invariant for the lifetime of a prepared statement, but Columns()/declTypes() called C.sqlite3_column_name and sqlite3_column_decltype on every query, once per column. On hot QueryRow paths reusing an explicit Prepare or a _stmt_cache_size statement, this is a fixed set of cgo crossings paid on every execution. Cache the names and decltypes on the SQLiteStmt the first time they are materialized and reuse them on subsequent executions. Caching is gated by cacheMetadata(): explicit prepared statements always cache, Query-created ephemeral statements cache only when they live in the connection stmt cache. One-shot statements keep the previous per-call behavior.
This commit is contained in:
44
sqlite3.go
44
sqlite3.go
@@ -476,6 +476,12 @@ type SQLiteStmt struct {
|
||||
cls bool // True if the statement was created by SQLiteConn.Query
|
||||
namedParams map[string][3]int
|
||||
cacheKey string
|
||||
metadata *sqliteStmtMetadata
|
||||
}
|
||||
|
||||
type sqliteStmtMetadata struct {
|
||||
cols []string
|
||||
decltype []string
|
||||
}
|
||||
|
||||
// SQLiteResult implements sql.Result.
|
||||
@@ -2475,6 +2481,36 @@ func (rc *SQLiteRows) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SQLiteStmt) cacheMetadata() bool {
|
||||
return !s.cls || s.cacheKey != ""
|
||||
}
|
||||
|
||||
func (s *SQLiteStmt) columnNamesLocked(n int) []string {
|
||||
if s.metadata == nil {
|
||||
s.metadata = &sqliteStmtMetadata{}
|
||||
}
|
||||
if len(s.metadata.cols) != n {
|
||||
s.metadata.cols = make([]string, n)
|
||||
for i := range s.metadata.cols {
|
||||
s.metadata.cols[i] = C.GoString(C.sqlite3_column_name(s.s, C.int(i)))
|
||||
}
|
||||
}
|
||||
return s.metadata.cols
|
||||
}
|
||||
|
||||
func (s *SQLiteStmt) declTypesLocked(n int) []string {
|
||||
if s.metadata == nil {
|
||||
s.metadata = &sqliteStmtMetadata{}
|
||||
}
|
||||
if len(s.metadata.decltype) != n {
|
||||
s.metadata.decltype = make([]string, n)
|
||||
for i := range s.metadata.decltype {
|
||||
s.metadata.decltype[i] = strings.ToLower(C.GoString(C.sqlite3_column_decltype(s.s, C.int(i))))
|
||||
}
|
||||
}
|
||||
return s.metadata.decltype
|
||||
}
|
||||
|
||||
// Columns return column names.
|
||||
func (rc *SQLiteRows) Columns() []string {
|
||||
if rc.s == nil {
|
||||
@@ -2483,21 +2519,29 @@ func (rc *SQLiteRows) Columns() []string {
|
||||
rc.s.mu.Lock()
|
||||
defer rc.s.mu.Unlock()
|
||||
if rc.s.s != nil && int(rc.nc) != len(rc.cols) {
|
||||
if rc.s.cacheMetadata() {
|
||||
rc.cols = rc.s.columnNamesLocked(int(rc.nc))
|
||||
} else {
|
||||
rc.cols = make([]string, rc.nc)
|
||||
for i := range rc.cols {
|
||||
rc.cols[i] = C.GoString(C.sqlite3_column_name(rc.s.s, C.int(i)))
|
||||
}
|
||||
}
|
||||
}
|
||||
return rc.cols
|
||||
}
|
||||
|
||||
func (rc *SQLiteRows) declTypes() []string {
|
||||
if rc.s.s != nil && rc.decltype == nil {
|
||||
if rc.s.cacheMetadata() {
|
||||
rc.decltype = rc.s.declTypesLocked(int(rc.nc))
|
||||
} else {
|
||||
rc.decltype = make([]string, rc.nc)
|
||||
for i := range rc.decltype {
|
||||
rc.decltype[i] = strings.ToLower(C.GoString(C.sqlite3_column_decltype(rc.s.s, C.int(i))))
|
||||
}
|
||||
}
|
||||
}
|
||||
return rc.decltype
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user