xref: /PHP-8.4/ext/pdo_odbc/odbc_stmt.c (revision c9eafc19)
1 /*
2   +----------------------------------------------------------------------+
3   | Copyright (c) The PHP Group                                          |
4   +----------------------------------------------------------------------+
5   | This source file is subject to version 3.01 of the PHP license,      |
6   | that is bundled with this package in the file LICENSE, and is        |
7   | available through the world-wide-web at the following url:           |
8   | https://www.php.net/license/3_01.txt                                 |
9   | If you did not receive a copy of the PHP license and are unable to   |
10   | obtain it through the world-wide-web, please send a note to          |
11   | license@php.net so we can mail you a copy immediately.               |
12   +----------------------------------------------------------------------+
13   | Author: Wez Furlong <wez@php.net>                                    |
14   +----------------------------------------------------------------------+
15 */
16 
17 #ifdef HAVE_CONFIG_H
18 #include <config.h>
19 #endif
20 
21 #include "php.h"
22 #include "php_ini.h"
23 #include "ext/standard/info.h"
24 #include "ext/pdo/php_pdo.h"
25 #include "ext/pdo/php_pdo_driver.h"
26 #include "php_pdo_odbc.h"
27 #include "php_pdo_odbc_int.h"
28 
29 enum pdo_odbc_conv_result {
30 	PDO_ODBC_CONV_NOT_REQUIRED,
31 	PDO_ODBC_CONV_OK,
32 	PDO_ODBC_CONV_FAIL
33 };
34 
pdo_odbc_sqltype_is_unicode(pdo_odbc_stmt * S,SQLSMALLINT sqltype)35 static int pdo_odbc_sqltype_is_unicode(pdo_odbc_stmt *S, SQLSMALLINT sqltype)
36 {
37 	if (!S->assume_utf8) return 0;
38 	switch (sqltype) {
39 #ifdef SQL_WCHAR
40 		case SQL_WCHAR:
41 			return 1;
42 #endif
43 #ifdef SQL_WLONGVARCHAR
44 		case SQL_WLONGVARCHAR:
45 			return 1;
46 #endif
47 #ifdef SQL_WVARCHAR
48 		case SQL_WVARCHAR:
49 			return 1;
50 #endif
51 		default:
52 			return 0;
53 	}
54 }
55 
pdo_odbc_utf82ucs2(pdo_stmt_t * stmt,int is_unicode,const char * buf,zend_ulong buflen,zend_ulong * outlen)56 static int pdo_odbc_utf82ucs2(pdo_stmt_t *stmt, int is_unicode, const char *buf,
57 	zend_ulong buflen, zend_ulong *outlen)
58 {
59 #ifdef PHP_WIN32
60 	if (is_unicode && buflen) {
61 		pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data;
62 		DWORD ret;
63 
64 		ret = MultiByteToWideChar(CP_UTF8, 0, buf, buflen, NULL, 0);
65 		if (ret == 0) {
66 			/*printf("%s:%d %d [%d] %.*s\n", __FILE__, __LINE__, GetLastError(), buflen, buflen, buf);*/
67 			return PDO_ODBC_CONV_FAIL;
68 		}
69 
70 		ret *= sizeof(WCHAR);
71 
72 		if (S->convbufsize <= ret) {
73 			S->convbufsize = ret + sizeof(WCHAR);
74 			S->convbuf = erealloc(S->convbuf, S->convbufsize);
75 		}
76 
77 		ret = MultiByteToWideChar(CP_UTF8, 0, buf, buflen, (LPWSTR)S->convbuf, S->convbufsize / sizeof(WCHAR));
78 		if (ret == 0) {
79 			/*printf("%s:%d %d [%d] %.*s\n", __FILE__, __LINE__, GetLastError(), buflen, buflen, buf);*/
80 			return PDO_ODBC_CONV_FAIL;
81 		}
82 
83 		ret *= sizeof(WCHAR);
84 		*outlen = ret;
85 		return PDO_ODBC_CONV_OK;
86 	}
87 #endif
88 	return PDO_ODBC_CONV_NOT_REQUIRED;
89 }
90 
pdo_odbc_ucs22utf8(pdo_stmt_t * stmt,int is_unicode,zval * result)91 static int pdo_odbc_ucs22utf8(pdo_stmt_t *stmt, int is_unicode, zval *result)
92 {
93 #ifdef PHP_WIN32
94 	ZEND_ASSERT(Z_TYPE_P(result) == IS_STRING);
95 	if (is_unicode && Z_STRLEN_P(result) != 0) {
96 		pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data;
97 		DWORD ret;
98 
99 		ret = WideCharToMultiByte(CP_UTF8, 0, (LPCWSTR) Z_STRVAL_P(result), Z_STRLEN_P(result)/sizeof(WCHAR), NULL, 0, NULL, NULL);
100 		if (ret == 0) {
101 			return PDO_ODBC_CONV_FAIL;
102 		}
103 
104 		zend_string *str = zend_string_alloc(ret, 0);
105 		ret = WideCharToMultiByte(CP_UTF8, 0, (LPCWSTR) Z_STRVAL_P(result), Z_STRLEN_P(result)/sizeof(WCHAR), ZSTR_VAL(str), ZSTR_LEN(str), NULL, NULL);
106 		if (ret == 0) {
107 			return PDO_ODBC_CONV_FAIL;
108 		}
109 
110 		ZSTR_VAL(str)[ret] = '\0';
111 		zval_ptr_dtor_str(result);
112 		ZVAL_STR(result, str);
113 		return PDO_ODBC_CONV_OK;
114 	}
115 #endif
116 	return PDO_ODBC_CONV_NOT_REQUIRED;
117 }
118 
free_cols(pdo_stmt_t * stmt,pdo_odbc_stmt * S)119 static void free_cols(pdo_stmt_t *stmt, pdo_odbc_stmt *S)
120 {
121 	if (S->cols) {
122 		int i;
123 
124 		for (i = 0; i < S->col_count; i++) {
125 			if (S->cols[i].data) {
126 				efree(S->cols[i].data);
127 			}
128 		}
129 		efree(S->cols);
130 		S->cols = NULL;
131 		S->col_count = 0;
132 	}
133 }
134 
odbc_stmt_dtor(pdo_stmt_t * stmt)135 static int odbc_stmt_dtor(pdo_stmt_t *stmt)
136 {
137 	pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data;
138 
139 	if (S->stmt != SQL_NULL_HANDLE) {
140 		if (stmt->executed) {
141 			SQLCloseCursor(S->stmt);
142 		}
143 		SQLFreeHandle(SQL_HANDLE_STMT, S->stmt);
144 		S->stmt = SQL_NULL_HANDLE;
145 	}
146 
147 	free_cols(stmt, S);
148 	if (S->convbuf) {
149 		efree(S->convbuf);
150 	}
151 	efree(S);
152 
153 	return 1;
154 }
155 
odbc_stmt_execute(pdo_stmt_t * stmt)156 static int odbc_stmt_execute(pdo_stmt_t *stmt)
157 {
158 	RETCODE rc, rc1;
159 	pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data;
160 	char *buf = NULL;
161 	SQLLEN row_count = -1;
162 
163 	if (stmt->executed) {
164 		SQLCloseCursor(S->stmt);
165 	}
166 
167 	rc = SQLExecute(S->stmt);
168 
169 	while (rc == SQL_NEED_DATA) {
170 		struct pdo_bound_param_data *param;
171 
172 		rc = SQLParamData(S->stmt, (SQLPOINTER*)&param);
173 		if (rc == SQL_NEED_DATA) {
174 			php_stream *stm;
175 			int len;
176 			pdo_odbc_param *P;
177 			zval *parameter;
178 
179 			P = (pdo_odbc_param*)param->driver_data;
180 			if (Z_ISREF(param->parameter)) {
181 				parameter = Z_REFVAL(param->parameter);
182 			} else {
183 				parameter = &param->parameter;
184 			}
185 			if (Z_TYPE_P(parameter) != IS_RESOURCE) {
186 				/* they passed in a string */
187 				zend_ulong ulen;
188 				convert_to_string(parameter);
189 
190 				switch (pdo_odbc_utf82ucs2(stmt, P->is_unicode,
191 							Z_STRVAL_P(parameter),
192 							Z_STRLEN_P(parameter),
193 							&ulen)) {
194 					case PDO_ODBC_CONV_NOT_REQUIRED:
195 						rc1 = SQLPutData(S->stmt, Z_STRVAL_P(parameter),
196 							Z_STRLEN_P(parameter));
197 						if (rc1 != SQL_SUCCESS && rc1 != SQL_SUCCESS_WITH_INFO) {
198 							rc = rc1;
199 						}
200 						break;
201 					case PDO_ODBC_CONV_OK:
202 						rc1 = SQLPutData(S->stmt, S->convbuf, ulen);
203 						if (rc1 != SQL_SUCCESS && rc1 != SQL_SUCCESS_WITH_INFO) {
204 							rc = rc1;
205 						}
206 						break;
207 					case PDO_ODBC_CONV_FAIL:
208 						pdo_odbc_stmt_error("error converting input string");
209 						SQLCloseCursor(S->stmt);
210 						if (buf) {
211 							efree(buf);
212 						}
213 						return 0;
214 				}
215 				continue;
216 			}
217 
218 			/* we assume that LOBs are binary and don't need charset
219 			 * conversion */
220 
221 			php_stream_from_zval_no_verify(stm, parameter);
222 			if (!stm) {
223 				/* shouldn't happen either */
224 				pdo_odbc_stmt_error("input LOB is no longer a stream");
225 				SQLCloseCursor(S->stmt);
226 				if (buf) {
227 					efree(buf);
228 				}
229 				return 0;
230 			}
231 
232 			/* now suck data from the stream and stick it into the database */
233 			if (buf == NULL) {
234 				buf = emalloc(8192);
235 			}
236 
237 			do {
238 				len = php_stream_read(stm, buf, 8192);
239 				if (len == 0) {
240 					break;
241 				}
242 				rc1 = SQLPutData(S->stmt, buf, len);
243 				if (rc1 != SQL_SUCCESS && rc1 != SQL_SUCCESS_WITH_INFO) {
244 					rc = rc1;
245 				}
246 			} while (1);
247 		}
248 	}
249 
250 	if (buf) {
251 		efree(buf);
252 	}
253 
254 	switch (rc) {
255 		case SQL_SUCCESS:
256 			break;
257 		case SQL_NO_DATA_FOUND:
258 		case SQL_SUCCESS_WITH_INFO:
259 			pdo_odbc_stmt_error("SQLExecute");
260 			break;
261 
262 		default:
263 			pdo_odbc_stmt_error("SQLExecute");
264 			return 0;
265 	}
266 
267 	SQLRowCount(S->stmt, &row_count);
268 	stmt->row_count = row_count;
269 
270 	if (S->cols == NULL) {
271 		/* do first-time-only definition of bind/mapping stuff */
272 		SQLSMALLINT colcount;
273 
274 		/* how many columns do we have ? */
275 		SQLNumResultCols(S->stmt, &colcount);
276 
277 		stmt->column_count = S->col_count = (int)colcount;
278 		S->cols = ecalloc(colcount, sizeof(pdo_odbc_column));
279 		S->going_long = 0;
280 	}
281 
282 	return 1;
283 }
284 
odbc_stmt_param_hook(pdo_stmt_t * stmt,struct pdo_bound_param_data * param,enum pdo_param_event event_type)285 static int odbc_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_data *param,
286 		enum pdo_param_event event_type)
287 {
288 	pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data;
289 	RETCODE rc;
290 	SQLSMALLINT sqltype = 0, ctype = 0, scale = 0, nullable = 0;
291 	SQLULEN precision = 0;
292 	pdo_odbc_param *P;
293 	zval *parameter;
294 
295 	/* we're only interested in parameters for prepared SQL right now */
296 	if (param->is_param) {
297 
298 		switch (event_type) {
299 			case PDO_PARAM_EVT_FETCH_PRE:
300 			case PDO_PARAM_EVT_FETCH_POST:
301 			case PDO_PARAM_EVT_NORMALIZE:
302 				/* Do nothing */
303 				break;
304 
305 			case PDO_PARAM_EVT_FREE:
306 				P = param->driver_data;
307 				if (P) {
308 					efree(P);
309 				}
310 				break;
311 
312 			case PDO_PARAM_EVT_ALLOC:
313 			{
314 				/* figure out what we're doing */
315 				switch (PDO_PARAM_TYPE(param->param_type)) {
316 					case PDO_PARAM_LOB:
317 						break;
318 
319 					case PDO_PARAM_STMT:
320 						return 0;
321 
322 					default:
323 						break;
324 				}
325 
326 				rc = SQLDescribeParam(S->stmt, (SQLUSMALLINT) param->paramno+1, &sqltype, &precision, &scale, &nullable);
327 				if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) {
328 					/* MS Access, for instance, doesn't support SQLDescribeParam,
329 					 * so we need to guess */
330 					switch (PDO_PARAM_TYPE(param->param_type)) {
331 						case PDO_PARAM_INT:
332 							sqltype = SQL_INTEGER;
333 							break;
334 						case PDO_PARAM_LOB:
335 							sqltype = SQL_LONGVARBINARY;
336 							break;
337 						default:
338 							sqltype = SQL_LONGVARCHAR;
339 					}
340 					precision = 4000;
341 					scale = 5;
342 					nullable = 1;
343 
344 					if (param->max_value_len > 0) {
345 						precision = param->max_value_len;
346 					}
347 				}
348 				if (sqltype == SQL_BINARY || sqltype == SQL_VARBINARY || sqltype == SQL_LONGVARBINARY) {
349 					ctype = SQL_C_BINARY;
350 				} else {
351 					ctype = SQL_C_CHAR;
352 				}
353 
354 				P = emalloc(sizeof(*P));
355 				param->driver_data = P;
356 
357 				P->len = 0; /* is re-populated each EXEC_PRE */
358 				P->outbuf = NULL;
359 
360 				P->is_unicode = pdo_odbc_sqltype_is_unicode(S, sqltype);
361 				if (P->is_unicode) {
362 					/* avoid driver auto-translation: we'll do it ourselves */
363 					ctype = SQL_C_BINARY;
364 				}
365 
366 				if ((param->param_type & PDO_PARAM_INPUT_OUTPUT) == PDO_PARAM_INPUT_OUTPUT) {
367 					P->paramtype = SQL_PARAM_INPUT_OUTPUT;
368 				} else if (param->max_value_len <= 0) {
369 					P->paramtype = SQL_PARAM_INPUT;
370 				} else {
371 					P->paramtype = SQL_PARAM_OUTPUT;
372 				}
373 
374 				if (P->paramtype != SQL_PARAM_INPUT) {
375 					if (PDO_PARAM_TYPE(param->param_type) != PDO_PARAM_NULL) {
376 						/* need an explicit buffer to hold result */
377 						P->len = param->max_value_len > 0 ? param->max_value_len : precision;
378 						if (P->is_unicode) {
379 							P->len *= 2;
380 						}
381 						P->outbuf = emalloc(P->len + (P->is_unicode ? 2:1));
382 					}
383 				}
384 
385 				if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_LOB && P->paramtype != SQL_PARAM_INPUT) {
386 					pdo_odbc_stmt_error("Can't bind a lob for output");
387 					return 0;
388 				}
389 
390 				rc = SQLBindParameter(S->stmt, (SQLUSMALLINT) param->paramno+1,
391 						P->paramtype, ctype, sqltype, precision, scale,
392 						P->paramtype == SQL_PARAM_INPUT ?
393 							(SQLPOINTER)param :
394 							P->outbuf,
395 						P->len,
396 						&P->len
397 						);
398 
399 				if (rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO) {
400 					return 1;
401 				}
402 				pdo_odbc_stmt_error("SQLBindParameter");
403 				return 0;
404 			}
405 
406 			case PDO_PARAM_EVT_EXEC_PRE:
407 				P = param->driver_data;
408 				if (!Z_ISREF(param->parameter)) {
409 					parameter = &param->parameter;
410 				} else {
411 					parameter = Z_REFVAL(param->parameter);
412 				}
413 
414 				if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_LOB) {
415 					if (Z_TYPE_P(parameter) == IS_RESOURCE) {
416 						php_stream *stm;
417 						php_stream_statbuf sb;
418 
419 						php_stream_from_zval_no_verify(stm, parameter);
420 
421 						if (!stm) {
422 							return 0;
423 						}
424 
425 						if (0 == php_stream_stat(stm, &sb)) {
426 							if (P->outbuf) {
427 								int len, amount;
428 								char *ptr = P->outbuf;
429 								char *end = P->outbuf + P->len;
430 
431 								P->len = 0;
432 								do {
433 									amount = end - ptr;
434 									if (amount == 0) {
435 										break;
436 									}
437 									if (amount > 8192)
438 										amount = 8192;
439 									len = php_stream_read(stm, ptr, amount);
440 									if (len == 0) {
441 										break;
442 									}
443 									ptr += len;
444 									P->len += len;
445 								} while (1);
446 
447 							} else {
448 								P->len = SQL_LEN_DATA_AT_EXEC(sb.sb.st_size);
449 							}
450 						} else {
451 							if (P->outbuf) {
452 								P->len = 0;
453 							} else {
454 								P->len = SQL_LEN_DATA_AT_EXEC(0);
455 							}
456 						}
457 					} else {
458 						convert_to_string(parameter);
459 						if (P->outbuf) {
460 							P->len = Z_STRLEN_P(parameter);
461 							memcpy(P->outbuf, Z_STRVAL_P(parameter), P->len);
462 						} else {
463 							P->len = SQL_LEN_DATA_AT_EXEC(Z_STRLEN_P(parameter));
464 						}
465 					}
466 				} else if (Z_TYPE_P(parameter) == IS_NULL || PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_NULL) {
467 					P->len = SQL_NULL_DATA;
468 				} else {
469 					convert_to_string(parameter);
470 					if (P->outbuf) {
471 						zend_ulong ulen;
472 						switch (pdo_odbc_utf82ucs2(stmt, P->is_unicode,
473 								Z_STRVAL_P(parameter),
474 								Z_STRLEN_P(parameter),
475 								&ulen)) {
476 							case PDO_ODBC_CONV_FAIL:
477 							case PDO_ODBC_CONV_NOT_REQUIRED:
478 								P->len = Z_STRLEN_P(parameter);
479 								memcpy(P->outbuf, Z_STRVAL_P(parameter), P->len);
480 								break;
481 							case PDO_ODBC_CONV_OK:
482 								P->len = ulen;
483 								memcpy(P->outbuf, S->convbuf, P->len);
484 								break;
485 						}
486 					} else {
487 						P->len = SQL_LEN_DATA_AT_EXEC(Z_STRLEN_P(parameter));
488 					}
489 				}
490 				return 1;
491 
492 			case PDO_PARAM_EVT_EXEC_POST:
493 				P = param->driver_data;
494 
495 				if (P->outbuf) {
496 					if (Z_ISREF(param->parameter)) {
497 						parameter = Z_REFVAL(param->parameter);
498 					} else {
499 						parameter = &param->parameter;
500 					}
501 					zval_ptr_dtor(parameter);
502 
503 					if (P->len >= 0) {
504 							ZVAL_STRINGL(parameter, P->outbuf, P->len);
505 							switch (pdo_odbc_ucs22utf8(stmt, P->is_unicode, parameter)) {
506 								case PDO_ODBC_CONV_FAIL:
507 									/* something fishy, but allow it to come back as binary */
508 								case PDO_ODBC_CONV_NOT_REQUIRED:
509 									break;
510 								case PDO_ODBC_CONV_OK:
511 									break;
512 							}
513 					} else {
514 						ZVAL_NULL(parameter);
515 					}
516 				}
517 				return 1;
518 		}
519 	}
520 	return 1;
521 }
522 
odbc_stmt_fetch(pdo_stmt_t * stmt,enum pdo_fetch_orientation ori,zend_long offset)523 static int odbc_stmt_fetch(pdo_stmt_t *stmt,
524 	enum pdo_fetch_orientation ori, zend_long offset)
525 {
526 	RETCODE rc;
527 	SQLSMALLINT odbcori;
528 	pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data;
529 
530 	switch (ori) {
531 		case PDO_FETCH_ORI_NEXT:	odbcori = SQL_FETCH_NEXT; break;
532 		case PDO_FETCH_ORI_PRIOR:	odbcori = SQL_FETCH_PRIOR; break;
533 		case PDO_FETCH_ORI_FIRST:	odbcori = SQL_FETCH_FIRST; break;
534 		case PDO_FETCH_ORI_LAST:	odbcori = SQL_FETCH_LAST; break;
535 		case PDO_FETCH_ORI_ABS:		odbcori = SQL_FETCH_ABSOLUTE; break;
536 		case PDO_FETCH_ORI_REL:		odbcori = SQL_FETCH_RELATIVE; break;
537 		default:
538 			strcpy(stmt->error_code, "HY106");
539 			return 0;
540 	}
541 	rc = SQLFetchScroll(S->stmt, odbcori, offset);
542 
543 	if (rc == SQL_SUCCESS) {
544 		return 1;
545 	}
546 	if (rc == SQL_SUCCESS_WITH_INFO) {
547 		pdo_odbc_stmt_error("SQLFetchScroll");
548 		return 1;
549 	}
550 
551 	if (rc == SQL_NO_DATA) {
552 		/* pdo_odbc_stmt_error("SQLFetchScroll"); */
553 		return 0;
554 	}
555 
556 	pdo_odbc_stmt_error("SQLFetchScroll");
557 
558 	return 0;
559 }
560 
odbc_stmt_describe(pdo_stmt_t * stmt,int colno)561 static int odbc_stmt_describe(pdo_stmt_t *stmt, int colno)
562 {
563 	pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data;
564 	struct pdo_column_data *col = &stmt->columns[colno];
565 	RETCODE rc;
566 	SQLSMALLINT colnamelen;
567 	SQLULEN	colsize;
568 	SQLLEN displaysize = 0;
569 
570 	rc = SQLDescribeCol(S->stmt, colno+1, (SQLCHAR *) S->cols[colno].colname,
571 			sizeof(S->cols[colno].colname)-1, &colnamelen,
572 			&S->cols[colno].coltype, &colsize, NULL, NULL);
573 
574 	/* This fixes a known issue with SQL Server and (max) lengths,
575 	may affect others as well.  If we are SQL_VARCHAR,
576 	SQL_VARBINARY, or SQL_WVARCHAR (or any of the long variations)
577 	and zero is returned from colsize then consider it long */
578 	if (0 == colsize &&
579 		(S->cols[colno].coltype == SQL_VARCHAR ||
580 		 S->cols[colno].coltype == SQL_LONGVARCHAR ||
581 #ifdef SQL_WVARCHAR
582 		 S->cols[colno].coltype == SQL_WVARCHAR ||
583 #endif
584 #ifdef SQL_WLONGVARCHAR
585 		 S->cols[colno].coltype == SQL_WLONGVARCHAR ||
586 #endif
587 		 S->cols[colno].coltype == SQL_VARBINARY ||
588 		 S->cols[colno].coltype == SQL_LONGVARBINARY)) {
589 			 S->going_long = 1;
590 	}
591 
592 	if (rc != SQL_SUCCESS) {
593 		pdo_odbc_stmt_error("SQLDescribeCol");
594 		if (rc != SQL_SUCCESS_WITH_INFO) {
595 			return 0;
596 		}
597 	}
598 
599 	rc = SQLColAttribute(S->stmt, colno+1,
600 			SQL_DESC_DISPLAY_SIZE,
601 			NULL, 0, NULL, &displaysize);
602 
603 	if (rc != SQL_SUCCESS) {
604 		pdo_odbc_stmt_error("SQLColAttribute");
605 		if (rc != SQL_SUCCESS_WITH_INFO) {
606 			return 0;
607 		}
608 	}
609 	colsize = displaysize;
610 
611 	col->maxlen = S->cols[colno].datalen = colsize;
612 	col->name = zend_string_init(S->cols[colno].colname, colnamelen, 0);
613 	S->cols[colno].is_unicode = pdo_odbc_sqltype_is_unicode(S, S->cols[colno].coltype);
614 
615 	/* tell ODBC to put it straight into our buffer, but only if it
616 	 * isn't "long" data, and only if we haven't already bound a long
617 	 * column. */
618 	if (colsize < 256 && !S->going_long) {
619 		S->cols[colno].data = emalloc(colsize+1);
620 		S->cols[colno].is_long = 0;
621 
622 		rc = SQLBindCol(S->stmt, colno+1,
623 			S->cols[colno].is_unicode ? SQL_C_BINARY : SQL_C_CHAR,
624 			S->cols[colno].data,
625 			S->cols[colno].datalen+1, &S->cols[colno].fetched_len);
626 
627 		if (rc != SQL_SUCCESS) {
628 			pdo_odbc_stmt_error("SQLBindCol");
629 			return 0;
630 		}
631 	} else {
632 		/* allocate a smaller buffer to keep around for smaller
633 		 * "long" columns */
634 		S->cols[colno].data = emalloc(256);
635 		S->going_long = 1;
636 		S->cols[colno].is_long = 1;
637 	}
638 
639 	return 1;
640 }
641 
odbc_stmt_get_column_meta(pdo_stmt_t * stmt,zend_long colno,zval * return_value)642 static int odbc_stmt_get_column_meta(pdo_stmt_t *stmt, zend_long colno, zval *return_value)
643 {
644 	array_init(return_value);
645 	add_assoc_long(return_value, "pdo_type", PDO_PARAM_STR);
646 	return 1;
647 }
648 
odbc_stmt_get_col(pdo_stmt_t * stmt,int colno,zval * result,enum pdo_param_type * type)649 static int odbc_stmt_get_col(pdo_stmt_t *stmt, int colno, zval *result, enum pdo_param_type *type)
650 {
651 	pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data;
652 	pdo_odbc_column *C = &S->cols[colno];
653 
654 	/* if it is a column containing "long" data, perform late binding now */
655 	if (C->is_long) {
656 		SQLLEN orig_fetched_len = SQL_NULL_DATA;
657 		RETCODE rc;
658 
659 		/* 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
661 		 * bigger buffer for the caller to free */
662 
663 		rc = SQLGetData(S->stmt, colno+1, C->is_unicode ? SQL_C_BINARY : SQL_C_CHAR, C->data,
664  			256, &C->fetched_len);
665 		orig_fetched_len = C->fetched_len;
666 
667 		if (rc == SQL_SUCCESS && C->fetched_len < 256) {
668 			/* all the data fit into our little buffer;
669 			 * jump down to the generic bound data case */
670 			goto in_data;
671 		}
672 
673 		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 */
686 
687 			do {
688 				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);
691 
692 				/* adjust `used` in case we have proper length info from the driver */
693 				if (orig_fetched_len >= 0 && C->fetched_len >= 0) {
694 					SQLLEN fixed_used = orig_fetched_len - C->fetched_len;
695 					if (fixed_used <= used + 1) {
696 						used = fixed_used;
697 					}
698 				}
699 
700 				/* resize output buffer and reassemble block */
701 				if (rc==SQL_SUCCESS_WITH_INFO || (rc==SQL_SUCCESS && C->fetched_len > 255)) {
702 					/* 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;
708 				} else if (rc==SQL_SUCCESS) {
709 					str = zend_string_realloc(str, used + C->fetched_len, 0);
710 					memcpy(ZSTR_VAL(str) + used, buf2, C->fetched_len);
711 					used = used + C->fetched_len;
712 				} else {
713 					/* includes SQL_NO_DATA */
714 					break;
715 				}
716 
717 			} while (1);
718 
719 			efree(buf2);
720 
721 			/* NULL terminate the buffer once, when finished, for use with the rest of PHP */
722 			ZSTR_VAL(str)[used] = '\0';
723 			ZVAL_STR(result, str);
724 			if (C->is_unicode) {
725 				goto unicode_conv;
726 			}
727 			return 1;
728 		}
729 
730 		/* something went caca */
731 		return 1;
732 	}
733 
734 in_data:
735 	/* check the indicator to ensure that the data is intact */
736 	if (C->fetched_len == SQL_NULL_DATA) {
737 		/* A NULL value */
738 		ZVAL_NULL(result);
739 		return 1;
740 	} else if (C->fetched_len >= 0) {
741 		/* it was stored perfectly */
742 		ZVAL_STRINGL_FAST(result, C->data, C->fetched_len);
743 		if (C->is_unicode) {
744 			goto unicode_conv;
745 		}
746 		return 1;
747 	} else {
748 		/* no data? */
749 		ZVAL_NULL(result);
750 		return 1;
751 	}
752 
753 unicode_conv:
754 	switch (pdo_odbc_ucs22utf8(stmt, C->is_unicode, result)) {
755 		case PDO_ODBC_CONV_FAIL:
756 			/* oh well.  They can have the binary version of it */
757 		case PDO_ODBC_CONV_NOT_REQUIRED:
758 			/* shouldn't happen... */
759 			return 1;
760 		case PDO_ODBC_CONV_OK:
761 			return 1;
762 	}
763 	return 1;
764 }
765 
odbc_stmt_set_param(pdo_stmt_t * stmt,zend_long attr,zval * val)766 static int odbc_stmt_set_param(pdo_stmt_t *stmt, zend_long attr, zval *val)
767 {
768 	SQLRETURN rc;
769 	pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data;
770 
771 	switch (attr) {
772 		case PDO_ATTR_CURSOR_NAME:
773 			convert_to_string(val);
774 			rc = SQLSetCursorName(S->stmt, (SQLCHAR *) Z_STRVAL_P(val), Z_STRLEN_P(val));
775 
776 			if (rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO) {
777 				return 1;
778 			}
779 			pdo_odbc_stmt_error("SQLSetCursorName");
780 			return 0;
781 
782 		case PDO_ODBC_ATTR_ASSUME_UTF8:
783 			S->assume_utf8 = zval_is_true(val);
784 			return 0;
785 		default:
786 			strcpy(S->einfo.last_err_msg, "Unknown Attribute");
787 			S->einfo.what = "setAttribute";
788 			strcpy(S->einfo.last_state, "IM001");
789 			return -1;
790 	}
791 }
792 
odbc_stmt_get_attr(pdo_stmt_t * stmt,zend_long attr,zval * val)793 static int odbc_stmt_get_attr(pdo_stmt_t *stmt, zend_long attr, zval *val)
794 {
795 	SQLRETURN rc;
796 	pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data;
797 
798 	switch (attr) {
799 		case PDO_ATTR_CURSOR_NAME:
800 		{
801 			char buf[256];
802 			SQLSMALLINT len = 0;
803 			rc = SQLGetCursorName(S->stmt, (SQLCHAR *) buf, sizeof(buf), &len);
804 
805 			if (rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO) {
806 				ZVAL_STRINGL(val, buf, len);
807 				return 1;
808 			}
809 			pdo_odbc_stmt_error("SQLGetCursorName");
810 			return 0;
811 		}
812 
813 		case PDO_ODBC_ATTR_ASSUME_UTF8:
814 			ZVAL_BOOL(val, S->assume_utf8 ? 1 : 0);
815 			return 0;
816 
817 		default:
818 			strcpy(S->einfo.last_err_msg, "Unknown Attribute");
819 			S->einfo.what = "getAttribute";
820 			strcpy(S->einfo.last_state, "IM001");
821 			return -1;
822 	}
823 }
824 
odbc_stmt_next_rowset(pdo_stmt_t * stmt)825 static int odbc_stmt_next_rowset(pdo_stmt_t *stmt)
826 {
827 	SQLRETURN rc;
828 	SQLSMALLINT colcount;
829 	pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data;
830 
831 	/* NOTE: can't guarantee that output or input/output parameters
832 	 * are set until this fella returns SQL_NO_DATA, according to
833 	 * MSDN ODBC docs */
834 	rc = SQLMoreResults(S->stmt);
835 
836 	if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) {
837 		return 0;
838 	}
839 
840 	free_cols(stmt, S);
841 	/* how many columns do we have ? */
842 	SQLNumResultCols(S->stmt, &colcount);
843 	stmt->column_count = S->col_count = (int)colcount;
844 	S->cols = ecalloc(colcount, sizeof(pdo_odbc_column));
845 	S->going_long = 0;
846 
847 	return 1;
848 }
849 
odbc_stmt_close_cursor(pdo_stmt_t * stmt)850 static int odbc_stmt_close_cursor(pdo_stmt_t *stmt)
851 {
852 	SQLRETURN rc;
853 	pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data;
854 
855 	rc = SQLCloseCursor(S->stmt);
856 	if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) {
857 		return 0;
858 	}
859 	return 1;
860 }
861 
862 const struct pdo_stmt_methods odbc_stmt_methods = {
863 	odbc_stmt_dtor,
864 	odbc_stmt_execute,
865 	odbc_stmt_fetch,
866 	odbc_stmt_describe,
867 	odbc_stmt_get_col,
868 	odbc_stmt_param_hook,
869 	odbc_stmt_set_param,
870 	odbc_stmt_get_attr,
871 	odbc_stmt_get_column_meta,
872 	odbc_stmt_next_rowset,
873 	odbc_stmt_close_cursor
874 };
875