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.

415 lines
12 KiB

  1. // Copyright (C) 2016 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
  2. // TODO: add "Gimpl do foo" team?
  3. //
  4. // Use of this source code is governed by an MIT-style
  5. // license that can be found in the LICENSE file.
  6. // +build 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. void stepTrampoline(sqlite3_context*, int, sqlite3_value**);
  16. void doneTrampoline(sqlite3_context*);
  17. void traceCallbackTrampoline(unsigned traceEventCode, void *ctx, void *p, void *x);
  18. */
  19. import "C"
  20. import (
  21. "errors"
  22. "fmt"
  23. "reflect"
  24. "strings"
  25. "sync"
  26. "unsafe"
  27. )
  28. // Trace... constants identify the possible events causing callback invocation.
  29. // Values are same as the corresponding SQLite Trace Event Codes.
  30. const (
  31. TraceStmt = C.SQLITE_TRACE_STMT
  32. TraceProfile = C.SQLITE_TRACE_PROFILE
  33. TraceRow = C.SQLITE_TRACE_ROW
  34. TraceClose = C.SQLITE_TRACE_CLOSE
  35. )
  36. type TraceInfo struct {
  37. // Pack together the shorter fields, to keep the struct smaller.
  38. // On a 64-bit machine there would be padding
  39. // between EventCode and ConnHandle; having AutoCommit here is "free":
  40. EventCode uint32
  41. AutoCommit bool
  42. ConnHandle uintptr
  43. // Usually filled, unless EventCode = TraceClose = SQLITE_TRACE_CLOSE:
  44. // identifier for a prepared statement:
  45. StmtHandle uintptr
  46. // Two strings filled when EventCode = TraceStmt = SQLITE_TRACE_STMT:
  47. // (1) either the unexpanded SQL text of the prepared statement, or
  48. // an SQL comment that indicates the invocation of a trigger;
  49. // (2) expanded SQL, if requested and if (1) is not an SQL comment.
  50. StmtOrTrigger string
  51. ExpandedSQL string // only if requested (TraceConfig.WantExpandedSQL = true)
  52. // filled when EventCode = TraceProfile = SQLITE_TRACE_PROFILE:
  53. // estimated number of nanoseconds that the prepared statement took to run:
  54. RunTimeNanosec int64
  55. DBError Error
  56. }
  57. // TraceUserCallback gives the signature for a trace function
  58. // provided by the user (Go application programmer).
  59. // SQLite 3.14 documentation (as of September 2, 2016)
  60. // for SQL Trace Hook = sqlite3_trace_v2():
  61. // The integer return value from the callback is currently ignored,
  62. // though this may change in future releases. Callback implementations
  63. // should return zero to ensure future compatibility.
  64. type TraceUserCallback func(TraceInfo) int
  65. type TraceConfig struct {
  66. Callback TraceUserCallback
  67. EventMask uint
  68. WantExpandedSQL bool
  69. }
  70. func fillDBError(dbErr *Error, db *C.sqlite3) {
  71. // See SQLiteConn.lastError(), in file 'sqlite3.go' at the time of writing (Sept 5, 2016)
  72. dbErr.Code = ErrNo(C.sqlite3_errcode(db))
  73. dbErr.ExtendedCode = ErrNoExtended(C.sqlite3_extended_errcode(db))
  74. dbErr.err = C.GoString(C.sqlite3_errmsg(db))
  75. }
  76. func fillExpandedSQL(info *TraceInfo, db *C.sqlite3, pStmt unsafe.Pointer) {
  77. if pStmt == nil {
  78. panic("No SQLite statement pointer in P arg of trace_v2 callback")
  79. }
  80. expSQLiteCStr := C.sqlite3_expanded_sql((*C.sqlite3_stmt)(pStmt))
  81. if expSQLiteCStr == nil {
  82. fillDBError(&info.DBError, db)
  83. return
  84. }
  85. info.ExpandedSQL = C.GoString(expSQLiteCStr)
  86. }
  87. //export traceCallbackTrampoline
  88. func traceCallbackTrampoline(
  89. traceEventCode uint,
  90. // Parameter named 'C' in SQLite docs = Context given at registration:
  91. ctx unsafe.Pointer,
  92. // Parameter named 'P' in SQLite docs (Primary event data?):
  93. p unsafe.Pointer,
  94. // Parameter named 'X' in SQLite docs (eXtra event data?):
  95. xValue unsafe.Pointer) int {
  96. if ctx == nil {
  97. panic(fmt.Sprintf("No context (ev 0x%x)", traceEventCode))
  98. }
  99. contextDB := (*C.sqlite3)(ctx)
  100. connHandle := uintptr(ctx)
  101. var traceConf TraceConfig
  102. var found bool
  103. if traceEventCode == TraceClose {
  104. // clean up traceMap: 'pop' means get and delete
  105. traceConf, found = popTraceMapping(connHandle)
  106. } else {
  107. traceConf, found = lookupTraceMapping(connHandle)
  108. }
  109. if !found {
  110. panic(fmt.Sprintf("Mapping not found for handle 0x%x (ev 0x%x)",
  111. connHandle, traceEventCode))
  112. }
  113. var info TraceInfo
  114. info.EventCode = uint32(traceEventCode)
  115. info.AutoCommit = (int(C.sqlite3_get_autocommit(contextDB)) != 0)
  116. info.ConnHandle = connHandle
  117. switch traceEventCode {
  118. case TraceStmt:
  119. info.StmtHandle = uintptr(p)
  120. var xStr string
  121. if xValue != nil {
  122. xStr = C.GoString((*C.char)(xValue))
  123. }
  124. info.StmtOrTrigger = xStr
  125. if !strings.HasPrefix(xStr, "--") {
  126. // Not SQL comment, therefore the current event
  127. // is not related to a trigger.
  128. // The user might want to receive the expanded SQL;
  129. // let's check:
  130. if traceConf.WantExpandedSQL {
  131. fillExpandedSQL(&info, contextDB, p)
  132. }
  133. }
  134. case TraceProfile:
  135. info.StmtHandle = uintptr(p)
  136. if xValue == nil {
  137. panic("NULL pointer in X arg of trace_v2 callback for SQLITE_TRACE_PROFILE event")
  138. }
  139. info.RunTimeNanosec = *(*int64)(xValue)
  140. // sample the error //TODO: is it safe? is it useful?
  141. fillDBError(&info.DBError, contextDB)
  142. case TraceRow:
  143. info.StmtHandle = uintptr(p)
  144. case TraceClose:
  145. handle := uintptr(p)
  146. if handle != info.ConnHandle {
  147. panic(fmt.Sprintf("Different conn handle 0x%x (expected 0x%x) in SQLITE_TRACE_CLOSE event.",
  148. handle, info.ConnHandle))
  149. }
  150. default:
  151. // Pass unsupported events to the user callback (if configured);
  152. // let the user callback decide whether to panic or ignore them.
  153. }
  154. // Do not execute user callback when the event was not requested by user!
  155. // Remember that the Close event is always selected when
  156. // registering this callback trampoline with SQLite --- for cleanup.
  157. // In the future there may be more events forced to "selected" in SQLite
  158. // for the driver's needs.
  159. if traceConf.EventMask&traceEventCode == 0 {
  160. return 0
  161. }
  162. r := 0
  163. if traceConf.Callback != nil {
  164. r = traceConf.Callback(info)
  165. }
  166. return r
  167. }
  168. type traceMapEntry struct {
  169. config TraceConfig
  170. }
  171. var traceMapLock sync.Mutex
  172. var traceMap = make(map[uintptr]traceMapEntry)
  173. func addTraceMapping(connHandle uintptr, traceConf TraceConfig) {
  174. traceMapLock.Lock()
  175. defer traceMapLock.Unlock()
  176. oldEntryCopy, found := traceMap[connHandle]
  177. if found {
  178. panic(fmt.Sprintf("Adding trace config %v: handle 0x%x already registered (%v).",
  179. traceConf, connHandle, oldEntryCopy.config))
  180. }
  181. traceMap[connHandle] = traceMapEntry{config: traceConf}
  182. fmt.Printf("Added trace config %v: handle 0x%x.\n", traceConf, connHandle)
  183. }
  184. func lookupTraceMapping(connHandle uintptr) (TraceConfig, bool) {
  185. traceMapLock.Lock()
  186. defer traceMapLock.Unlock()
  187. entryCopy, found := traceMap[connHandle]
  188. return entryCopy.config, found
  189. }
  190. // 'pop' = get and delete from map before returning the value to the caller
  191. func popTraceMapping(connHandle uintptr) (TraceConfig, bool) {
  192. traceMapLock.Lock()
  193. defer traceMapLock.Unlock()
  194. entryCopy, found := traceMap[connHandle]
  195. if found {
  196. delete(traceMap, connHandle)
  197. fmt.Printf("Pop handle 0x%x: deleted trace config %v.\n", connHandle, entryCopy.config)
  198. }
  199. return entryCopy.config, found
  200. }
  201. // RegisterAggregator makes a Go type available as a SQLite aggregation function.
  202. //
  203. // Because aggregation is incremental, it's implemented in Go with a
  204. // type that has 2 methods: func Step(values) accumulates one row of
  205. // data into the accumulator, and func Done() ret finalizes and
  206. // returns the aggregate value. "values" and "ret" may be any type
  207. // supported by RegisterFunc.
  208. //
  209. // RegisterAggregator takes as implementation a constructor function
  210. // that constructs an instance of the aggregator type each time an
  211. // aggregation begins. The constructor must return a pointer to a
  212. // type, or an interface that implements Step() and Done().
  213. //
  214. // The constructor function and the Step/Done methods may optionally
  215. // return an error in addition to their other return values.
  216. //
  217. // See _example/go_custom_funcs for a detailed example.
  218. func (c *SQLiteConn) RegisterAggregator(name string, impl interface{}, pure bool) error {
  219. var ai aggInfo
  220. ai.constructor = reflect.ValueOf(impl)
  221. t := ai.constructor.Type()
  222. if t.Kind() != reflect.Func {
  223. return errors.New("non-function passed to RegisterAggregator")
  224. }
  225. if t.NumOut() != 1 && t.NumOut() != 2 {
  226. return errors.New("SQLite aggregator constructors must return 1 or 2 values")
  227. }
  228. if t.NumOut() == 2 && !t.Out(1).Implements(reflect.TypeOf((*error)(nil)).Elem()) {
  229. return errors.New("Second return value of SQLite function must be error")
  230. }
  231. if t.NumIn() != 0 {
  232. return errors.New("SQLite aggregator constructors must not have arguments")
  233. }
  234. agg := t.Out(0)
  235. switch agg.Kind() {
  236. case reflect.Ptr, reflect.Interface:
  237. default:
  238. return errors.New("SQlite aggregator constructor must return a pointer object")
  239. }
  240. stepFn, found := agg.MethodByName("Step")
  241. if !found {
  242. return errors.New("SQlite aggregator doesn't have a Step() function")
  243. }
  244. step := stepFn.Type
  245. if step.NumOut() != 0 && step.NumOut() != 1 {
  246. return errors.New("SQlite aggregator Step() function must return 0 or 1 values")
  247. }
  248. if step.NumOut() == 1 && !step.Out(0).Implements(reflect.TypeOf((*error)(nil)).Elem()) {
  249. return errors.New("type of SQlite aggregator Step() return value must be error")
  250. }
  251. stepNArgs := step.NumIn()
  252. start := 0
  253. if agg.Kind() == reflect.Ptr {
  254. // Skip over the method receiver
  255. stepNArgs--
  256. start++
  257. }
  258. if step.IsVariadic() {
  259. stepNArgs--
  260. }
  261. for i := start; i < start+stepNArgs; i++ {
  262. conv, err := callbackArg(step.In(i))
  263. if err != nil {
  264. return err
  265. }
  266. ai.stepArgConverters = append(ai.stepArgConverters, conv)
  267. }
  268. if step.IsVariadic() {
  269. conv, err := callbackArg(t.In(start + stepNArgs).Elem())
  270. if err != nil {
  271. return err
  272. }
  273. ai.stepVariadicConverter = conv
  274. // Pass -1 to sqlite so that it allows any number of
  275. // arguments. The call helper verifies that the minimum number
  276. // of arguments is present for variadic functions.
  277. stepNArgs = -1
  278. }
  279. doneFn, found := agg.MethodByName("Done")
  280. if !found {
  281. return errors.New("SQlite aggregator doesn't have a Done() function")
  282. }
  283. done := doneFn.Type
  284. doneNArgs := done.NumIn()
  285. if agg.Kind() == reflect.Ptr {
  286. // Skip over the method receiver
  287. doneNArgs--
  288. }
  289. if doneNArgs != 0 {
  290. return errors.New("SQlite aggregator Done() function must have no arguments")
  291. }
  292. if done.NumOut() != 1 && done.NumOut() != 2 {
  293. return errors.New("SQLite aggregator Done() function must return 1 or 2 values")
  294. }
  295. if done.NumOut() == 2 && !done.Out(1).Implements(reflect.TypeOf((*error)(nil)).Elem()) {
  296. return errors.New("second return value of SQLite aggregator Done() function must be error")
  297. }
  298. conv, err := callbackRet(done.Out(0))
  299. if err != nil {
  300. return err
  301. }
  302. ai.doneRetConverter = conv
  303. ai.active = make(map[int64]reflect.Value)
  304. ai.next = 1
  305. // ai must outlast the database connection, or we'll have dangling pointers.
  306. c.aggregators = append(c.aggregators, &ai)
  307. cname := C.CString(name)
  308. defer C.free(unsafe.Pointer(cname))
  309. opts := C.SQLITE_UTF8
  310. if pure {
  311. opts |= C.SQLITE_DETERMINISTIC
  312. }
  313. rv := C._sqlite3_create_function(c.db, cname, C.int(stepNArgs), C.int(opts), C.uintptr_t(newHandle(c, &ai)), nil, (*[0]byte)(unsafe.Pointer(C.stepTrampoline)), (*[0]byte)(unsafe.Pointer(C.doneTrampoline)))
  314. if rv != C.SQLITE_OK {
  315. return c.lastError()
  316. }
  317. return nil
  318. }
  319. // SetTrace installs or removes the trace callback for the given database connection.
  320. // It's not named 'RegisterTrace' because only one callback can be kept and called.
  321. // Calling SetTrace a second time on same database connection
  322. // overrides (cancels) any prior callback and all its settings:
  323. // event mask, etc.
  324. func (c *SQLiteConn) SetTrace(requested *TraceConfig) error {
  325. connHandle := uintptr(unsafe.Pointer(c.db))
  326. _, _ = popTraceMapping(connHandle)
  327. if requested == nil {
  328. // The traceMap entry was deleted already by popTraceMapping():
  329. // can disable all events now, no need to watch for TraceClose.
  330. err := c.setSQLiteTrace(0)
  331. return err
  332. }
  333. reqCopy := *requested
  334. // Disable potentially expensive operations
  335. // if their result will not be used. We are doing this
  336. // just in case the caller provided nonsensical input.
  337. if reqCopy.EventMask&TraceStmt == 0 {
  338. reqCopy.WantExpandedSQL = false
  339. }
  340. addTraceMapping(connHandle, reqCopy)
  341. // The callback trampoline function does cleanup on Close event,
  342. // regardless of the presence or absence of the user callback.
  343. // Therefore it needs the Close event to be selected:
  344. actualEventMask := reqCopy.EventMask | TraceClose
  345. err := c.setSQLiteTrace(actualEventMask)
  346. return err
  347. }
  348. func (c *SQLiteConn) setSQLiteTrace(sqliteEventMask uint) error {
  349. rv := C.sqlite3_trace_v2(c.db,
  350. C.uint(sqliteEventMask),
  351. (*[0]byte)(unsafe.Pointer(C.traceCallbackTrampoline)),
  352. unsafe.Pointer(c.db)) // Fourth arg is same as first: we are
  353. // passing the database connection handle as callback context.
  354. if rv != C.SQLITE_OK {
  355. return c.lastError()
  356. }
  357. return nil
  358. }