Skip to content

Commit bd4bccc

Browse files
committed
Fix varchar limitation when max-len-attr is set (#231)
* Fix varchar limitation when max-len-attr is set This fixes the result rewriting of the SQLColumns due to varchar limit setting in case the SQL_ATTR_MAX_LENGTH attribute is set. The attribute changes the lenght indicator given by SQLGetData to the value of the attribute (which a setting application expects). Since the rewriting is serialized on the statment, simply reset this attribute's value while doing the writing. The types of the columns in the rewritten result are also corrected (they were copied from ES/SQL output, which are wrong and need fixing). (cherry picked from commit c6556af)
1 parent 2d07ebe commit bd4bccc

File tree

9 files changed

+109
-16
lines changed

9 files changed

+109
-16
lines changed

driver/catalogue.c

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ SQLSMALLINT fetch_server_attr(esodbc_dbc_st *dbc, SQLINTEGER attr_id,
176176
} else {
177177
if (1 < row_cnt) {
178178
WARNH(dbc, "more than one value (%lld) available for "
179-
"attribute %ld; picking first.", row_cnt, attr_id);
179+
"attribute %ld; picking first.", (int64_t)row_cnt, attr_id);
180180
}
181181

182182
if (! SQL_SUCCEEDED(EsSQLBindCol(stmt, /*col#*/1, SQL_C_WCHAR, buff,
@@ -522,7 +522,7 @@ static SQLRETURN copy_one_cell(esodbc_stmt_st *stmt, wstr_st *dest,
522522
/* fetch data's length (always converted to w-string) */
523523
ret = EsSQLGetData(stmt, col_idx, SQL_C_WCHAR, dest->str, 0, &len_ind);
524524
if (! SQL_SUCCEEDED(ret)) {
525-
ERRH(stmt, "failed to get data lenght for cell@[%ld, %hd].",
525+
ERRH(stmt, "failed to get data length for cell@[%ld, %hd].",
526526
row_cnt, col_idx);
527527
return ret;
528528
}
@@ -577,17 +577,17 @@ SQLRETURN TEST_API update_varchar_defs(esodbc_stmt_st *stmt)
577577
"{\"name\":\"TABLE_SCHEM\", \"type\":\"keyword\"},"
578578
"{\"name\":\"TABLE_NAME\", \"type\":\"keyword\"},"
579579
"{\"name\":\"COLUMN_NAME\", \"type\":\"keyword\"},"
580-
"{\"name\":\"DATA_TYPE\", \"type\":\"integer\"},"
580+
"{\"name\":\"DATA_TYPE\", \"type\":\"short\"},"
581581
"{\"name\":\"TYPE_NAME\", \"type\":\"keyword\"},"
582582
"{\"name\":\"COLUMN_SIZE\", \"type\":\"integer\"},"
583583
"{\"name\":\"BUFFER_LENGTH\", \"type\":\"integer\"},"
584-
"{\"name\":\"DECIMAL_DIGITS\", \"type\":\"integer\"},"
585-
"{\"name\":\"NUM_PREC_RADIX\", \"type\":\"integer\"},"
586-
"{\"name\":\"NULLABLE\", \"type\":\"integer\"},"
584+
"{\"name\":\"DECIMAL_DIGITS\", \"type\":\"short\"},"
585+
"{\"name\":\"NUM_PREC_RADIX\", \"type\":\"short\"},"
586+
"{\"name\":\"NULLABLE\", \"type\":\"short\"},"
587587
"{\"name\":\"REMARKS\", \"type\":\"keyword\"},"
588588
"{\"name\":\"COLUMN_DEF\", \"type\":\"keyword\"},"
589-
"{\"name\":\"SQL_DATA_TYPE\", \"type\":\"integer\"},"
590-
"{\"name\":\"SQL_DATETIME_SUB\", \"type\":\"integer\"},"
589+
"{\"name\":\"SQL_DATA_TYPE\", \"type\":\"short\"},"
590+
"{\"name\":\"SQL_DATETIME_SUB\", \"type\":\"short\"},"
591591
"{\"name\":\"CHAR_OCTET_LENGTH\", \"type\":\"integer\"},"
592592
"{\"name\":\"ORDINAL_POSITION\", \"type\":\"integer\"},"
593593
"{\"name\":\"IS_NULLABLE\", \"type\":\"keyword\"}"
@@ -605,9 +605,15 @@ SQLRETURN TEST_API update_varchar_defs(esodbc_stmt_st *stmt)
605605
long row_cnt;
606606
wstr_st dest = {0};
607607
size_t pos;
608+
SQLULEN max_length;
608609
cstr_st u8mb;
609610
wstr_st *lim = &HDRH(stmt)->dbc->varchar_limit_str;
610611

612+
/* save and reset SQL_ATTR_MAX_LENGTH attribute, it'll interfere with
613+
* reading length of avail data with SQLGetData() otherwise. */
614+
max_length = stmt->max_length;
615+
stmt->max_length = 0;
616+
611617
/* check that we have as many columns as members in target row struct */
612618
ret = EsSQLNumResultCols(stmt, &col_cnt);
613619
if (! SQL_SUCCEEDED(ret)) {
@@ -689,6 +695,8 @@ SQLRETURN TEST_API update_varchar_defs(esodbc_stmt_st *stmt)
689695
free(dest.str);
690696
dest.cnt = 0;
691697
}
698+
/* reinstate any saved SQL_ATTR_MAX_LENGTH value */
699+
stmt->max_length = max_length;
692700
return ret;
693701
}
694702

driver/convert.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ static thread_local struct tm today;
5252
} while (0)
5353

5454
#define DBL_BASE10_MAX_LEN /*-0.*/3 + DBL_DIG - DBL_MIN_10_EXP
55-
/* maximum lenght of an interval literal (with terminator; both ISO and SQL),
55+
/* maximum length of an interval literal (with terminator; both ISO and SQL),
5656
* with no field sanity checks: five longs with separators and sign */
5757
#define INTERVAL_VAL_MAX_LEN (5 * sizeof("4294967295"))
5858

@@ -2836,7 +2836,7 @@ static size_t print_interval_sec(esodbc_rec_st *rec, SQL_INTERVAL_STRUCT *ivl,
28362836

28372837
if (wide) {
28382838
wfmt[2] = L'0' + rec->precision;
2839-
/* printf's limits: max lenght of '<second>.<fraction>', accounted
2839+
/* printf's limits: max length of '<second>.<fraction>', accounted
28402840
* in buffer's max len estimation. */
28412841
res = swprintf((wchar_t *)dest, 2 * sizeof("4294967295") + 1,
28422842
wfmt, dbl);

driver/defs.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@
124124
*/
125125
/* maximum URL size */
126126
#define ESODBC_MAX_URL_LEN 2048
127-
/* maximum DNS attribute value lenght (should be long enought to accomodate a
127+
/* maximum DNS attribute value length (should be long enought to accomodate a
128128
* decently long FQ file path name) */
129129
#define ESODBC_DSN_MAX_ATTR_LEN 1024
130130

driver/dsn.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ int assign_dsn_attr(esodbc_dsn_attrs_st *attrs,
9393
ESODBC_DSN_ATTRS_COUNT);
9494

9595
if (ESODBC_DSN_MAX_ATTR_LEN < value->cnt) {
96-
ERR("attribute value lenght too large: %zu; max=%zu.", value->cnt,
96+
ERR("attribute value length too large: %zu; max=%zu.", value->cnt,
9797
ESODBC_DSN_MAX_ATTR_LEN);
9898
return -1;
9999
}
@@ -356,7 +356,7 @@ BOOL TEST_API parse_00_list(esodbc_dsn_attrs_st *attrs, SQLWCHAR *list00)
356356
cnt = wcslen(pos);
357357

358358
if (SHRT_MAX < cnt) {
359-
ERR("invalid list lenght (%zu).", cnt);
359+
ERR("invalid list length (%zu).", cnt);
360360
return FALSE;
361361
}
362362
if (! parse_connection_string(attrs, pos, (SQLSMALLINT)cnt)) {

driver/dsn.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ typedef struct {
9090
wstr_st trace_level;
9191
#define ESODBC_DSN_ATTRS_COUNT 29
9292
SQLWCHAR buff[ESODBC_DSN_ATTRS_COUNT * ESODBC_DSN_MAX_ATTR_LEN];
93-
/* DSN reading/writing functions are passed a SQLSMALLINT lenght param */
93+
/* DSN reading/writing functions are passed a SQLSMALLINT length param */
9494
#if SHRT_MAX < ESODBC_DSN_ATTRS_COUNT * ESODBC_DSN_MAX_ATTR_LEN
9595
#error "attrs buffer too large"
9696
#endif

driver/handles.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ typedef struct struct_dbc {
176176
/* maximum precision/length of types using same SQL data type ID */
177177
esodbc_estype_st *max_varchar_type; /* pointer to TEXT type */
178178
esodbc_estype_st *max_float_type; /* pointer to DOUBLE type */
179-
/* configuration imposed lenghts for the ES/SQL string types */
179+
/* configuration imposed lengths for the ES/SQL string types */
180180
SQLUINTEGER varchar_limit;
181181
wstr_st varchar_limit_str; /* convenience w-string of varchar limit */
182182

driver/util.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,7 @@ char *cstr_hex_dump(const cstr_st *buff);
336336
#endif /* _WIN32 */
337337

338338

339-
/* ISO time formats lenghts.
339+
/* ISO time formats lengths.
340340
* ES/SQL interface should only use UTC ('Z'ulu offset). */
341341
#define ISO8601_TIMESTAMP_LEN(prec) \
342342
(sizeof("yyyy-mm-ddThh:mm:ss+hh:mm") - /*\0*/1 + /*'.'*/!!prec + prec)

test/test_catalogue.cc

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,11 @@ TEST_F(Catalogue, Columns_update_varchar_defs) {
9292
"]"
9393
"}";
9494

95+
/* setting this attribute should not affect the outcome */
96+
ret = SQLSetStmtAttr(stmt, SQL_ATTR_MAX_LENGTH, (SQLPOINTER)INT_MAX,
97+
SQL_IS_UINTEGER);
98+
ASSERT_TRUE(SQL_SUCCEEDED(ret));
99+
95100
prepareStatement(response);
96101

97102
/* needs to be lower than ESODBC_MAX_KEYWORD_PRECISION (~32k) */
@@ -120,6 +125,26 @@ TEST_F(Catalogue, Columns_update_varchar_defs) {
120125
ASSERT_EQ(val, VARCHAR_LIMIT);
121126
}
122127

128+
/* above SYS COLUMNS result contains wrong (integer) type for the following
129+
* columns that should be short */
130+
const SQLUSMALLINT short_cols[] = {
131+
SQLCOLS_IDX_DATA_TYPE,
132+
SQLCOLS_IDX_DECIMAL_DIGITS,
133+
SQLCOLS_IDX_NUM_PREC_RADIX,
134+
SQLCOLS_IDX_NULLABLE,
135+
SQLCOLS_IDX_SQL_DATA_TYPE,
136+
SQLCOLS_IDX_SQL_DATETIME_SUB
137+
};
138+
SQLWCHAR col_name[128];
139+
SQLSMALLINT col_name_len, sql_type, scale, nullable;
140+
SQLULEN col_size;
141+
for (size_t i = 0; i < sizeof(short_cols)/sizeof(short_cols[0]); i ++) {
142+
ret = SQLDescribeCol(stmt, short_cols[i], col_name, sizeof(col_name),
143+
&col_name_len, &sql_type, &col_size, &scale, &nullable);
144+
ASSERT_TRUE(SQL_SUCCEEDED(ret));
145+
ASSERT_EQ(sql_type, SQL_SMALLINT);
146+
}
147+
123148
HDRH(stmt)->dbc->varchar_limit = 0;
124149
memset(&HDRH(stmt)->dbc->varchar_limit_str, 0, sizeof(wstr_st));
125150

test/test_queries.cc

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,66 @@ TEST_F(Queries, SQLDescribeCol_char) {
267267
ASSERT_EQ(nullable, SQL_NULLABLE_UNKNOWN);
268268
}
269269

270+
TEST_F(Queries, SQLDescribeCol_varchar_lim) {
271+
272+
# undef COL_NAME
273+
# define COL_NAME "SQLDescribeCol_varchar_lim"
274+
# define VARCHAR_LIMIT 333
275+
# define CONN_STR CONNECT_STRING "VarcharLimit=" STR(VARCHAR_LIMIT) ";"
276+
277+
const char json_answer[] = "\
278+
{\
279+
\"columns\": [\
280+
{\"name\": \"" COL_NAME "\", \"type\": \"text\"},\
281+
{\"name\": \"binary\", \"type\": \"binary\"}\
282+
],\
283+
\"rows\": [\
284+
[\"foo\", \"binary\"]\
285+
]\
286+
}\
287+
";
288+
289+
/* set varchar limit: this is set onto the varchar types structures, so the
290+
* DBC needs "reconnecting" and the corresponding DSN param set */
291+
ret = SQLFreeHandle(SQL_HANDLE_STMT, stmt);
292+
assert(SQL_SUCCEEDED(ret));
293+
ret = SQLFreeHandle(SQL_HANDLE_DBC, dbc);
294+
assert(SQL_SUCCEEDED(ret));
295+
ret = SQLAllocHandle(SQL_HANDLE_DBC, env, &dbc);
296+
assert(SQL_SUCCEEDED(ret));
297+
cstr_st types = {0};
298+
types.str = (SQLCHAR *)strdup(SYSTYPES_ANSWER);
299+
assert(types.str != NULL);
300+
types.cnt = sizeof(SYSTYPES_ANSWER) - 1;
301+
ret = SQLDriverConnect(dbc, (SQLHWND)&types, (SQLWCHAR *)CONN_STR,
302+
sizeof(CONN_STR) / sizeof(CONN_STR[0]) - 1, NULL, 0, NULL,
303+
ESODBC_SQL_DRIVER_TEST);
304+
assert(SQL_SUCCEEDED(ret));
305+
ret = SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt);
306+
assert(SQL_SUCCEEDED(ret));
307+
assert(stmt != NULL);
308+
309+
prepareStatement(json_answer);
310+
311+
SQLWCHAR col_name[sizeof(COL_NAME)];
312+
SQLSMALLINT col_name_len, sql_type, scale, nullable;
313+
SQLULEN col_size;
314+
/* text column */
315+
ret = SQLDescribeCol(stmt, /*col#*/1, col_name, sizeof(col_name),
316+
&col_name_len, &sql_type, &col_size, &scale, &nullable);
317+
ASSERT_TRUE(SQL_SUCCEEDED(ret));
318+
ASSERT_EQ(col_name_len, sizeof(COL_NAME) - 1);
319+
ASSERT_STREQ(col_name, MK_WPTR(COL_NAME));
320+
ASSERT_EQ(sql_type, ES_WVARCHAR_SQL);
321+
ASSERT_EQ(col_size, VARCHAR_LIMIT); /* limit enforced */
322+
ASSERT_EQ(nullable, SQL_NULLABLE_UNKNOWN);
323+
324+
/* binary column */
325+
ret = SQLDescribeCol(stmt, /*col#*/2, col_name, sizeof(col_name),
326+
&col_name_len, &sql_type, &col_size, &scale, &nullable);
327+
ASSERT_TRUE(SQL_SUCCEEDED(ret));
328+
ASSERT_EQ(col_size, INT_MAX); /* binary col's length must not be affected */
329+
}
270330

271331
} // test namespace
272332

0 commit comments

Comments
 (0)