@@ -1335,24 +1335,26 @@ int OdbcConvert::conv##TYPE_FROM##ToString(DescRecord * from, DescRecord * to)
13351335 \
13361336 ODBCCONVERT_CHECKNULL ( pointer ); \
13371337 \
1338- /* Issue #161: when writing into a Firebird input buffer, force sqltype to */ \
1339- /* SQL_TEXT. This routine writes raw ASCII digits at offset 0 of the target */ \
1340- /* buffer — that is the correct layout for SQL_TEXT, but for SQL_VARYING the */ \
1341- /* first 2 bytes are reserved for a length prefix. Leaving the buffer as */ \
1342- /* SQL_VARYING causes Firebird to read the digits as the length prefix, */ \
1343- /* silently corrupting the parameter (e.g. INTEGER→VARCHAR stored-procedure */ \
1344- /* parameters would swallow the data and commit only a fraction of rows). */ \
1345- if ( to->isIndicatorSqlDa ) \
1346- to->headSqlVarPtr ->setTypeText (); \
1338+ /* Issue #161: the buffer layout differs between SQL_TEXT and SQL_VARYING */ \
1339+ /* on the Firebird side — VARYING reserves the first two bytes for a length */ \
1340+ /* prefix. Detect the target at run-time and write accordingly. We do NOT */ \
1341+ /* mutate sqltype/sqllen so the prepared metadata stays intact (recent FB */ \
1342+ /* versions reject metadata where sqllen is not a multiple of the charset */ \
1343+ /* element size — e.g. sqllen=1 on a UTF-8 VARCHAR column — leading to */ \
1344+ /* stack-overflow crashes at execute time). */ \
1345+ const bool isVarying = \
1346+ to->isIndicatorSqlDa && to->headSqlVarPtr ->isSqlVarying (); \
1347+ char *writeStart = (char *)pointer + (isVarying ? sizeof (short ) : 0 ); \
1348+ int maxLen = to->length ; \
13471349 \
1348- int len = to-> length ; \
1350+ int len = maxLen; \
13491351 \
1350- if ( !len && to->dataPtr ) \
1352+ if ( !maxLen && to->dataPtr ) \
13511353 *(char *)to->dataPtr = 0 ; \
13521354 else \
13531355 { /* Original source from IscDbc/Value.cpp */ \
13541356 C_TYPE_FROM number = *(C_TYPE_FROM *)getAdressBindDataFrom ((char *)from->dataPtr ); \
1355- char *string = ( char *)pointer; \
1357+ char *string = writeStart; \
13561358 int scale = -from->scale ; \
13571359 \
13581360 if (number == 0 ) \
@@ -1397,8 +1399,8 @@ int OdbcConvert::conv##TYPE_FROM##ToString(DescRecord * from, DescRecord * to)
13971399 if (negative) \
13981400 *q++ = ' -' ,++l; \
13991401 \
1400- if ( p - temp > len - l ) \
1401- p = temp + len - l; \
1402+ if ( p - temp > maxLen - l ) \
1403+ p = temp + maxLen - l; \
14021404 \
14031405 while (p > temp) \
14041406 *q++ = *--p; \
@@ -1408,10 +1410,16 @@ int OdbcConvert::conv##TYPE_FROM##ToString(DescRecord * from, DescRecord * to)
14081410 } \
14091411 } \
14101412 \
1411- if ( to->isIndicatorSqlDa ) { \
1413+ if ( isVarying ) \
1414+ { \
1415+ /* Write the VARYING length prefix; leave the sqlvar's metadata alone. */ \
1416+ *(unsigned short *)pointer = (unsigned short )len; \
1417+ } \
1418+ else if ( to->isIndicatorSqlDa ) \
1419+ { \
14121420 to->headSqlVarPtr ->setSqlLen (len); \
1413- } else \
1414- if ( indicatorTo ) \
1421+ } \
1422+ else if ( indicatorTo ) \
14151423 setIndicatorPtr ( indicatorTo, len, to ); \
14161424 \
14171425 return SQL_SUCCESS ; \
@@ -1426,11 +1434,9 @@ int OdbcConvert::conv##TYPE_FROM##ToStringW(DescRecord * from, DescRecord * to)
14261434 \
14271435 ODBCCONVERT_CHECKNULLW ( pointer ); \
14281436 \
1429- /* Issue #161: see ODBCCONVERT_CONV_TO_STRING — same reasoning for the wide */ \
1430- /* variant. Data is written at offset 0 without a length prefix, so the */ \
1431- /* target must be SQL_TEXT when it is a Firebird input buffer. */ \
1432- if ( to->isIndicatorSqlDa ) \
1433- to->headSqlVarPtr ->setTypeText (); \
1437+ /* Issue #161: getAdressFunction routes Firebird-side numeric-to-string */ \
1438+ /* binds to the byte variant, so this wide variant is only reached with */ \
1439+ /* application-owned targets. No VARYING prefix handling is needed here. */ \
14341440 \
14351441 int len = to->length ; \
14361442 \
@@ -1814,16 +1820,23 @@ int OdbcConvert::convFloatToString(DescRecord * from, DescRecord * to)
18141820
18151821 ODBCCONVERT_CHECKNULL ( pointerTo );
18161822
1817- // Issue #161: see ODBCCONVERT_CONV_TO_STRING — force SQL_TEXT layout.
1818- if ( to->isIndicatorSqlDa )
1819- to->headSqlVarPtr ->setTypeText ();
1823+ // Issue #161: write the VARYING length prefix when the target is a
1824+ // Firebird VARYING buffer; see ODBCCONVERT_CONV_TO_STRING for rationale.
1825+ const bool isVarying =
1826+ to->isIndicatorSqlDa && to->headSqlVarPtr ->isSqlVarying ();
1827+ char *writeStart = pointerTo + (isVarying ? sizeof (short ) : 0 );
1828+ int maxLen = to->length ;
18201829
1821- int len = to-> length ;
1830+ int len = maxLen ;
18221831
1823- if ( len )
1824- ConvertFloatToString<char >(*(float *)getAdressBindDataFrom ((char *)from->dataPtr ), pointerTo, len , &len);
1832+ if ( maxLen )
1833+ ConvertFloatToString<char >(*(float *)getAdressBindDataFrom ((char *)from->dataPtr ), writeStart, maxLen , &len);
18251834
1826- if ( to->isIndicatorSqlDa ) {
1835+ if ( isVarying )
1836+ {
1837+ *(unsigned short *)pointerTo = (unsigned short )len;
1838+ }
1839+ else if ( to->isIndicatorSqlDa ) {
18271840 to->headSqlVarPtr ->setSqlLen (len);
18281841 } else
18291842 if ( indicatorTo )
@@ -1840,9 +1853,8 @@ int OdbcConvert::convFloatToStringW(DescRecord * from, DescRecord * to)
18401853
18411854 ODBCCONVERT_CHECKNULLW ( pointerTo );
18421855
1843- // Issue #161: see ODBCCONVERT_CONV_TO_STRING — force SQL_TEXT layout.
1844- if ( to->isIndicatorSqlDa )
1845- to->headSqlVarPtr ->setTypeText ();
1856+ // Issue #161: wide variant is only reached with application-owned targets
1857+ // — see ODBCCONVERT_CONV_TO_STRINGW.
18461858
18471859 int len = to->length ;
18481860
@@ -1929,16 +1941,22 @@ int OdbcConvert::convDoubleToString(DescRecord * from, DescRecord * to)
19291941
19301942 ODBCCONVERT_CHECKNULL ( pointerTo );
19311943
1932- // Issue #161: see ODBCCONVERT_CONV_TO_STRING — force SQL_TEXT layout.
1933- if ( to->isIndicatorSqlDa )
1934- to->headSqlVarPtr ->setTypeText ();
1944+ // Issue #161: see ODBCCONVERT_CONV_TO_STRING for rationale.
1945+ const bool isVarying =
1946+ to->isIndicatorSqlDa && to->headSqlVarPtr ->isSqlVarying ();
1947+ char *writeStart = pointerTo + (isVarying ? sizeof (short ) : 0 );
1948+ int maxLen = to->length ;
19351949
1936- int len = to-> length ;
1950+ int len = maxLen ;
19371951
1938- if ( len ) // MAX_DOUBLE_DIGIT_LENGTH = 15
1939- ConvertFloatToString<char >(*(double *)getAdressBindDataFrom ((char *)from->dataPtr ), pointerTo, len , &len);
1952+ if ( maxLen ) // MAX_DOUBLE_DIGIT_LENGTH = 15
1953+ ConvertFloatToString<char >(*(double *)getAdressBindDataFrom ((char *)from->dataPtr ), writeStart, maxLen , &len);
19401954
1941- if ( to->isIndicatorSqlDa ) {
1955+ if ( isVarying )
1956+ {
1957+ *(unsigned short *)pointerTo = (unsigned short )len;
1958+ }
1959+ else if ( to->isIndicatorSqlDa ) {
19421960 to->headSqlVarPtr ->setSqlLen (len);
19431961 } else
19441962 if ( indicatorTo )
@@ -1955,9 +1973,8 @@ int OdbcConvert::convDoubleToStringW(DescRecord * from, DescRecord * to)
19551973
19561974 ODBCCONVERT_CHECKNULLW ( pointerTo );
19571975
1958- // Issue #161: see ODBCCONVERT_CONV_TO_STRING — force SQL_TEXT layout.
1959- if ( to->isIndicatorSqlDa )
1960- to->headSqlVarPtr ->setTypeText ();
1976+ // Issue #161: wide variant is only reached with application-owned targets
1977+ // — see ODBCCONVERT_CONV_TO_STRINGW.
19611978
19621979 int len = to->length ;
19631980
@@ -2051,21 +2068,26 @@ int OdbcConvert::convDateToString(DescRecord * from, DescRecord * to)
20512068
20522069 ODBCCONVERT_CHECKNULL ( pointer );
20532070
2054- // Issue #161: see ODBCCONVERT_CONV_TO_STRING — force SQL_TEXT layout.
2055- if ( to->isIndicatorSqlDa )
2056- to->headSqlVarPtr ->setTypeText ();
2071+ // Issue #161: see ODBCCONVERT_CONV_TO_STRING for rationale.
2072+ const bool isVarying =
2073+ to->isIndicatorSqlDa && to->headSqlVarPtr ->isSqlVarying ();
2074+ char *writeStart = pointer + (isVarying ? sizeof (short ) : 0 );
20572075
20582076 SQLUSMALLINT mday, month;
20592077 SQLSMALLINT year;
20602078
20612079 decode_sql_date (*(int *)getAdressBindDataFrom ((char *)from->dataPtr ), mday, month, year);
20622080 int len, outlen = to->length ;
20632081
2064- len = snprintf (pointer , outlen, " %04d-%02d-%02d" ,year,month,mday);
2082+ len = snprintf (writeStart , outlen, " %04d-%02d-%02d" ,year,month,mday);
20652083
20662084 if ( len == -1 ) len = outlen;
20672085
2068- if ( to->isIndicatorSqlDa ) {
2086+ if ( isVarying )
2087+ {
2088+ *(unsigned short *)pointer = (unsigned short )len;
2089+ }
2090+ else if ( to->isIndicatorSqlDa ) {
20692091 to->headSqlVarPtr ->setSqlLen (len);
20702092 } else
20712093 if ( indicatorTo )
@@ -2082,9 +2104,8 @@ int OdbcConvert::convDateToStringW(DescRecord * from, DescRecord * to)
20822104
20832105 ODBCCONVERT_CHECKNULLW ( pointer );
20842106
2085- // Issue #161: see ODBCCONVERT_CONV_TO_STRING — force SQL_TEXT layout.
2086- if ( to->isIndicatorSqlDa )
2087- to->headSqlVarPtr ->setTypeText ();
2107+ // Issue #161: wide variant is only reached with application-owned targets
2108+ // — see ODBCCONVERT_CONV_TO_STRINGW.
20882109
20892110 SQLUSMALLINT mday, month;
20902111 SQLSMALLINT year;
@@ -2236,9 +2257,10 @@ int OdbcConvert::convTimeToString(DescRecord * from, DescRecord * to)
22362257
22372258 ODBCCONVERT_CHECKNULL ( pointer );
22382259
2239- // Issue #161: see ODBCCONVERT_CONV_TO_STRING — force SQL_TEXT layout.
2240- if ( to->isIndicatorSqlDa )
2241- to->headSqlVarPtr ->setTypeText ();
2260+ // Issue #161: see ODBCCONVERT_CONV_TO_STRING for rationale.
2261+ const bool isVarying =
2262+ to->isIndicatorSqlDa && to->headSqlVarPtr ->isSqlVarying ();
2263+ char *writeStart = pointer + (isVarying ? sizeof (short ) : 0 );
22422264
22432265 SQLUSMALLINT hour, minute, second;
22442266 int ntime = *(int *)getAdressBindDataFrom ((char *)from->dataPtr );
@@ -2249,13 +2271,17 @@ int OdbcConvert::convTimeToString(DescRecord * from, DescRecord * to)
22492271 int len, outlen = to->length ;
22502272
22512273 if ( nnano )
2252- len = snprintf (pointer , outlen, " %02d:%02d:%02d.%04lu" ,hour, minute, second, nnano);
2274+ len = snprintf (writeStart , outlen, " %02d:%02d:%02d.%04lu" ,hour, minute, second, nnano);
22532275 else
2254- len = snprintf (pointer , outlen, " %02d:%02d:%02d" ,hour, minute, second);
2276+ len = snprintf (writeStart , outlen, " %02d:%02d:%02d" ,hour, minute, second);
22552277
22562278 if ( len == -1 ) len = outlen;
22572279
2258- if ( to->isIndicatorSqlDa ) {
2280+ if ( isVarying )
2281+ {
2282+ *(unsigned short *)pointer = (unsigned short )len;
2283+ }
2284+ else if ( to->isIndicatorSqlDa ) {
22592285 to->headSqlVarPtr ->setSqlLen (len);
22602286 } else
22612287 if ( indicatorTo )
@@ -2272,9 +2298,8 @@ int OdbcConvert::convTimeToStringW(DescRecord * from, DescRecord * to)
22722298
22732299 ODBCCONVERT_CHECKNULLW ( pointer );
22742300
2275- // Issue #161: see ODBCCONVERT_CONV_TO_STRING — force SQL_TEXT layout.
2276- if ( to->isIndicatorSqlDa )
2277- to->headSqlVarPtr ->setTypeText ();
2301+ // Issue #161: wide variant is only reached with application-owned targets
2302+ // — see ODBCCONVERT_CONV_TO_STRINGW.
22782303
22792304 SQLUSMALLINT hour, minute, second;
22802305 int ntime = *(int *)getAdressBindDataFrom ((char *)from->dataPtr );
@@ -2436,9 +2461,10 @@ int OdbcConvert::convDateTimeToString(DescRecord * from, DescRecord * to)
24362461
24372462 ODBCCONVERT_CHECKNULL ( pointer );
24382463
2439- // Issue #161: see ODBCCONVERT_CONV_TO_STRING — force SQL_TEXT layout.
2440- if ( to->isIndicatorSqlDa )
2441- to->headSqlVarPtr ->setTypeText ();
2464+ // Issue #161: see ODBCCONVERT_CONV_TO_STRING for rationale.
2465+ const bool isVarying =
2466+ to->isIndicatorSqlDa && to->headSqlVarPtr ->isSqlVarying ();
2467+ char *writeStart = pointer + (isVarying ? sizeof (short ) : 0 );
24422468
24432469 QUAD pointerFrom = *(QUAD *)getAdressBindDataFrom ((char *)from->dataPtr );
24442470 int ndate = LO_LONG (pointerFrom);
@@ -2453,13 +2479,17 @@ int OdbcConvert::convDateTimeToString(DescRecord * from, DescRecord * to)
24532479 int len, outlen = to->length ;
24542480
24552481 if ( nnano )
2456- len = snprintf (pointer , outlen, " %04d-%02d-%02d %02d:%02d:%02d.%04lu" ,year,month,mday,hour, minute, second, nnano);
2482+ len = snprintf (writeStart , outlen, " %04d-%02d-%02d %02d:%02d:%02d.%04lu" ,year,month,mday,hour, minute, second, nnano);
24572483 else
2458- len = snprintf (pointer , outlen, " %04d-%02d-%02d %02d:%02d:%02d" ,year,month,mday,hour, minute, second);
2484+ len = snprintf (writeStart , outlen, " %04d-%02d-%02d %02d:%02d:%02d" ,year,month,mday,hour, minute, second);
24592485
24602486 if ( len == -1 ) len = outlen;
24612487
2462- if ( to->isIndicatorSqlDa ) {
2488+ if ( isVarying )
2489+ {
2490+ *(unsigned short *)pointer = (unsigned short )len;
2491+ }
2492+ else if ( to->isIndicatorSqlDa ) {
24632493 to->headSqlVarPtr ->setSqlLen (len);
24642494 } else
24652495 if ( indicatorTo )
@@ -2476,9 +2506,8 @@ int OdbcConvert::convDateTimeToStringW(DescRecord * from, DescRecord * to)
24762506
24772507 ODBCCONVERT_CHECKNULLW ( pointer );
24782508
2479- // Issue #161: see ODBCCONVERT_CONV_TO_STRING — force SQL_TEXT layout.
2480- if ( to->isIndicatorSqlDa )
2481- to->headSqlVarPtr ->setTypeText ();
2509+ // Issue #161: wide variant is only reached with application-owned targets
2510+ // — see ODBCCONVERT_CONV_TO_STRINGW.
24822511
24832512 QUAD pointerFrom = *(QUAD *)getAdressBindDataFrom ((char *)from->dataPtr );
24842513 int ndate = LO_LONG (pointerFrom);
0 commit comments