diff --git a/configure.ac b/configure.ac index 6b835ec..80547c4 100644 --- a/configure.ac +++ b/configure.ac @@ -232,6 +232,7 @@ AM_CONDITIONAL(ENABLE_DOCBOOK, test -n "$DOCBOOK_DSL") dnl Checks for library functions. VL_LIB_READLINE +AC_CHECK_FUNC(strptime,[ AC_DEFINE(HAVE_STRPTIME, 1, [strptime check]) AM_CONDITIONAL(HAVE_STRPTIME, true) ],[ AM_CONDITIONAL(HAVE_STRPTIME, false) ]) localedir=${datadir}/locale AC_SUBST(localedir) diff --git a/include/mdbsql.h b/include/mdbsql.h index 7fa8ffb..9c889e3 100644 --- a/include/mdbsql.h +++ b/include/mdbsql.h @@ -83,6 +83,7 @@ extern void mdb_sql_all_columns(MdbSQL *sql); extern void mdb_sql_sel_count(MdbSQL *sql); extern int mdb_sql_add_column(MdbSQL *sql, char *column_name); extern int mdb_sql_add_table(MdbSQL *sql, char *table_name); +extern char *mdb_sql_strptime(MdbSQL *sql, char *data, char *format); extern void mdb_sql_dump(MdbSQL *sql); extern void mdb_sql_exit(MdbSQL *sql); extern void mdb_sql_reset(MdbSQL *sql); diff --git a/include/mdbtools.h b/include/mdbtools.h index 60ed640..394b0d6 100644 --- a/include/mdbtools.h +++ b/include/mdbtools.h @@ -336,6 +336,7 @@ typedef struct { struct mdbsargtree { int op; MdbColumn *col; + unsigned char val_type; MdbAny value; void *parent; MdbSargNode *left; @@ -482,6 +483,7 @@ extern const char *mdb_col_get_prop(const MdbColumn *col, const gchar *key); extern int mdb_bind_column_by_name(MdbTableDef *table, gchar *col_name, void *bind_ptr, int *len_ptr); extern void mdb_data_dump(MdbTableDef *table); extern void mdb_date_to_tm(double td, struct tm *t); +extern void mdb_tm_to_date(struct tm *t, double *td); extern void mdb_bind_column(MdbTableDef *table, int col_num, void *bind_ptr, int *len_ptr); extern int mdb_rewind_table(MdbTableDef *table); extern int mdb_fetch_row(MdbTableDef *table); diff --git a/src/libmdb/data.c b/src/libmdb/data.c index 92e86c5..3950181 100644 --- a/src/libmdb/data.c +++ b/src/libmdb/data.c @@ -16,6 +16,7 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#define _XOPEN_SOURCE #include #include #include "mdbtools.h" @@ -37,6 +38,9 @@ static size_t mdb_copy_ole(MdbHandle *mdb, void *dest, int start, int size); #endif static char date_fmt[64] = "%x %X"; +static int noleap_cal[] = {0,31,59,90,120,151,181,212,243,273,304,334,365}; +static int leap_cal[] = {0,31,60,91,121,152,182,213,244,274,305,335,366}; + void mdb_set_date_fmt(const char *fmt) { @@ -778,18 +782,29 @@ static int trim_trailing_zeros(char * buff) } #endif + /* Date/Time is stored as a double, where the whole part is the days from 12/30/1899 and the fractional part is the fractional part of one day. */ +void +mdb_tm_to_date(struct tm *t, double *td) +{ + short yr = t->tm_year + 1900; + char leap = ((yr & 3) == 0) && ((yr % 100) != 0 || (yr % 400) == 0); + int *cal = leap ? leap_cal : noleap_cal; + long int time = (yr*365+(yr/4)-(yr/100)+(yr/400)+cal[t->tm_mon]+t->tm_mday)-693959; + + *td = (((long)t->tm_hour * 3600)+((long)t->tm_min * 60)+((long)t->tm_sec)) / 86400.0; + if (time>=0) *td+=time; else *td=time-*td; +} + void mdb_date_to_tm(double td, struct tm *t) { long int day, time; int yr, q; int *cal; - int noleap_cal[] = {0,31,59,90,120,151,181,212,243,273,304,334,365}; - int leap_cal[] = {0,31,60,91,121,152,182,213,244,274,305,335,366}; day = (long int)(td); time = (long int)(fabs(td - day) * 86400.0 + 0.5); diff --git a/src/libmdb/sargs.c b/src/libmdb/sargs.c index 1325852..fcf9557 100644 --- a/src/libmdb/sargs.c +++ b/src/libmdb/sargs.c @@ -98,6 +98,43 @@ int mdb_test_int(MdbSargNode *node, gint32 i) return 0; } +/* Actually used to not rely on libm. + * Maybe there is a cleaner and faster solution? */ +static double poor_mans_trunc(double x) +{ + char buf[16]; + sprintf(buf, "%.6f", x); + sscanf(buf, "%lf", &x); + return x; +} + +int mdb_test_double(int op, double vd, double d) +{ + switch (op) { + case MDB_EQUAL: + //fprintf(stderr, "comparing %lf and %lf\n", d, node->value.d); + if (vd == d) return 1; + break; + case MDB_GT: + if (vd < d) return 1; + break; + case MDB_LT: + if (vd > d) return 1; + break; + case MDB_GTEQ: + if (vd <= d) return 1; + break; + case MDB_LTEQ: + if (vd >= d) return 1; + break; + default: + fprintf(stderr, "Calling mdb_test_sarg on unknown operator. Add code to mdb_test_double() for operator %d\n",op); + break; + } + return 0; +} + +#if 0 // Obsolete int mdb_test_date(MdbSargNode *node, double td) { @@ -140,6 +177,7 @@ mdb_test_date(MdbSargNode *node, double td) } return 0; } +#endif int @@ -196,7 +234,7 @@ mdb_test_sarg(MdbHandle *mdb, MdbColumn *col, MdbSargNode *node, MdbField *field mdb_unicode2ascii(mdb, field->value, field->siz, tmpbuf, 256); return mdb_test_string(node, tmpbuf); case MDB_DATETIME: - return mdb_test_date(node, mdb_get_double(field->value, 0)); + return mdb_test_double(node->op, poor_mans_trunc(node->value.d), poor_mans_trunc(mdb_get_double(field->value, 0))); default: fprintf(stderr, "Calling mdb_test_sarg on unknown type. Add code to mdb_test_sarg() for type %d\n",col->col_type); break; diff --git a/src/odbc/odbc.c b/src/odbc/odbc.c index 57e386f..b798ef5 100644 --- a/src/odbc/odbc.c +++ b/src/odbc/odbc.c @@ -44,6 +44,8 @@ static SQLRETURN SQL_API _SQLAllocStmt(SQLHDBC hdbc, SQLHSTMT *phstmt); static SQLRETURN SQL_API _SQLFreeConnect(SQLHDBC hdbc); static SQLRETURN SQL_API _SQLFreeEnv(SQLHENV henv); static SQLRETURN SQL_API _SQLFreeStmt(SQLHSTMT hstmt, SQLUSMALLINT fOption); +static SQLRETURN SQL_API _SQLPrepare(SQLHSTMT hstmt, SQLCHAR *szSqlStr, SQLINTEGER cbSqlStr); + static void bind_columns (struct _hstmt*); static void unbind_columns (struct _hstmt*); @@ -1111,11 +1113,7 @@ static SQLRETURN SQL_API _SQLExecDirect( SQLCHAR *szSqlStr, SQLINTEGER cbSqlStr) { - struct _hstmt *stmt = (struct _hstmt *) hstmt; - - TRACE("_SQLExecDirect"); - strcpy(stmt->query, (char*)szSqlStr); - + _SQLPrepare(hstmt, szSqlStr, cbSqlStr); return _SQLExecute(hstmt); } @@ -1362,7 +1360,7 @@ SQLRETURN SQL_API SQLNumResultCols( return SQL_SUCCESS; } -SQLRETURN SQL_API SQLPrepare( +static SQLRETURN SQL_API _SQLPrepare( SQLHSTMT hstmt, SQLCHAR *szSqlStr, SQLINTEGER cbSqlStr) @@ -1378,6 +1376,14 @@ SQLRETURN SQL_API SQLPrepare( return SQL_SUCCESS; } +SQLRETURN SQL_API SQLPrepare( + SQLHSTMT hstmt, + SQLCHAR *szSqlStr, + SQLINTEGER cbSqlStr) +{ + return _SQLPrepare(hstmt, szSqlStr, cbSqlStr); +} + SQLRETURN SQL_API SQLRowCount( SQLHSTMT hstmt, SQLLEN *pcrow) diff --git a/src/sql/lexer.l b/src/sql/lexer.l index caa297f..9e10ba4 100644 --- a/src/sql/lexer.l +++ b/src/sql/lexer.l @@ -46,6 +46,7 @@ null { return NUL; } (>=) { return GTEQ; } like { return LIKE; } count { return COUNT; } +strptime { return STRPTIME; } [ \t\r] ; \"[^"]*\"\" { diff --git a/src/sql/mdbsql.c b/src/sql/mdbsql.c index 2086b98..42e62b2 100644 --- a/src/sql/mdbsql.c +++ b/src/sql/mdbsql.c @@ -17,6 +17,7 @@ */ #include +#define _XOPEN_SOURCE #include "mdbsql.h" #ifdef DMALLOC @@ -28,6 +29,12 @@ #include #endif +#ifdef HAVE_STRPTIME +#include +#include +#include +#endif + char *g_input_ptr; /* Prevent warnings from -Wmissing-prototypes. */ @@ -334,6 +341,50 @@ mdb_sql_dump_node(MdbSargNode *node, int level) mdb_sql_dump_node(node->right, mylevel); } } + +/* Parses date format specifier into JET date representation (double) */ +char * +mdb_sql_strptime(MdbSQL *sql, char *data, char *format) +{ +#ifndef HAVE_STRPTIME + mdb_sql_error(sql, "Your system doesn't support strptime."); + mdb_sql_reset(sql); + return NULL; +#else + char *p, *pszDate; + struct tm tm={0}; + double date=0; + + if (data[0]!='\'' || *(p=&data[strlen(data)-1])!='\'') { + mdb_sql_error(sql, "First parameter of strptime (data) must be a string."); + mdb_sql_reset(sql); + return NULL; + } + *p=0; ++data; + if (format[0]!='\'' || *(p=&format[strlen(format)-1])!='\'') { + mdb_sql_error(sql, "Second parameter of strptime (format) must be a string."); + mdb_sql_reset(sql); + return NULL; + } + *p=0; ++format; + if (!strptime(data, format, &tm)) { + mdb_sql_error(sql, "strptime('%s','%s') failed.", data, format); + mdb_sql_reset(sql); + return NULL; + } + mdb_tm_to_date(&tm, &date); + /* It seems that when just using a time offset without date in strptime, + * we always get 1 as decimal part, een though it should be 0 for time */ + if (date < 2 && date > 1) date--; + if ((pszDate=malloc(16))) { + char cLocale=localeconv()->decimal_point[0], *p; + sprintf(pszDate, "%lf", date); + if (cLocale!='.') for (p=pszDate; *p; p++) if (*p==cLocale) *p='.'; + } + return pszDate; +#endif +} + /* evaluate a expression involving 2 constants and add answer to the stack */ int mdb_sql_eval_expr(MdbSQL *sql, char *const1, int op, char *const2) @@ -387,6 +438,7 @@ int mdb_sql_add_sarg(MdbSQL *sql, char *col_name, int op, char *constant) { int lastchar; + char *p; MdbSargNode *node; node = mdb_sql_alloc_node(); @@ -406,8 +458,14 @@ mdb_sql_add_sarg(MdbSQL *sql, char *col_name, int op, char *constant) lastchar = strlen(constant) > 256 ? 256 : strlen(constant); strncpy(node->value.s, &constant[1], lastchar - 2);; node->value.s[lastchar - 1]='\0'; + node->val_type = MDB_TEXT; + } else if ((p=strchr(constant, '.'))) { + *p=localeconv()->decimal_point[0]; + node->value.d = strtod(constant, NULL); + node->val_type = MDB_DOUBLE; } else { node->value.i = atoi(constant); + node->val_type = MDB_INT; } mdb_sql_push_node(sql, node); @@ -478,6 +536,8 @@ void mdb_sql_exit(MdbSQL *sql) } void mdb_sql_reset(MdbSQL *sql) { + unsigned int i; + if (sql->cur_table) { mdb_index_scan_free(sql->cur_table); if (sql->cur_table->sarg_tree) { @@ -489,7 +549,6 @@ void mdb_sql_reset(MdbSQL *sql) } /* Reset bound values */ - unsigned int i; for (i=0;inum_columns;i++) { g_free(sql->bound_values[i]); sql->bound_values[i] = NULL; @@ -653,24 +712,40 @@ void mdb_sql_describe_table(MdbSQL *sql) sql->cur_table = ttable; } +MdbColumn *mdb_sql_find_colbyname(MdbTableDef *table, char *name) +{ + unsigned int i; + MdbColumn *col; + + for (i=0;inum_cols;i++) { + col=g_ptr_array_index(table->columns,i); + if (!g_ascii_strcasecmp(col->name, name)) return col; + } + return NULL; +} + int mdb_sql_find_sargcol(MdbSargNode *node, gpointer data) { MdbTableDef *table = data; - unsigned int i; MdbColumn *col; if (!mdb_is_relational_op(node->op)) return 0; if (!node->parent) return 0; - for (i=0;inum_cols;i++) { - col=g_ptr_array_index(table->columns,i); - if (!g_ascii_strcasecmp(col->name, (char *)node->parent)) { - node->col = col; - break; + if ((col = mdb_sql_find_colbyname(table, (char *)node->parent))) { + node->col = col; + /* Do conversion to required target value type. + * Plain integers are UNIX timestamps for backwards compatibility of parser + */ + if (col->col_type == MDB_DATETIME && node->val_type == MDB_INT) { + struct tm *tm = gmtime((time_t*)&node->value.i); + mdb_tm_to_date(tm, &node->value.d); + node->val_type = MDB_DOUBLE; } } return 0; } + void mdb_sql_select(MdbSQL *sql) { @@ -687,6 +762,7 @@ int found = 0; return; } + if (!sql->num_tables) return; sql_tab = g_ptr_array_index(sql->tables,0); table = mdb_read_table_by_name(mdb, sql_tab->name, MDB_TABLE); diff --git a/src/sql/parser.y b/src/sql/parser.y index af64556..f87c0c0 100644 --- a/src/sql/parser.y +++ b/src/sql/parser.y @@ -42,7 +42,7 @@ static MdbSQL *g_sql; %token IDENT NAME PATH STRING NUMBER -%token SELECT FROM WHERE CONNECT DISCONNECT TO LIST TABLES AND OR NOT COUNT +%token SELECT FROM WHERE CONNECT DISCONNECT TO LIST TABLES AND OR NOT COUNT STRPTIME %token DESCRIBE TABLE %token LTEQ GTEQ LIKE IS NUL @@ -132,7 +132,12 @@ nulloperator: ; constant: - NUMBER { $$ = $1; } + STRPTIME '(' constant ',' constant ')' { + $$ = mdb_sql_strptime(_mdb_sql(NULL), $3, $5); + free($3); + free($5); + } + | NUMBER { $$ = $1; } | STRING { $$ = $1; } ;