diff --git a/driver/catalogue.c b/driver/catalogue.c index 924fc2a2..69f2a7dd 100644 --- a/driver/catalogue.c +++ b/driver/catalogue.c @@ -35,20 +35,20 @@ SQLRETURN EsSQLTablesW( SQLHSTMT StatementHandle, - _In_reads_opt_(NameLength1) SQLTCHAR *CatalogName, + _In_reads_opt_(NameLength1) SQLWCHAR *CatalogName, SQLSMALLINT NameLength1, - _In_reads_opt_(NameLength2) SQLTCHAR *SchemaName, + _In_reads_opt_(NameLength2) SQLWCHAR *SchemaName, SQLSMALLINT NameLength2, - _In_reads_opt_(NameLength3) SQLTCHAR *TableName, + _In_reads_opt_(NameLength3) SQLWCHAR *TableName, SQLSMALLINT NameLength3, - _In_reads_opt_(NameLength4) SQLTCHAR *TableType, + _In_reads_opt_(NameLength4) SQLWCHAR *TableType, SQLSMALLINT NameLength4) { esodbc_stmt_st *stmt = STMH(StatementHandle); SQLRETURN ret; - SQLTCHAR wbuf[ESODBC_MAX_IDENTIFIER_LEN + sizeof(SQL_TABLES_START) + + SQLWCHAR wbuf[ESODBC_MAX_IDENTIFIER_LEN + sizeof(SQL_TABLES_START) + sizeof(SQL_TABLES_END)]; /*includes 2x\0*/ - SQLTCHAR *table_type, *table_name; + SQLWCHAR *table_type, *table_name; size_t count, pos; if (stmt->metadata_id == SQL_TRUE) @@ -56,27 +56,27 @@ SQLRETURN EsSQLTablesW( /* TODO: server support needed for cat. & sch. name filtering */ - if (CatalogName && (wmemcmp(CatalogName, MK_TSTR(SQL_ALL_CATALOGS), + if (CatalogName && (wmemcmp(CatalogName, MK_WPTR(SQL_ALL_CATALOGS), NameLength1) != 0)) { ERR("filtering by catalog is not supported."); RET_HDIAG(stmt, SQL_STATE_IM001, "catalog filtering not supported", 0); } - if (SchemaName && (wmemcmp(SchemaName, MK_TSTR(SQL_ALL_SCHEMAS), + if (SchemaName && (wmemcmp(SchemaName, MK_WPTR(SQL_ALL_SCHEMAS), NameLength2) != 0)) { ERR("filtering by schemas is not supported."); RET_HDIAG(stmt, SQL_STATE_IM001, "schema filtering not supported", 0); } if (TableType) { table_type = _wcsupr(TableType); - if ((! wcsstr(table_type, MK_TSTR("TABLE"))) && - (! wcsstr(table_type, MK_TSTR("ALIAS")))) { - WARN("no 'TABLE' or 'ALIAS' type fond in filter `"LTPD"`: " + if ((! wcsstr(table_type, MK_WPTR("TABLE"))) && + (! wcsstr(table_type, MK_WPTR("ALIAS")))) { + WARN("no 'TABLE' or 'ALIAS' type fond in filter `"LWPD"`: " "empty result set"); goto empty; } } if (TableName) { - DBG("filtering by table name `"LTPD"`.", TableName); + DBG("filtering by table name `"LWPD"`.", TableName); if (stmt->metadata_id == SQL_TRUE) { // FIXME: string needs escaping of % \\ _ FIXME; @@ -84,23 +84,23 @@ SQLRETURN EsSQLTablesW( table_name = TableName; count = NameLength3; } else { - table_name = MK_TSTR("%"); + table_name = MK_WPTR("%"); count = sizeof("%") - 1; } if (ESODBC_MAX_IDENTIFIER_LEN < NameLength3) { - ERR("TableName `" LTPDL "` too long (limit: %zd).", NameLength3, + ERR("TableName `" LWPDL "` too long (limit: %zd).", NameLength3, TableName, ESODBC_MAX_IDENTIFIER_LEN); RET_HDIAG(stmt, SQL_STATE_HY000, "TableName too long", 0); } /* print SQL to send to server */ /* count/pos always indicate number of characters (not bytes) */ pos = sizeof(SQL_TABLES_START) - 1; - memcpy(wbuf, MK_TSTR(SQL_TABLES_START), pos * sizeof(SQLTCHAR)); - memcpy(&wbuf[pos], table_name, count * sizeof(SQLTCHAR)); + memcpy(wbuf, MK_WPTR(SQL_TABLES_START), pos * sizeof(SQLWCHAR)); + memcpy(&wbuf[pos], table_name, count * sizeof(SQLWCHAR)); pos += count; count = sizeof(SQL_TABLES_END) - 1; - memcpy(&wbuf[pos], MK_TSTR(SQL_TABLES_END), count * sizeof(SQLTCHAR)); + memcpy(&wbuf[pos], MK_WPTR(SQL_TABLES_END), count * sizeof(SQLWCHAR)); pos += count; ret = EsSQLFreeStmt(stmt, ESODBC_SQL_CLOSE); @@ -129,10 +129,11 @@ SQLRETURN EsSQLColumnsW ) { esodbc_stmt_st *stmt = STMH(hstmt); - SQLTCHAR *tablename, *columnname; + SQLWCHAR *tablename, *columnname; SQLSMALLINT tn_clen, cn_clen; /* 8 for \' and spaces and extra */ - SQLTCHAR wbuf[sizeof(ESODBC_SQL_COLUMNS) + 2 * ESODBC_MAX_IDENTIFIER_LEN + 8]; + SQLWCHAR wbuf[sizeof(ESODBC_SQL_COLUMNS) + + 2 * ESODBC_MAX_IDENTIFIER_LEN + 8]; int clen; SQLRETURN ret; @@ -141,12 +142,12 @@ SQLRETURN EsSQLColumnsW /* TODO: server support needed for cat. & sch. name filtering */ - if (szCatalogName && (wmemcmp(szCatalogName, MK_TSTR(SQL_ALL_CATALOGS), + if (szCatalogName && (wmemcmp(szCatalogName, MK_WPTR(SQL_ALL_CATALOGS), cchCatalogName) != 0)) { ERR("filtering by catalog is not supported."); /* TODO? */ RET_HDIAG(stmt, SQL_STATE_IM001, "catalog filtering not supported", 0); } - if (szSchemaName && (wmemcmp(szSchemaName, MK_TSTR(SQL_ALL_SCHEMAS), + if (szSchemaName && (wmemcmp(szSchemaName, MK_WPTR(SQL_ALL_SCHEMAS), cchSchemaName) != 0)) { ERR("filtering by schemas is not supported."); RET_HDIAG(stmt, SQL_STATE_IM001, "schema filtering not supported", 0); @@ -154,35 +155,35 @@ SQLRETURN EsSQLColumnsW if (szTableName && cchTableName) { if (ESODBC_MAX_IDENTIFIER_LEN < cchTableName) { - ERR("TableName `" LTPDL "` too long (limit: %zd).", cchTableName, + ERR("TableName `" LWPDL "` too long (limit: %zd).", cchTableName, szTableName, ESODBC_MAX_IDENTIFIER_LEN); RET_HDIAG(stmt, SQL_STATE_HY000, "TableName too long", 0); } tablename = szTableName; tn_clen = cchTableName; } else { - tablename = MK_TSTR("%"); + tablename = MK_WPTR("%"); tn_clen = sizeof("%") - 1; } if (szColumnName && cchColumnName) { if (ESODBC_MAX_IDENTIFIER_LEN < cchColumnName) { - ERR("ColumnName `" LTPDL "` too long (limit: %zd).", cchColumnName, + ERR("ColumnName `" LWPDL "` too long (limit: %zd).", cchColumnName, szColumnName, ESODBC_MAX_IDENTIFIER_LEN); RET_HDIAG(stmt, SQL_STATE_HY000, "ColumnName too long", 0); } columnname = szColumnName; cn_clen = cchColumnName; } else { - columnname = MK_TSTR("%"); + columnname = MK_WPTR("%"); cn_clen = sizeof("%") - 1; } - clen = swprintf(wbuf, sizeof(wbuf)/sizeof(SQLTCHAR), - MK_TSTR("%s '%.*s' ESCAPE '" ESODBC_PATTERN_ESCAPE "' " + clen = swprintf(wbuf, sizeof(wbuf)/sizeof(SQLWCHAR), + MK_WPTR("%s '%.*s' ESCAPE '" ESODBC_PATTERN_ESCAPE "' " "'%.*s' ESCAPE '" ESODBC_PATTERN_ESCAPE "'"), - MK_TSTR(ESODBC_SQL_COLUMNS), + MK_WPTR(ESODBC_SQL_COLUMNS), tn_clen, tablename, cn_clen, columnname); - if (clen <= 0 || sizeof(wbuf)/sizeof(SQLTCHAR) <= clen) { /* == */ + if (clen <= 0 || sizeof(wbuf)/sizeof(SQLWCHAR) <= clen) { /* == */ ERRN("SQL printing failed (buffer too small (%zdB)?).", sizeof(wbuf)); RET_HDIAGS(stmt, SQL_STATE_HY000); } diff --git a/driver/connect.c b/driver/connect.c index e2332545..026780e2 100644 --- a/driver/connect.c +++ b/driver/connect.c @@ -18,16 +18,19 @@ #include #include #include +#include #include "connect.h" #include "queries.h" #include "log.h" #include "info.h" +#include "util.h" /* HTTP headers default for every request */ #define HTTP_ACCEPT_JSON "Accept: application/json" #define HTTP_CONTENT_TYPE_JSON "Content-Type: application/json; charset=utf-8" +/* JSON body build elements */ #define JSON_SQL_QUERY_START "{\"query\":\"" #define JSON_SQL_QUERY_MID "\"" #define JSON_SQL_QUERY_MID_FETCH JSON_SQL_QUERY_MID ",\"fetch_size\":" @@ -35,6 +38,52 @@ #define JSON_SQL_CURSOR_START "{\"cursor\":\"" #define JSON_SQL_CURSOR_END "\"}" +/* attribute keywords used in connection strings */ +#define CONNSTR_KW_DRIVER "Driver" +#define CONNSTR_KW_DSN "DSN" +#define CONNSTR_KW_PWD "PWD" +#define CONNSTR_KW_UID "UID" +#define CONNSTR_KW_ADDRESS "Address" +#define CONNSTR_KW_PORT "Port" +#define CONNSTR_KW_SECURE "Secure" +#define CONNSTR_KW_TIMEOUT "Timeout" +#define CONNSTR_KW_FOLLOW "Follow" +#define CONNSTR_KW_CATALOG "Catalog" +#define CONNSTR_KW_PACKING "Packing" +#define CONNSTR_KW_MAX_FETCH_SIZE "MaxFetchSize" +#define CONNSTR_KW_MAX_BODY_SIZE_MB "MaxBodySizeMB" +#define CONNSTR_KW_TRACE_FILE "TraceFile" +#define CONNSTR_KW_TRACE_LEVEL "TraceLevel" + +#define ODBC_REG_SUBKEY_PATH "SOFTWARE\\ODBC\\ODBC.INI" +#define REG_HKLM "HKEY_LOCAL_MACHINE" +#define REG_HKCU "HKEY_CURRENT_USER" + +/* max lenght 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 + + +/* stucture to collect all attributes in a connection string */ +typedef struct { + wstr_st driver; + wstr_st dsn; + wstr_st pwd; + wstr_st uid; + wstr_st address; + wstr_st port; + wstr_st secure; + wstr_st timeout; + wstr_st follow; + wstr_st catalog; + wstr_st packing; + wstr_st max_fetch_size; + wstr_st max_body_size; + wstr_st trace_file; + wstr_st trace_level; +} config_attrs_st; + /* * HTTP headers used for all requests (Content-Type, Accept). */ @@ -149,55 +198,9 @@ static SQLRETURN init_curl(esodbc_dbc_st *dbc) { CURLcode res; CURL *curl; - int n; - char *host, *port_s, *endptr; - char url[ESODBC_MAX_URL_LEN]; - long port, secure, timeout, follow; assert(! dbc->curl); - // FIXME: derive from connstr: - host = ESODBC_DEFAULT_HOST; - port_s = NULL; - secure = ESODBC_DEFAULT_SEC; - timeout = ESODBC_DEFAULT_TIMEOUT; - follow = ESODBC_DEFAULT_FOLLOW; - - /* check host and port values */ - if (ESODBC_MAX_DNS_LEN < strlen(host)) { - ERR("host name `%s` longer than max allowed (%d)", host, - ESODBC_MAX_DNS_LEN); - RET_HDIAG(dbc, SQL_STATE_IM010, "host name longer than max", 0); - } - if (port_s) { - if (/*65535*/5 < strlen(port_s)) { - ERR("port `%s` longer than allowed (5).", port_s); - /* reusing code */ - RET_HDIAG(dbc, SQL_STATE_IM010, "port longer than max", 0); - } - errno = 0; - port = strtol(port_s, &endptr, 10); - if (errno) { - ERRN("failed to scan port `%s` to integer.", port_s); - RET_HDIAG(dbc, SQL_STATE_IM010, "invalid port number", 0); - } else if (*endptr) { - ERR("failed to scan all chars in port `%s`.", port_s); - RET_HDIAG(dbc, SQL_STATE_IM010, "invalid port number", 0); - } else if (USHRT_MAX < port) { - ERR("port value %d higher than max (%u)", port, USHRT_MAX); - RET_HDIAG(dbc, SQL_STATE_IM010, "port value to high", 0); - } - } else { - port = ESODBC_DEFAULT_PORT; - } - n = snprintf(url, sizeof(url), "http%s://%s:%d" ELASTIC_SQL_PATH, - secure ? "s" : "", host, port); - if (n < 0 || sizeof(url) <= n) { - ERR("libcurl: failed to build destination URL."); - RET_HDIAG(dbc, SQL_STATE_IM010, "failed to build destination URL", 0); - } - DBG("libcurl: SQL URL to connect to %s.", url); - /* get a libcurl handle */ curl = curl_easy_init(); if (! curl) { @@ -214,11 +217,10 @@ static SQLRETURN init_curl(esodbc_dbc_st *dbc) } #endif /* NDEBUG */ - /* set URL to connect to */ - res = curl_easy_setopt(curl, CURLOPT_URL, url); + res = curl_easy_setopt(curl, CURLOPT_URL, dbc->url); if (res != CURLE_OK) { - ERR("libcurl: failed to set URL `%s`: %s (%d).", url, + ERR("libcurl: failed to set URL `%s`: %s (%d).", dbc->url, curl_easy_strerror(res), res); SET_HDIAG(dbc, SQL_STATE_HY000, "failed to init the transport", 0); goto err; @@ -242,7 +244,7 @@ static SQLRETURN init_curl(esodbc_dbc_st *dbc) } /* set the behavior for redirection */ - res = curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, follow); + res = curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, dbc->follow); if (res != CURLE_OK) { ERR("libcurl: failed to set redirection: %s (%d).", curl_easy_strerror(res), res); @@ -383,105 +385,14 @@ static SQLRETURN post_request(esodbc_stmt_st *stmt, "failed: '%s' (%d).", timeout, blen, u8body, res != CURLE_OK ? curl_easy_strerror(res) : "", res); err_net: /* the error occured after the request hit hit the network */ - if (abuff) - free(abuff); cleanup_curl(dbc); - if (abuff) /* if buffer had been set, the error occured in _perform() */ + if (abuff) { + free(abuff); + abuff = NULL; + /* if buffer had been set, the error occured in _perform() */ RET_HDIAG(stmt, SQL_STATE_08S01, "data transfer failure", res); - else - RET_HDIAG(stmt, SQL_STATE_HY000, "failed to init transport", res); -} - -/* retuns the lenght 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) -{ - size_t i, newlen = 0; - unsigned char uchar; - for (i = 0; i < len; i ++) { - uchar = (unsigned char)json[i]; - switch(uchar) { - case '"': - case '\\': - case '/': - case '\b': - case '\f': - case '\n': - case '\r': - case '\t': - newlen += /* '\' + json[i] */2; - break; - default: - newlen += (0x20 <= uchar) ? 1 : /* \u00XX */6; - // break - } } - return newlen; -} - -/* - * JSON-escapes a string. - * If string len is 0, it assumes a NTS. - * If output buffer (jout) is NULL, it returns the buffer size needed for - * escaping. - * Returns number of used bytes in buffer (which might be less than buffer - * size, if some char needs an escaping longer than remaining space). - */ -static size_t json_escape(const char *jin, size_t inlen, char *jout, - size_t outlen) -{ - size_t i, pos; - unsigned char uchar; - -#define I16TOA(_x) (10 <= (_x)) ? 'A' + (_x) - 10 : '0' + (_x) - - if (! inlen) - inlen = strlen(jin); - if (! jout) - return json_escaped_len(jin, inlen); - - for (i = 0, pos = 0; i < inlen; i ++) { - uchar = jin[i]; - switch(uchar) { - do { - case '\b': uchar = 'b'; break; - case '\f': uchar = 'f'; break; - case '\n': uchar = 'n'; break; - case '\r': uchar = 'r'; break; - case '\t': uchar = 't'; break; - } while (0); - case '"': - case '\\': - case '/': - if (outlen <= pos + 1) { - i = inlen; // break the for loop - continue; - } - jout[pos ++] = '\\'; - jout[pos ++] = (char)uchar; - break; - default: - if (0x20 <= uchar) { - if (outlen <= pos) { - i = inlen; - continue; - } - jout[pos ++] = uchar; - } else { // case 0x00 .. 0x1F - if (outlen <= pos + 5) { - i = inlen; - continue; - } - memcpy(jout + pos, "\\u00", sizeof("\\u00") - 1); - pos += sizeof("\\u00") - 1; - jout[pos ++] = I16TOA(uchar >> 4); - jout[pos ++] = I16TOA(uchar & 0xF); - } - break; - } - } - return pos; -#undef I16TOA + RET_HDIAG(stmt, SQL_STATE_HY000, "failed to init transport", res); } /* @@ -511,7 +422,7 @@ SQLRETURN post_statement(esodbc_stmt_st *stmt) u8len = WCS2U8(stmt->rset.ecurs, (int)stmt->rset.eccnt, u8curs, sizeof(u8curs)); if (u8len <= 0) { - ERRSTMT(stmt, "failed to convert cursor `" LTPDL "` to UTF8: %d.", + ERRSTMT(stmt, "failed to convert cursor `" LWPDL "` to UTF8: %d.", stmt->rset.eccnt, stmt->rset.ecurs, WCS2U8_ERRNO()); RET_HDIAGS(stmt, SQL_STATE_24000); } @@ -575,7 +486,7 @@ SQLRETURN post_statement(esodbc_stmt_st *stmt) pos += sizeof(JSON_SQL_QUERY_END) - /* but don't account for it */1; } - ret = post_request(stmt, ESODBC_DEFAULT_TIMEOUT, body, pos); + ret = post_request(stmt, dbc->timeout, body, pos); if (body != buff) free(body); /* hehe */ @@ -614,86 +525,554 @@ static SQLRETURN test_connect(CURL *curl) return SQL_SUCCESS; } -SQLRETURN EsSQLDriverConnectW -( - SQLHDBC hdbc, - SQLHWND hwnd, - /* "A full connection string, a partial connection string, or an empty - * string" */ - _In_reads_(cchConnStrIn) SQLWCHAR* szConnStrIn, - /* "Length of *InConnectionString, in characters if the string is - * Unicode, or bytes if string is ANSI or DBCS." */ - SQLSMALLINT cchConnStrIn, - /* "Pointer to a buffer for the completed connection string. Upon - * successful connection to the target data source, this buffer - * contains the completed connection string." */ - _Out_writes_opt_(cchConnStrOutMax) SQLWCHAR* szConnStrOut, - /* "Length of the *OutConnectionString buffer, in characters." */ - SQLSMALLINT cchConnStrOutMax, - /* "Pointer to a buffer in which to return the total number of - * characters (excluding the null-termination character) available to - * return in *OutConnectionString" */ - _Out_opt_ SQLSMALLINT* pcchConnStrOut, - /* "Flag that indicates whether the Driver Manager or driver must - * prompt for more connection information" */ - SQLUSMALLINT fDriverCompletion -) +static BOOL assign_config_attr(config_attrs_st *attrs, + wstr_st *keyword, wstr_st *value, BOOL overwrite) { - esodbc_dbc_st *dbc = DBCH(hdbc); - SQLRETURN ret; - SQLTCHAR *connstr; - size_t n, i; - char *url = NULL; + struct { + wstr_st *kw; + wstr_st *val; + } *iter, map[] = { + {&MK_WSTR(CONNSTR_KW_DRIVER), &attrs->driver}, + {&MK_WSTR(CONNSTR_KW_DSN), &attrs->dsn}, + {&MK_WSTR(CONNSTR_KW_PWD), &attrs->pwd}, + {&MK_WSTR(CONNSTR_KW_UID), &attrs->uid}, + {&MK_WSTR(CONNSTR_KW_ADDRESS), &attrs->address}, + {&MK_WSTR(CONNSTR_KW_PORT), &attrs->port}, + {&MK_WSTR(CONNSTR_KW_SECURE), &attrs->secure}, + {&MK_WSTR(CONNSTR_KW_TIMEOUT), &attrs->timeout}, + {&MK_WSTR(CONNSTR_KW_FOLLOW), &attrs->follow}, + {&MK_WSTR(CONNSTR_KW_CATALOG), &attrs->catalog}, + {&MK_WSTR(CONNSTR_KW_PACKING), &attrs->packing}, + {&MK_WSTR(CONNSTR_KW_MAX_FETCH_SIZE), &attrs->max_fetch_size}, + {&MK_WSTR(CONNSTR_KW_MAX_BODY_SIZE_MB), &attrs->max_body_size}, + {&MK_WSTR(CONNSTR_KW_TRACE_FILE), &attrs->trace_file}, + {&MK_WSTR(CONNSTR_KW_TRACE_LEVEL), &attrs->trace_level}, + {NULL, NULL} + }; + + for (iter = &map[0]; iter->kw; iter ++) { + if (! EQ_CASE_WSTR(iter->kw, keyword)) + continue; + /* it's a match: has it been assigned already? */ + if (iter->val->cnt) { + if (! overwrite) { + INFO("multiple occurances of keyword '" LWPDL "'; " + "ignoring new `" LWPDL "`, keeping `" LWPDL "`.", + LWSTR(iter->kw), LWSTR(value), LWSTR(iter->val)); + continue; + } + INFO("multiple occurances of keyword '" LWPDL "'; " + "overwriting old `" LWPDL "` with new `" LWPDL "`.", + LWSTR(iter->kw), LWSTR(iter->val), LWSTR(value)); + } + *iter->val = *value; + return TRUE; + } + + return FALSE; +} + +/* + * Advance position in string, skipping white space. + * if exended is true, `;` will be treated as white space too. + */ +static SQLWCHAR* skip_ws(SQLWCHAR **pos, SQLWCHAR *end, BOOL extended) +{ + while (*pos < end) { + switch(**pos) { + case ' ': + case '\t': + case '\r': + case '\n': + (*pos)++; + break; + + case '\0': + return NULL; + + case ';': + if (extended) { + (*pos)++; + break; + } + // no break; + + default: + return *pos; + } + } + + /* end of string reached */ + return NULL; +} + +/* + * Parse a keyword or a value. + * Within braces, any character is allowed, safe for \0 (i.e. no "bla{bla\0" + * is supported as keyword or value). + * Braces within braces are allowed. + */ +static BOOL parse_token(BOOL is_value, SQLWCHAR **pos, SQLWCHAR *end, + wstr_st *token) +{ + BOOL brace_escaped = FALSE; + int open_braces = 0; + SQLWCHAR *start = *pos; + BOOL stop = FALSE; + + while (*pos < end && (! stop)) { + switch (**pos) { + case '\\': + if (! is_value) { + ERR("keywords and data source names cannot contain " + "the backslash."); + return FALSE; + } + (*pos)++; + break; + + case ' ': + case '\t': + case '\r': + case '\n': + if (open_braces || is_value) + (*pos)++; + else + stop = TRUE; + break; + + case '=': + if (open_braces || is_value) + (*pos)++; + else + stop = TRUE; + break; + + case ';': + if (open_braces) { + (*pos)++; + } else if (is_value) { + stop = TRUE; + } else { + ERR("';' found while parsing keyword"); + return FALSE; + } + break; + + case '\0': + if (open_braces) { + ERR("null terminator found while within braces"); + return FALSE; + } else if (! is_value) { + ERR("null terminator found while parsing keyword."); + return FALSE; + } /* else: \0 used as delimiter of value string */ + stop = TRUE; + break; + + case '{': + if (*pos == start) + open_braces ++; + else if (open_braces) { + ERR("token started with opening brace, so can't use " + "inner braces"); + /* b/c: `{foo{ = }}bar} = val`; `foo{bar}baz` is fine */ + return FALSE; + } + (*pos)++; + break; - DBG("Input connection string: '"LTPD"' (%d).", szConnStrIn, cchConnStrIn); - if (! pcchConnStrOut) { - ERR("null pcchConnStrOut parameter"); - RET_HDIAGS(dbc, SQL_STATE_HY000); + case '}': + if (open_braces) { + open_braces --; + brace_escaped = TRUE; + stop = TRUE; + } + (*pos)++; + break; + + default: + (*pos)++; + } } - // - // FIXME: get and parse the connection string. - // -#if 0 -/* Options for SQLDriverConnect */ -#define SQL_DRIVER_NOPROMPT 0 -#define SQL_DRIVER_COMPLETE 1 -#define SQL_DRIVER_PROMPT 2 -#define SQL_DRIVER_COMPLETE_REQUIRED 3 -#endif - - // FIXME: set the proper connection string; - connstr = MK_TSTR("connection string placeholder"); - dbc->connstr = _wcsdup(connstr); - if (! dbc->connstr) { - ERRN("failed to dup string `%s`.", connstr); - RET_HDIAGS(dbc, SQL_STATE_HY001); + if (open_braces) { + ERR("string finished with open braces."); + return FALSE; + } + + token->str = start + (brace_escaped ? 1 : 0); + token->cnt = (*pos - start) - (brace_escaped ? 2 : 0); + return TRUE; +} + +static SQLWCHAR* parse_separator(SQLWCHAR **pos, SQLWCHAR *end) +{ + if (*pos < end) { + if (**pos == '=') { + (*pos)++; + return *pos; + } + } + return NULL; +} + +/* + * - "keywords and attribute values that contain the characters []{}(),;?*=!@ + * not enclosed with braces should be avoided"; => allowed. + * - "value of the DSN keyword cannot consist only of blanks and should not + * contain leading blanks"; + * - "keywords and data source names cannot contain the backslash (\) + * character."; + * - "value enclosed with braces ({}) containing any of the characters + * []{}(),;?*=!@ is passed intact to the driver."; + * + * foo{bar}=baz=foo; => "foo{bar}" = "baz=foo" + * + * * `=` is delimiter, unless within {} + * * `{` and `}` allowed within {} + * * brances need to be returned to out-str; + */ +static BOOL parse_connection_string(config_attrs_st *attrs, + SQLWCHAR* szConnStrIn, SQLSMALLINT cchConnStrIn) +{ + + SQLWCHAR *pos; + SQLWCHAR *end; + wstr_st keyword, value; + + /* parse and assign attributes in connection string */ + pos = szConnStrIn; + end = pos + (cchConnStrIn == SQL_NTS ? SHRT_MAX : cchConnStrIn); + + while (skip_ws(&pos, end, TRUE)) { + if (! parse_token(FALSE, &pos, end, &keyword)) { + ERR("failed to parse keyword at position %zd", pos - szConnStrIn); + return FALSE; + } + + if (! skip_ws(&pos, end, FALSE)) + return FALSE; + + if (! parse_separator(&pos, end)) { + ERR("failed to parse separator (`=`) at position %zd", + pos - szConnStrIn); + return FALSE; + } + + if (! skip_ws(&pos, end, FALSE)) + return FALSE; + + if (! parse_token(TRUE, &pos, end, &value)) { + ERR("failed to parse value at position %zd", pos - szConnStrIn); + return FALSE; + } + + DBG("read connection string attribute: `" LWPDL "` = `" LWPDL "`.", + LWSTR(&keyword), LWSTR(&value)); + if (! assign_config_attr(attrs, &keyword, &value, TRUE)) + ERR("keyword '" LWPDL "' is unknown, ignoring it.", + LWSTR(&keyword)); + } + + return TRUE; +} + +static inline BOOL needs_braces(wstr_st *token) +{ + int i; + + for (i = 0; i < token->cnt; i ++) { + switch(token->str[i]) { + case ' ': + case '\t': + case '\r': + case '\n': + case '=': + case ';': + return TRUE; + } + } + return FALSE; +} + +/* build a connection string to be written in the DSN files */ +static BOOL write_connection_string(config_attrs_st *attrs, + SQLWCHAR *szConnStrOut, SQLSMALLINT cchConnStrOutMax, + SQLSMALLINT *pcchConnStrOut) +{ + int n, braces; + size_t pos; + wchar_t *format; + struct { + wstr_st *val; + char *kw; + } *iter, map[] = { + {&attrs->driver, CONNSTR_KW_DRIVER}, + {&attrs->dsn, CONNSTR_KW_DSN}, + {&attrs->pwd, CONNSTR_KW_PWD}, + {&attrs->uid, CONNSTR_KW_UID}, + {&attrs->address, CONNSTR_KW_ADDRESS}, + {&attrs->port, CONNSTR_KW_PORT}, + {&attrs->secure, CONNSTR_KW_SECURE}, + {&attrs->timeout, CONNSTR_KW_TIMEOUT}, + {&attrs->follow, CONNSTR_KW_FOLLOW}, + {&attrs->catalog, CONNSTR_KW_CATALOG}, + {&attrs->packing, CONNSTR_KW_PACKING}, + {&attrs->max_fetch_size, CONNSTR_KW_MAX_FETCH_SIZE}, + {&attrs->max_body_size, CONNSTR_KW_MAX_BODY_SIZE_MB}, + {&attrs->trace_file, CONNSTR_KW_TRACE_FILE}, + {&attrs->trace_level, CONNSTR_KW_TRACE_LEVEL}, + {NULL, NULL} + }; + + for (iter = &map[0], pos = 0; iter->val; iter ++) { + if (iter->val->cnt) { + braces = needs_braces(iter->val) ? 2 : 0; + if (cchConnStrOutMax && szConnStrOut) { + /* swprintf will fail if formated string would overrun the + * buffer size */ + if (cchConnStrOutMax - pos < iter->val->cnt + braces) { + /* indicate that we've reached buffer limits: only account + * for how long the string would be */ + cchConnStrOutMax = 0; + pos += iter->val->cnt + braces; + continue; + } + if (braces) + format = WPFCP_DESC "={" WPFWP_LDESC "};"; + else + format = WPFCP_DESC "=" WPFWP_LDESC ";"; + n = swprintf(szConnStrOut + pos, cchConnStrOutMax - pos, + format, iter->kw, LWSTR(iter->val)); + if (n < 0) { + ERRN("failed to outprint connection string (space left:" + " %d; needed: %d).", cchConnStrOutMax - pos, + iter->val->cnt); + return FALSE; + } else { + pos += n; + } + } else { + /* simply increment the counter, since the untruncated lenght + * need to be returned to the app */ + pos += iter->val->cnt + braces; + } + } } - // FIXME: read from connection string - dbc->amax = ESODBC_DEFAULT_MAX_BODY_SIZE; - dbc->timeout = ESODBC_TIMEOUT_DEFAULT; - dbc->fetch.max = ESODBC_DEF_FETCH_SIZE; - dbc->metadata_id = SQL_FALSE; - dbc->async_enable = SQL_ASYNC_ENABLE_OFF; + *pcchConnStrOut = (SQLSMALLINT)pos; + DBG("Output connection string: `" LWPD "`; out len: %d.", szConnStrOut, + pos); + return TRUE; +} + +/* + * init dbc from configured attributes + */ +static SQLRETURN process_config(esodbc_dbc_st *dbc, config_attrs_st *attrs) +{ + esodbc_state_et state = SQL_STATE_HY000; + int n, cnt; + SQLWCHAR urlw[ESODBC_MAX_URL_LEN]; + BOOL secure; + long timeout, max_body_size, max_fetch_size; + + /* + * build connection URL + */ + secure = wstr2bool(&attrs->secure); + cnt = swprintf(urlw, sizeof(urlw)/sizeof(urlw[0]), + L"http" WPFCP_DESC "://" WPFWP_LDESC ":" WPFWP_LDESC + ELASTIC_SQL_PATH, secure ? "s" : "", LWSTR(&attrs->address), + LWSTR(&attrs->port)); + if (cnt < 0) { + ERRN("failed to print URL out of address: `" LWPDL "` [%zd], " + "port: `" LWPDL "` [%zd].", LWSTR(&attrs->address), + LWSTR(&attrs->port)); + goto err; + } + /* lenght of URL converted to U8 */ + n = WCS2U8(urlw, cnt, NULL, 0); + if (! n) { + ERRN("failed to estimate U8 conversion space necessary for `" + LWPDL " [%d]`.", cnt, urlw, cnt); + goto err; + } + dbc->url = malloc(n + /*0-term*/1); + if (! dbc->url) { + ERRN("OOM for size: %d.", n); + state = SQL_STATE_HY001; + goto err; + } + n = WCS2U8(urlw, cnt, dbc->url, n); + if (! n) { + ERRN("failed to U8 convert URL `" LWPDL "` [%d].", cnt, urlw, cnt); + goto err; + } + dbc->url[n] = 0; + /* URL should be 0-term'd, as printed by swprintf */ + INFO("DBC@0x%p: connection URL: `%s`.", dbc, dbc->url); + + /* follow param for liburl */ + dbc->follow = wstr2bool(&attrs->follow); + INFO("DBC@0x%p: follow: %s.", dbc, dbc->follow ? "true" : "false"); + + /* + * request timeout for liburl: negative reset to 0 + */ + if (! wstr2long(&attrs->timeout, &timeout)) { + ERR("failed to convert '" LWPDL "' to long.", LWSTR(&attrs->timeout)); + goto err; + } + if (timeout < 0) { + WARN("DBC@0x%p: set timeout is negative (%ld), normalized to 0.", dbc, + timeout); + timeout = 0; + } + dbc->timeout = (SQLUINTEGER)timeout; + INFO("DBC@0x%p: timeout: %lu.", dbc, dbc->timeout); + + /* + * set max body size + */ + if (! wstr2long(&attrs->max_body_size, &max_body_size)) { + ERR("failed to convert '" LWPDL "' to long.", + LWSTR(&attrs->max_body_size)); + goto err; + } + if (max_body_size < 0) { + ERR("'%s' setting can't be negative (%ld).", + CONNSTR_KW_MAX_BODY_SIZE_MB, max_body_size); + goto err; + } else { + dbc->amax = max_body_size * 1024 * 1024; + } + INFO("DBC@0x%p: max body size: %zd.", dbc, dbc->amax); + + /* + * set max fetch size + */ + if (! wstr2long(&attrs->max_fetch_size, &max_fetch_size)) { + ERR("failed to convert '" LWPDL "' to long.", + LWSTR(&attrs->max_fetch_size)); + goto err; + } + if (max_fetch_size < 0) { + ERR("'%s' setting can't be negative (%ld).", CONNSTR_KW_MAX_FETCH_SIZE, + max_fetch_size); + goto err; + } else { + dbc->fetch.max = max_fetch_size; + } /* set the string representation of fetch_size, once for all STMTs */ if (dbc->fetch.max) { - for (n = dbc->fetch.max, dbc->fetch.slen = 0; n; n /= 10) - dbc->fetch.slen ++; + dbc->fetch.slen = (char)attrs->max_fetch_size.cnt; dbc->fetch.str = malloc(dbc->fetch.slen + /*\0*/1); if (! dbc->fetch.str) { ERRN("failed to alloc %zdB.", dbc->fetch.slen); RET_HDIAGS(dbc, SQL_STATE_HY001); } dbc->fetch.str[dbc->fetch.slen] = 0; - for (n = dbc->fetch.max, i = dbc->fetch.slen; 0 < i; n /= 10, i --) - dbc->fetch.str[i - 1] = '0' + (n % 10); + ansi_w2c(attrs->max_fetch_size.str, dbc->fetch.str, dbc->fetch.slen); + } + INFO("DBC@0x%p: fetch_size: %s.", dbc, + dbc->fetch.str ? dbc->fetch.str : "none" ); + + // TODO: catalog handling - DBG("DBC@0x%p, fetch_size: `%.*s` (%c).", dbc, dbc->fetch.slen, - dbc->fetch.str, dbc->fetch.slen); + /* + * set the REST body format: JSON/CBOR + */ + if (EQ_CASE_WSTR(&attrs->packing, &MK_WSTR("JSON"))) { + dbc->pack_json = TRUE; + } else if (EQ_CASE_WSTR(&attrs->packing, &MK_WSTR("CBOR"))) { + dbc->pack_json = FALSE; + } else { + ERR("unknown packing encoding '" LWPDL "'.", LWSTR(&attrs->packing)); + goto err; } + INFO("DBC@0x%p: pack JSON: %s.", dbc, dbc->pack_json ? "true" : "false"); + // TODO: trace file handling + // TODO: trace level handling + + return SQL_SUCCESS; +err: + if (state == SQL_STATE_HY000) + RET_HDIAG(dbc, state, "invalid configuration parameter", 0); + RET_HDIAGS(dbc, state); +} + +static inline void assign_defaults(config_attrs_st *attrs) +{ + /* assign defaults where not assigned and applicable */ + if (! attrs->address.cnt) + attrs->address = MK_WSTR(ESODBC_DEF_HOST); + if (! attrs->port.cnt) + attrs->port = MK_WSTR(ESODBC_DEF_PORT); + if (! attrs->secure.cnt) + attrs->secure = MK_WSTR(ESODBC_DEF_SECURE); + if (! attrs->timeout.cnt) + attrs->timeout = MK_WSTR(ESODBC_DEF_TIMEOUT); + if (! attrs->follow.cnt) + 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); +} + +/* release all resources, except the handler itself */ +void cleanup_dbc(esodbc_dbc_st *dbc) +{ + if (dbc->url) { + free(dbc->url); + dbc->url = NULL; + } + if (dbc->fetch.str) { + free(dbc->fetch.str); + dbc->fetch.str = NULL; + dbc->fetch.slen = 0; + } + if (dbc->dsn.str && 0 < dbc->dsn.cnt) { + free(dbc->dsn.str); + dbc->dsn.str = NULL; + } else { + /* small hack: the API allows querying for a DSN also in the case a + * connection actually fails to be established; in which case the + * actual DSN value hasn't been allocated/copied. */ + dbc->dsn.str = MK_WPTR(""); + dbc->dsn.cnt = 0; + } + assert(dbc->abuff == NULL); /* reminder for when going multithreaded */ + cleanup_curl(dbc); +} + +static SQLRETURN do_connect(esodbc_dbc_st *dbc, config_attrs_st *attrs) +{ + SQLRETURN ret; + char *url = NULL; + + /* multiple connection attempts are possible (when prompting user) */ + cleanup_dbc(dbc); + + ret = process_config(dbc, attrs); + if (! SQL_SUCCEEDED(ret)) + return ret; + + /* init libcurl objects */ ret = init_curl(dbc); if (! SQL_SUCCEEDED(ret)) { ERR("failed to init transport for DBC 0x%p.", dbc); @@ -713,32 +1092,268 @@ SQLRETURN EsSQLDriverConnectW DBG("test connection to URL %s OK.", url); } - /* return the final connection string */ - if (szConnStrOut) { - // TODO: Driver param - n = swprintf(szConnStrOut, cchConnStrOutMax, WPFWP_DESC ";" - "Server="WPFCP_DESC";" - "Port=%d;" - "Secure=%d;" - "Packing="WPFCP_DESC";" - "MaxFetchSize=%d;" - "MaxBodySize="WPFCP_DESC";" - "Timeout=%d;" - "Follow=%d;" - "TraceFile="WPFCP_DESC";" - "TraceLevel="WPFCP_DESC";" - "User=user;Password=pass;Catalog=cat", - szConnStrIn, "host", 9200, 0, "json", 100, "10M", -1, 0, - "C:\\foo.txt", "DEBUG"); - if (n < 0) { - ERRN("failed to outprint connection string."); - RET_HDIAGS(dbc, SQL_STATE_HY000); + return SQL_SUCCESS; +} + +#if defined(_WIN32) || defined (WIN32) +/* + * Reads system registry for ODBC DSN subkey named in attrs->dsn. + */ +static BOOL read_system_info(config_attrs_st *attrs, TCHAR *buff) +{ + HKEY hkey; + BOOL ret = FALSE; + const char *ktree; + DWORD valsno, i, j; /* number of values in subkey */ + DWORD maxvallen; /* len of longest value name */ + DWORD maxdatalen; /* len of longest data (buffer) */ + TCHAR val[MAX_REG_VAL_NAME]; + DWORD vallen; + DWORD valtype; + BYTE *d; + DWORD datalen; + tstr_st tval, tdata; + + if (swprintf(val, sizeof(val)/sizeof(val[0]), WPFCP_DESC "\\" WPFWP_LDESC, + ODBC_REG_SUBKEY_PATH, LWSTR(&attrs->dsn)) < 0) { + ERRN("failed to print registry key path."); + return FALSE; + } + /* try accessing local user's config first, if that fails, systems' */ + if (RegOpenKeyEx(HKEY_CURRENT_USER, val, /*options*/0, KEY_READ, + &hkey) != ERROR_SUCCESS) { + INFO("failed to open registry key `" REG_HKCU "\\" LWPD "`.", val); + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, val, /*options*/0, KEY_READ, + &hkey) != ERROR_SUCCESS) { + INFO("failed to open registry key `" REG_HKLM "\\" LWPD "`.", val); + goto end; } else { - *pcchConnStrOut = (SQLSMALLINT)n; + ktree = REG_HKLM; } + } else { + ktree = REG_HKCU; } - RET_STATE(SQL_STATE_00000); + if (RegQueryInfoKey(hkey, /*key class*/NULL, /*len of key class*/NULL, + /*reserved*/NULL, /*no subkeys*/NULL, /*longest subkey*/NULL, + /*longest subkey class name*/NULL, &valsno, &maxvallen, + &maxdatalen, /*sec descr*/NULL, + /*update time */NULL) != ERROR_SUCCESS) { + ERRN("Failed to query registery key info for path `%s\\" LWPD "`.", + ktree, val); + goto end; + } else { + DBG("Subkey '%s\\" LWPD "': vals: %d, lengthiest name: %d, lenghtiest " + "data: %d.", ktree, val, valsno, maxvallen, maxdatalen); + // malloc buffers? + if (MAX_REG_VAL_NAME < maxvallen) + BUG("value name buffer too small (%d), needed: %dB.", + MAX_REG_VAL_NAME, maxvallen); + if (MAX_REG_DATA_SIZE < maxdatalen) + BUG("value data buffer too small (%d), needed: %dB.", + MAX_REG_DATA_SIZE, maxdatalen); + /* connection could still succeeded, so carry on */ + } + + for (i = 0, j = 0; i < valsno; i ++) { + vallen = sizeof(val) / sizeof(val[0]); + datalen = MAX_REG_DATA_SIZE; + d = (BYTE *)&buff[j * datalen]; + if (RegEnumValue(hkey, i, val, &vallen, /*reserved*/NULL, &valtype, + d, &datalen) != ERROR_SUCCESS) { + ERR("failed to read register subkey value."); + goto end; + } + if (valtype != REG_SZ) { + INFO("unused register values of type %d -- skipping.", valtype); + continue; + } + tval = (tstr_st){val, vallen}; + tdata = (tstr_st){(SQLTCHAR *)d, datalen}; + if (assign_config_attr(attrs, &tval, &tdata, FALSE)) { + j ++; + DBG("reg entry`" LTPDL "`: `" LTPDL "` assigned.", LTSTR(&tval), + LTSTR(&tdata)); + } else { + INFO("ignoring reg entry `" LTPDL "`: `" LTPDL "`.", + LTSTR(&tval), LTSTR(&tdata)); + /* entry not directly relevant to driver config */ + } + } + + + ret = TRUE; +end: + RegCloseKey(hkey); + + return ret; +} +#else /* defined(_WIN32) || defined (WIN32) */ +#error "unsupported platform" /* TODO */ +#endif /* defined(_WIN32) || defined (WIN32) */ + +static BOOL prompt_user(config_attrs_st *attrs, BOOL disable_conn) +{ + // + // TODO: display settings dialog box; + // An error message (if popping it more than once) might be needed. + // + return FALSE; +} + +SQLRETURN EsSQLDriverConnectW +( + SQLHDBC hdbc, + SQLHWND hwnd, + /* "A full connection string, a partial connection string, or an empty + * string" */ + _In_reads_(cchConnStrIn) SQLWCHAR* szConnStrIn, + /* "Length of *InConnectionString, in characters if the string is + * Unicode, or bytes if string is ANSI or DBCS." */ + SQLSMALLINT cchConnStrIn, + /* "Pointer to a buffer for the completed connection string. Upon + * successful connection to the target data source, this buffer + * contains the completed connection string." */ + _Out_writes_opt_(cchConnStrOutMax) SQLWCHAR* szConnStrOut, + /* "Length of the *OutConnectionString buffer, in characters." */ + SQLSMALLINT cchConnStrOutMax, + /* "Pointer to a buffer in which to return the total number of + * characters (excluding the null-termination character) available to + * return in *OutConnectionString" */ + _Out_opt_ SQLSMALLINT* pcchConnStrOut, + /* "Flag that indicates whether the Driver Manager or driver must + * prompt for more connection information" */ + SQLUSMALLINT fDriverCompletion +) +{ + esodbc_dbc_st *dbc = DBCH(hdbc); + SQLRETURN ret; + config_attrs_st attrs; + wstr_st orig_dsn; + BOOL disable_conn = FALSE; + BOOL user_canceled = FALSE; + TCHAR buff[(sizeof(config_attrs_st)/sizeof(wstr_st)) * MAX_REG_DATA_SIZE]; + + + memset(&attrs, 0, sizeof(attrs)); + + if (szConnStrIn) { + DBG("Input connection string: '"LWPD"'[%d].", szConnStrIn, + cchConnStrIn); + /* parse conn str into attrs */ + if (! parse_connection_string(&attrs, szConnStrIn, cchConnStrIn)) { + ERR("failed to parse connection string `" LWPDL "`.", + cchConnStrIn < 0 ? wcslen(szConnStrIn) : cchConnStrIn, + szConnStrIn); + RET_HDIAGS(dbc, SQL_STATE_HY000); + } + /* original received DSN saved for later query by the app */ + orig_dsn = attrs.dsn; + + /* set DSN (to DEFAULT) only if both DSN and Driver kw are missing */ + if ((! attrs.driver.cnt) && (! attrs.dsn.cnt)) { + /* If the connection string does not contain the DSN keyword, the + * specified data source is not found, or the DSN keyword is set + * to "DEFAULT", the driver retrieves the information for the + * Default data source. */ + INFO("no DRIVER or DSN keyword found in connection string: " + "using the \"DEFAULT\" DSN."); + attrs.dsn = MK_WSTR("DEFAULT"); + } + } else { + INFO("empty connection string: using the \"DEFAULT\" DSN."); + attrs.dsn = MK_WSTR("DEFAULT"); + } + assert(attrs.driver.cnt || attrs.dsn.cnt); + + if (attrs.dsn.cnt) { + /* "The driver uses any information it retrieves from the system + * information to augment the information passed to it in the + * connection string. If the information in the system information + * duplicates information in the connection string, the driver uses + * the information in the connection string." */ + INFO("configuring the driver by DSN '" LWPDL "'.", LWSTR(&attrs.dsn)); + if (! read_system_info(&attrs, buff)) { + /* warn, but try to carry on */ + WARN("failed to read system info for DSN '" LWPDL "' data.", + LWSTR(&attrs.dsn)); + /* DM should take care of this, but just in case */ + if (! EQ_WSTR(&attrs.dsn, &MK_WSTR("DEFAULT"))) { + attrs.dsn = MK_WSTR("DEFAULT"); + if (! read_system_info(&attrs, buff)) { + ERR("failed to read system info for default DSN."); + RET_HDIAGS(dbc, SQL_STATE_IM002); + } + } + } + } else { + /* "If the connection string contains the DRIVER keyword, the driver + * cannot retrieve information about the data source from the system + * information." */ + INFO("configuring the driver '" LWPDL "'.", LWSTR(&attrs.driver)); + } + + /* whatever attributes haven't yet been set, init them with defaults */ + assign_defaults(&attrs); + + switch (fDriverCompletion) { + case SQL_DRIVER_NOPROMPT: + ret = do_connect(dbc, &attrs); + if (! SQL_SUCCEEDED(ret)) + return ret; + break; + + case SQL_DRIVER_COMPLETE_REQUIRED: + disable_conn = TRUE; + //no break; + case SQL_DRIVER_COMPLETE: + /* try connect first, then, if that fails, prompt user */ + while (! SQL_SUCCEEDED(do_connect(dbc, &attrs))) { + if (! prompt_user(&attrs, disable_conn)) + /* user canceled */ + return SQL_NO_DATA; + } + break; + + case SQL_DRIVER_PROMPT: + do { + /* prompt user first, then try connect */ + if (! prompt_user(&attrs, FALSE)) + /* user canceled */ + return SQL_NO_DATA; + } while (! SQL_SUCCEEDED(do_connect(dbc, &attrs))); + break; + + default: + ERR("DBC@0x%p: unknown driver completion mode: %d", dbc, + fDriverCompletion); + RET_HDIAGS(dbc, SQL_STATE_HY110); + } + + + /* save the original DSN for later inquiry by app */ + dbc->dsn.str = malloc((orig_dsn.cnt + /*0*/1) * sizeof(SQLWCHAR)); + if (! dbc->dsn.str) { + ERRN("OOM for %zdB.", (orig_dsn.cnt + /*0*/1) * sizeof(SQLWCHAR)); + RET_HDIAGS(dbc, SQL_STATE_HY001); + } + dbc->dsn.str[orig_dsn.cnt] = '\0'; + wcsncpy(dbc->dsn.str, orig_dsn.str, orig_dsn.cnt); + dbc->dsn.cnt = orig_dsn.cnt; + + /* return the final connection string */ + if (szConnStrOut || pcchConnStrOut) { + /* might have been reset to DEFAULT, if orig was not found */ + attrs.dsn = orig_dsn; + if (! write_connection_string(&attrs, szConnStrOut, cchConnStrOutMax, + pcchConnStrOut)) { + ERR("DBC@0x%p: failed to build output connection string."); + RET_HDIAG(dbc, SQL_STATE_HY000, "failed to build connection " + "string", 0); + } + } + + return SQL_SUCCESS; } /* "Implicitly allocated descriptors can be freed only by calling @@ -747,17 +1362,8 @@ SQLRETURN EsSQLDriverConnectW SQLRETURN EsSQLDisconnect(SQLHDBC ConnectionHandle) { esodbc_dbc_st *dbc = DBCH(ConnectionHandle); - // FIXME: disconnect - DBG("disconnecting from 0x%p", dbc); - - if (dbc->connstr) { - free(dbc->connstr); - dbc->connstr = NULL; - } - - cleanup_curl(dbc); - - RET_STATE(SQL_STATE_00000); + cleanup_dbc(dbc); + return SQL_SUCCESS; } @@ -860,7 +1466,7 @@ SQLRETURN EsSQLGetConnectAttrW( esodbc_dbc_st *dbc = DBCH(ConnectionHandle); SQLRETURN ret; SQLSMALLINT used; -// SQLTCHAR *val; +// SQLWCHAR *val; switch(Attribute) { /* https://docs.microsoft.com/en-us/sql/odbc/reference/develop-app/automatic-population-of-the-ipd */ @@ -887,14 +1493,14 @@ SQLRETURN EsSQLGetConnectAttrW( } #endif //0 #if 0 - val = dbc->catalog ? dbc->catalog : MK_TSTR("null"); - *StringLengthPtr = wcslen(*(SQLTCHAR **)ValuePtr); - *StringLengthPtr *= sizeof(SQLTCHAR); - *(SQLTCHAR **)ValuePtr = val; + val = dbc->catalog ? dbc->catalog : MK_WPTR("null"); + *StringLengthPtr = wcslen(*(SQLWCHAR **)ValuePtr); + *StringLengthPtr *= sizeof(SQLWCHAR); + *(SQLWCHAR **)ValuePtr = val; #else //0 // FIXME; - ret = write_tstr(&dbc->diag, (SQLTCHAR *)ValuePtr, - MK_TSTR("NulL"), (SQLSMALLINT)BufferLength, &used); + ret = write_wptr(&dbc->diag, (SQLWCHAR *)ValuePtr, + MK_WPTR("NulL"), (SQLSMALLINT)BufferLength, &used); if (StringLengthPtr); *StringLengthPtr = (SQLINTEGER)used; return ret; diff --git a/driver/connect.h b/driver/connect.h index 477082d9..4afbfdea 100644 --- a/driver/connect.h +++ b/driver/connect.h @@ -25,6 +25,7 @@ BOOL connect_init(); void connect_cleanup(); SQLRETURN post_statement(esodbc_stmt_st *stmt); +void cleanup_dbc(esodbc_dbc_st *dbc); SQLRETURN EsSQLDriverConnectW ( diff --git a/driver/defs.h b/driver/defs.h index 0cc71207..d3b78af4 100644 --- a/driver/defs.h +++ b/driver/defs.h @@ -22,8 +22,6 @@ * DEFaultS */ -/* leave the timeout to default value (0: don't timeout, pos: seconds) */ -#define ESODBC_TIMEOUT_DEFAULT -1 // FIXME: review@alpha /* TODO: should there be a max? */ #define ESODBC_MAX_ROW_ARRAY_SIZE 128 @@ -33,8 +31,6 @@ /* values for SQL_ATTR_MAX_LENGTH statement attribute */ #define ESODBC_UP_MAX_LENGTH 0 // USHORT_MAX #define ESODBC_LO_MAX_LENGTH 0 -/* max number of rows to request from server */ -#define ESODBC_DEF_FETCH_SIZE 0 // no fetch size /* prepare a STMT for a new SQL operation. * To be used with catalog functions, that can be all called with same stmt */ #define ESODBC_SQL_CLOSE ((SQLUSMALLINT)-1) @@ -63,25 +59,38 @@ /* maximum DNS name */ /* SQL_MAX_DSN_LENGTH=32 < IPv6 len */ #define ESODBC_MAX_DNS_LEN 255 -/* default maximum amount of bytes to accept as answer */ -#define ESODBC_DEFAULT_MAX_BODY_SIZE 10 * 1024 * 1024 -/* initial size of */ -#define ESODBC_BODY_BUF_START_SIZE 4 * 1024 + /* SQL plugin's REST endpoint for SQL */ #define ELASTIC_SQL_PATH "/_xpack/sql" + #define ELASTIC_SQL_PATH_TABLES "tables" + +/* initial receive buffer size for REST answers */ +#define ESODBC_BODY_BUF_START_SIZE 4 * 1024 + +/* + * Config defaults + */ +/* default maximum amount of bytes to accept in REST answers */ +#define ESODBC_DEF_MAX_BODY_SIZE_MB "10" +/* max number of rows to request from server */ +#define ESODBC_DEF_FETCH_SIZE "0" // no fetch size /* default host to connect to */ -//#define ESODBC_DEFAULT_HOST "localhost" -/* to loopback capture on Win10 */ -#define ESODBC_DEFAULT_HOST "127.0.0.1" +//#define ESODBC_DEF_HOST "localhost" +/* to allow loopback capture on Win10 */ +#define ESODBC_DEF_HOST "127.0.0.1" /* Elasticsearch'es default port */ -#define ESODBC_DEFAULT_PORT 9200 +#define ESODBC_DEF_PORT "9200" /* default security (TLS) setting */ -#define ESODBC_DEFAULT_SEC 0 -/* default global request timeout */ -#define ESODBC_DEFAULT_TIMEOUT 0 +#define ESODBC_DEF_SECURE "no" +/* default global request timeout (0: no timeout) */ +#define ESODBC_DEF_TIMEOUT "0" /* don't follow redirection from the server */ -#define ESODBC_DEFAULT_FOLLOW 1 +#define ESODBC_DEF_FOLLOW "yes" +/* packing of REST bodies (JSON or CBOR) */ +#define ESODBC_DEF_PACKING "JSON" +/* default tracing level */ +#define ESODBC_DEF_TRACE_LEVEL "WARN" diff --git a/driver/error.c b/driver/error.c index 7d8cd9e3..2c634ed5 100644 --- a/driver/error.c +++ b/driver/error.c @@ -30,7 +30,7 @@ void init_diagnostic(esodbc_diag_st *dest) /* TODO: must the diagnostic be "cleared" after a succesful invokation?? */ SQLRETURN post_diagnostic(esodbc_diag_st *dest, esodbc_state_et state, - SQLTCHAR *text, SQLINTEGER code) + SQLWCHAR *text, SQLINTEGER code) { size_t pos, tcnt, ebufsz; @@ -46,7 +46,7 @@ SQLRETURN post_diagnostic(esodbc_diag_st *dest, esodbc_state_et state, pos = sizeof(ESODBC_DIAG_PREFIX) - 1; assert(pos < ebufsz); - wcsncpy(dest->text, MK_TSTR(ESODBC_DIAG_PREFIX), pos); + wcsncpy(dest->text, MK_WPTR(ESODBC_DIAG_PREFIX), pos); if (ebufsz <= pos + tcnt) { wcsncpy(dest->text + pos, text, ebufsz - (pos + 1)); @@ -56,7 +56,7 @@ SQLRETURN post_diagnostic(esodbc_diag_st *dest, esodbc_state_et state, wcsncpy(dest->text + pos, text, tcnt + /* 0-term */1); dest->text_len = (int)(pos + tcnt); } - DBG("diagnostic message: `" LTPD "` [%d], native code: %d.", dest->text, + DBG("diagnostic message: `" LWPD "` [%d], native code: %d.", dest->text, dest->text_len, dest->native_code); RET_STATE(state); @@ -64,7 +64,7 @@ SQLRETURN post_diagnostic(esodbc_diag_st *dest, esodbc_state_et state, } SQLRETURN post_row_diagnostic(esodbc_diag_st *dest, esodbc_state_et state, - SQLTCHAR *text, SQLINTEGER code, SQLLEN nrow, SQLINTEGER ncol) + SQLWCHAR *text, SQLINTEGER code, SQLLEN nrow, SQLINTEGER ncol) { dest->row_number = nrow; dest->column_number = ncol; diff --git a/driver/error.h b/driver/error.h index 851a9737..75c5b602 100644 --- a/driver/error.h +++ b/driver/error.h @@ -18,20 +18,11 @@ #ifndef __ERROR_H__ #define __ERROR_H__ -/* NOTE: this must be included in "top level" file (wherever SQL types are - * used */ -#if defined(_WIN32) || defined (WIN32) -/* FIXME: why isn't this included in sql/~ext.h???? */ -/* win function parameter attributes */ -#include -#endif /* _WIN32/WIN32 */ - -#include "sql.h" -#include "sqlext.h" +#include "util.h" typedef struct { SQLSTATE code; - SQLTCHAR *message; + SQLWCHAR *message; SQLRETURN retcode; } esodbc_errors_st; @@ -162,15 +153,6 @@ typedef enum { } esodbc_state_et; -#define _MK_WSTR(_cstr_) (L ## _cstr_) -#define MK_WSTR(_cstr_) _MK_WSTR(_cstr_) - -#ifdef UNICODE -#define MK_TSTR MK_WSTR -#else /* UNICODE */ -#define MK_TSTR(_cstr_) (_cstr_) -#endif /* UNICODE */ - /* * https://docs.microsoft.com/en-us/sql/odbc/reference/develop-app/sqlstate-mappings : @@ -197,262 +179,258 @@ typedef enum { */ /* Note: keep in sync with esodbc_state_et */ static esodbc_errors_st esodbc_errors[] = { - {MK_TSTR("00000"), MK_TSTR("Success"), + {MK_WPTR("00000"), MK_WPTR("Success"), SQL_SUCCESS}, /* non standard */ - {MK_TSTR("01000"), MK_TSTR("General warning"), + {MK_WPTR("01000"), MK_WPTR("General warning"), SQL_SUCCESS_WITH_INFO}, - {MK_TSTR("01001"), MK_TSTR("Cursor operation conflict"), + {MK_WPTR("01001"), MK_WPTR("Cursor operation conflict"), SQL_SUCCESS_WITH_INFO}, - {MK_TSTR("01002"), MK_TSTR("Disconnect error"), + {MK_WPTR("01002"), MK_WPTR("Disconnect error"), SQL_SUCCESS_WITH_INFO}, - {MK_TSTR("01003"), MK_TSTR("NULL value eliminated in set function"), + {MK_WPTR("01003"), MK_WPTR("NULL value eliminated in set function"), SQL_SUCCESS_WITH_INFO}, - {MK_TSTR("01004"), MK_TSTR("String data, right-truncated"), + {MK_WPTR("01004"), MK_WPTR("String data, right-truncated"), SQL_SUCCESS_WITH_INFO}, - {MK_TSTR("01006"), MK_TSTR("Privilege not revoked"), + {MK_WPTR("01006"), MK_WPTR("Privilege not revoked"), SQL_SUCCESS_WITH_INFO}, - {MK_TSTR("01007"), MK_TSTR("Privilege not granted"), + {MK_WPTR("01007"), MK_WPTR("Privilege not granted"), SQL_SUCCESS_WITH_INFO}, - {MK_TSTR("01S00"), MK_TSTR("Invalid connection string attribute"), + {MK_WPTR("01S00"), MK_WPTR("Invalid connection string attribute"), SQL_SUCCESS_WITH_INFO}, - {MK_TSTR("01S01"), MK_TSTR("Error in row"), + {MK_WPTR("01S01"), MK_WPTR("Error in row"), SQL_SUCCESS_WITH_INFO}, - {MK_TSTR("01S02"), MK_TSTR("Option value changed"), + {MK_WPTR("01S02"), MK_WPTR("Option value changed"), SQL_SUCCESS_WITH_INFO}, - {MK_TSTR("01S06"), MK_TSTR("Attempt to fetch before the result set "\ + {MK_WPTR("01S06"), MK_WPTR("Attempt to fetch before the result set "\ "returned the first rowset"), SQL_SUCCESS_WITH_INFO}, - {MK_TSTR("01S07"), MK_TSTR("Fractional truncation"), + {MK_WPTR("01S07"), MK_WPTR("Fractional truncation"), SQL_SUCCESS_WITH_INFO}, - {MK_TSTR("01S08"), MK_TSTR("Error saving File DSN"), + {MK_WPTR("01S08"), MK_WPTR("Error saving File DSN"), SQL_SUCCESS_WITH_INFO}, - {MK_TSTR("01S09"), MK_TSTR("Invalid keyword"), + {MK_WPTR("01S09"), MK_WPTR("Invalid keyword"), SQL_SUCCESS_WITH_INFO}, - {MK_TSTR("07001"), MK_TSTR("Wrong number of parameters"), + {MK_WPTR("07001"), MK_WPTR("Wrong number of parameters"), SQL_ERROR}, - {MK_TSTR("07002"), MK_TSTR("COUNT field incorrect"), + {MK_WPTR("07002"), MK_WPTR("COUNT field incorrect"), SQL_ERROR}, - {MK_TSTR("07005"), - MK_TSTR("Prepared statement not a cursor-specification"), + {MK_WPTR("07005"), + MK_WPTR("Prepared statement not a cursor-specification"), SQL_ERROR}, - {MK_TSTR("07006"), MK_TSTR("Restricted data type attribute violation"), + {MK_WPTR("07006"), MK_WPTR("Restricted data type attribute violation"), SQL_ERROR}, - {MK_TSTR("07009"), MK_TSTR("Invalid descriptor index"), + {MK_WPTR("07009"), MK_WPTR("Invalid descriptor index"), SQL_ERROR}, - {MK_TSTR("07S01"), MK_TSTR("Invalid use of default parameter"), + {MK_WPTR("07S01"), MK_WPTR("Invalid use of default parameter"), SQL_ERROR}, - {MK_TSTR("08001"), MK_TSTR("Client unable to establish connection"), + {MK_WPTR("08001"), MK_WPTR("Client unable to establish connection"), SQL_ERROR}, - {MK_TSTR("08002"), MK_TSTR("Connection name in use"), + {MK_WPTR("08002"), MK_WPTR("Connection name in use"), SQL_ERROR}, - {MK_TSTR("08003"), MK_TSTR("Connection not open"), + {MK_WPTR("08003"), MK_WPTR("Connection not open"), SQL_ERROR}, - {MK_TSTR("08004"), MK_TSTR("Server rejected the connection"), + {MK_WPTR("08004"), MK_WPTR("Server rejected the connection"), SQL_ERROR}, - {MK_TSTR("08007"), MK_TSTR("Connection failure during transaction"), + {MK_WPTR("08007"), MK_WPTR("Connection failure during transaction"), SQL_ERROR}, - {MK_TSTR("08S01"), MK_TSTR("Communication link failure"), + {MK_WPTR("08S01"), MK_WPTR("Communication link failure"), SQL_ERROR}, - {MK_TSTR("21S01"), MK_TSTR("Insert value list does not match column list"), + {MK_WPTR("21S01"), MK_WPTR("Insert value list does not match column list"), SQL_ERROR}, - {MK_TSTR("21S02"), - MK_TSTR("Degree of derived table does not match column list"), + {MK_WPTR("21S02"), + MK_WPTR("Degree of derived table does not match column list"), SQL_ERROR}, - {MK_TSTR("22001"), MK_TSTR("String data, right-truncated"), + {MK_WPTR("22001"), MK_WPTR("String data, right-truncated"), SQL_ERROR}, - {MK_TSTR("22002"), MK_TSTR("Indicator variable required but not supplied"), + {MK_WPTR("22002"), MK_WPTR("Indicator variable required but not supplied"), SQL_ERROR}, - {MK_TSTR("22003"), MK_TSTR("Numeric value out of range"), + {MK_WPTR("22003"), MK_WPTR("Numeric value out of range"), SQL_ERROR}, - {MK_TSTR("22007"), MK_TSTR("Invalid datetime format"), + {MK_WPTR("22007"), MK_WPTR("Invalid datetime format"), SQL_ERROR}, - {MK_TSTR("22008"), MK_TSTR("Datetime field overflow"), + {MK_WPTR("22008"), MK_WPTR("Datetime field overflow"), SQL_ERROR}, - {MK_TSTR("22012"), MK_TSTR("Division by zero"), + {MK_WPTR("22012"), MK_WPTR("Division by zero"), SQL_ERROR}, - {MK_TSTR("22015"), MK_TSTR("Interval field overflow"), + {MK_WPTR("22015"), MK_WPTR("Interval field overflow"), SQL_ERROR}, - {MK_TSTR("22018"), - MK_TSTR("Invalid character value for cast specification"), + {MK_WPTR("22018"), + MK_WPTR("Invalid character value for cast specification"), SQL_ERROR}, - {MK_TSTR("22019"), MK_TSTR("Invalid escape character"), + {MK_WPTR("22019"), MK_WPTR("Invalid escape character"), SQL_ERROR}, - {MK_TSTR("22025"), MK_TSTR("Invalid escape sequence"), + {MK_WPTR("22025"), MK_WPTR("Invalid escape sequence"), SQL_ERROR}, - {MK_TSTR("22026"), MK_TSTR("String data, length mismatch"), + {MK_WPTR("22026"), MK_WPTR("String data, length mismatch"), SQL_ERROR}, - {MK_TSTR("23000"), MK_TSTR("Integrity constraint violation"), + {MK_WPTR("23000"), MK_WPTR("Integrity constraint violation"), SQL_ERROR}, - {MK_TSTR("24000"), MK_TSTR("Invalid cursor state"), + {MK_WPTR("24000"), MK_WPTR("Invalid cursor state"), SQL_ERROR}, - {MK_TSTR("25000"), MK_TSTR("Invalid transaction state"), + {MK_WPTR("25000"), MK_WPTR("Invalid transaction state"), SQL_ERROR}, - {MK_TSTR("25S01"), MK_TSTR("Transaction state"), + {MK_WPTR("25S01"), MK_WPTR("Transaction state"), SQL_ERROR}, - {MK_TSTR("25S02"), MK_TSTR("Transaction is still active"), + {MK_WPTR("25S02"), MK_WPTR("Transaction is still active"), SQL_ERROR}, - {MK_TSTR("25S03"), MK_TSTR("Transaction is rolled back"), + {MK_WPTR("25S03"), MK_WPTR("Transaction is rolled back"), SQL_ERROR}, - {MK_TSTR("28000"), MK_TSTR("Invalid authorization specification"), + {MK_WPTR("28000"), MK_WPTR("Invalid authorization specification"), SQL_ERROR}, - {MK_TSTR("34000"), MK_TSTR("Invalid cursor name"), + {MK_WPTR("34000"), MK_WPTR("Invalid cursor name"), SQL_ERROR}, - {MK_TSTR("3C000"), MK_TSTR("Duplicate cursor name"), + {MK_WPTR("3C000"), MK_WPTR("Duplicate cursor name"), SQL_ERROR}, - {MK_TSTR("3D000"), MK_TSTR("Invalid catalog name"), + {MK_WPTR("3D000"), MK_WPTR("Invalid catalog name"), SQL_ERROR}, - {MK_TSTR("3F000"), MK_TSTR("Invalid schema name"), + {MK_WPTR("3F000"), MK_WPTR("Invalid schema name"), SQL_ERROR}, - {MK_TSTR("40001"), MK_TSTR("Serialization failure"), + {MK_WPTR("40001"), MK_WPTR("Serialization failure"), SQL_ERROR}, - {MK_TSTR("40002"), MK_TSTR("Integrity constraint violation"), + {MK_WPTR("40002"), MK_WPTR("Integrity constraint violation"), SQL_ERROR}, - {MK_TSTR("40003"), MK_TSTR("Statement completion unknown"), + {MK_WPTR("40003"), MK_WPTR("Statement completion unknown"), SQL_ERROR}, - {MK_TSTR("42000"), MK_TSTR("Syntax error or access violation"), + {MK_WPTR("42000"), MK_WPTR("Syntax error or access violation"), SQL_ERROR}, - {MK_TSTR("42S01"), MK_TSTR("Base table or view already exists"), + {MK_WPTR("42S01"), MK_WPTR("Base table or view already exists"), SQL_ERROR}, - {MK_TSTR("42S02"), MK_TSTR("Base table or view not found"), + {MK_WPTR("42S02"), MK_WPTR("Base table or view not found"), SQL_ERROR}, - {MK_TSTR("42S11"), MK_TSTR("Index already exists"), + {MK_WPTR("42S11"), MK_WPTR("Index already exists"), SQL_ERROR}, - {MK_TSTR("42S12"), MK_TSTR("Index not found"), + {MK_WPTR("42S12"), MK_WPTR("Index not found"), SQL_ERROR}, - {MK_TSTR("42S21"), MK_TSTR("Column already exists"), + {MK_WPTR("42S21"), MK_WPTR("Column already exists"), SQL_ERROR}, - {MK_TSTR("42S22"), MK_TSTR("Column not found"), + {MK_WPTR("42S22"), MK_WPTR("Column not found"), SQL_ERROR}, - {MK_TSTR("44000"), MK_TSTR("WITH CHECK OPTION violation"), + {MK_WPTR("44000"), MK_WPTR("WITH CHECK OPTION violation"), SQL_ERROR}, - {MK_TSTR("HY000"), MK_TSTR("General error"), + {MK_WPTR("HY000"), MK_WPTR("General error"), SQL_ERROR}, - {MK_TSTR("HY001"), MK_TSTR("Memory allocation error"), + {MK_WPTR("HY001"), MK_WPTR("Memory allocation error"), SQL_ERROR}, - {MK_TSTR("HY003"), MK_TSTR("Invalid application buffer type"), + {MK_WPTR("HY003"), MK_WPTR("Invalid application buffer type"), SQL_ERROR}, - {MK_TSTR("HY004"), MK_TSTR("Invalid SQL data type"), + {MK_WPTR("HY004"), MK_WPTR("Invalid SQL data type"), SQL_ERROR}, - {MK_TSTR("HY007"), MK_TSTR("Associated statement is not prepared"), + {MK_WPTR("HY007"), MK_WPTR("Associated statement is not prepared"), SQL_ERROR}, - {MK_TSTR("HY008"), MK_TSTR("Operation canceled"), + {MK_WPTR("HY008"), MK_WPTR("Operation canceled"), SQL_ERROR}, - {MK_TSTR("HY009"), MK_TSTR("Invalid use of null pointer"), + {MK_WPTR("HY009"), MK_WPTR("Invalid use of null pointer"), SQL_ERROR}, - {MK_TSTR("HY010"), MK_TSTR("Function sequence error"), + {MK_WPTR("HY010"), MK_WPTR("Function sequence error"), SQL_ERROR}, - {MK_TSTR("HY011"), MK_TSTR("Attribute cannot be set now"), + {MK_WPTR("HY011"), MK_WPTR("Attribute cannot be set now"), SQL_ERROR}, - {MK_TSTR("HY012"), MK_TSTR("Invalid transaction operation code"), + {MK_WPTR("HY012"), MK_WPTR("Invalid transaction operation code"), SQL_ERROR}, - {MK_TSTR("HY013"), MK_TSTR("Memory management error"), + {MK_WPTR("HY013"), MK_WPTR("Memory management error"), SQL_ERROR}, - {MK_TSTR("HY014"), MK_TSTR("Limit on the number of handles exceeded"), + {MK_WPTR("HY014"), MK_WPTR("Limit on the number of handles exceeded"), SQL_ERROR}, - {MK_TSTR("HY015"), MK_TSTR("No cursor name available"), + {MK_WPTR("HY015"), MK_WPTR("No cursor name available"), SQL_ERROR}, - {MK_TSTR("HY016"), - MK_TSTR("Cannot modify an implementation row descriptor"), + {MK_WPTR("HY016"), + MK_WPTR("Cannot modify an implementation row descriptor"), SQL_ERROR}, - {MK_TSTR("HY017"), MK_TSTR("Invalid use of an automatically allocated "\ + {MK_WPTR("HY017"), MK_WPTR("Invalid use of an automatically allocated "\ "descriptor handle"), SQL_ERROR}, - {MK_TSTR("HY018"), MK_TSTR("Server declined cancel request"), + {MK_WPTR("HY018"), MK_WPTR("Server declined cancel request"), SQL_ERROR}, - {MK_TSTR("HY019"), - MK_TSTR("Non-character and non-binary data sent in pieces"), + {MK_WPTR("HY019"), + MK_WPTR("Non-character and non-binary data sent in pieces"), SQL_ERROR}, - {MK_TSTR("HY020"), MK_TSTR("Attempt to concatenate a null value"), + {MK_WPTR("HY020"), MK_WPTR("Attempt to concatenate a null value"), SQL_ERROR}, - {MK_TSTR("HY021"), MK_TSTR("Inconsistent descriptor information"), + {MK_WPTR("HY021"), MK_WPTR("Inconsistent descriptor information"), SQL_ERROR}, - {MK_TSTR("HY024"), MK_TSTR("Invalid attribute value"), + {MK_WPTR("HY024"), MK_WPTR("Invalid attribute value"), SQL_ERROR}, - {MK_TSTR("HY090"), MK_TSTR("Invalid string or buffer length"), + {MK_WPTR("HY090"), MK_WPTR("Invalid string or buffer length"), SQL_ERROR}, - {MK_TSTR("HY091"), MK_TSTR("Invalid descriptor field identifier"), + {MK_WPTR("HY091"), MK_WPTR("Invalid descriptor field identifier"), SQL_ERROR}, - {MK_TSTR("HY092"), MK_TSTR("Invalid attribute/option identifier"), + {MK_WPTR("HY092"), MK_WPTR("Invalid attribute/option identifier"), SQL_ERROR}, - {MK_TSTR("HY095"), MK_TSTR("Function type out of range"), + {MK_WPTR("HY095"), MK_WPTR("Function type out of range"), SQL_ERROR}, - {MK_TSTR("HY096"), MK_TSTR("Invalid information type"), + {MK_WPTR("HY096"), MK_WPTR("Invalid information type"), SQL_ERROR}, - {MK_TSTR("HY097"), MK_TSTR("Column type out of range"), + {MK_WPTR("HY097"), MK_WPTR("Column type out of range"), SQL_ERROR}, - {MK_TSTR("HY098"), MK_TSTR("Scope type out of range"), + {MK_WPTR("HY098"), MK_WPTR("Scope type out of range"), SQL_ERROR}, - {MK_TSTR("HY099"), MK_TSTR("Nullable type out of range"), + {MK_WPTR("HY099"), MK_WPTR("Nullable type out of range"), SQL_ERROR}, - {MK_TSTR("HY100"), MK_TSTR("Uniqueness option type out of range"), + {MK_WPTR("HY100"), MK_WPTR("Uniqueness option type out of range"), SQL_ERROR}, - {MK_TSTR("HY101"), MK_TSTR("Accuracy option type out of range"), + {MK_WPTR("HY101"), MK_WPTR("Accuracy option type out of range"), SQL_ERROR}, - {MK_TSTR("HY103"), MK_TSTR("Invalid retrieval code"), + {MK_WPTR("HY103"), MK_WPTR("Invalid retrieval code"), SQL_ERROR}, - {MK_TSTR("HY104"), MK_TSTR("Invalid precision or scale value"), + {MK_WPTR("HY104"), MK_WPTR("Invalid precision or scale value"), SQL_ERROR}, - {MK_TSTR("HY105"), MK_TSTR("Invalid parameter type"), + {MK_WPTR("HY105"), MK_WPTR("Invalid parameter type"), SQL_ERROR}, - {MK_TSTR("HY106"), MK_TSTR("Fetch type out of range"), + {MK_WPTR("HY106"), MK_WPTR("Fetch type out of range"), SQL_ERROR}, - {MK_TSTR("HY107"), MK_TSTR("Row value out of range"), + {MK_WPTR("HY107"), MK_WPTR("Row value out of range"), SQL_ERROR}, - {MK_TSTR("HY109"), MK_TSTR("Invalid cursor position"), + {MK_WPTR("HY109"), MK_WPTR("Invalid cursor position"), SQL_ERROR}, - {MK_TSTR("HY110"), MK_TSTR("Invalid driver completion"), + {MK_WPTR("HY110"), MK_WPTR("Invalid driver completion"), SQL_ERROR}, - {MK_TSTR("HY111"), MK_TSTR("Invalid bookmark value"), + {MK_WPTR("HY111"), MK_WPTR("Invalid bookmark value"), SQL_ERROR}, - {MK_TSTR("HYC00"), MK_TSTR("Optional feature not implemented"), + {MK_WPTR("HYC00"), MK_WPTR("Optional feature not implemented"), SQL_ERROR}, - {MK_TSTR("HYT00"), MK_TSTR("Timeout expired"), + {MK_WPTR("HYT00"), MK_WPTR("Timeout expired"), SQL_ERROR}, - {MK_TSTR("HYT01"), MK_TSTR("Connection timeout expired"), + {MK_WPTR("HYT01"), MK_WPTR("Connection timeout expired"), SQL_ERROR}, - {MK_TSTR("IM001"), MK_TSTR("Driver does not support this function"), + {MK_WPTR("IM001"), MK_WPTR("Driver does not support this function"), SQL_ERROR}, - {MK_TSTR("IM002"), MK_TSTR("Data source name not found and no default "\ + {MK_WPTR("IM002"), MK_WPTR("Data source name not found and no default "\ "driver specified"), SQL_ERROR}, - {MK_TSTR("IM003"), MK_TSTR("Specified driver could not be loaded"), + {MK_WPTR("IM003"), MK_WPTR("Specified driver could not be loaded"), SQL_ERROR}, - {MK_TSTR("IM004"), MK_TSTR("Driver's SQLAllocHandle on SQL_HANDLE_ENV "\ + {MK_WPTR("IM004"), MK_WPTR("Driver's SQLAllocHandle on SQL_HANDLE_ENV "\ "failed"), SQL_ERROR}, - {MK_TSTR("IM005"), MK_TSTR("Driver's SQLAllocHandle on "\ + {MK_WPTR("IM005"), MK_WPTR("Driver's SQLAllocHandle on "\ "SQL_HANDLE_DBC failed"), SQL_ERROR}, - {MK_TSTR("IM006"), MK_TSTR("Driver's SQLSetConnectAttr failed"), + {MK_WPTR("IM006"), MK_WPTR("Driver's SQLSetConnectAttr failed"), SQL_ERROR}, - {MK_TSTR("IM007"), MK_TSTR("No data source or driver specified; dialog "\ + {MK_WPTR("IM007"), MK_WPTR("No data source or driver specified; dialog "\ "prohibited"), SQL_ERROR}, - {MK_TSTR("IM008"), MK_TSTR("Dialog failed"), + {MK_WPTR("IM008"), MK_WPTR("Dialog failed"), SQL_ERROR}, - {MK_TSTR("IM009"), MK_TSTR("Unable to load translation DLL"), + {MK_WPTR("IM009"), MK_WPTR("Unable to load translation DLL"), SQL_ERROR}, - {MK_TSTR("IM010"), MK_TSTR("Data source name too long"), + {MK_WPTR("IM010"), MK_WPTR("Data source name too long"), SQL_ERROR}, - {MK_TSTR("IM011"), MK_TSTR("Driver name too long"), + {MK_WPTR("IM011"), MK_WPTR("Driver name too long"), SQL_ERROR}, - {MK_TSTR("IM012"), MK_TSTR("DRIVER keyword syntax error"), + {MK_WPTR("IM012"), MK_WPTR("DRIVER keyword syntax error"), SQL_ERROR}, - {MK_TSTR("IM013"), MK_TSTR("Trace file error"), + {MK_WPTR("IM013"), MK_WPTR("Trace file error"), SQL_ERROR}, - {MK_TSTR("IM014"), MK_TSTR("Invalid name of File DSN"), + {MK_WPTR("IM014"), MK_WPTR("Invalid name of File DSN"), SQL_ERROR}, - {MK_TSTR("IM015"), MK_TSTR("Corrupt file data source"), + {MK_WPTR("IM015"), MK_WPTR("Corrupt file data source"), SQL_ERROR}, }; -/* stringifying in two preproc. passes */ -#define _STR(_x) # _x -#define STR(_x) _STR(_x) - /* driver version ex. 1.2(u) */ #define ESODBC_DRIVER_VER \ STR(DRV_VER_MAJOR) "." STR(DRV_VER_MINOR) "(" STR(DRV_ENCODING) ")" @@ -461,7 +439,7 @@ static esodbc_errors_st esodbc_errors[] = { typedef struct { esodbc_state_et state; /* [vendor-identifier][ODBC-component-identifier]component-supplied-text */ - SQLTCHAR text[SQL_MAX_MESSAGE_LENGTH]; + SQLWCHAR text[SQL_MAX_MESSAGE_LENGTH]; /* lenght of characters in the buffer */ SQLUSMALLINT text_len; /* in characters, not bytes, w/o the 0-term */ /* (SQLSMALLINT)wcslen(native_text) */ @@ -474,16 +452,16 @@ typedef struct { void init_diagnostic(esodbc_diag_st *dest); SQLRETURN post_diagnostic(esodbc_diag_st *dest, esodbc_state_et state, - SQLTCHAR *text, SQLINTEGER code); + SQLWCHAR *text, SQLINTEGER code); /* post state into the diagnostic and return state's return code */ #define RET_DIAG(_d/*est*/, _s/*tate*/, _t/*ext*/, _c/*ode*/) \ return post_diagnostic(_d, _s, _t, _c) /* same as above, but take C-strings as messages */ #define RET_CDIAG(_d/*est*/, _s/*tate*/, _t/*char text*/, _c/*ode*/) \ - RET_DIAG(_d, _s, MK_TSTR(_t), _c) + RET_DIAG(_d, _s, MK_WPTR(_t), _c) SQLRETURN post_row_diagnostic(esodbc_diag_st *dest, esodbc_state_et state, - SQLTCHAR *text, SQLINTEGER code, SQLLEN nrow, SQLINTEGER ncol); + SQLWCHAR *text, SQLINTEGER code, SQLLEN nrow, SQLINTEGER ncol); #endif /* __ERROR_H__ */ diff --git a/driver/handles.c b/driver/handles.c index 09399415..d53f647e 100644 --- a/driver/handles.c +++ b/driver/handles.c @@ -21,12 +21,13 @@ #include "handles.h" #include "log.h" #include "queries.h" +#include "connect.h" static void free_rec_fields(desc_rec_st *rec) { int i; - SQLTCHAR **tstr[] = { + SQLWCHAR **wptr[] = { &rec->base_column_name, &rec->base_table_name, &rec->catalog_name, @@ -39,11 +40,11 @@ static void free_rec_fields(desc_rec_st *rec) &rec->table_name, &rec->type_name, }; - for (i = 0; i < sizeof(tstr)/sizeof(tstr[0]); i ++) { - DBG("freeing field #%d = 0x%p.", i, *tstr[i]); - if (*tstr[i]) { - free(*tstr[i]); - *tstr[i] = NULL; + for (i = 0; i < sizeof(wptr)/sizeof(wptr[0]); i ++) { + DBG("freeing field #%d = 0x%p.", i, *wptr[i]); + if (*wptr[i]) { + free(*wptr[i]); + *wptr[i] = NULL; } } } @@ -128,17 +129,17 @@ void dump_record(desc_rec_st *rec) DUMP_FIELD(rec, data_ptr, "0x%p"); - DUMP_FIELD(rec, base_column_name, LTPD); - DUMP_FIELD(rec, base_table_name, LTPD); - DUMP_FIELD(rec, catalog_name, LTPD); - DUMP_FIELD(rec, label, LTPD); - DUMP_FIELD(rec, literal_prefix, LTPD); - DUMP_FIELD(rec, literal_suffix, LTPD); - DUMP_FIELD(rec, local_type_name, LTPD); - DUMP_FIELD(rec, name, LTPD); - DUMP_FIELD(rec, schema_name, LTPD); - DUMP_FIELD(rec, table_name, LTPD); - DUMP_FIELD(rec, type_name, LTPD); + 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"); @@ -241,6 +242,10 @@ SQLRETURN EsSQLAllocHandle(SQLSMALLINT HandleType, RET_HDIAGS(ENVH(InputHandle), SQL_STATE_HY001); } init_diagnostic(&dbc->diag); + dbc->dsn.str = MK_WPTR(""); /* see explanation in cleanup_dbc() */ + dbc->metadata_id = SQL_FALSE; + dbc->async_enable = SQL_ASYNC_ENABLE_OFF; + dbc->env = ENVH(InputHandle); /* rest of initialization done at connect time */ @@ -323,8 +328,8 @@ SQLRETURN EsSQLFreeHandle(SQLSMALLINT HandleType, SQLHANDLE Handle) break; case SQL_HANDLE_DBC: /* Connection Handle */ // TODO: remove from (potential) list? - if (DBCH(Handle)->fetch.str) - free(DBCH(Handle)->fetch.str); + /* app/DM should have SQLDisconnect'ed, but just in case */ + cleanup_dbc(DBCH(Handle)); free(Handle); break; case SQL_HANDLE_STMT: @@ -878,8 +883,8 @@ static esodbc_state_et check_buff(SQLSMALLINT field_id, SQLPOINTER buff, "negative (%d).", buff_len); return SQL_STATE_HY090; } - if (buff_len % sizeof(SQLTCHAR)) { - ERR("buffer not alligned to SQLTCHAR size (%d).", buff_len); + if (buff_len % sizeof(SQLWCHAR)) { + ERR("buffer not alligned to SQLWCHAR size (%d).", buff_len); return SQL_STATE_HY090; } if ((! writable) && (ESODBC_MAX_IDENTIFIER_LEN < buff_len)) { @@ -1263,7 +1268,7 @@ SQLRETURN EsSQLGetDescFieldW( { esodbc_desc_st *desc = DSCH(DescriptorHandle); esodbc_state_et state; - SQLTCHAR *tstr; + SQLWCHAR *wptr; SQLSMALLINT word; SQLINTEGER intgr; desc_rec_st *rec; @@ -1374,31 +1379,31 @@ SQLRETURN EsSQLGetDescFieldW( DBG("returning data pointer 0x%p.", rec->data_ptr); break; - /* */ + /* */ do { - case SQL_DESC_BASE_COLUMN_NAME: tstr = rec->base_column_name; break; - case SQL_DESC_BASE_TABLE_NAME: tstr = rec->base_table_name; break; - case SQL_DESC_CATALOG_NAME: tstr = rec->catalog_name; break; - case SQL_DESC_LABEL: tstr = rec->label; break; - case SQL_DESC_LITERAL_PREFIX: tstr = rec->literal_prefix; break; - case SQL_DESC_LITERAL_SUFFIX: tstr = rec->literal_suffix; break; - case SQL_DESC_LOCAL_TYPE_NAME: tstr = rec->local_type_name; break; - case SQL_DESC_NAME: tstr = rec->name; break; - case SQL_DESC_SCHEMA_NAME: tstr = rec->schema_name; break; - case SQL_DESC_TABLE_NAME: tstr = rec->table_name; break; - case SQL_DESC_TYPE_NAME: tstr = rec->type_name; break; + case SQL_DESC_BASE_COLUMN_NAME: wptr = rec->base_column_name; break; + case SQL_DESC_BASE_TABLE_NAME: wptr = rec->base_table_name; break; + case SQL_DESC_CATALOG_NAME: wptr = rec->catalog_name; break; + case SQL_DESC_LABEL: wptr = rec->label; break; + case SQL_DESC_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; } while (0); - if (! tstr) { + if (! wptr) { *StringLengthPtr = 0; } else { - *StringLengthPtr = (SQLINTEGER)wcslen(tstr) * sizeof(SQLTCHAR); + *StringLengthPtr = (SQLINTEGER)wcslen(wptr) * sizeof(SQLWCHAR); } if (ValuePtr) { - memcpy(ValuePtr, tstr, *StringLengthPtr); + memcpy(ValuePtr, wptr, *StringLengthPtr); /* TODO: 0-term setting? */ } - DBG("returning record field %d as SQLTCHAR 0x%p (`"LTPD"`).", - FieldIdentifier, tstr, tstr ? tstr : TS_NULL); + DBG("returning record field %d as SQLWCHAR 0x%p (`"LWPD"`).", + FieldIdentifier, wptr, wptr ? wptr : TS_NULL); break; /* */ @@ -1914,7 +1919,7 @@ SQLRETURN EsSQLSetDescFieldW( esodbc_desc_st *desc = DSCH(DescriptorHandle); esodbc_state_et state; desc_rec_st *rec; - SQLTCHAR **tstrp, *tstr; + SQLWCHAR **wptrp, *wptr; SQLSMALLINT *wordp; SQLINTEGER *intp; SQLSMALLINT count, type; @@ -2101,49 +2106,49 @@ SQLRETURN EsSQLSetDescFieldW( break; case SQL_DESC_NAME: - WARN("stored procedure params (to set to `"LTPD"`) not " - "supported.", ValuePtr ? (SQLTCHAR *)ValuePtr : TS_NULL); + WARN("stored procedure params (to set to `"LWPD"`) not " + "supported.", ValuePtr ? (SQLWCHAR *)ValuePtr : TS_NULL); RET_HDIAG(desc, SQL_STATE_HYC00, "stored procedure params not supported", 0); - /* */ + /* */ do { - case SQL_DESC_BASE_COLUMN_NAME: tstrp = &rec->base_column_name; break; - case SQL_DESC_BASE_TABLE_NAME: tstrp = &rec->base_table_name; break; - case SQL_DESC_CATALOG_NAME: tstrp = &rec->catalog_name; break; - case SQL_DESC_LABEL: tstrp = &rec->label; break; - case SQL_DESC_LITERAL_PREFIX: tstrp = &rec->literal_prefix; break; - case SQL_DESC_LITERAL_SUFFIX: tstrp = &rec->literal_suffix; break; - case SQL_DESC_LOCAL_TYPE_NAME: tstrp = &rec->local_type_name; break; - case SQL_DESC_SCHEMA_NAME: tstrp = &rec->schema_name; break; - case SQL_DESC_TABLE_NAME: tstrp = &rec->table_name; break; - case SQL_DESC_TYPE_NAME: tstrp = &rec->type_name; break; + case SQL_DESC_BASE_COLUMN_NAME: wptrp = &rec->base_column_name; break; + case SQL_DESC_BASE_TABLE_NAME: wptrp = &rec->base_table_name; break; + case SQL_DESC_CATALOG_NAME: wptrp = &rec->catalog_name; break; + case SQL_DESC_LABEL: wptrp = &rec->label; break; + case SQL_DESC_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; + 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); - DBG("setting SQLTCHAR field %d to 0x%p(`"LTPD"`).", + DBG("setting SQLWCHAR field %d to 0x%p(`"LWPD"`).", FieldIdentifier, ValuePtr, - ValuePtr ? (SQLTCHAR *)ValuePtr : TS_NULL); - if (*tstrp) { + ValuePtr ? (SQLWCHAR *)ValuePtr : TS_NULL); + if (*wptrp) { DBG("freeing previously allocated value for field %d " - "(`"LTPD"`).", *tstrp); - free(*tstrp); + "(`"LWPD"`).", *wptrp); + free(*wptrp); } if (! ValuePtr) { - *tstrp = NULL; + *wptrp = NULL; DBG("field %d reset to NULL.", FieldIdentifier); break; } if (BufferLength == SQL_NTS) - wlen = wcslen((SQLTCHAR *)ValuePtr); + wlen = wcslen((SQLWCHAR *)ValuePtr); else wlen = BufferLength; - tstr = (SQLTCHAR *)malloc((wlen + /*0-term*/1) * sizeof(SQLTCHAR)); - if (! tstr) { + wptr = (SQLWCHAR *)malloc((wlen + /*0-term*/1) * sizeof(SQLWCHAR)); + if (! wptr) { ERR("failed to alloc string buffer of len %d.", wlen + 1); RET_HDIAGS(desc, SQL_STATE_HY001); } - memcpy(tstr, ValuePtr, wlen * sizeof(SQLTCHAR)); - tstr[wlen] = 0; - *tstrp = tstr; + memcpy(wptr, ValuePtr, wlen * sizeof(SQLWCHAR)); + wptr[wlen] = 0; + *wptrp = wptr; break; /* , deferred */ diff --git a/driver/handles.h b/driver/handles.h index 0d4edf75..52b24036 100644 --- a/driver/handles.h +++ b/driver/handles.h @@ -56,18 +56,21 @@ typedef struct struct_dbc { esodbc_env_st *env; /* diagnostic/state keeping */ esodbc_diag_st diag; + wstr_st dsn; /* data source name */ + char *url; SQLUINTEGER timeout; + BOOL follow; struct { size_t max; /* max fetch size */ char *str; /* as string */ char slen; /* string's length (w/o terminator) */ } 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; - SQLTCHAR *connstr; /* connection string */ // TODO: IDNA? CURL *curl; /* cURL handle */ char *abuff; /* buffer holding the answer */ size_t alen; /* size of abuff */ @@ -76,7 +79,7 @@ typedef struct struct_dbc { /* "the catalog is a database", "For a single-tier driver, the catalog * might be a directory" */ - SQLTCHAR *catalog; + SQLWCHAR *catalog; // TODO: statements? /* options */ @@ -117,17 +120,17 @@ typedef struct desc_rec { SQLPOINTER data_ptr; /* array, if .array_size > 1 */ /* TODO: add (& use) the lenghts */ - SQLTCHAR *base_column_name; /* read-only */ - SQLTCHAR *base_table_name; /* r/o */ - SQLTCHAR *catalog_name; /* r/o */ - SQLTCHAR *label; /* r/o */ - SQLTCHAR *literal_prefix; /* r/o */ // TODO: static? - SQLTCHAR *literal_suffix; /* r/o */ // TODO: static? - SQLTCHAR *local_type_name; /* r/o */ - SQLTCHAR *name; - SQLTCHAR *schema_name; /* r/o */ // TODO: static? - SQLTCHAR *table_name; /* r/o */ - SQLTCHAR *type_name; /* r/o */ + SQLWCHAR *base_column_name; /* read-only */ + SQLWCHAR *base_table_name; /* r/o */ + SQLWCHAR *catalog_name; /* r/o */ + SQLWCHAR *label; /* r/o */ + 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 *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 */ @@ -352,15 +355,15 @@ SQLRETURN EsSQLSetDescRec( #define HDIAG_COPY(_s, _d) (_s)->diag = (_d)->diag /* set a diagnostic to a(ny) handle */ #define SET_HDIAG(_hp/*handle ptr*/, _s/*tate*/, _t/*char text*/, _c/*ode*/) \ - post_diagnostic(&(_hp)->diag, _s, MK_TSTR(_t), _c) + post_diagnostic(&(_hp)->diag, _s, MK_WPTR(_t), _c) /* return the code associated with the given state (and debug-log) */ #define RET_STATE(_s) \ do { \ assert(_s < SQL_STATE_MAX); \ SQLRETURN _r = esodbc_errors[_s].retcode; \ - SQLTCHAR *_c = esodbc_errors[_s].code; \ - DBG("returning state "LTPD", code %d.", _c, _r); \ + SQLWCHAR *_c = esodbc_errors[_s].code; \ + DBG("returning state "LWPD", code %d.", _c, _r); \ return _r; \ } while (0) diff --git a/driver/info.c b/driver/info.c index 0b58dc1d..5d99900e 100644 --- a/driver/info.c +++ b/driver/info.c @@ -134,8 +134,8 @@ static SQLUSMALLINT esodbc_functions[] = { * require character count (eg. SQLGetDiagRec, SQLDescribeCol), some others * bytes length (eg. SQLGetInfo, SQLGetDiagField, SQLGetConnectAttr, * EsSQLColAttributeW). */ -SQLRETURN write_tstr(esodbc_diag_st *diag, - SQLTCHAR *dest, const SQLTCHAR *src, +SQLRETURN write_wptr(esodbc_diag_st *diag, + SQLWCHAR *dest, const SQLWCHAR *src, SQLSMALLINT /*B*/avail, SQLSMALLINT /*B*/*usedp) { size_t src_cnt, awail; @@ -143,16 +143,16 @@ SQLRETURN write_tstr(esodbc_diag_st *diag, if (! dest) avail = 0; - /* needs to be multiple of SQLTCHAR units (2 on Win) */ - if (avail % sizeof(SQLTCHAR)) { + /* needs to be multiple of SQLWCHAR units (2 on Win) */ + if (avail % sizeof(SQLWCHAR)) { ERR("invalid buffer length provided: %d.", avail); RET_CDIAG(diag, SQL_STATE_HY090, "invalid buffer length provided", 0); } - awail = avail/sizeof(SQLTCHAR); + awail = avail/sizeof(SQLWCHAR); src_cnt = wcslen(src); /* return value always set to what it needs to be written (excluding \0).*/ - used = (SQLSMALLINT)src_cnt * sizeof(SQLTCHAR); + used = (SQLSMALLINT)src_cnt * sizeof(SQLWCHAR); if (! usedp) { WARN("invalid output buffer provided (NULL) to collect used space."); //RET_cDIAG(diag, SQL_STATE_HY013, "invalid used provided (NULL)", 0); @@ -171,7 +171,7 @@ SQLRETURN write_tstr(esodbc_diag_st *diag, dest[awail - 1] = 0; INFO("not enough buffer size to write required string (plus " - "terminator): `" LTPD "` [%zd]; available: %d.", src, + "terminator): `" LWPD "` [%zd]; available: %d.", src, src_cnt, awail); RET_DIAG(diag, SQL_STATE_01004, NULL, 0); } else { @@ -203,8 +203,8 @@ SQLRETURN EsSQLGetInfoW(SQLHDBC ConnectionHandle, /* Driver Information */ /* "what version of odbc a driver complies with" */ case SQL_DRIVER_ODBC_VER: - return write_tstr(&dbc->diag, InfoValue, - MK_TSTR(ESODBC_SQL_SPEC_STRING), BufferLength, + return write_wptr(&dbc->diag, InfoValue, + MK_WPTR(ESODBC_SQL_SPEC_STRING), BufferLength, StringLengthPtr); /* "if the driver can execute functions asynchronously on the @@ -249,25 +249,25 @@ SQLRETURN EsSQLGetInfoW(SQLHDBC ConnectionHandle, break; case SQL_DATA_SOURCE_NAME: - DBG("requested: data source name: `"LTPD"`.", dbc->connstr); - return write_tstr(&dbc->diag, InfoValue, dbc->connstr, + DBG("requested: data source name: `"LWPD"`.", dbc->dsn.str); + return write_wptr(&dbc->diag, InfoValue, dbc->dsn.str, BufferLength, StringLengthPtr); case SQL_DRIVER_NAME: DBG("requested: driver (file) name: %s.", DRIVER_NAME); - return write_tstr(&dbc->diag, InfoValue, - MK_TSTR(DRIVER_NAME), BufferLength, StringLengthPtr); + return write_wptr(&dbc->diag, InfoValue, + MK_WPTR(DRIVER_NAME), BufferLength, StringLengthPtr); break; case SQL_DATA_SOURCE_READ_ONLY: DBG("requested: if data source is read only (`Y`es, it is)."); - return write_tstr(&dbc->diag, InfoValue, MK_TSTR("Y"), + return write_wptr(&dbc->diag, InfoValue, MK_WPTR("Y"), BufferLength, StringLengthPtr); case SQL_SEARCH_PATTERN_ESCAPE: DBG("requested: escape character (`%s`).", ESODBC_PATTERN_ESCAPE); - return write_tstr(&dbc->diag, InfoValue, - MK_TSTR(ESODBC_PATTERN_ESCAPE), BufferLength, + return write_wptr(&dbc->diag, InfoValue, + MK_WPTR(ESODBC_PATTERN_ESCAPE), BufferLength, StringLengthPtr); case SQL_CORRELATION_NAME: @@ -288,8 +288,8 @@ SQLRETURN EsSQLGetInfoW(SQLHDBC ConnectionHandle, /* JDBC[0]: getCatalogSeparator() */ DBG("requested: catalogue separator (`%s`).", ESODBC_CATALOG_SEPARATOR); - return write_tstr(&dbc->diag, InfoValue, - MK_TSTR(ESODBC_CATALOG_SEPARATOR), BufferLength, + return write_wptr(&dbc->diag, InfoValue, + MK_WPTR(ESODBC_CATALOG_SEPARATOR), BufferLength, StringLengthPtr); case SQL_FILE_USAGE: @@ -304,8 +304,8 @@ SQLRETURN EsSQLGetInfoW(SQLHDBC ConnectionHandle, case SQL_CATALOG_TERM: /* SQL_QUALIFIER_TERM */ /* JDBC[0]: getCatalogSeparator() */ DBG("requested: catalogue term (`%s`).", ESODBC_CATALOG_TERM); - return write_tstr(&dbc->diag, InfoValue, - MK_TSTR(ESODBC_CATALOG_TERM), BufferLength, + return write_wptr(&dbc->diag, InfoValue, + MK_WPTR(ESODBC_CATALOG_TERM), BufferLength, StringLengthPtr); case SQL_MAX_SCHEMA_NAME_LEN: /* SQL_MAX_OWNER_NAME_LEN */ @@ -317,8 +317,8 @@ SQLRETURN EsSQLGetInfoW(SQLHDBC ConnectionHandle, case SQL_IDENTIFIER_QUOTE_CHAR: /* JDBC[0]: getIdentifierQuoteString() */ DBG("requested: quoting char (`%s`).", ESODBC_QUOTE_CHAR); - return write_tstr(&dbc->diag, InfoValue, - MK_TSTR(ESODBC_QUOTE_CHAR), BufferLength, + return write_wptr(&dbc->diag, InfoValue, + MK_WPTR(ESODBC_QUOTE_CHAR), BufferLength, StringLengthPtr); /* what Operations are supported by SQLSetPos */ @@ -356,15 +356,15 @@ SQLRETURN EsSQLGetInfoW(SQLHDBC ConnectionHandle, case SQL_SCHEMA_TERM: DBG("requested schema term (`%s`).", ESODBC_SCHEMA_TERM); - return write_tstr(&dbc->diag, InfoValue, - MK_TSTR(ESODBC_SCHEMA_TERM), BufferLength, + return write_wptr(&dbc->diag, InfoValue, + MK_WPTR(ESODBC_SCHEMA_TERM), BufferLength, StringLengthPtr); /* no procedures support */ case SQL_PROCEDURES: case SQL_ACCESSIBLE_PROCEDURES: DBG("requested: procedures support (`N`)."); - return write_tstr(&dbc->diag, InfoValue, MK_TSTR("N"), + return write_wptr(&dbc->diag, InfoValue, MK_WPTR("N"), BufferLength, StringLengthPtr); case SQL_MAX_PROCEDURE_NAME_LEN: DBG("requested max procedure name len (0)."); @@ -372,7 +372,7 @@ SQLRETURN EsSQLGetInfoW(SQLHDBC ConnectionHandle, break; case SQL_PROCEDURE_TERM: DBG("requested: procedure term (``)."); - return write_tstr(&dbc->diag, InfoValue, MK_TSTR(""), + return write_wptr(&dbc->diag, InfoValue, MK_WPTR(""), BufferLength, StringLengthPtr); default: @@ -397,7 +397,7 @@ SQLRETURN EsSQLGetDiagFieldW( esodbc_diag_st *diag, dummy; SQLSMALLINT used; size_t len; - SQLTCHAR *tstr; + SQLWCHAR *wptr; SQLRETURN ret; if (RecNumber <= 0) { @@ -449,13 +449,13 @@ SQLRETURN EsSQLGetDiagFieldW( /* Record Fields */ do { case SQL_DIAG_CLASS_ORIGIN: - len = (sizeof(ORIG_DISCRIM) - 1) * sizeof (SQLTCHAR); + len = (sizeof(ORIG_DISCRIM) - 1) * sizeof (SQLWCHAR); assert(len <= sizeof(esodbc_errors[diag->state].code)); - if (memcmp(esodbc_errors[diag->state].code, MK_TSTR(ORIG_DISCRIM), + if (memcmp(esodbc_errors[diag->state].code, MK_WPTR(ORIG_DISCRIM), len) == 0) { - tstr = MK_TSTR(ORIG_CLASS_ODBC); + wptr = MK_WPTR(ORIG_CLASS_ODBC); } else { - tstr = MK_TSTR(ORIG_CLASS_ISO); + wptr = MK_WPTR(ORIG_CLASS_ISO); } break; case SQL_DIAG_SUBCLASS_ORIGIN: @@ -502,29 +502,36 @@ SQLRETURN EsSQLGetDiagFieldW( case SQL_STATE_IM010: case SQL_STATE_IM011: case SQL_STATE_IM012: - tstr = MK_TSTR(ORIG_CLASS_ODBC); + wptr = MK_WPTR(ORIG_CLASS_ODBC); break; default: - tstr = MK_TSTR(ORIG_CLASS_ISO); + wptr = MK_WPTR(ORIG_CLASS_ISO); } break; } while (0); - DBG("diagnostic code '"LTPD"' is of class '"LTPD"'.", - esodbc_errors[diag->state].code, tstr); - return write_tstr(&dummy, DiagInfoPtr, tstr, BufferLength, + DBG("diagnostic code '"LWPD"' is of class '"LWPD"'.", + esodbc_errors[diag->state].code, wptr); + return write_wptr(&dummy, DiagInfoPtr, wptr, BufferLength, StringLengthPtr); case SQL_DIAG_CONNECTION_NAME: /* same as SQLGetInfo(SQL_DATA_SOURCE_NAME) */ case SQL_DIAG_SERVER_NAME: /* TODO: keep same as _CONNECTION_NAME? */ switch (HandleType) { - case SQL_HANDLE_DBC: tstr = DBCH(Handle)->connstr; break; - case SQL_HANDLE_DESC: // FIXME: once have db-stmt-desc link - case SQL_HANDLE_STMT: FIXME; break; - default: tstr = MK_TSTR(""); + case SQL_HANDLE_DBC: + wptr = DBCH(Handle)->dsn.str; + break; + case SQL_HANDLE_STMT: + wptr = STMH(Handle)->dbc->dsn.str; + break; + case SQL_HANDLE_DESC: + wptr = DSCH(Handle)->stmt->dbc->dsn.str; + break; + default: + wptr = MK_WPTR(""); } - DBG("inquired connection name (`"LTPD"`)", tstr); - return write_tstr(&dummy, DiagInfoPtr, tstr, BufferLength, + DBG("inquired connection name (`"LWPD"`)", wptr); + return write_wptr(&dummy, DiagInfoPtr, wptr, BufferLength, StringLengthPtr); @@ -540,7 +547,7 @@ SQLRETURN EsSQLGetDiagFieldW( return SQL_NO_DATA; } /* GetDiagField can't set diagnostics itself, so use a dummy */ - ret = write_tstr(&dummy, DiagInfoPtr, + ret = write_wptr(&dummy, DiagInfoPtr, esodbc_errors[diag->state].code, BufferLength, &used); if (StringLengthPtr) *StringLengthPtr = used; @@ -627,7 +634,7 @@ SQLRETURN EsSQLGetDiagRecW if (NativeError) *NativeError = diag->native_code; - ret = write_tstr(&dummy, MessageText, diag->text, + ret = write_wptr(&dummy, MessageText, diag->text, BufferLength * sizeof(*MessageText), &used); if (TextLength) *TextLength = used / sizeof(*MessageText); @@ -694,7 +701,7 @@ SQLRETURN EsSQLGetTypeInfoW(SQLHSTMT StatementHandle, SQLSMALLINT DataType) ret = EsSQLFreeStmt(stmt, ESODBC_SQL_CLOSE); assert(SQL_SUCCEEDED(ret)); /* can't return error */ - ret = attach_sql(stmt, MK_TSTR(SQL_TYPES_STATEMENT), + ret = attach_sql(stmt, MK_WPTR(SQL_TYPES_STATEMENT), sizeof(SQL_TYPES_STATEMENT) - 1); if (SQL_SUCCEEDED(ret)) ret = post_statement(stmt); diff --git a/driver/info.h b/driver/info.h index 82497c35..94d9c623 100644 --- a/driver/info.h +++ b/driver/info.h @@ -25,8 +25,8 @@ * TODO: change sign to ...src, lsrc, dst, dlen... (lsrc is nearly always * known) */ -SQLRETURN write_tstr(esodbc_diag_st *diag, - SQLTCHAR *dest, const SQLTCHAR *src, +SQLRETURN write_wptr(esodbc_diag_st *diag, + SQLWCHAR *dest, const SQLWCHAR *src, SQLSMALLINT /*B*/avail, SQLSMALLINT /*B*/*usedp); SQLRETURN EsSQLGetInfoW(SQLHDBC ConnectionHandle, diff --git a/driver/log.h b/driver/log.h index f003c0a7..f5b1d6fe 100644 --- a/driver/log.h +++ b/driver/log.h @@ -22,6 +22,7 @@ #include #include +#include "error.h" /* * w/printf() desriptors for char/wchar_t * @@ -58,8 +59,20 @@ #endif /* _WIN32 */ /* - * Descriptors to be used with logging with SQLTCHAR pointer type. - * "Log Tchar Pointer Descriptor [with Lenght]" + * Descriptors to be used with logging with SQLWCHAR pointer type. + * "Log Wchar Pointer Descriptor [with Lenght]" + */ +#ifdef UNICODE +#define LWPD PFWP_DESC +#define LWPDL PFWP_LDESC +#else /* UNICODE */ +#define LWPD PFCP_DESC +#define LWPDL PFCP_LDESC +#endif /* UNICODE */ + +/* + * Descriptors to be used with logging with SQLWCHAR pointer type. + * "Log Wchar Pointer Descriptor [with Lenght]" */ #ifdef UNICODE #define LTPD PFWP_DESC @@ -69,6 +82,10 @@ #define LTPDL PFCP_LDESC #endif /* UNICODE */ +/* macro for logging of wstr_st objects */ +#define LWSTR(_wptr) (int)(_wptr)->cnt, (_wptr)->str +#define LTSTR(_wptr) (int)(_wptr)->cnt, (_wptr)->str + /* Note: keep in sync with __ESODBC_LVL2STR */ @@ -107,7 +124,7 @@ extern int _esodbc_log_level; #define FIXME BUG("not yet implemented") #define TRACE DBG("===== TR4C3 ====="); -#define TS_NULL MK_TSTR("") +#define TS_NULL MK_WPTR("") #endif /* __LOG_H__ */ diff --git a/driver/queries.c b/driver/queries.c index 2756ca11..b1045271 100644 --- a/driver/queries.c +++ b/driver/queries.c @@ -72,47 +72,6 @@ void clear_resultset(esodbc_stmt_st *stmt) memset(&stmt->rset, 0, sizeof(stmt->rset)); } -/* - * Converts a wchar_t string to a C string for ANSI characters. - * 'dst' should be as character-long as 'src', if 'src' is not 0-terminated, - * OR one character longer (for the 0-term) otherwise. - * 'dst' will always be 0-term'd. - * Returns negative if conversion fails or number of converted wchars, - * including the 0-term. - * - */ -int ansi_w2c(const SQLTCHAR *src, char *dst, size_t chars) -{ - int i = 0; - - do { - if (CHAR_MAX < src[i]) - return -(i + 1); - dst[i] = (char)src[i]; - } while (src[i] && (++i < chars)); - - if (chars <= i) { - /* loop stopped b/c of lenght -> src is not 0-term'd */ - dst[i - 1] = 0; - return i; - } - return i + 1; -} - -static int wmemncasecmp(const wchar_t *a, const wchar_t *b, size_t len) -{ - size_t i; - int diff = 0; /* if len == 0 */ - for (i = 0; i < len; i ++) { - diff = towlower(a[i]) - towlower(b[i]); - if (diff) - break; - } - //DBG("`" LTPDL "` vs `" LTPDL "` => %d (len=%zd, i=%d).", - // len, a, len, b, diff, len, i); - return diff; -} - static SQLSMALLINT type_elastic2csql(const wchar_t *type_name, size_t len) { switch (len) { @@ -123,17 +82,17 @@ static SQLSMALLINT type_elastic2csql(const wchar_t *type_name, size_t len) case sizeof(JSON_COL_INTEGER) - 1: switch(tolower(type_name[0])) { case (wchar_t)'i': /* integer */ - if (wmemncasecmp(type_name, MK_WSTR(JSON_COL_INTEGER), + if (wmemncasecmp(type_name, MK_WPTR(JSON_COL_INTEGER), len) == 0) return SQL_C_SLONG; break; case (wchar_t)'b': /* boolean */ - if (wmemncasecmp(type_name, MK_WSTR(JSON_COL_BOOLEAN), + if (wmemncasecmp(type_name, MK_WPTR(JSON_COL_BOOLEAN), len) == 0) return SQL_C_UTINYINT; break; case (wchar_t)'k': /* keyword */ - if (wmemncasecmp(type_name, MK_WSTR(JSON_COL_KEYWORD), + if (wmemncasecmp(type_name, MK_WPTR(JSON_COL_KEYWORD), len) == 0) return SQL_C_CHAR; break; @@ -143,22 +102,22 @@ static SQLSMALLINT type_elastic2csql(const wchar_t *type_name, size_t len) case sizeof(JSON_COL_TEXT) - 1: switch(tolower(type_name[0])) { case (wchar_t)'t': - if (! wmemncasecmp(type_name, MK_WSTR(JSON_COL_TEXT), len)) + if (! wmemncasecmp(type_name, MK_WPTR(JSON_COL_TEXT), len)) // TODO: char/longvarchar/wchar/wvarchar? return SQL_C_CHAR; break; case (wchar_t)'d': - if (! wmemncasecmp(type_name, MK_WSTR(JSON_COL_DATE), len)) + if (! wmemncasecmp(type_name, MK_WPTR(JSON_COL_DATE), len)) // TODO: time/timestamp return SQL_C_TYPE_DATE; break; case (wchar_t)'b': - if (! wmemncasecmp(type_name, MK_WSTR(JSON_COL_BYTE), len)) + if (! wmemncasecmp(type_name, MK_WPTR(JSON_COL_BYTE), len)) return SQL_C_STINYINT; break; #if 1 // BUG FIXME case (wchar_t)'n': - if (! wmemncasecmp(type_name, MK_WSTR("null"), len)) + if (! wmemncasecmp(type_name, MK_WPTR("null"), len)) // TODO: time/timestamp return SQL_C_SSHORT; break; @@ -166,12 +125,12 @@ static SQLSMALLINT type_elastic2csql(const wchar_t *type_name, size_t len) } break; case sizeof(JSON_COL_SHORT) - 1: - if (! wmemncasecmp(type_name, MK_WSTR(JSON_COL_SHORT), len)) + if (! wmemncasecmp(type_name, MK_WPTR(JSON_COL_SHORT), len)) // TODO: time/timestamp return SQL_C_SSHORT; break; } - ERR("unrecognized Elastic type `" LTPDL "` (%zd).", len, type_name, len); + ERR("unrecognized Elastic type `" LWPDL "` (%zd).", len, type_name, len); return SQL_UNKNOWN_TYPE; } @@ -234,8 +193,8 @@ static SQLRETURN attach_columns(esodbc_stmt_st *stmt, UJObject columns) esodbc_desc_st *ird = stmt->ird; wchar_t *keys[] = { - MK_WSTR(JSON_ANSWER_COL_NAME), - MK_WSTR(JSON_ANSWER_COL_TYPE) + MK_WPTR(JSON_ANSWER_COL_NAME), + MK_WPTR(JSON_ANSWER_COL_TYPE) }; @@ -264,8 +223,8 @@ static SQLRETURN attach_columns(esodbc_stmt_st *stmt, UJObject columns) rec = &ird->recs[recno]; // +recno col_wname = UJReadString(name_o, &len); - assert(sizeof(*col_wname) == sizeof(SQLTCHAR)); /* TODO: no ANSI */ - rec->name = (SQLTCHAR *)col_wname; + assert(sizeof(*col_wname) == sizeof(SQLWCHAR)); /* TODO: no ANSI */ + rec->name = (SQLWCHAR *)col_wname; rec->unnamed = SQL_NAMED; col_wtype = UJReadString(type_o, &len); @@ -277,7 +236,7 @@ static SQLRETURN attach_columns(esodbc_stmt_st *stmt, UJObject columns) col_stype = type_elastic2csql(col_wtype, len); if (col_stype == SQL_UNKNOWN_TYPE) { ERRSTMT(stmt, "failed to convert Elastic to C SQL type `" - LTPDL "`.", len, col_wtype); + LWPDL "`.", len, col_wtype); RET_HDIAG(stmt, SQL_STATE_HY000, MSG_INV_SRV_ANS, 0); } rec->concise_type = col_stype; @@ -291,14 +250,14 @@ static SQLRETURN attach_columns(esodbc_stmt_st *stmt, UJObject columns) set_col_decdigits(rec); /* TODO: set all settable fields */ - rec->base_column_name = MK_TSTR(""); + rec->base_column_name = MK_WPTR(""); rec->display_size = 256; #ifndef NDEBUG //dump_record(rec); #endif /* NDEBUG */ - DBGSTMT(stmt, "column #%d: name=`" LTPD "`, type=%d (`" LTPD "`).", + DBGSTMT(stmt, "column #%d: name=`" LWPD "`, type=%d (`" LWPD "`).", recno, col_wname, col_stype, col_wtype); recno ++; } @@ -320,9 +279,9 @@ SQLRETURN attach_answer(esodbc_stmt_st *stmt, char *buff, size_t blen) const wchar_t *wcurs; size_t eccnt; wchar_t *keys[] = { - MK_WSTR(JSON_ANSWER_COLUMNS), - MK_WSTR(JSON_ANSWER_ROWS), - MK_WSTR(JSON_ANSWER_CURSOR) + MK_WPTR(JSON_ANSWER_COLUMNS), + MK_WPTR(JSON_ANSWER_ROWS), + MK_WPTR(JSON_ANSWER_CURSOR) }; /* clear any previous result set */ @@ -379,17 +338,16 @@ SQLRETURN attach_answer(esodbc_stmt_st *stmt, char *buff, size_t blen) if (cursor) { wcurs = UJReadString(cursor, &eccnt); if (eccnt) { - /* TODO: can this happen automatically anyways if hitting - * Elastic's scroller size? */ + /* this can happen automatically if hitting scroller size */ if (! stmt->dbc->fetch.max) - WARN("STMT@0x%p: no fetch size defined, but cursor returned."); + INFO("STMT@0x%p: no fetch size defined, but cursor returned."); if (stmt->rset.ecurs) - DBGSTMT(stmt, "replacing old cursor `" LTPDL "`.", + DBGSTMT(stmt, "replacing old cursor `" LWPDL "`.", stmt->rset.eccnt, stmt->rset.ecurs); /* store new cursor vals */ stmt->rset.ecurs = wcurs; stmt->rset.eccnt = eccnt; - DBGSTMT(stmt, "new elastic cursor: `" LTPDL "`[%zd].", + DBGSTMT(stmt, "new elastic cursor: `" LWPDL "`[%zd].", stmt->rset.eccnt, stmt->rset.ecurs, stmt->rset.eccnt); } else { WARNSTMT(stmt, "empty cursor found in the answer."); @@ -434,12 +392,12 @@ SQLRETURN attach_error(esodbc_stmt_st *stmt, char *buff, size_t blen) int n; void *state = NULL; wchar_t *outer_keys[] = { - MK_WSTR(JSON_ANSWER_ERROR), - MK_WSTR(JSON_ANSWER_STATUS) + MK_WPTR(JSON_ANSWER_ERROR), + MK_WPTR(JSON_ANSWER_STATUS) }; wchar_t *err_keys[] = { - MK_WSTR(JSON_ANSWER_ERR_TYPE), - MK_WSTR(JSON_ANSWER_ERR_REASON) + MK_WPTR(JSON_ANSWER_ERR_TYPE), + MK_WPTR(JSON_ANSWER_ERR_REASON) }; INFO("STMT@0x%p REST request failed with `%.*s` (%zd).", stmt, blen, buff, @@ -472,13 +430,13 @@ SQLRETURN attach_error(esodbc_stmt_st *stmt, char *buff, size_t blen) wreason = UJReadString(o_reason, &rlen); /* these return empty string in case of mismatch */ assert(wtype && wreason); - DBGSTMT(stmt, "server failures: type: [%zd] `" LTPDL "`, reason: [%zd] `" - LTPDL "`, status: %d.", tlen, tlen, wtype, rlen, rlen, wreason, + DBGSTMT(stmt, "server failures: type: [%zd] `" LWPDL "`, reason: [%zd] `" + LWPDL "`, status: %d.", tlen, tlen, wtype, rlen, rlen, wreason, UJNumericInt(o_status)); /* swprintf will fail if formated string would overrun the buffer size (as * opposed to write up to its limit) => find out the limit first.*/ - n = swprintf(NULL, 0, MK_WSTR("%.*s: %.*s"), (int)tlen, wtype, (int)rlen, + n = swprintf(NULL, 0, MK_WPTR("%.*s: %.*s"), (int)tlen, wtype, (int)rlen, wreason); if (0 < n) { wbuflen -= /* ": " */2 + /*\0*/1; @@ -487,14 +445,14 @@ SQLRETURN attach_error(esodbc_stmt_st *stmt, char *buff, size_t blen) rlen = left < rlen ? left : rlen; wbuflen += /* ": " */2 + /*\0*/1; /* swprintf will add the 0-term (or fail, if it can't) */ - n = swprintf(wbuf, wbuflen, MK_WSTR("%.*s: %.*s"), (int)tlen, wtype, + n = swprintf(wbuf, wbuflen, MK_WPTR("%.*s: %.*s"), (int)tlen, wtype, (int)rlen, wreason); } if (n < 0) { ERRN("failed to print error message from server."); assert(sizeof(MSG_INV_SRV_ANS) < sizeof(wbuf)); - memcpy(wbuf, MK_TSTR(MSG_INV_SRV_ANS), - sizeof(MSG_INV_SRV_ANS)*sizeof(SQLTCHAR)); + memcpy(wbuf, MK_WPTR(MSG_INV_SRV_ANS), + sizeof(MSG_INV_SRV_ANS)*sizeof(SQLWCHAR)); } post_diagnostic(&stmt->diag, SQL_STATE_HY000, wbuf, @@ -510,19 +468,19 @@ SQLRETURN attach_error(esodbc_stmt_st *stmt, char *buff, size_t blen) } SQLRETURN attach_sql(esodbc_stmt_st *stmt, - const SQLTCHAR *sql, /* SQL text statement */ + const SQLWCHAR *sql, /* SQL text statement */ size_t sqlcnt /* count of chars of 'sql' */) { char *u8; int len; - DBGSTMT(stmt, "attaching SQL `" LTPDL "` (%zd).", sqlcnt, sql, sqlcnt); + DBGSTMT(stmt, "attaching SQL `" LWPDL "` (%zd).", sqlcnt, sql, sqlcnt); #if 0 // FIXME if (wcslen(sql) < 1256) { if (wcsstr(sql, L"FROM test_emp")) { sql = L"SELECT emp_no, first_name, last_name, birth_date, 2+3 AS foo FROM test_emp"; sqlcnt = wcslen(sql); - DBGSTMT(stmt, "RE-attaching SQL `" LTPDL "` (%zd).", sqlcnt, + DBGSTMT(stmt, "RE-attaching SQL `" LWPDL "` (%zd).", sqlcnt, sql, sqlcnt); } } @@ -532,11 +490,11 @@ SQLRETURN attach_sql(esodbc_stmt_st *stmt, len = WCS2U8(sql, (int)sqlcnt, NULL, 0); if (len <= 0) { - ERRN("STMT@0x%p: failed to UCS2/UTF8 convert SQL `" LTPDL "` (%zd).", + ERRN("STMT@0x%p: failed to UCS2/UTF8 convert SQL `" LWPDL "` (%zd).", stmt, sqlcnt, sql, sqlcnt); RET_HDIAG(stmt, SQL_STATE_HY000, "UCS2/UTF8 conversion failure", 0); } - DBGSTMT(stmt, "wide char SQL `" LTPDL "`[%zd] converts to UTF8 on %d " + DBGSTMT(stmt, "wide char SQL `" LWPDL "`[%zd] converts to UTF8 on %d " "octets.", sqlcnt, sql, sqlcnt, len); u8 = malloc(len); @@ -547,7 +505,7 @@ SQLRETURN attach_sql(esodbc_stmt_st *stmt, len = WCS2U8(sql, (int)sqlcnt, u8, len); if (len <= 0) { /* can it happen? it's just succeded above */ - ERRN("STMT@0x%p: failed to UCS2/UTF8 convert SQL `" LTPDL "` (%zd).", + ERRN("STMT@0x%p: failed to UCS2/UTF8 convert SQL `" LWPDL "` (%zd).", stmt, sqlcnt, sql, sqlcnt); free(u8); RET_HDIAG(stmt, SQL_STATE_HY000, "UCS2/UTF8 conversion failure(2)", 0); @@ -827,7 +785,7 @@ static SQLRETURN copy_longlong(desc_rec_st *arec, desc_rec_st *irec, esodbc_desc_st *ard, *ird; SQLSMALLINT target_type; char buff[sizeof("18446744073709551616")]; /* = 1 << 8*8 */ - SQLTCHAR wbuff[sizeof("18446744073709551616")]; /* = 1 << 8*8 */ + SQLWCHAR wbuff[sizeof("18446744073709551616")]; /* = 1 << 8*8 */ size_t tocopy, blen; esodbc_state_et state = SQL_STATE_00000; @@ -921,7 +879,7 @@ static SQLRETURN wstr_to_cstr(desc_rec_st *arec, desc_rec_st *irec, if (out_bytes <= 0) { if (WCS2U8_BUFF_INSUFFICIENT) continue; - ERRN("failed to convert wchar* to char* for string `" LTPDL + ERRN("failed to convert wchar* to char* for string `" LWPDL "`.", chars, wstr); RET_HDIAGS(stmt, SQL_STATE_22018); } else { @@ -936,7 +894,7 @@ static SQLRETURN wstr_to_cstr(desc_rec_st *arec, desc_rec_st *irec, state = SQL_STATE_01004; /* indicate truncation */ } - DBG("REC@0x%p, data_ptr@0x%p, copied %zd bytes: `" LTPD "`.", arec, + DBG("REC@0x%p, data_ptr@0x%p, copied %zd bytes: `" LWPD "`.", arec, data_ptr, out_bytes, charp); } else { DBG("REC@0x%p, NULL data_ptr.", arec); @@ -946,7 +904,7 @@ static SQLRETURN wstr_to_cstr(desc_rec_st *arec, desc_rec_st *irec, if (octet_len_ptr) { out_bytes = (size_t)WCS2U8(wstr, (int)chars, NULL, 0); if (out_bytes <= 0) { - ERRN("failed to convert wchar* to char* for string `" LTPDL "`.", + ERRN("failed to convert wchar* to char* for string `" LWPDL "`.", chars, wstr); RET_HDIAGS(stmt, SQL_STATE_22018); } @@ -990,7 +948,7 @@ static SQLRETURN wstr_to_wstr(desc_rec_st *arec, desc_rec_st *irec, state = SQL_STATE_01004; /* indicate truncation */ } - DBG("REC@0x%p, data_ptr@0x%p, copied %zd bytes: `" LTPD "`.", arec, + DBG("REC@0x%p, data_ptr@0x%p, copied %zd bytes: `" LWPD "`.", arec, data_ptr, out_bytes, widep); } else { DBG("REC@0x%p, NULL data_ptr", arec); @@ -1019,7 +977,7 @@ static SQLRETURN wstr_to_timestamp(desc_rec_st *arec, desc_rec_st *irec, timestamp_t tsp; struct tm tmp; - DBG("converting ISO 8601 `" LTPDL "` to timestamp.", chars, wstr); + DBG("converting ISO 8601 `" LWPDL "` to timestamp.", chars, wstr); if (octet_len_ptr) *octet_len_ptr = sizeof(*tss); @@ -1029,7 +987,7 @@ static SQLRETURN wstr_to_timestamp(desc_rec_st *arec, desc_rec_st *irec, len = ansi_w2c(wstr, buff, len); if (len <= 0 || timestamp_parse(buff, len - 1, &tsp) || (! timestamp_to_tm_local(&tsp, &tmp))) { - ERR("data `" LTPDL "` not an ANSI ISO 8601 format.", chars, wstr); + ERR("data `" LWPDL "` not an ANSI ISO 8601 format.", chars, wstr); RET_HDIAGS(stmt, SQL_STATE_07006); } TM_TO_TIMESTAMP_STRUCT(&tmp, tss); @@ -1118,7 +1076,7 @@ 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->diag, _state, MK_TSTR(_message), 0, \ + return post_row_diagnostic(&stmt->diag, _state, MK_WPTR(_message), 0, \ rowno, _colno); \ } while (0) #define SET_ROW_DIAG(_rowno, _colno) \ @@ -1176,7 +1134,7 @@ static SQLRETURN copy_one_row(esodbc_stmt_st *stmt, SQLULEN pos, UJObject row) case UJT_String: wstr = UJReadString(obj, &len); - DBG("value [%zd, %d] is string: `" LTPD "`.", rowno, i + 1, + DBG("value [%zd, %d] is string: `" LWPD "`.", rowno, i + 1, wstr); /* UJSON4C returns chars count, but 0-terminates w/o counting * the terminator */ @@ -1491,7 +1449,7 @@ SQLRETURN EsSQLPrepareW ERRSTMT(stmt, "invalid statment lenght: %d.", cchSqlStr); RET_HDIAGS(stmt, SQL_STATE_HY090); } - DBGSTMT(stmt, "preparing `" LTPDL "` [%d]", cchSqlStr, szSqlStr, + DBGSTMT(stmt, "preparing `" LWPDL "` [%d]", cchSqlStr, szSqlStr, cchSqlStr); ret = EsSQLFreeStmt(stmt, ESODBC_SQL_CLOSE); @@ -1551,7 +1509,7 @@ SQLRETURN EsSQLExecDirectW ERRSTMT(stmt, "invalid statment lenght: %d.", cchSqlStr); RET_HDIAGS(stmt, SQL_STATE_HY090); } - DBGSTMT(stmt, "directly executing SQL: `" LTPDL "` [%d].", cchSqlStr, + DBGSTMT(stmt, "directly executing SQL: `" LWPDL "` [%d].", cchSqlStr, szSqlStr, cchSqlStr); ret = EsSQLFreeStmt(stmt, ESODBC_SQL_CLOSE); @@ -1648,10 +1606,10 @@ SQLRETURN EsSQLDescribeColW( #endif /* NDEBUG */ if (szColName) { - ret = write_tstr(&stmt->diag, szColName, rec->name, + ret = write_wptr(&stmt->diag, szColName, rec->name, cchColNameMax * sizeof(*szColName), &col_blen); if (! SQL_SUCCEEDED(ret)) { - ERRSTMT(stmt, "failed to copy column name `" LTPD "`.", rec->name); + ERRSTMT(stmt, "failed to copy column name `" LWPD "`.", rec->name); return ret; } } else { @@ -1726,7 +1684,7 @@ SQLRETURN EsSQLColAttributeW( esodbc_desc_st *ird = stmt->ird; desc_rec_st *rec; SQLSMALLINT sint; - SQLTCHAR *tstr; + SQLWCHAR *wptr; SQLLEN len; SQLINTEGER iint; @@ -1771,28 +1729,28 @@ SQLRETURN EsSQLColAttributeW( PNUMATTR_ASSIGN(SQLSMALLINT, sint); break; - /* SQLTCHAR* */ + /* SQLWCHAR* */ do { - case SQL_DESC_BASE_COLUMN_NAME: tstr = rec->base_column_name; break; - case SQL_DESC_BASE_TABLE_NAME: tstr = rec->base_table_name; break; - case SQL_DESC_CATALOG_NAME: tstr = rec->catalog_name; break; - case SQL_DESC_LABEL: tstr = rec->label; break; - case SQL_DESC_LITERAL_PREFIX: tstr = rec->literal_prefix; break; - case SQL_DESC_LITERAL_SUFFIX: tstr = rec->literal_suffix; break; - case SQL_DESC_LOCAL_TYPE_NAME: tstr = rec->type_name; break; - case SQL_DESC_NAME: tstr = rec->name; break; - case SQL_DESC_SCHEMA_NAME: tstr = rec->schema_name; break; - case SQL_DESC_TABLE_NAME: tstr = rec->table_name; break; - case SQL_DESC_TYPE_NAME: tstr = rec->type_name; break; + case SQL_DESC_BASE_COLUMN_NAME: wptr = rec->base_column_name; break; + case SQL_DESC_BASE_TABLE_NAME: wptr = rec->base_table_name; break; + case SQL_DESC_CATALOG_NAME: wptr = rec->catalog_name; break; + case SQL_DESC_LABEL: wptr = rec->label; break; + case SQL_DESC_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; } while (0); - if (! tstr) { + if (! wptr) { //BUG -- TODO: re-eval, once type handling is decided. ERRSTMT(stmt, "IRD@0x%p record field type %d not initialized.", ird, iField); - *(SQLTCHAR **)pCharAttr = MK_TSTR(""); + *(SQLWCHAR **)pCharAttr = MK_WPTR(""); *pcbCharAttr = 0; } else { - return write_tstr(&stmt->diag, pcbCharAttr, tstr, cbDescMax, + return write_wptr(&stmt->diag, pcbCharAttr, wptr, cbDescMax, pcbCharAttr); } break; diff --git a/driver/queries.h b/driver/queries.h index ace23c3c..ffc36f6f 100644 --- a/driver/queries.h +++ b/driver/queries.h @@ -23,39 +23,9 @@ void clear_resultset(esodbc_stmt_st *stmt); SQLRETURN attach_answer(esodbc_stmt_st *stmt, char *buff, size_t blen); SQLRETURN attach_error(esodbc_stmt_st *stmt, char *buff, size_t blen); -SQLRETURN attach_sql(esodbc_stmt_st *stmt, const SQLTCHAR *sql, size_t tlen); +SQLRETURN attach_sql(esodbc_stmt_st *stmt, const SQLWCHAR *sql, size_t tlen); void detach_sql(esodbc_stmt_st *stmt); -// TODO: move to util.h -#ifdef _WIN32 -/* - * "[D]oes not null-terminate an output string if the input string length is - * explicitly specified without a terminating null character. To - * null-terminate an output string for this function, the application should - * pass in -1 or explicitly count the terminating null character for the input - * string." - * "If successful, returns the number of bytes written" or required (if - * _ubytes == 0), OR "0 if it does not succeed". - */ -#define WCS2U8(_wstr, _wchars, _u8, _ubytes) \ - WideCharToMultiByte(CP_UTF8, WC_NO_BEST_FIT_CHARS, \ - _wstr, _wchars, _u8, _ubytes, \ - NULL, NULL) -#define WCS2U8_BUFF_INSUFFICIENT \ - (GetLastError() == ERROR_INSUFFICIENT_BUFFER) -#define WCS2U8_ERRNO() GetLastError() - -#else /* _WIN32 */ -#error "unsupported platform" /* TODO */ - /* "[R]eturns the number of bytes written into the multibyte output - * string, excluding the terminating NULL (if any)". Copies until \0 is - * met in wstr or buffer runs out. If \0 is met, it's copied, but not - * counted in return. (silly fn) */ - /* "[T]he multibyte character string at mbstr is null-terminated - * only if wcstombs encounters a wide-character null character - * during conversion." */ - // wcstombs(charp, wstr, octet_length); -#endif /* _WIN32 */ SQLRETURN EsSQLBindCol( SQLHSTMT StatementHandle, diff --git a/driver/tracing.h b/driver/tracing.h index 5d68ce71..bcd53ccf 100644 --- a/driver/tracing.h +++ b/driver/tracing.h @@ -75,7 +75,7 @@ case 'W': /* wchar_t* */ \ /* TODO: this can be problematic, for untouched buffs: add * len! */ \ - _n = snprintf(_bf + _ps, _AVAIL, "`" LTPD "`[%zd]", \ + _n = snprintf(_bf + _ps, _AVAIL, "`" LWPD "`[%zd]", \ val ? (wchar_t *)(uintptr_t)val : TS_NULL, \ val ? wcslen((wchar_t *)(uintptr_t)val) : 0); \ break; \ diff --git a/driver/util.c b/driver/util.c new file mode 100644 index 00000000..f7d36de8 --- /dev/null +++ b/driver/util.c @@ -0,0 +1,204 @@ +/* + * ELASTICSEARCH CONFIDENTIAL + * __________________ + * + * [2018] Elasticsearch Incorporated. All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Elasticsearch Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Elasticsearch Incorporated + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Elasticsearch Incorporated. + */ + +#include + +#include "util.h" + + + +BOOL wstr2bool(wstr_st *val) +{ + switch (val->cnt) { + case /*0*/1: return ! EQ_CASE_WSTR(val, &MK_WSTR("0")); + case /*no*/2: return ! EQ_CASE_WSTR(val, &MK_WSTR("no")); + case /*false*/5: return ! EQ_CASE_WSTR(val, &MK_WSTR("false")); + } + return TRUE; +} + +BOOL wstr2long(wstr_st *val, long *out) +{ + long res = 0, digit; + int i = 0; + BOOL negative; + + if (val->cnt < 1) + return FALSE; + + switch (val->str[0]) { + case '-': + negative = TRUE; + i ++; + break; + case '+': + negative = FALSE; + i ++; + break; + default: + negative = FALSE; + } + + for ( ; i < val->cnt; i ++) { + /* is it a number? */ + if (val->str[i] < '0' || '9' < val->str[i]) + return FALSE; + digit = val->str[i] - '0'; + /* would it overflow?*/ + if (LONG_MAX - res < digit) + return FALSE; + res *= 10; + res += digit; + } + *out = negative ? - res : res; + return TRUE; +} + +/* + * Converts a wchar_t string to a C string for ANSI characters. + * 'dst' should be as character-long as 'src', if 'src' is not 0-terminated, + * OR one character longer otherwise (for the 0-term). + * 'dst' will always be 0-term'd. + * Returns negative if conversion fails, OR number of converted wchars, + * including the 0-term. + * + */ +int ansi_w2c(const SQLWCHAR *src, char *dst, size_t chars) +{ + int i = 0; + + do { + if (CHAR_MAX < src[i]) + return -(i + 1); + dst[i] = (char)src[i]; + } while (src[i] && (++i < chars)); + + if (chars <= i) { + /* loop stopped b/c of lenght -> src is not 0-term'd */ + dst[i - 1] = 0; + return i; + } + return i + 1; +} + +int wmemncasecmp(const wchar_t *a, const wchar_t *b, size_t len) +{ + size_t i; + int diff = 0; /* if len == 0 */ + for (i = 0; i < len; i ++) { + diff = towlower(a[i]) - towlower(b[i]); + if (diff) + break; + } + //DBG("`" LWPDL "` vs `" LWPDL "` => %d (len=%zd, i=%d).", + // len, a, len, b, diff, len, i); + return diff; +} + +/* retuns the lenght 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) +{ + size_t i, newlen = 0; + unsigned char uchar; + for (i = 0; i < len; i ++) { + uchar = (unsigned char)json[i]; + switch(uchar) { + case '"': + case '\\': + case '/': + case '\b': + case '\f': + case '\n': + case '\r': + case '\t': + newlen += /* '\' + json[i] */2; + break; + default: + newlen += (0x20 <= uchar) ? 1 : /* \u00XX */6; + // break + } + } + return newlen; +} + +/* + * JSON-escapes a string. + * If string len is 0, it assumes a NTS. + * If output buffer (jout) is NULL, it returns the buffer size needed for + * escaping. + * Returns number of used bytes in buffer (which might be less than buffer + * size, if some char needs an escaping longer than remaining space). + */ +size_t json_escape(const char *jin, size_t inlen, char *jout, size_t outlen) +{ + size_t i, pos; + unsigned char uchar; + +#define I16TOA(_x) (10 <= (_x)) ? 'A' + (_x) - 10 : '0' + (_x) + + if (! inlen) + inlen = strlen(jin); + if (! jout) + return json_escaped_len(jin, inlen); + + for (i = 0, pos = 0; i < inlen; i ++) { + uchar = jin[i]; + switch(uchar) { + do { + case '\b': uchar = 'b'; break; + case '\f': uchar = 'f'; break; + case '\n': uchar = 'n'; break; + case '\r': uchar = 'r'; break; + case '\t': uchar = 't'; break; + } while (0); + case '"': + case '\\': + case '/': + if (outlen <= pos + 1) { + i = inlen; // break the for loop + continue; + } + jout[pos ++] = '\\'; + jout[pos ++] = (char)uchar; + break; + default: + if (0x20 <= uchar) { + if (outlen <= pos) { + i = inlen; + continue; + } + jout[pos ++] = uchar; + } else { // case 0x00 .. 0x1F + if (outlen <= pos + 5) { + i = inlen; + continue; + } + memcpy(jout + pos, "\\u00", sizeof("\\u00") - 1); + pos += sizeof("\\u00") - 1; + jout[pos ++] = I16TOA(uchar >> 4); + jout[pos ++] = I16TOA(uchar & 0xF); + } + break; + } + } + return pos; +#undef I16TOA +} + + +/* vim: set noet fenc=utf-8 ff=dos sts=0 sw=4 ts=4 : */ diff --git a/driver/util.h b/driver/util.h new file mode 100644 index 00000000..492cc29e --- /dev/null +++ b/driver/util.h @@ -0,0 +1,136 @@ +/* + * ELASTICSEARCH CONFIDENTIAL + * __________________ + * + * [2018] Elasticsearch Incorporated. All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Elasticsearch Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Elasticsearch Incorporated + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Elasticsearch Incorporated. + */ + +#ifndef __UTIL_H__ +#define __UTIL_H__ + +/* NOTE: this must be included in "top level" file (wherever SQL types are + * used */ +#if defined(_WIN32) || defined (WIN32) +/* FIXME: why isn't this included in sql/~ext.h? */ +/* win function parameter attributes */ +#include +#endif /* _WIN32/WIN32 */ + +#include +#include + +#include "sql.h" +#include "sqlext.h" + +/* + * Stringifying in two preproc. passes + */ +#define _STR(_x) # _x +#define STR(_x) _STR(_x) + +/* + * Turn a C static string to wide char string. + */ +#define _MK_WPTR(_cstr_) (L ## _cstr_) +#define MK_WPTR(_cstr_) _MK_WPTR(_cstr_) + +typedef struct cstr { + SQLCHAR *str; + size_t cnt; +} cstr_st; + +/* + * Copy converted strings from SQLWCHAR to char, for ANSI strings. + */ +int ansi_w2c(const SQLWCHAR *src, char *dst, size_t chars); +/* + * Compare two wchar_t object, case INsensitive. + */ +int wmemncasecmp(const wchar_t *a, const wchar_t *b, size_t len); + +typedef struct wstr { + SQLWCHAR *str; + size_t cnt; +} wstr_st; + +/* + * Turn a static C string t a wstr_st. + */ +#define MK_WSTR(_s) \ + ((wstr_st){.str = MK_WPTR(_s), .cnt = sizeof(_s) - 1}) +/* + * Test equality of two wstr_st objects. + */ +#define EQ_WSTR(s1, s2) \ + ((s1)->cnt == (s2)->cnt && wmemcmp((s1)->str, (s2)->str, (s1)->cnt) == 0) +/* + * Same as EQ_WSTR, but case INsensitive. + */ +#define EQ_CASE_WSTR(s1, s2) \ + ((s1)->cnt == (s2)->cnt && \ + wmemncasecmp((s1)->str, (s2)->str, (s1)->cnt) == 0) + +BOOL wstr2bool(wstr_st *val); +BOOL wstr2long(wstr_st *val, long *out); + +#ifdef _WIN32 +/* + * "[D]oes not null-terminate an output string if the input string length is + * explicitly specified without a terminating null character. To + * null-terminate an output string for this function, the application should + * pass in -1 or explicitly count the terminating null character for the input + * string." + * "If successful, returns the number of bytes written" or required (if + * _ubytes == 0), OR "0 if it does not succeed". + */ +#define WCS2U8(_wstr, _wchars, _u8, _ubytes) \ + WideCharToMultiByte(CP_UTF8, WC_NO_BEST_FIT_CHARS, \ + _wstr, _wchars, _u8, _ubytes, \ + NULL, NULL) +#define WCS2U8_BUFF_INSUFFICIENT \ + (GetLastError() == ERROR_INSUFFICIENT_BUFFER) +#define WCS2U8_ERRNO() GetLastError() + +#else /* _WIN32 */ +#error "unsupported platform" /* TODO */ + /* "[R]eturns the number of bytes written into the multibyte output + * string, excluding the terminating NULL (if any)". Copies until \0 is + * met in wstr or buffer runs out. If \0 is met, it's copied, but not + * counted in return. (silly fn) */ + /* "[T]he multibyte character string at mbstr is null-terminated + * only if wcstombs encounters a wide-character null character + * during conversion." */ + // wcstombs(charp, wstr, octet_length); +#endif /* _WIN32 */ + +#ifdef UNICODE +typedef wstr_st tstr_st; +#else /* UNICODE */ +typedef cstr_st tstr_st; +#endif /* UNICODE */ + + +/* + * JSON-escapes a string. + * If string len is 0, it assumes a NTS. + * If output buffer (jout) is NULL, it returns the buffer size needed for + * escaping. + * Returns number of used bytes in buffer (which might be less than buffer + * size, if some char needs an escaping longer than remaining space). + */ +size_t json_escape(const char *jin, size_t inlen, char *jout, size_t outlen); + + +#endif /* __UTIL_H__ */ + +/* vim: set noet fenc=utf-8 ff=dos sts=0 sw=4 ts=4 : */