4 * 99% of the handling is stolen from Q9.
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"
12 #include "../core/nsmalloc.h"
13 #include "../lib/irc_string.h"
14 #include "../lib/version.h"
15 #include "../lib/strlfunc.h"
18 #define BUILDING_DBAPI
19 #include "../dbapi/dbapi.h"
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
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.
40 typedef struct pqasyncquery_s
{
44 PQQueryHandler handler
;
46 PQModuleIdentifier identifier
;
47 struct pqasyncquery_s
*next
;
50 typedef struct pqtableloaderinfo_s
53 PQQueryHandler init
, data
, fini
;
55 } pqtableloaderinfo_s
;
57 pqasyncquery_s
*queryhead
= NULL
, *querytail
= NULL
;
59 static int dbconnected
= 0;
60 static PQModuleIdentifier moduleid
= 0;
61 static PGconn
*dbconn
;
63 void dbhandler(int fd
, short revents
);
64 void pqstartloadtable(PGconn
*dbconn
, void *arg
);
65 void dbstatus(int hooknum
, void *arg
);
66 void disconnectdb(void);
68 char* pqlasterror(PGconn
* pgconn
);
77 nscheckfreeall(POOL_PQSQL
);
80 PQModuleIdentifier
pqgetid(void) {
88 void pqfreeid(PQModuleIdentifier identifier
) {
89 pqasyncquery_s
*q
, *p
;
91 if(identifier
== 0 || !queryhead
)
94 if(queryhead
->identifier
== identifier
) {
95 (queryhead
->handler
)(NULL
, queryhead
->tag
);
96 queryhead
->identifier
= QH_ALREADYFIRED
;
99 for(p
=queryhead
,q
=queryhead
->next
;q
;) {
100 if(q
->identifier
== identifier
) {
101 (q
->handler
)(NULL
, q
->tag
);
105 freesstring(q
->query_ss
);
107 nsfree(POOL_PQSQL
, q
->query
);
109 nsfree(POOL_PQSQL
, q
);
120 void connectdb(void) {
121 sstring
*dbhost
, *dbusername
, *dbpassword
, *dbdatabase
, *dbport
;
122 char connectstr
[1024];
127 /* stolen from chanserv as I'm lazy */
128 dbhost
= getcopyconfigitem("pqsql", "host", "UNIX", HOSTLEN
);
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);
134 if(!dbhost
|| !dbusername
|| !dbpassword
|| !dbdatabase
|| !dbport
) {
135 /* freesstring allows NULL */
137 freesstring(dbusername
);
138 freesstring(dbpassword
);
139 freesstring(dbdatabase
);
144 if (!strcmp(dbhost
->content
,"UNIX")) {
145 snprintf(connectstr
, sizeof(connectstr
), "dbname=%s user=%s password=%s", dbdatabase
->content
, dbusername
->content
, dbpassword
->content
);
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
);
151 freesstring(dbusername
);
152 freesstring(dbpassword
);
153 freesstring(dbdatabase
);
156 Error("pqsql", ERR_INFO
, "Attempting database connection: %s", connectstr
);
158 /* Blocking connect for now.. */
159 dbconn
= PQconnectdb(connectstr
);
161 if (!dbconn
|| (PQstatus(dbconn
) != CONNECTION_OK
)) {
162 Error("pqsql", ERR_ERROR
, "Unable to connect to db: %s", pqlasterror(dbconn
));
165 Error("pqsql", ERR_INFO
, "Connected!");
169 PQsetnonblocking(dbconn
, 1);
171 /* this kicks ass, thanks splidge! */
172 registerhandler(PQsocket(dbconn
), POLLIN
, dbhandler
);
173 registerhook(HOOK_CORE_STATSREQUEST
, dbstatus
);
176 void dbhandler(int fd
, short revents
) {
180 if(revents
& POLLIN
) {
181 PQconsumeInput(dbconn
);
183 if(!PQisBusy(dbconn
)) { /* query is complete */
184 if(queryhead
->handler
&& queryhead
->identifier
!= QH_ALREADYFIRED
)
185 (queryhead
->handler
)(dbconn
, queryhead
->tag
);
187 while((res
= PQgetResult(dbconn
))) {
188 if(queryhead
->identifier
!= QH_ALREADYFIRED
) {
189 switch(PQresultStatus(res
)) {
190 case PGRES_TUPLES_OK
:
191 Error("pqsql", ERR_WARNING
, "Unhandled tuples output (query: %s)", queryhead
->query
);
194 case PGRES_NONFATAL_ERROR
:
195 case PGRES_FATAL_ERROR
:
196 /* if a create query returns an error assume it went ok, paul will winge about this */
197 if(!(queryhead
->flags
& DB_CREATE
))
198 Error("pqsql", ERR_WARNING
, "Unhandled error response (query: %s)", queryhead
->query
);
209 /* Free the query and advance */
211 if(queryhead
== querytail
)
214 queryhead
= queryhead
->next
;
217 freesstring(qqp
->query_ss
);
220 } else if (qqp
->query
) {
221 nsfree(POOL_PQSQL
, qqp
->query
);
224 nsfree(POOL_PQSQL
, qqp
);
226 if(queryhead
) { /* Submit the next query */
227 PQsendQuery(dbconn
, queryhead
->query
);
235 void pqasyncqueryf(int identifier
, PQQueryHandler handler
, void *tag
, int flags
, char *format
, ...) {
244 va_start(va
, format
);
245 len
= vsnprintf(querybuf
, sizeof(querybuf
), format
, va
);
248 /* PPA: no check here... */
249 qp
= (pqasyncquery_s
*)nsmalloc(POOL_PQSQL
, sizeof(pqasyncquery_s
));
252 Error("pqsql",ERR_STOP
,"malloc() failed in pqsql.c");
254 /* Use sstring or allocate (see above rant) */
255 if (len
> SSTRING_MAX
) {
256 qp
->query
= (char *)nsmalloc(POOL_PQSQL
, len
+1);
257 strcpy(qp
->query
,querybuf
);
260 qp
->query_ss
= getsstring(querybuf
, len
);
261 qp
->query
= qp
->query_ss
->content
;
264 qp
->handler
= handler
;
265 qp
->next
= NULL
; /* shove them at the end */
267 qp
->identifier
= identifier
;
270 querytail
->next
= qp
;
273 querytail
= queryhead
= qp
;
274 PQsendQuery(dbconn
, qp
->query
);
279 void pqloadtable(char *tablename
, PQQueryHandler init
, PQQueryHandler data
, PQQueryHandler fini
, void *tag
)
281 pqtableloaderinfo_s
*tli
;
283 tli
=(pqtableloaderinfo_s
*)nsmalloc(POOL_PQSQL
, sizeof(pqtableloaderinfo_s
));
284 tli
->tablename
=getsstring(tablename
, 100);
289 pqasyncquery(pqstartloadtable
, tli
, "SELECT COUNT(*) FROM %s", tli
->tablename
->content
);
292 void pqstartloadtable(PGconn
*dbconn
, void *arg
)
295 unsigned long i
, count
, tablecrc
;
296 pqtableloaderinfo_s
*tli
= arg
;
298 res
= PQgetResult(dbconn
);
300 if (PQresultStatus(res
) != PGRES_TUPLES_OK
&& PQresultStatus(res
) != PGRES_COMMAND_OK
) {
301 Error("pqsql", ERR_ERROR
, "Error getting row count for %s.", tli
->tablename
->content
);
305 if (PQnfields(res
) != 1) {
306 Error("pqsql", ERR_ERROR
, "Count query format error for %s.", tli
->tablename
->content
);
310 tablecrc
=crc32(tli
->tablename
->content
);
311 count
=strtoul(PQgetvalue(res
, 0, 0), NULL
, 10);
314 Error("pqsql", ERR_INFO
, "Found %lu entries in table %s, scheduling load.", count
, tli
->tablename
->content
);
316 pqasyncquery(tli
->init
, tli
->tag
, "BEGIN");
317 pqasyncquery(NULL
, NULL
, "DECLARE table%lx%lx CURSOR FOR SELECT * FROM %s", tablecrc
, count
, tli
->tablename
->content
);
319 for (i
=0;(count
- i
) > 1000; i
+=1000)
320 pqasyncquery(tli
->data
, tli
->tag
, "FETCH 1000 FROM table%lx%lx", tablecrc
, count
);
322 pqasyncquery(tli
->data
, tli
->tag
, "FETCH ALL FROM table%lx%lx", tablecrc
, count
);
324 pqasyncquery(NULL
, NULL
, "CLOSE table%lx%lx", tablecrc
, count
);
325 pqasyncquery(tli
->fini
, tli
->tag
, "COMMIT");
327 freesstring(tli
->tablename
);
328 nsfree(POOL_PQSQL
, tli
);
331 void disconnectdb(void) {
332 pqasyncquery_s
*qqp
= queryhead
, *nqqp
;
337 /* do this first else we may get conflicts */
338 deregisterhandler(PQsocket(dbconn
), 0);
340 /* Throw all the queued queries away, beware of data malloc()ed inside the query item.. */
344 freesstring(qqp
->query_ss
);
347 } else if (qqp
->query
) {
348 nsfree(POOL_PQSQL
, qqp
->query
);
351 nsfree(POOL_PQSQL
, qqp
);
355 deregisterhook(HOOK_CORE_STATSREQUEST
, dbstatus
);
357 dbconn
= NULL
; /* hmm? */
362 /* more stolen code from Q9 */
363 void dbstatus(int hooknum
, void *arg
) {
364 if ((long)arg
> 10) {
370 for(qqp
=queryhead
;qqp
;qqp
=qqp
->next
)
373 snprintf(message
, sizeof(message
), "PQSQL : %6d queries queued.",i
);
375 triggerhook(HOOK_CORE_STATSREPLY
, message
);
379 int pqconnected(void) {
383 char* pqlasterror(PGconn
* pgconn
) {
384 static char errormsg
[PQ_ERRORMSG_LENGTH
+ 1];
387 return "PGCONN NULL";
388 strlcpy(errormsg
, PQerrorMessage(pgconn
), PQ_ERRORMSG_LENGTH
);
389 for(i
=0;i
<errormsg
[i
];i
++) {
390 if((errormsg
[i
] == '\r') || (errormsg
[i
] == '\n'))
397 PQResult
*pqgetresult(PGconn
*c
) {
402 r
= (PQResult
*)nsmalloc(POOL_PQSQL
, sizeof(PQResult
));
404 r
->result
= PQgetResult(c
);
405 r
->rows
= PQntuples(r
->result
);
410 int pqfetchrow(PQResult
*res
) {
411 if(res
->row
+ 1 == res
->rows
)
419 char *pqgetvalue(PQResult
*res
, int column
) {
420 return PQgetvalue(res
->result
, res
->row
, column
);
423 void pqclear(PQResult
*res
) {
428 PQclear(res
->result
);
430 nsfree(POOL_PQSQL
, res
);