1 : /*
2 : +----------------------------------------------------------------------+
3 : | PHP Version 6 |
4 : +----------------------------------------------------------------------+
5 : | Copyright (c) 2006-2009 The PHP Group |
6 : +----------------------------------------------------------------------+
7 : | This source file is subject to version 3.01 of the PHP license, |
8 : | that is bundled with this package in the file LICENSE, and is |
9 : | available through the world-wide-web at the following url: |
10 : | http://www.php.net/license/3_01.txt |
11 : | If you did not receive a copy of the PHP license and are unable to |
12 : | obtain it through the world-wide-web, please send a note to |
13 : | license@php.net so we can mail you a copy immediately. |
14 : +----------------------------------------------------------------------+
15 : | Authors: Georg Richter <georg@mysql.com> |
16 : | Andrey Hristov <andrey@mysql.com> |
17 : | Ulf Wendel <uwendel@mysql.com> |
18 : +----------------------------------------------------------------------+
19 : */
20 :
21 : /* $Id: mysqlnd_ps.c 289850 2009-10-22 14:30:51Z andrey $ */
22 : #include "php.h"
23 : #include "mysqlnd.h"
24 : #include "mysqlnd_wireprotocol.h"
25 : #include "mysqlnd_priv.h"
26 : #include "mysqlnd_result.h"
27 : #include "mysqlnd_result_meta.h"
28 : #include "mysqlnd_statistics.h"
29 : #include "mysqlnd_debug.h"
30 :
31 :
32 : #define MYSQLND_SILENT
33 :
34 :
35 : const char * const mysqlnd_not_bound_as_blob = "Can't send long data for non-string/non-binary data types";
36 : const char * const mysqlnd_stmt_not_prepared = "Statement not prepared";
37 :
38 : static struct st_mysqlnd_stmt_methods *mysqlnd_stmt_methods;
39 :
40 : /* Exported by mysqlnd.c */
41 : enum_func_status mysqlnd_simple_command(MYSQLND *conn, enum php_mysqlnd_server_command command,
42 : const char * const arg, size_t arg_len,
43 : enum php_mysql_packet_type ok_packet,
44 : zend_bool silent, zend_bool ignore_upsert_status
45 : TSRMLS_DC);
46 :
47 : /* Exported by mysqlnd_ps_codec.c */
48 : zend_uchar* mysqlnd_stmt_execute_generate_request(MYSQLND_STMT *stmt, size_t *request_len,
49 : zend_bool *free_buffer TSRMLS_DC);
50 :
51 :
52 : MYSQLND_RES * _mysqlnd_stmt_use_result(MYSQLND_STMT *stmt TSRMLS_DC);
53 :
54 : enum_func_status mysqlnd_fetch_stmt_row_buffered(MYSQLND_RES *result, void *param,
55 : unsigned int flags,
56 : zend_bool *fetched_anything TSRMLS_DC);
57 :
58 : enum_func_status mysqlnd_fetch_stmt_row_cursor(MYSQLND_RES *result, void *param,
59 : unsigned int flags,
60 : zend_bool *fetched_anything TSRMLS_DC);
61 :
62 : static void mysqlnd_stmt_separate_result_bind(MYSQLND_STMT * const stmt TSRMLS_DC);
63 : static void mysqlnd_stmt_separate_one_result_bind(MYSQLND_STMT * const stmt, unsigned int param_no TSRMLS_DC);
64 :
65 : static void mysqlnd_internal_free_stmt_content(MYSQLND_STMT * const stmt TSRMLS_DC);
66 : static enum_func_status mysqlnd_stmt_execute_parse_response(MYSQLND_STMT * const stmt TSRMLS_DC);
67 :
68 : /* {{{ mysqlnd_stmt::store_result */
69 : static MYSQLND_RES *
70 : MYSQLND_METHOD(mysqlnd_stmt, store_result)(MYSQLND_STMT * const stmt TSRMLS_DC)
71 404 : {
72 : enum_func_status ret;
73 404 : MYSQLND *conn = stmt->conn;
74 : MYSQLND_RES *result;
75 404 : zend_bool to_cache = FALSE;
76 :
77 404 : DBG_ENTER("mysqlnd_stmt::store_result");
78 404 : DBG_INF_FMT("stmt=%lu", stmt->stmt_id);
79 :
80 : /* be compliant with libmysql - NULL will turn */
81 404 : if (!stmt->field_count) {
82 0 : DBG_RETURN(NULL);
83 : }
84 :
85 404 : if (stmt->cursor_exists) {
86 : /* Silently convert buffered to unbuffered, for now */
87 0 : DBG_RETURN(stmt->m->use_result(stmt TSRMLS_CC));
88 : }
89 :
90 : /* Nothing to store for UPSERT/LOAD DATA*/
91 404 : if (CONN_GET_STATE(conn) != CONN_FETCHING_DATA ||
92 : stmt->state != MYSQLND_STMT_WAITING_USE_OR_STORE)
93 : {
94 3 : SET_CLIENT_ERROR(conn->error_info, CR_COMMANDS_OUT_OF_SYNC,
95 : UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
96 3 : DBG_RETURN(NULL);
97 : }
98 :
99 401 : stmt->default_rset_handler = stmt->m->store_result;
100 :
101 401 : SET_EMPTY_ERROR(stmt->error_info);
102 401 : SET_EMPTY_ERROR(stmt->conn->error_info);
103 401 : MYSQLND_INC_CONN_STATISTIC(&conn->stats, STAT_PS_BUFFERED_SETS);
104 :
105 401 : result = stmt->result;
106 401 : result->type = MYSQLND_RES_PS_BUF;
107 401 : result->m.fetch_row = mysqlnd_fetch_stmt_row_buffered;
108 401 : result->m.fetch_lengths = NULL;/* makes no sense */
109 401 : if (!result->zval_cache) {
110 0 : result->zval_cache = mysqlnd_palloc_get_thd_cache_reference(conn->zval_cache);
111 : }
112 :
113 : /* Create room for 'next_extend' rows */
114 :
115 401 : ret = mysqlnd_store_result_fetch_data(conn, result, result->meta,
116 : TRUE, to_cache TSRMLS_CC);
117 :
118 401 : if (PASS == ret) {
119 : /* libmysql API docs say it should be so for SELECT statements */
120 401 : stmt->upsert_status.affected_rows = stmt->result->stored_data->row_count;
121 :
122 401 : stmt->state = MYSQLND_STMT_USE_OR_STORE_CALLED;
123 : } else {
124 0 : conn->error_info = result->stored_data->error_info;
125 0 : stmt->result->m.free_result_contents(stmt->result TSRMLS_CC);
126 0 : mnd_efree(stmt->result);
127 0 : stmt->result = NULL;
128 0 : stmt->state = MYSQLND_STMT_PREPARED;
129 : }
130 :
131 401 : DBG_RETURN(result);
132 : }
133 : /* }}} */
134 :
135 :
136 : /* {{{ mysqlnd_stmt::background_store_result */
137 : static MYSQLND_RES *
138 : MYSQLND_METHOD(mysqlnd_stmt, background_store_result)(MYSQLND_STMT * const stmt TSRMLS_DC)
139 0 : {
140 : enum_func_status ret;
141 0 : MYSQLND *conn = stmt->conn;
142 : MYSQLND_RES *result;
143 0 : zend_bool to_cache = FALSE;
144 :
145 0 : DBG_ENTER("mysqlnd_stmt::background_store_result");
146 0 : DBG_INF_FMT("stmt=%lu", stmt->stmt_id);
147 :
148 : /* be compliant with libmysql - NULL will turn */
149 0 : if (!stmt->field_count) {
150 0 : DBG_RETURN(NULL);
151 : }
152 :
153 0 : if (stmt->cursor_exists) {
154 : /* Silently convert buffered to unbuffered, for now */
155 0 : MYSQLND_RES * res = stmt->m->use_result(stmt TSRMLS_CC);
156 0 : DBG_RETURN(res);
157 : }
158 :
159 : /* Nothing to store for UPSERT/LOAD DATA*/
160 0 : if (CONN_GET_STATE(conn) != CONN_FETCHING_DATA ||
161 : stmt->state != MYSQLND_STMT_WAITING_USE_OR_STORE)
162 : {
163 0 : SET_CLIENT_ERROR(conn->error_info, CR_COMMANDS_OUT_OF_SYNC,
164 : UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
165 0 : DBG_RETURN(NULL);
166 : }
167 :
168 0 : stmt->default_rset_handler = stmt->m->store_result;
169 :
170 0 : SET_EMPTY_ERROR(stmt->error_info);
171 0 : SET_EMPTY_ERROR(stmt->conn->error_info);
172 0 : MYSQLND_INC_CONN_STATISTIC(&conn->stats, STAT_PS_BUFFERED_SETS);
173 :
174 0 : result = stmt->result;
175 0 : result->type = MYSQLND_RES_PS_BUF;
176 0 : result->m.fetch_row = mysqlnd_fetch_stmt_row_buffered;
177 0 : result->m.fetch_lengths = NULL;/* makes no sense */
178 0 : if (!result->zval_cache) {
179 0 : result->zval_cache = mysqlnd_palloc_get_thd_cache_reference(conn->zval_cache);
180 : }
181 :
182 : /* Create room for 'next_extend' rows */
183 :
184 : /* Not set for SHOW statements at PREPARE stage */
185 0 : if (result->conn) {
186 0 : result->conn->m->free_reference(result->conn TSRMLS_CC);
187 0 : result->conn = NULL; /* store result does not reference the connection */
188 : }
189 :
190 0 : ret = mysqlnd_store_result_fetch_data(conn, result, result->meta,
191 : TRUE, to_cache TSRMLS_CC);
192 :
193 0 : if (PASS == ret) {
194 : /* libmysql API docs say it should be so for SELECT statements */
195 0 : stmt->upsert_status.affected_rows = stmt->result->stored_data->row_count;
196 :
197 0 : stmt->state = MYSQLND_STMT_USE_OR_STORE_CALLED;
198 : } else {
199 0 : conn->error_info = result->stored_data->error_info;
200 0 : stmt->result->m.free_result_contents(stmt->result TSRMLS_CC);
201 0 : mnd_efree(stmt->result);
202 0 : stmt->result = NULL;
203 0 : stmt->state = MYSQLND_STMT_PREPARED;
204 : }
205 :
206 0 : DBG_RETURN(result);
207 : }
208 : /* }}} */
209 :
210 :
211 : /* {{{ mysqlnd_stmt::get_result */
212 : static MYSQLND_RES *
213 : MYSQLND_METHOD(mysqlnd_stmt, get_result)(MYSQLND_STMT * const stmt TSRMLS_DC)
214 584 : {
215 584 : MYSQLND *conn = stmt->conn;
216 : MYSQLND_RES *result;
217 :
218 584 : DBG_ENTER("mysqlnd_stmt::get_result");
219 584 : DBG_INF_FMT("stmt=%lu", stmt->stmt_id);
220 :
221 : /* be compliant with libmysql - NULL will turn */
222 584 : if (!stmt->field_count) {
223 0 : DBG_RETURN(NULL);
224 : }
225 :
226 584 : if (stmt->cursor_exists) {
227 : /* Silently convert buffered to unbuffered, for now */
228 0 : DBG_RETURN(stmt->m->use_result(stmt TSRMLS_CC));
229 : }
230 :
231 : /* Nothing to store for UPSERT/LOAD DATA*/
232 584 : if (CONN_GET_STATE(conn) != CONN_FETCHING_DATA || stmt->state != MYSQLND_STMT_WAITING_USE_OR_STORE) {
233 6 : SET_CLIENT_ERROR(conn->error_info, CR_COMMANDS_OUT_OF_SYNC,
234 : UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
235 6 : DBG_RETURN(NULL);
236 : }
237 :
238 578 : SET_EMPTY_ERROR(stmt->error_info);
239 578 : SET_EMPTY_ERROR(stmt->conn->error_info);
240 578 : MYSQLND_INC_CONN_STATISTIC(&conn->stats, STAT_BUFFERED_SETS);
241 :
242 578 : result = mysqlnd_result_init(stmt->result->field_count,
243 : mysqlnd_palloc_get_thd_cache_reference(conn->zval_cache) TSRMLS_CC);
244 :
245 578 : result->meta = stmt->result->meta->m->clone_metadata(stmt->result->meta, FALSE TSRMLS_CC);
246 :
247 578 : if ((result = result->m.store_result(result, conn, TRUE TSRMLS_CC))) {
248 578 : stmt->upsert_status.affected_rows = result->stored_data->row_count;
249 578 : stmt->state = MYSQLND_STMT_PREPARED;
250 578 : result->type = MYSQLND_RES_PS_BUF;
251 : } else {
252 0 : stmt->error_info = conn->error_info;
253 0 : stmt->state = MYSQLND_STMT_PREPARED;
254 : }
255 :
256 578 : DBG_RETURN(result);
257 : }
258 : /* }}} */
259 :
260 :
261 : /* {{{ mysqlnd_stmt::more_results */
262 : static zend_bool
263 : MYSQLND_METHOD(mysqlnd_stmt, more_results)(const MYSQLND_STMT * stmt TSRMLS_DC)
264 4135 : {
265 4135 : DBG_ENTER("mysqlnd_stmt::more_results");
266 : /* (conn->state == CONN_NEXT_RESULT_PENDING) too */
267 4135 : DBG_RETURN((stmt->conn && (stmt->conn->upsert_status.server_status &
268 : SERVER_MORE_RESULTS_EXISTS))?
269 : TRUE:
270 : FALSE);
271 : }
272 : /* }}} */
273 :
274 :
275 : /* {{{ mysqlnd_stmt::next_result */
276 : static enum_func_status
277 : MYSQLND_METHOD(mysqlnd_stmt, next_result)(MYSQLND_STMT * stmt TSRMLS_DC)
278 19 : {
279 19 : MYSQLND *conn = stmt->conn;
280 :
281 19 : DBG_ENTER("mysqlnd_stmt::next_result");
282 19 : DBG_INF_FMT("stmt=%lu", stmt->stmt_id);
283 :
284 19 : if (!conn ||
285 : CONN_GET_STATE(conn) != CONN_NEXT_RESULT_PENDING ||
286 : !(conn->upsert_status.server_status & SERVER_MORE_RESULTS_EXISTS) ||
287 : !stmt->result)
288 : {
289 2 : DBG_RETURN(FAIL);
290 : }
291 :
292 : /* Free space for next result */
293 17 : mysqlnd_internal_free_stmt_content(stmt TSRMLS_CC);
294 :
295 17 : DBG_RETURN(mysqlnd_stmt_execute_parse_response(stmt TSRMLS_CC));
296 : }
297 : /* }}} */
298 :
299 :
300 : /* {{{ mysqlnd_stmt_skip_metadata */
301 : static enum_func_status
302 : mysqlnd_stmt_skip_metadata(MYSQLND_STMT *stmt TSRMLS_DC)
303 817 : {
304 : /* Follows parameter metadata, we have just to skip it, as libmysql does */
305 817 : unsigned int i = 0;
306 817 : enum_func_status ret = PASS;
307 : php_mysql_packet_res_field field_packet;
308 :
309 817 : DBG_ENTER("mysqlnd_stmt_skip_metadata");
310 817 : DBG_INF_FMT("stmt=%lu", stmt->stmt_id);
311 :
312 817 : PACKET_INIT_ALLOCA(field_packet, PROT_RSET_FLD_PACKET);
313 817 : field_packet.skip_parsing = TRUE;
314 2171 : for (;i < stmt->param_count; i++) {
315 1354 : if (FAIL == PACKET_READ_ALLOCA(field_packet, stmt->conn)) {
316 0 : ret = FAIL;
317 0 : break;
318 : }
319 : }
320 817 : PACKET_FREE_ALLOCA(field_packet);
321 :
322 817 : DBG_RETURN(ret);
323 : }
324 : /* }}} */
325 :
326 :
327 : /* {{{ mysqlnd_stmt_read_prepare_response */
328 : static enum_func_status
329 : mysqlnd_stmt_read_prepare_response(MYSQLND_STMT *stmt TSRMLS_DC)
330 4096 : {
331 : php_mysql_packet_prepare_response prepare_resp;
332 4096 : enum_func_status ret = PASS;
333 :
334 4096 : DBG_ENTER("mysqlnd_stmt_read_prepare_response");
335 4096 : DBG_INF_FMT("stmt=%lu", stmt->stmt_id);
336 :
337 4096 : PACKET_INIT_ALLOCA(prepare_resp, PROT_PREPARE_RESP_PACKET);
338 4096 : if (FAIL == PACKET_READ_ALLOCA(prepare_resp, stmt->conn)) {
339 1 : ret = FAIL;
340 1 : goto done;
341 : }
342 :
343 4095 : if (0xFF == prepare_resp.error_code) {
344 26 : stmt->error_info = stmt->conn->error_info = prepare_resp.error_info;
345 26 : ret = FAIL;
346 26 : goto done;
347 : }
348 :
349 4069 : stmt->stmt_id = prepare_resp.stmt_id;
350 4069 : stmt->warning_count = stmt->conn->upsert_status.warning_count = prepare_resp.warning_count;
351 4069 : stmt->field_count = stmt->conn->field_count = prepare_resp.field_count;
352 4069 : stmt->param_count = prepare_resp.param_count;
353 4069 : PACKET_FREE_ALLOCA(prepare_resp);
354 :
355 4096 : done:
356 4096 : DBG_RETURN(ret);
357 : }
358 : /* }}} */
359 :
360 :
361 : /* {{{ mysqlnd_stmt_prepare_read_eof */
362 : static enum_func_status
363 : mysqlnd_stmt_prepare_read_eof(MYSQLND_STMT *stmt TSRMLS_DC)
364 3170 : {
365 : php_mysql_packet_eof fields_eof;
366 : enum_func_status ret;
367 :
368 3170 : DBG_ENTER("mysqlnd_stmt_prepare_read_eof");
369 3170 : DBG_INF_FMT("stmt=%lu", stmt->stmt_id);
370 :
371 3170 : PACKET_INIT_ALLOCA(fields_eof, PROT_EOF_PACKET);
372 3170 : if (FAIL == (ret = PACKET_READ_ALLOCA(fields_eof, stmt->conn))) {
373 0 : if (stmt->result) {
374 0 : stmt->result->m.free_result_contents(stmt->result TSRMLS_CC);
375 0 : mnd_efree(stmt->result);
376 0 : memset(stmt, 0, sizeof(MYSQLND_STMT));
377 0 : stmt->state = MYSQLND_STMT_INITTED;
378 : }
379 : } else {
380 3170 : stmt->upsert_status.server_status = fields_eof.server_status;
381 3170 : stmt->upsert_status.warning_count = fields_eof.warning_count;
382 3170 : stmt->state = MYSQLND_STMT_PREPARED;
383 : }
384 3170 : PACKET_FREE_ALLOCA(fields_eof);
385 :
386 3170 : DBG_RETURN(ret);
387 : }
388 : /* }}} */
389 :
390 :
391 : /* {{{ mysqlnd_stmt::prepare */
392 : static enum_func_status
393 : MYSQLND_METHOD(mysqlnd_stmt, prepare)(MYSQLND_STMT * const stmt, const char * const query,
394 : unsigned int query_len TSRMLS_DC)
395 4100 : {
396 4100 : MYSQLND_STMT *stmt_to_prepare = stmt;
397 :
398 4100 : DBG_ENTER("mysqlnd_stmt::prepare");
399 4100 : DBG_INF_FMT("stmt=%lu", stmt->stmt_id);
400 :
401 4100 : SET_ERROR_AFF_ROWS(stmt);
402 4100 : SET_ERROR_AFF_ROWS(stmt->conn);
403 :
404 4100 : SET_EMPTY_ERROR(stmt->error_info);
405 4100 : SET_EMPTY_ERROR(stmt->conn->error_info);
406 :
407 4100 : if (stmt->state > MYSQLND_STMT_INITTED) {
408 : /* See if we have to clean the wire */
409 2423 : if (stmt->state == MYSQLND_STMT_WAITING_USE_OR_STORE) {
410 : /* Do implicit use_result and then flush the result */
411 1 : stmt->default_rset_handler = stmt->m->use_result;
412 1 : stmt->default_rset_handler(stmt TSRMLS_CC);
413 : }
414 : /* No 'else' here please :) */
415 2423 : if (stmt->state > MYSQLND_STMT_WAITING_USE_OR_STORE) {
416 954 : stmt->result->m.skip_result(stmt->result TSRMLS_CC);
417 : }
418 : /*
419 : Create a new test statement, which we will prepare, but if anything
420 : fails, we will scrap it.
421 : */
422 2423 : stmt_to_prepare = mysqlnd_stmt_init(stmt->conn);
423 : }
424 :
425 4100 : if (FAIL == mysqlnd_simple_command(stmt_to_prepare->conn, COM_STMT_PREPARE, query,
426 : query_len, PROT_LAST, FALSE, TRUE TSRMLS_CC) ||
427 : FAIL == mysqlnd_stmt_read_prepare_response(stmt_to_prepare TSRMLS_CC)) {
428 : goto fail;
429 : }
430 :
431 4069 : if (stmt_to_prepare->param_count) {
432 817 : if (FAIL == mysqlnd_stmt_skip_metadata(stmt_to_prepare TSRMLS_CC) ||
433 : FAIL == mysqlnd_stmt_prepare_read_eof(stmt_to_prepare TSRMLS_CC))
434 : {
435 : goto fail;
436 : }
437 : }
438 :
439 : /*
440 : Read metadata only if there is actual result set.
441 : Beware that SHOW statements bypass the PS framework and thus they send
442 : no metadata at prepare.
443 : */
444 4069 : if (stmt_to_prepare->field_count) {
445 : MYSQLND_RES *result = mysqlnd_result_init(stmt_to_prepare->field_count,
446 : mysqlnd_palloc_get_thd_cache_reference(stmt->conn->zval_cache)
447 2353 : TSRMLS_CC);
448 : /* Allocate the result now as it is needed for the reading of metadata */
449 2353 : stmt_to_prepare->result = result;
450 :
451 2353 : result->conn = stmt_to_prepare->conn->m->get_reference(stmt_to_prepare->conn TSRMLS_CC);
452 :
453 2353 : result->type = MYSQLND_RES_PS_BUF;
454 :
455 2353 : if (FAIL == result->m.read_result_metadata(result, stmt_to_prepare->conn TSRMLS_CC) ||
456 : FAIL == mysqlnd_stmt_prepare_read_eof(stmt_to_prepare TSRMLS_CC)) {
457 : goto fail;
458 : }
459 : }
460 :
461 4069 : if (stmt_to_prepare != stmt) {
462 : /* Free old buffers, binding and resources on server */
463 2420 : stmt->m->net_close(stmt, TRUE TSRMLS_CC);
464 :
465 2420 : memcpy(stmt, stmt_to_prepare, sizeof(MYSQLND_STMT));
466 :
467 : /* Now we will have a clean new statement object */
468 2420 : mnd_efree(stmt_to_prepare);
469 : }
470 4069 : stmt->state = MYSQLND_STMT_PREPARED;
471 4069 : DBG_INF("PASS");
472 4069 : DBG_RETURN(PASS);
473 :
474 31 : fail:
475 31 : if (stmt_to_prepare != stmt) {
476 3 : stmt_to_prepare->m->dtor(stmt_to_prepare, TRUE TSRMLS_CC);
477 : }
478 31 : stmt->state = MYSQLND_STMT_INITTED;
479 :
480 31 : DBG_INF("FAIL");
481 31 : DBG_RETURN(FAIL);
482 : }
483 : /* }}} */
484 :
485 :
486 : /* {{{ mysqlnd_stmt_execute_parse_response */
487 : static enum_func_status
488 : mysqlnd_stmt_execute_parse_response(MYSQLND_STMT * const stmt TSRMLS_DC)
489 4752 : {
490 : enum_func_status ret;
491 4752 : MYSQLND *conn = stmt->conn;
492 :
493 4752 : DBG_ENTER("mysqlnd_stmt_execute_parse_response");
494 :
495 4752 : CONN_SET_STATE(conn, CONN_QUERY_SENT);
496 :
497 4752 : ret = mysqlnd_query_read_result_set_header(stmt->conn, stmt TSRMLS_CC);
498 4752 : if (ret == FAIL) {
499 6 : stmt->error_info = conn->error_info;
500 6 : stmt->upsert_status.affected_rows = conn->upsert_status.affected_rows;
501 6 : if (CONN_GET_STATE(conn) == CONN_QUIT_SENT) {
502 : /* close the statement here, the connection has been closed */
503 : }
504 6 : stmt->state = MYSQLND_STMT_PREPARED;
505 : } else {
506 4746 : SET_EMPTY_ERROR(stmt->error_info);
507 4746 : SET_EMPTY_ERROR(stmt->conn->error_info);
508 4746 : stmt->send_types_to_server = 0;
509 4746 : stmt->upsert_status = conn->upsert_status;
510 4746 : stmt->state = MYSQLND_STMT_EXECUTED;
511 4746 : if (conn->last_query_type == QUERY_UPSERT || conn->last_query_type == QUERY_LOAD_LOCAL) {
512 2507 : DBG_INF("PASS");
513 2507 : DBG_RETURN(PASS);
514 : }
515 :
516 2239 : stmt->result->type = MYSQLND_RES_PS_BUF;
517 2239 : if (!stmt->result->conn) {
518 : /*
519 : For SHOW we don't create (bypasses PS in server)
520 : a result set at prepare and thus a connection was missing
521 : */
522 24 : stmt->result->conn = stmt->conn->m->get_reference(stmt->conn TSRMLS_CC);
523 : }
524 :
525 : /* Update stmt->field_count as SHOW sets it to 0 at prepare */
526 2239 : stmt->field_count = stmt->result->field_count = conn->field_count;
527 2239 : stmt->result->lengths = NULL;
528 2239 : if (stmt->field_count) {
529 2239 : stmt->state = MYSQLND_STMT_WAITING_USE_OR_STORE;
530 : /*
531 : We need to set this because the user might not call
532 : use_result() or store_result() and we should be able to scrap the
533 : data on the line, if he just decides to close the statement.
534 : */
535 2239 : DBG_INF_FMT("server_status=%d cursor=%d", stmt->upsert_status.server_status,
536 : stmt->upsert_status.server_status & SERVER_STATUS_CURSOR_EXISTS);
537 :
538 2239 : if (stmt->upsert_status.server_status & SERVER_STATUS_CURSOR_EXISTS) {
539 4 : DBG_INF("cursor exists");
540 4 : stmt->cursor_exists = TRUE;
541 4 : CONN_SET_STATE(conn, CONN_READY);
542 : /* Only cursor read */
543 4 : stmt->default_rset_handler = stmt->m->use_result;
544 4 : DBG_INF("use_result");
545 2235 : } else if (stmt->flags & CURSOR_TYPE_READ_ONLY) {
546 0 : DBG_INF("asked for cursor but got none");
547 : /*
548 : We have asked for CURSOR but got no cursor, because the condition
549 : above is not fulfilled. Then...
550 :
551 : This is a single-row result set, a result set with no rows, EXPLAIN,
552 : SHOW VARIABLES, or some other command which either a) bypasses the
553 : cursors framework in the server and writes rows directly to the
554 : network or b) is more efficient if all (few) result set rows are
555 : precached on client and server's resources are freed.
556 : */
557 : /* preferred is buffered read */
558 0 : stmt->default_rset_handler = stmt->m->store_result;
559 0 : DBG_INF("store_result");
560 : } else {
561 2235 : DBG_INF("no cursor");
562 : /* preferred is unbuffered read */
563 2235 : stmt->default_rset_handler = stmt->m->use_result;
564 2235 : DBG_INF("use_result");
565 : }
566 : }
567 : }
568 :
569 2245 : DBG_INF(ret == PASS? "PASS":"FAIL");
570 2245 : DBG_RETURN(ret);
571 : }
572 : /* }}} */
573 :
574 :
575 : /* {{{ mysqlnd_stmt::execute */
576 : static enum_func_status
577 : MYSQLND_METHOD(mysqlnd_stmt, execute)(MYSQLND_STMT * const stmt TSRMLS_DC)
578 4742 : {
579 : enum_func_status ret;
580 4742 : MYSQLND *conn = stmt->conn;
581 : zend_uchar *request;
582 : size_t request_len;
583 : zend_bool free_request;
584 :
585 4742 : DBG_ENTER("mysqlnd_stmt::execute");
586 4742 : DBG_INF_FMT("stmt=%lu", stmt->stmt_id);
587 :
588 4742 : SET_ERROR_AFF_ROWS(stmt);
589 4742 : SET_ERROR_AFF_ROWS(stmt->conn);
590 :
591 6965 : if (stmt->result && stmt->state >= MYSQLND_STMT_PREPARED && stmt->field_count) {
592 : /*
593 : We don need to copy the data from the buffers which we will clean.
594 : Because it has already been copied. See
595 : #ifndef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF
596 : */
597 : #ifdef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF
598 : if (stmt->result_bind &&
599 : stmt->result_zvals_separated_once == TRUE &&
600 : stmt->state >= MYSQLND_STMT_USER_FETCHING)
601 : {
602 : /*
603 : We need to copy the data from the buffers which we will clean.
604 : The bound variables point to them only if the user has started
605 : to fetch data (MYSQLND_STMT_USER_FETCHING).
606 : We need to check 'result_zvals_separated_once' or we will leak
607 : in the following scenario
608 : prepare("select 1 from dual");
609 : execute();
610 : fetch(); <-- no binding, but that's not a problem
611 : bind_result();
612 : execute(); <-- here we will leak because we separate without need
613 : */
614 : unsigned int i;
615 : for (i = 0; i < stmt->field_count; i++) {
616 : if (stmt->result_bind[i].bound == TRUE) {
617 : zval_copy_ctor(stmt->result_bind[i].zv);
618 : }
619 : }
620 : }
621 : #endif
622 :
623 : /*
624 : If right after execute() we have to call the appropriate
625 : use_result() or store_result() and clean.
626 : */
627 2223 : if (stmt->state == MYSQLND_STMT_WAITING_USE_OR_STORE) {
628 0 : DBG_INF("fetching result set header");
629 : /* Do implicit use_result and then flush the result */
630 0 : stmt->default_rset_handler = stmt->m->use_result;
631 0 : stmt->default_rset_handler(stmt TSRMLS_CC);
632 : }
633 :
634 2223 : if (stmt->state > MYSQLND_STMT_WAITING_USE_OR_STORE) {
635 22 : DBG_INF("skipping result");
636 : /* Flush if anything is left and unbuffered set */
637 22 : stmt->result->m.skip_result(stmt->result TSRMLS_CC);
638 : }
639 :
640 2223 : if (stmt->state > MYSQLND_STMT_PREPARED) {
641 : /* As the buffers have been freed, we should go back to PREPARED */
642 22 : stmt->state = MYSQLND_STMT_PREPARED;
643 : }
644 :
645 : /*
646 : Executed, but the user hasn't started to fetch
647 : This will clean also the metadata, but after the EXECUTE call we will
648 : have it again.
649 : */
650 2223 : stmt->result->m.free_result_buffers(stmt->result TSRMLS_CC);
651 2519 : } else if (stmt->state < MYSQLND_STMT_PREPARED) {
652 : /* Only initted - error */
653 2 : SET_CLIENT_ERROR(conn->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE,
654 : mysqlnd_out_of_sync);
655 2 : SET_STMT_ERROR(stmt, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
656 2 : DBG_INF("FAIL");
657 2 : DBG_RETURN(FAIL);
658 : }
659 :
660 4740 : if (stmt->param_count) {
661 1426 : unsigned int i, not_bound = 0;
662 1426 : if (!stmt->param_bind) {
663 1 : SET_STMT_ERROR(stmt, CR_PARAMS_NOT_BOUND, UNKNOWN_SQLSTATE,
664 : "No data supplied for parameters in prepared statement");
665 1 : DBG_INF("FAIL");
666 1 : DBG_RETURN(FAIL);
667 : }
668 4181 : for (i = 0; i < stmt->param_count; i++) {
669 2756 : if (stmt->param_bind[i].zv == NULL) {
670 0 : not_bound++;
671 : }
672 : }
673 1425 : if (not_bound) {
674 : char * msg;
675 0 : spprintf(&msg, 0, "No data supplied for %d parameter%s in prepared statement",
676 : not_bound, not_bound>1 ?"s":"");
677 0 : SET_STMT_ERROR(stmt, CR_PARAMS_NOT_BOUND, UNKNOWN_SQLSTATE, msg);
678 0 : if (msg) {
679 0 : efree(msg);
680 : }
681 0 : DBG_INF("FAIL");
682 0 : DBG_RETURN(FAIL);
683 : }
684 : }
685 4739 : request = mysqlnd_stmt_execute_generate_request(stmt, &request_len, &free_request TSRMLS_CC);
686 :
687 : /* support for buffer types should be added here ! */
688 :
689 4739 : ret = mysqlnd_simple_command(stmt->conn, COM_STMT_EXECUTE, (char *)request, request_len,
690 : PROT_LAST /* we will handle the response packet*/,
691 : FALSE, FALSE TSRMLS_CC);
692 :
693 4739 : if (free_request) {
694 14 : mnd_efree(request);
695 : }
696 :
697 4739 : if (ret == FAIL) {
698 4 : stmt->error_info = conn->error_info;
699 4 : DBG_INF("FAIL");
700 4 : DBG_RETURN(FAIL);
701 : }
702 4735 : stmt->execute_count++;
703 :
704 4735 : ret = mysqlnd_stmt_execute_parse_response(stmt TSRMLS_CC);
705 :
706 4735 : if (ret == PASS && conn->last_query_type == QUERY_UPSERT && stmt->upsert_status.affected_rows) {
707 2412 : MYSQLND_INC_CONN_STATISTIC_W_VALUE(&conn->stats, STAT_ROWS_AFFECTED_PS, stmt->upsert_status.affected_rows);
708 : }
709 4735 : DBG_RETURN(ret);
710 : }
711 : /* }}} */
712 :
713 :
714 : /* {{{ mysqlnd_fetch_stmt_row_buffered */
715 : enum_func_status
716 : mysqlnd_fetch_stmt_row_buffered(MYSQLND_RES *result, void *param, unsigned int flags,
717 : zend_bool *fetched_anything TSRMLS_DC)
718 1668 : {
719 1668 : MYSQLND_STMT *stmt = (MYSQLND_STMT *) param;
720 1668 : MYSQLND_RES_BUFFERED *set = result->stored_data;
721 1668 : unsigned int field_count = result->meta->field_count;
722 :
723 1668 : DBG_ENTER("mysqlnd_fetch_stmt_row_buffered");
724 1668 : DBG_INF_FMT("stmt=%lu", stmt->stmt_id);
725 :
726 : /* If we haven't read everything */
727 3268 : if (set->data_cursor &&
728 : (set->data_cursor - set->data) < (set->row_count * field_count))
729 : {
730 : /* The user could have skipped binding - don't crash*/
731 1600 : if (stmt->result_bind) {
732 : unsigned int i;
733 1600 : MYSQLND_RES_METADATA * meta = result->meta;
734 1600 : zval **current_row = set->data_cursor;
735 :
736 1600 : if (NULL == current_row[0]) {
737 1600 : uint64_t row_num = (set->data_cursor - set->data) / field_count;
738 1600 : set->initialized_rows++;
739 1600 : result->m.row_decoder(set->row_buffers[row_num],
740 : current_row,
741 : meta->field_count,
742 : meta->fields,
743 : result->conn TSRMLS_CC);
744 1600 : if (stmt->update_max_length) {
745 0 : for (i = 0; i < result->field_count; i++) {
746 : /*
747 : NULL fields are 0 length, 0 is not more than 0
748 : String of zero size, definitely can't be the next max_length.
749 : Thus for NULL and zero-length we are quite efficient.
750 : */
751 0 : if (Z_TYPE_P(current_row[i]) >= IS_STRING) {
752 0 : unsigned long len = Z_STRLEN_P(current_row[i]);
753 0 : if (meta->fields[i].max_length < len) {
754 0 : meta->fields[i].max_length = len;
755 : }
756 : }
757 : }
758 : }
759 : }
760 :
761 5872 : for (i = 0; i < result->field_count; i++) {
762 : /* Clean what we copied last time */
763 : #ifndef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF
764 4272 : if (stmt->result_bind[i].zv) {
765 4272 : zval_dtor(stmt->result_bind[i].zv);
766 : }
767 : #endif
768 : /* copy the type */
769 4272 : if (stmt->result_bind[i].bound == TRUE) {
770 4272 : DBG_INF_FMT("i=%d type=%d", i, Z_TYPE_P(current_row[i]));
771 4272 : if (Z_TYPE_P(current_row[i]) != IS_NULL) {
772 : /*
773 : Copy the value.
774 : Pre-condition is that the zvals in the result_bind buffer
775 : have been ZVAL_NULL()-ed or to another simple type
776 : (int, double, bool but not string). Because of the reference
777 : counting the user can't delete the strings the variables point to.
778 : */
779 :
780 4264 : Z_TYPE_P(stmt->result_bind[i].zv) = Z_TYPE_P(current_row[i]);
781 4264 : stmt->result_bind[i].zv->value = current_row[i]->value;
782 : #ifndef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF
783 4264 : zval_copy_ctor(stmt->result_bind[i].zv);
784 : #endif
785 : } else {
786 8 : ZVAL_NULL(stmt->result_bind[i].zv);
787 : }
788 : }
789 : }
790 : }
791 1600 : set->data_cursor += field_count;
792 1600 : *fetched_anything = TRUE;
793 : /* buffered result sets don't have a connection */
794 1600 : MYSQLND_INC_GLOBAL_STATISTIC(STAT_ROWS_FETCHED_FROM_CLIENT_PS_BUF);
795 1600 : DBG_INF("row fetched");
796 : } else {
797 68 : set->data_cursor = NULL;
798 68 : *fetched_anything = FALSE;
799 68 : DBG_INF("no more data");
800 : }
801 1668 : DBG_INF("PASS");
802 1668 : DBG_RETURN(PASS);
803 : }
804 : /* }}} */
805 :
806 :
807 : /* {{{ mysqlnd_stmt_fetch_row_unbuffered */
808 : static enum_func_status
809 : mysqlnd_stmt_fetch_row_unbuffered(MYSQLND_RES *result, void *param, unsigned int flags,
810 : zend_bool *fetched_anything TSRMLS_DC)
811 10963 : {
812 : enum_func_status ret;
813 10963 : MYSQLND_STMT *stmt = (MYSQLND_STMT *) param;
814 10963 : php_mysql_packet_row *row_packet = result->row_packet;
815 :
816 10963 : DBG_ENTER("mysqlnd_stmt_fetch_row_unbuffered");
817 :
818 10963 : if (result->unbuf->eof_reached) {
819 : /* No more rows obviously */
820 0 : *fetched_anything = FALSE;
821 0 : DBG_INF("eof reached");
822 0 : DBG_RETURN(PASS);
823 : }
824 10963 : if (CONN_GET_STATE(result->conn) != CONN_FETCHING_DATA) {
825 4 : SET_CLIENT_ERROR(result->conn->error_info, CR_COMMANDS_OUT_OF_SYNC,
826 : UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
827 4 : DBG_ERR("command out of sync");
828 4 : DBG_RETURN(FAIL);
829 : }
830 : /* Let the row packet fill our buffer and skip additional malloc + memcpy */
831 10959 : row_packet->skip_extraction = stmt && stmt->result_bind? FALSE:TRUE;
832 :
833 : /*
834 : If we skip rows (stmt == NULL || stmt->result_bind == NULL) we have to
835 : mysqlnd_unbuffered_free_last_data() before it. The function returns always true.
836 : */
837 20669 : if (PASS == (ret = PACKET_READ(row_packet, result->conn)) && !row_packet->eof) {
838 9710 : unsigned int i, field_count = result->field_count;
839 9710 : result->unbuf->row_count++;
840 9710 : *fetched_anything = TRUE;
841 :
842 9710 : if (!row_packet->skip_extraction) {
843 9546 : mysqlnd_unbuffered_free_last_data(result TSRMLS_CC);
844 :
845 9546 : DBG_INF("extracting data");
846 9546 : result->unbuf->last_row_data = row_packet->fields;
847 9546 : result->unbuf->last_row_buffer = row_packet->row_buffer;
848 9546 : row_packet->fields = NULL;
849 9546 : row_packet->row_buffer = NULL;
850 :
851 9546 : result->m.row_decoder(result->unbuf->last_row_buffer,
852 : result->unbuf->last_row_data,
853 : row_packet->field_count,
854 : row_packet->fields_metadata,
855 : result->conn TSRMLS_CC);
856 :
857 30606 : for (i = 0; i < field_count; i++) {
858 21060 : if (stmt->result_bind[i].bound == TRUE) {
859 21060 : zval *data = result->unbuf->last_row_data[i];
860 : /*
861 : stmt->result_bind[i].zv has been already destructed
862 : in mysqlnd_unbuffered_free_last_data()
863 : */
864 : #ifndef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF
865 21060 : zval_dtor(stmt->result_bind[i].zv);
866 : #endif
867 21060 : if (IS_NULL != (Z_TYPE_P(stmt->result_bind[i].zv) = Z_TYPE_P(data)) ) {
868 20478 : if (
869 : (Z_TYPE_P(data) == IS_STRING
870 : #if PHP_MAJOR_VERSION >= 6
871 : || Z_TYPE_P(data) == IS_UNICODE
872 : #endif
873 : )
874 : && (result->meta->fields[i].max_length < (unsigned long) Z_STRLEN_P(data)))
875 : {
876 988 : result->meta->fields[i].max_length = Z_STRLEN_P(data);
877 : }
878 20478 : stmt->result_bind[i].zv->value = data->value;
879 : // copied data, thus also the ownership. Thus null data
880 20478 : ZVAL_NULL(data);
881 : }
882 : }
883 : }
884 9546 : MYSQLND_INC_CONN_STATISTIC(&stmt->conn->stats, STAT_ROWS_FETCHED_FROM_CLIENT_PS_UNBUF);
885 : } else {
886 164 : DBG_INF("skipping extraction");
887 : /*
888 : Data has been allocated and usually mysqlnd_unbuffered_free_last_data()
889 : frees it but we can't call this function as it will cause problems with
890 : the bound variables. Thus we need to do part of what it does or Zend will
891 : report leaks.
892 : */
893 164 : row_packet->row_buffer->free_chunk(row_packet->row_buffer, TRUE TSRMLS_CC);
894 164 : row_packet->row_buffer = NULL;
895 : }
896 1249 : } else if (ret == FAIL) {
897 0 : *fetched_anything = FALSE;
898 0 : if (row_packet->error_info.error_no) {
899 0 : stmt->conn->error_info = row_packet->error_info;
900 0 : stmt->error_info = row_packet->error_info;
901 : }
902 0 : CONN_SET_STATE(result->conn, CONN_READY);
903 0 : result->unbuf->eof_reached = TRUE; /* so next time we won't get an error */
904 1249 : } else if (row_packet->eof) {
905 1249 : *fetched_anything = FALSE;
906 1249 : DBG_INF("EOF");
907 : /* Mark the connection as usable again */
908 1249 : result->unbuf->eof_reached = TRUE;
909 1249 : result->conn->upsert_status.warning_count = row_packet->warning_count;
910 1249 : result->conn->upsert_status.server_status = row_packet->server_status;
911 : /*
912 : result->row_packet will be cleaned when
913 : destroying the result object
914 : */
915 1249 : if (result->conn->upsert_status.server_status & SERVER_MORE_RESULTS_EXISTS) {
916 5 : CONN_SET_STATE(result->conn, CONN_NEXT_RESULT_PENDING);
917 : } else {
918 1244 : CONN_SET_STATE(result->conn, CONN_READY);
919 : }
920 : }
921 :
922 10959 : DBG_INF_FMT("ret=%s fetched_anything=%d", ret == PASS? "PASS":"FAIL", *fetched_anything);
923 10959 : DBG_RETURN(ret);
924 : }
925 : /* }}} */
926 :
927 :
928 : /* {{{ mysqlnd_stmt::use_result */
929 : static MYSQLND_RES *
930 : MYSQLND_METHOD(mysqlnd_stmt, use_result)(MYSQLND_STMT *stmt TSRMLS_DC)
931 1260 : {
932 : MYSQLND_RES *result;
933 1260 : MYSQLND *conn = stmt->conn;
934 :
935 1260 : DBG_ENTER("mysqlnd_stmt::use_result");
936 1260 : DBG_INF_FMT("stmt=%lu", stmt->stmt_id);
937 :
938 1260 : if (!stmt->field_count ||
939 : (!stmt->cursor_exists && CONN_GET_STATE(conn) != CONN_FETCHING_DATA) ||
940 : (stmt->cursor_exists && CONN_GET_STATE(conn) != CONN_READY) ||
941 : (stmt->state != MYSQLND_STMT_WAITING_USE_OR_STORE))
942 : {
943 3 : SET_CLIENT_ERROR(conn->error_info, CR_COMMANDS_OUT_OF_SYNC,
944 : UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
945 3 : DBG_ERR("command out of sync");
946 3 : DBG_RETURN(NULL);
947 : }
948 :
949 1257 : SET_EMPTY_ERROR(stmt->error_info);
950 :
951 1257 : MYSQLND_INC_CONN_STATISTIC(&stmt->conn->stats, STAT_PS_UNBUFFERED_SETS);
952 1257 : result = stmt->result;
953 :
954 1257 : DBG_INF_FMT("%scursor exists", stmt->cursor_exists? "":"no ");
955 1257 : result->m.use_result(stmt->result, TRUE TSRMLS_CC);
956 1257 : result->m.fetch_row = stmt->cursor_exists? mysqlnd_fetch_stmt_row_cursor:
957 : mysqlnd_stmt_fetch_row_unbuffered;
958 1257 : stmt->state = MYSQLND_STMT_USE_OR_STORE_CALLED;
959 :
960 1257 : DBG_INF_FMT("%p", result);
961 1257 : DBG_RETURN(result);
962 : }
963 : /* }}} */
964 :
965 :
966 : #define STMT_ID_LENGTH 4
967 :
968 : /* {{{ mysqlnd_fetch_row_cursor */
969 : enum_func_status
970 : mysqlnd_fetch_stmt_row_cursor(MYSQLND_RES *result, void *param, unsigned int flags,
971 : zend_bool *fetched_anything TSRMLS_DC)
972 28 : {
973 : enum_func_status ret;
974 28 : MYSQLND_STMT *stmt = (MYSQLND_STMT *) param;
975 : zend_uchar buf[STMT_ID_LENGTH /* statement id */ + 4 /* number of rows to fetch */];
976 28 : php_mysql_packet_row *row_packet = result->row_packet;
977 :
978 28 : DBG_ENTER("mysqlnd_fetch_stmt_row_cursor");
979 :
980 28 : if (!stmt) {
981 2 : DBG_ERR("no statement");
982 2 : DBG_RETURN(FAIL);
983 : }
984 :
985 26 : DBG_INF_FMT("stmt=%lu flags=%u", stmt->stmt_id, flags);
986 :
987 26 : if (stmt->state < MYSQLND_STMT_USER_FETCHING) {
988 : /* Only initted - error */
989 0 : SET_CLIENT_ERROR(stmt->conn->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE,
990 : mysqlnd_out_of_sync);
991 0 : DBG_ERR("command out of sync");
992 0 : DBG_RETURN(FAIL);
993 : }
994 :
995 26 : SET_EMPTY_ERROR(stmt->error_info);
996 26 : SET_EMPTY_ERROR(stmt->conn->error_info);
997 :
998 26 : int4store(buf, stmt->stmt_id);
999 26 : int4store(buf + STMT_ID_LENGTH, 1); /* for now fetch only one row */
1000 :
1001 26 : if (FAIL == mysqlnd_simple_command(stmt->conn, COM_STMT_FETCH, (char *)buf, sizeof(buf),
1002 : PROT_LAST /* we will handle the response packet*/,
1003 : FALSE, TRUE TSRMLS_CC)) {
1004 0 : stmt->error_info = stmt->conn->error_info;
1005 0 : DBG_RETURN(FAIL);
1006 : }
1007 :
1008 26 : row_packet->skip_extraction = stmt->result_bind? FALSE:TRUE;
1009 :
1010 50 : if (PASS == (ret = PACKET_READ(row_packet, result->conn)) && !row_packet->eof) {
1011 24 : unsigned int i, field_count = result->field_count;
1012 24 : result->unbuf->row_count++;
1013 24 : *fetched_anything = TRUE;
1014 :
1015 24 : DBG_INF_FMT("skip_extraction=%d", row_packet->skip_extraction);
1016 24 : if (!row_packet->skip_extraction) {
1017 24 : mysqlnd_unbuffered_free_last_data(result TSRMLS_CC);
1018 :
1019 24 : DBG_INF("extracting data");
1020 24 : result->unbuf->last_row_data = row_packet->fields;
1021 24 : result->unbuf->last_row_buffer = row_packet->row_buffer;
1022 24 : row_packet->fields = NULL;
1023 24 : row_packet->row_buffer = NULL;
1024 :
1025 24 : result->m.row_decoder(result->unbuf->last_row_buffer,
1026 : result->unbuf->last_row_data,
1027 : row_packet->field_count,
1028 : row_packet->fields_metadata,
1029 : result->conn TSRMLS_CC);
1030 :
1031 : /* If no result bind, do nothing. We consumed the data */
1032 54 : for (i = 0; i < field_count; i++) {
1033 30 : if (stmt->result_bind[i].bound == TRUE) {
1034 30 : zval *data = result->unbuf->last_row_data[i];
1035 : /*
1036 : stmt->result_bind[i].zv has been already destructed
1037 : in mysqlnd_unbuffered_free_last_data()
1038 : */
1039 : #ifndef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF
1040 30 : zval_dtor(stmt->result_bind[i].zv);
1041 : #endif
1042 30 : DBG_INF_FMT("i=%d bound_var=%p type=%d refc=%u", i, stmt->result_bind[i].zv,
1043 : Z_TYPE_P(data), Z_REFCOUNT_P(stmt->result_bind[i].zv));
1044 30 : if (IS_NULL != (Z_TYPE_P(stmt->result_bind[i].zv) = Z_TYPE_P(data))) {
1045 30 : if ((Z_TYPE_P(data) == IS_STRING
1046 : #if PHP_MAJOR_VERSION >= 6
1047 : || Z_TYPE_P(data) == IS_UNICODE
1048 : #endif
1049 : )
1050 : && (result->meta->fields[i].max_length < (unsigned long) Z_STRLEN_P(data)))
1051 : {
1052 1 : result->meta->fields[i].max_length = Z_STRLEN_P(data);
1053 : }
1054 30 : stmt->result_bind[i].zv->value = data->value;
1055 : // copied data, thus also the ownership. Thus null data
1056 30 : ZVAL_NULL(data);
1057 : }
1058 : }
1059 : }
1060 : } else {
1061 0 : DBG_INF("skipping extraction");
1062 : /*
1063 : Data has been allocated and usually mysqlnd_unbuffered_free_last_data()
1064 : frees it but we can't call this function as it will cause problems with
1065 : the bound variables. Thus we need to do part of what it does or Zend will
1066 : report leaks.
1067 : */
1068 0 : row_packet->row_buffer->free_chunk(row_packet->row_buffer, TRUE TSRMLS_CC);
1069 0 : row_packet->row_buffer = NULL;
1070 : }
1071 : /* We asked for one row, the next one should be EOF, eat it */
1072 24 : ret = PACKET_READ(row_packet, result->conn);
1073 24 : if (row_packet->row_buffer) {
1074 24 : row_packet->row_buffer->free_chunk(row_packet->row_buffer, TRUE TSRMLS_CC);
1075 24 : row_packet->row_buffer = NULL;
1076 : }
1077 24 : MYSQLND_INC_CONN_STATISTIC(&stmt->conn->stats, STAT_ROWS_FETCHED_FROM_CLIENT_PS_CURSOR);
1078 : } else {
1079 2 : *fetched_anything = FALSE;
1080 :
1081 2 : stmt->upsert_status.warning_count =
1082 : stmt->conn->upsert_status.warning_count =
1083 : row_packet->warning_count;
1084 :
1085 2 : stmt->upsert_status.server_status =
1086 : stmt->conn->upsert_status.server_status =
1087 : row_packet->server_status;
1088 :
1089 2 : result->unbuf->eof_reached = row_packet->eof;
1090 : }
1091 26 : stmt->upsert_status.warning_count =
1092 : stmt->conn->upsert_status.warning_count =
1093 : row_packet->warning_count;
1094 26 : stmt->upsert_status.server_status =
1095 : stmt->conn->upsert_status.server_status =
1096 : row_packet->server_status;
1097 :
1098 26 : DBG_INF_FMT("ret=%s fetched=%d server_status=%d warnings=%d eof=%d",
1099 : ret == PASS? "PASS":"FAIL", *fetched_anything,
1100 : row_packet->server_status, row_packet->warning_count,
1101 : result->unbuf->eof_reached);
1102 26 : DBG_RETURN(ret);
1103 : }
1104 : /* }}} */
1105 :
1106 :
1107 : /* {{{ mysqlnd_stmt::fetch */
1108 : static enum_func_status
1109 : MYSQLND_METHOD(mysqlnd_stmt, fetch)(MYSQLND_STMT * const stmt,
1110 : zend_bool * const fetched_anything TSRMLS_DC)
1111 11407 : {
1112 : enum_func_status ret;
1113 11407 : DBG_ENTER("mysqlnd_stmt::fetch");
1114 11407 : DBG_INF_FMT("stmt=%lu", stmt->stmt_id);
1115 :
1116 11407 : if (!stmt->result ||
1117 : stmt->state < MYSQLND_STMT_WAITING_USE_OR_STORE) {
1118 8 : SET_STMT_ERROR(stmt, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
1119 :
1120 8 : DBG_ERR("command out of sync");
1121 8 : DBG_RETURN(FAIL);
1122 11399 : } else if (stmt->state == MYSQLND_STMT_WAITING_USE_OR_STORE) {
1123 : /* Execute only once. We have to free the previous contents of user's bound vars */
1124 :
1125 1239 : stmt->default_rset_handler(stmt TSRMLS_CC);
1126 : }
1127 11399 : stmt->state = MYSQLND_STMT_USER_FETCHING;
1128 :
1129 11399 : SET_EMPTY_ERROR(stmt->error_info);
1130 11399 : SET_EMPTY_ERROR(stmt->conn->error_info);
1131 :
1132 11399 : DBG_INF_FMT("result_bind=%p separated_once=%d", stmt->result_bind, stmt->result_zvals_separated_once);
1133 : /*
1134 : The user might have not bound any variables for result.
1135 : Do the binding once she does it.
1136 : */
1137 11399 : if (stmt->result_bind && !stmt->result_zvals_separated_once) {
1138 : unsigned int i;
1139 : /*
1140 : mysqlnd_stmt_store_result() has been called free the bind
1141 : variables to prevent leaking of their previous content.
1142 : */
1143 7582 : for (i = 0; i < stmt->result->field_count; i++) {
1144 6052 : if (stmt->result_bind[i].bound == TRUE) {
1145 6052 : zval_dtor(stmt->result_bind[i].zv);
1146 6052 : ZVAL_NULL(stmt->result_bind[i].zv);
1147 : }
1148 : }
1149 1530 : stmt->result_zvals_separated_once = TRUE;
1150 : }
1151 :
1152 11399 : ret = stmt->result->m.fetch_row(stmt->result, (void*)stmt, 0, fetched_anything TSRMLS_CC);
1153 11399 : DBG_RETURN(ret);
1154 : }
1155 : /* }}} */
1156 :
1157 :
1158 : /* {{{ mysqlnd_stmt::reset */
1159 : static enum_func_status
1160 : MYSQLND_METHOD(mysqlnd_stmt, reset)(MYSQLND_STMT * const stmt TSRMLS_DC)
1161 5 : {
1162 5 : enum_func_status ret = PASS;
1163 : zend_uchar cmd_buf[STMT_ID_LENGTH /* statement id */];
1164 :
1165 5 : DBG_ENTER("mysqlnd_stmt::reset");
1166 5 : DBG_INF_FMT("stmt=%lu", stmt->stmt_id);
1167 :
1168 5 : SET_EMPTY_ERROR(stmt->error_info);
1169 5 : SET_EMPTY_ERROR(stmt->conn->error_info);
1170 :
1171 5 : if (stmt->stmt_id) {
1172 5 : MYSQLND * conn = stmt->conn;
1173 5 : if (stmt->param_bind) {
1174 : unsigned int i;
1175 2 : DBG_INF("resetting long data");
1176 : /* Reset Long Data */
1177 4 : for (i = 0; i < stmt->param_count; i++) {
1178 2 : if (stmt->param_bind[i].flags & MYSQLND_PARAM_BIND_BLOB_USED) {
1179 1 : stmt->param_bind[i].flags &= ~MYSQLND_PARAM_BIND_BLOB_USED;
1180 : }
1181 : }
1182 : }
1183 :
1184 : /*
1185 : If the user decided to close the statement right after execute()
1186 : We have to call the appropriate use_result() or store_result() and
1187 : clean.
1188 : */
1189 5 : if (stmt->state == MYSQLND_STMT_WAITING_USE_OR_STORE) {
1190 2 : DBG_INF("fetching result set header");
1191 2 : stmt->default_rset_handler(stmt TSRMLS_CC);
1192 2 : stmt->state = MYSQLND_STMT_USER_FETCHING;
1193 : }
1194 :
1195 5 : if (stmt->result) {
1196 4 : DBG_INF("skipping result");
1197 4 : stmt->result->m.skip_result(stmt->result TSRMLS_CC);
1198 : }
1199 :
1200 : /*
1201 : Don't free now, let the result be usable. When the stmt will again be
1202 : executed then the result set will be cleaned, the bound variables will
1203 : be separated before that.
1204 : */
1205 :
1206 5 : int4store(cmd_buf, stmt->stmt_id);
1207 5 : if (CONN_GET_STATE(conn) == CONN_READY &&
1208 : FAIL == (ret = mysqlnd_simple_command(conn, COM_STMT_RESET, (char *)cmd_buf,
1209 : sizeof(cmd_buf), PROT_OK_PACKET,
1210 : FALSE, TRUE TSRMLS_CC))) {
1211 0 : stmt->error_info = conn->error_info;
1212 : }
1213 5 : stmt->upsert_status = conn->upsert_status;
1214 :
1215 5 : stmt->state = MYSQLND_STMT_PREPARED;
1216 : }
1217 5 : DBG_INF(ret == PASS? "PASS":"FAIL");
1218 5 : DBG_RETURN(ret);
1219 : }
1220 : /* }}} */
1221 :
1222 :
1223 : /* {{{ mysqlnd_stmt::send_long_data */
1224 : static enum_func_status
1225 : MYSQLND_METHOD(mysqlnd_stmt, send_long_data)(MYSQLND_STMT * const stmt, unsigned int param_no,
1226 : const char * const data, unsigned long length TSRMLS_DC)
1227 16 : {
1228 16 : enum_func_status ret = FAIL;
1229 16 : MYSQLND * conn = stmt->conn;
1230 : zend_uchar *cmd_buf;
1231 16 : enum php_mysqlnd_server_command cmd = COM_STMT_SEND_LONG_DATA;
1232 :
1233 16 : DBG_ENTER("mysqlnd_stmt::send_long_data");
1234 16 : DBG_INF_FMT("stmt=%lu param_no=%d data_len=%lu", stmt->stmt_id, param_no, length);
1235 :
1236 16 : SET_EMPTY_ERROR(stmt->error_info);
1237 16 : SET_EMPTY_ERROR(stmt->conn->error_info);
1238 :
1239 16 : if (stmt->state < MYSQLND_STMT_PREPARED) {
1240 0 : SET_STMT_ERROR(stmt, CR_NO_PREPARE_STMT, UNKNOWN_SQLSTATE, mysqlnd_stmt_not_prepared);
1241 0 : DBG_ERR("not prepared");
1242 0 : DBG_RETURN(FAIL);
1243 : }
1244 16 : if (!stmt->param_bind) {
1245 0 : SET_STMT_ERROR(stmt, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
1246 0 : DBG_ERR("command out of sync");
1247 0 : DBG_RETURN(FAIL);
1248 : }
1249 :
1250 16 : if (param_no >= stmt->param_count) {
1251 1 : SET_STMT_ERROR(stmt, CR_INVALID_PARAMETER_NO, UNKNOWN_SQLSTATE, "Invalid parameter number");
1252 1 : DBG_ERR("invalid param_no");
1253 1 : DBG_RETURN(FAIL);
1254 : }
1255 15 : if (stmt->param_bind[param_no].type != MYSQL_TYPE_LONG_BLOB) {
1256 0 : SET_STMT_ERROR(stmt, CR_INVALID_BUFFER_USE, UNKNOWN_SQLSTATE, mysqlnd_not_bound_as_blob);
1257 0 : DBG_ERR("param_no is not of a blob type");
1258 0 : DBG_RETURN(FAIL);
1259 : }
1260 :
1261 : /*
1262 : XXX: Unfortunately we have to allocate additional buffer to be able the
1263 : additional data, which is like a header inside the payload.
1264 : This should be optimised, but it will be a pervasive change, so
1265 : mysqlnd_simple_command() will accept not a buffer, but actually MYSQLND_STRING*
1266 : terminated by NULL, to send. If the strings are not big, we can collapse them
1267 : on the buffer every connection has, but otherwise we will just send them
1268 : one by one to the wire.
1269 : */
1270 :
1271 15 : if (CONN_GET_STATE(conn) == CONN_READY) {
1272 : size_t packet_len;
1273 15 : stmt->param_bind[param_no].flags |= MYSQLND_PARAM_BIND_BLOB_USED;
1274 15 : cmd_buf = mnd_emalloc(packet_len = STMT_ID_LENGTH + 2 + length);
1275 :
1276 15 : int4store(cmd_buf, stmt->stmt_id);
1277 15 : int2store(cmd_buf + STMT_ID_LENGTH, param_no);
1278 15 : memcpy(cmd_buf + STMT_ID_LENGTH + 2, data, length);
1279 :
1280 : /* COM_STMT_SEND_LONG_DATA doesn't send an OK packet*/
1281 15 : ret = mysqlnd_simple_command(conn, cmd, (char *)cmd_buf, packet_len,
1282 : PROT_LAST , FALSE, TRUE TSRMLS_CC);
1283 15 : mnd_efree(cmd_buf);
1284 15 : if (FAIL == ret) {
1285 0 : stmt->error_info = conn->error_info;
1286 : }
1287 : /*
1288 : Cover protocol error: COM_STMT_SEND_LONG_DATA was designed to be quick and not
1289 : sent response packets. According to documentation the only way to get an error
1290 : is to have out-of-memory on the server-side. However, that's not true, as if
1291 : max_allowed_packet_size is smaller than the chunk being sent to the server, the
1292 : latter will complain with an error message. However, normally we don't expect
1293 : an error message, thus we continue. When sending the next command, which expects
1294 : response we will read the unexpected data and error message will look weird.
1295 : Therefore we do non-blocking read to clean the line, if there is a need.
1296 : Nevertheless, there is a built-in protection when sending a command packet, that
1297 : checks if the line is clear - useful for debug purposes and to be switched off
1298 : in release builds.
1299 :
1300 : Maybe we can make it automatic by checking what's the value of
1301 : max_allowed_packet_size on the server and resending the data.
1302 : */
1303 : #ifdef MYSQLND_DO_WIRE_CHECK_BEFORE_COMMAND
1304 : #if HAVE_USLEEP && !defined(PHP_WIN32)
1305 : usleep(120000);
1306 : #endif
1307 : if ((packet_len = php_mysqlnd_consume_uneaten_data(conn, cmd TSRMLS_CC))) {
1308 : php_error_docref(NULL TSRMLS_CC, E_WARNING, "There was an error "
1309 : "while sending long data. Probably max_allowed_packet_size "
1310 : "is smaller than the data. You have to increase it or send "
1311 : "smaller chunks of data. Answer was "MYSQLND_SZ_T_SPEC" bytes long.", packet_len);
1312 : SET_STMT_ERROR(stmt, CR_CONNECTION_ERROR, UNKNOWN_SQLSTATE,
1313 : "Server responded to COM_STMT_SEND_LONG_DATA.");
1314 : ret = FAIL;
1315 : }
1316 : #endif
1317 : }
1318 :
1319 15 : DBG_INF(ret == PASS? "PASS":"FAIL");
1320 15 : DBG_RETURN(ret);
1321 : }
1322 : /* }}} */
1323 :
1324 :
1325 : /* {{{ mysqlnd_stmt::bind_parameters */
1326 : static enum_func_status
1327 : MYSQLND_METHOD(mysqlnd_stmt, bind_parameters)(MYSQLND_STMT * const stmt,
1328 : MYSQLND_PARAM_BIND * const param_bind TSRMLS_DC)
1329 20870 : {
1330 20870 : DBG_ENTER("mysqlnd_stmt::bind_param");
1331 20870 : DBG_INF_FMT("stmt=%lu param_count=%u", stmt->stmt_id, stmt->param_count);
1332 :
1333 20870 : if (stmt->state < MYSQLND_STMT_PREPARED) {
1334 0 : SET_STMT_ERROR(stmt, CR_NO_PREPARE_STMT, UNKNOWN_SQLSTATE, mysqlnd_stmt_not_prepared);
1335 0 : DBG_ERR("not prepared");
1336 0 : if (param_bind && stmt->param_bind_dtor) {
1337 0 : stmt->param_bind_dtor(param_bind TSRMLS_CC);
1338 : }
1339 0 : DBG_RETURN(FAIL);
1340 : }
1341 :
1342 20870 : SET_EMPTY_ERROR(stmt->error_info);
1343 20870 : SET_EMPTY_ERROR(stmt->conn->error_info);
1344 :
1345 20870 : if (stmt->param_count) {
1346 20870 : unsigned int i = 0;
1347 :
1348 20870 : if (!param_bind) {
1349 0 : SET_STMT_ERROR(stmt, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE,
1350 : "Re-binding (still) not supported");
1351 0 : DBG_ERR("Re-binding (still) not supported");
1352 0 : DBG_RETURN(FAIL);
1353 20870 : } else if (stmt->param_bind) {
1354 20435 : DBG_INF("Binding");
1355 : /*
1356 : There is already result bound.
1357 : Forbid for now re-binding!!
1358 : */
1359 61305 : for (i = 0; i < stmt->param_count; i++) {
1360 : /*
1361 : We may have the last reference, then call zval_ptr_dtor()
1362 : or we may leak memory.
1363 : Switching from bind_one_parameter to bind_parameters may result in zv being NULL
1364 : */
1365 40870 : if (stmt->param_bind[i].zv) {
1366 40870 : zval_ptr_dtor(&stmt->param_bind[i].zv);
1367 : }
1368 : }
1369 20435 : if (stmt->param_bind != param_bind && stmt->param_bind_dtor) {
1370 20435 : stmt->param_bind_dtor(stmt->param_bind TSRMLS_CC);
1371 : }
1372 : }
1373 :
1374 20870 : stmt->param_bind = param_bind;
1375 62611 : for (i = 0; i < stmt->param_count; i++) {
1376 : /* The client will use stmt_send_long_data */
1377 41741 : DBG_INF_FMT("%d is of type %d", i, stmt->param_bind[i].type);
1378 : /* Prevent from freeing */
1379 : /* Don't update is_ref, or we will leak during conversion */
1380 41741 : Z_ADDREF_P(stmt->param_bind[i].zv);
1381 41741 : stmt->param_bind[i].flags = 0;
1382 41741 : if (stmt->param_bind[i].type == MYSQL_TYPE_LONG_BLOB) {
1383 24 : stmt->param_bind[i].flags &= ~MYSQLND_PARAM_BIND_BLOB_USED;
1384 : }
1385 : }
1386 20870 : stmt->send_types_to_server = 1;
1387 : }
1388 20870 : DBG_INF("PASS");
1389 20870 : DBG_RETURN(PASS);
1390 : }
1391 : /* }}} */
1392 :
1393 :
1394 : /* {{{ mysqlnd_stmt::bind_one_parameter */
1395 : static enum_func_status
1396 : MYSQLND_METHOD(mysqlnd_stmt, bind_one_parameter)(MYSQLND_STMT * const stmt, unsigned int param_no,
1397 : zval * const zv, zend_uchar type TSRMLS_DC)
1398 283 : {
1399 283 : DBG_ENTER("mysqlnd_stmt::bind_one_parameter");
1400 283 : DBG_INF_FMT("stmt=%lu param_no=%d param_count=%u type=%d",
1401 : stmt->stmt_id, param_no, stmt->param_count, type);
1402 :
1403 283 : if (stmt->state < MYSQLND_STMT_PREPARED) {
1404 0 : SET_STMT_ERROR(stmt, CR_NO_PREPARE_STMT, UNKNOWN_SQLSTATE, mysqlnd_stmt_not_prepared);
1405 0 : DBG_ERR("not prepared");
1406 0 : DBG_RETURN(FAIL);
1407 : }
1408 :
1409 283 : if (param_no < 0 || param_no >= stmt->param_count) {
1410 0 : SET_STMT_ERROR(stmt, CR_INVALID_PARAMETER_NO, UNKNOWN_SQLSTATE, "Invalid parameter number");
1411 0 : DBG_ERR("invalid param_no");
1412 0 : DBG_RETURN(FAIL);
1413 : }
1414 283 : SET_EMPTY_ERROR(stmt->error_info);
1415 283 : SET_EMPTY_ERROR(stmt->conn->error_info);
1416 :
1417 283 : if (stmt->param_count) {
1418 283 : if (!stmt->param_bind) {
1419 183 : stmt->param_bind = mnd_ecalloc(stmt->param_count, sizeof(MYSQLND_PARAM_BIND));
1420 : }
1421 :
1422 : /* Prevent from freeing */
1423 : /* Don't update is_ref, or we will leak during conversion */
1424 283 : Z_ADDREF_P(zv);
1425 283 : DBG_INF("Binding");
1426 : /* Release what we had, if we had */
1427 283 : if (stmt->param_bind[param_no].zv) {
1428 7 : zval_ptr_dtor(&stmt->param_bind[param_no].zv);
1429 : }
1430 283 : if (type == MYSQL_TYPE_LONG_BLOB) {
1431 : /* The client will use stmt_send_long_data */
1432 0 : stmt->param_bind[param_no].flags &= ~MYSQLND_PARAM_BIND_BLOB_USED;
1433 : }
1434 283 : stmt->param_bind[param_no].zv = zv;
1435 283 : stmt->param_bind[param_no].type = type;
1436 :
1437 283 : stmt->send_types_to_server = 1;
1438 : }
1439 283 : DBG_INF("PASS");
1440 283 : DBG_RETURN(PASS);
1441 : }
1442 : /* }}} */
1443 :
1444 :
1445 : /* {{{ mysqlnd_stmt::refresh_bind_param */
1446 : static enum_func_status
1447 : MYSQLND_METHOD(mysqlnd_stmt, refresh_bind_param)(MYSQLND_STMT * const stmt TSRMLS_DC)
1448 0 : {
1449 0 : DBG_ENTER("mysqlnd_stmt::refresh_bind_param");
1450 0 : DBG_INF_FMT("stmt=%lu param_count=%u", stmt->stmt_id, stmt->param_count);
1451 :
1452 0 : if (stmt->state < MYSQLND_STMT_PREPARED) {
1453 0 : SET_STMT_ERROR(stmt, CR_NO_PREPARE_STMT, UNKNOWN_SQLSTATE, mysqlnd_stmt_not_prepared);
1454 0 : DBG_ERR("not prepared");
1455 0 : DBG_RETURN(FAIL);
1456 : }
1457 :
1458 0 : SET_EMPTY_ERROR(stmt->error_info);
1459 0 : SET_EMPTY_ERROR(stmt->conn->error_info);
1460 :
1461 0 : if (stmt->param_count) {
1462 0 : stmt->send_types_to_server = 1;
1463 : }
1464 0 : DBG_INF("PASS");
1465 0 : DBG_RETURN(PASS);
1466 : }
1467 : /* }}} */
1468 :
1469 :
1470 : /* {{{ mysqlnd_stmt::set_bind_param_dtor */
1471 : static void
1472 : MYSQLND_METHOD(mysqlnd_stmt, set_param_bind_dtor)(MYSQLND_STMT * const stmt,
1473 : void (*param_bind_dtor)(MYSQLND_PARAM_BIND * dtor TSRMLS_DC) TSRMLS_DC)
1474 4098 : {
1475 4098 : DBG_ENTER("mysqlnd_stmt::set_bind_param_dtor");
1476 4098 : DBG_INF_FMT("stmt=%p", param_bind_dtor);
1477 4098 : stmt->param_bind_dtor = param_bind_dtor;
1478 : DBG_VOID_RETURN;
1479 : }
1480 : /* }}} */
1481 :
1482 :
1483 : /* {{{ mysqlnd_stmt::bind_result */
1484 : static enum_func_status
1485 : MYSQLND_METHOD(mysqlnd_stmt, bind_result)(MYSQLND_STMT * const stmt,
1486 : MYSQLND_RESULT_BIND * const result_bind TSRMLS_DC)
1487 1261 : {
1488 1261 : DBG_ENTER("mysqlnd_stmt::bind_result");
1489 1261 : DBG_INF_FMT("stmt=%lu field_count=%u", stmt->stmt_id, stmt->field_count);
1490 :
1491 :
1492 1261 : if (stmt->state < MYSQLND_STMT_PREPARED) {
1493 0 : SET_STMT_ERROR(stmt, CR_NO_PREPARE_STMT, UNKNOWN_SQLSTATE, mysqlnd_stmt_not_prepared);
1494 0 : if (result_bind && stmt->result_bind_dtor) {
1495 0 : stmt->result_bind_dtor(result_bind TSRMLS_CC);
1496 : }
1497 0 : DBG_ERR("not prepared");
1498 0 : DBG_RETURN(FAIL);
1499 : }
1500 :
1501 1261 : SET_EMPTY_ERROR(stmt->error_info);
1502 1261 : SET_EMPTY_ERROR(stmt->conn->error_info);
1503 :
1504 1261 : if (stmt->field_count) {
1505 1261 : unsigned int i = 0;
1506 :
1507 1261 : if (!result_bind) {
1508 0 : DBG_ERR("no result bind passed");
1509 0 : DBG_RETURN(FAIL);
1510 : }
1511 :
1512 1261 : mysqlnd_stmt_separate_result_bind(stmt TSRMLS_CC);
1513 1261 : stmt->result_zvals_separated_once = FALSE;
1514 1261 : stmt->result_bind = result_bind;
1515 6778 : for (i = 0; i < stmt->field_count; i++) {
1516 : /* Prevent from freeing */
1517 5517 : Z_ADDREF_P(stmt->result_bind[i].zv);
1518 5517 : DBG_INF_FMT("ref of %p = %u", stmt->result_bind[i].zv, Z_REFCOUNT_P(stmt->result_bind[i].zv));
1519 : /*
1520 : Don't update is_ref !!! it's not our job
1521 : Otherwise either 009.phpt or mysqli_stmt_bind_result.phpt
1522 : will fail.
1523 : */
1524 5517 : stmt->result_bind[i].bound = TRUE;
1525 : }
1526 0 : } else if (result_bind && stmt->result_bind_dtor) {
1527 0 : stmt->result_bind_dtor(result_bind TSRMLS_CC);
1528 : }
1529 1261 : DBG_INF("PASS");
1530 1261 : DBG_RETURN(PASS);
1531 : }
1532 : /* }}} */
1533 :
1534 :
1535 : /* {{{ mysqlnd_stmt::bind_result */
1536 : static enum_func_status
1537 : MYSQLND_METHOD(mysqlnd_stmt, bind_one_result)(MYSQLND_STMT * const stmt, unsigned int param_no TSRMLS_DC)
1538 755 : {
1539 755 : DBG_ENTER("mysqlnd_stmt::bind_result");
1540 755 : DBG_INF_FMT("stmt=%lu field_count=%u", stmt->stmt_id, stmt->field_count);
1541 :
1542 755 : if (stmt->state < MYSQLND_STMT_PREPARED) {
1543 0 : SET_STMT_ERROR(stmt, CR_NO_PREPARE_STMT, UNKNOWN_SQLSTATE, mysqlnd_stmt_not_prepared);
1544 0 : DBG_ERR("not prepared");
1545 0 : DBG_RETURN(FAIL);
1546 : }
1547 :
1548 755 : if (param_no < 0 || param_no >= stmt->field_count) {
1549 0 : SET_STMT_ERROR(stmt, CR_INVALID_PARAMETER_NO, UNKNOWN_SQLSTATE, "Invalid parameter number");
1550 0 : DBG_ERR("invalid param_no");
1551 0 : DBG_RETURN(FAIL);
1552 : }
1553 :
1554 755 : SET_EMPTY_ERROR(stmt->error_info);
1555 755 : SET_EMPTY_ERROR(stmt->conn->error_info);
1556 :
1557 755 : if (stmt->field_count) {
1558 755 : mysqlnd_stmt_separate_one_result_bind(stmt, param_no TSRMLS_CC);
1559 : /* Guaranteed is that stmt->result_bind is NULL */
1560 755 : if (!stmt->result_bind) {
1561 360 : stmt->result_bind = mnd_ecalloc(stmt->field_count, sizeof(MYSQLND_RESULT_BIND));
1562 : } else {
1563 395 : stmt->result_bind = mnd_erealloc(stmt->result_bind, stmt->field_count * sizeof(MYSQLND_RESULT_BIND));
1564 : }
1565 755 : ALLOC_INIT_ZVAL(stmt->result_bind[param_no].zv);
1566 : /*
1567 : Don't update is_ref !!! it's not our job
1568 : Otherwise either 009.phpt or mysqli_stmt_bind_result.phpt
1569 : will fail.
1570 : */
1571 755 : stmt->result_bind[param_no].bound = TRUE;
1572 : }
1573 755 : DBG_INF("PASS");
1574 755 : DBG_RETURN(PASS);
1575 : }
1576 : /* }}} */
1577 :
1578 :
1579 : /* {{{ mysqlnd_stmt::set_bind_result_dtor */
1580 : static void
1581 : MYSQLND_METHOD(mysqlnd_stmt, set_result_bind_dtor)(MYSQLND_STMT * const stmt,
1582 : void (*result_bind_dtor)(MYSQLND_RESULT_BIND * dtor TSRMLS_DC) TSRMLS_DC)
1583 4098 : {
1584 4098 : DBG_ENTER("mysqlnd_stmt::set_bind_param_dtor");
1585 4098 : DBG_INF_FMT("stmt=%p", result_bind_dtor);
1586 4098 : stmt->result_bind_dtor = result_bind_dtor;
1587 : DBG_VOID_RETURN;
1588 : }
1589 : /* }}} */
1590 :
1591 :
1592 : /* {{{ mysqlnd_stmt::insert_id */
1593 : static uint64_t
1594 : MYSQLND_METHOD(mysqlnd_stmt, insert_id)(const MYSQLND_STMT * const stmt)
1595 9 : {
1596 9 : return stmt->upsert_status.last_insert_id;
1597 : }
1598 : /* }}} */
1599 :
1600 :
1601 : /* {{{ mysqlnd_stmt::affected_rows */
1602 : static uint64_t
1603 : MYSQLND_METHOD(mysqlnd_stmt, affected_rows)(const MYSQLND_STMT * const stmt)
1604 621 : {
1605 621 : return stmt->upsert_status.affected_rows;
1606 : }
1607 : /* }}} */
1608 :
1609 :
1610 : /* {{{ mysqlnd_stmt::num_rows */
1611 : static uint64_t
1612 : MYSQLND_METHOD(mysqlnd_stmt, num_rows)(const MYSQLND_STMT * const stmt)
1613 24 : {
1614 24 : return stmt->result? mysqlnd_num_rows(stmt->result):0;
1615 : }
1616 : /* }}} */
1617 :
1618 :
1619 : /* {{{ mysqlnd_stmt::warning_count */
1620 : static unsigned int
1621 : MYSQLND_METHOD(mysqlnd_stmt, warning_count)(const MYSQLND_STMT * const stmt)
1622 3 : {
1623 3 : return stmt->upsert_status.warning_count;
1624 : }
1625 : /* }}} */
1626 :
1627 :
1628 : /* {{{ mysqlnd_stmt::field_count */
1629 : static unsigned int
1630 : MYSQLND_METHOD(mysqlnd_stmt, field_count)(const MYSQLND_STMT * const stmt)
1631 1689 : {
1632 1689 : return stmt->field_count;
1633 : }
1634 : /* }}} */
1635 :
1636 :
1637 : /* {{{ mysqlnd_stmt::param_count */
1638 : static unsigned int
1639 : MYSQLND_METHOD(mysqlnd_stmt, param_count)(const MYSQLND_STMT * const stmt)
1640 21604 : {
1641 21604 : return stmt->param_count;
1642 : }
1643 : /* }}} */
1644 :
1645 :
1646 : /* {{{ mysqlnd_stmt::errno */
1647 : static unsigned int
1648 : MYSQLND_METHOD(mysqlnd_stmt, errno)(const MYSQLND_STMT * const stmt)
1649 19 : {
1650 19 : return stmt->error_info.error_no;
1651 : }
1652 : /* }}} */
1653 :
1654 :
1655 : /* {{{ mysqlnd_stmt::error */
1656 : static const char *
1657 : MYSQLND_METHOD(mysqlnd_stmt, error)(const MYSQLND_STMT * const stmt)
1658 11 : {
1659 11 : return stmt->error_info.error;
1660 : }
1661 : /* }}} */
1662 :
1663 :
1664 : /* {{{ mysqlnd_stmt::sqlstate */
1665 : static const char *
1666 : MYSQLND_METHOD(mysqlnd_stmt, sqlstate)(const MYSQLND_STMT * const stmt)
1667 10 : {
1668 10 : return stmt->error_info.sqlstate[0] ? stmt->error_info.sqlstate:MYSQLND_SQLSTATE_NULL;
1669 : }
1670 : /* }}} */
1671 :
1672 :
1673 : /* {{{ mysqlnd_stmt::data_seek */
1674 : static enum_func_status
1675 : MYSQLND_METHOD(mysqlnd_stmt, data_seek)(const MYSQLND_STMT * const stmt, uint64_t row TSRMLS_DC)
1676 3 : {
1677 3 : return stmt->result? stmt->result->m.seek_data(stmt->result, row TSRMLS_CC) : FAIL;
1678 : }
1679 : /* }}} */
1680 :
1681 :
1682 : /* {{{ mysqlnd_stmt::param_metadata */
1683 : static MYSQLND_RES *
1684 : MYSQLND_METHOD(mysqlnd_stmt, param_metadata)(MYSQLND_STMT * const stmt)
1685 0 : {
1686 0 : if (!stmt->param_count) {
1687 0 : return NULL;
1688 : }
1689 :
1690 0 : return NULL;
1691 : }
1692 : /* }}} */
1693 :
1694 :
1695 : /* {{{ mysqlnd_stmt::result_metadata */
1696 : static MYSQLND_RES *
1697 : MYSQLND_METHOD(mysqlnd_stmt, result_metadata)(MYSQLND_STMT * const stmt TSRMLS_DC)
1698 811 : {
1699 : MYSQLND_RES *result;
1700 :
1701 811 : DBG_ENTER("mysqlnd_stmt::result_metadata");
1702 811 : DBG_INF_FMT("stmt=%u field_count=%u", stmt->stmt_id, stmt->field_count);
1703 :
1704 811 : if (!stmt->field_count || !stmt->conn || !stmt->result || !stmt->result->meta) {
1705 228 : DBG_INF("NULL");
1706 228 : DBG_RETURN(NULL);
1707 : }
1708 :
1709 583 : if (stmt->update_max_length && stmt->result->stored_data) {
1710 : /* stored result, we have to update the max_length before we clone the meta data :( */
1711 1 : mysqlnd_res_initialize_result_set_rest(stmt->result TSRMLS_CC);
1712 : }
1713 : /*
1714 : TODO: This implementation is kind of a hack,
1715 : find a better way to do it. In different functions I have put
1716 : fuses to check for result->m.fetch_row() being NULL. This should
1717 : be handled in a better way.
1718 :
1719 : In the meantime we don't need a zval cache reference for this fake
1720 : result set, so we don't get one.
1721 : */
1722 583 : result = mysqlnd_result_init(stmt->field_count, NULL TSRMLS_CC);
1723 583 : result->type = MYSQLND_RES_NORMAL;
1724 583 : result->m.fetch_row = result->m.fetch_row_normal_unbuffered;
1725 583 : result->unbuf = mnd_ecalloc(1, sizeof(MYSQLND_RES_UNBUFFERED));
1726 583 : result->unbuf->eof_reached = TRUE;
1727 583 : result->meta = stmt->result->meta->m->clone_metadata(stmt->result->meta, FALSE TSRMLS_CC);
1728 :
1729 583 : DBG_INF_FMT("result=%p", result);
1730 583 : DBG_RETURN(result);
1731 : }
1732 : /* }}} */
1733 :
1734 :
1735 : /* {{{ mysqlnd_stmt::attr_set */
1736 : static enum_func_status
1737 : MYSQLND_METHOD(mysqlnd_stmt, attr_set)(MYSQLND_STMT * const stmt,
1738 : enum mysqlnd_stmt_attr attr_type,
1739 : const void * const value TSRMLS_DC)
1740 1110 : {
1741 1110 : unsigned long val = *(unsigned long *) value;
1742 1110 : DBG_ENTER("mysqlnd_stmt::attr_set");
1743 1110 : DBG_INF_FMT("stmt=%lu attr_type=%u value=%lu", stmt->stmt_id, attr_type, val);
1744 :
1745 1110 : switch (attr_type) {
1746 : case STMT_ATTR_UPDATE_MAX_LENGTH:
1747 : /*
1748 : XXX : libmysql uses my_bool, but mysqli uses ulong as storage on the stack
1749 : and mysqlnd won't be used out of the scope of PHP -> use ulong.
1750 : */
1751 2 : stmt->update_max_length = val? TRUE:FALSE;
1752 2 : break;
1753 : case STMT_ATTR_CURSOR_TYPE: {
1754 10 : if (val > (unsigned long) CURSOR_TYPE_READ_ONLY) {
1755 3 : SET_STMT_ERROR(stmt, CR_NOT_IMPLEMENTED, UNKNOWN_SQLSTATE, "Not implemented");
1756 3 : DBG_INF("FAIL");
1757 3 : DBG_RETURN(FAIL);
1758 : }
1759 7 : stmt->flags = val;
1760 7 : break;
1761 : }
1762 : case STMT_ATTR_PREFETCH_ROWS: {
1763 0 : if (val == 0) {
1764 0 : val = MYSQLND_DEFAULT_PREFETCH_ROWS;
1765 0 : } else if (val > 1) {
1766 0 : SET_STMT_ERROR(stmt, CR_NOT_IMPLEMENTED, UNKNOWN_SQLSTATE, "Not implemented");
1767 0 : DBG_INF("FAIL");
1768 0 : DBG_RETURN(FAIL);
1769 : }
1770 0 : stmt->prefetch_rows = val;
1771 0 : break;
1772 : }
1773 : default:
1774 1098 : SET_STMT_ERROR(stmt, CR_NOT_IMPLEMENTED, UNKNOWN_SQLSTATE, "Not implemented");
1775 1098 : DBG_RETURN(FAIL);
1776 : }
1777 9 : DBG_INF("PASS");
1778 9 : DBG_RETURN(PASS);
1779 : }
1780 : /* }}} */
1781 :
1782 :
1783 : /* {{{ mysqlnd_stmt::attr_get */
1784 : static enum_func_status
1785 : MYSQLND_METHOD(mysqlnd_stmt, attr_get)(const MYSQLND_STMT * const stmt,
1786 : enum mysqlnd_stmt_attr attr_type,
1787 : void * const value TSRMLS_DC)
1788 3 : {
1789 3 : DBG_ENTER("mysqlnd_stmt::attr_set");
1790 3 : DBG_INF_FMT("stmt=%lu attr_type=%u", stmt->stmt_id, attr_type);
1791 :
1792 3 : switch (attr_type) {
1793 : case STMT_ATTR_UPDATE_MAX_LENGTH:
1794 1 : *(zend_bool *) value= stmt->update_max_length;
1795 1 : break;
1796 : case STMT_ATTR_CURSOR_TYPE:
1797 1 : *(unsigned long *) value= stmt->flags;
1798 1 : break;
1799 : case STMT_ATTR_PREFETCH_ROWS:
1800 0 : *(unsigned long *) value= stmt->prefetch_rows;
1801 0 : break;
1802 : default:
1803 1 : DBG_RETURN(FAIL);
1804 : }
1805 2 : DBG_INF_FMT("value=%lu", value);
1806 2 : DBG_RETURN(PASS);
1807 : }
1808 : /* }}} */
1809 :
1810 : /* free_result() doesn't actually free stmt->result but only the buffers */
1811 : /* {{{ mysqlnd_stmt::free_result */
1812 : static enum_func_status
1813 : MYSQLND_METHOD(mysqlnd_stmt, free_result)(MYSQLND_STMT * const stmt TSRMLS_DC)
1814 258 : {
1815 258 : DBG_ENTER("mysqlnd_stmt::free_result");
1816 258 : DBG_INF_FMT("stmt=%lu", stmt->stmt_id);
1817 :
1818 258 : if (!stmt->result) {
1819 6 : DBG_INF("no result");
1820 6 : DBG_RETURN(PASS);
1821 : }
1822 :
1823 : /*
1824 : If right after execute() we have to call the appropriate
1825 : use_result() or store_result() and clean.
1826 : */
1827 252 : if (stmt->state == MYSQLND_STMT_WAITING_USE_OR_STORE) {
1828 3 : DBG_INF("fetching result set header");
1829 : /* Do implicit use_result and then flush the result */
1830 3 : stmt->default_rset_handler = stmt->m->use_result;
1831 3 : stmt->default_rset_handler(stmt TSRMLS_CC);
1832 : }
1833 :
1834 252 : if (stmt->state > MYSQLND_STMT_WAITING_USE_OR_STORE) {
1835 251 : DBG_INF("skipping result");
1836 : /* Flush if anything is left and unbuffered set */
1837 251 : stmt->result->m.skip_result(stmt->result TSRMLS_CC);
1838 : /*
1839 : Separate the bound variables, which point to the result set, then
1840 : destroy the set.
1841 : */
1842 251 : mysqlnd_stmt_separate_result_bind(stmt TSRMLS_CC);
1843 :
1844 : /* Now we can destroy the result set */
1845 251 : stmt->result->m.free_result_buffers(stmt->result TSRMLS_CC);
1846 : }
1847 :
1848 252 : if (stmt->state > MYSQLND_STMT_PREPARED) {
1849 : /* As the buffers have been freed, we should go back to PREPARED */
1850 251 : stmt->state = MYSQLND_STMT_PREPARED;
1851 : }
1852 :
1853 : /* Line is free! */
1854 252 : CONN_SET_STATE(stmt->conn, CONN_READY);
1855 :
1856 252 : DBG_RETURN(PASS);
1857 : }
1858 : /* }}} */
1859 :
1860 :
1861 : /* {{{ mysqlnd_stmt_separate_result_bind */
1862 : void mysqlnd_stmt_separate_result_bind(MYSQLND_STMT * const stmt TSRMLS_DC)
1863 5627 : {
1864 : unsigned int i;
1865 :
1866 5627 : DBG_ENTER("mysqlnd_stmt_separate_result_bind");
1867 5627 : DBG_INF_FMT("stmt=%lu result_bind=%p field_count=%u",
1868 : stmt->stmt_id, stmt->result_bind, stmt->field_count);
1869 :
1870 5627 : if (!stmt->result_bind) {
1871 4006 : DBG_VOID_RETURN;
1872 : }
1873 :
1874 : /*
1875 : Because only the bound variables can point to our internal buffers, then
1876 : separate or free only them. Free is possible because the user could have
1877 : lost reference.
1878 : */
1879 7854 : for (i = 0; i < stmt->field_count; i++) {
1880 : /* Let's try with no cache */
1881 6233 : if (stmt->result_bind[i].bound == TRUE) {
1882 6233 : DBG_INF_FMT("%d has refcount=%u", i, Z_REFCOUNT_P(stmt->result_bind[i].zv));
1883 : /*
1884 : We have to separate the actual zval value of the bound
1885 : variable from our allocated zvals or we will face double-free
1886 : */
1887 6233 : if (Z_REFCOUNT_P(stmt->result_bind[i].zv) > 1) {
1888 : #ifdef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF
1889 : zval_copy_ctor(stmt->result_bind[i].zv);
1890 : #endif
1891 5507 : zval_ptr_dtor(&stmt->result_bind[i].zv);
1892 : } else {
1893 : /*
1894 : If it is a string, what is pointed will be freed
1895 : later in free_result(). We need to remove the variable to
1896 : which the user has lost reference.
1897 : */
1898 : #ifdef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF
1899 : ZVAL_NULL(stmt->result_bind[i].zv);
1900 : #endif
1901 726 : zval_ptr_dtor(&stmt->result_bind[i].zv);
1902 : }
1903 : }
1904 : }
1905 1621 : if (stmt->result_bind_dtor) {
1906 1621 : stmt->result_bind_dtor(stmt->result_bind TSRMLS_CC);
1907 : }
1908 1621 : stmt->result_bind = NULL;
1909 :
1910 1621 : DBG_VOID_RETURN;
1911 : }
1912 : /* }}} */
1913 :
1914 :
1915 : /* {{{ mysqlnd_stmt_separate_one_result_bind */
1916 : void mysqlnd_stmt_separate_one_result_bind(MYSQLND_STMT * const stmt, unsigned int param_no TSRMLS_DC)
1917 755 : {
1918 755 : DBG_ENTER("mysqlnd_stmt_separate_one_result_bind");
1919 755 : DBG_INF_FMT("stmt=%lu result_bind=%p field_count=%u param_no=%d",
1920 : stmt->stmt_id, stmt->result_bind, stmt->field_count, param_no);
1921 :
1922 755 : if (!stmt->result_bind) {
1923 360 : DBG_VOID_RETURN;
1924 : }
1925 :
1926 : /*
1927 : Because only the bound variables can point to our internal buffers, then
1928 : separate or free only them. Free is possible because the user could have
1929 : lost reference.
1930 : */
1931 : /* Let's try with no cache */
1932 395 : if (stmt->result_bind[param_no].bound == TRUE) {
1933 39 : DBG_INF_FMT("%d has refcount=%u", param_no, Z_REFCOUNT_P(stmt->result_bind[param_no].zv));
1934 : /*
1935 : We have to separate the actual zval value of the bound
1936 : variable from our allocated zvals or we will face double-free
1937 : */
1938 39 : if (Z_REFCOUNT_P(stmt->result_bind[param_no].zv) > 1) {
1939 : #ifdef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF
1940 : zval_copy_ctor(stmt->result_bind[param_no].zv);
1941 : #endif
1942 0 : zval_ptr_dtor(&stmt->result_bind[param_no].zv);
1943 : } else {
1944 : /*
1945 : If it is a string, what is pointed will be freed
1946 : later in free_result(). We need to remove the variable to
1947 : which the user has lost reference.
1948 : */
1949 : #ifdef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF
1950 : ZVAL_NULL(stmt->result_bind[param_no].zv);
1951 : #endif
1952 39 : zval_ptr_dtor(&stmt->result_bind[param_no].zv);
1953 : }
1954 : }
1955 :
1956 395 : DBG_VOID_RETURN;
1957 : }
1958 : /* }}} */
1959 :
1960 :
1961 : /* {{{ mysqlnd_internal_free_stmt_content */
1962 : static
1963 : void mysqlnd_internal_free_stmt_content(MYSQLND_STMT * const stmt TSRMLS_DC)
1964 4115 : {
1965 4115 : DBG_ENTER("mysqlnd_internal_free_stmt_content");
1966 4115 : DBG_INF_FMT("stmt=%lu param_bind=%p param_count=%u",
1967 : stmt->stmt_id, stmt->param_bind, stmt->param_count);
1968 :
1969 : /* Destroy the input bind */
1970 4115 : if (stmt->param_bind) {
1971 : unsigned int i;
1972 : /*
1973 : Because only the bound variables can point to our internal buffers, then
1974 : separate or free only them. Free is possible because the user could have
1975 : lost reference.
1976 : */
1977 1766 : for (i = 0; i < stmt->param_count; i++) {
1978 : /*
1979 : If bind_one_parameter was used, but not everything was
1980 : bound and nothing was fetched, then some `zv` could be NULL
1981 : */
1982 1148 : if (stmt->param_bind[i].zv) {
1983 1147 : zval_ptr_dtor(&stmt->param_bind[i].zv);
1984 : }
1985 : }
1986 618 : if (stmt->param_bind_dtor) {
1987 618 : stmt->param_bind_dtor(stmt->param_bind TSRMLS_CC);
1988 : }
1989 618 : stmt->param_bind = NULL;
1990 : }
1991 :
1992 : /*
1993 : First separate the bound variables, which point to the result set, then
1994 : destroy the set.
1995 : */
1996 4115 : mysqlnd_stmt_separate_result_bind(stmt TSRMLS_CC);
1997 : /* Not every statement has a result set attached */
1998 4115 : if (stmt->result) {
1999 2377 : stmt->result->m.free_result_internal(stmt->result TSRMLS_CC);
2000 2377 : stmt->result = NULL;
2001 : }
2002 :
2003 : DBG_VOID_RETURN;
2004 : }
2005 : /* }}} */
2006 :
2007 :
2008 : /* {{{ mysqlnd_stmt::net_close */
2009 : static enum_func_status
2010 : MYSQLND_METHOD_PRIVATE(mysqlnd_stmt, net_close)(MYSQLND_STMT * const stmt, zend_bool implicit TSRMLS_DC)
2011 4098 : {
2012 4098 : MYSQLND * conn = stmt->conn;
2013 : zend_uchar cmd_buf[STMT_ID_LENGTH /* statement id */];
2014 4098 : enum_mysqlnd_collected_stats stat = STAT_LAST;
2015 :
2016 4098 : DBG_ENTER("mysqlnd_stmt::net_close");
2017 4098 : DBG_INF_FMT("stmt=%lu", stmt->stmt_id);
2018 :
2019 4098 : SET_EMPTY_ERROR(stmt->error_info);
2020 4098 : SET_EMPTY_ERROR(stmt->conn->error_info);
2021 :
2022 : /*
2023 : If the user decided to close the statement right after execute()
2024 : We have to call the appropriate use_result() or store_result() and
2025 : clean.
2026 : */
2027 : do {
2028 4099 : DBG_INF_FMT("stmt->state=%d", stmt->state);
2029 4099 : if (stmt->state == MYSQLND_STMT_WAITING_USE_OR_STORE) {
2030 15 : DBG_INF("fetching result set header");
2031 15 : stmt->default_rset_handler(stmt TSRMLS_CC);
2032 15 : stmt->state = MYSQLND_STMT_USER_FETCHING;
2033 : }
2034 :
2035 : /* unbuffered set not fetched to the end ? Clean the line */
2036 4099 : if (stmt->result) {
2037 2361 : DBG_INF("skipping result");
2038 2361 : stmt->result->m.skip_result(stmt->result TSRMLS_CC);
2039 : }
2040 4099 : } while (mysqlnd_stmt_more_results(stmt) && mysqlnd_stmt_next_result(stmt) == PASS);
2041 : /*
2042 : After this point we are allowed to free the result set,
2043 : as we have cleaned the line
2044 : */
2045 4098 : if (stmt->stmt_id) {
2046 4069 : MYSQLND_INC_GLOBAL_STATISTIC(implicit == TRUE? STAT_FREE_RESULT_IMPLICIT:
2047 : STAT_FREE_RESULT_EXPLICIT);
2048 :
2049 4069 : int4store(cmd_buf, stmt->stmt_id);
2050 4069 : if (CONN_GET_STATE(conn) == CONN_READY &&
2051 : FAIL == mysqlnd_simple_command(conn, COM_STMT_CLOSE, (char *)cmd_buf, sizeof(cmd_buf),
2052 : PROT_LAST /* COM_STMT_CLOSE doesn't send an OK packet*/,
2053 : FALSE, TRUE TSRMLS_CC)) {
2054 0 : stmt->error_info = conn->error_info;
2055 0 : DBG_RETURN(FAIL);
2056 : }
2057 : }
2058 4098 : switch (stmt->execute_count) {
2059 : case 0:
2060 260 : stat = STAT_PS_PREPARED_NEVER_EXECUTED;
2061 260 : break;
2062 : case 1:
2063 3521 : stat = STAT_PS_PREPARED_ONCE_USED;
2064 : break;
2065 : default:
2066 : break;
2067 : }
2068 4098 : if (stat != STAT_LAST) {
2069 3781 : MYSQLND_INC_CONN_STATISTIC(&conn->stats, stat);
2070 : }
2071 :
2072 4098 : if (stmt->execute_cmd_buffer.buffer) {
2073 4098 : mnd_efree(stmt->execute_cmd_buffer.buffer);
2074 4098 : stmt->execute_cmd_buffer.buffer = NULL;
2075 : }
2076 :
2077 4098 : mysqlnd_internal_free_stmt_content(stmt TSRMLS_CC);
2078 :
2079 4098 : if (stmt->conn) {
2080 4098 : stmt->conn->m->free_reference(stmt->conn TSRMLS_CC);
2081 4098 : stmt->conn = NULL;
2082 : }
2083 :
2084 4098 : DBG_RETURN(PASS);
2085 : }
2086 : /* }}} */
2087 :
2088 : /* {{{ mysqlnd_stmt::dtor */
2089 : static enum_func_status
2090 : MYSQLND_METHOD(mysqlnd_stmt, dtor)(MYSQLND_STMT * const stmt, zend_bool implicit TSRMLS_DC)
2091 1678 : {
2092 : enum_func_status ret;
2093 :
2094 1678 : DBG_ENTER("mysqlnd_stmt::dtor");
2095 1678 : DBG_INF_FMT("stmt=%p", stmt);
2096 :
2097 1678 : MYSQLND_INC_GLOBAL_STATISTIC(implicit == TRUE? STAT_STMT_CLOSE_IMPLICIT:
2098 : STAT_STMT_CLOSE_EXPLICIT);
2099 :
2100 1678 : ret = stmt->m->net_close(stmt, implicit TSRMLS_CC);
2101 1678 : mnd_efree(stmt);
2102 :
2103 1678 : DBG_INF(ret == PASS? "PASS":"FAIL");
2104 1678 : DBG_RETURN(ret);
2105 : }
2106 : /* }}} */
2107 :
2108 :
2109 : MYSQLND_CLASS_METHODS_START(mysqlnd_stmt)
2110 : MYSQLND_METHOD(mysqlnd_stmt, prepare),
2111 : MYSQLND_METHOD(mysqlnd_stmt, execute),
2112 : MYSQLND_METHOD(mysqlnd_stmt, use_result),
2113 : MYSQLND_METHOD(mysqlnd_stmt, store_result),
2114 : MYSQLND_METHOD(mysqlnd_stmt, background_store_result),
2115 : MYSQLND_METHOD(mysqlnd_stmt, get_result),
2116 : MYSQLND_METHOD(mysqlnd_stmt, more_results),
2117 : MYSQLND_METHOD(mysqlnd_stmt, next_result),
2118 : MYSQLND_METHOD(mysqlnd_stmt, free_result),
2119 : MYSQLND_METHOD(mysqlnd_stmt, data_seek),
2120 : MYSQLND_METHOD(mysqlnd_stmt, reset),
2121 : MYSQLND_METHOD_PRIVATE(mysqlnd_stmt, net_close),
2122 : MYSQLND_METHOD(mysqlnd_stmt, dtor),
2123 :
2124 : MYSQLND_METHOD(mysqlnd_stmt, fetch),
2125 :
2126 : MYSQLND_METHOD(mysqlnd_stmt, bind_parameters),
2127 : MYSQLND_METHOD(mysqlnd_stmt, bind_one_parameter),
2128 : MYSQLND_METHOD(mysqlnd_stmt, refresh_bind_param),
2129 : MYSQLND_METHOD(mysqlnd_stmt, set_param_bind_dtor),
2130 : MYSQLND_METHOD(mysqlnd_stmt, bind_result),
2131 : MYSQLND_METHOD(mysqlnd_stmt, bind_one_result),
2132 : MYSQLND_METHOD(mysqlnd_stmt, set_result_bind_dtor),
2133 : MYSQLND_METHOD(mysqlnd_stmt, send_long_data),
2134 : MYSQLND_METHOD(mysqlnd_stmt, param_metadata),
2135 : MYSQLND_METHOD(mysqlnd_stmt, result_metadata),
2136 :
2137 : MYSQLND_METHOD(mysqlnd_stmt, insert_id),
2138 : MYSQLND_METHOD(mysqlnd_stmt, affected_rows),
2139 : MYSQLND_METHOD(mysqlnd_stmt, num_rows),
2140 :
2141 : MYSQLND_METHOD(mysqlnd_stmt, param_count),
2142 : MYSQLND_METHOD(mysqlnd_stmt, field_count),
2143 : MYSQLND_METHOD(mysqlnd_stmt, warning_count),
2144 :
2145 : MYSQLND_METHOD(mysqlnd_stmt, errno),
2146 : MYSQLND_METHOD(mysqlnd_stmt, error),
2147 : MYSQLND_METHOD(mysqlnd_stmt, sqlstate),
2148 :
2149 : MYSQLND_METHOD(mysqlnd_stmt, attr_get),
2150 : MYSQLND_METHOD(mysqlnd_stmt, attr_set),
2151 : MYSQLND_CLASS_METHODS_END;
2152 :
2153 :
2154 : /* {{{ _mysqlnd_stmt_init */
2155 : MYSQLND_STMT * _mysqlnd_stmt_init(MYSQLND * const conn TSRMLS_DC)
2156 4098 : {
2157 4098 : MYSQLND_STMT *stmt = mnd_ecalloc(1, sizeof(MYSQLND_STMT));
2158 :
2159 4098 : DBG_ENTER("_mysqlnd_stmt_init");
2160 4098 : DBG_INF_FMT("stmt=%p", stmt);
2161 :
2162 4098 : stmt->m = mysqlnd_stmt_methods;
2163 4098 : stmt->state = MYSQLND_STMT_INITTED;
2164 4098 : stmt->execute_cmd_buffer.length = 4096;
2165 4098 : stmt->execute_cmd_buffer.buffer = mnd_emalloc(stmt->execute_cmd_buffer.length);
2166 :
2167 4098 : stmt->prefetch_rows = MYSQLND_DEFAULT_PREFETCH_ROWS;
2168 : /*
2169 : Mark that we reference the connection, thus it won't be
2170 : be destructed till there is open statements. The last statement
2171 : or normal query result will close it then.
2172 : */
2173 4098 : stmt->conn = conn->m->get_reference(conn TSRMLS_CC);
2174 :
2175 4098 : stmt->m->set_param_bind_dtor(stmt, mysqlnd_efree_param_bind_dtor TSRMLS_CC);
2176 4098 : stmt->m->set_result_bind_dtor(stmt, mysqlnd_efree_result_bind_dtor TSRMLS_CC);
2177 :
2178 4098 : DBG_RETURN(stmt);
2179 : }
2180 : /* }}} */
2181 :
2182 :
2183 : /* {{{ mysqlnd_efree_param_bind_dtor */
2184 : PHPAPI void
2185 : mysqlnd_efree_param_bind_dtor(MYSQLND_PARAM_BIND * param_bind TSRMLS_DC)
2186 21053 : {
2187 21053 : mnd_efree(param_bind);
2188 21053 : }
2189 : /* }}} */
2190 :
2191 :
2192 : /* {{{ mysqlnd_efree_result_bind_dtor */
2193 : PHPAPI void
2194 : mysqlnd_efree_result_bind_dtor(MYSQLND_RESULT_BIND * result_bind TSRMLS_DC)
2195 1621 : {
2196 1621 : mnd_efree(result_bind);
2197 1621 : }
2198 : /* }}} */
2199 :
2200 : /* {{{ _mysqlnd_init_ps_subsystem */
2201 : void _mysqlnd_init_ps_subsystem()
2202 17633 : {
2203 17633 : mysqlnd_stmt_methods = &MYSQLND_CLASS_METHOD_TABLE_NAME(mysqlnd_stmt);
2204 17633 : _mysqlnd_init_ps_fetch_subsystem();
2205 17633 : }
2206 : /* }}} */
2207 :
2208 : /* {{{ mysqlnd_conn_get_methods */
2209 : PHPAPI struct st_mysqlnd_stmt_methods * mysqlnd_stmt_get_methods()
2210 0 : {
2211 0 : return mysqlnd_stmt_methods;
2212 : }
2213 : /* }}} */
2214 :
2215 : /* {{{ mysqlnd_conn_set_methods */
2216 : PHPAPI void mysqlnd_stmt_set_methods(struct st_mysqlnd_stmt_methods *methods)
2217 0 : {
2218 0 : mysqlnd_stmt_methods = methods;
2219 0 : }
2220 : /* }}} */
2221 :
2222 :
2223 : /*
2224 : * Local variables:
2225 : * tab-width: 4
2226 : * c-basic-offset: 4
2227 : * End:
2228 : * vim600: noet sw=4 ts=4 fdm=marker
2229 : * vim<600: noet sw=4 ts=4
2230 : */
|