sqlite3_trace.go 8.2 KB

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