- conversion to allocation wrappers
[asterisk/asterisk.git] / apps / app_sql_postgres.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2002, Christos Ricudis
5  *
6  * Christos Ricudis <ricudis@itc.auth.gr>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18
19 /*! \file
20  *
21  * \brief Connect to PostgreSQL
22  *
23  * \author Christos Ricudis <ricudis@itc.auth.gr>
24  *
25  * \ingroup applications
26  */
27
28 #include <stdlib.h>
29 #include <unistd.h>
30 #include <string.h>
31 #include <sys/types.h>
32 #include <stdio.h>
33 #include <unistd.h>
34
35 #include "asterisk.h"
36
37 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
38
39 #include "asterisk/file.h"
40 #include "asterisk/logger.h"
41 #include "asterisk/channel.h"
42 #include "asterisk/pbx.h"
43 #include "asterisk/module.h"
44 #include "asterisk/linkedlists.h"
45 #include "asterisk/chanvars.h"
46 #include "asterisk/lock.h"
47 #include "asterisk/utils.h"
48
49 #include "libpq-fe.h"
50
51 static char *tdesc = "Simple PostgreSQL Interface";
52
53 static char *app = "PGSQL";
54
55 static char *synopsis = "Do several SQLy things";
56
57 static char *descrip =
58 "PGSQL():  Do several SQLy things\n"
59 "Syntax:\n"
60 "  PGSQL(Connect var option-string)\n"
61 "    Connects to a database.  Option string contains standard PostgreSQL\n"
62 "    parameters like host=, dbname=, user=.  Connection identifier returned\n"
63 "    in ${var}.\n"
64 "  PGSQL(Query var ${connection_identifier} query-string)\n"
65 "    Executes standard SQL query contained in query-string using established\n"
66 "    connection identified by ${connection_identifier}.  Result of query is\n"
67 "    stored in ${var}.\n"
68 "  PGSQL(Fetch statusvar ${result_identifier} var1 var2 ... varn)\n"
69 "    Fetches a single row from a result set contained in ${result_identifier}.\n"
70 "    Assigns returned fields to ${var1} ... ${varn}.  ${statusvar} is set TRUE\n"
71 "    if additional rows exist in result set.\n"
72 "  PGSQL(Clear ${result_identifier})\n"
73 "    Frees memory and data structures associated with result set.\n"
74 "  PGSQL(Disconnect ${connection_identifier})\n"
75 "    Disconnects from named connection to PostgreSQL.\n" ;
76
77 /*
78
79 Syntax of SQL commands :
80
81         Connect var option-string
82
83         Connects to a database using the option-string and stores the
84         connection identifier in ${var}
85
86
87         Query var ${connection_identifier} query-string
88
89         Submits query-string to database backend and stores the result
90         identifier in ${var}
91
92
93         Fetch statusvar ${result_identifier} var1 var2 var3 ... varn
94
95         Fetches a row from the query and stores end-of-table status in
96         ${statusvar} and columns in ${var1} ... ${varn}
97
98
99         Clear ${result_identifier}
100
101         Clears data structures associated with ${result_identifier}
102
103
104         Disconnect ${connection_identifier}
105
106         Disconnects from named connection
107
108
109 EXAMPLES OF USE :
110
111 exten => s,2,PGSQL(Connect connid host=localhost user=asterisk dbname=credit)
112 exten => s,3,PGSQL(Query resultid ${connid} SELECT username,credit FROM credit WHERE callerid=${CALLERIDNUM})
113 exten => s,4,PGSQL(Fetch fetchid ${resultid} datavar1 datavar2)
114 exten => s,5,GotoIf(${fetchid}?6:8)
115 exten => s,6,Festival("User ${datavar1} currently has credit balance of ${datavar2} dollars.")
116 exten => s,7,Goto(s,4)
117 exten => s,8,PGSQL(Clear ${resultid})
118 exten => s,9,PGSQL(Disconnect ${connid})
119
120 */
121
122 STANDARD_LOCAL_USER;
123
124 LOCAL_USER_DECL;
125
126 enum id_type {
127         AST_PGSQL_ID_DUMMY = 0,
128         AST_PGSQL_ID_CONNID,
129         AST_PGSQL_ID_RESID,
130         AST_PGSQL_ID_FETCHID
131 };
132
133 struct ast_PGSQL_id {
134         enum id_type identifier_type; /* 0 = dummy, 1 = connid, 2 = resultid, 3 = fetchid */
135         int identifier;
136         void *data;
137         AST_LIST_ENTRY(ast_PGSQL_id) entries;
138 } *ast_PGSQL_id;
139
140 AST_LIST_HEAD(PGSQLidshead, ast_PGSQL_id) PGSQLidshead;
141
142 static void *find_identifier(const int identifier, const enum id_type identifier_type)
143 {
144         struct PGSQLidshead *headp;
145         struct ast_PGSQL_id *i;
146         void *res = NULL;
147         int found = 0;
148
149         headp = &PGSQLidshead;
150
151         if (AST_LIST_LOCK(headp)) {
152                 ast_log(LOG_WARNING, "Unable to lock identifiers list\n");
153         } else {
154                 AST_LIST_TRAVERSE(headp, i, entries) {
155                         if ((i->identifier == identifier) && (i->identifier_type == identifier_type)) {
156                                 found = 1;
157                                 res = i->data;
158                                 break;
159                         }
160                 }
161                 if (!found) {
162                         ast_log(LOG_WARNING, "Identifier %d, identifier_type %d not found in identifier list\n", identifier, identifier_type);
163                 }
164                 AST_LIST_UNLOCK(headp);
165         }
166
167         return res;
168 }
169
170 static int add_identifier(const enum id_type identifier_type, void *data)
171 {
172         struct ast_PGSQL_id *i, *j;
173         struct PGSQLidshead *headp;
174         int maxidentifier = 0;
175
176         headp = &PGSQLidshead;
177         i = NULL;
178         j = NULL;
179
180         if (AST_LIST_LOCK(headp)) {
181                 ast_log(LOG_WARNING, "Unable to lock identifiers list\n");
182                 return -1;
183         }
184
185         if (!(i = ast_malloc(sizeof(*i)))) {
186                 AST_LIST_UNLOCK(headp);
187                 return -1;
188         }
189         
190         AST_LIST_TRAVERSE(headp, j, entries) {
191                 if (j->identifier > maxidentifier)
192                         maxidentifier = j->identifier;
193         }
194         i->identifier = maxidentifier + 1;
195         i->identifier_type = identifier_type;
196         i->data = data;
197         AST_LIST_INSERT_HEAD(headp, i, entries);
198         AST_LIST_UNLOCK(headp);
199
200         return i->identifier;
201 }
202
203 static int del_identifier(const int identifier, const enum id_type identifier_type)
204 {
205         struct ast_PGSQL_id *i;
206         struct PGSQLidshead *headp;
207         int found = 0;
208
209         headp = &PGSQLidshead;
210
211         if (AST_LIST_LOCK(headp)) {
212                 ast_log(LOG_WARNING, "Unable to lock identifiers list\n");
213         } else {
214                 AST_LIST_TRAVERSE(headp, i, entries) {
215                         if ((i->identifier == identifier) && (i->identifier_type == identifier_type)) {
216                                 AST_LIST_REMOVE(headp, i, entries);
217                                 free(i);
218                                 found = 1;
219                                 break;
220                         }
221                 }
222                 AST_LIST_UNLOCK(headp);
223         }
224
225         if (!found) {
226                 ast_log(LOG_WARNING, "Could not find identifier %d, identifier_type %d in list to delete\n", identifier, identifier_type);
227                 return -1;
228         } else {
229                 return 0;
230         }
231 }
232
233 static int aPGSQL_connect(struct ast_channel *chan, void *data)
234 {
235         char s[100] = "";
236         char *optionstring;
237         char *var;
238         int res = 0;
239         PGconn *PGSQLconn;
240         int id;
241         char *stringp = NULL;
242
243         if (!(stringp = ast_strdupa(data)))
244                 return -1;
245         
246         strsep(&stringp, " "); /* eat the first token, we already know it :P  */
247         var = strsep(&stringp, " ");
248         optionstring = strsep(&stringp, "\n");
249
250         PGSQLconn = PQconnectdb(optionstring);
251         if (PQstatus(PGSQLconn) == CONNECTION_BAD) {
252                 ast_log(LOG_WARNING, "Connection to database using '%s' failed. postgress reports : %s\n", optionstring, PQerrorMessage(PGSQLconn));
253                 res = -1;
254         } else {
255                 ast_log(LOG_WARNING, "Adding identifier\n");
256                 id = add_identifier(AST_PGSQL_ID_CONNID, PGSQLconn);
257                 snprintf(s, sizeof(s), "%d", id);
258                 pbx_builtin_setvar_helper(chan, var, s);
259         }
260
261         return res;
262 }
263
264 static int aPGSQL_query(struct ast_channel *chan, void *data)
265 {
266         char *s2, *s3;
267         char s[100] = "";
268         char *querystring;
269         char *var;
270         int res = 0, nres;
271         PGconn *PGSQLconn;
272         PGresult *PGSQLres;
273         int id, id1;
274         char *stringp = NULL;
275
276         if (!(stringp = ast_strdupa(data)))
277                 return -1;
278         
279         strsep(&stringp, " "); /* eat the first token, we already know it :P  */
280         s2 = strsep(&stringp, " ");
281         while (1) {     /* ugly trick to make branches with break; */
282                 var = s2;
283                 s3 = strsep(&stringp, " ");
284                 id = atoi(s3);
285                 querystring = strsep(&stringp, "\n");
286                 if (!(PGSQLconn = find_identifier(id, AST_PGSQL_ID_CONNID))) {
287                         ast_log(LOG_WARNING, "Invalid connection identifier %d passed in aPGSQL_query\n", id);
288                         res = -1;
289                         break;
290                 }
291                 if (!(PGSQLres = PQexec(PGSQLconn, querystring))) {
292                         ast_log(LOG_WARNING, "aPGSQL_query: Connection Error (connection identifier = %d, error message : %s)\n", id, PQerrorMessage(PGSQLconn));
293                         res = -1;
294                         break;
295                 }
296                 if (PQresultStatus(PGSQLres) == PGRES_BAD_RESPONSE ||
297                     PQresultStatus(PGSQLres) == PGRES_NONFATAL_ERROR ||
298                     PQresultStatus(PGSQLres) == PGRES_FATAL_ERROR) {
299                         ast_log(LOG_WARNING, "aPGSQL_query: Query Error (connection identifier : %d, error message : %s)\n", id, PQcmdStatus(PGSQLres));
300                         res = -1;
301                         break;
302                 }
303                 nres = PQnfields(PGSQLres);
304                 id1 = add_identifier(AST_PGSQL_ID_RESID, PGSQLres);
305                 snprintf(s, sizeof(s), "%d", id1);
306                 pbx_builtin_setvar_helper(chan, var, s);
307                 break;
308         }
309
310         return res;
311 }
312
313 static int aPGSQL_fetch(struct ast_channel *chan, void *data)
314 {
315         char *s2, *s3, *s4, *fetchid_var;
316         const char *s5 = NULL;
317         char s[100];
318         char *var;
319         int res = 0;
320         PGresult *PGSQLres;
321         int id, id1, i, j, fnd;
322         int *identp = NULL;
323         int nres;
324         struct ast_var_t *variables;
325         struct varshead *headp;
326         char *stringp = NULL;
327
328         headp = &chan->varshead;
329
330         if (!(stringp = ast_strdupa(data)))
331                 return -1;
332         
333         strsep(&stringp, " "); /* eat the first token, we already know it :P  */
334         fetchid_var = strsep(&stringp, " ");
335         while (1) {     /* ugly trick to make branches with break; */
336                 var = fetchid_var; /* fetchid */
337                 fnd = 0;
338
339                 AST_LIST_TRAVERSE(headp, variables, entries) {
340                         if (!(strncasecmp(ast_var_name(variables), fetchid_var, strlen(fetchid_var)))) {
341                                 s5 = ast_var_value(variables);
342                                 fnd = 1;
343                                 break;
344                         }
345                 }
346
347                 if (!fnd) {
348                         s5 = "0";
349                         pbx_builtin_setvar_helper(chan, fetchid_var, s5);
350                 }
351
352                 s2 = strsep(&stringp, " ");
353                 id = atoi(s2); /* resultid */
354                 if (!(PGSQLres = find_identifier(id, AST_PGSQL_ID_RESID))) {
355                         ast_log(LOG_WARNING, "Invalid result identifier %d passed in aPGSQL_fetch\n", id);
356                         res = -1;
357                         break;
358                 }
359                 id = atoi(s5); /*fetchid */
360                 if (!(identp = find_identifier(id, AST_PGSQL_ID_FETCHID))) {
361                         i = 0; /* fetching the very first row */
362                 } else {
363                         i = *identp;
364                         free(identp);
365                         del_identifier(id, AST_PGSQL_ID_FETCHID); /* will re-add it a bit later */
366                 }
367
368                 if (i < PQntuples(PGSQLres)) {
369                         nres = PQnfields(PGSQLres);
370                         ast_log(LOG_WARNING, "ast_PGSQL_fetch : nres = %d i = %d ;\n", nres, i);
371                         for (j = 0; j < nres; j++) {
372                                 if (!(s3 = strsep(&stringp, " "))) {
373                                         ast_log(LOG_WARNING, "ast_PGSQL_fetch : More tuples (%d) than variables (%d)\n", nres, j);
374                                         break;
375                                 }
376                                 if (!(s4 = PQgetvalue(PGSQLres, i, j))) {
377                                         ast_log(LOG_WARNING, "PQgetvalue(res, %d, %d) returned NULL in ast_PGSQL_fetch\n", i, j);
378                                         break;
379                                 }
380                                 ast_log(LOG_WARNING, "===setting variable '%s' to '%s'\n", s3, s4);
381                                 pbx_builtin_setvar_helper(chan, s3, s4);
382                         }
383                         if (!(identp = ast_malloc(sizeof(int))))
384                                 return -1;
385                         *identp = ++i; /* advance to the next row */
386                         id1 = add_identifier(AST_PGSQL_ID_FETCHID, identp);
387                 } else {
388                         ast_log(LOG_WARNING, "ast_PGSQL_fetch : EOF\n");
389                         id1 = 0; /* no more rows */
390                 }
391                 snprintf(s, sizeof(s), "%d", id1);
392                 ast_log(LOG_WARNING, "Setting var '%s' to value '%s'\n", fetchid_var, s);
393                 pbx_builtin_setvar_helper(chan, fetchid_var, s);
394                 break;
395         }
396
397         return res;
398 }
399
400 static int aPGSQL_reset(struct ast_channel *chan, void *data)
401 {
402         char *s;
403         PGconn *PGSQLconn;
404         int id;
405         char *stringp = NULL;
406
407         if (!(stringp = ast_strdupa(data)))
408                 return -1;
409
410         strsep(&stringp, " "); /* eat the first token, we already know it :P  */
411         s = strsep(&stringp, " ");
412         id = atoi(s);
413         if (!(PGSQLconn = find_identifier(id, AST_PGSQL_ID_CONNID))) {
414                 ast_log(LOG_WARNING, "Invalid connection identifier %d passed in aPGSQL_reset\n", id);
415         } else {
416                 PQreset(PGSQLconn);
417         }
418         
419         return 0;
420 }
421
422 static int aPGSQL_clear(struct ast_channel *chan, void *data)
423 {
424         char *s;
425         PGresult *PGSQLres;
426         int id;
427         char *stringp = NULL;
428
429         if (!(stringp = ast_strdupa(data)))
430                 return -1;
431         
432         strsep(&stringp, " "); /* eat the first token, we already know it :P  */
433         s = strsep(&stringp, " ");
434         id = atoi(s);
435         if (!(PGSQLres = find_identifier(id, AST_PGSQL_ID_RESID))) {
436                 ast_log(LOG_WARNING, "Invalid result identifier %d passed in aPGSQL_clear\n", id);
437         } else {
438                 PQclear(PGSQLres);
439                 del_identifier(id, AST_PGSQL_ID_RESID);
440         }
441
442         return 0;
443 }
444
445 static int aPGSQL_disconnect(struct ast_channel *chan, void *data)
446 {
447         char *s;
448         PGconn *PGSQLconn;
449         int id;
450         char *stringp = NULL;
451
452         if (!(stringp = ast_strdupa(data)))
453                 return -1;
454         
455         strsep(&stringp, " "); /* eat the first token, we already know it :P  */
456         s = strsep(&stringp, " ");
457         id = atoi(s);
458         if (!(PGSQLconn = find_identifier(id, AST_PGSQL_ID_CONNID))) {
459                 ast_log(LOG_WARNING, "Invalid connection identifier %d passed in aPGSQL_disconnect\n", id);
460         } else {
461                 PQfinish(PGSQLconn);
462                 del_identifier(id, AST_PGSQL_ID_CONNID);
463         }
464         
465         return 0;
466 }
467
468 static int aPGSQL_debug(struct ast_channel *chan, void *data)
469 {
470         ast_log(LOG_WARNING, "Debug : %s\n", (char *)data);
471         return 0;
472 }
473
474 static int PGSQL_exec(struct ast_channel *chan, void *data)
475 {
476         struct localuser *u;
477         int result;
478
479         if (ast_strlen_zero(data)) {
480                 ast_log(LOG_WARNING, "APP_PGSQL requires an argument (see manual)\n");
481                 return -1;
482         }
483
484         LOCAL_USER_ADD(u);
485
486         result = 0;
487
488         if (!(strncasecmp("connect", data, strlen("connect")))) {
489                 result = (aPGSQL_connect(chan, data));
490         } else if (!(strncasecmp("query", data, strlen("query")))) {
491                 result = (aPGSQL_query(chan, data));
492         } else if (!(strncasecmp("fetch", data, strlen("fetch")))) {
493                 result = (aPGSQL_fetch(chan, data));
494         } else if (!(strncasecmp("reset", data, strlen("reset")))) {
495                 result = (aPGSQL_reset(chan, data));
496         } else if (!(strncasecmp("clear", data, strlen("clear")))) {
497                 result = (aPGSQL_clear(chan, data));
498         } else if (!(strncasecmp("debug", data, strlen("debug")))) {
499                 result = (aPGSQL_debug(chan, data));
500         } else if (!(strncasecmp("disconnect", data, strlen("disconnect")))) {
501                 result = (aPGSQL_disconnect(chan, data));
502         } else {
503                 ast_log(LOG_WARNING, "Unknown APP_PGSQL argument : %s\n", (char *)data);
504                 result = -1;
505         }
506
507         LOCAL_USER_REMOVE(u);
508
509         return result;
510 }
511
512 int unload_module(void)
513 {
514         int res = ast_unregister_application(app);
515         STANDARD_HANGUP_LOCALUSERS;
516         return res;
517 }
518
519 int load_module(void)
520 {
521         struct PGSQLidshead *headp = &PGSQLidshead;
522         AST_LIST_HEAD_INIT(headp);
523         return ast_register_application(app, PGSQL_exec, synopsis, descrip);
524 }
525
526 char *description(void)
527 {
528         return tdesc;
529 }
530
531 int usecount(void)
532 {
533         int res;
534         STANDARD_USECOUNT(res);
535         return res;
536 }
537
538 char *key()
539 {
540         return ASTERISK_GPL_KEY;
541 }