Skip to content
Snippets Groups Projects
app_sql_postgres.c 14 KiB
Newer Older
  • Learn to ignore specific revisions
  • Mark Spencer's avatar
    Mark Spencer committed
    /*
    
     * Asterisk -- An open source telephony toolkit.
    
    Mark Spencer's avatar
    Mark Spencer committed
     *
     * Copyright (C) 2002, Christos Ricudis
     *
    
     * Christos Ricudis <ricudis@itc.auth.gr>
    
    Mark Spencer's avatar
    Mark Spencer committed
     *
    
     * See http://www.asterisk.org for more information about
     * the Asterisk project. Please do not directly contact
     * any of the maintainers of this project for assistance;
     * the project provides a web site, mailing lists and IRC
     * channels for your use.
     *
    
    Mark Spencer's avatar
    Mark Spencer committed
     * This program is free software, distributed under the terms of
    
     * the GNU General Public License Version 2. See the LICENSE file
     * at the top of the source tree.
     */
    
    
     * \brief Connect to PostgreSQL
    
     *
     * \author Christos Ricudis <ricudis@itc.auth.gr>
    
    Russell Bryant's avatar
    Russell Bryant committed
     * \ingroup applications
    
    #include <stdlib.h>
    #include <unistd.h>
    #include <string.h>
    #include <sys/types.h>
    #include <stdio.h>
    #include <unistd.h>
    
    #include "asterisk.h"
    
    ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
    
    
    #include "asterisk/file.h"
    #include "asterisk/logger.h"
    #include "asterisk/channel.h"
    #include "asterisk/pbx.h"
    #include "asterisk/module.h"
    #include "asterisk/linkedlists.h"
    #include "asterisk/chanvars.h"
    #include "asterisk/lock.h"
    
    Mark Spencer's avatar
    Mark Spencer committed
    #include "libpq-fe.h"
    
    static char *tdesc = "Simple PostgreSQL Interface";
    
    static char *app = "PGSQL";
    
    static char *synopsis = "Do several SQLy things";
    
    
    "PGSQL():  Do several SQLy things\n"
    "Syntax:\n"
    "  PGSQL(Connect var option-string)\n"
    "    Connects to a database.  Option string contains standard PostgreSQL\n"
    
    "    parameters like host=, dbname=, user=.  Connection identifier returned\n"
    "    in ${var}.\n"
    
    "  PGSQL(Query var ${connection_identifier} query-string)\n"
    "    Executes standard SQL query contained in query-string using established\n"
    
    "    connection identified by ${connection_identifier}.  Result of query is\n"
    "    stored in ${var}.\n"
    
    "  PGSQL(Fetch statusvar ${result_identifier} var1 var2 ... varn)\n"
    "    Fetches a single row from a result set contained in ${result_identifier}.\n"
    "    Assigns returned fields to ${var1} ... ${varn}.  ${statusvar} is set TRUE\n"
    
    "    if additional rows exist in result set.\n"
    
    "  PGSQL(Clear ${result_identifier})\n"
    
    "    Frees memory and data structures associated with result set.\n"
    
    "  PGSQL(Disconnect ${connection_identifier})\n"
    "    Disconnects from named connection to PostgreSQL.\n" ;
    
    	Connect var option-string
    
    
    	Connects to a database using the option-string and stores the
    
    	connection identifier in ${var}
    
    	Query var ${connection_identifier} query-string
    
    Mark Spencer's avatar
    Mark Spencer committed
    	Submits query-string to database backend and stores the result
    	identifier in ${var}
    
    	Fetch statusvar ${result_identifier} var1 var2 var3 ... varn
    
    
    	Fetches a row from the query and stores end-of-table status in
    	${statusvar} and columns in ${var1} ... ${varn}
    
    
    
    	Clear ${result_identifier}
    
    	Clears data structures associated with ${result_identifier}
    
    	Disconnect ${connection_identifier}
    
    Mark Spencer's avatar
    Mark Spencer committed
    	Disconnects from named connection
    
    exten => s,2,PGSQL(Connect connid host=localhost user=asterisk dbname=credit)
    exten => s,3,PGSQL(Query resultid ${connid} SELECT username,credit FROM credit WHERE callerid=${CALLERIDNUM})
    exten => s,4,PGSQL(Fetch fetchid ${resultid} datavar1 datavar2)
    exten => s,5,GotoIf(${fetchid}?6:8)
    
    exten => s,6,Festival("User ${datavar1} currently has credit balance of ${datavar2} dollars.")
    
    exten => s,7,Goto(s,4)
    exten => s,8,PGSQL(Clear ${resultid})
    exten => s,9,PGSQL(Disconnect ${connid})
    
    Mark Spencer's avatar
    Mark Spencer committed
    
    */
    
    STANDARD_LOCAL_USER;
    
    LOCAL_USER_DECL;
    
    #define AST_PGSQL_ID_DUMMY 0
    #define AST_PGSQL_ID_CONNID 1
    #define AST_PGSQL_ID_RESID 2
    #define AST_PGSQL_ID_FETCHID 3
    
    struct ast_PGSQL_id {
    
    	int identifier_type; /* 0 = dummy, 1 = connid, 2 = resultid */
    
    Mark Spencer's avatar
    Mark Spencer committed
    	int identifier;
    	void *data;
    	AST_LIST_ENTRY(ast_PGSQL_id) entries;
    } *ast_PGSQL_id;
    
    
    AST_LIST_HEAD(PGSQLidshead, ast_PGSQL_id) PGSQLidshead;
    
    static void *find_identifier(int identifier, int identifier_type)
    {
    
    Mark Spencer's avatar
    Mark Spencer committed
    	struct PGSQLidshead *headp;
    	struct ast_PGSQL_id *i;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	if (AST_LIST_LOCK(headp)) {
    
    		ast_log(LOG_WARNING, "Unable to lock identifiers list\n");
    
    Mark Spencer's avatar
    Mark Spencer committed
    	} else {
    
    		AST_LIST_TRAVERSE(headp, i, entries) {
    			if ((i->identifier == identifier) && (i->identifier_type == identifier_type)) {
    				found = 1;
    				res = i->data;
    
    Mark Spencer's avatar
    Mark Spencer committed
    				break;
    			}
    		}
    		if (!found) {
    
    			ast_log(LOG_WARNING, "Identifier %d, identifier_type %d not found in identifier list\n", identifier, identifier_type);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		}
    		AST_LIST_UNLOCK(headp);
    	}
    
    static int add_identifier(int identifier_type, void *data)
    {
    	struct ast_PGSQL_id *i, *j;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	struct PGSQLidshead *headp;
    
    	int maxidentifier = 0;
    
    	headp = &PGSQLidshead;
    	i = NULL;
    	j = NULL;
    
    
    Mark Spencer's avatar
    Mark Spencer committed
    	if (AST_LIST_LOCK(headp)) {
    
    		ast_log(LOG_WARNING, "Unable to lock identifiers list\n");
    		return -1;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	} else {
    
     		i = malloc(sizeof(struct ast_PGSQL_id));
    		AST_LIST_TRAVERSE(headp, j, entries) {
    			if (j->identifier > maxidentifier) {
    				maxidentifier = j->identifier;
    
    
    		i->identifier = maxidentifier + 1;
    		i->identifier_type = identifier_type;
    		i->data = data;
    		AST_LIST_INSERT_HEAD(headp, i, entries);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		AST_LIST_UNLOCK(headp);
    	}
    
    static int del_identifier(int identifier, int identifier_type)
    {
    
    Mark Spencer's avatar
    Mark Spencer committed
    	struct ast_PGSQL_id *i;
    	struct PGSQLidshead *headp;
    
    	int found = 0;
    
    	headp = &PGSQLidshead;
    
    	if (AST_LIST_LOCK(headp)) {
    		ast_log(LOG_WARNING, "Unable to lock identifiers list\n");
    
    Mark Spencer's avatar
    Mark Spencer committed
    	} else {
    
    		AST_LIST_TRAVERSE(headp, i, entries) {
    			if ((i->identifier == identifier) && (i->identifier_type == identifier_type)) {
    				AST_LIST_REMOVE(headp, i, entries);
    
    Mark Spencer's avatar
    Mark Spencer committed
    				free(i);
    
    Mark Spencer's avatar
    Mark Spencer committed
    				break;
    			}
    		}
    		AST_LIST_UNLOCK(headp);
    	}
    
    
    	if (!found) {
    		ast_log(LOG_WARNING, "Could not find identifier %d, identifier_type %d in list to delete\n", identifier, identifier_type);
    		return -1;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	} else {
    
    static int aPGSQL_connect(struct ast_channel *chan, void *data)
    {
    
    Mark Spencer's avatar
    Mark Spencer committed
    	char *optionstring;
    	char *var;
    	int l;
    	int res;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	int id;
    
    	char *stringp = NULL;
    
    	res = 0;
    	l = strlen(data) + 2;
    	s1 = malloc(l);
    	strncpy(s1, data, l - 1);
    	stringp = s1;
    	strsep(&stringp, " "); /* eat the first token, we already know it :P  */
    	var = strsep(&stringp, " ");
    	optionstring = strsep(&stringp, "\n");
    
    	PGSQLconn = PQconnectdb(optionstring);
    	if (PQstatus(PGSQLconn) == CONNECTION_BAD) {
    		ast_log(LOG_WARNING, "Connection to database using '%s' failed. postgress reports : %s\n", optionstring, PQerrorMessage(PGSQLconn));
    		res = -1;
    	} else {
    		ast_log(LOG_WARNING, "Adding identifier\n");
    		id = add_identifier(AST_PGSQL_ID_CONNID, PGSQLconn);
    
    		snprintf(s, sizeof(s), "%d", id);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	}
    
    Mark Spencer's avatar
    Mark Spencer committed
    	free(s1);
    	return res;
    }
    
    
    static int aPGSQL_query(struct ast_channel *chan, void *data)
    {
    	char *s1, *s2, *s3, *s4;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	char *querystring;
    	char *var;
    	int l;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	PGresult *PGSQLres;
    
    	int id, id1;
    	char *stringp = NULL;
    
    	res = 0;
    	l = strlen(data) + 2;
    	s1 = malloc(l);
    	s2 = malloc(l);
    
    	stringp = s1;
    	strsep(&stringp, " "); /* eat the first token, we already know it :P  */
    	s3 = strsep(&stringp, " ");
    
    	while (1) {	/* ugly trick to make branches with break; */
    
    		var = s3;
    		s4 = strsep(&stringp, " ");
    		id = atoi(s4);
    		querystring = strsep(&stringp, "\n");
    		if (!(PGSQLconn = find_identifier(id, AST_PGSQL_ID_CONNID))) {
    			ast_log(LOG_WARNING, "Invalid connection identifier %d passed in aPGSQL_query\n", id);
    			res = -1;
    
    Mark Spencer's avatar
    Mark Spencer committed
    			break;
    		}
    
    		if (!(PGSQLres = PQexec(PGSQLconn, querystring))) {
    			ast_log(LOG_WARNING, "aPGSQL_query: Connection Error (connection identifier = %d, error message : %s)\n", id, PQerrorMessage(PGSQLconn));
    			res = -1;
    
    Mark Spencer's avatar
    Mark Spencer committed
    			break;
    		}
    		if (PQresultStatus(PGSQLres) == PGRES_BAD_RESPONSE ||
    		    PQresultStatus(PGSQLres) == PGRES_NONFATAL_ERROR ||
    		    PQresultStatus(PGSQLres) == PGRES_FATAL_ERROR) {
    
    		    	ast_log(LOG_WARNING, "aPGSQL_query: Query Error (connection identifier : %d, error message : %s)\n", id, PQcmdStatus(PGSQLres));
    		    	res = -1;
    
    Mark Spencer's avatar
    Mark Spencer committed
    		    	break;
    		}
    
    		nres = PQnfields(PGSQLres);
    		id1 = add_identifier(AST_PGSQL_ID_RESID, PGSQLres);
    
    		snprintf(s, sizeof(s), "%d", id1);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	 	break;
    	}
    
    Mark Spencer's avatar
    Mark Spencer committed
    	free(s1);
    	free(s2);
    
    static int aPGSQL_fetch(struct ast_channel *chan, void *data)
    {
    	char *s1, *s2, *fetchid_var, *s4, *s5, *s6;
    	const char *s7;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	char s[100];
    	char *var;
    	int l;
    	int res;
    	PGresult *PGSQLres;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	int nres;
    
    	struct ast_var_t *variables;
    	struct varshead *headp;
    	char *stringp = NULL;
    
    	headp = &chan->varshead;
    
    	res = 0;
    	l = strlen(data) + 2;
    	s7 = NULL;
    	s1 = malloc(l);
    	s2 = malloc(l);
    
    	stringp = s1;
    	strsep(&stringp, " "); /* eat the first token, we already know it :P  */
    	fetchid_var = strsep(&stringp, " ");
    
    	while (1) {	/* ugly trick to make branches with break; */
    
    		var = fetchid_var; /* fetchid */
    		fnd = 0;
    
    		AST_LIST_TRAVERSE(headp, variables, entries) {
    			if (!(strncasecmp(ast_var_name(variables), fetchid_var, strlen(fetchid_var)))) {
    				s7 = ast_var_value(variables);
    				fnd = 1;
    				break;
    
    
    		if (!fnd) {
    			s7 = "0";
    			pbx_builtin_setvar_helper(chan, fetchid_var, s7);
    
    		s4 = strsep(&stringp, " ");
    		id = atoi(s4); /* resultid */
    		if (!(PGSQLres = find_identifier(id, AST_PGSQL_ID_RESID))) {
    			ast_log(LOG_WARNING, "Invalid result identifier %d passed in aPGSQL_fetch\n", id);
    			res = -1;
    
    Mark Spencer's avatar
    Mark Spencer committed
    			break;
    		}
    
    		id = atoi(s7); /*fetchid */
    		if (!(identp = find_identifier(id, AST_PGSQL_ID_FETCHID))) {
    			i = 0; /* fetching the very first row */
    
    Mark Spencer's avatar
    Mark Spencer committed
    		} else {
    
    			i = *identp;
    			free(identp);
    			del_identifier(id, AST_PGSQL_ID_FETCHID); /* will re-add it a bit later */
    
    Mark Spencer's avatar
    Mark Spencer committed
    		}
    
    		if (i < PQntuples(PGSQLres)) {
    			nres = PQnfields(PGSQLres);
    			ast_log(LOG_WARNING, "ast_PGSQL_fetch : nres = %d i = %d ;\n", nres, i);
    			for (j = 0; j < nres; j++) {
    				if (!(s5 = strsep(&stringp, " "))) {
    					ast_log(LOG_WARNING, "ast_PGSQL_fetch : More tuples (%d) than variables (%d)\n", nres, j);
    					break;
    				}
    				if (!(s6 = PQgetvalue(PGSQLres, i, j))) {
    					ast_log(LOG_WARNING, "PQgetvalue(res, %d, %d) returned NULL in ast_PGSQL_fetch\n", i, j);
    					break;
    				}
    				ast_log(LOG_WARNING, "===setting variable '%s' to '%s'\n", s5, s6);
    				pbx_builtin_setvar_helper(chan, s5, s6);
    
    Mark Spencer's avatar
    Mark Spencer committed
    			}
    
    			identp = malloc(sizeof(int));
    			*identp = ++i; /* advance to the next row */
    			id1 = add_identifier(AST_PGSQL_ID_FETCHID, identp);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		} else {
    
    			ast_log(LOG_WARNING, "ast_PGSQL_fetch : EOF\n");
    			id1 = 0; /* no more rows */
    
    Mark Spencer's avatar
    Mark Spencer committed
    		}
    
    		snprintf(s, sizeof(s), "%d", id1);
    
    		ast_log(LOG_WARNING, "Setting var '%s' to value '%s'\n", fetchid_var, s);
    		pbx_builtin_setvar_helper(chan, fetchid_var, s);
    		break;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	}
    
    Mark Spencer's avatar
    Mark Spencer committed
    	free(s1);
    	free(s2);
    
    static int aPGSQL_reset(struct ast_channel *chan, void *data)
    {
    	char *s1, *s3;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	int l;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	int id;
    
    	char *stringp = NULL;
    
    	l = strlen(data) + 2;
    	s1 = malloc(l);
    
    	stringp = s1;
    	strsep(&stringp, " "); /* eat the first token, we already know it :P  */
    	s3 = strsep(&stringp, " ");
    	id = atoi(s3);
    	if (!(PGSQLconn = find_identifier(id, AST_PGSQL_ID_CONNID))) {
    		ast_log(LOG_WARNING, "Invalid connection identifier %d passed in aPGSQL_reset\n", id);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	} else {
    
    static int aPGSQL_clear(struct ast_channel *chan, void *data)
    {
    	char *s1, *s3;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	int l;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	int id;
    
    	char *stringp = NULL;
    
    	l = strlen(data) + 2;
    	s1 = malloc(l);
    
    	stringp = s1;
    	strsep(&stringp, " "); /* eat the first token, we already know it :P  */
    	s3 = strsep(&stringp, " ");
    	id = atoi(s3);
    	if (!(PGSQLres = find_identifier(id, AST_PGSQL_ID_RESID))) {
    		ast_log(LOG_WARNING, "Invalid result identifier %d passed in aPGSQL_clear\n", id);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	} else {
    
    		PQclear(PGSQLres);
    		del_identifier(id, AST_PGSQL_ID_RESID);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	}
    
    Mark Spencer's avatar
    Mark Spencer committed
    	free(s1);
    
    static int aPGSQL_disconnect(struct ast_channel *chan, void *data)
    {
    	char *s1, *s3;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	int l;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	int id;
    
    	char *stringp = NULL;
    
    	l = strlen(data) + 2;
    	s1 = malloc(l);
    
    	stringp = s1;
    	strsep(&stringp, " "); /* eat the first token, we already know it :P  */
    	s3 = strsep(&stringp, " ");
    	id = atoi(s3);
    	if (!(PGSQLconn = find_identifier(id, AST_PGSQL_ID_CONNID))) {
    		ast_log(LOG_WARNING, "Invalid connection identifier %d passed in aPGSQL_disconnect\n", id);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	} else {
    
    		PQfinish(PGSQLconn);
    		del_identifier(id, AST_PGSQL_ID_CONNID);
    	}
    	
    
    Mark Spencer's avatar
    Mark Spencer committed
    	free(s1);
    	
    
    static int aPGSQL_debug(struct ast_channel *chan, void *data)
    {
    	ast_log(LOG_WARNING, "Debug : %s\n", (char *)data);
    	return 0;
    
    Mark Spencer's avatar
    Mark Spencer committed
    }
    
    static int PGSQL_exec(struct ast_channel *chan, void *data)
    {
    	struct localuser *u;
    	int result;
    
    
    	if (ast_strlen_zero(data)) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    		ast_log(LOG_WARNING, "APP_PGSQL requires an argument (see manual)\n");
    		return -1;
    	}
    
    Mark Spencer's avatar
    Mark Spencer committed
    	LOCAL_USER_ADD(u);
    
    	result = 0;
    
    	if (!(strncasecmp("connect", data, strlen("connect")))) {
    		result = (aPGSQL_connect(chan, data));
    	} else if (!(strncasecmp("query", data, strlen("query")))) {
    		result = (aPGSQL_query(chan, data));
    	} else if (!(strncasecmp("fetch", data, strlen("fetch")))) {
    		result = (aPGSQL_fetch(chan, data));
    	} else if (!(strncasecmp("reset", data, strlen("reset")))) {
    		result = (aPGSQL_reset(chan, data));
    	} else if (!(strncasecmp("clear", data, strlen("clear")))) {
    		result = (aPGSQL_clear(chan, data));
    	} else if (!(strncasecmp("debug", data, strlen("debug")))) {
    		result = (aPGSQL_debug(chan, data));
    	} else if (!(strncasecmp("disconnect", data, strlen("disconnect")))) {
    		result = (aPGSQL_disconnect(chan, data));
    
    Mark Spencer's avatar
    Mark Spencer committed
    	} else {
    
    		ast_log(LOG_WARNING, "Unknown APP_PGSQL argument : %s\n", (char *)data);
    		result = -1;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	}
    
    Mark Spencer's avatar
    Mark Spencer committed
    	return result;
    }
    
    int unload_module(void)
    {
    
    Mark Spencer's avatar
    Mark Spencer committed
    	STANDARD_HANGUP_LOCALUSERS;
    
    Mark Spencer's avatar
    Mark Spencer committed
    }
    
    int load_module(void)
    {
    
    	struct PGSQLidshead *headp = &PGSQLidshead;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	AST_LIST_HEAD_INIT(headp);
    	return ast_register_application(app, PGSQL_exec, synopsis, descrip);
    }
    
    char *description(void)
    {
    	return tdesc;
    }
    
    int usecount(void)
    {
    	int res;
    	STANDARD_USECOUNT(res);
    	return res;
    }
    
    char *key()
    {
    	return ASTERISK_GPL_KEY;
    }