driver/catalogue.c (827 lines of code) (raw):
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
#include <stdio.h>
#include <string.h>
#include <wchar.h>
#include "ujdecode.h"
#include "catalogue.h"
#include "connect.h"
#include "log.h"
#include "handles.h"
#include "info.h"
#include "queries.h"
/* 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 \
" ESCAPE '" ESODBC_PATTERN_ESCAPE "'"
#define SQL_TABLES_TAB \
" LIKE " ESODBC_STRING_DELIM WPFWP_LDESC ESODBC_STRING_DELIM \
" ESCAPE '" ESODBC_PATTERN_ESCAPE "'"
#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 \
" ESCAPE '" ESODBC_PATTERN_ESCAPE "'" \
" LIKE " ESODBC_STRING_DELIM WPFWP_LDESC ESODBC_STRING_DELIM \
" ESCAPE '" ESODBC_PATTERN_ESCAPE "'"
#define SQL_COL_CAT \
" CATALOG " ESODBC_STRING_DELIM WPFWP_LDESC ESODBC_STRING_DELIM
static SQLRETURN fake_answer(SQLHSTMT hstmt, cstr_st *answer)
{
cstr_st fake = *answer;
if (! (fake.str = strdup(answer->str))) {
ERRNH(hstmt, "OOM with %zu.", fake.cnt);
RET_HDIAGS(hstmt, SQL_STATE_HY001);
}
return attach_answer(STMH(hstmt), &fake, /*is JSON*/TRUE);
}
SQLRETURN EsSQLStatisticsW(
SQLHSTMT hstmt,
_In_reads_opt_(cchCatalogName) SQLWCHAR *szCatalogName,
SQLSMALLINT cchCatalogName,
_In_reads_opt_(cchSchemaName) SQLWCHAR *szSchemaName,
SQLSMALLINT cchSchemaName,
_In_reads_opt_(cchTableName) SQLWCHAR *szTableName,
SQLSMALLINT cchTableName,
SQLUSMALLINT fUnique,
SQLUSMALLINT fAccuracy)
{
cstr_st statistics = CSTR_INIT("{"
"\"columns\":["
"{\"name\":\"TABLE_CAT\", \"type\":\"TEXT\"},"
"{\"name\":\"TABLE_SCHEM\", \"type\":\"TEXT\"},"
"{\"name\":\"TABLE_NAME\", \"type\":\"TEXT\"},"
"{\"name\":\"NON_UNIQUE\", \"type\":\"SHORT\"},"
"{\"name\":\"INDEX_QUALIFIER\", \"type\":\"TEXT\"},"
"{\"name\":\"INDEX_NAME\", \"type\":\"TEXT\"},"
"{\"name\":\"TYPE\", \"type\":\"SHORT\"},"
"{\"name\":\"ORDINAL_POSITION\", \"type\":\"SHORT\"},"
"{\"name\":\"COLUMN_NAME \", \"type\":\"TEXT\"},"
"{\"name\":\"ASC_OR_DESC\", \"type\":\"BYTE\"},"
"{\"name\":\"CARDINALITY\", \"type\":\"INTEGER\"},"
"{\"name\":\"PAGES\", \"type\":\"INTEGER\"},"
"{\"name\":\"FILTER_CONDITION\", \"type\":\"TEXT\"}"
"],"
"\"rows\":[]"
"}");
INFOH(hstmt, "no statistics available.");
return fake_answer(hstmt, &statistics);
}
void free_current_catalog(esodbc_dbc_st *dbc)
{
if (dbc->catalog.w.str) {
free(dbc->catalog.w.str);
dbc->catalog.w.str = NULL;
dbc->catalog.w.cnt = 0;
}
if (dbc->catalog.c.str) {
free(dbc->catalog.c.str);
dbc->catalog.c.str = NULL;
dbc->catalog.c.cnt = 0;
}
}
SQLRETURN set_current_catalog(esodbc_dbc_st *dbc, wstr_st *catalog)
{
if (dbc->catalog.w.cnt) {
DBGH(dbc, "catalog previously set to `" LWPDL "`.",
LWSTR(&dbc->catalog.w));
if (! EQ_WSTR(&dbc->catalog.w, catalog)) {
free_current_catalog(dbc);
}
}
if (! catalog->cnt || ! catalog->str) {
WARNH(dbc, "catalog name set to empty value.");
return SQL_SUCCESS;
}
dbc->catalog.w.str = malloc((catalog->cnt + 1) * sizeof(SQLWCHAR));
if (! dbc->catalog.w.str) {
ERRNH(dbc, "OOM for %zu wchars.", catalog->cnt + 1);
RET_HDIAGS(dbc, SQL_STATE_HY001);
}
wmemcpy(dbc->catalog.w.str, catalog->str, catalog->cnt);
dbc->catalog.w.str[catalog->cnt] = L'\0';
dbc->catalog.w.cnt = catalog->cnt;
if (! wstr_to_utf8(catalog, &dbc->catalog.c)) {
goto err;
}
INFOH(dbc, "current catalog name: `" LWPDL "`.", LWSTR(&dbc->catalog.w));
return SQL_SUCCESS;
err:
free_current_catalog(dbc);
RET_HDIAG(dbc, SQL_STATE_HY000, "Saving current catalog failed", 0);
}
/* writes into 'dest', of size 'room', the current requested attr. of 'dbc'.
* returns negative on error, or the char count written otherwise */
SQLSMALLINT fetch_server_attr(esodbc_dbc_st *dbc, SQLINTEGER attr_id,
SQLWCHAR *dest, SQLSMALLINT room)
{
esodbc_stmt_st *stmt = NULL;
SQLSMALLINT used = -1; /*failure*/
SQLLEN row_cnt;
SQLLEN ind_len = SQL_NULL_DATA;
SQLWCHAR *buff;
static const size_t buff_cnt = ESODBC_MAX_IDENTIFIER_LEN + /*\0*/1;
wstr_st attr_val;
wstr_st attr_sql;
buff = malloc(sizeof(*buff) * buff_cnt);
if (! buff) {
ERRH(dbc, "OOM: %zu wchar_t.", buff_cnt);
return -1;
}
if (! SQL_SUCCEEDED(EsSQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt))) {
ERRH(dbc, "failed to alloc a statement handle.");
goto end;
}
assert(stmt);
switch (attr_id) {
case SQL_ATTR_CURRENT_CATALOG:
attr_sql = MK_WSTR("SELECT database()");
break;
case SQL_USER_NAME:
attr_sql = MK_WSTR("SELECT user()");
break;
default:
BUGH(dbc, "unexpected attribute ID: %ld.", attr_id);
goto end;
}
if (! SQL_SUCCEEDED(attach_sql(stmt, attr_sql.str, attr_sql.cnt))) {
ERRH(dbc, "failed to attach query to statement.");
goto end;
}
if (! SQL_SUCCEEDED(EsSQLExecute(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, "received no value for attribute %ld.", attr_id);
attr_val = MK_WSTR(""); /* empty string, it's not quite an error */
} else {
if (1 < row_cnt) {
WARNH(dbc, "more than one value (%lld) available for "
"attribute %ld; picking first.", (int64_t)row_cnt, attr_id);
}
if (! SQL_SUCCEEDED(EsSQLBindCol(stmt, /*col#*/1, SQL_C_WCHAR, buff,
sizeof(*buff) * (buff_cnt - 1), &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 value received for attribute %ld.", attr_id);
assert(ind_len == SQL_NULL_DATA);
attr_val = MK_WSTR("");
} else {
attr_val.str = buff;
attr_val.cnt = ind_len / sizeof(*buff);
/* 0-term room left out when binding */
buff[attr_val.cnt] = L'\0'; /* write_wstr() expects the 0-term */
}
}
DBGH(dbc, "attribute %ld value: `" LWPDL "`.", attr_id, LWSTR(&attr_val));
if (! SQL_SUCCEEDED(write_wstr(dbc, dest, &attr_val, room, &used))) {
ERRH(dbc, "failed to copy value: `" LWPDL "`.", LWSTR(&attr_val));
used = -1; /* write_wstr() can change pointer, and still fail */
}
end:
if (stmt) {
/* 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!");
}
}
free(buff);
return used;
}
/*
* Quote the tokens in a string: "a, b,,c" -> "'a','b',,'c'".
* No string sanity done (garbage in, garbage out).
*/
static 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];
}
if (copying) {
*pos ++ = L'\''; /* end last token */
} else if (dest < pos && pos[-1] == L',') {
/* trim last char, if it's a comma: LibreOffice (6.1.0.3) sends the
* table type string `VIEW,TABLE,%,` having the last `,` propagated to
* EsSQL, which rejects the query */
pos --;
}
/* 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,
SQLSMALLINT NameLength1,
_In_reads_opt_(NameLength2) SQLWCHAR *SchemaName,
SQLSMALLINT NameLength2,
_In_reads_opt_(NameLength3) SQLWCHAR *TableName,
SQLSMALLINT NameLength3,
_In_reads_opt_(NameLength4) SQLWCHAR *TableType,
SQLSMALLINT NameLength4)
{
esodbc_stmt_st *stmt = STMH(StatementHandle);
esodbc_dbc_st *dbc = HDRH(stmt)->dbc;
SQLRETURN ret = SQL_ERROR;
/* print buffer size */
static const size_t PBUF_CNT = 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 */;
/* work buffer sizing:
* - type: 2x: "a,b,c" -> "'a','b','c'" : each "x," => "'x',"
* - escaping: 2x: x -> \x */
static const size_t WBUF_CNT = 2 * ESODBC_MAX_IDENTIFIER_LEN;
SQLWCHAR *pbuf; /* print buffer for the final statement */
SQLWCHAR *wbuf; /* work buffer: for table type and escaping */
size_t cnt, pos;
wstr_st esrc, edst; /* escaping src, dst */
/* the buffer size could actually be more accurately calculated (i.e. to a
* smaller size than the max) */
pbuf = malloc((PBUF_CNT + WBUF_CNT) * sizeof(SQLWCHAR));
if (! pbuf) {
ERRNH(stmt, "OOM: %zu wchar_t.", PBUF_CNT + WBUF_CNT);
RET_HDIAGS(stmt, SQL_STATE_HY001);
} else {
wbuf = &pbuf[PBUF_CNT];
}
edst.str = wbuf;
pos = sizeof(SQL_TABLES) - 1;
wmemcpy(pbuf, MK_WPTR(SQL_TABLES), pos);
if (CatalogName) {
esrc.str = CatalogName;
if (NameLength1 == SQL_NTS) {
esrc.cnt = wcslen(esrc.str);
} else {
esrc.cnt = NameLength1;
}
if (ESODBC_MAX_IDENTIFIER_LEN < esrc.cnt) {
ERRH(stmt, "catalog identifier name '" LWPDL "' too long "
"(%zd. max=%d).", LWSTR(&esrc), esrc.cnt,
ESODBC_MAX_IDENTIFIER_LEN);
SET_HDIAG(stmt, SQL_STATE_HY090, "catalog name too long", 0);
goto end;
}
if (dbc->auto_esc_pva || stmt->metadata_id) {
metadata_id_escape(&esrc, &edst, (BOOL)stmt->metadata_id);
}
cnt = swprintf(pbuf + pos, PBUF_CNT - pos, SQL_TABLES_CAT,
(int)edst.cnt, edst.str);
if (cnt <= 0) {
ERRH(stmt, "failed to print 'catalog' for tables catalog SQL.");
SET_HDIAG(stmt, SQL_STATE_HY000, "internal printing error", 0);
goto end;
} else {
pos += cnt;
}
}
if (SchemaName) {
esrc.str = SchemaName;
if (NameLength2 == SQL_NTS) {
esrc.cnt = wcslen(esrc.str);
} else {
esrc.cnt = NameLength2;
}
if (ESODBC_MAX_IDENTIFIER_LEN < esrc.cnt) {
ERRH(stmt, "schema identifier name '" LWPDL "' too long "
"(%zd. max=%d).", LWSTR(&esrc), esrc.cnt,
ESODBC_MAX_IDENTIFIER_LEN);
SET_HDIAG(stmt, SQL_STATE_HY090, "schema name too long", 0);
goto end;
}
/* TODO: server support needed for sch. name filtering */
if (wszmemcmp(esrc.str, MK_WPTR(SQL_ALL_SCHEMAS), (long)esrc.cnt)) {
ERRH(stmt, "filtering by schemas is not supported.");
SET_HDIAG(stmt, SQL_STATE_IM001, "schema filtering not supported",
0);
goto end;
}
}
if (TableName) {
esrc.str = TableName;
if (NameLength3 == SQL_NTS) {
esrc.cnt = wcslen(esrc.str);
} else {
esrc.cnt = NameLength3;
}
if (ESODBC_MAX_IDENTIFIER_LEN < esrc.cnt) {
ERRH(stmt, "table identifier name '" LWPDL "' too long "
"(%zd. max=%d).", LWSTR(&esrc), esrc.cnt,
ESODBC_MAX_IDENTIFIER_LEN);
SET_HDIAG(stmt, SQL_STATE_HY090, "table name too long", 0);
goto end;
}
if (dbc->auto_esc_pva || stmt->metadata_id) {
metadata_id_escape(&esrc, &edst, (BOOL)stmt->metadata_id);
}
cnt = swprintf(pbuf + pos, PBUF_CNT - pos, SQL_TABLES_TAB,
(int)edst.cnt, edst.str);
if (cnt <= 0) {
ERRH(stmt, "failed to print 'table' for tables catalog SQL.");
SET_HDIAG(stmt, SQL_STATE_HY000, "internal printing error", 0);
goto end;
} else {
pos += cnt;
}
}
if (TableType) {
esrc.str = TableType;
if (NameLength4 == SQL_NTS) {
esrc.cnt = wcslen(esrc.str);
} else {
esrc.cnt = NameLength4;
}
if (ESODBC_MAX_IDENTIFIER_LEN < esrc.cnt) {
ERRH(stmt, "type identifier name '" LWPDL "' too long "
"(%zd. max=%d).", LWSTR(&esrc), esrc.cnt,
ESODBC_MAX_IDENTIFIER_LEN);
SET_HDIAG(stmt, SQL_STATE_HY090, "type name too long", 0);
goto end;
}
/* Only print TYPE if non-empty. This is incorrect, by the book,
* but there's little use to specifying an empty string as the type
* (vs NULL), so this should hopefully be safe. (ES/GH#40775) */
if (0 < esrc.cnt) {
/* Here, "each value can be enclosed in single quotation marks (')
* or unquoted" => quote if not quoted (see GH#30398). */
if (! wcsnstr(esrc.str, esrc.cnt, L'\'')) {
edst.cnt = quote_tokens(esrc.str, esrc.cnt, edst.str);
} else {
edst = esrc;
}
cnt = swprintf(pbuf + pos, PBUF_CNT - pos, SQL_TABLES_TYP,
(int)edst.cnt, edst.str);
if (cnt <= 0) {
ERRH(stmt, "failed to print 'type' for tables catalog SQL.");
SET_HDIAG(stmt, SQL_STATE_HY000, "internal printing error", 0);
goto end;
} else {
pos += cnt;
}
}
}
DBGH(stmt, "tables catalog SQL [%zu]:`" LWPDL "`.", pos, (int)pos, pbuf);
ret = EsSQLExecDirectW(stmt, pbuf, (SQLINTEGER)pos);
end:
free(pbuf);
return ret;
}
static inline BOOL col_of_type(SQLSMALLINT idx, SQLSMALLINT type)
{
switch (idx) {
case SQLCOLS_IDX_TABLE_CAT:
case SQLCOLS_IDX_TABLE_SCHEM:
case SQLCOLS_IDX_TABLE_NAME:
case SQLCOLS_IDX_COLUMN_NAME:
case SQLCOLS_IDX_TYPE_NAME:
case SQLCOLS_IDX_REMARKS:
case SQLCOLS_IDX_COLUMN_DEF:
case SQLCOLS_IDX_IS_NULLABLE:
return type == ES_KEYWORD_TO_SQL;
case SQLCOLS_IDX_DATA_TYPE:
case SQLCOLS_IDX_DECIMAL_DIGITS:
case SQLCOLS_IDX_NUM_PREC_RADIX:
case SQLCOLS_IDX_NULLABLE:
case SQLCOLS_IDX_SQL_DATA_TYPE:
case SQLCOLS_IDX_SQL_DATETIME_SUB:
return type == SQL_SMALLINT;
case SQLCOLS_IDX_COLUMN_SIZE:
case SQLCOLS_IDX_BUFFER_LENGTH:
case SQLCOLS_IDX_CHAR_OCTET_LENGTH:
case SQLCOLS_IDX_ORDINAL_POSITION:
return type == SQL_INTEGER;
default:
BUG("unexpected index %hd.", idx);
}
return false;
}
static SQLRETURN safe_copy(esodbc_stmt_st *stmt, wstr_st *dest,
size_t *pos, SQLWCHAR *str, size_t cnt)
{
SQLWCHAR *r;
DBGH(stmt, "writing @pos=%zu / max=%zu string [%zu] `" LWPDL "`.", *pos,
dest->cnt, cnt, cnt, str ? str : L"<null>");
/* is there enough room to copy the new string? */
assert(cnt);
if (dest->cnt <= *pos + cnt) {
dest->cnt = dest->cnt ? dest->cnt : ESODBC_BODY_BUF_START_SIZE;
while (dest->cnt <= *pos + cnt) {
dest->cnt *= 3;
}
if (! (r = realloc(dest->str, dest->cnt * sizeof(SQLWCHAR)))) {
ERRNH(stmt, "OOM: %zu w-chars.", dest->cnt);
RET_HDIAGS(stmt, SQL_STATE_HY001);
} else {
dest->str = r;
}
}
if (str) {
wmemcpy(dest->str + *pos, str, cnt);
*pos += cnt;
DBGH(stmt, "crr buffer: [%zu] `" LWPDL "`.", *pos, *pos, dest->str);
}
return SQL_SUCCESS;
}
static SQLRETURN copy_one_cell(esodbc_stmt_st *stmt, wstr_st *dest,
size_t *pos, long row_cnt, SQLSMALLINT col_idx)
{
SQLRETURN ret;
BOOL is_str;
SQLLEN len_ind;
const static wstr_st null = WSTR_INIT("null"), qmark = WSTR_INIT("\"");
/* fetch data's length (always converted to w-string) */
ret = EsSQLGetData(stmt, col_idx, SQL_C_WCHAR, dest->str, 0, &len_ind);
if (! SQL_SUCCEEDED(ret)) {
ERRH(stmt, "failed to get data length for cell@[%ld, %hd].",
row_cnt, col_idx);
return ret;
}
/* null data */
if (len_ind == SQL_NULL_DATA) {
return safe_copy(stmt, dest, pos, null.str, null.cnt);
} else if (len_ind < 0) {
ERRH(stmt, "unexpected no-total indicator for cell@[%ld, %hd].",
row_cnt, col_idx);
RET_HDIAGS(stmt, SQL_STATE_HY000);
} /* else: indicator >= 0 */
is_str = col_of_type(col_idx, ES_KEYWORD_TO_SQL);
/* make sure there's enough space to write string */
ret = safe_copy(stmt, dest, pos, NULL,
len_ind/sizeof(SQLWCHAR) + /*2x'"'*/2 * is_str + /*\0*/1);
if (! SQL_SUCCEEDED(ret)) {
return ret;
}
/* write opening quote */
if (is_str) {
ret = safe_copy(stmt, dest, pos, qmark.str, qmark.cnt);
assert(SQL_SUCCEEDED(ret));
}
ret = EsSQLGetData(stmt, col_idx, SQL_C_WCHAR, dest->str + *pos,
(len_ind + /*\0*/1) * sizeof(SQLWCHAR), &len_ind);
if (! SQL_SUCCEEDED(ret)) {
ERRH(stmt, "failed to get data for cell@[%ld, %hd].",
row_cnt, col_idx);
return ret;
} else {
assert(0 < len_ind);
*pos += len_ind / sizeof(SQLWCHAR);
}
/* write closing quote */
if (is_str) {
ret = safe_copy(stmt, dest, pos, qmark.str, qmark.cnt);
assert(SQL_SUCCEEDED(ret));
}
return SQL_SUCCESS;
}
/* in received result set update those varchar columns (DATA_TYPE ==
* SQL_VARCHAR) whose COLUMN_SIZE and BUFFER_LENGTH columns exceed the set
* limit */
SQLRETURN TEST_API update_varchar_defs(esodbc_stmt_st *stmt)
{
const static wstr_st sqlcols_resp_head = WSTR_INIT("{"
"\"columns\":["
"{\"name\":\"TABLE_CAT\", \"type\":\"keyword\"},"
"{\"name\":\"TABLE_SCHEM\", \"type\":\"keyword\"},"
"{\"name\":\"TABLE_NAME\", \"type\":\"keyword\"},"
"{\"name\":\"COLUMN_NAME\", \"type\":\"keyword\"},"
"{\"name\":\"DATA_TYPE\", \"type\":\"short\"},"
"{\"name\":\"TYPE_NAME\", \"type\":\"keyword\"},"
"{\"name\":\"COLUMN_SIZE\", \"type\":\"integer\"},"
"{\"name\":\"BUFFER_LENGTH\", \"type\":\"integer\"},"
"{\"name\":\"DECIMAL_DIGITS\", \"type\":\"short\"},"
"{\"name\":\"NUM_PREC_RADIX\", \"type\":\"short\"},"
"{\"name\":\"NULLABLE\", \"type\":\"short\"},"
"{\"name\":\"REMARKS\", \"type\":\"keyword\"},"
"{\"name\":\"COLUMN_DEF\", \"type\":\"keyword\"},"
"{\"name\":\"SQL_DATA_TYPE\", \"type\":\"short\"},"
"{\"name\":\"SQL_DATETIME_SUB\", \"type\":\"short\"},"
"{\"name\":\"CHAR_OCTET_LENGTH\", \"type\":\"integer\"},"
"{\"name\":\"ORDINAL_POSITION\", \"type\":\"integer\"},"
"{\"name\":\"IS_NULLABLE\", \"type\":\"keyword\"}"
"],"
"\"rows\":["
);
const static wstr_st sqlcols_resp_tail = WSTR_INIT("]}");
const static wstr_st first_brkt = WSTR_INIT("[");
const static wstr_st subseq_brkt = WSTR_INIT(",[");
const static wstr_st close_brkt = WSTR_INIT("]");
const static wstr_st comma = WSTR_INIT(", ");
const wstr_st *brkt;
SQLRETURN ret;
SQLSMALLINT col_cnt, i;
long row_cnt;
wstr_st dest = {0};
size_t pos;
SQLULEN max_length;
cstr_st u8mb;
wstr_st *lim = &HDRH(stmt)->dbc->varchar_limit_str;
/* save and reset SQL_ATTR_MAX_LENGTH attribute, it'll interfere with
* reading length of avail data with SQLGetData() otherwise. */
max_length = stmt->max_length;
stmt->max_length = 0;
/* check that we have as many columns as members in target row struct */
ret = EsSQLNumResultCols(stmt, &col_cnt);
if (! SQL_SUCCEEDED(ret)) {
ERRH(stmt, "failed to get result columns count.");
goto end;
} else if (col_cnt < SQLCOLS_IDX_MAX) {
ERRH(stmt, "Elasticsearch returned an unexpected number of columns "
"(%hd vs min expected %d).", col_cnt, SQLCOLS_IDX_MAX);
goto end;
} else {
DBGH(stmt, "Elasticsearch types columns count: %hd.", col_cnt);
}
pos = 0;
ret = safe_copy(stmt, &dest, &pos, sqlcols_resp_head.str,
sqlcols_resp_head.cnt);
if (! SQL_SUCCEEDED(ret)) {
goto end;
}
row_cnt = 1;
while (SQL_SUCCEEDED(ret = EsSQLFetch(stmt))) {
brkt = 1 < row_cnt ? &subseq_brkt : &first_brkt;
ret = safe_copy(stmt, &dest, &pos, brkt->str, brkt->cnt);
if (! SQL_SUCCEEDED(ret)) {
goto end;
}
for (i = 0; i < SQLCOLS_IDX_MAX; i ++) {
if (i) {
ret = safe_copy(stmt, &dest, &pos, comma.str, comma.cnt);
if (! SQL_SUCCEEDED(ret)) {
goto end;
}
}
switch (i + 1) {
case SQLCOLS_IDX_COLUMN_SIZE:
case SQLCOLS_IDX_BUFFER_LENGTH:
case SQLCOLS_IDX_CHAR_OCTET_LENGTH:
ret = safe_copy(stmt, &dest, &pos, lim->str, lim->cnt);
break;
default:
ret = copy_one_cell(stmt, &dest, &pos, row_cnt, i + 1);
}
if (! SQL_SUCCEEDED(ret)) {
goto end;
}
}
ret = safe_copy(stmt, &dest, &pos, close_brkt.str,
close_brkt.cnt);
if (! SQL_SUCCEEDED(ret)) {
goto end;
}
row_cnt ++;
}
ret = safe_copy(stmt, &dest, &pos, sqlcols_resp_tail.str,
sqlcols_resp_tail.cnt);
if (! SQL_SUCCEEDED(ret)) {
goto end;
}
/* answer succesfully rewritten */
dest.cnt = pos;
DBGH(stmt, "table definition: [%zu] `" LWPDL "`.", dest.cnt, LWSTR(&dest));
if (! wstr_to_utf8(&dest, &u8mb)) {
ERRH(stmt, "UTF16 to UTF8 conversion failed for: [%zu] `" LWPDL "`.",
dest.cnt, LWSTR(&dest));
ret = SET_HDIAG(stmt, SQL_STATE_HY000, "Strings conversion error", 0);
} else {
/* unbind previously received result set */
ret = EsSQLFreeStmt(stmt, SQL_CLOSE);
if (! SQL_SUCCEEDED(ret)) {
goto end;
}
ret = attach_answer(stmt, &u8mb, /*is json*/TRUE);
}
end:
if (dest.str) {
free(dest.str);
dest.cnt = 0;
}
/* reinstate any saved SQL_ATTR_MAX_LENGTH value */
stmt->max_length = max_length;
return ret;
}
SQLRETURN EsSQLColumnsW
(
SQLHSTMT hstmt,
_In_reads_opt_(cchCatalogName) SQLWCHAR *szCatalogName,
SQLSMALLINT cchCatalogName,
_In_reads_opt_(cchSchemaName) SQLWCHAR *szSchemaName,
SQLSMALLINT cchSchemaName,
_In_reads_opt_(cchTableName) SQLWCHAR *szTableName,
SQLSMALLINT cchTableName,
_In_reads_opt_(cchColumnName) SQLWCHAR *szColumnName,
SQLSMALLINT cchColumnName
)
{
esodbc_stmt_st *stmt = STMH(hstmt);
esodbc_dbc_st *dbc = HDRH(stmt)->dbc;
SQLRETURN ret = SQL_ERROR;
SQLWCHAR *pbuf; /* print buffer */
size_t arg_cnt, cnt;
wstr_st src_tab, src_col;
wstr_st catalog, schema, dst_tab, dst_col;
if (szCatalogName) {
catalog.str = szCatalogName;
if (cchCatalogName == SQL_NTS) {
catalog.cnt = wcslen(catalog.str);
} else {
catalog.cnt = cchCatalogName;
}
if (ESODBC_MAX_IDENTIFIER_LEN < catalog.cnt) {
ERRH(stmt, "catalog identifier name '" LWPDL "' too long "
"(%d. max=%d).", LWSTR(&catalog), catalog.cnt,
ESODBC_MAX_IDENTIFIER_LEN);
RET_HDIAG(stmt, SQL_STATE_HY090, "catalog name too long", 0);
}
} else {
catalog.str = NULL;
catalog.cnt = 0;
}
if (szSchemaName) {
schema.str = szSchemaName;
if (cchSchemaName == SQL_NTS) {
schema.cnt = wcslen(schema.str);
} else {
schema.cnt = cchSchemaName;
}
if (ESODBC_MAX_IDENTIFIER_LEN < schema.cnt) {
ERRH(stmt, "schema identifier name '" LWPDL "' too long "
"(%d. max=%d).", LWSTR(&schema), schema.cnt,
ESODBC_MAX_IDENTIFIER_LEN);
RET_HDIAG(stmt, SQL_STATE_HY090, "schema name too long", 0);
}
} else {
schema.str = MK_WPTR(SQL_ALL_SCHEMAS);
schema.cnt = sizeof(SQL_ALL_SCHEMAS) - /*0-term*/1;
}
/* TODO: server support needed for schema name filtering */
if (schema.cnt && wszmemcmp(schema.str, MK_WPTR(SQL_ALL_SCHEMAS),
(long)schema.cnt)) {
ERRH(stmt, "filtering by schemas is not supported.");
RET_HDIAG(stmt, SQL_STATE_IM001, "schema filtering not supported", 0);
}
if (szTableName) {
src_tab.str = szTableName;
if (cchTableName == SQL_NTS) {
src_tab.cnt = wcslen(src_tab.str);
} else {
src_tab.cnt = cchTableName;
}
if (ESODBC_MAX_IDENTIFIER_LEN < src_tab.cnt) {
ERRH(stmt, "table identifier name '" LWPDL "' too long "
"(%d. max=%d).", LWSTR(&src_tab), src_tab.cnt,
ESODBC_MAX_IDENTIFIER_LEN);
RET_HDIAG(stmt, SQL_STATE_HY090, "table name too long", 0);
}
} else {
src_tab.str = MK_WPTR(ESODBC_ALL_TABLES);
src_tab.cnt = sizeof(ESODBC_ALL_TABLES) - /*0-term*/1;
}
if (szColumnName) {
src_col.str = szColumnName;
if (cchColumnName == SQL_NTS) {
src_col.cnt = wcslen(src_col.str);
} else {
src_col.cnt = cchColumnName;
}
if (ESODBC_MAX_IDENTIFIER_LEN < src_col.cnt) {
ERRH(stmt, "column identifier name '" LWPDL "' too long "
"(%d. max=%d).", LWSTR(&src_col), src_col.cnt,
ESODBC_MAX_IDENTIFIER_LEN);
RET_HDIAG(stmt, SQL_STATE_HY090, "column name too long", 0);
}
} else {
src_col.str = MK_WPTR(ESODBC_ALL_COLUMNS);
src_col.cnt = sizeof(ESODBC_ALL_COLUMNS) - /*0-term*/1;
}
/* determine a maximum buff size for any of the provided value args */
arg_cnt = catalog.cnt;
arg_cnt = src_tab.cnt <= arg_cnt ? arg_cnt : src_tab.cnt;
arg_cnt = src_col.cnt <= arg_cnt ? arg_cnt : src_col.cnt;
arg_cnt *= 2; /* escaping can up to double the buffer */
/* size of chunk to allocate for the print buffer, plus each escape buff */
cnt = sizeof(SQL_COLUMNS(SQL_COL_CAT));
/* 5: (cat + tab + col) for printing + (tab + col) for escaping */
cnt += 5 * arg_cnt;
/* allocate a chunk for the printing buffer plus for each escape buffer */
pbuf = malloc(cnt * sizeof(*pbuf));
if (! pbuf) {
ERRNH(stmt, "OOM: %zu wchar_t.", cnt);
RET_HDIAGS(stmt, SQL_STATE_HY001);
} else {
dst_tab.str = &pbuf[sizeof(SQL_COLUMNS(SQL_COL_CAT)) + 3 * arg_cnt];
dst_col.str = &pbuf[sizeof(SQL_COLUMNS(SQL_COL_CAT)) + 4 * arg_cnt];
}
/* catalog argument is never a patern value -> no escaping */
/* escape table and column args */
if (dbc->auto_esc_pva || stmt->metadata_id) {
metadata_id_escape(&src_tab, &dst_tab, (BOOL)stmt->metadata_id);
metadata_id_escape(&src_col, &dst_col, (BOOL)stmt->metadata_id);
}
/* print query to send to server */
if (catalog.str) {
cnt = swprintf(pbuf, cnt, SQL_COLUMNS(SQL_COL_CAT),
(int)catalog.cnt, catalog.str,
(int)dst_tab.cnt, dst_tab.str,
(int)dst_col.cnt, dst_col.str);
} else {
cnt = swprintf(pbuf, cnt, SQL_COLUMNS(),
(int)dst_tab.cnt, dst_tab.str,
(int)dst_col.cnt, dst_col.str);
}
if (cnt <= 0) {
ERRH(stmt, "failed to print 'columns' catalog SQL.");
SET_HDIAG(stmt, SQL_STATE_HY000, "internal printing error", 0);
goto end;
}
ret = EsSQLExecDirectW(stmt, pbuf, (SQLINTEGER)cnt);
if (SQL_SUCCEEDED(ret) && HDRH(stmt)->dbc->varchar_limit) {
ret = update_varchar_defs(stmt);
}
end:
free(pbuf);
return ret;
}
SQLRETURN EsSQLSpecialColumnsW
(
SQLHSTMT hstmt,
SQLUSMALLINT fColType,
_In_reads_opt_(cchCatalogName) SQLWCHAR *szCatalogName,
SQLSMALLINT cchCatalogName,
_In_reads_opt_(cchSchemaName) SQLWCHAR *szSchemaName,
SQLSMALLINT cchSchemaName,
_In_reads_opt_(cchTableName) SQLWCHAR *szTableName,
SQLSMALLINT cchTableName,
SQLUSMALLINT fScope,
SQLUSMALLINT fNullable
)
{
cstr_st special_cols = CSTR_INIT("{"
"\"columns\":["
"{\"name\":\"SCOPE\", \"type\":\"SHORT\"},"
"{\"name\":\"COLUMN_NAME\", \"type\":\"TEXT\"},"
"{\"name\":\"DATA_TYPE\", \"type\":\"SHORT\"},"
"{\"name\":\"TYPE_NAME\", \"type\":\"TEXT\"},"
"{\"name\":\"COLUMN_SIZE\", \"type\":\"INTEGER\"},"
"{\"name\":\"BUFFER_LENGTH\", \"type\":\"INTEGER\"},"
"{\"name\":\"DECIMAL_DIGITS\", \"type\":\"SHORT\"},"
"{\"name\":\"PSEUDO_COLUMN\", \"type\":\"SHORT\"}"
"],"
"\"rows\":[]"
"}");
INFOH(hstmt, "no special columns available.");
return fake_answer(hstmt, &special_cols);
}
SQLRETURN EsSQLForeignKeysW(
SQLHSTMT hstmt,
_In_reads_opt_(cchPkCatalogName) SQLWCHAR *szPkCatalogName,
SQLSMALLINT cchPkCatalogName,
_In_reads_opt_(cchPkSchemaName) SQLWCHAR *szPkSchemaName,
SQLSMALLINT cchPkSchemaName,
_In_reads_opt_(cchPkTableName) SQLWCHAR *szPkTableName,
SQLSMALLINT cchPkTableName,
_In_reads_opt_(cchFkCatalogName) SQLWCHAR *szFkCatalogName,
SQLSMALLINT cchFkCatalogName,
_In_reads_opt_(cchFkSchemaName) SQLWCHAR *szFkSchemaName,
SQLSMALLINT cchFkSchemaName,
_In_reads_opt_(cchFkTableName) SQLWCHAR *szFkTableName,
SQLSMALLINT cchFkTableName)
{
cstr_st foreign_keys = CSTR_INIT("{"
"\"columns\":["
"{\"name\":\"PKTABLE_CAT\", \"type\":\"TEXT\"},"
"{\"name\":\"PKTABLE_SCHEM\", \"type\":\"TEXT\"},"
"{\"name\":\"PKTABLE_NAME\", \"type\":\"TEXT\"},"
"{\"name\":\"PKCOLUMN_NAME\", \"type\":\"TEXT\"},"
"{\"name\":\"FKTABLE_CAT\", \"type\":\"TEXT\"},"
"{\"name\":\"FKTABLE_SCHEM\", \"type\":\"TEXT\"},"
"{\"name\":\"FKTABLE_NAME\", \"type\":\"TEXT\"},"
"{\"name\":\"FKCOLUMN_NAME\", \"type\":\"TEXT\"},"
"{\"name\":\"KEY_SEQ\", \"type\":\"SHORT\"},"
"{\"name\":\"UPDATE_RULE\", \"type\":\"SHORT\"},"
"{\"name\":\"DELETE_RULE\", \"type\":\"SHORT\"},"
"{\"name\":\"FK_NAME\", \"type\":\"TEXT\"},"
"{\"name\":\"PK_NAME\", \"type\":\"TEXT\"},"
"{\"name\":\"DEFERRABILITY\", \"type\":\"SHORT\"}"
"],"
"\"rows\":[]"
"}");
INFOH(hstmt, "no foreign keys supported.");
return fake_answer(hstmt, &foreign_keys);
}
SQLRETURN EsSQLPrimaryKeysW(
SQLHSTMT hstmt,
_In_reads_opt_(cchCatalogName) SQLWCHAR *szCatalogName,
SQLSMALLINT cchCatalogName,
_In_reads_opt_(cchSchemaName) SQLWCHAR *szSchemaName,
SQLSMALLINT cchSchemaName,
_In_reads_opt_(cchTableName) SQLWCHAR *szTableName,
SQLSMALLINT cchTableName)
{
cstr_st prim_keys = CSTR_INIT("{"
"\"columns\":["
"{\"name\":\"TABLE_CAT\", \"type\":\"TEXT\"},"
"{\"name\":\"TABLE_SCHEM\", \"type\":\"TEXT\"},"
"{\"name\":\"TABLE_NAME\", \"type\":\"TEXT\"},"
"{\"name\":\"COLUMN_NAME\", \"type\":\"TEXT\"},"
"{\"name\":\"KEY_SEQ\", \"type\":\"SHORT\"},"
"{\"name\":\"PK_NAME\", \"type\":\"TEXT\"}"
"],"
"\"rows\":[]"
"}");
INFOH(hstmt, "no primary keys supported.");
return fake_answer(hstmt, &prim_keys);
}
/* vim: set noet fenc=utf-8 ff=dos sts=0 sw=4 ts=4 tw=78 : */