Skip to content

Commit d475fb8

Browse files
committed
pdo_odbc: Don't fetch 256 byte blocks for long columns
Fetching 256 byte blocks can confuse some drivers with conversion routines. That, and it seems to me the round trips to and from a database could be a major performance impact. Instead, we try to fetch all at once, and continue fetching if a driver somehow has more for us. This has been tested with a problematic case with the Db2i driver with stateful MBCS encodings. See phpGH-10733 for discussion about this and issues it can resolve.
1 parent 0d5c794 commit d475fb8

File tree

1 file changed

+26
-20
lines changed

1 file changed

+26
-20
lines changed

ext/pdo_odbc/odbc_stmt.c

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -671,23 +671,29 @@ static int odbc_stmt_get_col(pdo_stmt_t *stmt, int colno, zval *result, enum pdo
671671
}
672672

673673
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 */
674+
/*
675+
* This is a long column.
676+
*
677+
* Try to get as much as we can at once. If the
678+
* driver somehow has more for us, get more. We'll
679+
* assemble it into one big buffer at the end.
680+
*
681+
* N.B. with n and n+1 mentioned in the comments:
682+
* n is the size returned without null terminator.
683+
*
684+
* The extension previously tried getting it in 256
685+
* byte blocks, but this could have created trouble
686+
* with some drivers.
687+
*/
688+
SQLLEN to_fetch_len = orig_fetched_len + 1; /* for 0 at end */
689+
char *buf2 = emalloc(to_fetch_len);
690+
zend_string *str = zend_string_init(C->data, to_fetch_len, 0);
691+
size_t used = orig_fetched_len; /* not n + 1; the driver NUL terminated the buffer */
686692

687693
do {
688694
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);
695+
/* read block. n + 1 bytes => n bytes are actually read, the last 1 is NULL */
696+
rc = SQLGetData(S->stmt, colno+1, C->is_unicode ? SQL_C_BINARY : SQL_C_CHAR, buf2, to_fetch_len, &C->fetched_len);
691697

692698
/* adjust `used` in case we have length info from the driver */
693699
if (orig_fetched_len >= 0 && C->fetched_len >= 0) {
@@ -697,13 +703,13 @@ static int odbc_stmt_get_col(pdo_stmt_t *stmt, int colno, zval *result, enum pdo
697703
}
698704

699705
/* resize output buffer and reassemble block */
700-
if (rc==SQL_SUCCESS_WITH_INFO || (rc==SQL_SUCCESS && C->fetched_len > 255)) {
706+
if (rc==SQL_SUCCESS_WITH_INFO || (rc==SQL_SUCCESS && C->fetched_len > orig_fetched_len)) {
701707
/* point 5, in section "Retrieving Data with SQLGetData" in http://msdn.microsoft.com/en-us/library/windows/desktop/ms715441(v=vs.85).aspx
702-
states that if SQL_SUCCESS_WITH_INFO, fetched_len will be > 255 (greater than buf2's size)
703-
(if a driver fails to follow that and wrote less than 255 bytes to buf2, this will AV or read garbage into buf) */
704-
str = zend_string_realloc(str, used + 256, 0);
705-
memcpy(ZSTR_VAL(str) + used, buf2, 256);
706-
used = used + 255;
708+
states that if SQL_SUCCESS_WITH_INFO, fetched_len will be > n (greater than buf2's size)
709+
(if a driver fails to follow that and wrote less than n bytes to buf2, this will AV or read garbage into buf) */
710+
str = zend_string_realloc(str, used + to_fetch_len, 0);
711+
memcpy(ZSTR_VAL(str) + used, buf2, to_fetch_len);
712+
used = used + orig_fetched_len;
707713
} else if (rc==SQL_SUCCESS) {
708714
str = zend_string_realloc(str, used + C->fetched_len, 0);
709715
memcpy(ZSTR_VAL(str) + used, buf2, C->fetched_len);

0 commit comments

Comments
 (0)