diff --git a/driver/connect.c b/driver/connect.c index 0fc2dfe3..017f2177 100644 --- a/driver/connect.c +++ b/driver/connect.c @@ -97,6 +97,11 @@ #define ESINFO_KEY_VERSION "version" #define ESINFO_KEY_NUMBER "number" +/* "base" of the version number (how many values supported for each version + * constituent: major, minor, revision) */ +#define VER_LEVEL_MULTI 100L + + /* 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 */ @@ -1411,28 +1416,6 @@ SQLRETURN config_dbc(esodbc_dbc_st *dbc, esodbc_dsn_attrs_st *attrs) goto err; } - /* - * Version checking mode - */ - if (EQ_CASE_WSTR(&attrs->version_checking, - &MK_WSTR(ESODBC_DSN_VC_STRICT)) - || EQ_CASE_WSTR(&attrs->version_checking, - &MK_WSTR(ESODBC_DSN_VC_MAJOR)) -# ifndef NDEBUG - || EQ_CASE_WSTR(&attrs->version_checking, - &MK_WSTR(ESODBC_DSN_VC_NONE)) -# endif /* NDEBUG */ - ) { - dbc->srv_ver.checking = (unsigned char)attrs->version_checking.str[0]; - DBGH(dbc, "version checking mode: %c.", dbc->srv_ver.checking); - } else { - ERRH(dbc, "unknown version checking mode '" LWPDL "'.", - LWSTR(&attrs->version_checking)); - SET_HDIAG(dbc, SQL_STATE_HY000, "invalid version checking mode " - "setting", 0); - goto err; - } - /* "multifield leniency" param */ dbc->mfield_lenient = wstr2bool(&attrs->mfield_lenient); INFOH(dbc, "multifield lenient: %s.", @@ -1523,10 +1506,10 @@ void cleanup_dbc(esodbc_dbc_st *dbc) } else { assert(dbc->no_types == 0); } - if (dbc->srv_ver.string.cnt) { /* .str might be compromized by the union */ - free(dbc->srv_ver.string.str); - dbc->srv_ver.string.str = NULL; - dbc->srv_ver.string.cnt = 0; + if (dbc->srv_ver.str) { + free(dbc->srv_ver.str); + dbc->srv_ver.str = NULL; + dbc->srv_ver.cnt = 0; } if (dbc->catalog.str) { free(dbc->catalog.str); @@ -1652,10 +1635,59 @@ static BOOL parse_es_version_json(esodbc_dbc_st *dbc, cstr_st *rsp_body, return FALSE; } version->str = (SQLWCHAR *)UJReadString(o_number, &version->cnt); - DBGH(dbc, "Elasticsearch'es version number: [%zu] `" LWPDL "`.", + DBGH(dbc, "Elasticsearch's version number: [%zu] `" LWPDL "`.", version->cnt, LWSTR(version)); return TRUE; } + +/* parses a Major.Minor.Revison version format and returns a numerical value + * of it */ +static long version_to_id(wstr_st *ver) +{ + SQLWCHAR *stop; + long id, val; + + assert(ver->str[ver->cnt] == L'\0'); + + errno = 0; + stop = ver->str; + + /* parse major */ + id = wcstol(stop, &stop, /*base*/10); + if (errno || VER_LEVEL_MULTI <= id || id <= 0) { + return -1; + } + if (*stop != L'.') { + return -1; + } else { + stop ++; + } + id *= VER_LEVEL_MULTI; + + /* parse minor */ + val = wcstol(stop, &stop, /*base*/10); + if (errno || VER_LEVEL_MULTI <= val || val < 0) { + return -1; + } + if (*stop != L'.') { + return -1; + } else { + stop ++; + } + id += val; + id *= VER_LEVEL_MULTI; + + /* parse minor */ + val = wcstol(stop, &stop, /*base*/10); + if (errno || VER_LEVEL_MULTI <= val || val < 0) { + return -1; + } + id += val; + id *= VER_LEVEL_MULTI; + + return id; +} + /* * Note: not thread safe: only usable on connection setup. */ @@ -1667,16 +1699,17 @@ static SQLRETURN check_server_version(esodbc_dbc_st *dbc) BOOL is_json; SQLRETURN ret; void *state = NULL; - unsigned char ver_checking; - wstr_st own_ver = WSTR_INIT(STR(DRV_VERSION)); /*build-time define*/ - wstr_st es_ver, ver_no; + static wstr_st min_es_ver = WSTR_INIT(ESODBC_MIN_ES_VER); + wstr_st es_ver; cstr_st es_ver_c; - static const wchar_t err_msg_fmt[] = L"Version mismatch between server (" - WPFWP_LDESC ") and driver (" WPFWP_LDESC "). Please use a driver whose" - " version matches that of your server."; + long es_ver_l, min_es_ver_l; + static const wchar_t err_msg_fmt[] = L"Elasticsearch's version (" + WPFWP_LDESC ") is below minimum required (" ESODBC_MIN_ES_VER ") " + "version. Please use a driver whose version matches that of your " + "server."; /* 32: max length of the version strings for which the explicit message * above is provided. */ - SQLWCHAR wbuff[sizeof(err_msg_fmt)/sizeof(err_msg_fmt[0]) + 2*32]; + SQLWCHAR wbuff[sizeof(err_msg_fmt)/sizeof(err_msg_fmt[0]) + 32]; int n; ret = dbc_curl_set_url(dbc, ESODBC_CURL_ROOT); @@ -1711,54 +1744,43 @@ static SQLRETURN check_server_version(esodbc_dbc_st *dbc) goto err; } - ver_checking = dbc->srv_ver.checking; /* version is returned to application, which requires a NTS => +1 for \0 */ - dbc->srv_ver.string.str = malloc((n + 1) * sizeof(SQLWCHAR)); - if (! dbc->srv_ver.string.str) { + dbc->srv_ver.str = malloc((n + 1) * sizeof(SQLWCHAR)); + if (! dbc->srv_ver.str) { ERRNH(dbc, "OOM for %zu.", (n + 1) * sizeof(SQLWCHAR)); post_diagnostic(dbc, SQL_STATE_HY001, NULL, 0); goto err; } else if (is_json) { - memcpy(dbc->srv_ver.string.str, es_ver.str, n * sizeof(SQLWCHAR)); - } else if (ascii_c2w(es_ver_c.str, dbc->srv_ver.string.str, n) < 0) { + memcpy(dbc->srv_ver.str, es_ver.str, n * sizeof(SQLWCHAR)); + } else if (ascii_c2w(es_ver_c.str, dbc->srv_ver.str, n) < 0) { /* non-ASCII or empty */ - ERRH(dbc, "Elasticsearch version string is invalid."); + BUGH(dbc, "Elasticsearch version string is invalid."); goto err; } - dbc->srv_ver.string.cnt = n; - dbc->srv_ver.string.str[n] = 0; - ver_no = dbc->srv_ver.string; + dbc->srv_ver.cnt = n; + dbc->srv_ver.str[n] = 0; -# ifndef NDEBUG - /* strip any qualifiers (=anything following a first `-`) in debug mode */ - wtrim_at(&ver_no, L'-'); - wtrim_at(&own_ver, L'-'); -# endif /* !NDEBUG */ - - if (tolower(ver_checking) == tolower(ESODBC_DSN_VC_MAJOR[0])) { - /* trim versions to the first dot, i.e. major version */ - wtrim_at(&ver_no, L'.'); - wtrim_at(&own_ver, L'.'); - } - - if (tolower(ver_checking) != tolower(ESODBC_DSN_VC_NONE[0])) { - if (! EQ_WSTR(&ver_no, &own_ver)) { - ERRH(dbc, "version mismatch: server: " LWPDL ", " - "own: " LWPDL ".", LWSTR(&ver_no), LWSTR(&own_ver)); - n = swprintf(wbuff, sizeof(wbuff)/sizeof(wbuff[0]), - err_msg_fmt, LWSTR(&ver_no), LWSTR(&own_ver)); - ret = post_diagnostic(dbc, SQL_STATE_HY000, (n <= 0) ? - L"Version mismatch between server and driver" : - wbuff, 0); - } else { - INFOH(dbc, "server and driver versions aligned to: " LWPDL ".", - LWSTR(&own_ver)); - ret = SQL_SUCCESS; - } -# ifndef NDEBUG + min_es_ver_l = version_to_id(&min_es_ver); + assert(0 < min_es_ver_l); + es_ver_l = version_to_id(&dbc->srv_ver); + if (es_ver_l <= 0) { + BUGH(dbc, "failed to parse Elasticsearch version `" LWPDL "`.", + LWSTR(&dbc->srv_ver)); + goto err; + } + if (es_ver_l < min_es_ver_l) { + ERRH(dbc, "Elasticsearch version `" LWPDL "` is below minimum required" + " `" LWPDL "`.", LWSTR(&dbc->srv_ver), LWSTR(&min_es_ver)); + n = swprintf(wbuff, sizeof(wbuff)/sizeof(wbuff[0]), + err_msg_fmt, LWSTR(&dbc->srv_ver)); + ret = post_diagnostic(dbc, SQL_STATE_HY000, (n <= 0) ? + L"Version mismatch between server and driver" : + wbuff, 0); } else { - WARNH(dbc, "version checking disabled."); -# endif /* !NDEBUG */ + INFOH(dbc, "server version (" LWPDL ") %s minimum required " + "(" ESODBC_MIN_ES_VER ").", LWSTR(&dbc->srv_ver), + es_ver_l == min_es_ver_l ? "meets" : "exceeds"); + ret = SQL_SUCCESS; } free(rsp_body.str); diff --git a/driver/defs.h b/driver/defs.h index 6addf9f1..8c9b594c 100644 --- a/driver/defs.h +++ b/driver/defs.h @@ -122,8 +122,6 @@ /* maximum DNS attribute value lenght (should be long enought to accomodate a * decently long FQ file path name) */ #define ESODBC_DSN_MAX_ATTR_LEN 1024 -/* sample DSN name provisioned with the installation */ -#define ESODBC_DSN_SAMPLE_NAME "Elasticsearch ODBC Sample DSN" /* SQL plugin's REST endpoint for SQL */ #define ELASTIC_SQL_PATH "/_sql" @@ -139,6 +137,8 @@ #define ESODBC_DRIVER_VER STR(DRV_VERSION) \ "(" STR(DRV_SRC_VER) "," STR(DRV_ENCODING) "," STR(DRV_BUILD_TYPE) ")" #define ESODBC_ELASTICSEARCH_NAME "Elasticsearch" +/* the driver will work with an ES node of this version and higher */ +#define ESODBC_MIN_ES_VER "7.7.0" /* * Config defaults @@ -176,8 +176,6 @@ /* default of scientific floats printing */ #define ESODBC_DEF_SCI_FLOATS ESODBC_DSN_FLTS_DEF #define ESODBC_PWD_VAL_SUBST "" -/* default version checking mode: strict, major, none (dbg only) */ -#define ESODBC_DEF_VERSION_CHECKING ESODBC_DSN_VC_STRICT #define ESODBC_DEF_MFIELD_LENIENT "true" #define ESODBC_DEF_ESC_PVA "true" #define ESODBC_DEF_IDX_INC_FROZEN "false" diff --git a/driver/dsn.c b/driver/dsn.c index e0804c88..378976c9 100644 --- a/driver/dsn.c +++ b/driver/dsn.c @@ -880,9 +880,6 @@ void assign_dsn_defaults(esodbc_dsn_attrs_st *attrs) res |= assign_dsn_attr(attrs, &MK_WSTR(ESODBC_DSN_MAX_BODY_SIZE_MB), &MK_WSTR(ESODBC_DEF_MAX_BODY_SIZE_MB), /*overwrite?*/FALSE); - res |= assign_dsn_attr(attrs, &MK_WSTR(ESODBC_DSN_VERSION_CHECKING), - &MK_WSTR(ESODBC_DEF_VERSION_CHECKING), - /*overwrite?*/FALSE); res |= assign_dsn_attr(attrs, &MK_WSTR(ESODBC_DSN_APPLY_TZ), &MK_WSTR(ESODBC_DEF_APPLY_TZ), diff --git a/driver/dsn.h b/driver/dsn.h index fdf866a3..25f17de2 100644 --- a/driver/dsn.h +++ b/driver/dsn.h @@ -51,10 +51,6 @@ #define ESODBC_DSN_CMPSS_AUTO "auto" #define ESODBC_DSN_CMPSS_ON "on" #define ESODBC_DSN_CMPSS_OFF "off" -/* VersionChecking values */ -#define ESODBC_DSN_VC_STRICT "strict" -#define ESODBC_DSN_VC_MAJOR "major" -#define ESODBC_DSN_VC_NONE "none" /* Floats printing */ #define ESODBC_DSN_FLTS_DEF "default" #define ESODBC_DSN_FLTS_SCI "scientific" diff --git a/driver/handles.h b/driver/handles.h index 1770ad80..b9ee26b8 100644 --- a/driver/handles.h +++ b/driver/handles.h @@ -132,10 +132,7 @@ typedef struct struct_dbc { wstr_st dsn; /* data source name SQLGetInfo(SQL_DATA_SOURCE_NAME) */ wstr_st server; /* ~ name; requested with SQLGetInfo(SQL_SERVER_NAME) */ wstr_st catalog; /* cached value; checked against if app setting it */ - union { - wstr_st string; /* version: SQLGetInfo(SQL_DBMS_VER) */ - unsigned char checking; /* first letter of DSN config option value */ - } srv_ver; /* server version */ + wstr_st srv_ver; /* server version: SQLGetInfo(SQL_DBMS_VER) */ cstr_st url; /* SQL URL (posts) */ cstr_st close_url; /* SQL close URL (posts) */ cstr_st root_url; /* root URL (gets) */ diff --git a/driver/info.c b/driver/info.c index 97959d94..d06a5301 100644 --- a/driver/info.c +++ b/driver/info.c @@ -306,8 +306,8 @@ static SQLRETURN getinfo_dbms_product( StringLengthPtr); case SQL_DBMS_VER: DBGH(dbc, "requested: DBMS version (`" LWPDL "`).", - LWSTR(&dbc->srv_ver.string)); - return write_wstr(dbc, InfoValue, &dbc->srv_ver.string, + LWSTR(&dbc->srv_ver)); + return write_wstr(dbc, InfoValue, &dbc->srv_ver, BufferLength, StringLengthPtr); } *handled = FALSE; diff --git a/driver/queries.c b/driver/queries.c index 2d853187..4b567454 100644 --- a/driver/queries.c +++ b/driver/queries.c @@ -41,6 +41,7 @@ static SQLRETURN statement_params_len_cbor(esodbc_stmt_st *stmt, size_t *enc_len, size_t *conv_len); static thread_local cstr_st tz_param; +static cstr_st version = CSTR_INIT(STR(DRV_VERSION)); /* build-time define */ static BOOL print_tz_param(long tz_dst_offt) { @@ -142,10 +143,18 @@ static inline BOOL update_tz_param() BOOL queries_init() { + char *ptr; + /* for the casts in this module */ ASSERT_INTEGER_TYPES_EQUAL(wchar_t, SQLWCHAR); ASSERT_INTEGER_TYPES_EQUAL(char, SQLCHAR); + /* trim qualifiers */ + ptr = strchr(version.str, '-'); + if (ptr) { + version.cnt = ptr - version.str; + } + /* needed to correctly run the unit tests */ return update_tz_param(); } @@ -2973,7 +2982,9 @@ static SQLRETURN statement_len_cbor(esodbc_stmt_st *stmt, size_t *enc_len, /* "time_zone": "-05:45" */ bodylen += cbor_str_obj_len(sizeof(REQ_KEY_TIMEZONE) - 1); bodylen += cbor_str_obj_len(tz_param.cnt); /* lax len */ - *keys += 3; /* field_m._val., idx._inc._frozen, time_zone */ + bodylen += cbor_str_obj_len(sizeof(REQ_KEY_VERSION) - 1); + bodylen += cbor_str_obj_len(version.cnt); + *keys += 4; /* field_m._val., idx._inc._frozen, time_zone, version */ } bodylen += cbor_str_obj_len(sizeof(REQ_KEY_MODE) - 1); bodylen += cbor_str_obj_len(sizeof(REQ_VAL_MODE) - 1); @@ -3033,9 +3044,12 @@ static SQLRETURN statement_len_json(esodbc_stmt_st *stmt, size_t *outlen) /* "time_zone": "-05:45" */ bodylen += sizeof(JSON_KEY_TIMEZONE) - 1; bodylen += tz_param.cnt; + /* "version": */ + bodylen += sizeof(JSON_KEY_VERSION) - 1; + bodylen += version.cnt + /* 2x`"` */2; } - bodylen += sizeof(JSON_KEY_VAL_MODE) - 1; /* "mode": */ - bodylen += sizeof(JSON_KEY_CLT_ID) - 1; /* "client_id": */ + bodylen += sizeof(JSON_KEY_VAL_MODE) - 1; /* "mode": "ODBC" */ + bodylen += sizeof(JSON_KEY_CLT_ID) - 1; /* "client_id": "odbcXX" */ /* TODO: request_/page_timeout */ bodylen += 1; /* } */ @@ -3307,6 +3321,12 @@ static SQLRETURN serialize_to_cbor(esodbc_stmt_st *stmt, cstr_st *dest, } err = cbor_encode_text_string(&map, tz.str, tz.cnt); FAIL_ON_CBOR_ERR(stmt, err); + /* version */ + err = cbor_encode_text_string(&map, REQ_KEY_VERSION, + sizeof(REQ_KEY_VERSION) - 1); + FAIL_ON_CBOR_ERR(stmt, err); + err = cbor_encode_text_string(&map, version.str, version.cnt); + FAIL_ON_CBOR_ERR(stmt, err); } /* mode : ODBC */ err = cbor_encode_text_string(&map, REQ_KEY_MODE, @@ -3424,10 +3444,18 @@ static SQLRETURN serialize_to_json(esodbc_stmt_st *stmt, cstr_st *dest) sizeof(JSON_VAL_TIMEZONE_Z) - 1); pos += sizeof(JSON_VAL_TIMEZONE_Z) - 1; } + /* "version": ... */ + memcpy(body + pos, JSON_KEY_VERSION, sizeof(JSON_KEY_VERSION) - 1); + pos += sizeof(JSON_KEY_VERSION) - 1; + body[pos ++] = '"'; + memcpy(body + pos, version.str, version.cnt); + pos += version.cnt; + body[pos ++] = '"'; } - /* "mode": */ + /* "mode": "ODBC" */ memcpy(body + pos, JSON_KEY_VAL_MODE, sizeof(JSON_KEY_VAL_MODE) - 1); pos += sizeof(JSON_KEY_VAL_MODE) - 1; + /* "client_id": "odbcXX" */ memcpy(body + pos, JSON_KEY_CLT_ID, sizeof(JSON_KEY_CLT_ID) - 1); pos += sizeof(JSON_KEY_CLT_ID) - 1; body[pos ++] = '}'; diff --git a/driver/queries.h b/driver/queries.h index 9912c605..39927856 100644 --- a/driver/queries.h +++ b/driver/queries.h @@ -136,9 +136,9 @@ SQLRETURN EsSQLRowCount(_In_ SQLHSTMT StatementHandle, _Out_ SQLLEN *RowCount); #define REQ_KEY_FETCH "fetch_size" #define REQ_KEY_REQ_TOUT "request_timeout" #define REQ_KEY_PAGE_TOUT "page_timeout" -#define REQ_KEY_TIME_ZONE "time_zone" #define REQ_KEY_MODE "mode" #define REQ_KEY_CLT_ID "client_id" +#define REQ_KEY_VERSION "version" #define REQ_KEY_MULTIVAL "field_multi_value_leniency" #define REQ_KEY_IDX_FROZEN "index_include_frozen" #define REQ_KEY_TIMEZONE "time_zone" @@ -163,11 +163,11 @@ SQLRETURN EsSQLRowCount(_In_ SQLHSTMT StatementHandle, _Out_ SQLLEN *RowCount); #define JSON_KEY_FETCH ", \"" REQ_KEY_FETCH "\": " /* n-th key */ #define JSON_KEY_REQ_TOUT ", \"" REQ_KEY_REQ_TOUT "\": " /* n-th key */ #define JSON_KEY_PAGE_TOUT ", \"" REQ_KEY_PAGE_TOUT "\": " /* n-th key */ -#define JSON_KEY_TIME_ZONE ", \"" REQ_KEY_TIME_ZONE "\": " /* n-th key */ #define JSON_KEY_VAL_MODE ", \"" REQ_KEY_MODE "\": \"" \ REQ_VAL_MODE "\"" /* n-th key */ #define JSON_KEY_CLT_ID ", \"" REQ_KEY_CLT_ID "\": \"" \ REQ_VAL_CLT_ID "\"" /* n-th k. */ +#define JSON_KEY_VERSION ", \"" REQ_KEY_VERSION "\": " /* n-th key */ #define JSON_KEY_MULTIVAL ", \"" REQ_KEY_MULTIVAL "\": " /* n-th */ #define JSON_KEY_IDX_FROZEN ", \"" REQ_KEY_IDX_FROZEN "\": " /* n-th */ #define JSON_KEY_TIMEZONE ", \"" REQ_KEY_TIMEZONE "\": " /* n-th key */ diff --git a/test/connected_dbc.cc b/test/connected_dbc.cc index 936cab67..f686dfbf 100644 --- a/test/connected_dbc.cc +++ b/test/connected_dbc.cc @@ -21,6 +21,8 @@ extern "C" { ConnectedDBC::ConnectedDBC() { cstr_st types = {0}; + static char buff[sizeof(STR(DRV_VERSION))] = {0}; + char *ptr; assert(getenv("TZ") == NULL); @@ -46,6 +48,12 @@ ConnectedDBC::ConnectedDBC() ret = SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt); assert(SQL_SUCCEEDED(ret)); assert(stmt != NULL); + + version = STR(DRV_VERSION); + if ((ptr = strchr(version, '-'))) { + strncpy(buff, STR(DRV_VERSION), ptr - version); + version = buff; + } } ConnectedDBC::~ConnectedDBC() @@ -102,6 +110,7 @@ void ConnectedDBC::assertRequest(const char *params, const char *tz) JSON_KEY_MULTIVAL ESODBC_DEF_MFIELD_LENIENT JSON_KEY_IDX_FROZEN ESODBC_DEF_IDX_INC_FROZEN JSON_KEY_TIMEZONE "%s%s%s" + JSON_KEY_VERSION "\"%s\"" JSON_KEY_VAL_MODE JSON_KEY_CLT_ID "}"; @@ -114,10 +123,10 @@ void ConnectedDBC::assertRequest(const char *params, const char *tz) if (tz) { n = snprintf(expect, sizeof(expect), answ_templ, test_name, params, - "\"", tz, "\""); + "\"", tz, "\"", version); } else { n = snprintf(expect, sizeof(expect), answ_templ, test_name, params, - "", JSON_VAL_TIMEZONE_Z, ""); + "", JSON_VAL_TIMEZONE_Z, "", version); } ASSERT_LT(actual.cnt, sizeof(expect)); ASSERT_EQ(n, actual.cnt); diff --git a/test/connected_dbc.h b/test/connected_dbc.h index 7ff691da..a963ce6f 100644 --- a/test/connected_dbc.h +++ b/test/connected_dbc.h @@ -108,6 +108,7 @@ class ConnectedDBC { SQLRETURN ret; SQLLEN ind_len = SQL_NULL_DATA; const char *test_name; + char *version; ConnectedDBC();