2011-09-11 17:09:36 +00:00
/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 2011, Terry Wilson
*
* Terry Wilson <twilson@digium.com>
*
* 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.
*
* 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.
*
* Please follow coding guidelines
* http://svn.digium.com/view/asterisk/trunk/doc/CODING-GUIDELINES
*/
/*! \file
*
* \brief SQLite 3 configuration engine
*
* \author\verbatim Terry Wilson <twilson@digium.com> \endverbatim
*
* This is a realtime configuration engine for the SQLite 3 Database
* \ingroup resources
*/
/*** MODULEINFO
<depend>sqlite3</depend>
<support_level>core</support_level>
***/
#include "asterisk.h"
ASTERISK_FILE_VERSION ( __FILE__ , "$Revision$" )
#include <sqlite3.h>
#include "asterisk/module.h"
#include "asterisk/config.h"
#include "asterisk/paths.h"
#include "asterisk/astobj2.h"
#include "asterisk/lock.h"
#include "asterisk/utils.h"
#include "asterisk/app.h"
/*** DOCUMENTATION
***/
static struct ast_config * realtime_sqlite3_load ( const char * database , const char * table , const char * configfile , struct ast_config * config , struct ast_flags flags , const char * suggested_include_file , const char * who_asked );
static struct ast_variable * realtime_sqlite3 ( const char * database , const char * table , va_list ap );
static struct ast_config * realtime_sqlite3_multi ( const char * database , const char * table , va_list ap );
static int realtime_sqlite3_update ( const char * database , const char * table , const char * keyfield , const char * entity , va_list ap );
static int realtime_sqlite3_update2 ( const char * database , const char * table , va_list ap );
static int realtime_sqlite3_store ( const char * database , const char * table , va_list ap );
static int realtime_sqlite3_destroy ( const char * database , const char * table , const char * keyfield , const char * entity , va_list ap );
static int realtime_sqlite3_require ( const char * database , const char * table , va_list ap );
static int realtime_sqlite3_unload ( const char * database , const char * table );
struct ast_config_engine sqlite3_config_engine = {
. name = "sqlite3" ,
. load_func = realtime_sqlite3_load ,
. realtime_func = realtime_sqlite3 ,
. realtime_multi_func = realtime_sqlite3_multi ,
. update_func = realtime_sqlite3_update ,
. update2_func = realtime_sqlite3_update2 ,
. store_func = realtime_sqlite3_store ,
. destroy_func = realtime_sqlite3_destroy ,
. require_func = realtime_sqlite3_require ,
. unload_func = realtime_sqlite3_unload ,
};
enum {
REALTIME_SQLITE3_REQ_WARN ,
REALTIME_SQLITE3_REQ_CLOSE ,
REALTIME_SQLITE3_REQ_CHAR ,
};
struct realtime_sqlite3_db {
AST_DECLARE_STRING_FIELDS (
AST_STRING_FIELD ( name );
AST_STRING_FIELD ( filename );
);
sqlite3 * handle ;
pthread_t syncthread ;
ast_cond_t cond ;
unsigned int requirements : 2 ;
unsigned int dirty : 1 ;
unsigned int debug : 1 ;
unsigned int exiting : 1 ;
unsigned int wakeup : 1 ;
unsigned int batch ;
};
struct ao2_container * databases ;
#define DB_BUCKETS 7
AST_MUTEX_DEFINE_STATIC ( config_lock );
/* We need a separate buffer for each field we might use concurrently */
AST_THREADSTORAGE ( escape_table_buf );
AST_THREADSTORAGE ( escape_column_buf );
AST_THREADSTORAGE ( escape_value_buf );
static int realtime_sqlite3_execute_handle ( struct realtime_sqlite3_db * db , const char * sql , int ( * callback )( void * , int , char ** , char ** ), void * arg , int sync );
void db_start_batch ( struct realtime_sqlite3_db * db );
void db_stop_batch ( struct realtime_sqlite3_db * db );
static inline const char * sqlite3_escape_string_helper ( struct ast_threadstorage * ts , const char * param )
{
size_t maxlen = strlen ( param ) * 2 + sizeof ( " \"\" " );
/* It doesn't appear that sqlite3_snprintf will do more than double the
* length of a string with %q as an option. %Q could double and possibly
* add two quotes, and convert NULL pointers to the word "NULL", but we
* don't allow those anyway. Just going to use %q for now. */
struct ast_str * buf = ast_str_thread_get ( ts , maxlen );
char * tmp = ast_str_buffer ( buf );
char q = ts == & escape_value_buf ? '\'' : '"' ;
ast_str_reset ( buf );
* tmp ++ = q ; /* Initial quote */
while (( * tmp ++ = * param ++ )) {
/* Did we just copy a quote? Then double it. */
if ( * ( tmp - 1 ) == q ) {
* tmp ++ = q ;
}
}
* tmp = '\0' ; /* Terminate past NULL from copy */
* ( tmp - 1 ) = q ; /* Replace original NULL with the quote */
ast_str_update ( buf );
return ast_str_buffer ( buf );
}
static inline const char * sqlite3_escape_table ( const char * param )
{
return sqlite3_escape_string_helper ( & escape_table_buf , param );
}
static inline const char * sqlite3_escape_column ( const char * param )
{
return sqlite3_escape_string_helper ( & escape_column_buf , param );
}
/* Not inlining this function because it uses strdupa and I don't know if the compiler would be dumb */
static const char * sqlite3_escape_column_op ( const char * param )
{
size_t maxlen = strlen ( param ) * 2 + sizeof ( " \"\" =" );
struct ast_str * buf = ast_str_thread_get ( & escape_column_buf , maxlen );
char * tmp = ast_str_buffer ( buf );
int space = 0 ;
ast_str_reset ( buf );
* tmp ++ = '"' ;
while (( * tmp ++ = * param ++ )) {
/* If we have seen a space, don't double quotes. XXX If we ever make the column/op field
* available to users via an API, we will definitely need to avoid allowing special
* characters like ';' in the data past the space as it will be unquoted data */
if ( space ) {
continue ;
}
if ( * ( tmp - 1 ) == ' ' ) {
* ( tmp - 1 ) = '"' ;
* tmp ++ = ' ' ;
space = 1 ;
} else if ( * ( tmp - 1 ) == '"' ) {
* tmp ++ = '"' ;
}
}
if ( ! space ) {
strcpy ( tmp - 1 , " \" =" );
}
ast_str_update ( buf );
return ast_str_buffer ( buf );
}
static inline const char * sqlite3_escape_value ( const char * param )
{
return sqlite3_escape_string_helper ( & escape_value_buf , param );
}
static int db_hash_fn ( const void * obj , const int flags )
{
const struct realtime_sqlite3_db * db = obj ;
return ast_str_hash ( flags & OBJ_KEY ? ( const char * ) obj : db -> name );
}
static int db_cmp_fn ( void * obj , void * arg , int flags ) {
struct realtime_sqlite3_db * db = obj , * other = arg ;
const char * name = arg ;
return ! strcasecmp ( db -> name , flags & OBJ_KEY ? name : other -> name ) ? CMP_MATCH | CMP_STOP : 0 ;
}
static void db_destructor ( void * obj )
{
struct realtime_sqlite3_db * db = obj ;
ast_debug ( 1 , "Destroying db: %s \n " , db -> name );
ast_string_field_free_memory ( db );
db_stop_batch ( db );
if ( db -> handle ) {
ao2_lock ( db );
sqlite3_close ( db -> handle );
ao2_unlock ( db );
}
}
static struct realtime_sqlite3_db * find_database ( const char * database )
{
return ao2_find ( databases , database , OBJ_KEY );
}
static void unref_db ( struct realtime_sqlite3_db ** db )
{
ao2_ref ( * db , - 1 );
* db = NULL ;
}
static int stop_batch_cb ( void * obj , void * arg , int flags )
{
struct realtime_sqlite3_db * db = obj ;
db_stop_batch ( db );
return CMP_MATCH ;
}
static int mark_dirty_cb ( void * obj , void * arg , int flags )
{
struct realtime_sqlite3_db * db = obj ;
db -> dirty = 1 ;
return CMP_MATCH ;
}
static void mark_all_databases_dirty ( void )
{
ao2_callback ( databases , OBJ_MULTIPLE | OBJ_NODATA , mark_dirty_cb , NULL );
}
static int is_dirty_cb ( void * obj , void * arg , int flags )
{
struct realtime_sqlite3_db * db = obj ;
if ( db -> dirty ) {
db_stop_batch ( db );
return CMP_MATCH ;
}
return 0 ;
}
static void unlink_dirty_databases ( void )
{
ao2_callback ( databases , OBJ_MULTIPLE | OBJ_NODATA | OBJ_UNLINK , is_dirty_cb , NULL );
}
static int str_to_requirements ( const char * data )
{
if ( ! strcasecmp ( data , "createclose" )) {
return REALTIME_SQLITE3_REQ_CLOSE ;
} else if ( ! strcasecmp ( data , "createchar" )) {
return REALTIME_SQLITE3_REQ_CHAR ;
}
/* default */
return REALTIME_SQLITE3_REQ_WARN ;
}
/*! \note Since this is called while a query is executing, we should already hold the db lock */
static void trace_cb ( void * arg , const char * sql )
{
struct realtime_sqlite3_db * db = arg ;
ast_debug ( 3 , "DB: %s SQL: %s \n " , db -> name , sql );
}
/*! \brief Wrap commands in transactions increased write performance */
static void * db_sync_thread ( void * data )
{
struct realtime_sqlite3_db * db = data ;
ao2_lock ( db );
realtime_sqlite3_execute_handle ( db , "BEGIN TRANSACTION" , NULL , NULL , 0 );
for (;;) {
if ( ! db -> wakeup ) {
ast_cond_wait ( & db -> cond , ao2_object_get_lockaddr ( db ));
}
db -> wakeup = 0 ;
if ( realtime_sqlite3_execute_handle ( db , "COMMIT" , NULL , NULL , 0 ) < 0 ) {
realtime_sqlite3_execute_handle ( db , "ROLLBACK" , NULL , NULL , 0 );
}
if ( db -> exiting ) {
ao2_unlock ( db );
break ;
}
realtime_sqlite3_execute_handle ( db , "BEGIN TRANSACTION" , NULL , NULL , 0 );
ao2_unlock ( db );
usleep ( 1000 * db -> batch );
ao2_lock ( db );
}
unref_db ( & db );
return NULL ;
}
/*! \brief Open a database and appropriately set debugging on the db handle */
static int db_open ( struct realtime_sqlite3_db * db )
{
ao2_lock ( db );
if ( sqlite3_open ( db -> filename , & db -> handle ) != SQLITE_OK ) {
ast_log ( LOG_WARNING , "Could not open %s: %s \n " , db -> filename , sqlite3_errmsg ( db -> handle ));
ao2_unlock ( db );
return - 1 ;
}
if ( db -> debug ) {
sqlite3_trace ( db -> handle , trace_cb , db );
} else {
sqlite3_trace ( db -> handle , NULL , NULL );
}
ao2_unlock ( db );
return 0 ;
}
static void db_sync ( struct realtime_sqlite3_db * db )
{
db -> wakeup = 1 ;
ast_cond_signal ( & db -> cond );
}
void db_start_batch ( struct realtime_sqlite3_db * db )
{
if ( db -> batch ) {
ast_cond_init ( & db -> cond , NULL );
ao2_ref ( db , + 1 );
ast_pthread_create_background ( & db -> syncthread , NULL , db_sync_thread , db );
}
}
void db_stop_batch ( struct realtime_sqlite3_db * db )
{
if ( db -> batch ) {
db -> exiting = 1 ;
db_sync ( db );
pthread_join ( db -> syncthread , NULL );
}
}
/*! \brief Create a db object based on a config category
* \note Opening the db handle and linking to databases must be handled outside of this function
*/
static struct realtime_sqlite3_db * new_realtime_sqlite3_db ( struct ast_config * config , const char * cat )
{
struct ast_variable * var ;
struct realtime_sqlite3_db * db ;
if ( ! ( db = ao2_alloc ( sizeof ( * db ), db_destructor ))) {
return NULL ;
}
if ( ast_string_field_init ( db , 64 )) {
unref_db ( & db );
return NULL ;
}
/* Set defaults */
db -> requirements = REALTIME_SQLITE3_REQ_WARN ;
db -> batch = 100 ;
ast_string_field_set ( db , name , cat );
for ( var = ast_variable_browse ( config , cat ); var ; var = var -> next ) {
if ( ! strcasecmp ( var -> name , "dbfile" )) {
ast_string_field_set ( db , filename , var -> value );
} else if ( ! strcasecmp ( var -> name , "requirements" )) {
db -> requirements = str_to_requirements ( var -> value );
} else if ( ! strcasecmp ( var -> name , "batch" )) {
ast_app_parse_timelen ( var -> value , ( int * ) & db -> batch , TIMELEN_MILLISECONDS );
} else if ( ! strcasecmp ( var -> name , "debug" )) {
db -> debug = ast_true ( var -> value );
}
}
if ( ast_strlen_zero ( db -> filename )) {
ast_log ( LOG_WARNING , "Must specify dbfile in res_config_sqlite3.conf \n " );
unref_db ( & db );
return NULL ;
}
return db ;
}
/*! \brief Update an existing db object based on config data
* \param db The database object to update
* \param config The configuration data with which to update the db
* \param cat The config category (which becomes db->name)
*/
static int update_realtime_sqlite3_db ( struct realtime_sqlite3_db * db , struct ast_config * config , const char * cat )
{
struct realtime_sqlite3_db * new ;
if ( ! ( new = new_realtime_sqlite3_db ( config , cat ))) {
return - 1 ;
}
/* Copy fields that don't need anything special done on change */
db -> requirements = new -> requirements ;
/* Handle changes that require immediate behavior modification */
if ( db -> debug != new -> debug ) {
if ( db -> debug ) {
sqlite3_trace ( db -> handle , NULL , NULL );
} else {
sqlite3_trace ( db -> handle , trace_cb , db );
}
db -> debug = new -> debug ;
}
if ( strcmp ( db -> filename , new -> filename )) {
sqlite3_close ( db -> handle );
ast_string_field_set ( db , filename , new -> filename );
db_open ( db ); /* Also handles setting appropriate debug on new handle */
}
if ( db -> batch != new -> batch ) {
if ( db -> batch == 0 ) {
db -> batch = new -> batch ;
db_start_batch ( db );
} else if ( new -> batch == 0 ) {
db -> batch = new -> batch ;
db_stop_batch ( db );
}
db -> batch = new -> batch ;
}
db -> dirty = 0 ;
unref_db ( & new );
return 0 ;
}
/*! \brief Create a varlist from a single sqlite3 result row */
static int row_to_varlist ( void * arg , int num_columns , char ** values , char ** columns )
{
struct ast_variable ** head = arg , * tail ;
int i ;
struct ast_variable * new ;
if ( ! ( new = ast_variable_new ( columns [ 0 ], S_OR ( values [ 0 ], "" ), "" ))) {
return SQLITE_ABORT ;
}
* head = tail = new ;
for ( i = 1 ; i < num_columns ; i ++ ) {
if ( ! ( new = ast_variable_new ( columns [ i ], S_OR ( values [ i ], "" ), "" ))) {
ast_variables_destroy ( * head );
* head = NULL ;
return SQLITE_ABORT ;
}
tail -> next = new ;
tail = new ;
}
return 0 ;
}
/*! \brief Callback for creating an ast_config from a successive sqlite3 result rows */
static int append_row_to_cfg ( void * arg , int num_columns , char ** values , char ** columns )
{
struct ast_config * cfg = arg ;
struct ast_category * cat ;
int i ;
if ( ! ( cat = ast_category_new ( "" , "" , 99999 ))) {
return SQLITE_ABORT ;
}
for ( i = 0 ; i < num_columns ; i ++ ) {
struct ast_variable * var ;
if ( ! ( var = ast_variable_new ( columns [ i ], S_OR ( values [ i ], "" ), "" ))) {
ast_log ( LOG_ERROR , "Could not create new variable for '%s: %s', throwing away list \n " , columns [ i ], values [ i ]);
continue ;
}
ast_variable_append ( cat , var );
}
ast_category_append ( cfg , cat );
return 0 ;
}
/*!
* Structure sent to the SQLite 3 callback function for static configuration.
*
* \see static_realtime_cb()
*/
struct cfg_entry_args {
struct ast_config * cfg ;
struct ast_category * cat ;
char * cat_name ;
struct ast_flags flags ;
const char * who_asked ;
};
/*! Exeute an SQL statement given the database object
*
* \retval -1 ERROR
* \retval > -1 Number of rows changed
*/
static int realtime_sqlite3_execute_handle ( struct realtime_sqlite3_db * db , const char * sql , int ( * callback )( void * , int , char ** , char ** ), void * arg , int sync )
{
int res = 0 ;
char * errmsg ;
ao2_lock ( db );
if ( sqlite3_exec ( db -> handle , sql , callback , arg , & errmsg ) != SQLITE_OK ) {
ast_log ( LOG_WARNING , "Could not execute '%s': %s \n " , sql , errmsg );
sqlite3_free ( errmsg );
res = - 1 ;
} else {
res = sqlite3_changes ( db -> handle );
}
ao2_unlock ( db );
if ( sync ) {
db_sync ( db );
}
return res ;
}
/*! Exeute an SQL statement give the database name
*
* \retval -1 ERROR
* \retval > -1 Number of rows changed
*/
static int realtime_sqlite3_execute ( const char * database , const char * sql , int ( * callback )( void * , int , char ** , char ** ), void * arg , int sync )
{
struct realtime_sqlite3_db * db ;
int res ;
if ( ! ( db = find_database ( database ))) {
ast_log ( LOG_WARNING , "Could not find database: %s \n " , database );
return - 1 ;
}
res = realtime_sqlite3_execute_handle ( db , sql , callback , arg , sync );
ao2_ref ( db , - 1 );
return res ;
}
/*! \note It is important that the COL_* enum matches the order of the columns selected in static_sql */
static const char * static_sql = "SELECT category, var_name, var_val FROM \" %q \" WHERE filename = %Q AND commented = 0 ORDER BY cat_metric ASC, var_metric ASC" ;
enum {
COL_CATEGORY ,
COL_VAR_NAME ,
COL_VAR_VAL ,
COL_COLUMNS ,
};
static int static_realtime_cb ( void * arg , int num_columns , char ** values , char ** columns )
{
struct cfg_entry_args * args = arg ;
struct ast_variable * var ;
if ( ! strcmp ( values [ COL_VAR_NAME ], "#include" )) {
struct ast_config * cfg ;
char * val ;
val = values [ COL_VAR_VAL ];
if ( ! ( cfg = ast_config_internal_load ( val , args -> cfg , args -> flags , "" , args -> who_asked ))) {
ast_log ( LOG_WARNING , "Unable to include %s \n " , val );
return SQLITE_ABORT ;
} else {
args -> cfg = cfg ;
return 0 ;
}
}
if ( ! args -> cat_name || strcmp ( args -> cat_name , values [ COL_CATEGORY ])) {
if ( ! ( args -> cat = ast_category_new ( values [ COL_CATEGORY ], "" , 99999 ))) {
ast_log ( LOG_WARNING , "Unable to allocate category \n " );
return SQLITE_ABORT ;
}
ast_free ( args -> cat_name );
if ( ! ( args -> cat_name = ast_strdup ( values [ COL_CATEGORY ]))) {
ast_category_destroy ( args -> cat );
return SQLITE_ABORT ;
}
ast_category_append ( args -> cfg , args -> cat );
}
if ( ! ( var = ast_variable_new ( values [ COL_VAR_NAME ], values [ COL_VAR_VAL ], "" ))) {
2012-04-06 18:19:03 +00:00
ast_log ( LOG_WARNING , "Unable to allocate variable \n " );
2011-09-11 17:09:36 +00:00
return SQLITE_ABORT ;
}
ast_variable_append ( args -> cat , var );
return 0 ;
}
/*! \brief Realtime callback for static realtime
* \return ast_config on success, NULL on failure
*/
static struct ast_config * realtime_sqlite3_load ( const char * database , const char * table , const char * configfile , struct ast_config * config , struct ast_flags flags , const char * suggested_include_file , const char * who_asked )
{
char * sql ;
struct cfg_entry_args args ;
if ( ast_strlen_zero ( table )) {
ast_log ( LOG_WARNING , "Must have a table to query! \n " );
return NULL ;
}
if ( ! ( sql = sqlite3_mprintf ( static_sql , table , configfile ))) {
ast_log ( LOG_WARNING , "Couldn't allocate query \n " );
return NULL ;
};
args . cfg = config ;
args . cat = NULL ;
args . cat_name = NULL ;
args . flags = flags ;
args . who_asked = who_asked ;
realtime_sqlite3_execute ( database , sql , static_realtime_cb , & args , 0 );
sqlite3_free ( sql );
return config ;
}
/*! \brief Helper function for single and multi-row realtime load functions */
static int realtime_sqlite3_helper ( const char * database , const char * table , va_list ap , int is_multi , void * arg )
{
struct ast_str * sql ;
const char * param , * value ;
int first = 1 ;
if ( ast_strlen_zero ( table )) {
ast_log ( LOG_WARNING , "Must have a table to query! \n " );
return - 1 ;
}
if ( ! ( sql = ast_str_create ( 128 ))) {
return - 1 ;
}
while (( param = va_arg ( ap , const char * )) && ( value = va_arg ( ap , const char * ))) {
if ( first ) {
ast_str_set ( & sql , 0 , "SELECT * FROM %s WHERE %s %s" , sqlite3_escape_table ( table ),
sqlite3_escape_column_op ( param ), sqlite3_escape_value ( value ));
first = 0 ;
} else {
ast_str_append ( & sql , 0 , " AND %s %s" , sqlite3_escape_column_op ( param ),
sqlite3_escape_value ( value ));
}
}
if ( ! is_multi ) {
ast_str_append ( & sql , 0 , "%s" , " LIMIT 1" );
}
if ( realtime_sqlite3_execute ( database , ast_str_buffer ( sql ), is_multi ? append_row_to_cfg : row_to_varlist , arg , 0 ) < 0 ) {
ast_free ( sql );
return - 1 ;
}
ast_free ( sql );
return 0 ;
}
/*! \brief Realtime callback for a single row query
* \return ast_variable list for single result on success, NULL on empty/failure
*/
static struct ast_variable * realtime_sqlite3 ( const char * database , const char * table , va_list ap )
{
struct ast_variable * result_row = NULL ;
realtime_sqlite3_helper ( database , table , ap , 0 , & result_row );
return result_row ;
}
/*! \brief Realtime callback for a multi-row query
* \return ast_config containing possibly many results on success, NULL on empty/failure
*/
static struct ast_config * realtime_sqlite3_multi ( const char * database , const char * table , va_list ap )
{
struct ast_config * cfg ;
if ( ! ( cfg = ast_config_new ())) {
return NULL ;
}
if ( realtime_sqlite3_helper ( database , table , ap , 1 , cfg )) {
ast_config_destroy ( cfg );
return NULL ;
}
return cfg ;
}
/*! \brief Realtime callback for updating a row based on a single criteria
* \return Number of rows affected or -1 on error
*/
static int realtime_sqlite3_update ( const char * database , const char * table , const char * keyfield , const char * entity , va_list ap )
{
struct ast_str * sql ;
const char * key , * value ;
int first = 1 , res ;
if ( ast_strlen_zero ( table )) {
ast_log ( LOG_WARNING , "Must have a table to query! \n " );
return - 1 ;
}
if ( ! ( sql = ast_str_create ( 128 ))) {
return - 1 ;
}
while (( key = va_arg ( ap , const char * )) && ( value = va_arg ( ap , const char * ))) {
if ( first ) {
ast_str_set ( & sql , 0 , "UPDATE %s SET %s = %s" ,
sqlite3_escape_table ( table ), sqlite3_escape_column ( key ), sqlite3_escape_value ( value ));
first = 0 ;
} else {
ast_str_append ( & sql , 0 , ", %s = %s" , sqlite3_escape_column ( key ), sqlite3_escape_value ( value ));
}
}
ast_str_append ( & sql , 0 , " WHERE %s %s" , sqlite3_escape_column_op ( keyfield ), sqlite3_escape_value ( entity ));
res = realtime_sqlite3_execute ( database , ast_str_buffer ( sql ), NULL , NULL , 1 );
ast_free ( sql );
return res ;
}
/*! \brief Realtime callback for updating a row based on multiple criteria
* \return Number of rows affected or -1 on error
*/
static int realtime_sqlite3_update2 ( const char * database , const char * table , va_list ap )
{
struct ast_str * sql ;
struct ast_str * where_clause ;
const char * key , * value ;
int first = 1 , res ;
if ( ast_strlen_zero ( table )) {
ast_log ( LOG_WARNING , "Must have a table to query! \n " );
return - 1 ;
}
if ( ! ( sql = ast_str_create ( 128 ))) {
return - 1 ;
}
if ( ! ( where_clause = ast_str_create ( 128 ))) {
ast_free ( sql );
return - 1 ;
}
while (( key = va_arg ( ap , const char * )) && ( value = va_arg ( ap , const char * ))) {
if ( first ) {
ast_str_set ( & where_clause , 0 , " WHERE %s %s" , sqlite3_escape_column_op ( key ), sqlite3_escape_value ( value ));
first = 0 ;
} else {
ast_str_append ( & where_clause , 0 , " AND %s %s" , sqlite3_escape_column_op ( key ), sqlite3_escape_value ( value ));
}
}
first = 1 ;
while (( key = va_arg ( ap , const char * )) && ( value = va_arg ( ap , const char * ))) {
if ( first ) {
ast_str_set ( & sql , 0 , "UPDATE %s SET %s = %s" , sqlite3_escape_table ( table ), sqlite3_escape_column ( key ), sqlite3_escape_value ( value ));
first = 0 ;
} else {
ast_str_append ( & sql , 0 , ", %s = %s" , sqlite3_escape_column ( key ), sqlite3_escape_value ( value ));
}
}
ast_str_append ( & sql , 0 , "%s" , ast_str_buffer ( where_clause ));
res = realtime_sqlite3_execute ( database , ast_str_buffer ( sql ), NULL , NULL , 1 );
ast_free ( sql );
ast_free ( where_clause );
return res ;
}
/*! \brief Realtime callback for inserting a row
* \return Number of rows affected or -1 on error
*/
static int realtime_sqlite3_store ( const char * database , const char * table , va_list ap )
{
struct ast_str * sql , * values ;
const char * column , * value ;
int first = 1 , res ;
if ( ast_strlen_zero ( table )) {
ast_log ( LOG_WARNING , "Must have a table to query! \n " );
return - 1 ;
}
if ( ! ( sql = ast_str_create ( 128 ))) {
return - 1 ;
}
if ( ! ( values = ast_str_create ( 128 ))) {
ast_free ( sql );
return - 1 ;
}
while (( column = va_arg ( ap , const char * )) && ( value = va_arg ( ap , const char * ))) {
if ( first ) {
ast_str_set ( & sql , 0 , "INSERT INTO %s (%s" , sqlite3_escape_table ( table ), sqlite3_escape_column ( column ));
ast_str_set ( & values , 0 , ") VALUES (%s" , sqlite3_escape_value ( value ));
first = 0 ;
} else {
ast_str_append ( & sql , 0 , ", %s" , sqlite3_escape_column ( column ));
ast_str_append ( & values , 0 , ", %s" , sqlite3_escape_value ( value ));
}
}
ast_str_append ( & sql , 0 , "%s)" , ast_str_buffer ( values ));
res = realtime_sqlite3_execute ( database , ast_str_buffer ( sql ), NULL , NULL , 1 );
ast_free ( sql );
ast_free ( values );
return res ;
}
/*! \brief Realtime callback for deleting a row
* \return Number of rows affected or -1 on error
*/
static int realtime_sqlite3_destroy ( const char * database , const char * table , const char * keyfield , const char * entity , va_list ap )
{
struct ast_str * sql ;
const char * param , * value ;
int first = 1 , res ;
if ( ast_strlen_zero ( table )) {
ast_log ( LOG_WARNING , "Must have a table to query! \n " );
return - 1 ;
}
if ( ! ( sql = ast_str_create ( 128 ))) {
return - 1 ;
}
while (( param = va_arg ( ap , const char * )) && ( value = va_arg ( ap , const char * ))) {
if ( first ) {
ast_str_set ( & sql , 0 , "DELETE FROM %s WHERE %s %s" , sqlite3_escape_table ( table ),
sqlite3_escape_column_op ( param ), sqlite3_escape_value ( value ));
first = 0 ;
} else {
ast_str_append ( & sql , 0 , " AND %s %s" , sqlite3_escape_column_op ( param ), sqlite3_escape_value ( value ));
}
}
res = realtime_sqlite3_execute ( database , ast_str_buffer ( sql ), NULL , NULL , 1 );
ast_free ( sql );
return res ;
}
/*! \brief Convert Asterisk realtime types to SQLite 3 types
* \note SQLite 3 has NULL, INTEGER, REAL, TEXT, and BLOB types. Any column other than
* an INTEGER PRIMARY KEY will actually store any kind of data due to its dynamic
* typing. When we create columns, we'll go ahead and use these base types instead
* of messing with column widths, etc. */
static const char * get_sqlite_column_type ( int type )
{
switch ( type ) {
case RQ_INTEGER1 :
case RQ_UINTEGER1 :
case RQ_INTEGER2 :
case RQ_UINTEGER2 :
case RQ_INTEGER3 :
case RQ_UINTEGER3 :
case RQ_INTEGER4 :
case RQ_UINTEGER4 :
case RQ_INTEGER8 :
return "INTEGER" ;
case RQ_UINTEGER8 : /* SQLite3 stores INTEGER as signed 8-byte */
case RQ_CHAR :
case RQ_DATE :
case RQ_DATETIME :
return "TEXT" ;
case RQ_FLOAT :
return "REAL" ;
default :
return "TEXT" ;
}
return "TEXT" ;
}
/*! \brief Create a table if ast_realtime_require shows that we are configured to handle the data
*/
static int handle_missing_table ( struct realtime_sqlite3_db * db , const char * table , va_list ap )
{
const char * column ;
int type , first = 1 , res ;
size_t sz ;
struct ast_str * sql ;
if ( ! ( sql = ast_str_create ( 128 ))) {
return - 1 ;
}
while (( column = va_arg ( ap , typeof ( column ))) && ( type = va_arg ( ap , typeof ( type ))) && ( sz = va_arg ( ap , typeof ( sz )))) {
if ( first ) {
ast_str_set ( & sql , 0 , "CREATE TABLE IF NOT EXISTS %s (%s %s" , sqlite3_escape_table ( table ),
sqlite3_escape_column ( column ), get_sqlite_column_type ( type ));
first = 0 ;
} else {
ast_str_append ( & sql , 0 , ", %s %s" , sqlite3_escape_column ( column ), get_sqlite_column_type ( type ));
}
}
ast_str_append ( & sql , 0 , ")" );
res = realtime_sqlite3_execute_handle ( db , ast_str_buffer ( sql ), NULL , NULL , 1 ) < 0 ? - 1 : 0 ;
ast_free ( sql );
return res ;
}
/*! \brief If ast_realtime_require sends info about a column we don't have, create it
*/
static int handle_missing_column ( struct realtime_sqlite3_db * db , const char * table , const char * column , int type , size_t sz )
{
char * sql ;
const char * sqltype = get_sqlite_column_type ( type );
int res ;
if ( db -> requirements == REALTIME_SQLITE3_REQ_WARN ) {
ast_log ( LOG_WARNING , "Missing column '%s' of type '%s' in %s.%s \n " , column , sqltype , db -> name , table );
return - 1 ;
} else if ( db -> requirements == REALTIME_SQLITE3_REQ_CHAR ) {
sqltype = "TEXT" ;
}
if ( ! ( sql = sqlite3_mprintf ( "ALTER TABLE \" %q \" ADD COLUMN \" %q \" %s" , table , column , sqltype ))) {
return - 1 ;
}
2012-04-17 18:57:40 +00:00
if ( ! ( res = ( realtime_sqlite3_execute_handle ( db , sql , NULL , NULL , 1 ) < 0 ? - 1 : 0 ))) {
2011-09-11 17:09:36 +00:00
ast_log ( LOG_NOTICE , "Creating column '%s' type %s for table %s \n " , column , sqltype , table );
}
sqlite3_free ( sql );
return res ;
}
static int str_hash_fn ( const void * obj , const int flags )
{
return ast_str_hash (( const char * ) obj );
}
static int str_cmp_fn ( void * obj , void * arg , int flags ) {
return ! strcasecmp (( const char * ) obj , ( const char * ) arg );
}
/*! \brief Callback for creating a hash of column names for comparison in realtime_sqlite3_require
*/
static int add_column_name ( void * arg , int num_columns , char ** values , char ** columns )
{
char * column ;
struct ao2_container * cnames = arg ;
if ( ! ( column = ao2_alloc ( strlen ( values [ 1 ]) + 1 , NULL ))) {
return - 1 ;
}
strcpy ( column , values [ 1 ]);
ao2_link ( cnames , column );
ao2_ref ( column , - 1 );
return 0 ;
}
/*! \brief Callback for ast_realtime_require
* \retval 0 Required fields met specified standards
* \retval -1 One or more fields was missing or insufficient
*/
static int realtime_sqlite3_require ( const char * database , const char * table , va_list ap )
{
const char * column ;
char * sql ;
int type ;
int res ;
size_t sz ;
struct ao2_container * columns ;
struct realtime_sqlite3_db * db ;
/* SQLite3 columns are dynamically typed, with type affinity. Built-in functions will
* return the results as char * anyway. The only field that that cannot contain text
* data is an INTEGER PRIMARY KEY, which must be a 64-bit signed integer. So, for
* the purposes here we really only care whether the column exists and not what its
* type or length is. */
if ( ast_strlen_zero ( table )) {
ast_log ( LOG_WARNING , "Must have a table to query! \n " );
return - 1 ;
}
if ( ! ( db = find_database ( database ))) {
return - 1 ;
}
if ( ! ( columns = ao2_container_alloc ( 31 , str_hash_fn , str_cmp_fn ))) {
unref_db ( & db );
return - 1 ;
}
if ( ! ( sql = sqlite3_mprintf ( "PRAGMA table_info( \" %q \" )" , table ))) {
unref_db ( & db );
ao2_ref ( columns , - 1 );
return - 1 ;
}
if (( res = realtime_sqlite3_execute_handle ( db , sql , add_column_name , columns , 0 )) < 0 ) {
unref_db ( & db );
ao2_ref ( columns , - 1 );
sqlite3_free ( sql );
return - 1 ;
} else if ( res == 0 ) {
/* Table does not exist */
sqlite3_free ( sql );
res = handle_missing_table ( db , table , ap );
ao2_ref ( columns , - 1 );
unref_db ( & db );
return res ;
}
sqlite3_free ( sql );
while (( column = va_arg ( ap , typeof ( column ))) && ( type = va_arg ( ap , typeof ( type ))) && ( sz = va_arg ( ap , typeof ( sz )))) {
char * found ;
if ( ! ( found = ao2_find ( columns , column , OBJ_POINTER | OBJ_UNLINK ))) {
if ( handle_missing_column ( db , table , column , type , sz )) {
unref_db ( & db );
ao2_ref ( columns , - 1 );
return - 1 ;
}
} else {
ao2_ref ( found , - 1 );
}
}
ao2_ref ( columns , - 1 );
unref_db ( & db );
return 0 ;
}
/*! \brief Callback for clearing any cached info
* \note We don't currently cache anything
* \retval 0 If any cache was purged
* \retval -1 If no cache was found
*/
static int realtime_sqlite3_unload ( const char * database , const char * table )
{
/* We currently do no caching */
return - 1 ;
}
/*! \brief Parse the res_config_sqlite3 config file
*/
static int parse_config ( int reload )
{
struct ast_config * config ;
struct ast_flags config_flags = { CONFIG_FLAG_NOREALTIME | ( reload ? CONFIG_FLAG_FILEUNCHANGED : 0 ) };
static const char * config_filename = "res_config_sqlite3.conf" ;
config = ast_config_load ( config_filename , config_flags );
if ( config == CONFIG_STATUS_FILEUNCHANGED ) {
ast_debug ( 1 , "%s was unchanged, skipping parsing \n " , config_filename );
return 0 ;
}
ast_mutex_lock ( & config_lock );
if ( config == CONFIG_STATUS_FILEMISSING || config == CONFIG_STATUS_FILEINVALID ) {
ast_log ( LOG_ERROR , "%s config file '%s' \n " ,
config == CONFIG_STATUS_FILEMISSING ? "Missing" : "Invalid" , config_filename );
} else {
const char * cat ;
struct realtime_sqlite3_db * db ;
mark_all_databases_dirty ();
for ( cat = ast_category_browse ( config , NULL ); cat ; cat = ast_category_browse ( config , cat )) {
if ( ! strcasecmp ( cat , "general" )) {
continue ;
}
if ( ! ( db = find_database ( cat ))) {
if ( ! ( db = new_realtime_sqlite3_db ( config , cat ))) {
ast_log ( LOG_WARNING , "Could not allocate new db for '%s' - skipping. \n " , cat );
continue ;
}
if ( db_open ( db )) {
unref_db ( & db );
continue ;
}
db_start_batch ( db );
ao2_link ( databases , db );
unref_db ( & db );
} else {
if ( update_realtime_sqlite3_db ( db , config , cat )) {
unref_db ( & db );
continue ;
}
unref_db ( & db );
}
}
unlink_dirty_databases ();
}
ast_mutex_unlock ( & config_lock );
ast_config_destroy ( config );
return 0 ;
}
static int reload ( void )
{
parse_config ( 1 );
return 0 ;
}
static int unload_module ( void )
{
ast_mutex_lock ( & config_lock );
ao2_callback ( databases , OBJ_MULTIPLE | OBJ_NODATA | OBJ_UNLINK , stop_batch_cb , NULL );
ao2_ref ( databases , - 1 );
databases = NULL ;
ast_config_engine_deregister ( & sqlite3_config_engine );
ast_mutex_unlock ( & config_lock );
return 0 ;
}
static int load_module ( void )
{
if ( ! (( databases = ao2_container_alloc ( DB_BUCKETS , db_hash_fn , db_cmp_fn )))) {
return AST_MODULE_LOAD_FAILURE ;
}
if ( parse_config ( 0 )) {
ao2_ref ( databases , - 1 );
return AST_MODULE_LOAD_FAILURE ;
}
if ( ! ( ast_config_engine_register ( & sqlite3_config_engine ))) {
ast_log ( LOG_ERROR , "The config API must have changed, this shouldn't happen. \n " );
ao2_ref ( databases , - 1 );
return AST_MODULE_LOAD_FAILURE ;
}
return AST_MODULE_LOAD_SUCCESS ;
}
AST_MODULE_INFO ( ASTERISK_GPL_KEY , AST_MODFLAG_LOAD_ORDER , "SQLite 3 realtime config engine" ,
. load = load_module ,
. unload = unload_module ,
. reload = reload ,
. load_pri = AST_MODPRI_REALTIME_DRIVER ,
);