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 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 diff --git a/driver/connect.c b/driver/connect.c index bc3f5a32..7f5daf00 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" @@ -48,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 @@ -75,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). */ @@ -136,7 +206,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) @@ -407,7 +477,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, @@ -421,7 +491,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; @@ -682,7 +752,7 @@ static BOOL parse_token(BOOL is_value, SQLWCHAR **pos, SQLWCHAR *end, } (*pos)++; break; - + default: (*pos)++; } @@ -848,7 +918,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; } @@ -887,7 +957,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 `" @@ -1011,16 +1081,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); } @@ -1047,6 +1117,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 +1163,553 @@ 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 ESODBC_ES_TO_CSQL_BYTE; + } + break; + case (SQLWCHAR)'l': + if (! wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_LONG), + type_name->cnt)) { + return ESODBC_ES_TO_CSQL_LONG; + } + break; + case (SQLWCHAR)'t': + if (! wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_TEXT), + type_name->cnt)) { + return ESODBC_ES_TO_CSQL_TEXT; + } + break; + case (SQLWCHAR)'d': + if (! wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_DATE), + type_name->cnt)) { + return ESODBC_ES_TO_CSQL_DATE; + } + break; + case (SQLWCHAR)'n': + if (! wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_NULL), + type_name->cnt)) { + return ESODBC_ES_TO_CSQL_NULL; + } + 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 ESODBC_ES_TO_CSQL_SHORT; + } + break; + case (SQLWCHAR)'f': + if (! wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_FLOAT), + type_name->cnt)) { + return ESODBC_ES_TO_CSQL_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 ESODBC_ES_TO_CSQL_DOUBLE; + } + break; + case (SQLWCHAR)'b': + if (! wmemncasecmp(type_name->str, + MK_WPTR(JSON_COL_BINARY), type_name->cnt)) { + return ESODBC_ES_TO_CSQL_BINARY; + } + break; + case (SQLWCHAR)'o': + if (! wmemncasecmp(type_name->str, + MK_WPTR(JSON_COL_OBJECT), type_name->cnt)) { + return ESODBC_ES_TO_CSQL_OBJECT; + } + break; + case (SQLWCHAR)'n': + if (! wmemncasecmp(type_name->str, + MK_WPTR(JSON_COL_NESTED), type_name->cnt)) { + return ESODBC_ES_TO_CSQL_NESTED; + } + 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 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 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 ESODBC_ES_TO_CSQL_KEYWORD; + 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 ESODBC_ES_TO_CSQL_HALF_FLOAT; + } + break; + + /* 11: UNSUPPORTED */ + case sizeof(JSON_COL_UNSUPPORTED) - 1: + if (! wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_UNSUPPORTED), + type_name->cnt)) { + return ESODBC_ES_TO_CSQL_UNSUPPORTED; + } + 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 ESODBC_ES_TO_CSQL_SCALED_FLOAT; + } + break; + + } + ERR("unrecognized Elastic type `" LWPDL "` (%zd).", LWSTR(type_name), + type_name->cnt); + 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); + } +} + +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. + * + * 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; + /* 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; + esodbc_estype_st *types = NULL; + void *pos; + + 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; + } + + if (! bind_types_cols(stmt, type_row)) { + goto end; + } + + /* 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 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 ++) { + 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; + } + + if (! (pos = copy_types_rows(type_row, rows_fetched, types))) { + ERRH(dbc, "failed to process recieved ES/SQL data types."); + goto end; + } + /* 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. @@ -1137,7 +1761,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) @@ -1325,11 +1949,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 +2032,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); } @@ -1454,12 +2084,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, @@ -1469,9 +2112,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 */ @@ -1486,31 +2127,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 - if (! dbc->conn) { + 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: @@ -1557,7 +2184,7 @@ SQLRETURN EsSQLGetConnectAttrW( ERRH(dbc, "unknown Attribute type %d.", Attribute); RET_HDIAGS(DBCH(ConnectionHandle), SQL_STATE_HY092); } - + return SQL_SUCCESS; } 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/defs.h b/driver/defs.h index bcced22f..b8c352b6 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 @@ -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_SSHORT +/* 4: SQL_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 */ +#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_STINYINT +/* 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_STINYINT +/* 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__ */ 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 b0d9e712..6a01c50c 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,41 @@ 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 } @@ -343,7 +330,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." */ @@ -726,7 +712,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; @@ -753,9 +739,9 @@ 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_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) { @@ -964,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; } @@ -974,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; @@ -988,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; } @@ -998,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; } @@ -1021,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; } @@ -1083,6 +1069,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 +1093,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 +1105,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 +1130,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 +1412,7 @@ SQLRETURN EsSQLGetDescFieldW( RecNumber, rec); } + ASSERT_IXD_HAS_ES_TYPE(rec); /* record fields */ switch (FieldIdentifier) { @@ -1437,13 +1428,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; @@ -1472,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; @@ -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; /* */ @@ -1493,15 +1493,29 @@ 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_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; + 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 +1525,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, @@ -1526,7 +1556,7 @@ SQLRETURN EsSQLGetDescFieldW( ERRH(desc, "unknown FieldIdentifier: %d.", FieldIdentifier); RET_HDIAGS(desc, SQL_STATE_HY091); } - + return SQL_SUCCESS; } @@ -1570,11 +1600,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 board, 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) { @@ -1648,6 +1684,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; } @@ -1748,8 +1787,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; } @@ -1817,7 +1855,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; @@ -1828,13 +1866,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) { @@ -1850,8 +1888,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 */ @@ -1894,7 +1930,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) { @@ -1913,38 +1949,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, @@ -1990,13 +1994,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); @@ -2097,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) @@ -2182,12 +2186,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, @@ -2228,18 +2229,16 @@ 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; /* */ 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; @@ -2247,13 +2246,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 +2259,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 +2269,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); @@ -2288,7 +2286,7 @@ SQLRETURN EsSQLSetDescFieldW( ERRH(desc, "unknown FieldIdentifier: %d.", FieldIdentifier); RET_HDIAGS(desc, SQL_STATE_HY091); } - + return SQL_SUCCESS; } diff --git a/driver/handles.h b/driver/handles.h index e5ab6faf..54aa9570 100644 --- a/driver/handles.h +++ b/driver/handles.h @@ -53,6 +53,62 @@ 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; /* maps to rec's .concise_type member */ + 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; /* :-> 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; 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; + SQLLEN display_size; +} esodbc_estype_st; + + /* * https://docs.microsoft.com/en-us/sql/odbc/reference/develop-app/connection-handles : * """ @@ -77,15 +133,14 @@ 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 */ 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; @@ -93,27 +148,13 @@ 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 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; @@ -121,50 +162,48 @@ 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: + * 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; /* SQL_C_ -> AxD, SQL_ -> IxD */ + SQLSMALLINT type; 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 */ - SQLLEN display_size; SQLLEN octet_length; - 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; @@ -176,6 +215,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; @@ -200,11 +253,15 @@ 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 */ 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 cc023501..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 @@ -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) 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 2d713bce..387708e1 100644 --- a/driver/queries.c +++ b/driver/queries.c @@ -24,21 +24,10 @@ #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" -#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; \ @@ -61,132 +50,61 @@ 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; -} - +/* 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) { + 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 +130,42 @@ 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); + 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; + } + } + if (rec->es_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 { + ERRH(stmt, "type lookup failed for `" LWPDL "`.",LWSTR(&col_type)); RET_HDIAG(stmt, SQL_STATE_HY000, MSG_INV_SRV_ANS, 0); } - rec->concise_type = col_stype; - concise_to_type_code(col_stype, &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 @@ -249,7 +174,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]) ? @@ -259,8 +183,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 +393,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 +442,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) @@ -704,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', @@ -735,6 +666,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; @@ -751,14 +683,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; } @@ -773,11 +709,28 @@ 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; } +/* 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) { @@ -785,7 +738,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; @@ -804,11 +756,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? */ @@ -830,24 +778,27 @@ 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_SBIGINT: *(SQLBIGINT *)data_ptr = (SQLBIGINT)ll; write_copied_octets(octet_len_ptr, sizeof(SQLBIGINT), @@ -871,12 +822,75 @@ 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; + + 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); + } + + switch (get_c_target_type(arec, irec)) { + 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: + // TODO + + 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 + * 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, 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; @@ -888,18 +902,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 */ @@ -919,13 +933,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 length to the application */ + out_bytes --; } write_copied_octets(octet_len_ptr, out_bytes, stmt->max_length, irec->meta_type); @@ -940,42 +959,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 length 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 length is indicated, w/o possible buffer truncation (but with + * possible 'network' truncation) */ + write_copied_octets(octet_len_ptr, (chars_0 - /*0-term*/1) * sizeof(*wstr), + stmt->max_length, irec->meta_type); + if (state != SQL_STATE_00000) RET_HDIAGS(stmt, state); return SQL_SUCCESS; @@ -985,7 +1008,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; @@ -1065,16 +1088,20 @@ 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; SQLLEN *octet_len_ptr; esodbc_desc_st *ard, *ird; - SQLSMALLINT target_type; stmt = arec->desc->hdr.stmt; ird = stmt->ird; @@ -1085,23 +1112,19 @@ 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); + 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 @@ -1127,6 +1150,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 +1164,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 +1195,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 +1225,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 +1245,42 @@ 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; @@ -1350,7 +1368,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 */ @@ -1398,7 +1416,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; @@ -1511,15 +1529,15 @@ 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, cchSqlStr); - + ret = EsSQLFreeStmt(stmt, ESODBC_SQL_CLOSE); assert(SQL_SUCCEEDED(ret)); /* can't return error */ - + return attach_sql(stmt, szSqlStr, cchSqlStr); } @@ -1542,7 +1560,7 @@ SQLRETURN EsSQLExecute(SQLHSTMT hstmt) DBGH(stmt, "executing `%.*s` (%zd)", stmt->sqllen, stmt->u8sql, stmt->sqllen); - + return post_statement(stmt); } @@ -1571,12 +1589,12 @@ 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, szSqlStr, cchSqlStr); - + ret = EsSQLFreeStmt(stmt, ESODBC_SQL_CLOSE); assert(SQL_SUCCEEDED(ret)); /* can't return error */ @@ -1597,11 +1615,12 @@ 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: - return rec->precision; - + return rec->es_type->column_size; + case METATYPE_STRING: case METATYPE_BIN: case METATYPE_DATETIME: @@ -1609,7 +1628,13 @@ 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/syntax/sqldescribecol-function#arguments : + * "If the column size cannot be determined, the driver returns 0." */ return 0; } @@ -1619,11 +1644,15 @@ static inline SQLSMALLINT get_col_decdigits(esodbc_rec_st *rec) switch (rec->meta_type) { case METATYPE_DATETIME: case METATYPE_INTERVAL_WSEC: - return rec->precision; - + /* TODO: pending GH#30002 actually */ + return 3; + 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; } @@ -1660,7 +1689,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); @@ -1682,9 +1711,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); @@ -1702,7 +1731,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); @@ -1711,7 +1740,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); @@ -1720,8 +1749,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; @@ -1770,26 +1800,34 @@ 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_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; @@ -1800,43 +1838,57 @@ 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; + 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); break; - + /* 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; /* 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); @@ -1859,12 +1911,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; 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