xref: /PHP-8.2/ext/pdo/pdo_stmt.c (revision 4d14325b)
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   |         Marcus Boerger <helly@php.net>                               |
15   |         Sterling Hughes <sterling@php.net>                           |
16   +----------------------------------------------------------------------+
17 */
18 
19 /* The PDO Statement Handle Class */
20 
21 #ifdef HAVE_CONFIG_H
22 #include "config.h"
23 #endif
24 
25 #include "php.h"
26 #include "php_ini.h"
27 #include "ext/standard/info.h"
28 #include "ext/standard/php_var.h"
29 #include "php_pdo.h"
30 #include "php_pdo_driver.h"
31 #include "php_pdo_int.h"
32 #include "zend_exceptions.h"
33 #include "zend_interfaces.h"
34 #include "php_memory_streams.h"
35 #include "pdo_stmt_arginfo.h"
36 
37 #define PHP_STMT_GET_OBJ \
38 	pdo_stmt_t *stmt = Z_PDO_STMT_P(ZEND_THIS); \
39 	if (!stmt->dbh) { \
40 		zend_throw_error(NULL, "PDO object is uninitialized"); \
41 		RETURN_THROWS(); \
42 	} \
43 
rewrite_name_to_position(pdo_stmt_t * stmt,struct pdo_bound_param_data * param)44 static inline bool rewrite_name_to_position(pdo_stmt_t *stmt, struct pdo_bound_param_data *param) /* {{{ */
45 {
46 	if (stmt->bound_param_map) {
47 		/* rewriting :name to ? style.
48 		 * We need to fixup the parameter numbers on the parameters.
49 		 * If we find that a given named parameter has been used twice,
50 		 * we will raise an error, as we can't be sure that it is safe
51 		 * to bind multiple parameters onto the same zval in the underlying
52 		 * driver */
53 		zend_string *name;
54 		int position = 0;
55 
56 		if (stmt->named_rewrite_template) {
57 			/* this is not an error here */
58 			return 1;
59 		}
60 		if (!param->name) {
61 			/* do the reverse; map the parameter number to the name */
62 			if ((name = zend_hash_index_find_ptr(stmt->bound_param_map, param->paramno)) != NULL) {
63 				param->name = zend_string_copy(name);
64 				return 1;
65 			}
66 			/* TODO Error? */
67 			pdo_raise_impl_error(stmt->dbh, stmt, "HY093", "parameter was not defined");
68 			return 0;
69 		}
70 
71 		ZEND_HASH_FOREACH_PTR(stmt->bound_param_map, name) {
72 			if (!zend_string_equals(name, param->name)) {
73 				position++;
74 				continue;
75 			}
76 			if (param->paramno >= 0) {
77 				/* TODO Error? */
78 				pdo_raise_impl_error(stmt->dbh, stmt, "IM001", "PDO refuses to handle repeating the same :named parameter for multiple positions with this driver, as it might be unsafe to do so.  Consider using a separate name for each parameter instead");
79 				return -1;
80 			}
81 			param->paramno = position;
82 			return 1;
83 		} ZEND_HASH_FOREACH_END();
84 		/* TODO Error? */
85 		pdo_raise_impl_error(stmt->dbh, stmt, "HY093", "parameter was not defined");
86 		return 0;
87 	}
88 	return 1;
89 }
90 /* }}} */
91 
92 /* trigger callback hook for parameters */
dispatch_param_event(pdo_stmt_t * stmt,enum pdo_param_event event_type)93 static bool dispatch_param_event(pdo_stmt_t *stmt, enum pdo_param_event event_type) /* {{{ */
94 {
95 	bool ret = 1, is_param = 1;
96 	struct pdo_bound_param_data *param;
97 	HashTable *ht;
98 
99 	if (stmt->dbh->skip_param_evt & (1 << event_type)) {
100 		return 1;
101 	}
102 
103 	if (!stmt->methods->param_hook) {
104 		return 1;
105 	}
106 
107 	ht = stmt->bound_params;
108 
109 iterate:
110 	if (ht) {
111 		ZEND_HASH_FOREACH_PTR(ht, param) {
112 			if (!stmt->methods->param_hook(stmt, param, event_type)) {
113 				ret = 0;
114 				break;
115 			}
116 		} ZEND_HASH_FOREACH_END();
117 	}
118 	if (ret && is_param) {
119 		ht = stmt->bound_columns;
120 		is_param = 0;
121 		goto iterate;
122 	}
123 
124 	return ret;
125 }
126 /* }}} */
127 
pdo_stmt_describe_columns(pdo_stmt_t * stmt)128 bool pdo_stmt_describe_columns(pdo_stmt_t *stmt) /* {{{ */
129 {
130 	int col;
131 
132 	stmt->columns = ecalloc(stmt->column_count, sizeof(struct pdo_column_data));
133 
134 	for (col = 0; col < stmt->column_count; col++) {
135 		if (!stmt->methods->describer(stmt, col)) {
136 			return false;
137 		}
138 
139 		/* if we are applying case conversions on column names, do so now */
140 		if (stmt->dbh->native_case != stmt->dbh->desired_case && stmt->dbh->desired_case != PDO_CASE_NATURAL) {
141 			zend_string *orig_name = stmt->columns[col].name;
142 			switch (stmt->dbh->desired_case) {
143 				case PDO_CASE_LOWER:
144 					stmt->columns[col].name = zend_string_tolower(orig_name);
145 					zend_string_release(orig_name);
146 					break;
147 				case PDO_CASE_UPPER: {
148 					stmt->columns[col].name = zend_string_separate(orig_name, 0);
149 					char *s = ZSTR_VAL(stmt->columns[col].name);
150 					while (*s != '\0') {
151 						*s = toupper(*s);
152 						s++;
153 					}
154 					break;
155 				}
156 				EMPTY_SWITCH_DEFAULT_CASE()
157 			}
158 		}
159 
160 		/* update the column index on named bound parameters */
161 		if (stmt->bound_columns) {
162 			struct pdo_bound_param_data *param;
163 
164 			if ((param = zend_hash_find_ptr(stmt->bound_columns,
165 					stmt->columns[col].name)) != NULL) {
166 				param->paramno = col;
167 			}
168 		}
169 
170 	}
171 	return true;
172 }
173 /* }}} */
174 
pdo_stmt_reset_columns(pdo_stmt_t * stmt)175 static void pdo_stmt_reset_columns(pdo_stmt_t *stmt) {
176 	if (stmt->columns) {
177 		int i;
178 		struct pdo_column_data *cols = stmt->columns;
179 
180 		for (i = 0; i < stmt->column_count; i++) {
181 			if (cols[i].name) {
182 				zend_string_release_ex(cols[i].name, 0);
183 			}
184 		}
185 		efree(stmt->columns);
186 	}
187 	stmt->columns = NULL;
188 	stmt->column_count = 0;
189 }
190 
191 /**
192  * Change the column count on the statement. If it differs from the previous one,
193  * discard existing columns information.
194  */
php_pdo_stmt_set_column_count(pdo_stmt_t * stmt,int new_count)195 PDO_API void php_pdo_stmt_set_column_count(pdo_stmt_t *stmt, int new_count)
196 {
197 	/* Columns not yet "described". */
198 	if (!stmt->columns) {
199 		stmt->column_count = new_count;
200 		return;
201 	}
202 
203 	/* The column count has not changed: No need to reload columns description.
204 	 * Note: Do not handle attribute name change, without column count change. */
205 	if (new_count == stmt->column_count) {
206 		return;
207 	}
208 
209 	/* Free previous columns to force reload description. */
210 	pdo_stmt_reset_columns(stmt);
211 	stmt->column_count = new_count;
212 }
213 
get_lazy_object(pdo_stmt_t * stmt,zval * return_value)214 static void get_lazy_object(pdo_stmt_t *stmt, zval *return_value) /* {{{ */
215 {
216 	if (Z_ISUNDEF(stmt->lazy_object_ref)) {
217 		pdo_row_t *row = ecalloc(1, sizeof(pdo_row_t));
218 		row->stmt = stmt;
219 		zend_object_std_init(&row->std, pdo_row_ce);
220 		ZVAL_OBJ(&stmt->lazy_object_ref, &row->std);
221 		row->std.handlers = &pdo_row_object_handlers;
222 		GC_ADDREF(&stmt->std);
223 		GC_DELREF(&row->std);
224 	}
225 	ZVAL_COPY(return_value, &stmt->lazy_object_ref);
226 }
227 /* }}} */
228 
param_dtor(zval * el)229 static void param_dtor(zval *el) /* {{{ */
230 {
231 	struct pdo_bound_param_data *param = (struct pdo_bound_param_data *)Z_PTR_P(el);
232 
233 	/* tell the driver that it is going away */
234 	if (param->stmt->methods->param_hook) {
235 		param->stmt->methods->param_hook(param->stmt, param, PDO_PARAM_EVT_FREE);
236 	}
237 
238 	if (param->name) {
239 		zend_string_release_ex(param->name, 0);
240 	}
241 
242 	if (!Z_ISUNDEF(param->parameter)) {
243 		zval_ptr_dtor(&param->parameter);
244 		ZVAL_UNDEF(&param->parameter);
245 	}
246 	if (!Z_ISUNDEF(param->driver_params)) {
247 		zval_ptr_dtor(&param->driver_params);
248 	}
249 	efree(param);
250 }
251 /* }}} */
252 
really_register_bound_param(struct pdo_bound_param_data * param,pdo_stmt_t * stmt,bool is_param)253 static bool really_register_bound_param(struct pdo_bound_param_data *param, pdo_stmt_t *stmt, bool is_param) /* {{{ */
254 {
255 	HashTable *hash;
256 	zval *parameter;
257 	struct pdo_bound_param_data *pparam = NULL;
258 
259 	hash = is_param ? stmt->bound_params : stmt->bound_columns;
260 
261 	if (!hash) {
262 		ALLOC_HASHTABLE(hash);
263 		zend_hash_init(hash, 13, NULL, param_dtor, 0);
264 
265 		if (is_param) {
266 			stmt->bound_params = hash;
267 		} else {
268 			stmt->bound_columns = hash;
269 		}
270 	}
271 
272 	if (!Z_ISREF(param->parameter)) {
273 		parameter = &param->parameter;
274 	} else {
275 		parameter = Z_REFVAL(param->parameter);
276 	}
277 
278 	if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_STR && param->max_value_len <= 0 && !Z_ISNULL_P(parameter)) {
279 		if (!try_convert_to_string(parameter)) {
280 			return 0;
281 		}
282 	} else if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_INT && (Z_TYPE_P(parameter) == IS_FALSE || Z_TYPE_P(parameter) == IS_TRUE)) {
283 		convert_to_long(parameter);
284 	} else if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_BOOL && Z_TYPE_P(parameter) == IS_LONG) {
285 		convert_to_boolean(parameter);
286 	}
287 
288 	param->stmt = stmt;
289 	param->is_param = is_param;
290 
291 	if (Z_REFCOUNTED(param->driver_params)) {
292 		Z_ADDREF(param->driver_params);
293 	}
294 
295 	if (!is_param && param->name && stmt->columns) {
296 		/* try to map the name to the column */
297 		int i;
298 
299 		for (i = 0; i < stmt->column_count; i++) {
300 			if (zend_string_equals(stmt->columns[i].name, param->name)) {
301 				param->paramno = i;
302 				break;
303 			}
304 		}
305 
306 		/* if you prepare and then execute passing an array of params keyed by names,
307 		 * then this will trigger, and we don't want that */
308 		if (param->paramno == -1) {
309 			/* Should this always be an Error? */
310 			char *tmp;
311 			/* TODO Error? */
312 			spprintf(&tmp, 0, "Did not find column name '%s' in the defined columns; it will not be bound", ZSTR_VAL(param->name));
313 			pdo_raise_impl_error(stmt->dbh, stmt, "HY000", tmp);
314 			efree(tmp);
315 		}
316 	}
317 
318 	if (param->name) {
319 		if (is_param && ZSTR_VAL(param->name)[0] != ':') {
320 			zend_string *temp = zend_string_alloc(ZSTR_LEN(param->name) + 1, 0);
321 			ZSTR_VAL(temp)[0] = ':';
322 			memmove(ZSTR_VAL(temp) + 1, ZSTR_VAL(param->name), ZSTR_LEN(param->name) + 1);
323 			param->name = temp;
324 		} else {
325 			param->name = zend_string_init(ZSTR_VAL(param->name), ZSTR_LEN(param->name), 0);
326 		}
327 	}
328 
329 	if (is_param && !rewrite_name_to_position(stmt, param)) {
330 		if (param->name) {
331 			zend_string_release_ex(param->name, 0);
332 			param->name = NULL;
333 		}
334 		return 0;
335 	}
336 
337 	/* ask the driver to perform any normalization it needs on the
338 	 * parameter name.  Note that it is illegal for the driver to take
339 	 * a reference to param, as it resides in transient storage only
340 	 * at this time. */
341 	if (stmt->methods->param_hook) {
342 		if (!stmt->methods->param_hook(stmt, param, PDO_PARAM_EVT_NORMALIZE)) {
343 			PDO_HANDLE_STMT_ERR();
344 			if (param->name) {
345 				zend_string_release_ex(param->name, 0);
346 				param->name = NULL;
347 			}
348 			return 0;
349 		}
350 	}
351 
352 	/* delete any other parameter registered with this number.
353 	 * If the parameter is named, it will be removed and correctly
354 	 * disposed of by the hash_update call that follows */
355 	if (param->paramno >= 0) {
356 		zend_hash_index_del(hash, param->paramno);
357 	}
358 
359 	/* allocate storage for the parameter, keyed by its "canonical" name */
360 	if (param->name) {
361 		pparam = zend_hash_update_mem(hash, param->name, param, sizeof(struct pdo_bound_param_data));
362 	} else {
363 		pparam = zend_hash_index_update_mem(hash, param->paramno, param, sizeof(struct pdo_bound_param_data));
364 	}
365 
366 	/* tell the driver we just created a parameter */
367 	if (stmt->methods->param_hook) {
368 		if (!stmt->methods->param_hook(stmt, pparam, PDO_PARAM_EVT_ALLOC)) {
369 			PDO_HANDLE_STMT_ERR();
370 			/* undo storage allocation; the hash will free the parameter
371 			 * name if required */
372 			if (pparam->name) {
373 				zend_hash_del(hash, pparam->name);
374 			} else {
375 				zend_hash_index_del(hash, pparam->paramno);
376 			}
377 			/* param->parameter is freed by hash dtor */
378 			ZVAL_UNDEF(&param->parameter);
379 			return 0;
380 		}
381 	}
382 	return 1;
383 }
384 /* }}} */
385 
386 /* {{{ Execute a prepared statement, optionally binding parameters */
PHP_METHOD(PDOStatement,execute)387 PHP_METHOD(PDOStatement, execute)
388 {
389 	zval *input_params = NULL;
390 	int ret = 1;
391 
392 	ZEND_PARSE_PARAMETERS_START(0, 1)
393 		Z_PARAM_OPTIONAL
394 		Z_PARAM_ARRAY_OR_NULL(input_params)
395 	ZEND_PARSE_PARAMETERS_END();
396 
397 	PHP_STMT_GET_OBJ;
398 	PDO_STMT_CLEAR_ERR();
399 
400 	if (input_params) {
401 		struct pdo_bound_param_data param;
402 		zval *tmp;
403 		zend_string *key = NULL;
404 		zend_ulong num_index;
405 
406 		if (stmt->bound_params) {
407 			zend_hash_destroy(stmt->bound_params);
408 			FREE_HASHTABLE(stmt->bound_params);
409 			stmt->bound_params = NULL;
410 		}
411 
412 		ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(input_params), num_index, key, tmp) {
413 			memset(&param, 0, sizeof(param));
414 
415 			if (key) {
416 				/* yes this is correct.  we don't want to count the null byte.  ask wez */
417 				param.name = key;
418 				param.paramno = -1;
419 			} else {
420 				/* we're okay to be zero based here */
421 				/* num_index is unsignend */
422 				param.paramno = num_index;
423 			}
424 
425 			param.param_type = PDO_PARAM_STR;
426 			ZVAL_COPY(&param.parameter, tmp);
427 
428 			if (!really_register_bound_param(&param, stmt, 1)) {
429 				if (!Z_ISUNDEF(param.parameter)) {
430 					zval_ptr_dtor(&param.parameter);
431 				}
432 				RETURN_FALSE;
433 			}
434 		} ZEND_HASH_FOREACH_END();
435 	}
436 
437 	if (PDO_PLACEHOLDER_NONE == stmt->supports_placeholders) {
438 		/* handle the emulated parameter binding,
439 		 * stmt->active_query_string holds the query with binds expanded and
440 		 * quoted.
441 		 */
442 
443 		/* string is leftover from previous calls so PDOStatement::debugDumpParams() can access */
444 		if (stmt->active_query_string) {
445 			zend_string_release(stmt->active_query_string);
446 			stmt->active_query_string = NULL;
447 		}
448 
449 		ret = pdo_parse_params(stmt, stmt->query_string, &stmt->active_query_string);
450 
451 		if (ret == 0) {
452 			/* no changes were made */
453 			stmt->active_query_string = zend_string_copy(stmt->query_string);
454 			ret = 1;
455 		} else if (ret == -1) {
456 			/* something broke */
457 			RETURN_FALSE;
458 		}
459 	} else if (!dispatch_param_event(stmt, PDO_PARAM_EVT_EXEC_PRE)) {
460 		PDO_HANDLE_STMT_ERR();
461 		RETURN_FALSE;
462 	}
463 	if (stmt->methods->executer(stmt)) {
464 		if (!stmt->executed) {
465 			/* this is the first execute */
466 
467 			if (stmt->dbh->alloc_own_columns && !stmt->columns) {
468 				/* for "big boy" drivers, we need to allocate memory to fetch
469 				 * the results into, so lets do that now */
470 				ret = pdo_stmt_describe_columns(stmt);
471 			}
472 
473 			stmt->executed = 1;
474 		}
475 
476 		if (ret && !dispatch_param_event(stmt, PDO_PARAM_EVT_EXEC_POST)) {
477 			PDO_HANDLE_STMT_ERR();
478 			RETURN_FALSE;
479 		}
480 
481 		RETURN_BOOL(ret);
482 	}
483 	PDO_HANDLE_STMT_ERR();
484 	RETURN_FALSE;
485 }
486 /* }}} */
487 
fetch_value(pdo_stmt_t * stmt,zval * dest,int colno,enum pdo_param_type * type_override)488 static inline void fetch_value(pdo_stmt_t *stmt, zval *dest, int colno, enum pdo_param_type *type_override) /* {{{ */
489 {
490 	if (colno < 0) {
491 		zend_value_error("Column index must be greater than or equal to 0");
492 		ZVAL_NULL(dest);
493 		return;
494 	}
495 
496 	if (colno >= stmt->column_count) {
497 		zend_value_error("Invalid column index");
498 		ZVAL_NULL(dest);
499 		return;
500 	}
501 
502 	ZVAL_NULL(dest);
503 	stmt->methods->get_col(stmt, colno, dest, type_override);
504 
505 	if (Z_TYPE_P(dest) == IS_STRING && Z_STRLEN_P(dest) == 0
506 			&& stmt->dbh->oracle_nulls == PDO_NULL_EMPTY_STRING) {
507 		zval_ptr_dtor_str(dest);
508 		ZVAL_NULL(dest);
509 	}
510 
511 	/* If stringification is requested, override with PDO_PARAM_STR. */
512 	enum pdo_param_type pdo_param_str = PDO_PARAM_STR;
513 	if (stmt->dbh->stringify) {
514 		type_override = &pdo_param_str;
515 	}
516 
517 	if (type_override && Z_TYPE_P(dest) != IS_NULL) {
518 		switch (*type_override) {
519 			case PDO_PARAM_INT:
520 				convert_to_long(dest);
521 				break;
522 			case PDO_PARAM_BOOL:
523 				convert_to_boolean(dest);
524 				break;
525 			case PDO_PARAM_STR:
526 				if (Z_TYPE_P(dest) == IS_FALSE) {
527 					/* Return "0" rather than "", because this is what database drivers that
528 					 * don't have a dedicated boolean type would return. */
529 					zval_ptr_dtor_nogc(dest);
530 					ZVAL_INTERNED_STR(dest, ZSTR_CHAR('0'));
531 				} else if (Z_TYPE_P(dest) == IS_RESOURCE) {
532 					/* Convert LOB stream to string */
533 					php_stream *stream;
534 					php_stream_from_zval_no_verify(stream, dest);
535 					zend_string *str = php_stream_copy_to_mem(stream, PHP_STREAM_COPY_ALL, 0);
536 					zval_ptr_dtor_nogc(dest);
537 					if (str == NULL) {
538 						ZVAL_EMPTY_STRING(dest);
539 					} else {
540 						ZVAL_STR(dest, str);
541 					}
542 				} else {
543 					convert_to_string(dest);
544 				}
545 				break;
546 			case PDO_PARAM_NULL:
547 				convert_to_null(dest);
548 				break;
549 			case PDO_PARAM_LOB:
550 				if (Z_TYPE_P(dest) == IS_STRING) {
551 					php_stream *stream =
552 						php_stream_memory_open(TEMP_STREAM_READONLY, Z_STR_P(dest));
553 					zval_ptr_dtor_str(dest);
554 					php_stream_to_zval(stream, dest);
555 				}
556 				break;
557 			default:
558 				break;
559 		}
560 	}
561 
562 	if (Z_TYPE_P(dest) == IS_NULL && stmt->dbh->oracle_nulls == PDO_NULL_TO_STRING) {
563 		ZVAL_EMPTY_STRING(dest);
564 	}
565 }
566 /* }}} */
567 
do_fetch_common(pdo_stmt_t * stmt,enum pdo_fetch_orientation ori,zend_long offset)568 static bool do_fetch_common(pdo_stmt_t *stmt, enum pdo_fetch_orientation ori, zend_long offset) /* {{{ */
569 {
570 	if (!stmt->executed) {
571 		return 0;
572 	}
573 
574 	if (!dispatch_param_event(stmt, PDO_PARAM_EVT_FETCH_PRE)) {
575 		return 0;
576 	}
577 
578 	if (!stmt->methods->fetcher(stmt, ori, offset)) {
579 		return 0;
580 	}
581 
582 	/* some drivers might need to describe the columns now */
583 	if (!stmt->columns && !pdo_stmt_describe_columns(stmt)) {
584 		return 0;
585 	}
586 
587 	if (!dispatch_param_event(stmt, PDO_PARAM_EVT_FETCH_POST)) {
588 		return 0;
589 	}
590 
591 	if (stmt->bound_columns) {
592 		/* update those bound column variables now */
593 		struct pdo_bound_param_data *param;
594 
595 		ZEND_HASH_FOREACH_PTR(stmt->bound_columns, param) {
596 			if (param->paramno >= 0) {
597 				if (!Z_ISREF(param->parameter)) {
598 					continue;
599 				}
600 
601 				/* delete old value */
602 				zval_ptr_dtor(Z_REFVAL(param->parameter));
603 
604 				/* set new value */
605 				fetch_value(stmt, Z_REFVAL(param->parameter), param->paramno, &param->param_type);
606 
607 				/* TODO: some smart thing that avoids duplicating the value in the
608 				 * general loop below.  For now, if you're binding output columns,
609 				 * it's better to use LAZY or BOUND fetches if you want to shave
610 				 * off those cycles */
611 			}
612 		} ZEND_HASH_FOREACH_END();
613 	}
614 
615 	return 1;
616 }
617 /* }}} */
618 
do_fetch_class_prepare(pdo_stmt_t * stmt)619 static bool do_fetch_class_prepare(pdo_stmt_t *stmt) /* {{{ */
620 {
621 	zend_class_entry *ce = stmt->fetch.cls.ce;
622 	zend_fcall_info *fci = &stmt->fetch.cls.fci;
623 	zend_fcall_info_cache *fcc = &stmt->fetch.cls.fcc;
624 
625 	fci->size = sizeof(zend_fcall_info);
626 
627 	if (!ce) {
628 		stmt->fetch.cls.ce = ZEND_STANDARD_CLASS_DEF_PTR;
629 		ce = ZEND_STANDARD_CLASS_DEF_PTR;
630 	}
631 
632 	if (ce->constructor) {
633 		ZVAL_UNDEF(&fci->function_name);
634 		fci->retval = &stmt->fetch.cls.retval;
635 		fci->param_count = 0;
636 		fci->params = NULL;
637 
638 		zend_fcall_info_args_ex(fci, ce->constructor, &stmt->fetch.cls.ctor_args);
639 
640 		fcc->function_handler = ce->constructor;
641 		fcc->called_scope = ce;
642 		return 1;
643 	} else if (!Z_ISUNDEF(stmt->fetch.cls.ctor_args)) {
644 		zend_throw_error(NULL, "User-supplied statement does not accept constructor arguments");
645 		return 0;
646 	} else {
647 		return 1; /* no ctor no args is also ok */
648 	}
649 }
650 /* }}} */
651 
make_callable_ex(pdo_stmt_t * stmt,zval * callable,zend_fcall_info * fci,zend_fcall_info_cache * fcc,int num_args)652 static bool make_callable_ex(pdo_stmt_t *stmt, zval *callable, zend_fcall_info * fci, zend_fcall_info_cache * fcc, int num_args) /* {{{ */
653 {
654 	char *is_callable_error = NULL;
655 
656 	if (zend_fcall_info_init(callable, 0, fci, fcc, NULL, &is_callable_error) == FAILURE) {
657 		if (is_callable_error) {
658 			zend_type_error("%s", is_callable_error);
659 			efree(is_callable_error);
660 		} else {
661 			zend_type_error("User-supplied function must be a valid callback");
662 		}
663 		return false;
664 	}
665 	if (is_callable_error) {
666 		/* Possible error message */
667 		efree(is_callable_error);
668 	}
669 
670 	fci->param_count = num_args; /* probably less */
671 	fci->params = safe_emalloc(sizeof(zval), num_args, 0);
672 
673 	return true;
674 }
675 /* }}} */
676 
do_fetch_func_prepare(pdo_stmt_t * stmt)677 static bool do_fetch_func_prepare(pdo_stmt_t *stmt) /* {{{ */
678 {
679 	zend_fcall_info *fci = &stmt->fetch.cls.fci;
680 	zend_fcall_info_cache *fcc = &stmt->fetch.cls.fcc;
681 
682 	if (!make_callable_ex(stmt, &stmt->fetch.func.function, fci, fcc, stmt->column_count)) {
683 		return false;
684 	} else {
685 		stmt->fetch.func.values = safe_emalloc(sizeof(zval), stmt->column_count, 0);
686 		return true;
687 	}
688 }
689 /* }}} */
690 
do_fetch_opt_finish(pdo_stmt_t * stmt,int free_ctor_agrs)691 static void do_fetch_opt_finish(pdo_stmt_t *stmt, int free_ctor_agrs) /* {{{ */
692 {
693 	/* fci.size is used to check if it is valid */
694 	if (stmt->fetch.cls.fci.size && stmt->fetch.cls.fci.params) {
695 		if (!Z_ISUNDEF(stmt->fetch.cls.ctor_args)) {
696 			/* Added to free constructor arguments */
697 			zend_fcall_info_args_clear(&stmt->fetch.cls.fci, 1);
698 		} else {
699 			efree(stmt->fetch.cls.fci.params);
700 		}
701 		stmt->fetch.cls.fci.params = NULL;
702 	}
703 
704 	stmt->fetch.cls.fci.size = 0;
705 	if (!Z_ISUNDEF(stmt->fetch.cls.ctor_args) && free_ctor_agrs) {
706 		zval_ptr_dtor(&stmt->fetch.cls.ctor_args);
707 		ZVAL_UNDEF(&stmt->fetch.cls.ctor_args);
708 		stmt->fetch.cls.fci.param_count = 0;
709 	}
710 	if (stmt->fetch.func.values) {
711 		efree(stmt->fetch.func.values);
712 		stmt->fetch.func.values = NULL;
713 	}
714 }
715 /* }}} */
716 
717 /* perform a fetch.
718  * If return_value is not null, store values into it according to HOW. */
do_fetch(pdo_stmt_t * stmt,zval * return_value,enum pdo_fetch_type how,enum pdo_fetch_orientation ori,zend_long offset,zval * return_all)719 static bool do_fetch(pdo_stmt_t *stmt, zval *return_value, enum pdo_fetch_type how, enum pdo_fetch_orientation ori, zend_long offset, zval *return_all) /* {{{ */
720 {
721 	int flags, idx, old_arg_count = 0;
722 	zend_class_entry *ce = NULL, *old_ce = NULL;
723 	zval grp_val, *pgrp, retval, old_ctor_args = {{0}, {0}, {0}};
724 	int colno;
725 	int i = 0;
726 
727 	if (how == PDO_FETCH_USE_DEFAULT) {
728 		how = stmt->default_fetch_type;
729 	}
730 	flags = how & PDO_FETCH_FLAGS;
731 	how = how & ~PDO_FETCH_FLAGS;
732 
733 	if (!do_fetch_common(stmt, ori, offset)) {
734 		return 0;
735 	}
736 
737 	if (how == PDO_FETCH_BOUND) {
738 		RETVAL_TRUE;
739 		return 1;
740 	}
741 
742 	if ((flags & PDO_FETCH_GROUP) && stmt->fetch.column == -1) {
743 		colno = 1;
744 	} else {
745 		colno = stmt->fetch.column;
746 	}
747 
748 	/* If no return value we are done */
749 	if (!return_value) {
750 		return true;
751 	}
752 
753 	if (how == PDO_FETCH_LAZY) {
754 		get_lazy_object(stmt, return_value);
755 		return 1;
756 	}
757 
758 	RETVAL_FALSE;
759 
760 	switch (how) {
761 		case PDO_FETCH_USE_DEFAULT:
762 		case PDO_FETCH_ASSOC:
763 		case PDO_FETCH_BOTH:
764 		case PDO_FETCH_NUM:
765 		case PDO_FETCH_NAMED:
766 			if (!return_all) {
767 				array_init_size(return_value, stmt->column_count);
768 			} else {
769 				array_init(return_value);
770 			}
771 			break;
772 
773 		case PDO_FETCH_KEY_PAIR:
774 			if (stmt->column_count != 2) {
775 				/* TODO: Error? */
776 				pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "PDO::FETCH_KEY_PAIR fetch mode requires the result set to contain exactly 2 columns.");
777 				return 0;
778 			}
779 			if (!return_all) {
780 				array_init(return_value);
781 			}
782 			break;
783 
784 		case PDO_FETCH_COLUMN:
785 			if (colno < 0 ) {
786 				zend_value_error("Column index must be greater than or equal to 0");
787 				return false;
788 			}
789 
790 			if (colno >= stmt->column_count) {
791 				zend_value_error("Invalid column index");
792 				return false;
793 			}
794 
795 			if (flags == PDO_FETCH_GROUP && stmt->fetch.column == -1) {
796 				fetch_value(stmt, return_value, 1, NULL);
797 			} else if (flags == PDO_FETCH_GROUP && colno) {
798 				fetch_value(stmt, return_value, 0, NULL);
799 			} else {
800 				fetch_value(stmt, return_value, colno, NULL);
801 			}
802 			if (!return_all) {
803 				return 1;
804 			}
805 			break;
806 
807 		case PDO_FETCH_OBJ:
808 			object_init_ex(return_value, ZEND_STANDARD_CLASS_DEF_PTR);
809 			break;
810 
811 		case PDO_FETCH_CLASS:
812 			if (flags & PDO_FETCH_CLASSTYPE) {
813 				zval val;
814 				zend_class_entry *cep;
815 
816 				old_ce = stmt->fetch.cls.ce;
817 				ZVAL_COPY_VALUE(&old_ctor_args, &stmt->fetch.cls.ctor_args);
818 				old_arg_count = stmt->fetch.cls.fci.param_count;
819 				do_fetch_opt_finish(stmt, 0);
820 
821 				fetch_value(stmt, &val, i++, NULL);
822 				if (Z_TYPE(val) != IS_NULL) {
823 					if (!try_convert_to_string(&val)) {
824 						return 0;
825 					}
826 					if ((cep = zend_lookup_class(Z_STR(val))) == NULL) {
827 						stmt->fetch.cls.ce = ZEND_STANDARD_CLASS_DEF_PTR;
828 					} else {
829 						stmt->fetch.cls.ce = cep;
830 					}
831 				}
832 
833 				do_fetch_class_prepare(stmt);
834 				zval_ptr_dtor_str(&val);
835 			}
836 			ce = stmt->fetch.cls.ce;
837 			/* TODO: Make this an assertion and ensure this is true higher up? */
838 			if (!ce) {
839 				/* TODO Error? */
840 				pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "No fetch class specified");
841 				return 0;
842 			}
843 			if ((flags & PDO_FETCH_SERIALIZE) == 0) {
844 				if (UNEXPECTED(object_init_ex(return_value, ce) != SUCCESS)) {
845 					return 0;
846 				}
847 				if (!stmt->fetch.cls.fci.size) {
848 					if (!do_fetch_class_prepare(stmt)) {
849 						zval_ptr_dtor(return_value);
850 						return 0;
851 					}
852 				}
853 				if (ce->constructor && (flags & PDO_FETCH_PROPS_LATE)) {
854 					stmt->fetch.cls.fci.object = Z_OBJ_P(return_value);
855 					stmt->fetch.cls.fcc.object = Z_OBJ_P(return_value);
856 					if (zend_call_function(&stmt->fetch.cls.fci, &stmt->fetch.cls.fcc) == FAILURE) {
857 						/* TODO Error? */
858 						pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "could not call class constructor");
859 						return 0;
860 					} else {
861 						if (!Z_ISUNDEF(stmt->fetch.cls.retval)) {
862 							zval_ptr_dtor(&stmt->fetch.cls.retval);
863 							ZVAL_UNDEF(&stmt->fetch.cls.retval);
864 						}
865 					}
866 				}
867 			}
868 			break;
869 
870 		case PDO_FETCH_INTO:
871 			/* TODO: Make this an assertion and ensure this is true higher up? */
872 			if (Z_ISUNDEF(stmt->fetch.into)) {
873 				/* TODO ArgumentCountError? */
874 				pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "No fetch-into object specified.");
875 				return 0;
876 				break;
877 			}
878 
879 			ZVAL_COPY(return_value, &stmt->fetch.into);
880 
881 			if (Z_OBJ_P(return_value)->ce == ZEND_STANDARD_CLASS_DEF_PTR) {
882 				how = PDO_FETCH_OBJ;
883 			}
884 			break;
885 
886 		case PDO_FETCH_FUNC:
887 			/* TODO: Make this an assertion and ensure this is true higher up? */
888 			if (Z_ISUNDEF(stmt->fetch.func.function)) {
889 				/* TODO ArgumentCountError? */
890 				pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "No fetch function specified");
891 				return 0;
892 			}
893 			if (!stmt->fetch.func.fci.size) {
894 				if (!do_fetch_func_prepare(stmt))
895 				{
896 					return 0;
897 				}
898 			}
899 			break;
900 		EMPTY_SWITCH_DEFAULT_CASE();
901 	}
902 
903 	if (return_all && how != PDO_FETCH_KEY_PAIR) {
904 		if (flags == PDO_FETCH_GROUP && how == PDO_FETCH_COLUMN && stmt->fetch.column > 0) {
905 			fetch_value(stmt, &grp_val, colno, NULL);
906 		} else {
907 			fetch_value(stmt, &grp_val, i, NULL);
908 		}
909 		convert_to_string(&grp_val);
910 		if (how == PDO_FETCH_COLUMN) {
911 			i = stmt->column_count; /* no more data to fetch */
912 		} else {
913 			i++;
914 		}
915 	}
916 
917 	for (idx = 0; i < stmt->column_count; i++, idx++) {
918 		zval val;
919 		fetch_value(stmt, &val, i, NULL);
920 
921 		switch (how) {
922 			case PDO_FETCH_ASSOC:
923 				zend_symtable_update(Z_ARRVAL_P(return_value), stmt->columns[i].name, &val);
924 				break;
925 
926 			case PDO_FETCH_KEY_PAIR:
927 				{
928 					zval tmp;
929 					fetch_value(stmt, &tmp, ++i, NULL);
930 
931 					if (Z_TYPE(val) == IS_LONG) {
932 						zend_hash_index_update((return_all ? Z_ARRVAL_P(return_all) : Z_ARRVAL_P(return_value)), Z_LVAL(val), &tmp);
933 					} else {
934 						convert_to_string(&val);
935 						zend_symtable_update((return_all ? Z_ARRVAL_P(return_all) : Z_ARRVAL_P(return_value)), Z_STR(val), &tmp);
936 					}
937 					zval_ptr_dtor(&val);
938 					return 1;
939 				}
940 				break;
941 
942 			case PDO_FETCH_USE_DEFAULT:
943 			case PDO_FETCH_BOTH:
944 				zend_symtable_update(Z_ARRVAL_P(return_value), stmt->columns[i].name, &val);
945 				if (zend_hash_index_add(Z_ARRVAL_P(return_value), i, &val) != NULL) {
946 					Z_TRY_ADDREF(val);
947 				}
948 				break;
949 
950 			case PDO_FETCH_NAMED:
951 				/* already have an item with this name? */
952 				{
953 					zval *curr_val;
954 					if ((curr_val = zend_hash_find(Z_ARRVAL_P(return_value), stmt->columns[i].name))) {
955 						zval arr;
956 						if (Z_TYPE_P(curr_val) != IS_ARRAY) {
957 							/* a little bit of black magic here:
958 							 * we're creating a new array and swapping it for the
959 							 * zval that's already stored in the hash under the name
960 							 * we want.  We then add that zval to the array.
961 							 * This is effectively the same thing as:
962 							 * if (!is_array($hash[$name])) {
963 							 *   $hash[$name] = array($hash[$name]);
964 							 * }
965 							 * */
966 							zval cur;
967 
968 							array_init(&arr);
969 
970 							ZVAL_COPY_VALUE(&cur, curr_val);
971 							ZVAL_COPY_VALUE(curr_val, &arr);
972 
973 							zend_hash_next_index_insert_new(Z_ARRVAL(arr), &cur);
974 						} else {
975 							ZVAL_COPY_VALUE(&arr, curr_val);
976 						}
977 						zend_hash_next_index_insert_new(Z_ARRVAL(arr), &val);
978 					} else {
979 						zend_hash_update(Z_ARRVAL_P(return_value), stmt->columns[i].name, &val);
980 					}
981 				}
982 				break;
983 
984 			case PDO_FETCH_NUM:
985 				zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &val);
986 				break;
987 
988 			case PDO_FETCH_OBJ:
989 			case PDO_FETCH_INTO:
990 				zend_update_property_ex(NULL, Z_OBJ_P(return_value),
991 					stmt->columns[i].name,
992 					&val);
993 				zval_ptr_dtor(&val);
994 				break;
995 
996 			case PDO_FETCH_CLASS:
997 				if ((flags & PDO_FETCH_SERIALIZE) == 0 || idx) {
998 					zend_update_property_ex(ce, Z_OBJ_P(return_value),
999 						stmt->columns[i].name,
1000 						&val);
1001 					zval_ptr_dtor(&val);
1002 				} else {
1003 					if (!ce->unserialize) {
1004 						zval_ptr_dtor(&val);
1005 						pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "cannot unserialize class");
1006 						return 0;
1007 					} else if (ce->unserialize(return_value, ce, (unsigned char *)(Z_TYPE(val) == IS_STRING ? Z_STRVAL(val) : ""), Z_TYPE(val) == IS_STRING ? Z_STRLEN(val) : 0, NULL) == FAILURE) {
1008 						zval_ptr_dtor(&val);
1009 						pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "cannot unserialize class");
1010 						zval_ptr_dtor(return_value);
1011 						ZVAL_NULL(return_value);
1012 						return 0;
1013 					} else {
1014 						zval_ptr_dtor(&val);
1015 					}
1016 				}
1017 				break;
1018 
1019 			case PDO_FETCH_FUNC:
1020 				ZVAL_COPY_VALUE(&stmt->fetch.func.values[idx], &val);
1021 				ZVAL_COPY_VALUE(&stmt->fetch.cls.fci.params[idx], &stmt->fetch.func.values[idx]);
1022 				break;
1023 
1024 			default:
1025 				zval_ptr_dtor(&val);
1026 				zend_value_error("Fetch mode must be a bitmask of PDO::FETCH_* constants");
1027 				return 0;
1028 		}
1029 	}
1030 
1031 	switch (how) {
1032 		case PDO_FETCH_CLASS:
1033 			if (ce->constructor && !(flags & (PDO_FETCH_PROPS_LATE | PDO_FETCH_SERIALIZE))) {
1034 				stmt->fetch.cls.fci.object = Z_OBJ_P(return_value);
1035 				stmt->fetch.cls.fcc.object = Z_OBJ_P(return_value);
1036 				if (zend_call_function(&stmt->fetch.cls.fci, &stmt->fetch.cls.fcc) == FAILURE) {
1037 					/* TODO Error? */
1038 					pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "could not call class constructor");
1039 					return 0;
1040 				} else {
1041 					if (!Z_ISUNDEF(stmt->fetch.cls.retval)) {
1042 						zval_ptr_dtor(&stmt->fetch.cls.retval);
1043 					}
1044 				}
1045 			}
1046 			if (flags & PDO_FETCH_CLASSTYPE) {
1047 				do_fetch_opt_finish(stmt, 0);
1048 				stmt->fetch.cls.ce = old_ce;
1049 				ZVAL_COPY_VALUE(&stmt->fetch.cls.ctor_args, &old_ctor_args);
1050 				stmt->fetch.cls.fci.param_count = old_arg_count;
1051 			}
1052 			break;
1053 
1054 		case PDO_FETCH_FUNC:
1055 			stmt->fetch.func.fci.param_count = idx;
1056 			stmt->fetch.func.fci.retval = &retval;
1057 			if (zend_call_function(&stmt->fetch.func.fci, &stmt->fetch.func.fcc) == FAILURE) {
1058 				/* TODO Error? */
1059 				pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "could not call user-supplied function");
1060 				return 0;
1061 			} else {
1062 				if (return_all) {
1063 					zval_ptr_dtor(return_value); /* we don't need that */
1064 					ZVAL_COPY_VALUE(return_value, &retval);
1065 				} else if (!Z_ISUNDEF(retval)) {
1066 					ZVAL_COPY_VALUE(return_value, &retval);
1067 				}
1068 			}
1069 			while (idx--) {
1070 				zval_ptr_dtor(&stmt->fetch.func.values[idx]);
1071 			}
1072 			break;
1073 
1074 		default:
1075 			break;
1076 	}
1077 
1078 	if (return_all) {
1079 		if ((flags & PDO_FETCH_UNIQUE) == PDO_FETCH_UNIQUE) {
1080 			zend_symtable_update(Z_ARRVAL_P(return_all), Z_STR(grp_val), return_value);
1081 		} else {
1082 			zval grp;
1083 			if ((pgrp = zend_symtable_find(Z_ARRVAL_P(return_all), Z_STR(grp_val))) == NULL) {
1084 				array_init(&grp);
1085 				zend_symtable_update(Z_ARRVAL_P(return_all), Z_STR(grp_val), &grp);
1086 			} else {
1087 				ZVAL_COPY_VALUE(&grp, pgrp);
1088 			}
1089 			zend_hash_next_index_insert(Z_ARRVAL(grp), return_value);
1090 		}
1091 		zval_ptr_dtor_str(&grp_val);
1092 	}
1093 
1094 	return 1;
1095 }
1096 /* }}} */
1097 
pdo_stmt_verify_mode(pdo_stmt_t * stmt,zend_long mode,uint32_t mode_arg_num,bool fetch_all)1098 static bool pdo_stmt_verify_mode(pdo_stmt_t *stmt, zend_long mode, uint32_t mode_arg_num, bool fetch_all) /* {{{ */
1099 {
1100 	int flags = mode & PDO_FETCH_FLAGS;
1101 
1102 	mode = mode & ~PDO_FETCH_FLAGS;
1103 
1104 	if (mode < 0 || mode > PDO_FETCH__MAX) {
1105 		zend_argument_value_error(mode_arg_num, "must be a bitmask of PDO::FETCH_* constants");
1106 		return 0;
1107 	}
1108 
1109 	if (mode == PDO_FETCH_USE_DEFAULT) {
1110 		flags = stmt->default_fetch_type & PDO_FETCH_FLAGS;
1111 		mode = stmt->default_fetch_type & ~PDO_FETCH_FLAGS;
1112 	}
1113 
1114 	switch(mode) {
1115 		case PDO_FETCH_FUNC:
1116 			if (!fetch_all) {
1117 				zend_value_error("Can only use PDO::FETCH_FUNC in PDOStatement::fetchAll()");
1118 				return 0;
1119 			}
1120 			return 1;
1121 
1122 		case PDO_FETCH_LAZY:
1123 			if (fetch_all) {
1124 				zend_argument_value_error(mode_arg_num, "cannot be PDO::FETCH_LAZY in PDOStatement::fetchAll()");
1125 				return 0;
1126 			}
1127 			ZEND_FALLTHROUGH;
1128 		default:
1129 			if ((flags & PDO_FETCH_SERIALIZE) == PDO_FETCH_SERIALIZE) {
1130 				zend_argument_value_error(mode_arg_num, "must use PDO::FETCH_SERIALIZE with PDO::FETCH_CLASS");
1131 				return 0;
1132 			}
1133 			if ((flags & PDO_FETCH_CLASSTYPE) == PDO_FETCH_CLASSTYPE) {
1134 				zend_argument_value_error(mode_arg_num, "must use PDO::FETCH_CLASSTYPE with PDO::FETCH_CLASS");
1135 				return 0;
1136 			}
1137 			if (mode >= PDO_FETCH__MAX) {
1138 				zend_argument_value_error(mode_arg_num, "must be a bitmask of PDO::FETCH_* constants");
1139 				return 0;
1140 			}
1141 			ZEND_FALLTHROUGH;
1142 
1143 		case PDO_FETCH_CLASS:
1144 			if (flags & PDO_FETCH_SERIALIZE) {
1145 				php_error_docref(NULL, E_DEPRECATED, "The PDO::FETCH_SERIALIZE mode is deprecated");
1146 			}
1147 			return 1;
1148 	}
1149 }
1150 /* }}} */
1151 
1152 /* {{{ Fetches the next row and returns it, or false if there are no more rows */
PHP_METHOD(PDOStatement,fetch)1153 PHP_METHOD(PDOStatement, fetch)
1154 {
1155 	zend_long how = PDO_FETCH_USE_DEFAULT;
1156 	zend_long ori = PDO_FETCH_ORI_NEXT;
1157 	zend_long off = 0;
1158 
1159 	ZEND_PARSE_PARAMETERS_START(0, 3)
1160 		Z_PARAM_OPTIONAL
1161 		Z_PARAM_LONG(how)
1162 		Z_PARAM_LONG(ori)
1163 		Z_PARAM_LONG(off)
1164 	ZEND_PARSE_PARAMETERS_END();
1165 
1166 	PHP_STMT_GET_OBJ;
1167 	PDO_STMT_CLEAR_ERR();
1168 
1169 	if (!pdo_stmt_verify_mode(stmt, how, 1, false)) {
1170 		RETURN_THROWS();
1171 	}
1172 
1173 	if (!do_fetch(stmt, return_value, how, ori, off, NULL)) {
1174 		PDO_HANDLE_STMT_ERR();
1175 		RETURN_FALSE;
1176 	}
1177 }
1178 /* }}} */
1179 
1180 /* {{{ Fetches the next row and returns it as an object. */
PHP_METHOD(PDOStatement,fetchObject)1181 PHP_METHOD(PDOStatement, fetchObject)
1182 {
1183 	zend_class_entry *ce = NULL;
1184 	zend_class_entry *old_ce;
1185 	zval old_ctor_args, *ctor_args = NULL;
1186 	int old_arg_count;
1187 
1188 	ZEND_PARSE_PARAMETERS_START(0, 2)
1189 		Z_PARAM_OPTIONAL
1190 		Z_PARAM_CLASS_OR_NULL(ce)
1191 		Z_PARAM_ARRAY(ctor_args)
1192 	ZEND_PARSE_PARAMETERS_END();
1193 
1194 	PHP_STMT_GET_OBJ;
1195 	PDO_STMT_CLEAR_ERR();
1196 
1197 	old_ce = stmt->fetch.cls.ce;
1198 	ZVAL_COPY_VALUE(&old_ctor_args, &stmt->fetch.cls.ctor_args);
1199 	old_arg_count = stmt->fetch.cls.fci.param_count;
1200 
1201 	do_fetch_opt_finish(stmt, 0);
1202 
1203 	if (ctor_args && zend_hash_num_elements(Z_ARRVAL_P(ctor_args))) {
1204 		ZVAL_ARR(&stmt->fetch.cls.ctor_args, zend_array_dup(Z_ARRVAL_P(ctor_args)));
1205 	} else {
1206 		ZVAL_UNDEF(&stmt->fetch.cls.ctor_args);
1207 	}
1208 	if (ce) {
1209 		stmt->fetch.cls.ce = ce;
1210 	} else {
1211 		stmt->fetch.cls.ce = zend_standard_class_def;
1212 	}
1213 
1214 	if (!do_fetch(stmt, return_value, PDO_FETCH_CLASS, PDO_FETCH_ORI_NEXT, /* offset */ 0, NULL)) {
1215 		PDO_HANDLE_STMT_ERR();
1216 		RETVAL_FALSE;
1217 	}
1218 	do_fetch_opt_finish(stmt, 1);
1219 
1220 	stmt->fetch.cls.ce = old_ce;
1221 	ZVAL_COPY_VALUE(&stmt->fetch.cls.ctor_args, &old_ctor_args);
1222 	stmt->fetch.cls.fci.param_count = old_arg_count;
1223 }
1224 /* }}} */
1225 
1226 /* {{{ Returns a data of the specified column in the result set. */
PHP_METHOD(PDOStatement,fetchColumn)1227 PHP_METHOD(PDOStatement, fetchColumn)
1228 {
1229 	zend_long col_n = 0;
1230 
1231 	ZEND_PARSE_PARAMETERS_START(0, 1)
1232 		Z_PARAM_OPTIONAL
1233 		Z_PARAM_LONG(col_n)
1234 	ZEND_PARSE_PARAMETERS_END();
1235 
1236 	PHP_STMT_GET_OBJ;
1237 	PDO_STMT_CLEAR_ERR();
1238 
1239 	if (!do_fetch_common(stmt, PDO_FETCH_ORI_NEXT, 0)) {
1240 		PDO_HANDLE_STMT_ERR();
1241 		RETURN_FALSE;
1242 	}
1243 
1244 	fetch_value(stmt, return_value, col_n, NULL);
1245 }
1246 /* }}} */
1247 
1248 /* {{{ Returns an array of all of the results. */
PHP_METHOD(PDOStatement,fetchAll)1249 PHP_METHOD(PDOStatement, fetchAll)
1250 {
1251 	zend_long how = PDO_FETCH_USE_DEFAULT;
1252 	zval data, *return_all = NULL;
1253 	zval *arg2 = NULL;
1254 	zend_class_entry *old_ce;
1255 	zval old_ctor_args, *ctor_args = NULL;
1256 	bool error = false;
1257 	int flags, old_arg_count;
1258 
1259 	ZEND_PARSE_PARAMETERS_START(0, 3)
1260 		Z_PARAM_OPTIONAL
1261 		Z_PARAM_LONG(how)
1262 		Z_PARAM_ZVAL_OR_NULL(arg2)
1263 		Z_PARAM_ARRAY_OR_NULL(ctor_args)
1264 	ZEND_PARSE_PARAMETERS_END();
1265 
1266 	PHP_STMT_GET_OBJ;
1267 	if (!pdo_stmt_verify_mode(stmt, how, 1, true)) {
1268 		RETURN_THROWS();
1269 	}
1270 
1271 	old_ce = stmt->fetch.cls.ce;
1272 	ZVAL_COPY_VALUE(&old_ctor_args, &stmt->fetch.cls.ctor_args);
1273 	old_arg_count = stmt->fetch.cls.fci.param_count;
1274 
1275 	do_fetch_opt_finish(stmt, 0);
1276 
1277 	/* TODO Would be good to reuse part of pdo_stmt_setup_fetch_mode() in some way */
1278 
1279 	switch (how & ~PDO_FETCH_FLAGS) {
1280 		case PDO_FETCH_CLASS:
1281 			/* Figure out correct class */
1282 			if (arg2) {
1283 				if (Z_TYPE_P(arg2) != IS_STRING) {
1284 					zend_argument_type_error(2, "must be of type string, %s given", zend_zval_type_name(arg2));
1285 					RETURN_THROWS();
1286 				}
1287 				stmt->fetch.cls.ce = zend_fetch_class(Z_STR_P(arg2), ZEND_FETCH_CLASS_AUTO);
1288 				if (!stmt->fetch.cls.ce) {
1289 					zend_argument_type_error(2, "must be a valid class");
1290 					RETURN_THROWS();
1291 				}
1292 			} else {
1293 				stmt->fetch.cls.ce = zend_standard_class_def;
1294 			}
1295 
1296 			if (ctor_args && zend_hash_num_elements(Z_ARRVAL_P(ctor_args)) > 0) {
1297 				ZVAL_COPY_VALUE(&stmt->fetch.cls.ctor_args, ctor_args); /* we're not going to free these */
1298 			} else {
1299 				ZVAL_UNDEF(&stmt->fetch.cls.ctor_args);
1300 			}
1301 
1302 			do_fetch_class_prepare(stmt);
1303 			break;
1304 
1305 		case PDO_FETCH_FUNC: /* Cannot be a default fetch mode */
1306 			if (ZEND_NUM_ARGS() != 2) {
1307 				zend_string *func = get_active_function_or_method_name();
1308 				zend_argument_count_error("%s() expects exactly 2 argument for PDO::FETCH_FUNC, %d given",
1309 					ZSTR_VAL(func), ZEND_NUM_ARGS());
1310 				zend_string_release(func);
1311 				RETURN_THROWS();
1312 			}
1313 			if (arg2 == NULL) {
1314 				/* TODO use "must be of type callable" format? */
1315 				zend_argument_type_error(2, "must be a callable, null given");
1316 				RETURN_THROWS();
1317 			}
1318 			/* TODO Check it is a callable? */
1319 			ZVAL_COPY_VALUE(&stmt->fetch.func.function, arg2);
1320 			if (do_fetch_func_prepare(stmt) == false) {
1321 				RETURN_THROWS();
1322 			}
1323 			break;
1324 
1325 		case PDO_FETCH_COLUMN:
1326 			if (ZEND_NUM_ARGS() > 2) {
1327 				zend_string *func = get_active_function_or_method_name();
1328 				zend_argument_count_error("%s() expects at most 2 argument for the fetch mode provided, %d given",
1329 					ZSTR_VAL(func), ZEND_NUM_ARGS());
1330 				zend_string_release(func);
1331 				RETURN_THROWS();
1332 			}
1333 			/* Is column index passed? */
1334 			if (arg2) {
1335 				// Reuse convert_to_long(arg2); ?
1336 				if (Z_TYPE_P(arg2) != IS_LONG) {
1337 					zend_argument_type_error(2, "must be of type int, %s given", zend_zval_type_name(arg2));
1338 					RETURN_THROWS();
1339 				}
1340 				if (Z_LVAL_P(arg2) < 0) {
1341 					zend_argument_value_error(2, "must be greater than or equal to 0");
1342 					RETURN_THROWS();
1343 				}
1344 				stmt->fetch.column = Z_LVAL_P(arg2);
1345 			} else {
1346 				stmt->fetch.column = how & PDO_FETCH_GROUP ? -1 : 0;
1347 			}
1348 			break;
1349 
1350 		default:
1351 			/* No support for PDO_FETCH_INTO which takes 2 args??? */
1352 			if (ZEND_NUM_ARGS() > 1) {
1353 				zend_string *func = get_active_function_or_method_name();
1354 				zend_argument_count_error("%s() expects exactly 1 argument for the fetch mode provided, %d given",
1355 				ZSTR_VAL(func), ZEND_NUM_ARGS());
1356 				zend_string_release(func);
1357 				RETURN_THROWS();
1358 			}
1359 	}
1360 
1361 	flags = how & PDO_FETCH_FLAGS;
1362 
1363 	if ((how & ~PDO_FETCH_FLAGS) == PDO_FETCH_USE_DEFAULT) {
1364 		flags |= stmt->default_fetch_type & PDO_FETCH_FLAGS;
1365 		how |= stmt->default_fetch_type & ~PDO_FETCH_FLAGS;
1366 	}
1367 
1368 	PDO_STMT_CLEAR_ERR();
1369 	if ((how & PDO_FETCH_GROUP) || how == PDO_FETCH_KEY_PAIR ||
1370 		(how == PDO_FETCH_USE_DEFAULT && stmt->default_fetch_type == PDO_FETCH_KEY_PAIR)
1371 	) {
1372 		array_init(return_value);
1373 		return_all = return_value;
1374 	}
1375 	if (!do_fetch(stmt, &data, how | flags, PDO_FETCH_ORI_NEXT, /* offset */ 0, return_all)) {
1376 		error = true;
1377 	}
1378 
1379 	if (!error) {
1380 		if ((how & PDO_FETCH_GROUP) || how == PDO_FETCH_KEY_PAIR ||
1381 			(how == PDO_FETCH_USE_DEFAULT && stmt->default_fetch_type == PDO_FETCH_KEY_PAIR)
1382 		) {
1383 			while (do_fetch(stmt, &data, how | flags, PDO_FETCH_ORI_NEXT, /* offset */ 0, return_all));
1384 		} else {
1385 			array_init(return_value);
1386 			do {
1387 				zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &data);
1388 			} while (do_fetch(stmt, &data, how | flags, PDO_FETCH_ORI_NEXT, /* offset */ 0, NULL));
1389 		}
1390 	}
1391 
1392 	do_fetch_opt_finish(stmt, 0);
1393 
1394 	/* Restore defaults which were changed by PDO_FETCH_CLASS mode */
1395 	stmt->fetch.cls.ce = old_ce;
1396 	ZVAL_COPY_VALUE(&stmt->fetch.cls.ctor_args, &old_ctor_args);
1397 	stmt->fetch.cls.fci.param_count = old_arg_count;
1398 
1399 	/* on no results, return an empty array */
1400 	if (error) {
1401 		PDO_HANDLE_STMT_ERR();
1402 		if (Z_TYPE_P(return_value) != IS_ARRAY) {
1403 			array_init(return_value);
1404 		}
1405 	}
1406 }
1407 /* }}} */
1408 
register_bound_param(INTERNAL_FUNCTION_PARAMETERS,int is_param)1409 static void register_bound_param(INTERNAL_FUNCTION_PARAMETERS, int is_param) /* {{{ */
1410 {
1411 	struct pdo_bound_param_data param;
1412 	zend_long param_type = PDO_PARAM_STR;
1413 	zval *parameter, *driver_params = NULL;
1414 
1415 	memset(&param, 0, sizeof(param));
1416 
1417 	ZEND_PARSE_PARAMETERS_START(2, 5)
1418 		Z_PARAM_STR_OR_LONG(param.name, param.paramno)
1419 		Z_PARAM_ZVAL(parameter)
1420 		Z_PARAM_OPTIONAL
1421 		Z_PARAM_LONG(param_type)
1422 		Z_PARAM_LONG(param.max_value_len)
1423 		Z_PARAM_ZVAL_OR_NULL(driver_params)
1424 	ZEND_PARSE_PARAMETERS_END();
1425 
1426 	PHP_STMT_GET_OBJ;
1427 
1428 	param.param_type = (int) param_type;
1429 
1430 	if (param.name) {
1431 		if (ZSTR_LEN(param.name) == 0) {
1432 			zend_argument_value_error(1, "cannot be empty");
1433 			RETURN_THROWS();
1434 		}
1435 		param.paramno = -1;
1436 	} else if (param.paramno > 0) {
1437 		--param.paramno; /* make it zero-based internally */
1438 	} else {
1439 		zend_argument_value_error(1, "must be greater than or equal to 1");
1440 		RETURN_THROWS();
1441 	}
1442 
1443 	if (driver_params) {
1444 		ZVAL_COPY(&param.driver_params, driver_params);
1445 	}
1446 
1447 	ZVAL_COPY(&param.parameter, parameter);
1448 	if (!really_register_bound_param(&param, stmt, is_param)) {
1449 		if (!Z_ISUNDEF(param.parameter)) {
1450 			zval_ptr_dtor(&(param.parameter));
1451 		}
1452 
1453 		RETURN_FALSE;
1454 	}
1455 
1456 	RETURN_TRUE;
1457 } /* }}} */
1458 
1459 /* {{{ bind an input parameter to the value of a PHP variable.  $paramno is the 1-based position of the placeholder in the SQL statement (but can be the parameter name for drivers that support named placeholders).  It should be called prior to execute(). */
PHP_METHOD(PDOStatement,bindValue)1460 PHP_METHOD(PDOStatement, bindValue)
1461 {
1462 	struct pdo_bound_param_data param;
1463 	zend_long param_type = PDO_PARAM_STR;
1464 	zval *parameter;
1465 
1466 	memset(&param, 0, sizeof(param));
1467 
1468 	ZEND_PARSE_PARAMETERS_START(2, 3)
1469 		Z_PARAM_STR_OR_LONG(param.name, param.paramno)
1470 		Z_PARAM_ZVAL(parameter)
1471 		Z_PARAM_OPTIONAL
1472 		Z_PARAM_LONG(param_type)
1473 	ZEND_PARSE_PARAMETERS_END();
1474 
1475 	PHP_STMT_GET_OBJ;
1476 	param.param_type = (int) param_type;
1477 
1478 	if (param.name) {
1479 		if (ZSTR_LEN(param.name) == 0) {
1480 			zend_argument_value_error(1, "cannot be empty");
1481 			RETURN_THROWS();
1482 		}
1483 		param.paramno = -1;
1484 	} else if (param.paramno > 0) {
1485 		--param.paramno; /* make it zero-based internally */
1486 	} else {
1487 		zend_argument_value_error(1, "must be greater than or equal to 1");
1488 		RETURN_THROWS();
1489 	}
1490 
1491 	ZVAL_COPY(&param.parameter, parameter);
1492 	if (!really_register_bound_param(&param, stmt, TRUE)) {
1493 		if (!Z_ISUNDEF(param.parameter)) {
1494 			zval_ptr_dtor(&(param.parameter));
1495 			ZVAL_UNDEF(&param.parameter);
1496 		}
1497 		RETURN_FALSE;
1498 	}
1499 	RETURN_TRUE;
1500 }
1501 /* }}} */
1502 
1503 /* {{{ bind a parameter to a PHP variable.  $paramno is the 1-based position of the placeholder in the SQL statement (but can be the parameter name for drivers that support named placeholders).  This isn't supported by all drivers.  It should be called prior to execute(). */
PHP_METHOD(PDOStatement,bindParam)1504 PHP_METHOD(PDOStatement, bindParam)
1505 {
1506 	register_bound_param(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
1507 }
1508 /* }}} */
1509 
1510 /* {{{ bind a column to a PHP variable.  On each row fetch $param will contain the value of the corresponding column.  $column is the 1-based offset of the column, or the column name.  For portability, don't call this before execute(). */
PHP_METHOD(PDOStatement,bindColumn)1511 PHP_METHOD(PDOStatement, bindColumn)
1512 {
1513 	register_bound_param(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
1514 }
1515 /* }}} */
1516 
1517 /* {{{ Returns the number of rows in a result set, or the number of rows affected by the last execute().  It is not always meaningful. */
PHP_METHOD(PDOStatement,rowCount)1518 PHP_METHOD(PDOStatement, rowCount)
1519 {
1520 	ZEND_PARSE_PARAMETERS_NONE();
1521 
1522 	PHP_STMT_GET_OBJ;
1523 	RETURN_LONG(stmt->row_count);
1524 }
1525 /* }}} */
1526 
1527 /* {{{ Fetch the error code associated with the last operation on the statement handle */
PHP_METHOD(PDOStatement,errorCode)1528 PHP_METHOD(PDOStatement, errorCode)
1529 {
1530 	ZEND_PARSE_PARAMETERS_NONE();
1531 
1532 	PHP_STMT_GET_OBJ;
1533 	if (stmt->error_code[0] == '\0') {
1534 		RETURN_NULL();
1535 	}
1536 
1537 	RETURN_STRING(stmt->error_code);
1538 }
1539 /* }}} */
1540 
1541 /* {{{ Fetch extended error information associated with the last operation on the statement handle */
PHP_METHOD(PDOStatement,errorInfo)1542 PHP_METHOD(PDOStatement, errorInfo)
1543 {
1544 	int error_count;
1545 	int error_count_diff = 0;
1546 	int error_expected_count = 3;
1547 
1548 	ZEND_PARSE_PARAMETERS_NONE();
1549 
1550 	PHP_STMT_GET_OBJ;
1551 	array_init(return_value);
1552 	add_next_index_string(return_value, stmt->error_code);
1553 
1554 	if (strncmp(stmt->error_code, PDO_ERR_NONE, sizeof(PDO_ERR_NONE))) {
1555 		if (stmt->dbh->methods->fetch_err) {
1556 			stmt->dbh->methods->fetch_err(stmt->dbh, stmt, return_value);
1557 		}
1558 	}
1559 
1560 	error_count = zend_hash_num_elements(Z_ARRVAL_P(return_value));
1561 
1562 	if (error_expected_count > error_count) {
1563 		int current_index;
1564 
1565 		error_count_diff = error_expected_count - error_count;
1566 		for (current_index = 0; current_index < error_count_diff; current_index++) {
1567 			add_next_index_null(return_value);
1568 		}
1569 	}
1570 }
1571 /* }}} */
1572 
1573 /* {{{ Set an attribute */
PHP_METHOD(PDOStatement,setAttribute)1574 PHP_METHOD(PDOStatement, setAttribute)
1575 {
1576 	zend_long attr;
1577 	zval *value = NULL;
1578 
1579 	ZEND_PARSE_PARAMETERS_START(2, 2)
1580 		Z_PARAM_LONG(attr)
1581 		Z_PARAM_ZVAL_OR_NULL(value)
1582 	ZEND_PARSE_PARAMETERS_END();
1583 
1584 	PHP_STMT_GET_OBJ;
1585 
1586 	/* Driver hasn't registered a function for setting attributes */
1587 	if (!stmt->methods->set_attribute) {
1588 		pdo_raise_impl_error(stmt->dbh, stmt, "IM001", "This driver doesn't support setting attributes");
1589 		RETURN_FALSE;
1590 	}
1591 
1592 	PDO_STMT_CLEAR_ERR();
1593 	if (stmt->methods->set_attribute(stmt, attr, value)) {
1594 		RETURN_TRUE;
1595 	}
1596 
1597 	/* Error while setting attribute */
1598 	PDO_HANDLE_STMT_ERR();
1599 	RETURN_FALSE;
1600 }
1601 /* }}} */
1602 
1603 /* {{{ Get an attribute */
1604 
generic_stmt_attr_get(pdo_stmt_t * stmt,zval * return_value,zend_long attr)1605 static bool generic_stmt_attr_get(pdo_stmt_t *stmt, zval *return_value, zend_long attr)
1606 {
1607 	switch (attr) {
1608 		case PDO_ATTR_EMULATE_PREPARES:
1609 			RETVAL_BOOL(stmt->supports_placeholders == PDO_PLACEHOLDER_NONE);
1610 			return 1;
1611 	}
1612 	return 0;
1613 }
1614 
PHP_METHOD(PDOStatement,getAttribute)1615 PHP_METHOD(PDOStatement, getAttribute)
1616 {
1617 	zend_long attr;
1618 
1619 	ZEND_PARSE_PARAMETERS_START(1, 1)
1620 		Z_PARAM_LONG(attr)
1621 	ZEND_PARSE_PARAMETERS_END();
1622 
1623 	PHP_STMT_GET_OBJ;
1624 	if (!stmt->methods->get_attribute) {
1625 		if (!generic_stmt_attr_get(stmt, return_value, attr)) {
1626 			pdo_raise_impl_error(stmt->dbh, stmt, "IM001",
1627 				"This driver doesn't support getting attributes");
1628 			RETURN_FALSE;
1629 		}
1630 		return;
1631 	}
1632 
1633 	PDO_STMT_CLEAR_ERR();
1634 	switch (stmt->methods->get_attribute(stmt, attr, return_value)) {
1635 		case -1:
1636 			PDO_HANDLE_STMT_ERR();
1637 			RETURN_FALSE;
1638 
1639 		case 0:
1640 			if (!generic_stmt_attr_get(stmt, return_value, attr)) {
1641 				/* XXX: should do something better here */
1642 				pdo_raise_impl_error(stmt->dbh, stmt, "IM001",
1643 					"driver doesn't support getting that attribute");
1644 				RETURN_FALSE;
1645 			}
1646 			return;
1647 
1648 		default:
1649 			return;
1650 	}
1651 }
1652 /* }}} */
1653 
1654 /* {{{ Returns the number of columns in the result set */
PHP_METHOD(PDOStatement,columnCount)1655 PHP_METHOD(PDOStatement, columnCount)
1656 {
1657 	ZEND_PARSE_PARAMETERS_NONE();
1658 
1659 	PHP_STMT_GET_OBJ;
1660 	RETURN_LONG(stmt->column_count);
1661 }
1662 /* }}} */
1663 
1664 /* {{{ Returns meta data for a numbered column */
PHP_METHOD(PDOStatement,getColumnMeta)1665 PHP_METHOD(PDOStatement, getColumnMeta)
1666 {
1667 	zend_long colno;
1668 	struct pdo_column_data *col;
1669 
1670 	ZEND_PARSE_PARAMETERS_START(1, 1)
1671 		Z_PARAM_LONG(colno)
1672 	ZEND_PARSE_PARAMETERS_END();
1673 
1674 	PHP_STMT_GET_OBJ;
1675 	if (colno < 0) {
1676 		zend_argument_value_error(1, "must be greater than or equal to 0");
1677 		RETURN_THROWS();
1678 	}
1679 
1680 	if (!stmt->methods->get_column_meta) {
1681 		pdo_raise_impl_error(stmt->dbh, stmt, "IM001", "driver doesn't support meta data");
1682 		RETURN_FALSE;
1683 	}
1684 
1685 	PDO_STMT_CLEAR_ERR();
1686 	if (FAILURE == stmt->methods->get_column_meta(stmt, colno, return_value)) {
1687 		PDO_HANDLE_STMT_ERR();
1688 		RETURN_FALSE;
1689 	}
1690 
1691 	/* add stock items */
1692 	col = &stmt->columns[colno];
1693 	add_assoc_str(return_value, "name", zend_string_copy(col->name));
1694 	add_assoc_long(return_value, "len", col->maxlen); /* FIXME: unsigned ? */
1695 	add_assoc_long(return_value, "precision", col->precision);
1696 }
1697 /* }}} */
1698 
1699 /* {{{ Changes the default fetch mode for subsequent fetches (params have different meaning for different fetch modes) */
1700 
pdo_stmt_setup_fetch_mode(pdo_stmt_t * stmt,zend_long mode,uint32_t mode_arg_num,zval * args,uint32_t variadic_num_args)1701 bool pdo_stmt_setup_fetch_mode(pdo_stmt_t *stmt, zend_long mode, uint32_t mode_arg_num,
1702 	zval *args, uint32_t variadic_num_args)
1703 {
1704 	int flags = 0;
1705 	uint32_t arg1_arg_num = mode_arg_num + 1;
1706 	uint32_t constructor_arg_num = mode_arg_num + 2;
1707 	uint32_t total_num_args = mode_arg_num + variadic_num_args;
1708 
1709 	switch (stmt->default_fetch_type) {
1710 		case PDO_FETCH_INTO:
1711 			if (!Z_ISUNDEF(stmt->fetch.into)) {
1712 				zval_ptr_dtor(&stmt->fetch.into);
1713 				ZVAL_UNDEF(&stmt->fetch.into);
1714 			}
1715 			break;
1716 		default:
1717 			;
1718 	}
1719 
1720 	stmt->default_fetch_type = PDO_FETCH_BOTH;
1721 
1722 	flags = mode & PDO_FETCH_FLAGS;
1723 
1724 	if (!pdo_stmt_verify_mode(stmt, mode, mode_arg_num, false)) {
1725 		return false;
1726 	}
1727 
1728 	switch (mode & ~PDO_FETCH_FLAGS) {
1729 		case PDO_FETCH_USE_DEFAULT:
1730 		case PDO_FETCH_LAZY:
1731 		case PDO_FETCH_ASSOC:
1732 		case PDO_FETCH_NUM:
1733 		case PDO_FETCH_BOTH:
1734 		case PDO_FETCH_OBJ:
1735 		case PDO_FETCH_BOUND:
1736 		case PDO_FETCH_NAMED:
1737 		case PDO_FETCH_KEY_PAIR:
1738 			if (variadic_num_args != 0) {
1739 				zend_string *func = get_active_function_or_method_name();
1740 				zend_argument_count_error("%s() expects exactly %d arguments for the fetch mode provided, %d given",
1741 					ZSTR_VAL(func), mode_arg_num, total_num_args);
1742 				zend_string_release(func);
1743 				return false;
1744 			}
1745 			break;
1746 
1747 		case PDO_FETCH_COLUMN:
1748 			if (variadic_num_args != 1) {
1749 				zend_string *func = get_active_function_or_method_name();
1750 				zend_argument_count_error("%s() expects exactly %d arguments for the fetch mode provided, %d given",
1751 					ZSTR_VAL(func), arg1_arg_num, total_num_args);
1752 				zend_string_release(func);
1753 				return false;
1754 			}
1755 			if (Z_TYPE(args[0]) != IS_LONG) {
1756 				zend_argument_type_error(arg1_arg_num, "must be of type int, %s given", zend_zval_type_name(&args[0]));
1757 				return false;
1758 			}
1759 			if (Z_LVAL(args[0]) < 0) {
1760 				zend_argument_value_error(arg1_arg_num, "must be greater than or equal to 0");
1761 				return false;
1762 			}
1763 			stmt->fetch.column = Z_LVAL(args[0]);
1764 			break;
1765 
1766 		case PDO_FETCH_CLASS: {
1767 			HashTable *constructor_args = NULL;
1768 			/* Undef constructor arguments */
1769 			ZVAL_UNDEF(&stmt->fetch.cls.ctor_args);
1770 			/* Gets its class name from 1st column */
1771 			if ((flags & PDO_FETCH_CLASSTYPE) == PDO_FETCH_CLASSTYPE) {
1772 				if (variadic_num_args != 0) {
1773 					zend_string *func = get_active_function_or_method_name();
1774 					zend_argument_count_error("%s() expects exactly %d arguments for the fetch mode provided, %d given",
1775 						ZSTR_VAL(func), mode_arg_num, total_num_args);
1776 					zend_string_release(func);
1777 					return false;
1778 				}
1779 				stmt->fetch.cls.ce = NULL;
1780 			} else {
1781 				zend_class_entry *cep;
1782 				if (variadic_num_args == 0) {
1783 					zend_string *func = get_active_function_or_method_name();
1784 					zend_argument_count_error("%s() expects at least %d arguments for the fetch mode provided, %d given",
1785 						ZSTR_VAL(func), arg1_arg_num, total_num_args);
1786 					zend_string_release(func);
1787 					return false;
1788 				}
1789 				/* constructor_arguments can be null/not passed */
1790 				if (variadic_num_args > 2) {
1791 					zend_string *func = get_active_function_or_method_name();
1792 					zend_argument_count_error("%s() expects at most %d arguments for the fetch mode provided, %d given",
1793 						ZSTR_VAL(func), constructor_arg_num, total_num_args);
1794 					zend_string_release(func);
1795 					return false;
1796 				}
1797 				if (Z_TYPE(args[0]) != IS_STRING) {
1798 					zend_argument_type_error(arg1_arg_num, "must be of type string, %s given", zend_zval_type_name(&args[0]));
1799 					return false;
1800 				}
1801 				cep = zend_lookup_class(Z_STR(args[0]));
1802 				if (!cep) {
1803 					zend_argument_type_error(arg1_arg_num, "must be a valid class");
1804 					return false;
1805 				}
1806 				/* Verify constructor_args (args[1]) is ?array */
1807 				/* TODO: Improve logic? */
1808 				if (variadic_num_args == 2) {
1809 					if (Z_TYPE(args[1]) != IS_NULL && Z_TYPE(args[1]) != IS_ARRAY) {
1810 						zend_argument_type_error(constructor_arg_num, "must be of type ?array, %s given",
1811 							zend_zval_type_name(&args[1]));
1812 						return false;
1813 					}
1814 					if (Z_TYPE(args[1]) == IS_ARRAY && zend_hash_num_elements(Z_ARRVAL(args[1]))) {
1815 						constructor_args = Z_ARRVAL(args[1]);
1816 					}
1817 				}
1818 				stmt->fetch.cls.ce = cep;
1819 
1820 				/* If constructor arguments are present and not empty */
1821 				if (constructor_args) {
1822 					ZVAL_ARR(&stmt->fetch.cls.ctor_args, zend_array_dup(constructor_args));
1823 				}
1824 			}
1825 
1826 			do_fetch_class_prepare(stmt);
1827 			break;
1828 		}
1829 		case PDO_FETCH_INTO:
1830 			if (total_num_args != arg1_arg_num) {
1831 				zend_string *func = get_active_function_or_method_name();
1832 				zend_argument_count_error("%s() expects exactly %d arguments for the fetch mode provided, %d given",
1833 					ZSTR_VAL(func), arg1_arg_num, total_num_args);
1834 				zend_string_release(func);
1835 				return false;
1836 			}
1837 			if (Z_TYPE(args[0]) != IS_OBJECT) {
1838 				zend_argument_type_error(arg1_arg_num, "must be of type object, %s given", zend_zval_type_name(&args[0]));
1839 				return false;
1840 			}
1841 
1842 			ZVAL_COPY(&stmt->fetch.into, &args[0]);
1843 			break;
1844 		default:
1845 			zend_argument_value_error(mode_arg_num, "must be one of the PDO::FETCH_* constants");
1846 			return false;
1847 	}
1848 
1849 	stmt->default_fetch_type = mode;
1850 
1851 	return true;
1852 }
1853 
PHP_METHOD(PDOStatement,setFetchMode)1854 PHP_METHOD(PDOStatement, setFetchMode)
1855 {
1856 	zend_long fetch_mode;
1857 	zval *args = NULL;
1858 	uint32_t num_args = 0;
1859 
1860 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "l*", &fetch_mode, &args, &num_args) == FAILURE) {
1861 		RETURN_THROWS();
1862 	}
1863 
1864 	PHP_STMT_GET_OBJ;
1865 
1866 	do_fetch_opt_finish(stmt, 1);
1867 
1868 	if (!pdo_stmt_setup_fetch_mode(stmt, fetch_mode, 1, args, num_args)) {
1869 		RETURN_THROWS();
1870 	}
1871 
1872 	// TODO Void return?
1873 	RETURN_TRUE;
1874 }
1875 /* }}} */
1876 
1877 /* {{{ Advances to the next rowset in a multi-rowset statement handle. Returns true if it succeeded, false otherwise */
1878 
pdo_stmt_do_next_rowset(pdo_stmt_t * stmt)1879 static bool pdo_stmt_do_next_rowset(pdo_stmt_t *stmt)
1880 {
1881 	pdo_stmt_reset_columns(stmt);
1882 
1883 	if (!stmt->methods->next_rowset(stmt)) {
1884 		/* Set the executed flag to 0 to reallocate columns on next execute */
1885 		stmt->executed = 0;
1886 		return 0;
1887 	}
1888 
1889 	pdo_stmt_describe_columns(stmt);
1890 
1891 	return 1;
1892 }
1893 
PHP_METHOD(PDOStatement,nextRowset)1894 PHP_METHOD(PDOStatement, nextRowset)
1895 {
1896 	ZEND_PARSE_PARAMETERS_NONE();
1897 
1898 	PHP_STMT_GET_OBJ;
1899 	if (!stmt->methods->next_rowset) {
1900 		pdo_raise_impl_error(stmt->dbh, stmt, "IM001", "driver does not support multiple rowsets");
1901 		RETURN_FALSE;
1902 	}
1903 
1904 	PDO_STMT_CLEAR_ERR();
1905 
1906 	if (!pdo_stmt_do_next_rowset(stmt)) {
1907 		PDO_HANDLE_STMT_ERR();
1908 		RETURN_FALSE;
1909 	}
1910 
1911 	RETURN_TRUE;
1912 }
1913 /* }}} */
1914 
1915 /* {{{ Closes the cursor, leaving the statement ready for re-execution. */
PHP_METHOD(PDOStatement,closeCursor)1916 PHP_METHOD(PDOStatement, closeCursor)
1917 {
1918 	ZEND_PARSE_PARAMETERS_NONE();
1919 
1920 	PHP_STMT_GET_OBJ;
1921 	if (!stmt->methods->cursor_closer) {
1922 		/* emulate it by fetching and discarding rows */
1923 		do {
1924 			while (stmt->methods->fetcher(stmt, PDO_FETCH_ORI_NEXT, 0))
1925 				;
1926 			if (!stmt->methods->next_rowset) {
1927 				break;
1928 			}
1929 
1930 			if (!pdo_stmt_do_next_rowset(stmt)) {
1931 				break;
1932 			}
1933 
1934 		} while (1);
1935 		stmt->executed = 0;
1936 		RETURN_TRUE;
1937 	}
1938 
1939 	PDO_STMT_CLEAR_ERR();
1940 
1941 	if (!stmt->methods->cursor_closer(stmt)) {
1942 		PDO_HANDLE_STMT_ERR();
1943 		RETURN_FALSE;
1944 	}
1945 	stmt->executed = 0;
1946 	RETURN_TRUE;
1947 }
1948 /* }}} */
1949 
1950 /* {{{ A utility for internals hackers to debug parameter internals */
PHP_METHOD(PDOStatement,debugDumpParams)1951 PHP_METHOD(PDOStatement, debugDumpParams)
1952 {
1953 	ZEND_PARSE_PARAMETERS_NONE();
1954 
1955 	php_stream *out = php_stream_open_wrapper("php://output", "w", 0, NULL);
1956 	struct pdo_bound_param_data *param;
1957 
1958 	ZEND_PARSE_PARAMETERS_NONE();
1959 
1960 	PHP_STMT_GET_OBJ;
1961 
1962 	if (out == NULL) {
1963 		RETURN_FALSE;
1964 	}
1965 
1966 	/* break into multiple operations so query string won't be truncated at FORMAT_CONV_MAX_PRECISION */
1967 	php_stream_printf(out, "SQL: [%zd] ", ZSTR_LEN(stmt->query_string));
1968 	php_stream_write(out, ZSTR_VAL(stmt->query_string), ZSTR_LEN(stmt->query_string));
1969 	php_stream_write(out, "\n", 1);
1970 
1971 	/* show parsed SQL if emulated prepares enabled */
1972 	/* pointers will be equal if PDO::query() was invoked */
1973 	if (stmt->active_query_string != NULL && stmt->active_query_string != stmt->query_string) {
1974 		/* break into multiple operations so query string won't be truncated at FORMAT_CONV_MAX_PRECISION */
1975 		php_stream_printf(out, "Sent SQL: [%zd] ", ZSTR_LEN(stmt->active_query_string));
1976 		php_stream_write(out, ZSTR_VAL(stmt->active_query_string), ZSTR_LEN(stmt->active_query_string));
1977 		php_stream_write(out, "\n", 1);
1978 	}
1979 
1980 	php_stream_printf(out, "Params:  %d\n",
1981 		stmt->bound_params ? zend_hash_num_elements(stmt->bound_params) : 0);
1982 
1983 	if (stmt->bound_params) {
1984 		zend_ulong num;
1985 		zend_string *key = NULL;
1986 		ZEND_HASH_FOREACH_KEY_PTR(stmt->bound_params, num, key, param) {
1987 			if (key) {
1988 				php_stream_printf(out, "Key: Name: [%zd] %.*s\n",
1989 					ZSTR_LEN(key), (int) ZSTR_LEN(key), ZSTR_VAL(key));
1990 			} else {
1991 				php_stream_printf(out, "Key: Position #" ZEND_ULONG_FMT ":\n", num);
1992 			}
1993 
1994 			php_stream_printf(out,
1995 				"paramno=" ZEND_LONG_FMT "\n"
1996 				"name=[%zd] \"%.*s\"\n"
1997 				"is_param=%d\n"
1998 				"param_type=%d\n",
1999 				param->paramno, param->name ? ZSTR_LEN(param->name) : 0, param->name ? (int) ZSTR_LEN(param->name) : 0,
2000 				param->name ? ZSTR_VAL(param->name) : "",
2001 				param->is_param,
2002 				param->param_type);
2003 
2004 		} ZEND_HASH_FOREACH_END();
2005 	}
2006 
2007 	php_stream_close(out);
2008 }
2009 /* }}} */
2010 
PHP_METHOD(PDOStatement,getIterator)2011 PHP_METHOD(PDOStatement, getIterator)
2012 {
2013 	if (zend_parse_parameters_none() == FAILURE) {
2014 		return;
2015 	}
2016 
2017 	zend_create_internal_iterator_zval(return_value, ZEND_THIS);
2018 }
2019 
2020 /* {{{ overloaded handlers for PDOStatement class */
dbstmt_prop_write(zend_object * object,zend_string * name,zval * value,void ** cache_slot)2021 static zval *dbstmt_prop_write(zend_object *object, zend_string *name, zval *value, void **cache_slot)
2022 {
2023 	if (zend_string_equals_literal(name, "queryString")) {
2024 		zval *query_string = OBJ_PROP_NUM(object, 0);
2025 		if (!Z_ISUNDEF_P(query_string)) {
2026 			zend_throw_error(NULL, "Property queryString is read only");
2027 			return value;
2028 		}
2029 	}
2030 	return zend_std_write_property(object, name, value, cache_slot);
2031 }
2032 
dbstmt_prop_delete(zend_object * object,zend_string * name,void ** cache_slot)2033 static void dbstmt_prop_delete(zend_object *object, zend_string *name, void **cache_slot)
2034 {
2035 	if (zend_string_equals_literal(name, "queryString")) {
2036 		zend_throw_error(NULL, "Property queryString is read only");
2037 	} else {
2038 		zend_std_unset_property(object, name, cache_slot);
2039 	}
2040 }
2041 
dbstmt_method_get(zend_object ** object_pp,zend_string * method_name,const zval * key)2042 static zend_function *dbstmt_method_get(zend_object **object_pp, zend_string *method_name, const zval *key)
2043 {
2044 	zend_function *fbc = NULL;
2045 	zend_string *lc_method_name;
2046 	zend_object *object = *object_pp;
2047 
2048 	lc_method_name = zend_string_tolower(method_name);
2049 
2050 	if ((fbc = zend_hash_find_ptr(&object->ce->function_table, lc_method_name)) == NULL) {
2051 		pdo_stmt_t *stmt = php_pdo_stmt_fetch_object(object);
2052 		/* instance not created by PDO object */
2053 		if (!stmt->dbh) {
2054 			goto out;
2055 		}
2056 		/* not a pre-defined method, nor a user-defined method; check
2057 		 * the driver specific methods */
2058 		if (!stmt->dbh->cls_methods[PDO_DBH_DRIVER_METHOD_KIND_STMT]) {
2059 			if (!pdo_hash_methods(Z_PDO_OBJECT_P(&stmt->database_object_handle),
2060 				PDO_DBH_DRIVER_METHOD_KIND_STMT)
2061 				|| !stmt->dbh->cls_methods[PDO_DBH_DRIVER_METHOD_KIND_STMT]) {
2062 				goto out;
2063 			}
2064 		}
2065 
2066 		if ((fbc = zend_hash_find_ptr(stmt->dbh->cls_methods[PDO_DBH_DRIVER_METHOD_KIND_STMT], lc_method_name)) == NULL) {
2067 			goto out;
2068 		}
2069 		/* got it */
2070 	}
2071 
2072 out:
2073 	zend_string_release_ex(lc_method_name, 0);
2074 	if (!fbc) {
2075 		fbc = zend_std_get_method(object_pp, method_name, key);
2076 	}
2077 	return fbc;
2078 }
2079 
dbstmt_get_gc(zend_object * object,zval ** gc_data,int * gc_count)2080 static HashTable *dbstmt_get_gc(zend_object *object, zval **gc_data, int *gc_count)
2081 {
2082 	pdo_stmt_t *stmt = php_pdo_stmt_fetch_object(object);
2083 	*gc_data = &stmt->fetch.into;
2084 	*gc_count = 1;
2085 
2086 	/**
2087 	 * If there are no dynamic properties and the default property is 1 (that is, there is only one property
2088 	 * of string that does not participate in GC), there is no need to call zend_std_get_properties().
2089 	 */
2090 	if (object->properties == NULL && object->ce->default_properties_count <= 1) {
2091 		return NULL;
2092 	} else {
2093 		return zend_std_get_properties(object);
2094 	}
2095 }
2096 
2097 zend_object_handlers pdo_dbstmt_object_handlers;
2098 zend_object_handlers pdo_row_object_handlers;
2099 
php_pdo_free_statement(pdo_stmt_t * stmt)2100 PDO_API void php_pdo_free_statement(pdo_stmt_t *stmt)
2101 {
2102 	if (stmt->bound_params) {
2103 		zend_hash_destroy(stmt->bound_params);
2104 		FREE_HASHTABLE(stmt->bound_params);
2105 		stmt->bound_params = NULL;
2106 	}
2107 	if (stmt->bound_param_map) {
2108 		zend_hash_destroy(stmt->bound_param_map);
2109 		FREE_HASHTABLE(stmt->bound_param_map);
2110 		stmt->bound_param_map = NULL;
2111 	}
2112 	if (stmt->bound_columns) {
2113 		zend_hash_destroy(stmt->bound_columns);
2114 		FREE_HASHTABLE(stmt->bound_columns);
2115 		stmt->bound_columns = NULL;
2116 	}
2117 
2118 	if (stmt->methods && stmt->methods->dtor) {
2119 		stmt->methods->dtor(stmt);
2120 	}
2121 	if (stmt->active_query_string) {
2122 		zend_string_release(stmt->active_query_string);
2123 	}
2124 	if (stmt->query_string) {
2125 		zend_string_release(stmt->query_string);
2126 	}
2127 
2128 	pdo_stmt_reset_columns(stmt);
2129 
2130 	if (!Z_ISUNDEF(stmt->fetch.into) && stmt->default_fetch_type == PDO_FETCH_INTO) {
2131 		zval_ptr_dtor(&stmt->fetch.into);
2132 		ZVAL_UNDEF(&stmt->fetch.into);
2133 	}
2134 
2135 	do_fetch_opt_finish(stmt, 1);
2136 
2137 	if (!Z_ISUNDEF(stmt->database_object_handle)) {
2138 		zval_ptr_dtor(&stmt->database_object_handle);
2139 	}
2140 	zend_object_std_dtor(&stmt->std);
2141 }
2142 
pdo_dbstmt_free_storage(zend_object * std)2143 void pdo_dbstmt_free_storage(zend_object *std)
2144 {
2145 	pdo_stmt_t *stmt = php_pdo_stmt_fetch_object(std);
2146 	php_pdo_free_statement(stmt);
2147 }
2148 
pdo_dbstmt_new(zend_class_entry * ce)2149 zend_object *pdo_dbstmt_new(zend_class_entry *ce)
2150 {
2151 	pdo_stmt_t *stmt;
2152 
2153 	stmt = zend_object_alloc(sizeof(pdo_stmt_t), ce);
2154 	zend_object_std_init(&stmt->std, ce);
2155 	object_properties_init(&stmt->std, ce);
2156 
2157 	stmt->std.handlers = &pdo_dbstmt_object_handlers;
2158 
2159 	return &stmt->std;
2160 }
2161 /* }}} */
2162 
2163 /* {{{ statement iterator */
2164 
2165 struct php_pdo_iterator {
2166 	zend_object_iterator iter;
2167 	zend_ulong key;
2168 	zval fetch_ahead;
2169 };
2170 
pdo_stmt_iter_dtor(zend_object_iterator * iter)2171 static void pdo_stmt_iter_dtor(zend_object_iterator *iter)
2172 {
2173 	struct php_pdo_iterator *I = (struct php_pdo_iterator*)iter;
2174 
2175 	zval_ptr_dtor(&I->iter.data);
2176 
2177 	if (!Z_ISUNDEF(I->fetch_ahead)) {
2178 		zval_ptr_dtor(&I->fetch_ahead);
2179 	}
2180 }
2181 
pdo_stmt_iter_valid(zend_object_iterator * iter)2182 static int pdo_stmt_iter_valid(zend_object_iterator *iter)
2183 {
2184 	struct php_pdo_iterator *I = (struct php_pdo_iterator*)iter;
2185 
2186 	return Z_ISUNDEF(I->fetch_ahead) ? FAILURE : SUCCESS;
2187 }
2188 
pdo_stmt_iter_get_data(zend_object_iterator * iter)2189 static zval *pdo_stmt_iter_get_data(zend_object_iterator *iter)
2190 {
2191 	struct php_pdo_iterator *I = (struct php_pdo_iterator*)iter;
2192 
2193 	/* sanity */
2194 	if (Z_ISUNDEF(I->fetch_ahead)) {
2195 		return NULL;
2196 	}
2197 
2198 	return &I->fetch_ahead;
2199 }
2200 
pdo_stmt_iter_get_key(zend_object_iterator * iter,zval * key)2201 static void pdo_stmt_iter_get_key(zend_object_iterator *iter, zval *key)
2202 {
2203 	struct php_pdo_iterator *I = (struct php_pdo_iterator*)iter;
2204 
2205 	if (I->key == (zend_ulong)-1) {
2206 		ZVAL_NULL(key);
2207 	} else {
2208 		ZVAL_LONG(key, I->key);
2209 	}
2210 }
2211 
pdo_stmt_iter_move_forwards(zend_object_iterator * iter)2212 static void pdo_stmt_iter_move_forwards(zend_object_iterator *iter)
2213 {
2214 	struct php_pdo_iterator *I = (struct php_pdo_iterator*)iter;
2215 	pdo_stmt_t *stmt = Z_PDO_STMT_P(&I->iter.data); /* for PDO_HANDLE_STMT_ERR() */
2216 
2217 	if (!Z_ISUNDEF(I->fetch_ahead)) {
2218 		zval_ptr_dtor(&I->fetch_ahead);
2219 	}
2220 
2221 	if (!do_fetch(stmt, &I->fetch_ahead, PDO_FETCH_USE_DEFAULT,
2222 			PDO_FETCH_ORI_NEXT, /* offset */ 0, NULL)) {
2223 
2224 		PDO_HANDLE_STMT_ERR();
2225 		I->key = (zend_ulong)-1;
2226 		ZVAL_UNDEF(&I->fetch_ahead);
2227 
2228 		return;
2229 	}
2230 
2231 	I->key++;
2232 }
2233 
2234 static const zend_object_iterator_funcs pdo_stmt_iter_funcs = {
2235 	pdo_stmt_iter_dtor,
2236 	pdo_stmt_iter_valid,
2237 	pdo_stmt_iter_get_data,
2238 	pdo_stmt_iter_get_key,
2239 	pdo_stmt_iter_move_forwards,
2240 	NULL,
2241 	NULL,
2242 	NULL, /* get_gc */
2243 };
2244 
pdo_stmt_iter_get(zend_class_entry * ce,zval * object,int by_ref)2245 zend_object_iterator *pdo_stmt_iter_get(zend_class_entry *ce, zval *object, int by_ref)
2246 {
2247 	if (by_ref) {
2248 		zend_throw_error(NULL, "An iterator cannot be used with foreach by reference");
2249 		return NULL;
2250 	}
2251 
2252 	pdo_stmt_t *stmt = Z_PDO_STMT_P(object);
2253 	if (!stmt->dbh) {
2254 		zend_throw_error(NULL, "PDO object is uninitialized");
2255 		return NULL;
2256 	}
2257 
2258 	struct php_pdo_iterator *I = ecalloc(1, sizeof(struct php_pdo_iterator));
2259 	zend_iterator_init(&I->iter);
2260 	I->iter.funcs = &pdo_stmt_iter_funcs;
2261 	Z_ADDREF_P(object);
2262 	ZVAL_OBJ(&I->iter.data, Z_OBJ_P(object));
2263 
2264 	if (!do_fetch(stmt, &I->fetch_ahead, PDO_FETCH_USE_DEFAULT,
2265 			PDO_FETCH_ORI_NEXT, /* offset */ 0, NULL)) {
2266 		PDO_HANDLE_STMT_ERR();
2267 		I->key = (zend_ulong)-1;
2268 		ZVAL_UNDEF(&I->fetch_ahead);
2269 	}
2270 
2271 	return &I->iter;
2272 }
2273 
2274 /* }}} */
2275 
2276 /* {{{ overloaded handlers for PDORow class (used by PDO_FETCH_LAZY) */
row_read_column_name(pdo_stmt_t * stmt,zend_string * name,zval * rv)2277 static zval *row_read_column_name(pdo_stmt_t *stmt, zend_string *name, zval *rv)
2278 {
2279 	/* TODO: replace this with a hash of available column names to column numbers */
2280 	for (int colno = 0; colno < stmt->column_count; colno++) {
2281 		if (zend_string_equals(stmt->columns[colno].name, name)) {
2282 			fetch_value(stmt, rv, colno, NULL);
2283 			return rv;
2284 		}
2285 	}
2286 	return NULL;
2287 }
2288 
row_read_column_number(pdo_stmt_t * stmt,zend_long column,zval * rv)2289 static zval *row_read_column_number(pdo_stmt_t *stmt, zend_long column, zval *rv)
2290 {
2291 	if (column >= 0 && column < stmt->column_count) {
2292 		fetch_value(stmt, rv, column, NULL);
2293 		return rv;
2294 	}
2295 	return NULL;
2296 }
2297 
row_prop_read(zend_object * object,zend_string * name,int type,void ** cache_slot,zval * rv)2298 static zval *row_prop_read(zend_object *object, zend_string *name, int type, void **cache_slot, zval *rv)
2299 {
2300 	pdo_row_t *row = (pdo_row_t *)object;
2301 	pdo_stmt_t *stmt = row->stmt;
2302 	zend_long lval;
2303 	zval *retval;
2304 	ZEND_ASSERT(stmt);
2305 
2306 	ZVAL_NULL(rv);
2307 	if (zend_string_equals_literal(name, "queryString")) {
2308 		return zend_std_read_property(&stmt->std, name, type, cache_slot, rv);
2309 	} else if (is_numeric_str_function(name, &lval, /* dval */ NULL) == IS_LONG) {
2310 		retval = row_read_column_number(stmt, lval, rv);
2311 	} else {
2312 		retval = row_read_column_name(stmt, name, rv);
2313 	}
2314 	if (UNEXPECTED(!retval)) {
2315 		// TODO throw an error on master
2316 		//if (type != BP_VAR_IS) {
2317 		//	if (is_numeric) {
2318 		//		zend_value_error("Invalid column index");
2319 		//	} else {
2320 		//		zend_throw_error(NULL, "No column named \"%s\" exists", ZSTR_VAL(name));
2321 		//	}
2322 		//}
2323 		//return &EG(uninitialized_zval);
2324 		ZVAL_NULL(rv);
2325 		return rv;
2326 	}
2327 	return retval;
2328 }
2329 
row_dim_read(zend_object * object,zval * offset,int type,zval * rv)2330 static zval *row_dim_read(zend_object *object, zval *offset, int type, zval *rv)
2331 {
2332 	if (UNEXPECTED(!offset)) {
2333 		zend_throw_error(NULL, "Cannot append to PDORow offset");
2334 		return NULL;
2335 	}
2336 	if (Z_TYPE_P(offset) == IS_LONG) {
2337 		pdo_row_t *row = (pdo_row_t *)object;
2338 		pdo_stmt_t *stmt = row->stmt;
2339 		ZEND_ASSERT(stmt);
2340 
2341 		ZVAL_NULL(rv);
2342 		if (Z_LVAL_P(offset) >= 0 && Z_LVAL_P(offset) < stmt->column_count) {
2343 			fetch_value(stmt, rv, Z_LVAL_P(offset), NULL);
2344 		}
2345 		return rv;
2346 	} else {
2347 		zend_string *member = zval_try_get_string(offset);
2348 		if (!member) {
2349 			return NULL;
2350 		}
2351 		zval *result = row_prop_read(object, member, type, NULL, rv);
2352 		zend_string_release_ex(member, false);
2353 		return result;
2354 	}
2355 }
2356 
row_prop_write(zend_object * object,zend_string * name,zval * value,void ** cache_slot)2357 static zval *row_prop_write(zend_object *object, zend_string *name, zval *value, void **cache_slot)
2358 {
2359 	zend_throw_error(NULL, "Cannot write to PDORow property");
2360 	return value;
2361 }
2362 
row_dim_write(zend_object * object,zval * member,zval * value)2363 static void row_dim_write(zend_object *object, zval *member, zval *value)
2364 {
2365 	if (!member) {
2366 		zend_throw_error(NULL, "Cannot append to PDORow offset");
2367 	} else {
2368 		zend_throw_error(NULL, "Cannot write to PDORow offset");
2369 	}
2370 }
2371 
row_prop_exists(zend_object * object,zend_string * name,int check_empty,void ** cache_slot)2372 static int row_prop_exists(zend_object *object, zend_string *name, int check_empty, void **cache_slot)
2373 {
2374 	pdo_row_t *row = (pdo_row_t *)object;
2375 	pdo_stmt_t *stmt = row->stmt;
2376 	zend_long lval;
2377 	zval tmp_val;
2378 	zval *retval = NULL;
2379 	ZEND_ASSERT(stmt);
2380 
2381 	if (is_numeric_str_function(name, &lval, /* dval */ NULL) == IS_LONG) {
2382 		retval = row_read_column_number(stmt, lval, &tmp_val);
2383 	} else {
2384 		retval = row_read_column_name(stmt, name, &tmp_val);
2385 	}
2386 
2387 	if (!retval) {
2388 		return false;
2389 	}
2390 	ZEND_ASSERT(retval == &tmp_val);
2391 	int res = check_empty ? i_zend_is_true(retval) : Z_TYPE(tmp_val) != IS_NULL;
2392 	zval_ptr_dtor_nogc(retval);
2393 	return res;
2394 }
2395 
row_dim_exists(zend_object * object,zval * offset,int check_empty)2396 static int row_dim_exists(zend_object *object, zval *offset, int check_empty)
2397 {
2398 	if (Z_TYPE_P(offset) == IS_LONG) {
2399 		pdo_row_t *row = (pdo_row_t *)object;
2400 		pdo_stmt_t *stmt = row->stmt;
2401 		ZEND_ASSERT(stmt);
2402 		zend_long column = Z_LVAL_P(offset);
2403 
2404 		if (!check_empty) {
2405 			return column >= 0 && column < stmt->column_count;
2406 		}
2407 
2408 		zval tmp_val;
2409 		zval *retval = row_read_column_number(stmt, column, &tmp_val);
2410 		if (!retval) {
2411 			return false;
2412 		}
2413 		ZEND_ASSERT(retval == &tmp_val);
2414 		int res = check_empty ? i_zend_is_true(retval) : Z_TYPE(tmp_val) != IS_NULL;
2415 		zval_ptr_dtor_nogc(retval);
2416 		return res;
2417 	} else {
2418 		zend_string *member = zval_try_get_string(offset);
2419 		if (!member) {
2420 			return 0;
2421 		}
2422 		int result = row_prop_exists(object, member, check_empty, NULL);
2423 		zend_string_release_ex(member, false);
2424 		return result;
2425 	}
2426 }
2427 
row_prop_delete(zend_object * object,zend_string * offset,void ** cache_slot)2428 static void row_prop_delete(zend_object *object, zend_string *offset, void **cache_slot)
2429 {
2430 	zend_throw_error(NULL, "Cannot unset PDORow property");
2431 }
2432 
row_dim_delete(zend_object * object,zval * offset)2433 static void row_dim_delete(zend_object *object, zval *offset)
2434 {
2435 	zend_throw_error(NULL, "Cannot unset PDORow offset");
2436 }
2437 
row_get_properties_for(zend_object * object,zend_prop_purpose purpose)2438 static HashTable *row_get_properties_for(zend_object *object, zend_prop_purpose purpose)
2439 {
2440 	pdo_row_t *row = (pdo_row_t *)object;
2441 	pdo_stmt_t *stmt = row->stmt;
2442 	HashTable *props;
2443 	int i;
2444 	ZEND_ASSERT(stmt);
2445 
2446 	if (purpose != ZEND_PROP_PURPOSE_DEBUG) {
2447 		return zend_std_get_properties_for(object, purpose);
2448 	}
2449 
2450 	if (!stmt->std.properties) {
2451 		rebuild_object_properties(&stmt->std);
2452 	}
2453 	props = zend_array_dup(stmt->std.properties);
2454 	for (i = 0; i < stmt->column_count; i++) {
2455 		if (zend_string_equals_literal(stmt->columns[i].name, "queryString")) {
2456 			continue;
2457 		}
2458 
2459 		zval val;
2460 		fetch_value(stmt, &val, i, NULL);
2461 
2462 		zend_hash_update(props, stmt->columns[i].name, &val);
2463 	}
2464 	return props;
2465 }
2466 
row_get_ctor(zend_object * object)2467 static zend_function *row_get_ctor(zend_object *object)
2468 {
2469 	zend_throw_exception_ex(php_pdo_get_exception(), 0, "You may not create a PDORow manually");
2470 	return NULL;
2471 }
2472 
pdo_row_get_property_ptr_ptr(zend_object * object,zend_string * name,int type,void ** cache_slot)2473 static zval *pdo_row_get_property_ptr_ptr(zend_object *object, zend_string *name, int type, void **cache_slot)
2474 {
2475 	ZEND_IGNORE_VALUE(object);
2476 	ZEND_IGNORE_VALUE(name);
2477 	ZEND_IGNORE_VALUE(type);
2478 	ZEND_IGNORE_VALUE(cache_slot);
2479 
2480 	return NULL;
2481 }
2482 
pdo_row_free_storage(zend_object * std)2483 void pdo_row_free_storage(zend_object *std)
2484 {
2485 	pdo_row_t *row = (pdo_row_t *)std;
2486 	if (row->stmt) {
2487 		ZVAL_UNDEF(&row->stmt->lazy_object_ref);
2488 		OBJ_RELEASE(&row->stmt->std);
2489 	}
2490 }
2491 
pdo_row_new(zend_class_entry * ce)2492 zend_object *pdo_row_new(zend_class_entry *ce)
2493 {
2494 	pdo_row_t *row = ecalloc(1, sizeof(pdo_row_t));
2495 	zend_object_std_init(&row->std, ce);
2496 	row->std.handlers = &pdo_row_object_handlers;
2497 
2498 	return &row->std;
2499 }
2500 
pdo_stmt_init(void)2501 void pdo_stmt_init(void)
2502 {
2503 	pdo_dbstmt_ce = register_class_PDOStatement(zend_ce_aggregate);
2504 	pdo_dbstmt_ce->get_iterator = pdo_stmt_iter_get;
2505 	pdo_dbstmt_ce->create_object = pdo_dbstmt_new;
2506 
2507 	memcpy(&pdo_dbstmt_object_handlers, &std_object_handlers, sizeof(zend_object_handlers));
2508 	pdo_dbstmt_object_handlers.offset = XtOffsetOf(pdo_stmt_t, std);
2509 	pdo_dbstmt_object_handlers.free_obj = pdo_dbstmt_free_storage;
2510 	pdo_dbstmt_object_handlers.write_property = dbstmt_prop_write;
2511 	pdo_dbstmt_object_handlers.unset_property = dbstmt_prop_delete;
2512 	pdo_dbstmt_object_handlers.get_method = dbstmt_method_get;
2513 	pdo_dbstmt_object_handlers.compare = zend_objects_not_comparable;
2514 	pdo_dbstmt_object_handlers.clone_obj = NULL;
2515 	pdo_dbstmt_object_handlers.get_gc = dbstmt_get_gc;
2516 
2517 	pdo_row_ce = register_class_PDORow();
2518 	pdo_row_ce->create_object = pdo_row_new;
2519 
2520 	memcpy(&pdo_row_object_handlers, &std_object_handlers, sizeof(zend_object_handlers));
2521 	pdo_row_object_handlers.free_obj = pdo_row_free_storage;
2522 	pdo_row_object_handlers.clone_obj = NULL;
2523 	pdo_row_object_handlers.get_property_ptr_ptr = pdo_row_get_property_ptr_ptr;
2524 	pdo_row_object_handlers.read_property = row_prop_read;
2525 	pdo_row_object_handlers.write_property = row_prop_write;
2526 	pdo_row_object_handlers.has_property = row_prop_exists;
2527 	pdo_row_object_handlers.unset_property = row_prop_delete;
2528 	pdo_row_object_handlers.read_dimension = row_dim_read;
2529 	pdo_row_object_handlers.write_dimension = row_dim_write;
2530 	pdo_row_object_handlers.has_dimension = row_dim_exists;
2531 	pdo_row_object_handlers.unset_dimension = row_dim_delete;
2532 	pdo_row_object_handlers.get_properties_for = row_get_properties_for;
2533 	pdo_row_object_handlers.get_constructor = row_get_ctor;
2534 	pdo_row_object_handlers.compare = zend_objects_not_comparable;
2535 }
2536