1 // Copyright (C) 2016 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
3 // Use of this source code is governed by an MIT-style
4 // license that can be found in the LICENSE file.
6 // +build sqlite_trace trace
11 #ifndef USE_LIBSQLITE3
12 #include <sqlite3-binding.h>
18 int traceCallbackTrampoline(unsigned int traceEventCode, void *ctx, void *p, void *x);
29 // Trace... constants identify the possible events causing callback invocation.
30 // Values are same as the corresponding SQLite Trace Event Codes.
32 TraceStmt = uint32(C.SQLITE_TRACE_STMT)
33 TraceProfile = uint32(C.SQLITE_TRACE_PROFILE)
34 TraceRow = uint32(C.SQLITE_TRACE_ROW)
35 TraceClose = uint32(C.SQLITE_TRACE_CLOSE)
38 type TraceInfo struct {
39 // Pack together the shorter fields, to keep the struct smaller.
40 // On a 64-bit machine there would be padding
41 // between EventCode and ConnHandle; having AutoCommit here is "free":
46 // Usually filled, unless EventCode = TraceClose = SQLITE_TRACE_CLOSE:
47 // identifier for a prepared statement:
50 // Two strings filled when EventCode = TraceStmt = SQLITE_TRACE_STMT:
51 // (1) either the unexpanded SQL text of the prepared statement, or
52 // an SQL comment that indicates the invocation of a trigger;
53 // (2) expanded SQL, if requested and if (1) is not an SQL comment.
55 ExpandedSQL string // only if requested (TraceConfig.WantExpandedSQL = true)
57 // filled when EventCode = TraceProfile = SQLITE_TRACE_PROFILE:
58 // estimated number of nanoseconds that the prepared statement took to run:
64 // TraceUserCallback gives the signature for a trace function
65 // provided by the user (Go application programmer).
66 // SQLite 3.14 documentation (as of September 2, 2016)
67 // for SQL Trace Hook = sqlite3_trace_v2():
68 // The integer return value from the callback is currently ignored,
69 // though this may change in future releases. Callback implementations
70 // should return zero to ensure future compatibility.
71 type TraceUserCallback func(TraceInfo) int
73 type TraceConfig struct {
74 Callback TraceUserCallback
79 func fillDBError(dbErr *Error, db *C.sqlite3) {
80 // See SQLiteConn.lastError(), in file 'sqlite3.go' at the time of writing (Sept 5, 2016)
81 dbErr.Code = ErrNo(C.sqlite3_errcode(db))
82 dbErr.ExtendedCode = ErrNoExtended(C.sqlite3_extended_errcode(db))
83 dbErr.err = C.GoString(C.sqlite3_errmsg(db))
86 func fillExpandedSQL(info *TraceInfo, db *C.sqlite3, pStmt unsafe.Pointer) {
88 panic("No SQLite statement pointer in P arg of trace_v2 callback")
91 expSQLiteCStr := C.sqlite3_expanded_sql((*C.sqlite3_stmt)(pStmt))
92 if expSQLiteCStr == nil {
93 fillDBError(&info.DBError, db)
96 info.ExpandedSQL = C.GoString(expSQLiteCStr)
99 //export traceCallbackTrampoline
100 func traceCallbackTrampoline(
101 traceEventCode C.uint,
102 // Parameter named 'C' in SQLite docs = Context given at registration:
104 // Parameter named 'P' in SQLite docs (Primary event data?):
106 // Parameter named 'X' in SQLite docs (eXtra event data?):
107 xValue unsafe.Pointer) C.int {
109 eventCode := uint32(traceEventCode)
112 panic(fmt.Sprintf("No context (ev 0x%x)", traceEventCode))
115 contextDB := (*C.sqlite3)(ctx)
116 connHandle := uintptr(ctx)
118 var traceConf TraceConfig
120 if eventCode == TraceClose {
121 // clean up traceMap: 'pop' means get and delete
122 traceConf, found = popTraceMapping(connHandle)
124 traceConf, found = lookupTraceMapping(connHandle)
128 panic(fmt.Sprintf("Mapping not found for handle 0x%x (ev 0x%x)",
129 connHandle, eventCode))
134 info.EventCode = eventCode
135 info.AutoCommit = (int(C.sqlite3_get_autocommit(contextDB)) != 0)
136 info.ConnHandle = connHandle
140 info.StmtHandle = uintptr(p)
144 xStr = C.GoString((*C.char)(xValue))
146 info.StmtOrTrigger = xStr
147 if !strings.HasPrefix(xStr, "--") {
148 // Not SQL comment, therefore the current event
149 // is not related to a trigger.
150 // The user might want to receive the expanded SQL;
152 if traceConf.WantExpandedSQL {
153 fillExpandedSQL(&info, contextDB, p)
158 info.StmtHandle = uintptr(p)
161 panic("NULL pointer in X arg of trace_v2 callback for SQLITE_TRACE_PROFILE event")
164 info.RunTimeNanosec = *(*int64)(xValue)
166 // sample the error //TODO: is it safe? is it useful?
167 fillDBError(&info.DBError, contextDB)
170 info.StmtHandle = uintptr(p)
174 if handle != info.ConnHandle {
175 panic(fmt.Sprintf("Different conn handle 0x%x (expected 0x%x) in SQLITE_TRACE_CLOSE event.",
176 handle, info.ConnHandle))
180 // Pass unsupported events to the user callback (if configured);
181 // let the user callback decide whether to panic or ignore them.
184 // Do not execute user callback when the event was not requested by user!
185 // Remember that the Close event is always selected when
186 // registering this callback trampoline with SQLite --- for cleanup.
187 // In the future there may be more events forced to "selected" in SQLite
188 // for the driver's needs.
189 if traceConf.EventMask&eventCode == 0 {
194 if traceConf.Callback != nil {
195 r = traceConf.Callback(info)
200 type traceMapEntry struct {
204 var traceMapLock sync.Mutex
205 var traceMap = make(map[uintptr]traceMapEntry)
207 func addTraceMapping(connHandle uintptr, traceConf TraceConfig) {
209 defer traceMapLock.Unlock()
211 oldEntryCopy, found := traceMap[connHandle]
213 panic(fmt.Sprintf("Adding trace config %v: handle 0x%x already registered (%v).",
214 traceConf, connHandle, oldEntryCopy.config))
216 traceMap[connHandle] = traceMapEntry{config: traceConf}
217 fmt.Printf("Added trace config %v: handle 0x%x.\n", traceConf, connHandle)
220 func lookupTraceMapping(connHandle uintptr) (TraceConfig, bool) {
222 defer traceMapLock.Unlock()
224 entryCopy, found := traceMap[connHandle]
225 return entryCopy.config, found
228 // 'pop' = get and delete from map before returning the value to the caller
229 func popTraceMapping(connHandle uintptr) (TraceConfig, bool) {
231 defer traceMapLock.Unlock()
233 entryCopy, found := traceMap[connHandle]
235 delete(traceMap, connHandle)
236 fmt.Printf("Pop handle 0x%x: deleted trace config %v.\n", connHandle, entryCopy.config)
238 return entryCopy.config, found
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:
246 func (c *SQLiteConn) SetTrace(requested *TraceConfig) error {
247 connHandle := uintptr(unsafe.Pointer(c.db))
249 _, _ = popTraceMapping(connHandle)
251 if requested == nil {
252 // The traceMap entry was deleted already by popTraceMapping():
253 // can disable all events now, no need to watch for TraceClose.
254 err := c.setSQLiteTrace(0)
258 reqCopy := *requested
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.
263 if reqCopy.EventMask&TraceStmt == 0 {
264 reqCopy.WantExpandedSQL = false
267 addTraceMapping(connHandle, reqCopy)
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:
272 actualEventMask := uint(reqCopy.EventMask | TraceClose)
273 err := c.setSQLiteTrace(actualEventMask)
277 func (c *SQLiteConn) setSQLiteTrace(sqliteEventMask uint) error {
278 rv := C.sqlite3_trace_v2(c.db,
279 C.uint(sqliteEventMask),
280 (*[0]byte)(unsafe.Pointer(C.traceCallbackTrampoline)),
281 unsafe.Pointer(c.db)) // Fourth arg is same as first: we are
282 // passing the database connection handle as callback context.
284 if rv != C.SQLITE_OK {