]> jfr.im git - irc/quakenet/newserv.git/blame - sqlite/sqlite.c
To burst onto QuakeNet we need to fix a few more bugs.
[irc/quakenet/newserv.git] / sqlite / sqlite.c
CommitLineData
ee8cd7d0
CP
1/*
2 * SQLite module
3 */
4
aa944539
CP
5#include <stdlib.h>
6#include <stdio.h>
7#include <stdarg.h>
8#include <string.h>
9
10#define __USE_POSIX199309
11#include <time.h>
12
ee8cd7d0
CP
13#include "../core/config.h"
14#include "../core/error.h"
ee8cd7d0
CP
15#include "../core/events.h"
16#include "../core/hooks.h"
ee8cd7d0
CP
17#include "../lib/version.h"
18#include "../lib/strlfunc.h"
76c8da69 19#include "../core/nsmalloc.h"
aa944539 20#include "../core/schedule.h"
827cbcd7
CP
21
22#define BUILDING_DBAPI
ee8cd7d0
CP
23#include "../dbapi/dbapi.h"
24
aa944539 25#include "sqlite.h"
ee8cd7d0
CP
26
27MODULE_VERSION("");
28
29static int dbconnected = 0;
30static struct sqlite3 *conn;
008e57e5 31static SQLiteModuleIdentifier modid;
ee8cd7d0 32
aa944539
CP
33struct sqlitequeue {
34 sqlite3_stmt *statement;
35 SQLiteQueryHandler handler;
36 void *tag;
37 int identifier;
38 struct sqlitequeue *next;
39};
40
41static struct sqlitequeue *head, *tail;
42static int queuesize;
43static void *processsched;
44
76c8da69
CP
45#define SYNC_MODE "OFF"
46
aa944539
CP
47static void sqlitequeueprocessor(void *arg);
48static void dbstatus(int hooknum, void *arg);
49
ee8cd7d0
CP
50void _init(void) {
51 sstring *dbfile;
52 int rc;
53
54 dbfile = getcopyconfigitem("sqlite", "file", "newserv.db", 100);
55
aa944539
CP
56 if(!dbfile) {
57 Error("sqlite", ERR_ERROR, "Unable to get config settings.");
ee8cd7d0 58 return;
aa944539 59 }
ee8cd7d0 60
aa944539
CP
61 processsched = schedulerecurring(time(NULL), 0, 1, &sqlitequeueprocessor, NULL);
62 if(!processsched) {
63 Error("sqlite", ERR_ERROR, "Unable to schedule query processor.");
64 freesstring(dbfile);
65 return;
66 }
67
ee8cd7d0
CP
68 rc = sqlite3_open(dbfile->content, &conn);
69 freesstring(dbfile);
70
71 if(rc) {
72 Error("sqlite", ERR_ERROR, "Unable to connect to database: %s", sqlite3_errmsg(conn));
aa944539 73 deleteschedule(processsched, &sqlitequeueprocessor, NULL);
ee8cd7d0
CP
74 return;
75 }
76
77 dbconnected = 1;
78
76c8da69 79 sqliteasyncqueryf(0, NULL, NULL, 0, "PRAGMA synchronous=" SYNC_MODE ";");
aa944539 80 registerhook(HOOK_CORE_STATSREQUEST, dbstatus);
ee8cd7d0
CP
81}
82
83void _fini(void) {
aa944539 84 struct sqlitequeue *q, *nq;
ee8cd7d0
CP
85 if(!sqliteconnected())
86 return;
87
aa944539
CP
88 deregisterhook(HOOK_CORE_STATSREQUEST, dbstatus);
89 deleteschedule(processsched, &sqlitequeueprocessor, NULL);
90
91 /* we assume every module that's being unloaded
92 * has us as a dependency and will have cleaned up
93 * their queries by using freeid..
94 */
95 for(q=head;q;q=nq) {
96 nq = q->next;
97 sqlite3_finalize(q->statement);
98 nsfree(POOL_SQLITE, q);
99 }
100
ee8cd7d0
CP
101 sqlite3_close(conn);
102
103 dbconnected = 0;
76c8da69 104 nscheckfreeall(POOL_SQLITE);
ee8cd7d0
CP
105}
106
aa944539
CP
107/* busy processing is done externally as that varies depending on what you are... */
108static void processstatement(int rc, sqlite3_stmt *s, SQLiteQueryHandler handler, void *tag, char *querybuf) {
109 if(handler) { /* the handler deals with the cleanup */
110 SQLiteResult *r;
958aff66 111
aa944539
CP
112 if((rc != SQLITE_ROW) && (rc != SQLITE_DONE)) {
113 Error("sqlite", ERR_WARNING, "SQL error %d: %s (query: %s)", rc, sqlite3_errmsg(conn), querybuf);
114 handler(NULL, tag);
115 return;
116 }
958aff66 117
aa944539
CP
118 r = (SQLiteResult *)nsmalloc(POOL_SQLITE, sizeof(SQLiteResult));
119 r->r = s;
120 r->first = 1;
121 if(rc == SQLITE_ROW) {
122 r->final = 0;
123 } else if(rc == SQLITE_DONE) {
124 r->final = 1;
125 }
126 handler(r, tag);
127 } else {
128 if(rc == SQLITE_ROW) {
129 Error("sqlite", ERR_WARNING, "Unhandled data from query: %s", querybuf);
130 } else if(rc != SQLITE_DONE) {
131 Error("sqlite", ERR_WARNING, "SQL error %d: %s (query: %s)", rc, sqlite3_errmsg(conn), querybuf);
958aff66 132 }
aa944539
CP
133 sqlite3_finalize(s);
134 }
135}
136
137static void pushqueue(sqlite3_stmt *s, int identifier, SQLiteQueryHandler handler, void *tag) {
138 struct sqlitequeue *q = (struct sqlitequeue *)nsmalloc(POOL_SQLITE, sizeof(struct sqlitequeue));
139
140 q->identifier = identifier;
141 q->handler = handler;
142 q->tag = tag;
143 q->next = NULL;
144 q->statement = s;
145
146 if(!tail) {
147 head = q;
148 } else {
149 tail->next = q;
150 }
151 tail = q;
152 queuesize++;
153}
154
155static struct sqlitequeue *peekqueue(void) {
156 return head;
157}
158
159/* a weird pop that doesn't return the value */
160static void popqueue(void) {
161 struct sqlitequeue *q;
162 if(!head)
163 return;
164
165 q = head;
166 if(head == tail) {
167 head = NULL;
168 tail = NULL;
169 } else {
170 head = head->next;
958aff66 171 }
aa944539
CP
172
173 nsfree(POOL_SQLITE, q);
174 queuesize--;
175 return;
958aff66 176}
177
ee8cd7d0
CP
178void sqliteasyncqueryf(int identifier, SQLiteQueryHandler handler, void *tag, int flags, char *format, ...) {
179 char querybuf[8192];
180 va_list va;
181 int len;
182 int rc;
183 sqlite3_stmt *s;
958aff66 184
ee8cd7d0
CP
185 if(!sqliteconnected())
186 return;
187
188 va_start(va, format);
189 len = vsnprintf(querybuf, sizeof(querybuf), format, va);
190 va_end(va);
191
192 rc = sqlite3_prepare(conn, querybuf, -1, &s, NULL);
193 if(rc != SQLITE_OK) {
194 if(flags != DB_CREATE)
195 Error("sqlite", ERR_WARNING, "SQL error %d: %s (query: %s)", rc, sqlite3_errmsg(conn), querybuf);
196 if(handler)
197 handler(NULL, tag);
198 return;
199 }
200
aa944539
CP
201 if(head) { /* stuff already queued */
202 pushqueue(s, identifier, handler, tag);
203 return;
204 }
205
206 rc = sqlite3_step(s);
207 if(rc == SQLITE_BUSY) {
208 pushqueue(s, identifier, handler, tag);
209 return;
ee8cd7d0 210 }
aa944539
CP
211
212 processstatement(rc, s, handler, tag, querybuf);
ee8cd7d0
CP
213}
214
215int sqliteconnected(void) {
216 return dbconnected;
217}
218
219void sqliteescapestring(char *buf, char *src, unsigned int len) {
220 unsigned int i;
221 char *p;
222
223 for(p=src,i=0;i<len;i++,p++) {
224 if(*p == '\'')
225 *buf++ = *p;
226 *buf++ = *p;
227 }
228 *buf = '\0';
229}
230
231SQLiteResult *sqlitegetresult(SQLiteConn *r) {
aa944539 232 return r;
ee8cd7d0
CP
233}
234
235int sqlitefetchrow(SQLiteResult *r) {
aa944539
CP
236 int rc, v;
237 struct timespec t;
238
239 if(!r || !r->r || r->final)
240 return 0;
241
242 if(r->first) { /* we've extracted the first row already */
243 r->first = 0;
244 return 1;
245 }
246
247 t.tv_sec = 0;
248 for(;;) {
249 rc = sqlite3_step(r->r);
250 if(rc != SQLITE_BUSY)
251 break;
252
253 v = rand() % 50 + 50;
254 t.tv_nsec = v * 1000000;
255 Error("sqlite", ERR_WARNING, "SQLite is busy, retrying in %fs...", (double)v / 1000);
256 nanosleep(&t, NULL);
257 }
258
259 if(rc == SQLITE_DONE) {
260 r->final = 1;
261 return 0;
262 }
263
264 if(rc != SQLITE_ROW) {
265 Error("sqlite", ERR_WARNING, "SQL error %d: %s", rc, sqlite3_errmsg(conn));
266 r->final = 1;
ee8cd7d0 267 return 0;
aa944539 268 }
ee8cd7d0
CP
269
270 return 1;
271}
272
273void sqliteclear(SQLiteResult *r) {
274 if(!r)
275 return;
276
277 if(r->r)
278 sqlite3_finalize(r->r);
279
76c8da69 280 nsfree(POOL_SQLITE, r);
ee8cd7d0
CP
281}
282
283int sqlitequerysuccessful(SQLiteResult *r) {
284 if(r && r->r)
285 return 1;
286
287 return 0;
288}
289
008e57e5
CP
290struct sqlitetableloader {
291 SQLiteQueryHandler init, data, fini;
292 char tablename[];
293};
ee8cd7d0 294
008e57e5
CP
295static void loadtablerows(SQLiteConn *c, void *tag) {
296 struct sqlitetableloader *t = (struct sqlitetableloader *)tag;
958aff66 297
008e57e5 298 if(!c) { /* pqsql doesnt call the handlers so we don't either */
aa944539 299 nsfree(POOL_SQLITE, t);
ee8cd7d0
CP
300 return;
301 }
302
008e57e5
CP
303 /* the handlers do all the checking and cleanup */
304 if(t->init)
305 (t->init)(c, NULL);
306
307 (t->data)(c, NULL);
308
309 if(t->fini)
310 (t->fini)(c, NULL);
311
aa944539 312 nsfree(POOL_SQLITE, t);
008e57e5
CP
313}
314
315static void loadtablecount(SQLiteConn *c, void *tag) {
316 struct sqlitetableloader *t = (struct sqlitetableloader *)tag;
317 SQLiteResult *r = NULL;
318
319 if(!c) { /* unloaded */
aa944539 320 nsfree(POOL_SQLITE, t);
008e57e5
CP
321 return;
322 }
323
324 if(!(r = sqlitegetresult(c)) || !sqlitefetchrow(r)) {
325 Error("sqlite", ERR_ERROR, "Error getting row count for %s.", t->tablename);
aa944539 326 nsfree(POOL_SQLITE, t);
008e57e5
CP
327
328 if(r)
329 sqliteclear(r);
ee8cd7d0
CP
330 return;
331 }
332
008e57e5
CP
333 Error("sqlite", ERR_INFO, "Found %s entries in table %s, loading...", (char *)sqlite3_column_text(r->r, 0), t->tablename);
334 sqliteclear(r);
335
336 sqliteasyncqueryf(0, loadtablerows, t, 0, "SELECT * FROM %s", t->tablename);
337}
ee8cd7d0 338
008e57e5
CP
339void sqliteloadtable(char *tablename, SQLiteQueryHandler init, SQLiteQueryHandler data, SQLiteQueryHandler fini) {
340 struct sqlitetableloader *t;
341 int len;
ee8cd7d0 342
008e57e5 343 if(!sqliteconnected())
ee8cd7d0 344 return;
ee8cd7d0 345
008e57e5
CP
346 len = strlen(tablename);
347
aa944539 348 t = (struct sqlitetableloader *)nsmalloc(POOL_SQLITE, sizeof(struct sqlitetableloader) + len + 1);
008e57e5
CP
349 memcpy(t->tablename, tablename, len + 1);
350 t->init = init;
351 t->data = data;
352 t->fini = fini;
353
354 sqliteasyncqueryf(0, loadtablecount, t, 0, "SELECT COUNT(*) FROM %s", tablename);
ee8cd7d0 355}
76c8da69 356
aa944539 357void sqliteattach(char *schema) {
76c8da69 358 sqliteasyncqueryf(0, NULL, NULL, 0, "ATTACH DATABASE '%s.db' AS %s", schema, schema);
aa944539
CP
359 sqliteasyncqueryf(0, NULL, NULL, 0, "PRAGMA %s.synchronous=" SYNC_MODE, schema);
360}
361
362void sqlitedetach(char *schema) {
363 sqliteasyncqueryf(0, NULL, NULL, 0, "DETACH DATABASE %s", schema);
76c8da69 364}
008e57e5
CP
365
366int sqlitegetid(void) {
367 modid++;
368 if(modid == 0)
369 modid = 1;
370
371 return modid;
372}
373
374void sqlitefreeid(int id) {
aa944539
CP
375 struct sqlitequeue *q, *pq;
376 if(id == 0)
377 return;
008e57e5 378
aa944539
CP
379 for(pq=NULL,q=head;q;) {
380 if(q->identifier == id) {
381 if(pq) {
382 pq->next = q->next;
383 if(q == tail)
384 tail = pq;
385 } else { /* head */
386 head = q->next;
387 if(q == tail)
388 tail = NULL;
389 }
390 sqlite3_finalize(q->statement);
391
392 q->handler(NULL, q->tag);
393 nsfree(POOL_SQLITE, q);
394
395 queuesize--;
396 if(pq) {
397 q = pq->next;
398 } else {
399 q = head;
400 }
401 } else {
402 pq = q;
403 q = q->next;
404 }
405 }
406}
407
408static void sqlitequeueprocessor(void *arg) {
409 struct sqlitequeue *q = peekqueue();
410
411 while(q) {
412 int rc = sqlite3_step(q->statement);
413 if(rc == SQLITE_BUSY)
414 return;
415
416 processstatement(rc, q->statement, q->handler, q->tag, "??");
417 popqueue();
418
419 q = peekqueue();
420 }
421}
422
423static void dbstatus(int hooknum, void *arg) {
424 if((long)arg > 10) {
425 char message[100];
426
427 snprintf(message, sizeof(message), "SQLite : %6d queries queued.", queuesize);
428 triggerhook(HOOK_CORE_STATSREPLY, message);
429 }
008e57e5
CP
430}
431