podman
288 строк · 8.2 Кб
1// Copyright (C) 2019 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
2//
3// Use of this source code is governed by an MIT-style
4// license that can be found in the LICENSE file.
5
6//go:build sqlite_trace || trace
7// +build sqlite_trace trace
8
9package sqlite3
10
11/*
12#ifndef USE_LIBSQLITE3
13#include "sqlite3-binding.h"
14#else
15#include <sqlite3.h>
16#endif
17#include <stdlib.h>
18
19int traceCallbackTrampoline(unsigned int traceEventCode, void *ctx, void *p, void *x);
20*/
21import "C"
22
23import (
24"fmt"
25"strings"
26"sync"
27"unsafe"
28)
29
30// Trace... constants identify the possible events causing callback invocation.
31// Values are same as the corresponding SQLite Trace Event Codes.
32const (
33TraceStmt = uint32(C.SQLITE_TRACE_STMT)
34TraceProfile = uint32(C.SQLITE_TRACE_PROFILE)
35TraceRow = uint32(C.SQLITE_TRACE_ROW)
36TraceClose = uint32(C.SQLITE_TRACE_CLOSE)
37)
38
39type TraceInfo struct {
40// Pack together the shorter fields, to keep the struct smaller.
41// On a 64-bit machine there would be padding
42// between EventCode and ConnHandle; having AutoCommit here is "free":
43EventCode uint32
44AutoCommit bool
45ConnHandle uintptr
46
47// Usually filled, unless EventCode = TraceClose = SQLITE_TRACE_CLOSE:
48// identifier for a prepared statement:
49StmtHandle uintptr
50
51// Two strings filled when EventCode = TraceStmt = SQLITE_TRACE_STMT:
52// (1) either the unexpanded SQL text of the prepared statement, or
53// an SQL comment that indicates the invocation of a trigger;
54// (2) expanded SQL, if requested and if (1) is not an SQL comment.
55StmtOrTrigger string
56ExpandedSQL string // only if requested (TraceConfig.WantExpandedSQL = true)
57
58// filled when EventCode = TraceProfile = SQLITE_TRACE_PROFILE:
59// estimated number of nanoseconds that the prepared statement took to run:
60RunTimeNanosec int64
61
62DBError Error
63}
64
65// TraceUserCallback gives the signature for a trace function
66// provided by the user (Go application programmer).
67// SQLite 3.14 documentation (as of September 2, 2016)
68// for SQL Trace Hook = sqlite3_trace_v2():
69// The integer return value from the callback is currently ignored,
70// though this may change in future releases. Callback implementations
71// should return zero to ensure future compatibility.
72type TraceUserCallback func(TraceInfo) int
73
74type TraceConfig struct {
75Callback TraceUserCallback
76EventMask uint32
77WantExpandedSQL bool
78}
79
80func fillDBError(dbErr *Error, db *C.sqlite3) {
81// See SQLiteConn.lastError(), in file 'sqlite3.go' at the time of writing (Sept 5, 2016)
82dbErr.Code = ErrNo(C.sqlite3_errcode(db))
83dbErr.ExtendedCode = ErrNoExtended(C.sqlite3_extended_errcode(db))
84dbErr.err = C.GoString(C.sqlite3_errmsg(db))
85}
86
87func fillExpandedSQL(info *TraceInfo, db *C.sqlite3, pStmt unsafe.Pointer) {
88if pStmt == nil {
89panic("No SQLite statement pointer in P arg of trace_v2 callback")
90}
91
92expSQLiteCStr := C.sqlite3_expanded_sql((*C.sqlite3_stmt)(pStmt))
93defer C.sqlite3_free(unsafe.Pointer(expSQLiteCStr))
94if expSQLiteCStr == nil {
95fillDBError(&info.DBError, db)
96return
97}
98info.ExpandedSQL = C.GoString(expSQLiteCStr)
99}
100
101//export traceCallbackTrampoline
102func traceCallbackTrampoline(
103traceEventCode C.uint,
104// Parameter named 'C' in SQLite docs = Context given at registration:
105ctx unsafe.Pointer,
106// Parameter named 'P' in SQLite docs (Primary event data?):
107p unsafe.Pointer,
108// Parameter named 'X' in SQLite docs (eXtra event data?):
109xValue unsafe.Pointer) C.int {
110
111eventCode := uint32(traceEventCode)
112
113if ctx == nil {
114panic(fmt.Sprintf("No context (ev 0x%x)", traceEventCode))
115}
116
117contextDB := (*C.sqlite3)(ctx)
118connHandle := uintptr(ctx)
119
120var traceConf TraceConfig
121var found bool
122if eventCode == TraceClose {
123// clean up traceMap: 'pop' means get and delete
124traceConf, found = popTraceMapping(connHandle)
125} else {
126traceConf, found = lookupTraceMapping(connHandle)
127}
128
129if !found {
130panic(fmt.Sprintf("Mapping not found for handle 0x%x (ev 0x%x)",
131connHandle, eventCode))
132}
133
134var info TraceInfo
135
136info.EventCode = eventCode
137info.AutoCommit = (int(C.sqlite3_get_autocommit(contextDB)) != 0)
138info.ConnHandle = connHandle
139
140switch eventCode {
141case TraceStmt:
142info.StmtHandle = uintptr(p)
143
144var xStr string
145if xValue != nil {
146xStr = C.GoString((*C.char)(xValue))
147}
148info.StmtOrTrigger = xStr
149if !strings.HasPrefix(xStr, "--") {
150// Not SQL comment, therefore the current event
151// is not related to a trigger.
152// The user might want to receive the expanded SQL;
153// let's check:
154if traceConf.WantExpandedSQL {
155fillExpandedSQL(&info, contextDB, p)
156}
157}
158
159case TraceProfile:
160info.StmtHandle = uintptr(p)
161
162if xValue == nil {
163panic("NULL pointer in X arg of trace_v2 callback for SQLITE_TRACE_PROFILE event")
164}
165
166info.RunTimeNanosec = *(*int64)(xValue)
167
168// sample the error //TODO: is it safe? is it useful?
169fillDBError(&info.DBError, contextDB)
170
171case TraceRow:
172info.StmtHandle = uintptr(p)
173
174case TraceClose:
175handle := uintptr(p)
176if handle != info.ConnHandle {
177panic(fmt.Sprintf("Different conn handle 0x%x (expected 0x%x) in SQLITE_TRACE_CLOSE event.",
178handle, info.ConnHandle))
179}
180
181default:
182// Pass unsupported events to the user callback (if configured);
183// let the user callback decide whether to panic or ignore them.
184}
185
186// Do not execute user callback when the event was not requested by user!
187// Remember that the Close event is always selected when
188// registering this callback trampoline with SQLite --- for cleanup.
189// In the future there may be more events forced to "selected" in SQLite
190// for the driver's needs.
191if traceConf.EventMask&eventCode == 0 {
192return 0
193}
194
195r := 0
196if traceConf.Callback != nil {
197r = traceConf.Callback(info)
198}
199return C.int(r)
200}
201
202type traceMapEntry struct {
203config TraceConfig
204}
205
206var traceMapLock sync.Mutex
207var traceMap = make(map[uintptr]traceMapEntry)
208
209func addTraceMapping(connHandle uintptr, traceConf TraceConfig) {
210traceMapLock.Lock()
211defer traceMapLock.Unlock()
212
213oldEntryCopy, found := traceMap[connHandle]
214if found {
215panic(fmt.Sprintf("Adding trace config %v: handle 0x%x already registered (%v).",
216traceConf, connHandle, oldEntryCopy.config))
217}
218traceMap[connHandle] = traceMapEntry{config: traceConf}
219}
220
221func lookupTraceMapping(connHandle uintptr) (TraceConfig, bool) {
222traceMapLock.Lock()
223defer traceMapLock.Unlock()
224
225entryCopy, found := traceMap[connHandle]
226return entryCopy.config, found
227}
228
229// 'pop' = get and delete from map before returning the value to the caller
230func popTraceMapping(connHandle uintptr) (TraceConfig, bool) {
231traceMapLock.Lock()
232defer traceMapLock.Unlock()
233
234entryCopy, found := traceMap[connHandle]
235if found {
236delete(traceMap, connHandle)
237}
238return entryCopy.config, found
239}
240
241// SetTrace installs or removes the trace callback for the given database connection.
242// It's not named 'RegisterTrace' because only one callback can be kept and called.
243// Calling SetTrace a second time on same database connection
244// overrides (cancels) any prior callback and all its settings:
245// event mask, etc.
246func (c *SQLiteConn) SetTrace(requested *TraceConfig) error {
247connHandle := uintptr(unsafe.Pointer(c.db))
248
249_, _ = popTraceMapping(connHandle)
250
251if requested == nil {
252// The traceMap entry was deleted already by popTraceMapping():
253// can disable all events now, no need to watch for TraceClose.
254err := c.setSQLiteTrace(0)
255return err
256}
257
258reqCopy := *requested
259
260// Disable potentially expensive operations
261// if their result will not be used. We are doing this
262// just in case the caller provided nonsensical input.
263if reqCopy.EventMask&TraceStmt == 0 {
264reqCopy.WantExpandedSQL = false
265}
266
267addTraceMapping(connHandle, reqCopy)
268
269// The callback trampoline function does cleanup on Close event,
270// regardless of the presence or absence of the user callback.
271// Therefore it needs the Close event to be selected:
272actualEventMask := uint(reqCopy.EventMask | TraceClose)
273err := c.setSQLiteTrace(actualEventMask)
274return err
275}
276
277func (c *SQLiteConn) setSQLiteTrace(sqliteEventMask uint) error {
278rv := C.sqlite3_trace_v2(c.db,
279C.uint(sqliteEventMask),
280(*[0]byte)(unsafe.Pointer(C.traceCallbackTrampoline)),
281unsafe.Pointer(c.db)) // Fourth arg is same as first: we are
282// passing the database connection handle as callback context.
283
284if rv != C.SQLITE_OK {
285return c.lastError()
286}
287return nil
288}
289