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