OSDN Git Service

add sqlite vendor (#48)
[bytom/vapor.git] / vendor / github.com / mattn / go-sqlite3 / sqlite3_trace.go
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
6 // +build sqlite_trace trace
7
8 package sqlite3
9
10 /*
11 #ifndef USE_LIBSQLITE3
12 #include <sqlite3-binding.h>
13 #else
14 #include <sqlite3.h>
15 #endif
16 #include <stdlib.h>
17
18 int traceCallbackTrampoline(unsigned int traceEventCode, void *ctx, void *p, void *x);
19 */
20 import "C"
21
22 import (
23         "fmt"
24         "strings"
25         "sync"
26         "unsafe"
27 )
28
29 // Trace... constants identify the possible events causing callback invocation.
30 // Values are same as the corresponding SQLite Trace Event Codes.
31 const (
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)
36 )
37
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":
42         EventCode  uint32
43         AutoCommit bool
44         ConnHandle uintptr
45
46         // Usually filled, unless EventCode = TraceClose = SQLITE_TRACE_CLOSE:
47         // identifier for a prepared statement:
48         StmtHandle uintptr
49
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.
54         StmtOrTrigger string
55         ExpandedSQL   string // only if requested (TraceConfig.WantExpandedSQL = true)
56
57         // filled when EventCode = TraceProfile = SQLITE_TRACE_PROFILE:
58         // estimated number of nanoseconds that the prepared statement took to run:
59         RunTimeNanosec int64
60
61         DBError Error
62 }
63
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
72
73 type TraceConfig struct {
74         Callback        TraceUserCallback
75         EventMask       uint32
76         WantExpandedSQL bool
77 }
78
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))
84 }
85
86 func fillExpandedSQL(info *TraceInfo, db *C.sqlite3, pStmt unsafe.Pointer) {
87         if pStmt == nil {
88                 panic("No SQLite statement pointer in P arg of trace_v2 callback")
89         }
90
91         expSQLiteCStr := C.sqlite3_expanded_sql((*C.sqlite3_stmt)(pStmt))
92         if expSQLiteCStr == nil {
93                 fillDBError(&info.DBError, db)
94                 return
95         }
96         info.ExpandedSQL = C.GoString(expSQLiteCStr)
97 }
98
99 //export traceCallbackTrampoline
100 func traceCallbackTrampoline(
101         traceEventCode C.uint,
102         // Parameter named 'C' in SQLite docs = Context given at registration:
103         ctx unsafe.Pointer,
104         // Parameter named 'P' in SQLite docs (Primary event data?):
105         p unsafe.Pointer,
106         // Parameter named 'X' in SQLite docs (eXtra event data?):
107         xValue unsafe.Pointer) C.int {
108
109         eventCode := uint32(traceEventCode)
110
111         if ctx == nil {
112                 panic(fmt.Sprintf("No context (ev 0x%x)", traceEventCode))
113         }
114
115         contextDB := (*C.sqlite3)(ctx)
116         connHandle := uintptr(ctx)
117
118         var traceConf TraceConfig
119         var found bool
120         if eventCode == TraceClose {
121                 // clean up traceMap: 'pop' means get and delete
122                 traceConf, found = popTraceMapping(connHandle)
123         } else {
124                 traceConf, found = lookupTraceMapping(connHandle)
125         }
126
127         if !found {
128                 panic(fmt.Sprintf("Mapping not found for handle 0x%x (ev 0x%x)",
129                         connHandle, eventCode))
130         }
131
132         var info TraceInfo
133
134         info.EventCode = eventCode
135         info.AutoCommit = (int(C.sqlite3_get_autocommit(contextDB)) != 0)
136         info.ConnHandle = connHandle
137
138         switch eventCode {
139         case TraceStmt:
140                 info.StmtHandle = uintptr(p)
141
142                 var xStr string
143                 if xValue != nil {
144                         xStr = C.GoString((*C.char)(xValue))
145                 }
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;
151                         // let's check:
152                         if traceConf.WantExpandedSQL {
153                                 fillExpandedSQL(&info, contextDB, p)
154                         }
155                 }
156
157         case TraceProfile:
158                 info.StmtHandle = uintptr(p)
159
160                 if xValue == nil {
161                         panic("NULL pointer in X arg of trace_v2 callback for SQLITE_TRACE_PROFILE event")
162                 }
163
164                 info.RunTimeNanosec = *(*int64)(xValue)
165
166                 // sample the error //TODO: is it safe? is it useful?
167                 fillDBError(&info.DBError, contextDB)
168
169         case TraceRow:
170                 info.StmtHandle = uintptr(p)
171
172         case TraceClose:
173                 handle := 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))
177                 }
178
179         default:
180                 // Pass unsupported events to the user callback (if configured);
181                 // let the user callback decide whether to panic or ignore them.
182         }
183
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 {
190                 return 0
191         }
192
193         r := 0
194         if traceConf.Callback != nil {
195                 r = traceConf.Callback(info)
196         }
197         return C.int(r)
198 }
199
200 type traceMapEntry struct {
201         config TraceConfig
202 }
203
204 var traceMapLock sync.Mutex
205 var traceMap = make(map[uintptr]traceMapEntry)
206
207 func addTraceMapping(connHandle uintptr, traceConf TraceConfig) {
208         traceMapLock.Lock()
209         defer traceMapLock.Unlock()
210
211         oldEntryCopy, found := traceMap[connHandle]
212         if found {
213                 panic(fmt.Sprintf("Adding trace config %v: handle 0x%x already registered (%v).",
214                         traceConf, connHandle, oldEntryCopy.config))
215         }
216         traceMap[connHandle] = traceMapEntry{config: traceConf}
217         fmt.Printf("Added trace config %v: handle 0x%x.\n", traceConf, connHandle)
218 }
219
220 func lookupTraceMapping(connHandle uintptr) (TraceConfig, bool) {
221         traceMapLock.Lock()
222         defer traceMapLock.Unlock()
223
224         entryCopy, found := traceMap[connHandle]
225         return entryCopy.config, found
226 }
227
228 // 'pop' = get and delete from map before returning the value to the caller
229 func popTraceMapping(connHandle uintptr) (TraceConfig, bool) {
230         traceMapLock.Lock()
231         defer traceMapLock.Unlock()
232
233         entryCopy, found := traceMap[connHandle]
234         if found {
235                 delete(traceMap, connHandle)
236                 fmt.Printf("Pop handle 0x%x: deleted trace config %v.\n", connHandle, entryCopy.config)
237         }
238         return entryCopy.config, found
239 }
240
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:
245 // event mask, etc.
246 func (c *SQLiteConn) SetTrace(requested *TraceConfig) error {
247         connHandle := uintptr(unsafe.Pointer(c.db))
248
249         _, _ = popTraceMapping(connHandle)
250
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)
255                 return err
256         }
257
258         reqCopy := *requested
259
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
265         }
266
267         addTraceMapping(connHandle, reqCopy)
268
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)
274         return err
275 }
276
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.
283
284         if rv != C.SQLITE_OK {
285                 return c.lastError()
286         }
287         return nil
288 }