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 "pdo/php_pdo.h"
25 #include "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*)¶m);
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 = ¶m->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 = ¶m->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 = ¶m->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