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