44 #include <sys/types.h>
59 #define CONFIG "cdr_adaptive_odbc.conf"
61 static const char name[] =
"Adaptive ODBC";
63 static int maxsize = 512, maxsize2 = 512;
77 unsigned int negatefiltervalue:1;
84 char quoted_identifiers;
85 unsigned int usegmtime:1;
87 AST_RWLIST_ENTRY(tables) list;
92 static
int load_config(
void)
96 const char *tmp, *catg;
97 struct tables *tableptr;
98 struct columns *
entry;
104 char quoted_identifiers;
105 int lenconnection, lentable, lenschema, usegmtime = 0;
108 SQLHSTMT stmt = NULL;
112 if (!cfg || cfg == CONFIG_STATUS_FILEINVALID) {
113 ast_log(LOG_WARNING,
"Unable to load " CONFIG
". No adaptive ODBC CDRs.\n");
118 var = ast_variable_browse(cfg, catg);
122 if (ast_strlen_zero(tmp = ast_variable_retrieve(cfg, catg,
"connection"))) {
123 ast_log(LOG_WARNING,
"No connection parameter found in '%s'. Skipping.\n", catg);
127 lenconnection = strlen(connection);
129 if (!ast_strlen_zero(tmp = ast_variable_retrieve(cfg, catg,
"usegmtime"))) {
136 ast_log(LOG_WARNING,
"No such connection '%s' in the '%s' section of " CONFIG
". Check res_odbc.conf.\n", connection, catg);
140 if (ast_strlen_zero(tmp = ast_variable_retrieve(cfg, catg,
"table"))) {
141 ast_log(LOG_NOTICE,
"No table name found. Assuming 'cdr'.\n");
145 lentable = strlen(table);
147 if (ast_strlen_zero(tmp = ast_variable_retrieve(cfg, catg,
"schema"))) {
151 lenschema = strlen(schema);
153 if (ast_strlen_zero(tmp = ast_variable_retrieve(cfg, catg,
"quoted_identifiers"))) {
156 quoted_identifiers = tmp[0];
157 if (strlen(tmp) > 1) {
158 ast_log(LOG_ERROR,
"The quoted_identifiers setting only accepts a single character,"
159 " while a value of '%s' was provided. This option has been disabled as a result.\n", tmp);
160 quoted_identifiers =
'\0';
163 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->
con, &stmt);
164 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
165 ast_log(LOG_WARNING,
"SQL Alloc Handle failed on connection '%s'!\n", connection);
170 res = SQLColumns(stmt, NULL, 0, lenschema == 0 ? NULL : (
unsigned char *)schema, SQL_NTS, (
unsigned char *)table, SQL_NTS, (
unsigned char *)
"%", SQL_NTS);
171 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
172 ast_log(LOG_ERROR,
"Unable to query database columns on connection '%s'. Skipping.\n", connection);
173 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
178 tableptr =
ast_calloc(
sizeof(
char),
sizeof(*tableptr) + lenconnection + 1 + lentable + 1 + lenschema + 1 + 1);
180 ast_log(LOG_ERROR,
"Out of memory creating entry for table '%s' on connection '%s'%s%s%s\n", table, connection,
181 lenschema ?
" (schema '" :
"", lenschema ? schema :
"", lenschema ?
"')" :
"");
182 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
188 tableptr->usegmtime = usegmtime;
189 tableptr->connection = (
char *)tableptr +
sizeof(*tableptr);
190 tableptr->table = (
char *)tableptr +
sizeof(*tableptr) + lenconnection + 1;
191 tableptr->schema = (
char *)tableptr +
sizeof(*tableptr) + lenconnection + 1 + lentable + 1;
195 tableptr->quoted_identifiers = quoted_identifiers;
197 ast_verb(3,
"Found adaptive CDR table %s@%s.\n", tableptr->table, tableptr->connection);
200 for (var = ast_variable_browse(cfg, catg); var; var = var->
next) {
201 if (strncmp(var->
name,
"filter", 6) == 0) {
205 if (cdrvar[strlen(cdrvar) - 1] ==
'!') {
207 cdrvar[strlen(cdrvar) - 1] =
'\0';
211 ast_verb(3,
"Found filter %s'%s' for CDR variable %s in %s@%s\n", negate ?
"!" :
"", var->
value, cdrvar, tableptr->table, tableptr->connection);
213 entry =
ast_calloc(
sizeof(
char),
sizeof(*entry) + strlen(cdrvar) + 1 + strlen(var->
value) + 1);
215 ast_log(LOG_ERROR,
"Out of memory creating filter entry for CDR variable '%s' in table '%s' on connection '%s'\n", cdrvar, table, connection);
222 entry->cdrname = (
char *)entry +
sizeof(*entry);
223 entry->filtervalue = (
char *)entry +
sizeof(*entry) + strlen(cdrvar) + 1;
224 strcpy(entry->cdrname, cdrvar);
225 strcpy(entry->filtervalue, var->
value);
226 entry->negatefiltervalue = negate;
232 while ((res = SQLFetch(stmt)) != SQL_NO_DATA && res != SQL_ERROR) {
233 char *cdrvar =
"", *staticvalue =
"";
235 SQLGetData(stmt, 4, SQL_C_CHAR, columnname,
sizeof(columnname), &sqlptr);
243 for (var = ast_variable_browse(cfg, catg); var; var = var->
next) {
244 if (strncmp(var->
name,
"alias", 5) == 0 && strcasecmp(var->
value, columnname) == 0) {
247 ast_verb(3,
"Found alias %s for column %s in %s@%s\n", cdrvar, columnname, tableptr->table, tableptr->connection);
249 }
else if (strncmp(var->
name,
"static", 6) == 0 && strcasecmp(var->
value, columnname) == 0) {
252 if (item[0] ==
'"' && item[strlen(item) - 1] ==
'"') {
254 item[strlen(item) - 1] =
'\0';
261 entry =
ast_calloc(
sizeof(
char),
sizeof(*entry) + strlen(columnname) + 1 + strlen(cdrvar) + 1 + strlen(staticvalue) + 1);
263 ast_log(LOG_ERROR,
"Out of memory creating entry for column '%s' in table '%s' on connection '%s'\n", columnname, table, connection);
265 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
268 entry->name = (
char *)entry +
sizeof(*entry);
269 strcpy(entry->name, columnname);
271 if (!ast_strlen_zero(cdrvar)) {
272 entry->cdrname = entry->name + strlen(columnname) + 1;
273 strcpy(entry->cdrname, cdrvar);
275 entry->cdrname = (
char *)entry +
sizeof(*entry);
278 if (!ast_strlen_zero(staticvalue)) {
279 entry->staticvalue = entry->cdrname + strlen(entry->cdrname) + 1;
280 strcpy(entry->staticvalue, staticvalue);
283 SQLGetData(stmt, 5, SQL_C_SHORT, &entry->type,
sizeof(entry->type), NULL);
284 SQLGetData(stmt, 7, SQL_C_LONG, &entry->size,
sizeof(entry->size), NULL);
285 SQLGetData(stmt, 9, SQL_C_SHORT, &entry->decimals,
sizeof(entry->decimals), NULL);
286 SQLGetData(stmt, 10, SQL_C_SHORT, &entry->radix,
sizeof(entry->radix), NULL);
287 SQLGetData(stmt, 11, SQL_C_SHORT, &entry->nullable,
sizeof(entry->nullable), NULL);
288 SQLGetData(stmt, 16, SQL_C_LONG, &entry->octetlen,
sizeof(entry->octetlen), NULL);
293 if (entry->octetlen == 0)
294 entry->octetlen = entry->size;
296 ast_verb(4,
"Found %s column with type %hd with len %ld, octetlen %ld, and numlen (%hd,%hd)\n", entry->name, entry->type, (
long) entry->size, (
long) entry->octetlen, entry->decimals, entry->radix);
302 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
306 AST_RWLIST_INSERT_TAIL(&odbc_tables, tableptr, list);
314 static int free_config(
void)
316 struct tables *table;
318 while ((table = AST_RWLIST_REMOVE_HEAD(&
odbc_tables, list))) {
327 static SQLHSTMT generic_prepare(
struct odbc_obj *obj,
void *data)
331 SQLINTEGER nativeerror = 0, numfields = 0;
332 SQLSMALLINT diagbytes = 0;
333 unsigned char state[10], diagnostic[256];
335 res = SQLAllocHandle (SQL_HANDLE_STMT, obj->
con, &stmt);
336 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
337 ast_log(LOG_WARNING,
"SQL Alloc Handle failed!\n");
342 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
343 ast_log(LOG_WARNING,
"SQL Prepare failed![%s]\n", (
char *) data);
344 SQLGetDiagField(SQL_HANDLE_STMT, stmt, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
345 for (i = 0; i < numfields; i++) {
346 SQLGetDiagRec(SQL_HANDLE_STMT, stmt, i + 1, state, &nativeerror, diagnostic,
sizeof(diagnostic), &diagbytes);
347 ast_log(LOG_WARNING,
"SQL Execute returned an error %d: %s: %s (%d)\n", res, state, diagnostic, diagbytes);
349 ast_log(LOG_WARNING,
"Oh, that was good. There are really %d diagnostics?\n", (
int)numfields);
353 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
360 #define LENGTHEN_BUF(size, var_sql) \
363 if (ast_str_strlen(var_sql) + size + 1 > ast_str_size(var_sql)) { \
364 if (ast_str_make_space(&var_sql, ((ast_str_size(var_sql) + size + 1) / 512 + 1) * 512) != 0) { \
365 ast_log(LOG_ERROR, "Unable to allocate sufficient memory. Insert CDR '%s:%s' failed.\n", tableptr->connection, tableptr->table); \
368 AST_RWLIST_UNLOCK(&odbc_tables); \
374 #define LENGTHEN_BUF1(size) \
375 LENGTHEN_BUF(size, sql);
376 #define LENGTHEN_BUF2(size) \
377 LENGTHEN_BUF(size, sql2);
379 static int odbc_log(
struct ast_cdr *cdr)
381 struct tables *tableptr;
386 char colbuf[1024], *colptr;
387 SQLHSTMT stmt = NULL;
401 ast_log(LOG_ERROR,
"Unable to lock table list. Insert CDR(s) failed.\n");
411 if (tableptr->quoted_identifiers !=
'\0'){
415 if (ast_strlen_zero(tableptr->schema)) {
418 tableptr->quoted_identifiers, tableptr->table, tableptr->quoted_identifiers );
420 ast_str_set(&sql, 0,
"INSERT INTO %s (", tableptr->table);
424 ast_str_set(&sql, 0,
"INSERT INTO %c%s%c.%c%s%c (",
425 tableptr->quoted_identifiers, tableptr->schema, tableptr->quoted_identifiers,
426 tableptr->quoted_identifiers, tableptr->table, tableptr->quoted_identifiers);
428 ast_str_set(&sql, 0,
"INSERT INTO %s.%s (", tableptr->schema, tableptr->table);
435 ast_log(LOG_WARNING,
"cdr_adaptive_odbc: Unable to retrieve database handle for '%s:%s'. CDR failed: %s\n", tableptr->connection, tableptr->table,
ast_str_buffer(sql));
441 if (strcasecmp(entry->cdrname,
"start") == 0) {
443 }
else if (strcasecmp(entry->cdrname,
"answer") == 0) {
445 }
else if (strcasecmp(entry->cdrname,
"end") == 0) {
450 if (entry->staticvalue) {
452 }
else if (datefield && tableptr->usegmtime) {
453 struct timeval date_tv = (datefield == 1) ? cdr->start : (datefield == 2) ? cdr->answer : cdr->end;
454 struct ast_tm tm = { 0, };
456 ast_strftime(colbuf,
sizeof(colbuf),
"%Y-%m-%d %H:%M:%S", &tm);
459 ast_cdr_format_var(cdr, entry->cdrname, &colptr, colbuf,
sizeof(colbuf), datefield ? 0 : 1);
467 if ((entry->filtervalue && !entry->negatefiltervalue && strcasecmp(colptr, entry->filtervalue) != 0) ||
468 (entry->filtervalue && entry->negatefiltervalue && strcasecmp(colptr, entry->filtervalue) == 0)) {
469 ast_verb(4,
"CDR column '%s' with value '%s' does not match filter of"
470 " %s'%s'. Cancelling this CDR.\n",
471 entry->cdrname, colptr, entry->negatefiltervalue ?
"!" :
"", entry->filtervalue);
476 if (ast_strlen_zero(entry->name))
479 LENGTHEN_BUF1(strlen(entry->name));
481 switch (entry->type) {
484 case SQL_LONGVARCHAR:
485 #ifdef HAVE_ODBC_WCHAR
488 case SQL_WLONGVARCHAR:
492 case SQL_LONGVARBINARY:
497 if (strcasecmp(entry->name,
"disposition") == 0) {
499 }
else if (strcasecmp(entry->name,
"amaflags") == 0) {
504 if (entry->type != SQL_GUID) {
505 if (strlen(colptr) > entry->octetlen) {
506 colptr[entry->octetlen] =
'\0';
510 LENGTHEN_BUF2(strlen(colptr));
514 for (tmp = colptr; *tmp; tmp++) {
526 if (ast_strlen_zero(colptr)) {
529 int year = 0, month = 0, day = 0;
530 if (sscanf(colptr,
"%4d-%2d-%2d", &year, &month, &day) != 3 || year <= 0 ||
531 month <= 0 || month > 12 || day < 0 || day > 31 ||
532 ((month == 4 || month == 6 || month == 9 || month == 11) && day == 31) ||
533 (month == 2 && year % 400 == 0 && day > 29) ||
534 (month == 2 && year % 100 == 0 && day > 28) ||
535 (month == 2 && year % 4 == 0 && day > 29) ||
536 (month == 2 && year % 4 != 0 && day > 28)) {
537 ast_log(LOG_WARNING,
"CDR variable %s is not a valid date ('%s').\n", entry->name, colptr);
541 if (year > 0 && year < 100) {
546 ast_str_append(&sql2, 0,
"%s{ d '%04d-%02d-%02d' }", separator, year, month, day);
550 if (ast_strlen_zero(colptr)) {
553 int hour = 0, minute = 0, second = 0;
554 int count = sscanf(colptr,
"%2d:%2d:%2d", &hour, &minute, &second);
556 if ((count != 2 && count != 3) || hour < 0 || hour > 23 || minute < 0 || minute > 59 || second < 0 || second > 59) {
557 ast_log(LOG_WARNING,
"CDR variable %s is not a valid time ('%s').\n", entry->name, colptr);
562 ast_str_append(&sql2, 0,
"%s{ t '%02d:%02d:%02d' }", separator, hour, minute, second);
565 case SQL_TYPE_TIMESTAMP:
568 if (ast_strlen_zero(colptr)) {
571 int year = 0, month = 0, day = 0, hour = 0, minute = 0, second = 0;
572 int count = sscanf(colptr,
"%4d-%2d-%2d %2d:%2d:%2d", &year, &month, &day, &hour, &minute, &second);
574 if ((count != 3 && count != 5 && count != 6) || year <= 0 ||
575 month <= 0 || month > 12 || day < 0 || day > 31 ||
576 ((month == 4 || month == 6 || month == 9 || month == 11) && day == 31) ||
577 (month == 2 && year % 400 == 0 && day > 29) ||
578 (month == 2 && year % 100 == 0 && day > 28) ||
579 (month == 2 && year % 4 == 0 && day > 29) ||
580 (month == 2 && year % 4 != 0 && day > 28) ||
581 hour > 23 || minute > 59 || second > 59 || hour < 0 || minute < 0 || second < 0) {
582 ast_log(LOG_WARNING,
"CDR variable %s is not a valid timestamp ('%s').\n", entry->name, colptr);
586 if (year > 0 && year < 100) {
591 ast_str_append(&sql2, 0,
"%s{ ts '%04d-%02d-%02d %02d:%02d:%02d' }", separator, year, month, day, hour, minute, second);
595 if (ast_strlen_zero(colptr)) {
599 if (sscanf(colptr,
"%30d", &integer) != 1) {
600 ast_log(LOG_WARNING,
"CDR variable %s is not an integer.\n", entry->name);
609 if (ast_strlen_zero(colptr)) {
612 long long integer = 0;
613 if (sscanf(colptr,
"%30lld", &integer) != 1) {
614 ast_log(LOG_WARNING,
"CDR variable %s is not an integer.\n", entry->name);
623 if (ast_strlen_zero(colptr)) {
627 if (sscanf(colptr,
"%30hd", &integer) != 1) {
628 ast_log(LOG_WARNING,
"CDR variable %s is not an integer.\n", entry->name);
637 if (ast_strlen_zero(colptr)) {
640 signed char integer = 0;
641 if (sscanf(colptr,
"%30hhd", &integer) != 1) {
642 ast_log(LOG_WARNING,
"CDR variable %s is not an integer.\n", entry->name);
651 if (ast_strlen_zero(colptr)) {
654 signed char integer = 0;
655 if (sscanf(colptr,
"%30hhd", &integer) != 1) {
656 ast_log(LOG_WARNING,
"CDR variable %s is not an integer.\n", entry->name);
668 if (ast_strlen_zero(colptr)) {
673 if (!strcasecmp(entry->cdrname,
"billsec")) {
675 snprintf(colbuf,
sizeof(colbuf),
"%lf",
676 (
double) (
ast_tvdiff_us(cdr->end, cdr->answer) / 1000000.0));
680 }
else if (!strcasecmp(entry->cdrname,
"duration")) {
681 snprintf(colbuf,
sizeof(colbuf),
"%lf",
684 if (!ast_strlen_zero(colbuf)) {
689 if (sscanf(colptr,
"%30lf", &number) != 1) {
690 ast_log(LOG_WARNING,
"CDR variable %s is not an numeric type.\n", entry->name);
694 LENGTHEN_BUF2(entry->decimals);
695 ast_str_append(&sql2, 0,
"%s%*.*lf", separator, entry->decimals, entry->radix, number);
701 if (ast_strlen_zero(colptr)) {
706 if (!strcasecmp(entry->cdrname,
"billsec")) {
708 snprintf(colbuf,
sizeof(colbuf),
"%lf",
709 (
double) (
ast_tvdiff_us(cdr->end, cdr->answer) / 1000000.0));
713 }
else if (!strcasecmp(entry->cdrname,
"duration")) {
714 snprintf(colbuf,
sizeof(colbuf),
"%lf",
717 if (!ast_strlen_zero(colbuf)) {
722 if (sscanf(colptr,
"%30lf", &number) != 1) {
723 ast_log(LOG_WARNING,
"CDR variable %s is not an numeric type.\n", entry->name);
727 LENGTHEN_BUF2(entry->decimals);
732 ast_log(LOG_WARNING,
"Column type %d (field '%s:%s:%s') is unsupported at this time.\n", entry->type, tableptr->connection, tableptr->table, entry->name);
736 ast_str_append(&sql, 0,
"%s%c%s%c", separator, tableptr->quoted_identifiers, entry->name, tableptr->quoted_identifiers);
741 }
else if (entry->filtervalue
742 && ((!entry->negatefiltervalue && entry->filtervalue[0] !=
'\0')
743 || (entry->negatefiltervalue && entry->filtervalue[0] ==
'\0'))) {
744 ast_verb(4,
"CDR column '%s' was not set and does not match filter of"
745 " %s'%s'. Cancelling this CDR.\n",
746 entry->cdrname, entry->negatefiltervalue ?
"!" :
"",
762 SQLRowCount(stmt, &rows);
763 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
766 ast_log(LOG_WARNING,
"cdr_adaptive_odbc: Insert failed on '%s:%s'. CDR failed: %s\n", tableptr->connection, tableptr->table,
ast_str_buffer(sql));
786 static int unload_module(
void)
794 ast_log(LOG_ERROR,
"Unable to lock column list. Unload failed.\n");
803 static int load_module(
void)
806 ast_log(LOG_ERROR,
"Unable to lock column list. Load failed.\n");
816 static int reload(
void)
819 ast_log(LOG_ERROR,
"Unable to lock column list. Reload failed.\n");
829 AST_MODULE_INFO(
ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER,
"Adaptive ODBC CDR backend",
830 .support_level = AST_MODULE_SUPPORT_CORE,
832 .unload = unload_module,
835 .requires =
"cdr,res_odbc",
struct ast_variable * next
int ast_odbc_backslash_is_escape(struct odbc_obj *obj)
Checks if the database natively supports backslash as an escape character.
Asterisk locking-related definitions:
Asterisk main include file. File version handling, generic pbx functions.
#define AST_LIST_FIRST(head)
Returns the first entry contained in a list.
int ast_cdr_unregister(const char *name)
Unregister a CDR handling engine.
Time-related functions and macros.
#define ast_odbc_request_obj(name, check)
Get a ODBC connection object.
char * ast_str_buffer(const struct ast_str *buf)
Returns the string buffer within the ast_str buf.
#define AST_RWLIST_RDLOCK(head)
Read locks a list.
int ast_odbc_prepare(struct odbc_obj *obj, SQLHSTMT *stmt, const char *sql)
Prepares a SQL query on a statement.
struct ast_tm * ast_localtime(const struct timeval *timep, struct ast_tm *p_tm, const char *zone)
Timezone-independent version of localtime_r(3).
Structure for variables, used for configurations and for channel variables.
int ast_tvzero(const struct timeval t)
Returns true if the argument is 0,0.
#define AST_RWLIST_WRLOCK(head)
Write locks a list.
int ast_str_append(struct ast_str **buf, ssize_t max_len, const char *fmt,...)
Append to a thread local dynamic string.
char * ast_category_browse(struct ast_config *config, const char *prev_name)
Browse categories.
#define AST_RWLIST_HEAD_STATIC(name, type)
Defines a structure to be used to hold a read/write list of specified type, statically initialized...
void ast_cdr_format_var(struct ast_cdr *cdr, const char *name, char **ret, char *workspace, int workspacelen, int raw)
Format a CDR variable from an already posted CDR.
int ast_str_set(struct ast_str **buf, ssize_t max_len, const char *fmt,...)
Set a dynamic string using variable arguments.
Configuration File Parser.
#define ast_config_load(filename, flags)
Load a config file.
int ast_cdr_register(const char *name, const char *desc, ast_cdrbe be)
Register a CDR handling engine.
General Asterisk PBX channel definitions.
#define ast_strdupa(s)
duplicate a string in memory from the stack
char * ast_strip(char *s)
Strip leading/trailing whitespace from a string.
A set of macros to manage forward-linked lists.
#define ast_debug(level,...)
Log a DEBUG message.
#define AST_LIST_REMOVE_HEAD(head, field)
Removes and returns the head entry from a list.
#define AST_LIST_HEAD_NOLOCK(name, type)
Defines a structure to be used to hold a list of specified type (with no lock).
Responsible for call detail data.
#define AST_LIST_INSERT_TAIL(head, elm, field)
Appends a list entry to the tail of a list.
int attribute_pure ast_true(const char *val)
Make sure something is true. Determine if a string containing a boolean value is "true". This function checks to see whether a string passed to it is an indication of an "true" value. It checks to see if the string is "yes", "true", "y", "t", "on" or "1".
Support for dynamic strings.
char * ast_trim_blanks(char *str)
Trims trailing whitespace characters from a string.
#define AST_LIST_TRAVERSE(head, var, field)
Loops over (traverses) the entries in a list.
#define AST_LIST_ENTRY(type)
Declare a forward link structure inside a list entry.
#define ast_calloc(num, len)
A wrapper for calloc()
int ast_strftime(char *buf, size_t len, const char *format, const struct ast_tm *tm)
Special version of strftime(3) that handles fractions of a second. Takes the same arguments as strfti...
Structure used to handle boolean flags.
size_t ast_str_strlen(const struct ast_str *buf)
Returns the current length of the string stored within buf.
SQLHSTMT ast_odbc_prepare_and_execute(struct odbc_obj *obj, SQLHSTMT(*prepare_cb)(struct odbc_obj *obj, void *data), void *data)
Prepares, executes, and returns the resulting statement handle.
void ast_copy_string(char *dst, const char *src, size_t size)
Size-limited null-terminating string copy.
int64_t ast_tvdiff_us(struct timeval end, struct timeval start)
Computes the difference (in microseconds) between two struct timeval instances.
#define AST_RWLIST_UNLOCK(head)
Attempts to unlock a read/write based list.
void ast_config_destroy(struct ast_config *cfg)
Destroys a config.
void ast_odbc_release_obj(struct odbc_obj *obj)
Releases an ODBC object previously allocated by ast_odbc_request_obj()
#define ASTERISK_GPL_KEY
The text the key() function should return.
Asterisk module definitions.
#define ast_str_create(init_len)
Create a malloc'ed dynamic length string.