]> jfr.im git - irc/quakenet/newserv.git/blob - sqlite/sqlite.c
merge
[irc/quakenet/newserv.git] / sqlite / sqlite.c
1 /*
2 * SQLite module
3 */
4
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
13 #include "../core/config.h"
14 #include "../core/error.h"
15 #include "../core/events.h"
16 #include "../core/hooks.h"
17 #include "../lib/version.h"
18 #include "../lib/strlfunc.h"
19 #include "../core/nsmalloc.h"
20 #include "../core/schedule.h"
21
22 #define BUILDING_DBAPI
23 #include "../dbapi/dbapi.h"
24
25 #include "sqlite.h"
26
27 MODULE_VERSION("");
28
29 static int dbconnected = 0;
30 static struct sqlite3 *conn;
31 static SQLiteModuleIdentifier modid;
32
33 struct sqlitequeue {
34 sqlite3_stmt *statement;
35 SQLiteQueryHandler handler;
36 void *tag;
37 int identifier;
38 struct sqlitequeue *next;
39 };
40
41 static struct sqlitequeue *head, *tail;
42 static int queuesize;
43 static void *processsched;
44
45 #define SYNC_MODE "OFF"
46
47 static void sqlitequeueprocessor(void *arg);
48 static void dbstatus(int hooknum, void *arg);
49
50 void _init(void) {
51 sstring *dbfile;
52 int rc;
53
54 dbfile = getcopyconfigitem("sqlite", "file", "newserv.db", 100);
55
56 if(!dbfile) {
57 Error("sqlite", ERR_ERROR, "Unable to get config settings.");
58 return;
59 }
60
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
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));
73 deleteschedule(processsched, &sqlitequeueprocessor, NULL);
74 return;
75 }
76
77 dbconnected = 1;
78
79 sqliteasyncqueryf(0, NULL, NULL, 0, "PRAGMA synchronous=" SYNC_MODE ";");
80 registerhook(HOOK_CORE_STATSREQUEST, dbstatus);
81 }
82
83 void _fini(void) {
84 struct sqlitequeue *q, *nq;
85 if(!sqliteconnected())
86 return;
87
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
101 sqlite3_close(conn);
102
103 dbconnected = 0;
104 nscheckfreeall(POOL_SQLITE);
105 }
106
107 /* busy processing is done externally as that varies depending on what you are... */
108 static 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;
111
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 }
117
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);
132 }
133 sqlite3_finalize(s);
134 }
135 }
136
137 static 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
155 static struct sqlitequeue *peekqueue(void) {
156 return head;
157 }
158
159 /* a weird pop that doesn't return the value */
160 static 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;
171 }
172
173 nsfree(POOL_SQLITE, q);
174 queuesize--;
175 return;
176 }
177
178 void 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;
184
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
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;
210 }
211
212 processstatement(rc, s, handler, tag, querybuf);
213 }
214
215 int sqliteconnected(void) {
216 return dbconnected;
217 }
218
219 void 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
231 SQLiteResult *sqlitegetresult(SQLiteConn *r) {
232 return r;
233 }
234
235 int sqlitefetchrow(SQLiteResult *r) {
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;
267 return 0;
268 }
269
270 return 1;
271 }
272
273 void sqliteclear(SQLiteResult *r) {
274 if(!r)
275 return;
276
277 if(r->r)
278 sqlite3_finalize(r->r);
279
280 nsfree(POOL_SQLITE, r);
281 }
282
283 int sqlitequerysuccessful(SQLiteResult *r) {
284 if(r && r->r)
285 return 1;
286
287 return 0;
288 }
289
290 struct sqlitetableloader {
291 SQLiteQueryHandler init, data, fini;
292 char tablename[];
293 };
294
295 static void loadtablerows(SQLiteConn *c, void *tag) {
296 struct sqlitetableloader *t = (struct sqlitetableloader *)tag;
297
298 if(!c) { /* pqsql doesnt call the handlers so we don't either */
299 nsfree(POOL_SQLITE, t);
300 return;
301 }
302
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
312 nsfree(POOL_SQLITE, t);
313 }
314
315 static void loadtablecount(SQLiteConn *c, void *tag) {
316 struct sqlitetableloader *t = (struct sqlitetableloader *)tag;
317 SQLiteResult *r = NULL;
318
319 if(!c) { /* unloaded */
320 nsfree(POOL_SQLITE, t);
321 return;
322 }
323
324 if(!(r = sqlitegetresult(c)) || !sqlitefetchrow(r)) {
325 Error("sqlite", ERR_ERROR, "Error getting row count for %s.", t->tablename);
326 nsfree(POOL_SQLITE, t);
327
328 if(r)
329 sqliteclear(r);
330 return;
331 }
332
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 }
338
339 void sqliteloadtable(char *tablename, SQLiteQueryHandler init, SQLiteQueryHandler data, SQLiteQueryHandler fini) {
340 struct sqlitetableloader *t;
341 int len;
342
343 if(!sqliteconnected())
344 return;
345
346 len = strlen(tablename);
347
348 t = (struct sqlitetableloader *)nsmalloc(POOL_SQLITE, sizeof(struct sqlitetableloader) + len + 1);
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);
355 }
356
357 void sqliteattach(char *schema) {
358 sqliteasyncqueryf(0, NULL, NULL, 0, "ATTACH DATABASE '%s.db' AS %s", schema, schema);
359 sqliteasyncqueryf(0, NULL, NULL, 0, "PRAGMA %s.synchronous=" SYNC_MODE, schema);
360 }
361
362 void sqlitedetach(char *schema) {
363 sqliteasyncqueryf(0, NULL, NULL, 0, "DETACH DATABASE %s", schema);
364 }
365
366 int sqlitegetid(void) {
367 modid++;
368 if(modid == 0)
369 modid = 1;
370
371 return modid;
372 }
373
374 void sqlitefreeid(int id) {
375 struct sqlitequeue *q, *pq;
376 if(id == 0)
377 return;
378
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
408 static 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
423 static 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 }
430 }
431