]> jfr.im git - irc/quakenet/newserv.git/blame - pqsql/pqsql.c
BUILD: add require-all build mode
[irc/quakenet/newserv.git] / pqsql / pqsql.c
CommitLineData
e43481af
CP
1/*
2 * PQSQL module
3 *
4 * 99% of the handling is stolen from Q9.
5 */
6
7#include "../core/config.h"
8#include "../core/error.h"
9#include "../irc/irc_config.h"
10#include "../core/events.h"
11#include "../core/hooks.h"
76c8da69 12#include "../core/nsmalloc.h"
c5373bb8 13#include "../lib/irc_string.h"
87698d77 14#include "../lib/version.h"
a321474b 15#include "../lib/strlfunc.h"
e43481af
CP
16#include "pqsql.h"
17
827cbcd7
CP
18#define BUILDING_DBAPI
19#include "../dbapi/dbapi.h"
20
e43481af
CP
21#include <stdlib.h>
22#include <sys/poll.h>
23#include <stdarg.h>
860fc324 24#include <string.h>
e43481af 25
70b0a4e5 26MODULE_VERSION("");
87698d77 27
59b7d3c2 28/* It's possible that we might want to do a very long query, longer than the
29 * IRC-oriented SSTRING_MAX value. One option would be to increase
30 * SSTRING_MAX, but the whole purpose of sstring's is to efficiently deal
31 * with situations where the malloc() padding overhead is large compared to
32 * string length and strings are frequently recycled. Since neither of
33 * these are necessarily true for longer strings it makes more sense to use
34 * malloc() for them.
35 *
36 * So, query always points at the query string. If it fitted in a sstring,
37 * query_ss will point at the sstring for freeing purposes. If query_ss is
38 * NULL then it was malloc'd so should be free()'d directly.
39 */
e43481af 40typedef struct pqasyncquery_s {
59b7d3c2 41 sstring *query_ss;
42 char *query;
e43481af
CP
43 void *tag;
44 PQQueryHandler handler;
67545367 45 int flags;
332870cd 46 PQModuleIdentifier identifier;
e43481af
CP
47 struct pqasyncquery_s *next;
48} pqasyncquery_s;
49
c5373bb8
C
50typedef struct pqtableloaderinfo_s
51{
52 sstring *tablename;
53 PQQueryHandler init, data, fini;
91ad51f6 54 void *tag;
c5373bb8
C
55} pqtableloaderinfo_s;
56
e43481af
CP
57pqasyncquery_s *queryhead = NULL, *querytail = NULL;
58
ee8cd7d0 59static int dbconnected = 0;
332870cd 60static PQModuleIdentifier moduleid = 0;
ee8cd7d0 61static PGconn *dbconn;
e43481af
CP
62
63void dbhandler(int fd, short revents);
c5373bb8 64void pqstartloadtable(PGconn *dbconn, void *arg);
e43481af
CP
65void dbstatus(int hooknum, void *arg);
66void disconnectdb(void);
67void connectdb(void);
ee8cd7d0 68char* pqlasterror(PGconn * pgconn);
e43481af
CP
69
70void _init(void) {
71 connectdb();
72}
73
74void _fini(void) {
75 disconnectdb();
76c8da69
CP
76
77 nscheckfreeall(POOL_PQSQL);
e43481af
CP
78}
79
332870cd
CP
80PQModuleIdentifier pqgetid(void) {
81 moduleid++;
82 if(moduleid < 10)
83 moduleid = 10;
84
85 return moduleid;
86}
87
88void pqfreeid(PQModuleIdentifier identifier) {
89 pqasyncquery_s *q, *p;
90
91 if(identifier == 0 || !queryhead)
92 return;
93
94 if(queryhead->identifier == identifier) {
95 (queryhead->handler)(NULL, queryhead->tag);
96 queryhead->identifier = QH_ALREADYFIRED;
97 }
98
99 for(p=queryhead,q=queryhead->next;q;) {
100 if(q->identifier == identifier) {
101 (q->handler)(NULL, q->tag);
102 p->next = q->next;
103
104 if (q->query_ss) {
105 freesstring(q->query_ss);
106 } else {
76c8da69 107 nsfree(POOL_PQSQL, q->query);
332870cd 108 }
76c8da69 109 nsfree(POOL_PQSQL, q);
332870cd
CP
110 q = p->next;
111 } else {
112 p = q;
113 q = q->next;
114 }
115 }
116
117 querytail = p;
118}
119
e43481af
CP
120void connectdb(void) {
121 sstring *dbhost, *dbusername, *dbpassword, *dbdatabase, *dbport;
122 char connectstr[1024];
123
124 if(pqconnected())
125 return;
126
127 /* stolen from chanserv as I'm lazy */
a321474b 128 dbhost = getcopyconfigitem("pqsql", "host", "UNIX", HOSTLEN);
e43481af
CP
129 dbusername = getcopyconfigitem("pqsql", "username", "newserv", 20);
130 dbpassword = getcopyconfigitem("pqsql", "password", "moo", 20);
131 dbdatabase = getcopyconfigitem("pqsql", "database", "newserv", 20);
132 dbport = getcopyconfigitem("pqsql", "port", "431", 8);
133
134 if(!dbhost || !dbusername || !dbpassword || !dbdatabase || !dbport) {
135 /* freesstring allows NULL */
136 freesstring(dbhost);
137 freesstring(dbusername);
138 freesstring(dbpassword);
139 freesstring(dbdatabase);
140 freesstring(dbport);
141 return;
142 }
e43481af 143
a321474b 144 if (!strcmp(dbhost->content,"UNIX")) {
145 snprintf(connectstr, sizeof(connectstr), "dbname=%s user=%s password=%s", dbdatabase->content, dbusername->content, dbpassword->content);
146 } else {
147 snprintf(connectstr, sizeof(connectstr), "host=%s port=%s dbname=%s user=%s password=%s", dbhost->content, dbport->content, dbdatabase->content, dbusername->content, dbpassword->content);
148 }
149
e43481af
CP
150 freesstring(dbhost);
151 freesstring(dbusername);
152 freesstring(dbpassword);
153 freesstring(dbdatabase);
154 freesstring(dbport);
155
156 Error("pqsql", ERR_INFO, "Attempting database connection: %s", connectstr);
157
158 /* Blocking connect for now.. */
159 dbconn = PQconnectdb(connectstr);
160
7cc1a026 161 if (!dbconn || (PQstatus(dbconn) != CONNECTION_OK)) {
47d8702b 162 Error("pqsql", ERR_ERROR, "Unable to connect to db: %s", pqlasterror(dbconn));
e43481af 163 return;
7cc1a026 164 }
4f981d28
P
165 Error("pqsql", ERR_INFO, "Connected!");
166
e43481af
CP
167 dbconnected = 1;
168
169 PQsetnonblocking(dbconn, 1);
170
171 /* this kicks ass, thanks splidge! */
172 registerhandler(PQsocket(dbconn), POLLIN, dbhandler);
173 registerhook(HOOK_CORE_STATSREQUEST, dbstatus);
174}
175
176void dbhandler(int fd, short revents) {
177 PGresult *res;
178 pqasyncquery_s *qqp;
179
180 if(revents & POLLIN) {
181 PQconsumeInput(dbconn);
182
183 if(!PQisBusy(dbconn)) { /* query is complete */
332870cd 184 if(queryhead->handler && queryhead->identifier != QH_ALREADYFIRED)
e43481af
CP
185 (queryhead->handler)(dbconn, queryhead->tag);
186
187 while((res = PQgetResult(dbconn))) {
332870cd
CP
188 if(queryhead->identifier != QH_ALREADYFIRED) {
189 switch(PQresultStatus(res)) {
190 case PGRES_TUPLES_OK:
4d47ff97
CP
191 if(!(queryhead->flags & DB_CALL))
192 Error("pqsql", ERR_WARNING, "Unhandled tuples output (query: %s)", queryhead->query);
332870cd
CP
193 break;
194
195 case PGRES_NONFATAL_ERROR:
196 case PGRES_FATAL_ERROR:
197 /* if a create query returns an error assume it went ok, paul will winge about this */
ee8cd7d0 198 if(!(queryhead->flags & DB_CREATE))
a4699621 199 Error("pqsql", ERR_WARNING, "Unhandled error response (query: %s): %s", queryhead->query, PQresultErrorMessage(res));
332870cd 200 break;
e43481af 201
332870cd
CP
202 default:
203 break;
204 }
e43481af
CP
205 }
206
207 PQclear(res);
208 }
209
210 /* Free the query and advance */
211 qqp = queryhead;
212 if(queryhead == querytail)
213 querytail = NULL;
214
215 queryhead = queryhead->next;
216
59b7d3c2 217 if (qqp->query_ss) {
218 freesstring(qqp->query_ss);
219 qqp->query_ss=NULL;
220 qqp->query=NULL;
221 } else if (qqp->query) {
76c8da69 222 nsfree(POOL_PQSQL, qqp->query);
59b7d3c2 223 qqp->query=NULL;
224 }
76c8da69 225 nsfree(POOL_PQSQL, qqp);
e43481af
CP
226
227 if(queryhead) { /* Submit the next query */
860fc324 228 PQsendQuery(dbconn, queryhead->query);
e43481af
CP
229 PQflush(dbconn);
230 }
231 }
232 }
233}
234
235/* sorry Q9 */
24e1aba8 236void pqasyncqueryf(int identifier, PQQueryHandler handler, void *tag, int flags, char *format, ...) {
e43481af 237 char querybuf[8192];
e43481af
CP
238 int len;
239 pqasyncquery_s *qp;
24e1aba8 240 va_list va;
e43481af
CP
241
242 if(!pqconnected())
243 return;
244
24e1aba8 245 va_start(va, format);
e43481af 246 len = vsnprintf(querybuf, sizeof(querybuf), format, va);
24e1aba8 247 va_end(va);
e43481af
CP
248
249 /* PPA: no check here... */
76c8da69 250 qp = (pqasyncquery_s *)nsmalloc(POOL_PQSQL, sizeof(pqasyncquery_s));
59b7d3c2 251
67545367 252 if(!qp)
59b7d3c2 253 Error("pqsql",ERR_STOP,"malloc() failed in pqsql.c");
67545367 254
59b7d3c2 255 /* Use sstring or allocate (see above rant) */
256 if (len > SSTRING_MAX) {
76c8da69 257 qp->query = (char *)nsmalloc(POOL_PQSQL, len+1);
59b7d3c2 258 strcpy(qp->query,querybuf);
259 qp->query_ss=NULL;
260 } else {
261 qp->query_ss = getsstring(querybuf, len);
262 qp->query = qp->query_ss->content;
263 }
e43481af
CP
264 qp->tag = tag;
265 qp->handler = handler;
266 qp->next = NULL; /* shove them at the end */
67545367 267 qp->flags = flags;
332870cd 268 qp->identifier = identifier;
e43481af
CP
269
270 if(querytail) {
271 querytail->next = qp;
272 querytail = qp;
273 } else {
274 querytail = queryhead = qp;
860fc324 275 PQsendQuery(dbconn, qp->query);
e43481af
CP
276 PQflush(dbconn);
277 }
278}
279
91ad51f6 280void pqloadtable(char *tablename, PQQueryHandler init, PQQueryHandler data, PQQueryHandler fini, void *tag)
c5373bb8
C
281{
282 pqtableloaderinfo_s *tli;
283
76c8da69 284 tli=(pqtableloaderinfo_s *)nsmalloc(POOL_PQSQL, sizeof(pqtableloaderinfo_s));
c5373bb8
C
285 tli->tablename=getsstring(tablename, 100);
286 tli->init=init;
287 tli->data=data;
288 tli->fini=fini;
91ad51f6 289 tli->tag=tag;
c5373bb8
C
290 pqasyncquery(pqstartloadtable, tli, "SELECT COUNT(*) FROM %s", tli->tablename->content);
291}
292
293void pqstartloadtable(PGconn *dbconn, void *arg)
294{
295 PGresult *res;
296 unsigned long i, count, tablecrc;
297 pqtableloaderinfo_s *tli = arg;
298
299 res = PQgetResult(dbconn);
300
301 if (PQresultStatus(res) != PGRES_TUPLES_OK && PQresultStatus(res) != PGRES_COMMAND_OK) {
302 Error("pqsql", ERR_ERROR, "Error getting row count for %s.", tli->tablename->content);
303 return;
304 }
305
306 if (PQnfields(res) != 1) {
307 Error("pqsql", ERR_ERROR, "Count query format error for %s.", tli->tablename->content);
308 return;
309 }
310
729c4971 311 tablecrc=irc_crc32(tli->tablename->content);
c5373bb8
C
312 count=strtoul(PQgetvalue(res, 0, 0), NULL, 10);
313 PQclear(res);
314
315 Error("pqsql", ERR_INFO, "Found %lu entries in table %s, scheduling load.", count, tli->tablename->content);
316
91ad51f6 317 pqasyncquery(tli->init, tli->tag, "BEGIN");
c5373bb8
C
318 pqasyncquery(NULL, NULL, "DECLARE table%lx%lx CURSOR FOR SELECT * FROM %s", tablecrc, count, tli->tablename->content);
319
320 for (i=0;(count - i) > 1000; i+=1000)
91ad51f6 321 pqasyncquery(tli->data, tli->tag, "FETCH 1000 FROM table%lx%lx", tablecrc, count);
c5373bb8 322
91ad51f6 323 pqasyncquery(tli->data, tli->tag, "FETCH ALL FROM table%lx%lx", tablecrc, count);
c5373bb8
C
324
325 pqasyncquery(NULL, NULL, "CLOSE table%lx%lx", tablecrc, count);
91ad51f6 326 pqasyncquery(tli->fini, tli->tag, "COMMIT");
c5373bb8
C
327
328 freesstring(tli->tablename);
76c8da69 329 nsfree(POOL_PQSQL, tli);
c5373bb8
C
330}
331
e43481af
CP
332void disconnectdb(void) {
333 pqasyncquery_s *qqp = queryhead, *nqqp;
334
335 if(!pqconnected())
336 return;
337
338 /* do this first else we may get conflicts */
339 deregisterhandler(PQsocket(dbconn), 0);
340
341 /* Throw all the queued queries away, beware of data malloc()ed inside the query item.. */
342 while(qqp) {
343 nqqp = qqp->next;
59b7d3c2 344 if (qqp->query_ss) {
860fc324 345 freesstring(qqp->query_ss);
59b7d3c2 346 qqp->query_ss=NULL;
347 qqp->query=NULL;
348 } else if (qqp->query) {
76c8da69 349 nsfree(POOL_PQSQL, qqp->query);
59b7d3c2 350 qqp->query=NULL;
351 }
76c8da69 352 nsfree(POOL_PQSQL, qqp);
e43481af
CP
353 qqp = nqqp;
354 }
355
356 deregisterhook(HOOK_CORE_STATSREQUEST, dbstatus);
357 PQfinish(dbconn);
358 dbconn = NULL; /* hmm? */
359
360 dbconnected = 0;
361}
362
363/* more stolen code from Q9 */
364void dbstatus(int hooknum, void *arg) {
c3db6f7e 365 if ((long)arg > 10) {
e43481af
CP
366 int i = 0;
367 pqasyncquery_s *qqp;
368 char message[100];
369
370 if(queryhead)
371 for(qqp=queryhead;qqp;qqp=qqp->next)
372 i++;
373
6b5b3c37 374 snprintf(message, sizeof(message), "PQSQL : %6d queries queued.",i);
e43481af
CP
375
376 triggerhook(HOOK_CORE_STATSREPLY, message);
377 }
378}
379
380int pqconnected(void) {
381 return dbconnected;
382}
47d8702b 383
1e5a45f5
P
384char* pqlasterror(PGconn * pgconn) {
385 static char errormsg[PQ_ERRORMSG_LENGTH + 1];
47d8702b 386 int i;
1e5a45f5
P
387 if(!pgconn)
388 return "PGCONN NULL";
389 strlcpy(errormsg, PQerrorMessage(pgconn), PQ_ERRORMSG_LENGTH);
390 for(i=0;i<errormsg[i];i++) {
391 if((errormsg[i] == '\r') || (errormsg[i] == '\n'))
47d8702b 392 errormsg[i] = ' ';
1e5a45f5 393
47d8702b
P
394 }
395 return errormsg;
396}
ee8cd7d0
CP
397
398PQResult *pqgetresult(PGconn *c) {
399 PQResult *r;
400 if(!c)
401 return NULL;
402
76c8da69 403 r = (PQResult *)nsmalloc(POOL_PQSQL, sizeof(PQResult));
ee8cd7d0
CP
404 r->row = -1;
405 r->result = PQgetResult(c);
406 r->rows = PQntuples(r->result);
407
408 return r;
409}
410
411int pqfetchrow(PQResult *res) {
412 if(res->row + 1 == res->rows)
413 return 0;
414
415 res->row++;
416
417 return 1;
418}
419
420char *pqgetvalue(PQResult *res, int column) {
421 return PQgetvalue(res->result, res->row, column);
422}
423
424void pqclear(PQResult *res) {
425 if(!res)
426 return;
427
428 if(res->result)
429 PQclear(res->result);
430
76c8da69 431 nsfree(POOL_PQSQL, res);
ee8cd7d0 432}