php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Return to Bug #44278
Patch varcharmax-fix-v2.diff revision 2017-11-18 11:30 UTC by chris at ocproducts dot com
Patch varcharmax-fix.diff revision 2017-11-17 03:35 UTC by chris at ocproducts dot com

Patch varcharmax-fix-v2.diff for PDO ODBC Bug #44278

Patch version 2017-11-18 11:30 UTC

Return to Bug #44278 | Download this patch
Patch Revisions:

Developer: chris@ocproducts.com

diff --git a/ext/odbc/php_odbc.c b/ext/odbc/php_odbc.c
index f432dcd..cdcfe41 100644
--- a/ext/odbc/php_odbc.c
+++ b/ext/odbc/php_odbc.c
@@ -968,11 +968,14 @@ int odbc_bindcols(odbc_result *result)
 				result->values[i].name, sizeof(result->values[i].name), &colnamelen, 0);
 		rc = PHP_ODBC_SQLCOLATTRIBUTE(result->stmt, (SQLUSMALLINT)(i+1), SQL_COLUMN_TYPE, 
 				NULL, 0, NULL, &result->values[i].coltype);
-		
-		/* Don't bind LONG / BINARY columns, so that fetch behaviour can
-		 * be controlled by odbc_binmode() / odbc_longreadlen()
+
+		/* Don't bind LONG / BINARY columns:
+		 * a) so that fetch behaviour can be controlled by odbc_binmode()
+		 * b) ... or odbc_longreadlen(),
+		 * c) for performance reasons (user can do individual odbc_result calls)
+		 * (ODBC implementation may impose SqlGetData due to SQL_NO_TOTAL length anyway, handled if it happens)
 		 */
-		
+
 		switch(result->values[i].coltype) {
 			case SQL_BINARY:
 			case SQL_VARBINARY:
@@ -1024,23 +1027,36 @@ int odbc_bindcols(odbc_result *result)
 								NULL, 0, NULL, &displaysize);
 				}
 
-				/* Workaround for drivers that report NVARCHAR(MAX) columns as SQL_WVARCHAR with size 0 (bug #69975) */
+				/* Workaround for drivers that report NVARCHAR(MAX) columns as SQL_WVARCHAR with size 0 [SQL_SS_LENGTH_UNLIMITED] (bug #69975) */
 				if (result->values[i].coltype == SQL_WVARCHAR && displaysize == 0) {
 					result->values[i].coltype = SQL_WLONGVARCHAR;
 					result->values[i].value = NULL;
 					break;
 				}
 #endif
+				/* Workaround for drivers that report VARCHAR(MAX) columns as SQL_WVARCHAR with size 0 [SQL_SS_LENGTH_UNLIMITED] */
+				if (result->values[i].coltype == SQL_VARCHAR && displaysize == 0) {
+					result->values[i].coltype = SQL_LONGVARCHAR;
+					result->values[i].value = NULL;
+					break;
+				}
+
 				/* Workaround for Oracle ODBC Driver bug (#50162) when fetching TIMESTAMP column */
 				if (result->values[i].coltype == SQL_TIMESTAMP) {
 					displaysize += 3;
 				}
 
 				if (charextraalloc) {
-					/* Since we don't know the exact # of bytes, allocate extra */
+					/* Since we don't know the exact # of bytes, allocate extra. displaysize is smart enough to set length based on VARCHAR max length though */
 					displaysize *= 4;
 				}
 				result->values[i].value = (char *)emalloc(displaysize + 1);
+
+				/* It is conceivable that SQLBindCol will never actually execute filling up of the values[i] struct,
+				   due to long data and buggy ODBC implementation, so default it to say we need to do a long data call.
+				*/
+				result->values[i].vallen = SQL_NO_TOTAL;
+
 				rc = SQLBindCol(result->stmt, (SQLUSMALLINT)(i+1), SQL_C_CHAR, result->values[i].value,
 							displaysize + 1, &result->values[i].vallen);
 				break;
@@ -1775,6 +1791,11 @@ static void php_odbc_fetch_hash(INTERNAL_FUNCTION_PARAMETERS, int result_type)
 	for(i = 0; i < result->numcols; i++) {
 		sql_c_type = SQL_C_CHAR;
 
+		if (result->values[i].vallen < 0) {
+			/* Illegal value for doing emalloc, likely a SQL_NO_TOTAL (-4), meaning we need to do SQLGetData rather than just SQLBindCol */
+			result->values[i].coltype = SQL_LONGVARCHAR; /* A bit of a hack, but safe and this will allow the correct processing branch to flow */
+		}
+
 		switch(result->values[i].coltype) {
 			case SQL_BINARY:
 			case SQL_VARBINARY:
@@ -1929,6 +1950,11 @@ PHP_FUNCTION(odbc_fetch_into)
 	for(i = 0; i < result->numcols; i++) {
 		sql_c_type = SQL_C_CHAR;
 
+		if (result->values[i].vallen < 0) {
+			/* Illegal value for doing emalloc, likely a SQL_NO_TOTAL (-4), meaning we need to do SQLGetData rather than just SQLBindCol */
+			result->values[i].coltype = SQL_LONGVARCHAR; /* A bit of a hack, but safe and this will allow the correct processing branch to flow */
+		}
+		
 		switch(result->values[i].coltype) {
 			case SQL_BINARY:
 			case SQL_VARBINARY:
@@ -2155,6 +2181,11 @@ PHP_FUNCTION(odbc_result)
 		result->fetched++;
 	}
 
+	if (result->values[field_ind].vallen < 0) {
+		/* Illegal value for doing emalloc, likely a SQL_NO_TOTAL (-4), meaning we need to do SQLGetData rather than just SQLBindCol */
+		result->values[field_ind].coltype = SQL_LONGVARCHAR; /* A bit of a hack, but safe and this will allow the correct processing branch to flow */
+	}
+	
 	switch(result->values[field_ind].coltype) {
 		case SQL_BINARY:
 		case SQL_VARBINARY:
@@ -2320,6 +2351,11 @@ PHP_FUNCTION(odbc_result_all)
 		result->fetched++;
 		php_printf("<tr>");
 		for(i = 0; i < result->numcols; i++) {
+			if (result->values[i].vallen < 0) {
+				/* Illegal value for doing emalloc, likely a SQL_NO_TOTAL (-4), meaning we need to do SQLGetData rather than just SQLBindCol */
+				result->values[i].coltype = SQL_LONGVARCHAR; /* A bit of a hack, but safe and this will allow the correct processing branch to flow */
+			}
+		
 			sql_c_type = SQL_C_CHAR;
 			switch(result->values[i].coltype) {
 				case SQL_BINARY:
 
PHP Copyright © 2001-2019 The PHP Group
All rights reserved.
Last updated: Wed Oct 16 09:01:28 2019 UTC