From a8679f587b568780e541b70bb53460e80bd3d1e0 Mon Sep 17 00:00:00 2001 From: Dave Cramer Date: Wed, 7 Oct 2026 10:28:26 -0400 Subject: [PATCH] Report INT_MAX instead of SQL_NO_TOTAL for SQLGetTypeInfo COLUMN_SIZE (#215) With ByteaAsLongVarBinary=1 (the default), SQLGetTypeInfo reported COLUMN_SIZE = SQL_NO_TOTAL (-4) for bytea, and likewise for the large object type. SQL_NO_TOTAL is a length/indicator value, not a column size. MSDASQL binds COLUMN_SIZE as SQL_C_ULONG; before #210 the -4 silently wrapped to 4294967292, but since the 22003 range check was added SQLFetch fails on the bytea row, MSDASQL never learns the type, and ADO bytea parameters fail with "Type is invalid". Report INT_MAX (2147483647) instead, the largest size the INT4 column can hold. NULL is not an option: MSDASQL binds without an indicator, so a NULL would fail with 22002 instead. New typeinfo-column-size test fetches every SQLGetTypeInfo(SQL_ALL_TYPES) row with COLUMN_SIZE bound as SQL_C_ULONG and no indicator. Assisted-By: Claude --- info.c | 13 ++++++- test/expected/typeinfo-column-size.out | 3 ++ test/src/typeinfo-column-size-test.c | 54 ++++++++++++++++++++++++++ test/tests | 1 + 4 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 test/expected/typeinfo-column-size.out create mode 100644 test/src/typeinfo-column-size-test.c diff --git a/info.c b/info.c index 23623442..41f40e8c 100644 --- a/info.c +++ b/info.c @@ -21,6 +21,7 @@ #include #include +#include #ifndef WIN32 #include @@ -1246,6 +1247,7 @@ MYLOG(DETAIL_LOG_LEVEL, "%d sqltype=%d -> pgtype=%d\n", ci->bytea_as_longvarbina if (fSqlType == SQL_ALL_TYPES || fSqlType == sqlType) { int pgtcount = 1, aunq_match = -1, cnt; + Int4 column_size; /*if (SQL_INTEGER == sqlType || SQL_TINYINT == sqlType)*/ if (SQL_INTEGER == sqlType) @@ -1290,7 +1292,16 @@ MYLOG(DETAIL_LOG_LEVEL, "serial in\n"); set_tuplefield_null(&tuple[GETTYPE_LOCAL_TYPE_NAME]); /* These values can be NULL */ - set_nullfield_int4(&tuple[GETTYPE_COLUMN_SIZE], PGTYPE_COLUMN_SIZE(conn, pgType)); + column_size = PGTYPE_COLUMN_SIZE(conn, pgType); + /* + * COLUMN_SIZE is the maximum column size, so SQL_NO_TOTAL + * (a length/indicator value) is not valid here; applications + * such as MSDASQL bind it as SQL_C_ULONG, where -4 fails + * with 22003. Report the largest representable size. + */ + if (SQL_NO_TOTAL == column_size) + column_size = INT_MAX; + set_nullfield_int4(&tuple[GETTYPE_COLUMN_SIZE], column_size); set_nullfield_string(&tuple[GETTYPE_LITERAL_PREFIX], pgtype_literal_prefix(conn, pgType)); set_nullfield_string(&tuple[GETTYPE_LITERAL_SUFFIX], pgtype_literal_suffix(conn, pgType)); set_nullfield_string(&tuple[GETTYPE_CREATE_PARAMS], pgtype_create_params(conn, pgType)); diff --git a/test/expected/typeinfo-column-size.out b/test/expected/typeinfo-column-size.out new file mode 100644 index 00000000..2e7d6c0b --- /dev/null +++ b/test/expected/typeinfo-column-size.out @@ -0,0 +1,3 @@ +connected +bytea: COLUMN_SIZE=2147483647 +disconnecting diff --git a/test/src/typeinfo-column-size-test.c b/test/src/typeinfo-column-size-test.c new file mode 100644 index 00000000..bc423384 --- /dev/null +++ b/test/src/typeinfo-column-size-test.c @@ -0,0 +1,54 @@ +/* + * Test that every SQLGetTypeInfo(SQL_ALL_TYPES) row can be fetched with + * COLUMN_SIZE bound as SQL_C_ULONG and no indicator, the way MSDASQL binds + * it. bytea used to report SQL_NO_TOTAL (-4) there, which fails the 22003 + * range check for unsigned C types. (issue #215) + */ +#include +#include + +#include "common.h" + +int +main(int argc, char **argv) +{ + SQLRETURN rc; + HSTMT hstmt = SQL_NULL_HSTMT; + SQLCHAR type_name[64]; + SQLSMALLINT data_type; + SQLUINTEGER column_size; + + test_connect_ext("ByteaAsLongVarBinary=1"); + + rc = SQLAllocHandle(SQL_HANDLE_STMT, conn, &hstmt); + CHECK_CONN_RESULT(rc, "SQLAllocHandle failed", conn); + + rc = SQLGetTypeInfo(hstmt, SQL_ALL_TYPES); + CHECK_STMT_RESULT(rc, "SQLGetTypeInfo failed", hstmt); + + rc = SQLBindCol(hstmt, 1, SQL_C_CHAR, type_name, sizeof(type_name), NULL); + CHECK_STMT_RESULT(rc, "SQLBindCol failed", hstmt); + rc = SQLBindCol(hstmt, 2, SQL_C_SSHORT, &data_type, sizeof(data_type), NULL); + CHECK_STMT_RESULT(rc, "SQLBindCol failed", hstmt); + rc = SQLBindCol(hstmt, 3, SQL_C_ULONG, &column_size, sizeof(column_size), NULL); + CHECK_STMT_RESULT(rc, "SQLBindCol failed", hstmt); + + while ((rc = SQLFetch(hstmt)) != SQL_NO_DATA) + { + if (!SQL_SUCCEEDED(rc)) + { + print_diag("SQLFetch failed", SQL_HANDLE_STMT, hstmt); + break; + } + if (data_type == SQL_LONGVARBINARY) + printf("%s: COLUMN_SIZE=%lu\n", (char *) type_name, + (unsigned long) column_size); + } + + rc = SQLFreeStmt(hstmt, SQL_CLOSE); + CHECK_STMT_RESULT(rc, "SQLFreeStmt failed", hstmt); + + test_disconnect(); + + return 0; +} diff --git a/test/tests b/test/tests index 539ea0e3..359c351b 100644 --- a/test/tests +++ b/test/tests @@ -66,6 +66,7 @@ TESTBINS = exe/connect-test \ exe/primarykeys-include-test \ exe/interval-overflow-test \ exe/int-overflow-test \ + exe/typeinfo-column-size-test \ exe/conn-settings-test \ exe/percent-decode-test \ exe/connstring-escape-test \