Handle numeric columns for eventtype properly in cel_odbc

Patch also implements correct handling of datetime2 and datetimeoffset new
datatypes in SQL Server 2008 and 2008 R2.

(closes issue ASTERISK-17548)

Review: https://reviewboard.asterisk.org/r/1160/
Review: https://reviewboard.asterisk.org/r/1804/


git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@358576 65c4cc65-6c06-0410-ace0-fbb531ad65f3
This commit is contained in:
Terry Wilson
2012-03-07 21:28:55 +00:00
parent b9a7421482
commit 706f79d122
2 changed files with 101 additions and 80 deletions

View File

@@ -80,6 +80,7 @@ struct tables {
char *connection; char *connection;
char *table; char *table;
unsigned int usegmtime:1; unsigned int usegmtime:1;
unsigned int allowleapsec:1;
AST_LIST_HEAD_NOLOCK(odbc_columns, columns) columns; AST_LIST_HEAD_NOLOCK(odbc_columns, columns) columns;
AST_RWLIST_ENTRY(tables) list; AST_RWLIST_ENTRY(tables) list;
}; };
@@ -97,7 +98,7 @@ static int load_config(void)
char columnname[80]; char columnname[80];
char connection[40]; char connection[40];
char table[40]; char table[40];
int lenconnection, lentable, usegmtime = 0; int lenconnection, lentable;
SQLLEN sqlptr; SQLLEN sqlptr;
int res = 0; int res = 0;
SQLHSTMT stmt = NULL; SQLHSTMT stmt = NULL;
@@ -134,10 +135,6 @@ static int load_config(void)
ast_copy_string(connection, tmp, sizeof(connection)); ast_copy_string(connection, tmp, sizeof(connection));
lenconnection = strlen(connection); lenconnection = strlen(connection);
if (!ast_strlen_zero(tmp = ast_variable_retrieve(cfg, catg, "usegmtime"))) {
usegmtime = ast_true(tmp);
}
/* When loading, we want to be sure we can connect. */ /* When loading, we want to be sure we can connect. */
obj = ast_odbc_request_obj(connection, 1); obj = ast_odbc_request_obj(connection, 1);
if (!obj) { if (!obj) {
@@ -174,12 +171,21 @@ static int load_config(void)
break; break;
} }
tableptr->usegmtime = usegmtime;
tableptr->connection = (char *)tableptr + sizeof(*tableptr); tableptr->connection = (char *)tableptr + sizeof(*tableptr);
tableptr->table = (char *)tableptr + sizeof(*tableptr) + lenconnection + 1; tableptr->table = (char *)tableptr + sizeof(*tableptr) + lenconnection + 1;
ast_copy_string(tableptr->connection, connection, lenconnection + 1); ast_copy_string(tableptr->connection, connection, lenconnection + 1);
ast_copy_string(tableptr->table, table, lentable + 1); ast_copy_string(tableptr->table, table, lentable + 1);
tableptr->usegmtime = 0;
if (!ast_strlen_zero(tmp = ast_variable_retrieve(cfg, catg, "usegmtime"))) {
tableptr->usegmtime = ast_true(tmp);
}
tableptr->allowleapsec = 1;
if (!ast_strlen_zero(tmp = ast_variable_retrieve(cfg, catg, "allowleapsecond"))) {
tableptr->allowleapsec = ast_true(tmp);
}
ast_verb(3, "Found CEL table %s@%s.\n", tableptr->table, tableptr->connection); ast_verb(3, "Found CEL table %s@%s.\n", tableptr->table, tableptr->connection);
/* Check for filters first */ /* Check for filters first */
@@ -407,6 +413,7 @@ static void odbc_log(const struct ast_event *event, void *userdata)
AST_LIST_TRAVERSE(&(tableptr->columns), entry, list) { AST_LIST_TRAVERSE(&(tableptr->columns), entry, list) {
int datefield = 0; int datefield = 0;
int unknown = 0;
if (strcasecmp(entry->celname, "eventtime") == 0) { if (strcasecmp(entry->celname, "eventtime") == 0) {
datefield = 1; datefield = 1;
} }
@@ -418,7 +425,17 @@ static void odbc_log(const struct ast_event *event, void *userdata)
struct timeval date_tv = record.event_time; struct timeval date_tv = record.event_time;
struct ast_tm tm = { 0, }; struct ast_tm tm = { 0, };
ast_localtime(&date_tv, &tm, tableptr->usegmtime ? "UTC" : NULL); ast_localtime(&date_tv, &tm, tableptr->usegmtime ? "UTC" : NULL);
ast_strftime(colbuf, sizeof(colbuf), "%Y-%m-%d %H:%M:%S", &tm); /* SQL server 2008 added datetime2 and datetimeoffset data types, that
are reported to SQLColumns() as SQL_WVARCHAR, according to "Enhanced
Date/Time Type Behavior with Previous SQL Server Versions (ODBC)".
Here we format the event time with fraction seconds, so these new
column types will be set to high-precision event time. However, 'date'
and 'time' columns, also newly introduced, reported as SQL_WVARCHAR
too, and insertion of the value formatted here into these will fail.
This should be ok, however, as nobody is going to store just event
date or just time for CDR purposes.
*/
ast_strftime(colbuf, sizeof(colbuf), "%Y-%m-%d %H:%M:%S.%q", &tm);
colptr = colbuf; colptr = colbuf;
} else { } else {
if (strcmp(entry->celname, "userdeftype") == 0) { if (strcmp(entry->celname, "userdeftype") == 0) {
@@ -459,13 +476,16 @@ static void odbc_log(const struct ast_event *event, void *userdata)
snprintf(colbuf, sizeof(colbuf), "%d", record.amaflag); snprintf(colbuf, sizeof(colbuf), "%d", record.amaflag);
} else if (strcmp(entry->celname, "extra") == 0) { } else if (strcmp(entry->celname, "extra") == 0) {
ast_copy_string(colbuf, record.extra, sizeof(colbuf)); ast_copy_string(colbuf, record.extra, sizeof(colbuf));
} else if (strcmp(entry->celname, "eventtype") == 0) {
snprintf(colbuf, sizeof(colbuf), "%d", record.event_type);
} else { } else {
colbuf[0] = 0; colbuf[0] = 0;
unknown = 1;
} }
colptr = colbuf; colptr = colbuf;
} }
if (colptr) { if (colptr && !unknown) {
/* Check first if the column filters this entry. Note that this /* Check first if the column filters this entry. Note that this
* is very specifically NOT ast_strlen_zero(), because the filter * is very specifically NOT ast_strlen_zero(), because the filter
* could legitimately specify that the field is blank, which is * could legitimately specify that the field is blank, which is
@@ -536,24 +556,32 @@ static void odbc_log(const struct ast_event *event, void *userdata)
continue; continue;
} else { } else {
int year = 0, month = 0, day = 0; int year = 0, month = 0, day = 0;
if (sscanf(colptr, "%4d-%2d-%2d", &year, &month, &day) != 3 || year <= 0 || if (strcasecmp(entry->name, "eventdate") == 0) {
month <= 0 || month > 12 || day < 0 || day > 31 || struct ast_tm tm;
((month == 4 || month == 6 || month == 9 || month == 11) && day == 31) || ast_localtime(&record.event_time, &tm, tableptr->usegmtime ? "UTC" : NULL);
(month == 2 && year % 400 == 0 && day > 29) || year = tm.tm_year + 1900;
(month == 2 && year % 100 == 0 && day > 28) || month = tm.tm_mon + 1;
(month == 2 && year % 4 == 0 && day > 29) || day = tm.tm_mday;
(month == 2 && year % 4 != 0 && day > 28)) { } else {
ast_log(LOG_WARNING, "CEL variable %s is not a valid date ('%s').\n", entry->name, colptr); if (sscanf(colptr, "%4d-%2d-%2d", &year, &month, &day) != 3 || year <= 0 ||
continue; month <= 0 || month > 12 || day < 0 || day > 31 ||
} ((month == 4 || month == 6 || month == 9 || month == 11) && day == 31) ||
(month == 2 && year % 400 == 0 && day > 29) ||
(month == 2 && year % 100 == 0 && day > 28) ||
(month == 2 && year % 4 == 0 && day > 29) ||
(month == 2 && year % 4 != 0 && day > 28)) {
ast_log(LOG_WARNING, "CEL variable %s is not a valid date ('%s').\n", entry->name, colptr);
continue;
}
if (year > 0 && year < 100) { if (year > 0 && year < 100) {
year += 2000; year += 2000;
}
} }
ast_str_append(&sql, 0, "%s%s", first ? "" : ",", entry->name); ast_str_append(&sql, 0, "%s%s", first ? "" : ",", entry->name);
LENGTHEN_BUF2(17); LENGTHEN_BUF2(17);
ast_str_append(&sql2, 0, "%s{ d '%04d-%02d-%02d' }", first ? "" : ",", year, month, day); ast_str_append(&sql2, 0, "%s{d '%04d-%02d-%02d'}", first ? "" : ",", year, month, day);
} }
break; break;
case SQL_TYPE_TIME: case SQL_TYPE_TIME:
@@ -561,16 +589,24 @@ static void odbc_log(const struct ast_event *event, void *userdata)
continue; continue;
} else { } else {
int hour = 0, minute = 0, second = 0; int hour = 0, minute = 0, second = 0;
int count = sscanf(colptr, "%2d:%2d:%2d", &hour, &minute, &second); if (strcasecmp(entry->name, "eventdate") == 0) {
struct ast_tm tm;
ast_localtime(&record.event_time, &tm, tableptr->usegmtime ? "UTC" : NULL);
hour = tm.tm_hour;
minute = tm.tm_min;
second = (tableptr->allowleapsec || tm.tm_sec < 60) ? tm.tm_sec : 59;
} else {
int count = sscanf(colptr, "%2d:%2d:%2d", &hour, &minute, &second);
if ((count != 2 && count != 3) || hour < 0 || hour > 23 || minute < 0 || minute > 59 || second < 0 || second > 59) { if ((count != 2 && count != 3) || hour < 0 || hour > 23 || minute < 0 || minute > 59 || second < 0 || second > (tableptr->allowleapsec ? 60 : 59)) {
ast_log(LOG_WARNING, "CEL variable %s is not a valid time ('%s').\n", entry->name, colptr); ast_log(LOG_WARNING, "CEL variable %s is not a valid time ('%s').\n", entry->name, colptr);
continue; continue;
}
} }
ast_str_append(&sql, 0, "%s%s", first ? "" : ",", entry->name); ast_str_append(&sql, 0, "%s%s", first ? "" : ",", entry->name);
LENGTHEN_BUF2(15); LENGTHEN_BUF2(15);
ast_str_append(&sql2, 0, "%s{ t '%02d:%02d:%02d' }", first ? "" : ",", hour, minute, second); ast_str_append(&sql2, 0, "%s{t '%02d:%02d:%02d'}", first ? "" : ",", hour, minute, second);
} }
break; break;
case SQL_TYPE_TIMESTAMP: case SQL_TYPE_TIMESTAMP:
@@ -579,37 +615,44 @@ static void odbc_log(const struct ast_event *event, void *userdata)
continue; continue;
} else { } else {
int year = 0, month = 0, day = 0, hour = 0, minute = 0, second = 0; int year = 0, month = 0, day = 0, hour = 0, minute = 0, second = 0;
int count = sscanf(colptr, "%4d-%2d-%2d %2d:%2d:%2d", &year, &month, &day, &hour, &minute, &second); if (strcasecmp(entry->name, "eventdate") == 0) {
struct ast_tm tm;
ast_localtime(&record.event_time, &tm, tableptr->usegmtime ? "UTC" : NULL);
year = tm.tm_year + 1900;
month = tm.tm_mon + 1;
day = tm.tm_mday;
hour = tm.tm_hour;
minute = tm.tm_min;
second = (tableptr->allowleapsec || tm.tm_sec < 60) ? tm.tm_sec : 59;
} else {
int count = sscanf(colptr, "%4d-%2d-%2d %2d:%2d:%2d", &year, &month, &day, &hour, &minute, &second);
if ((count != 3 && count != 5 && count != 6) || year <= 0 || if ((count != 3 && count != 5 && count != 6) || year <= 0 ||
month <= 0 || month > 12 || day < 0 || day > 31 || month <= 0 || month > 12 || day < 0 || day > 31 ||
((month == 4 || month == 6 || month == 9 || month == 11) && day == 31) || ((month == 4 || month == 6 || month == 9 || month == 11) && day == 31) ||
(month == 2 && year % 400 == 0 && day > 29) || (month == 2 && year % 400 == 0 && day > 29) ||
(month == 2 && year % 100 == 0 && day > 28) || (month == 2 && year % 100 == 0 && day > 28) ||
(month == 2 && year % 4 == 0 && day > 29) || (month == 2 && year % 4 == 0 && day > 29) ||
(month == 2 && year % 4 != 0 && day > 28) || (month == 2 && year % 4 != 0 && day > 28) ||
hour > 23 || minute > 59 || second > 59 || hour < 0 || minute < 0 || second < 0) { hour > 23 || minute > 59 || second > (tableptr->allowleapsec ? 60 : 59) || hour < 0 || minute < 0 || second < 0) {
ast_log(LOG_WARNING, "CEL variable %s is not a valid timestamp ('%s').\n", entry->name, colptr); ast_log(LOG_WARNING, "CEL variable %s is not a valid timestamp ('%s').\n", entry->name, colptr);
continue; continue;
} }
if (year > 0 && year < 100) { if (year > 0 && year < 100) {
year += 2000; year += 2000;
}
} }
ast_str_append(&sql, 0, "%s%s", first ? "" : ",", entry->name); ast_str_append(&sql, 0, "%s%s", first ? "" : ",", entry->name);
LENGTHEN_BUF2(26); LENGTHEN_BUF2(27);
ast_str_append(&sql2, 0, "%s{ ts '%04d-%02d-%02d %02d:%02d:%02d' }", first ? "" : ",", year, month, day, hour, minute, second); ast_str_append(&sql2, 0, "%s{ts '%04d-%02d-%02d %02d:%02d:%02d'}", first ? "" : ",", year, month, day, hour, minute, second);
} }
break; break;
case SQL_INTEGER: case SQL_INTEGER:
{ {
int integer = 0; int integer = 0;
if (strcasecmp(entry->name, "eventtype") == 0) { if (sscanf(colptr, "%30d", &integer) != 1) {
integer = (int) record.event_type;
} else if (ast_strlen_zero(colptr)) {
continue;
} else if (sscanf(colptr, "%30d", &integer) != 1) {
ast_log(LOG_WARNING, "CEL variable %s is not an integer.\n", entry->name); ast_log(LOG_WARNING, "CEL variable %s is not an integer.\n", entry->name);
continue; continue;
} }
@@ -622,12 +665,9 @@ static void odbc_log(const struct ast_event *event, void *userdata)
case SQL_BIGINT: case SQL_BIGINT:
{ {
long long integer = 0; long long integer = 0;
if (strcasecmp(entry->name, "eventtype") == 0) { int ret;
integer = (long long) record.event_type; if ((ret = sscanf(colptr, "%30lld", &integer)) != 1) {
} else if (ast_strlen_zero(colptr)) { ast_log(LOG_WARNING, "CEL variable %s is not an integer. (%d - '%s')\n", entry->name, ret, colptr);
continue;
} else if (sscanf(colptr, "%30lld", &integer) != 1) {
ast_log(LOG_WARNING, "CEL variable %s is not an integer.\n", entry->name);
continue; continue;
} }
@@ -639,28 +679,20 @@ static void odbc_log(const struct ast_event *event, void *userdata)
case SQL_SMALLINT: case SQL_SMALLINT:
{ {
short integer = 0; short integer = 0;
if (strcasecmp(entry->name, "eventtype") == 0) { if (sscanf(colptr, "%30hd", &integer) != 1) {
integer = (short) record.event_type;
} else if (ast_strlen_zero(colptr)) {
continue;
} else if (sscanf(colptr, "%30hd", &integer) != 1) {
ast_log(LOG_WARNING, "CEL variable %s is not an integer.\n", entry->name); ast_log(LOG_WARNING, "CEL variable %s is not an integer.\n", entry->name);
continue; continue;
} }
ast_str_append(&sql, 0, "%s%s", first ? "" : ",", entry->name); ast_str_append(&sql, 0, "%s%s", first ? "" : ",", entry->name);
LENGTHEN_BUF2(6); LENGTHEN_BUF2(7);
ast_str_append(&sql2, 0, "%s%d", first ? "" : ",", integer); ast_str_append(&sql2, 0, "%s%d", first ? "" : ",", integer);
} }
break; break;
case SQL_TINYINT: case SQL_TINYINT:
{ {
char integer = 0; char integer = 0;
if (strcasecmp(entry->name, "eventtype") == 0) { if (sscanf(colptr, "%30hhd", &integer) != 1) {
integer = (char) record.event_type;
} else if (ast_strlen_zero(colptr)) {
continue;
} else if (sscanf(colptr, "%30hhd", &integer) != 1) {
ast_log(LOG_WARNING, "CEL variable %s is not an integer.\n", entry->name); ast_log(LOG_WARNING, "CEL variable %s is not an integer.\n", entry->name);
continue; continue;
} }
@@ -673,11 +705,7 @@ static void odbc_log(const struct ast_event *event, void *userdata)
case SQL_BIT: case SQL_BIT:
{ {
char integer = 0; char integer = 0;
if (strcasecmp(entry->name, "eventtype") == 0) { if (sscanf(colptr, "%30hhd", &integer) != 1) {
integer = (char) record.event_type;
} else if (ast_strlen_zero(colptr)) {
continue;
} else if (sscanf(colptr, "%30hhd", &integer) != 1) {
ast_log(LOG_WARNING, "CEL variable %s is not an integer.\n", entry->name); ast_log(LOG_WARNING, "CEL variable %s is not an integer.\n", entry->name);
continue; continue;
} }
@@ -693,17 +721,13 @@ static void odbc_log(const struct ast_event *event, void *userdata)
case SQL_DECIMAL: case SQL_DECIMAL:
{ {
double number = 0.0; double number = 0.0;
if (strcasecmp(entry->name, "eventtype") == 0) { if (sscanf(colptr, "%30lf", &number) != 1) {
number = (double)record.event_type;
} else if (ast_strlen_zero(colptr)) {
continue;
} else if (sscanf(colptr, "%30lf", &number) != 1) {
ast_log(LOG_WARNING, "CEL variable %s is not an numeric type.\n", entry->name); ast_log(LOG_WARNING, "CEL variable %s is not an numeric type.\n", entry->name);
continue; continue;
} }
ast_str_append(&sql, 0, "%s%s", first ? "" : ",", entry->name); ast_str_append(&sql, 0, "%s%s", first ? "" : ",", entry->name);
LENGTHEN_BUF2(entry->decimals); LENGTHEN_BUF2(entry->decimals + 2);
ast_str_append(&sql2, 0, "%s%*.*lf", first ? "" : ",", entry->decimals, entry->radix, number); ast_str_append(&sql2, 0, "%s%*.*lf", first ? "" : ",", entry->decimals, entry->radix, number);
} }
break; break;
@@ -712,11 +736,7 @@ static void odbc_log(const struct ast_event *event, void *userdata)
case SQL_DOUBLE: case SQL_DOUBLE:
{ {
double number = 0.0; double number = 0.0;
if (strcasecmp(entry->name, "eventtype") == 0) { if (sscanf(colptr, "%30lf", &number) != 1) {
number = (double) record.event_type;
} else if (ast_strlen_zero(colptr)) {
continue;
} else if (sscanf(colptr, "%30lf", &number) != 1) {
ast_log(LOG_WARNING, "CEL variable %s is not an numeric type.\n", entry->name); ast_log(LOG_WARNING, "CEL variable %s is not an numeric type.\n", entry->name);
continue; continue;
} }

View File

@@ -92,6 +92,7 @@
;connection=sqlserver ;connection=sqlserver
;table=AsteriskCEL ;table=AsteriskCEL
;usegmtime=yes ; defaults to no ;usegmtime=yes ; defaults to no
;allowleapsecond=no ; allow leap second in SQL column for eventtime, default yes.
;alias src => source ;alias src => source
;alias channel => source_channel ;alias channel => source_channel
;alias dst => dest ;alias dst => dest