-
Notifications
You must be signed in to change notification settings - Fork 81
/
Copy pathsqlite_wrap.c
282 lines (260 loc) · 9.51 KB
/
sqlite_wrap.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
/* ============================================================================
* SQLite abstraction
* ==========================================================================*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sqlite3.h>
#include <time.h>
#include "sqlite_wrap.h"
#include "sds.h"
#include "botlib.h"
#define SHOW_QUERY_ERRORS 1
/* This is the low level function that we use to model all the higher level
* functions.
*
* Queries can contain ?s ?b ?i and ?d special specifiers that are bound to
* the SQL query, and must be present later as additional arguments after
* the 'sql' argument.
*
* ?s -- TEXT field: char* argument.
* ?b -- Blob field: char* argument followed by size_t argument.
* ?i -- INT field : int64_t argument.
* ?d -- REAL field: double argument.
*
* The function returns the return code of the last SQLite query that
* failed on error. On success it returns what sqlite3_step() returns.
* If the function returns SQLITE_ROW, that is, if the query is
* returning data, the function returns, by reference, a sqlRow object
* that the caller can use to get the current and next rows.
*
* The user needs to later free this sqlRow object with sqlEnd() (but this
* is done automatically if all the rows are consumed with sqlNextRow()).
* Note that is valid to call sqlEnd() even if the query didn't return
* SQLITE_ROW, since in such case row->stmt is set to NULL.
*/
int sqlGenericQuery(sqlite3 *dbhandle, sqlRow *row, const char *sql, va_list ap) {
int rc = SQLITE_ERROR;
sqlite3_stmt *stmt = NULL;
sds query = sdsempty();
if (row) row->stmt = NULL; /* On error sqlNextRow() should return false. */
/* We need to build the query, substituting the following three
* classes of patterns with just "?", remembering the order and
* type, and later using the sql3 binding API in order to prepare
* the query:
*
* ?s string
* ?b blob (varargs must have char ptr and size_t len)
* ?i int64_t
* ?d double
*/
char spec[SQL_MAX_SPEC];
int numspec = 0;
const char *p = sql;
while(p[0]) {
if (p[0] == '?') {
if (p[1] == 's' || p[1] == 'i' || p[1] == 'd' || p[1] == 'b') {
if (numspec == SQL_MAX_SPEC) goto error;
spec[numspec++] = p[1];
} else {
goto error;
}
query = sdscatlen(query,"?",1);
p++; /* Skip the specifier. */
} else {
query = sdscatlen(query,p,1);
}
p++;
}
/* Prepare the query and bind the query arguments. */
rc = sqlite3_prepare_v2(dbhandle,query,-1,&stmt,NULL);
if (rc != SQLITE_OK) {
if (SHOW_QUERY_ERRORS) printf("%p: Query error: %s: %s\n",
(void*)dbhandle,
query,
sqlite3_errmsg(dbhandle));
goto error;
}
for (int j = 0; j < numspec; j++) {
switch(spec[j]) {
case 'b': {
char *blobptr = va_arg(ap,char*);
size_t bloblen = va_arg(ap,size_t);
rc = sqlite3_bind_blob64(stmt,j+1,blobptr,bloblen,NULL);
}
break;
case 's': rc = sqlite3_bind_text(stmt,j+1,va_arg(ap,char*),-1,NULL);
break;
case 'i': rc = sqlite3_bind_int64(stmt,j+1,va_arg(ap,int64_t));
break;
case 'd': rc = sqlite3_bind_double(stmt,j+1,va_arg(ap,double));
break;
}
if (rc != SQLITE_OK) goto error;
}
/* Execute. */
rc = sqlite3_step(stmt);
if (rc == SQLITE_ROW) {
if (row) {
row->stmt = stmt;
row->cols = 0;
row->col = NULL;
stmt = NULL; /* Don't free it on cleanup. */
}
}
error:
if (stmt) sqlite3_finalize(stmt);
sdsfree(query);
return rc;
}
/* This function should be called only if you don't get all the rows
* till the end. It is safe to call anyway. */
void sqlEnd(sqlRow *row) {
if (row->stmt == NULL) return;
xfree(row->col);
sqlite3_finalize(row->stmt);
row->col = NULL;
row->stmt = NULL;
}
/* After sqlGenericQuery() returns SQLITE_ROW, you can call this function
* with the 'row' object pointer in order to get the rows composing the
* result set. It returns 1 if the next row is available, otherwise 0
* is returned (and the row object is freed). If you stop the iteration
* before all the elements are used, you need to call sqlEnd(). */
int sqlNextRow(sqlRow *row) {
if (row->stmt == NULL) return 0;
if (row->col != NULL) {
if (sqlite3_step(row->stmt) != SQLITE_ROW) {
sqlEnd(row);
return 0;
}
}
xfree(row->col);
row->cols = sqlite3_data_count(row->stmt);
row->col = xmalloc(row->cols*sizeof(sqlCol));
for (int j = 0; j < row->cols; j++) {
row->col[j].type = sqlite3_column_type(row->stmt,j);
if (row->col[j].type == SQLITE_INTEGER) {
row->col[j].i = sqlite3_column_int64(row->stmt,j);
} else if (row->col[j].type == SQLITE_FLOAT) {
row->col[j].d = sqlite3_column_double(row->stmt,j);
} else if (row->col[j].type == SQLITE_TEXT) {
row->col[j].s = (char*)sqlite3_column_text(row->stmt,j);
row->col[j].i = sqlite3_column_bytes(row->stmt,j);
} else if (row->col[j].type == SQLITE_BLOB) {
row->col[j].s = sqlite3_column_blob(row->stmt,j);
row->col[j].i = sqlite3_column_bytes(row->stmt,j);
} else {
/* SQLITE_NULL. */
row->col[j].s = NULL;
row->col[j].i = 0;
row->col[j].d = 0;
}
}
return 1;
}
/* Wrapper for sqlGenericQuery() returning the last inserted ID or 0
* on error. */
int sqlInsert(sqlite3 *dbhandle, const char *sql, ...) {
int64_t lastid = 0;
va_list ap;
va_start(ap,sql);
int rc = sqlGenericQuery(dbhandle,NULL,sql,ap);
if (rc == SQLITE_DONE) lastid = sqlite3_last_insert_rowid(dbhandle);
va_end(ap);
return lastid;
}
/* Wrapper for sqlGenericQuery() returning 1 if the query resulted in
* SQLITE_DONE, otherwise zero. This is good for UPDATE and DELETE
* statements. */
int sqlQuery(sqlite3 *dbhandle, const char *sql, ...) {
int64_t retval = 0;
va_list ap;
va_start(ap,sql);
int rc = sqlGenericQuery(dbhandle,NULL,sql,ap);
retval = (rc == SQLITE_DONE);
va_end(ap);
return retval;
}
/* Wrapper for sqlGenericQuery() using varialbe number of args.
* This is what you want when doing SELECT queries. */
int sqlSelect(sqlite3 *dbhandle, sqlRow *row, const char *sql, ...) {
va_list ap;
va_start(ap,sql);
int rc = sqlGenericQuery(dbhandle,row,sql,ap);
va_end(ap);
return rc;
}
/* Wrapper for sqlGenericQuery() using variable number of args.
* This is what you want when doing SELECT queries that return a
* single row. This function will care to also call sqlNextRow() for
* you in case the return value is SQLITE_ROW. */
int sqlSelectOneRow(sqlite3 *dbhandle, sqlRow *row, const char *sql, ...) {
va_list ap;
va_start(ap,sql);
int rc = sqlGenericQuery(dbhandle,row,sql,ap);
if (rc == SQLITE_ROW) sqlNextRow(row);
va_end(ap);
return rc;
}
/* Wrapper for sqlGenericQuery() to do a SELECT and return directly
* the integer of the first row, or zero on error. */
int64_t sqlSelectInt(sqlite3 *dbhandle, const char *sql, ...) {
sqlRow row;
int64_t i = 0;
va_list ap;
va_start(ap,sql);
int rc = sqlGenericQuery(dbhandle,&row,sql,ap);
if (rc == SQLITE_ROW) {
sqlNextRow(&row);
i = row.col[0].i;
sqlEnd(&row);
}
va_end(ap);
return i;
}
/* ==========================================================================
* Key value store abstraction. This implements a trivial KV store on top
* of SQLite. It only has SET, GET, DEL and support for a maximum time to live.
* ======================================================================== */
/* Set the key to the specified value and expire time. An expire of zero
* means the key should not be expired at all. Return 1 on success, or
* 0 on error. */
int kvSetLen(sqlite3 *dbhandle, const char *key, const char *value, size_t vlen, int64_t expire) {
if (expire) expire += time(NULL);
if (!sqlInsert(dbhandle,"INSERT INTO KeyValue VALUES(?i,?s,?b)",
expire,key,value,vlen))
{
if (!sqlQuery(dbhandle,"UPDATE KeyValue SET expire=?i,value=?b WHERE key=?s",
expire,value,vlen,key))
{
return 0;
}
}
return 1;
}
/* Wrapper where the value len is obtained via strlen().*/
int kvSet(sqlite3 *dbhandle,const char *key, const char *value, int64_t expire) {
return kvSetLen(dbhandle,key,value,strlen(value),expire);
}
/* Get the specified key and return it as an SDS string. If the value is
* expired or does not exist NULL is returned. */
sds kvGet(sqlite3 *dbhandle,const char *key) {
sds value = NULL;
sqlRow row;
sqlSelect(dbhandle,&row,"SELECT expire,value FROM KeyValue WHERE key=?s",key);
if (sqlNextRow(&row)) {
int64_t expire = row.col[0].i;
if (expire && expire < time(NULL)) {
sqlQuery(dbhandle,"DELETE FROM KeyValue WHERE key=?s",key);
} else {
value = sdsnewlen(row.col[1].s,row.col[1].i);
}
}
sqlEnd(&row);
return value;
}
/* Delete the key if it exists. */
void kvDel(sqlite3 *dbhandle, const char *key) {
sqlQuery(dbhandle,"DELETE FROM KeyValue WHERE key=?s",key);
}