]>
Commit | Line | Data |
---|---|---|
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 | |
27 | MODULE_VERSION(""); | |
28 | ||
29 | static int dbconnected = 0; | |
30 | static struct sqlite3 *conn; | |
008e57e5 | 31 | static SQLiteModuleIdentifier modid; |
ee8cd7d0 | 32 | |
aa944539 CP |
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; | |
a0e90625 | 44 | static int inited; |
aa944539 | 45 | |
76c8da69 CP |
46 | #define SYNC_MODE "OFF" |
47 | ||
aa944539 CP |
48 | static void sqlitequeueprocessor(void *arg); |
49 | static void dbstatus(int hooknum, void *arg); | |
50 | ||
ee8cd7d0 CP |
51 | void _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 | ||
93 | void _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... */ |
124 | static 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 | ||
153 | static 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 | ||
171 | static struct sqlitequeue *peekqueue(void) { | |
172 | return head; | |
173 | } | |
174 | ||
175 | /* a weird pop that doesn't return the value */ | |
176 | static 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 | 194 | void 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 | ||
231 | int sqliteconnected(void) { | |
232 | return dbconnected; | |
233 | } | |
234 | ||
404d307c | 235 | size_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 | ||
249 | SQLiteResult *sqlitegetresult(SQLiteConn *r) { | |
aa944539 | 250 | return r; |
ee8cd7d0 CP |
251 | } |
252 | ||
253 | int 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 | ||
291 | void 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 | ||
301 | int sqlitequerysuccessful(SQLiteResult *r) { | |
302 | if(r && r->r) | |
303 | return 1; | |
304 | ||
305 | return 0; | |
306 | } | |
307 | ||
008e57e5 CP |
308 | struct sqlitetableloader { |
309 | SQLiteQueryHandler init, data, fini; | |
91ad51f6 | 310 | void *tag; |
008e57e5 CP |
311 | char tablename[]; |
312 | }; | |
ee8cd7d0 | 313 | |
008e57e5 CP |
314 | static 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 | ||
334 | static 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 | 358 | void 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 | 377 | void 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 | ||
382 | void sqlitedetach(char *schema) { | |
383 | sqliteasyncqueryf(0, NULL, NULL, 0, "DETACH DATABASE %s", schema); | |
76c8da69 | 384 | } |
008e57e5 CP |
385 | |
386 | int sqlitegetid(void) { | |
387 | modid++; | |
388 | if(modid == 0) | |
389 | modid = 1; | |
390 | ||
391 | return modid; | |
392 | } | |
393 | ||
394 | void 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 | ||
428 | static 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 | ||
443 | static 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 |