From 646ca9f50bbb2ae097b7e7df4a813a1a5668ee3c Mon Sep 17 00:00:00 2001 From: Bogdan Pintea Date: Thu, 26 Apr 2018 13:58:34 +0200 Subject: [PATCH 01/17] small build script improvements - make the warnings visible - warn if no MSVC edition is found --- build.bat | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/build.bat b/build.bat index a8949e90..81a20bf6 100644 --- a/build.bat +++ b/build.bat @@ -78,6 +78,8 @@ if /i not _%ARG:setup=% == _%ARG% ( echo. ) ) +REM + REM presence of 'fetch': invoke FETCH "function" if /i not _%ARG:fetch=% == _%ARG% ( @@ -140,7 +142,9 @@ REM if TEMP var not set, set it. if exist %TEMP% goto:eof set TEMP="%USERPROFILE%\Local Settings\Temp\" if exist %TEMP% goto:eof - echo WARN: no temporary directory available; using root + echo. + echo WARNING: no temporary directory available; using root + echo. set TEMP=\ goto:eof @@ -211,16 +215,23 @@ REM CLEAN function: clean up the build dir before building REM SETUP function: set-up the build environment :SETUP set RELEASE=2017 - set EDITION=Professional for %%e in (Enterprise, Professional, Community) do ( if exist "C:\Program Files (x86)\Microsoft Visual Studio\%RELEASE%\%%e\Common7\Tools\VsDevCmd.bat" ( if /i "%%e" == "Community" ( + echo. echo WARNING: Community edition is not licensed to build commerical projects. + echo. ) call "C:\Program Files (x86)\Microsoft Visual Studio\%RELEASE%\%%e\Common7\Tools\VsDevCmd.bat" -arch=!TARCH! + set EDITION=%%e break ) ) + if "%EDITION%" == "" ( + echo. + echo WARNING: no MSVC edition found, environment not set. + echo. + ) goto:eof From e911f9a73280c3f3b45d21959869eeed68600bb0 Mon Sep 17 00:00:00 2001 From: Bogdan Pintea Date: Thu, 26 Apr 2018 15:01:03 +0200 Subject: [PATCH 02/17] add a BUGH handle log macro + BUGH() --- driver/log.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/driver/log.h b/driver/log.h index cc023501..5427502e 100644 --- a/driver/log.h +++ b/driver/log.h @@ -77,7 +77,6 @@ extern int _esodbc_log_level; #define INFO(fmt, ...) LOG(LOG_LEVEL_INFO, fmt, __VA_ARGS__) #define DBG(fmt, ...) LOG(LOG_LEVEL_DBG, fmt, __VA_ARGS__) - #define BUG(fmt, ...) \ do { \ ERR("[BUG] " fmt, __VA_ARGS__); \ @@ -119,6 +118,11 @@ static inline char* _hhtype2str(void *handle) #define INFOH(hnd, fmt, ...) LOGH(LOG_LEVEL_INFO, 0, hnd, fmt, __VA_ARGS__) #define DBGH(hnd, fmt, ...) LOGH(LOG_LEVEL_DBG, 0, hnd, fmt, __VA_ARGS__) +#define BUGH(hnd, fmt, ...) \ + do { \ + ERRH(hnd, "[BUG] " fmt, __VA_ARGS__); \ + assert(0); \ + } while (0) From 492c75d983293ed29c3fc4632f01e9ada8b60cfd Mon Sep 17 00:00:00 2001 From: Bogdan Pintea Date: Thu, 26 Apr 2018 15:24:50 +0200 Subject: [PATCH 03/17] load and cache Elasticsearch/SQL data types The 'SYS TYPES' query is now executed right after connectivity check part of SQLDriverConnect(). The result is cached on the connection (es_type array member, one element for each type). When a query is executed and the results processed, the column data type sent by ES will be used to associate the right es_type element to the record associated with the column. This allows stripping the record structure from members that are only used for data type characterization, thus saving memory, as well as "transparency", in case new types are added or existing changed. (Some change would still be needed in the driver, due to way ODBC specifies "size" and "precision" of a column.) --- driver/connect.c | 510 ++++++++++++++++++++++++++++++++++++++++++++++- driver/connect.h | 1 + driver/handles.h | 66 ++++-- driver/queries.c | 295 +++++++++++++-------------- 4 files changed, 703 insertions(+), 169 deletions(-) diff --git a/driver/connect.c b/driver/connect.c index bc3f5a32..be9fd44b 100644 --- a/driver/connect.c +++ b/driver/connect.c @@ -27,6 +27,33 @@ #define JSON_SQL_CURSOR_START "{\"cursor\":\"" #define JSON_SQL_CURSOR_END "\"}" +/* Elasticsearch/SQL data types */ +/* 4 */ +#define JSON_COL_BYTE "byte" +#define JSON_COL_LONG "long" +#define JSON_COL_TEXT "text" +#define JSON_COL_DATE "date" +#define JSON_COL_NULL "null" +/* 5 */ +#define JSON_COL_SHORT "short" +#define JSON_COL_FLOAT "float" +/* 6 */ +#define JSON_COL_DOUBLE "double" +#define JSON_COL_BINARY "binary" +#define JSON_COL_OBJECT "object" +#define JSON_COL_NESTED "nested" +/* 7 */ +#define JSON_COL_BOOLEAN "boolean" +#define JSON_COL_INTEGER "integer" +#define JSON_COL_KEYWORD "keyword" +/* 10 */ +#define JSON_COL_HALF_FLOAT "half_float" +/* 11 */ +#define JSON_COL_UNSUPPORTED "unsupported" +/* 12 */ +#define JSON_COL_SCALED_FLOAT "scaled_float" + + /* attribute keywords used in connection strings */ #define CONNSTR_KW_DRIVER "Driver" #define CONNSTR_KW_DSN "DSN" @@ -1047,6 +1074,13 @@ void cleanup_dbc(esodbc_dbc_st *dbc) dbc->dsn.str = MK_WPTR(""); dbc->dsn.cnt = 0; } + if (dbc->es_types) { + free(dbc->es_types); + dbc->es_types = NULL; + dbc->no_types = 0; + } else { + assert(dbc->no_types == 0); + } assert(dbc->abuff == NULL); /* reminder for when going multithreaded */ cleanup_curl(dbc); } @@ -1086,6 +1120,466 @@ static SQLRETURN do_connect(esodbc_dbc_st *dbc, config_attrs_st *attrs) return SQL_SUCCESS; } + +/* Maps ES/SQL type to C SQL. */ +SQLSMALLINT type_elastic2csql(wstr_st *type_name) +{ + switch (type_name->cnt) { + /* 4: BYTE, LONG, TEXT, DATE, NULL */ + case sizeof(JSON_COL_BYTE) - 1: + switch (tolower(type_name->str[0])) { + case (SQLWCHAR)'b': + if (! wmemncasecmp(type_name->str, + MK_WPTR(JSON_COL_BYTE), type_name->cnt)) { + return SQL_C_STINYINT; + } + break; + case (SQLWCHAR)'l': + if (! wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_LONG), + type_name->cnt)) { + return SQL_C_SLONG; + } + break; + case (SQLWCHAR)'t': + if (! wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_TEXT), + type_name->cnt)) { + // TODO: char/longvarchar/wchar/wvarchar? + return SQL_C_CHAR; + } + break; + case (SQLWCHAR)'d': + if (! wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_DATE), + type_name->cnt)) { + return SQL_C_TYPE_TIMESTAMP; + } + break; + case (SQLWCHAR)'n': + if (! wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_NULL), + type_name->cnt)) { + // TODO: own type? + return SQL_C_UTINYINT; + } + break; + } + break; + + /* 5: SHORT, FLOAT */ + case sizeof(JSON_COL_SHORT) - 1: + switch (tolower(type_name->str[0])) { + case (SQLWCHAR)'s': + if (! wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_SHORT), + type_name->cnt)) { + return SQL_C_SSHORT; + } + break; + case (SQLWCHAR)'f': + if (! wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_FLOAT), + type_name->cnt)) { + return SQL_C_FLOAT; + } + break; + } + break; + + /* 6: DOUBLE, BINARY, OBJECT, NESTED */ + case sizeof(JSON_COL_DOUBLE) - 1: + switch (tolower(type_name->str[0])) { + case (SQLWCHAR)'d': + if (! wmemncasecmp(type_name->str, + MK_WPTR(JSON_COL_DOUBLE), type_name->cnt)) { + return SQL_C_DOUBLE; + } + break; + case (SQLWCHAR)'b': + if (! wmemncasecmp(type_name->str, + MK_WPTR(JSON_COL_BINARY), type_name->cnt)) { + return SQL_C_BINARY; + } + break; + case (SQLWCHAR)'o': + if (! wmemncasecmp(type_name->str, + MK_WPTR(JSON_COL_OBJECT), type_name->cnt)) { + return SQL_C_BINARY; + } + break; + case (SQLWCHAR)'n': + if (! wmemncasecmp(type_name->str, + MK_WPTR(JSON_COL_NESTED), type_name->cnt)) { + return SQL_C_BINARY; + } + break; + } + break; + + /* 7: INTEGER, BOOLEAN, KEYWORD */ + case sizeof(JSON_COL_INTEGER) - 1: + switch (tolower(type_name->str[0])) { + case (SQLWCHAR)'i': /* integer */ + if (wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_INTEGER), + type_name->cnt) == 0) + return SQL_C_SLONG; + break; + case (SQLWCHAR)'b': /* boolean */ + if (wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_BOOLEAN), + type_name->cnt) == 0) + return SQL_C_UTINYINT; + break; + case (SQLWCHAR)'k': /* keyword */ + if (wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_KEYWORD), + type_name->cnt) == 0) + return SQL_C_CHAR; + break; + } + break; + + /* 10: HALF_FLOAT */ + case sizeof(JSON_COL_HALF_FLOAT) - 1: + if (! wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_HALF_FLOAT), + type_name->cnt)) { + return SQL_C_FLOAT; + } + break; + + /* 11: UNSUPPORTED */ + case sizeof(JSON_COL_UNSUPPORTED) - 1: + if (! wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_UNSUPPORTED), + type_name->cnt)) { + return SQL_C_BINARY; + } + break; + + /* 12: SCALED_FLOAT */ + case sizeof(JSON_COL_SCALED_FLOAT) - 1: + if (! wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_SCALED_FLOAT), + type_name->cnt)) { + return SQL_C_FLOAT; + } + break; + + } + ERR("unrecognized Elastic type `" LWPDL "` (%zd).", LWSTR(type_name), + type_name->cnt); + return SQL_UNKNOWN_TYPE; +} + + +/* + * Load SYS TYPES data. + * + * One can not do a row-wise rowset fetch with ODBC where the + * length-indicator buffer is separate from the row structure => need to + * "manually" copy from a row structure (defined into the function) into the + * estype structure. The array of estype structs is returned. + */ +static BOOL load_es_types(esodbc_dbc_st *dbc) +{ + esodbc_stmt_st *stmt = NULL; + SQLRETURN ret = FALSE; + SQLSMALLINT col_cnt; + SQLLEN row_cnt; + /* structure for one row returned by the ES. + * This is a mirror of elasticsearch_type, with lenght-or-indicator fields + * for each of the members in elasticsearch_type */ + struct { + SQLWCHAR type_name[ESODBC_MAX_IDENTIFIER_LEN]; + SQLLEN type_name_loi; /* _ lenght or indicator */ + SQLSMALLINT data_type; + SQLLEN data_type_loi; + SQLINTEGER column_size; + SQLLEN column_size_loi; + SQLWCHAR literal_prefix[ESODBC_MAX_IDENTIFIER_LEN]; + SQLLEN literal_prefix_loi; + SQLWCHAR literal_suffix[ESODBC_MAX_IDENTIFIER_LEN]; + SQLLEN literal_suffix_loi; + SQLWCHAR create_params[ESODBC_MAX_IDENTIFIER_LEN]; + SQLLEN create_params_loi; + SQLSMALLINT nullable; + SQLLEN nullable_loi; + SQLSMALLINT case_sensitive; + SQLLEN case_sensitive_loi; + SQLSMALLINT searchable; + SQLLEN searchable_loi; + SQLSMALLINT unsigned_attribute; + SQLLEN unsigned_attribute_loi; + SQLSMALLINT fixed_prec_scale; + SQLLEN fixed_prec_scale_loi; + SQLSMALLINT auto_unique_value; + SQLLEN auto_unique_value_loi; + SQLWCHAR local_type_name[ESODBC_MAX_IDENTIFIER_LEN]; + SQLLEN local_type_name_loi; + SQLSMALLINT minimum_scale; + SQLLEN minimum_scale_loi; + SQLSMALLINT maximum_scale; + SQLLEN maximum_scale_loi; + SQLSMALLINT sql_data_type; + SQLLEN sql_data_type_loi; + SQLSMALLINT sql_datetime_sub; + SQLLEN sql_datetime_sub_loi; + SQLINTEGER num_prec_radix; + SQLLEN num_prec_radix_loi; + SQLSMALLINT interval_precision; + SQLLEN interval_precision_loi; + } type_row[ESODBC_MAX_ROW_ARRAY_SIZE]; + /* both arrays must use ESODBC_MAX_ROW_ARRAY_SIZE since no SQLFetch() + * looping is implemented (see check after SQLFetch() below). */ + SQLUSMALLINT row_status[ESODBC_MAX_ROW_ARRAY_SIZE]; + SQLULEN rows_fetched, i, strs_len; + size_t size; + SQLWCHAR *pos; + esodbc_estype_st *types = NULL; + + if (! SQL_SUCCEEDED(EsSQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt))) { + ERRH(dbc, "failed to alloc a statement handle."); + return FALSE; + } + assert(stmt); + + if (! SQL_SUCCEEDED(EsSQLGetTypeInfoW(stmt, SQL_ALL_TYPES))) { + ERRH(stmt, "failed to query Elasticsearch."); + goto end; + } + + /* check that we have as many columns as members in target row struct */ + if (! SQL_SUCCEEDED(EsSQLNumResultCols(stmt, &col_cnt))) { + ERRH(stmt, "failed to get result columns count."); + goto end; + } else if (col_cnt != ESODBC_TYPES_MEMBERS) { + ERRH(stmt, "Elasticsearch returned an unexpected number of columns " + "(%d vs expected %d).", col_cnt, ESODBC_TYPES_MEMBERS); + goto end; + } else { + DBGH(stmt, "Elasticsearch types columns count: %d.", col_cnt); + } + + /* check that we have received proper number of rows (non-0, less than + * max allowed here) */ + if (! SQL_SUCCEEDED(EsSQLRowCount(stmt, &row_cnt))) { + ERRH(stmt, "failed to get result rows count."); + goto end; + } else if (row_cnt <= 0) { + ERRH(stmt, "Elasticsearch returned no type as supported."); + goto end; + } else if (ESODBC_MAX_ROW_ARRAY_SIZE < row_cnt) { + ERRH(stmt, "Elasticsearch returned too many types (%d vs limit %zd).", + row_cnt, ESODBC_MAX_ROW_ARRAY_SIZE); + goto end; + } else { + DBGH(stmt, "Elasticsearch types rows count: %ld.", row_cnt); + } + + /* indicate bind type: row-wise (i.e. row size) */ + if (! SQL_SUCCEEDED(EsSQLSetStmtAttrW(stmt, SQL_ATTR_ROW_BIND_TYPE, + (SQLPOINTER)sizeof(type_row[0]), 0))) { + ERRH(stmt, "failed to set bind type to row-wise."); + goto end; + } + /* indicate rowset size */ + if (! SQL_SUCCEEDED(EsSQLSetStmtAttrW(stmt, SQL_ATTR_ROW_ARRAY_SIZE, + (SQLPOINTER)ESODBC_MAX_ROW_ARRAY_SIZE, 0))) { + ERRH(stmt, "failed to set rowset size (%zd).", + ESODBC_MAX_ROW_ARRAY_SIZE); + goto end; + } + /* indicate array to write row status into; initialize with error first */ + for (i = 0; i < ESODBC_MAX_ROW_ARRAY_SIZE; i ++) { + row_status[i] = SQL_ROW_ERROR; + } + if (! SQL_SUCCEEDED(EsSQLSetStmtAttrW(stmt, SQL_ATTR_ROW_STATUS_PTR, + row_status, 0))) { + ERRH(stmt, "failed to set row status array."); + goto end; + } + /* indicate pointer to write how many rows were fetched into */ + if (! SQL_SUCCEEDED(EsSQLSetStmtAttrW(stmt, SQL_ATTR_ROWS_FETCHED_PTR, + &rows_fetched, 0))) { + ERRH(stmt, "failed to set fetched size pointer."); + goto end; + } + + /* bind one column */ +#define ES_TYPES_BINDCOL(_col_nr, _member, _c_type) \ + do { \ + SQLPOINTER _ptr = _c_type == SQL_C_WCHAR ? \ + (SQLPOINTER)(uintptr_t)type_row[0]._member : \ + (SQLPOINTER)&type_row[0]._member; \ + if (! SQL_SUCCEEDED(EsSQLBindCol(stmt, _col_nr, _c_type, \ + _ptr, sizeof(type_row[0]._member), \ + &type_row[0]._member ## _loi))) { \ + ERRH(stmt, "failed to bind column #" STR(_col_nr) "."); \ + goto end; \ + } \ + } while (0) + + ES_TYPES_BINDCOL(1, type_name, SQL_C_WCHAR); + ES_TYPES_BINDCOL(2, data_type, SQL_C_SSHORT); + ES_TYPES_BINDCOL(3, column_size, SQL_C_SLONG); + ES_TYPES_BINDCOL(4, literal_prefix, SQL_C_WCHAR); + ES_TYPES_BINDCOL(5, literal_suffix, SQL_C_WCHAR); + ES_TYPES_BINDCOL(6, create_params, SQL_C_WCHAR); + ES_TYPES_BINDCOL(7, nullable, SQL_C_SSHORT); + ES_TYPES_BINDCOL(8, case_sensitive, SQL_C_SSHORT); + ES_TYPES_BINDCOL(9, searchable, SQL_C_SSHORT); + ES_TYPES_BINDCOL(10, unsigned_attribute, SQL_C_SSHORT); + ES_TYPES_BINDCOL(11, fixed_prec_scale, SQL_C_SSHORT); + ES_TYPES_BINDCOL(12, auto_unique_value, SQL_C_SSHORT); + ES_TYPES_BINDCOL(13, local_type_name, SQL_C_WCHAR); + ES_TYPES_BINDCOL(14, minimum_scale, SQL_C_SSHORT); + ES_TYPES_BINDCOL(15, maximum_scale, SQL_C_SSHORT); + ES_TYPES_BINDCOL(16, sql_data_type, SQL_C_SSHORT); + ES_TYPES_BINDCOL(17, sql_datetime_sub, SQL_C_SSHORT); + ES_TYPES_BINDCOL(18, num_prec_radix, SQL_C_SLONG); + ES_TYPES_BINDCOL(19, interval_precision, SQL_C_SSHORT); + +#undef ES_TYPES_BINDCOL + + /* fetch the results into the type_row array */ + if (! SQL_SUCCEEDED(EsSQLFetch(stmt))) { + ERRH(stmt, "failed to fetch results."); + goto end; + } else if (rows_fetched < (SQLULEN)row_cnt) { + /* we're using arrays with max size that SQLFetch() accepts => it + * should read all data in one go. */ + ERRH(stmt, "failed to fetch all the rows in one go."); + goto end; + } + + /* check row statues; + * calculate the lenght of all strings (SQLWCHAR members) returned + * count also the 0-terms, which are not counted for in the indicator */ + strs_len = 0; + for (i = 0; i < rows_fetched; i ++) { + if (row_status[i] != SQL_ROW_SUCCESS) { + ERRH(stmt, "row #%d not succesfully fetched; status: %d.", i, + row_status[i]); + goto end; + } + if (type_row[i].type_name_loi != SQL_NULL_DATA) { + strs_len += type_row[i].type_name_loi; + strs_len += sizeof(*type_row[i].type_name); /* 0-term */ + } + if (type_row[i].literal_prefix_loi != SQL_NULL_DATA) { + strs_len += type_row[i].literal_prefix_loi; + strs_len += sizeof(*type_row[i].literal_prefix); /* 0-term */ + } + if (type_row[i].literal_suffix_loi != SQL_NULL_DATA) { + strs_len += type_row[i].literal_suffix_loi; + strs_len += sizeof(*type_row[i].literal_suffix); /* 0-term */ + } + if (type_row[i].create_params_loi != SQL_NULL_DATA) { + strs_len += type_row[i].create_params_loi; + strs_len += sizeof(*type_row[i].create_params); /* 0-term */ + } + if (type_row[i].local_type_name_loi != SQL_NULL_DATA) { + strs_len += type_row[i].local_type_name_loi; + strs_len += sizeof(*type_row[i].local_type_name); /* 0-term */ + } + } + + /* collate types array and the strings referenced within it */ + size = rows_fetched * sizeof(esodbc_estype_st) + strs_len; + if (! (types = calloc(1, size))) { + ERRNH(stmt, "OOM for %ldB.", size); + goto end; + } + + /* start pointer where the strings will be copied in */ + pos = (SQLWCHAR *)&types[rows_fetched]; + + /* copy one integer member + * TODO: treat NULL case */ +#define ES_TYPES_COPY_INT(_member) \ + do { \ + if (type_row[i]._member ## _loi == SQL_NULL_DATA) { \ + types[i]._member = 0; \ + } else { \ + types[i]._member = type_row[i]._member; \ + } \ + } while (0) + /* copy one wstr_st member + * Note: it'll shift NULLs to empty strings, as most of the API asks for + * empty strings if data is unavailable ("unkown"). */ +#define ES_TYPES_COPY_WSTR(_wmember) \ + do { \ + if (type_row[i]._wmember ## _loi == SQL_NULL_DATA) { \ + types[i]._wmember.cnt = 0; \ + types[i]._wmember.str = MK_WPTR(""); \ + } else { \ + types[i]._wmember.cnt = \ + type_row[i]._wmember ## _loi / sizeof(SQLWCHAR); \ + types[i]._wmember.str = pos; \ + wmemcpy(types[i]._wmember.str, \ + type_row[i]._wmember, types[i]._wmember.cnt + /*\0*/1); \ + pos += types[i]._wmember.cnt + /*\0*/1; \ + } \ + } while (0) + + for (i = 0; i < rows_fetched; i ++) { + /* copy data */ + ES_TYPES_COPY_WSTR(type_name); + ES_TYPES_COPY_INT(data_type); + ES_TYPES_COPY_INT(column_size); + ES_TYPES_COPY_WSTR(literal_prefix); + ES_TYPES_COPY_WSTR(literal_suffix); + ES_TYPES_COPY_WSTR(create_params); + ES_TYPES_COPY_INT(nullable); + ES_TYPES_COPY_INT(case_sensitive); + ES_TYPES_COPY_INT(searchable); + ES_TYPES_COPY_INT(unsigned_attribute); + ES_TYPES_COPY_INT(fixed_prec_scale); + ES_TYPES_COPY_INT(auto_unique_value); + ES_TYPES_COPY_WSTR(local_type_name); + ES_TYPES_COPY_INT(minimum_scale); + ES_TYPES_COPY_INT(maximum_scale); + ES_TYPES_COPY_INT(sql_data_type); + ES_TYPES_COPY_INT(sql_datetime_sub); + ES_TYPES_COPY_INT(num_prec_radix); + ES_TYPES_COPY_INT(interval_precision); + + /* apply any customizations */ + + /* fix SQL_DATA_TYPE columns TODO: GH issue */ + types[i].sql_data_type = types[i].data_type; + + /* resolve ES type to SQL C type */ + types[i].sql_c_type = type_elastic2csql(&types[i].type_name); + if (types[i].sql_c_type == SQL_UNKNOWN_TYPE) { + BUG("failed to convert type name `" LWPDL "` to SQL C type.", + LWSTR(&types[i].type_name)); + goto end; + } + } + +#undef ES_TYPES_COPY_INT +#undef ES_TYPES_COPY_WCHAR + + /* I didn't overrun the buffer */ + assert((char *)pos - (char *)(types + rows_fetched) <= + (intptr_t)(strs_len)); + + ret = TRUE; +end: + if (! SQL_SUCCEEDED(EsSQLFreeStmt(stmt, SQL_UNBIND))) { + ERRH(stmt, "failed to unbind statement"); + ret = FALSE; + } + if (! SQL_SUCCEEDED(EsSQLFreeHandle(SQL_HANDLE_STMT, stmt))) { + ERRH(dbc, "failed to free statement handle!"); + ret = FALSE; + } + + if (ret) { + /* finally, associate the types to the dbc handle */ + dbc->es_types = types; + dbc->no_types = rows_fetched; + } else if (types) { + /* freeing the statement went wrong */ + free(types); + types = NULL; + } + + return ret; +} + #if defined(_WIN32) || defined (WIN32) /* * Reads system registry for ODBC DSN subkey named in attrs->dsn. @@ -1325,11 +1819,16 @@ SQLRETURN EsSQLDriverConnectW RET_HDIAGS(dbc, SQL_STATE_HY110); } + if (! load_es_types(dbc)) { + ERRH(dbc, "failed to load Elasticsearch/SQL types."); + RET_HDIAG(dbc, SQL_STATE_HY000, + "failed to load Elasticsearch/SQL types", 0); + } /* save the original DSN for later inquiry by app */ dbc->dsn.str = malloc((orig_dsn.cnt + /*0*/1) * sizeof(SQLWCHAR)); if (! dbc->dsn.str) { - ERRNH(dbc, "OOM for %zdB.", (orig_dsn.cnt + /*0*/1) * sizeof(SQLWCHAR)); + ERRNH(dbc, "OOM for %zdB.", (orig_dsn.cnt + 1) * sizeof(SQLWCHAR)); RET_HDIAGS(dbc, SQL_STATE_HY001); } dbc->dsn.str[orig_dsn.cnt] = '\0'; @@ -1403,9 +1902,10 @@ SQLRETURN EsSQLSetConnectAttrW( return SQL_ERROR; /* error means ANSI */ case SQL_ATTR_LOGIN_TIMEOUT: - if (dbc->conn) { - ERRH(dbc, "connection already established, can't set connection" - " timeout (to %u).", (SQLUINTEGER)(uintptr_t)Value); + if (dbc->es_types) { + ERRH(dbc, "connection already established, can't set " + "connection timeout anymore (to %u).", + (SQLUINTEGER)(uintptr_t)Value); RET_HDIAG(dbc, SQL_STATE_HY011, "connection established, " "can't set connection timeout.", 0); } @@ -1488,7 +1988,7 @@ SQLRETURN EsSQLGetConnectAttrW( case SQL_ATTR_CURRENT_CATALOG: DBGH(dbc, "requested: catalog name (@0x%p).", dbc->catalog); #if 0 - if (! dbc->conn) { + if (! dbc->es_types) { ERRH(dbc, "no connection active."); /* TODO: check connection state and correct state */ RET_HDIAGS(dbc, SQL_STATE_08003); diff --git a/driver/connect.h b/driver/connect.h index c9016de2..42af515e 100644 --- a/driver/connect.h +++ b/driver/connect.h @@ -15,6 +15,7 @@ void connect_cleanup(); SQLRETURN post_statement(esodbc_stmt_st *stmt); void cleanup_dbc(esodbc_dbc_st *dbc); +SQLSMALLINT type_elastic2csql(wstr_st *type_name); SQLRETURN EsSQLDriverConnectW ( diff --git a/driver/handles.h b/driver/handles.h index e5ab6faf..b704cf0b 100644 --- a/driver/handles.h +++ b/driver/handles.h @@ -53,6 +53,36 @@ typedef struct struct_env { // TODO?: connections } esodbc_env_st; + +typedef struct elasticsearch_type { + wstr_st type_name; + SQLSMALLINT data_type; + SQLINTEGER column_size; + wstr_st literal_prefix; + wstr_st literal_suffix; + wstr_st create_params; + SQLSMALLINT nullable; + SQLSMALLINT case_sensitive; + SQLSMALLINT searchable; + SQLSMALLINT unsigned_attribute; + SQLSMALLINT fixed_prec_scale; + SQLSMALLINT auto_unique_value; + wstr_st local_type_name; + SQLSMALLINT minimum_scale; + SQLSMALLINT maximum_scale; + SQLSMALLINT sql_data_type; + SQLSMALLINT sql_datetime_sub; + SQLINTEGER num_prec_radix; + SQLSMALLINT interval_precision; + + /* number of SYS TYPES result columns mapped over the above members */ +#define ESODBC_TYPES_MEMBERS 19 + + /* SQL C type driver mapping of ES' data_type */ + SQLSMALLINT sql_c_type; +} esodbc_estype_st; + + /* * https://docs.microsoft.com/en-us/sql/odbc/reference/develop-app/connection-handles : * """ @@ -77,9 +107,8 @@ typedef struct struct_dbc { } fetch; BOOL pack_json; /* should JSON be used in REST bodies? (vs. CBOR) *///TODO - // FIXME: placeholder; used if connection has been established or not - // TODO: PROTO - void *conn; + esodbc_estype_st *es_types; /* array with ES types */ + SQLULEN no_types; /* number of types in array */ CURL *curl; /* cURL handle */ char *abuff; /* buffer holding the answer */ @@ -121,25 +150,31 @@ typedef struct desc_rec { /* helper member, to characterize the type */ esodbc_metatype_et meta_type; - /* record fields */ + /* pointer to the ES/SQL type in DBC array + * need to be set for records in IxD descriptors */ + esodbc_estype_st *es_type; + + /* + * record fields + */ + /* following record fields have been moved into es_type: + * literal_prefix, literal_suffix, local_type_name, type_name, + * auto_unique_value, case_sensitive, fixed_prec_scale, nullable, + * searchable, usigned */ SQLSMALLINT concise_type; SQLSMALLINT type; /* SQL_C_ -> AxD, SQL_ -> IxD */ SQLSMALLINT datetime_interval_code; SQLPOINTER data_ptr; /* array, if .array_size > 1 */ - /* TODO: add (& use) the lenghts */ + /* 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 *literal_prefix; /* r/o */ // TODO: static? - SQLWCHAR *literal_suffix; /* r/o */ // TODO: static? - SQLWCHAR *local_type_name; /* r/o */ SQLWCHAR *name; - SQLWCHAR *schema_name; /* r/o */ // TODO: static? + SQLWCHAR *schema_name; /* r/o */ SQLWCHAR *table_name; /* r/o */ - SQLWCHAR *type_name; /* r/o */ SQLLEN *indicator_ptr; /* array, if .array_size > 1 */ SQLLEN *octet_length_ptr; /* array, if .array_size > 1 */ @@ -149,22 +184,15 @@ typedef struct desc_rec { SQLULEN length; - SQLINTEGER auto_unique_value; - SQLINTEGER case_sensitive; - SQLINTEGER datetime_interval_precision; - SQLINTEGER num_prec_radix; + SQLINTEGER datetime_interval_precision; /*TODO: -> es_type? */ + SQLINTEGER num_prec_radix; /*TODO: -> es_type? */ - SQLSMALLINT fixed_prec_scale; - SQLSMALLINT nullable; /* r/o */ SQLSMALLINT parameter_type; SQLSMALLINT precision; SQLSMALLINT rowver; SQLSMALLINT scale; - SQLSMALLINT searchable; SQLSMALLINT unnamed; - SQLSMALLINT usigned; SQLSMALLINT updatable; - /* /record fields */ } esodbc_rec_st; diff --git a/driver/queries.c b/driver/queries.c index 2d713bce..4cff6b60 100644 --- a/driver/queries.c +++ b/driver/queries.c @@ -24,16 +24,6 @@ #define JSON_ANSWER_ERR_REASON "reason" #define JSON_ANSWER_COL_NAME "name" #define JSON_ANSWER_COL_TYPE "type" -/* 4 */ -#define JSON_COL_TEXT "text" -#define JSON_COL_DATE "date" -#define JSON_COL_BYTE "byte" -/* 5 */ -#define JSON_COL_SHORT "short" -/* 7 */ -#define JSON_COL_BOOLEAN "boolean" -#define JSON_COL_INTEGER "integer" -#define JSON_COL_KEYWORD "keyword" #define MSG_INV_SRV_ANS "Invalid server answer" @@ -61,69 +51,6 @@ void clear_resultset(esodbc_stmt_st *stmt) memset(&stmt->rset, 0, sizeof(stmt->rset)); } -static SQLSMALLINT type_elastic2csql(const SQLWCHAR *type_name, size_t len) -{ - switch (len) { - // TODO: take in the precision for a better - // representation - // case sizeof(JSON_COL_KEYWORD) - 1: - // case sizeof(JSON_COL_BOOLEAN) - 1: - case sizeof(JSON_COL_INTEGER) - 1: - switch(tolower(type_name[0])) { - case (SQLWCHAR)'i': /* integer */ - if (wmemncasecmp(type_name, MK_WPTR(JSON_COL_INTEGER), - len) == 0) - return SQL_C_SLONG; - break; - case (SQLWCHAR)'b': /* boolean */ - if (wmemncasecmp(type_name, MK_WPTR(JSON_COL_BOOLEAN), - len) == 0) - return SQL_C_UTINYINT; - break; - case (SQLWCHAR)'k': /* keyword */ - if (wmemncasecmp(type_name, MK_WPTR(JSON_COL_KEYWORD), - len) == 0) - return SQL_C_CHAR; - break; - } - break; - // case sizeof(JSON_COL_DATE) - 1: - case sizeof(JSON_COL_TEXT) - 1: - switch(tolower(type_name[0])) { - case (SQLWCHAR)'t': - if (! wmemncasecmp(type_name, MK_WPTR(JSON_COL_TEXT), len)) - // TODO: char/longvarchar/wchar/wvarchar? - return SQL_C_CHAR; - break; - case (SQLWCHAR)'d': - if (! wmemncasecmp(type_name, MK_WPTR(JSON_COL_DATE), len)) - // TODO: time/timestamp - return SQL_C_TYPE_DATE; - break; - case (SQLWCHAR)'b': - if (! wmemncasecmp(type_name, MK_WPTR(JSON_COL_BYTE), len)) - return SQL_C_STINYINT; - break; -#if 1 // BUG FIXME - case (SQLWCHAR)'n': - if (! wmemncasecmp(type_name, MK_WPTR("null"), len)) - // TODO: time/timestamp - return SQL_C_SSHORT; - break; -#endif - } - break; - case sizeof(JSON_COL_SHORT) - 1: - if (! wmemncasecmp(type_name, MK_WPTR(JSON_COL_SHORT), len)) - // TODO: time/timestamp - return SQL_C_SSHORT; - break; - } - ERR("unrecognized Elastic type `" LWPDL "` (%zd).", len, type_name, - len); - return SQL_UNKNOWN_TYPE; -} - static void set_col_size(esodbc_rec_st *rec) { switch (rec->concise_type) { @@ -172,21 +99,22 @@ static void set_col_decdigits(esodbc_rec_st *rec) static SQLRETURN attach_columns(esodbc_stmt_st *stmt, UJObject columns) { + esodbc_desc_st *ird; + esodbc_dbc_st *dbc; esodbc_rec_st *rec; SQLRETURN ret; SQLSMALLINT recno; void *iter; UJObject col_o, name_o, type_o; - const wchar_t *col_wname, *col_wtype; - SQLSMALLINT col_stype; - size_t len, ncols; - - esodbc_desc_st *ird = stmt->ird; + wstr_st col_name, col_type; + size_t ncols, i; wchar_t *keys[] = { MK_WPTR(JSON_ANSWER_COL_NAME), MK_WPTR(JSON_ANSWER_COL_TYPE) }; + ird = stmt->ird; + dbc = stmt->hdr.dbc; ncols = UJArraySize(columns); DBGH(stmt, "columns received: %zd.", ncols); @@ -212,35 +140,45 @@ static SQLRETURN attach_columns(esodbc_stmt_st *stmt, UJObject columns) } rec = &ird->recs[recno]; // +recno - col_wname = UJReadString(name_o, &len); ASSERT_INTEGER_TYPES_EQUAL(wchar_t, SQLWCHAR); - rec->name = len ? (SQLWCHAR *)col_wname : MK_WPTR(""); - - col_wtype = UJReadString(type_o, &len); - /* If the type is unknown, an empty string is returned." */ - rec->type_name = len ? (SQLWCHAR *)col_wtype : MK_WPTR(""); - /* - * TODO: to ELASTIC types, rather? - * TODO: Read size (precision/lenght) and dec-dig(scale/precision) - * from received type. - */ - col_stype = type_elastic2csql(col_wtype, len); - if (col_stype == SQL_UNKNOWN_TYPE) { - ERRH(stmt, "failed to convert Elastic to C SQL type `" - LWPDL "`.", len, col_wtype); - RET_HDIAG(stmt, SQL_STATE_HY000, MSG_INV_SRV_ANS, 0); + col_name.str = (SQLWCHAR *)UJReadString(name_o, &col_name.cnt); + rec->name = col_name.cnt ? col_name.str : MK_WPTR(""); + + col_type.str = (SQLWCHAR *)UJReadString(type_o, &col_type.cnt); + + assert(! rec->es_type); + /* lookup the DBC-cashed ES type */ + for (i = 0; i < dbc->no_types; i ++) { + if (EQ_CASE_WSTR(&dbc->es_types[i].type_name, &col_type)) { + rec->es_type = &dbc->es_types[i]; + break; + } } - rec->concise_type = col_stype; - concise_to_type_code(col_stype, &rec->type, + if (rec->es_type) { + rec->concise_type = rec->es_type->sql_c_type; + } else { + do { + if (! dbc->no_types) { + /* the connection doesn't have the types cached yet (this + * is the SYS TYPES call) -> resolve the types "directly" + * */ + rec->concise_type = type_elastic2csql(&col_type); + if (rec->concise_type != SQL_UNKNOWN_TYPE) + break; + } + ERRH(stmt, "type lookup failed for `" LWPDL "`. (ES-driver " + "out of sync? check versions.)", LWSTR(&col_type)); + RET_HDIAG(stmt, SQL_STATE_HY000, MSG_INV_SRV_ANS, 0); + } while (0); + } + concise_to_type_code(rec->concise_type, &rec->type, &rec->datetime_interval_code); - /* TODO: here it should be 'ird->type'. But we're setting C SQL types - * for IRD as well for now. */ rec->meta_type = concise_to_meta(rec->concise_type, DESC_TYPE_ARD); set_col_size(rec); set_col_decdigits(rec); - /* TODO: set all settable fields */ + /* TODO: set remaining of settable fields (base table etc.) */ /* "If a base column name does not exist (as in the case of columns * that are expressions), then this variable contains an empty @@ -259,8 +197,9 @@ static SQLRETURN attach_columns(esodbc_stmt_st *stmt, UJObject columns) //dump_record(rec); #endif /* NDEBUG */ - DBGH(stmt, "column #%d: name=`" LWPD "`, type=%d (`" LWPD "`).", - recno, col_wname, col_stype, col_wtype); + DBGH(stmt, "column #%d: name=`" LWPDL "`, type=%d (`" LWPDL "`).", + recno, LWSTR(&col_name), rec->concise_type, + LWSTR(&col_type)); recno ++; } @@ -468,6 +407,9 @@ SQLRETURN attach_error(esodbc_stmt_st *stmt, char *buff, size_t blen) RET_STATE(stmt->hdr.diag.state); } +/* + * Attach an SQL query to the statment: malloc, convert, copy. + */ SQLRETURN attach_sql(esodbc_stmt_st *stmt, const SQLWCHAR *sql, /* SQL text statement */ size_t sqlcnt /* count of chars of 'sql' */) @@ -514,12 +456,15 @@ SQLRETURN attach_sql(esodbc_stmt_st *stmt, stmt->u8sql = u8; stmt->sqllen = (size_t)len; - + DBGH(stmt, "attached SQL `%.*s` (%zd).", len, u8, len); return SQL_SUCCESS; } +/* + * Detach the existing query (if any) from the statement. + */ void detach_sql(esodbc_stmt_st *stmt) { if (! stmt->u8sql) @@ -735,6 +680,7 @@ static size_t buff_octet_size( /* is target buffer to small? adjust size if so and indicate truncation */ /* Note: this should only be tested/applied if ARD.meta_type == STR||BIN */ + // FIXME: check note above if (room < max_copy) { INFO("applying buffer truncation %zd -> %zd.", max_copy, room); max_copy = room; @@ -871,6 +817,69 @@ static SQLRETURN copy_double(esodbc_rec_st *arec, esodbc_rec_st *irec, return SQL_ERROR; } +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; + SQLSMALLINT target_type; + + stmt = arec->desc->hdr.stmt; + + /* 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 boolean type, but NULL data ptr."); + RET_HDIAGS(stmt, SQL_STATE_HY009); + } + + target_type = arec->type == SQL_C_DEFAULT ? irec->type : arec->type; + DBGH(stmt, "target data type: %d.", target_type); + switch (target_type) { + case SQL_C_STINYINT: + case SQL_C_UTINYINT: + *(SQLSCHAR *)data_ptr = (SQLSCHAR)boolval; + write_copied_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), + 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), + 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), + stmt->max_length, irec->meta_type); + break; + + case SQL_C_CHAR: + case SQL_C_WCHAR: + + default: + FIXME; // FIXME + return SQL_ERROR; + } + + DBGH(stmt, "REC@0x%p, data_ptr@0x%p, copied boolean: `%d`.", arec, + data_ptr, boolval); + return SQL_SUCCESS; +} + /* * -> SQL_C_CHAR */ @@ -1127,6 +1136,7 @@ static SQLRETURN copy_one_row(esodbc_stmt_st *stmt, SQLULEN pos, UJObject row) long long ll; double dbl; const wchar_t *wstr; + BOOL boolval; size_t len; BOOL with_info; esodbc_desc_st *ard, *ird; @@ -1140,8 +1150,8 @@ static SQLRETURN copy_one_row(esodbc_stmt_st *stmt, SQLULEN pos, UJObject row) do { \ if (ard->array_status_ptr) \ ard->array_status_ptr[pos] = SQL_ROW_ERROR; \ - return post_row_diagnostic(&stmt->hdr.diag, _state, MK_WPTR(_message), 0, \ - rowno, _colno); \ + return post_row_diagnostic(&stmt->hdr.diag, _state, MK_WPTR(_message),\ + 0, rowno, _colno); \ } while (0) #define SET_ROW_DIAG(_rowno, _colno) \ do { \ @@ -1171,16 +1181,23 @@ static SQLRETURN copy_one_row(esodbc_stmt_st *stmt, SQLULEN pos, UJObject row) DBGH(stmt, "column #%d not bound, skipping it.", i + 1); continue; } - + arec = get_record(ard, i + 1, FALSE); irec = get_record(ird, i + 1, FALSE); assert(arec); assert(irec); - + switch (UJGetType(obj)) { + default: + ERRH(stmt, "unexpected object of type %d in row L#%zd/T#%zd.", + UJGetType(obj), stmt->rset.vrows, stmt->rset.frows); + RET_ROW_DIAG(SQL_STATE_01S01, MSG_INV_SRV_ANS, i + 1); + /* RET_.. returns */ + case UJT_Null: DBGH(stmt, "value [%zd, %d] is NULL.", rowno, i + 1); #if 0 + // FIXME: needed? if (! arec->nullable) { ERRH(stmt, "received a NULL for a not nullable val."); RET_ROW_DIAG(SQL_STATE_HY003, "NULL value received for non" @@ -1194,26 +1211,18 @@ static SQLRETURN copy_one_row(esodbc_stmt_st *stmt, SQLULEN pos, UJObject row) " but not supplied", i + 1); } *ind_len = SQL_NULL_DATA; - break; + continue; /* instead of break! no 'ret' processing to do. */ case UJT_String: wstr = UJReadString(obj, &len); - DBGH(stmt, "value [%zd, %d] is string: `" LWPD "`.", rowno, - i + 1, wstr); + DBGH(stmt, "value [%zd, %d] is string [%d]:`" LWPDL "`.", + rowno, i + 1, len, len, wstr); /* UJSON4C returns chars count, but 0-terminates w/o counting * the terminator */ assert(wstr[len] == 0); + /* "When character data is returned from the driver to the + * application, the driver must always null-terminate it." */ ret = copy_string(arec, irec, pos, wstr, len + /*\0*/1); - switch (ret) { - case SQL_SUCCESS_WITH_INFO: - with_info = TRUE; - SET_ROW_DIAG(rowno, i + 1); - case SQL_SUCCESS: - break; - default: - SET_ROW_DIAG(rowno, i + 1); - return ret; - } break; case UJT_Long: @@ -1222,47 +1231,43 @@ static SQLRETURN copy_one_row(esodbc_stmt_st *stmt, SQLULEN pos, UJObject row) DBGH(stmt, "value [%zd, %d] is numeric: %lld.", rowno, i + 1, ll); ret = copy_longlong(arec, irec, pos, ll); - switch (ret) { - case SQL_SUCCESS_WITH_INFO: - with_info = TRUE; - SET_ROW_DIAG(rowno, i + 1); - case SQL_SUCCESS: - break; - default: - SET_ROW_DIAG(rowno, i + 1); - return ret; - } break; case UJT_Double: dbl = UJNumericFloat(obj); - DBGH(stmt, "value [%zd, %d] is double: %f.", rowno, i + 1, dbl); + DBGH(stmt, "value [%zd, %d] is double: %f.", rowno, i + 1, + dbl); ret = copy_double(arec, irec, pos, dbl); - switch (ret) { - case SQL_SUCCESS_WITH_INFO: - with_info = TRUE; - SET_ROW_DIAG(rowno, i + 1); - case SQL_SUCCESS: - break; - default: - SET_ROW_DIAG(rowno, i + 1); - return ret; - } break; /* TODO: convert to 1/0? */ case UJT_True: case UJT_False: + boolval = UJGetType(obj) == UJT_True ? TRUE : FALSE; + DBGH(stmt, "value [%zd, %d] is boolean: %d.", rowno, i + 1, + boolval); + ret = copy_boolean(arec, irec, pos, boolval); + break; + } + + switch (ret) { + case SQL_SUCCESS_WITH_INFO: + with_info = TRUE; + SET_ROW_DIAG(rowno, i + 1); + case SQL_SUCCESS: + break; default: - ERRH(stmt, "unexpected object of type %d in row L#%zd/T#%zd.", - UJGetType(obj), stmt->rset.vrows, stmt->rset.frows); - RET_ROW_DIAG(SQL_STATE_01S01, MSG_INV_SRV_ANS, i + 1); + SET_ROW_DIAG(rowno, i + 1); + return ret; } } - if (ard->array_status_ptr) - ard->array_status_ptr[pos] = with_info ? SQL_ROW_SUCCESS_WITH_INFO : + if (ird->array_status_ptr) { + ird->array_status_ptr[pos] = with_info ? SQL_ROW_SUCCESS_WITH_INFO : SQL_ROW_SUCCESS; + DBGH(stmt, "status array @0x%p#%d set to %d.", ird->array_status_ptr, + pos, ird->array_status_ptr[pos]); + } return with_info ? SQL_SUCCESS_WITH_INFO : SQL_SUCCESS; From a8f65d7591e2ba3b0ff4db6899da60aa4bc21212 Mon Sep 17 00:00:00 2001 From: Bogdan Pintea Date: Thu, 26 Apr 2018 15:40:03 +0200 Subject: [PATCH 04/17] b/f: don't count 0-term for strings When returning strings to the application, ODBC requires to include, but don't count the 0-terminator. This fixes the counting part (it was counted before).. --- driver/queries.c | 76 ++++++++++++++++++++++++++++++------------------ 1 file changed, 48 insertions(+), 28 deletions(-) diff --git a/driver/queries.c b/driver/queries.c index 4cff6b60..e949432d 100644 --- a/driver/queries.c +++ b/driver/queries.c @@ -697,14 +697,18 @@ static size_t buff_octet_size( return max_copy; } +/* + * 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. + */ static inline void write_copied_octets(SQLLEN *octet_len_ptr, size_t copied, size_t attr_max, esodbc_metatype_et ird_mt) { 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.", copied); return; } @@ -882,10 +886,12 @@ static SQLRETURN copy_boolean(esodbc_rec_st *arec, esodbc_rec_st *irec, /* * -> SQL_C_CHAR + * Note: chars_0 param accounts for 0-term, but lenght indicated back to the + * application must not. */ static SQLRETURN wstr_to_cstr(esodbc_rec_st *arec, esodbc_rec_st *irec, void *data_ptr, SQLLEN *octet_len_ptr, - const wchar_t *wstr, size_t chars) + const wchar_t *wstr, size_t chars_0) { esodbc_state_et state = SQL_STATE_00000; esodbc_stmt_st *stmt = arec->desc->hdr.stmt; @@ -897,18 +903,18 @@ static SQLRETURN wstr_to_cstr(esodbc_rec_st *arec, esodbc_rec_st *irec, /* type is signed, driver should not allow a negative to this point. */ assert(0 <= arec->octet_length); - in_bytes = (int)buff_octet_size(chars * sizeof(*wstr), + in_bytes = (int)buff_octet_size(chars_0 * sizeof(*wstr), (size_t)arec->octet_length, stmt->max_length, sizeof(*charp), irec->meta_type, &state); /* trim the original string until it fits in output buffer, with given * length limitation */ - for (c = (int)chars; 0 < c; c --) { + for (c = (int)chars_0; 0 < c; c --) { out_bytes = WCS2U8(wstr, c, charp, in_bytes); if (out_bytes <= 0) { if (WCS2U8_BUFF_INSUFFICIENT) continue; ERRNH(stmt, "failed to convert wchar* to char* for string `" - LWPDL "`.", chars, wstr); + LWPDL "`.", chars_0, wstr); RET_HDIAGS(stmt, SQL_STATE_22018); } else { /* conversion succeeded */ @@ -928,13 +934,18 @@ static SQLRETURN wstr_to_cstr(esodbc_rec_st *arec, esodbc_rec_st *irec, DBGH(stmt, "REC@0x%p, NULL data_ptr.", arec); } - /* if length needs to be given, calculate it not truncated & converted */ + /* if length needs to be given, calculate it (not truncated) & converted */ if (octet_len_ptr) { - out_bytes = (size_t)WCS2U8(wstr, (int)chars, NULL, 0); + out_bytes = (size_t)WCS2U8(wstr, (int)chars_0, NULL, 0); if (out_bytes <= 0) { ERRNH(stmt, "failed to convert wchar* to char* for string `" - LWPDL "`.", chars, wstr); + LWPDL "`.", chars_0, wstr); RET_HDIAGS(stmt, SQL_STATE_22018); + } else { + /* chars_0 accounts for 0-terminator, so WCS2U8 will count that in + * the output as well => trim it, since we must not count it when + * indicating the lenght to the application */ + out_bytes --; } write_copied_octets(octet_len_ptr, out_bytes, stmt->max_length, irec->meta_type); @@ -949,42 +960,46 @@ static SQLRETURN wstr_to_cstr(esodbc_rec_st *arec, esodbc_rec_st *irec, /* * -> SQL_C_WCHAR + * Note: chars_0 accounts for 0-term, but lenght indicated back to the + * application must not. */ 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) + 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_bytes; + 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 * sizeof(*wstr), + 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_bytes = in_bytes / sizeof(*widep); + out_chars = in_bytes / sizeof(*widep); /* if buffer too small to accomodate original, 0-term it */ - if (widep[out_bytes - 1]) { - widep[out_bytes - 1] = 0; + 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 bytes: `" LWPD "`.", - arec, data_ptr, out_bytes, widep); + 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); } - - write_copied_octets(octet_len_ptr, chars * sizeof(*wstr), stmt->max_length, - irec->meta_type); - + + /* original lenght 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; @@ -1074,10 +1089,15 @@ static SQLRETURN wstr_to_date(esodbc_rec_st *arec, esodbc_rec_st *irec, } /* - * wstr: is 0-terminated, terminator is counted in 'chars'. + * wstr: is 0-terminated and terminator is counted in 'chars_0'. + * However: "[w]hen C strings are used to hold character data, the + * null-termination character is not considered to be part of the data and is + * not counted as part of its byte length." + * "If the data was converted to a variable-length data type, such as + * character or binary [...][i]t then null-terminates the data." */ static SQLRETURN copy_string(esodbc_rec_st *arec, esodbc_rec_st *irec, - SQLULEN pos, const wchar_t *wstr, size_t chars) + SQLULEN pos, const wchar_t *wstr, size_t chars_0) { esodbc_stmt_st *stmt; void *data_ptr; @@ -1101,16 +1121,16 @@ static SQLRETURN copy_string(esodbc_rec_st *arec, esodbc_rec_st *irec, switch (target_type) { case SQL_C_CHAR: return wstr_to_cstr(arec, irec, data_ptr, octet_len_ptr, - wstr, chars); + wstr, chars_0); case SQL_C_WCHAR: return wstr_to_wstr(arec, irec, data_ptr, octet_len_ptr, - wstr, chars); + wstr, chars_0); case SQL_C_TYPE_TIMESTAMP: return wstr_to_timestamp(arec, irec, data_ptr, octet_len_ptr, - wstr, chars); + wstr, chars_0); case SQL_C_TYPE_DATE: return wstr_to_date(arec, irec, data_ptr, octet_len_ptr, - wstr, chars); + wstr, chars_0); default: // FIXME: convert data From a1816d32b3cb34bd2999b88bd7eb87a93bdd1043 Mon Sep 17 00:00:00 2001 From: Bogdan Pintea Date: Thu, 26 Apr 2018 15:47:41 +0200 Subject: [PATCH 05/17] use the cached data types, remove them from recs make use of cached data types when returning info to application, strip them record structure. --- driver/handles.c | 116 +++++++++++++++++++++++++++-------------------- driver/handles.h | 20 +++++++- driver/queries.c | 51 ++++++++++++++------- 3 files changed, 119 insertions(+), 68 deletions(-) diff --git a/driver/handles.c b/driver/handles.c index b0d9e712..fd5582d8 100644 --- a/driver/handles.c +++ b/driver/handles.c @@ -21,13 +21,9 @@ static void free_rec_fields(esodbc_rec_st *rec) &rec->base_table_name, &rec->catalog_name, &rec->label, - &rec->literal_prefix, - &rec->literal_suffix, - &rec->local_type_name, &rec->name, &rec->schema_name, &rec->table_name, - &rec->type_name, }; for (i = 0; i < sizeof(wptr)/sizeof(wptr[0]); i ++) { DBGH(rec->desc, "freeing field #%d = 0x%p.", i, *wptr[i]); @@ -114,50 +110,43 @@ void dump_record(esodbc_rec_st *rec) #define DUMP_FIELD(_strp, _name, _desc) \ DBGH(rec->desc, "0x%p->%s: `" _desc "`.", _strp, # _name, (_strp)->_name) + // TODO: add dumping for the es_type? + DUMP_FIELD(rec, desc, "0x%p"); DUMP_FIELD(rec, meta_type, "%d"); - + DUMP_FIELD(rec, concise_type, "%d"); DUMP_FIELD(rec, type, "%d"); DUMP_FIELD(rec, datetime_interval_code, "%d"); - + DUMP_FIELD(rec, data_ptr, "0x%p"); - + DUMP_FIELD(rec, base_column_name, LWPD); DUMP_FIELD(rec, base_table_name, LWPD); DUMP_FIELD(rec, catalog_name, LWPD); DUMP_FIELD(rec, label, LWPD); - DUMP_FIELD(rec, literal_prefix, LWPD); - DUMP_FIELD(rec, literal_suffix, LWPD); - DUMP_FIELD(rec, local_type_name, LWPD); DUMP_FIELD(rec, name, LWPD); DUMP_FIELD(rec, schema_name, LWPD); DUMP_FIELD(rec, table_name, LWPD); - DUMP_FIELD(rec, type_name, LWPD); DUMP_FIELD(rec, indicator_ptr, "0x%p"); DUMP_FIELD(rec, octet_length_ptr, "0x%p"); - + DUMP_FIELD(rec, display_size, "%lld"); DUMP_FIELD(rec, octet_length, "%lld"); - + DUMP_FIELD(rec, length, "%llu"); - - DUMP_FIELD(rec, auto_unique_value, "%d"); - DUMP_FIELD(rec, case_sensitive, "%d"); + DUMP_FIELD(rec, datetime_interval_precision, "%d"); DUMP_FIELD(rec, num_prec_radix, "%d"); - - DUMP_FIELD(rec, fixed_prec_scale, "%d"); - DUMP_FIELD(rec, nullable, "%d"); + DUMP_FIELD(rec, parameter_type, "%d"); DUMP_FIELD(rec, precision, "%d"); DUMP_FIELD(rec, rowver, "%d"); DUMP_FIELD(rec, scale, "%d"); - DUMP_FIELD(rec, searchable, "%d"); DUMP_FIELD(rec, unnamed, "%d"); - DUMP_FIELD(rec, usigned, "%d"); DUMP_FIELD(rec, updatable, "%d"); + #undef DUMP_FIELD } @@ -1083,6 +1072,11 @@ static esodbc_state_et check_buff(SQLSMALLINT field_id, SQLPOINTER buff, * SQL_DESC_TYPE rw rw r rw */ // TODO: individual tests just for this +/* + * Check access to record headers/fields. + * IxD must have es_type pointer set and it's value is not checked at + * header/field access time -> only this function guards against NP deref'ing. + */ static BOOL check_access(desc_type_et desc_type, SQLSMALLINT field_id, char mode /* O_RDONLY | O_RDWR */) { @@ -1102,7 +1096,7 @@ static BOOL check_access(desc_type_et desc_type, SQLSMALLINT field_id, ret = TRUE; break; case SQL_DESC_ROWS_PROCESSED_PTR: - ret = desc_type == DESC_TYPE_IRD || desc_type == DESC_TYPE_IPD; + ret = DESC_TYPE_IS_IMPLEMENTATION(desc_type); break; case SQL_DESC_PARAMETER_TYPE: ret = desc_type == DESC_TYPE_IPD; @@ -1114,7 +1108,7 @@ static BOOL check_access(desc_type_et desc_type, SQLSMALLINT field_id, case SQL_DESC_DATA_PTR: case SQL_DESC_INDICATOR_PTR: case SQL_DESC_OCTET_LENGTH_PTR: - ret = desc_type == DESC_TYPE_ARD || desc_type == DESC_TYPE_APD; + ret = DESC_TYPE_IS_APPLICATION(desc_type); break; case SQL_DESC_AUTO_UNIQUE_VALUE: @@ -1139,8 +1133,7 @@ static BOOL check_access(desc_type_et desc_type, SQLSMALLINT field_id, case SQL_DESC_ROWVER: case SQL_DESC_UNSIGNED: case SQL_DESC_TYPE_NAME: - ret = mode == O_RDONLY && - (desc_type == DESC_TYPE_IRD || desc_type == DESC_TYPE_IPD); + ret = mode == O_RDONLY && DESC_TYPE_IS_IMPLEMENTATION(desc_type); break; case SQL_DESC_NAME: @@ -1422,6 +1415,7 @@ SQLRETURN EsSQLGetDescFieldW( RecNumber, rec); } + ASSERT_IXD_HAS_ES_TYPE(rec); /* record fields */ switch (FieldIdentifier) { @@ -1437,13 +1431,21 @@ SQLRETURN EsSQLGetDescFieldW( 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_LITERAL_PREFIX: wptr = rec->literal_prefix; break; - case SQL_DESC_LITERAL_SUFFIX: wptr = rec->literal_suffix; break; - case SQL_DESC_LOCAL_TYPE_NAME: wptr = rec->local_type_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_TYPE_NAME: wptr = rec->type_name; break; + case SQL_DESC_LITERAL_PREFIX: + wptr = rec->es_type->literal_prefix.str; + break; + case SQL_DESC_LITERAL_SUFFIX: + wptr = rec->es_type->literal_suffix.str; + break; + case SQL_DESC_LOCAL_TYPE_NAME: + wptr = rec->es_type->local_type_name.str; + break; + case SQL_DESC_TYPE_NAME: + wptr = rec->es_type->type_name.str; + break; } while (0); if (! wptr) { *StringLengthPtr = 0; @@ -1493,15 +1495,17 @@ SQLRETURN EsSQLGetDescFieldW( case SQL_DESC_DATETIME_INTERVAL_CODE: word = rec->datetime_interval_code; break; - case SQL_DESC_FIXED_PREC_SCALE: word = rec->fixed_prec_scale; break; - case SQL_DESC_NULLABLE: word = rec->nullable; break; case SQL_DESC_PARAMETER_TYPE: word = rec->parameter_type; break; case SQL_DESC_PRECISION: word = rec->precision; break; case SQL_DESC_ROWVER: word = rec->rowver; break; case SQL_DESC_SCALE: word = rec->scale; break; - case SQL_DESC_SEARCHABLE: word = rec->searchable; break; case SQL_DESC_UNNAMED: word = rec->unnamed; break; - case SQL_DESC_UNSIGNED: word = rec->usigned; break; + case SQL_DESC_FIXED_PREC_SCALE: + word = rec->es_type->fixed_prec_scale; + break; + case SQL_DESC_NULLABLE: word = rec->es_type->nullable; break; + case SQL_DESC_SEARCHABLE: word = rec->es_type->searchable; break; + case SQL_DESC_UNSIGNED: word = rec->es_type->unsigned_attribute; break; case SQL_DESC_UPDATABLE: word = rec->updatable; break; } while (0); *(SQLSMALLINT *)ValuePtr = word; @@ -1511,11 +1515,27 @@ SQLRETURN EsSQLGetDescFieldW( /* */ do { - case SQL_DESC_AUTO_UNIQUE_VALUE: intgr = rec->auto_unique_value; break; - case SQL_DESC_CASE_SENSITIVE: intgr = rec->case_sensitive; break; - case SQL_DESC_DATETIME_INTERVAL_PRECISION: - intgr = rec->datetime_interval_precision; break; - case SQL_DESC_NUM_PREC_RADIX: intgr = rec->num_prec_radix; break; + case SQL_DESC_DATETIME_INTERVAL_PRECISION: + if (DESC_TYPE_IS_IMPLEMENTATION(rec->desc->type)) { + /* not used with ES (so far), as no interval types are sup. */ + intgr = rec->es_type->interval_precision; + } else { + intgr = rec->datetime_interval_precision; + } + break; + case SQL_DESC_NUM_PREC_RADIX: + if DESC_TYPE_IS_IMPLEMENTATION(rec->desc->type) { + intgr = rec->es_type->num_prec_radix; + } else { + intgr = rec->num_prec_radix; + } + break; + case SQL_DESC_AUTO_UNIQUE_VALUE: + intgr = rec->es_type->auto_unique_value; + break; + case SQL_DESC_CASE_SENSITIVE: + intgr = rec->es_type->case_sensitive; + break; } while (0); *(SQLINTEGER *)ValuePtr = intgr; DBGH(desc, "returning record field %d as %d.", FieldIdentifier, @@ -2182,12 +2202,9 @@ SQLRETURN EsSQLSetDescFieldW( 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_LITERAL_PREFIX: wptrp = &rec->literal_prefix; break; - case SQL_DESC_LITERAL_SUFFIX: wptrp = &rec->literal_suffix; break; - case SQL_DESC_LOCAL_TYPE_NAME: wptrp = &rec->local_type_name; 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_TYPE_NAME: wptrp = &rec->type_name; break; } while (0); DBGH(desc, "setting SQLWCHAR field %d to 0x%p(`"LWPD"`).", FieldIdentifier, ValuePtr, @@ -2247,13 +2264,10 @@ SQLRETURN EsSQLSetDescFieldW( do { case SQL_DESC_DATETIME_INTERVAL_CODE: wordp = &rec->datetime_interval_code; break; - case SQL_DESC_FIXED_PREC_SCALE: wordp = &rec->fixed_prec_scale; break; - case SQL_DESC_NULLABLE: wordp = &rec->nullable; break; case SQL_DESC_PARAMETER_TYPE: wordp = &rec->parameter_type; break; case SQL_DESC_PRECISION: wordp = &rec->precision; break; case SQL_DESC_ROWVER: wordp = &rec->rowver; break; case SQL_DESC_SCALE: wordp = &rec->scale; break; - case SQL_DESC_SEARCHABLE: wordp = &rec->searchable; break; case SQL_DESC_UNNAMED: /* only driver can set this value */ if ((SQLSMALLINT)(intptr_t)ValuePtr == SQL_NAMED) { @@ -2263,7 +2277,7 @@ SQLRETURN EsSQLSetDescFieldW( } wordp = &rec->unnamed; break; - case SQL_DESC_UNSIGNED: wordp = &rec->usigned; break; + /* R/O field: fixed_prec_scale, nullable, searchable, unsigned */ case SQL_DESC_UPDATABLE: wordp = &rec->updatable; break; } while (0); DBGH(desc, "setting record field %d to %d.", FieldIdentifier, @@ -2273,11 +2287,13 @@ SQLRETURN EsSQLSetDescFieldW( /* */ do { - case SQL_DESC_AUTO_UNIQUE_VALUE: intp = &rec->auto_unique_value; break; - case SQL_DESC_CASE_SENSITIVE: intp = &rec->case_sensitive; break; + /* R/O field: auto_unique_value, case_sensitive */ case SQL_DESC_DATETIME_INTERVAL_PRECISION: - intp = &rec->datetime_interval_precision; break; - case SQL_DESC_NUM_PREC_RADIX: intp = &rec->num_prec_radix; break; + intp = &rec->datetime_interval_precision; + break; + case SQL_DESC_NUM_PREC_RADIX: + intp = &rec->num_prec_radix; + break; } while (0); DBGH(desc, "returning record field %d as %d.", FieldIdentifier, (SQLINTEGER)(intptr_t)ValuePtr); diff --git a/driver/handles.h b/driver/handles.h index b704cf0b..dc71821c 100644 --- a/driver/handles.h +++ b/driver/handles.h @@ -122,7 +122,7 @@ typedef struct struct_dbc { * might be a directory" */ SQLWCHAR *catalog; // TODO: statement list? - + /* options */ SQLULEN metadata_id; // default: SQL_FALSE SQLULEN async_enable; // default: SQL_ASYNC_ENABLE_OFF @@ -204,6 +204,20 @@ typedef enum { DESC_TYPE_IPD, } desc_type_et; +/* type is for an application descriptor */ +#define DESC_TYPE_IS_APPLICATION(_dtype) \ + (_dtype == DESC_TYPE_ARD || _dtype == DESC_TYPE_APD) +/* type is for an implementation descriptor */ +#define DESC_TYPE_IS_IMPLEMENTATION(_dtype) \ + (_dtype == DESC_TYPE_IRD || _dtype == DESC_TYPE_IPD) +/* type is for a record descriptor */ +#define DESC_TYPE_IS_RECORD(_dtype) \ + (_dtype == DESC_TYPE_ARD || _dtype == DESC_TYPE_IRD) +/* type is for a parameter descriptor */ +#define DESC_TYPE_IS_PARAMETER(_dtype) \ + (_dtype == DESC_TYPE_APD || _dtype == DESC_TYPE_IPD) + + typedef struct struct_desc { esodbc_hhdr_st hdr; @@ -228,6 +242,10 @@ typedef struct struct_desc { esodbc_rec_st *recs; } esodbc_desc_st; +/* the ES/SQL type must be set for implementation descriptor records */ +#define ASSERT_IXD_HAS_ES_TYPE(_rec) \ + assert(DESC_TYPE_IS_IMPLEMENTATION(_rec->desc->type) && _rec->es_type) + typedef struct struct_resultset { long code; /* code of last response */ diff --git a/driver/queries.c b/driver/queries.c index e949432d..ac13eef3 100644 --- a/driver/queries.c +++ b/driver/queries.c @@ -1745,8 +1745,9 @@ SQLRETURN EsSQLDescribeColW( RET_HDIAG(stmt, SQL_STATE_HY090, "no column decimal digits buffer provided", 0); } + ASSERT_IXD_HAS_ES_TYPE(rec); /* TODO: this would be available in SQLColumns resultset. */ - *pfNullable = rec->nullable; + *pfNullable = rec->es_type->nullable; DBGH(stmt, "col #%d nullable=%d.", icol, *pfNullable); return SQL_SUCCESS; @@ -1795,25 +1796,29 @@ SQLRETURN EsSQLColAttributeW( /* TODO: if implementing bookmarks */ RET_HDIAGS(stmt, SQL_STATE_HYC00); } - + rec = get_record(ird, iCol, FALSE); if (! rec) { ERRH(stmt, "no record for columns #%d.", iCol); RET_HDIAGS(stmt, SQL_STATE_07009); } + ASSERT_IXD_HAS_ES_TYPE(rec); + switch (iField) { /* SQLSMALLINT */ do { case SQL_DESC_CONCISE_TYPE: sint = rec->concise_type; break; case SQL_DESC_TYPE: sint = rec->type; break; - case SQL_DESC_FIXED_PREC_SCALE: sint = rec->fixed_prec_scale; break; - case SQL_DESC_NULLABLE: sint = rec->nullable; break; case SQL_DESC_PRECISION: sint = rec->precision; break; case SQL_DESC_SCALE: sint = rec->scale; break; - case SQL_DESC_SEARCHABLE: sint = rec->searchable; break; case SQL_DESC_UNNAMED: sint = rec->unnamed; break; - case SQL_DESC_UNSIGNED: sint = rec->usigned; break; + case SQL_DESC_FIXED_PREC_SCALE: + sint = rec->es_type->fixed_prec_scale; + break; + case SQL_DESC_NULLABLE: sint = rec->es_type->nullable; break; + case SQL_DESC_SEARCHABLE: sint = rec->es_type->searchable; break; + case SQL_DESC_UNSIGNED: sint = rec->es_type->unsigned_attribute; break; case SQL_DESC_UPDATABLE: sint = rec->updatable; break; } while (0); PNUMATTR_ASSIGN(SQLSMALLINT, sint); @@ -1825,26 +1830,34 @@ SQLRETURN EsSQLColAttributeW( 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_LITERAL_PREFIX: wptr = rec->literal_prefix; break; - case SQL_DESC_LITERAL_SUFFIX: wptr = rec->literal_suffix; break; - case SQL_DESC_LOCAL_TYPE_NAME: wptr = rec->type_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_TYPE_NAME: wptr = rec->type_name; break; + case SQL_DESC_LITERAL_PREFIX: + wptr = rec->es_type->literal_prefix.str; + break; + case SQL_DESC_LITERAL_SUFFIX: + wptr = rec->es_type->literal_suffix.str; + break; + case SQL_DESC_LOCAL_TYPE_NAME: + wptr = rec->es_type->type_name.str; + break; + case SQL_DESC_TYPE_NAME: + wptr = rec->es_type->type_name.str; + break; } while (0); if (! wptr) { - //BUG -- TODO: re-eval, once type handling is decided. - ERRH(stmt, "IRD@0x%p record field type %d not initialized.", + //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, pcbCharAttr, wptr, cbDescMax, - pcbCharAttr); + return write_wptr(&stmt->hdr.diag, pcbCharAttr, wptr, + cbDescMax, pcbCharAttr); } break; - + /* SQLLEN */ do { case SQL_DESC_DISPLAY_SIZE: len = rec->display_size; break; @@ -1860,8 +1873,12 @@ SQLRETURN EsSQLColAttributeW( /* SQLINTEGER */ do { - case SQL_DESC_AUTO_UNIQUE_VALUE: iint = rec->auto_unique_value; break; - case SQL_DESC_CASE_SENSITIVE: iint = rec->case_sensitive; break; + case SQL_DESC_AUTO_UNIQUE_VALUE: + iint = rec->es_type->auto_unique_value; + break; + case SQL_DESC_CASE_SENSITIVE: + iint = rec->es_type->case_sensitive; + break; case SQL_DESC_NUM_PREC_RADIX: iint = rec->num_prec_radix; break; } while (0); PNUMATTR_ASSIGN(SQLINTEGER, iint); From 7a2b692242078c7b680c1b90c8c3bc60f93a2195 Mon Sep 17 00:00:00 2001 From: Bogdan Pintea Date: Thu, 26 Apr 2018 15:52:15 +0200 Subject: [PATCH 06/17] reformatting: remove trailing white spaces in code --- driver/connect.c | 11 +++++------ driver/handles.c | 13 ++++++------- driver/queries.c | 26 +++++++++++++------------- 3 files changed, 24 insertions(+), 26 deletions(-) diff --git a/driver/connect.c b/driver/connect.c index be9fd44b..c28a9d74 100644 --- a/driver/connect.c +++ b/driver/connect.c @@ -709,7 +709,7 @@ static BOOL parse_token(BOOL is_value, SQLWCHAR **pos, SQLWCHAR *end, } (*pos)++; break; - + default: (*pos)++; } @@ -1038,16 +1038,16 @@ static inline void assign_defaults(config_attrs_st *attrs) attrs->follow = MK_WSTR(ESODBC_DEF_FOLLOW); /* no default packing */ - + if (! attrs->packing.cnt) attrs->packing = MK_WSTR(ESODBC_DEF_PACKING); if (! attrs->max_fetch_size.cnt) attrs->max_fetch_size = MK_WSTR(ESODBC_DEF_FETCH_SIZE); if (! attrs->max_body_size.cnt) attrs->max_body_size = MK_WSTR(ESODBC_DEF_MAX_BODY_SIZE_MB); - + /* default: no trace file */ - + if (! attrs->trace_level.cnt) attrs->trace_level = MK_WSTR(ESODBC_DEF_TRACE_LEVEL); } @@ -2009,7 +2009,6 @@ SQLRETURN EsSQLGetConnectAttrW( if (StringLengthPtr); *StringLengthPtr = (SQLINTEGER)used; return ret; - #endif //0 break; @@ -2057,7 +2056,7 @@ SQLRETURN EsSQLGetConnectAttrW( ERRH(dbc, "unknown Attribute type %d.", Attribute); RET_HDIAGS(DBCH(ConnectionHandle), SQL_STATE_HY092); } - + return SQL_SUCCESS; } diff --git a/driver/handles.c b/driver/handles.c index fd5582d8..717e96dc 100644 --- a/driver/handles.c +++ b/driver/handles.c @@ -332,7 +332,6 @@ SQLRETURN EsSQLFreeHandle(SQLSMALLINT HandleType, SQLHANDLE Handle) free(stmt); break; - /* "When an explicitly allocated descriptor is freed, all statement * handles to which the freed descriptor applied automatically revert * to the descriptors implicitly allocated for them." */ @@ -715,7 +714,7 @@ SQLRETURN EsSQLSetStmtAttrW( stmt->ard = (esodbc_desc_st *)ValuePtr; // FIXME: bind: re-init FIXME; - } + } case SQL_ATTR_APP_PARAM_DESC: // FIXME: same logic for ARD as above (part of params passing) FIXME; @@ -1546,7 +1545,7 @@ SQLRETURN EsSQLGetDescFieldW( ERRH(desc, "unknown FieldIdentifier: %d.", FieldIdentifier); RET_HDIAGS(desc, SQL_STATE_HY091); } - + return SQL_SUCCESS; } @@ -1837,7 +1836,7 @@ static esodbc_metatype_et sqlctype_to_meta(SQLSMALLINT concise) case SQL_C_INTERVAL_HOUR_TO_SECOND: case SQL_C_INTERVAL_MINUTE_TO_SECOND: return METATYPE_INTERVAL_WSEC; - + case SQL_C_BIT: return METATYPE_BIT; @@ -2010,13 +2009,13 @@ SQLRETURN EsSQLSetDescFieldW( * however, an application can do so to force a consistency check of * IPD fields." * TODO: the above won't work with the generic check implementation: - * is it worth hacking an exception here? (since IPD/.data_ptr is + * is it worth hacking an exception here? (since IPD/.data_ptr is * marked RO) */ ERRH(desc, "field access check failed: not defined or RO for " "desciptor."); RET_HDIAGS(desc, SQL_STATE_HY091); } - + state = check_buff(FieldIdentifier, ValuePtr, BufferLength, FALSE); if (state != SQL_STATE_00000) { ERRH(desc, "buffer/~ length check failed (%d).", state); @@ -2304,7 +2303,7 @@ SQLRETURN EsSQLSetDescFieldW( ERRH(desc, "unknown FieldIdentifier: %d.", FieldIdentifier); RET_HDIAGS(desc, SQL_STATE_HY091); } - + return SQL_SUCCESS; } diff --git a/driver/queries.c b/driver/queries.c index ac13eef3..a5b9a93e 100644 --- a/driver/queries.c +++ b/driver/queries.c @@ -797,7 +797,7 @@ static SQLRETURN copy_longlong(esodbc_rec_st *arec, esodbc_rec_st *irec, write_copied_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), @@ -1375,7 +1375,7 @@ SQLRETURN EsSQLFetch(SQLHSTMT StatementHandle) DBGH(stmt, "(`%.*s`); cursor @ %zd / %zd.", stmt->sqllen, stmt->u8sql, stmt->rset.vrows, stmt->rset.nrows); - + DBGH(stmt, "rowset max size: %d.", ard->array_size); errors = 0; /* for all rows in rowset/array, iterate over rows in current resultset */ @@ -1423,7 +1423,7 @@ SQLRETURN EsSQLFetch(SQLHSTMT StatementHandle) DBGH(stmt, "no data %sto return.", stmt->rset.vrows ? "left ": ""); return SQL_NO_DATA; } - + if (errors && i <= errors) { ERRH(stmt, "processing failed for all rows [%d].", errors); return SQL_ERROR; @@ -1541,10 +1541,10 @@ SQLRETURN EsSQLPrepareW } DBGH(stmt, "preparing `" LWPDL "` [%d]", cchSqlStr, szSqlStr, cchSqlStr); - + ret = EsSQLFreeStmt(stmt, ESODBC_SQL_CLOSE); assert(SQL_SUCCEEDED(ret)); /* can't return error */ - + return attach_sql(stmt, szSqlStr, cchSqlStr); } @@ -1567,7 +1567,7 @@ SQLRETURN EsSQLExecute(SQLHSTMT hstmt) DBGH(stmt, "executing `%.*s` (%zd)", stmt->sqllen, stmt->u8sql, stmt->sqllen); - + return post_statement(stmt); } @@ -1601,7 +1601,7 @@ SQLRETURN EsSQLExecDirectW } DBGH(stmt, "directly executing SQL: `" LWPDL "` [%d].", cchSqlStr, szSqlStr, cchSqlStr); - + ret = EsSQLFreeStmt(stmt, ESODBC_SQL_CLOSE); assert(SQL_SUCCEEDED(ret)); /* can't return error */ @@ -1626,7 +1626,7 @@ static inline SQLULEN get_col_size(esodbc_rec_st *rec) case METATYPE_EXACT_NUMERIC: case METATYPE_FLOAT_NUMERIC: return rec->precision; - + case METATYPE_STRING: case METATYPE_BIN: case METATYPE_DATETIME: @@ -1645,7 +1645,7 @@ static inline SQLSMALLINT get_col_decdigits(esodbc_rec_st *rec) case METATYPE_DATETIME: case METATYPE_INTERVAL_WSEC: return rec->precision; - + case METATYPE_EXACT_NUMERIC: return rec->scale; } @@ -1685,7 +1685,7 @@ SQLRETURN EsSQLDescribeColW( /* TODO: if implementing bookmarks */ RET_HDIAGS(stmt, SQL_STATE_HYC00); } - + rec = get_record(stmt->ird, icol, FALSE); if (! rec) { ERRH(stmt, "no record for columns #%d.", icol); @@ -1865,7 +1865,7 @@ SQLRETURN EsSQLColAttributeW( } while (0); PNUMATTR_ASSIGN(SQLLEN, len); break; - + /* SQLULEN */ case SQL_DESC_LENGTH: PNUMATTR_ASSIGN(SQLULEN, rec->length); @@ -1901,12 +1901,12 @@ SQLRETURN EsSQLColAttributeW( SQLRETURN EsSQLRowCount(_In_ SQLHSTMT StatementHandle, _Out_ SQLLEN* RowCount) { esodbc_stmt_st *stmt = STMH(StatementHandle); - + if (! STMT_HAS_RESULTSET(stmt)) { ERRH(stmt, "no resultset available on statement."); RET_HDIAGS(stmt, SQL_STATE_HY010); } - + DBGH(stmt, "current resultset rows count: %zd.", stmt->rset.nrows); *RowCount = (SQLLEN)stmt->rset.nrows; From e9c2dee277fe7db6a34972148f266d6299671088 Mon Sep 17 00:00:00 2001 From: Bogdan Pintea Date: Thu, 26 Apr 2018 15:56:44 +0200 Subject: [PATCH 07/17] reflect repo name change in README s/x-pack-odbc/elasticsearch-sql-odbc/ --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 742aaadc..62e7dc24 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ them in driver's libs directory, where the build script expects them by default: ``` somedirectory\ - |_x-pack-odbc + |_elasticsearch-sql-odbc |_README.md |_CMakeLists.txt |_build.bat From 5b859b02647fd4b36e2eca993d5d5565d1378870 Mon Sep 17 00:00:00 2001 From: Bogdan Pintea Date: Fri, 27 Apr 2018 17:59:40 +0200 Subject: [PATCH 08/17] .scale and .precision also read from cached type Use cached ES return type for these record members, in case they belong to an IRD descriptor. --- driver/connect.c | 9 ++++++++- driver/handles.c | 50 ++++++++++++++---------------------------------- driver/odbc.c | 4 ++++ driver/queries.c | 26 ++++++++++++++++--------- 4 files changed, 43 insertions(+), 46 deletions(-) diff --git a/driver/connect.c b/driver/connect.c index c28a9d74..6c234ead 100644 --- a/driver/connect.c +++ b/driver/connect.c @@ -1535,11 +1535,18 @@ static BOOL load_es_types(esodbc_dbc_st *dbc) ES_TYPES_COPY_INT(num_prec_radix); ES_TYPES_COPY_INT(interval_precision); - /* apply any customizations */ + /* apply any needed fixes */ /* fix SQL_DATA_TYPE columns TODO: GH issue */ types[i].sql_data_type = types[i].data_type; + /* warn if scales extremes are different */ + if (types[i].maximum_scale != types[i].minimum_scale) { + ERRH(dbc, "type `" LWPDL "` returned with non-equal max/min " + "scale: %d/%d.", LWSTR(&types[i].type_name), + types[i].maximum_scale, types[i].minimum_scale); + } + /* resolve ES type to SQL C type */ types[i].sql_c_type = type_elastic2csql(&types[i].type_name); if (types[i].sql_c_type == SQL_UNKNOWN_TYPE) { diff --git a/driver/handles.c b/driver/handles.c index 717e96dc..e8da7a76 100644 --- a/driver/handles.c +++ b/driver/handles.c @@ -1495,10 +1495,22 @@ SQLRETURN EsSQLGetDescFieldW( word = rec->datetime_interval_code; break; case SQL_DESC_PARAMETER_TYPE: word = rec->parameter_type; break; - case SQL_DESC_PRECISION: word = rec->precision; break; case SQL_DESC_ROWVER: word = rec->rowver; break; - case SQL_DESC_SCALE: word = rec->scale; break; case SQL_DESC_UNNAMED: word = rec->unnamed; break; + case SQL_DESC_PRECISION: + if (rec->desc->type == DESC_TYPE_IRD) { + word = (SQLSMALLINT)rec->es_type->column_size; + } else { + word = rec->precision; + } + break; + case SQL_DESC_SCALE: + if (rec->desc->type == DESC_TYPE_IRD) { + word = rec->es_type->maximum_scale; + } else { + word = rec->scale; + } + break; case SQL_DESC_FIXED_PREC_SCALE: word = rec->es_type->fixed_prec_scale; break; @@ -1869,8 +1881,6 @@ static BOOL consistency_check(esodbc_desc_st *desc, esodbc_rec_st *rec) return FALSE; } - /* TODO: use the get_rec_size/get_rec_decdigits() (below)? */ - //if (rec->meta_type == METATYPE_NUMERIC) { if (0) { // FIXME /* TODO: actually check validity of precision/scale for data type */ @@ -1932,38 +1942,6 @@ esodbc_metatype_et concise_to_meta(SQLSMALLINT concise_type, return METATYPE_UNKNOWN; } -/* - * https://docs.microsoft.com/en-us/sql/odbc/reference/appendixes/column-size - */ -SQLULEN get_rec_size(esodbc_rec_st *rec) -{ - if (rec->meta_type == METATYPE_EXACT_NUMERIC || - rec->meta_type == METATYPE_FLOAT_NUMERIC) { - if (rec->precision < 0) { - BUG("precision can't be negative."); - return 0; - } - return (SQLULEN)rec->precision; - } else { - return rec->length; - } -} - -SQLULEN get_rec_decdigits(esodbc_rec_st *rec) -{ - switch (rec->meta_type) { - case METATYPE_DATETIME: - case METATYPE_INTERVAL_WSEC: - return rec->precision; - case METATYPE_EXACT_NUMERIC: - return rec->scale; - } - /* 0 to be returned for unknown case: - * https://docs.microsoft.com/en-us/sql/odbc/reference/syntax/sqldescribecol-function#syntax - */ - return 0; -} - /* * "If an application calls SQLSetDescField to set any field other than * SQL_DESC_COUNT or the deferred fields SQL_DESC_DATA_PTR, diff --git a/driver/odbc.c b/driver/odbc.c index 41c9faee..78f418cc 100644 --- a/driver/odbc.c +++ b/driver/odbc.c @@ -491,6 +491,9 @@ SQLRETURN SQL_API SQLPrepareW #if WITH_EMPTY /* * https://docs.microsoft.com/en-us/sql/odbc/reference/develop-app/sending-long-data + * Note: must use EsSQLSetDescFieldW() for param data-type setting, to call + * set_defaults_from_type(), to meet the "Other fields implicitly set" + * requirements from the page linked in set_defaults_from_type() comments. */ SQLRETURN SQL_API SQLBindParameter( SQLHSTMT hstmt, @@ -613,6 +616,7 @@ SQLRETURN SQL_API SQLNativeSqlW * with information about the parameter, including the data type, precision, * scale, and other characteristics. This is equivalent to supporting * SQLDescribeParam." + * Note: see EsSQLDescribeColW() for size & dec digits impl. */ SQLRETURN SQL_API SQLDescribeParam( SQLHSTMT hstmt, diff --git a/driver/queries.c b/driver/queries.c index a5b9a93e..015129a3 100644 --- a/driver/queries.c +++ b/driver/queries.c @@ -1260,7 +1260,6 @@ static SQLRETURN copy_one_row(esodbc_stmt_st *stmt, SQLULEN pos, UJObject row) ret = copy_double(arec, irec, pos, dbl); break; - /* TODO: convert to 1/0? */ case UJT_True: case UJT_False: boolval = UJGetType(obj) == UJT_True ? TRUE : FALSE; @@ -1625,7 +1624,7 @@ static inline SQLULEN get_col_size(esodbc_rec_st *rec) switch (rec->meta_type) { case METATYPE_EXACT_NUMERIC: case METATYPE_FLOAT_NUMERIC: - return rec->precision; + return rec->es_type->column_size; case METATYPE_STRING: case METATYPE_BIN: @@ -1644,11 +1643,14 @@ static inline SQLSMALLINT get_col_decdigits(esodbc_rec_st *rec) switch (rec->meta_type) { case METATYPE_DATETIME: case METATYPE_INTERVAL_WSEC: - return rec->precision; + return (SQLSMALLINT)rec->es_type->column_size; case METATYPE_EXACT_NUMERIC: - return rec->scale; + return rec->es_type->maximum_scale; } + /* 0 to be returned for unknown case: + * https://docs.microsoft.com/en-us/sql/odbc/reference/syntax/sqldescribecol-function#syntax + */ return 0; } @@ -1810,16 +1812,20 @@ SQLRETURN EsSQLColAttributeW( do { case SQL_DESC_CONCISE_TYPE: sint = rec->concise_type; break; case SQL_DESC_TYPE: sint = rec->type; break; - case SQL_DESC_PRECISION: sint = rec->precision; break; - case SQL_DESC_SCALE: sint = rec->scale; break; case SQL_DESC_UNNAMED: sint = rec->unnamed; break; - case SQL_DESC_FIXED_PREC_SCALE: - sint = rec->es_type->fixed_prec_scale; - break; case SQL_DESC_NULLABLE: sint = rec->es_type->nullable; break; case SQL_DESC_SEARCHABLE: sint = rec->es_type->searchable; break; case SQL_DESC_UNSIGNED: sint = rec->es_type->unsigned_attribute; break; case SQL_DESC_UPDATABLE: sint = rec->updatable; break; + case SQL_DESC_PRECISION: + sint = rec->es_type->fixed_prec_scale; + break; + case SQL_DESC_SCALE: + sint = rec->es_type->maximum_scale; + break; + case SQL_DESC_FIXED_PREC_SCALE: + sint = rec->es_type->fixed_prec_scale; + break; } while (0); PNUMATTR_ASSIGN(SQLSMALLINT, sint); break; @@ -1868,6 +1874,8 @@ SQLRETURN EsSQLColAttributeW( /* SQLULEN */ case SQL_DESC_LENGTH: + /* "This information is returned from the SQL_DESC_LENGTH record + * field of the IRD." */ PNUMATTR_ASSIGN(SQLULEN, rec->length); break; From b016e71e1c9c4de72db3779f7fa10db56ab59b83 Mon Sep 17 00:00:00 2001 From: Bogdan Pintea Date: Fri, 27 Apr 2018 22:02:08 +0200 Subject: [PATCH 09/17] switch IRD to using pure SQL types Use the values provided by server in cached ES/SQL types to populate the IRD type members (away from SQL C types). This cleans up the implementation a bit and will allows copying of the descriptors later, if needed. --- driver/connect.c | 17 +++++++---- driver/handles.c | 26 +++++++++++------ driver/handles.h | 52 ++++++++++++++++++++------------- driver/queries.c | 76 +++++++++++++++++++++++++++--------------------- 4 files changed, 103 insertions(+), 68 deletions(-) diff --git a/driver/connect.c b/driver/connect.c index 6c234ead..850ff803 100644 --- a/driver/connect.c +++ b/driver/connect.c @@ -1537,9 +1537,6 @@ static BOOL load_es_types(esodbc_dbc_st *dbc) /* apply any needed fixes */ - /* fix SQL_DATA_TYPE columns TODO: GH issue */ - types[i].sql_data_type = types[i].data_type; - /* warn if scales extremes are different */ if (types[i].maximum_scale != types[i].minimum_scale) { ERRH(dbc, "type `" LWPDL "` returned with non-equal max/min " @@ -1548,12 +1545,20 @@ static BOOL load_es_types(esodbc_dbc_st *dbc) } /* resolve ES type to SQL C type */ - types[i].sql_c_type = type_elastic2csql(&types[i].type_name); - if (types[i].sql_c_type == SQL_UNKNOWN_TYPE) { - BUG("failed to convert type name `" LWPDL "` to SQL C type.", + types[i].c_concise_type = type_elastic2csql(&types[i].type_name); + if (types[i].c_concise_type == SQL_UNKNOWN_TYPE) { + /* ES version newer than driver's? */ + ERRH(dbc, "failed to convert type name `" LWPDL "` to SQL C type.", LWSTR(&types[i].type_name)); goto end; } + /* set meta type */ + types[i].meta_type = concise_to_meta(types[i].c_concise_type, + /*C type -> AxD*/DESC_TYPE_ARD); + + /* fix SQL_DATA_TYPE and SQL_DATETIME_SUB columns TODO: GH issue */ + concise_to_type_code(types[i].data_type, &types[i].sql_data_type, + &types[i].sql_datetime_sub); } #undef ES_TYPES_COPY_INT diff --git a/driver/handles.c b/driver/handles.c index e8da7a76..c47b1e2d 100644 --- a/driver/handles.c +++ b/driver/handles.c @@ -1601,11 +1601,17 @@ SQLRETURN EsSQLGetDescRecW( /* * https://docs.microsoft.com/en-us/sql/odbc/reference/appendixes/data-type-identifiers-and-descriptors * - * Note: C and SQL types have the same value for these following defines, - * so this function will work for both IxD and AxD descriptors. (There is no - * SQL_C_DATETIME or SQL_C_CODE_DATE.) + * Note: C and SQL types have the same value for the case values below, so + * this function will work no matter the concise type (i.e. for both IxD, + * using SQL_C_, and AxD, using SQL_, descriptors). + * The case values are for datetime and interval data types (see also the + * comment at function end), so these values must stay in sync, since there + * are no C corresponding defines for verbose and sub-code (i.e. nothing like + * "SQL_C_DATETIME" or "SQL_C_CODE_DATE"). + * The identity does not hold across the bord, though (extended values, like + * BIGINTs do differ)! */ -void concise_to_type_code(SQLSMALLINT concise, SQLSMALLINT *type, +void concise_to_type_code(SQLSMALLINT concise, SQLSMALLINT *type, SQLSMALLINT *code) { switch (concise) { @@ -1679,6 +1685,9 @@ void concise_to_type_code(SQLSMALLINT concise, SQLSMALLINT *type, *code = SQL_CODE_MINUTE_TO_SECOND; break; } + /* "For all data types except datetime and interval data types, the + * verbose type identifier is the same as the concise type identifier and + * the value in SQL_DESC_DATETIME_INTERVAL_CODE is equal to 0." */ *type = concise; *code = 0; } @@ -1779,8 +1788,7 @@ static esodbc_metatype_et sqltype_to_meta(SQLSMALLINT concise) return METATYPE_UID; } - // BUG? - WARN("unknown meta type for concise SQL type %d.", concise); + ERR("unknown meta type for concise SQL type %d.", concise); return METATYPE_UNKNOWN; } @@ -1859,13 +1867,13 @@ static esodbc_metatype_et sqlctype_to_meta(SQLSMALLINT concise) return METATYPE_MAX; } - // BUG? - WARN("unknown meta type for concise C SQL type %d.", concise); + ERR("unknown meta type for concise C SQL type %d.", concise); return METATYPE_UNKNOWN; } /* * https://docs.microsoft.com/en-us/sql/odbc/reference/syntax/sqlsetdescrec-function#consistency-checks + * FIXME: re-eval this function along with IPD implementation! */ static BOOL consistency_check(esodbc_desc_st *desc, esodbc_rec_st *rec) { @@ -1923,7 +1931,7 @@ static BOOL consistency_check(esodbc_desc_st *desc, esodbc_rec_st *rec) return TRUE; } -esodbc_metatype_et concise_to_meta(SQLSMALLINT concise_type, +esodbc_metatype_et concise_to_meta(SQLSMALLINT concise_type, desc_type_et desc_type) { switch (desc_type) { diff --git a/driver/handles.h b/driver/handles.h index dc71821c..0691f3ef 100644 --- a/driver/handles.h +++ b/driver/handles.h @@ -53,10 +53,26 @@ typedef struct struct_env { // TODO?: connections } esodbc_env_st; +/* meta data types (same for both SQL_C_ and SQL_ types) */ +typedef enum { + METATYPE_UNKNOWN = 0, + METATYPE_EXACT_NUMERIC, + METATYPE_FLOAT_NUMERIC, + METATYPE_STRING, + METATYPE_BIN, + METATYPE_DATETIME, + METATYPE_INTERVAL_WSEC, + METATYPE_INTERVAL_WOSEC, + METATYPE_BIT, + METATYPE_UID, + METATYPE_MAX // SQL_C_DEFAULT +} esodbc_metatype_et; +/* Structure mapping one ES/SQL data type. */ typedef struct elasticsearch_type { + /* fields of one row returned in response to 'SYS TYPES' query */ wstr_st type_name; - SQLSMALLINT data_type; + SQLSMALLINT data_type; /* maps to rec's .concise_type member */ SQLINTEGER column_size; wstr_st literal_prefix; wstr_st literal_suffix; @@ -70,16 +86,25 @@ typedef struct elasticsearch_type { wstr_st local_type_name; SQLSMALLINT minimum_scale; SQLSMALLINT maximum_scale; - SQLSMALLINT sql_data_type; - SQLSMALLINT sql_datetime_sub; + SQLSMALLINT sql_data_type; /* :-> rec's .type member */ + SQLSMALLINT sql_datetime_sub; /* :-> rec's .datetime_interval_code */ SQLINTEGER num_prec_radix; SQLSMALLINT interval_precision; /* number of SYS TYPES result columns mapped over the above members */ #define ESODBC_TYPES_MEMBERS 19 - /* SQL C type driver mapping of ES' data_type */ - SQLSMALLINT sql_c_type; + /* SQL C type driver mapping of ES' data_type; this is derived from + * .type_name, rathern than .(sql_)data_type (sice the name is the + * "unique" key and sole identifier in general queries results). */ + SQLSMALLINT c_concise_type; + /* There should be no need for a supplemental 'sql_c_type': if + * rec.datetime_interval_code == 0, then this member would equal the + * concise one (above); else, rec.type will contain the right value + * already (i.e. they'd be the same for SQL and SQL C data types). */ + + /* helper member, to characterize the type */ + esodbc_metatype_et meta_type; } esodbc_estype_st; @@ -129,20 +154,6 @@ typedef struct struct_dbc { SQLUINTEGER txn_isolation; // default: SQL_TXN_* } esodbc_dbc_st; -typedef enum { - METATYPE_UNKNOWN = 0, - METATYPE_EXACT_NUMERIC, - METATYPE_FLOAT_NUMERIC, - METATYPE_STRING, - METATYPE_BIN, - METATYPE_DATETIME, - METATYPE_INTERVAL_WSEC, - METATYPE_INTERVAL_WOSEC, - METATYPE_BIT, - METATYPE_UID, - METATYPE_MAX // SQL_C_DEFAULT -} esodbc_metatype_et; - typedef struct desc_rec { /* back ref to owning descriptor */ struct struct_desc *desc; @@ -161,8 +172,9 @@ typedef struct desc_rec { * literal_prefix, literal_suffix, local_type_name, type_name, * auto_unique_value, case_sensitive, fixed_prec_scale, nullable, * searchable, usigned */ + /* record types (SQL_ for IxD, or SQL_C_ for AxD) */ SQLSMALLINT concise_type; - SQLSMALLINT type; /* SQL_C_ -> AxD, SQL_ -> IxD */ + SQLSMALLINT type; SQLSMALLINT datetime_interval_code; SQLPOINTER data_ptr; /* array, if .array_size > 1 */ diff --git a/driver/queries.c b/driver/queries.c index 015129a3..d03f9e17 100644 --- a/driver/queries.c +++ b/driver/queries.c @@ -155,25 +155,23 @@ static SQLRETURN attach_columns(esodbc_stmt_st *stmt, UJObject columns) } } if (rec->es_type) { - rec->concise_type = rec->es_type->sql_c_type; + /* copy fileds pre-calculated at DB connect time */ + rec->concise_type = rec->es_type->data_type; + rec->type = rec->es_type->sql_data_type; + rec->datetime_interval_code = rec->es_type->sql_datetime_sub; + rec->meta_type = rec->es_type->meta_type; + } else if (! dbc->no_types) { + /* the connection doesn't have yet the types cached (this is the + * caching call) and don't have access to the data itself either, + * just the column names & type names => set unknowns. */ + rec->concise_type = SQL_UNKNOWN_TYPE; + rec->type = SQL_UNKNOWN_TYPE; + rec->datetime_interval_code = 0; + rec->meta_type = METATYPE_UNKNOWN; } else { - do { - if (! dbc->no_types) { - /* the connection doesn't have the types cached yet (this - * is the SYS TYPES call) -> resolve the types "directly" - * */ - rec->concise_type = type_elastic2csql(&col_type); - if (rec->concise_type != SQL_UNKNOWN_TYPE) - break; - } - ERRH(stmt, "type lookup failed for `" LWPDL "`. (ES-driver " - "out of sync? check versions.)", LWSTR(&col_type)); - RET_HDIAG(stmt, SQL_STATE_HY000, MSG_INV_SRV_ANS, 0); - } while (0); + ERRH(stmt, "type lookup failed for `" LWPDL "`.",LWSTR(&col_type)); + RET_HDIAG(stmt, SQL_STATE_HY000, MSG_INV_SRV_ANS, 0); } - concise_to_type_code(rec->concise_type, &rec->type, - &rec->datetime_interval_code); - rec->meta_type = concise_to_meta(rec->concise_type, DESC_TYPE_ARD); set_col_size(rec); set_col_decdigits(rec); @@ -728,6 +726,23 @@ static inline void write_copied_octets(SQLLEN *octet_len_ptr, size_t copied, *octet_len_ptr = copied; } +/* if an application doesn't specify the conversion, use column's type */ +static inline SQLSMALLINT get_c_target_type(esodbc_rec_st *arec, + esodbc_rec_st *irec) +{ + SQLSMALLINT ctype; + /* "To use the default mapping, an application specifies the SQL_C_DEFAULT + * type identifier." */ + if (arec->type != SQL_C_DEFAULT) { + ctype = arec->type; + } else { + ctype = irec->es_type->c_concise_type; + } + DBGH(arec->desc, "target data type: %d.", ctype); + return ctype; +} + + static SQLRETURN copy_longlong(esodbc_rec_st *arec, esodbc_rec_st *irec, SQLULEN pos, long long ll) { @@ -735,7 +750,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; - SQLSMALLINT target_type; char buff[sizeof("18446744073709551616")]; /* = 1 << 8*8 */ SQLWCHAR wbuff[sizeof("18446744073709551616")]; /* = 1 << 8*8 */ size_t tocopy, blen; @@ -754,11 +768,7 @@ static SQLRETURN copy_longlong(esodbc_rec_st *arec, esodbc_rec_st *irec, RET_HDIAGS(stmt, SQL_STATE_HY009); } - /* "To use the default mapping, an application specifies the SQL_C_DEFAULT - * type identifier." */ - target_type = arec->type == SQL_C_DEFAULT ? irec->type : arec->type; - DBGH(stmt, "target data type: %d.", target_type); - switch (target_type) { + 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? */ @@ -827,7 +837,6 @@ static SQLRETURN copy_boolean(esodbc_rec_st *arec, esodbc_rec_st *irec, esodbc_stmt_st *stmt; void *data_ptr; SQLLEN *octet_len_ptr; - SQLSMALLINT target_type; stmt = arec->desc->hdr.stmt; @@ -840,9 +849,7 @@ static SQLRETURN copy_boolean(esodbc_rec_st *arec, esodbc_rec_st *irec, RET_HDIAGS(stmt, SQL_STATE_HY009); } - target_type = arec->type == SQL_C_DEFAULT ? irec->type : arec->type; - DBGH(stmt, "target data type: %d.", target_type); - switch (target_type) { + switch (get_c_target_type(arec, irec)) { case SQL_C_STINYINT: case SQL_C_UTINYINT: *(SQLSCHAR *)data_ptr = (SQLSCHAR)boolval; @@ -873,6 +880,7 @@ static SQLRETURN copy_boolean(esodbc_rec_st *arec, esodbc_rec_st *irec, case SQL_C_CHAR: case SQL_C_WCHAR: + // TODO default: FIXME; // FIXME @@ -1103,7 +1111,6 @@ static SQLRETURN copy_string(esodbc_rec_st *arec, esodbc_rec_st *irec, void *data_ptr; SQLLEN *octet_len_ptr; esodbc_desc_st *ard, *ird; - SQLSMALLINT target_type; stmt = arec->desc->hdr.stmt; ird = stmt->ird; @@ -1114,11 +1121,7 @@ static SQLRETURN copy_string(esodbc_rec_st *arec, esodbc_rec_st *irec, /* pointer to app's buffer */ data_ptr = deferred_address(SQL_DESC_DATA_PTR, pos, arec); - /* "To use the default mapping, an application specifies the SQL_C_DEFAULT - * type identifier." */ - target_type = arec->type == SQL_C_DEFAULT ? irec->type : arec->type; - DBGH(stmt, "target data type: %d.", target_type); - switch (target_type) { + switch (get_c_target_type(arec, irec)) { case SQL_C_CHAR: return wstr_to_cstr(arec, irec, data_ptr, octet_len_ptr, wstr, chars_0); @@ -1634,6 +1637,13 @@ static inline SQLULEN get_col_size(esodbc_rec_st *rec) case METATYPE_BIT: return rec->length; } + /* + * https://docs.microsoft.com/en-us/sql/odbc/reference/appendixes/column-size : + * "If the driver cannot determine the column or parameter length for a + * variable type, it returns SQL_NO_TOTAL. + * We should always have the length for var types, so: + * https://docs.microsoft.com/en-us/sql/odbc/reference/syntax/sqldescribecol-function#arguments : + * "If the column size cannot be determined, the driver returns 0." */ return 0; } From 16de30bde4411fb7ba7f14b861e36e7edbe176e6 Mon Sep 17 00:00:00 2001 From: Bogdan Pintea Date: Mon, 30 Apr 2018 20:36:22 +0200 Subject: [PATCH 10/17] make ES/SQL to SQL C data mapping clear - placed in a header, to easy find and change if needed --- driver/connect.c | 36 ++++++++++++++++----------------- driver/defs.h | 52 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 19 deletions(-) diff --git a/driver/connect.c b/driver/connect.c index 850ff803..dcdd4615 100644 --- a/driver/connect.c +++ b/driver/connect.c @@ -1131,33 +1131,31 @@ SQLSMALLINT type_elastic2csql(wstr_st *type_name) case (SQLWCHAR)'b': if (! wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_BYTE), type_name->cnt)) { - return SQL_C_STINYINT; + return ESODBC_ES_TO_CSQL_BYTE; } break; case (SQLWCHAR)'l': if (! wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_LONG), type_name->cnt)) { - return SQL_C_SLONG; + return ESODBC_ES_TO_CSQL_LONG; } break; case (SQLWCHAR)'t': if (! wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_TEXT), type_name->cnt)) { - // TODO: char/longvarchar/wchar/wvarchar? - return SQL_C_CHAR; + return ESODBC_ES_TO_CSQL_TEXT; } break; case (SQLWCHAR)'d': if (! wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_DATE), type_name->cnt)) { - return SQL_C_TYPE_TIMESTAMP; + return ESODBC_ES_TO_CSQL_DATE; } break; case (SQLWCHAR)'n': if (! wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_NULL), type_name->cnt)) { - // TODO: own type? - return SQL_C_UTINYINT; + return ESODBC_ES_TO_CSQL_NULL; } break; } @@ -1169,13 +1167,13 @@ SQLSMALLINT type_elastic2csql(wstr_st *type_name) case (SQLWCHAR)'s': if (! wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_SHORT), type_name->cnt)) { - return SQL_C_SSHORT; + return ESODBC_ES_TO_CSQL_SHORT; } break; case (SQLWCHAR)'f': if (! wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_FLOAT), type_name->cnt)) { - return SQL_C_FLOAT; + return ESODBC_ES_TO_CSQL_FLOAT; } break; } @@ -1187,25 +1185,25 @@ SQLSMALLINT type_elastic2csql(wstr_st *type_name) case (SQLWCHAR)'d': if (! wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_DOUBLE), type_name->cnt)) { - return SQL_C_DOUBLE; + return ESODBC_ES_TO_CSQL_DOUBLE; } break; case (SQLWCHAR)'b': if (! wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_BINARY), type_name->cnt)) { - return SQL_C_BINARY; + return ESODBC_ES_TO_CSQL_BINARY; } break; case (SQLWCHAR)'o': if (! wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_OBJECT), type_name->cnt)) { - return SQL_C_BINARY; + return ESODBC_ES_TO_CSQL_OBJECT; } break; case (SQLWCHAR)'n': if (! wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_NESTED), type_name->cnt)) { - return SQL_C_BINARY; + return ESODBC_ES_TO_CSQL_NESTED; } break; } @@ -1217,17 +1215,17 @@ SQLSMALLINT type_elastic2csql(wstr_st *type_name) case (SQLWCHAR)'i': /* integer */ if (wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_INTEGER), type_name->cnt) == 0) - return SQL_C_SLONG; + return ESODBC_ES_TO_CSQL_INTEGER; break; case (SQLWCHAR)'b': /* boolean */ if (wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_BOOLEAN), type_name->cnt) == 0) - return SQL_C_UTINYINT; + return ESODBC_ES_TO_CSQL_BOOLEAN; break; case (SQLWCHAR)'k': /* keyword */ if (wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_KEYWORD), type_name->cnt) == 0) - return SQL_C_CHAR; + return ESODBC_ES_TO_CSQL_KEYWORD; break; } break; @@ -1236,7 +1234,7 @@ SQLSMALLINT type_elastic2csql(wstr_st *type_name) case sizeof(JSON_COL_HALF_FLOAT) - 1: if (! wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_HALF_FLOAT), type_name->cnt)) { - return SQL_C_FLOAT; + return ESODBC_ES_TO_CSQL_HALF_FLOAT; } break; @@ -1244,7 +1242,7 @@ SQLSMALLINT type_elastic2csql(wstr_st *type_name) case sizeof(JSON_COL_UNSUPPORTED) - 1: if (! wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_UNSUPPORTED), type_name->cnt)) { - return SQL_C_BINARY; + return ESODBC_ES_TO_CSQL_UNSUPPORTED; } break; @@ -1252,7 +1250,7 @@ SQLSMALLINT type_elastic2csql(wstr_st *type_name) case sizeof(JSON_COL_SCALED_FLOAT) - 1: if (! wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_SCALED_FLOAT), type_name->cnt)) { - return SQL_C_FLOAT; + return ESODBC_ES_TO_CSQL_SCALED_FLOAT; } break; diff --git a/driver/defs.h b/driver/defs.h index bcced22f..e585ba93 100644 --- a/driver/defs.h +++ b/driver/defs.h @@ -271,6 +271,58 @@ */ #define ODBC_SQL92_VALUE_EXPRESSIONS 0 +/* + * ES specific data types + */ +#define ESODBC_SQL_BOOLEAN 16 +#define ESODBC_SQL_NULL 0 +#define ESODBC_SQL_UNSUPPORTED 1111 +#define ESODBC_SQL_OBJECT 2002 +#define ESODBC_SQL_NESTED 2002 +/* + * ISO8601 template ('yyyy-mm-ddThh:mm:ss.sss+hh:mm') + */ +#define ESODBC_ISO8601_TEMPLATE "yyyy-mm-ddThh:mm:ss.sssZ" + +/* + * ES-to-C-SQL mappings + * DATA_TYPE(SYS TYPES) : SQL_ -> SQL_C_ + * Collected here for a quick overview (and easy change); can't be automated. + */ +/* -6: SQL_TINYINT -> SQL_C_TINYINT */ +#define ESODBC_ES_TO_CSQL_BYTE SQL_C_TINYINT +/* 5: SQL_SMALLINT -> SQL_C_SHORT */ +#define ESODBC_ES_TO_CSQL_SHORT SQL_C_SHORT +/* 4: SQL_INTEGER -> SQL_C_LONG */ +#define ESODBC_ES_TO_CSQL_INTEGER SQL_C_LONG +/* -5: SQL_BIGINT -> SQL_C_SBIGINT */ +#define ESODBC_ES_TO_CSQL_LONG SQL_C_SBIGINT +/* 6: SQL_FLOAT -> SQL_C_DOUBLE */ +#define ESODBC_ES_TO_CSQL_HALF_FLOAT SQL_C_DOUBLE +/* 6: SQL_FLOAT -> SQL_C_DOUBLE */ +#define ESODBC_ES_TO_CSQL_SCALED_FLOAT SQL_C_DOUBLE +/* 7: SQL_REAL -> SQL_C_DOUBLE */ +#define ESODBC_ES_TO_CSQL_FLOAT SQL_C_FLOAT +/* 8: SQL_DOUBLE -> SQL_C_FLOAT */ +#define ESODBC_ES_TO_CSQL_DOUBLE SQL_C_DOUBLE +/* 16: ??? -> SQL_C_TINYINT */ +#define ESODBC_ES_TO_CSQL_BOOLEAN SQL_C_TINYINT +/* 12: SQL_VARCHAR -> SQL_C_WCHAR */ +#define ESODBC_ES_TO_CSQL_KEYWORD SQL_C_WCHAR +/* 12: SQL_VARCHAR -> SQL_C_WCHAR */ +#define ESODBC_ES_TO_CSQL_TEXT SQL_C_WCHAR +/* 93: SQL_TYPE_TIMESTAMP -> SQL_C_TYPE_TIMESTAMP */ +#define ESODBC_ES_TO_CSQL_DATE SQL_C_TYPE_TIMESTAMP +/* -3: SQL_VARBINARY -> SQL_C_BINARY */ +#define ESODBC_ES_TO_CSQL_BINARY SQL_C_BINARY +/* 0: SQL_TYPE_NULL -> SQL_C_TINYINT */ +#define ESODBC_ES_TO_CSQL_NULL SQL_C_TINYINT +/* 1111: ??? -> SQL_C_BINARY */ +#define ESODBC_ES_TO_CSQL_UNSUPPORTED SQL_C_BINARY +/* 2002: ??? -> SQL_C_BINARY */ +#define ESODBC_ES_TO_CSQL_OBJECT SQL_C_BINARY +/* 2002: ??? -> SQL_C_BINARY */ +#define ESODBC_ES_TO_CSQL_NESTED SQL_C_BINARY #endif /* __DEFS_H__ */ From 710273ed9c64c384cb1b40766c4a0b1b03a14461 Mon Sep 17 00:00:00 2001 From: Bogdan Pintea Date: Mon, 30 Apr 2018 20:41:27 +0200 Subject: [PATCH 11/17] add the 'display size' handling - this is a r/o IRD specific field => moved it to es_type; - implement the Display Size calculation. --- driver/connect.c | 96 ++++++++++++++++++++++++++++++++++++++++++++++++ driver/handles.c | 17 ++++----- driver/handles.h | 11 +++--- driver/queries.c | 3 +- 4 files changed, 109 insertions(+), 18 deletions(-) diff --git a/driver/connect.c b/driver/connect.c index dcdd4615..1c4ffb41 100644 --- a/driver/connect.c +++ b/driver/connect.c @@ -1260,6 +1260,100 @@ SQLSMALLINT type_elastic2csql(wstr_st *type_name) return SQL_UNKNOWN_TYPE; } +/* + * https://docs.microsoft.com/en-us/sql/odbc/reference/appendixes/display-size + */ +static void set_display_size(esodbc_estype_st *es_type) +{ + switch (es_type->data_type) { + case SQL_CHAR: + case SQL_VARCHAR: /* KEYWORD, TEXT */ + case SQL_LONGVARCHAR: + case SQL_WCHAR: + case SQL_WVARCHAR: + case SQL_WLONGVARCHAR: + es_type->display_size = es_type->column_size; + break; + + case SQL_REAL: /* FLOAT */ + es_type->display_size = 14; + break; + case SQL_FLOAT: /* HALF_, SCALED_FLOAT */ + case SQL_DOUBLE: /* DOUBLE */ + es_type->display_size = 24; + break; + + case SQL_TINYINT: /* BYTE */ + case SQL_SMALLINT: /* SHORT */ + case SQL_INTEGER: /* INTEGER */ + es_type->display_size = es_type->column_size; + if (! es_type->unsigned_attribute) + es_type->display_size ++; + break; + case SQL_BIGINT: /* LONG */ + es_type->display_size = es_type->column_size; + if (es_type->unsigned_attribute) + es_type->display_size ++; + break; + + case SQL_BINARY: + case SQL_VARBINARY: /* BINARY */ + case SQL_LONGVARBINARY: + /* 0xAB */ + es_type->display_size = 2 * es_type->column_size; + break; + + case SQL_TYPE_DATE: + case SQL_TYPE_TIME: + case SQL_TYPE_TIMESTAMP: /* DATE */ + es_type->display_size = sizeof(ESODBC_ISO8601_TEMPLATE) - /*0*/1; + break; + + + case ESODBC_SQL_BOOLEAN: + es_type->display_size = /*'false'*/5; + break; + + case ESODBC_SQL_NULL: + es_type->display_size = /*'null'*/4; + break; + + /* treat these as variable binaries with unknown size */ + case ESODBC_SQL_UNSUPPORTED: + case ESODBC_SQL_OBJECT: /* == ESODBC_SQL_NESTED */ + es_type->display_size = SQL_NO_TOTAL; + break; + + /* + case SQL_TYPE_UTCDATETIME: + case SQL_TYPE_UTCTIME: + */ + + case SQL_DECIMAL: + case SQL_NUMERIC: + + case SQL_BIT: + + case SQL_INTERVAL_MONTH: + case SQL_INTERVAL_YEAR: + case SQL_INTERVAL_YEAR_TO_MONTH: + case SQL_INTERVAL_DAY: + case SQL_INTERVAL_HOUR: + case SQL_INTERVAL_MINUTE: + case SQL_INTERVAL_SECOND: + case SQL_INTERVAL_DAY_TO_HOUR: + case SQL_INTERVAL_DAY_TO_MINUTE: + case SQL_INTERVAL_DAY_TO_SECOND: + case SQL_INTERVAL_HOUR_TO_MINUTE: + case SQL_INTERVAL_HOUR_TO_SECOND: + case SQL_INTERVAL_MINUTE_TO_SECOND: + + case SQL_GUID: + + default: + BUG("unsupported ES/SQL data type: %d.", es_type->data_type); + } +} /* * Load SYS TYPES data. @@ -1557,6 +1651,8 @@ static BOOL load_es_types(esodbc_dbc_st *dbc) /* fix SQL_DATA_TYPE and SQL_DATETIME_SUB columns TODO: GH issue */ concise_to_type_code(types[i].data_type, &types[i].sql_data_type, &types[i].sql_datetime_sub); + + set_display_size(types + i); } #undef ES_TYPES_COPY_INT diff --git a/driver/handles.c b/driver/handles.c index c47b1e2d..0ace894f 100644 --- a/driver/handles.c +++ b/driver/handles.c @@ -132,9 +132,7 @@ void dump_record(esodbc_rec_st *rec) DUMP_FIELD(rec, indicator_ptr, "0x%p"); DUMP_FIELD(rec, octet_length_ptr, "0x%p"); - DUMP_FIELD(rec, display_size, "%lld"); DUMP_FIELD(rec, octet_length, "%lld"); - DUMP_FIELD(rec, length, "%llu"); DUMP_FIELD(rec, datetime_interval_precision, "%d"); @@ -743,7 +741,7 @@ SQLRETURN EsSQLSetStmtAttrW( ulen = (SQLULEN)ValuePtr; DBGH(stmt, "setting max_lenght to: %u.", ulen); if (ulen < ESODBC_LO_MAX_LENGTH) { - WARNH(stmt, "MAX_LENGHT lower than min allowed (%d) -- " + WARNH(stmt, "MAX_LENGTH lower than min allowed (%d) -- " "correcting value.", ESODBC_LO_MAX_LENGTH); ulen = ESODBC_LO_MAX_LENGTH; } else if (ESODBC_UP_MAX_LENGTH && ESODBC_UP_MAX_LENGTH < ulen) { @@ -1473,8 +1471,9 @@ SQLRETURN EsSQLGetDescFieldW( /* */ case SQL_DESC_DISPLAY_SIZE: - *(SQLLEN *)ValuePtr = rec->display_size; - DBGH(desc, "returning display size: %d.", rec->display_size); + *(SQLLEN *)ValuePtr = rec->es_type->display_size; + DBGH(desc, "returning display size: %d.", + rec->es_type->display_size); break; case SQL_DESC_OCTET_LENGTH: *(SQLLEN *)ValuePtr = rec->octet_length; @@ -2230,12 +2229,10 @@ SQLRETURN EsSQLSetDescFieldW( break; /* */ - case SQL_DESC_DISPLAY_SIZE: - DBGH(desc, "setting display size: %d.", (SQLLEN)(intptr_t)ValuePtr); - rec->display_size = (SQLLEN)(intptr_t)ValuePtr; - break; + /* R/O fields: display_size */ case SQL_DESC_OCTET_LENGTH: - DBGH(desc, "setting octet length: %d.", (SQLLEN)(intptr_t)ValuePtr); + DBGH(desc, "setting octet length: %d.", + (SQLLEN)(intptr_t)ValuePtr); rec->octet_length = (SQLLEN)(intptr_t)ValuePtr; break; diff --git a/driver/handles.h b/driver/handles.h index 0691f3ef..f2e2420a 100644 --- a/driver/handles.h +++ b/driver/handles.h @@ -97,7 +97,7 @@ typedef struct elasticsearch_type { /* SQL C type driver mapping of ES' data_type; this is derived from * .type_name, rathern than .(sql_)data_type (sice the name is the * "unique" key and sole identifier in general queries results). */ - SQLSMALLINT c_concise_type; + SQLSMALLINT c_concise_type; /* There should be no need for a supplemental 'sql_c_type': if * rec.datetime_interval_code == 0, then this member would equal the * concise one (above); else, rec.type will contain the right value @@ -105,6 +105,7 @@ typedef struct elasticsearch_type { /* helper member, to characterize the type */ esodbc_metatype_et meta_type; + SQLLEN display_size; } esodbc_estype_st; @@ -169,9 +170,9 @@ typedef struct desc_rec { * record fields */ /* following record fields have been moved into es_type: - * literal_prefix, literal_suffix, local_type_name, type_name, - * auto_unique_value, case_sensitive, fixed_prec_scale, nullable, - * searchable, usigned */ + * display_size, literal_prefix, literal_suffix, local_type_name, + * type_name, auto_unique_value, case_sensitive, fixed_prec_scale, + * nullable, searchable, usigned */ /* record types (SQL_ for IxD, or SQL_C_ for AxD) */ SQLSMALLINT concise_type; SQLSMALLINT type; @@ -191,9 +192,7 @@ typedef struct desc_rec { SQLLEN *indicator_ptr; /* array, if .array_size > 1 */ SQLLEN *octet_length_ptr; /* array, if .array_size > 1 */ - SQLLEN display_size; SQLLEN octet_length; - SQLULEN length; SQLINTEGER datetime_interval_precision; /*TODO: -> es_type? */ diff --git a/driver/queries.c b/driver/queries.c index d03f9e17..466d8321 100644 --- a/driver/queries.c +++ b/driver/queries.c @@ -185,7 +185,6 @@ static SQLRETURN attach_columns(esodbc_stmt_st *stmt, UJObject columns) /* "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->display_size = 256; assert(rec->name && rec->label); rec->unnamed = (rec->name[0] || rec->label[0]) ? @@ -1876,7 +1875,7 @@ SQLRETURN EsSQLColAttributeW( /* SQLLEN */ do { - case SQL_DESC_DISPLAY_SIZE: len = rec->display_size; break; + case SQL_DESC_DISPLAY_SIZE: len = rec->es_type->display_size; break; case SQL_DESC_OCTET_LENGTH: len = rec->octet_length; break; } while (0); PNUMATTR_ASSIGN(SQLLEN, len); From f96f56e5fbe532b34f68ab73332c04146c9f86af Mon Sep 17 00:00:00 2001 From: Bogdan Pintea Date: Mon, 30 Apr 2018 20:43:56 +0200 Subject: [PATCH 12/17] 'column size' and 'decimal digits' now correct - fixed/finished the logic for these two measures --- driver/queries.c | 78 +++++++++++++++++++++--------------------------- 1 file changed, 34 insertions(+), 44 deletions(-) diff --git a/driver/queries.c b/driver/queries.c index 466d8321..17f22608 100644 --- a/driver/queries.c +++ b/driver/queries.c @@ -28,7 +28,6 @@ #define MSG_INV_SRV_ANS "Invalid server answer" -#define ISO8601_TEMPLATE "yyyy-mm-ddThh:mm:ss.sss+hh:mm" #define TM_TO_TIMESTAMP_STRUCT(_tmp/*src*/, _tsp/*dst*/) \ do { \ (_tsp)->year = (_tmp)->tm_year + 1900; \ @@ -51,50 +50,41 @@ void clear_resultset(esodbc_stmt_st *stmt) memset(&stmt->rset, 0, sizeof(stmt->rset)); } +/* Set the desriptor fields associated with "size". This step is needed since + * the application could read the descriptors - like .length - individually, + * rather than through functions that make use of get_col_size() (where we + * could just read the es_type directly). */ static void set_col_size(esodbc_rec_st *rec) { - switch (rec->concise_type) { - /* .precision */ - // TODO: lifted from SYS TYPES: automate this? - case SQL_C_SLONG: rec->precision = 19; break; - case SQL_C_UTINYINT: - case SQL_C_STINYINT: rec->precision = 3; break; - case SQL_C_SSHORT: rec->precision = 5; break; - - /* .length */ - case SQL_C_CHAR: - rec->length = 256; /*TODO: max TEXT size */ - break; + assert(rec->desc->type == DESC_TYPE_IRD); - case SQL_C_TYPE_DATE: - rec->length = sizeof(ISO8601_TEMPLATE)/*+\0*/; + switch (rec->meta_type) { + case METATYPE_UNKNOWN: + /* SYS TYPES call */ break; - - default: - FIXME; // FIXME - } -} - -static void set_col_decdigits(esodbc_rec_st *rec) -{ - switch (rec->concise_type) { - case SQL_C_SLONG: - case SQL_C_UTINYINT: - case SQL_C_STINYINT: - case SQL_C_SSHORT: - rec->scale = 0; + case METATYPE_EXACT_NUMERIC: + case METATYPE_FLOAT_NUMERIC: + /* ignore, the .precision field is not used in IRDs, its value is + * always read from es_type.column_size directly */ break; - case SQL_C_TYPE_DATE: - rec->precision = 3; /* [seconds].xxx of ISO 8601 */ + /* + * https://docs.microsoft.com/en-us/sql/odbc/reference/appendixes/column-size : */ + case METATYPE_STRING: + /* "The defined or maximum column size in characters of the + * column" */ + /* no break */ + case METATYPE_BIN: + /* "The defined or maximum length in bytes of the column " */ + /* no break */ + case METATYPE_DATETIME: + /* "number of characters in the character representation" */ + rec->length = rec->es_type->column_size; break; - - case SQL_C_CHAR: break; /* n/a */ default: - FIXME; // FIXME + BUGH(rec->desc, "unsupported data c-type: %d.", rec->concise_type); } - } static SQLRETURN attach_columns(esodbc_stmt_st *stmt, UJObject columns) @@ -174,7 +164,6 @@ static SQLRETURN attach_columns(esodbc_stmt_st *stmt, UJObject columns) } set_col_size(rec); - set_col_decdigits(rec); /* TODO: set remaining of settable fields (base table etc.) */ @@ -1016,7 +1005,7 @@ static SQLRETURN wstr_to_wstr(esodbc_rec_st *arec, esodbc_rec_st *irec, static BOOL wstr_to_timestamp_struct(const wchar_t *wstr, size_t chars, TIMESTAMP_STRUCT *tss) { - char buff[sizeof(ISO8601_TEMPLATE)/*+\0*/]; + char buff[sizeof(ESODBC_ISO8601_TEMPLATE)/*+\0*/]; int len; timestamp_t tsp; struct tm tmp; @@ -1623,6 +1612,7 @@ SQLRETURN EsSQLExecDirectW static inline SQLULEN get_col_size(esodbc_rec_st *rec) { assert(rec->desc->type == DESC_TYPE_IRD); + switch (rec->meta_type) { case METATYPE_EXACT_NUMERIC: case METATYPE_FLOAT_NUMERIC: @@ -1635,12 +1625,11 @@ static inline SQLULEN get_col_size(esodbc_rec_st *rec) case METATYPE_INTERVAL_WOSEC: case METATYPE_BIT: return rec->length; + + case METATYPE_UID: + BUGH(rec->desc, "unsupported data c-type: %d.", rec->concise_type); } /* - * https://docs.microsoft.com/en-us/sql/odbc/reference/appendixes/column-size : - * "If the driver cannot determine the column or parameter length for a - * variable type, it returns SQL_NO_TOTAL. - * We should always have the length for var types, so: * https://docs.microsoft.com/en-us/sql/odbc/reference/syntax/sqldescribecol-function#arguments : * "If the column size cannot be determined, the driver returns 0." */ return 0; @@ -1652,7 +1641,8 @@ static inline SQLSMALLINT get_col_decdigits(esodbc_rec_st *rec) switch (rec->meta_type) { case METATYPE_DATETIME: case METATYPE_INTERVAL_WSEC: - return (SQLSMALLINT)rec->es_type->column_size; + /* TODO: pending GH#30002 actually */ + return 3; case METATYPE_EXACT_NUMERIC: return rec->es_type->maximum_scale; @@ -1738,7 +1728,7 @@ SQLRETURN EsSQLDescribeColW( ERRH(stmt, "no column size buffer provided."); RET_HDIAG(stmt, SQL_STATE_HY090, "no column size buffer provided", 0); } - *pcbColDef = get_col_size(rec); // TODO: set "size" of columns from type + *pcbColDef = get_col_size(rec); DBGH(stmt, "col #%d of meta type %d has size=%llu.", icol, rec->meta_type, *pcbColDef); @@ -1747,7 +1737,7 @@ SQLRETURN EsSQLDescribeColW( RET_HDIAG(stmt, SQL_STATE_HY090, "no column decimal digits buffer provided", 0); } - *pibScale = get_col_decdigits(rec); // TODO: set "decimal digits" from type + *pibScale = get_col_decdigits(rec); DBGH(stmt, "col #%d of meta type %d has decimal digits=%d.", icol, rec->meta_type, *pibScale); From 8a9f1175985828cf07a02aa3eeb5eacb0b7d3f29 Mon Sep 17 00:00:00 2001 From: Bogdan Pintea Date: Wed, 2 May 2018 12:57:46 +0200 Subject: [PATCH 13/17] typo fixes in comments or log messages - mostly s/lenght/length --- driver/connect.c | 22 +++++++++++----------- driver/defs.h | 4 ++-- driver/error.h | 2 +- driver/handles.c | 24 ++++++++++++------------ driver/handles.h | 4 ++-- driver/log.c | 2 +- driver/log.h | 4 ++-- driver/queries.c | 20 ++++++++++---------- driver/util.c | 4 ++-- driver/util.h | 2 +- 10 files changed, 44 insertions(+), 44 deletions(-) diff --git a/driver/connect.c b/driver/connect.c index 1c4ffb41..df8443c4 100644 --- a/driver/connect.c +++ b/driver/connect.c @@ -75,7 +75,7 @@ #define REG_HKLM "HKEY_LOCAL_MACHINE" #define REG_HKCU "HKEY_CURRENT_USER" -/* max lenght of a registry key value name */ +/* max length of a registry key value name */ #define MAX_REG_VAL_NAME 1024 /* max size of a registry key data */ #define MAX_REG_DATA_SIZE 4096 @@ -163,7 +163,7 @@ static size_t write_callback(char *ptr, size_t size, size_t nmemb, /* do I need to grow the existing buffer? */ if (avail < have) { - /* calculate how much space to allocate. start from existing lenght, + /* calculate how much space to allocate. start from existing length, * if set, othewise from a constant (on first allocation). */ for (need = dbc->alen ? dbc->alen : ESODBC_BODY_BUF_START_SIZE; need < dbc->apos + have; need *= 2) @@ -434,7 +434,7 @@ SQLRETURN post_statement(esodbc_stmt_st *stmt) * clean this function). */ /* evaluate how long the stringified REST object will be */ - if (stmt->rset.eccnt) { /* eval CURSOR object lenght */ + if (stmt->rset.eccnt) { /* eval CURSOR object length */ /* convert cursor to C [mb]string. */ /* TODO: ansi_w2c() fits better for Base64 encoded cursors. */ u8len = WCS2U8(stmt->rset.ecurs, (int)stmt->rset.eccnt, u8curs, @@ -448,7 +448,7 @@ SQLRETURN post_statement(esodbc_stmt_st *stmt) bodylen = sizeof(JSON_SQL_CURSOR_START) - /*\0*/1; bodylen += json_escape(u8curs, u8len, NULL, 0); bodylen += sizeof(JSON_SQL_CURSOR_END) - /*\0*/1; - } else { /* eval QUERY object lenght */ + } else { /* eval QUERY object length */ bodylen = sizeof(JSON_SQL_QUERY_START) - /*\0*/1; if (dbc->fetch.slen) { bodylen += sizeof(JSON_SQL_QUERY_MID_FETCH) - /*\0*/1; @@ -875,7 +875,7 @@ static BOOL write_connection_string(config_attrs_st *attrs, pos += n; } } else { - /* simply increment the counter, since the untruncated lenght + /* simply increment the counter, since the untruncated length * need to be returned to the app */ pos += iter->val->cnt + braces; } @@ -914,7 +914,7 @@ static SQLRETURN process_config(esodbc_dbc_st *dbc, config_attrs_st *attrs) LWSTR(&attrs->port)); goto err; } - /* lenght of URL converted to U8 */ + /* length of URL converted to U8 */ n = WCS2U8(urlw, cnt, NULL, 0); if (! n) { ERRNH(dbc, "failed to estimate U8 conversion space necessary for `" @@ -1370,11 +1370,11 @@ static BOOL load_es_types(esodbc_dbc_st *dbc) SQLSMALLINT col_cnt; SQLLEN row_cnt; /* structure for one row returned by the ES. - * This is a mirror of elasticsearch_type, with lenght-or-indicator fields + * This is a mirror of elasticsearch_type, with length-or-indicator fields * for each of the members in elasticsearch_type */ struct { SQLWCHAR type_name[ESODBC_MAX_IDENTIFIER_LEN]; - SQLLEN type_name_loi; /* _ lenght or indicator */ + SQLLEN type_name_loi; /* _ length or indicator */ SQLSMALLINT data_type; SQLLEN data_type_loi; SQLINTEGER column_size; @@ -1535,8 +1535,8 @@ static BOOL load_es_types(esodbc_dbc_st *dbc) goto end; } - /* check row statues; - * calculate the lenght of all strings (SQLWCHAR members) returned + /* check row statuses; + * calculate the length of all strings (SQLWCHAR members) returned * count also the 0-terms, which are not counted for in the indicator */ strs_len = 0; for (i = 0; i < rows_fetched; i ++) { @@ -1737,7 +1737,7 @@ static BOOL read_system_info(config_attrs_st *attrs, TCHAR *buff) goto end; } else { DBG("Subkey '%s\\" LWPD "': vals: %d, lengthiest name: %d, " - "lenghtiest data: %d.", ktree, val, valsno, maxvallen, + "lengthiest data: %d.", ktree, val, valsno, maxvallen, maxdatalen); // malloc buffers? if (MAX_REG_VAL_NAME < maxvallen) diff --git a/driver/defs.h b/driver/defs.h index e585ba93..043b2266 100644 --- a/driver/defs.h +++ b/driver/defs.h @@ -46,7 +46,7 @@ /* max # of active statements for a connection */ /* TODO: review@alpha */ #define ESODBC_MAX_CONCURRENT_ACTIVITIES 16 -/* maximum identifer lenght */ +/* maximum identifer length */ /* TODO: review@alpha */ #define ESODBC_MAX_IDENTIFIER_LEN 128 @@ -246,7 +246,7 @@ /* * SQL92 numeric value functions: * - supported: none. - * - not supported: BIT_LENGHT, CHAR_LENGTH, CHARACTER_LENGTH, EXTRACT, + * - not supported: BIT_LENGTH, CHAR_LENGTH, CHARACTER_LENGTH, EXTRACT, * OCTET_LENGTH, POSITION */ #define ESODBC_SQL92_NUMERIC_VALUE_FUNCTIONS 0 diff --git a/driver/error.h b/driver/error.h index e3b517c1..19d3b255 100644 --- a/driver/error.h +++ b/driver/error.h @@ -426,7 +426,7 @@ typedef struct { esodbc_state_et state; /* [vendor-identifier][ODBC-component-identifier]component-supplied-text */ SQLWCHAR text[SQL_MAX_MESSAGE_LENGTH]; - /* lenght of characters in the buffer */ + /* length of characters in the buffer */ SQLUSMALLINT text_len; /* in characters, not bytes, w/o the 0-term */ /* (SQLSMALLINT)wcslen(native_text) */ /* returned in SQLGetDiagField()/SQL_DIAG_NATIVE, SQLGetDiagRecW() */ diff --git a/driver/handles.c b/driver/handles.c index 0ace894f..6a01c50c 100644 --- a/driver/handles.c +++ b/driver/handles.c @@ -739,7 +739,7 @@ SQLRETURN EsSQLSetStmtAttrW( case SQL_ATTR_MAX_LENGTH: ulen = (SQLULEN)ValuePtr; - DBGH(stmt, "setting max_lenght to: %u.", ulen); + DBGH(stmt, "setting max_length to: %u.", ulen); if (ulen < ESODBC_LO_MAX_LENGTH) { WARNH(stmt, "MAX_LENGTH lower than min allowed (%d) -- " "correcting value.", ESODBC_LO_MAX_LENGTH); @@ -950,9 +950,9 @@ static esodbc_state_et check_buff(SQLSMALLINT field_id, SQLPOINTER buff, /* pointer to a value other than string or binary string */ case SQL_DESC_DATA_PTR: if ((buff_len != SQL_IS_POINTER) && (buff_len < 0)) { - /* spec says the lenght "should" be it's size => this check + /* spec says the length "should" be it's size => this check * might be too strict? */ - ERR("buffer is for pointer, but its lenght indicator " + ERR("buffer is for pointer, but its length indicator " "doesn't match (%d).", buff_len); return SQL_STATE_HY090; } @@ -960,7 +960,7 @@ static esodbc_state_et check_buff(SQLSMALLINT field_id, SQLPOINTER buff, } if (! writable) - /* this call is from SetDescField, so lenght for integer types are + /* this call is from SetDescField, so length for integer types are * ignored */ return SQL_STATE_00000; @@ -974,7 +974,7 @@ static esodbc_state_et check_buff(SQLSMALLINT field_id, SQLPOINTER buff, case SQL_DESC_DISPLAY_SIZE: case SQL_DESC_OCTET_LENGTH: if (buff_len != SQL_IS_INTEGER) { - ERR("buffer is for interger, but its lenght indicator " + ERR("buffer is for interger, but its length indicator " "doesn't match (%d).", buff_len); return SQL_STATE_HY090; } @@ -984,7 +984,7 @@ static esodbc_state_et check_buff(SQLSMALLINT field_id, SQLPOINTER buff, case SQL_DESC_ARRAY_SIZE: case SQL_DESC_LENGTH: if (buff_len != SQL_IS_UINTEGER) { - ERR("buffer is for uint, but its lenght indicator " + ERR("buffer is for uint, but its length indicator " "doesn't match (%d).", buff_len); return SQL_STATE_HY090; } @@ -1007,7 +1007,7 @@ static esodbc_state_et check_buff(SQLSMALLINT field_id, SQLPOINTER buff, case SQL_DESC_UNSIGNED: case SQL_DESC_UPDATABLE: if (buff_len != SQL_IS_SMALLINT) { - ERR("buffer is for short, but its lenght indicator " + ERR("buffer is for short, but its length indicator " "doesn't match (%d).", buff_len); return SQL_STATE_HY090; } @@ -1483,7 +1483,7 @@ SQLRETURN EsSQLGetDescFieldW( /* */ case SQL_DESC_LENGTH: *(SQLULEN *)ValuePtr = rec->length; - DBGH(desc, "returning lenght: %u.", rec->length); + DBGH(desc, "returning length: %u.", rec->length); break; /* */ @@ -1607,8 +1607,8 @@ SQLRETURN EsSQLGetDescRecW( * comment at function end), so these values must stay in sync, since there * are no C corresponding defines for verbose and sub-code (i.e. nothing like * "SQL_C_DATETIME" or "SQL_C_CODE_DATE"). - * The identity does not hold across the bord, though (extended values, like - * BIGINTs do differ)! + * The identity does not hold across the board, though (extended values, like + * BIGINTs, do differ)! */ void concise_to_type_code(SQLSMALLINT concise, SQLSMALLINT *type, SQLSMALLINT *code) @@ -2101,7 +2101,7 @@ SQLRETURN EsSQLSetDescFieldW( * the SQL_DESC_DATA_PTR field, the driver sets SQL_DESC_DATA_PTR to a * null pointer, unbinding the record." * - * NOTE: the record can actually still be bound by the lenght/indicator + * NOTE: the record can actually still be bound by the length/indicator * buffer(s), so the above "binding" definition is incomplete. */ if (FieldIdentifier != SQL_DESC_DATA_PTR) @@ -2238,7 +2238,7 @@ SQLRETURN EsSQLSetDescFieldW( /* */ case SQL_DESC_LENGTH: - DBGH(desc, "setting lenght: %u.", (SQLULEN)(uintptr_t)ValuePtr); + DBGH(desc, "setting length: %u.", (SQLULEN)(uintptr_t)ValuePtr); rec->length = (SQLULEN)(uintptr_t)ValuePtr; break; diff --git a/driver/handles.h b/driver/handles.h index f2e2420a..54aa9570 100644 --- a/driver/handles.h +++ b/driver/handles.h @@ -140,7 +140,7 @@ typedef struct struct_dbc { char *abuff; /* buffer holding the answer */ size_t alen; /* size of abuff */ size_t apos; /* current write position in the abuff */ - size_t amax; /* maximum lenght (bytes) that abuff can grow to */ + size_t amax; /* maximum length (bytes) that abuff can grow to */ /* window handler */ HWND hwin; @@ -261,7 +261,7 @@ typedef struct struct_desc { typedef struct struct_resultset { long code; /* code of last response */ char *buff; /* buffer containing the answer to the last request in a STM */ - size_t blen; /* lenght of the answer */ + size_t blen; /* length of the answer */ void *state; /* top UJSON decoder state */ void *rows_iter; /* UJSON array with the result set */ diff --git a/driver/log.c b/driver/log.c index cf23ee0a..5c8da1a0 100644 --- a/driver/log.c +++ b/driver/log.c @@ -91,7 +91,7 @@ BOOL log_init() /* is there a log level specified? */ if ((qmark = TSTRCHR(path, LOG_LEVEL_SEPARATOR))) { *qmark = 0; /* end the path here */ - pos = (int)(qmark - path); /* adjust the lenght of path */ + pos = (int)(qmark - path); /* adjust the length of path */ /* first letter will indicate the log level, with the default being * debug, since this is mostly a tracing functionality */ switch (qmark[1]) { diff --git a/driver/log.h b/driver/log.h index 5427502e..3b6b7194 100644 --- a/driver/log.h +++ b/driver/log.h @@ -18,7 +18,7 @@ /* * Descriptors to be used with logging for SQLWCHAR pointer type. - * "Log Wchar Pointer Descriptor [with Lenght]" + * "Log Wchar Pointer Descriptor [with Length]" */ #ifdef UNICODE #define LWPD PFWP_DESC @@ -30,7 +30,7 @@ /* * Descriptors to be used with logging for SQLTCHAR pointer type. - * "Log Tchar Pointer Descriptor [with Lenght]" + * "Log Tchar Pointer Descriptor [with Length]" */ #ifdef UNICODE #define LTPD PFWP_DESC diff --git a/driver/queries.c b/driver/queries.c index 17f22608..706ddc0d 100644 --- a/driver/queries.c +++ b/driver/queries.c @@ -635,7 +635,7 @@ static void* deferred_address(SQLSMALLINT field_id, size_t pos, } /* - * Handles the lenghts of the data to copy out to the application: + * 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', @@ -709,7 +709,7 @@ static inline void write_copied_octets(SQLLEN *octet_len_ptr, size_t copied, * figuring out what the actual length is" */ *octet_len_ptr = max; else - /* if no "network" truncation done, indicate data's lenght, no + /* if no "network" truncation done, indicate data's length, no * matter if truncated to buffer's size or not */ *octet_len_ptr = copied; } @@ -882,7 +882,7 @@ static SQLRETURN copy_boolean(esodbc_rec_st *arec, esodbc_rec_st *irec, /* * -> SQL_C_CHAR - * Note: chars_0 param accounts for 0-term, but lenght indicated back to the + * Note: chars_0 param accounts for 0-term, but length indicated back to the * application must not. */ static SQLRETURN wstr_to_cstr(esodbc_rec_st *arec, esodbc_rec_st *irec, @@ -940,7 +940,7 @@ static SQLRETURN wstr_to_cstr(esodbc_rec_st *arec, esodbc_rec_st *irec, } else { /* chars_0 accounts for 0-terminator, so WCS2U8 will count that in * the output as well => trim it, since we must not count it when - * indicating the lenght to the application */ + * indicating the length to the application */ out_bytes --; } write_copied_octets(octet_len_ptr, out_bytes, stmt->max_length, @@ -956,7 +956,7 @@ static SQLRETURN wstr_to_cstr(esodbc_rec_st *arec, esodbc_rec_st *irec, /* * -> SQL_C_WCHAR - * Note: chars_0 accounts for 0-term, but lenght indicated back to the + * Note: chars_0 accounts for 0-term, but length indicated back to the * application must not. */ static SQLRETURN wstr_to_wstr(esodbc_rec_st *arec, esodbc_rec_st *irec, @@ -991,7 +991,7 @@ static SQLRETURN wstr_to_wstr(esodbc_rec_st *arec, esodbc_rec_st *irec, DBGH(stmt, "REC@0x%p, NULL data_ptr", arec); } - /* original lenght is indicated, w/o possible buffer truncation (but with + /* 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); @@ -1526,7 +1526,7 @@ SQLRETURN EsSQLPrepareW if (cchSqlStr == SQL_NTS) { cchSqlStr = (SQLINTEGER)wcslen(szSqlStr); } else if (cchSqlStr <= 0) { - ERRH(stmt, "invalid statment lenght: %d.", cchSqlStr); + ERRH(stmt, "invalid statment length: %d.", cchSqlStr); RET_HDIAGS(stmt, SQL_STATE_HY090); } DBGH(stmt, "preparing `" LWPDL "` [%d]", cchSqlStr, szSqlStr, @@ -1586,7 +1586,7 @@ SQLRETURN EsSQLExecDirectW if (cchSqlStr == SQL_NTS) { cchSqlStr = (SQLINTEGER)wcslen(szSqlStr); } else if (cchSqlStr <= 0) { - ERRH(stmt, "invalid statment lenght: %d.", cchSqlStr); + ERRH(stmt, "invalid statment length: %d.", cchSqlStr); RET_HDIAGS(stmt, SQL_STATE_HY090); } DBGH(stmt, "directly executing SQL: `" LWPDL "` [%d].", cchSqlStr, @@ -1708,9 +1708,9 @@ SQLRETURN EsSQLDescribeColW( } if (! pcchColName) { - ERRH(stmt, "no column name lenght buffer provided."); + ERRH(stmt, "no column name length buffer provided."); RET_HDIAG(stmt, SQL_STATE_HY090, - "no column name lenght buffer provided", 0); + "no column name length buffer provided", 0); } *pcchColName = 0 <= col_blen ? (col_blen / sizeof(*szColName)) : (SQLSMALLINT)wcslen(rec->name); diff --git a/driver/util.c b/driver/util.c index 2a797a0a..c93b630c 100644 --- a/driver/util.c +++ b/driver/util.c @@ -77,7 +77,7 @@ int ansi_w2c(const SQLWCHAR *src, char *dst, size_t chars) } while (src[i] && (++i < chars)); if (chars <= i) { /* equiv to: (src[i] != 0) */ - /* loop stopped b/c of lenght -> src is not 0-term'd */ + /* loop stopped b/c of length -> src is not 0-term'd */ dst[i] = 0; } return i + 1; @@ -113,7 +113,7 @@ int wszmemcmp(const SQLWCHAR *a, const SQLWCHAR *b, long count) return *a - *b; } -/* retuns the lenght of a buffer to hold the escaped variant of the unescaped +/* retuns the length of a buffer to hold the escaped variant of the unescaped * given json object */ static inline size_t json_escaped_len(const char *json, size_t len) { diff --git a/driver/util.h b/driver/util.h index e0d567bd..0e48b810 100644 --- a/driver/util.h +++ b/driver/util.h @@ -206,7 +206,7 @@ size_t json_escape(const char *jin, size_t inlen, char *jout, size_t outlen); /* * Descriptors to be used with STPRINTF for TCHAR pointer type. - * "SNTPRINTF Tchar Pointer Descriptor [with Lenght]" + * "SNTPRINTF Tchar Pointer Descriptor [with Length]" */ #ifdef UNICODE #define STPD WPFWP_DESC From 0292ea341f48570cd3fe7fcea98b46b340eef841 Mon Sep 17 00:00:00 2001 From: Bogdan Pintea Date: Wed, 2 May 2018 14:07:03 +0200 Subject: [PATCH 14/17] use the newer signed defs for type mapping The sign-explicit defs have been added to replace the implicitly signed ones. --- driver/defs.h | 8 ++++---- driver/queries.c | 4 ++++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/driver/defs.h b/driver/defs.h index 043b2266..b8c352b6 100644 --- a/driver/defs.h +++ b/driver/defs.h @@ -293,9 +293,9 @@ /* -6: SQL_TINYINT -> SQL_C_TINYINT */ #define ESODBC_ES_TO_CSQL_BYTE SQL_C_TINYINT /* 5: SQL_SMALLINT -> SQL_C_SHORT */ -#define ESODBC_ES_TO_CSQL_SHORT SQL_C_SHORT +#define ESODBC_ES_TO_CSQL_SHORT SQL_C_SSHORT /* 4: SQL_INTEGER -> SQL_C_LONG */ -#define ESODBC_ES_TO_CSQL_INTEGER SQL_C_LONG +#define ESODBC_ES_TO_CSQL_INTEGER SQL_C_SLONG /* -5: SQL_BIGINT -> SQL_C_SBIGINT */ #define ESODBC_ES_TO_CSQL_LONG SQL_C_SBIGINT /* 6: SQL_FLOAT -> SQL_C_DOUBLE */ @@ -307,7 +307,7 @@ /* 8: SQL_DOUBLE -> SQL_C_FLOAT */ #define ESODBC_ES_TO_CSQL_DOUBLE SQL_C_DOUBLE /* 16: ??? -> SQL_C_TINYINT */ -#define ESODBC_ES_TO_CSQL_BOOLEAN SQL_C_TINYINT +#define ESODBC_ES_TO_CSQL_BOOLEAN SQL_C_STINYINT /* 12: SQL_VARCHAR -> SQL_C_WCHAR */ #define ESODBC_ES_TO_CSQL_KEYWORD SQL_C_WCHAR /* 12: SQL_VARCHAR -> SQL_C_WCHAR */ @@ -317,7 +317,7 @@ /* -3: SQL_VARBINARY -> SQL_C_BINARY */ #define ESODBC_ES_TO_CSQL_BINARY SQL_C_BINARY /* 0: SQL_TYPE_NULL -> SQL_C_TINYINT */ -#define ESODBC_ES_TO_CSQL_NULL SQL_C_TINYINT +#define ESODBC_ES_TO_CSQL_NULL SQL_C_STINYINT /* 1111: ??? -> SQL_C_BINARY */ #define ESODBC_ES_TO_CSQL_UNSUPPORTED SQL_C_BINARY /* 2002: ??? -> SQL_C_BINARY */ diff --git a/driver/queries.c b/driver/queries.c index 706ddc0d..2edc46b1 100644 --- a/driver/queries.c +++ b/driver/queries.c @@ -778,24 +778,28 @@ static SQLRETURN copy_longlong(esodbc_rec_st *arec, esodbc_rec_st *irec, 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), 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), 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), stmt->max_length, irec->meta_type); break; + case SQL_C_BIGINT: case SQL_C_SBIGINT: *(SQLBIGINT *)data_ptr = (SQLBIGINT)ll; write_copied_octets(octet_len_ptr, sizeof(SQLBIGINT), From 7f50431e22272ec1b9233a807dcc73ecfc78d604 Mon Sep 17 00:00:00 2001 From: Bogdan Pintea Date: Wed, 2 May 2018 14:10:23 +0200 Subject: [PATCH 15/17] remove dead code and update catalog fetching case Update the logic that gets the catalog name: there's a new SYS CATALOGS available system query that can be used, so the way to implement it in the driver is now clear. --- driver/connect.c | 44 +++++++++++++++++++++----------------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/driver/connect.c b/driver/connect.c index df8443c4..90132d51 100644 --- a/driver/connect.c +++ b/driver/connect.c @@ -2060,12 +2060,25 @@ SQLRETURN EsSQLSetConnectAttrW( return SQL_SUCCESS; } -#if 0 -static BOOL get_current_catalog(esodbc_dbc_st *dbc) +/* writes into 'dest', of size 'room', the current catalog of 'dbc'. + * returns negative on error, or the char count written otherwise */ +static SQLSMALLINT get_current_catalog(esodbc_dbc_st *dbc, SQLWCHAR *dest, + SQLSMALLINT room) { - FIXME; + SQLSMALLINT used; + SQLWCHAR *catalog = MK_WPTR("my_current_catalog"); // FIXME + + // + // TODO: use the new SYS CATALOGS query + // + + DBGH(dbc, "current catalog: `" LWPD "`.", catalog); + if (! SQL_SUCCEEDED(write_wptr(&dbc->hdr.diag, dest, catalog, room, + &used))) { + return -1; + } + return used; } -#endif //0 SQLRETURN EsSQLGetConnectAttrW( SQLHDBC ConnectionHandle, @@ -2075,9 +2088,7 @@ SQLRETURN EsSQLGetConnectAttrW( _Out_opt_ SQLINTEGER* StringLengthPtr) { esodbc_dbc_st *dbc = DBCH(ConnectionHandle); - SQLRETURN ret; SQLSMALLINT used; -// SQLWCHAR *val; switch(Attribute) { /* https://docs.microsoft.com/en-us/sql/odbc/reference/develop-app/automatic-population-of-the-ipd */ @@ -2092,30 +2103,17 @@ SQLRETURN EsSQLGetConnectAttrW( /* "the name of the catalog to be used by the data source" */ case SQL_ATTR_CURRENT_CATALOG: - DBGH(dbc, "requested: catalog name (@0x%p).", dbc->catalog); -#if 0 + DBGH(dbc, "requested: catalog name."); if (! dbc->es_types) { ERRH(dbc, "no connection active."); - /* TODO: check connection state and correct state */ RET_HDIAGS(dbc, SQL_STATE_08003); - } else if (! get_current_catalog(dbc)) { + } else if ((used = get_current_catalog(dbc, (SQLWCHAR *)ValuePtr, + (SQLSMALLINT)BufferLength)) < 0) { ERRH(dbc, "failed to get current catalog."); - RET_STATE(dbc, dbc->hdr.diag.state); + RET_STATE(dbc->hdr.diag.state); } -#endif //0 -#if 0 - val = dbc->catalog ? dbc->catalog : MK_WPTR("null"); - *StringLengthPtr = wcslen(*(SQLWCHAR **)ValuePtr); - *StringLengthPtr *= sizeof(SQLWCHAR); - *(SQLWCHAR **)ValuePtr = val; -#else //0 - // FIXME; - ret = write_wptr(&dbc->hdr.diag, (SQLWCHAR *)ValuePtr, - MK_WPTR("NulL"), (SQLSMALLINT)BufferLength, &used); if (StringLengthPtr); *StringLengthPtr = (SQLINTEGER)used; - return ret; -#endif //0 break; case SQL_ATTR_METADATA_ID: From 5b65ebdb21d0568762cf44b74b86d482122f8658 Mon Sep 17 00:00:00 2001 From: Bogdan Pintea Date: Wed, 2 May 2018 15:08:57 +0200 Subject: [PATCH 16/17] b/f from prev commit: SQL_C_BIGINT doesn't exist BIGINT is a new type, which are all explicitely signed/unsigned. (not sure how it slipped in previous commit, past a pre-commit compile) --- driver/queries.c | 1 - 1 file changed, 1 deletion(-) diff --git a/driver/queries.c b/driver/queries.c index 2edc46b1..387708e1 100644 --- a/driver/queries.c +++ b/driver/queries.c @@ -799,7 +799,6 @@ static SQLRETURN copy_longlong(esodbc_rec_st *arec, esodbc_rec_st *irec, stmt->max_length, irec->meta_type); break; - case SQL_C_BIGINT: case SQL_C_SBIGINT: *(SQLBIGINT *)data_ptr = (SQLBIGINT)ll; write_copied_octets(octet_len_ptr, sizeof(SQLBIGINT), From 2c6b6782c8a513869474ad202a4beb08b4be3adc Mon Sep 17 00:00:00 2001 From: Bogdan Pintea Date: Wed, 2 May 2018 15:12:38 +0200 Subject: [PATCH 17/17] break load_es_types() in smaller functions load_es_types() got too large. --- driver/connect.c | 350 +++++++++++++++++++++++++---------------------- 1 file changed, 187 insertions(+), 163 deletions(-) diff --git a/driver/connect.c b/driver/connect.c index 90132d51..7f5daf00 100644 --- a/driver/connect.c +++ b/driver/connect.c @@ -102,6 +102,49 @@ typedef struct { wstr_st trace_level; } config_attrs_st; +/* structure for one row returned by the ES. + * This is a mirror of elasticsearch_type, with length-or-indicator fields + * for each of the members in elasticsearch_type */ +typedef struct { + SQLWCHAR type_name[ESODBC_MAX_IDENTIFIER_LEN]; + SQLLEN type_name_loi; /* _ length or indicator */ + SQLSMALLINT data_type; + SQLLEN data_type_loi; + SQLINTEGER column_size; + SQLLEN column_size_loi; + SQLWCHAR literal_prefix[ESODBC_MAX_IDENTIFIER_LEN]; + SQLLEN literal_prefix_loi; + SQLWCHAR literal_suffix[ESODBC_MAX_IDENTIFIER_LEN]; + SQLLEN literal_suffix_loi; + SQLWCHAR create_params[ESODBC_MAX_IDENTIFIER_LEN]; + SQLLEN create_params_loi; + SQLSMALLINT nullable; + SQLLEN nullable_loi; + SQLSMALLINT case_sensitive; + SQLLEN case_sensitive_loi; + SQLSMALLINT searchable; + SQLLEN searchable_loi; + SQLSMALLINT unsigned_attribute; + SQLLEN unsigned_attribute_loi; + SQLSMALLINT fixed_prec_scale; + SQLLEN fixed_prec_scale_loi; + SQLSMALLINT auto_unique_value; + SQLLEN auto_unique_value_loi; + SQLWCHAR local_type_name[ESODBC_MAX_IDENTIFIER_LEN]; + SQLLEN local_type_name_loi; + SQLSMALLINT minimum_scale; + SQLLEN minimum_scale_loi; + SQLSMALLINT maximum_scale; + SQLLEN maximum_scale_loi; + SQLSMALLINT sql_data_type; + SQLLEN sql_data_type_loi; + SQLSMALLINT sql_datetime_sub; + SQLLEN sql_datetime_sub_loi; + SQLINTEGER num_prec_radix; + SQLLEN num_prec_radix_loi; + SQLSMALLINT interval_precision; + SQLLEN interval_precision_loi; +} estype_row_st; /* * HTTP headers used for all requests (Content-Type, Accept). */ @@ -1355,6 +1398,141 @@ static void set_display_size(esodbc_estype_st *es_type) } } +static BOOL bind_types_cols(esodbc_stmt_st *stmt, estype_row_st *type_row) +{ + + /* bind one column */ +#define ES_TYPES_BINDCOL(_col_nr, _member, _c_type) \ + do { \ + SQLPOINTER _ptr = _c_type == SQL_C_WCHAR ? \ + (SQLPOINTER)(uintptr_t)type_row[0]._member : \ + (SQLPOINTER)&type_row[0]._member; \ + if (! SQL_SUCCEEDED(EsSQLBindCol(stmt, _col_nr, _c_type, \ + _ptr, sizeof(type_row[0]._member), \ + &type_row[0]._member ## _loi))) { \ + ERRH(stmt, "failed to bind column #" STR(_col_nr) "."); \ + return FALSE; \ + } \ + } while (0) + + ES_TYPES_BINDCOL(1, type_name, SQL_C_WCHAR); + ES_TYPES_BINDCOL(2, data_type, SQL_C_SSHORT); + ES_TYPES_BINDCOL(3, column_size, SQL_C_SLONG); + ES_TYPES_BINDCOL(4, literal_prefix, SQL_C_WCHAR); + ES_TYPES_BINDCOL(5, literal_suffix, SQL_C_WCHAR); + ES_TYPES_BINDCOL(6, create_params, SQL_C_WCHAR); + ES_TYPES_BINDCOL(7, nullable, SQL_C_SSHORT); + ES_TYPES_BINDCOL(8, case_sensitive, SQL_C_SSHORT); + ES_TYPES_BINDCOL(9, searchable, SQL_C_SSHORT); + ES_TYPES_BINDCOL(10, unsigned_attribute, SQL_C_SSHORT); + ES_TYPES_BINDCOL(11, fixed_prec_scale, SQL_C_SSHORT); + ES_TYPES_BINDCOL(12, auto_unique_value, SQL_C_SSHORT); + ES_TYPES_BINDCOL(13, local_type_name, SQL_C_WCHAR); + ES_TYPES_BINDCOL(14, minimum_scale, SQL_C_SSHORT); + ES_TYPES_BINDCOL(15, maximum_scale, SQL_C_SSHORT); + ES_TYPES_BINDCOL(16, sql_data_type, SQL_C_SSHORT); + ES_TYPES_BINDCOL(17, sql_datetime_sub, SQL_C_SSHORT); + ES_TYPES_BINDCOL(18, num_prec_radix, SQL_C_SLONG); + ES_TYPES_BINDCOL(19, interval_precision, SQL_C_SSHORT); + +#undef ES_TYPES_BINDCOL + + return TRUE; +} + +static void* copy_types_rows(estype_row_st *type_row, SQLULEN rows_fetched, + esodbc_estype_st *types) +{ + SQLWCHAR *pos; + int i; + + /* start pointer where the strings will be copied in */ + pos = (SQLWCHAR *)&types[rows_fetched]; + + /* copy one integer member */ +#define ES_TYPES_COPY_INT(_member) \ + do { \ + if (type_row[i]._member ## _loi == SQL_NULL_DATA) { \ + /*null->0 is harmless for cached types */\ + types[i]._member = 0; \ + } else { \ + types[i]._member = type_row[i]._member; \ + } \ + } while (0) + /* copy one wstr_st member + * Note: it'll shift NULLs to empty strings, as most of the API asks for + * empty strings if data is unavailable ("unkown"). */ +#define ES_TYPES_COPY_WSTR(_wmember) \ + do { \ + if (type_row[i]._wmember ## _loi == SQL_NULL_DATA) { \ + types[i]._wmember.cnt = 0; \ + types[i]._wmember.str = MK_WPTR(""); \ + } else { \ + types[i]._wmember.cnt = \ + type_row[i]._wmember ## _loi / sizeof(SQLWCHAR); \ + types[i]._wmember.str = pos; \ + wmemcpy(types[i]._wmember.str, \ + type_row[i]._wmember, types[i]._wmember.cnt + /*\0*/1); \ + pos += types[i]._wmember.cnt + /*\0*/1; \ + } \ + } while (0) + + for (i = 0; i < rows_fetched; i ++) { + /* copy data */ + ES_TYPES_COPY_WSTR(type_name); + ES_TYPES_COPY_INT(data_type); + ES_TYPES_COPY_INT(column_size); + ES_TYPES_COPY_WSTR(literal_prefix); + ES_TYPES_COPY_WSTR(literal_suffix); + ES_TYPES_COPY_WSTR(create_params); + ES_TYPES_COPY_INT(nullable); + ES_TYPES_COPY_INT(case_sensitive); + ES_TYPES_COPY_INT(searchable); + ES_TYPES_COPY_INT(unsigned_attribute); + ES_TYPES_COPY_INT(fixed_prec_scale); + ES_TYPES_COPY_INT(auto_unique_value); + ES_TYPES_COPY_WSTR(local_type_name); + ES_TYPES_COPY_INT(minimum_scale); + ES_TYPES_COPY_INT(maximum_scale); + ES_TYPES_COPY_INT(sql_data_type); + ES_TYPES_COPY_INT(sql_datetime_sub); + ES_TYPES_COPY_INT(num_prec_radix); + ES_TYPES_COPY_INT(interval_precision); + + /* apply any needed fixes */ + + /* 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), + types[i].maximum_scale, types[i].minimum_scale); + } + + /* resolve ES type to SQL C type */ + types[i].c_concise_type = type_elastic2csql(&types[i].type_name); + if (types[i].c_concise_type == SQL_UNKNOWN_TYPE) { + /* ES version newer than driver's? */ + ERR("failed to convert type name `" LWPDL "` to SQL C type.", + LWSTR(&types[i].type_name)); + return NULL; + } + /* set meta type */ + types[i].meta_type = concise_to_meta(types[i].c_concise_type, + /*C type -> AxD*/DESC_TYPE_ARD); + + /* fix SQL_DATA_TYPE and SQL_DATETIME_SUB columns TODO: GH issue */ + concise_to_type_code(types[i].data_type, &types[i].sql_data_type, + &types[i].sql_datetime_sub); + + set_display_size(types + i); + } + +#undef ES_TYPES_COPY_INT +#undef ES_TYPES_COPY_WCHAR + + return pos; +} + /* * Load SYS TYPES data. * @@ -1369,56 +1547,14 @@ static BOOL load_es_types(esodbc_dbc_st *dbc) SQLRETURN ret = FALSE; SQLSMALLINT col_cnt; SQLLEN row_cnt; - /* structure for one row returned by the ES. - * This is a mirror of elasticsearch_type, with length-or-indicator fields - * for each of the members in elasticsearch_type */ - struct { - SQLWCHAR type_name[ESODBC_MAX_IDENTIFIER_LEN]; - SQLLEN type_name_loi; /* _ length or indicator */ - SQLSMALLINT data_type; - SQLLEN data_type_loi; - SQLINTEGER column_size; - SQLLEN column_size_loi; - SQLWCHAR literal_prefix[ESODBC_MAX_IDENTIFIER_LEN]; - SQLLEN literal_prefix_loi; - SQLWCHAR literal_suffix[ESODBC_MAX_IDENTIFIER_LEN]; - SQLLEN literal_suffix_loi; - SQLWCHAR create_params[ESODBC_MAX_IDENTIFIER_LEN]; - SQLLEN create_params_loi; - SQLSMALLINT nullable; - SQLLEN nullable_loi; - SQLSMALLINT case_sensitive; - SQLLEN case_sensitive_loi; - SQLSMALLINT searchable; - SQLLEN searchable_loi; - SQLSMALLINT unsigned_attribute; - SQLLEN unsigned_attribute_loi; - SQLSMALLINT fixed_prec_scale; - SQLLEN fixed_prec_scale_loi; - SQLSMALLINT auto_unique_value; - SQLLEN auto_unique_value_loi; - SQLWCHAR local_type_name[ESODBC_MAX_IDENTIFIER_LEN]; - SQLLEN local_type_name_loi; - SQLSMALLINT minimum_scale; - SQLLEN minimum_scale_loi; - SQLSMALLINT maximum_scale; - SQLLEN maximum_scale_loi; - SQLSMALLINT sql_data_type; - SQLLEN sql_data_type_loi; - SQLSMALLINT sql_datetime_sub; - SQLLEN sql_datetime_sub_loi; - SQLINTEGER num_prec_radix; - SQLLEN num_prec_radix_loi; - SQLSMALLINT interval_precision; - SQLLEN interval_precision_loi; - } type_row[ESODBC_MAX_ROW_ARRAY_SIZE]; - /* both arrays must use ESODBC_MAX_ROW_ARRAY_SIZE since no SQLFetch() + /* both arrays below must use ESODBC_MAX_ROW_ARRAY_SIZE since no SQLFetch() * looping is implemented (see check after SQLFetch() below). */ + estype_row_st type_row[ESODBC_MAX_ROW_ARRAY_SIZE]; SQLUSMALLINT row_status[ESODBC_MAX_ROW_ARRAY_SIZE]; SQLULEN rows_fetched, i, strs_len; size_t size; - SQLWCHAR *pos; esodbc_estype_st *types = NULL; + void *pos; if (! SQL_SUCCEEDED(EsSQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt))) { ERRH(dbc, "failed to alloc a statement handle."); @@ -1488,41 +1624,9 @@ static BOOL load_es_types(esodbc_dbc_st *dbc) goto end; } - /* bind one column */ -#define ES_TYPES_BINDCOL(_col_nr, _member, _c_type) \ - do { \ - SQLPOINTER _ptr = _c_type == SQL_C_WCHAR ? \ - (SQLPOINTER)(uintptr_t)type_row[0]._member : \ - (SQLPOINTER)&type_row[0]._member; \ - if (! SQL_SUCCEEDED(EsSQLBindCol(stmt, _col_nr, _c_type, \ - _ptr, sizeof(type_row[0]._member), \ - &type_row[0]._member ## _loi))) { \ - ERRH(stmt, "failed to bind column #" STR(_col_nr) "."); \ - goto end; \ - } \ - } while (0) - - ES_TYPES_BINDCOL(1, type_name, SQL_C_WCHAR); - ES_TYPES_BINDCOL(2, data_type, SQL_C_SSHORT); - ES_TYPES_BINDCOL(3, column_size, SQL_C_SLONG); - ES_TYPES_BINDCOL(4, literal_prefix, SQL_C_WCHAR); - ES_TYPES_BINDCOL(5, literal_suffix, SQL_C_WCHAR); - ES_TYPES_BINDCOL(6, create_params, SQL_C_WCHAR); - ES_TYPES_BINDCOL(7, nullable, SQL_C_SSHORT); - ES_TYPES_BINDCOL(8, case_sensitive, SQL_C_SSHORT); - ES_TYPES_BINDCOL(9, searchable, SQL_C_SSHORT); - ES_TYPES_BINDCOL(10, unsigned_attribute, SQL_C_SSHORT); - ES_TYPES_BINDCOL(11, fixed_prec_scale, SQL_C_SSHORT); - ES_TYPES_BINDCOL(12, auto_unique_value, SQL_C_SSHORT); - ES_TYPES_BINDCOL(13, local_type_name, SQL_C_WCHAR); - ES_TYPES_BINDCOL(14, minimum_scale, SQL_C_SSHORT); - ES_TYPES_BINDCOL(15, maximum_scale, SQL_C_SSHORT); - ES_TYPES_BINDCOL(16, sql_data_type, SQL_C_SSHORT); - ES_TYPES_BINDCOL(17, sql_datetime_sub, SQL_C_SSHORT); - ES_TYPES_BINDCOL(18, num_prec_radix, SQL_C_SLONG); - ES_TYPES_BINDCOL(19, interval_precision, SQL_C_SSHORT); - -#undef ES_TYPES_BINDCOL + if (! bind_types_cols(stmt, type_row)) { + goto end; + } /* fetch the results into the type_row array */ if (! SQL_SUCCEEDED(EsSQLFetch(stmt))) { @@ -1574,90 +1678,10 @@ static BOOL load_es_types(esodbc_dbc_st *dbc) goto end; } - /* start pointer where the strings will be copied in */ - pos = (SQLWCHAR *)&types[rows_fetched]; - - /* copy one integer member - * TODO: treat NULL case */ -#define ES_TYPES_COPY_INT(_member) \ - do { \ - if (type_row[i]._member ## _loi == SQL_NULL_DATA) { \ - types[i]._member = 0; \ - } else { \ - types[i]._member = type_row[i]._member; \ - } \ - } while (0) - /* copy one wstr_st member - * Note: it'll shift NULLs to empty strings, as most of the API asks for - * empty strings if data is unavailable ("unkown"). */ -#define ES_TYPES_COPY_WSTR(_wmember) \ - do { \ - if (type_row[i]._wmember ## _loi == SQL_NULL_DATA) { \ - types[i]._wmember.cnt = 0; \ - types[i]._wmember.str = MK_WPTR(""); \ - } else { \ - types[i]._wmember.cnt = \ - type_row[i]._wmember ## _loi / sizeof(SQLWCHAR); \ - types[i]._wmember.str = pos; \ - wmemcpy(types[i]._wmember.str, \ - type_row[i]._wmember, types[i]._wmember.cnt + /*\0*/1); \ - pos += types[i]._wmember.cnt + /*\0*/1; \ - } \ - } while (0) - - for (i = 0; i < rows_fetched; i ++) { - /* copy data */ - ES_TYPES_COPY_WSTR(type_name); - ES_TYPES_COPY_INT(data_type); - ES_TYPES_COPY_INT(column_size); - ES_TYPES_COPY_WSTR(literal_prefix); - ES_TYPES_COPY_WSTR(literal_suffix); - ES_TYPES_COPY_WSTR(create_params); - ES_TYPES_COPY_INT(nullable); - ES_TYPES_COPY_INT(case_sensitive); - ES_TYPES_COPY_INT(searchable); - ES_TYPES_COPY_INT(unsigned_attribute); - ES_TYPES_COPY_INT(fixed_prec_scale); - ES_TYPES_COPY_INT(auto_unique_value); - ES_TYPES_COPY_WSTR(local_type_name); - ES_TYPES_COPY_INT(minimum_scale); - ES_TYPES_COPY_INT(maximum_scale); - ES_TYPES_COPY_INT(sql_data_type); - ES_TYPES_COPY_INT(sql_datetime_sub); - ES_TYPES_COPY_INT(num_prec_radix); - ES_TYPES_COPY_INT(interval_precision); - - /* apply any needed fixes */ - - /* warn if scales extremes are different */ - if (types[i].maximum_scale != types[i].minimum_scale) { - ERRH(dbc, "type `" LWPDL "` returned with non-equal max/min " - "scale: %d/%d.", LWSTR(&types[i].type_name), - types[i].maximum_scale, types[i].minimum_scale); - } - - /* resolve ES type to SQL C type */ - types[i].c_concise_type = type_elastic2csql(&types[i].type_name); - if (types[i].c_concise_type == SQL_UNKNOWN_TYPE) { - /* ES version newer than driver's? */ - ERRH(dbc, "failed to convert type name `" LWPDL "` to SQL C type.", - LWSTR(&types[i].type_name)); - goto end; - } - /* set meta type */ - types[i].meta_type = concise_to_meta(types[i].c_concise_type, - /*C type -> AxD*/DESC_TYPE_ARD); - - /* fix SQL_DATA_TYPE and SQL_DATETIME_SUB columns TODO: GH issue */ - concise_to_type_code(types[i].data_type, &types[i].sql_data_type, - &types[i].sql_datetime_sub); - - set_display_size(types + i); + if (! (pos = copy_types_rows(type_row, rows_fetched, types))) { + ERRH(dbc, "failed to process recieved ES/SQL data types."); + goto end; } - -#undef ES_TYPES_COPY_INT -#undef ES_TYPES_COPY_WCHAR - /* I didn't overrun the buffer */ assert((char *)pos - (char *)(types + rows_fetched) <= (intptr_t)(strs_len));