login | register
Sun 12 of Oct, 2008 [09:08 UTC]

voip-info.org

Discuss [20] History

Asterisk cmd MYSQL

Created by: drencrom,Last modification on Tue 01 of Jul, 2008 [18:01 UTC] by rushowr

Synopsis

 Basic MYSQL Database Functionality

Installation

This is part of the asterisk-addons package available via the Digium Subversion asterisk-addons repository, or as a tarball from the Asterisk website. This is an ADD-ON of asterisk, is not installed by default and must be downloaded and installed with the asterisk-addons package.

Description

MYSQL(): Basic MYSQL Database Functionality

Syntax

(note that the help information returned from 'show application MYSQL' is a little misleading - what's shown below is easier to understand!)

MYSQL(Connect connid dhhost dbuser dbpass dbname)

Connects to a database. Arguments contain standard MySQL parameters passed to function mysql_real_connect. Connection identifer returned in ${connid}. If the connection wasn't possible, then ${connid} == "".

MYSQL(Query resultid ${connid} query-string)

Executes standard MySQL query contained in query-string using established connection identified by ${connid}. Result of query is stored in ${resultid}.

MYSQL(Fetch fetchid ${resultid} var1\ var2\ ...\ varN)

If any rows are available to select, ${fetchid} is set to 1 and a single row is fetched from a result set contained in ${resultid}. The return fields are assigned to ${var1}, ${var2} ... ${varN} respectively. If no rows are left to select, ${fetchid} is set to 0 and ${var1}, ${var2} ... ${varN} remain unchanged.

MYSQL(Clear ${resultid})

Frees memory and data structures associated with result set.

MYSQL(Disconnect ${connid})

Disconnects from named connection to MySQL.

Examples

exten => _X.,1,MYSQL(Connect connid localhost dbuser dbpass dbname)
exten => _X.,2,MYSQL(Query resultid ${connid} SELECT\ scriptname\ from\ mac2pin\ where\ userid=${CALLERIDNAME})
exten => _X.,3,MYSQL(Fetch fetchid ${resultid} AGIScript)
exten => _X.,4,GotoIf($[${AGIScript} = NULL]?5:7)
exten => _X.,5,AGI(${DefaultAGIScript},${EXTEN})
exten => _X.,6,Goto(_X.,8)
exten => _X.,7,AGI(${AGIScript},${EXTEN})
exten => _X.,8,MYSQL(Clear ${resultid})
exten => _X.,9,MYSQL(Disconnect ${connid})
exten => _X.,10,Hangup

In the above example, if the user hangs up at either priority 5 or priority 7, MYSQL(Clear...) and MYSQL(Disconnect....) will never be executed. This will constantly create connections over and over again as calls progressively increase. Eventually, MySQL will not allow anymore connections. (Depending on the limit set in the mysql conf file). Therefore, in this case the following will work:

exten => _X.,1,MYSQL(Connect connid localhost asterisk dbpass asterisk)
exten => _X.,2,MYSQL(Query resultid ${connid} SELECT\ scriptname\ from\ mac2pin\ where\ userid=${CALLERIDNAME})
exten => _X.,3,MYSQL(Fetch fetchid ${resultid} AGIScript)
exten => _X.,4,MYSQL(Clear ${resultid})
exten => _X.,5,MYSQL(Disconnect ${connid})
exten => _X.,6,GotoIf($[${fetchid}]?7:9)
exten => _X.,7,AGI(${DefaultAGIScript},${EXTEN})
exten => _X.,8,Hangup
exten => _X.,9,AGI(${AGIScript},${EXTEN})
exten => _X.,10,Hangup

Notice in this case, I am clearing and disconnecting after the Fetch. This ensures that before we even reach the AGI script to do a task, we ensure that Clearing and Disconnecting takes place unless the call is hungup after the connection and before the disconnect. The best way to handle this situation is to clear connections in the 'h' or hangup extension.

Other Things to Consider:

  • Escape spaces with \ in the query string. If you use quotes they are passed to the application as part of the string
  • Other characters that need to be escaped are quotes (\' and \"), commas (\,), tilde (\`), and backslash (\\). (Try Mysqlscape - see below)
  • The fields returned are assigned to the variables in the same order that they are returned by MySQL. It is not recommended to "SELECT *" because you cannot guarantee in which order the fields will be returned. You do not need to give them pretty names "SELECT (long ass query field) as shortfieldname" because the field name is irrelevant to the order.
  • The asterisk online command description states that ${fetchid} is set to TRUE if more rows are available. This is incorrect. It is set to 1 if a row was found in the last fetch and 0 if no row was found.
  • There appears to be a buglet in 'app_addon_sql_mysql.c' - change the line "#ifdef EXTRA_LOG" to "#if EXTRA_LOG" (save and 'make install') and you'll stop seeing warning messages like "May 21 15:51:53 WARNING[5309]: app_addon_sql_mysql.c:318 aMYSQL_fetch: ast_MYSQL_fetch: numFields=4".

Example

exten => 888,1,MYSQL(Connect connid localhost ipcontact passwd ipcontact)
exten => 888,n,GotoIf($["${connid}" = ""]?error,1)
exten => 888,n,MYSQL(Query resultid ${connid} SELECT\ `number`\ FROM\ `phones`\ WHERE\ `channel`=\'${chan}\')
exten => 888,n(fetchrow),MYSQL(Fetch foundRow ${resultid} number) ; fetch row
exten => 888,n,GotoIf($["${foundRow}" = "1"]?done) ; leave loop if no row found
exten => 888,n,NoOp(${number})
exten => 888,n,Goto(fetchrow) ; continue loop if row found
exten => 888,n(done),MYSQL(Clear ${resultid})
exten => 888,n,MYSQL(Disconnect ${connid})

exten => error,1,NoOp(Connection error - do whatever we have to do to crash nicely!)
exten => error,n,Hangup

Notes

  • Please note the corrections to MYSQL(Fetch). ${fetchid} does not necessarily return 1 if there are more available rows, it returns 1 if there was a row available during that call to MYSQL(Fetch). This appears to have been inaccurately explained since the creation of this page. I have corrected this above and in the example below. - Flobi.

  • If you do not properly Clear and Disconnect from MySQL, connections will persist and overload MySQL. The only way to undo this is to shutdown Asterisk and restart it, or use mytop and kill the processes that are hung. To avoid this, clean up connections in the 'h' extension. - R. Mills

  • Be sure to close connections and handle clean up in the h extension. This will ensure that on hangup connections do not persist. - R.Mills

  • Just to clear up the issue. There should be no problem selecting multiple fields. Be sure your syntax is correct. - R.Mills



More MYSQL Information


Check this page and Asterisk Queue Callback for background.

  • These two sets of statements accomplish the same thing:

  1. exten => 1,n,MYSQL(Query resultid ${connid} INSERT\ INTO\ callers\ SET\ uniqueid=${UNIQUEID}\, callback=1\, callbacknum=${CALLERID:-11:11})

  1. exten => 2,n,MYSQL(Query resultid ${connid} INSERT\ INTO\ callers\ SET\ uniqueid=${UNIQUEID})
  2. exten => 2,n,MYSQL(Query resultid ${connid} UPDATE\ callers\ SET\ callback=1\, callbacknum=${CALLERID:-11:11}\ WHERE\ uniqueid=${UNIQUEID})

    • Notice that in extension 2 the resultid value didn't need to be cleared.

  • These two sets of statements accomplish the same thing:

  1. exten => 1,n,MYSQL(Query resultid ${connid} DELETE\ FROM\ callers\ WHERE\ callbacknum=${cbn})

  1. exten => 2,n,System(mysql -u acd -h 127.0.0.1 -e "DELETE FROM callers WHERE callbacknum=${cbn}" --password=acdpass acd)

  • Now, in order to use the MYSQL() delete in stead of the System delete you'll have to clear the resultid value between queries:

  1. exten => 2,n,MYSQL(Query resultid ${connid} SELECT\ callbacknum\ FROM\ callers\ WHERE\ callbacknum=${CALLERID:-11:11})
  2. exten => 2,n,MYSQL(Fetch fetchid ${resultid} cbn)
  3. exten => 2,n,MYSQL(Clear ${resultid})
  4. exten => 2,n,MYSQL(Query resultid ${connid} DELETE\ FROM\ callers\ WHERE\ callbacknum=${cbn})

  • Moreover, if you are working with a TimeStamp column, you can retrieve values without the delimiters by appending a '+0' to the column name:

  1. exten => 1,n,MYSQL(Query resultid ${connid} SELECT\ called+0\ FROM\ callers\ WHERE\ callbacknum=${CALLERID(num):-11:11})
  2. This returns datetime yyyymmddhhmmss.


Comments

I find it easier to just disconnect and clear in the h extension, no problems.

Alternative: App RealTime

Q: What is a better way to do it then in terms of performance, security, and flexibility? Using exec and a shell script, or agi or something else?

A: Setup extconfig to have realtime access to the database/table you want to pull info from, then in the dialplan use the app Realtime.
Use the RealTime config handler system to read data into channel variables:

 RealTime(<family>|<colmatch>|<value>[|<prefix>])

All unique column names will be set as channel variables with optional prefix to the name. e.g. prefix of 'var_' would make the column 'name' become the variable ${var_name}
This will not show any auth info in the asterisk cli and automatically clears connect and fetch id's, works great and decreases the number of priority routines within an extension.

See also



86614 views strong.


Asterisk | Applications | Functions | Variables | Expressions | Asterisk FAQ


Comments

Comments Filter
222

333MySQL Macro Examples

by forrest.beck, Tuesday 23 of October, 2007 [02:48:33 UTC]
I posted some examples on the wiki:

http://www.voip-info.org/wiki/view/MySQL+Macros+for+Asterisk+RealTime
222

333Exampel of dialplan with error check

by litnimax, Monday 02 of July, 2007 [09:48:17 UTC]
Comments 1-st.
- This dialplan is used from many places in the following manner:

       Set(QUERY=SELECT number FROM pbx_speeddial WHERE account_id='${USER_ACCOUNT}' AND sequence='${EXTEN}');
       Set(RESULT_VAR=number);
       Gosub(db-query,${EXTEN},1);

- You can comment disconnect block and use one connection for all queries. It works perfect on SELECT but sometimes fails when using stored procedures CALL proc.

               MySQL(disconnect ${connid});
               Set(connid=);



/ Connect if required
       if (${ISNULL(${connid})} = 1) {
           Verbose(***DEBUG*** Connecting connid to the database.);
           MySQL(connect connid ${DBHOST} ${DBUSER} ${DBPASS} ${DB});
           if (${MYSQL_STATUS} = -1) {
               Gosub(causes,100,db-connection-failed);
               Hangup;
           }
           else {
               Verbose(***DEBUG*** Connected to the database. Status: ${MYSQL_STATUS}. connid ${connid});
           }
       }
       else {
           Verbose(***DEBUG*** Reusing existing connid ${connid});
       }
       
       // Connected. Now execute the query.
       MySQL(query resultid ${connid} ${QUERY});
       if (${MYSQL_STATUS} = -1) {
           Verbose(***ERROR*** Query returned error ${MYSQL_STATUS}.);
           Gosub(causes,100,database-error);
           Gosub(db-disconnect,100,1);
           Hangup;
       }
       else {
           Verbose(***DEBUG*** Successfully executed database query ${QUERY});
       }
       // Query executed. Fetch the result if any
       if (${LEN(${resultid})} != 0) { 
           MySQL(fetch fetchid ${resultid} ${RESULT_VAR}); 
           if (${MYSQL_STATUS} = -1) {
               Gosub(causes,100,database-error);
               Gosub(db-disconnect,100,1);
               Hangup;
           }
           else {
               MySQL(clear ${resultid});
               Verbose(***DEBUG*** Successfully fetched ${${RESULT_VAR}} into ${RESULT_VAR});
               MySQL(disconnect ${connid});
               Set(connid=);
           }
       }
       Set(resultid=);
       Set(fetchid=);
       Set(RESULT_VAR=);
       Set(QUERY=);
       Return;
   }
}


Comments are welcome.
222

333MySQL(query ...) bug

by litnimax, Monday 02 of July, 2007 [09:43:53 UTC]
app_mysql returns MYSQL_STATUS dialplan variable (see CLI> show application mysql) that equals to 0 when operation was successful.
It works as expected on MySQL(connect ...) but always returns 0 on MySQL(query ...) operation. Even when you put bad SQL it returns 0.
The problem is that mysql_query function call is not analyzed only number of rows. This is fixed in the following patch.



+++ app_addon_sql_mysql.c 2007-07-02 12:28:18.000000000 +0300 @@ -253,6 +253,7 @@

       char *resultid_var;
       int connid;
+ int query_status;        char *querystring;

       strsep(&data," "); // eat the first token, we already know it :P 
@@ -263,7 +264,9 @@

       if (resultid_var && (connid>=0) && querystring) {
               if mysql=find_identifier(connid,AST_MYSQL_ID_CONNID!=NULL) {
- mysql_query(mysql,querystring);
+ query_status = mysql_query(mysql,querystring); + if (query_status != 0) + return -1;                        if mysqlres=mysql_use_result(mysql!=NULL) {
                               add_identifier_and_set_asterisk_int(chan,resultid_var,AST_MYSQL_ID_RESID,mysqlres);
                               return 0;

222

333Re: Calling MySQL 5 stored procedures from app_mysql

by litnimax, Monday 02 of July, 2007 [09:15:51 UTC]
rushowr, CLIENT_MULTI_STATEMENTS|CLIENT_MULTI_RESULTS is already in trunk from Mar 2007. Both are needed. It allows calling stored procedures (MULTI_RESULTS) and grouping several mysql queries in one like MySQL(query resultid ${connectid} SELECT a; SELECT b).
Also I have some other patch. I will port it in next comment with proper subject.
222

333Re: Calling MySQL 5 stored procedures from app_mysql

by rushowr, Friday 18 of August, 2006 [00:20:56 UTC]
litnimax, In your replacement line, is it supposed to be:

 if (mysql_real_connect(mysql,dbhost,dbuser,dbpass,dbname,0,NULL,CLIENT_MULTI_STATEMENTS|CLIENT_MULTI_RESULTS)) {

or are the to new additions represented as an either or, using one item or the other such as:

 if (mysql_real_connect(mysql,dbhost,dbuser,dbpass,dbname,0,NULL,CLIENT_MULTI_STATEMENTS)) {

or

 if (mysql_real_connect(mysql,dbhost,dbuser,dbpass,dbname,0,NULL,CLIENT_MULTI_RESULTS)) {

Also, if this works reliably, I'd like to see it put into the addons project. If you don't want to submit it, I will with a credit to you for the actual code.
222

333

by mindaugas_kezys, Thursday 01 of June, 2006 [08:01:57 UTC]
exten => 888,4,GotoIf($["${foundRow}" = "1"]?7:5) ; leave loop if no row found

should be fixed to

exten => 888,4,GotoIf($["${foundRow}" = "0"]?7:5) ; leave loop if no row found

in order to work
222

333Re: DML statements in MySQL

by zvik, Thursday 09 of February, 2006 [00:11:16 UTC]
You may use update statement, below is an example that works for me:
exten => _2XXX,6,MYSQL(Connect connid localhost asterisk passwd asterisk_db)
exten => _2XXX,7,MYSQL(Query resultid ${connid} update\ AGENTS\ set\ floortime=\'NO\')
exten => _2XXX,8,MYSQL(Query resultid ${connid} update\ AGENTS\ set\ floortime=\'YES\'\ where\ ext=${junk})
exten => _2XXX,9,Wait(1)
exten => _2XXX,10,MYSQL(Clear ${resultid})
exten => _2XXX,11,MYSQL(Disconnect ${connid})
222

333Re: DML statements in MySQL

by swesche, Friday 27 of January, 2006 [09:18:28 UTC]
Please ignore my endless stupidity. It works by default. Just enter an INSERT or UPDATE statement in the query - as you would always do.

I must have been drunk.
Apologies!
222

333DML statements in MySQL

by swesche, Tuesday 24 of January, 2006 [11:08:44 UTC]
I have a requirement to update data in a MySQL database directly from the dialplan.

Does anyone know if there is an extension to the MySQL addon that allows DML? I am looking for something along the lines of:
exten => s,2,MYSQL(UPDATE ${connid} UPDATE <table> SET <column>=<value>)
and
exten => s,2,MYSQL(INSERT ${connid} INSERT INTO <table> VALUES (col1, col2,col3))

Is this possible? Is there maybe another way to achieve the same?
Any help greatly appreciated. Thanks in advance.
222

333Calling MySQL 5 stored procedures from app_mysql

by litnimax, Friday 20 of January, 2006 [22:39:19 UTC]
You had to change mysql_real_connect string in app_addon_sql_mysql.c and add CLIENT_MULTI_RESULTS and/or CLIENT_MULTI_RESULTS flag.

mysql_real_connect(&mysql,...,CLIENT_MULTI_RESULTS);

In my app_addon_sql_mysql.c it is line 230:
if (mysql_real_connect(mysql,dbhost,dbuser,dbpass,dbname,0,NULL,CLIENT_MULTI_STATEMENTS|CLIENT_MULTI_RESULTS))

Now
make clean
make
and copy fresh .so file to modules and restart asterisk.
Enjoy!

 — Executing Macro("IAX2/100-3", "local|100") in new stack
   — Executing MYSQL("IAX2/100-3", "Connect connid localhost asterisk asterisk pbx_manager_new") in new stack
   — Executing Set("IAX2/100-3", "QUERY=call p_pbxuser_user_data1('100')") in new stack
   — Executing MYSQL("IAX2/100-3", "Query resultid 1 call p_pbxuser_user_data1('100')") in new stack
   — Executing MYSQL("IAX2/100-3", "Fetch fetchid 2 vm_active") in new stack
Jan 21 00:41:46 WARNING32426: app_addon_sql_mysql.c:318 aMYSQL_fetch: ast_MYSQL_fetch: numFields=1
   — Executing NoOp("IAX2/100-3", "Voicemail active: 1") in new stack