Skip to content

Commit

Permalink
ODBC-43, ODBC-198 and ODBC-199 fixes.
Browse files Browse the repository at this point in the history
These are all date/time types related issues

Correct errors, fractional part, conversions.
ODBC-43(overflow errors detection and reporting) was partly done earlier.
ODBC-198 is mostly fix in C/C, but added similar changes to similar
function in c/odbc and added testcase.
  • Loading branch information
lawrinn committed Nov 12, 2018
1 parent ae8467a commit a94af40
Show file tree
Hide file tree
Showing 6 changed files with 159 additions and 26 deletions.
2 changes: 1 addition & 1 deletion libmariadb
69 changes: 50 additions & 19 deletions ma_typeconv.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@
#include <ma_odbc.h>

/* Borrowed from C/C and adapted */
SQLRETURN MADB_Str2Ts(const char *Str, size_t Length, SQL_TIMESTAMP_STRUCT *Ts, BOOL Interval, MADB_Error *Error)
SQLRETURN MADB_Str2Ts(const char *Str, size_t Length, SQL_TIMESTAMP_STRUCT *Ts, BOOL Interval, MADB_Error *Error, int *isTime)
{
char *Start= MADB_ALLOC(Length + 1);
my_bool is_date= 0, is_time= 0;
char *Start= MADB_ALLOC(Length + 1), *Frac, *End= Start + Length;
my_bool isDate= 0;

if (Start == NULL)
{
Expand Down Expand Up @@ -54,7 +54,7 @@ SQLRETURN MADB_Str2Ts(const char *Str, size_t Length, SQL_TIMESTAMP_STRUCT *Ts,
{
return MADB_SetError(Error, MADB_ERR_22008, NULL, 0);
}
is_date= 1;
isDate= 1;
if (!(Start= strchr(Start, ' ')))
{
goto check;
Expand All @@ -65,17 +65,29 @@ SQLRETURN MADB_Str2Ts(const char *Str, size_t Length, SQL_TIMESTAMP_STRUCT *Ts,
goto check;
}

is_time= 1;
if (isDate == 0)
{
*isTime= 1;
}

if (strchr(Start, '.')) /* fractional seconds */
if (Frac= strchr(Start, '.')) /* fractional seconds */
{
if (sscanf(Start, "%hd:%hu:%hu.%lu", &Ts->hour, &Ts->minute,
size_t FracMulIdx= End - (Frac + 1) - 1/*to get index array index */;
/* ODBC - nano-seconds */
if (sscanf(Start, "%hd:%hu:%hu.%9lu", &Ts->hour, &Ts->minute,
&Ts->second, &Ts->fraction) < 4)
{
return MADB_SetError(Error, MADB_ERR_22008, NULL, 0);
}
/* 9 digits up to nano-seconds, and -1 since comparing with arr idx */
if (FracMulIdx < 9 - 1)
{
static unsigned long Mul[]= {100000000, 10000000, 1000000, 100000, 10000, 1000, 100, 10 };
Ts->fraction*= Mul[FracMulIdx];
}
}
else {
else
{
if (sscanf(Start, "%hd:%hu:%hu", &Ts->hour, &Ts->minute,
&Ts->second) < 3)
{
Expand All @@ -86,7 +98,7 @@ SQLRETURN MADB_Str2Ts(const char *Str, size_t Length, SQL_TIMESTAMP_STRUCT *Ts,
check:
if (Interval == FALSE)
{
if (is_date)
if (isDate)
{
if (Ts->year > 0)
{
Expand Down Expand Up @@ -369,10 +381,11 @@ SQLRETURN MADB_Char2Sql(MADB_Stmt *Stmt, MADB_DescRecord *CRec, void* DataPtr, S
case SQL_DATETIME:
{
SQL_TIMESTAMP_STRUCT Ts;
int isTime;

/* Enforcing constraints on date/time values */
RETURN_ERROR_OR_CONTINUE(MADB_Str2Ts(DataPtr, Length, &Ts, FALSE, &Stmt->Error));
RETURN_ERROR_OR_CONTINUE(MADB_TsConversionIsPossible(&Ts, SqlRec->ConciseType, &Stmt->Error, MADB_ERR_22008));
RETURN_ERROR_OR_CONTINUE(MADB_Str2Ts(DataPtr, Length, &Ts, FALSE, &Stmt->Error, &isTime));
RETURN_ERROR_OR_CONTINUE(MADB_TsConversionIsPossible(&Ts, SqlRec->ConciseType, &Stmt->Error, MADB_ERR_22018, isTime));
/* To stay on the safe side - still sending as string in the default branch */
}
default:
Expand Down Expand Up @@ -421,7 +434,7 @@ SQLRETURN MADB_Numeric2Sql(MADB_Stmt *Stmt, MADB_DescRecord *CRec, void* DataPtr
/* }}} */

/* {{{ MADB_TsConversionIsPossible */
SQLRETURN MADB_TsConversionIsPossible(SQL_TIMESTAMP_STRUCT *ts, SQLSMALLINT SqlType, MADB_Error *Error, enum enum_madb_error SqlState)
SQLRETURN MADB_TsConversionIsPossible(SQL_TIMESTAMP_STRUCT *ts, SQLSMALLINT SqlType, MADB_Error *Error, enum enum_madb_error SqlState, int isTime)
{
/* I think instead of MADB_ERR_22008 there should be also SqlState */
switch (SqlType)
Expand All @@ -440,8 +453,8 @@ SQLRETURN MADB_TsConversionIsPossible(SQL_TIMESTAMP_STRUCT *ts, SQLSMALLINT SqlT
return MADB_SetError(Error, MADB_ERR_22008, NULL, 0);
}
default:
/* This only would be good for SQL_TYPE_TIME */
if (ts->year == 0 || ts->month == 0 || ts->day == 0)
/* This only would be good for SQL_TYPE_TIME. If C type is time(isTime!=0), and SQL type is timestamp, date fields may be NULL - driver should set them to current date */
if (isTime == 0 && ts->year == 0 || ts->month == 0 || ts->day == 0)
{
return MADB_SetError(Error, SqlState, NULL, 0);
}
Expand All @@ -457,7 +470,7 @@ SQLRETURN MADB_Timestamp2Sql(MADB_Stmt *Stmt, MADB_DescRecord *CRec, void* DataP
MYSQL_TIME *tm= NULL;
SQL_TIMESTAMP_STRUCT *ts= (SQL_TIMESTAMP_STRUCT *)DataPtr;

RETURN_ERROR_OR_CONTINUE(MADB_TsConversionIsPossible(ts, SqlRec->ConciseType, &Stmt->Error, MADB_ERR_22007));
RETURN_ERROR_OR_CONTINUE(MADB_TsConversionIsPossible(ts, SqlRec->ConciseType, &Stmt->Error, MADB_ERR_22007, 0));

if (*Buffer == NULL)
{
Expand Down Expand Up @@ -553,9 +566,27 @@ SQLRETURN MADB_Time2Sql(MADB_Stmt *Stmt, MADB_DescRecord *CRec, void* DataPtr, S
tm= *Buffer;
}

tm->year= 1970;
tm->month= 1;
tm->day= 1;
if(SqlRec->ConciseType == SQL_TYPE_TIMESTAMP ||
SqlRec->ConciseType == SQL_TIMESTAMP || SqlRec->ConciseType == SQL_DATETIME)
{
time_t sec_time;
struct tm * cur_tm;

sec_time= time(NULL);
cur_tm= localtime(&sec_time);

tm->year= 1900 + cur_tm->tm_year;
tm->month= cur_tm->tm_mon + 1;
tm->day= cur_tm->tm_mday;
tm->second_part= 0;
}
else
{
tm->year= 0;
tm->month= 0;
tm->day= 0;
}


tm->hour= ts->hour;
tm->minute= ts->minute;
Expand Down Expand Up @@ -674,7 +705,7 @@ SQLRETURN MADB_ConvertC2Sql(MADB_Stmt *Stmt, MADB_DescRecord *CRec, void* DataPt
RETURN_ERROR_OR_CONTINUE(MADB_Timestamp2Sql(Stmt, CRec, DataPtr, Length, SqlRec, MaBind, Buffer, LengthPtr));
break;
case SQL_C_TIME:
case SQL_TYPE_TIME:
case SQL_C_TYPE_TIME:
RETURN_ERROR_OR_CONTINUE(MADB_Time2Sql(Stmt, CRec, DataPtr, Length, SqlRec, MaBind, Buffer, LengthPtr));
break;
case SQL_C_INTERVAL_HOUR_TO_MINUTE:
Expand Down
2 changes: 1 addition & 1 deletion ma_typeconv.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,5 @@ SQLRETURN MADB_Date2Sql(MADB_Stmt *Stmt, MADB_DescRecord *CRec, void* DataPtr, S
SQLRETURN MADB_ConvertC2Sql(MADB_Stmt *Stmt, MADB_DescRecord *CRec, void* DataPtr, SQLLEN Length,
MADB_DescRecord *SqlRec, MYSQL_BIND *MaBind, void **Buffer, unsigned long *LengthPtr);

SQLRETURN MADB_TsConversionIsPossible(SQL_TIMESTAMP_STRUCT *ts, SQLSMALLINT SqlType, MADB_Error *Error, enum enum_madb_error SqlState);
SQLRETURN MADB_TsConversionIsPossible(SQL_TIMESTAMP_STRUCT *ts, SQLSMALLINT SqlType, MADB_Error *Error, enum enum_madb_error SqlState, int isTime);
#endif
65 changes: 62 additions & 3 deletions test/datetime.c
Original file line number Diff line number Diff line change
Expand Up @@ -1029,7 +1029,7 @@ ODBC_TEST(t_bug60646)
"SELECT timestamp('2012-01-01 01:01:01.000001')" /*1*/
" ,timestamp('2012-01-01 01:01:01.100002')" /*2*/
" ,'2011-07-29 17:52:15.0000000009'" /*3*/
" ,'1000-01-01 12:00:00.000000001'" /*4*/
" ,'1000-01-01 12:00:00.000001'" /*4*/
" ,time('2011-12-31 23:59:59.999999')" /*5*/
);
CHECK_STMT_RC(Stmt, SQLFetch(Stmt));
Expand All @@ -1048,7 +1048,7 @@ ODBC_TEST(t_bug60646)
Test using string as MySQL does not support units less than a microsecond */
CHECK_STMT_RC(Stmt, SQLGetData(Stmt, 3, SQL_C_TYPE_TIMESTAMP, &ts, sizeof(ts),
&len));
is_num(ts.fraction, 9000);
is_num(ts.fraction, 0);

/* 4) testing if min fraction detected
Again - mysql supports microseconds only. thus using string
Expand Down Expand Up @@ -1261,7 +1261,9 @@ ODBC_TEST(t_odbc70)
0, 0, ZeroDate, 0, NULL));

EXPECT_STMT(Stmt, SQLExecute(Stmt), SQL_ERROR);
CHECK_SQLSTATE(Stmt, "22008");
CHECK_SQLSTATE(Stmt, "22018");

OK_SIMPLE_STMT(Stmt, "DROP TABLE IF EXISTS t_odbc70");

return OK;
}
Expand Down Expand Up @@ -1489,6 +1491,62 @@ ODBC_TEST(t_odbc148)
}


ODBC_TEST(t_odbc199_time2timestamp)
{
SQL_TIME_STRUCT t;
SQL_TIMESTAMP_STRUCT ts= {0}, ts1= {0};
time_t sec_time;
struct tm * cur_tm;

sec_time= time(NULL);
cur_tm= localtime(&sec_time);

ts.year= 1900 + cur_tm->tm_year;
ts.month= cur_tm->tm_mon + 1;
ts.day= cur_tm->tm_mday;
ts.fraction= 0;

ts1.year= ts1.month= ts1.day= 1;
t.hour= 11;
t.minute= 21;
t.second= 33;

ts.hour= t.hour;
ts.minute= t.minute;
ts.second= t.second;

OK_SIMPLE_STMT(Stmt, "DROP TABLE IF EXISTS t_time2timestamp");
OK_SIMPLE_STMT(Stmt, "CREATE TABLE t_time2timestamp (ts TIMESTAMP)");

CHECK_STMT_RC(Stmt, SQLBindParameter(Stmt, 1, SQL_PARAM_INPUT, SQL_C_TYPE_TIME, SQL_TIMESTAMP,
0, 0, &t, sizeof(t), NULL));

CHECK_STMT_RC(Stmt, SQLPrepare(Stmt, "INSERT INTO t_time2timestamp(ts) \
VALUES (?)", SQL_NTS));
/* First valid values */
CHECK_STMT_RC(Stmt, SQLExecute(Stmt));

OK_SIMPLE_STMT(Stmt, "SELECT ts FROM t_time2timestamp");

CHECK_STMT_RC(Stmt, SQLFetch(Stmt));
CHECK_STMT_RC(Stmt, SQLGetData(Stmt, 1, SQL_C_TYPE_TIMESTAMP, &ts1, 0, NULL));

diag("This test may fail if run at the moment when day changes");
is_num(ts.year, ts1.year);
is_num(ts.month, ts1.month);
is_num(ts.day, ts1.day);
is_num(ts.hour, ts1.hour);
is_num(ts.minute, ts1.minute);
is_num(ts.second, ts1.second);
is_num(ts.fraction, ts1.fraction);

CHECK_STMT_RC(Stmt, SQLCloseCursor(Stmt));

OK_SIMPLE_STMT(Stmt, "DROP TABLE IF EXISTS t_time2timestamp");

return OK;
}

MA_ODBC_TESTS my_tests[]=
{
{my_ts, "my_ts", NORMAL},
Expand All @@ -1515,6 +1573,7 @@ MA_ODBC_TESTS my_tests[]=
{t_bug67793, "t_bug67793", NORMAL},
{t_odbc138, "t_odbc138_dateadd_negative", NORMAL},
{t_odbc148, "t_odbc148_datatypes_values_len", NORMAL},
{ t_odbc199_time2timestamp, "t_odbc199_time2timestamp", NORMAL},
{NULL, NULL}
};

Expand Down
44 changes: 43 additions & 1 deletion test/error.c
Original file line number Diff line number Diff line change
Expand Up @@ -690,6 +690,47 @@ ODBC_TEST(t_odbc123)
}


ODBC_TEST(t_odbc43)
{
char *TimeWithFraction= "00:00:00.123", *DateWithTime= "2018-11-06 00:00:00.01",
*GoodTime= "14:24:33.00", *GoodDate= "2002-02-02 00:00:00";
SQLLEN Len= SQL_NTS;

OK_SIMPLE_STMT(Stmt, "DROP TABLE IF EXISTS t_odbc43");
OK_SIMPLE_STMT(Stmt, "CREATE TABLE t_odbc43 (d DATE, t TIME)");

CHECK_STMT_RC(Stmt, SQLPrepare(Stmt, "INSERT INTO t_odbc43(d, t) \
VALUES (?, ?)", SQL_NTS));

/* First valid values */
CHECK_STMT_RC(Stmt, SQLBindParameter(Stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_DATE,
0, 0, GoodDate, 0, &Len));
CHECK_STMT_RC(Stmt, SQLBindParameter(Stmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR,
SQL_TIME, 0, 0, GoodTime, 0, &Len));

CHECK_STMT_RC(Stmt, SQLExecute(Stmt));

CHECK_STMT_RC(Stmt, SQLBindParameter(Stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_DATE,
0, 0, DateWithTime, 0, &Len));
CHECK_STMT_RC(Stmt, SQLBindParameter(Stmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR,
SQL_TIME, 0, 0, GoodTime, 0, &Len));

EXPECT_STMT(Stmt, SQLExecute(Stmt), SQL_ERROR);
CHECK_SQLSTATE(Stmt, "22008");

CHECK_STMT_RC(Stmt, SQLBindParameter(Stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_DATE,
0, 0, GoodDate, 0, &Len));
CHECK_STMT_RC(Stmt, SQLBindParameter(Stmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR,
SQL_TIME, 0, 0, TimeWithFraction, 0, &Len));

EXPECT_STMT(Stmt, SQLExecute(Stmt), SQL_ERROR);
CHECK_SQLSTATE(Stmt, "22008");

OK_SIMPLE_STMT(Stmt, "DROP TABLE t_odbc43");

return OK;
}

MA_ODBC_TESTS my_tests[]=
{
{t_odbc3_error, "t_odbc3_error"},
Expand All @@ -710,7 +751,8 @@ MA_ODBC_TESTS my_tests[]=
{t_bug49466, "t_bug49466"},
{t_odbc94, "t_odbc94"},
{t_odbc115, "t_odbc115"},
{ t_odbc123, "t_odbc123" },
{t_odbc123, "t_odbc123"},
{t_odbc43, "t_odbc43_datetime_overflow"},
{NULL, NULL}
};

Expand Down
3 changes: 2 additions & 1 deletion test/result2.c
Original file line number Diff line number Diff line change
Expand Up @@ -1385,7 +1385,7 @@ ODBC_TEST(t_odbc192)
ROW_WITH_DATETIME Rows[1];
SQLLEN BaseOffset=0x01, *OffsetPtr= (SQLLEN*)((char*)Rows - BaseOffset);

SQLExecDirect(Stmt, "DROP TABLE /*IF EXISTS*/ t_odbc192", SQL_NTS);
OK_SIMPLE_STMT(Stmt, "DROP TABLE IF EXISTS t_odbc192");
OK_SIMPLE_STMT(Stmt, "CREATE TABLE t_odbc192 (some_ts datetime)");
OK_SIMPLE_STMT(Stmt, "INSERT INTO t_odbc192 (some_ts) VALUES('2018-10-23 12:00:01')");

Expand Down Expand Up @@ -1415,6 +1415,7 @@ ODBC_TEST(t_odbc192)
is_num(Rows[0].val.second, 1);

CHECK_STMT_RC(Stmt, SQLFreeStmt(Stmt, SQL_CLOSE));
OK_SIMPLE_STMT(Stmt, "DROP TABLE t_odbc192");

return OK;
}
Expand Down

0 comments on commit a94af40

Please sign in to comment.