diff --git a/driver/catalogue.c b/driver/catalogue.c index 2b5b5d27..7c750f42 100644 --- a/driver/catalogue.c +++ b/driver/catalogue.c @@ -28,19 +28,153 @@ #include "info.h" #include "queries.h" -// TODO: add type (and schema, when supported) -#define SQL_TABLES "SYS TABLES" \ - " CATALOG LIKE " ESODBC_STRING_DELIM WPFWP_LDESC ESODBC_STRING_DELIM \ + +#define SYS_CATALOGS \ + "SYS CATALOGS" + +/* SYS TABLES syntax tokens; these need to stay broken down, since this + * query makes a difference between a predicate being '%' or left out */ +// TODO: schema, when supported +#define SQL_TABLES \ + "SYS TABLES" +#define SQL_TABLES_CAT \ + " CATALOG LIKE " ESODBC_STRING_DELIM WPFWP_LDESC ESODBC_STRING_DELIM +#define SQL_TABLES_TAB \ " LIKE " ESODBC_STRING_DELIM WPFWP_LDESC ESODBC_STRING_DELIM +#define SQL_TABLES_TYP \ + " TYPE " WPFWP_LDESC // TODO add schema, when supported #define SQL_COLUMNS(...) "SYS COLUMNS" __VA_ARGS__ \ " TABLE LIKE " ESODBC_STRING_DELIM WPFWP_LDESC ESODBC_STRING_DELIM \ " LIKE " ESODBC_STRING_DELIM WPFWP_LDESC ESODBC_STRING_DELIM -#define SQL_COL_CAT \ +#define SQL_COL_CAT \ " CATALOG " ESODBC_STRING_DELIM WPFWP_LDESC ESODBC_STRING_DELIM \ +/* writes into 'dest', of size 'room', the current catalog of 'dbc'. + * returns negative on error, or the char count written otherwise */ +SQLSMALLINT copy_current_catalog(esodbc_dbc_st *dbc, SQLWCHAR *dest, + SQLSMALLINT room) +{ + esodbc_stmt_st *stmt = NULL; + SQLSMALLINT used = -1; /*failure*/ + SQLLEN row_cnt; + SQLLEN ind_len = SQL_NULL_DATA; + SQLWCHAR buff[ESODBC_MAX_IDENTIFIER_LEN]; + SQLWCHAR *catalog; + + if (! SQL_SUCCEEDED(EsSQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt))) { + ERRH(dbc, "failed to alloc a statement handle."); + return -1; + } + assert(stmt); + + if (! SQL_SUCCEEDED(attach_sql(stmt, MK_WPTR(SYS_CATALOGS), + sizeof(SYS_CATALOGS) - 1))) { + ERRH(dbc, "failed to attach query to statement."); + goto end; + } + if (! SQL_SUCCEEDED(post_statement(stmt))) { + ERRH(dbc, "failed to post query."); + goto end; + } + + /* check that we have received proper number of rows (non-0, less than + * max allowed here) */ + if (! SQL_SUCCEEDED(EsSQLRowCount(stmt, &row_cnt))) { + ERRH(dbc, "failed to get result rows count."); + goto end; + } else if (row_cnt <= 0) { + WARNH(stmt, "Elasticsearch returned no current catalog."); + catalog = MK_WPTR(""); /* empty string, it's not quite an error */ + } else { + DBGH(stmt, "Elasticsearch catalogs rows count: %ld.", row_cnt); + if (1 < row_cnt) { + WARNH(dbc, "Elasticsearch connected to %d clusters, returning " + "the first's name as current catalog.", row_cnt); + } + + if (! SQL_SUCCEEDED(EsSQLBindCol(stmt, /*col#*/1, SQL_C_WCHAR, buff, + sizeof(buff), &ind_len))) { + ERRH(dbc, "failed to bind first column."); + goto end; + } + if (! SQL_SUCCEEDED(EsSQLFetch(stmt))) { + ERRH(stmt, "failed to fetch results."); + goto end; + } + if (ind_len <= 0) { + WARNH(dbc, "NULL catalog received."); /*tho maybe != NULL_DATA */ + catalog = MK_WPTR(""); + } else { + catalog = buff; + DBGH(dbc, "current catalog (first value returned): `" LWPD "`.", + catalog); + } + } + + if (! SQL_SUCCEEDED(write_wptr(&dbc->hdr.diag, dest, catalog, room, + &used))) { + ERRH(dbc, "failed to copy catalog: `" LWPD "`.", catalog); + used = -1; /* write_wptr() can change pointer, and still fail */ + } + +end: + /* safe even if no binding occured */ + if (! SQL_SUCCEEDED(EsSQLFreeStmt(stmt, SQL_UNBIND))) { + ERRH(stmt, "failed to unbind statement"); + used = -1; + } + if (! SQL_SUCCEEDED(EsSQLFreeHandle(SQL_HANDLE_STMT, stmt))) { + ERRH(dbc, "failed to free statement handle!"); + } + return used; +} + +/* + * Quote the tokens in a string: "a, b,,c" -> "'a','b',,'c'". + * No string sanity done (garbage in, garbage out). + */ +size_t quote_tokens(SQLWCHAR *src, size_t len, SQLWCHAR *dest) +{ + size_t i; + BOOL copying; + SQLWCHAR *pos; + + copying = FALSE; + pos = dest; + for (i = 0; i < len; i ++) { + switch (src[i]) { + /* ignore white space */ + case L' ': + case L'\t': + if (copying) { + *pos ++ = L'\''; /* end current token */ + copying = FALSE; + } + continue; /* don't copy WS */ + + case L',': + if (copying) { + *pos ++ = L'\''; /* end current token */ + copying = FALSE; + } /* else continue; -- to remove extra `,` */ + break; + + default: + if (! copying) { + *pos ++ = L'\''; /* start a new token */ + } + copying = TRUE; + } + *pos ++ = src[i]; + } + /* should not overrun */ + assert(i < 2/*see typ_buf below*/ * ESODBC_MAX_IDENTIFIER_LEN); + return pos - dest; +} + SQLRETURN EsSQLTablesW( SQLHSTMT StatementHandle, _In_reads_opt_(NameLength1) SQLWCHAR *CatalogName, @@ -54,29 +188,47 @@ SQLRETURN EsSQLTablesW( { esodbc_stmt_st *stmt = STMH(StatementHandle); SQLRETURN ret; - SQLWCHAR wbuf[sizeof(SQL_TABLES) + 2 * ESODBC_MAX_IDENTIFIER_LEN]; - SQLWCHAR *table, *schema, *catalog; - size_t cnt_tab, cnt_sch, cnt_cat, pos; + /* b/c declaring an array with a const doesn't work with MSVC's compiler */ + enum wbuf_len { wbuf_len = sizeof(SQL_TABLES) + + sizeof(SQL_TABLES_CAT) + + sizeof(SQL_TABLES_TAB) + + sizeof(SQL_TABLES_TYP) + + 3 * ESODBC_MAX_IDENTIFIER_LEN /* it has 4x 0-term space */ + }; + SQLWCHAR wbuf[wbuf_len]; + SQLWCHAR *table, *schema, *catalog, *type; + size_t cnt_tab, cnt_sch, cnt_cat, cnt_typ, pos; + /* 2x: "a,b,c" -> "'a','b','c'" : each "x," => "'x'," */ + SQLWCHAR typ_buf[2 * ESODBC_MAX_IDENTIFIER_LEN]; if (stmt->metadata_id == SQL_TRUE) FIXME; // FIXME + pos = sizeof(SQL_TABLES) - 1; + wmemcpy(wbuf, MK_WPTR(SQL_TABLES), pos); + if (CatalogName) { catalog = CatalogName; if (NameLength1 == SQL_NTS) { cnt_cat = wcslen(catalog); if (ESODBC_MAX_IDENTIFIER_LEN < cnt_cat) { ERRH(stmt, "catalog identifier name '" LTPDL "' too long " - "(%d. max=%d).", cnt_cat, catalog, cnt_cat, + "(%zd. max=%d).", (int)cnt_cat, catalog, cnt_cat, ESODBC_MAX_IDENTIFIER_LEN); RET_HDIAG(stmt, SQL_STATE_HY090, "catalog name too long", 0); } } else { cnt_cat = NameLength1; } - } else { - catalog = MK_WPTR(SQL_ALL_CATALOGS); - cnt_cat = sizeof(SQL_ALL_CATALOGS) - /*0-term*/1; + + cnt_cat = swprintf(wbuf + pos, wbuf_len - pos, SQL_TABLES_CAT, + (int)cnt_cat, catalog); + if (cnt_cat <= 0) { + ERRH(stmt, "failed to print 'catalog' for tables catalog SQL."); + RET_HDIAGS(stmt, SQL_STATE_HY000); + } else { + pos += cnt_cat; + } } if (SchemaName) { @@ -85,23 +237,20 @@ SQLRETURN EsSQLTablesW( cnt_sch = wcslen(schema); if (ESODBC_MAX_IDENTIFIER_LEN < cnt_sch) { ERRH(stmt, "schema identifier name '" LTPDL "' too long " - "(%d. max=%d).", cnt_sch, schema, cnt_sch, + "(%zd. max=%d).", (int)cnt_sch, schema, cnt_sch, ESODBC_MAX_IDENTIFIER_LEN); RET_HDIAG(stmt, SQL_STATE_HY090, "schema name too long", 0); } } else { cnt_sch = NameLength2; } - } else { - schema = MK_WPTR(SQL_ALL_SCHEMAS); - cnt_sch = sizeof(SQL_ALL_SCHEMAS) - /*0-term*/1; - } - /* TODO: server support needed for sch. name filtering */ - if (cnt_sch && wszmemcmp(schema, MK_WPTR(SQL_ALL_SCHEMAS), - (long)cnt_sch)) { - ERRH(stmt, "filtering by schemas is not supported."); - RET_HDIAG(stmt, SQL_STATE_IM001, "schema filtering not supported", 0); + /* TODO: server support needed for sch. name filtering */ + if (wszmemcmp(schema, MK_WPTR(SQL_ALL_SCHEMAS), (long)cnt_sch)) { + ERRH(stmt, "filtering by schemas is not supported."); + RET_HDIAG(stmt, SQL_STATE_IM001, "schema filtering not supported", + 0); + } } // FIXME: string needs escaping of % \\ _ @@ -111,32 +260,57 @@ SQLRETURN EsSQLTablesW( cnt_tab = wcslen(table); if (ESODBC_MAX_IDENTIFIER_LEN < cnt_tab) { ERRH(stmt, "table identifier name '" LTPDL "' too long " - "(%d. max=%d).", cnt_tab, table, cnt_tab, + "(%zd. max=%d).", (int)cnt_tab, table, cnt_tab, ESODBC_MAX_IDENTIFIER_LEN); RET_HDIAG(stmt, SQL_STATE_HY090, "table name too long", 0); } } else { cnt_tab = NameLength3; } - } else { - table = MK_WPTR(ESODBC_ALL_TABLES); - cnt_tab = sizeof(ESODBC_ALL_TABLES) - /*0-term*/1; - } -#if 1 // TODO: GH#4334 - if (cnt_tab == 0) { - table = MK_WPTR(ESODBC_ALL_TABLES); - cnt_tab = sizeof(ESODBC_ALL_TABLES) - /*0-term*/1; + + cnt_tab = swprintf(wbuf + pos, wbuf_len - pos, SQL_TABLES_TAB, + (int)cnt_tab, table); + if (cnt_tab <= 0) { + ERRH(stmt, "failed to print 'table' for tables catalog SQL."); + RET_HDIAGS(stmt, SQL_STATE_HY000); + } else { + pos += cnt_tab; + } } -#endif // 1 - /* print SQL to send to server */ - pos = swprintf(wbuf, sizeof(wbuf)/sizeof(wbuf[0]), SQL_TABLES, - (int)cnt_cat, catalog, (int)cnt_tab, table); - if (pos <= 0) { - ERRH(stmt, "failed to print 'tables' catalog SQL."); - RET_HDIAGS(stmt, SQL_STATE_HY000); + if (TableType) { + type = TableType; + if (NameLength4 == SQL_NTS) { + cnt_typ = wcslen(type); + if (ESODBC_MAX_IDENTIFIER_LEN < cnt_typ) { + ERRH(stmt, "type identifier name '" LTPDL "' too long " + "(%zd. max=%d).", (int)cnt_typ, type, cnt_typ, + ESODBC_MAX_IDENTIFIER_LEN); + RET_HDIAG(stmt, SQL_STATE_HY090, "type name too long", 0); + } + } else { + cnt_typ = NameLength4; + } + + /* In this argument, "each value can be enclosed in single quotation + * marks (') or unquoted" => quote if not quoted (see GH#30398). */ + if (! wcsnstr(type, cnt_typ, L'\'')) { + cnt_typ = quote_tokens(type, cnt_typ, typ_buf); + type = typ_buf; + } + + cnt_typ = swprintf(wbuf + pos, wbuf_len - pos, SQL_TABLES_TYP, + (int)cnt_typ, type); + if (cnt_typ <= 0) { + ERRH(stmt, "failed to print 'type' for tables catalog SQL."); + RET_HDIAGS(stmt, SQL_STATE_HY000); + } else { + pos += cnt_typ; + } } + DBGH(stmt, "tables catalog SQL [%d]:`" LWPDL "`.", pos, pos, wbuf); + ret = EsSQLFreeStmt(stmt, ESODBC_SQL_CLOSE); assert(SQL_SUCCEEDED(ret)); /* can't return error */ ret = attach_sql(stmt, wbuf, pos); diff --git a/driver/catalogue.h b/driver/catalogue.h index 03e07a0a..7d721223 100644 --- a/driver/catalogue.h +++ b/driver/catalogue.h @@ -19,6 +19,12 @@ #define __CATALOGUE_H__ #include "error.h" +#include "handles.h" + + +SQLSMALLINT copy_current_catalog(esodbc_dbc_st *dbc, SQLWCHAR *dest, + SQLSMALLINT room); + SQLRETURN EsSQLTablesW( SQLHSTMT StatementHandle, diff --git a/driver/connect.c b/driver/connect.c index ea9328b7..949fff91 100644 --- a/driver/connect.c +++ b/driver/connect.c @@ -22,6 +22,7 @@ #include "connect.h" #include "queries.h" +#include "catalogue.h" #include "log.h" #include "info.h" #include "util.h" @@ -2095,26 +2096,6 @@ SQLRETURN EsSQLSetConnectAttrW( return SQL_SUCCESS; } -/* writes into 'dest', of size 'room', the current catalog of 'dbc'. - * returns negative on error, or the char count written otherwise */ -static SQLSMALLINT get_current_catalog(esodbc_dbc_st *dbc, SQLWCHAR *dest, - SQLSMALLINT room) -{ - SQLSMALLINT used; - SQLWCHAR *catalog = MK_WPTR("my_current_catalog"); // FIXME - - // - // TODO: use the new SYS CATALOGS query - // - - DBGH(dbc, "current catalog: `" LWPD "`.", catalog); - if (! SQL_SUCCEEDED(write_wptr(&dbc->hdr.diag, dest, catalog, room, - &used))) { - return -1; - } - return used; -} - SQLRETURN EsSQLGetConnectAttrW( SQLHDBC ConnectionHandle, SQLINTEGER Attribute, @@ -2142,7 +2123,7 @@ SQLRETURN EsSQLGetConnectAttrW( if (! dbc->es_types) { ERRH(dbc, "no connection active."); RET_HDIAGS(dbc, SQL_STATE_08003); - } else if ((used = get_current_catalog(dbc, (SQLWCHAR *)ValuePtr, + } else if ((used = copy_current_catalog(dbc, (SQLWCHAR *)ValuePtr, (SQLSMALLINT)BufferLength)) < 0) { ERRH(dbc, "failed to get current catalog."); RET_STATE(dbc->hdr.diag.state); diff --git a/driver/defs.h b/driver/defs.h index 416cfb9f..c45fda9d 100644 --- a/driver/defs.h +++ b/driver/defs.h @@ -59,7 +59,8 @@ #define ESODBC_MAX_CONCURRENT_ACTIVITIES 16 /* maximum identifer length */ /* TODO: review@alpha */ -#define ESODBC_MAX_IDENTIFIER_LEN 128 +/* match 'keyword' ES type lenght */ +#define ESODBC_MAX_IDENTIFIER_LEN 256 /* diff --git a/driver/util.c b/driver/util.c index 8c9f2f0d..07a107da 100644 --- a/driver/util.c +++ b/driver/util.c @@ -103,7 +103,7 @@ int wmemncasecmp(const SQLWCHAR *a, const SQLWCHAR *b, size_t len) if (diff) break; } - //DBG("`" LWPDL "` vs `" LWPDL "` => %d (len=%zd, i=%d).", + //DBG("`" LWPDL "` vs `" LWPDL "` => %d (len=%zd, i=%d).", // len, a, len, b, diff, len, i); return diff; } @@ -124,6 +124,17 @@ int wszmemcmp(const SQLWCHAR *a, const SQLWCHAR *b, long count) return *a - *b; } +const SQLWCHAR* wcsnstr(const SQLWCHAR *hay, size_t len, SQLWCHAR needle) +{ + size_t i; + for (i = 0; i < len; i ++) { + if (hay[i] == needle) { + return hay + i; + } + } + return NULL; +} + /* retuns the length of a buffer to hold the escaped variant of the unescaped * given json object */ static inline size_t json_escaped_len(const char *json, size_t len) diff --git a/driver/util.h b/driver/util.h index f96f2630..5fa0870e 100644 --- a/driver/util.h +++ b/driver/util.h @@ -85,7 +85,7 @@ int wmemncasecmp(const SQLWCHAR *a, const SQLWCHAR *b, size_t len); * either of them or until 'count' characters are evaluated. If 'count' * parameter is negative, it is ignored. * - * This is useful in comparing SQL strings which the API allows to be passed + * This is useful in comparing SQL strings which the API allows to be passed * either as 0-terminated or not (SQL_NTS). * The function does a single pass (no length evaluation of the strings). * wmemcmp() might read over the boundary of one of the objects, if the @@ -93,6 +93,11 @@ int wmemncasecmp(const SQLWCHAR *a, const SQLWCHAR *b, size_t len); */ int wszmemcmp(const SQLWCHAR *a, const SQLWCHAR *b, long count); +/* + * wcsstr() variant for non-NTS. + */ +const SQLWCHAR* wcsnstr(const SQLWCHAR *hay, size_t len, SQLWCHAR needle); + typedef struct wstr { SQLWCHAR *str; size_t cnt; @@ -119,7 +124,7 @@ 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 @@ -155,7 +160,7 @@ 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 @@ -197,8 +202,8 @@ size_t json_escape(const char *jin, size_t inlen, char *jout, size_t outlen); #define PFWP_LDESC "%.*S" /* silly M$ */ /* wprintf char pointer descriptor */ -#define WPFCP_DESC L"%s -#define WPFCP_LDESC L"%.*s +#define WPFCP_DESC L"%s" +#define WPFCP_LDESC L"%.*s" /* printf char pointer descriptor */ #define PFCP_DESC "%s" #define PFCP_LDESC "%.*s"