diff --git a/.gitignore b/.gitignore index 842b9c96..5e260e24 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ *.swp driver/*.swp +cscope.out diff --git a/driver/connect.c b/driver/connect.c index fe3a9133..9b842477 100644 --- a/driver/connect.c +++ b/driver/connect.c @@ -957,7 +957,7 @@ static SQLRETURN process_config(esodbc_dbc_st *dbc, config_attrs_st *attrs) int n, cnt; SQLWCHAR urlw[ESODBC_MAX_URL_LEN]; BOOL secure; - long timeout, max_body_size, max_fetch_size; + long long timeout, max_body_size, max_fetch_size; /* * build connection URL @@ -1002,8 +1002,8 @@ static SQLRETURN process_config(esodbc_dbc_st *dbc, config_attrs_st *attrs) /* * request timeout for liburl: negative reset to 0 */ - if (! wstr2long(&attrs->timeout, &timeout)) { - ERRH(dbc, "failed to convert '" LWPDL "' to long.", + if (! wstr2llong(&attrs->timeout, &timeout)) { + ERRH(dbc, "failed to convert '" LWPDL "' to long long.", LWSTR(&attrs->timeout)); goto err; } @@ -1017,8 +1017,8 @@ static SQLRETURN process_config(esodbc_dbc_st *dbc, config_attrs_st *attrs) /* * set max body size */ - if (! wstr2long(&attrs->max_body_size, &max_body_size)) { - ERRH(dbc, "failed to convert '" LWPDL "' to long.", + if (! wstr2llong(&attrs->max_body_size, &max_body_size)) { + ERRH(dbc, "failed to convert '" LWPDL "' to long long.", LWSTR(&attrs->max_body_size)); goto err; } @@ -1034,8 +1034,8 @@ static SQLRETURN process_config(esodbc_dbc_st *dbc, config_attrs_st *attrs) /* * set max fetch size */ - if (! wstr2long(&attrs->max_fetch_size, &max_fetch_size)) { - ERRH(dbc, "failed to convert '" LWPDL "' to long.", + if (! wstr2llong(&attrs->max_fetch_size, &max_fetch_size)) { + ERRH(dbc, "failed to convert '" LWPDL "' to long long.", LWSTR(&attrs->max_fetch_size)); goto err; } diff --git a/driver/queries.c b/driver/queries.c index 93db3852..330c794a 100644 --- a/driver/queries.c +++ b/driver/queries.c @@ -9,6 +9,7 @@ #include /* WideCharToMultiByte() */ #include #include +#include #include "ujdecode.h" #include "timestamp.h" @@ -53,7 +54,7 @@ #define REJECT_AS_OOR(_stmt, _val, _fix_val, _target) /* Out Of Range */ \ do { \ if (_fix_val) { \ - ERRH(_stmt, "can't convert value %lld to %s: out of range", \ + ERRH(_stmt, "can't convert value %llx to %s: out of range", \ _val, STR(_target)); \ } else { \ ERRH(_stmt, "can't convert value %f to %s: out of range", \ @@ -972,7 +973,7 @@ static SQLRETURN double_to_numeric(esodbc_rec_st *arec, esodbc_rec_st *irec, memcpy(numeric->val, (char *)&ullng, sizeof(ullng)); memset(numeric->val+sizeof(ullng), 0, sizeof(numeric->val)-sizeof(ullng)); - DBGH(stmt, "double %.15f converted to numeric: .sign=%d, precision=%d " + DBGH(stmt, "double %.6e converted to numeric: .sign=%d, precision=%d " "(req: %d), .scale=%d (req: %d), .val:`" LCPDL "` (0x%lx).", src, numeric->sign, numeric->precision, arec->precision, numeric->scale, arec->scale, (int)sizeof(numeric->val), numeric->val, @@ -1318,11 +1319,11 @@ static SQLRETURN double_to_str(esodbc_rec_st *arec, esodbc_rec_st *irec, } } - if (wide) { /* 15-16 decimals precision for x64 double (TODO: 32b) */ - DBGH(stmt, "double %.16f converted to w-string `" LWPD "` on %zd " + if (wide) { + DBGH(stmt, "double %.6e converted to w-string `" LWPD "` on %zd " "octets (state: %d; scale: %d).", dbl, buff, octets, state, scale); } else { - DBGH(stmt, "double %.16f converted to string `" LCPD "` on %zd " + DBGH(stmt, "double %.6e converted to string `" LCPD "` on %zd " "octets (state: %d; scale: %d).", dbl, buff, octets, state, scale); } @@ -1440,7 +1441,7 @@ static SQLRETURN copy_double(esodbc_rec_st *arec, esodbc_rec_st *irec, return SQL_ERROR; } - DBGH(stmt, "REC@0x%p, data_ptr@0x%p, copied double: %f.", arec, + DBGH(stmt, "REC@0x%p, data_ptr@0x%p, copied double: %.6e.", arec, data_ptr, dbl); return SQL_SUCCESS; @@ -1808,6 +1809,12 @@ static SQLRETURN copy_string(esodbc_rec_st *arec, esodbc_rec_st *irec, void *data_ptr; SQLLEN *octet_len_ptr; esodbc_desc_st *ard, *ird; + SQLSMALLINT ctarget; + long long ll; + unsigned long long ull; + wstr_st wval; + double dbl; + SQLWCHAR *endp; stmt = arec->desc->hdr.stmt; ird = stmt->ird; @@ -1818,10 +1825,11 @@ static SQLRETURN copy_string(esodbc_rec_st *arec, esodbc_rec_st *irec, /* pointer to app's buffer */ data_ptr = deferred_address(SQL_DESC_DATA_PTR, pos, arec); - switch (get_c_target_type(arec, irec)) { + switch ((ctarget = get_c_target_type(arec, irec))) { case SQL_C_CHAR: return wstr_to_cstr(arec, irec, data_ptr, octet_len_ptr, wstr, chars_0); + case SQL_C_BINARY: /* treat binary as WCHAR */ case SQL_C_WCHAR: return wstr_to_wstr(arec, irec, data_ptr, octet_len_ptr, wstr, chars_0); @@ -1836,9 +1844,89 @@ static SQLRETURN copy_string(esodbc_rec_st *arec, esodbc_rec_st *irec, return wstr_to_time(arec, irec, data_ptr, octet_len_ptr, wstr, chars_0); + case SQL_C_TINYINT: + case SQL_C_STINYINT: + case SQL_C_SHORT: + case SQL_C_SSHORT: + case SQL_C_LONG: + case SQL_C_SLONG: + case SQL_C_SBIGINT: + /* trim any white spaces */ + wval.cnt = chars_0 - 1; + wval.str = (SQLWCHAR *)trim_ws(wstr, &wval.cnt); + /* convert to integer type */ + errno = 0; + if (! wstr2llong(&wval, &ll)) { /* string is 0-term -> wcstoll? */ + ERRH(stmt, "can't convert `" LWPD "` to long long.", wstr); + RET_HDIAGS(stmt, errno == ERANGE ? SQL_STATE_22003 : + SQL_STATE_22018); + } + DBGH(stmt, "string `" LWPD "` converted to LL=%lld.", wstr, ll); + /* delegate to existing functionality */ + return copy_longlong(arec, irec, pos, ll); + + case SQL_C_UTINYINT: + case SQL_C_USHORT: + case SQL_C_ULONG: + case SQL_C_UBIGINT: + /* trim any white spaces */ + wval.cnt = chars_0 - 1; + wval.str = (SQLWCHAR *)trim_ws(wstr, &wval.cnt); + /* convert to integer type */ + errno = 0; + if (! wstr2ullong(&wval, &ull)) { /* string is 0-term: wcstoull? */ + ERRH(stmt, "can't convert `" LWPD "` to unsigned long long.", + wstr); + RET_HDIAGS(stmt, errno == ERANGE ? SQL_STATE_22003 : + SQL_STATE_22018); + } + DBGH(stmt, "string `" LWPD "` converted to ULL=%llu.", wstr, ull); + if (ull <= LLONG_MAX) { + /* the cast is safe, delegate to existing functionality */ + return copy_longlong(arec, irec, pos, (long long)ull); + } + /* value is larger than what long long can hold: can only convert + * to SQLUBIGINT (and SQLULONG, if it has the same size), or fail + * as out-of-range */ + assert(sizeof(SQLUBIGINT) == sizeof(unsigned long long)); + if ((ctarget == SQL_C_UBIGINT) || (ctarget == SQL_C_ULONG && + sizeof(SQLUINTEGER) == sizeof(SQLUBIGINT))) { + /* write out the converted value */ + REJECT_IF_NULL_DEST_BUFF(stmt, data_ptr); + *(SQLUBIGINT *)data_ptr = (SQLUBIGINT)ull; + write_out_octets(octet_len_ptr, sizeof(SQLUBIGINT), irec); + DBGH(stmt, "converted string `" LWPD "` to " + "unsigned long long %llu.", wstr, ull); + } else { + REJECT_AS_OOR(stmt, ull, /*fixed?*/TRUE, "non-ULL"); + } + break; + + case SQL_C_FLOAT: + case SQL_C_DOUBLE: + case SQL_C_NUMERIC: + case SQL_C_BIT: + /* trim any white spaces */ + wval.cnt = chars_0 - 1; + wval.str = (SQLWCHAR *)trim_ws(wstr, &wval.cnt); + /* convert to double */ + errno = 0; + dbl = wcstod((wchar_t *)wval.str, (wchar_t **)&endp); + DBGH(stmt, "string `" LWPD "` converted to dbl=%.6e.", wstr, dbl); + /* if empty string, non-numeric or under/over-flow, bail out */ + if ((! wval.cnt) || (wval.str + wval.cnt != endp) || errno) { + ERRH(stmt, "can't convert `" LWPD "` to double.", wstr); + RET_HDIAGS(stmt, errno == ERANGE ? SQL_STATE_22003 : + SQL_STATE_22018); + } + /* delegate to existing functionality */ + return copy_double(arec, irec, pos, dbl); + + default: - // FIXME: convert data - FIXME; + BUGH(stmt, "unexpected unhandled data type: %d.", + get_c_target_type(arec, irec)); + return SQL_ERROR; } return SQL_SUCCESS; diff --git a/driver/util.c b/driver/util.c index c478b682..2c22b2dd 100644 --- a/driver/util.c +++ b/driver/util.c @@ -5,6 +5,7 @@ */ #include +#include #include "util.h" #include "log.h" @@ -24,46 +25,101 @@ BOOL wstr2bool(wstr_st *val) return TRUE; } -BOOL wstr2long(wstr_st *val, long *out) +BOOL wstr2ullong(wstr_st *val, unsigned long long *out) { - long res = 0, digit; + unsigned long long res, digit; + static const unsigned long long max_div_10 = ULLONG_MAX / 10ULL; int i = 0; - BOOL negative; if (val->cnt < 1) { + errno = EINVAL; return FALSE; + } else if (val->str[0] == L'+') { + i ++; } - switch (val->str[0]) { - case L'-': - negative = TRUE; - i ++; - break; - case '+': - negative = FALSE; - i ++; - break; - default: - negative = FALSE; - } - - for ( ; i < val->cnt; i ++) { + for (res = 0; i < val->cnt; i ++) { /* is it a number? */ if (val->str[i] < L'0' || L'9' < val->str[i]) { + errno = EINVAL; return FALSE; + } else { + digit = val->str[i] - L'0'; } - digit = val->str[i] - L'0'; - /* would it overflow?*/ - if (LONG_MAX - res < digit) { - return FALSE; + assert(sizeof(unsigned long long) == sizeof(uint64_t)); + if (i < ESODBC_PRECISION_UINT64 - 1) { + res *= 10; + res += digit; + } else { + /* would it overflow? */ + if (max_div_10 < res) { + errno = ERANGE; + return FALSE; + } else { + res *= 10; + } + if (ULLONG_MAX - res < digit) { + errno = ERANGE; + return FALSE; + } else { + res += digit; + } } - res *= 10; - res += digit; } - *out = negative ? - res : res; + *out = res; return TRUE; } +BOOL wstr2llong(wstr_st *val, long long *out) +{ + unsigned long long ull; + wstr_st uval; + int i = 0; + BOOL negative; + + if (val->cnt < 1) { + errno = EINVAL; + return FALSE; + } else { + switch (val->str[0]) { + case L'-': + negative = TRUE; + i ++; + break; + case '+': + negative = FALSE; + i ++; + break; + default: + negative = FALSE; + } + } + + uval = (wstr_st) { + .str = val->str + i, .cnt = val->cnt - i + }; + if (! wstr2ullong(&uval, &ull)) { + return FALSE; + } + if (negative) { + if ((unsigned long long)LLONG_MIN < ull) { + errno = ERANGE; + return FALSE; /* underflow */ + } else { + *out = -(long long)ull; + } + } else { + if ((unsigned long long)LLONG_MAX < ull) { + errno = ERANGE; + return FALSE; /* overflow */ + } else { + *out = (long long)ull; + } + } + return TRUE; +} + + size_t i64tot(int64_t i64, void *buff, BOOL wide) { if (wide) { diff --git a/driver/util.h b/driver/util.h index f214d5ae..fc34fe2d 100644 --- a/driver/util.h +++ b/driver/util.h @@ -130,7 +130,8 @@ typedef struct wstr { wmemncasecmp((s1)->str, (s2)->str, (s1)->cnt) == 0) BOOL wstr2bool(wstr_st *val); -BOOL wstr2long(wstr_st *val, long *out); +BOOL wstr2ullong(wstr_st *val, unsigned long long *out); +BOOL wstr2llong(wstr_st *val, long long *out); /* 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/test_conversion_sql2c_string.cc b/test/test_conversion_sql2c_string.cc new file mode 100644 index 00000000..3cf2c686 --- /dev/null +++ b/test/test_conversion_sql2c_string.cc @@ -0,0 +1,450 @@ +/* + * 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_RAW +#define SQL_VAL +#define SQL /* attached for troubleshooting purposes */ + +namespace test { + +class ConvertSQL2C_String : public ::testing::Test, public ConnectedDBC { +}; + + +TEST_F(ConvertSQL2C_String, String2Char) { + +#undef SQL_VAL +#undef SQL +#define SQL_VAL "abcdef" +#define SQL "CAST(" SQL_VAL " AS TEXT)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"text\"}\ + ],\ + \"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_String, String2WChar) { + +#undef SQL_VAL +#undef SQL +#define SQL_VAL "abcdef" +#define SQL "CAST(" SQL_VAL " AS KEYWORD)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"keyword\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + SQLWCHAR buff[sizeof(SQL_VAL)]; + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_WCHAR, &buff, sizeof(buff), + &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + EXPECT_EQ(ind_len, sizeof(*buff) * (sizeof(SQL_VAL) - /*\0*/1)); + EXPECT_STREQ((wchar_t/*4gtest*/*)buff, MK_WPTR(SQL_VAL)); +} + + +TEST_F(ConvertSQL2C_String, String2Char_zero_copy) { + +#undef SQL_VAL +#undef SQL +#define SQL_VAL "abcdef" +#define SQL "CAST(" SQL_VAL " AS TEXT)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"text\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + SQLCHAR buff[sizeof(SQL_VAL)] = {'x'}; + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_CHAR, &buff, 0, &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_EQ(buff[0], 'x'); +} + + +TEST_F(ConvertSQL2C_String, String2SLong) { + +#undef SQL_RAW +#undef SQL_VAL +#undef SQL +#define SQL_RAW 123 +#define SQL_VAL "+" STR(SQL_RAW) +#define SQL "CAST(" SQL_VAL " AS KEYWORD)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"keyword\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + SQLINTEGER val; + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_SLONG, &val, sizeof(val), &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + EXPECT_EQ(ind_len, sizeof(val)); + EXPECT_EQ(val, SQL_RAW); +} + + +TEST_F(ConvertSQL2C_String, String2SLong_min) { + +#undef SQL_RAW +#undef SQL_VAL +#undef SQL +#define SQL_RAW LONG_MIN +#ifdef _WIN32 +#define SQL_VAL "-2147483648" +#else // _WIN64 +#define SQL_VAL "-9223372036854775808" +#endif // _WIN64 +#define SQL "CAST(" SQL_VAL " AS KEYWORD)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"keyword\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + SQLINTEGER val; + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_SLONG, &val, sizeof(val), &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + EXPECT_EQ(ind_len, sizeof(val)); + EXPECT_EQ(val, SQL_RAW); +} + + +TEST_F(ConvertSQL2C_String, String2SLongLong_min) { + +#undef SQL_RAW +#undef SQL_VAL +#undef SQL +#define SQL_RAW LLONG_MIN +#define SQL_VAL "-9223372036854775808" +#define SQL "CAST(" SQL_VAL " AS KEYWORD)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"keyword\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + SQLBIGINT val; + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_SBIGINT, &val, sizeof(val), + &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + EXPECT_EQ(ind_len, sizeof(val)); + EXPECT_EQ(val, SQL_RAW); +} + + +TEST_F(ConvertSQL2C_String, String2ULongLong_max) { + +#undef SQL_RAW +#undef SQL_VAL +#undef SQL +#define SQL_RAW ULLONG_MAX +#define SQL_VAL "18446744073709551615" +#define SQL "CAST(" SQL_VAL " AS KEYWORD)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"keyword\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + SQLUBIGINT val; + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_UBIGINT, &val, sizeof(val), + &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + EXPECT_EQ(ind_len, sizeof(val)); + EXPECT_EQ(val, SQL_RAW); +} + + +TEST_F(ConvertSQL2C_String, String2ULongLong_fail_22018) { + +#undef SQL_RAW +#undef SQL_VAL +#undef SQL +#define SQL_RAW ULLONG_MAX +#define SQL_VAL "1844674407370955161X" +#define SQL "CAST(" SQL_VAL " AS KEYWORD)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"keyword\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + SQLUBIGINT val; + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_UBIGINT, &val, sizeof(val), + &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_FALSE(SQL_SUCCEEDED(ret)); + assertState(L"22018"); +} + + +TEST_F(ConvertSQL2C_String, String2ULongLong_fail_22003) { + +#undef SQL_RAW +#undef SQL_VAL +#undef SQL +#define SQL_RAW ULLONG_MAX +#define SQL_VAL "18446744073709551616" +#define SQL "CAST(" SQL_VAL " AS KEYWORD)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"keyword\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + SQLUBIGINT val; + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_UBIGINT, &val, sizeof(val), + &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_FALSE(SQL_SUCCEEDED(ret)); + assertState(L"22003"); +} + + +TEST_F(ConvertSQL2C_String, String2Float) { + +#undef SQL_RAW +#undef SQL_VAL +#undef SQL +#define SQL_RAW -0.333e-33 +#define SQL_VAL STR(SQL_RAW) +#define SQL "CAST(" SQL_VAL " AS KEYWORD)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"keyword\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + SQLREAL val = .0; + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_FLOAT, &val, sizeof(val), &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + EXPECT_EQ(ind_len, sizeof(val)); + EXPECT_LE(abs(val - SQL_RAW), 1e-33); +} + + +TEST_F(ConvertSQL2C_String, String2Float_fail_22003) { + +#undef SQL_RAW +#undef SQL_VAL +#undef SQL +#define SQL_RAW -0.333e-307 +#define SQL_VAL STR(SQL_RAW) +#define SQL "CAST(" SQL_VAL " AS KEYWORD)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"keyword\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + SQLREAL val = .0; + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_FLOAT, &val, sizeof(val), &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_FALSE(SQL_SUCCEEDED(ret)); + assertState(L"22003"); /* SQL_RAW fits a double, but not a float */ +} + +TEST_F(ConvertSQL2C_String, String2Double_fail_22003) { + +#undef SQL_RAW +#undef SQL_VAL +#undef SQL +#define SQL_RAW -1e-3080 +#define SQL_VAL STR(SQL_RAW) +#define SQL "CAST(" SQL_VAL " AS KEYWORD)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"keyword\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + SQLDOUBLE val = 1.; + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_DOUBLE, &val, sizeof(val), + &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_FALSE(SQL_SUCCEEDED(ret)); + assertState(L"22003"); /* SQL_RAW doen't fit a double */ +} + + +TEST_F(ConvertSQL2C_String, String2Bit) { + +#undef SQL_RAW +#undef SQL_VAL +#undef SQL +#define SQL_RAW 1.2 +#define SQL_VAL STR(SQL_RAW) +#define SQL "CAST(" SQL_VAL " AS KEYWORD)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"keyword\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + SQLCHAR val = -1; + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_BIT, &val, sizeof(val), &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + assertState(L"01S07"); + + EXPECT_EQ(ind_len, sizeof(val)); + EXPECT_LE(val, 1); +} + + + +} // test namespace +