2 * Asterisk -- A telephony toolkit for Linux.
4 * Connect to PostgreSQL
6 * Copyright (C) 2002, Christos Ricudis
8 * Christos Ricudis <ricudis@itc.auth.gr>
10 * This program is free software, distributed under the terms of
11 * the GNU General Public License
14 #include <asterisk/file.h>
15 #include <asterisk/logger.h>
16 #include <asterisk/channel.h>
17 #include <asterisk/pbx.h>
18 #include <asterisk/module.h>
19 #include <asterisk/linkedlists.h>
20 #include <asterisk/chanvars.h>
21 #include <asterisk/lock.h>
26 #include <sys/types.h>
34 static char *tdesc = "Simple PostgreSQL Interface";
36 static char *app = "PGSQL";
38 static char *synopsis = "Do several SQLy things";
40 static char *descrip =
41 "PGSQL(): Do several SQLy things\n"
43 " PGSQL(Connect var option-string)\n"
44 " Connects to a database. Option string contains standard PostgreSQL\n"
45 " parameters like host=, dbname=, user=. Connection identifer returned\n"
47 " PGSQL(Query var ${connection_identifier} query-string)\n"
48 " Executes standard SQL query contained in query-string using established\n"
49 " connection identified by ${connection_identifier}. Reseult of query is\n"
50 " is stored in ${var}.\n"
51 " PGSQL(Fetch statusvar ${result_identifier} var1 var2 ... varn)\n"
52 " Fetches a single row from a result set contained in ${result_identifier}.\n"
53 " Assigns returned fields to ${var1} ... ${varn}. ${statusvar} is set TRUE\n"
54 " if additional rows exist in reseult set.\n"
55 " PGSQL(Clear ${result_identifier})\n"
56 " Frees memory and datastructures associated with result set.\n"
57 " PGSQL(Disconnect ${connection_identifier})\n"
58 " Disconnects from named connection to PostgreSQL.\n" ;
62 Syntax of SQL commands :
64 Connect var option-string
66 Connects to a database using the option-string and stores the
67 connection identifier in ${var}
70 Query var ${connection_identifier} query-string
72 Submits query-string to database backend and stores the result
76 Fetch statusvar ${result_identifier} var1 var2 var3 ... varn
78 Fetches a row from the query and stores end-of-table status in
79 ${statusvar} and columns in ${var1}..${varn}
82 Clear ${result_identifier}
84 Clears data structures associated with ${result_identifier}
87 Disconnect ${connection_identifier}
89 Disconnects from named connection
94 exten => s,2,PGSQL(Connect connid host=localhost user=asterisk dbname=credit)
95 exten => s,3,PGSQL(Query resultid ${connid} SELECT username,credit FROM credit WHERE callerid=${CALLERIDNUM})
96 exten => s,4,PGSQL(Fetch fetchid ${resultid} datavar1 datavar2)
97 exten => s,5,GotoIf(${fetchid}?6:8)
98 exten => s,6,Festival("User ${datavar1} currently has credit balance of ${datavar2} dollars.")
99 exten => s,7,Goto(s,4)
100 exten => s,8,PGSQL(Clear ${resultid})
101 exten => s,9,PGSQL(Disconnect ${connid})
109 #define AST_PGSQL_ID_DUMMY 0
110 #define AST_PGSQL_ID_CONNID 1
111 #define AST_PGSQL_ID_RESID 2
112 #define AST_PGSQL_ID_FETCHID 3
114 struct ast_PGSQL_id {
115 int identifier_type; /* 0=dummy, 1=connid, 2=resultid */
118 AST_LIST_ENTRY(ast_PGSQL_id) entries;
121 AST_LIST_HEAD(PGSQLidshead,ast_PGSQL_id) PGSQLidshead;
123 static void *find_identifier(int identifier,int identifier_type) {
124 struct PGSQLidshead *headp;
125 struct ast_PGSQL_id *i;
131 if (AST_LIST_LOCK(headp)) {
132 ast_log(LOG_WARNING,"Unable to lock identifiers list\n");
134 AST_LIST_TRAVERSE(headp,i,entries) {
135 if ((i->identifier==identifier) && (i->identifier_type==identifier_type)) {
142 ast_log(LOG_WARNING,"Identifier %d, identifier_type %d not found in identifier list\n",identifier,identifier_type);
144 AST_LIST_UNLOCK(headp);
150 static int add_identifier(int identifier_type,void *data) {
151 struct ast_PGSQL_id *i,*j;
152 struct PGSQLidshead *headp;
159 if (AST_LIST_LOCK(headp)) {
160 ast_log(LOG_WARNING,"Unable to lock identifiers list\n");
163 i=malloc(sizeof(struct ast_PGSQL_id));
164 AST_LIST_TRAVERSE(headp,j,entries) {
165 if (j->identifier>maxidentifier) {
166 maxidentifier=j->identifier;
170 i->identifier=maxidentifier+1;
171 i->identifier_type=identifier_type;
173 AST_LIST_INSERT_HEAD(headp,i,entries);
174 AST_LIST_UNLOCK(headp);
176 return(i->identifier);
179 static int del_identifier(int identifier,int identifier_type) {
180 struct ast_PGSQL_id *i;
181 struct PGSQLidshead *headp;
186 if (AST_LIST_LOCK(headp)) {
187 ast_log(LOG_WARNING,"Unable to lock identifiers list\n");
189 AST_LIST_TRAVERSE(headp,i,entries) {
190 if ((i->identifier==identifier) &&
191 (i->identifier_type==identifier_type)) {
192 AST_LIST_REMOVE(headp,i,entries);
198 AST_LIST_UNLOCK(headp);
202 ast_log(LOG_WARNING,"Could not find identifier %d, identifier_type %d in list to delete\n",identifier,identifier_type);
209 static int aPGSQL_connect(struct ast_channel *chan, void *data) {
225 strncpy(s1, data, l -1);
227 strsep(&stringp," "); /* eat the first token, we already know it :P */
228 var=strsep(&stringp," ");
229 optionstring=strsep(&stringp,"\n");
231 karoto = PQconnectdb(optionstring);
232 if (PQstatus(karoto) == CONNECTION_BAD) {
233 ast_log(LOG_WARNING,"Connection to database using '%s' failed. postgress reports : %s\n", optionstring,
234 PQerrorMessage(karoto));
237 ast_log(LOG_WARNING,"adding identifier\n");
238 id=add_identifier(AST_PGSQL_ID_CONNID,karoto);
239 snprintf(s, sizeof(s), "%d", id);
240 pbx_builtin_setvar_helper(chan,var,s);
247 static int aPGSQL_query(struct ast_channel *chan, void *data) {
250 char *s1,*s2,*s3,*s4;
266 strncpy(s1, data, l - 1);
268 strsep(&stringp," "); /* eat the first token, we already know it :P */
269 s3=strsep(&stringp," ");
270 while (1) { /* ugly trick to make branches with break; */
272 s4=strsep(&stringp," ");
274 querystring=strsep(&stringp,"\n");
275 if ((karoto=find_identifier(id,AST_PGSQL_ID_CONNID))==NULL) {
276 ast_log(LOG_WARNING,"Invalid connection identifier %d passed in aPGSQL_query\n",id);
280 PGSQLres=PQexec(karoto,querystring);
281 if (PGSQLres==NULL) {
282 ast_log(LOG_WARNING,"aPGSQL_query: Connection Error (connection identifier = %d, error message : %s)\n",id,PQerrorMessage(karoto));
286 if (PQresultStatus(PGSQLres) == PGRES_BAD_RESPONSE ||
287 PQresultStatus(PGSQLres) == PGRES_NONFATAL_ERROR ||
288 PQresultStatus(PGSQLres) == PGRES_FATAL_ERROR) {
289 ast_log(LOG_WARNING,"aPGSQL_query: Query Error (connection identifier : %d, error message : %s)\n",id,PQcmdStatus(PGSQLres));
293 nres=PQnfields(PGSQLres);
294 id1=add_identifier(AST_PGSQL_ID_RESID,PGSQLres);
295 snprintf(s, sizeof(s), "%d", id1);
296 pbx_builtin_setvar_helper(chan,var,s);
307 static int aPGSQL_fetch(struct ast_channel *chan, void *data) {
309 char *s1,*s2,*fetchid_var,*s4,*s5,*s6,*s7;
318 struct ast_var_t *variables;
319 struct varshead *headp;
322 headp=&chan->varshead;
329 strncpy(s1, data, l - 1);
331 strsep(&stringp," "); /* eat the first token, we already know it :P */
332 fetchid_var=strsep(&stringp," ");
333 while (1) { /* ugly trick to make branches with break; */
334 var=fetchid_var; /* fetchid */
337 AST_LIST_TRAVERSE(headp,variables,entries) {
338 if (strncasecmp(ast_var_name(variables),fetchid_var,strlen(fetchid_var))==0) {
339 s7=ast_var_value(variables);
347 pbx_builtin_setvar_helper(chan,fetchid_var,s7);
350 s4=strsep(&stringp," ");
351 id=atoi(s4); /* resultid */
352 if ((PGSQLres=find_identifier(id,AST_PGSQL_ID_RESID))==NULL) {
353 ast_log(LOG_WARNING,"Invalid result identifier %d passed in aPGSQL_fetch\n",id);
357 id=atoi(s7); /*fetchid */
358 if ((lalares=find_identifier(id,AST_PGSQL_ID_FETCHID))==NULL) {
359 i=0; /* fetching the very first row */
363 del_identifier(id,AST_PGSQL_ID_FETCHID); /* will re-add it a bit later */
366 if (i<PQntuples(PGSQLres)) {
367 nres=PQnfields(PGSQLres);
368 ast_log(LOG_WARNING,"ast_PGSQL_fetch : nres = %d i = %d ;\n",nres,i);
369 for (j=0;j<nres;j++) {
370 s5=strsep(&stringp," ");
372 ast_log(LOG_WARNING,"ast_PGSQL_fetch : More tuples (%d) than variables (%d)\n",nres,j);
375 s6=PQgetvalue(PGSQLres,i,j);
377 ast_log(LOG_WARNING,"PWgetvalue(res,%d,%d) returned NULL in ast_PGSQL_fetch\n",i,j);
380 ast_log(LOG_WARNING,"===setting variable '%s' to '%s'\n",s5,s6);
381 pbx_builtin_setvar_helper(chan,s5,s6);
383 lalares=malloc(sizeof(int));
384 *lalares = ++i; /* advance to the next row */
385 id1 = add_identifier(AST_PGSQL_ID_FETCHID,lalares);
387 ast_log(LOG_WARNING,"ast_PGSQL_fetch : EOF\n");
388 id1 = 0; /* no more rows */
390 snprintf(s, sizeof(s), "%d", id1);
391 ast_log(LOG_WARNING,"Setting var '%s' to value '%s'\n",fetchid_var,s);
392 pbx_builtin_setvar_helper(chan,fetchid_var,s);
401 static int aPGSQL_reset(struct ast_channel *chan, void *data) {
412 strncpy(s1, data, l - 1);
414 strsep(&stringp," "); /* eat the first token, we already know it :P */
415 s3=strsep(&stringp," ");
417 if ((karoto=find_identifier(id,AST_PGSQL_ID_CONNID))==NULL) {
418 ast_log(LOG_WARNING,"Invalid connection identifier %d passed in aPGSQL_reset\n",id);
427 static int aPGSQL_clear(struct ast_channel *chan, void *data) {
438 strncpy(s1, data, l - 1);
440 strsep(&stringp," "); /* eat the first token, we already know it :P */
441 s3=strsep(&stringp," ");
443 if ((karoto=find_identifier(id,AST_PGSQL_ID_RESID))==NULL) {
444 ast_log(LOG_WARNING,"Invalid result identifier %d passed in aPGSQL_clear\n",id);
447 del_identifier(id,AST_PGSQL_ID_RESID);
457 static int aPGSQL_disconnect(struct ast_channel *chan, void *data) {
468 strncpy(s1, data, l - 1);
470 strsep(&stringp," "); /* eat the first token, we already know it :P */
471 s3=strsep(&stringp," ");
473 if ((karoto=find_identifier(id,AST_PGSQL_ID_CONNID))==NULL) {
474 ast_log(LOG_WARNING,"Invalid connection identifier %d passed in aPGSQL_disconnect\n",id);
477 del_identifier(id,AST_PGSQL_ID_CONNID);
484 static int aPGSQL_debug(struct ast_channel *chan, void *data) {
485 ast_log(LOG_WARNING,"Debug : %s\n",(char *)data);
491 static int PGSQL_exec(struct ast_channel *chan, void *data)
497 printf("PRSQL_exec: data=%s\n",(char*)data);
501 ast_log(LOG_WARNING, "APP_PGSQL requires an argument (see manual)\n");
507 if (strncasecmp("connect",data,strlen("connect"))==0) {
508 result=(aPGSQL_connect(chan,data));
509 } else if (strncasecmp("query",data,strlen("query"))==0) {
510 result=(aPGSQL_query(chan,data));
511 } else if (strncasecmp("fetch",data,strlen("fetch"))==0) {
512 result=(aPGSQL_fetch(chan,data));
513 } else if (strncasecmp("reset",data,strlen("reset"))==0) {
514 result=(aPGSQL_reset(chan,data));
515 } else if (strncasecmp("clear",data,strlen("clear"))==0) {
516 result=(aPGSQL_clear(chan,data));
517 } else if (strncasecmp("debug",data,strlen("debug"))==0) {
518 result=(aPGSQL_debug(chan,data));
519 } else if (strncasecmp("disconnect",data,strlen("disconnect"))==0) {
520 result=(aPGSQL_disconnect(chan,data));
522 ast_log(LOG_WARNING, "Unknown APP_PGSQL argument : %s\n",(char *)data);
526 LOCAL_USER_REMOVE(u);
531 int unload_module(void)
533 STANDARD_HANGUP_LOCALUSERS;
534 return ast_unregister_application(app);
537 int load_module(void)
539 struct PGSQLidshead *headp;
543 AST_LIST_HEAD_INIT(headp);
544 return ast_register_application(app, PGSQL_exec, synopsis, descrip);
547 char *description(void)
555 STANDARD_USECOUNT(res);
561 return ASTERISK_GPL_KEY;