-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsqlite3_traildb.c
430 lines (384 loc) · 13.8 KB
/
sqlite3_traildb.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
#include "sqlite3ext.h"
SQLITE_EXTENSION_INIT1
#include <traildb.h>
#include <string.h>
struct sqlite3_vtab_traildb {
struct sqlite3_vtab vtab;
tdb* traildb_handle;
};
struct sqlite3_vtab_cursor_traildb {
struct sqlite3_vtab_traildb* vtab;
tdb* traildb_handle;
tdb_cursor* cursor;
const tdb_event* event;
int empty_traildb;
uint64_t row_idx;
uint64_t trail_id;
uint64_t total_trails;
};
static int traildbNext(sqlite3_vtab_cursor* cur)
{
// This function is called when we have to go to next row.
struct sqlite3_vtab_cursor_traildb* scur = (struct sqlite3_vtab_cursor_traildb*) cur;
if ( scur->empty_traildb || scur->trail_id >= scur->total_trails ) {
// Stop here if we are at the end of TrailDB.
return SQLITE_OK;
}
do {
// In this do-while loop, we won't exit unless TrailDB is at EOF
// or we got next event to scur->event.
scur->event = tdb_cursor_next(scur->cursor);
if ( !scur->event ) {
// If there are no more events in cursor, step the cursor to next trail.
scur->trail_id++;
if ( scur->trail_id < scur->total_trails ) {
tdb_error err = tdb_get_trail(scur->cursor, scur->trail_id);
if ( err != TDB_ERR_OK ) {
return SQLITE_ERROR;
}
}
}
}
while ( scur->trail_id < scur->total_trails && scur->event == 0 );
// Increment row id (sqlite3 thing, it wants a unique row id for every row)
scur->row_idx++;
return SQLITE_OK;
}
static int traildbOpenCursor(sqlite3_vtab *vtab, sqlite3_vtab_cursor** cur)
{
// This function is called when sqlite3 wants to set up a cursor.
// It actually maps pretty nicely to TrailDB cursor.
struct sqlite3_vtab_traildb* svt = (struct sqlite3_vtab_traildb*) vtab;
(*cur) = sqlite3_malloc(sizeof(struct sqlite3_vtab_cursor_traildb));
if ( !(*cur) ) {
return SQLITE_NOMEM;
}
memset( (*cur), 0, sizeof(struct sqlite3_vtab_cursor_traildb) );
struct sqlite3_vtab_cursor_traildb* scur = (struct sqlite3_vtab_cursor_traildb*) (*cur);
// Make internal TrailDB cursor.
tdb_cursor* cursor = tdb_cursor_new(svt->traildb_handle);
if ( !cursor ) {
sqlite3_free( scur );
(*cur) = 0;
return SQLITE_NOMEM;
}
// How many trails do we have in total? If there are no trails, set a
// special flag.
scur->cursor = cursor;
if ( tdb_num_trails(svt->traildb_handle) == 0 ) {
scur->empty_traildb = 1;
} else {
tdb_error err = tdb_get_trail(cursor, 0);
if ( err != TDB_ERR_OK ) {
sqlite3_free( scur );
(*cur) = 0;
return SQLITE_NOMEM;
}
}
scur->traildb_handle = svt->traildb_handle;
scur->total_trails = tdb_num_trails(svt->traildb_handle);
return SQLITE_OK;
}
static int traildbEof(sqlite3_vtab_cursor* cursor)
{
// This function is called by sqlite3 to test if the cursor is exhausted.
struct sqlite3_vtab_cursor_traildb* cur = (struct sqlite3_vtab_cursor_traildb*) cursor;
if ( cur->empty_traildb || cur->trail_id >= cur->total_trails ) {
return 1;
}
return 0;
}
static int traildbBestIndex(sqlite3_vtab* vtab, sqlite3_index_info* info)
{
// This function can be used to set up indexes to help query planner.
//
// The thing we could do is match by UUID to only get a certain UUID.
// Right now we only fill estimated rows and basically Sqlite3 will almost
// always just scan everything.
//
// Other ideas:
// For equality checks we could do a quick check if the lexicon contains a value.
// If lexicon does not contain the value, we know we can skip everything.
//
// TODO: implement a filter that detects if query says something like
// UUID=<some value> and then only step on that UUID specifically.
struct sqlite3_vtab_traildb* svt = (struct sqlite3_vtab_traildb*) vtab;
info->estimatedRows = tdb_num_events(svt->traildb_handle);
info->estimatedCost = tdb_num_events(svt->traildb_handle);
return SQLITE_OK;
}
static int traildbFilter(sqlite3_vtab_cursor* cursor, int idxNum, const char *idxStr, int argc, sqlite3_value **argv)
{
// This function is called when sqlite3 wants to start a new query.
//
// This function is supposed to use idxNum and idxStr with traildbBestIndex
// to figure out how to actually run the query.
//
// Our queries are all just scans so we don't use them.
struct sqlite3_vtab_cursor_traildb* cur = (struct sqlite3_vtab_cursor_traildb*) cursor;
if ( cur->empty_traildb ) {
return SQLITE_OK;
}
tdb_error err = tdb_get_trail(cur->cursor, 0);
if ( err != TDB_ERR_OK ) {
return SQLITE_NOMEM;
}
int rc = traildbNext(cursor);
cur->row_idx = 0;
return rc;
}
static int traildbCloseCursor(sqlite3_vtab_cursor* cursor)
{
struct sqlite3_vtab_cursor_traildb* svct = (struct sqlite3_vtab_cursor_traildb*) cursor;
if ( svct->cursor ) {
tdb_cursor_free(svct->cursor);
svct->cursor = 0;
}
sqlite3_free(svct);
return SQLITE_OK;
}
// This function comes from zipfile module; renamed for TrailDB
// Its purpose is to remove quotes in TrailDB filename, e.g.
//
// CREATE VIRTUAL TABLE mytdb USING tdb ("./traildb");
//
// This function removes the double quotes in "./traildb". If we don't
// do this we actually try to find a file named "./traildb" (including the
// double quotes in the filename).
static void fileDequote(char *zIn){
char q = zIn[0];
if( q=='"' || q=='\'' || q=='`' || q=='[' ){
int iIn = 1;
int iOut = 0;
if( q=='[' ) q = ']';
while(1){
char c = zIn[iIn++];
if( c==q && zIn[iIn++]!=q ) break;
zIn[iOut++] = c;
}
zIn[iOut] = '\0';
}
}
static int traildbConnect(sqlite3 *db,
void* aux,
int argc,
const char *const *argv,
sqlite3_vtab **vtabs,
char** err)
{
// We want exactly 4 arguments.
// 0 = module name ("traildb")
// 1 = database name (we don't care about it)
// 2 = table name (we also don't care about this)
// 3 = first argument (this is what we care about, it's the name of the traildb)
if ( argc < 4 || argc > 4 ) {
*err = sqlite3_mprintf("traildb requires one argument exactly");
return SQLITE_ERROR;
}
// Set up TrailDB handles
tdb* t = tdb_init();
if ( !t ) {
*err = sqlite3_mprintf("traildb failed to call tdb_init()");
return SQLITE_ERROR;
}
char* fname = sqlite3_malloc(strlen(argv[3])+1);
if ( !fname ) {
*err = sqlite3_mprintf("out of memory");
return SQLITE_NOMEM;
}
memcpy(fname, argv[3], strlen(argv[3]));
fileDequote(fname);
tdb_error terr = tdb_open(t, fname);
if ( terr != TDB_ERR_OK ) {
*err = sqlite3_mprintf("traildb failed to call tdb_open(%s): %s", fname, tdb_error_str(terr));
sqlite3_free(fname);
return SQLITE_ERROR;
}
sqlite3_free(fname);
// We have to do a little dance to produce this CREATE TABLE line sqlite3 wants.
//
// CREATE TABLE v(uuid, timestamp, ...);
//
// ^ This is the string we have to create. Table name doesn't matter. Most
// hoops are in the last part.
//
// Allocate an array of pointers to C strings.
size_t total_length_of_fields = 0;
char** traildb_fields_str = sqlite3_malloc(sizeof(char*) * (tdb_num_fields(t)+1)); // uuid + all other fields
if ( !traildb_fields_str ) {
tdb_close(t);
return SQLITE_NOMEM;
}
memset(traildb_fields_str, 0, (tdb_num_fields(t)+1)*sizeof(char*));
// Set UUID to first field.
traildb_fields_str[0] = sqlite3_mprintf("uuid");
total_length_of_fields += 6;
if ( !traildb_fields_str[0] ) {
sqlite3_free(traildb_fields_str);
tdb_close(t);
return SQLITE_NOMEM;
}
// Set timestamp to second field.
traildb_fields_str[1] = sqlite3_mprintf("timestamp");
total_length_of_fields += 11;
if ( !traildb_fields_str[1] ) {
sqlite3_free(traildb_fields_str[0]);
sqlite3_free(traildb_fields_str);
tdb_close(t);
return SQLITE_NOMEM;
}
// Using TrailDB bindings, get the field names inside TrailDB and put them in our list.
for ( uint64_t i = 0; i < tdb_num_fields(t)-1; ++i ) {
traildb_fields_str[i+2] = sqlite3_mprintf("%s", tdb_get_field_name(t, i+1));
if ( !traildb_fields_str[i+2] ) {
for ( i = 0; i < tdb_num_fields(t)+1; ++i ) {
if ( traildb_fields_str[i] ) {
sqlite3_free(traildb_fields_str[i]);
}
}
sqlite3_free(traildb_fields_str);
tdb_close(t);
return SQLITE_NOMEM;
}
total_length_of_fields += strlen(traildb_fields_str[i+2])+2;
}
// Allocate a string that holds comma-separated list of fields.
char* concatfields = sqlite3_malloc(total_length_of_fields);
if ( !concatfields ) {
for ( uint64_t i = 0; i < tdb_num_fields(t)+1; ++i ) {
if ( traildb_fields_str[i] ) {
sqlite3_free(traildb_fields_str[i]);
}
}
tdb_close(t);
return SQLITE_NOMEM;
}
memset(concatfields, 0, total_length_of_fields);
uint64_t concat_cursor = 0;
// Fill concatfields with the fields.
for ( uint64_t i = 2; i < tdb_num_fields(t)+1; ++i ) {
if ( i > 2 ) {
// Put comma in-between fields
concatfields[concat_cursor] = ',';
concat_cursor++;
}
memcpy(&concatfields[concat_cursor], traildb_fields_str[i], strlen(traildb_fields_str[i]));
concat_cursor += strlen(traildb_fields_str[i]);
}
// No longer need field strings themselves, free them up.
for ( uint64_t i = 0; i < tdb_num_fields(t)+1; ++i ) {
if ( traildb_fields_str[i] ) {
sqlite3_free(traildb_fields_str[i]);
}
}
sqlite3_free(traildb_fields_str);
// Finally build the actual create table command.
char* traildb_fields = sqlite3_mprintf("CREATE TABLE t( uuid TEXT, timestamp INTEGER, %s );", concatfields);
sqlite3_free(concatfields);
if ( !traildb_fields ) {
tdb_close(t);
return SQLITE_NOMEM;
}
// Declare the table. Sqlite3 demands it.
int rc = sqlite3_declare_vtab(db, traildb_fields);
sqlite3_free(traildb_fields);
if ( rc != SQLITE_OK ) {
tdb_close(t);
return rc;
}
// Allocate sqlite3_vtab structure to keep track of our stuff.
struct sqlite3_vtab_traildb* svt = sqlite3_malloc(sizeof(struct sqlite3_vtab_traildb));
if ( !svt ) {
tdb_close(t);
return SQLITE_NOMEM;
}
memset(svt, 0, sizeof(struct sqlite3_vtab_traildb));
svt->traildb_handle = t;
(*vtabs) = (sqlite3_vtab*) svt;
return SQLITE_OK;
}
static int traildbDisconnect(sqlite3_vtab *vtab)
{
// Close TrailDB if needed.
struct sqlite3_vtab_traildb* svt = (struct sqlite3_vtab_traildb*) vtab;
if ( svt->traildb_handle ) {
tdb_close(svt->traildb_handle);
svt->traildb_handle = 0;
}
// Clean up all the things.
sqlite3_free(vtab);
return SQLITE_OK;
};
static int traildbColumn(sqlite3_vtab_cursor* cur, sqlite3_context* ctx, int N)
{
struct sqlite3_vtab_cursor_traildb* scur = (struct sqlite3_vtab_cursor_traildb*) cur;
// If there is no event, error out.
// I think this is not possible to happen with sqlite3.
if ( !scur->event ) {
return SQLITE_ERROR;
}
// N=0 UUID column
if ( N == 0 ) {
const uint8_t* uuid = tdb_get_uuid(scur->traildb_handle, scur->trail_id);
uint8_t hex_uuid[32];
tdb_uuid_hex(uuid, hex_uuid);
sqlite3_result_text(ctx, (const char*) hex_uuid, 32, SQLITE_TRANSIENT);
return SQLITE_OK;
}
// N=1 Timestamp column
if ( N == 1 ) {
sqlite3_result_int64(ctx, (sqlite3_int64) scur->event->timestamp);
return SQLITE_OK;
}
// Any other field, they are in N > 2
uint64_t len = 0;
const char* field_str = tdb_get_item_value(scur->traildb_handle, scur->event->items[N-2], &len);
sqlite3_result_text(ctx, field_str, (int) len, SQLITE_TRANSIENT);
return SQLITE_OK;
}
static int traildbRowId(sqlite3_vtab_cursor* cur, sqlite_int64* row_id)
{
// Return unique row id.
(*row_id) = ((struct sqlite3_vtab_cursor_traildb*) cur)->row_idx;
return SQLITE_OK;
}
static int traildbRename(sqlite3_vtab* cur, const char* newname)
{
// In this function we can object if sqlite3 wants to rename our table.
// I don't know any reasons why we would want to stop sqlite3 from doing
// that so I'm giving it permission every time.
return SQLITE_OK;
}
static int register_vtab_definitions(sqlite3* db)
{
// Sqlite3 boilerplate, teach it how to use our table.
static sqlite3_module traildbModule = {
1,
traildbConnect, // create
traildbConnect, // connect
traildbBestIndex, // bestindex
traildbDisconnect, // disconnect
traildbDisconnect, // destroy
traildbOpenCursor, // open
traildbCloseCursor, // close
traildbFilter,
traildbNext, // next
traildbEof, // eof
traildbColumn, // column
traildbRowId, // row id
0, // update
0, // begin
0, // sync
0, // commit
0, // rollback
0, // findmethod
traildbRename, // rename
};
int rc = sqlite3_create_module(db, "traildb", &traildbModule, 0);
return rc;
}
int sqlite3_sqlitetraildb_init(sqlite3* db, char** error_message, const sqlite3_api_routines *api)
{
SQLITE_EXTENSION_INIT2(api);
return register_vtab_definitions(db);
}