You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

sqlite3_trace.go 8.3KB

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