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