2626#include "php_pdo_odbc.h"
2727#include "php_pdo_odbc_int.h"
2828
29+ /* Buffer size; bigger columns than this become a "long column" */
30+ #define LONG_COLUMN_BUFFER_SIZE (ZEND_MM_PAGE_SIZE- ZSTR_MAX_OVERHEAD)
31+
2932enum pdo_odbc_conv_result {
3033 PDO_ODBC_CONV_NOT_REQUIRED ,
3134 PDO_ODBC_CONV_OK ,
@@ -615,7 +618,7 @@ static int odbc_stmt_describe(pdo_stmt_t *stmt, int colno)
615618 /* tell ODBC to put it straight into our buffer, but only if it
616619 * isn't "long" data, and only if we haven't already bound a long
617620 * column. */
618- if (colsize < 256 && !S -> going_long ) {
621+ if (colsize < LONG_COLUMN_BUFFER_SIZE && !S -> going_long ) {
619622 S -> cols [colno ].data = emalloc (colsize + 1 );
620623 S -> cols [colno ].is_long = 0 ;
621624
@@ -631,7 +634,7 @@ static int odbc_stmt_describe(pdo_stmt_t *stmt, int colno)
631634 } else {
632635 /* allocate a smaller buffer to keep around for smaller
633636 * "long" columns */
634- S -> cols [colno ].data = emalloc (256 );
637+ S -> cols [colno ].data = emalloc (LONG_COLUMN_BUFFER_SIZE );
635638 S -> going_long = 1 ;
636639 S -> cols [colno ].is_long = 1 ;
637640 }
@@ -657,37 +660,57 @@ static int odbc_stmt_get_col(pdo_stmt_t *stmt, int colno, zval *result, enum pdo
657660 RETCODE rc ;
658661
659662 /* fetch it into C->data, which is allocated with a length
660- * of 256 bytes; if there is more to be had, we then allocate
663+ * of the page size minus zend_string overhead (LONG_COLUMN_BUFFER_SIZE);
664+ * if there is more to be had, we then allocate
661665 * bigger buffer for the caller to free */
662666
663667 rc = SQLGetData (S -> stmt , colno + 1 , C -> is_unicode ? SQL_C_BINARY : SQL_C_CHAR , C -> data ,
664- 256 , & C -> fetched_len );
668+ LONG_COLUMN_BUFFER_SIZE , & C -> fetched_len );
665669 orig_fetched_len = C -> fetched_len ;
666670
667- if (rc == SQL_SUCCESS && C -> fetched_len < 256 ) {
671+ if (rc == SQL_SUCCESS && C -> fetched_len < LONG_COLUMN_BUFFER_SIZE ) {
668672 /* all the data fit into our little buffer;
669673 * jump down to the generic bound data case */
670674 goto in_data ;
671675 }
672676
673677 if (rc == SQL_SUCCESS_WITH_INFO || rc == SQL_SUCCESS ) {
674- /* this is a 'long column'
675-
676- read the column in 255 byte blocks until the end of the column is reached, reassembling those blocks
677- in order into the output buffer; 255 bytes are an optimistic assumption, since the driver may assert
678- more or less NUL bytes at the end; we cater to that later, if actual length information is available
679-
680- this loop has to work whether or not SQLGetData() provides the total column length.
681- calling SQLDescribeCol() or other, specifically to get the column length, then doing a single read
682- for that size would be slower except maybe for extremely long columns.*/
683- char * buf2 = emalloc (256 );
684- zend_string * str = zend_string_init (C -> data , 256 , 0 );
685- size_t used = 255 ; /* not 256; the driver NUL terminated the buffer */
678+ /*
679+ * This is a long column.
680+ *
681+ * Try to get as much as we can at once. If the
682+ * driver somehow has more for us, get more. We'll
683+ * assemble it into one big buffer at the end.
684+ *
685+ * N.B. with n and n+1 mentioned in the comments:
686+ * n is the size returned without null terminator.
687+ *
688+ * The extension previously tried getting it in 256
689+ * byte blocks, but this could have created trouble
690+ * with some drivers.
691+ *
692+ * However, depending on the driver, fetched_len may
693+ * not contain the number of bytes and SQL_NO_TOTAL
694+ * may be passed.
695+ * The behavior in this case is the same as before,
696+ * dividing the data into blocks. However, it has been
697+ * changed from 256 byte to LONG_COLUMN_BUFFER_SIZE.
698+ */
699+ ssize_t to_fetch_len ;
700+ if (orig_fetched_len == SQL_NO_TOTAL ) {
701+ to_fetch_len = C -> datalen > (LONG_COLUMN_BUFFER_SIZE - 1 ) ? (LONG_COLUMN_BUFFER_SIZE - 1 ) : C -> datalen ;
702+ } else {
703+ to_fetch_len = orig_fetched_len ;
704+ }
705+ ssize_t to_fetch_byte = to_fetch_len + 1 ;
706+ char * buf2 = emalloc (to_fetch_byte );
707+ zend_string * str = zend_string_init (C -> data , to_fetch_byte , 0 );
708+ size_t used = to_fetch_len ;
686709
687710 do {
688711 C -> fetched_len = 0 ;
689- /* read block. 256 bytes => 255 bytes are actually read, the last 1 is NULL */
690- rc = SQLGetData (S -> stmt , colno + 1 , C -> is_unicode ? SQL_C_BINARY : SQL_C_CHAR , buf2 , 256 , & C -> fetched_len );
712+ /* read block. n + 1 bytes => n bytes are actually read, the last 1 is NULL */
713+ rc = SQLGetData (S -> stmt , colno + 1 , C -> is_unicode ? SQL_C_BINARY : SQL_C_CHAR , buf2 , to_fetch_byte , & C -> fetched_len );
691714
692715 /* adjust `used` in case we have proper length info from the driver */
693716 if (orig_fetched_len >= 0 && C -> fetched_len >= 0 ) {
@@ -698,13 +721,13 @@ static int odbc_stmt_get_col(pdo_stmt_t *stmt, int colno, zval *result, enum pdo
698721 }
699722
700723 /* resize output buffer and reassemble block */
701- if (rc == SQL_SUCCESS_WITH_INFO || (rc == SQL_SUCCESS && C -> fetched_len > 255 )) {
724+ if (rc == SQL_SUCCESS_WITH_INFO || (rc == SQL_SUCCESS && C -> fetched_len > to_fetch_len )) {
702725 /* point 5, in section "Retrieving Data with SQLGetData" in http://msdn.microsoft.com/en-us/library/windows/desktop/ms715441(v=vs.85).aspx
703- states that if SQL_SUCCESS_WITH_INFO, fetched_len will be > 255 (greater than buf2's size)
704- (if a driver fails to follow that and wrote less than 255 bytes to buf2, this will AV or read garbage into buf) */
705- str = zend_string_realloc (str , used + 256 , 0 );
706- memcpy (ZSTR_VAL (str ) + used , buf2 , 256 );
707- used = used + 255 ;
726+ states that if SQL_SUCCESS_WITH_INFO, fetched_len will be > n (greater than buf2's size)
727+ (if a driver fails to follow that and wrote less than n bytes to buf2, this will AV or read garbage into buf) */
728+ str = zend_string_realloc (str , used + to_fetch_byte , 0 );
729+ memcpy (ZSTR_VAL (str ) + used , buf2 , to_fetch_byte );
730+ used = used + to_fetch_len ;
708731 } else if (rc == SQL_SUCCESS ) {
709732 str = zend_string_realloc (str , used + C -> fetched_len , 0 );
710733 memcpy (ZSTR_VAL (str ) + used , buf2 , C -> fetched_len );
0 commit comments