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.

288 lines
8.3 KiB

  1. // Copyright (C) 2016 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. if expSQLiteCStr == nil {
  77. fillDBError(&info.DBError, db)
  78. return
  79. }
  80. info.ExpandedSQL = C.GoString(expSQLiteCStr)
  81. }
  82. //export traceCallbackTrampoline
  83. func traceCallbackTrampoline(
  84. traceEventCode C.uint,
  85. // Parameter named 'C' in SQLite docs = Context given at registration:
  86. ctx unsafe.Pointer,
  87. // Parameter named 'P' in SQLite docs (Primary event data?):
  88. p unsafe.Pointer,
  89. // Parameter named 'X' in SQLite docs (eXtra event data?):
  90. xValue unsafe.Pointer) C.int {
  91. eventCode := uint32(traceEventCode)
  92. if ctx == nil {
  93. panic(fmt.Sprintf("No context (ev 0x%x)", traceEventCode))
  94. }
  95. contextDB := (*C.sqlite3)(ctx)
  96. connHandle := uintptr(ctx)
  97. var traceConf TraceConfig
  98. var found bool
  99. if eventCode == TraceClose {
  100. // clean up traceMap: 'pop' means get and delete
  101. traceConf, found = popTraceMapping(connHandle)
  102. } else {
  103. traceConf, found = lookupTraceMapping(connHandle)
  104. }
  105. if !found {
  106. panic(fmt.Sprintf("Mapping not found for handle 0x%x (ev 0x%x)",
  107. connHandle, eventCode))
  108. }
  109. var info TraceInfo
  110. info.EventCode = eventCode
  111. info.AutoCommit = (int(C.sqlite3_get_autocommit(contextDB)) != 0)
  112. info.ConnHandle = connHandle
  113. switch eventCode {
  114. case TraceStmt:
  115. info.StmtHandle = uintptr(p)
  116. var xStr string
  117. if xValue != nil {
  118. xStr = C.GoString((*C.char)(xValue))
  119. }
  120. info.StmtOrTrigger = xStr
  121. if !strings.HasPrefix(xStr, "--") {
  122. // Not SQL comment, therefore the current event
  123. // is not related to a trigger.
  124. // The user might want to receive the expanded SQL;
  125. // let's check:
  126. if traceConf.WantExpandedSQL {
  127. fillExpandedSQL(&info, contextDB, p)
  128. }
  129. }
  130. case TraceProfile:
  131. info.StmtHandle = uintptr(p)
  132. if xValue == nil {
  133. panic("NULL pointer in X arg of trace_v2 callback for SQLITE_TRACE_PROFILE event")
  134. }
  135. info.RunTimeNanosec = *(*int64)(xValue)
  136. // sample the error //TODO: is it safe? is it useful?
  137. fillDBError(&info.DBError, contextDB)
  138. case TraceRow:
  139. info.StmtHandle = uintptr(p)
  140. case TraceClose:
  141. handle := uintptr(p)
  142. if handle != info.ConnHandle {
  143. panic(fmt.Sprintf("Different conn handle 0x%x (expected 0x%x) in SQLITE_TRACE_CLOSE event.",
  144. handle, info.ConnHandle))
  145. }
  146. default:
  147. // Pass unsupported events to the user callback (if configured);
  148. // let the user callback decide whether to panic or ignore them.
  149. }
  150. // Do not execute user callback when the event was not requested by user!
  151. // Remember that the Close event is always selected when
  152. // registering this callback trampoline with SQLite --- for cleanup.
  153. // In the future there may be more events forced to "selected" in SQLite
  154. // for the driver's needs.
  155. if traceConf.EventMask&eventCode == 0 {
  156. return 0
  157. }
  158. r := 0
  159. if traceConf.Callback != nil {
  160. r = traceConf.Callback(info)
  161. }
  162. return C.int(r)
  163. }
  164. type traceMapEntry struct {
  165. config TraceConfig
  166. }
  167. var traceMapLock sync.Mutex
  168. var traceMap = make(map[uintptr]traceMapEntry)
  169. func addTraceMapping(connHandle uintptr, traceConf TraceConfig) {
  170. traceMapLock.Lock()
  171. defer traceMapLock.Unlock()
  172. oldEntryCopy, found := traceMap[connHandle]
  173. if found {
  174. panic(fmt.Sprintf("Adding trace config %v: handle 0x%x already registered (%v).",
  175. traceConf, connHandle, oldEntryCopy.config))
  176. }
  177. traceMap[connHandle] = traceMapEntry{config: traceConf}
  178. fmt.Printf("Added trace config %v: handle 0x%x.\n", traceConf, connHandle)
  179. }
  180. func lookupTraceMapping(connHandle uintptr) (TraceConfig, bool) {
  181. traceMapLock.Lock()
  182. defer traceMapLock.Unlock()
  183. entryCopy, found := traceMap[connHandle]
  184. return entryCopy.config, found
  185. }
  186. // 'pop' = get and delete from map before returning the value to the caller
  187. func popTraceMapping(connHandle uintptr) (TraceConfig, bool) {
  188. traceMapLock.Lock()
  189. defer traceMapLock.Unlock()
  190. entryCopy, found := traceMap[connHandle]
  191. if found {
  192. delete(traceMap, connHandle)
  193. fmt.Printf("Pop handle 0x%x: deleted trace config %v.\n", connHandle, entryCopy.config)
  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. }