From b01e173ec5877ce82f0e5c63b9fc83ce31ed8c60 Mon Sep 17 00:00:00 2001 From: Bogdan Pintea Date: Mon, 17 Jan 2022 13:34:01 +0100 Subject: [PATCH 1/3] Add unsigned_long ES type support This adds support for the ES/SQL UNSIGNED_LONG type. Values with this type can be received by the driver and forwarded to the application as SQL_C_UBIGINT types. The driver will also accept parameters of this type. The driver-internal conversion to/from UNSIGNED_LONG is also supported. The commit also adds logging of the name of the application loading the driver. --- driver/connect.c | 32 +- driver/convert.c | 440 +++++++++------ driver/convert.h | 21 +- driver/defs.h | 3 + driver/handles.h | 2 + driver/odbc.c | 12 + driver/queries.c | 96 ++-- driver/tinycbor.c | 60 +++ driver/tinycbor.h | 1 + driver/util.c | 4 +- test/connected_dbc.h | 1 + test/test_conversion_c2sql_numeric.cc | 133 ++++- test/test_conversion_sql2c_interval.cc | 66 +++ test/test_conversion_sql2c_ints.cc | 6 +- test/test_conversion_sql2c_ulong.cc | 708 +++++++++++++++++++++++++ 15 files changed, 1366 insertions(+), 219 deletions(-) create mode 100644 test/test_conversion_sql2c_ulong.cc diff --git a/driver/connect.c b/driver/connect.c index 1bccd305..e2c5429b 100644 --- a/driver/connect.c +++ b/driver/connect.c @@ -69,6 +69,8 @@ #define TYPE_UNSUPPORTED "UNSUPPORTED" /* 12 */ #define TYPE_SCALED_FLOAT "SCALED_FLOAT" +/* 13 */ +#define TYPE_UNSIGNED_LONG "UNSIGNED_LONG" /* * intervals @@ -1503,7 +1505,7 @@ SQLRETURN config_dbc(esodbc_dbc_st *dbc, esodbc_dsn_attrs_st *attrs) INFOH(dbc, "early execution: %s.", dbc->early_exec ? "true" : "false"); /* default current catalog */ if (attrs->catalog.cnt && - (! SQL_SUCCEEDED(set_current_catalog(dbc, &attrs->catalog)))) { + (! SQL_SUCCEEDED(set_current_catalog(dbc, &attrs->catalog)))) { goto err; } @@ -2386,6 +2388,17 @@ static BOOL elastic_name2types(wstr_st *type_name, return TRUE; } break; + + /* 13: UNSIGNED_LONG */ + case sizeof(TYPE_UNSIGNED_LONG) - 1: + if (! wmemncasecmp(type_name->str, MK_WPTR(TYPE_UNSIGNED_LONG), + type_name->cnt)) { + *c_sql = ES_ULONG_TO_CSQL; + *sql = ES_ULONG_TO_SQL; + return TRUE; + } + break; + } return elastic_intervals_name2types(type_name, c_sql, sql); @@ -2422,7 +2435,7 @@ static void set_display_size(esodbc_estype_st *es_type) es_type->display_size ++; } break; - case SQL_BIGINT: /* LONG */ + case SQL_BIGINT: /* LONG, UNSIGNED_LONG */ es_type->display_size = es_type->column_size; if (es_type->unsigned_attribute) { es_type->display_size ++; @@ -2713,6 +2726,7 @@ static void set_es_types(esodbc_dbc_st *dbc, SQLULEN rows_fetched, { SQLULEN i; SQLINTEGER max_float_size, max_varchar_size; + size_t lgst_name_len; dbc->es_types = types; dbc->no_types = rows_fetched; @@ -2720,6 +2734,7 @@ static void set_es_types(esodbc_dbc_st *dbc, SQLULEN rows_fetched, assert(dbc->max_float_type == NULL); assert(dbc->max_varchar_type == NULL); + lgst_name_len = 0; for (i = 0; i < rows_fetched; i ++) { switch (types[i].data_type) { case SQL_DOUBLE: @@ -2736,11 +2751,20 @@ static void set_es_types(esodbc_dbc_st *dbc, SQLULEN rows_fetched, if (max_varchar_size < types[i].column_size) { dbc->max_varchar_type = &types[i]; } + break; + } + if (types[i].c_concise_type == ES_ULONG_TO_CSQL) { + dbc->ulong = &types[i]; + } + if (lgst_name_len < types[i].type_name.cnt) { + lgst_name_len = types[i].type_name.cnt; + dbc->lgst_name = &types[i]; } } assert(dbc->max_float_type); assert(dbc->max_varchar_type); + assert(dbc->lgst_name); DBGH(dbc, "%lu ES/SQL types available, maximum sizes supported for: " "floats: %ld, varchar: %ld.", (unsigned long)rows_fetched, @@ -3478,8 +3502,8 @@ SQLRETURN EsSQLGetConnectAttrW( } if (dbc->catalog.w.cnt) { if (! SQL_SUCCEEDED(write_wstr(dbc, (SQLWCHAR *)ValuePtr, - &dbc->catalog.w, (SQLSMALLINT)BufferLength, - &used))) { + &dbc->catalog.w, (SQLSMALLINT)BufferLength, + &used))) { ERRH(dbc, "failed to copy current catalog out."); RET_STATE(dbc->hdr.diag.state); } diff --git a/driver/convert.c b/driver/convert.c index 4583a22f..600e2b01 100644 --- a/driver/convert.c +++ b/driver/convert.c @@ -42,8 +42,8 @@ static thread_local struct tm today; #define REJECT_AS_OOR(_stmt, _val, _fix_val, _target) /* Out Of Range */ \ do { \ if (_fix_val) { \ - ERRH(_stmt, "can't convert value %llx to %s: out of range.", \ - _val, STR(_target)); \ + ERRH(_stmt, "can't convert value 0x%llx to %s: out of range.", \ + (uint64_t)_val, STR(_target)); \ } else { \ ERRH(_stmt, "can't convert value %f to %s: out of range.", \ _val, STR(_target)); \ @@ -910,12 +910,14 @@ static SQLRETURN llong_to_binary(esodbc_rec_st *arec, esodbc_rec_st *irec, return SQL_SUCCESS; } -static SQLRETURN longlong_to_str(esodbc_rec_st *arec, esodbc_rec_st *irec, - long long ll, void *data_ptr, SQLLEN *octet_len_ptr, BOOL wide) + +static SQLRETURN quadword_to_str(esodbc_rec_st *arec, esodbc_rec_st *irec, + uint64_t qword, bool unsignd, void *data_ptr, SQLLEN *octet_len_ptr, + BOOL wide) { SQLRETURN ret; /* buffer is overprovisioned for !wide, but avoids double declaration */ - SQLCHAR buff[(ESODBC_PRECISION_INT64 + /*0-term*/1 + /*+/-*/1) + SQLCHAR buff[(ESODBC_PRECISION_UINT64 + /*0-term*/1 + /*+/-*/1) * sizeof(SQLWCHAR)]; esodbc_stmt_st *stmt = arec->desc->hdr.stmt; xstr_st xsrc = (xstr_st) { @@ -925,14 +927,15 @@ static SQLRETURN longlong_to_str(esodbc_rec_st *arec, esodbc_rec_st *irec, } }; - xsrc.w.cnt = i64tot((int64_t)ll, buff, wide); /*==.c.cnt*/ + xsrc.w.cnt = unsignd ? ui64tot(qword, buff, wide) : + i64tot((int64_t)qword, buff, wide); /* ==.c.cnt */ if (wide) { - DBGH(stmt, "long long %lld convertible to w-string `" LWPD "` on " - "%zd octets.", ll, xsrc.w.str, xsrc.w.cnt); + DBGH(stmt, "quad word 0x%llx convertible to w-string `" LWPD "` on " + "%zd octets.", qword, xsrc.w.str, xsrc.w.cnt); } else { - DBGH(stmt, "long long %lld convertible to string `" LCPD "` on " - "%zd octets.", ll, xsrc.c.str, xsrc.c.cnt); + DBGH(stmt, "quad word 0x%llx convertible to string `" LCPD "` on " + "%zd octets.", qword, xsrc.c.str, xsrc.c.cnt); } ret = transfer_xstr0(arec, irec, &xsrc, data_ptr, octet_len_ptr); @@ -943,19 +946,21 @@ static SQLRETURN longlong_to_str(esodbc_rec_st *arec, esodbc_rec_st *irec, if (STMT_GD_CALLING(stmt)) { return ret; } else { - REJECT_AS_OOR(stmt, ll, /*fixed?*/TRUE, "[STRING]<[value]"); + REJECT_AS_OOR(stmt, qword, /*fixed?*/TRUE, "[STRING]<[value]"); } } return SQL_SUCCESS; } -SQLRETURN sql2c_longlong(esodbc_rec_st *arec, esodbc_rec_st *irec, - SQLULEN pos, long long ll) +SQLRETURN sql2c_quadword(esodbc_rec_st *arec, esodbc_rec_st *irec, + SQLULEN pos, uint64_t qword, bool unsignd) { esodbc_stmt_st *stmt; void *data_ptr; SQLLEN *octet_len_ptr; esodbc_desc_st *ard, *ird; + long long ll; + unsigned long long ull; SQLSMALLINT ctype; SQLRETURN ret; SQL_INTERVAL_STRUCT ivl; @@ -967,71 +972,81 @@ SQLRETURN sql2c_longlong(esodbc_rec_st *arec, esodbc_rec_st *irec, ird = stmt->ird; ard = stmt->ard; + ull = (unsigned long long)qword; + ll = (long long)qword; + /* 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); /* Assume a C type behind an SQL C type, but check size representation. - * Note: won't work if _min==0 is a legit limit */ -# define REJECT_IF_OOR(_stmt, _ll, _min, _max, _sqlctype, _ctype) \ + * Uses local vars: stmt. */ +# define REJECT_IF_OOR(_qword, _min, _max, _sqlctype, _ctype) \ do { \ assert(sizeof(_sqlctype) == sizeof(_ctype)); \ - if ((_min && _ll < _min) || _max < _ll) { \ - REJECT_AS_OOR(_stmt, _ll, /*fixed int*/TRUE, _ctype); \ + if (_qword < _min || _max < _qword) { \ + REJECT_AS_OOR(stmt, _qword, /*fixed int*/TRUE, _ctype); \ } \ } while (0) - /* Transfer a long long to an SQL integer type. - * Uses local vars: stmt, data_ptr, irec, octet_len_ptr. */ -# define TRANSFER_LL(_ll, _min, _max, _sqlctype, _ctype) \ + /* Transfer a quadword to an SQL integer type. + * Uses local vars: ll, ull, stmt, data_ptr, irec, octet_len_ptr. */ +# define TRANSFER_QW(_min, _max, _sqlctype, _ctype) \ do { \ REJECT_IF_NULL_DEST_BUFF(stmt, data_ptr); \ - REJECT_IF_OOR(stmt, _ll, _min, _max, _sqlctype, _ctype); \ - *(_sqlctype *)data_ptr = (_sqlctype)_ll; \ + if (unsignd) { \ + REJECT_IF_OOR(ull, 0, _max, _sqlctype, _ctype); \ + *(_sqlctype *)data_ptr = (_sqlctype)ull; \ + DBGH(stmt, "converted unsigned quad word %llu to " STR(_sqlctype) \ + " 0x%llx.", ull, (uint64_t)*(_sqlctype *)data_ptr); \ + } else { \ + REJECT_IF_OOR(ll, _min, _max, _sqlctype, _ctype); \ + *(_sqlctype *)data_ptr = (_sqlctype)ll; \ + DBGH(stmt, "converted signed quad word %lld to " STR(_sqlctype) \ + " 0x%llx.", ll, (uint64_t)*(_sqlctype *)data_ptr); \ + } \ write_out_octets(octet_len_ptr, sizeof(_sqlctype), irec); \ - DBGH(stmt, "converted long long %lld to " STR(_sqlctype) " 0x%llx.", \ - _ll, (uint64_t)*(_sqlctype *)data_ptr); \ } while (0) switch ((ctype = get_rec_c_type(arec, irec))) { case SQL_C_CHAR: case SQL_C_WCHAR: - return longlong_to_str(arec, irec, ll, data_ptr, octet_len_ptr, - ctype == SQL_C_WCHAR); + return quadword_to_str(arec, irec, qword, unsignd, data_ptr, + octet_len_ptr, ctype == SQL_C_WCHAR); case SQL_C_TINYINT: case SQL_C_STINYINT: - TRANSFER_LL(ll, CHAR_MIN, CHAR_MAX, SQLSCHAR, char); + TRANSFER_QW(CHAR_MIN, CHAR_MAX, SQLSCHAR, char); break; case SQL_C_UTINYINT: - TRANSFER_LL(ll, 0, UCHAR_MAX, SQLCHAR, unsigned char); + TRANSFER_QW(0, UCHAR_MAX, SQLCHAR, unsigned char); break; case SQL_C_SHORT: case SQL_C_SSHORT: - TRANSFER_LL(ll, SHRT_MIN, SHRT_MAX, SQLSMALLINT, short); + TRANSFER_QW(SHRT_MIN, SHRT_MAX, SQLSMALLINT, short); break; case SQL_C_USHORT: - TRANSFER_LL(ll, 0, USHRT_MAX, SQLUSMALLINT, unsigned short); + TRANSFER_QW(0, USHRT_MAX, SQLUSMALLINT, unsigned short); break; case SQL_C_LONG: case SQL_C_SLONG: - TRANSFER_LL(ll, LONG_MIN, LONG_MAX, SQLINTEGER, long); + TRANSFER_QW(LONG_MIN, LONG_MAX, SQLINTEGER, long); break; case SQL_C_ULONG: - TRANSFER_LL(ll, 0, ULONG_MAX, SQLUINTEGER, unsigned long); + TRANSFER_QW(0, ULONG_MAX, SQLUINTEGER, unsigned long); break; case SQL_C_SBIGINT: - TRANSFER_LL(ll, LLONG_MIN, LLONG_MAX, SQLBIGINT, long long); + TRANSFER_QW(LLONG_MIN, LLONG_MAX, SQLBIGINT, long long); break; case SQL_C_UBIGINT: - TRANSFER_LL(ll, 0, ULLONG_MAX, SQLUBIGINT, unsigned long long); + TRANSFER_QW(0, ULLONG_MAX, SQLUBIGINT, unsigned long long); break; case SQL_C_BIT: REJECT_IF_NULL_DEST_BUFF(stmt, data_ptr); - if (ll < 0 || 2 <= ll) { + if (ll != 0 && ll != 1) { REJECT_AS_OOR(stmt, ll, /*fixed int*/TRUE, SQL_C_BIT); - } else { /* 0 or 1 */ + } else { *(SQLCHAR *)data_ptr = (SQLCHAR)ll; } write_out_octets(octet_len_ptr, sizeof(SQLSCHAR), irec); @@ -1039,7 +1054,8 @@ SQLRETURN sql2c_longlong(esodbc_rec_st *arec, esodbc_rec_st *irec, case SQL_C_NUMERIC: REJECT_IF_NULL_DEST_BUFF(stmt, data_ptr); - ret = double_to_numeric(arec, (double)ll, data_ptr); + ret = double_to_numeric(arec, unsignd ? (double)ull : (double)ll, + data_ptr); if (! SQL_SUCCEEDED(ret)) { return ret; } @@ -1048,15 +1064,13 @@ SQLRETURN sql2c_longlong(esodbc_rec_st *arec, esodbc_rec_st *irec, case SQL_C_FLOAT: REJECT_IF_NULL_DEST_BUFF(stmt, data_ptr); - REJECT_IF_OOR(stmt, ll, -FLT_MAX, FLT_MAX, SQLREAL, float); - *(SQLREAL *)data_ptr = (SQLREAL)ll; + *(SQLREAL *)data_ptr = unsignd ? (SQLREAL)ull : (SQLREAL)ll; write_out_octets(octet_len_ptr, sizeof(SQLREAL), irec); break; case SQL_C_DOUBLE: REJECT_IF_NULL_DEST_BUFF(stmt, data_ptr); - REJECT_IF_OOR(stmt, ll, -DBL_MAX, DBL_MAX, SQLDOUBLE, double); - *(SQLDOUBLE *)data_ptr = (SQLDOUBLE)ll; + *(SQLDOUBLE *)data_ptr = unsignd ? (SQLDOUBLE)ull : (SQLDOUBLE)ll; write_out_octets(octet_len_ptr, sizeof(SQLDOUBLE), irec); break; @@ -1092,20 +1106,26 @@ SQLRETURN sql2c_longlong(esodbc_rec_st *arec, esodbc_rec_st *irec, } 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; + if (unsignd) { + REJECT_IF_OOR(ull, 0, ULONG_MAX, SQLUINTEGER, unsigned long); + ivl_val = (long long)ull; } else { - ivl_val = ll; + if (ll < 0) { + ivl_val = -ll; + ivl.interval_sign = SQL_TRUE; + } else { + ivl_val = ll; + } + REJECT_IF_OOR(ivl_val, 0, ULONG_MAX, SQLUINTEGER, + unsigned long); } - REJECT_IF_OOR(stmt, ivl_val, 0, ULONG_MAX, SQLUINTEGER, - unsigned long); ivl.interval_type = ivl_type; + /* all interval components are defined as SQLUINTEGER */ *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); + DBGH(stmt, "converted quad word 0x%llx to interval %lld type %d.", + qword, ivl_val, ivl_type); break; /*INDENT-ON*/ @@ -1121,7 +1141,7 @@ SQLRETURN sql2c_longlong(esodbc_rec_st *arec, esodbc_rec_st *irec, return SQL_SUCCESS; # undef REJECT_IF_OOR -# undef TRANSFER_LL +# undef TRANSFER_QW } static SQLRETURN double_to_bit(esodbc_rec_st *arec, esodbc_rec_st *irec, @@ -3602,14 +3622,14 @@ SQLRETURN convertability_check(esodbc_stmt_st *stmt, SQLINTEGER idx, return SQL_SUCCESS; } -/* 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 - * representation. */ +/* Converts a C/W-string to a u/llong or double; the xstr->wide must be set. + * Returns success of conversion, converted value, its type and trimmed string + * source. */ static BOOL xstr_to_number(esodbc_stmt_st *stmt, void *data_ptr, - SQLLEN *octet_len_ptr, xstr_st *xstr, SQLSMALLINT dest_type, void *dest) + SQLLEN *octet_len_ptr, xstr_st *xstr, t_number_st *dest) { int res; + void *xptr; /* "If StrLen_or_IndPtr is a null pointer, the driver assumes that all * input parameter values are non-NULL and that character and binary data @@ -3636,60 +3656,43 @@ static BOOL xstr_to_number(esodbc_stmt_st *stmt, void *data_ptr, if (xstr->wide) { wtrim_ws(&xstr->w); + xptr = &xstr->w; DBGH(stmt, "converting paramter value `" LWPDL "` to number.", LWSTR(&xstr->w)); - switch (dest_type) { - case SQL_C_SBIGINT: - res = str2bigint(&xstr->w, /*wide?*/TRUE, (SQLBIGINT *)dest, - /*strict?*/TRUE); - break; - case SQL_C_UBIGINT: - res = str2bigint(&xstr->w, /*wide?*/TRUE, (SQLUBIGINT *)dest, - /*strict?*/TRUE); - break; - case SQL_C_DOUBLE: - res = str2double(&xstr->w, /*wide?*/TRUE, (SQLDOUBLE *)dest, - /*strict?*/TRUE); - break; - default: - BUGH(stmt, "unexpected numerical type %hd.", dest_type); - res = -1; - } } else { trim_ws(&xstr->c); + xptr = &xstr->c; DBGH(stmt, "converting paramter value `" LCPDL "` to number.", LCSTR(&xstr->c)); - switch (dest_type) { - case SQL_C_SBIGINT: - res = str2bigint(&xstr->c, /*wide?*/FALSE, (SQLBIGINT *)dest, - /*strict?*/TRUE); - break; - case SQL_C_UBIGINT: - res = str2bigint(&xstr->c, /*wide?*/FALSE, (SQLUBIGINT *)dest, - /*strict?*/TRUE); - break; - case SQL_C_DOUBLE: - res = str2double(&xstr->c, /*wide?*/FALSE, (SQLDOUBLE *)dest, - /*strict?*/TRUE); - break; - default: - BUGH(stmt, "unexpected numerical type %hd.", dest_type); - res = -1; + } + + /* try to parse the number sequentially as long long, unsigned long long + * and finally double */ + + dest->type = SQL_C_SBIGINT; + res = str2bigint(xptr, xstr->wide, &dest->bint, /*strict?*/TRUE); + if (res < 0) { + dest->type = SQL_C_UBIGINT; + res = str2ubigint(xptr, xstr->wide, &dest->ubint, /*strict?*/TRUE); + if (res < 0) { + dest->type = SQL_C_DOUBLE; + res = str2double(xptr, xstr->wide, &dest->dbl, /*strct?*/TRUE); } } if (res < 0) { if (xstr->wide) { - ERRH(stmt, "can't convert `" LWPDL "` to type %hd number.", - LWSTR(&xstr->w), dest_type); + ERRH(stmt, "can't convert `" LWPDL "` to a number.", + LWSTR(&xstr->w)); } else { - ERRH(stmt, "can't convert `" LCPDL "` to type %hd number.", - LCSTR(&xstr->c), dest_type); + ERRH(stmt, "can't convert `" LCPDL "` to a number.", + LCSTR(&xstr->c)); } return FALSE; - } else { - return TRUE; } + + DBGH(stmt, "parameter value converted as type: %hd.", dest->type); + return TRUE; } @@ -3731,7 +3734,8 @@ SQLRETURN c2sql_boolean(esodbc_rec_st *arec, esodbc_rec_st *irec, void *data_ptr; SQLLEN *octet_len_ptr; xstr_st xstr; - double dbl; + SQLDOUBLE dbl; + t_number_st xnr; esodbc_stmt_st *stmt = arec->desc->hdr.stmt; if (! dest) { @@ -3751,10 +3755,17 @@ SQLRETURN c2sql_boolean(esodbc_rec_st *arec, esodbc_rec_st *irec, octet_len_ptr = deferred_address(SQL_DESC_OCTET_LENGTH_PTR, pos, arec); xstr.wide = ctype == SQL_C_WCHAR; - if (! xstr_to_number(stmt, data_ptr, octet_len_ptr, &xstr, - SQL_C_DOUBLE, &dbl)) { + if (! xstr_to_number(stmt, data_ptr, octet_len_ptr, &xstr, &xnr)) { RET_HDIAGS(stmt, SQL_STATE_22018); } + switch (xnr.type) { + case SQL_C_SBIGINT: dbl = (double)xnr.bint; break; + case SQL_C_UBIGINT: dbl = (double)xnr.ubint; break; + case SQL_C_DOUBLE: dbl = xnr.dbl; break; + default: + BUGH(stmt, "unexpected number type: %hd.", xnr.type); + RET_HDIAGS(stmt, SQL_STATE_HY000); + } ret = double_to_bool(stmt, dbl, &val); if (! SQL_SUCCEEDED(ret)) { return ret; @@ -3825,23 +3836,65 @@ SQLRETURN c2sql_boolean(esodbc_rec_st *arec, esodbc_rec_st *irec, return SQL_SUCCESS; } +/* SQL_BIGINT will be provided for both signed (SQL_C_SBIGINT) and unsigned + * (SQL_C_UBIGINT) types. SQLBindParameter()'s size and decimal digits + * parameters aren't relevant when parameter type is SQL_BIGINT. Which means + * that setting the LONG vs UNSIGNED_LONG ES type can only be value-dependent. + * ES will convert whatever value is given according to the provided type, so + * this needs to be set right by the driver. */ +void irec_promote_bigint_type(esodbc_rec_st *irec, t_number_st *tnr) +{ + esodbc_stmt_st *stmt; + + if (irec->es_type->data_type != SQL_BIGINT) { + return; + } + switch (tnr->type) { + default: + return; + case SQL_C_UBIGINT: + if (tnr->ubint <= LLONG_MAX) { + return; + } + break; + case SQL_C_DOUBLE: + /* only convert doubles that won't fit in a long long, but will in + * an unsigned long long */ + if (tnr->dbl <= LLONG_MAX || ULLONG_MAX < tnr->dbl) { + return; + } + break; + } + + stmt = irec->desc->hdr.stmt; + irec->es_type = stmt->hdr.dbc->ulong; + DBGH(stmt, "number type %hd promoted to UBIGINT.", tnr->type); +} + /* - * Copies a C/W-string representing a number out to send buffer. + * Copies a C/W-string representing a number out to the send buffer. So no + * string->number->string conversion is performed, any needed conversion is + * delegated to ES (which will for instance accept a float value with interer + * type or the other way around). Range and precision checks are performed. * wide: type of string; * min, max: target SQL numeric type's limits; - * fixed: target SQL numberic type (integer or floating); * dest: buffer's pointer; can be null (when eval'ing) needed space; * len: how much of the buffer is needed or has been used. */ static SQLRETURN string_to_number(esodbc_rec_st *arec, esodbc_rec_st *irec, - SQLULEN pos, BOOL wide, double *min, double *max, BOOL fixed, + SQLULEN pos, BOOL wide, t_number_st *min, t_number_st *max, char *dest, size_t *len) { - void *data_ptr; + void *data_ptr, *src; + size_t src_len; SQLLEN *octet_len_ptr; xstr_st xstr; - SQLDOUBLE dbl, abs_dbl; + t_number_st xnr; + SQLDOUBLE abs_dbl; int ret; + BOOL fixed; + SQLWCHAR wbuff[ESODBC_PRECISION_UINT64 + /*`+`*/1]; + SQLCHAR buff[ESODBC_PRECISION_UINT64 + /*`+`*/1]; esodbc_stmt_st *stmt = arec->desc->hdr.stmt; /* pointer to app's buffer */ @@ -3850,59 +3903,102 @@ static SQLRETURN string_to_number(esodbc_rec_st *arec, esodbc_rec_st *irec, octet_len_ptr = deferred_address(SQL_DESC_OCTET_LENGTH_PTR, pos, arec); xstr.wide = wide; - /* do a conversion check: use double, as a capture all cases - * value: ES/SQL will accept a float for an INTEGER param */ - if (! xstr_to_number(stmt, data_ptr, octet_len_ptr, &xstr, - SQL_C_DOUBLE, dest ? &dbl : NULL)) { - ERRH(stmt, "failed to convert param value to a double."); + if (! xstr_to_number(stmt, data_ptr, octet_len_ptr, &xstr, &xnr)) { + ERRH(stmt, "failed to convert param value to a number."); RET_HDIAGS(stmt, SQL_STATE_22018); } + irec_promote_bigint_type(irec, &xnr); + if (! dest) { - /* check is superfluous, but safer */ *len = wide ? xstr.w.cnt : xstr.c.cnt; return SQL_SUCCESS; } + src = data_ptr; + src_len = wide ? xstr.w.cnt : xstr.c.cnt; /* check against truncation, limits and any given precision */ - abs_dbl = dbl < 0 ? -dbl : dbl; - if (fixed) { - if (0 < abs_dbl - (SQLUBIGINT)abs_dbl) { - ERRH(stmt, "conversion of double %.6e to fixed would" - " truncate fractional digits", dbl); - RET_HDIAGS(stmt, SQL_STATE_22001); - } - if ((min && dbl < *min) || (max && *max < dbl)) { - ERRH(stmt, "converted double %.6e out of bounds " - "[%.6e, %.6e]", dbl, min ? *min : 0, max ? *max : 0); - /* spec requires 22001 here, but that is wrong? */ - RET_HDIAGS(stmt, SQL_STATE_22003); - } - } else { - if ((min && abs_dbl < *min) || (max && *max < abs_dbl)) { - ERRH(stmt, "converted abs double %.6e out of bounds " - "[%.6e, %.6e]", abs_dbl, min ? *min : 0, max ? *max : 0); - RET_HDIAGS(stmt, SQL_STATE_22003); + if (min && max) { + fixed = min->type != SQL_C_DOUBLE; + /* TODO: spec requires 22001 instead of 22003 for out of bounds + * conversion, but that seems incorrect? */ + switch (xnr.type) { + case SQL_C_SBIGINT: + if (fixed && (xnr.bint < min->bint || (xnr.bint > 0 && + max->ubint < (SQLUBIGINT)xnr.bint))) { + ERRH(stmt, "converted long long %lld out of bounds " + "[%ld, %llu].", xnr.bint, min->bint, max->ubint); + RET_HDIAGS(stmt, SQL_STATE_22003); + } + break; + case SQL_C_UBIGINT: + if (fixed && max->ubint < xnr.ubint) { + ERRH(stmt, "converted unsigned long long %llu out of " + "bounds [0, %llu].", xnr.ubint, max->ubint); + RET_HDIAGS(stmt, SQL_STATE_22003); + } + break; + case SQL_C_DOUBLE: + abs_dbl = xnr.dbl < 0 ? -xnr.dbl : xnr.dbl; + if (fixed) { + if (xnr.dbl < min->bint || max->ubint < xnr.dbl) { + ERRH(stmt, "converted double %.6e out of bounds " + "[0/%lld, %llu].", xnr.dbl, min->bint, + max->ubint); + RET_HDIAGS(stmt, SQL_STATE_22003); + } + if (0 < abs_dbl - (SQLUBIGINT)abs_dbl) { + ERRH(stmt, "conversion of double %.6e to fixed would" + " truncate fractional digits", xnr.dbl); + RET_HDIAGS(stmt, SQL_STATE_22001); + } + /* xxx_MAX limits will be rounded up when cast to double, + * the comparisons won't be "accurate" -> numbers higher + * than long long could be sent as long and higher than + * unsigned long long as such => cast to fixed point and + * serialize the result. */ + src = wide ? (void *)wbuff : (void *)buff; + src_len = irec->es_type == stmt->hdr.dbc->ulong + ? ui64tot((SQLUBIGINT)xnr.dbl, src, wide) + : i64tot((SQLBIGINT)xnr.dbl, src, wide); + if (src_len < 0) { + ERRNH(stmt, "failed to convert %.6e to its fixed point" + " string representation.", xnr.dbl); + RET_HDIAGS(stmt, SQL_STATE_HY000); + } + assert(0 < src_len); + } else { /* string_double -> floating point */ + if (abs_dbl < min->dbl || max->dbl < abs_dbl) { + ERRH(stmt, "converted double double %.6e out of bounds" + " [%.6e, %.6e].", abs_dbl, min->dbl, max->dbl); + RET_HDIAGS(stmt, SQL_STATE_22003); + } + } + break; + default: + BUGH(stmt, "unexpected number type: %hd.", xnr.type); + RET_HDIAGS(stmt, SQL_STATE_HY000); } } + // // TODO: check IRD precision against avail value? // - /* copy values from app's buffer directly */ - if (wide) { /* need a conversion to ANSI */ - *len = xstr.w.cnt; - ret = ascii_w2c((SQLWCHAR *)data_ptr, dest, *len); - assert(0 < ret); /* it converted to a float already */ + /* copy values directly from app's buffer */ + if (wide) { /* need a conversion to ascii */ + ret = ascii_w2c((SQLWCHAR *)src, dest, src_len); + assert(0 < ret); /* it converted to a number already */ } else { - *len = xstr.c.cnt; - memcpy(dest, data_ptr, *len); + memcpy(dest, src, src_len); } + *len = src_len; return SQL_SUCCESS; } +/* signed fixed-point number source to (stringified) number destination */ static SQLRETURN sfixed_to_number(esodbc_stmt_st *stmt, SQLBIGINT src, - double *min, double *max, char *dest, size_t *len) + t_number_st *min, t_number_st *max, char *dest, size_t *len) { if (! dest) { /* largest space it could occupy */ @@ -3912,10 +4008,20 @@ static SQLRETURN sfixed_to_number(esodbc_stmt_st *stmt, SQLBIGINT src, DBGH(stmt, "converting paramter value %lld as number.", src); - if ((min && src < *min) || (max && *max < src)) { - ERRH(stmt, "source value %lld out of range [%e, %e] for dest type", - src, *min, *max); - RET_HDIAGS(stmt, SQL_STATE_22003); + if (min && max) { + if (min->type != SQL_C_DOUBLE) { /* target number is fixed type */ + if (src < min->bint || (src > 0 && max->ubint < (SQLUBIGINT)src)) { + ERRH(stmt, "source value %lld out of range [%lld, %llu] for " + "dest type", src, min->bint, max->ubint); + RET_HDIAGS(stmt, SQL_STATE_22003); + } + } else { + if ((SQLDOUBLE)src < min->dbl || max->dbl < (SQLDOUBLE)src) { + ERRH(stmt, "source value %lld out of range [%e, %e] for dest " + "type", src, min->dbl, max->dbl); + RET_HDIAGS(stmt, SQL_STATE_22003); + } + } } assert(sizeof(SQLBIGINT) == sizeof(int64_t)); @@ -3925,9 +4031,15 @@ static SQLRETURN sfixed_to_number(esodbc_stmt_st *stmt, SQLBIGINT src, return SQL_SUCCESS; } -static SQLRETURN ufixed_to_number(esodbc_stmt_st *stmt, SQLUBIGINT src, - double *max, char *dest, size_t *len) +/* unsigned fixed-point number source to (stringified) number destination */ +static SQLRETURN ufixed_to_number(esodbc_rec_st *irec, SQLUBIGINT src, + t_number_st *max, char *dest, size_t *len) { + t_number_st tnr = (t_number_st){.ubint = src, .type = SQL_C_UBIGINT}; + esodbc_stmt_st *stmt = irec->desc->hdr.stmt; + + irec_promote_bigint_type(irec, &tnr); + if (! dest) { /* largest space it could occupy */ *len = ESODBC_PRECISION_UINT64; @@ -3936,13 +4048,17 @@ static SQLRETURN ufixed_to_number(esodbc_stmt_st *stmt, SQLUBIGINT src, DBGH(stmt, "converting paramter value %llu as number.", src); - if (src < 0 || (max && *max < src)) { - ERRH(stmt, "source value %llu out of range [0, %e] for dest type", - src, *max); - RET_HDIAGS(stmt, SQL_STATE_22003); + if (max) { + if (max->type != SQL_C_DOUBLE) { /* target number is fixed type */ + if (max->ubint < src) { + ERRH(stmt, "source value %llu out of range [0, %llu] for dest " + "type", src, max->ubint); + RET_HDIAGS(stmt, SQL_STATE_22003); + } + } } - assert(sizeof(SQLBIGINT) == sizeof(int64_t)); + assert(sizeof(SQLUBIGINT) == sizeof(int64_t)); *len = ui64tot(src, dest, /*wide?*/FALSE); assert(*len <= ESODBC_PRECISION_UINT64); @@ -3953,14 +4069,17 @@ static SQLRETURN ufixed_to_number(esodbc_stmt_st *stmt, SQLUBIGINT src, * any of the allowed SQL target type - numeric (fixed, floating, * decimal/numeric), string, binary - except boolean/bit. */ static SQLRETURN floating_to_number(esodbc_rec_st *irec, SQLDOUBLE src, - double *min, double *max, char *dest, size_t *len) + t_number_st *min, t_number_st *max, char *dest, size_t *len) { SQLSMALLINT prec; SQLULEN colsize; int cnt; SQLDOUBLE abs_src; + t_number_st tnr = (t_number_st){.dbl = src, .type = SQL_C_DOUBLE}; esodbc_stmt_st *stmt = irec->desc->hdr.stmt; + irec_promote_bigint_type(irec, &tnr); + if (! dest) { /* largest space it could occupy */ *len = DBL_BASE10_MAX_LEN + /*0-term, for printf*/1; @@ -3968,10 +4087,20 @@ static SQLRETURN floating_to_number(esodbc_rec_st *irec, SQLDOUBLE src, } abs_src = src < 0 ? -src : src; - if ((min && abs_src < *min) || (max && *max < abs_src)) { - ERRH(stmt, "source value %e out of range [%e, %e] for dest type", - src, *min, *max); - RET_HDIAGS(stmt, SQL_STATE_22003); + if (min && max) { + if (min->type != SQL_C_DOUBLE) { /* target number is fixed type */ + if (src < min->bint || max->ubint < src) { + ERRH(stmt, "source value %e out of range [%lld, %llu] for dest" + " type", src, min->bint, max->ubint); + RET_HDIAGS(stmt, SQL_STATE_22003); + } + } else { + if (abs_src < min->dbl || max->dbl < abs_src) { + ERRH(stmt, "source value %e out of range [%e, %e] for dest " + "type", src, min->dbl, max->dbl); + RET_HDIAGS(stmt, SQL_STATE_22003); + } + } } switch (irec->es_type->meta_type) { @@ -4129,8 +4258,9 @@ static SQLRETURN numeric_to_number(esodbc_rec_st *irec, void *data_ptr, return floating_to_number(irec, dbl, NULL, NULL, dest, len); } +/* convertion to (stringified) number */ SQLRETURN c2sql_number(esodbc_rec_st *arec, esodbc_rec_st *irec, - SQLULEN pos, double *min, double *max, BOOL fixed, char *dest, size_t *len) + SQLULEN pos, t_number_st *min, t_number_st *max, char *dest, size_t *len) { void *data_ptr; SQLSMALLINT ctype; @@ -4147,7 +4277,7 @@ SQLRETURN c2sql_number(esodbc_rec_st *arec, esodbc_rec_st *irec, case SQL_C_WCHAR: case SQL_C_CHAR: return string_to_number(arec, irec, pos, ctype == SQL_C_WCHAR, - min, max, fixed, dest, len); + min, max, dest, len); case SQL_C_BINARY: return binary_to_number(arec, irec, pos, dest, len); @@ -4170,7 +4300,7 @@ SQLRETURN c2sql_number(esodbc_rec_st *arec, esodbc_rec_st *irec, case SQL_C_ULONG: ullng = (SQLUBIGINT)*(SQLUINTEGER *)data_ptr; break; case SQL_C_UBIGINT: ullng = *(SQLUBIGINT *)data_ptr; break; } while (0); - return ufixed_to_number(stmt, ullng, max, dest, len); + return ufixed_to_number(irec, ullng, max, dest, len); do { case SQL_C_FLOAT: dbl = (SQLDOUBLE)*(SQLREAL *)data_ptr; break; @@ -4832,7 +4962,7 @@ static SQLRETURN c2sql_wstr2qstr(esodbc_rec_st *arec, esodbc_rec_st *irec, ERRH(stmt, "converting to multibyte string failed: 0x%x", err); RET_HDIAGS(stmt, SQL_STATE_HY000); } - assert(0 < octets); /* shouldn't not fail and return negative */ + assert(0 < octets); /* should not fail and return negative */ } else { octets = 0; } @@ -4865,7 +4995,7 @@ static SQLRETURN c2sql_number2qstr(esodbc_rec_st *arec, esodbc_rec_st *irec, *dest = '"'; } - ret = c2sql_number(arec, irec, pos, NULL,NULL, 0, dest + !!dest, len); + ret = c2sql_number(arec, irec, pos, NULL, NULL, dest + !!dest, len); if (dest) { /* compare lengths only once number has actually been converted */ diff --git a/driver/convert.h b/driver/convert.h index a6e6f7f1..136bf99f 100644 --- a/driver/convert.h +++ b/driver/convert.h @@ -40,20 +40,33 @@ BOOL update_crr_date(struct tm *now); SQLRETURN sql2c_string(esodbc_rec_st *arec, esodbc_rec_st *irec, SQLULEN pos, const wchar_t *wstr, size_t chars_0); -SQLRETURN sql2c_longlong(esodbc_rec_st *arec, esodbc_rec_st *irec, - SQLULEN pos, long long ll); +SQLRETURN sql2c_quadword(esodbc_rec_st *arec, esodbc_rec_st *irec, + SQLULEN pos, uint64_t qword, bool unsignd); SQLRETURN sql2c_double(esodbc_rec_st *arec, esodbc_rec_st *irec, SQLULEN pos, double dbl); + +static inline SQLRETURN sql2c_longlong(esodbc_rec_st *arec, + esodbc_rec_st *irec, SQLULEN pos, long long ll) { + return sql2c_quadword(arec, irec, pos, (uint64_t)ll, /*unsigned*/false); +} /* * SQL C -> SQL */ +typedef struct { + SQLSMALLINT type; /* SQL_C_SBIGINT, SQL_C_UBIGINT, SQL_C_DOUBLE */ + union { + SQLDOUBLE dbl; + SQLBIGINT bint; + SQLUBIGINT ubint; + }; +} t_number_st; /* typed number struct type */ + SQLRETURN c2sql_null(esodbc_rec_st *arec, esodbc_rec_st *irec, char *dest, size_t *len); SQLRETURN c2sql_boolean(esodbc_rec_st *arec, esodbc_rec_st *irec, SQLULEN pos, char *dest, size_t *len); SQLRETURN c2sql_number(esodbc_rec_st *arec, esodbc_rec_st *irec, - SQLULEN pos, double *min, double *max, BOOL fixed, char *dest, - size_t *len); + SQLULEN pos, t_number_st *min, t_number_st *max, char *dest, size_t *len); SQLRETURN c2sql_varchar(esodbc_rec_st *arec, esodbc_rec_st *irec, SQLULEN pos, char *dest, size_t *len); SQLRETURN c2sql_date_time(esodbc_rec_st *arec, esodbc_rec_st *irec, diff --git a/driver/defs.h b/driver/defs.h index 973be647..608a0c4a 100644 --- a/driver/defs.h +++ b/driver/defs.h @@ -444,6 +444,9 @@ /* -5: LONG */ #define ES_LONG_TO_CSQL SQL_C_SBIGINT #define ES_LONG_TO_SQL SQL_BIGINT +/* 2: UNSIGNED_LONG */ +#define ES_ULONG_TO_CSQL SQL_C_UBIGINT +#define ES_ULONG_TO_SQL SQL_BIGINT /* 6: HALF_FLOAT */ #define ES_HALF_TO_CSQL_FLOAT SQL_C_DOUBLE #define ES_HALF_TO_SQL_FLOAT SQL_FLOAT diff --git a/driver/handles.h b/driver/handles.h index 20e58f0c..9fbd8a24 100644 --- a/driver/handles.h +++ b/driver/handles.h @@ -187,6 +187,8 @@ typedef struct struct_dbc { /* configuration imposed lengths for the ES/SQL string types */ SQLUINTEGER varchar_limit; wstr_st varchar_limit_str; /* convenience w-string of varchar limit */ + esodbc_estype_st *ulong; /* the UNSIGNED_LONG type in es_types array */ + esodbc_estype_st *lgst_name; /* type with longest name */ CURL *curl; /* cURL handle */ CURLcode curl_err; diff --git a/driver/odbc.c b/driver/odbc.c index 18f31629..d770e2ac 100644 --- a/driver/odbc.c +++ b/driver/odbc.c @@ -28,6 +28,9 @@ clock_t thread_local in_ticks = 0; static BOOL driver_init() { + int cnt; + wchar_t exe_path[MAX_PATH + 1]; + if (! log_init()) { return FALSE; } @@ -42,6 +45,15 @@ static BOOL driver_init() _CrtSetReportFile(_CRT_ASSERT, _gf_log->handle); } #endif /* !NDEBUG */ + + cnt = GetModuleFileName(/*handle*/NULL, exe_path, + sizeof(exe_path)/sizeof(exe_path[0])); + if (cnt <= 0) { + ERRN("failed to read current process name."); + } else { + INFO("calling application: " LWPDL, cnt, exe_path); + } + INFO("initializing driver."); if (! queries_init()) { return FALSE; diff --git a/driver/queries.c b/driver/queries.c index 6a8057e5..65bbcbc7 100644 --- a/driver/queries.c +++ b/driver/queries.c @@ -1319,6 +1319,7 @@ SQLRETURN copy_one_row_json(esodbc_stmt_st *stmt, SQLULEN pos) SQLRETURN ret; SQLLEN *ind_len; long long ll; + unsigned long long ull; double dbl; const wchar_t *wstr; BOOL boolval; @@ -1397,6 +1398,13 @@ SQLRETURN copy_one_row_json(esodbc_stmt_st *stmt, SQLULEN pos) ret = sql2c_longlong(arec, irec, pos, ll); break; + case UJT_UnsignedLongLong: + ull = UJNumericUnsignedLongLong(obj); + DBGH(stmt, "value [%zd, %d] is unsigned long long: %llu.", + rowno, i + 1, ull); + ret = sql2c_quadword(arec, irec, pos, ull, /*unsigned*/true); + break; + case UJT_Double: dbl = UJNumericFloat(obj); DBGH(stmt, "value [%zd, %d] is double: %f.", rowno, i + 1, @@ -1519,6 +1527,7 @@ static SQLRETURN copy_one_row_cbor(esodbc_stmt_st *stmt, SQLULEN pos) bool boolval; double dbl; uint16_t ui16; + uint64_t ui64; float flt; ard = stmt->ard; @@ -1558,7 +1567,6 @@ static SQLRETURN copy_one_row_cbor(esodbc_stmt_st *stmt, SQLULEN pos) case CborMapType: case CborSimpleType: case CborUndefinedType: - case CborTagType: default: /* + CborInvalidType */ ERRH(stmt, "unexpected elem. of type 0x%x in row", elem_type); goto err; @@ -1581,7 +1589,7 @@ static SQLRETURN copy_one_row_cbor(esodbc_stmt_st *stmt, SQLULEN pos) case CborTextStringType: res = cbor_value_get_utf16_wstr(&obj, &wstr); CHK_RES(stmt, "failed to extract text string"); - DBGH(stmt, "value [%zd, %d] is string: [%zu] `" LWPDL "`.", + DBGH(stmt, "value [%zu, %d] is string: [%zu] `" LWPDL "`.", rowno, i + 1, wstr.cnt, LWSTR(&wstr)); /* UTF8/16 conversion terminates the string */ assert(wstr.str[wstr.cnt] == '\0'); @@ -1593,12 +1601,23 @@ static SQLRETURN copy_one_row_cbor(esodbc_stmt_st *stmt, SQLULEN pos) case CborIntegerType: res = cbor_value_get_int64_checked(&obj, &i64); CHK_RES(stmt, "failed to extract int64 value"); - DBGH(stmt, "value [%zd, %d] is integer: %I64d.", rowno, + DBGH(stmt, "value [%zu, %d] is integer: %I64d.", rowno, i + 1, i64); assert(sizeof(int64_t) == sizeof(long long)); ret = sql2c_longlong(arec, irec, pos, (long long)i64); break; + case CborTagType: + /* Elastic's Java BigInteger is CBOR-encoded as (tagged) + * Bignum. No other tag is expected in the protocol, so it's + * either an unsigned long long, or an error. */ + res = cbor_value_get_tagged_uint64(&obj, &ui64); + CHK_RES(stmt, "failed to extract tagged uint64 value"); + DBGH(stmt, "value [%zu, %d] is biginteger: %I64u.", rowno, + i + 1, ui64); + ret = sql2c_quadword(arec, irec, pos, ui64, /*unsigned*/true); + break; + /*INDENT-OFF*/ do { case CborHalfFloatType: @@ -1616,7 +1635,7 @@ static SQLRETURN copy_one_row_cbor(esodbc_stmt_st *stmt, SQLULEN pos) } while (0); CHK_RES(stmt, "failed to extract flt. point type 0x%x", elem_type); - DBGH(stmt, "value [%zd, %d] is double: %f.", rowno, + DBGH(stmt, "value [%zu, %d] is double: %f.", rowno, i + 1, dbl); ret = sql2c_double(arec, irec, pos, dbl); break; @@ -1625,7 +1644,7 @@ static SQLRETURN copy_one_row_cbor(esodbc_stmt_st *stmt, SQLULEN pos) case CborBooleanType: res = cbor_value_get_boolean(&obj, &boolval); CHK_RES(stmt, "failed to extract boolean value"); - DBGH(stmt, "value [%zd, %d] is boolean: %d.", rowno, + DBGH(stmt, "value [%zu, %d] is boolean: %d.", rowno, i + 1, boolval); /* 'When bit SQL data is converted to character C data, the * possible values are "0" and "1".' */ @@ -1697,13 +1716,19 @@ static SQLRETURN unpack_one_row_cbor(esodbc_stmt_st *stmt, SQLULEN pos) if (cbor_value_at_end(&it)) { ERRH(stmt, "current row %zd counts fewer elements: %hd than " "columns: %hd.", rowno, i + 1, ird->count); + goto err; } irec = &ird->recs[i]; irec->i_val.cbor = it; - assert(! cbor_value_is_tag(&it)); - res = cbor_value_advance(&it); - if (res != CborNoError) { + if (cbor_value_is_tag(&it)) { /* CborPositiveBignumTag (or error) */ + if ((res = cbor_value_skip_tag(&it)) != CborNoError) { + ERRH(stmt, "failed to advance past current tag in row array: " + "%s.", cbor_error_string(res)); + goto err; + } + } + if ((res = cbor_value_advance(&it)) != CborNoError) { ERRH(stmt, "failed to advance past current value in row array: " "%s.", cbor_error_string(res)); goto err; @@ -2798,8 +2823,7 @@ static SQLRETURN convert_param_val(esodbc_rec_st *arec, esodbc_rec_st *irec, SQLULEN pos, char *dest, size_t *len) { SQLLEN *ind_ptr; - double min, max; - BOOL fixed; + t_number_st min, max; esodbc_stmt_st *stmt = arec->desc->hdr.stmt; ind_ptr = deferred_address(SQL_DESC_INDICATOR_PTR, pos, arec); @@ -2821,41 +2845,35 @@ static SQLRETURN convert_param_val(esodbc_rec_st *arec, esodbc_rec_st *irec, do { /* JSON long */ - case SQL_BIGINT: /* LONG */ - min = (double)LLONG_MIN; /* precision warnings */ - max = (double)LLONG_MAX; - fixed = TRUE; + case SQL_BIGINT: /* LONG, UNSIGNED_LONG */ + min = (t_number_st){.bint = LLONG_MIN, .type = SQL_C_SBIGINT}; + max = (t_number_st){.ubint = ULLONG_MAX, .type = SQL_C_UBIGINT}; break; case SQL_INTEGER: /* INTEGER */ - min = LONG_MIN; - max = LONG_MAX; - fixed = TRUE; + min = (t_number_st){.bint = LONG_MIN, .type = SQL_C_SBIGINT}; + max = (t_number_st){.ubint = LONG_MAX, .type = SQL_C_UBIGINT}; break; case SQL_SMALLINT: /* SHORT */ - min = SHRT_MIN; - max = SHRT_MAX; - fixed = TRUE; + min = (t_number_st){.bint = SHRT_MIN, .type = SQL_C_SBIGINT}; + max = (t_number_st){.ubint = SHRT_MAX, .type = SQL_C_UBIGINT}; case SQL_TINYINT: /* BYTE */ - min = CHAR_MIN; - max = CHAR_MAX; - fixed = TRUE; + min = (t_number_st){.bint = CHAR_MIN, .type = SQL_C_SBIGINT}; + max = (t_number_st){.ubint = CHAR_MAX, .type = SQL_C_UBIGINT}; break; /* JSON double */ // TODO: check accurate limits for floats in ES/SQL case SQL_REAL: /* FLOAT */ - min = FLT_MIN; - max = FLT_MAX; - fixed = FALSE; + min = (t_number_st){.dbl = FLT_MIN, .type = SQL_C_DOUBLE}; + max = (t_number_st){.dbl = FLT_MAX, .type = SQL_C_DOUBLE}; break; case SQL_FLOAT: /* HALF_FLOAT */ case SQL_DOUBLE: /* DOUBLE, SCALED_FLOAT */ - min = DBL_MIN; - max = DBL_MAX; - fixed = FALSE; + min = (t_number_st){.dbl = DBL_MIN, .type = SQL_C_DOUBLE}; + max = (t_number_st){.dbl = DBL_MAX, .type = SQL_C_DOUBLE}; break; } while (0); - return c2sql_number(arec, irec, pos, &min, &max, fixed, dest, len); + return c2sql_number(arec, irec, pos, &min, &max, dest, len); /* JSON string */ case ES_WVARCHAR_SQL: /* KEYWORD, TEXT */ @@ -2938,6 +2956,8 @@ static SQLRETURN serialize_params_json(esodbc_stmt_st *stmt, char *dest, SQLRETURN ret; SQLSMALLINT i, count; size_t l, pos; + esodbc_estype_st *estype; + esodbc_dbc_st *dbc = HDRH(stmt)->dbc; pos = 0; if (dest) { @@ -2967,9 +2987,12 @@ static SQLRETURN serialize_params_json(esodbc_stmt_st *stmt, char *dest, pos += 2 * !!i + j_type.cnt; /* copy/eval ES/SQL type name */ - pos += json_escape(irec->es_type->type_name_c.str, - irec->es_type->type_name_c.cnt, dest ? dest + pos : NULL, - /*"unlimited" output buffer*/(size_t)-1); + /* Due to UNSIGNED_LONG type requiring value evaluation, which only + * happens below with convert_param_val(), a max is used for the first + * lenght-evaluation pass. */ + estype = dest ? irec->es_type : dbc->lgst_name; + pos += json_escape(estype->type_name_c.str, estype->type_name_c.cnt, + dest ? dest + pos : NULL, /*"unlimited" buffer*/(size_t)-1); if (dest) { /* copy 'value' JSON key name */ @@ -3174,6 +3197,7 @@ static SQLRETURN statement_params_len_cbor(esodbc_stmt_st *stmt, size_t len, l, max; esodbc_rec_st *arec, *irec; SQLRETURN ret; + esodbc_dbc_st *dbc = HDRH(stmt)->dbc; /* Initial all-encompassing array preamble. */ /* ~ [] */ @@ -3190,7 +3214,11 @@ static SQLRETURN statement_params_len_cbor(esodbc_stmt_st *stmt, /* ~ "type": "..." */ len += cbor_str_obj_len(sizeof(REQ_KEY_PARAM_TYPE) - 1); - len += cbor_str_obj_len(irec->es_type->type_name.cnt); + /* Normally, irec->es_type->type_name.cnt should be used here. + * However, due to UNSIGNED_LONG type requiring value evaluation, + * which only happens below with convert_param_val(), a max is instead + * used for now. */ + len += cbor_str_obj_len(dbc->lgst_name->type_name.cnt); /* ~ "value": "..." */ len += cbor_str_obj_len(sizeof(REQ_KEY_PARAM_VAL) - 1); diff --git a/driver/tinycbor.c b/driver/tinycbor.c index d29b69c9..77c9c4a8 100644 --- a/driver/tinycbor.c +++ b/driver/tinycbor.c @@ -189,6 +189,66 @@ CborError cbor_container_is_empty(CborValue cont, BOOL *empty) return CborNoError; } +CborError cbor_value_get_tagged_uint64(CborValue *it, uint64_t *val) +{ + CborError res; + CborTag type; + cstr_st bstr; /* byte string */ + size_t val_offt, i; + + assert(cbor_value_is_tag(it)); + if ((res = cbor_value_get_tag(it, &type)) != CborNoError) { + ERR("failed to extract tag."); + return res; + } + if (type != CborPositiveBignumTag) { + ERR("tag type `%llu` not a positive bignum.", type); + return CborErrorInappropriateTagForType; + } + if ((res = cbor_value_advance_fixed(it)) != CborNoError) { + ERR("failed to advance past tag header."); + return res; + } + if (! cbor_value_is_byte_string(it)) { + ERR("tag content type `%d` not byte string.", cbor_value_get_type(it)); + return CborErrorImproperValue; + } + /* only expecting/supporting single-chunk bignums */ + if (! cbor_value_is_length_known(it)) { + ERR("byte string length is unknown."); + return CborErrorUnknownLength; + } + res = cbor_value_get_string_chunk(it, &bstr.str, &bstr.cnt); + if (res != CborNoError) { + ERR("failed to extract byte string chunk reference."); + return res; + } + + /* Jackson will encode values in range [1<<63, 1<<64-1] on 9 bytes -> + * allow arbitrary number of leading 0s. */ + i = 0; + if (sizeof(*val) < bstr.cnt) { + for (; sizeof(*val) < bstr.cnt - i; i ++) { + if (bstr.str[i] != 0) { + ERR("non-zero byte at offset %zu: 0x%x. bignum value exceeds " + "uint64_t range.", i, bstr.str[i]); + return CborErrorImproperValue; + } + } + val_offt = 0; + } else { + val_offt = sizeof(*val) - bstr.cnt; + } + + *val = 0LLU; + memcpy((uint8_t *)val + val_offt, bstr.str + i, bstr.cnt - i); +#if REG_DWORD == REG_DWORD_LITTLE_ENDIAN + *val = _byteswap_uint64(*val); +#endif /* LE */ + + return cbor_value_advance(it); +} + static BOOL enlist_utf_buffer(void *old, void *new) { void **r; diff --git a/driver/tinycbor.h b/driver/tinycbor.h index 6768d9e7..c3685e9c 100644 --- a/driver/tinycbor.h +++ b/driver/tinycbor.h @@ -86,6 +86,7 @@ CborError cbor_map_lookup_keys(CborValue *map, size_t cnt, CborError cbor_container_count(CborValue cont, size_t *count); CborError cbor_get_array_count(CborValue arr, size_t *count); CborError cbor_container_is_empty(CborValue cont, BOOL *empty); +CborError cbor_value_get_tagged_uint64(CborValue *it, uint64_t *val); CborError cbor_value_get_utf16_wstr(CborValue *it, wstr_st *utf8); void tinycbor_cleanup(); diff --git a/driver/util.c b/driver/util.c index 68314811..30f81cfb 100644 --- a/driver/util.c +++ b/driver/util.c @@ -63,7 +63,7 @@ int str2ubigint(void *val, BOOL wide, SQLUBIGINT *out, BOOL strict) } assert(sizeof(SQLUBIGINT) == sizeof(uint64_t)); if (i < ESODBC_PRECISION_UINT64 - 1) { - res *= 10; + res *= 10ULL; res += digit; } else { /* would it overflow? */ @@ -71,7 +71,7 @@ int str2ubigint(void *val, BOOL wide, SQLUBIGINT *out, BOOL strict) errno = ERANGE; return -1; } else { - res *= 10; + res *= 10ULL; } if (ULLONG_MAX - res < digit) { errno = ERANGE; diff --git a/test/connected_dbc.h b/test/connected_dbc.h index 16e596b5..ef337559 100644 --- a/test/connected_dbc.h +++ b/test/connected_dbc.h @@ -67,6 +67,7 @@ extern "C" { [\"LONG\", -5, 19, \"'\", \"'\", null, 2, false, 3, false, false, false, null, 0, 0, -5, 0, 10, null],\ [\"BINARY\", -2, 2147483647, \"'\", \"'\", null, 2, false, 3, true, false, false, null, null, null, -2, 0, null, null],\ [\"NULL\", 0, 0, \"'\", \"'\", null, 2, false, 3, true, false, false, null, null, null, 0, 0, null, null],\ + [\"UNSIGNED_LONG\", 2, 20, \"'\", \"'\", null, 2, false, 3, true, false, false, null, 0, 0, 2, 0, 10, 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, 3, \"'\", \"'\", null, 2, false, 3, false, false, false, null, 3, 3, 6, 0, 2, null],\ diff --git a/test/test_conversion_c2sql_numeric.cc b/test/test_conversion_c2sql_numeric.cc index cd3cda89..75488d9d 100644 --- a/test/test_conversion_c2sql_numeric.cc +++ b/test/test_conversion_c2sql_numeric.cc @@ -60,7 +60,6 @@ TEST_F(ConvertC2SQL_Numeric, CStr_Short2Integer_fail_22018) assertState(L"22018"); } -/* note: test name used in test */ TEST_F(ConvertC2SQL_Numeric, CStr_LLong2Long) { prepareStatement(); @@ -75,6 +74,40 @@ TEST_F(ConvertC2SQL_Numeric, CStr_LLong2Long) "\"value\": 9223372036854775807}]"); } +TEST_F(ConvertC2SQL_Numeric, CStr_ULLong2ULong) +{ + prepareStatement(); + + SQLCHAR val[] = "18446744073709551615"; /* ULLONG_MAX */ + ret = SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, + SQL_BIGINT, /*size*/0, /*decdigits*/0, 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)); + + assertRequest("[{\"type\": \"UNSIGNED_LONG\", " + "\"value\": 18446744073709551615}]"); +} + +TEST_F(ConvertC2SQL_Numeric, CStr_ULLong2ULong_fail_22003) +{ + prepareStatement(); + + SQLCHAR val[] = "18446744073709561616"; /* ULLONG_MAX + 10000 */ + ret = SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, + SQL_BIGINT, /*size*/0, /*decdigits*/0, val, SQL_NTSL, + /*IndLen*/NULL); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + cstr_st buff = {NULL, 0}; + ret = serialize_statement((esodbc_stmt_st *)stmt, &buff); + ASSERT_FALSE(SQL_SUCCEEDED(ret)); + assertState(L"22003"); +} + TEST_F(ConvertC2SQL_Numeric, CStr_LLong2Integer_fail_22003) { prepareStatement(); @@ -92,22 +125,55 @@ TEST_F(ConvertC2SQL_Numeric, CStr_LLong2Integer_fail_22003) } -/* note: test name used in test */ -TEST_F(ConvertC2SQL_Numeric, CStr_Float2Long) +TEST_F(ConvertC2SQL_Numeric, CStr_Float2Long_wrap) { prepareStatement(); - SQLCHAR val[] = "9223372036854775806.12345"; /* LLONG_MAX.12345 - 1 */ + SQLCHAR val[] = "9223372036854775808.0"; /* LLONG_MAX.0 + 1 */ ret = SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_BIGINT, /*size*/0, /*decdigits*/0, val, sizeof(val) - /*\0*/1, /*IndLen*/NULL); ASSERT_TRUE(SQL_SUCCEEDED(ret)); + // 9223372036854775808.0 == (double)LLONG_MAX, so the promotion to + // UNSIGNED_LONG won't happen -> + // (long long)9223372036854775808.0 == LLONG_MIN assertRequest("[{\"type\": \"LONG\", " - "\"value\": 9223372036854775806.12345}]"); + "\"value\": -9223372036854775808}]"); +} + +TEST_F(ConvertC2SQL_Numeric, WStr_Float2ULong) +{ + prepareStatement(); + + SQLWCHAR val[] = L"18446744073709541615.0"; /* ULLONG_MAX.0 - 10000 */ + ret = SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_WCHAR, + SQL_BIGINT, /*size*/0, /*decdigits*/0, val, + sizeof(val)/sizeof(*val) - /*\0*/1, /*IndLen*/NULL); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + // conversion of the double string to double is approximate, so conversion + // back to ULL won't contain same value + assertRequest("[{\"type\": \"UNSIGNED_LONG\", " + "\"value\": 18446744073709541376}]"); +} + +TEST_F(ConvertC2SQL_Numeric, CStr_Float2ULong_fail_22003) +{ + prepareStatement(); + + SQLCHAR val[] = "-9223372036854785808."; /* LLONG_MIN.0 - 10000 */ + ret = SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, + SQL_BIGINT, /*size*/0, /*decdigits*/0, val, sizeof(val) - /*\0*/1, + /*IndLen*/NULL); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + cstr_st buff = {NULL, 0}; + ret = serialize_statement((esodbc_stmt_st *)stmt, &buff); + ASSERT_FALSE(SQL_SUCCEEDED(ret)); + assertState(L"22003"); } -/* note: test name used in test */ TEST_F(ConvertC2SQL_Numeric, WStr_Byte2Integer) { prepareStatement(); @@ -121,7 +187,6 @@ TEST_F(ConvertC2SQL_Numeric, WStr_Byte2Integer) assertRequest("[{\"type\": \"INTEGER\", \"value\": -128}]"); } -/* note: test name used in test */ TEST_F(ConvertC2SQL_Numeric, WStr_Double2HFloat) { prepareStatement(); @@ -136,7 +201,6 @@ TEST_F(ConvertC2SQL_Numeric, WStr_Double2HFloat) "\"value\": -12345678901234567890.123456789}]"); } -/* note: test name used in test */ TEST_F(ConvertC2SQL_Numeric, WStr_Double2SFloat) { prepareStatement(); @@ -167,7 +231,6 @@ TEST_F(ConvertC2SQL_Numeric, WStr_Double2Real_fail_22003) assertState(L"22003"); } -/* note: test name used in test */ TEST_F(ConvertC2SQL_Numeric, Short2Integer) { prepareStatement(); @@ -181,7 +244,6 @@ TEST_F(ConvertC2SQL_Numeric, Short2Integer) assertRequest("[{\"type\": \"INTEGER\", \"value\": -12345}]"); } -/* note: test name used in test */ TEST_F(ConvertC2SQL_Numeric, LLong2Long) { prepareStatement(); @@ -196,6 +258,34 @@ TEST_F(ConvertC2SQL_Numeric, LLong2Long) "\"value\": 9223372036854775807}]"); } +TEST_F(ConvertC2SQL_Numeric, ULLong2Long) +{ + prepareStatement(); + + SQLBIGINT val = 9223372036854775807; /* LLONG_MAX */ + ret = SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_UBIGINT, + SQL_BIGINT, /*size*/0, /*decdigits*/0, &val, sizeof(val), + /*IndLen*/NULL); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + assertRequest("[{\"type\": \"LONG\", " + "\"value\": 9223372036854775807}]"); +} + +TEST_F(ConvertC2SQL_Numeric, ULLong2ULong) +{ + prepareStatement(); + + SQLUBIGINT val = 9223372036854775808; /* LLONG_MAX + 1 */ + ret = SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_UBIGINT, + SQL_BIGINT, /*size*/0, /*decdigits*/0, &val, sizeof(val), + /*IndLen*/NULL); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + assertRequest("[{\"type\": \"UNSIGNED_LONG\", " + "\"value\": 9223372036854775808}]"); +} + TEST_F(ConvertC2SQL_Numeric, LLong2Integer_fail_22003) { prepareStatement(); @@ -213,7 +303,23 @@ TEST_F(ConvertC2SQL_Numeric, LLong2Integer_fail_22003) } -/* note: test name used in test */ +TEST_F(ConvertC2SQL_Numeric, ULLong2Integer_fail_22003) +{ + prepareStatement(); + + SQLUBIGINT val = ULLONG_MAX; + ret = SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_UBIGINT, + SQL_INTEGER, /*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_FALSE(SQL_SUCCEEDED(ret)); + assertState(L"22003"); + +} + TEST_F(ConvertC2SQL_Numeric, Float2Long) { prepareStatement(); @@ -228,7 +334,6 @@ TEST_F(ConvertC2SQL_Numeric, Float2Long) "\"value\": 9223372036854775808}]"); // == (double)LLONG_MAX: XXX } -/* note: test name used in test */ TEST_F(ConvertC2SQL_Numeric, Byte2Integer) { prepareStatement(); @@ -242,7 +347,6 @@ TEST_F(ConvertC2SQL_Numeric, Byte2Integer) assertRequest("[{\"type\": \"INTEGER\", \"value\": -128}]"); } -/* note: test name used in test */ TEST_F(ConvertC2SQL_Numeric, Double2HFloat) { prepareStatement(); @@ -273,7 +377,6 @@ TEST_F(ConvertC2SQL_Numeric, Double2Real_fail_22003) assertState(L"22003"); } -/* note: test name used in test */ TEST_F(ConvertC2SQL_Numeric, Bin_LLong2Long) { prepareStatement(); @@ -306,7 +409,6 @@ TEST_F(ConvertC2SQL_Numeric, Bin_Byte2Integer_fail_HY090) assertState(L"HY090"); } -/* note: test name used in test */ TEST_F(ConvertC2SQL_Numeric, Bin_Real2HFloat) { prepareStatement(); @@ -322,7 +424,6 @@ TEST_F(ConvertC2SQL_Numeric, Bin_Real2HFloat) "\"value\": -123456.789}]"); } -/* note: test name used in test */ TEST_F(ConvertC2SQL_Numeric, Numeric2HFloat) { prepareStatement(); diff --git a/test/test_conversion_sql2c_interval.cc b/test/test_conversion_sql2c_interval.cc index d7b8603c..3c3b98d6 100644 --- a/test/test_conversion_sql2c_interval.cc +++ b/test/test_conversion_sql2c_interval.cc @@ -255,6 +255,72 @@ TEST_F(ConvertSQL2C_Interval, Float2Interval_violation_07006) assertState(L"07006"); } +TEST_F(ConvertSQL2C_Interval, ULong2Interval_second) +{ +# undef SQL_VAL +# undef SQL +# define SQL_RAW 4294967295 +# define SQL_VAL STR(SQL_RAW) +# define SQL "SELECT " SQL_VAL + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"unsigned_long\"}\ + ],\ + \"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_EQ(ind_len, sizeof(is)); + + SQL_INTERVAL_STRUCT is2 = {0}; + is2.interval_type = SQL_IS_SECOND; + is2.intval.day_second.second = SQL_RAW; + is2.interval_sign = SQL_FALSE; + ASSERT_TRUE(memcmp(&is, &is2, sizeof(is)) == 0); +} + +TEST_F(ConvertSQL2C_Interval, ULong2Interval_second_violation_22003) +{ +# undef SQL_RAW +# undef SQL_VAL +# undef SQL +# define SQL_RAW 4294967296 +# define SQL_VAL STR(SQL_RAW) +# define SQL "SELECT " SQL_VAL + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"unsigned_long\"}\ + ],\ + \"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_FALSE(SQL_SUCCEEDED(ret)); + assertState(L"22003"); +} + + TEST_F(ConvertSQL2C_Interval, Integer2Interval_multi_violation_07006) { # undef SQL_VAL diff --git a/test/test_conversion_sql2c_ints.cc b/test/test_conversion_sql2c_ints.cc index e96005bb..af1c7448 100644 --- a/test/test_conversion_sql2c_ints.cc +++ b/test/test_conversion_sql2c_ints.cc @@ -335,10 +335,8 @@ TEST_F(ConvertSQL2C_Ints, Byte2UShort_negative2unsigned) { ASSERT_TRUE(SQL_SUCCEEDED(ret)); ret = SQLFetch(stmt); - ASSERT_TRUE(SQL_SUCCEEDED(ret)); - - EXPECT_EQ(ind_len, sizeof(ushort)); - EXPECT_EQ(ushort, USHRT_MAX); + ASSERT_FALSE(SQL_SUCCEEDED(ret)); + assertState(L"22003"); } diff --git a/test/test_conversion_sql2c_ulong.cc b/test/test_conversion_sql2c_ulong.cc new file mode 100644 index 00000000..247a346f --- /dev/null +++ b/test/test_conversion_sql2c_ulong.cc @@ -0,0 +1,708 @@ +/* + * 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 + +/* placeholders; will be undef'd and redef'd */ +#define SQL_VAL +#define SQL /* attached for troubleshooting purposes */ + +namespace test { + +class ConvertSQL2C_ULong : public ::testing::Test, public ConnectedDBC { +}; + + +TEST_F(ConvertSQL2C_ULong, ULong2Char) { + +#undef SQL_VAL +#undef SQL +#define SQL_VAL "18446744073709551615" +#define SQL "CAST(" SQL_VAL " AS TEXT)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"unsigned_long\"}\ + ],\ + \"rows\": [\ + [" SQL_VAL "]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + SQLCHAR buff[sizeof(SQL_VAL)]; + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_CHAR, &buff, sizeof(buff), &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + EXPECT_EQ(ind_len, sizeof(SQL_VAL) - /*\0*/1); + EXPECT_STREQ((char/*4gtest*/*)buff, SQL_VAL); +} + +TEST_F(ConvertSQL2C_ULong, ULong2WChar) { + +#undef SQL_VAL +#undef SQL +#define SQL_VAL "18446744073709551614" +#define SQL "CAST(" SQL_VAL " AS W-TEXT)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"unsigned_long\"}\ + ],\ + \"rows\": [\ + [" SQL_VAL "]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + SQLWCHAR wbuff[sizeof(SQL_VAL)]; + 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(SQL_VAL) - /*\0*/1)); + EXPECT_STREQ(wbuff, MK_WPTR(SQL_VAL)); +} + + +TEST_F(ConvertSQL2C_ULong, ULong2Byte) { + +#undef SQL_VAL +#undef SQL +#define SQL_RAW 127 +#define SQL_VAL STR(SQL_RAW) +#define SQL "CAST(" SQL_VAL " AS TINYINT)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"unsigned_long\"}\ + ],\ + \"rows\": [\ + [" SQL_VAL "]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + SQLSCHAR schar; + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_STINYINT, &schar, sizeof(schar), + &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + EXPECT_EQ(ind_len, sizeof(schar)); + EXPECT_EQ(schar, SQL_RAW); +} + +TEST_F(ConvertSQL2C_ULong, ULong2UByte) { + +#undef SQL_RAW +#undef SQL_VAL +#undef SQL +#define SQL_RAW 255 +#define SQL_VAL STR(SQL_RAW) +#define SQL "CAST(" SQL_VAL " AS UTINYINT)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"unsigned_long\"}\ + ],\ + \"rows\": [\ + [" SQL_VAL "]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + SQLCHAR uchar; + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_UTINYINT, &uchar, sizeof(uchar), + &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + EXPECT_EQ(ind_len, sizeof(uchar)); + EXPECT_EQ(uchar, SQL_RAW); +} + +TEST_F(ConvertSQL2C_ULong, ULong2UByte_Fail_Range) { + +#undef SQL_RAW +#undef SQL_VAL +#undef SQL +#define SQL_RAW 256 +#define SQL_VAL STR(SQL_RAW) +#define SQL "CAST(" SQL_VAL " AS UTINYINT)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"unsigned_long\"}\ + ],\ + \"rows\": [\ + [" SQL_VAL "]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + SQLCHAR uchar; + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_UTINYINT, &uchar, sizeof(uchar), + &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_FALSE(SQL_SUCCEEDED(ret)); + assertState(L"22003"); +} + +TEST_F(ConvertSQL2C_ULong, ULong2Short) { + +#undef SQL_RAW +#undef SQL_VAL +#undef SQL +#define SQL_RAW 16384 +#define SQL_VAL STR(SQL_RAW) +#define SQL "CAST(" SQL_VAL " AS SHORT)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"unsigned_long\"}\ + ],\ + \"rows\": [\ + [" SQL_VAL "]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + SQLSMALLINT sshrt; + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_SHORT, &sshrt, sizeof(sshrt), + &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + EXPECT_EQ(ind_len, sizeof(sshrt)); + EXPECT_EQ(sshrt, SQL_RAW); +} + +TEST_F(ConvertSQL2C_ULong, ULong2UShort) { + +#undef SQL_RAW +#undef SQL_VAL +#undef SQL +#define SQL_RAW 32768 +#define SQL_VAL STR(SQL_RAW) +#define SQL "CAST(" SQL_VAL " AS USHORT)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"unsigned_long\"}\ + ],\ + \"rows\": [\ + [" SQL_VAL "]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + SQLUSMALLINT ushrt; + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_USHORT, &ushrt, sizeof(ushrt), + &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + EXPECT_EQ(ind_len, sizeof(ushrt)); + EXPECT_EQ(ushrt, SQL_RAW); +} + +TEST_F(ConvertSQL2C_ULong, ULong2Short_Fail_Range) { + +#undef SQL_RAW +#undef SQL_VAL +#undef SQL +#define SQL_RAW 16385 +#define SQL_VAL STR(SQL_RAW) +#define SQL "CAST(" SQL_VAL " AS SHORT)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"unsigned_long\"}\ + ],\ + \"rows\": [\ + [" SQL_VAL "]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + SQLSMALLINT sshrt; + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_UTINYINT, &sshrt, sizeof(sshrt), + &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_FALSE(SQL_SUCCEEDED(ret)); + assertState(L"22003"); +} + +TEST_F(ConvertSQL2C_ULong, ULong2Int) { + +#undef SQL_RAW +#undef SQL_VAL +#undef SQL +#define SQL_RAW 2147483647 +#define SQL_VAL STR(SQL_RAW) +#define SQL "CAST(" SQL_VAL " AS INT)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"unsigned_long\"}\ + ],\ + \"rows\": [\ + [" SQL_VAL "]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + SQLINTEGER sint; + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_LONG, &sint, sizeof(sint), + &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + EXPECT_EQ(ind_len, sizeof(sint)); + EXPECT_EQ(sint, SQL_RAW); +} + +TEST_F(ConvertSQL2C_ULong, ULong2UInt) { + +#undef SQL_RAW +#undef SQL_VAL +#undef SQL +#define SQL_RAW 4294967295 +#define SQL_VAL STR(SQL_RAW) +#define SQL "CAST(" SQL_VAL " AS UINT)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"unsigned_long\"}\ + ],\ + \"rows\": [\ + [" SQL_VAL "]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + SQLUINTEGER uint; + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_ULONG, &uint, sizeof(uint), + &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + EXPECT_EQ(ind_len, sizeof(uint)); + EXPECT_EQ(uint, SQL_RAW); +} + +TEST_F(ConvertSQL2C_ULong, ULong2UInt_Fail_Range) { + +#undef SQL_RAW +#undef SQL_VAL +#undef SQL +#define SQL_RAW 4294967296 +#define SQL_VAL STR(SQL_RAW) +#define SQL "CAST(" SQL_VAL " AS SHORT)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"unsigned_long\"}\ + ],\ + \"rows\": [\ + [" SQL_VAL "]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + SQLUINTEGER uint; + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_ULONG, &uint, sizeof(uint), + &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_FALSE(SQL_SUCCEEDED(ret)); + assertState(L"22003"); +} + +TEST_F(ConvertSQL2C_ULong, ULong2Long) { + +#undef SQL_RAW +#undef SQL_VAL +#undef SQL +#define SQL_RAW 9223372036854775807 +#define SQL_VAL STR(SQL_RAW) +#define SQL "CAST(" SQL_VAL " AS LONG)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"unsigned_long\"}\ + ],\ + \"rows\": [\ + [" SQL_VAL "]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + SQLBIGINT slong; + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_SBIGINT, &slong, sizeof(slong), + &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + EXPECT_EQ(ind_len, sizeof(slong)); + EXPECT_EQ(slong, SQL_RAW); +} + +TEST_F(ConvertSQL2C_ULong, ULong2ULong) { + +#undef SQL_RAW +#undef SQL_VAL +#undef SQL +#define SQL_RAW 18446744073709551615 +#define SQL_VAL STR(SQL_RAW) +#define SQL "CAST(" SQL_VAL " AS ULONG)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"unsigned_long\"}\ + ],\ + \"rows\": [\ + [" SQL_VAL "]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + SQLUBIGINT ulong; + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_UBIGINT, &ulong, sizeof(ulong), + &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + EXPECT_EQ(ind_len, sizeof(ulong)); + EXPECT_EQ(ulong, SQL_RAW); +} + +TEST_F(ConvertSQL2C_ULong, ULong2Long_Fail_Range) { + +#undef SQL_RAW +#undef SQL_VAL +#undef SQL +#define SQL_RAW 9223372036854775808 +#define SQL_VAL STR(SQL_RAW) +#define SQL "CAST(" SQL_VAL " AS LONG)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"unsigned_long\"}\ + ],\ + \"rows\": [\ + [" SQL_VAL "]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + SQLBIGINT slong; + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_SBIGINT, &slong, sizeof(slong), + &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_FALSE(SQL_SUCCEEDED(ret)); + assertState(L"22003"); +} + +TEST_F(ConvertSQL2C_ULong, ULong2Bit) { + +#undef SQL_RAW +#undef SQL_VAL +#undef SQL +#define SQL_RAW 1 +#define SQL_VAL STR(SQL_RAW) +#define SQL "CAST(" SQL_VAL " AS BIT)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"unsigned_long\"}\ + ],\ + \"rows\": [\ + [" SQL_VAL "]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + SQLCHAR bit; + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_BIT, &bit, sizeof(bit), + &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + EXPECT_EQ(ind_len, sizeof(bit)); + EXPECT_EQ(bit, SQL_RAW); +} + +TEST_F(ConvertSQL2C_ULong, ULong2Bit_Fail_Range) { + +#undef SQL_RAW +#undef SQL_VAL +#undef SQL +#define SQL_RAW 2 +#define SQL_VAL STR(SQL_RAW) +#define SQL "CAST(" SQL_VAL " AS BIT)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"unsigned_long\"}\ + ],\ + \"rows\": [\ + [" SQL_VAL "]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + SQLCHAR bit; + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_BIT, &bit, sizeof(bit), + &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_FALSE(SQL_SUCCEEDED(ret)); + assertState(L"22003"); +} + +TEST_F(ConvertSQL2C_ULong, ULong2Numeric) { + +#undef SQL_RAW +#undef SQL_VAL +#undef SQL +#define SQL_RAW 0xDeadBeef +#define SQL_VAL "3735928559" // 0xdeadbeef +#define SQL "CAST(" SQL_VAL " AS SQLNUMERIC)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"unsigned_long\"}\ + ],\ + \"rows\": [\ + [" SQL_VAL "]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + SQL_NUMERIC_STRUCT ns; + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_NUMERIC, &ns, sizeof(ns), &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + EXPECT_EQ(ind_len, sizeof(ns)); + EXPECT_EQ(ns.sign, 1); + EXPECT_EQ(ns.scale, 0); + EXPECT_EQ(ns.precision, sizeof(SQL_VAL) - 1); + assert(sizeof(ns.val) == 16); + long long expected = SQL_RAW; +#if REG_DWORD != REG_DWORD_LITTLE_ENDIAN + expected = _byteswap_ulong(expected); +#endif /* LE */ + EXPECT_EQ(*(uint64_t *)ns.val, expected); + EXPECT_EQ(((uint64_t *)ns.val)[1], 0L); +} + +TEST_F(ConvertSQL2C_ULong, ULong2Float) { + +#undef SQL_RAW +#undef SQL_VAL +#undef SQL +#define SQL_RAW 123456 +#define SQL_VAL STR(SQL_RAW) +#define SQL "CAST(" SQL_VAL " AS FLOAT)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"unsigned_long\"}\ + ],\ + \"rows\": [\ + [" SQL_VAL "]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + SQLREAL real; + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_FLOAT, &real, sizeof(real), + &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + EXPECT_EQ(ind_len, sizeof(real)); + EXPECT_EQ(real, SQL_RAW); /* float equality should hold for casts */ +} + +TEST_F(ConvertSQL2C_ULong, ULong2Double) { + +#undef SQL_RAW +#undef SQL_VAL +#undef SQL +#define SQL_RAW 18446744073709551615 +#define SQL_VAL STR(SQL_RAW) +#define SQL "CAST(" SQL_VAL " AS DOUBLE)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"unsigned_long\"}\ + ],\ + \"rows\": [\ + [" SQL_VAL "]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + SQLDOUBLE dbl; + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_DOUBLE, &dbl, sizeof(dbl), &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + EXPECT_EQ(ind_len, sizeof(dbl)); + EXPECT_EQ(dbl, SQL_RAW); /* float equality should hold for casts */ +} + +TEST_F(ConvertSQL2C_ULong, ULong2Binary) { + +#undef SQL_RAW +#undef SQL_VAL +#undef SQL +#define SQL_RAW 0xBeefed +#define SQL_VAL "12513261" // 0xbeefed +#define SQL "CAST(" SQL_VAL " AS BINARY)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"unsigned_long\"}\ + ],\ + \"rows\": [\ + [" SQL_VAL "]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + SQLBIGINT bin = LLONG_MAX; + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_BINARY, &bin, sizeof(bin), &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + EXPECT_EQ(ind_len, /*min aligned size for the value*/4); + if (4 < sizeof(bin)) { + bin &= 0x00000000ffffffff; /* the driver has only writtten 4 bytes */ + } + EXPECT_EQ(bin, SQL_RAW); +} + + +TEST_F(ConvertSQL2C_ULong, ULong2Binary_Fail_Range) { + +#undef SQL_RAW +#undef SQL_VAL +#undef SQL +#define SQL_RAW 0xBeefed +#define SQL_VAL "12513261" // 0xbeefed +#define SQL "CAST(" SQL_VAL " AS BINARY)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"unsigned_long\"}\ + ],\ + \"rows\": [\ + [" SQL_VAL "]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + SQLSMALLINT bin; + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_BINARY, &bin, sizeof(bin), &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_FALSE(SQL_SUCCEEDED(ret)); + assertState(L"22003"); +} + +} // test namespace + From d2b8e54d4842e86d9c6556cdfe8b8a82828bc2f0 Mon Sep 17 00:00:00 2001 From: Bogdan Pintea Date: Mon, 7 Feb 2022 23:00:00 +0100 Subject: [PATCH 2/3] Add unsigned long testing converage Add tests that pass and receive unsigned long values as arguments and result-set values. Change the CSV reconstitution to use the indices with given mapping (rather then all keywords). --- test/integration/data.py | 49 +++++++++++++++++++++++++++++++------ test/integration/testing.py | 16 +++++++++--- 2 files changed, 55 insertions(+), 10 deletions(-) diff --git a/test/integration/data.py b/test/integration/data.py index 01f5aa88..d7113df4 100644 --- a/test/integration/data.py +++ b/test/integration/data.py @@ -282,9 +282,32 @@ } } -ES_DATASET_BASE_URL = "https://raw.githubusercontent.com/elastic/elasticsearch/eda31b0ac00c952a52885902be59ac429b0ca81a/x-pack/plugin/sql/qa/src/main/resources/" +LOGS_UL_MAPPING =\ + { + "mappings": { + "properties": { + "@timestamp": { + "type": "date_nanos" + }, + "bytes_in": { + "type": "unsigned_long" + }, + "bytes_out": { + "type": "unsigned_long" + }, + "id": { + "type": "integer" + }, + "status": { + "type": "keyword" + } + } + } + } -ES_PROTO_CASE_BASE_URL = "https://raw.githubusercontent.com/elastic/elasticsearch/eda31b0ac00c952a52885902be59ac429b0ca81a/x-pack/plugin/sql/qa/src/main/java/org/elasticsearch/xpack/sql/qa/" +ES_BASE_URL = "https://raw.githubusercontent.com/elastic/elasticsearch/ad5a3fc81ee76465116cdff7ae3e1acfa22211e7/x-pack/plugin/sql/qa/server/src/main/" +ES_DATASET_BASE_URL = ES_BASE_URL + "resources/" +ES_PROTO_CASE_BASE_URL = ES_BASE_URL + "java/org/elasticsearch/xpack/sql/qa/" KIBANA_SAMPLES_BASE_URL = "https://raw.githubusercontent.com/elastic/kibana/3c3c9b2a154656f25e980ba3fa03d7325561c526/src/legacy/server/sample_data/data_sets" KIBANA_INDEX_PREFIX = "kibana_sample_data_" @@ -346,6 +369,9 @@ class TestData(object): EMPLOYEES_FILE = "employees.csv" EMPLOYEES_INDEX = "employees" EMP_TEST_INDEX = "test_emp" + LOGS_UL_FILE = "logs_unsigned_long.csv" + LOGS_UL_INDEX = "logs_unsigned_long" + LOGS_UL_TEST_INDEX = "test_logs_ul" PROTO_CASE_FILE = "SqlProtocolTestCase.java" @@ -539,7 +565,7 @@ def _load_tableau_sample(self, file_name, index_name, template, pipeline=None): self._post_ndjson(ndjsons, index_name, index_name if pipeline else None) self._wait_for_results(index_name) - def _load_elastic_sample(self, file_name, index_name): + def _load_elastic_index(self, file_name, index_name): ndjson = self._get_csv_as_ndjson(ES_DATASET_BASE_URL, file_name, index_name) if self.MODE_NOINDEX < self._mode: self._delete_if_needed(index_name) @@ -565,6 +591,11 @@ def _derive_with_mapping(self, src_index, dst_index, mapping_json): raise Exception("POST reindexing into %s failed with code: %s (content: %s) " % (dst_index, req.status_code, req.text)) + def _load_elastic_sample(self, file_name, src_index, dst_index, mapping_json): + self._load_elastic_index(file_name, src_index) + self._derive_with_mapping(src_index, dst_index, mapping_json) + self._dup_csv_attributes(src_index, dst_index) + def _get_kibana_file(self, sample_name, is_mapping=True): print("Fetching JS sample data for index '%s'." % sample_name) file_name = "field_mappings.js" if is_mapping else "%s.json.gz" % sample_name @@ -656,10 +687,9 @@ def load(self): self._load_tableau_sample(self.STAPLES_FILE, self.STAPLES_INDEX, STAPLES_TEMPLATE) self._load_tableau_sample(self.BATTERS_FILE, self.BATTERS_INDEX, BATTERS_TEMPLATE, BATTERS_PIPELINE) - self._load_elastic_sample(self.LIBRARY_FILE, self.LIBRARY_INDEX) - self._derive_with_mapping(self.LIBRARY_INDEX, self.LIB_TEST_INDEX, LIB_TEST_MAPPING) - self._load_elastic_sample(self.EMPLOYEES_FILE, self.EMPLOYEES_INDEX) - self._derive_with_mapping(self.EMPLOYEES_INDEX, self.EMP_TEST_INDEX, EMP_TEST_MAPPING) + self._load_elastic_sample(self.LIBRARY_FILE, self.LIBRARY_INDEX, self.LIB_TEST_INDEX, LIB_TEST_MAPPING) + self._load_elastic_sample(self.EMPLOYEES_FILE, self.EMPLOYEES_INDEX, self.EMP_TEST_INDEX, EMP_TEST_MAPPING) + self._load_elastic_sample(self.LOGS_UL_FILE, self.LOGS_UL_INDEX, self.LOGS_UL_TEST_INDEX, LOGS_UL_MAPPING) self._load_kibana_sample(self.ECOMMERCE_INDEX) self._load_kibana_sample(self.FLIGHTS_INDEX) @@ -675,6 +705,11 @@ def csv_attributes(self, csv_name): def has_csv_attributes(self): return 0 < len(self._csv_md5) + def _dup_csv_attributes(self, src_index, dst_index): + self._csv_md5[dst_index] = self._csv_md5[src_index] + self._csv_header[dst_index] = self._csv_header[src_index] + self._csv_lines[dst_index] = self._csv_lines[src_index] + def proto_tests(self): return self._proto_tests diff --git a/test/integration/testing.py b/test/integration/testing.py index 9d06be84..2afedfd7 100644 --- a/test/integration/testing.py +++ b/test/integration/testing.py @@ -11,6 +11,7 @@ import struct import ctypes import urllib3 +import decimal from elasticsearch import Elasticsearch from data import TestData, BATTERS_TEMPLATE @@ -53,7 +54,7 @@ def _reconstitute_csv(self, index_name): with self._pyodbc.connect(self._dsn, autocommit=True) as cnxn: csv = u"" cols = self._data.csv_attributes(index_name)[1] - fields = ",".join(cols) + fields = '"' + "\",\"".join(cols) + '"' with cnxn.execute("select %s from %s" % (fields, index_name)) as curs: csv += u",".join(cols) csv += u"\n" @@ -64,6 +65,8 @@ def _reconstitute_csv(self, index_name): val="" elif isinstance(val, datetime.datetime): val = val.isoformat() + "Z" + else: + val = str(val) vals.append(val) csv += u",".join(vals) csv += u"\n" @@ -211,6 +214,12 @@ def _type_to_instance(self, data_type, data_val): instance = int(data_val) elif data_type == "long": instance = int(data_val.strip("lL")) + elif data_type == "unsigned_long": + # test uses "UNSIGNED_LONG_MAX", a BigInteger instance + if data_val == "UNSIGNED_LONG_MAX": + data_val = 18446744073709551615 + # pyodbc will fail to handle large values unless provided as Decimal + instance = decimal.Decimal(data_val) elif data_type == "double": instance = float(data_val) elif data_type == "float": @@ -336,8 +345,9 @@ def perform(self): self._catalog_columns(use_catalog = False, use_surrogate = True) self._catalog_columns(use_catalog = True, use_surrogate = True) - self._as_csv(TestData.LIBRARY_INDEX) - self._as_csv(TestData.EMPLOYEES_INDEX) + self._as_csv(TestData.LIB_TEST_INDEX) + self._as_csv(TestData.EMP_TEST_INDEX) + self._as_csv(TestData.LOGS_UL_TEST_INDEX) self._count_all(TestData.CALCS_INDEX) self._count_all(TestData.STAPLES_INDEX) From 469345c5ae0718a7194d40e3923440d4d34561cf Mon Sep 17 00:00:00 2001 From: Bogdan Pintea Date: Mon, 7 Feb 2022 23:02:45 +0100 Subject: [PATCH 3/3] Skip advancing parser at end of tag When at the end of a tagged value, the CBOR parser no longer needs to be skipping the end-of-tag value, since the tagged value won't be inspected any further. --- driver/tinycbor.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/driver/tinycbor.c b/driver/tinycbor.c index 77c9c4a8..b318b6ef 100644 --- a/driver/tinycbor.c +++ b/driver/tinycbor.c @@ -231,7 +231,7 @@ CborError cbor_value_get_tagged_uint64(CborValue *it, uint64_t *val) for (; sizeof(*val) < bstr.cnt - i; i ++) { if (bstr.str[i] != 0) { ERR("non-zero byte at offset %zu: 0x%x. bignum value exceeds " - "uint64_t range.", i, bstr.str[i]); + "uint64_t range.", i, bstr.str[i]); return CborErrorImproperValue; } } @@ -246,7 +246,7 @@ CborError cbor_value_get_tagged_uint64(CborValue *it, uint64_t *val) *val = _byteswap_uint64(*val); #endif /* LE */ - return cbor_value_advance(it); + return CborNoError; } static BOOL enlist_utf_buffer(void *old, void *new)