From dfdd418ac76d585f57e19b58910ba377e345e3d2 Mon Sep 17 00:00:00 2001 From: Bogdan Pintea Date: Sat, 15 Dec 2018 16:42:15 +0200 Subject: [PATCH 1/3] add interval support This adds support for the interval types: - columns of type interval are now accepted; - the driver will perform the translation between ES/SQL's ISO 8601 representation and the SQL standard; - the results can be converted to all permited data types. --- driver/connect.c | 477 +++++-- driver/convert.c | 1186 +++++++++++++++-- driver/defs.h | 87 +- driver/handles.c | 86 +- driver/info.c | 34 +- driver/odbc.c | 6 +- driver/queries.c | 34 +- driver/util.c | 96 +- driver/util.h | 14 +- test/connected_dbc.cc | 142 ++- test/test_conversion_compatibility.cc | 43 +- test/test_conversion_sql2c_interval.cc | 1615 +++++++++++++++++++++++- 12 files changed, 3358 insertions(+), 462 deletions(-) diff --git a/driver/connect.c b/driver/connect.c index d03afcd8..3a192b54 100644 --- a/driver/connect.c +++ b/driver/connect.c @@ -24,31 +24,117 @@ /* Elasticsearch/SQL data types */ /* 2 */ -#define JSON_COL_IP "ip" +#define TYPE_IP "IP" /* 4 */ -#define JSON_COL_BYTE "byte" -#define JSON_COL_LONG "long" -#define JSON_COL_TEXT "text" -#define JSON_COL_DATE "date" -#define JSON_COL_NULL "null" +#define TYPE_BYTE "BYTE" +#define TYPE_LONG "LONG" +#define TYPE_TEXT "TEXT" +#define TYPE_DATE "DATE" +#define TYPE_NULL "NULL" /* 5 */ -#define JSON_COL_SHORT "short" -#define JSON_COL_FLOAT "float" +#define TYPE_SHORT "SHORT" +#define TYPE_FLOAT "FLOAT" /* 6 */ -#define JSON_COL_DOUBLE "double" -#define JSON_COL_BINARY "binary" -#define JSON_COL_OBJECT "object" -#define JSON_COL_NESTED "nested" +#define TYPE_DOUBLE "DOUBLE" +#define TYPE_BINARY "BINARY" +#define TYPE_OBJECT "OBJECT" +#define TYPE_NESTED "NESTED" /* 7 */ -#define JSON_COL_BOOLEAN "boolean" -#define JSON_COL_INTEGER "integer" -#define JSON_COL_KEYWORD "keyword" +#define TYPE_BOOLEAN "BOOLEAN" +#define TYPE_INTEGER "INTEGER" +#define TYPE_KEYWORD "KEYWORD" /* 10 */ -#define JSON_COL_HALF_FLOAT "half_float" +#define TYPE_HALF_FLOAT "HALF_FLOAT" /* 11 */ -#define JSON_COL_UNSUPPORTED "unsupported" +#define TYPE_UNSUPPORTED "UNSUPPORTED" /* 12 */ -#define JSON_COL_SCALED_FLOAT "scaled_float" +#define TYPE_SCALED_FLOAT "SCALED_FLOAT" +/* + * intervals + */ +#define TYPE_IVL_DAY "INTERVAL_DAY" +/* 13 */ +#define TYPE_IVL_YEAR "INTERVAL_YEAR" +#define TYPE_IVL_HOUR "INTERVAL_HOUR" +/* 14 */ +#define TYPE_IVL_MONTH "INTERVAL_MONTH" +/* 15 */ +#define TYPE_IVL_MINUTE "INTERVAL_MINUTE" +#define TYPE_IVL_SECOND "INTERVAL_SECOND" +/* 20 */ +#define TYPE_IVL_DAY_TO_HOUR "INTERVAL_DAY_TO_HOUR" +/* 22 */ +#define TYPE_IVL_DAY_TO_MINUTE "INTERVAL_DAY_TO_MINUTE" +#define TYPE_IVL_YEAR_TO_MONTH "INTERVAL_YEAR_TO_MONTH" +#define TYPE_IVL_DAY_TO_SECOND "INTERVAL_DAY_TO_SECOND" +/* 23 */ +#define TYPE_IVL_HOUR_TO_MINUTE "INTERVAL_HOUR_TO_MINUTE" +#define TYPE_IVL_HOUR_TO_SECOND "INTERVAL_HOUR_TO_SECOND" +/* 25 */ +#define TYPE_IVL_MINUTE_TO_SECOND "INTERVAL_MINUTE_TO_SECOND" + +/* + * ES-to-C-SQL mappings. + * DATA_TYPE(SYS TYPES) : SQL_ -> SQL_C_ + * Intervals not covered, since C==SQL, with no ES customization. + */ +/* -6: SQL_TINYINT -> SQL_C_TINYINT */ +#define ES_BYTE_TO_CSQL SQL_C_TINYINT +#define ES_BYTE_TO_SQL SQL_TINYINT +/* 5: SQL_SMALLINT -> SQL_C_SHORT */ +#define ES_SHORT_TO_CSQL SQL_C_SSHORT +#define ES_SHORT_TO_SQL SQL_SMALLINT +/* 4: SQL_INTEGER -> SQL_C_LONG */ +#define ES_INTEGER_TO_CSQL SQL_C_SLONG +#define ES_INTEGER_TO_SQL SQL_INTEGER +/* -5: SQL_BIGINT -> SQL_C_SBIGINT */ +#define ES_LONG_TO_CSQL SQL_C_SBIGINT +#define ES_LONG_TO_SQL SQL_BIGINT +/* 6: SQL_FLOAT -> SQL_C_DOUBLE */ +#define ES_HALF_TO_CSQL_FLOAT SQL_C_DOUBLE +#define ES_HALF_TO_SQL_FLOAT SQL_FLOAT +/* 6: SQL_FLOAT -> SQL_C_DOUBLE */ +#define ES_SCALED_TO_CSQL_FLOAT SQL_C_DOUBLE +#define ES_SCALED_TO_SQL_FLOAT SQL_FLOAT +/* 7: SQL_REAL -> SQL_C_DOUBLE */ +#define ES_FLOAT_TO_CSQL SQL_C_FLOAT +#define ES_FLOAT_TO_SQL SQL_REAL +/* 8: SQL_DOUBLE -> SQL_C_FLOAT */ +#define ES_DOUBLE_TO_CSQL SQL_C_DOUBLE +#define ES_DOUBLE_TO_SQL SQL_DOUBLE +/* 16: ??? -> SQL_C_TINYINT */ +#define ES_BOOLEAN_TO_CSQL SQL_C_BIT +#define ES_BOOLEAN_TO_SQL SQL_BIT +/* 12: SQL_VARCHAR -> SQL_C_WCHAR */ +#define ES_KEYWORD_TO_CSQL SQL_C_WCHAR /* XXX: CBOR needs _CHAR */ +#define ES_KEYWORD_TO_SQL SQL_VARCHAR +/* 12: SQL_VARCHAR -> SQL_C_WCHAR */ +#define ES_TEXT_TO_CSQL SQL_C_WCHAR /* XXX: CBOR needs _CHAR */ +#define ES_TEXT_TO_SQL SQL_VARCHAR +/* 12: SQL_VARCHAR -> SQL_C_WCHAR */ +#define ES_IP_TO_CSQL SQL_C_WCHAR /* XXX: CBOR needs _CHAR */ +#define ES_IP_TO_SQL SQL_VARCHAR +/* 93: SQL_TYPE_TIMESTAMP -> SQL_C_TYPE_TIMESTAMP */ +#define ES_DATE_TO_CSQL SQL_C_TYPE_TIMESTAMP +#define ES_DATE_TO_SQL SQL_TYPE_TIMESTAMP +/* -3: SQL_VARBINARY -> SQL_C_BINARY */ +#define ES_BINARY_TO_CSQL SQL_C_BINARY +#define ES_BINARY_TO_SQL SQL_VARBINARY +/* 0: SQL_TYPE_NULL -> SQL_C_TINYINT */ +#define ES_NULL_TO_CSQL SQL_C_STINYINT // ??? +#define ES_NULL_TO_SQL SQL_TYPE_NULL +/* + * ES-non mappable + */ +/* 1111: ??? -> SQL_C_BINARY */ +#define ES_UNSUPPORTED_TO_CSQL SQL_C_BINARY +#define ES_UNSUPPORTED_TO_SQL ESODBC_SQL_UNSUPPORTED +/* 2002: ??? -> SQL_C_BINARY */ +#define ES_OBJECT_TO_CSQL SQL_C_BINARY +#define ES_OBJECT_TO_SQL ESODBC_SQL_OBJECT +/* 2002: ??? -> SQL_C_BINARY */ +#define ES_NESTED_TO_CSQL SQL_C_BINARY +#define ES_NESTED_TO_SQL ESODBC_SQL_NESTED /* structure for one row returned by the ES. @@ -739,7 +825,7 @@ SQLRETURN config_dbc(esodbc_dbc_st *dbc, esodbc_dsn_attrs_st *attrs) goto err; } - if (! str2bigint(&attrs->secure, /*wide?*/TRUE, &secure)) { + if (str2bigint(&attrs->secure, /*wide?*/TRUE, &secure, /*stri*/TRUE) < 0) { ERRH(dbc, "failed to read secure param `" LWPDL "`.", LWSTR(&attrs->secure)); SET_HDIAG(dbc, SQL_STATE_HY000, "security setting number " @@ -826,7 +912,8 @@ SQLRETURN config_dbc(esodbc_dbc_st *dbc, esodbc_dsn_attrs_st *attrs) /* * request timeout for liburl: negative reset to 0 */ - if (! str2bigint(&attrs->timeout, /*wide?*/TRUE, (SQLBIGINT *)&timeout)) { + if (str2bigint(&attrs->timeout, /*wide?*/TRUE, + (SQLBIGINT *)&timeout, /*strict*/TRUE) < 0) { ERRH(dbc, "failed to convert `" LWPDL "` [%zu] to big int.", LWSTR(&attrs->timeout), attrs->timeout.cnt); SET_HDIAG(dbc, SQL_STATE_HY000, "timeout setting number " @@ -843,8 +930,8 @@ SQLRETURN config_dbc(esodbc_dbc_st *dbc, esodbc_dsn_attrs_st *attrs) /* * set max body size */ - if (! str2bigint(&attrs->max_body_size, /*wide?*/TRUE, - (SQLBIGINT *)&max_body_size)) { + if (str2bigint(&attrs->max_body_size, /*wide?*/TRUE, + (SQLBIGINT *)&max_body_size, /*strict*/TRUE) < 0) { ERRH(dbc, "failed to convert max body size `" LWPDL "` [%zu] to LL.", LWSTR(&attrs->max_body_size), attrs->max_body_size.cnt); SET_HDIAG(dbc, SQL_STATE_HY000, "max body size setting number " @@ -865,8 +952,8 @@ SQLRETURN config_dbc(esodbc_dbc_st *dbc, esodbc_dsn_attrs_st *attrs) /* * set max fetch size */ - if (! str2bigint(&attrs->max_fetch_size, /*wide?*/TRUE, - (SQLBIGINT *)&max_fetch_size)) { + if (str2bigint(&attrs->max_fetch_size, /*wide?*/TRUE, + (SQLBIGINT *)&max_fetch_size, /*strict*/TRUE) < 0) { ERRH(dbc, "failed to convert max fetch size `" LWPDL "` [%zu] to LL.", LWSTR(&attrs->max_fetch_size), attrs->max_fetch_size.cnt); SET_HDIAG(dbc, SQL_STATE_HY000, "max fetch size setting number " @@ -1018,67 +1105,211 @@ SQLRETURN do_connect(esodbc_dbc_st *dbc, esodbc_dsn_attrs_st *attrs) return ret; } - -/* Maps ES/SQL type name to C SQL and SQL id values. */ -static BOOL elastic_name2types(wstr_st *type_name, +static BOOL elastic_intervals_name2types(wstr_st *type_name, SQLSMALLINT *c_sql, SQLSMALLINT *sql) { - assert(0 < type_name->cnt); switch (type_name->cnt) { - /* 2: IP */ - case sizeof(JSON_COL_IP) - 1: - switch (tolower(type_name->str[0])) { - case (SQLWCHAR)'i': + /* 12: INTERVAL_DAY */ + case sizeof(TYPE_IVL_DAY) - 1: + if (! wmemncasecmp(type_name->str, + MK_WPTR(TYPE_IVL_DAY), type_name->cnt)) { + *c_sql = SQL_C_INTERVAL_DAY; + *sql = SQL_INTERVAL_DAY; + return TRUE; + } + break; + /* 13: INTERVAL_YEAR, INTERVAL_HOUR */ + case sizeof(TYPE_IVL_YEAR) - 1: + switch (tolower(type_name->str[/*Y in INTERVAL_YEAR*/9])) { + case (SQLWCHAR)'y': + if (! wmemncasecmp(type_name->str, + MK_WPTR(TYPE_IVL_YEAR), type_name->cnt)) { + *c_sql = SQL_C_INTERVAL_YEAR; + *sql = SQL_INTERVAL_YEAR; + return TRUE; + } + break; + case (SQLWCHAR)'h': + if (! wmemncasecmp(type_name->str, + MK_WPTR(TYPE_IVL_HOUR), type_name->cnt)) { + *c_sql = SQL_C_INTERVAL_HOUR; + *sql = SQL_INTERVAL_HOUR; + return TRUE; + } + break; + } + break; + /* 14: INTERVAL_MONTH */ + case sizeof(TYPE_IVL_MONTH) - 1: + if (! wmemncasecmp(type_name->str, + MK_WPTR(TYPE_IVL_MONTH), type_name->cnt)) { + *c_sql = SQL_C_INTERVAL_MONTH; + *sql = SQL_INTERVAL_MONTH; + return TRUE; + } + break; + /* 15: INTERVAL_MINUTE, INTERVAL_SECOND */ + case sizeof(TYPE_IVL_MINUTE) - 1: + switch (tolower(type_name->str[/*last letter*/14])) { + case (SQLWCHAR)'e': + if (! wmemncasecmp(type_name->str, + MK_WPTR(TYPE_IVL_MINUTE), type_name->cnt)) { + *c_sql = SQL_C_INTERVAL_MINUTE; + *sql = SQL_INTERVAL_MINUTE; + return TRUE; + } + break; + case (SQLWCHAR)'d': + if (! wmemncasecmp(type_name->str, + MK_WPTR(TYPE_IVL_SECOND), type_name->cnt)) { + *c_sql = SQL_C_INTERVAL_SECOND; + *sql = SQL_INTERVAL_SECOND; + return TRUE; + } + break; + } + break; + /* 20: TYPE_IVL_DAY_TO_HOUR */ + case sizeof(TYPE_IVL_DAY_TO_HOUR) - 1: + if (! wmemncasecmp(type_name->str, + MK_WPTR(TYPE_IVL_DAY_TO_HOUR), type_name->cnt)) { + *c_sql = SQL_C_INTERVAL_DAY_TO_HOUR; + *sql = SQL_INTERVAL_DAY_TO_HOUR; + return TRUE; + } + break; + /* 22: INTERVAL_DAY_TO_MINUTE, INTERVAL_YEAR_TO_MONTH, + * INTERVAL_DAY_TO_SECOND */ + case sizeof(TYPE_IVL_DAY_TO_MINUTE) - 1: + switch (tolower(type_name->str[/*last letter*/21])) { + case (SQLWCHAR)'e': + if (! wmemncasecmp(type_name->str, + MK_WPTR(TYPE_IVL_DAY_TO_MINUTE), type_name->cnt)) { + *c_sql = SQL_C_INTERVAL_DAY_TO_MINUTE; + *sql = SQL_INTERVAL_DAY_TO_MINUTE; + return TRUE; + } + break; + case (SQLWCHAR)'h': + if (! wmemncasecmp(type_name->str, + MK_WPTR(TYPE_IVL_YEAR_TO_MONTH), type_name->cnt)) { + *c_sql = SQL_C_INTERVAL_YEAR_TO_MONTH; + *sql = SQL_INTERVAL_YEAR_TO_MONTH; + return TRUE; + } + break; + case (SQLWCHAR)'d': if (! wmemncasecmp(type_name->str, - MK_WPTR(JSON_COL_IP), type_name->cnt)) { - *c_sql = ESODBC_ES_TO_CSQL_IP; - *sql = ESODBC_ES_TO_SQL_IP; + MK_WPTR(TYPE_IVL_DAY_TO_SECOND), type_name->cnt)) { + *c_sql = SQL_C_INTERVAL_DAY_TO_SECOND; + *sql = SQL_INTERVAL_DAY_TO_SECOND; return TRUE; } break; } break; + /* 23: INTERVAL_HOUR_TO_MINUTE, TYPE_IVL_HOUR_TO_SECOND */ + case sizeof(TYPE_IVL_HOUR_TO_MINUTE) - 1: + switch (tolower(type_name->str[/*last letter*/22])) { + case (SQLWCHAR)'e': + if (! wmemncasecmp(type_name->str, + MK_WPTR(TYPE_IVL_HOUR_TO_MINUTE), + type_name->cnt)) { + *c_sql = SQL_C_INTERVAL_HOUR_TO_MINUTE; + *sql = SQL_INTERVAL_HOUR_TO_MINUTE; + return TRUE; + } + break; + case (SQLWCHAR)'d': + if (! wmemncasecmp(type_name->str, + MK_WPTR(TYPE_IVL_HOUR_TO_SECOND), + type_name->cnt)) { + *c_sql = SQL_C_INTERVAL_HOUR_TO_SECOND; + *sql = SQL_INTERVAL_HOUR_TO_SECOND; + return TRUE; + } + break; + } + break; + /* 25: INTERVAL_MINUTE_TO_SECOND */ + case sizeof(TYPE_IVL_MINUTE_TO_SECOND) - 1: + if (! wmemncasecmp(type_name->str, + MK_WPTR(TYPE_IVL_MINUTE_TO_SECOND), type_name->cnt)) { + *c_sql = SQL_C_INTERVAL_MINUTE_TO_SECOND; + *sql = SQL_INTERVAL_MINUTE_TO_SECOND; + return TRUE; + } + break; + } + + ERR("unrecognized Elastic type `" LWPDL "` (%zd).", LWSTR(type_name), + type_name->cnt); + return FALSE; +} + +/* Maps ES/SQL type name to C SQL and SQL id values. + * ES/SQL type ID uses ODBC spec 3.x values for most common types (ES/SQL's + * "DATE" is an ODBC "TIMESTAMP", as an exception). + * The values are set here, since the driver: + * - must set these for the non-common types (KEYWORD etc.); + * - would need to check if the above mentioned identity is still true. + * => ignore ES/SQL's type IDs, set these explicitely. + */ +static BOOL elastic_name2types(wstr_st *type_name, + SQLSMALLINT *c_sql, SQLSMALLINT *sql) +{ + assert(0 < type_name->cnt); + switch (type_name->cnt) { + /* 2: IP */ + case sizeof(TYPE_IP) - 1: + if (! wmemncasecmp(type_name->str, + MK_WPTR(TYPE_IP), type_name->cnt)) { + *c_sql = ES_IP_TO_CSQL; + *sql = ES_IP_TO_SQL; + return TRUE; + } + break; /* 4: BYTE, LONG, TEXT, DATE, NULL */ - case sizeof(JSON_COL_BYTE) - 1: + case sizeof(TYPE_BYTE) - 1: switch (tolower(type_name->str[0])) { case (SQLWCHAR)'b': if (! wmemncasecmp(type_name->str, - MK_WPTR(JSON_COL_BYTE), type_name->cnt)) { - *c_sql = ESODBC_ES_TO_CSQL_BYTE; - *sql = ESODBC_ES_TO_SQL_BYTE; + MK_WPTR(TYPE_BYTE), type_name->cnt)) { + *c_sql = ES_BYTE_TO_CSQL; + *sql = ES_BYTE_TO_SQL; return TRUE; } break; case (SQLWCHAR)'l': - if (! wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_LONG), + if (! wmemncasecmp(type_name->str, MK_WPTR(TYPE_LONG), type_name->cnt)) { - *c_sql = ESODBC_ES_TO_CSQL_LONG; - *sql = ESODBC_ES_TO_SQL_LONG; + *c_sql = ES_LONG_TO_CSQL; + *sql = ES_LONG_TO_SQL; return TRUE; } break; case (SQLWCHAR)'t': - if (! wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_TEXT), + if (! wmemncasecmp(type_name->str, MK_WPTR(TYPE_TEXT), type_name->cnt)) { - *c_sql = ESODBC_ES_TO_CSQL_TEXT; - *sql = ESODBC_ES_TO_SQL_TEXT; + *c_sql = ES_TEXT_TO_CSQL; + *sql = ES_TEXT_TO_SQL; return TRUE; } break; case (SQLWCHAR)'d': - if (! wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_DATE), + if (! wmemncasecmp(type_name->str, MK_WPTR(TYPE_DATE), type_name->cnt)) { - *c_sql = ESODBC_ES_TO_CSQL_DATE; - *sql = ESODBC_ES_TO_SQL_DATE; + *c_sql = ES_DATE_TO_CSQL; + *sql = ES_DATE_TO_SQL; return TRUE; } break; case (SQLWCHAR)'n': - if (! wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_NULL), + if (! wmemncasecmp(type_name->str, MK_WPTR(TYPE_NULL), type_name->cnt)) { - *c_sql = ESODBC_ES_TO_CSQL_NULL; - *sql = ESODBC_ES_TO_SQL_NULL; + *c_sql = ES_NULL_TO_CSQL; + *sql = ES_NULL_TO_SQL; return TRUE; } break; @@ -1086,21 +1317,21 @@ static BOOL elastic_name2types(wstr_st *type_name, break; /* 5: SHORT, FLOAT */ - case sizeof(JSON_COL_SHORT) - 1: + case sizeof(TYPE_SHORT) - 1: switch (tolower(type_name->str[0])) { case (SQLWCHAR)'s': - if (! wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_SHORT), + if (! wmemncasecmp(type_name->str, MK_WPTR(TYPE_SHORT), type_name->cnt)) { - *c_sql = ESODBC_ES_TO_CSQL_SHORT; - *sql = ESODBC_ES_TO_SQL_SHORT; + *c_sql = ES_SHORT_TO_CSQL; + *sql = ES_SHORT_TO_SQL; return TRUE; } break; case (SQLWCHAR)'f': - if (! wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_FLOAT), + if (! wmemncasecmp(type_name->str, MK_WPTR(TYPE_FLOAT), type_name->cnt)) { - *c_sql = ESODBC_ES_TO_CSQL_FLOAT; - *sql = ESODBC_ES_TO_SQL_FLOAT; + *c_sql = ES_FLOAT_TO_CSQL; + *sql = ES_FLOAT_TO_SQL; return TRUE; } break; @@ -1108,37 +1339,37 @@ static BOOL elastic_name2types(wstr_st *type_name, break; /* 6: DOUBLE, BINARY, OBJECT, NESTED */ - case sizeof(JSON_COL_DOUBLE) - 1: + case sizeof(TYPE_DOUBLE) - 1: switch (tolower(type_name->str[0])) { case (SQLWCHAR)'d': if (! wmemncasecmp(type_name->str, - MK_WPTR(JSON_COL_DOUBLE), type_name->cnt)) { - *c_sql = ESODBC_ES_TO_CSQL_DOUBLE; - *sql = ESODBC_ES_TO_SQL_DOUBLE; + MK_WPTR(TYPE_DOUBLE), type_name->cnt)) { + *c_sql = ES_DOUBLE_TO_CSQL; + *sql = ES_DOUBLE_TO_SQL; return TRUE; } break; case (SQLWCHAR)'b': if (! wmemncasecmp(type_name->str, - MK_WPTR(JSON_COL_BINARY), type_name->cnt)) { - *c_sql = ESODBC_ES_TO_CSQL_BINARY; - *sql = ESODBC_ES_TO_SQL_BINARY; + MK_WPTR(TYPE_BINARY), type_name->cnt)) { + *c_sql = ES_BINARY_TO_CSQL; + *sql = ES_BINARY_TO_SQL; return TRUE; } break; case (SQLWCHAR)'o': if (! wmemncasecmp(type_name->str, - MK_WPTR(JSON_COL_OBJECT), type_name->cnt)) { - *c_sql = ESODBC_ES_TO_CSQL_OBJECT; - *sql = ESODBC_ES_TO_SQL_OBJECT; + MK_WPTR(TYPE_OBJECT), type_name->cnt)) { + *c_sql = ES_OBJECT_TO_CSQL; + *sql = ES_OBJECT_TO_SQL; return TRUE; } break; case (SQLWCHAR)'n': if (! wmemncasecmp(type_name->str, - MK_WPTR(JSON_COL_NESTED), type_name->cnt)) { - *c_sql = ESODBC_ES_TO_CSQL_NESTED; - *sql = ESODBC_ES_TO_SQL_NESTED; + MK_WPTR(TYPE_NESTED), type_name->cnt)) { + *c_sql = ES_NESTED_TO_CSQL; + *sql = ES_NESTED_TO_SQL; return TRUE; } break; @@ -1146,29 +1377,29 @@ static BOOL elastic_name2types(wstr_st *type_name, break; /* 7: INTEGER, BOOLEAN, KEYWORD */ - case sizeof(JSON_COL_INTEGER) - 1: + case sizeof(TYPE_INTEGER) - 1: switch (tolower(type_name->str[0])) { case (SQLWCHAR)'i': /* integer */ - if (wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_INTEGER), + if (wmemncasecmp(type_name->str, MK_WPTR(TYPE_INTEGER), type_name->cnt) == 0) { - *c_sql = ESODBC_ES_TO_CSQL_INTEGER; - *sql = ESODBC_ES_TO_SQL_INTEGER; + *c_sql = ES_INTEGER_TO_CSQL; + *sql = ES_INTEGER_TO_SQL; return TRUE; } break; case (SQLWCHAR)'b': /* boolean */ - if (wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_BOOLEAN), + if (wmemncasecmp(type_name->str, MK_WPTR(TYPE_BOOLEAN), type_name->cnt) == 0) { - *c_sql = ESODBC_ES_TO_CSQL_BOOLEAN; - *sql = ESODBC_ES_TO_SQL_BOOLEAN; + *c_sql = ES_BOOLEAN_TO_CSQL; + *sql = ES_BOOLEAN_TO_SQL; return TRUE; } break; case (SQLWCHAR)'k': /* keyword */ - if (wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_KEYWORD), + if (wmemncasecmp(type_name->str, MK_WPTR(TYPE_KEYWORD), type_name->cnt) == 0) { - *c_sql = ESODBC_ES_TO_CSQL_KEYWORD; - *sql = ESODBC_ES_TO_SQL_KEYWORD; + *c_sql = ES_KEYWORD_TO_CSQL; + *sql = ES_KEYWORD_TO_SQL; return TRUE; } break; @@ -1176,39 +1407,37 @@ static BOOL elastic_name2types(wstr_st *type_name, break; /* 10: HALF_FLOAT */ - case sizeof(JSON_COL_HALF_FLOAT) - 1: - if (! wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_HALF_FLOAT), + case sizeof(TYPE_HALF_FLOAT) - 1: + if (! wmemncasecmp(type_name->str, MK_WPTR(TYPE_HALF_FLOAT), type_name->cnt)) { - *c_sql = ESODBC_ES_TO_CSQL_HALF_FLOAT; - *sql = ESODBC_ES_TO_SQL_HALF_FLOAT; + *c_sql = ES_HALF_TO_CSQL_FLOAT; + *sql = ES_HALF_TO_SQL_FLOAT; return TRUE; } break; /* 11: UNSUPPORTED */ - case sizeof(JSON_COL_UNSUPPORTED) - 1: - if (! wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_UNSUPPORTED), + case sizeof(TYPE_UNSUPPORTED) - 1: + if (! wmemncasecmp(type_name->str, MK_WPTR(TYPE_UNSUPPORTED), type_name->cnt)) { - *c_sql = ESODBC_ES_TO_CSQL_UNSUPPORTED; - *sql = ESODBC_ES_TO_SQL_UNSUPPORTED; + *c_sql = ES_UNSUPPORTED_TO_CSQL; + *sql = ES_UNSUPPORTED_TO_SQL; return TRUE; } break; /* 12: SCALED_FLOAT */ - case sizeof(JSON_COL_SCALED_FLOAT) - 1: - if (! wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_SCALED_FLOAT), + case sizeof(TYPE_SCALED_FLOAT) - 1: + if (! wmemncasecmp(type_name->str, MK_WPTR(TYPE_SCALED_FLOAT), type_name->cnt)) { - *c_sql = ESODBC_ES_TO_CSQL_SCALED_FLOAT; - *sql = ESODBC_ES_TO_SQL_SCALED_FLOAT; + *c_sql = ES_SCALED_TO_CSQL_FLOAT; + *sql = ES_SCALED_TO_SQL_FLOAT; return TRUE; } break; - } - ERR("unrecognized Elastic type `" LWPDL "` (%zd).", LWSTR(type_name), - type_name->cnt); - return FALSE; + + return elastic_intervals_name2types(type_name, c_sql, sql); } /* @@ -1223,6 +1452,7 @@ static void set_display_size(esodbc_estype_st *es_type) case SQL_WCHAR: case SQL_WVARCHAR: case SQL_WLONGVARCHAR: + // TODO: 45 if IP? es_type->display_size = es_type->column_size; break; @@ -1277,33 +1507,68 @@ static void set_display_size(esodbc_estype_st *es_type) es_type->display_size = SQL_NO_TOTAL; break; - /* - case SQL_TYPE_UTCDATETIME: - case SQL_TYPE_UTCTIME: - */ - - case SQL_DECIMAL: - case SQL_NUMERIC: - + /* intervals */ case SQL_INTERVAL_MONTH: + es_type->display_size = ESODBC_MAX_IVL_MONTH_LEAD_PREC; + break; case SQL_INTERVAL_YEAR: + es_type->display_size = ESODBC_MAX_IVL_YEAR_LEAD_PREC; + break; case SQL_INTERVAL_YEAR_TO_MONTH: + es_type->display_size = 3 + ESODBC_MAX_IVL_YEAR_LEAD_PREC; + break; case SQL_INTERVAL_DAY: + es_type->display_size = ESODBC_MAX_IVL_DAY_LEAD_PREC; + break; case SQL_INTERVAL_HOUR: + es_type->display_size = ESODBC_MAX_IVL_HOUR_LEAD_PREC; + break; case SQL_INTERVAL_MINUTE: + es_type->display_size = ESODBC_MAX_IVL_MINUTE_LEAD_PREC; + break; case SQL_INTERVAL_SECOND: + es_type->display_size = ESODBC_MAX_IVL_SECOND_LEAD_PREC + /*.*/1 + + ESODBC_MAX_SEC_PRECISION; + break; case SQL_INTERVAL_DAY_TO_HOUR: + es_type->display_size = 3 + ESODBC_MAX_IVL_DAY_LEAD_PREC; + break; case SQL_INTERVAL_DAY_TO_MINUTE: + es_type->display_size = 6 + ESODBC_MAX_IVL_DAY_LEAD_PREC; + break; case SQL_INTERVAL_DAY_TO_SECOND: + es_type->display_size = 10 + ESODBC_MAX_IVL_DAY_LEAD_PREC + + ESODBC_MAX_SEC_PRECISION; + break; case SQL_INTERVAL_HOUR_TO_MINUTE: + es_type->display_size = 3 + ESODBC_MAX_IVL_HOUR_LEAD_PREC; + break; case SQL_INTERVAL_HOUR_TO_SECOND: + es_type->display_size = 7 + ESODBC_MAX_IVL_HOUR_LEAD_PREC + + ESODBC_MAX_SEC_PRECISION; + break; case SQL_INTERVAL_MINUTE_TO_SECOND: + es_type->display_size = 4 + ESODBC_MAX_IVL_MINUTE_LEAD_PREC + + ESODBC_MAX_SEC_PRECISION; + break; + + /* + case SQL_TYPE_UTCDATETIME: + case SQL_TYPE_UTCTIME: + */ + + case SQL_DECIMAL: + case SQL_NUMERIC: case SQL_GUID: default: BUG("unsupported ES/SQL data type: %d.", es_type->data_type); + return; } + + DBG("data type: %hd, display size: %lld", es_type->data_type, + es_type->data_type); } static BOOL bind_types_cols(esodbc_stmt_st *stmt, estype_row_st *type_row) @@ -1451,7 +1716,7 @@ static void *copy_types_rows(esodbc_dbc_st *dbc, estype_row_st *type_row, * other statement), which causes issues, since it's a non-SQL type * => change it to SQL_BIT */ if (types[i].data_type == ESODBC_SQL_BOOLEAN) { - types[i].data_type = ESODBC_ES_TO_SQL_BOOLEAN; + types[i].data_type = ES_BOOLEAN_TO_SQL; } /* .data_type is used in data conversions -> make sure the SQL type diff --git a/driver/convert.c b/driver/convert.c index 27375a38..5684108b 100644 --- a/driver/convert.c +++ b/driver/convert.c @@ -47,6 +47,9 @@ RET_HDIAGS(_stmt, SQL_STATE_22003); \ } while (0) +/* maximum lenght of an interval literal (with terminator), with no field + * sanity checks: five longs with separators and sign */ +#define INTERVAL_LIT_MAX_LEN (5 * sizeof("4294967295")) #if (0x0300 <= ODBCVER) # define ESSQL_TYPE_MIN SQL_GUID @@ -79,10 +82,12 @@ static BOOL compat_matrix[ESSQL_NORM_RANGE][ESSQL_C_NORM_RANGE] = {FALSE}; compat_matrix[ESSQL_TYPE_IDX(_sql)][ESSQL_C_TYPE_IDX(_csql)]) /* populates the compat_matrix as required in: - * https://docs.microsoft.com/en-us/sql/odbc/reference/appendixes/converting-data-from-c-to-sql-data-types */ + * https://docs.microsoft.com/en-us/sql/odbc/reference/appendixes/converting-data-from-c-to-sql-data-types + * "from" and "to" nameing attributes below correspond to items in the + * vertical and horizontal lists, respectively. */ void convert_init() { - SQLSMALLINT i, j, sql, csql; + SQLSMALLINT i, j, sql, csql, lim_i, lim_j; /*INDENT-OFF*/ SQLSMALLINT block_idx_sql[] = {SQL_CHAR, SQL_VARCHAR, SQL_LONGVARCHAR, SQL_WCHAR, SQL_WVARCHAR, SQL_WLONGVARCHAR, SQL_DECIMAL, @@ -97,31 +102,44 @@ void convert_init() SQL_C_USHORT, SQL_C_SHORT, SQL_C_SLONG, SQL_C_ULONG, SQL_C_LONG, SQL_C_FLOAT, SQL_C_DOUBLE, SQL_C_BINARY }; - SQLSMALLINT to_csql_interval[] = {SQL_CHAR, SQL_VARCHAR, SQL_LONGVARCHAR, - SQL_WCHAR, SQL_WVARCHAR, SQL_WLONGVARCHAR, SQL_DECIMAL, - SQL_NUMERIC, SQL_TINYINT, SQL_SMALLINT, SQL_INTEGER, SQL_BIGINT + + /* SQL types convertible to all interval types (v-chars) */ + SQLSMALLINT to_csql_interval_all[] = {SQL_CHAR, SQL_VARCHAR, + SQL_LONGVARCHAR, SQL_WCHAR, SQL_WVARCHAR, SQL_WLONGVARCHAR }; - SQLSMALLINT from_sql_interval[] = {SQL_C_CHAR, SQL_C_WCHAR, - SQL_C_BIT,SQL_C_NUMERIC, SQL_C_STINYINT, SQL_C_UTINYINT, - SQL_C_TINYINT, SQL_C_SBIGINT, SQL_C_UBIGINT, SQL_C_SSHORT, - SQL_C_USHORT, SQL_C_SHORT, SQL_C_SLONG, SQL_C_ULONG, - SQL_C_LONG + /* SQL types convertible to single-component intervals only */ + SQLSMALLINT to_csql_interval_single[] = {SQL_DECIMAL, SQL_NUMERIC, + SQL_TINYINT, SQL_SMALLINT, SQL_INTEGER, SQL_BIGINT + }; + SQLSMALLINT from_sql_interval_all[] = {SQL_C_CHAR, SQL_C_WCHAR}; + SQLSMALLINT from_sql_interval_single[] = {SQL_C_BIT, + SQL_C_NUMERIC, SQL_C_STINYINT, SQL_C_UTINYINT, SQL_C_TINYINT, + SQL_C_SBIGINT, SQL_C_UBIGINT, SQL_C_SSHORT, SQL_C_USHORT, + SQL_C_SHORT, SQL_C_SLONG, SQL_C_ULONG, SQL_C_LONG }; - SQLSMALLINT sql_interval[] = {SQL_INTERVAL_MONTH, SQL_INTERVAL_YEAR, - SQL_INTERVAL_YEAR_TO_MONTH, SQL_INTERVAL_DAY, - SQL_INTERVAL_HOUR, SQL_INTERVAL_MINUTE, SQL_INTERVAL_SECOND, + SQLSMALLINT sql_interval[] = {SQL_INTERVAL_YEAR, SQL_INTERVAL_MONTH, + SQL_INTERVAL_DAY, SQL_INTERVAL_HOUR, SQL_INTERVAL_MINUTE, + SQL_INTERVAL_SECOND, SQL_INTERVAL_YEAR_TO_MONTH, SQL_INTERVAL_DAY_TO_HOUR, SQL_INTERVAL_DAY_TO_MINUTE, SQL_INTERVAL_DAY_TO_SECOND, SQL_INTERVAL_HOUR_TO_MINUTE, SQL_INTERVAL_HOUR_TO_SECOND, SQL_INTERVAL_MINUTE_TO_SECOND }; - SQLSMALLINT csql_interval[] = {SQL_C_INTERVAL_DAY, SQL_C_INTERVAL_HOUR, - SQL_C_INTERVAL_MINUTE, SQL_C_INTERVAL_SECOND, + SQLSMALLINT sql_interval_single[] = {SQL_INTERVAL_YEAR, SQL_INTERVAL_MONTH, + SQL_INTERVAL_DAY, SQL_INTERVAL_HOUR, SQL_INTERVAL_MINUTE, + SQL_INTERVAL_SECOND + }; + SQLSMALLINT csql_interval[] = {SQL_C_INTERVAL_YEAR, SQL_C_INTERVAL_MONTH, + SQL_C_INTERVAL_DAY, SQL_C_INTERVAL_HOUR, SQL_C_INTERVAL_MINUTE, + SQL_C_INTERVAL_SECOND, SQL_C_INTERVAL_YEAR_TO_MONTH, SQL_C_INTERVAL_DAY_TO_HOUR, SQL_C_INTERVAL_DAY_TO_MINUTE, SQL_C_INTERVAL_DAY_TO_SECOND, SQL_C_INTERVAL_HOUR_TO_MINUTE, - SQL_C_INTERVAL_HOUR_TO_SECOND, - SQL_C_INTERVAL_MINUTE_TO_SECOND, SQL_C_INTERVAL_MONTH, - SQL_C_INTERVAL_YEAR, SQL_C_INTERVAL_YEAR_TO_MONTH + SQL_C_INTERVAL_HOUR_TO_SECOND, SQL_C_INTERVAL_MINUTE_TO_SECOND }; + SQLSMALLINT csql_interval_single[] = {SQL_C_INTERVAL_YEAR, + SQL_C_INTERVAL_MONTH, SQL_C_INTERVAL_DAY, SQL_C_INTERVAL_HOUR, + SQL_C_INTERVAL_MINUTE, SQL_C_INTERVAL_SECOND + }; + SQLSMALLINT to_csql_datetime[] = {SQL_CHAR, SQL_VARCHAR, SQL_LONGVARCHAR, SQL_WCHAR, SQL_WVARCHAR, SQL_WLONGVARCHAR, SQL_TYPE_DATE, SQL_TYPE_TIME, SQL_TYPE_TIMESTAMP @@ -131,8 +149,8 @@ void convert_init() }; /* fill the compact block of TRUEs (growing from the upper left corner) */ - for (i = 0; i < sizeof(block_idx_sql)/sizeof(*block_idx_sql) ; i ++) { - for (j = 0; j < sizeof(block_idx_csql)/sizeof(*block_idx_csql) ; j ++) { + for (i = 0; i < sizeof(block_idx_sql)/sizeof(*block_idx_sql); i ++) { + for (j = 0; j < sizeof(block_idx_csql)/sizeof(*block_idx_csql); j ++) { sql = block_idx_sql[i]; csql = block_idx_csql[j]; compat_matrix[ESSQL_TYPE_IDX(sql)][ESSQL_C_TYPE_IDX(csql)] = TRUE; @@ -153,24 +171,58 @@ void convert_init() compat_matrix[ESSQL_TYPE_IDX(ESODBC_SQL_NULL)][csql] = TRUE; } - /* set conversions to INTERVAL_C */ - for (i = 0; i < sizeof(to_csql_interval)/sizeof(*to_csql_interval); i ++) { - for (j = 0; j < sizeof(csql_interval)/sizeof(*csql_interval); j ++ ) { - sql = to_csql_interval[i]; + /* set conversions to all INTERVAL_C */ + lim_i = sizeof(to_csql_interval_all)/sizeof(*to_csql_interval_all); + lim_j = sizeof(csql_interval)/sizeof(*csql_interval); + for (i = 0; i < lim_i; i ++) { + for (j = 0; j < lim_j; j ++ ) { + sql = to_csql_interval_all[i]; csql = csql_interval[j]; compat_matrix[ESSQL_TYPE_IDX(sql)][ESSQL_C_TYPE_IDX(csql)] = TRUE; } } + /* set conversions to single-component INTERVAL_C */ + lim_i = sizeof(to_csql_interval_single)/sizeof(*to_csql_interval_single); + lim_j = sizeof(csql_interval_single)/sizeof(*csql_interval_single); + for (i = 0; i < lim_i; i ++) { + for (j = 0; j < lim_j; j ++ ) { + sql = to_csql_interval_single[i]; + csql = csql_interval_single[j]; + compat_matrix[ESSQL_TYPE_IDX(sql)][ESSQL_C_TYPE_IDX(csql)] = TRUE; + } + } - /* set conversions from INTERVAL_SQL */ - for (i = 0; i < sizeof(sql_interval)/sizeof(*sql_interval); i ++) { - for (j = 0; j < sizeof(from_sql_interval)/sizeof(*from_sql_interval); + /* set conversions from all INTERVAL_SQL */ + lim_i = sizeof(sql_interval)/sizeof(*sql_interval); + lim_j = sizeof(from_sql_interval_all)/sizeof(*from_sql_interval_all); + for (i = 0; i < lim_i; i ++) { + for (j = 0; j < lim_j; j ++ ) { sql = sql_interval[i]; - csql = from_sql_interval[j]; + csql = from_sql_interval_all[j]; compat_matrix[ESSQL_TYPE_IDX(sql)][ESSQL_C_TYPE_IDX(csql)] = TRUE; } } + /* set conversions from single_component INTERVAL_SQL */ + lim_i = sizeof(sql_interval_single)/sizeof(*sql_interval_single); + lim_j = sizeof(from_sql_interval_single)/sizeof(*from_sql_interval_single); + for (i = 0; i < lim_i; i ++) { + for (j = 0; j < lim_j; + j ++ ) { + sql = sql_interval_single[i]; + csql = from_sql_interval_single[j]; + compat_matrix[ESSQL_TYPE_IDX(sql)][ESSQL_C_TYPE_IDX(csql)] = TRUE; + } + } + /* set default conversions for all ITERVALs */ + lim_i = sizeof(sql_interval)/sizeof(*sql_interval); + lim_j = sizeof(csql_interval)/sizeof(*csql_interval); + assert(lim_i == lim_j); + for (i = 0; i < lim_i; i ++) { + sql = sql_interval[i]; + csql = csql_interval[i]; + compat_matrix[ESSQL_TYPE_IDX(sql)][ESSQL_C_TYPE_IDX(csql)] = TRUE; + } /* set conversions between date-time types */ for (i = 0; i < sizeof(to_csql_datetime)/sizeof(*to_csql_datetime); i ++) { @@ -898,6 +950,10 @@ SQLRETURN sql2c_longlong(esodbc_rec_st *arec, esodbc_rec_st *irec, esodbc_desc_st *ard, *ird; SQLSMALLINT ctype; SQLRETURN ret; + SQL_INTERVAL_STRUCT ivl; + SQLINTERVAL ivl_type; + SQLUINTEGER *ivl_comp; + long long ivl_val; stmt = arec->desc->hdr.stmt; ird = stmt->ird; @@ -999,6 +1055,52 @@ SQLRETURN sql2c_longlong(esodbc_rec_st *arec, esodbc_rec_st *irec, case SQL_C_BINARY: return llong_to_binary(arec, irec, ll, data_ptr, octet_len_ptr); + /*INDENT-OFF*/ + do { + case SQL_C_INTERVAL_YEAR: + ivl_comp = &ivl.intval.year_month.year; + ivl_type = SQL_IS_YEAR; + break; + case SQL_C_INTERVAL_MONTH: + ivl_comp = &ivl.intval.year_month.month; + ivl_type = SQL_IS_MONTH; + break; + case SQL_C_INTERVAL_DAY: + ivl_comp = &ivl.intval.day_second.day; + ivl_type = SQL_IS_DAY; + break; + case SQL_C_INTERVAL_HOUR: + ivl_comp = &ivl.intval.day_second.hour; + ivl_type = SQL_IS_HOUR; + break; + case SQL_C_INTERVAL_MINUTE: + ivl_comp = &ivl.intval.day_second.minute; + ivl_type = SQL_IS_MINUTE; + break; + case SQL_C_INTERVAL_SECOND: + ivl_comp = &ivl.intval.day_second.second; + ivl_type = SQL_IS_SECOND; + break; + } while (0); + REJECT_IF_NULL_DEST_BUFF(stmt, data_ptr); + memset(&ivl, 0, sizeof(ivl)); + if (ll < 0) { + ivl_val = -ll; + ivl.interval_sign = SQL_TRUE; + } else { + ivl_val = ll; + } + REJECT_IF_OOR(stmt, ivl_val, 0, ULONG_MAX, SQLUINTEGER, + unsigned long); + ivl.interval_type = ivl_type; + *ivl_comp = (SQLUINTEGER)ivl_val; + *(SQL_INTERVAL_STRUCT *)data_ptr = ivl; + write_out_octets(octet_len_ptr, sizeof(ivl), irec); + DBGH(stmt, "converted long long %lld to interval type %d.", + ll, ivl_type); + break; + /*INDENT-ON*/ + default: BUGH(stmt, "unexpected unhanlded data type: %d.", get_rec_c_type(arec, irec)); @@ -1653,6 +1755,895 @@ static SQLRETURN wstr_to_time(esodbc_rec_st *arec, esodbc_rec_st *irec, return SQL_SUCCESS; } +static inline SQLRETURN adjust_to_precision(esodbc_rec_st *arec, + SQLUBIGINT *val, SQLSMALLINT prec_have) +{ + esodbc_stmt_st *stmt = arec->desc->hdr.stmt; + + if (prec_have < arec->precision) { + DBGH(stmt, "augmenting value %llu with %hd digits.", *val, + arec->precision - prec_have); + /* no overflow check: the target precision should have been checked */ + *val *= pow10(arec->precision - prec_have); + return SQL_SUCCESS; + } else { + DBGH(stmt, "truncating value %llu with %hd digits.", *val, + prec_have - arec->precision); + *val /= pow10(prec_have - arec->precision); + RET_HDIAGS(stmt, SQL_STATE_01S07); + } +} + +static SQLRETURN parse_iso8601_number(esodbc_rec_st *arec, wstr_st *wstr, + SQLUINTEGER *uint, char *sign, + SQLUINTEGER *fraction, BOOL *has_fraction) +{ + esodbc_stmt_st *stmt = arec->desc->hdr.stmt; + char inc; + wstr_st nr; + int digits, fdigits; + SQLBIGINT bint; + SQLUBIGINT ubint; + SQLRETURN ret = SQL_SUCCESS;; + + nr = *wstr; + digits = str2bigint(&nr, /*w*/TRUE, &bint, /*strict*/FALSE); + if (digits < 0) { + return SQL_ERROR; + } else if (wstr->cnt <= digits) { + return SQL_ERROR; /* a number can't end the ISO value */ + } else if (wstr->str[digits] == L'.') { /* does it have a fraction? */ + if (digits == 0) { + bint = 0; /* value is `.` */ + } + if (wstr->cnt <= digits + 1) { + return SQL_ERROR; /* ISO value ends with `.` */ + } + nr.str = wstr->str + digits + /*`.`*/1; + nr.cnt = wstr->cnt - (digits + 1); + fdigits = str2ubigint(&nr, /*w*/TRUE, &ubint, FALSE); + if (fdigits < 0) { + return SQL_ERROR; + } else { + digits += fdigits + /*`.`*/1; + } + if (ULONG_MAX < ubint) { + ERRH(stmt, "fraction value too large (%llu).", ubint); + return SQL_ERROR; + } else { + ret = adjust_to_precision(arec, &ubint, fdigits); + assert(ubint < ULONG_MAX); /* due to previous sanity checks */ + *fraction = (SQLUINTEGER)ubint; + } + *has_fraction = TRUE; + } else { + *has_fraction = FALSE; + } + if (bint < 0) { + inc = -1; + bint = -bint; + } else { + inc = 1; + } + if (ULONG_MAX < bint) { + ERRH(stmt, "field value too large (%lld).", bint); + return SQL_ERROR; + } else { + *uint = (SQLUINTEGER)bint; + } + if (*sign && ((inc ^ *sign) & 0x80)) { + ERRH(stmt, "units with mixed signs not supported."); + return SQL_ERROR; + } else { + *sign += inc; + } + + wstr->str += digits; + wstr->cnt -= digits; + return ret; +} + +/* parse an ISO8601 period value + * Elasticsearch'es implementation deviates slightly, hiding the day field: + * `INTERVAL '1 1' DAY TO HOUR` -> `PT25H` instead of `P1DT1H`. */ +static SQLRETURN parse_interval_iso8601(esodbc_rec_st *arec, + SQLSMALLINT ctype, wstr_st *wstr, SQL_INTERVAL_STRUCT *ivl) +{ + esodbc_stmt_st *stmt = arec->desc->hdr.stmt; + char sign; + SQLWCHAR *crr, *end; + wstr_st nr; + SQLUINTEGER uint, fraction; + BOOL has_fraction; + enum {ST_UNINITED, ST_PERIOD, ST_TIME, ST_NUMBER} state, saved; + uint16_t fields_bm; /* read fields bit mask */ + static uint16_t type2bm[] = { + 1 << SQL_IS_YEAR, + 1 << SQL_IS_MONTH, + 1 << SQL_IS_DAY, + 1 << SQL_IS_HOUR, + 1 << SQL_IS_MINUTE, + 1 << SQL_IS_SECOND, + (1 << SQL_IS_YEAR) | (1 << SQL_IS_MONTH), + (1 << SQL_IS_DAY) | (1 << SQL_IS_HOUR), + (1 << SQL_IS_DAY) | (1 << SQL_IS_HOUR) | (1 << SQL_IS_MINUTE), + (1 << SQL_IS_DAY) | (1 << SQL_IS_HOUR) | (1 << SQL_IS_MINUTE) | + (1 << SQL_IS_SECOND), + (1 << SQL_IS_HOUR) | (1 << SQL_IS_MINUTE), + (1 << SQL_IS_HOUR) | (1 << SQL_IS_MINUTE) | (1 << SQL_IS_SECOND), + (1 << SQL_IS_MINUTE) | (1 << SQL_IS_SECOND), + }; + SQLRETURN ret; + + /* Sets a bit in a bitmask corresponding to one interval field, given + * `_ivl`, or errs if already set. + * Uses local vars: fields_bm. + * Jumps to err_format on error. */ +# define SET_BITMASK_OR_ERR(_ivl) \ + do { \ + uint8_t _bit = 1 << (_ivl); \ + if (_bit & fields_bm) { \ + ERRH(stmt, "field %d already set.", (_ivl)); \ + goto err_format; \ + } \ + fields_bm |= _bit; \ + } while (0) + /* Assigns a value to an interval field, `_ivl_field` of type `_ivl_type`, + * if current state is ST_NUMBER and the previous one matches the given + * `_prev_state`. + * Uses local vars: ivl, fields_bm, state, saved. + * Jumps to err_format on error. */ +# define ASSIGN_FIELD(_prev_state, _ivl_type, _ivl_field_) \ + do { \ + if (state != ST_NUMBER || saved != _prev_state) { \ + goto err_format; \ + } \ + if (_ivl_type != SQL_IS_SECOND && has_fraction) { \ + goto err_format; \ + } \ + SET_BITMASK_OR_ERR(_ivl_type); \ + ivl->intval._ivl_field_ = uint; \ + DBGH(stmt, "field %d assigned value %lu.", _ivl_type, uint); \ + state = saved; \ + } while (0) + + /* the interval type will be used as bitmask indexes */ + assert(0 < SQL_IS_YEAR && SQL_IS_MINUTE_TO_SECOND < 16); + DBGH(stmt, "ISO 8601 to parse: [%zu] `" LWPDL "`, C target: %hd.", + wstr->cnt, LWSTR(wstr), ctype); + + memset(ivl, 0, sizeof(*ivl)); + sign = 0; + fields_bm = 0; + fraction = 0; + state = saved = ST_UNINITED; + for (crr = wstr->str, end = wstr->str + wstr->cnt; crr < end; ) { + switch (*crr | 0x20) { + case L'p': + if (state != ST_UNINITED) { + goto err_parse; + } + state = ST_PERIOD; + break; + case L't': + if (state != ST_PERIOD) { + goto err_parse; + } + state = ST_TIME; + break; + case L'+': + case L'-': + case L'.': + /* L'0' .. L'9' */ + case L'0': + case L'1': + case L'2': + case L'3': + case L'4': + case L'5': + case L'6': + case L'7': + case L'8': + case L'9': + if (state != ST_PERIOD && state != ST_TIME) { + goto err_parse; + } + nr.str = crr; + nr.cnt = end - crr; + ret = parse_iso8601_number(arec, &nr, &uint, &sign, + &fraction, &has_fraction); + if (! SQL_SUCCEEDED(ret)) { + goto err_format; + } else { + crr = nr.str; + } + saved = state; + state = ST_NUMBER; + continue; + + case L'y': + ASSIGN_FIELD(ST_PERIOD, SQL_IS_YEAR, year_month.year); + break; + case L'm': + if (state != ST_NUMBER || + (saved != ST_PERIOD && saved != ST_TIME)) { + goto err_format; + } + if (has_fraction) { + goto err_format; + } + if (saved == ST_PERIOD) { + SET_BITMASK_OR_ERR(SQL_IS_MONTH); + ivl->intval.year_month.month = uint; + DBGH(stmt, "field %d assigned value %lu.", SQL_IS_MONTH, + uint); + } else { + SET_BITMASK_OR_ERR(SQL_IS_MINUTE); + ivl->intval.day_second.minute = uint; + DBGH(stmt, "field %d assigned value %lu.", SQL_IS_MONTH, + uint); + } + state = saved; + break; + case L'd': + ASSIGN_FIELD(ST_PERIOD, SQL_IS_DAY, day_second.day); + break; + case L'h': + ASSIGN_FIELD(ST_TIME, SQL_IS_HOUR, day_second.hour); + break; + case L's': + ASSIGN_FIELD(ST_TIME, SQL_IS_SECOND, day_second.second); + if (has_fraction) { + ivl->intval.day_second.fraction = fraction; + DBGH(stmt, "field fraction assigned value %lu.", fraction); + } + break; + default: + goto err_parse; + } + crr ++; + } + if (state != ST_PERIOD && state != ST_TIME) { + goto err_format; + } + + ivl->interval_sign = (sign < 0) ? SQL_TRUE : SQL_FALSE; + assert(SQL_CODE_YEAR == SQL_IS_YEAR); + ivl->interval_type = ctype - (SQL_INTERVAL_YEAR - SQL_CODE_YEAR); + + assert(0 < /*starts at 1*/ ivl->interval_type && + ivl->interval_type < 8 * sizeof(type2bm)/sizeof(type2bm[0])); + /* reinstate the day field merged by ES in the hour field when: + * - the day field hasn't been set; + * - it is an interval with day component; + * - the hour field overflows a day/24h */ + if (((1 << SQL_IS_DAY) & fields_bm) == 0 && + (type2bm[ivl->interval_type - 1] & (1 << SQL_IS_DAY)) && + 24 <= ivl->intval.day_second.hour) { + ivl->intval.day_second.day = ivl->intval.day_second.hour / 24; + ivl->intval.day_second.hour = ivl->intval.day_second.hour % 24; + fields_bm |= 1 << SQL_IS_DAY; + } + + /* Check that the ISO value has no fields set other than those allowed + * for the advertised type. Since the year_month and day_second form a + * union, this can't be done by checks against field values. */ + if ((~type2bm[ivl->interval_type - 1]) & fields_bm) { + ERRH(stmt, "illegal fields (0x%hx) for interval type %hd (0x%hx).", + fields_bm, ctype, type2bm[ivl->interval_type - 1]); + goto err_format; + } + + return ret; +err_parse: + ERRH(stmt, "unexpected current char `%c` in state %d.", *crr, state); +err_format: + ERRH(stmt, "invalid ISO8601 format [%zu] `" LWPDL "`.", wstr->cnt, + LWSTR(wstr)); + RET_HDIAG(stmt, SQL_STATE_22018, "Invalid server answer", 0); + +# undef ASSIGN_FIELD +# undef SET_BITMASK_OR_ERR +} + +/* Parse one field of the value. + * If 'limit' is non-zero, the field must remain below it; otehrwise it's a + * leading field and the precision must apply. + * 'wstr' is adjusted to have the field removed. + * The field value is stored in 'uiptr'. + * */ +static SQLRETURN parse_interval_field(esodbc_rec_st *arec, SQLUINTEGER limit, + wstr_st *wstr, SQLUINTEGER *uiptr) +{ + esodbc_stmt_st *stmt = arec->desc->hdr.stmt; + SQLUBIGINT ubint; + int digits; + + errno = 0; + digits = str2ubigint(wstr, /*wide*/TRUE, &ubint, /*strict*/FALSE); + if (digits <= 0) { + ERRH(stmt, "can not parse `" LWPDL "` as uinteger.", LWSTR(wstr)); + RET_HDIAGS(stmt, errno == ERANGE ? SQL_STATE_22015 : SQL_STATE_22018); + } else { + wstr->str += digits; + wstr->cnt -= digits; + } + if (ULONG_MAX < ubint) { + ERRH(stmt, "available value (%llu) larger than max (%lu).", + ubint, ULONG_MAX); + RET_HDIAGS(stmt, SQL_STATE_22015); + } + if (limit <= 0) { + if (arec->datetime_interval_precision < digits) { + ERRH(stmt, "interval's set dt_interval precision (%ld) lower than " + "data's (%d).", arec->datetime_interval_precision, digits); + RET_HDIAGS(stmt, SQL_STATE_22018); + } + } else if (limit < ubint) { + ERRH(stmt, "interval field value (%lu) higher than max (%lu).", ubint, + limit); + RET_HDIAGS(stmt, SQL_STATE_22015); + } + *uiptr = (SQLUINTEGER)ubint; + DBGH(stmt, "parsed field value: %lu.", *uiptr); + return SQL_SUCCESS; +} + +static SQLRETURN parse_interval_second(esodbc_rec_st *arec, SQLUINTEGER limit, + wstr_st *wstr, SQL_INTERVAL_STRUCT *ivl) +{ + esodbc_stmt_st *stmt = arec->desc->hdr.stmt; + size_t digits; + SQLRETURN ret; + unsigned long long ull; + + ret = parse_interval_field(arec, limit, wstr, + &ivl->intval.day_second.second); + if (! SQL_SUCCEEDED(ret)) { + return ret; + } + if (wstr->cnt <= 0) { /* fiels ends without fractions: `1` */ + DBGH(stmt, "component 'second' has value: %lu.", + ivl->intval.day_second.second); + return SQL_SUCCESS; + } + + if (wstr->str[0] != L'.') { + ERRH(stmt, "unexpected character %c in value.", wstr->str[0]); + RET_HDIAGS(stmt, SQL_STATE_22018); + } else { + wstr->str ++; + wstr->cnt --; + } + + if (wstr->cnt <= 0) { /* fiels ends after dot: `1.` */ + return SQL_SUCCESS; + } + digits = wstr->cnt; + ret = parse_interval_field(arec, ULONG_MAX, wstr, + &ivl->intval.day_second.fraction); + if (! SQL_SUCCEEDED(ret)) { + return ret; + } else { + digits -= wstr->cnt; + assert(digits < SHRT_MAX); + } + ull = ivl->intval.day_second.fraction; + ret = adjust_to_precision(arec, &ull, (SQLSMALLINT)digits); + assert(ull < ULONG_MAX); /* interval seconds precision limited to 9 */ + ivl->intval.day_second.fraction = (SQLUINTEGER)ull; + + if (wstr->cnt) { + ERRH(stmt, "invalid trailing chars: [%zu] `" LWPDL "`", + wstr->cnt, LWSTR(wstr)); + RET_HDIAGS(stmt, SQL_STATE_22018); + } else { + DBGH(stmt, "single component 'second' has value: %lu.%lu(%hd).", + ivl->intval.day_second.second, + ivl->intval.day_second.fraction, arec->precision); + } + return ret; +} + +/* TODO: use rec's .datetime_interval_precision and .precision through a + * "general" get_col_xxx() function (: decdigits or similar) */ +/* parses the `` in an interval literal */ +static SQLRETURN parse_interval_literal_value(esodbc_rec_st *arec, + wstr_st *wstr, SQL_INTERVAL_STRUCT *ivl) +{ + esodbc_stmt_st *stmt = arec->desc->hdr.stmt; + SQLUINTEGER *uiptr; + SQLRETURN ret; + + /* Parses one (numeric) field in the interval value and checks that the + * entire string has been parsed, if, `_at_the_end` is set (for single + * field intervals). + * Returns on error. */ +# define PARSE_IVL_FLD_OR_RET(arec, limit, wstr, uiptr, _at_the_end) \ + do { \ + ret = parse_interval_field(arec, limit, wstr, uiptr); \ + if (! SQL_SUCCEEDED(ret)) { \ + return ret; \ + } \ + if (_at_the_end && wstr->cnt) { \ + ERRH(stmt, "invalid trailing chars: [%zu] `" LWPDL "`", \ + wstr->cnt, LWSTR(wstr)); \ + RET_HDIAGS(stmt, SQL_STATE_22018); \ + } \ + } while (0) + + DBGH(stmt, "literal to parse: type: %d, value [%zu] `" LWPDL "`.", + ivl->interval_type, wstr->cnt, LWSTR(wstr)); + + /*INDENT-OFF*/ + switch (ivl->interval_type) { + do { + case SQL_IS_YEAR: uiptr = &ivl->intval.year_month.year; break; + case SQL_IS_MONTH: uiptr = &ivl->intval.year_month.month; break; + case SQL_IS_DAY: uiptr = &ivl->intval.day_second.day; break; + case SQL_IS_HOUR: uiptr = &ivl->intval.day_second.hour; break; + case SQL_IS_MINUTE: uiptr = &ivl->intval.day_second.minute; break; + } while (0); + PARSE_IVL_FLD_OR_RET(arec, /*limit*/0, wstr, uiptr, /*end?*/TRUE); + DBGH(stmt, "single component of type %d has value: %lu.", + ivl->interval_type, *uiptr); + break; + + case SQL_IS_SECOND: + ret = parse_interval_second(arec, /*limit*/0, wstr, ivl); + if (! SQL_SUCCEEDED(ret)) { + return ret; + } + break; + + case SQL_IS_YEAR_TO_MONTH: + PARSE_IVL_FLD_OR_RET(arec, /*limit*/0, wstr, + &ivl->intval.year_month.year, /*end?*/FALSE); + wltrim_ws(wstr); + if (wstr->str[0] != L'-') { + ERRH(stmt, "invalid char as separator: `%c`.", wstr->str[0]); + RET_HDIAGS(stmt, SQL_STATE_22018); + } else { + wstr->str ++; + wstr->cnt --; + } + wltrim_ws(wstr); + PARSE_IVL_FLD_OR_RET(arec, /*limit: months*/11, wstr, + &ivl->intval.year_month.month, /*end?*/TRUE); + break; + + case SQL_IS_DAY_TO_HOUR: + case SQL_IS_DAY_TO_MINUTE: + case SQL_IS_DAY_TO_SECOND: + PARSE_IVL_FLD_OR_RET(arec, /*limit*/0, wstr, + &ivl->intval.day_second.day, /*end?*/FALSE); + wltrim_ws(wstr); + PARSE_IVL_FLD_OR_RET(arec, /*hour limit*/23, wstr, + &ivl->intval.day_second.hour, + /*end?*/ivl->interval_type == SQL_IS_DAY_TO_HOUR); + if (ivl->interval_type == SQL_IS_DAY_TO_HOUR) { + break; + } + if (wstr->str[0] != L':') { + ERRH(stmt, "invalid char as separator: `%c`.", wstr->str[0]); + RET_HDIAGS(stmt, SQL_STATE_22018); + } else { + wstr->str ++; + wstr->cnt --; + } + PARSE_IVL_FLD_OR_RET(arec, /*minute limit*/59, wstr, + &ivl->intval.day_second.minute, + /*end?*/ivl->interval_type == SQL_IS_DAY_TO_MINUTE); + if (ivl->interval_type == SQL_IS_DAY_TO_MINUTE) { + break; + } + if (wstr->str[0] != L':') { + ERRH(stmt, "invalid char as separator: `%c`.", wstr->str[0]); + RET_HDIAGS(stmt, SQL_STATE_22018); + } else { + wstr->str ++; + wstr->cnt --; + } + ret = parse_interval_second(arec, /*second limit*/59, wstr, ivl); + if (! SQL_SUCCEEDED(ret)) { + return ret; + } + break; + + case SQL_IS_HOUR_TO_MINUTE: + case SQL_IS_HOUR_TO_SECOND: + PARSE_IVL_FLD_OR_RET(arec, /*limit*/0, wstr, + &ivl->intval.day_second.hour, /*end?*/FALSE); + if (wstr->str[0] != L':') { + ERRH(stmt, "invalid char as separator: `%c`.", wstr->str[0]); + RET_HDIAGS(stmt, SQL_STATE_22018); + } else { + wstr->str ++; + wstr->cnt --; + } + /*no break*/ + case SQL_IS_MINUTE_TO_SECOND: + PARSE_IVL_FLD_OR_RET(arec, + /*minute limit*/ + ivl->interval_type == SQL_IS_MINUTE_TO_SECOND ? 0 : 59, + wstr, &ivl->intval.day_second.minute, + /*end?*/ivl->interval_type == SQL_IS_HOUR_TO_MINUTE); + if (ivl->interval_type == SQL_IS_HOUR_TO_MINUTE) { + break; + } + if (wstr->str[0] != L':') { + ERRH(stmt, "invalid char as separator: `%c`.", wstr->str[0]); + RET_HDIAGS(stmt, SQL_STATE_22018); + } else { + wstr->str ++; + wstr->cnt --; + } + ret = parse_interval_second(arec, /*second limit*/59, wstr, ivl); + if (! SQL_SUCCEEDED(ret)) { + return ret; + } + break; + } + /*INDENT-ON*/ + + return SQL_SUCCESS; +# undef PARSE_IVL_FLD_OR_RET +} + +/* parse the qualifier in `± '' `, rtrim-ing it from wstr */ +static SQLSMALLINT parse_interval_type(wstr_st *wstr) +{ + /* compares the end of the local `wstr` against given `_end` string and + * trims the former, on match. + * Uses local var: wstr. + * Returns on no match (the qualifier is incorrect). */ +# define TRIM_IF_ENDS_WITH_OR_RET(_end) \ + do { \ + wstr_st _wend = MK_WSTR(_end); \ + if (wstr->cnt <= _wend.cnt || \ + wmemncasecmp(&wstr->str[wstr->cnt - _wend.cnt], \ + _wend.str, _wend.cnt)) { \ + return 0; \ + } \ + wstr->cnt -= _wend.cnt; \ + wrtrim_ws(wstr); \ + } while (0) +# define IS_DELIM(wc) \ + (wc == L'\'' || wc == L'.' || (L'0' <= wc && wc <= L'9' )) + + + if (wstr->cnt <= /* "day", smallest interval qualifier */3) { + return 0; + } + + /* split by last letter */ + switch (wstr->str[wstr->cnt - 1] | 0x20) { + case L'y': /* day */ + TRIM_IF_ENDS_WITH_OR_RET("day"); + return SQL_IS_DAY; + + case L'r': /* year, hour, day to hour */ + switch (wstr->str[wstr->cnt - 2] | 0x20) { + case L'a': /* ...in year */ + TRIM_IF_ENDS_WITH_OR_RET("year"); + return SQL_IS_YEAR; + case L'u': /* ...in hour */ + TRIM_IF_ENDS_WITH_OR_RET("hour"); + if (IS_DELIM(wstr->str[wstr->cnt - 1])) { + return SQL_IS_HOUR; + } + TRIM_IF_ENDS_WITH_OR_RET("to"); + TRIM_IF_ENDS_WITH_OR_RET("day"); + return SQL_IS_DAY_TO_HOUR; + } + break; + case L'e': /* minute, day/hour to minute */ + TRIM_IF_ENDS_WITH_OR_RET("minute"); + if (IS_DELIM(wstr->str[wstr->cnt - 1])) { + return SQL_IS_MINUTE; + } + TRIM_IF_ENDS_WITH_OR_RET("to"); + switch (wstr->str[wstr->cnt - 1] | 0x20) { + case L'y': /* ...in "day" */ + TRIM_IF_ENDS_WITH_OR_RET("day"); + return SQL_IS_DAY_TO_MINUTE; + case L'r': /* ...in "hour" */ + TRIM_IF_ENDS_WITH_OR_RET("hour"); + return SQL_IS_HOUR_TO_MINUTE; + } + case L'h': /* month, year to month */ + TRIM_IF_ENDS_WITH_OR_RET("month"); + if (IS_DELIM(wstr->str[wstr->cnt - 1])) { + return SQL_IS_MONTH; + } + TRIM_IF_ENDS_WITH_OR_RET("to"); + TRIM_IF_ENDS_WITH_OR_RET("year"); + return SQL_IS_YEAR_TO_MONTH; + case L'd': /* second, day/hour/minute to second */ + TRIM_IF_ENDS_WITH_OR_RET("second"); + if (IS_DELIM(wstr->str[wstr->cnt - 1])) { + return SQL_IS_SECOND; + } + TRIM_IF_ENDS_WITH_OR_RET("to"); + switch (wstr->str[wstr->cnt - 1] | 0x20) { + case L'y': /* ...in "day" */ + TRIM_IF_ENDS_WITH_OR_RET("day"); + return SQL_IS_DAY_TO_SECOND; + case L'r': /* ...in "hour" */ + TRIM_IF_ENDS_WITH_OR_RET("hour"); + return SQL_IS_HOUR_TO_SECOND; + case L'e': /* ...in "minute" */ + TRIM_IF_ENDS_WITH_OR_RET("minute"); + return SQL_IS_MINUTE_TO_SECOND; + } + } + return 0; +# undef TRIM_IF_ENDS_WITH_OR_RET +} + +/* parse `INTERVAL ± '' `, each space being + * optional and extendable; the entire expression can be enclosed in {} */ +static SQLRETURN parse_interval_literal(esodbc_rec_st *arec, wstr_st *wstr, + SQL_INTERVAL_STRUCT *ivl) +{ + esodbc_stmt_st *stmt = arec->desc->hdr.stmt; + static const wstr_st INTERVAL = WSTR_INIT("INTERVAL"); + + memset(ivl, 0, sizeof(*ivl)); + + if (wstr->cnt < INTERVAL.cnt + /* "INTERVAL1DAY" */4) { + ERRH(stmt, "too short for interval literal."); + RET_HDIAGS(stmt, SQL_STATE_22018); + } + if (wstr->str[0] == L'{' && wstr->str[wstr->cnt - 1] == L'}') { + /* strip enclosing {} */ + wstr->str += 1; + wstr->cnt -= 2; + + wtrim_ws(wstr); + if (wstr->cnt <= INTERVAL.cnt) { + ERRH(stmt, "not an interval literal."); + RET_HDIAGS(stmt, SQL_STATE_22018); + } + } + if (wmemncasecmp(wstr->str, INTERVAL.str, INTERVAL.cnt)) { + ERRH(stmt, "not an interval literal."); + RET_HDIAGS(stmt, SQL_STATE_22018); + } else { + wstr->str += INTERVAL.cnt; + wstr->cnt -= INTERVAL.cnt; + } + + wltrim_ws(wstr); + + ivl->interval_type = parse_interval_type(wstr); + if (! ivl->interval_type) { + ERRH(stmt, "failed to extract interval type in [%zu] `" LWPDL "`.", + wstr->cnt, LWSTR(wstr)); + RET_HDIAGS(stmt, SQL_STATE_22018); + } else { + DBGH(stmt, "parsed interval qualifier: %d", ivl->interval_type); + } + /* wstr is now adjusted to `± ''` */ + + if (wstr->str[0] == L'-' || wstr->str[0] == L'+') { + ivl->interval_sign = (wstr->str[0] == L'-') ? SQL_TRUE : SQL_FALSE; + /* "trim" +/- */ + wstr->str ++; + wstr->cnt --; + wltrim_ws(wstr); + } else { + ivl->interval_sign = SQL_FALSE; + } + DBGH(stmt, "parsed interval sign: %d", ivl->interval_sign); + + /* strip enclosing single quotes */ + if (wstr->str[0] == '\'' && wstr->str[wstr->cnt - 1] == '\'') { + wstr->str += 1; + wstr->cnt -= 2; + } + + return parse_interval_literal_value(arec, wstr, ivl); +} + +static SQLRETURN sql2c_interval(esodbc_rec_st *arec, + SQLSMALLINT sqltype, SQLSMALLINT ctype, void *data_ptr, wstr_st *wstr) +{ + esodbc_stmt_st *stmt = arec->desc->hdr.stmt; + SQLRETURN ret; + SQL_INTERVAL_STRUCT ivl; + SQLSMALLINT ivl_type2c_type[] = { + SQL_C_INTERVAL_YEAR, /* = 1, ++ */ + SQL_C_INTERVAL_MONTH, + SQL_C_INTERVAL_DAY, + SQL_C_INTERVAL_HOUR, + SQL_C_INTERVAL_MINUTE, + SQL_C_INTERVAL_SECOND, + SQL_C_INTERVAL_YEAR_TO_MONTH, + SQL_C_INTERVAL_DAY_TO_HOUR, + SQL_C_INTERVAL_DAY_TO_MINUTE, + SQL_C_INTERVAL_DAY_TO_SECOND, + SQL_C_INTERVAL_HOUR_TO_MINUTE, + SQL_C_INTERVAL_HOUR_TO_SECOND, + SQL_C_INTERVAL_MINUTE_TO_SECOND + }; + + if (wstr->cnt <= 0) { + ERRH(stmt, "too short for INTERVAL."); + goto err_22018; + } + + /* split processing by the source type */ + if (sqltype == SQL_VARCHAR) { + ret = parse_interval_literal(arec, wstr, &ivl); + assert(0 <= ivl.interval_type && ivl.interval_type <= + sizeof(ivl_type2c_type)/sizeof(ivl_type2c_type[0])); + if (ivl_type2c_type[ivl.interval_type - 1] != ctype) { + /* intra-interval conversion not supported */ + ERRH(stmt, "parsed interval type (%hd) differs from C type (%hd).", + ivl_type2c_type[ivl.interval_type - 1], ctype); + RET_HDIAGS(stmt, SQL_STATE_07006); + } + } else { + /* single-component intervals would be intra-convertible: TODO? */ + assert(sqltype == ctype); /* C == SQL types, for intervals */ + ret = parse_interval_iso8601(arec, ctype, wstr, &ivl); + } + + if (SQL_SUCCEEDED(ret)) { + DBGH(stmt, "interval succesfully parsed."); + assert(data_ptr); + *(SQL_INTERVAL_STRUCT *)data_ptr = ivl; + } else { + ERRH(stmt, "failed to convert [%zu] `" LWPDL "` to an interval.", + wstr->cnt, LWSTR(wstr)); + } + return ret; + +err_22018: + ERRH(stmt, "not a valid interval: [%zu] `" LWPDL "`.", wstr->cnt, + LWSTR(wstr)); + RET_HDIAGS(stmt, SQL_STATE_22018); +} + +/* Convert an interval struct to a SQL literal (value). + * There's no reference for the sign in: + * https://docs.microsoft.com/en-us/sql/odbc/reference/appendixes/interval-data-type-length + */ +static size_t print_interval_sql(esodbc_rec_st *arec, SQL_INTERVAL_STRUCT *ivl, + SQLWCHAR *dest) +{ + esodbc_stmt_st *stmt = arec->desc->hdr.stmt; + size_t pos; + SQLUINTEGER uint; + int res; + wchar_t fmt[] = L"%.0f";; + float flt; + + pos = 0; + if (ivl->interval_sign) { + dest[pos ++] = L'-'; + } + + /*INDENT-OFF*/ + switch (ivl->interval_type) { + do { + case SQL_IS_YEAR: + uint = ivl->intval.year_month.year; + break; + case SQL_IS_MONTH: + uint = ivl->intval.year_month.month; + break; + case SQL_IS_DAY: + uint = ivl->intval.day_second.day; + break; + case SQL_IS_HOUR: + uint = ivl->intval.day_second.hour; + break; + case SQL_IS_MINUTE: + uint = ivl->intval.day_second.minute; + break; + } while (0); + pos += ui64tot(uint, dest + pos, /*wide*/TRUE); + break; + + case SQL_IS_YEAR_TO_MONTH: + pos += ui64tot(ivl->intval.year_month.year, dest + pos, TRUE); + dest[pos ++] = L'-'; + pos += ui64tot(ivl->intval.year_month.month, dest + pos, TRUE); + break; + case SQL_IS_DAY_TO_HOUR: + pos += ui64tot(ivl->intval.day_second.day, dest + pos, TRUE); + dest[pos ++] = L' '; + pos += ui64tot(ivl->intval.day_second.hour, dest + pos, TRUE); + break; + case SQL_IS_DAY_TO_MINUTE: + pos += ui64tot(ivl->intval.day_second.day, dest + pos, TRUE); + dest[pos ++] = L' '; + pos += ui64tot(ivl->intval.day_second.hour, dest + pos, TRUE); + dest[pos ++] = L':'; + pos += ui64tot(ivl->intval.day_second.minute, dest + pos, TRUE); + break; + + case SQL_IS_DAY_TO_SECOND: + pos += ui64tot(ivl->intval.day_second.day, dest + pos, TRUE); + dest[pos ++] = L' '; + case SQL_IS_HOUR_TO_SECOND: + pos += ui64tot(ivl->intval.day_second.hour, dest + pos, TRUE); + dest[pos ++] = L':'; + case SQL_IS_MINUTE_TO_SECOND: + pos += ui64tot(ivl->intval.day_second.minute, dest + pos, TRUE); + dest[pos ++] = L':'; + case SQL_IS_SECOND: + if (ivl->intval.day_second.fraction) { + assert(ESODBC_MAX_SEC_PRECISION < 10); + assert(0 <= arec->precision && + arec->precision <= ESODBC_MAX_SEC_PRECISION); + fmt[2] = L'0' + arec->precision; + flt = (float)ivl->intval.day_second.fraction; + flt /= pow10(arec->precision); + flt += (float)ivl->intval.day_second.second; + res = swprintf(dest + pos, 2 * sizeof("4294967295") + 1, fmt, + flt); + if (res < 0) { + ERRNH(stmt, "failed to print the 'second' component for " + "second: %lu, fraction: %lu, precision: %hd.", + ivl->intval.day_second.second, + ivl->intval.day_second.fraction, arec->precision); + return 0; + } + pos += res; + } else { + pos += ui64tot(ivl->intval.day_second.second, dest + pos, + TRUE); + } + break; + + case SQL_IS_HOUR_TO_MINUTE: + pos += ui64tot(ivl->intval.day_second.hour, dest + pos, TRUE); + dest[pos ++] = L':'; + pos += ui64tot(ivl->intval.day_second.minute, dest + pos, TRUE); + break; + default: + BUGH(stmt, "unexpected interval type %d.", ivl->interval_type); + return 0; + } + /*INDENT-ON*/ + + return pos; +} + +/* translate the string representation of an interval value from the ISO8601 + * to SQL */ +static SQLRETURN interval_iso8601_to_sql(esodbc_rec_st *arec, + esodbc_rec_st *irec, const wchar_t *wstr, size_t *chars_0, + wchar_t *lit) +{ + esodbc_stmt_st *stmt = arec->desc->hdr.stmt; + SQLRETURN ret; + wstr_st ivl_wstr; + SQL_INTERVAL_STRUCT ivl; + size_t cnt; + + ivl_wstr.str = (SQLWCHAR *)wstr; + ivl_wstr.cnt = *chars_0 - 1; + ret = parse_interval_iso8601(arec, irec->es_type->data_type, &ivl_wstr, + &ivl); + if (! SQL_SUCCEEDED(ret)) { + return ret; + } + cnt = print_interval_sql(arec, &ivl, (SQLWCHAR *)lit); + if (cnt <= 0) { + ERRH(stmt, "sql interval printing failed for ISO8601`" LWPDL "`.", + chars_0 - 1, wstr); + RET_HDIAGS(stmt, SQL_STATE_HY000); + } + DBGH(arec->desc->hdr.stmt, "convered `" LWPDL "` to `" LWPDL "`.", + chars_0 - 1, wstr, cnt, lit); + lit[cnt ++] = '\0'; + *chars_0 = cnt; + return SQL_SUCCESS; +} /* * wstr: is 0-terminated and terminator is counted in 'chars_0'. * However: "[w]hen C strings are used to hold character data, the @@ -1674,22 +2665,47 @@ SQLRETURN sql2c_string(esodbc_rec_st *arec, esodbc_rec_st *irec, wstr_st wval; double dbl; SQLWCHAR *endp; + wchar_t buff[INTERVAL_LIT_MAX_LEN + /*0*/1]; + SQLRETURN ret; + + /* The interval strings are received from ES in ISO8601, not SQL format: + * if received value is of type interval, translate the format and set the + * local `wstr` to the static buffer containing the translation. + * Uses local vars: irec, ret, wstr. + * Returns on failure. */ +# define INTERVAL_ISO8601_TO_SQL() \ + do { \ + if (irec->type != SQL_INTERVAL) { \ + break; \ + } \ + ret = interval_iso8601_to_sql(arec, irec, wstr, &chars_0, \ + buff); \ + if (! SQL_SUCCEEDED(ret)) { \ + return ret; \ + } \ + wstr = buff; \ + } while (0) stmt = arec->desc->hdr.stmt; ird = stmt->ird; ard = stmt->ard; + assert(1 <= chars_0); /* _0 is really counted */ + /* pointer where to write how many characters we will/would use */ octet_len_ptr = deferred_address(SQL_DESC_OCTET_LENGTH_PTR, pos, arec); /* pointer to app's buffer */ data_ptr = deferred_address(SQL_DESC_DATA_PTR, pos, arec); - switch ((ctarget = get_rec_c_type(arec, irec))) { + ctarget = get_rec_c_type(arec, irec); + switch (ctarget) { case SQL_C_CHAR: + INTERVAL_ISO8601_TO_SQL(); return wstr_to_cstr(arec, irec, data_ptr, octet_len_ptr, wstr, chars_0); case SQL_C_BINARY: /* treat binary as WCHAR */ // TODO: add \0??? case SQL_C_WCHAR: + INTERVAL_ISO8601_TO_SQL(); return wstr_to_wstr(arec, irec, data_ptr, octet_len_ptr, wstr, chars_0); @@ -1702,7 +2718,15 @@ SQLRETURN sql2c_string(esodbc_rec_st *arec, esodbc_rec_st *irec, case SQL_C_TYPE_TIME: return wstr_to_time(arec, irec, data_ptr, octet_len_ptr, wstr, chars_0); + } + + wval = (wstr_st) { + (SQLWCHAR *)wstr, chars_0 - 1 + }; + /* trim any white spaces */ + wtrim_ws(&wval); + switch (ctarget) { case SQL_C_TINYINT: case SQL_C_STINYINT: case SQL_C_SHORT: @@ -1710,14 +2734,9 @@ SQLRETURN sql2c_string(esodbc_rec_st *arec, esodbc_rec_st *irec, case SQL_C_LONG: case SQL_C_SLONG: case SQL_C_SBIGINT: - wval = (wstr_st) { - (SQLWCHAR *)wstr, chars_0 - 1 - }; - /* trim any white spaces */ - wtrim_ws(&wval); /* convert to integer type */ errno = 0; - if (! str2bigint(&wval, /*wide?*/TRUE, (SQLBIGINT *)&ll)) { + if (str2bigint(&wval, /*wide?*/TRUE, (SQLBIGINT *)&ll, TRUE) < 0) { ERRH(stmt, "can't convert `" LWPD "` to long long.", wstr); RET_HDIAGS(stmt, errno == ERANGE ? SQL_STATE_22003 : SQL_STATE_22018); @@ -1730,14 +2749,10 @@ SQLRETURN sql2c_string(esodbc_rec_st *arec, esodbc_rec_st *irec, case SQL_C_USHORT: case SQL_C_ULONG: case SQL_C_UBIGINT: - wval = (wstr_st) { - (SQLWCHAR *)wstr, chars_0 - 1 - }; - /* trim any white spaces */ - wtrim_ws(&wval); /* convert to integer type */ errno = 0; - if (! str2ubigint(&wval, /*wide?*/TRUE, (SQLUBIGINT *)&ull)) { + if (str2ubigint(&wval, /*wide?*/TRUE, (SQLUBIGINT *)&ull, + /*strict*/TRUE) <= 0) { ERRH(stmt, "can't convert `" LWPD "` to unsigned long long.", wstr); RET_HDIAGS(stmt, errno == ERANGE ? SQL_STATE_22003 : @@ -1769,11 +2784,6 @@ SQLRETURN sql2c_string(esodbc_rec_st *arec, esodbc_rec_st *irec, case SQL_C_DOUBLE: case SQL_C_NUMERIC: case SQL_C_BIT: - wval = (wstr_st) { - (SQLWCHAR *)wstr, chars_0 - 1 - }; - /* trim any white spaces */ - wtrim_ws(&wval); /* convert to double */ errno = 0; dbl = wcstod((wchar_t *)wval.str, (wchar_t **)&endp); @@ -1787,14 +2797,33 @@ SQLRETURN sql2c_string(esodbc_rec_st *arec, esodbc_rec_st *irec, /* delegate to existing functionality */ return sql2c_double(arec, irec, pos, dbl); + case SQL_C_INTERVAL_YEAR: + case SQL_C_INTERVAL_MONTH: + case SQL_C_INTERVAL_DAY: + case SQL_C_INTERVAL_HOUR: + case SQL_C_INTERVAL_MINUTE: + case SQL_C_INTERVAL_SECOND: + case SQL_C_INTERVAL_YEAR_TO_MONTH: + case SQL_C_INTERVAL_DAY_TO_HOUR: + case SQL_C_INTERVAL_DAY_TO_MINUTE: + case SQL_C_INTERVAL_DAY_TO_SECOND: + case SQL_C_INTERVAL_HOUR_TO_MINUTE: + case SQL_C_INTERVAL_HOUR_TO_SECOND: + case SQL_C_INTERVAL_MINUTE_TO_SECOND: + REJECT_IF_NULL_DEST_BUFF(stmt, data_ptr); + write_out_octets(octet_len_ptr, sizeof(SQLUBIGINT), irec); + DBGH(stmt, "source for interval: [%zu] `" LWPDL "` as " LWPDL ".", + wval.cnt, LWSTR(&wval), LWSTR(&irec->es_type->type_name)); + return sql2c_interval(arec, irec->es_type->data_type, ctarget, + data_ptr, &wval); default: - BUGH(stmt, "unexpected unhandled data type: %d.", - get_rec_c_type(arec, irec)); + BUGH(stmt, "unexpected unhandled data type: %d.", ctarget); return SQL_ERROR; } return SQL_SUCCESS; +# undef INTERVAL_ISO8601_TO_SQL } @@ -1803,41 +2832,13 @@ static inline BOOL conv_implemented(SQLSMALLINT sqltype, SQLSMALLINT ctype) { switch (ctype) { case SQL_C_GUID: - - case SQL_C_INTERVAL_DAY: - case SQL_C_INTERVAL_HOUR: - case SQL_C_INTERVAL_MINUTE: - case SQL_C_INTERVAL_SECOND: - case SQL_C_INTERVAL_DAY_TO_HOUR: - case SQL_C_INTERVAL_DAY_TO_MINUTE: - case SQL_C_INTERVAL_DAY_TO_SECOND: - case SQL_C_INTERVAL_HOUR_TO_MINUTE: - case SQL_C_INTERVAL_HOUR_TO_SECOND: - case SQL_C_INTERVAL_MINUTE_TO_SECOND: - case SQL_C_INTERVAL_MONTH: - case SQL_C_INTERVAL_YEAR: - case SQL_C_INTERVAL_YEAR_TO_MONTH: // case SQL_C_TYPE_TIMESTAMP_WITH_TIMEZONE: // case SQL_C_TYPE_TIME_WITH_TIMEZONE: return FALSE; } switch (sqltype) { - case SQL_C_GUID: - - case SQL_INTERVAL_DAY: - case SQL_INTERVAL_HOUR: - case SQL_INTERVAL_MINUTE: - case SQL_INTERVAL_SECOND: - case SQL_INTERVAL_DAY_TO_HOUR: - case SQL_INTERVAL_DAY_TO_MINUTE: - case SQL_INTERVAL_DAY_TO_SECOND: - case SQL_INTERVAL_HOUR_TO_MINUTE: - case SQL_INTERVAL_HOUR_TO_SECOND: - case SQL_INTERVAL_MINUTE_TO_SECOND: - case SQL_INTERVAL_MONTH: - case SQL_INTERVAL_YEAR: - case SQL_INTERVAL_YEAR_TO_MONTH: + case SQL_GUID: // case SQL_TYPE_TIMESTAMP_WITH_TIMEZONE: // case SQL_TYPE_TIME_WITH_TIMEZONE: return FALSE; @@ -1847,10 +2848,10 @@ static inline BOOL conv_implemented(SQLSMALLINT sqltype, SQLSMALLINT ctype) } -/* check if data types in returned columns are compabile with buffer types - * bound for those columns +/* Check if data types in returned columns are compabile with buffer types + * bound for those columns OR if parameter data conversion is allowed. * idx: - * if >= 0: parameter number (0,) for parameter binding; + * if > 0: parameter number for parameter binding; * if < 0: indicator for bound columns check. * */ SQLRETURN convertability_check(esodbc_stmt_st *stmt, SQLINTEGER idx, @@ -1860,6 +2861,8 @@ SQLRETURN convertability_check(esodbc_stmt_st *stmt, SQLINTEGER idx, esodbc_desc_st *axd, *ixd; esodbc_rec_st *arec, *irec; + assert(idx); + if (idx < 0) { /* * bound columns check @@ -1876,7 +2879,6 @@ SQLRETURN convertability_check(esodbc_stmt_st *stmt, SQLINTEGER idx, /* * binding paramter check */ - assert(0 < idx); start = idx - 1; stop = idx; @@ -1972,7 +2974,7 @@ SQLRETURN sql2c_convertible(esodbc_stmt_st *stmt) static BOOL xstr_to_number(esodbc_stmt_st *stmt, void *data_ptr, SQLLEN *octet_len_ptr, xstr_st *xstr, SQLSMALLINT dest_type, void *dest) { - BOOL res; + int res; if (xstr->wide) { xstr->w.str = (SQLWCHAR *)data_ptr; @@ -2001,13 +3003,16 @@ static BOOL xstr_to_number(esodbc_stmt_st *stmt, void *data_ptr, LWSTR(&xstr->w)); switch (dest_type) { case SQL_C_SBIGINT: - res = str2bigint(&xstr->w, /*wide?*/TRUE, (SQLBIGINT *)dest); + res = str2bigint(&xstr->w, /*wide?*/TRUE, (SQLBIGINT *)dest, + /*strict?*/TRUE); break; case SQL_C_UBIGINT: - res = str2bigint(&xstr->w, /*wide?*/TRUE, (SQLUBIGINT *)dest); + res = str2bigint(&xstr->w, /*wide?*/TRUE, (SQLUBIGINT *)dest, + /*strict?*/TRUE); break; case SQL_C_DOUBLE: - res = str2double(&xstr->w, /*wide?*/TRUE, (SQLDOUBLE *)dest); + res = str2double(&xstr->w, /*wide?*/TRUE, (SQLDOUBLE *)dest, + /*strict?*/TRUE); break; default: assert(0); @@ -2018,20 +3023,23 @@ static BOOL xstr_to_number(esodbc_stmt_st *stmt, void *data_ptr, LCSTR(&xstr->c)); switch (dest_type) { case SQL_C_SBIGINT: - res = str2bigint(&xstr->c, /*wide?*/FALSE, (SQLBIGINT *)dest); + res = str2bigint(&xstr->c, /*wide?*/FALSE, (SQLBIGINT *)dest, + /*strict?*/TRUE); break; case SQL_C_UBIGINT: - res = str2bigint(&xstr->c, /*wide?*/FALSE, (SQLUBIGINT *)dest); + res = str2bigint(&xstr->c, /*wide?*/FALSE, (SQLUBIGINT *)dest, + /*strict?*/TRUE); break; case SQL_C_DOUBLE: - res = str2double(&xstr->c, /*wide?*/FALSE, (SQLDOUBLE *)dest); + res = str2double(&xstr->c, /*wide?*/FALSE, (SQLDOUBLE *)dest, + /*strict?*/TRUE); break; default: assert(0); } } - if (! res) { + if (res < 0) { if (xstr->wide) { ERRH(stmt, "can't convert `" LWPDL "` to type %hd number.", LWSTR(&xstr->w), dest_type); diff --git a/driver/defs.h b/driver/defs.h index d9aabdbd..abf5ed8c 100644 --- a/driver/defs.h +++ b/driver/defs.h @@ -75,8 +75,10 @@ #define ESODBC_MAX_FIX_PRECISION ESODBC_PRECISION_UINT64 /* maximum floating numeric precision */ #define ESODBC_MAX_FLT_PRECISION ESODBC_PRECISION_DOUBLE -/* maximum seconds precision */ -#define ESODBC_MAX_SEC_PRECISION ESODBC_PRECISION_UINT64 +/* maximum seconds precision (i.e. sub-second accuracy) */ +/* Seconds precision is currently 3, with ES/SQL's ISO8601 millis. + * (Should move to 9 with nanosecond implementation) */ +#define ESODBC_MAX_SEC_PRECISION 9 /* * standard specified defaults: * https://docs.microsoft.com/en-us/sql/odbc/reference/syntax/sqlsetdescfield-function##record-fields @@ -88,11 +90,26 @@ #define ESODBC_DEF_DATETIME_PRECISION 0 #define ESODBC_DEF_TIMESTAMP_PRECISION 6 /* interval */ -#define ESODBC_DEF_INTVL_WS_PRECISION 6 -#define ESODBC_DEF_INTVL_WOS_DT_PREC 2 +#define ESODBC_DEF_IVL_WS_PRECISION 6 +#define ESODBC_DEF_IVL_WOS_DT_PREC 2 /* decimal, numeric */ #define ESODBC_DEF_DECNUM_SCALE 0 - +/* + * interval lead precision maxes: + * - YEAR,MONTH: LONG_MAX (2147483647) + * - DAY: 106751991167300 + * - HOUR: 2562047788015215 + * - MINUTE: 153722867280912930 (0x0222,2222,2222,2222) + * - SECOND: LLONG_MAX (9223372036854775807) + * with Duration/Period of 11.0.1+13-LTS Win x64 O-JVM. + * TODO: reasoning for these maxes mix?? + */ +#define ESODBC_MAX_IVL_YEAR_LEAD_PREC (sizeof("-2147483647") - 1) +#define ESODBC_MAX_IVL_MONTH_LEAD_PREC (sizeof("-2147483647") - 1) +#define ESODBC_MAX_IVL_DAY_LEAD_PREC (sizeof("-106751991167300") - 1) +#define ESODBC_MAX_IVL_HOUR_LEAD_PREC (sizeof("-2562047788015215") - 1) +#define ESODBC_MAX_IVL_MINUTE_LEAD_PREC (sizeof("-153722867280912930") - 1) +#define ESODBC_MAX_IVL_SECOND_LEAD_PREC (sizeof("-9223372036854775807") - 1) @@ -359,66 +376,6 @@ #define ESODBC_DATE_TEMPLATE "yyyy-mm-ddT" #define ESODBC_TIME_TEMPLATE "hh:mm:ss.9999999" -/* - * ES-to-C-SQL mappings - * DATA_TYPE(SYS TYPES) : SQL_ -> SQL_C_ - * Collected here for a quick overview (and easy change); can't be automated. - */ -/* -6: SQL_TINYINT -> SQL_C_TINYINT */ -#define ESODBC_ES_TO_CSQL_BYTE SQL_C_TINYINT -#define ESODBC_ES_TO_SQL_BYTE SQL_TINYINT -/* 5: SQL_SMALLINT -> SQL_C_SHORT */ -#define ESODBC_ES_TO_CSQL_SHORT SQL_C_SSHORT -#define ESODBC_ES_TO_SQL_SHORT SQL_SMALLINT -/* 4: SQL_INTEGER -> SQL_C_LONG */ -#define ESODBC_ES_TO_CSQL_INTEGER SQL_C_SLONG -#define ESODBC_ES_TO_SQL_INTEGER SQL_INTEGER -/* -5: SQL_BIGINT -> SQL_C_SBIGINT */ -#define ESODBC_ES_TO_CSQL_LONG SQL_C_SBIGINT -#define ESODBC_ES_TO_SQL_LONG SQL_BIGINT -/* 6: SQL_FLOAT -> SQL_C_DOUBLE */ -#define ESODBC_ES_TO_CSQL_HALF_FLOAT SQL_C_DOUBLE -#define ESODBC_ES_TO_SQL_HALF_FLOAT SQL_FLOAT -/* 6: SQL_FLOAT -> SQL_C_DOUBLE */ -#define ESODBC_ES_TO_CSQL_SCALED_FLOAT SQL_C_DOUBLE -#define ESODBC_ES_TO_SQL_SCALED_FLOAT SQL_FLOAT -/* 7: SQL_REAL -> SQL_C_DOUBLE */ -#define ESODBC_ES_TO_CSQL_FLOAT SQL_C_FLOAT -#define ESODBC_ES_TO_SQL_FLOAT SQL_REAL -/* 8: SQL_DOUBLE -> SQL_C_FLOAT */ -#define ESODBC_ES_TO_CSQL_DOUBLE SQL_C_DOUBLE -#define ESODBC_ES_TO_SQL_DOUBLE SQL_DOUBLE -/* 16: ??? -> SQL_C_TINYINT */ -#define ESODBC_ES_TO_CSQL_BOOLEAN SQL_C_BIT -#define ESODBC_ES_TO_SQL_BOOLEAN SQL_BIT -/* 12: SQL_VARCHAR -> SQL_C_WCHAR */ -#define ESODBC_ES_TO_CSQL_KEYWORD SQL_C_WCHAR /* XXX: CBOR needs _CHAR */ -#define ESODBC_ES_TO_SQL_KEYWORD SQL_VARCHAR -/* 12: SQL_VARCHAR -> SQL_C_WCHAR */ -#define ESODBC_ES_TO_CSQL_TEXT SQL_C_WCHAR /* XXX: CBOR needs _CHAR */ -#define ESODBC_ES_TO_SQL_TEXT SQL_VARCHAR -/* 12: SQL_VARCHAR -> SQL_C_WCHAR */ -#define ESODBC_ES_TO_CSQL_IP SQL_C_WCHAR /* XXX: CBOR needs _CHAR */ -#define ESODBC_ES_TO_SQL_IP SQL_VARCHAR -/* 93: SQL_TYPE_TIMESTAMP -> SQL_C_TYPE_TIMESTAMP */ -#define ESODBC_ES_TO_CSQL_DATE SQL_C_TYPE_TIMESTAMP -#define ESODBC_ES_TO_SQL_DATE SQL_TYPE_TIMESTAMP -/* -3: SQL_VARBINARY -> SQL_C_BINARY */ -#define ESODBC_ES_TO_CSQL_BINARY SQL_C_BINARY -#define ESODBC_ES_TO_SQL_BINARY SQL_VARBINARY -/* 0: SQL_TYPE_NULL -> SQL_C_TINYINT */ -#define ESODBC_ES_TO_CSQL_NULL SQL_C_STINYINT -#define ESODBC_ES_TO_SQL_NULL SQL_TYPE_NULL -/* 1111: ??? -> SQL_C_BINARY */ -#define ESODBC_ES_TO_CSQL_UNSUPPORTED SQL_C_BINARY -#define ESODBC_ES_TO_SQL_UNSUPPORTED ESODBC_SQL_UNSUPPORTED -/* 2002: ??? -> SQL_C_BINARY */ -#define ESODBC_ES_TO_CSQL_OBJECT SQL_C_BINARY -#define ESODBC_ES_TO_SQL_OBJECT ESODBC_SQL_OBJECT -/* 2002: ??? -> SQL_C_BINARY */ -#define ESODBC_ES_TO_CSQL_NESTED SQL_C_BINARY -#define ESODBC_ES_TO_SQL_NESTED ESODBC_SQL_NESTED - #endif /* __DEFS_H__ */ /* vim: set noet fenc=utf-8 ff=dos sts=0 sw=4 ts=4 tw=78 : */ diff --git a/driver/handles.c b/driver/handles.c index 29c8423f..67b7ae05 100644 --- a/driver/handles.c +++ b/driver/handles.c @@ -710,7 +710,7 @@ SQLRETURN EsSQLSetStmtAttrW( desc = stmt->apd; break; } while (0); - ret = EsSQLSetDescFieldW(desc, NO_REC_NR, + ret = EsSQLSetDescFieldW(desc, NO_REC_NR, SQL_DESC_ARRAY_STATUS_PTR, ValuePtr, BufferLength); if (ret != SQL_SUCCESS) { /* _WITH_INFO wud be "error" here */ /* if SetDescField() fails, DM will check statement's diag */ @@ -732,7 +732,7 @@ SQLRETURN EsSQLSetStmtAttrW( desc = stmt->ipd; break; } while (0); - ret = EsSQLSetDescFieldW(desc, NO_REC_NR, + ret = EsSQLSetDescFieldW(desc, NO_REC_NR, SQL_DESC_ROWS_PROCESSED_PTR, ValuePtr, BufferLength); if (ret != SQL_SUCCESS) { /* _WITH_INFO wud be "error" here */ /* if SetDescField() fails, DM will check statement's diag */ @@ -1452,7 +1452,7 @@ SQLRETURN update_rec_count(esodbc_desc_st *desc, SQLSMALLINT new_count) */ esodbc_rec_st *get_record(esodbc_desc_st *desc, SQLSMALLINT rec_no, BOOL grow) { - assert(0 <= rec_no); + assert(0 < rec_no); if (desc->count < rec_no) { if (! grow) { @@ -1671,7 +1671,7 @@ SQLRETURN EsSQLGetDescFieldW( case SQL_DESC_LITERAL_PREFIX: wstr = rec->es_type->literal_prefix; break; - case SQL_DESC_LITERAL_SUFFIX: + case SQL_DESC_LITERAL_SUFFIX: wstr = rec->es_type->literal_suffix; break; case SQL_DESC_LOCAL_TYPE_NAME: @@ -1917,12 +1917,14 @@ void concise_to_type_code(SQLSMALLINT concise, SQLSMALLINT *type, *type = SQL_INTERVAL; *code = SQL_CODE_MINUTE_TO_SECOND; break; + default: + /* "For all data types except datetime and interval data types, + * the verbose type identifier is the same as the concise type + * identifier and the value in SQL_DESC_DATETIME_INTERVAL_CODE is + * equal to 0." */ + *type = concise; + *code = 0; } - /* "For all data types except datetime and interval data types, the - * verbose type identifier is the same as the concise type identifier and - * the value in SQL_DESC_DATETIME_INTERVAL_CODE is equal to 0." */ - *type = concise; - *code = 0; } /* @@ -1947,10 +1949,10 @@ static void set_defaults_from_meta_type(esodbc_rec_st *rec) } break; case METATYPE_INTERVAL_WSEC: - rec->precision = ESODBC_DEF_INTVL_WS_PRECISION; + rec->precision = ESODBC_DEF_IVL_WS_PRECISION; /* no break */ case METATYPE_INTERVAL_WOSEC: - rec->datetime_interval_precision = ESODBC_DEF_INTVL_WOS_DT_PREC; + rec->datetime_interval_precision = ESODBC_DEF_IVL_WOS_DT_PREC; break; case METATYPE_EXACT_NUMERIC: if (rec->concise_type == SQL_DECIMAL || @@ -2151,6 +2153,7 @@ static BOOL consistency_check(esodbc_rec_st *rec) { SQLSMALLINT type, code; esodbc_desc_st *desc = rec->desc; + SQLINTEGER max_prec; /* validity of C / SQL datatypes is checked when setting the meta_type */ assert(METATYPE_UNKNOWN <= rec->meta_type && @@ -2188,23 +2191,58 @@ static BOOL consistency_check(esodbc_rec_st *rec) } break; + /* check SQL_DESC_PRECISION field */ + /* "a time or timestamp data type" */ case METATYPE_DATETIME: if (rec->concise_type == SQL_TYPE_DATE) { break; } + /* "an interval type with a seconds component" */ case METATYPE_INTERVAL_WSEC: - // TODO: "or one of the interval data types with a time component" + /* "or one of the interval data types with a time component" */ case METATYPE_INTERVAL_WOSEC: - if (rec->precision < 0 || - ESODBC_MAX_SEC_PRECISION < rec->precision) { - ERRH(desc, "precision (%hd) out of bounds [0, %d].", - rec->precision, ESODBC_MAX_SEC_PRECISION); - return FALSE; + if (SQL_INTERVAL_MONTH < rec->concise_type && + rec->concise_type != SQL_INTERVAL_YEAR_TO_MONTH) { + if (rec->precision < 0 || + ESODBC_MAX_SEC_PRECISION < rec->precision) { + ERRH(desc, "precision (%hd) out of bounds [0, %d].", + rec->precision, ESODBC_MAX_SEC_PRECISION); + return FALSE; + } } if (rec->meta_type == METATYPE_DATETIME) { break; } - // TODO: check rec->datetime_interval_precision + /* check SQL_DESC_DATETIME_INTERVAL_PRECISION */ + switch (rec->concise_type) { + case SQL_INTERVAL_YEAR: + max_prec = ESODBC_MAX_IVL_YEAR_LEAD_PREC; + break; + case SQL_INTERVAL_MONTH: + max_prec = ESODBC_MAX_IVL_MONTH_LEAD_PREC; + break; + case SQL_INTERVAL_DAY: + max_prec = ESODBC_MAX_IVL_DAY_LEAD_PREC; + break; + case SQL_INTERVAL_HOUR: + max_prec = ESODBC_MAX_IVL_HOUR_LEAD_PREC; + break; + case SQL_INTERVAL_MINUTE: + max_prec = ESODBC_MAX_IVL_MINUTE_LEAD_PREC; + break; + case SQL_INTERVAL_SECOND: + max_prec = ESODBC_MAX_IVL_SECOND_LEAD_PREC; + break; + default: + max_prec = -1; + } + if (0 < max_prec && + (rec->datetime_interval_precision < 0 || + max_prec < rec->datetime_interval_precision)) { + ERRH(desc, "datetime_interval_precision (%hd) out of bounds " + "[0, %d].", rec->datetime_interval_precision, max_prec); + return FALSE; + } break; } @@ -2414,7 +2452,7 @@ SQLRETURN EsSQLSetDescFieldW( case SQL_DESC_TYPE: type = (SQLSMALLINT)(intptr_t)ValuePtr; DBGH(desc, "setting type of rec@0x%p to %d.", rec, type); - /* Note: SQL_[C_]DATE == SQL_DATETIME (== 9) => + /* Note: SQL_[C_]DATE == SQL_DATETIME (== 9) => * 1. one needs to always use SQL_DESC_CONCISE_TYPE for setting * the types from within the driver (binding cols, params): * "SQL_DESC_CONCISE_TYPE can be set by a call to SQLBindCol or @@ -2429,7 +2467,7 @@ SQLRETURN EsSQLSetDescFieldW( * valid and consistent." */ /* setting the verbose type only */ concise_to_type_code(rec->concise_type, &chk_type, &chk_code); - if (chk_type != type || + if (chk_type != type || chk_code != rec->datetime_interval_code || (! rec->datetime_interval_code)) { ERRH(desc, "type fields found inconsistent when setting " @@ -2445,10 +2483,10 @@ SQLRETURN EsSQLSetDescFieldW( /* no break! */ case SQL_DESC_CONCISE_TYPE: DBGH(desc, "setting concise type of rec 0x%p to %d.", rec, - (SQLSMALLINT)(intptr_t)ValuePtr); + (SQLSMALLINT)(intptr_t)ValuePtr); rec->concise_type = (SQLSMALLINT)(intptr_t)ValuePtr; - concise_to_type_code(rec->concise_type, &rec->type, + concise_to_type_code(rec->concise_type, &rec->type, &rec->datetime_interval_code); rec->meta_type = concise_to_meta(rec->concise_type, desc->type); if (rec->meta_type == METATYPE_UNKNOWN) { @@ -2513,7 +2551,7 @@ SQLRETURN EsSQLSetDescFieldW( case SQL_DESC_NAME: WARNH(desc, "stored procedure params (to set to `"LWPD"`) not " "supported.", ValuePtr ? (SQLWCHAR *)ValuePtr : TWS_NULL); - RET_HDIAG(desc, SQL_STATE_HYC00, + RET_HDIAG(desc, SQL_STATE_HYC00, "stored procedure params not supported", 0); /* */ @@ -2613,7 +2651,7 @@ SQLRETURN EsSQLSetDescFieldW( /* */ do { /* R/O field: auto_unique_value, case_sensitive */ - case SQL_DESC_DATETIME_INTERVAL_PRECISION: + case SQL_DESC_DATETIME_INTERVAL_PRECISION: intp = &rec->datetime_interval_precision; break; case SQL_DESC_NUM_PREC_RADIX: diff --git a/driver/info.c b/driver/info.c index 6593ac90..52b974d6 100644 --- a/driver/info.c +++ b/driver/info.c @@ -1189,37 +1189,24 @@ SQLRETURN EsSQLGetFunctions(SQLHDBC ConnectionHandle, return SQL_SUCCESS; } -/* - * Equivalent of JDBC's getTypeInfo() ([0]:900) - */ +/* "If the DataType argument specifies a data type which is valid for the + * version of ODBC supported by the driver, but is not supported by the + * driver, then it will return an empty result set." */ SQLRETURN EsSQLGetTypeInfoW(SQLHSTMT StatementHandle, SQLSMALLINT DataType) { #define SQL_TYPES_STMT "SYS TYPES" -#define SQL_TYPES_TYPE_SEL "TYPE" SQLRETURN ret; esodbc_stmt_st *stmt = STMH(StatementHandle); - SQLWCHAR wbuff[sizeof(SQL_TYPES_STMT " " SQL_TYPES_TYPE_SEL " 32767")]; + SQLWCHAR wbuff[sizeof(SQL_TYPES_STMT " 32767")]; size_t cnt; - switch (DataType) { - case SQL_ALL_TYPES: - DBGH(stmt, "requested type description for all supported types."); - wcscpy(wbuff, MK_WPTR(SQL_TYPES_STMT)); - cnt = sizeof(SQL_TYPES_STMT) - 1; - break; - - /* "If the DataType argument specifies a data type which is valid for - * the version of ODBC supported by the driver, but is not supported - * by the driver, then it will return an empty result set." */ - default: - cnt = swprintf(wbuff, sizeof(wbuff)/sizeof(*wbuff), - MK_WPTR(SQL_TYPES_STMT " " SQL_TYPES_TYPE_SEL " %hd"), - DataType); - if (cnt <= 0) { - ERRNH(stmt, "failed to print catalog query."); - RET_HDIAGS(stmt, SQL_STATE_HY000); - } + DBGH(stmt, "requested type description for type %hd.", DataType); + cnt = swprintf(wbuff, sizeof(wbuff)/sizeof(*wbuff), + MK_WPTR(SQL_TYPES_STMT " %hd"), DataType); + if (cnt <= 0) { + ERRNH(stmt, "failed to print catalog query."); + RET_HDIAGS(stmt, SQL_STATE_HY000); } ret = EsSQLFreeStmt(stmt, ESODBC_SQL_CLOSE); @@ -1231,7 +1218,6 @@ SQLRETURN EsSQLGetTypeInfoW(SQLHSTMT StatementHandle, SQLSMALLINT DataType) return ret; # undef SQL_TYPES_STMT -# undef SQL_TYPES_TYPE_SEL } /* vim: set noet fenc=utf-8 ff=dos sts=0 sw=4 ts=4 tw=78 : */ diff --git a/driver/odbc.c b/driver/odbc.c index 5ad7805a..2dababcd 100644 --- a/driver/odbc.c +++ b/driver/odbc.c @@ -459,12 +459,16 @@ SQLRETURN SQL_API SQLGetDescRecW( * SQL_DESC_TYPE field and the values applicable to the SQL_DESC_TYPE field in * the same record are valid and consistent. * - * The SQL_DESC_DATA_PTR field of an IPD is not normally set; however, an + * "The SQL_DESC_DATA_PTR field of an IPD is not normally set; however, an * application can do so to force a consistency check of IPD fields. The value * that the SQL_DESC_DATA_PTR field of the IPD is set to is not actually * stored and cannot be retrieved by a call to SQLGetDescField or * SQLGetDescRec; the setting is made only to force the consistency check. A * consistency check cannot be performed on an IRD." + * + * "A call to SQLSetDescRec sets the interval leading precision to the default + * but sets the interval seconds precision (in the SQL_DESC_PRECISION field) + * to the value of its Precision argument" */ SQLRETURN SQL_API SQLSetDescRec( SQLHDESC DescriptorHandle, diff --git a/driver/queries.c b/driver/queries.c index 3c427994..40d47f07 100644 --- a/driver/queries.c +++ b/driver/queries.c @@ -50,7 +50,7 @@ void clear_resultset(esodbc_stmt_st *stmt, BOOL on_close) STMT_GD_RESET(stmt); } -/* Set the desriptor fields associated with "size". This step is needed since +/* Set the descriptor fields associated with "size". This step is needed since * the application could read the descriptors - like .length - individually, * rather than through functions that make use of get_col_size() (where we * could just read the es_type directly). */ @@ -83,6 +83,11 @@ static void set_col_size(esodbc_rec_st *rec) rec->length = rec->es_type->column_size; break; + case METATYPE_INTERVAL_WSEC: + case METATYPE_INTERVAL_WOSEC: + rec->length = rec->es_type->display_size; + break; + default: BUGH(rec->desc, "unsupported data c-type: %d.", rec->concise_type); } @@ -232,7 +237,7 @@ SQLRETURN TEST_API attach_answer(esodbc_stmt_st *stmt, char *buff, size_t blen) if (! obj) { ERRH(stmt, "failed to decode JSON answer: %s ([%zu] `%.*s`).", stmt->rset.state ? UJGetError(stmt->rset.state) : "", - blen, buff); + blen, blen, buff); RET_HDIAG(stmt, SQL_STATE_HY000, MSG_INV_SRV_ANS, 0); } columns = rows = cursor = NULL; @@ -973,7 +978,7 @@ SQLRETURN EsSQLFetch(SQLHSTMT StatementHandle) /* return number of processed rows (even if 0) */ if (ird->rows_processed_ptr) { - DBGH(stmt, "setting number of processed rows to: %lu.", i); + DBGH(stmt, "setting number of processed rows to: %llu.", i); *ird->rows_processed_ptr = i; } @@ -1020,6 +1025,11 @@ static SQLRETURN gd_checks(esodbc_stmt_st *stmt, SQLUSMALLINT colno) ERRH(stmt, "SQLFetch() hasn't yet been called on result set."); RET_HDIAGS(stmt, SQL_STATE_24000); } + /* colno will be used as 1-based index, if not using bookmarks */ + if (colno < 1) { // TODO: add bookmark check (when implementing it) + ERRH(stmt, "column number (%hu) can be less than 1.", colno); + RET_HDIAGS(stmt, SQL_STATE_07009); + } return SQL_SUCCESS; } @@ -1343,7 +1353,7 @@ static esodbc_estype_st *lookup_es_type(esodbc_dbc_st *dbc, } /* find the matching ES/SQL type for app's SQL type, which can be an exact - * math against ES/SQL types, but also some other valid SQL type. */ + * match against ES/SQL types, but also some other valid SQL type. */ static esodbc_estype_st *match_es_type(esodbc_rec_st *arec, esodbc_rec_st *irec) { @@ -1402,9 +1412,7 @@ static esodbc_estype_st *match_es_type(esodbc_rec_st *arec, case METATYPE_INTERVAL_WSEC: case METATYPE_INTERVAL_WOSEC: - /* TODO: implement them once avail in ES */ - - case METATYPE_FLOAT_NUMERIC: /* should have matched already */ + case METATYPE_FLOAT_NUMERIC: /* these should have matched already */ case METATYPE_MAX: /* -> SQL_C_DEFAULT, ESODBC_SQL_NULL, should've matched already */ case METATYPE_UNKNOWN: @@ -1449,6 +1457,11 @@ SQLRETURN EsSQLBindParameter( ERRH(stmt, "parameter IO-type (%hd) not supported.", InputOutputType); RET_HDIAG(stmt, SQL_STATE_HYC00, "parameter IO-type not supported", 0); } + /* ParameterNumber will be used as 1-based index */ + if (ParameterNumber < 1) { + ERRH(stmt, "param. no (%hd) can't be less than 1.", ParameterNumber); + RET_HDIAGS(stmt, SQL_STATE_07009); + } /* Note: "If StrLen_or_IndPtr is a null pointer, the driver assumes that * all input parameter values are non-NULL and that character and binary @@ -2093,7 +2106,7 @@ static inline SQLULEN get_col_size(esodbc_rec_st *rec) switch (rec->meta_type) { case METATYPE_EXACT_NUMERIC: case METATYPE_FLOAT_NUMERIC: - return rec->es_type->column_size; + return rec->es_type->column_size; // precision? case METATYPE_STRING: case METATYPE_BIN: @@ -2119,8 +2132,7 @@ static inline SQLSMALLINT get_col_decdigits(esodbc_rec_st *rec) switch (rec->meta_type) { case METATYPE_DATETIME: case METATYPE_INTERVAL_WSEC: - /* TODO: pending GH#30002 actually */ - return 3; + return ESODBC_MAX_SEC_PRECISION; case METATYPE_EXACT_NUMERIC: return rec->es_type->maximum_scale; @@ -2421,4 +2433,4 @@ SQLRETURN EsSQLRowCount(_In_ SQLHSTMT StatementHandle, _Out_ SQLLEN *RowCount) return SQL_SUCCESS; } -/* vim: set noet fenc=utf-8 ff=dos sts=0 sw=4 ts=4 : */ +/* vim: set noet fenc=utf-8 ff=dos sts=0 sw=4 ts=4 tw=78 : */ diff --git a/driver/util.c b/driver/util.c index 96cc387e..e67ae0f3 100644 --- a/driver/util.c +++ b/driver/util.c @@ -27,7 +27,7 @@ BOOL wstr2bool(wstr_st *val) return TRUE; } -BOOL str2ubigint(void *val, const BOOL wide, SQLUBIGINT *out) +int str2ubigint(void *val, BOOL wide, SQLUBIGINT *out, BOOL strict) { SQLUBIGINT res, digit; size_t i, cnt; @@ -46,7 +46,7 @@ BOOL str2ubigint(void *val, const BOOL wide, SQLUBIGINT *out) if (cnt < 1) { errno = EINVAL; - return FALSE; + return -1; } digit = wide ? wstr[0] : cstr[0]; i = (digit == '+') ? 1 : 0; /* L'+' =(ubigint)= '+' */ @@ -55,8 +55,12 @@ BOOL str2ubigint(void *val, const BOOL wide, SQLUBIGINT *out) digit = wide ? wstr[i] - L'0' : cstr[i] - '0'; /* is it a number? */ if (digit < 0 || 9 < digit) { - errno = EINVAL; - return FALSE; + if (strict) { + errno = EINVAL; + return -1; + } else { + break; + } } assert(sizeof(SQLUBIGINT) == sizeof(uint64_t)); if (i < ESODBC_PRECISION_UINT64 - 1) { @@ -66,27 +70,33 @@ BOOL str2ubigint(void *val, const BOOL wide, SQLUBIGINT *out) /* would it overflow? */ if (max_div_10 < res) { errno = ERANGE; - return FALSE; + return -1; } else { res *= 10; } if (ULLONG_MAX - res < digit) { errno = ERANGE; - return FALSE; + return -1; } else { res += digit; } } } + if (cnt <= 1 && digit == '+') { + /* prevent '+' or smth like '+a..' from returning a valid result */ + return -1; + } *out = res; - return TRUE; + assert(i <= INT_MAX); /* long will take less than INT_MAX digits */ + return (long)i; } -BOOL str2bigint(void *val, const BOOL wide, SQLBIGINT *out) +int str2bigint(void *val, BOOL wide, SQLBIGINT *out, BOOL strict) { SQLUBIGINT ull; /* unsigned long long */ size_t i; - BOOL negative, ret; + BOOL negative; + int ret; cstr_st cstr; wstr_st wstr; @@ -100,7 +110,7 @@ BOOL str2bigint(void *val, const BOOL wide, SQLBIGINT *out) if (i < 1) { errno = EINVAL; - return FALSE; + return -1; } else { switch (wide ? wstr.str[0] : cstr.str[0]) { case '-': /* L'-' =(size_t)= '-' */ @@ -120,68 +130,76 @@ BOOL str2bigint(void *val, const BOOL wide, SQLBIGINT *out) if (wide) { wstr.str += i; wstr.cnt -= i; - ret = str2ubigint(&wstr, wide, &ull); + ret = str2ubigint(&wstr, wide, &ull, strict); } else { cstr.str += i; cstr.cnt -= i; - ret = str2ubigint(&cstr, wide, &ull); + ret = str2ubigint(&cstr, wide, &ull, strict); } - if (! ret) { - return FALSE; + if (ret < 0) { /* str2ubigint(,strict) won't return 0 */ + return -1; + } else if (ret == 0 && i) { /* +/- only is NaN */ + return -1; } if (negative) { if ((SQLUBIGINT)LLONG_MIN < ull) { errno = ERANGE; - return FALSE; /* underflow */ + return -1; /* underflow */ } else { *out = -(SQLBIGINT)ull; } } else { if ((SQLUBIGINT)LLONG_MAX < ull) { errno = ERANGE; - return FALSE; /* overflow */ + return -1; /* overflow */ } else { *out = (SQLBIGINT)ull; } } - return TRUE; + return ret + (int)i; } -BOOL str2double(void *val, BOOL wide, SQLDOUBLE *dbl) +int str2double(void *val, BOOL wide, SQLDOUBLE *dbl, BOOL strict) { wstr_st wstr; cstr_st cstr; wchar_t *endwptr; char *endptr; + size_t digits; errno = 0; if (wide) { wstr = *(wstr_st *)val; if (wstr.str[wstr.cnt]) { // TODO: simple quick parser instead of scanf - if(_snwscanf(wstr.str, wstr.cnt, L"%le", (double *)dbl) != 1) { - return FALSE; + if (_snwscanf(wstr.str, wstr.cnt, L"%le", (double *)dbl) != 1) { + return -1; } + digits = wstr.cnt; // TODO } else { *dbl = wcstod((wchar_t *)wstr.str, &endwptr); - if (errno || wstr.str + wstr.cnt != endwptr) { - return FALSE; + digits = endwptr - wstr.str; + if (errno || (strict && wstr.cnt != digits)) { + return -1; } } } else { cstr = *(cstr_st *)val; if (cstr.str[cstr.cnt]) { - if(_snscanf(cstr.str, cstr.cnt, "%le", (double *)dbl) != 1) { - return FALSE; + if (_snscanf(cstr.str, cstr.cnt, "%le", (double *)dbl) != 1) { + return -1; } + digits = cstr.cnt; } else { *dbl = strtod((char *)cstr.str, &endptr); - if (errno || cstr.str + cstr.cnt != endptr) { - return FALSE; + digits = endptr - cstr.str; + if (errno || (strict && cstr.cnt != digits)) { + return -1; } } } - return TRUE; + assert(digits <= INT_MAX); + return (int)digits; } size_t i64tot(int64_t i64, void *buff, BOOL wide) @@ -208,22 +226,36 @@ size_t ui64tot(uint64_t ui64, void *buff, BOOL wide) } /* - * Trims leading and trailing WS of a wide string of 'chars' length. + * Trims leading WS of a wide string of 'chars' length. * 0-terminator should not be counted (as it's a non-WS). */ -void wtrim_ws(wstr_st *wstr) +void wltrim_ws(wstr_st *wstr) { size_t cnt = wstr->cnt; SQLWCHAR *wcrr = wstr->str; SQLWCHAR *wend = wcrr + cnt; - /* right trim */ + /* left trim */ while (wcrr < wend && iswspace(*wcrr)) { wcrr ++; cnt --; } - /* left trim */ + *wstr = (wstr_st) { + wcrr, cnt + }; +} + +/* + * Trims leading and trailing WS of a wide string of 'chars' length. + * 0-terminator should not be counted (as it's a non-WS). + */ +void wrtrim_ws(wstr_st *wstr) +{ + size_t cnt = wstr->cnt; + SQLWCHAR *wcrr = wstr->str; + + /* right trim */ while (cnt && iswspace(wcrr[cnt - 1])) { cnt --; } @@ -232,6 +264,7 @@ void wtrim_ws(wstr_st *wstr) wcrr, cnt }; } + void trim_ws(cstr_st *cstr) { size_t cnt = cstr->cnt; @@ -254,6 +287,7 @@ void trim_ws(cstr_st *cstr) }; } + /* * Converts a wchar_t string to a C string for ASCII characters. * 'dst' should be at least as character-long as 'src', if 'src' is diff --git a/driver/util.h b/driver/util.h index 4d8e333d..811176f7 100644 --- a/driver/util.h +++ b/driver/util.h @@ -150,12 +150,18 @@ typedef struct { * 0-terminator should not be counted (as it's a non-WS). */ void trim_ws(cstr_st *str); -void wtrim_ws(wstr_st *wstr); +void wltrim_ws(wstr_st *wstr); +void wrtrim_ws(wstr_st *wstr); +#define wtrim_ws(_w) do { wltrim_ws(_w); wrtrim_ws(_w); } while (0) + BOOL wstr2bool(wstr_st *val); -BOOL str2ubigint(void *val, const BOOL wide, SQLUBIGINT *out); -BOOL str2bigint(void *val, const BOOL wide, SQLBIGINT *out); -BOOL str2double(void *val, BOOL wide, SQLDOUBLE *dbl); +/* Converts a [cw]str_st to a SQL(U)BIGINT. + * If !strict, parsing stops at first non-digit char. + * Returns the number of parsed characters or negative of failure. */ +int str2ubigint(void *val, BOOL wide, SQLUBIGINT *out, BOOL strict); +int str2bigint(void *val, BOOL wide, SQLBIGINT *out, BOOL strict); +int str2double(void *val, BOOL wide, SQLDOUBLE *dbl, BOOL strict); /* converts the int types to a C or wide string, returning the string length */ size_t i64tot(int64_t i64, void *buff, BOOL wide); diff --git a/test/connected_dbc.cc b/test/connected_dbc.cc index 29677597..b482d265 100644 --- a/test/connected_dbc.cc +++ b/test/connected_dbc.cc @@ -21,63 +21,89 @@ extern "C" { */ static const char systypes_answer[] = "\ {\ - \"columns\": [\ - {\"name\": \"TYPE_NAME\", \"type\": \"keyword\"},\ - {\"name\": \"DATA_TYPE\", \"type\": \"integer\"},\ - {\"name\": \"PRECISION\", \"type\": \"integer\"},\ - {\"name\": \"LITERAL_PREFIX\", \"type\": \"keyword\"},\ - {\"name\": \"LITERAL_SUFFIX\", \"type\": \"keyword\"},\ - {\"name\": \"CREATE_PARAMS\", \"type\": \"keyword\"},\ - {\"name\": \"NULLABLE\", \"type\": \"short\"},\ - {\"name\": \"CASE_SENSITIVE\", \"type\": \"boolean\"},\ - {\"name\": \"SEARCHABLE\", \"type\": \"short\"},\ - {\"name\": \"UNSIGNED_ATTRIBUTE\", \"type\": \"boolean\"},\ - {\"name\": \"FIXED_PREC_SCALE\", \"type\": \"boolean\"},\ - {\"name\": \"AUTO_INCREMENT\", \"type\": \"boolean\"},\ - {\"name\": \"LOCAL_TYPE_NAME\", \"type\": \"keyword\"},\ - {\"name\": \"MINIMUM_SCALE\", \"type\": \"short\"},\ - {\"name\": \"MAXIMUM_SCALE\", \"type\": \"short\"},\ - {\"name\": \"SQL_DATA_TYPE\", \"type\": \"integer\"},\ - {\"name\": \"SQL_DATETIME_SUB\", \"type\": \"integer\"},\ - {\"name\": \"NUM_PREC_RADIX\", \"type\": \"integer\"},\ - {\"name\": \"INTERVAL_PRECISION\", \"type\": \"integer\"}\ - ],\ - \"rows\": [\ - [\"BYTE\", -6, 3, \"'\", \"'\", null, 2, false, 3, false, false, false,\ - null, 0, 0, -6, 0, 10, null],\ - [\"LONG\", -5, 19, \"'\", \"'\", null, 2, false, 3, false, false, false,\ - null, 0, 0, -5, 0, 10, null],\ - [\"BINARY\", -3, 2147483647, \"'\", \"'\", null, 2, false, 3, true, false,\ - false, null, null, null, -3, 0, null, null],\ - [\"NULL\", 0, 0, \"'\", \"'\", null, 2, false, 3, true, false, false,\ - null, null, null, 0, 0, null, null],\ - [\"INTEGER\", 4, 10, \"'\", \"'\", null, 2, false, 3, false, false, false,\ - null, 0, 0, 4, 0, 10, null],\ - [\"SHORT\", 5, 5, \"'\", \"'\", null, 2, false, 3, false, false, false,\ - null, 0, 0, 5, 0, 10, null],\ - [\"HALF_FLOAT\", 6, 16, \"'\", \"'\", null, 2, false, 3, false, false,\ - false, null, 0, 16, 6, 0, 2, null],\ - [\"SCALED_FLOAT\", 6, 19, \"'\", \"'\", null, 2, false, 3, false, false,\ - false, null, 0, 19, 6, 0, 2, null],\ - [\"FLOAT\", 7, 7, \"'\", \"'\", null, 2, false, 3, false, false, false,\ - null, 0, 7, 7, 0, 2, null],\ - [\"DOUBLE\", 8, 15, \"'\", \"'\", null, 2, false, 3, false, false, false,\ - null, 0, 15, 8, 0, 2, null],\ - [\"KEYWORD\", 12, 256, \"'\", \"'\", null, 2, true, 3, true, false, false,\ - null, null, null, 12, 0, null, null],\ - [\"TEXT\", 12, 2147483647, \"'\", \"'\", null, 2, true, 3, true, false,\ - false, null, null, null, 12, 0, null, null],\ - [\"BOOLEAN\", 16, 1, \"'\", \"'\", null, 2, false, 3, true, false, false,\ - null, null, null, 16, 0, null, null],\ - [\"DATE\", 93, 24, \"'\", \"'\", null, 2, false, 3, true, false, false,\ - null, 3, 3, 9, 3, null, null],\ - [\"UNSUPPORTED\", 1111, 0, \"'\", \"'\", null, 2, false, 3, true, false,\ - false, null, null, null, 1111, 0, null, null],\ - [\"OBJECT\", 2002, 0, \"'\", \"'\", null, 2, false, 3, true, false, false,\ - null, null, null, 2002, 0, null, null],\ - [\"NESTED\", 2002, 0, \"'\", \"'\", null, 2, false, 3, true, false, false,\ - null, null, null, 2002, 0, null, null]\ - ]\ + \"columns\": [\ + {\"name\": \"TYPE_NAME\", \"type\": \"keyword\"},\ + {\"name\": \"DATA_TYPE\", \"type\": \"integer\"},\ + {\"name\": \"PRECISION\", \"type\": \"integer\"},\ + {\"name\": \"LITERAL_PREFIX\", \"type\": \"keyword\"},\ + {\"name\": \"LITERAL_SUFFIX\", \"type\": \"keyword\"},\ + {\"name\": \"CREATE_PARAMS\", \"type\": \"keyword\"},\ + {\"name\": \"NULLABLE\", \"type\": \"short\"},\ + {\"name\": \"CASE_SENSITIVE\", \"type\": \"boolean\"},\ + {\"name\": \"SEARCHABLE\", \"type\": \"short\"},\ + {\"name\": \"UNSIGNED_ATTRIBUTE\", \"type\": \"boolean\"},\ + {\"name\": \"FIXED_PREC_SCALE\", \"type\": \"boolean\"},\ + {\"name\": \"AUTO_INCREMENT\", \"type\": \"boolean\"},\ + {\"name\": \"LOCAL_TYPE_NAME\", \"type\": \"keyword\"},\ + {\"name\": \"MINIMUM_SCALE\", \"type\": \"short\"},\ + {\"name\": \"MAXIMUM_SCALE\", \"type\": \"short\"},\ + {\"name\": \"SQL_DATA_TYPE\", \"type\": \"integer\"},\ + {\"name\": \"SQL_DATETIME_SUB\", \"type\": \"integer\"},\ + {\"name\": \"NUM_PREC_RADIX\", \"type\": \"integer\"},\ + {\"name\": \"INTERVAL_PRECISION\", \"type\": \"integer\"}\ + ],\ + \"rows\": [\ + [\"BYTE\", -6, 3, \"'\", \"'\", null, 2, false, 3, false, false,\ + false, null, 0, 0, -6, 0, 10, null],\ + [\"LONG\", -5, 19, \"'\", \"'\", null, 2, false, 3, false, false,\ + false, null, 0, 0, -5, 0, 10, null],\ + [\"BINARY\", -3, 2147483647, \"'\", \"'\", null, 2, false, 3, true,\ + false, false, null, null, null, -3, 0, null, null],\ + [\"NULL\", 0, 0, \"'\", \"'\", null, 2, false, 3, true, false, false,\ + null, null, null, 0, 0, null, null],\ + [\"INTEGER\", 4, 10, \"'\", \"'\", null, 2, false, 3, false, false,\ + false, null, 0, 0, 4, 0, 10, null],\ + [\"SHORT\", 5, 5, \"'\", \"'\", null, 2, false, 3, false, false,\ + false, null, 0, 0, 5, 0, 10, null],\ + [\"HALF_FLOAT\", 6, 16, \"'\", \"'\", null, 2, false, 3, false, false,\ + false, null, 0, 16, 6, 0, 2, null],\ + [\"SCALED_FLOAT\", 6, 19, \"'\", \"'\", null, 2, false, 3, false,\ + false, false, null, 0, 19, 6, 0, 2, null],\ + [\"FLOAT\", 7, 7, \"'\", \"'\", null, 2, false, 3, false, false,\ + false, null, 0, 7, 7, 0, 2, null],\ + [\"DOUBLE\", 8, 15, \"'\", \"'\", null, 2, false, 3, false, false,\ + false, null, 0, 15, 8, 0, 2, null],\ + [\"KEYWORD\", 12, 256, \"'\", \"'\", null, 2, true, 3, true, false,\ + false, null, null, null, 12, 0, null, null],\ + [\"TEXT\", 12, 2147483647, \"'\", \"'\", null, 2, true, 3, true,\ + false, false, null, null, null, 12, 0, null, null],\ + [\"BOOLEAN\", 16, 1, \"'\", \"'\", null, 2, false, 3, true, false,\ + false, null, null, null, 16, 0, null, null],\ + [\"DATE\", 93, 24, \"'\", \"'\", null, 2, false, 3, true, false,\ + false, null, 3, 3, 9, 3, null, null],\ + [\"INTERVAL_YEAR\", 101, 7, \"'\", \"'\", null, 2, false, 3, true,\ + false, false, null, null, null, 101, 0, null, null],\ + [\"INTERVAL_MONTH\", 102, 7, \"'\", \"'\", null, 2, false, 3, true,\ + false, false, null, null, null, 102, 0, null, null],\ + [\"INTERVAL_DAY\", 103, 23, \"'\", \"'\", null, 2, false, 3, true,\ + false, false, null, null, null, 103, 0, null, null],\ + [\"INTERVAL_HOUR\", 104, 23, \"'\", \"'\", null, 2, false, 3, true,\ + false, false, null, null, null, 104, 0, null, null],\ + [\"INTERVAL_MINUTE\", 105, 23, \"'\", \"'\", null, 2, false, 3, true,\ + false, false, null, null, null, 105, 0, null, null],\ + [\"INTERVAL_SECOND\", 106, 23, \"'\", \"'\", null, 2, false, 3, true,\ + false, false, null, null, null, 106, 0, null, null],\ + [\"INTERVAL_YEAR_TO_MONTH\", 107, 7, \"'\", \"'\", null, 2, false, 3,\ + true, false, false, null, null, null, 107, 0, null, null],\ + [\"INTERVAL_DAY_TO_HOUR\", 108, 23, \"'\", \"'\", null, 2, false, 3,\ + true, false, false, null, null, null, 108, 0, null, null],\ + [\"INTERVAL_DAY_TO_MINUTE\", 109, 23, \"'\", \"'\", null, 2, false, 3,\ + true, false, false, null, null, null, 109, 0, null, null],\ + [\"INTERVAL_DAY_TO_SECOND\", 110, 23, \"'\", \"'\", null, 2, false, 3,\ + true, false, false, null, null, null, 110, 0, null, null],\ + [\"INTERVAL_HOUR_TO_MINUTE\", 111, 23, \"'\", \"'\", null, 2, false,\ + 3, true, false, false, null, null, null, 111, 0, null, null],\ + [\"INTERVAL_HOUR_TO_SECOND\", 112, 23, \"'\", \"'\", null, 2, false,\ + 3, true, false, false, null, null, null, 112, 0, null, null],\ + [\"INTERVAL_MINUTE_TO_SECOND\", 113, 23, \"'\", \"'\", null, 2, false,\ + 3, true, false, false, null, null, null, 113, 0, null, null],\ + [\"UNSUPPORTED\", 1111, 0, \"'\", \"'\", null, 2, false, 3, true,\ + false, false, null, null, null, 1111, 0, null, null],\ + [\"OBJECT\", 2002, 0, \"'\", \"'\", null, 2, false, 3, true, false,\ + false, null, null, null, 2002, 0, null, null],\ + [\"NESTED\", 2002, 0, \"'\", \"'\", null, 2, false, 3, true, false,\ + false, null, null, null, 2002, 0, null, null]\ + ]\ }\ "; @@ -198,3 +224,5 @@ void ConnectedDBC::prepareStatement(const char *jsonAnswer) ret = ATTACH_ANSWER(stmt, answer, strlen(answer)); ASSERT_TRUE(SQL_SUCCEEDED(ret)); } + +/* vim: set noet fenc=utf-8 ff=dos sts=0 sw=4 ts=4 tw=78 : */ diff --git a/test/test_conversion_compatibility.cc b/test/test_conversion_compatibility.cc index 295e32ab..52ebe166 100644 --- a/test/test_conversion_compatibility.cc +++ b/test/test_conversion_compatibility.cc @@ -40,12 +40,8 @@ static BOOL is_interval(SQLSMALLINT type) * https://docs.microsoft.com/en-us/sql/odbc/reference/appendixes/converting-data-from-c-to-sql-data-types */ static BOOL conv_supported(SQLSMALLINT sqltype, SQLSMALLINT ctype) { - - /* interval types not yet implemented */ - if (is_interval(sqltype) || is_interval(ctype)) - return FALSE; /* GUID type not yet implemented */ - if (sqltype == SQL_GUID || ctype == SQL_GUID) + if (sqltype == SQL_GUID || ctype == SQL_C_GUID) return FALSE; switch (ctype) { @@ -82,6 +78,13 @@ static BOOL conv_supported(SQLSMALLINT sqltype, SQLSMALLINT ctype) case SQL_C_TYPE_TIME: case SQL_C_TYPE_TIMESTAMP: case SQL_C_GUID: + case SQL_C_INTERVAL_YEAR_TO_MONTH: + case SQL_C_INTERVAL_DAY_TO_HOUR: + case SQL_C_INTERVAL_DAY_TO_MINUTE: + case SQL_C_INTERVAL_DAY_TO_SECOND: + case SQL_C_INTERVAL_HOUR_TO_MINUTE: + case SQL_C_INTERVAL_HOUR_TO_SECOND: + case SQL_C_INTERVAL_MINUTE_TO_SECOND: return FALSE; } break; @@ -145,17 +148,10 @@ static BOOL conv_supported(SQLSMALLINT sqltype, SQLSMALLINT ctype) case SQL_INTERVAL_MONTH: case SQL_INTERVAL_YEAR: - case SQL_INTERVAL_YEAR_TO_MONTH: case SQL_INTERVAL_DAY: case SQL_INTERVAL_HOUR: case SQL_INTERVAL_MINUTE: case SQL_INTERVAL_SECOND: - case SQL_INTERVAL_DAY_TO_HOUR: - case SQL_INTERVAL_DAY_TO_MINUTE: - case SQL_INTERVAL_DAY_TO_SECOND: - case SQL_INTERVAL_HOUR_TO_MINUTE: - case SQL_INTERVAL_HOUR_TO_SECOND: - case SQL_INTERVAL_MINUTE_TO_SECOND: switch (ctype) { case SQL_C_BIT: case SQL_C_NUMERIC: @@ -172,6 +168,21 @@ static BOOL conv_supported(SQLSMALLINT sqltype, SQLSMALLINT ctype) case SQL_C_ULONG: return TRUE; } + if (sqltype == ctype) { /* compat with self */ + return TRUE; + } + return FALSE; + + case SQL_INTERVAL_YEAR_TO_MONTH: + case SQL_INTERVAL_DAY_TO_HOUR: + case SQL_INTERVAL_DAY_TO_MINUTE: + case SQL_INTERVAL_DAY_TO_SECOND: + case SQL_INTERVAL_HOUR_TO_MINUTE: + case SQL_INTERVAL_HOUR_TO_SECOND: + case SQL_INTERVAL_MINUTE_TO_SECOND: + if (sqltype == ctype) { /* compat with self */ + return TRUE; + } return FALSE; case SQL_GUID: @@ -228,15 +239,11 @@ TEST_F(ConversionCompatibility, ConvCompat) ret = SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, csql, sql, /*size*/0, /*decdigits*/0, NULL, sizeof(NULL), &indlen); BOOL conv = conv_supported(sql, csql); - ASSERT_TRUE((!SQL_SUCCEEDED(ret) && !conv) || - (SQL_SUCCEEDED(ret) && conv)); - //ASSERT_TRUE((! SQL_SUCCEEDED(ret)) ^ conv_supported(sql, csql)); - //ASSERT_TRUE(((SQL_SUCCEEDED(ret)) && conv_supported(sql, csql)) || - // (! SQL_SUCCEEDED(ret))); + ASSERT_TRUE(!!SQL_SUCCEEDED(ret) == !!conv); } } } } // test namespace -/* vim: set noet fenc=utf-8 ff=dos sts=0 sw=4 ts=4 : */ +/* vim: set noet fenc=utf-8 ff=dos sts=0 sw=4 ts=4 tw=78 : */ diff --git a/test/test_conversion_sql2c_interval.cc b/test/test_conversion_sql2c_interval.cc index ff14ef19..65c546f5 100644 --- a/test/test_conversion_sql2c_interval.cc +++ b/test/test_conversion_sql2c_interval.cc @@ -15,71 +15,1622 @@ namespace test { -class ConvertSQL2C_Interval : public ::testing::Test, public ConnectedDBC { +class ConvertSQL2C_Interval : public ::testing::Test, public ConnectedDBC +{ + protected: + SQL_INTERVAL_STRUCT is = {0}; + // for interval values + SQLWCHAR wbuff[128] = {0}; + SQLCHAR buff[128] = {0}; +}; + +TEST_F(ConvertSQL2C_Interval, Integer2Interval_year) +{ +# undef SQL_VAL +# undef SQL +# define SQL_VAL "2001" +# define SQL "SELECT " SQL_VAL + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"integer\"}\ + ],\ + \"rows\": [\ + [" SQL_VAL "]\ + ]\ +}\ +"; + prepareStatement(json_answer); - protected: - SQL_INTERVAL_STRUCT is; + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_INTERVAL_YEAR, &is, + sizeof(is), &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); - void prepareAndBind(const char *jsonAnswer) { - prepareStatement(jsonAnswer); + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + ASSERT_EQ(ind_len, sizeof(is)); - ret = SQLBindCol(stmt, /*col#*/1, SQL_C_INTERVAL_HOUR, &is, sizeof(is), - &ind_len); - ASSERT_TRUE(SQL_SUCCEEDED(ret)); - } -}; + SQL_INTERVAL_STRUCT is2 = {0}; + is2.interval_type = SQL_IS_YEAR; + is2.intval.year_month.year = atoi(SQL_VAL); + ASSERT_TRUE(memcmp(&is, &is2, sizeof(is)) == 0); +} + +TEST_F(ConvertSQL2C_Interval, Integer2Interval_month) +{ +# undef SQL_VAL +# undef SQL +# define SQL_VAL "-2001" +# define SQL "SELECT " SQL_VAL + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"integer\"}\ + ],\ + \"rows\": [\ + [" SQL_VAL "]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_INTERVAL_MONTH, &is, + sizeof(is), &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + ASSERT_EQ(ind_len, sizeof(is)); + + SQL_INTERVAL_STRUCT is2 = {0}; + is2.interval_type = SQL_IS_MONTH; + is2.intval.year_month.month = abs(atoi(SQL_VAL)); + is2.interval_sign = SQL_VAL[0] == '-' ? SQL_TRUE : SQL_FALSE; + ASSERT_TRUE(memcmp(&is, &is2, sizeof(is)) == 0); +} + +TEST_F(ConvertSQL2C_Interval, Integer2Interval_day) +{ +# undef SQL_VAL +# undef SQL +# define SQL_VAL "26" +# define SQL "SELECT " SQL_VAL + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"integer\"}\ + ],\ + \"rows\": [\ + [" SQL_VAL "]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_INTERVAL_DAY, &is, + sizeof(is), &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + ASSERT_EQ(ind_len, sizeof(is)); + + SQL_INTERVAL_STRUCT is2 = {0}; + is2.interval_type = SQL_IS_DAY; + is2.intval.day_second.day = abs(atoi(SQL_VAL)); + is2.interval_sign = SQL_VAL[0] == '-' ? SQL_TRUE : SQL_FALSE; + ASSERT_TRUE(memcmp(&is, &is2, sizeof(is)) == 0); +} + +TEST_F(ConvertSQL2C_Interval, Long2Interval_hour) +{ +# undef SQL_VAL +# undef SQL +# define SQL_VAL "26" +# define SQL "SELECT " SQL_VAL + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"Long\"}\ + ],\ + \"rows\": [\ + [" SQL_VAL "]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_INTERVAL_HOUR, &is, + sizeof(is), &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + ASSERT_EQ(ind_len, sizeof(is)); + + SQL_INTERVAL_STRUCT is2 = {0}; + is2.interval_type = SQL_IS_HOUR; + is2.intval.day_second.hour = abs(atoi(SQL_VAL)); + is2.interval_sign = SQL_VAL[0] == '-' ? SQL_TRUE : SQL_FALSE; + ASSERT_TRUE(memcmp(&is, &is2, sizeof(is)) == 0); +} + +TEST_F(ConvertSQL2C_Interval, Short2Interval_minute) +{ +# undef SQL_VAL +# undef SQL +# define SQL_VAL "-26" +# define SQL "SELECT " SQL_VAL + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"Short\"}\ + ],\ + \"rows\": [\ + [" SQL_VAL "]\ + ]\ +}\ +"; + prepareStatement(json_answer); + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_INTERVAL_MINUTE, &is, + sizeof(is), &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + ASSERT_EQ(ind_len, sizeof(is)); -TEST_F(ConvertSQL2C_Interval, Integer2Interval_unsupported_HYC00) { + SQL_INTERVAL_STRUCT is2 = {0}; + is2.interval_type = SQL_IS_MINUTE; + is2.intval.day_second.minute = abs(atoi(SQL_VAL)); + is2.interval_sign = SQL_VAL[0] == '-' ? SQL_TRUE : SQL_FALSE; + ASSERT_TRUE(memcmp(&is, &is2, sizeof(is)) == 0); +} -#undef SQL_VAL -#undef SQL -#define SQL_VAL "1" -#define SQL "select " SQL_VAL +TEST_F(ConvertSQL2C_Interval, Byte2Interval_second) +{ +# undef SQL_VAL +# undef SQL +# define SQL_VAL "-26" +# define SQL "SELECT " SQL_VAL - const char json_answer[] = "\ + const char json_answer[] = "\ {\ \"columns\": [\ - {\"name\": \"select " SQL "\", \"type\": \"integer\"}\ + {\"name\": \"" SQL "\", \"type\": \"Byte\"}\ ],\ \"rows\": [\ [" SQL_VAL "]\ ]\ }\ "; - prepareAndBind(json_answer); + prepareStatement(json_answer); + + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_INTERVAL_SECOND, &is, + sizeof(is), &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + ASSERT_EQ(ind_len, sizeof(is)); - ret = SQLFetch(stmt); - ASSERT_FALSE(SQL_SUCCEEDED(ret)); - assertState(L"HYC00"); + SQL_INTERVAL_STRUCT is2 = {0}; + is2.interval_type = SQL_IS_SECOND; + is2.intval.day_second.second = abs(atoi(SQL_VAL)); + is2.interval_sign = SQL_VAL[0] == '-' ? SQL_TRUE : SQL_FALSE; + ASSERT_TRUE(memcmp(&is, &is2, sizeof(is)) == 0); } +TEST_F(ConvertSQL2C_Interval, Float2Interval_violation_07006) +{ +# undef SQL_VAL +# undef SQL +# define SQL_VAL "-26" +# define SQL "SELECT " SQL_VAL + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"Float\"}\ + ],\ + \"rows\": [\ + [" SQL_VAL "]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_INTERVAL_SECOND, &is, + sizeof(is), &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); -TEST_F(ConvertSQL2C_Interval, Integer2Interval_violation_07006) { + ret = SQLFetch(stmt); + ASSERT_FALSE(SQL_SUCCEEDED(ret)); + assertState(L"07006"); +} -#undef SQL_VAL -#undef SQL -#define SQL_VAL "1.1" -#define SQL "select " SQL_VAL +TEST_F(ConvertSQL2C_Interval, Integer2Interval_multi_violation_07006) +{ +# undef SQL_VAL +# undef SQL +# define SQL_VAL "1" +# define SQL "SELECT " SQL_VAL - const char json_answer[] = "\ + const char json_answer[] = "\ {\ \"columns\": [\ - {\"name\": \"select " SQL "\", \"type\": \"double\"}\ + {\"name\": \"" SQL "\", \"type\": \"integer\"}\ ],\ \"rows\": [\ [" SQL_VAL "]\ ]\ }\ "; - prepareAndBind(json_answer); + prepareStatement(json_answer); + + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_INTERVAL_DAY_TO_HOUR, &is, + sizeof(is), &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_FALSE(SQL_SUCCEEDED(ret)); + assertState(L"07006"); +} + +TEST_F(ConvertSQL2C_Interval, Interval_day2Interval_hour_violation_07006) +{ +# undef SQL_VAL +# undef SQL +# define SQL_VAL "{INTERVAL '1' DAY}" +# define SQL "SELECT " SQL_VAL + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"INTERVAL_DAY\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_INTERVAL_HOUR, &is, + sizeof(is), &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_FALSE(SQL_SUCCEEDED(ret)); + assertState(L"07006"); +} + +TEST_F(ConvertSQL2C_Interval, Text2Interval_C_violation_07006) +{ +# undef SQL_VAL +# undef SQL +# define SQL_VAL "INTERVAL '1' DAY" +# define SQL "SELECT " SQL_VAL + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"text\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_INTERVAL_HOUR, &is, + sizeof(is), &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_FALSE(SQL_SUCCEEDED(ret)); + assertState(L"07006"); +} + +TEST_F(ConvertSQL2C_Interval, Text2Interval_second_no_fraction) +{ +# undef SQL_VAL +# undef SQL +# define SQL_VAL "INTERVAL -'1' SECOND" +# define SQL "SELECT " SQL_VAL + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"text\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_INTERVAL_SECOND, &is, sizeof(is), + &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + ASSERT_TRUE(is.interval_type == SQL_IS_SECOND); + ASSERT_TRUE(is.interval_sign == SQL_TRUE); + ASSERT_TRUE(is.intval.day_second.second == 1); + ASSERT_TRUE(is.intval.day_second.fraction == 0); +} + +TEST_F(ConvertSQL2C_Interval, Text2Interval_second_with_fraction) +{ +# undef SQL_VAL +# undef SQL +# define SQL_VAL "INTERVAL 1.0004 SECOND" +# define SQL "SELECT " SQL_VAL + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"text\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_INTERVAL_SECOND, &is, sizeof(is), + &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + ASSERT_TRUE(is.interval_type == SQL_IS_SECOND); + ASSERT_TRUE(is.interval_sign == SQL_FALSE); + ASSERT_TRUE(is.intval.day_second.second == 1); + ASSERT_TRUE(is.intval.day_second.fraction == 400); // default precision: 6 +} + +TEST_F(ConvertSQL2C_Interval, Text2Interval_second_dot_no_fraction) +{ +# undef SQL_VAL +# undef SQL +# define SQL_VAL "INTERVAL 1. SECOND" +# define SQL "SELECT " SQL_VAL + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"text\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_INTERVAL_SECOND, &is, sizeof(is), + &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + ASSERT_TRUE(is.interval_type == SQL_IS_SECOND); + ASSERT_TRUE(is.interval_sign == SQL_FALSE); + ASSERT_TRUE(is.intval.day_second.second == 1); +} + +TEST_F(ConvertSQL2C_Interval, Text2Interval_second_fraction_trunc) +{ +# undef SQL_VAL +# undef SQL +# define SQL_VAL "INTERVAL 1.0123456789 SECOND" +# define SQL "SELECT " SQL_VAL + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"text\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_INTERVAL_SECOND, &is, sizeof(is), + &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(ret = SQL_SUCCESS_WITH_INFO); + ASSERT_TRUE(is.interval_type == SQL_IS_SECOND); + ASSERT_TRUE(is.interval_sign == SQL_FALSE); + ASSERT_TRUE(is.intval.day_second.second == 1); + ASSERT_TRUE(is.intval.day_second.fraction == 12345); // default precision: 6 +} + +TEST_F(ConvertSQL2C_Interval, Text2Interval_year) +{ +# undef SQL_VAL +# undef SQL +# define SQL_VAL "INTERVAL -'1' YEAR" +# define SQL "SELECT " SQL_VAL + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"text\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_INTERVAL_YEAR, &is, sizeof(is), + &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + ASSERT_TRUE(is.interval_type == SQL_IS_YEAR); + ASSERT_TRUE(is.interval_sign == SQL_TRUE); + ASSERT_TRUE(is.intval.year_month.year == 1); +} + +TEST_F(ConvertSQL2C_Interval, Text2Interval_year_to_month) +{ +# undef SQL_VAL +# undef SQL +# define SQL_VAL "INTERVAL -'22 - 11' YEAR TO MoNtH" +# define SQL "SELECT " SQL_VAL + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"text\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_INTERVAL_YEAR_TO_MONTH, &is, + sizeof(is), &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + ASSERT_TRUE(is.interval_type == SQL_IS_YEAR_TO_MONTH); + ASSERT_TRUE(is.interval_sign == SQL_TRUE); + ASSERT_TRUE(is.intval.year_month.year == 22); + ASSERT_TRUE(is.intval.year_month.month == 11); +} + +TEST_F(ConvertSQL2C_Interval, Text2Interval_year_to_month_ivl_prec_22018) +{ +# undef SQL_VAL +# undef SQL +# define SQL_VAL "INTERVAL -'202 - 11' YEAR TO MoNtH" +# define SQL "SELECT " SQL_VAL + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"text\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_INTERVAL_YEAR_TO_MONTH, &is, + sizeof(is), &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_FALSE(SQL_SUCCEEDED(ret)); // default interval precision: 2 + assertState(L"22018"); +} + +TEST_F(ConvertSQL2C_Interval, Text2Interval_year_to_month_non_lead_22015) +{ +# undef SQL_VAL +# undef SQL +# define SQL_VAL "INTERVAL -'22 - 12' YEAR TO MoNtH" +# define SQL "SELECT " SQL_VAL + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"text\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_INTERVAL_YEAR_TO_MONTH, &is, + sizeof(is), &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_FALSE(SQL_SUCCEEDED(ret)); // month is over limit of 11 + assertState(L"22015"); +} + +TEST_F(ConvertSQL2C_Interval, Text2Interval_day_to_hour) +{ +# undef SQL_VAL +# undef SQL +# define SQL_VAL "INTERVAL - 22 12 day to HOUR" +# define SQL "SELECT " SQL_VAL + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"text\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_INTERVAL_DAY_TO_HOUR, &is, + sizeof(is), &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + ASSERT_TRUE(is.interval_type == SQL_IS_DAY_TO_HOUR); + ASSERT_TRUE(is.interval_sign == SQL_TRUE); + ASSERT_TRUE(is.intval.day_second.day == 22); + ASSERT_TRUE(is.intval.day_second.hour == 12); +} + +TEST_F(ConvertSQL2C_Interval, Text2Interval_day_to_hour_non_lead_22015) +{ +# undef SQL_VAL +# undef SQL +# define SQL_VAL "INTERVAL - 22 24 day to HOUR" +# define SQL "SELECT " SQL_VAL + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"text\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_INTERVAL_DAY_TO_HOUR, &is, + sizeof(is), &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_FALSE(SQL_SUCCEEDED(ret)); + assertState(L"22015"); +} + +TEST_F(ConvertSQL2C_Interval, Text2Interval_day_to_minute) +{ +# undef SQL_VAL +# undef SQL +# define SQL_VAL "INTERVAL - 22 12:59 DAY TO MINUTE" +# define SQL "SELECT " SQL_VAL + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"text\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_INTERVAL_DAY_TO_MINUTE, &is, + sizeof(is), &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + ASSERT_TRUE(is.interval_type == SQL_IS_DAY_TO_MINUTE); + ASSERT_TRUE(is.interval_sign == SQL_TRUE); + ASSERT_TRUE(is.intval.day_second.day == 22); + ASSERT_TRUE(is.intval.day_second.hour == 12); + ASSERT_TRUE(is.intval.day_second.minute == 59); +} + +TEST_F(ConvertSQL2C_Interval, Text2Interval_day_to_minute_non_lead_22015) +{ +# undef SQL_VAL +# undef SQL +# define SQL_VAL "{INTERVAL - '22 12:61' DAY TO MINUTE}" +# define SQL "SELECT " SQL_VAL + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"text\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_INTERVAL_DAY_TO_MINUTE, &is, + sizeof(is), &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); - ret = SQLFetch(stmt); - ASSERT_FALSE(SQL_SUCCEEDED(ret)); - assertState(L"07006"); + ret = SQLFetch(stmt); + ASSERT_FALSE(SQL_SUCCEEDED(ret)); + assertState(L"22015"); } +TEST_F(ConvertSQL2C_Interval, Text2Interval_day_to_second) +{ +# undef SQL_VAL +# undef SQL +# define SQL_VAL "INTERVAL - 22 12:59:58 DAY TO SECOND" +# define SQL "SELECT " SQL_VAL + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"text\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_INTERVAL_DAY_TO_SECOND, &is, + sizeof(is), &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + ASSERT_TRUE(is.interval_type == SQL_IS_DAY_TO_SECOND); + ASSERT_TRUE(is.interval_sign == SQL_TRUE); + ASSERT_TRUE(is.intval.day_second.day == 22); + ASSERT_TRUE(is.intval.day_second.hour == 12); + ASSERT_TRUE(is.intval.day_second.minute == 59); + ASSERT_TRUE(is.intval.day_second.second == 58); +} + +TEST_F(ConvertSQL2C_Interval, Text2Interval_day_to_second_non_lead_22015) +{ +# undef SQL_VAL +# undef SQL +# define SQL_VAL "INTERVAL - 22 12:59:68 DAY TO SECOND" +# define SQL "SELECT " SQL_VAL + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"text\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_INTERVAL_DAY_TO_SECOND, &is, + sizeof(is), &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_FALSE(SQL_SUCCEEDED(ret)); + assertState(L"22015"); +} + +TEST_F(ConvertSQL2C_Interval, Text2Interval_day_to_second_fractions) +{ +# undef SQL_VAL +# undef SQL +# define SQL_VAL "INTERVAL -'25 23:59:59.999' DAY TO SECOND" +# define SQL "SELECT " SQL_VAL + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"text\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_INTERVAL_DAY_TO_SECOND, &is, + sizeof(is), &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + ASSERT_TRUE(is.interval_type == SQL_IS_DAY_TO_SECOND); + ASSERT_TRUE(is.interval_sign == SQL_TRUE); + ASSERT_TRUE(is.intval.day_second.day == 25); + ASSERT_TRUE(is.intval.day_second.hour == 23); + ASSERT_TRUE(is.intval.day_second.minute == 59); + ASSERT_TRUE(is.intval.day_second.second == 59); + ASSERT_TRUE(is.intval.day_second.fraction == 999000); // def precision: 6 +} + +TEST_F(ConvertSQL2C_Interval, Text2Interval_hour_to_minute) +{ +# undef SQL_VAL +# undef SQL +# define SQL_VAL "INTERVAL -'61:59' HOUR TO MINUTE" +# define SQL "SELECT " SQL_VAL + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"text\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_INTERVAL_HOUR_TO_MINUTE, &is, + sizeof(is), &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + ASSERT_TRUE(is.interval_type == SQL_IS_HOUR_TO_MINUTE); + ASSERT_TRUE(is.interval_sign == SQL_TRUE); + ASSERT_TRUE(is.intval.day_second.hour == 61); + ASSERT_TRUE(is.intval.day_second.minute == 59); +} + +TEST_F(ConvertSQL2C_Interval, Text2Interval_hour_to_minute_non_lead_22015) +{ +# undef SQL_VAL +# undef SQL +# define SQL_VAL "INTERVAL -'61:69' HOUR TO MINUTE" +# define SQL "SELECT " SQL_VAL + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"text\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_INTERVAL_HOUR_TO_MINUTE, &is, + sizeof(is), &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_FALSE(SQL_SUCCEEDED(ret)); + assertState(L"22015"); +} + +TEST_F(ConvertSQL2C_Interval, Text2Interval_hour_to_second_fraction) +{ +# undef SQL_VAL +# undef SQL +# define SQL_VAL "INTERVAL -'61:59:58.999' HOUR TO SECOND" +# define SQL "SELECT " SQL_VAL + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"text\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_INTERVAL_HOUR_TO_SECOND, &is, + sizeof(is), &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + ASSERT_TRUE(is.interval_type == SQL_IS_HOUR_TO_SECOND); + ASSERT_TRUE(is.interval_sign == SQL_TRUE); + ASSERT_TRUE(is.intval.day_second.hour == 61); + ASSERT_TRUE(is.intval.day_second.minute == 59); + ASSERT_TRUE(is.intval.day_second.second == 58); + ASSERT_TRUE(is.intval.day_second.fraction == 999000); // def precision: 6 +} + +TEST_F(ConvertSQL2C_Interval, Text2Interval_hour_to_second_non_lead_22015) +{ +# undef SQL_VAL +# undef SQL +# define SQL_VAL "INTERVAL -'61:61:58.999' HOUR TO SECOND" +# define SQL "SELECT " SQL_VAL + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"text\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_INTERVAL_HOUR_TO_SECOND, &is, + sizeof(is), &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_FALSE(SQL_SUCCEEDED(ret)); + assertState(L"22015"); +} + +TEST_F(ConvertSQL2C_Interval, Text2Interval_minute_to_second_fraction) +{ +# undef SQL_VAL +# undef SQL +# define SQL_VAL "INTERVAL -'61:59.999' MINUTE TO SECOND" +# define SQL "SELECT " SQL_VAL + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"text\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_INTERVAL_MINUTE_TO_SECOND, &is, + sizeof(is), &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + ASSERT_TRUE(is.interval_type == SQL_IS_MINUTE_TO_SECOND); + ASSERT_TRUE(is.interval_sign == SQL_TRUE); + ASSERT_TRUE(is.intval.day_second.minute == 61); + ASSERT_TRUE(is.intval.day_second.second == 59); + ASSERT_TRUE(is.intval.day_second.fraction == 999000); // def precision: 6 +} + +TEST_F(ConvertSQL2C_Interval, Iso86012Interval_year) +{ +# undef SQL_VAL +# undef SQL +# define SQL_VAL "P2Y" +# define SQL "SELECT " SQL_VAL + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"INTERVAL_YEAR\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_INTERVAL_YEAR, &is, + sizeof(is), &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + ASSERT_TRUE(is.interval_type == SQL_IS_YEAR); + ASSERT_TRUE(is.interval_sign == SQL_FALSE); + ASSERT_TRUE(is.intval.year_month.year == 2); +} + +TEST_F(ConvertSQL2C_Interval, Iso86012Interval_month) +{ +# undef SQL_VAL +# undef SQL +# define SQL_VAL "P-23M" +# define SQL "SELECT " SQL_VAL + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"INTERVAL_MONTH\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_INTERVAL_MONTH, &is, + sizeof(is), &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + ASSERT_TRUE(is.interval_type == SQL_IS_MONTH); + ASSERT_TRUE(is.interval_sign == SQL_TRUE); + ASSERT_TRUE(is.intval.year_month.month == 23); +} + +TEST_F(ConvertSQL2C_Interval, Iso86012Interval_year_to_month) +{ +# undef SQL_VAL +# undef SQL +# define SQL_VAL "P-2Y-3M" +# define SQL "SELECT " SQL_VAL + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"INTERVAL_YEAR_TO_MONTH\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_INTERVAL_YEAR_TO_MONTH, &is, + sizeof(is), &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + ASSERT_TRUE(is.interval_type == SQL_IS_YEAR_TO_MONTH); + ASSERT_TRUE(is.interval_sign == SQL_TRUE); + ASSERT_TRUE(is.intval.year_month.year == 2); + ASSERT_TRUE(is.intval.year_month.month == 3); +} + +TEST_F(ConvertSQL2C_Interval, Iso86012Interval_day_to_hour_day_compounding) +{ +# undef SQL_VAL +# undef SQL +# define SQL_VAL "PT25H" // = P1DT1H +# define SQL "SELECT " SQL_VAL + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"INTERVAL_DAY_TO_HOUR\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_INTERVAL_DAY_TO_HOUR, &is, + sizeof(is), &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + ASSERT_TRUE(is.interval_type == SQL_IS_DAY_TO_HOUR); + ASSERT_TRUE(is.interval_sign == SQL_FALSE); + ASSERT_TRUE(is.intval.day_second.day == 1); + ASSERT_TRUE(is.intval.day_second.hour == 1); +} + +TEST_F(ConvertSQL2C_Interval, Iso86012Interval_day_to_hour) +{ +# undef SQL_VAL +# undef SQL +# define SQL_VAL "P33DT22H" +# define SQL "SELECT " SQL_VAL + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"INTERVAL_DAY_TO_HOUR\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_INTERVAL_DAY_TO_HOUR, &is, + sizeof(is), &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + ASSERT_TRUE(is.interval_type == SQL_IS_DAY_TO_HOUR); + ASSERT_TRUE(is.interval_sign == SQL_FALSE); + ASSERT_TRUE(is.intval.day_second.day == 33); + ASSERT_TRUE(is.intval.day_second.hour == 22); +} + +TEST_F(ConvertSQL2C_Interval, Iso86012Interval_day_to_minute) +{ +# undef SQL_VAL +# undef SQL +# define SQL_VAL "P33DT22H44M" +# define SQL "SELECT " SQL_VAL + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"INTERVAL_DAY_TO_MINUTE\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_INTERVAL_DAY_TO_MINUTE, &is, + sizeof(is), &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + ASSERT_TRUE(is.interval_type == SQL_IS_DAY_TO_MINUTE); + ASSERT_TRUE(is.interval_sign == SQL_FALSE); + ASSERT_TRUE(is.intval.day_second.day == 33); + ASSERT_TRUE(is.intval.day_second.hour == 22); + ASSERT_TRUE(is.intval.day_second.minute == 44); +} + +TEST_F(ConvertSQL2C_Interval, Iso86012Interval_day_to_second) +{ +# undef SQL_VAL +# undef SQL +# define SQL_VAL "P33DT22H44M55.666S" +# define SQL "SELECT " SQL_VAL + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"INTERVAL_DAY_TO_SECOND\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_INTERVAL_DAY_TO_SECOND, &is, + sizeof(is), &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + ASSERT_TRUE(is.interval_type == SQL_IS_DAY_TO_SECOND); + ASSERT_TRUE(is.interval_sign == SQL_FALSE); + ASSERT_TRUE(is.intval.day_second.day == 33); + ASSERT_TRUE(is.intval.day_second.hour == 22); + ASSERT_TRUE(is.intval.day_second.minute == 44); + ASSERT_TRUE(is.intval.day_second.second == 55); + ASSERT_TRUE(is.intval.day_second.fraction == 666000); // def prec: 6 +} + +TEST_F(ConvertSQL2C_Interval, Iso86012Interval_hour_to_minute) +{ +# undef SQL_VAL +# undef SQL +# define SQL_VAL "PT-22H-44M" +# define SQL "SELECT " SQL_VAL + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"INTERVAL_HOUR_TO_MINUTE\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_INTERVAL_HOUR_TO_MINUTE, &is, + sizeof(is), &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + ASSERT_TRUE(is.interval_type == SQL_IS_HOUR_TO_MINUTE); + ASSERT_TRUE(is.interval_sign == SQL_TRUE); + ASSERT_TRUE(is.intval.day_second.hour == 22); + ASSERT_TRUE(is.intval.day_second.minute == 44); +} + +TEST_F(ConvertSQL2C_Interval, Iso86012Interval_hour_to_second) +{ +# undef SQL_VAL +# undef SQL +# define SQL_VAL "PT22H44M55.666S" +# define SQL "SELECT " SQL_VAL + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"INTERVAL_HOUR_TO_SECOND\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_INTERVAL_HOUR_TO_SECOND, &is, + sizeof(is), &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + ASSERT_TRUE(is.interval_type == SQL_IS_HOUR_TO_SECOND); + ASSERT_TRUE(is.interval_sign == SQL_FALSE); + ASSERT_TRUE(is.intval.day_second.hour == 22); + ASSERT_TRUE(is.intval.day_second.minute == 44); + ASSERT_TRUE(is.intval.day_second.second == 55); + ASSERT_TRUE(is.intval.day_second.fraction == 666000); // def prec: 6 +} + +TEST_F(ConvertSQL2C_Interval, Iso86012Interval_minute_to_second) +{ +# undef SQL_VAL +# undef SQL +# define SQL_VAL "PT44M.666S" +# define SQL "SELECT " SQL_VAL + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"INTERVAL_MINUTE_TO_SECOND\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_INTERVAL_MINUTE_TO_SECOND, &is, + sizeof(is), &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + ASSERT_TRUE(is.interval_type == SQL_IS_MINUTE_TO_SECOND); + ASSERT_TRUE(is.interval_sign == SQL_FALSE); + ASSERT_TRUE(is.intval.day_second.minute == 44); + ASSERT_TRUE(is.intval.day_second.second == 0); + ASSERT_TRUE(is.intval.day_second.fraction == 666000); // def prec: 6 +} + +TEST_F(ConvertSQL2C_Interval, Iso86012Interval_extra_field_22018) +{ +# undef SQL_VAL +# undef SQL +# define SQL_VAL "PT1H44M55.666S" // 1H is not a MIN-TO-SEC +# define SQL "SELECT " SQL_VAL + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"INTERVAL_MINUTE_TO_SECOND\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_INTERVAL_MINUTE_TO_SECOND, &is, + sizeof(is), &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_FALSE(SQL_SUCCEEDED(ret)); + assertState(L"22018"); +} + +TEST_F(ConvertSQL2C_Interval, Iso86012Interval_invalid_char_22018) +{ +# undef SQL_VAL +# undef SQL +# define SQL_VAL "PT1H4x4M55.666S" // x - invalid +# define SQL "SELECT " SQL_VAL + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"INTERVAL_MINUTE_TO_SECOND\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_INTERVAL_MINUTE_TO_SECOND, &is, + sizeof(is), &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_FALSE(SQL_SUCCEEDED(ret)); + assertState(L"22018"); +} + +TEST_F(ConvertSQL2C_Interval, Iso86012Interval_repeated_valid_22018) +{ +# undef SQL_VAL +# undef SQL +# define SQL_VAL "PT1H44MM55.666S" // 44MM, 2nd M +# define SQL "SELECT " SQL_VAL + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"INTERVAL_MINUTE_TO_SECOND\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_INTERVAL_MINUTE_TO_SECOND, &is, + sizeof(is), &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_FALSE(SQL_SUCCEEDED(ret)); + assertState(L"22018"); +} + +TEST_F(ConvertSQL2C_Interval, Iso86012Interval_plus_minus_22018) +{ +# undef SQL_VAL +# undef SQL +# define SQL_VAL "PT-44M+55.666S" // +M -S +# define SQL "SELECT " SQL_VAL + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"INTERVAL_MINUTE_TO_SECOND\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_INTERVAL_MINUTE_TO_SECOND, &is, + sizeof(is), &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_FALSE(SQL_SUCCEEDED(ret)); + assertState(L"22018"); +} + +TEST_F(ConvertSQL2C_Interval, Iso8601_year2WChar) +{ +# undef SQL_VAL +# undef SQL +# define SQL_VAL "P1Y" +# define SQL "SELECT " SQL_VAL + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"INTERVAL_YEAR\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_WCHAR, wbuff, sizeof(wbuff), + &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + EXPECT_EQ(ind_len, sizeof(SQLWCHAR) * (sizeof("1") - /*\0*/1)); + ASSERT_STREQ(wbuff, L"1"); +} + +TEST_F(ConvertSQL2C_Interval, Iso8601_year_to_month2WChar) +{ +# undef SQL_VAL +# undef SQL +# define SQL_VAL "P-1Y-2M" +# define SQL "SELECT " SQL_VAL + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"INTERVAL_YEAR_TO_MONTH\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_WCHAR, wbuff, sizeof(wbuff), + &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + EXPECT_EQ(ind_len, sizeof(SQLWCHAR) * (sizeof("-1-2") - /*\0*/1)); + ASSERT_STREQ(wbuff, L"-1-2"); +} + +TEST_F(ConvertSQL2C_Interval, Iso8601_day_to_hour2WChar) +{ +# undef SQL_VAL +# undef SQL +# define SQL_VAL "P1DT2h" +# define SQL "SELECT " SQL_VAL + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"INTERVAL_DAY_TO_HOUR\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_WCHAR, wbuff, sizeof(wbuff), + &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + EXPECT_EQ(ind_len, sizeof(SQLWCHAR) * (sizeof("1 2") - /*\0*/1)); + ASSERT_STREQ(wbuff, L"1 2"); +} + +TEST_F(ConvertSQL2C_Interval, Iso8601_day_to_minute2WChar) +{ +# undef SQL_VAL +# undef SQL +# define SQL_VAL "P1DT2H3M" +# define SQL "SELECT " SQL_VAL + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"INTERVAL_DAY_TO_MINUTE\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_WCHAR, wbuff, sizeof(wbuff), + &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + EXPECT_EQ(ind_len, sizeof(SQLWCHAR) * (sizeof("1 2:3") - /*\0*/1)); + ASSERT_STREQ(wbuff, L"1 2:3"); +} + +TEST_F(ConvertSQL2C_Interval, Iso8601_day_to_second2WChar) +{ +# undef SQL_VAL +# undef SQL +# define SQL_VAL "P1DT2H3M4S" +# define SQL "SELECT " SQL_VAL + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"INTERVAL_DAY_TO_SECOND\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_WCHAR, wbuff, sizeof(wbuff), + &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + EXPECT_EQ(ind_len, sizeof(SQLWCHAR) * (sizeof("1 2:3:4") - /*\0*/1)); + ASSERT_STREQ(wbuff, L"1 2:3:4"); +} + +TEST_F(ConvertSQL2C_Interval, Iso8601_hour_to_second2WChar) +{ +# undef SQL_VAL +# undef SQL +# define SQL_VAL "PT2H3M4.5555S" +# define SQL "SELECT " SQL_VAL + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"INTERVAL_HOUR_TO_SECOND\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_WCHAR, wbuff, sizeof(wbuff), + &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + SQLHDESC ard; + ret = SQLGetStmtAttr(stmt, SQL_ATTR_APP_ROW_DESC, &ard, 0, NULL); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + ret = SQLSetDescField(ard, 1, SQL_DESC_PRECISION, (SQLPOINTER)3, 0); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + // data ptr is reset by direct desc field setting + ret = SQLSetDescField(ard, 1, SQL_DESC_DATA_PTR, (SQLPOINTER)wbuff, 0); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + EXPECT_EQ(ind_len, sizeof(SQLWCHAR) * (sizeof("2:3:4.555") - /*\0*/1)); + ASSERT_STREQ(wbuff, L"2:3:4.555"); +} + +TEST_F(ConvertSQL2C_Interval, Iso8601_minute_to_second2Char) +{ +# undef SQL_VAL +# undef SQL +# define SQL_VAL "PT3M4.5555S" +# define SQL "SELECT " SQL_VAL + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"INTERVAL_MINUTE_TO_SECOND\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_CHAR, buff, sizeof(buff), + &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + SQLHDESC ard; + ret = SQLGetStmtAttr(stmt, SQL_ATTR_APP_ROW_DESC, &ard, 0, NULL); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + ret = SQLSetDescField(ard, 1, SQL_DESC_PRECISION, (SQLPOINTER)4, 0); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + // data ptr is reset by direct desc field setting + ret = SQLSetDescField(ard, 1, SQL_DESC_DATA_PTR, (SQLPOINTER)buff, 0); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + EXPECT_EQ(ind_len, sizeof("3:4.5555") - /*\0*/1); + ASSERT_STREQ((char *)buff, "3:4.5555"); +} + +TEST_F(ConvertSQL2C_Interval, Iso8601_hour_to_minute2Char) +{ +# undef SQL_VAL +# undef SQL +# define SQL_VAL "PT2H3M" +# define SQL "SELECT " SQL_VAL + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"INTERVAL_HOUR_TO_MINUTE\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_CHAR, buff, sizeof(buff), + &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + SQLHDESC ard; + ret = SQLGetStmtAttr(stmt, SQL_ATTR_APP_ROW_DESC, &ard, 0, NULL); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + ret = SQLSetDescField(ard, 1, SQL_DESC_PRECISION, (SQLPOINTER)4, 0); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + // data ptr is reset by direct desc field setting + ret = SQLSetDescField(ard, 1, SQL_DESC_DATA_PTR, (SQLPOINTER)buff, 0); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + EXPECT_EQ(ind_len, sizeof("2:3") - /*\0*/1); + ASSERT_STREQ((char *)buff, "2:3"); +} } // test namespace +/* set noet fenc=utf-8 ff=dos sts=0 sw=4 ts=4 tw=78 */ From a8160bc003b38da3d5131712b931865267cd259d Mon Sep 17 00:00:00 2001 From: Bogdan Pintea Date: Mon, 17 Dec 2018 01:36:20 +0100 Subject: [PATCH 2/3] add interval parameter support This adds support for parameters of type interval. The driver will perform the conversion between C values to interval SQL values within the allowed conversion matrix. --- driver/convert.c | 516 +++++++++++++++++++------ driver/convert.h | 2 + driver/handles.c | 9 +- driver/queries.c | 15 + test/test_conversion_c2sql_interval.cc | 278 +++++++++++++ 5 files changed, 706 insertions(+), 114 deletions(-) create mode 100644 test/test_conversion_c2sql_interval.cc diff --git a/driver/convert.c b/driver/convert.c index 5684108b..6905af31 100644 --- a/driver/convert.c +++ b/driver/convert.c @@ -47,9 +47,9 @@ RET_HDIAGS(_stmt, SQL_STATE_22003); \ } while (0) -/* maximum lenght of an interval literal (with terminator), with no field - * sanity checks: five longs with separators and sign */ -#define INTERVAL_LIT_MAX_LEN (5 * sizeof("4294967295")) +/* maximum lenght of an interval literal (with terminator; both ISO and SQL), + * with no field sanity checks: five longs with separators and sign */ +#define INTERVAL_VAL_MAX_LEN (5 * sizeof("4294967295")) #if (0x0300 <= ODBCVER) # define ESSQL_TYPE_MIN SQL_GUID @@ -1755,21 +1755,21 @@ static SQLRETURN wstr_to_time(esodbc_rec_st *arec, esodbc_rec_st *irec, return SQL_SUCCESS; } -static inline SQLRETURN adjust_to_precision(esodbc_rec_st *arec, +static inline SQLRETURN adjust_to_precision(esodbc_rec_st *rec, SQLUBIGINT *val, SQLSMALLINT prec_have) { - esodbc_stmt_st *stmt = arec->desc->hdr.stmt; + esodbc_stmt_st *stmt = rec->desc->hdr.stmt; - if (prec_have < arec->precision) { + if (prec_have < rec->precision) { DBGH(stmt, "augmenting value %llu with %hd digits.", *val, - arec->precision - prec_have); + rec->precision - prec_have); /* no overflow check: the target precision should have been checked */ - *val *= pow10(arec->precision - prec_have); + *val *= pow10(rec->precision - prec_have); return SQL_SUCCESS; } else { DBGH(stmt, "truncating value %llu with %hd digits.", *val, - prec_have - arec->precision); - *val /= pow10(prec_have - arec->precision); + prec_have - rec->precision); + *val /= pow10(prec_have - rec->precision); RET_HDIAGS(stmt, SQL_STATE_01S07); } } @@ -1857,7 +1857,7 @@ static SQLRETURN parse_interval_iso8601(esodbc_rec_st *arec, BOOL has_fraction; enum {ST_UNINITED, ST_PERIOD, ST_TIME, ST_NUMBER} state, saved; uint16_t fields_bm; /* read fields bit mask */ - static uint16_t type2bm[] = { + static const uint16_t type2bm[] = { 1 << SQL_IS_YEAR, 1 << SQL_IS_MONTH, 1 << SQL_IS_DAY, @@ -2052,10 +2052,10 @@ static SQLRETURN parse_interval_iso8601(esodbc_rec_st *arec, * 'wstr' is adjusted to have the field removed. * The field value is stored in 'uiptr'. * */ -static SQLRETURN parse_interval_field(esodbc_rec_st *arec, SQLUINTEGER limit, +static SQLRETURN parse_interval_field(esodbc_rec_st *rec, SQLUINTEGER limit, wstr_st *wstr, SQLUINTEGER *uiptr) { - esodbc_stmt_st *stmt = arec->desc->hdr.stmt; + esodbc_stmt_st *stmt = rec->desc->hdr.stmt; SQLUBIGINT ubint; int digits; @@ -2074,9 +2074,11 @@ static SQLRETURN parse_interval_field(esodbc_rec_st *arec, SQLUINTEGER limit, RET_HDIAGS(stmt, SQL_STATE_22015); } if (limit <= 0) { - if (arec->datetime_interval_precision < digits) { + // TODO: digits could account for leading 0s (that could wrongly match + // the condition) + if (rec->datetime_interval_precision < digits) { ERRH(stmt, "interval's set dt_interval precision (%ld) lower than " - "data's (%d).", arec->datetime_interval_precision, digits); + "data's (%d).", rec->datetime_interval_precision, digits); RET_HDIAGS(stmt, SQL_STATE_22018); } } else if (limit < ubint) { @@ -2089,15 +2091,15 @@ static SQLRETURN parse_interval_field(esodbc_rec_st *arec, SQLUINTEGER limit, return SQL_SUCCESS; } -static SQLRETURN parse_interval_second(esodbc_rec_st *arec, SQLUINTEGER limit, +static SQLRETURN parse_interval_second(esodbc_rec_st *rec, SQLUINTEGER limit, wstr_st *wstr, SQL_INTERVAL_STRUCT *ivl) { - esodbc_stmt_st *stmt = arec->desc->hdr.stmt; + esodbc_stmt_st *stmt = rec->desc->hdr.stmt; size_t digits; SQLRETURN ret; unsigned long long ull; - ret = parse_interval_field(arec, limit, wstr, + ret = parse_interval_field(rec, limit, wstr, &ivl->intval.day_second.second); if (! SQL_SUCCEEDED(ret)) { return ret; @@ -2120,7 +2122,7 @@ static SQLRETURN parse_interval_second(esodbc_rec_st *arec, SQLUINTEGER limit, return SQL_SUCCESS; } digits = wstr->cnt; - ret = parse_interval_field(arec, ULONG_MAX, wstr, + ret = parse_interval_field(rec, ULONG_MAX, wstr, &ivl->intval.day_second.fraction); if (! SQL_SUCCEEDED(ret)) { return ret; @@ -2129,7 +2131,7 @@ static SQLRETURN parse_interval_second(esodbc_rec_st *arec, SQLUINTEGER limit, assert(digits < SHRT_MAX); } ull = ivl->intval.day_second.fraction; - ret = adjust_to_precision(arec, &ull, (SQLSMALLINT)digits); + ret = adjust_to_precision(rec, &ull, (SQLSMALLINT)digits); assert(ull < ULONG_MAX); /* interval seconds precision limited to 9 */ ivl->intval.day_second.fraction = (SQLUINTEGER)ull; @@ -2140,7 +2142,7 @@ static SQLRETURN parse_interval_second(esodbc_rec_st *arec, SQLUINTEGER limit, } else { DBGH(stmt, "single component 'second' has value: %lu.%lu(%hd).", ivl->intval.day_second.second, - ivl->intval.day_second.fraction, arec->precision); + ivl->intval.day_second.fraction, rec->precision); } return ret; } @@ -2148,10 +2150,10 @@ static SQLRETURN parse_interval_second(esodbc_rec_st *arec, SQLUINTEGER limit, /* TODO: use rec's .datetime_interval_precision and .precision through a * "general" get_col_xxx() function (: decdigits or similar) */ /* parses the `` in an interval literal */ -static SQLRETURN parse_interval_literal_value(esodbc_rec_st *arec, +static SQLRETURN parse_interval_literal_value(esodbc_rec_st *rec, wstr_st *wstr, SQL_INTERVAL_STRUCT *ivl) { - esodbc_stmt_st *stmt = arec->desc->hdr.stmt; + esodbc_stmt_st *stmt = rec->desc->hdr.stmt; SQLUINTEGER *uiptr; SQLRETURN ret; @@ -2159,9 +2161,9 @@ static SQLRETURN parse_interval_literal_value(esodbc_rec_st *arec, * entire string has been parsed, if, `_at_the_end` is set (for single * field intervals). * Returns on error. */ -# define PARSE_IVL_FLD_OR_RET(arec, limit, wstr, uiptr, _at_the_end) \ +# define PARSE_IVL_FLD_OR_RET(rec, limit, wstr, uiptr, _at_the_end) \ do { \ - ret = parse_interval_field(arec, limit, wstr, uiptr); \ + ret = parse_interval_field(rec, limit, wstr, uiptr); \ if (! SQL_SUCCEEDED(ret)) { \ return ret; \ } \ @@ -2184,20 +2186,20 @@ static SQLRETURN parse_interval_literal_value(esodbc_rec_st *arec, case SQL_IS_HOUR: uiptr = &ivl->intval.day_second.hour; break; case SQL_IS_MINUTE: uiptr = &ivl->intval.day_second.minute; break; } while (0); - PARSE_IVL_FLD_OR_RET(arec, /*limit*/0, wstr, uiptr, /*end?*/TRUE); + PARSE_IVL_FLD_OR_RET(rec, /*limit*/0, wstr, uiptr, /*end?*/TRUE); DBGH(stmt, "single component of type %d has value: %lu.", ivl->interval_type, *uiptr); break; case SQL_IS_SECOND: - ret = parse_interval_second(arec, /*limit*/0, wstr, ivl); + ret = parse_interval_second(rec, /*limit*/0, wstr, ivl); if (! SQL_SUCCEEDED(ret)) { return ret; } break; case SQL_IS_YEAR_TO_MONTH: - PARSE_IVL_FLD_OR_RET(arec, /*limit*/0, wstr, + PARSE_IVL_FLD_OR_RET(rec, /*limit*/0, wstr, &ivl->intval.year_month.year, /*end?*/FALSE); wltrim_ws(wstr); if (wstr->str[0] != L'-') { @@ -2208,17 +2210,17 @@ static SQLRETURN parse_interval_literal_value(esodbc_rec_st *arec, wstr->cnt --; } wltrim_ws(wstr); - PARSE_IVL_FLD_OR_RET(arec, /*limit: months*/11, wstr, + PARSE_IVL_FLD_OR_RET(rec, /*limit: months*/11, wstr, &ivl->intval.year_month.month, /*end?*/TRUE); break; case SQL_IS_DAY_TO_HOUR: case SQL_IS_DAY_TO_MINUTE: case SQL_IS_DAY_TO_SECOND: - PARSE_IVL_FLD_OR_RET(arec, /*limit*/0, wstr, + PARSE_IVL_FLD_OR_RET(rec, /*limit*/0, wstr, &ivl->intval.day_second.day, /*end?*/FALSE); wltrim_ws(wstr); - PARSE_IVL_FLD_OR_RET(arec, /*hour limit*/23, wstr, + PARSE_IVL_FLD_OR_RET(rec, /*hour limit*/23, wstr, &ivl->intval.day_second.hour, /*end?*/ivl->interval_type == SQL_IS_DAY_TO_HOUR); if (ivl->interval_type == SQL_IS_DAY_TO_HOUR) { @@ -2231,7 +2233,7 @@ static SQLRETURN parse_interval_literal_value(esodbc_rec_st *arec, wstr->str ++; wstr->cnt --; } - PARSE_IVL_FLD_OR_RET(arec, /*minute limit*/59, wstr, + PARSE_IVL_FLD_OR_RET(rec, /*minute limit*/59, wstr, &ivl->intval.day_second.minute, /*end?*/ivl->interval_type == SQL_IS_DAY_TO_MINUTE); if (ivl->interval_type == SQL_IS_DAY_TO_MINUTE) { @@ -2244,7 +2246,7 @@ static SQLRETURN parse_interval_literal_value(esodbc_rec_st *arec, wstr->str ++; wstr->cnt --; } - ret = parse_interval_second(arec, /*second limit*/59, wstr, ivl); + ret = parse_interval_second(rec, /*second limit*/59, wstr, ivl); if (! SQL_SUCCEEDED(ret)) { return ret; } @@ -2252,7 +2254,7 @@ static SQLRETURN parse_interval_literal_value(esodbc_rec_st *arec, case SQL_IS_HOUR_TO_MINUTE: case SQL_IS_HOUR_TO_SECOND: - PARSE_IVL_FLD_OR_RET(arec, /*limit*/0, wstr, + PARSE_IVL_FLD_OR_RET(rec, /*limit*/0, wstr, &ivl->intval.day_second.hour, /*end?*/FALSE); if (wstr->str[0] != L':') { ERRH(stmt, "invalid char as separator: `%c`.", wstr->str[0]); @@ -2263,7 +2265,7 @@ static SQLRETURN parse_interval_literal_value(esodbc_rec_st *arec, } /*no break*/ case SQL_IS_MINUTE_TO_SECOND: - PARSE_IVL_FLD_OR_RET(arec, + PARSE_IVL_FLD_OR_RET(rec, /*minute limit*/ ivl->interval_type == SQL_IS_MINUTE_TO_SECOND ? 0 : 59, wstr, &ivl->intval.day_second.minute, @@ -2278,7 +2280,7 @@ static SQLRETURN parse_interval_literal_value(esodbc_rec_st *arec, wstr->str ++; wstr->cnt --; } - ret = parse_interval_second(arec, /*second limit*/59, wstr, ivl); + ret = parse_interval_second(rec, /*second limit*/59, wstr, ivl); if (! SQL_SUCCEEDED(ret)) { return ret; } @@ -2383,10 +2385,10 @@ static SQLSMALLINT parse_interval_type(wstr_st *wstr) /* parse `INTERVAL ± '' `, each space being * optional and extendable; the entire expression can be enclosed in {} */ -static SQLRETURN parse_interval_literal(esodbc_rec_st *arec, wstr_st *wstr, +static SQLRETURN parse_interval_literal(esodbc_rec_st *rec, wstr_st *wstr, SQL_INTERVAL_STRUCT *ivl) { - esodbc_stmt_st *stmt = arec->desc->hdr.stmt; + esodbc_stmt_st *stmt = rec->desc->hdr.stmt; static const wstr_st INTERVAL = WSTR_INIT("INTERVAL"); memset(ivl, 0, sizeof(*ivl)); @@ -2443,7 +2445,7 @@ static SQLRETURN parse_interval_literal(esodbc_rec_st *arec, wstr_st *wstr, wstr->cnt -= 2; } - return parse_interval_literal_value(arec, wstr, ivl); + return parse_interval_literal_value(rec, wstr, ivl); } static SQLRETURN sql2c_interval(esodbc_rec_st *arec, @@ -2452,7 +2454,7 @@ static SQLRETURN sql2c_interval(esodbc_rec_st *arec, esodbc_stmt_st *stmt = arec->desc->hdr.stmt; SQLRETURN ret; SQL_INTERVAL_STRUCT ivl; - SQLSMALLINT ivl_type2c_type[] = { + static const SQLSMALLINT ivl_type2c_type[] = { SQL_C_INTERVAL_YEAR, /* = 1, ++ */ SQL_C_INTERVAL_MONTH, SQL_C_INTERVAL_DAY, @@ -2506,6 +2508,50 @@ static SQLRETURN sql2c_interval(esodbc_rec_st *arec, RET_HDIAGS(stmt, SQL_STATE_22018); } +/* print just the 'second[.fraction]' field of an interval */ +static size_t print_interval_sec(esodbc_rec_st *rec, SQL_INTERVAL_STRUCT *ivl, + void *dest, BOOL wide) +{ + esodbc_stmt_st *stmt = rec->desc->hdr.stmt; + wchar_t wfmt[] = L"%.0lf"; + char cfmt[] = "%.0lf"; + double dbl; + size_t res; + + if (ivl->intval.day_second.fraction && rec->precision) { + assert(ESODBC_MAX_SEC_PRECISION < 10); + assert(0 <= rec->precision && + rec->precision <= ESODBC_MAX_SEC_PRECISION); + dbl = (double)ivl->intval.day_second.fraction; + dbl /= pow10(rec->precision); + dbl += (double)ivl->intval.day_second.second; + + if (wide) { + wfmt[2] = L'0' + rec->precision; + /* printf's limits: max lenght of '.', accounted + * in buffer's max len estimation. */ + res = swprintf((wchar_t *)dest, 2 * sizeof("4294967295") + 1, + wfmt, dbl); + } else { + cfmt[2] = '0' + rec->precision; + res = snprintf((char *)dest, 2 * sizeof("4294967295") + 1, + cfmt, dbl); + } + + if (res < 0) { + ERRNH(stmt, "failed to print the 'second' component for " + "second: %lu, fraction: %lu, precision: %hd.", + ivl->intval.day_second.second, + ivl->intval.day_second.fraction, rec->precision); + return 0; + } + } else { + res = ui64tot(ivl->intval.day_second.second, dest, wide); + } + + return res; +} + /* Convert an interval struct to a SQL literal (value). * There's no reference for the sign in: * https://docs.microsoft.com/en-us/sql/odbc/reference/appendixes/interval-data-type-length @@ -2514,11 +2560,9 @@ static size_t print_interval_sql(esodbc_rec_st *arec, SQL_INTERVAL_STRUCT *ivl, SQLWCHAR *dest) { esodbc_stmt_st *stmt = arec->desc->hdr.stmt; - size_t pos; + size_t pos, res; SQLUINTEGER uint; - int res; wchar_t fmt[] = L"%.0f";; - float flt; pos = 0; if (ivl->interval_sign) { @@ -2575,27 +2619,11 @@ static size_t print_interval_sql(esodbc_rec_st *arec, SQL_INTERVAL_STRUCT *ivl, pos += ui64tot(ivl->intval.day_second.minute, dest + pos, TRUE); dest[pos ++] = L':'; case SQL_IS_SECOND: - if (ivl->intval.day_second.fraction) { - assert(ESODBC_MAX_SEC_PRECISION < 10); - assert(0 <= arec->precision && - arec->precision <= ESODBC_MAX_SEC_PRECISION); - fmt[2] = L'0' + arec->precision; - flt = (float)ivl->intval.day_second.fraction; - flt /= pow10(arec->precision); - flt += (float)ivl->intval.day_second.second; - res = swprintf(dest + pos, 2 * sizeof("4294967295") + 1, fmt, - flt); - if (res < 0) { - ERRNH(stmt, "failed to print the 'second' component for " - "second: %lu, fraction: %lu, precision: %hd.", - ivl->intval.day_second.second, - ivl->intval.day_second.fraction, arec->precision); - return 0; - } - pos += res; + res = print_interval_sec(arec, ivl, dest + pos, /*wide*/TRUE); + if (res <= 0) { + return 0; } else { - pos += ui64tot(ivl->intval.day_second.second, dest + pos, - TRUE); + pos += res; } break; @@ -2613,6 +2641,95 @@ static size_t print_interval_sql(esodbc_rec_st *arec, SQL_INTERVAL_STRUCT *ivl, return pos; } +/* Convert an interval struct to a ISO8601 value. */ +static size_t print_interval_iso8601(esodbc_rec_st *rec, + SQL_INTERVAL_STRUCT *ivl, SQLCHAR *dest) +{ + esodbc_stmt_st *stmt = rec->desc->hdr.stmt; + size_t res, pos; + BOOL t_added; + + /* Prints one interval field, if non-zero. + * Uses local vars: ivl, dest, pos, t_added */ +# define PRINT_FIELD(_ivl_field, _field_qualif, _is_time) \ + do { \ + if (ivl->intval._ivl_field) { \ + if (_is_time && !t_added) { \ + dest[pos ++] = 'T'; \ + t_added = TRUE; \ + } \ + if (ivl->interval_sign) { \ + dest[pos ++] = '-'; \ + } \ + pos += ui64tot(ivl->intval._ivl_field, dest + pos, \ + /*wide*/FALSE); \ + dest[pos ++] = _field_qualif; \ + } \ + } while (0) + + pos = 0; + dest[pos ++] = 'P'; + switch (ivl->interval_type) { + case SQL_IS_YEAR: + case SQL_IS_MONTH: + case SQL_IS_YEAR_TO_MONTH: + PRINT_FIELD(year_month.year, 'Y', /* is time comp. */FALSE); + PRINT_FIELD(year_month.month, 'M', /* is time comp. */FALSE); + if (pos <= /*leading 'P'*/1) { /* 0 interval */ + dest[pos ++] = '0'; + dest[pos ++] = 'M'; + } + break; + + case SQL_IS_DAY: + case SQL_IS_HOUR: + case SQL_IS_MINUTE: + case SQL_IS_SECOND: + case SQL_IS_DAY_TO_HOUR: + case SQL_IS_DAY_TO_MINUTE: + case SQL_IS_DAY_TO_SECOND: + case SQL_IS_HOUR_TO_MINUTE: + case SQL_IS_HOUR_TO_SECOND: + case SQL_IS_MINUTE_TO_SECOND: + // TODO: compoound year to hour, ES/SQL-style? + // (see parse_interval_iso8601 note) + PRINT_FIELD(day_second.day, 'D', /* is time comp. */FALSE); + t_added = FALSE; + PRINT_FIELD(day_second.hour, 'H', /*is time*/TRUE); + PRINT_FIELD(day_second.minute, 'M', /*is time*/TRUE); + if (ivl->intval.day_second.second | + ivl->intval.day_second.fraction) { + if (! t_added) { + dest[pos ++] = 'T'; + } + if (ivl->interval_sign) { + dest[pos ++] = '-'; + } + res = print_interval_sec(rec, ivl, dest + pos, /*wide*/FALSE); + if (res <= 0) { + return 0; + } else { + pos += res; + } + dest[pos ++] = 'S'; + } + if (pos <= /*leading 'P'*/1) { /* 0 interval */ + dest[pos ++] = 'T'; + dest[pos ++] = '0'; + dest[pos ++] = 'S'; + } + break; + + default: + BUGH(stmt, "unexpected interval type %d.", ivl->interval_type); + return 0; + } + DBGH(stmt, "interval of type %d printed as [%zu] `" LCPDL "`.", + ivl->interval_type, pos, pos, dest); + return pos; +# undef PRINT_FIELD +} + /* translate the string representation of an interval value from the ISO8601 * to SQL */ static SQLRETURN interval_iso8601_to_sql(esodbc_rec_st *arec, @@ -2636,7 +2753,7 @@ static SQLRETURN interval_iso8601_to_sql(esodbc_rec_st *arec, if (cnt <= 0) { ERRH(stmt, "sql interval printing failed for ISO8601`" LWPDL "`.", chars_0 - 1, wstr); - RET_HDIAGS(stmt, SQL_STATE_HY000); + RET_HDIAG(stmt, SQL_STATE_HY000, "internal printing failed", 0); } DBGH(arec->desc->hdr.stmt, "convered `" LWPDL "` to `" LWPDL "`.", chars_0 - 1, wstr, cnt, lit); @@ -2665,7 +2782,7 @@ SQLRETURN sql2c_string(esodbc_rec_st *arec, esodbc_rec_st *irec, wstr_st wval; double dbl; SQLWCHAR *endp; - wchar_t buff[INTERVAL_LIT_MAX_LEN + /*0*/1]; + wchar_t buff[INTERVAL_VAL_MAX_LEN + /*0*/1]; SQLRETURN ret; /* The interval strings are received from ES in ISO8601, not SQL format: @@ -2923,50 +3040,6 @@ SQLRETURN convertability_check(esodbc_stmt_st *stmt, SQLINTEGER idx, return SQL_SUCCESS; } -#if 0 -/* check if data types in returned columns are compabile with buffer types - * bound for those columns */ -SQLRETURN sql2c_convertible(esodbc_stmt_st *stmt) -{ - SQLSMALLINT i, min, ret; - esodbc_desc_st *ard, *ird; - esodbc_rec_st *arec, *irec; - - assert(stmt->hdr.dbc->es_types); - assert(STMT_HAS_RESULTSET(stmt)); - - ard = stmt->ard; - ird = stmt->ird; - - min = ard->count < ird->count ? ard->count : ird->count; - for (i = 0; i < min; i ++) { - arec = &ard->recs[i]; - if (! REC_IS_BOUND(arec)) { - /* skip not bound columns */ - continue; - } - irec = &ird->recs[i]; - - if (! ESODBC_TYPES_COMPATIBLE(irec->concise_type,arec->concise_type)) { - ERRH(stmt, "type conversion not possible on column %d: IRD: %hd, " - "ARD: %hd.", i + 1, irec->concise_type, arec->concise_type); - stmt->sql2c_conversion = CONVERSION_VIOLATION; - RET_HDIAGS(stmt, SQL_STATE_07006); - } - if (! conv_implemented(irec->concise_type, arec->concise_type)) { - ERRH(stmt, "conversion not supported on column %d types: IRD: %hd," - " ARD: %hd.", i + 1, irec->concise_type, arec->concise_type); - stmt->sql2c_conversion = CONVERSION_UNSUPPORTED; - RET_HDIAGS(stmt, SQL_STATE_HYC00); - } - } - - stmt->sql2c_conversion = CONVERSION_SUPPORTED; - DBGH(stmt, "convertibility check: OK."); - return SQL_SUCCESS; -} -#endif - /* Converts a C/W-string to a u/llong or double (dest_type?); the xstr->wide * needs to be set; * Returns success of conversion and pointer to trimmed number str @@ -3676,8 +3749,7 @@ SQLRETURN c2sql_timestamp(esodbc_rec_st *arec, esodbc_rec_st *irec, break; default: - BUGH(stmt, "can't convert SQL C type %hd to timestamp.", - get_rec_c_type(arec, irec)); + BUGH(stmt, "can't convert SQL C type %hd to timestamp.", ctype); RET_HDIAG(stmt, SQL_STATE_HY000, "bug converting parameter", 0); } @@ -3735,6 +3807,228 @@ SQLRETURN c2sql_timestamp(esodbc_rec_st *arec, esodbc_rec_st *irec, return SQL_SUCCESS; } +/* parses an interval literal string from app's char/wchar_t buffer */ +SQLRETURN c2sql_str2interval(esodbc_rec_st *arec, esodbc_rec_st *irec, + SQLSMALLINT ctype, void *data_ptr, SQL_INTERVAL_STRUCT *ivl) +{ + esodbc_stmt_st *stmt = arec->desc->hdr.stmt; + wstr_st wstr; + SQLWCHAR wbuff[128], *wptr; + SQLLEN octet_length; + int ret; + + if (ctype == SQL_C_CHAR) { + octet_length = arec->octet_length; + if (octet_length == SQL_NTSL) { + octet_length = strlen((SQLCHAR *)data_ptr); + } + assert (0 <= octet_length); // checked on param bind + if (sizeof(wbuff)/sizeof(wbuff[0]) < (size_t)octet_length) { + INFOH(stmt, "translation buffer too small (%zu < %lld), " + "allocation needed.", sizeof(wbuff)/sizeof(wbuff[0]), + (size_t)octet_length); + wptr = malloc(octet_length * sizeof(SQLWCHAR)); + if (! wptr) { + ERRNH(stmt, "OOM for %lld x SQLWCHAR", octet_length); + RET_HDIAGS(stmt, SQL_STATE_HY001); + } + } else { + wptr = wbuff; + } + ret = ascii_c2w((SQLCHAR *)data_ptr, wptr, octet_length); + if (ret <= 0) { + ERRH(stmt, "SQLCHAR-to-SQLWCHAR conversion failed for " + "[%lld] `" LCPDL "`.", octet_length, octet_length, + (char *)data_ptr); + if (wptr != wbuff) { + free(wptr); + wptr = NULL; + } + /* should only happen on too short input string */ + RET_HDIAGS(stmt, SQL_STATE_22018); + } + wstr.str = wptr; + wstr.cnt = octet_length; + } else { + assert(ctype == SQL_C_WCHAR); + + wstr.str = (SQLWCHAR *)data_ptr; + octet_length = arec->octet_length; + if (octet_length == SQL_NTSL) { + wstr.cnt = wcslen(wstr.str); + } else { + assert (0 <= octet_length); // checked on param bind + wstr.cnt = (size_t)octet_length; + } + wptr = NULL; + } + + /* trim trailing NTS, if any */ + if (wstr.str[wstr.cnt - 1] == L'\0') { + wstr.cnt --; + } + /* TODO: spec is not clear if we'd get here a literal, or a value (which + * would make sense, given that the interval type is passed as SQL type; + * however, inter-interval types conversions are also not clearly spec'd: + * we could get a literal of one type and different SQL type in bound + * param). --> parse_interval_literal_value()?? */ + /* BindParam sets IPD's fields -> use irec */ + ret = parse_interval_literal(irec, &wstr, ivl); + if (wptr && wptr != wbuff) { + free(wptr); + wptr = NULL; + } + if (! SQL_SUCCEEDED(ret)) { + return ret; + } + + return SQL_SUCCESS; +} + +SQLRETURN c2sql_interval(esodbc_rec_st *arec, esodbc_rec_st *irec, + SQLULEN pos, char *dest, size_t *len) +{ + esodbc_stmt_st *stmt = arec->desc->hdr.stmt; + void *data_ptr; + SQLSMALLINT ctype; + SQLUBIGINT ubint; + SQLBIGINT bint; + SQLUINTEGER uint; + size_t res; + SQL_INTERVAL_STRUCT ivl = {0}; + SQLRETURN ret; + + /* Assign the temporary `ubint` the value passed in the client app buffer, + * setting interval's sign, if negative. + * Uses local vars: bint, ubint, ivl */ +# define ASSIGN_SIGNED(_sqltype) \ + do { \ + bint = (SQLBIGINT)*(_sqltype *)data_ptr; \ + if (bint < 0) { \ + ivl.interval_sign = SQL_TRUE; \ + ubint = -bint; \ + } else { \ + ubint = bint; \ + } \ + } while (0) + + if (! dest) { + /* maximum possible space it can take */ + *len = INTERVAL_VAL_MAX_LEN; + return SQL_SUCCESS; + } else { + *dest = '"'; + *len = 1; + } + + /* pointer to app's buffer */ + data_ptr = deferred_address(SQL_DESC_DATA_PTR, pos, arec); + + assert(SQL_FALSE == 0); /* == {0}'d above */ + /*INDENT-OFF*/ + switch ((ctype = get_rec_c_type(arec, irec))) { + case SQL_C_CHAR: + case SQL_C_WCHAR: + ret = c2sql_str2interval(arec, irec, ctype, data_ptr, &ivl); + if (! SQL_SUCCEEDED(ret)) { + return ret; + } + break; + + /* numeric exact */ + do { + case SQL_C_BIT: ubint = *(SQLCHAR *)data_ptr ? 1 : 0; break; + case SQL_C_SHORT: + case SQL_C_SSHORT: ASSIGN_SIGNED(SQLSMALLINT); break; + case SQL_C_USHORT: ubint = (SQLUBIGINT)*(SQLUSMALLINT*)data_ptr; break; + case SQL_C_LONG: + case SQL_C_SLONG: ASSIGN_SIGNED(SQLINTEGER); break; + case SQL_C_ULONG: ubint = (SQLUBIGINT)*(SQLUINTEGER *)data_ptr; break; + case SQL_C_TINYINT: + case SQL_C_STINYINT: ASSIGN_SIGNED(SQLSCHAR); break; + case SQL_C_UTINYINT: ubint = (SQLUBIGINT)*(SQLCHAR *)data_ptr; break; + case SQL_C_SBIGINT: ASSIGN_SIGNED(SQLBIGINT); break; + case SQL_C_UBIGINT: ubint = *(SQLUBIGINT *)data_ptr; break; + } while (0); + if (/*SQLUINTEGER*/ULONG_MAX < ubint) { + ERRH(stmt, "value too large for interval field: %llu.", ubint); + RET_HDIAGS(stmt, SQL_STATE_22015); + } else { + uint = (SQLUINTEGER)ubint; + } + assert(SQL_CODE_YEAR == SQL_IS_YEAR); + ivl.interval_type = irec->es_type->data_type - + (SQL_INTERVAL_YEAR - SQL_CODE_YEAR); + DBGH(stmt, "converting integer value %lu to interval.type: %d.", + uint, ivl.interval_type); + // TODO: precision checks? (ES/SQL takes already a u32/SQLUINT.) + switch (irec->es_type->data_type) { + case SQL_INTERVAL_YEAR: + ivl.intval.year_month.year = uint; + break; + case SQL_INTERVAL_MONTH: + ivl.intval.year_month.month = uint; + break; + case SQL_INTERVAL_DAY: + ivl.intval.day_second.day = uint; + break; + case SQL_INTERVAL_HOUR: + ivl.intval.day_second.hour = uint; + break; + case SQL_INTERVAL_MINUTE: + ivl.intval.day_second.minute = uint; + break; + case SQL_INTERVAL_SECOND: + ivl.intval.day_second.second = uint; + break; + default: // shold never get here + BUGH(stmt, "conversion not supported."); + RET_HDIAGS(stmt, SQL_STATE_HY000); + } + break; + + case SQL_C_INTERVAL_YEAR: + case SQL_C_INTERVAL_MONTH: + case SQL_C_INTERVAL_DAY: + case SQL_C_INTERVAL_HOUR: + case SQL_C_INTERVAL_MINUTE: + case SQL_C_INTERVAL_SECOND: + case SQL_C_INTERVAL_YEAR_TO_MONTH: + case SQL_C_INTERVAL_DAY_TO_HOUR: + case SQL_C_INTERVAL_DAY_TO_MINUTE: + case SQL_C_INTERVAL_DAY_TO_SECOND: + case SQL_C_INTERVAL_HOUR_TO_MINUTE: + case SQL_C_INTERVAL_HOUR_TO_SECOND: + case SQL_C_INTERVAL_MINUTE_TO_SECOND: + // by data compatibility + assert (irec->es_type->data_type == ctype); + ivl = *(SQL_INTERVAL_STRUCT *)data_ptr; + break; + + case SQL_C_NUMERIC: + case SQL_C_BINARY: + BUGH(stmt, "conversion not yet supported."); + RET_HDIAGS(stmt, SQL_STATE_HYC00); + break; + + default: + BUGH(stmt, "can't convert SQL C type %hd to interval.", ctype); + RET_HDIAG(stmt, SQL_STATE_HY000, "bug converting parameter", 0); + } + /*INDENT-ON*/ + + res = print_interval_iso8601(irec, &ivl, dest + *len); + if (res <= 0) { + ERRH(stmt, "printing interval of type %hd failed.", ivl.interval_type); + RET_HDIAG(stmt, SQL_STATE_HY000, "internal printing failed", 0); + } else { + *len += res; + } + + dest[(*len) ++] = '"'; + return SQL_SUCCESS; +# undef ASSIGN_SIGNED +} static SQLRETURN c2sql_cstr2qstr(esodbc_rec_st *arec, esodbc_rec_st *irec, SQLULEN pos, char *dest, size_t *len) diff --git a/driver/convert.h b/driver/convert.h index 52abc15e..1099effe 100644 --- a/driver/convert.h +++ b/driver/convert.h @@ -48,5 +48,7 @@ SQLRETURN c2sql_varchar(esodbc_rec_st *arec, esodbc_rec_st *irec, SQLULEN pos, char *dest, size_t *len); SQLRETURN c2sql_timestamp(esodbc_rec_st *arec, esodbc_rec_st *irec, SQLULEN pos, char *dest, size_t *len); +SQLRETURN c2sql_interval(esodbc_rec_st *arec, esodbc_rec_st *irec, + SQLULEN pos, char *dest, size_t *len); #endif /* __CONVERT_H__ */ diff --git a/driver/handles.c b/driver/handles.c index 67b7ae05..d37faabc 100644 --- a/driver/handles.c +++ b/driver/handles.c @@ -2311,6 +2311,7 @@ SQLRETURN EsSQLSetDescFieldW( SQLINTEGER *intp; SQLSMALLINT count, type, chk_type, chk_code; SQLULEN ulen; + SQLLEN slen; size_t wlen; if (! check_access(desc, FieldIdentifier, O_RDWR)) { @@ -2609,10 +2610,12 @@ SQLRETURN EsSQLSetDescFieldW( DBGH(desc, "setting octet length: %ld.", (SQLLEN)(intptr_t)ValuePtr); /* rec field's type is signed :/; a negative is dangerous l8r */ - if ((SQLLEN)(intptr_t)ValuePtr < 0) { + slen = (SQLLEN)(intptr_t)ValuePtr; + if (slen < 0 && slen != SQL_NTSL) { ERRH(desc, "octet length attribute can't be negative (%lld)", - (SQLLEN)(intptr_t)ValuePtr); - RET_HDIAGS(desc, SQL_STATE_HY000); + slen); + RET_HDIAG(desc, SQL_STATE_HY000, + "invalid negative octet lenght attribute", 0); } rec->octet_length = (SQLLEN)(intptr_t)ValuePtr; break; diff --git a/driver/queries.c b/driver/queries.c index 40d47f07..be6207a4 100644 --- a/driver/queries.c +++ b/driver/queries.c @@ -1715,6 +1715,21 @@ static SQLRETURN convert_param_val(esodbc_rec_st *arec, esodbc_rec_st *irec, case SQL_TYPE_TIMESTAMP: /* DATE */ return c2sql_timestamp(arec, irec, pos, dest, len); + case SQL_INTERVAL_YEAR: + case SQL_INTERVAL_MONTH: + case SQL_INTERVAL_DAY: + case SQL_INTERVAL_HOUR: + case SQL_INTERVAL_MINUTE: + case SQL_INTERVAL_SECOND: + case SQL_INTERVAL_YEAR_TO_MONTH: + case SQL_INTERVAL_DAY_TO_HOUR: + case SQL_INTERVAL_DAY_TO_MINUTE: + case SQL_INTERVAL_DAY_TO_SECOND: + case SQL_INTERVAL_HOUR_TO_MINUTE: + case SQL_INTERVAL_HOUR_TO_SECOND: + case SQL_INTERVAL_MINUTE_TO_SECOND: + return c2sql_interval(arec, irec, pos, dest, len); + /* JSON (Base64 encoded) string */ case SQL_VARBINARY: /* BINARY */ // XXX: json_escape diff --git a/test/test_conversion_c2sql_interval.cc b/test/test_conversion_c2sql_interval.cc new file mode 100644 index 00000000..55595860 --- /dev/null +++ b/test/test_conversion_c2sql_interval.cc @@ -0,0 +1,278 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +#include +#include "connected_dbc.h" + +#include + + +namespace test { + +class ConvertC2SQL_Interval : public ::testing::Test, public ConnectedDBC { +}; + +TEST_F(ConvertC2SQL_Interval, Bit2Interval_year) +{ + prepareStatement(); + + SQLCHAR val = 1; + ret = SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_BIT, + SQL_INTERVAL_YEAR, /*size*/0, /*decdigits*/0, &val, sizeof(val), + /*IndLen*/NULL); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + cstr_st buff = {NULL, 0}; + ret = serialize_statement((esodbc_stmt_st *)stmt, &buff); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + cstr_st expect = CSTR_INIT("{\"query\": \"Bit2Interval_year\", " + "\"params\": [{\"type\": \"INTERVAL_YEAR\", " + "\"value\": \"P1Y\"}], " + "\"mode\": \"ODBC\"}"); + + ASSERT_CSTREQ(buff, expect); +} + +TEST_F(ConvertC2SQL_Interval, Short2Interval_month) +{ + prepareStatement(); + + SQLSMALLINT val = -2; + ret = SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_SHORT, + SQL_INTERVAL_MONTH, /*size*/0, /*decdigits*/0, &val, sizeof(val), + /*IndLen*/NULL); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + cstr_st buff = {NULL, 0}; + ret = serialize_statement((esodbc_stmt_st *)stmt, &buff); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + cstr_st expect = CSTR_INIT("{\"query\": \"Short2Interval_month\", " + "\"params\": [{\"type\": \"INTERVAL_MONTH\", " + "\"value\": \"P-2M\"}], " + "\"mode\": \"ODBC\"}"); + + ASSERT_CSTREQ(buff, expect); +} + +TEST_F(ConvertC2SQL_Interval, Short2Interval_month_all_0) +{ + prepareStatement(); + + SQLSMALLINT val = 0; + ret = SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_SHORT, + SQL_INTERVAL_MONTH, /*size*/0, /*decdigits*/0, &val, sizeof(val), + /*IndLen*/NULL); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + cstr_st buff = {NULL, 0}; + ret = serialize_statement((esodbc_stmt_st *)stmt, &buff); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + cstr_st expect = CSTR_INIT("{\"query\": \"Short2Interval_month_all_0\", " + "\"params\": [{\"type\": \"INTERVAL_MONTH\", " + "\"value\": \"P0M\"}], " + "\"mode\": \"ODBC\"}"); + + ASSERT_CSTREQ(buff, expect); +} + +TEST_F(ConvertC2SQL_Interval, Integer2Interval_day) +{ + prepareStatement(); + + SQLINTEGER val = -3; + ret = SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_LONG, + SQL_INTERVAL_DAY, /*size*/0, /*decdigits*/0, &val, sizeof(val), + /*IndLen*/NULL); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + cstr_st buff = {NULL, 0}; + ret = serialize_statement((esodbc_stmt_st *)stmt, &buff); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + cstr_st expect = CSTR_INIT("{\"query\": \"Integer2Interval_day\", " + "\"params\": [{\"type\": \"INTERVAL_DAY\", " + "\"value\": \"P-3D\"}], " + "\"mode\": \"ODBC\"}"); + + ASSERT_CSTREQ(buff, expect); +} + +TEST_F(ConvertC2SQL_Interval, UBigInt2Interval_minute) +{ + prepareStatement(); + + SQLUBIGINT val = 12345678; + ret = SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_UBIGINT, + SQL_INTERVAL_MINUTE, /*size*/0, /*decdigits*/0, &val, sizeof(val), + /*IndLen*/NULL); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + cstr_st buff = {NULL, 0}; + ret = serialize_statement((esodbc_stmt_st *)stmt, &buff); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + cstr_st expect = CSTR_INIT("{\"query\": \"UBigInt2Interval_minute\", " + "\"params\": [{\"type\": \"INTERVAL_MINUTE\", " + "\"value\": \"PT12345678M\"}], " + "\"mode\": \"ODBC\"}"); + + ASSERT_CSTREQ(buff, expect); +} + +TEST_F(ConvertC2SQL_Interval, SBigInt2Interval_second) +{ + prepareStatement(); + + SQLUBIGINT val = -123456789; + ret = SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_SBIGINT, + SQL_INTERVAL_SECOND, /*size*/0, /*decdigits*/0, &val, sizeof(val), + /*IndLen*/NULL); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + cstr_st buff = {NULL, 0}; + ret = serialize_statement((esodbc_stmt_st *)stmt, &buff); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + cstr_st expect = CSTR_INIT("{\"query\": \"SBigInt2Interval_second\", " + "\"params\": [{\"type\": \"INTERVAL_SECOND\", " + "\"value\": \"PT-123456789S\"}], " + "\"mode\": \"ODBC\"}"); + + ASSERT_CSTREQ(buff, expect); +} + +TEST_F(ConvertC2SQL_Interval, SBigInt2Interval_second_all_0) +{ + prepareStatement(); + + SQLUBIGINT val = 0; + ret = SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_SBIGINT, + SQL_INTERVAL_SECOND, /*size*/0, /*decdigits*/0, &val, sizeof(val), + /*IndLen*/NULL); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + cstr_st buff = {NULL, 0}; + ret = serialize_statement((esodbc_stmt_st *)stmt, &buff); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + cstr_st expect = CSTR_INIT( + "{\"query\": \"SBigInt2Interval_second_all_0\", " + "\"params\": [{\"type\": \"INTERVAL_SECOND\", " + "\"value\": \"PT0S\"}], " + "\"mode\": \"ODBC\"}"); + + ASSERT_CSTREQ(buff, expect); +} + +TEST_F(ConvertC2SQL_Interval, WChar2Interval_day_to_second) +{ + prepareStatement(); + + SQLWCHAR val[] = L"INTERVAL -'2 03:04:05.678' DAY TO SECOND"; + ret = SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_WCHAR, + SQL_INTERVAL_DAY_TO_SECOND, /*size*/2, /*decdigits*/3, val, + SQL_NTSL, /*IndLen*/NULL); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + cstr_st buff = {NULL, 0}; + ret = serialize_statement((esodbc_stmt_st *)stmt, &buff); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + cstr_st expect = CSTR_INIT( + "{\"query\": \"WChar2Interval_day_to_second\", " + "\"params\": [{\"type\": \"INTERVAL_DAY_TO_SECOND\", " + "\"value\": \"P-2DT-3H-4M-5.678S\"}], " + "\"mode\": \"ODBC\"}"); + + ASSERT_CSTREQ(buff, expect); +} + +TEST_F(ConvertC2SQL_Interval, Char2Interval_hour_to_second) +{ + prepareStatement(); + + SQLCHAR val[] = "INTERVAL '03:04:05.678' HOUR TO SECOND"; + ret = SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, + SQL_INTERVAL_HOUR_TO_SECOND, /*size*/2, /*decdigits*/3, val, + sizeof(val) - 1, /*IndLen*/NULL); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + cstr_st buff = {NULL, 0}; + ret = serialize_statement((esodbc_stmt_st *)stmt, &buff); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + cstr_st expect = CSTR_INIT( + "{\"query\": \"Char2Interval_hour_to_second\", " + "\"params\": [{\"type\": \"INTERVAL_HOUR_TO_SECOND\", " + "\"value\": \"PT3H4M5.678S\"}], " + "\"mode\": \"ODBC\"}"); + + ASSERT_CSTREQ(buff, expect); +} + +TEST_F(ConvertC2SQL_Interval, Char2Interval_hour_to_second_force_alloc) +{ + prepareStatement(); + + SQLCHAR val[] = "INTERVAL '03:04:05.678' HOUR TO " + " " + " " + " " + "SECOND"; + ret = SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, + SQL_INTERVAL_HOUR_TO_SECOND, /*size*/2, /*decdigits*/3, val, + sizeof(val) - 1, /*IndLen*/NULL); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + cstr_st buff = {NULL, 0}; + ret = serialize_statement((esodbc_stmt_st *)stmt, &buff); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + cstr_st expect = CSTR_INIT( + "{\"query\": \"Char2Interval_hour_to_second_force_alloc\", " + "\"params\": [{\"type\": \"INTERVAL_HOUR_TO_SECOND\", " + "\"value\": \"PT3H4M5.678S\"}], " + "\"mode\": \"ODBC\"}"); + + ASSERT_CSTREQ(buff, expect); +} + +TEST_F(ConvertC2SQL_Interval, Interval2Interval_year_to_month) +{ + prepareStatement(); + + SQL_INTERVAL_STRUCT val; + val.interval_type = SQL_IS_YEAR_TO_MONTH; + val.interval_sign = SQL_TRUE; + val.intval.year_month.year = 12; + val.intval.year_month.month = 11; + + ret = SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, + SQL_C_INTERVAL_YEAR_TO_MONTH, SQL_INTERVAL_YEAR_TO_MONTH, + /*size*/2, /*decdigits*/3, &val, sizeof(val), /*IndLen*/NULL); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + cstr_st buff = {NULL, 0}; + ret = serialize_statement((esodbc_stmt_st *)stmt, &buff); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + cstr_st expect = CSTR_INIT( + "{\"query\": \"Interval2Interval_year_to_month\", " + "\"params\": [{\"type\": \"INTERVAL_YEAR_TO_MONTH\", " + "\"value\": \"P-12Y-11M\"}], " + "\"mode\": \"ODBC\"}"); + + ASSERT_CSTREQ(buff, expect); +} + + + +} // test namespace + +/* vim: set noet fenc=utf-8 ff=dos sts=0 sw=4 ts=4 : */ From 4396b068ff77cad962d9fbc2a650ac7cba3924c0 Mon Sep 17 00:00:00 2001 From: Bogdan Pintea Date: Mon, 17 Dec 2018 11:34:03 +0100 Subject: [PATCH 3/3] add comment on char lowering in switches - the used `| 0x20` lowers the character, if that has a value in the ASCII set. add comments to make that clearer. --- driver/convert.c | 10 +++++----- driver/log.c | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/driver/convert.c b/driver/convert.c index 6905af31..b492688f 100644 --- a/driver/convert.c +++ b/driver/convert.c @@ -1918,7 +1918,7 @@ static SQLRETURN parse_interval_iso8601(esodbc_rec_st *arec, fraction = 0; state = saved = ST_UNINITED; for (crr = wstr->str, end = wstr->str + wstr->cnt; crr < end; ) { - switch (*crr | 0x20) { + switch (*crr | 0x20) { /* ~tolower(), ascii vals only */ case L'p': if (state != ST_UNINITED) { goto err_parse; @@ -2319,13 +2319,13 @@ static SQLSMALLINT parse_interval_type(wstr_st *wstr) } /* split by last letter */ - switch (wstr->str[wstr->cnt - 1] | 0x20) { + switch (wstr->str[wstr->cnt - 1] | 0x20) { /* ~tolower(), ascii val only */ case L'y': /* day */ TRIM_IF_ENDS_WITH_OR_RET("day"); return SQL_IS_DAY; case L'r': /* year, hour, day to hour */ - switch (wstr->str[wstr->cnt - 2] | 0x20) { + switch (wstr->str[wstr->cnt - 2] | 0x20) { /* ~tolower() */ case L'a': /* ...in year */ TRIM_IF_ENDS_WITH_OR_RET("year"); return SQL_IS_YEAR; @@ -2345,7 +2345,7 @@ static SQLSMALLINT parse_interval_type(wstr_st *wstr) return SQL_IS_MINUTE; } TRIM_IF_ENDS_WITH_OR_RET("to"); - switch (wstr->str[wstr->cnt - 1] | 0x20) { + switch (wstr->str[wstr->cnt - 1] | 0x20) { /* ~tolower() */ case L'y': /* ...in "day" */ TRIM_IF_ENDS_WITH_OR_RET("day"); return SQL_IS_DAY_TO_MINUTE; @@ -2367,7 +2367,7 @@ static SQLSMALLINT parse_interval_type(wstr_st *wstr) return SQL_IS_SECOND; } TRIM_IF_ENDS_WITH_OR_RET("to"); - switch (wstr->str[wstr->cnt - 1] | 0x20) { + switch (wstr->str[wstr->cnt - 1] | 0x20) { /* ~tolower() */ case L'y': /* ...in "day" */ TRIM_IF_ENDS_WITH_OR_RET("day"); return SQL_IS_DAY_TO_SECOND; diff --git a/driver/log.c b/driver/log.c index ebd95572..070f09d0 100644 --- a/driver/log.c +++ b/driver/log.c @@ -105,7 +105,7 @@ int parse_log_level(wstr_st *level) return LOG_LEVEL_DISABLED; } /* first letter will indicate the log level */ - switch ((unsigned)level->str[0] | 0x20) { + switch ((unsigned)level->str[0] | 0x20) { /* ~tolower(), ascii set only */ case 'e': return LOG_LEVEL_ERR; case 'w':