driver/handles.c (1,998 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 <assert.h>
#include <fcntl.h>
#include "handles.h"
#include "log.h"
#include "queries.h"
#include "connect.h"
static void free_rec_fields(esodbc_rec_st *rec)
{
int i;
SQLWCHAR **wptr[] = {
&rec->base_column_name.str,
&rec->base_table_name.str,
&rec->catalog_name.str,
&rec->label.str,
&rec->name.str,
&rec->schema_name.str,
&rec->table_name.str,
};
for (i = 0; i < sizeof(wptr)/sizeof(wptr[0]); i ++) {
if (*wptr[i]) {
DBGH(rec->desc, "freeing field #%d = 0x%p.", i, *wptr[i]);
free(*wptr[i]);
*wptr[i] = NULL;
}
}
}
static inline void free_desc_recs(esodbc_desc_st *desc)
{
int i;
/* IRD's pointers are 0-copy, no need to free them */
if (desc->type != DESC_TYPE_IRD)
for (i = 0; i < desc->count; i++) {
free_rec_fields(&desc->recs[i]);
}
free(desc->recs);
desc->recs = NULL;
desc->count = 0;
}
static void init_hheader(esodbc_hhdr_st *hdr, SQLSMALLINT type, void *parent)
{
hdr->type = type;
init_diagnostic(&hdr->diag);
ESODBC_MUX_INIT(&hdr->mutex);
hdr->parent = parent;
/* init logging helpers */
switch(type) {
case SQL_HANDLE_ENV:
hdr->typew = MK_WSTR("ENV");
hdr->log = _gf_log; /* use global logger */
break;
case SQL_HANDLE_DBC:
hdr->typew = MK_WSTR("DBC");
hdr->log = _gf_log; /* use global logger, by default */
break;
case SQL_HANDLE_STMT:
hdr->typew = MK_WSTR("STMT");
hdr->log = HDRH(parent)->log; /* inherit */
break;
case SQL_HANDLE_DESC:
hdr->typew = MK_WSTR("DESC");
hdr->log = HDRH(parent)->log; /* inherit */
break;
default:
assert(0);
}
}
/*
* "un-static" the initializers as needed
*/
static inline void init_env(esodbc_env_st *env)
{
memset(env, 0, sizeof(*env));
init_hheader(HDRH(env), SQL_HANDLE_ENV, NULL);
}
void init_dbc(esodbc_dbc_st *dbc, SQLHANDLE InputHandle)
{
memset(dbc, 0, sizeof(*dbc));
init_hheader(HDRH(dbc), SQL_HANDLE_DBC, InputHandle);
dbc->metadata_id = SQL_FALSE;
ESODBC_MUX_INIT(&dbc->curl_mux);
/* rest of initialization done at connect time */
}
static void init_desc(esodbc_desc_st *desc, esodbc_stmt_st *stmt,
desc_type_et type, SQLSMALLINT alloc_type)
{
memset(desc, 0, sizeof(esodbc_desc_st));
/* mandatory defaulting to NULL/0: array_status_ptr, bind_offset_ptr,
* rows_processed_ptr, count */
init_hheader(&desc->hdr, SQL_HANDLE_DESC, stmt);
desc->alloc_type = alloc_type;
/* user allocated descriptors are reset to ANON */
desc->type = alloc_type == SQL_DESC_ALLOC_USER ? DESC_TYPE_ANON : type;
desc->array_size = ESODBC_DEF_ARRAY_SIZE;
if (DESC_TYPE_IS_APPLICATION(type)) {
desc->bind_type = SQL_BIND_BY_COLUMN;
}
}
static void clear_desc(esodbc_desc_st *desc, BOOL reinit)
{
DBGH(desc, "clearing desc type %d.", desc->type);
switch (desc->type) {
case DESC_TYPE_ANON:
case DESC_TYPE_ARD:
case DESC_TYPE_APD:
case DESC_TYPE_IPD:
break;
case DESC_TYPE_IRD:
if (STMT_HAS_CURSOR(HDRH(desc)->stmt)) {
close_es_cursor(HDRH(desc)->stmt);
}
if (STMT_HAS_RESULTSET(desc->hdr.stmt)) {
clear_resultset(desc->hdr.stmt, reinit);
}
break;
default:
BUG("no such descriptor of type %d.", desc->type);
return;
}
if (desc->recs) {
free_desc_recs(desc);
}
if (reinit) {
init_desc(desc, desc->hdr.stmt, desc->type, desc->alloc_type);
}
}
static void init_stmt(esodbc_stmt_st *stmt, SQLHANDLE InputHandle)
{
memset(stmt, 0, sizeof(*stmt));
init_hheader(HDRH(stmt), SQL_HANDLE_STMT, InputHandle);
init_desc(&stmt->i_ard, stmt, DESC_TYPE_ARD, SQL_DESC_ALLOC_AUTO);
init_desc(&stmt->i_ird, stmt, DESC_TYPE_IRD, SQL_DESC_ALLOC_AUTO);
init_desc(&stmt->i_apd, stmt, DESC_TYPE_APD, SQL_DESC_ALLOC_AUTO);
init_desc(&stmt->i_ipd, stmt, DESC_TYPE_IPD, SQL_DESC_ALLOC_AUTO);
/* "When a statement is allocated, four descriptor handles are
* automatically allocated and associated with the statement." */
stmt->ard = &stmt->i_ard;
stmt->ird = &stmt->i_ird;
stmt->apd = &stmt->i_apd;
stmt->ipd = &stmt->i_ipd;
/* set option defaults */
/* TODO: change to SQL_UB_DEFAULT when supporting bookmarks */
stmt->bookmarks = SQL_UB_OFF;
/* inherit this connection-statement attributes
* Note: these attributes won't propagate at statement level when
* set at connection level. */
stmt->metadata_id = DBCH(InputHandle)->metadata_id;
stmt->sql2c_conversion = CONVERSION_UNCHECKED;
stmt->early_executed = FALSE;
}
void dump_record(esodbc_rec_st *rec)
{
DBGH(rec->desc, "Dumping REC@0x%p", rec);
#define DUMP_FIELD(_strp, _name, _desc) \
DBGH(rec->desc, "0x%p->%s: `" _desc "`.", _strp, # _name, (_strp)->_name)
// TODO: add dumping for the es_type?
DUMP_FIELD(rec, desc, "0x%p");
DUMP_FIELD(rec, meta_type, "%d");
DUMP_FIELD(rec, concise_type, "%d");
DUMP_FIELD(rec, type, "%d");
DUMP_FIELD(rec, datetime_interval_code, "%d");
DUMP_FIELD(rec, data_ptr, "0x%p");
DUMP_FIELD(rec, base_column_name, LWPD);
DUMP_FIELD(rec, base_table_name, LWPD);
DUMP_FIELD(rec, catalog_name, LWPD);
DUMP_FIELD(rec, label, LWPD);
DUMP_FIELD(rec, name, LWPD);
DUMP_FIELD(rec, schema_name, LWPD);
DUMP_FIELD(rec, table_name, LWPD);
DUMP_FIELD(rec, indicator_ptr, "0x%p");
DUMP_FIELD(rec, octet_length_ptr, "0x%p");
/* these fields are SQLLEN and SQLULEN, so they'd need long long
* specifiers on WIN64 and long on WIN32; however it's hard to conceive a
* case where their fields would either be negative or exceed INT_MAX. */
DUMP_FIELD(rec, octet_length, "%ld");
DUMP_FIELD(rec, length, "%lu");
DUMP_FIELD(rec, datetime_interval_precision, "%d");
DUMP_FIELD(rec, num_prec_radix, "%d");
DUMP_FIELD(rec, parameter_type, "%d");
DUMP_FIELD(rec, precision, "%d");
DUMP_FIELD(rec, rowver, "%d");
DUMP_FIELD(rec, scale, "%d");
DUMP_FIELD(rec, unnamed, "%d");
DUMP_FIELD(rec, updatable, "%d");
#undef DUMP_FIELD
}
/*
* The Driver Manager does not call the driver-level environment handle
* allocation function until the application calls SQLConnect,
* SQLBrowseConnect, or SQLDriverConnect.
*
* 1. The Driver Manager then calls SQLAllocHandle in the driver with the
* SQL_HANDLE_DBC option, whether or not it was just loaded.
* 2. SQLSetConnectAttr()
* 3. Finally, the Driver Manager calls the connection function in the driver.
* 4. SQLDisconnect()
* 5. SQLFreeHandle()
*
* "The fields of an IRD have a default value only after the statement has
* been prepared or executed and the IRD has been populated, not when the
* statement handle or descriptor has been allocated. Until the IRD has been
* populated, any attempt to gain access to a field of an IRD will return an
* error."
*/
SQLRETURN EsSQLAllocHandle(SQLSMALLINT HandleType,
SQLHANDLE InputHandle, _Out_ SQLHANDLE *OutputHandle)
{
if (! OutputHandle) {
ERR("null output handle provided.");
RET_STATE(SQL_STATE_HY009);
}
if (HandleType != SQL_HANDLE_ENV) {
if (! InputHandle) {
ERR("null input handle provided");
RET_STATE(SQL_STATE_HY009);
}
} else {
if (InputHandle) {
ERR("not null (@0x%p) input handle for Environment.", InputHandle);
/* not fatal a.t.p., tho it'll likely fail (->error level) */
}
}
switch(HandleType) {
/*
* https://docs.microsoft.com/en-us/sql/odbc/reference/syntax/sqlallochandle-function :
* """
* If the Driver Manager cannot allocate memory for *OutputHandlePtr
* when SQLAllocHandle with a HandleType of SQL_HANDLE_ENV is called,
* or the application provides a null pointer for OutputHandlePtr,
* SQLAllocHandle returns SQL_ERROR. The Driver Manager sets
* *OutputHandlePtr to SQL_NULL_HENV (unless the application provided
* a null pointer, which returns SQL_ERROR). There is no handle with
* which to associate additional diagnostic information.
* """
*/
case SQL_HANDLE_ENV: /* Environment Handle */
*OutputHandle = (SQLHANDLE)malloc(sizeof(esodbc_env_st));
if (*OutputHandle) {
init_env(*OutputHandle);
DBG("new Environment handle @0x%p.", *OutputHandle);
}
break;
case SQL_HANDLE_DBC: /* Connection Handle */
/* https://docs.microsoft.com/en-us/sql/odbc/reference/syntax/sqlallochandle-function#diagnostics :
* """
* If the SQL_ATTR_ODBC_VERSION environment attribute is not set
* before SQLAllocHandle is called to allocate a connection handle
* on the environment, the call to allocate the connection will
* return SQLSTATE HY010 (Function sequence error).
* """
*/
if (! ENVH(InputHandle)->version) {
ERRH(InputHandle, "no version set in ENV.");
RET_HDIAG(InputHandle, SQL_STATE_HY010,
"enviornment has no version set", 0);
}
*OutputHandle = (SQLHANDLE)malloc(sizeof(esodbc_dbc_st));
if (*OutputHandle) {
init_dbc(*OutputHandle, InputHandle);
DBGH(InputHandle, "new Connection @0x%p.", *OutputHandle);
}
break;
case SQL_HANDLE_STMT: /* Statement Handle */
*OutputHandle = (SQLHANDLE)malloc(sizeof(esodbc_stmt_st));
if (*OutputHandle) {
init_stmt(*OutputHandle, InputHandle);
DBGH(InputHandle, "new Statement @0x%p.", *OutputHandle);
}
break;
case SQL_HANDLE_DESC: /* Descriptor Handle */
*OutputHandle = (SQLHANDLE)malloc(sizeof(esodbc_desc_st));
if (*OutputHandle) {
init_desc(*OutputHandle, InputHandle, DESC_TYPE_ANON,
SQL_DESC_ALLOC_USER);
DBGH(InputHandle, "new Statement @0x%p.", *OutputHandle);
}
break;
case SQL_HANDLE_SENV: /* Shared Environment Handle */
// case SQL_HANDLE_DBC_INFO_TOKEN:
ERR("handle type %hd not implemented.", HandleType);
RET_STATE(SQL_STATE_HYC00); // optional feature
default:
ERR("unknown HandleType: %d.", HandleType);
return SQL_INVALID_HANDLE;
}
if (! *OutputHandle) {
ERRN("OOM for handle type %hd, on input handle@0x%p.", HandleType,
InputHandle);
if (InputHandle) {
RET_HDIAGS(InputHandle, SQL_STATE_HY001);
} else {
RET_STATE(SQL_STATE_HY001);
}
} else {
/* new handle has been init'ed */
assert(HDRH(*OutputHandle)->type);
}
return SQL_SUCCESS;
}
SQLRETURN EsSQLFreeHandle(SQLSMALLINT HandleType, SQLHANDLE Handle)
{
esodbc_dbc_st *dbc;
esodbc_stmt_st *stmt;
esodbc_desc_st *desc;
if (! Handle) {
ERR("provided null Handle.");
RET_STATE(SQL_STATE_HY009);
}
switch(HandleType) {
case SQL_HANDLE_ENV: /* Environment Handle */
break;
case SQL_HANDLE_DBC: /* Connection Handle */
dbc = DBCH(Handle);
/* app/DM should have SQLDisconnect'ed, but just in case */
cleanup_dbc(dbc);
ESODBC_MUX_DEL(&dbc->curl_mux);
break;
case SQL_HANDLE_STMT:
stmt = STMH(Handle);
detach_sql(stmt);
clear_desc(stmt->ard, FALSE);
clear_desc(stmt->ird, FALSE);
clear_desc(stmt->apd, FALSE);
clear_desc(stmt->ipd, FALSE);
break;
// FIXME:
/* "When an explicitly allocated descriptor is freed, all statement
* handles to which the freed descriptor applied automatically revert
* to the descriptors implicitly allocated for them." */
case SQL_HANDLE_DESC:
desc = DSCH(Handle);
clear_desc(desc, FALSE);
break;
case SQL_HANDLE_SENV: /* Shared Environment Handle */
RET_STATE(SQL_STATE_HYC00);
#if 0
case SQL_HANDLE_DBC_INFO_TOKEN:
//break;
#endif
default:
ERR("unknown HandleType: %d.", HandleType);
return SQL_INVALID_HANDLE;
}
ESODBC_MUX_DEL(&HDRH(Handle)->mutex);
free(Handle);
return SQL_SUCCESS;
}
/*
* "Calling SQLFreeStmt with the SQL_CLOSE, SQL_UNBIND, or SQL_RESET_PARAMS
* option does not reset statement attributes."
*
* "To unbind all columns, an application calls SQLFreeStmt with fOption set
* to SQL_UNBIND. This can also be accomplished by setting the SQL_DESC_COUNT
* field of the ARD to zero.
*
* "If all columns are unbound by calling SQLFreeStmt with the SQL_UNBIND
* option, the SQL_DESC_COUNT fields in the ARD and IRD are set to 0. If
* SQLFreeStmt is called with the SQL_RESET_PARAMS option, the SQL_DESC_COUNT
* fields in the APD and IPD are set to 0." (.count)
* */
SQLRETURN EsSQLFreeStmt(SQLHSTMT StatementHandle, SQLUSMALLINT Option)
{
SQLRETURN ret;
esodbc_stmt_st *stmt = STMH(StatementHandle);
switch (Option) {
/* "deprecated. A call to SQLFreeStmt with an Option of SQL_DROP is
* mapped in the Driver Manager to SQLFreeHandle." */
case SQL_DROP:
/*TODO: if freeing, the app/DM might reuse the handler; if
* doing nothing, it might leak mem. */
ERRH(stmt, "DROPing is deprecated -- no action taken! "
"(might leak memory)");
//return SQLFreeHandle(SQL_HANDLE_STMT,(SQLHANDLE)StatementHandle);
break;
/* "This does not unbind the bookmark column; to do that, the
* SQL_DESC_DATA_PTR field of the ARD for the bookmark column is set
* to NULL." TODO, with bookmarks. */
case SQL_UNBIND:
DBGH(stmt, "unbinding.");
/* "Sets the SQL_DESC_COUNT field of the ARD to 0, releasing all
* column buffers bound by SQLBindCol for the given
* StatementHandle." */
ret = EsSQLSetDescFieldW(stmt->ard, NO_REC_NR, SQL_DESC_COUNT,
(SQLPOINTER)0, SQL_IS_SMALLINT);
if (ret != SQL_SUCCESS) {
/* copy error at top handle level, where it's going to be
* inquired from */
HDIAG_COPY(stmt->ard, stmt);
return ret;
}
break;
case ESODBC_SQL_CLOSE:
DBGH(stmt, "ES-closing.");
detach_sql(stmt);
/* no break */
/* "Closes the cursor associated with StatementHandle and discards all
* pending results." */
case SQL_CLOSE:
DBGH(stmt, "closing.");
clear_desc(stmt->ird, TRUE);
break;
/* "Sets the SQL_DESC_COUNT field of the APD to 0, releasing all
* parameter buffers set by SQLBindParameter for the given
* StatementHandle." */
case SQL_RESET_PARAMS:
DBGH(stmt, "resetting params.");
init_diagnostic(&stmt->hdr.diag);
ret = EsSQLSetDescFieldW(stmt->apd, NO_REC_NR, SQL_DESC_COUNT,
(SQLPOINTER)0, SQL_IS_SMALLINT);
if (! SQL_SUCCEEDED(ret)) {
/* copy error at top handle level, where it's going to be
* inquired from */
HDIAG_COPY(stmt->ard, stmt);
}
/* docs aren't explicit when should the IPD be cleared; but since
* execution of a statement is independent from params binding,
* the IPD and APD clearning can only be linked together. */
ret = EsSQLSetDescFieldW(stmt->ipd, NO_REC_NR, SQL_DESC_COUNT,
(SQLPOINTER)0, SQL_IS_SMALLINT);
if (! SQL_SUCCEEDED(ret)) {
HDIAG_COPY(stmt->ird, stmt);
}
RET_STATE(stmt->hdr.diag.state);
default:
ERRH(stmt, "unknown Option value: %d.", Option);
RET_HDIAGS(STMH(StatementHandle), SQL_STATE_HY092);
}
return SQL_SUCCESS;
}
/*
* To specify an ODBC compliance level of 3.8, an application calls
* SQLSetEnvAttr with the SQL_ATTR_ODBC_VERSION attribute set to
* SQL_OV_ODBC3_80. To determine the version of the driver, an application
* calls SQLGetInfo with SQL_DRIVER_ODBC_VER.
*/
SQLRETURN EsSQLSetEnvAttr(SQLHENV EnvironmentHandle,
SQLINTEGER Attribute,
_In_reads_bytes_opt_(StringLength) SQLPOINTER Value,
SQLINTEGER StringLength)
{
/*
* https://docs.microsoft.com/en-us/sql/odbc/reference/syntax/sqlsetenvattr-function :
* """
* ValuePtr
* [Input] Pointer to the value to be associated with Attribute. Depending
* on the value of Attribute, ValuePtr will be a 32-bit integer value or
* point to a null-terminated character string.
* """
*/
assert(sizeof(SQLINTEGER) == 4);
switch (Attribute) {
case SQL_ATTR_CONNECTION_POOLING:
case SQL_ATTR_CP_MATCH:
RET_HDIAG(ENVH(EnvironmentHandle), SQL_STATE_HYC00,
"Connection pooling not supported", 0);
case SQL_ATTR_ODBC_VERSION:
switch ((intptr_t)Value) {
// supporting applications of 2.x and 3.x<3.8 needs extensive
// review of the options.
case SQL_OV_ODBC2:
case SQL_OV_ODBC3:
WARNH(EnvironmentHandle, "application version %d not fully"
" supported.", (intptr_t)Value);
case SQL_OV_ODBC3_80:
break;
default:
ERRH(EnvironmentHandle, "application version %zd not "
"supported.", (intptr_t)Value);
RET_HDIAG(ENVH(EnvironmentHandle), SQL_STATE_HYC00,
"application version not supported", 0);
}
ENVH(EnvironmentHandle)->version = (SQLUINTEGER)(uintptr_t)Value;
INFOH(EnvironmentHandle, "set version to %lu.",
ENVH(EnvironmentHandle)->version);
break;
/* "If SQL_TRUE, the driver returns string data null-terminated" */
case SQL_ATTR_OUTPUT_NTS:
if ((intptr_t)Value != SQL_TRUE)
RET_HDIAG(ENVH(EnvironmentHandle), SQL_STATE_HYC00,
"Driver always returns null terminated strings", 0);
break;
default:
ERRH(EnvironmentHandle, "unsupported Attribute value: %d.",
Attribute);
RET_HDIAG(ENVH(EnvironmentHandle), SQL_STATE_HY024,
"Unsupported attribute value", 0);
}
return SQL_SUCCESS;
}
SQLRETURN EsSQLGetEnvAttr(SQLHENV EnvironmentHandle, SQLINTEGER Attribute,
_Out_writes_(_Inexpressible_(BufferLength)) SQLPOINTER Value,
SQLINTEGER BufferLength, _Out_opt_ SQLINTEGER *StringLength)
{
switch (Attribute) {
case SQL_ATTR_CONNECTION_POOLING:
case SQL_ATTR_CP_MATCH:
RET_HDIAG(ENVH(EnvironmentHandle), SQL_STATE_HYC00,
"Connection pooling not yet supported", 0);
case SQL_ATTR_ODBC_VERSION:
*((SQLUINTEGER *)Value) = ENVH(EnvironmentHandle)->version;
break;
case SQL_ATTR_OUTPUT_NTS:
*((SQLUINTEGER *)Value) = SQL_TRUE;
break;
default:
ERRH(EnvironmentHandle, "unsupported Attribute value: %d.",
Attribute);
RET_HDIAG(ENVH(EnvironmentHandle), SQL_STATE_HY024,
"Unsupported attribute value", 0);
}
return SQL_SUCCESS;
}
SQLRETURN EsSQLSetStmtAttrW(
SQLHSTMT StatementHandle,
SQLINTEGER Attribute,
SQLPOINTER ValuePtr,
SQLINTEGER BufferLength)
{
SQLRETURN ret;
SQLULEN ulen;
esodbc_desc_st *desc;
esodbc_stmt_st *stmt = STMH(StatementHandle);
/*INDENT-OFF*/
switch(Attribute) {
case SQL_ATTR_USE_BOOKMARKS:
DBGH(stmt, "setting use-bookmarks to: %llu.", (uint64_t)ValuePtr);
if ((SQLULEN)ValuePtr != SQL_UB_OFF) {
ERRH(stmt, "bookmarks are not supported by driver.");
RET_HDIAG(stmt, SQL_STATE_HYC00,
"bookmarks are not supported by driver", 0);
}
break;
do {
/* "If this field is non-null, the driver dereferences the pointer,
* adds the dereferenced value to each of the deferred fields in the
* descriptor record (SQL_DESC_DATA_PTR, SQL_DESC_INDICATOR_PTR, and
* SQL_DESC_OCTET_LENGTH_PTR), and uses the new pointer values when
* binding. It is set to null by default." */
case SQL_ATTR_ROW_BIND_OFFSET_PTR:
/* offset in bytes */
/* "Setting this statement attribute sets the
* SQL_DESC_BIND_OFFSET_PTR field in the ARD header." */
DBGH(stmt, "setting row-bind-offset pointer to: 0x%p.", ValuePtr);
desc = stmt->ard;
break;
case SQL_ATTR_PARAM_BIND_OFFSET_PTR:
DBGH(stmt, "setting param-bind-offset pointer to: 0x%p.", ValuePtr);
desc = stmt->apd;
break;
} while (0);
ret = EsSQLSetDescFieldW(desc, NO_REC_NR, SQL_DESC_BIND_OFFSET_PTR,
ValuePtr, BufferLength);
if (ret != SQL_SUCCESS) { /* _WITH_INFO wud be "error" here */
/* if SetDescField() fails, DM will check statement's diag */
HDIAG_COPY(desc, stmt);
}
return ret;
do {
/* "Setting this statement attribute sets the SQL_DESC_ARRAY_SIZE
* field in the ARD header." */
case SQL_ATTR_ROW_ARRAY_SIZE:
DBGH(stmt, "setting row array size to: %d.", (SQLULEN)ValuePtr);
desc = stmt->ard;
break;
/* "Setting this statement attribute sets the SQL_DESC_ARRAY_SIZE
* field in the APD header." */
case SQL_ATTR_PARAMSET_SIZE:
DBGH(stmt, "setting param set size to: %d.", (SQLULEN)ValuePtr);
desc = stmt->apd;
break;
} while (0);
ret = EsSQLSetDescFieldW(desc, NO_REC_NR, SQL_DESC_ARRAY_SIZE,
ValuePtr, BufferLength);
if (ret != SQL_SUCCESS) { /* _WITH_INFO wud be "error" here */
/* if SetDescField() fails, DM will check statement's diag */
HDIAG_COPY(desc, stmt);
}
return ret;
do {
/* "sets the binding orientation to be used when SQLFetch or
* SQLFetchScroll is called on the associated statement" */
/* "Setting this statement attribute sets the SQL_DESC_BIND_TYPE field
* in the ARD header." */
case SQL_ATTR_ROW_BIND_TYPE:
DBGH(stmt, "setting row bind type to: %d.", (SQLULEN)ValuePtr);
/* value is SQL_BIND_BY_COLUMN (0UL) or struct len */
/* "the driver can calculate the address of the data for a
* particular row and column as:
* Address = Bound Address + ((Row Number - 1) * Structure Size)"
* https://docs.microsoft.com/en-us/sql/odbc/reference/develop-app/column-wise-binding
* https://docs.microsoft.com/en-us/sql/odbc/reference/develop-app/row-wise-binding
*/
desc = stmt->ard;
break;
case SQL_ATTR_PARAM_BIND_TYPE:
DBGH(stmt, "setting param bind type to: %d.", (SQLULEN)ValuePtr);
desc = stmt->apd;
break;
} while (0);
ret = EsSQLSetDescFieldW(desc, NO_REC_NR, SQL_DESC_BIND_TYPE,
/* note: SetStmt()'s spec defineds the ValuePtr as
* SQLULEN, but SetDescField()'s as SQLUINTEGER.. */
ValuePtr, BufferLength);
if (ret != SQL_SUCCESS) { /* _WITH_INFO wud be "error" here */
/* if SetDescField() fails, DM will check statement's diag */
HDIAG_COPY(desc, stmt);
}
return ret;
do {
/* "an array of SQLUSMALLINT values containing row status values after
* a call to SQLFetch or SQLFetchScroll." */
/* https://docs.microsoft.com/en-us/sql/odbc/reference/develop-app/row-status-array */
/* "Setting this statement attribute sets the
* SQL_DESC_ARRAY_STATUS_PTR field in the IRD header." */
case SQL_ATTR_ROW_STATUS_PTR:
// TODO: call SQLSetDescField(IRD) here?
DBGH(stmt, "setting row status pointer to: 0x%p.", ValuePtr);
desc = stmt->ird;
break;
case SQL_ATTR_PARAM_STATUS_PTR:
DBGH(stmt, "setting param status pointer to: 0x%p.", ValuePtr);
desc = stmt->ipd;
break;
case SQL_ATTR_ROW_OPERATION_PTR:
DBGH(stmt, "setting row operation array pointer to: 0x%p.",
ValuePtr);
desc = stmt->ard;
break;
case SQL_ATTR_PARAM_OPERATION_PTR:
DBGH(stmt, "setting param operation array pointer to: 0x%p.",
ValuePtr);
desc = stmt->apd;
break;
} while (0);
ret = EsSQLSetDescFieldW(desc, NO_REC_NR,
SQL_DESC_ARRAY_STATUS_PTR, ValuePtr, BufferLength);
if (ret != SQL_SUCCESS) { /* _WITH_INFO wud be "error" here */
/* if SetDescField() fails, DM will check statement's diag */
HDIAG_COPY(desc, stmt);
}
return ret;
do {
/* "Setting this statement attribute sets the
* SQL_DESC_ROWS_PROCESSED_PTR field in the IRD header." */
case SQL_ATTR_ROWS_FETCHED_PTR:
DBGH(stmt, "setting rows fetched pointer to: 0x%p.", ValuePtr);
/* NOTE: documentation writes about "ARD", while also stating that
* this field is unused in the ARD. I assume the former as wrong */
desc = stmt->ird;
break;
case SQL_ATTR_PARAMS_PROCESSED_PTR:
DBGH(stmt, "setting params processed pointer to: 0x%p.", ValuePtr);
desc = stmt->ipd;
break;
} while (0);
ret = EsSQLSetDescFieldW(desc, NO_REC_NR,
SQL_DESC_ROWS_PROCESSED_PTR, ValuePtr, BufferLength);
if (ret != SQL_SUCCESS) { /* _WITH_INFO wud be "error" here */
/* if SetDescField() fails, DM will check statement's diag */
HDIAG_COPY(desc, stmt);
}
return ret;
case SQL_ATTR_APP_ROW_DESC:
desc = DSCH(ValuePtr);
if (desc == stmt->ard) {
WARNH(stmt, "trying to overwrite ARD with same value (@0x%p).",
ValuePtr);
break; /* nop */
}
if (desc == &stmt->i_ard || desc == SQL_NULL_HDESC) {
DBGH(stmt, "unbinding current ARD (@0x%p), rebinding with "
"implicit value (@0x%p).", stmt->ard, &stmt->i_ard);
/* re-anonymize the descriptor, makingit re-usable */
stmt->ard = &stmt->i_ard;
} else {
/* "This attribute cannot be set to a descriptor handle that
* was implicitly allocated for another statement or to
* another descriptor handle that was implicitly set on the
* same statement; implicitly allocated descriptor handles
* cannot be associated with more than one statement or
* descriptor handle." */
if (desc->alloc_type == SQL_DESC_ALLOC_AUTO) {
ERRH(stmt, "source ARD (@0x%p) is implicit (alloc: %d).",
desc, desc->alloc_type);
RET_HDIAGS(stmt, SQL_STATE_HY017);
} else {
switch (desc->type) {
case DESC_TYPE_ANON:
desc->type = DESC_TYPE_ARD;
case DESC_TYPE_ARD:
break;
default:
// TODO: should this be allowed?
/* this means a descriptor can not be changed from
* APD to ARD (IxD is also ruled out) */
ERRH(stmt, "can't convert descriptor from type %d"
" to ARD.", desc->type);
RET_HDIAGS(stmt, SQL_STATE_HY024);
}
DBGH(stmt, "overwritting current ARD (@0x%p) with new "
"value (@0x%p).", stmt->ard, desc);
stmt->ard = desc;
}
}
break;
case SQL_ATTR_APP_PARAM_DESC:
// FIXME: same logic for ARD as above (part of params passing)
FIXME;
break;
case SQL_ATTR_IMP_ROW_DESC:
case SQL_ATTR_IMP_PARAM_DESC:
ERRH(stmt, "trying to set IxD (%d) descriptor (to @0x%p).",
Attribute, ValuePtr);
RET_HDIAGS(stmt, SQL_STATE_HY017);
case SQL_ATTR_ROW_NUMBER:
ERRH(stmt, "row number attribute is read-only.");
RET_HDIAGS(stmt, SQL_STATE_HY024);
case SQL_ATTR_METADATA_ID:
DBGH(stmt, "setting metadata_id to: %llu", (uint64_t)ValuePtr);
stmt->metadata_id = (SQLULEN)ValuePtr;
break;
case SQL_ATTR_ASYNC_ENABLE:
ERRH(stmt, "no support for async API (setting param: %llu)",
(uint64_t)ValuePtr);
if ((SQLULEN)ValuePtr == SQL_ASYNC_ENABLE_ON) {
RET_HDIAGS(stmt, SQL_STATE_HYC00);
}
break;
case SQL_ATTR_ASYNC_STMT_EVENT:
// case SQL_ATTR_ASYNC_STMT_PCALLBACK:
// case SQL_ATTR_ASYNC_STMT_PCONTEXT:
ERRH(stmt, "no support for async API (attr: %ld)", Attribute);
RET_HDIAGS(stmt, SQL_STATE_S1118);
case SQL_ATTR_MAX_LENGTH:
ulen = (SQLULEN)ValuePtr;
DBGH(stmt, "setting max_length to: %llu.", (uint64_t)ulen);
if (ulen < ESODBC_LO_MAX_LENGTH) {
WARNH(stmt, "MAX_LENGTH lower than min allowed (%d) -- "
"correcting value.", ESODBC_LO_MAX_LENGTH);
ulen = ESODBC_LO_MAX_LENGTH;
} else if (ESODBC_UP_MAX_LENGTH && ESODBC_UP_MAX_LENGTH < ulen) {
WARNH(stmt, "MAX_LENGTH higher than max allowed (%d) -- "
"correcting value", ESODBC_UP_MAX_LENGTH);
ulen = ESODBC_UP_MAX_LENGTH;
}
stmt->max_length = ulen;
if (ulen != (SQLULEN)ValuePtr)
RET_HDIAGS(stmt, SQL_STATE_01S02);
break;
case SQL_ATTR_QUERY_TIMEOUT:
DBGH(stmt, "setting query timeout to: %llu.", (uint64_t)ValuePtr);
stmt->query_timeout = (SQLULEN)ValuePtr;
break;
case SQL_ATTR_CURSOR_TYPE:
DBGH(stmt, "setting cursor type: %llu.", (SQLUBIGINT)ValuePtr);
if ((SQLULEN)ValuePtr != SQL_CURSOR_FORWARD_ONLY) {
WARNH(stmt, "requested cursor_type substituted with "
"forward-only (%lu).", SQL_CURSOR_FORWARD_ONLY);
RET_HDIAGS(stmt, SQL_STATE_01S02);
}
break;
case SQL_ATTR_NOSCAN:
DBGH(stmt, "setting escape seq scanning: %llu -- NOOP.",
(uint64_t)ValuePtr);
/* nothing to do: the driver never scans the input, ESSQL processes
* the escape sequences */
break;
case SQL_ATTR_CONCURRENCY:
DBGH(stmt, "setting concurrency: %llu.", (uint64_t)ValuePtr);
if ((SQLULEN)ValuePtr != SQL_CONCUR_READ_ONLY) {
WARNH(stmt, "requested concurrency substituted with "
"read-only (%d).", SQL_CONCUR_READ_ONLY);
RET_HDIAGS(stmt, SQL_STATE_01S02);
}
break;
case SQL_ATTR_MAX_ROWS:
DBGH(stmt, "setting max rows: %llu.", (uint64_t)ValuePtr);
if ((SQLULEN)ValuePtr != 0) {
WARNH(stmt, "requested max_rows substituted with 0.");
RET_HDIAGS(stmt, SQL_STATE_01S02);
}
break;
case SQL_ATTR_CURSOR_SENSITIVITY:
DBGH(stmt, "setting cursor sensitivity: %llu.",
(uint64_t)ValuePtr);
if ((SQLULEN)ValuePtr != SQL_UNSPECIFIED) {
ERRH(stmt, "driver supports forward-only cursors.");
RET_HDIAGS(stmt, SQL_STATE_HYC00);
}
break;
case SQL_ATTR_CURSOR_SCROLLABLE:
DBGH(stmt, "setting scrollable cursor: %llu.", (uint64_t)ValuePtr);
if ((SQLULEN)ValuePtr != SQL_NONSCROLLABLE) {
ERRH(stmt, "driver supports only non-scrollable cursors.");
RET_HDIAGS(stmt, SQL_STATE_HYC00);
}
break;
case SQL_ATTR_RETRIEVE_DATA:
DBGH(stmt, "setting data retrieving: %llu.", (uint64_t)ValuePtr);
if ((SQLULEN)ValuePtr != SQL_RD_ON) {
WARNH(stmt, "no fetching without data retrieval possible.");
RET_HDIAGS(stmt, SQL_STATE_01S02);
}
break;
/* SQL Server non-standard attributes */
case 1226:
case 1227:
case 1228:
ERRH(stmt, "non-standard attribute: %d.", Attribute);
/* "Invalid attribute/option identifier" */
RET_HDIAGS(stmt, SQL_STATE_HY092);
default:
// FIXME
BUGH(stmt, "unknown Attribute: %d.", Attribute);
RET_HDIAGS(stmt, SQL_STATE_HY092);
}
/*INDENT-ON*/
return SQL_SUCCESS;
}
SQLRETURN EsSQLGetStmtAttrW(
SQLHSTMT StatementHandle,
SQLINTEGER Attribute,
SQLPOINTER ValuePtr,
SQLINTEGER BufferLength,
SQLINTEGER *StringLengthPtr)
{
esodbc_desc_st *desc;
esodbc_stmt_st *stmt = STMH(StatementHandle);
SQLRETURN ret;
/*INDENT-OFF*/
switch (Attribute) {
do {
case SQL_ATTR_APP_ROW_DESC: desc = stmt->ard; break;
case SQL_ATTR_APP_PARAM_DESC: desc = stmt->apd; break;
case SQL_ATTR_IMP_ROW_DESC: desc = stmt->ird; break;
case SQL_ATTR_IMP_PARAM_DESC: desc = stmt->ipd; break;
} while (0);
DBGH(stmt, "getting descriptor (attr type %d): 0x%p.", Attribute,
desc);
*(SQLPOINTER *)ValuePtr = desc;
break;
case SQL_ATTR_METADATA_ID:
DBGH(stmt, "getting metadata_id: %llu",
(uint64_t)stmt->metadata_id);
*(SQLULEN *)ValuePtr = stmt->metadata_id;
break;
case SQL_ATTR_ASYNC_ENABLE:
DBGH(stmt, "getting async mode: %lu", SQL_ASYNC_ENABLE_OFF);
*(SQLULEN *)ValuePtr = SQL_ASYNC_ENABLE_OFF;
break;
case SQL_ATTR_MAX_LENGTH:
DBGH(stmt, "getting max_length: %llu",
(uint64_t)stmt->max_length);
*(SQLULEN *)ValuePtr = stmt->max_length;
break;
case SQL_ATTR_QUERY_TIMEOUT:
DBGH(stmt, "getting query timeout: %llu",
(uint64_t)stmt->query_timeout);
*(SQLULEN *)ValuePtr = stmt->query_timeout;
break;
/* "determine the number of the current row in the result set" */
case SQL_ATTR_ROW_NUMBER:
*(SQLULEN *)ValuePtr = (SQLULEN)stmt->tv_rows;
DBGH(stmt, "getting row number: %llu",
(uint64_t)*(SQLULEN *)ValuePtr);
break;
case SQL_ATTR_CURSOR_TYPE:
DBGH(stmt, "getting cursor type: %lu.", SQL_CURSOR_FORWARD_ONLY);
/* we only support forward only cursor, so far - TODO */
*(SQLULEN *)ValuePtr = SQL_CURSOR_FORWARD_ONLY;
break;
do {
/* "Setting this statement attribute sets the SQL_DESC_ARRAY_SIZE
* field in the ARD header." */
case SQL_ATTR_ROW_ARRAY_SIZE:
DBGH(stmt, "getting row array size.");
desc = stmt->ard;
break;
/* "Setting this statement attribute sets the SQL_DESC_ARRAY_SIZE
* field in the APD header." */
case SQL_ATTR_PARAMSET_SIZE:
DBGH(stmt, "getting param set size.");
desc = stmt->apd;
break;
} while (0);
/* " If Attribute is an ODBC-defined attribute and *ValuePtr is an
* integer, BufferLength is ignored." */
ret = EsSQLGetDescFieldW(desc, NO_REC_NR, SQL_DESC_ARRAY_SIZE,
ValuePtr, SQL_IS_UINTEGER, NULL);
if (ret != SQL_SUCCESS) { /* _WITH_INFO wud be "error" here */
/* if SetDescField() fails, DM will check statement's diag */
HDIAG_COPY(desc, stmt);
}
return ret;
case SQL_ATTR_USE_BOOKMARKS:
DBGH(stmt, "getting use-bookmarks: %lu.", SQL_UB_OFF);
*(SQLULEN *)ValuePtr = SQL_UB_OFF;
break;
case SQL_ATTR_NOSCAN:
DBGH(stmt, "getting noscan: %lu.", SQL_NOSCAN_OFF);
*(SQLULEN *)ValuePtr = SQL_NOSCAN_OFF;
break;
case SQL_ATTR_CONCURRENCY:
DBGH(stmt, "getting concurrency: %d.", SQL_CONCUR_READ_ONLY);
*(SQLULEN *)ValuePtr = SQL_CONCUR_READ_ONLY;
break;
case SQL_ATTR_MAX_ROWS:
DBGH(stmt, "getting max rows: 0.");
*(SQLULEN *)ValuePtr = 0;
break;
case SQL_ATTR_CURSOR_SENSITIVITY:
DBGH(stmt, "getting cursor sensitivity: %d.", SQL_UNSPECIFIED);
*(SQLULEN *)ValuePtr = SQL_UNSPECIFIED;
break;
case SQL_ATTR_CURSOR_SCROLLABLE:
DBGH(stmt, "getting scrollable cursor: %d.", SQL_NONSCROLLABLE);
*(SQLULEN *)ValuePtr = SQL_NONSCROLLABLE;
break;
case SQL_ATTR_RETRIEVE_DATA:
DBGH(stmt, "getting data retrieving: %lu.", SQL_RD_ON);
*(SQLULEN *)ValuePtr = SQL_RD_ON;
break;
default:
ERRH(stmt, "unknown attribute: %ld.", Attribute);
RET_HDIAGS(stmt, SQL_STATE_HY092);
}
/*INDENT-ON*/
return SQL_SUCCESS;
}
/*INDENT-ON*/
/*
* SQL_DESC_BASE_COLUMN_NAME pointer to a character string
* SQL_DESC_BASE_TABLE_NAME pointer to a character string
* SQL_DESC_CATALOG_NAME pointer to a character string
* SQL_DESC_LABEL pointer to a character string
* SQL_DESC_LITERAL_PREFIX pointer to a character string
* SQL_DESC_LITERAL_SUFFIX pointer to a character string
* SQL_DESC_LOCAL_TYPE_NAME pointer to a character string
* SQL_DESC_NAME pointer to a character string
* SQL_DESC_SCHEMA_NAME pointer to a character string
* SQL_DESC_TABLE_NAME pointer to a character string
* SQL_DESC_TYPE_NAME pointer to a character string
* SQL_DESC_INDICATOR_PTR pointer to a binary buffer
* SQL_DESC_OCTET_LENGTH_PTR pointer to a binary buffer
* SQL_DESC_BIND_OFFSET_PTR pointer to a binary buffer
* SQL_DESC_ROWS_PROCESSED_PTR pointer to a binary buffer
* SQL_DESC_ARRAY_STATUS_PTR pointer to a binary buffer
* SQL_DESC_DATA_PTR pointer to a value,
* other than a character string or binary string
* SQL_DESC_BIND_TYPE SQL_IS_INTEGER
* SQL_DESC_AUTO_UNIQUE_VALUE SQL_IS_INTEGER
* SQL_DESC_CASE_SENSITIVE SQL_IS_INTEGER
* SQL_DESC_DATETIME_INTERVAL_PRECISION SQL_IS_INTEGER
* SQL_DESC_NUM_PREC_RADIX SQL_IS_INTEGER
* SQL_DESC_DISPLAY_SIZE SQL_IS_INTEGER
* SQL_DESC_OCTET_LENGTH SQL_IS_INTEGER
* SQL_DESC_ARRAY_SIZE SQL_IS_UINTEGER
* SQL_DESC_LENGTH SQL_IS_UINTEGER
* SQL_DESC_ALLOC_TYPE SQL_IS_SMALLINT
* SQL_DESC_COUNT SQL_IS_SMALLINT
* SQL_DESC_CONCISE_TYPE SQL_IS_SMALLINT
* SQL_DESC_DATETIME_INTERVAL_CODE SQL_IS_SMALLINT
* SQL_DESC_FIXED_PREC_SCALE SQL_IS_SMALLINT
* SQL_DESC_NULLABLE SQL_IS_SMALLINT
* SQL_DESC_PARAMETER_TYPE SQL_IS_SMALLINT
* SQL_DESC_PRECISION SQL_IS_SMALLINT
* SQL_DESC_ROWVER SQL_IS_SMALLINT
* SQL_DESC_SCALE SQL_IS_SMALLINT
* SQL_DESC_SEARCHABLE SQL_IS_SMALLINT
* SQL_DESC_TYPE SQL_IS_SMALLINT
* SQL_DESC_UNNAMED SQL_IS_SMALLINT
* SQL_DESC_UNSIGNED SQL_IS_SMALLINT
* SQL_DESC_UPDATABLE SQL_IS_SMALLINT
*/
static esodbc_state_et check_buff(SQLSMALLINT field_id, SQLPOINTER buff,
/*writable == buff is a pointer to write into (=Get)*/
SQLINTEGER buff_len, BOOL writable)
{
switch (field_id) {
/* pointer to a character string */
case SQL_DESC_BASE_COLUMN_NAME:
case SQL_DESC_BASE_TABLE_NAME:
case SQL_DESC_CATALOG_NAME:
case SQL_DESC_LABEL:
case SQL_DESC_LITERAL_PREFIX:
case SQL_DESC_LITERAL_SUFFIX:
case SQL_DESC_LOCAL_TYPE_NAME:
case SQL_DESC_NAME:
case SQL_DESC_SCHEMA_NAME:
case SQL_DESC_TABLE_NAME:
case SQL_DESC_TYPE_NAME:
if (buff_len < 0 && buff_len != SQL_NTS) {
ERR("buffer is for a character string and its length is "
"negative (%d).", buff_len);
return SQL_STATE_HY090;
}
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)) {
ERR("trying to set field %d to a string buffer larger "
"than max allowed (%d).", field_id,
ESODBC_MAX_IDENTIFIER_LEN);
return SQL_STATE_22001;
}
return SQL_STATE_00000;
}
/* do this check after string types: they may be NULL for both funcs */
if (writable && (! buff)) {
ERR("null output buffer provided for field (%d).", field_id);
return SQL_STATE_HY090;
}
switch (field_id) {
/* pointer to a binary buffer */
case SQL_DESC_INDICATOR_PTR:
case SQL_DESC_OCTET_LENGTH_PTR:
case SQL_DESC_BIND_OFFSET_PTR:
case SQL_DESC_ROWS_PROCESSED_PTR:
case SQL_DESC_ARRAY_STATUS_PTR:
if (0 < buff_len) {
ERR("buffer is for binary buffer, but length indicator "
"is positive (%d).", buff_len);
return SQL_STATE_HY090;
}
return SQL_STATE_00000;
/* pointer to a value other than string or binary string */
case SQL_DESC_DATA_PTR:
if ((buff_len != SQL_IS_POINTER) && (buff_len < 0)) {
/* according to the spec the length "should" be its size =>
* this check might be too strict? */
ERR("buffer is for pointer, but its length indicator "
"doesn't match (%d).", buff_len);
return SQL_STATE_HY090;
}
return SQL_STATE_00000;
}
if (! writable)
/* this call is from SetDescField, so length for integer types are
* ignored */
{
return SQL_STATE_00000;
}
switch (field_id) {
/* SQL_IS_INTEGER */
case SQL_DESC_BIND_TYPE:
case SQL_DESC_AUTO_UNIQUE_VALUE:
case SQL_DESC_CASE_SENSITIVE:
case SQL_DESC_DATETIME_INTERVAL_PRECISION:
case SQL_DESC_NUM_PREC_RADIX:
case SQL_DESC_DISPLAY_SIZE:
case SQL_DESC_OCTET_LENGTH:
if (buff_len != SQL_IS_INTEGER) {
ERR("buffer is for interger, but its length indicator "
"doesn't match (%d).", buff_len);
return SQL_STATE_HY090;
}
break;
/* SQL_IS_UINTEGER */
case SQL_DESC_ARRAY_SIZE:
case SQL_DESC_LENGTH:
if (buff_len != SQL_IS_UINTEGER) {
ERR("buffer is for uint, but its length indicator "
"doesn't match (%d).", buff_len);
return SQL_STATE_HY090;
}
break;
/* SQL_IS_SMALLINT */
case SQL_DESC_ALLOC_TYPE:
case SQL_DESC_COUNT:
case SQL_DESC_CONCISE_TYPE:
case SQL_DESC_DATETIME_INTERVAL_CODE:
case SQL_DESC_FIXED_PREC_SCALE:
case SQL_DESC_NULLABLE:
case SQL_DESC_PARAMETER_TYPE:
case SQL_DESC_PRECISION:
case SQL_DESC_ROWVER:
case SQL_DESC_SCALE:
case SQL_DESC_SEARCHABLE:
case SQL_DESC_TYPE:
case SQL_DESC_UNNAMED:
case SQL_DESC_UNSIGNED:
case SQL_DESC_UPDATABLE:
if (buff_len != SQL_IS_SMALLINT) {
ERR("buffer is for short, but its length indicator "
"doesn't match (%d).", buff_len);
return SQL_STATE_HY090;
}
break;
default:
ERR("unknown field identifier: %d.", field_id);
return SQL_STATE_HY091;
}
return SQL_STATE_00000;
}
/*INDENT-OFF*/
/*
* Access permission matrix (TSV) lifted and rearanged from:
* https://docs.microsoft.com/en-us/sql/odbc/reference/syntax/sqlsetdescfield-function#fieldidentifier-argument
*
* Name ARD APD IRD IPD
* SQL_DESC_ALLOC_TYPE r r r r
* SQL_DESC_ARRAY_STATUS_PTR rw rw rw rw
* SQL_DESC_ROWS_PROCESSED_PTR rw rw
* SQL_DESC_PARAMETER_TYPE rw
* SQL_DESC_ARRAY_SIZE rw rw
* SQL_DESC_BIND_OFFSET_PTR rw rw
* SQL_DESC_BIND_TYPE rw rw
* SQL_DESC_DATA_PTR rw rw [rw]
* SQL_DESC_INDICATOR_PTR rw rw
* SQL_DESC_OCTET_LENGTH_PTR rw rw
* SQL_DESC_AUTO_UNIQUE_VALUE r
* SQL_DESC_BASE_COLUMN_NAME r
* SQL_DESC_BASE_TABLE_NAME r
* SQL_DESC_DISPLAY_SIZE r
* SQL_DESC_CATALOG_NAME r
* SQL_DESC_LABEL r
* SQL_DESC_LITERAL_PREFIX r
* SQL_DESC_LITERAL_SUFFIX r
* SQL_DESC_SCHEMA_NAME r
* SQL_DESC_SEARCHABLE r
* SQL_DESC_TABLE_NAME r
* SQL_DESC_UPDATABLE r
* SQL_DESC_CASE_SENSITIVE r r
* SQL_DESC_FIXED_PREC_SCALE r r
* SQL_DESC_LOCAL_TYPE_NAME r r
* SQL_DESC_NULLABLE r r
* SQL_DESC_ROWVER r r
* SQL_DESC_UNSIGNED r r
* SQL_DESC_TYPE_NAME r r
* SQL_DESC_NAME r rw
* SQL_DESC_UNNAMED r rw
* SQL_DESC_COUNT rw rw r rw
* SQL_DESC_CONCISE_TYPE rw rw r rw
* SQL_DESC_DATETIME_INTERVAL_CODE rw rw r rw
* SQL_DESC_DATETIME_INTERVAL_PRECISION rw rw r rw
* SQL_DESC_LENGTH rw rw r rw
* SQL_DESC_NUM_PREC_RADIX rw rw r rw
* SQL_DESC_OCTET_LENGTH rw rw r rw
* SQL_DESC_PRECISION rw rw r rw
* SQL_DESC_SCALE rw rw r rw
* SQL_DESC_TYPE rw rw r rw
*/
/*INDENT-ON*/
// TODO: individual tests just for this
/*
* Check access to record headers/fields.
* IxD must have es_type pointer set and it's value is not checked at
* header/field access time -> only this function guards against NP deref'ing.
*/
static BOOL check_access(esodbc_desc_st *desc, SQLSMALLINT field_id,
char mode /* O_RDONLY | O_RDWR */)
{
BOOL ret;
desc_type_et desc_type = desc->type;
if (desc_type == DESC_TYPE_ANON) {
BUGH(desc, "can't check permissions against ANON descryptor type.");
return FALSE;
}
assert(mode == O_RDONLY || mode == O_RDWR);
switch (field_id) {
case SQL_DESC_ALLOC_TYPE:
ret = mode == O_RDONLY;
break;
case SQL_DESC_ARRAY_STATUS_PTR:
ret = TRUE;
break;
case SQL_DESC_ROWS_PROCESSED_PTR:
ret = DESC_TYPE_IS_IMPLEMENTATION(desc_type);
break;
case SQL_DESC_PARAMETER_TYPE:
ret = desc_type == DESC_TYPE_IPD;
break;
case SQL_DESC_ARRAY_SIZE:
case SQL_DESC_BIND_OFFSET_PTR:
case SQL_DESC_BIND_TYPE:
case SQL_DESC_INDICATOR_PTR:
case SQL_DESC_OCTET_LENGTH_PTR:
ret = DESC_TYPE_IS_APPLICATION(desc_type);
break;
case SQL_DESC_DATA_PTR:
/* "The SQL_DESC_DATA_PTR field in the IPD can be set to force a
* consistency check." */
ret = desc_type != DESC_TYPE_IRD;
break;
case SQL_DESC_AUTO_UNIQUE_VALUE:
case SQL_DESC_BASE_COLUMN_NAME:
case SQL_DESC_BASE_TABLE_NAME:
case SQL_DESC_DISPLAY_SIZE:
case SQL_DESC_CATALOG_NAME:
case SQL_DESC_LABEL:
case SQL_DESC_LITERAL_PREFIX:
case SQL_DESC_LITERAL_SUFFIX:
case SQL_DESC_SCHEMA_NAME:
case SQL_DESC_SEARCHABLE:
case SQL_DESC_TABLE_NAME:
case SQL_DESC_UPDATABLE:
ret = desc_type == DESC_TYPE_ARD && mode == O_RDONLY;
break;
case SQL_DESC_CASE_SENSITIVE:
case SQL_DESC_FIXED_PREC_SCALE:
case SQL_DESC_LOCAL_TYPE_NAME:
case SQL_DESC_NULLABLE:
case SQL_DESC_ROWVER:
case SQL_DESC_UNSIGNED:
case SQL_DESC_TYPE_NAME:
ret = mode == O_RDONLY && DESC_TYPE_IS_IMPLEMENTATION(desc_type);
break;
case SQL_DESC_NAME:
case SQL_DESC_UNNAMED:
ret = desc_type == DESC_TYPE_IPD ||
(desc_type == DESC_TYPE_IRD && mode == O_RDONLY);
break;
case SQL_DESC_COUNT:
case SQL_DESC_CONCISE_TYPE:
case SQL_DESC_DATETIME_INTERVAL_CODE:
case SQL_DESC_DATETIME_INTERVAL_PRECISION:
case SQL_DESC_LENGTH:
case SQL_DESC_NUM_PREC_RADIX:
case SQL_DESC_OCTET_LENGTH:
case SQL_DESC_PRECISION:
case SQL_DESC_SCALE:
case SQL_DESC_TYPE:
switch (desc_type) {
case DESC_TYPE_ARD:
case DESC_TYPE_APD:
case DESC_TYPE_IPD:
ret = TRUE;
break;
case DESC_TYPE_IRD:
ret = mode == O_RDONLY;
break;
// just for compiler warning, ANON mode checked above;
default:
ret = FALSE;
}
break;
default:
BUGH(desc, "unknown field identifier: %d.", field_id);
ret = FALSE;
}
LOGH(desc, ret ? LOG_LEVEL_DBG : LOG_LEVEL_ERR, /*werr*/0,
"Descriptor type: %d, Field ID: %d, mode=%s => grant: %s.",
desc_type, field_id,
mode == O_RDONLY ? "read" : "read/write",
ret ? "OK" : "NOT");
return ret;
}
static void get_rec_default(SQLSMALLINT field_id, SQLINTEGER buff_len,
SQLPOINTER buff)
{
size_t sz;
switch (field_id) {
case SQL_DESC_CONCISE_TYPE:
case SQL_DESC_TYPE:
*(SQLSMALLINT *)buff = SQL_C_DEFAULT;
return;
case SQL_DESC_PARAMETER_TYPE:
*(SQLSMALLINT *)buff = SQL_PARAM_INPUT;
return;
}
/*INDENT-OFF*/
switch (buff_len) {
case SQL_IS_INTEGER: sz = sizeof(SQLINTEGER); break;
case SQL_IS_UINTEGER: sz = sizeof(SQLULEN); break;
case SQL_IS_SMALLINT: sz = sizeof(SQLSMALLINT); break;
default: sz = 0;
}
/*INDENT-ON*/
if (sz) {
memset(buff, 0, sz);
}
}
static inline void init_rec(esodbc_rec_st *rec, esodbc_desc_st *desc)
{
memset(rec, 0, sizeof(esodbc_rec_st));
rec->desc = desc;
if (rec->desc->type == DESC_TYPE_IPD) {
/* "the field is set to SQL_PARAM_INPUT by default if the IPD is not
* automatically populated by the driver" */
rec->parameter_type = SQL_PARAM_INPUT;
}
/* further init to defaults is done once the data type is set */
}
SQLRETURN update_rec_count(esodbc_desc_st *desc, SQLSMALLINT new_count)
{
esodbc_rec_st *recs;
int i;
if (new_count < 0) {
ERRH(desc, "new record count is negative (%d).", new_count);
RET_HDIAGS(desc, SQL_STATE_07009);
}
if (desc->count == new_count) {
LOGH(desc, new_count ? LOG_LEVEL_INFO : LOG_LEVEL_DBG, 0,
"new descriptor count equals old one, %d.", new_count);
return SQL_SUCCESS;
}
/* the count type in the API (SQLBindCol/Param()) is given as unsigned */
assert(ESODBC_MAX_DESC_COUNT <= SHRT_MAX);
if (ESODBC_MAX_DESC_COUNT < new_count) {
ERRH(desc, "count value (%d) higher than allowed max (%d).",
new_count, ESODBC_MAX_DESC_COUNT);
RET_HDIAGS(desc, SQL_STATE_07009);
}
if (new_count == 0) {
DBGH(desc, "freeing the array of %d elems.", desc->count);
free_desc_recs(desc);
recs = NULL;
} else {
recs = (esodbc_rec_st *)realloc(desc->recs,
sizeof(esodbc_rec_st) * new_count);
if (! recs) {
ERRN("can't (re)alloc %d records.", new_count);
RET_HDIAGS(desc, SQL_STATE_HY001);
}
if (new_count < desc->count) { /* shrinking array */
DBGH(desc, "recs array is shrinking %d -> %d.", desc->count,
new_count);
for (i = new_count - 1; i < desc->count; i ++) {
free_rec_fields(&desc->recs[i]);
}
} else { /* growing array */
DBGH(desc, "recs array is growing %d -> %d.", desc->count,
new_count);
/* init all new records */
for (i = desc->count; i < new_count; i ++) {
init_rec(&recs[i], desc);
}
}
}
desc->count = new_count;
desc->recs = recs;
return SQL_SUCCESS;
}
/*
* Returns the record with desired 1-based index.
* Grow the array if needed (desired index higher than current count).
*/
esodbc_rec_st *get_record(esodbc_desc_st *desc, SQLSMALLINT rec_no, BOOL grow)
{
assert(0 < rec_no);
if (desc->count < rec_no) {
if (! grow) {
return NULL;
} else if (! SQL_SUCCEEDED(update_rec_count(desc, rec_no))) {
return NULL;
}
}
return &desc->recs[rec_no - 1];
}
SQLSMALLINT count_bound(esodbc_desc_st *desc)
{
SQLSMALLINT i;
for (i = desc->count; 0 < i && !REC_IS_BOUND(&desc->recs[i - 1]); i --) {
;
}
return i;
}
esodbc_desc_st *getdata_set_ard(esodbc_stmt_st *stmt, esodbc_desc_st *gd_ard,
SQLUSMALLINT colno, esodbc_rec_st *recs, SQLUSMALLINT count)
{
SQLRETURN ret;
esodbc_desc_st *ard = stmt->ard;
init_desc(gd_ard, stmt, DESC_TYPE_ARD, SQL_DESC_ALLOC_USER);
ret = EsSQLSetStmtAttrW(stmt, SQL_ATTR_APP_ROW_DESC, gd_ard,
SQL_IS_POINTER);
if (! SQL_SUCCEEDED(ret)) {
stmt->ard = ard;
return NULL;
}
if (colno < count) { /* can the static recs be used? */
assert(0 < colno);
init_rec(&recs[colno - 1], gd_ard);
gd_ard->count = colno;
gd_ard->recs = recs;
}
/* else: recs will be alloc'd later when binding the column */
DBGH(stmt, "GD ARD @0x%p, records allocated %s.", gd_ard,
colno < count ? "statically" : "dynamically");
return ard;
}
void getdata_reset_ard(esodbc_stmt_st *stmt, esodbc_desc_st *ard,
SQLUSMALLINT colno, esodbc_rec_st *recs, SQLUSMALLINT count)
{
SQLRETURN ret;
if (stmt->ard->recs != recs) { /* the recs are allocated */
ret = update_rec_count(stmt->ard, 0); /* free all */
assert(SQL_SUCCEEDED(ret));
} else { /* recs are on stack */
/* only the fields of the used rec */
free_rec_fields(&recs[colno - 1]);
}
/* re-instate old ARD value */
stmt->ard = ard;
DBGH(stmt, "ARD reset @0x%p.", stmt->ard);
}
/*
* "Even when freed, an implicitly allocated descriptor remains valid, and
* SQLGetDescField can be called on its fields."
*
* "In a subsequent call to SQLGetDescField or SQLGetDescRec, the driver is
* not required to return the value that SQL_DESC_DATA_PTR was set to."
*/
SQLRETURN EsSQLGetDescFieldW(
SQLHDESC DescriptorHandle,
SQLSMALLINT RecNumber,
SQLSMALLINT FieldIdentifier,
_Out_writes_opt_(_Inexpressible_(BufferLength))
SQLPOINTER ValuePtr,
SQLINTEGER BufferLength, /* byte count, also for wchar's */
SQLINTEGER *StringLengthPtr)
{
esodbc_desc_st *desc = DSCH(DescriptorHandle);
esodbc_state_et state;
wstr_st wstr;
SQLSMALLINT word;
SQLINTEGER intgr;
esodbc_rec_st *rec;
if (! check_access(desc, FieldIdentifier, O_RDONLY)) {
int llev;
#if 0
/*
* Actually, the spec ask to return success, but just set nothing: ???
* "When an application calls SQLGetDescField to retrieve the value of
* a field that is undefined for a particular descriptor type, the
* function returns SQL_SUCCESS but the value returned for the field
* is undefined."
*/
llev = LOG_LEVEL_ERR;
state = SQL_STATE_HY091;
#else /* 0 */
llev = LOG_LEVEL_WARN;
state = SQL_STATE_01000;
#endif /* 0 */
LOGH(desc, llev, 0, "field (%d) access check failed: not defined for "
"desciptor (type: %d).", FieldIdentifier, desc->type);
RET_HDIAG(desc, state,
"field type not defined for descriptor", 0);
}
state = check_buff(FieldIdentifier, ValuePtr, BufferLength, TRUE);
if (state != SQL_STATE_00000) {
ERRH(desc, "buffer/~ length check failed (%d).", state);
RET_HDIAGS(desc, state);
}
/* header fields */
switch (FieldIdentifier) {
case SQL_DESC_ALLOC_TYPE:
*(SQLSMALLINT *)ValuePtr = desc->alloc_type;
DBGH(desc, "returning: desc alloc type: %hu.",
*(SQLSMALLINT *)ValuePtr);
return SQL_SUCCESS;
case SQL_DESC_ARRAY_SIZE:
*(SQLULEN *)ValuePtr = desc->array_size;
DBGH(desc, "returning: desc array size: %llu.",
(uint64_t)*(SQLULEN *)ValuePtr);
return SQL_SUCCESS;
case SQL_DESC_ARRAY_STATUS_PTR:
*(SQLUSMALLINT **)ValuePtr =
desc->array_status_ptr;
DBGH(desc, "returning: status array ptr: 0x%p.",
(SQLUSMALLINT *)ValuePtr);
return SQL_SUCCESS;
case SQL_DESC_BIND_OFFSET_PTR:
*(SQLLEN **)ValuePtr = desc->bind_offset_ptr;
DBGH(desc, "returning binding offset ptr: 0x%p.",
(SQLLEN *)ValuePtr);
return SQL_SUCCESS;
case SQL_DESC_BIND_TYPE:
*(SQLUINTEGER *)ValuePtr = desc->bind_type;
DBGH(desc, "returning bind type: %lu.", *(SQLUINTEGER *)ValuePtr);
return SQL_SUCCESS;
case SQL_DESC_COUNT:
*(SQLSMALLINT *)ValuePtr = desc->count;
DBGH(desc, "returning count: %hd.", *(SQLSMALLINT *)ValuePtr);
return SQL_SUCCESS;
case SQL_DESC_ROWS_PROCESSED_PTR:
*(SQLULEN **)ValuePtr = desc->rows_processed_ptr;
DBGH(desc, "returning desc rows processed ptr: 0x%p.", ValuePtr);
return SQL_SUCCESS;
}
/*
* The field is a record field -> get the record to apply the field to.
*/
if (RecNumber < 0) { /* TODO: need to check also if AxD, as per spec?? */
ERRH(desc, "negative record number provided (%d) with record field "
"(%d).", RecNumber, FieldIdentifier);
RET_HDIAG(desc, SQL_STATE_07009,
"Negative record number provided with record field", 0);
} else if (RecNumber == 0) {
ERRH(desc, "unsupported record number 0."); /* TODO: bookmarks? */
RET_HDIAG(desc, SQL_STATE_07009,
"Unsupported record number 0", 0);
} else {
/*
* "When an application calls SQLGetDescField to retrieve the value of
* a field that is defined for a particular descriptor type but that
* has no default value and has not been set yet, the function returns
* SQL_SUCCESS but the value returned for the field is undefined."
*/
rec = get_record(desc, RecNumber, FALSE);
if (! rec) {
WARNH(desc, "record #%d not yet set; returning defaults.",
RecNumber);
get_rec_default(FieldIdentifier, BufferLength, ValuePtr);
return SQL_SUCCESS;
}
DBGH(desc, "getting field %d of record #%d @ 0x%p.", FieldIdentifier,
RecNumber, rec);
}
ASSERT_IXD_HAS_ES_TYPE(rec);
/*INDENT-OFF*/
/* record fields */
switch (FieldIdentifier) {
/* <SQLPOINTER> */
case SQL_DESC_DATA_PTR:
*(SQLPOINTER *)ValuePtr = rec->data_ptr;
DBGH(desc, "returning data pointer 0x%p.", rec->data_ptr);
break;
/* <SQLWCHAR *> */
do {
case SQL_DESC_BASE_COLUMN_NAME: wstr = rec->base_column_name; break;
case SQL_DESC_BASE_TABLE_NAME: wstr = rec->base_table_name; break;
case SQL_DESC_CATALOG_NAME: wstr = rec->catalog_name; break;
case SQL_DESC_LABEL: wstr = rec->label; break;
case SQL_DESC_NAME: wstr = rec->name; break;
case SQL_DESC_SCHEMA_NAME: wstr = rec->schema_name; break;
case SQL_DESC_TABLE_NAME: wstr = rec->table_name; break;
case SQL_DESC_LITERAL_PREFIX:
wstr = rec->es_type->literal_prefix;
break;
case SQL_DESC_LITERAL_SUFFIX:
wstr = rec->es_type->literal_suffix;
break;
case SQL_DESC_LOCAL_TYPE_NAME:
wstr = rec->es_type->local_type_name;
break;
case SQL_DESC_TYPE_NAME:
wstr = rec->es_type->type_name;
break;
} while (0);
if (! StringLengthPtr) {
RET_HDIAGS(desc, SQL_STATE_HY009);
} else {
*StringLengthPtr = (SQLINTEGER)wstr.cnt;
}
if (ValuePtr) {
memcpy(ValuePtr, wstr.str, *StringLengthPtr);
}
DBGH(desc, "returning SQLWCHAR record field %d: `" LWPDL "`.",
FieldIdentifier, LWSTR(&wstr));
break;
/* <SQLLEN *> */
case SQL_DESC_INDICATOR_PTR:
*(SQLLEN **)ValuePtr = rec->indicator_ptr;
DBGH(desc, "returning indicator pointer: 0x%p.",
rec->indicator_ptr);
break;
case SQL_DESC_OCTET_LENGTH_PTR:
*(SQLLEN **)ValuePtr = rec->octet_length_ptr;
DBGH(desc, "returning octet length pointer 0x%p.",
rec->octet_length_ptr);
break;
/* <SQLLEN> */
case SQL_DESC_DISPLAY_SIZE:
*(SQLLEN *)ValuePtr = rec->es_type->display_size;
DBGH(desc, "returning display size: %d.",
rec->es_type->display_size);
break;
case SQL_DESC_OCTET_LENGTH:
*(SQLLEN *)ValuePtr = rec->octet_length;
DBGH(desc, "returning octet length: %d.", rec->octet_length);
break;
/* <SQLULEN> */
case SQL_DESC_LENGTH:
*(SQLULEN *)ValuePtr = rec->length;
DBGH(desc, "returning length: %llu.", (uint64_t)rec->length);
break;
/* <SQLSMALLINT> */
do {
case SQL_DESC_CONCISE_TYPE: word = rec->concise_type; break;
case SQL_DESC_TYPE: word = rec->type; break;
case SQL_DESC_DATETIME_INTERVAL_CODE:
word = rec->datetime_interval_code; break;
case SQL_DESC_PARAMETER_TYPE: word = rec->parameter_type; break;
case SQL_DESC_ROWVER: word = rec->rowver; break;
case SQL_DESC_UNNAMED: word = rec->unnamed; break;
case SQL_DESC_PRECISION:
if (rec->desc->type == DESC_TYPE_IRD) {
word = (SQLSMALLINT)rec->es_type->column_size;
} else {
word = rec->precision;
}
break;
case SQL_DESC_SCALE:
if (rec->desc->type == DESC_TYPE_IRD) {
word = rec->es_type->maximum_scale;
} else {
word = rec->scale;
}
break;
case SQL_DESC_FIXED_PREC_SCALE:
word = rec->es_type->fixed_prec_scale;
break;
case SQL_DESC_NULLABLE: word = rec->es_type->nullable; break;
case SQL_DESC_SEARCHABLE: word = rec->es_type->searchable; break;
case SQL_DESC_UNSIGNED: word = rec->es_type->unsigned_attribute; break;
case SQL_DESC_UPDATABLE: word = rec->updatable; break;
} while (0);
*(SQLSMALLINT *)ValuePtr = word;
DBGH(desc, "returning record field %d as %d.", FieldIdentifier,
word);
break;
/* <SQLINTEGER> */
do {
case SQL_DESC_DATETIME_INTERVAL_PRECISION:
if (DESC_TYPE_IS_IMPLEMENTATION(rec->desc->type)) {
/* not used with ES (so far), as no interval types are sup. */
intgr = rec->es_type->interval_precision;
} else {
intgr = rec->datetime_interval_precision;
}
break;
case SQL_DESC_NUM_PREC_RADIX:
if DESC_TYPE_IS_IMPLEMENTATION(rec->desc->type) {
intgr = rec->es_type->num_prec_radix;
} else {
intgr = rec->num_prec_radix;
}
break;
case SQL_DESC_AUTO_UNIQUE_VALUE:
intgr = rec->es_type->auto_unique_value;
break;
case SQL_DESC_CASE_SENSITIVE:
intgr = rec->es_type->case_sensitive;
break;
} while (0);
*(SQLINTEGER *)ValuePtr = intgr;
DBGH(desc, "returning record field %d as %d.", FieldIdentifier,
intgr);
break;
default:
ERRH(desc, "unknown FieldIdentifier: %d.", FieldIdentifier);
RET_HDIAGS(desc, SQL_STATE_HY091);
}
/*INDENT-ON*/
return SQL_SUCCESS;
}
#if 0
/*
* "The fields of an IRD have a default value only after the statement has
* been prepared or executed and the IRD has been populated, not when the
* statement handle or descriptor has been allocated. Until the IRD has been
* populated, any attempt to gain access to a field of an IRD will return an
* error."
*
* "In a subsequent call to SQLGetDescField or SQLGetDescRec, the driver is
* not required to return the value that SQL_DESC_DATA_PTR was set to."
*/
SQLRETURN EsSQLGetDescRecW(
SQLHDESC DescriptorHandle,
SQLSMALLINT RecNumber,
_Out_writes_opt_(BufferLength)
SQLWCHAR *Name,
_Out_opt_
SQLSMALLINT BufferLength,
_Out_opt_
SQLSMALLINT *StringLengthPtr,
_Out_opt_
SQLSMALLINT *TypePtr,
_Out_opt_
SQLSMALLINT *SubTypePtr,
_Out_opt_
SQLLEN *LengthPtr,
_Out_opt_
SQLSMALLINT *PrecisionPtr,
_Out_opt_
SQLSMALLINT *ScalePtr,
_Out_opt_
SQLSMALLINT *NullablePtr)
{
RET_NOT_IMPLEMENTED;
}
#endif //0
/*
* https://docs.microsoft.com/en-us/sql/odbc/reference/appendixes/data-type-identifiers-and-descriptors
*
* Note: C and SQL types have the same value for the case values below, so
* this function will work no matter the concise type (i.e. for both IxD,
* using SQL_C_<type>, and AxD, using SQL_<type>, descriptors).
* The case values are for datetime and interval data types (see also the
* comment at function end), so these values must stay in sync, since there
* are no C corresponding defines for verbose and sub-code (i.e. nothing like
* "SQL_C_DATETIME" or "SQL_C_CODE_DATE").
* The identity does not hold across the board, though (extended values, like
* BIGINTs, do differ)!
*/
void concise_to_type_code(SQLSMALLINT concise, SQLSMALLINT *type,
SQLSMALLINT *code)
{
switch (concise) {
case SQL_TYPE_DATE:
*type = SQL_DATETIME;
*code = SQL_CODE_DATE;
break;
case SQL_TYPE_TIME:
//case SQL_TYPE_TIME_WITH_TIMEZONE: //4.0
*type = SQL_DATETIME;
*code = SQL_CODE_TIME;
break;
case SQL_TYPE_TIMESTAMP:
//case SQL_TYPE_TIMESTAMP_WITH_TIMEZONE: // 4.0
*type = SQL_DATETIME;
*code = SQL_CODE_TIMESTAMP;
break;
case SQL_INTERVAL_MONTH:
*type = SQL_INTERVAL;
*code = SQL_CODE_MONTH;
break;
case SQL_INTERVAL_YEAR:
*type = SQL_INTERVAL;
*code = SQL_CODE_YEAR;
break;
case SQL_INTERVAL_YEAR_TO_MONTH:
*type = SQL_INTERVAL;
*code = SQL_CODE_YEAR_TO_MONTH;
break;
case SQL_INTERVAL_DAY:
*type = SQL_INTERVAL;
*code = SQL_CODE_DAY;
break;
case SQL_INTERVAL_HOUR:
*type = SQL_INTERVAL;
*code = SQL_CODE_HOUR;
break;
case SQL_INTERVAL_MINUTE:
*type = SQL_INTERVAL;
*code = SQL_CODE_MINUTE;
break;
case SQL_INTERVAL_SECOND:
*type = SQL_INTERVAL;
*code = SQL_CODE_SECOND;
break;
case SQL_INTERVAL_DAY_TO_HOUR:
*type = SQL_INTERVAL;
*code = SQL_CODE_DAY_TO_HOUR;
break;
case SQL_INTERVAL_DAY_TO_MINUTE:
*type = SQL_INTERVAL;
*code = SQL_CODE_DAY_TO_MINUTE;
break;
case SQL_INTERVAL_DAY_TO_SECOND:
*type = SQL_INTERVAL;
*code = SQL_CODE_DAY_TO_SECOND;
break;
case SQL_INTERVAL_HOUR_TO_MINUTE:
*type = SQL_INTERVAL;
*code = SQL_CODE_HOUR_TO_MINUTE;
break;
case SQL_INTERVAL_HOUR_TO_SECOND:
*type = SQL_INTERVAL;
*code = SQL_CODE_HOUR_TO_SECOND;
break;
case SQL_INTERVAL_MINUTE_TO_SECOND:
*type = SQL_INTERVAL;
*code = SQL_CODE_MINUTE_TO_SECOND;
break;
default:
/* "For all data types except datetime and interval data types,
* the verbose type identifier is the same as the concise type
* identifier and the value in SQL_DESC_DATETIME_INTERVAL_CODE is
* equal to 0." */
*type = concise;
*code = 0;
}
}
/*
* From table in § SQL_DESC_TYPE of:
* https://docs.microsoft.com/en-us/sql/odbc/reference/syntax/sqlsetdescfield-function##record-fields
*/
static void set_defaults_from_meta_type(esodbc_rec_st *rec)
{
DBGH(rec->desc, "(re)setting record@0x%p length/precision/scale to "
"defaults.", rec);
switch (rec->meta_type) {
case METATYPE_STRING:
rec->length = ESODBC_DEF_STRING_LENGTH;
rec->precision = ESODBC_DEF_STRING_PRECISION;
break;
case METATYPE_DATE_TIME:
if (rec->datetime_interval_code == SQL_CODE_DATE ||
rec->datetime_interval_code == SQL_CODE_TIME) {
rec->precision = ESODBC_DEF_DATETIME_PRECISION;
} else if (rec->datetime_interval_code == SQL_CODE_TIMESTAMP) {
rec->precision = ESODBC_DEF_TIMESTAMP_PRECISION;
}
break;
case METATYPE_INTERVAL_WSEC:
rec->precision = ESODBC_DEF_IVL_WS_PRECISION;
/* no break */
case METATYPE_INTERVAL_WOSEC:
rec->datetime_interval_precision = ESODBC_DEF_IVL_WOS_DT_PREC;
break;
case METATYPE_EXACT_NUMERIC:
if (rec->concise_type == SQL_DECIMAL ||
rec->concise_type == SQL_NUMERIC) { /* == SQL_C_NUMERIC */
rec->scale = ESODBC_DEF_DECNUM_SCALE;
rec->precision = ESODBC_DEF_DECNUM_PRECISION;
}
break;
case METATYPE_FLOAT_NUMERIC:
if (rec->concise_type == SQL_FLOAT) { /* == SQL_C_FLOAT */
/* "implementation-defined default precision for SQL_FLOAT" */
rec->precision = ESODBC_DEF_FLOAT_PRECISION;
}
break;
case METATYPE_MAX: /* SQL_C_DEFAULT, ESODBC_SQL_NULL */
assert((DESC_TYPE_IS_APPLICATION(rec->desc->type) &&
rec->concise_type == SQL_C_DEFAULT) ||
(DESC_TYPE_IS_IMPLEMENTATION(rec->desc->type) &&
rec->concise_type == ESODBC_SQL_NULL));
DBGH(rec->desc, "max meta type (C default / SQL NULL): "
"can't set defaults");
break;
}
}
static esodbc_metatype_et sqltype_to_meta(SQLSMALLINT concise)
{
switch(concise) {
/* character */
case SQL_CHAR:
case SQL_VARCHAR:
case SQL_LONGVARCHAR:
case SQL_WCHAR:
case SQL_WVARCHAR:
case SQL_WLONGVARCHAR:
return METATYPE_STRING;
/* binary */
case SQL_BINARY:
case SQL_VARBINARY:
case SQL_LONGVARBINARY:
return METATYPE_BIN;
/* numeric exact */
case SQL_DECIMAL:
case SQL_INTEGER:
case SQL_NUMERIC:
case SQL_SMALLINT:
case SQL_TINYINT:
case SQL_BIGINT:
return METATYPE_EXACT_NUMERIC;
/* numeric floating */
case SQL_REAL:
case SQL_FLOAT:
case SQL_DOUBLE:
return METATYPE_FLOAT_NUMERIC;
/* datetime (note: SQL_DATETIME is verbose, not concise) */
/* SQL_DATE, SQL_TIME, SQL_TIMESTAMP:
* "In ODBC 3.x, the identifiers for date, time, and timestamp SQL
* data types have changed from SQL_DATE, SQL_TIME, and SQL_TIMESTAMP
* to SQL_TYPE_DATE, SQL_TYPE_TIME, and SQL_TYPE_TIMESTAMP".
* "Because of how the ODBC 3.x Driver Manager performs mapping of the
* date, time, and timestamp data types, ODBC 3.x drivers need only
* recognize" the new defines.
* */
case SQL_TYPE_DATE:
case SQL_TYPE_TIME:
case SQL_TYPE_TIMESTAMP:
// case SQL_TYPE_UTCDATETIME:
// case SQL_TYPE_UTCTIME:
return METATYPE_DATE_TIME;
/* interval (note: SQL_INTERVAL is verbose, not concise) */
case SQL_INTERVAL_MONTH:
case SQL_INTERVAL_YEAR:
case SQL_INTERVAL_YEAR_TO_MONTH:
case SQL_INTERVAL_DAY:
case SQL_INTERVAL_HOUR:
case SQL_INTERVAL_MINUTE:
case SQL_INTERVAL_DAY_TO_HOUR:
case SQL_INTERVAL_DAY_TO_MINUTE:
case SQL_INTERVAL_HOUR_TO_MINUTE:
return METATYPE_INTERVAL_WOSEC;
case SQL_INTERVAL_SECOND:
case SQL_INTERVAL_DAY_TO_SECOND:
case SQL_INTERVAL_HOUR_TO_SECOND:
case SQL_INTERVAL_MINUTE_TO_SECOND:
return METATYPE_INTERVAL_WSEC;
case SQL_GUID:
return METATYPE_UID;
/* bool */
case SQL_BIT:
/* ES/SQL types */
case ESODBC_SQL_BOOLEAN:
return METATYPE_BIT;
case ESODBC_SQL_NULL:
return METATYPE_MAX;
/* TODO: how to handle these?? */
case ESODBC_SQL_UNSUPPORTED:
case ESODBC_SQL_OBJECT: /* == ESODBC_SQL_NESTED */
ERR("ES/SQL types 'UNSUPPORTED'/'OBJECT'/'NESTED' "
"not allowed in DML (used: %hd).", concise);
}
ERR("unknown meta type for concise SQL type %d.", concise);
return METATYPE_UNKNOWN;
}
static esodbc_metatype_et sqlctype_to_meta(SQLSMALLINT concise)
{
switch (concise) {
/* character */
case SQL_C_CHAR:
case SQL_C_WCHAR:
// case SQL_C_TCHAR:
return METATYPE_STRING;
/* binary */
case SQL_C_BINARY:
#if (ODBCVER < 0x0300)
case SQL_C_BOOKMARK:
#endif /* ODBCVER [12].x */
//case SQL_C_VARBOOKMARK:
return METATYPE_BIN;
/* numeric exact */
case SQL_C_SHORT:
case SQL_C_SSHORT:
case SQL_C_USHORT:
case SQL_C_LONG:
case SQL_C_SLONG:
case SQL_C_ULONG:
case SQL_C_TINYINT:
case SQL_C_STINYINT:
case SQL_C_UTINYINT:
case SQL_C_SBIGINT:
case SQL_C_UBIGINT:
case SQL_C_NUMERIC:
return METATYPE_EXACT_NUMERIC;
/* numeric floating */
case SQL_C_FLOAT:
case SQL_C_DOUBLE:
return METATYPE_FLOAT_NUMERIC;
/* datetime */
/* SQL_C_DATE, SQL_C_TIME, SQL_C_TIMESTAMP: see sqltype_to_meta() */
case SQL_C_TYPE_DATE:
case SQL_C_TYPE_TIME:
case SQL_C_TYPE_TIMESTAMP:
// case SQL_C_TYPE_TIME_WITH_TIMEZONE:
// case SQL_C_TYPE_TIMESTAMP_WITH_TIMEZONE:
return METATYPE_DATE_TIME;
/* interval */
case SQL_C_INTERVAL_DAY:
case SQL_C_INTERVAL_HOUR:
case SQL_C_INTERVAL_MINUTE:
case SQL_C_INTERVAL_DAY_TO_HOUR:
case SQL_C_INTERVAL_DAY_TO_MINUTE:
case SQL_C_INTERVAL_HOUR_TO_MINUTE:
case SQL_C_INTERVAL_MONTH:
case SQL_C_INTERVAL_YEAR:
case SQL_C_INTERVAL_YEAR_TO_MONTH:
return METATYPE_INTERVAL_WOSEC;
case SQL_C_INTERVAL_SECOND:
case SQL_C_INTERVAL_DAY_TO_SECOND:
case SQL_C_INTERVAL_HOUR_TO_SECOND:
case SQL_C_INTERVAL_MINUTE_TO_SECOND:
return METATYPE_INTERVAL_WSEC;
/* boolean */
case SQL_C_BIT:
return METATYPE_BIT;
case SQL_C_GUID:
return METATYPE_UID;
case SQL_C_DEFAULT:
return METATYPE_MAX;
}
ERR("unknown meta type for concise C SQL type %d.", concise);
return METATYPE_UNKNOWN;
}
static SQLSMALLINT sqlctype_to_es(SQLSMALLINT c_concise)
{
switch (c_concise) {
case SQL_C_CHAR:
case SQL_C_WCHAR:
return ES_TEXT_TO_SQL;
case SQL_C_SHORT:
case SQL_C_SSHORT:
case SQL_C_USHORT:
return ES_SHORT_TO_SQL;
case SQL_C_LONG:
case SQL_C_SLONG:
case SQL_C_ULONG:
return ES_INTEGER_TO_SQL;
case SQL_C_FLOAT:
return ES_FLOAT_TO_SQL;
case SQL_C_DOUBLE:
return ES_DOUBLE_TO_SQL;
case SQL_C_BIT:
case SQL_C_TINYINT:
case SQL_C_STINYINT:
case SQL_C_UTINYINT:
return ES_BYTE_TO_SQL;
case SQL_C_SBIGINT:
case SQL_C_UBIGINT:
return ES_LONG_TO_SQL;
case SQL_C_BINARY:
return ES_BINARY_TO_SQL;
case SQL_C_TYPE_DATE:
case SQL_C_TYPE_TIME:
case SQL_C_TYPE_TIMESTAMP:
case SQL_C_NUMERIC:
case SQL_C_GUID:
case SQL_C_INTERVAL_DAY:
case SQL_C_INTERVAL_HOUR:
case SQL_C_INTERVAL_MINUTE:
case SQL_C_INTERVAL_SECOND:
case SQL_C_INTERVAL_DAY_TO_HOUR:
case SQL_C_INTERVAL_DAY_TO_MINUTE:
case SQL_C_INTERVAL_DAY_TO_SECOND:
case SQL_C_INTERVAL_HOUR_TO_MINUTE:
case SQL_C_INTERVAL_HOUR_TO_SECOND:
case SQL_C_INTERVAL_MINUTE_TO_SECOND:
case SQL_C_INTERVAL_MONTH:
case SQL_C_INTERVAL_YEAR:
case SQL_C_INTERVAL_YEAR_TO_MONTH:
/* C and SQL have the same ID mapping */
return c_concise;
default:
case SQL_C_DEFAULT:
/* case SQL_C_BOOKMARK: = SQL_C_UBIGINT, SQL_C_ULONG */
/* case SQL_C_VARBOOKMARK: = SQL_C_BINARY */
case SQL_C_DATE:
case SQL_C_TIME:
case SQL_C_TIMESTAMP:
/* case SQL_C_TYPE_TIME_WITH_TIMEZONE:
case SQL_C_TYPE_TIMESTAMP_WITH_TIMEZONE: */
break;
}
WARN("no C to ES SQL mapping exists for C type %hd.", c_concise);
return 0;
}
/*
* https://docs.microsoft.com/en-us/sql/odbc/reference/syntax/sqlsetdescrec-function#consistency-checks
*/
static BOOL consistency_check(esodbc_rec_st *rec)
{
SQLSMALLINT type, code, concise_type;
esodbc_desc_st *desc = rec->desc;
SQLINTEGER max_prec, column_size;
esodbc_estype_st *es_type;
esodbc_dbc_st *dbc;
/* validity of C / SQL datatypes is checked when setting the meta_type */
assert(METATYPE_UNKNOWN <= rec->meta_type &&
rec->meta_type <= METATYPE_MAX);
if (! rec->meta_type) {
ERRH(desc, "no meta type set.", rec);
return FALSE;
} /* else: concise_type had been set */
concise_to_type_code(rec->concise_type, &type, &code);
if (rec->type != type || rec->datetime_interval_code != code) {
ERRH(desc, "inconsistent types found: concise: %d, verbose: %d, "
"code: %d.", rec->concise_type, rec->type,
rec->datetime_interval_code);
return FALSE;
}
switch (rec->meta_type) {
case METATYPE_EXACT_NUMERIC:
if (rec->concise_type != SQL_NUMERIC && /* == _C_ type */
rec->concise_type != SQL_DECIMAL) { /* == _C_ type */
if (rec->scale) {
ERRH(desc, "fixed numeric type %hd having non-zero scale.",
rec->concise_type, rec->scale);
return FALSE;
}
} else {
break; /* TODO: check NUMERIC,DECIMAL bounds */
}
/* no break */
case METATYPE_FLOAT_NUMERIC:
if (rec->es_type) {
column_size = rec->es_type->column_size;
} else {
dbc = HDRH(HDRH(desc)->stmt)->dbc;
if (rec->concise_type == SQL_FLOAT) {
assert(desc->type == DESC_TYPE_IPD);
column_size = dbc->max_float_type->column_size;
} else {
if (DESC_TYPE_IS_APPLICATION(desc->type)) {
concise_type = sqlctype_to_es(rec->concise_type);
assert(concise_type); /* all numerics must be mapped */
} else {
concise_type = rec->concise_type;
}
if (! dbc->no_types) {
/* bootstraping mode still, loading types */
break;
}
es_type = lookup_es_type(dbc, concise_type, 0);
if (! es_type) {
BUGH(desc, "type lookup failed for concise type: %hd,"
" desc type: %d", concise_type, desc->type);
return FALSE;
}
column_size = es_type->column_size;
}
}
if (rec->precision < 0 || column_size < rec->precision) {
ERRH(desc, "precision (%hd) out of bounds [0, %ld].",
rec->precision, column_size);
return FALSE;
}
break;
/* check SQL_DESC_PRECISION field */
/* "a time or timestamp data type" */
case METATYPE_DATE_TIME:
if (rec->concise_type == SQL_TYPE_DATE) {
break;
}
/* "an interval type with a seconds component" */
case METATYPE_INTERVAL_WSEC:
/* "or one of the interval data types with a time component" */
case METATYPE_INTERVAL_WOSEC:
if (SQL_INTERVAL_MONTH < rec->concise_type &&
rec->concise_type != SQL_INTERVAL_YEAR_TO_MONTH) {
if (rec->precision < 0 ||
ESODBC_MAX_SEC_PRECISION < rec->precision) {
ERRH(desc, "precision (%hd) out of bounds [0, %d].",
rec->precision, ESODBC_MAX_SEC_PRECISION);
return FALSE;
}
}
if (rec->meta_type == METATYPE_DATE_TIME) {
break;
}
/* check SQL_DESC_DATETIME_INTERVAL_PRECISION */
switch (rec->concise_type) {
case SQL_INTERVAL_YEAR:
max_prec = ESODBC_MAX_IVL_YEAR_LEAD_PREC;
break;
case SQL_INTERVAL_MONTH:
max_prec = ESODBC_MAX_IVL_MONTH_LEAD_PREC;
break;
case SQL_INTERVAL_DAY:
max_prec = ESODBC_MAX_IVL_DAY_LEAD_PREC;
break;
case SQL_INTERVAL_HOUR:
max_prec = ESODBC_MAX_IVL_HOUR_LEAD_PREC;
break;
case SQL_INTERVAL_MINUTE:
max_prec = ESODBC_MAX_IVL_MINUTE_LEAD_PREC;
break;
case SQL_INTERVAL_SECOND:
max_prec = ESODBC_MAX_IVL_SECOND_LEAD_PREC;
break;
default:
max_prec = -1;
}
if (0 < max_prec &&
(rec->datetime_interval_precision < 0 ||
max_prec < rec->datetime_interval_precision)) {
ERRH(desc, "datetime_interval_precision (%hd) out of bounds "
"[0, %d].", rec->datetime_interval_precision, max_prec);
return FALSE;
}
break;
}
return TRUE;
}
esodbc_metatype_et concise_to_meta(SQLSMALLINT concise_type,
desc_type_et desc_type)
{
switch (desc_type) {
case DESC_TYPE_ARD:
case DESC_TYPE_APD:
return sqlctype_to_meta(concise_type);
case DESC_TYPE_IRD:
case DESC_TYPE_IPD:
return sqltype_to_meta(concise_type);
default:
BUG("can't use anonymous record type");
}
return METATYPE_UNKNOWN;
}
/*
* "If an application calls SQLSetDescField to set any field other than
* SQL_DESC_COUNT or the deferred fields SQL_DESC_DATA_PTR,
* SQL_DESC_OCTET_LENGTH_PTR, or SQL_DESC_INDICATOR_PTR, the record becomes
* unbound."
*
* "When SQLSetDescField is called to set a header field, the RecNumber
* argument is ignored."
*
* The "driver sets data type attribute fields to the appropriate default
* values for the data type" (once either of SQL_DESC_TYPE,
* SQL_DESC_CONCISE_TYPE, or SQL_DESC_DATETIME_INTERVAL_CODE are set).
*
* The "application can set SQL_DESC_DATA_PTR. This prompts a consistency
* check of descriptor fields."
*
* "If the application changes the data type or attributes after setting the
* SQL_DESC_DATA_PTR field, the driver sets SQL_DESC_DATA_PTR to a null
* pointer, unbinding the record." &&
* "If TargetValuePtr is a null pointer, the driver unbinds the data buffer
* for the column. [...] An application can unbind the data buffer for a
* column but still have a length/indicator buffer bound for the column, if
* the TargetValuePtr argument in the call to SQLBindCol is a null pointer but
* the StrLen_or_IndPtr argument is a valid value."
*
* "The only way to unbind a bookmark column is to set the SQL_DESC_DATA_PTR
* field to a null pointer."
*/
SQLRETURN EsSQLSetDescFieldW(
SQLHDESC DescriptorHandle,
SQLSMALLINT RecNumber,
SQLSMALLINT FieldIdentifier,
SQLPOINTER ValuePtr,
SQLINTEGER BufferLength)
{
esodbc_desc_st *desc = DSCH(DescriptorHandle);
esodbc_state_et state;
esodbc_rec_st *rec;
wstr_st *wstrp;
SQLSMALLINT *wordp;
SQLINTEGER *intp;
SQLSMALLINT count, type, chk_type, chk_code;
SQLULEN ulen;
SQLLEN slen;
size_t wlen;
if (! check_access(desc, FieldIdentifier, O_RDWR)) {
/* "The SQL_DESC_DATA_PTR field of an IPD is not normally set;
* however, an application can do so to force a consistency check of
* IPD fields."
* TODO: the above won't work with the generic check implementation:
* is it worth hacking an exception here? (since IPD/.data_ptr is
* marked RO) */
ERRH(desc, "field access check failed: not defined or RO for "
"desciptor.");
RET_HDIAGS(desc, SQL_STATE_HY091);
}
state = check_buff(FieldIdentifier, ValuePtr, BufferLength, FALSE);
if (state != SQL_STATE_00000) {
ERRH(desc, "buffer/~ length check failed (%d).", state);
RET_HDIAGS(desc, state);
}
/* header fields */
switch (FieldIdentifier) {
case SQL_DESC_ARRAY_SIZE:
ulen = (SQLULEN)(uintptr_t)ValuePtr;
DBGH(desc, "setting desc array size to: %llu.", (uint64_t)ulen);
if (DESC_TYPE_IS_RECORD(desc->type)) {
if (ESODBC_MAX_ROW_ARRAY_SIZE < ulen) {
WARNH(desc, "provided desc array size (%llu) larger than "
"allowed max (%hu) -- set value adjusted to max.",
(uint64_t)ulen, ESODBC_MAX_ROW_ARRAY_SIZE);
desc->array_size = ESODBC_MAX_ROW_ARRAY_SIZE;
RET_HDIAGS(desc, SQL_STATE_01S02);
} else if (ulen < 1) {
ERRH(desc, "can't set the array size to less than 1.");
RET_HDIAGS(desc, SQL_STATE_HY092);
}
} else { /* IS_PARAMETER */
/* no support for param arrays (yet) TODO */
if (1 < ulen) {
ERRH(desc, "no support for arrays of parameters.");
RET_HDIAG(desc, SQL_STATE_HYC00,
"Parameter arrays not implemented", 0);
}
}
desc->array_size = ulen;
return SQL_SUCCESS;
case SQL_DESC_ARRAY_STATUS_PTR:
DBGH(desc, "setting desc array status ptr to: 0x%p.", ValuePtr);
/* deferred */
desc->array_status_ptr = (SQLUSMALLINT *)ValuePtr;
return SQL_SUCCESS;
case SQL_DESC_BIND_OFFSET_PTR:
DBGH(desc, "setting binding offset ptr to: 0x%p.", ValuePtr);
/* deferred */
desc->bind_offset_ptr = (SQLLEN *)ValuePtr;
return SQL_SUCCESS;
case SQL_DESC_BIND_TYPE:
DBGH(desc, "setting bind type to: %lu.",
(SQLUINTEGER)(uintptr_t)ValuePtr);
desc->bind_type = (SQLUINTEGER)(uintptr_t)ValuePtr;
return SQL_SUCCESS;
/*
* "Specifies the 1-based index of the highest-numbered record that
* contains data."
* "Is not a count of all data columns or of all parameters that are
* bound, but the number of the highest-numbered record."
*
* "If the highest-numbered column or parameter is unbound, then
* SQL_DESC_COUNT is changed to the number of the next
* highest-numbered column or parameter. If a column or a parameter
* with a number that is less than the number of the highest-numbered
* column is unbound, SQL_DESC_COUNT is not changed. If additional
* columns or parameters are bound with numbers greater than the
* highest-numbered record that contains data, the driver
* automatically increases the value in the SQL_DESC_COUNT field."
*
* "If the value in SQL_DESC_COUNT is explicitly decreased, all
* records with numbers greater than the new value in SQL_DESC_COUNT
* are effectively removed. If the value in SQL_DESC_COUNT is
* explicitly set to 0 and the field is in an ARD, all data buffers
* except a bound bookmark column are released."
*/
case SQL_DESC_COUNT:
return update_rec_count(desc, (SQLSMALLINT)(intptr_t)ValuePtr);
case SQL_DESC_ROWS_PROCESSED_PTR:
DBGH(desc, "setting desc rows processed ptr to: 0x%p.", ValuePtr);
desc->rows_processed_ptr = (SQLULEN *)ValuePtr;
return SQL_SUCCESS;
}
/*
* The field is a record field -> get the record to apply the field to.
*/
if (RecNumber < 0) { /* TODO: need to check also if AxD, as per spec?? */
ERRH(desc, "negative record number provided (%d) with record field "
"(%d).", RecNumber, FieldIdentifier);
RET_HDIAG(desc, SQL_STATE_07009,
"Negative record number provided with record field", 0);
} else if (RecNumber == 0) {
ERRH(desc, "unsupported record number 0."); /* TODO: bookmarks? */
RET_HDIAG(desc, SQL_STATE_07009,
"Unsupported record number 0", 0);
} else { /* apparently one can set a record before the count is set */
rec = get_record(desc, RecNumber, TRUE);
if (! rec) {
ERRH(desc, "can't get record with number %d.", RecNumber);
RET_STATE(desc->hdr.diag.state);
}
DBGH(desc, "setting field %d of record #%d @ 0x%p.", FieldIdentifier,
RecNumber, rec);
}
/*
* "If the application changes the data type or attributes after setting
* the SQL_DESC_DATA_PTR field, the driver sets SQL_DESC_DATA_PTR to a
* null pointer, unbinding the record."
*
* NOTE: the record can actually still be bound by the length/indicator
* buffer(s), so the above "binding" definition is incomplete.
*/
if (FieldIdentifier != SQL_DESC_DATA_PTR) {
DBGH(desc, "attribute to set is different than data ptr (%d) => "
"unbinding data buffer (was 0x%p).", SQL_DESC_DATA_PTR,
rec->data_ptr);
rec->data_ptr = NULL;
}
/*INDENT-OFF*/
/* record fields */
switch (FieldIdentifier) {
/* "For datetime and interval data types, however, a verbose type
* (SQL_DATETIME or SQL_INTERVAL) is stored in SQL_DESC_TYPE, a
* concise type is stored in SQL_DESC_CONCISE_TYPE, and a subcode for
* each concise type is stored in SQL_DESC_DATETIME_INTERVAL_CODE." */
case SQL_DESC_TYPE:
type = (SQLSMALLINT)(intptr_t)ValuePtr;
DBGH(desc, "setting type of rec@0x%p to %d.", rec, type);
/* Note: SQL_[C_]DATE == SQL_DATETIME (== 9) =>
* 1. one needs to always use SQL_DESC_CONCISE_TYPE for setting
* the types from within the driver (binding cols, params):
* "SQL_DESC_CONCISE_TYPE can be set by a call to SQLBindCol or
* SQLBindParameter, or SQLSetDescField. SQL_DESC_TYPE can be set
* by a call to SQLSetDescField or SQLSetDescRec."
* 2. SQL_DESC_TYPE can only be used when setting the type record
* fields (.type, .concise_type, datetime_interval_code)
* individually. */
if (type == SQL_DATETIME || type == SQL_INTERVAL) {
/* "When the application sets the SQL_DESC_TYPE field, the
* driver checks that other fields that specify the type are
* valid and consistent." */
/* setting the verbose type only */
concise_to_type_code(rec->concise_type, &chk_type, &chk_code);
if (chk_type != type ||
chk_code != rec->datetime_interval_code ||
(! rec->datetime_interval_code)) {
ERRH(desc, "type fields found inconsistent when setting "
"the type to %hd: concise: %hd, datetime_code: %hd.",
(SQLSMALLINT)(intptr_t)ValuePtr,
rec->concise_type, rec->datetime_interval_code);
RET_HDIAGS(desc, SQL_STATE_HY021);
} else {
rec->type = type;
}
break;
}
/* no break! */
case SQL_DESC_CONCISE_TYPE:
DBGH(desc, "setting concise type of rec 0x%p to %d.", rec,
(SQLSMALLINT)(intptr_t)ValuePtr);
rec->concise_type = (SQLSMALLINT)(intptr_t)ValuePtr;
concise_to_type_code(rec->concise_type, &rec->type,
&rec->datetime_interval_code);
rec->meta_type = concise_to_meta(rec->concise_type, desc->type);
if (rec->meta_type == METATYPE_UNKNOWN) {
ERRH(desc, "REC@0x%p: incorrect concise type %d for rec #%d.",
rec, rec->concise_type, RecNumber);
RET_HDIAGS(desc, DESC_TYPE_IS_APPLICATION(desc->type) ?
SQL_STATE_HY003 : SQL_STATE_HY004);
}
/* "When the SQL_DESC_TYPE or SQL_DESC_CONCISE_TYPE field is set
* for some data types, the SQL_DESC_DATETIME_INTERVAL_PRECISION,
* SQL_DESC_LENGTH, SQL_DESC_PRECISION, and SQL_DESC_SCALE fields
* are automatically set to default values". */
set_defaults_from_meta_type(rec);
DBGH(desc, "REC@0x%p types: concise: %d, verbose: %d, code: %d.",
rec, rec->concise_type, rec->type,
rec->datetime_interval_code);
break;
case SQL_DESC_DATA_PTR:
DBGH(desc, "setting data ptr to 0x%p of type %d.", ValuePtr,
BufferLength);
/* deferred */
rec->data_ptr = ValuePtr;
if (rec->data_ptr) {
/* "A consistency check is performed by the driver
* automatically whenever an application sets the
* SQL_DESC_DATA_PTR field of an APD, ARD, or IPD."
* "The SQL_DESC_DATA_PTR field of an IPD is not normally set;
* however, an application can do so to force a consistency
* check of IPD fields. A consistency check cannot be
* performed on an IRD." */
if ((desc->type != DESC_TYPE_IRD) &&
(! consistency_check(rec))) {
ERRH(desc, "consistency check failed on rec@0x%p.", rec);
RET_HDIAGS(desc, SQL_STATE_HY021);
} else {
DBGH(desc, "rec@0x%p: bound data ptr@0x%p.", rec,
rec->data_ptr);
}
} else {
/* "If the highest-numbered column or parameter is unbound,
* then SQL_DESC_COUNT is changed to the number of the next
* highest-numbered column or parameter. " */
if (DESC_TYPE_IS_APPLICATION(desc->type) &&
/* see function-top comments on when to unbind */
(! REC_IS_BOUND(rec))) {
DBGH(desc, "rec 0x%p of desc type %d unbound.", rec,
desc->type);
if (RecNumber == desc->count) {
count = count_bound(desc);
/* worst case: trying to unbind a not-yet-bound rec */
if (count != desc->count) {
DBGH(desc, "adjusting rec count from %hd to %hd.",
desc->count, count);
return update_rec_count(desc, count);
}
}
}
}
break;
case SQL_DESC_NAME:
WARNH(desc, "stored procedure params (to set to `"LWPD"`) not "
"supported.", ValuePtr ? (SQLWCHAR *)ValuePtr : TWS_NULL);
RET_HDIAG(desc, SQL_STATE_HYC00,
"stored procedure params not supported", 0);
/* <SQLWCHAR *> */
do {
case SQL_DESC_BASE_COLUMN_NAME: wstrp = &rec->base_column_name; break;
case SQL_DESC_BASE_TABLE_NAME: wstrp = &rec->base_table_name; break;
case SQL_DESC_CATALOG_NAME: wstrp = &rec->catalog_name; break;
case SQL_DESC_LABEL: wstrp = &rec->label; break;
/* R/O fields: literal_prefix/_suffix, local_type_name, type_name */
case SQL_DESC_SCHEMA_NAME: wstrp = &rec->schema_name; break;
case SQL_DESC_TABLE_NAME: wstrp = &rec->table_name; break;
} while (0);
if (BufferLength == SQL_NTS) {
wlen = ValuePtr ? wcslen((SQLWCHAR *)ValuePtr) : 0;
} else {
wlen = BufferLength;
}
DBGH(desc, "setting SQLWCHAR field %d to `" LWPDL "`(@0x%p).",
FieldIdentifier, wlen, ValuePtr, wlen, ValuePtr);
if (wstrp->str) {
DBGH(desc, "freeing previously allocated value for field %d "
"(`" LWPDL "`).", FieldIdentifier, LWSTR(wstrp));
free(wstrp->str);
wstrp->str = NULL;
wstrp->cnt = 0;
}
if (! ValuePtr) {
DBGH(desc, "field %d reset to NULL.", FieldIdentifier);
break;
}
if (! (wstrp->str = (SQLWCHAR *)malloc((wlen + /*0-term*/1)
* sizeof(SQLWCHAR)))) {
ERRH(desc, "failed to alloc w-string buffer of len %zd.",
wlen + 1);
RET_HDIAGS(desc, SQL_STATE_HY001);
}
memcpy(wstrp->str, ValuePtr, wlen * sizeof(SQLWCHAR));
wstrp->str[wlen] = 0;
wstrp->cnt = wlen;
break;
/* <SQLLEN *>, deferred */
case SQL_DESC_INDICATOR_PTR:
DBGH(desc, "setting indicator pointer to 0x%p.", ValuePtr);
rec->indicator_ptr = (SQLLEN *)ValuePtr;
break;
case SQL_DESC_OCTET_LENGTH_PTR:
DBGH(desc, "setting octet length pointer to 0x%p.", ValuePtr);
rec->octet_length_ptr = (SQLLEN *)ValuePtr;
break;
/* <SQLLEN> */
/* R/O fields: display_size */
case SQL_DESC_OCTET_LENGTH:
slen = (SQLLEN)ValuePtr;
DBGH(desc, "setting octet length: %lld.", (int64_t)slen);
/* rec field's type is signed; a negative can be dangerous */
if (slen < 0) {
WARNH(desc, "negative octet length provided (%lld)",
(int64_t)slen);
/* no eror returned: in non-str/binary, it is to be ignorred */
}
rec->octet_length = slen;
break;
/* <SQLULEN> */
case SQL_DESC_LENGTH:
DBGH(desc, "setting length: %llu.", (uint64_t)ValuePtr);
rec->length = (SQLULEN)ValuePtr;
break;
/* <SQLSMALLINT> */
do {
case SQL_DESC_DATETIME_INTERVAL_CODE:
wordp = &rec->datetime_interval_code; break;
case SQL_DESC_PARAMETER_TYPE: wordp = &rec->parameter_type; break;
case SQL_DESC_PRECISION: wordp = &rec->precision; break;
case SQL_DESC_ROWVER: wordp = &rec->rowver; break;
case SQL_DESC_SCALE: wordp = &rec->scale; break;
case SQL_DESC_UNNAMED:
/* only driver can set this value */
if ((SQLSMALLINT)(intptr_t)ValuePtr == SQL_NAMED) {
ERRH(desc, "only the driver can set %d field to 'SQL_NAMED'.",
FieldIdentifier);
RET_HDIAGS(desc, SQL_STATE_HY091);
}
wordp = &rec->unnamed;
break;
/* R/O field: fixed_prec_scale, nullable, searchable, unsigned */
case SQL_DESC_UPDATABLE: wordp = &rec->updatable; break;
} while (0);
DBGH(desc, "setting record field %d to %d.", FieldIdentifier,
(SQLSMALLINT)(intptr_t)ValuePtr);
*wordp = (SQLSMALLINT)(intptr_t)ValuePtr;
break;
/* <SQLINTEGER> */
do {
/* R/O field: auto_unique_value, case_sensitive */
case SQL_DESC_DATETIME_INTERVAL_PRECISION:
intp = &rec->datetime_interval_precision;
break;
case SQL_DESC_NUM_PREC_RADIX:
intp = &rec->num_prec_radix;
break;
} while (0);
DBGH(desc, "returning record field %d as %d.", FieldIdentifier,
(SQLINTEGER)(intptr_t)ValuePtr);
*intp = (SQLINTEGER)(intptr_t)ValuePtr;
break;
default:
ERRH(desc, "unknown FieldIdentifier: %d.", FieldIdentifier);
RET_HDIAGS(desc, SQL_STATE_HY091);
}
/*INDENT-ON*/
return SQL_SUCCESS;
}
/* vim: set noet fenc=utf-8 ff=dos sts=0 sw=4 ts=4 : */