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