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
;
54 } pqtableloaderinfo_s
;
56 pqasyncquery_s
*queryhead
= NULL
, *querytail
= NULL
;
58 static int dbconnected
= 0;
59 static PQModuleIdentifier moduleid
= 0;
60 static PGconn
*dbconn
;
62 void dbhandler(int fd
, short revents
);
63 void pqstartloadtable(PGconn
*dbconn
, void *arg
);
64 void dbstatus(int hooknum
, void *arg
);
65 void disconnectdb(void);
67 char* pqlasterror(PGconn
* pgconn
);
76 nscheckfreeall(POOL_PQSQL
);
79 PQModuleIdentifier
pqgetid(void) {
87 void pqfreeid(PQModuleIdentifier identifier
) {
88 pqasyncquery_s
*q
, *p
;
90 if(identifier
== 0 || !queryhead
)
93 if(queryhead
->identifier
== identifier
) {
94 (queryhead
->handler
)(NULL
, queryhead
->tag
);
95 queryhead
->identifier
= QH_ALREADYFIRED
;
98 for(p
=queryhead
,q
=queryhead
->next
;q
;) {
99 if(q
->identifier
== identifier
) {
100 (q
->handler
)(NULL
, q
->tag
);
104 freesstring(q
->query_ss
);
106 nsfree(POOL_PQSQL
, q
->query
);
108 nsfree(POOL_PQSQL
, q
);
119 void connectdb(void) {
120 sstring
*dbhost
, *dbusername
, *dbpassword
, *dbdatabase
, *dbport
;
121 char connectstr
[1024];
126 /* stolen from chanserv as I'm lazy */
127 dbhost
= getcopyconfigitem("pqsql", "host", "UNIX", HOSTLEN
);
128 dbusername
= getcopyconfigitem("pqsql", "username", "newserv", 20);
129 dbpassword
= getcopyconfigitem("pqsql", "password", "moo", 20);
130 dbdatabase
= getcopyconfigitem("pqsql", "database", "newserv", 20);
131 dbport
= getcopyconfigitem("pqsql", "port", "431", 8);
133 if(!dbhost
|| !dbusername
|| !dbpassword
|| !dbdatabase
|| !dbport
) {
134 /* freesstring allows NULL */
136 freesstring(dbusername
);
137 freesstring(dbpassword
);
138 freesstring(dbdatabase
);
143 if (!strcmp(dbhost
->content
,"UNIX")) {
144 snprintf(connectstr
, sizeof(connectstr
), "dbname=%s user=%s password=%s", dbdatabase
->content
, dbusername
->content
, dbpassword
->content
);
146 snprintf(connectstr
, sizeof(connectstr
), "host=%s port=%s dbname=%s user=%s password=%s", dbhost
->content
, dbport
->content
, dbdatabase
->content
, dbusername
->content
, dbpassword
->content
);
150 freesstring(dbusername
);
151 freesstring(dbpassword
);
152 freesstring(dbdatabase
);
155 Error("pqsql", ERR_INFO
, "Attempting database connection: %s", connectstr
);
157 /* Blocking connect for now.. */
158 dbconn
= PQconnectdb(connectstr
);
160 if (!dbconn
|| (PQstatus(dbconn
) != CONNECTION_OK
)) {
161 Error("pqsql", ERR_ERROR
, "Unable to connect to db: %s", pqlasterror(dbconn
));
164 Error("pqsql", ERR_INFO
, "Connected!");
168 PQsetnonblocking(dbconn
, 1);
170 /* this kicks ass, thanks splidge! */
171 registerhandler(PQsocket(dbconn
), POLLIN
, dbhandler
);
172 registerhook(HOOK_CORE_STATSREQUEST
, dbstatus
);
175 void dbhandler(int fd
, short revents
) {
179 if(revents
& POLLIN
) {
180 PQconsumeInput(dbconn
);
182 if(!PQisBusy(dbconn
)) { /* query is complete */
183 if(queryhead
->handler
&& queryhead
->identifier
!= QH_ALREADYFIRED
)
184 (queryhead
->handler
)(dbconn
, queryhead
->tag
);
186 while((res
= PQgetResult(dbconn
))) {
187 if(queryhead
->identifier
!= QH_ALREADYFIRED
) {
188 switch(PQresultStatus(res
)) {
189 case PGRES_TUPLES_OK
:
190 Error("pqsql", ERR_WARNING
, "Unhandled tuples output (query: %s)", queryhead
->query
);
193 case PGRES_NONFATAL_ERROR
:
194 case PGRES_FATAL_ERROR
:
195 /* if a create query returns an error assume it went ok, paul will winge about this */
196 if(!(queryhead
->flags
& DB_CREATE
))
197 Error("pqsql", ERR_WARNING
, "Unhandled error response (query: %s)", queryhead
->query
);
208 /* Free the query and advance */
210 if(queryhead
== querytail
)
213 queryhead
= queryhead
->next
;
216 freesstring(qqp
->query_ss
);
219 } else if (qqp
->query
) {
220 nsfree(POOL_PQSQL
, qqp
->query
);
223 nsfree(POOL_PQSQL
, qqp
);
225 if(queryhead
) { /* Submit the next query */
226 PQsendQuery(dbconn
, queryhead
->query
);
234 void pqasyncqueryf(int identifier
, PQQueryHandler handler
, void *tag
, int flags
, char *format
, ...) {
243 va_start(va
, format
);
244 len
= vsnprintf(querybuf
, sizeof(querybuf
), format
, va
);
247 /* PPA: no check here... */
248 qp
= (pqasyncquery_s
*)nsmalloc(POOL_PQSQL
, sizeof(pqasyncquery_s
));
251 Error("pqsql",ERR_STOP
,"malloc() failed in pqsql.c");
253 /* Use sstring or allocate (see above rant) */
254 if (len
> SSTRING_MAX
) {
255 qp
->query
= (char *)nsmalloc(POOL_PQSQL
, len
+1);
256 strcpy(qp
->query
,querybuf
);
259 qp
->query_ss
= getsstring(querybuf
, len
);
260 qp
->query
= qp
->query_ss
->content
;
263 qp
->handler
= handler
;
264 qp
->next
= NULL
; /* shove them at the end */
266 qp
->identifier
= identifier
;
269 querytail
->next
= qp
;
272 querytail
= queryhead
= qp
;
273 PQsendQuery(dbconn
, qp
->query
);
278 void pqloadtable(char *tablename
, PQQueryHandler init
, PQQueryHandler data
, PQQueryHandler fini
)
280 pqtableloaderinfo_s
*tli
;
282 tli
=(pqtableloaderinfo_s
*)nsmalloc(POOL_PQSQL
, sizeof(pqtableloaderinfo_s
));
283 tli
->tablename
=getsstring(tablename
, 100);
287 pqasyncquery(pqstartloadtable
, tli
, "SELECT COUNT(*) FROM %s", tli
->tablename
->content
);
290 void pqstartloadtable(PGconn
*dbconn
, void *arg
)
293 unsigned long i
, count
, tablecrc
;
294 pqtableloaderinfo_s
*tli
= arg
;
296 res
= PQgetResult(dbconn
);
298 if (PQresultStatus(res
) != PGRES_TUPLES_OK
&& PQresultStatus(res
) != PGRES_COMMAND_OK
) {
299 Error("pqsql", ERR_ERROR
, "Error getting row count for %s.", tli
->tablename
->content
);
303 if (PQnfields(res
) != 1) {
304 Error("pqsql", ERR_ERROR
, "Count query format error for %s.", tli
->tablename
->content
);
308 tablecrc
=crc32(tli
->tablename
->content
);
309 count
=strtoul(PQgetvalue(res
, 0, 0), NULL
, 10);
312 Error("pqsql", ERR_INFO
, "Found %lu entries in table %s, scheduling load.", count
, tli
->tablename
->content
);
314 pqasyncquery(tli
->init
, NULL
, "BEGIN");
315 pqasyncquery(NULL
, NULL
, "DECLARE table%lx%lx CURSOR FOR SELECT * FROM %s", tablecrc
, count
, tli
->tablename
->content
);
317 for (i
=0;(count
- i
) > 1000; i
+=1000)
318 pqasyncquery(tli
->data
, NULL
, "FETCH 1000 FROM table%lx%lx", tablecrc
, count
);
320 pqasyncquery(tli
->data
, NULL
, "FETCH ALL FROM table%lx%lx", tablecrc
, count
);
322 pqasyncquery(NULL
, NULL
, "CLOSE table%lx%lx", tablecrc
, count
);
323 pqasyncquery(tli
->fini
, NULL
, "COMMIT");
325 freesstring(tli
->tablename
);
326 nsfree(POOL_PQSQL
, tli
);
329 void disconnectdb(void) {
330 pqasyncquery_s
*qqp
= queryhead
, *nqqp
;
335 /* do this first else we may get conflicts */
336 deregisterhandler(PQsocket(dbconn
), 0);
338 /* Throw all the queued queries away, beware of data malloc()ed inside the query item.. */
342 freesstring(qqp
->query_ss
);
345 } else if (qqp
->query
) {
346 nsfree(POOL_PQSQL
, qqp
->query
);
349 nsfree(POOL_PQSQL
, qqp
);
353 deregisterhook(HOOK_CORE_STATSREQUEST
, dbstatus
);
355 dbconn
= NULL
; /* hmm? */
360 /* more stolen code from Q9 */
361 void dbstatus(int hooknum
, void *arg
) {
362 if ((long)arg
> 10) {
368 for(qqp
=queryhead
;qqp
;qqp
=qqp
->next
)
371 snprintf(message
, sizeof(message
), "PQSQL : %6d queries queued.",i
);
373 triggerhook(HOOK_CORE_STATSREPLY
, message
);
377 int pqconnected(void) {
381 char* pqlasterror(PGconn
* pgconn
) {
382 static char errormsg
[PQ_ERRORMSG_LENGTH
+ 1];
385 return "PGCONN NULL";
386 strlcpy(errormsg
, PQerrorMessage(pgconn
), PQ_ERRORMSG_LENGTH
);
387 for(i
=0;i
<errormsg
[i
];i
++) {
388 if((errormsg
[i
] == '\r') || (errormsg
[i
] == '\n'))
395 PQResult
*pqgetresult(PGconn
*c
) {
400 r
= (PQResult
*)nsmalloc(POOL_PQSQL
, sizeof(PQResult
));
402 r
->result
= PQgetResult(c
);
403 r
->rows
= PQntuples(r
->result
);
408 int pqfetchrow(PQResult
*res
) {
409 if(res
->row
+ 1 == res
->rows
)
417 char *pqgetvalue(PQResult
*res
, int column
) {
418 return PQgetvalue(res
->result
, res
->row
, column
);
421 void pqclear(PQResult
*res
) {
426 PQclear(res
->result
);
428 nsfree(POOL_PQSQL
, res
);