]>
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; | |
44 | ||
76c8da69 CP |
45 | #define SYNC_MODE "OFF" |
46 | ||
aa944539 CP |
47 | static void sqlitequeueprocessor(void *arg); |
48 | static void dbstatus(int hooknum, void *arg); | |
49 | ||
ee8cd7d0 CP |
50 | void _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 | ||
83 | void _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... */ |
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; | |
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 | ||
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; | |
958aff66 | 171 | } |
aa944539 CP |
172 | |
173 | nsfree(POOL_SQLITE, q); | |
174 | queuesize--; | |
175 | return; | |
958aff66 | 176 | } |
177 | ||
ee8cd7d0 CP |
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; | |
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 | ||
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) { | |
aa944539 | 232 | return r; |
ee8cd7d0 CP |
233 | } |
234 | ||
235 | int 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 | ||
273 | void 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 | ||
283 | int sqlitequerysuccessful(SQLiteResult *r) { | |
284 | if(r && r->r) | |
285 | return 1; | |
286 | ||
287 | return 0; | |
288 | } | |
289 | ||
008e57e5 CP |
290 | struct sqlitetableloader { |
291 | SQLiteQueryHandler init, data, fini; | |
292 | char tablename[]; | |
293 | }; | |
ee8cd7d0 | 294 | |
008e57e5 CP |
295 | static 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 | ||
315 | static 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 |
339 | void 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 | 357 | void 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 | ||
362 | void sqlitedetach(char *schema) { | |
363 | sqliteasyncqueryf(0, NULL, NULL, 0, "DETACH DATABASE %s", schema); | |
76c8da69 | 364 | } |
008e57e5 CP |
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) { | |
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 | ||
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 | } | |
008e57e5 CP |
430 | } |
431 |