From 639a333366b336e548e59474258d57fd10b391c3 Mon Sep 17 00:00:00 2001 From: Bogdan Pintea Date: Fri, 9 Feb 2018 20:40:02 +0100 Subject: [PATCH] support for SQLGetColAttributes and SQLDescribeCol - functions just use the IRD values; these will need to be populated properly from the result set. - change tracing handling of integer pointers -- added descriptors for all SQL integer types; - changed handling of SQL_DESC_MAX_LENGTH attribute; - added a metatype to records, to distinguish the numerics, intervals etc. --- driver/connect.c | 12 +- driver/error.c | 4 +- driver/handles.c | 384 ++++++++++++++++++++++++---------------------- driver/handles.h | 31 +++- driver/info.c | 9 +- driver/info.h | 9 ++ driver/odbc.c | 96 ++++++------ driver/queries.c | 390 ++++++++++++++++++++++++++++++++++++++++++----- driver/queries.h | 45 +++++- driver/tracing.h | 101 +++++++++++- 10 files changed, 789 insertions(+), 292 deletions(-) diff --git a/driver/connect.c b/driver/connect.c index c10dc79e..9c16bf36 100644 --- a/driver/connect.c +++ b/driver/connect.c @@ -11,6 +11,7 @@ #include "connect.h" #include "queries.h" #include "log.h" +#include "info.h" /* * Not authoriative (there's no formal limit), but pretty informed: @@ -143,7 +144,7 @@ static size_t write_callback(char *ptr, size_t size, size_t nmemb, memcpy(dbc->abuff + dbc->apos, ptr, have); dbc->apos += have; DBG("libcurl: DBC@0x%p, copied: %zd bytes.", dbc, have); - DBG("libcurl: DBC@0x%p, copied: `%.*s`.", dbc, have, ptr); + //DBG("libcurl: DBC@0x%p, copied: `%.*s`.", dbc, have, ptr); /* @@ -837,6 +838,8 @@ SQLRETURN EsSQLGetConnectAttrW( _Out_opt_ SQLINTEGER* StringLengthPtr) { esodbc_dbc_st *dbc = DBCH(ConnectionHandle); + SQLRETURN ret; + SQLSMALLINT used; // SQLTCHAR *val; switch(Attribute) { @@ -870,8 +873,11 @@ SQLRETURN EsSQLGetConnectAttrW( *(SQLTCHAR **)ValuePtr = val; #else //0 // FIXME; - memcpy(ValuePtr, MK_TSTR("NulL"), 8); - ((SQLTCHAR *)ValuePtr)[5] = 0; + ret = write_tstr(&dbc->diag, (SQLTCHAR *)ValuePtr, + MK_TSTR("NulL"), (SQLSMALLINT)BufferLength, &used); + if (StringLengthPtr); + *StringLengthPtr = (SQLINTEGER)used; + return ret; #endif //0 break; diff --git a/driver/error.c b/driver/error.c index b06aae96..1f91b44d 100644 --- a/driver/error.c +++ b/driver/error.c @@ -36,13 +36,13 @@ SQLRETURN post_diagnostic(esodbc_diag_st *dest, esodbc_state_et state, assert(pos < ebufsz); wcsncpy(dest->text, MK_TSTR(ESODBC_DIAG_PREFIX), pos); - if (ebufsz - pos <= tlen) { + if (ebufsz <= pos + tlen) { wcsncpy(dest->text + pos, text, ebufsz - (pos + 1)); dest->text[ebufsz - 1] = 0; dest->text_len = (int)ebufsz - 1; } else { wcsncpy(dest->text + pos, text, tlen + /* 0-term */1); - dest->text_len = (int)tlen; + dest->text_len = (int)(pos + tlen); } DBG("diagnostic message: `" LTPD "` (%d), native code: %d.", dest->text, dest->text_len, dest->native_code); diff --git a/driver/handles.c b/driver/handles.c index 50d8d5f1..11cfdcb8 100644 --- a/driver/handles.c +++ b/driver/handles.c @@ -1180,7 +1180,7 @@ desc_rec_st* get_record(esodbc_desc_st *desc, SQLSMALLINT rec_no, BOOL grow) if (desc->count < rec_no) { if (! grow) return NULL; - if (! SQL_SUCCEEDED(update_rec_count(desc, rec_no))) + else if (! SQL_SUCCEEDED(update_rec_count(desc, rec_no))) return NULL; } return &desc->recs[rec_no - 1]; @@ -1277,7 +1277,7 @@ SQLRETURN EsSQLGetDescFieldW( case SQL_DESC_COUNT: *(SQLSMALLINT *)ValuePtr = desc->count; - DBG("returning count: %d.", (SQLSMALLINT *)ValuePtr); + DBG("returning count: %d.", *(SQLSMALLINT *)ValuePtr); RET_STATE(SQL_STATE_00000); case SQL_DESC_ROWS_PROCESSED_PTR: @@ -1461,85 +1461,86 @@ SQLRETURN EsSQLGetDescRecW( /* * https://docs.microsoft.com/en-us/sql/odbc/reference/appendixes/data-type-identifiers-and-descriptors * - * Note: C and SQL are the same for these following defines. + * Note: C and SQL types have the same value for these following defines, + * so this function will work for both IxD and AxD descriptors. (There is no + * SQL_C_DATETIME or SQL_C_CODE_DATE.) */ -static void concise_to_type_code(SQLSMALLINT concise, SQLSMALLINT *type, +void concise_to_type_code(SQLSMALLINT concise, SQLSMALLINT *type, SQLSMALLINT *code) { switch (concise) { - case SQL_C_DATE: - case SQL_C_TYPE_DATE: + case SQL_DATE: + case SQL_TYPE_DATE: *type = SQL_DATETIME; *code = SQL_CODE_DATE; break; - case SQL_C_TIME: - case SQL_C_TYPE_TIME: - //case SQL_C_TYPE_TIME_WITH_TIMEZONE: //4.0 + case SQL_TIME: + case SQL_TYPE_TIME: + //case SQL_TYPE_TIME_WITH_TIMEZONE: //4.0 *type = SQL_DATETIME; *code = SQL_CODE_TIME; break; - case SQL_C_TIMESTAMP: - case SQL_C_TYPE_TIMESTAMP: - //case SQL_C_TYPE_TIMESTAMP_WITH_TIMEZONE: // 4.0 + case SQL_TIMESTAMP: + case SQL_TYPE_TIMESTAMP: + //case SQL_TYPE_TIMESTAMP_WITH_TIMEZONE: // 4.0 *type = SQL_DATETIME; *code = SQL_CODE_TIMESTAMP; break; - case SQL_C_INTERVAL_MONTH: + case SQL_INTERVAL_MONTH: *type = SQL_INTERVAL; *code = SQL_CODE_MONTH; break; - case SQL_C_INTERVAL_YEAR: + case SQL_INTERVAL_YEAR: *type = SQL_INTERVAL; *code = SQL_CODE_YEAR; break; - case SQL_C_INTERVAL_YEAR_TO_MONTH: + case SQL_INTERVAL_YEAR_TO_MONTH: *type = SQL_INTERVAL; *code = SQL_CODE_YEAR_TO_MONTH; break; - case SQL_C_INTERVAL_DAY: + case SQL_INTERVAL_DAY: *type = SQL_INTERVAL; *code = SQL_CODE_DAY; break; - case SQL_C_INTERVAL_HOUR: + case SQL_INTERVAL_HOUR: *type = SQL_INTERVAL; *code = SQL_CODE_HOUR; break; - case SQL_C_INTERVAL_MINUTE: + case SQL_INTERVAL_MINUTE: *type = SQL_INTERVAL; *code = SQL_CODE_MINUTE; break; - case SQL_C_INTERVAL_SECOND: + case SQL_INTERVAL_SECOND: *type = SQL_INTERVAL; *code = SQL_CODE_SECOND; break; - case SQL_C_INTERVAL_DAY_TO_HOUR: + case SQL_INTERVAL_DAY_TO_HOUR: *type = SQL_INTERVAL; *code = SQL_CODE_DAY_TO_HOUR; break; - case SQL_C_INTERVAL_DAY_TO_MINUTE: + case SQL_INTERVAL_DAY_TO_MINUTE: *type = SQL_INTERVAL; *code = SQL_CODE_DAY_TO_MINUTE; break; - case SQL_C_INTERVAL_DAY_TO_SECOND: + case SQL_INTERVAL_DAY_TO_SECOND: *type = SQL_INTERVAL; *code = SQL_CODE_DAY_TO_SECOND; break; - case SQL_C_INTERVAL_HOUR_TO_MINUTE: + case SQL_INTERVAL_HOUR_TO_MINUTE: *type = SQL_INTERVAL; *code = SQL_CODE_HOUR_TO_MINUTE; break; - case SQL_C_INTERVAL_HOUR_TO_SECOND: + case SQL_INTERVAL_HOUR_TO_SECOND: *type = SQL_INTERVAL; *code = SQL_CODE_HOUR_TO_SECOND; break; - case SQL_C_INTERVAL_MINUTE_TO_SECOND: + case SQL_INTERVAL_MINUTE_TO_SECOND: *type = SQL_INTERVAL; *code = SQL_CODE_MINUTE_TO_SECOND; break; - default: - *type = concise; - *code = 0; } + *type = concise; + *code = 0; } /* @@ -1547,231 +1548,185 @@ static void concise_to_type_code(SQLSMALLINT concise, SQLSMALLINT *type, */ static void set_defaults_from_type(desc_rec_st *rec) { - switch (rec->type) { - case SQL_CHAR: //case SQL_C_CHAR: - assert(SQL_CHAR == SQL_C_CHAR); - case SQL_VARCHAR: /* SQL_C_VARCHAR doesn't exist */ - /* TODO: SQL_ LONGVARCHAR/WCHAR/etc. ? */ + switch (rec->meta_type) { + case METATYPE_STRING: rec->length = 1; rec->precision = 0; break; - - case SQL_DATETIME: + case METATYPE_DATETIME: if (rec->datetime_interval_code == SQL_CODE_DATE || rec->datetime_interval_code == SQL_CODE_TIME) rec->precision = 0; else if (rec->datetime_interval_code == SQL_CODE_TIMESTAMP) rec->precision = 6; break; - - case SQL_DECIMAL: - case SQL_NUMERIC: //case SQL_C_NUMERIC: - assert(SQL_NUMERIC == SQL_C_NUMERIC); - + case METATYPE_EXACT_NUMERIC: rec->scale = 0; - rec->precision = 38; /* TODO: "implementation-defined precision" */ + rec->precision = 19; /* TODO: "implementation-defined precision" */ break; - - case SQL_FLOAT: - case SQL_C_FLOAT: - rec->precision = 38; /* TODO: "implementation-defined precision" */ + case METATYPE_FLOAT_NUMERIC: + rec->precision = 8; /* TODO: "implementation-defined precision" */ break; - - case SQL_INTERVAL: - if (rec->datetime_interval_code) - rec->datetime_interval_precision = 2; - /* TODO: "When the interval has a seconds component, " */ + case METATYPE_INTERVAL_WSEC: rec->precision = 6; break; } } -static inline BOOL is_c_type(SQLSMALLINT type) -{ - switch (type) { - case SQL_C_CHAR: - case SQL_C_WCHAR: - case SQL_C_SSHORT: - case SQL_C_USHORT: - case SQL_C_SLONG: - case SQL_C_ULONG: - case SQL_C_FLOAT: - case SQL_C_DOUBLE: - case SQL_C_BIT: - case SQL_C_STINYINT: - case SQL_C_UTINYINT: - case SQL_C_SBIGINT: - case SQL_C_UBIGINT: - case SQL_C_BINARY: - //case SQL_C_BOOKMARK: - //case SQL_C_VARBOOKMARK: - - case SQL_C_DEFAULT: - return TRUE; - } - return FALSE; -} -static inline BOOL is_sql_type(SQLSMALLINT type) +static esodbc_metatype_et sqltype_to_meta(SQLSMALLINT concise) { - switch (type) { + 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_INTEGER: + case SQL_TINYINT: + case SQL_BIGINT: + return METATYPE_EXACT_NUMERIC; + + /* numeric floating */ case SQL_REAL: case SQL_FLOAT: case SQL_DOUBLE: - case SQL_BIT: - case SQL_TINYINT: - case SQL_BIGINT: - case SQL_BINARY: - case SQL_VARBINARY: - case SQL_LONGVARBINARY: + return METATYPE_FLOAT_NUMERIC; + + /* datetime (note: SQL_DATETIME is verbose, not concise) */ case SQL_TYPE_DATE: case SQL_TYPE_TIME: case SQL_TYPE_TIMESTAMP: - //case SQL_TYPE_UTCDATETIME: - //case SQL_TYPE_UTCTIME: + // case SQL_TYPE_UTCDATETIME: + // case SQL_TYPE_UTCTIME: + return METATYPE_DATETIME; + + /* 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_SECOND: case SQL_INTERVAL_DAY_TO_HOUR: case SQL_INTERVAL_DAY_TO_MINUTE: - case SQL_INTERVAL_DAY_TO_SECOND: 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: - case SQL_GUID: + return METATYPE_INTERVAL_WSEC; + + case SQL_BIT: + return METATYPE_BIT; - case SQL_DEFAULT: - return TRUE; + case SQL_GUID: + return METATYPE_UID; } - return FALSE; + + return METATYPE_UNKNOWN; } -static inline BOOL is_numeric(SQLSMALLINT type) +static esodbc_metatype_et sqlctype_to_meta(SQLSMALLINT concise) { - /* C types */ - switch (type) { + switch (concise) { + /* character */ + case SQL_C_CHAR: + case SQL_C_WCHAR: + // case SQL_C_TCHAR: + return METATYPE_STRING; + /* binary */ + case SQL_C_BINARY: + case SQL_C_BOOKMARK: + //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_FLOAT: - case SQL_C_DOUBLE: - case SQL_C_BIT: case SQL_C_TINYINT: case SQL_C_STINYINT: case SQL_C_UTINYINT: case SQL_C_SBIGINT: - case SQL_C_UBIGINT: - return TRUE; - } + //case SQL_C_UBIGINT: + case SQL_C_NUMERIC: + return METATYPE_EXACT_NUMERIC; - /* XXX: there's an overlap between the two (most C types are defines of - * SQL types), but I might need to split this check by SQL/C group type */ - /* SQL types */ - switch (type) { - case SQL_DECIMAL: - case SQL_NUMERIC: - case SQL_SMALLINT: - case SQL_INTEGER: - case SQL_REAL: - case SQL_FLOAT: - case SQL_DOUBLE: - case SQL_BIT: - case SQL_TINYINT: - case SQL_BIGINT: - return TRUE; - } - - return FALSE; -} + /* numeric floating */ + case SQL_C_FLOAT: + case SQL_C_DOUBLE: + return METATYPE_FLOAT_NUMERIC; -static inline BOOL needs_precision(SQLSMALLINT concise) -{ - switch (concise) { - /* "a time or timestamp data type" */ + /* datetime */ + case SQL_C_DATE: + case SQL_C_TYPE_DATE: case SQL_C_TIME: case SQL_C_TYPE_TIME: - //case SQL_C_TYPE_TIME_WITH_TIMEZONE: //4.0 + // case SQL_C_TYPE_TIME_WITH_TIMEZONE: case SQL_C_TIMESTAMP: case SQL_C_TYPE_TIMESTAMP: - // case SQL_C_TYPE_TIMESTAMP_WITH_TIMEZONE: // 4.0 - - /* TODO: what's "an interval type with a seconds component"? */ + // case SQL_C_TYPE_TIMESTAMP_WITH_TIMEZONE: + return METATYPE_DATETIME; - /* "interval data types with a time component" */ + /* interval */ 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: - - /* Note: SQL types are the same defines (check if adding extras) */ - - return TRUE; - } - return FALSE; -} + case SQL_C_INTERVAL_MONTH: + case SQL_C_INTERVAL_YEAR: + case SQL_C_INTERVAL_YEAR_TO_MONTH: + return METATYPE_INTERVAL_WOSEC; -static inline BOOL is_interval(SQLSMALLINT concise) -{ - switch (concise) { - 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: + return METATYPE_INTERVAL_WSEC; + + case SQL_C_BIT: + return METATYPE_BIT; - /* Note: SQL types are the same defines (check if adding extras) */ + case SQL_C_GUID: + return METATYPE_UID; - return TRUE; + case SQL_C_DEFAULT: + return METATYPE_MAX; } - return FALSE; + + return METATYPE_UNKNOWN; } +/* + * https://docs.microsoft.com/en-us/sql/odbc/reference/syntax/sqlsetdescrec-function#consistency-checks + */ static BOOL consistency_check(esodbc_desc_st *desc, desc_rec_st *rec) { SQLSMALLINT type, code; - if ((! is_c_type(rec->type)) && (! is_sql_type(rec->type))) { - ERR("record 0x%p type %d is neither C nor SQL type.", rec, rec->type); - return FALSE; - } - if ((! is_c_type(rec->concise_type)) && - (! is_sql_type(rec->concise_type))) { - ERR("record 0x%p concise type %d is neither C nor SQL type.", - rec, rec->type); - return FALSE; - } + /* validity of C / SQL datatypes is checked when setting the meta_type */ concise_to_type_code(rec->concise_type, &type, &code); if (rec->type != type || rec->datetime_interval_code != code) { @@ -1781,7 +1736,10 @@ static BOOL consistency_check(esodbc_desc_st *desc, desc_rec_st *rec) return FALSE; } - if (is_numeric(rec->type)) { + /* TODO: use the get_rec_size/get_rec_decdigits() (below)? */ + + //if (rec->meta_type == METATYPE_NUMERIC) { + if (0) { // FIXME /* TODO: actually check validity of precision/scale for data type */ if ((! rec->precision) || (! rec->scale)) { ERR("invalid numeric precision/scale: %d/%d for data type %d.", @@ -1790,8 +1748,16 @@ static BOOL consistency_check(esodbc_desc_st *desc, desc_rec_st *rec) } } - if (needs_precision(rec->concise_type)) { + if (rec->meta_type == METATYPE_DATETIME || 0) { // FIXME +// rec->meta_type == METATYPE_INTERVAL) { /* TODO: actually check validity of precision for data type */ + /* + * TODO: this should be rec->length, acc to: + * https://docs.microsoft.com/en-us/sql/odbc/reference/appendixes/column-size + * but rec->precision, acc to: + * https://docs.microsoft.com/en-us/sql/odbc/reference/syntax/sqlsetdescrec-function#consistency-checks + * ??? + */ if (! rec->precision) { ERR("invalid time/timestamp/interval with seconds/time " "precision %d for concise type %d.", @@ -1800,7 +1766,8 @@ static BOOL consistency_check(esodbc_desc_st *desc, desc_rec_st *rec) } } - if (is_interval(rec->concise_type)) { + // if (rec->meta_type == METATYPE_INTERVAL) { + if (0) { // FIXME /* TODO: actually check the validity of dt_i_precision for data type */ if (! rec->datetime_interval_precision) { ERR("invalid datetime_interval_precision %d for interval concise " @@ -1813,6 +1780,57 @@ static BOOL consistency_check(esodbc_desc_st *desc, desc_rec_st *rec) return TRUE; } +static inline 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; +} + +/* + * https://docs.microsoft.com/en-us/sql/odbc/reference/appendixes/column-size + */ +SQLULEN get_rec_size(desc_rec_st *rec) +{ + if (rec->meta_type == METATYPE_EXACT_NUMERIC || + rec->meta_type == METATYPE_FLOAT_NUMERIC) { + if (rec->precision < 0) { + BUG("precision can't be negative."); + return 0; + } + return (SQLULEN)rec->precision; + } else { + return rec->length; + } +} + +SQLULEN get_rec_decdigits(desc_rec_st *rec) +{ + switch (rec->meta_type) { + case METATYPE_DATETIME: + case METATYPE_INTERVAL_WSEC: + return rec->precision; + case METATYPE_EXACT_NUMERIC: + return rec->scale; + } + /* 0 to be returned for unknown case: + * https://docs.microsoft.com/en-us/sql/odbc/reference/syntax/sqldescribecol-function#syntax + */ + return 0; +} + /* * "If an application calls SQLSetDescField to set any field other than * SQL_DESC_COUNT or the deferred fields SQL_DESC_DATA_PTR, @@ -1849,7 +1867,7 @@ SQLRETURN EsSQLSetDescFieldW( SQLTCHAR **tstrp, *tstr; SQLSMALLINT *wordp; SQLINTEGER *intp; - SQLSMALLINT count; + SQLSMALLINT count, type; SQLULEN ulen; size_t wlen; @@ -1974,27 +1992,27 @@ SQLRETURN EsSQLSetDescFieldW( /* record fields */ switch (FieldIdentifier) { + case SQL_DESC_TYPE: + type = (SQLSMALLINT)(intptr_t)ValuePtr; + DBG("setting type of rec 0x%p to %d.", rec, type); + if (type == SQL_DATETIME || type == SQL_INTERVAL) + RET_HDIAGS(desc, SQL_STATE_HY021); + /* no break */ case SQL_DESC_CONCISE_TYPE: DBG("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); - set_defaults_from_type(rec); - DBG("rec 0x%p types: concise: %d, verbose: %d, code: %d.", rec, - rec->concise_type, rec->type, rec->datetime_interval_code); - break; - - case SQL_DESC_TYPE: - DBG("setting type of rec 0x%p to %d.", rec, - (SQLSMALLINT)(intptr_t)ValuePtr); - rec->type = (SQLSMALLINT)(intptr_t)ValuePtr; - if (rec->type == SQL_DATETIME || rec->type == SQL_INTERVAL) + rec->meta_type = concise_to_meta(rec->concise_type, desc->type); + if (rec->meta_type == METATYPE_UNKNOWN) { + ERR("REC@0x%p: incorrect concise type %d for rec #%d.", rec, + rec->concise_type, RecNumber); RET_HDIAGS(desc, SQL_STATE_HY021); - rec->concise_type = rec->type; - rec->datetime_interval_code = 0; + } set_defaults_from_type(rec); - DBG("rec 0x%p types: concise: %d, verbose: %d, code: %d.", rec, + DBG("REC@0x%p types: concise: %d, verbose: %d, code: %d.", rec, rec->concise_type, rec->type, rec->datetime_interval_code); break; @@ -2005,7 +2023,7 @@ SQLRETURN EsSQLSetDescFieldW( if (rec->data_ptr) { if ( #if 1 - 0 /* FIXME! */ && + 0 /* FIXME when adding data defs! */ && #endif //1 (desc->type != DESC_TYPE_IRD) && (! consistency_check(desc, rec))) { diff --git a/driver/handles.h b/driver/handles.h index f468441c..b03d21bd 100644 --- a/driver/handles.h +++ b/driver/handles.h @@ -76,9 +76,26 @@ typedef struct struct_dbc { struct struct_desc; struct struct_stmt; +typedef enum { + METATYPE_UNKNOWN = 0, + METATYPE_EXACT_NUMERIC, + METATYPE_FLOAT_NUMERIC, + METATYPE_STRING, + METATYPE_BIN, + METATYPE_DATETIME, + METATYPE_INTERVAL_WSEC, + METATYPE_INTERVAL_WOSEC, + METATYPE_BIT, + METATYPE_UID, + METATYPE_MAX // SQL_C_DEFAULT +} esodbc_metatype_et; + typedef struct desc_rec { /* back ref to owning descriptor */ - struct struct_desc *desc; + struct struct_desc *desc; + + /* helper member, to characterize the type */ + esodbc_metatype_et meta_type; /* record fields */ SQLSMALLINT concise_type; @@ -87,15 +104,16 @@ typedef struct desc_rec { SQLPOINTER data_ptr; /* array, if .array_size > 1 */ + /* TODO: add (& use) the lenghts */ SQLTCHAR *base_column_name; /* read-only */ SQLTCHAR *base_table_name; /* r/o */ SQLTCHAR *catalog_name; /* r/o */ SQLTCHAR *label; /* r/o */ - SQLTCHAR *literal_prefix; /* r/o */ - SQLTCHAR *literal_suffix; /* r/o */ + SQLTCHAR *literal_prefix; /* r/o */ // TODO: static? + SQLTCHAR *literal_suffix; /* r/o */ // TODO: static? SQLTCHAR *local_type_name; /* r/o */ SQLTCHAR *name; - SQLTCHAR *schema_name; /* r/o */ + SQLTCHAR *schema_name; /* r/o */ // TODO: static? SQLTCHAR *table_name; /* r/o */ SQLTCHAR *type_name; /* r/o */ @@ -123,7 +141,7 @@ typedef struct desc_rec { SQLSMALLINT usigned; SQLSMALLINT updatable; /* /record fields */ -} desc_rec_st; +} desc_rec_st; /* TODO: -> esodbc_rec_st */ typedef enum { @@ -237,6 +255,9 @@ typedef struct struct_stmt { SQLRETURN update_rec_count(esodbc_desc_st *desc, SQLSMALLINT new_count); desc_rec_st* get_record(esodbc_desc_st *desc, SQLSMALLINT rec_no, BOOL grow); +/* TODO: move to some utils.h */ +void concise_to_type_code(SQLSMALLINT concise, SQLSMALLINT *type, + SQLSMALLINT *code); SQLRETURN EsSQLAllocHandle(SQLSMALLINT HandleType, SQLHANDLE InputHandle, _Out_ SQLHANDLE *OutputHandle); diff --git a/driver/info.c b/driver/info.c index cc236d41..3e37e3ff 100644 --- a/driver/info.c +++ b/driver/info.c @@ -32,8 +32,7 @@ #endif /* win32 */ #define ESODBC_PATTERN_ESCAPE "\\" -#define ESODBC_CATALOG_SEPARATOR "." -//#define ESODBC_CATALOG_SEPARATOR "" +#define ESODBC_CATALOG_SEPARATOR ":" #define ESODBC_CATALOG_TERM "clusterName" #define ESODBC_MAX_SCHEMA_LEN 0 #define ESODBC_QUOTE_CHAR "\"" @@ -131,9 +130,9 @@ static SQLUSMALLINT esodbc_functions[] = { *(((UWORD*) (pfExists)) + ((uwAPI) >> 4)) |= (1 << ((uwAPI) & 0x000F)) #define SQL_API_ODBC2_ALL_FUNCTIONS_SIZE 100 -static SQLRETURN write_tstr(esodbc_diag_st *diag, +SQLRETURN write_tstr(esodbc_diag_st *diag, SQLTCHAR *dest, const SQLTCHAR *src, - SQLSMALLINT avail, SQLSMALLINT *usedp) + SQLSMALLINT /*B*/avail, SQLSMALLINT *usedp) { size_t src_len, awail; SQLSMALLINT used; @@ -623,7 +622,7 @@ SQLRETURN EsSQLGetDiagRecW } /* no error indication exists */ wcsncpy(MessageText, diag->text, diag->text_len); - DBG("diagnostic text: '"LTPD"' (%d).", MessageText,diag->text_len); + DBG("diagnostic text: `"LTPD"` (%d).", MessageText,diag->text_len); return SQL_SUCCESS; } else { if (BufferLength < 0) { diff --git a/driver/info.h b/driver/info.h index 0565398c..d081a28d 100644 --- a/driver/info.h +++ b/driver/info.h @@ -13,6 +13,15 @@ /* TODO: review@alpha */ #define ESODBC_MAX_IDENTIFIER_LEN 128 +/* + * TODO: move into a util.h + * TODO: change sign to ...src, lsrc, dst, dlen... (lsrc is nearly always + * known) + */ +SQLRETURN write_tstr(esodbc_diag_st *diag, + SQLTCHAR *dest, const SQLTCHAR *src, + SQLSMALLINT /*B*/avail, SQLSMALLINT *usedp); + SQLRETURN EsSQLGetInfoW(SQLHDBC ConnectionHandle, SQLUSMALLINT InfoType, _Out_writes_bytes_opt_(BufferLength) SQLPOINTER InfoValue, diff --git a/driver/odbc.c b/driver/odbc.c index 1ced1ad7..0f7e6916 100644 --- a/driver/odbc.c +++ b/driver/odbc.c @@ -109,11 +109,11 @@ SQLRETURN SQL_API SQLDriverConnectW ) { SQLRETURN ret; - TRACE8(_IN, "ppWdpdpd", hdbc, hwnd, szConnStrIn, cchConnStrIn, + TRACE8(_IN, "pppdpdpd", hdbc, hwnd, szConnStrIn, cchConnStrIn, szConnStrOut, cchConnStrOutMax, pcchConnStrOut, fDriverCompletion); ret = EsSQLDriverConnectW(hdbc, hwnd, szConnStrIn, cchConnStrIn, szConnStrOut, cchConnStrOutMax, pcchConnStrOut, fDriverCompletion); - TRACE9(_OUT, "dppWdWdpd", ret, hdbc, hwnd, szConnStrIn, cchConnStrIn, + TRACE9(_OUT, "dppWdWdtd", ret, hdbc, hwnd, szConnStrIn, cchConnStrIn, szConnStrOut, cchConnStrOutMax, pcchConnStrOut, fDriverCompletion); return ret; } @@ -185,12 +185,12 @@ SQLRETURN SQL_API SQLGetInfoW(SQLHDBC ConnectionHandle, _Out_opt_ SQLSMALLINT *StringLengthPtr) { SQLRETURN ret; - TRACE6(_IN, "pupUdp", ConnectionHandle, InfoType, InfoValue, InfoValue, + TRACE5(_IN, "pupdp", ConnectionHandle, InfoType, InfoValue, BufferLength, StringLengthPtr); - ret = EsSQLGetInfoW(ConnectionHandle, InfoType, InfoValue, BufferLength, - StringLengthPtr); - TRACE7(_OUT, "dpupUdD", ret, ConnectionHandle, InfoType, - InfoValue, InfoValue, BufferLength, StringLengthPtr); + ret = EsSQLGetInfoW(ConnectionHandle, InfoType, InfoValue, + BufferLength, StringLengthPtr); + TRACE6(_OUT, "dpupdt", ret, ConnectionHandle, InfoType, + InfoValue, BufferLength, StringLengthPtr); return ret; } @@ -199,9 +199,9 @@ SQLRETURN SQL_API SQLGetFunctions(SQLHDBC ConnectionHandle, _Out_writes_opt_(_Inexpressible_("Buffer length pfExists points to depends on fFunction value.")) SQLUSMALLINT *Supported) { SQLRETURN ret; - TRACE3(_IN, "pdU", ConnectionHandle, FunctionId, Supported); + TRACE3(_IN, "pdp", ConnectionHandle, FunctionId, Supported); ret = EsSQLGetFunctions(ConnectionHandle, FunctionId, Supported); - TRACE4(_IN, "dpdU", ret, ConnectionHandle, FunctionId, Supported); + TRACE4(_IN, "dpdT", ret, ConnectionHandle, FunctionId, Supported); return ret; } @@ -251,7 +251,7 @@ SQLRETURN SQL_API SQLGetConnectAttrW( BufferLength, StringLengthPtr); ret = EsSQLGetConnectAttrW(ConnectionHandle, Attribute, ValuePtr, BufferLength, StringLengthPtr); - TRACE6(_OUT, "dpdpdD", ret, ConnectionHandle, Attribute, ValuePtr, + TRACE6(_OUT, "dpdpdg", ret, ConnectionHandle, Attribute, ValuePtr, BufferLength, StringLengthPtr); return ret; } @@ -280,7 +280,7 @@ SQLRETURN SQL_API SQLGetEnvAttr(SQLHENV EnvironmentHandle, StringLength); ret = EsSQLGetEnvAttr(EnvironmentHandle, Attribute, Value, BufferLength, StringLength); - TRACE6(_OUT, "dpdpdD", ret, EnvironmentHandle, Attribute, Value, + TRACE6(_OUT, "dpdpdg", ret, EnvironmentHandle, Attribute, Value, BufferLength, StringLength); return ret; } @@ -312,7 +312,7 @@ SQLRETURN SQL_API SQLGetStmtAttrW( StringLengthPtr); ret = EsSQLGetStmtAttrW(StatementHandle, Attribute, ValuePtr, BufferLength, StringLengthPtr); - TRACE6(_OUT, "dpdpdD", ret, StatementHandle, Attribute, ValuePtr, + TRACE6(_OUT, "dpdpdg", ret, StatementHandle, Attribute, ValuePtr, BufferLength, StringLengthPtr); return ret; } @@ -338,7 +338,7 @@ SQLRETURN SQL_API SQLGetDescFieldW( ValuePtr, BufferLength, StringLengthPtr); ret = EsSQLGetDescFieldW(DescriptorHandle, RecNumber, FieldIdentifier, ValuePtr, BufferLength, StringLengthPtr); - TRACE7(_OUT, "dpddpdD", ret, DescriptorHandle, RecNumber, FieldIdentifier, + TRACE7(_OUT, "dpddpdg", ret, DescriptorHandle, RecNumber, FieldIdentifier, ValuePtr, BufferLength, StringLengthPtr); return ret; } @@ -372,7 +372,7 @@ SQLRETURN SQL_API SQLGetDescRecW( ret = EsSQLGetDescRecW(DescriptorHandle, RecNumber, Name, BufferLength, StringLengthPtr, TypePtr, SubTypePtr, LengthPtr, PrecisionPtr, ScalePtr, NullablePtr); - TRACE12(_OUT, "dpdWdDDDDDDD", ret, DescriptorHandle, RecNumber, Name, + TRACE12(_OUT, "dpdWdttttttt", ret, DescriptorHandle, RecNumber, Name, BufferLength, StringLengthPtr, TypePtr, SubTypePtr, LengthPtr, PrecisionPtr, ScalePtr, NullablePtr); return ret; @@ -415,7 +415,7 @@ SQLRETURN SQL_API SQLSetDescRec( Length, Precision, Scale, Data, StringLength, Indicator); ret = EsSQLSetDescRec(DescriptorHandle, RecNumber, Type, SubType, Length, Precision, Scale, Data, StringLength, Indicator); - TRACE11(_OUT, "dpddddddpDD", ret, DescriptorHandle, RecNumber, Type, + TRACE11(_OUT, "dpddddddpnn", ret, DescriptorHandle, RecNumber, Type, SubType, Length, Precision, Scale, Data, StringLength, Indicator); return ret; } @@ -457,7 +457,7 @@ SQLRETURN SQL_API SQLPrepareW SQLRETURN ret; TRACE3(_IN, "ppd", hstmt, szSqlStr, cchSqlStr); ret = EsSQLPrepareW(hstmt, szSqlStr, cchSqlStr); - TRACE4(_OUT, "dpSd", ret, hstmt, szSqlStr, cchSqlStr); + TRACE4(_OUT, "dpWd", ret, hstmt, szSqlStr, cchSqlStr); return ret; } @@ -563,7 +563,7 @@ SQLRETURN SQL_API SQLExecDirectW SQLRETURN ret; TRACE3(_IN, "ppd", hstmt, szSqlStr, cchSqlStr); ret = EsSQLExecDirectW(hstmt, szSqlStr, cchSqlStr); - TRACE4(_OUT, "dpSd", ret, hstmt, szSqlStr, cchSqlStr); + TRACE4(_OUT, "dpWd", ret, hstmt, szSqlStr, cchSqlStr); return ret; } @@ -642,17 +642,17 @@ SQLRETURN SQL_API SQLNumResultCols(SQLHSTMT StatementHandle, SQLRETURN ret; TRACE2(_IN, "pp", StatementHandle, ColumnCount); ret = EsSQLNumResultCols(StatementHandle, ColumnCount); - TRACE3(_OUT, "dpD", ret, StatementHandle, ColumnCount); + TRACE3(_OUT, "dpt", ret, StatementHandle, ColumnCount); return ret; } -#if WITH_EMPTY SQLRETURN SQL_API SQLDescribeColW ( SQLHSTMT hstmt, SQLUSMALLINT icol, - _Out_writes_opt_(cchColNameMax) SQLWCHAR* szColName, + _Out_writes_opt_(cchColNameMax) + SQLWCHAR *szColName, SQLSMALLINT cchColNameMax, _Out_opt_ SQLSMALLINT* pcchColName, @@ -666,10 +666,16 @@ SQLRETURN SQL_API SQLDescribeColW SQLSMALLINT* pfNullable ) { - RET_NOT_IMPLEMENTED; + SQLRETURN ret; + TRACE9(_IN, "pupdppppp", hstmt, icol, szColName, cchColNameMax, + pcchColName, pfSqlType, pcbColDef, pibScale, pfNullable); + ret = EsSQLDescribeColW(hstmt, icol, szColName, cchColNameMax, + pcchColName, pfSqlType, pcbColDef, pibScale, pfNullable); + TRACE10(_OUT, "dpuWdttNtt", ret, hstmt, icol, szColName, cchColNameMax, + pcchColName, pfSqlType, pcbColDef, pibScale, pfNullable); + return ret; } -#ifdef _WIN64 SQLRETURN SQL_API SQLColAttributeW ( SQLHSTMT hstmt, @@ -681,41 +687,41 @@ SQLRETURN SQL_API SQLColAttributeW _Out_opt_ SQLSMALLINT *pcbCharAttr, _Out_opt_ +#ifdef _WIN64 SQLLEN *pNumAttr +#else /* _WIN64 */ + SQLPOINTER pNumAttr +#endif /* _WIN64 */ ) { - RET_NOT_IMPLEMENTED; -} + SQLRETURN ret; + TRACE7(_IN, "pddpdtp", hstmt, iCol, iField, pCharAttr, cbDescMax, + pcbCharAttr, pNumAttr); + ret = EsSQLColAttributeW(hstmt, iCol, iField, pCharAttr, cbDescMax, + pcbCharAttr, pNumAttr); +#ifdef _WIN64 + TRACE8(_OUT, "dpddpdtn", ret, hstmt, iCol, iField, pCharAttr, cbDescMax, + pcbCharAttr, pNumAttr); #else /* _WIN64 */ -SQLRETURN SQL_API SQLColAttributeW( - SQLHSTMT hstmt, - SQLUSMALLINT iCol, - SQLUSMALLINT iField, - _Out_writes_bytes_opt_(cbDescMax) - SQLPOINTER pCharAttr, - SQLSMALLINT cbDescMax, - _Out_opt_ - SQLSMALLINT *pcbCharAttr, - _Out_opt_ - SQLPOINTER pNumAttr) -{ - RET_NOT_IMPLEMENTED; -} + TRACE8(_OUT, "dpddpdtp", ret, hstmt, iCol, iField, pCharAttr, cbDescMax, + pcbCharAttr, pNumAttr); #endif /* _WIN64 */ + return ret; +} -#endif /*WITH_EMPTY*/ SQLRETURN SQL_API SQLBindCol(SQLHSTMT StatementHandle, - SQLUSMALLINT ColumnNumber, SQLSMALLINT TargetType, - _Inout_updates_opt_(_Inexpressible_(BufferLength)) SQLPOINTER TargetValue, - SQLLEN BufferLength, _Inout_opt_ SQLLEN *StrLen_or_Ind) + SQLUSMALLINT ColumnNumber, SQLSMALLINT TargetType, + _Inout_updates_opt_(_Inexpressible_(BufferLength)) + SQLPOINTER TargetValue, + SQLLEN BufferLength, _Inout_opt_ SQLLEN *StrLen_or_Ind) { SQLRETURN ret; TRACE6(_IN, "pddpdp", StatementHandle, ColumnNumber, TargetType, TargetValue, BufferLength, StrLen_or_Ind); ret = EsSQLBindCol(StatementHandle, ColumnNumber, TargetType, TargetValue, BufferLength, StrLen_or_Ind); - TRACE7(_OUT, "dpddpdD", ret, StatementHandle, ColumnNumber, TargetType, + TRACE7(_OUT, "dpddpdn", ret, StatementHandle, ColumnNumber, TargetType, TargetValue, BufferLength, StrLen_or_Ind); return ret; } @@ -821,7 +827,7 @@ SQLRETURN SQL_API SQLGetDiagFieldW( DiagInfoPtr, BufferLength, StringLengthPtr); ret = EsSQLGetDiagFieldW(HandleType, Handle, RecNumber, DiagIdentifier, DiagInfoPtr, BufferLength, StringLengthPtr); - TRACE8(_OUT, "ddpddpdD", ret, HandleType, Handle, RecNumber, + TRACE8(_OUT, "ddpddpdt", ret, HandleType, Handle, RecNumber, DiagIdentifier, DiagInfoPtr, BufferLength, StringLengthPtr); return ret; } @@ -843,7 +849,7 @@ SQLRETURN SQL_API SQLGetDiagRecW NativeError, MessageText, BufferLength, TextLength); ret = EsSQLGetDiagRecW(HandleType, Handle, RecNumber, Sqlstate, NativeError, MessageText, BufferLength, TextLength); - TRACE9(_OUT, "ddpdWDWdD", ret, HandleType, Handle, RecNumber, Sqlstate, + TRACE9(_OUT, "ddpdWgWdt", ret, HandleType, Handle, RecNumber, Sqlstate, NativeError, MessageText, BufferLength, TextLength); return ret; } diff --git a/driver/queries.c b/driver/queries.c index 27c4c637..37f2bea2 100644 --- a/driver/queries.c +++ b/driver/queries.c @@ -4,11 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ +#include #include /* WideCharToMultiByte() */ #include "queries.h" #include "handles.h" #include "connect.h" +#include "info.h" #define MSG_INV_SRV_ANS "Invalid server answer" @@ -98,6 +100,7 @@ static SQLSMALLINT type_elastic2csql(const wchar_t *type_name, size_t len) static SQLRETURN attach_columns(esodbc_stmt_st *stmt, UJObject columns) { + desc_rec_st *rec; SQLRETURN ret; SQLSMALLINT recno; void *iter; @@ -114,7 +117,7 @@ static SQLRETURN attach_columns(esodbc_stmt_st *stmt, UJObject columns) ncols = UJArraySize(columns); - DBG("columns received: %zd.", ncols); + DBG("STMT@0x%p: columns received: %zd.", stmt, ncols); ret = update_rec_count(ird, (SQLSMALLINT)ncols); if (! SQL_SUCCEEDED(ret)) { ERR("failed to set IRD's record count to %d.", ncols); @@ -135,21 +138,35 @@ static SQLRETURN attach_columns(esodbc_stmt_st *stmt, UJObject columns) UJGetError(stmt->rset.state)); RET_HDIAG(stmt, SQL_STATE_HY000, MSG_INV_SRV_ANS, 0); } + rec = &ird->recs[recno]; // +recno col_wname = UJReadString(name_o, &len); - ird->recs[recno].base_column_name = (SQLTCHAR *)col_wname; + assert(sizeof(*col_wname) == sizeof(SQLTCHAR)); /* TODO: no ANSI */ + rec->name = (SQLTCHAR *)col_wname; + rec->unnamed = SQL_NAMED; col_wtype = UJReadString(type_o, &len); + /* + * TODO: to ELASTIC types, rather? + * TODO: Read size (precision/lenght) and dec-dig(scale/precision) + * from received type. + */ col_stype = type_elastic2csql(col_wtype, len); if (col_stype == SQL_UNKNOWN_TYPE) { ERR("failed to convert Elastic to C SQL type `" LTPDL "`.", len, col_wtype); RET_HDIAG(stmt, SQL_STATE_HY000, MSG_INV_SRV_ANS, 0); } - ird->recs[recno].type = col_stype; + rec->concise_type = col_stype; + concise_to_type_code(col_stype, &rec->type, + &rec->datetime_interval_code); - DBG("column #%d: name=`" LTPD "`, type=%d (`" LTPD "`).", recno, - col_wname, col_stype, col_wtype); + /* TODO: set all settable fields */ + rec->base_column_name = MK_TSTR(""); + rec->display_size = 256; + + DBG("STMT@0x%p: column #%d: name=`" LTPD "`, type=%d (`" LTPD "`).", + stmt, recno, col_wname, col_stype, col_wtype); recno ++; } @@ -195,18 +212,19 @@ SQLRETURN attach_answer(esodbc_stmt_st *stmt, char *buff, size_t blen) /* set the cursor */ stmt->rset.rows_iter = UJBeginArray(rows); -#if 0 /* UJSON4C will return NULL above, for empty array (meh!) */ if (! stmt->rset.rows_iter) { +#if 0 /* UJSON4C will return NULL above, for empty array (meh!) */ ERR("failed to get iterrator on received rows: %s.", UJGetError(stmt->rset.state)); RET_HDIAGS(stmt, SQL_STATE_HY000); - } - stmt->rset.nrows = (size_t)UJArraySize(rows); #else /*0*/ - DBG("received empty resultset array: forcing nodata."); - STMT_FORCE_NODATA(stmt); - stmt->rset.nrows = 0; + DBG("received empty resultset array: forcing nodata."); + STMT_FORCE_NODATA(stmt); + stmt->rset.nrows = 0; #endif /*0*/ + } else { + stmt->rset.nrows = (size_t)UJArraySize(rows); + } DBG("rows received in result set: %zd.", stmt->rset.nrows); return attach_columns(stmt, columns); @@ -304,18 +322,28 @@ SQLRETURN attach_error(esodbc_stmt_st *stmt, char *buff, size_t blen) } SQLRETURN attach_sql(esodbc_stmt_st *stmt, - const SQLTCHAR *sql, /* SQL text statement */ +// const SQLTCHAR *sql, /* SQL text statement */ + const SQLTCHAR *_sql, /* SQL text statement */ size_t sqlcnt /* count of chars of 'sql' */) { char *u8; int len; +#if 1 // FIXME + DBG("STMT@0x%p orig attaching SQL `" LTPDL "` (%zd).", stmt, sqlcnt, + _sql, sqlcnt); + SQLTCHAR *sql = _sql; + if (wcslen(sql) < 1256) { + if (wcsstr(sql, L"FROM test_emp")) { + sql = L"SELECT emp_no, first_name, birth_date, 2+3 AS foo FROM test_emp"; + sqlcnt = wcslen(sql); + } + } +#endif DBG("STMT@0x%p attaching SQL `" LTPDL "` (%zd).", stmt, sqlcnt,sql,sqlcnt); assert(! stmt->u8sql); - // TODO: escaping? in UCS2 or UTF8 - len = WideCharToMultiByte(CP_UTF8, WC_NO_BEST_FIT_CHARS, sql, (int)sqlcnt, NULL, 0, NULL, NULL); if (len <= 0) { @@ -475,14 +503,15 @@ SQLRETURN EsSQLBindCol( /* * field: SQL_DESC_: DATA_PTR / INDICATOR_PTR / OCTET_LENGTH_PTR - * pos: position in array/rowset (not result set) + * pos: position in array/row_set (not result_set) */ static void* deferred_address(SQLSMALLINT field_id, size_t pos, - esodbc_desc_st *desc, desc_rec_st *rec) + desc_rec_st *rec) { size_t elem_size; SQLLEN offt; void *base; + esodbc_desc_st *desc = rec->desc; #define ROW_OFFSETS \ do { \ @@ -512,7 +541,7 @@ static void* deferred_address(SQLSMALLINT field_id, size_t pos, case SQL_DESC_OCTET_LENGTH_PTR: base = rec->octet_length_ptr; if (desc->bind_type == SQL_BIND_BY_COLUMN) { - elem_size = sizeof(*rec->indicator_ptr); + elem_size = sizeof(*rec->octet_length_ptr); offt = 0; } else { /* by row */ ROW_OFFSETS; @@ -531,19 +560,78 @@ static void* deferred_address(SQLSMALLINT field_id, size_t pos, return base ? (char *)base + offt + pos * elem_size : NULL; } +static inline void get_deferred_buffers(desc_rec_st *rec, size_t pos, + void **data_ptr, void **octet_len_ptr) +{ + /* only indicate length if have dedicated buffer for it */ + if (rec->octet_length_ptr == rec->indicator_ptr) + *octet_len_ptr = NULL; + else + /* pointer where to write how many characters we will/would use */ + *octet_len_ptr = deferred_address(SQL_DESC_OCTET_LENGTH_PTR, pos, rec); + /* pointer to app's buffer */ + *data_ptr = deferred_address(SQL_DESC_DATA_PTR, pos, rec); +} + +/* + * Returns the amount of bytes to copy (in the data_ptr), taking into account + * the bytes of the data 'have', buffer size 'available' and statement + * attribute SQL_ATTR_MAX_LENGTH 'max'. + * Sets the octet len pointer and indicates truncation into 'state'. + */ +static inline size_t bytes_to_copy(size_t have, size_t available, size_t max, + SQLLEN *octet_len_ptr, esodbc_state_et *state) +{ + size_t to_copy; + + /* apply "network" truncation first, if limitation exists */ + if (0 < max && max < have) { + to_copy = max; + /* no truncation indicated for this case */ + } else { + to_copy = have; + } + + /* is target buffer to small? adjust size if so and indicate truncation */ + if (available < to_copy) { + to_copy = available; + *state = SQL_STATE_22001; + } + + if (octet_len_ptr) { + if (0 < max) + /* put the value of SQL_ATTR_MAX_LENGTH attribute.. even + * if this would be larger than what the data actually + * occupies after conversion: "the driver has no way of + * figuring out what the actual length is" */ + *octet_len_ptr = max; + else + /* if no "network" truncation done, indicate data's lenght, no + * matter if truncated to buffer's size or not */ + *octet_len_ptr = have; + } + + return to_copy; +} + static SQLRETURN copy_longlong(desc_rec_st *arec, desc_rec_st *irec, SQLULEN pos, long long ll) { esodbc_stmt_st *stmt; void *data_ptr; + SQLLEN *octet_len_ptr; esodbc_desc_st *ard, *ird; SQLSMALLINT target_type; + char buff[sizeof("18446744073709551616")]; /* = 1 << 8*8 */ + SQLTCHAR wbuff[sizeof("18446744073709551616")]; /* = 1 << 8*8 */ + size_t tocopy; + esodbc_state_et state = SQL_STATE_00000; stmt = arec->desc->stmt; ird = stmt->ird; ard = stmt->ard; - - data_ptr = deferred_address(SQL_DESC_DATA_PTR, pos, ard, arec); + + get_deferred_buffers(arec, pos, &data_ptr, (void **)&octet_len_ptr); if (! data_ptr) { ERR("STMT@0x%p: received integer type, but NULL data ptr.", stmt); RET_HDIAGS(stmt, SQL_STATE_HY009); @@ -554,6 +642,14 @@ static SQLRETURN copy_longlong(desc_rec_st *arec, desc_rec_st *irec, target_type = arec->type == SQL_C_DEFAULT ? irec->type : arec->type; DBG("target data type: %d.", target_type); switch (target_type) { + case SQL_C_CHAR: + _i64toa((int64_t)ll, buff, /*radix*/10); + /* TODO: find/write a function that returns len of conversion? */ + tocopy = bytes_to_copy(strlen(buff) + /* \0 */1, + arec->octet_length, stmt->max_length, octet_len_ptr, + &state); + memcpy(data_ptr, buff, tocopy); + break; case SQL_C_SLONG: case SQL_C_SSHORT: *(SQLINTEGER *)data_ptr = (SQLINTEGER)ll; @@ -564,7 +660,8 @@ static SQLRETURN copy_longlong(desc_rec_st *arec, desc_rec_st *irec, } DBG("REC@0x%p, data_ptr@0x%p, copied long long: `%d`.", arec, data_ptr, (SQLINTEGER)ll); - return SQL_SUCCESS; + + RET_STATE(state); } static SQLRETURN copy_double(desc_rec_st *arec, desc_rec_st *irec, @@ -621,7 +718,7 @@ static SQLRETURN wstr_to_cstr(desc_rec_st *arec, desc_rec_st *irec, char *charp; size_t in_bytes, out_bytes; size_t octet_length; /* need it for comparision to -1 (w/o int promotion)*/ - BOOL was_truncated; + BOOL was_truncated; /* TODO: use bytes_to_copy() */ stmt = arec->desc->stmt; @@ -754,7 +851,7 @@ static SQLRETURN copy_string(desc_rec_st *arec, desc_rec_st *irec, { esodbc_stmt_st *stmt; void *data_ptr; - SQLLEN *octet_len_ptr, *indicator_ptr; + SQLLEN *octet_len_ptr; esodbc_desc_st *ard, *ird; SQLSMALLINT target_type; @@ -762,14 +859,7 @@ static SQLRETURN copy_string(desc_rec_st *arec, desc_rec_st *irec, ird = stmt->ird; ard = stmt->ard; - /* pointer where to write how many characters we will/would use */ - octet_len_ptr = deferred_address(SQL_DESC_OCTET_LENGTH_PTR, pos, ard,arec); - indicator_ptr = deferred_address(SQL_DESC_INDICATOR_PTR, pos, ard, arec); - /* only indicate length if have dedicated buffer for it */ - if (octet_len_ptr == indicator_ptr) - octet_len_ptr = NULL; - /* pointer to app's buffer */ - data_ptr = deferred_address(SQL_DESC_DATA_PTR, pos, ard, arec); + get_deferred_buffers(arec, pos, &data_ptr, (void **)&octet_len_ptr); /* "To use the default mapping, an application specifies the SQL_C_DEFAULT * type identifier." */ @@ -866,8 +956,7 @@ static SQLRETURN copy_one_row(esodbc_stmt_st *stmt, SQLULEN pos, UJObject row) " nullable data type", i + 1); } #endif //0 - ind_len = deferred_address(SQL_DESC_INDICATOR_PTR, pos, ard, - arec); + ind_len = deferred_address(SQL_DESC_INDICATOR_PTR, pos, arec); if (! ind_len) { ERR("STMT@0x%p: no buffer to signal NULL value.", stmt); RET_ROW_DIAG(SQL_STATE_22002, "Indicator variable required" @@ -1175,9 +1264,9 @@ SQLRETURN EsSQLNumResultCols(SQLHSTMT StatementHandle, */ SQLRETURN EsSQLPrepareW ( - SQLHSTMT hstmt, - _In_reads_(cchSqlStr) SQLWCHAR* szSqlStr, - SQLINTEGER cchSqlStr + SQLHSTMT hstmt, + _In_reads_(cchSqlStr) SQLWCHAR* szSqlStr, + SQLINTEGER cchSqlStr ) { esodbc_stmt_st *stmt = STMH(hstmt); @@ -1189,8 +1278,6 @@ SQLRETURN EsSQLPrepareW ret = EsSQLFreeStmt(stmt, ESODBC_SQL_CLOSE); assert(SQL_SUCCEEDED(ret)); /* can't return error */ - // TODO: escaping? - return attach_sql(stmt, szSqlStr, cchSqlStr); } @@ -1231,9 +1318,9 @@ SQLRETURN EsSQLExecute(SQLHSTMT hstmt) */ SQLRETURN EsSQLExecDirectW ( - SQLHSTMT hstmt, - _In_reads_opt_(TextLength) SQLWCHAR* szSqlStr, - SQLINTEGER cchSqlStr + SQLHSTMT hstmt, + _In_reads_opt_(TextLength) SQLWCHAR* szSqlStr, + SQLINTEGER cchSqlStr ) { esodbc_stmt_st *stmt = STMH(hstmt); @@ -1247,8 +1334,6 @@ SQLRETURN EsSQLExecDirectW if (stmt->apd->array_size) FIXME; //FIXME: multiple executions? - // TODO: escaping? - ret = attach_sql(stmt, szSqlStr, cchSqlStr); if (SQL_SUCCEEDED(ret)) { ret = post_statement(stmt); @@ -1257,4 +1342,225 @@ SQLRETURN EsSQLExecDirectW return ret; } + +SQLRETURN EsSQLDescribeColW( + SQLHSTMT hstmt, + SQLUSMALLINT icol, + _Out_writes_opt_(cchColNameMax) + SQLWCHAR *szColName, + SQLSMALLINT cchColNameMax, + _Out_opt_ + SQLSMALLINT* pcchColName, + _Out_opt_ + SQLSMALLINT* pfSqlType, + _Out_opt_ + SQLULEN* pcbColDef, + _Out_opt_ + SQLSMALLINT* pibScale, + _Out_opt_ + SQLSMALLINT* pfNullable) +{ + esodbc_stmt_st *stmt = STMH(hstmt); + desc_rec_st *rec; + SQLRETURN ret; + SQLSMALLINT col_len = -1; + + DBG("STMT@0x%p, IRD@0x%p, column #%d.", stmt, stmt->ird, icol); + + if (! STMT_HAS_RESULTSET(stmt)) { + ERR("no resultset available on statement 0x%p.", stmt); + RET_HDIAGS(stmt, SQL_STATE_HY010); + } + + if (icol < 1) { + /* TODO: if implementing bookmarks */ + RET_HDIAGS(stmt, SQL_STATE_HYC00); + } + + rec = get_record(stmt->ird, icol, FALSE); + if (! rec) { + ERR("STMT@0x%p: no record for columns #%d.", stmt, icol); + RET_HDIAGS(stmt, SQL_STATE_07009); + } + + if (szColName) { + ret = write_tstr(&stmt->diag, szColName, rec->name, + cchColNameMax * sizeof(SQLTCHAR), &col_len); + if (! SQL_SUCCEEDED(ret)) { + ERR("STMT@0x%p: failed to copy column name `" LTPD "`.", stmt, + rec->name); + return ret; + } + } else { + DBG("STMT@0x%p: NULL column name buffer provided.", stmt); + } + + if (! pcchColName) { + /* TODO: STMT_ERR -> ERR("STMT@...", stmt, ...) */ + ERR("STMT@0x%p, no column name lenght buffer provided."); + RET_HDIAG(stmt, SQL_STATE_HY090, + "no column name lenght buffer provided", 0); + } + *pcchColName = 0 <= col_len ? col_len : (SQLSMALLINT)wcslen(rec->name); + + if (! pfSqlType) { + ERR("STMT@0x%p, no column data type buffer provided."); + RET_HDIAG(stmt, SQL_STATE_HY090, + "no column data type buffer provided", 0); + } + *pfSqlType = rec->concise_type; + + if (! pcbColDef) { + ERR("STMT@0x%p, no column size buffer provided."); + RET_HDIAG(stmt, SQL_STATE_HY090, "no column size buffer provided", 0); + } + *pcbColDef = 0; // TODO: concise to size + + if (! pibScale) { + ERR("STMT@0x%p, no column decimal digits buffer provided."); + RET_HDIAG(stmt, SQL_STATE_HY090, + "no column decimal digits buffer provided", 0); + } + *pibScale = 0; // TODO: concise to decimal digits + + if (! pfNullable) { + ERR("STMT@0x%p, no column decimal digits buffer provided."); + RET_HDIAG(stmt, SQL_STATE_HY090, + "no column decimal digits buffer provided", 0); + } + /* TODO: this would be available in SQLColumns resultset. */ + *pfNullable = SQL_NULLABLE_UNKNOWN; + + return SQL_SUCCESS; +} + + +SQLRETURN EsSQLColAttributeW( + SQLHSTMT hstmt, + SQLUSMALLINT iCol, + SQLUSMALLINT iField, + _Out_writes_bytes_opt_(cbDescMax) + SQLPOINTER pCharAttr, /* [out] value, if string; can be NULL */ + SQLSMALLINT cbDescMax, /* [in] byte len of pCharAttr */ + _Out_opt_ + SQLSMALLINT *pcbCharAttr, /* [out] len written in pCharAttr (w/o \0 */ + _Out_opt_ +#ifdef _WIN64 + SQLLEN *pNumAttr /* [out] value, if numeric */ +#else /* _WIN64 */ + SQLPOINTER pNumAttr +#endif /* _WIN64 */ +) +{ + esodbc_stmt_st *stmt = STMH(hstmt); + esodbc_desc_st *ird = stmt->ird; + desc_rec_st *rec; + SQLSMALLINT sint; + SQLTCHAR *tstr; + SQLLEN len; + SQLINTEGER iint; + +#ifdef _WIN64 +#define PNUMATTR_ASSIGN(type, value) *pNumAttr = (SQLLEN)(value) +#else /* _WIN64 */ +#define PNUMATTR_ASSIGN(type, value) *(type *)pNumAttr = (type)(value) +#endif /* _WIN64 */ + + DBG("STMT@0x%p, IRD@0x%p, column #%d, field: %d.", stmt, ird, iCol,iField); + + if (! STMT_HAS_RESULTSET(stmt)) { + ERR("no resultset available on statement 0x%p.", stmt); + RET_HDIAGS(stmt, SQL_STATE_HY010); + } + + if (iCol < 1) { + /* TODO: if implementing bookmarks */ + RET_HDIAGS(stmt, SQL_STATE_HYC00); + } + + rec = get_record(ird, iCol, FALSE); + if (! rec) { + ERR("STMT@0x%p: no record for columns #%d.", stmt, iCol); + RET_HDIAGS(stmt, SQL_STATE_07009); + } + + switch (iField) { + /* SQLSMALLINT */ + do { + case SQL_DESC_CONCISE_TYPE: sint = rec->concise_type; break; + case SQL_DESC_TYPE: sint = rec->type; break; + case SQL_DESC_FIXED_PREC_SCALE: sint = rec->fixed_prec_scale; break; + case SQL_DESC_NULLABLE: sint = rec->nullable; break; + case SQL_DESC_PRECISION: sint = rec->precision; break; + case SQL_DESC_SCALE: sint = rec->scale; break; + case SQL_DESC_SEARCHABLE: sint = rec->searchable; break; + case SQL_DESC_UNNAMED: sint = rec->unnamed; break; + case SQL_DESC_UNSIGNED: sint = rec->usigned; break; + case SQL_DESC_UPDATABLE: sint = rec->updatable; break; + } while (0); + PNUMATTR_ASSIGN(SQLSMALLINT, sint); + break; + + /* SQLTCHAR* */ + do { + case SQL_DESC_BASE_COLUMN_NAME: tstr = rec->base_column_name; break; + case SQL_DESC_BASE_TABLE_NAME: tstr = rec->base_table_name; break; + case SQL_DESC_CATALOG_NAME: tstr = rec->catalog_name; break; + case SQL_DESC_LABEL: tstr = rec->label; break; + case SQL_DESC_LITERAL_PREFIX: tstr = rec->literal_prefix; break; + case SQL_DESC_LITERAL_SUFFIX: tstr = rec->literal_suffix; break; + case SQL_DESC_LOCAL_TYPE_NAME: tstr = rec->type_name; break; + case SQL_DESC_NAME: tstr = rec->name; break; + case SQL_DESC_SCHEMA_NAME: tstr = rec->schema_name; break; + case SQL_DESC_TABLE_NAME: tstr = rec->table_name; break; + case SQL_DESC_TYPE_NAME: tstr = rec->type_name; break; + } while (0); + if (! tstr) { + //BUG -- TODO: re-eval, once type handling is decided. + ERR("STMT@0x%p IRD@0x%p record field type %d not initialized.", + stmt, ird, iField); + *(SQLTCHAR **)pCharAttr = MK_TSTR(""); + *pcbCharAttr = 0; + } else { + return write_tstr(&stmt->diag, pcbCharAttr, tstr, cbDescMax, + pcbCharAttr); + } + break; + + /* SQLLEN */ + do { + case SQL_DESC_DISPLAY_SIZE: len = rec->display_size; break; + case SQL_DESC_OCTET_LENGTH: len = rec->octet_length; break; + } while (0); + PNUMATTR_ASSIGN(SQLLEN, len); + break; + + /* SQLULEN */ + case SQL_DESC_LENGTH: + PNUMATTR_ASSIGN(SQLULEN, rec->length); + break; + + /* SQLINTEGER */ + do { + case SQL_DESC_AUTO_UNIQUE_VALUE: iint = rec->auto_unique_value; + case SQL_DESC_CASE_SENSITIVE: iint = rec->case_sensitive; + case SQL_DESC_NUM_PREC_RADIX: iint = rec->num_prec_radix; + } while (0); + PNUMATTR_ASSIGN(SQLINTEGER, iint); + break; + + + case SQL_DESC_COUNT: + PNUMATTR_ASSIGN(SQLSMALLINT, ird->count); + break; + + default: + ERR("STMT@0x%p: unknown field type %d.", stmt, iField); + RET_HDIAGS(stmt, SQL_STATE_HY091); + } + + return SQL_SUCCESS; +#undef PNUMATTR_ASSIGN +} + /* vim: set noet fenc=utf-8 ff=dos sts=0 sw=4 ts=4 : */ diff --git a/driver/queries.h b/driver/queries.h index 1b0fdfc3..08f797a1 100644 --- a/driver/queries.h +++ b/driver/queries.h @@ -56,14 +56,47 @@ SQLRETURN EsSQLNumResultCols(SQLHSTMT StatementHandle, _Out_ SQLSMALLINT *ColumnCount); SQLRETURN EsSQLPrepareW( - SQLHSTMT hstmt, - _In_reads_(cchSqlStr) SQLWCHAR* szSqlStr, - SQLINTEGER cchSqlStr); + SQLHSTMT hstmt, + _In_reads_(cchSqlStr) SQLWCHAR* szSqlStr, + SQLINTEGER cchSqlStr); SQLRETURN EsSQLExecute(SQLHSTMT hstmt); SQLRETURN EsSQLExecDirectW( - SQLHSTMT hstmt, - _In_reads_opt_(TextLength) SQLWCHAR* szSqlStr, - SQLINTEGER cchSqlStr); + SQLHSTMT hstmt, + _In_reads_opt_(TextLength) SQLWCHAR* szSqlStr, + SQLINTEGER cchSqlStr); + +SQLRETURN EsSQLDescribeColW( + SQLHSTMT hstmt, + SQLUSMALLINT icol, + _Out_writes_opt_(cchColNameMax) + SQLWCHAR *szColName, + SQLSMALLINT cchColNameMax, + _Out_opt_ + SQLSMALLINT* pcchColName, + _Out_opt_ + SQLSMALLINT* pfSqlType, + _Out_opt_ + SQLULEN* pcbColDef, + _Out_opt_ + SQLSMALLINT* pibScale, + _Out_opt_ + SQLSMALLINT* pfNullable); +SQLRETURN EsSQLColAttributeW( + SQLHSTMT hstmt, + SQLUSMALLINT iCol, + SQLUSMALLINT iField, + _Out_writes_bytes_opt_(cbDescMax) + SQLPOINTER pCharAttr, + SQLSMALLINT cbDescMax, + _Out_opt_ + SQLSMALLINT *pcbCharAttr, + _Out_opt_ +#ifdef _WIN64 + SQLLEN *pNumAttr +#else /* _WIN64 */ + SQLPOINTER pNumAttr +#endif /* _WIN64 */ +); #endif /* __QUERIES_H__ */ diff --git a/driver/tracing.h b/driver/tracing.h index 26fee6e5..bdb738be 100644 --- a/driver/tracing.h +++ b/driver/tracing.h @@ -14,6 +14,7 @@ #define _AVAIL sizeof(_bf) - _ps +#if 0 #define _PRINT_PARAM_VAL(type, val) \ do { \ switch(type) { \ @@ -36,11 +37,109 @@ if (0 < _n) \ _ps += _n; \ } while (0) +#else +/* TODO: the SQL[U]LEN for _WIN32 */ +#define _PRINT_PARAM_VAL(type, val) \ + do { \ + switch(type) { \ + /* numeric pointers */ \ + /* SQLNUMERIC/SQLDATE/SQLDECIMAL/SQLCHAR/etc. = unsigned char */ \ + /* SQLSCHAR = char */ \ + case 'c': /* char signed */ \ + _n = snprintf(_bf + _ps, _AVAIL, "%hhd", \ + val ? *(char *)(uintptr_t)val : 0); \ + break; \ + case 'C': /* char unsigned */ \ + _n = snprintf(_bf + _ps, _AVAIL, "%hhu", \ + val ? *(unsigned char *)(uintptr_t)val : 0); \ + break; \ + /* SQL[U]SMALLINT = [unsigned] short */ \ + case 't': /* short signed */ \ + _n = snprintf(_bf + _ps, _AVAIL, "%hd", \ + val ? *(short *)(uintptr_t)val : 0); \ + break; \ + case 'T': /* short unsigned */ \ + _n = snprintf(_bf + _ps, _AVAIL, "%hu", \ + val ? *(unsigned short *)(uintptr_t)val : 0); \ + break; \ + /* SQL[U]INTEGER = [unsigned] long */ \ + case 'g': /* long signed */ \ + _n = snprintf(_bf + _ps, _AVAIL, "%ld", \ + val ? *(long *)(uintptr_t)val : 0); \ + break; \ + case 'G': /* long unsigned */ \ + _n = snprintf(_bf + _ps, _AVAIL, "%lu", \ + val ? *(unsigned long *)(uintptr_t)val : 0); \ + break; \ + /* SQL[U]LEN = [unsigned] long OR [u]int64_t (64b _WIN32) */ \ + case 'n': /* long/int64_t signed */ \ + _n = snprintf(_bf + _ps, _AVAIL, "%lld", \ + val ? *(int64_t *)(uintptr_t)val : 0); \ + break; \ + case 'N': /* long/int64_t unsigned */ \ + _n = snprintf(_bf + _ps, _AVAIL, "%llu", \ + val ? *(uint64_t *)(uintptr_t)val : 0); \ + /* non-numeric pointers */ \ + case 'p': /* void* */ \ + _n = snprintf(_bf + _ps, _AVAIL, "@0x%p", \ + (void *)(uintptr_t)val); \ + break; \ + case 'W': /* wchar_t* */ \ + /* TODO: this can be problematic, for untouched buffs: add + * len! */ \ + _n = snprintf(_bf + _ps, _AVAIL, "`" LTPD "`[%zd]", \ + val ? (wchar_t *)(uintptr_t)val : \ + MK_WSTR(""), \ + val ? wcslen((wchar_t *)(uintptr_t)val) : 0); \ + break; \ + /* imediat values */ \ + /* longs */ \ + case 'l': /* long signed */ \ + _n = snprintf(_bf + _ps, _AVAIL, "%ld", \ + (long)(intptr_t)val); \ + break;\ + case 'L': /* long unsigned */ \ + _n = snprintf(_bf + _ps, _AVAIL, "%lu", \ + (unsigned long)(uintptr_t)val); \ + break;\ + /* ints */ \ + case 'd': /* int signed */ \ + _n = snprintf(_bf + _ps, _AVAIL, "%d", \ + (int)(intptr_t)val); \ + break;\ + case 'u': /* int unsigned */ \ + _n = snprintf(_bf + _ps, _AVAIL, "%u", \ + (unsigned)(uintptr_t)val); \ + break;\ + default: \ + _n = snprintf(_bf+_ps, _AVAIL, "BUG! unknown type: %d",type); \ + break; \ + } \ + if (0 < _n) \ + _ps += _n; \ + } while (0) +#endif + +#define _IS_PTR(type, _is_ptr) \ + do {\ + switch (type) { \ + case 'l': \ + case 'L': \ + case 'd': \ + case 'u': \ + _is_ptr = FALSE; \ + break; \ + default: \ + _is_ptr = TRUE; \ + } \ + } while (0) #define _PRINT_PARAM(type, param, add_comma) \ do { \ + BOOL _is_ptr; \ + _IS_PTR(type, _is_ptr); \ int _n = snprintf(_bf + _ps, _AVAIL, "%s%s%s=", add_comma ? ", " : "",\ - type&0x20 ? "" : "*", # param); \ + _is_ptr ? "*" : "", # param); \ if (0 < _n) /* no proper err check */ \ _ps += _n; \ _PRINT_PARAM_VAL(type, param); \