From e450ecc05c164164d23df041f1f57804cca3b6dd Mon Sep 17 00:00:00 2001 From: Bogdan Pintea Date: Sun, 10 Jun 2018 12:30:21 +0200 Subject: [PATCH 01/15] add possiblity to build & run one test suite only build.bat suite=my_test.cc --- build.bat | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/build.bat b/build.bat index fbd8ef59..bdcc19d4 100644 --- a/build.bat +++ b/build.bat @@ -1,4 +1,4 @@ -@echo off +echo off rem rem Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one rem or more contributor license agreements. Licensed under the Elastic License; @@ -96,6 +96,8 @@ if /i not [%ARG:type=%] == [%ARG%] ( call:BUILDTYPE ) else ( echo Invoked without 'type', default applied (see usage^). + set MSBUILD_ARGS=/p:Configuration=Debug + set CFG_INTDIR=Debug ) REM absence of nobuild: invoke BUILD "function"; @@ -106,9 +108,9 @@ if /i [%ARG:nobuild=%] == [%ARG%] ( echo Invoked with 'nobuild', building skipped. ) -REM presence of 'test': invoke TEST "function" -if /i not [%ARG:test=%] == [%ARG%] ( - call:TEST +REM presence of 'test': invoke TESTS "function" +if /i not [%ARG:tests=%] == [%ARG%] ( + call:TESTS ) else ( REM Invoked without 'test': tests running skipped. ) @@ -237,11 +239,12 @@ REM USAGE function: output a usage message echo exports : dump the exported symbols in the DLL after - and echo only if - building the driver. echo all : build all artifacts (driver and tests^). - echo test : run all the defined tests: invoke the 'all' build, + echo tests : run all the defined tests: invoke the 'all' build, echo then the 'tests' build. echo suites : compile and run each test individually, stopping at the echo first failure; the 'all' or 'test' targets must be echo built beforehand. + echo suite=S : compile and run one test, S, individually. echo copy : copy the driver into the test dir (%INSTALL_DIR%^). echo regadd : register the driver into the registry; echo (needs Administrator privileges^). @@ -387,7 +390,7 @@ REM BUILD function: build various targets %CMAKE% !CMAKE_ARGS! .. ) - if /i not [%ARG:test=%] == [%ARG%] ( + if /i not [%ARG:tests=%] == [%ARG%] ( echo Building all the project (including tests^). MSBuild ALL_BUILD.vcxproj %MSBUILD_ARGS% ) else if /i not [%ARG:all=%] == [%ARG%] ( @@ -408,6 +411,24 @@ REM BUILD function: build various targets goto:eof ) ) + ) else if /i not [%ARG:suite==%] == [%ARG%] ( + REM cycle through the args, look for 'suite' token and use the + REM follow-up item + set prev= + for %%a in (%ARG:"=%) do ( + if /i [!prev!] == [suite] ( + set SUITE=%%a + echo Building and running one suite: !SUITE! + echo MSBuild test\%%a.vcxproj %MSBUILD_ARGS% + MSBuild test\%%a.vcxproj %MSBUILD_ARGS% + if not ERRORLEVEL 1 ( + test\%CFG_INTDIR%\%%a.exe + ) + goto:eof + ) else ( + set prev=%%a + ) + ) ) else ( echo Building the driver only. REM file name expansion, cmd style... @@ -420,8 +441,8 @@ REM BUILD function: build various targets goto:eof -REM TEST function: run the compiled tests -:TEST +REM TESTS function: run the compiled tests +:TESTS REM if called with nobuild, but test, this will trigger the build if not exist RUN_TESTS.vcxproj ( call:BUILD From 44b24f10c6d9386513b2ab0394cd9174b0130521 Mon Sep 17 00:00:00 2001 From: Bogdan Pintea Date: Sun, 10 Jun 2018 12:44:23 +0200 Subject: [PATCH 02/15] refactor write_wptr into write_wstr The refactoring avoid calculating the lenght of the writen wide char string, since that's nearly always known before the call. --- driver/catalogue.c | 21 ++--- driver/handles.c | 118 +++++++++++++++-------------- driver/handles.h | 15 ++-- driver/info.c | 185 ++++++++++++++++----------------------------- driver/info.h | 8 -- driver/util.c | 60 +++++++++++++++ driver/util.h | 9 +++ 7 files changed, 216 insertions(+), 200 deletions(-) diff --git a/driver/catalogue.c b/driver/catalogue.c index 5595199d..9762ce16 100644 --- a/driver/catalogue.c +++ b/driver/catalogue.c @@ -51,7 +51,7 @@ SQLSMALLINT copy_current_catalog(esodbc_dbc_st *dbc, SQLWCHAR *dest, SQLLEN row_cnt; SQLLEN ind_len = SQL_NULL_DATA; SQLWCHAR buff[ESODBC_MAX_IDENTIFIER_LEN]; - SQLWCHAR *catalog; + wstr_st catalog; if (! SQL_SUCCEEDED(EsSQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt))) { ERRH(dbc, "failed to alloc a statement handle."); @@ -76,7 +76,7 @@ SQLSMALLINT copy_current_catalog(esodbc_dbc_st *dbc, SQLWCHAR *dest, goto end; } else if (row_cnt <= 0) { WARNH(stmt, "Elasticsearch returned no current catalog."); - catalog = MK_WPTR(""); /* empty string, it's not quite an error */ + catalog = MK_WSTR(""); /* empty string, it's not quite an error */ } else { DBGH(stmt, "Elasticsearch catalogs rows count: %ld.", row_cnt); if (1 < row_cnt) { @@ -95,18 +95,19 @@ SQLSMALLINT copy_current_catalog(esodbc_dbc_st *dbc, SQLWCHAR *dest, } if (ind_len <= 0) { WARNH(dbc, "NULL catalog received."); /*tho maybe != NULL_DATA */ - catalog = MK_WPTR(""); + catalog = MK_WSTR(""); } else { - catalog = buff; - DBGH(dbc, "current catalog (first value returned): `" LWPD "`.", - catalog); + catalog = (wstr_st) { + buff, ind_len + }; + DBGH(dbc, "current catalog (first value returned): `" LWPDL "`.", + LWSTR(&catalog)); } } - if (! SQL_SUCCEEDED(write_wptr(&dbc->hdr.diag, dest, catalog, room, - &used))) { - ERRH(dbc, "failed to copy catalog: `" LWPD "`.", catalog); - used = -1; /* write_wptr() can change pointer, and still fail */ + if (! SQL_SUCCEEDED(write_wstr(dbc, dest, &catalog, room, &used))) { + ERRH(dbc, "failed to copy catalog: `" LWPDL "`.", LWSTR(&catalog)); + used = -1; /* write_wstr() can change pointer, and still fail */ } end: diff --git a/driver/handles.c b/driver/handles.c index f19f5c62..760cb6b6 100644 --- a/driver/handles.c +++ b/driver/handles.c @@ -17,13 +17,13 @@ static void free_rec_fields(esodbc_rec_st *rec) { int i; SQLWCHAR **wptr[] = { - &rec->base_column_name, - &rec->base_table_name, - &rec->catalog_name, - &rec->label, - &rec->name, - &rec->schema_name, - &rec->table_name, + &rec->base_column_name.str, + &rec->base_table_name.str, + &rec->catalog_name.str, + &rec->label.str, + &rec->name.str, + &rec->schema_name.str, + &rec->table_name.str, }; for (i = 0; i < sizeof(wptr)/sizeof(wptr[0]); i ++) { DBGH(rec->desc, "freeing field #%d = 0x%p.", i, *wptr[i]); @@ -54,8 +54,8 @@ static void init_hheader(esodbc_hhdr_st *hdr, SQLSMALLINT type, void *parent) hdr->parent = parent; } -void init_desc(esodbc_desc_st *desc, esodbc_stmt_st *stmt, desc_type_et type, - SQLSMALLINT alloc_type) +static void init_desc(esodbc_desc_st *desc, esodbc_stmt_st *stmt, + desc_type_et type, SQLSMALLINT alloc_type) { memset(desc, 0, sizeof(esodbc_desc_st)); @@ -1331,7 +1331,7 @@ SQLRETURN EsSQLGetDescFieldW( { esodbc_desc_st *desc = DSCH(DescriptorHandle); esodbc_state_et state; - SQLWCHAR *wptr; + wstr_st wstr; SQLSMALLINT word; SQLINTEGER intgr; esodbc_rec_st *rec; @@ -1451,37 +1451,36 @@ SQLRETURN EsSQLGetDescFieldW( /* */ do { - case SQL_DESC_BASE_COLUMN_NAME: wptr = rec->base_column_name; break; - case SQL_DESC_BASE_TABLE_NAME: wptr = rec->base_table_name; break; - case SQL_DESC_CATALOG_NAME: wptr = rec->catalog_name; break; - case SQL_DESC_LABEL: wptr = rec->label; break; - case SQL_DESC_NAME: wptr = rec->name; break; - case SQL_DESC_SCHEMA_NAME: wptr = rec->schema_name; break; - case SQL_DESC_TABLE_NAME: wptr = rec->table_name; break; + case SQL_DESC_BASE_COLUMN_NAME: wstr = rec->base_column_name; break; + case SQL_DESC_BASE_TABLE_NAME: wstr = rec->base_table_name; break; + case SQL_DESC_CATALOG_NAME: wstr = rec->catalog_name; break; + case SQL_DESC_LABEL: wstr = rec->label; break; + case SQL_DESC_NAME: wstr = rec->name; break; + case SQL_DESC_SCHEMA_NAME: wstr = rec->schema_name; break; + case SQL_DESC_TABLE_NAME: wstr = rec->table_name; break; case SQL_DESC_LITERAL_PREFIX: - wptr = rec->es_type->literal_prefix.str; + wstr = rec->es_type->literal_prefix; break; case SQL_DESC_LITERAL_SUFFIX: - wptr = rec->es_type->literal_suffix.str; + wstr = rec->es_type->literal_suffix; break; case SQL_DESC_LOCAL_TYPE_NAME: - wptr = rec->es_type->local_type_name.str; + wstr = rec->es_type->local_type_name; break; case SQL_DESC_TYPE_NAME: - wptr = rec->es_type->type_name.str; + wstr = rec->es_type->type_name; break; } while (0); - if (! wptr) { - *StringLengthPtr = 0; + if (! StringLengthPtr) { + RET_HDIAGS(desc, SQL_STATE_HY009); } else { - *StringLengthPtr = (SQLINTEGER)wcslen(wptr) * sizeof(SQLWCHAR); + *StringLengthPtr = (SQLINTEGER)wstr.cnt; } if (ValuePtr) { - memcpy(ValuePtr, wptr, *StringLengthPtr); - /* TODO: 0-term setting? */ + memcpy(ValuePtr, wstr.str, *StringLengthPtr); } - DBGH(desc, "returning record field %d as SQLWCHAR 0x%p (`"LWPD"`).", - FieldIdentifier, wptr, wptr ? wptr : TS_NULL); + DBGH(desc, "returning SQLWCHAR record field %d: `" LWPDL "`.", + FieldIdentifier, LWSTR(&wstr)); break; /* */ @@ -2011,7 +2010,7 @@ SQLRETURN EsSQLSetDescFieldW( esodbc_desc_st *desc = DSCH(DescriptorHandle); esodbc_state_et state; esodbc_rec_st *rec; - SQLWCHAR **wptrp, *wptr; + wstr_st *wstrp; SQLSMALLINT *wordp; SQLINTEGER *intp; SQLSMALLINT count, type; @@ -2042,8 +2041,8 @@ SQLRETURN EsSQLSetDescFieldW( ulen = (SQLULEN)(uintptr_t)ValuePtr; DBGH(desc, "setting desc array size to: %u.", ulen); if (ESODBC_MAX_ROW_ARRAY_SIZE < ulen) { - WARNH(desc, "provided desc array size (%u) larger than allowed " - "max (%u) -- set value adjusted to max.", ulen, + WARNH(desc, "provided desc array size (%u) larger than " + "allowed max (%u) -- set value adjusted to max.", ulen, ESODBC_MAX_ROW_ARRAY_SIZE); desc->array_size = ESODBC_MAX_ROW_ARRAY_SIZE; RET_HDIAGS(desc, SQL_STATE_01S02); @@ -2213,40 +2212,41 @@ SQLRETURN EsSQLSetDescFieldW( /* */ do { - case SQL_DESC_BASE_COLUMN_NAME: wptrp = &rec->base_column_name; break; - case SQL_DESC_BASE_TABLE_NAME: wptrp = &rec->base_table_name; break; - case SQL_DESC_CATALOG_NAME: wptrp = &rec->catalog_name; break; - case SQL_DESC_LABEL: wptrp = &rec->label; break; + case SQL_DESC_BASE_COLUMN_NAME: wstrp = &rec->base_column_name; break; + case SQL_DESC_BASE_TABLE_NAME: wstrp = &rec->base_table_name; break; + case SQL_DESC_CATALOG_NAME: wstrp = &rec->catalog_name; break; + case SQL_DESC_LABEL: wstrp = &rec->label; break; /* R/O fields: literal_prefix/_suffix, local_type_name, type_name */ - case SQL_DESC_SCHEMA_NAME: wptrp = &rec->schema_name; break; - case SQL_DESC_TABLE_NAME: wptrp = &rec->table_name; break; + case SQL_DESC_SCHEMA_NAME: wstrp = &rec->schema_name; break; + case SQL_DESC_TABLE_NAME: wstrp = &rec->table_name; break; } while (0); - DBGH(desc, "setting SQLWCHAR field %d to 0x%p(`"LWPD"`).", - FieldIdentifier, ValuePtr, - ValuePtr ? (SQLWCHAR *)ValuePtr : TS_NULL); - if (*wptrp) { + if (BufferLength == SQL_NTS) { + wlen = ValuePtr ? wcslen((SQLWCHAR *)ValuePtr) : 0; + } else { + wlen = BufferLength; + } + DBGH(desc, "setting SQLWCHAR field %d to `" LWPDL "`(@0x%p).", + FieldIdentifier, wlen, ValuePtr, wlen, ValuePtr); + if (wstrp->str) { DBGH(desc, "freeing previously allocated value for field %d " - "(`"LWPD"`).", *wptrp); - free(*wptrp); + "(`" LWPDL "`).", FieldIdentifier, LWSTR(wstrp)); + free(wstrp->str); + wstrp->str = NULL; + wstrp->cnt = 0; } if (! ValuePtr) { - *wptrp = NULL; DBGH(desc, "field %d reset to NULL.", FieldIdentifier); break; } - if (BufferLength == SQL_NTS) - wlen = wcslen((SQLWCHAR *)ValuePtr); - else - wlen = BufferLength; - wptr = (SQLWCHAR *)malloc((wlen + /*0-term*/1) * sizeof(SQLWCHAR)); - if (! wptr) { - ERRH(desc, "failed to alloc string buffer of len %d.", - wlen + 1); + if (! (wstrp->str = (SQLWCHAR *)malloc((wlen + /*0-term*/1) + * sizeof(SQLWCHAR)))) { + ERRH(desc, "failed to alloc w-string buffer of len %zd.", + wlen + 1); RET_HDIAGS(desc, SQL_STATE_HY001); } - memcpy(wptr, ValuePtr, wlen * sizeof(SQLWCHAR)); - wptr[wlen] = 0; - *wptrp = wptr; + memcpy(wstrp->str, ValuePtr, wlen * sizeof(SQLWCHAR)); + wstrp->str[wlen] = 0; + wstrp->cnt = wlen; break; /* , deferred */ @@ -2262,8 +2262,14 @@ SQLRETURN EsSQLSetDescFieldW( /* */ /* R/O fields: display_size */ case SQL_DESC_OCTET_LENGTH: - DBGH(desc, "setting octet length: %d.", + DBGH(desc, "setting octet length: %ld.", (SQLLEN)(intptr_t)ValuePtr); + /* rec field's type is signed :/; a negative is dangerous l8r */ + if ((SQLLEN)(intptr_t)ValuePtr < 0) { + ERRH(desc, "octet lenght attribute can't be negative (%lu)", + (SQLLEN)(intptr_t)ValuePtr); + RET_HDIAGS(desc, SQL_STATE_HY000); + } rec->octet_length = (SQLLEN)(intptr_t)ValuePtr; break; diff --git a/driver/handles.h b/driver/handles.h index c86a29b7..d5e2c8e1 100644 --- a/driver/handles.h +++ b/driver/handles.h @@ -178,14 +178,13 @@ typedef struct desc_rec { SQLPOINTER data_ptr; /* array, if .array_size > 1 */ - /* TODO: move all SQLWCHARs to wstr_st */ - SQLWCHAR *base_column_name; /* read-only */ - SQLWCHAR *base_table_name; /* r/o */ - SQLWCHAR *catalog_name; /* r/o */ - SQLWCHAR *label; /* r/o */ //alias? - SQLWCHAR *name; - SQLWCHAR *schema_name; /* r/o */ - SQLWCHAR *table_name; /* r/o */ + wstr_st base_column_name; /* read-only */ + wstr_st base_table_name; /* r/o */ + wstr_st catalog_name; /* r/o */ + wstr_st label; /* r/o */ //alias? + wstr_st name; + wstr_st schema_name; /* r/o */ + wstr_st table_name; /* r/o */ SQLLEN *indicator_ptr; /* array, if .array_size > 1 */ SQLLEN *octet_length_ptr; /* array, if .array_size > 1 */ diff --git a/driver/info.c b/driver/info.c index c65f487f..3a63622b 100644 --- a/driver/info.c +++ b/driver/info.c @@ -105,60 +105,6 @@ static SQLUSMALLINT esodbc_functions[] = { *(((UWORD*) (pfExists)) + ((uwAPI) >> 4)) |= (1 << ((uwAPI) & 0x000F)) #define SQL_API_ODBC2_ALL_FUNCTIONS_SIZE 100 -/* Note: for input/output size indication (avail/usedp), some functions - * require character count (eg. SQLGetDiagRec, SQLDescribeCol), some others - * bytes length (eg. SQLGetInfo, SQLGetDiagField, SQLGetConnectAttr, - * EsSQLColAttributeW). */ -SQLRETURN write_wptr(esodbc_diag_st *diag, - SQLWCHAR *dest, const SQLWCHAR *src, - SQLSMALLINT /*B*/avail, SQLSMALLINT /*B*/*usedp) -{ - size_t src_cnt, awail; - SQLSMALLINT used; - - if (! dest) { - avail = 0; - } - /* needs to be multiple of SQLWCHAR units (2 on Win) */ - if (avail % sizeof(SQLWCHAR)) { - ERR("invalid buffer length provided: %d.", avail); - RET_CDIAG(diag, SQL_STATE_HY090, "invalid buffer length provided", 0); - } - awail = avail/sizeof(SQLWCHAR); - src_cnt = wcslen(src); - - /* return value always set to what it needs to be written (excluding \0).*/ - used = (SQLSMALLINT)src_cnt * sizeof(SQLWCHAR); - if (! usedp) { - WARN("invalid output buffer provided (NULL) to collect used " - "space."); - //RET_cDIAG(diag, SQL_STATE_HY013, "invalid used provided (NULL)", 0); - } else { - /* how many bytes are available to return (not how many would be - * written into the buffer (which could be less)) */ - *usedp = used; - } - - if (! dest) { - /* only return how large of a buffer we need */ - INFO("NULL out buff: returning needed buffer size only (%d).", - used); - } else { - if (awail <= src_cnt) { /* =, since src_cnt doesn't count the \0 */ - wcsncpy(dest, src, awail - /* 0-term */1); - dest[awail - 1] = 0; - - INFO("not enough buffer size to write required string (plus " - "terminator): `" LWPD "` [%zd]; available: %d.", src, - src_cnt, awail); - RET_DIAG(diag, SQL_STATE_01004, NULL, 0); - } else { - wcsncpy(dest, src, src_cnt + /* 0-term */1); - } - } - - return SQL_SUCCESS; -} // [0] x-p-es/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcDatabaseMetaData.java : DatabaseMetaData /* @@ -181,9 +127,8 @@ SQLRETURN EsSQLGetInfoW(SQLHDBC ConnectionHandle, /* Driver Information */ /* "what version of odbc a driver complies with" */ case SQL_DRIVER_ODBC_VER: - return write_wptr(&dbc->hdr.diag, InfoValue, - MK_WPTR(ESODBC_SQL_SPEC_STRING), BufferLength, - StringLengthPtr); + return write_wstr(dbc, InfoValue, &MK_WSTR(ESODBC_SQL_SPEC_STRING), + BufferLength, StringLengthPtr); /* "if the driver can execute functions asynchronously on the * connection handle" */ @@ -228,28 +173,27 @@ SQLRETURN EsSQLGetInfoW(SQLHDBC ConnectionHandle, case SQL_DATA_SOURCE_NAME: DBGH(dbc, "requested: data source name: `"LWPD"`.", dbc->dsn.str); - return write_wptr(&dbc->hdr.diag, InfoValue, dbc->dsn.str, - BufferLength, StringLengthPtr); + return write_wstr(dbc, InfoValue, &dbc->dsn, BufferLength, + StringLengthPtr); case SQL_DRIVER_NAME: DBGH(dbc, "requested: driver (file) name: %s.", DRIVER_NAME); - return write_wptr(&dbc->hdr.diag, InfoValue, - MK_WPTR(DRIVER_NAME), BufferLength, StringLengthPtr); + return write_wstr(dbc, InfoValue, &MK_WSTR(DRIVER_NAME), + BufferLength, StringLengthPtr); break; case SQL_DATA_SOURCE_READ_ONLY: DBGH(dbc, "requested: if data source is read only (`%s`).", ESODBC_DATA_SOURCE_READ_ONLY); - return write_wptr(&dbc->hdr.diag, InfoValue, - MK_WPTR(ESODBC_DATA_SOURCE_READ_ONLY), BufferLength, + return write_wstr(dbc, InfoValue, + &MK_WSTR(ESODBC_DATA_SOURCE_READ_ONLY), BufferLength, StringLengthPtr); case SQL_SEARCH_PATTERN_ESCAPE: DBGH(dbc, "requested: escape character (`%s`).", ESODBC_PATTERN_ESCAPE); - return write_wptr(&dbc->hdr.diag, InfoValue, - MK_WPTR(ESODBC_PATTERN_ESCAPE), BufferLength, - StringLengthPtr); + return write_wstr(dbc, InfoValue, &MK_WSTR(ESODBC_PATTERN_ESCAPE), + BufferLength, StringLengthPtr); case SQL_CORRELATION_NAME: // JDBC[0]: supportsDifferentTableCorrelationNames() @@ -274,8 +218,8 @@ SQLRETURN EsSQLGetInfoW(SQLHDBC ConnectionHandle, /* JDBC[0]: getCatalogSeparator() */ DBGH(dbc, "requested: catalogue separator (`%s`).", ESODBC_CATALOG_SEPARATOR); - return write_wptr(&dbc->hdr.diag, InfoValue, - MK_WPTR(ESODBC_CATALOG_SEPARATOR), BufferLength, + return write_wstr(dbc, InfoValue, + &MK_WSTR(ESODBC_CATALOG_SEPARATOR), BufferLength, StringLengthPtr); case SQL_FILE_USAGE: @@ -290,9 +234,8 @@ SQLRETURN EsSQLGetInfoW(SQLHDBC ConnectionHandle, case SQL_CATALOG_TERM: /* SQL_QUALIFIER_TERM */ /* JDBC[0]: getCatalogSeparator() */ DBGH(dbc, "requested: catalogue term (`%s`).", ESODBC_CATALOG_TERM); - return write_wptr(&dbc->hdr.diag, InfoValue, - MK_WPTR(ESODBC_CATALOG_TERM), BufferLength, - StringLengthPtr); + return write_wstr(dbc, InfoValue, &MK_WSTR(ESODBC_CATALOG_TERM), + BufferLength, StringLengthPtr); case SQL_MAX_SCHEMA_NAME_LEN: /* SQL_MAX_OWNER_NAME_LEN */ /* JDBC[0]: getMaxSchemaNameLength() */ @@ -303,9 +246,8 @@ SQLRETURN EsSQLGetInfoW(SQLHDBC ConnectionHandle, case SQL_IDENTIFIER_QUOTE_CHAR: /* JDBC[0]: getIdentifierQuoteString() */ DBGH(dbc, "requested: quoting char (`%s`).", ESODBC_QUOTE_CHAR); - return write_wptr(&dbc->hdr.diag, InfoValue, - MK_WPTR(ESODBC_QUOTE_CHAR), BufferLength, - StringLengthPtr); + return write_wstr(dbc, InfoValue, &MK_WSTR(ESODBC_QUOTE_CHAR), + BufferLength, StringLengthPtr); /* what Operations are supported by SQLSetPos */ // FIXME: review@alpha @@ -343,32 +285,29 @@ SQLRETURN EsSQLGetInfoW(SQLHDBC ConnectionHandle, case SQL_SCHEMA_TERM: DBGH(dbc, "requested schema term (`%s`).", ESODBC_SCHEMA_TERM); - return write_wptr(&dbc->hdr.diag, InfoValue, - MK_WPTR(ESODBC_SCHEMA_TERM), BufferLength, - StringLengthPtr); + return write_wstr(dbc, InfoValue, &MK_WSTR(ESODBC_SCHEMA_TERM), + BufferLength, StringLengthPtr); case SQL_TABLE_TERM: DBGH(dbc, "requested table term (`%s`).", ESODBC_TABLE_TERM); - return write_wptr(&dbc->hdr.diag, InfoValue, - MK_WPTR(ESODBC_TABLE_TERM), BufferLength, - StringLengthPtr); + return write_wstr(dbc, InfoValue, &MK_WSTR(ESODBC_TABLE_TERM), + BufferLength, StringLengthPtr); /* no procedures support */ case SQL_PROCEDURES: case SQL_ACCESSIBLE_PROCEDURES: DBGH(dbc, "requested: procedures support (`%s`).", ESODBC_PROCEDURES); - return write_wptr(&dbc->hdr.diag, InfoValue, - MK_WPTR(ESODBC_PROCEDURES), BufferLength, - StringLengthPtr); + return write_wstr(dbc, InfoValue, &MK_WSTR(ESODBC_PROCEDURES), + BufferLength, StringLengthPtr); case SQL_MAX_PROCEDURE_NAME_LEN: DBGH(dbc, "requested max procedure name len (0)."); *(SQLUSMALLINT *)InfoValue = 0; /* no support */ break; case SQL_PROCEDURE_TERM: DBGH(dbc, "requested: procedure term (``)."); - return write_wptr(&dbc->hdr.diag, InfoValue, MK_WPTR(""), - BufferLength, StringLengthPtr); + return write_wstr(dbc, InfoValue, &MK_WSTR(""), BufferLength, + StringLengthPtr); case SQL_TXN_ISOLATION_OPTION: DBGH(dbc, "requested: transaction isolation options (SQL_TXN_*)."); @@ -492,8 +431,8 @@ SQLRETURN EsSQLGetInfoW(SQLHDBC ConnectionHandle, case SQL_SPECIAL_CHARACTERS: DBGH(dbc, "requested: special characters (`%s`).", ESODBC_SPECIAL_CHARACTERS); - return write_wptr(&dbc->hdr.diag, InfoValue, - MK_WPTR(ESODBC_SPECIAL_CHARACTERS), BufferLength, + return write_wstr(dbc, InfoValue, + &MK_WSTR(ESODBC_SPECIAL_CHARACTERS), BufferLength, StringLengthPtr); break; @@ -505,9 +444,8 @@ SQLRETURN EsSQLGetInfoW(SQLHDBC ConnectionHandle, case SQL_COLUMN_ALIAS: DBGH(dbc, "requested: column alias (`%s`).", ESODBC_COLUMN_ALIAS); - return write_wptr(&dbc->hdr.diag, InfoValue, - MK_WPTR(ESODBC_COLUMN_ALIAS), BufferLength, - StringLengthPtr); + return write_wstr(dbc, InfoValue, &MK_WSTR(ESODBC_COLUMN_ALIAS), + BufferLength, StringLengthPtr); case SQL_SQL_CONFORMANCE: DBGH(dbc, "requested: SQL conformance (%lu).", @@ -523,22 +461,21 @@ SQLRETURN EsSQLGetInfoW(SQLHDBC ConnectionHandle, case SQL_DRIVER_VER: DBGH(dbc, "requested: driver version (`%s`).", ESODBC_DRIVER_VER); - return write_wptr(&dbc->hdr.diag, InfoValue, - MK_WPTR(ESODBC_DRIVER_VER), BufferLength, - StringLengthPtr); + return write_wstr(dbc, InfoValue, &MK_WSTR(ESODBC_DRIVER_VER), + BufferLength, StringLengthPtr); case SQL_DBMS_VER: DBGH(dbc, "requested: DBMS version (`%s`).", ESODBC_ELASTICSEARCH_VER); - return write_wptr(&dbc->hdr.diag, InfoValue, - MK_WPTR(ESODBC_ELASTICSEARCH_VER), BufferLength, + return write_wstr(dbc, InfoValue, + &MK_WSTR(ESODBC_ELASTICSEARCH_VER), BufferLength, StringLengthPtr); case SQL_DBMS_NAME: DBGH(dbc, "requested: DBMS name (`%s`).", ESODBC_ELASTICSEARCH_NAME); - return write_wptr(&dbc->hdr.diag, InfoValue, - MK_WPTR(ESODBC_ELASTICSEARCH_NAME), BufferLength, + return write_wstr(dbc, InfoValue, + &MK_WSTR(ESODBC_ELASTICSEARCH_NAME), BufferLength, StringLengthPtr); case SQL_TXN_CAPABLE: /* SQL_TRANSACTION_CAPABLE */ @@ -592,10 +529,11 @@ SQLRETURN EsSQLGetDiagFieldW( SQLSMALLINT BufferLength, _Out_opt_ SQLSMALLINT *StringLengthPtr) { - esodbc_diag_st *diag, dummy; + esodbc_diag_st *diag; + esodbc_env_st dummy; SQLSMALLINT used; size_t len; - SQLWCHAR *wptr; + wstr_st *wstrp, wstr; SQLRETURN ret; if (RecNumber <= 0) { @@ -654,9 +592,9 @@ SQLRETURN EsSQLGetDiagFieldW( assert(len <= sizeof(esodbc_errors[diag->state].code)); if (memcmp(esodbc_errors[diag->state].code, MK_WPTR(ORIG_DISCRIM), len) == 0) { - wptr = MK_WPTR(ORIG_CLASS_ODBC); + wstrp = &MK_WSTR(ORIG_CLASS_ODBC); } else { - wptr = MK_WPTR(ORIG_CLASS_ISO); + wstrp = &MK_WSTR(ORIG_CLASS_ISO); } break; case SQL_DIAG_SUBCLASS_ORIGIN: @@ -703,16 +641,18 @@ SQLRETURN EsSQLGetDiagFieldW( case SQL_STATE_IM010: case SQL_STATE_IM011: case SQL_STATE_IM012: - wptr = MK_WPTR(ORIG_CLASS_ODBC); + wstrp = &MK_WSTR(ORIG_CLASS_ODBC); break; default: - wptr = MK_WPTR(ORIG_CLASS_ISO); + wstrp = &MK_WSTR(ORIG_CLASS_ISO); } break; } while (0); - DBGH(Handle, "diagnostic code '"LWPD"' is of class '"LWPD"'.", - esodbc_errors[diag->state].code, wptr); - return write_wptr(&dummy, DiagInfoPtr, wptr, BufferLength, + DBGH(Handle, "diagnostic code '"LWPD"' is of class '" LWPDL "'.", + esodbc_errors[diag->state].code, LWSTR(wstrp)); + /* GetDiagField can't set diagnostics itself, so use a dummy */ + *HDRH(&dummy) = *HDRH(Handle); /* need a valid hhdr struct */ + return write_wstr(&dummy, DiagInfoPtr, wstrp, BufferLength, StringLengthPtr); case SQL_DIAG_CONNECTION_NAME: @@ -720,19 +660,20 @@ SQLRETURN EsSQLGetDiagFieldW( case SQL_DIAG_SERVER_NAME: /* TODO: keep same as _CONNECTION_NAME? */ switch (HandleType) { case SQL_HANDLE_DBC: - wptr = DBCH(Handle)->dsn.str; + wstrp = &DBCH(Handle)->dsn; break; case SQL_HANDLE_STMT: - wptr = STMH(Handle)->hdr.dbc->dsn.str; + wstrp = &STMH(Handle)->hdr.dbc->dsn; break; case SQL_HANDLE_DESC: - wptr = DSCH(Handle)->hdr.stmt->hdr.dbc->dsn.str; + wstrp = &DSCH(Handle)->hdr.stmt->hdr.dbc->dsn; break; default: - wptr = MK_WPTR(""); + wstrp = &MK_WSTR(""); } - DBGH(Handle, "inquired connection name (`"LWPD"`)", wptr); - return write_wptr(&dummy, DiagInfoPtr, wptr, BufferLength, + DBGH(Handle, "inquired connection name (`" LWPDL "`)", + LWSTR(wstrp)); + return write_wstr(&dummy, DiagInfoPtr, wstrp, BufferLength, StringLengthPtr); @@ -748,16 +689,19 @@ SQLRETURN EsSQLGetDiagFieldW( HandleType); return SQL_NO_DATA; } + wstr.str = esodbc_errors[diag->state].code; + wstr.cnt = wcslen(wstr.str); /* GetDiagField can't set diagnostics itself, so use a dummy */ - ret = write_wptr(&dummy, DiagInfoPtr, - esodbc_errors[diag->state].code, BufferLength, &used); - if (StringLengthPtr) + *HDRH(&dummy) = *HDRH(Handle); /* need a valid hhdr struct */ + ret = write_wstr(&dummy, DiagInfoPtr, &wstr, BufferLength, &used); + if (StringLengthPtr) { *StringLengthPtr = used; - else + } else { /* SQLSTATE is always on 5 chars, but this Identifier sticks * out, by not being given a buffer to write this into */ WARNH(Handle, "SQLSTATE writen on %uB, but no output buffer " "provided.", used); + } return ret; default: @@ -792,9 +736,11 @@ SQLRETURN EsSQLGetDiagRecW _Out_opt_ SQLSMALLINT *TextLength ) { - esodbc_diag_st *diag, dummy; + esodbc_diag_st *diag; + esodbc_env_st dummy; SQLRETURN ret; SQLSMALLINT used; + wstr_st wstr; if (RecNumber <= 0) { ERRH(Handle, "record number must be >=1; received: %d.", RecNumber); @@ -840,7 +786,10 @@ SQLRETURN EsSQLGetDiagRecW *NativeError = diag->native_code; } - ret = write_wptr(&dummy, MessageText, diag->text, + wstr.str = diag->text; + wstr.cnt = wcslen(wstr.str); + *HDRH(&dummy) = *HDRH(Handle); /* need a valid hhdr struct */ + ret = write_wstr(&dummy, MessageText, &wstr, BufferLength * sizeof(*MessageText), &used); if (TextLength) { *TextLength = used / sizeof(*MessageText); diff --git a/driver/info.h b/driver/info.h index 28c945eb..80da76df 100644 --- a/driver/info.h +++ b/driver/info.h @@ -9,14 +9,6 @@ #include "sqlext.h" -/* - * TODO: move into a util.h - * TODO: change sign to ...src, lsrc, dst, dlen... (lsrc is nearly always - * known) - */ -SQLRETURN write_wptr(esodbc_diag_st *diag, - SQLWCHAR *dest, const SQLWCHAR *src, - SQLSMALLINT /*B*/avail, SQLSMALLINT /*B*/*usedp); SQLRETURN EsSQLGetInfoW(SQLHDBC ConnectionHandle, SQLUSMALLINT InfoType, diff --git a/driver/util.c b/driver/util.c index 71380b37..4a10c75d 100644 --- a/driver/util.c +++ b/driver/util.c @@ -7,6 +7,8 @@ #include #include "util.h" +#include "log.h" +#include "error.h" @@ -252,5 +254,63 @@ size_t json_escape(const char *jin, size_t inlen, char *jout, size_t outlen) #undef I16TOA } +/* Note: for input/output size indication (avail/usedp), some functions + * require character count (eg. SQLGetDiagRec, SQLDescribeCol), some others + * bytes length (eg. SQLGetInfo, SQLGetDiagField, SQLGetConnectAttr, + * EsSQLColAttributeW). */ +/* + * Copy a WSTR back to application; typically with non-SQLFetch() calls. + * The WSTR must not count the 0-tem. + * The function checks against the correct size of available bytes, copies the + * wstr according to avaialble space and indicates the available bytes to copy + * back into provided buffer (if not NULL). + */ +SQLRETURN write_wstr(SQLHANDLE hnd, SQLWCHAR *dest, wstr_st *src, + SQLSMALLINT /*B*/avail, SQLSMALLINT /*B*/*usedp) +{ + size_t awail; + + /* cnt must not count the 0-term (XXX: ever need to copy 0s?) */ + assert(src->cnt <= 0 || src->str[src->cnt - 1]); + + DBGH(hnd, "copying %zd wchars (`" LWPDL "`) into buffer @0x%p, of %dB " + "len; out-len @0x%p.", src->cnt, LWSTR(src), dest, avail, usedp); + + if (usedp) { + /* how many bytes are available to return (not how many would be + * written into the buffer (which could be less)); + * it excludes the 0-term .*/ + *usedp = (SQLSMALLINT)(src->cnt * sizeof(src->str[0])); + } else { + INFOH(hnd, "NULL required-space-buffer provided."); + } + + if (dest) { + /* needs to be multiple of SQLWCHAR units (2 on Win) */ + if (avail % sizeof(SQLWCHAR)) { + ERRH(hnd, "invalid buffer length provided: %d.", avail); + RET_DIAG(&HDRH(hnd)->diag, SQL_STATE_HY090, NULL, 0); + } else { + awail = avail/sizeof(SQLWCHAR); + } + + if (awail <= src->cnt) { /* =, since src->cnt doesn't count the \0 */ + wcsncpy(dest, src->str, awail - /* 0-term */1); + dest[awail - 1] = 0; + + INFOH(hnd, "not enough buffer size to write required string (plus " + "terminator): `" LWPD "` [%zd]; available: %d.", + LWSTR(src), src->cnt, awail); + RET_DIAG(&HDRH(hnd)->diag, SQL_STATE_01004, NULL, 0); + } else { + wcsncpy(dest, src->str, src->cnt + /* 0-term */1); + } + } else { + /* only return how large of a buffer we need */ + INFOH(hnd, "NULL out buff."); + } + + return SQL_SUCCESS; +} /* vim: set noet fenc=utf-8 ff=dos sts=0 sw=4 ts=4 : */ diff --git a/driver/util.h b/driver/util.h index 78598ed0..439700a4 100644 --- a/driver/util.h +++ b/driver/util.h @@ -178,6 +178,15 @@ typedef cstr_st tstr_st; */ size_t json_escape(const char *jin, size_t inlen, char *jout, size_t outlen); +/* + * Copy a WSTR back to application. + * The WSTR must not count the 0-tem. + * The function checks against the correct size of available bytes, copies the + * wstr according to avaialble space and indicates the available bytes to copy + * back into provided buffer (if not NULL). + */ +SQLRETURN write_wstr(SQLHANDLE hnd, SQLWCHAR *dest, wstr_st *src, + SQLSMALLINT /*B*/avail, SQLSMALLINT /*B*/*usedp); /* * Printing aids. From 66fea8016ae157b42d6e7f63ab97106b54a55779 Mon Sep 17 00:00:00 2001 From: Bogdan Pintea Date: Sun, 10 Jun 2018 12:48:38 +0200 Subject: [PATCH 03/15] implement conversions for ES/SQL boolean type --- CMakeLists.txt | 2 + driver/connect.c | 4 +- driver/log.h | 32 ++- driver/queries.c | 343 ++++++++++++++++++-------- driver/util.h | 40 +-- test/test_conversion_sql2c_boolean.cc | 202 +++++++++++++++ 6 files changed, 488 insertions(+), 135 deletions(-) create mode 100644 test/test_conversion_sql2c_boolean.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index 21eeebb0..f715eb4c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -104,6 +104,8 @@ if (${WIN32}) # (this will allow RelWithDebInfo buliding and still be able to test it) set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /DTEST_API=") set(CMAKE_C_FLAGS_MINSIZEREL "${CMAKE_C_FLAGS_RELEASE} /DTEST_API=") + + # set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /DNDEBUG") else (${WIN32}) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g") endif (${WIN32}) diff --git a/driver/connect.c b/driver/connect.c index 1483939c..fe3a9133 100644 --- a/driver/connect.c +++ b/driver/connect.c @@ -1571,8 +1571,8 @@ static void *copy_types_rows(estype_row_st *type_row, SQLULEN rows_fetched, /* warn if scales extremes are different */ if (types[i].maximum_scale != types[i].minimum_scale) { - ERR("type `" LWPDL "` returned with non-equal max/min " - "scale: %d/%d.", LWSTR(&types[i].type_name), + INFO("type `" LWPDL "` returned with non-equal max/min " + "scale: %d/%d -- using the max.", LWSTR(&types[i].type_name), types[i].maximum_scale, types[i].minimum_scale); } diff --git a/driver/log.h b/driver/log.h index 4f90e3f8..4dba7989 100644 --- a/driver/log.h +++ b/driver/log.h @@ -20,30 +20,38 @@ * Descriptors to be used with logging for SQLWCHAR pointer type. * "Log Wchar Pointer Descriptor [with Length]" */ -#ifdef UNICODE #define LWPD PFWP_DESC #define LWPDL PFWP_LDESC -#else /* UNICODE */ -#define LWPD PFCP_DESC -#define LWPDL PFCP_LDESC -#endif /* UNICODE */ + +/* + * Descriptors to be used with logging for SQLCHAR pointer type. + * "Log Char Pointer Descriptor [with Length]" + */ +#define LCPD PFCP_DESC +#define LCPDL PFCP_LDESC /* * Descriptors to be used with logging for SQLTCHAR pointer type. * "Log Tchar Pointer Descriptor [with Length]" */ #ifdef UNICODE -#define LTPD PFWP_DESC -#define LTPDL PFWP_LDESC +# define LTPD LWPD +# define LTPDL LWPDL #else /* UNICODE */ -#define LTPD PFCP_DESC -#define LTPDL PFCP_LDESC +# define LTPD LCPD +# define LTPDL LCPDL #endif /* UNICODE */ -/* macro for logging of wstr_st objects */ + +/* macro for logging of Xstr_st objects */ #define LWSTR(_wptr) (int)(_wptr)->cnt, (_wptr)->str -#define LTSTR(_wptr) (int)(_wptr)->cnt, (_wptr)->str +#define LCSTR(_wptr) (int)(_wptr)->cnt, (_wptr)->str +#ifdef UNICODE +# define LTSTR LWSTR +#else /* UNICODE */ +# define LTSTR LCSTR +#endif /* UNICODE */ /* Note: keep in sync with __ESODBC_LVL2STR */ @@ -94,7 +102,7 @@ extern int _esodbc_log_level; */ /* get handle type prefix */ -static inline char *_hhtype2str(void *handle) +static inline char *_hhtype2str(SQLHANDLE handle) { if (! handle) { return ""; diff --git a/driver/queries.c b/driver/queries.c index 064b8b90..d031b136 100644 --- a/driver/queries.c +++ b/driver/queries.c @@ -5,6 +5,7 @@ */ #include +#include #include /* WideCharToMultiByte() */ #include "ujdecode.h" @@ -115,7 +116,7 @@ static SQLRETURN attach_columns(esodbc_stmt_st *stmt, UJObject columns) SQLSMALLINT recno; void *iter; UJObject col_o, name_o, type_o; - wstr_st col_name, col_type; + wstr_st col_type; size_t ncols, i; const wchar_t *keys[] = { MK_WPTR(JSON_ANSWER_COL_NAME), @@ -150,8 +151,10 @@ static SQLRETURN attach_columns(esodbc_stmt_st *stmt, UJObject columns) rec = &ird->recs[recno]; // +recno ASSERT_INTEGER_TYPES_EQUAL(wchar_t, SQLWCHAR); - col_name.str = (SQLWCHAR *)UJReadString(name_o, &col_name.cnt); - rec->name = col_name.cnt ? col_name.str : MK_WPTR(""); + rec->name.str = (SQLWCHAR *)UJReadString(name_o, &rec->name.cnt); + if (! rec->name.str) { + rec->name = MK_WSTR(""); + } col_type.str = (SQLWCHAR *)UJReadString(type_o, &col_type.cnt); @@ -189,13 +192,13 @@ static SQLRETURN attach_columns(esodbc_stmt_st *stmt, UJObject columns) /* "If a base column name does not exist (as in the case of columns * that are expressions), then this variable contains an empty * string." */ - rec->base_column_name = MK_WPTR(""); + rec->base_column_name = MK_WSTR(""); /* "If a column does not have a label, the column name is returned. If * the column is unlabeled and unnamed, an empty string is ret" */ - rec->label = rec->name ? rec->name : MK_WPTR(""); + rec->label = rec->name.cnt ? rec->name : MK_WSTR(""); - assert(rec->name && rec->label); - rec->unnamed = (rec->name[0] || rec->label[0]) ? + assert(rec->name.str && rec->label.str); + rec->unnamed = (rec->name.cnt || rec->label.cnt) ? SQL_NAMED : SQL_UNNAMED; #ifndef NDEBUG @@ -203,8 +206,7 @@ static SQLRETURN attach_columns(esodbc_stmt_st *stmt, UJObject columns) #endif /* NDEBUG */ DBGH(stmt, "column #%d: name=`" LWPDL "`, type=%d (`" LWPDL "`).", - recno, LWSTR(&col_name), rec->concise_type, - LWSTR(&col_type)); + recno, LWSTR(&rec->name), rec->concise_type, LWSTR(&col_type)); recno ++; } @@ -673,17 +675,19 @@ static void *deferred_address(SQLSMALLINT field_id, size_t pos, /* * Handles the lengths of the data to copy out to the application: * (1) returns the max amount of bytes to copy (in the data_ptr), taking into - * account: - * - the bytes of the data 'avail', - * - buffer size 'room', - * - statement attribute SQL_ATTR_MAX_LENGTH 'attr_max' and - * - required byte alignment (potentially to wchar_t size); + * account size of data and of buffer, relevant statement attribute and + * buffer type; * (2) indicates if truncation occured into 'state'. - * Only to be used with ARD.meta_type == STR || BIN (as it can truncate). + * WARN: only to be used with ARD.meta_type == STR || BIN (as it can indicate + * a size to copy smaller than the original -- truncating). */ static size_t buff_octet_size( - size_t avail, size_t room, size_t attr_max, size_t unit_size, - esodbc_metatype_et ird_mt, esodbc_state_et *state) + size_t avail, /* how many bytes are there to copy out */ + size_t room, /* how large (bytes) is the buffer to copy into*/ + size_t attr_max, /* statement attribute SQL_ATTR_MAX_LENGTH value */ + size_t unit_size, /* the unit size of the buffer (i.e. sizeof(wchar_t)) */ + esodbc_metatype_et ird_mt, /* meta type of IRD */ + esodbc_state_et *state /* out param: only written when truncating */) { size_t max_copy, max; @@ -721,12 +725,17 @@ static size_t buff_octet_size( } /* - * Indicate the amount of data copied to the application, taking into account: - * the type of data, should truncation - due to max length attr setting - need - * to be indicated. + * Indicate the amount of data copied (or that would have been, if buffer too + * small) to the application, taking into account: the type of data, + * should truncation - due to max length attr setting - need to be indicated, + * since original length is indicated, w/o possible buffer truncation, but + * with possible 'network' truncation. */ -static inline void write_copied_octets(SQLLEN *octet_len_ptr, size_t copied, - size_t attr_max, esodbc_metatype_et ird_mt) +static inline void write_out_octets( + SQLLEN *octet_len_ptr, /* buffer to write the copied octets into */ + size_t copied, /* amount of bytes copied */ + size_t attr_max, /* statement attribute SQL_ATTR_MAX_LENGTH value */ + esodbc_metatype_et ird_mt /* meta type of IRD */) { size_t max; @@ -802,7 +811,7 @@ static SQLRETURN copy_longlong(esodbc_rec_st *arec, esodbc_rec_st *irec, tocopy = buff_octet_size(blen, arec->octet_length, stmt->max_length, sizeof(*buff), irec->meta_type, &state); memcpy(data_ptr, buff, tocopy); - write_copied_octets(octet_len_ptr, blen, stmt->max_length, + write_out_octets(octet_len_ptr, blen, stmt->max_length, irec->meta_type); break; case SQL_C_WCHAR: @@ -812,34 +821,34 @@ static SQLRETURN copy_longlong(esodbc_rec_st *arec, esodbc_rec_st *irec, tocopy = buff_octet_size(blen, arec->octet_length, stmt->max_length, sizeof(*wbuff), irec->meta_type, &state); memcpy(data_ptr, wbuff, tocopy); - write_copied_octets(octet_len_ptr, blen, stmt->max_length, + write_out_octets(octet_len_ptr, blen, stmt->max_length, irec->meta_type); break; case SQL_C_TINYINT: case SQL_C_STINYINT: *(SQLSCHAR *)data_ptr = (SQLSCHAR)ll; - write_copied_octets(octet_len_ptr, sizeof(SQLSCHAR), + write_out_octets(octet_len_ptr, sizeof(SQLSCHAR), stmt->max_length, irec->meta_type); break; case SQL_C_SHORT: case SQL_C_SSHORT: *(SQLSMALLINT *)data_ptr = (SQLSMALLINT)ll; - write_copied_octets(octet_len_ptr, sizeof(SQLSMALLINT), + write_out_octets(octet_len_ptr, sizeof(SQLSMALLINT), stmt->max_length, irec->meta_type); break; case SQL_C_LONG: case SQL_C_SLONG: *(SQLINTEGER *)data_ptr = (SQLINTEGER)ll; - write_copied_octets(octet_len_ptr, sizeof(SQLINTEGER), + write_out_octets(octet_len_ptr, sizeof(SQLINTEGER), stmt->max_length, irec->meta_type); break; case SQL_C_SBIGINT: *(SQLBIGINT *)data_ptr = (SQLBIGINT)ll; - write_copied_octets(octet_len_ptr, sizeof(SQLBIGINT), + write_out_octets(octet_len_ptr, sizeof(SQLBIGINT), stmt->max_length, irec->meta_type); break; @@ -860,56 +869,231 @@ static SQLRETURN copy_double(esodbc_rec_st *arec, esodbc_rec_st *irec, return SQL_ERROR; } +static SQLRETURN transfer_wstr(esodbc_rec_st *arec, esodbc_rec_st *irec, + wstr_st *src, void *w_dst, SQLLEN *len_dst) +{ + size_t in_bytes; + esodbc_state_et state = SQL_STATE_00000; + esodbc_stmt_st *stmt = arec->desc->hdr.stmt; + + /* the source string must be 0-term'd */ + assert(src->str[src->cnt] == 0); + + in_bytes = buff_octet_size((src->cnt + 1) * sizeof(*src->str), + (size_t)arec->octet_length, stmt->max_length, + sizeof(*src->str), irec->meta_type, &state); + if (w_dst) { + memcpy(w_dst, src->str, in_bytes); + if (state != SQL_STATE_00000) { + /* 0-term the buffer */ + ((SQLWCHAR *)w_dst)[(in_bytes/sizeof(SQLWCHAR)) - 1] = 0; + } else { + assert(((SQLWCHAR *)w_dst)[(in_bytes/sizeof(SQLWCHAR)) - 1] == 0); + } + } + /* always return the app the untruncated number of bytes */ + write_out_octets(len_dst, src->cnt * sizeof(*src->str), + stmt->max_length, irec->meta_type); + + DBGH(stmt, "trasferred into REC@0x%p, data_ptr@0x%p wstring `" LWPDL "`" + " [%zd], state: %d.", arec, w_dst, LWSTR(src), src->cnt, state); + + if (state != SQL_STATE_00000) { + RET_HDIAGS(stmt, state); + } + return SQL_SUCCESS; +} + +static SQLRETURN transfer_cstr(esodbc_rec_st *arec, esodbc_rec_st *irec, + cstr_st *src, void *c_dst, SQLLEN *len_dst) +{ + size_t in_bytes; + esodbc_state_et state = SQL_STATE_00000; + esodbc_stmt_st *stmt = arec->desc->hdr.stmt; + + /* the source string must be 0-term'd */ + assert(src->str[src->cnt] == 0); + + in_bytes = buff_octet_size((src->cnt + 1) * sizeof(*src->str), + (size_t)arec->octet_length, stmt->max_length, + sizeof(*src->str), irec->meta_type, &state); + if (c_dst) { + memcpy(c_dst, src->str, in_bytes); + if (state != SQL_STATE_00000) { + /* 0-term the buffer */ + ((SQLCHAR *)c_dst)[(in_bytes/sizeof(SQLCHAR)) - 1] = 0; + } else { + assert(((SQLCHAR *)c_dst)[(in_bytes/sizeof(SQLCHAR)) - 1] == 0); + } + } + /* always return the app the untruncated number of bytes */ + write_out_octets(len_dst, src->cnt * sizeof(*src->str), + stmt->max_length, irec->meta_type); + + DBGH(stmt, "trasferred into REC@0x%p, data_ptr@0x%p string `" LCPDL "`" + " [%zd], state: %d.", arec, c_dst, LCSTR(src), src->cnt, state); + + if (state != SQL_STATE_00000) { + RET_HDIAGS(stmt, state); + } + return SQL_SUCCESS; +} + +/* 10^n */ +static inline long pow10(int n) +{ + long pow = 1; + pow <<= n; + while (n--) { + pow += pow << 2; + } + return pow; +} + +static SQLRETURN double_to_numeric(esodbc_rec_st *arec, esodbc_rec_st *irec, + double src, void *dst) +{ + SQL_NUMERIC_STRUCT *numeric; + esodbc_stmt_st *stmt; + SQLSMALLINT prec/*..ision*/, adj_scale /*..usted*/; + unsigned long ulng; + long lng; + + stmt = arec->desc->hdr.stmt; + numeric = (SQL_NUMERIC_STRUCT *)dst; + assert(numeric); + + numeric->sign = 0 <= src; + + ulng = numeric->sign ? (unsigned long)src : (unsigned long)-src; + /* =~ log10(abs(src)) */ + for (prec = 0 ; ulng; prec ++) { + ulng /= 10; + } + if (arec->scale < 0) { + adj_scale = - arec->scale; + lng = (long)(src / pow10(arec->scale)); + } else { + adj_scale = 0; + lng = (long)(src * pow10(arec->scale)); + } + ulng = numeric->sign ? (unsigned long)lng : (unsigned long)-lng; + + if (0 < arec->precision && arec->precision < prec - adj_scale) { + /* precision of source is higher than requested => overflow */ + ERRH(stmt, "conversion overflow. source: %lf; requested: " + "precisions: %d, scale: %d.", src, arec->precision, arec->scale); + RET_HDIAGS(stmt, SQL_STATE_22003); + } else { + numeric->precision = (SQLCHAR)arec->precision; + } + numeric->scale = (SQLCHAR)arec->scale; + numeric->sign = 0 <= src; + + +#if REG_DWORD != REG_DWORD_LITTLE_ENDIAN + ulng = _byteswap_ulong(ulng); +#endif /* LE */ + assert(sizeof(ulng) <= sizeof(numeric->val)); + memcpy(numeric->val, (char *)&ulng, sizeof(ulng)); + memset(numeric->val+sizeof(ulng), 0, sizeof(numeric->val)-sizeof(ulng)); + + DBGH(stmt, "double %lf converted to numeric: .sign=%d, precision=%d " + "(req: %d), .scale=%d (req: %d), .val:`" LCPDL "`.", src, + numeric->sign, numeric->precision, arec->precision, + numeric->scale, arec->scale, (int)sizeof(numeric->val), numeric->val); + return SQL_SUCCESS; +} + static SQLRETURN copy_boolean(esodbc_rec_st *arec, esodbc_rec_st *irec, SQLULEN pos, BOOL boolval) { esodbc_stmt_st *stmt; void *data_ptr; SQLLEN *octet_len_ptr; + wstr_st wbool; + cstr_st cbool; + SQLRETURN ret; stmt = arec->desc->hdr.stmt; - /* pointer where to write how many characters we will/would use */ + /* the check is in place just to return the right error code (according to + * conversion rules); a value of 0 is safe for the driver. */ + if (arec->octet_length < 1) { + ERRH(stmt, "output buffer less than one byte, can't convert"); + RET_HDIAGS(stmt, SQL_STATE_22003); + } + + /* pointer where to write how many bytes 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); - if (! data_ptr) { + if ((! data_ptr) && (arec->meta_type == METATYPE_EXACT_NUMERIC || + arec->meta_type == METATYPE_FLOAT_NUMERIC)) { ERRH(stmt, "received boolean type, but NULL data ptr."); RET_HDIAGS(stmt, SQL_STATE_HY009); } switch (get_c_target_type(arec, irec)) { + case SQL_C_BINARY: + case SQL_C_BIT: case SQL_C_STINYINT: case SQL_C_UTINYINT: *(SQLSCHAR *)data_ptr = (SQLSCHAR)boolval; - write_copied_octets(octet_len_ptr, sizeof(SQLSCHAR), + write_out_octets(octet_len_ptr, sizeof(SQLSCHAR), stmt->max_length, irec->meta_type); break; case SQL_C_SSHORT: case SQL_C_USHORT: *(SQLSMALLINT *)data_ptr = (SQLSMALLINT)boolval; - write_copied_octets(octet_len_ptr, sizeof(SQLSMALLINT), + write_out_octets(octet_len_ptr, sizeof(SQLSMALLINT), stmt->max_length, irec->meta_type); break; case SQL_C_SLONG: case SQL_C_ULONG: *(SQLINTEGER *)data_ptr = (SQLINTEGER)boolval; - write_copied_octets(octet_len_ptr, sizeof(SQLINTEGER), + write_out_octets(octet_len_ptr, sizeof(SQLINTEGER), stmt->max_length, irec->meta_type); break; case SQL_C_SBIGINT: case SQL_C_UBIGINT: *(SQLBIGINT *)data_ptr = (SQLBIGINT)boolval; - write_copied_octets(octet_len_ptr, sizeof(SQLBIGINT), + write_out_octets(octet_len_ptr, sizeof(SQLBIGINT), + stmt->max_length, irec->meta_type); + break; + + case SQL_C_FLOAT: + *(SQLREAL *)data_ptr = boolval ? 1.0f : 0.0f; + write_out_octets(octet_len_ptr, sizeof(SQLREAL), + stmt->max_length, irec->meta_type); + break; + case SQL_C_DOUBLE: + *(SQLDOUBLE *)data_ptr = boolval ? 1.0 : 0.0; + write_out_octets(octet_len_ptr, sizeof(SQLDOUBLE), + stmt->max_length, irec->meta_type); + break; + + case SQL_C_NUMERIC: + if (data_ptr) { + ret = double_to_numeric(arec, irec, boolval ? 1.0 : 0.0, + data_ptr); + if (! SQL_SUCCEEDED(ret)) { + return ret; + } + } + write_out_octets(octet_len_ptr, sizeof(SQL_NUMERIC_STRUCT), stmt->max_length, irec->meta_type); break; - case SQL_C_CHAR: case SQL_C_WCHAR: - // TODO + wbool = boolval ? MK_WSTR("true") : MK_WSTR("false"); + return transfer_wstr(arec, irec, &wbool, data_ptr, octet_len_ptr); + case SQL_C_CHAR: + cbool = boolval ? MK_CSTR("true") : MK_CSTR("false"); + return transfer_cstr(arec, irec, &cbool, data_ptr, octet_len_ptr); default: FIXME; // FIXME @@ -985,7 +1169,7 @@ static SQLRETURN wstr_to_cstr(esodbc_rec_st *arec, esodbc_rec_st *irec, * indicating the length to the application */ out_bytes --; } - write_copied_octets(octet_len_ptr, out_bytes, stmt->max_length, + write_out_octets(octet_len_ptr, out_bytes, stmt->max_length, irec->meta_type); } else { DBGH(stmt, "REC@0x%p, NULL octet_len_ptr.", arec); @@ -1006,43 +1190,8 @@ static SQLRETURN wstr_to_wstr(esodbc_rec_st *arec, esodbc_rec_st *irec, void *data_ptr, SQLLEN *octet_len_ptr, const wchar_t *wstr, size_t chars_0) { - esodbc_state_et state = SQL_STATE_00000; - esodbc_stmt_st *stmt = arec->desc->hdr.stmt; - size_t in_bytes, out_chars; - wchar_t *widep; - - - if (data_ptr) { - widep = (wchar_t *)data_ptr; - - assert(0 <= arec->octet_length); - in_bytes = buff_octet_size(chars_0 * sizeof(*wstr), - (size_t)arec->octet_length, stmt->max_length, sizeof(*wstr), - irec->meta_type, &state); - - memcpy(widep, wstr, in_bytes); - out_chars = in_bytes / sizeof(*widep); - /* if buffer too small to accomodate original, 0-term it */ - if (widep[out_chars - 1]) { - widep[out_chars - 1] = 0; - state = SQL_STATE_01004; /* indicate truncation */ - } - - DBGH(stmt, "REC@0x%p, data_ptr@0x%p, copied %zd chars (and 0-term): `" - LWPD "`.", arec, data_ptr, out_chars, widep); - } else { - DBGH(stmt, "REC@0x%p, NULL data_ptr", arec); - } - - /* original length is indicated, w/o possible buffer truncation (but with - * possible 'network' truncation) */ - write_copied_octets(octet_len_ptr, (chars_0 - /*0-term*/1) * sizeof(*wstr), - stmt->max_length, irec->meta_type); - - if (state != SQL_STATE_00000) { - RET_HDIAGS(stmt, state); - } - return SQL_SUCCESS; + wstr_st wsrc = {(SQLWCHAR *)wstr, chars_0 - 1}; + return transfer_wstr(arec, irec, &wsrc, data_ptr, octet_len_ptr); } /* function expects chars not to count the 0-term */ @@ -2084,7 +2233,7 @@ SQLRETURN EsSQLDescribeColW( esodbc_stmt_st *stmt = STMH(hstmt); esodbc_rec_st *rec; SQLRETURN ret; - SQLSMALLINT col_blen = -1; + SQLSMALLINT col_blen; DBGH(stmt, "IRD@0x%p, column #%d.", stmt->ird, icol); @@ -2108,14 +2257,16 @@ SQLRETURN EsSQLDescribeColW( #endif /* NDEBUG */ if (szColName) { - ret = write_wptr(&stmt->hdr.diag, szColName, rec->name, + ret = write_wstr(stmt, szColName, &rec->name, cchColNameMax * sizeof(*szColName), &col_blen); if (! SQL_SUCCEEDED(ret)) { - ERRH(stmt, "failed to copy column name `" LWPD "`.", rec->name); + ERRH(stmt, "failed to copy column name `" LWPDL "`.", + LWSTR(&rec->name)); return ret; } } else { DBGH(stmt, "NULL column name buffer provided."); + col_blen = -1; } if (! pcchColName) { @@ -2124,7 +2275,7 @@ SQLRETURN EsSQLDescribeColW( "no column name length buffer provided", 0); } *pcchColName = 0 <= col_blen ? (col_blen / sizeof(*szColName)) : - (SQLSMALLINT)wcslen(rec->name); + (SQLSMALLINT)rec->name.cnt; DBGH(stmt, "col #%d name has %d chars.", icol, *pcchColName); if (! pfSqlType) { @@ -2187,7 +2338,7 @@ SQLRETURN EsSQLColAttributeW( esodbc_desc_st *ird = stmt->ird; esodbc_rec_st *rec; SQLSMALLINT sint; - SQLWCHAR *wptr; + wstr_st *wstrp; SQLLEN len; SQLINTEGER iint; @@ -2243,37 +2394,27 @@ SQLRETURN EsSQLColAttributeW( /* SQLWCHAR* */ do { - case SQL_DESC_BASE_COLUMN_NAME: wptr = rec->base_column_name; break; - case SQL_DESC_LABEL: wptr = rec->label; break; - case SQL_DESC_BASE_TABLE_NAME: wptr = rec->base_table_name; break; - case SQL_DESC_CATALOG_NAME: wptr = rec->catalog_name; break; - case SQL_DESC_NAME: wptr = rec->name; break; - case SQL_DESC_SCHEMA_NAME: wptr = rec->schema_name; break; - case SQL_DESC_TABLE_NAME: wptr = rec->table_name; break; + case SQL_DESC_BASE_COLUMN_NAME: wstrp = &rec->base_column_name; break; + case SQL_DESC_LABEL: wstrp = &rec->label; break; + case SQL_DESC_BASE_TABLE_NAME: wstrp = &rec->base_table_name; break; + case SQL_DESC_CATALOG_NAME: wstrp = &rec->catalog_name; break; + case SQL_DESC_NAME: wstrp = &rec->name; break; + case SQL_DESC_SCHEMA_NAME: wstrp = &rec->schema_name; break; + case SQL_DESC_TABLE_NAME: wstrp = &rec->table_name; break; case SQL_DESC_LITERAL_PREFIX: - wptr = rec->es_type->literal_prefix.str; + wstrp = &rec->es_type->literal_prefix; break; case SQL_DESC_LITERAL_SUFFIX: - wptr = rec->es_type->literal_suffix.str; + wstrp = &rec->es_type->literal_suffix; break; case SQL_DESC_LOCAL_TYPE_NAME: - wptr = rec->es_type->type_name.str; + wstrp = &rec->es_type->type_name; break; case SQL_DESC_TYPE_NAME: - wptr = rec->es_type->type_name.str; + wstrp = &rec->es_type->type_name; break; } while (0); - if (! wptr) { - //FIXME: re-eval, once type handling is decided. - BUGH(stmt, "IRD@0x%p record field type %d not initialized.", - ird, iField); - *(SQLWCHAR **)pCharAttr = MK_WPTR(""); - *pcbCharAttr = 0; - } else { - return write_wptr(&stmt->hdr.diag, pCharAttr, wptr, cbDescMax, - pcbCharAttr); - } - break; + return write_wstr(stmt, pCharAttr, wstrp, cbDescMax, pcbCharAttr); /* SQLLEN */ do { diff --git a/driver/util.h b/driver/util.h index 439700a4..8ecffbd5 100644 --- a/driver/util.h +++ b/driver/util.h @@ -111,10 +111,11 @@ typedef struct wstr { * Turns a static C string into a wstr_st. */ #ifndef __cplusplus /* no MSVC support for compound literals with /TP */ -#define MK_WSTR(_s) \ - ((wstr_st){.str = MK_WPTR(_s), .cnt = sizeof(_s) - 1}) +# define MK_WSTR(_s) ((wstr_st){.str = MK_WPTR(_s), .cnt = sizeof(_s) - 1}) +# define MK_CSTR(_s) ((cstr_st){.str = _s, .cnt = sizeof(_s) - 1}) #else /* !__cplusplus */ -#define WSTR_INIT(_s) {MK_WPTR(_s), sizeof(_s) - 1} +# define WSTR_INIT(_s) {MK_WPTR(_s), sizeof(_s) - 1} +# define CSTR_INIT(_s) {_s, sizeof(_s) - 1} #endif /* !__cplusplus */ /* * Test equality of two wstr_st objects. @@ -199,31 +200,30 @@ SQLRETURN write_wstr(SQLHANDLE hnd, SQLWCHAR *dest, wstr_st *src, #ifdef _WIN32 /* funny M$ 'inverted' logic */ /* wprintf wide_t pointer descriptor */ -#define WPFWP_DESC L"%s" -#define WPFWP_LDESC L"%.*s" +# define WPFWP_DESC L"%s" +# define WPFWP_LDESC L"%.*s" /* printf wide_t pointer descriptor */ -#define PFWP_DESC "%S" -#define PFWP_LDESC "%.*S" +# define PFWP_DESC "%S" +# define PFWP_LDESC "%.*S" /* wprintf char pointer descriptor */ -#define WPFCP_DESC L"%S" -#define WPFCP_LDESC L"%.*S" +# define WPFCP_DESC L"%S" +# define WPFCP_LDESC L"%.*S" /* printf char pointer descriptor */ -#define PFCP_DESC "%s" -#define PFCP_LDESC "%.*s" +# define PFCP_DESC "%s" +# define PFCP_LDESC "%.*s" #else /* _WIN32 */ /* wprintf wide_t pointer descriptor */ -#define WPFWP_DESC L"%S" -#define WPFWP_LDESC L"%.*S" +# define WPFWP_DESC L"%S" +# define WPFWP_LDESC L"%.*S" /* printf wide_t pointer descriptor */ -#define PFWP_DESC "%S" -#define PFWP_LDESC "%.*S" -/* silly M$ */ +# define PFWP_DESC "%S" +# define PFWP_LDESC "%.*S" /* wprintf char pointer descriptor */ -#define WPFCP_DESC L"%s" -#define WPFCP_LDESC L"%.*s" +# define WPFCP_DESC L"%s" +# define WPFCP_LDESC L"%.*s" /* printf char pointer descriptor */ -#define PFCP_DESC "%s" -#define PFCP_LDESC "%.*s" +# define PFCP_DESC "%s" +# define PFCP_LDESC "%.*s" #endif /* _WIN32 */ diff --git a/test/test_conversion_sql2c_boolean.cc b/test/test_conversion_sql2c_boolean.cc new file mode 100644 index 00000000..2454960b --- /dev/null +++ b/test/test_conversion_sql2c_boolean.cc @@ -0,0 +1,202 @@ +/* + * 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_Boolean : public ::testing::Test, public ConnectedDBC { + + protected: + SQLRETURN ret; + SQLLEN ind_len = SQL_NULL_DATA; + + ConvertSQL2C_Boolean() { + } + + virtual ~ConvertSQL2C_Boolean() { + } + + virtual void SetUp() { + } + + virtual void TearDown() { + } + + void prepareStatement(const SQLWCHAR *sql, const char *json_answer) { + char *answer = STRDUP(json_answer); + ASSERT_TRUE(answer != NULL); + ret = ATTACH_ANSWER(stmt, answer, strlen(answer)); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + ret = ATTACH_SQL(stmt, sql, wcslen(sql)); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + } +}; + + +TEST_F(ConvertSQL2C_Boolean, Boolean2Numeric) { + +#undef SQL_VAL +#undef SQL +#define SQL_VAL "true" +#define SQL "CAST(" SQL_VAL " AS SQLNUMERIC)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"boolean\"}\ + ],\ + \"rows\": [\ + [" SQL_VAL "]\ + ]\ +}\ +"; + prepareStatement(MK_WPTR(SQL), 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); + assert(sizeof(ns.val) == 16); + EXPECT_EQ(*(uint64_t *)ns.val, 1L); + EXPECT_EQ(((uint64_t *)ns.val)[1], 0L); +} + +TEST_F(ConvertSQL2C_Boolean, Boolean2STinyInt) { + +#undef SQL_VAL +#undef SQL +#define SQL_VAL "true" +#define SQL "CAST(" SQL_VAL " AS SQLNUMERIC)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"boolean\"}\ + ],\ + \"rows\": [\ + [" SQL_VAL "]\ + ]\ +}\ +"; + prepareStatement(MK_WPTR(SQL), json_answer); + + SQLCHAR c; + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_STINYINT, &c, sizeof(c), &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + EXPECT_EQ(ind_len, sizeof(c)); + EXPECT_EQ(c, 1); +} + +TEST_F(ConvertSQL2C_Boolean, Boolean2UShort) { + +#undef SQL_VAL +#undef SQL +#define SQL_VAL "false" +#define SQL "CAST(" SQL_VAL " AS SQLNUMERIC)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"boolean\"}\ + ],\ + \"rows\": [\ + [" SQL_VAL "]\ + ]\ +}\ +"; + prepareStatement(MK_WPTR(SQL), json_answer); + + SQLUSMALLINT si; + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_USHORT, &si, sizeof(si), &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + EXPECT_EQ(ind_len, sizeof(si)); + EXPECT_EQ(si, 0); +} + +TEST_F(ConvertSQL2C_Boolean, Boolean2SBigInt) { + +#undef SQL_VAL +#undef SQL +#define SQL_VAL "true" +#define SQL "CAST(" SQL_VAL " AS SQLNUMERIC)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"boolean\"}\ + ],\ + \"rows\": [\ + [" SQL_VAL "]\ + ]\ +}\ +"; + prepareStatement(MK_WPTR(SQL), json_answer); + + SQLBIGINT bi; + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_SBIGINT, &bi, sizeof(bi), &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + EXPECT_EQ(ind_len, sizeof(bi)); + EXPECT_EQ(bi, 1); +} + +TEST_F(ConvertSQL2C_Boolean, Boolean2Double) { + +#undef SQL_VAL +#undef SQL +#define SQL_VAL "true" +#define SQL "CAST(" SQL_VAL " AS SQLNUMERIC)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"boolean\"}\ + ],\ + \"rows\": [\ + [" SQL_VAL "]\ + ]\ +}\ +"; + prepareStatement(MK_WPTR(SQL), 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, 1.0); +} + +} // test namespace + From 6a4e52349a66603da7acd497372279c13c0c8334 Mon Sep 17 00:00:00 2001 From: Bogdan Pintea Date: Thu, 14 Jun 2018 17:35:17 +0200 Subject: [PATCH 04/15] b/f: fix record unbinding This only needs to happen when all indicator buffers are NULL. -> use REC_IS_BOUND for this. Also fixed a typo that was checking twice for ARD record type (insead of ARD or APD). --- driver/handles.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/driver/handles.c b/driver/handles.c index 760cb6b6..da5d9165 100644 --- a/driver/handles.c +++ b/driver/handles.c @@ -1995,7 +1995,12 @@ esodbc_metatype_et concise_to_meta(SQLSMALLINT concise_type, * * "If the application changes the data type or attributes after setting the * SQL_DESC_DATA_PTR field, the driver sets SQL_DESC_DATA_PTR to a null - * pointer, unbinding the record." + * pointer, unbinding the record." && + * "If TargetValuePtr is a null pointer, the driver unbinds the data buffer + * for the column. [...] An application can unbind the data buffer for a + * column but still have a length/indicator buffer bound for the column, if + * the TargetValuePtr argument in the call to SQLBindCol is a null pointer but + * the StrLen_or_IndPtr argument is a valid value." * * "The only way to unbind a bookmark column is to set the SQL_DESC_DATA_PTR * field to a null pointer." @@ -2183,12 +2188,12 @@ SQLRETURN EsSQLSetDescFieldW( rec->data_ptr, rec); } } else { - // TODO: should this actually use REC_IS_BOUND()? /* "If the highest-numbered column or parameter is unbound, * then SQL_DESC_COUNT is changed to the number of the next * highest-numbered column or parameter. " */ - if ((desc->type == DESC_TYPE_ARD) || - (desc->type == DESC_TYPE_ARD)) { + if (DESC_TYPE_IS_APPLICATION(desc->type) && + /* see function-top comments on when to unbind */ + (! REC_IS_BOUND(rec))) { DBGH(desc, "rec 0x%p of desc type %d unbound.", rec, desc->type); if (RecNumber == desc->count) { From 063b8d41fb4c715dfe59813b6004378cad47f2c9 Mon Sep 17 00:00:00 2001 From: Bogdan Pintea Date: Thu, 14 Jun 2018 17:38:26 +0200 Subject: [PATCH 05/15] fix: use test name as "SQL query" this allows for faster log lines related to one test --- test/test_conversion_sql2c_boolean.cc | 74 +++++++++++++------------ test/test_conversion_sql2c_date.cc | 33 +++-------- test/test_conversion_sql2c_interval.cc | 27 ++------- test/test_conversion_sql2c_time.cc | 35 +++--------- test/test_conversion_sql2c_timestamp.cc | 37 +++---------- 5 files changed, 68 insertions(+), 138 deletions(-) diff --git a/test/test_conversion_sql2c_boolean.cc b/test/test_conversion_sql2c_boolean.cc index 2454960b..e2daecd9 100644 --- a/test/test_conversion_sql2c_boolean.cc +++ b/test/test_conversion_sql2c_boolean.cc @@ -16,31 +16,6 @@ namespace test { class ConvertSQL2C_Boolean : public ::testing::Test, public ConnectedDBC { - - protected: - SQLRETURN ret; - SQLLEN ind_len = SQL_NULL_DATA; - - ConvertSQL2C_Boolean() { - } - - virtual ~ConvertSQL2C_Boolean() { - } - - virtual void SetUp() { - } - - virtual void TearDown() { - } - - void prepareStatement(const SQLWCHAR *sql, const char *json_answer) { - char *answer = STRDUP(json_answer); - ASSERT_TRUE(answer != NULL); - ret = ATTACH_ANSWER(stmt, answer, strlen(answer)); - ASSERT_TRUE(SQL_SUCCEEDED(ret)); - ret = ATTACH_SQL(stmt, sql, wcslen(sql)); - ASSERT_TRUE(SQL_SUCCEEDED(ret)); - } }; @@ -61,7 +36,7 @@ TEST_F(ConvertSQL2C_Boolean, Boolean2Numeric) { ]\ }\ "; - prepareStatement(MK_WPTR(SQL), json_answer); + prepareStatement(json_answer); SQL_NUMERIC_STRUCT ns; ret = SQLBindCol(stmt, /*col#*/1, SQL_C_NUMERIC, &ns, sizeof(ns), &ind_len); @@ -83,7 +58,7 @@ TEST_F(ConvertSQL2C_Boolean, Boolean2STinyInt) { #undef SQL_VAL #undef SQL #define SQL_VAL "true" -#define SQL "CAST(" SQL_VAL " AS SQLNUMERIC)" +#define SQL "CAST(" SQL_VAL " AS BYTE)" const char json_answer[] = "\ {\ @@ -95,7 +70,7 @@ TEST_F(ConvertSQL2C_Boolean, Boolean2STinyInt) { ]\ }\ "; - prepareStatement(MK_WPTR(SQL), json_answer); + prepareStatement(json_answer); SQLCHAR c; ret = SQLBindCol(stmt, /*col#*/1, SQL_C_STINYINT, &c, sizeof(c), &ind_len); @@ -113,7 +88,7 @@ TEST_F(ConvertSQL2C_Boolean, Boolean2UShort) { #undef SQL_VAL #undef SQL #define SQL_VAL "false" -#define SQL "CAST(" SQL_VAL " AS SQLNUMERIC)" +#define SQL "CAST(" SQL_VAL " AS SHORT)" const char json_answer[] = "\ {\ @@ -125,7 +100,7 @@ TEST_F(ConvertSQL2C_Boolean, Boolean2UShort) { ]\ }\ "; - prepareStatement(MK_WPTR(SQL), json_answer); + prepareStatement(json_answer); SQLUSMALLINT si; ret = SQLBindCol(stmt, /*col#*/1, SQL_C_USHORT, &si, sizeof(si), &ind_len); @@ -143,7 +118,7 @@ TEST_F(ConvertSQL2C_Boolean, Boolean2SBigInt) { #undef SQL_VAL #undef SQL #define SQL_VAL "true" -#define SQL "CAST(" SQL_VAL " AS SQLNUMERIC)" +#define SQL "CAST(" SQL_VAL " AS LONG)" const char json_answer[] = "\ {\ @@ -155,7 +130,7 @@ TEST_F(ConvertSQL2C_Boolean, Boolean2SBigInt) { ]\ }\ "; - prepareStatement(MK_WPTR(SQL), json_answer); + prepareStatement(json_answer); SQLBIGINT bi; ret = SQLBindCol(stmt, /*col#*/1, SQL_C_SBIGINT, &bi, sizeof(bi), &ind_len); @@ -173,7 +148,7 @@ TEST_F(ConvertSQL2C_Boolean, Boolean2Double) { #undef SQL_VAL #undef SQL #define SQL_VAL "true" -#define SQL "CAST(" SQL_VAL " AS SQLNUMERIC)" +#define SQL "CAST(" SQL_VAL " AS DOUBLE)" const char json_answer[] = "\ {\ @@ -185,7 +160,7 @@ TEST_F(ConvertSQL2C_Boolean, Boolean2Double) { ]\ }\ "; - prepareStatement(MK_WPTR(SQL), json_answer); + prepareStatement(json_answer); SQLDOUBLE dbl; ret = SQLBindCol(stmt, /*col#*/1, SQL_C_DOUBLE, &dbl, sizeof(dbl), &ind_len); @@ -198,5 +173,36 @@ TEST_F(ConvertSQL2C_Boolean, Boolean2Double) { EXPECT_EQ(dbl, 1.0); } +TEST_F(ConvertSQL2C_Boolean, Boolean2WString) { + +#undef SQL_VAL +#undef SQL +#define SQL_VAL "true" +#define SQL "CAST(" SQL_VAL " AS W-TEXT)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"boolean\"}\ + ],\ + \"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 namespace diff --git a/test/test_conversion_sql2c_date.cc b/test/test_conversion_sql2c_date.cc index 6de92ddc..07853313 100644 --- a/test/test_conversion_sql2c_date.cc +++ b/test/test_conversion_sql2c_date.cc @@ -18,29 +18,10 @@ namespace test { class ConvertSQL2C_Date : public ::testing::Test, public ConnectedDBC { protected: - SQLRETURN ret; DATE_STRUCT ds; - SQLLEN ind_len = SQL_NULL_DATA; - ConvertSQL2C_Date() { - } - - virtual ~ConvertSQL2C_Date() { - } - - virtual void SetUp() { - } - - virtual void TearDown() { - } - - void prepareStatement(const SQLWCHAR *sql, const char *json_answer) { - char *answer = STRDUP(json_answer); - ASSERT_TRUE(answer != NULL); - ret = ATTACH_ANSWER(stmt, answer, strlen(answer)); - ASSERT_TRUE(SQL_SUCCEEDED(ret)); - ret = ATTACH_SQL(stmt, sql, wcslen(sql)); - ASSERT_TRUE(SQL_SUCCEEDED(ret)); + void prepareAndBind(const char *json_answer) { + prepareStatement(json_answer); ret = SQLBindCol(stmt, /*col#*/1, SQL_C_TYPE_DATE, &ds, sizeof(ds), &ind_len); @@ -67,7 +48,7 @@ TEST_F(ConvertSQL2C_Date, Timestamp2Date) { ]\ }\ "; - prepareStatement(MK_WPTR(SQL), json_answer); + prepareAndBind(json_answer); ret = SQLFetch(stmt); ASSERT_TRUE(SQL_SUCCEEDED(ret)); @@ -95,7 +76,7 @@ TEST_F(ConvertSQL2C_Date, Timestamp2Date_truncate) { ]\ }\ "; - prepareStatement(MK_WPTR(SQL), json_answer); + prepareAndBind(json_answer); ret = SQLFetch(stmt); ASSERT_TRUE(SQL_SUCCEEDED(ret)); @@ -125,7 +106,7 @@ TEST_F(ConvertSQL2C_Date, Date2Date) { ]\ }\ "; - prepareStatement(MK_WPTR(SQL), json_answer); + prepareAndBind(json_answer); ret = SQLFetch(stmt); ASSERT_TRUE(SQL_SUCCEEDED(ret)); @@ -154,7 +135,7 @@ TEST_F(ConvertSQL2C_Date, Time2Date_22018) { ]\ }\ "; - prepareStatement(MK_WPTR(SQL), json_answer); + prepareAndBind(json_answer); ret = SQLFetch(stmt); ASSERT_FALSE(SQL_SUCCEEDED(ret)); @@ -179,7 +160,7 @@ TEST_F(ConvertSQL2C_Date, Integer2Date_violation_07006) { ]\ }\ "; - prepareStatement(MK_WPTR(SQL_VAL), json_answer); + prepareAndBind(json_answer); ret = SQLFetch(stmt); ASSERT_FALSE(SQL_SUCCEEDED(ret)); diff --git a/test/test_conversion_sql2c_interval.cc b/test/test_conversion_sql2c_interval.cc index 7ee9c06c..ff14ef19 100644 --- a/test/test_conversion_sql2c_interval.cc +++ b/test/test_conversion_sql2c_interval.cc @@ -18,29 +18,10 @@ namespace test { class ConvertSQL2C_Interval : public ::testing::Test, public ConnectedDBC { protected: - SQLRETURN ret; SQL_INTERVAL_STRUCT is; - SQLLEN ind_len = SQL_NULL_DATA; - ConvertSQL2C_Interval() { - } - - virtual ~ConvertSQL2C_Interval() { - } - - virtual void SetUp() { - } - - virtual void TearDown() { - } - - void prepareStatement(const SQLWCHAR *sql, const char *json_answer) { - char *answer = STRDUP(json_answer); - ASSERT_TRUE(answer != NULL); - ret = ATTACH_ANSWER(stmt, answer, strlen(answer)); - ASSERT_TRUE(SQL_SUCCEEDED(ret)); - ret = ATTACH_SQL(stmt, sql, wcslen(sql)); - ASSERT_TRUE(SQL_SUCCEEDED(ret)); + void prepareAndBind(const char *jsonAnswer) { + prepareStatement(jsonAnswer); ret = SQLBindCol(stmt, /*col#*/1, SQL_C_INTERVAL_HOUR, &is, sizeof(is), &ind_len); @@ -67,7 +48,7 @@ TEST_F(ConvertSQL2C_Interval, Integer2Interval_unsupported_HYC00) { ]\ }\ "; - prepareStatement(MK_WPTR(SQL), json_answer); + prepareAndBind(json_answer); ret = SQLFetch(stmt); ASSERT_FALSE(SQL_SUCCEEDED(ret)); @@ -92,7 +73,7 @@ TEST_F(ConvertSQL2C_Interval, Integer2Interval_violation_07006) { ]\ }\ "; - prepareStatement(MK_WPTR(SQL), json_answer); + prepareAndBind(json_answer); ret = SQLFetch(stmt); ASSERT_FALSE(SQL_SUCCEEDED(ret)); diff --git a/test/test_conversion_sql2c_time.cc b/test/test_conversion_sql2c_time.cc index 03e8b805..64f9f10e 100644 --- a/test/test_conversion_sql2c_time.cc +++ b/test/test_conversion_sql2c_time.cc @@ -18,29 +18,10 @@ namespace test { class ConvertSQL2C_Time : public ::testing::Test, public ConnectedDBC { protected: - SQLRETURN ret; TIME_STRUCT ts; - SQLLEN ind_len = SQL_NULL_DATA; - ConvertSQL2C_Time() { - } - - virtual ~ConvertSQL2C_Time() { - } - - virtual void SetUp() { - } - - virtual void TearDown() { - } - - void prepareStatement(const SQLWCHAR *sql, const char *json_answer) { - char *answer = STRDUP(json_answer); - ASSERT_TRUE(answer != NULL); - ret = ATTACH_ANSWER(stmt, answer, strlen(answer)); - ASSERT_TRUE(SQL_SUCCEEDED(ret)); - ret = ATTACH_SQL(stmt, sql, wcslen(sql)); - ASSERT_TRUE(SQL_SUCCEEDED(ret)); + void prepareAndBind(const char *jsonAnswer) { + prepareStatement(jsonAnswer); ret = SQLBindCol(stmt, /*col#*/1, SQL_C_TYPE_TIME, &ts, sizeof(ts), &ind_len); @@ -67,7 +48,7 @@ TEST_F(ConvertSQL2C_Time, Timestamp2Time) { ]\ }\ "; - prepareStatement(MK_WPTR(SQL), json_answer); + prepareAndBind(json_answer); ret = SQLFetch(stmt); ASSERT_TRUE(SQL_SUCCEEDED(ret)); @@ -95,7 +76,7 @@ TEST_F(ConvertSQL2C_Time, Timestamp2Time_truncate) { ]\ }\ "; - prepareStatement(MK_WPTR(SQL), json_answer); + prepareAndBind(json_answer); ret = SQLFetch(stmt); ASSERT_TRUE(SQL_SUCCEEDED(ret)); @@ -125,7 +106,7 @@ TEST_F(ConvertSQL2C_Time, Time2Time) { ]\ }\ "; - prepareStatement(MK_WPTR(SQL), json_answer); + prepareAndBind(json_answer); ret = SQLFetch(stmt); ASSERT_TRUE(SQL_SUCCEEDED(ret)); @@ -154,7 +135,7 @@ TEST_F(ConvertSQL2C_Time, Time2Time_truncate) { ]\ }\ "; - prepareStatement(MK_WPTR(SQL), json_answer); + prepareAndBind(json_answer); ret = SQLFetch(stmt); ASSERT_TRUE(SQL_SUCCEEDED(ret)); @@ -185,7 +166,7 @@ TEST_F(ConvertSQL2C_Time, Date2Time_22018) { ]\ }\ "; - prepareStatement(MK_WPTR(SQL_VAL), json_answer); + prepareAndBind(json_answer); ret = SQLFetch(stmt); ASSERT_FALSE(SQL_SUCCEEDED(ret)); @@ -210,7 +191,7 @@ TEST_F(ConvertSQL2C_Time, Integer2Date_violation_07006) { ]\ }\ "; - prepareStatement(MK_WPTR(SQL), json_answer); + prepareAndBind(json_answer); ret = SQLFetch(stmt); ASSERT_FALSE(SQL_SUCCEEDED(ret)); diff --git a/test/test_conversion_sql2c_timestamp.cc b/test/test_conversion_sql2c_timestamp.cc index 45b8a8ef..0923b263 100644 --- a/test/test_conversion_sql2c_timestamp.cc +++ b/test/test_conversion_sql2c_timestamp.cc @@ -18,29 +18,10 @@ namespace test { class ConvertSQL2C_Timestamp : public ::testing::Test, public ConnectedDBC { protected: - SQLRETURN ret; TIMESTAMP_STRUCT ts; - SQLLEN ind_len = SQL_NULL_DATA; - ConvertSQL2C_Timestamp() { - } - - virtual ~ConvertSQL2C_Timestamp() { - } - - virtual void SetUp() { - } - - virtual void TearDown() { - } - - void prepareStatement(const SQLWCHAR *sql, const char *json_answer) { - char *answer = STRDUP(json_answer); - ASSERT_TRUE(answer != NULL); - ret = ATTACH_ANSWER(stmt, answer, strlen(answer)); - ASSERT_TRUE(SQL_SUCCEEDED(ret)); - ret = ATTACH_SQL(stmt, sql, wcslen(sql)); - ASSERT_TRUE(SQL_SUCCEEDED(ret)); + void prepareAndBind(const char *jsonAnswer) { + prepareStatement(jsonAnswer); ret = SQLBindCol(stmt, /*col#*/1, SQL_C_TYPE_TIMESTAMP, &ts, sizeof(ts), &ind_len); @@ -67,7 +48,7 @@ TEST_F(ConvertSQL2C_Timestamp, Timestamp2Timestamp_noTruncate) { ]\ }\ "; - prepareStatement(MK_WPTR(SQL), json_answer); + prepareAndBind(json_answer); ret = SQLFetch(stmt); ASSERT_TRUE(SQL_SUCCEEDED(ret)); @@ -100,7 +81,7 @@ TEST_F(ConvertSQL2C_Timestamp, Timestamp2Timestamp_trimming) { ]\ }\ "; - prepareStatement(MK_WPTR(SQL), json_answer); + prepareAndBind(json_answer); ret = SQLFetch(stmt); ASSERT_TRUE(SQL_SUCCEEDED(ret)); @@ -133,7 +114,7 @@ TEST_F(ConvertSQL2C_Timestamp, Date2Timestamp) { ]\ }\ "; - prepareStatement(MK_WPTR(SQL), json_answer); + prepareAndBind(json_answer); ret = SQLFetch(stmt); ASSERT_TRUE(SQL_SUCCEEDED(ret)); @@ -166,7 +147,7 @@ TEST_F(ConvertSQL2C_Timestamp, Time2Timestamp) { ]\ }\ "; - prepareStatement(MK_WPTR(SQL), json_answer); + prepareAndBind(json_answer); ret = SQLFetch(stmt); ASSERT_TRUE(SQL_SUCCEEDED(ret)); @@ -199,7 +180,7 @@ TEST_F(ConvertSQL2C_Timestamp, Time2Timestamp_trimming) { ]\ }\ "; - prepareStatement(MK_WPTR(SQL), json_answer); + prepareAndBind(json_answer); ret = SQLFetch(stmt); ASSERT_TRUE(SQL_SUCCEEDED(ret)); @@ -232,7 +213,7 @@ TEST_F(ConvertSQL2C_Timestamp, String2Timestamp_invalidFormat_22018) { ]\ }\ "; - prepareStatement(MK_WPTR(SQL), json_answer); + prepareAndBind(json_answer); ret = SQLFetch(stmt); ASSERT_FALSE(SQL_SUCCEEDED(ret)); @@ -256,7 +237,7 @@ TEST_F(ConvertSQL2C_Timestamp, Integer2Timestamp_violation_07006) { ]\ }\ "; - prepareStatement(MK_WPTR(SQL), json_answer); + prepareAndBind(json_answer); ret = SQLFetch(stmt); ASSERT_FALSE(SQL_SUCCEEDED(ret)); From 145ce6470d435927cfcb4a2057ed0e7bd6935a1f Mon Sep 17 00:00:00 2001 From: Bogdan Pintea Date: Thu, 14 Jun 2018 17:40:30 +0200 Subject: [PATCH 06/15] add the conversions for integers Converting between ES/SQL and SQL C types will now work. --- driver/defs.h | 2 +- driver/queries.c | 466 +++++++++++++++--------- test/test_conversion_sql2c_ints.cc | 562 +++++++++++++++++++++++++++++ 3 files changed, 866 insertions(+), 164 deletions(-) create mode 100644 test/test_conversion_sql2c_ints.cc diff --git a/driver/defs.h b/driver/defs.h index c0d2575a..6dfdd451 100644 --- a/driver/defs.h +++ b/driver/defs.h @@ -324,7 +324,7 @@ #define ESODBC_ES_TO_CSQL_DOUBLE SQL_C_DOUBLE #define ESODBC_ES_TO_SQL_DOUBLE SQL_DOUBLE /* 16: ??? -> SQL_C_TINYINT */ -#define ESODBC_ES_TO_CSQL_BOOLEAN SQL_C_STINYINT +#define ESODBC_ES_TO_CSQL_BOOLEAN SQL_C_STINYINT /* TODO: _C_BIT? */ #define ESODBC_ES_TO_SQL_BOOLEAN ESODBC_SQL_BOOLEAN /* 12: SQL_VARCHAR -> SQL_C_WCHAR */ #define ESODBC_ES_TO_CSQL_KEYWORD SQL_C_WCHAR /* XXX: CBOR needs _CHAR */ diff --git a/driver/queries.c b/driver/queries.c index d031b136..f7d77259 100644 --- a/driver/queries.c +++ b/driver/queries.c @@ -7,6 +7,7 @@ #include #include #include /* WideCharToMultiByte() */ +#include #include "ujdecode.h" #include "timestamp.h" @@ -40,6 +41,14 @@ (_tsp)->second = (_tmp)->tm_sec; \ } while (0) +/* For fixed size (destination) types, the target buffer can't be NULL. */ +#define REJECT_IF_NULL_DEST_BUFF(_s/*tatement*/, _p/*ointer*/) \ + do { \ + if (! _p) { \ + ERRH(_s, "destination buffer can't be NULL."); \ + RET_HDIAGS(stmt, SQL_STATE_HY009); \ + } \ + } while (0) /* TODO: this is inefficient: add directly into ujson4c lib (as .size of * ArrayItem struct, inc'd in arrayAddItem()) or local utils file. */ @@ -725,22 +734,21 @@ static size_t buff_octet_size( } /* - * Indicate the amount of data copied (or that would have been, if buffer too - * small) to the application, taking into account: the type of data, - * should truncation - due to max length attr setting - need to be indicated, - * since original length is indicated, w/o possible buffer truncation, but - * with possible 'network' truncation. + * Indicate the amount of data available to the application, taking into + * account: the type of data, should truncation - due to max length attr + * setting - need to be indicated, since original length is indicated, w/o + * possible buffer truncation, but with possible 'network' truncation. */ static inline void write_out_octets( - SQLLEN *octet_len_ptr, /* buffer to write the copied octets into */ - size_t copied, /* amount of bytes copied */ + SQLLEN *octet_len_ptr, /* buffer to write the avail octets into */ + size_t avail, /* amount of bytes avail */ size_t attr_max, /* statement attribute SQL_ATTR_MAX_LENGTH value */ esodbc_metatype_et ird_mt /* meta type of IRD */) { size_t max; if (! octet_len_ptr) { - DBG("NULL octet len pointer, length (%zd) not indicated.", copied); + DBG("NULL octet len pointer, length (%zd) not indicated.", avail); return; } @@ -754,11 +762,14 @@ static inline void write_out_octets( * occupies after conversion: "the driver has no way of * figuring out what the actual length is" */ *octet_len_ptr = max; + DBG("max lenght (%zd) attribute enforced.", max); } else { /* if no "network" truncation done, indicate data's length, no * matter if truncated to buffer's size or not */ - *octet_len_ptr = copied; + *octet_len_ptr = avail; } + + DBG("lenght of data available for transfer: %ld", *octet_len_ptr); } /* if an application doesn't specify the conversion, use column's type */ @@ -777,165 +788,91 @@ static inline SQLSMALLINT get_c_target_type(esodbc_rec_st *arec, return ctype; } - -static SQLRETURN copy_longlong(esodbc_rec_st *arec, esodbc_rec_st *irec, - SQLULEN pos, long long ll) -{ - esodbc_stmt_st *stmt; - void *data_ptr; - SQLLEN *octet_len_ptr; - esodbc_desc_st *ard, *ird; - char buff[sizeof("18446744073709551616")]; /* = 1 << 8*8 */ - SQLWCHAR wbuff[sizeof("18446744073709551616")]; /* = 1 << 8*8 */ - size_t tocopy, blen; - esodbc_state_et state = SQL_STATE_00000; - - stmt = arec->desc->hdr.stmt; - ird = stmt->ird; - ard = stmt->ard; - - /* 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); - if (! data_ptr) { - ERRH(stmt, "received numeric type, but NULL data ptr."); - RET_HDIAGS(stmt, SQL_STATE_HY009); - } - - switch (get_c_target_type(arec, irec)) { - case SQL_C_CHAR: - _i64toa((int64_t)ll, buff, /*radix*/10); - /* TODO: find/write a function that returns len of conversion? */ - blen = strlen(buff) + /* \0 */1; - tocopy = buff_octet_size(blen, arec->octet_length, - stmt->max_length, sizeof(*buff), irec->meta_type, &state); - memcpy(data_ptr, buff, tocopy); - write_out_octets(octet_len_ptr, blen, stmt->max_length, - irec->meta_type); - break; - case SQL_C_WCHAR: - _i64tow((int64_t)ll, wbuff, /*radix*/10); - /* TODO: find/write a function that returns len of conversion? */ - blen = (wcslen(wbuff) + /* \0 */1) * sizeof(*wbuff); - tocopy = buff_octet_size(blen, arec->octet_length, - stmt->max_length, sizeof(*wbuff), irec->meta_type, &state); - memcpy(data_ptr, wbuff, tocopy); - write_out_octets(octet_len_ptr, blen, stmt->max_length, - irec->meta_type); - break; - - case SQL_C_TINYINT: - case SQL_C_STINYINT: - *(SQLSCHAR *)data_ptr = (SQLSCHAR)ll; - write_out_octets(octet_len_ptr, sizeof(SQLSCHAR), - stmt->max_length, irec->meta_type); - break; - - case SQL_C_SHORT: - case SQL_C_SSHORT: - *(SQLSMALLINT *)data_ptr = (SQLSMALLINT)ll; - write_out_octets(octet_len_ptr, sizeof(SQLSMALLINT), - stmt->max_length, irec->meta_type); - break; - - case SQL_C_LONG: - case SQL_C_SLONG: - *(SQLINTEGER *)data_ptr = (SQLINTEGER)ll; - write_out_octets(octet_len_ptr, sizeof(SQLINTEGER), - stmt->max_length, irec->meta_type); - break; - - case SQL_C_SBIGINT: - *(SQLBIGINT *)data_ptr = (SQLBIGINT)ll; - write_out_octets(octet_len_ptr, sizeof(SQLBIGINT), - stmt->max_length, irec->meta_type); - break; - - default: - FIXME; // FIXME - return SQL_ERROR; - } - DBGH(stmt, "REC@0x%p, data_ptr@0x%p, copied long long: `%d`.", arec, - data_ptr, (SQLINTEGER)ll); - - RET_STATE(state); -} - -static SQLRETURN copy_double(esodbc_rec_st *arec, esodbc_rec_st *irec, - SQLULEN pos, double dbl) -{ - FIXME; // FIXME - return SQL_ERROR; -} - -static SQLRETURN transfer_wstr(esodbc_rec_st *arec, esodbc_rec_st *irec, +/* transfer to the application a 0-terminated (but unaccounted for) wstr_st */ +static SQLRETURN transfer_wstr0(esodbc_rec_st *arec, esodbc_rec_st *irec, wstr_st *src, void *w_dst, SQLLEN *len_dst) { size_t in_bytes; - esodbc_state_et state = SQL_STATE_00000; + esodbc_state_et state; + SQLWCHAR *dst; esodbc_stmt_st *stmt = arec->desc->hdr.stmt; - /* the source string must be 0-term'd */ + /* the source string must be 0-term'd (since this needs to be transfered + * out to the application) */ assert(src->str[src->cnt] == 0); - in_bytes = buff_octet_size((src->cnt + 1) * sizeof(*src->str), - (size_t)arec->octet_length, stmt->max_length, - sizeof(*src->str), irec->meta_type, &state); + /* always return the app the untruncated number of bytes */ + write_out_octets(len_dst, src->cnt * sizeof(*src->str), + stmt->max_length, irec->meta_type); + if (w_dst) { - memcpy(w_dst, src->str, in_bytes); + dst = (SQLWCHAR *)w_dst; + state = SQL_STATE_00000; + in_bytes = buff_octet_size((src->cnt + 1) * sizeof(*src->str), + (size_t)arec->octet_length, stmt->max_length, + sizeof(*src->str), irec->meta_type, &state); + + memcpy(dst, src->str, in_bytes); + if (state != SQL_STATE_00000) { /* 0-term the buffer */ ((SQLWCHAR *)w_dst)[(in_bytes/sizeof(SQLWCHAR)) - 1] = 0; + DBGH(stmt, "aREC@0x%p: `" LWPDL "` transfered truncated as `%s`.", + arec, LWSTR(src), dst); + RET_HDIAGS(stmt, state); } else { assert(((SQLWCHAR *)w_dst)[(in_bytes/sizeof(SQLWCHAR)) - 1] == 0); + DBGH(stmt, "aREC@0x%p: `" LWPDL "` transfered @ data_ptr@0x%p.", + arec, LWSTR(src), dst); } + } else { + DBGH(stmt, "aREC@0x%p: NULL transfer buffer.", arec); } - /* always return the app the untruncated number of bytes */ - write_out_octets(len_dst, src->cnt * sizeof(*src->str), - stmt->max_length, irec->meta_type); - - DBGH(stmt, "trasferred into REC@0x%p, data_ptr@0x%p wstring `" LWPDL "`" - " [%zd], state: %d.", arec, w_dst, LWSTR(src), src->cnt, state); - if (state != SQL_STATE_00000) { - RET_HDIAGS(stmt, state); - } return SQL_SUCCESS; } -static SQLRETURN transfer_cstr(esodbc_rec_st *arec, esodbc_rec_st *irec, +/* transfer to the application a 0-terminated (but unaccounted for) cstr_st */ +static SQLRETURN transfer_cstr0(esodbc_rec_st *arec, esodbc_rec_st *irec, cstr_st *src, void *c_dst, SQLLEN *len_dst) { size_t in_bytes; - esodbc_state_et state = SQL_STATE_00000; + esodbc_state_et state; + SQLCHAR *dst = (SQLCHAR *)c_dst; esodbc_stmt_st *stmt = arec->desc->hdr.stmt; - /* the source string must be 0-term'd */ + /* the source string must be 0-term'd (since this needs to be transfered + * out to the application) */ assert(src->str[src->cnt] == 0); - in_bytes = buff_octet_size((src->cnt + 1) * sizeof(*src->str), - (size_t)arec->octet_length, stmt->max_length, - sizeof(*src->str), irec->meta_type, &state); + /* always return the app the untruncated number of bytes */ + write_out_octets(len_dst, src->cnt * sizeof(*src->str), + stmt->max_length, irec->meta_type); + if (c_dst) { - memcpy(c_dst, src->str, in_bytes); + dst = (SQLCHAR *)c_dst; + state = SQL_STATE_00000; + in_bytes = buff_octet_size((src->cnt + 1) * sizeof(*src->str), + (size_t)arec->octet_length, stmt->max_length, + sizeof(*src->str), irec->meta_type, &state); + + memcpy(dst, src->str, in_bytes); + if (state != SQL_STATE_00000) { /* 0-term the buffer */ - ((SQLCHAR *)c_dst)[(in_bytes/sizeof(SQLCHAR)) - 1] = 0; + dst[(in_bytes/sizeof(SQLCHAR)) - 1] = 0; + DBGH(stmt, "aREC@0x%p: `" LCPDL "` transfered truncated as `%s`.", + arec, LCSTR(src), dst); + RET_HDIAGS(stmt, state); } else { - assert(((SQLCHAR *)c_dst)[(in_bytes/sizeof(SQLCHAR)) - 1] == 0); + assert(dst[(in_bytes/sizeof(SQLCHAR)) - 1] == 0); + DBGH(stmt, "aREC@0x%p: `" LCPDL "` transfered @ data_ptr@0x%p.", + arec, LCSTR(src), dst); } + } else { + DBGH(stmt, "aREC@0x%p: NULL transfer buffer.", arec); } - /* always return the app the untruncated number of bytes */ - write_out_octets(len_dst, src->cnt * sizeof(*src->str), - stmt->max_length, irec->meta_type); - - DBGH(stmt, "trasferred into REC@0x%p, data_ptr@0x%p string `" LCPDL "`" - " [%zd], state: %d.", arec, c_dst, LCSTR(src), src->cnt, state); - if (state != SQL_STATE_00000) { - RET_HDIAGS(stmt, state); - } return SQL_SUCCESS; } @@ -956,8 +893,8 @@ static SQLRETURN double_to_numeric(esodbc_rec_st *arec, esodbc_rec_st *irec, SQL_NUMERIC_STRUCT *numeric; esodbc_stmt_st *stmt; SQLSMALLINT prec/*..ision*/, adj_scale /*..usted*/; - unsigned long ulng; - long lng; + unsigned long long ullng; + long long llng; stmt = arec->desc->hdr.stmt; numeric = (SQL_NUMERIC_STRUCT *)dst; @@ -965,19 +902,20 @@ static SQLRETURN double_to_numeric(esodbc_rec_st *arec, esodbc_rec_st *irec, numeric->sign = 0 <= src; - ulng = numeric->sign ? (unsigned long)src : (unsigned long)-src; + ullng = numeric->sign ? (unsigned long long)src : (unsigned long long)-src; /* =~ log10(abs(src)) */ - for (prec = 0 ; ulng; prec ++) { - ulng /= 10; + for (prec = 0 ; ullng; prec ++) { + ullng /= 10; } if (arec->scale < 0) { adj_scale = - arec->scale; - lng = (long)(src / pow10(arec->scale)); + llng = (long long)(src / pow10(arec->scale)); } else { adj_scale = 0; - lng = (long)(src * pow10(arec->scale)); + llng = (long long)(src * pow10(arec->scale)); } - ulng = numeric->sign ? (unsigned long)lng : (unsigned long)-lng; + ullng = numeric->sign ? (unsigned long long)llng : + (unsigned long long)-llng; if (0 < arec->precision && arec->precision < prec - adj_scale) { /* precision of source is higher than requested => overflow */ @@ -992,19 +930,222 @@ static SQLRETURN double_to_numeric(esodbc_rec_st *arec, esodbc_rec_st *irec, #if REG_DWORD != REG_DWORD_LITTLE_ENDIAN - ulng = _byteswap_ulong(ulng); + ullng = _byteswap_ulong(ullng); #endif /* LE */ - assert(sizeof(ulng) <= sizeof(numeric->val)); - memcpy(numeric->val, (char *)&ulng, sizeof(ulng)); - memset(numeric->val+sizeof(ulng), 0, sizeof(numeric->val)-sizeof(ulng)); + assert(sizeof(ullng) <= sizeof(numeric->val)); + memcpy(numeric->val, (char *)&ullng, sizeof(ullng)); + memset(numeric->val+sizeof(ullng), 0, sizeof(numeric->val)-sizeof(ullng)); DBGH(stmt, "double %lf converted to numeric: .sign=%d, precision=%d " - "(req: %d), .scale=%d (req: %d), .val:`" LCPDL "`.", src, + "(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); + numeric->scale, arec->scale, (int)sizeof(numeric->val), numeric->val, + ullng); return SQL_SUCCESS; } +static SQLRETURN copy_longlong(esodbc_rec_st *arec, esodbc_rec_st *irec, + SQLULEN pos, long long ll) +{ + esodbc_stmt_st *stmt; + void *data_ptr; + SQLLEN *octet_len_ptr; + esodbc_desc_st *ard, *ird; + char buff[sizeof("18446744073709551616")]; /* = 1 << 8*8 */ + SQLWCHAR wbuff[sizeof("18446744073709551616")]; + wstr_st llwstr; + cstr_st llcstr; + esodbc_state_et state = SQL_STATE_00000; + SQLRETURN ret; + + stmt = arec->desc->hdr.stmt; + ird = stmt->ird; + ard = stmt->ard; + + /* 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); + +#define REJECT_AS_OOR(_stmt, _ll, _type) /* Out Of Range */ \ + do { \ + ERRH(_stmt, "can't convert value %lld to a %s: out of range", \ + _ll, STR(_type)); \ + RET_HDIAGS(_stmt, SQL_STATE_22003); \ + } while (0) + /* assume a C type behind an SQL C type, but check representation */ +#define REJECT_IF_SIGN_OOR(_stmt, _ll, _min, _max, _sqlctype, _ctype) \ + do { \ + assert(sizeof(_sqlctype) == sizeof(_ctype)); \ + if (_ll < _min || _max < _ll) { \ + REJECT_AS_OOR(_stmt, _ll, _ctype); \ + } \ + } while (0) +#define REJECT_IF_USIGN_OOR(_stmt, _ll, _max, _sqlctype, _ctype) \ + do { \ + assert(sizeof(_sqlctype) == sizeof(_ctype)); \ + if (_max < _ll) { \ + REJECT_AS_OOR(_stmt, _ll, _ctype); \ + } \ + } while (0) + + switch (get_c_target_type(arec, irec)) { + case SQL_C_CHAR: + _i64toa((int64_t)ll, buff, /*radix*/10); + /* TODO: find/write a function that returns len of conversion? */ + llcstr.cnt = strlen(buff); + llcstr.str = buff; + ret = transfer_cstr0(arec, irec, &llcstr, data_ptr, octet_len_ptr); + /* need to change the error code from truncation to "out of + * range", since "whole digits" are truncated */ + if (ret == SQL_SUCCESS_WITH_INFO && + HDRH(stmt)->diag.state == SQL_STATE_01004) { + ERRH(stmt, "can't convert %lld into a string of lenght %d.", + ll, arec->octet_length); + RET_HDIAGS(stmt, SQL_STATE_22003); + } + return ret; + case SQL_C_WCHAR: + _i64tow((int64_t)ll, wbuff, /*radix*/10); + /* TODO: find/write a function that returns len of conversion? */ + llwstr.cnt = wcslen(wbuff); + llwstr.str = wbuff; + ret = transfer_wstr0(arec, irec, &llwstr, data_ptr, octet_len_ptr); + /* need to change the error code from truncation to "out of + * range", since "whole digits" are truncated */ + if (ret == SQL_SUCCESS_WITH_INFO && + HDRH(stmt)->diag.state == SQL_STATE_01004) { + ERRH(stmt, "can't convert %lld into a string of lenght %d.", + ll, arec->octet_length); + RET_HDIAGS(stmt, SQL_STATE_22003); + } + return ret; + + case SQL_C_TINYINT: + case SQL_C_STINYINT: + REJECT_IF_NULL_DEST_BUFF(stmt, data_ptr); + REJECT_IF_SIGN_OOR(stmt, ll, CHAR_MIN, CHAR_MAX, SQLSCHAR, char); + *(SQLSCHAR *)data_ptr = (SQLSCHAR)ll; + write_out_octets(octet_len_ptr, sizeof(SQLSCHAR), + stmt->max_length, irec->meta_type); + break; + case SQL_C_UTINYINT: + REJECT_IF_NULL_DEST_BUFF(stmt, data_ptr); + REJECT_IF_USIGN_OOR(stmt, ll, UCHAR_MAX, SQLCHAR, unsigned char); + *(SQLCHAR *)data_ptr = (SQLCHAR)ll; + write_out_octets(octet_len_ptr, sizeof(SQLCHAR), + stmt->max_length, irec->meta_type); + break; + + case SQL_C_SHORT: + case SQL_C_SSHORT: + REJECT_IF_NULL_DEST_BUFF(stmt, data_ptr); + REJECT_IF_SIGN_OOR(stmt, ll, SHRT_MIN, SHRT_MAX, + SQLSMALLINT, short); + *(SQLSMALLINT *)data_ptr = (SQLSMALLINT)ll; + write_out_octets(octet_len_ptr, sizeof(SQLSMALLINT), + stmt->max_length, irec->meta_type); + break; + case SQL_C_USHORT: + REJECT_IF_NULL_DEST_BUFF(stmt, data_ptr); + REJECT_IF_USIGN_OOR(stmt, ll, USHRT_MAX, + SQLUSMALLINT, unsigned short); + *(SQLUSMALLINT *)data_ptr = (SQLUSMALLINT)ll; + write_out_octets(octet_len_ptr, sizeof(SQLUSMALLINT), + stmt->max_length, irec->meta_type); + break; + + case SQL_C_LONG: + case SQL_C_SLONG: + REJECT_IF_NULL_DEST_BUFF(stmt, data_ptr); + REJECT_IF_SIGN_OOR(stmt, ll, LONG_MIN, LONG_MAX, SQLINTEGER, long); + *(SQLINTEGER *)data_ptr = (SQLINTEGER)ll; + write_out_octets(octet_len_ptr, sizeof(SQLINTEGER), + stmt->max_length, irec->meta_type); + break; + case SQL_C_ULONG: + REJECT_IF_NULL_DEST_BUFF(stmt, data_ptr); + REJECT_IF_USIGN_OOR(stmt, ll, ULONG_MAX, + SQLUINTEGER, unsigned long); + *(SQLUINTEGER *)data_ptr = (SQLUINTEGER)ll; + write_out_octets(octet_len_ptr, sizeof(SQLUINTEGER), + stmt->max_length, irec->meta_type); + break; + + case SQL_C_SBIGINT: + REJECT_IF_NULL_DEST_BUFF(stmt, data_ptr); + REJECT_IF_SIGN_OOR(stmt, ll, LLONG_MIN, LLONG_MAX, + SQLBIGINT, long long); + *(SQLBIGINT *)data_ptr = (SQLBIGINT)ll; + write_out_octets(octet_len_ptr, sizeof(SQLBIGINT), + stmt->max_length, irec->meta_type); + break; + case SQL_C_UBIGINT: + REJECT_IF_NULL_DEST_BUFF(stmt, data_ptr); + REJECT_IF_USIGN_OOR(stmt, ll, ULLONG_MAX, + SQLUBIGINT, unsigned long long); + *(SQLUBIGINT *)data_ptr = (SQLUBIGINT)ll; + write_out_octets(octet_len_ptr, sizeof(SQLUBIGINT), + stmt->max_length, irec->meta_type); + break; + + case SQL_C_BIT: + REJECT_IF_NULL_DEST_BUFF(stmt, data_ptr); + if (ll < 0 || 2 <= ll) { + REJECT_AS_OOR(stmt, ll, SQL_C_BIT); + } else { /* 0 or 1 */ + *(SQLCHAR *)data_ptr = (SQLCHAR)ll; + } + write_out_octets(octet_len_ptr, sizeof(SQLSCHAR), + stmt->max_length, irec->meta_type); + break; + + case SQL_C_NUMERIC: + REJECT_IF_NULL_DEST_BUFF(stmt, data_ptr); + ret = double_to_numeric(arec, irec, (double)ll, data_ptr); + if (! SQL_SUCCEEDED(ret)) { + return ret; + } + write_out_octets(octet_len_ptr, sizeof(SQL_NUMERIC_STRUCT), + stmt->max_length, irec->meta_type); + break; + + case SQL_C_FLOAT: + REJECT_IF_NULL_DEST_BUFF(stmt, data_ptr); + REJECT_IF_SIGN_OOR(stmt, ll, -FLT_MAX, FLT_MAX, SQLREAL, float); + *(SQLREAL *)data_ptr = (SQLREAL)ll; + write_out_octets(octet_len_ptr, sizeof(SQLREAL), + stmt->max_length, irec->meta_type); + break; + + case SQL_C_DOUBLE: + REJECT_IF_NULL_DEST_BUFF(stmt, data_ptr); + REJECT_IF_SIGN_OOR(stmt, ll, -DBL_MAX, DBL_MAX, SQLDOUBLE, double); + *(SQLDOUBLE *)data_ptr = (SQLDOUBLE)ll; + write_out_octets(octet_len_ptr, sizeof(SQLDOUBLE), + stmt->max_length, irec->meta_type); + break; + + default: + FIXME; // FIXME + return SQL_ERROR; + } + DBGH(stmt, "REC@0x%p, data_ptr@0x%p, copied long long: `%d`.", arec, + data_ptr, (SQLINTEGER)ll); + + return SQL_SUCCESS; + +#undef REJECT_AS_OOR +#undef REJECT_IF_OOR +} + +static SQLRETURN copy_double(esodbc_rec_st *arec, esodbc_rec_st *irec, + SQLULEN pos, double dbl) +{ + FIXME; // FIXME + return SQL_ERROR; +} + static SQLRETURN copy_boolean(esodbc_rec_st *arec, esodbc_rec_st *irec, SQLULEN pos, BOOL boolval) { @@ -1028,17 +1169,13 @@ static SQLRETURN copy_boolean(esodbc_rec_st *arec, esodbc_rec_st *irec, 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); - if ((! data_ptr) && (arec->meta_type == METATYPE_EXACT_NUMERIC || - arec->meta_type == METATYPE_FLOAT_NUMERIC)) { - ERRH(stmt, "received boolean type, but NULL data ptr."); - RET_HDIAGS(stmt, SQL_STATE_HY009); - } switch (get_c_target_type(arec, irec)) { case SQL_C_BINARY: case SQL_C_BIT: case SQL_C_STINYINT: case SQL_C_UTINYINT: + REJECT_IF_NULL_DEST_BUFF(stmt, data_ptr); *(SQLSCHAR *)data_ptr = (SQLSCHAR)boolval; write_out_octets(octet_len_ptr, sizeof(SQLSCHAR), stmt->max_length, irec->meta_type); @@ -1046,6 +1183,7 @@ static SQLRETURN copy_boolean(esodbc_rec_st *arec, esodbc_rec_st *irec, case SQL_C_SSHORT: case SQL_C_USHORT: + REJECT_IF_NULL_DEST_BUFF(stmt, data_ptr); *(SQLSMALLINT *)data_ptr = (SQLSMALLINT)boolval; write_out_octets(octet_len_ptr, sizeof(SQLSMALLINT), stmt->max_length, irec->meta_type); @@ -1053,6 +1191,7 @@ static SQLRETURN copy_boolean(esodbc_rec_st *arec, esodbc_rec_st *irec, case SQL_C_SLONG: case SQL_C_ULONG: + REJECT_IF_NULL_DEST_BUFF(stmt, data_ptr); *(SQLINTEGER *)data_ptr = (SQLINTEGER)boolval; write_out_octets(octet_len_ptr, sizeof(SQLINTEGER), stmt->max_length, irec->meta_type); @@ -1060,29 +1199,30 @@ static SQLRETURN copy_boolean(esodbc_rec_st *arec, esodbc_rec_st *irec, case SQL_C_SBIGINT: case SQL_C_UBIGINT: + REJECT_IF_NULL_DEST_BUFF(stmt, data_ptr); *(SQLBIGINT *)data_ptr = (SQLBIGINT)boolval; write_out_octets(octet_len_ptr, sizeof(SQLBIGINT), stmt->max_length, irec->meta_type); break; case SQL_C_FLOAT: + REJECT_IF_NULL_DEST_BUFF(stmt, data_ptr); *(SQLREAL *)data_ptr = boolval ? 1.0f : 0.0f; write_out_octets(octet_len_ptr, sizeof(SQLREAL), stmt->max_length, irec->meta_type); break; case SQL_C_DOUBLE: + REJECT_IF_NULL_DEST_BUFF(stmt, data_ptr); *(SQLDOUBLE *)data_ptr = boolval ? 1.0 : 0.0; write_out_octets(octet_len_ptr, sizeof(SQLDOUBLE), stmt->max_length, irec->meta_type); break; case SQL_C_NUMERIC: - if (data_ptr) { - ret = double_to_numeric(arec, irec, boolval ? 1.0 : 0.0, - data_ptr); - if (! SQL_SUCCEEDED(ret)) { - return ret; - } + REJECT_IF_NULL_DEST_BUFF(stmt, data_ptr); + ret = double_to_numeric(arec, irec, boolval ? 1.0 : 0.0, data_ptr); + if (! SQL_SUCCEEDED(ret)) { + return ret; } write_out_octets(octet_len_ptr, sizeof(SQL_NUMERIC_STRUCT), stmt->max_length, irec->meta_type); @@ -1090,10 +1230,10 @@ static SQLRETURN copy_boolean(esodbc_rec_st *arec, esodbc_rec_st *irec, case SQL_C_WCHAR: wbool = boolval ? MK_WSTR("true") : MK_WSTR("false"); - return transfer_wstr(arec, irec, &wbool, data_ptr, octet_len_ptr); + return transfer_wstr0(arec, irec, &wbool, data_ptr, octet_len_ptr); case SQL_C_CHAR: cbool = boolval ? MK_CSTR("true") : MK_CSTR("false"); - return transfer_cstr(arec, irec, &cbool, data_ptr, octet_len_ptr); + return transfer_cstr0(arec, irec, &cbool, data_ptr, octet_len_ptr); default: FIXME; // FIXME @@ -1191,7 +1331,7 @@ static SQLRETURN wstr_to_wstr(esodbc_rec_st *arec, esodbc_rec_st *irec, const wchar_t *wstr, size_t chars_0) { wstr_st wsrc = {(SQLWCHAR *)wstr, chars_0 - 1}; - return transfer_wstr(arec, irec, &wsrc, data_ptr, octet_len_ptr); + return transfer_wstr0(arec, irec, &wsrc, data_ptr, octet_len_ptr); } /* function expects chars not to count the 0-term */ diff --git a/test/test_conversion_sql2c_ints.cc b/test/test_conversion_sql2c_ints.cc new file mode 100644 index 00000000..0df75561 --- /dev/null +++ b/test/test_conversion_sql2c_ints.cc @@ -0,0 +1,562 @@ +/* + * 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_Ints : public ::testing::Test, public ConnectedDBC { +}; + + +TEST_F(ConvertSQL2C_Ints, Byte2Char) { + +#undef SQL_VAL +#undef SQL +#define SQL_VAL "-128" +#define SQL "CAST(" SQL_VAL " AS TEXT)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"byte\"}\ + ],\ + \"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_Ints, Byte2Char_null_buff_len) { + +#undef SQL_VAL +#undef SQL +#define SQL_VAL "-100" +#define SQL "CAST(" SQL_VAL " AS TEXT)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"byte\"}\ + ],\ + \"rows\": [\ + [" SQL_VAL "]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_CHAR, NULL, 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); +} + + +TEST_F(ConvertSQL2C_Ints, Short2WChar) { + +#undef SQL_VAL +#undef SQL +#define SQL_VAL "-128" +#define SQL "CAST(" SQL_VAL " AS W-TEXT)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"short\"}\ + ],\ + \"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_Ints, Long2Char_truncate_22003) { + +#undef SQL_VAL +#undef SQL +#define SQL_VAL "12345678" +#define SQL "CAST(" SQL_VAL " AS TEXT)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"integer\"}\ + ],\ + \"rows\": [\ + [" SQL_VAL "]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + SQLCHAR buff[sizeof(SQL_VAL)/2]; + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_CHAR, &buff, sizeof(buff), &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_FALSE(SQL_SUCCEEDED(ret)); + assertState(L"22003"); +} + + +TEST_F(ConvertSQL2C_Ints, Short2Byte) { + +#undef SQL_VAL +#undef SQL +#define SQL_RAW -128 +#define SQL_VAL STR(SQL_RAW) +#define SQL "CAST(" SQL_VAL " AS SHORT)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"short\"}\ + ],\ + \"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_Ints, Short2Byte_truncate_22003) { + +#undef SQL_RAW +#undef SQL_VAL +#undef SQL +#define SQL_RAW -129 +#define SQL_VAL STR(SQL_RAW) +#define SQL "CAST(" SQL_VAL " AS SHORT)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"short\"}\ + ],\ + \"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_FALSE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_FALSE(SQL_SUCCEEDED(ret)); + assertState(L"22003"); +} + + +TEST_F(ConvertSQL2C_Ints, Short2UByte) { + +#undef SQL_RAW +#undef SQL_VAL +#undef SQL +#define SQL_RAW 255 +#define SQL_VAL STR(SQL_RAW) +#define SQL "CAST(" SQL_VAL " AS SHORT)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"short\"}\ + ],\ + \"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_Ints, Byte2UShort) { + +#undef SQL_RAW +#undef SQL_VAL +#undef SQL +#define SQL_RAW 255 +#define SQL_VAL STR(SQL_RAW) +#define SQL "CAST(" SQL_VAL " AS SHORT)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"byte\"}\ + ],\ + \"rows\": [\ + [" SQL_VAL "]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + SQLUSMALLINT ushort; + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_USHORT, &ushort, sizeof(ushort), + &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + EXPECT_EQ(ind_len, sizeof(ushort)); + EXPECT_EQ(ushort, SQL_RAW); +} + + +TEST_F(ConvertSQL2C_Ints, Byte2UShort_negative2unsigned) { + +#undef SQL_RAW +#undef SQL_VAL +#undef SQL +#define SQL_RAW -1 +#define SQL_VAL STR(SQL_RAW) +#define SQL "CAST(" SQL_VAL " AS SHORT)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"short\"}\ + ],\ + \"rows\": [\ + [" SQL_VAL "]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + SQLUSMALLINT ushort; + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_USHORT, &ushort, sizeof(ushort), + &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + EXPECT_EQ(ind_len, sizeof(ushort)); + EXPECT_EQ(ushort, USHRT_MAX); +} + + +TEST_F(ConvertSQL2C_Ints, Integer2Null_fail_HY009) { + +#undef SQL_RAW +#undef SQL_VAL +#undef SQL +#define SQL_RAW 256 +#define SQL_VAL STR(SQL_RAW) +#define SQL "CAST(" SQL_VAL " AS INTEGER)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"integer\"}\ + ],\ + \"rows\": [\ + [" SQL_VAL "]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_ULONG, NULL, 0, &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_FALSE(SQL_SUCCEEDED(ret)); + assertState(L"HY009"); +} + + +TEST_F(ConvertSQL2C_Ints, Long2BigInt_signed_min) { + +#undef SQL_RAW +#undef SQL_VAL +#undef SQL +#define SQL_RAW LLONG_MIN +//#define SQL_VAL STR(SQL_RAW) +#ifdef _WIN64 +#define SQL_VAL "-9223372036854775808" +#else /* _WIN64 */ +#define SQL_VAL "-2147483648" +#endif /* _WIN64 */ +#define SQL "CAST(" SQL_VAL " AS INTEGER)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"integer\"}\ + ],\ + \"rows\": [\ + [" SQL_VAL "]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + SQLBIGINT bi; + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_SBIGINT, &bi, sizeof(bi), &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + EXPECT_EQ(ind_len, sizeof(bi)); + EXPECT_EQ(bi, SQL_RAW); +} + + +TEST_F(ConvertSQL2C_Ints, Long2UBigInt_signed_max) { + +#undef SQL_RAW +#undef SQL_VAL +#undef SQL +#define SQL_RAW LLONG_MAX +//#define SQL_VAL STR(SQL_RAW) +#ifdef _WIN64 +#define SQL_VAL "9223372036854775807" +#else /* _WIN64 */ +#define SQL_VAL "2147483647" +#endif /* _WIN64 */ +#define SQL "CAST(" SQL_VAL " AS INTEGER)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"integer\"}\ + ],\ + \"rows\": [\ + [" SQL_VAL "]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + SQLUBIGINT ubi; + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_UBIGINT, &ubi, sizeof(ubi), + &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + EXPECT_EQ(ind_len, sizeof(ubi)); + EXPECT_EQ(ubi, SQL_RAW); +} + +TEST_F(ConvertSQL2C_Ints, Long2Numeric) { + +#undef SQL_RAW +#undef SQL_VAL +#undef SQL +#define SQL_RAW 0xDeadBeef +#define SQL_VAL "3735928559" +#define SQL "CAST(" SQL_VAL " AS SQLNUMERIC)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"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); + 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_Ints, Long2Bit) { + +#undef SQL_RAW +#undef SQL_VAL +#undef SQL +#define SQL_RAW 1 +#define SQL_VAL STR(SQL_RAW) +#define SQL "CAST(" SQL_VAL " AS INTEGER)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"integer\"}\ + ],\ + \"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_Ints, Long2Float) { + +#undef SQL_RAW +#undef SQL_VAL +#undef SQL +#define SQL_RAW 123456 +#define SQL_VAL STR(SQL_RAW) +#define SQL "CAST(" SQL_VAL " AS INTEGER)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"integer\"}\ + ],\ + \"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_Ints, Long2Double) { + +#undef SQL_RAW +#undef SQL_VAL +#undef SQL +#define SQL_RAW -9223372036854775807 +#define SQL_VAL STR(SQL_RAW) +#define SQL "CAST(" SQL_VAL " AS INTEGER)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"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 namespace + From 9bbf5cceafef888180d07e9ecb2160821d7b6d0f Mon Sep 17 00:00:00 2001 From: Bogdan Pintea Date: Thu, 14 Jun 2018 17:42:32 +0200 Subject: [PATCH 07/15] complete 063b8d4 these changes belong to the set in 063b8d4. --- test/connected_dbc.cc | 25 +++++++++++++++++++++++++ test/connected_dbc.h | 7 +++++++ 2 files changed, 32 insertions(+) diff --git a/test/connected_dbc.cc b/test/connected_dbc.cc index 5c5e8f66..a51d64e5 100644 --- a/test/connected_dbc.cc +++ b/test/connected_dbc.cc @@ -143,3 +143,28 @@ void ConnectedDBC::assertState(const SQLWCHAR *state) { ASSERT_STREQ(buff, state); } + +void ConnectedDBC::prepareStatement(const SQLWCHAR *sql, + const char *json_answer) { + char *answer = STRDUP(json_answer); + ASSERT_TRUE(answer != NULL); + ret = ATTACH_ANSWER(stmt, answer, strlen(answer)); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + ret = ATTACH_SQL(stmt, sql, wcslen(sql)); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); +} + +void ConnectedDBC::prepareStatement(const char *json_answer) { + char *answer = STRDUP(json_answer); + ASSERT_TRUE(answer != NULL); + ret = ATTACH_ANSWER(stmt, answer, strlen(answer)); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + const char *testName = + ::testing::UnitTest::GetInstance()->current_test_info()->name(); + size_t nameLen = strlen(testName); + std::wstring wstr(nameLen, L' '); + ASSERT_TRUE(mbstowcs(&wstr[0], testName, nameLen + 1) != (size_t)-1); + ret = ATTACH_SQL(stmt, &wstr[0], nameLen); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); +} diff --git a/test/connected_dbc.h b/test/connected_dbc.h index 63a9c568..bdc48156 100644 --- a/test/connected_dbc.h +++ b/test/connected_dbc.h @@ -32,11 +32,18 @@ extern "C" { class ConnectedDBC { protected: SQLHANDLE env, dbc, stmt; + SQLRETURN ret; + SQLLEN ind_len = SQL_NULL_DATA; + ConnectedDBC(); virtual ~ConnectedDBC(); void assertState(const SQLWCHAR *state); + // use an actual SQL statement (if it might be processed) + void prepareStatement(const SQLWCHAR *sql, const char *json_answer); + // use the test name as SQL (for faster working with the logs) + void prepareStatement(const char *json_answer); }; #endif /* __CONNECTED_DBC_H__ */ From 03d00aadedad5c36cd990ad798b48e26084b03c4 Mon Sep 17 00:00:00 2001 From: Bogdan Pintea Date: Thu, 14 Jun 2018 20:35:55 +0200 Subject: [PATCH 08/15] add integers to binary conversion - also, pipe all conversions of boolean through longlogn conversion, except the strings ones. --- driver/queries.c | 134 +++++++++++++---------------- test/test_conversion_sql2c_ints.cc | 45 ++++++++-- 2 files changed, 100 insertions(+), 79 deletions(-) diff --git a/driver/queries.c b/driver/queries.c index f7d77259..10da5a2d 100644 --- a/driver/queries.c +++ b/driver/queries.c @@ -877,9 +877,9 @@ static SQLRETURN transfer_cstr0(esodbc_rec_st *arec, esodbc_rec_st *irec, } /* 10^n */ -static inline long pow10(int n) +static inline long long pow10(int n) { - long pow = 1; + long long pow = 1; pow <<= n; while (n--) { pow += pow << 2; @@ -892,7 +892,7 @@ static SQLRETURN double_to_numeric(esodbc_rec_st *arec, esodbc_rec_st *irec, { SQL_NUMERIC_STRUCT *numeric; esodbc_stmt_st *stmt; - SQLSMALLINT prec/*..ision*/, adj_scale /*..usted*/; + SQLSMALLINT prec/*..ision*/; unsigned long long ullng; long long llng; @@ -900,6 +900,7 @@ static SQLRETURN double_to_numeric(esodbc_rec_st *arec, esodbc_rec_st *irec, numeric = (SQL_NUMERIC_STRUCT *)dst; assert(numeric); + numeric->scale = (SQLCHAR)arec->scale; numeric->sign = 0 <= src; ullng = numeric->sign ? (unsigned long long)src : (unsigned long long)-src; @@ -908,25 +909,28 @@ static SQLRETURN double_to_numeric(esodbc_rec_st *arec, esodbc_rec_st *irec, ullng /= 10; } if (arec->scale < 0) { - adj_scale = - arec->scale; llng = (long long)(src / pow10(arec->scale)); - } else { - adj_scale = 0; + prec -= arec->scale; + } else if (0 < arec->scale) { llng = (long long)(src * pow10(arec->scale)); + prec += arec->scale; + } else { + llng = (long long)src; } ullng = numeric->sign ? (unsigned long long)llng : (unsigned long long)-llng; - if (0 < arec->precision && arec->precision < prec - adj_scale) { + if ((0 < arec->precision && arec->precision < prec) + || (UCHAR_MAX < prec)) { /* precision of source is higher than requested => overflow */ ERRH(stmt, "conversion overflow. source: %lf; requested: " "precisions: %d, scale: %d.", src, arec->precision, arec->scale); RET_HDIAGS(stmt, SQL_STATE_22003); - } else { - numeric->precision = (SQLCHAR)arec->precision; + } else if (prec < 0) { + prec = 0; + assert(ullng == 0); } - numeric->scale = (SQLCHAR)arec->scale; - numeric->sign = 0 <= src; + numeric->precision = (SQLCHAR)prec; #if REG_DWORD != REG_DWORD_LITTLE_ENDIAN @@ -944,6 +948,46 @@ static SQLRETURN double_to_numeric(esodbc_rec_st *arec, esodbc_rec_st *irec, return SQL_SUCCESS; } +/* + * https://docs.microsoft.com/en-us/sql/odbc/reference/appendixes/transferring-data-in-its-binary-form + */ +static SQLRETURN llong_to_binary(esodbc_rec_st *arec, esodbc_rec_st *irec, + long long src, void *dst, SQLLEN *src_len) +{ + size_t cnt; + char *s = (char *)&src; + esodbc_stmt_st *stmt = arec->desc->hdr.stmt; + unsigned long long ull = src < 0 ? -src : src; + + /* UJ4C uses long long for any integer type -> find out the + * smallest type that would accomodate the value */ + if (ull < CHAR_MAX) { + cnt = sizeof(char); + } else if (ull < SHRT_MAX) { + cnt = sizeof(short); + } else if (ull < INT_MAX) { + cnt = sizeof(int); + } else if (ull < LONG_MAX) { + cnt = sizeof(long); + } else { /* definetely ull < LLONG_MAX */ + cnt = sizeof(long long); + } + if (arec->octet_length < (SQLLEN)cnt) { + ERRH(stmt, "can't convert value %lld on %zd octets: out of range", + src, cnt); + RET_HDIAGS(stmt, SQL_STATE_22003); + } + + /* copy bytes as-are: the reverse conversion need to take place on same + * "DBMS and hardare platform". */ + memcpy(dst, s, cnt); + memset((char *)dst + cnt, 0, arec->octet_length - cnt); + write_out_octets(src_len, cnt, stmt->max_length, irec->meta_type); + DBGH(stmt, "long long value %lld, converted on %zd octets.", src, cnt); + + return SQL_SUCCESS; +} + static SQLRETURN copy_longlong(esodbc_rec_st *arec, esodbc_rec_st *irec, SQLULEN pos, long long ll) { @@ -1126,8 +1170,12 @@ static SQLRETURN copy_longlong(esodbc_rec_st *arec, esodbc_rec_st *irec, stmt->max_length, irec->meta_type); break; + case SQL_C_BINARY: + return llong_to_binary(arec, irec, ll, data_ptr, octet_len_ptr); + default: - FIXME; // FIXME + BUGH(stmt, "unexpected unhanlded data type: %d.", + get_c_target_type(arec, irec)); return SQL_ERROR; } DBGH(stmt, "REC@0x%p, data_ptr@0x%p, copied long long: `%d`.", arec, @@ -1154,7 +1202,6 @@ static SQLRETURN copy_boolean(esodbc_rec_st *arec, esodbc_rec_st *irec, SQLLEN *octet_len_ptr; wstr_st wbool; cstr_st cbool; - SQLRETURN ret; stmt = arec->desc->hdr.stmt; @@ -1171,73 +1218,14 @@ static SQLRETURN copy_boolean(esodbc_rec_st *arec, esodbc_rec_st *irec, data_ptr = deferred_address(SQL_DESC_DATA_PTR, pos, arec); switch (get_c_target_type(arec, irec)) { - case SQL_C_BINARY: - case SQL_C_BIT: - case SQL_C_STINYINT: - case SQL_C_UTINYINT: - REJECT_IF_NULL_DEST_BUFF(stmt, data_ptr); - *(SQLSCHAR *)data_ptr = (SQLSCHAR)boolval; - write_out_octets(octet_len_ptr, sizeof(SQLSCHAR), - stmt->max_length, irec->meta_type); - break; - - case SQL_C_SSHORT: - case SQL_C_USHORT: - REJECT_IF_NULL_DEST_BUFF(stmt, data_ptr); - *(SQLSMALLINT *)data_ptr = (SQLSMALLINT)boolval; - write_out_octets(octet_len_ptr, sizeof(SQLSMALLINT), - stmt->max_length, irec->meta_type); - break; - - case SQL_C_SLONG: - case SQL_C_ULONG: - REJECT_IF_NULL_DEST_BUFF(stmt, data_ptr); - *(SQLINTEGER *)data_ptr = (SQLINTEGER)boolval; - write_out_octets(octet_len_ptr, sizeof(SQLINTEGER), - stmt->max_length, irec->meta_type); - break; - - case SQL_C_SBIGINT: - case SQL_C_UBIGINT: - REJECT_IF_NULL_DEST_BUFF(stmt, data_ptr); - *(SQLBIGINT *)data_ptr = (SQLBIGINT)boolval; - write_out_octets(octet_len_ptr, sizeof(SQLBIGINT), - stmt->max_length, irec->meta_type); - break; - - case SQL_C_FLOAT: - REJECT_IF_NULL_DEST_BUFF(stmt, data_ptr); - *(SQLREAL *)data_ptr = boolval ? 1.0f : 0.0f; - write_out_octets(octet_len_ptr, sizeof(SQLREAL), - stmt->max_length, irec->meta_type); - break; - case SQL_C_DOUBLE: - REJECT_IF_NULL_DEST_BUFF(stmt, data_ptr); - *(SQLDOUBLE *)data_ptr = boolval ? 1.0 : 0.0; - write_out_octets(octet_len_ptr, sizeof(SQLDOUBLE), - stmt->max_length, irec->meta_type); - break; - - case SQL_C_NUMERIC: - REJECT_IF_NULL_DEST_BUFF(stmt, data_ptr); - ret = double_to_numeric(arec, irec, boolval ? 1.0 : 0.0, data_ptr); - if (! SQL_SUCCEEDED(ret)) { - return ret; - } - write_out_octets(octet_len_ptr, sizeof(SQL_NUMERIC_STRUCT), - stmt->max_length, irec->meta_type); - break; - case SQL_C_WCHAR: wbool = boolval ? MK_WSTR("true") : MK_WSTR("false"); return transfer_wstr0(arec, irec, &wbool, data_ptr, octet_len_ptr); case SQL_C_CHAR: cbool = boolval ? MK_CSTR("true") : MK_CSTR("false"); return transfer_cstr0(arec, irec, &cbool, data_ptr, octet_len_ptr); - default: - FIXME; // FIXME - return SQL_ERROR; + return copy_longlong(arec, irec, pos, boolval ? 1L : 0L); } DBGH(stmt, "REC@0x%p, data_ptr@0x%p, copied boolean: `%d`.", arec, diff --git a/test/test_conversion_sql2c_ints.cc b/test/test_conversion_sql2c_ints.cc index 0df75561..0e56acfe 100644 --- a/test/test_conversion_sql2c_ints.cc +++ b/test/test_conversion_sql2c_ints.cc @@ -347,7 +347,7 @@ TEST_F(ConvertSQL2C_Ints, Long2BigInt_signed_min) { #define SQL_RAW LLONG_MIN //#define SQL_VAL STR(SQL_RAW) #ifdef _WIN64 -#define SQL_VAL "-9223372036854775808" +#define SQL_VAL "-9223372036854775808" // 0x8000000000000000 #else /* _WIN64 */ #define SQL_VAL "-2147483648" #endif /* _WIN64 */ @@ -385,7 +385,7 @@ TEST_F(ConvertSQL2C_Ints, Long2UBigInt_signed_max) { #define SQL_RAW LLONG_MAX //#define SQL_VAL STR(SQL_RAW) #ifdef _WIN64 -#define SQL_VAL "9223372036854775807" +#define SQL_VAL "9223372036854775807" // 0x7fffffffffffffff #else /* _WIN64 */ #define SQL_VAL "2147483647" #endif /* _WIN64 */ @@ -421,7 +421,7 @@ TEST_F(ConvertSQL2C_Ints, Long2Numeric) { #undef SQL_VAL #undef SQL #define SQL_RAW 0xDeadBeef -#define SQL_VAL "3735928559" +#define SQL_VAL "3735928559" // 0xdeadbeef #define SQL "CAST(" SQL_VAL " AS SQLNUMERIC)" const char json_answer[] = "\ @@ -446,6 +446,7 @@ TEST_F(ConvertSQL2C_Ints, Long2Numeric) { 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 @@ -529,7 +530,7 @@ TEST_F(ConvertSQL2C_Ints, Long2Double) { #undef SQL_RAW #undef SQL_VAL #undef SQL -#define SQL_RAW -9223372036854775807 +#define SQL_RAW -9223372036854775807 // 0x7fffffffffffffff #define SQL_VAL STR(SQL_RAW) #define SQL "CAST(" SQL_VAL " AS INTEGER)" @@ -546,8 +547,7 @@ TEST_F(ConvertSQL2C_Ints, Long2Double) { prepareStatement(json_answer); SQLDOUBLE dbl; - ret = SQLBindCol(stmt, /*col#*/1, SQL_C_DOUBLE, &dbl, sizeof(dbl), - &ind_len); + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_DOUBLE, &dbl, sizeof(dbl), &ind_len); ASSERT_TRUE(SQL_SUCCEEDED(ret)); ret = SQLFetch(stmt); @@ -558,5 +558,38 @@ TEST_F(ConvertSQL2C_Ints, Long2Double) { } +TEST_F(ConvertSQL2C_Ints, Long2Binary) { + +#undef SQL_RAW +#undef SQL_VAL +#undef SQL +#define SQL_RAW 0xBeefed +#define SQL_VAL "12513261" // 0xbeefed +#define SQL "CAST(" SQL_VAL " AS INTEGER)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"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); + EXPECT_EQ(bin, SQL_RAW); +} + + } // test namespace From 79f79ebae97d087ece6d9e73ab26efdf2e4e6538 Mon Sep 17 00:00:00 2001 From: Bogdan Pintea Date: Mon, 18 Jun 2018 15:35:17 +0200 Subject: [PATCH 09/15] add conversion for floats - add unit testing for them - small refactoring of ints conversions to reduce code duplication --- driver/connect.c | 9 + driver/defs.h | 5 + driver/handles.c | 4 + driver/queries.c | 627 +++++++++++++++++++-------- driver/util.c | 23 + driver/util.h | 5 + test/connected_dbc.cc | 10 +- test/test_conversion_sql2c_floats.cc | 554 +++++++++++++++++++++++ test/test_conversion_sql2c_ints.cc | 64 +++ 9 files changed, 1111 insertions(+), 190 deletions(-) create mode 100644 test/test_conversion_sql2c_floats.cc diff --git a/driver/connect.c b/driver/connect.c index fe3a9133..138b0c79 100644 --- a/driver/connect.c +++ b/driver/connect.c @@ -1918,6 +1918,15 @@ static BOOL prompt_user(config_attrs_st *attrs, BOOL disable_conn) // TODO: display settings dialog box; // An error message (if popping it more than once) might be needed. // +#if 1 // FIXME + static int i = 0; + attrs->server = MK_WSTR("10.0.2.2"); + i ++; + if (2 < i) { + return FALSE; + } + return TRUE; +#endif return FALSE; } diff --git a/driver/defs.h b/driver/defs.h index 6dfdd451..cbf7d84a 100644 --- a/driver/defs.h +++ b/driver/defs.h @@ -56,6 +56,11 @@ /* match 'keyword' ES type lenght */ #define ESODBC_MAX_IDENTIFIER_LEN 256 +/* 20 = len("18446744073709551616"), 1 << (sizeof(uint64_t) * 8bits) */ +#define ESODBC_PRECISION_UINT64 20 +/* 19 = len("9223372036854775808"), 1 << 63 */ +#define ESODBC_PRECISION_INT64 19 + /* * Not authoriative (there's actually no formal limit), but pretty informed: diff --git a/driver/handles.c b/driver/handles.c index da5d9165..3027cff2 100644 --- a/driver/handles.c +++ b/driver/handles.c @@ -1723,6 +1723,8 @@ void concise_to_type_code(SQLSMALLINT concise, SQLSMALLINT *type, */ static void set_defaults_from_type(esodbc_rec_st *rec) { + DBGH(rec->desc, "(re)setting record@0x%p lenght/precision/scale to " + "defaults.", rec); switch (rec->meta_type) { case METATYPE_STRING: rec->length = 1; @@ -2138,6 +2140,8 @@ SQLRETURN EsSQLSetDescFieldW( * buffer(s), so the above "binding" definition is incomplete. */ if (FieldIdentifier != SQL_DESC_DATA_PTR) { + DBGH(desc, "attribute to set is different than %d => unbinding data " + "buffer (was 0x%p).", rec->data_ptr); rec->data_ptr = NULL; } diff --git a/driver/queries.c b/driver/queries.c index 10da5a2d..28a2533f 100644 --- a/driver/queries.c +++ b/driver/queries.c @@ -8,6 +8,7 @@ #include #include /* WideCharToMultiByte() */ #include +#include #include "ujdecode.h" #include "timestamp.h" @@ -49,6 +50,17 @@ RET_HDIAGS(stmt, SQL_STATE_HY009); \ } \ } while (0) +#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", \ + _val, STR(_target)); \ + } else { \ + ERRH(_stmt, "can't convert value %f to %s: out of range", \ + _val, STR(_target)); \ + } \ + RET_HDIAGS(_stmt, SQL_STATE_22003); \ + } while (0) /* TODO: this is inefficient: add directly into ujson4c lib (as .size of * ArrayItem struct, inc'd in arrayAddItem()) or local utils file. */ @@ -252,6 +264,7 @@ SQLRETURN TEST_API attach_answer(esodbc_stmt_st *stmt, char *buff, size_t blen) /* the statement takes ownership of mem obj */ stmt->rset.buff = buff; stmt->rset.blen = blen; + DBGH(stmt, "attaching answer [%zd]`" LCPDL "`.", blen, blen, buff); /* parse the entire JSON answer */ obj = UJDecode(buff, blen, NULL, &stmt->rset.state); @@ -692,14 +705,23 @@ static void *deferred_address(SQLSMALLINT field_id, size_t pos, */ static size_t buff_octet_size( size_t avail, /* how many bytes are there to copy out */ - size_t room, /* how large (bytes) is the buffer to copy into*/ - size_t attr_max, /* statement attribute SQL_ATTR_MAX_LENGTH value */ size_t unit_size, /* the unit size of the buffer (i.e. sizeof(wchar_t)) */ - esodbc_metatype_et ird_mt, /* meta type of IRD */ + esodbc_rec_st *arec, esodbc_rec_st *irec, esodbc_state_et *state /* out param: only written when truncating */) { + esodbc_stmt_st *stmt = arec->desc->hdr.stmt; + /* how large (bytes) is the buffer to copy into*/ + size_t room = (size_t)arec->octet_length; + /* statement attribute SQL_ATTR_MAX_LENGTH value */ + size_t attr_max = stmt->max_length; + /* meta type of IRD */ + esodbc_metatype_et ird_mt = irec->meta_type; size_t max_copy, max; + /* type is signed, driver should not allow a negative to this point: + * making sure the cast above is sane. */ + assert(0 <= arec->octet_length); + /* truncate to statment max bytes, only if "the column contains character * or binary data" */ max = (ird_mt == METATYPE_STRING || ird_mt == METATYPE_BIN) ? attr_max : 0; @@ -742,9 +764,13 @@ static size_t buff_octet_size( static inline void write_out_octets( SQLLEN *octet_len_ptr, /* buffer to write the avail octets into */ size_t avail, /* amount of bytes avail */ - size_t attr_max, /* statement attribute SQL_ATTR_MAX_LENGTH value */ - esodbc_metatype_et ird_mt /* meta type of IRD */) + esodbc_rec_st *irec) { + esodbc_stmt_st *stmt = irec->desc->hdr.stmt; + /* statement attribute SQL_ATTR_MAX_LENGTH value */ + size_t attr_max = stmt->max_length; + /* meta type of IRD */ + esodbc_metatype_et ird_mt = irec->meta_type; size_t max; if (! octet_len_ptr) { @@ -790,7 +816,7 @@ static inline SQLSMALLINT get_c_target_type(esodbc_rec_st *arec, /* transfer to the application a 0-terminated (but unaccounted for) wstr_st */ static SQLRETURN transfer_wstr0(esodbc_rec_st *arec, esodbc_rec_st *irec, - wstr_st *src, void *w_dst, SQLLEN *len_dst) + wstr_st *src, void *data_ptr, SQLLEN *octet_len_ptr) { size_t in_bytes; esodbc_state_et state; @@ -802,28 +828,31 @@ static SQLRETURN transfer_wstr0(esodbc_rec_st *arec, esodbc_rec_st *irec, assert(src->str[src->cnt] == 0); /* always return the app the untruncated number of bytes */ - write_out_octets(len_dst, src->cnt * sizeof(*src->str), - stmt->max_length, irec->meta_type); + write_out_octets(octet_len_ptr, src->cnt * sizeof(*src->str), irec); - if (w_dst) { - dst = (SQLWCHAR *)w_dst; + if (data_ptr) { + dst = (SQLWCHAR *)data_ptr; state = SQL_STATE_00000; in_bytes = buff_octet_size((src->cnt + 1) * sizeof(*src->str), - (size_t)arec->octet_length, stmt->max_length, - sizeof(*src->str), irec->meta_type, &state); - - memcpy(dst, src->str, in_bytes); - - if (state != SQL_STATE_00000) { - /* 0-term the buffer */ - ((SQLWCHAR *)w_dst)[(in_bytes/sizeof(SQLWCHAR)) - 1] = 0; - DBGH(stmt, "aREC@0x%p: `" LWPDL "` transfered truncated as `%s`.", - arec, LWSTR(src), dst); - RET_HDIAGS(stmt, state); - } else { - assert(((SQLWCHAR *)w_dst)[(in_bytes/sizeof(SQLWCHAR)) - 1] == 0); - DBGH(stmt, "aREC@0x%p: `" LWPDL "` transfered @ data_ptr@0x%p.", - arec, LWSTR(src), dst); + sizeof(*src->str), arec, irec, &state); + + if (in_bytes) { + memcpy(dst, src->str, in_bytes); + /* TODO: should the left be filled with spaces? : + * https://docs.microsoft.com/en-us/sql/odbc/reference/appendixes/rules-for-conversions */ + + if (state != SQL_STATE_00000) { + /* 0-term the buffer */ + ((SQLWCHAR *)data_ptr)[(in_bytes/sizeof(SQLWCHAR)) - 1] = 0; + DBGH(stmt, "aREC@0x%p: `" LWPDL "` transfered truncated as " + "`%s`.", arec, LWSTR(src), dst); + RET_HDIAGS(stmt, state); + } else { + assert(((SQLWCHAR *)data_ptr)[(in_bytes/sizeof(SQLWCHAR))-1] + == 0); + DBGH(stmt, "aREC@0x%p: `" LWPDL "` transfered @ " + "data_ptr@0x%p.", arec, LWSTR(src), dst); + } } } else { DBGH(stmt, "aREC@0x%p: NULL transfer buffer.", arec); @@ -834,11 +863,11 @@ static SQLRETURN transfer_wstr0(esodbc_rec_st *arec, esodbc_rec_st *irec, /* transfer to the application a 0-terminated (but unaccounted for) cstr_st */ static SQLRETURN transfer_cstr0(esodbc_rec_st *arec, esodbc_rec_st *irec, - cstr_st *src, void *c_dst, SQLLEN *len_dst) + cstr_st *src, void *data_ptr, SQLLEN *octet_len_ptr) { size_t in_bytes; esodbc_state_et state; - SQLCHAR *dst = (SQLCHAR *)c_dst; + SQLCHAR *dst = (SQLCHAR *)data_ptr; esodbc_stmt_st *stmt = arec->desc->hdr.stmt; /* the source string must be 0-term'd (since this needs to be transfered @@ -846,28 +875,30 @@ static SQLRETURN transfer_cstr0(esodbc_rec_st *arec, esodbc_rec_st *irec, assert(src->str[src->cnt] == 0); /* always return the app the untruncated number of bytes */ - write_out_octets(len_dst, src->cnt * sizeof(*src->str), - stmt->max_length, irec->meta_type); + write_out_octets(octet_len_ptr, src->cnt * sizeof(*src->str), irec); - if (c_dst) { - dst = (SQLCHAR *)c_dst; + if (data_ptr) { + dst = (SQLCHAR *)data_ptr; state = SQL_STATE_00000; in_bytes = buff_octet_size((src->cnt + 1) * sizeof(*src->str), - (size_t)arec->octet_length, stmt->max_length, - sizeof(*src->str), irec->meta_type, &state); - - memcpy(dst, src->str, in_bytes); - - if (state != SQL_STATE_00000) { - /* 0-term the buffer */ - dst[(in_bytes/sizeof(SQLCHAR)) - 1] = 0; - DBGH(stmt, "aREC@0x%p: `" LCPDL "` transfered truncated as `%s`.", - arec, LCSTR(src), dst); - RET_HDIAGS(stmt, state); - } else { - assert(dst[(in_bytes/sizeof(SQLCHAR)) - 1] == 0); - DBGH(stmt, "aREC@0x%p: `" LCPDL "` transfered @ data_ptr@0x%p.", - arec, LCSTR(src), dst); + sizeof(*src->str), arec, irec, &state); + + if (in_bytes) { + memcpy(dst, src->str, in_bytes); + /* TODO: should the left be filled with spaces? : + * https://docs.microsoft.com/en-us/sql/odbc/reference/appendixes/rules-for-conversions */ + + if (state != SQL_STATE_00000) { + /* 0-term the buffer */ + dst[(in_bytes/sizeof(SQLCHAR)) - 1] = 0; + DBGH(stmt, "aREC@0x%p: `" LCPDL "` transfered truncated as " + "`%s`.", arec, LCSTR(src), dst); + RET_HDIAGS(stmt, state); + } else { + assert(dst[(in_bytes/sizeof(SQLCHAR)) - 1] == 0); + DBGH(stmt, "aREC@0x%p: `" LCPDL "` transfered @ " + "data_ptr@0x%p.", arec, LCSTR(src), dst); + } } } else { DBGH(stmt, "aREC@0x%p: NULL transfer buffer.", arec); @@ -877,9 +908,9 @@ static SQLRETURN transfer_cstr0(esodbc_rec_st *arec, esodbc_rec_st *irec, } /* 10^n */ -static inline long long pow10(int n) +static inline unsigned long long pow10(int n) { - long long pow = 1; + unsigned long long pow = 1; pow <<= n; while (n--) { pow += pow << 2; @@ -908,6 +939,7 @@ static SQLRETURN double_to_numeric(esodbc_rec_st *arec, esodbc_rec_st *irec, for (prec = 0 ; ullng; prec ++) { ullng /= 10; } + DBGH(stmt, "arec@0x%p, arec->scale=%d", arec, arec->scale); if (arec->scale < 0) { llng = (long long)(src / pow10(arec->scale)); prec -= arec->scale; @@ -923,7 +955,7 @@ static SQLRETURN double_to_numeric(esodbc_rec_st *arec, esodbc_rec_st *irec, if ((0 < arec->precision && arec->precision < prec) || (UCHAR_MAX < prec)) { /* precision of source is higher than requested => overflow */ - ERRH(stmt, "conversion overflow. source: %lf; requested: " + ERRH(stmt, "conversion overflow. source: %f; requested: " "precisions: %d, scale: %d.", src, arec->precision, arec->scale); RET_HDIAGS(stmt, SQL_STATE_22003); } else if (prec < 0) { @@ -940,7 +972,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 %lf converted to numeric: .sign=%d, precision=%d " + DBGH(stmt, "double %.15f 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, @@ -956,11 +988,13 @@ static SQLRETURN llong_to_binary(esodbc_rec_st *arec, esodbc_rec_st *irec, { size_t cnt; char *s = (char *)&src; + esodbc_state_et state = SQL_STATE_00000; esodbc_stmt_st *stmt = arec->desc->hdr.stmt; unsigned long long ull = src < 0 ? -src : src; /* UJ4C uses long long for any integer type -> find out the - * smallest type that would accomodate the value */ + * smallest type that would accomodate the value (since fixed negatives + * would take more space then minimally required). */ if (ull < CHAR_MAX) { cnt = sizeof(char); } else if (ull < SHRT_MAX) { @@ -972,22 +1006,58 @@ static SQLRETURN llong_to_binary(esodbc_rec_st *arec, esodbc_rec_st *irec, } else { /* definetely ull < LLONG_MAX */ cnt = sizeof(long long); } - if (arec->octet_length < (SQLLEN)cnt) { - ERRH(stmt, "can't convert value %lld on %zd octets: out of range", - src, cnt); - RET_HDIAGS(stmt, SQL_STATE_22003); + + cnt = buff_octet_size(cnt, sizeof(*s), arec, irec, &state); + if (state) { /* has it been shrunk? */ + REJECT_AS_OOR(stmt, src, /*fixed?*/TRUE, "[BINARY]<[value]"); } - /* copy bytes as-are: the reverse conversion need to take place on same - * "DBMS and hardare platform". */ - memcpy(dst, s, cnt); - memset((char *)dst + cnt, 0, arec->octet_length - cnt); - write_out_octets(src_len, cnt, stmt->max_length, irec->meta_type); + if (dst) { + /* copy bytes as-are: the reverse conversion need to take place on + * "same DBMS and hardare platform". */ + memcpy(dst, s, cnt); + //TODO: should the driver clear all the received buffer?? Cfg option? + //memset((char *)dst + cnt, 0, arec->octet_length - cnt); + } + write_out_octets(src_len, cnt, irec); DBGH(stmt, "long long value %lld, converted on %zd octets.", src, cnt); 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) +{ + /* buffer is overprovisioned for !wide, but avoids double declaration */ + SQLCHAR buff[(ESODBC_PRECISION_INT64 + /*0-term*/1 + /*+/-*/1) + * sizeof(SQLWCHAR)]; + size_t cnt; + SQLRETURN ret; + esodbc_stmt_st *stmt = arec->desc->hdr.stmt; + + cnt = i64tot((int64_t)ll, buff, wide); + + if (wide) { + wstr_st llwstr = {.str = (SQLWCHAR *)buff, .cnt = cnt}; + ret = transfer_wstr0(arec, irec, &llwstr, data_ptr, octet_len_ptr); + DBGH(stmt, "long long %lld convertible to w-string `" LWPD "` on " + "%zd octets.", ll, (SQLWCHAR *)buff, cnt); + } else { + cstr_st llcstr = {.str = buff, .cnt = cnt}; + ret = transfer_cstr0(arec, irec, &llcstr, data_ptr, octet_len_ptr); + DBGH(stmt, "long long %lld convertible to string `" LCPD "` on " + "%zd octets.", ll, (SQLCHAR *)buff, cnt); + } + + /* need to change the error code from truncation to "out of + * range", since "whole digits" are truncated */ + if (ret == SQL_SUCCESS_WITH_INFO && + HDRH(stmt)->diag.state == SQL_STATE_01004) { + REJECT_AS_OOR(stmt, ll, /*fixed?*/TRUE, "[STRING]<[value]"); + } + return SQL_SUCCESS; +} + static SQLRETURN copy_longlong(esodbc_rec_st *arec, esodbc_rec_st *irec, SQLULEN pos, long long ll) { @@ -995,11 +1065,6 @@ static SQLRETURN copy_longlong(esodbc_rec_st *arec, esodbc_rec_st *irec, void *data_ptr; SQLLEN *octet_len_ptr; esodbc_desc_st *ard, *ird; - char buff[sizeof("18446744073709551616")]; /* = 1 << 8*8 */ - SQLWCHAR wbuff[sizeof("18446744073709551616")]; - wstr_st llwstr; - cstr_st llcstr; - esodbc_state_et state = SQL_STATE_00000; SQLRETURN ret; stmt = arec->desc->hdr.stmt; @@ -1011,137 +1076,71 @@ static SQLRETURN copy_longlong(esodbc_rec_st *arec, esodbc_rec_st *irec, /* pointer to app's buffer */ data_ptr = deferred_address(SQL_DESC_DATA_PTR, pos, arec); -#define REJECT_AS_OOR(_stmt, _ll, _type) /* Out Of Range */ \ - do { \ - ERRH(_stmt, "can't convert value %lld to a %s: out of range", \ - _ll, STR(_type)); \ - RET_HDIAGS(_stmt, SQL_STATE_22003); \ - } while (0) - /* assume a C type behind an SQL C type, but check representation */ -#define REJECT_IF_SIGN_OOR(_stmt, _ll, _min, _max, _sqlctype, _ctype) \ + /* 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) \ do { \ assert(sizeof(_sqlctype) == sizeof(_ctype)); \ - if (_ll < _min || _max < _ll) { \ - REJECT_AS_OOR(_stmt, _ll, _ctype); \ + if ((_min && _ll < _min) || _max < _ll) { \ + REJECT_AS_OOR(_stmt, _ll, /*fixed int*/TRUE, _ctype); \ } \ } while (0) -#define REJECT_IF_USIGN_OOR(_stmt, _ll, _max, _sqlctype, _ctype) \ + /* 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) \ do { \ - assert(sizeof(_sqlctype) == sizeof(_ctype)); \ - if (_max < _ll) { \ - REJECT_AS_OOR(_stmt, _ll, _ctype); \ - } \ + REJECT_IF_NULL_DEST_BUFF(stmt, data_ptr); \ + REJECT_IF_OOR(stmt, _ll, _min, _max, _sqlctype, _ctype); \ + *(_sqlctype *)data_ptr = (_sqlctype)_ll; \ + write_out_octets(octet_len_ptr, sizeof(_sqlctype), irec); \ + DBGH(stmt, "converted long long %lld to " STR(_sqlctype) " 0x%llx.", \ + _ll, (intptr_t)*(_sqlctype *)data_ptr); \ } while (0) switch (get_c_target_type(arec, irec)) { case SQL_C_CHAR: - _i64toa((int64_t)ll, buff, /*radix*/10); - /* TODO: find/write a function that returns len of conversion? */ - llcstr.cnt = strlen(buff); - llcstr.str = buff; - ret = transfer_cstr0(arec, irec, &llcstr, data_ptr, octet_len_ptr); - /* need to change the error code from truncation to "out of - * range", since "whole digits" are truncated */ - if (ret == SQL_SUCCESS_WITH_INFO && - HDRH(stmt)->diag.state == SQL_STATE_01004) { - ERRH(stmt, "can't convert %lld into a string of lenght %d.", - ll, arec->octet_length); - RET_HDIAGS(stmt, SQL_STATE_22003); - } - return ret; + return longlong_to_str(arec, irec, ll, data_ptr, octet_len_ptr, + FALSE); case SQL_C_WCHAR: - _i64tow((int64_t)ll, wbuff, /*radix*/10); - /* TODO: find/write a function that returns len of conversion? */ - llwstr.cnt = wcslen(wbuff); - llwstr.str = wbuff; - ret = transfer_wstr0(arec, irec, &llwstr, data_ptr, octet_len_ptr); - /* need to change the error code from truncation to "out of - * range", since "whole digits" are truncated */ - if (ret == SQL_SUCCESS_WITH_INFO && - HDRH(stmt)->diag.state == SQL_STATE_01004) { - ERRH(stmt, "can't convert %lld into a string of lenght %d.", - ll, arec->octet_length); - RET_HDIAGS(stmt, SQL_STATE_22003); - } - return ret; + return longlong_to_str(arec, irec, ll, data_ptr, octet_len_ptr, + TRUE); case SQL_C_TINYINT: case SQL_C_STINYINT: - REJECT_IF_NULL_DEST_BUFF(stmt, data_ptr); - REJECT_IF_SIGN_OOR(stmt, ll, CHAR_MIN, CHAR_MAX, SQLSCHAR, char); - *(SQLSCHAR *)data_ptr = (SQLSCHAR)ll; - write_out_octets(octet_len_ptr, sizeof(SQLSCHAR), - stmt->max_length, irec->meta_type); + TRANSFER_LL(ll, CHAR_MIN, CHAR_MAX, SQLSCHAR, char); break; case SQL_C_UTINYINT: - REJECT_IF_NULL_DEST_BUFF(stmt, data_ptr); - REJECT_IF_USIGN_OOR(stmt, ll, UCHAR_MAX, SQLCHAR, unsigned char); - *(SQLCHAR *)data_ptr = (SQLCHAR)ll; - write_out_octets(octet_len_ptr, sizeof(SQLCHAR), - stmt->max_length, irec->meta_type); + TRANSFER_LL(ll, 0, UCHAR_MAX, SQLCHAR, unsigned char); break; - case SQL_C_SHORT: case SQL_C_SSHORT: - REJECT_IF_NULL_DEST_BUFF(stmt, data_ptr); - REJECT_IF_SIGN_OOR(stmt, ll, SHRT_MIN, SHRT_MAX, - SQLSMALLINT, short); - *(SQLSMALLINT *)data_ptr = (SQLSMALLINT)ll; - write_out_octets(octet_len_ptr, sizeof(SQLSMALLINT), - stmt->max_length, irec->meta_type); + TRANSFER_LL(ll, SHRT_MIN, SHRT_MAX, SQLSMALLINT, short); break; case SQL_C_USHORT: - REJECT_IF_NULL_DEST_BUFF(stmt, data_ptr); - REJECT_IF_USIGN_OOR(stmt, ll, USHRT_MAX, - SQLUSMALLINT, unsigned short); - *(SQLUSMALLINT *)data_ptr = (SQLUSMALLINT)ll; - write_out_octets(octet_len_ptr, sizeof(SQLUSMALLINT), - stmt->max_length, irec->meta_type); + TRANSFER_LL(ll, 0, USHRT_MAX, SQLUSMALLINT, unsigned short); break; - case SQL_C_LONG: case SQL_C_SLONG: - REJECT_IF_NULL_DEST_BUFF(stmt, data_ptr); - REJECT_IF_SIGN_OOR(stmt, ll, LONG_MIN, LONG_MAX, SQLINTEGER, long); - *(SQLINTEGER *)data_ptr = (SQLINTEGER)ll; - write_out_octets(octet_len_ptr, sizeof(SQLINTEGER), - stmt->max_length, irec->meta_type); + TRANSFER_LL(ll, LONG_MIN, LONG_MAX, SQLINTEGER, long); break; case SQL_C_ULONG: - REJECT_IF_NULL_DEST_BUFF(stmt, data_ptr); - REJECT_IF_USIGN_OOR(stmt, ll, ULONG_MAX, - SQLUINTEGER, unsigned long); - *(SQLUINTEGER *)data_ptr = (SQLUINTEGER)ll; - write_out_octets(octet_len_ptr, sizeof(SQLUINTEGER), - stmt->max_length, irec->meta_type); + TRANSFER_LL(ll, 0, ULONG_MAX, SQLUINTEGER, unsigned long); break; - case SQL_C_SBIGINT: - REJECT_IF_NULL_DEST_BUFF(stmt, data_ptr); - REJECT_IF_SIGN_OOR(stmt, ll, LLONG_MIN, LLONG_MAX, - SQLBIGINT, long long); - *(SQLBIGINT *)data_ptr = (SQLBIGINT)ll; - write_out_octets(octet_len_ptr, sizeof(SQLBIGINT), - stmt->max_length, irec->meta_type); + TRANSFER_LL(ll, LLONG_MIN, LLONG_MAX, SQLBIGINT, long long); break; case SQL_C_UBIGINT: - REJECT_IF_NULL_DEST_BUFF(stmt, data_ptr); - REJECT_IF_USIGN_OOR(stmt, ll, ULLONG_MAX, - SQLUBIGINT, unsigned long long); - *(SQLUBIGINT *)data_ptr = (SQLUBIGINT)ll; - write_out_octets(octet_len_ptr, sizeof(SQLUBIGINT), - stmt->max_length, irec->meta_type); + TRANSFER_LL(ll, 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) { - REJECT_AS_OOR(stmt, ll, SQL_C_BIT); + REJECT_AS_OOR(stmt, ll, /*fixed int*/TRUE, SQL_C_BIT); } else { /* 0 or 1 */ *(SQLCHAR *)data_ptr = (SQLCHAR)ll; } - write_out_octets(octet_len_ptr, sizeof(SQLSCHAR), - stmt->max_length, irec->meta_type); + write_out_octets(octet_len_ptr, sizeof(SQLSCHAR), irec); break; case SQL_C_NUMERIC: @@ -1150,24 +1149,21 @@ static SQLRETURN copy_longlong(esodbc_rec_st *arec, esodbc_rec_st *irec, if (! SQL_SUCCEEDED(ret)) { return ret; } - write_out_octets(octet_len_ptr, sizeof(SQL_NUMERIC_STRUCT), - stmt->max_length, irec->meta_type); + write_out_octets(octet_len_ptr, sizeof(SQL_NUMERIC_STRUCT), irec); break; case SQL_C_FLOAT: REJECT_IF_NULL_DEST_BUFF(stmt, data_ptr); - REJECT_IF_SIGN_OOR(stmt, ll, -FLT_MAX, FLT_MAX, SQLREAL, float); + REJECT_IF_OOR(stmt, ll, -FLT_MAX, FLT_MAX, SQLREAL, float); *(SQLREAL *)data_ptr = (SQLREAL)ll; - write_out_octets(octet_len_ptr, sizeof(SQLREAL), - stmt->max_length, irec->meta_type); + write_out_octets(octet_len_ptr, sizeof(SQLREAL), irec); break; case SQL_C_DOUBLE: REJECT_IF_NULL_DEST_BUFF(stmt, data_ptr); - REJECT_IF_SIGN_OOR(stmt, ll, -DBL_MAX, DBL_MAX, SQLDOUBLE, double); + REJECT_IF_OOR(stmt, ll, -DBL_MAX, DBL_MAX, SQLDOUBLE, double); *(SQLDOUBLE *)data_ptr = (SQLDOUBLE)ll; - write_out_octets(octet_len_ptr, sizeof(SQLDOUBLE), - stmt->max_length, irec->meta_type); + write_out_octets(octet_len_ptr, sizeof(SQLDOUBLE), irec); break; case SQL_C_BINARY: @@ -1178,20 +1174,278 @@ static SQLRETURN copy_longlong(esodbc_rec_st *arec, esodbc_rec_st *irec, get_c_target_type(arec, irec)); return SQL_ERROR; } - DBGH(stmt, "REC@0x%p, data_ptr@0x%p, copied long long: `%d`.", arec, - data_ptr, (SQLINTEGER)ll); + DBGH(stmt, "REC@0x%p, data_ptr@0x%p, copied long long: %lld.", arec, + data_ptr, ll); return SQL_SUCCESS; -#undef REJECT_AS_OOR -#undef REJECT_IF_OOR +# undef REJECT_IF_OOR +# undef TRANSFER_LL +} + +static SQLRETURN double_to_bit(esodbc_rec_st *arec, esodbc_rec_st *irec, + double src, void *data_ptr, SQLLEN *octet_len_ptr) +{ + esodbc_state_et state = SQL_STATE_00000; + esodbc_stmt_st *stmt = arec->desc->hdr.stmt; + + REJECT_IF_NULL_DEST_BUFF(stmt, data_ptr); + + write_out_octets(octet_len_ptr, sizeof(SQLCHAR), irec); + + if (src < 0 || 2 <= src) { + REJECT_AS_OOR(stmt, src, /*fixed?*/FALSE, SQL_C_BIT); + } else if (0 < src && src < 1) { + *(SQLCHAR *)data_ptr = 0; + state = SQL_STATE_01S07; + } else if (1 < src && src < 2) { + *(SQLCHAR *)data_ptr = 1; + state = SQL_STATE_01S07; + } else { /* 0 or 1 */ + *(SQLCHAR *)data_ptr = (SQLCHAR)src; + } + if (state != SQL_STATE_00000) { + INFOH(stmt, "truncating when converting %f as %d.", src, + *(SQLCHAR *)data_ptr); + RET_HDIAGS(stmt, state); + } + + DBGH(stmt, "double %f converted to bit %d.", src, *(SQLCHAR *)data_ptr); + + return SQL_SUCCESS; +} + +static SQLRETURN double_to_binary(esodbc_rec_st *arec, esodbc_rec_st *irec, + double dbl, void *data_ptr, SQLLEN *octet_len_ptr) +{ + size_t cnt; + double udbl = dbl < 0 ? -dbl : dbl; + float flt; + char *ptr; + esodbc_state_et state = SQL_STATE_00000; + esodbc_stmt_st *stmt = arec->desc->hdr.stmt; + + if (udbl < FLT_MIN || FLT_MAX < udbl) { + /* value's precision/scale requires a double */ + cnt = sizeof(dbl); + ptr = (char *)&dbl; + } else { + flt = (float)dbl; + cnt = sizeof(flt); + ptr = (char *)&flt; + } + + cnt = buff_octet_size(cnt, sizeof(*ptr), arec, irec, &state); + if (state) { + REJECT_AS_OOR(stmt, dbl, /*fixed?*/FALSE, "[BINARY]<[floating]"); + } + write_out_octets(octet_len_ptr, cnt, irec); + if (data_ptr) { + memcpy(data_ptr, ptr, cnt); + //TODO: should the driver clear all the received buffer?? Cfg option? + //memset((char *)data_ptr + cnt, 0, arec->octet_length - cnt); + } + + DBGH(stmt, "converted double %f to binary on %zd octets.", dbl, cnt); + + return SQL_SUCCESS; +} + +static SQLRETURN double_to_str(esodbc_rec_st *arec, esodbc_rec_st *irec, + double dbl, void *data_ptr, SQLLEN *octet_len_ptr, BOOL wide) +{ + long long whole; + unsigned long long fraction; + double rest; + SQLSMALLINT scale; + size_t pos, octets; + /* buffer is overprovisioned for !wide, but avoids double declaration */ + SQLCHAR buff[(2 * ESODBC_PRECISION_INT64 + /*.*/1 + /*\0*/1) + * sizeof(SQLWCHAR)]; + /* buffer unit size */ + size_t usize = wide ? sizeof(SQLWCHAR) : sizeof(SQLCHAR); + esodbc_state_et state = SQL_STATE_00000; + esodbc_stmt_st *stmt = arec->desc->hdr.stmt; + + /* + * split the whole and fractional parts + */ + assert(sizeof(dbl) == sizeof(whole)); /* [double]==[long long] */ + whole = (long long)dbl; + rest = dbl - whole; + + /* retain user defined or data source default number of fraction digits */ + scale = 0 < arec->scale ? arec->scale : irec->es_type->maximum_scale; + rest *= pow10(scale); + rest = round(rest); + fraction = rest < 0 ? (unsigned long long) -rest + : (unsigned long long)rest; + + /* copy integer part into work buffer */ + pos = i64tot((int64_t)whole, buff, wide); + /* would writing just the whole part + \0 fit into the buffer? */ + octets = buff_octet_size((pos + 1) * usize, usize, arec, irec, &state); + if (state) { + REJECT_AS_OOR(stmt, dbl, /*fixed?*/FALSE, "[STRING]<[floating.whole]"); + } else { + assert(octets == (pos + 1) * usize); + } + + if (wide) { + ((SQLWCHAR *)buff)[pos ++] = L'.'; + } else { + ((SQLCHAR *)buff)[pos ++] = '.'; + } + + /* copy fractional part into work buffer */ + pos += ui64tot((uint64_t)fraction, (char *)buff + pos * usize, wide); + + /* write how many bytes (w/o \0) we'd write if buffer is large enough */ + write_out_octets(octet_len_ptr, pos * usize, irec); + /* compute how many bytes we can actually transfer, including \0 */ + octets = buff_octet_size((pos + 1) * usize, usize, arec, irec, &state); + + if (data_ptr) { + /* transfer the bytes out */ + memcpy(data_ptr, buff, octets); + if (state) { + /* usize < octets, since user input is checked above for OOR */ + if (wide) { + ((SQLWCHAR *)data_ptr)[octets/usize - 1] = L'\0'; + } else { + ((SQLCHAR *)data_ptr)[octets/usize - 1] = '\0'; + } + } + } + + if (wide) { /* 15-16 decimals precision for x64 double (TODO: 32b) */ + DBGH(stmt, "double %.16f 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 " + "octets (state: %d; scale: %d).", dbl, buff, octets, state, scale); + } + + if (state) { + RET_HDIAGS(stmt, state); + } else { + return SQL_SUCCESS; + } } static SQLRETURN copy_double(esodbc_rec_st *arec, esodbc_rec_st *irec, SQLULEN pos, double dbl) { - FIXME; // FIXME - return SQL_ERROR; + esodbc_stmt_st *stmt; + void *data_ptr; + SQLLEN *octet_len_ptr; + esodbc_desc_st *ard, *ird; + SQLRETURN ret; + double udbl; + + stmt = arec->desc->hdr.stmt; + ird = stmt->ird; + ard = stmt->ard; + + /* 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); + + /* Transfer a double to an SQL integer type. + * Uses local vars: stmt, data_ptr, irec, octet_len_ptr. + * Returns - RET_ - 01S07 on success (due to truncation of fractionals). */ +# define RET_TRANSFER_DBL(_dbl, _min, _max, _sqlctype, _ctype) \ + do { \ + /* using C type limits, so check C and SQL C type precision */ \ + assert(sizeof(_sqlctype) == sizeof(_ctype)); \ + if (_dbl) { \ + if ((_sqlctype)_dbl < _min || _max < (_sqlctype)_dbl) { \ + REJECT_AS_OOR(stmt, _dbl, /*fixed?*/FALSE, _sqlctype); \ + } \ + } else { \ + double __udbl = dbl < 0 ? -dbl : dbl; \ + if (_max < (_sqlctype)__udbl) { \ + REJECT_AS_OOR(stmt, _dbl, /*fixed?*/FALSE, _sqlctype); \ + } \ + } \ + *(_sqlctype *)data_ptr = (_sqlctype)_dbl; \ + write_out_octets(octet_len_ptr, sizeof(_sqlctype), irec); \ + DBGH(stmt, "converted double %f to " STR(_sqlctype) " 0x%llx.", _dbl, \ + (intptr_t)*(_sqlctype *)data_ptr); \ + RET_HDIAGS(stmt, SQL_STATE_01S07); \ + } while (0) + + switch (get_c_target_type(arec, irec)) { + case SQL_C_CHAR: + return double_to_str(arec, irec, dbl, data_ptr, octet_len_ptr, + FALSE); + case SQL_C_WCHAR: + return double_to_str(arec, irec, dbl, data_ptr, octet_len_ptr, + TRUE); + + case SQL_C_TINYINT: + case SQL_C_STINYINT: + RET_TRANSFER_DBL(dbl, CHAR_MIN, CHAR_MAX, SQLSCHAR, char); + case SQL_C_UTINYINT: + RET_TRANSFER_DBL(dbl, 0, UCHAR_MAX, SQLCHAR, unsigned char); + case SQL_C_SBIGINT: + RET_TRANSFER_DBL(dbl, LLONG_MIN, LLONG_MAX, SQLBIGINT, long long); + case SQL_C_UBIGINT: + RET_TRANSFER_DBL(dbl, 0, LLONG_MAX, SQLUBIGINT, long long); + case SQL_C_SHORT: + case SQL_C_SSHORT: + RET_TRANSFER_DBL(dbl, SHRT_MIN, SHRT_MAX, SQLSMALLINT, short); + case SQL_C_USHORT: + RET_TRANSFER_DBL(dbl, 0, USHRT_MAX, SQLUSMALLINT, unsigned short); + case SQL_C_LONG: + case SQL_C_SLONG: + RET_TRANSFER_DBL(dbl, LONG_MIN, LONG_MAX, SQLINTEGER, long); + case SQL_C_ULONG: + RET_TRANSFER_DBL(dbl, 0, ULONG_MAX, SQLINTEGER, unsigned long); + + case SQL_C_NUMERIC: + REJECT_IF_NULL_DEST_BUFF(stmt, data_ptr); + ret = double_to_numeric(arec, irec, dbl, data_ptr); + if (! SQL_SUCCEEDED(ret)) { + return ret; + } + write_out_octets(octet_len_ptr, sizeof(SQL_NUMERIC_STRUCT), irec); + break; + + case SQL_C_FLOAT: + REJECT_IF_NULL_DEST_BUFF(stmt, data_ptr); + udbl = dbl < 0 ? -dbl : dbl; + if (udbl < FLT_MIN || FLT_MAX < udbl) { + REJECT_AS_OOR(stmt, dbl, /* is fixed */FALSE, SQLREAL); + } + *(SQLREAL *)data_ptr = (SQLREAL)dbl; + write_out_octets(octet_len_ptr, sizeof(SQLREAL), irec); + break; + case SQL_C_DOUBLE: + REJECT_IF_NULL_DEST_BUFF(stmt, data_ptr); + *(SQLDOUBLE *)data_ptr = dbl; + write_out_octets(octet_len_ptr, sizeof(SQLDOUBLE), irec); + break; + + case SQL_C_BIT: + return double_to_bit(arec, irec, dbl, data_ptr, octet_len_ptr); + + case SQL_C_BINARY: + return double_to_binary(arec, irec, dbl, data_ptr, octet_len_ptr); + + default: + BUGH(stmt, "unexpected unhanlded data type: %d.", + get_c_target_type(arec, irec)); + return SQL_ERROR; + } + + DBGH(stmt, "REC@0x%p, data_ptr@0x%p, copied double: %f.", arec, + data_ptr, dbl); + + return SQL_SUCCESS; + +# undef RET_TRANSFER_DBL } static SQLRETURN copy_boolean(esodbc_rec_st *arec, esodbc_rec_st *irec, @@ -1205,13 +1459,6 @@ static SQLRETURN copy_boolean(esodbc_rec_st *arec, esodbc_rec_st *irec, stmt = arec->desc->hdr.stmt; - /* the check is in place just to return the right error code (according to - * conversion rules); a value of 0 is safe for the driver. */ - if (arec->octet_length < 1) { - ERRH(stmt, "output buffer less than one byte, can't convert"); - RET_HDIAGS(stmt, SQL_STATE_22003); - } - /* pointer where to write how many bytes we will/would use */ octet_len_ptr = deferred_address(SQL_DESC_OCTET_LENGTH_PTR, pos, arec); /* pointer to app's buffer */ @@ -1219,13 +1466,19 @@ static SQLRETURN copy_boolean(esodbc_rec_st *arec, esodbc_rec_st *irec, switch (get_c_target_type(arec, irec)) { case SQL_C_WCHAR: + if (arec->octet_length < 1) { /* can't inquiry needed buffer len */ + REJECT_AS_OOR(stmt, boolval, /*fixed int*/TRUE, NULL WCHAR); + } wbool = boolval ? MK_WSTR("true") : MK_WSTR("false"); return transfer_wstr0(arec, irec, &wbool, data_ptr, octet_len_ptr); case SQL_C_CHAR: + if (arec->octet_length < 1) { /* can't inquiry needed buffer len */ + REJECT_AS_OOR(stmt, boolval, /*fixed int*/TRUE, NULL CHAR); + } cbool = boolval ? MK_CSTR("true") : MK_CSTR("false"); return transfer_cstr0(arec, irec, &cbool, data_ptr, octet_len_ptr); default: - return copy_longlong(arec, irec, pos, boolval ? 1L : 0L); + return copy_longlong(arec, irec, pos, boolval ? 1LL : 0LL); } DBGH(stmt, "REC@0x%p, data_ptr@0x%p, copied boolean: `%d`.", arec, @@ -1250,11 +1503,8 @@ static SQLRETURN wstr_to_cstr(esodbc_rec_st *arec, esodbc_rec_st *irec, if (data_ptr) { charp = (char *)data_ptr; - /* type is signed, driver should not allow a negative to this point. */ - assert(0 <= arec->octet_length); in_bytes = (int)buff_octet_size(chars_0 * sizeof(*wstr), - (size_t)arec->octet_length, stmt->max_length, sizeof(*charp), - irec->meta_type, &state); + sizeof(*charp), arec, irec, &state); /* trim the original string until it fits in output buffer, with given * length limitation */ for (c = (int)chars_0; 0 < c; c --) { @@ -1272,6 +1522,10 @@ static SQLRETURN wstr_to_cstr(esodbc_rec_st *arec, esodbc_rec_st *irec, } } + /* if 0's present => 0 < out_bytes */ + assert(wstr[chars_0 - 1] == L'\0'); + assert(0 < out_bytes); + /* is user gives 0 as buffer size, out_bytes will also be 0 */ if (charp[out_bytes - 1]) { /* ran out of buffer => not 0-terminated and truncated already */ charp[out_bytes - 1] = 0; @@ -1297,8 +1551,7 @@ static SQLRETURN wstr_to_cstr(esodbc_rec_st *arec, esodbc_rec_st *irec, * indicating the length to the application */ out_bytes --; } - write_out_octets(octet_len_ptr, out_bytes, stmt->max_length, - irec->meta_type); + write_out_octets(octet_len_ptr, out_bytes, irec); } else { DBGH(stmt, "REC@0x%p, NULL octet_len_ptr.", arec); } diff --git a/driver/util.c b/driver/util.c index 4a10c75d..fe6fbe64 100644 --- a/driver/util.c +++ b/driver/util.c @@ -64,6 +64,29 @@ BOOL wstr2long(wstr_st *val, long *out) return TRUE; } +size_t i64tot(int64_t i64, void *buff, BOOL wide) +{ + if (wide) { + _i64tow((int64_t)i64, buff, /*radix*/10); + /* TODO: find/write a function that returns len of conversion? */ + return wcslen(buff); + } else { + _i64toa((int64_t)i64, buff, /*radix*/10); + return strlen(buff); + } +} + +size_t ui64tot(uint64_t ui64, void *buff, BOOL wide) +{ + if (wide) { + _ui64tow((uint64_t)ui64, buff, /*radix*/10); + return wcslen(buff); + } else { + _ui64toa((uint64_t)ui64, buff, /*radix*/10); + return strlen(buff); + } +} + /* * Trims leading and trailing WS of a wide string of 'chars' lenght. * 0-terminator should not be counted (as it's a non-WS). diff --git a/driver/util.h b/driver/util.h index 8ecffbd5..7de653e8 100644 --- a/driver/util.h +++ b/driver/util.h @@ -132,6 +132,11 @@ typedef struct wstr { BOOL wstr2bool(wstr_st *val); BOOL wstr2long(wstr_st *val, 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); +size_t ui64tot(uint64_t ui64, void *buff, BOOL wide); + + #ifdef _WIN32 /* * "[D]oes not null-terminate an output string if the input string length is diff --git a/test/connected_dbc.cc b/test/connected_dbc.cc index a51d64e5..80fb4541 100644 --- a/test/connected_dbc.cc +++ b/test/connected_dbc.cc @@ -138,9 +138,13 @@ void ConnectedDBC::assertState(const SQLWCHAR *state) { ret = SQLGetDiagField(SQL_HANDLE_STMT, stmt, 1, SQL_DIAG_SQLSTATE, buff, (SQL_SQLSTATE_SIZE + 1) * sizeof(buff[0]), &len); - ASSERT_TRUE(SQL_SUCCEEDED(ret)); - ASSERT_EQ(len, SQL_SQLSTATE_SIZE * sizeof(buff[0])); - ASSERT_STREQ(buff, state); + if (state) { + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + ASSERT_EQ(len, SQL_SQLSTATE_SIZE * sizeof(buff[0])); + ASSERT_STREQ(buff, state); + } else { + ASSERT_EQ(ret, SQL_NO_DATA); + } } diff --git a/test/test_conversion_sql2c_floats.cc b/test/test_conversion_sql2c_floats.cc new file mode 100644 index 00000000..6a26e9bb --- /dev/null +++ b/test/test_conversion_sql2c_floats.cc @@ -0,0 +1,554 @@ +/* + * 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 +#include + +/* placeholders; will be undef'd and redef'd */ +#define SQL_SCALE +#define SQL_RAW +#define SQL_VAL +#define SQL /* attached for troubleshooting purposes */ + +namespace test { + +class ConvertSQL2C_Floats : public ::testing::Test, public ConnectedDBC { +}; + + +TEST_F(ConvertSQL2C_Floats, ScaledFloat2Char_scale_default) { + +#undef SQL_VAL +#undef SQL +#define SQL_VAL "0.98765432100123456789" //20 fractional digits +#define SQL "CAST(" SQL_VAL " AS SCALED_FLOAT)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"scaled_float\"}\ + ],\ + \"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 , /*0.*/2 + /* max ES/SQL double scale */19); + //std::cerr << buff << std::endl; + EXPECT_EQ(memcmp(buff, SQL_VAL, /*0.*/2+/*x64 dbl precision*/15), 0); +} + + +TEST_F(ConvertSQL2C_Floats, Float2Char_scale_default) { + +#undef SQL_VAL +#undef SQL +#define SQL_VAL "0.98765432109876543219" //20 fractional digits +#define SQL "CAST(" SQL_VAL " AS DOUBLE)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"float\"}\ + ],\ + \"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 , /*0.*/2 + /* max ES/SQL double scale */7); +} + + +TEST_F(ConvertSQL2C_Floats, Float2Char_scale_1) { + +#undef SQL_SCALE +#undef SQL_VAL +#undef SQL +#define SQL_SCALE 1 +#define SQL_VAL "0.9" +#define SQL "CAST(" SQL_VAL " AS DOUBLE)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"float\"}\ + ],\ + \"rows\": [\ + [" SQL_VAL "]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + // set scale + SQLHDESC ard; + ret = SQLGetStmtAttr(stmt, SQL_ATTR_APP_ROW_DESC, &ard, 0, NULL); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + ret = SQLSetDescField(ard, /*col#*/1, SQL_DESC_SCALE, (SQLPOINTER)SQL_SCALE, + 0); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + 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) - 1); + EXPECT_STREQ((char/*4gtest*/*)buff, SQL_VAL); +} + + +TEST_F(ConvertSQL2C_Floats, Float2WChar) { + +#undef SQL_SCALE +#undef SQL_VAL +#undef SQL +#define SQL_SCALE 3 +#define SQL_VAL "-128.998" +#define SQL "CAST(" SQL_VAL " AS FLOAT)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"float\"}\ + ],\ + \"rows\": [\ + [" SQL_VAL "]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + // set scale + SQLHDESC ard; + ret = SQLGetStmtAttr(stmt, SQL_ATTR_APP_ROW_DESC, &ard, 0, NULL); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + ret = SQLSetDescField(ard, /*col#*/1, SQL_DESC_SCALE, (SQLPOINTER)SQL_SCALE, + 0); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + 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(*wbuff), sizeof(SQL_VAL) - /*\0*/1); + EXPECT_STREQ((wchar_t/*4gtest*/*)wbuff, MK_WPTR(SQL_VAL)); +} + + +TEST_F(ConvertSQL2C_Floats, Float2Char) { + +#undef SQL_VAL +#undef SQL +#define SQL_VAL "-128.998" +#define SQL "CAST(" SQL_VAL " AS FLOAT)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"float\"}\ + ],\ + \"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)); + + // GE here, since the default scale is high, so more digits would be avail + EXPECT_GE(ind_len / sizeof(*buff), sizeof(SQL_VAL) - /*\0*/1); + double sql_val = atof((char *)SQL_VAL); + double buff2dbl = atof((char *)buff); + EXPECT_LE(fabs(sql_val - buff2dbl), .001); +} + + +TEST_F(ConvertSQL2C_Floats, Float2WChar_dotzero) { + +#undef SQL_VAL +#undef SQL +#define SQL_VAL "0.0" +#define SQL "CAST(" SQL_VAL " AS FLOAT)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"float\"}\ + ],\ + \"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(*wbuff), sizeof(SQL_VAL) - /*\0*/1); + EXPECT_STREQ((wchar_t/*4gtest*/*)wbuff, MK_WPTR(SQL_VAL)); +} + + +TEST_F(ConvertSQL2C_Floats, Float2TinyInt) { + +#undef SQL_RAW +#undef SQL_VAL +#undef SQL +#define SQL_RAW -128.998 +#define SQL_VAL STR(SQL_RAW) +#define SQL "CAST(" SQL_VAL " AS FLOAT)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"float\"}\ + ],\ + \"rows\": [\ + [" SQL_VAL "]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + SQLSCHAR val; + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_TINYINT, &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((SQLSCHAR)SQL_RAW, val); +} + + +TEST_F(ConvertSQL2C_Floats, Float2UShort) { + +#undef SQL_RAW +#undef SQL_VAL +#undef SQL +#define SQL_RAW 65535.0 // USHRT_MAX .0 +#define SQL_VAL STR(SQL_RAW) +#define SQL "CAST(" SQL_VAL " AS FLOAT)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"float\"}\ + ],\ + \"rows\": [\ + [" SQL_VAL "]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + SQLUSMALLINT val; + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_USHORT, &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((SQLUSMALLINT)SQL_RAW, val); +} + + +TEST_F(ConvertSQL2C_Floats, Float2Long) { + +#undef SQL_RAW +#undef SQL_VAL +#undef SQL +#define SQL_RAW -2147483648.99 // INT32_MIN .99 +#define SQL_VAL STR(SQL_RAW) +#define SQL "CAST(" SQL_VAL " AS FLOAT)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"float\"}\ + ],\ + \"rows\": [\ + [" SQL_VAL "]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + SQLINTEGER val; + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_LONG, &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((SQLINTEGER)SQL_RAW, val); +} + + +TEST_F(ConvertSQL2C_Floats, Double2Float) { + +#undef SQL_RAW +#undef SQL_VAL +#undef SQL +#define SQL_RAW -2147483648.99 // INT32_MIN .99 +#define SQL_VAL STR(SQL_RAW) +#define SQL "CAST(" SQL_VAL " AS DOUBLE)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"double\"}\ + ],\ + \"rows\": [\ + [" SQL_VAL "]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + SQLREAL val; + 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(SQL_RAW, val); +} + + +TEST_F(ConvertSQL2C_Floats, HalfFloat2Bit_fail_22003) { + +#undef SQL_RAW +#undef SQL_VAL +#undef SQL +#define SQL_RAW -.1 +#define SQL_VAL STR(SQL_RAW) +#define SQL "CAST(" SQL_VAL " AS DOUBLE)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"half_float\"}\ + ],\ + \"rows\": [\ + [" SQL_VAL "]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + SQLCHAR val; + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_BIT, &val, sizeof(val), &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_FALSE(SQL_SUCCEEDED(ret)); + assertState(L"22003"); +} + + +TEST_F(ConvertSQL2C_Floats, HalfFloat2Bit_truncate_01S07) { + +#undef SQL_RAW +#undef SQL_VAL +#undef SQL +#define SQL_RAW 1.1 +#define SQL_VAL STR(SQL_RAW) +#define SQL "CAST(" SQL_VAL " AS DOUBLE)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"half_float\"}\ + ],\ + \"rows\": [\ + [" SQL_VAL "]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + SQLCHAR val; + 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"); + ASSERT_EQ(val, 1); +} + + +TEST_F(ConvertSQL2C_Floats, HalfFloat2Bit) { + +#undef SQL_RAW +#undef SQL_VAL +#undef SQL +#define SQL_RAW 1.0 +#define SQL_VAL STR(SQL_RAW) +#define SQL "CAST(" SQL_VAL " AS DOUBLE)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"half_float\"}\ + ],\ + \"rows\": [\ + [" SQL_VAL "]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + SQLCHAR val; + 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(NULL); + ASSERT_EQ(val, 1); +} + + +TEST_F(ConvertSQL2C_Floats, Double2Numeric) { + +#undef SQL_SCALE +#undef SQL_RAW +#undef SQL_VAL +#undef SQL +#define SQL_SCALE 3 +#define SQL_PREC 5 +#define SQL_RAW 25.212 +#define SQL_VAL STR(SQL_RAW) +#define SQL "CAST(" SQL_VAL " AS DOUBLE)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"double\"}\ + ],\ + \"rows\": [\ + [" SQL_VAL "]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + // + //set scale + // + SQLHDESC ard; + ret = SQLGetStmtAttr(stmt, SQL_ATTR_APP_ROW_DESC, &ard, 0, NULL); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLSetDescField(ard, 1, SQL_DESC_TYPE, (SQLPOINTER)SQL_C_NUMERIC, 0); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + ret = SQLSetDescField(ard, 1, SQL_DESC_PRECISION, (SQLPOINTER)SQL_PREC, 0); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + ret = SQLSetDescField(ard, 1, SQL_DESC_SCALE, (SQLPOINTER)SQL_SCALE, 0); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + ret = SQLSetDescField(ard, /*col#*/1, SQL_DESC_OCTET_LENGTH_PTR, + (SQLPOINTER)&ind_len, 0); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + SQL_NUMERIC_STRUCT val; + ret = SQLSetDescField(ard, 1, SQL_DESC_DATA_PTR, (SQLPOINTER) &val, 0); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + EXPECT_EQ(ind_len, sizeof(val)); + EXPECT_EQ(val.sign, 1); + EXPECT_EQ(val.scale, SQL_SCALE); + EXPECT_EQ(val.precision, SQL_PREC); + EXPECT_EQ(memcmp(val.val, "|b", 2), 0); +} + + +TEST_F(ConvertSQL2C_Floats, Double2Binary) { + +#undef SQL_RAW +#undef SQL_VAL +#undef SQL +#define SQL_RAW -123.001 +#define SQL_VAL STR(SQL_RAW) +#define SQL "CAST(" SQL_VAL " AS DOUBLE)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"double\"}\ + ],\ + \"rows\": [\ + [" SQL_VAL "]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + SQLDOUBLE val = 0; + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_DOUBLE, &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 namespace + diff --git a/test/test_conversion_sql2c_ints.cc b/test/test_conversion_sql2c_ints.cc index 0e56acfe..47567fb3 100644 --- a/test/test_conversion_sql2c_ints.cc +++ b/test/test_conversion_sql2c_ints.cc @@ -139,6 +139,36 @@ TEST_F(ConvertSQL2C_Ints, Long2Char_truncate_22003) { } +TEST_F(ConvertSQL2C_Ints, Long2Char_zero_copy) { + +#undef SQL_VAL +#undef SQL +#define SQL_VAL "12345678" +#define SQL "CAST(" SQL_VAL " AS TEXT)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"integer\"}\ + ],\ + \"rows\": [\ + [" SQL_VAL "]\ + ]\ +}\ +"; + prepareStatement(json_answer); + + SQLCHAR buff[sizeof(SQL_VAL)] = {0}; + 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) - 1); + EXPECT_EQ(buff[0], 0); /* nothing copied, since 0 buff size indicated */ +} + + TEST_F(ConvertSQL2C_Ints, Short2Byte) { #undef SQL_VAL @@ -587,9 +617,43 @@ TEST_F(ConvertSQL2C_Ints, Long2Binary) { 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_Ints, Long2Binary_fail_22003) { + +#undef SQL_RAW +#undef SQL_VAL +#undef SQL +#define SQL_RAW 0xBeefed +#define SQL_VAL "12513261" // 0xbeefed +#define SQL "CAST(" SQL_VAL " AS INTEGER)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"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 937c4a7a0729da65f120e73aed0d1c26ad555262 Mon Sep 17 00:00:00 2001 From: Bogdan Pintea Date: Mon, 18 Jun 2018 18:01:40 +0200 Subject: [PATCH 10/15] b/f: remove debug code no longer needed --- driver/connect.c | 9 --------- 1 file changed, 9 deletions(-) diff --git a/driver/connect.c b/driver/connect.c index 138b0c79..fe3a9133 100644 --- a/driver/connect.c +++ b/driver/connect.c @@ -1918,15 +1918,6 @@ static BOOL prompt_user(config_attrs_st *attrs, BOOL disable_conn) // TODO: display settings dialog box; // An error message (if popping it more than once) might be needed. // -#if 1 // FIXME - static int i = 0; - attrs->server = MK_WSTR("10.0.2.2"); - i ++; - if (2 < i) { - return FALSE; - } - return TRUE; -#endif return FALSE; } From 6227ac7ee15e683c81876918f8a8f6077d102781 Mon Sep 17 00:00:00 2001 From: Bogdan Pintea Date: Tue, 19 Jun 2018 17:21:29 +0200 Subject: [PATCH 11/15] b/f typos: s/lenght/length --- driver/defs.h | 2 +- driver/handles.c | 4 ++-- driver/queries.c | 4 ++-- driver/util.c | 2 +- driver/util.h | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/driver/defs.h b/driver/defs.h index cbf7d84a..cd1e5021 100644 --- a/driver/defs.h +++ b/driver/defs.h @@ -53,7 +53,7 @@ #define ESODBC_MAX_CONCURRENT_ACTIVITIES 16 /* maximum identifer length */ /* TODO: review@alpha */ -/* match 'keyword' ES type lenght */ +/* match 'keyword' ES type length */ #define ESODBC_MAX_IDENTIFIER_LEN 256 /* 20 = len("18446744073709551616"), 1 << (sizeof(uint64_t) * 8bits) */ diff --git a/driver/handles.c b/driver/handles.c index 3027cff2..1bf179bd 100644 --- a/driver/handles.c +++ b/driver/handles.c @@ -1723,7 +1723,7 @@ void concise_to_type_code(SQLSMALLINT concise, SQLSMALLINT *type, */ static void set_defaults_from_type(esodbc_rec_st *rec) { - DBGH(rec->desc, "(re)setting record@0x%p lenght/precision/scale to " + DBGH(rec->desc, "(re)setting record@0x%p lengt/precision/scale to " "defaults.", rec); switch (rec->meta_type) { case METATYPE_STRING: @@ -2275,7 +2275,7 @@ SQLRETURN EsSQLSetDescFieldW( (SQLLEN)(intptr_t)ValuePtr); /* rec field's type is signed :/; a negative is dangerous l8r */ if ((SQLLEN)(intptr_t)ValuePtr < 0) { - ERRH(desc, "octet lenght attribute can't be negative (%lu)", + ERRH(desc, "octet length attribute can't be negative (%lu)", (SQLLEN)(intptr_t)ValuePtr); RET_HDIAGS(desc, SQL_STATE_HY000); } diff --git a/driver/queries.c b/driver/queries.c index 28a2533f..93db3852 100644 --- a/driver/queries.c +++ b/driver/queries.c @@ -788,14 +788,14 @@ static inline void write_out_octets( * occupies after conversion: "the driver has no way of * figuring out what the actual length is" */ *octet_len_ptr = max; - DBG("max lenght (%zd) attribute enforced.", max); + DBG("max length (%zd) attribute enforced.", max); } else { /* if no "network" truncation done, indicate data's length, no * matter if truncated to buffer's size or not */ *octet_len_ptr = avail; } - DBG("lenght of data available for transfer: %ld", *octet_len_ptr); + DBG("length of data available for transfer: %ld", *octet_len_ptr); } /* if an application doesn't specify the conversion, use column's type */ diff --git a/driver/util.c b/driver/util.c index fe6fbe64..c478b682 100644 --- a/driver/util.c +++ b/driver/util.c @@ -88,7 +88,7 @@ size_t ui64tot(uint64_t ui64, void *buff, BOOL wide) } /* - * Trims leading and trailing WS of a wide string of 'chars' lenght. + * Trims leading and trailing WS of a wide string of 'chars' length. * 0-terminator should not be counted (as it's a non-WS). */ const SQLWCHAR *trim_ws(const SQLWCHAR *wstr, size_t *chars) diff --git a/driver/util.h b/driver/util.h index 7de653e8..f214d5ae 100644 --- a/driver/util.h +++ b/driver/util.h @@ -72,7 +72,7 @@ typedef struct cstr { } cstr_st; /* - * Trims leading and trailing WS of a wide string of 'chars' lenght. + * Trims leading and trailing WS of a wide string of 'chars' length. * 0-terminator should not be counted (as it's a non-WS). */ const SQLWCHAR *trim_ws(const SQLWCHAR *wstr, size_t *chars); From 52bcdae5f913d8f10c06ac07644ff7f897c7e6c7 Mon Sep 17 00:00:00 2001 From: Bogdan Pintea Date: Wed, 20 Jun 2018 13:47:29 +0200 Subject: [PATCH 12/15] fixes: logging descriptors, var naming disambiguation --- driver/handles.c | 2 +- driver/util.c | 15 ++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/driver/handles.c b/driver/handles.c index 1bf179bd..b4a08964 100644 --- a/driver/handles.c +++ b/driver/handles.c @@ -2275,7 +2275,7 @@ SQLRETURN EsSQLSetDescFieldW( (SQLLEN)(intptr_t)ValuePtr); /* rec field's type is signed :/; a negative is dangerous l8r */ if ((SQLLEN)(intptr_t)ValuePtr < 0) { - ERRH(desc, "octet length attribute can't be negative (%lu)", + ERRH(desc, "octet length attribute can't be negative (%ld)", (SQLLEN)(intptr_t)ValuePtr); RET_HDIAGS(desc, SQL_STATE_HY000); } diff --git a/driver/util.c b/driver/util.c index c478b682..366642c3 100644 --- a/driver/util.c +++ b/driver/util.c @@ -291,7 +291,7 @@ size_t json_escape(const char *jin, size_t inlen, char *jout, size_t outlen) SQLRETURN write_wstr(SQLHANDLE hnd, SQLWCHAR *dest, wstr_st *src, SQLSMALLINT /*B*/avail, SQLSMALLINT /*B*/*usedp) { - size_t awail; + size_t wide_avail; /* cnt must not count the 0-term (XXX: ever need to copy 0s?) */ assert(src->cnt <= 0 || src->str[src->cnt - 1]); @@ -314,16 +314,17 @@ SQLRETURN write_wstr(SQLHANDLE hnd, SQLWCHAR *dest, wstr_st *src, ERRH(hnd, "invalid buffer length provided: %d.", avail); RET_DIAG(&HDRH(hnd)->diag, SQL_STATE_HY090, NULL, 0); } else { - awail = avail/sizeof(SQLWCHAR); + wide_avail = avail/sizeof(SQLWCHAR); } - if (awail <= src->cnt) { /* =, since src->cnt doesn't count the \0 */ - wcsncpy(dest, src->str, awail - /* 0-term */1); - dest[awail - 1] = 0; + /* '=' (in <=), since src->cnt doesn't count the \0 */ + if (wide_avail <= src->cnt) { + wcsncpy(dest, src->str, wide_avail - /* 0-term */1); + dest[wide_avail - 1] = 0; INFOH(hnd, "not enough buffer size to write required string (plus " - "terminator): `" LWPD "` [%zd]; available: %d.", - LWSTR(src), src->cnt, awail); + "terminator): `" LWPD "` [%zd]; available: %zd.", + LWSTR(src), src->cnt, wide_avail); RET_DIAG(&HDRH(hnd)->diag, SQL_STATE_01004, NULL, 0); } else { wcsncpy(dest, src->str, src->cnt + /* 0-term */1); From 1cbb53af7a6596e235ceabf64903ffd94019b520 Mon Sep 17 00:00:00 2001 From: Bogdan Pintea Date: Wed, 20 Jun 2018 13:58:45 +0200 Subject: [PATCH 13/15] b/f: (re)fix logging descriptor SQLLEN is defined as int64 --- driver/handles.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/driver/handles.c b/driver/handles.c index b4a08964..4ceb3d1c 100644 --- a/driver/handles.c +++ b/driver/handles.c @@ -2275,7 +2275,7 @@ SQLRETURN EsSQLSetDescFieldW( (SQLLEN)(intptr_t)ValuePtr); /* rec field's type is signed :/; a negative is dangerous l8r */ if ((SQLLEN)(intptr_t)ValuePtr < 0) { - ERRH(desc, "octet length attribute can't be negative (%ld)", + ERRH(desc, "octet length attribute can't be negative (%lld)", (SQLLEN)(intptr_t)ValuePtr); RET_HDIAGS(desc, SQL_STATE_HY000); } From 08ac85dd41d4758b1291387d6123339b6058086f Mon Sep 17 00:00:00 2001 From: Bogdan Pintea Date: Wed, 20 Jun 2018 14:11:28 +0200 Subject: [PATCH 14/15] b/f: (re)fix descriptor for size_t variable --- driver/util.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/driver/util.c b/driver/util.c index 366642c3..0e781eb4 100644 --- a/driver/util.c +++ b/driver/util.c @@ -323,7 +323,7 @@ SQLRETURN write_wstr(SQLHANDLE hnd, SQLWCHAR *dest, wstr_st *src, dest[wide_avail - 1] = 0; INFOH(hnd, "not enough buffer size to write required string (plus " - "terminator): `" LWPD "` [%zd]; available: %zd.", + "terminator): `" LWPD "` [%zu]; available: %zu.", LWSTR(src), src->cnt, wide_avail); RET_DIAG(&HDRH(hnd)->diag, SQL_STATE_01004, NULL, 0); } else { From 31e28019fad6a2218151e5d41619cf8d49d5a002 Mon Sep 17 00:00:00 2001 From: Bogdan Pintea Date: Wed, 20 Jun 2018 14:27:31 +0200 Subject: [PATCH 15/15] b/f: add back the @ mutting for 'echo off' itself --- build.bat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.bat b/build.bat index bdcc19d4..72bf4697 100644 --- a/build.bat +++ b/build.bat @@ -1,4 +1,4 @@ -echo off +@echo off rem rem Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one rem or more contributor license agreements. Licensed under the Elastic License;